mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 04:49:33 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
edc2f852a1 | ||
|
|
348e44a9c4 | ||
|
|
42f663cac0 | ||
|
|
4dd5d2f8cb | ||
|
|
4cdfc7e72c | ||
|
|
9516da1751 | ||
|
|
f3de82b2a2 | ||
|
|
c4c506c1cb | ||
|
|
0c62a91ded | ||
|
|
2fd0a4c60a | ||
|
|
d3188e39df | ||
|
|
3229b5b7e3 | ||
|
|
d9ede96aca | ||
|
|
4b4219fc33 | ||
|
|
02ead29989 | ||
|
|
37919009ab | ||
|
|
782ff4f582 | ||
|
|
3c468c71a4 | ||
|
|
a31679cf04 | ||
|
|
9cd65ed152 | ||
|
|
79ad405319 | ||
|
|
b60a164fcc | ||
|
|
2d4ba3ac74 | ||
|
|
42eabf9f71 | ||
|
|
aa4b3f7d01 | ||
|
|
2d121b8d6d | ||
|
|
c86a59a511 | ||
|
|
f7dea3a21b | ||
|
|
651ae8a2a8 | ||
|
|
5f7aca6276 | ||
|
|
39cdf61695 | ||
|
|
5c4955763b | ||
|
|
dae070283b | ||
|
|
403655904b | ||
|
|
44942b638b | ||
|
|
ceffd4679b | ||
|
|
6fa742be67 | ||
|
|
484e357971 | ||
|
|
53a5a37370 | ||
|
|
41579888b6 | ||
|
|
1e2d84038e | ||
|
|
6c7700b051 | ||
|
|
34c53777fb | ||
|
|
7be1b66791 | ||
|
|
4bd80ec21c | ||
|
|
a9ab0ff452 | ||
|
|
206bf65c50 | ||
|
|
2b3e8eec05 | ||
|
|
9eac818254 | ||
|
|
6fe0cadfa1 | ||
|
|
1a2baee096 | ||
|
|
728b265cbd | ||
|
|
9fa61b9298 | ||
|
|
6ad942cb27 | ||
|
|
eed4496af2 | ||
|
|
17c292c2b4 | ||
|
|
ab1ab78f8a | ||
|
|
8506065fd2 | ||
|
|
a291d0d448 | ||
|
|
690fefb97d | ||
|
|
c7c1779ad7 | ||
|
|
ec095e7513 | ||
|
|
69a91e8aa0 | ||
|
|
af46000645 | ||
|
|
b1658c4702 | ||
|
|
9f85267b44 | ||
|
|
f8d20cd3ce | ||
|
|
a396fa400f | ||
|
|
d8a4f14fd0 | ||
|
|
c8b5fae2d0 | ||
|
|
0ce32d36d5 | ||
|
|
01cd5d1a32 | ||
|
|
39a25e2394 | ||
|
|
bfdd75ed38 | ||
|
|
5411f4216e | ||
|
|
3cf00e30d5 | ||
|
|
0f714710f0 | ||
|
|
83ec409426 | ||
|
|
e6ae83fbf9 | ||
|
|
00702773f4 | ||
|
|
1739cfbfb4 | ||
|
|
ff689a1253 | ||
|
|
ae427d133e | ||
|
|
d92af2456d | ||
|
|
b3e1092879 | ||
|
|
46ef71eb75 | ||
|
|
871ef8cf14 | ||
|
|
c1b1caa854 | ||
|
|
dcab76697e | ||
|
|
cf11efb147 | ||
|
|
a688b73f8e | ||
|
|
9bb50ae7a5 | ||
|
|
ead6235470 | ||
|
|
967006479f | ||
|
|
f843daee9b | ||
|
|
c7129916ee | ||
|
|
e234ce25b6 | ||
|
|
02e1b9de10 | ||
|
|
effde5ffa9 | ||
|
|
1d3706285e | ||
|
|
085a1148fd | ||
|
|
5cf3e53ab9 | ||
|
|
a5aa387cb4 | ||
|
|
f99cb33c2d | ||
|
|
e55703405b | ||
|
|
4c71174d5d | ||
|
|
2ea08ac4b3 | ||
|
|
82ace062db | ||
|
|
17f0d85efb | ||
|
|
a994d71fa1 | ||
|
|
0eea8f4015 | ||
|
|
2e3b56ee3f | ||
|
|
b8bcb8a2a3 | ||
|
|
ac3ee91cbd | ||
|
|
18df821625 | ||
|
|
79b3fc4b6a | ||
|
|
9e09e74db3 | ||
|
|
9ee10ba4bd | ||
|
|
fe7c8c26b7 | ||
|
|
23576b6e48 | ||
|
|
cfa9e404d1 | ||
|
|
641360b0aa | ||
|
|
4ccc9eb89a | ||
|
|
a801dde186 | ||
|
|
e2d53f26e2 | ||
|
|
742cdeec66 | ||
|
|
c2116aa7ca | ||
|
|
fed950837a | ||
|
|
508890d33d | ||
|
|
141556db44 | ||
|
|
8589403fe0 | ||
|
|
cd2224742f | ||
|
|
0b1d11ed01 | ||
|
|
22443b1490 | ||
|
|
a3a53e9a0c | ||
|
|
dd3d045396 | ||
|
|
673d790372 | ||
|
|
6cac0aba6a | ||
|
|
1a13c5076e | ||
|
|
c504911bf6 | ||
|
|
db5fa8a8cd | ||
|
|
c03400303f | ||
|
|
7f91a134ef | ||
|
|
d4891d5ff1 | ||
|
|
bdc2336265 | ||
|
|
2a0f65084e | ||
|
|
db146cfc37 | ||
|
|
49f38828d1 | ||
|
|
0c972cf59d | ||
|
|
f057fdadba | ||
|
|
3fb2571255 | ||
|
|
619f741ca9 | ||
|
|
713d81947a | ||
|
|
f76d3e0d0c | ||
|
|
488cd5d933 | ||
|
|
55bcb72be7 | ||
|
|
7f8872b2f1 | ||
|
|
139807080b | ||
|
|
86b37b112f | ||
|
|
d56f528c27 | ||
|
|
a1b81b249f | ||
|
|
fa0de2f03b | ||
|
|
a357445654 | ||
|
|
8f5c49c655 | ||
|
|
03b5ce7c6f | ||
|
|
63df2a3c8b | ||
|
|
9a09ead865 | ||
|
|
c2380ff7bc | ||
|
|
c49a8b5b07 | ||
|
|
51fec5f1ff | ||
|
|
ac20d8729b | ||
|
|
7e9fedd203 | ||
|
|
576cd86ecb | ||
|
|
bb1db6cf17 | ||
|
|
3529a2fcc6 | ||
|
|
e842bee049 | ||
|
|
3c8684c528 | ||
|
|
108815dbe1 | ||
|
|
d75386b545 | ||
|
|
bb1934bb9f | ||
|
|
f01817f39e | ||
|
|
fad4c1b56c | ||
|
|
3c8868280d | ||
|
|
a665ca766d | ||
|
|
0f85278c73 | ||
|
|
085727234a | ||
|
|
83fe073e97 | ||
|
|
8a25c1bfc1 | ||
|
|
a94cd68ce1 | ||
|
|
68ac03d19c | ||
|
|
b79bc35fd9 | ||
|
|
90839d747f | ||
|
|
1b1ab744e5 | ||
|
|
f02db0fa0f | ||
|
|
fbcee0b716 | ||
|
|
c105cfd35a | ||
|
|
bb402480a5 | ||
|
|
c983321fed | ||
|
|
43ebc96bfe | ||
|
|
8ae1ad57a4 | ||
|
|
bd39bfdc5b | ||
|
|
a11566795d | ||
|
|
3676c596df | ||
|
|
0947836a44 | ||
|
|
2383e42a5c | ||
|
|
049dbb73fe | ||
|
|
95894ef2e2 | ||
|
|
7f7a744ebb | ||
|
|
3833a4a457 | ||
|
|
a0516a371f | ||
|
|
29491df6f2 | ||
|
|
599ed1dde0 | ||
|
|
143111ae5a | ||
|
|
d753ea5bdb | ||
|
|
b205eb7fa6 | ||
|
|
d8baee9605 | ||
|
|
6dacc5192a | ||
|
|
4f5f88258c | ||
|
|
f5abedad01 | ||
|
|
02aee3b43e | ||
|
|
c1406860fa | ||
|
|
53759731cf | ||
|
|
b018baec8a | ||
|
|
28ddea5371 | ||
|
|
82989a8b7c | ||
|
|
d78b748696 | ||
|
|
77968a7fc1 | ||
|
|
00a68c5fcf | ||
|
|
6f9752a1bf | ||
|
|
6a32828dd8 | ||
|
|
b5e0d7a7c9 | ||
|
|
3b0b28ff97 | ||
|
|
6ce3730083 | ||
|
|
d3e7d88753 | ||
|
|
9dee80eb4f | ||
|
|
eb99c87f75 | ||
|
|
e45f2a8efb | ||
|
|
86ab3890d6 | ||
|
|
3b5ccb9342 | ||
|
|
9441170d39 | ||
|
|
ffb83247f4 | ||
|
|
13f42071b9 | ||
|
|
032931fa3b | ||
|
|
472b325349 | ||
|
|
b852d925c2 | ||
|
|
56cdba50cf | ||
|
|
cfec68e054 | ||
|
|
d9f850b032 | ||
|
|
1f012337c0 | ||
|
|
90cf642ca7 | ||
|
|
d12c48ce63 | ||
|
|
67b9d6cb8d | ||
|
|
2e12e635a0 | ||
|
|
1b5631990c | ||
|
|
acf846acc0 | ||
|
|
e1347d8f7d | ||
|
|
6d809b56e6 | ||
|
|
4528507155 | ||
|
|
da07871416 | ||
|
|
354d0254ee | ||
|
|
b711bf2009 | ||
|
|
94e2a71866 | ||
|
|
f467ad1add | ||
|
|
f631477a68 | ||
|
|
56479e724f | ||
|
|
d752bac24a | ||
|
|
a50258fdf8 | ||
|
|
710dfd01d6 | ||
|
|
03b1a1cc68 | ||
|
|
1c73fd57fc | ||
|
|
24078a4395 | ||
|
|
270cbf42df | ||
|
|
ea75dae8ec | ||
|
|
6d290babe2 | ||
|
|
428faa75b2 | ||
|
|
829c9a980e | ||
|
|
76af9907fd | ||
|
|
9893bb331f | ||
|
|
6108e60936 | ||
|
|
9e2e376f5a | ||
|
|
2db8b3fa48 | ||
|
|
e2c6f0c171 | ||
|
|
eba7cdade2 | ||
|
|
ca88e45bf3 | ||
|
|
19776686ec | ||
|
|
e0a150a22c | ||
|
|
0df6063d8a | ||
|
|
5199414933 | ||
|
|
85f1dec81d | ||
|
|
97c937a56d | ||
|
|
f1113f7c76 | ||
|
|
0ced83597f | ||
|
|
1f5fbb808b | ||
|
|
5fb6091fb7 | ||
|
|
e764557007 | ||
|
|
961cc10e0d | ||
|
|
49fc7da32e | ||
|
|
dd56734fda | ||
|
|
2015347b0c | ||
|
|
4daa9aad36 | ||
|
|
084ca55a36 | ||
|
|
e06d886252 | ||
|
|
9fe3d750cc | ||
|
|
13bb00bfa2 | ||
|
|
79a72011b7 | ||
|
|
f2b278ce10 | ||
|
|
ec10452b67 | ||
|
|
ba3fd65bcc | ||
|
|
f816c45b88 | ||
|
|
ca14cdf9b6 | ||
|
|
6d667fad45 | ||
|
|
aa644914fe | ||
|
|
1cbe4d44ae | ||
|
|
91805d1e26 | ||
|
|
9212f2ebd7 | ||
|
|
784fd27e9e | ||
|
|
b78f237a68 | ||
|
|
1582b720d5 | ||
|
|
710127977e | ||
|
|
8e07496e08 | ||
|
|
163f5db0d1 | ||
|
|
be2e1b17cd | ||
|
|
5f5e73360b | ||
|
|
b0b015bc2e | ||
|
|
7f66689e21 | ||
|
|
17a5c3e0ab | ||
|
|
3ed5b8ddf7 | ||
|
|
de991b519a | ||
|
|
a066fe25c6 | ||
|
|
6e8a9dfec1 | ||
|
|
31eeb44d4f | ||
|
|
44b15cbb35 | ||
|
|
b5b3ffa6fe | ||
|
|
83a924ddac | ||
|
|
2e8bd0b7a6 | ||
|
|
b685f3cab7 | ||
|
|
2d25a93126 | ||
|
|
a8eb52f61a | ||
|
|
761c341c19 | ||
|
|
000e395c07 | ||
|
|
4b06f1e9f6 | ||
|
|
29dc0cf53f | ||
|
|
f8b55680f8 | ||
|
|
cb08466693 | ||
|
|
72671df483 | ||
|
|
83524e3690 | ||
|
|
0125d9c254 | ||
|
|
1ac5fe7c1e | ||
|
|
4b42f38257 | ||
|
|
d2b153c522 | ||
|
|
ab126ad1d9 | ||
|
|
b04aba89d3 | ||
|
|
2cb41d8a2b | ||
|
|
1b1a142a89 | ||
|
|
dcfe76bc80 | ||
|
|
d4d93d7fb7 | ||
|
|
ebd780e8ba | ||
|
|
65bd36ba40 | ||
|
|
bc86ab514f | ||
|
|
8ee7773260 | ||
|
|
e011889436 | ||
|
|
16e22ec067 | ||
|
|
d2ee47a4ac | ||
|
|
dce8ebe526 | ||
|
|
23de4be59f | ||
|
|
76e3f87775 | ||
|
|
fca6fad720 | ||
|
|
ceffcfcadd | ||
|
|
1ad19a4ce1 | ||
|
|
67a58c01d0 | ||
|
|
186ac191c4 | ||
|
|
34bf04bc41 | ||
|
|
e6db53fadb | ||
|
|
461001328b | ||
|
|
4bffe26bc0 | ||
|
|
290b4eaa46 | ||
|
|
0f1b828e6a | ||
|
|
46288758a0 | ||
|
|
e8789c71a4 | ||
|
|
f54203d76c | ||
|
|
a5634794b3 | ||
|
|
b403b811cd | ||
|
|
36fc065548 | ||
|
|
39b4548a2a | ||
|
|
52f0c4a06f | ||
|
|
c74fe12dd5 | ||
|
|
fe11565110 | ||
|
|
cca255e6be | ||
|
|
325d12d169 | ||
|
|
2a26a0db64 | ||
|
|
72c9425ae3 | ||
|
|
ff46c33d3f | ||
|
|
c59b3f5fa5 | ||
|
|
84a5509da2 | ||
|
|
305153b9c7 | ||
|
|
1adde7aa51 | ||
|
|
4fa2bc17b8 | ||
|
|
294a467116 | ||
|
|
67eb3069b3 | ||
|
|
ad2694445c | ||
|
|
6a1713e440 | ||
|
|
62d65371d7 | ||
|
|
c4722fe5ae | ||
|
|
13472be059 | ||
|
|
6d2dbd42a6 | ||
|
|
d2970e096d | ||
|
|
77c4795139 | ||
|
|
5434babdf3 | ||
|
|
530b3ebd88 | ||
|
|
33172a7b61 | ||
|
|
5065547c4e | ||
|
|
0870078ed2 | ||
|
|
992ad0fc08 | ||
|
|
5ae8ec9669 | ||
|
|
0bd5adde7e | ||
|
|
7e8a9f230c | ||
|
|
f7bc901c2d | ||
|
|
e1a7590db3 | ||
|
|
f592b6e167 | ||
|
|
8c0f455b97 | ||
|
|
0d08c52258 | ||
|
|
a42626b975 | ||
|
|
ec728d0cef | ||
|
|
20bddc75cf | ||
|
|
45bece4bd9 | ||
|
|
445a23fbc0 | ||
|
|
6952a2efa5 | ||
|
|
8c2ccd2f55 | ||
|
|
9ce963b480 | ||
|
|
35794cfc66 | ||
|
|
e300c6d9b2 | ||
|
|
8660db16a8 | ||
|
|
25b4f0b054 | ||
|
|
fc809c6c73 | ||
|
|
51845ac5fe | ||
|
|
8e50fad6b9 | ||
|
|
3e818cd350 | ||
|
|
4327d4547e | ||
|
|
abba8b5864 | ||
|
|
8ede25fa42 | ||
|
|
50d4bc04ab | ||
|
|
128f8b3b8d | ||
|
|
5140010a6e | ||
|
|
24c21d7ed2 | ||
|
|
e50590043c | ||
|
|
fdc592b169 | ||
|
|
82731d62f9 | ||
|
|
912a863b13 | ||
|
|
627bf7c8ef | ||
|
|
beddabe64d | ||
|
|
339b1b4704 | ||
|
|
8dff18db0e | ||
|
|
ebb9b6aff8 | ||
|
|
22af2ff328 | ||
|
|
d755793345 | ||
|
|
ec756333cb | ||
|
|
ce3aa9a752 | ||
|
|
e48b90f941 | ||
|
|
7f407fb1ae | ||
|
|
ac212c3d00 | ||
|
|
040180f933 | ||
|
|
7e6554677f | ||
|
|
dbeb10c0d2 | ||
|
|
c935d6387a | ||
|
|
acdd92e741 | ||
|
|
62660bc02a | ||
|
|
90d9fd06ae | ||
|
|
82d412cbff | ||
|
|
85efa3796a | ||
|
|
3f672919c2 | ||
|
|
a2e8f13ddd | ||
|
|
821fb07083 | ||
|
|
b9bd71ecfc | ||
|
|
9df4d6e479 | ||
|
|
29eb97fda5 | ||
|
|
67c6255c9a | ||
|
|
2d68cbbfd2 | ||
|
|
f98e0ef92a | ||
|
|
83303995c7 | ||
|
|
7d369dd9e8 | ||
|
|
9f66ea7026 | ||
|
|
67a96ae8e1 | ||
|
|
8936194ba7 | ||
|
|
00af084416 | ||
|
|
3829372a42 | ||
|
|
54ab1ec4b4 | ||
|
|
f4b0596b3a | ||
|
|
71f6b0bc32 | ||
|
|
fb8e63e498 | ||
|
|
1a133a4df5 | ||
|
|
60fc036bd6 | ||
|
|
3d96596485 | ||
|
|
0a8e90bd84 | ||
|
|
1e961000e6 | ||
|
|
3539487471 | ||
|
|
6caf0867b0 | ||
|
|
28a6c0e4f0 | ||
|
|
a4349df0a5 | ||
|
|
d2335707de | ||
|
|
1d2b08ade7 | ||
|
|
dd93988dc6 | ||
|
|
06ec355e88 | ||
|
|
c10f36c71c | ||
|
|
f32ce333dd | ||
|
|
d48c52cc2f | ||
|
|
25d02bb261 | ||
|
|
fda7657eab | ||
|
|
983fe0f10f | ||
|
|
8437097305 | ||
|
|
5a9341659c | ||
|
|
125af4c855 | ||
|
|
fa1db07ec8 | ||
|
|
f09bf0615b | ||
|
|
534cd392db | ||
|
|
5dee5ea49d | ||
|
|
957eaf0f18 | ||
|
|
12fad60eb4 | ||
|
|
404077e330 | ||
|
|
73947c56e1 | ||
|
|
63fae64e77 | ||
|
|
7f27b180ac | ||
|
|
6f4ba44946 | ||
|
|
dcc58e4f7a | ||
|
|
a68dfdb2d3 | ||
|
|
277dd7710a | ||
|
|
d03c9a1654 | ||
|
|
19bb456721 | ||
|
|
4e26d7b237 | ||
|
|
6a797a4fbc | ||
|
|
14784bbc4e | ||
|
|
6fd47dbab4 | ||
|
|
e31801f22f | ||
|
|
58e81bb0a0 | ||
|
|
d2cdce4410 | ||
|
|
7132822cc7 | ||
|
|
ca98fe843d | ||
|
|
bbfed9af3f | ||
|
|
707ddaa630 | ||
|
|
d127583c86 | ||
|
|
7128ee371d | ||
|
|
321158b99f | ||
|
|
d71a672089 | ||
|
|
e17d7daee8 | ||
|
|
d4e40a31c1 | ||
|
|
915cd23b2f | ||
|
|
994f800f8f | ||
|
|
2b3a684869 | ||
|
|
68b2b35148 | ||
|
|
da6dd7c0ac | ||
|
|
f2311b1efa | ||
|
|
b8bb78fde7 | ||
|
|
e4a1a57318 | ||
|
|
1ebaa27fc4 | ||
|
|
d6db850121 | ||
|
|
ebf732fca9 | ||
|
|
eba86d5312 | ||
|
|
648fcc60ca | ||
|
|
9f5b7e278f | ||
|
|
14fe1ba070 | ||
|
|
9d3cfd5c7d | ||
|
|
c2d5f602e7 | ||
|
|
ed42ce0d36 | ||
|
|
8201d0ed2a | ||
|
|
779a404950 | ||
|
|
52a0638dd2 | ||
|
|
73034f94fe | ||
|
|
ad0b6d0ac6 | ||
|
|
6e918eda31 | ||
|
|
3c968289f8 | ||
|
|
cb204decf4 | ||
|
|
f9fe3d621f | ||
|
|
b51d5baae6 | ||
|
|
747ba1d41e | ||
|
|
5c4ed9259e | ||
|
|
d3e0ffe0b9 | ||
|
|
17a24cdd7e | ||
|
|
4a66d7a573 | ||
|
|
cbd88ed3f3 | ||
|
|
9157414cae | ||
|
|
6663adb700 | ||
|
|
7c64f67499 | ||
|
|
7539c3fd15 | ||
|
|
83f6bc18e1 | ||
|
|
ea4cb40131 | ||
|
|
bdc3d71a23 | ||
|
|
b15ce5203d | ||
|
|
4507e6e51d | ||
|
|
c86689b227 | ||
|
|
89156f3fcc | ||
|
|
d0abdf3bbe | ||
|
|
9615b8b962 | ||
|
|
608b328cbf | ||
|
|
cbc71ac363 | ||
|
|
df9e997cc3 | ||
|
|
abe7943c06 | ||
|
|
764f802180 | ||
|
|
651adad5f2 | ||
|
|
34bfbc0983 | ||
|
|
9c355b7765 | ||
|
|
ccd47db916 | ||
|
|
ad30db1102 | ||
|
|
b3db6c9976 | ||
|
|
02ca6cc643 | ||
|
|
d5a1c7a1b3 | ||
|
|
13a4cf9994 | ||
|
|
e63bf01704 | ||
|
|
f23c1c9b69 | ||
|
|
e454d54a8a | ||
|
|
33af22848f | ||
|
|
610d90fa69 | ||
|
|
9c62c55a2c | ||
|
|
34ca952a9b | ||
|
|
487ef64336 | ||
|
|
f33968c782 | ||
|
|
7c03f5e4c0 | ||
|
|
6ff6084ab8 | ||
|
|
326e2c6ba1 | ||
|
|
efe117d946 | ||
|
|
b39ee6a985 | ||
|
|
5f99006de3 | ||
|
|
033d5f3406 | ||
|
|
d96c9b8236 | ||
|
|
a45f97b474 | ||
|
|
10a1ca0d8e | ||
|
|
4362727cc8 | ||
|
|
40ddfa533b | ||
|
|
447bb8b036 | ||
|
|
6231510794 | ||
|
|
352c086047 | ||
|
|
fb1ca49d9b | ||
|
|
1b0979c795 | ||
|
|
5a38f70a37 | ||
|
|
4da942723e | ||
|
|
e26ea4ba05 | ||
|
|
8cfa64944e | ||
|
|
21855a194c | ||
|
|
3411e17d8b | ||
|
|
e1f587eafb | ||
|
|
215f351dcd | ||
|
|
a49a201695 | ||
|
|
e7d80bcf33 | ||
|
|
feaf2632c5 | ||
|
|
b510588677 | ||
|
|
aa51aea245 | ||
|
|
b70211ec08 | ||
|
|
30f79a0d62 | ||
|
|
a3a98ac5f5 | ||
|
|
ed235f3db6 | ||
|
|
ad79674ec5 | ||
|
|
2ab4175ac1 | ||
|
|
408bdc1e87 | ||
|
|
86e6fa17a5 | ||
|
|
57c5b9ea27 | ||
|
|
23fff53e94 | ||
|
|
c829aed2df | ||
|
|
e53df1deda | ||
|
|
1283061d2b | ||
|
|
5e976dbf97 | ||
|
|
a86d94cee7 | ||
|
|
365779fbe4 | ||
|
|
0c456efebb | ||
|
|
e2d7353213 | ||
|
|
864f9a0490 | ||
|
|
262bf67b20 | ||
|
|
bffc887dae | ||
|
|
262ef21f32 | ||
|
|
07d89116a8 | ||
|
|
cc3e5fd1a8 | ||
|
|
b2858d2680 | ||
|
|
293a4ca425 | ||
|
|
22c9ec8cb6 | ||
|
|
a260630f48 | ||
|
|
86d37b4720 | ||
|
|
099663aaae | ||
|
|
678ef917cc | ||
|
|
d05e83e40b | ||
|
|
a18fe8bccf | ||
|
|
56c56cc450 | ||
|
|
998c7afb67 | ||
|
|
a90e7ac782 | ||
|
|
bd584ac05e | ||
|
|
30aecda799 | ||
|
|
05ff76f361 | ||
|
|
83d8189c51 | ||
|
|
7c56dddc8c | ||
|
|
ba8a4d9b98 | ||
|
|
afe01a1367 | ||
|
|
bcc74d68ee | ||
|
|
a6c4530e43 | ||
|
|
f83581880d | ||
|
|
0d4193f564 | ||
|
|
88b4c1eb81 | ||
|
|
cd4fb9f26e | ||
|
|
3e23192256 | ||
|
|
589cb55516 | ||
|
|
b14727149b | ||
|
|
df8e2d859c | ||
|
|
faa71c6f1c | ||
|
|
3486f69fcf | ||
|
|
0f36a40a44 | ||
|
|
73f32940ab | ||
|
|
3d255b2cdf | ||
|
|
27b543a725 | ||
|
|
79483332b7 | ||
|
|
b09be697b5 | ||
|
|
b7439b5efc | ||
|
|
8c87f16592 | ||
|
|
e16328e790 | ||
|
|
bb5d96be6e | ||
|
|
3c1a0550da | ||
|
|
dd90ade29d | ||
|
|
5866c9e95a | ||
|
|
372f34300e | ||
|
|
6cb5b53d5d | ||
|
|
1d4b2071ca | ||
|
|
f0774d4e24 | ||
|
|
f007127ad3 | ||
|
|
38c1211dba | ||
|
|
bd46edf3f0 | ||
|
|
48b2c0f8a3 | ||
|
|
8975fc2ed2 | ||
|
|
594beb67a2 | ||
|
|
804b843983 | ||
|
|
032a44626f | ||
|
|
d2cfffc24d | ||
|
|
c2a0ae1c96 | ||
|
|
cf8cce6d17 | ||
|
|
0420a527c0 | ||
|
|
54e45746cc | ||
|
|
c0c863ff8b | ||
|
|
e08efab1e7 | ||
|
|
41bc68e26d | ||
|
|
8f682da608 | ||
|
|
72ac3f200d | ||
|
|
32dcda5aa9 | ||
|
|
26e3203e9a | ||
|
|
1969b52569 | ||
|
|
53a84a182c | ||
|
|
c1b57d9977 | ||
|
|
556a5cabf9 | ||
|
|
3a4166a066 | ||
|
|
85c3467147 | ||
|
|
7f124d4271 | ||
|
|
8d2ddf7038 | ||
|
|
3d0bd4dacf | ||
|
|
d5bbe347a1 | ||
|
|
f9c721f9a9 | ||
|
|
fc16eb4e34 | ||
|
|
683583a8c4 | ||
|
|
6524389621 | ||
|
|
75e059af5b | ||
|
|
ecd61a4fed | ||
|
|
8614fbbaff | ||
|
|
e1445e7a84 | ||
|
|
be01de4b45 | ||
|
|
968a8cf0be | ||
|
|
e5e3925d5b | ||
|
|
57b09ccfb7 | ||
|
|
50d0cc6959 | ||
|
|
f43c0fde26 | ||
|
|
3c4dd61710 | ||
|
|
ed8580f86a | ||
|
|
b2f44b07fc | ||
|
|
1ccf1f8ffb | ||
|
|
2e3e177a6d | ||
|
|
5e345b0f82 | ||
|
|
6fd2c69926 | ||
|
|
f8e7bace50 | ||
|
|
46def3b49f | ||
|
|
c9a7ae733c | ||
|
|
9d437dfd6f | ||
|
|
5d277c1b90 | ||
|
|
a8b070bc56 | ||
|
|
6eb6a93ced | ||
|
|
89ebf2179c | ||
|
|
22635e3716 | ||
|
|
2ae49688e8 | ||
|
|
988241975f | ||
|
|
f5fb6b4abb | ||
|
|
ada8dec8da | ||
|
|
a18316a83a | ||
|
|
3f81358b54 | ||
|
|
ce3ac5d2f4 | ||
|
|
a2c7ad822b | ||
|
|
72fffee54f | ||
|
|
c77c41eaf8 | ||
|
|
d1a0e2c2c5 | ||
|
|
bbb03c9096 | ||
|
|
0b9f4b0e45 | ||
|
|
f689d25798 | ||
|
|
dae1edc578 | ||
|
|
df00fe8a58 | ||
|
|
428218ce30 | ||
|
|
20eaa1d19c | ||
|
|
d30d743cb0 | ||
|
|
14a6e2012d | ||
|
|
94fb470e29 | ||
|
|
968e970adb | ||
|
|
2d0a38436c | ||
|
|
9513f63a29 | ||
|
|
8b373d38ce | ||
|
|
456d34bde3 | ||
|
|
622c2d479c | ||
|
|
d835d016e0 | ||
|
|
700a475597 | ||
|
|
c2a34cc1e3 | ||
|
|
1f67ccfa20 | ||
|
|
2b7b45c8d1 | ||
|
|
c9803c8984 | ||
|
|
c55931dae5 | ||
|
|
94122f4ffd | ||
|
|
22f3b7ed24 | ||
|
|
a67e9f54f8 | ||
|
|
63933a9221 | ||
|
|
e25802f84f | ||
|
|
73a467a3c9 | ||
|
|
91378b9c2f | ||
|
|
7f2a2c4233 | ||
|
|
ddd5264abd | ||
|
|
5797a59ab9 | ||
|
|
9875150677 | ||
|
|
c9ff2a21c5 | ||
|
|
bd555b33ab | ||
|
|
51427cd7d2 | ||
|
|
0e0bd8a4f0 | ||
|
|
a8321acb88 | ||
|
|
3127508040 | ||
|
|
4fc877779b | ||
|
|
dae7dca0b9 | ||
|
|
b43b31eae8 | ||
|
|
bedcb1bf56 | ||
|
|
9fdb09ae0e | ||
|
|
f1b50695e5 | ||
|
|
9fcd6e965d | ||
|
|
228b7464bf | ||
|
|
ce87053c78 | ||
|
|
554caeeb34 | ||
|
|
3bddfc5fd2 | ||
|
|
127758047c | ||
|
|
8449ead2a7 | ||
|
|
aab34e52cf | ||
|
|
8186b8a242 | ||
|
|
bd77bcdab4 | ||
|
|
f82c0c0f83 | ||
|
|
7607e3cbc2 | ||
|
|
00acf3d2f6 | ||
|
|
2942ba83d4 | ||
|
|
30bd1c26c8 | ||
|
|
ba640ceac6 | ||
|
|
097b872536 | ||
|
|
ab5d04b670 | ||
|
|
5fe60e3b6d | ||
|
|
e0ff005333 | ||
|
|
98df612168 | ||
|
|
15979f9a10 | ||
|
|
5218f73773 | ||
|
|
064ab23511 | ||
|
|
774e5c3bb6 | ||
|
|
28598c7703 | ||
|
|
91bcc51e60 | ||
|
|
b5a4d98ef2 | ||
|
|
b365d6ba1a | ||
|
|
2e9a4ccd10 | ||
|
|
2fbdf1f982 | ||
|
|
20173ad233 | ||
|
|
602f423241 | ||
|
|
460b443736 | ||
|
|
99b886f9c4 | ||
|
|
feb9cd5859 | ||
|
|
c921959b81 | ||
|
|
7816fd8249 | ||
|
|
1483ed9ac6 | ||
|
|
0cc131c065 | ||
|
|
2e11c57a6e | ||
|
|
67738238be | ||
|
|
467b08a782 | ||
|
|
903fff7701 | ||
|
|
430818b366 | ||
|
|
dda70ce920 | ||
|
|
f7876a9f2a | ||
|
|
303cba23b5 | ||
|
|
13eda8051a | ||
|
|
0763dd5639 | ||
|
|
95276c98fe | ||
|
|
5a0ad2aa59 | ||
|
|
4db7574e84 | ||
|
|
997e53472d | ||
|
|
3a308270bd | ||
|
|
af6475caa7 | ||
|
|
d0210b85b9 | ||
|
|
73cdbb320d | ||
|
|
6088d954f6 | ||
|
|
20ca4e38f4 | ||
|
|
616f8b2205 | ||
|
|
4379dc4afa | ||
|
|
c5106da010 | ||
|
|
0d6e373ffc | ||
|
|
6361875a15 | ||
|
|
9c7970f0af | ||
|
|
59ea52933d | ||
|
|
4fa41e1ad4 | ||
|
|
ce35e19c6d | ||
|
|
8643520464 | ||
|
|
d01deafff0 | ||
|
|
8f74e48dfb | ||
|
|
59a713fb4c | ||
|
|
626619a8b0 | ||
|
|
585eae2030 | ||
|
|
730e970e79 | ||
|
|
26c503ad30 | ||
|
|
1a99724eca | ||
|
|
03dad9ba14 | ||
|
|
e7a7f9e39a | ||
|
|
e228abfd5a | ||
|
|
61f28d270c | ||
|
|
9535e080e9 | ||
|
|
f9750d163d | ||
|
|
f8cb32e59a | ||
|
|
4508d93d08 | ||
|
|
fb4b67ae18 | ||
|
|
1e98ec0b38 | ||
|
|
0b50ebda47 | ||
|
|
5469f5ac1b | ||
|
|
068ce4654f | ||
|
|
800fcdc5d4 | ||
|
|
0d7a5a7284 | ||
|
|
7db72a871c | ||
|
|
9842388cd0 | ||
|
|
29c4403ec4 | ||
|
|
74124636dc | ||
|
|
193c245781 | ||
|
|
5cdc761746 | ||
|
|
d35d3922f0 | ||
|
|
c4850273c7 | ||
|
|
86f3130e44 | ||
|
|
2dc1fecb02 | ||
|
|
33e3a75a79 | ||
|
|
3fe46175f3 | ||
|
|
861bccce72 | ||
|
|
60a75d0549 | ||
|
|
610042a8e9 | ||
|
|
1cfa4a93ae | ||
|
|
23905bba19 | ||
|
|
eb391f99cf | ||
|
|
78713299e3 | ||
|
|
1f32a6daf6 | ||
|
|
6b00a6c2ca | ||
|
|
f18af9e2f4 | ||
|
|
288cd5b400 | ||
|
|
48dd520014 | ||
|
|
2aa24949c7 | ||
|
|
323ad30e25 | ||
|
|
96f7b664e9 | ||
|
|
a137bc4111 | ||
|
|
0f6a03a9ba | ||
|
|
17cb7d8dce | ||
|
|
0df3a2bf7b | ||
|
|
d5c59fc919 | ||
|
|
0364934636 | ||
|
|
edd10419dd | ||
|
|
c1c6749940 | ||
|
|
750d536401 | ||
|
|
e56a3751bf | ||
|
|
ad87ac28b7 | ||
|
|
7cb97e153a | ||
|
|
db4bcd4138 | ||
|
|
1493a5cfd3 | ||
|
|
04033a1b52 | ||
|
|
d2b7b04be3 | ||
|
|
bc22065684 | ||
|
|
bf2d6bda28 | ||
|
|
d1cc5844c6 | ||
|
|
c5e9c30ce8 | ||
|
|
2fb054d4a5 | ||
|
|
8fb6fb295b | ||
|
|
341705e4a2 | ||
|
|
53848be39f | ||
|
|
b3ed06aa39 | ||
|
|
207d8535f8 | ||
|
|
519595c0c3 | ||
|
|
916f8b196a | ||
|
|
38b2843f84 | ||
|
|
df371c392b | ||
|
|
5171c9164e | ||
|
|
c74486172b | ||
|
|
8fb9cb8815 | ||
|
|
dd4048b7c4 | ||
|
|
d3dc5e31eb | ||
|
|
dcc0dff67d | ||
|
|
b1382fd5e6 | ||
|
|
f4ce278729 | ||
|
|
30dd63cc0a | ||
|
|
2dbbc54c28 | ||
|
|
55552b1481 | ||
|
|
1e24ce1f50 | ||
|
|
687bd8ab65 | ||
|
|
08f34e3d67 | ||
|
|
83e0632935 | ||
|
|
2e7a0ad5f4 | ||
|
|
1c63e0577a | ||
|
|
c45db5c659 | ||
|
|
0d205aa5e8 | ||
|
|
501a9fb57d | ||
|
|
33d388cc48 | ||
|
|
5010c0e66a | ||
|
|
a484df6885 | ||
|
|
ce12303229 | ||
|
|
fb046cc6ed | ||
|
|
f172cada2f | ||
|
|
5a5d25895c | ||
|
|
bdecfeef7f | ||
|
|
86fd0241e0 | ||
|
|
c3e8fea746 | ||
|
|
336a7b89c3 | ||
|
|
0d368b44de | ||
|
|
12ff7bb067 | ||
|
|
305f0f801e | ||
|
|
e11aa75543 | ||
|
|
2d79d1f65c | ||
|
|
e4c682b645 | ||
|
|
cc3d9a274e | ||
|
|
2d5c9e0ff7 | ||
|
|
1a58a0a6d4 | ||
|
|
2e099a12a7 | ||
|
|
8352edfe94 | ||
|
|
87f0262ce8 | ||
|
|
3b987874f4 | ||
|
|
4d9de802fc | ||
|
|
6102ed531c | ||
|
|
c9ec169d50 | ||
|
|
944d0fd3f6 | ||
|
|
88e275f2b1 | ||
|
|
d91ebb6c0c | ||
|
|
e48b7e767b | ||
|
|
8d0f3ee849 | ||
|
|
fc66759444 | ||
|
|
8bedc4d7e7 | ||
|
|
c2baf10d6b | ||
|
|
5dc906dc6b | ||
|
|
8ea3316c7f | ||
|
|
5ff581b363 | ||
|
|
7778ef7fa3 | ||
|
|
ec4c1ae981 | ||
|
|
1906d210ab | ||
|
|
864d91569d | ||
|
|
c05e2691c0 | ||
|
|
d3ab0c732c | ||
|
|
bd16377bd8 | ||
|
|
c38bf979ce | ||
|
|
8251c0d467 | ||
|
|
52fc5877f7 | ||
|
|
e1275942c8 | ||
|
|
9e4e81b061 | ||
|
|
c7f181c30b | ||
|
|
03721f9abe | ||
|
|
a79a6059e2 | ||
|
|
1145900c51 | ||
|
|
6bbf82f3b0 | ||
|
|
7aae0bf7ba | ||
|
|
2b0ab0e974 | ||
|
|
4bf21122e1 | ||
|
|
b6b6c178b4 | ||
|
|
5eb81b4e00 | ||
|
|
f5aeeeb01d | ||
|
|
1efc14d45d | ||
|
|
da5d4380cf | ||
|
|
9d873f555e | ||
|
|
a39086fa3b | ||
|
|
ffac38ae9e | ||
|
|
182db15da7 | ||
|
|
4801234609 | ||
|
|
6ea03e6433 | ||
|
|
5000a5f05d | ||
|
|
89f8990dde | ||
|
|
e5adf2fe49 | ||
|
|
ed0996a590 | ||
|
|
f19c242901 | ||
|
|
443a9c6d2d | ||
|
|
d414f8ff6b | ||
|
|
1205b1c18b | ||
|
|
1b86708683 | ||
|
|
ef04befcba | ||
|
|
6c964df459 | ||
|
|
c74c001891 | ||
|
|
324658fe1c | ||
|
|
c89d742be6 | ||
|
|
0f611697e0 | ||
|
|
850627e7c9 | ||
|
|
c4e8fab106 | ||
|
|
a7f1e20594 | ||
|
|
48ee718f23 | ||
|
|
ae91e487df | ||
|
|
1709ad5996 | ||
|
|
424f43772a | ||
|
|
3663b60074 | ||
|
|
3c123cfe9d | ||
|
|
0fa4cff086 | ||
|
|
2f5366f764 | ||
|
|
92c2c9bc8f | ||
|
|
19db4bbe13 | ||
|
|
0929788ccc | ||
|
|
fb4c21a65a | ||
|
|
b36562fc94 | ||
|
|
5cf3ad6fe7 | ||
|
|
930964ab38 | ||
|
|
56de92f65f | ||
|
|
7d8bf817e9 | ||
|
|
438d67bcf5 | ||
|
|
020863f238 | ||
|
|
322ce8eb9d | ||
|
|
57f2099a36 | ||
|
|
2fcfb9b470 | ||
|
|
8ef01f87b1 | ||
|
|
5eed32bcf2 | ||
|
|
c7767dd36d | ||
|
|
5b147db113 | ||
|
|
313629e1af | ||
|
|
260182d439 | ||
|
|
458f88b6cf | ||
|
|
34ee49f3de | ||
|
|
5746060cde | ||
|
|
f7f2ef6182 | ||
|
|
886dbe6b24 | ||
|
|
d1d6c00e0c | ||
|
|
3bf3709799 | ||
|
|
d504b24419 | ||
|
|
b8ab2fd016 | ||
|
|
a5448f2a48 | ||
|
|
e541ad3811 | ||
|
|
db415be21c | ||
|
|
33de554f41 | ||
|
|
6abf26ef38 | ||
|
|
1ed941f3f0 | ||
|
|
e62de0b31f | ||
|
|
56b9ad9c62 | ||
|
|
9464a12960 | ||
|
|
7aa87220c4 | ||
|
|
95ad47ca7d | ||
|
|
9db16a87f5 | ||
|
|
3d33349691 | ||
|
|
5fab142c73 | ||
|
|
0a4ec45949 | ||
|
|
c9fbc48a6c | ||
|
|
14b2ba3b74 | ||
|
|
41c762b6c5 | ||
|
|
d22e85f345 | ||
|
|
07ecfbcfe1 | ||
|
|
c623b4e3f0 | ||
|
|
747c205773 | ||
|
|
2941d75c71 | ||
|
|
a2552b6fd1 | ||
|
|
919f58dc5d | ||
|
|
ef6e6ecdf4 | ||
|
|
9c7b95f108 | ||
|
|
6f6e0a6611 | ||
|
|
3c1425fe88 | ||
|
|
101c64b427 | ||
|
|
622e18bd40 | ||
|
|
652ab0c1da | ||
|
|
64f6a89a0d | ||
|
|
b93ad52a73 | ||
|
|
6b23821276 | ||
|
|
d37bc2eb2d | ||
|
|
b5f9d936da | ||
|
|
97bf734b67 | ||
|
|
38dfad1c6f | ||
|
|
302a9ed8d8 | ||
|
|
264ca5aab4 | ||
|
|
b2ffaf9294 | ||
|
|
a1e0ca8a7f | ||
|
|
54f50a7cb4 | ||
|
|
ae53c4202a | ||
|
|
1dd2b9e662 | ||
|
|
4d02762fd9 | ||
|
|
218038c7a4 | ||
|
|
c320f546fe | ||
|
|
068e52859b | ||
|
|
615978a132 | ||
|
|
867c285332 | ||
|
|
d92cef9559 | ||
|
|
b67335fadd | ||
|
|
18a424dbc9 | ||
|
|
d315ff2eff | ||
|
|
5c275ce843 | ||
|
|
46f74fe7ed | ||
|
|
8e922416ee | ||
|
|
24affd8ed3 | ||
|
|
0e4db53774 | ||
|
|
57442144ef | ||
|
|
3f45f68a5d | ||
|
|
3625c40f04 | ||
|
|
9094e2eee1 | ||
|
|
5e8e35940a | ||
|
|
3400567fee | ||
|
|
91ce29d30c | ||
|
|
3564b71c4f | ||
|
|
e9714be08f | ||
|
|
99d6478b35 | ||
|
|
321d8208cb | ||
|
|
76ab25d486 | ||
|
|
3b58bf52a7 | ||
|
|
ce85848867 | ||
|
|
9784632214 | ||
|
|
e6625d0c86 | ||
|
|
1f53fd2adf | ||
|
|
c0cd1f5d58 | ||
|
|
8c4d65179f | ||
|
|
f1ccc05c3f | ||
|
|
46c38ca738 | ||
|
|
546c552644 | ||
|
|
a1a8058b3d | ||
|
|
d8fa5343ec | ||
|
|
9dcc70f6ee | ||
|
|
139aae9444 | ||
|
|
956433bd8e | ||
|
|
6952ba3953 | ||
|
|
0788013cd8 | ||
|
|
39847a4a27 | ||
|
|
6f6a814439 | ||
|
|
a789e48452 | ||
|
|
7b6db0308e | ||
|
|
3e185623c1 | ||
|
|
459cdcb6f1 | ||
|
|
a9e697660b | ||
|
|
dbfb584ab9 | ||
|
|
154031af66 | ||
|
|
ee11979a76 | ||
|
|
b87398be69 | ||
|
|
982b06da2d | ||
|
|
5c8cd76d2e | ||
|
|
b28e771756 | ||
|
|
1174cf7406 | ||
|
|
190da9ce92 | ||
|
|
874876ab82 | ||
|
|
4698c1364a | ||
|
|
adde0130f5 | ||
|
|
562b52906f | ||
|
|
c56429b1d6 | ||
|
|
7cdf0abbb3 | ||
|
|
d4004cf054 | ||
|
|
c1bdbe2327 | ||
|
|
ce78dfd4b2 | ||
|
|
2331492840 | ||
|
|
a189dae361 | ||
|
|
d72e96f02e | ||
|
|
1fdb13c816 | ||
|
|
8af8eced16 | ||
|
|
d92d6c0cd7 | ||
|
|
20edf1b44b | ||
|
|
441d53c302 | ||
|
|
26168f662a | ||
|
|
2af061f7fd | ||
|
|
fea0734c41 | ||
|
|
f0f8b30a2d | ||
|
|
9d748ba967 | ||
|
|
37863ccbaf | ||
|
|
fb5b828996 | ||
|
|
7d36399035 | ||
|
|
0825208628 | ||
|
|
86d67b1b17 | ||
|
|
8612d411d1 | ||
|
|
3fd9ca553b | ||
|
|
62c077924a | ||
|
|
3795b71350 | ||
|
|
bcb21edcb1 | ||
|
|
30617cc632 | ||
|
|
183e4db696 | ||
|
|
1b9dc06150 | ||
|
|
34b63e7f92 | ||
|
|
045bc58da5 | ||
|
|
70b69b2698 | ||
|
|
0856d3f76e | ||
|
|
f8176051c1 | ||
|
|
80f2c66413 | ||
|
|
e00ddceda2 | ||
|
|
9e6e472b2a | ||
|
|
c55e1216b8 | ||
|
|
07636727c7 | ||
|
|
75c142f321 | ||
|
|
8e2e6d8603 | ||
|
|
fdcb3c2efe | ||
|
|
b4c148dd0c | ||
|
|
4e5fe38a2d | ||
|
|
9fd8313657 | ||
|
|
6a71ec6804 | ||
|
|
896e49cc9e | ||
|
|
258ec353b0 | ||
|
|
7b9bfe08a6 | ||
|
|
a3512e6636 | ||
|
|
145369e1f1 | ||
|
|
26591cfd41 | ||
|
|
c8abc0f189 | ||
|
|
ede7a5aaa3 | ||
|
|
5919914c54 | ||
|
|
34bd56e15b | ||
|
|
da550493b6 | ||
|
|
b1055b42b7 | ||
|
|
b941a37b1e | ||
|
|
c0ff4a246b | ||
|
|
ed1acd31c8 | ||
|
|
cda57cfe4a | ||
|
|
428aac7458 | ||
|
|
629f2157dd | ||
|
|
8dc8ddb5d4 | ||
|
|
d4c7062bd4 | ||
|
|
bbf591288e | ||
|
|
f702c398b2 | ||
|
|
a5eb8b9d24 | ||
|
|
56c7761485 | ||
|
|
6d588cc3a8 | ||
|
|
e01f8b4993 | ||
|
|
cbf786bb59 | ||
|
|
2ee1b530b6 | ||
|
|
b96a4150d9 | ||
|
|
a6777e9c50 | ||
|
|
20effdc21c | ||
|
|
c7ad760534 | ||
|
|
7406b18042 | ||
|
|
c97d6088e5 | ||
|
|
01240db538 | ||
|
|
055e2423e5 | ||
|
|
ac7a7cb1f3 | ||
|
|
fe59dc9dff | ||
|
|
3bf132f7ed | ||
|
|
100fcaebf4 | ||
|
|
1b3e0fe0fb | ||
|
|
b0776d9496 | ||
|
|
7b6dc763ad | ||
|
|
43e3aa24ef | ||
|
|
1cbae48a29 | ||
|
|
623549ff45 | ||
|
|
042910a848 | ||
|
|
0f7c87ca03 | ||
|
|
febb696a39 | ||
|
|
594dd50246 | ||
|
|
6aa2c56497 | ||
|
|
5ca3d4bf48 | ||
|
|
a3a0582ac4 | ||
|
|
6b5f0f1c92 | ||
|
|
5dcb2b634e | ||
|
|
aa9cab8323 | ||
|
|
d739547feb | ||
|
|
20d80b806a | ||
|
|
21be1d7d5e | ||
|
|
1227897ab0 | ||
|
|
3cd25344a8 | ||
|
|
3e02327858 | ||
|
|
c8013ae871 | ||
|
|
98b35d3793 | ||
|
|
5a8181f7ff | ||
|
|
f38e131242 | ||
|
|
2dd6bbbb3d | ||
|
|
87e2e06530 | ||
|
|
678dcfb748 | ||
|
|
7b4afc48ff | ||
|
|
fda84aa648 | ||
|
|
12b5699750 | ||
|
|
aa300ae973 | ||
|
|
c1de2d53dc | ||
|
|
da7c3f878f | ||
|
|
5bf05b5f45 | ||
|
|
410e1633e2 | ||
|
|
5871018d10 | ||
|
|
2940d93123 | ||
|
|
be0131fc63 | ||
|
|
4d8d3fda55 | ||
|
|
28f89b0a4f | ||
|
|
906abcedee | ||
|
|
1950def5f9 | ||
|
|
692fe9af1e | ||
|
|
ff6afd39e0 | ||
|
|
a712f91a03 | ||
|
|
8c7f31cafa | ||
|
|
eede5a08b0 | ||
|
|
ecdb6034af | ||
|
|
85d496dd45 | ||
|
|
00273f3d0c | ||
|
|
7cad171a34 | ||
|
|
1d0c1f8f32 | ||
|
|
7ffdcd49d8 | ||
|
|
bb0e601eaa | ||
|
|
f34c345c7c | ||
|
|
d2bba23541 | ||
|
|
fc49faa896 | ||
|
|
ddbf501a66 | ||
|
|
be13b5de4a | ||
|
|
72e26d9b69 | ||
|
|
6d51493e46 | ||
|
|
25c7aee1d9 | ||
|
|
fafc0d0b94 | ||
|
|
8d16301665 | ||
|
|
98042443ac | ||
|
|
2c86726c06 | ||
|
|
ba43a01c8c | ||
|
|
c1dc0e6c2d | ||
|
|
3ab548971f | ||
|
|
b6549580be | ||
|
|
807af97a4b | ||
|
|
682fc41c0d | ||
|
|
ff0679f7b8 | ||
|
|
b7bfe418ba | ||
|
|
05f8bcff31 | ||
|
|
d148db3c2d | ||
|
|
e58f55959d | ||
|
|
9507f11f77 | ||
|
|
114fb21b8e | ||
|
|
29f054c49c | ||
|
|
fc38c15897 | ||
|
|
4528abc1c9 | ||
|
|
a83985c3b3 | ||
|
|
0beba29082 | ||
|
|
62ee0b8d72 | ||
|
|
0b42416868 | ||
|
|
6cd5eb93b6 | ||
|
|
ef7c698713 | ||
|
|
97bea18e5a | ||
|
|
b8855f0749 | ||
|
|
78d65510ff | ||
|
|
13808663cc | ||
|
|
645c8f9931 | ||
|
|
1f7553d53b | ||
|
|
cf208a531e | ||
|
|
2fae57c4d2 | ||
|
|
1408e6ab6c | ||
|
|
994af8db20 | ||
|
|
fccf3902e0 | ||
|
|
f667b0c4b9 | ||
|
|
66dfce5d03 | ||
|
|
7c32e613e8 | ||
|
|
a0dc74bb87 | ||
|
|
96524e48fd | ||
|
|
b48d3d3da3 | ||
|
|
899e6c5e6e | ||
|
|
6ae335f379 | ||
|
|
0d2a258e77 | ||
|
|
df521f2a04 | ||
|
|
fb71467574 | ||
|
|
62511ae2c3 | ||
|
|
b23ae9de2a | ||
|
|
ceeec0e1da | ||
|
|
56bacad887 | ||
|
|
a6c0aba80b | ||
|
|
e989b57f8f | ||
|
|
5403df8cfa | ||
|
|
0069fff8a5 | ||
|
|
4a2f6574f4 | ||
|
|
b6bd7b83f0 | ||
|
|
017f2492bf | ||
|
|
c7534f49f1 | ||
|
|
eb2f02c2e2 | ||
|
|
9e303fd8ac | ||
|
|
df1b8d9f56 | ||
|
|
ea779c36a4 | ||
|
|
1a0a242c45 | ||
|
|
2fa850ceeb | ||
|
|
7e56070c77 | ||
|
|
a557877fb5 | ||
|
|
5f2a4a0498 | ||
|
|
099f85a29d | ||
|
|
f0d0cd5b1c | ||
|
|
a96657bcac | ||
|
|
d887569522 | ||
|
|
c625e28e1c | ||
|
|
8b45ffe17a | ||
|
|
c79f58856f | ||
|
|
78f8832acf | ||
|
|
4c760cd87f | ||
|
|
e06e6b474d | ||
|
|
10a9a6bafb | ||
|
|
e6b1e6d7ec | ||
|
|
34bdfeb69f | ||
|
|
4c5cad43fb | ||
|
|
09d12639b4 | ||
|
|
2f38ebd1cc | ||
|
|
136f32ac2e | ||
|
|
cddbdba091 | ||
|
|
99090d0e37 | ||
|
|
9598ba4d25 | ||
|
|
c43f648fa1 | ||
|
|
82564cb571 | ||
|
|
e791b34d50 | ||
|
|
584837e294 | ||
|
|
037d6e6d22 | ||
|
|
f08bc94618 | ||
|
|
14bf89918c | ||
|
|
33d8657270 | ||
|
|
9de22f8945 | ||
|
|
bd5fb9ec9b | ||
|
|
e8453216cf | ||
|
|
c39b9bb100 | ||
|
|
6eac1e60ad | ||
|
|
00aa3124cd | ||
|
|
3ad19a7555 | ||
|
|
d1babd0682 | ||
|
|
8999732344 | ||
|
|
667d1e7d6e | ||
|
|
f189ea805c | ||
|
|
1fc86d7905 | ||
|
|
e54d7e9761 | ||
|
|
538239e1c2 | ||
|
|
3de6eb920c | ||
|
|
91d40e07fc | ||
|
|
70b5a69f31 | ||
|
|
0604f638ea | ||
|
|
00a004d698 | ||
|
|
cef99ce48f | ||
|
|
6f78946a15 | ||
|
|
0a4586ab96 | ||
|
|
ec5da29d17 | ||
|
|
63115cfa34 | ||
|
|
ce849a2e5f | ||
|
|
bbafdc3d23 | ||
|
|
29c4a9c996 | ||
|
|
42e8f9a439 | ||
|
|
e65cc7feea | ||
|
|
340d8b3742 | ||
|
|
8a3b291ee7 | ||
|
|
2d364b0e78 | ||
|
|
a0dffce882 | ||
|
|
694f973dc5 | ||
|
|
8c4109389d | ||
|
|
800d246ec3 | ||
|
|
2eb0258991 | ||
|
|
70bdc9bc94 | ||
|
|
a8bd32d2ea | ||
|
|
ac705872dd | ||
|
|
a3da318ff9 | ||
|
|
a2c3b6fa1c | ||
|
|
5e65a4fa77 | ||
|
|
89038c2c27 | ||
|
|
e2ca959bed | ||
|
|
f787f4bdac | ||
|
|
c760c5daf7 | ||
|
|
4cb4a93d3d | ||
|
|
f6051993e4 | ||
|
|
277d56d6f4 | ||
|
|
a31f95bd9a | ||
|
|
b9b000bed0 | ||
|
|
45b3fd5ecf | ||
|
|
1992af8b51 | ||
|
|
159d4401df | ||
|
|
e6e68945cf | ||
|
|
cb6e1a5b9a | ||
|
|
c794112836 | ||
|
|
7bd86d9c48 | ||
|
|
a9b34e6f03 | ||
|
|
c4b3c28445 | ||
|
|
4682f02a45 | ||
|
|
88edf02912 | ||
|
|
b184ebb73d | ||
|
|
f8d81f0981 | ||
|
|
c945c855ad | ||
|
|
7af3664791 | ||
|
|
ba3903e969 | ||
|
|
f26f1e6192 | ||
|
|
29ca848dc9 | ||
|
|
abb3ee0a62 | ||
|
|
95863f9ccf | ||
|
|
647395ecff | ||
|
|
ab1ee42a9e | ||
|
|
784eaa2d39 | ||
|
|
19a681e06b | ||
|
|
a3b9c26657 | ||
|
|
9a7b63b55a | ||
|
|
15dbcb6d52 | ||
|
|
bdf5373119 | ||
|
|
300f051939 | ||
|
|
507fb3f445 | ||
|
|
63552afa91 | ||
|
|
d47a18bde2 | ||
|
|
454fd916ff | ||
|
|
0f3437a0f0 | ||
|
|
1d09662818 | ||
|
|
cbfc042dae | ||
|
|
14c8a119f4 | ||
|
|
00d9017088 | ||
|
|
71daaa22df | ||
|
|
7d91c754dd | ||
|
|
6901c1ff45 | ||
|
|
8eaf81b0bd | ||
|
|
112b52c300 | ||
|
|
c661a467cb | ||
|
|
9c61fc648d | ||
|
|
e2730c6e0e | ||
|
|
7890bf36b7 | ||
|
|
58fd94d96d | ||
|
|
5a422bd8a1 | ||
|
|
0ef1e49134 | ||
|
|
49ec4671fb | ||
|
|
2406e4cd5f | ||
|
|
0a80016276 | ||
|
|
9005e163bd | ||
|
|
a653478d85 | ||
|
|
a817f8d576 | ||
|
|
b613a4192b | ||
|
|
a5dd93ea7b | ||
|
|
35d94cc046 | ||
|
|
60070e0c13 | ||
|
|
04d76d7929 | ||
|
|
38d2b22f9b | ||
|
|
a436ba36e5 | ||
|
|
17f0f7a7c2 | ||
|
|
41cb428550 | ||
|
|
431211321c | ||
|
|
f6fb88a3ec | ||
|
|
771a97822b | ||
|
|
c1bc12b2db | ||
|
|
4636580508 | ||
|
|
156aad6354 | ||
|
|
6d8dcc9cea | ||
|
|
f341fbfdc9 | ||
|
|
0515b4b100 | ||
|
|
c8ae70d591 | ||
|
|
3a6e1cd79a | ||
|
|
b871a4ff66 | ||
|
|
97737a32ec | ||
|
|
dea48143db | ||
|
|
f57d10dcda | ||
|
|
a4a0592795 | ||
|
|
1bc11579e7 | ||
|
|
19d9255d72 | ||
|
|
6d5e3e7d18 | ||
|
|
053b90b489 | ||
|
|
64d7532b75 | ||
|
|
a8d1e293fe | ||
|
|
b6f4e171c5 | ||
|
|
980219b435 | ||
|
|
bfba468ddd | ||
|
|
f9e3a31d25 | ||
|
|
a2c06b2975 | ||
|
|
a65d1a58be | ||
|
|
bbae3116e4 | ||
|
|
810eac0ff8 | ||
|
|
d0ec2243b1 | ||
|
|
9666041de7 | ||
|
|
6b27a7e9b3 | ||
|
|
000b51a60c | ||
|
|
3c236bdcfb | ||
|
|
b1242f55e9 | ||
|
|
bce2170cfe | ||
|
|
da4c309071 | ||
|
|
47793b0aa4 | ||
|
|
5dd282ded5 | ||
|
|
a595c4874d | ||
|
|
0a8431b930 | ||
|
|
3fbb604577 | ||
|
|
70a64b7391 | ||
|
|
76f6959a8e | ||
|
|
0902dd731f | ||
|
|
18ca557fbd | ||
|
|
9eeae5b295 | ||
|
|
9ea9a3312d | ||
|
|
59542519eb | ||
|
|
a2262989f9 | ||
|
|
0bbad93d02 | ||
|
|
d51c5a197d | ||
|
|
446474e46e | ||
|
|
9afbd99d88 | ||
|
|
f5b0c8d47a | ||
|
|
7d5e4bdd48 | ||
|
|
b575d2ba79 | ||
|
|
dcaef43283 | ||
|
|
1bec466d3b | ||
|
|
b439a2ba36 | ||
|
|
f77220c9fa | ||
|
|
168cc16de2 | ||
|
|
1df6fa8855 | ||
|
|
22c78e112b | ||
|
|
6abf01597e | ||
|
|
e8332bb052 | ||
|
|
01bf7847d3 | ||
|
|
94d6edb7eb | ||
|
|
c434c2ce94 | ||
|
|
ed1166a08f | ||
|
|
76f4c28a1b | ||
|
|
9f05c9e26f | ||
|
|
6bbc3a56ca | ||
|
|
5eaeed25aa | ||
|
|
93e476796b | ||
|
|
f70a86fe45 | ||
|
|
802aad8891 | ||
|
|
3b796819e5 | ||
|
|
4050f5241f | ||
|
|
2ed36aef23 | ||
|
|
10fa6c87ef | ||
|
|
f72b93cc66 | ||
|
|
9daf9f942c | ||
|
|
73ee17337c | ||
|
|
7908eae30a | ||
|
|
e79f21d2e3 | ||
|
|
d35d7bf918 | ||
|
|
27ef381242 | ||
|
|
0bf4d90b9b | ||
|
|
340a302e3c | ||
|
|
6780da9eb0 | ||
|
|
9aa1eb50f4 | ||
|
|
43231abc5c | ||
|
|
d5f9ca608a | ||
|
|
fbd75e2831 | ||
|
|
688ded77ac | ||
|
|
630acaea46 | ||
|
|
9774ff6134 | ||
|
|
c3a901e0f2 | ||
|
|
2721c45b41 | ||
|
|
f0e1237b00 | ||
|
|
5dd122710c | ||
|
|
71c8e5d8c0 | ||
|
|
cd46476c76 | ||
|
|
965b562cdf | ||
|
|
f7703dacd8 | ||
|
|
1c75580a24 | ||
|
|
61dc019c05 | ||
|
|
a681e3add3 | ||
|
|
fde9e852f3 | ||
|
|
e8a915e0dd | ||
|
|
996dc7c9a3 | ||
|
|
6df1f30750 | ||
|
|
c8af11588a | ||
|
|
246f1a1b49 | ||
|
|
08a83c7aaa | ||
|
|
ee68d1f2ff | ||
|
|
8524705a48 | ||
|
|
2ade8e950f | ||
|
|
daf2a5173c | ||
|
|
ae2e177cb4 | ||
|
|
a7d09b7149 | ||
|
|
051dbc0bef | ||
|
|
c3a86f5b24 | ||
|
|
d8ad53cc6d | ||
|
|
b1fff13659 | ||
|
|
78e676cdd0 | ||
|
|
46be0e152b | ||
|
|
b55cd9be14 | ||
|
|
16752b82a3 | ||
|
|
5dbb996b36 | ||
|
|
d0e2437f4a | ||
|
|
b16306806d | ||
|
|
910c82a4f4 | ||
|
|
3a1a8b0dc7 | ||
|
|
635c017400 | ||
|
|
b2c6206aa9 | ||
|
|
e15cdbfad2 | ||
|
|
f7f257a663 | ||
|
|
415673a49e | ||
|
|
0c1c48c3b2 | ||
|
|
eab4f663bf | ||
|
|
3041714a16 | ||
|
|
c622da4a62 | ||
|
|
6ed017919e | ||
|
|
eee1888d2c | ||
|
|
b6eaa8505b | ||
|
|
a998d5e845 | ||
|
|
83e4163ed4 | ||
|
|
6a613a12e2 | ||
|
|
3987ebaf34 | ||
|
|
f8a1d6b3f1 | ||
|
|
863c454979 | ||
|
|
023f1f3596 | ||
|
|
00e7119440 | ||
|
|
cdd10d4e2e | ||
|
|
a937e8fe55 | ||
|
|
3ee4611640 | ||
|
|
76b1845732 | ||
|
|
1a041ffc62 | ||
|
|
b3bc37c479 | ||
|
|
2ea5bd3b02 | ||
|
|
56e82cfc50 | ||
|
|
fb91f8dfc2 | ||
|
|
d4ccc10ed4 | ||
|
|
add94b8626 | ||
|
|
6d4f228238 | ||
|
|
b42d039bd5 | ||
|
|
12ec2b8acc | ||
|
|
637e05bed3 | ||
|
|
e30e8e363a | ||
|
|
4739e5f724 | ||
|
|
5f8609fe7a | ||
|
|
76cb2f917f | ||
|
|
627cb0664c | ||
|
|
9bcf974a11 | ||
|
|
a8345e83c5 | ||
|
|
7177f7442b | ||
|
|
da2f3fe12c | ||
|
|
10c211f1ab | ||
|
|
135e6465c9 | ||
|
|
d3cf7cd239 | ||
|
|
989dacf6d2 | ||
|
|
04e3492d77 | ||
|
|
456071dafd | ||
|
|
ab93c8c1e3 | ||
|
|
2a8eb67089 | ||
|
|
aa78db8193 | ||
|
|
1709fc3ca0 | ||
|
|
7d91674a98 | ||
|
|
8f2768904f | ||
|
|
04dc1a74db | ||
|
|
d553cea606 | ||
|
|
71886e5e2a | ||
|
|
d9db6bcf49 | ||
|
|
123dde1e8f | ||
|
|
172c7f6bb4 | ||
|
|
0ce50e1cf8 | ||
|
|
1121879199 | ||
|
|
d2829e1806 | ||
|
|
0988565c2f | ||
|
|
a70b1eb58b | ||
|
|
bb999c5e93 | ||
|
|
b23c47ae6d | ||
|
|
8cd699c110 | ||
|
|
e3495efad7 | ||
|
|
5414ae7a36 | ||
|
|
bc81472adb | ||
|
|
b83cb924a5 | ||
|
|
536e0e7dca | ||
|
|
b19599f81d | ||
|
|
bbbda7925e | ||
|
|
467294eb17 | ||
|
|
13ef968fb1 | ||
|
|
3deaafca30 | ||
|
|
f998104bb0 | ||
|
|
4a73ca28ca | ||
|
|
070907e367 | ||
|
|
ed72ca3509 | ||
|
|
5a493d4868 | ||
|
|
b2b16e66b4 | ||
|
|
4ed7d72372 | ||
|
|
8143fe2fcc | ||
|
|
65b0aec355 | ||
|
|
12f941c58d | ||
|
|
08cf940a7a | ||
|
|
0e1b60dd6e | ||
|
|
68bd00caa7 | ||
|
|
d10b129b13 | ||
|
|
bedbb1f4e5 | ||
|
|
59f4197edb | ||
|
|
390d0dc634 | ||
|
|
8fb3082529 | ||
|
|
f81337cc8b | ||
|
|
62ee59ecc6 | ||
|
|
54399fe567 | ||
|
|
d3d0c81bc6 | ||
|
|
18063f3ac9 | ||
|
|
5b8f9f1a7a | ||
|
|
a6d9955a08 | ||
|
|
92cbaada34 | ||
|
|
e9b07df4fb | ||
|
|
04909597e9 | ||
|
|
e2c43dcb5e | ||
|
|
853d1f8a6f | ||
|
|
339eeef435 | ||
|
|
4a21d5dca3 | ||
|
|
b824f73912 | ||
|
|
b5ed4a67cb | ||
|
|
0a57aec7bb | ||
|
|
e0906564dc | ||
|
|
1c8778b6e3 | ||
|
|
58f393ac8b | ||
|
|
cd25f6b6c7 | ||
|
|
e115aaef4c | ||
|
|
85217abc7a | ||
|
|
b48ff94ad1 | ||
|
|
23b43d29c5 | ||
|
|
c0e4de2feb | ||
|
|
5900ee701c | ||
|
|
fe09596d7c | ||
|
|
c1195691f3 | ||
|
|
bb1d2147a7 | ||
|
|
b80b94bd96 | ||
|
|
ff087b26a2 | ||
|
|
0a73cf1bfc | ||
|
|
3c56ade25f | ||
|
|
e6a0e67a38 | ||
|
|
d7b42f29f2 | ||
|
|
852bd457d8 | ||
|
|
b66e55c7e5 | ||
|
|
3ba30f7f09 | ||
|
|
f6a85acc07 | ||
|
|
990b0ac4cd | ||
|
|
29af9b5177 | ||
|
|
a8559be1b1 | ||
|
|
c8b0522fa6 | ||
|
|
70d5f60aa1 | ||
|
|
4335732d5d | ||
|
|
fc98d73d7e | ||
|
|
1275b80eb8 | ||
|
|
dabe283e4a | ||
|
|
b8a4df51ca | ||
|
|
2af7e73258 | ||
|
|
ed7e7c7ae0 | ||
|
|
152ee5a261 | ||
|
|
b33c3eb010 | ||
|
|
5a190422cd | ||
|
|
c4eefe267d | ||
|
|
b8dbaff539 | ||
|
|
dd32de31c8 | ||
|
|
9f771dd129 | ||
|
|
764d7575f7 | ||
|
|
04efce2d29 | ||
|
|
e3e371dcd3 | ||
|
|
4f1211f7fb | ||
|
|
4d3e59f716 | ||
|
|
816835e25c | ||
|
|
01f89730e1 | ||
|
|
55379e1142 | ||
|
|
df3f6999a3 | ||
|
|
d9cb06c270 | ||
|
|
31d2a902e5 | ||
|
|
593f2cfdbc | ||
|
|
6a44eadd2f | ||
|
|
0cc9715df1 | ||
|
|
50ccbcb444 | ||
|
|
12c9c53b90 | ||
|
|
4a9e1439f6 | ||
|
|
4aca9d471e | ||
|
|
af4ef3c76c | ||
|
|
a4cc7f358a | ||
|
|
be5fe4c727 | ||
|
|
5634b2287a | ||
|
|
5d5f75c41d | ||
|
|
256c467b05 | ||
|
|
5071c42e72 | ||
|
|
15f1139c31 | ||
|
|
d6526ed9da | ||
|
|
4928e3276d | ||
|
|
2bfc724e42 | ||
|
|
8bd705a3cb | ||
|
|
691892f48a | ||
|
|
22e1bba58e | ||
|
|
808f1240ac | ||
|
|
0e88fd047e | ||
|
|
80dc5cf1fb | ||
|
|
53e07d3a89 | ||
|
|
5fe5294133 | ||
|
|
4af6ff614c | ||
|
|
eaf52ee844 | ||
|
|
ab81c9e51d | ||
|
|
95d0af2958 | ||
|
|
9056011f67 | ||
|
|
ff45af0f34 | ||
|
|
4df062c72e | ||
|
|
721c59aea9 | ||
|
|
9ddbfa863a | ||
|
|
cd2b8f93e4 | ||
|
|
e2e025ed29 | ||
|
|
15441b60d3 | ||
|
|
71c4c90922 | ||
|
|
8d29501887 | ||
|
|
ea308bc77b | ||
|
|
ad427f8629 | ||
|
|
23b90dfc38 | ||
|
|
7f7d10cc89 | ||
|
|
14392e7720 | ||
|
|
3b75398786 | ||
|
|
5688115bb6 | ||
|
|
b1d951a96f | ||
|
|
ff1d5cd245 | ||
|
|
3bd972ad85 | ||
|
|
575a59ef75 | ||
|
|
9a0b7e826b | ||
|
|
ded9cd4ffa | ||
|
|
556e953045 | ||
|
|
4a1d0dfc31 | ||
|
|
73074389ed | ||
|
|
b0c6aa9a0f | ||
|
|
883320d5e7 | ||
|
|
b81f3c22be | ||
|
|
26d45ed4c1 | ||
|
|
b5d7335e35 | ||
|
|
bdee09ad02 | ||
|
|
a6a03b5851 | ||
|
|
8aa05de5e3 | ||
|
|
0e1226287b | ||
|
|
685085f6fe | ||
|
|
582350cc81 | ||
|
|
335698851b | ||
|
|
7b5b1a340b | ||
|
|
3e6f6fe8b5 | ||
|
|
c001b75d90 | ||
|
|
48b782ac6d | ||
|
|
0be1318b7e | ||
|
|
1524746a80 | ||
|
|
2963ab672c | ||
|
|
fe72463f55 | ||
|
|
831f872886 | ||
|
|
d9a28221dd | ||
|
|
62be1fcdb0 | ||
|
|
a7b8da9345 | ||
|
|
9e2e70ce7d | ||
|
|
21a2d6bb6c | ||
|
|
9a3188bcf5 | ||
|
|
9cd811393f | ||
|
|
8b86c49f42 | ||
|
|
52943009b7 | ||
|
|
00c16556de | ||
|
|
b8f6fe1a1a | ||
|
|
a9f063a394 | ||
|
|
b0ad0048b2 | ||
|
|
37d798f064 | ||
|
|
a9a8205be2 | ||
|
|
0bbc1808cc | ||
|
|
cd3d82c89e | ||
|
|
52c8e09ddb | ||
|
|
c214bfc96b | ||
|
|
7b8d7a6417 | ||
|
|
cfb7c268f5 | ||
|
|
285ea7ba26 | ||
|
|
603a831f02 | ||
|
|
edfe9f30f2 | ||
|
|
5228fa2d78 | ||
|
|
e4e267cc8b | ||
|
|
75ac73809b | ||
|
|
1b89d52504 | ||
|
|
b05a671789 | ||
|
|
30222dfb3f | ||
|
|
91aca3b7cf | ||
|
|
610e839e9e | ||
|
|
dcbc85b412 | ||
|
|
7133f8a547 | ||
|
|
792773be20 | ||
|
|
13816a14e1 | ||
|
|
8f02509ba6 | ||
|
|
87163c667a | ||
|
|
e45bebd1c7 | ||
|
|
d0cc19735d | ||
|
|
2c9e97c768 | ||
|
|
114cb8e9c8 | ||
|
|
2af30acbb2 | ||
|
|
6f9eedf98a | ||
|
|
e60bf30513 | ||
|
|
610ce3c307 | ||
|
|
f3e1f728d3 | ||
|
|
6f04ef9cf2 | ||
|
|
880204ebe5 | ||
|
|
406d8786c9 | ||
|
|
e11e68d508 | ||
|
|
3af44d08db | ||
|
|
d5b5b9afe1 | ||
|
|
0b537ae54c | ||
|
|
b364b3f453 | ||
|
|
ea31281149 | ||
|
|
ce34654924 | ||
|
|
2d34ba13cb | ||
|
|
eee315ea52 | ||
|
|
d2d73cbcbc | ||
|
|
c786f55370 | ||
|
|
8ff4bd5e0f | ||
|
|
169a22d643 | ||
|
|
4a8cda75ad | ||
|
|
1ebaf5be84 | ||
|
|
a05aae7d7a | ||
|
|
f9ea7890a8 | ||
|
|
0488d95f38 | ||
|
|
6d0d19dffb | ||
|
|
e3e0593162 | ||
|
|
7511aee558 | ||
|
|
6a3c35d97c | ||
|
|
032b4d4cc5 | ||
|
|
3fb8b483b4 | ||
|
|
e14788102c | ||
|
|
96d9859f45 | ||
|
|
56e6c038dc | ||
|
|
1f5da3d313 | ||
|
|
fc1aed3e99 | ||
|
|
4b754348f5 | ||
|
|
eee1a87742 | ||
|
|
1b52d228d4 | ||
|
|
3842222956 | ||
|
|
dce00d511b | ||
|
|
0de862191b | ||
|
|
485877a826 | ||
|
|
7478c27ce8 | ||
|
|
a5c216310b | ||
|
|
c55c98c4f2 | ||
|
|
6b3fc59143 | ||
|
|
6779b2bdfe | ||
|
|
7097946eee | ||
|
|
67ec8f3376 | ||
|
|
0723476fa5 | ||
|
|
a50173c259 | ||
|
|
280782b3ae | ||
|
|
5621394dc3 | ||
|
|
fa420c38d4 | ||
|
|
ae33fd278f | ||
|
|
68a779f76b | ||
|
|
f730dde6e7 | ||
|
|
8cf514408b | ||
|
|
72c8446eb7 | ||
|
|
f5d85e854f | ||
|
|
4e845f3972 | ||
|
|
03af112272 | ||
|
|
66c1433be1 | ||
|
|
621e2edd65 | ||
|
|
03045cb2d0 | ||
|
|
a572b803d2 | ||
|
|
7c52c41d7b | ||
|
|
21fea57d99 | ||
|
|
965fa17b7b | ||
|
|
826bb56d59 | ||
|
|
3f12a843a6 | ||
|
|
e895351481 | ||
|
|
1eac1e6cb4 | ||
|
|
0fab94639f | ||
|
|
348f2bb9da | ||
|
|
2f78d2eb94 | ||
|
|
4826d393d1 | ||
|
|
b82aa99bca | ||
|
|
080ebb9ee4 | ||
|
|
4f77af8e76 | ||
|
|
cffcea8d44 | ||
|
|
4622b6b14c | ||
|
|
395b962c3d | ||
|
|
f0f194e373 | ||
|
|
45615c9d81 | ||
|
|
d9e2669063 | ||
|
|
d31b63e48b | ||
|
|
5612636fe4 | ||
|
|
be19241630 | ||
|
|
2f243d2f52 | ||
|
|
8911458961 | ||
|
|
4aeee88ef8 | ||
|
|
936d3b3c30 | ||
|
|
0a89a07e6d | ||
|
|
79654bfde0 | ||
|
|
379ff3d781 | ||
|
|
c797408167 | ||
|
|
20ad945fbf | ||
|
|
702ec961ff | ||
|
|
5e4f8924fd | ||
|
|
f55fd7d9f2 | ||
|
|
ae29091aa6 | ||
|
|
e9f6715c6e | ||
|
|
7730cb3d25 | ||
|
|
c36291be60 | ||
|
|
c1164cda77 | ||
|
|
e770df42d9 | ||
|
|
14c717d698 | ||
|
|
b32bc66901 | ||
|
|
9e09a21eac | ||
|
|
f5c8d7509e | ||
|
|
95164b3556 | ||
|
|
4d06199e0d | ||
|
|
bd292ef782 | ||
|
|
b626d74a5f | ||
|
|
e7d4d2c39b | ||
|
|
48c9157fb1 | ||
|
|
e37fe163b7 | ||
|
|
4ca2d82da8 | ||
|
|
2850c5cfe6 | ||
|
|
75df1c82f1 | ||
|
|
754b995e1c | ||
|
|
bafa43d4c7 | ||
|
|
760f39d129 | ||
|
|
99b622ffd1 | ||
|
|
7b097e51c9 | ||
|
|
16735e1e68 | ||
|
|
18e24a6e4d | ||
|
|
29e15384ed | ||
|
|
229f610825 | ||
|
|
e9def90e9a | ||
|
|
f844179fd3 | ||
|
|
ccf6b0aa33 | ||
|
|
ca3cd7c3fa | ||
|
|
b5587d930e | ||
|
|
4c034da565 | ||
|
|
a1711b97b3 | ||
|
|
e293d56cc1 | ||
|
|
7f84e4242c | ||
|
|
ffb18422c1 | ||
|
|
33e00f2fb5 | ||
|
|
5c3bbb801d | ||
|
|
def3c95b7f | ||
|
|
bd3fa9a3db | ||
|
|
ef053c41fe | ||
|
|
03cda81af6 | ||
|
|
51f99db269 | ||
|
|
94c100c211 | ||
|
|
943cb99a84 | ||
|
|
eb90d1bcbb | ||
|
|
7156ef7daa | ||
|
|
fb89e06e2b | ||
|
|
6b77349b4a | ||
|
|
2c5582b9e9 | ||
|
|
7ce2c7edb5 | ||
|
|
b5bfe4c607 | ||
|
|
b9827f29e7 | ||
|
|
3ec1e8d9df | ||
|
|
55eb8dee4d | ||
|
|
da22677bf9 | ||
|
|
c654d42c14 | ||
|
|
4b945c839f | ||
|
|
e4b8fa3c26 | ||
|
|
6d67614004 | ||
|
|
1052b93c4f | ||
|
|
0692db3481 | ||
|
|
68f4891107 | ||
|
|
37a50f8996 | ||
|
|
352ab1a3f9 | ||
|
|
519de432c7 | ||
|
|
4d380ec645 | ||
|
|
0326d66b24 | ||
|
|
f392b4aeb3 | ||
|
|
a21a79e846 | ||
|
|
ca6d4a53c4 | ||
|
|
66e585eebe | ||
|
|
deebb497d3 | ||
|
|
466e93ea46 | ||
|
|
3008879d23 | ||
|
|
e8166bcb1b | ||
|
|
27908af67d | ||
|
|
c9288c5b92 | ||
|
|
188f17524f | ||
|
|
2b86786419 | ||
|
|
bcc8375958 | ||
|
|
f0bb588e32 | ||
|
|
3cf8fc4b35 | ||
|
|
64f0ba4a9a | ||
|
|
611fa3c7a4 | ||
|
|
69d23bc10f | ||
|
|
a6c3f3c082 | ||
|
|
64b8168916 | ||
|
|
27409530d7 | ||
|
|
b1c0a13386 | ||
|
|
fcb3bedf0d | ||
|
|
e68fdc396e | ||
|
|
a98a19acfa | ||
|
|
1a8ebf89db | ||
|
|
f086145fc4 | ||
|
|
8cbcd9777f | ||
|
|
2e47fe5815 | ||
|
|
61d19e65e2 | ||
|
|
c64fc2ea6d | ||
|
|
5b4c698a9c | ||
|
|
7f3a05ad0f | ||
|
|
a5c7c98af8 | ||
|
|
00148f4d5e | ||
|
|
9459b0f4f5 | ||
|
|
ee96875fcc | ||
|
|
4bc287ff48 | ||
|
|
73a9b6ec7d | ||
|
|
5cf365b4f5 | ||
|
|
5a239ef4b2 | ||
|
|
344c027a91 | ||
|
|
c1409d95a4 | ||
|
|
adb1ee4c0a | ||
|
|
5e389f99d4 | ||
|
|
c054e3d197 | ||
|
|
53fd03e115 | ||
|
|
99155920af | ||
|
|
05acf9da2c | ||
|
|
4ef90d4316 | ||
|
|
4d61d462ef | ||
|
|
d68c7eb5e4 | ||
|
|
3c284f09ab | ||
|
|
f59bd5ea23 | ||
|
|
b6e9c6da66 | ||
|
|
f8296ba0fc | ||
|
|
90e0228601 | ||
|
|
15eb278ab1 | ||
|
|
c1ba1707fc | ||
|
|
4ae6091442 | ||
|
|
5c63d2e031 | ||
|
|
166a67d5ee | ||
|
|
6b5c47882a | ||
|
|
b8b6d0d3ae | ||
|
|
144c156dc6 | ||
|
|
edc1c68efb | ||
|
|
e6446b3931 | ||
|
|
d1ade871f0 | ||
|
|
72ef8fc73a | ||
|
|
abaceb458f | ||
|
|
d22cdf8540 | ||
|
|
7f9d16e971 | ||
|
|
f48895cbf3 | ||
|
|
bd12a98533 | ||
|
|
ab63994c97 | ||
|
|
537a062d25 | ||
|
|
5d7064c01e | ||
|
|
f822898c2f | ||
|
|
b2cf48de33 | ||
|
|
188a0f1b3c | ||
|
|
e4d6c8c382 | ||
|
|
f787e99f16 | ||
|
|
128b345b9a | ||
|
|
4c758a62b1 | ||
|
|
2a672254a2 | ||
|
|
159d89036e | ||
|
|
407846a8c6 | ||
|
|
d8a8877875 | ||
|
|
448a9ec4e3 | ||
|
|
4819d3dc53 | ||
|
|
f8361f6389 | ||
|
|
596926008a | ||
|
|
9fbd71ec6b | ||
|
|
727d34d727 | ||
|
|
6d1f67fc59 | ||
|
|
6910f8e590 | ||
|
|
ca25f1fd20 | ||
|
|
0dc5ed38ed | ||
|
|
3694ffe61d | ||
|
|
2d3b410a3c | ||
|
|
38f2b965db | ||
|
|
cde4b0fb45 | ||
|
|
ed9a29b713 | ||
|
|
06cdd7e951 | ||
|
|
232dafa8d0 | ||
|
|
31ba99a7cd | ||
|
|
885b5c0e10 | ||
|
|
cd54cacb1b | ||
|
|
eb7433f3ac | ||
|
|
86ad2c5b4d | ||
|
|
68bcc8ea40 | ||
|
|
44228d91b0 | ||
|
|
dbf1040245 | ||
|
|
f223b9c292 | ||
|
|
3e18883b4b | ||
|
|
5090ad6111 | ||
|
|
11cbf9571b | ||
|
|
a15f387526 | ||
|
|
d56ba46cb2 | ||
|
|
dd8e15952f | ||
|
|
22a8b3095c | ||
|
|
5d68c800ed | ||
|
|
a5ff3c8287 | ||
|
|
06405073a9 | ||
|
|
0abd0a40db | ||
|
|
7cfc894914 | ||
|
|
230976a139 | ||
|
|
74ad5da477 | ||
|
|
d0d7f948f0 | ||
|
|
da7b5db350 | ||
|
|
bf902e1dd2 | ||
|
|
12cfddc611 | ||
|
|
1d8c0567a8 | ||
|
|
a9f2a8ddfe | ||
|
|
b5a1d3ace2 | ||
|
|
3499ee6ac7 | ||
|
|
eae062f815 | ||
|
|
2ce5e22193 | ||
|
|
c1c20aa427 | ||
|
|
af592f303c | ||
|
|
d0fc8fb005 | ||
|
|
a85b1f539a | ||
|
|
d3130f3648 | ||
|
|
f48825decb | ||
|
|
920266bce4 | ||
|
|
d714819759 | ||
|
|
517cce0371 | ||
|
|
e5bcbdf487 | ||
|
|
62b3a3dca9 | ||
|
|
fafb75b0e5 | ||
|
|
43cd7a4035 | ||
|
|
708ad63474 | ||
|
|
3f545317c9 | ||
|
|
c8d8f86a52 | ||
|
|
f1f2d40469 | ||
|
|
a15b73a6ee | ||
|
|
bcac1a3c53 | ||
|
|
a63c25b020 | ||
|
|
668b17b3fa | ||
|
|
3b9dc73210 | ||
|
|
1bd48be605 | ||
|
|
eca94d9081 | ||
|
|
615687b2ed | ||
|
|
ee95df673b | ||
|
|
e429773794 | ||
|
|
e89ec11427 | ||
|
|
43ab68b95d | ||
|
|
3455e8bd82 | ||
|
|
9899769713 | ||
|
|
8b584f8297 | ||
|
|
e088db72e0 | ||
|
|
8298c0ad16 | ||
|
|
f378fdc495 | ||
|
|
897f969017 | ||
|
|
17907964ac | ||
|
|
9431704230 | ||
|
|
1b5735543f | ||
|
|
886695d914 | ||
|
|
0fd6a6ca90 | ||
|
|
d391fd9a59 | ||
|
|
face536c39 | ||
|
|
3015f5e418 | ||
|
|
fc9f9e570e | ||
|
|
f1e925d416 | ||
|
|
a761f6a75c | ||
|
|
ea600a752b | ||
|
|
fa6d0c33d7 | ||
|
|
b516828b37 | ||
|
|
5a2589bd94 | ||
|
|
4f4bfdbba0 | ||
|
|
a830fcdb58 | ||
|
|
fd8a6a2c5d | ||
|
|
ac244ede10 | ||
|
|
5d4e7a9e31 | ||
|
|
1cce39f113 | ||
|
|
f9768c6352 | ||
|
|
fe134ab3ab | ||
|
|
f97ff51a72 | ||
|
|
b05bf30e57 | ||
|
|
6baef60a48 | ||
|
|
b101bfbf5f | ||
|
|
f3adbe7c8f | ||
|
|
a8c4c71dd7 | ||
|
|
eb5de9c12f | ||
|
|
23218e7ae2 | ||
|
|
8c5e4df741 | ||
|
|
92505f4aea | ||
|
|
d1a723a53a | ||
|
|
de45e4af6d | ||
|
|
ad3487376c | ||
|
|
3b3333a27f | ||
|
|
9c865b976b | ||
|
|
494b28cfb3 | ||
|
|
d4d1f2c697 | ||
|
|
00381ea64b | ||
|
|
9781d35aba | ||
|
|
ead0c96187 | ||
|
|
eed62bd9d4 | ||
|
|
6d5c44ab58 | ||
|
|
5b28bfd6c8 | ||
|
|
bad0cbc72a | ||
|
|
b6fb244f14 | ||
|
|
1c84145810 | ||
|
|
f945077e42 | ||
|
|
e0dd53b4ad | ||
|
|
267a1ff5b7 | ||
|
|
93cefc5280 | ||
|
|
b3e0955d88 | ||
|
|
c7339c33e0 | ||
|
|
1ac4030556 | ||
|
|
478871c06e | ||
|
|
d18d971962 | ||
|
|
b26e1a3c32 | ||
|
|
f12a9bb5ff | ||
|
|
cf850f8993 | ||
|
|
ec062f7241 | ||
|
|
fc798bc4ea | ||
|
|
bd65e0908f | ||
|
|
8a5a9e1ffe | ||
|
|
c6dace2335 | ||
|
|
32e0863675 | ||
|
|
8398d592de | ||
|
|
fa2ff3b9e9 | ||
|
|
3fb93c7cc8 | ||
|
|
8dddd98dfd | ||
|
|
17f16d016c | ||
|
|
21e1a80cec | ||
|
|
d4cc0ed3fb | ||
|
|
a4398c7c32 | ||
|
|
7ebd071273 | ||
|
|
369e8a19c5 | ||
|
|
d73587033f | ||
|
|
70bd1adbab | ||
|
|
f675faade2 | ||
|
|
3c895abefa | ||
|
|
ff1b97bcdb | ||
|
|
19ad74ac52 | ||
|
|
45df585268 | ||
|
|
deb212bbfc | ||
|
|
1fa6b3cd17 | ||
|
|
b0596ab8fb | ||
|
|
1e59bb518a | ||
|
|
efcbb02e83 | ||
|
|
d8a12490f5 | ||
|
|
b5a75c4215 | ||
|
|
5803c3c200 | ||
|
|
c90801e629 | ||
|
|
8073a92614 | ||
|
|
c4c7e45886 | ||
|
|
6a6ae217a3 | ||
|
|
30a0fac952 | ||
|
|
133946af0c | ||
|
|
c6ae4234b8 | ||
|
|
1f55e40a8a |
No files matched your search
@@ -0,0 +1,13 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [rui314] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Build all tarballs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
release:
|
||||
types: [ published ]
|
||||
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
|
||||
+141
-48
@@ -1,56 +1,149 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
env:
|
||||
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
|
||||
jobs:
|
||||
build-clang:
|
||||
runs-on: ubuntu-20.04
|
||||
build-sanitizers:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- ''
|
||||
- '-DMOLD_USE_ASAN=On'
|
||||
- '-DMOLD_USE_TSAN=On'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: rui314/setup-mold@v1
|
||||
with:
|
||||
make-default: false
|
||||
- name: apt-get
|
||||
run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install git build-essential libstdc++-10-dev cmake clang libssl-dev zlib1g-dev libxxhash-dev libtbb-dev git bsdmainutils dwarfdump pkg-config
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make CC=clang CXX=clang++ LDFLAGS=-fuse-ld=mold -j$(nproc)
|
||||
- name: make test
|
||||
run: make -j$(nproc) test
|
||||
build-gcc:
|
||||
runs-on: ubuntu-20.04
|
||||
container: gcc:11.1.0
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: apt-get
|
||||
- uses: actions/checkout@v4
|
||||
- uses: rui314/setup-mold@staging
|
||||
- run: sudo ./install-build-deps.sh
|
||||
- name: build
|
||||
run: |
|
||||
dpkg --add-architecture i386 && apt-get update && apt-get install -y sudo libssl-dev zlib1g-dev libxxhash-dev cmake clang bsdmainutils dwarfdump qemu-user gcc-10-i686-linux-gnu gcc-10-aarch64-linux-gnu gcc-10-riscv64-linux-gnu gcc-10-arm-linux-gnueabihf g++-10-i686-linux-gnu g++-10-aarch64-linux-gnu g++-10-riscv64-linux-gnu g++-10-arm-linux-gnueabihf
|
||||
ln -sf /usr/bin/i686-linux-gnu-gcc-10 /usr/bin/i686-linux-gnu-gcc
|
||||
ln -sf /usr/bin/i686-linux-gnu-g++-10 /usr/bin/i686-linux-gnu-g++
|
||||
ln -sf /usr/bin/aarch64-linux-gnu-gcc-10 /usr/bin/aarch64-linux-gnu-gcc
|
||||
ln -sf /usr/bin/aarch64-linux-gnu-g++-10 /usr/bin/aarch64-linux-gnu-g++
|
||||
ln -sf /usr/bin/arm-linux-gnueabihf-gcc-10 /usr/bin/arm-linux-gnueabihf-gcc
|
||||
ln -sf /usr/bin/arm-linux-gnueabihf-g++-10 /usr/bin/arm-linux-gnueabihf-g++
|
||||
ln -sf /usr/bin/riscv64-linux-gnu-gcc-10 /usr/bin/riscv64-linux-gnu-gcc
|
||||
ln -sf /usr/bin/riscv64-linux-gnu-g++-10 /usr/bin/riscv64-linux-gnu-g++
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make CC=gcc CXX=g++ -j$(nproc)
|
||||
- name: make test
|
||||
run: make -j$(nproc) test-all
|
||||
build-macos:
|
||||
runs-on: macos-11
|
||||
sudo apt-get install -y clang-18 clang gcc-multilib gdb dwarfdump zstd
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 ${{ matrix.target }} ..
|
||||
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-clang
|
||||
path: |
|
||||
build
|
||||
!build/CMakeFiles
|
||||
|
||||
build-multi-archs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: brew
|
||||
run: brew install xxhash
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make -j$(sysctl -n hw.logicalcpu)
|
||||
- name: make test
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make -j$(sysctl -n hw.logicalcpu) test
|
||||
- uses: actions/checkout@v4
|
||||
- name: install-build-deps
|
||||
run: |
|
||||
sudo ./install-build-deps.sh
|
||||
sudo ./install-cross-tools.sh
|
||||
sudo .github/workflows/install-extras.sh
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
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-multi-archs
|
||||
path: |
|
||||
build
|
||||
!build/CMakeFiles
|
||||
|
||||
build-distros:
|
||||
strategy:
|
||||
matrix:
|
||||
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
|
||||
container: ${{ matrix.distro }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: ./install-build-deps.sh
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
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
|
||||
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
|
||||
steps:
|
||||
- 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
|
||||
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
|
||||
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.01.20/riscv32-glibc-ubuntu-22.04-gcc-nightly-2025.01.20-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.02.21/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_14.2.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/2024.11.01/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/843185/sde-external-9.48.0-2024-11-25-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
|
||||
ln -s /sde/sde64 /usr/bin
|
||||
@@ -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)
|
||||
+4
-7
@@ -1,14 +1,11 @@
|
||||
*.o
|
||||
*.so
|
||||
*~
|
||||
**/.DS_Store
|
||||
.cache
|
||||
compile_commands.json
|
||||
/mold
|
||||
/test/tmp
|
||||
options.inc
|
||||
core
|
||||
ld
|
||||
ld64.mold
|
||||
gentoo
|
||||
out
|
||||
/mold-*.tar.gz
|
||||
/build*
|
||||
/mold
|
||||
/dist
|
||||
+461
@@ -0,0 +1,461 @@
|
||||
# You can customize a build by specifying CMake options. An option may be
|
||||
# given in the -Dvariable=value form. For a boolean variable, `ON` or `1`
|
||||
# means true, while `OFF` or `0` means false.
|
||||
#
|
||||
# Here are a couple of common cmake options:
|
||||
#
|
||||
# -DCMAKE_C_COMPILER=<command-name>
|
||||
#
|
||||
# Specifies the C compiler name to use. The default value is `cc`.
|
||||
#
|
||||
# -DCMAKE_CXX_COMPILER=<command-name>
|
||||
#
|
||||
# Specifies the C++ compiler name to use. The default value is `c++`.
|
||||
#
|
||||
# -DCMAKE_INSTALL_PREFIX=<directory>
|
||||
#
|
||||
# Specifies the install target directory. The default value is `/usr/local`.
|
||||
#
|
||||
# -DCMAKE_BUILD_TYPE=[Debug | Release | RelWithDebInfo | MinSizeRel]
|
||||
#
|
||||
# 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 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
|
||||
# artifacts to a temporary target directory, run `cmake --install . --prefix
|
||||
# <dir>`.
|
||||
#
|
||||
# You can see the current cmake variables and their values by running
|
||||
# `cmake -N -L .` in a build directory.
|
||||
#
|
||||
# Note that in this file, we provide various dials and knobs to configure
|
||||
# how to build mold. However, as a policy, we do not provide a way to
|
||||
# enable/disable any individual mold's feature. In other words, we do not
|
||||
# provide options like `--enable-foo` or `--disable-foo`. The motivation
|
||||
# behind 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.40.0)
|
||||
|
||||
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)
|
||||
target_link_options(mold PRIVATE -fuse-ld=mold)
|
||||
|
||||
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
|
||||
-Wno-unused-function
|
||||
-Wno-unused-parameter
|
||||
-Wno-missing-field-initializers
|
||||
-ggnu-pubnames)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_options(mold PRIVATE -D_GLIBCXX_ASSERTIONS)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
|
||||
set(OPENBSD ON)
|
||||
endif()
|
||||
|
||||
# Build mold with -flto if -DMOLD_LTO=ON
|
||||
option(MOLD_LTO "Build mold with link-time optimization enabled")
|
||||
if(MOLD_LTO)
|
||||
set_property(TARGET mold PROPERTY INTERPROCEDURAL_OPTIMIZATION ON)
|
||||
endif()
|
||||
|
||||
# Enable AddressSanitizer if -DMOLD_USE_ASAN=ON
|
||||
option(MOLD_USE_ASAN "Build mold with AddressSanitizer" OFF)
|
||||
if(MOLD_USE_ASAN)
|
||||
target_compile_options(mold PRIVATE -fsanitize=address -fsanitize=undefined)
|
||||
target_link_options(mold PRIVATE -fsanitize=address -fsanitize=undefined)
|
||||
endif()
|
||||
|
||||
# Enabled ThreadSanitizer if -DMOLD_USE_TSAN=ON
|
||||
option(MOLD_USE_TSAN "Build mold with ThreadSanitizer" OFF)
|
||||
if(MOLD_USE_TSAN)
|
||||
target_compile_options(mold PRIVATE -fsanitize=thread)
|
||||
target_link_options(mold PRIVATE -fsanitize=thread)
|
||||
endif()
|
||||
|
||||
# Statically-link libstdc++ if -DMOLD_MOSTLY_STATIC=ON.
|
||||
#
|
||||
# This option is intended to be used by `./dist.sh` script to create a
|
||||
# mold binary that works on various Linux distros. You probably don't
|
||||
# need nor want to set this to ON.
|
||||
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and some other libraries" OFF)
|
||||
if(MOLD_MOSTLY_STATIC)
|
||||
target_link_options(mold PRIVATE -static-libstdc++)
|
||||
endif()
|
||||
|
||||
# Find zlib. If libz.so is not found, we compile a bundled one and
|
||||
# statically-link it to mold.
|
||||
find_package(ZLIB QUIET)
|
||||
if(ZLIB_FOUND 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>)
|
||||
target_link_libraries(mold PRIVATE zlibstatic)
|
||||
endif()
|
||||
|
||||
# Find BLAKE3 cryptographic hash library. Just like zlib, if libblkae3.so
|
||||
# is not found, we compile a bundled one and statically-link it to mold.
|
||||
find_package(BLAKE3 QUIET)
|
||||
if(BLAKE3_FOUND AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE BLAKE3::blake3)
|
||||
else()
|
||||
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 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)
|
||||
|
||||
if(HAVE_ZSTD_H AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE zstd)
|
||||
else()
|
||||
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(libzstd_static PRIVATE
|
||||
ZSTD_BUILD_STATIC=1
|
||||
ZSTD_BUILD_SHARED=0
|
||||
ZSTD_BUILD_PROGRAMS=0
|
||||
ZSTD_MULTITHREAD_SUPPORT=0
|
||||
ZSTD_BUILD_TESTS=0)
|
||||
target_include_directories(mold PUBLIC third-party/zstd/lib)
|
||||
target_link_libraries(mold PRIVATE libzstd_static)
|
||||
endif()
|
||||
|
||||
# Find mimalloc. mimalloc is an alternative malloc implementation
|
||||
# optimized for multi-threaded applications.
|
||||
#
|
||||
# If you want to use the usual libc's malloc, pass -DMOLD_USE_MIMALLOC=OFF.
|
||||
#
|
||||
# We enable mimalloc by default for 64-bit targets. It doesn't seem to
|
||||
# be stable on 32-bit targets.
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_MIMALLOC "Use mimalloc" ON
|
||||
"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
|
||||
MOLD_USE_MIMALLOC OFF)
|
||||
|
||||
# By default, we build a bundled mimalloc and statically-link it to
|
||||
# mold. If you want to dynamically link to the system's
|
||||
# libmimalloc.so, pass -DMOLD_USE_SYSTEM_MIMALLOC=ON.
|
||||
if(MOLD_USE_MIMALLOC)
|
||||
if(MOLD_USE_SYSTEM_MIMALLOC)
|
||||
find_package(mimalloc REQUIRED)
|
||||
target_link_libraries(mold PRIVATE mimalloc)
|
||||
else()
|
||||
function(mold_add_mimalloc)
|
||||
set(MI_BUILD_STATIC ON CACHE INTERNAL "")
|
||||
set(MI_BUILD_TESTS OFF CACHE INTERNAL "")
|
||||
add_subdirectory(third-party/mimalloc EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(mimalloc-static PRIVATE MI_USE_ENVIRON=0)
|
||||
target_link_libraries(mold PRIVATE mimalloc-static)
|
||||
endfunction()
|
||||
|
||||
mold_add_mimalloc()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Find TBB. TBB (OneTBB or Intel TBB) is a high-level threading library.
|
||||
# Use of this library is mandatory.
|
||||
#
|
||||
# By default, we build a bundled one and statically-link the library
|
||||
# to mold. If you want to link to the system's libtbb2.so, pass
|
||||
# -DMOLD_USE_SYSTEM_TBB=ON.
|
||||
option(MOLD_USE_SYSTEM_TBB "Use system or vendored TBB" OFF)
|
||||
if(MOLD_USE_SYSTEM_TBB)
|
||||
find_package(TBB REQUIRED)
|
||||
target_link_libraries(mold PRIVATE TBB::tbb)
|
||||
else()
|
||||
function(mold_add_tbb)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
set(TBB_TEST OFF CACHE INTERNAL "")
|
||||
set(TBB_STRICT OFF CACHE INTERNAL "")
|
||||
add_subdirectory(third-party/tbb EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(tbb PRIVATE __TBB_DYNAMIC_LOAD_ENABLED=0)
|
||||
target_link_libraries(mold PRIVATE TBB::tbb)
|
||||
endfunction()
|
||||
|
||||
mold_add_tbb()
|
||||
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")
|
||||
message(FATAL_ERROR
|
||||
"Your compiler is not supported; install Clang from Visual Studio Installer and re-run cmake with '-T clangcl'")
|
||||
endif()
|
||||
|
||||
target_compile_definitions(mold PRIVATE NOGDI NOMINMAX)
|
||||
if(MINGW)
|
||||
target_compile_definitions(mold PRIVATE _WIN32_WINNT=0xA00)
|
||||
target_link_libraries(mold PRIVATE bcrypt)
|
||||
endif()
|
||||
else()
|
||||
include(CheckLibraryExists)
|
||||
check_library_exists(m pow "" LIBM_FOUND)
|
||||
if(LIBM_FOUND)
|
||||
target_link_libraries(mold PRIVATE m)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Build mold-wrapper.so
|
||||
if(NOT APPLE AND NOT WIN32)
|
||||
add_library(mold-wrapper SHARED)
|
||||
install(TARGETS mold-wrapper DESTINATION ${CMAKE_INSTALL_LIBDIR}/mold)
|
||||
|
||||
# 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 src/mold-wrapper.c)
|
||||
endif()
|
||||
|
||||
# If atomics doesn't work by default, add -latomic.
|
||||
# We need the flag on riscv, armv6 and m68k.
|
||||
include(CheckCXXSourceCompiles)
|
||||
check_cxx_source_compiles("#include <atomic>
|
||||
int main() {
|
||||
std::atomic_uint8_t a;
|
||||
std::atomic_uint16_t b;
|
||||
std::atomic_uint32_t c;
|
||||
std::atomic_uint64_t d;
|
||||
return ++a + ++b + ++c + ++d;
|
||||
}" HAVE_FULL_ATOMIC_SUPPORT)
|
||||
|
||||
if(NOT HAVE_FULL_ATOMIC_SUPPORT)
|
||||
target_link_libraries(mold PRIVATE atomic)
|
||||
endif()
|
||||
|
||||
# Add -pthread
|
||||
if(NOT APPLE AND NOT MSVC)
|
||||
target_compile_options(mold PRIVATE -pthread)
|
||||
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}/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(lib/config.h.in config.h)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# Almost all functions are template in mold which take a target type
|
||||
# (e.g. X86_64) as its type parameter. Since we suport more than 10
|
||||
# targets, compiling a single source file for all the targets is very
|
||||
# slow.
|
||||
#
|
||||
# As a workaround, we create a .cc file for each target and spawn many
|
||||
# compiler instances. This is hacky but greatly reduces compile time
|
||||
# on a multicore machine.
|
||||
list(APPEND MOLD_ELF_TARGETS
|
||||
X86_64 I386 ARM64LE ARM64BE ARM32LE ARM32BE RV32LE RV32BE RV64LE
|
||||
RV64BE PPC32 PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE
|
||||
LOONGARCH32 LOONGARCH64)
|
||||
|
||||
list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
src/arch-arm32.cc
|
||||
src/arch-arm64.cc
|
||||
src/arch-loongarch.cc
|
||||
src/arch-riscv.cc
|
||||
src/arch-sh4.cc
|
||||
src/cmdline.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
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
if(NOT EXISTS ${PATH})
|
||||
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
|
||||
#define MOLD_TARGET ${TARGET}
|
||||
#include \"${CMAKE_SOURCE_DIR}/${SOURCE}\"
|
||||
")
|
||||
endif()
|
||||
target_sources(mold PRIVATE ${PATH})
|
||||
endfunction()
|
||||
|
||||
foreach (SOURCE IN LISTS MOLD_ELF_TEMPLATE_FILES)
|
||||
foreach(TARGET IN LISTS MOLD_ELF_TARGETS)
|
||||
mold_instantiate_templates(${SOURCE} ${TARGET})
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
# Add other non-template source files.
|
||||
target_sources(mold PRIVATE
|
||||
lib/compress.cc
|
||||
lib/crc32.cc
|
||||
lib/demangle.cc
|
||||
lib/filepath.cc
|
||||
lib/glob.cc
|
||||
lib/hyperloglog.cc
|
||||
lib/mimalloc.cc
|
||||
lib/multi-glob.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/elf.cc
|
||||
src/entry.cc
|
||||
third-party/rust-demangle/rust-demangle.c
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
target_sources(mold PRIVATE
|
||||
lib/jobs-win32.cc
|
||||
lib/mapped-file-win32.cc
|
||||
lib/signal-win32.cc
|
||||
)
|
||||
else()
|
||||
target_sources(mold PRIVATE
|
||||
lib/jobs-unix.cc
|
||||
lib/mapped-file-unix.cc
|
||||
lib/signal-unix.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
include(CTest)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
# Create the ld symlinks required for testing
|
||||
if(NOT WIN32)
|
||||
add_custom_command(
|
||||
TARGET mold POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld
|
||||
BYPRODUCTS ld
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
VERBATIM)
|
||||
endif()
|
||||
|
||||
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/)
|
||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
|
||||
function(mold_install_relative_symlink OLD NEW)
|
||||
install(CODE "
|
||||
get_filename_component(PREFIX_ABS \${CMAKE_INSTALL_PREFIX}/ ABSOLUTE)
|
||||
get_filename_component(OLD_ABS ${OLD} ABSOLUTE BASE_DIR \${PREFIX_ABS})
|
||||
get_filename_component(NEW_ABS ${NEW} ABSOLUTE BASE_DIR \${PREFIX_ABS})
|
||||
get_filename_component(NEW_DIR \${NEW_ABS} DIRECTORY)
|
||||
file(RELATIVE_PATH OLD_REL \${NEW_DIR} \${OLD_ABS})
|
||||
message(STATUS \"Installing symlink: \$ENV{DESTDIR}\${NEW_ABS} -> \${OLD_REL}\")
|
||||
file(MAKE_DIRECTORY \$ENV{DESTDIR}\${NEW_DIR})
|
||||
file(CREATE_LINK \${OLD_REL} \$ENV{DESTDIR}\${NEW_ABS} SYMBOLIC)")
|
||||
endfunction()
|
||||
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
|
||||
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld${CMAKE_EXECUTABLE_SUFFIX})
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
|
||||
${CMAKE_INSTALL_BINDIR}/ld.mold${CMAKE_EXECUTABLE_SUFFIX})
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
|
||||
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
|
||||
endif()
|
||||
@@ -1,54 +0,0 @@
|
||||
# Contributing to mold
|
||||
|
||||
Summary: When you send a patch, you must agree that the patch is
|
||||
released under the dual license of the GNU AGPLv3 and the MIT license.
|
||||
As an indication of agreement, please add a `Signed-off-by` line to
|
||||
your commit message using `git commit -s`.
|
||||
|
||||
Here is the long official version:
|
||||
|
||||
mold is currently licensed under the GNU AGPLv3, but I want to relicense
|
||||
it under a more liberal license in the future. To make this possible,
|
||||
I want all patches to be submitted under the dual license of the GNU
|
||||
AGPLv3 and the MIT license (the "Dual License") from the beginning.
|
||||
|
||||
To make it clear that contributors have agreed to release their
|
||||
patches under the Dual License, I introduced a sign-off procedure just
|
||||
like the Linux kernel project did. The sign-off is a simple line at
|
||||
the end of the git commit message, which certifies that you wrote it
|
||||
or otherwise have the right to pass it on as an open-source patch. The
|
||||
rules are pretty simple: if you can certify the below:
|
||||
|
||||
> ## Developer's Certificate of Origin
|
||||
>
|
||||
> By making a contribution to this project, I certify that:
|
||||
>
|
||||
> 1. The contribution was created in whole or in part by me and I
|
||||
> have the right to submit it under the Dual License; or
|
||||
>
|
||||
> 2. The contribution is based upon previous work that, to the best
|
||||
> of my knowledge, is covered under the the Dual License and I
|
||||
> have the right under the Dual License to submit that work with
|
||||
> modifications, whether created in whole or in part by me, under
|
||||
> the Dual License (unless I am permitted to submit under
|
||||
> different licenses), as indicated in the file; or
|
||||
>
|
||||
> 3. The contribution was provided directly to me by some other
|
||||
> person who certified (1), (2) or (3) and I have not modified it.
|
||||
>
|
||||
> 4. I understand and agree that this project and the contribution
|
||||
> are public and that a record of the contribution (including all
|
||||
> personal information I submit with it, including my sign-off) is
|
||||
> maintained indefinitely and may be redistributed consistent with
|
||||
> this project or the open source license(s) involved.
|
||||
|
||||
then you just add a line saying:
|
||||
|
||||
```
|
||||
Signed-off-by: Random J Developer <[email protected]>
|
||||
```
|
||||
|
||||
using your real name (sorry, no pseudonyms or anonymous contributions.)
|
||||
This will be done for you automatically if you use ``git commit -s``.
|
||||
Reverts should also include "Signed-off-by". ``git revert -s`` does that
|
||||
for you.
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
# This dockerfile creates a reproducible build environment for mold.
|
||||
#
|
||||
# $ docker build -t rui314/mold-builder:v1-$(uname -m) .
|
||||
# $ docker push rui314/mold-builder:v1-$(uname -m)
|
||||
|
||||
FROM ubuntu:18.04
|
||||
RUN apt-get update && \
|
||||
TZ=Europe/London apt-get install -y tzdata && \
|
||||
apt-get install -y --no-install-recommends software-properties-common && \
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
|
||||
apt-get install -y --no-install-recommends build-essential git \
|
||||
wget pkg-config cmake libstdc++-11-dev zlib1g-dev gpg gpg-agent && \
|
||||
bash -c "$(wget -O- https://apt.llvm.org/llvm.sh)" && \
|
||||
apt-get install -y --no-install-recommends clang-14 && \
|
||||
apt clean && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
cd / && \
|
||||
wget -O- https://www.openssl.org/source/openssl-3.0.2.tar.gz | tar xzf - && \
|
||||
mv openssl-3.0.2 openssl && \
|
||||
cd openssl && \
|
||||
./Configure --prefix=/openssl && \
|
||||
make -j$(nproc)
|
||||
@@ -1,671 +1,21 @@
|
||||
Note: I'm looking for a sponsor who wants to purchase the copyright
|
||||
of this work and relicense it under a more liberal license such as
|
||||
the MIT license. For now, mold is released under the GNU AGPL v3.
|
||||
|
||||
If you just want to use mold but do not want to use AGPL software
|
||||
in your organization, a commercial license is available. Please
|
||||
contact [email protected].
|
||||
|
||||
--
|
||||
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Rui Ueyama
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,224 +0,0 @@
|
||||
# If you want to enable ASAN, run `make` with the following options:
|
||||
#
|
||||
# make CXXFLAGS='-fsanitize=address -g' LDFLAGS=-fsanitize=address USE_MIMALLOC=0
|
||||
|
||||
VERSION = 1.2.0
|
||||
|
||||
PREFIX = /usr/local
|
||||
BINDIR = $(PREFIX)/bin
|
||||
LIBDIR = $(PREFIX)/lib
|
||||
LIBEXECDIR = $(PREFIX)/libexec
|
||||
MANDIR = $(PREFIX)/share/man
|
||||
|
||||
INSTALL = install
|
||||
INSTALL_PROGRAM = $(INSTALL)
|
||||
INSTALL_DATA = $(INSTALL) -m 644
|
||||
|
||||
D = $(DESTDIR)
|
||||
|
||||
# CXX defaults to `g++`. Rewrite it with a vendor-neutral compiler
|
||||
# name `c++`.
|
||||
ifeq ($(origin CXX), default)
|
||||
CXX = c++
|
||||
endif
|
||||
|
||||
# Allow overriding pkg-config binary
|
||||
PKG_CONFIG = pkg-config
|
||||
|
||||
# If you want to keep symbols in the installed binary, run make with
|
||||
# `STRIP=true` to run /bin/true instead of the strip command.
|
||||
STRIP = strip
|
||||
|
||||
SRCS = $(wildcard *.cc elf/*.cc macho/*.cc)
|
||||
OBJS = $(SRCS:%.cc=out/%.o)
|
||||
|
||||
OS := $(shell uname -s)
|
||||
ARCH := $(shell uname -m)
|
||||
|
||||
IS_ANDROID = 0
|
||||
ifneq ($(findstring -android,$(shell $(CC) -dumpmachine)),)
|
||||
IS_ANDROID = 1
|
||||
endif
|
||||
|
||||
# If you want to compile mold for debugging, invoke make as
|
||||
# `make CXXFLAGS=-g`.
|
||||
CFLAGS = -O2
|
||||
CXXFLAGS = -O2
|
||||
|
||||
MOLD_CXXFLAGS := -std=c++20 -fno-exceptions -fno-unwind-tables \
|
||||
-fno-asynchronous-unwind-tables -Ithird-party/xxhash \
|
||||
-DMOLD_VERSION=\"$(VERSION)\" -DLIBDIR="\"$(LIBDIR)\""
|
||||
|
||||
MOLD_LDFLAGS := -pthread -lz -lm -ldl
|
||||
|
||||
GIT_HASH := $(shell [ -d .git ] && git rev-parse HEAD)
|
||||
ifneq ($(GIT_HASH),)
|
||||
MOLD_CXXFLAGS += -DGIT_HASH=\"$(GIT_HASH)\"
|
||||
endif
|
||||
|
||||
LTO = 0
|
||||
ifeq ($(LTO), 1)
|
||||
CXXFLAGS += -flto -O3
|
||||
LDFLAGS += -flto
|
||||
endif
|
||||
|
||||
# By default, we want to use mimalloc as a memory allocator. mimalloc
|
||||
# is disabled on macOS and Android because it didn't work on those hosts.
|
||||
USE_MIMALLOC = 1
|
||||
ifeq ($(OS), Darwin)
|
||||
USE_MIMALLOC = 0
|
||||
else ifeq ($(IS_ANDROID), 1)
|
||||
USE_MIMALLOC = 0
|
||||
endif
|
||||
|
||||
ifeq ($(USE_MIMALLOC), 1)
|
||||
ifdef SYSTEM_MIMALLOC
|
||||
MOLD_CXXFLAGS += -DUSE_SYSTEM_MIMALLOC
|
||||
MOLD_LDFLAGS += -lmimalloc
|
||||
else
|
||||
MIMALLOC_LIB = out/mimalloc/libmimalloc.a
|
||||
MOLD_CXXFLAGS += -Ithird-party/mimalloc/include
|
||||
MOLD_LDFLAGS += -Wl,-whole-archive $(MIMALLOC_LIB) -Wl,-no-whole-archive
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef SYSTEM_TBB
|
||||
MOLD_LDFLAGS += -ltbb
|
||||
else
|
||||
TBB_LIB = out/tbb/libs/libtbb.a
|
||||
MOLD_LDFLAGS += $(TBB_LIB)
|
||||
MOLD_CXXFLAGS += -Ithird-party/tbb/include
|
||||
endif
|
||||
|
||||
ifeq ($(OS), Linux)
|
||||
ifeq ($(IS_ANDROID), 0)
|
||||
# glibc before 2.17 need librt for clock_gettime
|
||||
MOLD_LDFLAGS += -Wl,-push-state -Wl,-as-needed -lrt -Wl,-pop-state
|
||||
endif
|
||||
endif
|
||||
|
||||
NEEDS_LIBCRYPTO = 0
|
||||
ifneq ($(OS), Darwin)
|
||||
NEEDS_LIBCRYPTO = 1
|
||||
endif
|
||||
|
||||
ifeq ($(NEEDS_LIBCRYPTO), 1)
|
||||
MOLD_CXXFLAGS += $(shell $(PKG_CONFIG) --cflags-only-I openssl)
|
||||
MOLD_LDFLAGS += $(shell $(PKG_CONFIG) --libs-only-L openssl) -lcrypto
|
||||
endif
|
||||
|
||||
# '-latomic' flag is needed building on riscv64 system
|
||||
# RV32 system doesn't tested yet
|
||||
# seems like '-atomic' would be better but not working.
|
||||
ifeq ($(ARCH), riscv64)
|
||||
MOLD_LDFLAGS += -latomic
|
||||
endif
|
||||
|
||||
# -Wc++11-narrowing is a fatal error on Android, so disable it.
|
||||
ifeq ($(IS_ANDROID), 1)
|
||||
MOLD_CXXFLAGS += -Wno-c++11-narrowing
|
||||
endif
|
||||
|
||||
ifeq ($(OS), Linux)
|
||||
MOLD_WRAPPER_LDFLAGS = -Wl,-push-state -Wl,-no-as-needed -ldl -Wl,-pop-state
|
||||
endif
|
||||
|
||||
DEPFLAGS = -MT $@ -MMD -MP -MF out/$*.d
|
||||
|
||||
all: mold mold-wrapper.so
|
||||
|
||||
-include $(SRCS:%.cc=out/%.d)
|
||||
|
||||
mold: $(OBJS) $(MIMALLOC_LIB) $(TBB_LIB)
|
||||
$(CXX) $(OBJS) -o $@ $(MOLD_LDFLAGS) $(LDFLAGS)
|
||||
ln -sf mold ld
|
||||
ln -sf mold ld64.mold
|
||||
|
||||
mold-wrapper.so: elf/mold-wrapper.c
|
||||
$(CC) $(DEPFLAGS) $(CFLAGS) -fPIC -shared -o $@ $< $(MOLD_WRAPPER_LDFLAGS) $(LDFLAGS)
|
||||
|
||||
out/%.o: %.cc out/elf/.keep out/macho/.keep
|
||||
$(CXX) $(MOLD_CXXFLAGS) $(DEPFLAGS) $(CXXFLAGS) -c -o $@ $<
|
||||
|
||||
out/elf/.keep out/macho/.keep:
|
||||
mkdir -p $(@D)
|
||||
touch $@
|
||||
|
||||
$(MIMALLOC_LIB):
|
||||
mkdir -p out/mimalloc
|
||||
(cd out/mimalloc; CFLAGS=-DMI_USE_ENVIRON=0 cmake -G'Unix Makefiles' ../../third-party/mimalloc)
|
||||
$(MAKE) -C out/mimalloc mimalloc-static
|
||||
|
||||
$(TBB_LIB):
|
||||
mkdir -p out/tbb
|
||||
(cd out/tbb; cmake -G'Unix Makefiles' -DBUILD_SHARED_LIBS=OFF -DTBB_TEST=OFF -DCMAKE_CXX_FLAGS="$(CXXFLAGS) -D__TBB_DYNAMIC_LOAD_ENABLED=0" -DTBB_STRICT=OFF ../../third-party/tbb)
|
||||
$(MAKE) -C out/tbb tbb
|
||||
(cd out/tbb; ln -sf *_relwithdebinfo libs)
|
||||
|
||||
test tests check: all
|
||||
ifeq ($(OS), Darwin)
|
||||
$(MAKE) -C test -f Makefile.darwin --no-print-directory
|
||||
else
|
||||
$(MAKE) -C test -f Makefile.linux --no-print-directory --output-sync
|
||||
endif
|
||||
@if test -t 1; then \
|
||||
printf '\e[32mPassed all tests\e[0m\n'; \
|
||||
else \
|
||||
echo 'Passed all tests'; \
|
||||
fi
|
||||
|
||||
test-x86-64: all
|
||||
@echo x86_64
|
||||
CC=x86_64-linux-gnu-gcc CXX=x86_64-linux-gnu-g++ GCC=x86_64-linux-gnu-gcc GXX=x86_64-linux-gnu-g++ OBJDUMP=x86_64-linux-gnu-objdump MACHINE=x86_64 QEMU='qemu-x86_64 -L /usr/x86_64-linux-gnu' $(MAKE) test
|
||||
|
||||
test-i386: all
|
||||
@echo i386
|
||||
CC=i686-linux-gnu-gcc CXX=i686-linux-gnu-g++ GCC=i686-linux-gnu-gcc GXX=i686-linux-gnu-g++ OBJDUMP=x86_64-linux-gnu-objdump MACHINE=i386 QEMU='qemu-i386 -L /usr/i686-linux-gnu' $(MAKE) test
|
||||
|
||||
test-arm64: all
|
||||
@echo arm64
|
||||
CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ GCC=aarch64-linux-gnu-gcc GXX=aarch64-linux-gnu-g++ OBJDUMP=aarch64-linux-gnu-objdump MACHINE=aarch64 QEMU='qemu-aarch64 -L /usr/aarch64-linux-gnu' $(MAKE) test
|
||||
|
||||
test-arm32: all
|
||||
@echo arm
|
||||
CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ GCC=arm-linux-gnueabihf-gcc GXX=arm-linux-gnueabihf-g++ OBJDUMP=arm-linux-gnueabihf-objdump MACHINE=arm QEMU='qemu-arm -L /usr/arm-linux-gnueabihf' $(MAKE) test
|
||||
|
||||
test-riscv64: all
|
||||
@echo riscv64
|
||||
CC=riscv64-linux-gnu-gcc CXX=riscv64-linux-gnu-g++ GCC=riscv64-linux-gnu-gcc GXX=riscv64-linux-gnu-g++ OBJDUMP=riscv64-linux-gnu-objdump MACHINE=riscv64 QEMU='qemu-riscv64 -L /usr/riscv64-linux-gnu' $(MAKE) test
|
||||
|
||||
|
||||
test-all:
|
||||
$(MAKE) test-x86-64
|
||||
$(MAKE) test-i386
|
||||
$(MAKE) test-arm64
|
||||
$(MAKE) test-arm32
|
||||
$(MAKE) test-riscv64
|
||||
|
||||
install: all
|
||||
$(INSTALL) -d $D$(BINDIR)
|
||||
$(INSTALL_PROGRAM) mold $D$(BINDIR)
|
||||
$(STRIP) $D$(BINDIR)/mold
|
||||
|
||||
$(INSTALL) -d $D$(LIBDIR)/mold
|
||||
$(INSTALL_DATA) mold-wrapper.so $D$(LIBDIR)/mold
|
||||
$(STRIP) $D$(LIBDIR)/mold/mold-wrapper.so
|
||||
|
||||
$(INSTALL) -d $D$(LIBEXECDIR)/mold
|
||||
ln -sf $(BINDIR)/mold $D$(LIBEXECDIR)/mold/ld
|
||||
|
||||
$(INSTALL) -d $D$(MANDIR)/man1
|
||||
$(INSTALL_DATA) docs/mold.1 $D$(MANDIR)/man1
|
||||
|
||||
ln -sf mold $D$(BINDIR)/ld.mold
|
||||
ln -sf mold $D$(BINDIR)/ld64.mold
|
||||
|
||||
uninstall:
|
||||
rm -f $D$(BINDIR)/mold $D$(BINDIR)/ld.mold $D$(BINDIR)/ld64.mold
|
||||
rm -f $D$(MANDIR)/man1/mold.1
|
||||
rm -rf $D$(LIBDIR)/mold
|
||||
|
||||
clean:
|
||||
rm -rf *~ mold mold-wrapper.so out ld ld64.mold mold-*-linux.tar.gz
|
||||
|
||||
.PHONY: all test tests check clean test-x86-64 test-i386 test-arm64 test-arm32 test-riscv64 test-all
|
||||
@@ -1,135 +1,117 @@
|
||||
# mold: A Modern Linker
|
||||
|
||||
mold is a faster drop-in replacement for existing Unix linkers.
|
||||
It is several times faster than the LLVM lld linker, the second-fastest
|
||||
open-source linker which I originally created a few years ago.
|
||||
mold is designed to increase developer productivity by reducing
|
||||
build time, especially in rapid debug-edit-rebuild cycles.
|
||||
mold is a faster drop-in replacement for existing Unix linkers. It is several
|
||||
times quicker than the LLVM lld linker, the second-fastest open-source linker,
|
||||
which I initially developed a few years ago. mold aims to enhance developer
|
||||
productivity by minimizing build time, particularly in rapid
|
||||
debug-edit-rebuild cycles.
|
||||
|
||||
Here is a performance comparison of GNU gold, LLVM lld, and mold for
|
||||
linking final debuginfo-enabled executables of major large programs
|
||||
on a simulated 8-core 16-threads machine.
|
||||
Here is a performance comparison of GNU 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 `cp` on the same
|
||||
machine. Feel free to [file a bug](https://github.com/rui314/mold/issues)
|
||||
if you find mold is not faster than other linkers.
|
||||
mold is so fast that it is only 2x _slower_ than the `cp` command on the same
|
||||
machine. If you find that mold is not faster than other linkers, feel
|
||||
free to [file a bug report](https://github.com/rui314/mold/issues).
|
||||
|
||||
mold currently supports x86-64, i386, ARM32, ARM64 and 64-bit RISC-V.
|
||||
mold supports x86-64, i386, ARM64, ARM32, 64-bit/32-bit little/big-endian
|
||||
RISC-V, 32-bit PowerPC, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian
|
||||
PowerPC ELFv2, s390x, 64-bit/32-bit LoongArch, SPARC64, m68k, and SH-4.
|
||||
|
||||
## Why does the speed of linking matter?
|
||||
## Why does linking speed matter?
|
||||
|
||||
If you are using a compiled language such as C, C++ or Rust, a build
|
||||
consists of two phases. In the first phase, a compiler compiles a
|
||||
source file into an object file (`.o` files). In the second phase,
|
||||
a linker takes all object files to combine them into a single executable
|
||||
or a shared library file.
|
||||
If you are using a compiled language such as C, C++, or Rust, a build consists
|
||||
of two phases. In the first phase, a compiler compiles source files into
|
||||
object files (`.o` files). In the second phase, a linker takes all object
|
||||
files and combines them into a single executable or shared library file.
|
||||
|
||||
The second phase takes a long time if your build output is large.
|
||||
mold can make it faster, saving your time and keeping you from being
|
||||
distracted while waiting for a long build to finish. The difference is
|
||||
most noticeable when you are in rapid debug-edit-rebuild cycles.
|
||||
The second phase can be time-consuming if your build output is large. mold can
|
||||
speed up this process, saving you time and preventing distractions while
|
||||
waiting for a lengthy build to finish. The difference is most noticeable
|
||||
during rapid debug-edit-rebuild cycles.
|
||||
|
||||
## Install
|
||||
## Installation
|
||||
|
||||
Binary packages for the following distros are currently available.
|
||||
Binary packages for the following systems are currently available:
|
||||
|
||||
[](https://repology.org/project/mold/versions)
|
||||
|
||||
## How to build
|
||||
## How to Build
|
||||
|
||||
mold is written in C++20, so if you build mold yourself, you need a
|
||||
recent version of a C++ compiler and a C++ standard library. GCC 10.2
|
||||
or Clang 12.0.0 as well as libstdc++ 10 or libc++ 7 are recommended.
|
||||
mold is written in C++20, so if you build mold yourself, you will need a
|
||||
recent version of a C++ compiler and a C++ standard library. We recommend GCC
|
||||
10.2 or Clang 12.0.0 (or later) and libstdc++ 10 or libc++ 7 (or later).
|
||||
|
||||
I'm using Ubuntu 20.04 as a development platform. In that environment,
|
||||
you can build mold by the following commands.
|
||||
### Install Dependencies
|
||||
|
||||
### Install dependencies
|
||||
|
||||
#### Ubuntu 20.04 and later / Debian 11 and later
|
||||
|
||||
```shell
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential git clang cmake libstdc++-10-dev libssl-dev libxxhash-dev zlib1g-dev pkg-config
|
||||
```
|
||||
|
||||
#### Fedora 34 and later
|
||||
|
||||
```shell
|
||||
sudo dnf install -y git clang cmake openssl-devel xxhash-devel zlib-devel libstdc++-devel
|
||||
```
|
||||
To install build dependencies, run `./install-build-deps.sh` in this
|
||||
directory. It will detect your Linux distribution and attempt to install the
|
||||
necessary packages. You may need to run it as root.
|
||||
|
||||
### Compile mold
|
||||
|
||||
```shell
|
||||
git clone https://github.com/rui314/mold.git
|
||||
git clone --branch stable https://github.com/rui314/mold.git
|
||||
cd mold
|
||||
git checkout v1.2.0
|
||||
make -j$(nproc) CXX=clang++
|
||||
sudo make install
|
||||
./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 may need to pass a C++20 compiler command name to `make`.
|
||||
In the above case, `clang++` is passed. If it doesn't work for you,
|
||||
try a specific version of a compiler such as `g++-10` or `clang++-12`.
|
||||
You might need to pass a C++20 compiler command name to `cmake`. In the
|
||||
example above, `c++` is passed. If that doesn't work for you, try a specific
|
||||
version of a compiler, such as `g++-10` or `clang++-12`.
|
||||
|
||||
By default, `mold` is installed to `/usr/local/bin`.
|
||||
By default, `mold` is installed to `/usr/local/bin`. You can change the
|
||||
installation location by passing `-DCMAKE_INSTALL_PREFIX=<directory>`.
|
||||
For other cmake options, see the comments in `CMakeLists.txt`.
|
||||
|
||||
If you don't use a recent enough Linux distribution, or if for any reason
|
||||
`make` in the above commands doesn't work for you, you can use Docker to
|
||||
build it in a Docker environment. To do so, just run `./dist.sh` in this
|
||||
directory instead of running `make -j$(nproc)`. The shell script creates a
|
||||
Ubuntu 18.04 Docker image, installs necessary tools and libraries to it,
|
||||
builds mold and auxiliary files, and packs them into a single tar file
|
||||
`mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere
|
||||
and use `mold` executable in it.
|
||||
|
||||
`make test` depends on a few more packages. To install, run the following commands:
|
||||
|
||||
```shell
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt update
|
||||
sudo apt-get install bsdmainutils dwarfdump libc6-dev:i386 lib32gcc-10-dev libstdc++-10-dev-arm64-cross gcc-10-aarch64-linux-gnu g++-10-aarch64-linux-gnu
|
||||
```
|
||||
If you are not using a recent enough Linux distribution, or if `cmake` does
|
||||
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
|
||||
`dist/mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere
|
||||
and use the mold executable in it.
|
||||
|
||||
## How to use
|
||||
|
||||
<details><summary>A classic way to use mold</summary>
|
||||
|
||||
On Unix, the linker command (which is usually `/usr/bin/ld`) is
|
||||
invoked indirectly by the compiler driver (which is usually `cc`,
|
||||
`gcc` or `clang`), which is typically in turn indirectly invoked by
|
||||
`make` or some other build system command.
|
||||
On Unix, the linker command (usually `/usr/bin/ld`) is indirectly invoked by
|
||||
the compiler driver (typically `cc`, `gcc`, or `clang`), which is in turn
|
||||
indirectly invoked by `make` or other build system commands.
|
||||
|
||||
If you can specify an additional command line option to your compiler
|
||||
driver by modifying build system's config files, add one of the
|
||||
following flags to use `mold` instead of `/usr/bin/ld`:
|
||||
If you can specify an additional command line option for your compiler driver
|
||||
by modifying the build system's config files, add one of the following flags
|
||||
to use mold instead of `/usr/bin/ld`:
|
||||
|
||||
- Clang: pass `-fuse-ld=mold`
|
||||
- For Clang: pass `-fuse-ld=mold`
|
||||
|
||||
- GCC 12.1.0 (upcoming version) or later: pass `-fuse-ld=mold`
|
||||
- For GCC 12.1.0 or later: pass `-fuse-ld=mold`
|
||||
|
||||
- GCC before 12.1.0: `-fuse-ld` does not accept `mold` as a valid
|
||||
argument, so you need to use `-B` option instead. `-B` is an option
|
||||
to tell GCC where to look for external commands such as `ld`.
|
||||
- For GCC before 12.1.0: the `-fuse-ld` option does not accept `mold` as a
|
||||
valid argument, so you need to use the `-B` option instead. The `-B` option
|
||||
tells GCC where to look for external commands like `ld`.
|
||||
|
||||
If you have installed mold with `make install`, there should be a
|
||||
directory named `/usr/libexec/mold` (or `/usr/local/libexec/mold`,
|
||||
depending on your $PREFIX), and `ld` command should be there. The
|
||||
`ld` is actually a symlink to `mold`. So, all you need is to pass
|
||||
`-B/usr/libexec/mold` (or `-B/usr/local/libexec/mold`) to GCC.
|
||||
If you have installed mold with `make install`, there should be a directory
|
||||
named `/usr/libexec/mold` (or `/usr/local/libexec/mold`, depending on your
|
||||
`$PREFIX`), and the `ld` command should be there. The `ld` is actually a
|
||||
symlink to `mold`. So, all you need is to pass `-B/usr/libexec/mold` (or
|
||||
`-B/usr/local/libexec/mold`) to GCC.
|
||||
|
||||
If you haven't installed `mold` to any `$PATH`, you can still pass
|
||||
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. GCC does not
|
||||
take an absolute path as an argument for `-fuse-ld` though.
|
||||
If you haven't installed `ld.mold` to any `$PATH`, you can still pass
|
||||
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. However, GCC does not
|
||||
accept an absolute path as an argument for `-fuse-ld`.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -137,83 +119,157 @@ take an absolute path as an argument for `-fuse-ld` though.
|
||||
|
||||
Create `.cargo/config.toml` in your project directory with the following:
|
||||
|
||||
```
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
```toml
|
||||
[target.'cfg(target_os = "linux")']
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
|
||||
```
|
||||
|
||||
where `/path/to/mold` is an absolute path to `mold` exectuable.
|
||||
Please make sure you have installed `clang`.
|
||||
where `/path/to/mold` is an absolute path to the mold executable. In the
|
||||
example above, we use `clang` as a linker driver since it always accepts the
|
||||
`-fuse-ld` option. If your GCC is recent enough to recognize the option, you
|
||||
may be able to remove the `linker = "clang"` line.
|
||||
|
||||
If you want to use mold for all projects, put the above snippet to
|
||||
```toml
|
||||
[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
|
||||
`~/.cargo/config.toml`.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>If you are using Nim</summary>
|
||||
|
||||
Create `config.nims` in your project directory with the following:
|
||||
|
||||
```nim
|
||||
when findExe("mold").len > 0 and defined(linux):
|
||||
switch("passL", "-fuse-ld=mold")
|
||||
```
|
||||
|
||||
where `mold` must be included in the `PATH` environment variable. In this
|
||||
example, `gcc` is used as the linker driver. Use the `-fuse-ld` option if your
|
||||
GCC is recent enough to recognize this option.
|
||||
|
||||
If you want to use mold for all projects, add the above snippet to
|
||||
`~/.config/config.nims`.
|
||||
|
||||
</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 deal with the situation,
|
||||
mold has a feature to intercept all invocations of `ld`, `ld.lld` or
|
||||
`ld.gold` and redirect it to itself. To use the feature, run `make`
|
||||
(or another build command) as a subcommand of mold as follows:
|
||||
It is sometimes very hard to pass an appropriate command line option to `cc`
|
||||
to specify an alternative linker. To address this situation, mold has a
|
||||
feature to intercept all invocations of `ld`, `ld.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 a given command with `LD_PRELOAD` environment
|
||||
variable set to its companion shared object file. The shared object
|
||||
file intercepts all function calls to `exec(3)`-family functions to
|
||||
replace `argv[0]` with `mold` if it is `ld`, `ld.gold` or `ld.lld`.
|
||||
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.bf`, `ld.gold`, or `ld.lld`.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>GitHub Action</summary>
|
||||
If you want to use mold in your GitHub-hosted CI to speed up continuous
|
||||
build, you can use <a href=https://github.com/rui314/setup-mold>setup-mold</a>
|
||||
GitHub Action. GitHub runs a CI on a two-core machine, but mold is
|
||||
still significantly faster than the default GNU linker there
|
||||
especially when a program being linked is large.
|
||||
<details><summary>GitHub Actions</summary>
|
||||
|
||||
You can use our [setup-mold](https://github.com/rui314/setup-mold) GitHub
|
||||
Action to speed up GitHub-hosted continuous builds. Although GitHub Actions
|
||||
run on a 4 core machine, mold is still significantly faster than the default
|
||||
GNU linker, especially when linking large programs.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Verify that you are using mold</summary>
|
||||
|
||||
mold leaves its identification string in `.comment` section in an output
|
||||
mold leaves its identification string in the `.comment` section of an output
|
||||
file. You can print it out to verify that you are actually using mold.
|
||||
|
||||
```shell
|
||||
readelf -p .comment <executable-file>
|
||||
$ readelf -p .comment <executable-file>
|
||||
|
||||
String dump of section '.comment':
|
||||
[ 0] GCC: (Ubuntu 10.2.0-5ubuntu1~20.04) 10.2.0
|
||||
[ 2b] mold 9a1679b47d9b22012ec7dfbda97c8983956716f7
|
||||
```
|
||||
|
||||
If `mold` is in `.comment`, the file is created by mold.
|
||||
If `mold` is present in the `.comment` section, the file was created by mold.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Online manual</summary>
|
||||
|
||||
Since mold is a drop-in replacement, you should be able to use it without
|
||||
reading its manual. However, if you need it, [mold's man page](docs/mold.md)
|
||||
is available online. You can read the same manual by running `man mold`.
|
||||
|
||||
</details>
|
||||
|
||||
## Why is mold so fast?
|
||||
|
||||
One reason is because it simply uses faster algorithms and efficient
|
||||
data structures than other linkers do. The other reason is that the
|
||||
new linker is highly parallelized.
|
||||
One reason is that it utilizes faster algorithms and more efficient data
|
||||
structures compared to other linkers. Another reason is that mold is highly
|
||||
parallelized.
|
||||
|
||||
Here is a side-by-side comparison of per-core CPU usage of lld (left)
|
||||
and mold (right). They are linking the same program, Chromium
|
||||
executable.
|
||||
Here is a side-by-side comparison of per-core CPU usage for lld (left) and
|
||||
mold (right), linking the same program, a Chromium executable.
|
||||
|
||||

|
||||
|
||||
As you can see, mold uses all available cores throughout its execution
|
||||
and finishes quickly. On the other hand, lld failed to use available
|
||||
cores most of the time. In this demo, the maximum parallelism is
|
||||
artificially capped to 16 so that the bars fit in the GIF.
|
||||
As you can see, mold uses all available cores throughout its execution and
|
||||
finishes quickly. In contrast, lld fails to utilize available cores most of
|
||||
the time. In this demo, the maximum parallelism is artificially capped at 16,
|
||||
so that the bars fit in the GIF.
|
||||
|
||||
For details, please read [design notes](docs/design.md).
|
||||
For details, please see the [design notes](docs/design.md).
|
||||
|
||||
# Logo
|
||||
## Sponsors
|
||||
|
||||
-
|
||||
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
|
||||
|
||||
<a href="https://mercury.com"><img src="docs/mercury-logo.png" align=center height=120 width=400 alt=Mercury></a>
|
||||
|
||||
<a href="https://cybozu-global.com"><img src="docs/cyboze-logo.png" align=center height=120 width=133 alt=Cybozu></a>
|
||||
|
||||
<a href="https://www.emergetools.com"><img src="docs/emerge-tools-logo.png" align=center height=120 width=240 alt="Emerge Tools"></a><br>
|
||||
|
||||
- [G-Research](https://www.gresearch.co.uk)
|
||||
- [Signal Slot Inc.](https://github.com/signal-slot)
|
||||
- [GlareDB](https://github.com/GlareDB)
|
||||
|
||||
### Individual sponsors
|
||||
|
||||
- [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,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef __BIG_ENDIAN__
|
||||
#error "mold does not support big-endian hosts"
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename T>
|
||||
class BigEndian {
|
||||
public:
|
||||
BigEndian() = delete;
|
||||
|
||||
operator T() const {
|
||||
T x;
|
||||
memcpy(&x, val, sizeof(T));
|
||||
return bswap(x);
|
||||
}
|
||||
|
||||
BigEndian &operator=(T x) {
|
||||
x = bswap(x);
|
||||
memcpy(&val, &x, sizeof(T));
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigEndian &operator++() {
|
||||
return *this = *this + 1;
|
||||
}
|
||||
|
||||
BigEndian operator++(int) {
|
||||
T ret = *this;
|
||||
*this = *this + 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator--() {
|
||||
return *this = *this - 1;
|
||||
}
|
||||
|
||||
BigEndian operator--(int) {
|
||||
T ret = *this;
|
||||
*this = *this - 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator+=(T x) {
|
||||
return *this = *this + x;
|
||||
}
|
||||
|
||||
BigEndian &operator&=(T x) {
|
||||
return *this = *this & x;
|
||||
}
|
||||
|
||||
BigEndian &operator|=(T x) {
|
||||
return *this = *this | x;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t val[sizeof(T)];
|
||||
|
||||
static T bswap(T x) {
|
||||
if constexpr (sizeof(T) == 2)
|
||||
return __builtin_bswap16(x);
|
||||
else if constexpr (sizeof(T) == 4)
|
||||
return __builtin_bswap32(x);
|
||||
else
|
||||
return __builtin_bswap64(x);
|
||||
}
|
||||
};
|
||||
|
||||
using ibig16 = BigEndian<int16_t>;
|
||||
using ibig32 = BigEndian<int32_t>;
|
||||
using ibig64 = BigEndian<int64_t>;
|
||||
using ubig16 = BigEndian<uint16_t>;
|
||||
using ubig32 = BigEndian<uint32_t>;
|
||||
using ubig64 = BigEndian<uint64_t>;
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,63 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename C>
|
||||
std::vector<std::string_view>
|
||||
read_response_file(C &ctx, std::string_view path) {
|
||||
std::vector<std::string_view> vec;
|
||||
MappedFile<C> *mf = MappedFile<C>::must_open(ctx, std::string(path));
|
||||
u8 *data = mf->data;
|
||||
|
||||
auto read_quoted = [&](i64 i, char quote) {
|
||||
std::string buf;
|
||||
while (i < mf->size && data[i] != quote) {
|
||||
if (data[i] == '\\') {
|
||||
buf.append(1, data[i + 1]);
|
||||
i += 2;
|
||||
} else {
|
||||
buf.append(1, data[i++]);
|
||||
}
|
||||
}
|
||||
if (i >= mf->size)
|
||||
Fatal(ctx) << path << ": premature end of input";
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i + 1;
|
||||
};
|
||||
|
||||
auto read_unquoted = [&](i64 i) {
|
||||
std::string buf;
|
||||
while (i < mf->size && !isspace(data[i]))
|
||||
buf.append(1, data[i++]);
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i;
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < mf->size;) {
|
||||
if (isspace(data[i]))
|
||||
i++;
|
||||
else if (data[i] == '\'')
|
||||
i = read_quoted(i + 1, '\'');
|
||||
else if (data[i] == '\"')
|
||||
i = read_quoted(i + 1, '\"');
|
||||
else
|
||||
i = read_unquoted(i);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
std::vector<std::string_view> expand_response_files(C &ctx, char **argv) {
|
||||
std::vector<std::string_view> vec;
|
||||
for (i64 i = 0; argv[i]; i++) {
|
||||
if (argv[i][0] == '@')
|
||||
append(vec, read_response_file(ctx, argv[i] + 1));
|
||||
else
|
||||
vec.push_back(argv[i]);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cxxabi.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string_view demangle(std::string_view name) {
|
||||
if (name.starts_with("_Z")) {
|
||||
static thread_local char *buf;
|
||||
static thread_local size_t buflen;
|
||||
|
||||
int status;
|
||||
char *p = abi::__cxa_demangle(std::string(name).c_str(), buf, &buflen, &status);
|
||||
if (status == 0) {
|
||||
buf = p;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,35 +1,227 @@
|
||||
#!/bin/bash -x
|
||||
# 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`).
|
||||
#!/bin/bash
|
||||
#
|
||||
# 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 creates a mold binary distribution. The output is written in
|
||||
# `dist` directory as `mold-$version-$arch-linux.tar.gz` (e.g.,
|
||||
# `mold-1.0.3-x86_64-linux.tar.gz`).
|
||||
#
|
||||
# The mold executable created by this script is statically linked to
|
||||
# libstdc++ but dynamically linked to libc, libm, libz, and librt, as
|
||||
# these libraries almost always exist on any Linux system. We can't
|
||||
# statically link libc because doing so would disable dlopen(), which is
|
||||
# necessary to open the LTO linker plugin.
|
||||
#
|
||||
# This script aims to produce reproducible outputs. That means if you run
|
||||
# the script twice on the same git commit, it should produce bit-by-bit
|
||||
# identical binary files. This property is crucial as a countermeasure
|
||||
# against supply chain attacks. With this, you can verify that the binary
|
||||
# files distributed on the GitHub release pages are created from the
|
||||
# commit with release tags by rebuilding the binaries yourself.
|
||||
#
|
||||
# Debian provides snapshot.debian.org to host all historical binary
|
||||
# packages. We use it to construct Podman images pinned to a
|
||||
# particular timestamp.
|
||||
#
|
||||
# We aim to use a reasonably old Debian version 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 an older version of glibc.
|
||||
#
|
||||
# We want 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 yields a
|
||||
# slightly larger binary than Clang's.
|
||||
#
|
||||
# We need a recent version of Clang to build mold. If it's not available
|
||||
# via apt-get, we'll build it ourselves.
|
||||
#
|
||||
# You may need to install qemu-user-static package to build non-native
|
||||
# binaries.
|
||||
|
||||
set -e
|
||||
set -e -x
|
||||
cd "$(dirname $0)"
|
||||
|
||||
version=$(grep '^VERSION =' $(dirname $0)/Makefile | sed 's/.* = //')
|
||||
arch=$(uname -m)
|
||||
dest=mold-$version-$arch-linux
|
||||
|
||||
if [ $arch = x86_64 ]; then
|
||||
image=rui314/mold-builder:v1-x86_64
|
||||
elif [ $arch = aarch64 -o $arch = arm64 ]; then
|
||||
image=rui314/mold-builder:v1-aarch64
|
||||
else
|
||||
echo "Error: no docker image for $arch"
|
||||
usage() {
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x | loongarch64 ]"
|
||||
exit 1
|
||||
}
|
||||
|
||||
case $# in
|
||||
0)
|
||||
arch=$(uname -m)
|
||||
if [ $arch = arm64 ]; then
|
||||
arch=aarch64
|
||||
elif [[ $arch = arm* ]]; then
|
||||
arch=arm
|
||||
fi
|
||||
;;
|
||||
1)
|
||||
arch="$1"
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
esac
|
||||
|
||||
# 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
|
||||
|
||||
docker images -q $image 2> /dev/null || docker pull $image
|
||||
case $arch in
|
||||
x86_64)
|
||||
# Debian 9 (Stretch) released in June 2017.
|
||||
# We use a Google-provided mirror (gcr.io) of the official Docker hub
|
||||
# (docker.io) because docker.io has a strict rate limit policy.
|
||||
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 [trusted=yes] /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 && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
|
||||
docker run -it --rm -v "$(pwd):/mold:Z" -u "$(id -u):$(id -g)" $image \
|
||||
bash -c "cp -r /mold /tmp/mold &&
|
||||
cd /tmp/mold &&
|
||||
make clean &&
|
||||
make -j\$(nproc) CXX=clang++-14 CXXFLAGS='-I/openssl/include -O2 $CXXFLAGS' LDFLAGS='-static-libstdc++ /openssl/libcrypto.a' NEEDS_LIBCRYPTO=0 LTO=${LTO:-0} &&
|
||||
make install PREFIX=/ DESTDIR=$dest &&
|
||||
ln -sfr $dest/bin/mold $dest/libexec/mold/ld &&
|
||||
tar czf /mold/$dest.tar.gz $dest &&
|
||||
cp mold /mold"
|
||||
# Build CMake 3.27
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget -O- --no-check-certificate --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- --no-check-certificate --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- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/gmp/gmp-6.3.0.tar.gz | tar xzf - --strip-components=1 -C gmp && \
|
||||
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/mpc/mpc-1.3.1.tar.gz | tar xzf - --strip-components=1 -C mpc && \
|
||||
wget -O- --no-check-certificate --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- --no-check-certificate --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- --no-check-certificate --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- --no-check-certificate --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 with Qemu becuase
|
||||
# that'd take extremely long time. Also I believe old build machines
|
||||
# are usually 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 [trusted=yes] /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)
|
||||
# LoongArch build is not reproducible yet
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/loongarch64/debian:sid
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN 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 history.
|
||||
# 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)
|
||||
|
||||
# Build mold in a container.
|
||||
mkdir -p dist
|
||||
|
||||
# If the host is ARM64, `uname -m` in an ARM32 container running on it
|
||||
# reports it not as ARM32 but as ARM64, which confuses BLAKE3's cmake script
|
||||
# and erroneously enables NEON SIMD instructions. `setarch` can be used
|
||||
# to change the output of `uname -m`.
|
||||
[ $arch = arm ] && setarch='setarch linux32'
|
||||
|
||||
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 |
Binary file not shown.
|
After Width: | Height: | Size: 23 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
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
+4
-4
@@ -23,10 +23,10 @@ process.
|
||||
|
||||
To prevent this type of attack, the stack area is no longer executable
|
||||
since the early 2000s. On Linux, the stack's executable-ness is
|
||||
controlled by a bit in an executable, and the loader respect that
|
||||
controlled by a bit in an executable, and the loader respects that
|
||||
bit. The bit is set by the linker.
|
||||
|
||||
GCC had (and still have) a feature that depends on the executable
|
||||
GCC had (and still has) a feature that depends on the executable
|
||||
stack, so they invented a way to tell the linker to mark the stack
|
||||
executable. Specifically, if an object file contains a
|
||||
`.note.GNU-stack` section with the `SHF_EXECSTACK` bit, GNU linker
|
||||
@@ -48,8 +48,8 @@ feature which still depends on the executable stack.
|
||||
|
||||
# How to fix it?
|
||||
|
||||
If you know what you are doing, pass `-z execstack` to mold. Beaware
|
||||
If you know what you are doing, pass `-z execstack` to mold. Beware
|
||||
that this will significantly weaken your program's security.
|
||||
|
||||
If you don't want to pass `-z execstack`, rewrite your code so that
|
||||
your code does not depends on the executable stack.
|
||||
your code does not depend on the executable stack.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
+552
-1090
File diff suppressed because it is too large.
Load diff
Binary file not shown.
|
Before Width: | Height: | Size: 95 KiB |
+935
@@ -0,0 +1,935 @@
|
||||
mold(1) -- a modern linker
|
||||
==========================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`mold` [_option_...] _file_...
|
||||
|
||||
## DESCRIPTION
|
||||
`mold` is a faster drop-in replacement for the default GNU ld(1).
|
||||
|
||||
### How to use
|
||||
See <https://github.com/rui314/mold#how-to-use>.
|
||||
|
||||
### Compatibility
|
||||
|
||||
`mold` is designed to be a drop-in replacement for the GNU linkers for
|
||||
linking user-land programs. If your user-land program cannot be built due to
|
||||
missing command-line options, please file a bug at
|
||||
<https://github.com/rui314/mold/issues>.
|
||||
|
||||
`mold` supports a very limited set of linker script features, which is just
|
||||
sufficient to read `/usr/lib/x86_64-linux-gnu/libc.so` on Linux systems (on
|
||||
Linux, that file is contrary to its name not a shared library but an ASCII
|
||||
linker script that loads a real `libc.so` file.)
|
||||
|
||||
Beyond that, we have no plan to support any additional linker script features.
|
||||
The linker script is an ad-hoc, over-designed, complex language which we
|
||||
believe needs to be replaced by a simpler mechanism. We have a plan to add a
|
||||
replacement for the linker script to `mold` instead.
|
||||
|
||||
### Archive symbol resolution
|
||||
|
||||
Traditionally, Unix linkers are sensitive to the order in which input files
|
||||
appear on the command line. They process input files from the first (leftmost)
|
||||
file to the last (rightmost) file one-by-one. While reading input files, they
|
||||
maintain sets of defined and undefined symbols. When visiting an archive file
|
||||
(`.a` files), they pull out object files to resolve as many undefined symbols
|
||||
as possible and move on to the next input file. Object files that weren't
|
||||
pulled out will never have a chance for a second look.
|
||||
|
||||
Due to this behavior, you usually have to add archive files at the end of a
|
||||
command line, so that when a linker reaches archive files, it knows what
|
||||
symbols remain as undefined.
|
||||
|
||||
If you put archive files at the beginning of a command line, a linker doesn't
|
||||
have any undefined symbols, and thus no object files will be pulled out from
|
||||
archives. You can change the processing order by using the `--start-group` and
|
||||
`--end-group` options, though they make a linker slower.
|
||||
|
||||
`mold`, as well as the LLVM lld(1) linker, takes a different approach. They
|
||||
remember which symbols can be resolved from archive files instead of
|
||||
forgetting them after processing each archive. Therefore, `mold` and lld(1)
|
||||
can "go back" in a command line to pull out object files from archives if they
|
||||
are needed to resolve remaining undefined symbols. They are not sensitive to
|
||||
the input file order.
|
||||
|
||||
`--start-group` and `--end-group` are still accepted by `mold` and lld(1)
|
||||
for compatibility with traditional linkers, but they are silently ignored.
|
||||
|
||||
### Dynamic symbol resolution
|
||||
|
||||
Some Unix linker features are difficult to understand without comprehending
|
||||
the semantics of dynamic symbol resolution. Therefore, even though it's not
|
||||
specific to `mold`, we'll explain it here.
|
||||
|
||||
We use "ELF module" or just "module" as a collective term to refer to an
|
||||
executable or a shared library file in the ELF format.
|
||||
|
||||
An ELF module may have lists of imported symbols and exported symbols, as well
|
||||
as a list of shared library names from which imported symbols should be
|
||||
imported. The point is that imported symbols are not bound to any specific
|
||||
shared library until runtime.
|
||||
|
||||
Here is how the Unix dynamic linker resolves dynamic symbols. Upon the start
|
||||
of an ELF program, the dynamic linker constructs a list of ELF modules which,
|
||||
as a whole, consist of a complete program. The executable file is always at
|
||||
the beginning of the list followed by its dependent shared libraries. An
|
||||
imported symbol is searched from the beginning of the list to the end. If two
|
||||
or more modules define the same symbol, the one that appears first in the list
|
||||
takes precedence over the others.
|
||||
|
||||
This Unix semantics are contrary to systems such as Windows that have a
|
||||
two-level namespace for dynamic symbols. On Windows, for example, dynamic
|
||||
symbols are represented as a tuple of (`symbol-name`, `shared-library-name`),
|
||||
so that each dynamic symbol is guaranteed to be resolved from some specific
|
||||
library.
|
||||
|
||||
Typically, an ELF module that exports a symbol also imports the same symbol.
|
||||
Such a symbol is usually resolved to itself, but that's not the case if a
|
||||
module that appears before it in the symbol search list provides another
|
||||
definition of the same symbol.
|
||||
|
||||
Let's take `malloc` as an example. Assume that you define your version of
|
||||
`malloc` in your main executable file. Then, all `malloc` calls from any
|
||||
module are resolved to your function instead of the one in libc, because the
|
||||
executable is always at the beginning of the dynamic symbol search list. Note
|
||||
that even `malloc` calls within libc are resolved to your definition since
|
||||
libc exports and imports `malloc`. Therefore, by defining `malloc` yourself,
|
||||
you can overwrite a library function, and the `malloc` in libc becomes dead
|
||||
code.
|
||||
|
||||
These Unix semantics are tricky and sometimes considered harmful. For example,
|
||||
assume that you accidentally define `atoi` as a global function in your
|
||||
executable that behaves completely differently from the one in the C standard.
|
||||
Then, all `atoi` function calls from any modules (even function calls within
|
||||
libc) are redirected to your function instead of the one in libc, which will
|
||||
very likely cause a problem. That is a somewhat surprising consequence for an
|
||||
accidental name conflict. On the other hand, this semantic is sometimes useful
|
||||
because it allows users to override library functions without rebuilding
|
||||
modules containing them.
|
||||
|
||||
Whether good or bad, you should keep these semantics in mind to understand
|
||||
Unix linkers' behaviors.
|
||||
|
||||
### Build reproducibility
|
||||
|
||||
`mold`'s output is deterministic. That is, if you pass the same object files
|
||||
and the same command-line options to the same version of `mold`, it is
|
||||
guaranteed that `mold` produces the bit-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.
|
||||
|
||||
`mold` does not have any host-specific default settings. This is contrary to the
|
||||
GNU linkers, for which some configurable values, such as system-dependent
|
||||
library search paths, are hard-coded. `mold` depends only on its command-line
|
||||
arguments.
|
||||
|
||||
## 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_:
|
||||
Set _dir_ as the root directory.
|
||||
|
||||
* `--color-diagnostics`=[ _auto_ | _always_ | _never_ ]:
|
||||
Show diagnostic messages in color using ANSI escape sequences. `auto` means
|
||||
that `mold` prints out messages in color only if the standard output is
|
||||
connected to a TTY. Default is `auto`.
|
||||
|
||||
* `--color-diagnostics`:
|
||||
Synonym for `--color-diagnostics=auto`.
|
||||
|
||||
* `--no-color-diagnostics`:
|
||||
Synonym for `--color-diagnostics=never`.
|
||||
|
||||
* `--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.
|
||||
|
||||
* `--perf`:
|
||||
Print performance statistics.
|
||||
|
||||
* `--print-dependencies`:
|
||||
Print out dependency information for input files.
|
||||
|
||||
Each line of the output for this option shows which file depends on which
|
||||
file to use a specific symbol. This option is useful for debugging why some
|
||||
object file in a static archive got linked or why some shared library is
|
||||
kept in an output file's dependency list even with `--as-needed`.
|
||||
|
||||
* `--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.
|
||||
This is useful for reporting a bug with a reproducer. The output filename is
|
||||
`path/to/output.tar`, where `path/to/output` is an output filename specified
|
||||
by `-o`.
|
||||
|
||||
* `--reverse-sections`:
|
||||
Reverse the order of input sections before assigning them the offsets in the
|
||||
output file.
|
||||
|
||||
This option is useful for finding bugs that depend on the initialization
|
||||
order of global objects. In C++, constructors of global objects in a single
|
||||
source file are guaranteed to be executed in the source order, but there's
|
||||
no such guarantee across compilation units. Usually, constructors are
|
||||
executed in the order given to the linker, but depending on it is a mistake.
|
||||
|
||||
By reversing the order of input sections using `--reverse-sections`, you can
|
||||
easily test that your program works in the reversed initialization order.
|
||||
|
||||
* `--run` _command_ _arg_...:
|
||||
Run _command_ with `mold` 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
|
||||
assigning them the offsets in the output file. If a _number_ is given, it's
|
||||
used as a seed for the random number generator, so that the linker produces
|
||||
the same output for the same seed. If no seed is given, a random number is
|
||||
used as a seed.
|
||||
|
||||
This option is useful for benchmarking. Modern CPUs are sensitive to a
|
||||
program's memory layout. A seemingly benign change in program layout, such
|
||||
as a small size increase of a function in the middle of a program, can
|
||||
affect the program's performance. Therefore, even if you write new code and
|
||||
get a good benchmark result, it is hard to say whether the new code improves
|
||||
the program's performance; it is possible that the new memory layout happens
|
||||
to perform better.
|
||||
|
||||
By running a benchmark multiple times with randomized memory layouts using
|
||||
`--shuffle-sections`, you can isolate your program's real performance number
|
||||
from the randomness caused by memory layout changes.
|
||||
|
||||
* `--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.
|
||||
|
||||
* `--thread-count`=_count_:
|
||||
Use _count_ number of threads.
|
||||
|
||||
* `--threads`, `--no-threads`:
|
||||
Use multiple threads. By default, `mold` uses as many threads as the number of
|
||||
cores or 32, whichever is smaller. The reason it is capped at 32 is because
|
||||
`mold` doesn't scale well beyond that point. To use only one thread, pass
|
||||
`--no-threads` or `--thread-count=1`.
|
||||
|
||||
* `--quick-exit`, `--no-quick-exit`:
|
||||
Use or do not use `quick_exit` to exit.
|
||||
|
||||
* `-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`:
|
||||
Report usage information to stdout and exit.
|
||||
|
||||
* `-v`, `--version`:
|
||||
Report version information to stdout.
|
||||
|
||||
* `-V`:
|
||||
Report version and target information to stdout.
|
||||
|
||||
* `-E`, `--export-dynamic`, `--no-export-dynamic`:
|
||||
When creating an executable, using the `-E` option causes all global symbols
|
||||
to be put into the dynamic symbol table, so that the symbols are visible
|
||||
from other ELF modules at runtime.
|
||||
|
||||
By default, or if `--no-export-dynamic` is given, only symbols that are
|
||||
referenced by DSOs at link-time are exported from an executable.
|
||||
|
||||
* `-F` _libname_, `--filter`=_libname_:
|
||||
Set the `DT_FILTER` dynamic section field to _libname_.
|
||||
|
||||
* `-I` _file_, `--dynamic-linker`=_file_, `--no-dynamic-linker`:
|
||||
Set the dynamic linker path to _file_. If no `-I` option is given, or if
|
||||
`--no-dynamic-linker` is given, no dynamic linker path is set to an output
|
||||
file. This is contrary to the GNU linkers which set a default dynamic linker
|
||||
path in that case. This difference doesn't usually make any difference
|
||||
because the compiler driver always passes `-I` to the linker.
|
||||
|
||||
* `-L` _dir_, `--library-path`=_dir_:
|
||||
Add _dir_ to the list of library search paths from which `mold` searches
|
||||
libraries for the `-l` option.
|
||||
|
||||
Unlike the GNU linkers, `mold` does not have default search paths. This
|
||||
difference doesn't usually make any difference because the compiler driver
|
||||
always passes all necessary search paths to the linker.
|
||||
|
||||
* `-M`, `--print-map`:
|
||||
Write a map file to stdout.
|
||||
|
||||
* `-N`, `--omagic`, `--no-omagic`:
|
||||
Force `mold` to emit an output file with an old-fashioned memory layout.
|
||||
First, it makes the first data segment not aligned to a page boundary.
|
||||
Second, text segments are marked as writable if the option is given.
|
||||
|
||||
* `-S`, `--strip-debug`:
|
||||
Omit `.debug_*` sections from the output file.
|
||||
|
||||
* `-T` _file_, `--script`=_file_:
|
||||
Read linker script from _file_.
|
||||
|
||||
* `-X`, `--discard-locals`:
|
||||
Discard temporary local symbols to reduce the sizes of the symbol table and
|
||||
the string table. Temporary local symbols are local symbols starting with
|
||||
`.L`. Compilers usually generate such symbols for unnamed program elements
|
||||
such as string literals or floating-point literals.
|
||||
|
||||
* `-e` _symbol_, `--entry`=_symbol_:
|
||||
|
||||
Use _symbol_ as the entry point symbol instead of the default entry
|
||||
point symbol _start.
|
||||
|
||||
* `-f` _shlib_, `--auxiliary`=_shlib_:
|
||||
Set the `DT_AUXILIARY` dynamic section field to _shlib_.
|
||||
|
||||
* `-h` _libname_, `--soname`=_libname_:
|
||||
Set the `DT_SONAME` dynamic section field to _libname_. This option is used
|
||||
when creating a shared object file. Typically, when you create `libfoo.so`,
|
||||
you want to pass `--soname=foo` to a linker.
|
||||
|
||||
* `-l` _libname_:
|
||||
Search for `lib`_libname_`.so` or `lib`_libname_`.a` from library search
|
||||
paths.
|
||||
|
||||
* `-m` _target_:
|
||||
Choose a _target_.
|
||||
|
||||
* `-o` _file_, `--output`=_file_:
|
||||
Use _file_ as the output file name instead of the default name `a.out`.
|
||||
|
||||
* `-r`, `--relocatable`:
|
||||
Instead of generating an executable or a shared object file, combine input
|
||||
object files to generate another object file that can be used as an input to
|
||||
a linker.
|
||||
|
||||
* `-s`, `--strip-all`:
|
||||
Omit `.symtab` section from the output file.
|
||||
|
||||
* `-u` _symbol_, `--undefined`=_symbol_:
|
||||
If _symbol_ remains as an undefined symbol after reading all object files,
|
||||
and if there is a static archive that contains an object file defining
|
||||
_symbol_, pull out the object file and link it so that the output file
|
||||
contains a definition of _symbol_.
|
||||
|
||||
* `-y` _symbol_, `--trace-symbol`=_symbol_:
|
||||
Trace references to _symbol_.
|
||||
|
||||
* `--Bdynamic`:
|
||||
Link against shared libraries.
|
||||
|
||||
* `--Bstatic`:
|
||||
Do not link against shared libraries.
|
||||
|
||||
* `--Bsymbolic`:
|
||||
When creating a shared library, make global symbols export-only (i.e. do not
|
||||
import the same symbol). As a result, references within a shared library are
|
||||
always resolved locally, negating symbol override at runtime. See "Dynamic
|
||||
symbol resolution" for more information about symbol imports and exports.
|
||||
|
||||
* `--Bsymbolic-functions`:
|
||||
This option has the same effect as `--Bsymbolic` but works only for function
|
||||
symbols. Data symbols remain being both imported and exported.
|
||||
|
||||
* `--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`, `--Bsymbolic-functions`, `--Bsymbolic-non-weak` and
|
||||
`--Bsymbolic-non-weak-functions`.
|
||||
|
||||
* `--Map`=_file_:
|
||||
Write map file to _file_.
|
||||
|
||||
* `--Tbss`=_address_:
|
||||
Alias for `--section-start=.bss=`_address_.
|
||||
|
||||
* `--Tdata`=_address_:
|
||||
Alias for `--section-start=.data=`_address_.
|
||||
|
||||
* `--Ttext`=_address_:
|
||||
Alias for `--section-start=.text=`_address_.
|
||||
|
||||
* `--allow-multiple-definition`:
|
||||
Normally, the linker reports an error if there are more than one definition
|
||||
of a symbol. This option changes the default behavior so that it doesn't
|
||||
report an error for duplicate definitions and instead use the first
|
||||
definition.
|
||||
|
||||
* `--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
|
||||
libraries after `--as-needed` are added to the list only when at least one
|
||||
symbol is actually used by the output file. In other words, shared libraries
|
||||
after `--as-needed` are not added to the list of needed libraries if they
|
||||
are not needed by a program.
|
||||
|
||||
The `--no-as-needed` option restores the default behavior for subsequent
|
||||
files.
|
||||
|
||||
* `--build-id`=[ `md5` | `sha1` | `sha256` | `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_. `fast` is a synonym for `sha256`.
|
||||
|
||||
* `--build-id`:
|
||||
Synonym for `--build-id=sha256`.
|
||||
|
||||
* `--no-build-id`:
|
||||
Synonym for `--build-id=none`.
|
||||
|
||||
* `--compress-debug-sections`=[ `zlib` | `zlib-gabi` | `zstd` | `none` ]:
|
||||
Compress DWARF debug info (`.debug_*` sections) using the zlib or zstd
|
||||
compression algorithm. `zlib-gabi` is an alias for `zlib`.
|
||||
|
||||
* `--defsym`=_symbol_=_value_:
|
||||
Define _symbol_ as an alias for _value_.
|
||||
|
||||
_value_ is either an integer (in decimal or hexadecimal with `0x` prefix) or
|
||||
a symbol name. If an integer is given as a value, _symbol_ is defined as an
|
||||
absolute symbol with the given value.
|
||||
|
||||
* `--default-symver`:
|
||||
Use soname as a symbol version and append that version to all symbols.
|
||||
|
||||
* `--demangle`, `--no-demangle`:
|
||||
Demangle C++ and Rust symbols in log messages.
|
||||
|
||||
* `--dependency-file`=_file_:
|
||||
Write a dependency file to _file_. The contents of the written file is
|
||||
readable by make(1), which defines only one rule with the linker's output
|
||||
file as a target and all input files as its prerequisites. Users are
|
||||
expected to include the generated dependency file into a Makefile to
|
||||
automate the dependency management. This option is analogous to the
|
||||
compiler's `-MM -MF` options.
|
||||
|
||||
* `--dynamic-list`=_file_:
|
||||
Read a list of dynamic symbols from _file_. Same as
|
||||
`--export-dynamic-symbol-list`, except that it implies `--Bsymbolic`. If
|
||||
_file_ does not exist in the current directory, it is searched from library
|
||||
search paths for the sake of compatibility with GNU ld.
|
||||
|
||||
* `--eh-frame-hdr`, `--no-eh-frame-hdr`:
|
||||
Create `.eh_frame_hdr` section.
|
||||
|
||||
* `--emit-relocs`:
|
||||
The linker usually "consumes" relocation sections. That is, the linker
|
||||
applies relocations to other sections, and relocation sections themselves
|
||||
are discarded.
|
||||
|
||||
The `--emit-relocs` instructs the linker to leave relocation sections in the
|
||||
output file. Some post-link binary analysis or optimization tools such as
|
||||
LLVM Bolt need them.
|
||||
|
||||
* `--enable-new-dtags`, `--disable-new-dtags`:
|
||||
By default, `mold` emits `DT_RUNPATH` for `--rpath`. If you pass
|
||||
`--disable-new-dtags`, `mold` emits `DT_RPATH` for `--rpath` instead.
|
||||
|
||||
* `--execute-only`:
|
||||
|
||||
Traditionally, 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.
|
||||
|
||||
* `--export-dynamic-symbol`=_symbol_:
|
||||
Put symbols matching _symbol_ in the dynamic symbol table. _symbol_ may be a
|
||||
glob pattern in the same syntax as for the `--export-dynamic-symbol-list` or
|
||||
`--version-script` options.
|
||||
|
||||
* `--export-dynamic-symbol-list`=_file_:
|
||||
Read a list of dynamic symbols from _file_.
|
||||
|
||||
* `--fatal-warnings`, `--no-fatal-warnings`:
|
||||
Treat warnings as errors.
|
||||
|
||||
* `--fini`=_symbol_:
|
||||
Call _symbol_ at unload-time.
|
||||
|
||||
* `--gc-sections`, `--no-gc-sections`:
|
||||
Remove unreferenced sections.
|
||||
|
||||
* `--gdb-index`:
|
||||
Create a `.gdb_index` section to speed up GNU debugger. To use this, you
|
||||
need to compile source files with the `-ggnu-pubnames` compiler flag.
|
||||
|
||||
* `--hash-style`=[ `sysv` | `gnu` | `both` | `none` ]:
|
||||
Set hash style.
|
||||
|
||||
* `--icf`=[ `safe` | `all` | `none` ], `--no-icf`:
|
||||
It is not uncommon for a program to contain many identical functions that
|
||||
differ only in name. For example, a C++ template `std::vector` is very
|
||||
likely to be instantiated to the identical code for `std::vector<int>` and
|
||||
`std::vector<unsigned>` because the container cares only about the size of
|
||||
the parameter type. Identical Code Folding (ICF) is a size optimization to
|
||||
identify and merge such identical functions.
|
||||
|
||||
If `--icf=all` is given, `mold` tries to merge all identical functions. This
|
||||
reduces the size of the output most, but it is not a "safe" optimization. It
|
||||
is guaranteed in C and C++ that two pointers pointing two different
|
||||
functions will never be equal, but `--icf=all` breaks that assumption as two
|
||||
identical functions have the same address after merging. So a care must be
|
||||
taken when you use this flag that your program does not depend on the
|
||||
function pointer uniqueness.
|
||||
|
||||
`--icf=safe` is a flag to merge functions only when it is safe to do so.
|
||||
That is, if a program does not take an address of a function, it is safe to
|
||||
merge that function with other function, as you cannot compare a function
|
||||
pointer with something else without taking an address of a function.
|
||||
|
||||
`--icf=safe` needs to be used with a compiler that supports `.llvm_addrsig`
|
||||
section which contains the information as to what symbols are address-taken.
|
||||
LLVM/Clang supports that section by default. Since GCC does not support it
|
||||
yet, you cannot use `--icf=safe` with GCC (it doesn't do any harm but can't
|
||||
optimize at all.)
|
||||
|
||||
`--icf=none` and `--no-icf` disables ICF.
|
||||
|
||||
* `--ignore-data-address-equality`:
|
||||
Make ICF to merge not only functions but also data. This option should be
|
||||
used in combination with `--icf=all`.
|
||||
|
||||
* `--image-base`=_addr_:
|
||||
Set the base address to _addr_.
|
||||
|
||||
* `--init`=_symbol_:
|
||||
Call _symbol_ at load-time.
|
||||
|
||||
* `--no-undefined`:
|
||||
Report undefined symbols (even with `--shared`).
|
||||
|
||||
* `--noinhibit-exec`:
|
||||
Create an output file even if errors occur.
|
||||
|
||||
* `--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
|
||||
`.relr.dyn` section uses a space-efficient encoding scheme, specifying this
|
||||
flag can reduce the size of the output. This is typically most effective for
|
||||
position-independent executable.
|
||||
|
||||
Note that a runtime loader has to support `.relr.dyn` to run executables or
|
||||
shared libraries linked with `--pack-dyn-relocs=relr`. As of 2022, only
|
||||
ChromeOS, Android and Fuchsia support it.
|
||||
|
||||
* `--pie`, `--pic-executable`, `--no-pie`, `--no-pic-executable`:
|
||||
Create a position-independent executable.
|
||||
|
||||
* `--print-gc-sections`, `--no-print-gc-sections`:
|
||||
Print removed unreferenced sections.
|
||||
|
||||
* `--print-icf-sections`, `--no-print-icf-sections`:
|
||||
Print folded identical sections.
|
||||
|
||||
* `--push-state`, `--pop-state`:
|
||||
`--push-state` saves the current values of `--as-needed`, `--whole-archive`,
|
||||
`--static`, and `--start-lib`. The saved values can be restored by
|
||||
pop-state.
|
||||
|
||||
`--push-state` and `--pop-state` pairs can nest.
|
||||
|
||||
These options are useful when you want to construct linker command line
|
||||
options programmatically. For example, if you want to link `libfoo.so` by
|
||||
as-needed basis but don't want to change the global state of `--as-needed`,
|
||||
you can append `--push-state --as-needed -lfoo --pop-state` to the linker
|
||||
command line options.
|
||||
|
||||
* `--relax, --no-relax`:
|
||||
Rewrite machine instructions with more efficient ones for some relocations.
|
||||
The feature is enabled by default.
|
||||
|
||||
* `--require-defined`=_symbol_:
|
||||
Like `--undefined`, except the new symbol must be defined by the end of the
|
||||
link.
|
||||
|
||||
* `--retain-symbols-file`=_file_:
|
||||
Keep only symbols listed in _file_. _file_ is a text file containing a
|
||||
symbol name on each line. `mold` discards all local symbols as well as
|
||||
global symbol that are not in _file_. Note that this option removes symbols
|
||||
only from `.symtab` section and does not affect `.dynsym` section, which is
|
||||
used for dynamic linking.
|
||||
|
||||
* `--rpath`=_dir_:
|
||||
Add _dir_ to runtime search paths.
|
||||
|
||||
* `--section-start`=_section_=_address_:
|
||||
Set _address_ to section. _address_ is a hexadecimal number that may start
|
||||
with an optional `0x`.
|
||||
|
||||
* `--shared`, `--Bshareable`:
|
||||
Create a share library.
|
||||
|
||||
* `--spare-dynamic-tags`=_number_:
|
||||
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
|
||||
an archive file. That means object files between them are linked only when
|
||||
they are needed to resolve undefined symbols. The options are useful if you
|
||||
want to link object files only when they are needed but want to avoid the
|
||||
overhead of running ar(3).
|
||||
|
||||
* `--static`:
|
||||
Do not link against shared libraries.
|
||||
|
||||
* `--sysroot`=_dir_:
|
||||
Set target system root directory to _dir_.
|
||||
|
||||
* `--trace`:
|
||||
Print name of each input file.
|
||||
|
||||
* `--undefined-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
|
||||
by `--undefined-version`.
|
||||
|
||||
* `--unique`=_pattern_:
|
||||
Don't merge input sections that match the given glob pattern _pattern_.
|
||||
|
||||
* `--unresolved-symbols`=[ `report-all` | `ignore-all` | `ignore-in-object-files` | `ignore-in-shared-libs` ]:
|
||||
How to handle undefined symbols.
|
||||
|
||||
* `--version-script`=_file_:
|
||||
Read version script from _file_. If _file_ does not exist in the current
|
||||
directory, it is searched from library search paths for the sake of
|
||||
compatibility with GNU ld.
|
||||
|
||||
* `--warn-common`, `--no-warn-common`:
|
||||
Warn about common symbols.
|
||||
|
||||
* `--warn-once`:
|
||||
Only warn once for each undefined symbol instead of warn for each relocation
|
||||
referring an undefined symbol.
|
||||
|
||||
* `--warn-unresolved-symbols`, `--error-unresolved-symbols`:
|
||||
Normally, the linker reports an error for unresolved symbols.
|
||||
`--warn-unresolved-symbols` option turns it into a warning.
|
||||
`--error-unresolved-symbols` option restores the default behavior.
|
||||
|
||||
* `--whole-archive`, `--no-whole-archive`:
|
||||
When archive files (`.a` files) are given to the linker, only object files
|
||||
that are needed to resolve undefined symbols are extracted from them and
|
||||
linked to an output file. `--whole-archive` changes that behavior for
|
||||
subsequent archives so that the linker extracts all object files and links
|
||||
them to an output. For example, if you are creating a shared object file and
|
||||
you want to include all archive members to the output, you should pass
|
||||
`--whole-archive`. `--no-whole-archive` restores the default behavior for
|
||||
subsequent archives.
|
||||
|
||||
* `--wrap`=_symbol_:
|
||||
Make _symbol_ be resolved to `__wrap_`_symbol_. The original symbol can be
|
||||
resolved as `__real_`_symbol_. This option is typically used for wrapping an
|
||||
existing function.
|
||||
|
||||
* `-z cet-report`=[ `warning` | `error` | `none` ]:
|
||||
Intel Control-flow Enforcement Technology (CET) is a new x86 feature
|
||||
available since Tiger Lake which is released in 2020. It defines new
|
||||
instructions to harden security to protect programs from control hijacking
|
||||
attacks. You can tell the compiler to use the feature by specifying the
|
||||
`-fcf-protection` flag.
|
||||
|
||||
`-z cet-report` flag is used to make sure that all object files were
|
||||
compiled with a correct `-fcf-protection` flag. If `warning` or `error` are
|
||||
given, `mold` prints out a warning or an error message if an object file was
|
||||
not compiled with the compiler flag.
|
||||
|
||||
`mold` looks for `GNU_PROPERTY_X86_FEATURE_1_IBT` bit and
|
||||
`GNU_PROPERTY_X86_FEATURE_1_SHSTK` bit in `.note.gnu.property` section to
|
||||
determine whether or not an object file was compiled with `-fcf-protection`.
|
||||
|
||||
* `-z now`, `-z lazy`:
|
||||
By default, functions referring to other ELF modules are resolved by the
|
||||
dynamic linker when they are called for the first time. `-z now` marks an
|
||||
executable or a shared library file so that all dynamic symbols are resolved
|
||||
when a file is loaded to memory. `-z lazy` restores the default behavior.
|
||||
|
||||
* `-z origin`:
|
||||
Mark object requiring immediate `$ORIGIN` processing at runtime.
|
||||
|
||||
* `-z ibt`:
|
||||
Turn on `GNU_PROPERTY_X86_FEATURE_1_IBT` bit in `.note.gnu.property` section
|
||||
to indicate that the output uses IBT-enabled PLT. This option implies `-z
|
||||
ibtplt`.
|
||||
|
||||
* `-z ibtplt`:
|
||||
Generate Intel Branch Tracking (IBT)-enabled PLT which is the default on
|
||||
x86-64. This is the default.
|
||||
|
||||
* `-z execstack`, `-z noexecstack`:
|
||||
By default, the pages for the stack area (i.e. the pages where local
|
||||
variables reside) are not executable for security reasons. `-z execstack`
|
||||
makes it executable. `-z noexecstack` restores the default behavior.
|
||||
|
||||
* `-z keep-text-section-prefix`, `-z nokeep-text-section-prefix`:
|
||||
Keep `.text.hot`, `.text.unknown`, `.text.unlikely`, `.text.startup`, and
|
||||
`.text.exit` as separate sections in the final binary instead of merging
|
||||
them as `.text`.
|
||||
|
||||
* `-z rodynamic`:
|
||||
Make the `.dynamic` section read-only.
|
||||
|
||||
* `-z relro`, `-z norelro`:
|
||||
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.
|
||||
|
||||
By default, `mold` generates a relro segment. `-z norelro` disables the
|
||||
feature.
|
||||
|
||||
* `-z sectionheader`, `-z nosectionheader`:
|
||||
`-z nosectionheader` tell the linker to omit the section header.
|
||||
By default, the linker does not omit the section header.
|
||||
|
||||
* `-z separate-loadable-segments`, `-z separate-code`, `-z noseparate-code`:
|
||||
If one memory page contains multiple segments, the page protection bits are
|
||||
set in such a way that the needed attributes (writable or executable) are
|
||||
satisfied for all segments. This usually happens at a boundary of two
|
||||
segments with two different attributes.
|
||||
|
||||
`separate-loadable-segments` adds paddings between segments with different
|
||||
attributes so that they do not share the same page. This is the default.
|
||||
|
||||
`separate-code` adds paddings only between executable and non-executable
|
||||
segments.
|
||||
|
||||
`noseparate-code` does not add any paddings between segments.
|
||||
|
||||
* `-z defs`, `-z nodefs`:
|
||||
Report undefined symbols (even with `--shared`).
|
||||
|
||||
* `-z shstk`:
|
||||
Enforce shadow stack by turning `GNU_PROPERTY_X86_FEATURE_1_SHSTK` bit in
|
||||
`.note.gnu.property` output section. Shadow stack is part of Intel
|
||||
Control-flow Enforcement Technology (CET), which is available since Tiger
|
||||
Lake (2020).
|
||||
|
||||
* `-z 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
|
||||
such dynamic relocations without reporting an error. `-z text` restores the
|
||||
default behavior.
|
||||
|
||||
* `-z max-page-size`=_number_:
|
||||
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.
|
||||
|
||||
* `-z nodelete`:
|
||||
Mark DSO non-deletable at runtime.
|
||||
|
||||
* `-z nodlopen`:
|
||||
Mark DSO not available to dlopen(3). This option makes it possible for the
|
||||
linker to optimize thread-local variable accesses by rewriting instructions
|
||||
for some targets.
|
||||
|
||||
* `-z nodump`:
|
||||
Mark DSO not available to dldump(3).
|
||||
|
||||
* `-z nocopyreloc`:
|
||||
Do not create copy relocations.
|
||||
|
||||
* `-z initfirst`:
|
||||
Mark DSO to be initialized first at runtime.
|
||||
|
||||
* `-z interpose`:
|
||||
Mark object to interpose all DSOs but executable.
|
||||
|
||||
* `-(`, `-)`, `-EL`, `-O`_number_, `--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
|
||||
|
||||
* `MOLD_JOBS`:
|
||||
If this variable is set to `1`, only one `mold` 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.
|
||||
|
||||
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.
|
||||
|
||||
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 `1` to see if it addresses the
|
||||
OOM issue.
|
||||
|
||||
Currently, any value other than `1` is silently ignored.
|
||||
|
||||
* `MOLD_DEBUG`:
|
||||
If this variable is set to a non-empty string, `mold` embeds its
|
||||
command-line options in the output file's `.comment` section.
|
||||
|
||||
* `MOLD_REPRO`:
|
||||
Setting this variable to a non-empty string has the same effect as passing
|
||||
the `--repro` option.
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
gold(1), ld(1), elf(5), ld.so(8)
|
||||
|
||||
## AUTHOR
|
||||
|
||||
Rui Ueyama <ruiu@cs.stanford.edu>
|
||||
|
||||
## BUGS
|
||||
|
||||
Report bugs to <https://github.com/rui314/mold/issues>.
|
||||
@@ -1,600 +0,0 @@
|
||||
// ARM32 is a bit special from the linker's viewpoint because ARM
|
||||
// processors support two different instruction encodings: Thumb and
|
||||
// ARM (in a narrower sense). Thumb instructions are either 16 bits or
|
||||
// 32 bits, while ARM instructions are all 32 bits. Feature-wise,
|
||||
// thumb is a subset of ARM, so not all ARM instructions are
|
||||
// representable in Thumb.
|
||||
//
|
||||
// ARM processors originally supported only ARM instructions. Thumb
|
||||
// instructions were later added to increase code density.
|
||||
//
|
||||
// ARM processors runs in either ARM mode or Thumb mode. The mode can
|
||||
// be switched using BX (branch and mode exchange)-family instructions.
|
||||
// We need to use such instructions to, for example, call a function
|
||||
// encoded in Thumb from a function encoded in ARM. Sometimes, the
|
||||
// linker even has to emit an interworking thunk code to switch from
|
||||
// Thumb to ARM.
|
||||
//
|
||||
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
|
||||
// byte boundaries.
|
||||
//
|
||||
// You can distinguish Thumb functions from ARM functions by looking
|
||||
// at the least significant bit (LSB) of its "address". If LSB is 0,
|
||||
// it's ARM; otherwise, Thumb. LSB is not a part of its real address.
|
||||
// For example, if a symbol `foo` is of type STT_FUNC and has value
|
||||
// 0x2001, then `foo` is a function using Thumb instructions whose
|
||||
// address is 0x2000 (not 0x2001).
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <tbb/parallel_sort.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = ARM32;
|
||||
|
||||
static u32 bit(u32 val, i64 pos) {
|
||||
return (val >> pos) & 1;
|
||||
}
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u32 bits(u32 val, i64 hi, i64 lo) {
|
||||
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
static i32 sign_extend(u32 val, u32 size) {
|
||||
return (i64)(val << (31 - size)) >> (31 - size);
|
||||
};
|
||||
|
||||
static void write_mov_imm(u8 *loc, u32 val) {
|
||||
u32 imm12 = bits(val, 11, 0);
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
*(u32 *)loc = (*(u32 *)loc & 0xfff0f000) | (imm4 << 16) | imm12;
|
||||
}
|
||||
|
||||
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);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
*(u16 *)loc = (*(u16 *)loc & 0xf800) | (sign << 10) | imm10;
|
||||
*(u16 *)(loc + 2) =
|
||||
(*(u16 *)(loc + 2) & 0xd000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
|
||||
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
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
u32 i = bit(val, 11);
|
||||
u32 imm3 = bits(val, 10, 8);
|
||||
u32 imm8 = bits(val, 7, 0);
|
||||
*(u16 *)loc = (*(u16 *)loc & 0b1111'1011'1111'0000) | (i << 10) | imm4;
|
||||
*(u16 *)(loc + 2) =
|
||||
((*(u16 *)(loc + 2)) & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
static const u32 plt0[] = {
|
||||
0xe52de004, // 1: push {lr}
|
||||
0xe59fe004, // ldr lr, [pc, #4]
|
||||
0xe08fe00e, // add lr, pc, lr
|
||||
0xe5bef008, // ldr pc, [lr, #8]!
|
||||
0x00000000, // .word .got.plt - 1b - 16
|
||||
};
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr - 16;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
static const u32 plt[] = {
|
||||
0xe59fc004, // 1: ldr ip, 2f
|
||||
0xe08cc00f, // add ip, ip, pc
|
||||
0xe59cf000, // ldr pc, [ip]
|
||||
0x00000000, // 2: .word sym@PLTGOT - 1b
|
||||
};
|
||||
|
||||
u8 *ent = buf + sizeof(plt0) + sym->get_plt_idx(ctx) * sizeof(plt);
|
||||
memcpy(ent, plt, sizeof(plt));
|
||||
*(u32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 12;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
static const u32 plt[] = {
|
||||
0xe59fc004, // 1: ldr ip, 2f
|
||||
0xe08cc00f, // add ip, ip, pc
|
||||
0xe59cf000, // ldr pc, [ip]
|
||||
0x00000000, // 2: .word sym@GOT - 1b
|
||||
};
|
||||
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * sizeof(plt);
|
||||
memcpy(ent, plt, sizeof(plt));
|
||||
*(u32 *)(ent + 12) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 12;
|
||||
}
|
||||
}
|
||||
|
||||
// ARM does not use .eh_frame for exception handling. Instead, it uses
|
||||
// .ARM.exidx and .ARM.extab. So this function is empty.
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
i64 frag_idx = 0;
|
||||
|
||||
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_ARM_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
const SectionFragmentRef<E> *frag_ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
frag_ref = &rel_fragments[frag_idx++];
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : this->get_addend(rel))
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define T (sym.get_addr(ctx) & 1)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_TARGET1:
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
*(u32 *)loc = S + A;
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_ARM_ABS32, (u32)sym.get_dynsym_idx(ctx)};
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_ARM_RELATIVE, 0};
|
||||
*(u32 *)loc = S + A;
|
||||
}
|
||||
continue;
|
||||
case R_ARM_REL32:
|
||||
*(u32 *)loc = S + A - P;
|
||||
continue;
|
||||
case R_ARM_THM_CALL:
|
||||
// THM_CALL relocation refers either BL or BLX instruction.
|
||||
// They are different in only one bit. We need to use BL if
|
||||
// the jump target is Thumb. Otherwise, use BLX.
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
write_thm_b_imm(loc, 4);
|
||||
*(u16 *)(loc + 2) |= (1 << 12); // rewrite with BL
|
||||
} else if (T) {
|
||||
write_thm_b_imm(loc, S + A - P);
|
||||
*(u16 *)(loc + 2) |= (1 << 12); // rewrite with BL
|
||||
} else {
|
||||
write_thm_b_imm(loc, align_to(S + A - P, 4));
|
||||
*(u16 *)(loc + 2) &= ~(1 << 12); // rewrite with BLX
|
||||
}
|
||||
continue;
|
||||
case R_ARM_BASE_PREL:
|
||||
*(u32 *)loc = GOT + A - P;
|
||||
continue;
|
||||
case R_ARM_GOT_PREL:
|
||||
*(u32 *)loc = G + A - P;
|
||||
continue;
|
||||
case R_ARM_GOT_BREL:
|
||||
*(u32 *)loc = G + A;
|
||||
continue;
|
||||
case R_ARM_TARGET2:
|
||||
*(u32 *)loc = GOT + G + A - P;
|
||||
continue;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24: {
|
||||
u32 val;
|
||||
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
val = 4;
|
||||
} else {
|
||||
val = S + A - P;
|
||||
}
|
||||
|
||||
*(u32 *)loc = (*(u32 *)loc & 0xff00'0000) | ((val >> 2) & 0x00ff'ffff);
|
||||
continue;
|
||||
}
|
||||
case R_ARM_THM_JUMP11: {
|
||||
assert(T);
|
||||
u32 val = (S + A - P) >> 1;
|
||||
*(u16 *)loc = (*(u16 *)loc & 0xf800) | (val & 0x07ff);
|
||||
continue;
|
||||
}
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (T) {
|
||||
write_thm_b_imm(loc, S + A - P);
|
||||
} else {
|
||||
assert(sym.extra.thumb_to_arm_thunk_idx != -1);
|
||||
u64 thunk_addr =
|
||||
ctx.thumb_to_arm->shdr.sh_addr +
|
||||
sym.extra.thumb_to_arm_thunk_idx * ThumbToArmSection::ENTRY_SIZE;
|
||||
write_thm_b_imm(loc, thunk_addr - P - 4);
|
||||
}
|
||||
continue;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
write_mov_imm(loc, ((S + A) | T) - P);
|
||||
continue;
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
write_mov_imm(loc, (S + A) | T);
|
||||
continue;
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
write_thm_mov_imm(loc, ((S + A) | T) - P);
|
||||
continue;
|
||||
case R_ARM_PREL31: {
|
||||
u32 val = S + A - P;
|
||||
*(u32 *)loc = (*(u32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
continue;
|
||||
}
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
write_thm_mov_imm(loc, (S + A) | T);
|
||||
continue;
|
||||
case R_ARM_MOVT_PREL:
|
||||
write_mov_imm(loc, (S + A - P) >> 16);
|
||||
continue;
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
write_thm_mov_imm(loc, (S + A - P) >> 16);
|
||||
continue;
|
||||
case R_ARM_MOVT_ABS:
|
||||
write_mov_imm(loc, (S + A) >> 16);
|
||||
continue;
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
write_thm_mov_imm(loc, (S + A) >> 16);
|
||||
continue;
|
||||
case R_ARM_TLS_GD32:
|
||||
*(u32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
continue;
|
||||
case R_ARM_TLS_LDM32:
|
||||
*(u32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
continue;
|
||||
case R_ARM_TLS_LDO32:
|
||||
*(u32 *)loc = S + A - ctx.tls_begin;
|
||||
continue;
|
||||
case R_ARM_TLS_IE32:
|
||||
*(u32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
continue;
|
||||
case R_ARM_TLS_LE32:
|
||||
*(u32 *)loc = S + A - ctx.tls_begin + 8;
|
||||
continue;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1)
|
||||
*(u32 *)loc = S - ctx.tls_begin + 8;
|
||||
else
|
||||
*(u32 *)loc = sym.get_tlsdesc_addr(ctx) + A - P - 6;
|
||||
continue;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
// BL -> NOP
|
||||
*(u32 *)loc = 0x8000f3af;
|
||||
} else {
|
||||
u64 addr = ctx.tls_trampoline->shdr.sh_addr;
|
||||
write_thm_b_imm(loc, align_to(addr - P - 4, 4));
|
||||
*(u16 *)(loc + 2) &= ~(1 << 12); // rewrite BL with BLX
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef T
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<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_ARM_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 addend;
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : this->get_addend(rel))
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_ARM_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= 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: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
break;
|
||||
case R_ARM_THM_CALL: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, NONE, PLT, PLT }, // DSO
|
||||
{ NONE, NONE, PLT, PLT }, // PIE
|
||||
{ NONE, NONE, PLT, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TARGET2:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (sym.is_imported || sym.get_type() == STT_GNU_IFUNC)
|
||||
sym.flags |= NEEDS_PLT | NEEDS_THUMB_TO_ARM_THUNK;
|
||||
else if (sym.esym().st_value % 2 == 0)
|
||||
sym.flags |= NEEDS_THUMB_TO_ARM_THUNK;
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
case R_ARM_PREL31: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_ARM_TLS_GD32:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
if (!ctx.relax_tlsdesc || sym.is_imported)
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
break;
|
||||
case R_ARM_THM_JUMP11:
|
||||
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_TLS_LE32:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If a function referenced by a Thumb B (branch) instruction is
|
||||
// resovled to a non-thumb function, we can't directly jump from the
|
||||
// thumb function to the ARM function. We can't rewrite B with BX
|
||||
// because there's no such BX instruction that takes an immediate.
|
||||
// BX takes only a register.
|
||||
//
|
||||
// In order to support such branch, we insert a small piece of code to
|
||||
// the resulting executable which switches the processor mode from
|
||||
// Thumb to ARM. This section contains such code.
|
||||
void ThumbToArmSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
if (sym->extra.thumb_to_arm_thunk_idx == -1) {
|
||||
sym->extra.thumb_to_arm_thunk_idx = symbols.size();
|
||||
symbols.push_back(sym);
|
||||
}
|
||||
}
|
||||
|
||||
void ThumbToArmSection::update_shdr(Context<E> &ctx) {
|
||||
this->shdr.sh_size = symbols.size() * ENTRY_SIZE;
|
||||
}
|
||||
|
||||
void ThumbToArmSection::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
i64 offset = 0;
|
||||
|
||||
static u16 insn[] = {
|
||||
0x4778, // bx pc
|
||||
0x46c0, // nop
|
||||
0, 0, // b <imm24>
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == ENTRY_SIZE);
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
memcpy(buf + offset, insn, sizeof(insn));
|
||||
|
||||
u32 val = sym->get_addr(ctx) - this->shdr.sh_addr - offset - 12;
|
||||
*(u32 *)(buf + offset + 4) = 0xea00'0000 | (0x00ff'ffff & (val >> 2));
|
||||
offset += sizeof(insn);
|
||||
}
|
||||
}
|
||||
|
||||
void TlsTrampolineSection::copy_buf(Context<E> &ctx) {
|
||||
// Trampoline code for TLSDESC
|
||||
static u32 insn[] = {
|
||||
0xe08e0000, // add r0, lr, r0
|
||||
0xe5901004, // ldr r1, [r0, #4]
|
||||
0xe12fff11, // bx r1
|
||||
};
|
||||
memcpy(ctx.buf + this->shdr.sh_offset, insn, sizeof(insn));
|
||||
}
|
||||
|
||||
// ARM executables use an .ARM.exidx section to look up an exception
|
||||
// handling record for the current instruction pointer. The table needs
|
||||
// to be sorted by their addresses.
|
||||
//
|
||||
// Other target uses .eh_frame_hdr instead for the same purpose.
|
||||
// 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 sort_arm_exidx(Context<E> &ctx) {
|
||||
Timer t(ctx, "sort_arm_exidx");
|
||||
|
||||
auto find_exidx = [&]() -> OutputSection<E> * {
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
if (osec->shdr.sh_type == SHT_ARM_EXIDX)
|
||||
return osec.get();
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
OutputSection<E> *osec = find_exidx();
|
||||
if (!osec)
|
||||
return;
|
||||
|
||||
// .ARM.exidx records consists of a signed 31-bit relative address
|
||||
// and a 32-bit value. The relative address indicates the start
|
||||
// address of a function that the record covers. The value is one of
|
||||
// the followings:
|
||||
//
|
||||
// 1. CANTUNWIND indicating that there's no unwinding info for the function,
|
||||
// 2. a compact unwinding record encoded into a 32-bit value, or
|
||||
// 3. a 31-bit relative address which points to a larger record in
|
||||
// the .ARM.extab section.
|
||||
//
|
||||
// CANTUNWIND is value 1. The most significant is set in (2) but not
|
||||
// in (3). So they can be distinguished just by looking at a value.
|
||||
struct Entry {
|
||||
u32 addr;
|
||||
u32 val;
|
||||
};
|
||||
|
||||
if (osec->shdr.sh_size % sizeof(Entry))
|
||||
Fatal(ctx) << "invalid .ARM.exidx section size";
|
||||
|
||||
Entry *begin = (Entry *)(ctx.buf + osec->shdr.sh_offset);
|
||||
Entry *end = (Entry *)(ctx.buf + osec->shdr.sh_offset + osec->shdr.sh_size);
|
||||
|
||||
struct Entry2 {
|
||||
u32 addr;
|
||||
u32 val;
|
||||
u32 idx;
|
||||
};
|
||||
|
||||
// Read section contents
|
||||
std::vector<Entry2> vec;
|
||||
vec.reserve(end - begin);
|
||||
for (Entry *it = begin; it < end; it++)
|
||||
vec.push_back({it->addr, it->val, (u32)(it - begin)});
|
||||
|
||||
// Sort the records
|
||||
tbb::parallel_sort(vec.begin(), vec.end(), [](const Entry2 &a, const Entry2 &b) {
|
||||
return sign_extend(a.addr, 30) + a.idx * sizeof(Entry) <
|
||||
sign_extend(b.addr, 30) + b.idx * sizeof(Entry);
|
||||
});
|
||||
|
||||
// Write back the sorted records while adjusting relative addresses
|
||||
for (i64 i = 0; i < vec.size(); i++) {
|
||||
u32 offset = (vec[i].idx - i) * sizeof(Entry);
|
||||
begin[i].addr = 0x7fff'ffff & (sign_extend(vec[i].addr, 30) + offset);
|
||||
|
||||
const u32 EXIDX_CANTUNWIND = 1;
|
||||
if (vec[i].val == EXIDX_CANTUNWIND || (vec[i].val & 0x8000'0000))
|
||||
begin[i].val = vec[i].val;
|
||||
else
|
||||
begin[i].val = 0x7fff'ffff & (sign_extend(vec[i].val, 30) + offset);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -1,798 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = ARM64;
|
||||
|
||||
static void write_adr(u8 *buf, u64 val) {
|
||||
u32 hi = (val & 0x1ffffc) << 3;
|
||||
u32 lo = (val & 3) << 29;
|
||||
*(u32 *)buf = (*(u32 *)buf & 0x9f00001f) | hi | lo;
|
||||
}
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u64 bits(u64 val, u64 hi, u64 lo) {
|
||||
return (val >> lo) & (((u64)1 << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
static u64 page(u64 val) {
|
||||
return val & ~(u64)0xfff;
|
||||
}
|
||||
|
||||
static void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
// Write PLT header
|
||||
static const u32 plt0[] = {
|
||||
0xa9bf7bf0, // stp x16, x30, [sp,#-16]!
|
||||
0x90000010, // adrp x16, .got.plt[2]
|
||||
0xf9400211, // ldr x17, [x16, .got.plt[2]]
|
||||
0x91000210, // add x16, x16, .got.plt[2]
|
||||
0xd61f0220, // br x17
|
||||
0xd503201f, // nop
|
||||
0xd503201f, // nop
|
||||
0xd503201f, // nop
|
||||
};
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr + 16;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
write_adr(buf + 4, bits(page(gotplt) - page(plt + 4), 32, 12));
|
||||
*(u32 *)(buf + 8) |= bits(gotplt, 11, 3) << 10;
|
||||
*(u32 *)(buf + 12) |= ((gotplt) & 0xfff) << 10;
|
||||
}
|
||||
|
||||
static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u8 *ent = buf + ctx.plt_hdr_size + sym.get_plt_idx(ctx) * ctx.plt_size;
|
||||
|
||||
static const u32 data[] = {
|
||||
0x90000010, // adrp x16, .got.plt[n]
|
||||
0xf9400211, // ldr x17, [x16, .got.plt[n]]
|
||||
0x91000210, // add x16, x16, .got.plt[n]
|
||||
0xd61f0220, // br x17
|
||||
};
|
||||
|
||||
u64 gotplt = sym.get_gotplt_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_adr(ent, bits(page(gotplt) - page(plt), 32, 12));
|
||||
*(u32 *)(ent + 4) |= bits(gotplt, 11, 3) << 10;
|
||||
*(u32 *)(ent + 8) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
write_plt_header(ctx, buf);
|
||||
for (Symbol<E> *sym : symbols)
|
||||
write_plt_entry(ctx, buf, *sym);
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * ARM64::pltgot_size;
|
||||
|
||||
static const u32 data[] = {
|
||||
0x90000010, // adrp x16, GOT[n]
|
||||
0xf9400211, // ldr x17, [x16, GOT[n]]
|
||||
0xd61f0220, // br x17
|
||||
0xd503201f, // nop
|
||||
};
|
||||
|
||||
u64 got = sym->get_got_addr(ctx);
|
||||
u64 plt = sym->get_plt_addr(ctx);
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_adr(ent, bits(page(got) - page(plt), 32, 12));
|
||||
*(u32 *)(ent + 4) |= bits(got, 11, 3) << 10;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
*(u64 *)loc = val;
|
||||
return;
|
||||
case R_AARCH64_PREL32:
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
case R_AARCH64_PREL64:
|
||||
*(u64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = get_range_extn();
|
||||
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_AARCH64_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
const SectionFragmentRef<E> *frag_ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
frag_ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
*(u64 *)loc = S + A;
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_AARCH64_ABS64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
*(u64 *)loc = A;
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_AARCH64_RELATIVE, 0, (i64)(S + A)};
|
||||
*(u64 *)loc = S + A;
|
||||
}
|
||||
continue;
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 1) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 2) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 3) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 4) << 10;
|
||||
continue;
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
*(u32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
*(u32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
*(u32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
*(u32 *)loc |= bits(S + A, 63, 48) << 5;
|
||||
continue;
|
||||
case R_AARCH64_ADR_GOT_PAGE: {
|
||||
i64 val = page(G + GOT + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_PG_HI21: {
|
||||
i64 val = page(S + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_LO21: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 20), (i64)1 << 20);
|
||||
write_adr(loc, val);
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26: {
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(u32 *)loc |= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
i64 lo = -(1 << 27);
|
||||
i64 hi = 1 << 27;
|
||||
i64 val = S + A - P;
|
||||
|
||||
if (val < lo || hi <= val) {
|
||||
RangeExtensionRef ref = range_extn[i];
|
||||
val = output_section->thunks[ref.thunk_idx]->get_addr(ref.sym_idx) + A - P;
|
||||
assert(lo <= val && val < hi);
|
||||
}
|
||||
|
||||
*(u32 *)loc |= (val >> 2) & 0x3ffffff;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 20), (i64)1 << 20);
|
||||
*(u32 *)loc |= bits(val, 20, 2) << 5;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_PREL16: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 15), (i64)1 << 15);
|
||||
*(u16 *)loc = val;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_PREL32: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 32);
|
||||
*(u32 *)loc = val;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_PREL64:
|
||||
*(u64 *)loc = S + A - P;
|
||||
continue;
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
*(u32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15: {
|
||||
i64 val = G + GOT + A - page(GOT);
|
||||
overflow_check(val, 0, 1 << 15);
|
||||
*(u32 *)loc |= bits(val, 14, 3) << 10;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: {
|
||||
i64 val = page(sym.get_gottp_addr(ctx) + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
*(u32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
continue;
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12: {
|
||||
i64 val = S + A - ctx.tls_begin + 16;
|
||||
overflow_check(val, 0, (i64)1 << 24);
|
||||
*(u32 *)loc |= bits(val, 23, 12) << 10;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A - ctx.tls_begin + 16, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21: {
|
||||
i64 val = page(sym.get_tlsgd_addr(ctx) + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
*(u32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21: {
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// adrp x0, 0 -> movz x0, #tls_ofset_hi, lsl #16
|
||||
i64 val = (S + A - ctx.tls_begin + 16);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
*(u32 *)loc = 0xd2a00000 | (bits(val, 32, 16) << 5);
|
||||
} else {
|
||||
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// ldr x2, [x0] -> movk x0, #tls_ofset_lo
|
||||
u32 offset_lo = (S + A - ctx.tls_begin + 16) & 0xffff;
|
||||
*(u32 *)loc = 0xf2800000 | (offset_lo << 5);
|
||||
} else {
|
||||
*(u32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
|
||||
}
|
||||
continue;
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// add x0, x0, #0 -> nop
|
||||
*(u32 *)loc = 0xd503201f;
|
||||
} else {
|
||||
*(u32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
|
||||
}
|
||||
continue;
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// blr x2 -> nop
|
||||
*(u32 *)loc = 0xd503201f;
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<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_AARCH64_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 addend;
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A;
|
||||
continue;
|
||||
case R_AARCH64_ABS32:
|
||||
*(u32 *)loc = S + A;
|
||||
continue;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_AARCH64_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ADR_GOT_PAGE:
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_AARCH64_ADR_PREL_PG_HI21: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (!ctx.relax_tlsdesc || sym.is_imported)
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
break;
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
case R_AARCH64_ADR_PREL_LO21:
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19:
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
case R_AARCH64_PREL16:
|
||||
case R_AARCH64_PREL32:
|
||||
case R_AARCH64_PREL64:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 &= (u8)~NEEDS_RANGE_EXTN_THUNK;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
|
||||
InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
// We always create a thunk for an absolute symbol conservatively
|
||||
// because `shrink_sections` may increase a distance between a
|
||||
// branch instruction and an absolute symbol. Branching to an
|
||||
// absolute location is extremely rare in real code, though.
|
||||
if (sym.is_absolute())
|
||||
return false;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol
|
||||
// and check if they are within reach.
|
||||
i64 S = sym.get_addr(ctx);
|
||||
i64 A = rel.r_addend;
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
i64 val = S + A - P;
|
||||
return -(1 << 27) <= val && val < (1 << 27);
|
||||
}
|
||||
|
||||
// We create a thunk no further than 100 MiB from any section.
|
||||
static constexpr i64 MAX_DISTANCE = 100 * 1024 * 1024;
|
||||
|
||||
// We create a thunk for each 10 MiB input sections.
|
||||
static constexpr i64 GROUP_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
static void create_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
std::span<InputSection<E> *> members = osec.members;
|
||||
members[0]->offset = 0;
|
||||
|
||||
// Initialize input sections with very large dummy offsets so that
|
||||
// sections that have got real offsets are separated from the ones
|
||||
// without in the virtual address space.
|
||||
tbb::parallel_for((i64)1, (i64)members.size(), [&](i64 i) {
|
||||
members[i]->offset = 1 << 31;
|
||||
});
|
||||
|
||||
// We create thunks from the beginning of the section to the end.
|
||||
// We manage progress using four offsets which increase monotonically.
|
||||
// The locations they point to are always A <= B <= C <= D.
|
||||
i64 a = 0;
|
||||
i64 b = 0;
|
||||
i64 c = 0;
|
||||
i64 d = 0;
|
||||
i64 offset = 0;
|
||||
|
||||
while (b < members.size()) {
|
||||
// Move D foward as far as we can jump from B to D.
|
||||
while (d < members.size() && offset - members[b]->offset < MAX_DISTANCE) {
|
||||
offset = align_to(offset, 1 << members[d]->p2align);
|
||||
members[d]->offset = offset;
|
||||
offset += members[d]->sh_size;
|
||||
d++;
|
||||
}
|
||||
|
||||
// Move C forward so that C is apart from B by GROUP_SIZE.
|
||||
while (c < members.size() &&
|
||||
members[c]->offset - members[b]->offset < GROUP_SIZE)
|
||||
c++;
|
||||
|
||||
// Move A forward so that A is reachable from C.
|
||||
if (c > 0) {
|
||||
i64 c_end = members[c - 1]->offset + members[c - 1]->sh_size;
|
||||
while (a < osec.thunks.size() &&
|
||||
osec.thunks[a]->offset < c_end - MAX_DISTANCE)
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
}
|
||||
|
||||
// Create a thunk for input sections between B and C and place it at D.
|
||||
osec.thunks.emplace_back(new RangeExtensionThunk<E>{osec});
|
||||
|
||||
RangeExtensionThunk<E> &thunk = *osec.thunks.back();
|
||||
thunk.thunk_idx = osec.thunks.size() - 1;
|
||||
thunk.offset = offset;
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need thunks.
|
||||
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
|
||||
[&](InputSection<E> *isec) {
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::vector<RangeExtensionRef> &range_extn = isec->get_range_extn();
|
||||
range_extn.resize(rels.size());
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type != R_AARCH64_CALL26 && rel.r_type != R_AARCH64_JUMP26)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
|
||||
|
||||
// Skip if the destination is within reach.
|
||||
if (is_reachable(ctx, sym, *isec, rel))
|
||||
continue;
|
||||
|
||||
// If the symbol is already in another thunk, reuse it.
|
||||
if (sym.extra.thunk_idx != -1) {
|
||||
range_extn[i] = {sym.extra.thunk_idx, sym.extra.thunk_sym_idx};
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the symbol to this thunk if it's not added already.
|
||||
range_extn[i] = {thunk.thunk_idx, -1};
|
||||
|
||||
if (!(sym.flags.fetch_or(NEEDS_RANGE_EXTN_THUNK) &
|
||||
NEEDS_RANGE_EXTN_THUNK)) {
|
||||
std::scoped_lock lock(thunk.mu);
|
||||
thunk.symbols.push_back(&sym);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Now that we know the number of symbols in the thunk, we can compute
|
||||
// its size.
|
||||
offset += thunk.size();
|
||||
|
||||
// Sort symbols added to the thunk to make the output deterministic.
|
||||
sort(thunk.symbols, [](Symbol<E> *a, Symbol<E> *b) { return *a < *b; });
|
||||
|
||||
// Assign offsets within the thunk to the symbols.
|
||||
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
|
||||
sym->extra.thunk_idx = thunk.thunk_idx;
|
||||
sym->extra.thunk_sym_idx = i++;
|
||||
}
|
||||
|
||||
// Scan relocations again to fix symbol offsets in the last thunk.
|
||||
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
|
||||
[&](InputSection<E> *isec) {
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = isec->get_range_extn();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
if (range_extn[i].thunk_idx == thunk.thunk_idx) {
|
||||
Symbol<E> &sym = *isec->file.symbols[rels[i].r_sym];
|
||||
range_extn[i].sym_idx = sym.extra.thunk_sym_idx;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Move B forward to point to the begining of the next group.
|
||||
b = c;
|
||||
}
|
||||
|
||||
while (a < osec.thunks.size())
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
|
||||
osec.shdr.sh_size = offset;
|
||||
}
|
||||
|
||||
static void gc_thunk_symbols(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
for (std::unique_ptr<RangeExtensionThunk<E>> &thunk : osec.thunks) {
|
||||
i64 sz = thunk->symbols.size();
|
||||
thunk->symbol_map.resize(sz);
|
||||
thunk->used.reset(new std::atomic_bool[sz]{});
|
||||
}
|
||||
|
||||
// Mark referenced thunk symbols
|
||||
tbb::parallel_for_each(osec.members, [&](InputSection<E> *isec) {
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = isec->get_range_extn();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
RangeExtensionRef &ref = range_extn[i];
|
||||
if (ref.thunk_idx == -1)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *isec->file.symbols[rels[i].r_sym];
|
||||
if (!is_reachable(ctx, sym, *isec, rels[i]))
|
||||
osec.thunks[ref.thunk_idx]->used[ref.sym_idx] = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Remove unreferenced thunk symbols
|
||||
tbb::parallel_for_each(osec.thunks,
|
||||
[&](std::unique_ptr<RangeExtensionThunk<E>> &thunk) {
|
||||
i64 i = 0;
|
||||
for (i64 j = 0; j < thunk->symbols.size(); j++) {
|
||||
if (thunk->used[j]) {
|
||||
thunk->symbol_map[j] = i;
|
||||
thunk->symbols[i] = thunk->symbols[j];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
thunk->symbols.resize(i);
|
||||
});
|
||||
}
|
||||
|
||||
static void shrink_section(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
std::span<std::unique_ptr<RangeExtensionThunk<E>>> thunks = osec.thunks;
|
||||
std::span<InputSection<E> *> members = osec.members;
|
||||
|
||||
i64 offset = 0;
|
||||
|
||||
auto add_thunk = [&] {
|
||||
thunks[0]->offset = offset;
|
||||
offset += thunks[0]->size();
|
||||
thunks = thunks.subspan(1);
|
||||
};
|
||||
|
||||
auto add_isec = [&] {
|
||||
offset = align_to(offset, 1 << members[0]->p2align);
|
||||
members[0]->offset = offset;
|
||||
offset += members[0]->sh_size;
|
||||
members = members.subspan(1);
|
||||
};
|
||||
|
||||
while (!thunks.empty() && !members.empty()) {
|
||||
if (thunks[0]->offset < members[0]->offset)
|
||||
add_thunk();
|
||||
else
|
||||
add_isec();
|
||||
}
|
||||
|
||||
while (!thunks.empty())
|
||||
add_thunk();
|
||||
while (!members.empty())
|
||||
add_isec();
|
||||
|
||||
assert(offset <= osec.shdr.sh_size);
|
||||
osec.shdr.sh_size = offset;
|
||||
}
|
||||
|
||||
// ARM64's call/jump instructions take 27 bits displacement, so they
|
||||
// can refer only up to ±128 MiB. If a branch target is further than
|
||||
// that, we need to let it branch to a linker-synthesized code
|
||||
// sequence that construct a full 32 bit address in a register and
|
||||
// jump there. That linker-synthesized code is called "thunk".
|
||||
i64 create_range_extension_thunks(Context<E> &ctx) {
|
||||
Timer t(ctx, "create_range_extension_thunks");
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->range_extn.resize(file->sections.size());
|
||||
|
||||
// First, we create thunks with a pessimistic assumption that all
|
||||
// out-of-section relocations would need thunks. To do so, we start
|
||||
// with an initial layout in which output sections are separated far
|
||||
// apart.
|
||||
for (i64 i = 0; Chunk<E> *chunk : ctx.chunks)
|
||||
if (chunk->shdr.sh_flags & SHF_ALLOC)
|
||||
chunk->shdr.sh_addr = i++ << 31;
|
||||
|
||||
std::vector<OutputSection<E> *> sections;
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
if (!osec->members.empty() && (osec->shdr.sh_flags & SHF_EXECINSTR))
|
||||
sections.push_back(osec.get());
|
||||
|
||||
for (OutputSection<E> *osec : sections)
|
||||
create_thunks(ctx, *osec);
|
||||
|
||||
// Recompute file layout.
|
||||
set_osec_offsets(ctx);
|
||||
|
||||
// Based on the current file layout, remove thunk symbols that turned
|
||||
// out to be unnecessary.
|
||||
tbb::parallel_for_each(sections, [&](OutputSection<E> *osec) {
|
||||
gc_thunk_symbols(ctx, *osec);
|
||||
});
|
||||
|
||||
// Recompute output section sizes that contain thunks. New section
|
||||
// sizes must be equal to or smaller than previous values, so all
|
||||
// relocations that were previously reachable will still be reachable
|
||||
// after this step.
|
||||
for (OutputSection<E> *osec : sections)
|
||||
shrink_section(ctx, *osec);
|
||||
|
||||
// Compute the final layout.
|
||||
return set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
static const u32 data[] = {
|
||||
0x90000010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
|
||||
0x91000210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(ENTRY_SIZE == sizeof(data));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = output_section.shdr.sh_addr + offset + i * ENTRY_SIZE;
|
||||
|
||||
u8 *loc = buf + i * ENTRY_SIZE;
|
||||
memcpy(loc , data, sizeof(data));
|
||||
write_adr(loc, bits(page(S) - page(P), 32, 12));
|
||||
*(u32 *)(loc + 4) |= bits(S, 11, 0) << 10;
|
||||
}
|
||||
}
|
||||
|
||||
void write_thunks(Context<E> &ctx) {
|
||||
Timer t(ctx, "write_thunks");
|
||||
|
||||
tbb::parallel_for_each(ctx.output_sections,
|
||||
[&](std::unique_ptr<OutputSection<E>> &osec) {
|
||||
tbb::parallel_for_each(osec->thunks,
|
||||
[&](std::unique_ptr<RangeExtensionThunk<E>> &thunk) {
|
||||
thunk->copy_buf(ctx);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -1,555 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = I386;
|
||||
|
||||
static void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0xb3, 0, 0, 0, 0, // pushl GOTPLT+4(%ebx)
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *GOTPLT+8(%ebx)
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 8;
|
||||
} else {
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0x35, 0, 0, 0, 0, // pushl GOTPLT+4
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+8
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr + 8;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym,
|
||||
i64 idx) {
|
||||
u8 *ent = buf + ctx.plt_hdr_size + sym.get_plt_idx(ctx) * ctx.plt_size;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 data[] = {
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0x68, 0, 0, 0, 0, // pushl $reloc_offset
|
||||
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x68, 0, 0, 0, 0, // pushl $reloc_offset
|
||||
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym.get_gotplt_addr(ctx);
|
||||
}
|
||||
|
||||
*(u32 *)(ent + 7) = idx * sizeof(ElfRel<E>);
|
||||
*(u32 *)(ent + 12) = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 16;
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
write_plt_header(ctx, buf);
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++)
|
||||
write_plt_entry(ctx, buf, *symbols[i], i);
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 data[] = {
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
}
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_NONE:
|
||||
return;
|
||||
case R_386_32:
|
||||
*(u32 *)loc = val;
|
||||
return;
|
||||
case R_386_PC32:
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
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_386_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
const SectionFragmentRef<E> *frag_ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
frag_ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto write8 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write8s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(u16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write16s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 15), 1 << 15);
|
||||
*(u16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
*(u32 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : this->get_addend(rel))
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
write8(S + A);
|
||||
continue;
|
||||
case R_386_16:
|
||||
write16(S + A);
|
||||
continue;
|
||||
case R_386_32:
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
write32(S + A);
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_386_32, (u32)sym.get_dynsym_idx(ctx)};
|
||||
write32(A);
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_386_RELATIVE, 0};
|
||||
write32(S + A);
|
||||
}
|
||||
continue;
|
||||
case R_386_PC8:
|
||||
write8s(S + A);
|
||||
continue;
|
||||
case R_386_PC16:
|
||||
write16s(S + A);
|
||||
continue;
|
||||
case R_386_PC32:
|
||||
if (sym.is_absolute() || !sym.is_imported || !ctx.arg.shared) {
|
||||
write32(S + A - P);
|
||||
} else {
|
||||
*dynrel++ = {P, R_386_32, (u32)sym.get_dynsym_idx(ctx)};
|
||||
write32(A);
|
||||
}
|
||||
continue;
|
||||
case R_386_PLT32:
|
||||
write32(S + A - P);
|
||||
continue;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
write32(G + A);
|
||||
continue;
|
||||
case R_386_GOTOFF:
|
||||
write32(S + A - GOT);
|
||||
continue;
|
||||
case R_386_GOTPC:
|
||||
write32(GOT + A - P);
|
||||
continue;
|
||||
case R_386_TLS_GOTIE:
|
||||
write32(sym.get_gottp_addr(ctx) + A - GOT);
|
||||
continue;
|
||||
case R_386_TLS_LE:
|
||||
write32(S + A - ctx.tls_end);
|
||||
continue;
|
||||
case R_386_TLS_IE:
|
||||
write32(sym.get_gottp_addr(ctx) + A);
|
||||
continue;
|
||||
case R_386_TLS_GD:
|
||||
if (sym.get_tlsgd_idx(ctx) == -1) {
|
||||
// Relax GD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_386_PLT32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %rax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // add $0, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 5) = ctx.tls_end - S - A;
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %rax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // add $0, %rax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 6) = ctx.tls_end - S - A;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
} else {
|
||||
write32(sym.get_tlsgd_addr(ctx) + A - GOT);
|
||||
}
|
||||
continue;
|
||||
case R_386_TLS_LDM:
|
||||
if (ctx.got->tlsld_idx == -1) {
|
||||
// Relax LD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_386_PLT32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x8d, 0x74, 0x26, 0x00, // lea (%esi,1), %esi
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x8d, 0x74, 0x26, 0x00, // lea (%esi,1), %esi
|
||||
0x66, 0x90,
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
} else {
|
||||
write32(ctx.got->get_tlsld_addr(ctx) + A - GOT);
|
||||
}
|
||||
continue;
|
||||
case R_386_TLS_LDO_32:
|
||||
if (ctx.got->tlsld_idx == -1)
|
||||
write32(S + A - ctx.tls_end);
|
||||
else
|
||||
write32(S + A - ctx.tls_begin);
|
||||
continue;
|
||||
case R_386_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
continue;
|
||||
case R_386_TLS_GOTDESC:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
static const u8 insn[] = {
|
||||
0x8d, 0x05, 0, 0, 0, 0, // lea 0, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
write32(S + A - ctx.tls_end);
|
||||
} else {
|
||||
write32(sym.get_tlsdesc_addr(ctx) + A - GOT);
|
||||
}
|
||||
continue;
|
||||
case R_386_TLS_DESC_CALL:
|
||||
if (ctx.arg.relax && !ctx.arg.shared) {
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<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_386_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 addend;
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto write8 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write8s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(u16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write16s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 15), 1 << 15);
|
||||
*(u16 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : this->get_addend(rel))
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
write8(S + A);
|
||||
continue;
|
||||
case R_386_16:
|
||||
write16(S + A);
|
||||
continue;
|
||||
case R_386_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A;
|
||||
continue;
|
||||
case R_386_PC8:
|
||||
write8s(S + A);
|
||||
continue;
|
||||
case R_386_PC16:
|
||||
write16s(S + A);
|
||||
continue;
|
||||
case R_386_PC32:
|
||||
*(u32 *)loc = S + A;
|
||||
continue;
|
||||
case R_386_GOTPC:
|
||||
*(u32 *)loc = GOT + A;
|
||||
continue;
|
||||
case R_386_GOTOFF:
|
||||
*(u32 *)loc = S + A - GOT;
|
||||
continue;
|
||||
case R_386_TLS_LDO_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A - ctx.tls_begin;
|
||||
continue;
|
||||
case R_386_SIZE32:
|
||||
*(u32 *)loc = sym.esym().st_size + A;
|
||||
continue;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
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<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_386_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_16: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // DSO
|
||||
{ NONE, ERROR, ERROR, ERROR }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_386_32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_386_PC8:
|
||||
case R_386_PC16: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_386_PC32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, DYNREL, DYNREL }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
case R_386_GOTPC:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_386_PLT32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, NONE, PLT, PLT }, // DSO
|
||||
{ NONE, NONE, PLT, PLT }, // PIE
|
||||
{ NONE, NONE, PLT, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_IE:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_GOT32)
|
||||
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported)
|
||||
i++;
|
||||
else
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_GOT32)
|
||||
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
if (!ctx.arg.relax || ctx.arg.shared)
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_DESC_CALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -1,813 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = RISCV64;
|
||||
|
||||
static u32 bit(u32 val, i64 pos) {
|
||||
return (val >> pos) & 1;
|
||||
}
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u32 bits(u32 val, i64 hi, i64 lo) {
|
||||
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
static u32 itype(u32 val) {
|
||||
return val << 20;
|
||||
}
|
||||
|
||||
static u32 stype(u32 val) {
|
||||
return bits(val, 11, 5) << 25 | bits(val, 4, 0) << 7;
|
||||
}
|
||||
|
||||
static u32 btype(u32 val) {
|
||||
return bit(val, 12) << 31 | bits(val, 10, 5) << 25 |
|
||||
bits(val, 4, 1) << 8 | bit(val, 11) << 7;
|
||||
}
|
||||
|
||||
static u32 utype(u32 val) {
|
||||
// U-type instructions are used in combination with I-type
|
||||
// instructions. U-type insn sets an immediate to the upper 20-bits
|
||||
// of a register. I-type insn sign-extends a 12-bits immediate and
|
||||
// add it to a register value to construct a complete value. 0x800
|
||||
// is added here to compensate for the sign-extension.
|
||||
return bits(val + 0x800, 31, 12) << 12;
|
||||
}
|
||||
|
||||
static u32 jtype(u32 val) {
|
||||
return bit(val, 20) << 31 | bits(val, 10, 1) << 21 |
|
||||
bit(val, 11) << 20 | bits(val, 19, 12) << 12;
|
||||
}
|
||||
|
||||
static u32 cbtype(u32 val) {
|
||||
return bit(val, 8) << 12 | bit(val, 4) << 11 | bit(val, 3) << 10 |
|
||||
bit(val, 7) << 6 | bit(val, 6) << 5 | bit(val, 2) << 4 |
|
||||
bit(val, 1) << 3 | bit(val, 5) << 2;
|
||||
}
|
||||
|
||||
static u32 cjtype(u32 val) {
|
||||
return bit(val, 11) << 12 | bit(val, 4) << 11 | bit(val, 9) << 10 |
|
||||
bit(val, 8) << 9 | bit(val, 10) << 8 | bit(val, 6) << 7 |
|
||||
bit(val, 7) << 6 | bit(val, 3) << 5 | bit(val, 2) << 4 |
|
||||
bit(val, 1) << 3 | bit(val, 5) << 2;
|
||||
}
|
||||
|
||||
static void write_itype(u32 *loc, u32 val) {
|
||||
u32 mask = 0b000000'00000'11111'111'11111'1111111;
|
||||
*loc = (*loc & mask) | itype(val);
|
||||
}
|
||||
|
||||
static void write_stype(u32 *loc, u32 val) {
|
||||
u32 mask = 0b000000'11111'11111'111'00000'1111111;
|
||||
*loc = (*loc & mask) | stype(val);
|
||||
}
|
||||
|
||||
static void write_btype(u32 *loc, u32 val) {
|
||||
u32 mask = 0b000000'11111'11111'111'00000'1111111;
|
||||
*loc = (*loc & mask) | btype(val);
|
||||
}
|
||||
|
||||
static void write_utype(u32 *loc, u32 val) {
|
||||
u32 mask = 0b000000'00000'00000'000'11111'1111111;
|
||||
*loc = (*loc & mask) | utype(val);
|
||||
}
|
||||
|
||||
static void write_jtype(u32 *loc, u32 val) {
|
||||
u32 mask = 0b000000'00000'00000'000'11111'1111111;
|
||||
*loc = (*loc & mask) | jtype(val);
|
||||
}
|
||||
|
||||
static void write_cbtype(u16 *loc, u32 val) {
|
||||
u32 mask = 0b1110001110000011;
|
||||
*loc = (*loc & mask) | cbtype(val);
|
||||
}
|
||||
|
||||
static void write_cjtype(u16 *loc, u32 val) {
|
||||
u32 mask = 0b1110000000000011;
|
||||
*loc = (*loc & mask) | cjtype(val);
|
||||
}
|
||||
|
||||
static void write_plt_header(Context<E> &ctx) {
|
||||
u32 *buf = (u32 *)(ctx.buf + ctx.plt->shdr.sh_offset);
|
||||
|
||||
static const u32 plt0[] = {
|
||||
0x00000397, // auipc t2, %pcrel_hi(.got.plt)
|
||||
0x41c30333, // sub t1, t1, t3 # .plt entry + hdr + 12
|
||||
0x0003be03, // ld t3, %pcrel_lo(1b)(t2) # _dl_runtime_resolve
|
||||
0xfd430313, // addi t1, t1, -44 # .plt entry
|
||||
0x00038293, // addi t0, t2, %pcrel_lo(1b) # &.got.plt
|
||||
0x00135313, // srli t1, t1, 1 # .plt entry offset
|
||||
0x0082b283, // ld t0, 8(t0) # link map
|
||||
0x000e0067, // jr t3
|
||||
};
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
write_utype(buf, gotplt - plt);
|
||||
write_itype(buf + 2, gotplt - plt);
|
||||
write_itype(buf + 4, gotplt - plt);
|
||||
}
|
||||
|
||||
static void write_plt_entry(Context<E> &ctx, Symbol<E> &sym) {
|
||||
u32 *ent = (u32 *)(ctx.buf + ctx.plt->shdr.sh_offset + ctx.plt_hdr_size +
|
||||
sym.get_plt_idx(ctx) * ctx.plt_size);
|
||||
|
||||
static const u32 data[] = {
|
||||
0x00000e17, // auipc t3, %pcrel_hi([email protected])
|
||||
0x000e3e03, // ld t3, %pcrel_lo(1b)(t3)
|
||||
0x000e0367, // jalr t1, t3
|
||||
0x00000013, // nop
|
||||
};
|
||||
|
||||
u64 gotplt = sym.get_gotplt_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_utype(ent, gotplt - plt);
|
||||
write_itype(ent + 1, gotplt - plt);
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
write_plt_header(ctx);
|
||||
for (Symbol<E> *sym : symbols)
|
||||
write_plt_entry(ctx, *sym);
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u32 *buf = (u32 *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
static const u32 data[] = {
|
||||
0x00000e17, // auipc t3, %pcrel_hi([email protected])
|
||||
0x000e3e03, // ld t3, %pcrel_lo(1b)(t3)
|
||||
0x000e0367, // jalr t1, t3
|
||||
0x00000013, // nop
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u32 *ent = buf + sym->get_pltgot_idx(ctx) * 4;
|
||||
u64 got = sym->get_got_addr(ctx);
|
||||
u64 plt = sym->get_plt_addr(ctx);
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_utype(ent, got - plt);
|
||||
write_itype(ent + 1, got - plt);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_ADD32:
|
||||
*(u32 *)loc += val;
|
||||
return;
|
||||
case R_RISCV_SUB8:
|
||||
*loc -= val;
|
||||
return;
|
||||
case R_RISCV_SUB16:
|
||||
*(u16 *)loc -= val;
|
||||
return;
|
||||
case R_RISCV_SUB32:
|
||||
*(u32 *)loc -= val;
|
||||
return;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - val) & 0b0011'1111);
|
||||
return;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | (val & 0b0011'1111);
|
||||
return;
|
||||
case R_RISCV_SET8:
|
||||
*loc = val;
|
||||
return;
|
||||
case R_RISCV_SET16:
|
||||
*(u16 *)loc = val;
|
||||
return;
|
||||
case R_RISCV_32_PCREL:
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<i32> r_deltas = get_r_deltas();
|
||||
|
||||
i64 frag_idx = 0;
|
||||
|
||||
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_RISCV_NONE || rel.r_type == R_RISCV_RELAX ||
|
||||
rel.r_type == R_RISCV_ALIGN)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
i64 r_offset = rel.r_offset + r_deltas[i];
|
||||
u8 *loc = base + r_offset;
|
||||
|
||||
const SectionFragmentRef<E> *frag_ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
frag_ref = &rel_fragments[frag_idx++];
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
*(u32 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
*(u64 *)loc = S + A;
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_RISCV_64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
*(u64 *)loc = A;
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_RISCV_RELATIVE, 0, (i64)(S + A)};
|
||||
*(u64 *)loc = S + A;
|
||||
}
|
||||
break;
|
||||
case R_RISCV_TLS_DTPMOD32:
|
||||
case R_RISCV_TLS_DTPMOD64:
|
||||
case R_RISCV_TLS_DTPREL32:
|
||||
case R_RISCV_TLS_DTPREL64:
|
||||
case R_RISCV_TLS_TPREL32:
|
||||
case R_RISCV_TLS_TPREL64:
|
||||
Error(ctx) << *this << ": unsupported relocation: " << rel;
|
||||
break;
|
||||
case R_RISCV_BRANCH:
|
||||
write_btype((u32 *)loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_JAL:
|
||||
write_jtype((u32 *)loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT: {
|
||||
if (r_deltas[i + 1] - r_deltas[i] != 0) {
|
||||
// auipc + jalr -> jal
|
||||
assert(r_deltas[i + 1] - r_deltas[i] == -4);
|
||||
u32 jalr = *(u32 *)&contents[rels[i].r_offset + 4];
|
||||
*(u32 *)loc = (0b11111'000000 & jalr) | 0b101111;
|
||||
write_jtype((u32 *)loc, S + A - P);
|
||||
} else {
|
||||
u64 val = sym.esym().is_undef_weak() ? 0 : S + A - P;
|
||||
write_utype((u32 *)loc, val);
|
||||
write_itype((u32 *)(loc + 4), val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_GOT_HI20:
|
||||
*(u32 *)loc = G + GOT + A - P;
|
||||
break;
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
*(u32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_RISCV_TLS_GD_HI20:
|
||||
*(u32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_RISCV_PCREL_HI20:
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// Calling an undefined weak symbol does not make sense.
|
||||
// We make such call into an infinite loop. This should
|
||||
// help debugging of a faulty program.
|
||||
*(u32 *)loc = P;
|
||||
} else {
|
||||
*(u32 *)loc = S + A - P;
|
||||
}
|
||||
break;
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
assert(sym.get_input_section() == this);
|
||||
assert(sym.value < r_offset);
|
||||
write_itype((u32 *)loc, *(u32 *)(base + sym.value));
|
||||
break;
|
||||
case R_RISCV_LO12_I:
|
||||
case R_RISCV_TPREL_LO12_I:
|
||||
write_itype((u32 *)loc, S + A);
|
||||
break;
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
assert(sym.get_input_section() == this);
|
||||
assert(sym.value < r_offset);
|
||||
write_stype((u32 *)loc, *(u32 *)(base + sym.value));
|
||||
break;
|
||||
case R_RISCV_LO12_S:
|
||||
case R_RISCV_TPREL_LO12_S:
|
||||
write_stype((u32 *)loc, S + A);
|
||||
break;
|
||||
case R_RISCV_HI20:
|
||||
write_utype((u32 *)loc, S + A);
|
||||
break;
|
||||
case R_RISCV_TPREL_HI20:
|
||||
write_utype((u32 *)loc, S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_RISCV_TPREL_ADD:
|
||||
break;
|
||||
case R_RISCV_ADD8:
|
||||
loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD16:
|
||||
*(u16 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD32:
|
||||
*(u32 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD64:
|
||||
*(u64 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_SUB8:
|
||||
loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB16:
|
||||
*(u16 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB32:
|
||||
*(u32 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB64:
|
||||
*(u64 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_RVC_BRANCH:
|
||||
write_cbtype((u16 *)loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_RVC_JUMP:
|
||||
write_cjtype((u16 *)loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_RVC_LUI:
|
||||
Error(ctx) << *this << ": unsupported relocation: " << rel;
|
||||
break;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | ((S + A) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET8:
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET16:
|
||||
*(u16 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET32:
|
||||
*(u32 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_32_PCREL:
|
||||
*(u32 *)loc = S + A - P;
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
|
||||
// In the above loop, PC-relative HI20 relocations overwrote
|
||||
// instructions with full 32-bit values to allow their corresponding
|
||||
// PCREL_LO12 relocations to read their values. This loop restore
|
||||
// the original instructions.
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
switch (rels[i].r_type) {
|
||||
case R_RISCV_GOT_HI20:
|
||||
case R_RISCV_PCREL_HI20:
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
case R_RISCV_TLS_GD_HI20: {
|
||||
u32 *loc = (u32 *)(base + rels[i].r_offset + r_deltas[i]);
|
||||
u32 val = *loc;
|
||||
*loc = *(u32 *)&contents[rels[i].r_offset];
|
||||
write_utype(loc, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<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_RISCV_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 addend;
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
*(u32 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_ADD8:
|
||||
*loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD16:
|
||||
*(u16 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD32:
|
||||
*(u32 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD64:
|
||||
*(u64 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_SUB8:
|
||||
*loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB16:
|
||||
*(u16 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB32:
|
||||
*(u32 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB64:
|
||||
*(u64 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | ((S + A) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET8:
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET16:
|
||||
*(u16 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET32:
|
||||
*(u32 *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
|
||||
// A non-alloc section isn't relaxed, so just copy it as one big chunk.
|
||||
if (!(shdr().sh_flags & SHF_ALLOC)) {
|
||||
if (is_compressed())
|
||||
uncompress(ctx, buf);
|
||||
else
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
return;
|
||||
}
|
||||
|
||||
// Memory-allocated sections may be relaxed, so copy each segment
|
||||
// individually.
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<i32> r_deltas = get_r_deltas();
|
||||
i64 pos = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
i64 delta = r_deltas[i + 1] - r_deltas[i];
|
||||
if (delta == 0)
|
||||
continue;
|
||||
assert(delta < 0);
|
||||
|
||||
const ElfRel<E> &r = rels[i];
|
||||
memcpy(buf, contents.data() + pos, r.r_offset - pos);
|
||||
buf += r.r_offset - pos;
|
||||
pos = r.r_offset - delta;
|
||||
}
|
||||
|
||||
memcpy(buf, contents.data() + pos, contents.size() - pos);
|
||||
}
|
||||
|
||||
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<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_RISCV_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
case R_RISCV_HI20: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // DSO
|
||||
{ NONE, ERROR, ERROR, ERROR }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_64: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_TLS_DTPMOD32:
|
||||
case R_RISCV_TLS_DTPMOD64:
|
||||
case R_RISCV_TLS_DTPREL32:
|
||||
case R_RISCV_TLS_DTPREL64:
|
||||
case R_RISCV_TLS_TPREL32:
|
||||
case R_RISCV_TLS_TPREL64:
|
||||
Error(ctx) << *this << ": unsupported relocation: " << rel;
|
||||
break;
|
||||
case R_RISCV_BRANCH:
|
||||
case R_RISCV_JAL:
|
||||
break;
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_RISCV_GOT_HI20:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
ctx.has_gottp_rel = true;
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_RISCV_TLS_GD_HI20:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_RISCV_PCREL_HI20:
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
case R_RISCV_LO12_I:
|
||||
case R_RISCV_LO12_S:
|
||||
case R_RISCV_TPREL_HI20:
|
||||
case R_RISCV_TPREL_LO12_I:
|
||||
case R_RISCV_TPREL_LO12_S:
|
||||
case R_RISCV_TPREL_ADD:
|
||||
case R_RISCV_ADD8:
|
||||
case R_RISCV_ADD16:
|
||||
case R_RISCV_ADD32:
|
||||
case R_RISCV_ADD64:
|
||||
case R_RISCV_SUB8:
|
||||
case R_RISCV_SUB16:
|
||||
case R_RISCV_SUB32:
|
||||
case R_RISCV_SUB64:
|
||||
case R_RISCV_ALIGN:
|
||||
break;
|
||||
case R_RISCV_RVC_BRANCH:
|
||||
case R_RISCV_RVC_JUMP:
|
||||
break;
|
||||
case R_RISCV_RVC_LUI:
|
||||
Error(ctx) << *this << ": unsupported relocation: " << rel;
|
||||
break;
|
||||
case R_RISCV_RELAX:
|
||||
case R_RISCV_SUB6:
|
||||
case R_RISCV_SET6:
|
||||
case R_RISCV_SET8:
|
||||
case R_RISCV_SET16:
|
||||
case R_RISCV_SET32:
|
||||
break;
|
||||
case R_RISCV_32_PCREL: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_resizable(Context<E> &ctx, InputSection<E> *isec) {
|
||||
return isec && (isec->shdr().sh_flags & SHF_ALLOC);
|
||||
}
|
||||
|
||||
// Initializes r_deltas and sorted_symbols.
|
||||
static void initialize_storage(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->r_deltas.resize(file->sections.size());
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (is_resizable(ctx, isec.get()))
|
||||
isec->get_r_deltas().resize(isec->get_rels(ctx).size() + 1);
|
||||
|
||||
file->sorted_symbols.resize(file->sections.size());
|
||||
|
||||
for (Symbol<E> *sym : file->symbols)
|
||||
if (sym->file == file)
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
file->sorted_symbols[isec->shndx].push_back(sym);
|
||||
|
||||
for (std::vector<Symbol<E> *> &vec : file->sorted_symbols)
|
||||
sort(vec, [](Symbol<E> *a, Symbol<E> *b) { return a->value < b->value; });
|
||||
});
|
||||
}
|
||||
|
||||
// Returns the distance between a relocated place and a symbol.
|
||||
static 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 `relax_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 INT32_MAX;
|
||||
|
||||
// Likewise, relocations against weak undefined symbols won't be relaxed.
|
||||
if (sym.esym().is_undef_weak())
|
||||
return INT32_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;
|
||||
}
|
||||
|
||||
// Relax R_RISCV_CALL and R_RISCV_CALL_PLT relocations.
|
||||
static void relax_section(Context<E> &ctx, InputSection<E> &isec) {
|
||||
std::span<i32> r_deltas = isec.get_r_deltas();
|
||||
std::span<Symbol<E> *> syms = isec.get_sorted_symbols();
|
||||
i64 delta = 0;
|
||||
|
||||
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &r = rels[i];
|
||||
i64 delta2 = 0;
|
||||
|
||||
r_deltas[i] += delta;
|
||||
|
||||
switch (r.r_type) {
|
||||
case R_RISCV_ALIGN: {
|
||||
// R_RISCV_ALIGN refers NOP instructions. We need to eliminate
|
||||
// some or all of the instructions so that the instruction that
|
||||
// immediately follows the NOPs is aligned to a specified
|
||||
// alignment boundary.
|
||||
u64 loc = isec.get_addr() + r.r_offset + delta;
|
||||
|
||||
// The total bytes of NOPs is stored to r_addend, so the next
|
||||
// instruction is r_addend away.
|
||||
u64 next_loc = loc + r.r_addend;
|
||||
|
||||
u64 alignment = (std::popcount<u64>(r.r_addend) == 1)
|
||||
? r.r_addend : next_power_of_two(r.r_addend);
|
||||
|
||||
if (next_loc % alignment)
|
||||
delta2 = align_to(loc, alignment) - next_loc;
|
||||
break;
|
||||
}
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT:
|
||||
if (ctx.arg.relax) {
|
||||
if (i == rels.size() - 1 || rels[i + 1].r_type != R_RISCV_RELAX)
|
||||
break;
|
||||
|
||||
// If the jump target is within ±1 MiB, we can replace AUIPC+JALR
|
||||
// with JAL, saving 4 bytes.
|
||||
Symbol<E> &sym = *isec.file.symbols[r.r_sym];
|
||||
i64 dist = compute_distance(ctx, sym, isec, r);
|
||||
if (dist % 2 == 0 && -(1 << 20) <= dist && dist < (1 << 20))
|
||||
delta2 = -4;
|
||||
}
|
||||
}
|
||||
|
||||
if (delta2 == 0)
|
||||
continue;
|
||||
|
||||
while (!syms.empty() && syms[0]->value <= r.r_offset) {
|
||||
syms[0]->value += delta;
|
||||
syms = syms.subspan(1);
|
||||
}
|
||||
|
||||
delta += delta2;
|
||||
}
|
||||
|
||||
for (Symbol<E> *sym : syms)
|
||||
sym->value += delta;
|
||||
r_deltas[rels.size()] += delta;
|
||||
|
||||
isec.sh_size += delta;
|
||||
}
|
||||
|
||||
// RISC-V instructions are 16 or 32 bits long, so immediates encoded
|
||||
// in instructions can't be 32 bits long. Therefore, branch and load
|
||||
// instructions can't refer the 4 GiB address space unlike x86-64.
|
||||
// In fact, JAL (jump and link) instruction can jump to only within
|
||||
// ±1 MiB as their immediate is only 21 bits long.
|
||||
//
|
||||
// If you want to jump to somewhere further than that, you need to
|
||||
// construct a full 32-bit offset using multiple instruction and
|
||||
// branch to that place (e.g. AUIPC and JALR instead of JAL).
|
||||
// In this comment, we refer instructions such as JAL as the short
|
||||
// encoding and ones such as AUIPC+JALR as the long encoding.
|
||||
//
|
||||
// By default, compiler always uses the long encoding so that branch
|
||||
// targets are always encodable. This is a safe bet for them but
|
||||
// may result in inefficient code. Therefore, the RISC-V psABI defines
|
||||
// a mechanism for the linker to replace long encoding instructions
|
||||
// with short ones, shrinking the section and increasing the code
|
||||
// density.
|
||||
//
|
||||
// This is contrary to the psABIs for the other RISC processors such as
|
||||
// ARM64. Typically, they use short instructions by default, and a
|
||||
// linker creates so-called "thunks" to extend ranges of short jumps.
|
||||
// On RISC-V, instructions are in the long encoding by default, and
|
||||
// the linker shrinks them if it can.
|
||||
//
|
||||
// When we shrink a section, we need to adjust relocation offsets and
|
||||
// symbol values. For example, if we replace AUIPC+JALR with JAL
|
||||
// (which saves 4 bytes), all relocations pointing to anywhere after
|
||||
// that location need to be shifted by 4. In addition to that, any
|
||||
// symbol that refers anywhere after that locatioin need to be shifted
|
||||
// by 4 bytes as well.
|
||||
//
|
||||
// For relocations, we use `r_deltas` array to memorize how many bytes
|
||||
// have be adjusted. For symbols, we directly mutate their `value`
|
||||
// member.
|
||||
//
|
||||
// This operation seems to be optional, as by default instructions are
|
||||
// using the long encoding, but calling this function is actually
|
||||
// mandatory because of R_RISCV_ALIGN. R_RISCV_ALIGN relocation is a
|
||||
// directive to the linker to align the location referred to by the
|
||||
// relocation to a specified byte boundary. We at least have to
|
||||
// interpret them satisfy the constraints imposed by R_RISCV_ALIGN
|
||||
// relocations.
|
||||
i64 riscv_resize_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "riscv_resize_sections");
|
||||
initialize_storage(ctx);
|
||||
|
||||
// Find R_RISCV_CALL AND R_RISCV_CALL_PLT that can be relaxed.
|
||||
// This step should only shrink sections.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (is_resizable(ctx, isec.get()))
|
||||
relax_section(ctx, *isec);
|
||||
});
|
||||
|
||||
// Re-compute section offset again to finalize them.
|
||||
compute_section_sizes(ctx);
|
||||
return set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -1,830 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = X86_64;
|
||||
|
||||
// The compact PLT format is used when `-z now` is given. If the flag
|
||||
// is given, all PLT symbols are resolved eagerly on startup, so we
|
||||
// can omit code for lazy symbol resolution from PLT in that case.
|
||||
static void write_compact_plt(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + ctx.plt->shdr.sh_offset;
|
||||
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : ctx.plt->symbols) {
|
||||
u8 *ent = buf + sym->get_plt_idx(ctx) * ctx.plt_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
}
|
||||
}
|
||||
|
||||
// The IBTPLT is a security-enhanced version of the regular PLT.
|
||||
// It uses Indirect Branch Tracking (IBT) feature which is part of
|
||||
// Intel Control-Flow Enforcement (CET).
|
||||
//
|
||||
// Note that our IBTPLT instruction sequence is different from the one
|
||||
// used in GNU ld. GNU's IBTPLT implementation uses two separate
|
||||
// sections (.plt and .plt.sec) in which one PLT entry takes 32 bytes
|
||||
// in total. Our PLT consists of just .plt and each entry is 16 bytes
|
||||
// long.
|
||||
//
|
||||
// Our PLT entry clobbers r11, but that's fine because the resolver
|
||||
// function (_dl_runtime_resolve) does not preserve r11 anyway.
|
||||
static void write_ibtplt(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + ctx.plt->shdr.sh_offset;
|
||||
|
||||
// Write PLT header
|
||||
static const u8 plt0[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0x53, // push %r11
|
||||
0xff, 0x35, 0, 0, 0, 0, // push GOTPLT+8(%rip)
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+16(%rip)
|
||||
0x0f, 0x1f, 0x40, 0x00, // nop
|
||||
0x0f, 0x1f, 0x40, 0x00, // nop
|
||||
0x0f, 0x1f, 0x40, 0x00, // nop
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 4;
|
||||
*(u32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 2;
|
||||
|
||||
// Write PLT entries
|
||||
i64 relplt_idx = 0;
|
||||
|
||||
static const u8 data[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : ctx.plt->symbols) {
|
||||
u8 *ent = buf + ctx.plt_hdr_size + sym->get_plt_idx(ctx) * ctx.plt_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 6) = relplt_idx++;
|
||||
*(u32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 16;
|
||||
}
|
||||
}
|
||||
|
||||
// The regular PLT.
|
||||
static void write_plt(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + ctx.plt->shdr.sh_offset;
|
||||
|
||||
// Write PLT header
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0x35, 0, 0, 0, 0, // pushq GOTPLT+8(%rip)
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+16(%rip)
|
||||
0x0f, 0x1f, 0x40, 0x00, // nop
|
||||
};
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 2;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4;
|
||||
|
||||
// Write PLT entries
|
||||
i64 relplt_idx = 0;
|
||||
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
0x68, 0, 0, 0, 0, // push $index_in_relplt
|
||||
0xe9, 0, 0, 0, 0, // jmp PLT[0]
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : ctx.plt->symbols) {
|
||||
u8 *ent = buf + ctx.plt_hdr_size + sym->get_plt_idx(ctx) * ctx.plt_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
*(u32 *)(ent + 7) = relplt_idx++;
|
||||
*(u32 *)(ent + 12) = ctx.plt->shdr.sh_addr - sym->get_plt_addr(ctx) - 16;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
if (ctx.arg.z_now)
|
||||
write_compact_plt(ctx);
|
||||
else if (ctx.arg.z_ibtplt)
|
||||
write_ibtplt(ctx);
|
||||
else
|
||||
write_plt(ctx);
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * X86_64::pltgot_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_NONE:
|
||||
return;
|
||||
case R_X86_64_32:
|
||||
*(u32 *)loc = val;
|
||||
return;
|
||||
case R_X86_64_64:
|
||||
*(u64 *)loc = val;
|
||||
return;
|
||||
case R_X86_64_PC32:
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
case R_X86_64_PC64:
|
||||
*(u64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_gotpc32_tlsdesc(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488d05: return 0x48c7c0; // lea 0(%rip), %rax -> mov $0, %rax
|
||||
case 0x488d0d: return 0x48c7c1; // lea 0(%rip), %rcx -> mov $0, %rcx
|
||||
case 0x488d15: return 0x48c7c2; // lea 0(%rip), %rdx -> mov $0, %rdx
|
||||
case 0x488d1d: return 0x48c7c3; // lea 0(%rip), %rbx -> mov $0, %rbx
|
||||
case 0x488d25: return 0x48c7c4; // lea 0(%rip), %rsp -> mov $0, %rsp
|
||||
case 0x488d2d: return 0x48c7c5; // lea 0(%rip), %rbp -> mov $0, %rbp
|
||||
case 0x488d35: return 0x48c7c6; // lea 0(%rip), %rsi -> mov $0, %rsi
|
||||
case 0x488d3d: return 0x48c7c7; // lea 0(%rip), %rdi -> mov $0, %rdi
|
||||
case 0x4c8d05: return 0x49c7c0; // lea 0(%rip), %r8 -> mov $0, %r8
|
||||
case 0x4c8d0d: return 0x49c7c1; // lea 0(%rip), %r9 -> mov $0, %r9
|
||||
case 0x4c8d15: return 0x49c7c2; // lea 0(%rip), %r10 -> mov $0, %r10
|
||||
case 0x4c8d1d: return 0x49c7c3; // lea 0(%rip), %r11 -> mov $0, %r11
|
||||
case 0x4c8d25: return 0x49c7c4; // lea 0(%rip), %r12 -> mov $0, %r12
|
||||
case 0x4c8d2d: return 0x49c7c5; // lea 0(%rip), %r13 -> mov $0, %r13
|
||||
case 0x4c8d35: return 0x49c7c6; // lea 0(%rip), %r14 -> mov $0, %r14
|
||||
case 0x4c8d3d: return 0x49c7c7; // lea 0(%rip), %r15 -> mov $0, %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Apply relocations to SHF_ALLOC sections (i.e. sections that are
|
||||
// mapped to memory at runtime) based on the result of
|
||||
// scan_relocations().
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
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_X86_64_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
const SectionFragmentRef<E> *frag_ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
frag_ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto write8 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write8s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(u16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write16s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 15), 1 << 15);
|
||||
*(u16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
overflow_check(val, 0, (i64)1 << 32);
|
||||
*(u32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32s = [&](u64 val) {
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
|
||||
*(u32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write64 = [&](u64 val) {
|
||||
*(u64 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr)
|
||||
#define GOT ctx.gotplt->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
write8(S + A);
|
||||
continue;
|
||||
case R_X86_64_16:
|
||||
write16(S + A);
|
||||
continue;
|
||||
case R_X86_64_32:
|
||||
write32(S + A);
|
||||
continue;
|
||||
case R_X86_64_32S:
|
||||
write32s(S + A);
|
||||
continue;
|
||||
case R_X86_64_64:
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
write64(S + A);
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_X86_64_64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
write64(A);
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_X86_64_RELATIVE, 0, (i64)(S + A)};
|
||||
write64(S + A);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_PC8:
|
||||
write8s(S + A - P);
|
||||
continue;
|
||||
case R_X86_64_PC16:
|
||||
write16s(S + A - P);
|
||||
continue;
|
||||
case R_X86_64_PC32:
|
||||
write32s(S + A - P);
|
||||
continue;
|
||||
case R_X86_64_PC64:
|
||||
if (sym.is_absolute() || !sym.is_imported || !ctx.arg.shared) {
|
||||
write64(S + A - P);
|
||||
} else {
|
||||
*dynrel++ = {P, R_X86_64_64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
write64(A);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_PLT32:
|
||||
write32s(S + A - P);
|
||||
continue;
|
||||
case R_X86_64_PLTOFF64:
|
||||
write64(S + A - GOT);
|
||||
break;
|
||||
case R_X86_64_GOT32:
|
||||
write32s(G + A);
|
||||
continue;
|
||||
case R_X86_64_GOT64:
|
||||
write64(G + A);
|
||||
continue;
|
||||
case R_X86_64_GOTOFF64:
|
||||
write64(S + A - GOT);
|
||||
continue;
|
||||
case R_X86_64_GOTPC32:
|
||||
write32s(GOT + A - P);
|
||||
continue;
|
||||
case R_X86_64_GOTPC64:
|
||||
write64(GOT + A - P);
|
||||
continue;
|
||||
case R_X86_64_GOTPCREL:
|
||||
write32s(G + GOT + A - P);
|
||||
continue;
|
||||
case R_X86_64_GOTPCREL64:
|
||||
write64(G + GOT + A - P);
|
||||
continue;
|
||||
case R_X86_64_GOTPCRELX:
|
||||
if (sym.get_got_idx(ctx) == -1) {
|
||||
u32 insn = relax_gotpcrelx(loc - 2);
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S + A - P);
|
||||
} else {
|
||||
write32s(G + GOT + A - P);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
if (sym.get_got_idx(ctx) == -1) {
|
||||
u32 insn = relax_rex_gotpcrelx(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S + A - P);
|
||||
} else {
|
||||
write32s(G + GOT + A - P);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_TLSGD:
|
||||
if (sym.get_tlsgd_idx(ctx) == -1) {
|
||||
// Relax GD to LE
|
||||
i64 val = S - ctx.tls_end + A + 4;
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
|
||||
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 8) = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
|
||||
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 9) = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
} else {
|
||||
write32s(sym.get_tlsgd_addr(ctx) + A - P);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_TLSLD:
|
||||
if (ctx.got->tlsld_idx == -1) {
|
||||
// Relax LD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_X86_64_PLT32: {
|
||||
static const u8 insn[] = {
|
||||
0x66, 0x66, 0x66, // (padding)
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
static const u8 insn[] = {
|
||||
0x66, 0x66, 0x66, // (padding)
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
static const u8 insn[] = {
|
||||
0x66, 0x66, 0x66, // (padding)
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x66, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
} else {
|
||||
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_DTPOFF32:
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
write32s(S + A - ctx.tls_end);
|
||||
else
|
||||
write32s(S + A - ctx.tls_begin);
|
||||
continue;
|
||||
case R_X86_64_DTPOFF64:
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
write64(S + A - ctx.tls_end);
|
||||
else
|
||||
write64(S + A - ctx.tls_begin);
|
||||
continue;
|
||||
case R_X86_64_TPOFF32:
|
||||
write32s(S + A - ctx.tls_end);
|
||||
continue;
|
||||
case R_X86_64_TPOFF64:
|
||||
write64(S + A - ctx.tls_end);
|
||||
continue;
|
||||
case R_X86_64_GOTTPOFF:
|
||||
if (sym.get_gottp_idx(ctx) == -1) {
|
||||
u32 insn = relax_gottpoff(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S + A - ctx.tls_end + 4);
|
||||
} else {
|
||||
write32s(sym.get_gottp_addr(ctx) + A - P);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
u32 insn = relax_gotpc32_tlsdesc(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S + A - ctx.tls_end + 4);
|
||||
} else {
|
||||
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
continue;
|
||||
case R_X86_64_SIZE64:
|
||||
write64(sym.esym().st_size + A);
|
||||
continue;
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
// This function is responsible for applying relocations against
|
||||
// non-SHF_ALLOC sections (i.e. sections that are not mapped to memory
|
||||
// at runtime).
|
||||
//
|
||||
// Relocations against non-SHF_ALLOC sections are much easier to
|
||||
// handle than that against SHF_ALLOC sections. It is because, since
|
||||
// they are not mapped to memory, they don't contain any variable or
|
||||
// function and never need PLT or GOT. Non-SHF_ALLOC sections are
|
||||
// mostly debug info sections.
|
||||
//
|
||||
// Relocations against non-SHF_ALLOC sections are not scanned by
|
||||
// scan_relocations.
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<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_X86_64_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 addend;
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto write8 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(u16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
overflow_check(val, 0, (i64)1 << 32);
|
||||
*(u32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32s = [&](u64 val) {
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
|
||||
*(u32 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
write8(S + A);
|
||||
break;
|
||||
case R_X86_64_16:
|
||||
write16(S + A);
|
||||
break;
|
||||
case R_X86_64_32:
|
||||
write32(S + A);
|
||||
break;
|
||||
case R_X86_64_32S:
|
||||
write32s(S + A);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A;
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
write32s(S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_X86_64_DTPOFF64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
break;
|
||||
case R_X86_64_SIZE64:
|
||||
*(u64 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
}
|
||||
}
|
||||
|
||||
// Linker has to create data structures in an output file to apply
|
||||
// some type of relocations. For example, if a relocation refers a GOT
|
||||
// or a PLT entry of a symbol, linker has to create an entry in .got
|
||||
// or in .plt for that symbol. In order to fix the file layout, we
|
||||
// need to scan relocations.
|
||||
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<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_X86_64_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
case R_X86_64_16:
|
||||
case R_X86_64_32:
|
||||
case R_X86_64_32S: {
|
||||
// Dynamic linker does not support 8, 16 or 32-bit dynamic
|
||||
// relocations for these types of relocations. We report an
|
||||
// error if we cannot relocate them even at load-time.
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // DSO
|
||||
{ NONE, ERROR, ERROR, ERROR }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_64: {
|
||||
// Unlike the above, we can use R_X86_64_RELATIVE and R_86_64_64
|
||||
// relocations.
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC8:
|
||||
case R_X86_64_PC16:
|
||||
case R_X86_64_PC32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC64: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, DYNREL, DYNREL }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOT32:
|
||||
case R_X86_64_GOT64:
|
||||
case R_X86_64_GOTPC32:
|
||||
case R_X86_64_GOTPC64:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCREL64:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPCRELX";
|
||||
|
||||
bool do_relax = ctx.arg.relax && !sym.is_imported &&
|
||||
sym.is_relative() && relax_gotpcrelx(loc - 2);
|
||||
if (!do_relax)
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_REX_GOTPCRELX: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_REX_GOTPCRELX";
|
||||
|
||||
bool do_relax = ctx.arg.relax && !sym.is_imported &&
|
||||
sym.is_relative() && relax_rex_gotpcrelx(loc - 3);
|
||||
if (!do_relax)
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PLTOFF64: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, NONE, PLT, PLT }, // DSO
|
||||
{ NONE, NONE, PLT, PLT }, // PIE
|
||||
{ NONE, NONE, PLT, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_TLSGD: {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PLTOFF64 &&
|
||||
ty != R_X86_64_GOTPCREL && ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported)
|
||||
i++;
|
||||
else
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_TLSLD: {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PLTOFF64 &&
|
||||
ty != R_X86_64_GOTPCREL && ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTTPOFF: {
|
||||
ctx.has_gottp_rel = true;
|
||||
|
||||
bool do_relax = ctx.arg.relax && !ctx.arg.shared &&
|
||||
!sym.is_imported && relax_gottpoff(loc - 3);
|
||||
if (!do_relax)
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPC32_TLSDESC: {
|
||||
if (relax_gotpc32_tlsdesc(loc - 3) == 0)
|
||||
Fatal(ctx) << *this << ": GOTPC32_TLSDESC relocation is used"
|
||||
<< " against an invalid code sequence";
|
||||
|
||||
bool do_relax = ctx.relax_tlsdesc && !sym.is_imported;
|
||||
if (!do_relax)
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTOFF64:
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_DTPOFF64:
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_TPOFF64:
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_SIZE64:
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
-1114
File diff suppressed because it is too large.
Load diff
-1469
File diff suppressed because it is too large.
Load diff
@@ -1,258 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <limits>
|
||||
#include <zlib.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
bool CieRecord<E>::equals(const CieRecord<E> &other) const {
|
||||
if (get_contents() != other.get_contents())
|
||||
return false;
|
||||
|
||||
std::span<ElfRel<E>> x = get_rels();
|
||||
std::span<ElfRel<E>> y = other.get_rels();
|
||||
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] ||
|
||||
input_section.get_addend(x[i]) != other.input_section.get_addend(y[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline 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,
|
||||
std::string_view name, i64 shndx)
|
||||
: file(file), shndx(shndx) {
|
||||
if (shndx < file.elf_sections.size())
|
||||
contents = {(char *)file.mf->data + shdr().sh_offset, shdr().sh_size};
|
||||
|
||||
bool compressed;
|
||||
|
||||
if (name.starts_with(".zdebug")) {
|
||||
sh_size = *(ubig64 *)&contents[4];
|
||||
p2align = to_p2align(shdr().sh_addralign);
|
||||
compressed = true;
|
||||
} else if (shdr().sh_flags & SHF_COMPRESSED) {
|
||||
ElfChdr<E> &chdr = *(ElfChdr<E> *)&contents[0];
|
||||
sh_size = chdr.ch_size;
|
||||
p2align = to_p2align(chdr.ch_addralign);
|
||||
compressed = true;
|
||||
} else {
|
||||
sh_size = shdr().sh_size;
|
||||
p2align = to_p2align(shdr().sh_addralign);
|
||||
compressed = false;
|
||||
}
|
||||
|
||||
// Uncompress early if the relocation is REL-type so that we can read
|
||||
// addends from section contents. If RELA-type, we don't need to do this
|
||||
// because addends are in relocations.
|
||||
if (compressed && E::is_rel) {
|
||||
u8 *buf = new u8[sh_size];
|
||||
uncompress(ctx, buf);
|
||||
contents = {(char *)buf, sh_size};
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
}
|
||||
|
||||
output_section =
|
||||
OutputSection<E>::get_instance(ctx, name, shdr().sh_type, shdr().sh_flags);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool InputSection<E>::is_compressed() {
|
||||
return !E::is_rel &&
|
||||
(name().starts_with(".zdebug") || (shdr().sh_flags & SHF_COMPRESSED));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::uncompress(Context<E> &ctx, u8 *buf) {
|
||||
auto do_uncompress = [&](std::string_view data) {
|
||||
unsigned long size = sh_size;
|
||||
if (::uncompress(buf, &size, (u8 *)data.data(), data.size()) != Z_OK)
|
||||
Fatal(ctx) << *this << ": uncompress failed";
|
||||
assert(size == sh_size);
|
||||
};
|
||||
|
||||
if (name().starts_with(".zdebug")) {
|
||||
// Old-style compressed section
|
||||
if (!contents.starts_with("ZLIB") || contents.size() <= 12)
|
||||
Fatal(ctx) << *this << ": corrupted compressed section";
|
||||
do_uncompress(contents.substr(12));
|
||||
return;
|
||||
}
|
||||
|
||||
assert(shdr().sh_flags & SHF_COMPRESSED);
|
||||
|
||||
// New-style compressed section
|
||||
if (contents.size() < sizeof(ElfChdr<E>))
|
||||
Fatal(ctx) << *this << ": corrupted compressed section";
|
||||
|
||||
ElfChdr<E> &hdr = *(ElfChdr<E> *)&contents[0];
|
||||
if (hdr.ch_type != ELFCOMPRESS_ZLIB)
|
||||
Fatal(ctx) << *this << ": unsupported compression type";
|
||||
do_uncompress(contents.substr(sizeof(ElfChdr<E>)));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static 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 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>::dispatch(Context<E> &ctx, Action table[3][4], i64 i,
|
||||
const ElfRel<E> &rel, Symbol<E> &sym) {
|
||||
Action action = table[get_output_type(ctx)][get_sym_type(sym)];
|
||||
bool is_writable = (shdr().sh_flags & SHF_WRITE);
|
||||
|
||||
auto error = [&] {
|
||||
std::string msg = sym.is_absolute() ? "-fno-PIC" : "-fPIC";
|
||||
Error(ctx) << *this << ": " << rel << " relocation at offset 0x"
|
||||
<< std::hex << rel.r_offset << " against symbol `"
|
||||
<< sym << "' can not be used; recompile with " << msg;
|
||||
};
|
||||
|
||||
auto warn_textrel = [&] {
|
||||
if (ctx.arg.warn_textrel)
|
||||
Warn(ctx) << *this << ": relocation against symbol `" << sym
|
||||
<< "' in read-only section";
|
||||
};
|
||||
|
||||
switch (action) {
|
||||
case NONE:
|
||||
return;
|
||||
case ERROR:
|
||||
error();
|
||||
return;
|
||||
case COPYREL:
|
||||
if (!ctx.arg.z_copyreloc) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
|
||||
if (sym.esym().st_visibility == STV_PROTECTED) {
|
||||
Error(ctx) << *this
|
||||
<< ": cannot make copy relocation for protected symbol '"
|
||||
<< sym << "', defined in " << *sym.file
|
||||
<< "; recompile with -fPIC";
|
||||
return;
|
||||
}
|
||||
|
||||
sym.flags |= NEEDS_COPYREL;
|
||||
return;
|
||||
case PLT:
|
||||
sym.flags |= NEEDS_PLT;
|
||||
return;
|
||||
case CPLT: {
|
||||
std::scoped_lock lock(sym.mu);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
sym.is_canonical = true;
|
||||
return;
|
||||
}
|
||||
case DYNREL:
|
||||
if (!is_writable) {
|
||||
if (ctx.arg.z_text) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
warn_textrel();
|
||||
ctx.has_textrel = true;
|
||||
}
|
||||
|
||||
assert(sym.is_imported);
|
||||
file.num_dynrel++;
|
||||
return;
|
||||
case BASEREL:
|
||||
if (!is_writable) {
|
||||
if (ctx.arg.z_text) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
warn_textrel();
|
||||
ctx.has_textrel = true;
|
||||
}
|
||||
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
file.num_dynrel++;
|
||||
return;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
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 (std::is_same_v<E, RISCV64>) {
|
||||
copy_contents_riscv(ctx, buf);
|
||||
} else if (is_compressed()) {
|
||||
uncompress(ctx, buf);
|
||||
} else {
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
}
|
||||
|
||||
// Apply relocations
|
||||
if (shdr().sh_flags & SHF_ALLOC)
|
||||
apply_reloc_alloc(ctx, buf);
|
||||
else
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void report_undef(Context<E> &ctx, InputFile<E> &file, Symbol<E> &sym) {
|
||||
if (ctx.arg.warn_once && !ctx.warned.insert({(void *)&sym, 1}))
|
||||
return;
|
||||
|
||||
switch (ctx.arg.unresolved_symbols) {
|
||||
case UNRESOLVED_ERROR:
|
||||
Error(ctx) << "undefined symbol: " << file << ": " << sym;
|
||||
break;
|
||||
case UNRESOLVED_WARN:
|
||||
Warn(ctx) << "undefined symbol: " << file << ": " << sym;
|
||||
break;
|
||||
case UNRESOLVED_IGNORE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template struct CieRecord<E>; \
|
||||
template class InputSection<E>; \
|
||||
template void report_undef(Context<E> &, InputFile<E> &, Symbol<E> &)
|
||||
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -1,414 +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));
|
||||
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[0] == ";") {
|
||||
tok = tok.subspan(1);
|
||||
} else {
|
||||
SyntaxError(ctx, tok[0]) << "unknown linker script token";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 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 EM_X86_64;
|
||||
if (tok[2] == "elf32-i386")
|
||||
return EM_386;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
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,
|
||||
u16 ver_idx, bool is_cpp) {
|
||||
bool is_global = true;
|
||||
|
||||
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_idx, false);
|
||||
} else {
|
||||
tok = skip(ctx, tok, "\"C++\"");
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands(ctx, tok, ver_idx, true);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "}");
|
||||
tok = skip(ctx, tok, ";");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tok[0] == "*") {
|
||||
ctx.default_version = (is_global ? ver_idx : VER_NDX_LOCAL);
|
||||
} else if (is_global) {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), ver_idx, is_cpp});
|
||||
} else {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), 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] != "}") {
|
||||
u16 ver_idx;
|
||||
if (tok[0] == "{") {
|
||||
ver_idx = VER_NDX_GLOBAL;
|
||||
} else {
|
||||
ver_idx = next_ver++;
|
||||
ctx.arg.version_definitions.push_back(std::string(tok[0]));
|
||||
tok = tok.subspan(1);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands(ctx, tok, ver_idx, false);
|
||||
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, std::string path) {
|
||||
current_file<E> = MappedFile<Context<E>>::must_open(ctx, path);
|
||||
std::vector<std::string_view> vec =
|
||||
tokenize(ctx, current_file<E>->get_contents());
|
||||
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]), VER_NDX_GLOBAL, is_cpp});
|
||||
|
||||
tok = skip(ctx, tok.subspan(1), ";");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_dynamic_list(Context<E> &ctx, std::string path) {
|
||||
current_file<E> = MappedFile<Context<E>>::must_open(ctx, path);
|
||||
std::vector<std::string_view> vec =
|
||||
tokenize(ctx, current_file<E>->get_contents());
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template \
|
||||
void parse_linker_script(Context<E> &ctx, MappedFile<Context<E>> *mf); \
|
||||
template \
|
||||
i64 get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf); \
|
||||
template \
|
||||
void parse_version_script(Context<E> &ctx, std::string path); \
|
||||
template \
|
||||
void parse_dynamic_list(Context<E> &ctx, std::string path)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
-772
@@ -1,772 +0,0 @@
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
#include "../cmdline.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <tbb/global_control.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <unistd.h>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
static ObjectFile<E> *new_object_file(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
if (i64 type = ((ElfEhdr<E> *)mf->data)->e_machine; type != E::e_machine)
|
||||
Fatal(ctx) << mf->name << ": incompatible file type: " << type;
|
||||
|
||||
static Counter count("parsed_objs");
|
||||
count++;
|
||||
|
||||
bool in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
|
||||
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mf, archive_name, in_lib);
|
||||
file->priority = ctx.file_priority++;
|
||||
ctx.tg.run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(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 Counter count("parsed_lto_objs");
|
||||
count++;
|
||||
|
||||
if (ctx.arg.ignore_ir_file.count(mf->get_identifier()))
|
||||
return new ObjectFile<E>;
|
||||
|
||||
ObjectFile<E> *file = read_lto_object(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
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;
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static SharedFile<E> *
|
||||
new_shared_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
if (i64 type = ((ElfEhdr<E> *)mf->data)->e_machine; type != E::e_machine)
|
||||
Fatal(ctx) << mf->name << ": incompatible file type: " << type;
|
||||
|
||||
SharedFile<E> *file = SharedFile<E>::create(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
ctx.tg.run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(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;
|
||||
|
||||
FileType type = get_file_type(mf);
|
||||
switch (type) {
|
||||
case FileType::ELF_OBJ:
|
||||
ctx.objs.push_back(new_object_file(ctx, mf, ""));
|
||||
return;
|
||||
case FileType::ELF_DSO:
|
||||
ctx.dsos.push_back(new_shared_file(ctx, mf));
|
||||
ctx.visited.insert(mf->name);
|
||||
return;
|
||||
case FileType::AR:
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf)) {
|
||||
switch (get_file_type(child)) {
|
||||
case FileType::ELF_OBJ:
|
||||
ctx.objs.push_back(new_object_file(ctx, child, mf->name));
|
||||
break;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
ctx.objs.push_back(new_lto_obj(ctx, child, mf->name));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
ctx.visited.insert(mf->name);
|
||||
return;
|
||||
case FileType::TEXT:
|
||||
parse_linker_script(ctx, mf);
|
||||
return;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
ctx.objs.push_back(new_lto_obj(ctx, mf, ""));
|
||||
return;
|
||||
default:
|
||||
Fatal(ctx) << mf->name << ": unknown file type: " << type;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the beginning of a given file and returns its machine type
|
||||
// (e.g. EM_X86_64 or EM_386). Return -1 if unknown.
|
||||
template <typename E>
|
||||
static i64 get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
switch (get_file_type(mf)) {
|
||||
case FileType::ELF_OBJ:
|
||||
case FileType::ELF_DSO:
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
return ((ElfEhdr<E> *)mf->data)->e_machine;
|
||||
case FileType::AR:
|
||||
for (MappedFile<Context<E>> *child : read_fat_archive_members(ctx, mf))
|
||||
if (get_file_type(child) == FileType::ELF_OBJ)
|
||||
return ((ElfEhdr<E> *)child->data)->e_machine;
|
||||
return -1;
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_thin_archive_members(ctx, mf))
|
||||
if (get_file_type(child) == FileType::ELF_OBJ)
|
||||
return ((ElfEhdr<E> *)child->data)->e_machine;
|
||||
return -1;
|
||||
case FileType::TEXT:
|
||||
return get_script_output_type(ctx, mf);
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64
|
||||
deduce_machine_type(Context<E> &ctx, std::span<std::string_view> args) {
|
||||
for (std::string_view arg : args)
|
||||
if (!arg.starts_with('-'))
|
||||
if (auto *mf = MappedFile<Context<E>>::open(ctx, std::string(arg)))
|
||||
if (i64 type = get_machine_type(ctx, mf); type != -1)
|
||||
return type;
|
||||
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);
|
||||
if (!mf)
|
||||
return nullptr;
|
||||
|
||||
i64 type = get_machine_type(ctx, mf);
|
||||
if (type == -1 || type == E::e_machine)
|
||||
return mf;
|
||||
Warn(ctx) << path << ": skipping incompatible file " << (int)type
|
||||
<< " " << (int)E::e_machine;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *find_library(Context<E> &ctx, 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))
|
||||
return mf;
|
||||
}
|
||||
Fatal(ctx) << "library not found: " << 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"))
|
||||
return mf;
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, stem + ".a"))
|
||||
return mf;
|
||||
}
|
||||
Fatal(ctx) << "library not found: " << name;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_input_files(Context<E> &ctx, std::span<std::string_view> args) {
|
||||
Timer t(ctx, "read_input_files");
|
||||
|
||||
std::vector<std::tuple<bool, bool, bool, bool>> state;
|
||||
ctx.is_static = ctx.arg.is_static;
|
||||
|
||||
while (!args.empty()) {
|
||||
std::string_view arg;
|
||||
|
||||
if (read_flag(args, "as-needed")) {
|
||||
ctx.as_needed = true;
|
||||
} else if (read_flag(args, "no-as-needed")) {
|
||||
ctx.as_needed = false;
|
||||
} else if (read_flag(args, "whole-archive")) {
|
||||
ctx.whole_archive = true;
|
||||
} else if (read_flag(args, "no-whole-archive")) {
|
||||
ctx.whole_archive = false;
|
||||
} else if (read_flag(args, "Bstatic")) {
|
||||
ctx.is_static = true;
|
||||
} else if (read_flag(args, "Bdynamic")) {
|
||||
ctx.is_static = false;
|
||||
} else if (read_flag(args, "start-lib")) {
|
||||
ctx.in_lib = true;
|
||||
} else if (read_flag(args, "end-lib")) {
|
||||
ctx.in_lib = false;
|
||||
} else if (read_arg(ctx, args, arg, "version-script")) {
|
||||
parse_version_script(ctx, std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "dynamic-list")) {
|
||||
parse_dynamic_list(ctx, std::string(arg));
|
||||
} else if (read_flag(args, "push-state")) {
|
||||
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static,
|
||||
ctx.in_lib});
|
||||
} else if (read_flag(args, "pop-state")) {
|
||||
if (state.empty())
|
||||
Fatal(ctx) << "no state pushed before popping";
|
||||
std::tie(ctx.as_needed, ctx.whole_archive, ctx.is_static, ctx.in_lib) =
|
||||
state.back();
|
||||
state.pop_back();
|
||||
} else if (read_arg(ctx, args, arg, "l")) {
|
||||
MappedFile<Context<E>> *mf = find_library(ctx, std::string(arg));
|
||||
mf->given_fullpath = false;
|
||||
read_file(ctx, mf);
|
||||
} else {
|
||||
read_file(ctx, MappedFile<Context<E>>::must_open(ctx, std::string(args[0])));
|
||||
args = args.subspan(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.objs.empty())
|
||||
Fatal(ctx) << "no input files";
|
||||
|
||||
ctx.tg.wait();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 get_mtime(Context<E> &ctx, std::string path) {
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) < 0)
|
||||
Fatal(ctx) << path << ": stat failed: " << errno_string();
|
||||
return st.st_mtime;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool reload_input_files(Context<E> &ctx) {
|
||||
Timer t(ctx, "reload_input_files");
|
||||
|
||||
std::vector<ObjectFile<E> *> objs;
|
||||
std::vector<SharedFile<E> *> dsos;
|
||||
|
||||
// Reload updated .o files
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file->mf->parent) {
|
||||
if (get_mtime(ctx, file->mf->parent->name) != file->mf->parent->mtime)
|
||||
return false;
|
||||
objs.push_back(file);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (get_mtime(ctx, file->mf->name) == file->mf->mtime) {
|
||||
objs.push_back(file);
|
||||
continue;
|
||||
}
|
||||
|
||||
MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::must_open(ctx, file->mf->name);
|
||||
objs.push_back(new_object_file(ctx, mf, file->mf->name));
|
||||
}
|
||||
|
||||
// Reload updated .so files
|
||||
for (SharedFile<E> *file : ctx.dsos) {
|
||||
if (get_mtime(ctx, file->mf->name) == file->mf->mtime) {
|
||||
dsos.push_back(file);
|
||||
} else {
|
||||
MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::must_open(ctx, file->mf->name);
|
||||
dsos.push_back(new_shared_file(ctx, mf));
|
||||
}
|
||||
}
|
||||
|
||||
ctx.objs = objs;
|
||||
ctx.dsos = dsos;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void show_stats(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *obj : ctx.objs) {
|
||||
static Counter defined("defined_syms");
|
||||
defined += obj->first_global - 1;
|
||||
|
||||
static Counter undefined("undefined_syms");
|
||||
undefined += obj->symbols.size() - obj->first_global;
|
||||
|
||||
for (std::unique_ptr<InputSection<E>> &sec : obj->sections) {
|
||||
if (!sec || !sec->is_alive)
|
||||
continue;
|
||||
|
||||
static Counter alloc("reloc_alloc");
|
||||
static Counter nonalloc("reloc_nonalloc");
|
||||
|
||||
if (sec->shdr().sh_flags & SHF_ALLOC)
|
||||
alloc += sec->get_rels(ctx).size();
|
||||
else
|
||||
nonalloc += sec->get_rels(ctx).size();
|
||||
}
|
||||
|
||||
static Counter comdats("comdats");
|
||||
comdats += obj->comdat_groups.size();
|
||||
|
||||
static Counter removed_comdats("removed_comdat_mem");
|
||||
for (auto &pair : obj->comdat_groups)
|
||||
if (ComdatGroup *group = pair.first; group->owner != obj->priority)
|
||||
removed_comdats += pair.second.size();
|
||||
|
||||
static Counter num_cies("num_cies");
|
||||
num_cies += obj->cies.size();
|
||||
|
||||
static Counter num_unique_cies("num_unique_cies");
|
||||
for (CieRecord<E> &cie : obj->cies)
|
||||
if (cie.is_leader)
|
||||
num_unique_cies++;
|
||||
|
||||
static Counter num_fdes("num_fdes");
|
||||
num_fdes += obj->fdes.size();
|
||||
}
|
||||
|
||||
static Counter num_bytes("total_input_bytes");
|
||||
for (std::unique_ptr<MappedFile<Context<E>>> &mf : ctx.mf_pool)
|
||||
num_bytes += mf->size;
|
||||
|
||||
static Counter num_input_sections("input_sections");
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
num_input_sections += file->sections.size();
|
||||
|
||||
static Counter num_output_chunks("output_chunks", ctx.chunks.size());
|
||||
static Counter num_objs("num_objs", ctx.objs.size());
|
||||
static Counter num_dsos("num_dsos", ctx.dsos.size());
|
||||
|
||||
if constexpr (std::is_same_v<E, ARM64>) {
|
||||
static Counter num_thunks("num_thunks");
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
for (std::unique_ptr<RangeExtensionThunk<E>> &thunk : osec->thunks)
|
||||
num_thunks += thunk->symbols.size();
|
||||
}
|
||||
|
||||
Counter::print();
|
||||
|
||||
for (std::unique_ptr<MergedSection<E>> &sec : ctx.merged_sections)
|
||||
sec->print_stats(ctx);
|
||||
}
|
||||
|
||||
static i64 get_default_thread_count() {
|
||||
// mold doesn't scale well above 32 threads.
|
||||
int n = tbb::global_control::active_value(
|
||||
tbb::global_control::max_allowed_parallelism);
|
||||
return std::min(n, 32);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static int elf_main(int argc, char **argv) {
|
||||
Context<E> ctx;
|
||||
|
||||
// Process -run option first. process_run_subcommand() does not return.
|
||||
if (argc >= 2)
|
||||
if (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);
|
||||
std::vector<std::string_view> file_args;
|
||||
parse_nonpositional_args(ctx, file_args);
|
||||
|
||||
// If no -m option is given, deduce it from input files.
|
||||
if (ctx.arg.emulation == -1)
|
||||
ctx.arg.emulation = deduce_machine_type(ctx, file_args);
|
||||
|
||||
// Redo if -m is not x86-64.
|
||||
if (ctx.arg.emulation != E::e_machine) {
|
||||
switch (ctx.arg.emulation) {
|
||||
case EM_386:
|
||||
return elf_main<I386>(argc, argv);
|
||||
case EM_AARCH64:
|
||||
return elf_main<ARM64>(argc, argv);
|
||||
case EM_ARM:
|
||||
return elf_main<ARM32>(argc, argv);
|
||||
case EM_RISCV:
|
||||
return elf_main<RISCV64>(argc, argv);
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
Timer t_all(ctx, "all");
|
||||
|
||||
if (ctx.arg.relocatable) {
|
||||
combine_objects(ctx, file_args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ctx.arg.preload)
|
||||
try_resume_daemon(ctx);
|
||||
|
||||
i64 thread_count = ctx.arg.thread_count;
|
||||
if (thread_count == 0)
|
||||
thread_count = get_default_thread_count();
|
||||
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
|
||||
thread_count);
|
||||
|
||||
install_signal_handler();
|
||||
|
||||
if (!ctx.arg.directory.empty())
|
||||
if (chdir(ctx.arg.directory.c_str()) == -1)
|
||||
Fatal(ctx) << "chdir failed: " << ctx.arg.directory
|
||||
<< ": " << errno_string();
|
||||
|
||||
// Handle --wrap options if any.
|
||||
for (std::string_view name : ctx.arg.wrap)
|
||||
get_symbol(ctx, name)->wrap = true;
|
||||
|
||||
// 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;
|
||||
|
||||
// Preload input files
|
||||
std::function<void()> on_complete;
|
||||
std::function<void()> wait_for_client;
|
||||
|
||||
if (ctx.arg.preload)
|
||||
daemonize(ctx, &wait_for_client, &on_complete);
|
||||
else if (ctx.arg.fork)
|
||||
on_complete = fork_child();
|
||||
|
||||
for (std::string_view arg : ctx.arg.trace_symbol)
|
||||
get_symbol(ctx, arg)->traced = true;
|
||||
|
||||
// Parse input files
|
||||
read_input_files(ctx, file_args);
|
||||
|
||||
if (ctx.arg.preload) {
|
||||
wait_for_client();
|
||||
if (!reload_input_files(ctx)) {
|
||||
std::vector<char *> args(argv, argv + argc);
|
||||
args.push_back((char *)"--no-preload");
|
||||
args.push_back(nullptr);
|
||||
return elf_main<E>(argc + 1, args.data());
|
||||
}
|
||||
}
|
||||
|
||||
// Uniquify shared object files by soname
|
||||
{
|
||||
std::unordered_set<std::string_view> seen;
|
||||
std::erase_if(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
return !seen.insert(file->soname).second;
|
||||
});
|
||||
}
|
||||
|
||||
Timer t_total(ctx, "total");
|
||||
Timer t_before_copy(ctx, "before_copy");
|
||||
|
||||
// Apply -exclude-libs
|
||||
apply_exclude_libs(ctx);
|
||||
|
||||
// Resolve symbols and fix the set of object files that are
|
||||
// included to the final output.
|
||||
resolve_symbols(ctx);
|
||||
|
||||
// Resolve mergeable section pieces to merge them.
|
||||
register_section_pieces(ctx);
|
||||
|
||||
// Remove redundant comdat sections (e.g. duplicate inline functions).
|
||||
eliminate_comdats(ctx);
|
||||
|
||||
// Create .bss sections for common symbols.
|
||||
convert_common_symbols(ctx);
|
||||
|
||||
// Apply version scripts.
|
||||
apply_version_script(ctx);
|
||||
|
||||
// Parse symbol version suffixes (e.g. "foo@ver1").
|
||||
parse_symbol_version(ctx);
|
||||
|
||||
// Set is_import and is_export bits for each symbol.
|
||||
compute_import_export(ctx);
|
||||
|
||||
// Garbage-collect unreachable sections.
|
||||
if (ctx.arg.gc_sections)
|
||||
gc_sections(ctx);
|
||||
|
||||
// Merge identical read-only sections.
|
||||
if (ctx.arg.icf)
|
||||
icf_sections(ctx);
|
||||
|
||||
// Compute sizes of sections containing mergeable strings.
|
||||
compute_merged_section_sizes(ctx);
|
||||
|
||||
// Create instances of linker-synthesized sections such as
|
||||
// .got or .plt.
|
||||
create_synthetic_sections(ctx);
|
||||
|
||||
// Bin input sections into output sections.
|
||||
bin_sections(ctx);
|
||||
|
||||
// Get a list of output sections.
|
||||
append(ctx.chunks, collect_output_sections(ctx));
|
||||
|
||||
// Create a dummy file containing linker-synthesized symbols
|
||||
// (e.g. `__bss_start`).
|
||||
ctx.internal_obj = create_internal_file(ctx);
|
||||
ctx.objs.push_back(ctx.internal_obj);
|
||||
|
||||
// Beyond this point, no new files will be added to ctx.objs
|
||||
// or ctx.dsos.
|
||||
|
||||
// Handle `-z cet-report`.
|
||||
if (ctx.arg.z_cet_report != CET_REPORT_NONE)
|
||||
check_cet_errors(ctx);
|
||||
|
||||
// If we are linking a .so file, remaining undefined symbols does
|
||||
// not cause a linker error. Instead, they are treated as if they
|
||||
// were imported symbols.
|
||||
//
|
||||
// If we are linking an executable, weak undefs are converted to
|
||||
// weakly imported symbol so that they'll have another chance to be
|
||||
// resolved.
|
||||
claim_unresolved_symbols(ctx);
|
||||
|
||||
// Beyond this point, no new symbols will be added to the result.
|
||||
|
||||
// Handle -repro
|
||||
if (ctx.arg.repro)
|
||||
write_repro_file(ctx);
|
||||
|
||||
// Make sure that all symbols have been resolved.
|
||||
if (!ctx.arg.allow_multiple_definition)
|
||||
check_duplicate_symbols(ctx);
|
||||
|
||||
// Handle --require-defined
|
||||
for (std::string_view name : ctx.arg.require_defined)
|
||||
if (!get_symbol(ctx, name)->file)
|
||||
Error(ctx) << "--require-defined: undefined symbol: " << name;
|
||||
|
||||
// .init_array and .fini_array contents have to be sorted by
|
||||
// a special rule. Sort them.
|
||||
sort_init_fini(ctx);
|
||||
|
||||
// Likewise, .ctors and .dtors have to be sorted. They are rare
|
||||
// because they are superceded by .init_array/.fini_array, though.
|
||||
sort_ctor_dtor(ctx);
|
||||
|
||||
// Handle --shuffle-sections
|
||||
if (ctx.arg.shuffle_sections != SHUFFLE_SECTIONS_NONE)
|
||||
shuffle_sections(ctx);
|
||||
|
||||
// Compute sizes of output sections while assigning offsets
|
||||
// within an output section to input sections.
|
||||
compute_section_sizes(ctx);
|
||||
|
||||
// Sort sections by section attributes so that we'll have to
|
||||
// create as few segments as possible.
|
||||
sort(ctx.chunks, [&](Chunk<E> *a, Chunk<E> *b) {
|
||||
return get_section_rank(ctx, a) < get_section_rank(ctx, b);
|
||||
});
|
||||
|
||||
// Copy string referred by .dynamic to .dynstr.
|
||||
for (SharedFile<E> *file : ctx.dsos)
|
||||
ctx.dynstr->add_string(file->soname);
|
||||
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);
|
||||
|
||||
// Scan relocations to find symbols that need entries in .got, .plt,
|
||||
// .got.plt, .dynsym, .dynstr, etc.
|
||||
scan_rels(ctx);
|
||||
|
||||
// If --packed_dyn_relocs=relr was given, base relocations are stored
|
||||
// to a .relr.dyn section in a compressed form. Construct a compressed
|
||||
// relocations now so that we can fix section sizes and file layout.
|
||||
if (ctx.arg.pack_dyn_relocs_relr)
|
||||
construct_relr(ctx);
|
||||
|
||||
// Reserve a space for dynamic symbol strings in .dynstr and sort
|
||||
// .dynsym contents if necessary. Beyond this point, no symbol will
|
||||
// be added to .dynsym.
|
||||
ctx.dynsym->finalize(ctx);
|
||||
|
||||
// Fill .gnu.version_d section contents.
|
||||
if (ctx.verdef)
|
||||
ctx.verdef->construct(ctx);
|
||||
|
||||
// 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.
|
||||
// Here, we construct output .eh_frame contents.
|
||||
ctx.eh_frame->construct(ctx);
|
||||
|
||||
// Handle --gdb-index.
|
||||
if (ctx.arg.gdb_index)
|
||||
ctx.gdb_index->construct(ctx);
|
||||
|
||||
// If --emit-relocs is given, we'll copy relocation sections from input
|
||||
// files to an output file.
|
||||
if (ctx.arg.emit_relocs)
|
||||
create_reloc_sections(ctx);
|
||||
|
||||
// Update sh_size for each chunk and remove empty ones.
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
std::erase_if(ctx.chunks, [](Chunk<E> *chunk) {
|
||||
return !chunk->is_output_section() && chunk->shdr.sh_size == 0;
|
||||
});
|
||||
|
||||
// Set section indices.
|
||||
for (i64 i = 0, shndx = 1; i < ctx.chunks.size(); i++)
|
||||
if (!ctx.chunks[i]->is_header())
|
||||
ctx.chunks[i]->shndx = shndx++;
|
||||
|
||||
// Some types of section header refer other section by index.
|
||||
// Recompute the section header to fill such fields with correct values.
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
// Assign offsets to output sections
|
||||
i64 filesize = set_osec_offsets(ctx);
|
||||
|
||||
// On ARM64, we may need to create so-called "range extension thunks"
|
||||
// to extend branch instructions reach, as they can jump only to
|
||||
// ±128 MiB.
|
||||
if constexpr (std::is_same_v<E, ARM64>)
|
||||
filesize = create_range_extension_thunks(ctx);
|
||||
|
||||
// On RISC-V, branches are encode using multiple instructions so
|
||||
// that they can jump to anywhere in ±2 GiB by default. They may
|
||||
// be replaced with shorter instruction sequences if destinations
|
||||
// are close enough. Do this optimization.
|
||||
if constexpr (std::is_same_v<E, RISCV64>)
|
||||
filesize = riscv_resize_sections(ctx);
|
||||
|
||||
// Fix linker-synthesized symbol addresses.
|
||||
fix_synthetic_symbols(ctx);
|
||||
|
||||
// Beyond this, you can assume that symbol addresses including their
|
||||
// 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);
|
||||
|
||||
// At this point, file layout is fixed.
|
||||
|
||||
t_before_copy.stop();
|
||||
|
||||
// Create an output file
|
||||
ctx.output_file = OutputFile<E>::open(ctx, ctx.arg.output, filesize, 0777);
|
||||
ctx.buf = ctx.output_file->buf;
|
||||
|
||||
Timer t_copy(ctx, "copy");
|
||||
|
||||
// Copy input sections to the output file
|
||||
{
|
||||
Timer t(ctx, "copy_buf");
|
||||
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
std::string name =
|
||||
chunk->name.empty() ? "(header)" : std::string(chunk->name);
|
||||
Timer t2(ctx, name, &t);
|
||||
chunk->copy_buf(ctx);
|
||||
});
|
||||
|
||||
ctx.checkpoint();
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<E, ARM64>)
|
||||
write_thunks(ctx);
|
||||
|
||||
if constexpr (std::is_same_v<E, ARM32>)
|
||||
sort_arm_exidx(ctx);
|
||||
|
||||
// Some part of .gdb_index couldn't be computed until other debug
|
||||
// sections are complete. We have complete debug sections now, so
|
||||
// write the rest of .gdb_index.
|
||||
if (ctx.gdb_index)
|
||||
ctx.gdb_index->write_address_areas(ctx);
|
||||
|
||||
// Dynamic linker works better with sorted .rela.dyn section,
|
||||
// so we sort them.
|
||||
ctx.reldyn->sort(ctx);
|
||||
|
||||
// Zero-clear paddings between sections
|
||||
clear_padding(ctx);
|
||||
|
||||
if (ctx.buildid) {
|
||||
Timer t(ctx, "build_id");
|
||||
ctx.buildid->write_buildid(ctx);
|
||||
}
|
||||
|
||||
t_copy.stop();
|
||||
|
||||
// Close the output file. This is the end of the linker's main job.
|
||||
ctx.output_file->close(ctx);
|
||||
|
||||
// Handle --dependency-file
|
||||
if (!ctx.arg.dependency_file.empty())
|
||||
write_dependency_file(ctx);
|
||||
|
||||
// Handle --print-dependencies
|
||||
if (ctx.arg.print_dependencies == 1)
|
||||
print_dependencies(ctx);
|
||||
else if (ctx.arg.print_dependencies == 2)
|
||||
print_dependencies_full(ctx);
|
||||
|
||||
if (ctx.has_lto_object)
|
||||
lto_cleanup(ctx);
|
||||
|
||||
t_total.stop();
|
||||
t_all.stop();
|
||||
|
||||
if (ctx.arg.print_map)
|
||||
print_map(ctx);
|
||||
|
||||
// Show stats numbers
|
||||
if (ctx.arg.stats)
|
||||
show_stats(ctx);
|
||||
|
||||
if (ctx.arg.perf)
|
||||
print_timer_records(ctx.timer_records);
|
||||
|
||||
std::cout << std::flush;
|
||||
std::cerr << std::flush;
|
||||
if (on_complete)
|
||||
on_complete();
|
||||
|
||||
if (ctx.arg.quick_exit)
|
||||
_exit(0);
|
||||
|
||||
for (std::function<void()> &fn : ctx.on_exit)
|
||||
fn();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
return elf_main<X86_64>(argc, argv);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void read_file(Context<E> &, MappedFile<Context<E>> *);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <ios>
|
||||
#include <sstream>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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; });
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void print_map(Context<E> &ctx) {
|
||||
std::ostream *out = &std::cout;
|
||||
std::unique_ptr<std::ofstream> file;
|
||||
|
||||
if (!ctx.arg.Map.empty()) {
|
||||
file = open_output_file(ctx);
|
||||
out = file.get();
|
||||
}
|
||||
|
||||
// Construct a section-to-symbol map.
|
||||
Map<E> map = get_map(ctx);
|
||||
|
||||
// Print a mapfile.
|
||||
*out << " VMA Size Align Out In Symbol\n";
|
||||
|
||||
for (Chunk<E> *osec : 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";
|
||||
|
||||
if (!osec->is_output_section())
|
||||
continue;
|
||||
|
||||
std::span<InputSection<E> *> members = ((OutputSection<E> *)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;
|
||||
|
||||
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)
|
||||
<< " " << *mem << "\n";
|
||||
|
||||
typename Map<E>::const_accessor acc;
|
||||
if (map.find(acc, mem))
|
||||
for (Symbol<E> *sym : acc->second)
|
||||
ss << std::showbase
|
||||
<< std::setw(18) << std::hex << sym->get_addr(ctx) << std::dec
|
||||
<< " 0 0 "
|
||||
<< *sym << "\n";
|
||||
|
||||
bufs[i] = ss.str();
|
||||
});
|
||||
|
||||
for (std::string &str : bufs)
|
||||
*out << str;
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void print_map(Context<E> &ctx);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
-2652
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,144 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
inline u32 get_umask() {
|
||||
u32 orig_umask = umask(0);
|
||||
umask(orig_umask);
|
||||
return orig_umask;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
static std::pair<i64, char *>
|
||||
open_or_create_file(C &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.
|
||||
if (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 E>
|
||||
class MemoryMappedOutputFile : public OutputFile<E> {
|
||||
public:
|
||||
MemoryMappedOutputFile(Context<E> &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<E>(path, filesize, true) {
|
||||
i64 fd;
|
||||
std::tie(fd, output_tmpfile) = open_or_create_file(ctx, path, filesize, perm);
|
||||
|
||||
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, fd, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
::close(fd);
|
||||
}
|
||||
|
||||
void close(Context<E> &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (!this->is_unmapped)
|
||||
munmap(this->buf, this->filesize);
|
||||
|
||||
if (rename(output_tmpfile, this->path.c_str()) == -1)
|
||||
Fatal(ctx) << this->path << ": rename failed: " << errno_string();
|
||||
output_tmpfile = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class MallocOutputFile : public OutputFile<E> {
|
||||
public:
|
||||
MallocOutputFile(Context<E> &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<E>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)mmap(NULL, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << "mmap failed: " << errno_string();
|
||||
}
|
||||
|
||||
void close(Context<E> &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (this->path == "-") {
|
||||
fwrite(this->buf, this->filesize, 1, stdout);
|
||||
fclose(stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
|
||||
FILE *fp = fdopen(fd, "w");
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::unique_ptr<OutputFile<E>>
|
||||
OutputFile<E>::open(Context<E> &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
Timer t(ctx, "open_file");
|
||||
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
bool is_special = false;
|
||||
if (path == "-") {
|
||||
is_special = true;
|
||||
} else {
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) != S_IFREG)
|
||||
is_special = true;
|
||||
}
|
||||
|
||||
std::unique_ptr<OutputFile<E>> file;
|
||||
if (is_special)
|
||||
file = std::make_unique<MallocOutputFile<E>>(ctx, path, filesize, perm);
|
||||
else
|
||||
file = std::make_unique<MemoryMappedOutputFile<E>>(ctx, path, filesize, perm);
|
||||
|
||||
if (ctx.arg.filler != -1)
|
||||
memset(file->buf, ctx.arg.filler, filesize);
|
||||
return file;
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class OutputFile<E>;
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
-1681
File diff suppressed because it is too large.
Load diff
@@ -1,597 +0,0 @@
|
||||
// This file implements the -r or ---relocatable option. If the option
|
||||
// is given to a linker, the linker creates not an executable nor a
|
||||
// shared library file but instead create another object file by
|
||||
// combining input object files.
|
||||
//
|
||||
// The behavior of a linker with -r is pretty different from the one
|
||||
// without -r, and adding code for -r to the regular code path could
|
||||
// severely complicate it. Therefore, we implement -r as an
|
||||
// independent feature from others. That's why this file share only a
|
||||
// small amount of code with other files.
|
||||
//
|
||||
// Since the -r option is a minor feature, we don't aim for speed in
|
||||
// this file. This is a "better than nothing" implementation of the -r
|
||||
// option.
|
||||
//
|
||||
// Here is the strategy as to how to combine multiple object files
|
||||
// into one:
|
||||
//
|
||||
// - Regular sections containing opaque data (e.g. ".text" or
|
||||
// ".data") are just copied as-is without merging.
|
||||
//
|
||||
// - .symtab, .strtab and .shstrtab are merged.
|
||||
//
|
||||
// - COMDAT groups are uniquified.
|
||||
//
|
||||
// - Relocation sections are copied one by one, but we need to fix
|
||||
// symbol indices.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E> class RObjectFile;
|
||||
|
||||
template <typename E>
|
||||
class RChunk {
|
||||
public:
|
||||
RChunk() {
|
||||
out_shdr.sh_addralign = 1;
|
||||
}
|
||||
|
||||
virtual ~RChunk() = default;
|
||||
virtual void update_shdr(Context<E> &ctx) {}
|
||||
virtual void write_to(Context<E> &ctx) = 0;
|
||||
|
||||
std::string_view name;
|
||||
i64 shndx = 0;
|
||||
ElfShdr<E> in_shdr = {};
|
||||
ElfShdr<E> out_shdr = {};
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RInputSection : public RChunk<E> {
|
||||
public:
|
||||
RInputSection(Context<E> &ctx, RObjectFile<E> &file, const ElfShdr<E> &shdr);
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void write_to(Context<E> &ctx) override;
|
||||
|
||||
RObjectFile<E> &file;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RSymtabSection : public RChunk<E> {
|
||||
public:
|
||||
RSymtabSection() {
|
||||
this->name = ".symtab";
|
||||
this->out_shdr.sh_type = SHT_SYMTAB;
|
||||
this->out_shdr.sh_entsize = sizeof(ElfSym<E>);
|
||||
this->out_shdr.sh_addralign = E::word_size;
|
||||
}
|
||||
|
||||
void add_local_symbol(Context<E> &ctx, RObjectFile<E> &file, i64 idx);
|
||||
void add_global_symbol(Context<E> &ctx, RObjectFile<E> &file, i64 idx);
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void write_to(Context<E> &ctx) override;
|
||||
|
||||
std::unordered_map<std::string_view, i64> sym_map;
|
||||
std::vector<ElfSym<E>> syms{1};
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RStrtabSection : public RChunk<E> {
|
||||
public:
|
||||
RStrtabSection(std::string_view name) {
|
||||
this->name = name;
|
||||
this->out_shdr.sh_type = SHT_STRTAB;
|
||||
this->out_shdr.sh_size = 1;
|
||||
}
|
||||
|
||||
i64 add_string(std::string_view str);
|
||||
void write_to(Context<E> &ctx) override;
|
||||
|
||||
std::unordered_map<std::string_view, i64> strings;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class ROutputEhdr : public RChunk<E> {
|
||||
public:
|
||||
ROutputEhdr() {
|
||||
this->out_shdr.sh_size = sizeof(ElfEhdr<E>);
|
||||
}
|
||||
|
||||
void write_to(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class ROutputShdr : public RChunk<E> {
|
||||
public:
|
||||
ROutputShdr() {
|
||||
this->out_shdr.sh_size = sizeof(ElfShdr<E>);
|
||||
}
|
||||
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void write_to(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RObjectFile {
|
||||
public:
|
||||
RObjectFile(Context<E> &ctx, MappedFile<Context<E>> &mf, bool is_alive);
|
||||
|
||||
void remove_comdats(Context<E> &ctx,
|
||||
std::unordered_set<std::string_view> &groups);
|
||||
|
||||
template <typename T>
|
||||
std::span<T> get_data(Context<E> &ctx, const ElfShdr<E> &shdr);
|
||||
|
||||
MappedFile<Context<E>> &mf;
|
||||
std::span<ElfShdr<E>> elf_sections;
|
||||
std::vector<std::unique_ptr<RInputSection<E>>> sections;
|
||||
std::span<const ElfSym<E>> syms;
|
||||
std::vector<i64> symidx;
|
||||
std::unordered_set<std::string_view> defined_syms;
|
||||
std::unordered_set<std::string_view> undef_syms;
|
||||
i64 symtab_shndx = 0;
|
||||
i64 first_global = 0;
|
||||
bool is_alive;
|
||||
const char *strtab = nullptr;
|
||||
const char *shstrtab = nullptr;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void RSymtabSection<E>::add_local_symbol(Context<E> &ctx, RObjectFile<E> &file,
|
||||
i64 idx) {
|
||||
ElfSym<E> sym = file.syms[idx];
|
||||
assert(sym.st_bind == STB_LOCAL);
|
||||
|
||||
if (!sym.is_undef() && !sym.is_abs() && !sym.is_common()) {
|
||||
if (!file.sections[sym.st_shndx])
|
||||
return;
|
||||
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
|
||||
}
|
||||
|
||||
std::string_view name = file.strtab + sym.st_name;
|
||||
sym.st_name = ctx.r_strtab->add_string(name);
|
||||
|
||||
file.symidx[idx] = syms.size();
|
||||
syms.push_back(sym);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RSymtabSection<E>::add_global_symbol(Context<E> &ctx, RObjectFile<E> &file,
|
||||
i64 idx) {
|
||||
ElfSym<E> sym = file.syms[idx];
|
||||
assert(sym.st_bind != STB_LOCAL);
|
||||
|
||||
std::string_view name = file.strtab + sym.st_name;
|
||||
auto [it, inserted] = sym_map.insert({name, syms.size()});
|
||||
|
||||
if (inserted) {
|
||||
if (!sym.is_undef() && !sym.is_abs() && !sym.is_common())
|
||||
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
|
||||
sym.st_name = ctx.r_strtab->add_string(name);
|
||||
file.symidx[idx] = syms.size();
|
||||
syms.push_back(sym);
|
||||
return;
|
||||
}
|
||||
|
||||
file.symidx[idx] = it->second;
|
||||
|
||||
ElfSym<E> &existing = syms[it->second];
|
||||
if (existing.is_undef() && !sym.is_undef()) {
|
||||
if (!sym.is_abs() && !sym.is_common())
|
||||
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
|
||||
sym.st_name = existing.st_name;
|
||||
existing = sym;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RSymtabSection<E>::update_shdr(Context<E> &ctx) {
|
||||
this->out_shdr.sh_size = syms.size() * sizeof(ElfSym<E>);
|
||||
this->out_shdr.sh_link = ctx.r_strtab->shndx;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RSymtabSection<E>::write_to(Context<E> &ctx) {
|
||||
ElfSym<E> *buf = (ElfSym<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
for (i64 i = 1; i < syms.size(); i++)
|
||||
buf[i] = syms[i];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
RInputSection<E>::RInputSection(Context<E> &ctx, RObjectFile<E> &file,
|
||||
const ElfShdr<E> &shdr)
|
||||
: file(file) {
|
||||
this->name = file.shstrtab + shdr.sh_name;
|
||||
this->in_shdr = shdr;
|
||||
this->out_shdr = shdr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RInputSection<E>::update_shdr(Context<E> &ctx) {
|
||||
switch (this->in_shdr.sh_type) {
|
||||
case SHT_GROUP:
|
||||
this->out_shdr.sh_link = ctx.r_symtab->shndx;
|
||||
this->out_shdr.sh_info = file.symidx[this->in_shdr.sh_info];
|
||||
break;
|
||||
case SHT_REL:
|
||||
case SHT_RELA:
|
||||
this->out_shdr.sh_link = ctx.r_symtab->shndx;
|
||||
this->out_shdr.sh_info = file.sections[this->in_shdr.sh_info]->shndx;
|
||||
break;
|
||||
default:
|
||||
if (this->in_shdr.sh_link) {
|
||||
std::unique_ptr<RInputSection<E>> &sec =
|
||||
file.sections[this->in_shdr.sh_info];
|
||||
|
||||
if (sec)
|
||||
this->out_shdr.sh_link = sec->shndx;
|
||||
else if (this->in_shdr.sh_link == file.symtab_shndx)
|
||||
this->out_shdr.sh_link = ctx.r_symtab->shndx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RInputSection<E>::write_to(Context<E> &ctx) {
|
||||
if (this->in_shdr.sh_type == SHT_NOBITS)
|
||||
return;
|
||||
|
||||
std::span<u8> contents = file.template get_data<u8>(ctx, this->in_shdr);
|
||||
memcpy(ctx.buf + this->out_shdr.sh_offset, contents.data(), contents.size());
|
||||
|
||||
switch (this->in_shdr.sh_type) {
|
||||
case SHT_GROUP: {
|
||||
u32 *mem = (u32 *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
for (i64 i = 1; i < this->out_shdr.sh_size / sizeof(u32); i++)
|
||||
mem[i] = file.sections[mem[i]]->shndx;
|
||||
break;
|
||||
}
|
||||
case SHT_REL:
|
||||
case SHT_RELA: {
|
||||
ElfRel<E> *rel = (ElfRel<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
i64 size = this->out_shdr.sh_size / sizeof(ElfRel<E>);
|
||||
|
||||
for (i64 i = 0; i < size; i++) {
|
||||
const ElfSym<E> &sym = file.syms[rel[i].r_sym];
|
||||
if (sym.is_undef() || sym.is_abs() || sym.is_common() ||
|
||||
file.sections[sym.st_shndx])
|
||||
rel[i].r_sym = file.symidx[rel[i].r_sym];
|
||||
else
|
||||
memset(rel + i, 0, sizeof(ElfRel<E>));
|
||||
}
|
||||
|
||||
i64 i = 0;
|
||||
i64 j = 0;
|
||||
for (; j < size; j++)
|
||||
if (rel[j].r_type)
|
||||
rel[i++] = rel[j];
|
||||
for (; i < size; i++)
|
||||
memset(rel + i, 0, sizeof(ElfRel<E>));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 RStrtabSection<E>::add_string(std::string_view str) {
|
||||
auto [it, inserted] = strings.insert({str, this->out_shdr.sh_size});
|
||||
if (inserted)
|
||||
this->out_shdr.sh_size += str.size() + 1;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RStrtabSection<E>::write_to(Context<E> &ctx) {
|
||||
for (auto [str, offset] : strings)
|
||||
memcpy(ctx.buf + this->out_shdr.sh_offset + offset, str.data(), str.size());
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ROutputEhdr<E>::write_to(Context<E> &ctx) {
|
||||
ElfEhdr<E> &hdr = *(ElfEhdr<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
memcpy(&hdr.e_ident, "\177ELF", 4);
|
||||
hdr.e_ident[EI_CLASS] = (E::word_size == 8) ? ELFCLASS64 : ELFCLASS32;
|
||||
hdr.e_ident[EI_DATA] = E::is_le ? ELFDATA2LSB : ELFDATA2MSB;
|
||||
hdr.e_ident[EI_VERSION] = EV_CURRENT;
|
||||
hdr.e_type = ET_REL;
|
||||
hdr.e_machine = E::e_machine;
|
||||
hdr.e_version = EV_CURRENT;
|
||||
hdr.e_shoff = ctx.r_shdr->out_shdr.sh_offset;
|
||||
hdr.e_ehsize = sizeof(ElfEhdr<E>);
|
||||
hdr.e_shentsize = sizeof(ElfShdr<E>);
|
||||
hdr.e_shstrndx = ctx.r_shstrtab->shndx;
|
||||
hdr.e_shnum = ctx.r_shdr->out_shdr.sh_size / sizeof(ElfShdr<E>);
|
||||
hdr.e_shstrndx = ctx.r_shstrtab->shndx;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ROutputShdr<E>::update_shdr(Context<E> &ctx) {
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk->shndx)
|
||||
this->out_shdr.sh_size += sizeof(ElfShdr<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ROutputShdr<E>::write_to(Context<E> &ctx) {
|
||||
ElfShdr<E> *hdr = (ElfShdr<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk->shndx)
|
||||
hdr[chunk->shndx] = chunk->out_shdr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
RObjectFile<E>::RObjectFile(Context<E> &ctx, MappedFile<Context<E>> &mf,
|
||||
bool is_alive)
|
||||
: mf(mf), is_alive(is_alive) {
|
||||
// Read ELF header and section header
|
||||
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)mf.data;
|
||||
ElfShdr<E> *sh_begin = (ElfShdr<E> *)(mf.data + ehdr.e_shoff);
|
||||
i64 num_sections = (ehdr.e_shnum == 0) ? sh_begin->sh_size : ehdr.e_shnum;
|
||||
elf_sections = {sh_begin, sh_begin + num_sections};
|
||||
sections.resize(num_sections);
|
||||
|
||||
// Read .shstrtab
|
||||
i64 shstrtab_idx = (ehdr.e_shstrndx == SHN_XINDEX)
|
||||
? sh_begin->sh_link : ehdr.e_shstrndx;
|
||||
shstrtab = (char *)(mf.data + elf_sections[shstrtab_idx].sh_offset);
|
||||
|
||||
// Read .symtab
|
||||
for (i64 i = 1; i < elf_sections.size(); i++) {
|
||||
ElfShdr<E> &shdr = elf_sections[i];
|
||||
if (shdr.sh_type == SHT_SYMTAB) {
|
||||
syms = get_data<const ElfSym<E>>(ctx, shdr);
|
||||
strtab = (char *)(mf.data + elf_sections[shdr.sh_link].sh_offset);
|
||||
symtab_shndx = i;
|
||||
first_global = shdr.sh_info;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
symidx.resize(syms.size());
|
||||
|
||||
// Read sections
|
||||
for (i64 i = 1; i < elf_sections.size(); i++) {
|
||||
ElfShdr<E> &shdr = elf_sections[i];
|
||||
switch (shdr.sh_type) {
|
||||
case SHT_NULL:
|
||||
case SHT_SYMTAB:
|
||||
case SHT_STRTAB:
|
||||
break;
|
||||
default:
|
||||
sections[i].reset(new RInputSection(ctx, *this, shdr));
|
||||
}
|
||||
}
|
||||
|
||||
// Read global symbols
|
||||
for (i64 i = first_global; i < syms.size(); i++) {
|
||||
std::string_view name = strtab + syms[i].st_name;
|
||||
if (syms[i].is_defined())
|
||||
defined_syms.insert(name);
|
||||
else
|
||||
undef_syms.insert(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicate comdat groups
|
||||
template <typename E>
|
||||
void RObjectFile<E>::remove_comdats(Context<E> &ctx,
|
||||
std::unordered_set<std::string_view> &groups) {
|
||||
for (i64 i = 1; i < sections.size(); i++) {
|
||||
if (!sections[i])
|
||||
continue;
|
||||
ElfShdr<E> &shdr = sections[i]->in_shdr;
|
||||
if (shdr.sh_type != SHT_GROUP)
|
||||
continue;
|
||||
|
||||
// Get a comdat group signature and insert it into a set.
|
||||
const ElfSym<E> &sym = syms[shdr.sh_info];
|
||||
std::string_view signature = strtab + sym.st_name;
|
||||
if (groups.insert(signature).second)
|
||||
continue;
|
||||
|
||||
// If it is a duplicate, remove it and its members.
|
||||
for (i64 j : this->template get_data<u32>(ctx, shdr).subspan(1))
|
||||
sections[j] = nullptr;
|
||||
sections[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
template <typename T>
|
||||
std::span<T> RObjectFile<E>::get_data(Context<E> &ctx, const ElfShdr<E> &shdr) {
|
||||
T *begin = (T *)(mf.data + shdr.sh_offset);
|
||||
T *end = (T *)(mf.data + shdr.sh_offset + shdr.sh_size);
|
||||
return {begin, end};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<std::unique_ptr<RObjectFile<E>>>
|
||||
open_files(Context<E> &ctx, std::span<std::string_view> args) {
|
||||
std::vector<std::unique_ptr<RObjectFile<E>>> files;
|
||||
bool whole_archive = false;
|
||||
|
||||
while (!args.empty()) {
|
||||
if (read_flag(args, "whole-archive")) {
|
||||
whole_archive = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read_flag(args, "no-whole-archive")) {
|
||||
whole_archive = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string_view arg;
|
||||
if (read_arg(ctx, args, arg, "version-script") ||
|
||||
read_arg(ctx, args, arg, "dynamic-list"))
|
||||
continue;
|
||||
|
||||
MappedFile<Context<E>> *mf = nullptr;
|
||||
|
||||
if (read_arg(ctx, args, arg, "l")) {
|
||||
mf = find_library(ctx, std::string(arg));
|
||||
} else {
|
||||
if (arg.starts_with('-'))
|
||||
continue;
|
||||
arg = args[0];
|
||||
args = args.subspan(1);
|
||||
mf = MappedFile<Context<E>>::must_open(ctx, std::string(arg));
|
||||
}
|
||||
|
||||
switch (get_file_type(mf)) {
|
||||
case FileType::ELF_OBJ:
|
||||
files.emplace_back(new RObjectFile<E>(ctx, *mf, true));
|
||||
break;
|
||||
case FileType::AR:
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf))
|
||||
if (get_file_type(child) == FileType::ELF_OBJ)
|
||||
files.emplace_back(new RObjectFile<E>(ctx, *child, whole_archive));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 assign_offsets(Context<E> &ctx) {
|
||||
i64 offset = 0;
|
||||
for (RChunk<E> *chunk : ctx.r_chunks) {
|
||||
offset = align_to(offset, chunk->out_shdr.sh_addralign);
|
||||
chunk->out_shdr.sh_offset = offset;
|
||||
offset += chunk->out_shdr.sh_size;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
static bool contains(std::unordered_set<std::string_view> &a,
|
||||
std::unordered_set<std::string_view> &b) {
|
||||
for (std::string_view x : b)
|
||||
if (a.contains(x))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void combine_objects(Context<E> &ctx, std::span<std::string_view> file_args) {
|
||||
// Read object files
|
||||
std::vector<std::unique_ptr<RObjectFile<E>>> files = open_files(ctx, file_args);
|
||||
|
||||
// Identify needed objects
|
||||
std::unordered_set<std::string_view> undef_syms;
|
||||
auto add_syms = [&](RObjectFile<E> &file) {
|
||||
undef_syms.insert(file.undef_syms.begin(), file.undef_syms.end());
|
||||
for (std::string_view name : file.defined_syms)
|
||||
undef_syms.erase(name);
|
||||
file.is_alive = true;
|
||||
};
|
||||
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
if (file->is_alive)
|
||||
add_syms(*file);
|
||||
|
||||
for (;;) {
|
||||
bool added = false;
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files) {
|
||||
if (!file->is_alive && contains(undef_syms, file->defined_syms)) {
|
||||
add_syms(*file);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
if (!added)
|
||||
break;
|
||||
}
|
||||
|
||||
std::erase_if(files, [](std::unique_ptr<RObjectFile<E>> &file) {
|
||||
return !file->is_alive;
|
||||
});
|
||||
|
||||
// Remove duplicate comdat groups
|
||||
std::unordered_set<std::string_view> comdat_groups;
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
file->remove_comdats(ctx, comdat_groups);
|
||||
|
||||
// Create headers and linker-synthesized sections
|
||||
ROutputEhdr<E> ehdr;
|
||||
ROutputShdr<E> shdr;
|
||||
RSymtabSection<E> symtab;
|
||||
RStrtabSection<E> shstrtab(".shstrtab");
|
||||
RStrtabSection<E> strtab(".strtab");
|
||||
|
||||
ctx.r_chunks.push_back(&ehdr);
|
||||
ctx.r_chunks.push_back(&shstrtab);
|
||||
ctx.r_chunks.push_back(&strtab);
|
||||
|
||||
ctx.r_ehdr = &ehdr;
|
||||
ctx.r_shdr = &shdr;
|
||||
ctx.r_shstrtab = &shstrtab;
|
||||
ctx.r_strtab = &strtab;
|
||||
ctx.r_symtab = &symtab;
|
||||
|
||||
// Add input sections to output sections
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
for (std::unique_ptr<RInputSection<E>> &sec : file->sections)
|
||||
if (sec)
|
||||
ctx.r_chunks.push_back(sec.get());
|
||||
|
||||
ctx.r_chunks.push_back(&symtab);
|
||||
ctx.r_chunks.push_back(&shdr);
|
||||
|
||||
// Assign output section indices
|
||||
i64 shndx = 1;
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk != &ehdr && chunk != &shdr)
|
||||
chunk->shndx = shndx++;
|
||||
|
||||
// Add section names to .shstrtab
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk->shndx)
|
||||
chunk->out_shdr.sh_name = shstrtab.add_string(chunk->name);
|
||||
|
||||
// Copy symbols from input objects to an output object
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
for (i64 i = 1; i < file->first_global; i++)
|
||||
symtab.add_local_symbol(ctx, *file, i);
|
||||
|
||||
symtab.out_shdr.sh_info = symtab.syms.size();
|
||||
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
for (i64 i = file->first_global; i < file->syms.size(); i++)
|
||||
symtab.add_global_symbol(ctx, *file, i);
|
||||
|
||||
// Finalize section header
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
// Open an output file
|
||||
i64 filesize = assign_offsets(ctx);
|
||||
std::unique_ptr<OutputFile<E>> out =
|
||||
OutputFile<E>::open(ctx, ctx.arg.output, filesize, 0666);
|
||||
memset(out->buf, 0, filesize);
|
||||
ctx.buf = out->buf;
|
||||
|
||||
// Write to the output file
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
chunk->write_to(ctx);
|
||||
out->close(ctx);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void combine_objects(Context<E> &, std::span<std::string_view>);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -1,321 +0,0 @@
|
||||
#include "mold.h"
|
||||
#include "../sha.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <signal.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define DAEMON_TIMEOUT 30
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Exiting from a program with large memory usage is slow --
|
||||
// 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() {
|
||||
int pipefd[2];
|
||||
if (pipe(pipefd) == -1) {
|
||||
perror("pipe");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
perror("fork");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (pid > 0) {
|
||||
// Parent
|
||||
close(pipefd[1]);
|
||||
|
||||
char buf[1];
|
||||
if (read(pipefd[0], buf, 1) == 1)
|
||||
_exit(0);
|
||||
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
if (WIFEXITED(status))
|
||||
_exit(WEXITSTATUS(status));
|
||||
if (WIFSIGNALED(status))
|
||||
raise(WTERMSIG(status));
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
// Child
|
||||
close(pipefd[0]);
|
||||
|
||||
return [=] {
|
||||
char buf[] = {1};
|
||||
int n = write(pipefd[1], buf, 1);
|
||||
assert(n == 1);
|
||||
};
|
||||
}
|
||||
|
||||
static std::string base64(u8 *data, u64 size) {
|
||||
static const char chars[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+_";
|
||||
|
||||
std::ostringstream out;
|
||||
|
||||
auto encode = [&](u32 x) {
|
||||
out << chars[x & 0b111111]
|
||||
<< chars[(x >> 6) & 0b111111]
|
||||
<< chars[(x >> 12) & 0b111111]
|
||||
<< chars[(x >> 18) & 0b111111];
|
||||
};
|
||||
|
||||
i64 i = 0;
|
||||
for (; i < size - 3; i += 3)
|
||||
encode((data[i + 2] << 16) | (data[i + 1] << 8) | data[i]);
|
||||
|
||||
if (i == size - 1)
|
||||
encode(data[i]);
|
||||
else if (i == size - 2)
|
||||
encode((data[i + 1] << 8) | data[i]);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
static std::string compute_sha256(std::span<std::string_view> argv) {
|
||||
SHA256_CTX sha;
|
||||
SHA256_Init(&sha);
|
||||
|
||||
for (std::string_view arg : argv) {
|
||||
if (arg != "-preload" && arg != "--preload") {
|
||||
SHA256_Update(&sha, arg.data(), arg.size());
|
||||
char buf[] = {0};
|
||||
SHA256_Update(&sha, buf, 1);
|
||||
}
|
||||
}
|
||||
|
||||
u8 digest[SHA256_SIZE];
|
||||
SHA256_Final(digest, &sha);
|
||||
return base64(digest, SHA256_SIZE);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void send_fd(Context<E> &ctx, i64 conn, i64 fd) {
|
||||
struct iovec iov;
|
||||
char dummy = '1';
|
||||
iov.iov_base = &dummy;
|
||||
iov.iov_len = 1;
|
||||
|
||||
struct msghdr msg = {};
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
char buf[CMSG_SPACE(sizeof(int))];
|
||||
msg.msg_control = buf;
|
||||
msg.msg_controllen = CMSG_LEN(sizeof(int));
|
||||
|
||||
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS;
|
||||
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
|
||||
*(int *)CMSG_DATA(cmsg) = fd;
|
||||
|
||||
if (sendmsg(conn, &msg, 0) == -1)
|
||||
Fatal(ctx) << "sendmsg failed: " << errno_string();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 recv_fd(Context<E> &ctx, i64 conn) {
|
||||
struct iovec iov;
|
||||
char buf[1];
|
||||
iov.iov_base = buf;
|
||||
iov.iov_len = sizeof(buf);
|
||||
|
||||
struct msghdr msg = {};
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
char cmsgbuf[CMSG_SPACE(sizeof(int))];
|
||||
msg.msg_control = (caddr_t)cmsgbuf;
|
||||
msg.msg_controllen = sizeof(cmsgbuf);
|
||||
|
||||
i64 len = recvmsg(conn, &msg, 0);
|
||||
if (len <= 0)
|
||||
Fatal(ctx) << "recvmsg failed: " << errno_string();
|
||||
|
||||
struct cmsghdr *cmsg;
|
||||
cmsg = CMSG_FIRSTHDR(&msg);
|
||||
return *(int *)CMSG_DATA(cmsg);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void try_resume_daemon(Context<E> &ctx) {
|
||||
i64 conn = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (conn == -1)
|
||||
Fatal(ctx) << "socket failed: " << errno_string();
|
||||
|
||||
std::string path = "/tmp/mold-" + compute_sha256(ctx.cmdline_args);
|
||||
|
||||
struct sockaddr_un name = {};
|
||||
name.sun_family = AF_UNIX;
|
||||
memcpy(name.sun_path, path.data(), path.size());
|
||||
|
||||
if (connect(conn, (struct sockaddr *)&name, sizeof(name)) != 0) {
|
||||
close(conn);
|
||||
return;
|
||||
}
|
||||
|
||||
send_fd(ctx, conn, STDOUT_FILENO);
|
||||
send_fd(ctx, conn, STDERR_FILENO);
|
||||
|
||||
char buf[1];
|
||||
i64 r = read(conn, buf, 1);
|
||||
close(conn);
|
||||
if (r == 1)
|
||||
exit(0);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void daemonize(Context<E> &ctx, std::function<void()> *wait_for_client,
|
||||
std::function<void()> *on_complete) {
|
||||
if (daemon(1, 0) == -1)
|
||||
Fatal(ctx) << "daemon failed: " << errno_string();
|
||||
|
||||
i64 sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sock == -1)
|
||||
Fatal(ctx) << "socket failed: " << errno_string();
|
||||
|
||||
socket_tmpfile =
|
||||
strdup(("/tmp/mold-" + compute_sha256(ctx.cmdline_args)).c_str());
|
||||
|
||||
struct sockaddr_un name = {};
|
||||
name.sun_family = AF_UNIX;
|
||||
strcpy(name.sun_path, socket_tmpfile);
|
||||
|
||||
u32 orig_mask = umask(0177);
|
||||
|
||||
if (bind(sock, (struct sockaddr *)&name, sizeof(name)) == -1) {
|
||||
if (errno != EADDRINUSE)
|
||||
Fatal(ctx) << "bind failed: " << errno_string();
|
||||
|
||||
unlink(socket_tmpfile);
|
||||
if (bind(sock, (struct sockaddr *)&name, sizeof(name)) == -1)
|
||||
Fatal(ctx) << "bind failed: " << errno_string();
|
||||
}
|
||||
|
||||
umask(orig_mask);
|
||||
|
||||
if (listen(sock, 0) == -1)
|
||||
Fatal(ctx) << "listen failed: " << errno_string();
|
||||
|
||||
static i64 conn = -1;
|
||||
|
||||
*wait_for_client = [=, &ctx] {
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(sock, &rfds);
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = DAEMON_TIMEOUT;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
i64 res = select(sock + 1, &rfds, NULL, NULL, &tv);
|
||||
if (res == -1)
|
||||
Fatal(ctx) << "select failed: " << errno_string();
|
||||
|
||||
if (res == 0) {
|
||||
std::cout << "timeout\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
conn = accept(sock, NULL, NULL);
|
||||
if (conn == -1)
|
||||
Fatal(ctx) << "accept failed: " << errno_string();
|
||||
unlink(socket_tmpfile);
|
||||
|
||||
dup2(recv_fd(ctx, conn), STDOUT_FILENO);
|
||||
dup2(recv_fd(ctx, conn), STDERR_FILENO);
|
||||
};
|
||||
|
||||
*on_complete = [=] {
|
||||
char buf[] = {1};
|
||||
int n = write(conn, buf, 1);
|
||||
assert(n == 1);
|
||||
};
|
||||
}
|
||||
|
||||
static std::string get_self_path() {
|
||||
return std::filesystem::read_symlink("/proc/self/exe");
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
|
||||
// Look for mold-wrapper.so from the same directory as the executable is.
|
||||
std::filesystem::path path = self.parent_path() / "mold-wrapper.so";
|
||||
std::error_code ec;
|
||||
if (std::filesystem::is_regular_file(path, ec) && !ec)
|
||||
return path;
|
||||
|
||||
#ifdef LIBDIR
|
||||
// If not found, search $(LIBDIR)/mold, which is /usr/local/lib/mold
|
||||
// by default.
|
||||
path = LIBDIR "/mold/mold-wrapper.so";
|
||||
if (std::filesystem::is_regular_file(path, ec) && !ec)
|
||||
return path;
|
||||
#endif
|
||||
|
||||
// Look for ../lib/mold/mold-wrapper.so
|
||||
path = self.parent_path() / "../lib/mold/mold-wrapper.so";
|
||||
if (std::filesystem::is_regular_file(path, ec) && !ec)
|
||||
return path;
|
||||
|
||||
Fatal(ctx) << "mold-wrapper.so is missing";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
[[noreturn]]
|
||||
void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
std::string_view arg1 = argv[1];
|
||||
assert(arg1 == "-run" || arg1 == "--run");
|
||||
if (!argv[2])
|
||||
Fatal(ctx) << "-run: argument missing";
|
||||
|
||||
// Get the mold-wrapper.so path
|
||||
std::string self = get_self_path();
|
||||
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()));
|
||||
|
||||
// If ld, ld.lld or ld.gold is specified, run mold itself
|
||||
if (std::string cmd = filepath(argv[2]).filename();
|
||||
cmd == "ld" || cmd == "ld.lld" || cmd == "ld.gold") {
|
||||
std::vector<char *> args;
|
||||
args.push_back(argv[0]);
|
||||
args.insert(args.end(), argv + 3, argv + argc);
|
||||
args.push_back(nullptr);
|
||||
execv(self.c_str(), args.data());
|
||||
Fatal(ctx) << "mold -run failed: " << self << ": " << errno_string();
|
||||
}
|
||||
|
||||
// Execute a given command
|
||||
execvp(argv[2], argv + 2);
|
||||
Fatal(ctx) << "mold -run failed: " << argv[2] << ": " << errno_string();
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void try_resume_daemon(Context<E> &); \
|
||||
template void daemonize(Context<E> &, std::function<void()> *, \
|
||||
std::function<void()> *); \
|
||||
template void process_run_subcommand(Context<E> &, int, char **)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string get_realpath(std::string_view path) {
|
||||
std::error_code ec;
|
||||
std::string ret = std::filesystem::canonical(path, ec);
|
||||
return ec ? std::string(path) : ret;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
std::filesystem::path to_abs_path(std::filesystem::path path) {
|
||||
if (path.is_absolute())
|
||||
return path;
|
||||
return (std::filesystem::current_path() / path).lexically_normal();
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
-148
@@ -1,148 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
#include "elf/elf.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
enum class FileType {
|
||||
UNKNOWN,
|
||||
EMPTY,
|
||||
ELF_OBJ,
|
||||
ELF_DSO,
|
||||
MACH_OBJ,
|
||||
MACH_DYLIB,
|
||||
MACH_UNIVERSAL,
|
||||
AR,
|
||||
THIN_AR,
|
||||
TAPI,
|
||||
TEXT,
|
||||
GCC_LTO_OBJ,
|
||||
LLVM_BITCODE,
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
bool is_text_file(MappedFile<C> *mf) {
|
||||
u8 *data = mf->data;
|
||||
return mf->size >= 4 && isprint(data[0]) && isprint(data[1]) &&
|
||||
isprint(data[2]) && isprint(data[3]);
|
||||
}
|
||||
|
||||
template <typename E, typename C>
|
||||
inline bool is_gcc_lto_obj(MappedFile<C> *mf) {
|
||||
using namespace mold::elf;
|
||||
|
||||
const char *data = mf->get_contents().data();
|
||||
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)data;
|
||||
std::span<ElfShdr<E>> shdrs{(ElfShdr<E> *)(data + ehdr.e_shoff), ehdr.e_shnum};
|
||||
|
||||
for (ElfShdr<E> &sec : shdrs) {
|
||||
if (sec.sh_type != SHT_SYMTAB)
|
||||
continue;
|
||||
|
||||
std::span<ElfSym<E>> elf_syms{(ElfSym<E> *)(data + sec.sh_offset),
|
||||
sec.sh_size / sizeof(ElfSym<E>)};
|
||||
|
||||
auto skip = [](u8 type) {
|
||||
return type == STT_NOTYPE || type == STT_FILE || type == STT_SECTION;
|
||||
};
|
||||
|
||||
// GCC LTO object contains only sections symbols followed by a common
|
||||
// symbol whose name is `__gnu_lto_v1` or `__gnu_lto_slim`.
|
||||
i64 i = 1;
|
||||
while (i < elf_syms.size() && skip(elf_syms[i].st_type))
|
||||
i++;
|
||||
|
||||
if (i < elf_syms.size() && elf_syms[i].st_shndx == SHN_COMMON) {
|
||||
std::string_view name =
|
||||
data + shdrs[sec.sh_link].sh_offset + elf_syms[i].st_name;
|
||||
if (name.starts_with("__gnu_lto_"))
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
FileType get_file_type(MappedFile<C> *mf) {
|
||||
std::string_view data = mf->get_contents();
|
||||
|
||||
if (data.empty())
|
||||
return FileType::EMPTY;
|
||||
|
||||
if (data.starts_with("\177ELF")) {
|
||||
switch (*(u16 *)(data.data() + 16)) {
|
||||
case 1: {
|
||||
// ET_REL
|
||||
elf::Elf32Ehdr &ehdr = *(elf::Elf32Ehdr *)data.data();
|
||||
|
||||
if (ehdr.e_ident[elf::EI_CLASS] == elf::ELFCLASS32) {
|
||||
if (is_gcc_lto_obj<elf::I386>(mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
} else {
|
||||
if (is_gcc_lto_obj<elf::X86_64>(mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
}
|
||||
|
||||
return FileType::ELF_OBJ;
|
||||
}
|
||||
case 3: // ET_DYN
|
||||
return FileType::ELF_DSO;
|
||||
}
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
if (data.starts_with("\xcf\xfa\xed\xfe")) {
|
||||
switch (*(u32 *)(data.data() + 12)) {
|
||||
case 1: // MH_OBJECT
|
||||
return FileType::MACH_OBJ;
|
||||
case 6: // MH_DYLIB
|
||||
return FileType::MACH_DYLIB;
|
||||
}
|
||||
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"))
|
||||
return FileType::LLVM_BITCODE;
|
||||
if (data.starts_with("BC\xc0\xde"))
|
||||
return FileType::LLVM_BITCODE;
|
||||
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_OBJ: return "MACH_OBJ";
|
||||
case FileType::MACH_DYLIB: return "MACH_DYLIB";
|
||||
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";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &out, FileType type) {
|
||||
out << filetype_to_string(type);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/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
|
||||
|
||||
case "$ID" in
|
||||
ubuntu | pop | linuxmint | debian | raspbian | neon)
|
||||
apt-get update
|
||||
apt-get install -y cmake gcc g++ clang gdb
|
||||
;;
|
||||
fedora | amzn | rhel | centos)
|
||||
dnf install -y gcc-g++ cmake glibc-static libstdc++-static diffutils util-linux tar
|
||||
;;
|
||||
rocky)
|
||||
dnf install -y gcc-g++ cmake diffutils util-linux
|
||||
;;
|
||||
opensuse-*)
|
||||
zypper install -y make cmake gcc-c++ glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
gentoo)
|
||||
emerge-webrsync
|
||||
FEATURES='getbinpkg binpkg-request-signature' emerge dev-build/cmake
|
||||
;;
|
||||
arch | archarm | artix | endeavouros)
|
||||
pacman -Sy --needed --noconfirm base-devel cmake util-linux
|
||||
;;
|
||||
void)
|
||||
xbps-install -Sy xbps bash make cmake gcc tar diffutils util-linux
|
||||
;;
|
||||
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
|
||||
esac
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
. /etc/os-release
|
||||
|
||||
set -x
|
||||
|
||||
# This script install packages for -DMOLD_ENABLE_QEMU_TESTS=1
|
||||
# to enable cross-target tests.
|
||||
#
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
case "$ID" 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"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,7 +1,31 @@
|
||||
// This file contains functions to read an archive file (.a file).
|
||||
// An archive file is just a bundle of object files. It's similar to
|
||||
// tar or zip, but the contents are not compressed.
|
||||
//
|
||||
// An archive file is either "regular" or "thin". A regular archive
|
||||
// contains object files directly, while a thin archive contains only
|
||||
// pathnames. In the latter case, actual file contents have to be read
|
||||
// from given pathnames. A regular archive is sometimes called "fat"
|
||||
// archive as opposed to "thin".
|
||||
//
|
||||
// If an archive file is given to the linker, the linker pulls out
|
||||
// object files that are needed to resolve undefined symbols. So,
|
||||
// bunding object files as an archive and giving that archive to the
|
||||
// linker has a different meaning than directly giving the same set of
|
||||
// object files to the linker. The former links only needed object
|
||||
// files, while the latter links all the given object files.
|
||||
//
|
||||
// Therefore, if you link libc.a for example, not all the libc
|
||||
// functions are linked to your binary. Instead, only object files
|
||||
// that provides functions and variables used in your program get
|
||||
// linked. To make this efficient, static library functions are
|
||||
// usually separated to each object file in an archive file. You can
|
||||
// see the contents of libc.a by running `ar t
|
||||
// /usr/lib/x86_64-linux-gnu/libc.a`.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
#include "filetype.h"
|
||||
#include "common.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -51,12 +75,12 @@ struct ArHdr {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
std::vector<MappedFile<C> *>
|
||||
read_thin_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
template <typename Context>
|
||||
std::vector<MappedFile *>
|
||||
read_thin_archive_members(Context &ctx, MappedFile *mf) {
|
||||
u8 *begin = mf->data;
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MappedFile<C> *> vec;
|
||||
std::vector<MappedFile *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (data < begin + mf->size) {
|
||||
@@ -91,19 +115,19 @@ read_thin_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
continue;
|
||||
|
||||
std::string path = name.starts_with('/') ?
|
||||
name : (filepath(mf->name).parent_path() / name).string();
|
||||
vec.push_back(MappedFile<C>::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 C>
|
||||
std::vector<MappedFile<C> *>
|
||||
read_fat_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
template <typename Context>
|
||||
std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf) {
|
||||
u8 *begin = mf->data;
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MappedFile<C> *> vec;
|
||||
std::vector<MappedFile *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (begin + mf->size - data >= 2) {
|
||||
@@ -137,17 +161,13 @@ read_fat_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
std::vector<MappedFile<C> *>
|
||||
read_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
switch (get_file_type(mf)) {
|
||||
case FileType::AR:
|
||||
template <typename Context>
|
||||
std::vector<MappedFile *> read_archive_members(Context &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);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+977
@@ -0,0 +1,977 @@
|
||||
#pragma once
|
||||
|
||||
#include "config.h"
|
||||
#include "integers.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 char *output_tmpfile;
|
||||
|
||||
inline u8 *output_buffer_start = nullptr;
|
||||
inline u8 *output_buffer_end = nullptr;
|
||||
|
||||
std::string errno_string();
|
||||
void cleanup();
|
||||
void install_signal_handler();
|
||||
|
||||
inline u64 combine_hash(u64 a, u64 b) {
|
||||
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
|
||||
}
|
||||
|
||||
//
|
||||
// Error output
|
||||
//
|
||||
|
||||
// Some C++ stdlibs don't support std::osyncstream even though
|
||||
// it's is in the C++20 standard. So we implement it ourselves.
|
||||
class SyncStream {
|
||||
public:
|
||||
SyncStream(std::ostream &out) : out(out) {}
|
||||
|
||||
~SyncStream() {
|
||||
emit();
|
||||
}
|
||||
|
||||
template <typename T> SyncStream &operator<<(T &&val) {
|
||||
ss << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void emit() {
|
||||
if (emitted)
|
||||
return;
|
||||
|
||||
std::scoped_lock lock(mu);
|
||||
out << ss.str() << '\n';
|
||||
emitted = true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream &out;
|
||||
std::stringstream ss;
|
||||
bool emitted = false;
|
||||
static inline std::mutex mu;
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
class Out {
|
||||
public:
|
||||
Out(Context &ctx) {}
|
||||
|
||||
template <typename T> Out &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncStream out{std::cout};
|
||||
};
|
||||
|
||||
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 Context>
|
||||
class Fatal {
|
||||
public:
|
||||
Fatal(Context &ctx) {
|
||||
out << (ctx.arg.color_diagnostics ? fatal_color : fatal_mono);
|
||||
}
|
||||
|
||||
[[noreturn]] ~Fatal() {
|
||||
out.emit();
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
template <typename T> Fatal &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncStream out{std::cerr};
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
class Error {
|
||||
public:
|
||||
Error(Context &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 T> Error &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncStream out{std::cerr};
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
class Warn {
|
||||
public:
|
||||
Warn(Context &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);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> Warn &operator<<(T &&val) {
|
||||
if (out)
|
||||
*out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<SyncStream> out;
|
||||
};
|
||||
|
||||
//
|
||||
// Atomics
|
||||
//
|
||||
|
||||
// This is the same as std::atomic except that the default memory
|
||||
// order is relaxed instead of sequential consistency.
|
||||
template <typename T>
|
||||
struct Atomic : std::atomic<T> {
|
||||
static constexpr std::memory_order relaxed = std::memory_order_relaxed;
|
||||
|
||||
using std::atomic<T>::atomic;
|
||||
|
||||
Atomic(const Atomic<T> &other) : 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);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// mimalloc.cc
|
||||
//
|
||||
|
||||
void set_mimalloc_options();
|
||||
|
||||
//
|
||||
// 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];
|
||||
};
|
||||
|
||||
//
|
||||
// glob.cc
|
||||
//
|
||||
|
||||
class Glob {
|
||||
typedef enum { STRING, STAR, QUESTION, BRACKET } Kind;
|
||||
|
||||
struct Element {
|
||||
Element(Kind k) : kind(k) {}
|
||||
Kind kind;
|
||||
std::string str;
|
||||
std::bitset<256> bitset;
|
||||
};
|
||||
|
||||
public:
|
||||
static std::optional<Glob> compile(std::string_view pat);
|
||||
bool match(std::string_view str);
|
||||
|
||||
private:
|
||||
Glob(std::vector<Element> &&vec) : elements(vec) {}
|
||||
static bool do_match(std::string_view str, std::span<Element> elements);
|
||||
|
||||
std::vector<Element> elements;
|
||||
};
|
||||
|
||||
//
|
||||
// multi-glob.cc
|
||||
//
|
||||
|
||||
class MultiGlob {
|
||||
public:
|
||||
bool add(std::string_view pat, i64 val);
|
||||
bool empty() const { return strings.empty(); }
|
||||
std::optional<i64> find(std::string_view str);
|
||||
|
||||
private:
|
||||
struct TrieNode {
|
||||
i64 value = -1;
|
||||
TrieNode *suffix_link = nullptr;
|
||||
std::unique_ptr<TrieNode> children[256];
|
||||
};
|
||||
|
||||
void compile();
|
||||
void fix_suffix_links(TrieNode &node);
|
||||
void fix_values();
|
||||
i64 find_aho_corasick(std::string_view str);
|
||||
|
||||
std::vector<std::string> strings;
|
||||
std::unique_ptr<TrieNode> root;
|
||||
std::vector<std::pair<Glob, i64>> globs;
|
||||
std::once_flag once;
|
||||
bool is_compiled = false;
|
||||
bool prefix_match = false;
|
||||
};
|
||||
|
||||
//
|
||||
// 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);
|
||||
|
||||
//
|
||||
// jbos.cc
|
||||
//
|
||||
|
||||
void acquire_global_lock();
|
||||
void release_global_lock();
|
||||
|
||||
//
|
||||
// crc32.cc
|
||||
//
|
||||
|
||||
u32 compute_crc32(u32 crc, u8 *buf, i64 len);
|
||||
std::vector<u8> crc32_solve(u32 current, u32 desired);
|
||||
|
||||
//
|
||||
// compress.cc
|
||||
//
|
||||
|
||||
class Compressor {
|
||||
public:
|
||||
virtual void write_to(u8 *buf) = 0;
|
||||
virtual ~Compressor() {}
|
||||
i64 compressed_size = 0;
|
||||
};
|
||||
|
||||
class ZlibCompressor : public Compressor {
|
||||
public:
|
||||
ZlibCompressor(u8 *buf, i64 size);
|
||||
void write_to(u8 *buf) override;
|
||||
|
||||
private:
|
||||
std::vector<std::vector<u8>> shards;
|
||||
u64 checksum = 0;
|
||||
};
|
||||
|
||||
class ZstdCompressor : public Compressor {
|
||||
public:
|
||||
ZstdCompressor(u8 *buf, i64 size);
|
||||
void write_to(u8 *buf) override;
|
||||
|
||||
private:
|
||||
std::vector<std::vector<u8>> shards;
|
||||
};
|
||||
|
||||
//
|
||||
// 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;
|
||||
};
|
||||
|
||||
//
|
||||
// Memory-mapped file
|
||||
//
|
||||
|
||||
// MappedFile represents an mmap'ed input file.
|
||||
// mold uses mmap-IO only.
|
||||
class MappedFile {
|
||||
public:
|
||||
~MappedFile() { unmap(); }
|
||||
void unmap();
|
||||
void close_fd();
|
||||
void reopen_fd(const std::string &path);
|
||||
|
||||
template <typename Context>
|
||||
MappedFile *slice(Context &ctx, std::string name, u64 start, u64 size) {
|
||||
MappedFile *mf = new MappedFile;
|
||||
mf->name = name;
|
||||
mf->data = data + start;
|
||||
mf->size = size;
|
||||
mf->parent = this;
|
||||
|
||||
ctx.mf_pool.emplace_back(mf);
|
||||
return mf;
|
||||
}
|
||||
|
||||
std::string_view get_contents() {
|
||||
return std::string_view((char *)data, size);
|
||||
}
|
||||
|
||||
i64 get_offset() const {
|
||||
return parent ? (data - parent->data + parent->get_offset()) : 0;
|
||||
}
|
||||
|
||||
// Returns a string that uniquely identify a file that is possibly
|
||||
// in an archive.
|
||||
std::string get_identifier() const {
|
||||
if (parent) {
|
||||
// We use the file offset within an archive as an identifier
|
||||
// because archive members may have the same name.
|
||||
return parent->name + ":" + std::to_string(get_offset());
|
||||
}
|
||||
|
||||
if (thin_parent) {
|
||||
// If this is a thin archive member, the filename part is
|
||||
// guaranteed to be unique.
|
||||
return thin_parent->name + ":" + name;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
u8 *data = nullptr;
|
||||
i64 size = 0;
|
||||
bool given_fullpath = true;
|
||||
MappedFile *parent = nullptr;
|
||||
MappedFile *thin_parent = nullptr;
|
||||
|
||||
// For --dependency-file
|
||||
bool is_dependency = true;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE fd = INVALID_HANDLE_VALUE;
|
||||
#else
|
||||
int fd = -1;
|
||||
#endif
|
||||
};
|
||||
|
||||
MappedFile *open_file_impl(const std::string &path, std::string &error);
|
||||
|
||||
template <typename Context>
|
||||
MappedFile *open_file(Context &ctx, std::string path) {
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
std::string error;
|
||||
MappedFile *mf = open_file_impl(path, error);
|
||||
if (!error.empty())
|
||||
Fatal(ctx) << error;
|
||||
|
||||
if (mf)
|
||||
ctx.mf_pool.emplace_back(mf);
|
||||
return mf;
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
MappedFile *must_open_file(Context &ctx, std::string path) {
|
||||
MappedFile *mf = open_file(ctx, path);
|
||||
if (!mf)
|
||||
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
|
||||
return mf;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,23 +1,30 @@
|
||||
// This file implements a multi-threaded zlib compression routine.
|
||||
// This file implements a multi-threaded zlib and zstd compression
|
||||
// routine.
|
||||
//
|
||||
// Multiple pieces of raw compressed data in zlib-format can be merged
|
||||
// just by concatenation as long as each zlib stream is flushed with
|
||||
// Z_SYNC_FLUSH. In this file, we split input data into multiple
|
||||
// shards, compress them individually and concatenate them. We then
|
||||
// append a header, a trailer and a checksum so that the concatenated
|
||||
// data is valid zlib-format data.
|
||||
// zlib-compressed data can be merged just by concatenation as long as
|
||||
// each piece of data is flushed with Z_SYNC_FLUSH. In this file, we
|
||||
// split input data into multiple shards, compress them individually
|
||||
// and concatenate them. We then append a header, a trailer and a
|
||||
// checksum so that the concatenated data is valid zlib-format data.
|
||||
//
|
||||
// zstd-compressed data can be merged in the same way.
|
||||
//
|
||||
// Using threads to compress data has a downside. Since the dictionary
|
||||
// is reset on boundaries of shards, compression ratio is sacrificed
|
||||
// a little bit. However, if a shard size is large enough, that loss
|
||||
// is negligible in practice.
|
||||
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
#define CHECK(fn) do { int r = (fn); assert(r == Z_OK); } while (0)
|
||||
#define CHECK(fn) \
|
||||
do { \
|
||||
[[maybe_unused]] int r = (fn); \
|
||||
assert(r == Z_OK); \
|
||||
} while (0)
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -35,7 +42,7 @@ static std::vector<std::string_view> split(std::string_view input) {
|
||||
return shards;
|
||||
}
|
||||
|
||||
static std::vector<u8> do_compress(std::string_view input) {
|
||||
static std::vector<u8> zlib_compress(std::string_view input) {
|
||||
// Initialize zlib stream. Since debug info is generally compressed
|
||||
// pretty well with lower compression levels, we chose compression
|
||||
// level 1.
|
||||
@@ -85,11 +92,13 @@ static std::vector<u8> do_compress(std::string_view input) {
|
||||
|
||||
assert(strm.avail_out > 0);
|
||||
buf.resize(buf.size() - strm.avail_out);
|
||||
buf.shrink_to_fit();
|
||||
deflateEnd(&strm);
|
||||
return buf;
|
||||
}
|
||||
|
||||
ZlibCompressor::ZlibCompressor(std::string_view input) {
|
||||
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
|
||||
std::string_view input{(char *)buf, (size_t)size};
|
||||
std::vector<std::string_view> inputs = split(input);
|
||||
std::vector<u64> adlers(inputs.size());
|
||||
shards.resize(inputs.size());
|
||||
@@ -97,20 +106,18 @@ ZlibCompressor::ZlibCompressor(std::string_view input) {
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
adlers[i] = adler32(1, (u8 *)inputs[i].data(), inputs[i].size());
|
||||
shards[i] = do_compress(inputs[i]);
|
||||
shards[i] = zlib_compress(inputs[i]);
|
||||
});
|
||||
|
||||
// Combine checksums
|
||||
checksum = adlers[0];
|
||||
for (i64 i = 1; i < inputs.size(); i++)
|
||||
checksum = adler32_combine(checksum, adlers[i], inputs[i].size());
|
||||
}
|
||||
|
||||
i64 ZlibCompressor::size() const {
|
||||
i64 size = 2; // +2 for header
|
||||
for (const std::vector<u8> &shard : shards)
|
||||
size += shard.size();
|
||||
return size + 6; // +6 for trailer and checksum
|
||||
// Comput the total size
|
||||
compressed_size = 8; // the header and the trailer
|
||||
for (std::vector<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
void ZlibCompressor::write_to(u8 *buf) {
|
||||
@@ -125,68 +132,54 @@ void ZlibCompressor::write_to(u8 *buf) {
|
||||
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());
|
||||
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
|
||||
});
|
||||
|
||||
// Write a trailer
|
||||
u8 *end = buf + size();
|
||||
u8 *end = buf + compressed_size;
|
||||
end[-6] = 3;
|
||||
end[-5] = 0;
|
||||
|
||||
// Write a checksum
|
||||
*(ubig32 *)(end - 4) = checksum;
|
||||
*(ub32 *)(end - 4) = checksum;
|
||||
}
|
||||
|
||||
GzipCompressor::GzipCompressor(std::string_view input) {
|
||||
static std::vector<u8> zstd_compress(std::string_view input) {
|
||||
std::vector<u8> buf(ZSTD_COMPRESSBOUND(input.size()));
|
||||
constexpr int LEVEL = 3; // compression level; must be between 1 to 22
|
||||
|
||||
size_t sz = ZSTD_compress(buf.data(), buf.size(), input.data(), input.size(),
|
||||
LEVEL);
|
||||
assert(!ZSTD_isError(sz));
|
||||
buf.resize(sz);
|
||||
buf.shrink_to_fit();
|
||||
return buf;
|
||||
}
|
||||
|
||||
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size) {
|
||||
std::string_view input{(char *)buf, (size_t)size};
|
||||
std::vector<std::string_view> inputs = split(input);
|
||||
std::vector<u32> crc(inputs.size());
|
||||
shards.resize(inputs.size());
|
||||
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
crc[i] = crc32(0, (u8 *)inputs[i].data(), inputs[i].size());
|
||||
shards[i] = do_compress(inputs[i]);
|
||||
shards[i] = zstd_compress(inputs[i]);
|
||||
});
|
||||
|
||||
// Combine checksums
|
||||
checksum = crc[0];
|
||||
for (i64 i = 1; i < inputs.size(); i++)
|
||||
checksum = crc32_combine(checksum, crc[i], inputs[i].size());
|
||||
|
||||
uncompressed_size = input.size();
|
||||
compressed_size = 0;
|
||||
for (std::vector<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
i64 GzipCompressor::size() const {
|
||||
i64 size = 10; // +10 for header
|
||||
for (const std::vector<u8> &shard : shards)
|
||||
size += shard.size();
|
||||
return size + 10; // +10 for trailer and checksum
|
||||
}
|
||||
|
||||
void GzipCompressor::write_to(u8 *buf) {
|
||||
// Write a zlib-format header
|
||||
memset(buf, 0, 10);
|
||||
buf[0] = 0x1f; // magic
|
||||
buf[1] = 0x8b; // magic
|
||||
buf[2] = 0x08; // compression method is zlib
|
||||
buf[9] = 0xff; // made on unknown OS
|
||||
|
||||
void ZstdCompressor::write_to(u8 *buf) {
|
||||
// Copy compressed data
|
||||
std::vector<i64> offsets(shards.size());
|
||||
offsets[0] = 10; // +10 for header
|
||||
for (i64 i = 1; i < shards.size(); i++)
|
||||
offsets[i] = offsets[i - 1] + shards[i - 1].size();
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
|
||||
memcpy(&buf[offsets[i]], shards[i].data(), shards[i].size());
|
||||
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
|
||||
});
|
||||
|
||||
// Write a trailer
|
||||
u8 *end = buf + size();
|
||||
end[-10] = 0x3; // two-byte zlib stream terminator
|
||||
end[-9] = 0;
|
||||
*(u32 *)(end - 8) = checksum;
|
||||
*(u32 *)(end - 4) = uncompressed_size;
|
||||
}
|
||||
|
||||
} // 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 "common.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
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
#include "../third-party/rust-demangle/rust-demangle.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::optional<std::string_view> demangle_cpp(std::string_view name) {
|
||||
static thread_local char *buf;
|
||||
static thread_local size_t buflen;
|
||||
|
||||
// TODO(cwasser): Actually demangle Symbols on Windows using e.g.
|
||||
// `UnDecorateSymbolName` from Dbghelp, maybe even Itanium symbols?
|
||||
#ifndef _WIN32
|
||||
if (name.starts_with("_Z")) {
|
||||
int status;
|
||||
char *p = abi::__cxa_demangle(std::string(name).c_str(), buf, &buflen, &status);
|
||||
if (status == 0) {
|
||||
buf = p;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
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 "common.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
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This test script takes a Gentoo package name and tries to build it
|
||||
# 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:
|
||||
#
|
||||
# podman run --rm mold-gentoo emerge --color n -s '' | \
|
||||
# perl -ne 'next unless m!^\*\s+(\S+/\S+)!; print "$1\n"'
|
||||
|
||||
package="$1"
|
||||
|
||||
if [ "$package" = "" ]; then
|
||||
echo "Usage: $0 gentoo-package-name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -x
|
||||
|
||||
# Create a Podman image
|
||||
if ! podman image ls mold-gentoo | grep -q mold-gentoo; then
|
||||
set -e
|
||||
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 -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 && \
|
||||
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=$(./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 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'`
|
||||
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
|
||||
|
||||
$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
|
||||
|
||||
$podman chrt --idle 0 nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
|
||||
if [ $? = 0 ]; then
|
||||
mv "$dir"/"$filename".ld "$dir"/success
|
||||
else
|
||||
mv "$dir"/"$filename".ld "$dir"/failure
|
||||
fi
|
||||
+26
-21
@@ -1,10 +1,10 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
std::optional<GlobPattern> GlobPattern::compile(std::string_view pat) {
|
||||
std::optional<Glob> Glob::compile(std::string_view pat) {
|
||||
std::vector<Element> vec;
|
||||
|
||||
while (!pat.empty()) {
|
||||
@@ -19,9 +19,8 @@ std::optional<GlobPattern> GlobPattern::compile(std::string_view pat) {
|
||||
// [$\]!]: $, ] or !
|
||||
// [a-czg-i]: a, b, c, z, g, h, or i
|
||||
// [^a-z]: Any character except lowercase letters
|
||||
vec.push_back({BRACKET});
|
||||
std::vector<bool> &bitmap = vec.back().bitmap;
|
||||
bitmap.resize(256);
|
||||
vec.emplace_back(BRACKET);
|
||||
std::bitset<256> &bitset = vec.back().bitset;
|
||||
|
||||
bool negate = false;
|
||||
if (!pat.empty() && pat[0] == '^') {
|
||||
@@ -60,9 +59,9 @@ std::optional<GlobPattern> GlobPattern::compile(std::string_view pat) {
|
||||
return {};
|
||||
|
||||
for (i64 i = start; i <= end; i++)
|
||||
bitmap[i] = true;
|
||||
bitset[i] = true;
|
||||
} else {
|
||||
bitmap[(u8)pat[0]] = true;
|
||||
bitset[(u8)pat[0]] = true;
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
}
|
||||
@@ -71,39 +70,46 @@ std::optional<GlobPattern> GlobPattern::compile(std::string_view pat) {
|
||||
return {};
|
||||
|
||||
if (negate)
|
||||
for (i64 i = 0; i < 256; i++)
|
||||
bitmap[i] = !bitmap[i];
|
||||
bitset.flip();
|
||||
break;
|
||||
}
|
||||
case '?':
|
||||
vec.push_back({QUESTION});
|
||||
vec.emplace_back(QUESTION);
|
||||
break;
|
||||
case '*':
|
||||
vec.push_back({STAR});
|
||||
vec.emplace_back(STAR);
|
||||
break;
|
||||
case '\\':
|
||||
if (pat.empty())
|
||||
return {};
|
||||
if (vec.empty() || vec.back().kind != STRING)
|
||||
vec.emplace_back(STRING);
|
||||
vec.back().str += pat[0];
|
||||
pat = pat.substr(1);
|
||||
break;
|
||||
default:
|
||||
if (vec.empty() || vec.back().kind != STRING)
|
||||
vec.push_back({STRING});
|
||||
vec.emplace_back(STRING);
|
||||
vec.back().str += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {GlobPattern{std::move(vec)}};
|
||||
return {Glob{std::move(vec)}};
|
||||
}
|
||||
|
||||
bool GlobPattern::match(std::string_view str) {
|
||||
bool Glob::match(std::string_view str) {
|
||||
return do_match(str, elements);
|
||||
}
|
||||
|
||||
bool GlobPattern::do_match(std::string_view str, std::span<Element> elements) {
|
||||
bool Glob::do_match(std::string_view str, std::span<Element> elements) {
|
||||
while (!elements.empty()) {
|
||||
Element &e = elements[0];
|
||||
elements = elements.subspan(1);
|
||||
|
||||
switch (e.kind) {
|
||||
case STRING:
|
||||
if (str.empty() || !str.starts_with(e.str))
|
||||
if (!str.starts_with(e.str))
|
||||
return false;
|
||||
str = str.substr(e.str.size());
|
||||
break;
|
||||
@@ -118,13 +124,12 @@ bool GlobPattern::do_match(std::string_view str, std::span<Element> elements) {
|
||||
for (;;) {
|
||||
size_t pos = str.find(elements[0].str);
|
||||
if (pos == str.npos)
|
||||
break;
|
||||
return false;
|
||||
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.
|
||||
@@ -138,7 +143,7 @@ bool GlobPattern::do_match(std::string_view str, std::span<Element> elements) {
|
||||
str = str.substr(1);
|
||||
break;
|
||||
case BRACKET:
|
||||
if (str.empty() || !e.bitmap[str[0]])
|
||||
if (str.empty() || !e.bitset[str[0]])
|
||||
return false;
|
||||
str = str.substr(1);
|
||||
break;
|
||||
@@ -148,4 +153,4 @@ bool GlobPattern::do_match(std::string_view str, std::span<Element> elements) {
|
||||
return str.empty();
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -4,7 +4,7 @@
|
||||
// For more info, read
|
||||
// https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
|
||||
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace mold {
|
||||
i64 HyperLogLog::get_cardinality() const {
|
||||
double z = 0;
|
||||
for (i64 val : buckets)
|
||||
z += pow(2, -val);
|
||||
z += std::ldexp(1.0, -val);
|
||||
return ALPHA * NBUCKETS * NBUCKETS / z;
|
||||
}
|
||||
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
// 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
|
||||
// big-endian SPARC machine to create a little-endian RV64 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 member 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 don't depend on host byte order and
|
||||
// don't do unaligned access. Note that modern compilers are smart enough
|
||||
// to recognize shift and bitwise OR patterns and compile them into a
|
||||
// single load instruction.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#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, bool is_le, int size = sizeof(T)>
|
||||
class Integer {
|
||||
public:
|
||||
constexpr Integer() = default;
|
||||
|
||||
constexpr Integer(T x) requires (is_le && size == 2)
|
||||
: buf{(u8)x, (u8)(x >> 8)} {}
|
||||
|
||||
constexpr Integer(T x) requires (is_le && size == 3)
|
||||
: buf{(u8)x, (u8)(x >> 8), (u8)(x >> 16)} {}
|
||||
|
||||
constexpr Integer(T x) requires (is_le && size == 4)
|
||||
: buf{(u8)x, (u8)(x >> 8), (u8)(x >> 16), (u8)(x >> 24)} {}
|
||||
|
||||
constexpr Integer(T x) requires (is_le && size == 8)
|
||||
: buf{(u8)x, (u8)(x >> 8), (u8)(x >> 16), (u8)(x >> 24),
|
||||
(u8)(x >> 32), (u8)(x >> 40), (u8)(x >> 48), (u8)(x >> 56)} {}
|
||||
|
||||
constexpr Integer(T x) requires (!is_le && size == 2)
|
||||
: buf{(u8)(x >> 8), (u8)x} {}
|
||||
|
||||
constexpr Integer(T x) requires (!is_le && size == 3)
|
||||
: buf{(u8)(x >> 16), (u8)(x >> 8), (u8)x} {}
|
||||
|
||||
constexpr Integer(T x) requires (!is_le && size == 4)
|
||||
: buf{(u8)(x >> 24), (u8)(x >> 16), (u8)(x >> 8), (u8)x} {}
|
||||
|
||||
constexpr Integer(T x) requires (!is_le && size == 8)
|
||||
: buf{(u8)(x >> 56), (u8)(x >> 48), (u8)(x >> 40), (u8)(x >> 32),
|
||||
(u8)(x >> 24), (u8)(x >> 16), (u8)(x >> 8), (u8)x} {}
|
||||
|
||||
operator T() const {
|
||||
if constexpr (is_le) {
|
||||
if constexpr (size == 2)
|
||||
return buf[1] << 8 | buf[0];
|
||||
else if constexpr (size == 3)
|
||||
return buf[2] << 16 | buf[1] << 8 | buf[0];
|
||||
else if constexpr (size == 4)
|
||||
return buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0];
|
||||
else
|
||||
return (u64)buf[7] << 56 | (u64)buf[6] << 48 |
|
||||
(u64)buf[5] << 40 | (u64)buf[4] << 32 |
|
||||
(u64)buf[3] << 24 | (u64)buf[2] << 16 |
|
||||
(u64)buf[1] << 8 | (u64)buf[0];
|
||||
} else {
|
||||
if constexpr (size == 2)
|
||||
return buf[0] << 8 | buf[1];
|
||||
else if constexpr (size == 3)
|
||||
return buf[0] << 16 | buf[1] << 8 | buf[2];
|
||||
else if constexpr (size == 4)
|
||||
return buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
|
||||
else
|
||||
return (u64)buf[0] << 56 | (u64)buf[1] << 48 |
|
||||
(u64)buf[2] << 40 | (u64)buf[3] << 32 |
|
||||
(u64)buf[4] << 24 | (u64)buf[5] << 16 |
|
||||
(u64)buf[6] << 8 | (u64)buf[7];
|
||||
}
|
||||
}
|
||||
|
||||
Integer &operator=(T x) {
|
||||
new (this) Integer(x);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Integer &operator++() { return *this = *this + 1; }
|
||||
Integer operator++(int) { return ++*this - 1; }
|
||||
Integer &operator--() { return *this = *this - 1; }
|
||||
Integer operator--(int) { return --*this + 1; }
|
||||
Integer &operator+=(T x) { return *this = *this + x; }
|
||||
Integer &operator-=(T x) { return *this = *this - x; }
|
||||
Integer &operator&=(T x) { return *this = *this & x; }
|
||||
Integer &operator|=(T x) { return *this = *this | x; }
|
||||
|
||||
private:
|
||||
u8 buf[size];
|
||||
};
|
||||
|
||||
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,50 @@
|
||||
// Many build systems attempt to invoke as many linker processes as there
|
||||
// are cores, based on the assumption that the linker is single-threaded.
|
||||
// However, since mold is multi-threaded, such build systems' behavior is
|
||||
// not beneficial and just increases the overall peak memory usage.
|
||||
// On machines with limited memory, this could lead to an out-of-memory
|
||||
// error.
|
||||
//
|
||||
// This file implements a feature that limits the number of concurrent
|
||||
// mold processes to just 1 for each user. It is intended to be used as
|
||||
// `MOLD_JOBS=1 ninja` or `MOLD_JOBS=1 make -j$(nproc)`.
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
static int lock_fd = -1;
|
||||
|
||||
void acquire_global_lock() {
|
||||
char *jobs = getenv("MOLD_JOBS");
|
||||
if (!jobs || jobs != "1"s)
|
||||
return;
|
||||
|
||||
std::string path;
|
||||
if (char *dir = getenv("XDG_RUNTIME_DIR"))
|
||||
path = dir + "/mold-lock"s;
|
||||
else
|
||||
path = "/tmp/mold-lock-"s + getpwuid(getuid())->pw_name;
|
||||
|
||||
int fd = open(path.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0600);
|
||||
if (fd == -1)
|
||||
return;
|
||||
|
||||
if (lockf(fd, F_LOCK, 0) == -1)
|
||||
return;
|
||||
lock_fd = fd;
|
||||
}
|
||||
|
||||
void release_global_lock() {
|
||||
if (lock_fd != -1)
|
||||
close(lock_fd);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace mold {
|
||||
|
||||
void acquire_global_lock() {}
|
||||
void release_global_lock() {}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "common.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 "common.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
|
||||
@@ -0,0 +1,24 @@
|
||||
#include "common.h"
|
||||
|
||||
// Including mimalloc-new-delete.h overrides new/delete operators.
|
||||
// We need it only when we are using mimalloc as a dynamic library.
|
||||
#if MOLD_USE_SYSTEM_MIMALLOC
|
||||
# include <mimalloc-new-delete.h>
|
||||
#endif
|
||||
|
||||
// Silence mimalloc warning messages that users can just ignore.
|
||||
#if MOLD_USE_MIMALLOC
|
||||
# include <mimalloc.h>
|
||||
|
||||
namespace mold {
|
||||
void set_mimalloc_options() {
|
||||
mi_option_disable(mi_option_verbose);
|
||||
mi_option_disable(mi_option_show_errors);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
namespace mold {
|
||||
void set_mimalloc_options() {}
|
||||
}
|
||||
#endif
|
||||
@@ -1,65 +1,75 @@
|
||||
// This file implements the Aho-Corasick algorithm to match version
|
||||
// script patterns to symbol strings as quickly as possible.
|
||||
// This file implements the Aho-Corasick algorithm to match multiple
|
||||
// glob patterns to symbol strings as quickly as possible.
|
||||
//
|
||||
// Here are some examples of version script patterns:
|
||||
// Here are some examples of glob patterns:
|
||||
//
|
||||
// qt_private_api_tag*
|
||||
// *16QAccessibleCache*
|
||||
// *32QAbstractFileIconProviderPrivate*
|
||||
// *17QPixmapIconEngine*
|
||||
//
|
||||
// The pattern is a glob pattern, so `*` is a wildcard that matches
|
||||
// any substring. We sometimes have hundreds of version script
|
||||
// patterns and have to match them against millions of symbol strings.
|
||||
// `*` is a wildcard that matches any substring. We sometimes have
|
||||
// hundreds of glob patterns and have to match them against millions
|
||||
// of symbol strings.
|
||||
//
|
||||
// Aho-Corasick cannot handle complex patterns such as `*foo*bar*`.
|
||||
// We handle such patterns with GlobPattern. GlobPattern is relatively
|
||||
// slow, but complex patterns are rare in practice, so it should be OK.
|
||||
// We handle such patterns with the Glob class. Glob is relatively
|
||||
// slow, but complex patterns are rare in practice, so it should be
|
||||
// OK.
|
||||
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
std::optional<u16> VersionMatcher::find(std::string_view str) {
|
||||
u32 idx = UINT32_MAX;
|
||||
std::optional<i64> MultiGlob::find(std::string_view str) {
|
||||
std::call_once(once, [&] { compile(); });
|
||||
i64 val = -1;
|
||||
|
||||
if (root) {
|
||||
std::call_once(once_flag, [&] { compile(); });
|
||||
|
||||
// Match against simple glob patterns
|
||||
TrieNode *node = root.get();
|
||||
|
||||
auto walk = [&](u8 c) {
|
||||
for (;;) {
|
||||
if (node->children[c]) {
|
||||
node = node->children[c].get();
|
||||
idx = std::min(idx, node->value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node->suffix_link)
|
||||
return;
|
||||
node = node->suffix_link;
|
||||
}
|
||||
};
|
||||
|
||||
walk('\0');
|
||||
for (u8 c : str)
|
||||
walk(c);
|
||||
walk('\0');
|
||||
}
|
||||
// Match against simple glob patterns
|
||||
if (root)
|
||||
val = find_aho_corasick(str);
|
||||
|
||||
// Match against complex glob patterns
|
||||
for (std::pair<GlobPattern, u32> &glob : globs)
|
||||
for (std::pair<Glob, i64> &glob : globs)
|
||||
if (glob.first.match(str))
|
||||
idx = std::min(idx, glob.second);
|
||||
val = std::max(val, glob.second);
|
||||
|
||||
if (idx == UINT32_MAX)
|
||||
if (val == -1)
|
||||
return {};
|
||||
return versions[idx];
|
||||
return val;
|
||||
}
|
||||
|
||||
i64 MultiGlob::find_aho_corasick(std::string_view str) {
|
||||
TrieNode *node = root.get();
|
||||
i64 val = -1;
|
||||
|
||||
auto walk = [&](u8 c) {
|
||||
for (;;) {
|
||||
if (node->children[c]) {
|
||||
node = node->children[c].get();
|
||||
val = std::max(val, node->value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node->suffix_link)
|
||||
return;
|
||||
node = node->suffix_link;
|
||||
}
|
||||
};
|
||||
|
||||
walk('\0');
|
||||
|
||||
for (u8 c : str) {
|
||||
if (prefix_match && node == root.get())
|
||||
return val;
|
||||
walk(c);
|
||||
}
|
||||
|
||||
walk('\0');
|
||||
return val;
|
||||
}
|
||||
|
||||
static bool is_simple_pattern(std::string_view pat) {
|
||||
@@ -82,18 +92,16 @@ static std::string handle_stars(std::string_view pat) {
|
||||
return "\0"s + str + "\0"s;
|
||||
}
|
||||
|
||||
bool VersionMatcher::add(std::string_view pat, u16 ver) {
|
||||
assert(!compiled);
|
||||
bool MultiGlob::add(std::string_view pat, i64 val) {
|
||||
assert(!is_compiled);
|
||||
assert(!pat.empty());
|
||||
|
||||
u32 idx = strings.size();
|
||||
strings.push_back(std::string(pat));
|
||||
versions.push_back(ver);
|
||||
strings.emplace_back(pat);
|
||||
|
||||
// Complex glob pattern
|
||||
if (!is_simple_pattern(pat)) {
|
||||
if (std::optional<GlobPattern> glob = GlobPattern::compile(pat)) {
|
||||
globs.push_back({std::move(*glob), idx});
|
||||
if (std::optional<Glob> glob = Glob::compile(pat)) {
|
||||
globs.emplace_back(std::move(*glob), val);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -110,17 +118,29 @@ bool VersionMatcher::add(std::string_view pat, u16 ver) {
|
||||
node = node->children[c].get();
|
||||
}
|
||||
|
||||
node->value = std::min(node->value, idx);
|
||||
node->value = std::max(node->value, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VersionMatcher::compile() {
|
||||
fix_suffix_links(*root);
|
||||
fix_values();
|
||||
compiled = true;
|
||||
void MultiGlob::compile() {
|
||||
is_compiled = true;
|
||||
if (root) {
|
||||
fix_suffix_links(*root);
|
||||
fix_values();
|
||||
|
||||
// If no pattern starts with '*', set prefix_match to true.
|
||||
// We'll use this flag for optimization.
|
||||
prefix_match = true;
|
||||
for (i64 i = 1; i < 256; i++) {
|
||||
if (root->children[i]) {
|
||||
prefix_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VersionMatcher::fix_suffix_links(TrieNode &node) {
|
||||
void MultiGlob::fix_suffix_links(TrieNode &node) {
|
||||
for (i64 i = 0; i < 256; i++) {
|
||||
if (!node.children[i])
|
||||
continue;
|
||||
@@ -146,7 +166,7 @@ void VersionMatcher::fix_suffix_links(TrieNode &node) {
|
||||
}
|
||||
}
|
||||
|
||||
void VersionMatcher::fix_values() {
|
||||
void MultiGlob::fix_values() {
|
||||
std::queue<TrieNode *> queue;
|
||||
queue.push(root.get());
|
||||
|
||||
@@ -157,10 +177,10 @@ void VersionMatcher::fix_values() {
|
||||
for (std::unique_ptr<TrieNode> &child : node->children) {
|
||||
if (!child)
|
||||
continue;
|
||||
child->value = std::min(child->value, child->suffix_link->value);
|
||||
child->value = std::max(child->value, child->suffix_link->value);
|
||||
queue.push(child.get());
|
||||
}
|
||||
} while (!queue.empty());
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
+35
-25
@@ -1,10 +1,14 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <ios>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/resource.h>
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -13,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
|
||||
@@ -23,24 +25,33 @@ void Counter::print() {
|
||||
}
|
||||
|
||||
static i64 now_nsec() {
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
return (i64)t.tv_sec * 1000000000 + t.tv_nsec;
|
||||
return (i64)std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
}
|
||||
|
||||
static i64 to_nsec(struct timeval t) {
|
||||
return (i64)t.tv_sec * 1000000000 + t.tv_usec * 1000;
|
||||
static std::pair<i64, i64> get_usage() {
|
||||
#ifdef _WIN32
|
||||
auto to_nsec = [](FILETIME t) -> i64 {
|
||||
return (((u64)t.dwHighDateTime << 32) + (u64)t.dwLowDateTime) * 100;
|
||||
};
|
||||
|
||||
FILETIME creation, exit, kernel, user;
|
||||
GetProcessTimes(GetCurrentProcess(), &creation, &exit, &kernel, &user);
|
||||
return {to_nsec(user), to_nsec(kernel)};
|
||||
#else
|
||||
auto to_nsec = [](struct timeval t) -> i64 {
|
||||
return (i64)t.tv_sec * 1'000'000'000 + t.tv_usec * 1'000;
|
||||
};
|
||||
|
||||
struct rusage ru;
|
||||
getrusage(RUSAGE_SELF, &ru);
|
||||
return {to_nsec(ru.ru_utime), to_nsec(ru.ru_stime)};
|
||||
#endif
|
||||
}
|
||||
|
||||
TimerRecord::TimerRecord(std::string name, TimerRecord *parent)
|
||||
: name(name), parent(parent) {
|
||||
struct rusage usage;
|
||||
getrusage(RUSAGE_SELF, &usage);
|
||||
|
||||
start = now_nsec();
|
||||
user = to_nsec(usage.ru_utime);
|
||||
sys = to_nsec(usage.ru_stime);
|
||||
|
||||
std::tie(user, sys) = get_usage();
|
||||
if (parent)
|
||||
parent->children.push_back(this);
|
||||
}
|
||||
@@ -50,25 +61,24 @@ void TimerRecord::stop() {
|
||||
return;
|
||||
stopped = true;
|
||||
|
||||
struct rusage usage;
|
||||
getrusage(RUSAGE_SELF, &usage);
|
||||
i64 user2;
|
||||
i64 sys2;
|
||||
std::tie(user2, sys2) = get_usage();
|
||||
|
||||
end = now_nsec();
|
||||
user = to_nsec(usage.ru_utime) - user;
|
||||
sys = to_nsec(usage.ru_stime) - sys;
|
||||
user = user2 - user;
|
||||
sys = sys2 - sys;
|
||||
}
|
||||
|
||||
static void print_rec(TimerRecord &rec, i64 indent) {
|
||||
printf(" % 8.3f % 8.3f % 8.3f %s%s\n",
|
||||
((double)rec.user / 1000000000),
|
||||
((double)rec.sys / 1000000000),
|
||||
(((double)rec.end - rec.start) / 1000000000),
|
||||
((double)rec.user / 1'000'000'000),
|
||||
((double)rec.sys / 1'000'000'000),
|
||||
(((double)rec.end - rec.start) / 1'000'000'000),
|
||||
std::string(indent * 2, ' ').c_str(),
|
||||
rec.name.c_str());
|
||||
|
||||
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 "common.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
|
||||
@@ -0,0 +1,88 @@
|
||||
#include "common.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 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.
|
||||
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 "common.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
|
||||
+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>;
|
||||
+37
-32
@@ -1,7 +1,15 @@
|
||||
#include "mold.h"
|
||||
// This file contains functions to create a tar file.
|
||||
|
||||
#include "common.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,23 +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];
|
||||
assert(sum < 1000000);
|
||||
sprintf(checksum, "%06o", sum);
|
||||
}
|
||||
|
||||
char name[100];
|
||||
char mode[8];
|
||||
char uid[8];
|
||||
@@ -47,6 +38,25 @@ struct UstarHeader {
|
||||
char pad[12];
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
@@ -73,13 +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);
|
||||
sprintf(pax.size, "%011zo", attr.size());
|
||||
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
|
||||
@@ -87,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);
|
||||
sprintf(ustar.size, "%011zo", data.size());
|
||||
ustar.finalize();
|
||||
snprintf(ustar.size, sizeof(ustar.size), "%011zo", data.size());
|
||||
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
|
||||
@@ -0,0 +1,33 @@
|
||||
# Get a git hash value. We do not want to use git command here
|
||||
# because we don't want to make git a build-time dependency.
|
||||
if(EXISTS "${SOURCE_DIR}/.git/HEAD")
|
||||
file(READ "${SOURCE_DIR}/.git/HEAD" HASH)
|
||||
string(STRIP "${HASH}" HASH)
|
||||
|
||||
if(HASH MATCHES "^ref: (.*)")
|
||||
set(HEAD "${CMAKE_MATCH_1}")
|
||||
if(EXISTS "${SOURCE_DIR}/.git/${HEAD}")
|
||||
file(READ "${SOURCE_DIR}/.git/${HEAD}" HASH)
|
||||
string(STRIP "${HASH}" HASH)
|
||||
else()
|
||||
file(READ "${SOURCE_DIR}/.git/packed-refs" PACKED_REFS)
|
||||
string(REGEX REPLACE ".*\n([0-9a-f]+) ${HEAD}\n.*" "\\1" HASH "\n${PACKED_REFS}")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Create new file contents and update a given file if necessary.
|
||||
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)
|
||||
if(NOT "${NEW_CONTENTS}" STREQUAL "${OLD_CONTENTS}")
|
||||
file(WRITE "${OUTPUT_FILE}" "${NEW_CONTENTS}")
|
||||
endif()
|
||||
else()
|
||||
file(WRITE "${OUTPUT_FILE}" "${NEW_CONTENTS}")
|
||||
endif()
|
||||
@@ -1,258 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u64 bits(u64 val, u64 hi, u64 lo) {
|
||||
return (val >> lo) & (((u64)1 << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
static u64 page(u64 val) {
|
||||
return val & 0xffff'ffff'ffff'f000;
|
||||
}
|
||||
|
||||
static u64 encode_page(u64 val) {
|
||||
return (bits(val, 13, 12) << 29) | (bits(val, 32, 14) << 5);
|
||||
}
|
||||
|
||||
template <>
|
||||
void StubsSection<ARM64>::copy_buf(Context<ARM64> &ctx) {
|
||||
u32 *buf = (u32 *)(ctx.buf + this->hdr.offset);
|
||||
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
static const u32 insn[] = {
|
||||
0x90000010, // adrp x16, $ptr@PAGE
|
||||
0xf9400210, // ldr x16, [x16, $ptr@PAGEOFF]
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == ARM64::stub_size);
|
||||
|
||||
u64 la_addr = ctx.lazy_symbol_ptr.hdr.addr + ARM64::word_size * i;
|
||||
u64 this_addr = this->hdr.addr + ARM64::stub_size * i;
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
buf[0] |= encode_page(page(la_addr) - page(this_addr));
|
||||
buf[1] |= bits(la_addr, 11, 3) << 10;
|
||||
buf += 3;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void StubHelperSection<ARM64>::copy_buf(Context<ARM64> &ctx) {
|
||||
u32 *start = (u32 *)(ctx.buf + this->hdr.offset);
|
||||
u32 *buf = start;
|
||||
|
||||
static const u32 insn0[] = {
|
||||
0x90000011, // adrp x17, $__dyld_private@PAGE
|
||||
0x91000231, // add x17, x17, $__dyld_private@PAGEOFF
|
||||
0xa9bf47f0, // stp x16, x17, [sp, #-16]!
|
||||
0x90000010, // adrp x16, $dyld_stub_binder@PAGE
|
||||
0xf9400210, // ldr x16, [x16, $dyld_stub_binder@PAGEOFF]
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn0) == ARM64::stub_helper_hdr_size);
|
||||
memcpy(buf, insn0, sizeof(insn0));
|
||||
|
||||
u64 dyld_private = get_symbol(ctx, "__dyld_private")->get_addr(ctx);
|
||||
buf[0] |= encode_page(page(dyld_private) - page(this->hdr.addr));
|
||||
buf[1] |= bits(dyld_private, 11, 0) << 10;
|
||||
|
||||
u64 stub_binder = get_symbol(ctx, "dyld_stub_binder")->get_addr(ctx);
|
||||
buf[3] |= encode_page(page(stub_binder) - page(this->hdr.addr - 12));
|
||||
buf[4] |= bits(stub_binder, 11, 0) << 10;
|
||||
|
||||
buf += 6;
|
||||
|
||||
for (i64 i = 0; i < ctx.stubs.syms.size(); i++) {
|
||||
static const u32 insn[] = {
|
||||
0x18000050, // ldr w16, addr
|
||||
0x14000000, // b stubHelperHeader
|
||||
0x00000000, // addr: .long <idx>
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == ARM64::stub_helper_size);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
buf[1] |= bits((start - buf - 1) * 4, 27, 2);
|
||||
buf[2] = ctx.stubs.bind_offsets[i];
|
||||
buf += 3;
|
||||
}
|
||||
}
|
||||
|
||||
static i64 read_addend(u8 *buf, const MachRel &r) {
|
||||
if (r.p2size == 2)
|
||||
return *(i32 *)(buf + r.offset);
|
||||
if (r.p2size == 3)
|
||||
return *(i64 *)(buf + r.offset);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
static Relocation<ARM64>
|
||||
read_reloc(Context<ARM64> &ctx, ObjectFile<ARM64> &file,
|
||||
const MachSection &hdr, MachRel *rels, i64 &idx) {
|
||||
i64 addend = 0;
|
||||
|
||||
switch (rels[idx].type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_SUBTRACTOR:
|
||||
addend = read_addend((u8 *)file.mf->data + hdr.offset, rels[idx]);
|
||||
break;
|
||||
case ARM64_RELOC_ADDEND:
|
||||
addend = rels[idx++].offset;
|
||||
break;
|
||||
}
|
||||
|
||||
MachRel &r = rels[idx];
|
||||
Relocation<ARM64> rel{r.offset, (u8)r.type, (u8)r.p2size, (bool)r.is_pcrel};
|
||||
|
||||
if (r.is_extern) {
|
||||
rel.sym = file.syms[r.idx];
|
||||
rel.addend = addend;
|
||||
return rel;
|
||||
}
|
||||
|
||||
u32 addr;
|
||||
if (r.is_pcrel)
|
||||
addr = hdr.addr + r.offset + addend;
|
||||
else
|
||||
addr = addend;
|
||||
|
||||
Subsection<ARM64> *target = file.find_subsection(ctx, addr);
|
||||
if (!target)
|
||||
Fatal(ctx) << file << ": bad relocation: " << r.offset;
|
||||
|
||||
rel.subsec = target;
|
||||
rel.addend = addr - target->input_addr;
|
||||
return rel;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::vector<Relocation<ARM64>>
|
||||
read_relocations(Context<ARM64> &ctx, ObjectFile<ARM64> &file,
|
||||
const MachSection &hdr) {
|
||||
std::vector<Relocation<ARM64>> vec;
|
||||
|
||||
MachRel *rels = (MachRel *)(file.mf->data + hdr.reloff);
|
||||
for (i64 i = 0; i < hdr.nreloc; i++)
|
||||
vec.push_back(read_reloc(ctx, file, hdr, rels, i));
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<ARM64>::scan_relocations(Context<ARM64> &ctx) {
|
||||
for (Relocation<ARM64> &r : get_rels()) {
|
||||
Symbol<ARM64> *sym = r.sym;
|
||||
if (!sym)
|
||||
continue;
|
||||
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
sym->flags |= NEEDS_GOT;
|
||||
break;
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12:
|
||||
sym->flags |= NEEDS_THREAD_PTR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sym->file && sym->file->is_dylib()) {
|
||||
sym->flags |= NEEDS_STUB;
|
||||
((DylibFile<ARM64> *)sym->file)->is_needed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<ARM64>::apply_reloc(Context<ARM64> &ctx, u8 *buf) {
|
||||
std::span<Relocation<ARM64>> rels = get_rels();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
Relocation<ARM64> &r = rels[i];
|
||||
|
||||
if (r.sym && !r.sym->file) {
|
||||
Error(ctx) << "undefined symbol: " << isec.file << ": " << *r.sym;
|
||||
continue;
|
||||
}
|
||||
|
||||
u64 val = 0;
|
||||
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_BRANCH26:
|
||||
case ARM64_RELOC_PAGE21:
|
||||
case ARM64_RELOC_PAGEOFF12:
|
||||
val = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
break;
|
||||
case ARM64_RELOC_SUBTRACTOR: {
|
||||
Relocation<ARM64> s = rels[++i];
|
||||
assert(s.type == ARM64_RELOC_UNSIGNED);
|
||||
u64 val1 = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
u64 val2 = s.sym ? s.sym->get_addr(ctx) : s.subsec->get_addr(ctx);
|
||||
val = val2 - val1;
|
||||
break;
|
||||
}
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
val = r.sym->get_got_addr(ctx);
|
||||
break;
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12:
|
||||
val = r.sym->get_tlv_addr(ctx);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
|
||||
}
|
||||
|
||||
val += r.addend;
|
||||
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_SUBTRACTOR:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
if (r.is_pcrel)
|
||||
val -= get_addr(ctx) + r.offset;
|
||||
|
||||
if (r.p2size == 2)
|
||||
*(i32 *)(buf + r.offset) = val;
|
||||
else if (r.p2size == 3)
|
||||
*(i64 *)(buf + r.offset) = val;
|
||||
else
|
||||
unreachable();
|
||||
break;
|
||||
case ARM64_RELOC_BRANCH26:
|
||||
if (r.is_pcrel)
|
||||
val -= get_addr(ctx) + r.offset;
|
||||
*(u32 *)(buf + r.offset) |= bits(val, 27, 2);
|
||||
break;
|
||||
case ARM64_RELOC_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21:
|
||||
assert(r.is_pcrel);
|
||||
*(u32 *)(buf + r.offset) |=
|
||||
encode_page(page(val) - page(get_addr(ctx) + r.offset));
|
||||
break;
|
||||
case ARM64_RELOC_PAGEOFF12:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12: {
|
||||
if (r.is_pcrel)
|
||||
val -= get_addr(ctx) + r.offset;
|
||||
|
||||
u32 insn = *(u32 *)(buf + r.offset);
|
||||
i64 scale = 0;
|
||||
if ((insn & 0x3b000000) == 0x39000000)
|
||||
scale = insn >> 30;
|
||||
*(u32 *)(buf + r.offset) |= bits(val, 11, scale) << 10;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -1,222 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <>
|
||||
void StubsSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
u8 *buf = ctx.buf + this->hdr.offset;
|
||||
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
// `ff 25 xx xx xx xx` is a RIP-relative indirect jump instruction,
|
||||
// i.e., `jmp *IMM(%rip)`. It loads an address from la_symbol_ptr
|
||||
// and jump there.
|
||||
static_assert(X86_64::stub_size == 6);
|
||||
buf[i * 6] = 0xff;
|
||||
buf[i * 6 + 1] = 0x25;
|
||||
*(u32 *)(buf + i * 6 + 2) =
|
||||
ctx.lazy_symbol_ptr.hdr.addr + i * X86_64::word_size -
|
||||
(this->hdr.addr + i * 6 + 6);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void StubHelperSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
u8 *start = ctx.buf + this->hdr.offset;
|
||||
u8 *buf = start;
|
||||
|
||||
static const u8 insn0[] = {
|
||||
0x4c, 0x8d, 0x1d, 0, 0, 0, 0, // lea $__dyld_private(%rip), %r11
|
||||
0x41, 0x53, // push %r11
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *$dyld_stub_binder@GOT(%rip)
|
||||
0x90, // nop
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn0) == X86_64::stub_helper_hdr_size);
|
||||
|
||||
memcpy(buf, insn0, sizeof(insn0));
|
||||
*(u32 *)(buf + 3) =
|
||||
get_symbol(ctx, "__dyld_private")->get_addr(ctx) - this->hdr.addr - 7;
|
||||
*(u32 *)(buf + 11) =
|
||||
get_symbol(ctx, "dyld_stub_binder")->get_got_addr(ctx) - this->hdr.addr - 15;
|
||||
|
||||
buf += 16;
|
||||
|
||||
for (i64 i = 0; i < ctx.stubs.syms.size(); i++) {
|
||||
u8 insn[] = {
|
||||
0x68, 0, 0, 0, 0, // push $bind_offset
|
||||
0xe9, 0, 0, 0, 0, // jmp $__stub_helper
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == X86_64::stub_helper_size);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(u32 *)(buf + 1) = ctx.stubs.bind_offsets[i];
|
||||
*(u32 *)(buf + 6) = start - buf - 10;
|
||||
buf += 10;
|
||||
}
|
||||
}
|
||||
|
||||
static i64 get_reloc_addend(u32 type) {
|
||||
switch (type) {
|
||||
case X86_64_RELOC_SIGNED_1:
|
||||
return 1;
|
||||
case X86_64_RELOC_SIGNED_2:
|
||||
return 2;
|
||||
case X86_64_RELOC_SIGNED_4:
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static i64 read_addend(u8 *buf, const MachRel &r) {
|
||||
if (r.p2size == 2)
|
||||
return *(i32 *)(buf + r.offset) + get_reloc_addend(r.type);
|
||||
if (r.p2size == 3)
|
||||
return *(i64 *)(buf + r.offset) + get_reloc_addend(r.type);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
static Relocation<X86_64>
|
||||
read_reloc(Context<X86_64> &ctx, ObjectFile<X86_64> &file,
|
||||
const MachSection &hdr, MachRel &r) {
|
||||
if (r.p2size != 2 && r.p2size != 3)
|
||||
Fatal(ctx) << file << ": invalid r.p2size: " << (u32)r.p2size;
|
||||
|
||||
if (r.is_pcrel) {
|
||||
if (r.p2size != 2)
|
||||
Fatal(ctx) << file << ": invalid PC-relative reloc: " << r.offset;
|
||||
} else {
|
||||
if (r.p2size != 3)
|
||||
Fatal(ctx) << file << ": invalid non-PC-relative reloc: " << r.offset;
|
||||
}
|
||||
|
||||
u8 *buf = (u8 *)file.mf->data + hdr.offset;
|
||||
Relocation<X86_64> rel{r.offset, (u8)r.type, (u8)r.p2size, (bool)r.is_pcrel};
|
||||
i64 addend = read_addend(buf, r);
|
||||
|
||||
if (r.is_extern) {
|
||||
rel.sym = file.syms[r.idx];
|
||||
rel.addend = addend;
|
||||
return rel;
|
||||
}
|
||||
|
||||
u32 addr;
|
||||
if (r.is_pcrel)
|
||||
addr = hdr.addr + r.offset + 4 + addend;
|
||||
else
|
||||
addr = addend;
|
||||
|
||||
Subsection<X86_64> *target = file.find_subsection(ctx, addr);
|
||||
if (!target)
|
||||
Fatal(ctx) << file << ": bad relocation: " << r.offset;
|
||||
|
||||
rel.subsec = target;
|
||||
rel.addend = addr - target->input_addr;
|
||||
return rel;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::vector<Relocation<X86_64>>
|
||||
read_relocations(Context<X86_64> &ctx, ObjectFile<X86_64> &file,
|
||||
const MachSection &hdr) {
|
||||
std::vector<Relocation<X86_64>> vec;
|
||||
vec.reserve(hdr.nreloc);
|
||||
|
||||
MachRel *rels = (MachRel *)(file.mf->data + hdr.reloff);
|
||||
for (i64 i = 0; i < hdr.nreloc; i++)
|
||||
vec.push_back(read_reloc(ctx, file, hdr, rels[i]));
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<X86_64>::scan_relocations(Context<X86_64> &ctx) {
|
||||
for (Relocation<X86_64> &r : get_rels()) {
|
||||
Symbol<X86_64> *sym = r.sym;
|
||||
if (!sym)
|
||||
continue;
|
||||
|
||||
switch (r.type) {
|
||||
case X86_64_RELOC_GOT_LOAD:
|
||||
if (sym->file && sym->file->is_dylib())
|
||||
sym->flags |= NEEDS_GOT;
|
||||
break;
|
||||
case X86_64_RELOC_GOT:
|
||||
sym->flags |= NEEDS_GOT;
|
||||
break;
|
||||
case X86_64_RELOC_TLV:
|
||||
if (sym->file && sym->file->is_dylib())
|
||||
sym->flags |= NEEDS_THREAD_PTR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sym->file && sym->file->is_dylib()) {
|
||||
sym->flags |= NEEDS_STUB;
|
||||
((DylibFile<X86_64> *)sym->file)->is_needed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<X86_64>::apply_reloc(Context<X86_64> &ctx, u8 *buf) {
|
||||
for (const Relocation<X86_64> &r : get_rels()) {
|
||||
if (r.sym && !r.sym->file) {
|
||||
Error(ctx) << "undefined symbol: " << isec.file << ": " << *r.sym;
|
||||
continue;
|
||||
}
|
||||
|
||||
u64 val = 0;
|
||||
|
||||
switch (r.type) {
|
||||
case X86_64_RELOC_UNSIGNED:
|
||||
case X86_64_RELOC_SIGNED:
|
||||
case X86_64_RELOC_BRANCH:
|
||||
case X86_64_RELOC_SIGNED_1:
|
||||
case X86_64_RELOC_SIGNED_2:
|
||||
case X86_64_RELOC_SIGNED_4:
|
||||
val = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
break;
|
||||
case X86_64_RELOC_GOT_LOAD:
|
||||
if (r.sym->got_idx != -1) {
|
||||
val = r.sym->get_got_addr(ctx);
|
||||
} else {
|
||||
// Relax MOVQ into LEAQ
|
||||
if (buf[r.offset - 2] != 0x8b)
|
||||
Error(ctx) << isec << ": invalid GOT_LOAD relocation";
|
||||
buf[r.offset - 2] = 0x8d;
|
||||
val = r.sym->get_addr(ctx);
|
||||
}
|
||||
break;
|
||||
case X86_64_RELOC_GOT:
|
||||
val = r.sym->get_got_addr(ctx);
|
||||
break;
|
||||
case X86_64_RELOC_TLV:
|
||||
if (r.sym->tlv_idx != -1) {
|
||||
val = r.sym->get_tlv_addr(ctx);
|
||||
} else {
|
||||
// Relax MOVQ into LEAQ
|
||||
if (buf[r.offset - 2] != 0x8b)
|
||||
Error(ctx) << isec << ": invalid TLV relocation";
|
||||
buf[r.offset - 2] = 0x8d;
|
||||
val = r.sym->get_addr(ctx);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
|
||||
}
|
||||
|
||||
val += r.addend;
|
||||
|
||||
if (r.is_pcrel)
|
||||
val -= get_addr(ctx) + r.offset + 4 + get_reloc_addend(r.type);
|
||||
|
||||
if (r.p2size == 2)
|
||||
*(u32 *)(buf + r.offset) = val;
|
||||
else if (r.p2size == 3)
|
||||
*(u64 *)(buf + r.offset) = val;
|
||||
else
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -1,322 +0,0 @@
|
||||
#include "mold.h"
|
||||
#include "../cmdline.h"
|
||||
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <regex>
|
||||
#include <unistd.h>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
static const char helpmsg[] = R"(
|
||||
Options:
|
||||
-F<PATH> Add DIR to framework search path
|
||||
-L<PATH> Add DIR to library search path
|
||||
-Z Do not search the standard directories when
|
||||
searching for libraries and frameworks
|
||||
-ObjC Load all static archive members that implement
|
||||
an Objective-C class or category
|
||||
-adhoc_codesign Add ad-hoc code signature to the output file
|
||||
-no_adhoc_codesign
|
||||
-arch <ARCH_NAME> Specify target architecture
|
||||
-bundle Produce a mach-o bundle
|
||||
-dead_strip Remove unreachable functions and data
|
||||
-dead_strip_dylibs Remove unreachable dylibs from dependencies
|
||||
-demangle Demangle C++ symbols in log messages (default)
|
||||
-dylib Produce a dynamic library
|
||||
-dynamic Link against dylibs (default)
|
||||
-e <SYMBOL> Specify the entry point of a main executable
|
||||
-execute Produce an executable (default)
|
||||
-filelist <FILE>[,<DIR>] Specify the list of input file names
|
||||
-framework <NAME>,[,<SUFFIX>]
|
||||
Search for a given framework
|
||||
-headerpad <SIZE> Allocate the size of padding after load commands
|
||||
-headerpad_max_install_names
|
||||
Allocate MAXPATHLEN byte padding after load commands
|
||||
-help Report usage information
|
||||
-l<LIB> Search for a given library
|
||||
-lto_library <FILE> Ignored
|
||||
-map <FILE> Write map file to a given file
|
||||
-needed-l<LIB> Search for a given library
|
||||
-needed-framework <NAME>[,<SUFFIX>]
|
||||
Search for a given framework
|
||||
-no_deduplicate Ignored
|
||||
-o <FILE> Set output filename
|
||||
-pagezero_size <SIZE> Specify the size of the __PAGEZERO segment
|
||||
-platform_version <PLATFORM> <MIN_VERSION> <SDK_VERSION>
|
||||
Set platform, platform version and SDK version
|
||||
-rpath <PATH> Add PATH to the runpath search path list
|
||||
-syslibroot <DIR> Prepend DIR to library search paths
|
||||
-t Print out each file the linker loads
|
||||
-v Report version information)";
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_platform(Context<E> &ctx, std::string_view arg) {
|
||||
static std::regex re(R"(\d+)", std::regex_constants::ECMAScript);
|
||||
if (std::regex_match(arg.begin(), arg.end(), re))
|
||||
return stoi(std::string(arg));
|
||||
|
||||
if (arg == "macos")
|
||||
return PLATFORM_MACOS;
|
||||
if (arg == "ios")
|
||||
return PLATFORM_IOS;
|
||||
if (arg == "tvos")
|
||||
return PLATFORM_TVOS;
|
||||
if (arg == "watchos")
|
||||
return PLATFORM_WATCHOS;
|
||||
if (arg == "bridgeos")
|
||||
return PLATFORM_BRIDGEOS;
|
||||
if (arg == "mac-catalyst")
|
||||
return PLATFORM_MACCATALYST;
|
||||
if (arg == "ios-simulator")
|
||||
return PLATFORM_IOSSIMULATOR;
|
||||
if (arg == "tvos-simulator")
|
||||
return PLATFORM_TVOSSIMULATOR;
|
||||
if (arg == "watchos-simulator")
|
||||
return PLATFORM_WATCHOSSIMULATOR;
|
||||
if (arg == "driverkit")
|
||||
return PLATFORM_DRIVERKIT;
|
||||
Fatal(ctx) << "unknown -platform_version name: " << arg;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_version(Context<E> &ctx, std::string_view arg) {
|
||||
static std::regex re(R"((\d+)(?:\.(\d+))?(?:\.(\d+))?)",
|
||||
std::regex_constants::ECMAScript);
|
||||
std::cmatch m;
|
||||
if (!std::regex_match(arg.begin(), arg.end(), m, re))
|
||||
Fatal(ctx) << "malformed version number: " << arg;
|
||||
|
||||
i64 major = (m[1].length() == 0) ? 0 : stoi(m[1]);
|
||||
i64 minor = (m[2].length() == 0) ? 0 : stoi(m[2]);
|
||||
i64 patch = (m[3].length() == 0) ? 0 : stoi(m[3]);
|
||||
return (major << 16) | (minor << 8) | patch;
|
||||
}
|
||||
|
||||
static bool is_directory(std::filesystem::path path) {
|
||||
std::error_code ec;
|
||||
return std::filesystem::is_directory(path, ec) && !ec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_nonpositional_args(Context<E> &ctx,
|
||||
std::vector<std::string> &remaining) {
|
||||
std::vector<std::string_view> &args = ctx.cmdline_args;
|
||||
i64 i = 1;
|
||||
|
||||
std::vector<std::string> framework_paths;
|
||||
std::vector<std::string> library_paths;
|
||||
bool nostdlib = false;
|
||||
std::optional<i64> pagezero_size;
|
||||
|
||||
while (i < args.size()) {
|
||||
std::string_view arg;
|
||||
std::string_view arg2;
|
||||
std::string_view arg3;
|
||||
|
||||
auto read_arg = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
if (args.size() <= i + 1)
|
||||
Fatal(ctx) << "option -" << name << ": argument missing";
|
||||
arg = args[i + 1];
|
||||
i += 2;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_arg3 = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
if (args.size() <= i + 3)
|
||||
Fatal(ctx) << "option -" << name << ": argument missing";
|
||||
arg = args[i + 1];
|
||||
arg2 = args[i + 2];
|
||||
arg3 = args[i + 3];
|
||||
i += 4;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_joined = [&](std::string name) {
|
||||
if (read_arg(name))
|
||||
return true;
|
||||
if (args[i].starts_with(name)) {
|
||||
arg = args[i].substr(name.size());
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_flag = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (args[i].starts_with('@')) {
|
||||
std::vector<std::string_view> vec =
|
||||
read_response_file(ctx, args[i].substr(1));
|
||||
args.erase(args.begin() + i);
|
||||
args.insert(args.begin() + i, vec.begin(), vec.end());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read_flag("-help") || read_flag("--help")) {
|
||||
SyncOut(ctx) << "Usage: " << ctx.cmdline_args[i]
|
||||
<< " [options] file...\n" << helpmsg;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (read_joined("-F")) {
|
||||
framework_paths.push_back(std::string(arg));
|
||||
} else if (read_joined("-L")) {
|
||||
library_paths.push_back(std::string(arg));
|
||||
} else if (read_flag("-Z")) {
|
||||
nostdlib = true;
|
||||
} else if (read_flag("-ObjC")) {
|
||||
ctx.arg.ObjC = true;
|
||||
} else if (read_flag("-adhoc_codesign")) {
|
||||
ctx.arg.adhoc_codesign = true;
|
||||
} else if (read_flag("-no_adhoc_codesign")) {
|
||||
ctx.arg.adhoc_codesign = false;
|
||||
} else if (read_arg("-arch")) {
|
||||
if (arg == "x86_64")
|
||||
ctx.arg.arch = CPU_TYPE_X86_64;
|
||||
else if (arg == "arm64")
|
||||
ctx.arg.arch = CPU_TYPE_ARM64;
|
||||
else
|
||||
Fatal(ctx) << "unknown -arch: " << arg;
|
||||
} else if (read_flag("-bundle")) {
|
||||
ctx.output_type = MH_BUNDLE;
|
||||
} else if (read_flag("-color-diagnostics") ||
|
||||
read_flag("--color-diagnostics")) {
|
||||
} else if (read_flag("-dead_strip")) {
|
||||
ctx.arg.dead_strip = true;
|
||||
} else if (read_flag("-dead_strip_dylibs")) {
|
||||
ctx.arg.dead_strip_dylibs = true;
|
||||
} else if (read_flag("-demangle")) {
|
||||
ctx.arg.demangle = true;
|
||||
} else if (read_flag("-dylib")) {
|
||||
ctx.output_type = MH_DYLIB;
|
||||
} else if (read_arg("-headerpad")) {
|
||||
size_t pos;
|
||||
ctx.arg.headerpad = std::stol(std::string(arg), &pos, 16);
|
||||
if (pos != arg.size())
|
||||
Fatal(ctx) << "malformed -headerpad: " << arg;
|
||||
} else if (read_flag("-headerpad_max_install_names")) {
|
||||
ctx.arg.headerpad = 1024;
|
||||
} else if (read_flag("-dynamic")) {
|
||||
ctx.arg.dynamic = true;
|
||||
} else if (read_arg("-e")) {
|
||||
ctx.arg.entry = arg;
|
||||
} else if (read_flag("-execute")) {
|
||||
ctx.output_type = MH_EXECUTE;
|
||||
} else if (read_arg("-fatal_warnings")) {
|
||||
} else if (read_arg("-filelist")) {
|
||||
remaining.push_back("-filelist");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-framework")) {
|
||||
remaining.push_back("-framework");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-lto_library")) {
|
||||
} else if (read_joined("-l")) {
|
||||
remaining.push_back("-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-map")) {
|
||||
ctx.arg.map = arg;
|
||||
} else if (read_joined("-needed-l")) {
|
||||
remaining.push_back("-needed-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-needed_framework")) {
|
||||
remaining.push_back("-needed_framework");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_flag("-no_deduplicate")) {
|
||||
} else if (read_arg("-o")) {
|
||||
ctx.arg.output = arg;
|
||||
} else if (read_arg("-pagezero_size")) {
|
||||
size_t pos;
|
||||
pagezero_size = std::stol(std::string(arg), &pos, 16);
|
||||
if (pos != arg.size())
|
||||
Fatal(ctx) << "malformed -pagezero_size: " << arg;
|
||||
} else if (read_arg3("-platform_version")) {
|
||||
ctx.arg.platform = parse_platform(ctx, arg);
|
||||
ctx.arg.platform_min_version = parse_version(ctx, arg2);
|
||||
ctx.arg.platform_sdk_version = parse_version(ctx, arg3);
|
||||
} else if (read_arg("-rpath")) {
|
||||
ctx.arg.rpath.push_back(std::string(arg));
|
||||
} else if (read_flag("-search_dylibs_first")) {
|
||||
Fatal(ctx) << "-search_dylibs_first is not supported";
|
||||
} else if (read_flag("-search_paths_first")) {
|
||||
} else if (read_arg("-syslibroot")) {
|
||||
ctx.arg.syslibroot.push_back(std::string(arg));
|
||||
} else if (read_flag("-t")) {
|
||||
ctx.arg.trace = true;
|
||||
} else if (read_flag("-v")) {
|
||||
SyncOut(ctx) << mold_version;
|
||||
} else {
|
||||
if (args[i][0] == '-')
|
||||
Fatal(ctx) << "unknown command line option: " << args[i];
|
||||
remaining.push_back(std::string(args[i]));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
auto add_search_path = [&](std::vector<std::string> &vec, std::string path) {
|
||||
if (!path.starts_with('/') || ctx.arg.syslibroot.empty()) {
|
||||
if (is_directory(path))
|
||||
vec.push_back(path);
|
||||
return;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (std::string &dir : ctx.arg.syslibroot) {
|
||||
std::string str = path_clean(dir + "/" + path);
|
||||
if (is_directory(str)) {
|
||||
vec.push_back(str);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found && is_directory(path))
|
||||
vec.push_back(path);
|
||||
};
|
||||
|
||||
for (std::string &path : library_paths)
|
||||
add_search_path(ctx.arg.library_paths, path);
|
||||
|
||||
if (!nostdlib) {
|
||||
add_search_path(ctx.arg.library_paths, "/usr/lib");
|
||||
add_search_path(ctx.arg.library_paths, "/usr/local/lib");
|
||||
}
|
||||
|
||||
for (std::string &path : framework_paths)
|
||||
add_search_path(ctx.arg.framework_paths, path);
|
||||
|
||||
if (!nostdlib) {
|
||||
add_search_path(ctx.arg.framework_paths, "/Library/Frameworks");
|
||||
add_search_path(ctx.arg.framework_paths, "/System/Library/Frameworks");
|
||||
}
|
||||
|
||||
if (pagezero_size.has_value()) {
|
||||
if (ctx.output_type != MH_EXECUTE)
|
||||
Error(ctx) << "-pagezero_size option can only be used when"
|
||||
<< " linking a main executable";
|
||||
ctx.arg.pagezero_size = *pagezero_size;
|
||||
} else {
|
||||
ctx.arg.pagezero_size = (ctx.output_type == MH_EXECUTE) ? 0x100000000 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void parse_nonpositional_args(Context<E> &, std::vector<std::string> &)
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -1,112 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
static std::vector<Subsection<E> *> collect_root_set(Context<E> &ctx) {
|
||||
std::vector<Subsection<E> *> rootset;
|
||||
|
||||
auto mark = [&](Symbol<E> *sym) {
|
||||
if (sym && sym->subsec)
|
||||
rootset.push_back(sym->subsec);
|
||||
};
|
||||
|
||||
mark(get_symbol(ctx, ctx.arg.entry));
|
||||
|
||||
if (ctx.output_type == MH_DYLIB || ctx.output_type == MH_BUNDLE)
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (Symbol<E> *sym : file->syms)
|
||||
if (sym->file == file && sym->is_extern)
|
||||
mark(sym);
|
||||
|
||||
return rootset;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void visit(Context<E> &ctx, Subsection<E> &subsec) {
|
||||
if (subsec.is_alive.exchange(true))
|
||||
return;
|
||||
|
||||
for (Relocation<E> &rel : subsec.get_rels()) {
|
||||
if (rel.sym) {
|
||||
if (rel.sym->subsec)
|
||||
visit(ctx, *rel.sym->subsec);
|
||||
} else {
|
||||
visit(ctx, *rel.subsec);
|
||||
}
|
||||
}
|
||||
|
||||
for (UnwindRecord<E> &rec : subsec.get_unwind_records()) {
|
||||
rec.is_alive = true;
|
||||
visit(ctx, *rec.subsec);
|
||||
if (rec.lsda)
|
||||
visit(ctx, *rec.lsda);
|
||||
if (Symbol<E> *sym = rec.personality; sym && sym->subsec)
|
||||
visit(ctx, *sym->subsec);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool refers_live_subsection(Subsection<E> &subsec) {
|
||||
for (Relocation<E> &rel : subsec.get_rels()) {
|
||||
if (rel.sym) {
|
||||
if (!rel.sym->subsec || rel.sym->subsec->is_alive)
|
||||
return true;
|
||||
} else {
|
||||
if (rel.subsec->is_alive)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void mark(Context<E> &ctx, const std::vector<Subsection<E> *> &rootset) {
|
||||
for (Subsection<E> *subsec : rootset)
|
||||
visit(ctx, *subsec);
|
||||
|
||||
bool repeat;
|
||||
do {
|
||||
repeat = false;
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
for (std::unique_ptr<Subsection<E>> &subsec : file->subsections) {
|
||||
if ((subsec->isec.hdr.attr & S_ATTR_LIVE_SUPPORT) &&
|
||||
!subsec->is_alive &&
|
||||
refers_live_subsection(*subsec)) {
|
||||
visit(ctx, *subsec);
|
||||
repeat = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (repeat);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void sweep(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
std::erase_if(file->subsections,
|
||||
[](const std::unique_ptr<Subsection<E>> &subsec) {
|
||||
return !subsec->is_alive;
|
||||
});
|
||||
}
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (Symbol<E> *&sym : file->syms)
|
||||
if (sym->file == file && sym->subsec && !sym->subsec->is_alive)
|
||||
sym = nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void dead_strip(Context<E> &ctx) {
|
||||
std::vector<Subsection<E> *> rootset = collect_root_set(ctx);
|
||||
mark(ctx, rootset);
|
||||
sweep(ctx);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void dead_strip(Context<E> &)
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
|
||||
} // namespace mold::macho
|
||||
-511
@@ -1,511 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
static u8 *open_file(std::string path) {
|
||||
i64 fd = ::open(path.c_str(), O_RDONLY);
|
||||
if (fd == -1)
|
||||
return nullptr;
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == -1) {
|
||||
std::cerr << path << ": fstat failed: " << errno_string();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void *ptr = mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (ptr == MAP_FAILED) {
|
||||
std::cerr << path << ": mmap failed: " << errno_string();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return (u8 *)ptr;
|
||||
}
|
||||
|
||||
static void print_bytes(u8 *buf, i64 size) {
|
||||
if (size == 0) {
|
||||
std::cout << "[]\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "[" << std::setw(2) << std::setfill('0') << (u32)buf[0];
|
||||
for (i64 i = 1; i < size; i++)
|
||||
std::cout << " " << std::setw(2) << std::setfill('0') << (u32)buf[i];
|
||||
std::cout << "]\n";
|
||||
}
|
||||
|
||||
struct ExportEntry {
|
||||
std::string name;
|
||||
u32 flags;
|
||||
u64 addr;
|
||||
};
|
||||
|
||||
static void read_trie(std::vector<ExportEntry> &vec, u8 *start, i64 offset = 0,
|
||||
const std::string &prefix = "") {
|
||||
u8 *buf = start + offset;
|
||||
|
||||
if (read_uleb(buf)) {
|
||||
ExportEntry ent;
|
||||
ent.name = prefix;
|
||||
ent.flags = read_uleb(buf);
|
||||
ent.addr = read_uleb(buf);
|
||||
vec.push_back(std::move(ent));
|
||||
}
|
||||
|
||||
for (i64 i = 0, n = read_uleb(buf); i < n; i++) {
|
||||
std::string suffix((char *)buf);
|
||||
buf += suffix.size() + 1;
|
||||
i64 off = read_uleb(buf);
|
||||
read_trie(vec, start, off, prefix + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
void dump_unwind_info(u8 *buf, MachSection &sec) {
|
||||
UnwindSectionHeader &hdr = *(UnwindSectionHeader *)(buf + sec.offset);
|
||||
|
||||
std::cout << std::hex << " Unwind info:"
|
||||
<< "\n version: 0x" << hdr.version
|
||||
<< "\n encoding_offset: 0x" << hdr.encoding_offset
|
||||
<< "\n encoding_count: 0x" << hdr.encoding_count
|
||||
<< "\n personality_offset: 0x" << hdr.personality_offset
|
||||
<< "\n personality_count: 0x" << hdr.personality_count
|
||||
<< "\n page_offset: 0x" << hdr.page_offset
|
||||
<< "\n page_count: 0x" << hdr.page_count;
|
||||
|
||||
std::cout << "\n encoding:";
|
||||
u32 *enc = (u32 *)(buf + sec.offset + hdr.encoding_offset);
|
||||
for (i64 i = 0; i < hdr.encoding_count; i++)
|
||||
std::cout << std::hex << "\n 0x" << enc[i];
|
||||
|
||||
std::cout << "\n personality:";
|
||||
u32 *pers = (u32 *)(buf + sec.offset + hdr.personality_offset);
|
||||
for (i64 i = 0; i < hdr.personality_count; i++)
|
||||
std::cout << std::hex << "\n 0x" << pers[i];
|
||||
|
||||
UnwindFirstLevelPage *ent =
|
||||
(UnwindFirstLevelPage *)(buf + sec.offset + hdr.page_offset);
|
||||
|
||||
for (i64 i = 0; i < hdr.page_count; i++) {
|
||||
std::cout << std::hex << "\n function:"
|
||||
<< "\n func_addr: 0x" << ent[i].func_addr
|
||||
<< "\n page_offset: 0x" << ent[i].page_offset
|
||||
<< "\n lsda_offset: 0x" << ent[i].lsda_offset;
|
||||
|
||||
if (i != hdr.page_count - 1) {
|
||||
UnwindLsdaEntry *lsda =
|
||||
(UnwindLsdaEntry *)(buf + sec.offset + ent[i].lsda_offset);
|
||||
i64 lsda_size = ent[i + 1].lsda_offset - ent[i].lsda_offset;
|
||||
for (i64 j = 0; j < lsda_size / sizeof(UnwindLsdaEntry); j++)
|
||||
std::cout << std::hex
|
||||
<< "\n lsda:"
|
||||
<< "\n func_addr: 0x" << lsda[j].func_addr
|
||||
<< "\n lsda_addr: 0x" << lsda[j].lsda_addr;
|
||||
}
|
||||
|
||||
if (ent[i].page_offset == 0)
|
||||
break;
|
||||
|
||||
u8 *addr = buf + sec.offset + ent[i].page_offset;
|
||||
|
||||
switch (u32 kind = *addr; kind) {
|
||||
case UNWIND_SECOND_LEVEL_REGULAR: {
|
||||
std::cout << "\n UNWIND_SECOND_LEVEL_REGULAR:" ;
|
||||
break;
|
||||
}
|
||||
case UNWIND_SECOND_LEVEL_COMPRESSED: {
|
||||
std::cout << "\n UNWIND_SECOND_LEVEL_COMPRESSED" ;
|
||||
UnwindSecondLevelPage &hdr2 = *(UnwindSecondLevelPage *)addr;
|
||||
std::cout << std::hex
|
||||
<< "\n page_offset: 0x" << hdr2.page_offset
|
||||
<< "\n page_count: 0x" << hdr2.page_count
|
||||
<< "\n encoding_offset: 0x" << hdr2.encoding_offset
|
||||
<< "\n encoding_count: 0x" << hdr2.encoding_count;
|
||||
|
||||
UnwindPageEntry *ent2 = (UnwindPageEntry *)(addr + hdr2.page_offset);
|
||||
for (i64 j = 0; j < hdr2.page_count; j++)
|
||||
std::cout << std::hex << "\n ent 0x"
|
||||
<< (ent[i].func_addr + ent2[j].func_addr)
|
||||
<< " 0x" << ent2[j].encoding;
|
||||
|
||||
u32 *enc = (u32 *)(addr + hdr2.encoding_offset);
|
||||
for (i64 j = 0; j < hdr2.encoding_count; j++)
|
||||
std::cout << std::hex << "\n 0x" << enc[j];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::cout << "\n bad 2nd-level unwind info header: " << kind;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
void dump_compact_unwind(u8 *buf, MachSection &sec) {
|
||||
CompactUnwindEntry *ent = (CompactUnwindEntry *)(buf + sec.offset);
|
||||
i64 nentry = sec.size / sizeof(CompactUnwindEntry);
|
||||
|
||||
std::cout << " Compact unwind:"
|
||||
<< "\n num_entry: " << nentry;
|
||||
|
||||
for (i64 i = 0; i < nentry; i++) {
|
||||
std::cout << std::hex
|
||||
<< "\n entry: 0x" << (i * sizeof(CompactUnwindEntry))
|
||||
<< "\n code_start: 0x" << ent[i].code_start
|
||||
<< "\n code_len: 0x" << ent[i].code_len
|
||||
<< "\n encoding: 0x" << ent[i].encoding
|
||||
<< "\n personality: 0x" << ent[i].personality
|
||||
<< "\n lsda: 0x" << ent[i].lsda;
|
||||
}
|
||||
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
void dump_file(std::string path) {
|
||||
u8 *buf = open_file(path);
|
||||
if (!buf) {
|
||||
std::cerr << "cannot open " << path << "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
MachHeader &hdr = *(MachHeader *)buf;
|
||||
std::cout << "magic: 0x" << std::hex << hdr.magic
|
||||
<< "\ncputype: 0x" << hdr.cputype
|
||||
<< "\ncpusubtype: 0x" << hdr.cpusubtype
|
||||
<< "\nfiletype: 0x" << hdr.filetype
|
||||
<< "\nncmds: 0x" << hdr.ncmds
|
||||
<< "\nsizeofcmds: 0x" << hdr.sizeofcmds
|
||||
<< "\nflags: 0x" << hdr.flags
|
||||
<< "\n\n";
|
||||
|
||||
u8 *p = buf + sizeof(MachHeader);
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
std::cout << "fileoff: 0x" << std::hex << (p - buf) << "\n";
|
||||
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
p += lc.cmdsize;
|
||||
|
||||
auto print_dylib = [&] {
|
||||
DylibCommand &cmd = *(DylibCommand *)&lc;
|
||||
std::cout << " cmdsize: 0x" << cmd.cmdsize
|
||||
<< "\n nameoff: 0x" << cmd.nameoff
|
||||
<< "\n timestamp: 0x" << cmd.timestamp
|
||||
<< "\n current_version: 0x" << cmd.current_version
|
||||
<< "\n compatibility_version: 0x" << cmd.compatibility_version
|
||||
<< "\n data: " << (char *)((char *)&cmd + cmd.nameoff)
|
||||
<< "\n";
|
||||
};
|
||||
|
||||
switch (lc.cmd) {
|
||||
case LC_SYMTAB: {
|
||||
std::cout << "LC_SYMTAB\n";
|
||||
SymtabCommand &cmd = *(SymtabCommand *)&lc;
|
||||
std::cout << " cmdsize: " << cmd.cmdsize
|
||||
<< "\n symoff: 0x" << std::hex << cmd.symoff
|
||||
<< "\n nsyms: " << std::dec << cmd.nsyms
|
||||
<< "\n stroff: 0x" << std::hex << cmd.stroff
|
||||
<< "\n strsize: 0x" << cmd.strsize
|
||||
<< "\n symdata: ";
|
||||
print_bytes(buf + cmd.symoff, cmd.nsyms * sizeof(MachSym));
|
||||
|
||||
std::cout << " strdata: ";
|
||||
print_bytes(buf + cmd.stroff, cmd.strsize);
|
||||
|
||||
MachSym *syms = (MachSym *)(buf + cmd.symoff);
|
||||
for (i64 j = 0; j < cmd.nsyms; j++) {
|
||||
std::cout << " symbol:"
|
||||
<< "\n name: " << (char *)(buf + cmd.stroff + syms[j].stroff)
|
||||
<< "\n stub: " << (u32)syms[j].stub
|
||||
<< "\n pext: " << (u32)syms[j].pext
|
||||
<< "\n type: " << (u32)syms[j].type
|
||||
<< "\n ext: " << (u32)syms[j].ext
|
||||
<< "\n sect: 0x" << (u32)syms[j].sect
|
||||
<< "\n desc: 0x" << (u32)syms[j].desc
|
||||
<< "\n value: 0x" << syms[j].value
|
||||
<< "\n";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LC_DYSYMTAB: {
|
||||
std::cout << "LC_DYSYMTAB\n";
|
||||
DysymtabCommand &cmd = *(DysymtabCommand *)&lc;
|
||||
std::cout << " cmdsize: 0x" << cmd.cmdsize
|
||||
<< "\n ilocalsym: 0x" << cmd.ilocalsym
|
||||
<< "\n nlocalsym: 0x" << cmd.nlocalsym
|
||||
<< "\n iextdefsym: 0x" << cmd.iextdefsym
|
||||
<< "\n nextdefsym: 0x" << cmd.nextdefsym
|
||||
<< "\n iundefsym: 0x" << cmd.iundefsym
|
||||
<< "\n nundefsym: 0x" << cmd.nundefsym
|
||||
<< "\n tocoff: 0x" << cmd.tocoff
|
||||
<< "\n ntoc: 0x" << cmd.ntoc
|
||||
<< "\n modtaboff: 0x" << cmd.modtaboff
|
||||
<< "\n nmodtab: 0x" << cmd.nmodtab
|
||||
<< "\n extrefsymoff: 0x" << cmd.extrefsymoff
|
||||
<< "\n nextrefsyms: 0x" << cmd.nextrefsyms
|
||||
<< "\n indirectsymoff: 0x" << cmd.indirectsymoff
|
||||
<< "\n nindirectsyms: 0x" << cmd.nindirectsyms
|
||||
<< "\n extreloff: 0x" << cmd.extreloff
|
||||
<< "\n nextrel: 0x" << cmd.nextrel
|
||||
<< "\n locreloff: 0x" << cmd.locreloff
|
||||
<< "\n nlocrel: 0x" << cmd.nlocrel
|
||||
<< "\n";
|
||||
|
||||
if (cmd.indirectsymoff) {
|
||||
std::cout << " indirectsymdata: ";
|
||||
print_bytes(buf + cmd.indirectsymoff, 4 * cmd.nindirectsyms);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LC_LOAD_DYLIB:
|
||||
std::cout << "LC_LOAD_DYLIB\n";
|
||||
print_dylib();
|
||||
break;
|
||||
case LC_LOAD_WEAK_DYLIB:
|
||||
std::cout << "LC_LOAD_WEAK_DYLIB\n";
|
||||
print_dylib();
|
||||
break;
|
||||
case LC_ID_DYLIB:
|
||||
std::cout << "LC_ID_DYLIB\n";
|
||||
print_dylib();
|
||||
break;
|
||||
case LC_LOAD_DYLINKER: {
|
||||
std::cout << "LC_LOAD_DYLINKER\n";
|
||||
DylinkerCommand &cmd = *(DylinkerCommand *)&lc;
|
||||
std::cout << " cmdsize: 0x" << cmd.cmdsize
|
||||
<< "\n nameoff: 0x" << cmd.nameoff
|
||||
<< "\n data: " << (char *)((char *)&cmd + cmd.nameoff)
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
case LC_SEGMENT_64: {
|
||||
std::cout << "LC_SEGMENT_64\n";
|
||||
SegmentCommand &cmd = *(SegmentCommand *)&lc;
|
||||
std::cout << " cmdsize: " << cmd.cmdsize
|
||||
<< "\n segname: " << cmd.get_segname()
|
||||
<< "\n vmaddr: 0x" << std::hex << cmd.vmaddr
|
||||
<< "\n vmsize: 0x" << cmd.vmsize
|
||||
<< "\n fileoff: 0x" << cmd.fileoff
|
||||
<< "\n filesize: 0x" << cmd.filesize
|
||||
<< "\n maxprot: " << std::dec << cmd.maxprot
|
||||
<< "\n initprot: " << cmd.initprot
|
||||
<< "\n nsects: " << cmd.nsects
|
||||
<< "\n flags: 0x" << std::hex << cmd.flags
|
||||
<< "\n";
|
||||
|
||||
MachSection *sec = (MachSection *)((u8 *)&lc + sizeof(cmd));
|
||||
for (i64 j = 0; j < cmd.nsects; j++) {
|
||||
std::cout << " section:\n sectname: " << sec[j].get_sectname()
|
||||
<< "\n segname: " << sec[j].get_segname()
|
||||
<< "\n addr: 0x" << std::hex << sec[j].addr
|
||||
<< "\n size: 0x" << sec[j].size
|
||||
<< "\n offset: 0x" << sec[j].offset
|
||||
<< "\n p2align: " << std::dec << sec[j].p2align
|
||||
<< "\n reloff: " << std::hex << sec[j].reloff
|
||||
<< "\n nreloc: " << std::dec << sec[j].nreloc
|
||||
<< "\n type: 0x" << std::hex << sec[j].type
|
||||
<< "\n attr: 0x" << std::hex << sec[j].attr
|
||||
<< "\n";
|
||||
|
||||
if (sec[j].type != S_ZEROFILL) {
|
||||
std::cout << " contents: ";
|
||||
print_bytes(buf + sec[j].offset, sec[j].size);
|
||||
}
|
||||
|
||||
if (sec[j].reloff) {
|
||||
MachRel *rel = (MachRel *)(buf + sec[j].reloff);
|
||||
for (i64 k = 0; k < sec[j].nreloc; k++) {
|
||||
std::cout << " reloc: "
|
||||
<< "\n offset: 0x" << rel[k].offset
|
||||
<< "\n idx: 0x" << rel[k].idx
|
||||
<< "\n is_pcrel: " << rel[k].is_pcrel
|
||||
<< "\n p2size: 0x" << rel[k].p2size
|
||||
<< "\n is_extern: " << rel[k].is_extern
|
||||
<< "\n type: " << rel[k].type
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (sec[j].match("__TEXT", "__unwind_info"))
|
||||
dump_unwind_info(buf, sec[j]);
|
||||
|
||||
if (sec[j].match("__LD", "__compact_unwind"))
|
||||
dump_compact_unwind(buf, sec[j]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LC_UUID: {
|
||||
std::cout << "LC_UUID\n";
|
||||
UUIDCommand &cmd = *(UUIDCommand *)&lc;
|
||||
std::cout << " data: ";
|
||||
print_bytes(cmd.uuid, sizeof(cmd.uuid));
|
||||
break;
|
||||
}
|
||||
case LC_DYLD_INFO_ONLY: {
|
||||
std::cout << "LC_DYLD_INFO_ONLY\n";
|
||||
DyldInfoCommand &cmd = *(DyldInfoCommand *)&lc;
|
||||
|
||||
if (cmd.rebase_off) {
|
||||
std::cout << " rebase: ";
|
||||
print_bytes(buf + cmd.rebase_off, cmd.rebase_size);
|
||||
std::cout << " rebase_off: 0x" << std::hex << cmd.rebase_off
|
||||
<< "\n rebase_size: 0x" << cmd.rebase_size
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if (cmd.bind_off) {
|
||||
std::cout << " bind: ";
|
||||
print_bytes(buf + cmd.bind_off, cmd.bind_size);
|
||||
std::cout << " bind_off: 0x" << std::hex << cmd.bind_off
|
||||
<< "\n bind_size: 0x" << cmd.bind_size
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if (cmd.weak_bind_off) {
|
||||
std::cout << " weak_bind: ";
|
||||
print_bytes(buf + cmd.weak_bind_off, cmd.weak_bind_size);
|
||||
std::cout << " weak_bind_off: 0x" << std::hex << cmd.weak_bind_off
|
||||
<< "\n weak_bind_size: 0x" << cmd.weak_bind_size
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if (cmd.lazy_bind_off) {
|
||||
std::cout << " lazy_bind: ";
|
||||
print_bytes(buf + cmd.lazy_bind_off, cmd.lazy_bind_size);
|
||||
std::cout << " lazy_bind_off: 0x" << std::hex << cmd.lazy_bind_off
|
||||
<< "\n lazy_bind_size: 0x" << cmd.lazy_bind_size
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if (cmd.export_off) {
|
||||
std::cout << " export: ";
|
||||
print_bytes(buf + cmd.export_off, cmd.export_size);
|
||||
std::cout << " export_off: 0x" << std::hex << cmd.export_off
|
||||
<< "\n export_size: 0x" << cmd.export_size
|
||||
<< "\n";
|
||||
|
||||
std::vector<ExportEntry> vec;
|
||||
read_trie(vec, buf + cmd.export_off);
|
||||
for (ExportEntry &ent : vec)
|
||||
std::cout << " export_sym: " << ent.name << " 0x" << ent.addr << "\n";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LC_FUNCTION_STARTS: {
|
||||
std::cout << "LC_FUNCTION_STARTS\n";
|
||||
LinkEditDataCommand &cmd = *(LinkEditDataCommand *)&lc;
|
||||
std::cout << " dataoff: 0x" << cmd.dataoff
|
||||
<< "\n datasize: 0x" << cmd.datasize
|
||||
<< "\n data:";
|
||||
|
||||
u8 *p = buf + cmd.dataoff;
|
||||
u64 addr = 0;
|
||||
for (;;) {
|
||||
u64 delta = read_uleb(p);
|
||||
if (!delta)
|
||||
break;
|
||||
addr += delta;
|
||||
std::cout << std::hex << " 0x" << addr;
|
||||
}
|
||||
std::cout << "\n";
|
||||
break;
|
||||
}
|
||||
case LC_MAIN: {
|
||||
std::cout << "LC_MAIN\n";
|
||||
EntryPointCommand &cmd = *(EntryPointCommand *)&lc;
|
||||
std::cout << " cmdsize: 0x" << cmd.cmdsize
|
||||
<< "\n entryoff: 0x" << cmd.entryoff
|
||||
<< "\n stacksize: 0x" << cmd.stacksize
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
case LC_DATA_IN_CODE: {
|
||||
std::cout << "LC_DATA_IN_CODE\n";
|
||||
LinkEditDataCommand &cmd = *(LinkEditDataCommand *)&lc;
|
||||
std::cout << " dataoff: 0x" << cmd.dataoff
|
||||
<< "\n datasize: 0x" << cmd.datasize
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
case LC_SOURCE_VERSION: {
|
||||
std::cout << "LC_SOURCE_VERSION\n";
|
||||
SourceVersionCommand &cmd = *(SourceVersionCommand *)&lc;
|
||||
std::cout << " version: 0x" << cmd.version
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case LC_BUILD_VERSION: {
|
||||
std::cout << "LC_BUILD_VERSION\n";
|
||||
BuildVersionCommand &cmd = *(BuildVersionCommand *)&lc;
|
||||
std::cout << " cmdsize: 0x" << cmd.cmdsize
|
||||
<< "\n platform: 0x" << cmd.platform
|
||||
<< "\n minos: 0x" << cmd.minos
|
||||
<< "\n sdk: 0x" << cmd.sdk
|
||||
<< "\n ntools: 0x" << cmd.ntools
|
||||
<< "\n";
|
||||
|
||||
BuildToolVersion *tools =
|
||||
(BuildToolVersion *)((u8 *)&lc + sizeof(BuildVersionCommand));
|
||||
for (i64 i = 0; i < cmd.ntools; i++)
|
||||
std::cout << " tool: 0x" << tools[i].tool
|
||||
<< "\n version: 0x" << tools[i].version
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
case LC_VERSION_MIN_MACOSX: {
|
||||
std::cout << "LC_VERSION_MIN_MACOSX\n";
|
||||
VersionMinCommand &cmd = *(VersionMinCommand *)&lc;
|
||||
std::cout << " version: " << (int)cmd.version
|
||||
<< "\n sdk: " << (int)cmd.sdk
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
case LC_CODE_SIGNATURE: {
|
||||
std::cout << "LC_CODE_SIGNATURE\n";
|
||||
LinkEditDataCommand &cmd = *(LinkEditDataCommand *)&lc;
|
||||
|
||||
CodeSignatureHeader &sig = *(CodeSignatureHeader *)(buf + cmd.dataoff);
|
||||
std::cout << " magic: " << sig.magic
|
||||
<< "\n length: " << sig.length
|
||||
<< "\n count: " << sig.count
|
||||
<< "\n";
|
||||
|
||||
for (i64 i = 0; i < sig.count; i++) {
|
||||
CodeSignatureBlobIndex &idx = ((CodeSignatureBlobIndex *)(&sig + 1))[i];
|
||||
std::cout << " idx type: " << idx.type
|
||||
<< "\n idx offset: " << idx.offset
|
||||
<< "\n";
|
||||
|
||||
CodeSignatureDirectory &dir =
|
||||
*(CodeSignatureDirectory *)((u8 *)&sig + idx.offset);
|
||||
std::cout << std::hex
|
||||
<< " magic: 0x" << dir.magic
|
||||
<< "\n version: 0x" << dir.version
|
||||
<< "\n flags: 0x" << dir.flags
|
||||
<< "\n hash_offset: 0x" << dir.hash_offset
|
||||
<< "\n n_code_slots: 0x" << dir.n_code_slots
|
||||
<< "\n hash_size: 0x" << (u32)dir.hash_size
|
||||
<< "\n hash_type: 0x" << (u32)dir.hash_type
|
||||
<< "\n page_size: 0x" << (1 << dir.page_size)
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::cout << "UNKNOWN (0x" << std::hex << lc.cmd << ")\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -1,60 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
const MachSection &hdr)
|
||||
: file(file), hdr(hdr),
|
||||
osec(*OutputSection<E>::get_instance(ctx, hdr.get_segname(),
|
||||
hdr.get_sectname())) {
|
||||
if (hdr.type != S_ZEROFILL)
|
||||
contents = file.mf->get_contents().substr(hdr.offset, hdr.size);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::parse_relocations(Context<E> &ctx) {
|
||||
rels.reserve(hdr.nreloc);
|
||||
|
||||
// Parse mach-o relocations to fill `rels` vector
|
||||
rels = read_relocations(ctx, file, hdr);
|
||||
|
||||
// Sort `rels` vector
|
||||
sort(rels, [](const Relocation<E> &a, const Relocation<E> &b) {
|
||||
return a.offset < b.offset;
|
||||
});
|
||||
|
||||
// Find subsections for this section
|
||||
auto begin = std::lower_bound(
|
||||
file.subsections.begin(), file.subsections.end(), hdr.addr,
|
||||
[](std::unique_ptr<Subsection<E>> &subsec, u32 addr) {
|
||||
return subsec->input_addr < addr;
|
||||
});
|
||||
|
||||
auto end = std::lower_bound(
|
||||
begin, file.subsections.end(), hdr.addr + hdr.size,
|
||||
[](std::unique_ptr<Subsection<E>> &subsec, u32 addr) {
|
||||
return subsec->input_addr < addr;
|
||||
});
|
||||
|
||||
// Assign each subsection a group of relocations
|
||||
i64 i = 0;
|
||||
for (auto it = begin; it < end; it++) {
|
||||
Subsection<E> &subsec = **it;
|
||||
subsec.rel_offset = i;
|
||||
while (i < rels.size() &&
|
||||
rels[i].offset < subsec.input_offset + subsec.input_size) {
|
||||
rels[i].offset -= subsec.input_offset;
|
||||
i++;
|
||||
}
|
||||
subsec.nrels = i - subsec.rel_offset;
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class InputSection<E>
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
|
||||
} // namespace mold::macho
|
||||
-693
@@ -1,693 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../big-endian.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string_view>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
|
||||
static constexpr u32 FAT_MAGIC = 0xcafebabe;
|
||||
|
||||
static constexpr u32 MH_OBJECT = 0x1;
|
||||
static constexpr u32 MH_EXECUTE = 0x2;
|
||||
static constexpr u32 MH_FVMLIB = 0x3;
|
||||
static constexpr u32 MH_CORE = 0x4;
|
||||
static constexpr u32 MH_PRELOAD = 0x5;
|
||||
static constexpr u32 MH_DYLIB = 0x6;
|
||||
static constexpr u32 MH_DYLINKER = 0x7;
|
||||
static constexpr u32 MH_BUNDLE = 0x8;
|
||||
static constexpr u32 MH_DYLIB_STUB = 0x9;
|
||||
static constexpr u32 MH_DSYM = 0xa;
|
||||
static constexpr u32 MH_KEXT_BUNDLE = 0xb;
|
||||
|
||||
static constexpr u32 MH_NOUNDEFS = 0x1;
|
||||
static constexpr u32 MH_INCRLINK = 0x2;
|
||||
static constexpr u32 MH_DYLDLINK = 0x4;
|
||||
static constexpr u32 MH_BINDATLOAD = 0x8;
|
||||
static constexpr u32 MH_PREBOUND = 0x10;
|
||||
static constexpr u32 MH_SPLIT_SEGS = 0x20;
|
||||
static constexpr u32 MH_LAZY_INIT = 0x40;
|
||||
static constexpr u32 MH_TWOLEVEL = 0x80;
|
||||
static constexpr u32 MH_FORCE_FLAT = 0x100;
|
||||
static constexpr u32 MH_NOMULTIDEFS = 0x200;
|
||||
static constexpr u32 MH_NOFIXPREBINDING = 0x400;
|
||||
static constexpr u32 MH_PREBINDABLE = 0x800;
|
||||
static constexpr u32 MH_ALLMODSBOUND = 0x1000;
|
||||
static constexpr u32 MH_SUBSECTIONS_VIA_SYMBOLS = 0x2000;
|
||||
static constexpr u32 MH_CANONICAL = 0x4000;
|
||||
static constexpr u32 MH_WEAK_DEFINES = 0x8000;
|
||||
static constexpr u32 MH_BINDS_TO_WEAK = 0x10000;
|
||||
static constexpr u32 MH_ALLOW_STACK_EXECUTION = 0x20000;
|
||||
static constexpr u32 MH_ROOT_SAFE = 0x40000;
|
||||
static constexpr u32 MH_SETUID_SAFE = 0x80000;
|
||||
static constexpr u32 MH_NO_REEXPORTED_DYLIBS = 0x100000;
|
||||
static constexpr u32 MH_PIE = 0x200000;
|
||||
static constexpr u32 MH_DEAD_STRIPPABLE_DYLIB = 0x400000;
|
||||
static constexpr u32 MH_HAS_TLV_DESCRIPTORS = 0x800000;
|
||||
static constexpr u32 MH_NO_HEAP_EXECUTION = 0x1000000;
|
||||
static constexpr u32 MH_APP_EXTENSION_SAFE = 0x02000000;
|
||||
static constexpr u32 MH_NLIST_OUTOFSYNC_WITH_DYLDINFO = 0x04000000;
|
||||
static constexpr u32 MH_SIM_SUPPORT = 0x08000000;
|
||||
|
||||
static constexpr u32 VM_PROT_READ = 0x1;
|
||||
static constexpr u32 VM_PROT_WRITE = 0x2;
|
||||
static constexpr u32 VM_PROT_EXECUTE = 0x4;
|
||||
static constexpr u32 VM_PROT_NO_CHANGE = 0x8;
|
||||
static constexpr u32 VM_PROT_COPY = 0x10;
|
||||
static constexpr u32 VM_PROT_WANTS_COPY = 0x10;
|
||||
|
||||
static constexpr u32 LC_REQ_DYLD = 0x80000000;
|
||||
|
||||
static constexpr u32 LC_SEGMENT = 0x1;
|
||||
static constexpr u32 LC_SYMTAB = 0x2;
|
||||
static constexpr u32 LC_SYMSEG = 0x3;
|
||||
static constexpr u32 LC_THREAD = 0x4;
|
||||
static constexpr u32 LC_UNIXTHREAD = 0x5;
|
||||
static constexpr u32 LC_LOADFVMLIB = 0x6;
|
||||
static constexpr u32 LC_IDFVMLIB = 0x7;
|
||||
static constexpr u32 LC_IDENT = 0x8;
|
||||
static constexpr u32 LC_FVMFILE = 0x9;
|
||||
static constexpr u32 LC_PREPAGE = 0xa;
|
||||
static constexpr u32 LC_DYSYMTAB = 0xb;
|
||||
static constexpr u32 LC_LOAD_DYLIB = 0xc;
|
||||
static constexpr u32 LC_ID_DYLIB = 0xd;
|
||||
static constexpr u32 LC_LOAD_DYLINKER = 0xe;
|
||||
static constexpr u32 LC_ID_DYLINKER = 0xf;
|
||||
static constexpr u32 LC_PREBOUND_DYLIB = 0x10;
|
||||
static constexpr u32 LC_ROUTINES = 0x11;
|
||||
static constexpr u32 LC_SUB_FRAMEWORK = 0x12;
|
||||
static constexpr u32 LC_SUB_UMBRELLA = 0x13;
|
||||
static constexpr u32 LC_SUB_CLIENT = 0x14;
|
||||
static constexpr u32 LC_SUB_LIBRARY = 0x15;
|
||||
static constexpr u32 LC_TWOLEVEL_HINTS = 0x16;
|
||||
static constexpr u32 LC_PREBIND_CKSUM = 0x17;
|
||||
static constexpr u32 LC_LOAD_WEAK_DYLIB = (0x18 | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_SEGMENT_64 = 0x19;
|
||||
static constexpr u32 LC_ROUTINES_64 = 0x1a;
|
||||
static constexpr u32 LC_UUID = 0x1b;
|
||||
static constexpr u32 LC_RPATH = (0x1c | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_CODE_SIGNATURE = 0x1d;
|
||||
static constexpr u32 LC_SEGMENT_SPLIT_INFO = 0x1e;
|
||||
static constexpr u32 LC_REEXPORT_DYLIB = (0x1f | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_LAZY_LOAD_DYLIB = 0x20;
|
||||
static constexpr u32 LC_ENCRYPTION_INFO = 0x21;
|
||||
static constexpr u32 LC_DYLD_INFO = 0x22;
|
||||
static constexpr u32 LC_DYLD_INFO_ONLY = (0x22 | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_LOAD_UPWARD_DYLIB = (0x23 | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_VERSION_MIN_MACOSX = 0x24;
|
||||
static constexpr u32 LC_VERSION_MIN_IPHONEOS = 0x25;
|
||||
static constexpr u32 LC_FUNCTION_STARTS = 0x26;
|
||||
static constexpr u32 LC_DYLD_ENVIRONMENT = 0x27;
|
||||
static constexpr u32 LC_MAIN = (0x28 | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_DATA_IN_CODE = 0x29;
|
||||
static constexpr u32 LC_SOURCE_VERSION = 0x2A;
|
||||
static constexpr u32 LC_DYLIB_CODE_SIGN_DRS = 0x2B;
|
||||
static constexpr u32 LC_ENCRYPTION_INFO_64 = 0x2C;
|
||||
static constexpr u32 LC_LINKER_OPTION = 0x2D;
|
||||
static constexpr u32 LC_LINKER_OPTIMIZATION_HINT = 0x2E;
|
||||
static constexpr u32 LC_VERSION_MIN_TVOS = 0x2F;
|
||||
static constexpr u32 LC_VERSION_MIN_WATCHOS = 0x30;
|
||||
static constexpr u32 LC_NOTE = 0x31;
|
||||
static constexpr u32 LC_BUILD_VERSION = 0x32;
|
||||
static constexpr u32 LC_DYLD_EXPORTS_TRIE = (0x33 | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_DYLD_CHAINED_FIXUPS = (0x34 | LC_REQ_DYLD);
|
||||
|
||||
static constexpr u32 SG_HIGHVM = 0x1;
|
||||
static constexpr u32 SG_FVMLIB = 0x2;
|
||||
static constexpr u32 SG_NORELOC = 0x4;
|
||||
static constexpr u32 SG_PROTECTED_VERSION_1 = 0x8;
|
||||
static constexpr u32 SG_READ_ONLY = 0x10;
|
||||
|
||||
static constexpr u32 S_REGULAR = 0x0;
|
||||
static constexpr u32 S_ZEROFILL = 0x1;
|
||||
static constexpr u32 S_CSTRING_LITERALS = 0x2;
|
||||
static constexpr u32 S_4BYTE_LITERALS = 0x3;
|
||||
static constexpr u32 S_8BYTE_LITERALS = 0x4;
|
||||
static constexpr u32 S_LITERAL_POINTERS = 0x5;
|
||||
static constexpr u32 S_NON_LAZY_SYMBOL_POINTERS = 0x6;
|
||||
static constexpr u32 S_LAZY_SYMBOL_POINTERS = 0x7;
|
||||
static constexpr u32 S_SYMBOL_STUBS = 0x8;
|
||||
static constexpr u32 S_MOD_INIT_FUNC_POINTERS = 0x9;
|
||||
static constexpr u32 S_MOD_TERM_FUNC_POINTERS = 0xa;
|
||||
static constexpr u32 S_COALESCED = 0xb;
|
||||
static constexpr u32 S_GB_ZEROFILL = 0xc;
|
||||
static constexpr u32 S_INTERPOSING = 0xd;
|
||||
static constexpr u32 S_16BYTE_LITERALS = 0xe;
|
||||
static constexpr u32 S_DTRACE_DOF = 0xf;
|
||||
static constexpr u32 S_LAZY_DYLIB_SYMBOL_POINTERS = 0x10;
|
||||
static constexpr u32 S_THREAD_LOCAL_REGULAR = 0x11;
|
||||
static constexpr u32 S_THREAD_LOCAL_ZEROFILL = 0x12;
|
||||
static constexpr u32 S_THREAD_LOCAL_VARIABLES = 0x13;
|
||||
static constexpr u32 S_THREAD_LOCAL_VARIABLE_POINTERS = 0x14;
|
||||
static constexpr u32 S_THREAD_LOCAL_INIT_FUNCTION_POINTERS = 0x15;
|
||||
static constexpr u32 S_INIT_FUNC_OFFSETS = 0x16;
|
||||
|
||||
static constexpr u32 S_ATTR_LOC_RELOC = 0x000001;
|
||||
static constexpr u32 S_ATTR_EXT_RELOC = 0x000002;
|
||||
static constexpr u32 S_ATTR_SOME_INSTRUCTIONS = 0x000004;
|
||||
|
||||
static constexpr u32 S_ATTR_DEBUG = 0x020000;
|
||||
static constexpr u32 S_ATTR_SELF_MODIFYING_CODE = 0x040000;
|
||||
static constexpr u32 S_ATTR_LIVE_SUPPORT = 0x080000;
|
||||
static constexpr u32 S_ATTR_NO_DEAD_STRIP = 0x100000;
|
||||
static constexpr u32 S_ATTR_STRIP_STATIC_SYMS = 0x200000;
|
||||
static constexpr u32 S_ATTR_NO_TOC = 0x400000;
|
||||
static constexpr u32 S_ATTR_PURE_INSTRUCTIONS = 0x800000;
|
||||
|
||||
static constexpr u32 CPU_TYPE_X86_64 = 0x1000007;
|
||||
static constexpr u32 CPU_TYPE_ARM64 = 0x100000c;
|
||||
|
||||
static constexpr u32 CPU_SUBTYPE_X86_64_ALL = 3;
|
||||
static constexpr u32 CPU_SUBTYPE_ARM64_ALL = 0;
|
||||
|
||||
static constexpr u32 REBASE_TYPE_POINTER = 1;
|
||||
static constexpr u32 REBASE_TYPE_TEXT_ABSOLUTE32 = 2;
|
||||
static constexpr u32 REBASE_TYPE_TEXT_PCREL32 = 3;
|
||||
|
||||
static constexpr u32 REBASE_OPCODE_MASK = 0xf0;
|
||||
static constexpr u32 REBASE_IMMEDIATE_MASK = 0x0f;
|
||||
static constexpr u32 REBASE_OPCODE_DONE = 0x00;
|
||||
static constexpr u32 REBASE_OPCODE_SET_TYPE_IMM = 0x10;
|
||||
static constexpr u32 REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = 0x20;
|
||||
static constexpr u32 REBASE_OPCODE_ADD_ADDR_ULEB = 0x30;
|
||||
static constexpr u32 REBASE_OPCODE_ADD_ADDR_IMM_SCALED = 0x40;
|
||||
static constexpr u32 REBASE_OPCODE_DO_REBASE_IMM_TIMES = 0x50;
|
||||
static constexpr u32 REBASE_OPCODE_DO_REBASE_ULEB_TIMES = 0x60;
|
||||
static constexpr u32 REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB = 0x70;
|
||||
static constexpr u32 REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB = 0x80;
|
||||
|
||||
static constexpr u32 BIND_TYPE_POINTER = 1;
|
||||
static constexpr u32 BIND_TYPE_TEXT_ABSOLUTE32 = 2;
|
||||
static constexpr u32 BIND_TYPE_TEXT_PCREL32 = 3;
|
||||
|
||||
static constexpr u32 BIND_SPECIAL_DYLIB_SELF = 0;
|
||||
static constexpr u32 BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE = -1;
|
||||
static constexpr u32 BIND_SPECIAL_DYLIB_FLAT_LOOKUP = -2;
|
||||
static constexpr u32 BIND_SPECIAL_DYLIB_WEAK_LOOKUP = -3;
|
||||
|
||||
static constexpr u32 BIND_SYMBOL_FLAGS_WEAK_IMPORT = 0x1;
|
||||
static constexpr u32 BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION = 0x8;
|
||||
|
||||
static constexpr u32 BIND_OPCODE_MASK = 0xF0;
|
||||
static constexpr u32 BIND_IMMEDIATE_MASK = 0x0F;
|
||||
static constexpr u32 BIND_OPCODE_DONE = 0x00;
|
||||
static constexpr u32 BIND_OPCODE_SET_DYLIB_ORDINAL_IMM = 0x10;
|
||||
static constexpr u32 BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB = 0x20;
|
||||
static constexpr u32 BIND_OPCODE_SET_DYLIB_SPECIAL_IMM = 0x30;
|
||||
static constexpr u32 BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM = 0x40;
|
||||
static constexpr u32 BIND_OPCODE_SET_TYPE_IMM = 0x50;
|
||||
static constexpr u32 BIND_OPCODE_SET_ADDEND_SLEB = 0x60;
|
||||
static constexpr u32 BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = 0x70;
|
||||
static constexpr u32 BIND_OPCODE_ADD_ADDR_ULEB = 0x80;
|
||||
static constexpr u32 BIND_OPCODE_DO_BIND = 0x90;
|
||||
static constexpr u32 BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB = 0xA0;
|
||||
static constexpr u32 BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED = 0xB0;
|
||||
static constexpr u32 BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB = 0xC0;
|
||||
static constexpr u32 BIND_OPCODE_THREADED = 0xD0;
|
||||
static constexpr u32 BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB = 0x00;
|
||||
static constexpr u32 BIND_SUBOPCODE_THREADED_APPLY = 0x01;
|
||||
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_KIND_MASK = 0x03;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_KIND_REGULAR = 0x00;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL = 0x01;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE = 0x02;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION = 0x04;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_REEXPORT = 0x08;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER = 0x10;
|
||||
|
||||
static constexpr u32 DICE_KIND_DATA = 1;
|
||||
static constexpr u32 DICE_KIND_JUMP_TABLE8 = 2;
|
||||
static constexpr u32 DICE_KIND_JUMP_TABLE16 = 3;
|
||||
static constexpr u32 DICE_KIND_JUMP_TABLE32 = 4;
|
||||
static constexpr u32 DICE_KIND_ABS_JUMP_TABLE32 = 5;
|
||||
|
||||
static constexpr u32 N_UNDF = 0;
|
||||
static constexpr u32 N_ABS = 1;
|
||||
static constexpr u32 N_INDR = 5;
|
||||
static constexpr u32 N_PBUD = 6;
|
||||
static constexpr u32 N_SECT = 7;
|
||||
|
||||
static constexpr u32 N_GSYM = 0x20;
|
||||
static constexpr u32 N_FNAME = 0x22;
|
||||
static constexpr u32 N_FUN = 0x24;
|
||||
static constexpr u32 N_STSYM = 0x26;
|
||||
static constexpr u32 N_LCSYM = 0x28;
|
||||
static constexpr u32 N_BNSYM = 0x2e;
|
||||
static constexpr u32 N_AST = 0x32;
|
||||
static constexpr u32 N_OPT = 0x3c;
|
||||
static constexpr u32 N_RSYM = 0x40;
|
||||
static constexpr u32 N_SLINE = 0x44;
|
||||
static constexpr u32 N_ENSYM = 0x4e;
|
||||
static constexpr u32 N_SSYM = 0x60;
|
||||
static constexpr u32 N_SO = 0x64;
|
||||
static constexpr u32 N_OSO = 0x66;
|
||||
static constexpr u32 N_LSYM = 0x80;
|
||||
static constexpr u32 N_BINCL = 0x82;
|
||||
static constexpr u32 N_SOL = 0x84;
|
||||
static constexpr u32 N_PARAMS = 0x86;
|
||||
static constexpr u32 N_VERSION = 0x88;
|
||||
static constexpr u32 N_OLEVEL = 0x8A;
|
||||
static constexpr u32 N_PSYM = 0xa0;
|
||||
static constexpr u32 N_EINCL = 0xa2;
|
||||
static constexpr u32 N_ENTRY = 0xa4;
|
||||
static constexpr u32 N_LBRAC = 0xc0;
|
||||
static constexpr u32 N_EXCL = 0xc2;
|
||||
static constexpr u32 N_RBRAC = 0xe0;
|
||||
static constexpr u32 N_BCOMM = 0xe2;
|
||||
static constexpr u32 N_ECOMM = 0xe4;
|
||||
static constexpr u32 N_ECOML = 0xe8;
|
||||
static constexpr u32 N_LENG = 0xfe;
|
||||
static constexpr u32 N_PC = 0x30;
|
||||
|
||||
static constexpr u32 REFERENCE_TYPE = 0xf;
|
||||
static constexpr u32 REFERENCE_FLAG_UNDEFINED_NON_LAZY = 0;
|
||||
static constexpr u32 REFERENCE_FLAG_UNDEFINED_LAZY = 1;
|
||||
static constexpr u32 REFERENCE_FLAG_DEFINED = 2;
|
||||
static constexpr u32 REFERENCE_FLAG_PRIVATE_DEFINED = 3;
|
||||
static constexpr u32 REFERENCE_FLAG_PRIVATE_UNDEFINED_NON_LAZY = 4;
|
||||
static constexpr u32 REFERENCE_FLAG_PRIVATE_UNDEFINED_LAZY = 5;
|
||||
|
||||
static constexpr u32 REFERENCED_DYNAMICALLY = 0x0010;
|
||||
|
||||
static constexpr u32 N_NO_DEAD_STRIP = 0x0020;
|
||||
static constexpr u32 N_DESC_DISCARDED = 0x0020;
|
||||
static constexpr u32 N_WEAK_REF = 0x0040;
|
||||
static constexpr u32 N_WEAK_DEF = 0x0080;
|
||||
static constexpr u32 N_REF_TO_WEAK = 0x0080;
|
||||
static constexpr u32 N_ARM_THUMB_DEF = 0x0008;
|
||||
static constexpr u32 N_SYMBOL_RESOLVER = 0x0100;
|
||||
static constexpr u32 N_ALT_ENTRY = 0x0200;
|
||||
|
||||
static constexpr u32 PLATFORM_MACOS = 1;
|
||||
static constexpr u32 PLATFORM_IOS = 2;
|
||||
static constexpr u32 PLATFORM_TVOS = 3;
|
||||
static constexpr u32 PLATFORM_WATCHOS = 4;
|
||||
static constexpr u32 PLATFORM_BRIDGEOS = 5;
|
||||
static constexpr u32 PLATFORM_MACCATALYST = 6;
|
||||
static constexpr u32 PLATFORM_IOSSIMULATOR = 7;
|
||||
static constexpr u32 PLATFORM_TVOSSIMULATOR = 8;
|
||||
static constexpr u32 PLATFORM_WATCHOSSIMULATOR = 9;
|
||||
static constexpr u32 PLATFORM_DRIVERKIT = 10;
|
||||
|
||||
static constexpr u32 TOOL_CLANG = 1;
|
||||
static constexpr u32 TOOL_SWIFT = 2;
|
||||
static constexpr u32 TOOL_LD = 3;
|
||||
|
||||
static constexpr u32 ARM64_RELOC_UNSIGNED = 0;
|
||||
static constexpr u32 ARM64_RELOC_SUBTRACTOR = 1;
|
||||
static constexpr u32 ARM64_RELOC_BRANCH26 = 2;
|
||||
static constexpr u32 ARM64_RELOC_PAGE21 = 3;
|
||||
static constexpr u32 ARM64_RELOC_PAGEOFF12 = 4;
|
||||
static constexpr u32 ARM64_RELOC_GOT_LOAD_PAGE21 = 5;
|
||||
static constexpr u32 ARM64_RELOC_GOT_LOAD_PAGEOFF12 = 6;
|
||||
static constexpr u32 ARM64_RELOC_POINTER_TO_GOT = 7;
|
||||
static constexpr u32 ARM64_RELOC_TLVP_LOAD_PAGE21 = 8;
|
||||
static constexpr u32 ARM64_RELOC_TLVP_LOAD_PAGEOFF12 = 9;
|
||||
static constexpr u32 ARM64_RELOC_ADDEND = 10;
|
||||
|
||||
static constexpr u32 X86_64_RELOC_UNSIGNED = 0;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED = 1;
|
||||
static constexpr u32 X86_64_RELOC_BRANCH = 2;
|
||||
static constexpr u32 X86_64_RELOC_GOT_LOAD = 3;
|
||||
static constexpr u32 X86_64_RELOC_GOT = 4;
|
||||
static constexpr u32 X86_64_RELOC_SUBTRACTOR = 5;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED_1 = 6;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED_2 = 7;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED_4 = 8;
|
||||
static constexpr u32 X86_64_RELOC_TLV = 9;
|
||||
|
||||
struct FatHeader {
|
||||
ubig32 magic;
|
||||
ubig32 nfat_arch;
|
||||
};
|
||||
|
||||
struct FatArch {
|
||||
ubig32 cputype;
|
||||
ubig32 cpusubtype;
|
||||
ubig32 offset;
|
||||
ubig32 size;
|
||||
ubig32 align;
|
||||
};
|
||||
|
||||
struct MachHeader {
|
||||
u32 magic;
|
||||
u32 cputype;
|
||||
u32 cpusubtype;
|
||||
u32 filetype;
|
||||
u32 ncmds;
|
||||
u32 sizeofcmds;
|
||||
u32 flags;
|
||||
u32 reserved;
|
||||
};
|
||||
|
||||
struct LoadCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
};
|
||||
|
||||
struct SegmentCommand {
|
||||
std::string_view get_segname() const {
|
||||
return {segname, strnlen(segname, sizeof(segname))};
|
||||
}
|
||||
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
char segname[16];
|
||||
u64 vmaddr;
|
||||
u64 vmsize;
|
||||
u64 fileoff;
|
||||
u64 filesize;
|
||||
u32 maxprot;
|
||||
u32 initprot;
|
||||
u32 nsects;
|
||||
u32 flags;
|
||||
};
|
||||
|
||||
struct MachSection {
|
||||
void set_segname(std::string_view name) {
|
||||
assert(name.size() <= sizeof(segname));
|
||||
memcpy(segname, name.data(), name.size());
|
||||
}
|
||||
|
||||
std::string_view get_segname() const {
|
||||
return {segname, strnlen(segname, sizeof(segname))};
|
||||
}
|
||||
|
||||
void set_sectname(std::string_view name) {
|
||||
assert(name.size() <= sizeof(sectname));
|
||||
memcpy(sectname, name.data(), name.size());
|
||||
}
|
||||
|
||||
std::string_view get_sectname() const {
|
||||
return {sectname, strnlen(sectname, sizeof(sectname))};
|
||||
}
|
||||
|
||||
bool match(std::string_view segname, std::string_view sectname) const {
|
||||
return get_segname() == segname && get_sectname() == sectname;
|
||||
}
|
||||
|
||||
char sectname[16];
|
||||
char segname[16];
|
||||
u64 addr;
|
||||
u64 size;
|
||||
u32 offset;
|
||||
u32 p2align;
|
||||
u32 reloff;
|
||||
u32 nreloc;
|
||||
u32 type : 8;
|
||||
u32 attr : 24;
|
||||
u32 reserved1;
|
||||
u32 reserved2;
|
||||
u32 reserved3;
|
||||
};
|
||||
|
||||
struct DylibCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 nameoff;
|
||||
u32 timestamp;
|
||||
u32 current_version;
|
||||
u32 compatibility_version;
|
||||
};
|
||||
|
||||
struct DylinkerCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 nameoff;
|
||||
};
|
||||
|
||||
struct SymtabCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 symoff;
|
||||
u32 nsyms;
|
||||
u32 stroff;
|
||||
u32 strsize;
|
||||
};
|
||||
|
||||
struct DysymtabCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 ilocalsym;
|
||||
u32 nlocalsym;
|
||||
u32 iextdefsym;
|
||||
u32 nextdefsym;
|
||||
u32 iundefsym;
|
||||
u32 nundefsym;
|
||||
u32 tocoff;
|
||||
u32 ntoc;
|
||||
u32 modtaboff;
|
||||
u32 nmodtab;
|
||||
u32 extrefsymoff;
|
||||
u32 nextrefsyms;
|
||||
u32 indirectsymoff;
|
||||
u32 nindirectsyms;
|
||||
u32 extreloff;
|
||||
u32 nextrel;
|
||||
u32 locreloff;
|
||||
u32 nlocrel;
|
||||
};
|
||||
|
||||
struct VersionMinCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 version;
|
||||
u32 sdk;
|
||||
};
|
||||
|
||||
struct DyldInfoCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 rebase_off;
|
||||
u32 rebase_size;
|
||||
u32 bind_off;
|
||||
u32 bind_size;
|
||||
u32 weak_bind_off;
|
||||
u32 weak_bind_size;
|
||||
u32 lazy_bind_off;
|
||||
u32 lazy_bind_size;
|
||||
u32 export_off;
|
||||
u32 export_size;
|
||||
};
|
||||
|
||||
struct UUIDCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u8 uuid[16];
|
||||
};
|
||||
|
||||
struct RpathCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 path_off;
|
||||
};
|
||||
|
||||
struct LinkEditDataCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 dataoff;
|
||||
u32 datasize;
|
||||
};
|
||||
|
||||
struct BuildToolVersion {
|
||||
u32 tool;
|
||||
u32 version;
|
||||
};
|
||||
|
||||
struct BuildVersionCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 platform;
|
||||
u32 minos;
|
||||
u32 sdk;
|
||||
u32 ntools;
|
||||
};
|
||||
|
||||
struct EntryPointCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u64 entryoff;
|
||||
u64 stacksize;
|
||||
};
|
||||
|
||||
struct SourceVersionCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u64 version;
|
||||
};
|
||||
|
||||
struct DataInCodeEntry {
|
||||
u32 offset;
|
||||
u16 length;
|
||||
u16 kind;
|
||||
};
|
||||
|
||||
// This struct is named `n_list` on BSD and macOS.
|
||||
struct MachSym {
|
||||
bool is_undef() const {
|
||||
return type == N_UNDF && !is_common();
|
||||
}
|
||||
|
||||
bool is_common() const {
|
||||
return type == N_UNDF && ext && value;
|
||||
}
|
||||
|
||||
u32 stroff;
|
||||
u8 ext : 1;
|
||||
u8 type : 3;
|
||||
u8 pext : 1;
|
||||
u8 stub : 3;
|
||||
u8 sect;
|
||||
union {
|
||||
u16 desc;
|
||||
struct {
|
||||
u16 : 8;
|
||||
u16 p2align : 4;
|
||||
u16 : 4;
|
||||
};
|
||||
};
|
||||
u64 value;
|
||||
};
|
||||
|
||||
// This struct is named `relocation_info` on BSD and macOS.
|
||||
struct MachRel {
|
||||
u32 offset;
|
||||
u32 idx : 24;
|
||||
u32 is_pcrel : 1;
|
||||
u32 p2size : 2;
|
||||
u32 is_extern : 1;
|
||||
u32 type : 4;
|
||||
};
|
||||
|
||||
// __TEXT,__unwind_info section contents
|
||||
|
||||
static constexpr u32 UNWIND_SECTION_VERSION = 1;
|
||||
static constexpr u32 UNWIND_SECOND_LEVEL_REGULAR = 2;
|
||||
static constexpr u32 UNWIND_SECOND_LEVEL_COMPRESSED = 3;
|
||||
static constexpr u32 UNWIND_PERSONALITY_MASK = 0x30000000;
|
||||
|
||||
struct UnwindSectionHeader {
|
||||
u32 version;
|
||||
u32 encoding_offset;
|
||||
u32 encoding_count;
|
||||
u32 personality_offset;
|
||||
u32 personality_count;
|
||||
u32 page_offset;
|
||||
u32 page_count;
|
||||
};
|
||||
|
||||
struct UnwindFirstLevelPage {
|
||||
u32 func_addr;
|
||||
u32 page_offset;
|
||||
u32 lsda_offset;
|
||||
};
|
||||
|
||||
struct UnwindSecondLevelPage {
|
||||
u32 kind;
|
||||
u16 page_offset;
|
||||
u16 page_count;
|
||||
u16 encoding_offset;
|
||||
u16 encoding_count;
|
||||
};
|
||||
|
||||
struct UnwindLsdaEntry {
|
||||
u32 func_addr;
|
||||
u32 lsda_addr;
|
||||
};
|
||||
|
||||
struct UnwindPageEntry {
|
||||
u32 func_addr : 24;
|
||||
u32 encoding : 8;
|
||||
};
|
||||
|
||||
// __LD,__compact_unwind section contents
|
||||
|
||||
struct CompactUnwindEntry {
|
||||
u64 code_start;
|
||||
u32 code_len;
|
||||
u32 encoding;
|
||||
u64 personality;
|
||||
u64 lsda;
|
||||
};
|
||||
|
||||
// __LINKEDIT,__code_signature
|
||||
|
||||
static constexpr u32 CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0;
|
||||
static constexpr u32 CS_SUPPORTSEXECSEG = 0x20400;
|
||||
static constexpr u32 CSMAGIC_CODEDIRECTORY = 0xfade0c02;
|
||||
static constexpr u32 CSSLOT_CODEDIRECTORY = 0;
|
||||
static constexpr u32 CS_ADHOC = 0x00000002;
|
||||
static constexpr u32 CS_LINKER_SIGNED = 0x00020000;
|
||||
static constexpr u32 CS_EXECSEG_MAIN_BINARY = 1;
|
||||
static constexpr u32 CS_HASHTYPE_SHA256 = 2;
|
||||
|
||||
struct CodeSignatureHeader {
|
||||
ubig32 magic;
|
||||
ubig32 length;
|
||||
ubig32 count;
|
||||
};
|
||||
|
||||
struct CodeSignatureBlobIndex {
|
||||
ubig32 type;
|
||||
ubig32 offset;
|
||||
u32 padding;
|
||||
};
|
||||
|
||||
struct CodeSignatureDirectory {
|
||||
ubig32 magic;
|
||||
ubig32 length;
|
||||
ubig32 version;
|
||||
ubig32 flags;
|
||||
ubig32 hash_offset;
|
||||
ubig32 ident_offset;
|
||||
ubig32 n_special_slots;
|
||||
ubig32 n_code_slots;
|
||||
ubig32 code_limit;
|
||||
u8 hash_size;
|
||||
u8 hash_type;
|
||||
u8 platform;
|
||||
u8 page_size;
|
||||
ubig32 spare2;
|
||||
ubig32 scatter_offset;
|
||||
ubig32 team_offset;
|
||||
ubig32 spare3;
|
||||
ubig64 code_limit64;
|
||||
ubig64 exec_seg_base;
|
||||
ubig64 exec_seg_limit;
|
||||
ubig64 exec_seg_flags;
|
||||
};
|
||||
|
||||
struct ARM64 {
|
||||
static constexpr u32 cputype = CPU_TYPE_ARM64;
|
||||
static constexpr u32 cpusubtype = CPU_SUBTYPE_ARM64_ALL;
|
||||
static constexpr u32 abs_rel = ARM64_RELOC_UNSIGNED;
|
||||
static constexpr u32 word_size = 8;
|
||||
static constexpr u32 stub_size = 12;
|
||||
static constexpr u32 stub_helper_hdr_size = 24;
|
||||
static constexpr u32 stub_helper_size = 12;
|
||||
};
|
||||
|
||||
struct X86_64 {
|
||||
static constexpr u32 cputype = CPU_TYPE_X86_64;
|
||||
static constexpr u32 cpusubtype = CPU_SUBTYPE_X86_64_ALL;
|
||||
static constexpr u32 abs_rel = X86_64_RELOC_UNSIGNED;
|
||||
static constexpr u32 word_size = 8;
|
||||
static constexpr u32 stub_size = 6;
|
||||
static constexpr u32 stub_helper_hdr_size = 16;
|
||||
static constexpr u32 stub_helper_size = 10;
|
||||
};
|
||||
|
||||
} // namespace mold::macho
|
||||
-489
@@ -1,489 +0,0 @@
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
#include "../cmdline.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
static std::pair<std::string_view, std::string_view>
|
||||
split_string(std::string_view str, char sep) {
|
||||
size_t pos = str.find(sep);
|
||||
if (pos == str.npos)
|
||||
return {str, ""};
|
||||
return {str.substr(0, pos), str.substr(pos + 1)};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void create_internal_file(Context<E> &ctx) {
|
||||
ObjectFile<E> *obj = new ObjectFile<E>;
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
ctx.objs.push_back(obj);
|
||||
|
||||
auto add = [&](std::string_view name) {
|
||||
Symbol<E> *sym = get_symbol(ctx, name);
|
||||
sym->file = obj;
|
||||
obj->syms.push_back(sym);
|
||||
return sym;
|
||||
};
|
||||
|
||||
add("__dyld_private");
|
||||
|
||||
switch (ctx.output_type) {
|
||||
case MH_EXECUTE: {
|
||||
Symbol<E> *sym = add("__mh_execute_header");
|
||||
sym->is_extern = true;
|
||||
sym->referenced_dynamically = true;
|
||||
sym->value = ctx.arg.pagezero_size;
|
||||
break;
|
||||
}
|
||||
case MH_DYLIB:
|
||||
add("__mh_dylib_header");
|
||||
break;
|
||||
case MH_BUNDLE:
|
||||
add("__mh_bundle_header");
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool compare_segments(const std::unique_ptr<OutputSegment<E>> &a,
|
||||
const std::unique_ptr<OutputSegment<E>> &b) {
|
||||
// We want to sort output segments in the following order:
|
||||
// __TEXT, __DATA_CONST, __DATA, <other segments>, __LINKEDIT
|
||||
auto get_rank = [](std::string_view name) {
|
||||
if (name == "__TEXT")
|
||||
return 0;
|
||||
if (name == "__DATA_CONST")
|
||||
return 1;
|
||||
if (name == "__DATA")
|
||||
return 2;
|
||||
return INT_MAX;
|
||||
};
|
||||
|
||||
std::string_view na = a->cmd.get_segname();
|
||||
std::string_view nb = b->cmd.get_segname();
|
||||
i64 ra = get_rank(na);
|
||||
i64 rb = get_rank(nb);
|
||||
if (ra != INT_MAX || rb != INT_MAX)
|
||||
return ra < rb;
|
||||
|
||||
if (na == "__LINKEDIT" || nb == "__LINKEDIT")
|
||||
return na != "__LINKEDIT";
|
||||
return na < nb;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool compare_chunks(const Chunk<E> *a, const Chunk<E> *b) {
|
||||
assert(a->hdr.get_segname() == b->hdr.get_segname());
|
||||
|
||||
if ((a->hdr.type == S_ZEROFILL) != (b->hdr.type == S_ZEROFILL))
|
||||
return a->hdr.type != S_ZEROFILL;
|
||||
|
||||
static const std::string_view rank[] = {
|
||||
// __TEXT
|
||||
"__mach_header",
|
||||
"__text",
|
||||
"__stubs",
|
||||
"__stub_helper",
|
||||
"__gcc_except_tab",
|
||||
"__cstring",
|
||||
"__unwind_info",
|
||||
// __DATA_CONST
|
||||
"__got",
|
||||
// __DATA
|
||||
"__la_symbol_ptr",
|
||||
"__data",
|
||||
"__thread_ptr",
|
||||
"__thread_data",
|
||||
"__thread_vars",
|
||||
"__thread_bss",
|
||||
"__common",
|
||||
"__bss",
|
||||
// __LINKEDIT
|
||||
"__rebase",
|
||||
"__binding",
|
||||
"__weak_binding",
|
||||
"__lazy_binding",
|
||||
"__export",
|
||||
"__func_starts",
|
||||
"__data_in_code",
|
||||
"__symbol_table",
|
||||
"__ind_sym_tab",
|
||||
"__string_table",
|
||||
"__code_signature",
|
||||
};
|
||||
|
||||
auto get_rank = [](const Chunk<E> *chunk) -> i64 {
|
||||
std::string_view name = chunk->hdr.get_sectname();
|
||||
i64 i = 0;
|
||||
for (; i < sizeof(rank) / sizeof(rank[0]); i++)
|
||||
if (name == rank[i])
|
||||
return i;
|
||||
return INT_MAX;
|
||||
};
|
||||
|
||||
i64 ra = get_rank(a);
|
||||
i64 rb = get_rank(b);
|
||||
if (ra == INT_MAX && rb == INT_MAX)
|
||||
return a->hdr.get_sectname() < b->hdr.get_sectname();
|
||||
return ra < rb;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void create_synthetic_chunks(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (std::unique_ptr<Subsection<E>> &subsec : file->subsections)
|
||||
subsec->isec.osec.add_subsec(subsec.get());
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (chunk != ctx.data && chunk->is_regular &&
|
||||
((OutputSection<E> *)chunk)->members.empty())
|
||||
continue;
|
||||
|
||||
OutputSegment<E> *seg =
|
||||
OutputSegment<E>::get_instance(ctx, chunk->hdr.get_segname());
|
||||
seg->chunks.push_back(chunk);
|
||||
}
|
||||
|
||||
sort(ctx.segments, compare_segments<E>);
|
||||
|
||||
for (std::unique_ptr<OutputSegment<E>> &seg : ctx.segments)
|
||||
sort(seg->chunks, compare_chunks<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void scan_unwind_info(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (UnwindRecord<E> &rec : file->unwind_records)
|
||||
if (!ctx.arg.dead_strip || rec.is_alive)
|
||||
if (rec.personality)
|
||||
rec.personality->flags |= NEEDS_GOT;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void export_symbols(Context<E> &ctx) {
|
||||
ctx.got.add(ctx, get_symbol(ctx, "dyld_stub_binder"));
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
for (Symbol<E> *sym : file->syms) {
|
||||
if (sym && sym->file == file) {
|
||||
if (sym->flags & NEEDS_GOT)
|
||||
ctx.got.add(ctx, sym);
|
||||
if (sym->flags & NEEDS_THREAD_PTR)
|
||||
ctx.thread_ptrs.add(ctx, sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (DylibFile<E> *file : ctx.dylibs) {
|
||||
for (Symbol<E> *sym : file->syms) {
|
||||
if (sym && sym->file == file) {
|
||||
if (sym->flags & NEEDS_STUB)
|
||||
ctx.stubs.add(ctx, sym);
|
||||
if (sym->flags & NEEDS_GOT)
|
||||
ctx.got.add(ctx, sym);
|
||||
if (sym->flags & NEEDS_THREAD_PTR)
|
||||
ctx.thread_ptrs.add(ctx, sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 assign_offsets(Context<E> &ctx) {
|
||||
i64 sect_idx = 1;
|
||||
for (std::unique_ptr<OutputSegment<E>> &seg : ctx.segments)
|
||||
for (Chunk<E> *chunk : seg->chunks)
|
||||
if (!chunk->is_hidden)
|
||||
chunk->sect_idx = sect_idx++;
|
||||
|
||||
i64 fileoff = 0;
|
||||
i64 vmaddr = ctx.arg.pagezero_size;
|
||||
|
||||
for (std::unique_ptr<OutputSegment<E>> &seg : ctx.segments) {
|
||||
seg->set_offset(ctx, fileoff, vmaddr);
|
||||
fileoff += seg->cmd.filesize;
|
||||
vmaddr += seg->cmd.vmsize;
|
||||
}
|
||||
return fileoff;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void fix_synthetic_symbol_values(Context<E> &ctx) {
|
||||
get_symbol(ctx, "__dyld_private")->value = ctx.data->hdr.addr;
|
||||
get_symbol(ctx, "__mh_dylib_header")->value = ctx.data->hdr.addr;
|
||||
get_symbol(ctx, "__mh_bundle_header")->value = ctx.data->hdr.addr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *find_framework(Context<E> &ctx, std::string name) {
|
||||
std::string suffix;
|
||||
std::tie(name, suffix) = split_string(name, ',');
|
||||
|
||||
for (std::string path : ctx.arg.framework_paths) {
|
||||
path = get_realpath(path + "/" + name + ".framework/" + name);
|
||||
|
||||
if (!suffix.empty())
|
||||
if (auto *mf = MappedFile<Context<E>>::open(ctx, path + suffix))
|
||||
return mf;
|
||||
|
||||
if (auto *mf = MappedFile<Context<E>>::open(ctx, path + ".tbd"))
|
||||
return mf;
|
||||
|
||||
if (auto *mf = MappedFile<Context<E>>::open(ctx, path))
|
||||
return mf;
|
||||
}
|
||||
Fatal(ctx) << "-framework not found: " << name;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *find_library(Context<E> &ctx, std::string name) {
|
||||
for (std::string dir : ctx.arg.library_paths) {
|
||||
for (std::string ext : {".tbd", ".dylib", ".a"}) {
|
||||
std::string path = dir + "/lib" + name + ext;
|
||||
if (MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, path))
|
||||
return mf;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static MappedFile<Context<E>> *
|
||||
strip_universal_header(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
FatHeader &hdr = *(FatHeader *)mf->data;
|
||||
assert(hdr.magic == FAT_MAGIC);
|
||||
|
||||
FatArch *arch = (FatArch *)(mf->data + sizeof(hdr));
|
||||
for (i64 i = 0; i < hdr.nfat_arch; i++)
|
||||
if (arch[i].cputype == E::cputype)
|
||||
return mf->slice(ctx, mf->name, arch[i].offset, arch[i].size);
|
||||
Fatal(ctx) << mf->name << ": fat file contains no matching file";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
bool is_needed = false) {
|
||||
if (get_file_type(mf) == FileType::MACH_UNIVERSAL)
|
||||
mf = strip_universal_header(ctx, mf);
|
||||
|
||||
switch (get_file_type(mf)) {
|
||||
case FileType::TAPI:
|
||||
case FileType::MACH_DYLIB:
|
||||
ctx.dylibs.push_back(DylibFile<E>::create(ctx, mf));
|
||||
if (is_needed || !ctx.arg.dead_strip_dylibs)
|
||||
ctx.dylibs.back()->is_needed = true;
|
||||
break;
|
||||
case FileType::MACH_OBJ:
|
||||
ctx.objs.push_back(ObjectFile<E>::create(ctx, mf, ""));
|
||||
break;
|
||||
case FileType::AR:
|
||||
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf))
|
||||
if (get_file_type(child) == FileType::MACH_OBJ)
|
||||
ctx.objs.push_back(ObjectFile<E>::create(ctx, child, mf->name));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
// Fatal(ctx) << mf->name << ": unknown file type";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<std::string>
|
||||
read_filelist(Context<E> &ctx, std::string arg) {
|
||||
std::string path;
|
||||
std::string dir;
|
||||
|
||||
if (size_t pos = arg.find(','); pos != arg.npos) {
|
||||
path = arg.substr(0, pos);
|
||||
dir = arg.substr(pos + 1) + "/";
|
||||
} else {
|
||||
path = arg;
|
||||
}
|
||||
|
||||
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, path);
|
||||
if (!mf)
|
||||
Fatal(ctx) << "-filepath: cannot open " << path;
|
||||
|
||||
std::vector<std::string> vec;
|
||||
for (std::string_view str = mf->get_contents(); !str.empty();) {
|
||||
std::string_view path;
|
||||
std::tie(path, str) = split_string(str, '\n');
|
||||
vec.push_back(path_clean(dir + std::string(path)));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
while (!args.empty()) {
|
||||
if (args[0].starts_with("-filelist")) {
|
||||
for (std::string &path : read_filelist(ctx, args[1])) {
|
||||
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, path);
|
||||
if (!mf)
|
||||
Fatal(ctx) << "-filepath " << args[1] << ": cannot open file: " << path;
|
||||
read_file(ctx, mf);
|
||||
}
|
||||
args = args.subspan(2);
|
||||
} else if (args[0] == "-framework" || args[0] == "-needed_framework") {
|
||||
bool needed = (args[0] == "-needed_framework");
|
||||
read_file(ctx, find_framework(ctx, args[1]), needed);
|
||||
args = args.subspan(2);
|
||||
} else if (args[0] == "-l" || args[0] == "-needed-l") {
|
||||
MappedFile<Context<E>> *mf = find_library(ctx, args[1]);
|
||||
if (!mf)
|
||||
Fatal(ctx) << "library not found: -l" << args[1];
|
||||
bool needed = (args[0] == "-needed-l");
|
||||
read_file(ctx, mf, needed);
|
||||
args = args.subspan(2);
|
||||
} else {
|
||||
read_file(ctx, MappedFile<Context<E>>::must_open(ctx, args[0]));
|
||||
args = args.subspan(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.objs.empty())
|
||||
Fatal(ctx) << "no input files";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static int do_main(int argc, char **argv) {
|
||||
Context<E> ctx;
|
||||
|
||||
if (argc > 1 && std::string_view(argv[1]) == "-dump") {
|
||||
if (argc != 3)
|
||||
Fatal(ctx) << "usage: ld64.mold -dump <executable-name>\n";
|
||||
dump_file(argv[2]);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
for (i64 i = 0; i < argc; i++)
|
||||
ctx.cmdline_args.push_back(argv[i]);
|
||||
|
||||
std::vector<std::string> file_args;
|
||||
parse_nonpositional_args(ctx, file_args);
|
||||
|
||||
if (ctx.arg.arch != E::cputype) {
|
||||
if (ctx.arg.arch == CPU_TYPE_X86_64)
|
||||
return do_main<X86_64>(argc, argv);
|
||||
}
|
||||
|
||||
read_input_files(ctx, file_args);
|
||||
|
||||
i64 priority = 1;
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->priority = priority++;
|
||||
for (DylibFile<E> *dylib : ctx.dylibs)
|
||||
dylib->priority = priority++;
|
||||
|
||||
for (i64 i = 0; i < ctx.dylibs.size(); i++)
|
||||
ctx.dylibs[i]->dylib_idx = i + 1;
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->parse(ctx);
|
||||
for (DylibFile<E> *dylib : ctx.dylibs)
|
||||
dylib->parse(ctx);
|
||||
|
||||
if (ctx.arg.ObjC)
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (!file->archive_name.empty() && file->is_objc_object(ctx))
|
||||
file->is_alive = true;
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file->is_alive)
|
||||
file->resolve_regular_symbols(ctx);
|
||||
else
|
||||
file->resolve_lazy_symbols(ctx);
|
||||
}
|
||||
|
||||
std::vector<ObjectFile<E> *> live_objs;
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->is_alive)
|
||||
live_objs.push_back(file);
|
||||
|
||||
for (i64 i = 0; i < live_objs.size(); i++)
|
||||
append(live_objs, live_objs[i]->mark_live_objects(ctx));
|
||||
|
||||
for (DylibFile<E> *dylib : ctx.dylibs)
|
||||
dylib->resolve_symbols(ctx);
|
||||
|
||||
if (ctx.output_type == MH_EXECUTE && !get_symbol(ctx, ctx.arg.entry)->file)
|
||||
Error(ctx) << "undefined entry point symbol: " << ctx.arg.entry;
|
||||
|
||||
create_internal_file(ctx);
|
||||
|
||||
std::erase_if(ctx.objs, [](ObjectFile<E> *file) { return !file->is_alive; });
|
||||
std::erase_if(ctx.dylibs, [](DylibFile<E> *file) { return !file->is_alive; });
|
||||
|
||||
if (ctx.arg.trace) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
SyncOut(ctx) << *file;
|
||||
for (DylibFile<E> *file : ctx.dylibs)
|
||||
SyncOut(ctx) << *file;
|
||||
}
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->convert_common_symbols(ctx);
|
||||
|
||||
if (ctx.arg.dead_strip)
|
||||
dead_strip(ctx);
|
||||
|
||||
create_synthetic_chunks(ctx);
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->check_duplicate_symbols(ctx);
|
||||
|
||||
for (i64 i = 0; i < ctx.segments.size(); i++)
|
||||
ctx.segments[i]->seg_idx = i + 1;
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (std::unique_ptr<Subsection<E> > &subsec : file->subsections)
|
||||
subsec->scan_relocations(ctx);
|
||||
|
||||
scan_unwind_info(ctx);
|
||||
|
||||
if (ctx.arg.dead_strip_dylibs)
|
||||
std::erase_if(ctx.dylibs, [](DylibFile<E> *file) { return !file->is_needed; });
|
||||
|
||||
export_symbols(ctx);
|
||||
i64 output_size = assign_offsets(ctx);
|
||||
fix_synthetic_symbol_values(ctx);
|
||||
|
||||
ctx.output_file = OutputFile<E>::open(ctx, ctx.arg.output, output_size, 0777);
|
||||
ctx.buf = ctx.output_file->buf;
|
||||
|
||||
for (std::unique_ptr<OutputSegment<E>> &seg : ctx.segments)
|
||||
seg->copy_buf(ctx);
|
||||
ctx.code_sig.write_signature(ctx);
|
||||
|
||||
ctx.output_file->close(ctx);
|
||||
ctx.checkpoint();
|
||||
|
||||
if (!ctx.arg.map.empty())
|
||||
print_map(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (!getenv("MOLD_SUPPRESS_MACHO_WARNING")) {
|
||||
std::cerr <<
|
||||
R"(********************************************************************************
|
||||
mold for macOS is pre-alpha. Do not use unless you know what you are doing.
|
||||
Do not report bugs because it's too early to manage missing features as bugs.
|
||||
********************************************************************************
|
||||
)";
|
||||
}
|
||||
|
||||
return do_main<ARM64>(argc, argv);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
struct Sym {
|
||||
u64 addr;
|
||||
u32 size;
|
||||
u32 fileidx;
|
||||
std::string_view name;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void print_map(Context<E> &ctx) {
|
||||
std::ofstream out(ctx.arg.map.c_str());
|
||||
if (!out.is_open())
|
||||
Fatal(ctx) << "cannot open " << ctx.arg.map << ": " << errno_string();
|
||||
|
||||
out << "# Path: " << ctx.arg.output << "\n"
|
||||
<< "# Arch: x86-64\n"
|
||||
<< "# Object files:\n";
|
||||
|
||||
std::vector<Sym> syms;
|
||||
|
||||
for (i64 i = 0; i < ctx.objs.size(); i++){
|
||||
ObjectFile<E> &file = *ctx.objs[i];
|
||||
|
||||
if (file.is_alive) {
|
||||
out << "[" << std::setw(3) << i << "] " << file << "\n";
|
||||
for (Symbol<E> *sym : file.syms)
|
||||
if (sym->file == &file)
|
||||
syms.push_back({sym->get_addr(ctx), 0, (u32)i, sym->name});
|
||||
}
|
||||
}
|
||||
|
||||
sort(syms, [](const Sym &a, const Sym &b) {
|
||||
return a.addr < b.addr;
|
||||
});
|
||||
|
||||
out << "# Sections:\n"
|
||||
<< "# Address Size Segment Section\n";
|
||||
|
||||
for (std::unique_ptr<OutputSegment<E>> &seg : ctx.segments) {
|
||||
for (Chunk<E> *chunk : seg->chunks) {
|
||||
if (!chunk->is_hidden) {
|
||||
out << "0x" << std::hex
|
||||
<< std::setw(8) << std::setfill('0') << chunk->hdr.addr
|
||||
<< " 0x"
|
||||
<< std::setw(8) << std::setfill('0') << chunk->hdr.size
|
||||
<< " " << std::setw(7) << std::left << std::setfill(' ')
|
||||
<< chunk->hdr.get_segname()
|
||||
<< " " << chunk->hdr.get_sectname() << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out << "# Symbols:\n"
|
||||
<< "# Address Size File Name\n";
|
||||
|
||||
for (Sym &sym : syms) {
|
||||
out << "0x" << std::hex
|
||||
<< std::setw(8) << std::setfill('0') << sym.addr
|
||||
<< " 0x"
|
||||
<< std::setw(8) << std::setfill('0') << 0
|
||||
<< " ["
|
||||
<< std::setw(3) << std::right << std::setfill(' ') << std::dec
|
||||
<< sym.fileidx
|
||||
<< "] " << sym.name << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void print_map(Context<E> &)
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
|
||||
} // namespace mold::macho
|
||||
-920
@@ -1,920 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "macho.h"
|
||||
#include "../mold.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <tbb/concurrent_hash_map.h>
|
||||
#include <tbb/spin_mutex.h>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
static constexpr i64 COMMON_PAGE_SIZE = 0x4000;
|
||||
static constexpr i64 SHA256_SIZE = 32;
|
||||
|
||||
template <typename E> class Chunk;
|
||||
template <typename E> class InputSection;
|
||||
template <typename E> class OutputSection;
|
||||
template <typename E> class Subsection;
|
||||
template <typename E> struct Context;
|
||||
template <typename E> struct Symbol;
|
||||
|
||||
//
|
||||
// object-file.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
struct Relocation {
|
||||
u32 offset = 0;
|
||||
u8 type = -1;
|
||||
u8 p2size = 0;
|
||||
bool is_pcrel = false;
|
||||
i64 addend = 0;
|
||||
Symbol<E> *sym = nullptr;
|
||||
Subsection<E> *subsec = nullptr;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct UnwindRecord {
|
||||
UnwindRecord(u32 len, u32 enc) : code_len(len), encoding(enc) {}
|
||||
|
||||
inline u64 get_func_raddr(Context<E> &ctx) const;
|
||||
|
||||
Subsection<E> *subsec = nullptr;
|
||||
u32 offset = 0;
|
||||
u32 code_len;
|
||||
u32 encoding;
|
||||
Symbol<E> *personality = nullptr;
|
||||
Subsection<E> *lsda = nullptr;
|
||||
u32 lsda_offset = 0;
|
||||
bool is_alive = false;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class InputFile {
|
||||
public:
|
||||
virtual ~InputFile() = default;
|
||||
|
||||
MappedFile<Context<E>> *mf = nullptr;
|
||||
std::vector<Symbol<E> *> syms;
|
||||
i64 priority = 0;
|
||||
std::atomic_bool is_alive = false;
|
||||
std::string archive_name;
|
||||
|
||||
virtual bool is_dylib() const = 0;
|
||||
|
||||
protected:
|
||||
InputFile() = default;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class ObjectFile : public InputFile<E> {
|
||||
public:
|
||||
ObjectFile() = default;
|
||||
|
||||
static ObjectFile *create(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name);
|
||||
bool is_dylib() const override { return false; }
|
||||
void parse(Context<E> &ctx);
|
||||
Subsection<E> *find_subsection(Context<E> &ctx, u32 addr);
|
||||
void parse_compact_unwind(Context<E> &ctx, MachSection &hdr);
|
||||
void resolve_regular_symbols(Context<E> &ctx);
|
||||
void resolve_lazy_symbols(Context<E> &ctx);
|
||||
bool is_objc_object(Context<E> &ctx);
|
||||
std::vector<ObjectFile *> mark_live_objects(Context<E> &ctx);
|
||||
void convert_common_symbols(Context<E> &ctx);
|
||||
void check_duplicate_symbols(Context<E> &ctx);
|
||||
|
||||
Relocation<E> read_reloc(Context<E> &ctx, const MachSection &hdr, MachRel r);
|
||||
|
||||
std::vector<std::unique_ptr<InputSection<E>>> sections;
|
||||
std::vector<std::unique_ptr<Subsection<E>>> subsections;
|
||||
std::vector<u32> sym_to_subsec;
|
||||
std::span<MachSym> mach_syms;
|
||||
std::vector<Symbol<E>> local_syms;
|
||||
std::vector<UnwindRecord<E>> unwind_records;
|
||||
std::span<DataInCodeEntry> data_in_code_entries;
|
||||
|
||||
private:
|
||||
void parse_sections(Context<E> &ctx);
|
||||
void parse_symtab(Context<E> &ctx);
|
||||
void split_subsections(Context<E> &ctx);
|
||||
void parse_data_in_code(Context<E> &ctx);
|
||||
LoadCommand *find_load_command(Context<E> &ctx, u32 type);
|
||||
i64 find_subsection_idx(Context<E> &ctx, u32 addr);
|
||||
void override_symbol(Context<E> &ctx, i64 symidx);
|
||||
InputSection<E> *get_common_sec(Context<E> &ctx);
|
||||
|
||||
MachSection *unwind_sec = nullptr;
|
||||
std::unique_ptr<MachSection> common_hdr;
|
||||
InputSection<E> *common_sec = nullptr;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class DylibFile : public InputFile<E> {
|
||||
public:
|
||||
static DylibFile *create(Context<E> &ctx, MappedFile<Context<E>> *mf);
|
||||
void parse(Context<E> &ctx);
|
||||
void resolve_symbols(Context<E> &ctx);
|
||||
|
||||
std::string_view install_name;
|
||||
i64 dylib_idx = 0;
|
||||
std::atomic_bool is_needed = false;
|
||||
|
||||
bool is_dylib() const override { return true; }
|
||||
|
||||
private:
|
||||
void parse_dylib(Context<E> &ctx);
|
||||
void read_trie(Context<E> &ctx, u8 *start, i64 offset = 0,
|
||||
const std::string &prefix = "");
|
||||
|
||||
DylibFile() {
|
||||
this->is_alive = true;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const InputFile<E> &file);
|
||||
|
||||
//
|
||||
// input-sections.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
class InputSection {
|
||||
public:
|
||||
InputSection(Context<E> &ctx, ObjectFile<E> &file, const MachSection &hdr);
|
||||
void parse_relocations(Context<E> &ctx);
|
||||
|
||||
ObjectFile<E> &file;
|
||||
const MachSection &hdr;
|
||||
OutputSection<E> &osec;
|
||||
std::string_view contents;
|
||||
std::vector<Symbol<E> *> syms;
|
||||
std::vector<Relocation<E>> rels;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const InputSection<E> &sec);
|
||||
|
||||
template <typename E>
|
||||
class Subsection {
|
||||
public:
|
||||
inline u64 get_addr(Context<E> &ctx) const;
|
||||
|
||||
std::string_view get_contents() {
|
||||
assert(isec.hdr.type != S_ZEROFILL);
|
||||
return isec.contents.substr(input_offset, input_size);
|
||||
}
|
||||
|
||||
std::span<UnwindRecord<E>> get_unwind_records() {
|
||||
return std::span<UnwindRecord<E>>(isec.file.unwind_records)
|
||||
.subspan(unwind_offset, nunwind);
|
||||
}
|
||||
|
||||
std::span<Relocation<E>> get_rels() const {
|
||||
return std::span<Relocation<E>>(isec.rels).subspan(rel_offset, nrels);
|
||||
}
|
||||
|
||||
void scan_relocations(Context<E> &ctx);
|
||||
void apply_reloc(Context<E> &ctx, u8 *buf);
|
||||
|
||||
InputSection<E> &isec;
|
||||
u32 input_offset = 0;
|
||||
u32 input_size = 0;
|
||||
u32 input_addr = 0;
|
||||
u32 rel_offset = 0;
|
||||
u32 nrels = 0;
|
||||
u32 unwind_offset = 0;
|
||||
u32 nunwind = 0;
|
||||
u32 raddr = -1;
|
||||
u16 p2align = 0;
|
||||
std::atomic_bool is_alive = false;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::vector<Relocation<E>>
|
||||
read_relocations(Context<E> &ctx, ObjectFile<E> &file, const MachSection &hdr);
|
||||
|
||||
//
|
||||
// Symbol
|
||||
//
|
||||
|
||||
enum {
|
||||
NEEDS_GOT = 1 << 0,
|
||||
NEEDS_STUB = 1 << 1,
|
||||
NEEDS_THREAD_PTR = 1 << 2,
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct Symbol {
|
||||
Symbol() = default;
|
||||
Symbol(std::string_view name) : name(name) {}
|
||||
Symbol(const Symbol<E> &other) : name(other.name) {}
|
||||
|
||||
std::string_view name;
|
||||
InputFile<E> *file = nullptr;
|
||||
Subsection<E> *subsec = nullptr;
|
||||
u64 value = 0;
|
||||
|
||||
i32 stub_idx = -1;
|
||||
i32 got_idx = -1;
|
||||
i32 tlv_idx = -1;
|
||||
|
||||
tbb::spin_mutex mu;
|
||||
|
||||
std::atomic_uint8_t flags = 0;
|
||||
|
||||
u8 is_extern : 1 = false;
|
||||
u8 is_lazy : 1 = false;
|
||||
u8 is_common : 1 = false;
|
||||
u8 referenced_dynamically : 1 = false;
|
||||
|
||||
inline u64 get_addr(Context<E> &ctx) const;
|
||||
inline u64 get_got_addr(Context<E> &ctx) const;
|
||||
inline u64 get_tlv_addr(Context<E> &ctx) const;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym);
|
||||
|
||||
//
|
||||
// output-chunks.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
class OutputSegment {
|
||||
public:
|
||||
static OutputSegment<E> *
|
||||
get_instance(Context<E> &ctx, std::string_view name);
|
||||
|
||||
void set_offset(Context<E> &ctx, i64 fileoff, u64 vmaddr);
|
||||
void copy_buf(Context<E> &ctx);
|
||||
|
||||
std::string_view name;
|
||||
SegmentCommand cmd = {};
|
||||
i32 seg_idx = -1;
|
||||
std::vector<Chunk<E> *> chunks;
|
||||
|
||||
private:
|
||||
OutputSegment(std::string_view name);
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class Chunk {
|
||||
public:
|
||||
inline Chunk(Context<E> &ctx, std::string_view segname,
|
||||
std::string_view sectname);
|
||||
|
||||
virtual ~Chunk() = default;
|
||||
virtual void compute_size(Context<E> &ctx) {};
|
||||
virtual void copy_buf(Context<E> &ctx) {}
|
||||
|
||||
MachSection hdr = {};
|
||||
u32 sect_idx = 0;
|
||||
bool is_hidden = false;
|
||||
bool is_regular = false;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const Chunk<E> &chunk);
|
||||
|
||||
template <typename E>
|
||||
class OutputMachHeader : public Chunk<E> {
|
||||
public:
|
||||
OutputMachHeader(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__TEXT", "__mach_header") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputSection : public Chunk<E> {
|
||||
public:
|
||||
static OutputSection<E> *
|
||||
get_instance(Context<E> &ctx, std::string_view segname,
|
||||
std::string_view sectname);
|
||||
|
||||
OutputSection(Context<E> &ctx, std::string_view segname,
|
||||
std::string_view sectname)
|
||||
: Chunk<E>(ctx, segname, sectname) {
|
||||
this->is_regular = true;
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
void add_subsec(Subsection<E> *subsec) {
|
||||
members.push_back(subsec);
|
||||
this->hdr.p2align = std::max<u32>(this->hdr.p2align, subsec->p2align);
|
||||
this->hdr.attr |= subsec->isec.hdr.attr;
|
||||
this->hdr.type = subsec->isec.hdr.type;
|
||||
}
|
||||
|
||||
std::vector<Subsection<E> *> members;
|
||||
};
|
||||
|
||||
class RebaseEncoder {
|
||||
public:
|
||||
RebaseEncoder();
|
||||
void add(i64 seg_idx, i64 offset);
|
||||
void flush();
|
||||
void finish();
|
||||
|
||||
std::vector<u8> buf;
|
||||
|
||||
private:
|
||||
i64 cur_seg = -1;
|
||||
i64 cur_off = 0;
|
||||
i64 times = 0;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputRebaseSection : public Chunk<E> {
|
||||
public:
|
||||
OutputRebaseSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__rebase") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<u8> contents;
|
||||
};
|
||||
|
||||
class BindEncoder {
|
||||
public:
|
||||
BindEncoder();
|
||||
void add(i64 dylib_idx, std::string_view sym, i64 flags, i64 seg_idx,
|
||||
i64 offset);
|
||||
void finish();
|
||||
|
||||
std::vector<u8> buf;
|
||||
|
||||
private:
|
||||
std::string_view last_sym;
|
||||
i64 last_flags = -1;
|
||||
i64 last_dylib = -1;
|
||||
i64 last_seg = -1;
|
||||
i64 last_off = -1;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputBindSection : public Chunk<E> {
|
||||
public:
|
||||
OutputBindSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__binding") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<u8> contents;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputLazyBindSection : public Chunk<E> {
|
||||
public:
|
||||
OutputLazyBindSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__lazy_binding") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
}
|
||||
|
||||
void add(Context<E> &ctx, Symbol<E> &sym, i64 flags);
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<u8> contents;
|
||||
};
|
||||
|
||||
class ExportEncoder {
|
||||
public:
|
||||
void add(std::string_view name, u32 flags, u64 addr);
|
||||
i64 finish();
|
||||
|
||||
void write_trie(u8 *buf) {
|
||||
write_trie(buf, root);
|
||||
}
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
std::string_view name;
|
||||
u32 flags;
|
||||
u64 addr;
|
||||
};
|
||||
|
||||
struct TrieNode {
|
||||
std::string_view prefix;
|
||||
bool is_leaf = false;
|
||||
u32 flags = 0;
|
||||
u64 addr = 0;
|
||||
u32 offset = -1;
|
||||
std::vector<TrieNode> children;
|
||||
};
|
||||
|
||||
static i64 common_prefix_len(std::span<Entry> entries, i64 len);
|
||||
static TrieNode construct_trie(std::span<Entry> entries, i64 len);
|
||||
static i64 set_offset(TrieNode &node, i64 offset);
|
||||
void write_trie(u8 *buf, TrieNode &node);
|
||||
|
||||
TrieNode root;
|
||||
std::vector<Entry> entries;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputExportSection : public Chunk<E> {
|
||||
public:
|
||||
OutputExportSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__export") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
private:
|
||||
ExportEncoder enc;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputFunctionStartsSection : public Chunk<E> {
|
||||
public:
|
||||
OutputFunctionStartsSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__func_starts") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<u8> contents;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputSymtabSection : public Chunk<E> {
|
||||
public:
|
||||
OutputSymtabSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__symbol_table") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
struct Entry {
|
||||
Symbol<E> *sym;
|
||||
i64 stroff;
|
||||
};
|
||||
|
||||
std::vector<Entry> locals;
|
||||
std::vector<Entry> globals;
|
||||
std::vector<Entry> undefs;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputStrtabSection : public Chunk<E> {
|
||||
public:
|
||||
OutputStrtabSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__string_table") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
}
|
||||
|
||||
i64 add_string(std::string_view str);
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::string contents{1, '\0'};
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputIndirectSymtabSection : public Chunk<E> {
|
||||
public:
|
||||
OutputIndirectSymtabSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__ind_sym_tab") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
|
||||
static constexpr i64 ENTRY_SIZE = 4;
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
struct Entry {
|
||||
Symbol<E> *sym;
|
||||
i64 symtab_idx;
|
||||
};
|
||||
|
||||
std::vector<Entry> stubs;
|
||||
std::vector<Entry> gots;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class CodeSignatureSection : public Chunk<E> {
|
||||
public:
|
||||
CodeSignatureSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__code_signature") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = std::countr_zero(16U);
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void write_signature(Context<E> &ctx);
|
||||
|
||||
static constexpr i64 BLOCK_SIZE = 4096;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class DataInCodeSection : public Chunk<E> {
|
||||
public:
|
||||
DataInCodeSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__data_in_code") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = std::countr_zero(alignof(DataInCodeEntry));
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<DataInCodeEntry> contents;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class StubsSection : public Chunk<E> {
|
||||
public:
|
||||
StubsSection(Context<E> &ctx) : Chunk<E>(ctx, "__TEXT", "__stubs") {
|
||||
this->hdr.p2align = std::countr_zero(2U);
|
||||
this->hdr.type = S_SYMBOL_STUBS;
|
||||
this->hdr.attr = S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS;
|
||||
this->hdr.reserved2 = E::stub_size;
|
||||
}
|
||||
|
||||
void add(Context<E> &ctx, Symbol<E> *sym);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<Symbol<E> *> syms;
|
||||
std::vector<u32> bind_offsets;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class StubHelperSection : public Chunk<E> {
|
||||
public:
|
||||
StubHelperSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__TEXT", "__stub_helper") {
|
||||
this->hdr.p2align = std::countr_zero(4U);
|
||||
this->hdr.attr = S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS;
|
||||
}
|
||||
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class UnwindEncoder {
|
||||
public:
|
||||
std::vector<u8> encode(Context<E> &ctx, std::span<UnwindRecord<E>> records);
|
||||
|
||||
private:
|
||||
u32 encode_personality(Context<E> &ctx, Symbol<E> *sym);
|
||||
|
||||
std::vector<std::span<UnwindRecord<E>>>
|
||||
split_records(Context<E> &ctx, std::span<UnwindRecord<E>>);
|
||||
|
||||
std::vector<Symbol<E> *> personalities;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class UnwindInfoSection : public Chunk<E> {
|
||||
public:
|
||||
UnwindInfoSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__TEXT", "__unwind_info") {
|
||||
this->hdr.p2align = std::countr_zero(4U);
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<u8> contents;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class GotSection : public Chunk<E> {
|
||||
public:
|
||||
GotSection(Context<E> &ctx) : Chunk<E>(ctx, "__DATA_CONST", "__got") {
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
this->hdr.type = S_NON_LAZY_SYMBOL_POINTERS;
|
||||
}
|
||||
|
||||
void add(Context<E> &ctx, Symbol<E> *sym);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<Symbol<E> *> syms;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class LazySymbolPtrSection : public Chunk<E> {
|
||||
public:
|
||||
LazySymbolPtrSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__DATA", "__la_symbol_ptr") {
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
this->hdr.type = S_LAZY_SYMBOL_POINTERS;
|
||||
}
|
||||
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class ThreadPtrsSection : public Chunk<E> {
|
||||
public:
|
||||
ThreadPtrsSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__DATA", "__thread_ptrs") {
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
this->hdr.type = S_THREAD_LOCAL_VARIABLE_POINTERS;
|
||||
}
|
||||
|
||||
void add(Context<E> &ctx, Symbol<E> *sym);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<Symbol<E> *> syms;
|
||||
};
|
||||
|
||||
//
|
||||
// mapfile.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
void print_map(Context<E> &ctx);
|
||||
|
||||
//
|
||||
// dumper.cc
|
||||
//
|
||||
|
||||
void dump_file(std::string path);
|
||||
|
||||
//
|
||||
// output-file.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
class OutputFile {
|
||||
public:
|
||||
static std::unique_ptr<OutputFile>
|
||||
open(Context<E> &ctx, std::string path, i64 filesize, i64 perm);
|
||||
|
||||
virtual void close(Context<E> &ctx) = 0;
|
||||
virtual ~OutputFile() {}
|
||||
|
||||
u8 *buf = nullptr;
|
||||
std::string path;
|
||||
i64 filesize;
|
||||
bool is_mmapped;
|
||||
bool is_unmapped = false;
|
||||
|
||||
protected:
|
||||
OutputFile(std::string path, i64 filesize, bool is_mmapped)
|
||||
: path(path), filesize(filesize), is_mmapped(is_mmapped) {}
|
||||
};
|
||||
|
||||
//
|
||||
// yaml.cc
|
||||
//
|
||||
|
||||
struct YamlNode {
|
||||
std::variant<std::string_view,
|
||||
std::vector<YamlNode>,
|
||||
std::map<std::string_view, YamlNode>> data;
|
||||
};
|
||||
|
||||
struct YamlError {
|
||||
std::string msg;
|
||||
i64 pos;
|
||||
};
|
||||
|
||||
std::variant<std::vector<YamlNode>, YamlError>
|
||||
parse_yaml(std::string_view str);
|
||||
|
||||
//
|
||||
// tapi.cc
|
||||
//
|
||||
|
||||
struct TextDylib {
|
||||
std::string_view uuid;
|
||||
std::string_view install_name;
|
||||
std::string_view current_version = "1.0";
|
||||
std::string_view parent_umbrella;
|
||||
std::vector<std::string_view> reexported_libs;
|
||||
std::vector<std::string_view> exports;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
TextDylib parse_tbd(Context<E> &ctx, MappedFile<Context<E>> *mf);
|
||||
|
||||
//
|
||||
// cmdline.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
void parse_nonpositional_args(Context<E> &ctx,
|
||||
std::vector<std::string> &remaining);
|
||||
|
||||
//
|
||||
// dead-strip.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
void dead_strip(Context<E> &ctx);
|
||||
|
||||
//
|
||||
// main.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
struct Context {
|
||||
Context() {
|
||||
text_seg = OutputSegment<E>::get_instance(*this, "__TEXT");
|
||||
data_const_seg = OutputSegment<E>::get_instance(*this, "__DATA_CONST");
|
||||
data_seg = OutputSegment<E>::get_instance(*this, "__DATA");
|
||||
linkedit_seg = OutputSegment<E>::get_instance(*this, "__LINKEDIT");
|
||||
|
||||
text = OutputSection<E>::get_instance(*this, "__TEXT", "__text");
|
||||
data = OutputSection<E>::get_instance(*this, "__DATA", "__data");
|
||||
bss = OutputSection<E>::get_instance(*this, "__DATA", "__bss");
|
||||
cstring = OutputSection<E>::get_instance(*this, "__TEXT", "__cstring");
|
||||
common = OutputSection<E>::get_instance(*this, "__DATA", "__common");
|
||||
|
||||
bss->hdr.type = S_ZEROFILL;
|
||||
cstring->hdr.type = S_CSTRING_LITERALS;
|
||||
common->hdr.type = S_ZEROFILL;
|
||||
}
|
||||
|
||||
Context(const Context<E> &) = delete;
|
||||
|
||||
void checkpoint() {
|
||||
if (has_error) {
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Command-line arguments
|
||||
struct {
|
||||
bool ObjC = false;
|
||||
bool adhoc_codesign = true;
|
||||
bool color_diagnostics = false;
|
||||
bool dead_strip = true;
|
||||
bool dead_strip_dylibs = false;
|
||||
bool deduplicate = true;
|
||||
bool demangle = false;
|
||||
bool dylib = false;
|
||||
bool dynamic = true;
|
||||
bool fatal_warnings = false;
|
||||
bool noinhibit_exec = false;
|
||||
bool trace = false;
|
||||
i64 arch = CPU_TYPE_ARM64;
|
||||
i64 headerpad = 256;
|
||||
i64 pagezero_size = 0;
|
||||
i64 platform = PLATFORM_MACOS;
|
||||
i64 platform_min_version = 0;
|
||||
i64 platform_sdk_version = 0;
|
||||
std::string chroot;
|
||||
std::string entry = "_main";
|
||||
std::string map;
|
||||
std::string output = "a.out";
|
||||
std::vector<std::string> framework_paths;
|
||||
std::vector<std::string> library_paths;
|
||||
std::vector<std::string> rpath;
|
||||
std::vector<std::string> syslibroot;
|
||||
} arg;
|
||||
|
||||
std::vector<std::string_view> cmdline_args;
|
||||
u32 output_type = MH_EXECUTE;
|
||||
|
||||
bool has_error = false;
|
||||
|
||||
tbb::concurrent_hash_map<std::string_view, Symbol<E>> symbol_map;
|
||||
|
||||
std::unique_ptr<OutputFile<E>> output_file;
|
||||
u8 *buf;
|
||||
|
||||
tbb::concurrent_vector<std::unique_ptr<ObjectFile<E>>> obj_pool;
|
||||
tbb::concurrent_vector<std::unique_ptr<DylibFile<E>>> dylib_pool;
|
||||
tbb::concurrent_vector<std::unique_ptr<u8[]>> string_pool;
|
||||
tbb::concurrent_vector<std::unique_ptr<MappedFile<Context<E>>>> mf_pool;
|
||||
std::vector<std::unique_ptr<OutputSection<E>>> osec_pool;
|
||||
|
||||
tbb::concurrent_vector<std::unique_ptr<TimerRecord>> timer_records;
|
||||
|
||||
std::vector<ObjectFile<E> *> objs;
|
||||
std::vector<DylibFile<E> *> dylibs;
|
||||
|
||||
OutputSegment<E> *text_seg = nullptr;
|
||||
OutputSegment<E> *data_const_seg = nullptr;
|
||||
OutputSegment<E> *data_seg = nullptr;
|
||||
OutputSegment<E> *linkedit_seg = nullptr;
|
||||
|
||||
std::vector<std::unique_ptr<OutputSegment<E>>> segments;
|
||||
std::vector<Chunk<E> *> chunks;
|
||||
|
||||
OutputMachHeader<E> mach_hdr{*this};
|
||||
StubsSection<E> stubs{*this};
|
||||
StubHelperSection<E> stub_helper{*this};
|
||||
UnwindInfoSection<E> unwind_info{*this};
|
||||
GotSection<E> got{*this};
|
||||
LazySymbolPtrSection<E> lazy_symbol_ptr{*this};
|
||||
CodeSignatureSection<E> code_sig{*this};
|
||||
DataInCodeSection<E> data_in_code{*this};
|
||||
ThreadPtrsSection<E> thread_ptrs{*this};
|
||||
|
||||
OutputRebaseSection<E> rebase{*this};
|
||||
OutputBindSection<E> bind{*this};
|
||||
OutputLazyBindSection<E> lazy_bind{*this};
|
||||
OutputExportSection<E> export_{*this};
|
||||
OutputFunctionStartsSection<E> function_starts{*this};
|
||||
OutputSymtabSection<E> symtab{*this};
|
||||
OutputIndirectSymtabSection<E> indir_symtab{*this};
|
||||
OutputStrtabSection<E> strtab{*this};
|
||||
|
||||
OutputSection<E> *text = nullptr;
|
||||
OutputSection<E> *data = nullptr;
|
||||
OutputSection<E> *bss = nullptr;
|
||||
OutputSection<E> *cstring = nullptr;
|
||||
OutputSection<E> *common = nullptr;
|
||||
};
|
||||
|
||||
int main(int argc, char **argv);
|
||||
|
||||
//
|
||||
// Inline functions
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const InputSection<E> &sec) {
|
||||
out << sec.file << "(" << sec.hdr.get_segname() << ","
|
||||
<< sec.hdr.get_sectname() << ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 Subsection<E>::get_addr(Context<E> &ctx) const {
|
||||
return ctx.arg.pagezero_size + raddr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 Symbol<E>::get_addr(Context<E> &ctx) const {
|
||||
if (subsec)
|
||||
return subsec->get_addr(ctx) + value;
|
||||
if (stub_idx != -1)
|
||||
return ctx.stubs.hdr.addr + stub_idx * E::stub_size;
|
||||
return value;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 Symbol<E>::get_got_addr(Context<E> &ctx) const {
|
||||
assert(got_idx != -1);
|
||||
return ctx.got.hdr.addr + got_idx * E::word_size;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 Symbol<E>::get_tlv_addr(Context<E> &ctx) const {
|
||||
assert(tlv_idx != -1);
|
||||
return ctx.thread_ptrs.hdr.addr + tlv_idx * E::word_size;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline Symbol<E> *get_symbol(Context<E> &ctx, std::string_view name) {
|
||||
typename decltype(ctx.symbol_map)::const_accessor acc;
|
||||
ctx.symbol_map.insert(acc, {name, Symbol<E>(name)});
|
||||
return (Symbol<E> *)(&acc->second);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym) {
|
||||
out << sym.name;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Chunk<E>::Chunk(Context<E> &ctx, std::string_view segname,
|
||||
std::string_view sectname) {
|
||||
ctx.chunks.push_back(this);
|
||||
hdr.set_segname(segname);
|
||||
hdr.set_sectname(sectname);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 UnwindRecord<E>::get_func_raddr(Context<E> &ctx) const {
|
||||
return subsec->raddr + offset;
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -1,622 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include "../archive-file.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const InputFile<E> &file) {
|
||||
if (file.archive_name.empty())
|
||||
out << path_clean(file.mf->name);
|
||||
else
|
||||
out << path_clean(file.archive_name) << "(" << path_clean(file.mf->name) + ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *
|
||||
ObjectFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
ObjectFile<E> *obj = new ObjectFile<E>;
|
||||
obj->mf = mf;
|
||||
obj->archive_name = archive_name;
|
||||
obj->is_alive = archive_name.empty();
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
return obj;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
parse_sections(ctx);
|
||||
parse_symtab(ctx);
|
||||
split_subsections(ctx);
|
||||
parse_data_in_code(ctx);
|
||||
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections)
|
||||
if (isec)
|
||||
isec->parse_relocations(ctx);
|
||||
|
||||
if (unwind_sec)
|
||||
parse_compact_unwind(ctx, *unwind_sec);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_sections(Context<E> &ctx) {
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
// Read all but a symtab section
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
p += lc.cmdsize;
|
||||
if (lc.cmd != LC_SEGMENT_64)
|
||||
continue;
|
||||
|
||||
SegmentCommand &cmd = *(SegmentCommand *)&lc;
|
||||
MachSection *mach_sec = (MachSection *)((u8 *)&lc + sizeof(cmd));
|
||||
|
||||
for (MachSection &msec : std::span(mach_sec, mach_sec + cmd.nsects)) {
|
||||
sections.push_back(nullptr);
|
||||
|
||||
if (msec.match("__LD", "__compact_unwind")) {
|
||||
unwind_sec = &msec;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msec.attr & S_ATTR_DEBUG)
|
||||
continue;
|
||||
|
||||
sections.back().reset(new InputSection<E>(ctx, *this, msec));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_symtab(Context<E> &ctx) {
|
||||
SymtabCommand *cmd = (SymtabCommand *)find_load_command(ctx, LC_SYMTAB);
|
||||
if (!cmd)
|
||||
return;
|
||||
|
||||
mach_syms = {(MachSym *)(this->mf->data + cmd->symoff), cmd->nsyms};
|
||||
this->syms.reserve(mach_syms.size());
|
||||
|
||||
i64 nlocal = 0;
|
||||
for (MachSym &msym : mach_syms)
|
||||
if (!msym.ext)
|
||||
nlocal++;
|
||||
local_syms.reserve(nlocal);
|
||||
|
||||
for (MachSym &msym : mach_syms) {
|
||||
std::string_view name = (char *)(this->mf->data + cmd->stroff + msym.stroff);
|
||||
|
||||
if (msym.ext) {
|
||||
this->syms.push_back(get_symbol(ctx, name));
|
||||
} else {
|
||||
local_syms.emplace_back(name);
|
||||
this->syms.push_back(&local_syms.back());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SplitRegion {
|
||||
u32 offset;
|
||||
u32 size;
|
||||
u32 symidx;
|
||||
bool is_alt_entry;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct SplitInfo {
|
||||
SplitInfo(InputSection<E> *isec) : isec(isec) {}
|
||||
InputSection<E> *isec;
|
||||
std::vector<SplitRegion> regions;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
static std::vector<SplitInfo<E>> split(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
std::vector<SplitInfo<E>> vec;
|
||||
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file.sections)
|
||||
vec.push_back({isec.get()});
|
||||
|
||||
for (i64 i = 0; i < file.mach_syms.size(); i++) {
|
||||
MachSym &msym = file.mach_syms[i];
|
||||
if (msym.type == N_SECT && file.sections[msym.sect - 1]) {
|
||||
SplitRegion r;
|
||||
r.offset = msym.value - file.sections[msym.sect - 1]->hdr.addr;
|
||||
r.symidx = i;
|
||||
r.is_alt_entry = (msym.desc & N_ALT_ENTRY);
|
||||
vec[msym.sect - 1].regions.push_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
std::erase_if(vec, [](const SplitInfo<E> &info) { return !info.isec; });
|
||||
|
||||
sort(vec, [](const SplitInfo<E> &a, const SplitInfo<E> &b) {
|
||||
return a.isec->hdr.addr < b.isec->hdr.addr;
|
||||
});
|
||||
|
||||
for (SplitInfo<E> &info : vec) {
|
||||
std::vector<SplitRegion> &r = info.regions;
|
||||
|
||||
if (r.empty()) {
|
||||
r.push_back({0, (u32)info.isec->hdr.size, (u32)-1, false});
|
||||
continue;
|
||||
}
|
||||
|
||||
sort(r, [](const SplitRegion &a, const SplitRegion &b) {
|
||||
return a.offset < b.offset;
|
||||
});
|
||||
|
||||
if (r[0].offset > 0)
|
||||
r.insert(r.begin(), {0, r[0].offset, (u32)-1, false});
|
||||
|
||||
for (i64 i = 1; i < r.size(); i++)
|
||||
if (r[i - 1].offset == r[i].offset)
|
||||
r[i++].is_alt_entry = true;
|
||||
|
||||
i64 last = -1;
|
||||
for (i64 i = 0; i < r.size(); i++) {
|
||||
if (!r[i].is_alt_entry) {
|
||||
if (last != -1)
|
||||
r[last].size = r[i].offset - r[last].offset;
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (last != -1)
|
||||
r[last].size = info.isec->hdr.size - r[last].offset;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::split_subsections(Context<E> &ctx) {
|
||||
sym_to_subsec.resize(mach_syms.size());
|
||||
|
||||
for (SplitInfo<E> &info : split(ctx, *this)) {
|
||||
InputSection<E> &isec = *info.isec;
|
||||
|
||||
for (SplitRegion &r : info.regions) {
|
||||
if (!r.is_alt_entry) {
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = isec,
|
||||
.input_offset = r.offset,
|
||||
.input_size = r.size,
|
||||
.input_addr = (u32)(isec.hdr.addr + r.offset),
|
||||
.p2align = (u8)isec.hdr.p2align,
|
||||
};
|
||||
subsections.emplace_back(subsec);
|
||||
}
|
||||
|
||||
if (r.symidx != -1)
|
||||
sym_to_subsec[r.symidx] = subsections.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (i64 i = 0; i < mach_syms.size(); i++)
|
||||
if (!mach_syms[i].ext)
|
||||
override_symbol(ctx, i);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_data_in_code(Context<E> &ctx) {
|
||||
if (auto *cmd = (LinkEditDataCommand *)find_load_command(ctx, LC_DATA_IN_CODE))
|
||||
data_in_code_entries = {
|
||||
(DataInCodeEntry *)(this->mf->data + cmd->dataoff),
|
||||
cmd->datasize / sizeof(DataInCodeEntry)
|
||||
};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
LoadCommand *ObjectFile<E>::find_load_command(Context<E> &ctx, u32 type) {
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
if (lc.cmd == type)
|
||||
return &lc;
|
||||
p += lc.cmdsize;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 ObjectFile<E>::find_subsection_idx(Context<E> &ctx, u32 addr) {
|
||||
auto it = std::upper_bound(
|
||||
subsections.begin(), subsections.end(), addr,
|
||||
[&](u32 addr, const std::unique_ptr<Subsection<E>> &subsec) {
|
||||
return addr < subsec->input_addr;
|
||||
});
|
||||
|
||||
if (it == subsections.begin())
|
||||
return -1;
|
||||
return it - subsections.begin() - 1;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Subsection<E> *ObjectFile<E>::find_subsection(Context<E> &ctx, u32 addr) {
|
||||
i64 i = find_subsection_idx(ctx, addr);
|
||||
return (i == -1) ? nullptr : subsections[i].get();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_compact_unwind(Context<E> &ctx, MachSection &hdr) {
|
||||
if (hdr.size % sizeof(CompactUnwindEntry))
|
||||
Fatal(ctx) << *this << ": invalid __compact_unwind section size";
|
||||
|
||||
i64 num_entries = hdr.size / sizeof(CompactUnwindEntry);
|
||||
unwind_records.reserve(num_entries);
|
||||
|
||||
CompactUnwindEntry *src = (CompactUnwindEntry *)(this->mf->data + hdr.offset);
|
||||
|
||||
// Read compact unwind entries
|
||||
for (i64 i = 0; i < num_entries; i++)
|
||||
unwind_records.emplace_back(src[i].code_len, src[i].encoding);
|
||||
|
||||
// Read relocations
|
||||
MachRel *mach_rels = (MachRel *)(this->mf->data + hdr.reloff);
|
||||
for (i64 i = 0; i < hdr.nreloc; i++) {
|
||||
MachRel &r = mach_rels[i];
|
||||
if (r.offset >= hdr.size)
|
||||
Fatal(ctx) << *this << ": relocation offset too large: " << i;
|
||||
|
||||
i64 idx = r.offset / sizeof(CompactUnwindEntry);
|
||||
UnwindRecord<E> &dst = unwind_records[idx];
|
||||
|
||||
auto error = [&] {
|
||||
Fatal(ctx) << *this << ": __compact_unwind: unsupported relocation: " << i;
|
||||
};
|
||||
|
||||
switch (r.offset % sizeof(CompactUnwindEntry)) {
|
||||
case offsetof(CompactUnwindEntry, code_start): {
|
||||
if (r.is_pcrel || r.p2size != 3 || r.is_extern || r.type)
|
||||
error();
|
||||
|
||||
Subsection<E> *target = find_subsection(ctx, src[idx].code_start);
|
||||
if (!target)
|
||||
error();
|
||||
dst.subsec = target;
|
||||
dst.offset = src[idx].code_start - target->input_addr;
|
||||
break;
|
||||
}
|
||||
case offsetof(CompactUnwindEntry, personality):
|
||||
if (r.is_pcrel || r.p2size != 3 || !r.is_extern || r.type)
|
||||
error();
|
||||
dst.personality = this->syms[r.idx];
|
||||
break;
|
||||
case offsetof(CompactUnwindEntry, lsda): {
|
||||
if (r.is_pcrel || r.p2size != 3 || r.is_extern || r.type)
|
||||
error();
|
||||
|
||||
i32 addr = *(i32 *)((u8 *)this->mf->data + hdr.offset + r.offset);
|
||||
Subsection<E> *target = find_subsection(ctx, addr);
|
||||
if (!target)
|
||||
error();
|
||||
dst.lsda = target;
|
||||
dst.lsda_offset = addr - target->input_addr;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << *this << ": __compact_unwind: unsupported relocation: " << i;
|
||||
}
|
||||
}
|
||||
|
||||
for (i64 i = 0; i < num_entries; i++)
|
||||
if (!unwind_records[i].subsec)
|
||||
Fatal(ctx) << ": __compact_unwind: missing relocation at " << i;
|
||||
|
||||
// Sort unwind entries by offset
|
||||
sort(unwind_records, [](const UnwindRecord<E> &a, const UnwindRecord<E> &b) {
|
||||
return std::tuple(a.subsec->input_addr, a.offset) <
|
||||
std::tuple(b.subsec->input_addr, b.offset);
|
||||
});
|
||||
|
||||
// Associate unwind entries to subsections
|
||||
for (i64 i = 0; i < num_entries;) {
|
||||
Subsection<E> &subsec = *unwind_records[i].subsec;
|
||||
subsec.unwind_offset = i;
|
||||
|
||||
i64 j = i + 1;
|
||||
while (j < num_entries && unwind_records[j].subsec == &subsec)
|
||||
j++;
|
||||
subsec.nunwind = j - i;
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
// Symbols with higher priorities overwrites symbols with lower priorities.
|
||||
// Here is the list of priorities, from the highest to the lowest.
|
||||
//
|
||||
// 1. Strong defined symbol
|
||||
// 2. Weak defined symbol
|
||||
// 3. Strong defined symbol in a DSO
|
||||
// 4. Weak defined symbol in a DSO
|
||||
// 5. Strong or weak defined symbol in an archive
|
||||
// 6. Common symbol
|
||||
// 7. Unclaimed (nonexistent) symbol
|
||||
//
|
||||
// Ties are broken by file priority.
|
||||
template <typename E>
|
||||
static u64 get_rank(InputFile<E> *file, MachSym &msym, bool is_lazy) {
|
||||
if (msym.is_common())
|
||||
return (6 << 24) + file->priority;
|
||||
if (is_lazy)
|
||||
return (5 << 24) + file->priority;
|
||||
if (file->is_dylib())
|
||||
return (3 << 24) + file->priority;
|
||||
return (1 << 24) + file->priority;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static u64 get_rank(Symbol<E> &sym) {
|
||||
InputFile<E> *file = sym.file;
|
||||
if (!file)
|
||||
return 7 << 24;
|
||||
if (sym.is_common)
|
||||
return (6 << 24) + file->priority;
|
||||
if (!file->archive_name.empty())
|
||||
return (5 << 24) + file->priority;
|
||||
if (file->is_dylib())
|
||||
return (3 << 24) + file->priority;
|
||||
return (1 << 24) + file->priority;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::override_symbol(Context<E> &ctx, i64 idx) {
|
||||
Symbol<E> &sym = *this->syms[idx];
|
||||
MachSym &msym = mach_syms[idx];
|
||||
|
||||
sym.file = this;
|
||||
sym.is_extern = msym.ext;
|
||||
sym.is_lazy = false;
|
||||
|
||||
switch (msym.type) {
|
||||
case N_UNDF:
|
||||
assert(msym.is_common());
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
sym.is_common = true;
|
||||
break;
|
||||
case N_ABS:
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
sym.is_common = false;
|
||||
break;
|
||||
case N_SECT:
|
||||
sym.subsec = subsections[sym_to_subsec[idx]].get();
|
||||
sym.value = msym.value - sym.subsec->input_addr;
|
||||
sym.is_common = false;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << sym << ": unknown symbol type: " << (u64)msym.type;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::resolve_regular_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.ext || msym.is_undef())
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
if (get_rank(this, msym, false) < get_rank(sym))
|
||||
override_symbol(ctx, i);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::resolve_lazy_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.ext || msym.is_undef() || msym.is_common())
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
if (get_rank(this, msym, true) < get_rank(sym)) {
|
||||
sym.file = this;
|
||||
sym.subsec = nullptr;
|
||||
sym.value = 0;
|
||||
sym.is_extern = false;
|
||||
sym.is_lazy = true;
|
||||
sym.is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool ObjectFile<E>::is_objc_object(Context<E> &ctx) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections)
|
||||
if (isec->hdr.match("__DATA", "__objc_catlist") ||
|
||||
isec->hdr.match("__TEXT", "__swift"))
|
||||
return true;
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++)
|
||||
if (!mach_syms[i].is_undef() && mach_syms[i].ext &&
|
||||
this->syms[i]->name.starts_with("_OBJC_CLASS_$_"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<ObjectFile<E> *> ObjectFile<E>::mark_live_objects(Context<E> &ctx) {
|
||||
std::vector<ObjectFile<E> *> vec;
|
||||
assert(this->is_alive);
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.ext)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
if (msym.is_undef()) {
|
||||
if (sym.file && !sym.file->is_alive.exchange(true))
|
||||
vec.push_back((ObjectFile<E> *)sym.file);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (get_rank(this, msym, false) < get_rank(sym))
|
||||
override_symbol(ctx, i);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
MachSym &msym = mach_syms[i];
|
||||
|
||||
if (sym.file == this && sym.is_common) {
|
||||
InputSection<E> *isec = get_common_sec(ctx);
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = *isec,
|
||||
.input_size = (u32)msym.value,
|
||||
.p2align = (u8)msym.p2align,
|
||||
};
|
||||
|
||||
subsections.emplace_back(subsec);
|
||||
|
||||
sym.subsec = subsec;
|
||||
sym.value = 0;
|
||||
sym.is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::check_duplicate_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
Symbol<E> *sym = this->syms[i];
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (sym && !msym.is_undef() && !msym.is_common() && sym->file != this)
|
||||
Error(ctx) << "duplicate symbol: " << *this << ": " << *sym->file
|
||||
<< ": " << *sym;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E> *ObjectFile<E>::get_common_sec(Context<E> &ctx) {
|
||||
if (!common_sec) {
|
||||
MachSection *hdr = new MachSection;
|
||||
common_hdr.reset(hdr);
|
||||
|
||||
memset(hdr, 0, sizeof(*hdr));
|
||||
hdr->set_segname("__DATA");
|
||||
hdr->set_sectname("__common");
|
||||
hdr->type = S_ZEROFILL;
|
||||
|
||||
common_sec = new InputSection<E>(ctx, *this, *hdr);
|
||||
sections.emplace_back(common_sec);
|
||||
}
|
||||
return common_sec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
DylibFile<E> *DylibFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
DylibFile<E> *dylib = new DylibFile<E>;
|
||||
dylib->mf = mf;
|
||||
ctx.dylib_pool.emplace_back(dylib);
|
||||
return dylib;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::read_trie(Context<E> &ctx, u8 *start, i64 offset,
|
||||
const std::string &prefix) {
|
||||
u8 *buf = start + offset;
|
||||
|
||||
if (*buf) {
|
||||
read_uleb(buf); // size
|
||||
read_uleb(buf); // flags
|
||||
read_uleb(buf); // addr
|
||||
this->syms.push_back(get_symbol(ctx, prefix));
|
||||
} else {
|
||||
buf++;
|
||||
}
|
||||
|
||||
i64 nchild = *buf++;
|
||||
|
||||
for (i64 i = 0; i < nchild; i++) {
|
||||
std::string suffix((char *)buf);
|
||||
buf += suffix.size() + 1;
|
||||
i64 off = read_uleb(buf);
|
||||
read_trie(ctx, start, off, prefix + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse_dylib(Context<E> &ctx) {
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
|
||||
switch (lc.cmd) {
|
||||
case LC_ID_DYLIB: {
|
||||
DylibCommand &cmd = *(DylibCommand *)p;
|
||||
install_name = (char *)p + cmd.nameoff;
|
||||
break;
|
||||
}
|
||||
case LC_DYLD_INFO_ONLY: {
|
||||
DyldInfoCommand &cmd = *(DyldInfoCommand *)p;
|
||||
if (cmd.export_off)
|
||||
read_trie(ctx, this->mf->data + cmd.export_off);
|
||||
break;
|
||||
}
|
||||
case LC_DYLD_EXPORTS_TRIE: {
|
||||
LinkEditDataCommand &cmd = *(LinkEditDataCommand *)p;
|
||||
read_trie(ctx, this->mf->data + cmd.dataoff);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
p += lc.cmdsize;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse(Context<E> &ctx) {
|
||||
switch (get_file_type(this->mf)) {
|
||||
case FileType::TAPI: {
|
||||
TextDylib tbd = parse_tbd(ctx, this->mf);
|
||||
for (std::string_view sym : tbd.exports)
|
||||
this->syms.push_back(get_symbol(ctx, sym));
|
||||
install_name = tbd.install_name;
|
||||
break;
|
||||
}
|
||||
case FileType::MACH_DYLIB:
|
||||
parse_dylib(ctx);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << this->mf->name << ": is not a dylib";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
for (Symbol<E> *sym : this->syms) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
if (sym->file && sym->file->priority < this->priority)
|
||||
continue;
|
||||
sym->file = this;
|
||||
sym->is_extern = true;
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class ObjectFile<E>; \
|
||||
template class DylibFile<E>; \
|
||||
template std::ostream &operator<<(std::ostream &, const InputFile<E> &)
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
|
||||
} // namespace mold::macho
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,120 +0,0 @@
|
||||
// This file implements the same functionality as elf/output-file.cc.
|
||||
// We have two different implemetntations for ELF and Mach-O because
|
||||
// we implemented a special optimization for ELF or Linux.
|
||||
//
|
||||
// On Linux, it is faster to write to an existing file than creating a
|
||||
// new file and writing to it. So, we overwrite an existing executable
|
||||
// if exists.
|
||||
//
|
||||
// On macOS, it looks like the system does not assume that an executable
|
||||
// is mutated once it is created. Due to some code signing mechanism or
|
||||
// something, we always have to create a fresh file.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
class MemoryMappedOutputFile : public OutputFile<E> {
|
||||
public:
|
||||
MemoryMappedOutputFile(Context<E> &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<E>(path, filesize, true) {
|
||||
std::string tmp_path = filepath(path).parent_path() / ".mold-XXXXXX";
|
||||
output_tmpfile = (char *)save_string(ctx, tmp_path).data();
|
||||
|
||||
i64 fd = mkstemp(output_tmpfile);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << output_tmpfile << ": " << errno_string();
|
||||
|
||||
if (ftruncate(fd, filesize))
|
||||
Fatal(ctx) << "ftruncate failed: " << errno_string();
|
||||
if (fchmod(fd, perm) == -1)
|
||||
Fatal(ctx) << "fchmod failed: " << errno_string();
|
||||
|
||||
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, fd, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
::close(fd);
|
||||
}
|
||||
|
||||
void close(Context<E> &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
munmap(this->buf, this->filesize);
|
||||
if (rename(output_tmpfile, this->path.c_str()) == -1)
|
||||
Fatal(ctx) << this->path << ": rename failed: " << errno_string();
|
||||
output_tmpfile = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class MallocOutputFile : public OutputFile<E> {
|
||||
public:
|
||||
MallocOutputFile(Context<E> &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<E>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)mmap(NULL, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << "mmap failed: " << errno_string();
|
||||
}
|
||||
|
||||
void close(Context<E> &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (this->path == "-") {
|
||||
fwrite(this->buf, this->filesize, 1, stdout);
|
||||
fclose(stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
|
||||
FILE *fp = fdopen(fd, "w");
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::unique_ptr<OutputFile<E>>
|
||||
OutputFile<E>::open(Context<E> &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
Timer t(ctx, "open_file");
|
||||
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
bool is_special = false;
|
||||
if (path == "-") {
|
||||
is_special = true;
|
||||
} else {
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) != S_IFREG)
|
||||
is_special = true;
|
||||
}
|
||||
|
||||
std::unique_ptr<OutputFile> file;
|
||||
if (is_special)
|
||||
file = std::make_unique<MallocOutputFile<E>>(ctx, path, filesize, perm);
|
||||
else
|
||||
file = std::make_unique<MemoryMappedOutputFile<E>>(ctx, path, filesize, perm);
|
||||
return file;
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class OutputFile<E>
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
|
||||
} // namespace mold::macho
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
static std::vector<YamlNode>
|
||||
get_vector(YamlNode &node, std::string_view key) {
|
||||
if (auto *map = std::get_if<std::map<std::string_view, YamlNode>>(&node.data))
|
||||
if (auto it = map->find(key); it != map->end())
|
||||
if (auto *vec = std::get_if<std::vector<YamlNode>>(&it->second.data))
|
||||
return *vec;
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::optional<std::string_view>
|
||||
get_string(YamlNode &node, std::string_view key) {
|
||||
if (auto *map = std::get_if<std::map<std::string_view, YamlNode>>(&node.data))
|
||||
if (auto it = map->find(key); it != map->end())
|
||||
if (auto *str = std::get_if<std::string_view>(&it->second.data))
|
||||
return *str;
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool contains(const std::vector<YamlNode> &vec, std::string_view key) {
|
||||
for (const YamlNode &mem : vec)
|
||||
if (const std::string_view *s = std::get_if<std::string_view>(&mem.data))
|
||||
if (*s == key)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::optional<TextDylib> to_tbd(YamlNode &node, std::string_view arch) {
|
||||
if (!contains(get_vector(node, "targets"), arch))
|
||||
return {};
|
||||
|
||||
TextDylib tbd;
|
||||
|
||||
for (YamlNode &mem : get_vector(node, "uuids"))
|
||||
if (auto target = get_string(mem, "target"))
|
||||
if (*target == arch)
|
||||
if (auto value = get_string(mem, "value"))
|
||||
tbd.uuid = *value;
|
||||
|
||||
if (auto val = get_string(node, "install-name"))
|
||||
tbd.install_name = *val;
|
||||
|
||||
if (auto val = get_string(node, "current-version"))
|
||||
tbd.current_version = *val;
|
||||
|
||||
for (YamlNode &mem : get_vector(node, "parent-umbrella"))
|
||||
if (contains(get_vector(mem, "targets"), arch))
|
||||
if (auto val = get_string(mem, "umbrella"))
|
||||
tbd.parent_umbrella = *val;
|
||||
|
||||
for (YamlNode &mem : get_vector(node, "reexported-libraries"))
|
||||
if (contains(get_vector(mem, "targets"), arch))
|
||||
for (YamlNode &mem : get_vector(mem, "libraries"))
|
||||
if (auto *lib = std::get_if<std::string_view>(&mem.data))
|
||||
tbd.reexported_libs.push_back(*lib);
|
||||
|
||||
for (std::string_view key : {"exports", "reexports"})
|
||||
for (YamlNode &mem : get_vector(node, key))
|
||||
if (contains(get_vector(mem, "targets"), arch))
|
||||
for (YamlNode &mem : get_vector(mem, "symbols"))
|
||||
if (auto *sym = std::get_if<std::string_view>(&mem.data))
|
||||
tbd.exports.push_back(*sym);
|
||||
|
||||
return tbd;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static TextDylib squash(Context<E> &ctx, std::span<TextDylib> tbds) {
|
||||
std::unordered_map<std::string_view, TextDylib> map;
|
||||
|
||||
TextDylib main = std::move(tbds[0]);
|
||||
for (TextDylib &tbd : tbds.subspan(1))
|
||||
map[tbd.install_name] = std::move(tbd);
|
||||
|
||||
std::vector<std::string_view> libs;
|
||||
|
||||
for (std::string_view lib : main.reexported_libs) {
|
||||
auto it = map.find(lib);
|
||||
if (it != map.end())
|
||||
append(main.exports, it->second.exports);
|
||||
else
|
||||
libs.push_back(lib);
|
||||
}
|
||||
|
||||
main.reexported_libs = std::move(libs);
|
||||
return main;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static TextDylib parse(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string_view arch) {
|
||||
std::string_view contents = mf->get_contents();
|
||||
std::variant<std::vector<YamlNode>, YamlError> res = parse_yaml(contents);
|
||||
|
||||
if (YamlError *err = std::get_if<YamlError>(&res)) {
|
||||
i64 lineno = std::count(contents.begin(), contents.begin() + err->pos, '\n');
|
||||
Fatal(ctx) << mf->name << ":" << (lineno + 1)
|
||||
<< ": YAML parse error: " << err->msg;
|
||||
}
|
||||
|
||||
std::vector<YamlNode> &nodes = std::get<std::vector<YamlNode>>(res);
|
||||
if (nodes.empty())
|
||||
Fatal(ctx) << mf->name << ": malformed TBD file";
|
||||
|
||||
std::vector<TextDylib> vec;
|
||||
for (YamlNode &node : nodes)
|
||||
if (std::optional<TextDylib> dylib = to_tbd(node, arch))
|
||||
vec.push_back(*dylib);
|
||||
return squash(ctx, vec);
|
||||
}
|
||||
|
||||
template <>
|
||||
TextDylib parse_tbd(Context<ARM64> &ctx, MappedFile<Context<ARM64>> *mf) {
|
||||
return parse(ctx, mf, "arm64-macos");
|
||||
}
|
||||
|
||||
template <>
|
||||
TextDylib parse_tbd(Context<X86_64> &ctx, MappedFile<Context<X86_64>> *mf) {
|
||||
return parse(ctx, mf, "x86_64-macos");
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
-360
@@ -1,360 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
struct Token {
|
||||
enum { STRING = 1, INDENT, DEDENT, END };
|
||||
|
||||
u8 kind = 0;
|
||||
std::string_view str;
|
||||
};
|
||||
|
||||
class YamlParser {
|
||||
public:
|
||||
YamlParser(std::string_view input) : input(input) {}
|
||||
|
||||
std::variant<std::vector<YamlNode>, YamlError> parse();
|
||||
|
||||
private:
|
||||
std::optional<YamlError> tokenize();
|
||||
|
||||
void tokenize_bare_string(std::string_view &str);
|
||||
|
||||
std::optional<YamlError> tokenize_list(std::string_view &str);
|
||||
std::optional<YamlError> tokenize_string(std::string_view &str, char end);
|
||||
|
||||
std::variant<YamlNode, YamlError> parse_element(std::span<Token> &tok);
|
||||
std::variant<YamlNode, YamlError> parse_list(std::span<Token> &tok);
|
||||
std::variant<YamlNode, YamlError> parse_map(std::span<Token> &tok);
|
||||
std::variant<YamlNode, YamlError> parse_flow_element(std::span<Token> &tok);
|
||||
std::variant<YamlNode, YamlError> parse_flow_list(std::span<Token> &tok);
|
||||
|
||||
std::string_view input;
|
||||
std::vector<Token> tokens;
|
||||
};
|
||||
|
||||
std::optional<YamlError> YamlParser::tokenize() {
|
||||
std::vector<i64> indents = {0};
|
||||
|
||||
auto indent = [&](std::string_view str, i64 depth) {
|
||||
tokens.push_back({Token::INDENT, str});
|
||||
indents.push_back(depth);
|
||||
};
|
||||
|
||||
auto dedent = [&](std::string_view str) {
|
||||
assert(indents.size() > 1);
|
||||
tokens.push_back({Token::DEDENT, str});
|
||||
indents.pop_back();
|
||||
};
|
||||
|
||||
auto skip_line = [](std::string_view &str) {
|
||||
size_t pos = str.find('\n');
|
||||
if (pos == str.npos)
|
||||
str = str.substr(str.size());
|
||||
else
|
||||
str = str.substr(pos + 1);
|
||||
};
|
||||
|
||||
auto tokenize_line = [&](std::string_view &str) -> std::optional<YamlError> {
|
||||
const char *start = str.data();
|
||||
|
||||
if (str.starts_with("---")) {
|
||||
while (indents.size() > 1)
|
||||
dedent(str);
|
||||
tokens.push_back({Token::END, str.substr(0, 3)});
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (str.starts_with("...")) {
|
||||
while (indents.size() > 1)
|
||||
dedent(str);
|
||||
tokens.push_back({Token::END, str.substr(0, 3)});
|
||||
str = str.substr(str.size());
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t pos = str.find_first_not_of(" \t");
|
||||
if (pos == str.npos || str[pos] == '#' || str[pos] == '\n') {
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (indents.back() != pos) {
|
||||
if (indents.back() < pos) {
|
||||
indent(str, pos);
|
||||
} else {
|
||||
while (indents.back() != pos) {
|
||||
if (pos < indents.back())
|
||||
dedent(str);
|
||||
else
|
||||
return YamlError{"bad indentation", start - input.data()};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
str = str.substr(pos);
|
||||
|
||||
while (!str.empty()) {
|
||||
if (str[0] == '\n') {
|
||||
str = str.substr(1);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (str.starts_with("- ")) {
|
||||
tokens.push_back({'-', str.substr(0, 1)});
|
||||
|
||||
size_t pos = str.find_first_not_of(" \t", 1);
|
||||
if (pos == str.npos || str[pos] == '\n') {
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
|
||||
str = str.substr(pos);
|
||||
indent(str, str.data() - start);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with('['))
|
||||
return tokenize_list(str);
|
||||
|
||||
if (str.starts_with('\'')) {
|
||||
if (std::optional<YamlError> err = tokenize_string(str, '\''))
|
||||
return err;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with('"')) {
|
||||
if (std::optional<YamlError> err = tokenize_string(str, '"'))
|
||||
return err;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with('#')) {
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (str.starts_with(':')) {
|
||||
tokens.push_back({':', str.substr(0, 1)});
|
||||
|
||||
size_t pos = str.find_first_not_of(" \t", 1);
|
||||
if (pos == str.npos || str[pos] == '\n') {
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
|
||||
str = str.substr(pos);
|
||||
continue;
|
||||
}
|
||||
|
||||
tokenize_bare_string(str);
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
std::string_view str = input;
|
||||
while (!str.empty())
|
||||
if (std::optional<YamlError> err = tokenize_line(str))
|
||||
return err;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<YamlError> YamlParser::tokenize_list(std::string_view &str) {
|
||||
const char *start = str.data();
|
||||
|
||||
tokens.push_back({'[', str.substr(0, 1)});
|
||||
str = str.substr(1);
|
||||
|
||||
while (!str.empty() && str[0] != ']') {
|
||||
if (size_t pos = str.find_first_not_of(" \t\n"); pos) {
|
||||
str = str.substr(pos);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with('\'')) {
|
||||
if (std::optional<YamlError> err = tokenize_string(str, '\''))
|
||||
return err;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with('"')) {
|
||||
if (std::optional<YamlError> err = tokenize_string(str, '"'))
|
||||
return err;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with(',')) {
|
||||
tokens.push_back({',', str.substr(0, 1)});
|
||||
str = str.substr(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
tokenize_bare_string(str);
|
||||
}
|
||||
|
||||
if (str.empty())
|
||||
return YamlError{"unclosed list", start - input.data()};
|
||||
|
||||
const char *bracket = str.data();
|
||||
tokens.push_back({']', str.substr(0, 1)});
|
||||
str = str.substr(1);
|
||||
|
||||
while (!str.empty() && (str[0] == ' ' || str[0] == '\t'))
|
||||
str = str.substr(1);
|
||||
if (str.empty() || str[0] != '\n')
|
||||
return YamlError{"no newline after ']'", bracket - input.data()};
|
||||
str = str.substr(1);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<YamlError>
|
||||
YamlParser::tokenize_string(std::string_view &str, char end) {
|
||||
const char *start = str.data();
|
||||
size_t pos = str.find(end, 1);
|
||||
if (pos == str.npos)
|
||||
return YamlError{"unterminated string literal", start - input.data()};
|
||||
|
||||
tokens.push_back({Token::STRING, str.substr(1, pos - 1)});
|
||||
str = str.substr(pos + 1);
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
YamlParser::tokenize_bare_string(std::string_view &str) {
|
||||
size_t pos = str.find_first_not_of(
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-/.");
|
||||
if (pos == str.npos)
|
||||
pos = str.size();
|
||||
tokens.push_back({Token::STRING, str.substr(0, pos)});
|
||||
str = str.substr(pos);
|
||||
}
|
||||
|
||||
std::variant<std::vector<YamlNode>, YamlError> YamlParser::parse() {
|
||||
if (std::optional<YamlError> err = tokenize())
|
||||
return *err;
|
||||
|
||||
std::span<Token> tok(tokens);
|
||||
std::vector<YamlNode> vec;
|
||||
|
||||
while (!tok.empty()) {
|
||||
if (tok[0].kind == Token::END) {
|
||||
tok = tok.subspan(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError> elem = parse_element(tok);
|
||||
if (YamlError *err = std::get_if<YamlError>(&elem))
|
||||
return *err;
|
||||
vec.push_back(std::get<YamlNode>(elem));
|
||||
|
||||
if (tok[0].kind != Token::END)
|
||||
return YamlError{"stray token", tok[0].str.data() - input.data()};
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_element(std::span<Token> &tok) {
|
||||
if (tok[0].kind == Token::INDENT) {
|
||||
tok = tok.subspan(1);
|
||||
|
||||
std::variant<YamlNode, YamlError> elem = parse_element(tok);
|
||||
assert(tok[0].kind == Token::DEDENT);
|
||||
tok = tok.subspan(1);
|
||||
return elem;
|
||||
}
|
||||
|
||||
if (tok[0].kind == '-')
|
||||
return parse_list(tok);
|
||||
|
||||
if (tok.size() > 2 && tok[0].kind == Token::STRING && tok[1].kind == ':')
|
||||
return parse_map(tok);
|
||||
|
||||
return parse_flow_element(tok);
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_list(std::span<Token> &tok) {
|
||||
std::vector<YamlNode> vec;
|
||||
|
||||
while (tok[0].kind != Token::END && tok[0].kind != Token::DEDENT) {
|
||||
if (tok[0].kind != '-')
|
||||
return YamlError{"list element expected", tok[0].str.data() - input.data()};
|
||||
tok = tok.subspan(1);
|
||||
|
||||
std::variant<YamlNode, YamlError> elem = parse_element(tok);
|
||||
if (YamlError *err = std::get_if<YamlError>(&elem))
|
||||
return *err;
|
||||
vec.push_back(std::get<YamlNode>(elem));
|
||||
}
|
||||
return YamlNode{vec};
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_map(std::span<Token> &tok) {
|
||||
std::map<std::string_view, YamlNode> map;
|
||||
|
||||
while (tok[0].kind != Token::END && tok[0].kind != Token::DEDENT) {
|
||||
if (tok.size() < 2 || tok[0].kind != Token::STRING || tok[1].kind != ':')
|
||||
return YamlError{"map key expected", tok[0].str.data() - input.data()};
|
||||
|
||||
std::string_view key = tok[0].str;
|
||||
tok = tok.subspan(2);
|
||||
|
||||
std::variant<YamlNode, YamlError> elem = parse_element(tok);
|
||||
if (YamlError *err = std::get_if<YamlError>(&elem))
|
||||
return *err;
|
||||
map[key] = std::get<YamlNode>(elem);
|
||||
}
|
||||
return YamlNode{map};
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_flow_element(std::span<Token> &tok) {
|
||||
if (tok[0].kind == '[') {
|
||||
tok = tok.subspan(1);
|
||||
return parse_flow_list(tok);
|
||||
}
|
||||
|
||||
if (tok[0].kind != Token::STRING)
|
||||
return YamlError{"scalar expected", tok[0].str.data() - input.data()};
|
||||
|
||||
std::string_view val = tok[0].str;
|
||||
tok = tok.subspan(1);
|
||||
return YamlNode{val};
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_flow_list(std::span<Token> &tok) {
|
||||
std::vector<YamlNode> vec;
|
||||
const char *start = tok[0].str.data();
|
||||
|
||||
while (tok[0].kind != ']' && tok[0].kind != Token::END) {
|
||||
std::variant<YamlNode, YamlError> elem = parse_flow_element(tok);
|
||||
if (YamlError *err = std::get_if<YamlError>(&elem))
|
||||
return *err;
|
||||
vec.push_back(std::get<YamlNode>(elem));
|
||||
|
||||
if (tok[0].kind == ']')
|
||||
break;
|
||||
if (tok[0].kind != ',')
|
||||
return YamlError{"comma expected", tok[0].str.data() - input.data()};
|
||||
tok = tok.subspan(1);
|
||||
}
|
||||
|
||||
if (tok[0].kind == Token::END)
|
||||
return YamlError{"unterminated flow list", start - input.data()};
|
||||
|
||||
tok = tok.subspan(1);
|
||||
return YamlNode{vec};
|
||||
}
|
||||
|
||||
std::variant<std::vector<YamlNode>, YamlError>
|
||||
parse_yaml(std::string_view str) {
|
||||
return YamlParser(str).parse();
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -1,47 +0,0 @@
|
||||
#include "elf/mold.h"
|
||||
#include "macho/mold.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <signal.h>
|
||||
|
||||
#ifdef USE_SYSTEM_MIMALLOC
|
||||
#include <mimalloc-new-delete.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
#ifdef GIT_HASH
|
||||
const std::string mold_version =
|
||||
"mold " MOLD_VERSION " (" GIT_HASH "; compatible with GNU ld)";
|
||||
#else
|
||||
const std::string mold_version =
|
||||
"mold " MOLD_VERSION " (compatible with GNU ld)";
|
||||
#endif
|
||||
|
||||
void cleanup() {
|
||||
if (output_tmpfile)
|
||||
unlink(output_tmpfile);
|
||||
if (socket_tmpfile)
|
||||
unlink(socket_tmpfile);
|
||||
}
|
||||
|
||||
static void signal_handler(int) {
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
void install_signal_handler() {
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
std::string cmd = mold::filepath(argv[0]).filename();
|
||||
|
||||
if (cmd == "ld64" || cmd == "ld64.mold")
|
||||
return mold::macho::main(argc, argv);
|
||||
|
||||
return mold::elf::main(argc, argv);
|
||||
}
|
||||
@@ -1,666 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "big-endian.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/enumerable_thread_specific.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef NDEBUG
|
||||
# define unreachable() __builtin_unreachable()
|
||||
#else
|
||||
# define unreachable() assert(0 && "unreachable")
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
using namespace std::literals::string_literals;
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
|
||||
template <typename C> class OutputFile;
|
||||
|
||||
inline char *output_tmpfile;
|
||||
inline char *socket_tmpfile;
|
||||
inline thread_local bool opt_demangle;
|
||||
|
||||
extern const std::string mold_version;
|
||||
|
||||
std::string_view errno_string();
|
||||
void cleanup();
|
||||
void install_signal_handler();
|
||||
|
||||
//
|
||||
// Error output
|
||||
//
|
||||
|
||||
template <typename C>
|
||||
class SyncOut {
|
||||
public:
|
||||
SyncOut(C &ctx, std::ostream &out = std::cout) : out(out) {
|
||||
opt_demangle = ctx.arg.demangle;
|
||||
}
|
||||
|
||||
~SyncOut() {
|
||||
std::scoped_lock lock(mu);
|
||||
out << ss.str() << "\n";
|
||||
}
|
||||
|
||||
template <class T> SyncOut &operator<<(T &&val) {
|
||||
ss << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
static inline std::mutex mu;
|
||||
|
||||
private:
|
||||
std::ostream &out;
|
||||
std::stringstream ss;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
static std::string add_color(C &ctx, std::string msg) {
|
||||
if (ctx.arg.color_diagnostics)
|
||||
return "mold: \033[0;1;31m" + msg + ":\033[0m ";
|
||||
return "mold: " + msg + ": ";
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
class Fatal {
|
||||
public:
|
||||
Fatal(C &ctx) : out(ctx, std::cerr) {
|
||||
out << add_color(ctx, "fatal");
|
||||
}
|
||||
|
||||
[[noreturn]] ~Fatal() {
|
||||
out.~SyncOut();
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
template <class T> Fatal &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncOut<C> out;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
class Error {
|
||||
public:
|
||||
Error(C &ctx) : out(ctx, std::cerr) {
|
||||
if (ctx.arg.noinhibit_exec) {
|
||||
out << add_color(ctx, "warning");
|
||||
} else {
|
||||
out << add_color(ctx, "error");
|
||||
ctx.has_error = true;
|
||||
}
|
||||
}
|
||||
|
||||
template <class T> Error &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncOut<C> out;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
class Warn {
|
||||
public:
|
||||
Warn(C &ctx) : out(ctx, std::cerr) {
|
||||
if (ctx.arg.fatal_warnings) {
|
||||
out << add_color(ctx, "error");
|
||||
ctx.has_error = true;
|
||||
} else {
|
||||
out << add_color(ctx, "warning");
|
||||
}
|
||||
}
|
||||
|
||||
template <class T> Warn &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SyncOut<C> out;
|
||||
};
|
||||
|
||||
//
|
||||
// Utility functions
|
||||
//
|
||||
|
||||
inline u64 align_to(u64 val, u64 align) {
|
||||
if (align == 0)
|
||||
return val;
|
||||
assert(std::popcount(align) == 1);
|
||||
return (val + align - 1) & ~(align - 1);
|
||||
}
|
||||
|
||||
inline u64 align_down(u64 val, u64 align) {
|
||||
assert(std::popcount(align) == 1);
|
||||
return val & ~(align - 1);
|
||||
}
|
||||
|
||||
inline u64 next_power_of_two(u64 val) {
|
||||
assert(val >> 63 == 0);
|
||||
if (val == 0 || val == 1)
|
||||
return 1;
|
||||
return (u64)1 << (64 - std::countl_zero(val - 1));
|
||||
}
|
||||
|
||||
template <typename T, typename Compare = std::less<T>>
|
||||
void update_minimum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
T old_val = atomic;
|
||||
while (cmp(new_val, old_val) &&
|
||||
!atomic.compare_exchange_weak(old_val, new_val));
|
||||
}
|
||||
|
||||
template <typename T, typename Compare = std::less<T>>
|
||||
void update_maximum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
T old_val = atomic;
|
||||
while (cmp(old_val, new_val) &&
|
||||
!atomic.compare_exchange_weak(old_val, new_val));
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline void append(std::vector<T> &vec1, std::vector<U> vec2) {
|
||||
vec1.insert(vec1.end(), vec2.begin(), vec2.end());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::vector<T> flatten(std::vector<std::vector<T>> &vec) {
|
||||
std::vector<T> ret;
|
||||
for (std::vector<T> &v : vec)
|
||||
append(ret, v);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void sort(T &vec) {
|
||||
std::stable_sort(vec.begin(), vec.end());
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline void sort(T &vec, U less) {
|
||||
std::stable_sort(vec.begin(), vec.end(), less);
|
||||
}
|
||||
|
||||
inline i64 write_string(u8 *buf, std::string_view str) {
|
||||
memcpy(buf, str.data(), str.size());
|
||||
buf[str.size()] = '\0';
|
||||
return str.size() + 1;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline i64 write_vector(u8 *buf, const std::vector<T> &vec) {
|
||||
i64 sz = vec.size() * sizeof(T);
|
||||
memcpy(buf, vec.data(), sz);
|
||||
return sz;
|
||||
}
|
||||
|
||||
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 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 i64 uleb_size(u64 val) {
|
||||
i64 i = 0;
|
||||
do {
|
||||
i++;
|
||||
val >>= 7;
|
||||
} while (val);
|
||||
return i;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
std::string_view save_string(C &ctx, const std::string &str) {
|
||||
u8 *buf = new u8[str.size() + 1];
|
||||
memcpy(buf, str.data(), str.size());
|
||||
buf[str.size()] = '\0';
|
||||
ctx.string_pool.push_back(std::unique_ptr<u8[]>(buf));
|
||||
return {(char *)buf, str.size()};
|
||||
}
|
||||
|
||||
//
|
||||
// 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.
|
||||
template <typename T>
|
||||
class ConcurrentMap {
|
||||
public:
|
||||
ConcurrentMap() {}
|
||||
|
||||
ConcurrentMap(i64 nbuckets) {
|
||||
resize(nbuckets);
|
||||
}
|
||||
|
||||
~ConcurrentMap() {
|
||||
if (keys) {
|
||||
free((void *)keys);
|
||||
free((void *)key_sizes);
|
||||
free((void *)values);
|
||||
}
|
||||
}
|
||||
|
||||
void resize(i64 nbuckets) {
|
||||
this->~ConcurrentMap();
|
||||
|
||||
nbuckets = std::max<i64>(MIN_NBUCKETS, next_power_of_two(nbuckets));
|
||||
|
||||
this->nbuckets = nbuckets;
|
||||
keys = (std::atomic<const char *> *)calloc(nbuckets, sizeof(keys[0]));
|
||||
key_sizes = (u32 *)calloc(nbuckets, sizeof(key_sizes[0]));
|
||||
values = (T *)calloc(nbuckets, sizeof(values[0]));
|
||||
}
|
||||
|
||||
std::pair<T *, bool> insert(std::string_view key, u64 hash, const T &val) {
|
||||
if (!keys)
|
||||
return {nullptr, false};
|
||||
|
||||
assert(std::popcount<u64>(nbuckets) == 1);
|
||||
i64 idx = hash & (nbuckets - 1);
|
||||
i64 retry = 0;
|
||||
|
||||
while (retry < MAX_RETRY) {
|
||||
const char *ptr = keys[idx];
|
||||
if (ptr == marker) {
|
||||
pause();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ptr == nullptr) {
|
||||
if (!keys[idx].compare_exchange_weak(ptr, marker))
|
||||
continue;
|
||||
new (values + idx) T(val);
|
||||
key_sizes[idx] = key.size();
|
||||
keys[idx] = key.data();
|
||||
return {values + idx, true};
|
||||
}
|
||||
|
||||
if (key.size() == key_sizes[idx] &&
|
||||
memcmp(ptr, key.data(), key_sizes[idx]) == 0)
|
||||
return {values + idx, false};
|
||||
|
||||
u64 mask = nbuckets / NUM_SHARDS - 1;
|
||||
idx = (idx & ~mask) | ((idx + 1) & mask);
|
||||
retry++;
|
||||
}
|
||||
|
||||
assert(false && "ConcurrentMap is full");
|
||||
return {nullptr, false};
|
||||
}
|
||||
|
||||
bool has_key(i64 idx) {
|
||||
return keys[idx].load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
static constexpr i64 MIN_NBUCKETS = 2048;
|
||||
static constexpr i64 NUM_SHARDS = 16;
|
||||
static constexpr i64 MAX_RETRY = 128;
|
||||
|
||||
i64 nbuckets = 0;
|
||||
std::atomic<const char *> *keys = nullptr;
|
||||
u32 *key_sizes = nullptr;
|
||||
T *values = nullptr;
|
||||
|
||||
private:
|
||||
static void pause() {
|
||||
#if defined(__x86_64__)
|
||||
asm volatile("pause");
|
||||
#elif defined(__aarch64__)
|
||||
asm volatile("yield");
|
||||
#endif
|
||||
}
|
||||
|
||||
static constexpr const char *marker = "marker";
|
||||
};
|
||||
|
||||
//
|
||||
// threads.cc
|
||||
//
|
||||
|
||||
void set_thread_count(i64 n);
|
||||
|
||||
//
|
||||
// hyperloglog.cc
|
||||
//
|
||||
|
||||
class HyperLogLog {
|
||||
public:
|
||||
HyperLogLog() : buckets(NBUCKETS) {}
|
||||
|
||||
void insert(u32 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;
|
||||
|
||||
std::vector<std::atomic_uint8_t> buckets;
|
||||
};
|
||||
|
||||
//
|
||||
// filepath.cc
|
||||
//
|
||||
|
||||
template <typename T>
|
||||
std::filesystem::path filepath(const T &path) {
|
||||
return {path, std::filesystem::path::format::generic_format};
|
||||
}
|
||||
|
||||
std::string get_realpath(std::string_view path);
|
||||
std::string path_clean(std::string_view path);
|
||||
std::filesystem::path to_abs_path(std::filesystem::path path);
|
||||
|
||||
//
|
||||
// demangle.cc
|
||||
//
|
||||
|
||||
std::string_view demangle(std::string_view name);
|
||||
|
||||
//
|
||||
// compress.cc
|
||||
//
|
||||
|
||||
class ZlibCompressor {
|
||||
public:
|
||||
ZlibCompressor(std::string_view input);
|
||||
void write_to(u8 *buf);
|
||||
i64 size() const;
|
||||
|
||||
private:
|
||||
std::vector<std::vector<u8>> shards;
|
||||
u64 checksum = 0;
|
||||
};
|
||||
|
||||
class GzipCompressor {
|
||||
public:
|
||||
GzipCompressor(std::string_view input);
|
||||
void write_to(u8 *buf);
|
||||
i64 size() const;
|
||||
|
||||
private:
|
||||
std::vector<std::vector<u8>> shards;
|
||||
u32 checksum = 0;
|
||||
u32 uncompressed_size = 0;
|
||||
};
|
||||
|
||||
//
|
||||
// perf.cc
|
||||
//
|
||||
|
||||
// Counter is used to collect statistics numbers.
|
||||
class Counter {
|
||||
public:
|
||||
Counter(std::string_view name, i64 value = 0) : name(name), values(value) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
instances.push_back(this);
|
||||
}
|
||||
|
||||
Counter &operator++(int) {
|
||||
if (enabled)
|
||||
values.local()++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Counter &operator+=(int delta) {
|
||||
if (enabled)
|
||||
values.local() += delta;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static void print();
|
||||
|
||||
static inline bool enabled = false;
|
||||
|
||||
private:
|
||||
i64 get_value();
|
||||
|
||||
std::string_view name;
|
||||
tbb::enumerable_thread_specific<i64> values;
|
||||
|
||||
static inline std::vector<Counter *> instances;
|
||||
};
|
||||
|
||||
// Timer and TimeRecord records elapsed time (wall clock time)
|
||||
// used by each pass of the linker.
|
||||
struct TimerRecord {
|
||||
TimerRecord(std::string name, TimerRecord *parent = nullptr);
|
||||
void stop();
|
||||
|
||||
std::string name;
|
||||
TimerRecord *parent;
|
||||
tbb::concurrent_vector<TimerRecord *> children;
|
||||
i64 start;
|
||||
i64 end;
|
||||
i64 user;
|
||||
i64 sys;
|
||||
bool stopped = false;
|
||||
};
|
||||
|
||||
void
|
||||
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
|
||||
|
||||
template <typename C>
|
||||
class Timer {
|
||||
public:
|
||||
Timer(C &ctx, std::string name, Timer *parent = nullptr) {
|
||||
record = new TimerRecord(name, parent ? parent->record : nullptr);
|
||||
ctx.timer_records.push_back(std::unique_ptr<TimerRecord>(record));
|
||||
}
|
||||
|
||||
Timer(const Timer &) = delete;
|
||||
|
||||
~Timer() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
private:
|
||||
TimerRecord *record;
|
||||
};
|
||||
|
||||
//
|
||||
// tar.cc
|
||||
//
|
||||
|
||||
// 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:
|
||||
static constexpr i64 BLOCK_SIZE = 512;
|
||||
|
||||
TarWriter(FILE *out, std::string basedir) : out(out), basedir(basedir) {}
|
||||
|
||||
FILE *out = nullptr;
|
||||
std::string basedir;
|
||||
};
|
||||
|
||||
//
|
||||
// Memory-mapped file
|
||||
//
|
||||
|
||||
// MappedFile represents an mmap'ed input file.
|
||||
// mold uses mmap-IO only.
|
||||
template <typename C>
|
||||
class MappedFile {
|
||||
public:
|
||||
static MappedFile *open(C &ctx, std::string path);
|
||||
static MappedFile *must_open(C &ctx, std::string path);
|
||||
|
||||
~MappedFile();
|
||||
|
||||
MappedFile *slice(C &ctx, std::string name, u64 start, u64 size);
|
||||
|
||||
std::string_view get_contents() {
|
||||
return std::string_view((char *)data, size);
|
||||
}
|
||||
|
||||
i64 get_offset() const {
|
||||
return parent ? (data - parent->data + parent->get_offset()) : 0;
|
||||
}
|
||||
|
||||
// Returns a string that uniquely identify a file that is possibly
|
||||
// in an archive.
|
||||
std::string get_identifier() const {
|
||||
if (parent) {
|
||||
// We use the file offset within an archive as an identifier
|
||||
// because archive members may have the same name.
|
||||
return parent->name + ":" + std::to_string(get_offset());
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
u8 *data = nullptr;
|
||||
i64 size = 0;
|
||||
i64 mtime = 0;
|
||||
bool given_fullpath = true;
|
||||
MappedFile *parent = nullptr;
|
||||
int fd = -1;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
i64 fd = ::open(path.c_str(), O_RDONLY);
|
||||
if (fd == -1)
|
||||
return nullptr;
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == -1)
|
||||
Fatal(ctx) << path << ": fstat failed: " << errno_string();
|
||||
|
||||
MappedFile *mf = new MappedFile;
|
||||
ctx.mf_pool.push_back(std::unique_ptr<MappedFile>(mf));
|
||||
|
||||
mf->name = path;
|
||||
mf->size = st.st_size;
|
||||
|
||||
#ifdef __APPLE__
|
||||
mf->mtime = (u64)st.st_mtimespec.tv_sec * 1000000000 + st.st_mtimespec.tv_nsec;
|
||||
#else
|
||||
mf->mtime = (u64)st.st_mtim.tv_sec * 1000000000 + st.st_mtim.tv_nsec;
|
||||
#endif
|
||||
|
||||
if (st.st_size > 0) {
|
||||
mf->data = (u8 *)mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (mf->data == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return mf;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
MappedFile<C> *MappedFile<C>::must_open(C &ctx, std::string path) {
|
||||
if (MappedFile *mf = MappedFile::open(ctx, path))
|
||||
return mf;
|
||||
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
MappedFile<C> *
|
||||
MappedFile<C>::slice(C &ctx, std::string name, u64 start, u64 size) {
|
||||
MappedFile *mf = new MappedFile<C>;
|
||||
mf->name = name;
|
||||
mf->data = data + start;
|
||||
mf->size = size;
|
||||
mf->parent = this;
|
||||
|
||||
ctx.mf_pool.push_back(std::unique_ptr<MappedFile>(mf));
|
||||
return mf;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
MappedFile<C>::~MappedFile() {
|
||||
if (size && !parent)
|
||||
munmap(data, size);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,8 +0,0 @@
|
||||
#ifdef __APPLE__
|
||||
# define COMMON_DIGEST_FOR_OPENSSL
|
||||
# include <CommonCrypto/CommonDigest.h>
|
||||
# define SHA256(data, len, md) CC_SHA256(data, len, md)
|
||||
#else
|
||||
# define OPENSSL_SUPPRESS_DEPRECATED 1
|
||||
# include <openssl/sha.h>
|
||||
#endif
|
||||
@@ -0,0 +1,919 @@
|
||||
// ARM32 is a bit special from the linker's viewpoint because ARM
|
||||
// processors support two different instruction encodings: Thumb and
|
||||
// ARM (in a narrower sense). Thumb instructions are either 16 bits or
|
||||
// 32 bits, while ARM instructions are all 32 bits. Feature-wise,
|
||||
// Thumb is a subset of ARM, so not all ARM instructions are
|
||||
// representable in Thumb.
|
||||
//
|
||||
// ARM processors originally supported only ARM instructions. Thumb
|
||||
// instructions were later added to increase code density.
|
||||
//
|
||||
// ARM processors runs in either ARM mode or Thumb mode. The mode can
|
||||
// be switched using BX (branch and mode exchange)-family instructions.
|
||||
// We need to use that instructions to, for example, call a function
|
||||
// encoded in Thumb from a function encoded in ARM. Sometimes, the
|
||||
// linker even has to emit interworking thunk code to switch mode.
|
||||
//
|
||||
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
|
||||
// byte boundaries. So the least significant bit of a function address
|
||||
// is always 0.
|
||||
//
|
||||
// To distinguish Thumb functions from ARM fucntions, the LSB of a
|
||||
// function address is repurposed as a boolean flag. If the LSB is 0,
|
||||
// the function referred to by the address is encoded in ARM;
|
||||
// otherwise, Thumb.
|
||||
//
|
||||
// For example, if a symbol `foo` is of type STT_FUNC and has value
|
||||
// 0x2001, `foo` is a function using Thumb instructions whose address
|
||||
// is 0x2000 (not 0x2001, as Thumb instructions are always 2-byte
|
||||
// aligned). Likewise, if a function pointer has value 0x2001, it
|
||||
// refers a Thumb function at 0x2000.
|
||||
//
|
||||
// https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst
|
||||
|
||||
#if MOLD_ARM32LE || MOLD_ARM32BE
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
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_BASE_PREL:
|
||||
case R_ARM_GOTOFF32:
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TLS_GD32:
|
||||
case R_ARM_TLS_LDM32:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_TLS_IE32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_TARGET2:
|
||||
return (I32<E>)*arm;
|
||||
case R_ARM_THM_JUMP8:
|
||||
return sign_extend(thm[0], 8) << 1;
|
||||
case R_ARM_THM_JUMP11:
|
||||
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: {
|
||||
// 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(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, 25);
|
||||
}
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_TLS_CALL:
|
||||
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: {
|
||||
// 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(*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: {
|
||||
// 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, 16);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_arm_mov(u8 *loc, u32 val) {
|
||||
u32 imm12 = bits(val, 11, 0);
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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_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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_NONE:
|
||||
break;
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
case R_ARM_GOTOFF32:
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TLS_GD32:
|
||||
case R_ARM_TLS_LDM32:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_TLS_IE32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_TARGET2:
|
||||
*(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:
|
||||
*(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_b25(loc, val);
|
||||
break;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
*(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_arm_mov(loc, val);
|
||||
break;
|
||||
case R_ARM_PREL31:
|
||||
*(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(loc, val);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
constexpr ul32 insn[] = {
|
||||
0xe52d'e004, // push {lr}
|
||||
0xe59f'e004, // ldr lr, 2f
|
||||
0xe08f'e00e, // 1: add lr, pc, lr
|
||||
0xe5be'f008, // ldr pc, [lr, #8]!
|
||||
0x0000'0000, // 2: .word .got.plt - 1b - 8
|
||||
0x0000'0000, // (padding)
|
||||
0x0000'0000, // (padding)
|
||||
0x0000'0000, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(U32<E> *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
|
||||
}
|
||||
|
||||
static constexpr ul32 plt_entry[] = {
|
||||
0xe59f'c004, // 1: ldr ip, 2f
|
||||
0xe08c'c00f, // add ip, ip, pc
|
||||
0xe59c'f000, // ldr pc, [ip]
|
||||
0x0000'0000, // 2: .word sym@GOT - 1b
|
||||
};
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(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));
|
||||
*(U32<E> *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
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_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);
|
||||
|
||||
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)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = get_addend(*this, rel);
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 T = S & 1;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto 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:
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(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.
|
||||
*(U32<E> *)loc = 0x8000'f3af; // NOP.W
|
||||
break;
|
||||
}
|
||||
|
||||
// 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 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 {
|
||||
*(U16<E> *)(loc + 2) |= 0x1000; // BL
|
||||
write_thm_b25(loc, get_thumb_thunk_addr() + A - P);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_ARM_BASE_PREL:
|
||||
*(U32<E> *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_ARM_GOTOFF32:
|
||||
*(U32<E> *)loc = ((S + A) | T) - GOT;
|
||||
break;
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_TARGET2:
|
||||
*(U32<E> *)loc = GOT + G + A - P;
|
||||
break;
|
||||
case R_ARM_GOT_BREL:
|
||||
*(U32<E> *)loc = G + A;
|
||||
break;
|
||||
case R_ARM_CALL: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(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 = ((*(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";
|
||||
|
||||
i64 val = S + A - P;
|
||||
if (is_int(val, 26)) {
|
||||
if (T) {
|
||||
*(U32<E> *)loc = 0xfa00'0000; // BLX
|
||||
*(U32<E> *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
|
||||
} else {
|
||||
*(U32<E> *)loc = 0xeb00'0000; // BL
|
||||
*(U32<E> *)loc |= bits(val, 25, 2);
|
||||
}
|
||||
} else {
|
||||
*(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()) {
|
||||
*(U32<E> *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
// These relocs refers to a B (unconditional branch) instruction.
|
||||
// Unlike BL or BLX, we can't rewrite B to BX in place when the
|
||||
// processor mode switch is required because BX doesn't takes an
|
||||
// immediate; it takes only a register. So if mode switch is
|
||||
// required, we jump to a linker-synthesized thunk which does the
|
||||
// job with a longer code sequence.
|
||||
i64 val = S + A - P;
|
||||
if (T || !is_int(val, 26))
|
||||
val = get_arm_thunk_addr() + A - P;
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
break;
|
||||
}
|
||||
case R_ARM_PLT32:
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(U32<E> *)loc = 0xe320'f000; // NOP
|
||||
} else {
|
||||
u64 val = (T ? get_arm_thunk_addr() : S) + A - P;
|
||||
*(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:
|
||||
check(S + A - P, -(1 << 11), 1 << 11);
|
||||
*(U16<E> *)loc &= 0xf800;
|
||||
*(U16<E> *)loc |= bits(S + A - P, 11, 1);
|
||||
break;
|
||||
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()) {
|
||||
*(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.
|
||||
i64 val = S + A - P;
|
||||
if (!T || !is_int(val, 25))
|
||||
val = get_thumb_thunk_addr() + A - P;
|
||||
write_thm_b25(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
write_arm_mov(loc, ((S + A) | T) - P);
|
||||
break;
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
write_arm_mov(loc, (S + A) | T);
|
||||
break;
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
write_thm_mov(loc, ((S + A) | T) - P);
|
||||
break;
|
||||
case R_ARM_PREL31:
|
||||
check(S + A - P, -(1LL << 30), 1LL << 30);
|
||||
*(U32<E> *)loc &= 0x8000'0000;
|
||||
*(U32<E> *)loc |= (S + A - P) & 0x7fff'ffff;
|
||||
break;
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
write_thm_mov(loc, (S + A) | T);
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
write_arm_mov(loc, (S + A - P) >> 16);
|
||||
break;
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
write_thm_mov(loc, (S + A - P) >> 16);
|
||||
break;
|
||||
case R_ARM_MOVT_ABS:
|
||||
write_arm_mov(loc, (S + A) >> 16);
|
||||
break;
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
write_thm_mov(loc, (S + A) >> 16);
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
*(U32<E> *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
*(U32<E> *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
*(U32<E> *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
*(U32<E> *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LE32:
|
||||
*(U32<E> *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
// ARM32 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in r0.
|
||||
//
|
||||
// ldr r0, .L2
|
||||
// .L1: bl foo
|
||||
// R_ARM_TLS_CALL
|
||||
// .L2: .word foo + . - .L1
|
||||
// R_ARM_TLS_GOTDESC
|
||||
//
|
||||
// We may relax the instructions to the following 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.
|
||||
*(U32<E> *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(U32<E> *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
|
||||
} else {
|
||||
*(U32<E> *)loc = S - ctx.tp_addr;
|
||||
}
|
||||
break;
|
||||
case R_ARM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(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)) {
|
||||
*(U32<E> *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
|
||||
} else {
|
||||
*(U32<E> *)loc = 0xe320'f000; // nop
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
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.
|
||||
*(U16<E> *)loc = 0x4478; // add r0, pc
|
||||
*(U16<E> *)(loc + 2) = 0x6800; // ldr r0, [r0]
|
||||
} else {
|
||||
*(U32<E> *)loc = 0x8000'f3af; // nop.w
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 : get_addend(*this, rel);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(U32<E> *)loc = *val;
|
||||
else
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(U32<E> *)loc = *val;
|
||||
else
|
||||
*(U32<E> *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
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:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TARGET2:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
case R_ARM_PREL31:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_ARM_TLS_CALL:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
case R_ARM_TLS_LE32:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_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_THM_MOVW_PREL_NC:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_V4BX:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
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.
|
||||
constexpr ul32 hdr[] = {
|
||||
0xe08e'0000, // add r0, lr, r0
|
||||
0xe590'1004, // ldr r1, [r0, #4]
|
||||
0xe12f'ff11, // bx r1
|
||||
0xe320'f000, // nop
|
||||
};
|
||||
|
||||
// This is a range extension and mode switch thunk.
|
||||
// It has two entry points: +0 for Thumb and +4 for ARM.
|
||||
constexpr u8 entry[] = {
|
||||
// .thumb
|
||||
0x78, 0x47, // bx pc # jumps to 1f
|
||||
0xc0, 0x46, // nop
|
||||
// .arm
|
||||
0x00, 0xc0, 0x9f, 0xe5, // 1: ldr ip, 3f
|
||||
0x0f, 0xf0, 0x8c, 0xe0, // 2: add pc, ip, pc
|
||||
0x00, 0x00, 0x00, 0x00, // 3: .word sym - 2b
|
||||
};
|
||||
|
||||
static_assert(E::thunk_hdr_size == sizeof(hdr));
|
||||
static_assert(E::thunk_size == sizeof(entry));
|
||||
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
memcpy(buf, hdr, sizeof(hdr));
|
||||
buf += sizeof(hdr);
|
||||
|
||||
u64 P = output_section.shdr.sh_addr + offset + sizeof(hdr);
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
memcpy(buf, entry, sizeof(entry));
|
||||
*(U32<E> *)(buf + 12) = sym->get_addr(ctx) - P - 16;
|
||||
|
||||
buf += sizeof(entry);
|
||||
P += sizeof(entry);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
u64 get_eflags(Context<E> &ctx) {
|
||||
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
|
||||
// handling record for the current instruction pointer. The table needs
|
||||
// to be sorted by their addresses.
|
||||
//
|
||||
// Other target uses .eh_frame_hdr instead for the same purpose.
|
||||
// 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 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
|
||||
// the followings:
|
||||
//
|
||||
// 1. CANTUNWIND indicating that there's no unwinding info for the function,
|
||||
// 2. a compact unwinding record encoded into a 32-bit value, or
|
||||
// 3. a 31-bit relative address which points to a larger record in
|
||||
// the .ARM.extab section.
|
||||
//
|
||||
// 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 CANTUNWIND = 1;
|
||||
|
||||
struct Entry {
|
||||
U32<E> addr;
|
||||
U32<E> val;
|
||||
};
|
||||
|
||||
// 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();
|
||||
|
||||
// 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 address, we first translate them so that the addresses
|
||||
// are relative to the beginning of the section.
|
||||
auto is_relative = [](u32 val) {
|
||||
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, 31) + offset;
|
||||
if (is_relative(ent[i].val))
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
|
||||
});
|
||||
|
||||
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) {
|
||||
i64 offset = sizeof(Entry) * i;
|
||||
ent[i].addr = 0x7fff'ffff & (ent[i].addr - offset);
|
||||
if (is_relative(ent[i].val))
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val - offset);
|
||||
});
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -0,0 +1,617 @@
|
||||
// This file contains ARM64-specific code. Being new, the ARM64's ELF
|
||||
// psABI doesn't have anything peculiar. ARM64 is a clean RISC
|
||||
// instruction set that supports PC-relative load/store instructions.
|
||||
//
|
||||
// Unlike ARM32, instructions length doesn't vary. All ARM64
|
||||
// instructions are 4 bytes long.
|
||||
//
|
||||
// Branch instructions used for function call can jump within ±128 MiB.
|
||||
// We need to create range extension thunks to support binaries whose
|
||||
// .text is larger than that.
|
||||
//
|
||||
// Unlike most other targets, the TLSDESC access model is used by default
|
||||
// for -fPIC to access thread-local variables instead of the less
|
||||
// efficient GD model. You can still enable GD but it needs the
|
||||
// -mtls-dialect=trad flag. Since GD is used rarely, we don't need to
|
||||
// implement GD → LE relaxation.
|
||||
//
|
||||
// https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst
|
||||
|
||||
#if MOLD_ARM64LE || MOLD_ARM64BE
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
static void write_adrp(u8 *buf, u64 val) {
|
||||
*(ul32 *)buf |= (bits(val, 13, 12) << 29) | (bits(val, 32, 14) << 5);
|
||||
}
|
||||
|
||||
static void write_adr(u8 *buf, u64 val) {
|
||||
*(ul32 *)buf |= (bits(val, 1, 0) << 29) | (bits(val, 20, 2) << 5);
|
||||
}
|
||||
|
||||
static void write_movn_movz(u8 *buf, i64 val) {
|
||||
*(ul32 *)buf &= 0b0000'0000'0110'0000'0000'0000'0001'1111;
|
||||
|
||||
if (val >= 0)
|
||||
*(ul32 *)buf |= 0xd280'0000 | (bits(val, 15, 0) << 5); // rewrite to movz
|
||||
else
|
||||
*(ul32 *)buf |= 0x9280'0000 | (bits(~val, 15, 0) << 5); // rewrite to movn
|
||||
}
|
||||
|
||||
static u64 page(u64 val) {
|
||||
return val & 0xffff'ffff'ffff'f000;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
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
|
||||
0xd420'7d00, // brk
|
||||
0xd420'7d00, // brk
|
||||
0xd420'7d00, // brk
|
||||
};
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr + 16;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_adrp(buf + 4, page(gotplt) - page(plt + 4));
|
||||
*(ul32 *)(buf + 8) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(buf + 12) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
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]
|
||||
0xd61f'0220, // br x17
|
||||
};
|
||||
|
||||
u64 gotplt = sym.get_gotplt_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_adrp(buf, page(gotplt) - page(plt));
|
||||
*(ul32 *)(buf + 4) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(buf + 8) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
constexpr ul32 insn[] = {
|
||||
0x9000'0010, // adrp x16, GOT[n]
|
||||
0xf940'0211, // ldr x17, [x16, GOT[n]]
|
||||
0xd61f'0220, // br x17
|
||||
0xd420'7d00, // brk
|
||||
};
|
||||
|
||||
u64 got = sym.get_got_pltgot_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_adrp(buf, page(got) - page(plt));
|
||||
*(ul32 *)(buf + 4) |= bits(got, 11, 3) << 10;
|
||||
}
|
||||
|
||||
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_AARCH64_ABS64:
|
||||
*(U64<E> *)loc = val;
|
||||
break;
|
||||
case R_AARCH64_PREL32:
|
||||
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
case R_AARCH64_PREL64:
|
||||
*(U64<E> *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_adrp(u8 *loc) {
|
||||
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADRP--Form-PC-relative-address-to-4KB-page-
|
||||
u32 insn = *(ul32 *)loc;
|
||||
return (bits(insn, 31, 24) & 0b1001'1111) == 0b1001'0000;
|
||||
}
|
||||
|
||||
static bool is_ldr(u8 *loc) {
|
||||
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDR--immediate---Load-Register--immediate--
|
||||
u32 insn = *(ul32 *)loc;
|
||||
return (bits(insn, 31, 20) & 0b1111'1111'1100) == 0b1111'1001'0100;
|
||||
}
|
||||
|
||||
static bool is_add(u8 *loc) {
|
||||
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADD--immediate---Add--immediate--
|
||||
u32 insn = *(ul32 *)loc;
|
||||
return (bits(insn, 31, 20) & 0b1111'1111'1100) == 0b1001'0001'0000;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
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;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
break;
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 1) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 2) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 3) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 4) << 10;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
check(S + A, 0, 1LL << 48);
|
||||
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
*(ul32 *)loc |= bits(S + A, 63, 48) << 5;
|
||||
break;
|
||||
case R_AARCH64_ADR_GOT_PAGE:
|
||||
if (sym.has_got(ctx)) {
|
||||
i64 val = page(G + GOT + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
} else {
|
||||
// Relax GOT-loading ADRP+LDR to an immediate ADRP+ADD
|
||||
i64 val = page(S + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
|
||||
u32 reg = bits(*(ul32 *)loc, 4, 0);
|
||||
*(ul32 *)(loc + 4) = 0x9100'0000 | (reg << 5) | reg; // ADD
|
||||
*(ul32 *)(loc + 4) |= bits(S + A, 11, 0) << 10;
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_ADR_PREL_PG_HI21:
|
||||
case R_AARCH64_ADR_PREL_PG_HI21_NC: {
|
||||
// The ARM64 psABI defines that an `ADRP x0, foo` and `ADD x0, x0,
|
||||
// :lo12: foo` instruction pair to materialize a PC-relative address
|
||||
// in a register can be relaxed to `NOP` followed by `ADR x0, foo`
|
||||
// if foo is in PC ± 1 MiB.
|
||||
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
|
||||
i + 1 < rels.size()) {
|
||||
i64 val = S + A - P - 4;
|
||||
const ElfRel<E> &rel2 = rels[i + 1];
|
||||
if (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 &&
|
||||
rel2.r_addend == rel.r_addend &&
|
||||
is_adrp(loc) &&
|
||||
is_add(loc + 4)) {
|
||||
u32 reg1 = bits(*(ul32 *)loc, 4, 0);
|
||||
u32 reg2 = bits(*(ul32 *)(loc + 4), 4, 0);
|
||||
if (reg1 == reg2) {
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
*(ul32 *)(loc + 4) = 0x1000'0000 | reg1; // adr
|
||||
write_adr(loc + 4, val);
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i64 val = page(S + A) - page(P);
|
||||
if (rel.r_type == R_AARCH64_ADR_PREL_PG_HI21)
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_LO21:
|
||||
check(S + A - P, -(1LL << 20), 1LL << 20);
|
||||
write_adr(loc, S + A - P);
|
||||
break;
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
break;
|
||||
}
|
||||
|
||||
i64 val = S + 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);
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19:
|
||||
check(S + A - P, -(1LL << 20), 1LL << 20);
|
||||
*(ul32 *)loc |= bits(S + A - P, 20, 2) << 5;
|
||||
break;
|
||||
case R_AARCH64_PREL16:
|
||||
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);
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_PREL64:
|
||||
*(U64<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
*(ul32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15: {
|
||||
i64 val = G + GOT + A - page(GOT);
|
||||
check(val, 0, 1 << 15);
|
||||
*(ul32 *)loc |= bits(val, 14, 3) << 10;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: {
|
||||
i64 val = page(sym.get_gottp_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
write_movn_movz(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
write_movn_movz(loc, val >> 16);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G2: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, -(1LL << 47), 1LL << 47);
|
||||
write_movn_movz(loc, val >> 32);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, 0, 1LL << 24);
|
||||
*(ul32 *)loc |= bits(val, 23, 12) << 10;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
check(S + A - ctx.tp_addr, 0, 1 << 12);
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21: {
|
||||
i64 val = page(sym.get_tlsgd_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
*(ul32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
// ARM64 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in x0.
|
||||
//
|
||||
// adrp x0, 0
|
||||
// R_AARCH64_TLSDESC_ADR_PAGE21 foo
|
||||
// ldr x1, [x0]
|
||||
// R_AARCH64_TLSDESC_LD64_LO12 foo
|
||||
// add x0, x0, #0
|
||||
// R_AARCH64_TLSDESC_ADD_LO12 foo
|
||||
// blr x1
|
||||
// R_AARCH64_TLSDESC_CALL foo
|
||||
//
|
||||
// We may relax the instructions to the following 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);
|
||||
write_adrp(loc, val);
|
||||
} else {
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
if (sym.has_tlsdesc(ctx))
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
|
||||
else
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0x9000'0000; // adrp x0, 0
|
||||
write_adrp(loc, page(sym.get_gottp_addr(ctx) + A) - page(P));
|
||||
} else {
|
||||
*(ul32 *)loc = 0xd2a0'0000; // movz x0, 0, lsl #16
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 32, 16) << 5;
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// Do nothing
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0xf940'0000; // ldr x0, [x0, 0]
|
||||
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
} else {
|
||||
*(ul32 *)loc = 0xf280'0000; // movk x0, 0
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
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;
|
||||
|
||||
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))
|
||||
*(U64<E> *)loc = *val;
|
||||
else
|
||||
*(U64<E> *)loc = S + A;
|
||||
break;
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_ADR_GOT_PAGE:
|
||||
// An ADR_GOT_PAGE and GOT_LO12_NC relocation pair is used to load a
|
||||
// symbol's address from GOT. If the GOT value is a link-time
|
||||
// constant, we may be able to rewrite the ADRP+LDR instruction pair
|
||||
// with an ADRP+ADD, eliminating a GOT memory load.
|
||||
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
|
||||
i + 1 < rels.size()) {
|
||||
// ADRP+LDR must be consecutive and use the same register to relax.
|
||||
const ElfRel<E> &rel2 = rels[i + 1];
|
||||
if (rel2.r_type == R_AARCH64_LD64_GOT_LO12_NC &&
|
||||
rel2.r_offset == rel.r_offset + 4 &&
|
||||
rel2.r_sym == rel.r_sym &&
|
||||
rel.r_addend == 0 &&
|
||||
rel2.r_addend == 0 &&
|
||||
is_adrp(loc) &&
|
||||
is_ldr(loc + 4)) {
|
||||
u32 rd = bits(*(ul32 *)loc, 4, 0);
|
||||
u32 rn = bits(*(ul32 *)(loc + 4), 9, 5);
|
||||
u32 rt = bits(*(ul32 *)(loc + 4), 4, 0);
|
||||
if (rd == rn && rn == rt) {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26:
|
||||
case R_AARCH64_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_AARCH64_ADR_PREL_PG_HI21:
|
||||
case R_AARCH64_ADR_PREL_PG_HI21_NC:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G2:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_ABS64:
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
case R_AARCH64_ADR_PREL_LO21:
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19:
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
case R_AARCH64_PREL16:
|
||||
case R_AARCH64_PREL32:
|
||||
case R_AARCH64_PREL64:
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
constexpr 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
|
||||
0xd420'7d00, // brk
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,598 @@
|
||||
// i386 is similar to x86-64 but lacks PC-relative memory access
|
||||
// instructions. So it's not straightforward to support position-
|
||||
// independent code (PIC) on that target.
|
||||
//
|
||||
// If an object file is compiled with -fPIC, a function that needs to load
|
||||
// a value from memory first obtains its own address with the following
|
||||
// code
|
||||
//
|
||||
// call __x86.get_pc_thunk.bx
|
||||
//
|
||||
// where __x86.get_pc_thunk.bx is defined as
|
||||
//
|
||||
// __x86.get_pc_thunk.bx:
|
||||
// mov (%esp), %ebx # move the return address to %ebx
|
||||
// ret
|
||||
//
|
||||
// . With the function's own address (or, more precisely, the address
|
||||
// immediately after the call instruction), the function can compute an
|
||||
// absolute address of a variable with its address + link-time constant.
|
||||
//
|
||||
// Executing call-mov-ret isn't very cheap, and allocating one register to
|
||||
// store PC isn't cheap too, especially given that i386 has only 8
|
||||
// general-purpose registers. But that's the cost of PIC on i386. You need
|
||||
// to pay it when creating a .so and a position-independent executable.
|
||||
//
|
||||
// When a position-independent function calls another function, it sets
|
||||
// %ebx to the address of .got. Position-independent PLT entries use that
|
||||
// register to load values from .got.plt/.got.
|
||||
//
|
||||
// If we are creating a position-dependent executable (PDE), we can't
|
||||
// assume that %ebx is set to .got. For PDE, we need to create position-
|
||||
// dependent PLT entries which don't use %ebx.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/i386.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = I386;
|
||||
|
||||
template <>
|
||||
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_PC8:
|
||||
return *loc;
|
||||
case R_386_16:
|
||||
case R_386_PC16:
|
||||
return *(ul16 *)loc;
|
||||
case R_386_32:
|
||||
case R_386_PC32:
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
case R_386_PLT32:
|
||||
case R_386_GOTOFF:
|
||||
case R_386_GOTPC:
|
||||
case R_386_TLS_LDM:
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_IE:
|
||||
case R_386_TLS_GD:
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_GOTDESC:
|
||||
return *(ul32 *)loc;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_NONE:
|
||||
break;
|
||||
case R_386_8:
|
||||
case R_386_PC8:
|
||||
*loc = val;
|
||||
break;
|
||||
case R_386_16:
|
||||
case R_386_PC16:
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
case R_386_32:
|
||||
case R_386_PC32:
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
case R_386_PLT32:
|
||||
case R_386_GOTOFF:
|
||||
case R_386_GOTPC:
|
||||
case R_386_TLS_LDM:
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_IE:
|
||||
case R_386_TLS_GD:
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_GOTDESC:
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
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 + 3) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
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 + 2) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
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 + 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[] = {
|
||||
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 + 1) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ul32 *)(buf + 7) = sym.get_gotplt_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
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_386_32:
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
case R_386_PC32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 relax_got32x(u8 *loc) {
|
||||
// mov imm(%reg1), %reg2 -> lea imm(%reg1), %reg2
|
||||
if (loc[0] == 0x8b)
|
||||
return 0x8d00 | loc[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Relax GD to LE
|
||||
static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %eax
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32:
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 6) = val;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
// Relax LD to LE
|
||||
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 tls_size) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x2d, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = 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
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 6) = tls_size;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = get_addend(*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);
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_386_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
case R_386_32:
|
||||
break;
|
||||
case R_386_PC8:
|
||||
check(S + A - P, -(1 << 7), 1 << 7);
|
||||
*loc = S + A - P;
|
||||
break;
|
||||
case R_386_PC16:
|
||||
check(S + A - P, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = S + A - P;
|
||||
break;
|
||||
case R_386_PC32:
|
||||
case R_386_PLT32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
*(ul32 *)loc = G + A;
|
||||
break;
|
||||
case R_386_GOT32X:
|
||||
if (sym.has_got(ctx)) {
|
||||
*(ul32 *)loc = G + A;
|
||||
} else {
|
||||
u32 insn = relax_got32x(loc - 2);
|
||||
assert(insn);
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
}
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_386_GOTPC:
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_386_TLS_GOTIE:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_386_TLS_LE:
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_386_TLS_IE:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
if (sym.has_tlsgd(ctx))
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
else
|
||||
relax_gd_to_le(loc, rels[++i], S - ctx.tp_addr);
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
else
|
||||
relax_ld_to_le(loc, rels[++i], ctx.tp_addr - ctx.tls_begin);
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
// i386 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in %eax.
|
||||
//
|
||||
// lea 0(%ebx), %eax
|
||||
// R_386_TLS_GOTDESC foo
|
||||
// call *(%eax)
|
||||
// R_386_TLS_DESC_CALL foo
|
||||
//
|
||||
// We may relax the instructions to the following 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
|
||||
//
|
||||
// We allow the following alternative code sequence too because
|
||||
// LLVM emits such code.
|
||||
//
|
||||
// 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)) {
|
||||
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 {
|
||||
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;
|
||||
case R_386_TLS_DESC_CALL:
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// call *(%eax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
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 : 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);
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_386_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
case R_386_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_386_PC8:
|
||||
check(S + A, -(1 << 7), 1 << 7);
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_386_PC16:
|
||||
check(S + A, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
case R_386_PC32:
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_386_GOTPC:
|
||||
*(ul32 *)loc = GOT + A;
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
if (rel.r_type == R_386_TLS_GD || rel.r_type == R_386_TLS_LDM) {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_PC32 &&
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_16:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_PC8:
|
||||
case R_386_PC16:
|
||||
case R_386_PC32:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOTPC:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_386_GOT32X:
|
||||
// We always want to relax GOT32X even if --no-relax is given
|
||||
// because static PIE doesn't work without it.
|
||||
if (sym.is_pcrel_linktime_const(ctx) && relax_got32x(loc - 2)) {
|
||||
// Do nothing
|
||||
} else {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
}
|
||||
break;
|
||||
case R_386_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_IE:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx)))
|
||||
i++;
|
||||
else
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared))
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
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:
|
||||
case R_386_TLS_DESC_CALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,312 @@
|
||||
// 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 a retro-computing hobby activity, but the
|
||||
// processor was a popular choice to build Unix computers during '80s.
|
||||
// Early Sun workstations for example used m68k. Macintosh until 1994 were
|
||||
// based on m68k as well until they switched to PowerPC (and then to x86
|
||||
// and to ARM.)
|
||||
//
|
||||
// From the linker's point of view, it is not hard to support m68k. It's
|
||||
// just a 32-bit big-endian CISC ISA. Compared to comtemporary i386,
|
||||
// m68k's psABI is actually simpler because m68k has PC-relative memory
|
||||
// access instructions and therefore can support position-independent
|
||||
// code without too much hassle.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/m68k.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = M68K;
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const u8 insn[] = {
|
||||
0x2f, 0x00, // move.l %d0, -(%sp)
|
||||
0x2f, 0x3b, 0x01, 0x70, 0, 0, 0, 0, // move.l (GOTPLT+4, %pc), -(%sp)
|
||||
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOTPLT+8, %pc])
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 6) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr;
|
||||
*(ub32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 4;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0x20, 0x3c, 0, 0, 0, 0, // move.l PLT_OFFSET, %d0
|
||||
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOTPLT_ENTRY, %pc])
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 2) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ub32 *)(buf + 10) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOT_ENTRY, %pc])
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 4) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 2;
|
||||
}
|
||||
|
||||
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_68K_32:
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
case R_68K_PC32:
|
||||
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
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;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
check(val, 0, 1 << 16);
|
||||
*(ub16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write16s = [&](u64 val) {
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write8 = [&](u64 val) {
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write8s = [&](u64 val) {
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_32:
|
||||
break;
|
||||
case R_68K_16:
|
||||
write16(S + A);
|
||||
break;
|
||||
case R_68K_8:
|
||||
write8(S + A);
|
||||
break;
|
||||
case R_68K_PC32:
|
||||
case R_68K_PLT32:
|
||||
*(ub32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_68K_PC16:
|
||||
case R_68K_PLT16:
|
||||
write16s(S + A - P);
|
||||
break;
|
||||
case R_68K_PC8:
|
||||
case R_68K_PLT8:
|
||||
write8s(S + A - P);
|
||||
break;
|
||||
case R_68K_GOTPCREL32:
|
||||
*(ub32 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_68K_GOTPCREL16:
|
||||
write16s(GOT + A - P);
|
||||
break;
|
||||
case R_68K_GOTPCREL8:
|
||||
write8s(GOT + A - P);
|
||||
break;
|
||||
case R_68K_GOTOFF32:
|
||||
*(ub32 *)loc = G + A;
|
||||
break;
|
||||
case R_68K_GOTOFF16:
|
||||
write16(G + A);
|
||||
break;
|
||||
case R_68K_GOTOFF8:
|
||||
write8(G + A);
|
||||
break;
|
||||
case R_68K_TLS_GD32:
|
||||
*(ub32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_68K_TLS_GD16:
|
||||
write16(sym.get_tlsgd_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_GD8:
|
||||
write8(sym.get_tlsgd_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LDM32:
|
||||
*(ub32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_68K_TLS_LDM16:
|
||||
write16(ctx.got->get_tlsld_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LDM8:
|
||||
write8(ctx.got->get_tlsld_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LDO32:
|
||||
*(ub32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_68K_TLS_LDO16:
|
||||
write16s(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_68K_TLS_LDO8:
|
||||
write8s(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_68K_TLS_IE32:
|
||||
*(ub32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_68K_TLS_IE16:
|
||||
write16(sym.get_gottp_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_IE8:
|
||||
write8(sym.get_gottp_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LE32:
|
||||
*(ub32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_68K_TLS_LE16:
|
||||
write16(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_68K_TLS_LE8:
|
||||
write8(S + A - ctx.tp_addr);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || 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_68K_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ub32 *)loc = *val;
|
||||
else
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
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 m68k";
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_16:
|
||||
case R_68K_8:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_PC32:
|
||||
case R_68K_PC16:
|
||||
case R_68K_PC8:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_GOTPCREL32:
|
||||
case R_68K_GOTPCREL16:
|
||||
case R_68K_GOTPCREL8:
|
||||
case R_68K_GOTOFF32:
|
||||
case R_68K_GOTOFF16:
|
||||
case R_68K_GOTOFF8:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_68K_PLT32:
|
||||
case R_68K_PLT16:
|
||||
case R_68K_PLT8:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_68K_TLS_GD32:
|
||||
case R_68K_TLS_GD16:
|
||||
case R_68K_TLS_GD8:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_68K_TLS_LDM32:
|
||||
case R_68K_TLS_LDM16:
|
||||
case R_68K_TLS_LDM8:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_68K_TLS_IE32:
|
||||
case R_68K_TLS_IE16:
|
||||
case R_68K_TLS_IE8:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_68K_TLS_LE32:
|
||||
case R_68K_TLS_LE16:
|
||||
case R_68K_TLS_LE8:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_32:
|
||||
case R_68K_TLS_LDO32:
|
||||
case R_68K_TLS_LDO16:
|
||||
case R_68K_TLS_LDO8:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,442 @@
|
||||
// This file implements the PowerPC 32-bit ISA. For 64-bit PowerPC, see
|
||||
// arch-ppc64v1.cpp and arch-ppc64v2.cpp.
|
||||
//
|
||||
// PPC32 is a RISC ISA. It has 32 general-purpose registers (GPRs).
|
||||
// r0, r11 and r12 are reserved for static linkers, so we can use these
|
||||
// registers in PLTs and range extension thunks. In addition to that, it
|
||||
// has a few special registers. Notable ones are LR which holds a return
|
||||
// address and CTR which we can use to store a branch target address.
|
||||
//
|
||||
// It feels that the PPC32 psABI is unnecessarily complicated at first
|
||||
// glance, but that is mainly stemmed from the fact that the ISA lacks
|
||||
// PC-relative load/store instructions. Since machine instructions cannot
|
||||
// load data relative to its own address, it is not straightforward to
|
||||
// support position-independent code (PIC) on PPC32.
|
||||
//
|
||||
// A position-independent function typically contains the following code
|
||||
// in the prologue to obtain its own address:
|
||||
//
|
||||
// mflr r0 // save the current return address to %r0
|
||||
// bcl 20, 31, 4 // call the next instruction as if it were a function
|
||||
// mtlr r12 // save the return address to %r12
|
||||
// mtlr r0 // restore the original return address
|
||||
//
|
||||
// An object file compiled with -fPIC contains a data section named
|
||||
// `.got2` to store addresses of locally-defined global variables and
|
||||
// constants. A PIC function usually computes its .got2+0x8000 and set it
|
||||
// to %r30. This scheme allows the function to access global objects
|
||||
// defined in the same input file with a single %r30-relative load/store
|
||||
// instruction with a 16-bit offset, given that .got2 is smaller than
|
||||
// 0x10000 (or 65536) bytes.
|
||||
//
|
||||
// Since each object file has its own .got2, %r30 refers to different
|
||||
// places in a merged .got2 for two functions that came from different
|
||||
// input files. Therefore, %r30 makes sense only within a single function.
|
||||
//
|
||||
// Technically, we can reuse a %r30 value in our PLT if we create a PLT
|
||||
// _for each input file_ (that's what GNU ld seems to be doing), but that
|
||||
// doesn't seems to be worth its complexity. Our PLT simply doesn't rely
|
||||
// on a %r30 value.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/ppc32.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = PPC32;
|
||||
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
constexpr ub32 insn[] = {
|
||||
// Get the address of this PLT section
|
||||
0x7c08'02a6, // mflr r0
|
||||
0x429f'0005, // bcl 20, 31, 4
|
||||
0x7d88'02a6, // 1: mflr r12
|
||||
0x7c08'03a6, // mtlr r0
|
||||
|
||||
// Compute the runtime address of GOTPLT+12
|
||||
0x3d8c'0000, // addis r12, r12, (GOTPLT - 1b)@higha
|
||||
0x398c'0000, // addi r12, r12, (GOTPLT - 1b)@lo
|
||||
|
||||
// Compute the PLT entry offset
|
||||
0x7d6c'5850, // sub r11, r11, r12
|
||||
0x1d6b'0003, // mulli r11, r11, 3
|
||||
|
||||
// Load GOTPLT[2] and branch to GOTPLT[1]
|
||||
0x800c'fff8, // lwz r0, -8(r12)
|
||||
0x7c09'03a6, // mtctr r0
|
||||
0x818c'fffc, // lwz r12, -4(r12)
|
||||
0x4e80'0420, // bctr
|
||||
0x6000'0000, // nop
|
||||
0x6000'0000, // nop
|
||||
0x6000'0000, // nop
|
||||
0x6000'0000, // nop
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::plt_hdr_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
|
||||
ub32 *loc = (ub32 *)buf;
|
||||
loc[4] |= higha(ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4);
|
||||
loc[5] |= lo(ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4);
|
||||
}
|
||||
|
||||
static constexpr ub32 plt_entry[] = {
|
||||
// Get the address of this PLT entry
|
||||
0x7c08'02a6, // mflr r0
|
||||
0x429f'0005, // bcl 20, 31, 4
|
||||
0x7d88'02a6, // mflr r12
|
||||
0x7c08'03a6, // mtlr r0
|
||||
|
||||
// Load an address from the GOT/GOTPLT entry and jump to that address
|
||||
0x3d6c'0000, // addis r11, r12, OFFSET@higha
|
||||
0x396b'0000, // addi r11, r11, OFFSET@lo
|
||||
0x818b'0000, // lwz r12, 0(r11)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static_assert(E::plt_size == sizeof(plt_entry));
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
|
||||
ub32 *loc = (ub32 *)buf;
|
||||
i64 offset = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 8;
|
||||
loc[4] |= higha(offset);
|
||||
loc[5] |= lo(offset);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static_assert(E::pltgot_size == sizeof(plt_entry));
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
|
||||
ub32 *loc = (ub32 *)buf;
|
||||
i64 offset = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 8;
|
||||
loc[4] |= higha(offset);
|
||||
loc[5] |= lo(offset);
|
||||
}
|
||||
|
||||
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_PPC_ADDR32:
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
case R_PPC_REL32:
|
||||
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
u64 GOT2 = file.extra.got2 ? file.extra.got2->get_addr() : 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC_ADDR14:
|
||||
*(ub32 *)loc |= bits(S + A, 15, 2) << 2;
|
||||
break;
|
||||
case R_PPC_ADDR16:
|
||||
case R_PPC_UADDR16:
|
||||
case R_PPC_ADDR16_LO:
|
||||
*(ub16 *)loc = lo(S + A);
|
||||
break;
|
||||
case R_PPC_ADDR16_HI:
|
||||
*(ub16 *)loc = hi(S + A);
|
||||
break;
|
||||
case R_PPC_ADDR16_HA:
|
||||
*(ub16 *)loc = ha(S + A);
|
||||
break;
|
||||
case R_PPC_ADDR24:
|
||||
*(ub32 *)loc |= bits(S + A, 25, 2) << 2;
|
||||
break;
|
||||
case R_PPC_ADDR30:
|
||||
*(ub32 *)loc |= bits(S + A, 31, 2) << 2;
|
||||
break;
|
||||
case R_PPC_PLT16_LO:
|
||||
*(ub16 *)loc = lo(G + GOT - A - GOT2);
|
||||
break;
|
||||
case R_PPC_PLT16_HI:
|
||||
*(ub16 *)loc = hi(G + GOT - A - GOT2);
|
||||
break;
|
||||
case R_PPC_PLT16_HA:
|
||||
*(ub16 *)loc = ha(G + GOT - A - GOT2);
|
||||
break;
|
||||
case R_PPC_PLT32:
|
||||
*(ub32 *)loc = G + GOT - A - GOT2;
|
||||
break;
|
||||
case R_PPC_REL14:
|
||||
*(ub32 *)loc |= bits(S + A - P, 15, 2) << 2;
|
||||
break;
|
||||
case R_PPC_REL16:
|
||||
case R_PPC_REL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - P);
|
||||
break;
|
||||
case R_PPC_REL16_HI:
|
||||
*(ub16 *)loc = hi(S + A - P);
|
||||
break;
|
||||
case R_PPC_REL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - P);
|
||||
break;
|
||||
case R_PPC_REL24:
|
||||
case R_PPC_LOCAL24PC: {
|
||||
i64 val = S + A - P;
|
||||
if (!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) || !is_int(val, 26))
|
||||
val = sym.get_thunk_addr(ctx, P) - P;
|
||||
*(ub32 *)loc |= bits(val, 25, 2) << 2;
|
||||
break;
|
||||
}
|
||||
case R_PPC_REL32:
|
||||
case R_PPC_PLTREL32:
|
||||
*(ub32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_PPC_GOT16:
|
||||
case R_PPC_GOT16_LO:
|
||||
*(ub16 *)loc = lo(G + A);
|
||||
break;
|
||||
case R_PPC_GOT16_HI:
|
||||
*(ub16 *)loc = hi(G + A);
|
||||
break;
|
||||
case R_PPC_GOT16_HA:
|
||||
*(ub16 *)loc = ha(G + A);
|
||||
break;
|
||||
case R_PPC_TPREL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC_TPREL16_HI:
|
||||
*(ub16 *)loc = hi(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC_TPREL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC_DTPREL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC_DTPREL16_HI:
|
||||
*(ub16 *)loc = hi(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC_DTPREL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC_GOT_TLSGD16:
|
||||
*(ub16 *)loc = sym.get_tlsgd_addr(ctx) - GOT;
|
||||
break;
|
||||
case R_PPC_GOT_TLSLD16:
|
||||
*(ub16 *)loc = ctx.got->get_tlsld_addr(ctx) - GOT;
|
||||
break;
|
||||
case R_PPC_GOT_TPREL16:
|
||||
*(ub16 *)loc = sym.get_gottp_addr(ctx) - GOT;
|
||||
break;
|
||||
case R_PPC_ADDR32:
|
||||
case R_PPC_UADDR32:
|
||||
case R_PPC_TLS:
|
||||
case R_PPC_TLSGD:
|
||||
case R_PPC_TLSLD:
|
||||
case R_PPC_PLTSEQ:
|
||||
case R_PPC_PLTCALL:
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || 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_PPC_ADDR32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ub32 *)loc = *val;
|
||||
else
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC_ADDR14:
|
||||
case R_PPC_ADDR16:
|
||||
case R_PPC_UADDR16:
|
||||
case R_PPC_ADDR16_LO:
|
||||
case R_PPC_ADDR16_HI:
|
||||
case R_PPC_ADDR16_HA:
|
||||
case R_PPC_ADDR24:
|
||||
case R_PPC_ADDR30:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC_REL14:
|
||||
case R_PPC_REL16:
|
||||
case R_PPC_REL16_LO:
|
||||
case R_PPC_REL16_HI:
|
||||
case R_PPC_REL16_HA:
|
||||
case R_PPC_REL32:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC_GOT16:
|
||||
case R_PPC_GOT16_LO:
|
||||
case R_PPC_GOT16_HI:
|
||||
case R_PPC_GOT16_HA:
|
||||
case R_PPC_PLT16_LO:
|
||||
case R_PPC_PLT16_HI:
|
||||
case R_PPC_PLT16_HA:
|
||||
case R_PPC_PLT32:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_PPC_REL24:
|
||||
case R_PPC_PLTREL24:
|
||||
case R_PPC_PLTREL32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_PPC_GOT_TLSGD16:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_PPC_GOT_TLSLD16:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_PPC_GOT_TPREL16:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_PPC_TPREL16_LO:
|
||||
case R_PPC_TPREL16_HI:
|
||||
case R_PPC_TPREL16_HA:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC_ADDR32:
|
||||
case R_PPC_UADDR32:
|
||||
case R_PPC_LOCAL24PC:
|
||||
case R_PPC_TLS:
|
||||
case R_PPC_TLSGD:
|
||||
case R_PPC_TLSLD:
|
||||
case R_PPC_DTPREL16_LO:
|
||||
case R_PPC_DTPREL16_HI:
|
||||
case R_PPC_DTPREL16_HA:
|
||||
case R_PPC_PLTSEQ:
|
||||
case R_PPC_PLTCALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void 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
|
||||
0x7d88'02a6, // mflr r12
|
||||
0x7c08'03a6, // mtlr r0
|
||||
|
||||
// Materialize the destination's address in %r11 and jump to that address
|
||||
0x3d6c'0000, // addis r11, r12, OFFSET@higha
|
||||
0x396b'0000, // addi r11, r11, OFFSET@lo
|
||||
0x7d69'03a6, // mtctr r11
|
||||
0x4e80'0420, // bctr
|
||||
0x6000'0000, // nop
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(plt_entry));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + 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);
|
||||
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;
|
||||
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
|
||||
@@ -0,0 +1,665 @@
|
||||
// This file contains code for the 64-bit PowerPC ELFv1 ABI that is
|
||||
// commonly used for big-endian PPC systems. Modern PPC systems that use
|
||||
// the processor in the little-endian mode use the ELFv2 ABI instead. For
|
||||
// ELFv2, see arch-ppc64v2.cc.
|
||||
//
|
||||
// Even though they are similiar, ELFv1 isn't only different from ELFv2 in
|
||||
// endianness. The most notable difference is, in ELFv1, a function
|
||||
// pointer doesn't directly refer to the entry point of a function but
|
||||
// instead refers to a data structure so-called "function descriptor".
|
||||
//
|
||||
// The function descriptor is essentially a pair of a function entry point
|
||||
// address and a value that should be set to %r2 before calling that
|
||||
// function. There is also a third member for "the environment pointer for
|
||||
// languages such as Pascal and PL/1" according to the psABI, but it looks
|
||||
// like no one acutally uses it. In total, the function descriptor is 24
|
||||
// bytes long. Here is why we need it.
|
||||
//
|
||||
// PPC generally lacks PC-relative data access instructions. Position-
|
||||
// independent code sets GOT + 0x8000 to %r2 and access global variables
|
||||
// relative to %r2.
|
||||
//
|
||||
// Each ELF file has its own GOT. If a function calls another function in
|
||||
// the same ELF file, it doesn't have to reset %r2. However, if it is in
|
||||
// other file (e.g. other .so), it has to set a new value to %r2 so that
|
||||
// the register contains the callee's GOT + 0x8000.
|
||||
//
|
||||
// In this way, you can't call a function just by knowing the function's
|
||||
// entry point address. You also need to know a proper %r2 value for the
|
||||
// function. This is why a function pointer refers to a tuple of an
|
||||
// address and a %r2 value.
|
||||
//
|
||||
// If a function call is made through PLT, PLT takes care of restoring %r2.
|
||||
// Therefore, the caller has to restore %r2 only for function calls
|
||||
// through function pointers.
|
||||
//
|
||||
// .opd (short for "official procedure descriptors") contains function
|
||||
// descriptors.
|
||||
//
|
||||
// You can think OPD as this: even in other targets, a function can have a
|
||||
// few different addresses for different purposes. It may not only have an
|
||||
// entry point address but may also have PLT and/or GOT addresses.
|
||||
// In PPCV1, it may have an OPD address in addition to these. OPD address
|
||||
// is used for relocations that refers to the address of a function as a
|
||||
// function pointer.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/ppc64v1.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = PPC64V1;
|
||||
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
|
||||
// .plt is used only for lazy symbol resolution on PPC64. All PLT
|
||||
// calls are made via range extension thunks even if they are within
|
||||
// reach. Thunks read addresses from .got.plt and jump there.
|
||||
// Therefore, once PLT symbols are resolved and final addresses are
|
||||
// written to .got.plt, thunks just skip .plt and directly jump to the
|
||||
// resolved addresses.
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
constexpr ub32 insn[] = {
|
||||
0x7d88'02a6, // mflr r12
|
||||
0x429f'0005, // bcl 20, 31, 4 // obtain PC
|
||||
0x7d68'02a6, // mflr r11
|
||||
0x7d88'03a6, // mtlr r12
|
||||
0x3d6b'0000, // addis r11, r11, GOTPLT_OFFSET@ha
|
||||
0x396b'0000, // addi r11, r11, GOTPLT_OFFSET@lo
|
||||
0xe98b'0000, // ld r12,0(r11)
|
||||
0xe84b'0008, // ld r2,8(r11)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0xe96b'0010, // ld r11,16(r11)
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::plt_hdr_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
|
||||
i64 val = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
|
||||
*(ub32 *)(buf + 16) |= higha(val);
|
||||
*(ub32 *)(buf + 20) |= lo(val);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
ub32 *loc = (ub32 *)buf;
|
||||
i64 idx = sym.get_plt_idx(ctx);
|
||||
|
||||
// The PPC64 ELFv1 ABI requires PLT entries to be vary in size depending
|
||||
// on their indices. Unlike other targets, .got.plt is filled not by us
|
||||
// but by the loader, so we don't have a control over where the initial
|
||||
// call to the PLT entry jumps to. So we need to strictly follow the PLT
|
||||
// section layout as the loader expect it to be.
|
||||
if (idx < 0x8000) {
|
||||
constexpr ub32 insn[] = {
|
||||
0x3800'0000, // li r0, PLT_INDEX
|
||||
0x4b00'0000, // b plt0
|
||||
};
|
||||
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
loc[0] |= idx;
|
||||
loc[1] |= (ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 4) & 0x00ff'ffff;
|
||||
} else {
|
||||
constexpr ub32 insn[] = {
|
||||
0x3c00'0000, // lis r0, PLT_INDEX@high
|
||||
0x6000'0000, // ori r0, r0, PLT_INDEX@lo
|
||||
0x4b00'0000, // b plt0
|
||||
};
|
||||
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
loc[0] |= high(idx);
|
||||
loc[1] |= lo(idx);
|
||||
loc[2] |= (ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 8) & 0x00ff'ffff;
|
||||
}
|
||||
}
|
||||
|
||||
// .plt.got is not necessary on PPC64 because range extension thunks
|
||||
// directly read GOT entries and jump there.
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_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_PPC64_ADDR64:
|
||||
*(ub64 *)loc = val;
|
||||
break;
|
||||
case R_PPC64_REL32:
|
||||
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
case R_PPC64_REL64:
|
||||
*(ub64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
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 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_TOC:
|
||||
break;
|
||||
case R_PPC64_TOC16_HA:
|
||||
*(ub16 *)loc = ha(S + A - TOC);
|
||||
break;
|
||||
case R_PPC64_TOC16_LO:
|
||||
*(ub16 *)loc = lo(S + A - TOC);
|
||||
break;
|
||||
case R_PPC64_TOC16_DS:
|
||||
check(S + A - TOC, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc |= (S + A - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_TOC16_LO_DS:
|
||||
*(ub16 *)loc |= (S + A - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_REL24: {
|
||||
i64 val = sym.get_addr(ctx, NO_OPD) + 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;
|
||||
|
||||
// If a callee is an external function, PLT saves %r2 to the
|
||||
// caller's r2 save slot. We need to restore it after function
|
||||
// return. To do so, there's usually a NOP as a placeholder
|
||||
// after a BL. 0x6000'0000 is a NOP.
|
||||
if (sym.has_plt(ctx) && *(ub32 *)(loc + 4) == 0x6000'0000)
|
||||
*(ub32 *)(loc + 4) = 0xe841'0028; // ld r2, 40(r1)
|
||||
break;
|
||||
}
|
||||
case R_PPC64_REL32:
|
||||
*(ub32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_PPC64_REL64:
|
||||
*(ub64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_PPC64_REL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - P);
|
||||
break;
|
||||
case R_PPC64_REL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - P);
|
||||
break;
|
||||
case R_PPC64_PLT16_HA:
|
||||
*(ub16 *)loc = ha(G + GOT - TOC);
|
||||
break;
|
||||
case R_PPC64_PLT16_HI:
|
||||
*(ub16 *)loc = hi(G + GOT - TOC);
|
||||
break;
|
||||
case R_PPC64_PLT16_LO:
|
||||
*(ub16 *)loc = lo(G + GOT - TOC);
|
||||
break;
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
*(ub16 *)loc |= (G + GOT - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
*(ub16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
*(ub16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
*(ub16 *)loc = lo(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
*(ub16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
*(ub16 *)loc = lo(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_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:
|
||||
case R_PPC64_TLSGD:
|
||||
case R_PPC64_TLSLD:
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || 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;
|
||||
|
||||
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))
|
||||
*(ub64 *)loc = *val;
|
||||
else
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
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;
|
||||
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);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT | NEEDS_PPC_OPD;
|
||||
|
||||
// Any relocation except R_PPC64_REL24 is considered as an
|
||||
// address-taking relocation.
|
||||
if (rel.r_type != R_PPC64_REL24 && sym.get_type() == STT_FUNC)
|
||||
sym.flags |= NEEDS_PPC_OPD;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_PPC64_REL24:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_PPC64_PLT16_HA:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
ctx.needs_tlsld = true;
|
||||
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:
|
||||
case R_PPC64_TOC16_LO:
|
||||
case R_PPC64_TOC16_LO_DS:
|
||||
case R_PPC64_TOC16_DS:
|
||||
case R_PPC64_REL16_HA:
|
||||
case R_PPC64_REL16_LO:
|
||||
case R_PPC64_PLT16_HI:
|
||||
case R_PPC64_PLT16_LO:
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
case R_PPC64_TLS:
|
||||
case R_PPC64_TLSGD:
|
||||
case R_PPC64_TLSLD:
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
case R_PPC64_DTPREL16_LO_DS:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
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.
|
||||
constexpr ub32 pltgot_thunk[] = {
|
||||
// Store the caller's %r2
|
||||
0xf841'0028, // std %r2, 40(%r1)
|
||||
|
||||
// Load an address of a function descriptor
|
||||
0x3d82'0000, // addis %r12, %r2, foo@got@toc@ha
|
||||
0xe98c'0000, // ld %r12, foo@got@toc@lo(%r12)
|
||||
|
||||
// Restore the callee's %r2
|
||||
0xe84c'0008, // ld %r2, 8(%r12)
|
||||
|
||||
// Jump to the function
|
||||
0xe98c'0000, // ld %r12, 0(%r12)
|
||||
0x7d89'03a6, // mtctr %r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
// If the destination is .plt, read a function descriptor from .got.plt.
|
||||
constexpr ub32 plt_thunk[] = {
|
||||
// Store the caller's %r2
|
||||
0xf841'0028, // std %r2, 40(%r1)
|
||||
|
||||
// Materialize an address of a function descriptor
|
||||
0x3d82'0000, // addis %r12, %r2, foo@gotplt@toc@ha
|
||||
0x398c'0000, // addi %r12, %r12, foo@gotplt@toc@lo
|
||||
|
||||
// Restore the callee's %r2
|
||||
0xe84c'0008, // ld %r2, 8(%r12)
|
||||
|
||||
// Jump to the function
|
||||
0xe98c'0000, // ld %r12, 0(%r12)
|
||||
0x7d89'03a6, // mtctr %r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
// If the destination is a non-imported function, we directly jump
|
||||
// to the function entry address.
|
||||
constexpr ub32 local_thunk[] = {
|
||||
0x3d82'0000, // addis r12, r2, foo@toc@ha
|
||||
0x398c'0000, // addi r12, r12, foo@toc@lo
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
0x6000'0000, // nop
|
||||
0x6000'0000, // nop
|
||||
0x6000'0000, // nop
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(pltgot_thunk));
|
||||
static_assert(E::thunk_size == sizeof(plt_thunk));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
if (sym->has_got(ctx)) {
|
||||
i64 val = sym->get_got_addr(ctx) - ctx.extra.TOC->value;
|
||||
memcpy(buf, pltgot_thunk, sizeof(pltgot_thunk));
|
||||
*(ub32 *)(buf + 4) |= higha(val);
|
||||
*(ub32 *)(buf + 8) |= lo(val);
|
||||
} else if(sym->has_plt(ctx)) {
|
||||
i64 val = sym->get_gotplt_addr(ctx) - ctx.extra.TOC->value;
|
||||
memcpy(buf, plt_thunk, sizeof(plt_thunk));
|
||||
*(ub32 *)(buf + 4) |= higha(val);
|
||||
*(ub32 *)(buf + 8) |= lo(val);
|
||||
} else {
|
||||
i64 val = sym->get_addr(ctx, NO_OPD) - ctx.extra.TOC->value;
|
||||
memcpy(buf, local_thunk, sizeof(local_thunk));
|
||||
*(ub32 *)buf |= higha(val);
|
||||
*(ub32 *)(buf + 4) |= lo(val);
|
||||
}
|
||||
|
||||
buf += E::thunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
static InputSection<E> *get_opd_section(ObjectFile<E> &file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file.sections)
|
||||
if (isec && isec->name() == ".opd")
|
||||
return isec.get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static ElfRel<E> *
|
||||
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, u64 offset) {
|
||||
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
auto it = ranges::lower_bound(rels, offset, {}, [](const ElfRel<E> &r) {
|
||||
return (u64)r.r_offset;
|
||||
});
|
||||
if (it == rels.end() || it->r_offset != offset)
|
||||
return nullptr;
|
||||
return &*it;
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct OpdSymbol {
|
||||
u64 r_offset = 0;
|
||||
Symbol<E> *sym = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
static Symbol<E> *
|
||||
get_opd_sym_at(std::span<OpdSymbol> syms, u64 offset) {
|
||||
auto it = ranges::lower_bound(syms, offset, {}, &OpdSymbol::r_offset);
|
||||
if (it == syms.end() || it->r_offset != offset)
|
||||
return nullptr;
|
||||
return it->sym;
|
||||
}
|
||||
|
||||
// Compiler creates an .opd entry for each function symbol. The intention
|
||||
// is to make it possible to create an output .opd section just by linking
|
||||
// input .opd sections in the same manner as we do to other normal input
|
||||
// sections.
|
||||
//
|
||||
// However, in reality, .opd isn't a normal input section. It needs many
|
||||
// special treatments as follows:
|
||||
//
|
||||
// 1. A function symbol refers to not a .text but an .opd. Its address
|
||||
// works fine for address-taking relocations such as R_PPC64_ADDR64.
|
||||
// However, R_PPC64_REL24 (which is used for branch instruction) needs
|
||||
// a function's real address instead of the function's .opd address.
|
||||
// We need to read .opd contents to find out a function entry point
|
||||
// address to apply R_PPC64_REL24.
|
||||
//
|
||||
// 2. Output .opd entries are needed only for functions whose addresses
|
||||
// are taken. Just copying input .opd sections to an output would
|
||||
// produces lots of dead .opd entries.
|
||||
//
|
||||
// 3. In this design, all function symbols refer to an .opd section, and
|
||||
// that doesn't work well with graph traversal optimizations such as
|
||||
// garbage collection or identical comdat folding. For example, garbage
|
||||
// collector would mark an .opd alive which in turn mark all functions
|
||||
// thatare referenced by .opd as alive, effectively keeping all
|
||||
// functions as alive.
|
||||
//
|
||||
// The problem is that the compiler creates a half-baked .opd section, and
|
||||
// the linker has to figure out what all these .opd entries and
|
||||
// relocations are trying to achieve. It's like the compiler would emit a
|
||||
// half-baked .plt section in an object file and the linker has to deal
|
||||
// with that. That's not a good design.
|
||||
//
|
||||
// So, in this function, we undo what the compiler did to .opd. We remove
|
||||
// function symbols from .opd and reattach them to their function entry
|
||||
// points. We also rewrite relocations that directly refer to an input
|
||||
// .opd section so that they refer to function symbols instead. We then
|
||||
// mark input .opd sections as dead.
|
||||
//
|
||||
// After this function, we mark symbols with the NEEDS_PPC_OPD flag if the
|
||||
// symbol needs an .opd entry. We then create an output .opd just like we
|
||||
// do for .plt or .got.
|
||||
void ppc64v1_rewrite_opd(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
InputSection<E> *opd = get_opd_section(*file);
|
||||
if (!opd)
|
||||
return;
|
||||
opd->is_alive = false;
|
||||
|
||||
// Move symbols from .opd to .text.
|
||||
std::vector<OpdSymbol> opd_syms;
|
||||
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file != file || sym->get_input_section() != opd)
|
||||
continue;
|
||||
|
||||
if (u32 ty = sym->get_type(); ty != STT_FUNC && ty != STT_GNU_IFUNC)
|
||||
continue;
|
||||
|
||||
ElfRel<E> *rel = get_relocation_at(ctx, *opd, sym->value);
|
||||
if (!rel)
|
||||
Fatal(ctx) << *file << ": cannot find a relocation in .opd for "
|
||||
<< *sym << " at offset 0x" << std::hex << (u64)sym->value;
|
||||
|
||||
Symbol<E> *sym2 = file->symbols[rel->r_sym];
|
||||
if (sym2->get_type() != STT_SECTION)
|
||||
Fatal(ctx) << *file << ": bad relocation in .opd referring to " << *sym2;
|
||||
|
||||
opd_syms.push_back({sym->value, sym});
|
||||
|
||||
sym->set_input_section(sym2->get_input_section());
|
||||
sym->value = rel->r_addend;
|
||||
}
|
||||
|
||||
// Sort symbols so that get_opd_sym_at() can do binary search.
|
||||
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) {
|
||||
if (!isec || !isec->is_alive || isec.get() == opd)
|
||||
continue;
|
||||
|
||||
for (ElfRel<E> &r : isec->get_rels(ctx)) {
|
||||
Symbol<E> &sym = *file->symbols[r.r_sym];
|
||||
if (sym.get_input_section() != opd)
|
||||
continue;
|
||||
|
||||
Symbol<E> *real_sym = get_opd_sym_at(opd_syms, r.r_addend);
|
||||
if (!real_sym)
|
||||
Fatal(ctx) << *isec << ": cannot find a symbol in .opd for " << r
|
||||
<< " at offset 0x" << std::hex << (u64)r.r_addend;
|
||||
|
||||
r.r_sym = real_sym->sym_idx;
|
||||
r.r_addend = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// When a function is exported, the dynamic symbol for the function should
|
||||
// refers to the function's .opd entry. This function marks such symbols
|
||||
// with NEEDS_PPC_OPD.
|
||||
void ppc64v1_scan_symbols(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols)
|
||||
if (sym->file == file && sym->is_exported)
|
||||
if (u32 ty = sym->get_type(); ty == STT_FUNC || ty == STT_GNU_IFUNC)
|
||||
sym->flags |= NEEDS_PPC_OPD;
|
||||
});
|
||||
|
||||
// Functions referenced by the ELF header also have to have .opd entries.
|
||||
if (!ctx.arg.entry->is_imported)
|
||||
ctx.arg.entry->flags |= NEEDS_PPC_OPD;
|
||||
|
||||
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) {
|
||||
sym->set_opd_idx(ctx, symbols.size());
|
||||
symbols.push_back(sym);
|
||||
this->shdr.sh_size += ENTRY_SIZE;
|
||||
}
|
||||
|
||||
i64 PPC64OpdSection::get_reldyn_size(Context<E> &ctx) const {
|
||||
if (ctx.arg.pic)
|
||||
return symbols.size() * 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PPC64OpdSection::copy_buf(Context<E> &ctx) {
|
||||
ub64 *buf = (ub64 *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
ElfRel<E> *rel = nullptr;
|
||||
if (ctx.arg.pic)
|
||||
rel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset + reldyn_offset);
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u64 addr = sym->get_addr(ctx, NO_PLT | NO_OPD);
|
||||
*buf++ = addr;
|
||||
*buf++ = ctx.extra.TOC->value;
|
||||
*buf++ = 0;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
u64 loc = sym->get_opd_addr(ctx);
|
||||
*rel++ = ElfRel<E>(loc, E::R_RELATIVE, 0, addr);
|
||||
*rel++ = ElfRel<E>(loc + 8, E::R_RELATIVE, 0, ctx.extra.TOC->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,660 @@
|
||||
// This file implements the PowerPC ELFv2 ABI which was standardized in
|
||||
// 2014. Modern little-endian PowerPC systems are based on this ABI.
|
||||
// The ABI is often referred to as "ppc64le". This shouldn't be confused
|
||||
// with "ppc64" which refers to the original, big-endian PowerPC systems.
|
||||
//
|
||||
// PPC64 is a bit tricky to support because PC-relative load/store
|
||||
// instructions hadn't been available until Power10 which debuted in 2021.
|
||||
// Prior to Power10, it wasn't trivial for position-independent code (PIC)
|
||||
// to load a value from, for example, .got, as we can't do that with [PC +
|
||||
// the offset to the .got entry].
|
||||
//
|
||||
// In the following, I'll explain how PIC is supported on pre-Power10
|
||||
// systems first and then explain what has changed with Power10.
|
||||
//
|
||||
//
|
||||
// Position-independent code on Power9 or earlier:
|
||||
//
|
||||
// We can get the program counter on older PPC64 systems with the
|
||||
// following four instructions
|
||||
//
|
||||
// mflr r1 // save the current link register to r1
|
||||
// bl .+4 // branch to the next instruction as if it were a function
|
||||
// mflr r0 // copy the return address to r0
|
||||
// mtlr r1 // restore the original link register value
|
||||
//
|
||||
// , but it's too expensive to do if we do this for each load/store.
|
||||
//
|
||||
// As a workaround, most functions are compiled in such a way that r2 is
|
||||
// assumed to always contain the address of .got + 0x8000. With this, we
|
||||
// can for example load the first entry of .got with a single instruction
|
||||
// `lw r0, -0x8000(r2)`. r2 is called the TOC pointer.
|
||||
//
|
||||
// There's only one .got for each ELF module. Therefore, if a callee is in
|
||||
// the same ELF module, r2 doesn't have to be recomputed. Most function
|
||||
// calls are usually within the same ELF module, so this mechanism is
|
||||
// efficient.
|
||||
//
|
||||
// A function compiled for pre-Power10 usually has two entry points,
|
||||
// global and local. The global entry point usually 8 bytes precedes
|
||||
// the local entry point. In between is the following instructions:
|
||||
//
|
||||
// addis r2, r12, .TOC.@ha
|
||||
// addi r2, r2, .TOC.@lo + 4;
|
||||
//
|
||||
// The global entry point assumes that the address of itself is in r12,
|
||||
// and it computes its own TOC pointer from r12. It's easy to do so for
|
||||
// the callee because the offset between its .got + 0x8000 and the
|
||||
// function is known at link-time. The above code sequence then falls
|
||||
// through to the local entry point that assumes r2 is .got + 0x8000.
|
||||
//
|
||||
// So, if a callee's TOC pointer is different from the current one
|
||||
// (e.g. calling a function in another .so), we first load the callee's
|
||||
// address to r12 (e.g. from .got.plt with a r2-relative load) and branch
|
||||
// to that address. Then the callee computes its own TOC pointer using
|
||||
// r12.
|
||||
//
|
||||
//
|
||||
// Position-independent code on Power10:
|
||||
//
|
||||
// Power10 added 8-bytes-long instructions to the ISA. Some of them are
|
||||
// PC-relative load/store instructions that take 34 bits offsets.
|
||||
// Functions compiled with `-mcpu=power10` use these instructions for PIC.
|
||||
// r2 does not have a special meaning in such fucntions.
|
||||
//
|
||||
// When a fucntion compiled for Power10 calls a function that uses the TOC
|
||||
// pointer, we need to compute a correct value for TOC and set it to r2
|
||||
// before transferring the control to the callee. Thunks are responsible
|
||||
// for doing it.
|
||||
//
|
||||
// `_NOTOC` relocations such as `R_PPC64_REL24_NOTOC` indicate that the
|
||||
// callee does not use TOC (i.e. compiled with `-mcpu=power10`). If a
|
||||
// function using TOC is referenced via a `_NOTOC` relocation, that call
|
||||
// is made through a range extension thunk.
|
||||
//
|
||||
//
|
||||
// Note on section names: the PPC64 psABI uses a weird naming convention
|
||||
// which calls .got.plt .plt. We ignored that part because it's just
|
||||
// confusing. Since the runtime only cares about segments, we should be
|
||||
// able to name sections whatever we want.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/ppc64v2.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = PPC64V2;
|
||||
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
|
||||
static void write34(u8 *loc, u64 x) {
|
||||
ul32 *buf = (ul32 *)loc;
|
||||
buf[0] = (buf[0] & 0xfffc'0000) | bits(x, 33, 16);
|
||||
buf[1] = (buf[1] & 0xffff'0000) | bits(x, 15, 0);
|
||||
}
|
||||
|
||||
// .plt is used only for lazy symbol resolution on PPC64. All PLT
|
||||
// calls are made via range extension thunks even if they are within
|
||||
// reach. Thunks read addresses from .got.plt and jump there.
|
||||
// Therefore, once PLT symbols are resolved and final addresses are
|
||||
// written to .got.plt, thunks just skip .plt and directly jump to the
|
||||
// resolved addresses.
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
constexpr ul32 insn[] = {
|
||||
// Get PC
|
||||
0x7c08'02a6, // mflr r0
|
||||
0x429f'0005, // bcl 20, 31, 4 // obtain PC
|
||||
0x7d68'02a6, // mflr r11
|
||||
0x7c08'03a6, // mtlr r0
|
||||
|
||||
// Compute the PLT entry index
|
||||
0x398c'ffd4, // addi r12, r12, -44
|
||||
0x7c0b'6050, // subf r0, r11, r12
|
||||
0x7800'f082, // rldicl r0, r0, 62, 2
|
||||
|
||||
// Compute the address of .got.plt
|
||||
0x3d6b'0000, // addis r11, r11, GOTPLT_OFFSET@ha
|
||||
0x396b'0000, // addi r11, r11, GOTPLT_OFFSET@lo
|
||||
|
||||
// Load .got.plt[0] and .got.plt[1] and branch to .got.plt[0]
|
||||
0xe98b'0000, // ld r12, 0(r11)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0xe96b'0008, // ld r11, 8(r11)
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
|
||||
i64 val = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
|
||||
*(ul32 *)(buf + 28) |= higha(val);
|
||||
*(ul32 *)(buf + 32) |= lo(val);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
// When the control is transferred to a PLT entry, the PLT entry's
|
||||
// address is already set to %r12 by the caller.
|
||||
i64 offset = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx);
|
||||
*(ul32 *)buf = 0x4b00'0000 | (offset & 0x00ff'ffff); // b plt0
|
||||
}
|
||||
|
||||
// .plt.got is not necessary on PPC64 because range extension thunks
|
||||
// directly read GOT entries and jump there.
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_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_PPC64_ADDR64:
|
||||
*(ul64 *)loc = val;
|
||||
break;
|
||||
case R_PPC64_REL32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
case R_PPC64_REL64:
|
||||
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
static u64 get_local_entry_offset(Context<E> &ctx, Symbol<E> &sym) {
|
||||
i64 val = sym.esym().ppc64_local_entry;
|
||||
assert(val <= 7);
|
||||
if (val == 7)
|
||||
Fatal(ctx) << sym << ": local entry offset 7 is reserved";
|
||||
|
||||
if (val == 0 || val == 1)
|
||||
return 0;
|
||||
return 1 << val;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
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_TOC16_HA:
|
||||
*(ul16 *)loc = ha(S + A - TOC);
|
||||
break;
|
||||
case R_PPC64_TOC16_LO:
|
||||
*(ul16 *)loc = lo(S + A - TOC);
|
||||
break;
|
||||
case R_PPC64_TOC16_DS:
|
||||
case R_PPC64_TOC16_LO_DS:
|
||||
*(ul16 *)loc |= (S + A - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_REL24:
|
||||
if (sym.has_plt(ctx) || !sym.esym().ppc64_preserves_r2()) {
|
||||
i64 val = r2save_thunk_addr() + A - P;
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
|
||||
// The thunk saves %r2 to the caller's r2 save slot. We need to
|
||||
// restore it after function return. To do so, there's usually a
|
||||
// NOP as a placeholder after a BL. 0x6000'0000 is a NOP.
|
||||
if (*(ul32 *)(loc + 4) == 0x6000'0000)
|
||||
*(ul32 *)(loc + 4) = 0xe841'0018; // ld r2, 24(r1)
|
||||
} else {
|
||||
i64 val = S + get_local_entry_offset(ctx, sym) + A - P;
|
||||
if (!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().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 (!is_int(val, 26))
|
||||
val = no_r2save_thunk_addr() + A - P;
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
}
|
||||
break;
|
||||
case R_PPC64_REL32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_PPC64_REL64:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_PPC64_REL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - P);
|
||||
break;
|
||||
case R_PPC64_REL16_LO:
|
||||
*(ul16 *)loc = lo(S + A - P);
|
||||
break;
|
||||
case R_PPC64_PLT16_HA:
|
||||
*(ul16 *)loc = ha(G + GOT - TOC);
|
||||
break;
|
||||
case R_PPC64_PLT16_HI:
|
||||
*(ul16 *)loc = hi(G + GOT - TOC);
|
||||
break;
|
||||
case R_PPC64_PLT16_LO:
|
||||
*(ul16 *)loc = lo(G + GOT - TOC);
|
||||
break;
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
*(ul16 *)loc |= (G + GOT - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_PLT_PCREL34:
|
||||
case R_PPC64_PLT_PCREL34_NOTOC:
|
||||
case R_PPC64_GOT_PCREL34:
|
||||
write34(loc, G + GOT - P);
|
||||
break;
|
||||
case R_PPC64_PCREL34:
|
||||
write34(loc, S + A - P);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
*(ul16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL_PCREL34:
|
||||
write34(loc, sym.get_gottp_addr(ctx) - P);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
*(ul16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
*(ul16 *)loc = lo(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD_PCREL34:
|
||||
write34(loc, sym.get_tlsgd_addr(ctx) - P);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
*(ul16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
*(ul16 *)loc = lo(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD_PCREL34:
|
||||
write34(loc, ctx.got->get_tlsld_addr(ctx) - P);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_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;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_LO:
|
||||
*(ul16 *)loc = lo(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_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:
|
||||
case R_PPC64_PLTCALL_NOTOC:
|
||||
case R_PPC64_TLS:
|
||||
case R_PPC64_TLSGD:
|
||||
case R_PPC64_TLSLD:
|
||||
case R_PPC64_ENTRY:
|
||||
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;
|
||||
|
||||
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))
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(ul64 *)loc = S + A;
|
||||
break;
|
||||
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;
|
||||
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);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
case R_PPC64_GOT_TPREL_PCREL34:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_PPC64_REL24:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_PPC64_REL24_NOTOC:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
ctx.extra.is_power10 = true;
|
||||
break;
|
||||
case R_PPC64_PLT16_HA:
|
||||
case R_PPC64_PLT_PCREL34:
|
||||
case R_PPC64_PLT_PCREL34_NOTOC:
|
||||
case R_PPC64_GOT_PCREL34:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
case R_PPC64_GOT_TLSGD_PCREL34:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
case R_PPC64_GOT_TLSLD_PCREL34:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_TPREL16_LO_DS:
|
||||
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:
|
||||
case R_PPC64_TOC16_LO:
|
||||
case R_PPC64_TOC16_LO_DS:
|
||||
case R_PPC64_TOC16_DS:
|
||||
case R_PPC64_REL16_HA:
|
||||
case R_PPC64_REL16_LO:
|
||||
case R_PPC64_PLT16_HI:
|
||||
case R_PPC64_PLT16_LO:
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
case R_PPC64_PCREL34:
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTSEQ_NOTOC:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_PLTCALL_NOTOC:
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
case R_PPC64_TLS:
|
||||
case R_PPC64_TLSGD:
|
||||
case R_PPC64_TLSLD:
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
case R_PPC64_DTPREL16_LO_DS:
|
||||
case R_PPC64_DTPREL34:
|
||||
case R_PPC64_ENTRY:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
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.
|
||||
constexpr ul32 plt_thunk[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x3d82'0000, // addis r12, r2, foo@gotplt@toc@ha
|
||||
0xe98c'0000, // ld r12, foo@gotplt@toc@lo(r12)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
constexpr ul32 plt_thunk_power10[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x0410'0000, // pld r12, foo@gotplt@pcrel
|
||||
0xe580'0000,
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
// If the destination is a non-imported function, we directly jump
|
||||
// to its local entry point.
|
||||
constexpr ul32 local_thunk[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x3d82'0000, // addis r12, r2, foo@toc@ha
|
||||
0x398c'0000, // addi r12, r12, foo@toc@lo
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
constexpr ul32 local_thunk_power10[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x0610'0000, // pla r12, foo@pcrel
|
||||
0x3980'0000,
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(plt_thunk));
|
||||
static_assert(E::thunk_size == sizeof(plt_thunk_power10));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk_power10));
|
||||
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + 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);
|
||||
|
||||
if (ctx.extra.is_power10) {
|
||||
memcpy(buf, plt_thunk_power10, E::thunk_size);
|
||||
write34(buf + 8, got - P - 8);
|
||||
} else {
|
||||
memcpy(buf, plt_thunk, E::thunk_size);
|
||||
*(ul32 *)(buf + 8) |= higha(got - TOC);
|
||||
*(ul32 *)(buf + 12) |= lo(got - TOC);
|
||||
}
|
||||
} else {
|
||||
u64 S = sym->get_addr(ctx);
|
||||
if (ctx.extra.is_power10) {
|
||||
memcpy(buf, local_thunk_power10, E::thunk_size);
|
||||
write34(buf + 8, S - P - 8);
|
||||
} else {
|
||||
memcpy(buf, local_thunk, E::thunk_size);
|
||||
*(ul32 *)(buf + 8) |= higha(S - TOC);
|
||||
*(ul32 *)(buf + 12) |= lo(S - TOC);
|
||||
}
|
||||
}
|
||||
|
||||
buf += E::thunk_size;
|
||||
P += E::thunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
+1204
File diff suppressed because it is too large.
Load diff
Loaded 100 of 2755 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user