mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 17:52:02 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7bfe418ba | ||
|
|
05f8bcff31 | ||
|
|
d148db3c2d | ||
|
|
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']
|
||||
+143
-22
@@ -4,30 +4,65 @@ on:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
env:
|
||||
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
|
||||
MOLD_SUPPRESS_MACHO_WARNING: 1
|
||||
jobs:
|
||||
build-clang:
|
||||
build-clang-make:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- test
|
||||
- test-asan
|
||||
- test-tsan
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: rui314/setup-mold@v1
|
||||
with:
|
||||
make-default: false
|
||||
- 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
|
||||
- uses: rui314/setup-mold@staging
|
||||
- name: install-build-deps
|
||||
run: sudo apt-get update && sudo ./install-build-deps.sh
|
||||
- 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:
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
make -j$(nproc) CC=clang CXX=clang++ ${{ matrix.target }}
|
||||
|
||||
build-clang-cmake:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- ''
|
||||
- '-DMOLD_USE_ASAN=On'
|
||||
- '-DMOLD_USE_TSAN=On'
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: rui314/setup-mold@staging
|
||||
- name: install-build-deps
|
||||
run: sudo apt-get update && sudo ./install-build-deps.sh
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ${{ matrix.target }} ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest . -j$(nproc)
|
||||
|
||||
build-gcc-make:
|
||||
runs-on: ubuntu-20.04
|
||||
container: gcc:11.1.0
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: apt-get
|
||||
- name: install-build-deps
|
||||
run: |
|
||||
dpkg --add-architecture i386 && apt-get update && apt-get install -y sudo libssl-dev zlib1g-dev libxxhash-dev cmake clang bsdmainutils dwarfdump qemu-user gcc-10-i686-linux-gnu gcc-10-aarch64-linux-gnu gcc-10-riscv64-linux-gnu gcc-10-arm-linux-gnueabihf g++-10-i686-linux-gnu g++-10-aarch64-linux-gnu g++-10-riscv64-linux-gnu g++-10-arm-linux-gnueabihf
|
||||
dpkg --add-architecture i386
|
||||
apt-get update
|
||||
./install-build-deps.sh
|
||||
apt-get install -y sudo qemu-user gcc-10-i686-linux-gnu gcc-10-aarch64-linux-gnu gcc-10-riscv64-linux-gnu gcc-10-arm-linux-gnueabihf g++-10-i686-linux-gnu g++-10-aarch64-linux-gnu g++-10-riscv64-linux-gnu g++-10-arm-linux-gnueabihf
|
||||
ln -sf /usr/bin/i686-linux-gnu-gcc-10 /usr/bin/i686-linux-gnu-gcc
|
||||
ln -sf /usr/bin/i686-linux-gnu-g++-10 /usr/bin/i686-linux-gnu-g++
|
||||
ln -sf /usr/bin/aarch64-linux-gnu-gcc-10 /usr/bin/aarch64-linux-gnu-gcc
|
||||
@@ -38,19 +73,105 @@ jobs:
|
||||
ln -sf /usr/bin/riscv64-linux-gnu-g++-10 /usr/bin/riscv64-linux-gnu-g++
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make CC=gcc CXX=g++ -j$(nproc)
|
||||
- name: make test
|
||||
run: make -j$(nproc) test-all
|
||||
build-macos:
|
||||
- name: make test-all
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
make -j$(nproc) CC=gcc CXX=g++ test-all
|
||||
|
||||
build-gcc-cmake:
|
||||
runs-on: ubuntu-20.04
|
||||
container: gcc:11.1.0
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install-build-deps
|
||||
run: |
|
||||
# Install cross toolchains
|
||||
dpkg --add-architecture i386
|
||||
apt-get update
|
||||
./install-build-deps.sh
|
||||
apt-get install -y sudo 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++
|
||||
|
||||
# Install a RV32 toolchain from third party since it's not available
|
||||
# as an Ubuntu package.
|
||||
mkdir /usr/local/rv32
|
||||
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2022.08.26/riscv32-glibc-ubuntu-20.04-nightly-2022.08.26-nightly.tar.gz | tar -C /usr/local/rv32 --strip-components=1 -xzf -
|
||||
|
||||
ln -sf /usr/local/rv32/sysroot /usr/riscv32-linux-gnu
|
||||
ln -sf /usr/local/rv32/bin/riscv32-unknown-linux-gnu-objdump /usr/bin/riscv32-linux-gnu-objdump
|
||||
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
|
||||
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-g++ -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-g++
|
||||
chmod 755 /usr/bin/riscv32-linux-gnu-gcc /usr/bin/riscv32-linux-gnu-g++
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DMOLD_ENABLE_QEMU_TESTS=On -DMOLD_ENABLE_QEMU_TESTS_RV32=On ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest . -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: test-results
|
||||
path: |
|
||||
build/out
|
||||
build/Testing
|
||||
|
||||
build-macos-make:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- test
|
||||
- test-asan
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: brew
|
||||
run: brew install xxhash
|
||||
- uses: rui314/setup-mold@staging
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make -j$(sysctl -n hw.logicalcpu)
|
||||
- name: make test
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make -j$(sysctl -n hw.logicalcpu) test
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
make -j$(sysctl -n hw.logicalcpu) ${{ matrix.target }}
|
||||
|
||||
build-macos-cmake:
|
||||
runs-on: macos-11
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- ''
|
||||
- '-DMOLD_USE_ASAN=On'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ${{ matrix.target }} ..
|
||||
cmake --build . -j$(sysctl -n hw.physicalcpu)
|
||||
ctest . -j$(sysctl -n hw.physicalcpu)
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -T clangcl ..
|
||||
cmake --build . -j $Env:NUMBER_OF_PROCESSORS
|
||||
@@ -8,7 +8,9 @@ compile_commands.json
|
||||
options.inc
|
||||
core
|
||||
ld
|
||||
ld64
|
||||
ld64.mold
|
||||
gentoo
|
||||
out
|
||||
/mold-*.tar.gz
|
||||
/build*
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(mold VERSION 1.4.2)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# Add -fuse-ld=mold if accepted by the compiler.
|
||||
option(MOLD_USE_MOLD "Use mold to build mold" ON)
|
||||
if(MOLD_USE_MOLD)
|
||||
include(CheckLinkerFlag)
|
||||
check_linker_flag(CXX -fuse-ld=mold CXX_SUPPORTS_FUSE_MOLD)
|
||||
if(CXX_SUPPORTS_FUSE_MOLD)
|
||||
add_link_options(-fuse-ld=mold)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_executable(mold)
|
||||
target_compile_features(mold PRIVATE cxx_std_20)
|
||||
target_link_libraries(mold PRIVATE ${CMAKE_DL_LIBS})
|
||||
|
||||
if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
|
||||
target_compile_options(mold PRIVATE
|
||||
-fno-exceptions
|
||||
-fno-unwind-tables
|
||||
-fno-asynchronous-unwind-tables)
|
||||
endif()
|
||||
|
||||
# Build mold with -flto if MOLD_LTO=On
|
||||
option(MOLD_LTO "Build mold with link-time optimization enabled")
|
||||
if(MOLD_LTO)
|
||||
set_property(TARGET mold PROPERTY INTERPROCEDURAL_OPTIMIZATION ON)
|
||||
endif()
|
||||
|
||||
# Handle MOLD_USE_ASAN and MOLD_USE_TSAN
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
# Static link libstdc++ and libcrypto if MOLD_MOSTLY_STATIC=On
|
||||
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and libcrypto" OFF)
|
||||
if(MOLD_MOSTLY_STATIC)
|
||||
target_link_options(mold PRIVATE -static-libstdc++)
|
||||
target_link_libraries(mold PRIVATE libcrypto.a)
|
||||
endif()
|
||||
|
||||
# Setup zlib
|
||||
find_package(ZLIB QUIET)
|
||||
if(ZLIB_FOUND)
|
||||
target_link_libraries(mold PRIVATE ZLIB::ZLIB)
|
||||
else()
|
||||
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()
|
||||
|
||||
# Setup mimalloc
|
||||
cmake_dependent_option(MOLD_USE_MIMALLOC "Use mimalloc" ON
|
||||
"NOT APPLE;NOT ANDROID" OFF)
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
|
||||
MOLD_USE_MIMALLOC OFF)
|
||||
|
||||
if(MOLD_USE_MIMALLOC)
|
||||
if(MOLD_USE_SYSTEM_MIMALLOC)
|
||||
find_package(mimalloc REQUIRED)
|
||||
target_link_libraries(mold PRIVATE mimalloc)
|
||||
target_compile_definitions(mold PRIVATE USE_SYSTEM_MIMALLOC)
|
||||
else()
|
||||
function(mold_add_mimalloc)
|
||||
set(MI_BUILD_STATIC ON)
|
||||
option(MI_BUILD_TESTS "Build test executables" OFF)
|
||||
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()
|
||||
|
||||
# Setup TBB
|
||||
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()
|
||||
|
||||
# MOLD_X86_64_ONLY is a developer-only option. You should not use it
|
||||
# for creating an executable for production use.
|
||||
option(MOLD_X86_64_ONLY "Developer-only option" OFF)
|
||||
if(MOLD_X86_64_ONLY)
|
||||
set(CMAKE_BUILD_TYPE "Debug")
|
||||
target_compile_options(mold PRIVATE -ffunction-sections -fdata-sections
|
||||
-DMOLD_DEBUG_X86_64_ONLY)
|
||||
target_link_options(mold PRIVATE -Wl,--gc-sections)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
if(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)
|
||||
|
||||
target_sources(mold PRIVATE
|
||||
elf/lto-win32.cc
|
||||
macho/lto-win32.cc)
|
||||
else()
|
||||
include(CheckLibraryExists)
|
||||
check_library_exists(m pow "" LIBM_FOUND)
|
||||
if(LIBM_FOUND)
|
||||
target_link_libraries(mold PRIVATE m)
|
||||
endif()
|
||||
|
||||
target_sources(mold PRIVATE
|
||||
elf/lto-unix.cc
|
||||
elf/subprocess.cc
|
||||
macho/lto-unix.cc)
|
||||
endif()
|
||||
|
||||
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 elf/mold-wrapper.c)
|
||||
endif()
|
||||
|
||||
if(NOT APPLE AND NOT WIN32 AND NOT MOLD_MOSTLY_STATIC)
|
||||
find_package(OpenSSL REQUIRED COMPONENTS Crypto)
|
||||
target_link_libraries(mold PRIVATE OpenSSL::Crypto)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(riscv64|armv)")
|
||||
target_link_libraries(mold PRIVATE atomic)
|
||||
endif()
|
||||
|
||||
set_property(SOURCE main.cc elf/lto-unix.cc macho/output-chunks.cc APPEND PROPERTY
|
||||
COMPILE_DEFINITIONS "MOLD_VERSION=\"${CMAKE_PROJECT_VERSION}\"")
|
||||
|
||||
add_custom_target(git_hash
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
|
||||
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/git-hash.cc
|
||||
-P ${CMAKE_SOURCE_DIR}/update-git-hash.cmake
|
||||
DEPENDS update-git-hash.cmake
|
||||
BYPRODUCTS git-hash.cc
|
||||
VERBATIM)
|
||||
|
||||
add_dependencies(mold git_hash)
|
||||
|
||||
target_sources(mold PRIVATE
|
||||
compress.cc
|
||||
demangle.cc
|
||||
elf/arch-arm32.cc
|
||||
elf/arch-arm64.cc
|
||||
elf/arch-i386.cc
|
||||
elf/arch-riscv.cc
|
||||
elf/arch-x86-64.cc
|
||||
elf/cmdline.cc
|
||||
elf/dwarf.cc
|
||||
elf/gc-sections.cc
|
||||
elf/icf.cc
|
||||
elf/input-files.cc
|
||||
elf/input-sections.cc
|
||||
elf/linker-script.cc
|
||||
elf/main.cc
|
||||
elf/mapfile.cc
|
||||
elf/output-chunks.cc
|
||||
elf/passes.cc
|
||||
elf/relocatable.cc
|
||||
elf/thunks.cc
|
||||
filepath.cc
|
||||
git-hash.cc
|
||||
glob.cc
|
||||
hyperloglog.cc
|
||||
macho/arch-arm64.cc
|
||||
macho/arch-x86-64.cc
|
||||
macho/cmdline.cc
|
||||
macho/dead-strip.cc
|
||||
macho/input-files.cc
|
||||
macho/input-sections.cc
|
||||
macho/main.cc
|
||||
macho/mapfile.cc
|
||||
macho/output-chunks.cc
|
||||
macho/tapi.cc
|
||||
macho/yaml.cc
|
||||
main.cc
|
||||
multi-glob.cc
|
||||
perf.cc
|
||||
strerror.cc
|
||||
tar.cc
|
||||
third-party/rust-demangle/rust-demangle.c
|
||||
uuid.cc
|
||||
)
|
||||
|
||||
include(CTest)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
# Create the ld and ld64 symlinks required for testing
|
||||
if(NOT WIN32)
|
||||
add_custom_command(
|
||||
TARGET mold POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld64
|
||||
BYPRODUCTS ld ld64
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
VERBATIM)
|
||||
endif()
|
||||
|
||||
if(${APPLE})
|
||||
add_subdirectory(test/macho)
|
||||
elseif(${UNIX})
|
||||
add_subdirectory(test/elf)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
install(TARGETS mold)
|
||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
install(FILES docs/mold.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)
|
||||
install(CODE "
|
||||
set(DEST \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}\")
|
||||
file(RELATIVE_PATH RELPATH
|
||||
/${CMAKE_INSTALL_LIBEXECDIR}/mold /${CMAKE_INSTALL_BINDIR}/mold)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||
\${DEST}/${CMAKE_INSTALL_LIBEXECDIR}/mold)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink \${RELPATH}
|
||||
\${DEST}/${CMAKE_INSTALL_LIBEXECDIR}/mold/ld)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink mold
|
||||
\${DEST}/${CMAKE_INSTALL_BINDIR}/ld.mold)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink mold
|
||||
\${DEST}/${CMAKE_INSTALL_BINDIR}/ld64.mold)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink mold.1
|
||||
\${DEST}/${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)")
|
||||
endif()
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "x64-Debug",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Debug",
|
||||
"inheritEnvironments": [ "clang_cl_x64" ],
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
+8
-4
@@ -1,9 +1,9 @@
|
||||
# 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`.
|
||||
Summary: When you send a patch that is longer than 10 lines, 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:
|
||||
|
||||
@@ -52,3 +52,7 @@ using your real name (sorry, no pseudonyms or anonymous contributions.)
|
||||
This will be done for you automatically if you use ``git commit -s``.
|
||||
Reverts should also include "Signed-off-by". ``git revert -s`` does that
|
||||
for you.
|
||||
|
||||
We assume that any contribution that's 10 lines or less does not meet
|
||||
the threshold of originality and therefore copyright does not apply.
|
||||
You don't need to add a "Signed-off-by" line to such small commits.
|
||||
+10
-11
@@ -4,19 +4,18 @@
|
||||
# $ docker push rui314/mold-builder:v1-$(uname -m)
|
||||
|
||||
FROM ubuntu:18.04
|
||||
ENV TZ=Europe/London
|
||||
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/* && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential wget \
|
||||
libstdc++-11-dev zlib1g-dev gcc-10 g++-10 python3 && \
|
||||
cd / && \
|
||||
wget -O- https://www.openssl.org/source/openssl-3.0.2.tar.gz | tar xzf - && \
|
||||
mv openssl-3.0.2 openssl && \
|
||||
wget -O- -q https://www.openssl.org/source/openssl-3.0.5.tar.gz | tar xzf - && \
|
||||
mv openssl-3.0.5 openssl && \
|
||||
cd openssl && \
|
||||
./Configure --prefix=/openssl && \
|
||||
make -j$(nproc)
|
||||
./Configure --prefix=/usr/local && \
|
||||
make -j$(nproc) && \
|
||||
make -j$(nproc) install && \
|
||||
wget -O- -q https://github.com/Kitware/CMake/releases/download/v3.24.0/cmake-3.24.0-linux-$(uname -m).tar.gz | tar -C /usr/local --strip-components=1 -xzf -
|
||||
@@ -4,9 +4,9 @@ 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 rui[email protected].
|
||||
contact rui[email protected].
|
||||
|
||||
|
||||
--
|
||||
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
@@ -669,3 +669,323 @@ specific requirements.
|
||||
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/>.
|
||||
|
||||
|
||||
|
||||
|
||||
MOLD SUBCOMPONENTS:
|
||||
|
||||
Mold includes a number of subcomponents with separate copyright notices
|
||||
and license terms. Your use of the source code for the these subcomponents
|
||||
is subject to the terms and conditions of the following licenses.
|
||||
|
||||
|
||||
For the third-party/mimalloc component:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2021 Microsoft Corporation, Daan Leijen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
For the third-party/rust-demangle component:
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
For the third-party/tbb component:
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
For the third-party/xxhash component:
|
||||
|
||||
xxHash Library
|
||||
Copyright (c) 2012-2020 Yann Collet
|
||||
All rights reserved.
|
||||
|
||||
BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
For the third-party/zlib component:
|
||||
|
||||
(C) 1995-2022 Jean-loup Gailly and Mark Adler
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
Jean-loup Gailly Mark Adler
|
||||
[email protected] [email protected]
|
||||
@@ -1,19 +1,25 @@
|
||||
# If you want to enable ASAN, run `make` with the following options:
|
||||
#
|
||||
# make CXXFLAGS='-fsanitize=address -g' LDFLAGS=-fsanitize=address USE_MIMALLOC=0
|
||||
|
||||
VERSION = 1.2.0
|
||||
VERSION = 1.4.2
|
||||
|
||||
PREFIX = /usr/local
|
||||
BINDIR = $(PREFIX)/bin
|
||||
LIBDIR = $(PREFIX)/lib
|
||||
LIBEXECDIR = $(PREFIX)/libexec
|
||||
MANDIR = $(PREFIX)/share/man
|
||||
DOCDIR = $(PREFIX)/share/doc
|
||||
|
||||
INSTALL = install
|
||||
INSTALL_PROGRAM = $(INSTALL)
|
||||
INSTALL_DATA = $(INSTALL) -m 644
|
||||
|
||||
OS := $(shell uname -s)
|
||||
ARCH := $(shell uname -m)
|
||||
|
||||
ifeq ($(OS), Darwin)
|
||||
TESTS := $(wildcard test/macho/*.sh)
|
||||
else
|
||||
TESTS := $(wildcard test/elf/*.sh)
|
||||
endif
|
||||
|
||||
D = $(DESTDIR)
|
||||
|
||||
# CXX defaults to `g++`. Rewrite it with a vendor-neutral compiler
|
||||
@@ -22,18 +28,12 @@ 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)
|
||||
OBJS = $(SRCS:%.cc=out/%.o) out/rust-demangle.o out/git-hash.o
|
||||
|
||||
IS_ANDROID = 0
|
||||
ifneq ($(findstring -android,$(shell $(CC) -dumpmachine)),)
|
||||
@@ -46,14 +46,13 @@ CFLAGS = -O2
|
||||
CXXFLAGS = -O2
|
||||
|
||||
MOLD_CXXFLAGS := -std=c++20 -fno-exceptions -fno-unwind-tables \
|
||||
-fno-asynchronous-unwind-tables -Ithird-party/xxhash \
|
||||
-fno-asynchronous-unwind-tables \
|
||||
-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)\"
|
||||
ifeq ($(OS), OpenBSD)
|
||||
MOLD_LDFLAGS := -pthread -lz -lm
|
||||
else
|
||||
MOLD_LDFLAGS := -pthread -lz -lm -ldl
|
||||
endif
|
||||
|
||||
LTO = 0
|
||||
@@ -82,6 +81,10 @@ ifeq ($(USE_MIMALLOC), 1)
|
||||
endif
|
||||
endif
|
||||
|
||||
# Note: Do NOT specify `SYSTEM_TBB=1` unless your system-wide OneTBB
|
||||
# library is compiled with https://github.com/oneapi-src/oneTBB/pull/824.
|
||||
# mold with an unpatched OneTBB is unstable when doing LTO if the system
|
||||
# is under high load.
|
||||
ifdef SYSTEM_TBB
|
||||
MOLD_LDFLAGS += -ltbb
|
||||
else
|
||||
@@ -97,20 +100,18 @@ ifeq ($(OS), Linux)
|
||||
endif
|
||||
endif
|
||||
|
||||
NEEDS_LIBCRYPTO = 0
|
||||
ifneq ($(OS), Darwin)
|
||||
NEEDS_LIBCRYPTO = 1
|
||||
NEEDS_LIBCRYPTO = 1
|
||||
ifeq ($(OS), Darwin)
|
||||
NEEDS_LIBCRYPTO = 0
|
||||
endif
|
||||
|
||||
ifeq ($(NEEDS_LIBCRYPTO), 1)
|
||||
MOLD_CXXFLAGS += $(shell $(PKG_CONFIG) --cflags-only-I openssl)
|
||||
MOLD_LDFLAGS += $(shell $(PKG_CONFIG) --libs-only-L openssl) -lcrypto
|
||||
MOLD_LDFLAGS += -lcrypto
|
||||
endif
|
||||
|
||||
# '-latomic' flag is needed building on riscv64 system
|
||||
# RV32 system doesn't tested yet
|
||||
# seems like '-atomic' would be better but not working.
|
||||
ifeq ($(ARCH), riscv64)
|
||||
# '-latomic' flag is needed building on armv6/riscv64 systems.
|
||||
# Seems like '-atomic' would be better but not working.
|
||||
ifneq (,$(filter armv6% riscv64, $(ARCH)))
|
||||
MOLD_LDFLAGS += -latomic
|
||||
endif
|
||||
|
||||
@@ -129,14 +130,25 @@ all: mold mold-wrapper.so
|
||||
|
||||
-include $(SRCS:%.cc=out/%.d)
|
||||
|
||||
out/git-hash.cc: FORCE
|
||||
cmake -DSOURCE_DIR=. -DOUTPUT_FILE=out/git-hash.cc -P update-git-hash.cmake
|
||||
|
||||
FORCE:
|
||||
|
||||
out/git-hash.o: out/git-hash.cc
|
||||
$(CXX) $(MOLD_CXXFLAGS) $(CXXFLAGS) -c -o $@ $<
|
||||
|
||||
mold: $(OBJS) $(MIMALLOC_LIB) $(TBB_LIB)
|
||||
$(CXX) $(OBJS) -o $@ $(MOLD_LDFLAGS) $(LDFLAGS)
|
||||
ln -sf mold ld
|
||||
ln -sf mold ld64.mold
|
||||
ln -sf mold ld64
|
||||
|
||||
mold-wrapper.so: elf/mold-wrapper.c
|
||||
$(CC) $(DEPFLAGS) $(CFLAGS) -fPIC -shared -o $@ $< $(MOLD_WRAPPER_LDFLAGS) $(LDFLAGS)
|
||||
|
||||
out/rust-demangle.o: third-party/rust-demangle/rust-demangle.c
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
out/%.o: %.cc out/elf/.keep out/macho/.keep
|
||||
$(CXX) $(MOLD_CXXFLAGS) $(DEPFLAGS) $(CXXFLAGS) -c -o $@ $<
|
||||
|
||||
@@ -151,49 +163,53 @@ $(MIMALLOC_LIB):
|
||||
|
||||
$(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)
|
||||
(cd out/tbb; cmake -G'Unix Makefiles' -DCMAKE_BUILD_TYPE=RelWithDebInfo -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
|
||||
@$(MAKE) $(TESTS) --no-print-directory
|
||||
else
|
||||
$(MAKE) -C test -f Makefile.linux --no-print-directory --output-sync
|
||||
@$(MAKE) $(TESTS) --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-arch:
|
||||
TEST_CC=${TRIPLE}-gcc \
|
||||
TEST_CXX=${TRIPLE}-g++ \
|
||||
TEST_GCC=${TRIPLE}-gcc \
|
||||
TEST_GXX=${TRIPLE}-g++ \
|
||||
OBJDUMP=${TRIPLE}-objdump \
|
||||
MACHINE=${MACHINE} \
|
||||
QEMU="qemu-${MACHINE} -L /usr/${TRIPLE}" \
|
||||
$(MAKE) test
|
||||
|
||||
test-i386: all
|
||||
@echo i386
|
||||
CC=i686-linux-gnu-gcc CXX=i686-linux-gnu-g++ GCC=i686-linux-gnu-gcc GXX=i686-linux-gnu-g++ OBJDUMP=x86_64-linux-gnu-objdump MACHINE=i386 QEMU='qemu-i386 -L /usr/i686-linux-gnu' $(MAKE) test
|
||||
test-all: all
|
||||
$(MAKE) test-arch TRIPLE=x86_64-linux-gnu MACHINE=x86_64
|
||||
$(MAKE) test-arch TRIPLE=i686-linux-gnu MACHINE=i386
|
||||
$(MAKE) test-arch TRIPLE=aarch64-linux-gnu MACHINE=aarch64
|
||||
$(MAKE) test-arch TRIPLE=arm-linux-gnueabihf MACHINE=arm
|
||||
$(MAKE) test-arch TRIPLE=riscv64-linux-gnu MACHINE=riscv64
|
||||
|
||||
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
|
||||
# macOS's GNU make hasn't been updated since 3.8.1 perhaps due a concern
|
||||
# of GPLv3. The --output-sync flag was introduced in GNU Make 4.0, so we
|
||||
# can't use that flag on macOS.
|
||||
#
|
||||
# `tail -r | tail -r` is a poor-man's way to enable full buffering on a
|
||||
# command output. `tail -r` outputs an input from the last line to the
|
||||
# first.
|
||||
$(TESTS):
|
||||
ifeq ($(OS), Darwin)
|
||||
@set -o pipefail; ./$@ 2>&1 | tail -r | tail -r
|
||||
else
|
||||
@./$@
|
||||
endif
|
||||
|
||||
install: all
|
||||
$(INSTALL) -d $D$(BINDIR)
|
||||
@@ -201,14 +217,21 @@ install: all
|
||||
$(STRIP) $D$(BINDIR)/mold
|
||||
|
||||
$(INSTALL) -d $D$(LIBDIR)/mold
|
||||
|
||||
ifneq ($(OS), Darwin)
|
||||
$(INSTALL_DATA) mold-wrapper.so $D$(LIBDIR)/mold
|
||||
$(STRIP) $D$(LIBDIR)/mold/mold-wrapper.so
|
||||
endif
|
||||
|
||||
$(INSTALL) -d $D$(LIBEXECDIR)/mold
|
||||
ln -sf $(BINDIR)/mold $D$(LIBEXECDIR)/mold/ld
|
||||
cmake -DSOURCE=$D$(BINDIR)/mold -DDEST=$D$(LIBEXECDIR)/mold/ld -P create-symlink.cmake
|
||||
|
||||
$(INSTALL) -d $D$(MANDIR)/man1
|
||||
$(INSTALL_DATA) docs/mold.1 $D$(MANDIR)/man1
|
||||
ln -sf mold.1 $D$(MANDIR)/man1/ld.mold.1
|
||||
|
||||
$(INSTALL) -d $D$(DOCDIR)/mold
|
||||
$(INSTALL_DATA) LICENSE $D$(DOCDIR)/mold/
|
||||
|
||||
ln -sf mold $D$(BINDIR)/ld.mold
|
||||
ln -sf mold $D$(BINDIR)/ld64.mold
|
||||
@@ -218,7 +241,13 @@ uninstall:
|
||||
rm -f $D$(MANDIR)/man1/mold.1
|
||||
rm -rf $D$(LIBDIR)/mold
|
||||
|
||||
clean:
|
||||
rm -rf *~ mold mold-wrapper.so out ld ld64.mold mold-*-linux.tar.gz
|
||||
test-asan test-ubsan:
|
||||
$(MAKE) USE_MIMALLOC=0 CXXFLAGS='-fsanitize=address -fsanitize=undefined -O0 -g' LDFLAGS='-fsanitize=address -fsanitize=undefined' test
|
||||
|
||||
.PHONY: all test tests check clean test-x86-64 test-i386 test-arm64 test-arm32 test-riscv64 test-all
|
||||
test-tsan:
|
||||
$(MAKE) USE_MIMALLOC=0 CXXFLAGS='-fsanitize=thread -O0 -g' LDFLAGS=-fsanitize=thread test
|
||||
|
||||
clean:
|
||||
rm -rf *~ mold mold-wrapper.so out ld ld64 mold-*-linux.tar.gz
|
||||
|
||||
.PHONY: all test tests check clean test-arch test-all test-asan test-ubsan test-tsan $(TESTS)
|
||||
@@ -27,8 +27,8 @@ mold currently supports x86-64, i386, ARM32, ARM64 and 64-bit RISC-V.
|
||||
## Why does the speed of linking matter?
|
||||
|
||||
If you are using a compiled language such as C, C++ or Rust, a build
|
||||
consists of two phases. In the first phase, a compiler compiles a
|
||||
source file into an object file (`.o` files). In the second phase,
|
||||
consists of two phases. In the first phase, a compiler compiles
|
||||
source files into object files (`.o` files). In the second phase,
|
||||
a linker takes all object files to combine them into a single executable
|
||||
or a shared library file.
|
||||
|
||||
@@ -39,7 +39,7 @@ most noticeable when you are in rapid debug-edit-rebuild cycles.
|
||||
|
||||
## Install
|
||||
|
||||
Binary packages for the following distros are currently available.
|
||||
Binary packages for the following systems are currently available.
|
||||
|
||||
[](https://repology.org/project/mold/versions)
|
||||
|
||||
@@ -47,32 +47,21 @@ Binary packages for the following distros are currently available.
|
||||
|
||||
mold is written in C++20, so if you build mold yourself, you need a
|
||||
recent version of a C++ compiler and a C++ standard library. GCC 10.2
|
||||
or Clang 12.0.0 as well as libstdc++ 10 or libc++ 7 are recommended.
|
||||
|
||||
I'm using Ubuntu 20.04 as a development platform. In that environment,
|
||||
you can build mold by the following commands.
|
||||
or Clang 12.0.0 (or later) as well as libstdc++ 10 or libc++ 7 (or
|
||||
later) are recommended.
|
||||
|
||||
### Install dependencies
|
||||
|
||||
#### Ubuntu 20.04 and later / Debian 11 and later
|
||||
|
||||
```shell
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential git clang cmake libstdc++-10-dev libssl-dev libxxhash-dev zlib1g-dev pkg-config
|
||||
```
|
||||
|
||||
#### Fedora 34 and later
|
||||
|
||||
```shell
|
||||
sudo dnf install -y git clang cmake openssl-devel xxhash-devel zlib-devel libstdc++-devel
|
||||
```
|
||||
To install build dependencies, run `./install-build-deps.sh` in this
|
||||
directory. It recognizes your Linux distribution and tries to install
|
||||
necessary packages. You may want to run it as root.
|
||||
|
||||
### Compile mold
|
||||
|
||||
```shell
|
||||
git clone https://github.com/rui314/mold.git
|
||||
cd mold
|
||||
git checkout v1.2.0
|
||||
git checkout v1.4.2
|
||||
make -j$(nproc) CXX=clang++
|
||||
sudo make install
|
||||
```
|
||||
@@ -86,19 +75,10 @@ By default, `mold` is installed to `/usr/local/bin`.
|
||||
If you don't use a recent enough Linux distribution, or if for any reason
|
||||
`make` in the above commands doesn't work for you, you can use Docker to
|
||||
build it in a Docker environment. To do so, just run `./dist.sh` in this
|
||||
directory instead of running `make -j$(nproc)`. The shell script creates a
|
||||
Ubuntu 18.04 Docker image, installs necessary tools and libraries to it,
|
||||
builds mold and auxiliary files, and packs them into a single tar file
|
||||
`mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere
|
||||
and use `mold` executable in it.
|
||||
|
||||
`make test` depends on a few more packages. To install, run the following commands:
|
||||
|
||||
```shell
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt update
|
||||
sudo apt-get install bsdmainutils dwarfdump libc6-dev:i386 lib32gcc-10-dev libstdc++-10-dev-arm64-cross gcc-10-aarch64-linux-gnu g++-10-aarch64-linux-gnu
|
||||
```
|
||||
directory instead of running `make -j$(nproc)`. The shell script pulls a
|
||||
Docker image, builds mold and auxiliary files inside it, and packs
|
||||
them into a single tar file `mold-$version-$arch-linux.tar.gz`.
|
||||
You can extract the tar file anywhere and use `mold` executable in it.
|
||||
|
||||
## How to use
|
||||
|
||||
@@ -115,7 +95,7 @@ following flags to use `mold` instead of `/usr/bin/ld`:
|
||||
|
||||
- Clang: pass `-fuse-ld=mold`
|
||||
|
||||
- GCC 12.1.0 (upcoming version) or later: pass `-fuse-ld=mold`
|
||||
- GCC 12.1.0 or later: pass `-fuse-ld=mold`
|
||||
|
||||
- GCC before 12.1.0: `-fuse-ld` does not accept `mold` as a valid
|
||||
argument, so you need to use `-B` option instead. `-B` is an option
|
||||
@@ -123,7 +103,7 @@ following flags to use `mold` instead of `/usr/bin/ld`:
|
||||
|
||||
If you have installed mold with `make install`, there should be a
|
||||
directory named `/usr/libexec/mold` (or `/usr/local/libexec/mold`,
|
||||
depending on your $PREFIX), and `ld` command should be there. The
|
||||
depending on your `$PREFIX`), and `ld` command should be there. The
|
||||
`ld` is actually a symlink to `mold`. So, all you need is to pass
|
||||
`-B/usr/libexec/mold` (or `-B/usr/local/libexec/mold`) to GCC.
|
||||
|
||||
@@ -137,7 +117,7 @@ take an absolute path as an argument for `-fuse-ld` though.
|
||||
|
||||
Create `.cargo/config.toml` in your project directory with the following:
|
||||
|
||||
```
|
||||
```toml
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
|
||||
@@ -149,6 +129,19 @@ Please make sure you have installed `clang`.
|
||||
If you want to use mold for all projects, put the above snippet to
|
||||
`~/.cargo/config.toml`.
|
||||
|
||||
If you are using macOS, you can modify `config.toml` in a similar manner.
|
||||
Here is an example with `mold` installed via [Homebrew](https://brew.sh).
|
||||
|
||||
```toml
|
||||
[target.x86_64-apple-darwin]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
|
||||
[target.aarch64-apple-darwin]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>mold -run</summary>
|
||||
@@ -170,12 +163,28 @@ replace `argv[0]` with `mold` if it is `ld`, `ld.gold` or `ld.lld`.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>GitHub Action</summary>
|
||||
If you want to use mold in your GitHub-hosted CI to speed up continuous
|
||||
build, you can use <a href=https://github.com/rui314/setup-mold>setup-mold</a>
|
||||
GitHub Action. GitHub runs a CI on a two-core machine, but mold is
|
||||
still significantly faster than the default GNU linker there
|
||||
especially when a program being linked is large.
|
||||
<details><summary>On macOS</summary>
|
||||
|
||||
mold/macOS is available as an alpha version. It can be used to build not
|
||||
only macOS apps but also iOS apps because their binary formats are the same.
|
||||
|
||||
The command name of mold/macOS is `ld64.mold`. If you build mold on macOS,
|
||||
it still produces `mold` and `ld.mold`, but these executables are useful
|
||||
only for cross compilation (i.e. building Linux apps on macOS.)
|
||||
|
||||
If you find any issue with mold/macOS, please file it to
|
||||
<a href=https://github.com/rui314/mold/issues>our GitHub Issues</a>.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>GitHub Actions</summary>
|
||||
|
||||
You can use our <a href=https://github.com/rui314/setup-mold>setup-mold</a>
|
||||
GitHub Action to speed up GitHub-hosted continuous build. GitHub Actions
|
||||
runs on a two-core machine, but mold is still significantly faster than
|
||||
the default GNU linker there especially when a program being linked is
|
||||
large.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Verify that you are using mold</summary>
|
||||
@@ -184,7 +193,7 @@ mold leaves its identification string in `.comment` section in an output
|
||||
file. You can print it out to verify that you are actually using mold.
|
||||
|
||||
```shell
|
||||
readelf -p .comment <executable-file>
|
||||
$ readelf -p .comment <executable-file>
|
||||
|
||||
String dump of section '.comment':
|
||||
[ 0] GCC: (Ubuntu 10.2.0-5ubuntu1~20.04) 10.2.0
|
||||
@@ -195,6 +204,15 @@ If `mold` is in `.comment`, the file is created by mold.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Online manual</summary>
|
||||
|
||||
Since mold is a drop-in replacement, you should be able to use it
|
||||
without reading its manual. But just in case you need it, it's available
|
||||
online at <a href=https://rui314.github.io/mold.html>here</a>.
|
||||
You can also read the same manual by `man mold`.
|
||||
|
||||
</details>
|
||||
|
||||
## Why is mold so fast?
|
||||
|
||||
One reason is because it simply uses faster algorithms and efficient
|
||||
@@ -214,6 +232,34 @@ artificially capped to 16 so that the bars fit in the GIF.
|
||||
|
||||
For details, please read [design notes](docs/design.md).
|
||||
|
||||
# Logo
|
||||
## License
|
||||
|
||||
-
|
||||
mold is available under AGPL. Note that that does not mean that you
|
||||
have to license your program under AGPL if you use mold to link your
|
||||
program. An output of the mold linker is a derived work of the object
|
||||
files and libraries you pass to the linker but not a derived work of
|
||||
the mold linker itself.
|
||||
|
||||
Besides that, you can also buy a commercial, non-AGPL license with
|
||||
technical support from our company, Blue Whale Systems PTE LTD. If you
|
||||
are a big company, please consider obtaining it before making hundreds
|
||||
or thousands of developers of your company to depend on mold. mold is
|
||||
mostly a single-person open-source project, and just like other
|
||||
open-source projects, we are not legally obligated to keep maintaining
|
||||
it. A legally-binding commercial license contract addresses the
|
||||
concern. By purchasing a license, you are guaranteed that mold will be
|
||||
maintained for you. Please [contact us](mailto:[email protected])
|
||||
for a commercial license inquiry.
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
We accept donations via [GitHub Sponsors](https://github.com/sponsors/rui314)
|
||||
and [OpenCollective](https://opencollective.com/mold-linker).
|
||||
We thank you to everybody who sponsors our project. In particular,
|
||||
we'd like to acknowledge the following people and organizations who
|
||||
have sponsored $128/month or more:
|
||||
|
||||
- [300baud](https://github.com/300baud)
|
||||
- [Mercury](https://github.com/MercuryTechnologies)
|
||||
- [Wei Wu](https://github.com/lazyparser)
|
||||
- [Signal Slot Inc.](https://github.com/signal-slot)
|
||||
@@ -1,3 +1,28 @@
|
||||
// This file contains functions to read an archive file (.a file).
|
||||
// An archive file is just a bundle of object files. It's similar to
|
||||
// tar or zip, but the contents are not compressed.
|
||||
//
|
||||
// An archive file is either "regular" or "thin". A regular archive
|
||||
// contains object files directly, while a thin archive contains only
|
||||
// pathnames. In the latter case, actual file contents have to be read
|
||||
// from given pathnames. A regular archive is sometimes called "fat"
|
||||
// archive as opposed to "thin".
|
||||
//
|
||||
// If an archive file is given to the linker, the linker pulls out
|
||||
// object files that are needed to resolve undefined symbols. So,
|
||||
// bunding object files as an archive and giving that archive to the
|
||||
// linker has a different meaning than directly giving the same set of
|
||||
// object files to the linker. The former links only needed object
|
||||
// files, while the latter links all the given object files.
|
||||
//
|
||||
// Therefore, if you link libc.a for example, not all the libc
|
||||
// functions are linked to your binary. Instead, only object files
|
||||
// that provides functions and variables used in your program get
|
||||
// linked. To make this efficient, static library functions are
|
||||
// usually separated to each object file in an archive file. You can
|
||||
// see the contents of libc.a by runnning `ar t
|
||||
// /usr/lib/x86_64-linux-gnu/libc.a`.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
@@ -93,6 +118,7 @@ read_thin_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
std::string path = name.starts_with('/') ?
|
||||
name : (filepath(mf->name).parent_path() / name).string();
|
||||
vec.push_back(MappedFile<C>::must_open(ctx, path));
|
||||
vec.back()->thin_parent = mf;
|
||||
data = body;
|
||||
}
|
||||
return vec;
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef __BIG_ENDIAN__
|
||||
#error "mold does not support big-endian hosts"
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename T>
|
||||
class BigEndian {
|
||||
public:
|
||||
BigEndian() = delete;
|
||||
|
||||
operator T() const {
|
||||
T x;
|
||||
memcpy(&x, val, sizeof(T));
|
||||
return bswap(x);
|
||||
}
|
||||
|
||||
BigEndian &operator=(T x) {
|
||||
x = bswap(x);
|
||||
memcpy(&val, &x, sizeof(T));
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigEndian &operator++() {
|
||||
return *this = *this + 1;
|
||||
}
|
||||
|
||||
BigEndian operator++(int) {
|
||||
T ret = *this;
|
||||
*this = *this + 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator--() {
|
||||
return *this = *this - 1;
|
||||
}
|
||||
|
||||
BigEndian operator--(int) {
|
||||
T ret = *this;
|
||||
*this = *this - 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator+=(T x) {
|
||||
return *this = *this + x;
|
||||
}
|
||||
|
||||
BigEndian &operator&=(T x) {
|
||||
return *this = *this & x;
|
||||
}
|
||||
|
||||
BigEndian &operator|=(T x) {
|
||||
return *this = *this | x;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t val[sizeof(T)];
|
||||
|
||||
static T bswap(T x) {
|
||||
if constexpr (sizeof(T) == 2)
|
||||
return __builtin_bswap16(x);
|
||||
else if constexpr (sizeof(T) == 4)
|
||||
return __builtin_bswap32(x);
|
||||
else
|
||||
return __builtin_bswap64(x);
|
||||
}
|
||||
};
|
||||
|
||||
using ibig16 = BigEndian<int16_t>;
|
||||
using ibig32 = BigEndian<int32_t>;
|
||||
using ibig64 = BigEndian<int64_t>;
|
||||
using ubig16 = BigEndian<uint16_t>;
|
||||
using ubig32 = BigEndian<uint32_t>;
|
||||
using ubig64 = BigEndian<uint64_t>;
|
||||
|
||||
} // namespace mold
|
||||
@@ -48,6 +48,7 @@ read_response_file(C &ctx, std::string_view path) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Replace "@path/to/some/text/file" with its file contents.
|
||||
template <typename C>
|
||||
std::vector<std::string_view> expand_response_files(C &ctx, char **argv) {
|
||||
std::vector<std::string_view> vec;
|
||||
@@ -60,4 +61,16 @@ std::vector<std::string_view> expand_response_files(C &ctx, char **argv) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
static inline std::string_view string_trim(std::string_view str) {
|
||||
size_t pos = str.find_first_not_of(" \t");
|
||||
if (pos == str.npos)
|
||||
return "";
|
||||
str = str.substr(pos);
|
||||
|
||||
pos = str.find_last_not_of(" \t");
|
||||
if (pos == str.npos)
|
||||
return str;
|
||||
return str.substr(0, pos + 1);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+6
-5
@@ -89,7 +89,8 @@ static std::vector<u8> do_compress(std::string_view input) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
ZlibCompressor::ZlibCompressor(std::string_view input) {
|
||||
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
|
||||
std::string_view input{(char *)buf, (size_t)size};
|
||||
std::vector<std::string_view> inputs = split(input);
|
||||
std::vector<u64> adlers(inputs.size());
|
||||
shards.resize(inputs.size());
|
||||
@@ -134,7 +135,7 @@ void ZlibCompressor::write_to(u8 *buf) {
|
||||
end[-5] = 0;
|
||||
|
||||
// Write a checksum
|
||||
*(ubig32 *)(end - 4) = checksum;
|
||||
*(ub32 *)(end - 4) = checksum;
|
||||
}
|
||||
|
||||
GzipCompressor::GzipCompressor(std::string_view input) {
|
||||
@@ -183,10 +184,10 @@ void GzipCompressor::write_to(u8 *buf) {
|
||||
|
||||
// Write a trailer
|
||||
u8 *end = buf + size();
|
||||
end[-10] = 0x3; // two-byte zlib stream terminator
|
||||
end[-10] = 3; // two-byte zlib stream terminator
|
||||
end[-9] = 0;
|
||||
*(u32 *)(end - 8) = checksum;
|
||||
*(u32 *)(end - 4) = uncompressed_size;
|
||||
*(ul32 *)(end - 8) = checksum;
|
||||
*(ul32 *)(end - 4) = uncompressed_size;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,7 @@
|
||||
# Create a symbolic link with a relative path. GNU ln takes the --relative
|
||||
# option to do that, but that's not portable, so we do it in CMake.
|
||||
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
get_filename_component(DIR "${DEST}" DIRECTORY)
|
||||
file(RELATIVE_PATH PATH "/${DIR}" "/${SOURCE}")
|
||||
file(CREATE_LINK "${PATH}" "${DEST}" SYMBOLIC)
|
||||
+31
-4
@@ -1,15 +1,41 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
#include "third-party/rust-demangle/rust-demangle.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;
|
||||
static thread_local char *p;
|
||||
if (p)
|
||||
free(p);
|
||||
|
||||
// Try to demangle as a Rust symbol. Since legacy-style Rust symbols
|
||||
// are also valid as a C++ mangled name, we need to call this before
|
||||
// cpp_demangle.
|
||||
p = rust_demangle(std::string(name).c_str(), 0);
|
||||
if (p)
|
||||
return p;
|
||||
|
||||
// Try to demangle as a C++ symbol.
|
||||
if (std::optional<std::string_view> s = cpp_demangle(name))
|
||||
return *s;
|
||||
return name;
|
||||
}
|
||||
|
||||
std::optional<std::string_view> cpp_demangle(std::string_view name) {
|
||||
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) {
|
||||
@@ -17,8 +43,9 @@ std::string_view demangle(std::string_view name) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return name;
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -9,27 +9,27 @@
|
||||
|
||||
set -e
|
||||
|
||||
version=$(grep '^VERSION =' $(dirname $0)/Makefile | sed 's/.* = //')
|
||||
# Unlike Linux, macOS's uname returns arm64 for aarch64.
|
||||
arch=$(uname -m)
|
||||
dest=mold-$version-$arch-linux
|
||||
[ $arch = arm64 ] && arch=aarch64
|
||||
|
||||
if [ $arch = x86_64 ]; then
|
||||
image=rui314/mold-builder:v1-x86_64
|
||||
elif [ $arch = aarch64 -o $arch = arm64 ]; then
|
||||
image=rui314/mold-builder:v1-aarch64
|
||||
else
|
||||
if [ $arch != x86_64 -a $arch != aarch64 ]; then
|
||||
echo "Error: no docker image for $arch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version=$(grep '^VERSION =' $(dirname $0)/Makefile | sed 's/.* = //')
|
||||
dest=mold-$version-$arch-linux
|
||||
image=rui314/mold-builder:v1-$arch
|
||||
|
||||
docker images -q $image 2> /dev/null || docker pull $image
|
||||
|
||||
docker run -it --rm -v "$(pwd):/mold:Z" -u "$(id -u):$(id -g)" $image \
|
||||
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 &&
|
||||
docker run -it --rm -v "$(pwd):/mold" -e "OWNER=$(id -u):$(id -g)" $image \
|
||||
bash -c "mkdir /tmp/build &&
|
||||
cd /tmp/build &&
|
||||
cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DMOLD_MOSTLY_STATIC=On -DCMAKE_BUILD_TYPE=Release /mold &&
|
||||
cmake --build . -j\$(nproc)
|
||||
cmake --install . --prefix $dest --strip
|
||||
tar czf /mold/$dest.tar.gz $dest &&
|
||||
cp mold /mold"
|
||||
cp mold mold-wrapper.so /mold &&
|
||||
chown \$OWNER /mold/mold /mold/mold-wrapper.so /mold/$dest.tar.gz"
|
||||
+105
-29
@@ -352,7 +352,7 @@ or
|
||||
.Pf Sy lib Ar libname Ns Sy \.a
|
||||
from library search paths.
|
||||
.Pp
|
||||
.It Fl m Op Sy elf_x86_64 | elf_i386 | aarch64linux
|
||||
.It Fl m Op Ar target
|
||||
Choose a target.
|
||||
.Pp
|
||||
.It Fl o Ar file , Fl -output Ns = Ns Ar file
|
||||
@@ -473,6 +473,20 @@ Set
|
||||
.Ar dir
|
||||
to root directory.
|
||||
.Pp
|
||||
.It Fl -color-diagnostics Ns = Ns Op Sy auto | always | never
|
||||
.It Fl -color-diagnostics
|
||||
.It Fl -no-color-diagnostics
|
||||
.Pp
|
||||
Show diagnostics messages in color using ANSI escape sequences.
|
||||
.Ar auto
|
||||
means that
|
||||
.Nm
|
||||
prints out messages in color only if the standard output is connected to a TTY. \
|
||||
Default is
|
||||
.Ar auto .
|
||||
.Pp
|
||||
.It Fl -defsym Ns = Ns Ar symbol Ns = Ns Ar value
|
||||
.Pp
|
||||
.It Fl -compress-debug-sections Ns = Ns Op Sy none | zlib | zlib-gabi | zlib-gnu
|
||||
Compress DWARF debug info
|
||||
.Pf ( Sy .debug_*
|
||||
@@ -515,6 +529,10 @@ options.
|
||||
.It Fl -dynamic-list Ns = Ns Ar file
|
||||
Read a list of dynamic symbols from
|
||||
.Ar file .
|
||||
Same as
|
||||
.Fl -export-dynamic-symbol-list ,
|
||||
except that it implies
|
||||
.Fl -Bsymbolic .
|
||||
.Pp
|
||||
.It Fl -eh-frame-hdr
|
||||
.It Fl -no-eh-frame-hdr
|
||||
@@ -555,6 +573,24 @@ Mark all symbols in the given
|
||||
.Ar libraries
|
||||
hidden.
|
||||
.Pp
|
||||
.It Fl -export-dynamic-symbol Ns = Ns Ar sym
|
||||
Put symbols matching
|
||||
.Ar sym
|
||||
in the dynamic symbol table.
|
||||
.Ar sym
|
||||
may be a glob, with the same syntax as the globs used in
|
||||
.Fl -export-dynamic-symbol-list
|
||||
or
|
||||
.Fl -version-script .
|
||||
.Pp
|
||||
.It Fl -export-dynamic-symbol-list Ns = Ns Ar file
|
||||
Read a list of dynamic symbols from
|
||||
.Ar file .
|
||||
.Pp
|
||||
.It Fl -fatal-warnings
|
||||
.It Fl -no-fatal-warnings
|
||||
Treat warnings as errors.
|
||||
.Pp
|
||||
.It Fl -fini Ns = Ns Ar symbol
|
||||
Call
|
||||
.Ar symbol
|
||||
@@ -574,12 +610,67 @@ hides that latency.
|
||||
.It Fl -no-gc-sections
|
||||
Remove unreferenced sections.
|
||||
.Pp
|
||||
.It Fl -gdb-index
|
||||
Create a
|
||||
.Li .gdb_index
|
||||
section to speed up GNU debugger. To use this, you need to compile source files \
|
||||
with the
|
||||
.Fl -ggnu-pubnames
|
||||
compiler flag.
|
||||
.Pp
|
||||
.It Fl -hash-style Ns = Ns Op Sy sysv | gnu | both
|
||||
Set hash style.
|
||||
.Pp
|
||||
.It Fl -icf Ns = Ns Sy all
|
||||
.It Fl -icf Ns = Ns Op Sy none | safe | all
|
||||
.It Fl -no-icf
|
||||
Fold identical code.
|
||||
It is not uncommon for a program to contain many identical functions that differ \
|
||||
only in name. For example, a C++ template
|
||||
.Sy std::vector
|
||||
is very likely to be instantiated to the identical code for
|
||||
.Sy std::vector<int>
|
||||
and
|
||||
.Sy std::vector<unsigned>
|
||||
because the container cares only about the size of the parameter type. \
|
||||
Identical Code Folding (ICF) is a size optimization to identify and merge \
|
||||
such identical functions.
|
||||
.Pp
|
||||
If
|
||||
.Fl -icf=all
|
||||
is given,
|
||||
.Nm
|
||||
tries to merge all identical functions. This reduces the size of the output \
|
||||
most, but it is not
|
||||
.Dq safe
|
||||
optimization. It is guaranteed in C and C++ that two pointeres pointing two \
|
||||
different functions will never be equal, but
|
||||
.Fl -icf=all
|
||||
breaks that assumption as two functions have the same address after merging. \
|
||||
So a care must be taken when you use that flag that your program does not \
|
||||
depend on the function pointer uniqueness.
|
||||
.Pp
|
||||
.Fl -icf=safe
|
||||
is a flag to merge functions only when it is safe to do so. That is, if a \
|
||||
program does not take an address of a function, it is safe to merge that \
|
||||
function with other function, as you cannot compare a function pointer \
|
||||
with something else without taking an address of a function.
|
||||
.FL -icf=safe
|
||||
needs to be used with a compiler that supports
|
||||
.Sy .llvm_addrsig
|
||||
section which contains the information as to what symbols are address-taken. \
|
||||
LLVM/Clang supports that section by default. Since GCC does not support it \
|
||||
yet, you cannot use
|
||||
.Fl -icf=safe
|
||||
with GCC (it doesn't do any harm but can't optimize at all.)
|
||||
.Pp
|
||||
.Fl -icf=none
|
||||
and
|
||||
.Fl -no-icf
|
||||
disables ICF.
|
||||
.Pp
|
||||
.It Fl -ignore-data-address-equality
|
||||
Make ICF to merge not only functions but also data. This option should be \
|
||||
used in combination with
|
||||
.Fl -icf=all .
|
||||
.Pp
|
||||
.It Fl -image-base Ns = Ns Ar addr
|
||||
Set the base address to
|
||||
@@ -620,6 +711,14 @@ to run executables or shared libraries linked with
|
||||
.Fl -pack-dyn-relocs=relr ,
|
||||
and only ChromeOS, Android and Fuchsia support it as of now in 2022.
|
||||
.Pp
|
||||
.It Fl -package-metadata Ns = Ns Ar string
|
||||
Embed
|
||||
.Ar string
|
||||
to a .note.package section. This option in intended to be used by a \
|
||||
package management command such as
|
||||
.Cm rpm
|
||||
to embed metadata regarding a package to each executable file.
|
||||
.Pp
|
||||
.It Fl -perf
|
||||
Print performance statistics.
|
||||
.Pp
|
||||
@@ -898,23 +997,7 @@ section to indicate that the output uses IBT-enabled PLT. This option implies
|
||||
.Fl z Cm ibtplt .
|
||||
.Pp
|
||||
.It Fl z Cm ibtplt
|
||||
Generate Intel Branch Tracking (IBT)-enabled PLT.
|
||||
.Pp
|
||||
IBT is part of Intel Control-flow Enforcement Technology (CET).
|
||||
IBT is a new x86 feature available since Tiger Lake which is released in 2020.
|
||||
If IBT is enabled, all indirect branch instructions have to branch to a \
|
||||
so-called "landing pad" instruction. Landing pad itself is a no-op, but \
|
||||
it works as a marker that branching to that instruction is expected.
|
||||
If there's no landing pad after branch, the CPU raises an exception.
|
||||
This mechanism makes ROP attacks difficult.
|
||||
.Pp
|
||||
Since PLT can be used as an indirect branch target, we need a different \
|
||||
instruction sequence for IBT-enabled PLT. If
|
||||
.Sy -z Cm ibtplt
|
||||
is specified,
|
||||
.Nm
|
||||
generates PLT entries that start with a landing pad. The size of IBT-enabled \
|
||||
PLT is 24 bytes as opposed to 16 bytes regular PLT.
|
||||
Generate Intel Branch Tracking (IBT)-enabled PLT which is the default on x86-64.
|
||||
.Pp
|
||||
.It Fl z Cm execstack
|
||||
.It Fl z Cm noexecstack
|
||||
@@ -948,15 +1031,13 @@ As a security mitigation,
|
||||
it is preferred to make such segments read-only during program execution.
|
||||
.Pp
|
||||
.Fl z Cm relro
|
||||
puts such sections into a special section called
|
||||
puts such sections into a special segment called
|
||||
.Dv relro .
|
||||
The dynamic linker make a relro segment read-only after it finishes its job.
|
||||
.Pp
|
||||
By default,
|
||||
.Nm
|
||||
generates a
|
||||
.Sy relro
|
||||
segment.
|
||||
generates a relro segment.
|
||||
.Fl z Cm norelro
|
||||
disables the feature.
|
||||
.Pp
|
||||
@@ -1044,20 +1125,15 @@ Mark object to interpose all DSOs but executable.
|
||||
.It Fl EL
|
||||
.It Fl O Ns Ar number
|
||||
.It Fl -allow-shlib-undefined
|
||||
.It Fl -color-diagnostics
|
||||
.It Fl -dc
|
||||
.It Fl -dp
|
||||
.It Fl -end-group
|
||||
.It Fl -fatal-warnings
|
||||
.It Fl -gdb-index
|
||||
.It Fl -no-add-needed
|
||||
.It Fl -no-allow-shlib-undefined
|
||||
.It Fl -no-copy-dt-needed-entries
|
||||
.It Fl -no-fatal-warnings
|
||||
.It Fl -no-undefined-version
|
||||
.It Fl -nostdlib
|
||||
.It Fl -plugin
|
||||
.It Fl -plugin-opt
|
||||
.It Fl -rpath-link Ns = Ns Ar dir
|
||||
.It Fl -sort-common
|
||||
.It Fl -sort-section
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 95 KiB |
+291
-235
@@ -12,18 +12,21 @@
|
||||
// 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.
|
||||
// linker even has to emit an interworking thunk code to switch between
|
||||
// them.
|
||||
//
|
||||
// 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.
|
||||
// it's ARM; otherwise, Thumb.
|
||||
//
|
||||
// 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).
|
||||
// 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.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -35,23 +38,10 @@ namespace mold::elf {
|
||||
|
||||
using E = ARM32;
|
||||
|
||||
static u32 bit(u32 val, i64 pos) {
|
||||
return (val >> pos) & 1;
|
||||
}
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u32 bits(u32 val, i64 hi, i64 lo) {
|
||||
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
static i32 sign_extend(u32 val, u32 size) {
|
||||
return (i64)(val << (31 - size)) >> (31 - size);
|
||||
};
|
||||
|
||||
static void write_mov_imm(u8 *loc, u32 val) {
|
||||
u32 imm12 = bits(val, 11, 0);
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
*(u32 *)loc = (*(u32 *)loc & 0xfff0f000) | (imm4 << 16) | imm12;
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xfff0f000) | (imm4 << 16) | imm12;
|
||||
}
|
||||
|
||||
static void write_thm_b_imm(u8 *loc, u32 val) {
|
||||
@@ -64,9 +54,9 @@ static void write_thm_b_imm(u8 *loc, u32 val) {
|
||||
u32 imm10 = bits(val, 21, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
*(u16 *)loc = (*(u16 *)loc & 0xf800) | (sign << 10) | imm10;
|
||||
*(u16 *)(loc + 2) =
|
||||
(*(u16 *)(loc + 2) & 0xd000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
ul16 *buf = (ul16 *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1000'0000'0000) | (sign << 10) | imm10;
|
||||
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
|
||||
static void write_thm_mov_imm(u8 *loc, u32 val) {
|
||||
@@ -75,9 +65,10 @@ static void write_thm_mov_imm(u8 *loc, u32 val) {
|
||||
u32 i = bit(val, 11);
|
||||
u32 imm3 = bits(val, 10, 8);
|
||||
u32 imm8 = bits(val, 7, 0);
|
||||
*(u16 *)loc = (*(u16 *)loc & 0b1111'1011'1111'0000) | (i << 10) | imm4;
|
||||
*(u16 *)(loc + 2) =
|
||||
((*(u16 *)(loc + 2)) & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
|
||||
|
||||
ul16 *buf = (ul16 *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1011'1111'0000) | (i << 10) | imm4;
|
||||
buf[1] = (buf[1] & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -85,15 +76,18 @@ void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
static const u32 plt0[] = {
|
||||
0xe52de004, // 1: push {lr}
|
||||
0xe59fe004, // ldr lr, [pc, #4]
|
||||
0xe08fe00e, // add lr, pc, lr
|
||||
0xe5bef008, // ldr pc, [lr, #8]!
|
||||
0x00000000, // .word .got.plt - 1b - 16
|
||||
0xe52de004, // push {lr}
|
||||
0xe59fe004, // ldr lr, 2f
|
||||
0xe08fe00e, // 1: add lr, pc, lr
|
||||
0xe5bef008, // ldr pc, [lr, #8]!
|
||||
0x00000000, // 2: .word .got.plt - 1b - 8
|
||||
0xe320f000, // nop
|
||||
0xe320f000, // nop
|
||||
0xe320f000, // nop
|
||||
};
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr - 16;
|
||||
*(ul32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr - 16;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
static const u32 plt[] = {
|
||||
@@ -105,7 +99,7 @@ void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
u8 *ent = buf + sizeof(plt0) + sym->get_plt_idx(ctx) * sizeof(plt);
|
||||
memcpy(ent, plt, sizeof(plt));
|
||||
*(u32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 12;
|
||||
*(ul32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 12;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,22 +117,28 @@ void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * sizeof(plt);
|
||||
memcpy(ent, plt, sizeof(plt));
|
||||
*(u32 *)(ent + 12) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 12;
|
||||
*(ul32 *)(ent + 12) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 12;
|
||||
}
|
||||
}
|
||||
|
||||
// ARM does not use .eh_frame for exception handling. Instead, it uses
|
||||
// .ARM.exidx and .ARM.extab. So this function is empty.
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {}
|
||||
|
||||
// ARM and Thumb branch instructions can jump within ±16 MiB.
|
||||
static bool is_jump_reachable(i64 val) {
|
||||
return sign_extend(val, 24) == val;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
i64 frag_idx = 0;
|
||||
i64 trampoline_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
@@ -146,7 +146,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_ARM_NONE)
|
||||
if (rel.r_type == R_ARM_NONE || rel.r_type == R_ARM_V4BX)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
@@ -160,84 +160,131 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
#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 G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
auto get_thumb_thunk_addr = [&] {
|
||||
RangeExtensionRef ref = extra.range_extn[i];
|
||||
assert(ref.thunk_idx != -1);
|
||||
u64 addr = output_section->thunks[ref.thunk_idx]->get_addr(ref.sym_idx);
|
||||
assert(is_jump_reachable(addr + A - P));
|
||||
return addr;
|
||||
};
|
||||
|
||||
auto get_arm_thunk_addr = [&] {
|
||||
return get_thumb_thunk_addr() + 4;
|
||||
};
|
||||
|
||||
auto get_trampoline_addr = [&](u64 addr) {
|
||||
for (; trampoline_idx < output_section->thunks.size(); trampoline_idx++) {
|
||||
RangeExtensionThunk<E> &thunk = *output_section->thunks[trampoline_idx];
|
||||
i64 disp = output_section->shdr.sh_addr + thunk.offset - addr;
|
||||
if (is_jump_reachable(disp))
|
||||
return disp;
|
||||
}
|
||||
unreachable();
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
apply_abs_dyn_rel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
continue;
|
||||
case R_ARM_REL32:
|
||||
*(u32 *)loc = S + A - P;
|
||||
*(ul32 *)loc = S + A - P;
|
||||
continue;
|
||||
case R_ARM_THM_CALL:
|
||||
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
|
||||
*(ul16 *)(loc + 2) |= 0x1000; // rewrite to BL
|
||||
continue;
|
||||
}
|
||||
|
||||
u64 val = S + A - P;
|
||||
if (is_jump_reachable(val)) {
|
||||
if (T) {
|
||||
write_thm_b_imm(loc, val);
|
||||
*(ul16 *)(loc + 2) |= 0x1000; // rewrite to BL
|
||||
} else {
|
||||
write_thm_b_imm(loc, align_to(val, 4));
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite to BLX
|
||||
}
|
||||
} else {
|
||||
write_thm_b_imm(loc, align_to(S + A - P, 4));
|
||||
*(u16 *)(loc + 2) &= ~(1 << 12); // rewrite with BLX
|
||||
write_thm_b_imm(loc, align_to(get_arm_thunk_addr() + A - P, 4));
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite to BLX
|
||||
}
|
||||
continue;
|
||||
}
|
||||
case R_ARM_BASE_PREL:
|
||||
*(u32 *)loc = GOT + A - P;
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
continue;
|
||||
case R_ARM_GOT_PREL:
|
||||
*(u32 *)loc = G + A - P;
|
||||
case R_ARM_TARGET2:
|
||||
*(ul32 *)loc = GOT + G + A - P;
|
||||
continue;
|
||||
case R_ARM_GOT_BREL:
|
||||
*(u32 *)loc = G + A;
|
||||
*(ul32 *)loc = G + A;
|
||||
continue;
|
||||
case R_ARM_TARGET2:
|
||||
*(u32 *)loc = GOT + G + A - P;
|
||||
continue;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24: {
|
||||
u32 val;
|
||||
case R_ARM_CALL: {
|
||||
// Just like THM_CALL, ARM_CALL relocation refers either BL or
|
||||
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
|
||||
bool is_bl = ((*(ul32 *)loc & 0xff00'0000) == 0xeb00'0000);
|
||||
bool is_blx = ((*(ul32 *)loc & 0xfe00'0000) == 0xfa00'0000);
|
||||
if (!is_bl && !is_blx)
|
||||
Fatal(ctx) << *this << ": R_ARM_CALL refers neither BL nor BLX";
|
||||
|
||||
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;
|
||||
*(ul32 *)loc = 0xeb00'0001;
|
||||
continue;
|
||||
}
|
||||
|
||||
*(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);
|
||||
u64 val = S + A - P;
|
||||
if (is_jump_reachable(val)) {
|
||||
if (T)
|
||||
*(ul32 *)loc = 0xfa00'0000 | (bit(val, 1) << 24) | bits(val, 25, 2);
|
||||
else
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(val, 25, 2);
|
||||
} 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);
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(get_arm_thunk_addr() + A - P, 25, 2);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
case R_ARM_JUMP24:
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | 1;
|
||||
} else {
|
||||
// Unlike BL and BLX, we can't rewrite B to BX because BX doesn't
|
||||
// takes an immediate; it takes only a register. So if mode switch
|
||||
// is required, we jump to a linker-synthesized thunk which constructs
|
||||
// a branch destination in a register and branches to that address.
|
||||
u64 val = S + A - P;
|
||||
if (!is_jump_reachable(val) || T)
|
||||
val = get_arm_thunk_addr() + A - P;
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
}
|
||||
continue;
|
||||
case R_ARM_THM_JUMP11:
|
||||
assert(T);
|
||||
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(S + A - P, 11, 1);
|
||||
continue;
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | 1;
|
||||
} else {
|
||||
// Just like R_ARM_JUMP24, we need to jump to a thunk if we need to
|
||||
// switch processor mode.
|
||||
u64 val = S + A - P;
|
||||
if (!is_jump_reachable(val) || !T)
|
||||
val = get_thumb_thunk_addr() + A - P;
|
||||
write_thm_b_imm(loc, val);
|
||||
}
|
||||
continue;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
@@ -249,11 +296,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
write_thm_mov_imm(loc, ((S + A) | T) - P);
|
||||
continue;
|
||||
case R_ARM_PREL31: {
|
||||
u32 val = S + A - P;
|
||||
*(u32 *)loc = (*(u32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
case R_ARM_PREL31:
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | ((S + A - P) & 0x7fff'ffff);
|
||||
continue;
|
||||
}
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
write_thm_mov_imm(loc, (S + A) | T);
|
||||
continue;
|
||||
@@ -270,34 +315,47 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_thm_mov_imm(loc, (S + A) >> 16);
|
||||
continue;
|
||||
case R_ARM_TLS_GD32:
|
||||
*(u32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
continue;
|
||||
case R_ARM_TLS_LDM32:
|
||||
*(u32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
continue;
|
||||
case R_ARM_TLS_LDO32:
|
||||
*(u32 *)loc = S + A - ctx.tls_begin;
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin;
|
||||
continue;
|
||||
case R_ARM_TLS_IE32:
|
||||
*(u32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
continue;
|
||||
case R_ARM_TLS_LE32:
|
||||
*(u32 *)loc = S + A - ctx.tls_begin + 8;
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin + E::tls_tp_offset;
|
||||
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;
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
*(ul32 *)loc = S - ctx.tls_begin + E::tls_tp_offset;
|
||||
} else if (A & 1) {
|
||||
// A is odd if the corresponding TLS_CALL is Thumb.
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 6;
|
||||
} else {
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 4;
|
||||
}
|
||||
continue;
|
||||
case R_ARM_TLS_CALL:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
// BL -> NOP
|
||||
*(ul32 *)loc = 0xe320'f000;
|
||||
} else {
|
||||
// BL <tls_trampoline>
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(get_trampoline_addr(P + 8), 25, 2);
|
||||
}
|
||||
continue;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
// BL -> NOP
|
||||
*(u32 *)loc = 0x8000f3af;
|
||||
*(ul32 *)loc = 0x8000'f3af;
|
||||
} 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
|
||||
u64 val = align_to(get_trampoline_addr(P + 4), 4);
|
||||
write_thm_b_imm(loc, val);
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
@@ -315,7 +373,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -326,7 +384,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -339,16 +397,19 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A;
|
||||
if (!frag) {
|
||||
if (std::optional<u64> val = get_tombstone(sym)) {
|
||||
*(ul32 *)loc = *val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A - ctx.tls_begin;
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
@@ -366,7 +427,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
@@ -377,70 +438,37 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
if (sym.get_type() == STT_GNU_IFUNC)
|
||||
sym.flags |= (NEEDS_GOT | NEEDS_PLT);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_MOVT_ABS:
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
case R_ARM_TARGET1: {
|
||||
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);
|
||||
case R_ARM_TARGET1:
|
||||
scan_abs_dyn_rel(ctx, sym, rel);
|
||||
break;
|
||||
}
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
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);
|
||||
case R_ARM_PREL31:
|
||||
scan_pcrel_rel(ctx, sym, rel);
|
||||
break;
|
||||
}
|
||||
case R_ARM_TLS_GD32:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
@@ -454,6 +482,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
if (!ctx.relax_tlsdesc || sym.is_imported)
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
case R_ARM_THM_JUMP11:
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
@@ -461,7 +491,9 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_TLS_CALL:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
case R_ARM_V4BX:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
@@ -469,55 +501,88 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
// For range extension thunks
|
||||
template <>
|
||||
bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
|
||||
InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
// We pessimistically assume that PLT entries are unreacahble.
|
||||
if (sym.has_plt(ctx))
|
||||
return false;
|
||||
|
||||
// We create thunks with a pessimistic assumption that all
|
||||
// out-of-section relocations would be out-of-range.
|
||||
InputSection<E> *isec2 = sym.get_input_section();
|
||||
if (!isec2 || isec.output_section != isec2->output_section)
|
||||
return false;
|
||||
|
||||
// If the target section is in the same output section but hasn't got
|
||||
// any address yet, that's unreacahble.
|
||||
if (isec2->offset == -1)
|
||||
return false;
|
||||
|
||||
// Thumb and ARM B instructions cannot be converted to BX, so we
|
||||
// always have to make them jump to a thunk to switch processor mode
|
||||
// even if their destinations are within their ranges.
|
||||
bool is_thumb = sym.get_addr(ctx) & 1;
|
||||
if ((rel.r_type == R_ARM_THM_JUMP24 && !is_thumb) ||
|
||||
(rel.r_type == R_ARM_JUMP24 && is_thumb))
|
||||
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 = isec.get_addend(rel);
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
return is_jump_reachable(S + A - P);
|
||||
}
|
||||
|
||||
void ThumbToArmSection::update_shdr(Context<E> &ctx) {
|
||||
this->shdr.sh_size = symbols.size() * ENTRY_SIZE;
|
||||
}
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
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[] = {
|
||||
// TLS trampoline code. ARM32's TLSDESC is designed so that this
|
||||
// common piece of code is factored out from object files to reduce
|
||||
// output size. Since no one provide, the linker has to synthesize it.
|
||||
static u32 hdr[] = {
|
||||
0xe08e0000, // add r0, lr, r0
|
||||
0xe5901004, // ldr r1, [r0, #4]
|
||||
0xe12fff11, // bx r1
|
||||
};
|
||||
memcpy(ctx.buf + this->shdr.sh_offset, insn, sizeof(insn));
|
||||
|
||||
// This is a range extension and mode switch thunk.
|
||||
// It has two entry points: +0 for Thumb and +4 for ARM.
|
||||
const u8 entry[] = {
|
||||
// .thumb
|
||||
0xfc, 0x46, // mov ip, pc
|
||||
0x60, 0x47, // bx ip # jumps to the following `ldr` insn
|
||||
// .arm
|
||||
0x04, 0xc0, 0x9f, 0xe5, // ldr ip, 2f
|
||||
0x0f, 0xc0, 0x8c, 0xe0, // 1: add ip, ip, pc
|
||||
0x1c, 0xff, 0x2f, 0xe1, // bx ip
|
||||
0x00, 0x00, 0x00, 0x00, // 2: .word sym - 1b
|
||||
};
|
||||
|
||||
static_assert(E::thunk_hdr_size == sizeof(hdr));
|
||||
static_assert(E::thunk_size == sizeof(entry));
|
||||
|
||||
memcpy(buf, hdr, sizeof(hdr));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *loc = buf + sizeof(hdr) + i * sizeof(entry);
|
||||
memcpy(loc, entry, sizeof(entry));
|
||||
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = output_section.shdr.sh_addr + offset + sizeof(hdr) + i * sizeof(entry);
|
||||
*(ul32 *)(loc + 16) = S - P - 16;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static OutputSection<E> *find_exidx_section(Context<E> &ctx) {
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
if (osec->shdr.sh_type == SHT_ARM_EXIDX)
|
||||
return osec.get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ARM executables use an .ARM.exidx section to look up an exception
|
||||
@@ -532,14 +597,7 @@ void TlsTrampolineSection::copy_buf(Context<E> &ctx) {
|
||||
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();
|
||||
OutputSection<E> *osec = find_exidx_section(ctx);
|
||||
if (!osec)
|
||||
return;
|
||||
|
||||
@@ -553,48 +611,46 @@ void sort_arm_exidx(Context<E> &ctx) {
|
||||
// 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.
|
||||
// CANTUNWIND is value 1. The most significant bit is set in (2) but
|
||||
// not in (3). So we can distinguished them just by looking at a value.
|
||||
const u32 EXIDX_CANTUNWIND = 1;
|
||||
|
||||
struct Entry {
|
||||
u32 addr;
|
||||
u32 val;
|
||||
ul32 addr;
|
||||
ul32 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);
|
||||
Entry *ent = (Entry *)(ctx.buf + osec->shdr.sh_offset);
|
||||
i64 num_entries = osec->shdr.sh_size / sizeof(Entry);
|
||||
|
||||
struct Entry2 {
|
||||
u32 addr;
|
||||
u32 val;
|
||||
u32 idx;
|
||||
// Entry's addresses are relative to themselves. In order to sort
|
||||
// records by addresses, we first translate them so that the addresses
|
||||
// are relative to the beginning of the section.
|
||||
auto is_relative = [](u32 val) {
|
||||
return val != EXIDX_CANTUNWIND && !(val & 0x8000'0000);
|
||||
};
|
||||
|
||||
// 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);
|
||||
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
|
||||
i64 offset = sizeof(Entry) * i;
|
||||
ent[i].addr = sign_extend(ent[i].addr, 30) + offset;
|
||||
if (is_relative(ent[i].val))
|
||||
ent[i].val = 0x7fff'ffff & (sign_extend(ent[i].val, 30) + offset);
|
||||
});
|
||||
|
||||
// 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);
|
||||
tbb::parallel_sort(ent, ent + num_entries, [](const Entry &a, const Entry &b) {
|
||||
return a.addr < b.addr;
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+103
-384
@@ -1,21 +1,21 @@
|
||||
#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;
|
||||
static void write_adrp(u8 *buf, u64 val) {
|
||||
u32 hi = bits(val, 32, 14);
|
||||
u32 lo = bits(val, 13, 12);
|
||||
u32 op = *(ul32 *)buf & 0b1001'1111'0000'0000'0000'0000'0001'1111;
|
||||
*(ul32 *)buf = (lo << 29) | (hi << 5) | op;
|
||||
}
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u64 bits(u64 val, u64 hi, u64 lo) {
|
||||
return (val >> lo) & (((u64)1 << (hi - lo + 1)) - 1);
|
||||
static void write_adr(u8 *buf, u64 val) {
|
||||
u32 hi = bits(val, 20, 2);
|
||||
u32 lo = bits(val, 1, 0);
|
||||
u32 op = *(ul32 *)buf & 0b1001'1111'0000'0000'0000'0000'0001'1111;
|
||||
*(ul32 *)buf = (lo << 29) | (hi << 5) | op;
|
||||
}
|
||||
|
||||
static u64 page(u64 val) {
|
||||
@@ -39,13 +39,13 @@ static void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
write_adr(buf + 4, bits(page(gotplt) - page(plt + 4), 32, 12));
|
||||
*(u32 *)(buf + 8) |= bits(gotplt, 11, 3) << 10;
|
||||
*(u32 *)(buf + 12) |= ((gotplt) & 0xfff) << 10;
|
||||
write_adrp(buf + 4, page(gotplt) - page(plt + 4));
|
||||
*(ul32 *)(buf + 8) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(buf + 12) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u8 *ent = buf + ctx.plt_hdr_size + sym.get_plt_idx(ctx) * ctx.plt_size;
|
||||
u8 *ent = buf + E::plt_hdr_size + sym.get_plt_idx(ctx) * E::plt_size;
|
||||
|
||||
static const u32 data[] = {
|
||||
0x90000010, // adrp x16, .got.plt[n]
|
||||
@@ -58,9 +58,9 @@ static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_adr(ent, bits(page(gotplt) - page(plt), 32, 12));
|
||||
*(u32 *)(ent + 4) |= bits(gotplt, 11, 3) << 10;
|
||||
*(u32 *)(ent + 8) |= (gotplt & 0xfff) << 10;
|
||||
write_adrp(ent, page(gotplt) - page(plt));
|
||||
*(ul32 *)(ent + 4) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(ent + 8) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -89,25 +89,25 @@ void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u64 plt = sym->get_plt_addr(ctx);
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_adr(ent, bits(page(got) - page(plt), 32, 12));
|
||||
*(u32 *)(ent + 4) |= bits(got, 11, 3) << 10;
|
||||
write_adrp(ent, page(got) - page(plt));
|
||||
*(ul32 *)(ent + 4) |= bits(got, 11, 3) << 10;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
*(u64 *)loc = val;
|
||||
*(ul64 *)loc = val;
|
||||
return;
|
||||
case R_AARCH64_PREL32:
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
case R_AARCH64_PREL64:
|
||||
*(u64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
@@ -116,8 +116,7 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = get_range_extn();
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
i64 frag_idx = 0;
|
||||
|
||||
@@ -145,67 +144,58 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
};
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : rel.r_addend)
|
||||
#define A (frag_ref ? (u64)frag_ref->addend : (u64)rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#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;
|
||||
}
|
||||
apply_abs_dyn_rel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
continue;
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 1) << 10;
|
||||
*(ul32 *)loc |= bits(S + A, 11, 1) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 2) << 10;
|
||||
*(ul32 *)loc |= bits(S + A, 11, 2) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 3) << 10;
|
||||
*(ul32 *)loc |= bits(S + A, 11, 3) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 4) << 10;
|
||||
*(ul32 *)loc |= bits(S + A, 11, 4) << 10;
|
||||
continue;
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
*(u32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
*(u32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
*(u32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
*(u32 *)loc |= bits(S + A, 63, 48) << 5;
|
||||
*(ul32 *)loc |= bits(S + A, 63, 48) << 5;
|
||||
continue;
|
||||
case R_AARCH64_ADR_GOT_PAGE: {
|
||||
i64 val = page(G + GOT + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
write_adrp(loc, val);
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_PG_HI21: {
|
||||
i64 val = page(S + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
write_adrp(loc, val);
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_LO21: {
|
||||
@@ -219,7 +209,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(u32 *)loc |= 1;
|
||||
*(ul32 *)loc |= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -228,107 +218,107 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
i64 val = S + A - P;
|
||||
|
||||
if (val < lo || hi <= val) {
|
||||
RangeExtensionRef ref = range_extn[i];
|
||||
RangeExtensionRef ref = extra.range_extn[i];
|
||||
val = output_section->thunks[ref.thunk_idx]->get_addr(ref.sym_idx) + A - P;
|
||||
assert(lo <= val && val < hi);
|
||||
}
|
||||
|
||||
*(u32 *)loc |= (val >> 2) & 0x3ffffff;
|
||||
*(ul32 *)loc |= (val >> 2) & 0x3ffffff;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 20), (i64)1 << 20);
|
||||
*(u32 *)loc |= bits(val, 20, 2) << 5;
|
||||
*(ul32 *)loc |= bits(val, 20, 2) << 5;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_PREL16: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 15), (i64)1 << 15);
|
||||
*(u16 *)loc = val;
|
||||
*(ul16 *)loc = val;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_PREL32: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 32);
|
||||
*(u32 *)loc = val;
|
||||
*(ul32 *)loc = val;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_PREL64:
|
||||
*(u64 *)loc = S + A - P;
|
||||
*(ul64 *)loc = S + A - P;
|
||||
continue;
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
*(u32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
|
||||
*(ul32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15: {
|
||||
i64 val = G + GOT + A - page(GOT);
|
||||
overflow_check(val, 0, 1 << 15);
|
||||
*(u32 *)loc |= bits(val, 14, 3) << 10;
|
||||
*(ul32 *)loc |= bits(val, 14, 3) << 10;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: {
|
||||
i64 val = page(sym.get_gottp_addr(ctx) + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
write_adrp(loc, val);
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
*(u32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
continue;
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12: {
|
||||
i64 val = S + A - ctx.tls_begin + 16;
|
||||
i64 val = S + A - ctx.tls_begin + E::tls_tp_offset;
|
||||
overflow_check(val, 0, (i64)1 << 24);
|
||||
*(u32 *)loc |= bits(val, 23, 12) << 10;
|
||||
*(ul32 *)loc |= bits(val, 23, 12) << 10;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A - ctx.tls_begin + 16, 11, 0) << 10;
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tls_begin + E::tls_tp_offset, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21: {
|
||||
i64 val = page(sym.get_tlsgd_addr(ctx) + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
write_adrp(loc, val);
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
*(u32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
|
||||
*(ul32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21: {
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// adrp x0, 0 -> movz x0, #tls_ofset_hi, lsl #16
|
||||
i64 val = (S + A - ctx.tls_begin + 16);
|
||||
i64 val = (S + A - ctx.tls_begin + E::tls_tp_offset);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
*(u32 *)loc = 0xd2a00000 | (bits(val, 32, 16) << 5);
|
||||
*(ul32 *)loc = 0xd2a00000 | (bits(val, 32, 16) << 5);
|
||||
} else {
|
||||
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
write_adrp(loc, val);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
u32 offset_lo = (S + A - ctx.tls_begin + E::tls_tp_offset) & 0xffff;
|
||||
*(ul32 *)loc = 0xf2800000 | (offset_lo << 5);
|
||||
} else {
|
||||
*(u32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
|
||||
}
|
||||
continue;
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// add x0, x0, #0 -> nop
|
||||
*(u32 *)loc = 0xd503201f;
|
||||
*(ul32 *)loc = 0xd503201f;
|
||||
} else {
|
||||
*(u32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
|
||||
}
|
||||
continue;
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// blr x2 -> nop
|
||||
*(u32 *)loc = 0xd503201f;
|
||||
*(ul32 *)loc = 0xd503201f;
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
@@ -345,7 +335,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -356,7 +346,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -365,17 +355,20 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : rel.r_addend)
|
||||
#define A (frag ? (u64)addend : (u64)rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A;
|
||||
if (!frag) {
|
||||
if (std::optional<u64> val = get_tombstone(sym)) {
|
||||
*(ul64 *)loc = *val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*(ul64 *)loc = S + A;
|
||||
continue;
|
||||
case R_AARCH64_ABS32:
|
||||
*(u32 *)loc = S + A;
|
||||
*(ul32 *)loc = S + A;
|
||||
continue;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
@@ -393,7 +386,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
@@ -404,26 +397,17 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
if (sym.get_type() == STT_GNU_IFUNC)
|
||||
sym.flags |= (NEEDS_GOT | 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);
|
||||
case R_AARCH64_ABS64:
|
||||
scan_abs_dyn_rel(ctx, sym, rel);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ADR_GOT_PAGE:
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15:
|
||||
@@ -438,16 +422,9 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
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);
|
||||
case R_AARCH64_ADR_PREL_PG_HI21:
|
||||
scan_pcrel_rel(ctx, sym, rel);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
@@ -488,21 +465,21 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
// For range extension thunks
|
||||
template <>
|
||||
bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
|
||||
InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
// We pessimistically assume that PLT entries are unreacahble.
|
||||
if (sym.has_plt(ctx))
|
||||
return false;
|
||||
|
||||
static bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
|
||||
InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
// We always create a thunk for an absolute symbol conservatively
|
||||
// because `shrink_sections` may increase a distance between a
|
||||
// branch instruction and an absolute symbol. Branching to an
|
||||
// absolute location is extremely rare in real code, though.
|
||||
if (sym.is_absolute())
|
||||
// We create thunks with a pessimistic assumption that all
|
||||
// out-of-section relocations would be out-of-range.
|
||||
InputSection<E> *isec2 = sym.get_input_section();
|
||||
if (!isec2 || isec.output_section != isec2->output_section)
|
||||
return false;
|
||||
|
||||
if (isec2->offset == -1)
|
||||
return false;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol
|
||||
@@ -514,253 +491,7 @@ static bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
|
||||
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);
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
@@ -770,29 +501,17 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(ENTRY_SIZE == sizeof(data));
|
||||
static_assert(E::thunk_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;
|
||||
u64 P = output_section.shdr.sh_addr + offset + i * E::thunk_size;
|
||||
|
||||
u8 *loc = buf + i * ENTRY_SIZE;
|
||||
u8 *loc = buf + i * E::thunk_size;
|
||||
memcpy(loc , data, sizeof(data));
|
||||
write_adr(loc, bits(page(S) - page(P), 32, 12));
|
||||
*(u32 *)(loc + 4) |= bits(S, 11, 0) << 10;
|
||||
write_adrp(loc, page(S) - page(P));
|
||||
*(ul32 *)(loc + 4) |= bits(S, 11, 0) << 10;
|
||||
}
|
||||
}
|
||||
|
||||
void write_thunks(Context<E> &ctx) {
|
||||
Timer t(ctx, "write_thunks");
|
||||
|
||||
tbb::parallel_for_each(ctx.output_sections,
|
||||
[&](std::unique_ptr<OutputSection<E>> &osec) {
|
||||
tbb::parallel_for_each(osec->thunks,
|
||||
[&](std::unique_ptr<RangeExtensionThunk<E>> &thunk) {
|
||||
thunk->copy_buf(ctx);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+107
-174
@@ -4,6 +4,10 @@ namespace mold::elf {
|
||||
|
||||
using E = I386;
|
||||
|
||||
// Emitting position-independent code (PIC) for i386 is a bit tricky
|
||||
// because i386 doesn't support PC-relative memory access instructions.
|
||||
// By default, i386 executables are not PIC. If PIC, %ebx is used to
|
||||
// store the location of .got and access all data with offsets from .got.
|
||||
static void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 plt0[] = {
|
||||
@@ -12,8 +16,8 @@ static void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 8;
|
||||
*(ul32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
|
||||
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 8;
|
||||
} else {
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0x35, 0, 0, 0, 0, // pushl GOTPLT+4
|
||||
@@ -21,14 +25,14 @@ static void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr + 8;
|
||||
*(ul32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr + 8;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
u8 *ent = buf + E::plt_hdr_size + sym.get_plt_idx(ctx) * E::plt_size;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 data[] = {
|
||||
@@ -37,7 +41,7 @@ static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym,
|
||||
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
*(ul32 *)(ent + 2) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
@@ -45,11 +49,11 @@ static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym,
|
||||
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym.get_gotplt_addr(ctx);
|
||||
*(ul32 *)(ent + 2) = sym.get_gotplt_addr(ctx);
|
||||
}
|
||||
|
||||
*(u32 *)(ent + 7) = idx * sizeof(ElfRel<E>);
|
||||
*(u32 *)(ent + 12) = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 16;
|
||||
*(ul32 *)(ent + 7) = idx * sizeof(ElfRel<E>);
|
||||
*(ul32 *)(ent + 12) = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 16;
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -74,7 +78,7 @@ void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
*(ul32 *)(ent + 2) = symbols[i]->get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
}
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
@@ -85,13 +89,13 @@ void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx);
|
||||
*(ul32 *)(ent + 2) = symbols[i]->get_got_addr(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
@@ -99,10 +103,10 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
case R_386_NONE:
|
||||
return;
|
||||
case R_386_32:
|
||||
*(u32 *)loc = val;
|
||||
*(ul32 *)loc = val;
|
||||
return;
|
||||
case R_386_PC32:
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
unreachable();
|
||||
@@ -111,7 +115,7 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
@@ -137,90 +141,62 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
<< 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 G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
write8(S + A);
|
||||
case R_386_8: {
|
||||
i64 val = S + A;
|
||||
overflow_check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
continue;
|
||||
case R_386_16:
|
||||
write16(S + A);
|
||||
}
|
||||
case R_386_16: {
|
||||
i64 val = S + A;
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
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);
|
||||
}
|
||||
apply_abs_dyn_rel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
continue;
|
||||
case R_386_PC8:
|
||||
write8s(S + A);
|
||||
case R_386_PC8: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
continue;
|
||||
case R_386_PC16:
|
||||
write16s(S + A);
|
||||
};
|
||||
case R_386_PC16: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
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);
|
||||
*(ul32 *)loc = S + A - P;
|
||||
continue;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
write32(G + A);
|
||||
*(ul32 *)loc = G + A;
|
||||
continue;
|
||||
case R_386_GOTOFF:
|
||||
write32(S + A - GOT);
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
continue;
|
||||
case R_386_GOTPC:
|
||||
write32(GOT + A - P);
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
continue;
|
||||
case R_386_TLS_GOTIE:
|
||||
write32(sym.get_gottp_addr(ctx) + A - GOT);
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
continue;
|
||||
case R_386_TLS_LE:
|
||||
write32(S + A - ctx.tls_end);
|
||||
*(ul32 *)loc = S + A - ctx.tls_end;
|
||||
continue;
|
||||
case R_386_TLS_IE:
|
||||
write32(sym.get_gottp_addr(ctx) + A);
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A;
|
||||
continue;
|
||||
case R_386_TLS_GD:
|
||||
if (sym.get_tlsgd_idx(ctx) == -1) {
|
||||
@@ -232,7 +208,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
0x81, 0xe8, 0, 0, 0, 0, // add $0, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 5) = ctx.tls_end - S - A;
|
||||
*(ul32 *)(loc + 5) = ctx.tls_end - S - A;
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32: {
|
||||
@@ -241,7 +217,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
0x81, 0xe8, 0, 0, 0, 0, // add $0, %rax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 6) = ctx.tls_end - S - A;
|
||||
*(ul32 *)(loc + 6) = ctx.tls_end - S - A;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -250,7 +226,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
i++;
|
||||
} else {
|
||||
write32(sym.get_tlsgd_addr(ctx) + A - GOT);
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
}
|
||||
continue;
|
||||
case R_386_TLS_LDM:
|
||||
@@ -281,17 +257,17 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
i++;
|
||||
} else {
|
||||
write32(ctx.got->get_tlsld_addr(ctx) + A - GOT);
|
||||
*(ul32 *)loc = 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);
|
||||
*(ul32 *)loc = S + A - ctx.tls_end;
|
||||
else
|
||||
write32(S + A - ctx.tls_begin);
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin;
|
||||
continue;
|
||||
case R_386_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
continue;
|
||||
case R_386_TLS_GOTDESC:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
@@ -299,9 +275,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
0x8d, 0x05, 0, 0, 0, 0, // lea 0, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
write32(S + A - ctx.tls_end);
|
||||
*(ul32 *)loc = S + A - ctx.tls_end;
|
||||
} else {
|
||||
write32(sym.get_tlsdesc_addr(ctx) + A - GOT);
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
|
||||
}
|
||||
continue;
|
||||
case R_386_TLS_DESC_CALL:
|
||||
@@ -325,7 +301,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -336,7 +312,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -351,67 +327,62 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
<< 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 G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
write8(S + A);
|
||||
case R_386_8: {
|
||||
i64 val = S + A;
|
||||
overflow_check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
continue;
|
||||
case R_386_16:
|
||||
write16(S + A);
|
||||
}
|
||||
case R_386_16: {
|
||||
i64 val = S + A;
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
continue;
|
||||
}
|
||||
case R_386_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A;
|
||||
if (!frag) {
|
||||
if (std::optional<u64> val = get_tombstone(sym)) {
|
||||
*(ul32 *)loc = *val;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
*(ul32 *)loc = S + A;
|
||||
continue;
|
||||
case R_386_PC8:
|
||||
write8s(S + A);
|
||||
case R_386_PC8: {
|
||||
i64 val = S + A;
|
||||
overflow_check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
continue;
|
||||
case R_386_PC16:
|
||||
write16s(S + A);
|
||||
}
|
||||
case R_386_PC16: {
|
||||
i64 val = S + A;
|
||||
overflow_check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
continue;
|
||||
};
|
||||
case R_386_PC32:
|
||||
*(u32 *)loc = S + A;
|
||||
*(ul32 *)loc = S + A;
|
||||
continue;
|
||||
case R_386_GOTPC:
|
||||
*(u32 *)loc = GOT + A;
|
||||
*(ul32 *)loc = GOT + A;
|
||||
continue;
|
||||
case R_386_GOTOFF:
|
||||
*(u32 *)loc = S + A - GOT;
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
continue;
|
||||
case R_386_TLS_LDO_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A - ctx.tls_begin;
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin;
|
||||
continue;
|
||||
case R_386_SIZE32:
|
||||
*(u32 *)loc = sym.esym().st_size + A;
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
continue;
|
||||
default:
|
||||
unreachable();
|
||||
@@ -428,7 +399,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
@@ -439,73 +410,35 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
if (sym.get_type() == STT_GNU_IFUNC)
|
||||
sym.flags |= (NEEDS_GOT | 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);
|
||||
case R_386_16:
|
||||
scan_abs_rel(ctx, sym, rel);
|
||||
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);
|
||||
case R_386_32:
|
||||
scan_abs_dyn_rel(ctx, sym, rel);
|
||||
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);
|
||||
case R_386_PC16:
|
||||
case R_386_PC32:
|
||||
scan_pcrel_rel(ctx, sym, rel);
|
||||
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);
|
||||
case R_386_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
}
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_IE:
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+72
-189
@@ -4,38 +4,22 @@ 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).
|
||||
// This is a security-enhanced version of the regular PLT. The PLT
|
||||
// header and each PLT entry starts with endbr64 for the Intel's
|
||||
// control-flow enforcement security mechanism.
|
||||
//
|
||||
// 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.
|
||||
// Note that our IBT-enabled PLT 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 IBTPLT 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;
|
||||
// Our PLT entry clobbers %r11, but that's fine because the resolver
|
||||
// function (_dl_runtime_resolve) clobbers %r11 anyway.
|
||||
template <>
|
||||
void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
memset(buf, 0xcc, this->shdr.sh_size);
|
||||
|
||||
// Write PLT header
|
||||
static const u8 plt0[] = {
|
||||
@@ -43,94 +27,47 @@ static void write_ibtplt(Context<E> &ctx) {
|
||||
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;
|
||||
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr - 4;
|
||||
*(ul32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - this->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;
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
i64 idx = sym->get_plt_idx(ctx);
|
||||
u8 *ent = buf + E::plt_hdr_size + idx * E::plt_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 6) = relplt_idx++;
|
||||
*(u32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 16;
|
||||
*(ul32 *)(ent + 6) = idx;
|
||||
*(ul32 *)(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;
|
||||
memset(buf, 0xcc, this->shdr.sh_size);
|
||||
|
||||
static const u8 data[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
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;
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * E::pltgot_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
*(ul32 *)(ent + 6) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 10;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
@@ -138,16 +75,16 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
case R_X86_64_NONE:
|
||||
return;
|
||||
case R_X86_64_32:
|
||||
*(u32 *)loc = val;
|
||||
*(ul32 *)loc = val;
|
||||
return;
|
||||
case R_X86_64_64:
|
||||
*(u64 *)loc = val;
|
||||
*(ul64 *)loc = val;
|
||||
return;
|
||||
case R_X86_64_PC32:
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
case R_X86_64_PC64:
|
||||
*(u64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
unreachable();
|
||||
@@ -233,7 +170,7 @@ static u32 relax_gotpc32_tlsdesc(u8 *loc) {
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
@@ -271,30 +208,30 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(u16 *)loc = val;
|
||||
*(ul16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write16s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 15), 1 << 15);
|
||||
*(u16 *)loc = val;
|
||||
*(ul16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
overflow_check(val, 0, (i64)1 << 32);
|
||||
*(u32 *)loc = val;
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32s = [&](u64 val) {
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
|
||||
*(u32 *)loc = val;
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write64 = [&](u64 val) {
|
||||
*(u64 *)loc = val;
|
||||
*(ul64 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : rel.r_addend)
|
||||
#define A (frag_ref ? (u64)frag_ref->addend : (u64)rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr)
|
||||
#define GOT ctx.gotplt->shdr.sh_addr
|
||||
@@ -313,16 +250,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
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);
|
||||
}
|
||||
apply_abs_dyn_rel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
continue;
|
||||
case R_X86_64_PC8:
|
||||
write8s(S + A - P);
|
||||
@@ -334,12 +262,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
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);
|
||||
}
|
||||
write64(S + A - P);
|
||||
continue;
|
||||
case R_X86_64_PLT32:
|
||||
write32s(S + A - P);
|
||||
@@ -404,7 +327,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 8) = val;
|
||||
*(ul32 *)(loc + 8) = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
@@ -414,7 +337,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 9) = val;
|
||||
*(ul32 *)(loc + 9) = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -545,7 +468,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
// scan_relocations.
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -556,7 +479,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -578,21 +501,21 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(u16 *)loc = val;
|
||||
*(ul16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
overflow_check(val, 0, (i64)1 << 32);
|
||||
*(u32 *)loc = val;
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32s = [&](u64 val) {
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
|
||||
*(u32 *)loc = val;
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : rel.r_addend)
|
||||
#define A (frag ? (u64)addend : (u64)rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
@@ -608,28 +531,31 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
write32s(S + A);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A;
|
||||
if (!frag) {
|
||||
if (std::optional<u64> val = get_tombstone(sym)) {
|
||||
*(ul64 *)loc = *val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*(ul64 *)loc = S + A;
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
write32s(S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_X86_64_DTPOFF64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A - ctx.tls_begin;
|
||||
*(ul64 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
break;
|
||||
case R_X86_64_SIZE64:
|
||||
*(u64 *)loc = sym.esym().st_size + A;
|
||||
*(ul64 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
@@ -652,7 +578,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
@@ -664,66 +590,29 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
if (sym.get_type() == STT_GNU_IFUNC)
|
||||
sym.flags |= (NEEDS_GOT | 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);
|
||||
case R_X86_64_32S:
|
||||
scan_abs_rel(ctx, sym, rel);
|
||||
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);
|
||||
case R_X86_64_64:
|
||||
scan_abs_dyn_rel(ctx, sym, rel);
|
||||
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);
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_PC64:
|
||||
scan_pcrel_rel(ctx, sym, rel);
|
||||
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:
|
||||
@@ -753,16 +642,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
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);
|
||||
case R_X86_64_PLTOFF64:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_TLSGD: {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
+441
-464
File diff suppressed because it is too large.
Load diff
+561
@@ -0,0 +1,561 @@
|
||||
// This file contains code to read DWARF debug info to create .gdb_index.
|
||||
//
|
||||
// .gdb_index is an optional section to speed up GNU debugger. It contains
|
||||
// two maps: 1) a map from function/variable/type names to compunits, and
|
||||
// 2) a map from function address ranges to compunits. gdb uses these
|
||||
// maps to quickly find a compunit given a name or an instruction pointer.
|
||||
//
|
||||
// (Terminology: a compilation unit, which often abbreviated as compunit
|
||||
// or cu, is a unit of debug info. An input .debug_info section usually
|
||||
// contains one compunit, and thus an output .debug_info contains as
|
||||
// many compunits as the number of input files.)
|
||||
//
|
||||
// .gdb_index is not mandatory. All the information in .gdb_index is
|
||||
// also in other debug info sections. You can actually create an
|
||||
// executable without .gdb_index and later add it using `gdb-add-index`
|
||||
// post-processing tool that comes with gdb.
|
||||
//
|
||||
// The mapping from names to compunits is 1:n while the mapping from
|
||||
// address ranges to compunits is 1:1. That is, two object files may
|
||||
// define the same type name (with the same definition), while there
|
||||
// should be no two functions that overlap with each other in memory.
|
||||
//
|
||||
// .gdb_index contains an on-disk hash table for names, so gdb can
|
||||
// lookup names without loading all strings into memory and construct an
|
||||
// in-memory hash table.
|
||||
//
|
||||
// Names are in .debug_gnu_pubnames and .debug_gnu_pubtypes input
|
||||
// sections. These sections are created if `-ggnu-pubnames` is given.
|
||||
// Besides names, these sections contains attributes for each name so
|
||||
// that gdb can distinguish type names from function names, for example.
|
||||
//
|
||||
// A compunit contains one or more function address ranges. If an
|
||||
// object file is compiled without -ffunction-sections, it contains
|
||||
// only one .text section and therefore contains a single address range.
|
||||
// Such range is typically stored directly to the compunit.
|
||||
//
|
||||
// If an object file is compiled with -fucntion-sections, it contains
|
||||
// more than one .text section, and it has as many address ranges as
|
||||
// the number of .text sections. Such discontiguous address ranges are
|
||||
// stored to .debug_ranges in DWARF 2/3/4/5 and
|
||||
// .debug_rnglists/.debug_addr in DWARF 5.
|
||||
//
|
||||
// .debug_info section contains DWARF debug info. Although we don't need
|
||||
// to parse the whole .debug_info section to read address ranges, we
|
||||
// have to do a little bit. DWARF is complicated and often handled using
|
||||
// a library such as libdwarf. But we don't use any library because we
|
||||
// don't want to add an extra run-time dependency just for --gdb-index.
|
||||
//
|
||||
// This page explains the format of .gdb_index:
|
||||
// https://sourceware.org/gdb/onlinedocs/gdb/Index-Section-Format.html
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// The hash function for .gdb_index.
|
||||
static u32 gdb_hash(std::string_view name) {
|
||||
u32 h = 0;
|
||||
for (u8 c : name) {
|
||||
if ('A' <= c && c <= 'Z')
|
||||
c = 'a' + c - 'A';
|
||||
h = h * 67 + c - 113;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
// Split .debug_info into so-called "compilation units". A .debug_info
|
||||
// section usually contains one compunit unless it was created by `ld -r`.
|
||||
// This is for --gdb-index.
|
||||
template <typename E>
|
||||
std::vector<std::string_view>
|
||||
read_compunits(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
file.debug_info->uncompress(ctx);
|
||||
std::string_view data = file.debug_info->contents;
|
||||
std::vector<std::string_view> vec;
|
||||
|
||||
while (!data.empty()) {
|
||||
if (data.size() < 4)
|
||||
Fatal(ctx) << *file.debug_info << ": corrupted .debug_info";
|
||||
if (*(ul32 *)data.data() == 0xffffffff)
|
||||
Fatal(ctx) << *file.debug_info << ": --gdb-index: DWARF64 not supported";
|
||||
i64 len = *(ul32 *)data.data() + 4;
|
||||
vec.push_back(data.substr(0, len));
|
||||
data = data.substr(len);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Parses .debug_gnu_pubnames and .debug_gnu_pubtypes. These sections
|
||||
// start with a 14 bytes header followed by (4-byte offset, 1-byte type,
|
||||
// null-terminated string) tuples.
|
||||
//
|
||||
// The 4-byte offset is an offset into .debug_info that contains details
|
||||
// about the name. The 1-byte type is a type of the corresponding name
|
||||
// (e.g. function, variable or datatype). The string is a name of a
|
||||
// function, a variable or a type.
|
||||
template <typename E>
|
||||
std::vector<GdbIndexName> read_pubnames(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
std::vector<GdbIndexName> vec;
|
||||
|
||||
auto get_cu_idx = [&](InputSection<E> &isec, i64 offset) {
|
||||
i64 off = 0;
|
||||
for (i64 i = 0; i < file.compunits.size(); i++) {
|
||||
if (offset == off)
|
||||
return file.compunits_idx + i;
|
||||
off += file.compunits[i].size();
|
||||
}
|
||||
Fatal(ctx) << isec << ": corrupted debug_info_offset";
|
||||
};
|
||||
|
||||
auto read = [&](InputSection<E> &isec) {
|
||||
isec.uncompress(ctx);
|
||||
std::string_view contents = isec.contents;
|
||||
|
||||
while (!contents.empty()) {
|
||||
if (contents.size() < 14)
|
||||
Fatal(ctx) << isec << ": corrupted header";
|
||||
|
||||
u32 len = *(ul32 *)contents.data() + 4;
|
||||
u32 debug_info_offset = *(ul32 *)(contents.data() + 6);
|
||||
u32 cu_idx = get_cu_idx(isec, debug_info_offset);
|
||||
|
||||
std::string_view data = contents.substr(14, len - 14);
|
||||
contents = contents.substr(len);
|
||||
|
||||
while (!data.empty()) {
|
||||
u32 offset = *(ul32 *)data.data();
|
||||
data = data.substr(4);
|
||||
if (offset == 0)
|
||||
break;
|
||||
|
||||
u8 type = data[0];
|
||||
data = data.substr(1);
|
||||
|
||||
std::string_view name = data.data();
|
||||
data = data.substr(name.size() + 1);
|
||||
|
||||
vec.push_back({name, gdb_hash(name), (type << 24) | cu_idx});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (file.debug_pubnames)
|
||||
read(*file.debug_pubnames);
|
||||
if (file.debug_pubtypes)
|
||||
read(*file.debug_pubtypes);
|
||||
|
||||
// Uniquify elements because GCC 11 seems to emit one record for each
|
||||
// comdat group which results in having a lot of duplicate records.
|
||||
auto less = [](const GdbIndexName &a, const GdbIndexName &b) {
|
||||
return std::tuple{a.hash, a.attr, a.name} <
|
||||
std::tuple{b.hash, b.attr, b.name};
|
||||
};
|
||||
|
||||
auto equal = [](const GdbIndexName &a, const GdbIndexName &b) {
|
||||
return std::tuple{a.hash, a.attr, a.name} ==
|
||||
std::tuple{b.hash, b.attr, b.name};
|
||||
};
|
||||
|
||||
std::sort(vec.begin(), vec.end(), less);
|
||||
vec.erase(std::unique(vec.begin(), vec.end(), equal), vec.end());
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static u8 *get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
if (u8 *buf = chunk->get_uncompressed_data())
|
||||
return buf;
|
||||
return ctx.buf + chunk->shdr.sh_offset;
|
||||
}
|
||||
|
||||
// Try to find a compilation unit from .debug_info and its
|
||||
// corresponding record from .debug_abbrev and returns them.
|
||||
template <typename E>
|
||||
static std::tuple<u8 *, u8 *, u32>
|
||||
find_compunit(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
// Read .debug_info to find the record at a given offset.
|
||||
u8 *cu = get_buffer(ctx, ctx.debug_info) + offset;
|
||||
u32 dwarf_version = *(ul16 *)(cu + 4);
|
||||
u32 abbrev_offset;
|
||||
|
||||
// Skip a header.
|
||||
switch (dwarf_version) {
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
abbrev_offset = *(ul32 *)(cu + 6);
|
||||
if (u32 address_size = cu[10]; address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
|
||||
<< address_size;
|
||||
cu += 11;
|
||||
break;
|
||||
case 5: {
|
||||
abbrev_offset = *(ul32 *)(cu + 8);
|
||||
if (u32 address_size = cu[7]; address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
|
||||
<< address_size;
|
||||
|
||||
switch (u32 unit_type = cu[6]; unit_type) {
|
||||
case DW_UT_compile:
|
||||
case DW_UT_partial:
|
||||
cu += 12;
|
||||
break;
|
||||
case DW_UT_skeleton:
|
||||
case DW_UT_split_compile:
|
||||
cu += 20;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unknown DW_UT_* value: 0x"
|
||||
<< std::hex << unit_type;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unknown DWARF version: "
|
||||
<< dwarf_version;
|
||||
}
|
||||
|
||||
u32 abbrev_code = read_uleb(cu);
|
||||
|
||||
// Find a .debug_abbrev record corresponding to the .debug_info record.
|
||||
// We assume the .debug_info record at a given offset is of
|
||||
// DW_TAG_compile_unit which describes a compunit.
|
||||
u8 *abbrev = get_buffer(ctx, ctx.debug_abbrev) + abbrev_offset;
|
||||
|
||||
for (;;) {
|
||||
u32 code = read_uleb(abbrev);
|
||||
if (code == 0)
|
||||
Fatal(ctx) << file << ": --gdb-index: .debug_abbrev does not contain"
|
||||
<< " a record for the first .debug_info record";
|
||||
|
||||
if (code == abbrev_code) {
|
||||
// Found a record
|
||||
u64 abbrev_tag = read_uleb(abbrev);
|
||||
if (abbrev_tag != DW_TAG_compile_unit && abbrev_tag != DW_TAG_skeleton_unit)
|
||||
Fatal(ctx) << file << ": --gdb-index: the first entry's tag is not"
|
||||
<< " DW_TAG_compile_unit/DW_TAG_skeleton_unit but 0x"
|
||||
<< std::hex << abbrev_tag;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip an uninteresting record
|
||||
read_uleb(abbrev); // tag
|
||||
abbrev++; // has_children byte
|
||||
for (;;) {
|
||||
u64 name = read_uleb(abbrev);
|
||||
u64 form = read_uleb(abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
if (form == DW_FORM_implicit_const)
|
||||
read_uleb(abbrev);
|
||||
}
|
||||
}
|
||||
|
||||
abbrev++; // skip has_children byte
|
||||
return {cu, abbrev, dwarf_version};
|
||||
}
|
||||
|
||||
// Estimate the number of address ranges contained in a given file.
|
||||
// It may over-estimate but never under-estimate.
|
||||
template <typename E>
|
||||
i64 estimate_address_areas(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
// Each CU contains zero or one address area.
|
||||
i64 ret = file.compunits.size();
|
||||
|
||||
// In DWARF 4, a CU can refer address ranges in .debug_ranges.
|
||||
// .debug_ranges contains a vector of [begin, end) address pairs.
|
||||
// The last entry must be a null terminator, so we do -1.
|
||||
if (file.debug_ranges)
|
||||
ret += file.debug_ranges->sh_size / sizeof(Word<E>) / 2 - 1;
|
||||
|
||||
// In DWARF 5, a CU can refer address ranges in .debug_rnglists, which
|
||||
// contains variable-length entries. The smallest possible range entry
|
||||
// is one byte for the code and two ULEB128 values (each can be as
|
||||
// small as one byte), so 3 bytes.
|
||||
if (file.debug_rnglists)
|
||||
ret += file.debug_rnglists->sh_size / 3;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// .debug_info contains variable-length fields. This class reads them.
|
||||
template <typename E>
|
||||
class DebugInfoReader {
|
||||
public:
|
||||
DebugInfoReader(Context<E> &ctx, ObjectFile<E> &file, u8 *cu)
|
||||
: ctx(ctx), file(file), cu(cu) {}
|
||||
|
||||
u64 read(u64 form);
|
||||
|
||||
Context<E> &ctx;
|
||||
ObjectFile<E> &file;
|
||||
u8 *cu;
|
||||
};
|
||||
|
||||
// Read value of the given DW_FORM_* form. If a value is not scalar,
|
||||
// returns a dummy value 0.
|
||||
template <typename E>
|
||||
inline u64 DebugInfoReader<E>::read(u64 form) {
|
||||
switch (form) {
|
||||
case DW_FORM_flag_present:
|
||||
return 0;
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_flag:
|
||||
case DW_FORM_strx1:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_ref1:
|
||||
return *cu++;
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_strx2:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_ref2: {
|
||||
u64 val = *(ul16 *)cu;
|
||||
cu += 2;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx3:
|
||||
case DW_FORM_addrx3: {
|
||||
u64 val = *(ul24 *)cu;
|
||||
cu += 3;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_strp:
|
||||
case DW_FORM_sec_offset:
|
||||
case DW_FORM_line_strp:
|
||||
case DW_FORM_strx4:
|
||||
case DW_FORM_addrx4:
|
||||
case DW_FORM_ref4: {
|
||||
u64 val = *(ul32 *)cu;
|
||||
cu += 4;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data8:
|
||||
case DW_FORM_ref8: {
|
||||
u64 val = *(ul64 *)cu;
|
||||
cu += 8;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_addr:
|
||||
case DW_FORM_ref_addr: {
|
||||
u64 val = *(Word<E> *)cu;
|
||||
cu += sizeof(Word<E>);
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx:
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_ref_udata:
|
||||
case DW_FORM_loclistx:
|
||||
case DW_FORM_rnglistx:
|
||||
return read_uleb(cu);
|
||||
case DW_FORM_string:
|
||||
cu += strlen((char *)cu) + 1;
|
||||
return 0;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled debug info form: 0x"
|
||||
<< std::hex << form;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Read a range list from .debug_ranges starting at the given offset
|
||||
// (until an end of list entry).
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_debug_range(Context<E> &ctx, ObjectFile<E> &file, Word<E> *range) {
|
||||
std::vector<u64> vec;
|
||||
u64 base = 0;
|
||||
|
||||
for (i64 i = 0; range[i] || range[i + 1]; i += 2) {
|
||||
if (range[i] + 1 == 0) {
|
||||
// base address selection entry
|
||||
base = range[i + 1];
|
||||
} else {
|
||||
vec.push_back(range[i] + base);
|
||||
vec.push_back(range[i + 1] + base);
|
||||
}
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Read a range list from .debug_rnglists starting at the given offset
|
||||
// (until an end of list entry).
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_rnglist_range(Context<E> &ctx, ObjectFile<E> &file, u8 *rnglist,
|
||||
Word<E> *addrx) {
|
||||
std::vector<u64> vec;
|
||||
u64 base = 0;
|
||||
|
||||
for (;;) {
|
||||
switch (*rnglist++) {
|
||||
case DW_RLE_end_of_list:
|
||||
return vec;
|
||||
case DW_RLE_base_addressx:
|
||||
base = addrx[read_uleb(rnglist)];
|
||||
break;
|
||||
case DW_RLE_startx_endx:
|
||||
vec.push_back(addrx[read_uleb(rnglist)]);
|
||||
vec.push_back(addrx[read_uleb(rnglist)]);
|
||||
break;
|
||||
case DW_RLE_startx_length:
|
||||
vec.push_back(addrx[read_uleb(rnglist)]);
|
||||
vec.push_back(vec.back() + read_uleb(rnglist));
|
||||
break;
|
||||
case DW_RLE_offset_pair:
|
||||
vec.push_back(base + read_uleb(rnglist));
|
||||
vec.push_back(base + read_uleb(rnglist));
|
||||
break;
|
||||
case DW_RLE_base_address:
|
||||
base = *(Word<E> *)rnglist;
|
||||
rnglist += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_end:
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_length:
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
vec.push_back(vec.back() + read_uleb(rnglist));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a list of address ranges explained by a compunit at the
|
||||
// `offset` in an output .debug_info section.
|
||||
//
|
||||
// .debug_info contains DWARF debug info records, so this function
|
||||
// parses DWARF. If a designated compunit contains multiple ranges, the
|
||||
// ranges are read from .debug_ranges (or .debug_rnglists for DWARF5).
|
||||
// Otherwise, a range is read directly from .debug_info (or possibly
|
||||
// from .debug_addr for DWARF5).
|
||||
template <typename E>
|
||||
std::vector<u64>
|
||||
read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
u8 *cu;
|
||||
u8 *abbrev;
|
||||
u32 dwarf_version;
|
||||
std::tie(cu, abbrev, dwarf_version) = find_compunit(ctx, file, offset);
|
||||
|
||||
DebugInfoReader<E> reader{ctx, file, cu};
|
||||
|
||||
struct Record {
|
||||
u64 form = 0;
|
||||
u64 value = 0;
|
||||
};
|
||||
|
||||
Record low_pc;
|
||||
Record high_pc;
|
||||
Record ranges;
|
||||
std::optional<u64> rnglists_base;
|
||||
Word<E> *addrx = nullptr;
|
||||
|
||||
// Read all interesting debug records.
|
||||
for (;;) {
|
||||
u64 name = read_uleb(abbrev);
|
||||
u64 form = read_uleb(abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
|
||||
u64 val = reader.read(form);
|
||||
|
||||
switch (name) {
|
||||
case DW_AT_low_pc:
|
||||
low_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_high_pc:
|
||||
high_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_rnglists_base:
|
||||
rnglists_base = val;
|
||||
break;
|
||||
case DW_AT_addr_base:
|
||||
addrx = (Word<E> *)(get_buffer(ctx, ctx.debug_addr) + val);
|
||||
break;
|
||||
case DW_AT_ranges:
|
||||
ranges = {form, val};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-contiguous address ranges.
|
||||
if (ranges.form) {
|
||||
if (dwarf_version <= 4) {
|
||||
Word<E> *range_begin =
|
||||
(Word<E> *)(get_buffer(ctx, ctx.debug_ranges) + ranges.value);
|
||||
return read_debug_range(ctx, file, range_begin);
|
||||
}
|
||||
|
||||
assert(dwarf_version == 5);
|
||||
|
||||
u8 *buf = get_buffer(ctx, ctx.debug_rnglists);
|
||||
if (ranges.form == DW_FORM_sec_offset)
|
||||
return read_rnglist_range(ctx, file, buf + ranges.value, addrx);
|
||||
|
||||
if (!rnglists_base)
|
||||
Fatal(ctx) << file << ": --gdb-index: missing DW_AT_rnglists_base";
|
||||
|
||||
u8 *base = buf + *rnglists_base;
|
||||
return read_rnglist_range(ctx, file, base + *(ul32 *)base, addrx);
|
||||
}
|
||||
|
||||
// Handle a contiguous address range.
|
||||
if (low_pc.form && high_pc.form) {
|
||||
u64 lo;
|
||||
|
||||
switch (low_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
lo = low_pc.value;
|
||||
break;
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
lo = addrx[low_pc.value];
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled form for DW_AT_low_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
|
||||
switch (high_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
return {lo, high_pc.value};
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
return {lo, addrx[high_pc.value]};
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_data8:
|
||||
return {lo, lo + high_pc.value};
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled form for DW_AT_high_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template std::vector<std::string_view> \
|
||||
read_compunits(Context<E> &, ObjectFile<E> &); \
|
||||
template std::vector<GdbIndexName> \
|
||||
read_pubnames(Context<E> &, ObjectFile<E> &); \
|
||||
template i64 \
|
||||
estimate_address_areas(Context<E> &, ObjectFile<E> &); \
|
||||
template std::vector<u64> \
|
||||
read_address_areas(Context<E> &, ObjectFile<E> &, i64)
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../inttypes.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
@@ -21,6 +23,7 @@ struct I386;
|
||||
struct ARM64;
|
||||
struct ARM32;
|
||||
struct RISCV64;
|
||||
struct RISCV32;
|
||||
|
||||
template <typename E> struct ElfSym;
|
||||
template <typename E> struct ElfShdr;
|
||||
@@ -30,6 +33,21 @@ template <typename E> struct ElfRel;
|
||||
template <typename E> struct ElfDyn;
|
||||
template <typename E> struct ElfChdr;
|
||||
|
||||
enum class MachineType { NONE, X86_64, I386, ARM64, ARM32, RISCV64, RISCV32 };
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &out, MachineType mt) {
|
||||
switch (mt) {
|
||||
case MachineType::NONE: out << "none"; break;
|
||||
case MachineType::X86_64: out << "x86_64"; break;
|
||||
case MachineType::I386: out << "i386"; break;
|
||||
case MachineType::ARM64: out << "arm64"; break;
|
||||
case MachineType::ARM32: out << "arm32"; break;
|
||||
case MachineType::RISCV64: out << "riscv64"; break;
|
||||
case MachineType::RISCV32: out << "riscv32"; break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::string rel_to_string(u32 r_type);
|
||||
|
||||
@@ -62,6 +80,7 @@ static constexpr u32 SHT_PREINIT_ARRAY = 16;
|
||||
static constexpr u32 SHT_GROUP = 17;
|
||||
static constexpr u32 SHT_SYMTAB_SHNDX = 18;
|
||||
static constexpr u32 SHT_RELR = 19;
|
||||
static constexpr u32 SHT_LLVM_ADDRSIG = 0x6fff4c03;
|
||||
static constexpr u32 SHT_GNU_HASH = 0x6ffffff6;
|
||||
static constexpr u32 SHT_GNU_VERDEF = 0x6ffffffd;
|
||||
static constexpr u32 SHT_GNU_VERNEED = 0x6ffffffe;
|
||||
@@ -80,6 +99,7 @@ static constexpr u32 SHF_LINK_ORDER = 0x80;
|
||||
static constexpr u32 SHF_GROUP = 0x200;
|
||||
static constexpr u32 SHF_TLS = 0x400;
|
||||
static constexpr u32 SHF_COMPRESSED = 0x800;
|
||||
static constexpr u32 SHF_GNU_RETAIN = 0x200000;
|
||||
static constexpr u32 SHF_EXCLUDE = 0x80000000;
|
||||
|
||||
static constexpr u32 GRP_COMDAT = 1;
|
||||
@@ -143,6 +163,7 @@ static constexpr u32 ELFCLASS64 = 2;
|
||||
|
||||
static constexpr u32 EV_CURRENT = 1;
|
||||
|
||||
static constexpr u32 EM_NONE = 0;
|
||||
static constexpr u32 EM_386 = 3;
|
||||
static constexpr u32 EM_ARM = 40;
|
||||
static constexpr u32 EM_X86_64 = 62;
|
||||
@@ -189,6 +210,8 @@ static constexpr u32 DT_INIT_ARRAYSZ = 27;
|
||||
static constexpr u32 DT_FINI_ARRAYSZ = 28;
|
||||
static constexpr u32 DT_RUNPATH = 29;
|
||||
static constexpr u32 DT_FLAGS = 30;
|
||||
static constexpr u32 DT_PREINIT_ARRAY = 32;
|
||||
static constexpr u32 DT_PREINIT_ARRAYSZ = 33;
|
||||
static constexpr u32 DT_RELRSZ = 35;
|
||||
static constexpr u32 DT_RELR = 36;
|
||||
static constexpr u32 DT_RELRENT = 37;
|
||||
@@ -225,6 +248,7 @@ static constexpr u32 NT_GNU_HWCAP = 2;
|
||||
static constexpr u32 NT_GNU_BUILD_ID = 3;
|
||||
static constexpr u32 NT_GNU_GOLD_VERSION = 4;
|
||||
static constexpr u32 NT_GNU_PROPERTY_TYPE_0 = 5;
|
||||
static constexpr u32 NT_FDO_PACKAGING_METADATA = 0xcafe1a7e;
|
||||
|
||||
static constexpr u32 GNU_PROPERTY_AARCH64_FEATURE_1_AND = 0xc0000000;
|
||||
static constexpr u32 GNU_PROPERTY_X86_FEATURE_1_AND = 0xc0000002;
|
||||
@@ -960,8 +984,6 @@ static constexpr u32 R_RISCV_SUB8 = 37;
|
||||
static constexpr u32 R_RISCV_SUB16 = 38;
|
||||
static constexpr u32 R_RISCV_SUB32 = 39;
|
||||
static constexpr u32 R_RISCV_SUB64 = 40;
|
||||
static constexpr u32 R_RISCV_GNU_VTINHERIT = 41;
|
||||
static constexpr u32 R_RISCV_GNU_VTENTRY = 42;
|
||||
static constexpr u32 R_RISCV_ALIGN = 43;
|
||||
static constexpr u32 R_RISCV_RVC_BRANCH = 44;
|
||||
static constexpr u32 R_RISCV_RVC_JUMP = 45;
|
||||
@@ -1015,8 +1037,6 @@ inline std::string rel_to_string<RISCV64>(u32 r_type) {
|
||||
case R_RISCV_SUB16: return "R_RISCV_SUB16";
|
||||
case R_RISCV_SUB32: return "R_RISCV_SUB32";
|
||||
case R_RISCV_SUB64: return "R_RISCV_SUB64";
|
||||
case R_RISCV_GNU_VTINHERIT: return "R_RISCV_GNU_VTINHERIT";
|
||||
case R_RISCV_GNU_VTENTRY: return "R_RISCV_GNU_VTENTRY";
|
||||
case R_RISCV_ALIGN: return "R_RISCV_ALIGN";
|
||||
case R_RISCV_RVC_BRANCH: return "R_RISCV_RVC_BRANCH";
|
||||
case R_RISCV_RVC_JUMP: return "R_RISCV_RVC_JUMP";
|
||||
@@ -1033,6 +1053,11 @@ inline std::string rel_to_string<RISCV64>(u32 r_type) {
|
||||
return "unknown (" + std::to_string(r_type) + ")";
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string rel_to_string<RISCV32>(u32 r_type) {
|
||||
return rel_to_string<RISCV64>(r_type);
|
||||
}
|
||||
|
||||
static constexpr u32 DW_EH_PE_absptr = 0;
|
||||
static constexpr u32 DW_EH_PE_omit = 0xff;
|
||||
static constexpr u32 DW_EH_PE_uleb128 = 0x01;
|
||||
@@ -1054,8 +1079,16 @@ static constexpr u32 DW_AT_low_pc = 0x11;
|
||||
static constexpr u32 DW_AT_high_pc = 0x12;
|
||||
static constexpr u32 DW_AT_producer = 0x25;
|
||||
static constexpr u32 DW_AT_ranges = 0x55;
|
||||
static constexpr u32 DW_AT_addr_base = 0x73;
|
||||
static constexpr u32 DW_AT_rnglists_base = 0x74;
|
||||
|
||||
static constexpr u32 DW_TAG_compile_unit = 0x11;
|
||||
static constexpr u32 DW_TAG_skeleton_unit = 0x4a;
|
||||
|
||||
static constexpr u32 DW_UT_compile = 0x01;
|
||||
static constexpr u32 DW_UT_partial = 0x03;
|
||||
static constexpr u32 DW_UT_skeleton = 0x04;
|
||||
static constexpr u32 DW_UT_split_compile = 0x05;
|
||||
|
||||
static constexpr u32 DW_FORM_addr = 0x01;
|
||||
static constexpr u32 DW_FORM_block2 = 0x03;
|
||||
@@ -1081,6 +1114,34 @@ static constexpr u32 DW_FORM_indirect = 0x16;
|
||||
static constexpr u32 DW_FORM_sec_offset = 0x17;
|
||||
static constexpr u32 DW_FORM_exprloc = 0x18;
|
||||
static constexpr u32 DW_FORM_flag_present = 0x19;
|
||||
static constexpr u32 DW_FORM_strx = 0x1a;
|
||||
static constexpr u32 DW_FORM_addrx = 0x1b;
|
||||
static constexpr u32 DW_FORM_ref_sup4 = 0x1c;
|
||||
static constexpr u32 DW_FORM_strp_sup = 0x1d;
|
||||
static constexpr u32 DW_FORM_data16 = 0x1e;
|
||||
static constexpr u32 DW_FORM_line_strp = 0x1f;
|
||||
static constexpr u32 DW_FORM_ref_sig8 = 0x20;
|
||||
static constexpr u32 DW_FORM_implicit_const = 0x21;
|
||||
static constexpr u32 DW_FORM_loclistx = 0x22;
|
||||
static constexpr u32 DW_FORM_rnglistx = 0x23;
|
||||
static constexpr u32 DW_FORM_ref_sup8 = 0x24;
|
||||
static constexpr u32 DW_FORM_strx1 = 0x25;
|
||||
static constexpr u32 DW_FORM_strx2 = 0x26;
|
||||
static constexpr u32 DW_FORM_strx3 = 0x27;
|
||||
static constexpr u32 DW_FORM_strx4 = 0x28;
|
||||
static constexpr u32 DW_FORM_addrx1 = 0x29;
|
||||
static constexpr u32 DW_FORM_addrx2 = 0x2a;
|
||||
static constexpr u32 DW_FORM_addrx3 = 0x2b;
|
||||
static constexpr u32 DW_FORM_addrx4 = 0x2c;
|
||||
|
||||
static constexpr u32 DW_RLE_end_of_list = 0x00;
|
||||
static constexpr u32 DW_RLE_base_addressx = 0x01;
|
||||
static constexpr u32 DW_RLE_startx_endx = 0x02;
|
||||
static constexpr u32 DW_RLE_startx_length = 0x03;
|
||||
static constexpr u32 DW_RLE_offset_pair = 0x04;
|
||||
static constexpr u32 DW_RLE_base_address = 0x05;
|
||||
static constexpr u32 DW_RLE_start_end = 0x06;
|
||||
static constexpr u32 DW_RLE_start_length = 0x07;
|
||||
|
||||
struct Elf64Sym {
|
||||
bool is_defined() const { return !is_undef(); }
|
||||
@@ -1088,22 +1149,15 @@ struct Elf64Sym {
|
||||
bool is_abs() const { return st_shndx == SHN_ABS; }
|
||||
bool is_common() const { return st_shndx == SHN_COMMON; }
|
||||
bool is_weak() const { return st_bind == STB_WEAK; }
|
||||
bool is_undef_weak() const { return is_undef() && is_weak(); }
|
||||
|
||||
bool is_undef_strong() const {
|
||||
return st_shndx == SHN_UNDEF && st_bind != STB_WEAK;
|
||||
}
|
||||
|
||||
bool is_undef_weak() const {
|
||||
return st_shndx == SHN_UNDEF && st_bind == STB_WEAK;
|
||||
}
|
||||
|
||||
u32 st_name;
|
||||
ul32 st_name;
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u16 st_shndx;
|
||||
u64 st_value;
|
||||
u64 st_size;
|
||||
ul16 st_shndx;
|
||||
ul64 st_value;
|
||||
ul64 st_size;
|
||||
};
|
||||
|
||||
struct Elf32Sym {
|
||||
@@ -1112,194 +1166,233 @@ struct Elf32Sym {
|
||||
bool is_abs() const { return st_shndx == SHN_ABS; }
|
||||
bool is_common() const { return st_shndx == SHN_COMMON; }
|
||||
bool is_weak() const { return st_bind == STB_WEAK; }
|
||||
bool is_undef_weak() const { return is_undef() && is_weak(); }
|
||||
|
||||
bool is_undef_strong() const {
|
||||
return st_shndx == SHN_UNDEF && st_bind != STB_WEAK;
|
||||
}
|
||||
|
||||
bool is_undef_weak() const {
|
||||
return st_shndx == SHN_UNDEF && st_bind == STB_WEAK;
|
||||
}
|
||||
|
||||
u32 st_name;
|
||||
u32 st_value;
|
||||
u32 st_size;
|
||||
ul32 st_name;
|
||||
ul32 st_value;
|
||||
ul32 st_size;
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u16 st_shndx;
|
||||
ul16 st_shndx;
|
||||
};
|
||||
|
||||
struct Elf64Shdr {
|
||||
u32 sh_name;
|
||||
u32 sh_type;
|
||||
u64 sh_flags;
|
||||
u64 sh_addr;
|
||||
u64 sh_offset;
|
||||
u64 sh_size;
|
||||
u32 sh_link;
|
||||
u32 sh_info;
|
||||
u64 sh_addralign;
|
||||
u64 sh_entsize;
|
||||
ul32 sh_name;
|
||||
ul32 sh_type;
|
||||
ul64 sh_flags;
|
||||
ul64 sh_addr;
|
||||
ul64 sh_offset;
|
||||
ul64 sh_size;
|
||||
ul32 sh_link;
|
||||
ul32 sh_info;
|
||||
ul64 sh_addralign;
|
||||
ul64 sh_entsize;
|
||||
};
|
||||
|
||||
struct Elf32Shdr {
|
||||
u32 sh_name;
|
||||
u32 sh_type;
|
||||
u32 sh_flags;
|
||||
u32 sh_addr;
|
||||
u32 sh_offset;
|
||||
u32 sh_size;
|
||||
u32 sh_link;
|
||||
u32 sh_info;
|
||||
u32 sh_addralign;
|
||||
u32 sh_entsize;
|
||||
ul32 sh_name;
|
||||
ul32 sh_type;
|
||||
ul32 sh_flags;
|
||||
ul32 sh_addr;
|
||||
ul32 sh_offset;
|
||||
ul32 sh_size;
|
||||
ul32 sh_link;
|
||||
ul32 sh_info;
|
||||
ul32 sh_addralign;
|
||||
ul32 sh_entsize;
|
||||
};
|
||||
|
||||
struct Elf64Ehdr {
|
||||
u8 e_ident[16];
|
||||
u16 e_type;
|
||||
u16 e_machine;
|
||||
u32 e_version;
|
||||
u64 e_entry;
|
||||
u64 e_phoff;
|
||||
u64 e_shoff;
|
||||
u32 e_flags;
|
||||
u16 e_ehsize;
|
||||
u16 e_phentsize;
|
||||
u16 e_phnum;
|
||||
u16 e_shentsize;
|
||||
u16 e_shnum;
|
||||
u16 e_shstrndx;
|
||||
ul16 e_type;
|
||||
ul16 e_machine;
|
||||
ul32 e_version;
|
||||
ul64 e_entry;
|
||||
ul64 e_phoff;
|
||||
ul64 e_shoff;
|
||||
ul32 e_flags;
|
||||
ul16 e_ehsize;
|
||||
ul16 e_phentsize;
|
||||
ul16 e_phnum;
|
||||
ul16 e_shentsize;
|
||||
ul16 e_shnum;
|
||||
ul16 e_shstrndx;
|
||||
};
|
||||
|
||||
struct Elf32Ehdr {
|
||||
u8 e_ident[16];
|
||||
u16 e_type;
|
||||
u16 e_machine;
|
||||
u32 e_version;
|
||||
u32 e_entry;
|
||||
u32 e_phoff;
|
||||
u32 e_shoff;
|
||||
u32 e_flags;
|
||||
u16 e_ehsize;
|
||||
u16 e_phentsize;
|
||||
u16 e_phnum;
|
||||
u16 e_shentsize;
|
||||
u16 e_shnum;
|
||||
u16 e_shstrndx;
|
||||
ul16 e_type;
|
||||
ul16 e_machine;
|
||||
ul32 e_version;
|
||||
ul32 e_entry;
|
||||
ul32 e_phoff;
|
||||
ul32 e_shoff;
|
||||
ul32 e_flags;
|
||||
ul16 e_ehsize;
|
||||
ul16 e_phentsize;
|
||||
ul16 e_phnum;
|
||||
ul16 e_shentsize;
|
||||
ul16 e_shnum;
|
||||
ul16 e_shstrndx;
|
||||
};
|
||||
|
||||
struct Elf64Phdr {
|
||||
u32 p_type;
|
||||
u32 p_flags;
|
||||
u64 p_offset;
|
||||
u64 p_vaddr;
|
||||
u64 p_paddr;
|
||||
u64 p_filesz;
|
||||
u64 p_memsz;
|
||||
u64 p_align;
|
||||
ul32 p_type;
|
||||
ul32 p_flags;
|
||||
ul64 p_offset;
|
||||
ul64 p_vaddr;
|
||||
ul64 p_paddr;
|
||||
ul64 p_filesz;
|
||||
ul64 p_memsz;
|
||||
ul64 p_align;
|
||||
};
|
||||
|
||||
struct Elf32Phdr {
|
||||
u32 p_type;
|
||||
u32 p_offset;
|
||||
u32 p_vaddr;
|
||||
u32 p_paddr;
|
||||
u32 p_filesz;
|
||||
u32 p_memsz;
|
||||
u32 p_flags;
|
||||
u32 p_align;
|
||||
ul32 p_type;
|
||||
ul32 p_offset;
|
||||
ul32 p_vaddr;
|
||||
ul32 p_paddr;
|
||||
ul32 p_filesz;
|
||||
ul32 p_memsz;
|
||||
ul32 p_flags;
|
||||
ul32 p_align;
|
||||
};
|
||||
|
||||
// Depending on the target, ElfRel may or may not contain r_addend member.
|
||||
// The relocation record containing r_addend is called RELA, and that
|
||||
// without r_addend is called REL.
|
||||
//
|
||||
// If REL, relocation addends are stored as parts of section contents.
|
||||
// That means we add a computed value to an existing value when writing a
|
||||
// relocated value if REL. If RELA, we just overwrite an existing value
|
||||
// with a newly computed value.
|
||||
//
|
||||
// We don't want to have too many `if (REL)`s and `if (RELA)`s in our
|
||||
// codebase, so we write dynamic relocations in the following manner:
|
||||
//
|
||||
// - We always create a dynamic relocation with an addend. If it's REL,
|
||||
// the addend will be discarded.
|
||||
//
|
||||
// - We also always write an addend to the relocated place even though
|
||||
// it's redundant for RELA. If RELA, the written value will be
|
||||
// ovewritten by the dynamic linker at load-time.
|
||||
struct Elf64Rel {
|
||||
u64 r_offset;
|
||||
u32 r_type;
|
||||
u32 r_sym;
|
||||
Elf64Rel(u64 r_offset, u32 r_type, u32 r_sym, i64 r_addend = 0)
|
||||
: r_offset(r_offset), r_type(r_type), r_sym(r_sym) {}
|
||||
|
||||
ul64 r_offset;
|
||||
ul32 r_type;
|
||||
ul32 r_sym;
|
||||
};
|
||||
|
||||
struct Elf32Rel {
|
||||
u32 r_offset;
|
||||
u32 r_type : 8;
|
||||
u32 r_sym : 24;
|
||||
Elf32Rel(u64 r_offset, u32 r_type, u32 r_sym, i64 r_addend = 0)
|
||||
: r_offset(r_offset), r_type(r_type), r_sym(r_sym) {}
|
||||
|
||||
ul32 r_offset;
|
||||
u8 r_type;
|
||||
ul24 r_sym;
|
||||
};
|
||||
|
||||
struct Elf64Rela {
|
||||
u64 r_offset;
|
||||
u32 r_type;
|
||||
u32 r_sym;
|
||||
i64 r_addend;
|
||||
Elf64Rela(u64 r_offset, u32 r_type, u32 r_sym, i64 r_addend)
|
||||
: r_offset(r_offset), r_type(r_type), r_sym(r_sym), r_addend(r_addend) {}
|
||||
|
||||
ul64 r_offset;
|
||||
ul32 r_type;
|
||||
ul32 r_sym;
|
||||
il64 r_addend;
|
||||
};
|
||||
|
||||
struct Elf32Rela {
|
||||
u32 r_offset;
|
||||
u32 r_type : 8;
|
||||
u32 r_sym : 24;
|
||||
i32 r_addend;
|
||||
Elf32Rela(u64 r_offset, u32 r_type, u32 r_sym, i64 r_addend)
|
||||
: r_offset(r_offset), r_type(r_type), r_sym(r_sym), r_addend(r_addend) {}
|
||||
|
||||
ul32 r_offset;
|
||||
u8 r_type;
|
||||
ul24 r_sym;
|
||||
il32 r_addend;
|
||||
};
|
||||
|
||||
struct Elf64Dyn {
|
||||
u64 d_tag;
|
||||
u64 d_val;
|
||||
ul64 d_tag;
|
||||
ul64 d_val;
|
||||
};
|
||||
|
||||
struct Elf32Dyn {
|
||||
u32 d_tag;
|
||||
u32 d_val;
|
||||
ul32 d_tag;
|
||||
ul32 d_val;
|
||||
};
|
||||
|
||||
struct ElfVerneed {
|
||||
u16 vn_version;
|
||||
u16 vn_cnt;
|
||||
u32 vn_file;
|
||||
u32 vn_aux;
|
||||
u32 vn_next;
|
||||
ul16 vn_version;
|
||||
ul16 vn_cnt;
|
||||
ul32 vn_file;
|
||||
ul32 vn_aux;
|
||||
ul32 vn_next;
|
||||
};
|
||||
|
||||
struct ElfVernaux {
|
||||
u32 vna_hash;
|
||||
u16 vna_flags;
|
||||
u16 vna_other;
|
||||
u32 vna_name;
|
||||
u32 vna_next;
|
||||
ul32 vna_hash;
|
||||
ul16 vna_flags;
|
||||
ul16 vna_other;
|
||||
ul32 vna_name;
|
||||
ul32 vna_next;
|
||||
};
|
||||
|
||||
struct ElfVerdef {
|
||||
u16 vd_version;
|
||||
u16 vd_flags;
|
||||
u16 vd_ndx;
|
||||
u16 vd_cnt;
|
||||
u32 vd_hash;
|
||||
u32 vd_aux;
|
||||
u32 vd_next;
|
||||
ul16 vd_version;
|
||||
ul16 vd_flags;
|
||||
ul16 vd_ndx;
|
||||
ul16 vd_cnt;
|
||||
ul32 vd_hash;
|
||||
ul32 vd_aux;
|
||||
ul32 vd_next;
|
||||
};
|
||||
|
||||
struct ElfVerdaux {
|
||||
u32 vda_name;
|
||||
u32 vda_next;
|
||||
ul32 vda_name;
|
||||
ul32 vda_next;
|
||||
};
|
||||
|
||||
struct Elf64Chdr {
|
||||
u32 ch_type;
|
||||
u32 ch_reserved;
|
||||
u64 ch_size;
|
||||
u64 ch_addralign;
|
||||
ul32 ch_type;
|
||||
ul32 ch_reserved;
|
||||
ul64 ch_size;
|
||||
ul64 ch_addralign;
|
||||
};
|
||||
|
||||
struct Elf32Chdr {
|
||||
u32 ch_type;
|
||||
u32 ch_size;
|
||||
u32 ch_addralign;
|
||||
ul32 ch_type;
|
||||
ul32 ch_size;
|
||||
ul32 ch_addralign;
|
||||
};
|
||||
|
||||
struct ElfNhdr {
|
||||
u32 n_namesz;
|
||||
u32 n_descsz;
|
||||
u32 n_type;
|
||||
ul32 n_namesz;
|
||||
ul32 n_descsz;
|
||||
ul32 n_type;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
inline constexpr bool is_rela = requires(ElfRel<E> r) { r.r_addend; };
|
||||
|
||||
template <typename E>
|
||||
inline constexpr bool supports_tlsdesc = requires { E::R_TLSDESC; };
|
||||
|
||||
template <typename E>
|
||||
inline constexpr bool needs_thunk = requires { E::thunk_size; };
|
||||
|
||||
template <typename E>
|
||||
inline constexpr bool is_riscv =
|
||||
std::is_same_v<E, RISCV64> || std::is_same_v<E, RISCV32>;
|
||||
|
||||
template <typename E>
|
||||
using Word = typename E::Word;
|
||||
|
||||
struct X86_64 {
|
||||
using WordTy = u64;
|
||||
using Word = ul64;
|
||||
|
||||
static constexpr u32 R_NONE = R_X86_64_NONE;
|
||||
static constexpr u32 R_COPY = R_X86_64_COPY;
|
||||
@@ -1313,25 +1406,25 @@ struct X86_64 {
|
||||
static constexpr u32 R_DTPMOD = R_X86_64_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_X86_64_TLSDESC;
|
||||
|
||||
static constexpr u32 word_size = 8;
|
||||
static constexpr MachineType machine_type = MachineType::X86_64;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_X86_64;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr bool is_rel = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool supports_tlsdesc = true;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 tls_dtv_offset = 0;
|
||||
};
|
||||
|
||||
template <> struct ElfSym<X86_64> : public Elf64Sym {};
|
||||
template <> struct ElfShdr<X86_64> : public Elf64Shdr {};
|
||||
template <> struct ElfEhdr<X86_64> : public Elf64Ehdr {};
|
||||
template <> struct ElfPhdr<X86_64> : public Elf64Phdr {};
|
||||
template <> struct ElfRel<X86_64> : public Elf64Rela {};
|
||||
template <> struct ElfRel<X86_64> : public Elf64Rela { using Elf64Rela::Elf64Rela; };
|
||||
template <> struct ElfDyn<X86_64> : public Elf64Dyn {};
|
||||
template <> struct ElfChdr<X86_64> : public Elf64Chdr {};
|
||||
|
||||
struct I386 {
|
||||
using WordTy = u32;
|
||||
using Word = ul32;
|
||||
|
||||
static constexpr u32 R_NONE = R_386_NONE;
|
||||
static constexpr u32 R_COPY = R_386_COPY;
|
||||
@@ -1345,25 +1438,25 @@ struct I386 {
|
||||
static constexpr u32 R_DTPMOD = R_386_TLS_DTPMOD32;
|
||||
static constexpr u32 R_TLSDESC = R_386_TLS_DESC;
|
||||
|
||||
static constexpr u32 word_size = 4;
|
||||
static constexpr MachineType machine_type = MachineType::I386;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_386;
|
||||
static constexpr u32 plt_hdr_size = 16;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr bool is_rel = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool supports_tlsdesc = true;
|
||||
static constexpr u32 tls_dtv_offset = 0;
|
||||
};
|
||||
|
||||
template <> struct ElfSym<I386> : public Elf32Sym {};
|
||||
template <> struct ElfShdr<I386> : public Elf32Shdr {};
|
||||
template <> struct ElfEhdr<I386> : public Elf32Ehdr {};
|
||||
template <> struct ElfPhdr<I386> : public Elf32Phdr {};
|
||||
template <> struct ElfRel<I386> : public Elf32Rel {};
|
||||
template <> struct ElfRel<I386> : public Elf32Rel { using Elf32Rel::Elf32Rel; };
|
||||
template <> struct ElfDyn<I386> : public Elf32Dyn {};
|
||||
template <> struct ElfChdr<I386> : public Elf32Chdr {};
|
||||
|
||||
struct ARM64 {
|
||||
using WordTy = u64;
|
||||
using Word = ul64;
|
||||
|
||||
static constexpr u32 R_NONE = R_AARCH64_NONE;
|
||||
static constexpr u32 R_COPY = R_AARCH64_COPY;
|
||||
@@ -1377,25 +1470,40 @@ struct ARM64 {
|
||||
static constexpr u32 R_DTPMOD = R_AARCH64_TLS_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_AARCH64_TLSDESC;
|
||||
|
||||
static constexpr u32 word_size = 8;
|
||||
static constexpr MachineType machine_type = MachineType::ARM64;
|
||||
static constexpr u32 page_size = 65536;
|
||||
static constexpr u32 e_machine = EM_AARCH64;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr bool is_rel = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool supports_tlsdesc = true;
|
||||
|
||||
// Each thread has its own value in TP (thread pointer) register, and
|
||||
// TLVs defined in the main executable are accessed relative to TP.
|
||||
// ARM runtime reserves two words right at TP, so the TLVs start at
|
||||
// TP + 16 (or TP + 8 on ARM32).
|
||||
static constexpr u32 tls_tp_offset = 16;
|
||||
|
||||
static constexpr u32 tls_dtv_offset = 0;
|
||||
|
||||
// For ARM, we need to insert a piece of code between a function call
|
||||
// site and the callee if they are not reachable with a single branch
|
||||
// instruction. Here are parameters for the thunk creation function.
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 12;
|
||||
static constexpr u32 thunk_max_distance = 100 * 1024 * 1024;
|
||||
static constexpr u32 thunk_group_size = 10 * 1024 * 1024;
|
||||
};
|
||||
|
||||
template <> struct ElfSym<ARM64> : public Elf64Sym {};
|
||||
template <> struct ElfShdr<ARM64> : public Elf64Shdr {};
|
||||
template <> struct ElfEhdr<ARM64> : public Elf64Ehdr {};
|
||||
template <> struct ElfPhdr<ARM64> : public Elf64Phdr {};
|
||||
template <> struct ElfRel<ARM64> : public Elf64Rela {};
|
||||
template <> struct ElfRel<ARM64> : public Elf64Rela { using Elf64Rela::Elf64Rela; };
|
||||
template <> struct ElfDyn<ARM64> : public Elf64Dyn {};
|
||||
template <> struct ElfChdr<ARM64> : public Elf64Chdr {};
|
||||
|
||||
struct ARM32 {
|
||||
using WordTy = u32;
|
||||
using Word = ul32;
|
||||
|
||||
static constexpr u32 R_NONE = R_ARM_NONE;
|
||||
static constexpr u32 R_COPY = R_ARM_COPY;
|
||||
@@ -1409,25 +1517,31 @@ struct ARM32 {
|
||||
static constexpr u32 R_DTPMOD = R_ARM_TLS_DTPMOD32;
|
||||
static constexpr u32 R_TLSDESC = R_ARM_TLS_DESC;
|
||||
|
||||
static constexpr u32 word_size = 4;
|
||||
static constexpr MachineType machine_type = MachineType::ARM32;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_ARM;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr bool is_rel = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool supports_tlsdesc = true;
|
||||
static constexpr u32 tls_tp_offset = 8;
|
||||
static constexpr u32 tls_dtv_offset = 0;
|
||||
|
||||
static constexpr u32 thunk_hdr_size = 12;
|
||||
static constexpr u32 thunk_size = 20;
|
||||
static constexpr u32 thunk_max_distance = 10 * 1024 * 1024;
|
||||
static constexpr u32 thunk_group_size = 2 * 1024 * 1024;
|
||||
};
|
||||
|
||||
template <> struct ElfSym<ARM32> : public Elf32Sym {};
|
||||
template <> struct ElfShdr<ARM32> : public Elf32Shdr {};
|
||||
template <> struct ElfEhdr<ARM32> : public Elf32Ehdr {};
|
||||
template <> struct ElfPhdr<ARM32> : public Elf32Phdr {};
|
||||
template <> struct ElfRel<ARM32> : public Elf32Rel {};
|
||||
template <> struct ElfRel<ARM32> : public Elf32Rel { using Elf32Rel::Elf32Rel; };
|
||||
template <> struct ElfDyn<ARM32> : public Elf32Dyn {};
|
||||
template <> struct ElfChdr<ARM32> : public Elf32Chdr {};
|
||||
|
||||
struct RISCV64 {
|
||||
using WordTy = u64;
|
||||
using Word = ul64;
|
||||
|
||||
static constexpr u32 R_NONE = R_RISCV_NONE;
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
@@ -1440,21 +1554,76 @@ struct RISCV64 {
|
||||
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD64;
|
||||
|
||||
static constexpr u32 word_size = 8;
|
||||
static constexpr MachineType machine_type = MachineType::RISCV64;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_RISCV;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr bool is_rel = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool supports_tlsdesc = false;
|
||||
static constexpr u32 tls_tp_offset = 0;
|
||||
|
||||
// When __tls_get_addr is called to resolve a thread-local variable's
|
||||
// address, the following two arguments are passed to the function:
|
||||
//
|
||||
// 1. the module number that the variable belongs, and
|
||||
// 2. the variable's offset within the module's TLS block.
|
||||
//
|
||||
// These values are usually computed by the dynamic linker and set to
|
||||
// GOT slots as a result of resolving R_DTPMOD and R_DTPOFF dynamic
|
||||
// relocations.
|
||||
//
|
||||
// On RISC-V, R_DTPOFF is resolved to the address 0x800 past the start
|
||||
// of the TLS block. The bias maximizes the accessible range for
|
||||
// load/store instructions with 12-bits signed immediates. That is, if
|
||||
// the offset was right at the beginning of the start of the TLS block,
|
||||
// the half of addressible space (negative immediates) would have been
|
||||
// wasted.
|
||||
//
|
||||
// In most cases we don't have to think about the bias, as the DTPOFF
|
||||
// values are usually computed and used only by runtime. But when we do
|
||||
// compute DTPOFF for statically-linked executable, we need to offset
|
||||
// the bias by subtracting 0x800.
|
||||
static constexpr u32 tls_dtv_offset = 0x800;
|
||||
};
|
||||
|
||||
template <> struct ElfSym<RISCV64> : public Elf64Sym {};
|
||||
template <> struct ElfShdr<RISCV64> : public Elf64Shdr {};
|
||||
template <> struct ElfEhdr<RISCV64> : public Elf64Ehdr {};
|
||||
template <> struct ElfPhdr<RISCV64> : public Elf64Phdr {};
|
||||
template <> struct ElfRel<RISCV64> : public Elf64Rela {};
|
||||
template <> struct ElfRel<RISCV64> : public Elf64Rela { using Elf64Rela::Elf64Rela; };
|
||||
template <> struct ElfDyn<RISCV64> : public Elf64Dyn {};
|
||||
template <> struct ElfChdr<RISCV64> : public Elf64Chdr {};
|
||||
|
||||
struct RISCV32 {
|
||||
using Word = ul32;
|
||||
|
||||
static constexpr u32 R_NONE = R_RISCV_NONE;
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
|
||||
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_RISCV_32;
|
||||
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD32;
|
||||
|
||||
static constexpr MachineType machine_type = MachineType::RISCV32;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_RISCV;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 tls_tp_offset = 0;
|
||||
static constexpr u32 tls_dtv_offset = 0x800;
|
||||
};
|
||||
|
||||
template <> struct ElfSym<RISCV32> : public Elf32Sym {};
|
||||
template <> struct ElfShdr<RISCV32> : public Elf32Shdr {};
|
||||
template <> struct ElfEhdr<RISCV32> : public Elf32Ehdr {};
|
||||
template <> struct ElfPhdr<RISCV32> : public Elf32Phdr {};
|
||||
template <> struct ElfRel<RISCV32> : public Elf32Rela { using Elf32Rela::Elf32Rela; };
|
||||
template <> struct ElfDyn<RISCV32> : public Elf32Dyn {};
|
||||
template <> struct ElfChdr<RISCV32> : public Elf32Chdr {};
|
||||
|
||||
} // namespace mold::elf
|
||||
+15
-23
@@ -26,13 +26,13 @@ static bool is_init_fini(const InputSection<E> &isec) {
|
||||
|
||||
template <typename E>
|
||||
static bool mark_section(InputSection<E> *isec) {
|
||||
return isec && isec->is_alive && !isec->extra().is_visited.exchange(true);
|
||||
return isec && isec->is_alive && !isec->is_visited.exchange(true);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
|
||||
assert(isec->extra().is_visited);
|
||||
assert(isec->is_visited);
|
||||
|
||||
// A relocation can refer either a section fragment (i.e. a piece of
|
||||
// string in a mergeable string section) or a symbol. Mark all
|
||||
@@ -45,12 +45,12 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
// describing how to handle exceptions for that function.
|
||||
// We want to keep associated .eh_frame records.
|
||||
for (FdeRecord<E> &fde : isec->get_fdes())
|
||||
for (ElfRel<E> &rel : fde.get_rels(isec->file).subspan(1))
|
||||
for (const ElfRel<E> &rel : fde.get_rels(isec->file).subspan(1))
|
||||
if (Symbol<E> *sym = isec->file.symbols[rel.r_sym])
|
||||
if (mark_section(sym->get_input_section()))
|
||||
feeder.add(sym->get_input_section());
|
||||
|
||||
for (ElfRel<E> &rel : isec->get_rels(ctx)) {
|
||||
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
|
||||
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
|
||||
|
||||
// Symbol can refer either a section fragment or an input section.
|
||||
@@ -73,10 +73,9 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static tbb::concurrent_vector<InputSection<E> *>
|
||||
collect_root_set(Context<E> &ctx) {
|
||||
static void collect_root_set(Context<E> &ctx,
|
||||
tbb::concurrent_vector<InputSection<E> *> &rootset) {
|
||||
Timer t(ctx, "collect_root_set");
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset;
|
||||
|
||||
auto enqueue_section = [&](InputSection<E> *isec) {
|
||||
if (mark_section(isec))
|
||||
@@ -102,11 +101,12 @@ collect_root_set(Context<E> &ctx) {
|
||||
// reduce the amount of non-memory-mapped segments, you should
|
||||
// use `strip` command, compile without debug info or use
|
||||
// -strip-all linker option.
|
||||
if (!(isec->shdr().sh_flags & SHF_ALLOC))
|
||||
isec->extra().is_visited = true;
|
||||
u32 flags = isec->shdr().sh_flags;
|
||||
if (!(flags & SHF_ALLOC))
|
||||
isec->is_visited = true;
|
||||
|
||||
if (is_init_fini(*isec) || is_c_identifier(isec->name()) ||
|
||||
isec->shdr().sh_type == SHT_NOTE)
|
||||
(flags & SHF_GNU_RETAIN) || isec->shdr().sh_type == SHT_NOTE)
|
||||
enqueue_section(isec.get());
|
||||
}
|
||||
});
|
||||
@@ -132,11 +132,9 @@ collect_root_set(Context<E> &ctx) {
|
||||
// We just keep all CIEs and everything that are referenced by them.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (CieRecord<E> &cie : file->cies)
|
||||
for (ElfRel<E> &rel : cie.get_rels())
|
||||
for (const ElfRel<E> &rel : cie.get_rels())
|
||||
enqueue_symbol(file->symbols[rel.r_sym]);
|
||||
});
|
||||
|
||||
return rootset;
|
||||
}
|
||||
|
||||
// Mark all reachable sections
|
||||
@@ -159,7 +157,7 @@ static void sweep(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && !isec->extra().is_visited) {
|
||||
if (isec && isec->is_alive && !isec->is_visited) {
|
||||
if (ctx.arg.print_gc_sections)
|
||||
SyncOut(ctx) << "removing unused section " << *isec;
|
||||
isec->kill();
|
||||
@@ -188,12 +186,10 @@ template <typename E>
|
||||
void gc_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "gc");
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->extras.resize(file->sections.size());
|
||||
|
||||
mark_nonalloc_fragments(ctx);
|
||||
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset = collect_root_set(ctx);
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset;
|
||||
collect_root_set(ctx, rootset);
|
||||
mark(ctx, rootset);
|
||||
sweep(ctx);
|
||||
}
|
||||
@@ -201,10 +197,6 @@ void gc_sections(Context<E> &ctx) {
|
||||
#define INSTANTIATE(E) \
|
||||
template void gc_sections(Context<E> &ctx);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+92
-58
@@ -40,22 +40,35 @@
|
||||
// regular trees are equal. Given this formulation, we want to find as
|
||||
// many identical vertices as possible.
|
||||
//
|
||||
// Solving such problem is computationally intensive, but mold is quite fast.
|
||||
// For Chromium, mold's ICF finishes in less than 1 second with 20 threads.
|
||||
// This is contrary to lld and gold, which take about 5 and 50 seconds to
|
||||
// run ICF under the same condition, respectively.
|
||||
// Just like a lot of problems with graph, this problem doesn't have a
|
||||
// straightforward "optimal" solution, and we need to resort to heuristics.
|
||||
//
|
||||
// mold's ICF is faster because we are using a better algorithm.
|
||||
// It's actually me who developed and implemented the lld's ICF algorithm,
|
||||
// and I can say that mold's algorithm is better than that in all aspects.
|
||||
// It scales better for number of available cores, require less overall
|
||||
// computation, and has a smaller working set. So, it's better with a single
|
||||
// thread and even better with multiple threads.
|
||||
// mold approaches this problem by hashing program trees with increasing depth
|
||||
// on each iteration.
|
||||
// For example, when we start, we only hash individual functions with
|
||||
// their call into other functions omitted. From the second iteration, we
|
||||
// put the function they call into the hash by appending the hash of those
|
||||
// functions from the previous iteration. This means that the nth iteration
|
||||
// hashes call chain up to (n-1) levels deep.
|
||||
// We use a cryptographic hash function, so the unique number of hashes will
|
||||
// only monotonically increase as we take into account of deeper trees with
|
||||
// iterations (otherwise, that means we have found a hash collision). We stop
|
||||
// when the unique number of hashes stop increasing; this is based on the fact
|
||||
// that once we observe an iteration with the same amount of unique hashes as
|
||||
// the previous iteration, it will remain unchanged for further iterations.
|
||||
// This is provable, but here we omit the proof for brevity.
|
||||
//
|
||||
// When compared to other approaches, mold's approach has a relatively cheaper
|
||||
// cost per iteration, and as a bonus, is highly parallelizable.
|
||||
// For Chromium, mold's ICF finishes in less than 1 second with 20 threads,
|
||||
// whereas lld takes 5 seconds and gold takes 50 seconds under the same
|
||||
// conditions.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../sha.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <tbb/concurrent_unordered_map.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/enumerable_thread_specific.h>
|
||||
@@ -98,12 +111,13 @@ static void uniquify_cies(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool is_eligible(InputSection<E> &isec) {
|
||||
static bool is_eligible(Context<E> &ctx, InputSection<E> &isec) {
|
||||
const ElfShdr<E> &shdr = isec.shdr();
|
||||
std::string_view name = isec.name();
|
||||
|
||||
bool is_alloc = (shdr.sh_flags & SHF_ALLOC);
|
||||
bool is_executable = (shdr.sh_flags & SHF_EXECINSTR);
|
||||
bool is_exec = (shdr.sh_flags & SHF_EXECINSTR) ||
|
||||
ctx.arg.ignore_data_address_equality;
|
||||
bool is_relro = (name == ".data.rel.ro" ||
|
||||
name.starts_with(".data.rel.ro."));
|
||||
bool is_readonly = !(shdr.sh_flags & SHF_WRITE) || is_relro;
|
||||
@@ -112,15 +126,15 @@ static bool is_eligible(InputSection<E> &isec) {
|
||||
bool is_init = (shdr.sh_type == SHT_INIT_ARRAY || name == ".init");
|
||||
bool is_fini = (shdr.sh_type == SHT_FINI_ARRAY || name == ".fini");
|
||||
bool is_enumerable = is_c_identifier(name);
|
||||
bool is_addr_taken = !ctx.arg.icf_all && isec.address_significant;
|
||||
|
||||
return is_alloc && is_executable && is_readonly && !is_bss &&
|
||||
!is_empty && !is_init && !is_fini && !is_enumerable;
|
||||
return is_alloc && is_exec && is_readonly && !is_bss && !is_empty &&
|
||||
!is_init && !is_fini && !is_enumerable && !is_addr_taken;
|
||||
}
|
||||
|
||||
static Digest digest_final(SHA256_CTX &sha) {
|
||||
static Digest digest_final(SHA256Hash &sha) {
|
||||
u8 buf[SHA256_SIZE];
|
||||
int res = SHA256_Final(buf, &sha);
|
||||
assert(res == 1);
|
||||
sha.finish(buf);
|
||||
|
||||
Digest digest;
|
||||
memcpy(digest.data(), buf, HASH_SIZE);
|
||||
@@ -175,6 +189,8 @@ struct LeafEq {
|
||||
}
|
||||
};
|
||||
|
||||
// Early merge of leaf nodes, which can be processed without constructing the
|
||||
// entire graph. This reduces the vertex count and improves memory efficiency.
|
||||
template <typename E>
|
||||
static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
Timer t(ctx, "merge_leaf_nodes");
|
||||
@@ -191,30 +207,30 @@ static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
if (!isec || !isec->is_alive)
|
||||
continue;
|
||||
|
||||
if (!is_eligible(*isec)) {
|
||||
if (!is_eligible(ctx, *isec)) {
|
||||
non_eligible++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_leaf(ctx, *isec)) {
|
||||
leaf++;
|
||||
isec->extra().icf_leaf = true;
|
||||
isec->icf_leaf = true;
|
||||
auto [it, inserted] = map.insert({isec.get(), isec.get()});
|
||||
if (!inserted && isec->get_priority() < it->second->get_priority())
|
||||
it->second = isec.get();
|
||||
} else {
|
||||
eligible++;
|
||||
isec->extra().icf_eligible = true;
|
||||
isec->icf_eligible = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections) {
|
||||
if (isec && isec->is_alive && isec->extra().icf_leaf) {
|
||||
if (isec && isec->is_alive && isec->icf_leaf) {
|
||||
auto it = map.find(isec.get());
|
||||
assert(it != map.end());
|
||||
isec->extra().leader = it->second;
|
||||
isec->leader = it->second;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -222,16 +238,15 @@ static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
SHA256_CTX sha;
|
||||
SHA256_Init(&sha);
|
||||
SHA256Hash sha;
|
||||
|
||||
auto hash = [&](auto val) {
|
||||
SHA256_Update(&sha, &val, sizeof(val));
|
||||
sha.update((u8 *)&val, sizeof(val));
|
||||
};
|
||||
|
||||
auto hash_string = [&](std::string_view str) {
|
||||
hash(str.size());
|
||||
SHA256_Update(&sha, str.data(), str.size());
|
||||
sha.update((u8 *)str.data(), str.size());
|
||||
};
|
||||
|
||||
auto hash_symbol = [&](Symbol<E> &sym) {
|
||||
@@ -245,10 +260,10 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
hash((u64)frag);
|
||||
} else if (!isec) {
|
||||
hash('3');
|
||||
} else if (isec->extra().leader) {
|
||||
} else if (isec->leader) {
|
||||
hash('4');
|
||||
hash((u64)isec->extra().leader);
|
||||
} else if (isec->extra().icf_eligible) {
|
||||
hash((u64)isec->leader);
|
||||
} else if (isec->icf_eligible) {
|
||||
hash('5');
|
||||
} else {
|
||||
hash('6');
|
||||
@@ -271,7 +286,7 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
|
||||
hash(fde.get_rels(isec.file).size());
|
||||
|
||||
for (ElfRel<E> &rel : fde.get_rels(isec.file).subspan(1)) {
|
||||
for (const ElfRel<E> &rel : fde.get_rels(isec.file).subspan(1)) {
|
||||
hash_symbol(*isec.file.symbols[rel.r_sym]);
|
||||
hash(rel.r_type);
|
||||
hash(rel.r_offset - fde.input_offset);
|
||||
@@ -282,7 +297,7 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 i = 0; i < isec.get_rels(ctx).size(); i++) {
|
||||
ElfRel<E> &rel = isec.get_rels(ctx)[i];
|
||||
const ElfRel<E> &rel = isec.get_rels(ctx)[i];
|
||||
hash(rel.r_offset);
|
||||
hash(rel.r_type);
|
||||
hash(isec.get_addend(rel));
|
||||
@@ -309,7 +324,7 @@ static std::vector<InputSection<E> *> gather_sections(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections)
|
||||
if (isec && isec->is_alive && isec->extra().icf_eligible)
|
||||
if (isec && isec->is_alive && isec->icf_eligible)
|
||||
num_sections[i]++;
|
||||
});
|
||||
|
||||
@@ -324,12 +339,12 @@ static std::vector<InputSection<E> *> gather_sections(Context<E> &ctx) {
|
||||
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
|
||||
i64 idx = section_indices[i];
|
||||
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections)
|
||||
if (isec && isec->is_alive && isec->extra().icf_eligible)
|
||||
if (isec && isec->is_alive && isec->icf_eligible)
|
||||
sections[idx++] = isec.get();
|
||||
});
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
|
||||
sections[i]->extra().icf_idx = i;
|
||||
sections[i]->icf_idx = i;
|
||||
});
|
||||
|
||||
return sections;
|
||||
@@ -347,6 +362,9 @@ compute_digests(Context<E> &ctx, std::span<InputSection<E> *> sections) {
|
||||
return digests;
|
||||
}
|
||||
|
||||
// Build a graph, treating every function as a vertex and every function call
|
||||
// as an edge. See the description at the top for a more detailed formulation.
|
||||
// We use u32 indices here to improve cache locality.
|
||||
template <typename E>
|
||||
static void gather_edges(Context<E> &ctx,
|
||||
std::span<InputSection<E> *> sections,
|
||||
@@ -362,18 +380,18 @@ static void gather_edges(Context<E> &ctx,
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
|
||||
InputSection<E> &isec = *sections[i];
|
||||
assert(isec.extra().icf_eligible);
|
||||
assert(isec.icf_eligible);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 j = 0; j < isec.get_rels(ctx).size(); j++) {
|
||||
if (isec.rel_fragments && isec.rel_fragments[frag_idx].idx == j) {
|
||||
frag_idx++;
|
||||
} else {
|
||||
ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
const ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.get_frag())
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
if (isec->extra().icf_eligible)
|
||||
if (isec->icf_eligible)
|
||||
num_edges[i]++;
|
||||
}
|
||||
}
|
||||
@@ -392,12 +410,13 @@ static void gather_edges(Context<E> &ctx,
|
||||
for (i64 j = 0; j < isec.get_rels(ctx).size(); j++) {
|
||||
if (isec.rel_fragments && isec.rel_fragments[frag_idx].idx == j) {
|
||||
frag_idx++;
|
||||
ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
} else {
|
||||
const ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.get_frag())
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
if (isec->extra().icf_eligible)
|
||||
edges[idx++] = isec->extra().icf_idx;
|
||||
if (isec->icf_eligible)
|
||||
edges[idx++] = isec->icf_idx;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -417,15 +436,15 @@ static i64 propagate(std::span<std::vector<Digest>> digests,
|
||||
if (digests[slot][i] == digests[!slot][i])
|
||||
return;
|
||||
|
||||
SHA256_CTX sha;
|
||||
SHA256_Init(&sha);
|
||||
SHA256_Update(&sha, digests[2][i].data(), HASH_SIZE);
|
||||
SHA256Hash sha;
|
||||
sha.update(digests[2][i].data(), HASH_SIZE);
|
||||
|
||||
i64 begin = edge_indices[i];
|
||||
i64 end = (i + 1 == num_digests) ? edges.size() : edge_indices[i + 1];
|
||||
|
||||
for (i64 j : edges.subspan(begin, end - begin))
|
||||
SHA256_Update(&sha, digests[slot][j].data(), HASH_SIZE);
|
||||
for (i64 j : edges.subspan(begin, end - begin)) {
|
||||
sha.update(digests[slot][j].data(), HASH_SIZE);
|
||||
}
|
||||
|
||||
digests[!slot][i] = digest_final(sha);
|
||||
|
||||
@@ -458,11 +477,11 @@ static void print_icf_sections(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && isec->extra().leader) {
|
||||
if (isec.get() == isec->extra().leader)
|
||||
if (isec && isec->is_alive && isec->leader) {
|
||||
if (isec.get() == isec->leader)
|
||||
leaders.push_back(isec.get());
|
||||
else
|
||||
map.insert({isec->extra().leader, isec.get()});
|
||||
map.insert({isec->leader, isec.get()});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -495,9 +514,8 @@ static void print_icf_sections(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
void icf_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "icf");
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->extras.resize(file->sections.size());
|
||||
if (ctx.objs.empty())
|
||||
return;
|
||||
|
||||
uniquify_cies(ctx);
|
||||
merge_leaf_nodes(ctx);
|
||||
@@ -505,6 +523,11 @@ void icf_sections(Context<E> &ctx) {
|
||||
// Prepare for the propagation rounds.
|
||||
std::vector<InputSection<E> *> sections = gather_sections(ctx);
|
||||
|
||||
// We allocate 3 arrays to store hashes for each vertex.
|
||||
// Index 0 and 1 are used for tree hashes from the previous iteration and the current iteration.
|
||||
// They switch roles every iteration --- see `slot` below.
|
||||
// Index 2 stores the initial, single-vertex hash --- this is combined with hashes from the connected vertices to
|
||||
// form the tree hash described above.
|
||||
std::vector<std::vector<Digest>> digests(3);
|
||||
digests[0] = compute_digests<E>(ctx, sections);
|
||||
digests[1].resize(digests[0].size());
|
||||
@@ -521,6 +544,17 @@ void icf_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "propagate");
|
||||
tbb::affinity_partitioner ap;
|
||||
|
||||
// A cheap test that the graph hasn't converged yet.
|
||||
// The loop after this one uses a strict condition, but it's expensive
|
||||
// as it requires sorting the entire hash collection.
|
||||
//
|
||||
// For nodes that have a cycle in downstream (i.e. recursive
|
||||
// functions and functions that calls recursive functions) will always
|
||||
// change with the iterations. Nodes that doesn't (i.e. non-recursive
|
||||
// functions) will stop changing as soon as the propagation depth reaches
|
||||
// the call tree depth.
|
||||
// Here, we test whether we have reached sufficient depth for the latter,
|
||||
// which is a necessary (but not sufficient) condition for convergence.
|
||||
i64 num_changed = -1;
|
||||
for (;;) {
|
||||
i64 n = propagate<E>(digests, edges, edge_indices, slot, ap);
|
||||
@@ -529,8 +563,12 @@ void icf_sections(Context<E> &ctx) {
|
||||
num_changed = n;
|
||||
}
|
||||
|
||||
// Run the pass until the unique number of hashes stop increasing, at which
|
||||
// point we have achieved convergence (proof omitted for brevity).
|
||||
i64 num_classes = -1;
|
||||
for (;;) {
|
||||
// count_num_classes requires sorting which is O(n log n), so do a little
|
||||
// more work beforehand to amortize that log factor.
|
||||
for (i64 i = 0; i < 10; i++)
|
||||
propagate<E>(digests, edges, edge_indices, slot, ap);
|
||||
|
||||
@@ -558,7 +596,7 @@ void icf_sections(Context<E> &ctx) {
|
||||
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
|
||||
auto it = map->find(digest[i]);
|
||||
assert(it != map->end());
|
||||
sections[i]->extra().leader = it->second;
|
||||
sections[i]->leader = it->second;
|
||||
});
|
||||
|
||||
// Since free'ing the map is slow, postpone it.
|
||||
@@ -575,7 +613,7 @@ void icf_sections(Context<E> &ctx) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file == file) {
|
||||
InputSection<E> *isec = sym->get_input_section();
|
||||
if (isec && isec->extra().leader && isec->extra().leader != isec) {
|
||||
if (isec && isec->leader && isec->leader != isec) {
|
||||
isec->kill();
|
||||
isec->killed_by_icf = true;
|
||||
}
|
||||
@@ -588,10 +626,6 @@ void icf_sections(Context<E> &ctx) {
|
||||
#define INSTANTIATE(E) \
|
||||
template void icf_sections(Context<E> &ctx);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+192
-102
@@ -2,7 +2,10 @@
|
||||
|
||||
#include <cstring>
|
||||
#include <regex>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
@@ -63,6 +66,15 @@ void InputFile<E>::clear_symbols() {
|
||||
}
|
||||
}
|
||||
|
||||
// Find the source filename. It should be listed in symtab as STT_FILE.
|
||||
template <typename E>
|
||||
std::string_view InputFile<E>::get_source_name() const {
|
||||
for (i64 i = 0; i < first_global; i++)
|
||||
if (Symbol<E> *sym = symbols[i]; sym->get_type() == STT_FILE)
|
||||
return sym->name();
|
||||
return "";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E>::ObjectFile(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name, bool is_in_lib)
|
||||
@@ -99,18 +111,18 @@ u32 ObjectFile<E>::read_note_gnu_property(Context<E> &ctx,
|
||||
data = data.substr(align_to(hdr.n_namesz, 4));
|
||||
|
||||
std::string_view desc = data.substr(0, hdr.n_descsz);
|
||||
data = data.substr(align_to(hdr.n_descsz, E::word_size));
|
||||
data = data.substr(align_to(hdr.n_descsz, sizeof(Word<E>)));
|
||||
|
||||
if (hdr.n_type != NT_GNU_PROPERTY_TYPE_0 || name != "GNU")
|
||||
continue;
|
||||
|
||||
while (!desc.empty()) {
|
||||
u32 type = *(u32 *)desc.data();
|
||||
u32 size = *(u32 *)(desc.data() + 4);
|
||||
u32 type = *(ul32 *)desc.data();
|
||||
u32 size = *(ul32 *)(desc.data() + 4);
|
||||
desc = desc.substr(8);
|
||||
if (type == GNU_PROPERTY_X86_FEATURE_1_AND)
|
||||
ret |= *(u32 *)desc.data();
|
||||
desc = desc.substr(align_to(size, E::word_size));
|
||||
ret |= *(ul32 *)desc.data();
|
||||
desc = desc.substr(align_to(size, sizeof(Word<E>)));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@@ -122,7 +134,8 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->elf_sections.size(); i++) {
|
||||
const ElfShdr<E> &shdr = this->elf_sections[i];
|
||||
|
||||
if ((shdr.sh_flags & SHF_EXCLUDE) && !(shdr.sh_flags & SHF_ALLOC))
|
||||
if ((shdr.sh_flags & SHF_EXCLUDE) && !(shdr.sh_flags & SHF_ALLOC) &&
|
||||
shdr.sh_type != SHT_LLVM_ADDRSIG)
|
||||
continue;
|
||||
|
||||
switch (shdr.sh_type) {
|
||||
@@ -131,10 +144,15 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
if (shdr.sh_info >= this->elf_syms.size())
|
||||
Fatal(ctx) << *this << ": invalid symbol index";
|
||||
const ElfSym<E> &sym = this->elf_syms[shdr.sh_info];
|
||||
std::string_view signature = symbol_strtab.data() + sym.st_name;
|
||||
std::string_view signature = this->symbol_strtab.data() + sym.st_name;
|
||||
|
||||
// Ignore a broken comdat group GCC emits for .debug_macros.
|
||||
// https://github.com/rui314/mold/issues/438
|
||||
if (signature.starts_with("wm4."))
|
||||
continue;
|
||||
|
||||
// Get comdat group members.
|
||||
std::span<u32> entries = this->template get_data<u32>(ctx, shdr);
|
||||
std::span<ul32> entries = this->template get_data<ul32>(ctx, shdr);
|
||||
|
||||
if (entries.empty())
|
||||
Fatal(ctx) << *this << ": empty SHT_GROUP";
|
||||
@@ -167,11 +185,14 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
// making the stack area executable is too dangerous. Tell our
|
||||
// users about the difference if that matters.
|
||||
if (name == ".note.GNU-stack") {
|
||||
if (shdr.sh_flags & SHF_EXECINSTR)
|
||||
Warn(ctx) << *this << ": this file may cause a segmentation"
|
||||
" fault because it requires an executable stack. See"
|
||||
" https://github.com/rui314/mold/tree/main/docs/execstack.md"
|
||||
" for more info.";
|
||||
if (shdr.sh_flags & SHF_EXECINSTR) {
|
||||
if (!ctx.arg.z_execstack && !ctx.arg.z_execstack_if_needed)
|
||||
Warn(ctx) << *this << ": this file may cause a segmentation"
|
||||
" fault because it requires an executable stack. See"
|
||||
" https://github.com/rui314/mold/tree/main/docs/execstack.md"
|
||||
" for more info.";
|
||||
needs_executable_stack = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -199,16 +220,20 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
|
||||
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, name, i);
|
||||
|
||||
// Save .llvm_addrsig for --icf=safe.
|
||||
if (shdr.sh_type == SHT_LLVM_ADDRSIG)
|
||||
llvm_addrsig = this->sections[i].get();
|
||||
|
||||
// Save debug sections for --gdb-index.
|
||||
if (ctx.arg.gdb_index) {
|
||||
InputSection<E> *isec = this->sections[i].get();
|
||||
|
||||
if (name == ".debug_info")
|
||||
debug_info = isec;
|
||||
if (name == ".debug_abbrev")
|
||||
debug_abbrev = isec;
|
||||
if (name == ".debug_ranges")
|
||||
debug_ranges = isec;
|
||||
if (name == ".debug_rnglists")
|
||||
debug_rnglists = isec;
|
||||
|
||||
// If --gdb-index is given, contents of .debug_gnu_pubnames and
|
||||
// .debug_gnu_pubtypes are copied to .gdb_index, so keeping them
|
||||
@@ -222,6 +247,16 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
debug_pubtypes = isec;
|
||||
isec->is_alive = false;
|
||||
}
|
||||
|
||||
// .debug_types is similar to .debug_info but contains type info
|
||||
// only. It exists only in DWARF 4, has been removed in DWARF 5 and
|
||||
// neither GCC nor Clang generate it by default
|
||||
// (-fdebug-types-section is needed). As such there is probably
|
||||
// little need to support it.
|
||||
if (name == ".debug_types")
|
||||
Fatal(ctx) << *this << ": mold's --gdb-index is not compatible"
|
||||
" with .debug_types; to fix this error, remove"
|
||||
" -fdebug-types-section and recompile";
|
||||
}
|
||||
|
||||
static Counter counter("regular_sections");
|
||||
@@ -234,7 +269,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
// Attach relocation sections to their target sections.
|
||||
for (i64 i = 0; i < this->elf_sections.size(); i++) {
|
||||
const ElfShdr<E> &shdr = this->elf_sections[i];
|
||||
if (shdr.sh_type != (E::is_rel ? SHT_REL : SHT_RELA))
|
||||
if (shdr.sh_type != (is_rela<E> ? SHT_RELA : SHT_REL))
|
||||
continue;
|
||||
|
||||
if (shdr.sh_info >= sections.size())
|
||||
@@ -299,13 +334,13 @@ void ObjectFile<E>::read_ehframe(Context<E> &ctx, InputSection<E> &isec) {
|
||||
i64 rel_idx = 0;
|
||||
|
||||
for (std::string_view data = contents; !data.empty();) {
|
||||
i64 size = *(u32 *)data.data();
|
||||
i64 size = *(ul32 *)data.data();
|
||||
if (size == 0)
|
||||
break;
|
||||
|
||||
i64 begin_offset = data.data() - contents.data();
|
||||
i64 end_offset = begin_offset + size + 4;
|
||||
i64 id = *(u32 *)(data.data() + 4);
|
||||
i64 id = *(ul32 *)(data.data() + 4);
|
||||
data = data.substr(size + 4);
|
||||
|
||||
i64 rel_begin = rel_idx;
|
||||
@@ -341,7 +376,7 @@ void ObjectFile<E>::read_ehframe(Context<E> &ctx, InputSection<E> &isec) {
|
||||
};
|
||||
|
||||
for (i64 i = fdes_begin; i < fdes.size(); i++) {
|
||||
i64 cie_offset = *(i32 *)(contents.data() + fdes[i].input_offset + 4);
|
||||
i64 cie_offset = *(il32 *)(contents.data() + fdes[i].input_offset + 4);
|
||||
fdes[i].cie_idx = find_cie(fdes[i].input_offset + 4 - cie_offset);
|
||||
}
|
||||
|
||||
@@ -374,7 +409,7 @@ template <typename E>
|
||||
static Symbol<E> *insert_symbol(Context<E> &ctx, const ElfSym<E> &esym,
|
||||
std::string_view key, std::string_view name) {
|
||||
if (esym.is_undef() && name.starts_with("__real_") &&
|
||||
ctx.arg.wrap.count(name.substr(7))) {
|
||||
ctx.arg.wrap.contains(name.substr(7))) {
|
||||
return get_symbol(ctx, key.substr(7), name.substr(7));
|
||||
}
|
||||
|
||||
@@ -408,7 +443,7 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
if (esym.is_common())
|
||||
Fatal(ctx) << *this << ": common local symbol?";
|
||||
|
||||
std::string_view name = symbol_strtab.data() + esym.st_name;
|
||||
std::string_view name = this->symbol_strtab.data() + esym.st_name;
|
||||
if (name.empty() && esym.st_type == STT_SECTION)
|
||||
if (InputSection<E> *sec = get_section(esym))
|
||||
name = sec->name();
|
||||
@@ -437,7 +472,7 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
const ElfSym<E> &esym = this->elf_syms[i];
|
||||
|
||||
// Get a symbol name
|
||||
std::string_view key = symbol_strtab.data() + esym.st_name;
|
||||
std::string_view key = this->symbol_strtab.data() + esym.st_name;
|
||||
std::string_view name = key;
|
||||
|
||||
// Parse symbol version after atsign
|
||||
@@ -464,27 +499,20 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
// We expect them to be sorted, so sort them if necessary.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::sort_relocations(Context<E> &ctx) {
|
||||
if (!std::is_same_v<E, RISCV64>)
|
||||
return;
|
||||
if constexpr (is_riscv<E>) {
|
||||
auto less = [&](const ElfRel<E> &a, const ElfRel<E> &b) {
|
||||
return a.r_offset < b.r_offset;
|
||||
};
|
||||
|
||||
auto less = [&](const ElfRel<E> &a, const ElfRel<E> &b) {
|
||||
return a.r_type != E::R_NONE && b.r_type != E::R_NONE &&
|
||||
a.r_offset < b.r_offset;
|
||||
};
|
||||
for (i64 i = 1; i < sections.size(); i++) {
|
||||
std::unique_ptr<InputSection<E>> &isec = sections[i];
|
||||
if (!isec || !isec->is_alive || !(isec->shdr().sh_flags & SHF_ALLOC))
|
||||
continue;
|
||||
|
||||
sorted_rels.resize(sections.size());
|
||||
|
||||
for (i64 i = 1; i < sections.size(); i++) {
|
||||
std::unique_ptr<InputSection<E>> &isec = sections[i];;
|
||||
if (!isec || !isec->is_alive || !(isec->shdr().sh_flags & SHF_ALLOC))
|
||||
continue;
|
||||
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
if (std::is_sorted(rels.begin(), rels.end(), less))
|
||||
continue;
|
||||
|
||||
sorted_rels[isec->shndx] = {rels.begin(), rels.end()};
|
||||
sort(sorted_rels[isec->shndx], less);
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
if (!std::is_sorted(rels.begin(), rels.end(), less))
|
||||
sort(rels, less);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,16 +552,10 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
sec.shdr().sh_flags);
|
||||
rec->p2align = sec.p2align;
|
||||
|
||||
std::string_view data = sec.contents;
|
||||
|
||||
// If thes section contents are compressed, uncompress them.
|
||||
if (sec.is_compressed()) {
|
||||
u8 *buf = new u8[sec.sh_size];
|
||||
sec.uncompress(ctx, buf);
|
||||
data = {(char *)buf, sec.sh_size};
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
}
|
||||
sec.uncompress(ctx);
|
||||
|
||||
std::string_view data = sec.contents;
|
||||
const char *begin = data.data();
|
||||
u64 entsize = sec.shdr().sh_entsize;
|
||||
HyperLogLog estimator;
|
||||
@@ -728,15 +750,44 @@ void ObjectFile<E>::register_section_pieces(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::mark_addrsig(Context<E> &ctx) {
|
||||
// Parse a .llvm_addrsig section.
|
||||
if (llvm_addrsig) {
|
||||
u8 *cur = (u8 *)llvm_addrsig->contents.data();
|
||||
u8 *end = cur + llvm_addrsig->contents.size();
|
||||
|
||||
while (cur != end) {
|
||||
Symbol<E> &sym = *this->symbols[read_uleb(cur)];
|
||||
if (sym.file == this)
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
isec->address_significant = true;
|
||||
}
|
||||
}
|
||||
|
||||
// We treat a symbol's address as significant if
|
||||
//
|
||||
// 1. we have no address significance information for the symbol, or
|
||||
// 2. the symbol can be referenced from the outside in an address-
|
||||
// significant manner.
|
||||
for (Symbol<E> *sym : this->symbols)
|
||||
if (sym->file == this)
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
if (!llvm_addrsig || sym->is_exported)
|
||||
isec->address_significant = true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
sections.resize(this->elf_sections.size());
|
||||
symtab_sec = this->find_section(SHT_SYMTAB);
|
||||
|
||||
if (symtab_sec) {
|
||||
// In ELF, all local symbols precede global symbols in the symbol table.
|
||||
// sh_info has an index of the first global symbol.
|
||||
this->first_global = symtab_sec->sh_info;
|
||||
this->elf_syms = this->template get_data<ElfSym<E>>(ctx, *symtab_sec);
|
||||
symbol_strtab = this->get_string(ctx, symtab_sec->sh_link);
|
||||
this->symbol_strtab = this->get_string(ctx, symtab_sec->sh_link);
|
||||
}
|
||||
|
||||
initialize_sections(ctx);
|
||||
@@ -925,7 +976,7 @@ void ObjectFile<E>::eliminate_duplicate_comdat_groups() {
|
||||
if (group->owner == this->priority)
|
||||
continue;
|
||||
|
||||
std::span<u32> entries = pair.second;
|
||||
std::span<ul32> entries = pair.second;
|
||||
for (u32 i : entries)
|
||||
if (sections[i])
|
||||
sections[i]->kill();
|
||||
@@ -937,6 +988,18 @@ void ObjectFile<E>::claim_unresolved_symbols(Context<E> &ctx) {
|
||||
if (!this->is_alive)
|
||||
return;
|
||||
|
||||
auto report_undef = [&](Symbol<E> &sym) {
|
||||
std::stringstream ss;
|
||||
if (std::string_view source = this->get_source_name(); !source.empty())
|
||||
ss << ">>> referenced by " << source << "\n";
|
||||
else
|
||||
ss << ">>> referenced by " << *this << "\n";
|
||||
|
||||
typename decltype(ctx.undef_errors)::accessor acc;
|
||||
ctx.undef_errors.insert(acc, {sym.name(), {}});
|
||||
acc->second.push_back(ss.str());
|
||||
};
|
||||
|
||||
for (i64 i = this->first_global; i < this->symbols.size(); i++) {
|
||||
const ElfSym<E> &esym = this->elf_syms[i];
|
||||
Symbol<E> &sym = *this->symbols[i];
|
||||
@@ -949,7 +1012,7 @@ void ObjectFile<E>::claim_unresolved_symbols(Context<E> &ctx) {
|
||||
// imported symbol, it's handled as if no symbols were found.
|
||||
if (sym.file && sym.file->is_dso &&
|
||||
(sym.visibility == STV_PROTECTED || sym.visibility == STV_HIDDEN)) {
|
||||
report_undef(ctx, *this, sym);
|
||||
report_undef(sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -959,7 +1022,7 @@ void ObjectFile<E>::claim_unresolved_symbols(Context<E> &ctx) {
|
||||
|
||||
// If a symbol name is in the form of "foo@version", search for
|
||||
// symbol "foo" and check if the symbol has version "version".
|
||||
std::string_view key = symbol_strtab.data() + esym.st_name;
|
||||
std::string_view key = this->symbol_strtab.data() + esym.st_name;
|
||||
if (i64 pos = key.find('@'); pos != key.npos) {
|
||||
Symbol<E> *sym2 = get_symbol(ctx, key.substr(0, pos));
|
||||
if (sym2->file && sym2->file->is_dso &&
|
||||
@@ -979,7 +1042,7 @@ void ObjectFile<E>::claim_unresolved_symbols(Context<E> &ctx) {
|
||||
};
|
||||
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
|
||||
report_undef(ctx, *this, sym);
|
||||
report_undef(sym);
|
||||
|
||||
// Convert remaining undefined symbols to dynamic symbols.
|
||||
if (ctx.arg.shared) {
|
||||
@@ -1055,18 +1118,22 @@ void ObjectFile<E>::scan_relocations(Context<E> &ctx) {
|
||||
// definition, the tentative definition gets the default initial value 0.
|
||||
//
|
||||
// Tentative definitions are represented as "common symbols" in an object
|
||||
// file. In this function, we allocate spaces in .bss for remaining common
|
||||
// symbols that were not resolved to usual defined symbols in previous
|
||||
// passes.
|
||||
// file. In this function, we allocate spaces in .common or .tls_common
|
||||
// for remaining common symbols that were not resolved to usual defined
|
||||
// symbols in previous passes.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
if (!has_common_symbol)
|
||||
return;
|
||||
|
||||
OutputSection<E> *osec =
|
||||
OutputSection<E> *common =
|
||||
OutputSection<E>::get_instance(ctx, ".common", SHT_NOBITS,
|
||||
SHF_WRITE | SHF_ALLOC);
|
||||
|
||||
OutputSection<E> *tls_common =
|
||||
OutputSection<E>::get_instance(ctx, ".tls_common", SHT_NOBITS,
|
||||
SHF_WRITE | SHF_ALLOC | SHF_TLS);
|
||||
|
||||
for (i64 i = this->first_global; i < this->elf_syms.size(); i++) {
|
||||
if (!this->elf_syms[i].is_common())
|
||||
continue;
|
||||
@@ -1082,17 +1149,20 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
|
||||
elf_sections2.push_back({});
|
||||
ElfShdr<E> &shdr = elf_sections2.back();
|
||||
|
||||
memset(&shdr, 0, sizeof(shdr));
|
||||
shdr.sh_flags = SHF_ALLOC;
|
||||
|
||||
bool is_tls = (sym.get_type() == STT_TLS);
|
||||
shdr.sh_flags = is_tls ? (SHF_ALLOC | SHF_TLS) : SHF_ALLOC;
|
||||
shdr.sh_type = SHT_NOBITS;
|
||||
shdr.sh_size = this->elf_syms[i].st_size;
|
||||
shdr.sh_addralign = this->elf_syms[i].st_value;
|
||||
|
||||
i64 idx = this->elf_sections.size() + elf_sections2.size() - 1;
|
||||
std::unique_ptr<InputSection<E>> isec =
|
||||
std::make_unique<InputSection<E>>(ctx, *this, ".common", idx);
|
||||
isec->output_section = osec;
|
||||
std::make_unique<InputSection<E>>(ctx, *this,
|
||||
is_tls ? ".tls_common" : ".common",
|
||||
idx);
|
||||
isec->output_section = is_tls ? tls_common : common;
|
||||
|
||||
sym.file = this;
|
||||
sym.shndx = idx;
|
||||
@@ -1113,9 +1183,9 @@ static bool should_write_to_local_symtab(Context<E> &ctx, Symbol<E> &sym) {
|
||||
return false;
|
||||
|
||||
// Local symbols are discarded if --discard-local is given or they
|
||||
// are not in a mergeable section. I *believe* we exclude symbols in
|
||||
// mergeable sections because (1) they are too many and (2) they are
|
||||
// merged, so their origins shouldn't matter, but I dont' really
|
||||
// are in a mergeable section. I *believe* we exclude symbols in
|
||||
// mergeable sections because (1) there are too many and (2) they are
|
||||
// merged, so their origins shouldn't matter, but I don't really
|
||||
// know the rationale. Anyway, this is the behavior of the
|
||||
// traditional linkers.
|
||||
if (sym.name().starts_with(".L")) {
|
||||
@@ -1166,56 +1236,48 @@ void ObjectFile<E>::compute_symtab(Context<E> &ctx) {
|
||||
if (sym.file == this && is_alive(sym) &&
|
||||
(!ctx.arg.retain_symbols_file || sym.write_to_symtab)) {
|
||||
this->strtab_size += sym.name().size() + 1;
|
||||
this->num_global_symtab++;
|
||||
// Global symbols can be demoted to local symbols based on visibility,
|
||||
// version scripts etc.
|
||||
if (sym.is_local())
|
||||
this->num_local_symtab++;
|
||||
else
|
||||
this->num_global_symtab++;
|
||||
sym.write_to_symtab = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::write_symtab(Context<E> &ctx) {
|
||||
void ObjectFile<E>::populate_symtab(Context<E> &ctx) {
|
||||
ElfSym<E> *symtab_base = (ElfSym<E> *)(ctx.buf + ctx.symtab->shdr.sh_offset);
|
||||
i64 symtab_idx;
|
||||
|
||||
u8 *strtab_base = ctx.buf + ctx.strtab->shdr.sh_offset;
|
||||
i64 strtab_off = this->strtab_offset;
|
||||
|
||||
auto write_sym = [&](Symbol<E> &sym) {
|
||||
auto write_sym = [&](Symbol<E> &sym, i64 &symtab_idx) {
|
||||
ElfSym<E> &esym = symtab_base[symtab_idx++];
|
||||
|
||||
esym = sym.esym();
|
||||
esym = to_output_esym(ctx, sym);
|
||||
esym.st_name = strtab_off;
|
||||
|
||||
if (sym.get_type() == STT_TLS)
|
||||
esym.st_value = sym.get_addr(ctx) - ctx.tls_begin;
|
||||
else
|
||||
esym.st_value = sym.get_addr(ctx);
|
||||
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
esym.st_shndx = isec->output_section->shndx;
|
||||
else if (sym.shndx < 0)
|
||||
esym.st_shndx = -sym.shndx;
|
||||
else if (esym.is_undef())
|
||||
esym.st_shndx = SHN_UNDEF;
|
||||
else
|
||||
esym.st_shndx = SHN_ABS;
|
||||
|
||||
write_string(strtab_base + strtab_off, sym.name());
|
||||
strtab_off += sym.name().size() + 1;
|
||||
};
|
||||
|
||||
symtab_idx = this->local_symtab_idx;
|
||||
i64 local_symtab_idx = this->local_symtab_idx;
|
||||
i64 global_symtab_idx = this->global_symtab_idx;
|
||||
for (i64 i = 1; i < this->first_global; i++) {
|
||||
Symbol<E> &sym = *this->symbols[i];
|
||||
if (sym.write_to_symtab)
|
||||
write_sym(sym);
|
||||
write_sym(sym, local_symtab_idx);
|
||||
}
|
||||
|
||||
symtab_idx = this->global_symtab_idx;
|
||||
for (i64 i = this->first_global; i < this->elf_syms.size(); i++) {
|
||||
Symbol<E> &sym = *this->symbols[i];
|
||||
if (sym.file == this && sym.write_to_symtab)
|
||||
write_sym(sym);
|
||||
if (sym.file == this && sym.write_to_symtab) {
|
||||
if (sym.is_local())
|
||||
write_sym(sym, local_symtab_idx);
|
||||
else
|
||||
write_sym(sym, global_symtab_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1251,6 +1313,7 @@ SharedFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
template <typename E>
|
||||
SharedFile<E>::SharedFile(Context<E> &ctx, MappedFile<Context<E>> *mf)
|
||||
: InputFile<E>(ctx, mf) {
|
||||
this->is_needed = ctx.as_needed;
|
||||
this->is_alive = !ctx.as_needed;
|
||||
}
|
||||
|
||||
@@ -1259,12 +1322,12 @@ std::string SharedFile<E>::get_soname(Context<E> &ctx) {
|
||||
if (ElfShdr<E> *sec = this->find_section(SHT_DYNAMIC))
|
||||
for (ElfDyn<E> &dyn : this->template get_data<ElfDyn<E>>(ctx, *sec))
|
||||
if (dyn.d_tag == DT_SONAME)
|
||||
return symbol_strtab.data() + dyn.d_val;
|
||||
return this->symbol_strtab.data() + dyn.d_val;
|
||||
|
||||
if (this->mf->given_fullpath)
|
||||
return this->filename;
|
||||
|
||||
return filepath(this->filename).filename();
|
||||
return filepath(this->filename).filename().string();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -1273,7 +1336,7 @@ void SharedFile<E>::parse(Context<E> &ctx) {
|
||||
if (!symtab_sec)
|
||||
return;
|
||||
|
||||
symbol_strtab = this->get_string(ctx, symtab_sec->sh_link);
|
||||
this->symbol_strtab = this->get_string(ctx, symtab_sec->sh_link);
|
||||
soname = get_soname(ctx);
|
||||
version_strings = read_verdef(ctx);
|
||||
|
||||
@@ -1294,7 +1357,7 @@ void SharedFile<E>::parse(Context<E> &ctx) {
|
||||
if (ver == VER_NDX_LOCAL)
|
||||
continue;
|
||||
|
||||
std::string_view name = symbol_strtab.data() + esyms[i].st_name;
|
||||
std::string_view name = this->symbol_strtab.data() + esyms[i].st_name;
|
||||
bool is_hidden = (!vers.empty() && (vers[i] & VERSYM_HIDDEN));
|
||||
|
||||
this->elf_syms2.push_back(esyms[i]);
|
||||
@@ -1316,6 +1379,36 @@ void SharedFile<E>::parse(Context<E> &ctx) {
|
||||
counter += this->elf_syms.size();
|
||||
}
|
||||
|
||||
// Symbol versioning is a GNU extension to the ELF file format. I don't
|
||||
// particularly like the feature as it complicates the semantics of
|
||||
// dynamic linking, but we need to support it anyway because it is
|
||||
// mandatory on glibc-based systems such as most Linux distros.
|
||||
//
|
||||
// Let me explain what symbol versioning is. Symbol versioning is a
|
||||
// mechanism to allow multiple symbols of the same name but of different
|
||||
// versions live together in a shared object file. It's convenient if you
|
||||
// want to make an API-breaking change to some function but want to keep
|
||||
// old programs working with the newer libraries.
|
||||
//
|
||||
// With symbol versioning, dynamic symbols are resolved by (name, version)
|
||||
// tuple instead of just by name. For example, glibc 2.35 defines two
|
||||
// different versions of `posix_spawn`, `posix_spawn` of version
|
||||
// "GLIBC_2.15" and that of version "GLIBC_2.2.5". Any executable that
|
||||
// uses `posix_spawn` is linked either to that of "GLIBC_2.15" or that of
|
||||
// "GLIBC_2.2.5"
|
||||
//
|
||||
// Versions are just stirngs, and no ordering is defined between them.
|
||||
// For example, "GLIBC_2.15" is not considered a newer version of
|
||||
// "GLIBC_2.2.5" or vice versa. They are considered just different.
|
||||
//
|
||||
// If a shared object file has versioned symbols, it contains a parallel
|
||||
// array for the symbol table. Version strings can be found in that
|
||||
// parallel table.
|
||||
//
|
||||
// One version is considered the "default" version for each shared object.
|
||||
// If an undefiend symbol `foo` is resolved to a symbol defined by the
|
||||
// shared object, it's marked so that it'll be resolved to (`foo`, the
|
||||
// default version of the library) at load-time.
|
||||
template <typename E>
|
||||
std::vector<std::string_view> SharedFile<E>::read_verdef(Context<E> &ctx) {
|
||||
std::vector<std::string_view> ret(VER_NDX_LAST_RESERVED + 1);
|
||||
@@ -1430,7 +1523,7 @@ void SharedFile<E>::compute_symtab(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void SharedFile<E>::write_symtab(Context<E> &ctx) {
|
||||
void SharedFile<E>::populate_symtab(Context<E> &ctx) {
|
||||
ElfSym<E> *symtab =
|
||||
(ElfSym<E> *)(ctx.buf + ctx.symtab->shdr.sh_offset) + this->global_symtab_idx;
|
||||
|
||||
@@ -1456,14 +1549,11 @@ void SharedFile<E>::write_symtab(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class InputFile<E>; \
|
||||
template class ObjectFile<E>; \
|
||||
template class SharedFile<E>; \
|
||||
template std::ostream &operator<<(std::ostream &, const InputFile<E> &)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+222
-104
@@ -10,8 +10,8 @@ bool CieRecord<E>::equals(const CieRecord<E> &other) const {
|
||||
if (get_contents() != other.get_contents())
|
||||
return false;
|
||||
|
||||
std::span<ElfRel<E>> x = get_rels();
|
||||
std::span<ElfRel<E>> y = other.get_rels();
|
||||
std::span<const ElfRel<E>> x = get_rels();
|
||||
std::span<const ElfRel<E>> y = other.get_rels();
|
||||
if (x.size() != y.size())
|
||||
return false;
|
||||
|
||||
@@ -36,12 +36,10 @@ InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
std::string_view name, i64 shndx)
|
||||
: file(file), shndx(shndx) {
|
||||
if (shndx < file.elf_sections.size())
|
||||
contents = {(char *)file.mf->data + shdr().sh_offset, shdr().sh_size};
|
||||
|
||||
bool compressed;
|
||||
contents = {(char *)file.mf->data + shdr().sh_offset, (size_t)shdr().sh_size};
|
||||
|
||||
if (name.starts_with(".zdebug")) {
|
||||
sh_size = *(ubig64 *)&contents[4];
|
||||
sh_size = *(ub64 *)&contents[4];
|
||||
p2align = to_p2align(shdr().sh_addralign);
|
||||
compressed = true;
|
||||
} else if (shdr().sh_flags & SHF_COMPRESSED) {
|
||||
@@ -55,28 +53,37 @@ InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
compressed = false;
|
||||
}
|
||||
|
||||
// Uncompress early if the relocation is REL-type so that we can read
|
||||
// addends from section contents. If RELA-type, we don't need to do this
|
||||
// because addends are in relocations.
|
||||
if (compressed && E::is_rel) {
|
||||
u8 *buf = new u8[sh_size];
|
||||
uncompress(ctx, buf);
|
||||
contents = {(char *)buf, sh_size};
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
}
|
||||
// Sections may have been compressed. We usually uncompress them
|
||||
// directly into the mmap'ed output file, but we want to uncompress
|
||||
// early for REL-type ELF types to read relocation addends from
|
||||
// section contents. For RELA-type, we don't need to do this because
|
||||
// addends are in relocations.
|
||||
if constexpr (!is_rela<E>)
|
||||
uncompress(ctx);
|
||||
|
||||
output_section =
|
||||
OutputSection<E>::get_instance(ctx, name, shdr().sh_type, shdr().sh_flags);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool InputSection<E>::is_compressed() {
|
||||
return !E::is_rel &&
|
||||
(name().starts_with(".zdebug") || (shdr().sh_flags & SHF_COMPRESSED));
|
||||
void InputSection<E>::uncompress(Context<E> &ctx) {
|
||||
if (!compressed || uncompressed)
|
||||
return;
|
||||
|
||||
u8 *buf = new u8[sh_size];
|
||||
uncompress_to(ctx, buf);
|
||||
contents = {(char *)buf, sh_size};
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
uncompressed = true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::uncompress(Context<E> &ctx, u8 *buf) {
|
||||
void InputSection<E>::uncompress_to(Context<E> &ctx, u8 *buf) {
|
||||
if (!compressed || uncompressed) {
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
return;
|
||||
}
|
||||
|
||||
auto do_uncompress = [&](std::string_view data) {
|
||||
unsigned long size = sh_size;
|
||||
if (::uncompress(buf, &size, (u8 *)data.data(), data.size()) != Z_OK)
|
||||
@@ -100,47 +107,35 @@ void InputSection<E>::uncompress(Context<E> &ctx, u8 *buf) {
|
||||
|
||||
ElfChdr<E> &hdr = *(ElfChdr<E> *)&contents[0];
|
||||
if (hdr.ch_type != ELFCOMPRESS_ZLIB)
|
||||
Fatal(ctx) << *this << ": unsupported compression type";
|
||||
Fatal(ctx) << *this << ": unsupported compression type: 0x"
|
||||
<< std::hex << hdr.ch_type;
|
||||
do_uncompress(contents.substr(sizeof(ElfChdr<E>)));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
typedef enum { NONE, ERROR, COPYREL, PLT, CPLT, DYNREL, BASEREL } Action;
|
||||
|
||||
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);
|
||||
|
||||
static void
|
||||
dispatch(Context<E> &ctx, InputSection<E> &isec, Action action,
|
||||
Symbol<E> &sym, const ElfRel<E> &rel) {
|
||||
auto error = [&] {
|
||||
std::string msg = sym.is_absolute() ? "-fno-PIC" : "-fPIC";
|
||||
Error(ctx) << *this << ": " << rel << " relocation at offset 0x"
|
||||
Error(ctx) << isec << ": " << 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
|
||||
auto check_textrel = [&] {
|
||||
if (isec.shdr().sh_flags & SHF_WRITE)
|
||||
return;
|
||||
|
||||
if (ctx.arg.z_text) {
|
||||
error();
|
||||
} else if (ctx.arg.warn_textrel) {
|
||||
Warn(ctx) << isec << ": relocation against symbol `" << sym
|
||||
<< "' in read-only section";
|
||||
}
|
||||
ctx.has_textrel = true;
|
||||
};
|
||||
|
||||
switch (action) {
|
||||
@@ -152,69 +147,147 @@ void InputSection<E>::dispatch(Context<E> &ctx, Action table[3][4], i64 i,
|
||||
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 '"
|
||||
} else if (sym.esym().st_visibility == STV_PROTECTED) {
|
||||
Error(ctx) << isec << ": cannot make copy relocation for protected symbol '"
|
||||
<< sym << "', defined in " << *sym.file
|
||||
<< "; recompile with -fPIC";
|
||||
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;
|
||||
case CPLT:
|
||||
sym.flags |= NEEDS_CPLT;
|
||||
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++;
|
||||
check_textrel();
|
||||
isec.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++;
|
||||
check_textrel();
|
||||
if (!isec.is_relr_reloc(ctx, rel))
|
||||
isec.file.num_dynrel++;
|
||||
return;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_rel_action(Context<E> &ctx, const Action table[3][4],
|
||||
Symbol<E> &sym) {
|
||||
auto get_output_type = [&] {
|
||||
if (ctx.arg.shared)
|
||||
return 0;
|
||||
if (ctx.arg.pie)
|
||||
return 1;
|
||||
return 2;
|
||||
};
|
||||
|
||||
auto get_sym_type = [&] {
|
||||
if (sym.is_absolute())
|
||||
return 0;
|
||||
if (!sym.is_imported)
|
||||
return 1;
|
||||
if (sym.get_type() != STT_FUNC)
|
||||
return 2;
|
||||
return 3;
|
||||
};
|
||||
|
||||
return table[get_output_type()][get_sym_type()];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_abs_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
// This is a decision table for absolute relocations that is smaller
|
||||
// than the word size (e.g. R_X86_64_32). Since the dynamic linker
|
||||
// generally does not support dynamic relocations smaller than the
|
||||
// word size, we need to report an error if a relocation cannot be
|
||||
// resolved at link-time.
|
||||
constexpr Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // Shared object
|
||||
{ NONE, ERROR, ERROR, ERROR }, // Position-independent exec
|
||||
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
|
||||
};
|
||||
Action action = get_rel_action(ctx, table, sym);
|
||||
dispatch(ctx, *this, action, sym, rel);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_abs_dyn_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
// This is a decision table for absolute relocations for the word
|
||||
// size data (e.g. R_X86_64_64). Unlike the above, we can emit a
|
||||
// dynamic relocation if we cannot resolve its address at link-time.
|
||||
constexpr Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
|
||||
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
|
||||
};
|
||||
return get_rel_action(ctx, table, sym);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_abs_dyn_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
Action action = get_abs_dyn_action(ctx, sym);
|
||||
dispatch(ctx, *this, action, sym, rel);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_pcrel_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
// This is for PC-relative relocations (e.g. R_X86_64_PC32).
|
||||
// We cannot promote them to dynamic relocations because the dynamic
|
||||
// linker generally does not support PC-relative relocations.
|
||||
constexpr Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, PLT }, // Shared object
|
||||
{ ERROR, NONE, COPYREL, PLT }, // Position-independent exec
|
||||
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
|
||||
};
|
||||
Action action = get_rel_action(ctx, table, sym);
|
||||
dispatch(ctx, *this, action, sym, rel);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::apply_abs_dyn_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, u8 *loc,
|
||||
u64 S, i64 A, u64 P, ElfRel<E> *&dynrel) {
|
||||
switch (get_abs_dyn_action(ctx, sym)) {
|
||||
case COPYREL:
|
||||
case CPLT:
|
||||
case NONE:
|
||||
*(Word<E> *)loc = S + A;
|
||||
break;
|
||||
case BASEREL:
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = ElfRel<E>(P, E::R_RELATIVE, 0, S + A);
|
||||
*(Word<E> *)loc = S + A;
|
||||
break;
|
||||
case DYNREL:
|
||||
*dynrel++ = ElfRel<E>(P, E::R_ABS, sym.get_dynsym_idx(ctx), A);
|
||||
*(Word<E> *)loc = A;
|
||||
break;
|
||||
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>) {
|
||||
if constexpr (is_riscv<E>) {
|
||||
copy_contents_riscv(ctx, buf);
|
||||
} else if (is_compressed()) {
|
||||
uncompress(ctx, buf);
|
||||
} else if (compressed) {
|
||||
uncompress_to(ctx, buf);
|
||||
} else {
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
}
|
||||
@@ -226,33 +299,78 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
}
|
||||
|
||||
// Get the name of a function containin a given offset.
|
||||
template <typename E>
|
||||
void report_undef(Context<E> &ctx, InputFile<E> &file, Symbol<E> &sym) {
|
||||
if (ctx.arg.warn_once && !ctx.warned.insert({(void *)&sym, 1}))
|
||||
return;
|
||||
|
||||
switch (ctx.arg.unresolved_symbols) {
|
||||
case UNRESOLVED_ERROR:
|
||||
Error(ctx) << "undefined symbol: " << file << ": " << sym;
|
||||
break;
|
||||
case UNRESOLVED_WARN:
|
||||
Warn(ctx) << "undefined symbol: " << file << ": " << sym;
|
||||
break;
|
||||
case UNRESOLVED_IGNORE:
|
||||
break;
|
||||
std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) {
|
||||
for (const ElfSym<E> &esym : file.elf_syms) {
|
||||
if (esym.st_shndx == shndx && esym.st_type == STT_FUNC &&
|
||||
esym.st_value <= offset && offset < esym.st_value + esym.st_size) {
|
||||
std::string_view name = file.symbol_strtab.data() + esym.st_name;
|
||||
if (ctx.arg.demangle)
|
||||
return demangle(name);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Record an undefined symbol error which will be displayed all at
|
||||
// once by report_undef_errors().
|
||||
template <typename E>
|
||||
void InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel) {
|
||||
std::stringstream ss;
|
||||
if (std::string_view source = file.get_source_name(); !source.empty())
|
||||
ss << ">>> referenced by " << source << "\n";
|
||||
else
|
||||
ss << ">>> referenced by " << *this << "\n";
|
||||
|
||||
ss << ">>> " << file;
|
||||
if (std::string_view func = get_func_name(ctx, rel.r_offset); !func.empty())
|
||||
ss << ":(" << func << ")";
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
typename decltype(ctx.undef_errors)::accessor acc;
|
||||
ctx.undef_errors.insert(acc, {sym.name(), {}});
|
||||
acc->second.push_back(ss.str());
|
||||
}
|
||||
|
||||
// Report all undefined symbols, grouped by symbol.
|
||||
template <typename E>
|
||||
void report_undef_errors(Context<E> &ctx) {
|
||||
constexpr i64 max_errors = 3;
|
||||
|
||||
for (auto &pair : ctx.undef_errors) {
|
||||
std::string_view sym_name = pair.first;
|
||||
std::span<std::string> errors = pair.second;
|
||||
|
||||
if (ctx.arg.demangle)
|
||||
sym_name = demangle(sym_name);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "undefined symbol: " << sym_name << "\n";
|
||||
|
||||
for (i64 i = 0; i < errors.size() && i < max_errors; i++)
|
||||
ss << errors[i];
|
||||
|
||||
if (errors.size() > max_errors)
|
||||
ss << ">>> referenced " << (errors.size() - max_errors) << " more times\n";
|
||||
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
|
||||
Error(ctx) << ss.str();
|
||||
else if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
|
||||
Warn(ctx) << ss.str();
|
||||
}
|
||||
|
||||
ctx.checkpoint();
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template struct CieRecord<E>; \
|
||||
template class InputSection<E>; \
|
||||
template void report_undef(Context<E> &, InputFile<E> &, Symbol<E> &)
|
||||
template void report_undef_errors(Context<E> &)
|
||||
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+24
-20
@@ -146,8 +146,9 @@ read_output_format(Context<E> &ctx, std::span<std::string_view> tok) {
|
||||
|
||||
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));
|
||||
std::string rel = to_abs_path(path)
|
||||
.lexically_relative(to_abs_path(ctx.arg.sysroot))
|
||||
.string();
|
||||
return rel != "." && !rel.starts_with("../");
|
||||
}
|
||||
|
||||
@@ -225,6 +226,10 @@ void parse_linker_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script(ctx, tok);
|
||||
tok = skip(ctx, tok, "}");
|
||||
} else if (tok.size() > 3 && tok[1] == "=" && tok[3] == ";") {
|
||||
ctx.arg.defsyms.emplace_back(get_symbol(ctx, unquote(tok[0])),
|
||||
get_symbol(ctx, unquote(tok[2])));
|
||||
tok = tok.subspan(4);
|
||||
} else if (tok[0] == ";") {
|
||||
tok = tok.subspan(1);
|
||||
} else {
|
||||
@@ -234,7 +239,7 @@ void parse_linker_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
MachineType 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());
|
||||
@@ -242,11 +247,11 @@ i64 get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
|
||||
if (tok.size() >= 3 && tok[0] == "OUTPUT_FORMAT" && tok[1] == "(") {
|
||||
if (tok[2] == "elf64-x86-64")
|
||||
return EM_X86_64;
|
||||
return MachineType::X86_64;
|
||||
if (tok[2] == "elf32-i386")
|
||||
return EM_386;
|
||||
return MachineType::I386;
|
||||
}
|
||||
return -1;
|
||||
return MachineType::NONE;
|
||||
}
|
||||
|
||||
static bool read_label(std::span<std::string_view> &tok,
|
||||
@@ -267,6 +272,7 @@ template <typename E>
|
||||
static void
|
||||
read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
u16 ver_idx, bool is_cpp) {
|
||||
ctx.version_specified = true;
|
||||
bool is_global = true;
|
||||
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
@@ -351,6 +357,8 @@ void parse_version_script(Context<E> &ctx, std::string path) {
|
||||
template <typename E>
|
||||
void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
bool is_cpp) {
|
||||
ctx.version_specified = true;
|
||||
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
if (tok[0] == "extern") {
|
||||
tok = tok.subspan(1);
|
||||
@@ -395,20 +403,16 @@ void parse_dynamic_list(Context<E> &ctx, std::string path) {
|
||||
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)
|
||||
#define INSTANTIATE(E) \
|
||||
template \
|
||||
void parse_linker_script(Context<E> &, MappedFile<Context<E>> *); \
|
||||
template \
|
||||
MachineType get_script_output_type(Context<E> &, MappedFile<Context<E>> *); \
|
||||
template \
|
||||
void parse_version_script(Context<E> &, std::string); \
|
||||
template \
|
||||
void parse_dynamic_list(Context<E> &, std::string);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+103
-60
@@ -79,13 +79,14 @@
|
||||
// the compiler backend.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../lto.h"
|
||||
#include "lto.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <dlfcn.h>
|
||||
#include <fcntl.h>
|
||||
#include <sstream>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if 0
|
||||
@@ -108,16 +109,33 @@ static std::vector<PluginSymbol> plugin_symbols;
|
||||
static ClaimFileHandler *claim_file_hook;
|
||||
static AllSymbolsReadHandler *all_symbols_read_hook;
|
||||
static CleanupHandler *cleanup_hook;
|
||||
static bool is_gcc_linker_api_v1 = false;
|
||||
|
||||
// Event handlers
|
||||
|
||||
static PluginStatus message(int level, const char *fmt, ...) {
|
||||
template <typename E>
|
||||
static PluginStatus message(PluginLevel level, const char *fmt, ...) {
|
||||
LOG << "message\n";
|
||||
Context<E> &ctx = *gctx<E>;
|
||||
|
||||
char buf[1000];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
fprintf(stderr, "mold: ");
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fprintf(stderr, "\n");
|
||||
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
switch (level) {
|
||||
case LDPL_INFO:
|
||||
SyncOut(ctx) << buf;
|
||||
break;
|
||||
case LDPL_WARNING:
|
||||
Warn(ctx) << buf;
|
||||
break;
|
||||
case LDPL_ERROR:
|
||||
case LDPL_FATAL:
|
||||
Fatal(ctx) << buf;
|
||||
}
|
||||
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
@@ -304,6 +322,44 @@ get_symbols(const void *handle, int nsyms, PluginSymbol *psyms, bool is_v2) {
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
// This function restarts mold itself with `--:lto-pass2` and
|
||||
// `--:ignore-ir-file` flags. We do this as a workaround for the old
|
||||
// linker plugins that do not support the get_symbols_v3 API.
|
||||
//
|
||||
// get_symbols_v1 and get_symbols_v2 don't provide a way to ignore an
|
||||
// object file we previously passed to the linker plugin. So we can't
|
||||
// "unload" object files in archives that we ended up not choosing to
|
||||
// include into the final output.
|
||||
//
|
||||
// As a workaround, we restart the linker with a list of object files
|
||||
// the linker has to ignore, so that it won't read the object files
|
||||
// from archives next time.
|
||||
//
|
||||
// This is an ugly hack and should be removed once GCC adopts the v3 API.
|
||||
template <typename E>
|
||||
static void restart_process(Context<E> &ctx) {
|
||||
std::vector<const char *> args;
|
||||
|
||||
for (std::string_view arg : ctx.cmdline_args)
|
||||
args.push_back(strdup(std::string(arg).c_str()));
|
||||
|
||||
for (std::unique_ptr<ObjectFile<E>> &file : ctx.obj_pool)
|
||||
if (file->is_lto_obj && !file->is_alive)
|
||||
args.push_back(strdup(("--:ignore-ir-file=" +
|
||||
file->mf->get_identifier()).c_str()));
|
||||
|
||||
args.push_back("--:lto-pass2");
|
||||
args.push_back(nullptr);
|
||||
|
||||
std::string self = std::filesystem::read_symlink("/proc/self/exe");
|
||||
|
||||
std::cout << std::flush;
|
||||
std::cerr << std::flush;
|
||||
execv(self.c_str(), (char * const *)args.data());
|
||||
std::cerr << "execv failed: " << errno_string() << "\n";
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
get_symbols_v2(const void *handle, int nsyms, PluginSymbol *psyms) {
|
||||
@@ -359,6 +415,27 @@ get_wrap_symbols(uint64_t *num_symbols, const char ***wrap_symbols) {
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginLinkerAPIVersion
|
||||
get_api_version(const char *plugin_identifier,
|
||||
unsigned plugin_version,
|
||||
int minimal_api_supported,
|
||||
int maximal_api_supported,
|
||||
const char **linker_identifier,
|
||||
const char **linker_version) {
|
||||
if (LAPI_V1 < minimal_api_supported)
|
||||
Fatal(*gctx<E>) << "LTO plugin does not support V0 or V1 API";
|
||||
|
||||
*linker_identifier = "mold";
|
||||
*linker_version = MOLD_VERSION;
|
||||
|
||||
if (LAPI_V1 <= maximal_api_supported) {
|
||||
is_gcc_linker_api_v1 = true;
|
||||
return LAPI_V1;
|
||||
}
|
||||
return LAPI_V0;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void load_plugin(Context<E> &ctx) {
|
||||
assert(phase == 0);
|
||||
@@ -379,7 +456,7 @@ static void load_plugin(Context<E> &ctx) {
|
||||
};
|
||||
|
||||
std::vector<PluginTagValue> tv;
|
||||
tv.emplace_back(LDPT_MESSAGE, message);
|
||||
tv.emplace_back(LDPT_MESSAGE, message<E>);
|
||||
|
||||
if (ctx.arg.shared)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_DYN);
|
||||
@@ -410,6 +487,7 @@ static void load_plugin(Context<E> &ctx) {
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_CONTENTS, get_input_section_contents);
|
||||
tv.emplace_back(LDPT_UPDATE_SECTION_ORDER, update_section_order);
|
||||
tv.emplace_back(LDPT_ALLOW_SECTION_ORDERING, allow_section_ordering);
|
||||
tv.emplace_back(LDPT_ADD_SYMBOLS_V2, add_symbols);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V2, get_symbols_v2<E>);
|
||||
tv.emplace_back(LDPT_ALLOW_UNIQUE_SEGMENT_FOR_SECTIONS,
|
||||
allow_unique_segment_for_sections);
|
||||
@@ -419,9 +497,11 @@ static void load_plugin(Context<E> &ctx) {
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_SIZE, get_input_section_size);
|
||||
tv.emplace_back(LDPT_REGISTER_NEW_INPUT_HOOK, register_new_input_hook<E>);
|
||||
tv.emplace_back(LDPT_GET_WRAP_SYMBOLS, get_wrap_symbols);
|
||||
tv.emplace_back(LDPT_GET_API_VERSION, get_api_version<E>);
|
||||
tv.emplace_back(LDPT_NULL, 0);
|
||||
|
||||
onload(tv.data());
|
||||
PluginStatus status = onload(tv.data());
|
||||
assert(status == LDPS_OK);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -485,15 +565,19 @@ static bool is_llvm(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
// Returns true if a given linker plugin supports the get_symbols_v3 API.
|
||||
// Currently, we simply assume that LLVM supports it and GCC does not.
|
||||
// Any version of LLVM and GCC 12 or newer support it.
|
||||
template <typename E>
|
||||
static bool suppots_v3_api(Context<E> &ctx) {
|
||||
return is_llvm(ctx);
|
||||
return is_gcc_linker_api_v1 || is_llvm(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
LOG << "read_lto_object: " << mf->name << "\n";
|
||||
// V0 API's claim_file is not thread-safe.
|
||||
static std::mutex mu;
|
||||
std::unique_lock lock(mu, std::defer_lock);
|
||||
if (!is_gcc_linker_api_v1)
|
||||
lock.lock();
|
||||
|
||||
if (ctx.arg.plugin.empty())
|
||||
Fatal(ctx) << mf->name << ": don't know how to handle this LTO object file "
|
||||
@@ -539,7 +623,9 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
int claimed = false;
|
||||
claim_file_hook(&file, &claimed);
|
||||
if (!claimed)
|
||||
Fatal(ctx) << mf->name << ": not claimed by the LTO plugin";
|
||||
Fatal(ctx) << mf->name << ": not claimed by the LTO plugin;"
|
||||
<< " please make sure you are using the same compiler of the"
|
||||
<< " same version for all object files";
|
||||
|
||||
// It looks like GCC doesn't need fd after claim_file_hook() while
|
||||
// LLVM needs it and takes the ownership of fd. To prevent "too many
|
||||
@@ -564,44 +650,6 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
// This function restarts mold itself with `--:lto-pass2` and
|
||||
// `--:ignore-ir-file` flags. We do this as a workaround for the old
|
||||
// linker plugins that do not support the get_symbols_v3 API.
|
||||
//
|
||||
// get_symbols_v1 and get_symbols_v2 don't provide a way to ignore an
|
||||
// object file we previously passed to the linker plugin. So we can't
|
||||
// "unload" object files in archives that we ended up not choosing to
|
||||
// include into the final output.
|
||||
//
|
||||
// As a workaround, we restart the linker with a list of object files
|
||||
// the linker has to ignore, so that it won't read the object files
|
||||
// from archives next time.
|
||||
//
|
||||
// This is an ugly hack and should be removed once GCC adopts the v3 API.
|
||||
template <typename E>
|
||||
static void restart_process(Context<E> &ctx) {
|
||||
std::vector<const char *> args;
|
||||
|
||||
for (std::string_view arg : ctx.cmdline_args)
|
||||
args.push_back(strdup(std::string(arg).c_str()));
|
||||
|
||||
for (std::unique_ptr<ObjectFile<E>> &file : ctx.obj_pool)
|
||||
if (file->is_lto_obj && !file->is_alive)
|
||||
args.push_back(strdup(("--:ignore-ir-file=" +
|
||||
file->mf->get_identifier()).c_str()));
|
||||
|
||||
args.push_back("--:lto-pass2");
|
||||
args.push_back(nullptr);
|
||||
|
||||
std::string self = std::filesystem::read_symlink("/proc/self/exe");
|
||||
|
||||
std::cout << std::flush;
|
||||
std::cerr << std::flush;
|
||||
execv(self.c_str(), (char * const *)args.data());
|
||||
std::cerr << "execv failed: " << errno_string() << "\n";
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
// Entry point
|
||||
template <typename E>
|
||||
std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
@@ -614,21 +662,20 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
phase = 2;
|
||||
|
||||
// Set `referenced_by_regular_obj` bit.
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
if (file->is_lto_obj)
|
||||
continue;
|
||||
return;
|
||||
|
||||
for (i64 i = file->first_global; i < file->symbols.size(); i++) {
|
||||
ElfSym<E> &esym = file->elf_syms[i];
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
|
||||
if (esym.is_undef() && sym.file && sym.file != file &&
|
||||
!sym.file->is_dso && ((ObjectFile<E> *)sym.file)->is_lto_obj) {
|
||||
if (sym.file && !sym.file->is_dso &&
|
||||
((ObjectFile<E> *)sym.file)->is_lto_obj) {
|
||||
std::scoped_lock lock(sym.mu);
|
||||
sym.referenced_by_regular_obj = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// all_symbols_read_hook() calls add_input_file() and add_input_library()
|
||||
LOG << "all symbols read\n";
|
||||
@@ -652,10 +699,6 @@ void lto_cleanup(Context<E> &ctx) {
|
||||
template std::vector<ObjectFile<E> *> do_lto(Context<E> &); \
|
||||
template void lto_cleanup(Context<E> &)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "mold.h"
|
||||
#include "lto.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
Fatal(ctx) << "LTO is not supported on Windows";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void lto_cleanup(Context<E> &ctx) {}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template ObjectFile<E> * \
|
||||
read_lto_object(Context<E> &, MappedFile<Context<E>> *); \
|
||||
template std::vector<ObjectFile<E> *> do_lto(Context<E> &); \
|
||||
template void lto_cleanup(Context<E> &)
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
|
||||
#endif
|
||||
+12
-6
@@ -44,6 +44,7 @@ enum PluginTag {
|
||||
LDPT_REGISTER_NEW_INPUT_HOOK,
|
||||
LDPT_GET_WRAP_SYMBOLS,
|
||||
LDPT_ADD_SYMBOLS_V2,
|
||||
LDPT_GET_API_VERSION,
|
||||
};
|
||||
|
||||
enum PluginApiVersion {
|
||||
@@ -72,15 +73,15 @@ enum PluginOutputFileType {
|
||||
|
||||
struct PluginInputFile {
|
||||
const char *name;
|
||||
int fd;
|
||||
off_t offset;
|
||||
off_t filesize;
|
||||
int32_t fd;
|
||||
uint64_t offset;
|
||||
uint64_t filesize;
|
||||
void *handle;
|
||||
};
|
||||
|
||||
struct PluginSection {
|
||||
const void *handle;
|
||||
int shndx;
|
||||
uint32_t shndx;
|
||||
};
|
||||
|
||||
struct PluginSymbol {
|
||||
@@ -89,10 +90,10 @@ struct PluginSymbol {
|
||||
char def;
|
||||
char symbol_type;
|
||||
char section_kind;
|
||||
int visibility;
|
||||
int32_t visibility;
|
||||
uint64_t size;
|
||||
char *comdat_key;
|
||||
int resolution;
|
||||
int32_t resolution;
|
||||
};
|
||||
|
||||
enum PluginSymbolKind {
|
||||
@@ -141,6 +142,11 @@ enum PluginLevel {
|
||||
LDPL_FATAL,
|
||||
};
|
||||
|
||||
enum PluginLinkerAPIVersion {
|
||||
LAPI_V0 = 0,
|
||||
LAPI_V1,
|
||||
};
|
||||
|
||||
typedef PluginStatus OnloadFn(PluginTagValue *tv);
|
||||
typedef PluginStatus ClaimFileHandler(const PluginInputFile *, int *);
|
||||
typedef PluginStatus AllSymbolsReadHandler();
|
||||
+184
-204
@@ -1,6 +1,7 @@
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
#include "../cmdline.h"
|
||||
#include "../output-file.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
@@ -11,20 +12,78 @@
|
||||
#include <sys/types.h>
|
||||
#include <tbb/global_control.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <unistd.h>
|
||||
#include <unordered_set>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <direct.h>
|
||||
# define _chdir chdir
|
||||
#else
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Read the beginning of a given file and returns its machine type
|
||||
// (e.g. EM_X86_64 or EM_386).
|
||||
template <typename E>
|
||||
static MachineType get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
auto get_elf_type = [](u8 *buf) {
|
||||
switch (ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)buf; ehdr.e_machine) {
|
||||
case EM_386:
|
||||
return MachineType::I386;
|
||||
case EM_X86_64:
|
||||
return MachineType::X86_64;
|
||||
case EM_ARM:
|
||||
return MachineType::ARM32;
|
||||
case EM_AARCH64:
|
||||
return MachineType::ARM64;
|
||||
case EM_RISCV:
|
||||
return (ehdr.e_ident[EI_CLASS] == ELFCLASS64)
|
||||
? MachineType::RISCV64 : MachineType::RISCV32;
|
||||
default:
|
||||
return MachineType::NONE;
|
||||
}
|
||||
};
|
||||
|
||||
switch (get_file_type(mf)) {
|
||||
case FileType::ELF_OBJ:
|
||||
case FileType::ELF_DSO:
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
return get_elf_type(mf->data);
|
||||
case FileType::AR:
|
||||
for (MappedFile<Context<E>> *child : read_fat_archive_members(ctx, mf))
|
||||
if (get_file_type(child) == FileType::ELF_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return MachineType::NONE;
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_thin_archive_members(ctx, mf))
|
||||
if (get_file_type(child) == FileType::ELF_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return MachineType::NONE;
|
||||
case FileType::TEXT:
|
||||
return get_script_output_type(ctx, mf);
|
||||
default:
|
||||
return MachineType::NONE;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void
|
||||
check_file_compatibility(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
MachineType mt = get_machine_type(ctx, mf);
|
||||
if (mt != ctx.arg.emulation)
|
||||
Fatal(ctx) << mf->name << ": incompatible file type: "
|
||||
<< ctx.arg.emulation << " is expected but got " << mt;
|
||||
}
|
||||
|
||||
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++;
|
||||
|
||||
check_file_compatibility(ctx, mf);
|
||||
|
||||
bool in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
|
||||
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mf, archive_name, in_lib);
|
||||
file->priority = ctx.file_priority++;
|
||||
@@ -41,10 +100,11 @@ static ObjectFile<E> *new_lto_obj(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
count++;
|
||||
|
||||
if (ctx.arg.ignore_ir_file.count(mf->get_identifier()))
|
||||
return new ObjectFile<E>;
|
||||
return nullptr;
|
||||
|
||||
ObjectFile<E> *file = read_lto_object(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
file->archive_name = archive_name;
|
||||
file->is_in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
|
||||
file->is_alive = !file->is_in_lib;
|
||||
ctx.has_lto_object = true;
|
||||
@@ -56,8 +116,7 @@ static ObjectFile<E> *new_lto_obj(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
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;
|
||||
check_file_compatibility(ctx, mf);
|
||||
|
||||
SharedFile<E> *file = SharedFile<E>::create(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
@@ -90,7 +149,8 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
break;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
ctx.objs.push_back(new_lto_obj(ctx, child, mf->name));
|
||||
if (ObjectFile<E> *file = new_lto_obj(ctx, child, mf->name))
|
||||
ctx.objs.push_back(file);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -103,47 +163,22 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
return;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
ctx.objs.push_back(new_lto_obj(ctx, mf, ""));
|
||||
if (ObjectFile<E> *file = new_lto_obj(ctx, mf, ""))
|
||||
ctx.objs.push_back(file);
|
||||
return;
|
||||
default:
|
||||
Fatal(ctx) << mf->name << ": unknown file type: " << type;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
Fatal(ctx) << mf->name << ": unknown file type";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64
|
||||
deduce_machine_type(Context<E> &ctx, std::span<std::string_view> args) {
|
||||
static MachineType
|
||||
deduce_machine_type(Context<E> &ctx, std::span<std::string> args) {
|
||||
for (std::string_view arg : args)
|
||||
if (!arg.starts_with('-'))
|
||||
if (auto *mf = MappedFile<Context<E>>::open(ctx, std::string(arg)))
|
||||
if (i64 type = get_machine_type(ctx, mf); type != -1)
|
||||
return type;
|
||||
if (MachineType ty = get_machine_type(ctx, mf); ty != MachineType::NONE)
|
||||
return ty;
|
||||
Fatal(ctx) << "-m option is missing";
|
||||
}
|
||||
|
||||
@@ -153,10 +188,10 @@ MappedFile<Context<E>> *open_library(Context<E> &ctx, std::string path) {
|
||||
if (!mf)
|
||||
return nullptr;
|
||||
|
||||
i64 type = get_machine_type(ctx, mf);
|
||||
if (type == -1 || type == E::e_machine)
|
||||
MachineType ty = get_machine_type(ctx, mf);
|
||||
if (ty == MachineType::NONE || ty == E::machine_type)
|
||||
return mf;
|
||||
Warn(ctx) << path << ": skipping incompatible file " << (int)type
|
||||
Warn(ctx) << path << ": skipping incompatible file " << (int)ty
|
||||
<< " " << (int)E::e_machine;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -184,51 +219,59 @@ MappedFile<Context<E>> *find_library(Context<E> &ctx, std::string name) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_input_files(Context<E> &ctx, std::span<std::string_view> args) {
|
||||
static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
Timer t(ctx, "read_input_files");
|
||||
|
||||
std::vector<std::tuple<bool, bool, bool, bool>> state;
|
||||
ctx.is_static = ctx.arg.is_static;
|
||||
|
||||
while (!args.empty()) {
|
||||
std::string_view arg;
|
||||
std::string_view arg = args[0];
|
||||
args = args.subspan(1);
|
||||
|
||||
if (read_flag(args, "as-needed")) {
|
||||
if (arg == "--as-needed") {
|
||||
ctx.as_needed = true;
|
||||
} else if (read_flag(args, "no-as-needed")) {
|
||||
} else if (arg == "--no-as-needed") {
|
||||
ctx.as_needed = false;
|
||||
} else if (read_flag(args, "whole-archive")) {
|
||||
} else if (arg == "--whole-archive") {
|
||||
ctx.whole_archive = true;
|
||||
} else if (read_flag(args, "no-whole-archive")) {
|
||||
} else if (arg == "--no-whole-archive") {
|
||||
ctx.whole_archive = false;
|
||||
} else if (read_flag(args, "Bstatic")) {
|
||||
} else if (arg == "--Bstatic") {
|
||||
ctx.is_static = true;
|
||||
} else if (read_flag(args, "Bdynamic")) {
|
||||
} else if (arg == "--Bdynamic") {
|
||||
ctx.is_static = false;
|
||||
} else if (read_flag(args, "start-lib")) {
|
||||
} else if (arg == "--start-lib") {
|
||||
ctx.in_lib = true;
|
||||
} else if (read_flag(args, "end-lib")) {
|
||||
} else if (arg == "--end-lib") {
|
||||
ctx.in_lib = false;
|
||||
} else if (read_arg(ctx, args, arg, "version-script")) {
|
||||
} else if (remove_prefix(arg, "--version-script=")) {
|
||||
parse_version_script(ctx, std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "dynamic-list")) {
|
||||
} else if (remove_prefix(arg, "--dynamic-list=")) {
|
||||
parse_dynamic_list(ctx, std::string(arg));
|
||||
} else if (read_flag(args, "push-state")) {
|
||||
} else if (remove_prefix(arg, "--export-dynamic-symbol=")) {
|
||||
if (arg == "*")
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.version_patterns.push_back({arg, VER_NDX_GLOBAL, false});
|
||||
ctx.version_specified = true;
|
||||
} else if (remove_prefix(arg, "--export-dynamic-symbol-list=")) {
|
||||
parse_dynamic_list(ctx, std::string(arg));
|
||||
} else if (arg == "--push-state") {
|
||||
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static,
|
||||
ctx.in_lib});
|
||||
} else if (read_flag(args, "pop-state")) {
|
||||
} else if (arg == "--pop-state") {
|
||||
if (state.empty())
|
||||
Fatal(ctx) << "no state pushed before popping";
|
||||
std::tie(ctx.as_needed, ctx.whole_archive, ctx.is_static, ctx.in_lib) =
|
||||
state.back();
|
||||
state.pop_back();
|
||||
} else if (read_arg(ctx, args, arg, "l")) {
|
||||
} else if (remove_prefix(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);
|
||||
read_file(ctx, MappedFile<Context<E>>::must_open(ctx, std::string(arg)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,56 +281,6 @@ static void read_input_files(Context<E> &ctx, std::span<std::string_view> args)
|
||||
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) {
|
||||
@@ -342,7 +335,7 @@ static void show_stats(Context<E> &ctx) {
|
||||
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>) {
|
||||
if constexpr (needs_thunk<E>) {
|
||||
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)
|
||||
@@ -355,62 +348,49 @@ static void show_stats(Context<E> &ctx) {
|
||||
sec->print_stats(ctx);
|
||||
}
|
||||
|
||||
static i64 get_default_thread_count() {
|
||||
// mold doesn't scale well above 32 threads.
|
||||
int n = tbb::global_control::active_value(
|
||||
tbb::global_control::max_allowed_parallelism);
|
||||
return std::min(n, 32);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static int elf_main(int argc, char **argv) {
|
||||
Context<E> ctx;
|
||||
|
||||
// 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);
|
||||
if (argc >= 2 && (argv[1] == "-run"sv || argv[1] == "--run"sv)) {
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
Fatal(ctx) << ": -run is supported only on Unix";
|
||||
#endif
|
||||
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);
|
||||
std::vector<std::string> file_args = parse_nonpositional_args(ctx);
|
||||
|
||||
// If no -m option is given, deduce it from input files.
|
||||
if (ctx.arg.emulation == -1)
|
||||
if (ctx.arg.emulation == MachineType::NONE)
|
||||
ctx.arg.emulation = deduce_machine_type(ctx, file_args);
|
||||
|
||||
// Redo if -m is not x86-64.
|
||||
if (ctx.arg.emulation != E::e_machine) {
|
||||
if (ctx.arg.emulation != E::machine_type) {
|
||||
#ifndef MOLD_DEBUG_X86_64_ONLY
|
||||
switch (ctx.arg.emulation) {
|
||||
case EM_386:
|
||||
case MachineType::I386:
|
||||
return elf_main<I386>(argc, argv);
|
||||
case EM_AARCH64:
|
||||
case MachineType::ARM64:
|
||||
return elf_main<ARM64>(argc, argv);
|
||||
case EM_ARM:
|
||||
case MachineType::ARM32:
|
||||
return elf_main<ARM32>(argc, argv);
|
||||
case EM_RISCV:
|
||||
case MachineType::RISCV64:
|
||||
return elf_main<RISCV64>(argc, argv);
|
||||
case MachineType::RISCV32:
|
||||
return elf_main<RISCV32>(argc, argv);
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
#endif
|
||||
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())
|
||||
@@ -418,6 +398,22 @@ static int elf_main(int argc, char **argv) {
|
||||
Fatal(ctx) << "chdir failed: " << ctx.arg.directory
|
||||
<< ": " << errno_string();
|
||||
|
||||
if (ctx.arg.relocatable) {
|
||||
combine_objects(ctx, file_args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fork a subprocess unless --no-fork is given.
|
||||
std::function<void()> on_complete;
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
if (ctx.arg.fork)
|
||||
on_complete = fork_child();
|
||||
#endif
|
||||
|
||||
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
|
||||
ctx.arg.thread_count);
|
||||
|
||||
// Handle --wrap options if any.
|
||||
for (std::string_view name : ctx.arg.wrap)
|
||||
get_symbol(ctx, name)->wrap = true;
|
||||
@@ -427,31 +423,12 @@ static int elf_main(int argc, char **argv) {
|
||||
for (std::string_view name : *ctx.arg.retain_symbols_file)
|
||||
get_symbol(ctx, name)->write_to_symtab = true;
|
||||
|
||||
// Preload input files
|
||||
std::function<void()> on_complete;
|
||||
std::function<void()> wait_for_client;
|
||||
|
||||
if (ctx.arg.preload)
|
||||
daemonize(ctx, &wait_for_client, &on_complete);
|
||||
else if (ctx.arg.fork)
|
||||
on_complete = fork_child();
|
||||
|
||||
for (std::string_view arg : ctx.arg.trace_symbol)
|
||||
get_symbol(ctx, arg)->traced = true;
|
||||
|
||||
// Parse input files
|
||||
read_input_files(ctx, file_args);
|
||||
|
||||
if (ctx.arg.preload) {
|
||||
wait_for_client();
|
||||
if (!reload_input_files(ctx)) {
|
||||
std::vector<char *> args(argv, argv + argc);
|
||||
args.push_back((char *)"--no-preload");
|
||||
args.push_back(nullptr);
|
||||
return elf_main<E>(argc + 1, args.data());
|
||||
}
|
||||
}
|
||||
|
||||
// Uniquify shared object files by soname
|
||||
{
|
||||
std::unordered_set<std::string_view> seen;
|
||||
@@ -466,6 +443,9 @@ static int elf_main(int argc, char **argv) {
|
||||
// Apply -exclude-libs
|
||||
apply_exclude_libs(ctx);
|
||||
|
||||
// Create a dummy file containing linker-synthesized symbols.
|
||||
create_internal_file(ctx);
|
||||
|
||||
// Resolve symbols and fix the set of object files that are
|
||||
// included to the final output.
|
||||
resolve_symbols(ctx);
|
||||
@@ -488,6 +468,10 @@ static int elf_main(int argc, char **argv) {
|
||||
// Set is_import and is_export bits for each symbol.
|
||||
compute_import_export(ctx);
|
||||
|
||||
// Read address-significant section information.
|
||||
if (ctx.arg.icf && !ctx.arg.icf_all)
|
||||
mark_addrsig(ctx);
|
||||
|
||||
// Garbage-collect unreachable sections.
|
||||
if (ctx.arg.gc_sections)
|
||||
gc_sections(ctx);
|
||||
@@ -509,10 +493,8 @@ static int elf_main(int argc, char **argv) {
|
||||
// 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);
|
||||
// Add synthetic symbols such as __ehdr_start or __end.
|
||||
add_synthetic_symbols(ctx);
|
||||
|
||||
// Beyond this point, no new files will be added to ctx.objs
|
||||
// or ctx.dsos.
|
||||
@@ -521,6 +503,12 @@ static int elf_main(int argc, char **argv) {
|
||||
if (ctx.arg.z_cet_report != CET_REPORT_NONE)
|
||||
check_cet_errors(ctx);
|
||||
|
||||
// Handle `-z execstack-if-needed`.
|
||||
if (ctx.arg.z_execstack_if_needed)
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->needs_executable_stack)
|
||||
ctx.arg.z_execstack = true;
|
||||
|
||||
// If we are linking a .so file, remaining undefined symbols does
|
||||
// not cause a linker error. Instead, they are treated as if they
|
||||
// were imported symbols.
|
||||
@@ -532,6 +520,12 @@ static int elf_main(int argc, char **argv) {
|
||||
|
||||
// Beyond this point, no new symbols will be added to the result.
|
||||
|
||||
// Handle --print-dependencies
|
||||
if (ctx.arg.print_dependencies == 1)
|
||||
print_dependencies(ctx);
|
||||
else if (ctx.arg.print_dependencies == 2)
|
||||
print_dependencies_full(ctx);
|
||||
|
||||
// Handle -repro
|
||||
if (ctx.arg.repro)
|
||||
write_repro_file(ctx);
|
||||
@@ -557,16 +551,6 @@ static int elf_main(int argc, char **argv) {
|
||||
if (ctx.arg.shuffle_sections != SHUFFLE_SECTIONS_NONE)
|
||||
shuffle_sections(ctx);
|
||||
|
||||
// Compute sizes of output sections while assigning offsets
|
||||
// within an output section to input sections.
|
||||
compute_section_sizes(ctx);
|
||||
|
||||
// Sort sections by section attributes so that we'll have to
|
||||
// create as few segments as possible.
|
||||
sort(ctx.chunks, [&](Chunk<E> *a, Chunk<E> *b) {
|
||||
return get_section_rank(ctx, a) < get_section_rank(ctx, b);
|
||||
});
|
||||
|
||||
// Copy string referred by .dynamic to .dynstr.
|
||||
for (SharedFile<E> *file : ctx.dsos)
|
||||
ctx.dynstr->add_string(file->soname);
|
||||
@@ -583,6 +567,16 @@ static int elf_main(int argc, char **argv) {
|
||||
// .got.plt, .dynsym, .dynstr, etc.
|
||||
scan_rels(ctx);
|
||||
|
||||
// Compute sizes of output sections while assigning offsets
|
||||
// within an output section to input sections.
|
||||
compute_section_sizes(ctx);
|
||||
|
||||
// Sort sections by section attributes so that we'll have to
|
||||
// create as few segments as possible.
|
||||
sort(ctx.chunks, [&](Chunk<E> *a, Chunk<E> *b) {
|
||||
return get_section_rank(ctx, a) < get_section_rank(ctx, b);
|
||||
});
|
||||
|
||||
// If --packed_dyn_relocs=relr was given, base relocations are stored
|
||||
// to a .relr.dyn section in a compressed form. Construct a compressed
|
||||
// relocations now so that we can fix section sizes and file layout.
|
||||
@@ -594,6 +588,10 @@ static int elf_main(int argc, char **argv) {
|
||||
// be added to .dynsym.
|
||||
ctx.dynsym->finalize(ctx);
|
||||
|
||||
// Print reports about undefined symbols, if needed.
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
|
||||
report_undef_errors(ctx);
|
||||
|
||||
// Fill .gnu.version_d section contents.
|
||||
if (ctx.verdef)
|
||||
ctx.verdef->construct(ctx);
|
||||
@@ -624,12 +622,12 @@ static int elf_main(int argc, char **argv) {
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
std::erase_if(ctx.chunks, [](Chunk<E> *chunk) {
|
||||
return !chunk->is_output_section() && chunk->shdr.sh_size == 0;
|
||||
return chunk->kind() != OUTPUT_SECTION && chunk->shdr.sh_size == 0;
|
||||
});
|
||||
|
||||
// Set section indices.
|
||||
for (i64 i = 0, shndx = 1; i < ctx.chunks.size(); i++)
|
||||
if (!ctx.chunks[i]->is_header())
|
||||
if (ctx.chunks[i]->kind() != HEADER)
|
||||
ctx.chunks[i]->shndx = shndx++;
|
||||
|
||||
// Some types of section header refer other section by index.
|
||||
@@ -640,17 +638,11 @@ static int elf_main(int argc, char **argv) {
|
||||
// Assign offsets to output sections
|
||||
i64 filesize = set_osec_offsets(ctx);
|
||||
|
||||
// On ARM64, we may need to create so-called "range extension thunks"
|
||||
// to extend branch instructions reach, as they can jump only to
|
||||
// ±128 MiB.
|
||||
if constexpr (std::is_same_v<E, ARM64>)
|
||||
filesize = create_range_extension_thunks(ctx);
|
||||
|
||||
// On RISC-V, branches are encode using multiple instructions so
|
||||
// that they can jump to anywhere in ±2 GiB by default. They may
|
||||
// be replaced with shorter instruction sequences if destinations
|
||||
// are close enough. Do this optimization.
|
||||
if constexpr (std::is_same_v<E, RISCV64>)
|
||||
if constexpr (is_riscv<E>)
|
||||
filesize = riscv_resize_sections(ctx);
|
||||
|
||||
// Fix linker-synthesized symbol addresses.
|
||||
@@ -669,7 +661,8 @@ static int elf_main(int argc, char **argv) {
|
||||
t_before_copy.stop();
|
||||
|
||||
// Create an output file
|
||||
ctx.output_file = OutputFile<E>::open(ctx, ctx.arg.output, filesize, 0777);
|
||||
ctx.output_file =
|
||||
OutputFile<Context<E>>::open(ctx, ctx.arg.output, filesize, 0777);
|
||||
ctx.buf = ctx.output_file->buf;
|
||||
|
||||
Timer t_copy(ctx, "copy");
|
||||
@@ -685,12 +678,9 @@ static int elf_main(int argc, char **argv) {
|
||||
chunk->copy_buf(ctx);
|
||||
});
|
||||
|
||||
ctx.checkpoint();
|
||||
report_undef_errors(ctx);
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<E, ARM64>)
|
||||
write_thunks(ctx);
|
||||
|
||||
if constexpr (std::is_same_v<E, ARM32>)
|
||||
sort_arm_exidx(ctx);
|
||||
|
||||
@@ -707,12 +697,11 @@ static int elf_main(int argc, char **argv) {
|
||||
// Zero-clear paddings between sections
|
||||
clear_padding(ctx);
|
||||
|
||||
if (ctx.buildid) {
|
||||
Timer t(ctx, "build_id");
|
||||
if (ctx.buildid)
|
||||
ctx.buildid->write_buildid(ctx);
|
||||
}
|
||||
|
||||
t_copy.stop();
|
||||
ctx.checkpoint();
|
||||
|
||||
// Close the output file. This is the end of the linker's main job.
|
||||
ctx.output_file->close(ctx);
|
||||
@@ -721,12 +710,6 @@ static int elf_main(int argc, char **argv) {
|
||||
if (!ctx.arg.dependency_file.empty())
|
||||
write_dependency_file(ctx);
|
||||
|
||||
// Handle --print-dependencies
|
||||
if (ctx.arg.print_dependencies == 1)
|
||||
print_dependencies(ctx);
|
||||
else if (ctx.arg.print_dependencies == 2)
|
||||
print_dependencies_full(ctx);
|
||||
|
||||
if (ctx.has_lto_object)
|
||||
lto_cleanup(ctx);
|
||||
|
||||
@@ -753,6 +736,7 @@ static int elf_main(int argc, char **argv) {
|
||||
|
||||
for (std::function<void()> &fn : ctx.on_exit)
|
||||
fn();
|
||||
ctx.checkpoint();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -763,10 +747,6 @@ int main(int argc, char **argv) {
|
||||
#define INSTANTIATE(E) \
|
||||
template void read_file(Context<E> &, MappedFile<Context<E>> *);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+5
-6
@@ -40,6 +40,9 @@ static Map<E> get_map(Context<E> &ctx) {
|
||||
}
|
||||
});
|
||||
|
||||
if (map.size() <= 1)
|
||||
return map;
|
||||
|
||||
tbb::parallel_for(map.range(), [](const typename Map<E>::range_type &range) {
|
||||
for (auto it = range.begin(); it != range.end(); it++) {
|
||||
std::vector<Symbol<E> *> &vec = it->second;
|
||||
@@ -72,7 +75,7 @@ void print_map(Context<E> &ctx) {
|
||||
<< std::setw(6) << (u64)osec->shdr.sh_addralign
|
||||
<< " " << osec->name << "\n";
|
||||
|
||||
if (!osec->is_output_section())
|
||||
if (osec->kind() != OUTPUT_SECTION)
|
||||
continue;
|
||||
|
||||
std::span<InputSection<E> *> members = ((OutputSection<E> *)osec)->members;
|
||||
@@ -109,10 +112,6 @@ void print_map(Context<E> &ctx) {
|
||||
#define INSTANTIATE(E) \
|
||||
template void print_map(Context<E> &ctx);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+31
-26
@@ -1,5 +1,8 @@
|
||||
#define _GNU_SOURCE 1
|
||||
|
||||
#if !defined(__OpenBSD__) && !defined(__FreeBSD__)
|
||||
# include <alloca.h>
|
||||
#endif
|
||||
#include <dlfcn.h>
|
||||
#include <spawn.h>
|
||||
#include <stdarg.h>
|
||||
@@ -31,13 +34,25 @@ static void debug_print(char *fmt, ...) {
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static void get_args(va_list *ap, int argc, char **argv) {
|
||||
for (int i = 1; i < argc - 1; i++) {
|
||||
char *arg = va_arg(*ap, char *);
|
||||
if (!arg)
|
||||
break;
|
||||
argv[i] = arg;
|
||||
}
|
||||
static int count_args(va_list *ap) {
|
||||
va_list aq;
|
||||
va_copy(aq, *ap);
|
||||
|
||||
int i = 0;
|
||||
while (va_arg(aq, char *))
|
||||
i++;
|
||||
va_end(aq);
|
||||
return i;
|
||||
}
|
||||
|
||||
static void copy_args(char **argv, const char *arg0, va_list *ap) {
|
||||
int i = 1;
|
||||
char *arg;
|
||||
while ((arg = va_arg(*ap, char *)))
|
||||
argv[i++] = arg;
|
||||
|
||||
((const char **)argv)[0] = arg0;
|
||||
((const char **)argv)[i] = NULL;
|
||||
}
|
||||
|
||||
static bool is_ld(const char *path) {
|
||||
@@ -52,10 +67,8 @@ static bool is_ld(const char *path) {
|
||||
int execvpe(const char *file, char *const *argv, char *const *envp) {
|
||||
debug_print("execvpe %s\n", file);
|
||||
|
||||
if (!strcmp(file, "ld") || is_ld(file)) {
|
||||
if (!strcmp(file, "ld") || is_ld(file))
|
||||
file = get_mold_path();
|
||||
((const char **)argv)[0] = file;
|
||||
}
|
||||
|
||||
for (int i = 0; envp[i]; i++)
|
||||
putenv(envp[i]);
|
||||
@@ -66,12 +79,8 @@ int execvpe(const char *file, char *const *argv, char *const *envp) {
|
||||
|
||||
int execve(const char *path, char *const *argv, char *const *envp) {
|
||||
debug_print("execve %s\n", path);
|
||||
|
||||
if (is_ld(path)) {
|
||||
if (is_ld(path))
|
||||
path = get_mold_path();
|
||||
((const char **)argv)[0] = path;
|
||||
}
|
||||
|
||||
typeof(execve) *real = dlsym(RTLD_NEXT, "execve");
|
||||
return real(path, argv, envp);
|
||||
}
|
||||
@@ -79,8 +88,8 @@ int execve(const char *path, char *const *argv, char *const *envp) {
|
||||
int execl(const char *path, const char *arg0, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, arg0);
|
||||
char *argv[4096] = {(char *)arg0};
|
||||
get_args(&ap, 4096, argv);
|
||||
char **argv = alloca((count_args(&ap) + 2) * sizeof(char *));
|
||||
copy_args(argv, arg0, &ap);
|
||||
va_end(ap);
|
||||
return execve(path, argv, environ);
|
||||
}
|
||||
@@ -88,8 +97,8 @@ int execl(const char *path, const char *arg0, ...) {
|
||||
int execlp(const char *file, const char *arg0, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, arg0);
|
||||
char *argv[4096] = {(char *)arg0};
|
||||
get_args(&ap, 4096, argv);
|
||||
char **argv = alloca((count_args(&ap) + 2) * sizeof(char *));
|
||||
copy_args(argv, arg0, &ap);
|
||||
va_end(ap);
|
||||
return execvpe(file, argv, environ);
|
||||
}
|
||||
@@ -97,8 +106,8 @@ int execlp(const char *file, const char *arg0, ...) {
|
||||
int execle(const char *path, const char *arg0, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, arg0);
|
||||
char *argv[4096] = {(char *)arg0};
|
||||
get_args(&ap, 4096, argv);
|
||||
char **argv = alloca((count_args(&ap) + 2) * sizeof(char *));
|
||||
copy_args(argv, arg0, &ap);
|
||||
char **env = va_arg(ap, char **);
|
||||
va_end(ap);
|
||||
return execve(path, argv, env);
|
||||
@@ -117,12 +126,8 @@ int posix_spawn(pid_t *pid, const char *path,
|
||||
const posix_spawnattr_t *attrp,
|
||||
char *const *argv, char *const *envp) {
|
||||
debug_print("posix_spawn %s\n", path);
|
||||
|
||||
if (is_ld(path)) {
|
||||
if (is_ld(path))
|
||||
path = get_mold_path();
|
||||
((const char **)argv)[0] = path;
|
||||
}
|
||||
|
||||
typeof(posix_spawn) *real = dlsym(RTLD_NEXT, "posix_spawn");
|
||||
return real(pid, path, file_actions, attrp, argv, envp);
|
||||
}
|
||||
+393
-522
File diff suppressed because it is too large.
Load diff
+453
-676
File diff suppressed because it is too large.
Load diff
+217
-150
@@ -34,7 +34,8 @@ template <typename E>
|
||||
void create_synthetic_sections(Context<E> &ctx) {
|
||||
auto push = [&]<typename T>(T *x) {
|
||||
ctx.chunks.push_back(x);
|
||||
return std::unique_ptr<T>(x);
|
||||
ctx.chunk_pool.emplace_back(x);
|
||||
return x;
|
||||
};
|
||||
|
||||
if (!ctx.arg.oformat_binary) {
|
||||
@@ -82,13 +83,9 @@ void create_synthetic_sections(Context<E> &ctx) {
|
||||
|
||||
ctx.versym = push(new VersymSection<E>);
|
||||
ctx.verneed = push(new VerneedSection<E>);
|
||||
ctx.note_package = push(new NotePackageSection<E>);
|
||||
ctx.note_property = push(new NotePropertySection<E>);
|
||||
|
||||
if constexpr (std::is_same_v<E, ARM32>) {
|
||||
ctx.thumb_to_arm = push(new ThumbToArmSection);
|
||||
ctx.tls_trampoline = push(new TlsTrampolineSection);
|
||||
}
|
||||
|
||||
// If .dynamic exists, .dynsym and .dynstr must exist as well
|
||||
// since .dynamic refers them.
|
||||
if (ctx.dynamic) {
|
||||
@@ -128,8 +125,6 @@ static void mark_live_objects(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void do_resolve_symbols(Context<E> &ctx) {
|
||||
Timer t(ctx, "do_resolve_symbols");
|
||||
|
||||
auto for_each_file = [&](std::function<void(InputFile<E> *)> fn) {
|
||||
tbb::parallel_for_each(ctx.objs, fn);
|
||||
tbb::parallel_for_each(ctx.dsos, fn);
|
||||
@@ -163,7 +158,7 @@ void do_resolve_symbols(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void resolve_symbols(Context<E> &ctx) {
|
||||
Timer t(ctx, "do_resolve_symbols");
|
||||
Timer t(ctx, "resolve_symbols");
|
||||
|
||||
std::vector<ObjectFile<E> *> objs = ctx.objs;
|
||||
std::vector<SharedFile<E> *> dsos = ctx.dsos;
|
||||
@@ -207,7 +202,7 @@ void resolve_symbols(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
file->clear_symbols();
|
||||
file->is_alive = true;
|
||||
file->is_alive = !file->is_needed;
|
||||
});
|
||||
|
||||
do_resolve_symbols(ctx);
|
||||
@@ -352,28 +347,55 @@ void bin_sections(Context<E> &ctx) {
|
||||
// Create a dummy object file containing linker-synthesized
|
||||
// symbols.
|
||||
template <typename E>
|
||||
ObjectFile<E> *create_internal_file(Context<E> &ctx) {
|
||||
void create_internal_file(Context<E> &ctx) {
|
||||
ObjectFile<E> *obj = new ObjectFile<E>;
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
ctx.internal_obj = obj;
|
||||
ctx.objs.push_back(obj);
|
||||
|
||||
// Create linker-synthesized symbols.
|
||||
auto *esyms = new std::vector<ElfSym<E>>(1);
|
||||
ctx.internal_esyms.resize(1);
|
||||
|
||||
obj->symbols.push_back(new Symbol<E>);
|
||||
obj->first_global = 1;
|
||||
obj->is_alive = true;
|
||||
obj->features = -1;
|
||||
obj->priority = 1;
|
||||
|
||||
auto add = [&](std::string_view name, u8 st_type = STT_NOTYPE) {
|
||||
ElfSym<E> esym = {};
|
||||
esym.st_type = st_type;
|
||||
// Add symbols for --defsym.
|
||||
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++) {
|
||||
Symbol<E> *sym = ctx.arg.defsyms[i].first;
|
||||
obj->symbols.push_back(sym);
|
||||
|
||||
ElfSym<E> esym;
|
||||
memset(&esym, 0, sizeof(esym));
|
||||
esym.st_type = STT_NOTYPE;
|
||||
esym.st_shndx = SHN_ABS;
|
||||
esym.st_bind = STB_GLOBAL;
|
||||
esym.st_visibility = STV_DEFAULT;
|
||||
ctx.internal_esyms.push_back(esym);
|
||||
};
|
||||
|
||||
obj->elf_syms = ctx.internal_esyms;
|
||||
obj->sym_fragments.resize(ctx.internal_esyms.size());
|
||||
obj->symvers.resize(ctx.internal_esyms.size() - 1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void add_synthetic_symbols(Context<E> &ctx) {
|
||||
ObjectFile<E> &obj = *ctx.internal_obj;
|
||||
|
||||
auto add = [&](std::string_view name) {
|
||||
ElfSym<E> esym;
|
||||
memset(&esym, 0, sizeof(esym));
|
||||
esym.st_type = STT_NOTYPE;
|
||||
esym.st_shndx = SHN_ABS;
|
||||
esym.st_bind = STB_GLOBAL;
|
||||
esym.st_visibility = STV_HIDDEN;
|
||||
esyms->push_back(esym);
|
||||
ctx.internal_esyms.push_back(esym);
|
||||
|
||||
Symbol<E> *sym = get_symbol(ctx, name);
|
||||
obj->symbols.push_back(sym);
|
||||
obj.symbols.push_back(sym);
|
||||
return sym;
|
||||
};
|
||||
|
||||
@@ -393,9 +415,9 @@ ObjectFile<E> *create_internal_file(Context<E> &ctx) {
|
||||
ctx.__executable_start = add("__executable_start");
|
||||
|
||||
ctx.__rel_iplt_start =
|
||||
add(E::is_rel ? "__rel_iplt_start" : "__rela_iplt_start");
|
||||
add(is_rela<E> ? "__rela_iplt_start" : "__rel_iplt_start");
|
||||
ctx.__rel_iplt_end =
|
||||
add(E::is_rel ? "__rel_iplt_end" : "__rela_iplt_end");
|
||||
add(is_rela<E> ? "__rela_iplt_end" : "__rel_iplt_end");
|
||||
|
||||
if (ctx.arg.eh_frame_hdr)
|
||||
ctx.__GNU_EH_FRAME_HDR = add("__GNU_EH_FRAME_HDR");
|
||||
@@ -406,11 +428,13 @@ ObjectFile<E> *create_internal_file(Context<E> &ctx) {
|
||||
ctx.etext = add("etext");
|
||||
if (!get_symbol(ctx, "edata")->file)
|
||||
ctx.edata = add("edata");
|
||||
if (!get_symbol(ctx, "__dso_handle")->file)
|
||||
ctx.__dso_handle = add("__dso_handle");
|
||||
|
||||
if constexpr (E::supports_tlsdesc)
|
||||
if constexpr (supports_tlsdesc<E>)
|
||||
ctx._TLS_MODULE_BASE_ = add("_TLS_MODULE_BASE_");
|
||||
|
||||
if constexpr (std::is_same_v<E, RISCV64>)
|
||||
if constexpr (is_riscv<E>)
|
||||
if (!ctx.arg.shared)
|
||||
ctx.__global_pointer = add("__global_pointer$");
|
||||
|
||||
@@ -420,52 +444,30 @@ ObjectFile<E> *create_internal_file(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (!is_c_identifier(chunk->name))
|
||||
continue;
|
||||
|
||||
add(save_string(ctx, "__start_" + std::string(chunk->name)));
|
||||
add(save_string(ctx, "__stop_" + std::string(chunk->name)));
|
||||
if (is_c_identifier(chunk->name)) {
|
||||
add(save_string(ctx, "__start_" + std::string(chunk->name)));
|
||||
add(save_string(ctx, "__stop_" + std::string(chunk->name)));
|
||||
}
|
||||
}
|
||||
|
||||
i64 first_defsym = obj->symbols.size();
|
||||
obj.elf_syms = ctx.internal_esyms;
|
||||
obj.sym_fragments.resize(ctx.internal_esyms.size());
|
||||
obj.symvers.resize(ctx.internal_esyms.size() - 1);
|
||||
|
||||
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++) {
|
||||
Symbol<E> *sym = ctx.arg.defsyms[i].first;
|
||||
ElfSym<E> esym = {};
|
||||
esym.st_type = STT_NOTYPE;
|
||||
esym.st_shndx = SHN_ABS;
|
||||
esym.st_bind = STB_GLOBAL;
|
||||
esym.st_visibility = STV_DEFAULT;
|
||||
esyms->push_back(esym);
|
||||
obj->symbols.push_back(sym);
|
||||
};
|
||||
obj.resolve_symbols(ctx);
|
||||
|
||||
obj->elf_syms = *esyms;
|
||||
obj->sym_fragments.resize(obj->elf_syms.size());
|
||||
|
||||
i64 num_globals = obj->elf_syms.size() - obj->first_global;
|
||||
obj->symvers.resize(num_globals);
|
||||
|
||||
obj->resolve_symbols(ctx);
|
||||
|
||||
for (i64 i = obj->first_global; i < first_defsym; i++)
|
||||
obj->symbols[i]->shndx = -1; // dummy value to make it a relative symbol
|
||||
// Make all synthetic symbols relative ones.
|
||||
for (Symbol<E> *sym : obj.symbols)
|
||||
sym->shndx = -1; // dummy value to make it a relative symbol
|
||||
|
||||
// Make defsym symbols absolute if necessary.
|
||||
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++) {
|
||||
Symbol<E> *sym = ctx.arg.defsyms[i].first;
|
||||
std::variant<Symbol<E> *, u64> val = ctx.arg.defsyms[i].second;
|
||||
|
||||
if (Symbol<E> **sym2 = std::get_if<Symbol<E> *>(&val))
|
||||
if ((*sym2)->is_relative())
|
||||
sym->shndx = -1; // dummy value to make it a relative symbol
|
||||
if (std::holds_alternative<u64>(val) ||
|
||||
std::get<Symbol<E> *>(val)->is_absolute())
|
||||
sym->shndx = 0;
|
||||
}
|
||||
|
||||
ctx.on_exit.push_back([=] {
|
||||
delete esyms;
|
||||
delete obj->symbols[0];
|
||||
});
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -550,7 +552,7 @@ R"(# This is an output of the mold linker's --print-dependencies=full option.
|
||||
|
||||
std::unordered_set<void *> visited;
|
||||
|
||||
for (ElfRel<E> &r : isec->get_rels(ctx)) {
|
||||
for (const ElfRel<E> &r : isec->get_rels(ctx)) {
|
||||
if (r.r_type == E::R_NONE)
|
||||
continue;
|
||||
|
||||
@@ -579,7 +581,7 @@ static std::string create_response_file(Context<E> &ctx) {
|
||||
std::string buf;
|
||||
std::stringstream out;
|
||||
|
||||
std::string cwd = std::filesystem::current_path();
|
||||
std::string cwd = std::filesystem::current_path().string();
|
||||
out << "-C " << cwd.substr(1) << "\n";
|
||||
|
||||
if (cwd != "/") {
|
||||
@@ -613,7 +615,7 @@ void write_repro_file(Context<E> &ctx) {
|
||||
std::unordered_set<std::string> seen;
|
||||
for (std::unique_ptr<MappedFile<Context<E>>> &mf : ctx.mf_pool) {
|
||||
if (!mf->parent) {
|
||||
std::string path = to_abs_path(mf->name);
|
||||
std::string path = to_abs_path(mf->name).string();
|
||||
if (seen.insert(path).second)
|
||||
tar->append(path, mf->get_contents());
|
||||
}
|
||||
@@ -653,15 +655,16 @@ void sort_init_fini(Context<E> &ctx) {
|
||||
|
||||
auto get_priority = [](InputSection<E> *isec) {
|
||||
static std::regex re(R"(\.(\d+)$)", std::regex_constants::optimize);
|
||||
std::string name = isec->name().begin();
|
||||
std::smatch m;
|
||||
if (std::regex_search(name, m, re))
|
||||
std::string_view name = isec->name();
|
||||
std::cmatch m;
|
||||
if (std::regex_search(name.data(), name.data() + name.size(), m, re))
|
||||
return std::stoi(m[1]);
|
||||
return 65536;
|
||||
};
|
||||
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections) {
|
||||
if (osec->name == ".init_array" || osec->name == ".fini_array") {
|
||||
if (osec->name == ".init_array" || osec->name == ".preinit_array" ||
|
||||
osec->name == ".fini_array") {
|
||||
if (ctx.arg.shuffle_sections == SHUFFLE_SECTIONS_REVERSE)
|
||||
std::reverse(osec->members.begin(), osec->members.end());
|
||||
|
||||
@@ -741,8 +744,9 @@ void shuffle_sections(Context<E> &ctx) {
|
||||
|
||||
auto is_eligible = [](OutputSection<E> &osec) {
|
||||
return osec.name != ".init" && osec.name != ".fini" &&
|
||||
osec.name != ".init_array" && osec.name != ".fini_array" &&
|
||||
osec.name != ".ctors" && osec.name != ".dtors";
|
||||
osec.name != ".ctors" && osec.name != ".dtors" &&
|
||||
osec.name != ".init_array" && osec.name != ".preinit_array" &&
|
||||
osec.name != ".fini_array";
|
||||
};
|
||||
|
||||
switch (ctx.arg.shuffle_sections) {
|
||||
@@ -753,7 +757,7 @@ void shuffle_sections(Context<E> &ctx) {
|
||||
if (ctx.arg.shuffle_sections_seed)
|
||||
seed = *ctx.arg.shuffle_sections_seed;
|
||||
else
|
||||
seed = std::random_device()();
|
||||
seed = ((u64)std::random_device()() << 32) | std::random_device()();
|
||||
|
||||
tbb::parallel_for_each(ctx.output_sections,
|
||||
[&](std::unique_ptr<OutputSection<E>> &osec) {
|
||||
@@ -806,6 +810,11 @@ void compute_section_sizes(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for_each(ctx.output_sections,
|
||||
[&](std::unique_ptr<OutputSection<E>> &osec) {
|
||||
// This pattern will be processed in the next loop.
|
||||
if constexpr (needs_thunk<E>)
|
||||
if (osec->shdr.sh_flags & SHF_EXECINSTR)
|
||||
return;
|
||||
|
||||
// Since one output section may contain millions of input sections,
|
||||
// we first split input sections into groups and assign offsets to
|
||||
// groups.
|
||||
@@ -845,6 +854,19 @@ void compute_section_sizes(Context<E> &ctx) {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// On ARM32 or ARM64, we may need to create so-called "range extension
|
||||
// thunks" to extend branch instructions reach, as they can jump only
|
||||
// to ±16 MiB or ±128 MiB, respecitvely.
|
||||
//
|
||||
// In the following loop, We compute the sizes of sections while
|
||||
// inserting thunks. This pass cannot be parallelized. That is,
|
||||
// create_range_extension_thunks is parallelized internally, but the
|
||||
// function itself is not thread-safe.
|
||||
if constexpr (needs_thunk<E>)
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
if (osec->shdr.sh_flags & SHF_EXECINSTR)
|
||||
create_range_extension_thunks(ctx, *osec);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -902,21 +924,21 @@ void scan_rels(Context<E> &ctx) {
|
||||
if (sym->flags & NEEDS_GOT)
|
||||
ctx.got->add_got_symbol(ctx, sym);
|
||||
|
||||
if (sym->flags & NEEDS_PLT) {
|
||||
if (sym->is_canonical) {
|
||||
// A canonical PLT needs to be visible from DSOs.
|
||||
sym->is_exported = true;
|
||||
if (sym->flags & NEEDS_CPLT) {
|
||||
sym->is_canonical = true;
|
||||
|
||||
// We can't use .plt.got for a canonical PLT because otherwise
|
||||
// .plt.got and .got would refer each other, resulting in an
|
||||
// infinite loop at runtime.
|
||||
// A canonical PLT needs to be visible from DSOs.
|
||||
sym->is_exported = true;
|
||||
|
||||
// We can't use .plt.got for a canonical PLT because otherwise
|
||||
// .plt.got and .got would refer each other, resulting in an
|
||||
// infinite loop at runtime.
|
||||
ctx.plt->add_symbol(ctx, sym);
|
||||
} else if (sym->flags & NEEDS_PLT) {
|
||||
if (sym->flags & NEEDS_GOT)
|
||||
ctx.pltgot->add_symbol(ctx, sym);
|
||||
else
|
||||
ctx.plt->add_symbol(ctx, sym);
|
||||
} else {
|
||||
if (sym->flags & NEEDS_GOT)
|
||||
ctx.pltgot->add_symbol(ctx, sym);
|
||||
else
|
||||
ctx.plt->add_symbol(ctx, sym);
|
||||
}
|
||||
}
|
||||
|
||||
if (sym->flags & NEEDS_GOTTP)
|
||||
@@ -956,10 +978,6 @@ void scan_rels(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<E, ARM32>)
|
||||
if (sym->flags & NEEDS_THUMB_TO_ARM_THUNK)
|
||||
ctx.thumb_to_arm->add_symbol(ctx, sym);
|
||||
|
||||
sym->flags = 0;
|
||||
}
|
||||
|
||||
@@ -978,7 +996,7 @@ void create_reloc_sections(Context<E> &ctx) {
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections) {
|
||||
RelocSection<E> *r = new RelocSection<E>(ctx, *osec);
|
||||
ctx.chunks.push_back(r);
|
||||
ctx.output_chunks.emplace_back(r);
|
||||
ctx.chunk_pool.emplace_back(r);
|
||||
}
|
||||
|
||||
// Create a table to map input symbol indices to output symbol indices
|
||||
@@ -1048,8 +1066,8 @@ void apply_version_script(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
// Otherwise, use glob pattern matchers.
|
||||
VersionMatcher matcher;
|
||||
VersionMatcher cpp_matcher;
|
||||
MultiGlob matcher;
|
||||
MultiGlob cpp_matcher;
|
||||
|
||||
for (VersionPattern &v : ctx.version_patterns) {
|
||||
if (v.is_cpp) {
|
||||
@@ -1073,9 +1091,12 @@ void apply_version_script(Context<E> &ctx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cpp_matcher.empty())
|
||||
if (std::optional<u16> ver = cpp_matcher.find(demangle(name)))
|
||||
if (!cpp_matcher.empty()) {
|
||||
if (std::optional<std::string_view> s = cpp_demangle(name))
|
||||
name = *s;
|
||||
if (std::optional<u16> ver = cpp_matcher.find(name))
|
||||
sym->ver_idx = *ver;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1093,6 +1114,8 @@ void parse_symbol_version(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (i64 i = 0; i < file->symbols.size() - file->first_global; i++) {
|
||||
// Match VERSION part of symbol foo@VERSION with version definitions.
|
||||
// The symbols' VERSION parts are in file->symvers.
|
||||
if (!file->symvers[i])
|
||||
continue;
|
||||
|
||||
@@ -1118,6 +1141,16 @@ void parse_symbol_version(Context<E> &ctx) {
|
||||
sym->ver_idx = it->second;
|
||||
if (!is_default)
|
||||
sym->ver_idx |= VERSYM_HIDDEN;
|
||||
|
||||
// If both symbol `foo` and `foo@VERSION` are defined, `foo@VERSION`
|
||||
// hides `foo` so that all references to `foo` are resolved to a
|
||||
// versioned symbol. Likewise, if `foo@VERSION` and `foo@@VERSION` are
|
||||
// defined, the default one takes precedence.
|
||||
Symbol<E> *sym2 = get_symbol(ctx, sym->name());
|
||||
if (sym2->file == file && !file->symvers[sym2->sym_idx - file->first_global])
|
||||
if (sym2->ver_idx == ctx.default_version ||
|
||||
(sym2->ver_idx & ~VERSYM_HIDDEN) == (sym->ver_idx & ~VERSYM_HIDDEN))
|
||||
sym2->ver_idx = VER_NDX_LOCAL;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1126,30 +1159,37 @@ template <typename E>
|
||||
void compute_import_export(Context<E> &ctx) {
|
||||
Timer t(ctx, "compute_import_export");
|
||||
|
||||
// Export symbols referenced by DSOs.
|
||||
// If we are creating an executable, we want to export symbols referenced
|
||||
// by DSOs unless they are explicitly marked as local by a version script.
|
||||
if (!ctx.arg.shared) {
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file && !sym->file->is_dso && sym->visibility != STV_HIDDEN) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->is_exported = true;
|
||||
if (sym->ver_idx != VER_NDX_LOCAL || !ctx.version_specified) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->is_exported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Export symbols that are not hidden or marked as local.
|
||||
// We also want to mark imported symbols as such.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->get_global_syms()) {
|
||||
if (!sym->file || sym->visibility == STV_HIDDEN ||
|
||||
sym->ver_idx == VER_NDX_LOCAL)
|
||||
continue;
|
||||
|
||||
if (sym->file != file && sym->file->is_dso) {
|
||||
// If we are using a symbol in a DSO, we need to import it at runtime.
|
||||
if (sym->file != file && sym->file->is_dso && !sym->is_absolute()) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->is_imported = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we are creating a DSO, all global symbols are exported by default.
|
||||
if (sym->file == file) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->is_exported = true;
|
||||
@@ -1163,6 +1203,15 @@ void compute_import_export(Context<E> &ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void mark_addrsig(Context<E> &ctx) {
|
||||
Timer t(ctx, "mark_addrsig");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->mark_addrsig(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void clear_padding(Context<E> &ctx) {
|
||||
Timer t(ctx, "clear_padding");
|
||||
@@ -1225,34 +1274,34 @@ i64 get_section_rank(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
u64 type = chunk->shdr.sh_type;
|
||||
u64 flags = chunk->shdr.sh_flags;
|
||||
|
||||
if (chunk == ctx.ehdr.get())
|
||||
if (chunk == ctx.ehdr)
|
||||
return 0;
|
||||
if (chunk == ctx.phdr.get())
|
||||
if (chunk == ctx.phdr)
|
||||
return 1;
|
||||
if (chunk == ctx.interp.get())
|
||||
if (chunk == ctx.interp)
|
||||
return 2;
|
||||
|
||||
if (type == SHT_NOTE && (flags & SHF_ALLOC))
|
||||
return (1 << 10) + chunk->shdr.sh_addralign;
|
||||
|
||||
if (chunk == ctx.hash.get())
|
||||
if (chunk == ctx.hash)
|
||||
return (1 << 11) + 0;
|
||||
if (chunk == ctx.gnu_hash.get())
|
||||
if (chunk == ctx.gnu_hash)
|
||||
return (1 << 11) + 1;
|
||||
if (chunk == ctx.dynsym.get())
|
||||
if (chunk == ctx.dynsym)
|
||||
return (1 << 11) + 2;
|
||||
if (chunk == ctx.dynstr.get())
|
||||
if (chunk == ctx.dynstr)
|
||||
return (1 << 11) + 3;
|
||||
if (chunk == ctx.versym.get())
|
||||
if (chunk == ctx.versym)
|
||||
return (1 << 11) + 4;
|
||||
if (chunk == ctx.verneed.get())
|
||||
if (chunk == ctx.verneed)
|
||||
return (1 << 11) + 5;
|
||||
if (chunk == ctx.reldyn.get())
|
||||
if (chunk == ctx.reldyn)
|
||||
return (1 << 11) + 6;
|
||||
if (chunk == ctx.relplt.get())
|
||||
if (chunk == ctx.relplt)
|
||||
return (1 << 11) + 7;
|
||||
|
||||
if (chunk == ctx.shdr.get())
|
||||
if (chunk == ctx.shdr)
|
||||
return 1 << 30;
|
||||
if (!(flags & SHF_ALLOC))
|
||||
return (1 << 30) - 1;
|
||||
@@ -1283,22 +1332,26 @@ i64 do_set_osec_offsets(Context<E> &ctx) {
|
||||
|
||||
// Assign virtual addresses
|
||||
u64 addr = ctx.arg.image_base;
|
||||
for (i64 i = 0; i < chunks.size(); i++) {
|
||||
if (!(chunks[i]->shdr.sh_flags & SHF_ALLOC))
|
||||
for (Chunk<E> *chunk : chunks) {
|
||||
if (!(chunk->shdr.sh_flags & SHF_ALLOC))
|
||||
continue;
|
||||
|
||||
if (auto it = ctx.arg.section_start.find(chunks[i]->name);
|
||||
it != ctx.arg.section_start.end())
|
||||
bool addr_specified = false;
|
||||
if (auto it = ctx.arg.section_start.find(chunk->name);
|
||||
it != ctx.arg.section_start.end()) {
|
||||
addr = it->second;
|
||||
addr_specified = true;
|
||||
}
|
||||
|
||||
if (is_tbss(chunks[i])) {
|
||||
chunks[i]->shdr.sh_addr = addr;
|
||||
if (is_tbss(chunk)) {
|
||||
chunk->shdr.sh_addr = addr;
|
||||
continue;
|
||||
}
|
||||
|
||||
addr = align_to(addr, alignment(chunks[i]));
|
||||
chunks[i]->shdr.sh_addr = addr;
|
||||
addr += chunks[i]->shdr.sh_size;
|
||||
if (!addr_specified)
|
||||
addr = align_to(addr, alignment(chunk));
|
||||
chunk->shdr.sh_addr = addr;
|
||||
addr += chunk->shdr.sh_size;
|
||||
}
|
||||
|
||||
// Fix tbss virtual addresses. tbss sections are laid out as if they
|
||||
@@ -1329,26 +1382,39 @@ i64 do_set_osec_offsets(Context<E> &ctx) {
|
||||
while (i < chunks.size() && (chunks[i]->shdr.sh_flags & SHF_ALLOC)) {
|
||||
Chunk<E> &first = *chunks[i];
|
||||
assert(first.shdr.sh_type != SHT_NOBITS);
|
||||
|
||||
fileoff = align_to(fileoff, alignment(&first));
|
||||
|
||||
u64 end = fileoff;
|
||||
while (i < chunks.size() && (chunks[i]->shdr.sh_flags & SHF_ALLOC) &&
|
||||
chunks[i]->shdr.sh_type != SHT_NOBITS) {
|
||||
// The addresses may not increase monotonically if a user uses
|
||||
// --start-sections.
|
||||
// Assign ALLOC sections contiguous file offsets as long as they
|
||||
// are contiguous in memory.
|
||||
for (;;) {
|
||||
chunks[i]->shdr.sh_offset =
|
||||
fileoff + chunks[i]->shdr.sh_addr - first.shdr.sh_addr;
|
||||
i++;
|
||||
|
||||
if (i >= chunks.size() ||
|
||||
!(chunks[i]->shdr.sh_flags & SHF_ALLOC) ||
|
||||
chunks[i]->shdr.sh_type == SHT_NOBITS)
|
||||
break;
|
||||
|
||||
// If --start-section is given, addresses may not increase
|
||||
// monotonically.
|
||||
if (chunks[i]->shdr.sh_addr < first.shdr.sh_addr)
|
||||
break;
|
||||
|
||||
chunks[i]->shdr.sh_offset =
|
||||
fileoff + chunks[i]->shdr.sh_addr - first.shdr.sh_addr;
|
||||
end = chunks[i]->shdr.sh_offset + chunks[i]->shdr.sh_size;
|
||||
i++;
|
||||
i64 gap_size = chunks[i]->shdr.sh_addr - chunks[i - 1]->shdr.sh_addr -
|
||||
chunks[i - 1]->shdr.sh_size;
|
||||
|
||||
// If --start-section is given, there may be a large gap between
|
||||
// sections. We don't want to allocate a disk space for a gap if
|
||||
// exists.
|
||||
if (gap_size >= ctx.page_size)
|
||||
break;
|
||||
}
|
||||
|
||||
fileoff = end;
|
||||
fileoff = chunks[i - 1]->shdr.sh_offset + chunks[i - 1]->shdr.sh_size;
|
||||
|
||||
while (i < chunks.size() && (chunks[i]->shdr.sh_flags & SHF_ALLOC) &&
|
||||
while (i < chunks.size() &&
|
||||
(chunks[i]->shdr.sh_flags & SHF_ALLOC) &&
|
||||
chunks[i]->shdr.sh_type == SHT_NOBITS)
|
||||
i++;
|
||||
}
|
||||
@@ -1416,7 +1482,6 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
if (Chunk<E> *chunk = find(".bss"))
|
||||
start(ctx.__bss_start, chunk);
|
||||
|
||||
// __ehdr_start and __executable_start
|
||||
if (ctx.ehdr) {
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (chunk->shndx == 1) {
|
||||
@@ -1424,6 +1489,11 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
ctx.__ehdr_start->value = ctx.ehdr->shdr.sh_addr;
|
||||
ctx.__executable_start->shndx = -1;
|
||||
ctx.__executable_start->value = ctx.ehdr->shdr.sh_addr;
|
||||
|
||||
if (ctx.__dso_handle) {
|
||||
ctx.__dso_handle->shndx = -1;
|
||||
ctx.__dso_handle->value = ctx.ehdr->shdr.sh_addr;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1454,6 +1524,10 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
start(ctx.__init_array_start, chunk);
|
||||
stop(ctx.__init_array_end, chunk);
|
||||
break;
|
||||
case SHT_PREINIT_ARRAY:
|
||||
start(ctx.__preinit_array_start, chunk);
|
||||
stop(ctx.__preinit_array_end, chunk);
|
||||
break;
|
||||
case SHT_FINI_ARRAY:
|
||||
start(ctx.__fini_array_start, chunk);
|
||||
stop(ctx.__fini_array_end, chunk);
|
||||
@@ -1463,7 +1537,7 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
|
||||
// _end, _etext, _edata and the like
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (chunk->is_header())
|
||||
if (chunk->kind() == HEADER)
|
||||
continue;
|
||||
|
||||
if (chunk->shdr.sh_flags & SHF_ALLOC) {
|
||||
@@ -1493,7 +1567,7 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
start(ctx._GLOBAL_OFFSET_TABLE_, ctx.got);
|
||||
|
||||
// _TLS_MODULE_BASE_
|
||||
if constexpr (E::supports_tlsdesc) {
|
||||
if (ctx._TLS_MODULE_BASE_) {
|
||||
// _TLS_MODULE_BASE_ is used for Local Dynamic model for TLSDESC.
|
||||
// I believe GCC and Clang don't create a reference to it, but Intel
|
||||
// compiler seems to be using this symbol.
|
||||
@@ -1504,10 +1578,7 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
// DTPOFF's relaxed value.
|
||||
u64 addr;
|
||||
if constexpr (std::is_same_v<E, X86_64> || std::is_same_v<E, I386>) {
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
addr = ctx.tls_end;
|
||||
else
|
||||
addr = ctx.tls_begin;
|
||||
addr = (ctx.arg.relax && !ctx.arg.shared) ? ctx.tls_end : ctx.tls_begin;
|
||||
} else {
|
||||
addr = ctx.tls_begin;
|
||||
}
|
||||
@@ -1520,18 +1591,16 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
start(ctx.__GNU_EH_FRAME_HDR, ctx.eh_frame_hdr);
|
||||
|
||||
// RISC-V's __global_pointer$
|
||||
if constexpr (std::is_same_v<E, RISCV64>) {
|
||||
if (!ctx.arg.shared) {
|
||||
if (Chunk<E> *chunk = find(".sdata"))
|
||||
ctx.__global_pointer->shndx = -chunk->shndx;
|
||||
else
|
||||
ctx.__global_pointer->shndx = -1;
|
||||
ctx.__global_pointer->value = 0x800;
|
||||
}
|
||||
if (ctx.__global_pointer) {
|
||||
if (Chunk<E> *chunk = find(".sdata"))
|
||||
ctx.__global_pointer->shndx = -chunk->shndx;
|
||||
else
|
||||
ctx.__global_pointer->shndx = -1;
|
||||
ctx.__global_pointer->value = 0x800;
|
||||
}
|
||||
|
||||
// ARM32's __exidx_{start,end}
|
||||
if constexpr (std::is_same_v<E, ARM32>) {
|
||||
if (ctx.__exidx_start) {
|
||||
if (Chunk<E> *chunk = find(".ARM.exidx")) {
|
||||
start(ctx.__exidx_start, chunk);
|
||||
stop(ctx.__exidx_end, chunk);
|
||||
@@ -1595,7 +1664,7 @@ i64 compress_debug_sections(Context<E> &ctx) {
|
||||
comp = new GnuCompressedSection<E>(ctx, chunk);
|
||||
assert(comp);
|
||||
|
||||
ctx.output_chunks.emplace_back(comp);
|
||||
ctx.chunk_pool.emplace_back(comp);
|
||||
ctx.chunks[i] = comp;
|
||||
});
|
||||
|
||||
@@ -1638,6 +1707,7 @@ void write_dependency_file(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void create_internal_file(Context<E> &); \
|
||||
template void apply_exclude_libs(Context<E> &); \
|
||||
template void create_synthetic_sections(Context<E> &); \
|
||||
template void resolve_symbols(Context<E> &); \
|
||||
@@ -1646,7 +1716,7 @@ void write_dependency_file(Context<E> &ctx) {
|
||||
template void convert_common_symbols(Context<E> &); \
|
||||
template void compute_merged_section_sizes(Context<E> &); \
|
||||
template void bin_sections(Context<E> &); \
|
||||
template ObjectFile<E> *create_internal_file(Context<E> &); \
|
||||
template void add_synthetic_symbols(Context<E> &); \
|
||||
template void check_cet_errors(Context<E> &); \
|
||||
template void print_dependencies(Context<E> &); \
|
||||
template void print_dependencies_full(Context<E> &); \
|
||||
@@ -1665,6 +1735,7 @@ void write_dependency_file(Context<E> &ctx) {
|
||||
template void apply_version_script(Context<E> &); \
|
||||
template void parse_symbol_version(Context<E> &); \
|
||||
template void compute_import_export(Context<E> &); \
|
||||
template void mark_addrsig(Context<E> &); \
|
||||
template void clear_padding(Context<E> &); \
|
||||
template i64 get_section_rank(Context<E> &, Chunk<E> *); \
|
||||
template i64 set_osec_offsets(Context<E> &); \
|
||||
@@ -1672,10 +1743,6 @@ void write_dependency_file(Context<E> &ctx) {
|
||||
template i64 compress_debug_sections(Context<E> &); \
|
||||
template void write_dependency_file(Context<E> &);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+22
-23
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
#include "../output-file.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
@@ -70,7 +71,7 @@ public:
|
||||
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;
|
||||
this->out_shdr.sh_addralign = sizeof(Word<E>);
|
||||
}
|
||||
|
||||
void add_local_symbol(Context<E> &ctx, RObjectFile<E> &file, i64 idx);
|
||||
@@ -297,8 +298,8 @@ 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_CLASS] = (sizeof(Word<E>) == 8) ? ELFCLASS64 : ELFCLASS32;
|
||||
hdr.e_ident[EI_DATA] = ELFDATA2LSB;
|
||||
hdr.e_ident[EI_VERSION] = EV_CURRENT;
|
||||
hdr.e_type = ET_REL;
|
||||
hdr.e_machine = E::e_machine;
|
||||
@@ -413,36 +414,38 @@ std::span<T> RObjectFile<E>::get_data(Context<E> &ctx, const ElfShdr<E> &shdr) {
|
||||
|
||||
template <typename E>
|
||||
static std::vector<std::unique_ptr<RObjectFile<E>>>
|
||||
open_files(Context<E> &ctx, std::span<std::string_view> args) {
|
||||
open_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
std::vector<std::unique_ptr<RObjectFile<E>>> files;
|
||||
bool whole_archive = false;
|
||||
|
||||
while (!args.empty()) {
|
||||
if (read_flag(args, "whole-archive")) {
|
||||
const std::string &arg = args[0];
|
||||
args = args.subspan(1);
|
||||
|
||||
if (arg == "--whole-archive") {
|
||||
whole_archive = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read_flag(args, "no-whole-archive")) {
|
||||
if (arg == "--no-whole-archive") {
|
||||
whole_archive = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string_view arg;
|
||||
if (read_arg(ctx, args, arg, "version-script") ||
|
||||
read_arg(ctx, args, arg, "dynamic-list"))
|
||||
if (arg.starts_with("--version-script=") ||
|
||||
arg.starts_with("--dynamic-list=") ||
|
||||
arg.starts_with("--export-dynamic-symbol=") ||
|
||||
arg.starts_with("--export-dynamic-symbol-list="))
|
||||
continue;
|
||||
|
||||
MappedFile<Context<E>> *mf = nullptr;
|
||||
|
||||
if (read_arg(ctx, args, arg, "l")) {
|
||||
mf = find_library(ctx, std::string(arg));
|
||||
if (arg == "-l") {
|
||||
mf = find_library(ctx, arg.substr(2));
|
||||
} else {
|
||||
if (arg.starts_with('-'))
|
||||
continue;
|
||||
arg = args[0];
|
||||
args = args.subspan(1);
|
||||
mf = MappedFile<Context<E>>::must_open(ctx, std::string(arg));
|
||||
mf = MappedFile<Context<E>>::must_open(ctx, arg);
|
||||
}
|
||||
|
||||
switch (get_file_type(mf)) {
|
||||
@@ -482,7 +485,7 @@ static bool contains(std::unordered_set<std::string_view> &a,
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void combine_objects(Context<E> &ctx, std::span<std::string_view> file_args) {
|
||||
void combine_objects(Context<E> &ctx, std::span<std::string> file_args) {
|
||||
// Read object files
|
||||
std::vector<std::unique_ptr<RObjectFile<E>>> files = open_files(ctx, file_args);
|
||||
|
||||
@@ -574,8 +577,8 @@ void combine_objects(Context<E> &ctx, std::span<std::string_view> file_args) {
|
||||
|
||||
// Open an output file
|
||||
i64 filesize = assign_offsets(ctx);
|
||||
std::unique_ptr<OutputFile<E>> out =
|
||||
OutputFile<E>::open(ctx, ctx.arg.output, filesize, 0666);
|
||||
std::unique_ptr<OutputFile<Context<E>>> out =
|
||||
OutputFile<Context<E>>::open(ctx, ctx.arg.output, filesize, 0666);
|
||||
memset(out->buf, 0, filesize);
|
||||
ctx.buf = out->buf;
|
||||
|
||||
@@ -586,12 +589,8 @@ void combine_objects(Context<E> &ctx, std::span<std::string_view> file_args) {
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void combine_objects(Context<E> &, std::span<std::string_view>);
|
||||
template void combine_objects(Context<E> &, std::span<std::string>);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+3
-206
@@ -1,19 +1,13 @@
|
||||
#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 --
|
||||
@@ -60,198 +54,8 @@ std::function<void()> fork_child() {
|
||||
};
|
||||
}
|
||||
|
||||
static std::string base64(u8 *data, u64 size) {
|
||||
static const char chars[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+_";
|
||||
|
||||
std::ostringstream out;
|
||||
|
||||
auto encode = [&](u32 x) {
|
||||
out << chars[x & 0b111111]
|
||||
<< chars[(x >> 6) & 0b111111]
|
||||
<< chars[(x >> 12) & 0b111111]
|
||||
<< chars[(x >> 18) & 0b111111];
|
||||
};
|
||||
|
||||
i64 i = 0;
|
||||
for (; i < size - 3; i += 3)
|
||||
encode((data[i + 2] << 16) | (data[i + 1] << 8) | data[i]);
|
||||
|
||||
if (i == size - 1)
|
||||
encode(data[i]);
|
||||
else if (i == size - 2)
|
||||
encode((data[i + 1] << 8) | data[i]);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
static std::string compute_sha256(std::span<std::string_view> argv) {
|
||||
SHA256_CTX sha;
|
||||
SHA256_Init(&sha);
|
||||
|
||||
for (std::string_view arg : argv) {
|
||||
if (arg != "-preload" && arg != "--preload") {
|
||||
SHA256_Update(&sha, arg.data(), arg.size());
|
||||
char buf[] = {0};
|
||||
SHA256_Update(&sha, buf, 1);
|
||||
}
|
||||
}
|
||||
|
||||
u8 digest[SHA256_SIZE];
|
||||
SHA256_Final(digest, &sha);
|
||||
return base64(digest, SHA256_SIZE);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void send_fd(Context<E> &ctx, i64 conn, i64 fd) {
|
||||
struct iovec iov;
|
||||
char dummy = '1';
|
||||
iov.iov_base = &dummy;
|
||||
iov.iov_len = 1;
|
||||
|
||||
struct msghdr msg = {};
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
char buf[CMSG_SPACE(sizeof(int))];
|
||||
msg.msg_control = buf;
|
||||
msg.msg_controllen = CMSG_LEN(sizeof(int));
|
||||
|
||||
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS;
|
||||
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
|
||||
*(int *)CMSG_DATA(cmsg) = fd;
|
||||
|
||||
if (sendmsg(conn, &msg, 0) == -1)
|
||||
Fatal(ctx) << "sendmsg failed: " << errno_string();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 recv_fd(Context<E> &ctx, i64 conn) {
|
||||
struct iovec iov;
|
||||
char buf[1];
|
||||
iov.iov_base = buf;
|
||||
iov.iov_len = sizeof(buf);
|
||||
|
||||
struct msghdr msg = {};
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
char cmsgbuf[CMSG_SPACE(sizeof(int))];
|
||||
msg.msg_control = (caddr_t)cmsgbuf;
|
||||
msg.msg_controllen = sizeof(cmsgbuf);
|
||||
|
||||
i64 len = recvmsg(conn, &msg, 0);
|
||||
if (len <= 0)
|
||||
Fatal(ctx) << "recvmsg failed: " << errno_string();
|
||||
|
||||
struct cmsghdr *cmsg;
|
||||
cmsg = CMSG_FIRSTHDR(&msg);
|
||||
return *(int *)CMSG_DATA(cmsg);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void try_resume_daemon(Context<E> &ctx) {
|
||||
i64 conn = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (conn == -1)
|
||||
Fatal(ctx) << "socket failed: " << errno_string();
|
||||
|
||||
std::string path = "/tmp/mold-" + compute_sha256(ctx.cmdline_args);
|
||||
|
||||
struct sockaddr_un name = {};
|
||||
name.sun_family = AF_UNIX;
|
||||
memcpy(name.sun_path, path.data(), path.size());
|
||||
|
||||
if (connect(conn, (struct sockaddr *)&name, sizeof(name)) != 0) {
|
||||
close(conn);
|
||||
return;
|
||||
}
|
||||
|
||||
send_fd(ctx, conn, STDOUT_FILENO);
|
||||
send_fd(ctx, conn, STDERR_FILENO);
|
||||
|
||||
char buf[1];
|
||||
i64 r = read(conn, buf, 1);
|
||||
close(conn);
|
||||
if (r == 1)
|
||||
exit(0);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void daemonize(Context<E> &ctx, std::function<void()> *wait_for_client,
|
||||
std::function<void()> *on_complete) {
|
||||
if (daemon(1, 0) == -1)
|
||||
Fatal(ctx) << "daemon failed: " << errno_string();
|
||||
|
||||
i64 sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sock == -1)
|
||||
Fatal(ctx) << "socket failed: " << errno_string();
|
||||
|
||||
socket_tmpfile =
|
||||
strdup(("/tmp/mold-" + compute_sha256(ctx.cmdline_args)).c_str());
|
||||
|
||||
struct sockaddr_un name = {};
|
||||
name.sun_family = AF_UNIX;
|
||||
strcpy(name.sun_path, socket_tmpfile);
|
||||
|
||||
u32 orig_mask = umask(0177);
|
||||
|
||||
if (bind(sock, (struct sockaddr *)&name, sizeof(name)) == -1) {
|
||||
if (errno != EADDRINUSE)
|
||||
Fatal(ctx) << "bind failed: " << errno_string();
|
||||
|
||||
unlink(socket_tmpfile);
|
||||
if (bind(sock, (struct sockaddr *)&name, sizeof(name)) == -1)
|
||||
Fatal(ctx) << "bind failed: " << errno_string();
|
||||
}
|
||||
|
||||
umask(orig_mask);
|
||||
|
||||
if (listen(sock, 0) == -1)
|
||||
Fatal(ctx) << "listen failed: " << errno_string();
|
||||
|
||||
static i64 conn = -1;
|
||||
|
||||
*wait_for_client = [=, &ctx] {
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(sock, &rfds);
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = DAEMON_TIMEOUT;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
i64 res = select(sock + 1, &rfds, NULL, NULL, &tv);
|
||||
if (res == -1)
|
||||
Fatal(ctx) << "select failed: " << errno_string();
|
||||
|
||||
if (res == 0) {
|
||||
std::cout << "timeout\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
conn = accept(sock, NULL, NULL);
|
||||
if (conn == -1)
|
||||
Fatal(ctx) << "accept failed: " << errno_string();
|
||||
unlink(socket_tmpfile);
|
||||
|
||||
dup2(recv_fd(ctx, conn), STDOUT_FILENO);
|
||||
dup2(recv_fd(ctx, conn), STDERR_FILENO);
|
||||
};
|
||||
|
||||
*on_complete = [=] {
|
||||
char buf[] = {1};
|
||||
int n = write(conn, buf, 1);
|
||||
assert(n == 1);
|
||||
};
|
||||
}
|
||||
|
||||
static std::string get_self_path() {
|
||||
return std::filesystem::read_symlink("/proc/self/exe");
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
|
||||
static std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
|
||||
// Look for mold-wrapper.so from the same directory as the executable is.
|
||||
std::filesystem::path path = self.parent_path() / "mold-wrapper.so";
|
||||
std::error_code ec;
|
||||
@@ -283,7 +87,7 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
Fatal(ctx) << "-run: argument missing";
|
||||
|
||||
// Get the mold-wrapper.so path
|
||||
std::string self = get_self_path();
|
||||
std::string self = std::filesystem::read_symlink("/proc/self/exe");
|
||||
std::string dso_path = find_dso(ctx, self);
|
||||
|
||||
// Set environment variables
|
||||
@@ -307,15 +111,8 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void try_resume_daemon(Context<E> &); \
|
||||
template void daemonize(Context<E> &, std::function<void()> *, \
|
||||
std::function<void()> *); \
|
||||
template void process_run_subcommand(Context<E> &, int, char **)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
// RISC instructions are usually up to 4 bytes long, so the immediates
|
||||
// of their branch instructions are naturally smaller than 32 bits.
|
||||
// This is contrary to x86-64 on which branch instructions take 4
|
||||
// bytes immediates and can jump to anywhere within PC ± 2 GiB.
|
||||
//
|
||||
// In fact, ARM32's branch instructions can jump only within ±16 MiB
|
||||
// and ARM64's ±128 MiB. If a branch target is further than that, we
|
||||
// need to let it branch to a linker-synthesized code sequence that
|
||||
// construct a full 32 bit address in a register and jump there. That
|
||||
// linker-synthesized code is called "thunk".
|
||||
//
|
||||
// The function in this file creates thunks.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
for (Symbol<E> *sym : thunk.symbols) {
|
||||
sym->extra.thunk_idx = -1;
|
||||
sym->extra.thunk_sym_idx = -1;
|
||||
sym->flags = 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
std::span<InputSection<E> *> members = osec.members;
|
||||
if (members.empty())
|
||||
return;
|
||||
|
||||
members[0]->offset = 0;
|
||||
|
||||
// Initialize input sections with a dummy offset so that we can
|
||||
// distinguish sections that have got an address with the one who
|
||||
// haven't.
|
||||
tbb::parallel_for((i64)1, (i64)members.size(), [&](i64 i) {
|
||||
members[i]->offset = -1;
|
||||
});
|
||||
|
||||
// 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 < E::thunk_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 < E::thunk_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 - E::thunk_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;
|
||||
offset = align_to(offset, thunk.alignment);
|
||||
thunk.offset = offset;
|
||||
|
||||
std::mutex mu;
|
||||
|
||||
// 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<const ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::vector<RangeExtensionRef> &range_extn = isec->extra.range_extn;
|
||||
range_extn.resize(rels.size());
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (!needs_thunk_rel(rel))
|
||||
continue;
|
||||
|
||||
// Skip if the symbol is undefined. apply_reloc() will report an error.
|
||||
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
|
||||
if (!sym.file)
|
||||
continue;
|
||||
|
||||
// Skip if the destination is within reach.
|
||||
if (is_reachable(ctx, sym, *isec, rel))
|
||||
continue;
|
||||
|
||||
// If the symbol is already in another thunk, reuse it.
|
||||
if (sym.extra.thunk_idx != -1) {
|
||||
range_extn[i].thunk_idx = sym.extra.thunk_idx;
|
||||
range_extn[i].sym_idx = sym.extra.thunk_sym_idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the symbol to this thunk if it's not added already.
|
||||
range_extn[i].thunk_idx = thunk.thunk_idx;
|
||||
range_extn[i].sym_idx = -1;
|
||||
|
||||
if (sym.flags.exchange(-1) == 0) {
|
||||
std::scoped_lock lock(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 std::tuple{a->file->priority, a->sym_idx} <
|
||||
std::tuple{b->file->priority, b->sym_idx};
|
||||
});
|
||||
|
||||
// Assign offsets within the thunk to the symbols.
|
||||
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
|
||||
sym->extra.thunk_idx = thunk.thunk_idx;
|
||||
sym->extra.thunk_sym_idx = i++;
|
||||
}
|
||||
|
||||
// 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<const ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::vector<RangeExtensionRef> &range_extn = isec->extra.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;
|
||||
|
||||
for (InputSection<E> *isec : members)
|
||||
osec.shdr.sh_addralign =
|
||||
std::max<u32>(osec.shdr.sh_addralign, 1 << isec->p2align);
|
||||
}
|
||||
|
||||
template
|
||||
void create_range_extension_thunks(Context<ARM64> &, OutputSection<ARM64> &);
|
||||
|
||||
template
|
||||
void create_range_extension_thunks(Context<ARM32> &, OutputSection<ARM32> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
+2
-2
@@ -7,7 +7,7 @@ namespace mold {
|
||||
|
||||
std::string get_realpath(std::string_view path) {
|
||||
std::error_code ec;
|
||||
std::string ret = std::filesystem::canonical(path, ec);
|
||||
std::string ret = std::filesystem::canonical(path, ec).string();
|
||||
return ec ? std::string(path) : ret;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ std::string get_realpath(std::string_view 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();
|
||||
return filepath(path).lexically_normal().string();
|
||||
}
|
||||
|
||||
std::filesystem::path to_abs_path(std::filesystem::path path) {
|
||||
|
||||
+11
-3
@@ -11,7 +11,9 @@ enum class FileType {
|
||||
ELF_OBJ,
|
||||
ELF_DSO,
|
||||
MACH_OBJ,
|
||||
MACH_EXE,
|
||||
MACH_DYLIB,
|
||||
MACH_BUNDLE,
|
||||
MACH_UNIVERSAL,
|
||||
AR,
|
||||
THIN_AR,
|
||||
@@ -41,7 +43,7 @@ inline bool is_gcc_lto_obj(MappedFile<C> *mf) {
|
||||
continue;
|
||||
|
||||
std::span<ElfSym<E>> elf_syms{(ElfSym<E> *)(data + sec.sh_offset),
|
||||
sec.sh_size / sizeof(ElfSym<E>)};
|
||||
(size_t)sec.sh_size / sizeof(ElfSym<E>)};
|
||||
|
||||
auto skip = [](u8 type) {
|
||||
return type == STT_NOTYPE || type == STT_FILE || type == STT_SECTION;
|
||||
@@ -73,7 +75,7 @@ FileType get_file_type(MappedFile<C> *mf) {
|
||||
return FileType::EMPTY;
|
||||
|
||||
if (data.starts_with("\177ELF")) {
|
||||
switch (*(u16 *)(data.data() + 16)) {
|
||||
switch (*(ul16 *)(data.data() + 16)) {
|
||||
case 1: {
|
||||
// ET_REL
|
||||
elf::Elf32Ehdr &ehdr = *(elf::Elf32Ehdr *)data.data();
|
||||
@@ -95,11 +97,15 @@ FileType get_file_type(MappedFile<C> *mf) {
|
||||
}
|
||||
|
||||
if (data.starts_with("\xcf\xfa\xed\xfe")) {
|
||||
switch (*(u32 *)(data.data() + 12)) {
|
||||
switch (*(ul32 *)(data.data() + 12)) {
|
||||
case 1: // MH_OBJECT
|
||||
return FileType::MACH_OBJ;
|
||||
case 2: // MH_EXECUTE
|
||||
return FileType::MACH_EXE;
|
||||
case 6: // MH_DYLIB
|
||||
return FileType::MACH_DYLIB;
|
||||
case 8: // MH_BUNDLE
|
||||
return FileType::MACH_BUNDLE;
|
||||
}
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
@@ -127,8 +133,10 @@ inline std::string filetype_to_string(FileType type) {
|
||||
case FileType::EMPTY: return "EMPTY";
|
||||
case FileType::ELF_OBJ: return "ELF_OBJ";
|
||||
case FileType::ELF_DSO: return "ELF_DSO";
|
||||
case FileType::MACH_EXE: return "MACH_EXE";
|
||||
case FileType::MACH_OBJ: return "MACH_OBJ";
|
||||
case FileType::MACH_DYLIB: return "MACH_DYLIB";
|
||||
case FileType::MACH_BUNDLE: return "MACH_BUNDLE";
|
||||
case FileType::MACH_UNIVERSAL: return "MACH_UNIVERSAL";
|
||||
case FileType::AR: return "AR";
|
||||
case FileType::THIN_AR: return "THIN_AR";
|
||||
|
||||
+11
-13
@@ -2,9 +2,9 @@
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
std::optional<GlobPattern> GlobPattern::compile(std::string_view pat) {
|
||||
std::optional<Glob> Glob::compile(std::string_view pat) {
|
||||
std::vector<Element> vec;
|
||||
|
||||
while (!pat.empty()) {
|
||||
@@ -20,8 +20,7 @@ std::optional<GlobPattern> GlobPattern::compile(std::string_view pat) {
|
||||
// [a-czg-i]: a, b, c, z, g, h, or i
|
||||
// [^a-z]: Any character except lowercase letters
|
||||
vec.push_back({BRACKET});
|
||||
std::vector<bool> &bitmap = vec.back().bitmap;
|
||||
bitmap.resize(256);
|
||||
std::bitset<256> &bitset = vec.back().bitset;
|
||||
|
||||
bool negate = false;
|
||||
if (!pat.empty() && pat[0] == '^') {
|
||||
@@ -60,9 +59,9 @@ std::optional<GlobPattern> GlobPattern::compile(std::string_view pat) {
|
||||
return {};
|
||||
|
||||
for (i64 i = start; i <= end; i++)
|
||||
bitmap[i] = true;
|
||||
bitset[i] = true;
|
||||
} else {
|
||||
bitmap[(u8)pat[0]] = true;
|
||||
bitset[(u8)pat[0]] = true;
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
}
|
||||
@@ -71,8 +70,7 @@ std::optional<GlobPattern> GlobPattern::compile(std::string_view pat) {
|
||||
return {};
|
||||
|
||||
if (negate)
|
||||
for (i64 i = 0; i < 256; i++)
|
||||
bitmap[i] = !bitmap[i];
|
||||
bitset.flip();
|
||||
break;
|
||||
}
|
||||
case '?':
|
||||
@@ -89,14 +87,14 @@ std::optional<GlobPattern> GlobPattern::compile(std::string_view pat) {
|
||||
}
|
||||
}
|
||||
|
||||
return {GlobPattern{std::move(vec)}};
|
||||
return {Glob{std::move(vec)}};
|
||||
}
|
||||
|
||||
bool GlobPattern::match(std::string_view str) {
|
||||
bool Glob::match(std::string_view str) {
|
||||
return do_match(str, elements);
|
||||
}
|
||||
|
||||
bool GlobPattern::do_match(std::string_view str, std::span<Element> elements) {
|
||||
bool Glob::do_match(std::string_view str, std::span<Element> elements) {
|
||||
while (!elements.empty()) {
|
||||
Element &e = elements[0];
|
||||
elements = elements.subspan(1);
|
||||
@@ -138,7 +136,7 @@ bool GlobPattern::do_match(std::string_view str, std::span<Element> elements) {
|
||||
str = str.substr(1);
|
||||
break;
|
||||
case BRACKET:
|
||||
if (str.empty() || !e.bitmap[str[0]])
|
||||
if (str.empty() || !e.bitset[str[0]])
|
||||
return false;
|
||||
str = str.substr(1);
|
||||
break;
|
||||
@@ -148,4 +146,4 @@ bool GlobPattern::do_match(std::string_view str, std::span<Element> elements) {
|
||||
return str.empty();
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash -x
|
||||
set -e
|
||||
source /etc/os-release
|
||||
|
||||
# The first line for each distro installs a build dependency.
|
||||
# The second line installs extra packages for `make test`.
|
||||
#
|
||||
# Feel free to send me a PR if you find a missing OS.
|
||||
|
||||
case "$ID-$VERSION_ID" in
|
||||
ubuntu-20.* | pop-20.*)
|
||||
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++ g++-10
|
||||
apt-get install -y file bsdmainutils
|
||||
;;
|
||||
ubuntu-22.* | pop-22.* | debian-11 | raspbian-*)
|
||||
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++
|
||||
apt-get install -y file bsdmainutils
|
||||
;;
|
||||
fedora-*)
|
||||
dnf install -y gcc-g++ cmake openssl-devel zlib-devel
|
||||
dnf install -y glibc-static file libstdc++-static diffutils
|
||||
;;
|
||||
opensuse-leap-*)
|
||||
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++ gcc11-c++
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
opensuse-tumbleweed-*)
|
||||
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
gentoo-*)
|
||||
emerge dev-util/cmake sys-libs/zlib
|
||||
;;
|
||||
arch-*)
|
||||
pacman -S --needed --noconfirm base-devel zlib openssl cmake util-linux
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
|
||||
exit 1
|
||||
esac
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
// This file defines integral types for file input/output. You should
|
||||
// use these types instead of the plain integers (such as u32 or i32)
|
||||
// when reading from/writing to an mmap'ed file area.
|
||||
//
|
||||
// Here is why you need these types. In C/C++, all data accesses must
|
||||
// be aligned. That is, if you read an N byte value from memory, the
|
||||
// address of that value must be a multiple of N. For example, reading
|
||||
// an u32 value from address 16 is legal, while reading from 18 is not
|
||||
// (since 18 is not a multiple of 4.) An unaligned access yields an
|
||||
// undefined behavior.
|
||||
//
|
||||
// All data members of the ELF data structures are naturally aligned,
|
||||
// so it may look like we don't need the integral types defined in
|
||||
// this file to access them. But there's a catch; if an object file is
|
||||
// in an archive file (a .a file), the beginning of the file is
|
||||
// guaranteed to be aligned only to a 2 bytes boundary, because ar
|
||||
// aligns each member only to a 2 byte boundary. Therefore, any data
|
||||
// members larger than 2 bytes may be unaligned in an mmap'ed memory,
|
||||
// and thus you always need to use the integral types in this file to
|
||||
// access it.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef __BIG_ENDIAN__
|
||||
#error "mold does not support big-endian hosts"
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename T, size_t SIZE = sizeof(T)>
|
||||
class LittleEndian {
|
||||
public:
|
||||
LittleEndian() { *this = 0; }
|
||||
LittleEndian(T x) { *this = x; }
|
||||
|
||||
operator T() const {
|
||||
T x = 0;
|
||||
memcpy(&x, val, SIZE);
|
||||
return x;
|
||||
}
|
||||
|
||||
LittleEndian &operator=(T x) {
|
||||
memcpy(&val, &x, SIZE);
|
||||
return *this;
|
||||
}
|
||||
|
||||
LittleEndian &operator++() {
|
||||
return *this = *this + 1;
|
||||
}
|
||||
|
||||
LittleEndian operator++(int) {
|
||||
T ret = *this;
|
||||
*this = *this + 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LittleEndian &operator--() {
|
||||
return *this = *this - 1;
|
||||
}
|
||||
|
||||
LittleEndian operator--(int) {
|
||||
T ret = *this;
|
||||
*this = *this - 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LittleEndian &operator+=(T x) {
|
||||
return *this = *this + x;
|
||||
}
|
||||
|
||||
LittleEndian &operator-=(T x) {
|
||||
return *this = *this - x;
|
||||
}
|
||||
|
||||
LittleEndian &operator&=(T x) {
|
||||
return *this = *this & x;
|
||||
}
|
||||
|
||||
LittleEndian &operator|=(T x) {
|
||||
return *this = *this | x;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t val[SIZE];
|
||||
};
|
||||
|
||||
using il16 = LittleEndian<int16_t>;
|
||||
using il32 = LittleEndian<int32_t>;
|
||||
using il64 = LittleEndian<int64_t>;
|
||||
using ul16 = LittleEndian<uint16_t>;
|
||||
using ul24 = LittleEndian<uint32_t, 3>;
|
||||
using ul32 = LittleEndian<uint32_t>;
|
||||
using ul64 = LittleEndian<uint64_t>;
|
||||
|
||||
template <typename T>
|
||||
class BigEndian {
|
||||
public:
|
||||
BigEndian() = delete;
|
||||
|
||||
operator T() const {
|
||||
T x;
|
||||
memcpy(&x, val, sizeof(T));
|
||||
return bswap(x);
|
||||
}
|
||||
|
||||
BigEndian &operator=(T x) {
|
||||
x = bswap(x);
|
||||
memcpy(&val, &x, sizeof(T));
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigEndian &operator++() {
|
||||
return *this = *this + 1;
|
||||
}
|
||||
|
||||
BigEndian operator++(int) {
|
||||
T ret = *this;
|
||||
*this = *this + 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator--() {
|
||||
return *this = *this - 1;
|
||||
}
|
||||
|
||||
BigEndian operator--(int) {
|
||||
T ret = *this;
|
||||
*this = *this - 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator+=(T x) {
|
||||
return *this = *this + x;
|
||||
}
|
||||
|
||||
BigEndian &operator&=(T x) {
|
||||
return *this = *this & x;
|
||||
}
|
||||
|
||||
BigEndian &operator|=(T x) {
|
||||
return *this = *this | x;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t val[sizeof(T)];
|
||||
|
||||
static T bswap(T x) {
|
||||
// Compiler is usually smart enough to compile the following code into
|
||||
// a single bswap instruction. See https://godbolt.org/z/7nvaM7qab
|
||||
if constexpr (sizeof(T) == 2) {
|
||||
return ((x & 0xff00) >> 8) |
|
||||
((x & 0x00ff) << 8);
|
||||
} else if constexpr (sizeof(T) == 4) {
|
||||
return ((x & 0xff000000) >> 24) |
|
||||
((x & 0x00ff0000) >> 8) |
|
||||
((x & 0x0000ff00) << 8) |
|
||||
((x & 0x000000ff) << 24);
|
||||
} else {
|
||||
return ((x & 0xff000000'00000000) >> 56) |
|
||||
((x & 0x00ff0000'00000000) >> 40) |
|
||||
((x & 0x0000ff00'00000000) >> 24) |
|
||||
((x & 0x000000ff'00000000) >> 8) |
|
||||
((x & 0x00000000'ff000000) << 8) |
|
||||
((x & 0x00000000'00ff0000) << 24) |
|
||||
((x & 0x00000000'0000ff00) << 40) |
|
||||
((x & 0x00000000'000000ff) << 56);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using ib16 = BigEndian<int16_t>;
|
||||
using ib32 = BigEndian<int32_t>;
|
||||
using ib64 = BigEndian<int64_t>;
|
||||
using ub16 = BigEndian<uint16_t>;
|
||||
using ub32 = BigEndian<uint32_t>;
|
||||
using ub64 = BigEndian<uint64_t>;
|
||||
|
||||
} // namespace mold
|
||||
+556
-105
@@ -1,23 +1,24 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u64 bits(u64 val, u64 hi, u64 lo) {
|
||||
return (val >> lo) & (((u64)1 << (hi - lo + 1)) - 1);
|
||||
}
|
||||
using E = ARM64;
|
||||
|
||||
static u64 page(u64 val) {
|
||||
return val & 0xffff'ffff'ffff'f000;
|
||||
}
|
||||
|
||||
static u64 encode_page(u64 val) {
|
||||
static u64 page_offset(u64 hi, u64 lo) {
|
||||
u64 val = page(hi) - page(lo);
|
||||
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);
|
||||
void StubsSection<E>::copy_buf(Context<E> &ctx) {
|
||||
ul32 *buf = (ul32 *)(ctx.buf + this->hdr.offset);
|
||||
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
static const u32 insn[] = {
|
||||
@@ -26,22 +27,22 @@ void StubsSection<ARM64>::copy_buf(Context<ARM64> &ctx) {
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == ARM64::stub_size);
|
||||
static_assert(sizeof(insn) == E::stub_size);
|
||||
|
||||
u64 la_addr = ctx.lazy_symbol_ptr.hdr.addr + ARM64::word_size * i;
|
||||
u64 this_addr = this->hdr.addr + ARM64::stub_size * i;
|
||||
u64 la_addr = ctx.lazy_symbol_ptr.hdr.addr + word_size * i;
|
||||
u64 this_addr = this->hdr.addr + E::stub_size * i;
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
buf[0] |= encode_page(page(la_addr) - page(this_addr));
|
||||
buf[0] |= page_offset(la_addr, 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;
|
||||
void StubHelperSection<E>::copy_buf(Context<E> &ctx) {
|
||||
ul32 *start = (ul32 *)(ctx.buf + this->hdr.offset);
|
||||
ul32 *buf = start;
|
||||
|
||||
static const u32 insn0[] = {
|
||||
0x90000011, // adrp x17, $__dyld_private@PAGE
|
||||
@@ -52,15 +53,15 @@ void StubHelperSection<ARM64>::copy_buf(Context<ARM64> &ctx) {
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn0) == ARM64::stub_helper_hdr_size);
|
||||
static_assert(sizeof(insn0) == E::stub_helper_hdr_size);
|
||||
memcpy(buf, insn0, sizeof(insn0));
|
||||
|
||||
u64 dyld_private = get_symbol(ctx, "__dyld_private")->get_addr(ctx);
|
||||
buf[0] |= encode_page(page(dyld_private) - page(this->hdr.addr));
|
||||
buf[0] |= page_offset(dyld_private, 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));
|
||||
u64 stub_binder = get_symbol(ctx, "dyld_stub_binder")->get_got_addr(ctx);
|
||||
buf[3] |= page_offset(stub_binder, this->hdr.addr - 12);
|
||||
buf[4] |= bits(stub_binder, 11, 0) << 10;
|
||||
|
||||
buf += 6;
|
||||
@@ -72,7 +73,7 @@ void StubHelperSection<ARM64>::copy_buf(Context<ARM64> &ctx) {
|
||||
0x00000000, // addr: .long <idx>
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == ARM64::stub_helper_size);
|
||||
static_assert(sizeof(insn) == E::stub_helper_size);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
buf[1] |= bits((start - buf - 1) * 4, 27, 2);
|
||||
@@ -81,73 +82,86 @@ void StubHelperSection<ARM64>::copy_buf(Context<ARM64> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
static i64 read_addend(u8 *buf, const MachRel &r) {
|
||||
if (r.p2size == 2)
|
||||
return *(i32 *)(buf + r.offset);
|
||||
if (r.p2size == 3)
|
||||
return *(i64 *)(buf + r.offset);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
static Relocation<ARM64>
|
||||
read_reloc(Context<ARM64> &ctx, ObjectFile<ARM64> &file,
|
||||
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;
|
||||
std::vector<Relocation<E>>
|
||||
read_relocations(Context<E> &ctx, ObjectFile<E> &file, const MachSection &hdr) {
|
||||
std::vector<Relocation<E>> vec;
|
||||
vec.reserve(hdr.nreloc);
|
||||
|
||||
MachRel *rels = (MachRel *)(file.mf->data + hdr.reloff);
|
||||
for (i64 i = 0; i < hdr.nreloc; i++)
|
||||
vec.push_back(read_reloc(ctx, file, hdr, rels, i));
|
||||
|
||||
for (i64 i = 0; i < hdr.nreloc; i++) {
|
||||
i64 addend = 0;
|
||||
|
||||
switch (rels[i].type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_SUBTRACTOR:
|
||||
switch (rels[i].p2size) {
|
||||
case 2:
|
||||
addend = *(il32 *)((u8 *)file.mf->data + hdr.offset + rels[i].offset);
|
||||
break;
|
||||
case 3:
|
||||
addend = *(il64 *)((u8 *)file.mf->data + hdr.offset + rels[i].offset);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
break;
|
||||
case ARM64_RELOC_ADDEND:
|
||||
addend = rels[i++].idx;
|
||||
break;
|
||||
}
|
||||
|
||||
MachRel &r = rels[i];
|
||||
vec.push_back({r.offset, (u8)r.type, (u8)r.p2size});
|
||||
|
||||
Relocation<E> &rel = vec.back();
|
||||
|
||||
if (i > 0 && rels[i - 1].type == ARM64_RELOC_SUBTRACTOR)
|
||||
rel.is_subtracted = true;
|
||||
|
||||
if (!rel.is_subtracted && rels[i].type != ARM64_RELOC_SUBTRACTOR)
|
||||
rel.is_pcrel = r.is_pcrel;
|
||||
|
||||
if (r.is_extern) {
|
||||
rel.sym = file.syms[r.idx];
|
||||
rel.addend = addend;
|
||||
continue;
|
||||
}
|
||||
|
||||
u64 addr = r.is_pcrel ? (hdr.addr + r.offset + addend) : addend;
|
||||
Subsection<E> *target = file.find_subsection(ctx, r.idx - 1, addr);
|
||||
if (!target)
|
||||
Fatal(ctx) << file << ": bad relocation: " << r.offset;
|
||||
|
||||
rel.subsec = target;
|
||||
rel.addend = addr - target->input_addr;
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<ARM64>::scan_relocations(Context<ARM64> &ctx) {
|
||||
for (Relocation<ARM64> &r : get_rels()) {
|
||||
Symbol<ARM64> *sym = r.sym;
|
||||
void Subsection<E>::scan_relocations(Context<E> &ctx) {
|
||||
for (Relocation<E> &r : get_rels()) {
|
||||
Symbol<E> *sym = r.sym;
|
||||
if (!sym)
|
||||
continue;
|
||||
|
||||
if (sym->is_imported && sym->file->is_dylib)
|
||||
((DylibFile<E> *)sym->file)->is_alive = true;
|
||||
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
if (sym->is_imported) {
|
||||
if (r.p2size != 3) {
|
||||
Error(ctx) << this->isec << ": " << r << " relocation at offset 0x"
|
||||
<< std::hex << r.offset << " against symbol `"
|
||||
<< *sym << "' can not be used";
|
||||
}
|
||||
r.needs_dynrel = true;
|
||||
}
|
||||
break;
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
@@ -159,94 +173,108 @@ void Subsection<ARM64>::scan_relocations(Context<ARM64> &ctx) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (sym->file && sym->file->is_dylib()) {
|
||||
if (sym->is_imported)
|
||||
sym->flags |= NEEDS_STUB;
|
||||
((DylibFile<ARM64> *)sym->file)->is_needed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<ARM64>::apply_reloc(Context<ARM64> &ctx, u8 *buf) {
|
||||
std::span<Relocation<ARM64>> rels = get_rels();
|
||||
void Subsection<E>::apply_reloc(Context<E> &ctx, u8 *buf) {
|
||||
std::span<Relocation<E>> rels = get_rels();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
Relocation<ARM64> &r = rels[i];
|
||||
Relocation<E> &r = rels[i];
|
||||
u8 *loc = buf + r.offset;
|
||||
i64 val = r.addend;
|
||||
u64 pc = get_addr(ctx) + r.offset;
|
||||
|
||||
if (r.sym && !r.sym->file) {
|
||||
Error(ctx) << "undefined symbol: " << isec.file << ": " << *r.sym;
|
||||
continue;
|
||||
}
|
||||
|
||||
u64 val = 0;
|
||||
|
||||
// Compute a relocated value.
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_BRANCH26:
|
||||
case ARM64_RELOC_PAGE21:
|
||||
case ARM64_RELOC_PAGEOFF12:
|
||||
val = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
val += r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
break;
|
||||
case ARM64_RELOC_SUBTRACTOR: {
|
||||
Relocation<ARM64> s = rels[++i];
|
||||
Relocation<E> s = rels[++i];
|
||||
assert(s.type == ARM64_RELOC_UNSIGNED);
|
||||
u64 val1 = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
u64 val2 = s.sym ? s.sym->get_addr(ctx) : s.subsec->get_addr(ctx);
|
||||
val = val2 - val1;
|
||||
val += val2 - val1;
|
||||
break;
|
||||
}
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
val = r.sym->get_got_addr(ctx);
|
||||
val += r.sym->get_got_addr(ctx);
|
||||
break;
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12:
|
||||
val = r.sym->get_tlv_addr(ctx);
|
||||
val += r.sym->get_tlv_addr(ctx);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
|
||||
}
|
||||
|
||||
val += r.addend;
|
||||
// An address of a thread-local variable is computed as an offset
|
||||
// to the beginning of the first thread-local section.
|
||||
if (isec.hdr.type == S_THREAD_LOCAL_VARIABLES)
|
||||
val -= ctx.tls_begin;
|
||||
|
||||
// Write a computed value to the output buffer.
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_SUBTRACTOR:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
if (r.is_pcrel)
|
||||
val -= get_addr(ctx) + r.offset;
|
||||
val -= pc;
|
||||
|
||||
if (r.p2size == 2)
|
||||
*(i32 *)(buf + r.offset) = val;
|
||||
*(ul32 *)loc = val;
|
||||
else if (r.p2size == 3)
|
||||
*(i64 *)(buf + r.offset) = val;
|
||||
*(ul64 *)loc = val;
|
||||
else
|
||||
unreachable();
|
||||
break;
|
||||
case ARM64_RELOC_BRANCH26:
|
||||
if (r.is_pcrel)
|
||||
val -= get_addr(ctx) + r.offset;
|
||||
*(u32 *)(buf + r.offset) |= bits(val, 27, 2);
|
||||
case ARM64_RELOC_BRANCH26: {
|
||||
assert(r.is_pcrel);
|
||||
val -= pc;
|
||||
|
||||
i64 lo = -(1 << 27);
|
||||
i64 hi = 1 << 27;
|
||||
|
||||
if (val < lo || hi <= val) {
|
||||
val = isec.osec.thunks[r.thunk_idx]->get_addr(r.thunk_sym_idx) - pc;
|
||||
assert(lo <= val && val < hi);
|
||||
}
|
||||
|
||||
*(ul32 *)loc |= bits(val, 27, 2);
|
||||
break;
|
||||
}
|
||||
case ARM64_RELOC_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21:
|
||||
assert(r.is_pcrel);
|
||||
*(u32 *)(buf + r.offset) |=
|
||||
encode_page(page(val) - page(get_addr(ctx) + r.offset));
|
||||
*(ul32 *)loc |= page_offset(val, pc);
|
||||
break;
|
||||
case ARM64_RELOC_PAGEOFF12:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12: {
|
||||
if (r.is_pcrel)
|
||||
val -= get_addr(ctx) + r.offset;
|
||||
|
||||
u32 insn = *(u32 *)(buf + r.offset);
|
||||
assert(!r.is_pcrel);
|
||||
u32 insn = *(ul32 *)loc;
|
||||
i64 scale = 0;
|
||||
if ((insn & 0x3b000000) == 0x39000000)
|
||||
scale = insn >> 30;
|
||||
*(u32 *)(buf + r.offset) |= bits(val, 11, scale) << 10;
|
||||
if ((insn & 0x3b000000) == 0x39000000) {
|
||||
scale = bits(insn, 31, 30);
|
||||
if (scale == 0 && (insn & 0x04800000) == 0x04800000)
|
||||
scale = 4;
|
||||
}
|
||||
*(ul32 *)loc |= bits(val, 11, scale) << 10;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -255,4 +283,427 @@ void Subsection<ARM64>::apply_reloc(Context<ARM64> &ctx, u8 *buf) {
|
||||
}
|
||||
}
|
||||
|
||||
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
for (Symbol<E> *sym : thunk.symbols) {
|
||||
sym->thunk_idx = -1;
|
||||
sym->thunk_sym_idx = -1;
|
||||
sym->flags &= (u8)~NEEDS_RANGE_EXTN_THUNK;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
|
||||
Subsection<E> &subsec, Relocation<E> &rel) {
|
||||
// We pessimistically assume that PLT entries are unreacahble.
|
||||
if (sym.stub_idx != -1)
|
||||
return false;
|
||||
|
||||
// We create thunks with a pessimistic assumption that all
|
||||
// out-of-section relocations would be out-of-range.
|
||||
if (!sym.subsec || &sym.subsec->isec.osec != &subsec.isec.osec)
|
||||
return false;
|
||||
|
||||
if (sym.subsec->output_offset == -1)
|
||||
return false;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol
|
||||
// and check if they are within reach.
|
||||
i64 addr = sym.get_addr(ctx);
|
||||
i64 pc = subsec.get_addr(ctx) + rel.offset;
|
||||
i64 val = addr + rel.addend - pc;
|
||||
return -(1 << 27) <= val && val < (1 << 27);
|
||||
}
|
||||
|
||||
// We create a thunk no further than 100 MiB from any section.
|
||||
static constexpr i64 MAX_DISTANCE = 100 * 1024 * 1024;
|
||||
|
||||
// We create a thunk for each 10 MiB input sections.
|
||||
static constexpr i64 GROUP_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
// ARM64's call/jump instructions take 27 bits displacement, so they
|
||||
// can refer only up to ±128 MiB. If a branch target is further than
|
||||
// that, we need to let it branch to a linker-synthesized code
|
||||
// sequence that construct a full 32 bit address in a register and
|
||||
// jump there. That linker-synthesized code is called "thunk".
|
||||
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
std::span<Subsection<E> *> members = osec.members;
|
||||
if (members.empty())
|
||||
return;
|
||||
|
||||
members[0]->output_offset = 0;
|
||||
|
||||
// Initialize input sections with a dummy offset so that we can
|
||||
// distinguish sections that have got an address with the one who
|
||||
// haven't.
|
||||
tbb::parallel_for((i64)1, (i64)members.size(), [&](i64 i) {
|
||||
members[i]->output_offset = -1;
|
||||
});
|
||||
|
||||
// We create thunks from the beginning of the section to the end.
|
||||
// We manage progress using four offsets which increase monotonically.
|
||||
// The locations they point to are always A <= B <= C <= D.
|
||||
i64 a = 0;
|
||||
i64 b = 0;
|
||||
i64 c = 0;
|
||||
i64 d = 0;
|
||||
i64 offset = 0;
|
||||
|
||||
while (b < members.size()) {
|
||||
// Move D foward as far as we can jump from B to D.
|
||||
while (d < members.size() &&
|
||||
offset - members[b]->output_offset < MAX_DISTANCE) {
|
||||
offset = align_to(offset, 1 << members[d]->p2align);
|
||||
members[d]->output_offset = offset;
|
||||
offset += members[d]->input_size;
|
||||
d++;
|
||||
}
|
||||
|
||||
// Move C forward so that C is apart from B by GROUP_SIZE.
|
||||
while (c < members.size() &&
|
||||
members[c]->output_offset - members[b]->output_offset < GROUP_SIZE)
|
||||
c++;
|
||||
|
||||
// Move A forward so that A is reachable from C.
|
||||
if (c > 0) {
|
||||
i64 c_end = members[c - 1]->output_offset + members[c - 1]->input_size;
|
||||
while (a < osec.thunks.size() &&
|
||||
osec.thunks[a]->offset < c_end - MAX_DISTANCE)
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
}
|
||||
|
||||
// Create a thunk for input sections between B and C and place it at D.
|
||||
osec.thunks.emplace_back(new RangeExtensionThunk<E>{osec});
|
||||
|
||||
RangeExtensionThunk<E> &thunk = *osec.thunks.back();
|
||||
thunk.thunk_idx = osec.thunks.size() - 1;
|
||||
thunk.offset = offset;
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need thunks.
|
||||
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
|
||||
[&](Subsection<E> *subsec) {
|
||||
std::span<Relocation<E>> rels = subsec->get_rels();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
Relocation<E> &r = rels[i];
|
||||
if (!r.sym->file || r.type != ARM64_RELOC_BRANCH26)
|
||||
continue;
|
||||
|
||||
// Skip if the destination is within reach.
|
||||
if (is_reachable(ctx, *r.sym, *subsec, r))
|
||||
continue;
|
||||
|
||||
// If the symbol is already in another thunk, reuse it.
|
||||
if (r.sym->thunk_idx != -1) {
|
||||
r.thunk_idx = r.sym->thunk_idx;
|
||||
r.thunk_sym_idx = r.sym->thunk_sym_idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the symbol to this thunk if it's not added already.
|
||||
r.thunk_idx = thunk.thunk_idx;
|
||||
r.thunk_sym_idx = -1;
|
||||
|
||||
if (!(r.sym->flags.fetch_or(NEEDS_RANGE_EXTN_THUNK) &
|
||||
NEEDS_RANGE_EXTN_THUNK)) {
|
||||
std::scoped_lock lock(thunk.mu);
|
||||
thunk.symbols.push_back(r.sym);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Now that we know the number of symbols in the thunk, we can compute
|
||||
// its size.
|
||||
offset += thunk.size();
|
||||
|
||||
// Sort symbols added to the thunk to make the output deterministic.
|
||||
sort(thunk.symbols, [](Symbol<E> *a, Symbol<E> *b) {
|
||||
return std::tuple{a->file->priority, a->value} <
|
||||
std::tuple{b->file->priority, b->value};
|
||||
});
|
||||
|
||||
// Assign offsets within the thunk to the symbols.
|
||||
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
|
||||
sym->thunk_idx = thunk.thunk_idx;
|
||||
sym->thunk_sym_idx = i++;
|
||||
}
|
||||
|
||||
// Scan relocations again to fix symbol offsets in the last thunk.
|
||||
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
|
||||
[&](Subsection<E> *subsec) {
|
||||
for (Relocation<E> &r : subsec->get_rels())
|
||||
if (r.thunk_idx == thunk.thunk_idx)
|
||||
r.thunk_sym_idx = r.sym->thunk_sym_idx;
|
||||
});
|
||||
|
||||
// Move B forward to point to the begining of the next group.
|
||||
b = c;
|
||||
}
|
||||
|
||||
while (a < osec.thunks.size())
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
|
||||
osec.hdr.size = offset;
|
||||
}
|
||||
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.hdr.offset + offset;
|
||||
|
||||
static const u32 data[] = {
|
||||
0x90000010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
|
||||
0x91000210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(ENTRY_SIZE == sizeof(data));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 addr = symbols[i]->get_addr(ctx);
|
||||
u64 pc = output_section.hdr.addr + offset + i * ENTRY_SIZE;
|
||||
|
||||
u8 *loc = buf + i * ENTRY_SIZE;
|
||||
memcpy(loc , data, sizeof(data));
|
||||
*(ul32 *)loc |= page_offset(addr, pc);
|
||||
*(ul32 *)(loc + 4) |= bits(addr, 11, 0) << 10;
|
||||
}
|
||||
}
|
||||
|
||||
#define ASSERT_RANGE(val, start, size) \
|
||||
assert((start) <= (val) && (val) < ((start) + (size)))
|
||||
|
||||
// Rewrite
|
||||
// ldr Xb/Wb, [Xa, #imm]
|
||||
// with
|
||||
// ldr Xb/Wb, [#base_addr + #imm]
|
||||
// if we can.
|
||||
static bool relax_ldr(ul32 *loc, u64 loc_addr, u64 base_addr) {
|
||||
switch (*loc & 0xffc0'0000) {
|
||||
case 0xf940'0000: { // ldr Xb, [Xa, #imm]
|
||||
u64 imm = bits(*loc, 21, 10) * 8;
|
||||
i64 disp = base_addr + imm - loc_addr;
|
||||
if (disp == sign_extend(disp, 20) && (disp & 0b11) == 0) {
|
||||
// ldr Xc, [#base_addr + #imm]
|
||||
*loc = 0x5800'0000 | (bits(disp, 20, 2) << 5) | bits(*loc, 4, 0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case 0xb940'0000: { // ldr Wb, [Xa, #imm]
|
||||
u64 imm = bits(*loc, 21, 10) * 4;
|
||||
i64 disp = base_addr + imm - loc_addr;
|
||||
if (disp == sign_extend(disp, 20) && (disp & 0b11) == 0) {
|
||||
// ldr Wc, [#base_addr + #imm]
|
||||
*loc = 0x1800'0000 | (bits(disp, 20, 2) << 5) | bits(*loc, 4, 0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_adrp(ul32 *loc) {
|
||||
return (*loc & 0x9f00'0000) == 0x9000'0000;
|
||||
}
|
||||
|
||||
static u64 get_adrp_imm(ul32 *loc) {
|
||||
return (bits(*loc, 23, 5) << 14) + (bits(*loc, 30, 29) << 12);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void relax_adrp_ldr_got_ldr(Context<E> &ctx,
|
||||
ul32 *loc1, ul32 *loc2, ul32 *loc3,
|
||||
u64 addr1, u64 addr2, u64 addr3,
|
||||
ObjectFile<E> *file) {
|
||||
// We expect the following instructions for a GOT-indirect load:
|
||||
//
|
||||
// adrp Xa, _foo@GOTPAGE
|
||||
// ldr Xb, [Xa, _foo@GOTPAGEOFF]
|
||||
// ldr/ldrb/ldrsb/ldrsh/... Xc/Wc, [Xb, #imm]
|
||||
if (!is_adrp(loc1) || (*loc2 & 0xffc0'0000) != 0xf940'0000)
|
||||
return;
|
||||
|
||||
u64 got_page = page(addr1) + get_adrp_imm(loc1);
|
||||
u64 pageoff = bits(*loc2, 21, 10) << 3;
|
||||
|
||||
if (got_page + pageoff < ctx.got.hdr.addr ||
|
||||
ctx.got.hdr.addr + ctx.got.hdr.size <= got_page + pageoff)
|
||||
Fatal(ctx) << *file << ": LDR_GOT_LDR out of range";
|
||||
|
||||
ASSERT_RANGE(got_page + pageoff, ctx.got.hdr.addr, ctx.got.hdr.size);
|
||||
|
||||
u64 got_value = *(ul64 *)(ctx.buf + ctx.got.hdr.offset + got_page + pageoff -
|
||||
ctx.got.hdr.addr);
|
||||
|
||||
// If the GOT slot is already filled with a value and we can
|
||||
// inline the value, do it.
|
||||
if (got_value && relax_ldr(loc3, addr3, got_value)) {
|
||||
*loc1 = 0xd503'201f; // nop
|
||||
*loc2 = 0xd503'201f; // nop
|
||||
return;
|
||||
}
|
||||
|
||||
// If the GOT slot is close enough to PC, we can eliminate ADRP.
|
||||
if (relax_ldr(loc2, addr2, got_page))
|
||||
*loc1 = 0xd503'201f; // nop
|
||||
}
|
||||
|
||||
static void relax_adrp_adrp(Context<E> &ctx, ul32 *loc1, ul32 *loc2,
|
||||
u64 addr1, u64 addr2) {
|
||||
// adrp Xa, _foo@PAGE
|
||||
// adrp Xa, _bar@PAGE
|
||||
// ->
|
||||
// ldr Xa, _foo@PAGE
|
||||
// nop
|
||||
|
||||
if (is_adrp(loc1) && is_adrp(loc2) &&
|
||||
page(addr1) + get_adrp_imm(loc1) == page(addr2) + get_adrp_imm(loc2) &&
|
||||
bits(*loc1, 4, 0) == bits(*loc2, 4, 0))
|
||||
*loc2 = 0xd503'201f; // nop
|
||||
}
|
||||
|
||||
static void relax_adrp_ldr(Context<E> &ctx, ul32 *loc1, ul32 *loc2,
|
||||
u64 addr1, u64 addr2) {
|
||||
// adrp Xa, _foo@PAGE
|
||||
// ldr Xb/Wb, [Xa, _foo@PAGEOFF]
|
||||
// ->
|
||||
// nop
|
||||
// ldr Xb/Wb, [_foo]
|
||||
if (is_adrp(loc1) && relax_ldr(loc2, addr2, page(addr1) + get_adrp_imm(loc1)))
|
||||
*loc1 = 0xd503'201f; // nop
|
||||
}
|
||||
|
||||
static void relax_adrp_add(Context<E> &ctx, ul32 *loc1, ul32 *loc2,
|
||||
u64 addr1, u64 addr2) {
|
||||
// adrp Xa, _foo@PAGE
|
||||
// add Xb, Xa, _foo@PAGEOFF
|
||||
// ->
|
||||
// nop
|
||||
// adr Xb, _foo
|
||||
if (!is_adrp(loc1) || (*loc2 & 0xffc0'0000) != 0x9100'0000)
|
||||
return;
|
||||
|
||||
i64 disp = page(addr1) + get_adrp_imm(loc1) + bits(*loc2, 21, 10) - addr2;
|
||||
|
||||
if (disp == sign_extend(disp, 20)) {
|
||||
*loc1 = 0xd503'201f; // nop
|
||||
*loc2 = 0x1000'0000 | (bits(disp, 1, 0) << 29) |
|
||||
(bits(disp, 20, 2) << 5) | bits(*loc2, 4, 0); // adr Xb, _foo
|
||||
}
|
||||
}
|
||||
|
||||
static Subsection<E> *find_subsection_by_addr(ObjectFile<E> *file, u32 addr) {
|
||||
for (Subsection<E> *subsec : file->subsections)
|
||||
if (subsec->input_addr <= addr &&
|
||||
addr < subsec->input_addr + subsec->input_size)
|
||||
return subsec;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// On ARM, we generally need two or more instructions to materialize
|
||||
// an address of an object in a register or jump to a function.
|
||||
// However, if an object or a function is close enough to PC, a single
|
||||
// instruction suffices.
|
||||
//
|
||||
// This function replaces such redundant instruction sequences with
|
||||
// shorter instruction sequences. We pad holes with NOPs, so the total
|
||||
// number of instructions won't change by this relaxation, but replacing
|
||||
// instructions with NOPs generally makes program faster since the CPU
|
||||
// has a special logic to skip NOPs as quickly as possible.
|
||||
//
|
||||
// Locations of relaxable instructions are in the
|
||||
// LC_LINKER_OPTIMIZATION_HINT segment. That segment contains a
|
||||
// sequence of ULEB-encoded integers.
|
||||
//
|
||||
// This pass is optional; an output file works correctly without
|
||||
// applying these optimizations.
|
||||
void apply_linker_optimization_hints(Context<E> &ctx) {
|
||||
Timer t(ctx, "apply_linker_optimization_hints");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
std::string_view hints = file->get_linker_optimization_hints(ctx);
|
||||
|
||||
while (!hints.empty()) {
|
||||
i64 type = read_uleb(hints);
|
||||
if (type == 0)
|
||||
return;
|
||||
|
||||
i64 nargs = read_uleb(hints);
|
||||
|
||||
switch (type) {
|
||||
case LOH_ARM64_ADRP_LDR_GOT_LDR: {
|
||||
assert(nargs == 3);
|
||||
i64 input_addr1 = read_uleb(hints);
|
||||
i64 input_addr2 = read_uleb(hints);
|
||||
i64 input_addr3 = read_uleb(hints);
|
||||
|
||||
Subsection<E> *subsec = find_subsection_by_addr(file, input_addr1);
|
||||
if (!subsec || !subsec->is_alive)
|
||||
return;
|
||||
|
||||
ASSERT_RANGE(input_addr1, subsec->input_addr, subsec->input_size);
|
||||
ASSERT_RANGE(input_addr2, subsec->input_addr, subsec->input_size);
|
||||
ASSERT_RANGE(input_addr3, subsec->input_addr, subsec->input_size);
|
||||
|
||||
i64 offset1 = subsec->input_addr - input_addr1;
|
||||
i64 offset2 = subsec->input_addr - input_addr2;
|
||||
i64 offset3 = subsec->input_addr - input_addr3;
|
||||
|
||||
u8 *loc = ctx.buf + subsec->isec.osec.hdr.offset + subsec->output_offset;
|
||||
ul32 *loc1 = (ul32 *)(loc + offset1);
|
||||
ul32 *loc2 = (ul32 *)(loc + offset2);
|
||||
ul32 *loc3 = (ul32 *)(loc + offset3);
|
||||
|
||||
u64 addr1 = subsec->get_addr(ctx) + offset1;
|
||||
u64 addr2 = subsec->get_addr(ctx) + offset2;
|
||||
u64 addr3 = subsec->get_addr(ctx) + offset3;
|
||||
|
||||
relax_adrp_ldr_got_ldr(ctx, loc1, loc2, loc3, addr1, addr2, addr3,
|
||||
file);
|
||||
break;
|
||||
}
|
||||
case LOH_ARM64_ADRP_ADRP:
|
||||
case LOH_ARM64_ADRP_LDR:
|
||||
case LOH_ARM64_ADRP_ADD: {
|
||||
assert(nargs == 2);
|
||||
i64 input_addr1 = read_uleb(hints);
|
||||
i64 input_addr2 = read_uleb(hints);
|
||||
|
||||
Subsection<E> *subsec = find_subsection_by_addr(file, input_addr1);
|
||||
if (!subsec || !subsec->is_alive)
|
||||
return;
|
||||
|
||||
ASSERT_RANGE(input_addr1, subsec->input_addr, subsec->input_size);
|
||||
ASSERT_RANGE(input_addr2, subsec->input_addr, subsec->input_size);
|
||||
|
||||
i64 offset1 = subsec->input_addr - input_addr1;
|
||||
i64 offset2 = subsec->input_addr - input_addr2;
|
||||
|
||||
u8 *loc = ctx.buf + subsec->isec.osec.hdr.offset + subsec->output_offset;
|
||||
ul32 *loc1 = (ul32 *)(loc + offset1);
|
||||
ul32 *loc2 = (ul32 *)(loc + offset2);
|
||||
|
||||
u64 addr1 = subsec->get_addr(ctx) + offset1;
|
||||
u64 addr2 = subsec->get_addr(ctx) + offset2;
|
||||
|
||||
switch (type) {
|
||||
case LOH_ARM64_ADRP_ADRP:
|
||||
relax_adrp_adrp(ctx, loc1, loc2, addr1, addr2);
|
||||
break;
|
||||
case LOH_ARM64_ADRP_LDR:
|
||||
relax_adrp_ldr(ctx, loc1, loc2, addr1, addr2);
|
||||
break;
|
||||
case LOH_ARM64_ADRP_ADD:
|
||||
relax_adrp_add(ctx, loc1, loc2, addr1, addr2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Skip unsupported optimizations hints.
|
||||
for (i64 i = 0; i < nargs; i++)
|
||||
read_uleb(hints);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
+93
-99
@@ -2,25 +2,26 @@
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
using E = X86_64;
|
||||
|
||||
template <>
|
||||
void StubsSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
void StubsSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->hdr.offset;
|
||||
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
// `ff 25 xx xx xx xx` is a RIP-relative indirect jump instruction,
|
||||
// i.e., `jmp *IMM(%rip)`. It loads an address from la_symbol_ptr
|
||||
// and jump there.
|
||||
static_assert(X86_64::stub_size == 6);
|
||||
static_assert(E::stub_size == 6);
|
||||
buf[i * 6] = 0xff;
|
||||
buf[i * 6 + 1] = 0x25;
|
||||
*(u32 *)(buf + i * 6 + 2) =
|
||||
ctx.lazy_symbol_ptr.hdr.addr + i * X86_64::word_size -
|
||||
(this->hdr.addr + i * 6 + 6);
|
||||
*(ul32 *)(buf + i * 6 + 2) =
|
||||
ctx.lazy_symbol_ptr.hdr.addr + i * word_size - (this->hdr.addr + i * 6 + 6);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void StubHelperSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
void StubHelperSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *start = ctx.buf + this->hdr.offset;
|
||||
u8 *buf = start;
|
||||
|
||||
@@ -31,12 +32,12 @@ void StubHelperSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
0x90, // nop
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn0) == X86_64::stub_helper_hdr_size);
|
||||
static_assert(sizeof(insn0) == E::stub_helper_hdr_size);
|
||||
|
||||
memcpy(buf, insn0, sizeof(insn0));
|
||||
*(u32 *)(buf + 3) =
|
||||
*(ul32 *)(buf + 3) =
|
||||
get_symbol(ctx, "__dyld_private")->get_addr(ctx) - this->hdr.addr - 7;
|
||||
*(u32 *)(buf + 11) =
|
||||
*(ul32 *)(buf + 11) =
|
||||
get_symbol(ctx, "dyld_stub_binder")->get_got_addr(ctx) - this->hdr.addr - 15;
|
||||
|
||||
buf += 16;
|
||||
@@ -47,11 +48,11 @@ void StubHelperSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
0xe9, 0, 0, 0, 0, // jmp $__stub_helper
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == X86_64::stub_helper_size);
|
||||
static_assert(sizeof(insn) == E::stub_helper_size);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(u32 *)(buf + 1) = ctx.stubs.bind_offsets[i];
|
||||
*(u32 *)(buf + 6) = start - buf - 10;
|
||||
*(ul32 *)(buf + 1) = ctx.stubs.bind_offsets[i];
|
||||
*(ul32 *)(buf + 6) = start - buf - 10;
|
||||
buf += 10;
|
||||
}
|
||||
}
|
||||
@@ -71,102 +72,101 @@ static i64 get_reloc_addend(u32 type) {
|
||||
|
||||
static i64 read_addend(u8 *buf, const MachRel &r) {
|
||||
if (r.p2size == 2)
|
||||
return *(i32 *)(buf + r.offset) + get_reloc_addend(r.type);
|
||||
return *(il32 *)(buf + r.offset) + get_reloc_addend(r.type);
|
||||
if (r.p2size == 3)
|
||||
return *(i64 *)(buf + r.offset) + get_reloc_addend(r.type);
|
||||
return *(il64 *)(buf + r.offset) + get_reloc_addend(r.type);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
static Relocation<X86_64>
|
||||
read_reloc(Context<X86_64> &ctx, ObjectFile<X86_64> &file,
|
||||
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,
|
||||
std::vector<Relocation<E>>
|
||||
read_relocations(Context<E> &ctx, ObjectFile<E> &file,
|
||||
const MachSection &hdr) {
|
||||
std::vector<Relocation<X86_64>> vec;
|
||||
std::vector<Relocation<E>> vec;
|
||||
vec.reserve(hdr.nreloc);
|
||||
|
||||
MachRel *rels = (MachRel *)(file.mf->data + hdr.reloff);
|
||||
for (i64 i = 0; i < hdr.nreloc; i++)
|
||||
vec.push_back(read_reloc(ctx, file, hdr, rels[i]));
|
||||
|
||||
for (i64 i = 0; i < hdr.nreloc; i++) {
|
||||
MachRel &r = rels[i];
|
||||
i64 addend = read_addend((u8 *)file.mf->data + hdr.offset, r);
|
||||
|
||||
vec.push_back({r.offset, (u8)r.type, (u8)r.p2size});
|
||||
Relocation<E> &rel = vec.back();
|
||||
|
||||
if (i > 0 && rels[i - 1].type == X86_64_RELOC_SUBTRACTOR)
|
||||
rel.is_subtracted = true;
|
||||
|
||||
if (!rel.is_subtracted && rels[i].type != X86_64_RELOC_SUBTRACTOR)
|
||||
rel.is_pcrel = r.is_pcrel;
|
||||
|
||||
if (r.is_extern) {
|
||||
rel.sym = file.syms[r.idx];
|
||||
rel.addend = addend;
|
||||
continue;
|
||||
}
|
||||
|
||||
u64 addr = r.is_pcrel ? (hdr.addr + r.offset + addend + 4) : addend;
|
||||
Subsection<E> *target = file.find_subsection(ctx, r.idx - 1, addr);
|
||||
if (!target)
|
||||
Fatal(ctx) << file << ": bad relocation: " << r.offset;
|
||||
|
||||
rel.subsec = target;
|
||||
rel.addend = addr - target->input_addr;
|
||||
}
|
||||
|
||||
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;
|
||||
void Subsection<E>::scan_relocations(Context<E> &ctx) {
|
||||
for (Relocation<E> &r : get_rels()) {
|
||||
Symbol<E> *sym = r.sym;
|
||||
if (!sym)
|
||||
continue;
|
||||
|
||||
if (sym->is_imported && sym->file->is_dylib)
|
||||
((DylibFile<E> *)sym->file)->is_alive = true;
|
||||
|
||||
switch (r.type) {
|
||||
case X86_64_RELOC_GOT_LOAD:
|
||||
if (sym->file && sym->file->is_dylib())
|
||||
sym->flags |= NEEDS_GOT;
|
||||
case X86_64_RELOC_UNSIGNED:
|
||||
case X86_64_RELOC_SUBTRACTOR:
|
||||
if (sym->is_imported) {
|
||||
if (r.p2size != 3) {
|
||||
Error(ctx) << this->isec << ": " << r << " relocation at offset 0x"
|
||||
<< std::hex << r.offset << " against symbol `"
|
||||
<< *sym << "' can not be used";
|
||||
}
|
||||
r.needs_dynrel = true;
|
||||
}
|
||||
break;
|
||||
case X86_64_RELOC_GOT:
|
||||
case X86_64_RELOC_GOT_LOAD:
|
||||
sym->flags |= NEEDS_GOT;
|
||||
break;
|
||||
case X86_64_RELOC_TLV:
|
||||
if (sym->file && sym->file->is_dylib())
|
||||
sym->flags |= NEEDS_THREAD_PTR;
|
||||
sym->flags |= NEEDS_THREAD_PTR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sym->file && sym->file->is_dylib()) {
|
||||
if (sym->is_imported)
|
||||
sym->flags |= NEEDS_STUB;
|
||||
((DylibFile<X86_64> *)sym->file)->is_needed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<X86_64>::apply_reloc(Context<X86_64> &ctx, u8 *buf) {
|
||||
for (const Relocation<X86_64> &r : get_rels()) {
|
||||
void Subsection<E>::apply_reloc(Context<E> &ctx, u8 *buf) {
|
||||
std::span<Relocation<E>> rels = get_rels();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
Relocation<E> &r = rels[i];
|
||||
u64 val = r.addend;
|
||||
|
||||
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:
|
||||
@@ -174,46 +174,40 @@ void Subsection<X86_64>::apply_reloc(Context<X86_64> &ctx, u8 *buf) {
|
||||
case X86_64_RELOC_SIGNED_1:
|
||||
case X86_64_RELOC_SIGNED_2:
|
||||
case X86_64_RELOC_SIGNED_4:
|
||||
val = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
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);
|
||||
}
|
||||
case X86_64_RELOC_SUBTRACTOR: {
|
||||
Relocation<E> s = rels[++i];
|
||||
assert(s.type == X86_64_RELOC_UNSIGNED);
|
||||
assert(r.p2size == s.p2size);
|
||||
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 X86_64_RELOC_GOT:
|
||||
val = r.sym->get_got_addr(ctx);
|
||||
case X86_64_RELOC_GOT_LOAD:
|
||||
val += r.sym->get_got_addr(ctx);
|
||||
break;
|
||||
case X86_64_RELOC_TLV:
|
||||
if (r.sym->tlv_idx != -1) {
|
||||
val = r.sym->get_tlv_addr(ctx);
|
||||
} else {
|
||||
// Relax MOVQ into LEAQ
|
||||
if (buf[r.offset - 2] != 0x8b)
|
||||
Error(ctx) << isec << ": invalid TLV relocation";
|
||||
buf[r.offset - 2] = 0x8d;
|
||||
val = r.sym->get_addr(ctx);
|
||||
}
|
||||
val += r.sym->get_tlv_addr(ctx);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
|
||||
}
|
||||
|
||||
val += r.addend;
|
||||
|
||||
if (r.is_pcrel)
|
||||
if (isec.hdr.type == S_THREAD_LOCAL_VARIABLES) {
|
||||
// An address of a thread-local variable is computed as an offset
|
||||
// to the beginning of the first thread-local section.
|
||||
val -= ctx.tls_begin;
|
||||
} else if (r.is_pcrel) {
|
||||
val -= get_addr(ctx) + r.offset + 4 + get_reloc_addend(r.type);
|
||||
}
|
||||
|
||||
if (r.p2size == 2)
|
||||
*(u32 *)(buf + r.offset) = val;
|
||||
*(ul32 *)(buf + r.offset) = val;
|
||||
else if (r.p2size == 3)
|
||||
*(u64 *)(buf + r.offset) = val;
|
||||
*(ul64 *)(buf + r.offset) = val;
|
||||
else
|
||||
unreachable();
|
||||
}
|
||||
|
||||
+279
-28
@@ -2,56 +2,112 @@
|
||||
#include "../cmdline.h"
|
||||
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <regex>
|
||||
#include <unistd.h>
|
||||
#include <unordered_set>
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
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
|
||||
-U <SYMBOL> Allow a symbol to be undefined
|
||||
-Z Do not search the standard directories when
|
||||
searching for libraries and frameworks
|
||||
-add_ast_path <FILE> Add a N_AST symbol with the given filename
|
||||
-add_empty_section <SEGNAME> <SECTNAME>
|
||||
Add an empty section
|
||||
-adhoc_codesign Add ad-hoc code signature to the output file
|
||||
-no_adhoc_codesign
|
||||
-all_load Include all objects from static archives
|
||||
-noall_load
|
||||
-application_extension Verify that all dylibs are extension-safe
|
||||
-no_application_extension
|
||||
-arch <ARCH_NAME> Specify target architecture
|
||||
-bundle Produce a mach-o bundle
|
||||
-bundle_loader <EXECUTABLE> Resolve undefined symbols using the given executable
|
||||
-compatibility_version <VERSION>
|
||||
Specifies the compatibility version number of the library
|
||||
-current_version <VERSION> Specifies the current version number of the library.
|
||||
-dead_strip Remove unreachable functions and data
|
||||
-dead_strip_dylibs Remove unreachable dylibs from dependencies
|
||||
-debug_variant Ignored
|
||||
-demangle Demangle C++ symbols in log messages (default)
|
||||
-dependency_info <FILE> Ignored
|
||||
-dylib Produce a dynamic library
|
||||
-dylib_compatibility_version <VERSION>
|
||||
Alias for -compatibility_version
|
||||
-dylib_current_version <VERSION>
|
||||
Alias for -current_version
|
||||
-dylib_install_name Alias for -install_name
|
||||
-dynamic Link against dylibs (default)
|
||||
-e <SYMBOL> Specify the entry point of a main executable
|
||||
-execute Produce an executable (default)
|
||||
-export_dynamic Preserves all global symbols in main executables during LTO
|
||||
-exported_symbol <SYMBOL> Export a given symbol
|
||||
-exported_symbols_list <FILE>
|
||||
Read a list of exported symbols from a given file
|
||||
-filelist <FILE>[,<DIR>] Specify the list of input file names
|
||||
-final_output <NAME>
|
||||
-force_load <FILE> Include all objects from a given static archive
|
||||
-framework <NAME>,[,<SUFFIX>]
|
||||
Search for a given framework
|
||||
-headerpad <SIZE> Allocate the size of padding after load commands
|
||||
-headerpad_max_install_names
|
||||
Allocate MAXPATHLEN byte padding after load commands
|
||||
-help Report usage information
|
||||
-hidden-l<LIB>
|
||||
-ignore_optimization_hints Do not rewrite instructions as optimization (default)
|
||||
-enable_optimization_hints
|
||||
-install_name <NAME>
|
||||
-l<LIB> Search for a given library
|
||||
-lto_library <FILE> Ignored
|
||||
-macos_version_min <VERSION>
|
||||
-map <FILE> Write map file to a given file
|
||||
-mark_dead_strippable_dylib Mark the output as dead-strippable
|
||||
-needed-l<LIB> Search for a given library
|
||||
-needed-framework <NAME>[,<SUFFIX>]
|
||||
Search for a given framework
|
||||
-no_deduplicate Ignored
|
||||
-no_function_starts Do not generate an LC_FUNCTION_STARTS load command
|
||||
-no_uuid Do not generate an LC_UUID load command
|
||||
-o <FILE> Set output filename
|
||||
-objc_abi_version <VERSION> Ignored
|
||||
-object_path_lto <FILE> Write a LTO temporary file to a given path
|
||||
-order_file <FILE> Ignored
|
||||
-pagezero_size <SIZE> Specify the size of the __PAGEZERO segment
|
||||
-platform_version <PLATFORM> <MIN_VERSION> <SDK_VERSION>
|
||||
Set platform, platform version and SDK version
|
||||
-random_uuid Generate a random LC_UUID load command
|
||||
-reexport-l<LIB> Search for a given library
|
||||
-rpath <PATH> Add PATH to the runpath search path list
|
||||
-search_dylibs_first
|
||||
-search_paths_first
|
||||
-sectalign <SEGNAME> <SECTNAME> <VALUE>
|
||||
Set a section's alignment to a given value
|
||||
-sectcreate <SEGNAME> <SECTNAME> <FILE>
|
||||
-stack_size <SIZE>
|
||||
-stats Show statistics info
|
||||
-syslibroot <DIR> Prepend DIR to library search paths
|
||||
-t Print out each file the linker loads
|
||||
-v Report version information)";
|
||||
-thread_count <NUMBER> Use given number of threads
|
||||
-u <SYMBOL> Force load a given symbol from archive if necessary
|
||||
-unexported_symbol <SYMBOL> Export all but a given symbol
|
||||
-unexported_symbols_list <FILE>
|
||||
Read a list of unexported symbols from a given file
|
||||
-v Report version information
|
||||
-weak_framework <NAME>[,<SUFFIX>]
|
||||
Search for a given framework
|
||||
-weak-l<LIB> Search for a given library)";
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_platform(Context<E> &ctx, std::string_view arg) {
|
||||
@@ -83,11 +139,11 @@ static i64 parse_platform(Context<E> &ctx, std::string_view arg) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_version(Context<E> &ctx, std::string_view arg) {
|
||||
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))
|
||||
if (!std::regex_match(arg.data(), arg.data() + arg.size(), m, re))
|
||||
Fatal(ctx) << "malformed version number: " << arg;
|
||||
|
||||
i64 major = (m[1].length() == 0) ? 0 : stoi(m[1]);
|
||||
@@ -96,26 +152,70 @@ static i64 parse_version(Context<E> &ctx, std::string_view arg) {
|
||||
return (major << 16) | (minor << 8) | patch;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 parse_hex(Context<E> &ctx, std::string_view arg) {
|
||||
auto flags = std::regex_constants::ECMAScript | std::regex_constants::icase;
|
||||
static std::regex re(R"((?:0x)?[0-9a-f]+)", flags);
|
||||
|
||||
std::cmatch m;
|
||||
if (!std::regex_match(arg.begin(), arg.end(), re))
|
||||
Fatal(ctx) << "malformed hexadecimal number: " << arg;
|
||||
|
||||
if (arg.starts_with("0x") || arg.starts_with("0X"))
|
||||
return std::stoll(std::string(arg.substr(2)), nullptr, 16);
|
||||
return std::stoll(std::string(arg), nullptr, 16);
|
||||
}
|
||||
|
||||
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) {
|
||||
static std::vector<std::string>
|
||||
read_lines(Context<E> &ctx, std::string_view path) {
|
||||
MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::must_open(ctx, std::string(path));
|
||||
std::string_view data((char *)mf->data, mf->size);
|
||||
|
||||
std::vector<std::string> vec;
|
||||
|
||||
while (!data.empty()) {
|
||||
size_t pos = data.find('\n');
|
||||
std::string_view line;
|
||||
|
||||
if (pos == data.npos) {
|
||||
line = data;
|
||||
data = "";
|
||||
} else {
|
||||
line = data.substr(0, pos);
|
||||
data = data.substr(pos + 1);
|
||||
}
|
||||
|
||||
line = string_trim(line);
|
||||
if (!line.empty() && !line.starts_with('#'))
|
||||
vec.push_back(std::string(line));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
std::vector<std::string_view> &args = ctx.cmdline_args;
|
||||
std::vector<std::string> remaining;
|
||||
i64 i = 1;
|
||||
|
||||
std::vector<std::string> framework_paths;
|
||||
std::vector<std::string> library_paths;
|
||||
bool nostdlib = false;
|
||||
bool version_shown = false;
|
||||
std::optional<i64> pagezero_size;
|
||||
|
||||
while (i < args.size()) {
|
||||
std::string_view arg;
|
||||
std::string_view arg2;
|
||||
std::string_view arg3;
|
||||
u64 hex_arg;
|
||||
|
||||
auto read_arg = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
@@ -128,6 +228,18 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_arg2 = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
if (args.size() <= i + 2)
|
||||
Fatal(ctx) << "option -" << name << ": argument missing";
|
||||
arg = args[i + 1];
|
||||
arg2 = args[i + 2];
|
||||
i += 3;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_arg3 = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
if (args.size() <= i + 3)
|
||||
@@ -160,6 +272,17 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_hex = [&](std::string name) {
|
||||
if (!read_arg(name))
|
||||
return false;
|
||||
|
||||
size_t pos;
|
||||
hex_arg = std::stol(std::string(arg), &pos, 16);
|
||||
if (pos != arg.size())
|
||||
Fatal(ctx) << "malformed " << name << ": " << arg;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (args[i].starts_with('@')) {
|
||||
std::vector<std::string_view> vec =
|
||||
read_response_file(ctx, args[i].substr(1));
|
||||
@@ -169,7 +292,7 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
}
|
||||
|
||||
if (read_flag("-help") || read_flag("--help")) {
|
||||
SyncOut(ctx) << "Usage: " << ctx.cmdline_args[i]
|
||||
SyncOut(ctx) << "Usage: " << ctx.cmdline_args[0]
|
||||
<< " [options] file...\n" << helpmsg;
|
||||
exit(0);
|
||||
}
|
||||
@@ -182,10 +305,24 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
nostdlib = true;
|
||||
} else if (read_flag("-ObjC")) {
|
||||
ctx.arg.ObjC = true;
|
||||
} else if (read_arg("-U")) {
|
||||
ctx.arg.U.push_back(std::string(arg));
|
||||
} else if (read_arg("-add_ast_path")) {
|
||||
ctx.arg.add_ast_path.push_back(std::string(arg));
|
||||
} else if (read_arg2("-add_empty_section")) {
|
||||
ctx.arg.add_empty_section.push_back({arg, arg2});
|
||||
} else if (read_flag("-adhoc_codesign")) {
|
||||
ctx.arg.adhoc_codesign = true;
|
||||
} else if (read_flag("-no_adhoc_codesign")) {
|
||||
ctx.arg.adhoc_codesign = false;
|
||||
} else if (read_flag("-all_load")) {
|
||||
remaining.push_back("-all_load");
|
||||
} else if (read_flag("-noall_load")) {
|
||||
remaining.push_back("-noall_load");
|
||||
} else if (read_flag("-application_extension")) {
|
||||
ctx.arg.application_extension = true;
|
||||
} else if (read_flag("-no_application_extension")) {
|
||||
ctx.arg.application_extension = false;
|
||||
} else if (read_arg("-arch")) {
|
||||
if (arg == "x86_64")
|
||||
ctx.arg.arch = CPU_TYPE_X86_64;
|
||||
@@ -195,42 +332,83 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
Fatal(ctx) << "unknown -arch: " << arg;
|
||||
} else if (read_flag("-bundle")) {
|
||||
ctx.output_type = MH_BUNDLE;
|
||||
} else if (read_arg("-bundle_loader")) {
|
||||
ctx.arg.bundle_loader = arg;
|
||||
} else if (read_arg("-compatibility_version") ||
|
||||
read_arg("-dylib_compatibility_version")) {
|
||||
ctx.arg.compatibility_version = parse_version(ctx, arg);
|
||||
} else if (read_arg("-current_version") ||
|
||||
read_arg("-dylib_current_version")) {
|
||||
ctx.arg.current_version = parse_version(ctx, arg);
|
||||
} else if (read_flag("-color-diagnostics") ||
|
||||
read_flag("--color-diagnostics")) {
|
||||
ctx.arg.color_diagnostics = true;
|
||||
} else if (read_flag("-dead_strip")) {
|
||||
ctx.arg.dead_strip = true;
|
||||
} else if (read_flag("-dead_strip_dylibs")) {
|
||||
ctx.arg.dead_strip_dylibs = true;
|
||||
} else if (read_flag("-debug_variant")) {
|
||||
} else if (read_flag("-demangle")) {
|
||||
ctx.arg.demangle = true;
|
||||
} else if (read_arg("-dependency_info")) {
|
||||
ctx.arg.dependency_info = arg;
|
||||
} else if (read_flag("-dylib")) {
|
||||
ctx.output_type = MH_DYLIB;
|
||||
} else if (read_arg("-headerpad")) {
|
||||
size_t pos;
|
||||
ctx.arg.headerpad = std::stol(std::string(arg), &pos, 16);
|
||||
if (pos != arg.size())
|
||||
Fatal(ctx) << "malformed -headerpad: " << arg;
|
||||
} else if (read_hex("-headerpad")) {
|
||||
ctx.arg.headerpad = hex_arg;
|
||||
} else if (read_flag("-headerpad_max_install_names")) {
|
||||
ctx.arg.headerpad = 1024;
|
||||
} else if (read_flag("-dynamic")) {
|
||||
ctx.arg.dynamic = true;
|
||||
} else if (read_arg("-e")) {
|
||||
ctx.arg.entry = arg;
|
||||
ctx.arg.entry = get_symbol(ctx, arg);
|
||||
} else if (read_flag("-execute")) {
|
||||
ctx.output_type = MH_EXECUTE;
|
||||
} else if (read_flag("-export_dynamic")) {
|
||||
ctx.arg.export_dynamic = true;
|
||||
} else if (read_arg("-exported_symbol")) {
|
||||
if (!ctx.arg.exported_symbols_list.add(arg, 1))
|
||||
Fatal(ctx) << "-exported_symbol: invalid glob pattern: " << arg;
|
||||
} else if (read_arg("-exported_symbols_list")) {
|
||||
for (std::string_view pat : read_lines(ctx, arg))
|
||||
if (!ctx.arg.exported_symbols_list.add(pat, 1))
|
||||
Fatal(ctx) << "-exported_symbols_list: " << arg
|
||||
<< ": invalid glob pattern: " << pat;
|
||||
} else if (read_arg("-fatal_warnings")) {
|
||||
} else if (read_arg("-filelist")) {
|
||||
remaining.push_back("-filelist");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-final_output")) {
|
||||
ctx.arg.final_output = arg;
|
||||
} else if (read_arg("-force_load")) {
|
||||
remaining.push_back("-force_load");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-framework")) {
|
||||
remaining.push_back("-framework");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-lto_library")) {
|
||||
ctx.arg.lto_library = arg;
|
||||
} else if (read_arg("-macos_version_min")) {
|
||||
ctx.arg.platform = PLATFORM_MACOS;
|
||||
ctx.arg.platform_min_version = parse_version(ctx, arg);
|
||||
} else if (read_joined("-hidden-l")) {
|
||||
remaining.push_back("-hidden-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_flag("-ignore_optimization_hints")) {
|
||||
ctx.arg.ignore_optimization_hints = true;
|
||||
} else if (read_flag("-enable_optimization_hints")) {
|
||||
ctx.arg.ignore_optimization_hints = false;
|
||||
} else if (read_arg("-install_name") || read_arg("-dylib_install_name")) {
|
||||
ctx.arg.install_name = arg;
|
||||
} else if (read_joined("-l")) {
|
||||
remaining.push_back("-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-map")) {
|
||||
ctx.arg.map = arg;
|
||||
} else if (read_flag("-mark_dead_strippable_dylib")) {
|
||||
ctx.arg.mark_dead_strippable_dylib = true;
|
||||
} else if (read_arg("-mllvm")) {
|
||||
ctx.arg.mllvm.push_back(std::string(arg));
|
||||
} else if (read_joined("-needed-l")) {
|
||||
remaining.push_back("-needed-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
@@ -238,28 +416,78 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
remaining.push_back("-needed_framework");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_flag("-no_deduplicate")) {
|
||||
} else if (read_flag("-no_function_starts")) {
|
||||
ctx.arg.function_starts = false;
|
||||
} else if (read_flag("-no_uuid")) {
|
||||
ctx.arg.uuid = UUID_NONE;
|
||||
} else if (read_arg("-o")) {
|
||||
ctx.arg.output = arg;
|
||||
} else if (read_arg("-pagezero_size")) {
|
||||
size_t pos;
|
||||
pagezero_size = std::stol(std::string(arg), &pos, 16);
|
||||
if (pos != arg.size())
|
||||
Fatal(ctx) << "malformed -pagezero_size: " << arg;
|
||||
} else if (read_arg("-objc_abi_version")) {
|
||||
} else if (read_arg("-object_path_lto")) {
|
||||
ctx.arg.object_path_lto = arg;
|
||||
} else if (read_arg("-order_file")) {
|
||||
ctx.arg.order_file = read_lines(ctx, arg);
|
||||
} else if (read_hex("-pagezero_size")) {
|
||||
pagezero_size = hex_arg;
|
||||
} else if (read_flag("-perf")) {
|
||||
ctx.arg.perf = true;
|
||||
} else if (read_arg3("-platform_version")) {
|
||||
ctx.arg.platform = parse_platform(ctx, arg);
|
||||
ctx.arg.platform_min_version = parse_version(ctx, arg2);
|
||||
ctx.arg.platform_sdk_version = parse_version(ctx, arg3);
|
||||
} else if (read_flag("-quick_exit")) {
|
||||
ctx.arg.quick_exit = true;
|
||||
} else if (read_flag("-no_quick_exit")) {
|
||||
ctx.arg.quick_exit = false;
|
||||
} else if (read_flag("-random_uuid")) {
|
||||
ctx.arg.uuid = UUID_RANDOM;
|
||||
} else if (read_joined("-reexport-l")) {
|
||||
remaining.push_back("-reexport-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-rpath")) {
|
||||
ctx.arg.rpath.push_back(std::string(arg));
|
||||
} else if (read_flag("-search_dylibs_first")) {
|
||||
Fatal(ctx) << "-search_dylibs_first is not supported";
|
||||
} else if (read_flag("-search_paths_first")) {
|
||||
ctx.arg.search_paths_first = true;
|
||||
} else if (read_flag("-search_dylibs_first")) {
|
||||
ctx.arg.search_paths_first = false;
|
||||
} else if (read_arg3("-sectalign")) {
|
||||
u64 val = parse_hex(ctx, arg3);
|
||||
std::string key = std::string(arg) + "," + std::string(arg2);
|
||||
if (!has_single_bit(val))
|
||||
Fatal(ctx) << "-sectalign: invalid alignment value: " << arg3;
|
||||
ctx.arg.sectalign.push_back({arg, arg2, (u8)std::countl_zero(val)});
|
||||
} else if (read_arg3("-sectcreate")) {
|
||||
ctx.arg.sectcreate.push_back({arg, arg2, arg3});
|
||||
} else if (read_hex("-stack_size")) {
|
||||
ctx.arg.stack_size = hex_arg;
|
||||
} else if (read_flag("-stats")) {
|
||||
ctx.arg.stats = true;
|
||||
Counter::enabled = true;
|
||||
} else if (read_arg("-syslibroot")) {
|
||||
ctx.arg.syslibroot.push_back(std::string(arg));
|
||||
} else if (read_flag("-t")) {
|
||||
ctx.arg.trace = true;
|
||||
} else if (read_arg("-thread_count")) {
|
||||
ctx.arg.thread_count = std::stoi(std::string(arg));
|
||||
} else if (read_arg("-u")) {
|
||||
ctx.arg.u.push_back(std::string(arg));
|
||||
} else if (read_arg("-unexported_symbol")) {
|
||||
if (!ctx.arg.unexported_symbols_list.add(arg, 1))
|
||||
Fatal(ctx) << "-unexported_symbol: invalid glob pattern: " << arg;
|
||||
} else if (read_arg("-unexported_symbols_list")) {
|
||||
for (std::string_view pat : read_lines(ctx, arg))
|
||||
if (!ctx.arg.unexported_symbols_list.add(pat, 1))
|
||||
Fatal(ctx) << "-unexported_symbols_list: " << arg
|
||||
<< ": invalid glob pattern: " << pat;
|
||||
} else if (read_flag("-v")) {
|
||||
SyncOut(ctx) << mold_version;
|
||||
version_shown = true;
|
||||
} else if (read_arg("-weak_framework")) {
|
||||
remaining.push_back("-weak_framework");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_joined("-weak-l")) {
|
||||
remaining.push_back("-weak-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else {
|
||||
if (args[i][0] == '-')
|
||||
Fatal(ctx) << "unknown command line option: " << args[i];
|
||||
@@ -268,6 +496,15 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx.arg.entry)
|
||||
ctx.arg.entry = get_symbol(ctx, "_main");
|
||||
|
||||
if (ctx.arg.thread_count == 0)
|
||||
ctx.arg.thread_count = get_default_thread_count();
|
||||
|
||||
if (!ctx.arg.bundle_loader.empty() && ctx.output_type != MH_BUNDLE)
|
||||
Fatal(ctx) << "-bundle_loader cannot be specified without -bundle";
|
||||
|
||||
auto add_search_path = [&](std::vector<std::string> &vec, std::string path) {
|
||||
if (!path.starts_with('/') || ctx.arg.syslibroot.empty()) {
|
||||
if (is_directory(path))
|
||||
@@ -305,18 +542,32 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
|
||||
if (pagezero_size.has_value()) {
|
||||
if (ctx.output_type != MH_EXECUTE)
|
||||
Error(ctx) << "-pagezero_size option can only be used when"
|
||||
Fatal(ctx) << "-pagezero_size option can only be used when"
|
||||
<< " linking a main executable";
|
||||
ctx.arg.pagezero_size = *pagezero_size;
|
||||
} else {
|
||||
ctx.arg.pagezero_size = (ctx.output_type == MH_EXECUTE) ? 0x100000000 : 0;
|
||||
}
|
||||
|
||||
if (ctx.arg.final_output == "") {
|
||||
if (ctx.arg.install_name != "")
|
||||
ctx.arg.final_output = ctx.arg.install_name;
|
||||
else
|
||||
ctx.arg.final_output = ctx.arg.output;
|
||||
}
|
||||
|
||||
if (ctx.arg.uuid == UUID_RANDOM)
|
||||
memcpy(ctx.uuid, get_uuid_v4().data(), 16);
|
||||
|
||||
if (version_shown && remaining.empty())
|
||||
exit(0);
|
||||
return remaining;
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void parse_nonpositional_args(Context<E> &, std::vector<std::string> &)
|
||||
#define INSTANTIATE(E) \
|
||||
template i64 parse_version(Context<E> &, std::string_view); \
|
||||
template std::vector<std::string> parse_nonpositional_args(Context<E> &)
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
+57
-22
@@ -1,24 +1,48 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
static std::vector<Subsection<E> *> collect_root_set(Context<E> &ctx) {
|
||||
Timer t(ctx, "collect_root_set");
|
||||
|
||||
std::vector<Subsection<E> *> rootset;
|
||||
|
||||
auto mark = [&](Symbol<E> *sym) {
|
||||
if (sym && sym->subsec)
|
||||
auto add = [&](Symbol<E> *sym) {
|
||||
if (sym->subsec)
|
||||
rootset.push_back(sym->subsec);
|
||||
};
|
||||
|
||||
mark(get_symbol(ctx, ctx.arg.entry));
|
||||
auto keep = [&](Symbol<E> *sym) {
|
||||
if (sym->no_dead_strip)
|
||||
return true;
|
||||
if (ctx.output_type == MH_DYLIB || ctx.output_type == MH_BUNDLE)
|
||||
if (sym->scope == SCOPE_EXTERN || sym->referenced_dynamically)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
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);
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
for (Symbol<E> *sym : file->syms)
|
||||
if (sym->file == file && keep(sym))
|
||||
add(sym);
|
||||
|
||||
for (Subsection<E> *subsec : file->subsections)
|
||||
if (const MachSection &hdr = subsec->isec.hdr;
|
||||
(hdr.attr & S_ATTR_NO_DEAD_STRIP) ||
|
||||
hdr.type == S_MOD_INIT_FUNC_POINTERS ||
|
||||
hdr.type == S_MOD_TERM_FUNC_POINTERS)
|
||||
rootset.push_back(subsec);
|
||||
}
|
||||
|
||||
for (std::string_view name : ctx.arg.u)
|
||||
if (Symbol<E> *sym = get_symbol(ctx, name); sym->file)
|
||||
add(sym);
|
||||
|
||||
add(ctx.arg.entry);
|
||||
add(get_symbol(ctx, "dyld_stub_binder"));
|
||||
return rootset;
|
||||
}
|
||||
|
||||
@@ -37,12 +61,11 @@ static void visit(Context<E> &ctx, Subsection<E> &subsec) {
|
||||
}
|
||||
|
||||
for (UnwindRecord<E> &rec : subsec.get_unwind_records()) {
|
||||
rec.is_alive = true;
|
||||
visit(ctx, *rec.subsec);
|
||||
if (rec.lsda)
|
||||
visit(ctx, *rec.lsda);
|
||||
if (Symbol<E> *sym = rec.personality; sym && sym->subsec)
|
||||
visit(ctx, *sym->subsec);
|
||||
if (rec.personality && rec.personality->subsec)
|
||||
visit(ctx, *rec.personality->subsec);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +73,7 @@ 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)
|
||||
if (rel.sym->subsec && rel.sym->subsec->is_alive)
|
||||
return true;
|
||||
} else {
|
||||
if (rel.subsec->is_alive)
|
||||
@@ -62,6 +85,15 @@ static bool refers_live_subsection(Subsection<E> &subsec) {
|
||||
|
||||
template <typename E>
|
||||
static void mark(Context<E> &ctx, const std::vector<Subsection<E> *> &rootset) {
|
||||
Timer t(ctx, "mark");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
for (Subsection<E> *subsec : file->subsections)
|
||||
subsec->is_alive.store(false, std::memory_order_relaxed);
|
||||
});
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
|
||||
for (Subsection<E> *subsec : rootset)
|
||||
visit(ctx, *subsec);
|
||||
|
||||
@@ -69,7 +101,7 @@ static void mark(Context<E> &ctx, const std::vector<Subsection<E> *> &rootset) {
|
||||
do {
|
||||
repeat = false;
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
for (std::unique_ptr<Subsection<E>> &subsec : file->subsections) {
|
||||
for (Subsection<E> *subsec : file->subsections) {
|
||||
if ((subsec->isec.hdr.attr & S_ATTR_LIVE_SUPPORT) &&
|
||||
!subsec->is_alive &&
|
||||
refers_live_subsection(*subsec)) {
|
||||
@@ -83,21 +115,25 @@ static void mark(Context<E> &ctx, const std::vector<Subsection<E> *> &rootset) {
|
||||
|
||||
template <typename E>
|
||||
static void sweep(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
std::erase_if(file->subsections,
|
||||
[](const std::unique_ptr<Subsection<E>> &subsec) {
|
||||
return !subsec->is_alive;
|
||||
});
|
||||
}
|
||||
Timer t(ctx, "sweep");
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *&sym : file->syms)
|
||||
if (sym->file == file && sym->subsec && !sym->subsec->is_alive)
|
||||
sym = nullptr;
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
std::erase_if(file->subsections, [](Subsection<E> *subsec) {
|
||||
return !subsec->is_alive;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void dead_strip(Context<E> &ctx) {
|
||||
Timer t(ctx, "dead_strip");
|
||||
|
||||
std::vector<Subsection<E> *> rootset = collect_root_set(ctx);
|
||||
mark(ctx, rootset);
|
||||
sweep(ctx);
|
||||
@@ -106,7 +142,6 @@ void dead_strip(Context<E> &ctx) {
|
||||
#define INSTANTIATE(E) \
|
||||
template void dead_strip(Context<E> &)
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
-511
@@ -1,511 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
static u8 *open_file(std::string path) {
|
||||
i64 fd = ::open(path.c_str(), O_RDONLY);
|
||||
if (fd == -1)
|
||||
return nullptr;
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == -1) {
|
||||
std::cerr << path << ": fstat failed: " << errno_string();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void *ptr = mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (ptr == MAP_FAILED) {
|
||||
std::cerr << path << ": mmap failed: " << errno_string();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return (u8 *)ptr;
|
||||
}
|
||||
|
||||
static void print_bytes(u8 *buf, i64 size) {
|
||||
if (size == 0) {
|
||||
std::cout << "[]\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "[" << std::setw(2) << std::setfill('0') << (u32)buf[0];
|
||||
for (i64 i = 1; i < size; i++)
|
||||
std::cout << " " << std::setw(2) << std::setfill('0') << (u32)buf[i];
|
||||
std::cout << "]\n";
|
||||
}
|
||||
|
||||
struct ExportEntry {
|
||||
std::string name;
|
||||
u32 flags;
|
||||
u64 addr;
|
||||
};
|
||||
|
||||
static void read_trie(std::vector<ExportEntry> &vec, u8 *start, i64 offset = 0,
|
||||
const std::string &prefix = "") {
|
||||
u8 *buf = start + offset;
|
||||
|
||||
if (read_uleb(buf)) {
|
||||
ExportEntry ent;
|
||||
ent.name = prefix;
|
||||
ent.flags = read_uleb(buf);
|
||||
ent.addr = read_uleb(buf);
|
||||
vec.push_back(std::move(ent));
|
||||
}
|
||||
|
||||
for (i64 i = 0, n = read_uleb(buf); i < n; i++) {
|
||||
std::string suffix((char *)buf);
|
||||
buf += suffix.size() + 1;
|
||||
i64 off = read_uleb(buf);
|
||||
read_trie(vec, start, off, prefix + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
void dump_unwind_info(u8 *buf, MachSection &sec) {
|
||||
UnwindSectionHeader &hdr = *(UnwindSectionHeader *)(buf + sec.offset);
|
||||
|
||||
std::cout << std::hex << " Unwind info:"
|
||||
<< "\n version: 0x" << hdr.version
|
||||
<< "\n encoding_offset: 0x" << hdr.encoding_offset
|
||||
<< "\n encoding_count: 0x" << hdr.encoding_count
|
||||
<< "\n personality_offset: 0x" << hdr.personality_offset
|
||||
<< "\n personality_count: 0x" << hdr.personality_count
|
||||
<< "\n page_offset: 0x" << hdr.page_offset
|
||||
<< "\n page_count: 0x" << hdr.page_count;
|
||||
|
||||
std::cout << "\n encoding:";
|
||||
u32 *enc = (u32 *)(buf + sec.offset + hdr.encoding_offset);
|
||||
for (i64 i = 0; i < hdr.encoding_count; i++)
|
||||
std::cout << std::hex << "\n 0x" << enc[i];
|
||||
|
||||
std::cout << "\n personality:";
|
||||
u32 *pers = (u32 *)(buf + sec.offset + hdr.personality_offset);
|
||||
for (i64 i = 0; i < hdr.personality_count; i++)
|
||||
std::cout << std::hex << "\n 0x" << pers[i];
|
||||
|
||||
UnwindFirstLevelPage *ent =
|
||||
(UnwindFirstLevelPage *)(buf + sec.offset + hdr.page_offset);
|
||||
|
||||
for (i64 i = 0; i < hdr.page_count; i++) {
|
||||
std::cout << std::hex << "\n function:"
|
||||
<< "\n func_addr: 0x" << ent[i].func_addr
|
||||
<< "\n page_offset: 0x" << ent[i].page_offset
|
||||
<< "\n lsda_offset: 0x" << ent[i].lsda_offset;
|
||||
|
||||
if (i != hdr.page_count - 1) {
|
||||
UnwindLsdaEntry *lsda =
|
||||
(UnwindLsdaEntry *)(buf + sec.offset + ent[i].lsda_offset);
|
||||
i64 lsda_size = ent[i + 1].lsda_offset - ent[i].lsda_offset;
|
||||
for (i64 j = 0; j < lsda_size / sizeof(UnwindLsdaEntry); j++)
|
||||
std::cout << std::hex
|
||||
<< "\n lsda:"
|
||||
<< "\n func_addr: 0x" << lsda[j].func_addr
|
||||
<< "\n lsda_addr: 0x" << lsda[j].lsda_addr;
|
||||
}
|
||||
|
||||
if (ent[i].page_offset == 0)
|
||||
break;
|
||||
|
||||
u8 *addr = buf + sec.offset + ent[i].page_offset;
|
||||
|
||||
switch (u32 kind = *addr; kind) {
|
||||
case UNWIND_SECOND_LEVEL_REGULAR: {
|
||||
std::cout << "\n UNWIND_SECOND_LEVEL_REGULAR:" ;
|
||||
break;
|
||||
}
|
||||
case UNWIND_SECOND_LEVEL_COMPRESSED: {
|
||||
std::cout << "\n UNWIND_SECOND_LEVEL_COMPRESSED" ;
|
||||
UnwindSecondLevelPage &hdr2 = *(UnwindSecondLevelPage *)addr;
|
||||
std::cout << std::hex
|
||||
<< "\n page_offset: 0x" << hdr2.page_offset
|
||||
<< "\n page_count: 0x" << hdr2.page_count
|
||||
<< "\n encoding_offset: 0x" << hdr2.encoding_offset
|
||||
<< "\n encoding_count: 0x" << hdr2.encoding_count;
|
||||
|
||||
UnwindPageEntry *ent2 = (UnwindPageEntry *)(addr + hdr2.page_offset);
|
||||
for (i64 j = 0; j < hdr2.page_count; j++)
|
||||
std::cout << std::hex << "\n ent 0x"
|
||||
<< (ent[i].func_addr + ent2[j].func_addr)
|
||||
<< " 0x" << ent2[j].encoding;
|
||||
|
||||
u32 *enc = (u32 *)(addr + hdr2.encoding_offset);
|
||||
for (i64 j = 0; j < hdr2.encoding_count; j++)
|
||||
std::cout << std::hex << "\n 0x" << enc[j];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::cout << "\n bad 2nd-level unwind info header: " << kind;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
void dump_compact_unwind(u8 *buf, MachSection &sec) {
|
||||
CompactUnwindEntry *ent = (CompactUnwindEntry *)(buf + sec.offset);
|
||||
i64 nentry = sec.size / sizeof(CompactUnwindEntry);
|
||||
|
||||
std::cout << " Compact unwind:"
|
||||
<< "\n num_entry: " << nentry;
|
||||
|
||||
for (i64 i = 0; i < nentry; i++) {
|
||||
std::cout << std::hex
|
||||
<< "\n entry: 0x" << (i * sizeof(CompactUnwindEntry))
|
||||
<< "\n code_start: 0x" << ent[i].code_start
|
||||
<< "\n code_len: 0x" << ent[i].code_len
|
||||
<< "\n encoding: 0x" << ent[i].encoding
|
||||
<< "\n personality: 0x" << ent[i].personality
|
||||
<< "\n lsda: 0x" << ent[i].lsda;
|
||||
}
|
||||
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
void dump_file(std::string path) {
|
||||
u8 *buf = open_file(path);
|
||||
if (!buf) {
|
||||
std::cerr << "cannot open " << path << "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
MachHeader &hdr = *(MachHeader *)buf;
|
||||
std::cout << "magic: 0x" << std::hex << hdr.magic
|
||||
<< "\ncputype: 0x" << hdr.cputype
|
||||
<< "\ncpusubtype: 0x" << hdr.cpusubtype
|
||||
<< "\nfiletype: 0x" << hdr.filetype
|
||||
<< "\nncmds: 0x" << hdr.ncmds
|
||||
<< "\nsizeofcmds: 0x" << hdr.sizeofcmds
|
||||
<< "\nflags: 0x" << hdr.flags
|
||||
<< "\n\n";
|
||||
|
||||
u8 *p = buf + sizeof(MachHeader);
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
std::cout << "fileoff: 0x" << std::hex << (p - buf) << "\n";
|
||||
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
p += lc.cmdsize;
|
||||
|
||||
auto print_dylib = [&] {
|
||||
DylibCommand &cmd = *(DylibCommand *)&lc;
|
||||
std::cout << " cmdsize: 0x" << cmd.cmdsize
|
||||
<< "\n nameoff: 0x" << cmd.nameoff
|
||||
<< "\n timestamp: 0x" << cmd.timestamp
|
||||
<< "\n current_version: 0x" << cmd.current_version
|
||||
<< "\n compatibility_version: 0x" << cmd.compatibility_version
|
||||
<< "\n data: " << (char *)((char *)&cmd + cmd.nameoff)
|
||||
<< "\n";
|
||||
};
|
||||
|
||||
switch (lc.cmd) {
|
||||
case LC_SYMTAB: {
|
||||
std::cout << "LC_SYMTAB\n";
|
||||
SymtabCommand &cmd = *(SymtabCommand *)&lc;
|
||||
std::cout << " cmdsize: " << cmd.cmdsize
|
||||
<< "\n symoff: 0x" << std::hex << cmd.symoff
|
||||
<< "\n nsyms: " << std::dec << cmd.nsyms
|
||||
<< "\n stroff: 0x" << std::hex << cmd.stroff
|
||||
<< "\n strsize: 0x" << cmd.strsize
|
||||
<< "\n symdata: ";
|
||||
print_bytes(buf + cmd.symoff, cmd.nsyms * sizeof(MachSym));
|
||||
|
||||
std::cout << " strdata: ";
|
||||
print_bytes(buf + cmd.stroff, cmd.strsize);
|
||||
|
||||
MachSym *syms = (MachSym *)(buf + cmd.symoff);
|
||||
for (i64 j = 0; j < cmd.nsyms; j++) {
|
||||
std::cout << " symbol:"
|
||||
<< "\n name: " << (char *)(buf + cmd.stroff + syms[j].stroff)
|
||||
<< "\n stub: " << (u32)syms[j].stub
|
||||
<< "\n pext: " << (u32)syms[j].pext
|
||||
<< "\n type: " << (u32)syms[j].type
|
||||
<< "\n ext: " << (u32)syms[j].ext
|
||||
<< "\n sect: 0x" << (u32)syms[j].sect
|
||||
<< "\n desc: 0x" << (u32)syms[j].desc
|
||||
<< "\n value: 0x" << syms[j].value
|
||||
<< "\n";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LC_DYSYMTAB: {
|
||||
std::cout << "LC_DYSYMTAB\n";
|
||||
DysymtabCommand &cmd = *(DysymtabCommand *)&lc;
|
||||
std::cout << " cmdsize: 0x" << cmd.cmdsize
|
||||
<< "\n ilocalsym: 0x" << cmd.ilocalsym
|
||||
<< "\n nlocalsym: 0x" << cmd.nlocalsym
|
||||
<< "\n iextdefsym: 0x" << cmd.iextdefsym
|
||||
<< "\n nextdefsym: 0x" << cmd.nextdefsym
|
||||
<< "\n iundefsym: 0x" << cmd.iundefsym
|
||||
<< "\n nundefsym: 0x" << cmd.nundefsym
|
||||
<< "\n tocoff: 0x" << cmd.tocoff
|
||||
<< "\n ntoc: 0x" << cmd.ntoc
|
||||
<< "\n modtaboff: 0x" << cmd.modtaboff
|
||||
<< "\n nmodtab: 0x" << cmd.nmodtab
|
||||
<< "\n extrefsymoff: 0x" << cmd.extrefsymoff
|
||||
<< "\n nextrefsyms: 0x" << cmd.nextrefsyms
|
||||
<< "\n indirectsymoff: 0x" << cmd.indirectsymoff
|
||||
<< "\n nindirectsyms: 0x" << cmd.nindirectsyms
|
||||
<< "\n extreloff: 0x" << cmd.extreloff
|
||||
<< "\n nextrel: 0x" << cmd.nextrel
|
||||
<< "\n locreloff: 0x" << cmd.locreloff
|
||||
<< "\n nlocrel: 0x" << cmd.nlocrel
|
||||
<< "\n";
|
||||
|
||||
if (cmd.indirectsymoff) {
|
||||
std::cout << " indirectsymdata: ";
|
||||
print_bytes(buf + cmd.indirectsymoff, 4 * cmd.nindirectsyms);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LC_LOAD_DYLIB:
|
||||
std::cout << "LC_LOAD_DYLIB\n";
|
||||
print_dylib();
|
||||
break;
|
||||
case LC_LOAD_WEAK_DYLIB:
|
||||
std::cout << "LC_LOAD_WEAK_DYLIB\n";
|
||||
print_dylib();
|
||||
break;
|
||||
case LC_ID_DYLIB:
|
||||
std::cout << "LC_ID_DYLIB\n";
|
||||
print_dylib();
|
||||
break;
|
||||
case LC_LOAD_DYLINKER: {
|
||||
std::cout << "LC_LOAD_DYLINKER\n";
|
||||
DylinkerCommand &cmd = *(DylinkerCommand *)&lc;
|
||||
std::cout << " cmdsize: 0x" << cmd.cmdsize
|
||||
<< "\n nameoff: 0x" << cmd.nameoff
|
||||
<< "\n data: " << (char *)((char *)&cmd + cmd.nameoff)
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
case LC_SEGMENT_64: {
|
||||
std::cout << "LC_SEGMENT_64\n";
|
||||
SegmentCommand &cmd = *(SegmentCommand *)&lc;
|
||||
std::cout << " cmdsize: " << cmd.cmdsize
|
||||
<< "\n segname: " << cmd.get_segname()
|
||||
<< "\n vmaddr: 0x" << std::hex << cmd.vmaddr
|
||||
<< "\n vmsize: 0x" << cmd.vmsize
|
||||
<< "\n fileoff: 0x" << cmd.fileoff
|
||||
<< "\n filesize: 0x" << cmd.filesize
|
||||
<< "\n maxprot: " << std::dec << cmd.maxprot
|
||||
<< "\n initprot: " << cmd.initprot
|
||||
<< "\n nsects: " << cmd.nsects
|
||||
<< "\n flags: 0x" << std::hex << cmd.flags
|
||||
<< "\n";
|
||||
|
||||
MachSection *sec = (MachSection *)((u8 *)&lc + sizeof(cmd));
|
||||
for (i64 j = 0; j < cmd.nsects; j++) {
|
||||
std::cout << " section:\n sectname: " << sec[j].get_sectname()
|
||||
<< "\n segname: " << sec[j].get_segname()
|
||||
<< "\n addr: 0x" << std::hex << sec[j].addr
|
||||
<< "\n size: 0x" << sec[j].size
|
||||
<< "\n offset: 0x" << sec[j].offset
|
||||
<< "\n p2align: " << std::dec << sec[j].p2align
|
||||
<< "\n reloff: " << std::hex << sec[j].reloff
|
||||
<< "\n nreloc: " << std::dec << sec[j].nreloc
|
||||
<< "\n type: 0x" << std::hex << sec[j].type
|
||||
<< "\n attr: 0x" << std::hex << sec[j].attr
|
||||
<< "\n";
|
||||
|
||||
if (sec[j].type != S_ZEROFILL) {
|
||||
std::cout << " contents: ";
|
||||
print_bytes(buf + sec[j].offset, sec[j].size);
|
||||
}
|
||||
|
||||
if (sec[j].reloff) {
|
||||
MachRel *rel = (MachRel *)(buf + sec[j].reloff);
|
||||
for (i64 k = 0; k < sec[j].nreloc; k++) {
|
||||
std::cout << " reloc: "
|
||||
<< "\n offset: 0x" << rel[k].offset
|
||||
<< "\n idx: 0x" << rel[k].idx
|
||||
<< "\n is_pcrel: " << rel[k].is_pcrel
|
||||
<< "\n p2size: 0x" << rel[k].p2size
|
||||
<< "\n is_extern: " << rel[k].is_extern
|
||||
<< "\n type: " << rel[k].type
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (sec[j].match("__TEXT", "__unwind_info"))
|
||||
dump_unwind_info(buf, sec[j]);
|
||||
|
||||
if (sec[j].match("__LD", "__compact_unwind"))
|
||||
dump_compact_unwind(buf, sec[j]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LC_UUID: {
|
||||
std::cout << "LC_UUID\n";
|
||||
UUIDCommand &cmd = *(UUIDCommand *)&lc;
|
||||
std::cout << " data: ";
|
||||
print_bytes(cmd.uuid, sizeof(cmd.uuid));
|
||||
break;
|
||||
}
|
||||
case LC_DYLD_INFO_ONLY: {
|
||||
std::cout << "LC_DYLD_INFO_ONLY\n";
|
||||
DyldInfoCommand &cmd = *(DyldInfoCommand *)&lc;
|
||||
|
||||
if (cmd.rebase_off) {
|
||||
std::cout << " rebase: ";
|
||||
print_bytes(buf + cmd.rebase_off, cmd.rebase_size);
|
||||
std::cout << " rebase_off: 0x" << std::hex << cmd.rebase_off
|
||||
<< "\n rebase_size: 0x" << cmd.rebase_size
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if (cmd.bind_off) {
|
||||
std::cout << " bind: ";
|
||||
print_bytes(buf + cmd.bind_off, cmd.bind_size);
|
||||
std::cout << " bind_off: 0x" << std::hex << cmd.bind_off
|
||||
<< "\n bind_size: 0x" << cmd.bind_size
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if (cmd.weak_bind_off) {
|
||||
std::cout << " weak_bind: ";
|
||||
print_bytes(buf + cmd.weak_bind_off, cmd.weak_bind_size);
|
||||
std::cout << " weak_bind_off: 0x" << std::hex << cmd.weak_bind_off
|
||||
<< "\n weak_bind_size: 0x" << cmd.weak_bind_size
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if (cmd.lazy_bind_off) {
|
||||
std::cout << " lazy_bind: ";
|
||||
print_bytes(buf + cmd.lazy_bind_off, cmd.lazy_bind_size);
|
||||
std::cout << " lazy_bind_off: 0x" << std::hex << cmd.lazy_bind_off
|
||||
<< "\n lazy_bind_size: 0x" << cmd.lazy_bind_size
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if (cmd.export_off) {
|
||||
std::cout << " export: ";
|
||||
print_bytes(buf + cmd.export_off, cmd.export_size);
|
||||
std::cout << " export_off: 0x" << std::hex << cmd.export_off
|
||||
<< "\n export_size: 0x" << cmd.export_size
|
||||
<< "\n";
|
||||
|
||||
std::vector<ExportEntry> vec;
|
||||
read_trie(vec, buf + cmd.export_off);
|
||||
for (ExportEntry &ent : vec)
|
||||
std::cout << " export_sym: " << ent.name << " 0x" << ent.addr << "\n";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LC_FUNCTION_STARTS: {
|
||||
std::cout << "LC_FUNCTION_STARTS\n";
|
||||
LinkEditDataCommand &cmd = *(LinkEditDataCommand *)&lc;
|
||||
std::cout << " dataoff: 0x" << cmd.dataoff
|
||||
<< "\n datasize: 0x" << cmd.datasize
|
||||
<< "\n data:";
|
||||
|
||||
u8 *p = buf + cmd.dataoff;
|
||||
u64 addr = 0;
|
||||
for (;;) {
|
||||
u64 delta = read_uleb(p);
|
||||
if (!delta)
|
||||
break;
|
||||
addr += delta;
|
||||
std::cout << std::hex << " 0x" << addr;
|
||||
}
|
||||
std::cout << "\n";
|
||||
break;
|
||||
}
|
||||
case LC_MAIN: {
|
||||
std::cout << "LC_MAIN\n";
|
||||
EntryPointCommand &cmd = *(EntryPointCommand *)&lc;
|
||||
std::cout << " cmdsize: 0x" << cmd.cmdsize
|
||||
<< "\n entryoff: 0x" << cmd.entryoff
|
||||
<< "\n stacksize: 0x" << cmd.stacksize
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
case LC_DATA_IN_CODE: {
|
||||
std::cout << "LC_DATA_IN_CODE\n";
|
||||
LinkEditDataCommand &cmd = *(LinkEditDataCommand *)&lc;
|
||||
std::cout << " dataoff: 0x" << cmd.dataoff
|
||||
<< "\n datasize: 0x" << cmd.datasize
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
case LC_SOURCE_VERSION: {
|
||||
std::cout << "LC_SOURCE_VERSION\n";
|
||||
SourceVersionCommand &cmd = *(SourceVersionCommand *)&lc;
|
||||
std::cout << " version: 0x" << cmd.version
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case LC_BUILD_VERSION: {
|
||||
std::cout << "LC_BUILD_VERSION\n";
|
||||
BuildVersionCommand &cmd = *(BuildVersionCommand *)&lc;
|
||||
std::cout << " cmdsize: 0x" << cmd.cmdsize
|
||||
<< "\n platform: 0x" << cmd.platform
|
||||
<< "\n minos: 0x" << cmd.minos
|
||||
<< "\n sdk: 0x" << cmd.sdk
|
||||
<< "\n ntools: 0x" << cmd.ntools
|
||||
<< "\n";
|
||||
|
||||
BuildToolVersion *tools =
|
||||
(BuildToolVersion *)((u8 *)&lc + sizeof(BuildVersionCommand));
|
||||
for (i64 i = 0; i < cmd.ntools; i++)
|
||||
std::cout << " tool: 0x" << tools[i].tool
|
||||
<< "\n version: 0x" << tools[i].version
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
case LC_VERSION_MIN_MACOSX: {
|
||||
std::cout << "LC_VERSION_MIN_MACOSX\n";
|
||||
VersionMinCommand &cmd = *(VersionMinCommand *)&lc;
|
||||
std::cout << " version: " << (int)cmd.version
|
||||
<< "\n sdk: " << (int)cmd.sdk
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
case LC_CODE_SIGNATURE: {
|
||||
std::cout << "LC_CODE_SIGNATURE\n";
|
||||
LinkEditDataCommand &cmd = *(LinkEditDataCommand *)&lc;
|
||||
|
||||
CodeSignatureHeader &sig = *(CodeSignatureHeader *)(buf + cmd.dataoff);
|
||||
std::cout << " magic: " << sig.magic
|
||||
<< "\n length: " << sig.length
|
||||
<< "\n count: " << sig.count
|
||||
<< "\n";
|
||||
|
||||
for (i64 i = 0; i < sig.count; i++) {
|
||||
CodeSignatureBlobIndex &idx = ((CodeSignatureBlobIndex *)(&sig + 1))[i];
|
||||
std::cout << " idx type: " << idx.type
|
||||
<< "\n idx offset: " << idx.offset
|
||||
<< "\n";
|
||||
|
||||
CodeSignatureDirectory &dir =
|
||||
*(CodeSignatureDirectory *)((u8 *)&sig + idx.offset);
|
||||
std::cout << std::hex
|
||||
<< " magic: 0x" << dir.magic
|
||||
<< "\n version: 0x" << dir.version
|
||||
<< "\n flags: 0x" << dir.flags
|
||||
<< "\n hash_offset: 0x" << dir.hash_offset
|
||||
<< "\n n_code_slots: 0x" << dir.n_code_slots
|
||||
<< "\n hash_size: 0x" << (u32)dir.hash_size
|
||||
<< "\n hash_type: 0x" << (u32)dir.hash_type
|
||||
<< "\n page_size: 0x" << (1 << dir.page_size)
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::cout << "UNKNOWN (0x" << std::hex << lc.cmd << ")\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,983 @@
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const InputFile<E> &file) {
|
||||
if (file.archive_name.empty())
|
||||
out << path_clean(file.filename);
|
||||
else
|
||||
out << path_clean(file.archive_name) << "(" << path_clean(file.filename) + ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputFile<E>::clear_symbols() {
|
||||
for (Symbol<E> *sym : syms) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
if (sym->file == this) {
|
||||
sym->file = nullptr;
|
||||
sym->scope = SCOPE_LOCAL;
|
||||
sym->is_imported = false;
|
||||
sym->is_weak = false;
|
||||
sym->no_dead_strip = false;
|
||||
sym->subsec = nullptr;
|
||||
sym->value = 0;
|
||||
sym->is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *
|
||||
ObjectFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
ObjectFile<E> *obj = new ObjectFile<E>(mf);
|
||||
obj->archive_name = archive_name;
|
||||
obj->is_alive = archive_name.empty() || ctx.all_load;
|
||||
obj->is_hidden = ctx.hidden_l;
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
return obj;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
if (get_file_type(this->mf) == FileType::LLVM_BITCODE) {
|
||||
// Open an compiler IR file
|
||||
load_lto_plugin(ctx);
|
||||
this->lto_module =
|
||||
ctx.lto.module_create_from_memory(this->mf->data, this->mf->size);
|
||||
if (!this->lto_module)
|
||||
Fatal(ctx) << *this << ": lto_module_create_from_memory failed";
|
||||
|
||||
// Read a symbol table
|
||||
parse_lto_symbols(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
parse_sections(ctx);
|
||||
parse_symbols(ctx);
|
||||
|
||||
MachHeader &mach_hdr = *(MachHeader *)this->mf->data;
|
||||
if (mach_hdr.flags & MH_SUBSECTIONS_VIA_SYMBOLS)
|
||||
split_subsections_via_symbols(ctx);
|
||||
else
|
||||
init_subsections(ctx);
|
||||
|
||||
sort(subsections, [](Subsection<E> *a, Subsection<E> *b) {
|
||||
return a->input_addr < b->input_addr;
|
||||
});
|
||||
|
||||
fix_subsec_members(ctx);
|
||||
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections)
|
||||
if (isec)
|
||||
isec->parse_relocations(ctx);
|
||||
|
||||
if (unwind_sec)
|
||||
parse_compact_unwind(ctx, *unwind_sec);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_sections(Context<E> &ctx) {
|
||||
SegmentCommand *cmd = (SegmentCommand *)find_load_command(ctx, LC_SEGMENT_64);
|
||||
if (!cmd)
|
||||
return;
|
||||
|
||||
MachSection *mach_sec = (MachSection *)((u8 *)cmd + sizeof(*cmd));
|
||||
|
||||
for (i64 i = 0; i < cmd->nsects; i++) {
|
||||
MachSection &msec = mach_sec[i];
|
||||
sections.push_back(nullptr);
|
||||
|
||||
if (msec.match("__LD", "__compact_unwind")) {
|
||||
unwind_sec = &msec;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msec.match("__DATA", "__objc_imageinfo") ||
|
||||
msec.match("__DATA_CONST", "__objc_imageinfo")) {
|
||||
if (msec.size != sizeof(ObjcImageInfo))
|
||||
Fatal(ctx) << *this << ": __objc_imageinfo: invalid size";
|
||||
|
||||
objc_image_info =
|
||||
(ObjcImageInfo *)(this->mf->get_contents().data() + msec.offset);
|
||||
|
||||
if (objc_image_info->version != 0)
|
||||
Fatal(ctx) << *this << ": __objc_imageinfo: unknown version: "
|
||||
<< (u32)objc_image_info->version;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msec.attr & S_ATTR_DEBUG)
|
||||
continue;
|
||||
|
||||
sections.back().reset(new InputSection<E>(ctx, *this, msec, i));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_symbols(Context<E> &ctx) {
|
||||
SymtabCommand *cmd = (SymtabCommand *)find_load_command(ctx, LC_SYMTAB);
|
||||
if (!cmd)
|
||||
return;
|
||||
|
||||
mach_syms = {(MachSym *)(this->mf->data + cmd->symoff), cmd->nsyms};
|
||||
this->syms.reserve(mach_syms.size());
|
||||
|
||||
i64 nlocal = 0;
|
||||
for (MachSym &msym : mach_syms)
|
||||
if (!msym.is_extern)
|
||||
nlocal++;
|
||||
local_syms.reserve(nlocal);
|
||||
|
||||
for (i64 i = 0; i < mach_syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
std::string_view name = (char *)(this->mf->data + cmd->stroff + msym.stroff);
|
||||
|
||||
if (msym.is_extern) {
|
||||
this->syms.push_back(get_symbol(ctx, name));
|
||||
} else {
|
||||
local_syms.emplace_back(name);
|
||||
Symbol<E> &sym = local_syms.back();
|
||||
|
||||
sym.file = this;
|
||||
sym.subsec = nullptr;
|
||||
sym.scope = SCOPE_LOCAL;
|
||||
sym.is_common = false;
|
||||
sym.is_weak = false;
|
||||
if (msym.type == N_ABS)
|
||||
sym.value = msym.value;
|
||||
sym.no_dead_strip = (msym.desc & N_NO_DEAD_STRIP);
|
||||
this->syms.push_back(&sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SplitRegion {
|
||||
u32 offset;
|
||||
u32 size;
|
||||
u32 symidx;
|
||||
bool is_alt_entry;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct SplitInfo {
|
||||
InputSection<E> *isec;
|
||||
std::vector<SplitRegion> regions;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
static std::vector<SplitInfo<E>>
|
||||
split_regular_sections(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
std::vector<SplitInfo<E>> vec(file.sections.size());
|
||||
|
||||
for (i64 i = 0; i < file.sections.size(); i++)
|
||||
if (InputSection<E> *isec = file.sections[i].get())
|
||||
if (isec->hdr.type != S_CSTRING_LITERALS)
|
||||
vec[i].isec = isec;
|
||||
|
||||
// Find all symbols whose type is N_SECT.
|
||||
for (i64 i = 0; i < file.mach_syms.size(); i++) {
|
||||
MachSym &msym = file.mach_syms[i];
|
||||
if (!msym.stab && msym.type == N_SECT && vec[msym.sect - 1].isec) {
|
||||
SplitRegion r;
|
||||
r.offset = msym.value - vec[msym.sect - 1].isec->hdr.addr;
|
||||
r.symidx = i;
|
||||
r.is_alt_entry = (msym.desc & N_ALT_ENTRY);
|
||||
vec[msym.sect - 1].regions.push_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
std::erase_if(vec, [](const SplitInfo<E> &info) { return !info.isec; });
|
||||
|
||||
sort(vec, [](const SplitInfo<E> &a, const SplitInfo<E> &b) {
|
||||
return a.isec->hdr.addr < b.isec->hdr.addr;
|
||||
});
|
||||
|
||||
for (SplitInfo<E> &info : vec) {
|
||||
sort(info.regions, [](const SplitRegion &a, const SplitRegion &b) {
|
||||
return a.offset < b.offset;
|
||||
});
|
||||
}
|
||||
|
||||
// If two symbols point to the same location, we create only one
|
||||
// subsection.
|
||||
for (SplitInfo<E> &info : vec) {
|
||||
i64 last = -1;
|
||||
for (SplitRegion &r : info.regions) {
|
||||
if (!r.is_alt_entry) {
|
||||
if (r.offset == last)
|
||||
r.is_alt_entry = true;
|
||||
last = r.offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix regions so that they cover the entire section without overlapping.
|
||||
for (SplitInfo<E> &info : vec) {
|
||||
std::vector<SplitRegion> &r = info.regions;
|
||||
|
||||
if (r.empty()) {
|
||||
r.push_back({0, (u32)info.isec->hdr.size, (u32)-1, false});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r[0].offset > 0)
|
||||
r.insert(r.begin(), {0, r[0].offset, (u32)-1, false});
|
||||
|
||||
for (i64 i = 1; i < r.size(); i++)
|
||||
if (r[i - 1].offset == r[i].offset)
|
||||
r[i++].is_alt_entry = true;
|
||||
|
||||
i64 last = -1;
|
||||
for (i64 i = 0; i < r.size(); i++) {
|
||||
if (!r[i].is_alt_entry) {
|
||||
if (last != -1)
|
||||
r[last].size = r[i].offset - r[last].offset;
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (last != -1)
|
||||
r[last].size = info.isec->hdr.size - r[last].offset;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::split_subsections_via_symbols(Context<E> &ctx) {
|
||||
sym_to_subsec.resize(mach_syms.size());
|
||||
|
||||
auto add = [&](InputSection<E> &isec, u32 offset, u32 size, u8 p2align,
|
||||
bool is_cstring) {
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = isec,
|
||||
.input_offset = offset,
|
||||
.input_size = size,
|
||||
.input_addr = (u32)(isec.hdr.addr + offset),
|
||||
.p2align = p2align,
|
||||
.is_cstring = is_cstring,
|
||||
};
|
||||
|
||||
subsec_pool.emplace_back(subsec);
|
||||
subsections.push_back(subsec);
|
||||
};
|
||||
|
||||
// Split regular sections into subsections.
|
||||
for (SplitInfo<E> &info : split_regular_sections(ctx, *this)) {
|
||||
InputSection<E> &isec = *info.isec;
|
||||
for (SplitRegion &r : info.regions) {
|
||||
if (!r.is_alt_entry)
|
||||
add(isec, r.offset, r.size, isec.hdr.p2align, false);
|
||||
if (r.symidx != -1)
|
||||
sym_to_subsec[r.symidx] = subsections.back();
|
||||
}
|
||||
}
|
||||
|
||||
// Split __cstring section.
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections) {
|
||||
if (isec && isec->hdr.type == S_CSTRING_LITERALS) {
|
||||
std::string_view str = isec->contents;
|
||||
size_t pos = 0;
|
||||
|
||||
while (pos < str.size()) {
|
||||
size_t end = str.find('\0', pos);
|
||||
if (end == str.npos)
|
||||
Fatal(ctx) << *this << " corruupted cstring section: " << *isec;
|
||||
|
||||
end = str.find_first_not_of('\0', end);
|
||||
if (end == str.npos)
|
||||
end = str.size();
|
||||
|
||||
// A constant string in __cstring has no alignment info, so we
|
||||
// need to infer it.
|
||||
u8 p2align = std::min<u8>(isec->hdr.p2align, std::countr_zero(pos));
|
||||
add(*isec, pos, end - pos, p2align, true);
|
||||
pos = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::init_subsections(Context<E> &ctx) {
|
||||
subsections.resize(sections.size());
|
||||
|
||||
for (i64 i = 0; i < sections.size(); i++) {
|
||||
if (InputSection<E> *isec = sections[i].get()) {
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = *isec,
|
||||
.input_offset = 0,
|
||||
.input_size = (u32)isec->hdr.size,
|
||||
.input_addr = (u32)isec->hdr.addr,
|
||||
.p2align = (u8)isec->hdr.p2align,
|
||||
};
|
||||
subsec_pool.emplace_back(subsec);
|
||||
subsections[i] = subsec;
|
||||
}
|
||||
}
|
||||
|
||||
sym_to_subsec.resize(mach_syms.size());
|
||||
|
||||
for (i64 i = 0; i < mach_syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.stab && msym.type == N_SECT)
|
||||
sym_to_subsec[i] = subsections[msym.sect - 1];
|
||||
}
|
||||
|
||||
std::erase(subsections, nullptr);
|
||||
}
|
||||
|
||||
// Fix local symbols `subsec` members.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::fix_subsec_members(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < mach_syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
|
||||
if (!msym.stab && !msym.is_extern && msym.type == N_SECT) {
|
||||
Subsection<E> *subsec = sym_to_subsec[i];
|
||||
if (!subsec)
|
||||
subsec = find_subsection(ctx, msym.sect - 1, msym.value);
|
||||
|
||||
if (subsec) {
|
||||
sym.subsec = subsec;
|
||||
sym.value = msym.value - subsec->input_addr;
|
||||
} else {
|
||||
// Subsec is null if a symbol is in a __compact_unwind.
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_data_in_code(Context<E> &ctx) {
|
||||
if (auto *cmd = (LinkEditDataCommand *)find_load_command(ctx, LC_DATA_IN_CODE)) {
|
||||
data_in_code_entries = {
|
||||
(DataInCodeEntry *)(this->mf->data + cmd->dataoff),
|
||||
cmd->datasize / sizeof(DataInCodeEntry),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<std::string> ObjectFile<E>::get_linker_options(Context<E> &ctx) {
|
||||
if (get_file_type(this->mf) == FileType::LLVM_BITCODE)
|
||||
return {};
|
||||
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
std::vector<std::string> vec;
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
p += lc.cmdsize;
|
||||
|
||||
if (lc.cmd == LC_LINKER_OPTION) {
|
||||
LinkerOptionCommand *cmd = (LinkerOptionCommand *)&lc;
|
||||
char *buf = (char *)cmd + sizeof(*cmd);
|
||||
for (i64 i = 0; i < cmd->count; i++) {
|
||||
vec.push_back(buf);
|
||||
buf += vec.back().size() + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
LoadCommand *ObjectFile<E>::find_load_command(Context<E> &ctx, u32 type) {
|
||||
if (!this->mf)
|
||||
return nullptr;
|
||||
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
if (lc.cmd == type)
|
||||
return &lc;
|
||||
p += lc.cmdsize;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Subsection<E> *
|
||||
ObjectFile<E>::find_subsection(Context<E> &ctx, u32 secidx, u32 addr) {
|
||||
Subsection<E> *ret = nullptr;
|
||||
for (Subsection<E> *subsec : subsections)
|
||||
if (subsec->isec.secidx == secidx && subsec->input_addr <= addr)
|
||||
ret = subsec;
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Symbol<E> *ObjectFile<E>::find_symbol(Context<E> &ctx, u32 addr) {
|
||||
for (i64 i = 0; i < mach_syms.size(); i++)
|
||||
if (MachSym &msym = mach_syms[i]; msym.is_extern && msym.value == addr)
|
||||
return this->syms[i];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_compact_unwind(Context<E> &ctx, MachSection &hdr) {
|
||||
if (hdr.size % sizeof(CompactUnwindEntry))
|
||||
Fatal(ctx) << *this << ": invalid __compact_unwind section size";
|
||||
|
||||
i64 num_entries = hdr.size / sizeof(CompactUnwindEntry);
|
||||
unwind_records.reserve(num_entries);
|
||||
|
||||
CompactUnwindEntry *src = (CompactUnwindEntry *)(this->mf->data + hdr.offset);
|
||||
|
||||
// Read compact unwind entries
|
||||
for (i64 i = 0; i < num_entries; i++)
|
||||
unwind_records.emplace_back(src[i].code_len, src[i].encoding);
|
||||
|
||||
// Read relocations
|
||||
MachRel *mach_rels = (MachRel *)(this->mf->data + hdr.reloff);
|
||||
for (i64 i = 0; i < hdr.nreloc; i++) {
|
||||
MachRel &r = mach_rels[i];
|
||||
if (r.offset >= hdr.size)
|
||||
Fatal(ctx) << *this << ": relocation offset too large: " << i;
|
||||
|
||||
i64 idx = r.offset / sizeof(CompactUnwindEntry);
|
||||
UnwindRecord<E> &dst = unwind_records[idx];
|
||||
|
||||
auto error = [&] {
|
||||
Fatal(ctx) << *this << ": __compact_unwind: unsupported relocation: " << i
|
||||
<< " " << *this->syms[r.idx];
|
||||
};
|
||||
|
||||
if (r.is_pcrel || r.p2size != 3 || r.type)
|
||||
error();
|
||||
|
||||
switch (r.offset % sizeof(CompactUnwindEntry)) {
|
||||
case offsetof(CompactUnwindEntry, code_start): {
|
||||
Subsection<E> *target;
|
||||
if (r.is_extern)
|
||||
target = sym_to_subsec[r.idx];
|
||||
else
|
||||
target = find_subsection(ctx, r.idx - 1, src[idx].code_start);
|
||||
|
||||
if (!target)
|
||||
error();
|
||||
|
||||
dst.subsec = target;
|
||||
dst.offset = src[idx].code_start - target->input_addr;
|
||||
break;
|
||||
}
|
||||
case offsetof(CompactUnwindEntry, personality):
|
||||
if (r.is_extern) {
|
||||
dst.personality = this->syms[r.idx];
|
||||
} else {
|
||||
u32 addr = *(ul32 *)((u8 *)this->mf->data + hdr.offset + r.offset);
|
||||
dst.personality = find_symbol(ctx, addr);
|
||||
}
|
||||
|
||||
if (!dst.personality)
|
||||
Fatal(ctx) << *this << ": __compact_unwind: unsupported "
|
||||
<< "personality reference: " << i;
|
||||
break;
|
||||
case offsetof(CompactUnwindEntry, lsda): {
|
||||
u32 addr = *(ul32 *)((u8 *)this->mf->data + hdr.offset + r.offset);
|
||||
|
||||
Subsection<E> *target;
|
||||
if (r.is_extern)
|
||||
target = sym_to_subsec[r.idx];
|
||||
else
|
||||
target = find_subsection(ctx, r.idx - 1, addr);
|
||||
|
||||
if (!target)
|
||||
error();
|
||||
|
||||
dst.lsda = target;
|
||||
dst.lsda_offset = addr - target->input_addr;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
error();
|
||||
}
|
||||
}
|
||||
|
||||
for (i64 i = 0; i < num_entries; i++)
|
||||
if (!unwind_records[i].subsec)
|
||||
Fatal(ctx) << *this << "_: _compact_unwind: missing relocation at " << i;
|
||||
|
||||
// Sort unwind entries by offset
|
||||
sort(unwind_records, [](const UnwindRecord<E> &a, const UnwindRecord<E> &b) {
|
||||
return std::tuple(a.subsec->input_addr, a.offset) <
|
||||
std::tuple(b.subsec->input_addr, b.offset);
|
||||
});
|
||||
|
||||
// Associate unwind entries to subsections
|
||||
for (i64 i = 0; i < num_entries;) {
|
||||
Subsection<E> &subsec = *unwind_records[i].subsec;
|
||||
subsec.unwind_offset = i;
|
||||
|
||||
i64 j = i + 1;
|
||||
while (j < num_entries && unwind_records[j].subsec == &subsec)
|
||||
j++;
|
||||
subsec.nunwind = j - i;
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
// Symbols with higher priorities overwrites symbols with lower priorities.
|
||||
// Here is the list of priorities, from the highest to the lowest.
|
||||
//
|
||||
// 1. Strong defined symbol
|
||||
// 2. Weak defined symbol
|
||||
// 3. Strong defined symbol in a DSO/archive
|
||||
// 4. Weak Defined symbol in a DSO/archive
|
||||
// 5. Common symbol
|
||||
// 6. Common symbol in an archive
|
||||
// 7. Unclaimed (nonexistent) symbol
|
||||
//
|
||||
// Ties are broken by file priority.
|
||||
template <typename E>
|
||||
static u64 get_rank(InputFile<E> *file, bool is_common, bool is_weak) {
|
||||
if (is_common) {
|
||||
assert(!file->is_dylib);
|
||||
if (!file->is_alive)
|
||||
return (6 << 24) + file->priority;
|
||||
return (5 << 24) + file->priority;
|
||||
}
|
||||
|
||||
if (file->is_dylib || !file->is_alive) {
|
||||
if (is_weak)
|
||||
return (4 << 24) + file->priority;
|
||||
return (3 << 24) + file->priority;
|
||||
}
|
||||
|
||||
if (is_weak)
|
||||
return (2 << 24) + file->priority;
|
||||
return (1 << 24) + file->priority;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static u64 get_rank(Symbol<E> &sym) {
|
||||
if (!sym.file)
|
||||
return 7 << 24;
|
||||
return get_rank(sym.file, sym.is_common, sym.is_weak);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
auto is_private_extern = [&](MachSym &msym) {
|
||||
return this->is_hidden || msym.is_private_extern ||
|
||||
((msym.desc & N_WEAK_REF) && (msym.desc & N_WEAK_DEF));
|
||||
};
|
||||
|
||||
auto merge_scope = [&](Symbol<E> &sym, MachSym &msym) {
|
||||
// If at least one symbol defines it as an EXTERN symbol,
|
||||
// the result is an EXTERN symbol instead of PRIVATE_EXTERN,
|
||||
// so that the symbol is exported.
|
||||
if (sym.scope == SCOPE_EXTERN)
|
||||
return SCOPE_EXTERN;
|
||||
return is_private_extern(msym) ? SCOPE_PRIVATE_EXTERN : SCOPE_EXTERN;
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.is_extern || msym.is_undef())
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
bool is_weak = (msym.desc & N_WEAK_DEF);
|
||||
|
||||
sym.scope = merge_scope(sym, msym);
|
||||
|
||||
if (get_rank(this, msym.is_common(), is_weak) < get_rank(sym)) {
|
||||
sym.file = this;
|
||||
sym.is_imported = false;
|
||||
sym.is_weak = is_weak;
|
||||
sym.no_dead_strip = (msym.desc & N_NO_DEAD_STRIP);
|
||||
|
||||
switch (msym.type) {
|
||||
case N_UNDF:
|
||||
assert(msym.is_common());
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
sym.is_common = true;
|
||||
break;
|
||||
case N_ABS:
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
sym.is_common = false;
|
||||
break;
|
||||
case N_SECT:
|
||||
sym.subsec = sym_to_subsec[i];
|
||||
sym.value = msym.value - sym.subsec->input_addr;
|
||||
sym.is_common = false;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << sym << ": unknown symbol type: " << (u64)msym.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool ObjectFile<E>::is_objc_object(Context<E> &ctx) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections)
|
||||
if (isec)
|
||||
if (isec->hdr.match("__DATA", "__objc_catlist") ||
|
||||
(isec->hdr.get_segname() == "__TEXT" &&
|
||||
isec->hdr.get_sectname().starts_with("__swift")))
|
||||
return true;
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++)
|
||||
if (!mach_syms[i].is_undef() && mach_syms[i].is_extern &&
|
||||
this->syms[i]->name.starts_with("_OBJC_CLASS_$_"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void
|
||||
ObjectFile<E>::mark_live_objects(Context<E> &ctx,
|
||||
std::function<void(ObjectFile<E> *)> feeder) {
|
||||
assert(this->is_alive);
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.is_extern)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
if (!sym.file)
|
||||
continue;
|
||||
|
||||
if (msym.is_undef() || (msym.is_common() && !sym.is_common))
|
||||
if (InputFile<E> *file = sym.file)
|
||||
if (!file->is_alive.exchange(true) && !file->is_dylib)
|
||||
feeder((ObjectFile<E> *)file);
|
||||
}
|
||||
|
||||
for (Subsection<E> *subsec : subsections)
|
||||
for (UnwindRecord<E> &rec : subsec->get_unwind_records())
|
||||
if (Symbol<E> *sym = rec.personality)
|
||||
if (InputFile<E> *file = sym->file)
|
||||
if (!file->is_alive.exchange(true) && !file->is_dylib)
|
||||
feeder((ObjectFile<E> *)file);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->mach_syms.size(); i++) {
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
MachSym &msym = mach_syms[i];
|
||||
|
||||
if (sym.file == this && sym.is_common) {
|
||||
InputSection<E> *isec = get_common_sec(ctx);
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = *isec,
|
||||
.input_size = (u32)msym.value,
|
||||
.p2align = (u8)msym.common_p2align,
|
||||
};
|
||||
|
||||
subsections.emplace_back(subsec);
|
||||
|
||||
sym.is_imported = false;
|
||||
sym.is_weak = false;
|
||||
sym.no_dead_strip = (msym.desc & N_NO_DEAD_STRIP);
|
||||
sym.subsec = subsec;
|
||||
sym.value = 0;
|
||||
sym.is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::check_duplicate_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->mach_syms.size(); i++) {
|
||||
Symbol<E> *sym = this->syms[i];
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (sym && sym->file && sym->file != this && !msym.is_undef() &&
|
||||
!msym.is_common() && !(msym.desc & N_WEAK_DEF))
|
||||
Error(ctx) << "duplicate symbol: " << *this << ": " << *sym->file
|
||||
<< ": " << *sym;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E> *ObjectFile<E>::get_common_sec(Context<E> &ctx) {
|
||||
if (!common_sec) {
|
||||
MachSection *hdr = new MachSection;
|
||||
common_hdr.reset(hdr);
|
||||
|
||||
memset(hdr, 0, sizeof(*hdr));
|
||||
hdr->set_segname("__DATA");
|
||||
hdr->set_sectname("__common");
|
||||
hdr->type = S_ZEROFILL;
|
||||
|
||||
common_sec = new InputSection<E>(ctx, *this, *hdr, sections.size());
|
||||
sections.emplace_back(common_sec);
|
||||
}
|
||||
return common_sec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_lto_symbols(Context<E> &ctx) {
|
||||
i64 nsyms = ctx.lto.module_get_num_symbols(this->lto_module);
|
||||
this->syms.reserve(nsyms);
|
||||
this->mach_syms2.reserve(nsyms);
|
||||
|
||||
for (i64 i = 0; i < nsyms; i++) {
|
||||
std::string_view name = ctx.lto.module_get_symbol_name(this->lto_module, i);
|
||||
this->syms.push_back(get_symbol(ctx, name));
|
||||
|
||||
u32 attr = ctx.lto.module_get_symbol_attribute(this->lto_module, i);
|
||||
MachSym msym = {};
|
||||
|
||||
switch (attr & LTO_SYMBOL_DEFINITION_MASK) {
|
||||
case LTO_SYMBOL_DEFINITION_REGULAR:
|
||||
case LTO_SYMBOL_DEFINITION_TENTATIVE:
|
||||
case LTO_SYMBOL_DEFINITION_WEAK:
|
||||
msym.type = N_ABS;
|
||||
break;
|
||||
case LTO_SYMBOL_DEFINITION_UNDEFINED:
|
||||
case LTO_SYMBOL_DEFINITION_WEAKUNDEF:
|
||||
msym.type = N_UNDF;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
switch (attr & LTO_SYMBOL_SCOPE_MASK) {
|
||||
case 0:
|
||||
case LTO_SYMBOL_SCOPE_INTERNAL:
|
||||
case LTO_SYMBOL_SCOPE_HIDDEN:
|
||||
break;
|
||||
case LTO_SYMBOL_SCOPE_DEFAULT:
|
||||
case LTO_SYMBOL_SCOPE_PROTECTED:
|
||||
case LTO_SYMBOL_SCOPE_DEFAULT_CAN_BE_HIDDEN:
|
||||
msym.is_extern = true;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
mach_syms2.push_back(msym);
|
||||
}
|
||||
|
||||
mach_syms = mach_syms2;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::string_view ObjectFile<E>::get_linker_optimization_hints(Context<E> &ctx) {
|
||||
LinkEditDataCommand *cmd =
|
||||
(LinkEditDataCommand *)find_load_command(ctx, LC_LINKER_OPTIMIZATION_HINT);
|
||||
|
||||
if (cmd)
|
||||
return {(char *)this->mf->data + cmd->dataoff, cmd->datasize};
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
DylibFile<E>::DylibFile(Context<E> &ctx, MappedFile<Context<E>> *mf)
|
||||
: InputFile<E>(mf) {
|
||||
this->is_dylib = true;
|
||||
this->is_alive = (ctx.needed_l || !ctx.arg.dead_strip_dylibs);
|
||||
this->is_weak = ctx.weak_l;
|
||||
this->is_reexported = ctx.reexport_l;
|
||||
ctx.dylib_pool.emplace_back(this);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
DylibFile<E> *DylibFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
DylibFile<E> *file = new DylibFile<E>(ctx, mf);
|
||||
file->parse(ctx);
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static MappedFile<Context<E>> *
|
||||
find_external_lib(Context<E> &ctx, std::string_view parent, std::string path) {
|
||||
if (!path.starts_with('/'))
|
||||
return MappedFile<Context<E>>::open(ctx, path);
|
||||
|
||||
for (const std::string &root : ctx.arg.syslibroot) {
|
||||
if (path.ends_with(".tbd")) {
|
||||
if (auto *file = MappedFile<Context<E>>::open(ctx, root + path))
|
||||
return file;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path.ends_with(".dylib")) {
|
||||
std::string stem(path.substr(0, path.size() - 6));
|
||||
if (auto *file = MappedFile<Context<E>>::open(ctx, root + stem + ".tbd"))
|
||||
return file;
|
||||
if (auto *file = MappedFile<Context<E>>::open(ctx, root + path))
|
||||
return file;
|
||||
}
|
||||
|
||||
for (std::string extn : {".tbd", ".dylib"})
|
||||
if (auto *file = MappedFile<Context<E>>::open(ctx, root + path + extn))
|
||||
return file;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse(Context<E> &ctx) {
|
||||
switch (get_file_type(this->mf)) {
|
||||
case FileType::TAPI:
|
||||
parse_tapi(ctx);
|
||||
break;
|
||||
case FileType::MACH_DYLIB:
|
||||
parse_dylib(ctx);
|
||||
break;
|
||||
case FileType::MACH_EXE:
|
||||
parse_dylib(ctx);
|
||||
dylib_idx = BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": is not a dylib";
|
||||
}
|
||||
|
||||
// Read reexported libraries if any
|
||||
for (std::string_view path : reexported_libs) {
|
||||
MappedFile<Context<E>> *mf =
|
||||
find_external_lib(ctx, install_name, std::string(path));
|
||||
if (!mf)
|
||||
Fatal(ctx) << install_name << ": cannot open reexported library " << path;
|
||||
|
||||
DylibFile<E> *child = DylibFile<E>::create(ctx, mf);
|
||||
exports.merge(child->exports);
|
||||
weak_exports.merge(child->weak_exports);
|
||||
}
|
||||
|
||||
// Initialize syms and is_weak_symbols vectors
|
||||
for (std::string_view s : exports) {
|
||||
this->syms.push_back(get_symbol(ctx, s));
|
||||
is_weak_symbol.push_back(false);
|
||||
}
|
||||
|
||||
for (std::string_view s : weak_exports) {
|
||||
if (!exports.contains(s)) {
|
||||
this->syms.push_back(get_symbol(ctx, s));
|
||||
is_weak_symbol.push_back(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
i64 flags = read_uleb(buf) & ~EXPORT_SYMBOL_FLAGS_KIND_MASK;
|
||||
read_uleb(buf); // addr
|
||||
|
||||
std::string_view name = save_string(ctx, prefix);
|
||||
if (flags & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION)
|
||||
weak_exports.insert(name);
|
||||
else
|
||||
exports.insert(name);
|
||||
|
||||
if (flags & EXPORT_SYMBOL_FLAGS_REEXPORT)
|
||||
read_uleb(buf); // skip a library ordinal
|
||||
} else {
|
||||
buf++;
|
||||
}
|
||||
|
||||
i64 nchild = *buf++;
|
||||
|
||||
for (i64 i = 0; i < nchild; i++) {
|
||||
std::string suffix((char *)buf);
|
||||
buf += suffix.size() + 1;
|
||||
i64 off = read_uleb(buf);
|
||||
read_trie(ctx, start, off, prefix + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse_tapi(Context<E> &ctx) {
|
||||
TextDylib tbd = parse_tbd(ctx, this->mf);
|
||||
|
||||
install_name = tbd.install_name;
|
||||
reexported_libs = std::move(tbd.reexported_libs);
|
||||
exports = std::move(tbd.exports);
|
||||
weak_exports = std::move(tbd.weak_exports);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse_dylib(Context<E> &ctx) {
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
if (ctx.arg.application_extension && !(hdr.flags & MH_APP_EXTENSION_SAFE))
|
||||
Warn(ctx) << "linking against a dylib which is not safe for use in "
|
||||
<< "application extensions: " << *this;
|
||||
|
||||
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;
|
||||
}
|
||||
case LC_REEXPORT_DYLIB:
|
||||
if (!(hdr.flags & MH_NO_REEXPORTED_DYLIBS)) {
|
||||
DylibCommand &cmd = *(DylibCommand *)p;
|
||||
reexported_libs.push_back((char *)p + cmd.nameoff);
|
||||
}
|
||||
break;
|
||||
}
|
||||
p += lc.cmdsize;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
if (get_rank(this, false, false) < get_rank(sym)) {
|
||||
sym.file = this;
|
||||
sym.scope = SCOPE_LOCAL;
|
||||
sym.is_imported = true;
|
||||
sym.is_weak = (this->is_weak || is_weak_symbol[i]);
|
||||
sym.no_dead_strip = false;
|
||||
sym.subsec = nullptr;
|
||||
sym.value = 0;
|
||||
sym.is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class InputFile<E>; \
|
||||
template class ObjectFile<E>; \
|
||||
template class DylibFile<E>; \
|
||||
template std::ostream &operator<<(std::ostream &, const InputFile<E> &)
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
+30
-18
@@ -2,20 +2,36 @@
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
OutputSection<E> &get_output_section(Context<E> &ctx, const MachSection &hdr) {
|
||||
static std::unordered_set<std::string_view> set = {
|
||||
"__got", "__auth_got", "__auth_ptr", "__nl_symbol_ptr", "__const",
|
||||
"__cfstring", "__mod_init_func", "__mod_term_func", "__objc_classlist",
|
||||
"__objc_nlclslist", "__objc_catlist", "__objc_nlcatlist", "__objc_protolist",
|
||||
};
|
||||
|
||||
std::string_view seg = hdr.get_segname();
|
||||
std::string_view sect = hdr.get_sectname();
|
||||
|
||||
if (seg == "__DATA" && set.contains(sect)) {
|
||||
seg = "__DATA_CONST";
|
||||
} else if (seg == "__TEXT" && sect == "__StaticInit") {
|
||||
sect = "__text";
|
||||
}
|
||||
|
||||
return *OutputSection<E>::get_instance(ctx, seg, sect);
|
||||
}
|
||||
|
||||
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())) {
|
||||
const MachSection &hdr, u32 secidx)
|
||||
: file(file), hdr(hdr), secidx(secidx), osec(get_output_section(ctx, hdr)) {
|
||||
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);
|
||||
|
||||
@@ -24,18 +40,15 @@ void InputSection<E>::parse_relocations(Context<E> &ctx) {
|
||||
return a.offset < b.offset;
|
||||
});
|
||||
|
||||
// Find subsections for this section
|
||||
auto begin = std::lower_bound(
|
||||
file.subsections.begin(), file.subsections.end(), hdr.addr,
|
||||
[](std::unique_ptr<Subsection<E>> &subsec, u32 addr) {
|
||||
// Find subsections this relocation section refers to
|
||||
auto cmp = [](Subsection<E> *subsec, u32 addr) {
|
||||
return subsec->input_addr < addr;
|
||||
});
|
||||
};
|
||||
|
||||
auto end = std::lower_bound(
|
||||
begin, file.subsections.end(), hdr.addr + hdr.size,
|
||||
[](std::unique_ptr<Subsection<E>> &subsec, u32 addr) {
|
||||
return subsec->input_addr < addr;
|
||||
});
|
||||
auto begin = std::lower_bound(file.subsections.begin(),
|
||||
file.subsections.end(), hdr.addr, cmp);
|
||||
auto end = std::lower_bound(begin, file.subsections.end(),
|
||||
hdr.addr + hdr.size, cmp);
|
||||
|
||||
// Assign each subsection a group of relocations
|
||||
i64 i = 0;
|
||||
@@ -54,7 +67,6 @@ void InputSection<E>::parse_relocations(Context<E> &ctx) {
|
||||
#define INSTANTIATE(E) \
|
||||
template class InputSection<E>
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,164 @@
|
||||
#include "lto.h"
|
||||
#include "mold.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
LTOPlugin::~LTOPlugin() {
|
||||
if (dlopen_handle)
|
||||
dlclose(dlopen_handle);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void do_load_plugin(Context<E> &ctx) {
|
||||
void *handle = dlopen(ctx.arg.lto_library.c_str(), RTLD_NOW | RTLD_GLOBAL);
|
||||
if (!handle)
|
||||
Fatal(ctx) << "could not open plugin file: " << dlerror();
|
||||
|
||||
ctx.lto.dlopen_handle = handle;
|
||||
|
||||
// I don't want to use a macro, but I couldn't find an easy way to
|
||||
// write the same thing without a macro.
|
||||
#define DLSYM(x) ctx.lto.x = (decltype(ctx.lto.x))dlsym(handle, "lto_" #x)
|
||||
DLSYM(get_version);
|
||||
DLSYM(get_error_message);
|
||||
DLSYM(module_is_object_file);
|
||||
DLSYM(module_is_object_file_for_target);
|
||||
DLSYM(module_has_objc_category);
|
||||
DLSYM(module_is_object_file_in_memory);
|
||||
DLSYM(module_is_object_file_in_memory_for_target);
|
||||
DLSYM(module_create);
|
||||
DLSYM(module_create_from_memory);
|
||||
DLSYM(module_create_from_memory_with_path);
|
||||
DLSYM(module_create_in_local_context);
|
||||
DLSYM(module_create_in_codegen_context);
|
||||
DLSYM(module_create_from_fd);
|
||||
DLSYM(module_create_from_fd_at_offset);
|
||||
DLSYM(module_dispose);
|
||||
DLSYM(module_get_target_triple);
|
||||
DLSYM(module_set_target_triple);
|
||||
DLSYM(module_get_num_symbols);
|
||||
DLSYM(module_get_symbol_name);
|
||||
DLSYM(module_get_symbol_attribute);
|
||||
DLSYM(module_get_linkeropts);
|
||||
DLSYM(module_get_macho_cputype);
|
||||
DLSYM(module_has_ctor_dtor);
|
||||
DLSYM(codegen_set_diagnostic_handler);
|
||||
DLSYM(codegen_create);
|
||||
DLSYM(codegen_create_in_local_context);
|
||||
DLSYM(codegen_dispose);
|
||||
DLSYM(codegen_add_module);
|
||||
DLSYM(codegen_set_module);
|
||||
DLSYM(codegen_set_debug_model);
|
||||
DLSYM(codegen_set_pic_model);
|
||||
DLSYM(codegen_set_cpu);
|
||||
DLSYM(codegen_set_assembler_path);
|
||||
DLSYM(codegen_set_assembler_args);
|
||||
DLSYM(codegen_add_must_preserve_symbol);
|
||||
DLSYM(codegen_write_merged_modules);
|
||||
DLSYM(codegen_compile);
|
||||
DLSYM(codegen_compile_to_file);
|
||||
DLSYM(codegen_optimize);
|
||||
DLSYM(codegen_compile_optimized);
|
||||
DLSYM(api_version);
|
||||
DLSYM(set_debug_options);
|
||||
DLSYM(codegen_debug_options);
|
||||
DLSYM(codegen_debug_options_array);
|
||||
DLSYM(initialize_disassembler);
|
||||
DLSYM(codegen_set_should_internalize);
|
||||
DLSYM(codegen_set_should_embed_uselists);
|
||||
#undef DLSYM
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void load_lto_plugin(Context<E> &ctx) {
|
||||
std::call_once(ctx.lto_plugin_loaded, [&]() { do_load_plugin(ctx); });
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void do_lto(Context<E> &ctx) {
|
||||
LTOCodeGen *cg = ctx.lto.codegen_create();
|
||||
|
||||
// Add bitcode files to CodeGen.
|
||||
for (const std::string &opt : ctx.arg.mllvm)
|
||||
ctx.lto.codegen_debug_options(cg, opt.c_str());
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->lto_module)
|
||||
ctx.lto.codegen_add_module(cg, file->lto_module);
|
||||
|
||||
// Mark symbols that have to be preserved. All symbols that are not
|
||||
// marked here may be internalized and deleted as an extenrally-
|
||||
// visible symbol.
|
||||
if (ctx.output_type == MH_DYLIB || ctx.arg.export_dynamic) {
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (!file->lto_module) {
|
||||
for (i64 i = 0; i < file->mach_syms.size(); i++) {
|
||||
MachSym &msym = file->mach_syms[i];
|
||||
Symbol<E> &sym = *file->syms[i];
|
||||
if (msym.is_undef() && !sym.file->is_dylib &&
|
||||
((ObjectFile<E> *)sym.file)->lto_module)
|
||||
ctx.lto.codegen_add_must_preserve_symbol(cg, sym.name.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->lto_module)
|
||||
for (Symbol<E> *sym : file->syms)
|
||||
if (sym->file == file && sym->scope != SCOPE_LOCAL)
|
||||
ctx.lto.codegen_add_must_preserve_symbol(cg, sym->name.data());
|
||||
}
|
||||
|
||||
if (ctx.arg.entry->file)
|
||||
ctx.lto.codegen_add_must_preserve_symbol(cg, ctx.arg.entry->name.data());
|
||||
|
||||
// Run the compiler backend to do LTO.
|
||||
size_t size;
|
||||
u8 *data = (u8 *)ctx.lto.codegen_compile(cg, &size);
|
||||
if (!data)
|
||||
Fatal(ctx) << "lto_codegen_compile failed: " << ctx.lto.get_error_message();
|
||||
|
||||
if (!ctx.arg.object_path_lto.empty()) {
|
||||
FILE *out = fopen(ctx.arg.object_path_lto.c_str(), "w");
|
||||
if (!out)
|
||||
Fatal(ctx) << "-object_path_lto: cannot open " << ctx.arg.object_path_lto
|
||||
<< ": " << errno_string();
|
||||
fwrite(data, size, 1, out);
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
// Remove bitcode object files from ctx.objs.
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file->lto_module) {
|
||||
file->clear_symbols();
|
||||
file->is_alive = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a result of LTO as a new object file.
|
||||
MappedFile<Context<E>> *mf = new MappedFile<Context<E>>;
|
||||
mf->name = "<LTO>";
|
||||
mf->data = data;
|
||||
mf->size = size;
|
||||
ctx.mf_pool.emplace_back(mf);
|
||||
|
||||
ObjectFile<E> *obj = ObjectFile<E>::create(ctx, mf, "");
|
||||
obj->parse(ctx);
|
||||
obj->is_alive = true;
|
||||
obj->priority = 100; // regular file starts with priority 10,000
|
||||
obj->resolve_symbols(ctx);
|
||||
ctx.objs.push_back(obj);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void load_lto_plugin(Context<E> &); \
|
||||
template void do_lto(Context<E> &);
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "lto.h"
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
LTOPlugin::~LTOPlugin() {}
|
||||
|
||||
template <typename E>
|
||||
void load_lto_plugin(Context<E> &ctx) {
|
||||
Fatal(ctx) << "LTO is not supported on Windows";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void do_lto(Context<E> &ctx) {}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void load_lto_plugin(Context<E> &); \
|
||||
template void do_lto(Context<E> &);
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
|
||||
#endif
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
enum LTOSymbolAttributes {
|
||||
LTO_SYMBOL_ALIGNMENT_MASK = 0x0000001F,
|
||||
LTO_SYMBOL_PERMISSIONS_MASK = 0x000000E0,
|
||||
LTO_SYMBOL_PERMISSIONS_CODE = 0x000000A0,
|
||||
LTO_SYMBOL_PERMISSIONS_DATA = 0x000000C0,
|
||||
LTO_SYMBOL_PERMISSIONS_RODATA = 0x00000080,
|
||||
LTO_SYMBOL_DEFINITION_MASK = 0x00000700,
|
||||
LTO_SYMBOL_DEFINITION_REGULAR = 0x00000100,
|
||||
LTO_SYMBOL_DEFINITION_TENTATIVE = 0x00000200,
|
||||
LTO_SYMBOL_DEFINITION_WEAK = 0x00000300,
|
||||
LTO_SYMBOL_DEFINITION_UNDEFINED = 0x00000400,
|
||||
LTO_SYMBOL_DEFINITION_WEAKUNDEF = 0x00000500,
|
||||
LTO_SYMBOL_SCOPE_MASK = 0x00003800,
|
||||
LTO_SYMBOL_SCOPE_INTERNAL = 0x00000800,
|
||||
LTO_SYMBOL_SCOPE_HIDDEN = 0x00001000,
|
||||
LTO_SYMBOL_SCOPE_DEFAULT = 0x00001800,
|
||||
LTO_SYMBOL_SCOPE_PROTECTED = 0x00002000,
|
||||
LTO_SYMBOL_SCOPE_DEFAULT_CAN_BE_HIDDEN = 0x00002800,
|
||||
LTO_SYMBOL_COMDAT = 0x00004000,
|
||||
LTO_SYMBOL_ALIAS = 0x00008000,
|
||||
};
|
||||
|
||||
enum LTODebugModel {
|
||||
LTO_DEBUG_MODEL_NONE = 0,
|
||||
LTO_DEBUG_MODEL_DWARF = 1,
|
||||
};
|
||||
|
||||
enum LTOCodegenModel {
|
||||
LTO_CODEGEN_PIC_MODEL_STATIC = 0,
|
||||
LTO_CODEGEN_PIC_MODEL_DYNAMIC = 1,
|
||||
LTO_CODEGEN_PIC_MODEL_DYNAMIC_NO_PIC = 2,
|
||||
LTO_CODEGEN_PIC_MODEL_DEFAULT = 3,
|
||||
};
|
||||
|
||||
enum LTOCodegenDiagnosticSeverity {
|
||||
LTO_DS_ERROR = 0,
|
||||
LTO_DS_WARNING = 1,
|
||||
LTO_DS_REMARK = 3,
|
||||
LTO_DS_NOTE = 2,
|
||||
};
|
||||
|
||||
struct LTOObjectBuffer {
|
||||
const char *buffer;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
using LTOModule = void;
|
||||
using LTOCodeGen = void;
|
||||
|
||||
typedef void LTODiagnosticHandler(LTOCodegenDiagnosticSeverity severity,
|
||||
const char *diag, void *ctxt);
|
||||
|
||||
struct LTOPlugin {
|
||||
void *dlopen_handle = nullptr;
|
||||
~LTOPlugin();
|
||||
|
||||
char (*get_version)();
|
||||
|
||||
char (*get_error_message)();
|
||||
|
||||
bool (*module_is_object_file)(const char *path);
|
||||
|
||||
bool (*module_is_object_file_for_target)(const char *path,
|
||||
const char *target_triple_prefix);
|
||||
|
||||
bool (*module_has_objc_category)(const void *mem, size_t length);
|
||||
|
||||
bool (*module_is_object_file_in_memory)(const void *mem, size_t length);
|
||||
|
||||
bool (*module_is_object_file_in_memory_for_target)(
|
||||
const void *mem, size_t length, const char *target_triple_prefix);
|
||||
|
||||
LTOModule *(*module_create)(const char *path);
|
||||
|
||||
LTOModule *(*module_create_from_memory)(const void *mem, size_t length);
|
||||
|
||||
LTOModule *(*module_create_from_memory_with_path)(const void *mem,
|
||||
size_t length,
|
||||
const char *path);
|
||||
|
||||
LTOModule *(*module_create_in_local_context)(const void *mem, size_t length,
|
||||
const char *path);
|
||||
|
||||
LTOModule *(*module_create_in_codegen_context)(const void *mem, size_t length,
|
||||
const char *path,
|
||||
LTOCodeGen *cg);
|
||||
|
||||
LTOModule *(*module_create_from_fd)(int fd, const char *path,
|
||||
size_t file_size);
|
||||
|
||||
LTOModule *(*module_create_from_fd_at_offset)(int fd, const char *path,
|
||||
size_t file_size,
|
||||
size_t map_size, uint64_t offset);
|
||||
|
||||
void (*module_dispose)(LTOModule *mod);
|
||||
|
||||
const char *(*module_get_target_triple)(LTOModule *mod);
|
||||
|
||||
void (*module_set_target_triple)(LTOModule *mod, const char *triple);
|
||||
|
||||
unsigned (*module_get_num_symbols)(LTOModule *mod);
|
||||
|
||||
const char *(*module_get_symbol_name)(LTOModule *mod, unsigned index);
|
||||
|
||||
uint32_t (*module_get_symbol_attribute)(LTOModule *mod, unsigned index);
|
||||
|
||||
const char *(*module_get_linkeropts)(LTOModule *mod);
|
||||
|
||||
bool (*module_get_macho_cputype)(LTOModule *mod, unsigned *out_cputype,
|
||||
unsigned *out_cpusubtype);
|
||||
|
||||
bool (*module_has_ctor_dtor)(LTOModule *mod);
|
||||
|
||||
void (*codegen_set_diagnostic_handler)(LTOCodeGen *cg, LTODiagnosticHandler,
|
||||
void *);
|
||||
|
||||
LTOCodeGen *(*codegen_create)();
|
||||
|
||||
LTOCodeGen *(*codegen_create_in_local_context)();
|
||||
|
||||
void (*codegen_dispose)(LTOCodeGen *cg);
|
||||
|
||||
bool (*codegen_add_module)(LTOCodeGen *cg, LTOModule *mod);
|
||||
|
||||
void (*codegen_set_module)(LTOCodeGen *cg, LTOModule *mod);
|
||||
|
||||
bool (*codegen_set_debug_model)(LTOCodeGen *cg, LTODebugModel);
|
||||
|
||||
bool (*codegen_set_pic_model)(LTOCodeGen *cg, LTOCodegenModel);
|
||||
|
||||
void (*codegen_set_cpu)(LTOCodeGen *cg, const char *cpu);
|
||||
|
||||
void (*codegen_set_assembler_path)(LTOCodeGen *cg, const char *path);
|
||||
|
||||
void (*codegen_set_assembler_args)(LTOCodeGen *cg, const char **args,
|
||||
int nargs);
|
||||
|
||||
void (*codegen_add_must_preserve_symbol)(LTOCodeGen *cg, const char *symbol);
|
||||
|
||||
bool (*codegen_write_merged_modules)(LTOCodeGen *cg, const char *path);
|
||||
|
||||
const void *(*codegen_compile)(LTOCodeGen *cg, size_t *length);
|
||||
|
||||
bool (*codegen_compile_to_file)(LTOCodeGen *cg, const char **name);
|
||||
|
||||
bool (*codegen_optimize)(LTOCodeGen *cg);
|
||||
|
||||
const void *(*codegen_compile_optimized)(LTOCodeGen *cg, size_t *length);
|
||||
|
||||
unsigned (*api_version)();
|
||||
|
||||
void (*set_debug_options)(const char *const *options, int number);
|
||||
|
||||
void (*codegen_debug_options)(LTOCodeGen *cg, const char *);
|
||||
|
||||
void (*codegen_debug_options_array)(LTOCodeGen *cg, const char *const *,
|
||||
int number);
|
||||
|
||||
void (*initialize_disassembler)();
|
||||
|
||||
void (*codegen_set_should_internalize)(LTOCodeGen *cg,
|
||||
bool should_internalize);
|
||||
|
||||
void (*codegen_set_should_embed_uselists)(LTOCodeGen *cg,
|
||||
bool should_embed_use_lists);
|
||||
};
|
||||
|
||||
} // namespace mold::macho
|
||||
+282
-194
@@ -1,10 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "../big-endian.h"
|
||||
#include "../inttypes.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace mold::macho {
|
||||
@@ -19,6 +20,12 @@ typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
|
||||
struct ARM64;
|
||||
struct X86_64;
|
||||
|
||||
template <typename E>
|
||||
std::string rel_to_string(u8 r_type);
|
||||
|
||||
static constexpr u32 FAT_MAGIC = 0xcafebabe;
|
||||
|
||||
static constexpr u32 MH_OBJECT = 0x1;
|
||||
@@ -155,13 +162,13 @@ 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_LOC_RELOC = 0x1;
|
||||
static constexpr u32 S_ATTR_EXT_RELOC = 0x2;
|
||||
static constexpr u32 S_ATTR_SOME_INSTRUCTIONS = 0x4;
|
||||
|
||||
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_DEBUG = 0x20000;
|
||||
static constexpr u32 S_ATTR_SELF_MODIFYING_CODE = 0x40000;
|
||||
static constexpr u32 S_ATTR_LIVE_SUPPORT = 0x80000;
|
||||
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;
|
||||
@@ -193,10 +200,10 @@ 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 i32 BIND_SPECIAL_DYLIB_SELF = 0;
|
||||
static constexpr i32 BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE = -1;
|
||||
static constexpr i32 BIND_SPECIAL_DYLIB_FLAT_LOOKUP = -2;
|
||||
static constexpr i32 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;
|
||||
@@ -282,6 +289,11 @@ static constexpr u32 REFERENCE_FLAG_PRIVATE_UNDEFINED_LAZY = 5;
|
||||
|
||||
static constexpr u32 REFERENCED_DYNAMICALLY = 0x0010;
|
||||
|
||||
static constexpr u32 SELF_LIBRARY_ORDINAL = 0x0;
|
||||
static constexpr u32 MAX_LIBRARY_ORDINAL = 0xfd;
|
||||
static constexpr u32 DYNAMIC_LOOKUP_ORDINAL = 0xfe;
|
||||
static constexpr u32 EXECUTABLE_ORDINAL = 0xff;
|
||||
|
||||
static constexpr u32 N_NO_DEAD_STRIP = 0x0020;
|
||||
static constexpr u32 N_DESC_DISCARDED = 0x0020;
|
||||
static constexpr u32 N_WEAK_REF = 0x0040;
|
||||
@@ -305,6 +317,23 @@ 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 TOOL_MOLD = 54321; // Randomly chosen!
|
||||
|
||||
static constexpr u32 OBJC_IMAGE_SUPPORTS_GC = 1 << 1;
|
||||
static constexpr u32 OBJC_IMAGE_REQUIRES_GC = 1 << 2;
|
||||
static constexpr u32 OBJC_IMAGE_OPTIMIZED_BY_DYLD = 1 << 3;
|
||||
static constexpr u32 OBJC_IMAGE_SUPPORTS_COMPACTION = 1 << 4;
|
||||
static constexpr u32 OBJC_IMAGE_IS_SIMULATED = 1 << 5;
|
||||
static constexpr u32 OBJC_IMAGE_HAS_CATEGORY_CLASS_PROPERTIES = 1 << 6;
|
||||
|
||||
static constexpr u32 LOH_ARM64_ADRP_ADRP = 1;
|
||||
static constexpr u32 LOH_ARM64_ADRP_LDR = 2;
|
||||
static constexpr u32 LOH_ARM64_ADRP_ADD_LDR = 3;
|
||||
static constexpr u32 LOH_ARM64_ADRP_LDR_GOT_LDR = 4;
|
||||
static constexpr u32 LOH_ARM64_ADRP_ADD_STR = 5;
|
||||
static constexpr u32 LOH_ARM64_ADRP_LDR_GOT_STR = 6;
|
||||
static constexpr u32 LOH_ARM64_ADRP_ADD = 7;
|
||||
static constexpr u32 LOH_ARM64_ADRP_LDR_GOT = 8;
|
||||
|
||||
static constexpr u32 ARM64_RELOC_UNSIGNED = 0;
|
||||
static constexpr u32 ARM64_RELOC_SUBTRACTOR = 1;
|
||||
@@ -318,6 +347,24 @@ static constexpr u32 ARM64_RELOC_TLVP_LOAD_PAGE21 = 8;
|
||||
static constexpr u32 ARM64_RELOC_TLVP_LOAD_PAGEOFF12 = 9;
|
||||
static constexpr u32 ARM64_RELOC_ADDEND = 10;
|
||||
|
||||
template <>
|
||||
inline std::string rel_to_string<ARM64>(u8 type) {
|
||||
switch (type) {
|
||||
case ARM64_RELOC_UNSIGNED: return "ARM64_RELOC_UNSIGNED";
|
||||
case ARM64_RELOC_SUBTRACTOR: return "ARM64_RELOC_SUBTRACTOR";
|
||||
case ARM64_RELOC_BRANCH26: return "ARM64_RELOC_BRANCH26";
|
||||
case ARM64_RELOC_PAGE21: return "ARM64_RELOC_PAGE21";
|
||||
case ARM64_RELOC_PAGEOFF12: return "ARM64_RELOC_PAGEOFF12";
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21: return "ARM64_RELOC_GOT_LOAD_PAGE21";
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12: return "ARM64_RELOC_GOT_LOAD_PAGEOFF12";
|
||||
case ARM64_RELOC_POINTER_TO_GOT: return "ARM64_RELOC_POINTER_TO_GOT";
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21: return "ARM64_RELOC_TLVP_LOAD_PAGE21";
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12: return "ARM64_RELOC_TLVP_LOAD_PAGEOFF12";
|
||||
case ARM64_RELOC_ADDEND: return "ARM64_RELOC_ADDEND";
|
||||
}
|
||||
return "unknown (" + std::to_string(type) + ")";
|
||||
}
|
||||
|
||||
static constexpr u32 X86_64_RELOC_UNSIGNED = 0;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED = 1;
|
||||
static constexpr u32 X86_64_RELOC_BRANCH = 2;
|
||||
@@ -329,33 +376,50 @@ static constexpr u32 X86_64_RELOC_SIGNED_2 = 7;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED_4 = 8;
|
||||
static constexpr u32 X86_64_RELOC_TLV = 9;
|
||||
|
||||
template <>
|
||||
inline std::string rel_to_string<X86_64>(u8 type) {
|
||||
switch (type) {
|
||||
case X86_64_RELOC_UNSIGNED: return "X86_64_RELOC_UNSIGNED";
|
||||
case X86_64_RELOC_SIGNED: return "X86_64_RELOC_SIGNED";
|
||||
case X86_64_RELOC_BRANCH: return "X86_64_RELOC_BRANCH";
|
||||
case X86_64_RELOC_GOT_LOAD: return "X86_64_RELOC_GOT_LOAD";
|
||||
case X86_64_RELOC_GOT: return "X86_64_RELOC_GOT";
|
||||
case X86_64_RELOC_SUBTRACTOR: return "X86_64_RELOC_SUBTRACTOR";
|
||||
case X86_64_RELOC_SIGNED_1: return "X86_64_RELOC_SIGNED_1";
|
||||
case X86_64_RELOC_SIGNED_2: return "X86_64_RELOC_SIGNED_2";
|
||||
case X86_64_RELOC_SIGNED_4: return "X86_64_RELOC_SIGNED_4";
|
||||
case X86_64_RELOC_TLV: return "X86_64_RELOC_TLV";
|
||||
}
|
||||
return "unknown (" + std::to_string(type) + ")";
|
||||
}
|
||||
|
||||
struct FatHeader {
|
||||
ubig32 magic;
|
||||
ubig32 nfat_arch;
|
||||
ub32 magic;
|
||||
ub32 nfat_arch;
|
||||
};
|
||||
|
||||
struct FatArch {
|
||||
ubig32 cputype;
|
||||
ubig32 cpusubtype;
|
||||
ubig32 offset;
|
||||
ubig32 size;
|
||||
ubig32 align;
|
||||
ub32 cputype;
|
||||
ub32 cpusubtype;
|
||||
ub32 offset;
|
||||
ub32 size;
|
||||
ub32 align;
|
||||
};
|
||||
|
||||
struct MachHeader {
|
||||
u32 magic;
|
||||
u32 cputype;
|
||||
u32 cpusubtype;
|
||||
u32 filetype;
|
||||
u32 ncmds;
|
||||
u32 sizeofcmds;
|
||||
u32 flags;
|
||||
u32 reserved;
|
||||
ul32 magic;
|
||||
ul32 cputype;
|
||||
ul32 cpusubtype;
|
||||
ul32 filetype;
|
||||
ul32 ncmds;
|
||||
ul32 sizeofcmds;
|
||||
ul32 flags;
|
||||
ul32 reserved;
|
||||
};
|
||||
|
||||
struct LoadCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
};
|
||||
|
||||
struct SegmentCommand {
|
||||
@@ -363,17 +427,17 @@ struct SegmentCommand {
|
||||
return {segname, strnlen(segname, sizeof(segname))};
|
||||
}
|
||||
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
char segname[16];
|
||||
u64 vmaddr;
|
||||
u64 vmsize;
|
||||
u64 fileoff;
|
||||
u64 filesize;
|
||||
u32 maxprot;
|
||||
u32 initprot;
|
||||
u32 nsects;
|
||||
u32 flags;
|
||||
ul64 vmaddr;
|
||||
ul64 vmsize;
|
||||
ul64 fileoff;
|
||||
ul64 filesize;
|
||||
ul32 maxprot;
|
||||
ul32 initprot;
|
||||
ul32 nsects;
|
||||
ul32 flags;
|
||||
};
|
||||
|
||||
struct MachSection {
|
||||
@@ -401,138 +465,144 @@ struct MachSection {
|
||||
|
||||
char sectname[16];
|
||||
char segname[16];
|
||||
u64 addr;
|
||||
u64 size;
|
||||
u32 offset;
|
||||
u32 p2align;
|
||||
u32 reloff;
|
||||
u32 nreloc;
|
||||
u32 type : 8;
|
||||
u32 attr : 24;
|
||||
u32 reserved1;
|
||||
u32 reserved2;
|
||||
u32 reserved3;
|
||||
ul64 addr;
|
||||
ul64 size;
|
||||
ul32 offset;
|
||||
ul32 p2align;
|
||||
ul32 reloff;
|
||||
ul32 nreloc;
|
||||
u8 type;
|
||||
ul24 attr;
|
||||
ul32 reserved1;
|
||||
ul32 reserved2;
|
||||
ul32 reserved3;
|
||||
};
|
||||
|
||||
struct DylibCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 nameoff;
|
||||
u32 timestamp;
|
||||
u32 current_version;
|
||||
u32 compatibility_version;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 nameoff;
|
||||
ul32 timestamp;
|
||||
ul32 current_version;
|
||||
ul32 compatibility_version;
|
||||
};
|
||||
|
||||
struct DylinkerCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 nameoff;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 nameoff;
|
||||
};
|
||||
|
||||
struct SymtabCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 symoff;
|
||||
u32 nsyms;
|
||||
u32 stroff;
|
||||
u32 strsize;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 symoff;
|
||||
ul32 nsyms;
|
||||
ul32 stroff;
|
||||
ul32 strsize;
|
||||
};
|
||||
|
||||
struct DysymtabCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 ilocalsym;
|
||||
u32 nlocalsym;
|
||||
u32 iextdefsym;
|
||||
u32 nextdefsym;
|
||||
u32 iundefsym;
|
||||
u32 nundefsym;
|
||||
u32 tocoff;
|
||||
u32 ntoc;
|
||||
u32 modtaboff;
|
||||
u32 nmodtab;
|
||||
u32 extrefsymoff;
|
||||
u32 nextrefsyms;
|
||||
u32 indirectsymoff;
|
||||
u32 nindirectsyms;
|
||||
u32 extreloff;
|
||||
u32 nextrel;
|
||||
u32 locreloff;
|
||||
u32 nlocrel;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 ilocalsym;
|
||||
ul32 nlocalsym;
|
||||
ul32 iextdefsym;
|
||||
ul32 nextdefsym;
|
||||
ul32 iundefsym;
|
||||
ul32 nundefsym;
|
||||
ul32 tocoff;
|
||||
ul32 ntoc;
|
||||
ul32 modtaboff;
|
||||
ul32 nmodtab;
|
||||
ul32 extrefsymoff;
|
||||
ul32 nextrefsyms;
|
||||
ul32 indirectsymoff;
|
||||
ul32 nindirectsyms;
|
||||
ul32 extreloff;
|
||||
ul32 nextrel;
|
||||
ul32 locreloff;
|
||||
ul32 nlocrel;
|
||||
};
|
||||
|
||||
struct VersionMinCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 version;
|
||||
u32 sdk;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 version;
|
||||
ul32 sdk;
|
||||
};
|
||||
|
||||
struct DyldInfoCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 rebase_off;
|
||||
u32 rebase_size;
|
||||
u32 bind_off;
|
||||
u32 bind_size;
|
||||
u32 weak_bind_off;
|
||||
u32 weak_bind_size;
|
||||
u32 lazy_bind_off;
|
||||
u32 lazy_bind_size;
|
||||
u32 export_off;
|
||||
u32 export_size;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 rebase_off;
|
||||
ul32 rebase_size;
|
||||
ul32 bind_off;
|
||||
ul32 bind_size;
|
||||
ul32 weak_bind_off;
|
||||
ul32 weak_bind_size;
|
||||
ul32 lazy_bind_off;
|
||||
ul32 lazy_bind_size;
|
||||
ul32 export_off;
|
||||
ul32 export_size;
|
||||
};
|
||||
|
||||
struct UUIDCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
u8 uuid[16];
|
||||
};
|
||||
|
||||
struct RpathCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 path_off;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 path_off;
|
||||
};
|
||||
|
||||
struct LinkEditDataCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 dataoff;
|
||||
u32 datasize;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 dataoff;
|
||||
ul32 datasize;
|
||||
};
|
||||
|
||||
struct BuildToolVersion {
|
||||
u32 tool;
|
||||
u32 version;
|
||||
ul32 tool;
|
||||
ul32 version;
|
||||
};
|
||||
|
||||
struct BuildVersionCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 platform;
|
||||
u32 minos;
|
||||
u32 sdk;
|
||||
u32 ntools;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 platform;
|
||||
ul32 minos;
|
||||
ul32 sdk;
|
||||
ul32 ntools;
|
||||
};
|
||||
|
||||
struct EntryPointCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u64 entryoff;
|
||||
u64 stacksize;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul64 entryoff;
|
||||
ul64 stacksize;
|
||||
};
|
||||
|
||||
struct SourceVersionCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u64 version;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul64 version;
|
||||
};
|
||||
|
||||
struct DataInCodeEntry {
|
||||
u32 offset;
|
||||
u16 length;
|
||||
u16 kind;
|
||||
ul32 offset;
|
||||
ul16 length;
|
||||
ul16 kind;
|
||||
};
|
||||
|
||||
struct LinkerOptionCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 count;
|
||||
};
|
||||
|
||||
// This struct is named `n_list` on BSD and macOS.
|
||||
@@ -542,34 +612,42 @@ struct MachSym {
|
||||
}
|
||||
|
||||
bool is_common() const {
|
||||
return type == N_UNDF && ext && value;
|
||||
return type == N_UNDF && is_extern && value;
|
||||
}
|
||||
|
||||
u32 stroff;
|
||||
u8 ext : 1;
|
||||
u8 type : 3;
|
||||
u8 pext : 1;
|
||||
u8 stub : 3;
|
||||
u8 sect;
|
||||
ul32 stroff;
|
||||
|
||||
union {
|
||||
u16 desc;
|
||||
u8 n_type;
|
||||
struct {
|
||||
u16 : 8;
|
||||
u16 p2align : 4;
|
||||
u16 : 4;
|
||||
u8 is_extern : 1;
|
||||
u8 type : 3;
|
||||
u8 is_private_extern : 1;
|
||||
u8 stab : 3;
|
||||
};
|
||||
};
|
||||
u64 value;
|
||||
|
||||
u8 sect;
|
||||
|
||||
union {
|
||||
ul16 desc;
|
||||
struct {
|
||||
u8 padding;
|
||||
u8 common_p2align : 4;
|
||||
};
|
||||
};
|
||||
|
||||
ul64 value;
|
||||
};
|
||||
|
||||
// This struct is named `relocation_info` on BSD and macOS.
|
||||
struct MachRel {
|
||||
u32 offset;
|
||||
u32 idx : 24;
|
||||
u32 is_pcrel : 1;
|
||||
u32 p2size : 2;
|
||||
u32 is_extern : 1;
|
||||
u32 type : 4;
|
||||
ul32 offset;
|
||||
ul24 idx;
|
||||
u8 is_pcrel : 1;
|
||||
u8 p2size : 2;
|
||||
u8 is_extern : 1;
|
||||
u8 type : 4;
|
||||
};
|
||||
|
||||
// __TEXT,__unwind_info section contents
|
||||
@@ -580,47 +658,47 @@ static constexpr u32 UNWIND_SECOND_LEVEL_COMPRESSED = 3;
|
||||
static constexpr u32 UNWIND_PERSONALITY_MASK = 0x30000000;
|
||||
|
||||
struct UnwindSectionHeader {
|
||||
u32 version;
|
||||
u32 encoding_offset;
|
||||
u32 encoding_count;
|
||||
u32 personality_offset;
|
||||
u32 personality_count;
|
||||
u32 page_offset;
|
||||
u32 page_count;
|
||||
ul32 version;
|
||||
ul32 encoding_offset;
|
||||
ul32 encoding_count;
|
||||
ul32 personality_offset;
|
||||
ul32 personality_count;
|
||||
ul32 page_offset;
|
||||
ul32 page_count;
|
||||
};
|
||||
|
||||
struct UnwindFirstLevelPage {
|
||||
u32 func_addr;
|
||||
u32 page_offset;
|
||||
u32 lsda_offset;
|
||||
ul32 func_addr;
|
||||
ul32 page_offset;
|
||||
ul32 lsda_offset;
|
||||
};
|
||||
|
||||
struct UnwindSecondLevelPage {
|
||||
u32 kind;
|
||||
u16 page_offset;
|
||||
u16 page_count;
|
||||
u16 encoding_offset;
|
||||
u16 encoding_count;
|
||||
ul32 kind;
|
||||
ul16 page_offset;
|
||||
ul16 page_count;
|
||||
ul16 encoding_offset;
|
||||
ul16 encoding_count;
|
||||
};
|
||||
|
||||
struct UnwindLsdaEntry {
|
||||
u32 func_addr;
|
||||
u32 lsda_addr;
|
||||
ul32 func_addr;
|
||||
ul32 lsda_addr;
|
||||
};
|
||||
|
||||
struct UnwindPageEntry {
|
||||
u32 func_addr : 24;
|
||||
u32 encoding : 8;
|
||||
ul24 func_addr;
|
||||
u8 encoding;
|
||||
};
|
||||
|
||||
// __LD,__compact_unwind section contents
|
||||
|
||||
struct CompactUnwindEntry {
|
||||
u64 code_start;
|
||||
u32 code_len;
|
||||
u32 encoding;
|
||||
u64 personality;
|
||||
u64 lsda;
|
||||
ul64 code_start;
|
||||
ul32 code_len;
|
||||
ul32 encoding;
|
||||
ul64 personality;
|
||||
ul64 lsda;
|
||||
};
|
||||
|
||||
// __LINKEDIT,__code_signature
|
||||
@@ -635,46 +713,54 @@ static constexpr u32 CS_EXECSEG_MAIN_BINARY = 1;
|
||||
static constexpr u32 CS_HASHTYPE_SHA256 = 2;
|
||||
|
||||
struct CodeSignatureHeader {
|
||||
ubig32 magic;
|
||||
ubig32 length;
|
||||
ubig32 count;
|
||||
ub32 magic;
|
||||
ub32 length;
|
||||
ub32 count;
|
||||
};
|
||||
|
||||
struct CodeSignatureBlobIndex {
|
||||
ubig32 type;
|
||||
ubig32 offset;
|
||||
u32 padding;
|
||||
ub32 type;
|
||||
ub32 offset;
|
||||
ub32 padding;
|
||||
};
|
||||
|
||||
struct CodeSignatureDirectory {
|
||||
ubig32 magic;
|
||||
ubig32 length;
|
||||
ubig32 version;
|
||||
ubig32 flags;
|
||||
ubig32 hash_offset;
|
||||
ubig32 ident_offset;
|
||||
ubig32 n_special_slots;
|
||||
ubig32 n_code_slots;
|
||||
ubig32 code_limit;
|
||||
ub32 magic;
|
||||
ub32 length;
|
||||
ub32 version;
|
||||
ub32 flags;
|
||||
ub32 hash_offset;
|
||||
ub32 ident_offset;
|
||||
ub32 n_special_slots;
|
||||
ub32 n_code_slots;
|
||||
ub32 code_limit;
|
||||
u8 hash_size;
|
||||
u8 hash_type;
|
||||
u8 platform;
|
||||
u8 page_size;
|
||||
ubig32 spare2;
|
||||
ubig32 scatter_offset;
|
||||
ubig32 team_offset;
|
||||
ubig32 spare3;
|
||||
ubig64 code_limit64;
|
||||
ubig64 exec_seg_base;
|
||||
ubig64 exec_seg_limit;
|
||||
ubig64 exec_seg_flags;
|
||||
ub32 spare2;
|
||||
ub32 scatter_offset;
|
||||
ub32 team_offset;
|
||||
ub32 spare3;
|
||||
ub64 code_limit64;
|
||||
ub64 exec_seg_base;
|
||||
ub64 exec_seg_limit;
|
||||
ub64 exec_seg_flags;
|
||||
};
|
||||
|
||||
// __DATA,__objc_imageinfo
|
||||
struct ObjcImageInfo {
|
||||
ul32 version = 0;
|
||||
u8 flags = 0;
|
||||
u8 swift_version = 0;
|
||||
ul16 swift_lang_version = 0;
|
||||
};
|
||||
|
||||
struct ARM64 {
|
||||
static constexpr u32 cputype = CPU_TYPE_ARM64;
|
||||
static constexpr u32 cpusubtype = CPU_SUBTYPE_ARM64_ALL;
|
||||
static constexpr u32 page_size = 16384;
|
||||
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;
|
||||
@@ -683,11 +769,13 @@ struct ARM64 {
|
||||
struct X86_64 {
|
||||
static constexpr u32 cputype = CPU_TYPE_X86_64;
|
||||
static constexpr u32 cpusubtype = CPU_SUBTYPE_X86_64_ALL;
|
||||
static constexpr u32 page_size = 4096;
|
||||
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;
|
||||
};
|
||||
|
||||
inline constexpr size_t word_size = 8;
|
||||
|
||||
} // namespace mold::macho
|
||||
+777
-141
File diff suppressed because it is too large.
Load diff
+4
-4
@@ -30,8 +30,9 @@ void print_map(Context<E> &ctx) {
|
||||
if (file.is_alive) {
|
||||
out << "[" << std::setw(3) << i << "] " << file << "\n";
|
||||
for (Symbol<E> *sym : file.syms)
|
||||
if (sym->file == &file)
|
||||
syms.push_back({sym->get_addr(ctx), 0, (u32)i, sym->name});
|
||||
if (sym && sym->file == &file)
|
||||
if (!sym->subsec || sym->subsec->is_alive)
|
||||
syms.push_back({sym->get_addr(ctx), 0, (u32)i, sym->name});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +75,6 @@ void print_map(Context<E> &ctx) {
|
||||
#define INSTANTIATE(E) \
|
||||
template void print_map(Context<E> &)
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
+334
-202
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
+683
-313
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
|
||||
+209
-42
@@ -1,9 +1,27 @@
|
||||
// On macOS, you can pass a test file describing a dylib instead of an
|
||||
// actual dylib file to link against a dynamic library. Such text file
|
||||
// should be in the YAML format and contains dylib's exported symbols
|
||||
// as well as the file's various attributes. The extension of the text
|
||||
// file is `.tbd`.
|
||||
//
|
||||
// .tbd files allows users to link against a library without
|
||||
// distributing the binary of the library file itself.
|
||||
//
|
||||
// This file contains functions to parse the .tbd file.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename T, typename U>
|
||||
static void merge(std::set<T> &a, U &&b) {
|
||||
a.insert(b.begin(), b.end());
|
||||
}
|
||||
|
||||
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))
|
||||
@@ -13,6 +31,15 @@ get_vector(YamlNode &node, std::string_view key) {
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::vector<std::string_view>
|
||||
get_string_vector(YamlNode &node, std::string_view key) {
|
||||
std::vector<std::string_view> vec;
|
||||
for (YamlNode &mem : get_vector(node, key))
|
||||
if (auto val = std::get_if<std::string_view>(&mem.data))
|
||||
vec.push_back(*val);
|
||||
return vec;
|
||||
}
|
||||
|
||||
static std::optional<std::string_view>
|
||||
get_string(YamlNode &node, std::string_view key) {
|
||||
if (auto *map = std::get_if<std::map<std::string_view, YamlNode>>(&node.data))
|
||||
@@ -24,70 +51,206 @@ get_string(YamlNode &node, std::string_view key) {
|
||||
|
||||
static bool contains(const std::vector<YamlNode> &vec, std::string_view key) {
|
||||
for (const YamlNode &mem : vec)
|
||||
if (const std::string_view *s = std::get_if<std::string_view>(&mem.data))
|
||||
if (*s == key)
|
||||
if (auto val = std::get_if<std::string_view>(&mem.data))
|
||||
if (*val == key)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::optional<TextDylib> to_tbd(YamlNode &node, std::string_view arch) {
|
||||
if (!contains(get_vector(node, "targets"), arch))
|
||||
static bool match_arch(const std::vector<YamlNode> &vec, std::string_view arch) {
|
||||
for (const YamlNode &mem : vec)
|
||||
if (auto val = std::get_if<std::string_view>(&mem.data))
|
||||
if (*val == arch || val->starts_with(std::string(arch) + "-"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::optional<TextDylib>
|
||||
to_tbd(Context<E> &ctx, YamlNode &node, std::string_view arch,
|
||||
std::string_view filename) {
|
||||
if (!match_arch(get_vector(node, "targets"), arch))
|
||||
return {};
|
||||
|
||||
TextDylib tbd;
|
||||
if (ctx.arg.application_extension &&
|
||||
contains(get_vector(node, "flags"), "not_app_extension_safe"))
|
||||
Warn(ctx) << "linking against a dylib which is not safe for use in "
|
||||
<< "application extensions: " << filename;
|
||||
|
||||
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;
|
||||
TextDylib tbd;
|
||||
|
||||
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);
|
||||
if (match_arch(get_vector(mem, "targets"), arch))
|
||||
append(tbd.reexported_libs, get_string_vector(mem, "libraries"));
|
||||
|
||||
for (std::string_view key : {"exports", "reexports"})
|
||||
for (YamlNode &mem : get_vector(node, key))
|
||||
if (contains(get_vector(mem, "targets"), arch))
|
||||
for (YamlNode &mem : get_vector(mem, "symbols"))
|
||||
if (auto *sym = std::get_if<std::string_view>(&mem.data))
|
||||
tbd.exports.push_back(*sym);
|
||||
auto concat = [&](const std::string &x, std::string_view y) {
|
||||
return save_string(ctx, x + std::string(y));
|
||||
};
|
||||
|
||||
for (std::string_view key : {"exports", "reexports"}) {
|
||||
for (YamlNode &mem : get_vector(node, key)) {
|
||||
if (match_arch(get_vector(mem, "targets"), arch)) {
|
||||
merge(tbd.exports, get_string_vector(mem, "symbols"));
|
||||
merge(tbd.weak_exports, get_string_vector(mem, "weak-symbols"));
|
||||
|
||||
for (std::string_view s : get_string_vector(mem, "objc-classes")) {
|
||||
tbd.exports.insert(concat("_OBJC_CLASS_$_", s));
|
||||
tbd.exports.insert(concat("_OBJC_METACLASS_$_", s));
|
||||
}
|
||||
|
||||
for (std::string_view s : get_string_vector(mem, "objc-eh-types"))
|
||||
tbd.exports.insert(concat("_OBJC_EHTYPE_$_", s));
|
||||
|
||||
for (std::string_view s : get_string_vector(mem, "objc-ivars"))
|
||||
tbd.exports.insert(concat("_OBJC_IVAR_$_", s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tbd;
|
||||
}
|
||||
|
||||
static i64 parse_version(const std::string &arg) {
|
||||
auto flags = std::regex_constants::ECMAScript | std::regex_constants::optimize;
|
||||
static std::regex re(R"((\d+)(?:\.(\d+))?(?:\.(\d+))?)", flags);
|
||||
|
||||
std::smatch m;
|
||||
bool ok = std::regex_match(arg, m, re);
|
||||
assert(ok);
|
||||
|
||||
i64 major = (m[1].length() == 0) ? 0 : stoi(m[1]);
|
||||
i64 minor = (m[2].length() == 0) ? 0 : stoi(m[2]);
|
||||
i64 patch = (m[3].length() == 0) ? 0 : stoi(m[3]);
|
||||
return (major << 16) | (minor << 8) | patch;
|
||||
}
|
||||
|
||||
// Dylib can contain special symbols whose name starts with "$ld$".
|
||||
// Such symbols aren't actually symbols but linker directives.
|
||||
// We interpret such symbols in this function.
|
||||
template <typename E>
|
||||
static TextDylib squash(Context<E> &ctx, std::span<TextDylib> tbds) {
|
||||
static void interpret_ld_symbols(Context<E> &ctx, TextDylib &tbd) {
|
||||
std::set<std::string_view> syms;
|
||||
std::unordered_set<std::string_view> hidden_syms;
|
||||
|
||||
auto string_view = [](const std::csub_match &sub) {
|
||||
return std::string_view{sub.first, (size_t)sub.length()};
|
||||
};
|
||||
|
||||
for (std::string_view s : tbd.exports) {
|
||||
if (!s.starts_with("$ld$"))
|
||||
continue;
|
||||
|
||||
auto flags = std::regex_constants::ECMAScript | std::regex_constants::optimize;
|
||||
std::cmatch m;
|
||||
|
||||
// $ld$previous$ symbol replaces the default install name with a
|
||||
// specified one if the platform OS version is in a specified range.
|
||||
static std::regex previous_re(
|
||||
R"(\$ld\$previous\$([^$]+)\$([\d.]*)\$(\d+)\$([\d.]+)\$([\d.]+)\$(.*)\$)",
|
||||
flags);
|
||||
|
||||
if (std::regex_match(s.data(), s.data() + s.size(), m, previous_re)) {
|
||||
std::string_view install_name = string_view(m[1]);
|
||||
i64 platform = std::stoi(m[3].str());
|
||||
i64 min_version = parse_version(m[4]);
|
||||
i64 max_version = parse_version(m[5]);
|
||||
std::string_view symbol_name = string_view(m[6]);
|
||||
|
||||
if (!symbol_name.empty()) {
|
||||
// ld64 source seems to have implemented a feature to give an
|
||||
// alternative install name for a matching symbol, but it didn't
|
||||
// work in practice (or I may be using the feature in a wrong way.)
|
||||
// Ignore such symbol for now.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (platform == ctx.arg.platform &&
|
||||
min_version <= ctx.arg.platform_min_version &&
|
||||
ctx.arg.platform_min_version < max_version) {
|
||||
tbd.install_name = install_name;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// $ld$add$os_version$symbol adds a symbol if the given OS version
|
||||
// matches.
|
||||
static std::regex add_re(R"(\$ld\$add\$os([\d.]+)\$(.+))", flags);
|
||||
|
||||
if (std::regex_match(s.data(), s.data() + s.size(), m, add_re)) {
|
||||
if (ctx.arg.platform_min_version == parse_version(m[1]))
|
||||
syms.insert(string_view(m[2]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// $ld$hide$os_version$symbol hides a symbol if the given OS version
|
||||
// matches.
|
||||
static std::regex hidden_re(R"(\$ld\$hide\$os([\d.]+)\$(.+))", flags);
|
||||
|
||||
if (std::regex_match(s.data(), s.data() + s.size(), m, hidden_re)) {
|
||||
if (ctx.arg.platform_min_version == parse_version(m[1]))
|
||||
hidden_syms.insert(string_view(m[2]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// $ld$install_name$os_version$name changes the install name to a
|
||||
// given name.
|
||||
static std::regex
|
||||
install_name_re(R"(\$ld\$install_name\$os([\d.]+)\$(.+))", flags);
|
||||
|
||||
if (std::regex_match(s.data(), s.data() + s.size(), m, install_name_re)) {
|
||||
if (ctx.arg.platform_min_version == parse_version(m[1]))
|
||||
tbd.install_name = string_view(m[2]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::string_view s : tbd.exports)
|
||||
if (!s.starts_with("$ld$") && !hidden_syms.contains(s))
|
||||
syms.insert(s);
|
||||
|
||||
tbd.exports = std::move(syms);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static TextDylib parse(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string_view arch);
|
||||
|
||||
// A single YAML file may contain multiple text dylibs. The first text
|
||||
// dylib is the main file followed by optional other text dylibs for
|
||||
// re-exported libraries.
|
||||
//
|
||||
// This fucntion squashes multiple text dylibs into a single text dylib
|
||||
// by copying symbols of re-exported text dylibs to the main text dylib.
|
||||
template <typename E>
|
||||
static TextDylib
|
||||
squash(Context<E> &ctx, std::span<TextDylib> tbds, std::string_view arch) {
|
||||
std::unordered_map<std::string_view, TextDylib> map;
|
||||
std::vector<std::string_view> remainings;
|
||||
|
||||
TextDylib main = std::move(tbds[0]);
|
||||
for (TextDylib &tbd : tbds.subspan(1))
|
||||
map[tbd.install_name] = std::move(tbd);
|
||||
|
||||
std::vector<std::string_view> libs;
|
||||
std::function<void(TextDylib &)> visit = [&](TextDylib &tbd) {
|
||||
for (std::string_view lib : tbd.reexported_libs) {
|
||||
auto it = map.find(lib);
|
||||
|
||||
for (std::string_view lib : main.reexported_libs) {
|
||||
auto it = map.find(lib);
|
||||
if (it != map.end())
|
||||
append(main.exports, it->second.exports);
|
||||
else
|
||||
libs.push_back(lib);
|
||||
}
|
||||
if (it != map.end()) {
|
||||
TextDylib &child = it->second;
|
||||
merge(main.exports, child.exports);
|
||||
merge(main.weak_exports, child.weak_exports);
|
||||
visit(child);
|
||||
} else {
|
||||
remainings.push_back(lib);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
main.reexported_libs = std::move(libs);
|
||||
visit(main);
|
||||
main.reexported_libs = remainings;
|
||||
return main;
|
||||
}
|
||||
|
||||
@@ -109,19 +272,23 @@ static TextDylib parse(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
|
||||
std::vector<TextDylib> vec;
|
||||
for (YamlNode &node : nodes)
|
||||
if (std::optional<TextDylib> dylib = to_tbd(node, arch))
|
||||
if (std::optional<TextDylib> dylib = to_tbd(ctx, node, arch, mf->name))
|
||||
vec.push_back(*dylib);
|
||||
return squash(ctx, vec);
|
||||
|
||||
for (TextDylib &tbd : vec)
|
||||
interpret_ld_symbols(ctx, tbd);
|
||||
|
||||
return squash(ctx, vec, arch);
|
||||
}
|
||||
|
||||
template <>
|
||||
TextDylib parse_tbd(Context<ARM64> &ctx, MappedFile<Context<ARM64>> *mf) {
|
||||
return parse(ctx, mf, "arm64-macos");
|
||||
return parse(ctx, mf, "arm64");
|
||||
}
|
||||
|
||||
template <>
|
||||
TextDylib parse_tbd(Context<X86_64> &ctx, MappedFile<Context<X86_64>> *mf) {
|
||||
return parse(ctx, mf, "x86_64-macos");
|
||||
return parse(ctx, mf, "x86_64");
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
+37
-19
@@ -1,12 +1,27 @@
|
||||
// On macOS, a .tbd file can be used instead of a .dylib to link
|
||||
// against a dynamic library. A .tbd file is a text file in the YAML
|
||||
// format, so the linker has to be able to parse a YAML file. This
|
||||
// file implements a YAML parser.
|
||||
//
|
||||
// We implemented a YAML parser ourselves instead of using an external
|
||||
// library. We didn't want to add an dependency to an external library
|
||||
// just to read .tdb files.
|
||||
//
|
||||
// YAML as a format is very complicated. For example, the official
|
||||
// YAML specification allows embedding a JSON string into YAML; so it
|
||||
// is strictly larger than JSON. It has surprisingly many features
|
||||
// that most users are not aware of. Fortunately, we have to support
|
||||
// only a small portion of the spec to read a .tbd file.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
struct Token {
|
||||
enum { STRING = 1, INDENT, DEDENT, END };
|
||||
enum { STRING = 1, INDENT, DEDENT, END };
|
||||
|
||||
struct Token {
|
||||
u8 kind = 0;
|
||||
std::string_view str;
|
||||
};
|
||||
@@ -35,17 +50,20 @@ private:
|
||||
std::vector<Token> tokens;
|
||||
};
|
||||
|
||||
// A tokenizer for YAML. YAML represents blocks by indentation. This
|
||||
// tokenizer inserts INDENT and DEDENT special tokens before and after
|
||||
// each indented text block.
|
||||
std::optional<YamlError> YamlParser::tokenize() {
|
||||
std::vector<i64> indents = {0};
|
||||
|
||||
auto indent = [&](std::string_view str, i64 depth) {
|
||||
tokens.push_back({Token::INDENT, str});
|
||||
tokens.push_back({INDENT, str});
|
||||
indents.push_back(depth);
|
||||
};
|
||||
|
||||
auto dedent = [&](std::string_view str) {
|
||||
assert(indents.size() > 1);
|
||||
tokens.push_back({Token::DEDENT, str});
|
||||
tokens.push_back({DEDENT, str});
|
||||
indents.pop_back();
|
||||
};
|
||||
|
||||
@@ -63,7 +81,7 @@ std::optional<YamlError> YamlParser::tokenize() {
|
||||
if (str.starts_with("---")) {
|
||||
while (indents.size() > 1)
|
||||
dedent(str);
|
||||
tokens.push_back({Token::END, str.substr(0, 3)});
|
||||
tokens.push_back({END, str.substr(0, 3)});
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
@@ -71,7 +89,7 @@ std::optional<YamlError> YamlParser::tokenize() {
|
||||
if (str.starts_with("...")) {
|
||||
while (indents.size() > 1)
|
||||
dedent(str);
|
||||
tokens.push_back({Token::END, str.substr(0, 3)});
|
||||
tokens.push_back({END, str.substr(0, 3)});
|
||||
str = str.substr(str.size());
|
||||
return {};
|
||||
}
|
||||
@@ -217,7 +235,7 @@ YamlParser::tokenize_string(std::string_view &str, char end) {
|
||||
if (pos == str.npos)
|
||||
return YamlError{"unterminated string literal", start - input.data()};
|
||||
|
||||
tokens.push_back({Token::STRING, str.substr(1, pos - 1)});
|
||||
tokens.push_back({STRING, str.substr(1, pos - 1)});
|
||||
str = str.substr(pos + 1);
|
||||
return {};
|
||||
}
|
||||
@@ -228,7 +246,7 @@ YamlParser::tokenize_bare_string(std::string_view &str) {
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-/.");
|
||||
if (pos == str.npos)
|
||||
pos = str.size();
|
||||
tokens.push_back({Token::STRING, str.substr(0, pos)});
|
||||
tokens.push_back({STRING, str.substr(0, pos)});
|
||||
str = str.substr(pos);
|
||||
}
|
||||
|
||||
@@ -240,7 +258,7 @@ std::variant<std::vector<YamlNode>, YamlError> YamlParser::parse() {
|
||||
std::vector<YamlNode> vec;
|
||||
|
||||
while (!tok.empty()) {
|
||||
if (tok[0].kind == Token::END) {
|
||||
if (tok[0].kind == END) {
|
||||
tok = tok.subspan(1);
|
||||
continue;
|
||||
}
|
||||
@@ -250,7 +268,7 @@ std::variant<std::vector<YamlNode>, YamlError> YamlParser::parse() {
|
||||
return *err;
|
||||
vec.push_back(std::get<YamlNode>(elem));
|
||||
|
||||
if (tok[0].kind != Token::END)
|
||||
if (tok[0].kind != END)
|
||||
return YamlError{"stray token", tok[0].str.data() - input.data()};
|
||||
}
|
||||
return vec;
|
||||
@@ -258,11 +276,11 @@ std::variant<std::vector<YamlNode>, YamlError> YamlParser::parse() {
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_element(std::span<Token> &tok) {
|
||||
if (tok[0].kind == Token::INDENT) {
|
||||
if (tok[0].kind == INDENT) {
|
||||
tok = tok.subspan(1);
|
||||
|
||||
std::variant<YamlNode, YamlError> elem = parse_element(tok);
|
||||
assert(tok[0].kind == Token::DEDENT);
|
||||
assert(tok[0].kind == DEDENT);
|
||||
tok = tok.subspan(1);
|
||||
return elem;
|
||||
}
|
||||
@@ -270,7 +288,7 @@ YamlParser::parse_element(std::span<Token> &tok) {
|
||||
if (tok[0].kind == '-')
|
||||
return parse_list(tok);
|
||||
|
||||
if (tok.size() > 2 && tok[0].kind == Token::STRING && tok[1].kind == ':')
|
||||
if (tok.size() > 2 && tok[0].kind == STRING && tok[1].kind == ':')
|
||||
return parse_map(tok);
|
||||
|
||||
return parse_flow_element(tok);
|
||||
@@ -280,7 +298,7 @@ std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_list(std::span<Token> &tok) {
|
||||
std::vector<YamlNode> vec;
|
||||
|
||||
while (tok[0].kind != Token::END && tok[0].kind != Token::DEDENT) {
|
||||
while (tok[0].kind != END && tok[0].kind != DEDENT) {
|
||||
if (tok[0].kind != '-')
|
||||
return YamlError{"list element expected", tok[0].str.data() - input.data()};
|
||||
tok = tok.subspan(1);
|
||||
@@ -297,8 +315,8 @@ std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_map(std::span<Token> &tok) {
|
||||
std::map<std::string_view, YamlNode> map;
|
||||
|
||||
while (tok[0].kind != Token::END && tok[0].kind != Token::DEDENT) {
|
||||
if (tok.size() < 2 || tok[0].kind != Token::STRING || tok[1].kind != ':')
|
||||
while (tok[0].kind != END && tok[0].kind != DEDENT) {
|
||||
if (tok.size() < 2 || tok[0].kind != STRING || tok[1].kind != ':')
|
||||
return YamlError{"map key expected", tok[0].str.data() - input.data()};
|
||||
|
||||
std::string_view key = tok[0].str;
|
||||
@@ -319,7 +337,7 @@ YamlParser::parse_flow_element(std::span<Token> &tok) {
|
||||
return parse_flow_list(tok);
|
||||
}
|
||||
|
||||
if (tok[0].kind != Token::STRING)
|
||||
if (tok[0].kind != STRING)
|
||||
return YamlError{"scalar expected", tok[0].str.data() - input.data()};
|
||||
|
||||
std::string_view val = tok[0].str;
|
||||
@@ -332,7 +350,7 @@ YamlParser::parse_flow_list(std::span<Token> &tok) {
|
||||
std::vector<YamlNode> vec;
|
||||
const char *start = tok[0].str.data();
|
||||
|
||||
while (tok[0].kind != ']' && tok[0].kind != Token::END) {
|
||||
while (tok[0].kind != ']' && tok[0].kind != END) {
|
||||
std::variant<YamlNode, YamlError> elem = parse_flow_element(tok);
|
||||
if (YamlError *err = std::get_if<YamlError>(&elem))
|
||||
return *err;
|
||||
@@ -345,7 +363,7 @@ YamlParser::parse_flow_list(std::span<Token> &tok) {
|
||||
tok = tok.subspan(1);
|
||||
}
|
||||
|
||||
if (tok[0].kind == Token::END)
|
||||
if (tok[0].kind == END)
|
||||
return YamlError{"unterminated flow list", start - input.data()};
|
||||
|
||||
tok = tok.subspan(1);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "elf/mold.h"
|
||||
#include "macho/mold.h"
|
||||
#include "mold.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <signal.h>
|
||||
#include <tbb/global_control.h>
|
||||
|
||||
#ifdef USE_SYSTEM_MIMALLOC
|
||||
#include <mimalloc-new-delete.h>
|
||||
@@ -10,13 +10,19 @@
|
||||
|
||||
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
|
||||
namespace elf {
|
||||
int main(int argc, char **argv);
|
||||
}
|
||||
|
||||
namespace macho {
|
||||
int main(int argc, char **argv);
|
||||
}
|
||||
|
||||
static std::string get_mold_version() {
|
||||
if (mold_git_hash.empty())
|
||||
return "mold " MOLD_VERSION " (compatible with GNU ld)";
|
||||
return "mold " MOLD_VERSION " (" + mold_git_hash + "; compatible with GNU ld)";
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
if (output_tmpfile)
|
||||
@@ -25,23 +31,85 @@ void cleanup() {
|
||||
unlink(socket_tmpfile);
|
||||
}
|
||||
|
||||
static void signal_handler(int) {
|
||||
// mold mmap's an output file, and the mmap succeeds even if there's
|
||||
// no enough space left on the filesystem. The actual disk blocks are
|
||||
// not allocated on the mmap call but when the program writes to it
|
||||
// for the first time.
|
||||
//
|
||||
// If a disk becomes full as a result of a write to an mmap'ed memory
|
||||
// region, the failure of the write is reported as a SIGBUS or structured
|
||||
// exeption with code EXCEPTION_IN_PAGE_ERROR on Windows. This
|
||||
// signal handler catches that signal and prints out a user-friendly
|
||||
// error message. Without this, it is very hard to realize that the
|
||||
// disk might be full.
|
||||
#ifdef _WIN32
|
||||
|
||||
static LONG WINAPI vectored_handler(_EXCEPTION_POINTERS *exception_info) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock{mu};
|
||||
|
||||
PEXCEPTION_RECORD exception_record = exception_info->ExceptionRecord;
|
||||
ULONG_PTR *exception_information = exception_record->ExceptionInformation;
|
||||
if (exception_record->ExceptionCode == EXCEPTION_IN_PAGE_ERROR &&
|
||||
(ULONG_PTR)output_buffer_start <= exception_information[1] &&
|
||||
exception_information[1] < (ULONG_PTR)output_buffer_end) {
|
||||
|
||||
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
|
||||
(void)!write(_fileno(stderr), msg, sizeof(msg) - 1);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
void install_signal_handler() {
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
AddVectoredExceptionHandler(0, vectored_handler);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void sighandler(int signo, siginfo_t *info, void *ucontext) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock{mu};
|
||||
|
||||
if ((signo == SIGSEGV || signo == SIGBUS) &&
|
||||
output_buffer_start <= info->si_addr &&
|
||||
info->si_addr < output_buffer_end) {
|
||||
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
|
||||
(void)!write(STDERR_FILENO, msg, sizeof(msg) - 1);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
void install_signal_handler() {
|
||||
struct sigaction action;
|
||||
action.sa_sigaction = sighandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
|
||||
sigaction(SIGINT, &action, NULL);
|
||||
sigaction(SIGTERM, &action, NULL);
|
||||
sigaction(SIGBUS, &action, NULL);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
i64 get_default_thread_count() {
|
||||
// mold doesn't scale well above 32 threads.
|
||||
int n = tbb::global_control::active_value(
|
||||
tbb::global_control::max_allowed_parallelism);
|
||||
return std::min(n, 32);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
std::string cmd = mold::filepath(argv[0]).filename();
|
||||
mold::mold_version = mold::get_mold_version();
|
||||
|
||||
std::string cmd = mold::filepath(argv[0]).filename().string();
|
||||
if (cmd == "ld64" || cmd == "ld64.mold")
|
||||
return mold::macho::main(argc, argv);
|
||||
|
||||
return mold::elf::main(argc, argv);
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "big-endian.h"
|
||||
#include "inttypes.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <bitset>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
@@ -11,24 +13,48 @@
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#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()
|
||||
#ifdef _WIN32
|
||||
# include <io.h>
|
||||
#else
|
||||
# define unreachable() assert(0 && "unreachable")
|
||||
# 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 {
|
||||
|
||||
using namespace std::literals::string_literals;
|
||||
@@ -50,11 +76,16 @@ inline char *output_tmpfile;
|
||||
inline char *socket_tmpfile;
|
||||
inline thread_local bool opt_demangle;
|
||||
|
||||
extern const std::string mold_version;
|
||||
inline u8 *output_buffer_start = nullptr;
|
||||
inline u8 *output_buffer_end = nullptr;
|
||||
|
||||
inline std::string mold_version;
|
||||
extern std::string mold_git_hash;
|
||||
|
||||
std::string_view errno_string();
|
||||
void cleanup();
|
||||
void install_signal_handler();
|
||||
i64 get_default_thread_count();
|
||||
|
||||
//
|
||||
// Error output
|
||||
@@ -159,25 +190,43 @@ private:
|
||||
// Utility functions
|
||||
//
|
||||
|
||||
// Some C++ libraries haven't implemented std::has_single_bit yet.
|
||||
inline bool has_single_bit(u64 val) {
|
||||
return std::popcount(val) == 1;
|
||||
}
|
||||
|
||||
// Some C++ libraries haven't implemented std::bit_ceil yet.
|
||||
inline u64 bit_ceil(u64 val) {
|
||||
if (has_single_bit(val))
|
||||
return val;
|
||||
return (u64)1 << (64 - std::countl_zero(val));
|
||||
}
|
||||
|
||||
inline u64 align_to(u64 val, u64 align) {
|
||||
if (align == 0)
|
||||
return val;
|
||||
assert(std::popcount(align) == 1);
|
||||
assert(has_single_bit(align));
|
||||
return (val + align - 1) & ~(align - 1);
|
||||
}
|
||||
|
||||
inline u64 align_down(u64 val, u64 align) {
|
||||
assert(std::popcount(align) == 1);
|
||||
assert(has_single_bit(align));
|
||||
return val & ~(align - 1);
|
||||
}
|
||||
|
||||
inline u64 next_power_of_two(u64 val) {
|
||||
assert(val >> 63 == 0);
|
||||
if (val == 0 || val == 1)
|
||||
return 1;
|
||||
return (u64)1 << (64 - std::countl_zero(val - 1));
|
||||
inline u64 bit(u64 val, i64 pos) {
|
||||
return (val >> pos) & 1;
|
||||
};
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
inline u64 bits(u64 val, u64 hi, u64 lo) {
|
||||
return (val >> lo) & (((u64)1 << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
inline i64 sign_extend(u64 val, i64 size) {
|
||||
return (i64)(val << (63 - size)) >> (63 - size);
|
||||
};
|
||||
|
||||
template <typename T, typename Compare = std::less<T>>
|
||||
void update_minimum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
T old_val = atomic;
|
||||
@@ -215,14 +264,19 @@ inline void sort(T &vec, U less) {
|
||||
std::stable_sort(vec.begin(), vec.end(), less);
|
||||
}
|
||||
|
||||
inline i64 write_string(u8 *buf, std::string_view str) {
|
||||
template <typename T>
|
||||
inline void remove_duplicates(std::vector<T> &vec) {
|
||||
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
|
||||
}
|
||||
|
||||
inline i64 write_string(void *buf, std::string_view str) {
|
||||
memcpy(buf, str.data(), str.size());
|
||||
buf[str.size()] = '\0';
|
||||
*((u8 *)buf + str.size()) = '\0';
|
||||
return str.size() + 1;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline i64 write_vector(u8 *buf, const std::vector<T> &vec) {
|
||||
inline i64 write_vector(void *buf, const std::vector<T> &vec) {
|
||||
i64 sz = vec.size() * sizeof(T);
|
||||
memcpy(buf, vec.data(), sz);
|
||||
return sz;
|
||||
@@ -236,6 +290,20 @@ inline void encode_uleb(std::vector<u8> &vec, u64 val) {
|
||||
} 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 {
|
||||
@@ -258,13 +326,26 @@ inline u64 read_uleb(u8 *&buf) {
|
||||
return val;
|
||||
}
|
||||
|
||||
inline u64 read_uleb(u8 const*&buf) {
|
||||
return read_uleb(const_cast<u8 *&>(buf));
|
||||
}
|
||||
|
||||
inline u64 read_uleb(std::string_view &str) {
|
||||
u8 *start = (u8 *)&str[0];
|
||||
u8 *ptr = start;
|
||||
u64 val = read_uleb(ptr);
|
||||
str = str.substr(ptr - start);
|
||||
return val;
|
||||
}
|
||||
|
||||
inline i64 uleb_size(u64 val) {
|
||||
i64 i = 0;
|
||||
do {
|
||||
i++;
|
||||
val >>= 7;
|
||||
} while (val);
|
||||
return i;
|
||||
#if __GNUC__
|
||||
#pragma GCC unroll 8
|
||||
#endif
|
||||
for (int i = 1; i < 9; i++)
|
||||
if (val < ((u64)1 << (7 * i)))
|
||||
return i;
|
||||
return 9;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
@@ -276,6 +357,14 @@ std::string_view save_string(C &ctx, const std::string &str) {
|
||||
return {(char *)buf, str.size()};
|
||||
}
|
||||
|
||||
inline bool remove_prefix(std::string_view &s, std::string_view prefix) {
|
||||
if (s.starts_with(prefix)) {
|
||||
s = s.substr(prefix.size());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Concurrent Map
|
||||
//
|
||||
@@ -305,7 +394,7 @@ public:
|
||||
void resize(i64 nbuckets) {
|
||||
this->~ConcurrentMap();
|
||||
|
||||
nbuckets = std::max<i64>(MIN_NBUCKETS, next_power_of_two(nbuckets));
|
||||
nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
|
||||
|
||||
this->nbuckets = nbuckets;
|
||||
keys = (std::atomic<const char *> *)calloc(nbuckets, sizeof(keys[0]));
|
||||
@@ -317,7 +406,7 @@ public:
|
||||
if (!keys)
|
||||
return {nullptr, false};
|
||||
|
||||
assert(std::popcount<u64>(nbuckets) == 1);
|
||||
assert(has_single_bit(nbuckets));
|
||||
i64 idx = hash & (nbuckets - 1);
|
||||
i64 retry = 0;
|
||||
|
||||
@@ -376,10 +465,28 @@ private:
|
||||
};
|
||||
|
||||
//
|
||||
// threads.cc
|
||||
// output-file.h
|
||||
//
|
||||
|
||||
void set_thread_count(i64 n);
|
||||
template <typename C>
|
||||
class OutputFile {
|
||||
public:
|
||||
static std::unique_ptr<OutputFile<C>>
|
||||
open(C &ctx, std::string path, i64 filesize, i64 perm);
|
||||
|
||||
virtual void close(C &ctx) = 0;
|
||||
virtual ~OutputFile() = default;
|
||||
|
||||
u8 *buf = nullptr;
|
||||
std::string path;
|
||||
i64 filesize;
|
||||
bool is_mmapped;
|
||||
bool is_unmapped = false;
|
||||
|
||||
protected:
|
||||
OutputFile(std::string path, i64 filesize, bool is_mmapped)
|
||||
: path(path), filesize(filesize), is_mmapped(is_mmapped) {}
|
||||
};
|
||||
|
||||
//
|
||||
// hyperloglog.cc
|
||||
@@ -407,6 +514,66 @@ private:
|
||||
std::vector<std::atomic_uint8_t> buckets;
|
||||
};
|
||||
|
||||
//
|
||||
// glob.cc
|
||||
//
|
||||
|
||||
class Glob {
|
||||
typedef enum { STRING, STAR, QUESTION, BRACKET } Kind;
|
||||
|
||||
struct Element {
|
||||
Element(Kind k) : kind(k) {}
|
||||
Kind kind;
|
||||
std::string str;
|
||||
std::bitset<256> bitset;
|
||||
};
|
||||
|
||||
public:
|
||||
static std::optional<Glob> compile(std::string_view pat);
|
||||
bool match(std::string_view str);
|
||||
|
||||
private:
|
||||
Glob(std::vector<Element> &&vec) : elements(vec) {}
|
||||
static bool do_match(std::string_view str, std::span<Element> elements);
|
||||
|
||||
std::vector<Element> elements;
|
||||
};
|
||||
|
||||
//
|
||||
// multi-glob.cc
|
||||
//
|
||||
|
||||
class MultiGlob {
|
||||
public:
|
||||
bool add(std::string_view pat, u32 val);
|
||||
bool empty() const { return strings.empty(); }
|
||||
std::optional<u32> find(std::string_view str);
|
||||
|
||||
private:
|
||||
struct TrieNode {
|
||||
u32 value = -1;
|
||||
TrieNode *suffix_link = nullptr;
|
||||
std::unique_ptr<TrieNode> children[256];
|
||||
};
|
||||
|
||||
void compile();
|
||||
void fix_suffix_links(TrieNode &node);
|
||||
void fix_values();
|
||||
|
||||
std::vector<std::string> strings;
|
||||
std::unique_ptr<TrieNode> root;
|
||||
std::vector<std::pair<Glob, u32>> globs;
|
||||
std::vector<u32> values;
|
||||
std::once_flag once;
|
||||
bool is_compiled = false;
|
||||
};
|
||||
|
||||
//
|
||||
// uuid.cc
|
||||
//
|
||||
|
||||
std::array<u8, 16> get_uuid_v4();
|
||||
|
||||
//
|
||||
// filepath.cc
|
||||
//
|
||||
@@ -425,6 +592,7 @@ std::filesystem::path to_abs_path(std::filesystem::path path);
|
||||
//
|
||||
|
||||
std::string_view demangle(std::string_view name);
|
||||
std::optional<std::string_view> cpp_demangle(std::string_view name);
|
||||
|
||||
//
|
||||
// compress.cc
|
||||
@@ -432,7 +600,7 @@ std::string_view demangle(std::string_view name);
|
||||
|
||||
class ZlibCompressor {
|
||||
public:
|
||||
ZlibCompressor(std::string_view input);
|
||||
ZlibCompressor(u8 *buf, i64 size);
|
||||
void write_to(u8 *buf);
|
||||
i64 size() const;
|
||||
|
||||
@@ -590,6 +758,12 @@ public:
|
||||
// because archive members may have the same name.
|
||||
return parent->name + ":" + std::to_string(get_offset());
|
||||
}
|
||||
|
||||
if (thin_parent) {
|
||||
// If this is a thin archive member, the filename part is
|
||||
// guaranteed to be unique.
|
||||
return thin_parent->name + ":" + name;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -599,7 +773,11 @@ public:
|
||||
i64 mtime = 0;
|
||||
bool given_fullpath = true;
|
||||
MappedFile *parent = nullptr;
|
||||
MappedFile *thin_parent = nullptr;
|
||||
int fd = -1;
|
||||
#ifdef _WIN32
|
||||
HANDLE file_handle = INVALID_HANDLE_VALUE;
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
@@ -607,7 +785,14 @@ 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);
|
||||
|
||||
i64 fd;
|
||||
#ifdef _WIN32
|
||||
fd = ::_open(path.c_str(), O_RDONLY);
|
||||
#else
|
||||
fd = ::open(path.c_str(), O_RDONLY);
|
||||
#endif
|
||||
|
||||
if (fd == -1)
|
||||
return nullptr;
|
||||
|
||||
@@ -621,17 +806,32 @@ MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
|
||||
mf->name = path;
|
||||
mf->size = st.st_size;
|
||||
|
||||
#ifdef __APPLE__
|
||||
mf->mtime = (u64)st.st_mtimespec.tv_sec * 1000000000 + st.st_mtimespec.tv_nsec;
|
||||
#ifdef _WIN32
|
||||
mf->mtime = st.st_mtime;
|
||||
#elif defined(__APPLE__)
|
||||
mf->mtime = (u64)st.st_mtimespec.tv_sec * 1000000000 + st.st_mtimespec.tv_nsec;
|
||||
#else
|
||||
mf->mtime = (u64)st.st_mtim.tv_sec * 1000000000 + st.st_mtim.tv_nsec;
|
||||
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);
|
||||
#ifdef _WIN32
|
||||
HANDLE handle = CreateFileMapping((HANDLE)_get_osfhandle(fd),
|
||||
nullptr, PAGE_READWRITE, 0,
|
||||
st.st_size, nullptr);
|
||||
if (!handle)
|
||||
Fatal(ctx) << path << ": CreateFileMapping failed: " << GetLastError();
|
||||
mf->file_handle = handle;
|
||||
mf->data = (u8 *)MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, st.st_size);
|
||||
if (!mf->data)
|
||||
Fatal(ctx) << path << ": MapViewOfFile failed: " << GetLastError();
|
||||
#else
|
||||
mf->data = (u8 *)mmap(nullptr, st.st_size, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE, fd, 0);
|
||||
if (mf->data == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return mf;
|
||||
@@ -659,8 +859,16 @@ MappedFile<C>::slice(C &ctx, std::string name, u64 start, u64 size) {
|
||||
|
||||
template <typename C>
|
||||
MappedFile<C>::~MappedFile() {
|
||||
if (size && !parent)
|
||||
munmap(data, size);
|
||||
if (size == 0 || parent)
|
||||
return;
|
||||
|
||||
#ifdef _WIN32
|
||||
UnmapViewOfFile(data);
|
||||
if (file_handle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(file_handle);
|
||||
#else
|
||||
munmap(data, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,34 +1,34 @@
|
||||
// This file implements the Aho-Corasick algorithm to match version
|
||||
// script patterns to symbol strings as quickly as possible.
|
||||
// This file implements the Aho-Corasick algorithm to match multiple
|
||||
// glob patterns to symbol strings as quickly as possible.
|
||||
//
|
||||
// Here are some examples of version script patterns:
|
||||
// Here are some examples of glob patterns:
|
||||
//
|
||||
// qt_private_api_tag*
|
||||
// *16QAccessibleCache*
|
||||
// *32QAbstractFileIconProviderPrivate*
|
||||
// *17QPixmapIconEngine*
|
||||
//
|
||||
// The pattern is a glob pattern, so `*` is a wildcard that matches
|
||||
// any substring. We sometimes have hundreds of version script
|
||||
// patterns and have to match them against millions of symbol strings.
|
||||
// `*` is a wildcard that matches any substring. We sometimes have
|
||||
// hundreds of glob patterns and have to match them against millions
|
||||
// of symbol strings.
|
||||
//
|
||||
// Aho-Corasick cannot handle complex patterns such as `*foo*bar*`.
|
||||
// We handle such patterns with GlobPattern. GlobPattern is relatively
|
||||
// slow, but complex patterns are rare in practice, so it should be OK.
|
||||
// We handle such patterns with the Glob class. Glob is relatively
|
||||
// slow, but complex patterns are rare in practice, so it should be
|
||||
// OK.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
std::optional<u16> VersionMatcher::find(std::string_view str) {
|
||||
std::optional<u32> MultiGlob::find(std::string_view str) {
|
||||
std::call_once(once, [&] { compile(); });
|
||||
u32 idx = UINT32_MAX;
|
||||
|
||||
if (root) {
|
||||
std::call_once(once_flag, [&] { compile(); });
|
||||
|
||||
// Match against simple glob patterns
|
||||
TrieNode *node = root.get();
|
||||
|
||||
@@ -53,13 +53,13 @@ std::optional<u16> VersionMatcher::find(std::string_view str) {
|
||||
}
|
||||
|
||||
// Match against complex glob patterns
|
||||
for (std::pair<GlobPattern, u32> &glob : globs)
|
||||
for (std::pair<Glob, u32> &glob : globs)
|
||||
if (glob.first.match(str))
|
||||
idx = std::min(idx, glob.second);
|
||||
|
||||
if (idx == UINT32_MAX)
|
||||
return {};
|
||||
return versions[idx];
|
||||
return values[idx];
|
||||
}
|
||||
|
||||
static bool is_simple_pattern(std::string_view pat) {
|
||||
@@ -82,17 +82,17 @@ static std::string handle_stars(std::string_view pat) {
|
||||
return "\0"s + str + "\0"s;
|
||||
}
|
||||
|
||||
bool VersionMatcher::add(std::string_view pat, u16 ver) {
|
||||
assert(!compiled);
|
||||
bool MultiGlob::add(std::string_view pat, u32 val) {
|
||||
assert(!is_compiled);
|
||||
assert(!pat.empty());
|
||||
|
||||
u32 idx = strings.size();
|
||||
strings.push_back(std::string(pat));
|
||||
versions.push_back(ver);
|
||||
values.push_back(val);
|
||||
|
||||
// Complex glob pattern
|
||||
if (!is_simple_pattern(pat)) {
|
||||
if (std::optional<GlobPattern> glob = GlobPattern::compile(pat)) {
|
||||
if (std::optional<Glob> glob = Glob::compile(pat)) {
|
||||
globs.push_back({std::move(*glob), idx});
|
||||
return true;
|
||||
}
|
||||
@@ -114,13 +114,15 @@ bool VersionMatcher::add(std::string_view pat, u16 ver) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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 +148,7 @@ void VersionMatcher::fix_suffix_links(TrieNode &node) {
|
||||
}
|
||||
}
|
||||
|
||||
void VersionMatcher::fix_values() {
|
||||
void MultiGlob::fix_values() {
|
||||
std::queue<TrieNode *> queue;
|
||||
queue.push(root.get());
|
||||
|
||||
@@ -163,4 +165,4 @@ void VersionMatcher::fix_values() {
|
||||
} while (!queue.empty());
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -1,11 +1,12 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
inline u32 get_umask() {
|
||||
u32 orig_umask = umask(0);
|
||||
@@ -23,8 +24,10 @@ open_or_create_file(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << path2 << ": " << errno_string();
|
||||
|
||||
// Reuse an existing file if exists and writable.
|
||||
if (rename(path.c_str(), path2) == 0) {
|
||||
// Reuse an existing file if exists and writable because on Linux,
|
||||
// writing to an existing file is much faster than creating a fresh
|
||||
// file and writing to it.
|
||||
if (ctx.overwrite_output_file && rename(path.c_str(), path2) == 0) {
|
||||
::close(fd);
|
||||
fd = ::open(path2, O_RDWR | O_CREAT, perm);
|
||||
if (fd != -1 && !ftruncate(fd, filesize) && !fchmod(fd, perm & ~get_umask()))
|
||||
@@ -44,11 +47,11 @@ open_or_create_file(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
return {fd, path2};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
class MemoryMappedOutputFile : public OutputFile<E> {
|
||||
template <typename C>
|
||||
class MemoryMappedOutputFile : public OutputFile<C> {
|
||||
public:
|
||||
MemoryMappedOutputFile(Context<E> &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<E>(path, filesize, true) {
|
||||
MemoryMappedOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<C>(path, filesize, true) {
|
||||
i64 fd;
|
||||
std::tie(fd, output_tmpfile) = open_or_create_file(ctx, path, filesize, perm);
|
||||
|
||||
@@ -57,9 +60,12 @@ public:
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
::close(fd);
|
||||
|
||||
mold::output_buffer_start = this->buf;
|
||||
mold::output_buffer_end = this->buf + filesize;
|
||||
}
|
||||
|
||||
void close(Context<E> &ctx) override {
|
||||
void close(C &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (!this->is_unmapped)
|
||||
@@ -71,18 +77,18 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class MallocOutputFile : public OutputFile<E> {
|
||||
template <typename C>
|
||||
class MallocOutputFile : public OutputFile<C> {
|
||||
public:
|
||||
MallocOutputFile(Context<E> &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<E>(path, filesize, false), perm(perm) {
|
||||
MallocOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<C>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)mmap(NULL, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << "mmap failed: " << errno_string();
|
||||
}
|
||||
|
||||
void close(Context<E> &ctx) override {
|
||||
void close(C &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (this->path == "-") {
|
||||
@@ -104,9 +110,9 @@ private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::unique_ptr<OutputFile<E>>
|
||||
OutputFile<E>::open(Context<E> &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
template <typename C>
|
||||
std::unique_ptr<OutputFile<C>>
|
||||
OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
Timer t(ctx, "open_file");
|
||||
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
@@ -121,24 +127,15 @@ OutputFile<E>::open(Context<E> &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
is_special = true;
|
||||
}
|
||||
|
||||
std::unique_ptr<OutputFile<E>> file;
|
||||
OutputFile<C> *file;
|
||||
if (is_special)
|
||||
file = std::make_unique<MallocOutputFile<E>>(ctx, path, filesize, perm);
|
||||
file = new MallocOutputFile<C>(ctx, path, filesize, perm);
|
||||
else
|
||||
file = std::make_unique<MemoryMappedOutputFile<E>>(ctx, path, filesize, perm);
|
||||
file = new MemoryMappedOutputFile<C>(ctx, path, filesize, perm);
|
||||
|
||||
if (ctx.arg.filler != -1)
|
||||
memset(file->buf, ctx.arg.filler, filesize);
|
||||
return file;
|
||||
return std::unique_ptr<OutputFile<C>>(file);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class OutputFile<E>;
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,57 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <windows.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename C>
|
||||
class MallocOutputFile : public OutputFile<C> {
|
||||
public:
|
||||
MallocOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<C>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)malloc(filesize);
|
||||
if (!this->buf)
|
||||
Fatal(ctx) << "malloc failed";
|
||||
}
|
||||
|
||||
void close(C &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (this->path == "-") {
|
||||
fwrite(this->buf, this->filesize, 1, stdout);
|
||||
fclose(stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
|
||||
FILE *fp = fdopen(fd, "w");
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
fclose(fp);
|
||||
free(this->buf);
|
||||
}
|
||||
|
||||
private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
std::unique_ptr<OutputFile<C>>
|
||||
OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
Timer t(ctx, "open_file");
|
||||
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
OutputFile<C> *file = new MallocOutputFile<C>(ctx, path, filesize, perm);
|
||||
|
||||
if (ctx.arg.filler != -1)
|
||||
memset(file->buf, ctx.arg.filler, filesize);
|
||||
return std::unique_ptr<OutputFile<C>>(file);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,5 @@
|
||||
#if _WIN32
|
||||
# include "output-file-win32.h"
|
||||
#else
|
||||
# include "output-file-unix.h"
|
||||
#endif
|
||||
@@ -3,8 +3,11 @@
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <ios>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/resource.h>
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -23,24 +26,39 @@ void Counter::print() {
|
||||
}
|
||||
|
||||
static i64 now_nsec() {
|
||||
#ifdef _WIN32
|
||||
return (i64)std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
#else
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
return (i64)t.tv_sec * 1000000000 + t.tv_nsec;
|
||||
#endif
|
||||
}
|
||||
|
||||
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 * 1000000000 + t.tv_usec * 1000;
|
||||
};
|
||||
|
||||
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,12 +68,13 @@ 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) {
|
||||
|
||||
@@ -1,3 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
typedef uint8_t u8;
|
||||
static constexpr int64_t SHA256_SIZE = 32;
|
||||
|
||||
#ifdef _WIN32
|
||||
// On Windows, we use Microsoft CNG.
|
||||
|
||||
#include <Windows.h>
|
||||
#include <bcrypt.h>
|
||||
#include <ntstatus.h>
|
||||
|
||||
inline static BCRYPT_ALG_HANDLE get_sha256_handle() {
|
||||
static std::once_flag once;
|
||||
static BCRYPT_ALG_HANDLE alg;
|
||||
|
||||
std::call_once(once, [&]() {
|
||||
BCryptOpenAlgorithmProvider(&alg, BCRYPT_SHA256_ALGORITHM, nullptr, 0);
|
||||
});
|
||||
return alg;
|
||||
}
|
||||
|
||||
inline void sha256_hash(u8 *in, size_t len, u8 *out) {
|
||||
BCryptHash(get_sha256_handle(), nullptr, 0, in, len, out, SHA256_SIZE);
|
||||
}
|
||||
|
||||
class SHA256Hash {
|
||||
public:
|
||||
SHA256Hash() {
|
||||
BCryptCreateHash(get_sha256_handle(), &handle, nullptr, 0, nullptr, 0, 0);
|
||||
}
|
||||
|
||||
void update(u8 *data, size_t len) {
|
||||
BCryptHashData(handle, data, len, 0);
|
||||
}
|
||||
|
||||
void finish(u8 *out) {
|
||||
BCryptFinishHash(handle, out, SHA256_SIZE, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
BCRYPT_HASH_HANDLE handle;
|
||||
};
|
||||
|
||||
#else
|
||||
// On Unix, we use OpenSSL or the Apple's OpenSSL-compatible API.
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define COMMON_DIGEST_FOR_OPENSSL
|
||||
# include <CommonCrypto/CommonDigest.h>
|
||||
@@ -6,3 +55,27 @@
|
||||
# define OPENSSL_SUPPRESS_DEPRECATED 1
|
||||
# include <openssl/sha.h>
|
||||
#endif
|
||||
|
||||
inline void sha256_hash(u8 *in, size_t len, u8 *out) {
|
||||
SHA256(in, len, out);
|
||||
}
|
||||
|
||||
class SHA256Hash {
|
||||
public:
|
||||
SHA256Hash() {
|
||||
SHA256_Init(&ctx);
|
||||
}
|
||||
|
||||
void update(u8 *data, size_t len) {
|
||||
SHA256_Update(&ctx, data, len);
|
||||
}
|
||||
|
||||
void finish(u8 *out) {
|
||||
SHA256_Final(out, &ctx);
|
||||
}
|
||||
|
||||
private:
|
||||
SHA256_CTX ctx;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -13,7 +13,11 @@ namespace mold {
|
||||
|
||||
std::string_view errno_string() {
|
||||
static thread_local char buf[200];
|
||||
#ifdef _WIN32
|
||||
strerror_s(buf, errno);
|
||||
#else
|
||||
strerror_r(errno, buf, sizeof(buf));
|
||||
#endif
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
TESTS = $(wildcard macho/*.sh)
|
||||
|
||||
test: $(TESTS)
|
||||
|
||||
# macOS's GNU make hasn't been updated since 3.8.1 perhaps due a concern
|
||||
# of GPLv3. The --output-sync flag was introduced in GNU Make 4.0, so we
|
||||
# can't use that flag on macOS.
|
||||
#
|
||||
# `tail -r | tail -r` is a poor-man's way to enable full buffering on a
|
||||
# command output. `tail -r` outputs an input from the last line to the
|
||||
# first.
|
||||
$(TESTS):
|
||||
@./$@ 2>&1 | tail -r | tail -r
|
||||
|
||||
.PHONY: test $(TESTS)
|
||||
@@ -1,8 +0,0 @@
|
||||
TESTS = $(wildcard elf/*.sh)
|
||||
|
||||
test: $(TESTS)
|
||||
|
||||
$(TESTS):
|
||||
@./$@
|
||||
|
||||
.PHONY: test $(TESTS)
|
||||
@@ -0,0 +1,43 @@
|
||||
option(MOLD_ENABLE_QEMU_TESTS "Enable tests on non-native targets" OFF)
|
||||
option(MOLD_ENABLE_QEMU_TESTS_RV32 "Enable tests on RV32" OFF)
|
||||
|
||||
file(GLOB TESTS
|
||||
RELATIVE ${CMAKE_CURRENT_LIST_DIR}
|
||||
CONFIGURE_DEPENDS
|
||||
"*.sh")
|
||||
|
||||
function(add_qemu_test TEST TRIPLE MACHINE)
|
||||
if(NOT ${CMAKE_HOST_SYSTEM_PROCESSOR} STREQUAL ${MACHINE})
|
||||
add_test(NAME "${MACHINE}-${TEST}"
|
||||
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
|
||||
WORKING_DIRECTORY ${mold_BINARY_DIR})
|
||||
|
||||
set(ENV
|
||||
TEST_CC=${TRIPLE}-gcc
|
||||
TEST_CXX=${TRIPLE}-g++
|
||||
TEST_GCC=${TRIPLE}-gcc
|
||||
TEST_GXX=${TRIPLE}-g++
|
||||
OBJDUMP=${TRIPLE}-objdump
|
||||
MACHINE=${MACHINE}
|
||||
"QEMU=qemu-${MACHINE} -L /usr/${TRIPLE}")
|
||||
set_tests_properties("${MACHINE}-${TEST}" PROPERTIES ENVIRONMENT "${ENV}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
foreach(TEST IN LISTS TESTS)
|
||||
add_test(NAME "${CMAKE_HOST_SYSTEM_PROCESSOR}-${TEST}"
|
||||
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
|
||||
WORKING_DIRECTORY ${mold_BINARY_DIR})
|
||||
|
||||
if(MOLD_ENABLE_QEMU_TESTS)
|
||||
add_qemu_test(${TEST} x86_64-linux-gnu x86_64)
|
||||
add_qemu_test(${TEST} i686-linux-gnu i386)
|
||||
add_qemu_test(${TEST} aarch64-linux-gnu aarch64)
|
||||
add_qemu_test(${TEST} arm-linux-gnueabihf arm)
|
||||
add_qemu_test(${TEST} riscv64-linux-gnu riscv64)
|
||||
endif()
|
||||
|
||||
if (MOLD_ENABLE_QEMU_TESTS_RV32)
|
||||
add_qemu_test(${TEST} riscv32-linux-gnu riscv32)
|
||||
endif()
|
||||
endforeach()
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
[ $MACHINE = aarch64 ] && { echo skipped; exit; }
|
||||
|
||||
cat <<EOF | $CC -fPIC -c -o $t/a.o -xassembler -
|
||||
.globl foo
|
||||
foo = 3;
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -fno-PIC -c -o $t/b.o -xc -
|
||||
#include <stdio.h>
|
||||
extern char foo;
|
||||
int main() { printf("foo=%p\n", &foo); }
|
||||
EOF
|
||||
|
||||
! $CC -B. -o $t/exe -pie $t/a.o $t/b.o -Wl,-z,text >& $t/log
|
||||
grep -q 'recompile with -fPIC' $t/log
|
||||
|
||||
echo OK
|
||||
@@ -1,17 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
[ $MACHINE = x86_64 ] || { echo skipped; exit; }
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
echo 'int main() { return 0; }' > $t/a.c
|
||||
echo 'int main() { return 0; }' > $t/b.c
|
||||
echo 'int main() { return 0; }' | $CC -c -o $t/a.o -xc -
|
||||
echo 'int main() { return 1; }' | $CC -c -o $t/b.o -xc -
|
||||
|
||||
! $CC -B. -o $t/exe $t/a.c $t/b.c 2> /dev/null || false
|
||||
$CC -B. -o $t/exe $t/a.c $t/b.c -Wl,-allow-multiple-definition
|
||||
$CC -B. -o $t/exe $t/a.c $t/b.c -Wl,-z,muldefs
|
||||
! $CC -B. -o $t/exe $t/a.o $t/b.o 2> /dev/null || false
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-allow-multiple-definition
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-z,muldefs
|
||||
|
||||
echo OK
|
||||
@@ -1,17 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
[[ $MACHINE == arm* ]] || { echo skipped; exit; }
|
||||
|
||||
echo 'int foo() { return 0; }' | $CC -o /dev/null -c -xc - -mthumb 2> /dev/null || { echo skipped; exit; }
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -mthumb
|
||||
#include <stdio.h>
|
||||
int bar();
|
||||
int foo() {
|
||||
printf(" foo");
|
||||
bar();
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc - -marm
|
||||
#include <stdio.h>
|
||||
int bar() {
|
||||
printf(" bar\n");
|
||||
}
|
||||
|
||||
int foo();
|
||||
|
||||
int main() {
|
||||
printf("main");
|
||||
foo();
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o
|
||||
$QEMU $t/exe | grep -q 'main foo bar'
|
||||
|
||||
echo OK
|
||||
@@ -1,17 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CC -fPIC -o $t/a.o -c -xc -
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CC -shared -fPIC -o $t/a.so -xc -
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -x assembler -
|
||||
@@ -21,7 +19,7 @@ _start:
|
||||
nop
|
||||
EOF
|
||||
|
||||
"$mold" -o $t/b.so $t/a.o -auxiliary foo -f bar -shared
|
||||
./mold -o $t/b.so $t/a.o -auxiliary foo -f bar -shared
|
||||
|
||||
readelf --dynamic $t/b.so > $t/log
|
||||
fgrep -q 'Auxiliary library: [foo]' $t/log
|
||||
|
||||
+6
-8
@@ -1,24 +1,22 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
[ $MACHINE = x86_64 ] || { echo skipped; exit; }
|
||||
|
||||
echo '.globl _start; _start: jmp loop' | $CC -o $t/a.o -c -x assembler -
|
||||
echo '.globl loop; loop: jmp loop' | $CC -o $t/b.o -c -x assembler -
|
||||
"$mold" -static -o $t/exe $t/a.o $t/b.o
|
||||
./mold -static -o $t/exe $t/a.o $t/b.o
|
||||
$OBJDUMP -d $t/exe > /dev/null
|
||||
file $t/exe | grep -q ELF
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o$t/a.o -xc -
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -fPIC -xc -
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o$t/a.o -xc -
|
||||
|
||||
+6
-8
@@ -1,17 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
# Verify that mold does not crash if no object file is included
|
||||
@@ -26,6 +24,6 @@ EOF
|
||||
rm -f $t/a.a
|
||||
ar rcs $t/a.a $t/a.o
|
||||
|
||||
"$mold" -o $t/exe $t/a.a
|
||||
./mold -o $t/exe $t/a.a
|
||||
|
||||
echo OK
|
||||
@@ -1,17 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
echo 'int main() { return 0; }' > $t/a.c
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CC -o $t/a.so -fPIC -shared -xc -
|
||||
|
||||
+9
-11
@@ -1,22 +1,20 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
(! "$mold" -zfoo) 2>&1 | grep -q 'unknown command line option: -zfoo'
|
||||
(! "$mold" -z foo) 2>&1 | grep -q 'unknown command line option: -z foo'
|
||||
(! "$mold" -abcdefg) 2>&1 | grep -q 'unknown command line option: -abcdefg'
|
||||
(! "$mold" --abcdefg) 2>&1 | grep -q 'unknown command line option: --abcdefg'
|
||||
{ ./mold -zfoo || true; } 2>&1 | grep -q 'unknown command line option: -zfoo'
|
||||
{ ./mold -z foo || true; } 2>&1 | grep -q 'unknown command line option: -z foo'
|
||||
{ ./mold -abcdefg || true; } 2>&1 | grep -q 'unknown command line option: -abcdefg'
|
||||
{ ./mold --abcdefg || true; } 2>&1 | grep -q 'unknown command line option: --abcdefg'
|
||||
|
||||
echo OK
|
||||
@@ -1,17 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
@@ -19,16 +17,16 @@ int foo();
|
||||
int main() { foo(); }
|
||||
EOF
|
||||
|
||||
! "$mold" -o $t/exe $t/a.o --color-diagnostics 2> $t/log
|
||||
grep -q $'\033' $t/log
|
||||
|
||||
! "$mold" -o $t/exe $t/a.o --color-diagnostics=always 2> $t/log
|
||||
grep -q $'\033' $t/log
|
||||
|
||||
! "$mold" -o $t/exe $t/a.o --color-diagnostics=never 2> $t/log
|
||||
! ./mold -o $t/exe $t/a.o --color-diagnostics 2> $t/log
|
||||
! grep -q $'\033' $t/log || false
|
||||
|
||||
! "$mold" -o $t/exe $t/a.o --color-diagnostics=auto 2> $t/log
|
||||
! ./mold -o $t/exe $t/a.o --color-diagnostics=always 2> $t/log
|
||||
grep -q $'\033' $t/log
|
||||
|
||||
! ./mold -o $t/exe $t/a.o --color-diagnostics=never 2> $t/log
|
||||
! grep -q $'\033' $t/log || false
|
||||
|
||||
! ./mold -o $t/exe $t/a.o --color-diagnostics=auto 2> $t/log
|
||||
! grep -q $'\033' $t/log || false
|
||||
|
||||
echo OK
|
||||
+5
-7
@@ -1,17 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -xc -
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | $CC -fcommon -xc -c -o $t/a.o -
|
||||
|
||||
Loaded 100 of 740 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user