mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 04:49:33 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f86868cb38 | ||
|
|
421289445a | ||
|
|
100922b650 | ||
|
|
0daf6233fb | ||
|
|
d0e4eee204 | ||
|
|
a7475dd5e3 | ||
|
|
c4af13889d | ||
|
|
7aaa4afce7 | ||
|
|
2fb96ad83f | ||
|
|
a2fa4e5ecc | ||
|
|
68877ab6ca | ||
|
|
f62777e606 | ||
|
|
c80e316649 | ||
|
|
d1b9549817 | ||
|
|
01d2dd8dd9 | ||
|
|
6109154a07 | ||
|
|
49d4dac7e6 | ||
|
|
87a5f238dd | ||
|
|
e448e0eb2b | ||
|
|
4df5d6497a | ||
|
|
e90ad9be16 | ||
|
|
b5788e0caa | ||
|
|
9178e43681 | ||
|
|
5feab826de | ||
|
|
2fc0231348 | ||
|
|
804ae412ed | ||
|
|
f0a030cbcd | ||
|
|
ba6347996b | ||
|
|
a151f4180f | ||
|
|
2c947ce2a9 | ||
|
|
63b7102a4c | ||
|
|
731dd0ed25 | ||
|
|
3105195364 | ||
|
|
23045c6f95 | ||
|
|
2d29f234e2 | ||
|
|
4edaf82691 | ||
|
|
49aa10318c | ||
|
|
2c03ed5673 | ||
|
|
e0bc74ad8a | ||
|
|
3768ca612e | ||
|
|
86d59c29eb | ||
|
|
96a4a1b635 | ||
|
|
006b13aec1 | ||
|
|
91947405fe | ||
|
|
b7123bded5 | ||
|
|
0884f279e0 | ||
|
|
615a24d1dd | ||
|
|
e1e4e9fd9e | ||
|
|
ceed69ff49 | ||
|
|
d90f7e162b | ||
|
|
340528bdc3 | ||
|
|
392781a0e2 | ||
|
|
4e10ee7e50 | ||
|
|
8d94fd6ca3 | ||
|
|
358f244247 | ||
|
|
7a1cbb9b34 | ||
|
|
e6d4154688 | ||
|
|
60eae9ed20 | ||
|
|
7fb1b1b9ec | ||
|
|
0ecd72900e | ||
|
|
9eb4e76daf | ||
|
|
b6876b5640 | ||
|
|
f4753b34bf | ||
|
|
8196199cac | ||
|
|
2a3ee11b7d | ||
|
|
68f2a26987 | ||
|
|
227a6c8a88 | ||
|
|
fe617c3618 | ||
|
|
e3a7c9e017 | ||
|
|
5ee6f89544 | ||
|
|
40c2ad7839 | ||
|
|
5edd07efd9 | ||
|
|
8e5def28ce | ||
|
|
b534d5af27 | ||
|
|
673f531adb | ||
|
|
66d3de438c | ||
|
|
d9ea875677 | ||
|
|
577361478a | ||
|
|
408e2bfee8 | ||
|
|
9ac23730b7 | ||
|
|
bf26753bbd | ||
|
|
8e3cee958b | ||
|
|
05f4a43c8f | ||
|
|
ca227fb8da | ||
|
|
78e4b06cb8 | ||
|
|
94cace7fc1 | ||
|
|
812c59fd5c | ||
|
|
8770bd775b | ||
|
|
89d43f2673 | ||
|
|
632a2ec1b4 | ||
|
|
6af78bf17b | ||
|
|
9360267cc3 | ||
|
|
66b995b5a9 | ||
|
|
94e2d7d6dc | ||
|
|
759aa95618 | ||
|
|
575a604d5d | ||
|
|
ac9a1c4048 | ||
|
|
d5cf590498 | ||
|
|
18656091f6 | ||
|
|
ead32811b3 | ||
|
|
7cdfa31e6c | ||
|
|
a6e4676a71 | ||
|
|
0c88d323c8 | ||
|
|
326150a182 | ||
|
|
339816702d | ||
|
|
146ddd711c | ||
|
|
32d1006e29 | ||
|
|
66a057cce7 | ||
|
|
619ff869e4 | ||
|
|
e7c071ea36 | ||
|
|
3d68824dea | ||
|
|
c98d276916 | ||
|
|
446c461201 | ||
|
|
98edd441ce | ||
|
|
37f6ef2081 | ||
|
|
41cf6d7814 | ||
|
|
bb5a596bc5 | ||
|
|
1b6f301644 | ||
|
|
8a089fee45 | ||
|
|
82e80721ac | ||
|
|
d3dea2f435 | ||
|
|
a20e3b8004 | ||
|
|
c1af9c97e7 | ||
|
|
3999aa8b18 | ||
|
|
b4d9db9db8 | ||
|
|
95c7871c6e | ||
|
|
3f7bffe93c | ||
|
|
ec473c74d7 | ||
|
|
9e11b40a99 | ||
|
|
a4fd7e3819 | ||
|
|
aae3dc3c1e | ||
|
|
ba7967152d | ||
|
|
d6dae3be6d | ||
|
|
83aaa63a49 | ||
|
|
b0d91b9967 | ||
|
|
4ea052591a | ||
|
|
2f03e46bb3 | ||
|
|
6205a1edc3 | ||
|
|
4dea134949 | ||
|
|
eb79859ea2 | ||
|
|
8714b78d48 | ||
|
|
86b142e555 | ||
|
|
e5549c73f0 | ||
|
|
4dae896726 | ||
|
|
83e05dae28 | ||
|
|
99a0aaf133 | ||
|
|
7068c0cd0f | ||
|
|
f49e4024e9 | ||
|
|
567a78dbd5 | ||
|
|
f2d27d8d24 | ||
|
|
dc9573989c | ||
|
|
e469b04f0d | ||
|
|
32e158d340 | ||
|
|
18b6f96756 | ||
|
|
2b7dbb01c4 | ||
|
|
c1c9bdf363 | ||
|
|
d4bbf31db2 | ||
|
|
9b5d404d1e | ||
|
|
4caadefac6 | ||
|
|
8f21cc386e | ||
|
|
f7de9571f8 | ||
|
|
e413433752 | ||
|
|
e67f460bfd | ||
|
|
e281ed88bb | ||
|
|
41986279de | ||
|
|
480efdedf0 | ||
|
|
99e3f959fc | ||
|
|
4ec0bbf048 | ||
|
|
f4c0e321db | ||
|
|
31a5cdf86f | ||
|
|
a44de89991 | ||
|
|
30683645c2 | ||
|
|
ac3ae60184 | ||
|
|
9f34830882 | ||
|
|
4aa4bfa4a6 | ||
|
|
fb6967c0cf | ||
|
|
385c1c494d | ||
|
|
4667be6134 | ||
|
|
3f9868238b | ||
|
|
3e8c519d4e | ||
|
|
aa27d674c1 | ||
|
|
3cbc57f9af | ||
|
|
a1e6d8e8df | ||
|
|
a054bcd4ff | ||
|
|
e47c67727c | ||
|
|
16bdf4009a | ||
|
|
c173f9c0ab | ||
|
|
89612b7096 | ||
|
|
d258140d54 | ||
|
|
6ce142d73a | ||
|
|
3a15c54f10 | ||
|
|
4a6e71096c | ||
|
|
ec9ac181f8 | ||
|
|
ccd475e41d | ||
|
|
7dc6ce72dd | ||
|
|
064be343f5 | ||
|
|
c8853223a3 | ||
|
|
0b7d210f99 | ||
|
|
b22e6f80fc | ||
|
|
15fa05eff6 | ||
|
|
61e0311889 | ||
|
|
825b918146 | ||
|
|
19587d43ee | ||
|
|
f4a9b05114 | ||
|
|
7735cf5b95 | ||
|
|
fcb28161b1 | ||
|
|
b57da5413c | ||
|
|
630b262d2b | ||
|
|
8f0acbf413 | ||
|
|
a3f2a5c7ec | ||
|
|
b5db63f4bb | ||
|
|
c325cc039a | ||
|
|
0b7edc07f4 | ||
|
|
da48efe63b | ||
|
|
3f1b071ae5 | ||
|
|
444cb7a566 | ||
|
|
889f9f3a36 | ||
|
|
e4c4ab053e | ||
|
|
10ff2d705c | ||
|
|
31f7427859 | ||
|
|
c338540885 | ||
|
|
d908d4c61c | ||
|
|
b94dd6dbeb | ||
|
|
a7333bdb4e | ||
|
|
881b55d691 | ||
|
|
28236b1b34 | ||
|
|
7e62ce4bd8 | ||
|
|
b0d75f8957 | ||
|
|
68a35bfa03 | ||
|
|
70ec56f8bb | ||
|
|
e18eaac3eb | ||
|
|
5f17da80d0 | ||
|
|
1663bcf932 | ||
|
|
16a3c9491f | ||
|
|
bab74c0c7e | ||
|
|
5c5d50929e | ||
|
|
ce744aeca4 | ||
|
|
0222dc6f39 | ||
|
|
46f954d23d | ||
|
|
42b3038463 | ||
|
|
c9612d3120 | ||
|
|
60d0900496 | ||
|
|
c1963ce2ac | ||
|
|
1dcd20b401 | ||
|
|
7e91897154 | ||
|
|
61eb265cf4 | ||
|
|
46995bcfc3 | ||
|
|
dd8002f092 | ||
|
|
6ee5aea1bf | ||
|
|
81cf1171a9 | ||
|
|
955c6d1fe4 | ||
|
|
7546b44111 | ||
|
|
d4679063fc | ||
|
|
e8c72767fa | ||
|
|
440e4c4ade | ||
|
|
70afbd1ca7 | ||
|
|
5e9238e3f0 | ||
|
|
7f6bfc1bdb | ||
|
|
ebc2bfafdd | ||
|
|
249c331117 | ||
|
|
818c59e4dd | ||
|
|
ddcb7b4197 | ||
|
|
4a0eef3b14 | ||
|
|
5850f237fe | ||
|
|
0f8bf23bcf | ||
|
|
a2fa0934d0 | ||
|
|
214c35e05d | ||
|
|
e5f4b967ed | ||
|
|
73159e241d | ||
|
|
a1287c26b9 | ||
|
|
36fe7511f1 | ||
|
|
4551fffe69 | ||
|
|
bef545bdb9 | ||
|
|
ab2db49fbe | ||
|
|
e65c5d2a1c | ||
|
|
27c50e8158 | ||
|
|
07a480c43c | ||
|
|
6ffcae4286 | ||
|
|
03e0cef726 | ||
|
|
a4d9bc7550 | ||
|
|
f24b997501 | ||
|
|
f72999a1df | ||
|
|
b02c8cb8f5 | ||
|
|
579fafc04b | ||
|
|
77716836be | ||
|
|
017082f22f | ||
|
|
1abc1f9275 | ||
|
|
12df85ad8c | ||
|
|
1b2b20423e | ||
|
|
afca9d55aa | ||
|
|
043449125d | ||
|
|
26fe71d64c | ||
|
|
354d1b7d2f | ||
|
|
dca8d58b51 | ||
|
|
1550b5afff | ||
|
|
e4c03c238e | ||
|
|
0542a2254b | ||
|
|
d0ab7d9e17 | ||
|
|
dbe99c2884 | ||
|
|
c6ddcc4344 | ||
|
|
bfa4ac2ef8 | ||
|
|
818971841f | ||
|
|
5c123e2c68 | ||
|
|
dc7eedee74 | ||
|
|
2359222723 | ||
|
|
9b90f642a1 | ||
|
|
0acd4b9d2f | ||
|
|
7fcff98f29 | ||
|
|
3c25434202 | ||
|
|
e98aab7ea9 | ||
|
|
a380c1675f | ||
|
|
3232c1f884 | ||
|
|
c6b34c2b77 | ||
|
|
d3140a25f6 | ||
|
|
5859be88b9 | ||
|
|
35c0d4ae9a | ||
|
|
7aa35d12c3 | ||
|
|
8465438051 | ||
|
|
d301b8a3fe | ||
|
|
3f442402f8 | ||
|
|
705451d21e | ||
|
|
f7b2ce278b | ||
|
|
c6e4fb56b4 | ||
|
|
e630dea2ab | ||
|
|
2afe571c07 | ||
|
|
1de4874478 | ||
|
|
00203dbc7c | ||
|
|
190003dac7 | ||
|
|
dcdffbdab7 | ||
|
|
80008796de | ||
|
|
5c89a6d08c | ||
|
|
0c4f8bed1b | ||
|
|
8a0bf96f6d | ||
|
|
7718000dba | ||
|
|
e061e3ca70 | ||
|
|
e06396dd79 | ||
|
|
77d979f112 | ||
|
|
57f996f806 | ||
|
|
81e6a88d58 | ||
|
|
e76f7c0d47 | ||
|
|
32a7e7cb17 | ||
|
|
379ee22af3 | ||
|
|
b879bade6a | ||
|
|
bc66d9a684 | ||
|
|
59826c197b | ||
|
|
33a5888725 | ||
|
|
0d542fb3bc | ||
|
|
229ee85890 | ||
|
|
110a29552a | ||
|
|
6c9bb0d7ff | ||
|
|
6a86370b0e | ||
|
|
cb94a4ab1b | ||
|
|
76b127e824 | ||
|
|
51f7caf263 | ||
|
|
e74760e780 | ||
|
|
614b6c0e38 | ||
|
|
df75bbee0e | ||
|
|
220161b6c6 | ||
|
|
b23f8e067c | ||
|
|
a38111d6bb | ||
|
|
daccce7bb8 | ||
|
|
4fc27d2195 | ||
|
|
708112cd16 | ||
|
|
3322ff6e7c | ||
|
|
42b7eb87a1 | ||
|
|
b4fa51e981 | ||
|
|
dc7432a6dd | ||
|
|
0a0f9b3ad5 | ||
|
|
b2ef045c03 | ||
|
|
e83d1616c8 | ||
|
|
d196ec43e6 | ||
|
|
03ba8f9325 | ||
|
|
79f83561bd | ||
|
|
0653716408 | ||
|
|
6fb64656b9 | ||
|
|
21dd8834d3 | ||
|
|
c6dc8da704 | ||
|
|
f8e7b157c7 | ||
|
|
9ddc188f97 | ||
|
|
6671d5b03b | ||
|
|
10e666ce0f | ||
|
|
27f5b12ee2 | ||
|
|
f7b00b7adb | ||
|
|
ca3d5f49d2 | ||
|
|
1d0a854957 | ||
|
|
d1151a9528 | ||
|
|
1ae6c55c52 | ||
|
|
f7ce2f6748 | ||
|
|
c6b89f9766 | ||
|
|
8eae808223 | ||
|
|
31ca2cc2ae | ||
|
|
5cf2328a8b | ||
|
|
ac93f8dfd1 | ||
|
|
28c36e58b7 | ||
|
|
ba6ccd109c | ||
|
|
e9a5d1cee3 | ||
|
|
4ee3062054 | ||
|
|
3f4e570320 | ||
|
|
d0c1c4db19 | ||
|
|
05e1054058 | ||
|
|
036fdf0b29 | ||
|
|
2b034335ec | ||
|
|
be6ae07ac1 | ||
|
|
e3359ea415 | ||
|
|
2cc85cc21d | ||
|
|
9c0a55a2eb | ||
|
|
8b1905453d | ||
|
|
fa0cb30d8f | ||
|
|
2a4d8b4f72 | ||
|
|
92876820cb | ||
|
|
02c9fa82d4 | ||
|
|
1b01448f01 | ||
|
|
dfeb17a5c8 | ||
|
|
d8e3980b86 | ||
|
|
0540f2c10b | ||
|
|
809652d740 | ||
|
|
be41fdbc21 | ||
|
|
fbd4e559e6 | ||
|
|
c34c77b9a5 | ||
|
|
a4d81fb723 | ||
|
|
948248b732 | ||
|
|
807679cb0a | ||
|
|
67179950d0 | ||
|
|
af05107964 | ||
|
|
fe209f3f4c | ||
|
|
e6d1d011ef | ||
|
|
fe7bc2a846 | ||
|
|
9453488424 | ||
|
|
267125e5b1 | ||
|
|
43b2a1e423 | ||
|
|
04ba8f4cbd | ||
|
|
c747162717 | ||
|
|
3ad29b230c | ||
|
|
7a8c14a634 | ||
|
|
021463c628 | ||
|
|
1152b6eb38 | ||
|
|
27be74e800 | ||
|
|
709ed92ac0 | ||
|
|
faa75ee9e7 | ||
|
|
3411511a8b | ||
|
|
12fcc76ff0 | ||
|
|
0c19046721 | ||
|
|
bd6afa1b23 | ||
|
|
5fc0e503e1 | ||
|
|
8e709d2b62 | ||
|
|
7fa15fd17e | ||
|
|
a80524b478 | ||
|
|
2ee2243c63 | ||
|
|
bba506d6b4 | ||
|
|
d8dc8a6787 | ||
|
|
a9cca00933 | ||
|
|
d3cd2e780f | ||
|
|
1e81c772ae | ||
|
|
d2363f1819 | ||
|
|
c3466b9021 | ||
|
|
011a7d35d6 | ||
|
|
9d27ee0839 | ||
|
|
299078aefe | ||
|
|
74e0e4f6d0 | ||
|
|
60c134893c | ||
|
|
e78608a37a | ||
|
|
d2c57e99ca | ||
|
|
bb18b29e13 | ||
|
|
4b6ed1dbf7 | ||
|
|
cea6a569af | ||
|
|
205138f16e | ||
|
|
342ab00ac3 | ||
|
|
24b629a216 | ||
|
|
cafc57fa65 | ||
|
|
ce5749ce2f | ||
|
|
b6316ef085 | ||
|
|
2464a5551a | ||
|
|
cf7cc2bd52 | ||
|
|
b18d488104 | ||
|
|
9ca6a9dc5e | ||
|
|
17bdd95936 | ||
|
|
31a43a7ba6 | ||
|
|
778f8629eb | ||
|
|
e29bd8f42b | ||
|
|
013e6ea06e | ||
|
|
27d836180f | ||
|
|
9b710cf832 | ||
|
|
0370e7f1b3 | ||
|
|
fbfa01dcd1 | ||
|
|
18367e69a8 | ||
|
|
badc733488 | ||
|
|
54e71f43d4 | ||
|
|
329e382c6d | ||
|
|
644fdc8a2f | ||
|
|
c9352710f7 | ||
|
|
f4a37b2e95 | ||
|
|
3dd76cd953 | ||
|
|
d86a774ebf | ||
|
|
e7decd2c77 | ||
|
|
4324cb2998 | ||
|
|
4348417dd8 | ||
|
|
0e17dbeda8 | ||
|
|
3b33420fe5 | ||
|
|
e075e622ba | ||
|
|
092286fc1d | ||
|
|
9004c514da | ||
|
|
a27c113d35 | ||
|
|
1acabd65e3 | ||
|
|
2f86fef3a0 | ||
|
|
79e397ea03 | ||
|
|
daa88f2f06 | ||
|
|
404fa1288b | ||
|
|
c33df4771f | ||
|
|
8f23d17ece | ||
|
|
09b9d0af1d | ||
|
|
43fa021d48 | ||
|
|
907f713e51 | ||
|
|
276b9d005d | ||
|
|
fe14a08761 | ||
|
|
3865a10600 | ||
|
|
df865725ed | ||
|
|
be49d673a5 | ||
|
|
31a6a1164e | ||
|
|
e8bdbaab22 | ||
|
|
285c9e2c32 | ||
|
|
a5029d19a8 | ||
|
|
0565a6a6ff | ||
|
|
6987f06151 | ||
|
|
a2839f60dd | ||
|
|
1bc7c332ab | ||
|
|
8ffe4cd472 | ||
|
|
70704c7c09 | ||
|
|
5029a6d044 | ||
|
|
9894b3173b | ||
|
|
4b2f10d009 | ||
|
|
8590fd9947 | ||
|
|
7bb478b683 | ||
|
|
ce2fee10b5 | ||
|
|
4033b53d7f | ||
|
|
8c0e2378c9 | ||
|
|
3fb54162d6 | ||
|
|
571a4f72a4 | ||
|
|
2b818dbe44 | ||
|
|
19dce5342f | ||
|
|
632fe22240 | ||
|
|
5618038463 | ||
|
|
9178b762fc | ||
|
|
fea59750ed | ||
|
|
fba4cb9114 | ||
|
|
b82badd127 | ||
|
|
d3dc8a2377 | ||
|
|
df24372775 | ||
|
|
0a8231b59d | ||
|
|
1f7727975c | ||
|
|
68fa44d399 | ||
|
|
ab83ed8691 | ||
|
|
ebe982c443 | ||
|
|
4ccbd24c60 | ||
|
|
da0d0a7b52 | ||
|
|
a537509aa6 | ||
|
|
865b6fed89 | ||
|
|
d100a735a0 | ||
|
|
902f23c456 | ||
|
|
c92e192e56 | ||
|
|
440ff27e58 | ||
|
|
085e3c3abe | ||
|
|
58e43633fc | ||
|
|
d743acb6cd | ||
|
|
cb2b4b8f72 | ||
|
|
f0874f187a | ||
|
|
c029521b24 | ||
|
|
7a8beac1c2 | ||
|
|
b5172caeb5 | ||
|
|
baff2948d4 | ||
|
|
66c9659f5a | ||
|
|
9697febd83 | ||
|
|
f29a85f20a | ||
|
|
cc9f6f430b | ||
|
|
7776165ef7 | ||
|
|
f6e91df440 | ||
|
|
3f6e889449 | ||
|
|
8d838e4fd1 | ||
|
|
68c4b76352 | ||
|
|
f6e800655b | ||
|
|
35ca107361 | ||
|
|
3f8f2e89e5 | ||
|
|
d49c50ddaf | ||
|
|
44ae92c4ed | ||
|
|
ec628cb0bf | ||
|
|
0dcc910223 | ||
|
|
1c6c8c3479 | ||
|
|
99866f01b2 | ||
|
|
f3766cda81 | ||
|
|
946e0c3acd | ||
|
|
e1ce8994b9 | ||
|
|
b151de6684 | ||
|
|
4bfaa964a7 | ||
|
|
cfed33ee42 | ||
|
|
aa972e5351 | ||
|
|
27509f0408 | ||
|
|
f943da88d7 | ||
|
|
e8dcecfff0 | ||
|
|
cfccfc247d | ||
|
|
04ad22d326 | ||
|
|
1228bf3865 | ||
|
|
31b0248b05 | ||
|
|
d1161137d9 | ||
|
|
888474b59c | ||
|
|
5c35d2a41f | ||
|
|
eaa8bf8b30 | ||
|
|
0d3c9811c1 | ||
|
|
6e290aab3e | ||
|
|
d1a47ee57b | ||
|
|
7aa5c393e4 | ||
|
|
7b3c640472 | ||
|
|
d9606d65b5 | ||
|
|
13245fd9a8 | ||
|
|
04ccd4dbdd | ||
|
|
5601cf4236 | ||
|
|
088912e2fc | ||
|
|
29d956a7b0 | ||
|
|
1ba6cc9188 | ||
|
|
27f0e8c0e5 | ||
|
|
92bc33eed4 | ||
|
|
6160f3874f | ||
|
|
135f17c5aa | ||
|
|
d60e302f70 | ||
|
|
d858068975 | ||
|
|
8c86c28496 | ||
|
|
76407a6646 | ||
|
|
56f63dd295 | ||
|
|
c8cec61e1c | ||
|
|
feb63f7b5b | ||
|
|
4d5e4730a2 | ||
|
|
24bf92c27c | ||
|
|
8bc57363c8 | ||
|
|
e65560f963 | ||
|
|
9326f90d14 | ||
|
|
390e737dea | ||
|
|
358464d5ad | ||
|
|
5bc8bc9d64 | ||
|
|
74fdf90d70 | ||
|
|
530568e662 | ||
|
|
f9ff04866e | ||
|
|
96c44be33d | ||
|
|
2bd3bc6d47 | ||
|
|
fb89ce22fd | ||
|
|
3e397d6e78 | ||
|
|
c824e7831a | ||
|
|
8f1c328d1a | ||
|
|
4d78510cb0 | ||
|
|
dbc9869d7e | ||
|
|
27147dfe32 | ||
|
|
665bffa52f | ||
|
|
65dc32fdb8 | ||
|
|
0621402e0a | ||
|
|
1d6d0e2fe3 | ||
|
|
4f37c3d024 | ||
|
|
072cf0eb30 | ||
|
|
455e729393 | ||
|
|
fbee0ec588 | ||
|
|
9a96dc1c33 | ||
|
|
36633895e5 | ||
|
|
1baac27e8c | ||
|
|
a9f9163d2c | ||
|
|
e4fc96f691 | ||
|
|
d3adb6ca09 | ||
|
|
46f22a2778 | ||
|
|
75bd303b15 | ||
|
|
1c9eea0033 | ||
|
|
c77271aa56 | ||
|
|
04d5abc9eb | ||
|
|
031ee33093 | ||
|
|
307552a70c | ||
|
|
f17a2eb941 | ||
|
|
613ca6fc80 | ||
|
|
e610f62818 | ||
|
|
6fd76f2619 | ||
|
|
ed9924895d | ||
|
|
27f72ac320 | ||
|
|
ba1b63eb76 | ||
|
|
9c651d513a | ||
|
|
d9903d72c7 | ||
|
|
c9a2fd1d7d | ||
|
|
e99baebc49 | ||
|
|
0c6458da26 | ||
|
|
770b7312f5 | ||
|
|
f05c3162cd | ||
|
|
d821445304 | ||
|
|
100ed91a78 | ||
|
|
733bb6354f | ||
|
|
10a318d5ba | ||
|
|
e394fa73d4 | ||
|
|
54d75153d2 | ||
|
|
84cd674ff1 | ||
|
|
58b74bf961 | ||
|
|
381ff9ee48 | ||
|
|
b6549eb4c9 | ||
|
|
22116629f1 | ||
|
|
0328f37b2a | ||
|
|
c5f5e6153f | ||
|
|
6fae1d8555 | ||
|
|
f6609474b6 | ||
|
|
83721464ed | ||
|
|
da13b1d3a6 | ||
|
|
efefba2bf3 | ||
|
|
cc35d725f2 | ||
|
|
a3e0e9f197 | ||
|
|
a3fd0ea5a4 | ||
|
|
6a3ddf1ab4 | ||
|
|
a7cae9b2a4 | ||
|
|
d8e25b9753 | ||
|
|
a415accbc3 | ||
|
|
d6a9005b14 | ||
|
|
bf7366438b | ||
|
|
412e3aa136 | ||
|
|
d23183ed31 | ||
|
|
fa8d5d97d0 | ||
|
|
16921c78ea | ||
|
|
790703b5fa | ||
|
|
4878b8bca2 | ||
|
|
bdb01432df | ||
|
|
6dfede035b | ||
|
|
4bd92c0d46 | ||
|
|
57d7a2e8dd | ||
|
|
8ceaf53728 | ||
|
|
ee05f3a65f | ||
|
|
7156dd265f | ||
|
|
9e7e41cfac | ||
|
|
4998a130e5 | ||
|
|
39fbd5b29c | ||
|
|
e99727f87f | ||
|
|
0ee3986da6 | ||
|
|
1219d3250c | ||
|
|
8ae43210e7 | ||
|
|
5deb697ec4 | ||
|
|
a4a21b6a92 | ||
|
|
c3c832fffa | ||
|
|
b32f4592fa | ||
|
|
c8f1766b71 | ||
|
|
411706d147 | ||
|
|
72cea9a0bf | ||
|
|
5804d6b4b2 | ||
|
|
af4a4af038 | ||
|
|
602ec8d0c0 | ||
|
|
efb6e6729a | ||
|
|
6087782660 | ||
|
|
cee6412e3d | ||
|
|
1e4262753a | ||
|
|
39676ef7a9 | ||
|
|
8b41bb923a | ||
|
|
e2e344ea7a | ||
|
|
94889d7a02 | ||
|
|
cd404f72cd | ||
|
|
c13da97044 | ||
|
|
0ac8821015 | ||
|
|
40b0e08d35 | ||
|
|
15215b1f71 | ||
|
|
18cfe1f4ca | ||
|
|
750d6ca3ce | ||
|
|
06bc828a9b | ||
|
|
91b9f925d6 | ||
|
|
433666a470 | ||
|
|
d0138a404d | ||
|
|
75c516f3cd | ||
|
|
fcd592e53d | ||
|
|
4a1ec53c24 | ||
|
|
2b807ddf83 | ||
|
|
66b95013f9 | ||
|
|
a5e3c681bc | ||
|
|
2d5cb51fe2 | ||
|
|
03b9e1b914 | ||
|
|
132dd7a7f2 | ||
|
|
25a902c45a | ||
|
|
be6824b14d | ||
|
|
026f7b9abc | ||
|
|
bec8166a77 | ||
|
|
9f6db83054 | ||
|
|
87c4c1df15 | ||
|
|
92f56aa73f | ||
|
|
3a24532d2a | ||
|
|
cd73b40635 | ||
|
|
72587e643f | ||
|
|
ea6d310576 | ||
|
|
0cb34615d9 | ||
|
|
c89448d17c | ||
|
|
0f0f747135 | ||
|
|
fd5d95f9d0 | ||
|
|
a9fea29aaf | ||
|
|
6d709de396 | ||
|
|
b78a62c7ab | ||
|
|
d88b4c1e86 | ||
|
|
a4bdd81966 | ||
|
|
97ed33758e | ||
|
|
f7b2558b0e | ||
|
|
f232e857f0 | ||
|
|
977ebe5256 | ||
|
|
cbc990d459 | ||
|
|
cd47a3b2be | ||
|
|
f5dbb8112c | ||
|
|
247655179f | ||
|
|
3f2124e612 | ||
|
|
27ed76ddac | ||
|
|
f90a55cec9 | ||
|
|
06f377806b | ||
|
|
32f30c304e | ||
|
|
3d02a3b20b | ||
|
|
0f2dbbc09d | ||
|
|
bfa50ac015 | ||
|
|
23bbb0fcd2 | ||
|
|
32cdec42cb | ||
|
|
ac8597dee0 | ||
|
|
9cef61debe | ||
|
|
2524c6448e | ||
|
|
c5f3a92f51 | ||
|
|
00e45a4ba1 | ||
|
|
51779b2a35 | ||
|
|
76a92b1a10 | ||
|
|
2af5ec006e | ||
|
|
581c445062 | ||
|
|
28984b5362 | ||
|
|
4de3bb9a78 | ||
|
|
95f8035ec0 | ||
|
|
fe9756c9b4 | ||
|
|
9cc0a2e5be | ||
|
|
bfec2435da | ||
|
|
fa5688737c | ||
|
|
1f5400071e | ||
|
|
fb31927a33 | ||
|
|
9820a60374 | ||
|
|
3c297c2254 | ||
|
|
ec5c85dcc7 | ||
|
|
1ab9f39f70 | ||
|
|
01fb761d98 | ||
|
|
3b47e90274 | ||
|
|
631412844c | ||
|
|
0a6d3986cd | ||
|
|
4afc68d74d | ||
|
|
5ec27ff6c8 | ||
|
|
c227286dd5 | ||
|
|
16d904a198 | ||
|
|
e1ab83155a | ||
|
|
3fba4dabd6 | ||
|
|
d3ef7e754d | ||
|
|
4c60f05ca5 | ||
|
|
e1909a541f | ||
|
|
b1440067cc | ||
|
|
58f6502a1c | ||
|
|
9973e51b92 | ||
|
|
30dd7b0230 | ||
|
|
bfc43ef52b | ||
|
|
495df9ea00 | ||
|
|
4bf1ba34f6 | ||
|
|
7236d14650 | ||
|
|
04fcb66580 | ||
|
|
6b02b3d9aa | ||
|
|
6a83705a68 | ||
|
|
2ec3b16ffd | ||
|
|
0bdb744918 | ||
|
|
cd3726b394 | ||
|
|
6cdfd45134 | ||
|
|
5549a005f1 | ||
|
|
5837fe75eb | ||
|
|
d7fb95fa63 | ||
|
|
8471f47abb | ||
|
|
48e7616263 | ||
|
|
2589190724 | ||
|
|
e12055de49 | ||
|
|
986516545a | ||
|
|
ee0ee2375d | ||
|
|
ed2a7ba1ed | ||
|
|
07bf64d9cb | ||
|
|
d6657a3cb0 | ||
|
|
5817265e91 | ||
|
|
a86ad2771a | ||
|
|
31e49a08a0 | ||
|
|
2e07e62ffd | ||
|
|
638bcfe311 | ||
|
|
5450debfce | ||
|
|
948f4d2605 | ||
|
|
cb9d159738 | ||
|
|
53fe663791 | ||
|
|
e7f465bf16 | ||
|
|
5c8481f1ca | ||
|
|
2e92fe2e2a | ||
|
|
9d81e7e1a3 | ||
|
|
78ff2bb73a | ||
|
|
31373b1d6d | ||
|
|
c56a83c405 | ||
|
|
94cda25277 | ||
|
|
b3528e0aee | ||
|
|
ecf17abfd3 | ||
|
|
be147aea80 | ||
|
|
07f6e9517d | ||
|
|
ba8e305fdb | ||
|
|
0fbbef7858 | ||
|
|
857aef3244 | ||
|
|
1f5bc4b3ee | ||
|
|
ef943d5f9a | ||
|
|
a70fa4f6b1 | ||
|
|
8c8aaf4133 | ||
|
|
0b233ee1e6 | ||
|
|
1a70f94dcd | ||
|
|
e66be5010f | ||
|
|
7de2e86045 | ||
|
|
dc22d42e96 | ||
|
|
bab46b211a | ||
|
|
4b6407dfc0 | ||
|
|
59139a778d | ||
|
|
a599ce318e | ||
|
|
5bac5f3c60 | ||
|
|
794882435f | ||
|
|
d599ebe3b9 | ||
|
|
834d0045ae | ||
|
|
0430c806c4 | ||
|
|
12ea4e69f1 | ||
|
|
43ff20c0a8 | ||
|
|
d29e75ac7a | ||
|
|
ee158a031d | ||
|
|
95ae34887c | ||
|
|
3b357976df | ||
|
|
577199ce64 | ||
|
|
71a51d5775 | ||
|
|
815707e66c | ||
|
|
e7cc301f17 | ||
|
|
2e32498ef1 | ||
|
|
dbf3594146 | ||
|
|
baec02dc03 | ||
|
|
7769a6d2bc | ||
|
|
951eca1680 | ||
|
|
e28838031c | ||
|
|
5d910222bf | ||
|
|
86255ca6bb | ||
|
|
d200431602 | ||
|
|
6f2a13544b | ||
|
|
761a4d953b | ||
|
|
6d23937bc7 | ||
|
|
b7993a5f74 | ||
|
|
88e714db09 | ||
|
|
b66f6e0e61 | ||
|
|
9605844ccf | ||
|
|
45406823c6 | ||
|
|
465ff1e238 | ||
|
|
43a1742460 | ||
|
|
075ac54cd0 | ||
|
|
1ec2e78306 | ||
|
|
1e39d9f8c8 | ||
|
|
42258f0225 | ||
|
|
983cf8f203 | ||
|
|
63b4e583dc | ||
|
|
0393fc2fda | ||
|
|
67f8782b53 | ||
|
|
fa10e60189 | ||
|
|
e218692672 | ||
|
|
4fee9df38d | ||
|
|
de34da86a0 | ||
|
|
97ab328bd7 | ||
|
|
4e30c31e17 | ||
|
|
1e63f6fbf6 | ||
|
|
c3e3cfaf44 | ||
|
|
0426f3d1de | ||
|
|
31e9cea7d2 | ||
|
|
06404b96ba | ||
|
|
964702c385 | ||
|
|
3faa340895 | ||
|
|
369c19fdf6 | ||
|
|
4ab5e09974 | ||
|
|
f8ff631605 | ||
|
|
2fcbb582ed | ||
|
|
f382337eb5 | ||
|
|
f64446c06f | ||
|
|
d8d733d211 | ||
|
|
81581a475d | ||
|
|
8124a07e07 | ||
|
|
d72c1bf62c | ||
|
|
34485cc1ec | ||
|
|
8097c09c85 | ||
|
|
a89c7e528f | ||
|
|
8639fc8a9b | ||
|
|
031de0df32 | ||
|
|
43f2e46f34 | ||
|
|
bb67c78df8 | ||
|
|
a0a162a48c | ||
|
|
d176ed8fbb | ||
|
|
96b07d875d | ||
|
|
c1c8270e45 | ||
|
|
e6c614d69c | ||
|
|
ffb8e03c97 | ||
|
|
06c237838c | ||
|
|
e942f5e28e | ||
|
|
038f42a579 | ||
|
|
69ce692671 | ||
|
|
d76c6966b0 | ||
|
|
f7d3fda88a | ||
|
|
45fdfd4e87 | ||
|
|
8ecb117997 | ||
|
|
0b5b5dbcf5 | ||
|
|
071c74acb3 | ||
|
|
86c8b77dd6 | ||
|
|
91ec5ae972 | ||
|
|
14cadf44ac | ||
|
|
a414c03bf3 | ||
|
|
a8b2f521f7 | ||
|
|
d71c33b3e8 | ||
|
|
301d45b2fa | ||
|
|
f49265c788 | ||
|
|
510ec4b003 | ||
|
|
45788bd8ad | ||
|
|
e2d7cd7b03 | ||
|
|
627fbc3ec0 | ||
|
|
3c513a31cc | ||
|
|
7fc365a657 | ||
|
|
dc9cfc4f41 | ||
|
|
e36cf0271f | ||
|
|
46c20da35e | ||
|
|
27e52a430b | ||
|
|
46d3e12946 | ||
|
|
a861f192ec | ||
|
|
bd259a795f | ||
|
|
2da5057d58 | ||
|
|
d111361a1f | ||
|
|
46868805e2 | ||
|
|
e1e603f633 | ||
|
|
0a130212cb | ||
|
|
7dd9a38b3b | ||
|
|
e7f3e94f30 | ||
|
|
2dc8ac0bdd | ||
|
|
4932daadba | ||
|
|
74244c3c9c | ||
|
|
8965de697b | ||
|
|
89d5e9e3b4 | ||
|
|
fd62bbfacb | ||
|
|
e6edf2002d | ||
|
|
f68e301c30 | ||
|
|
745792b34d | ||
|
|
7933e44493 | ||
|
|
840d19bb5e | ||
|
|
ca9a6d0843 | ||
|
|
de1db98cde | ||
|
|
7b251e3d7b | ||
|
|
30b45d2263 | ||
|
|
3fc9c6c3ed | ||
|
|
97ff050396 | ||
|
|
e89af3ee94 | ||
|
|
77069156cb | ||
|
|
0c42930d55 | ||
|
|
928c39937a | ||
|
|
001cf042d9 | ||
|
|
fcd10254b7 | ||
|
|
bc3f556055 | ||
|
|
0426ec6d2f | ||
|
|
19b1b0252c | ||
|
|
ebc8a68cb3 | ||
|
|
4a65748851 | ||
|
|
56b7c42fae | ||
|
|
52c7793326 | ||
|
|
7c9205f68d | ||
|
|
ffd34678e0 | ||
|
|
a6c75e969d | ||
|
|
a2c9d0ad4d | ||
|
|
aa46df7735 | ||
|
|
d123009460 | ||
|
|
cc5cd031ab | ||
|
|
e3d47ddafa | ||
|
|
4f69f53cae | ||
|
|
99fba5995a | ||
|
|
9d805f5d26 | ||
|
|
5e3f526bc1 | ||
|
|
c74b2197f9 | ||
|
|
dad3306d1f | ||
|
|
2844f1f573 | ||
|
|
bc2bd0e26a | ||
|
|
3d95b0e1d4 | ||
|
|
5bd9edeeed | ||
|
|
c40b9aea5f | ||
|
|
1fb421d82c | ||
|
|
541399911d | ||
|
|
67990f775b | ||
|
|
58f1fbadc3 | ||
|
|
6536663130 | ||
|
|
774fc0f340 | ||
|
|
a5d02b18c7 | ||
|
|
e4861bb3e1 | ||
|
|
d11ecda08c | ||
|
|
23ab97351f | ||
|
|
326831837d | ||
|
|
bdd458241b | ||
|
|
fc2ee0d0b0 | ||
|
|
c002284cd8 | ||
|
|
56fb6ba443 | ||
|
|
c66bd302bd | ||
|
|
17c67418f6 | ||
|
|
935194d5ae | ||
|
|
02ce171aba | ||
|
|
a52179d090 | ||
|
|
1390a7f622 | ||
|
|
a05d073018 | ||
|
|
c0daab6075 | ||
|
|
18fb0d5d3f | ||
|
|
5f5139ad6c | ||
|
|
046324b64c | ||
|
|
48c16571d1 | ||
|
|
1127061d54 | ||
|
|
fa2f54b358 | ||
|
|
afbc46e135 | ||
|
|
e3cebd981e | ||
|
|
9881a61cb3 | ||
|
|
43e513d7af | ||
|
|
08f049434f | ||
|
|
a71f70708c | ||
|
|
7f671f5390 | ||
|
|
d84b813cb3 | ||
|
|
fabd425f67 | ||
|
|
fbf3a1a24e | ||
|
|
76874d84ba | ||
|
|
ded5d5e6a1 | ||
|
|
2d5e6888f9 | ||
|
|
3a503ce2cc | ||
|
|
1f3f019a59 | ||
|
|
f55fc5f082 | ||
|
|
fb0b316c81 | ||
|
|
1ebfc28aea | ||
|
|
da5effd52d | ||
|
|
e8aa31cd8b | ||
|
|
c7243f6c5c | ||
|
|
2500ae7e27 | ||
|
|
5af8995471 | ||
|
|
aa5061d784 | ||
|
|
4b79c858dd | ||
|
|
d2aec43b66 | ||
|
|
76b767df4b | ||
|
|
1dc41ab2d0 | ||
|
|
c6d3370f07 | ||
|
|
40a689011e | ||
|
|
03d73f2e9e | ||
|
|
142a9c2703 | ||
|
|
49233b1ef6 | ||
|
|
958d79fa6b | ||
|
|
079900fa84 | ||
|
|
211bc1810b | ||
|
|
0d60940b40 | ||
|
|
1e3aff2d71 | ||
|
|
7bab3aa994 | ||
|
|
bfdf9ecf7b | ||
|
|
4933af3550 | ||
|
|
474dc4a123 | ||
|
|
aea0022c5c | ||
|
|
a1ec749dfe | ||
|
|
6b477e37ca | ||
|
|
28092ffe9d | ||
|
|
5e83481ffb | ||
|
|
7232178513 | ||
|
|
aaf04a515b | ||
|
|
5ea876ff2f | ||
|
|
2fedb900d3 | ||
|
|
150565601b | ||
|
|
6ab3ddaf8e | ||
|
|
cae5a93c7e | ||
|
|
955f35e977 | ||
|
|
a504ef9ba7 | ||
|
|
dcc3cbf1b1 | ||
|
|
6aed43b89c | ||
|
|
8035024531 | ||
|
|
90380ed232 | ||
|
|
568bba75c6 | ||
|
|
f81c869d8f | ||
|
|
f546e7e696 | ||
|
|
730e030994 | ||
|
|
2e24055f5c | ||
|
|
e9caa18ff9 | ||
|
|
93bc0cf38a | ||
|
|
1fe9055055 | ||
|
|
79f2b2bbdc | ||
|
|
fbc2ea478e | ||
|
|
7ffc3a4545 | ||
|
|
e051ad2a9a | ||
|
|
02b9744d5d | ||
|
|
08b61f29d2 | ||
|
|
1cb750a649 | ||
|
|
730105898b | ||
|
|
0a4305f82a | ||
|
|
2e5f480a97 | ||
|
|
0001cb2818 | ||
|
|
8558a98bf6 | ||
|
|
c42bd19c49 | ||
|
|
68ff4a8408 | ||
|
|
dc7e0ca4bf | ||
|
|
78bddacd5b | ||
|
|
8f10ac5047 | ||
|
|
5dab8612c8 | ||
|
|
bfa24a3205 | ||
|
|
cab0ccf0bd | ||
|
|
6cf0a010ec | ||
|
|
b7be2e6d3b | ||
|
|
aed968ebe6 | ||
|
|
087d4300f2 | ||
|
|
dc672ad821 | ||
|
|
b39a47a759 | ||
|
|
c21302e28a | ||
|
|
76018da068 | ||
|
|
b4953951f0 | ||
|
|
459b5973bb | ||
|
|
1103b14683 | ||
|
|
177dc71335 | ||
|
|
f7a4aceeae | ||
|
|
fd391b03ef | ||
|
|
1be5d61bb4 | ||
|
|
4678fcee52 | ||
|
|
e7537116b3 | ||
|
|
16d48eae6f | ||
|
|
0ce0ceb2c2 | ||
|
|
0bac54baaa | ||
|
|
d994020807 | ||
|
|
6a33e7eaa1 | ||
|
|
2a2da35c25 | ||
|
|
b3d0ee093b | ||
|
|
d3577550d2 | ||
|
|
16c02c2cc3 | ||
|
|
92855aa6ee | ||
|
|
e69182d7bc | ||
|
|
4734007ec9 | ||
|
|
ed230a12cc | ||
|
|
0b2584018d | ||
|
|
9501d2cc67 | ||
|
|
c50dd18881 | ||
|
|
d06e2f71f3 | ||
|
|
d43e43adbb | ||
|
|
59c4fc06f3 | ||
|
|
5016240d14 | ||
|
|
2032b51dac | ||
|
|
06b11da76d | ||
|
|
475a250ad4 | ||
|
|
b99626fbf2 | ||
|
|
046cdbe724 | ||
|
|
5d53ed9eae | ||
|
|
b933cbbae6 | ||
|
|
92616474e4 | ||
|
|
ffb32fe73e | ||
|
|
e4e618628f | ||
|
|
be4f2f52b2 | ||
|
|
05ecceb436 | ||
|
|
81cb2fab89 | ||
|
|
8bb9fe654e | ||
|
|
684962b84c | ||
|
|
75b85b2e8f | ||
|
|
51705d90c4 | ||
|
|
5912a0022f | ||
|
|
fe9e52b184 | ||
|
|
a4aa3b93cb | ||
|
|
f0f40d073b | ||
|
|
bb1ec6177f | ||
|
|
7f943d1445 | ||
|
|
abc2ffc185 | ||
|
|
4c710df347 | ||
|
|
3c79d672dc | ||
|
|
a82bc71088 | ||
|
|
96589d4acf | ||
|
|
771fc02b6d | ||
|
|
f570c4e9e8 | ||
|
|
2a3639a7d8 | ||
|
|
e4547c31d3 | ||
|
|
91d14191c7 | ||
|
|
34558f410d | ||
|
|
aea7fac8f0 | ||
|
|
4bd77f1d78 | ||
|
|
a46ba486a8 | ||
|
|
f9336832d4 | ||
|
|
cc7dc1cf0b | ||
|
|
631913599e | ||
|
|
184583f1ac | ||
|
|
f6c92c2eae | ||
|
|
f448fddd50 | ||
|
|
a44ca755d2 | ||
|
|
3720528354 | ||
|
|
ef69e9e736 | ||
|
|
31be7ba0d9 | ||
|
|
91428eaa96 | ||
|
|
05b532af79 | ||
|
|
035aa73c43 | ||
|
|
d25c2553ad | ||
|
|
965da0af4a | ||
|
|
73d1dcac5d | ||
|
|
2d003c2546 | ||
|
|
79787360c6 | ||
|
|
6b0d599a31 | ||
|
|
0f82b352c7 | ||
|
|
fbdb8367ef | ||
|
|
982544a63d | ||
|
|
ec3319b37f | ||
|
|
6fa659c41b | ||
|
|
e4e8535345 | ||
|
|
d1422a03e2 | ||
|
|
e13b1180af | ||
|
|
33fef23ffb | ||
|
|
9e78e4a792 | ||
|
|
800aba8312 | ||
|
|
6254d7def3 | ||
|
|
81ea1d407a | ||
|
|
271c9151f4 | ||
|
|
59822bf07e | ||
|
|
fe26dad744 | ||
|
|
4d81fdd4d8 | ||
|
|
2bf2da4822 | ||
|
|
e9c6a08e1e | ||
|
|
afc1fe6645 | ||
|
|
c0c2e1409c | ||
|
|
bb989791a7 | ||
|
|
04e9aa4ebc | ||
|
|
abb1258ee2 | ||
|
|
c220909e1b | ||
|
|
cce1cb353f | ||
|
|
c44fa6172b | ||
|
|
280892f061 | ||
|
|
bedb2f55f9 | ||
|
|
dca0e6b7ad | ||
|
|
1713f6e2f7 | ||
|
|
3a7b0525c7 | ||
|
|
7937360c6a | ||
|
|
fb100f21b3 | ||
|
|
fa543ae350 | ||
|
|
d8ad600b6a | ||
|
|
d79cdb8371 | ||
|
|
41b2fa7375 | ||
|
|
6bb091595e | ||
|
|
ecbd3eae14 | ||
|
|
5fb7ecbd03 | ||
|
|
e0d44b6678 | ||
|
|
3204bb01d9 | ||
|
|
faac31f217 | ||
|
|
e855b90344 | ||
|
|
63b6d30876 | ||
|
|
3acb7c8e2f | ||
|
|
50b7497e19 | ||
|
|
64913ba610 | ||
|
|
f9a5e9171e | ||
|
|
67948ac222 | ||
|
|
84d9e4d5ac | ||
|
|
72cc07fb42 | ||
|
|
057fffb1dd | ||
|
|
6ae115ac69 | ||
|
|
1aec54efdb | ||
|
|
e3dccd0157 | ||
|
|
048a478bab | ||
|
|
36853d1669 | ||
|
|
6d6df45a23 | ||
|
|
d4a67f6f38 | ||
|
|
b9c35e0009 | ||
|
|
4dffb74762 | ||
|
|
ce06ea80ca | ||
|
|
41497d7990 | ||
|
|
fc4aafa974 | ||
|
|
d0265554a7 | ||
|
|
713b3a9925 | ||
|
|
b596248a1e | ||
|
|
426a689d22 | ||
|
|
b721514f98 | ||
|
|
4282ef24cd | ||
|
|
7bbfaac67a | ||
|
|
11f7653daf | ||
|
|
19d29f25f2 | ||
|
|
32dff0b877 | ||
|
|
f3a7f86abb | ||
|
|
be6d443bc8 | ||
|
|
70d1acd744 | ||
|
|
30ecba45c6 | ||
|
|
accf96877f | ||
|
|
d502b6ec60 | ||
|
|
546df46eaf | ||
|
|
fe3af50fb8 | ||
|
|
6935272033 | ||
|
|
3c992c02ea | ||
|
|
4c4bfca20e | ||
|
|
2abca8b5c1 | ||
|
|
662e8aa60a | ||
|
|
3208ceaac6 | ||
|
|
c278c66e16 | ||
|
|
a48eee9179 | ||
|
|
475fad3a78 | ||
|
|
4749764e3e | ||
|
|
9c541ad461 | ||
|
|
2f681babf5 | ||
|
|
3e3d58b6f1 | ||
|
|
a32c81f94c | ||
|
|
c3c791efcb | ||
|
|
d19c7dbafc | ||
|
|
29a7d06dd4 | ||
|
|
cd55d22611 | ||
|
|
5c846496a2 | ||
|
|
0fd62ce693 | ||
|
|
18156f3965 | ||
|
|
4efc795520 | ||
|
|
9abfeee0e3 | ||
|
|
44bdbbb164 | ||
|
|
b044ac844c | ||
|
|
34a75dee02 | ||
|
|
da619e8615 | ||
|
|
aa7d0f5cca | ||
|
|
b6e97e5fb5 | ||
|
|
6432114e2a | ||
|
|
a89df30537 | ||
|
|
9a09c777d9 | ||
|
|
a1073e69b0 | ||
|
|
93845d6a2c |
No files matched your search
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Delete this message and describe the bug here. Note that we only fix bugs in the most recent release of mold, so please make sure that you are using the most recent version.
|
||||
@@ -5,13 +5,52 @@ on:
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
jobs:
|
||||
build:
|
||||
build-clang:
|
||||
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 build-essential libstdc++-10-dev cmake clang libssl-dev zlib1g-dev libxxhash-dev libtbb-dev git dwarfdump libc6-dev:i386 lib32gcc-10-dev gcc-multilib
|
||||
run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install git build-essential libstdc++-10-dev cmake clang libssl-dev zlib1g-dev libxxhash-dev libtbb-dev git bsdmainutils dwarfdump pkg-config
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
run: make -j$(nproc)
|
||||
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 test
|
||||
run: make -j$(nproc) test
|
||||
build-gcc:
|
||||
runs-on: ubuntu-20.04
|
||||
container: gcc:11.1.0
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: apt-get
|
||||
run: |
|
||||
dpkg --add-architecture i386 && apt-get update && apt-get install -y sudo libssl-dev zlib1g-dev libxxhash-dev cmake clang bsdmainutils dwarfdump qemu-user gcc-10-i686-linux-gnu gcc-10-aarch64-linux-gnu gcc-10-riscv64-linux-gnu gcc-10-arm-linux-gnueabihf g++-10-i686-linux-gnu g++-10-aarch64-linux-gnu g++-10-riscv64-linux-gnu g++-10-arm-linux-gnueabihf
|
||||
ln -sf /usr/bin/i686-linux-gnu-gcc-10 /usr/bin/i686-linux-gnu-gcc
|
||||
ln -sf /usr/bin/i686-linux-gnu-g++-10 /usr/bin/i686-linux-gnu-g++
|
||||
ln -sf /usr/bin/aarch64-linux-gnu-gcc-10 /usr/bin/aarch64-linux-gnu-gcc
|
||||
ln -sf /usr/bin/aarch64-linux-gnu-g++-10 /usr/bin/aarch64-linux-gnu-g++
|
||||
ln -sf /usr/bin/arm-linux-gnueabihf-gcc-10 /usr/bin/arm-linux-gnueabihf-gcc
|
||||
ln -sf /usr/bin/arm-linux-gnueabihf-g++-10 /usr/bin/arm-linux-gnueabihf-g++
|
||||
ln -sf /usr/bin/riscv64-linux-gnu-gcc-10 /usr/bin/riscv64-linux-gnu-gcc
|
||||
ln -sf /usr/bin/riscv64-linux-gnu-g++-10 /usr/bin/riscv64-linux-gnu-g++
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make CC=gcc CXX=g++ -j$(nproc)
|
||||
- name: make test
|
||||
run: make -j$(nproc) test-all
|
||||
build-macos:
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: brew
|
||||
run: brew install xxhash
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make -j$(sysctl -n hw.logicalcpu)
|
||||
- name: make test
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make -j$(sysctl -n hw.logicalcpu) test
|
||||
+5
-1
@@ -1,10 +1,14 @@
|
||||
*.o
|
||||
*.so
|
||||
*~
|
||||
.cache
|
||||
compile_commands.json
|
||||
/mold
|
||||
/test/tmp
|
||||
options.inc
|
||||
core
|
||||
ld
|
||||
oneTBB
|
||||
ld64.mold
|
||||
gentoo
|
||||
out
|
||||
/mold-*.tar.gz
|
||||
Whitespace-only changes.
+22
@@ -0,0 +1,22 @@
|
||||
# This dockerfile creates a reproducible build environment for mold.
|
||||
#
|
||||
# $ docker build -t rui314/mold-builder:v1-$(uname -m) .
|
||||
# $ docker push rui314/mold-builder:v1-$(uname -m)
|
||||
|
||||
FROM ubuntu:18.04
|
||||
RUN apt-get update && \
|
||||
TZ=Europe/London apt-get install -y tzdata && \
|
||||
apt-get install -y --no-install-recommends software-properties-common && \
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
|
||||
apt-get install -y --no-install-recommends build-essential git \
|
||||
wget pkg-config cmake libstdc++-11-dev zlib1g-dev gpg gpg-agent && \
|
||||
bash -c "$(wget -O- https://apt.llvm.org/llvm.sh)" && \
|
||||
apt-get install -y --no-install-recommends clang-14 && \
|
||||
apt clean && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
cd / && \
|
||||
wget -O- https://www.openssl.org/source/openssl-3.0.2.tar.gz | tar xzf - && \
|
||||
mv openssl-3.0.2 openssl && \
|
||||
cd openssl && \
|
||||
./Configure --prefix=/openssl && \
|
||||
make -j$(nproc)
|
||||
@@ -1,89 +1,224 @@
|
||||
CC = clang
|
||||
CXX = clang++
|
||||
# If you want to enable ASAN, run `make` with the following options:
|
||||
#
|
||||
# make CXXFLAGS='-fsanitize=address -g' LDFLAGS=-fsanitize=address USE_MIMALLOC=0
|
||||
|
||||
MIMALLOC_LIB = mimalloc/out/release/libmimalloc.a
|
||||
GIT_HASH = $(shell [ -d .git ] && git rev-parse HEAD)
|
||||
VERSION = 1.2.0
|
||||
|
||||
CPPFLAGS = -g -Imimalloc/include -pthread -std=c++20 \
|
||||
-Wno-deprecated-volatile \
|
||||
-DMOLD_VERSION=\"0.1.1\" \
|
||||
-DGIT_HASH=\"$(GIT_HASH)\" \
|
||||
$(EXTRA_CPPFLAGS)
|
||||
LDFLAGS = $(EXTRA_LDFLAGS)
|
||||
LIBS = -Wl,-as-needed -lcrypto -pthread -ltbb -lz -lxxhash -ldl
|
||||
OBJS = main.o object_file.o input_sections.o output_chunks.o \
|
||||
mapfile.o perf.o linker_script.o archive_file.o output_file.o \
|
||||
subprocess.o gc_sections.o icf.o symbols.o cmdline.o filepath.o \
|
||||
passes.o tar.o compress.o memory_mapped_file.o relocatable.o \
|
||||
arch_x86_64.o arch_i386.o
|
||||
PREFIX = /usr/local
|
||||
BINDIR = $(PREFIX)/bin
|
||||
LIBDIR = $(PREFIX)/lib
|
||||
LIBEXECDIR = $(PREFIX)/libexec
|
||||
MANDIR = $(PREFIX)/share/man
|
||||
|
||||
PREFIX ?= /usr
|
||||
DEBUG ?= 0
|
||||
LTO ?= 0
|
||||
ASAN ?= 0
|
||||
TSAN ?= 0
|
||||
INSTALL = install
|
||||
INSTALL_PROGRAM = $(INSTALL)
|
||||
INSTALL_DATA = $(INSTALL) -m 644
|
||||
|
||||
ifeq ($(DEBUG), 1)
|
||||
CPPFLAGS += -O0
|
||||
else
|
||||
CPPFLAGS += -O2
|
||||
D = $(DESTDIR)
|
||||
|
||||
# CXX defaults to `g++`. Rewrite it with a vendor-neutral compiler
|
||||
# name `c++`.
|
||||
ifeq ($(origin CXX), default)
|
||||
CXX = c++
|
||||
endif
|
||||
|
||||
# Allow overriding pkg-config binary
|
||||
PKG_CONFIG = pkg-config
|
||||
|
||||
# If you want to keep symbols in the installed binary, run make with
|
||||
# `STRIP=true` to run /bin/true instead of the strip command.
|
||||
STRIP = strip
|
||||
|
||||
SRCS = $(wildcard *.cc elf/*.cc macho/*.cc)
|
||||
OBJS = $(SRCS:%.cc=out/%.o)
|
||||
|
||||
OS := $(shell uname -s)
|
||||
ARCH := $(shell uname -m)
|
||||
|
||||
IS_ANDROID = 0
|
||||
ifneq ($(findstring -android,$(shell $(CC) -dumpmachine)),)
|
||||
IS_ANDROID = 1
|
||||
endif
|
||||
|
||||
# If you want to compile mold for debugging, invoke make as
|
||||
# `make CXXFLAGS=-g`.
|
||||
CFLAGS = -O2
|
||||
CXXFLAGS = -O2
|
||||
|
||||
MOLD_CXXFLAGS := -std=c++20 -fno-exceptions -fno-unwind-tables \
|
||||
-fno-asynchronous-unwind-tables -Ithird-party/xxhash \
|
||||
-DMOLD_VERSION=\"$(VERSION)\" -DLIBDIR="\"$(LIBDIR)\""
|
||||
|
||||
MOLD_LDFLAGS := -pthread -lz -lm -ldl
|
||||
|
||||
GIT_HASH := $(shell [ -d .git ] && git rev-parse HEAD)
|
||||
ifneq ($(GIT_HASH),)
|
||||
MOLD_CXXFLAGS += -DGIT_HASH=\"$(GIT_HASH)\"
|
||||
endif
|
||||
|
||||
LTO = 0
|
||||
ifeq ($(LTO), 1)
|
||||
CPPFLAGS += -flto -O3
|
||||
CXXFLAGS += -flto -O3
|
||||
LDFLAGS += -flto
|
||||
endif
|
||||
|
||||
ifeq ($(ASAN), 1)
|
||||
CPPFLAGS += -fsanitize=address
|
||||
LDFLAGS += -fsanitize=address
|
||||
else
|
||||
# By default, we want to use mimalloc as a memory allocator.
|
||||
# Since replacing the standard malloc is not compatible with ASAN,
|
||||
# we do that only when ASAN is not enabled.
|
||||
LDFLAGS += -Wl,-whole-archive $(MIMALLOC_LIB) -Wl,-no-whole-archive
|
||||
# By default, we want to use mimalloc as a memory allocator. mimalloc
|
||||
# is disabled on macOS and Android because it didn't work on those hosts.
|
||||
USE_MIMALLOC = 1
|
||||
ifeq ($(OS), Darwin)
|
||||
USE_MIMALLOC = 0
|
||||
else ifeq ($(IS_ANDROID), 1)
|
||||
USE_MIMALLOC = 0
|
||||
endif
|
||||
|
||||
ifeq ($(TSAN), 1)
|
||||
CPPFLAGS += -fsanitize=thread
|
||||
LDFLAGS += -fsanitize=thread
|
||||
ifeq ($(USE_MIMALLOC), 1)
|
||||
ifdef SYSTEM_MIMALLOC
|
||||
MOLD_CXXFLAGS += -DUSE_SYSTEM_MIMALLOC
|
||||
MOLD_LDFLAGS += -lmimalloc
|
||||
else
|
||||
MIMALLOC_LIB = out/mimalloc/libmimalloc.a
|
||||
MOLD_CXXFLAGS += -Ithird-party/mimalloc/include
|
||||
MOLD_LDFLAGS += -Wl,-whole-archive $(MIMALLOC_LIB) -Wl,-no-whole-archive
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef SYSTEM_TBB
|
||||
MOLD_LDFLAGS += -ltbb
|
||||
else
|
||||
TBB_LIB = out/tbb/libs/libtbb.a
|
||||
MOLD_LDFLAGS += $(TBB_LIB)
|
||||
MOLD_CXXFLAGS += -Ithird-party/tbb/include
|
||||
endif
|
||||
|
||||
ifeq ($(OS), Linux)
|
||||
ifeq ($(IS_ANDROID), 0)
|
||||
# glibc before 2.17 need librt for clock_gettime
|
||||
MOLD_LDFLAGS += -Wl,-push-state -Wl,-as-needed -lrt -Wl,-pop-state
|
||||
endif
|
||||
endif
|
||||
|
||||
NEEDS_LIBCRYPTO = 0
|
||||
ifneq ($(OS), Darwin)
|
||||
NEEDS_LIBCRYPTO = 1
|
||||
endif
|
||||
|
||||
ifeq ($(NEEDS_LIBCRYPTO), 1)
|
||||
MOLD_CXXFLAGS += $(shell $(PKG_CONFIG) --cflags-only-I openssl)
|
||||
MOLD_LDFLAGS += $(shell $(PKG_CONFIG) --libs-only-L openssl) -lcrypto
|
||||
endif
|
||||
|
||||
# '-latomic' flag is needed building on riscv64 system
|
||||
# RV32 system doesn't tested yet
|
||||
# seems like '-atomic' would be better but not working.
|
||||
ifeq ($(ARCH), riscv64)
|
||||
MOLD_LDFLAGS += -latomic
|
||||
endif
|
||||
|
||||
# -Wc++11-narrowing is a fatal error on Android, so disable it.
|
||||
ifeq ($(IS_ANDROID), 1)
|
||||
MOLD_CXXFLAGS += -Wno-c++11-narrowing
|
||||
endif
|
||||
|
||||
ifeq ($(OS), Linux)
|
||||
MOLD_WRAPPER_LDFLAGS = -Wl,-push-state -Wl,-no-as-needed -ldl -Wl,-pop-state
|
||||
endif
|
||||
|
||||
DEPFLAGS = -MT $@ -MMD -MP -MF out/$*.d
|
||||
|
||||
all: mold mold-wrapper.so
|
||||
|
||||
mold: $(OBJS) $(MIMALLOC_LIB)
|
||||
$(CXX) $(CFLAGS) $(OBJS) -o $@ $(LDFLAGS) $(LIBS)
|
||||
-include $(SRCS:%.cc=out/%.d)
|
||||
|
||||
mold-wrapper.so: mold-wrapper.c Makefile
|
||||
$(CC) -fPIC -shared -o $@ $< -ldl
|
||||
mold: $(OBJS) $(MIMALLOC_LIB) $(TBB_LIB)
|
||||
$(CXX) $(OBJS) -o $@ $(MOLD_LDFLAGS) $(LDFLAGS)
|
||||
ln -sf mold ld
|
||||
ln -sf mold ld64.mold
|
||||
|
||||
$(OBJS): mold.h elf.h Makefile
|
||||
mold-wrapper.so: elf/mold-wrapper.c
|
||||
$(CC) $(DEPFLAGS) $(CFLAGS) -fPIC -shared -o $@ $< $(MOLD_WRAPPER_LDFLAGS) $(LDFLAGS)
|
||||
|
||||
out/%.o: %.cc out/elf/.keep out/macho/.keep
|
||||
$(CXX) $(MOLD_CXXFLAGS) $(DEPFLAGS) $(CXXFLAGS) -c -o $@ $<
|
||||
|
||||
out/elf/.keep out/macho/.keep:
|
||||
mkdir -p $(@D)
|
||||
touch $@
|
||||
|
||||
$(MIMALLOC_LIB):
|
||||
mkdir -p mimalloc/out/release
|
||||
(cd mimalloc/out/release; CFLAGS=-DMI_USE_ENVIRON=0 cmake ../..)
|
||||
$(MAKE) -C mimalloc/out/release
|
||||
mkdir -p out/mimalloc
|
||||
(cd out/mimalloc; CFLAGS=-DMI_USE_ENVIRON=0 cmake -G'Unix Makefiles' ../../third-party/mimalloc)
|
||||
$(MAKE) -C out/mimalloc mimalloc-static
|
||||
|
||||
$(TBB_LIB):
|
||||
mkdir -p out/tbb
|
||||
(cd out/tbb; cmake -G'Unix Makefiles' -DBUILD_SHARED_LIBS=OFF -DTBB_TEST=OFF -DCMAKE_CXX_FLAGS="$(CXXFLAGS) -D__TBB_DYNAMIC_LOAD_ENABLED=0" -DTBB_STRICT=OFF ../../third-party/tbb)
|
||||
$(MAKE) -C out/tbb tbb
|
||||
(cd out/tbb; ln -sf *_relwithdebinfo libs)
|
||||
|
||||
test tests check: all
|
||||
$(MAKE) -C test --output-sync --no-print-directory
|
||||
ifeq ($(OS), Darwin)
|
||||
$(MAKE) -C test -f Makefile.darwin --no-print-directory
|
||||
else
|
||||
$(MAKE) -C test -f Makefile.linux --no-print-directory --output-sync
|
||||
endif
|
||||
@if test -t 1; then \
|
||||
printf '\e[32mPassed all tests\e[0m\n'; \
|
||||
else \
|
||||
echo 'Passed all tests'; \
|
||||
fi
|
||||
|
||||
test-x86-64: all
|
||||
@echo x86_64
|
||||
CC=x86_64-linux-gnu-gcc CXX=x86_64-linux-gnu-g++ GCC=x86_64-linux-gnu-gcc GXX=x86_64-linux-gnu-g++ OBJDUMP=x86_64-linux-gnu-objdump MACHINE=x86_64 QEMU='qemu-x86_64 -L /usr/x86_64-linux-gnu' $(MAKE) test
|
||||
|
||||
test-i386: all
|
||||
@echo i386
|
||||
CC=i686-linux-gnu-gcc CXX=i686-linux-gnu-g++ GCC=i686-linux-gnu-gcc GXX=i686-linux-gnu-g++ OBJDUMP=x86_64-linux-gnu-objdump MACHINE=i386 QEMU='qemu-i386 -L /usr/i686-linux-gnu' $(MAKE) test
|
||||
|
||||
test-arm64: all
|
||||
@echo arm64
|
||||
CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ GCC=aarch64-linux-gnu-gcc GXX=aarch64-linux-gnu-g++ OBJDUMP=aarch64-linux-gnu-objdump MACHINE=aarch64 QEMU='qemu-aarch64 -L /usr/aarch64-linux-gnu' $(MAKE) test
|
||||
|
||||
test-arm32: all
|
||||
@echo arm
|
||||
CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ GCC=arm-linux-gnueabihf-gcc GXX=arm-linux-gnueabihf-g++ OBJDUMP=arm-linux-gnueabihf-objdump MACHINE=arm QEMU='qemu-arm -L /usr/arm-linux-gnueabihf' $(MAKE) test
|
||||
|
||||
test-riscv64: all
|
||||
@echo riscv64
|
||||
CC=riscv64-linux-gnu-gcc CXX=riscv64-linux-gnu-g++ GCC=riscv64-linux-gnu-gcc GXX=riscv64-linux-gnu-g++ OBJDUMP=riscv64-linux-gnu-objdump MACHINE=riscv64 QEMU='qemu-riscv64 -L /usr/riscv64-linux-gnu' $(MAKE) test
|
||||
|
||||
|
||||
test-all:
|
||||
$(MAKE) test-x86-64
|
||||
$(MAKE) test-i386
|
||||
$(MAKE) test-arm64
|
||||
$(MAKE) test-arm32
|
||||
$(MAKE) test-riscv64
|
||||
|
||||
install: all
|
||||
install -m 755 mold $(PREFIX)/bin
|
||||
strip $(PREFIX)/bin/mold
|
||||
$(INSTALL) -d $D$(BINDIR)
|
||||
$(INSTALL_PROGRAM) mold $D$(BINDIR)
|
||||
$(STRIP) $D$(BINDIR)/mold
|
||||
|
||||
install -m 755 -d $(PREFIX)/lib/mold
|
||||
install -m 644 mold-wrapper.so $(PREFIX)/lib/mold
|
||||
strip $(PREFIX)/lib/mold/mold-wrapper.so
|
||||
$(INSTALL) -d $D$(LIBDIR)/mold
|
||||
$(INSTALL_DATA) mold-wrapper.so $D$(LIBDIR)/mold
|
||||
$(STRIP) $D$(LIBDIR)/mold/mold-wrapper.so
|
||||
|
||||
install -m 644 docs/mold.1 $(PREFIX)/share/man/man1
|
||||
rm -f $(PREFIX)/share/man/man1/mold.1.gz
|
||||
gzip -9 $(PREFIX)/share/man/man1/mold.1
|
||||
$(INSTALL) -d $D$(LIBEXECDIR)/mold
|
||||
ln -sf $(BINDIR)/mold $D$(LIBEXECDIR)/mold/ld
|
||||
|
||||
$(INSTALL) -d $D$(MANDIR)/man1
|
||||
$(INSTALL_DATA) docs/mold.1 $D$(MANDIR)/man1
|
||||
|
||||
ln -sf mold $D$(BINDIR)/ld.mold
|
||||
ln -sf mold $D$(BINDIR)/ld64.mold
|
||||
|
||||
uninstall:
|
||||
rm -rf $(PREFIX)/bin/mold $(PREFIX)/share/man/man1/mold.1.gz \
|
||||
$(PREFIX)/lib/mold
|
||||
rm -f $D$(BINDIR)/mold $D$(BINDIR)/ld.mold $D$(BINDIR)/ld64.mold
|
||||
rm -f $D$(MANDIR)/man1/mold.1
|
||||
rm -rf $D$(LIBDIR)/mold
|
||||
|
||||
clean:
|
||||
rm -rf *.o *~ mold mold-wrapper.so test/tmp
|
||||
rm -rf *~ mold mold-wrapper.so out ld ld64.mold mold-*-linux.tar.gz
|
||||
|
||||
.PHONY: all test tests check clean $(TESTS)
|
||||
.PHONY: all test tests check clean test-x86-64 test-i386 test-arm64 test-arm32 test-riscv64 test-all
|
||||
@@ -1,116 +1,190 @@
|
||||
# mold: A Modern Linker
|
||||
|
||||

|
||||
mold is a faster drop-in replacement for existing Unix linkers.
|
||||
It is several times faster than the LLVM lld linker, the second-fastest
|
||||
open-source linker which I originally created a few years ago.
|
||||
mold is designed to increase developer productivity by reducing
|
||||
build time, especially in rapid debug-edit-rebuild cycles.
|
||||
|
||||
mold is a high performance drop-in replacement for existing Unix
|
||||
linkers. It is several times faster than LLVM lld linker, the (then-)
|
||||
fastest open-source linker which I originally created a few years ago.
|
||||
Here is a performance comparison of GNU gold, LLVM lld and mold for
|
||||
linking final executables of major large programs.
|
||||
Here is a performance comparison of GNU gold, LLVM lld, and mold for
|
||||
linking final debuginfo-enabled executables of major large programs
|
||||
on a simulated 8-core 16-threads machine.
|
||||
|
||||
| Program (linker output size) | GNU gold | LLVM lld | mold | mold w/ preloading
|
||||
|-------------------------------|----------|----------|-------|-------------------
|
||||
| Firefox 87 (1.6 GiB) | 29.2s | 6.16s | 1.69s | 0.79s
|
||||
| Chrome 86 (1.9 GiB) | 54.5s | 11.7s | 1.85s | 0.97s
|
||||
| Clang 13 (3.1 GiB) | 59.4s | 5.68s | 2.76s | 0.86s
|
||||

|
||||
|
||||
(These nubmers are measured on an AMD Threadripper 3990X 64-core
|
||||
machine with 32 threads enabled. All programs are built with debug
|
||||
info enabled.)
|
||||
| Program (linker output size) | GNU gold | LLVM lld | mold
|
||||
|-------------------------------|----------|----------|--------
|
||||
| Chrome 96 (1.89 GiB) | 53.86s | 11.74s | 2.21s
|
||||
| Clang 13 (3.18 GiB) | 64.12s | 5.82s | 2.90s
|
||||
| Firefox 89 libxul (1.64 GiB) | 32.95s | 6.80s | 1.42s
|
||||
|
||||
Let me explain the "w/ preloading" column. mold supports the file
|
||||
preloading feature. That is, if you run mold with `-preload` flag
|
||||
along with other command line flags, it becomes a daemon and halts
|
||||
after parsing input files. Then, if you invoke mold with the same
|
||||
command line options (except `-preload` flag), it tells the daemon to
|
||||
reload only updated files and proceed. With this feature enabled, and
|
||||
if most of the input files haven't been updated, mold achieve a
|
||||
near-`cp` performance or even exceeds it, as the throughput of file
|
||||
copy using the `cp` command is about 2 GiB/s on my machine.
|
||||
mold is so fast that it is only 2x _slower_ than `cp` on the same
|
||||
machine. Feel free to [file a bug](https://github.com/rui314/mold/issues)
|
||||
if you find mold is not faster than other linkers.
|
||||
|
||||
So, mold is extremely fast per-se and even faster with a bit of cheating.
|
||||
mold currently supports x86-64, i386, ARM32, ARM64 and 64-bit RISC-V.
|
||||
|
||||
Why is mold so fast? One reason is because it simply uses faster
|
||||
algorithms and efficient data structures than other linkers do.
|
||||
The other reason is that the new linker is highly parallelized.
|
||||
## Why does the speed of linking matter?
|
||||
|
||||
Here is a side-by-side comparison of per-core CPU usage of lld (left)
|
||||
and mold (right). They are linking the same program, Chromium
|
||||
executable.
|
||||
If you are using a compiled language such as C, C++ or Rust, a build
|
||||
consists of two phases. In the first phase, a compiler compiles a
|
||||
source file into an object file (`.o` files). In the second phase,
|
||||
a linker takes all object files to combine them into a single executable
|
||||
or a shared library file.
|
||||
|
||||

|
||||
The second phase takes a long time if your build output is large.
|
||||
mold can make it faster, saving your time and keeping you from being
|
||||
distracted while waiting for a long build to finish. The difference is
|
||||
most noticeable when you are in rapid debug-edit-rebuild cycles.
|
||||
|
||||
As you can see, mold uses all available cores throughout its execution
|
||||
and finishes quickly. On the other hand, lld failed to use available
|
||||
cores most of the time. On this demo, the maximum parallelism is
|
||||
artificially capped to 16 so that the bars fit in the GIF.
|
||||
## Install
|
||||
|
||||
Currently, mold is being developed with Linux/x86-64 as the primary
|
||||
target platform. mold can link many user-land programs including large
|
||||
ones such as web browsers for that target. It also has preliminary
|
||||
Linux/i386 support. Supporting other OSes and ISAs are planned after
|
||||
Linux/x86-64 support is complete.
|
||||
Binary packages for the following distros are currently available.
|
||||
|
||||
[](https://repology.org/project/mold/versions)
|
||||
|
||||
## How to build
|
||||
|
||||
mold is written in C++20, so you need a very recent version of GCC or
|
||||
Clang. I'm using Ubuntu 20.04 as a development platform. In that
|
||||
environment, you can build mold by the following commands.
|
||||
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.
|
||||
|
||||
```
|
||||
$ sudo apt-get install build-essential libstdc++-10-dev cmake clang libssl-dev zlib1g-dev libxxhash-dev libtbb-dev git
|
||||
$ git clone https://github.com/rui314/mold.git
|
||||
$ cd mold
|
||||
$ make
|
||||
I'm using Ubuntu 20.04 as a development platform. In that environment,
|
||||
you can build mold by the following commands.
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
The last `make` command creates `mold` executable.
|
||||
#### Fedora 34 and later
|
||||
|
||||
If you don't have Ubuntu 20.04, 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 `./build-static.sh` in this
|
||||
directory. The script creates a Ubuntu 20.04 Docker image, install
|
||||
necessary tools and libraries to it and build mold as a static binary.
|
||||
```shell
|
||||
sudo dnf install -y git clang cmake openssl-devel xxhash-devel zlib-devel libstdc++-devel
|
||||
```
|
||||
|
||||
### Compile mold
|
||||
|
||||
```shell
|
||||
git clone https://github.com/rui314/mold.git
|
||||
cd mold
|
||||
git checkout v1.2.0
|
||||
make -j$(nproc) CXX=clang++
|
||||
sudo make install
|
||||
```
|
||||
|
||||
You may need to pass a C++20 compiler command name to `make`.
|
||||
In the above case, `clang++` is passed. If it doesn't work for you,
|
||||
try a specific version of a compiler such as `g++-10` or `clang++-12`.
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
$ sudo dpkg --add-architecture i386
|
||||
$ sudo apt-get install dwarfdump libc6-dev:i386 lib32gcc-10-dev gcc-multilib
|
||||
```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
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
On Unix, the linker command (which is usually `/usr/bin/ld`) is
|
||||
invoked indirectly by `cc` (or `gcc` or `clang`), which is typically
|
||||
in turn indirectly invoked by `make` or some other build system
|
||||
command. It is sometimes very hard to pass an appropriate command line
|
||||
option to `cc` to specify an alternative linker.
|
||||
<details><summary>A classic way to use mold</summary>
|
||||
|
||||
To deal with the situation, mold has a feature to intercept all
|
||||
invocations of `/usr/bin/ld`, `/usr/bin/ld.lld` or `/usr/bin/ld.gold`
|
||||
and redirect it to itself. To use the feature, run `make` (or other
|
||||
build command) as a subcommand of mold as follows:
|
||||
On Unix, the linker command (which is usually `/usr/bin/ld`) is
|
||||
invoked indirectly by the compiler driver (which is usually `cc`,
|
||||
`gcc` or `clang`), which is typically in turn indirectly invoked by
|
||||
`make` or some other build system command.
|
||||
|
||||
If you can specify an additional command line option to your compiler
|
||||
driver by modifying build system's config files, add one of the
|
||||
following flags to use `mold` instead of `/usr/bin/ld`:
|
||||
|
||||
- Clang: pass `-fuse-ld=mold`
|
||||
|
||||
- GCC 12.1.0 (upcoming version) or later: pass `-fuse-ld=mold`
|
||||
|
||||
- GCC before 12.1.0: `-fuse-ld` does not accept `mold` as a valid
|
||||
argument, so you need to use `-B` option instead. `-B` is an option
|
||||
to tell GCC where to look for external commands such as `ld`.
|
||||
|
||||
If you have installed mold with `make install`, there should be a
|
||||
directory named `/usr/libexec/mold` (or `/usr/local/libexec/mold`,
|
||||
depending on your $PREFIX), and `ld` command should be there. The
|
||||
`ld` is actually a symlink to `mold`. So, all you need is to pass
|
||||
`-B/usr/libexec/mold` (or `-B/usr/local/libexec/mold`) to GCC.
|
||||
|
||||
If you haven't installed `mold` to any `$PATH`, you can still pass
|
||||
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. GCC does not
|
||||
take an absolute path as an argument for `-fuse-ld` though.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>If you are using Rust</summary>
|
||||
|
||||
Create `.cargo/config.toml` in your project directory with the following:
|
||||
|
||||
```
|
||||
$ path/to/mold -run make <make-options-if-any>
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
|
||||
```
|
||||
|
||||
where `/path/to/mold` is an absolute path to `mold` exectuable.
|
||||
Please make sure you have installed `clang`.
|
||||
|
||||
If you want to use mold for all projects, put the above snippet to
|
||||
`~/.cargo/config.toml`.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>mold -run</summary>
|
||||
|
||||
It is sometimes very hard to pass an appropriate command line option
|
||||
to `cc` to specify an alternative linker. To deal with the situation,
|
||||
mold has a feature to intercept all invocations of `ld`, `ld.lld` or
|
||||
`ld.gold` and redirect it to itself. To use the feature, run `make`
|
||||
(or another build command) as a subcommand of mold as follows:
|
||||
|
||||
```shell
|
||||
mold -run make <make-options-if-any>
|
||||
```
|
||||
|
||||
Internally, mold invokes a given command with `LD_PRELOAD` environment
|
||||
variable set to its companion shared object file. The shared object
|
||||
file intercepts all function calls to exec-family functions to replace
|
||||
`argv[0]` with `mold` if it is `/usr/bin/ld`, `/usr/bin/ld.gold` or
|
||||
`/usr/bin/ld.lld`.
|
||||
file intercepts all function calls to `exec(3)`-family functions to
|
||||
replace `argv[0]` with `mold` if it is `ld`, `ld.gold` or `ld.lld`.
|
||||
|
||||
Alternatively, you can pass `-fuse-ld=<absolute-path-to-mold-executable>`
|
||||
to a linker command line. Since GCC doesn't support that option,
|
||||
I recommend using clang.
|
||||
</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>
|
||||
|
||||
<details><summary>Verify that you are using mold</summary>
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
$ readelf -p .comment <executable-file>
|
||||
```shell
|
||||
readelf -p .comment <executable-file>
|
||||
|
||||
String dump of section '.comment':
|
||||
[ 0] GCC: (Ubuntu 10.2.0-5ubuntu1~20.04) 10.2.0
|
||||
@@ -119,503 +193,27 @@ String dump of section '.comment':
|
||||
|
||||
If `mold` is in `.comment`, the file is created by mold.
|
||||
|
||||
# Design and implementation of mold
|
||||
</details>
|
||||
|
||||
For the rest of this documentation, I'll explain the design and the
|
||||
implementation of mold. If you are only interested in using mold, you
|
||||
don't need to read the below.
|
||||
## Why is mold so fast?
|
||||
|
||||
## Motivation
|
||||
One reason is because it simply uses faster algorithms and efficient
|
||||
data structures than other linkers do. The other reason is that the
|
||||
new linker is highly parallelized.
|
||||
|
||||
Here is why I'm writing a new linker:
|
||||
Here is a side-by-side comparison of per-core CPU usage of lld (left)
|
||||
and mold (right). They are linking the same program, Chromium
|
||||
executable.
|
||||
|
||||
- Even though lld has significantly improved the situation, linking is
|
||||
still one of the slowest steps in a build. It is especially
|
||||
annoying when I changed one line of code and had to wait for a few
|
||||
seconds or even more for a linker to complete. It should be
|
||||
instantaneous. There's a need for a faster linker.
|
||||

|
||||
|
||||
- The number of cores on a PC has increased a lot lately, and this
|
||||
trend is expected to continue. However, the existing linkers can't
|
||||
take the advantage of the trend because they don't scale well for more
|
||||
cores. I have a 64-core/128-thread machine, so my goal is to create
|
||||
a linker that uses the CPU nicely. mold should be much faster than
|
||||
other linkers on 4 or 8-core machines too, though.
|
||||
As you can see, mold uses all available cores throughout its execution
|
||||
and finishes quickly. On the other hand, lld failed to use available
|
||||
cores most of the time. In this demo, the maximum parallelism is
|
||||
artificially capped to 16 so that the bars fit in the GIF.
|
||||
|
||||
- It looks to me that the designs of the existing linkers are somewhat
|
||||
too similar, and I believe there are a lot of drastically different
|
||||
designs that haven't been explored yet. Developers generally don't
|
||||
care about linkers as long as they work correctly, and they don't
|
||||
even think about creating a new one. So there may be lots of low
|
||||
hanging fruits there in this area.
|
||||
For details, please read [design notes](docs/design.md).
|
||||
|
||||
## Basic design
|
||||
# Logo
|
||||
|
||||
- In order to achieve a `cp`-like performance, the most important
|
||||
thing is to fix the layout of an output file as quickly as possible, so
|
||||
that we can start copying actual data from input object files to an
|
||||
output file as soon as possible.
|
||||
|
||||
- Copying data from input files to an output file is I/O-bounded, so
|
||||
there should be room for doing computationally-intensive tasks while
|
||||
copying data from one file to another.
|
||||
|
||||
- We should allow the linker to preload object files from disk and
|
||||
parse them in memory before a complete set of input object files
|
||||
is ready. To do so, we need
|
||||
to split the linker into two in such a way that the latter half of
|
||||
the process finishes as quickly as possible by speculatively parsing
|
||||
and preprocessing input files in the first half of the process.
|
||||
|
||||
- One of the most computationally-intensive stage among linker stages
|
||||
is symbol resolution. To resolve symbols, we basically have to throw
|
||||
all symbol strings into a hash table to match undefined symbols with
|
||||
defined symbols. But this can be done in the preloading stage using
|
||||
[string interning](https://en.wikipedia.org/wiki/String_interning).
|
||||
|
||||
- Object files may contain a special section called a mergeable string
|
||||
section. The section contains lots of null-terminated strings, and
|
||||
the linker is expected to gather all mergeable string sections and
|
||||
merge their contents. So, if two object files contain the same
|
||||
string literal, for example, the resulting output will contain a
|
||||
single merged string. This step is computationally-intensive, but string
|
||||
merging can be done in the preloading stage using string interning.
|
||||
|
||||
- Static archives (.a files) contain object files, but the static
|
||||
archive's string table contains only defined symbols of member
|
||||
object files and lacks other types of symbols. That makes static
|
||||
archives unsuitable for speculative parsing. Therefore, the linker
|
||||
should ignore the symbol table of static archive and directly read
|
||||
static archive members.
|
||||
|
||||
- If there's a relocation that uses a GOT of a symbol, then we have to
|
||||
create a GOT entry for that symbol. Otherwise, we shouldn't. That
|
||||
means we need to scan all relocation tables to fix the length and
|
||||
the contents of a .got section. This is computationally intensive,
|
||||
but this step is parallelizable.
|
||||
|
||||
## Compatibility
|
||||
|
||||
- GNU ld, GNU gold and LLVM lld support essentially the same set of
|
||||
command line options and features. mold doesn't have to be
|
||||
completely compatible with them. As long as it can be used for
|
||||
linking large user-land programs, I'm fine with that. It is OK to
|
||||
leave some command line options unimplemented; if mold is blazingly
|
||||
fast, other projects would still be happy to adopt it by modifying
|
||||
their projects' build files.
|
||||
|
||||
- mold emits Linux executables and runs only on Linux. I won't avoid
|
||||
Unix-ism when writing code. I don't want to think about portability
|
||||
until mold becomes a thing that's worth to be ported.
|
||||
|
||||
## Linker Script
|
||||
|
||||
Linker script is an embedded language for the linker. It is mainly
|
||||
used to control how input sections are mapped to output sections and
|
||||
the layout of the output, but it can also do a lot of tricky stuff.
|
||||
Its feature is useful especially for embedded programming, but it's
|
||||
also an awfully underdocumented and complex language.
|
||||
|
||||
We have to implement a subset of the linker script language anwyay,
|
||||
because on Linux, /usr/lib/x86_64-linux-gnu/libc.so is (despite its
|
||||
name) not a shared object file but actually an ASCII file containing
|
||||
linker script code to load the _actual_ libc.so file. But the feature
|
||||
set for this purpose is very limited, and it is okay to implement them
|
||||
to mold.
|
||||
|
||||
Besides that, we really don't want to implement the linker script
|
||||
langauge. But at the same time, we want to satisfy the user needs that
|
||||
are currently satisfied with the linker script langauge. So, what
|
||||
should we do? Here is my observation:
|
||||
|
||||
- Linker script allows to do a lot of tricky stuff, such as specifying
|
||||
the exact layout of a file, inserting arbitrary bytes between
|
||||
sections, etc. But most of them can be done with a post-link binary
|
||||
editing tool (such as `objcopy`).
|
||||
|
||||
- It looks like there are two things that truely cannot be done by a
|
||||
post-link editing tool: (a) mapping input sections to output
|
||||
sections, and (b) applying relocations.
|
||||
|
||||
From the above observation, I believe we need to provide only the
|
||||
following features instead of the entire linker script langauge:
|
||||
|
||||
- A method to specify how input sections are mapped to output
|
||||
sections, and
|
||||
|
||||
- a method to set addresses to output sections, so that relocations
|
||||
are applied based on desired adddresses.
|
||||
|
||||
I believe everything else can be done with a post-link binary editing
|
||||
tool.
|
||||
|
||||
## Details
|
||||
|
||||
- As we aim to the 1 second goal for Chromium, every millisecond
|
||||
counts. We can't ignore the latency of process exit. If we mmap a
|
||||
lot of files, \_exit(2) is not instantaneous but takes a few hundred
|
||||
milliseconds because the kernel has to clean up a lot of
|
||||
resources. As a workaround, we should organize the linker command as
|
||||
two processes; the first process forks the second process, and the
|
||||
second process does the actual work. As soon as the second process
|
||||
writes a result file to a filesystem, it notifies the first process,
|
||||
and the first process exits. The second process can take time to
|
||||
exit, because it is not an interactive process.
|
||||
|
||||
- At least on Linux, it looks like the filesystem's performance to
|
||||
allocate new blocks to a new file is the limiting factor when
|
||||
creating a new large file and filling its contents using mmap.
|
||||
If you already have a large file in the buffer cache, writing to it is
|
||||
much faster than creating a new fresh file and writing to it.
|
||||
Based on this observation, mold overwrites to an existing
|
||||
executable file if exists. My quick benchmark showed that I could
|
||||
save 300 milliseconds when creating a 2 GiB output file.
|
||||
Linux doesn't allow to open an executable for writing if it is
|
||||
running (you'll get "text busy" error if you attempt). mold
|
||||
falls back to the usual way if it fails to open an output file.
|
||||
|
||||
- The output from the linker should be deterministic for the sake of
|
||||
[build reproducibility](https://en.wikipedia.org/wiki/Reproducible_builds)
|
||||
and ease of debugging. This might add a little bit of overhead to
|
||||
the linker, but that shouldn't be too much.
|
||||
|
||||
- A .build-id, a unique ID embedded to an output file, is usually
|
||||
computed by applying a cryptographic hash function (e.g. SHA-1) to
|
||||
an output file. This is a slow step, but we can speed it up by
|
||||
splitting a file into small chunks, computing SHA-1 for each chunk,
|
||||
and then computing SHA-1 of the concatenated SHA-1 hashes
|
||||
(i.e. constructing a [Markle
|
||||
Tree](https://en.wikipedia.org/wiki/Merkle_tree) of height 2).
|
||||
Modern x86 processors have purpose-built instructions for SHA-1 and
|
||||
can compute SHA-1 pretty quickly at about 2 GiB/s rate. Using 16
|
||||
cores, a build-id for a 2 GiB executable can be computed in 60 to 70
|
||||
milliseconds.
|
||||
|
||||
- BFD, gold, and lld support section garbage collection. That is, a
|
||||
linker runs a mark-sweep garbage collection on an input graph, where
|
||||
sections are vertices and relocations are edges, to discard all
|
||||
sections that are not reachable from the entry point symbol
|
||||
(i.e. `_start`) or a few other root sections. In mold, we are using
|
||||
multiple threads to mark sections concurrently.
|
||||
|
||||
- Similarly, BFD, gold an lld support Identical Comdat Folding (ICF)
|
||||
as a yet another size optimization. ICF merges two or more read-only
|
||||
sections that happen to have the same contents and relocations.
|
||||
To do that, we have to find isomorphic subgraphs from larger graphs.
|
||||
I implemented a new algorithm for mold, which is 5x faster than lld
|
||||
to do ICF for Chromium (from 5 seconds to 1 second).
|
||||
|
||||
- [Intel Threading Building
|
||||
Blocks](https://github.com/oneapi-src/oneTBB) (TBB) is a good
|
||||
library for parallel execution and has several concurrent
|
||||
containers. We are particularly interested in using
|
||||
`parallel_for_each` and `concurrent_hash_map`.
|
||||
|
||||
- TBB provides `tbbmalloc` which works better for multi-threaded
|
||||
applications than the glib'c malloc, but it looks like
|
||||
[jemalloc](https://github.com/jemalloc/jemalloc) and
|
||||
[mimalloc](https://github.com/microsoft/mimalloc) are a little bit
|
||||
more scalable than `tbbmalloc`.
|
||||
|
||||
## Size of the problem
|
||||
|
||||
When linking Chrome, a linker reads 3,430,966,844 bytes of data in
|
||||
total. The data contains the following items:
|
||||
|
||||
| Data item | Number
|
||||
| ------------------------ | ------
|
||||
| Object files | 30,723
|
||||
| Public undefined symbols | 1,428,149
|
||||
| Mergeable strings | 1,579,996
|
||||
| Comdat groups | 9,914,510
|
||||
| Regular sections¹ | 10,345,314
|
||||
| Public defined symbols | 10,512,135
|
||||
| Symbols | 23,953,607
|
||||
| Sections | 27,543,225
|
||||
| Relocations against SHF_ALLOC sections | 39,496,375
|
||||
| Relocations | 62,024,719
|
||||
|
||||
¹ Sections that have to be copied from input object files to an
|
||||
output file. Sections that contain relocations or symbols are for
|
||||
example excluded.
|
||||
|
||||
## Internals
|
||||
|
||||
In this section, I'll explain the internals of mold linker.
|
||||
|
||||
### A brief history of Unix and the Unix linker
|
||||
|
||||
Conceptually, what a linker does is pretty simple. A compiler compiles
|
||||
a fragment of a program (a single source file) into a fragment of
|
||||
machine code and data (an object file, which typically has the .o
|
||||
extension), and a linker stiches them together into a single
|
||||
executable or a shared library image.
|
||||
|
||||
In reality, modern linkers for Unix-like systems are much more
|
||||
compilcated than the naive understanding because they have gradually
|
||||
gained one feature at a time over the 50 years history of Unix, and
|
||||
they are now something like a bag of lots of miscellaneous features in
|
||||
which none of the features is more important than the others. It is
|
||||
very easy to miss the forest for the trees, since for those who don't
|
||||
know the details of the Unix linker, it is not clear which feature is
|
||||
essential and which is not.
|
||||
|
||||
That being said, one thing is clear that at any point of Unix history,
|
||||
a Unix linker has a coherent feature set for the Unix of that age. So,
|
||||
let me entangle the history to see how the operating system, runtime
|
||||
and linker have gained features that we see today. That should give
|
||||
you an idea why a particular feature has been added to a linker in the
|
||||
first place.
|
||||
|
||||
1. Original Unix didn't support shared library, and a program was
|
||||
always loaded to a fixed address. An executable was something like
|
||||
a memory dump which was just loaded to a particular address by the
|
||||
kernel. After loading, the kernel started executing the program by
|
||||
setting the instruction pointer to a particular address.
|
||||
|
||||
The most essential feature for any linker is relocation processing.
|
||||
The original Unix linker of course supported that. Let me explain
|
||||
what that is.
|
||||
|
||||
Individual object files are inevitably incomplete as a program,
|
||||
because when a compiler created them, it only see a part of an
|
||||
entire program. For example, if an object file contains a function
|
||||
call that refers other object file, the `call` instruction in the
|
||||
object cannot be complete, as the compiler has no idea as to what
|
||||
is the called function's address. To deal with this, the compiler
|
||||
emits a placeholder value (typically just zero) instead of a real
|
||||
address and leave a metadata in an object file saying "fix offset X
|
||||
of this file with an address of Y". That metadata is called
|
||||
"relocation". Relocations are typically processed by the linker.
|
||||
|
||||
It is easy for a linker to apply relocations for the original Unix
|
||||
because a program is always loaded to a fixed address. It exactly
|
||||
knows the addresses of all functions and data when linking a
|
||||
program.
|
||||
|
||||
Static library support, which is still an important feature of Unix
|
||||
linker, also dates back to this early period of Unix history.
|
||||
To understand what it is, imagine that you are trying to compile
|
||||
a program for the early Unix. You don't want to waste time to
|
||||
compile libc functions every time you compile your program (the
|
||||
computers of the era was incredibly slow), so you have already
|
||||
placed each libc function into a separate source file and compiled
|
||||
them individually. That means, you have object files for each libc
|
||||
function, e.g., printf.o, scanf.o, atoi.o, write.o, etc.
|
||||
|
||||
Given this configuration, all you have to do to link your program
|
||||
against libc functions is to pick up a right set of libc object
|
||||
files and give them to the linker along with the object files of your
|
||||
program. But, keeping the linker command line in sync with the
|
||||
libc functions you are using in your program is bothersome. You can
|
||||
be conservative; you can specify all libc object files to the
|
||||
command line, but that leads to program bloat because the linker
|
||||
unconditionally link all object files given to it no matter whether
|
||||
they are used or not. So, a new feature was added to the linker to
|
||||
fix the problem. That is the static library, which is also called
|
||||
the archive file.
|
||||
|
||||
An archive file is just a bundle of object files, just like zip
|
||||
file but in an uncompressed form. An achive file typically has the
|
||||
.a file extension and named after its contents. For example, the
|
||||
archive file containing all libc objects is named `libc.a`.
|
||||
|
||||
If you pass an archive file along with other object files to the
|
||||
linker, the linker pulls out an object file from the archive _only
|
||||
when_ it is referenced by other object files. In other words,
|
||||
unlike object files directly given to a linker, object files
|
||||
wrapped in an archive are not linked to an output by default.
|
||||
An archive works as supplements to complete your program.
|
||||
|
||||
Even today, you can still find a libc archive file. Run `ar t
|
||||
/usr/lib/x86_64-linux-gnu/libc.a` on Linux should give you a list
|
||||
of object files in the libc archive.
|
||||
|
||||
2. In '80s, Sun Microsystems, a leading commercial Unix vendor at the
|
||||
time, added a shared library support to their Unix variant, SunOS.
|
||||
|
||||
(This section is incomplete.)
|
||||
|
||||
## Concurrency strategy
|
||||
|
||||
In this section, I'll explain the high level concurrency strategy of
|
||||
mold.
|
||||
|
||||
In most places, mold adopts data parallelism. That is, we have a huge
|
||||
number of piece of data of the same kind, and we process each of them
|
||||
individually using parallel for-loop. For example, after identifying
|
||||
the exact set of input object files, we need to scan all relocation
|
||||
tables to determine the sizes of .got and .plt sections. We do that
|
||||
using a parallel for-loop. The granularity of parallel processing in
|
||||
this case is the relocation table.
|
||||
|
||||
Data parallelism is very efficient and scalable because there's no
|
||||
need for threads to communicate with each other while working on each
|
||||
element of data. In addition to that, data parallelism is easy to
|
||||
understand, as it is just a for-loop in which multiple iterations may
|
||||
be executed in parallel. We don't use high-level communication or
|
||||
synchronization mechanisms such as channels, futures, promises,
|
||||
latches or something like that in mold.
|
||||
|
||||
In some cases, we need to share a little bit of data between threads
|
||||
while executing a parallel for-loop. For example, the loop to scan
|
||||
relocations turns on "requires GOT" or "requires PLT" flags in a
|
||||
symbol. Symbol is a shared resource, and writing to them from multiple
|
||||
threads without synchronization is unsafe. To deal with it, we made
|
||||
the flag an atomic variable.
|
||||
|
||||
The other common pattern you can find in mold which is build on top of
|
||||
the parallel for-loop is the map-reduce pattern. That is, we run a
|
||||
parallel for-loop on a large data set to produce a small data set and
|
||||
process the small data set with a single thread. Let me take a
|
||||
build-id computation as an example. Build-id is typically computed by
|
||||
applying a cryptographic hash function such as SHA-1 on a linker's
|
||||
output file. To compute it, we first consider an output as a sequence
|
||||
of 1 MiB blocks and compute a SHA-1 hash for each block in parallel.
|
||||
Then, we concatenate the SHA-1 hashes and compute a SHA-1 hash on the
|
||||
hashes to get a final build-id.
|
||||
|
||||
Finally, we use concurrent hashmap at a few places in mold. Concurrent
|
||||
hashmap is a hashmap to which multiple threads can safely insert items
|
||||
in parallel. We use it in the symbol resolution stage, for example.
|
||||
To resolve symbols, we basically have to throw in all defined symbols
|
||||
into a hash table, so that we can find a matching defined symbol for
|
||||
an undefined symbol by name. We do the hash table insertion from a
|
||||
parallel for-loop which iterates over a list of input files.
|
||||
|
||||
Overall, even though mold is highly scalable, it succeeded to avoid
|
||||
complexties you often find in complex parallel programs. From high
|
||||
level, mold just serially executes linker's internal passes one by
|
||||
one. Each pass is parallelized using parallel for-loops.
|
||||
|
||||
## Rejected ideas
|
||||
|
||||
In this section, I'll explain the alternative designs I currently do
|
||||
not plan to implement and why I turned them down.
|
||||
|
||||
- Placing variable-length sections at end of an output file and start
|
||||
copying file contents before fixing the output file layout
|
||||
|
||||
Idea: Fixing the layout of regular sections seems easy, and if we
|
||||
place them at beginning of a file, we can start copying their
|
||||
contents from their input files to an output file. While copying
|
||||
file contents, we can compute the sizes of variable-length sections
|
||||
such as .got or .plt and place them at end of the file.
|
||||
|
||||
Reason for rejection: I did not choose this design because I doubt
|
||||
if it could actually shorten link time and I think I don't need it
|
||||
anyway.
|
||||
|
||||
The linker has to de-duplicate comdat sections (i.e. inline
|
||||
functions that are included into multiple object files), so we
|
||||
cannot compute the layout of regular sections until we resolve all
|
||||
symbols and de-duplicate comdats. That takes a few hundred
|
||||
milliseconds. After that, we can compute the sizes of
|
||||
variable-length sections in less than 100 milliseconds. It's quite
|
||||
fast, so it doesn't seem to make much sense to proceed without
|
||||
fixing the final file layout.
|
||||
|
||||
The other reason to reject this idea is because there's good a
|
||||
chance for this idea to have a negative impact on linker's overall
|
||||
performance. If we copy file contents before fixing the layout, we
|
||||
can't apply relocations to them while copying because symbol
|
||||
addresses are not available yet. If we fix the file layout first, we
|
||||
can apply relocations while copying, which is effectively zero-cost
|
||||
due to a very good data locality. On the other hand, if we apply
|
||||
relocations long after we copy file contents, it's pretty expensive
|
||||
because section contents are very likely to have been evicted from
|
||||
CPU cache.
|
||||
|
||||
- Incremental linking
|
||||
|
||||
Idea: Incremental linking is a technique to patch a previous linker's
|
||||
output file so that only functions or data that are updated from the
|
||||
previous build are written to it. It is expected to significantly
|
||||
reduce the amount of data copied from input files to an output file
|
||||
and thus speed up linking. GNU BFD and gold linkers support it.
|
||||
|
||||
Reason for rejection: I turned it down because it (1) is
|
||||
complicated, (2) doesn't seem to speed it up that much and (3) has
|
||||
several practical issues. Let me explain each of them.
|
||||
|
||||
First, incremental linking for real C/C++ programs is not as easy as
|
||||
one might think. Let me take malloc as an example. malloc is usually
|
||||
defined by libc, but you can implement it in your program, and if
|
||||
that's the case, the symbol `malloc` will be resolved to your
|
||||
function instead of the one in libc. If you include a library that
|
||||
defines malloc (such as libjemalloc or libtbbmallc) before libc,
|
||||
their malloc will override libc's malloc.
|
||||
|
||||
Assume that you are using a nonstandard malloc. What if you remove
|
||||
your malloc from your code, or remove `-ljemalloc` from your
|
||||
Makefile? The linker has to include a malloc from libc, which may
|
||||
include more object files to satisfy its dependencies. Such code
|
||||
change can affect the entire program rather than just replacing one
|
||||
function. The same is true to adding malloc to your program. Making
|
||||
a local change doesn't necessarily result in a local change in the
|
||||
binary level. It can easily have cascading effects.
|
||||
|
||||
Some ELF fancy features make incremental linking even harder to
|
||||
implement. Take the weak symbol as an example. If you define `atoi`
|
||||
as a weak symbol in your program, and if you are not using `atoi`
|
||||
at all in your program, that symbol will be resolved to address
|
||||
0. But if you start using some libc function that indirectly calls
|
||||
`atoi`, then `atoi` will be included to your program, and your weak
|
||||
symbol will be resolved to that function. I don't know how to
|
||||
efficiently fix up a binary for this case.
|
||||
|
||||
This is a hard problem, so existing linkers don't try too hard to
|
||||
solve it. For example, IIRC, gold falls back to full link if any
|
||||
function is removed from a previous build. If you want to not annoy
|
||||
users in the fallback case, you need to make full link fast anyway.
|
||||
|
||||
Second, incremental linking itself has an overhead. It has to detect
|
||||
updated files, patch an existing output file and write additional
|
||||
data to an output file for future incremental linking. GNU gold, for
|
||||
instance, takes almost 30 seconds on my machine to do a null
|
||||
incremental link (i.e. no object files are updated from a previous
|
||||
build) for chrome. It's just too slow.
|
||||
|
||||
Third, there are other practical issues in incremental linking. It's
|
||||
not reproducible, so your binary isn't going to be the same as other
|
||||
binaries even if you are compiling the same source tree using the
|
||||
same compiler toolchain. Or, it is complex and there might be a bug
|
||||
in it. If something doesn't work correctly, "remove --incremental
|
||||
from your Makefile and try again" could be a piece of advise, but
|
||||
that isn't ideal.
|
||||
|
||||
So, all in all, incremental linking is tricky. I wanted to make full
|
||||
link as fast as possible, so that we don't have to think about how
|
||||
to workaround the slowness of full link.
|
||||
|
||||
- Defining a completely new file format and use it
|
||||
|
||||
Idea: Sometimes, the ELF file format itself seems to be a limiting
|
||||
factor of improving linker's performance. We might be able to make a
|
||||
far better one if we create a new file format.
|
||||
|
||||
Reason for rejection: I rejected the idea because it apparently has
|
||||
a practical issue (backward compatibility issue) and also doesn't
|
||||
seem to improve performance of linkers that much. As clearly
|
||||
demonstrated by mold, we can create a fast linker for ELF. I believe
|
||||
ELF isn't that bad, after all. The semantics of the existing Unix
|
||||
linkers, such as the name resolution algorithm or the linker script,
|
||||
have slowed the linkers down, but that's not a problem of the file
|
||||
format itself.
|
||||
|
||||
- Watching object files using inotify(2)
|
||||
|
||||
Idea: When mold is running as a daemon for preloading, use
|
||||
inotify(2) to watch file system updates so that it can reload files
|
||||
as soon as they are updated.
|
||||
|
||||
Reason for rejection: Just like the maximum number of files you can
|
||||
simultaneously open, the maximum number of files you can watch using
|
||||
inotify(2) isn't that large. Maybe just a single instance of mold is
|
||||
fine with inotify(2), but it may fail if you run multiple of it.
|
||||
|
||||
The other reason for not doing it is because mold is quite fast
|
||||
without it anyway. Invoking stat(2) on each file for file update
|
||||
check takes less than 100 milliseconds for Chrome, and if most of
|
||||
the input files are not updated, parsing updated files takes almost
|
||||
no time.
|
||||
-
|
||||
-441
@@ -1,441 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
enum {
|
||||
R_TLS_GOTIE = R_END + 1,
|
||||
R_TLS_LE,
|
||||
R_TLS_GD,
|
||||
R_TLS_LD,
|
||||
R_TLS_GOTDESC,
|
||||
R_TLS_GOTDESC_RELAX,
|
||||
R_TLS_DESC_CALL_RELAX,
|
||||
R_TPOFF,
|
||||
};
|
||||
|
||||
template <>
|
||||
void PltSection<I386>::copy_buf(Context<I386> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
// Write PLT header
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0xb3, 0x04, 0, 0, 0, // pushl 4(%ebx)
|
||||
0xff, 0xa3, 0x08, 0, 0, 0, // jmp *8(%ebx)
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
} else {
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0x35, 0, 0, 0, 0, // pushl (GOTPLT+4)
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *(GOTPLT+8)
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr + 8;
|
||||
}
|
||||
|
||||
// Write PLT entries
|
||||
i64 relplt_idx = 0;
|
||||
|
||||
for (Symbol<I386> *sym : symbols) {
|
||||
u8 *ent = buf + sym->get_plt_idx(ctx) * I386::plt_size;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 data[] = {
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0x68, 0, 0, 0, 0, // pushl $reloc_offset
|
||||
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx) - ctx.gotplt->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x68, 0, 0, 0, 0, // pushl $reloc_offset
|
||||
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx);
|
||||
}
|
||||
|
||||
*(u32 *)(ent + 7) = relplt_idx++ * sizeof(ElfRel<I386>);
|
||||
*(u32 *)(ent + 12) = this->shdr.sh_addr - sym->get_plt_addr(ctx) - 16;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<I386>::copy_buf(Context<I386> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 data[] = {
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr;
|
||||
}
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<I386>::apply_reloc(Context<I386> &ctx,
|
||||
ElfRel<I386> &rel,
|
||||
u64 loc, u64 val) {
|
||||
u8 *base = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_NONE:
|
||||
return;
|
||||
case R_386_32:
|
||||
*(u32 *)(base + loc) = val;
|
||||
return;
|
||||
case R_386_PC32:
|
||||
*(u32 *)(base + loc) = val - this->shdr.sh_addr - loc;
|
||||
return;
|
||||
}
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
static void write_val(Context<I386> &ctx, u64 r_type, u8 *loc, u64 val) {
|
||||
switch (r_type) {
|
||||
case R_386_NONE:
|
||||
return;
|
||||
case R_386_8:
|
||||
case R_386_PC8:
|
||||
*loc += val;
|
||||
return;
|
||||
case R_386_16:
|
||||
case R_386_PC16:
|
||||
*(u16 *)loc += val;
|
||||
return;
|
||||
case R_386_32:
|
||||
case R_386_PC32:
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
case R_386_PLT32:
|
||||
case R_386_GOTOFF:
|
||||
case R_386_GOTPC:
|
||||
case R_386_TLS_LDM:
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_GD:
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_GOTDESC:
|
||||
*(u32 *)loc += val;
|
||||
return;
|
||||
}
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<I386>::apply_reloc_alloc(Context<I386> &ctx, u8 *base) {
|
||||
ElfRel<I386> *dynrel = nullptr;
|
||||
std::span<ElfRel<I386>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<I386> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<I386> &rel = rels[i];
|
||||
Symbol<I386> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
const SectionFragmentRef<I386> *ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto write = [&](u64 val) {
|
||||
write_val(ctx, rel.r_type, loc, val);
|
||||
};
|
||||
|
||||
#define S (ref ? ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (ref ? ref->addend : 0)
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOTPLT ctx.gotplt->shdr.sh_addr
|
||||
|
||||
switch (rel_types[i]) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_ABS:
|
||||
write(S + A);
|
||||
break;
|
||||
case R_BASEREL:
|
||||
*dynrel++ = {P, R_386_RELATIVE, 0};
|
||||
*(u32 *)loc += S + A;
|
||||
break;
|
||||
case R_DYN:
|
||||
*dynrel++ = {P, R_386_32, (u32)sym.get_dynsym_idx(ctx)};
|
||||
*(u32 *)loc += A;
|
||||
break;
|
||||
case R_PC:
|
||||
write(S + A - P);
|
||||
break;
|
||||
case R_GOT:
|
||||
write(sym.get_got_addr(ctx) + A - GOTPLT);
|
||||
break;
|
||||
case R_GOTOFF:
|
||||
write(S + A - GOTPLT);
|
||||
break;
|
||||
case R_GOTPC:
|
||||
write(GOTPLT + A - P);
|
||||
break;
|
||||
case R_GOTPCREL:
|
||||
write(G + GOTPLT + A - P);
|
||||
break;
|
||||
case R_TLS_GOTIE:
|
||||
write(sym.get_gottp_addr(ctx) + A - GOTPLT);
|
||||
break;
|
||||
case R_TLS_LE:
|
||||
write(S + A - ctx.tls_end);
|
||||
break;
|
||||
case R_TLS_GD:
|
||||
write(sym.get_tlsgd_addr(ctx) + A - GOTPLT);
|
||||
break;
|
||||
case R_TLS_LD:
|
||||
write(ctx.got->get_tlsld_addr(ctx) + A - GOTPLT);
|
||||
break;
|
||||
case R_TPOFF:
|
||||
write(S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_TLS_GOTDESC:
|
||||
write(sym.get_tlsdesc_addr(ctx) + A - GOTPLT);
|
||||
break;
|
||||
case R_TLS_GOTDESC_RELAX: {
|
||||
static const u8 insn[] = {
|
||||
0x8d, 0x05, 0, 0, 0, 0, // lea 0, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
write(S + A - ctx.tls_end);
|
||||
break;
|
||||
}
|
||||
case R_TLS_DESC_CALL_RELAX:
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
break;
|
||||
case R_SIZE:
|
||||
write(sym.esym().st_size + A);
|
||||
break;
|
||||
default:
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOTPLT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<I386>::apply_reloc_nonalloc(Context<I386> &ctx, u8 *base) {
|
||||
std::span<ElfRel<I386>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<I386> &rel = rels[i];
|
||||
if (rel.r_type == R_386_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<I386> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
const SectionFragmentRef<I386> *ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto write = [&](u64 val) {
|
||||
write_val(ctx, rel.r_type, loc, val);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_16:
|
||||
case R_386_32:
|
||||
case R_386_PC8:
|
||||
case R_386_PC16:
|
||||
case R_386_PC32:
|
||||
case R_386_GOTPC:
|
||||
if (ref)
|
||||
write(ref->frag->get_addr(ctx) + ref->addend);
|
||||
else
|
||||
write(sym.get_addr(ctx));
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
write(sym.get_addr(ctx) - ctx.got->shdr.sh_addr);
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
write(sym.get_addr(ctx) - ctx.tls_begin);
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
write(sym.esym().st_size);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel_to_string<I386>(rel.r_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<I386>::scan_relocations(Context<I386> &ctx) {
|
||||
assert(shdr.sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<I386>);
|
||||
std::span<ElfRel<I386>> rels = get_rels(ctx);
|
||||
bool is_writable = (shdr.sh_flags & SHF_WRITE);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<I386> &rel = rels[i];
|
||||
|
||||
if (rel.r_type == R_386_NONE) {
|
||||
rel_types[i] = R_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
Symbol<I386> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_16: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // DSO
|
||||
{ NONE, ERROR, ERROR, ERROR }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_ABS, i);
|
||||
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, DYNREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_ABS, i);
|
||||
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, R_PC, i);
|
||||
break;
|
||||
}
|
||||
case R_386_PC32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ BASEREL, NONE, ERROR, ERROR }, // DSO
|
||||
{ BASEREL, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_PC, i);
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOT;
|
||||
break;
|
||||
case R_386_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
rel_types[i] = R_PC;
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
rel_types[i] = R_GOTOFF;
|
||||
break;
|
||||
case R_386_GOTPC:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOTPC;
|
||||
break;
|
||||
case R_386_TLS_GOTIE:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
rel_types[i] = R_TLS_GOTIE;
|
||||
break;
|
||||
case R_386_TLS_LE:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
rel_types[i] = R_TLS_LE;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
rel_types[i] = R_TLS_GD;
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
sym.flags |= NEEDS_TLSLD;
|
||||
rel_types[i] = R_TLS_LD;
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
rel_types[i] = R_TPOFF;
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
rel_types[i] = R_SIZE;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
if (ctx.arg.relax && !ctx.arg.shared) {
|
||||
rel_types[i] = R_TLS_GOTDESC_RELAX;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
rel_types[i] = R_TLS_GOTDESC;
|
||||
}
|
||||
break;
|
||||
case R_386_TLS_DESC_CALL:
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
rel_types[i] = R_TLS_DESC_CALL_RELAX;
|
||||
else
|
||||
rel_types[i] = R_NONE;
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: "
|
||||
<< rel_to_string<I386>(rel.r_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
-687
@@ -1,687 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
enum {
|
||||
R_GOTPCRELX_RELAX = R_END + 1,
|
||||
R_REX_GOTPCRELX_RELAX,
|
||||
R_TLSGD,
|
||||
R_TLSGD_RELAX_LE,
|
||||
R_TLSLD,
|
||||
R_TLSLD_RELAX_LE,
|
||||
R_DTPOFF,
|
||||
R_DTPOFF_RELAX,
|
||||
R_TPOFF,
|
||||
R_GOTTPOFF,
|
||||
R_GOTTPOFF_RELAX,
|
||||
R_GOTPC_TLSDESC,
|
||||
R_GOTPC_TLSDESC_RELAX_LE,
|
||||
R_TLSDESC_CALL_RELAX,
|
||||
};
|
||||
|
||||
template <>
|
||||
void PltSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
u8 *buf = ctx.buf + this->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 - this->shdr.sh_addr + 2;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - this->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<X86_64> *sym : symbols) {
|
||||
u8 *ent = buf + sym->get_plt_idx(ctx) * X86_64::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) = this->shdr.sh_addr - sym->get_plt_addr(ctx) - 16;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (Symbol<X86_64> *sym : symbols) {
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * X86_64::pltgot_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<X86_64>::apply_reloc(Context<X86_64> &ctx,
|
||||
ElfRel<X86_64> &rel,
|
||||
u64 loc, u64 val) {
|
||||
u8 *base = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_NONE:
|
||||
return;
|
||||
case R_X86_64_32:
|
||||
*(u32 *)(base + loc) = val;
|
||||
return;
|
||||
case R_X86_64_64:
|
||||
*(u64 *)(base + loc) = val;
|
||||
return;
|
||||
case R_X86_64_PC32:
|
||||
*(u32 *)(base + loc) = val - this->shdr.sh_addr - loc;
|
||||
return;
|
||||
case R_X86_64_PC64:
|
||||
*(u64 *)(base + loc) = val - this->shdr.sh_addr - loc;
|
||||
return;
|
||||
}
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
static void overflow_check(Context<X86_64> &ctx, InputSection<X86_64> *sec,
|
||||
Symbol<X86_64> &sym, u64 r_type, u64 val) {
|
||||
switch (r_type) {
|
||||
case R_X86_64_8:
|
||||
if (val != (u8)val)
|
||||
Error(ctx) << *sec << ": relocation R_X86_64_8 against " << sym
|
||||
<< " out of range: " << val << " is not in [0, 255]";
|
||||
return;
|
||||
case R_X86_64_PC8:
|
||||
if (val != (i8)val)
|
||||
Error(ctx) << *sec << ": relocation R_X86_64_PC8 against " << sym
|
||||
<< " out of range: " << (i64)val << " is not in [-128, 127]";
|
||||
return;
|
||||
case R_X86_64_16:
|
||||
if (val != (u16)val)
|
||||
Error(ctx) << *sec << ": relocation R_X86_64_16 against " << sym
|
||||
<< " out of range: " << val << " is not in [0, 65535]";
|
||||
return;
|
||||
case R_X86_64_PC16:
|
||||
if (val != (i16)val)
|
||||
Error(ctx) << *sec << ": relocation R_X86_64_PC16 against " << sym
|
||||
<< " out of range: " << (i64)val
|
||||
<< " is not in [-32768, 32767]";
|
||||
return;
|
||||
case R_X86_64_32:
|
||||
if (val != (u32)val)
|
||||
Error(ctx) << *sec << ": relocation R_X86_64_32 against " << sym
|
||||
<< " out of range: " << val << " is not in [0, 4294967296]";
|
||||
return;
|
||||
case R_X86_64_32S:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_GOT32:
|
||||
case R_X86_64_GOTPC32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX:
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_TLSGD:
|
||||
case R_X86_64_TLSLD:
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_GOTTPOFF:
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
if (val != (i32)val)
|
||||
Error(ctx) << *sec << ": relocation " << rel_to_string<X86_64>(r_type)
|
||||
<< " against " << sym << " out of range: " << (i64)val
|
||||
<< " is not in [-2147483648, 2147483647]";
|
||||
return;
|
||||
case R_X86_64_NONE:
|
||||
case R_X86_64_64:
|
||||
case R_X86_64_PC64:
|
||||
case R_X86_64_TPOFF64:
|
||||
case R_X86_64_DTPOFF64:
|
||||
case R_X86_64_GOT64:
|
||||
case R_X86_64_GOTPCREL64:
|
||||
case R_X86_64_GOTPC64:
|
||||
case R_X86_64_SIZE64:
|
||||
return;
|
||||
}
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
static void write_val(Context<X86_64> &ctx, u64 r_type, u8 *loc, u64 val) {
|
||||
switch (r_type) {
|
||||
case R_X86_64_NONE:
|
||||
return;
|
||||
case R_X86_64_8:
|
||||
case R_X86_64_PC8:
|
||||
*loc = val;
|
||||
return;
|
||||
case R_X86_64_16:
|
||||
case R_X86_64_PC16:
|
||||
*(u16 *)loc = val;
|
||||
return;
|
||||
case R_X86_64_32:
|
||||
case R_X86_64_32S:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_GOT32:
|
||||
case R_X86_64_GOTPC32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX:
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_TLSGD:
|
||||
case R_X86_64_TLSLD:
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_GOTTPOFF:
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
*(u32 *)loc = val;
|
||||
return;
|
||||
case R_X86_64_64:
|
||||
case R_X86_64_PC64:
|
||||
case R_X86_64_TPOFF64:
|
||||
case R_X86_64_DTPOFF64:
|
||||
case R_X86_64_GOT64:
|
||||
case R_X86_64_GOTPCREL64:
|
||||
case R_X86_64_GOTPC64:
|
||||
case R_X86_64_SIZE64:
|
||||
*(u64 *)loc = val;
|
||||
return;
|
||||
}
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
static u32 relax_gotpcrelx(u8 *loc) {
|
||||
switch ((loc[0] << 8) | loc[1]) {
|
||||
case 0xff15: return 0x90e8; // call *0(%rip) -> call 0
|
||||
case 0xff25: return 0x90e9; // jmp *0(%rip) -> jmp 0
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_rex_gotpcrelx(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488b05: return 0x488d05; // mov 0(%rip), %rax -> lea 0(%rip), %rax
|
||||
case 0x488b0d: return 0x488d0d; // mov 0(%rip), %rcx -> lea 0(%rip), %rcx
|
||||
case 0x488b15: return 0x488d15; // mov 0(%rip), %rdx -> lea 0(%rip), %rdx
|
||||
case 0x488b1d: return 0x488d1d; // mov 0(%rip), %rbx -> lea 0(%rip), %rbx
|
||||
case 0x488b25: return 0x488d25; // mov 0(%rip), %rsp -> lea 0(%rip), %rsp
|
||||
case 0x488b2d: return 0x488d2d; // mov 0(%rip), %rbp -> lea 0(%rip), %rbp
|
||||
case 0x488b35: return 0x488d35; // mov 0(%rip), %rsi -> lea 0(%rip), %rsi
|
||||
case 0x488b3d: return 0x488d3d; // mov 0(%rip), %rdi -> lea 0(%rip), %rdi
|
||||
case 0x4c8b05: return 0x4c8d05; // mov 0(%rip), %r8 -> lea 0(%rip), %r8
|
||||
case 0x4c8b0d: return 0x4c8d0d; // mov 0(%rip), %r9 -> lea 0(%rip), %r9
|
||||
case 0x4c8b15: return 0x4c8d15; // mov 0(%rip), %r10 -> lea 0(%rip), %r10
|
||||
case 0x4c8b1d: return 0x4c8d1d; // mov 0(%rip), %r11 -> lea 0(%rip), %r11
|
||||
case 0x4c8b25: return 0x4c8d25; // mov 0(%rip), %r12 -> lea 0(%rip), %r12
|
||||
case 0x4c8b2d: return 0x4c8d2d; // mov 0(%rip), %r13 -> lea 0(%rip), %r13
|
||||
case 0x4c8b35: return 0x4c8d35; // mov 0(%rip), %r14 -> lea 0(%rip), %r14
|
||||
case 0x4c8b3d: return 0x4c8d3d; // mov 0(%rip), %r15 -> lea 0(%rip), %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_gottpoff(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488b05: return 0x48c7c0; // mov 0(%rip), %rax -> mov $0, %rax
|
||||
case 0x488b0d: return 0x48c7c1; // mov 0(%rip), %rcx -> mov $0, %rcx
|
||||
case 0x488b15: return 0x48c7c2; // mov 0(%rip), %rdx -> mov $0, %rdx
|
||||
case 0x488b1d: return 0x48c7c3; // mov 0(%rip), %rbx -> mov $0, %rbx
|
||||
case 0x488b25: return 0x48c7c4; // mov 0(%rip), %rsp -> mov $0, %rsp
|
||||
case 0x488b2d: return 0x48c7c5; // mov 0(%rip), %rbp -> mov $0, %rbp
|
||||
case 0x488b35: return 0x48c7c6; // mov 0(%rip), %rsi -> mov $0, %rsi
|
||||
case 0x488b3d: return 0x48c7c7; // mov 0(%rip), %rdi -> mov $0, %rdi
|
||||
case 0x4c8b05: return 0x49c7c0; // mov 0(%rip), %r8 -> mov $0, %r8
|
||||
case 0x4c8b0d: return 0x49c7c1; // mov 0(%rip), %r9 -> mov $0, %r9
|
||||
case 0x4c8b15: return 0x49c7c2; // mov 0(%rip), %r10 -> mov $0, %r10
|
||||
case 0x4c8b1d: return 0x49c7c3; // mov 0(%rip), %r11 -> mov $0, %r11
|
||||
case 0x4c8b25: return 0x49c7c4; // mov 0(%rip), %r12 -> mov $0, %r12
|
||||
case 0x4c8b2d: return 0x49c7c5; // mov 0(%rip), %r13 -> mov $0, %r13
|
||||
case 0x4c8b35: return 0x49c7c6; // mov 0(%rip), %r14 -> mov $0, %r14
|
||||
case 0x4c8b3d: return 0x49c7c7; // mov 0(%rip), %r15 -> mov $0, %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Apply relocations to SHF_ALLOC sections (i.e. sections that are
|
||||
// mapped to memory at runtime) based on the result of
|
||||
// scan_relocations().
|
||||
template <>
|
||||
void InputSection<X86_64>::apply_reloc_alloc(Context<X86_64> &ctx, u8 *base) {
|
||||
ElfRel<X86_64> *dynrel = nullptr;
|
||||
std::span<ElfRel<X86_64>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<X86_64> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<X86_64> &rel = rels[i];
|
||||
Symbol<X86_64> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
const SectionFragmentRef<X86_64> *ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto write = [&](u64 val) {
|
||||
overflow_check(ctx, this, sym, rel.r_type, val);
|
||||
write_val(ctx, rel.r_type, loc, val);
|
||||
};
|
||||
|
||||
#define S (ref ? ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (ref ? ref->addend : rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel_types[i]) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_ABS:
|
||||
write(S + A);
|
||||
break;
|
||||
case R_BASEREL:
|
||||
*dynrel++ = {P, R_X86_64_RELATIVE, 0, (i64)(S + A)};
|
||||
*(u64 *)loc = S + A;
|
||||
break;
|
||||
case R_DYN:
|
||||
*dynrel++ = {P, R_X86_64_64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
*(u64 *)loc = A;
|
||||
break;
|
||||
case R_PC:
|
||||
write(S + A - P);
|
||||
break;
|
||||
case R_GOT:
|
||||
write(G + A);
|
||||
break;
|
||||
case R_GOTPC:
|
||||
write(GOT + A - P);
|
||||
break;
|
||||
case R_GOTPCREL:
|
||||
write(G + GOT + A - P);
|
||||
break;
|
||||
case R_GOTPCRELX_RELAX: {
|
||||
u32 insn = relax_gotpcrelx(loc - 2);
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write(S + A - P);
|
||||
break;
|
||||
}
|
||||
case R_REX_GOTPCRELX_RELAX: {
|
||||
u32 insn = relax_rex_gotpcrelx(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write(S + A - P);
|
||||
break;
|
||||
}
|
||||
case R_TLSGD:
|
||||
write(sym.get_tlsgd_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_TLSGD_RELAX_LE: {
|
||||
// Relax GD to LE
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 8) = S - ctx.tls_end + A + 4;
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
case R_TLSLD:
|
||||
write(ctx.got->get_tlsld_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_TLSLD_RELAX_LE: {
|
||||
// Relax LD to LE
|
||||
static const u8 insn[] = {
|
||||
0x66, 0x66, 0x66, // (padding)
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
case R_DTPOFF:
|
||||
write(S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_DTPOFF_RELAX:
|
||||
write(S + A - ctx.tls_end);
|
||||
break;
|
||||
case R_TPOFF:
|
||||
write(S + A - ctx.tls_end);
|
||||
break;
|
||||
case R_GOTTPOFF:
|
||||
write(sym.get_gottp_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_GOTTPOFF_RELAX: {
|
||||
u32 insn = relax_gottpoff(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write(S + A - ctx.tls_end + 4);
|
||||
break;
|
||||
}
|
||||
case R_GOTPC_TLSDESC:
|
||||
write(sym.get_tlsdesc_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_GOTPC_TLSDESC_RELAX_LE: {
|
||||
static const u8 insn[] = {
|
||||
0x48, 0xc7, 0xc0, 0, 0, 0, 0, // mov $0, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
write(S + A - ctx.tls_end + 4);
|
||||
break;
|
||||
}
|
||||
case R_SIZE:
|
||||
write(sym.esym().st_size + A);
|
||||
break;
|
||||
case R_TLSDESC_CALL_RELAX:
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
break;
|
||||
default:
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
// This function is responsible for applying relocations against
|
||||
// non-SHF_ALLOC sections (i.e. sections that are not mapped to memory
|
||||
// at runtime).
|
||||
//
|
||||
// Relocations against non-SHF_ALLOC sections are much easier to
|
||||
// handle than that against SHF_ALLOC sections. It is because, since
|
||||
// they are not mapped to memory, they don't contain any variable or
|
||||
// function and never need PLT or GOT. Non-SHF_ALLOC sections are
|
||||
// mostly debug info sections.
|
||||
//
|
||||
// Relocations against non-SHF_ALLOC sections are not scanned by
|
||||
// scan_relocations.
|
||||
template <>
|
||||
void InputSection<X86_64>::apply_reloc_nonalloc(Context<X86_64> &ctx, u8 *base) {
|
||||
std::span<ElfRel<X86_64>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<X86_64> &rel = rels[i];
|
||||
if (rel.r_type == R_X86_64_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<X86_64> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
const SectionFragmentRef<X86_64> *ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto write = [&](u64 val) {
|
||||
overflow_check(ctx, this, sym, rel.r_type, val);
|
||||
write_val(ctx, rel.r_type, loc, val);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
case R_X86_64_16:
|
||||
case R_X86_64_32:
|
||||
case R_X86_64_32S:
|
||||
case R_X86_64_64:
|
||||
if (ref)
|
||||
write(ref->frag->get_addr(ctx) + ref->addend);
|
||||
else
|
||||
write(sym.get_addr(ctx) + rel.r_addend);
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_DTPOFF64:
|
||||
write(sym.get_addr(ctx) + rel.r_addend - ctx.tls_begin);
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_SIZE64:
|
||||
write(sym.esym().st_size + rel.r_addend);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel_to_string<X86_64>(rel.r_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Linker has to create data structures in an output file to apply
|
||||
// some type of relocations. For example, if a relocation refers a GOT
|
||||
// or a PLT entry of a symbol, linker has to create an entry in .got
|
||||
// or in .plt for that symbol. In order to fix the file layout, we
|
||||
// need to scan relocations.
|
||||
template <>
|
||||
void InputSection<X86_64>::scan_relocations(Context<X86_64> &ctx) {
|
||||
assert(shdr.sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<X86_64>);
|
||||
std::span<ElfRel<X86_64>> rels = get_rels(ctx);
|
||||
bool is_writable = (shdr.sh_flags & SHF_WRITE);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<X86_64> &rel = rels[i];
|
||||
|
||||
if (rel.r_type == R_X86_64_NONE) {
|
||||
rel_types[i] = R_NONE;
|
||||
continue;
|
||||
}
|
||||
|
||||
Symbol<X86_64> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
case R_X86_64_16:
|
||||
case R_X86_64_32:
|
||||
case R_X86_64_32S: {
|
||||
// Dynamic linker does not support 8, 16 or 32-bit dynamic
|
||||
// relocations for these types of relocations. We report an
|
||||
// error if we cannot relocate them even at load-time.
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // DSO
|
||||
{ NONE, ERROR, ERROR, ERROR }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_ABS, i);
|
||||
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, DYNREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_ABS, i);
|
||||
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, R_PC, i);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC64: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ BASEREL, NONE, ERROR, ERROR }, // DSO
|
||||
{ BASEREL, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_PC, i);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOT32:
|
||||
case R_X86_64_GOT64:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOT;
|
||||
break;
|
||||
case R_X86_64_GOTPC32:
|
||||
case R_X86_64_GOTPC64:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOTPC;
|
||||
break;
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCREL64:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOTPCREL;
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPCRELX";
|
||||
|
||||
if (ctx.arg.relax && !sym.is_imported && sym.is_relative(ctx) &&
|
||||
relax_gotpcrelx(loc - 2)) {
|
||||
rel_types[i] = R_GOTPCRELX_RELAX;
|
||||
} else {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOTPCREL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_REX_GOTPCRELX";
|
||||
|
||||
if (ctx.arg.relax && !sym.is_imported && sym.is_relative(ctx) &&
|
||||
relax_rex_gotpcrelx(loc - 3)) {
|
||||
rel_types[i] = R_REX_GOTPCRELX_RELAX;
|
||||
} else {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOTPCREL;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
rel_types[i] = R_PC;
|
||||
break;
|
||||
case R_X86_64_TLSGD: {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this
|
||||
<< ": TLSGD reloc must be followed by PLT32 or GOTPCREL";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported) {
|
||||
rel_types[i++] = R_TLSGD_RELAX_LE;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
rel_types[i] = R_TLSGD;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_X86_64_TLSLD:
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this
|
||||
<< ": TLSGD reloc must be followed by PLT32 or GOTPCREL";
|
||||
if (sym.is_imported)
|
||||
Fatal(ctx) << *this << ": TLSLD reloc refers external symbol " << sym;
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared) {
|
||||
rel_types[i++] = R_TLSLD_RELAX_LE;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSLD;
|
||||
rel_types[i] = R_TLSLD;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_DTPOFF64:
|
||||
if (sym.is_imported)
|
||||
Fatal(ctx) << *this << ": DTPOFF reloc refers external symbol " << sym;
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
rel_types[i] = R_DTPOFF_RELAX;
|
||||
else
|
||||
rel_types[i] = R_DTPOFF;
|
||||
break;
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_TPOFF64:
|
||||
rel_types[i] = R_TPOFF;
|
||||
break;
|
||||
case R_X86_64_GOTTPOFF:
|
||||
ctx.has_gottp_rel = true;
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported &&
|
||||
relax_gottpoff(loc - 3)) {
|
||||
rel_types[i] = R_GOTTPOFF_RELAX;
|
||||
} else {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
rel_types[i] = R_GOTTPOFF;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
if (memcmp(loc - 3, "\x48\x8d\x05", 3))
|
||||
Fatal(ctx) << *this << ": GOTPC32_TLSDESC relocation is used"
|
||||
<< " against an invalid code sequence";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared) {
|
||||
rel_types[i] = R_GOTPC_TLSDESC_RELAX_LE;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
rel_types[i] = R_GOTPC_TLSDESC;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_SIZE64:
|
||||
rel_types[i] = R_SIZE;
|
||||
break;
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
rel_types[i] = R_TLSDESC_CALL_RELAX;
|
||||
else
|
||||
rel_types[i] = R_NONE;
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: "
|
||||
<< rel_to_string<X86_64>(rel.r_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
#include "filetype.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
struct ArHdr {
|
||||
char ar_name[16];
|
||||
char ar_date[12];
|
||||
char ar_uid[6];
|
||||
char ar_gid[6];
|
||||
char ar_mode[8];
|
||||
char ar_size[10];
|
||||
char ar_fmag[2];
|
||||
|
||||
bool starts_with(std::string_view s) const {
|
||||
return std::string_view(ar_name, s.size()) == s;
|
||||
}
|
||||
|
||||
bool is_strtab() const {
|
||||
return starts_with("// ");
|
||||
}
|
||||
|
||||
bool is_symtab() const {
|
||||
return starts_with("/ ") || starts_with("/SYM64/ ");
|
||||
}
|
||||
|
||||
std::string read_name(std::string_view strtab, u8 *&ptr) const {
|
||||
// BSD-style long filename
|
||||
if (starts_with("#1/")) {
|
||||
int namelen = atoi(ar_name + 3);
|
||||
std::string name{(char *)ptr, (size_t)namelen};
|
||||
ptr += namelen;
|
||||
|
||||
if (size_t pos = name.find('\0'))
|
||||
name = name.substr(0, pos);
|
||||
return name;
|
||||
}
|
||||
|
||||
// SysV-style long filename
|
||||
if (starts_with("/")) {
|
||||
const char *start = strtab.data() + atoi(ar_name + 1);
|
||||
return {start, (const char *)strstr(start, "/\n")};
|
||||
}
|
||||
|
||||
// Short fileanme
|
||||
if (const char *end = (char *)memchr(ar_name, '/', sizeof(ar_name)))
|
||||
return {ar_name, end};
|
||||
return {ar_name, sizeof(ar_name)};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
std::vector<MappedFile<C> *>
|
||||
read_thin_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
u8 *begin = mf->data;
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MappedFile<C> *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (data < begin + mf->size) {
|
||||
// Each header is aligned to a 2 byte boundary.
|
||||
if ((begin - data) % 2)
|
||||
data++;
|
||||
|
||||
ArHdr &hdr = *(ArHdr *)data;
|
||||
u8 *body = data + sizeof(hdr);
|
||||
u64 size = atol(hdr.ar_size);
|
||||
|
||||
// Read a string table.
|
||||
if (hdr.is_strtab()) {
|
||||
strtab = {(char *)body, (size_t)size};
|
||||
data = body + size;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip a symbol table.
|
||||
if (hdr.is_symtab()) {
|
||||
data = body + size;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!hdr.starts_with("#1/") && !hdr.starts_with("/"))
|
||||
Fatal(ctx) << mf->name << ": filename is not stored as a long filename";
|
||||
|
||||
std::string name = hdr.read_name(strtab, body);
|
||||
|
||||
// Skip if symbol table
|
||||
if (name == "__.SYMDEF" || name == "__.SYMDEF SORTED")
|
||||
continue;
|
||||
|
||||
std::string path = name.starts_with('/') ?
|
||||
name : (filepath(mf->name).parent_path() / name).string();
|
||||
vec.push_back(MappedFile<C>::must_open(ctx, path));
|
||||
data = body;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
std::vector<MappedFile<C> *>
|
||||
read_fat_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
u8 *begin = mf->data;
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MappedFile<C> *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (begin + mf->size - data >= 2) {
|
||||
if ((begin - data) % 2)
|
||||
data++;
|
||||
|
||||
ArHdr &hdr = *(ArHdr *)data;
|
||||
u8 *body = data + sizeof(hdr);
|
||||
u64 size = atol(hdr.ar_size);
|
||||
data = body + size;
|
||||
|
||||
// Read if string table
|
||||
if (hdr.is_strtab()) {
|
||||
strtab = {(char *)body, (size_t)size};
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if symbol table
|
||||
if (hdr.is_symtab())
|
||||
continue;
|
||||
|
||||
// Read the name field
|
||||
std::string name = hdr.read_name(strtab, body);
|
||||
|
||||
// Skip if symbol table
|
||||
if (name == "__.SYMDEF" || name == "__.SYMDEF SORTED")
|
||||
continue;
|
||||
|
||||
vec.push_back(mf->slice(ctx, name, body - begin, data - body));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
std::vector<MappedFile<C> *>
|
||||
read_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
switch (get_file_type(mf)) {
|
||||
case FileType::AR:
|
||||
return read_fat_archive_members(ctx, mf);
|
||||
case FileType::THIN_AR:
|
||||
return read_thin_archive_members(ctx, mf);
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
struct ArHdr {
|
||||
char ar_name[16];
|
||||
char ar_date[12];
|
||||
char ar_uid[6];
|
||||
char ar_gid[6];
|
||||
char ar_mode[8];
|
||||
char ar_size[10];
|
||||
char ar_fmag[2];
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::vector<MemoryMappedFile<E> *>
|
||||
read_thin_archive_members(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
u8 *begin = mb->data(ctx);
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MemoryMappedFile<E> *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (data < begin + mb->size()) {
|
||||
// Each header is aligned to a 2 byte boundary.
|
||||
if ((begin - data) % 2)
|
||||
data++;
|
||||
|
||||
ArHdr &hdr = *(ArHdr *)data;
|
||||
u8 *body = data + sizeof(hdr);
|
||||
u64 size = atol(hdr.ar_size);
|
||||
|
||||
// Read a string table.
|
||||
if (!memcmp(hdr.ar_name, "// ", 3)) {
|
||||
strtab = {(char *)body, size};
|
||||
data = body + size;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip a symbol table.
|
||||
if (!memcmp(hdr.ar_name, "/ ", 2)) {
|
||||
data = body + size;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hdr.ar_name[0] != '/')
|
||||
Fatal(ctx) << mb->name << ": filename is not stored as a long filename";
|
||||
|
||||
const char *start = strtab.data() + atoi(hdr.ar_name + 1);
|
||||
std::string name(start, (const char *)strstr(start, "/\n"));
|
||||
std::string path = name.starts_with('/') ?
|
||||
name : std::string(path_dirname(mb->name)) + "/" + name;
|
||||
vec.push_back(MemoryMappedFile<E>::must_open(ctx, path));
|
||||
data = body;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<MemoryMappedFile<E> *>
|
||||
read_fat_archive_members(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
u8 *begin = mb->data(ctx);
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MemoryMappedFile<E> *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (begin + mb->size() - data >= 2) {
|
||||
if ((begin - data) % 2)
|
||||
data++;
|
||||
|
||||
ArHdr &hdr = *(ArHdr *)data;
|
||||
u8 *body = data + sizeof(hdr);
|
||||
u64 size = atol(hdr.ar_size);
|
||||
data = body + size;
|
||||
|
||||
if (!memcmp(hdr.ar_name, "// ", 3)) {
|
||||
strtab = {(char *)body, size};
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!memcmp(hdr.ar_name, "/ ", 2) ||
|
||||
!memcmp(hdr.ar_name, "__.SYMDEF/", 10))
|
||||
continue;
|
||||
|
||||
std::string name;
|
||||
|
||||
if (hdr.ar_name[0] == '/') {
|
||||
const char *start = strtab.data() + atoi(hdr.ar_name + 1);
|
||||
name = {start, (const char *)strstr(start, "/\n")};
|
||||
} else {
|
||||
name = {hdr.ar_name, strchr(hdr.ar_name, '/')};
|
||||
}
|
||||
|
||||
vec.push_back(mb->slice(ctx, name, body - begin, size));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<MemoryMappedFile<E> *>
|
||||
read_archive_members(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
switch (get_file_type(ctx, mb)) {
|
||||
case FileType::AR:
|
||||
return read_fat_archive_members(ctx, mb);
|
||||
case FileType::THIN_AR:
|
||||
return read_thin_archive_members(ctx, mb);
|
||||
default:
|
||||
unreachable(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template std::vector<MemoryMappedFile<E> *> \
|
||||
read_fat_archive_members(Context<E> &, MemoryMappedFile<E> *); \
|
||||
template std::vector<MemoryMappedFile<E> *> \
|
||||
read_thin_archive_members(Context<E> &, MemoryMappedFile<E> *); \
|
||||
template std::vector<MemoryMappedFile<E> *> \
|
||||
read_archive_members(Context<E> &, MemoryMappedFile<E> *);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef __BIG_ENDIAN__
|
||||
#error "mold does not support big-endian hosts"
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename T>
|
||||
class BigEndian {
|
||||
public:
|
||||
BigEndian() = delete;
|
||||
|
||||
operator T() const {
|
||||
T x;
|
||||
memcpy(&x, val, sizeof(T));
|
||||
return bswap(x);
|
||||
}
|
||||
|
||||
BigEndian &operator=(T x) {
|
||||
x = bswap(x);
|
||||
memcpy(&val, &x, sizeof(T));
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigEndian &operator++() {
|
||||
return *this = *this + 1;
|
||||
}
|
||||
|
||||
BigEndian operator++(int) {
|
||||
T ret = *this;
|
||||
*this = *this + 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator--() {
|
||||
return *this = *this - 1;
|
||||
}
|
||||
|
||||
BigEndian operator--(int) {
|
||||
T ret = *this;
|
||||
*this = *this - 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator+=(T x) {
|
||||
return *this = *this + x;
|
||||
}
|
||||
|
||||
BigEndian &operator&=(T x) {
|
||||
return *this = *this & x;
|
||||
}
|
||||
|
||||
BigEndian &operator|=(T x) {
|
||||
return *this = *this | x;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t val[sizeof(T)];
|
||||
|
||||
static T bswap(T x) {
|
||||
if constexpr (sizeof(T) == 2)
|
||||
return __builtin_bswap16(x);
|
||||
else if constexpr (sizeof(T) == 4)
|
||||
return __builtin_bswap32(x);
|
||||
else
|
||||
return __builtin_bswap64(x);
|
||||
}
|
||||
};
|
||||
|
||||
using ibig16 = BigEndian<int16_t>;
|
||||
using ibig32 = BigEndian<int32_t>;
|
||||
using ibig64 = BigEndian<int64_t>;
|
||||
using ubig16 = BigEndian<uint16_t>;
|
||||
using ubig32 = BigEndian<uint32_t>;
|
||||
using ubig64 = BigEndian<uint64_t>;
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/bin/bash -x
|
||||
# This is a shell script to build a statically-linked mold
|
||||
# executable using Docker, so that it is easy to build mold
|
||||
# on non-Ubuntu 20.04 machines.
|
||||
#
|
||||
# A docker image used for building mold is persistent.
|
||||
# Run `docker image rm mold-build-ubuntu20` to remove the image
|
||||
# from disk.
|
||||
|
||||
set -e
|
||||
|
||||
# If the existing file is not statically-linked, remove it.
|
||||
[ -f mold ] && ! ldd mold 2>&1 | grep -q 'not a dynamic executable' && \
|
||||
rm mold
|
||||
|
||||
cat <<EOF | docker build -t mold-build-ubuntu20 -
|
||||
FROM ubuntu:20.04
|
||||
RUN apt-get update && \
|
||||
TZ=Europe/London apt-get install -y tzdata && \
|
||||
apt-get install -y build-essential git clang lld cmake \
|
||||
libstdc++-10-dev libxxhash-dev zlib1g-dev libssl-dev && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
EOF
|
||||
|
||||
if ! [ -d oneTBB ]; then
|
||||
git clone https://github.com/oneapi-src/oneTBB.git
|
||||
(cd oneTBB; git checkout --quiet v2020.3)
|
||||
fi
|
||||
|
||||
docker run -it --rm -v `pwd`/oneTBB:/oneTBB -u $(id -u):$(id -g) \
|
||||
mold-build-ubuntu20 \
|
||||
make -C /oneTBB -j$(nproc) extra_inc=big_iron.inc
|
||||
|
||||
tbb_bindir=$(ls -d oneTBB/build/linux_intel64_*_release)
|
||||
|
||||
docker run -it --rm -v `pwd`:/mold -u $(id -u):$(id -g) \
|
||||
mold-build-ubuntu20 \
|
||||
make -C /mold -j$(nproc) \
|
||||
EXTRA_CPPFLAGS=-IoneTBB/include \
|
||||
EXTRA_LDFLAGS="-fuse-ld=lld -static -L$tbb_bindir"
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename C>
|
||||
std::vector<std::string_view>
|
||||
read_response_file(C &ctx, std::string_view path) {
|
||||
std::vector<std::string_view> vec;
|
||||
MappedFile<C> *mf = MappedFile<C>::must_open(ctx, std::string(path));
|
||||
u8 *data = mf->data;
|
||||
|
||||
auto read_quoted = [&](i64 i, char quote) {
|
||||
std::string buf;
|
||||
while (i < mf->size && data[i] != quote) {
|
||||
if (data[i] == '\\') {
|
||||
buf.append(1, data[i + 1]);
|
||||
i += 2;
|
||||
} else {
|
||||
buf.append(1, data[i++]);
|
||||
}
|
||||
}
|
||||
if (i >= mf->size)
|
||||
Fatal(ctx) << path << ": premature end of input";
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i + 1;
|
||||
};
|
||||
|
||||
auto read_unquoted = [&](i64 i) {
|
||||
std::string buf;
|
||||
while (i < mf->size && !isspace(data[i]))
|
||||
buf.append(1, data[i++]);
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i;
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < mf->size;) {
|
||||
if (isspace(data[i]))
|
||||
i++;
|
||||
else if (data[i] == '\'')
|
||||
i = read_quoted(i + 1, '\'');
|
||||
else if (data[i] == '\"')
|
||||
i = read_quoted(i + 1, '\"');
|
||||
else
|
||||
i = read_unquoted(i);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
std::vector<std::string_view> expand_response_files(C &ctx, char **argv) {
|
||||
std::vector<std::string_view> vec;
|
||||
for (i64 i = 0; argv[i]; i++) {
|
||||
if (argv[i][0] == '@')
|
||||
append(vec, read_response_file(ctx, argv[i] + 1));
|
||||
else
|
||||
vec.push_back(argv[i]);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+92
-10
@@ -17,6 +17,10 @@
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#define CHECK(fn) do { int r = (fn); assert(r == Z_OK); } while (0)
|
||||
|
||||
namespace mold {
|
||||
|
||||
static constexpr i64 SHARD_SIZE = 1024 * 1024;
|
||||
|
||||
static std::vector<std::string_view> split(std::string_view input) {
|
||||
@@ -33,13 +37,14 @@ static std::vector<std::string_view> split(std::string_view input) {
|
||||
|
||||
static std::vector<u8> do_compress(std::string_view input) {
|
||||
// Initialize zlib stream. Since debug info is generally compressed
|
||||
// pretty well, we chose compression level 3.
|
||||
// pretty well with lower compression levels, we chose compression
|
||||
// level 1.
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
int r = deflateInit2(&strm, 3, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
|
||||
assert(r == Z_OK);
|
||||
|
||||
CHECK(deflateInit2(&strm, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
|
||||
|
||||
// Set an input buffer
|
||||
strm.avail_in = input.size();
|
||||
@@ -49,19 +54,42 @@ static std::vector<u8> do_compress(std::string_view input) {
|
||||
// on the compression size. +16 for Z_SYNC_FLUSH.
|
||||
std::vector<u8> buf(deflateBound(&strm, strm.avail_in) + 16);
|
||||
|
||||
// Compress data. It writes all compressed bytes except the last
|
||||
// partial byte, so up to 7 bits can be held to be written to the
|
||||
// buffer.
|
||||
strm.avail_out = buf.size();
|
||||
strm.next_out = buf.data();
|
||||
CHECK(deflate(&strm, Z_BLOCK));
|
||||
|
||||
// This is a workaround for libbacktrace before 2022-04-06.
|
||||
//
|
||||
// Zlib is a bit stream, and what Z_SYNC_FLUSH does is to write a
|
||||
// three bit value indicating the start of an uncompressed data
|
||||
// block followed by four byte data 00 00 ff ff which indicates that
|
||||
// the length of the block is zero. libbacktrace uses its own zlib
|
||||
// inflate routine, and it had a bug that if that particular three
|
||||
// bit value happens to end at a byte boundary, it accidentally
|
||||
// skipped the next byte.
|
||||
//
|
||||
// In order to avoid triggering that bug, we should avoid calling
|
||||
// deflate() with Z_SYNC_FLUSH if the current bit position is 5.
|
||||
// If it's 5, we insert an empty block consisting of 10 bits so
|
||||
// that the bit position is 7 in the next byte.
|
||||
//
|
||||
// https://github.com/ianlancetaylor/libbacktrace/pull/87
|
||||
int nbits;
|
||||
deflatePending(&strm, Z_NULL, &nbits);
|
||||
if (nbits == 5)
|
||||
CHECK(deflatePrime(&strm, 10, 2));
|
||||
CHECK(deflate(&strm, Z_SYNC_FLUSH));
|
||||
|
||||
r = deflate(&strm, Z_SYNC_FLUSH);
|
||||
assert(r == Z_OK);
|
||||
assert(strm.avail_out > 0);
|
||||
|
||||
buf.resize(buf.size() - strm.avail_out);
|
||||
deflateEnd(&strm);
|
||||
return buf;
|
||||
}
|
||||
|
||||
Compressor::Compressor(std::string_view input) {
|
||||
ZlibCompressor::ZlibCompressor(std::string_view input) {
|
||||
std::vector<std::string_view> inputs = split(input);
|
||||
std::vector<u64> adlers(inputs.size());
|
||||
shards.resize(inputs.size());
|
||||
@@ -78,14 +106,14 @@ Compressor::Compressor(std::string_view input) {
|
||||
checksum = adler32_combine(checksum, adlers[i], inputs[i].size());
|
||||
}
|
||||
|
||||
i64 Compressor::size() const {
|
||||
i64 ZlibCompressor::size() const {
|
||||
i64 size = 2; // +2 for header
|
||||
for (const std::vector<u8> &shard : shards)
|
||||
size += shard.size();
|
||||
return size + 6; // +6 for trailer and checksum
|
||||
}
|
||||
|
||||
void Compressor::write_to(u8 *buf) {
|
||||
void ZlibCompressor::write_to(u8 *buf) {
|
||||
// Write a zlib-format header
|
||||
buf[0] = 0x78;
|
||||
buf[1] = 0x9c;
|
||||
@@ -106,5 +134,59 @@ void Compressor::write_to(u8 *buf) {
|
||||
end[-5] = 0;
|
||||
|
||||
// Write a checksum
|
||||
write32be(end - 4, checksum);
|
||||
*(ubig32 *)(end - 4) = checksum;
|
||||
}
|
||||
|
||||
GzipCompressor::GzipCompressor(std::string_view input) {
|
||||
std::vector<std::string_view> inputs = split(input);
|
||||
std::vector<u32> crc(inputs.size());
|
||||
shards.resize(inputs.size());
|
||||
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
crc[i] = crc32(0, (u8 *)inputs[i].data(), inputs[i].size());
|
||||
shards[i] = do_compress(inputs[i]);
|
||||
});
|
||||
|
||||
// Combine checksums
|
||||
checksum = crc[0];
|
||||
for (i64 i = 1; i < inputs.size(); i++)
|
||||
checksum = crc32_combine(checksum, crc[i], inputs[i].size());
|
||||
|
||||
uncompressed_size = input.size();
|
||||
}
|
||||
|
||||
i64 GzipCompressor::size() const {
|
||||
i64 size = 10; // +10 for header
|
||||
for (const std::vector<u8> &shard : shards)
|
||||
size += shard.size();
|
||||
return size + 10; // +10 for trailer and checksum
|
||||
}
|
||||
|
||||
void GzipCompressor::write_to(u8 *buf) {
|
||||
// Write a zlib-format header
|
||||
memset(buf, 0, 10);
|
||||
buf[0] = 0x1f; // magic
|
||||
buf[1] = 0x8b; // magic
|
||||
buf[2] = 0x08; // compression method is zlib
|
||||
buf[9] = 0xff; // made on unknown OS
|
||||
|
||||
// Copy compressed data
|
||||
std::vector<i64> offsets(shards.size());
|
||||
offsets[0] = 10; // +10 for header
|
||||
for (i64 i = 1; i < shards.size(); i++)
|
||||
offsets[i] = offsets[i - 1] + shards[i - 1].size();
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
|
||||
memcpy(&buf[offsets[i]], shards[i].data(), shards[i].size());
|
||||
});
|
||||
|
||||
// Write a trailer
|
||||
u8 *end = buf + size();
|
||||
end[-10] = 0x3; // two-byte zlib stream terminator
|
||||
end[-9] = 0;
|
||||
*(u32 *)(end - 8) = checksum;
|
||||
*(u32 *)(end - 4) = uncompressed_size;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cxxabi.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string_view demangle(std::string_view name) {
|
||||
if (name.starts_with("_Z")) {
|
||||
static thread_local char *buf;
|
||||
static thread_local size_t buflen;
|
||||
|
||||
int status;
|
||||
char *p = abi::__cxa_demangle(std::string(name).c_str(), buf, &buflen, &status);
|
||||
if (status == 0) {
|
||||
buf = p;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash -x
|
||||
# This script creates a mold binary distribution. The output is
|
||||
# written in this directory as `mold-$version-$arch-linux.tar.gz`
|
||||
# (e.g. `mold-1.0.3-x86_64-linux.tar.gz`).
|
||||
#
|
||||
# The mold executable created by this script is statically-linked to
|
||||
# libstdc++ and libcrypto but dynamically-linked to libc, libm, libz
|
||||
# and librt, as they almost always exist on any Linux systems.
|
||||
|
||||
set -e
|
||||
|
||||
version=$(grep '^VERSION =' $(dirname $0)/Makefile | sed 's/.* = //')
|
||||
arch=$(uname -m)
|
||||
dest=mold-$version-$arch-linux
|
||||
|
||||
if [ $arch = x86_64 ]; then
|
||||
image=rui314/mold-builder:v1-x86_64
|
||||
elif [ $arch = aarch64 -o $arch = arm64 ]; then
|
||||
image=rui314/mold-builder:v1-aarch64
|
||||
else
|
||||
echo "Error: no docker image for $arch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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 &&
|
||||
tar czf /mold/$dest.tar.gz $dest &&
|
||||
cp mold /mold"
|
||||
+2
-2
@@ -31,7 +31,7 @@ If a program is statically-linked, there's no dynamic loader that
|
||||
rewrites the GOT entries. Therefore, if a program is
|
||||
statically-linked, a libc's startup routine does that on behalf of the
|
||||
dynamic loader. Concretely, a startup routine interprets all dynamic
|
||||
relocations between `__rela_iplt_start` and `__rela_iplt_start`
|
||||
relocations between `__rela_iplt_start` and `__rela_iplt_stop`
|
||||
symbols. It is linker's responsibility to emit dynamic relocations
|
||||
for IFUNC symbols even if it is linking a statically-linked program
|
||||
and mark the beginning and the ending of a `.rela.dyn` section with
|
||||
@@ -41,7 +41,7 @@ The bug was my linker didn't define `__rela_iplt_start` and
|
||||
`__rela_iplt_stop` symbols. Since these symbols are weak, they are
|
||||
initialized to zero. From the point of the initializer function,
|
||||
there's no dynamic relocations between `__rela_iplt_start` and
|
||||
`__rela_iplt_start` symbols. That left GOT entries for IFUNC symbols
|
||||
`__rela_iplt_stop` symbols. That left GOT entries for IFUNC symbols
|
||||
untouched.
|
||||
|
||||
The proper fix was to emit dynamic relocations for IFUNC symbols and
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# Coding Guidelines
|
||||
|
||||
mold is written in C++20, but as is the case with every C++ project,
|
||||
it has local coding rules. In this document, I'll explain some of them
|
||||
and try to give justifications for why I chose such rules.
|
||||
|
||||
## DOs
|
||||
|
||||
- Always use `i64` (which is a type alias for `int64_t` in mold) for
|
||||
integers unless you have a reason not to. For example, even if you know
|
||||
that a loop counter won't exceed 100, you should stop thinking about it
|
||||
and just use `i64`.
|
||||
|
||||
Justification: Local variables are usually on CPU registers, so on
|
||||
64-bit CPUs, there's no performance peanlty on choosing `i64` over
|
||||
`i32`. Even if a compiler has to spill register values to the stack,
|
||||
I don't think there's an observable difference between `i32` and
|
||||
`i64`. Therefore, extra 32 bits are essentially free. On 32-bit CPUs,
|
||||
they are not free, but that's OK because we are writing mold for modern
|
||||
computers. mold will still run on 32-bit computers but a bit slowly.
|
||||
|
||||
By always using `i64`, we can eliminate the need to think about the
|
||||
"right" size for each variable. It also reduces the risk of integer
|
||||
overflow.
|
||||
|
||||
Exceptions: If you have to allocate a very large number (e.g. millions)
|
||||
of the same object, its size matters. In that case, use a smaller type.
|
||||
|
||||
## DON'Ts
|
||||
|
||||
- Don't use `auto` unless its actual type is obvious in the very narrow
|
||||
context. Currently, we use `auto` only for lambdas.
|
||||
|
||||
Justification: I think `auto` makes code writing easier but code reading
|
||||
harder, because readers have to make a guess as to what is the actual
|
||||
type of `auto`. If you are already familiar with the existing codebase,
|
||||
you may be able to guess it easily, but that's not always the case.
|
||||
I want to keep the mold codebase friendly to first-time readers.
|
||||
|
||||
- Don't over-use inheritance. In mold, most classes don't have parents,
|
||||
and even if they do, their class hierarchy is very shallow. Currently
|
||||
its height is just two (i.e. abstract classes and their implementations).
|
||||
|
||||
Justification: Designing class hierarchies is fun as it feels like
|
||||
taxonomy, but I don't think that always help writing code. It looks like
|
||||
simpler class hierarchy makes its code simpler.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
+486
@@ -0,0 +1,486 @@
|
||||
## Design and implementation of mold
|
||||
|
||||
For the rest of this documentation, I'll explain the design and the
|
||||
implementation of mold. If you are only interested in using mold, you
|
||||
don't need to read the below.
|
||||
|
||||
### Motivation
|
||||
|
||||
Here is why I'm writing a new linker:
|
||||
|
||||
- Even though lld has significantly improved the situation, linking is
|
||||
still one of the slowest steps in a build. It is especially
|
||||
annoying when I changed one line of code and had to wait for a few
|
||||
seconds or even more for a linker to complete. It should be
|
||||
instantaneous. There's a need for a faster linker.
|
||||
|
||||
- The number of cores on a PC has increased a lot lately, and this
|
||||
trend is expected to continue. However, the existing linkers can't
|
||||
take the advantage of the trend because they don't scale well for more
|
||||
cores. I have a 64-core/128-thread machine, so my goal is to create
|
||||
a linker that uses the CPU nicely. mold should be much faster than
|
||||
other linkers on 4 or 8-core machines too, though.
|
||||
|
||||
- It looks to me that the designs of the existing linkers are somewhat
|
||||
too similar, and I believe there are a lot of drastically different
|
||||
designs that haven't been explored yet. Developers generally don't
|
||||
care about linkers as long as they work correctly, and they don't
|
||||
even think about creating a new one. So there may be lots of low
|
||||
hanging fruits there in this area.
|
||||
|
||||
### Basic design
|
||||
|
||||
- In order to achieve a `cp`-like performance, the most important
|
||||
thing is to fix the layout of an output file as quickly as possible, so
|
||||
that we can start copying actual data from input object files to an
|
||||
output file as soon as possible.
|
||||
|
||||
- Copying data from input files to an output file is I/O-bounded, so
|
||||
there should be room for doing computationally-intensive tasks while
|
||||
copying data from one file to another.
|
||||
|
||||
- We should allow the linker to preload object files from disk and
|
||||
parse them in memory before a complete set of input object files
|
||||
is ready. To do so, we need
|
||||
to split the linker into two in such a way that the latter half of
|
||||
the process finishes as quickly as possible by speculatively parsing
|
||||
and preprocessing input files in the first half of the process.
|
||||
|
||||
- One of the most computationally-intensive stage among linker stages
|
||||
is symbol resolution. To resolve symbols, we basically have to throw
|
||||
all symbol strings into a hash table to match undefined symbols with
|
||||
defined symbols. But this can be done in the preloading stage using
|
||||
[string interning](https://en.wikipedia.org/wiki/String_interning).
|
||||
|
||||
- Object files may contain a special section called a mergeable string
|
||||
section. The section contains lots of null-terminated strings, and
|
||||
the linker is expected to gather all mergeable string sections and
|
||||
merge their contents. So, if two object files contain the same
|
||||
string literal, for example, the resulting output will contain a
|
||||
single merged string. This step is computationally intensive, but string
|
||||
merging can be done in the preloading stage using string interning.
|
||||
|
||||
- Static archives (.a files) contain object files, but the static
|
||||
archive's string table contains only defined symbols of member
|
||||
object files and lacks other types of symbols. That makes static
|
||||
archives unsuitable for speculative parsing. Therefore, the linker
|
||||
should ignore the symbol table of static archive and directly read
|
||||
static archive members.
|
||||
|
||||
- If there's a relocation that uses a GOT of a symbol, then we have to
|
||||
create a GOT entry for that symbol. Otherwise, we shouldn't. That
|
||||
means we need to scan all relocation tables to fix the length and
|
||||
the contents of a .got section. This is computationally intensive,
|
||||
but this step is parallelizable.
|
||||
|
||||
### Linker Script
|
||||
|
||||
Linker script is an embedded language for the linker. It is mainly
|
||||
used to control how input sections are mapped to output sections and
|
||||
the layout of the output, but it can also do a lot of tricky stuff.
|
||||
Its feature is useful especially for embedded programming, but it's
|
||||
also an awfully underdocumented and complex language.
|
||||
|
||||
We have to implement a subset of the linker script language anwyay,
|
||||
because on Linux, /usr/lib/x86_64-linux-gnu/libc.so is (despite its
|
||||
name) not a shared object file but actually an ASCII file containing
|
||||
linker script code to load the _actual_ libc.so file. But the feature
|
||||
set for this purpose is very limited, and it is okay to implement them
|
||||
to mold.
|
||||
|
||||
Besides that, we really don't want to implement the linker script
|
||||
language. But at the same time, we want to satisfy the user needs that
|
||||
are currently satisfied with the linker script language. So, what
|
||||
should we do? Here is my observation:
|
||||
|
||||
- Linker script allows doing a lot of tricky stuff, such as specifying
|
||||
the exact layout of a file, inserting arbitrary bytes between
|
||||
sections, etc. But most of them can be done with a post-link binary
|
||||
editing tool (such as `objcopy`).
|
||||
|
||||
- It looks like there are two things that truly cannot be done by a
|
||||
post-link editing tool: (a) mapping input sections to output
|
||||
sections, and (b) applying relocations.
|
||||
|
||||
From the above observation, I believe we need to provide only the
|
||||
following features instead of the entire linker script language:
|
||||
|
||||
- A method to specify how input sections are mapped to output
|
||||
sections, and
|
||||
|
||||
- a method to set addresses to output sections, so that relocations
|
||||
are applied based on desired addresses.
|
||||
|
||||
I believe everything else can be done with a post-link binary editing
|
||||
tool.
|
||||
|
||||
### Details
|
||||
|
||||
- As we aim to the 1-second goal for Chromium, every millisecond
|
||||
counts. We can't ignore the latency of process exit. If we mmap a
|
||||
lot of files, \_exit(2) is not instantaneous but takes a few hundred
|
||||
milliseconds because the kernel has to clean up a lot of
|
||||
resources. As a workaround, we should organize the linker command as
|
||||
two processes; the first process forks the second process, and the
|
||||
second process does the actual work. As soon as the second process
|
||||
writes a result file to a filesystem, it notifies the first process,
|
||||
and the first process exits. The second process can take time to
|
||||
exit, because it is not an interactive process.
|
||||
|
||||
- At least on Linux, it looks like the filesystem's performance to
|
||||
allocate new blocks to a new file is the limiting factor when
|
||||
creating a new large file and filling its contents using mmap.
|
||||
If you already have a large file in the buffer cache, writing to it is
|
||||
much faster than creating a new fresh file and writing to it.
|
||||
Based on this observation, mold overwrites to an existing
|
||||
executable file if exists. My quick benchmark showed that I could
|
||||
save 300 milliseconds when creating a 2 GiB output file.
|
||||
Linux doesn't allow to open an executable for writing if it is
|
||||
running (you'll get a "text busy" error if you attempt). mold
|
||||
falls back to the usual way if it fails to open an output file.
|
||||
|
||||
- The output from the linker should be deterministic for the sake of
|
||||
[build reproducibility](https://en.wikipedia.org/wiki/Reproducible_builds)
|
||||
and ease of debugging. This might add a little bit of overhead to
|
||||
the linker, but that shouldn't be too much.
|
||||
|
||||
- A .build-id, a unique ID embedded to an output file, is usually
|
||||
computed by applying a cryptographic hash function (e.g. SHA-1) to
|
||||
an output file. This is a slow step, but we can speed it up by
|
||||
splitting a file into small chunks, computing SHA-1 for each chunk,
|
||||
and then computing SHA-1 of the concatenated SHA-1 hashes
|
||||
(i.e. constructing a [Merkle
|
||||
Tree](https://en.wikipedia.org/wiki/Merkle_tree) of height 2).
|
||||
Modern x86 processors have purpose-built instructions for SHA-1 and
|
||||
can compute SHA-1 pretty quickly at about 2 GiB/s. Using 16
|
||||
cores, a build-id for a 2 GiB executable can be computed in 60 to 70
|
||||
milliseconds.
|
||||
|
||||
- BFD, gold, and lld support section garbage collection. That is, a
|
||||
linker runs a mark-sweep garbage collection on an input graph, where
|
||||
sections are vertices and relocations are edges, to discard all
|
||||
sections that are not reachable from the entry point symbol
|
||||
(i.e. `_start`) or a few other root sections. In mold, we are using
|
||||
multiple threads to mark sections concurrently.
|
||||
|
||||
- Similarly, BFD, gold an lld support Identical Comdat Folding (ICF)
|
||||
as yet another size optimization. ICF merges two or more read-only
|
||||
sections that happen to have the same contents and relocations.
|
||||
To do that, we have to find isomorphic subgraphs from larger graphs.
|
||||
I implemented a new algorithm for mold, which is 5x faster than lld
|
||||
to do ICF for Chromium (from 5 seconds to 1 second).
|
||||
|
||||
- [Intel Threading Building
|
||||
Blocks](https://github.com/oneapi-src/oneTBB) (TBB) is a good
|
||||
library for parallel execution and has several concurrent
|
||||
containers. We are particularly interested in using
|
||||
`parallel_for_each` and `concurrent_hash_map`.
|
||||
|
||||
- TBB provides `tbbmalloc` which works better for multi-threaded
|
||||
applications than the glib'c malloc, but it looks like
|
||||
[jemalloc](https://github.com/jemalloc/jemalloc) and
|
||||
[mimalloc](https://github.com/microsoft/mimalloc) are a little bit
|
||||
more scalable than `tbbmalloc`.
|
||||
|
||||
### Size of the problem
|
||||
|
||||
When linking Chrome, a linker reads 3,430,966,844 bytes of data in
|
||||
total. The data contains the following items:
|
||||
|
||||
| Data item | Number
|
||||
| ------------------------ | ------
|
||||
| Object files | 30,723
|
||||
| Public undefined symbols | 1,428,149
|
||||
| Mergeable strings | 1,579,996
|
||||
| Comdat groups | 9,914,510
|
||||
| Regular sections¹ | 10,345,314
|
||||
| Public defined symbols | 10,512,135
|
||||
| Symbols | 23,953,607
|
||||
| Sections | 27,543,225
|
||||
| Relocations against SHF_ALLOC sections | 39,496,375
|
||||
| Relocations | 62,024,719
|
||||
|
||||
¹ Sections that have to be copied from input object files to an
|
||||
output file. Sections that contain relocations or symbols are for
|
||||
example excluded.
|
||||
|
||||
### Internals
|
||||
|
||||
In this section, I'll explain the internals of mold linker.
|
||||
|
||||
#### A brief history of Unix and the Unix linker
|
||||
|
||||
Conceptually, what a linker does is pretty simple. A compiler compiles
|
||||
a fragment of a program (a single source file) into a fragment of
|
||||
machine code and data (an object file, which typically has the .o
|
||||
extension), and a linker stitches them together into a single
|
||||
executable or a shared library image.
|
||||
|
||||
In reality, modern linkers for Unix-like systems are much more
|
||||
complicated than the naive understanding because they have gradually
|
||||
gained one feature at a time over the 50 years history of Unix, and
|
||||
they are now something like a bag of lots of miscellaneous features in
|
||||
which none of the features is more important than the others. It is
|
||||
very easy to miss the forest for the trees, since for those who don't
|
||||
know the details of the Unix linker, it is not clear which feature is
|
||||
essential and which is not.
|
||||
|
||||
That being said, one thing is clear that at any point of Unix history,
|
||||
a Unix linker has a coherent feature set for the Unix of that age. So,
|
||||
let me entangle the history to see how the operating system, runtime,
|
||||
and linker have gained features that we see today. That should give
|
||||
you an idea of why a particular feature has been added to a linker in the
|
||||
first place.
|
||||
|
||||
1. Original Unix didn't support shared libraries, and a program was
|
||||
always loaded to a fixed address. An executable was something like
|
||||
a memory dump that was just loaded to a particular address by the
|
||||
kernel. After loading, the kernel started executing the program by
|
||||
setting the instruction pointer to a particular address.
|
||||
|
||||
The most essential feature for any linker is relocation processing.
|
||||
The original Unix linker of course supported that. Let me explain
|
||||
what that is.
|
||||
|
||||
Individual object files are inevitably incomplete as a program,
|
||||
because when a compiler created them, it only see a part of an
|
||||
entire program. For example, if an object file contains a function
|
||||
call that refers to another object file, the `call` instruction in the
|
||||
object cannot be complete, as the compiler has no idea as to what
|
||||
is the called function's address. To deal with this, the compiler
|
||||
emits a placeholder value (typically just zero) instead of a real
|
||||
address and leaves metadata in an object file saying "fix offset X
|
||||
of this file with an address of Y". That metadata is called
|
||||
"relocation". Relocations are typically processed by the linker.
|
||||
|
||||
It is easy for a linker to apply relocations for the original Unix
|
||||
because a program is always loaded to a fixed address. It exactly
|
||||
knows the addresses of all functions and data when linking a
|
||||
program.
|
||||
|
||||
Static library support, which is still an important feature of Unix
|
||||
linker, also dates back to this early period of Unix history.
|
||||
To understand what it is, imagine that you are trying to compile
|
||||
a program for the early Unix. You don't want to waste time to
|
||||
compile libc functions every time you compile your program (the
|
||||
computers of the era were incredibly slow), so you have already
|
||||
placed each libc function into a separate source file and compiled
|
||||
them individually. That means you have object files for each libc
|
||||
function, e.g., printf.o, scanf.o, atoi.o, write.o, etc.
|
||||
|
||||
Given this configuration, all you have to do to link your program
|
||||
against libc functions is to pick up the right set of libc object
|
||||
files and give them to the linker along with the object files of your
|
||||
program. But, keeping the linker command line in sync with the
|
||||
libc functions you are using in your program is bothersome. You can
|
||||
be conservative; you can specify all libc object files to the
|
||||
command line, but that leads to program bloat because the linker
|
||||
unconditionally link all object files given to it no matter whether
|
||||
they are used or not. So, a new feature was added to the linker to
|
||||
fix the problem. That is the static library, which is also called
|
||||
the archive file.
|
||||
|
||||
An archive file is just a bundle of object files, just like zip
|
||||
file but in an uncompressed form. An archive file typically has the
|
||||
.a file extension and named after its contents. For example, the
|
||||
archive file containing all libc objects is named `libc.a`.
|
||||
|
||||
If you pass an archive file along with other object files to the
|
||||
linker, the linker pulls out an object file from the archive _only
|
||||
when_ it is referenced by other object files. In other words,
|
||||
unlike object files directly given to a linker, object files
|
||||
wrapped in an archive are not linked to the output by default.
|
||||
An archive works as a supplement to complete your program.
|
||||
|
||||
Even today, you can still find a libc archive file. Run `ar t
|
||||
/usr/lib/x86_64-linux-gnu/libc.a` on Linux should give you a list
|
||||
of object files in the libc archive.
|
||||
|
||||
2. In the '80s, Sun Microsystems, a leading commercial Unix vendor at the
|
||||
time, added shared library support to their Unix variant, SunOS.
|
||||
|
||||
(This section is incomplete.)
|
||||
|
||||
### Concurrency strategy
|
||||
|
||||
In this section, I'll explain the high-level concurrency strategy of
|
||||
mold.
|
||||
|
||||
In most places, mold adopts data parallelism. That is, we have a huge
|
||||
number of pieces of data of the same kind, and we process each of them
|
||||
individually using parallel for-loop. For example, after identifying
|
||||
the exact set of input object files, we need to scan all relocation
|
||||
tables to determine the sizes of .got and .plt sections. We do that
|
||||
using a parallel for-loop. The granularity of parallel processing in
|
||||
this case is the relocation table.
|
||||
|
||||
Data parallelism is very efficient and scalable because there's no
|
||||
need for threads to communicate with each other while working on each
|
||||
element of data. In addition to that, data parallelism is easy to
|
||||
understand, as it is just a for-loop in which multiple iterations may
|
||||
be executed in parallel. We don't use high-level communication or
|
||||
synchronization mechanisms such as channels, futures, promises,
|
||||
latches or something like that in mold.
|
||||
|
||||
In some cases, we need to share a little bit of data between threads
|
||||
while executing a parallel for-loop. For example, the loop to scan
|
||||
relocations turns on "requires GOT" or "requires PLT" flags in a
|
||||
symbol. Symbol is a shared resource, and writing to them from multiple
|
||||
threads without synchronization is unsafe. To deal with it, we made
|
||||
the flag an atomic variable.
|
||||
|
||||
The other common pattern you can find in mold which is build on top of
|
||||
the parallel for-loop is the map-reduce pattern. That is, we run a
|
||||
parallel for-loop on a large data set to produce a small data set and
|
||||
process the small data set with a single thread. Let me take a
|
||||
build-id computation as an example. Build-id is typically computed by
|
||||
applying a cryptographic hash function such as SHA-1 on a linker's
|
||||
output file. To compute it, we first consider an output as a sequence
|
||||
of 1 MiB blocks and compute a SHA-1 hash for each block in parallel.
|
||||
Then, we concatenate the SHA-1 hashes and compute a SHA-1 hash on the
|
||||
hashes to get a final build-id.
|
||||
|
||||
Finally, we use concurrent hashmap at a few places in mold. Concurrent
|
||||
hashmap is a hashmap to which multiple threads can safely insert items
|
||||
in parallel. We use it in the symbol resolution stage, for example.
|
||||
To resolve symbols, we basically have to throw in all defined symbols
|
||||
into a hash table, so that we can find a matching defined symbol for
|
||||
an undefined symbol by name. We do the hash table insertion from a
|
||||
parallel for-loop which iterates over a list of input files.
|
||||
|
||||
Overall, even though mold is highly scalable, it succeeded to avoid
|
||||
complexties you often find in complex parallel programs. From high
|
||||
level, mold just serially executes the linker's internal passes one by
|
||||
one. Each pass is parallelized using parallel for-loops.
|
||||
|
||||
### Rejected ideas
|
||||
|
||||
In this section, I'll explain the alternative designs I currently do
|
||||
not plan to implement and why I turned them down.
|
||||
|
||||
- Placing variable-length sections at end of an output file and start
|
||||
copying file contents before fixing the output file layout
|
||||
|
||||
Idea: Fixing the layout of regular sections seems easy, and if we
|
||||
place them at beginning of a file, we can start copying their
|
||||
contents from their input files to an output file. While copying
|
||||
file contents, we can compute the sizes of variable-length sections
|
||||
such as .got or .plt and place them at end of the file.
|
||||
|
||||
Reason for rejection: I did not choose this design because I doubt
|
||||
if it could actually shorten link time and I think I don't need it
|
||||
anyway.
|
||||
|
||||
The linker has to de-duplicate comdat sections (i.e. inline
|
||||
functions that are included in multiple object files), so we
|
||||
cannot compute the layout of regular sections until we resolve all
|
||||
symbols and de-duplicate comdats. That takes a few hundred
|
||||
milliseconds. After that, we can compute the sizes of
|
||||
variable-length sections in less than 100 milliseconds. It's quite
|
||||
fast, so it doesn't seem to make much sense to proceed without
|
||||
fixing the final file layout.
|
||||
|
||||
The other reason to reject this idea is because there's good a
|
||||
chance for this idea to have a negative impact on linker's overall
|
||||
performance. If we copy file contents before fixing the layout, we
|
||||
can't apply relocations to them while copying because symbol
|
||||
addresses are not available yet. If we fix the file layout first, we
|
||||
can apply relocations while copying, which is effectively zero-cost
|
||||
due to a very good data locality. On the other hand, if we apply
|
||||
relocations long after we copy file contents, it's pretty expensive
|
||||
because section contents are very likely to have been evicted from
|
||||
CPU cache.
|
||||
|
||||
- Incremental linking
|
||||
|
||||
Idea: Incremental linking is a technique to patch a previous linker's
|
||||
output file so that only functions or data that are updated from the
|
||||
previous build are written to it. It is expected to significantly
|
||||
reduce the amount of data copied from input files to an output file
|
||||
and thus speed up linking. GNU BFD and gold linkers support it.
|
||||
|
||||
Reason for rejection: I turned it down because it (1) is
|
||||
complicated, (2) doesn't seem to speed it up that much and (3) has
|
||||
several practical issues. Let me explain each of them.
|
||||
|
||||
First, incremental linking for real C/C++ programs is not as easy as
|
||||
one might think. Let me take malloc as an example. malloc is usually
|
||||
defined by libc, but you can implement it in your program, and if
|
||||
that's the case, the symbol `malloc` will be resolved to your
|
||||
function instead of the one in libc. If you include a library that
|
||||
defines malloc (such as libjemalloc or libtbbmallc) before libc,
|
||||
their malloc will override libc's malloc.
|
||||
|
||||
Assume that you are using a nonstandard malloc. What if you remove
|
||||
your malloc from your code, or remove `-ljemalloc` from your
|
||||
Makefile? The linker has to include a malloc from libc, which may
|
||||
include more object files to satisfy its dependencies. Such code
|
||||
change can affect the entire program rather than just replacing one
|
||||
function. The same is true for adding malloc to your program. Making
|
||||
a local change doesn't necessarily result in a local change in the
|
||||
binary level. It can easily have cascading effects.
|
||||
|
||||
Some ELF fancy features make incremental linking even harder to
|
||||
implement. Take the weak symbol as an example. If you define `atoi`
|
||||
as a weak symbol in your program, and if you are not using `atoi`
|
||||
at all in your program, that symbol will be resolved to address
|
||||
0. But if you start using some libc function that indirectly calls
|
||||
`atoi`, then `atoi` will be included in your program, and your weak
|
||||
symbol will be resolved to that function. I don't know how to
|
||||
efficiently fix up a binary for this case.
|
||||
|
||||
This is a hard problem, so existing linkers don't try too hard to
|
||||
solve it. For example, IIRC, gold falls back to full link if any
|
||||
function is removed from a previous build. If you want to not annoy
|
||||
users in the fallback case, you need to make full link fast anyway.
|
||||
|
||||
Second, incremental linking itself has an overhead. It has to detect
|
||||
updated files, patch an existing output file and write additional
|
||||
data to an output file for future incremental linking. GNU gold, for
|
||||
instance, takes almost 30 seconds on my machine to do a null
|
||||
incremental link (i.e. no object files are updated from a previous
|
||||
build) for chrome. It's just too slow.
|
||||
|
||||
Third, there are other practical issues in incremental linking. It's
|
||||
not reproducible, so your binary isn't going to be the same as other
|
||||
binaries even if you are compiling the same source tree using the
|
||||
same compiler toolchain. Or, it is complex and there might be a bug
|
||||
in it. If something doesn't work correctly, "remove --incremental
|
||||
from your Makefile and try again" could be a piece of advice, but
|
||||
that isn't ideal.
|
||||
|
||||
So, all in all, incremental linking is tricky. I wanted to make full
|
||||
link as fast as possible, so that we don't have to think about how
|
||||
to work around the slowness of full link.
|
||||
|
||||
- Defining a completely new file format and use it
|
||||
|
||||
Idea: Sometimes, the ELF file format itself seems to be a limiting
|
||||
factor in improving the linker's performance. We might be able to make a
|
||||
far better one if we create a new file format.
|
||||
|
||||
Reason for rejection: I rejected the idea because it apparently has
|
||||
a practical issue (backward compatibility issue) and also doesn't
|
||||
seem to improve the performance of linkers that much. As clearly
|
||||
demonstrated by mold, we can create a fast linker for ELF. I believe
|
||||
ELF isn't that bad, after all. The semantics of the existing Unix
|
||||
linkers, such as the name resolution algorithm or the linker script,
|
||||
have slowed the linkers down, but that's not a problem of the file
|
||||
format itself.
|
||||
|
||||
- Watching object files using inotify(2)
|
||||
|
||||
Idea: When mold is running as a daemon for preloading, use
|
||||
inotify(2) to watch file system updates so that it can reload files
|
||||
as soon as they are updated.
|
||||
|
||||
Reason for rejection: Just like the maximum number of files you can
|
||||
simultaneously open, the maximum number of files you can watch using
|
||||
inotify(2) isn't that large. Maybe just a single instance of mold is
|
||||
fine with inotify(2), but it may fail if you run multiple of it.
|
||||
|
||||
The other reason for not doing it is because mold is quite fast
|
||||
without it anyway. Invoking stat(2) on each file for file update
|
||||
check takes less than 100 milliseconds for Chrome, and if most of
|
||||
the input files are not updated, parsing updated files takes almost
|
||||
no time.
|
||||
@@ -0,0 +1,55 @@
|
||||
This page explains the following warning message and how to fix it.
|
||||
mold emits this message when it sees an object file that may not be
|
||||
compatible with mold.
|
||||
|
||||
|
||||
```
|
||||
mold: warning: foo.o: 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.
|
||||
```
|
||||
|
||||
# Background
|
||||
|
||||
On modern computers, the stack area (to which local variables are
|
||||
stored) cannot contain executable code. If the control reaches the
|
||||
stack area, the CPU refuses to execute any code there and the program
|
||||
is usually terminated due to segmentation fault.
|
||||
|
||||
This is a security measure. The stack area used to be executable (old
|
||||
CPUs generally execute any code as long as it is in a readable memory
|
||||
region), but that provided an easy attack vector to a malicious user.
|
||||
They wrote executable code to the stack area using some buffer
|
||||
overflow bug and jumped there to run arbitrary code in a remote server
|
||||
process.
|
||||
|
||||
To prevent this type of attack, the stack area is no longer executable
|
||||
since the early 2000s. On Linux, the stack's executable-ness is
|
||||
controlled by a bit in an executable, and the loader respect that
|
||||
bit. The bit is set by the linker.
|
||||
|
||||
GCC had (and still have) a feature that depends on the executable
|
||||
stack, so they invented a way to tell the linker to mark the stack
|
||||
executable. Specifically, if an object file contains a
|
||||
`.note.GNU-stack` section with the `SHF_EXECSTACK` bit, GNU linker
|
||||
silently makes the stack of an output file executable.
|
||||
|
||||
But the GNU linker's behavior is dangerous. If you accidentally link
|
||||
an object file that has that marker section, the entire stack area
|
||||
silently becomes executable, disabling the security mechanism.
|
||||
|
||||
Therefore, mold simply ignores that marker section. If you are using
|
||||
mold, you need to explicitly pass `-z execstack` to the linker to make
|
||||
the stack executable.
|
||||
|
||||
# What caused this issue?
|
||||
|
||||
You are likely to use GCC's [Nested
|
||||
Functions](https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html)
|
||||
feature which still depends on the executable stack.
|
||||
|
||||
# How to fix it?
|
||||
|
||||
If you know what you are doing, pass `-z execstack` to mold. Beaware
|
||||
that this will significantly weaken your program's security.
|
||||
|
||||
If you don't want to pass `-z execstack`, rewrite your code so that
|
||||
your code does not depends on the executable stack.
|
||||
@@ -0,0 +1,158 @@
|
||||
The very concept of linking is simple: a compiler compiles a piece of
|
||||
source code into an object file (a file containing machine code), and
|
||||
a linker combines object files into a single executable or a shared
|
||||
library file. However, the actual implementation of the linker for
|
||||
modern systems is much more complicated because hardware, operating
|
||||
system, compiler and linker all have many more features.
|
||||
|
||||
In this file, I'll explain random topics in the glossary format that
|
||||
you need to understand to read mold code.
|
||||
|
||||
## DSO
|
||||
|
||||
A .so file. Short for Dynamic Shared Object. Often called as a
|
||||
shared library, a dynamic libray or a shared object as well.
|
||||
|
||||
An DSO contains common functions and data that are used by multiple
|
||||
executables and/or other DSOs. At runtime, a DSO is loaded to a
|
||||
contiguous region in the virtual address.
|
||||
|
||||
## Object file
|
||||
|
||||
A .o file. An object file contains machine code and data, but it
|
||||
cannot be executed because it's not self-contained. For example,
|
||||
if you compile a C source file containing a call of `printf`,
|
||||
the actual function code of `printf` is not included in the resulting
|
||||
object file. You include `stdio.h`, but that teaches the compiler
|
||||
only about `printf`'s type, and the compiler still don't know what
|
||||
`printf` actually does. Therefore, it cannot emit code for `printf`.
|
||||
|
||||
You need to link an object file with other object file or a shared
|
||||
library to make it exectuable.
|
||||
|
||||
## Virtual address space
|
||||
|
||||
A pointer has a value like 0x803020 which is an address of the
|
||||
pointee. But it doesn't mean that the pointee resides at the
|
||||
physical memory address 0x803020 on the computer. Modern CPUs
|
||||
contains so-called Mmeory Management Unit (MMU), and all access to
|
||||
the memory are first translated by MMU to the physical address.
|
||||
The address before translation is called the "virtual address".
|
||||
Unless you are doing the kernel programming, all addresses you
|
||||
handle are virtual addresses.
|
||||
|
||||
The OS kernel controls the MMU so that each process owns the entire
|
||||
virtual address space. So, even if two process uses the same virtual
|
||||
address, they don't conflict. They are mapped to different physical
|
||||
addresses.
|
||||
|
||||
The existence of MMU has several implications to the linker. First,
|
||||
we can link the main executable to a specific address. On process
|
||||
startup, there's no code or data in the virtual address space, so
|
||||
the mapping of the main executable always succeed. However, it's not
|
||||
true to DSOs because they are loaded after the main executable and
|
||||
possibly other DSOs. Therefore, shared libraries must be linked in a
|
||||
way that they can be loaded to any address in the virtual address
|
||||
space.
|
||||
|
||||
## Relocation
|
||||
|
||||
A piece of information for the linker as to how to link object files
|
||||
or a dynamic objects.
|
||||
|
||||
Object files can refer functions or data in other object files. For
|
||||
example, if you compile a function which calls a non-local function
|
||||
`foo`, the resulting code contains something like this:
|
||||
|
||||
```
|
||||
26: e8 00 00 00 00 callq 2b <bar+0xb>
|
||||
27: R_X86_64_PLT32 foo-0x4
|
||||
```
|
||||
|
||||
The above `callq` is the instruction to call a function at the
|
||||
machine code level. It's opcode is `0xe8` in x86-64, so the
|
||||
instruction begins with `0xe8`. The following four bytes are
|
||||
displacement; that is, the address of the branch target relative to
|
||||
the end of this `callq` instruction. Notice that the displacement is
|
||||
0. The compiler couldn't fill the displacement because it has no
|
||||
idea as to where `foo` will be at runtime. So, the compiler write 0
|
||||
as a placeholder and instead write a relocation `R_X86_64_PLT32`
|
||||
with `foo` as its associated symbol. The linker reads this
|
||||
relocation, computes the offsets between this call instruction and
|
||||
function `foo` and overwrite the placeholder value 0 with an actual
|
||||
displacement.
|
||||
|
||||
There are many different types of relocations. For example, if you
|
||||
want to fix up not with a displacement but with an absolute address
|
||||
of a symbol, you need to use `R_X86_64_ABS64` instead.
|
||||
|
||||
## Static library
|
||||
|
||||
A .a file. Often called as an archive file or just archive as well.
|
||||
|
||||
A static library is a container just like tar or zip. Actually,
|
||||
there's no technical reason to not use tar or (uncompressed) zip,
|
||||
but traditionally the .a file format is used by the linker.
|
||||
|
||||
A static library contains object files and can be passed to the
|
||||
linker along with other object files and/or archives.
|
||||
|
||||
A linker pulls out object files from an archive only if it is needed
|
||||
to resolve undefined symbols. In other words, object files in an
|
||||
archive are not linked by default and used as a complement to supply
|
||||
missing definitions. This is ideal for a library because you don't
|
||||
want to link library functions unless you are actually using them.
|
||||
|
||||
Contrary to archive files, object files directly given to a linker
|
||||
are always linked to the output.
|
||||
|
||||
To maximize the benefit of archive files, a library often used as a
|
||||
static library is broken down to small files to separate each
|
||||
function individually (for example, look at
|
||||
https://git.musl-libc.org/cgit/musl/tree/src/stdio). By doing this,
|
||||
you import only used functions.
|
||||
|
||||
A static file is created by `ar`, whose command line arguments are
|
||||
similar to `tar`. A static library contains the symbol table which
|
||||
offers a quick way to look up an object file for a defined symbol,
|
||||
but mold does not use the static library's symbol table. mold
|
||||
doesdn't need a symbol table to exist in an archive, and if exists,
|
||||
mold just ignores it.
|
||||
|
||||
See also: DSO (dynamic library)
|
||||
|
||||
## Symbol
|
||||
|
||||
A symbol is a label assigned to a specific location in an input file
|
||||
or an output file. For example, if you define function `foo` and
|
||||
compile it, the resulting object file contains a symbol `foo`
|
||||
pointing to the beginning of the machine code for `foo`.
|
||||
|
||||
Usually, a symbol name is a function or a variable name. If an
|
||||
object is anonymous (such the one for a string literal), a compiler
|
||||
generated a unique symbol, which often starts with `.` to avoid
|
||||
conflict with user-defined symbols.
|
||||
|
||||
For C++, symbol name is a complex "mangled" name. We need to mangle
|
||||
identifiers because a simple name such as `foo` cannot be uniquely
|
||||
identify a function or a data in C++, because for example `foo` may
|
||||
be in a namespace or defined as a static member in some class. If
|
||||
`foo` is an overloaded function, we need to distinguish different
|
||||
`foo`s by its type. Therefore, C++ compiler mangles an identifier by
|
||||
appending nmaepsace names, type information and such so that
|
||||
different things get different names.
|
||||
|
||||
For example, a function `int foo(int)` in a namespace `bar` is
|
||||
mangled as `_ZN3bar3fooEi`.
|
||||
|
||||
A symbol can be either defined or undefined. A defined symbol points
|
||||
to some location in a file which may contain the function's machine
|
||||
code or the variable's initial value. An undefined symbol does not
|
||||
point to anywhere. It needs to be merged with a defined symbol with
|
||||
the same name at link-time. This merging process is called "name
|
||||
resolution".
|
||||
|
||||
For example, if your program is using `printf`, it usually contains
|
||||
`printf` as an undefined symbol. You need to link it with `libc.a`
|
||||
or `libc.so`, which contain a defined symbol of `printf`, to make a
|
||||
complete program.
|
||||
+1059
-654
File diff suppressed because it is too large.
Load diff
@@ -1,702 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <endian.h>
|
||||
#include <string>
|
||||
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
#error "mold does not support big-endian hosts"
|
||||
#endif
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
|
||||
template <typename E>
|
||||
static std::string rel_to_string(u32 r_type);
|
||||
|
||||
struct X86_64;
|
||||
struct I386;
|
||||
|
||||
static constexpr u32 SHN_UNDEF = 0;
|
||||
static constexpr u32 SHN_ABS = 0xfff1;
|
||||
static constexpr u32 SHN_COMMON = 0xfff2;
|
||||
static constexpr u32 SHN_XINDEX = 0xffff;
|
||||
|
||||
static constexpr u32 SHT_NULL = 0;
|
||||
static constexpr u32 SHT_PROGBITS = 1;
|
||||
static constexpr u32 SHT_SYMTAB = 2;
|
||||
static constexpr u32 SHT_STRTAB = 3;
|
||||
static constexpr u32 SHT_RELA = 4;
|
||||
static constexpr u32 SHT_HASH = 5;
|
||||
static constexpr u32 SHT_DYNAMIC = 6;
|
||||
static constexpr u32 SHT_NOTE = 7;
|
||||
static constexpr u32 SHT_NOBITS = 8;
|
||||
static constexpr u32 SHT_REL = 9;
|
||||
static constexpr u32 SHT_SHLIB = 10;
|
||||
static constexpr u32 SHT_DYNSYM = 11;
|
||||
static constexpr u32 SHT_INIT_ARRAY = 14;
|
||||
static constexpr u32 SHT_FINI_ARRAY = 15;
|
||||
static constexpr u32 SHT_PREINIT_ARRAY = 16;
|
||||
static constexpr u32 SHT_GROUP = 17;
|
||||
static constexpr u32 SHT_SYMTAB_SHNDX = 18;
|
||||
static constexpr u32 SHT_GNU_HASH = 0x6ffffff6;
|
||||
static constexpr u32 SHT_GNU_VERDEF = 0x6ffffffd;
|
||||
static constexpr u32 SHT_GNU_VERNEED = 0x6ffffffe;
|
||||
static constexpr u32 SHT_GNU_VERSYM = 0x6fffffff;
|
||||
static constexpr u32 SHT_X86_64_UNWIND = 0x70000001;
|
||||
|
||||
static constexpr u32 SHF_WRITE = 0x1;
|
||||
static constexpr u32 SHF_ALLOC = 0x2;
|
||||
static constexpr u32 SHF_EXECINSTR = 0x4;
|
||||
static constexpr u32 SHF_MERGE = 0x10;
|
||||
static constexpr u32 SHF_STRINGS = 0x20;
|
||||
static constexpr u32 SHF_INFO_LINK = 0x40;
|
||||
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_EXCLUDE = 0x80000000;
|
||||
|
||||
static constexpr u32 GRP_COMDAT = 1;
|
||||
|
||||
static constexpr u32 STT_NOTYPE = 0;
|
||||
static constexpr u32 STT_OBJECT = 1;
|
||||
static constexpr u32 STT_FUNC = 2;
|
||||
static constexpr u32 STT_SECTION = 3;
|
||||
static constexpr u32 STT_FILE = 4;
|
||||
static constexpr u32 STT_COMMON = 5;
|
||||
static constexpr u32 STT_TLS = 6;
|
||||
static constexpr u32 STT_GNU_IFUNC = 10;
|
||||
|
||||
static constexpr u32 STB_LOCAL = 0;
|
||||
static constexpr u32 STB_GLOBAL = 1;
|
||||
static constexpr u32 STB_WEAK = 2;
|
||||
static constexpr u32 STB_GNU_UNIQUE = 10;
|
||||
|
||||
static constexpr u32 STV_DEFAULT = 0;
|
||||
static constexpr u32 STV_INTERNAL = 1;
|
||||
static constexpr u32 STV_HIDDEN = 2;
|
||||
static constexpr u32 STV_PROTECTED = 3;
|
||||
|
||||
static constexpr u32 VER_NDX_LOCAL = 0;
|
||||
static constexpr u32 VER_NDX_GLOBAL = 1;
|
||||
static constexpr u32 VER_NDX_LAST_RESERVED = 1;
|
||||
|
||||
static constexpr u32 VER_FLG_BASE = 1;
|
||||
static constexpr u32 VER_FLG_WEAK = 2;
|
||||
static constexpr u32 VER_FLG_INFO = 4;
|
||||
|
||||
static constexpr u32 VERSYM_HIDDEN = 0x8000;
|
||||
|
||||
static constexpr u32 PT_NULL = 0;
|
||||
static constexpr u32 PT_LOAD = 1;
|
||||
static constexpr u32 PT_DYNAMIC = 2;
|
||||
static constexpr u32 PT_INTERP = 3;
|
||||
static constexpr u32 PT_NOTE = 4;
|
||||
static constexpr u32 PT_SHLIB = 5;
|
||||
static constexpr u32 PT_PHDR = 6;
|
||||
static constexpr u32 PT_TLS = 7;
|
||||
static constexpr u32 PT_GNU_EH_FRAME = 0x6474e550;
|
||||
static constexpr u32 PT_GNU_STACK = 0x6474e551;
|
||||
static constexpr u32 PT_GNU_RELRO = 0x6474e552;
|
||||
|
||||
static constexpr u32 PF_X = 1;
|
||||
static constexpr u32 PF_W = 2;
|
||||
static constexpr u32 PF_R = 4;
|
||||
|
||||
static constexpr u32 ET_NONE = 0;
|
||||
static constexpr u32 ET_REL = 1;
|
||||
static constexpr u32 ET_EXEC = 2;
|
||||
static constexpr u32 ET_DYN = 3;
|
||||
|
||||
static constexpr u32 ELFDATA2LSB = 1;
|
||||
static constexpr u32 ELFDATA2MSB = 2;
|
||||
|
||||
static constexpr u32 ELFCLASS32 = 1;
|
||||
static constexpr u32 ELFCLASS64 = 2;
|
||||
|
||||
static constexpr u32 EV_CURRENT = 1;
|
||||
|
||||
static constexpr u32 EM_386 = 3;
|
||||
static constexpr u32 EM_X86_64 = 62;
|
||||
|
||||
static constexpr u32 EI_CLASS = 4;
|
||||
static constexpr u32 EI_DATA = 5;
|
||||
static constexpr u32 EI_VERSION = 6;
|
||||
static constexpr u32 EI_OSABI = 7;
|
||||
static constexpr u32 EI_ABIVERSION = 8;
|
||||
|
||||
static constexpr u32 ELFOSABI_NONE = 0;
|
||||
static constexpr u32 ELFOSABI_GNU = 3;
|
||||
|
||||
static constexpr u32 DT_NULL = 0;
|
||||
static constexpr u32 DT_NEEDED = 1;
|
||||
static constexpr u32 DT_PLTRELSZ = 2;
|
||||
static constexpr u32 DT_PLTGOT = 3;
|
||||
static constexpr u32 DT_HASH = 4;
|
||||
static constexpr u32 DT_STRTAB = 5;
|
||||
static constexpr u32 DT_SYMTAB = 6;
|
||||
static constexpr u32 DT_RELA = 7;
|
||||
static constexpr u32 DT_RELASZ = 8;
|
||||
static constexpr u32 DT_RELAENT = 9;
|
||||
static constexpr u32 DT_STRSZ = 10;
|
||||
static constexpr u32 DT_SYMENT = 11;
|
||||
static constexpr u32 DT_INIT = 12;
|
||||
static constexpr u32 DT_FINI = 13;
|
||||
static constexpr u32 DT_SONAME = 14;
|
||||
static constexpr u32 DT_RPATH = 15;
|
||||
static constexpr u32 DT_SYMBOLIC = 16;
|
||||
static constexpr u32 DT_REL = 17;
|
||||
static constexpr u32 DT_RELSZ = 18;
|
||||
static constexpr u32 DT_RELENT = 19;
|
||||
static constexpr u32 DT_PLTREL = 20;
|
||||
static constexpr u32 DT_DEBUG = 21;
|
||||
static constexpr u32 DT_TEXTREL = 22;
|
||||
static constexpr u32 DT_JMPREL = 23;
|
||||
static constexpr u32 DT_BIND_NOW = 24;
|
||||
static constexpr u32 DT_INIT_ARRAY = 25;
|
||||
static constexpr u32 DT_FINI_ARRAY = 26;
|
||||
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_GNU_HASH = 0x6ffffef5;
|
||||
static constexpr u32 DT_VERSYM = 0x6ffffff0;
|
||||
static constexpr u32 DT_RELACOUNT = 0x6ffffff9;
|
||||
static constexpr u32 DT_RELCOUNT = 0x6ffffffa;
|
||||
static constexpr u32 DT_FLAGS_1 = 0x6ffffffb;
|
||||
static constexpr u32 DT_VERDEF = 0x6ffffffc;
|
||||
static constexpr u32 DT_VERDEFNUM = 0x6ffffffd;
|
||||
static constexpr u32 DT_VERNEED = 0x6ffffffe;
|
||||
static constexpr u32 DT_VERNEEDNUM = 0x6fffffff;
|
||||
static constexpr u32 DT_AUXILIARY = 0x7ffffffd;
|
||||
static constexpr u32 DT_FILTER = 0x7fffffff;
|
||||
|
||||
static constexpr u32 DF_ORIGIN = 0x01;
|
||||
static constexpr u32 DF_SYMBOLIC = 0x02;
|
||||
static constexpr u32 DF_TEXTREL = 0x04;
|
||||
static constexpr u32 DF_BIND_NOW = 0x08;
|
||||
static constexpr u32 DF_STATIC_TLS = 0x10;
|
||||
|
||||
static constexpr u32 DF_1_NOW = 0x00000001;
|
||||
static constexpr u32 DF_1_NODELETE = 0x00000008;
|
||||
static constexpr u32 DF_1_INITFIRST = 0x00000020;
|
||||
static constexpr u32 DF_1_NOOPEN = 0x00000040;
|
||||
static constexpr u32 DF_1_ORIGIN = 0x00000080;
|
||||
static constexpr u32 DF_1_INTERPOSE = 0x00000400;
|
||||
static constexpr u32 DF_1_NODUMP = 0x00001000;
|
||||
static constexpr u32 DF_1_PIE = 0x08000000;
|
||||
|
||||
static constexpr u32 NT_GNU_ABI_TAG = 1;
|
||||
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 GNU_PROPERTY_AARCH64_FEATURE_1_AND = 0xc0000000;
|
||||
static constexpr u32 GNU_PROPERTY_X86_FEATURE_1_AND = 0xc0000002;
|
||||
|
||||
static constexpr u32 ELFCOMPRESS_ZLIB = 1;
|
||||
|
||||
static constexpr u32 R_X86_64_NONE = 0;
|
||||
static constexpr u32 R_X86_64_64 = 1;
|
||||
static constexpr u32 R_X86_64_PC32 = 2;
|
||||
static constexpr u32 R_X86_64_GOT32 = 3;
|
||||
static constexpr u32 R_X86_64_PLT32 = 4;
|
||||
static constexpr u32 R_X86_64_COPY = 5;
|
||||
static constexpr u32 R_X86_64_GLOB_DAT = 6;
|
||||
static constexpr u32 R_X86_64_JUMP_SLOT = 7;
|
||||
static constexpr u32 R_X86_64_RELATIVE = 8;
|
||||
static constexpr u32 R_X86_64_GOTPCREL = 9;
|
||||
static constexpr u32 R_X86_64_32 = 10;
|
||||
static constexpr u32 R_X86_64_32S = 11;
|
||||
static constexpr u32 R_X86_64_16 = 12;
|
||||
static constexpr u32 R_X86_64_PC16 = 13;
|
||||
static constexpr u32 R_X86_64_8 = 14;
|
||||
static constexpr u32 R_X86_64_PC8 = 15;
|
||||
static constexpr u32 R_X86_64_DTPMOD64 = 16;
|
||||
static constexpr u32 R_X86_64_DTPOFF64 = 17;
|
||||
static constexpr u32 R_X86_64_TPOFF64 = 18;
|
||||
static constexpr u32 R_X86_64_TLSGD = 19;
|
||||
static constexpr u32 R_X86_64_TLSLD = 20;
|
||||
static constexpr u32 R_X86_64_DTPOFF32 = 21;
|
||||
static constexpr u32 R_X86_64_GOTTPOFF = 22;
|
||||
static constexpr u32 R_X86_64_TPOFF32 = 23;
|
||||
static constexpr u32 R_X86_64_PC64 = 24;
|
||||
static constexpr u32 R_X86_64_GOTOFF64 = 25;
|
||||
static constexpr u32 R_X86_64_GOTPC32 = 26;
|
||||
static constexpr u32 R_X86_64_GOT64 = 27;
|
||||
static constexpr u32 R_X86_64_GOTPCREL64 = 28;
|
||||
static constexpr u32 R_X86_64_GOTPC64 = 29;
|
||||
static constexpr u32 R_X86_64_GOTPLT64 = 30;
|
||||
static constexpr u32 R_X86_64_PLTOFF64 = 31;
|
||||
static constexpr u32 R_X86_64_SIZE32 = 32;
|
||||
static constexpr u32 R_X86_64_SIZE64 = 33;
|
||||
static constexpr u32 R_X86_64_GOTPC32_TLSDESC = 34;
|
||||
static constexpr u32 R_X86_64_TLSDESC_CALL = 35;
|
||||
static constexpr u32 R_X86_64_TLSDESC = 36;
|
||||
static constexpr u32 R_X86_64_IRELATIVE = 37;
|
||||
static constexpr u32 R_X86_64_GOTPCRELX = 41;
|
||||
static constexpr u32 R_X86_64_REX_GOTPCRELX = 42;
|
||||
|
||||
template <>
|
||||
inline std::string rel_to_string<X86_64>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
case R_X86_64_NONE: return "R_X86_64_NONE";
|
||||
case R_X86_64_64: return "R_X86_64_64";
|
||||
case R_X86_64_PC32: return "R_X86_64_PC32";
|
||||
case R_X86_64_GOT32: return "R_X86_64_GOT32";
|
||||
case R_X86_64_PLT32: return "R_X86_64_PLT32";
|
||||
case R_X86_64_COPY: return "R_X86_64_COPY";
|
||||
case R_X86_64_GLOB_DAT: return "R_X86_64_GLOB_DAT";
|
||||
case R_X86_64_JUMP_SLOT: return "R_X86_64_JUMP_SLOT";
|
||||
case R_X86_64_RELATIVE: return "R_X86_64_RELATIVE";
|
||||
case R_X86_64_GOTPCREL: return "R_X86_64_GOTPCREL";
|
||||
case R_X86_64_32: return "R_X86_64_32";
|
||||
case R_X86_64_32S: return "R_X86_64_32S";
|
||||
case R_X86_64_16: return "R_X86_64_16";
|
||||
case R_X86_64_PC16: return "R_X86_64_PC16";
|
||||
case R_X86_64_8: return "R_X86_64_8";
|
||||
case R_X86_64_PC8: return "R_X86_64_PC8";
|
||||
case R_X86_64_DTPMOD64: return "R_X86_64_DTPMOD64";
|
||||
case R_X86_64_DTPOFF64: return "R_X86_64_DTPOFF64";
|
||||
case R_X86_64_TPOFF64: return "R_X86_64_TPOFF64";
|
||||
case R_X86_64_TLSGD: return "R_X86_64_TLSGD";
|
||||
case R_X86_64_TLSLD: return "R_X86_64_TLSLD";
|
||||
case R_X86_64_DTPOFF32: return "R_X86_64_DTPOFF32";
|
||||
case R_X86_64_GOTTPOFF: return "R_X86_64_GOTTPOFF";
|
||||
case R_X86_64_TPOFF32: return "R_X86_64_TPOFF32";
|
||||
case R_X86_64_PC64: return "R_X86_64_PC64";
|
||||
case R_X86_64_GOTOFF64: return "R_X86_64_GOTOFF64";
|
||||
case R_X86_64_GOTPC32: return "R_X86_64_GOTPC32";
|
||||
case R_X86_64_GOT64: return "R_X86_64_GOT64";
|
||||
case R_X86_64_GOTPCREL64: return "R_X86_64_GOTPCREL64";
|
||||
case R_X86_64_GOTPC64: return "R_X86_64_GOTPC64";
|
||||
case R_X86_64_GOTPLT64: return "R_X86_64_GOTPLT64";
|
||||
case R_X86_64_PLTOFF64: return "R_X86_64_PLTOFF64";
|
||||
case R_X86_64_SIZE32: return "R_X86_64_SIZE32";
|
||||
case R_X86_64_SIZE64: return "R_X86_64_SIZE64";
|
||||
case R_X86_64_GOTPC32_TLSDESC: return "R_X86_64_GOTPC32_TLSDESC";
|
||||
case R_X86_64_TLSDESC_CALL: return "R_X86_64_TLSDESC_CALL";
|
||||
case R_X86_64_TLSDESC: return "R_X86_64_TLSDESC";
|
||||
case R_X86_64_IRELATIVE: return "R_X86_64_IRELATIVE";
|
||||
case R_X86_64_GOTPCRELX: return "R_X86_64_GOTPCRELX";
|
||||
case R_X86_64_REX_GOTPCRELX: return "R_X86_64_REX_GOTPCRELX";
|
||||
}
|
||||
return "unknown (" + std::to_string(r_type) + ")";
|
||||
}
|
||||
|
||||
static constexpr u32 R_386_NONE = 0;
|
||||
static constexpr u32 R_386_32 = 1;
|
||||
static constexpr u32 R_386_PC32 = 2;
|
||||
static constexpr u32 R_386_GOT32 = 3;
|
||||
static constexpr u32 R_386_PLT32 = 4;
|
||||
static constexpr u32 R_386_COPY = 5;
|
||||
static constexpr u32 R_386_GLOB_DAT = 6;
|
||||
static constexpr u32 R_386_JUMP_SLOT = 7;
|
||||
static constexpr u32 R_386_RELATIVE = 8;
|
||||
static constexpr u32 R_386_GOTOFF = 9;
|
||||
static constexpr u32 R_386_GOTPC = 10;
|
||||
static constexpr u32 R_386_32PLT = 11;
|
||||
static constexpr u32 R_386_TLS_TPOFF = 14;
|
||||
static constexpr u32 R_386_TLS_IE = 15;
|
||||
static constexpr u32 R_386_TLS_GOTIE = 16;
|
||||
static constexpr u32 R_386_TLS_LE = 17;
|
||||
static constexpr u32 R_386_TLS_GD = 18;
|
||||
static constexpr u32 R_386_TLS_LDM = 19;
|
||||
static constexpr u32 R_386_16 = 20;
|
||||
static constexpr u32 R_386_PC16 = 21;
|
||||
static constexpr u32 R_386_8 = 22;
|
||||
static constexpr u32 R_386_PC8 = 23;
|
||||
static constexpr u32 R_386_TLS_GD_32 = 24;
|
||||
static constexpr u32 R_386_TLS_GD_PUSH = 25;
|
||||
static constexpr u32 R_386_TLS_GD_CALL = 26;
|
||||
static constexpr u32 R_386_TLS_GD_POP = 27;
|
||||
static constexpr u32 R_386_TLS_LDM_32 = 28;
|
||||
static constexpr u32 R_386_TLS_LDM_PUSH = 29;
|
||||
static constexpr u32 R_386_TLS_LDM_CALL = 30;
|
||||
static constexpr u32 R_386_TLS_LDM_POP = 31;
|
||||
static constexpr u32 R_386_TLS_LDO_32 = 32;
|
||||
static constexpr u32 R_386_TLS_IE_32 = 33;
|
||||
static constexpr u32 R_386_TLS_LE_32 = 34;
|
||||
static constexpr u32 R_386_TLS_DTPMOD32 = 35;
|
||||
static constexpr u32 R_386_TLS_DTPOFF32 = 36;
|
||||
static constexpr u32 R_386_TLS_TPOFF32 = 37;
|
||||
static constexpr u32 R_386_SIZE32 = 38;
|
||||
static constexpr u32 R_386_TLS_GOTDESC = 39;
|
||||
static constexpr u32 R_386_TLS_DESC_CALL = 40;
|
||||
static constexpr u32 R_386_TLS_DESC = 41;
|
||||
static constexpr u32 R_386_IRELATIVE = 42;
|
||||
static constexpr u32 R_386_GOT32X = 43;
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<I386>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
case R_386_NONE: return "R_386_NONE";
|
||||
case R_386_32: return "R_386_32";
|
||||
case R_386_PC32: return "R_386_PC32";
|
||||
case R_386_GOT32: return "R_386_GOT32";
|
||||
case R_386_PLT32: return "R_386_PLT32";
|
||||
case R_386_COPY: return "R_386_COPY";
|
||||
case R_386_GLOB_DAT: return "R_386_GLOB_DAT";
|
||||
case R_386_JUMP_SLOT: return "R_386_JUMP_SLOT";
|
||||
case R_386_RELATIVE: return "R_386_RELATIVE";
|
||||
case R_386_GOTOFF: return "R_386_GOTOFF";
|
||||
case R_386_GOTPC: return "R_386_GOTPC";
|
||||
case R_386_32PLT: return "R_386_32PLT";
|
||||
case R_386_TLS_TPOFF: return "R_386_TLS_TPOFF";
|
||||
case R_386_TLS_IE: return "R_386_TLS_IE";
|
||||
case R_386_TLS_GOTIE: return "R_386_TLS_GOTIE";
|
||||
case R_386_TLS_LE: return "R_386_TLS_LE";
|
||||
case R_386_TLS_GD: return "R_386_TLS_GD";
|
||||
case R_386_TLS_LDM: return "R_386_TLS_LDM";
|
||||
case R_386_16: return "R_386_16";
|
||||
case R_386_PC16: return "R_386_PC16";
|
||||
case R_386_8: return "R_386_8";
|
||||
case R_386_PC8: return "R_386_PC8";
|
||||
case R_386_TLS_GD_32: return "R_386_TLS_GD_32";
|
||||
case R_386_TLS_GD_PUSH: return "R_386_TLS_GD_PUSH";
|
||||
case R_386_TLS_GD_CALL: return "R_386_TLS_GD_CALL";
|
||||
case R_386_TLS_GD_POP: return "R_386_TLS_GD_POP";
|
||||
case R_386_TLS_LDM_32: return "R_386_TLS_LDM_32";
|
||||
case R_386_TLS_LDM_PUSH: return "R_386_TLS_LDM_PUSH";
|
||||
case R_386_TLS_LDM_CALL: return "R_386_TLS_LDM_CALL";
|
||||
case R_386_TLS_LDM_POP: return "R_386_TLS_LDM_POP";
|
||||
case R_386_TLS_LDO_32: return "R_386_TLS_LDO_32";
|
||||
case R_386_TLS_IE_32: return "R_386_TLS_IE_32";
|
||||
case R_386_TLS_LE_32: return "R_386_TLS_LE_32";
|
||||
case R_386_TLS_DTPMOD32: return "R_386_TLS_DTPMOD32";
|
||||
case R_386_TLS_DTPOFF32: return "R_386_TLS_DTPOFF32";
|
||||
case R_386_TLS_TPOFF32: return "R_386_TLS_TPOFF32";
|
||||
case R_386_SIZE32: return "R_386_SIZE32";
|
||||
case R_386_TLS_GOTDESC: return "R_386_TLS_GOTDESC";
|
||||
case R_386_TLS_DESC_CALL: return "R_386_TLS_DESC_CALL";
|
||||
case R_386_TLS_DESC: return "R_386_TLS_DESC";
|
||||
case R_386_IRELATIVE: return "R_386_IRELATIVE";
|
||||
case R_386_GOT32X: return "R_386_GOT32X";
|
||||
}
|
||||
return "unknown (" + std::to_string(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;
|
||||
static constexpr u32 DW_EH_PE_udata2 = 0x02;
|
||||
static constexpr u32 DW_EH_PE_udata4 = 0x03;
|
||||
static constexpr u32 DW_EH_PE_udata8 = 0x04;
|
||||
static constexpr u32 DW_EH_PE_signed = 0x08;
|
||||
static constexpr u32 DW_EH_PE_sleb128 = 0x09;
|
||||
static constexpr u32 DW_EH_PE_sdata2 = 0x0a;
|
||||
static constexpr u32 DW_EH_PE_sdata4 = 0x0b;
|
||||
static constexpr u32 DW_EH_PE_sdata8 = 0x0c;
|
||||
static constexpr u32 DW_EH_PE_pcrel = 0x10;
|
||||
static constexpr u32 DW_EH_PE_textrel = 0x20;
|
||||
static constexpr u32 DW_EH_PE_datarel = 0x30;
|
||||
static constexpr u32 DW_EH_PE_funcrel = 0x40;
|
||||
static constexpr u32 DW_EH_PE_aligned = 0x50;
|
||||
|
||||
struct Elf64Sym {
|
||||
bool is_defined() const { return !is_undef(); }
|
||||
bool is_undef() const { return st_shndx == SHN_UNDEF; }
|
||||
bool is_abs() const { return st_shndx == SHN_ABS; }
|
||||
bool is_common() const { return st_shndx == SHN_COMMON; }
|
||||
bool is_weak() const { return st_bind == STB_WEAK; }
|
||||
|
||||
bool is_undef_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;
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u16 st_shndx;
|
||||
u64 st_value;
|
||||
u64 st_size;
|
||||
};
|
||||
|
||||
struct Elf32Sym {
|
||||
bool is_defined() const { return !is_undef(); }
|
||||
bool is_undef() const { return st_shndx == SHN_UNDEF; }
|
||||
bool is_abs() const { return st_shndx == SHN_ABS; }
|
||||
bool is_common() const { return st_shndx == SHN_COMMON; }
|
||||
bool is_weak() const { return st_bind == STB_WEAK; }
|
||||
|
||||
bool is_undef_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;
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u16 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;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
struct Elf64Rel {
|
||||
u64 r_offset;
|
||||
u32 r_type;
|
||||
u32 r_sym;
|
||||
};
|
||||
|
||||
struct Elf32Rel {
|
||||
u32 r_offset;
|
||||
u32 r_type : 8;
|
||||
u32 r_sym : 24;
|
||||
};
|
||||
|
||||
struct Elf64Rela {
|
||||
u64 r_offset;
|
||||
u32 r_type;
|
||||
u32 r_sym;
|
||||
i64 r_addend;
|
||||
};
|
||||
|
||||
struct Elf32Rela {
|
||||
u32 r_offset;
|
||||
u32 r_type : 8;
|
||||
u32 r_sym : 24;
|
||||
i32 r_addend;
|
||||
};
|
||||
|
||||
struct Elf64Dyn {
|
||||
u64 d_tag;
|
||||
u64 d_val;
|
||||
};
|
||||
|
||||
struct Elf32Dyn {
|
||||
u32 d_tag;
|
||||
u32 d_val;
|
||||
};
|
||||
|
||||
struct Elf64Verneed {
|
||||
u16 vn_version;
|
||||
u16 vn_cnt;
|
||||
u32 vn_file;
|
||||
u32 vn_aux;
|
||||
u32 vn_next;
|
||||
};
|
||||
|
||||
struct Elf64Vernaux {
|
||||
u32 vna_hash;
|
||||
u16 vna_flags;
|
||||
u16 vna_other;
|
||||
u32 vna_name;
|
||||
u32 vna_next;
|
||||
};
|
||||
|
||||
struct Elf64Verdef {
|
||||
u16 vd_version;
|
||||
u16 vd_flags;
|
||||
u16 vd_ndx;
|
||||
u16 vd_cnt;
|
||||
u32 vd_hash;
|
||||
u32 vd_aux;
|
||||
u32 vd_next;
|
||||
};
|
||||
|
||||
struct Elf64Verdaux {
|
||||
u32 vda_name;
|
||||
u32 vda_next;
|
||||
};
|
||||
|
||||
struct Elf64Chdr {
|
||||
u32 ch_type;
|
||||
u32 ch_reserved;
|
||||
u64 ch_size;
|
||||
u64 ch_addralign;
|
||||
};
|
||||
|
||||
struct Elf32Chdr {
|
||||
u32 ch_type;
|
||||
u32 ch_size;
|
||||
u32 ch_addralign;
|
||||
};
|
||||
|
||||
struct Elf64Nhdr {
|
||||
u32 n_namesz;
|
||||
u32 n_descsz;
|
||||
u32 n_type;
|
||||
};
|
||||
|
||||
template <typename E> struct ElfSym;
|
||||
template <typename E> struct ElfShdr;
|
||||
template <typename E> struct ElfEhdr;
|
||||
template <typename E> struct ElfPhdr;
|
||||
template <typename E> struct ElfRel;
|
||||
template <typename E> struct ElfDyn;
|
||||
template <typename E> struct ElfVerneed;
|
||||
template <typename E> struct ElfVernaux;
|
||||
template <typename E> struct ElfVerdef;
|
||||
template <typename E> struct ElfVerdaux;
|
||||
template <typename E> struct ElfChdr;
|
||||
template <typename E> struct ElfNhdr;
|
||||
|
||||
struct X86_64 {
|
||||
typedef u64 WordTy;
|
||||
|
||||
static constexpr u32 R_NONE = R_X86_64_NONE;
|
||||
static constexpr u32 R_COPY = R_X86_64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_X86_64_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_X86_64_JUMP_SLOT;
|
||||
static constexpr u32 R_RELATIVE = R_X86_64_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_X86_64_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_X86_64_DTPOFF64;
|
||||
static constexpr u32 R_TPOFF = R_X86_64_TPOFF64;
|
||||
static constexpr u32 R_DTPMOD = R_X86_64_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_X86_64_TLSDESC;
|
||||
|
||||
static constexpr u32 wordsize = 8;
|
||||
static constexpr u32 e_machine = EM_X86_64;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr bool is_rel = false;
|
||||
static constexpr bool is_le = true;
|
||||
};
|
||||
|
||||
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 ElfDyn<X86_64> : public Elf64Dyn {};
|
||||
template <> struct ElfVerneed<X86_64> : public Elf64Verneed {};
|
||||
template <> struct ElfVernaux<X86_64> : public Elf64Vernaux {};
|
||||
template <> struct ElfVerdef<X86_64> : public Elf64Verdef {};
|
||||
template <> struct ElfVerdaux<X86_64> : public Elf64Verdaux {};
|
||||
template <> struct ElfChdr<X86_64> : public Elf64Chdr {};
|
||||
template <> struct ElfNhdr<X86_64> : public Elf64Nhdr {};
|
||||
|
||||
struct I386 {
|
||||
typedef u32 WordTy;
|
||||
|
||||
static constexpr u32 R_NONE = R_386_NONE;
|
||||
static constexpr u32 R_COPY = R_386_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_386_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_386_JUMP_SLOT;
|
||||
static constexpr u32 R_RELATIVE = R_386_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_386_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_386_TLS_DTPOFF32;
|
||||
static constexpr u32 R_TPOFF = R_386_TLS_TPOFF;
|
||||
static constexpr u32 R_DTPMOD = R_386_TLS_DTPMOD32;
|
||||
static constexpr u32 R_TLSDESC = R_386_TLS_DESC;
|
||||
|
||||
static constexpr u32 wordsize = 4;
|
||||
static constexpr u32 e_machine = EM_386;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr bool is_rel = true;
|
||||
static constexpr bool is_le = true;
|
||||
};
|
||||
|
||||
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 ElfDyn<I386> : public Elf32Dyn {};
|
||||
template <> struct ElfVerneed<I386> : public Elf64Verneed {};
|
||||
template <> struct ElfVernaux<I386> : public Elf64Vernaux {};
|
||||
template <> struct ElfVerdef<I386> : public Elf64Verdef {};
|
||||
template <> struct ElfVerdaux<I386> : public Elf64Verdaux {};
|
||||
template <> struct ElfChdr<I386> : public Elf32Chdr {};
|
||||
template <> struct ElfNhdr<I386> : public Elf64Nhdr {};
|
||||
@@ -0,0 +1,600 @@
|
||||
// ARM32 is a bit special from the linker's viewpoint because ARM
|
||||
// processors support two different instruction encodings: Thumb and
|
||||
// ARM (in a narrower sense). Thumb instructions are either 16 bits or
|
||||
// 32 bits, while ARM instructions are all 32 bits. Feature-wise,
|
||||
// thumb is a subset of ARM, so not all ARM instructions are
|
||||
// representable in Thumb.
|
||||
//
|
||||
// ARM processors originally supported only ARM instructions. Thumb
|
||||
// instructions were later added to increase code density.
|
||||
//
|
||||
// ARM processors runs in either ARM mode or Thumb mode. The mode can
|
||||
// be switched using BX (branch and mode exchange)-family instructions.
|
||||
// We need to use such instructions to, for example, call a function
|
||||
// encoded in Thumb from a function encoded in ARM. Sometimes, the
|
||||
// linker even has to emit an interworking thunk code to switch from
|
||||
// Thumb to ARM.
|
||||
//
|
||||
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
|
||||
// byte boundaries.
|
||||
//
|
||||
// You can distinguish Thumb functions from ARM functions by looking
|
||||
// at the least significant bit (LSB) of its "address". If LSB is 0,
|
||||
// it's ARM; otherwise, Thumb. LSB is not a part of its real address.
|
||||
// For example, if a symbol `foo` is of type STT_FUNC and has value
|
||||
// 0x2001, then `foo` is a function using Thumb instructions whose
|
||||
// address is 0x2000 (not 0x2001).
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <tbb/parallel_sort.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = ARM32;
|
||||
|
||||
static u32 bit(u32 val, i64 pos) {
|
||||
return (val >> pos) & 1;
|
||||
}
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u32 bits(u32 val, i64 hi, i64 lo) {
|
||||
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
static i32 sign_extend(u32 val, u32 size) {
|
||||
return (i64)(val << (31 - size)) >> (31 - size);
|
||||
};
|
||||
|
||||
static void write_mov_imm(u8 *loc, u32 val) {
|
||||
u32 imm12 = bits(val, 11, 0);
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
*(u32 *)loc = (*(u32 *)loc & 0xfff0f000) | (imm4 << 16) | imm12;
|
||||
}
|
||||
|
||||
static void write_thm_b_imm(u8 *loc, u32 val) {
|
||||
// https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/BL--BLX--immediate-
|
||||
u32 sign = bit(val, 24);
|
||||
u32 I1 = bit(val, 23);
|
||||
u32 I2 = bit(val, 22);
|
||||
u32 J1 = !I1 ^ sign;
|
||||
u32 J2 = !I2 ^ sign;
|
||||
u32 imm10 = bits(val, 21, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
*(u16 *)loc = (*(u16 *)loc & 0xf800) | (sign << 10) | imm10;
|
||||
*(u16 *)(loc + 2) =
|
||||
(*(u16 *)(loc + 2) & 0xd000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
|
||||
static void write_thm_mov_imm(u8 *loc, u32 val) {
|
||||
// https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/MOVT
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
u32 i = bit(val, 11);
|
||||
u32 imm3 = bits(val, 10, 8);
|
||||
u32 imm8 = bits(val, 7, 0);
|
||||
*(u16 *)loc = (*(u16 *)loc & 0b1111'1011'1111'0000) | (i << 10) | imm4;
|
||||
*(u16 *)(loc + 2) =
|
||||
((*(u16 *)(loc + 2)) & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
static const u32 plt0[] = {
|
||||
0xe52de004, // 1: push {lr}
|
||||
0xe59fe004, // ldr lr, [pc, #4]
|
||||
0xe08fe00e, // add lr, pc, lr
|
||||
0xe5bef008, // ldr pc, [lr, #8]!
|
||||
0x00000000, // .word .got.plt - 1b - 16
|
||||
};
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr - 16;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
static const u32 plt[] = {
|
||||
0xe59fc004, // 1: ldr ip, 2f
|
||||
0xe08cc00f, // add ip, ip, pc
|
||||
0xe59cf000, // ldr pc, [ip]
|
||||
0x00000000, // 2: .word sym@PLTGOT - 1b
|
||||
};
|
||||
|
||||
u8 *ent = buf + sizeof(plt0) + sym->get_plt_idx(ctx) * sizeof(plt);
|
||||
memcpy(ent, plt, sizeof(plt));
|
||||
*(u32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 12;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
static const u32 plt[] = {
|
||||
0xe59fc004, // 1: ldr ip, 2f
|
||||
0xe08cc00f, // add ip, ip, pc
|
||||
0xe59cf000, // ldr pc, [ip]
|
||||
0x00000000, // 2: .word sym@GOT - 1b
|
||||
};
|
||||
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * sizeof(plt);
|
||||
memcpy(ent, plt, sizeof(plt));
|
||||
*(u32 *)(ent + 12) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 12;
|
||||
}
|
||||
}
|
||||
|
||||
// ARM does not use .eh_frame for exception handling. Instead, it uses
|
||||
// .ARM.exidx and .ARM.extab. So this function is empty.
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_ARM_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
const SectionFragmentRef<E> *frag_ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
frag_ref = &rel_fragments[frag_idx++];
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : this->get_addend(rel))
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define T (sym.get_addr(ctx) & 1)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_TARGET1:
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
*(u32 *)loc = S + A;
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_ARM_ABS32, (u32)sym.get_dynsym_idx(ctx)};
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_ARM_RELATIVE, 0};
|
||||
*(u32 *)loc = S + A;
|
||||
}
|
||||
continue;
|
||||
case R_ARM_REL32:
|
||||
*(u32 *)loc = S + A - P;
|
||||
continue;
|
||||
case R_ARM_THM_CALL:
|
||||
// THM_CALL relocation refers either BL or BLX instruction.
|
||||
// They are different in only one bit. We need to use BL if
|
||||
// the jump target is Thumb. Otherwise, use BLX.
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
write_thm_b_imm(loc, 4);
|
||||
*(u16 *)(loc + 2) |= (1 << 12); // rewrite with BL
|
||||
} else if (T) {
|
||||
write_thm_b_imm(loc, S + A - P);
|
||||
*(u16 *)(loc + 2) |= (1 << 12); // rewrite with BL
|
||||
} else {
|
||||
write_thm_b_imm(loc, align_to(S + A - P, 4));
|
||||
*(u16 *)(loc + 2) &= ~(1 << 12); // rewrite with BLX
|
||||
}
|
||||
continue;
|
||||
case R_ARM_BASE_PREL:
|
||||
*(u32 *)loc = GOT + A - P;
|
||||
continue;
|
||||
case R_ARM_GOT_PREL:
|
||||
*(u32 *)loc = G + A - P;
|
||||
continue;
|
||||
case R_ARM_GOT_BREL:
|
||||
*(u32 *)loc = G + A;
|
||||
continue;
|
||||
case R_ARM_TARGET2:
|
||||
*(u32 *)loc = GOT + G + A - P;
|
||||
continue;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24: {
|
||||
u32 val;
|
||||
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
val = 4;
|
||||
} else {
|
||||
val = S + A - P;
|
||||
}
|
||||
|
||||
*(u32 *)loc = (*(u32 *)loc & 0xff00'0000) | ((val >> 2) & 0x00ff'ffff);
|
||||
continue;
|
||||
}
|
||||
case R_ARM_THM_JUMP11: {
|
||||
assert(T);
|
||||
u32 val = (S + A - P) >> 1;
|
||||
*(u16 *)loc = (*(u16 *)loc & 0xf800) | (val & 0x07ff);
|
||||
continue;
|
||||
}
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (T) {
|
||||
write_thm_b_imm(loc, S + A - P);
|
||||
} else {
|
||||
assert(sym.extra.thumb_to_arm_thunk_idx != -1);
|
||||
u64 thunk_addr =
|
||||
ctx.thumb_to_arm->shdr.sh_addr +
|
||||
sym.extra.thumb_to_arm_thunk_idx * ThumbToArmSection::ENTRY_SIZE;
|
||||
write_thm_b_imm(loc, thunk_addr - P - 4);
|
||||
}
|
||||
continue;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
write_mov_imm(loc, ((S + A) | T) - P);
|
||||
continue;
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
write_mov_imm(loc, (S + A) | T);
|
||||
continue;
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
write_thm_mov_imm(loc, ((S + A) | T) - P);
|
||||
continue;
|
||||
case R_ARM_PREL31: {
|
||||
u32 val = S + A - P;
|
||||
*(u32 *)loc = (*(u32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
continue;
|
||||
}
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
write_thm_mov_imm(loc, (S + A) | T);
|
||||
continue;
|
||||
case R_ARM_MOVT_PREL:
|
||||
write_mov_imm(loc, (S + A - P) >> 16);
|
||||
continue;
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
write_thm_mov_imm(loc, (S + A - P) >> 16);
|
||||
continue;
|
||||
case R_ARM_MOVT_ABS:
|
||||
write_mov_imm(loc, (S + A) >> 16);
|
||||
continue;
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
write_thm_mov_imm(loc, (S + A) >> 16);
|
||||
continue;
|
||||
case R_ARM_TLS_GD32:
|
||||
*(u32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
continue;
|
||||
case R_ARM_TLS_LDM32:
|
||||
*(u32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
continue;
|
||||
case R_ARM_TLS_LDO32:
|
||||
*(u32 *)loc = S + A - ctx.tls_begin;
|
||||
continue;
|
||||
case R_ARM_TLS_IE32:
|
||||
*(u32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
continue;
|
||||
case R_ARM_TLS_LE32:
|
||||
*(u32 *)loc = S + A - ctx.tls_begin + 8;
|
||||
continue;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1)
|
||||
*(u32 *)loc = S - ctx.tls_begin + 8;
|
||||
else
|
||||
*(u32 *)loc = sym.get_tlsdesc_addr(ctx) + A - P - 6;
|
||||
continue;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
// BL -> NOP
|
||||
*(u32 *)loc = 0x8000f3af;
|
||||
} else {
|
||||
u64 addr = ctx.tls_trampoline->shdr.sh_addr;
|
||||
write_thm_b_imm(loc, align_to(addr - P - 4, 4));
|
||||
*(u16 *)(loc + 2) &= ~(1 << 12); // rewrite BL with BLX
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef T
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_ARM_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 addend;
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : this->get_addend(rel))
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_ARM_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_MOVT_ABS:
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
case R_ARM_TARGET1: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
break;
|
||||
case R_ARM_THM_CALL: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, NONE, PLT, PLT }, // DSO
|
||||
{ NONE, NONE, PLT, PLT }, // PIE
|
||||
{ NONE, NONE, PLT, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TARGET2:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (sym.is_imported || sym.get_type() == STT_GNU_IFUNC)
|
||||
sym.flags |= NEEDS_PLT | NEEDS_THUMB_TO_ARM_THUNK;
|
||||
else if (sym.esym().st_value % 2 == 0)
|
||||
sym.flags |= NEEDS_THUMB_TO_ARM_THUNK;
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
case R_ARM_PREL31: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_ARM_TLS_GD32:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
if (!ctx.relax_tlsdesc || sym.is_imported)
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
break;
|
||||
case R_ARM_THM_JUMP11:
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If a function referenced by a Thumb B (branch) instruction is
|
||||
// resovled to a non-thumb function, we can't directly jump from the
|
||||
// thumb function to the ARM function. We can't rewrite B with BX
|
||||
// because there's no such BX instruction that takes an immediate.
|
||||
// BX takes only a register.
|
||||
//
|
||||
// In order to support such branch, we insert a small piece of code to
|
||||
// the resulting executable which switches the processor mode from
|
||||
// Thumb to ARM. This section contains such code.
|
||||
void ThumbToArmSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
if (sym->extra.thumb_to_arm_thunk_idx == -1) {
|
||||
sym->extra.thumb_to_arm_thunk_idx = symbols.size();
|
||||
symbols.push_back(sym);
|
||||
}
|
||||
}
|
||||
|
||||
void ThumbToArmSection::update_shdr(Context<E> &ctx) {
|
||||
this->shdr.sh_size = symbols.size() * ENTRY_SIZE;
|
||||
}
|
||||
|
||||
void ThumbToArmSection::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
i64 offset = 0;
|
||||
|
||||
static u16 insn[] = {
|
||||
0x4778, // bx pc
|
||||
0x46c0, // nop
|
||||
0, 0, // b <imm24>
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == ENTRY_SIZE);
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
memcpy(buf + offset, insn, sizeof(insn));
|
||||
|
||||
u32 val = sym->get_addr(ctx) - this->shdr.sh_addr - offset - 12;
|
||||
*(u32 *)(buf + offset + 4) = 0xea00'0000 | (0x00ff'ffff & (val >> 2));
|
||||
offset += sizeof(insn);
|
||||
}
|
||||
}
|
||||
|
||||
void TlsTrampolineSection::copy_buf(Context<E> &ctx) {
|
||||
// Trampoline code for TLSDESC
|
||||
static u32 insn[] = {
|
||||
0xe08e0000, // add r0, lr, r0
|
||||
0xe5901004, // ldr r1, [r0, #4]
|
||||
0xe12fff11, // bx r1
|
||||
};
|
||||
memcpy(ctx.buf + this->shdr.sh_offset, insn, sizeof(insn));
|
||||
}
|
||||
|
||||
// ARM executables use an .ARM.exidx section to look up an exception
|
||||
// handling record for the current instruction pointer. The table needs
|
||||
// to be sorted by their addresses.
|
||||
//
|
||||
// Other target uses .eh_frame_hdr instead for the same purpose.
|
||||
// I don't know why only ARM uses the different mechanism, but it's
|
||||
// likely that it's due to some historical reason.
|
||||
//
|
||||
// This function sorts .ARM.exidx records.
|
||||
void sort_arm_exidx(Context<E> &ctx) {
|
||||
Timer t(ctx, "sort_arm_exidx");
|
||||
|
||||
auto find_exidx = [&]() -> OutputSection<E> * {
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
if (osec->shdr.sh_type == SHT_ARM_EXIDX)
|
||||
return osec.get();
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
OutputSection<E> *osec = find_exidx();
|
||||
if (!osec)
|
||||
return;
|
||||
|
||||
// .ARM.exidx records consists of a signed 31-bit relative address
|
||||
// and a 32-bit value. The relative address indicates the start
|
||||
// address of a function that the record covers. The value is one of
|
||||
// the followings:
|
||||
//
|
||||
// 1. CANTUNWIND indicating that there's no unwinding info for the function,
|
||||
// 2. a compact unwinding record encoded into a 32-bit value, or
|
||||
// 3. a 31-bit relative address which points to a larger record in
|
||||
// the .ARM.extab section.
|
||||
//
|
||||
// CANTUNWIND is value 1. The most significant is set in (2) but not
|
||||
// in (3). So they can be distinguished just by looking at a value.
|
||||
struct Entry {
|
||||
u32 addr;
|
||||
u32 val;
|
||||
};
|
||||
|
||||
if (osec->shdr.sh_size % sizeof(Entry))
|
||||
Fatal(ctx) << "invalid .ARM.exidx section size";
|
||||
|
||||
Entry *begin = (Entry *)(ctx.buf + osec->shdr.sh_offset);
|
||||
Entry *end = (Entry *)(ctx.buf + osec->shdr.sh_offset + osec->shdr.sh_size);
|
||||
|
||||
struct Entry2 {
|
||||
u32 addr;
|
||||
u32 val;
|
||||
u32 idx;
|
||||
};
|
||||
|
||||
// Read section contents
|
||||
std::vector<Entry2> vec;
|
||||
vec.reserve(end - begin);
|
||||
for (Entry *it = begin; it < end; it++)
|
||||
vec.push_back({it->addr, it->val, (u32)(it - begin)});
|
||||
|
||||
// Sort the records
|
||||
tbb::parallel_sort(vec.begin(), vec.end(), [](const Entry2 &a, const Entry2 &b) {
|
||||
return sign_extend(a.addr, 30) + a.idx * sizeof(Entry) <
|
||||
sign_extend(b.addr, 30) + b.idx * sizeof(Entry);
|
||||
});
|
||||
|
||||
// Write back the sorted records while adjusting relative addresses
|
||||
for (i64 i = 0; i < vec.size(); i++) {
|
||||
u32 offset = (vec[i].idx - i) * sizeof(Entry);
|
||||
begin[i].addr = 0x7fff'ffff & (sign_extend(vec[i].addr, 30) + offset);
|
||||
|
||||
const u32 EXIDX_CANTUNWIND = 1;
|
||||
if (vec[i].val == EXIDX_CANTUNWIND || (vec[i].val & 0x8000'0000))
|
||||
begin[i].val = vec[i].val;
|
||||
else
|
||||
begin[i].val = 0x7fff'ffff & (sign_extend(vec[i].val, 30) + offset);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,798 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = ARM64;
|
||||
|
||||
static void write_adr(u8 *buf, u64 val) {
|
||||
u32 hi = (val & 0x1ffffc) << 3;
|
||||
u32 lo = (val & 3) << 29;
|
||||
*(u32 *)buf = (*(u32 *)buf & 0x9f00001f) | hi | lo;
|
||||
}
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u64 bits(u64 val, u64 hi, u64 lo) {
|
||||
return (val >> lo) & (((u64)1 << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
static u64 page(u64 val) {
|
||||
return val & ~(u64)0xfff;
|
||||
}
|
||||
|
||||
static void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
// Write PLT header
|
||||
static const u32 plt0[] = {
|
||||
0xa9bf7bf0, // stp x16, x30, [sp,#-16]!
|
||||
0x90000010, // adrp x16, .got.plt[2]
|
||||
0xf9400211, // ldr x17, [x16, .got.plt[2]]
|
||||
0x91000210, // add x16, x16, .got.plt[2]
|
||||
0xd61f0220, // br x17
|
||||
0xd503201f, // nop
|
||||
0xd503201f, // nop
|
||||
0xd503201f, // nop
|
||||
};
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr + 16;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
write_adr(buf + 4, bits(page(gotplt) - page(plt + 4), 32, 12));
|
||||
*(u32 *)(buf + 8) |= bits(gotplt, 11, 3) << 10;
|
||||
*(u32 *)(buf + 12) |= ((gotplt) & 0xfff) << 10;
|
||||
}
|
||||
|
||||
static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u8 *ent = buf + ctx.plt_hdr_size + sym.get_plt_idx(ctx) * ctx.plt_size;
|
||||
|
||||
static const u32 data[] = {
|
||||
0x90000010, // adrp x16, .got.plt[n]
|
||||
0xf9400211, // ldr x17, [x16, .got.plt[n]]
|
||||
0x91000210, // add x16, x16, .got.plt[n]
|
||||
0xd61f0220, // br x17
|
||||
};
|
||||
|
||||
u64 gotplt = sym.get_gotplt_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_adr(ent, bits(page(gotplt) - page(plt), 32, 12));
|
||||
*(u32 *)(ent + 4) |= bits(gotplt, 11, 3) << 10;
|
||||
*(u32 *)(ent + 8) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
write_plt_header(ctx, buf);
|
||||
for (Symbol<E> *sym : symbols)
|
||||
write_plt_entry(ctx, buf, *sym);
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * ARM64::pltgot_size;
|
||||
|
||||
static const u32 data[] = {
|
||||
0x90000010, // adrp x16, GOT[n]
|
||||
0xf9400211, // ldr x17, [x16, GOT[n]]
|
||||
0xd61f0220, // br x17
|
||||
0xd503201f, // nop
|
||||
};
|
||||
|
||||
u64 got = sym->get_got_addr(ctx);
|
||||
u64 plt = sym->get_plt_addr(ctx);
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_adr(ent, bits(page(got) - page(plt), 32, 12));
|
||||
*(u32 *)(ent + 4) |= bits(got, 11, 3) << 10;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
*(u64 *)loc = val;
|
||||
return;
|
||||
case R_AARCH64_PREL32:
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
case R_AARCH64_PREL64:
|
||||
*(u64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = get_range_extn();
|
||||
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_AARCH64_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
const SectionFragmentRef<E> *frag_ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
frag_ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
*(u64 *)loc = S + A;
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_AARCH64_ABS64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
*(u64 *)loc = A;
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_AARCH64_RELATIVE, 0, (i64)(S + A)};
|
||||
*(u64 *)loc = S + A;
|
||||
}
|
||||
continue;
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 1) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 2) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 3) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 4) << 10;
|
||||
continue;
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
*(u32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
*(u32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
*(u32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
*(u32 *)loc |= bits(S + A, 63, 48) << 5;
|
||||
continue;
|
||||
case R_AARCH64_ADR_GOT_PAGE: {
|
||||
i64 val = page(G + GOT + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_PG_HI21: {
|
||||
i64 val = page(S + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_LO21: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 20), (i64)1 << 20);
|
||||
write_adr(loc, val);
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26: {
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(u32 *)loc |= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
i64 lo = -(1 << 27);
|
||||
i64 hi = 1 << 27;
|
||||
i64 val = S + A - P;
|
||||
|
||||
if (val < lo || hi <= val) {
|
||||
RangeExtensionRef ref = range_extn[i];
|
||||
val = output_section->thunks[ref.thunk_idx]->get_addr(ref.sym_idx) + A - P;
|
||||
assert(lo <= val && val < hi);
|
||||
}
|
||||
|
||||
*(u32 *)loc |= (val >> 2) & 0x3ffffff;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 20), (i64)1 << 20);
|
||||
*(u32 *)loc |= bits(val, 20, 2) << 5;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_PREL16: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 15), (i64)1 << 15);
|
||||
*(u16 *)loc = val;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_PREL32: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 32);
|
||||
*(u32 *)loc = val;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_PREL64:
|
||||
*(u64 *)loc = S + A - P;
|
||||
continue;
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
*(u32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15: {
|
||||
i64 val = G + GOT + A - page(GOT);
|
||||
overflow_check(val, 0, 1 << 15);
|
||||
*(u32 *)loc |= bits(val, 14, 3) << 10;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: {
|
||||
i64 val = page(sym.get_gottp_addr(ctx) + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
*(u32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
continue;
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12: {
|
||||
i64 val = S + A - ctx.tls_begin + 16;
|
||||
overflow_check(val, 0, (i64)1 << 24);
|
||||
*(u32 *)loc |= bits(val, 23, 12) << 10;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A - ctx.tls_begin + 16, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21: {
|
||||
i64 val = page(sym.get_tlsgd_addr(ctx) + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
*(u32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21: {
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// adrp x0, 0 -> movz x0, #tls_ofset_hi, lsl #16
|
||||
i64 val = (S + A - ctx.tls_begin + 16);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
*(u32 *)loc = 0xd2a00000 | (bits(val, 32, 16) << 5);
|
||||
} else {
|
||||
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// ldr x2, [x0] -> movk x0, #tls_ofset_lo
|
||||
u32 offset_lo = (S + A - ctx.tls_begin + 16) & 0xffff;
|
||||
*(u32 *)loc = 0xf2800000 | (offset_lo << 5);
|
||||
} else {
|
||||
*(u32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
|
||||
}
|
||||
continue;
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// add x0, x0, #0 -> nop
|
||||
*(u32 *)loc = 0xd503201f;
|
||||
} else {
|
||||
*(u32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
|
||||
}
|
||||
continue;
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// blr x2 -> nop
|
||||
*(u32 *)loc = 0xd503201f;
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_AARCH64_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 addend;
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A;
|
||||
continue;
|
||||
case R_AARCH64_ABS32:
|
||||
*(u32 *)loc = S + A;
|
||||
continue;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_AARCH64_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ADR_GOT_PAGE:
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_AARCH64_ADR_PREL_PG_HI21: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (!ctx.relax_tlsdesc || sym.is_imported)
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
break;
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
case R_AARCH64_ADR_PREL_LO21:
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19:
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
case R_AARCH64_PREL16:
|
||||
case R_AARCH64_PREL32:
|
||||
case R_AARCH64_PREL64:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
for (Symbol<E> *sym : thunk.symbols) {
|
||||
sym->extra.thunk_idx = -1;
|
||||
sym->extra.thunk_sym_idx = -1;
|
||||
sym->flags &= (u8)~NEEDS_RANGE_EXTN_THUNK;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
|
||||
InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
// We always create a thunk for an absolute symbol conservatively
|
||||
// because `shrink_sections` may increase a distance between a
|
||||
// branch instruction and an absolute symbol. Branching to an
|
||||
// absolute location is extremely rare in real code, though.
|
||||
if (sym.is_absolute())
|
||||
return false;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol
|
||||
// and check if they are within reach.
|
||||
i64 S = sym.get_addr(ctx);
|
||||
i64 A = rel.r_addend;
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
i64 val = S + A - P;
|
||||
return -(1 << 27) <= val && val < (1 << 27);
|
||||
}
|
||||
|
||||
// We create a thunk no further than 100 MiB from any section.
|
||||
static constexpr i64 MAX_DISTANCE = 100 * 1024 * 1024;
|
||||
|
||||
// We create a thunk for each 10 MiB input sections.
|
||||
static constexpr i64 GROUP_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
static void create_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
std::span<InputSection<E> *> members = osec.members;
|
||||
members[0]->offset = 0;
|
||||
|
||||
// Initialize input sections with very large dummy offsets so that
|
||||
// sections that have got real offsets are separated from the ones
|
||||
// without in the virtual address space.
|
||||
tbb::parallel_for((i64)1, (i64)members.size(), [&](i64 i) {
|
||||
members[i]->offset = 1 << 31;
|
||||
});
|
||||
|
||||
// We create thunks from the beginning of the section to the end.
|
||||
// We manage progress using four offsets which increase monotonically.
|
||||
// The locations they point to are always A <= B <= C <= D.
|
||||
i64 a = 0;
|
||||
i64 b = 0;
|
||||
i64 c = 0;
|
||||
i64 d = 0;
|
||||
i64 offset = 0;
|
||||
|
||||
while (b < members.size()) {
|
||||
// Move D foward as far as we can jump from B to D.
|
||||
while (d < members.size() && offset - members[b]->offset < MAX_DISTANCE) {
|
||||
offset = align_to(offset, 1 << members[d]->p2align);
|
||||
members[d]->offset = offset;
|
||||
offset += members[d]->sh_size;
|
||||
d++;
|
||||
}
|
||||
|
||||
// Move C forward so that C is apart from B by GROUP_SIZE.
|
||||
while (c < members.size() &&
|
||||
members[c]->offset - members[b]->offset < GROUP_SIZE)
|
||||
c++;
|
||||
|
||||
// Move A forward so that A is reachable from C.
|
||||
if (c > 0) {
|
||||
i64 c_end = members[c - 1]->offset + members[c - 1]->sh_size;
|
||||
while (a < osec.thunks.size() &&
|
||||
osec.thunks[a]->offset < c_end - MAX_DISTANCE)
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
}
|
||||
|
||||
// Create a thunk for input sections between B and C and place it at D.
|
||||
osec.thunks.emplace_back(new RangeExtensionThunk<E>{osec});
|
||||
|
||||
RangeExtensionThunk<E> &thunk = *osec.thunks.back();
|
||||
thunk.thunk_idx = osec.thunks.size() - 1;
|
||||
thunk.offset = offset;
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need thunks.
|
||||
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
|
||||
[&](InputSection<E> *isec) {
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::vector<RangeExtensionRef> &range_extn = isec->get_range_extn();
|
||||
range_extn.resize(rels.size());
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type != R_AARCH64_CALL26 && rel.r_type != R_AARCH64_JUMP26)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
|
||||
|
||||
// Skip if the destination is within reach.
|
||||
if (is_reachable(ctx, sym, *isec, rel))
|
||||
continue;
|
||||
|
||||
// If the symbol is already in another thunk, reuse it.
|
||||
if (sym.extra.thunk_idx != -1) {
|
||||
range_extn[i] = {sym.extra.thunk_idx, sym.extra.thunk_sym_idx};
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the symbol to this thunk if it's not added already.
|
||||
range_extn[i] = {thunk.thunk_idx, -1};
|
||||
|
||||
if (!(sym.flags.fetch_or(NEEDS_RANGE_EXTN_THUNK) &
|
||||
NEEDS_RANGE_EXTN_THUNK)) {
|
||||
std::scoped_lock lock(thunk.mu);
|
||||
thunk.symbols.push_back(&sym);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Now that we know the number of symbols in the thunk, we can compute
|
||||
// its size.
|
||||
offset += thunk.size();
|
||||
|
||||
// Sort symbols added to the thunk to make the output deterministic.
|
||||
sort(thunk.symbols, [](Symbol<E> *a, Symbol<E> *b) { return *a < *b; });
|
||||
|
||||
// Assign offsets within the thunk to the symbols.
|
||||
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
|
||||
sym->extra.thunk_idx = thunk.thunk_idx;
|
||||
sym->extra.thunk_sym_idx = i++;
|
||||
}
|
||||
|
||||
// Scan relocations again to fix symbol offsets in the last thunk.
|
||||
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
|
||||
[&](InputSection<E> *isec) {
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = isec->get_range_extn();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
if (range_extn[i].thunk_idx == thunk.thunk_idx) {
|
||||
Symbol<E> &sym = *isec->file.symbols[rels[i].r_sym];
|
||||
range_extn[i].sym_idx = sym.extra.thunk_sym_idx;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Move B forward to point to the begining of the next group.
|
||||
b = c;
|
||||
}
|
||||
|
||||
while (a < osec.thunks.size())
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
|
||||
osec.shdr.sh_size = offset;
|
||||
}
|
||||
|
||||
static void gc_thunk_symbols(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
for (std::unique_ptr<RangeExtensionThunk<E>> &thunk : osec.thunks) {
|
||||
i64 sz = thunk->symbols.size();
|
||||
thunk->symbol_map.resize(sz);
|
||||
thunk->used.reset(new std::atomic_bool[sz]{});
|
||||
}
|
||||
|
||||
// Mark referenced thunk symbols
|
||||
tbb::parallel_for_each(osec.members, [&](InputSection<E> *isec) {
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = isec->get_range_extn();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
RangeExtensionRef &ref = range_extn[i];
|
||||
if (ref.thunk_idx == -1)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *isec->file.symbols[rels[i].r_sym];
|
||||
if (!is_reachable(ctx, sym, *isec, rels[i]))
|
||||
osec.thunks[ref.thunk_idx]->used[ref.sym_idx] = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Remove unreferenced thunk symbols
|
||||
tbb::parallel_for_each(osec.thunks,
|
||||
[&](std::unique_ptr<RangeExtensionThunk<E>> &thunk) {
|
||||
i64 i = 0;
|
||||
for (i64 j = 0; j < thunk->symbols.size(); j++) {
|
||||
if (thunk->used[j]) {
|
||||
thunk->symbol_map[j] = i;
|
||||
thunk->symbols[i] = thunk->symbols[j];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
thunk->symbols.resize(i);
|
||||
});
|
||||
}
|
||||
|
||||
static void shrink_section(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
std::span<std::unique_ptr<RangeExtensionThunk<E>>> thunks = osec.thunks;
|
||||
std::span<InputSection<E> *> members = osec.members;
|
||||
|
||||
i64 offset = 0;
|
||||
|
||||
auto add_thunk = [&] {
|
||||
thunks[0]->offset = offset;
|
||||
offset += thunks[0]->size();
|
||||
thunks = thunks.subspan(1);
|
||||
};
|
||||
|
||||
auto add_isec = [&] {
|
||||
offset = align_to(offset, 1 << members[0]->p2align);
|
||||
members[0]->offset = offset;
|
||||
offset += members[0]->sh_size;
|
||||
members = members.subspan(1);
|
||||
};
|
||||
|
||||
while (!thunks.empty() && !members.empty()) {
|
||||
if (thunks[0]->offset < members[0]->offset)
|
||||
add_thunk();
|
||||
else
|
||||
add_isec();
|
||||
}
|
||||
|
||||
while (!thunks.empty())
|
||||
add_thunk();
|
||||
while (!members.empty())
|
||||
add_isec();
|
||||
|
||||
assert(offset <= osec.shdr.sh_size);
|
||||
osec.shdr.sh_size = offset;
|
||||
}
|
||||
|
||||
// ARM64's call/jump instructions take 27 bits displacement, so they
|
||||
// can refer only up to ±128 MiB. If a branch target is further than
|
||||
// that, we need to let it branch to a linker-synthesized code
|
||||
// sequence that construct a full 32 bit address in a register and
|
||||
// jump there. That linker-synthesized code is called "thunk".
|
||||
i64 create_range_extension_thunks(Context<E> &ctx) {
|
||||
Timer t(ctx, "create_range_extension_thunks");
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->range_extn.resize(file->sections.size());
|
||||
|
||||
// First, we create thunks with a pessimistic assumption that all
|
||||
// out-of-section relocations would need thunks. To do so, we start
|
||||
// with an initial layout in which output sections are separated far
|
||||
// apart.
|
||||
for (i64 i = 0; Chunk<E> *chunk : ctx.chunks)
|
||||
if (chunk->shdr.sh_flags & SHF_ALLOC)
|
||||
chunk->shdr.sh_addr = i++ << 31;
|
||||
|
||||
std::vector<OutputSection<E> *> sections;
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
if (!osec->members.empty() && (osec->shdr.sh_flags & SHF_EXECINSTR))
|
||||
sections.push_back(osec.get());
|
||||
|
||||
for (OutputSection<E> *osec : sections)
|
||||
create_thunks(ctx, *osec);
|
||||
|
||||
// Recompute file layout.
|
||||
set_osec_offsets(ctx);
|
||||
|
||||
// Based on the current file layout, remove thunk symbols that turned
|
||||
// out to be unnecessary.
|
||||
tbb::parallel_for_each(sections, [&](OutputSection<E> *osec) {
|
||||
gc_thunk_symbols(ctx, *osec);
|
||||
});
|
||||
|
||||
// Recompute output section sizes that contain thunks. New section
|
||||
// sizes must be equal to or smaller than previous values, so all
|
||||
// relocations that were previously reachable will still be reachable
|
||||
// after this step.
|
||||
for (OutputSection<E> *osec : sections)
|
||||
shrink_section(ctx, *osec);
|
||||
|
||||
// Compute the final layout.
|
||||
return set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
static const u32 data[] = {
|
||||
0x90000010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
|
||||
0x91000210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(ENTRY_SIZE == sizeof(data));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = output_section.shdr.sh_addr + offset + i * ENTRY_SIZE;
|
||||
|
||||
u8 *loc = buf + i * ENTRY_SIZE;
|
||||
memcpy(loc , data, sizeof(data));
|
||||
write_adr(loc, bits(page(S) - page(P), 32, 12));
|
||||
*(u32 *)(loc + 4) |= bits(S, 11, 0) << 10;
|
||||
}
|
||||
}
|
||||
|
||||
void write_thunks(Context<E> &ctx) {
|
||||
Timer t(ctx, "write_thunks");
|
||||
|
||||
tbb::parallel_for_each(ctx.output_sections,
|
||||
[&](std::unique_ptr<OutputSection<E>> &osec) {
|
||||
tbb::parallel_for_each(osec->thunks,
|
||||
[&](std::unique_ptr<RangeExtensionThunk<E>> &thunk) {
|
||||
thunk->copy_buf(ctx);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,555 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = I386;
|
||||
|
||||
static void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0xb3, 0, 0, 0, 0, // pushl GOTPLT+4(%ebx)
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *GOTPLT+8(%ebx)
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 8;
|
||||
} else {
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0x35, 0, 0, 0, 0, // pushl GOTPLT+4
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+8
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr + 8;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym,
|
||||
i64 idx) {
|
||||
u8 *ent = buf + ctx.plt_hdr_size + sym.get_plt_idx(ctx) * ctx.plt_size;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 data[] = {
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0x68, 0, 0, 0, 0, // pushl $reloc_offset
|
||||
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x68, 0, 0, 0, 0, // pushl $reloc_offset
|
||||
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym.get_gotplt_addr(ctx);
|
||||
}
|
||||
|
||||
*(u32 *)(ent + 7) = idx * sizeof(ElfRel<E>);
|
||||
*(u32 *)(ent + 12) = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 16;
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
write_plt_header(ctx, buf);
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++)
|
||||
write_plt_entry(ctx, buf, *symbols[i], i);
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 data[] = {
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
}
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_NONE:
|
||||
return;
|
||||
case R_386_32:
|
||||
*(u32 *)loc = val;
|
||||
return;
|
||||
case R_386_PC32:
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_386_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
const SectionFragmentRef<E> *frag_ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
frag_ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto write8 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write8s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(u16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write16s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 15), 1 << 15);
|
||||
*(u16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
*(u32 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : this->get_addend(rel))
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
write8(S + A);
|
||||
continue;
|
||||
case R_386_16:
|
||||
write16(S + A);
|
||||
continue;
|
||||
case R_386_32:
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
write32(S + A);
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_386_32, (u32)sym.get_dynsym_idx(ctx)};
|
||||
write32(A);
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_386_RELATIVE, 0};
|
||||
write32(S + A);
|
||||
}
|
||||
continue;
|
||||
case R_386_PC8:
|
||||
write8s(S + A);
|
||||
continue;
|
||||
case R_386_PC16:
|
||||
write16s(S + A);
|
||||
continue;
|
||||
case R_386_PC32:
|
||||
if (sym.is_absolute() || !sym.is_imported || !ctx.arg.shared) {
|
||||
write32(S + A - P);
|
||||
} else {
|
||||
*dynrel++ = {P, R_386_32, (u32)sym.get_dynsym_idx(ctx)};
|
||||
write32(A);
|
||||
}
|
||||
continue;
|
||||
case R_386_PLT32:
|
||||
write32(S + A - P);
|
||||
continue;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
write32(G + A);
|
||||
continue;
|
||||
case R_386_GOTOFF:
|
||||
write32(S + A - GOT);
|
||||
continue;
|
||||
case R_386_GOTPC:
|
||||
write32(GOT + A - P);
|
||||
continue;
|
||||
case R_386_TLS_GOTIE:
|
||||
write32(sym.get_gottp_addr(ctx) + A - GOT);
|
||||
continue;
|
||||
case R_386_TLS_LE:
|
||||
write32(S + A - ctx.tls_end);
|
||||
continue;
|
||||
case R_386_TLS_IE:
|
||||
write32(sym.get_gottp_addr(ctx) + A);
|
||||
continue;
|
||||
case R_386_TLS_GD:
|
||||
if (sym.get_tlsgd_idx(ctx) == -1) {
|
||||
// Relax GD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_386_PLT32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %rax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // add $0, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 5) = ctx.tls_end - S - A;
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %rax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // add $0, %rax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 6) = ctx.tls_end - S - A;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
} else {
|
||||
write32(sym.get_tlsgd_addr(ctx) + A - GOT);
|
||||
}
|
||||
continue;
|
||||
case R_386_TLS_LDM:
|
||||
if (ctx.got->tlsld_idx == -1) {
|
||||
// Relax LD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_386_PLT32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x8d, 0x74, 0x26, 0x00, // lea (%esi,1), %esi
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x8d, 0x74, 0x26, 0x00, // lea (%esi,1), %esi
|
||||
0x66, 0x90,
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
} else {
|
||||
write32(ctx.got->get_tlsld_addr(ctx) + A - GOT);
|
||||
}
|
||||
continue;
|
||||
case R_386_TLS_LDO_32:
|
||||
if (ctx.got->tlsld_idx == -1)
|
||||
write32(S + A - ctx.tls_end);
|
||||
else
|
||||
write32(S + A - ctx.tls_begin);
|
||||
continue;
|
||||
case R_386_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
continue;
|
||||
case R_386_TLS_GOTDESC:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
static const u8 insn[] = {
|
||||
0x8d, 0x05, 0, 0, 0, 0, // lea 0, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
write32(S + A - ctx.tls_end);
|
||||
} else {
|
||||
write32(sym.get_tlsdesc_addr(ctx) + A - GOT);
|
||||
}
|
||||
continue;
|
||||
case R_386_TLS_DESC_CALL:
|
||||
if (ctx.arg.relax && !ctx.arg.shared) {
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_386_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 addend;
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto write8 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write8s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(u16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write16s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 15), 1 << 15);
|
||||
*(u16 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : this->get_addend(rel))
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
write8(S + A);
|
||||
continue;
|
||||
case R_386_16:
|
||||
write16(S + A);
|
||||
continue;
|
||||
case R_386_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A;
|
||||
continue;
|
||||
case R_386_PC8:
|
||||
write8s(S + A);
|
||||
continue;
|
||||
case R_386_PC16:
|
||||
write16s(S + A);
|
||||
continue;
|
||||
case R_386_PC32:
|
||||
*(u32 *)loc = S + A;
|
||||
continue;
|
||||
case R_386_GOTPC:
|
||||
*(u32 *)loc = GOT + A;
|
||||
continue;
|
||||
case R_386_GOTOFF:
|
||||
*(u32 *)loc = S + A - GOT;
|
||||
continue;
|
||||
case R_386_TLS_LDO_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A - ctx.tls_begin;
|
||||
continue;
|
||||
case R_386_SIZE32:
|
||||
*(u32 *)loc = sym.esym().st_size + A;
|
||||
continue;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_386_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_16: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // DSO
|
||||
{ NONE, ERROR, ERROR, ERROR }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_386_32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_386_PC8:
|
||||
case R_386_PC16: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_386_PC32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, DYNREL, DYNREL }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
case R_386_GOTPC:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_386_PLT32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, NONE, PLT, PLT }, // DSO
|
||||
{ NONE, NONE, PLT, PLT }, // PIE
|
||||
{ NONE, NONE, PLT, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_IE:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_GOT32)
|
||||
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported)
|
||||
i++;
|
||||
else
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_GOT32)
|
||||
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
if (!ctx.arg.relax || ctx.arg.shared)
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_DESC_CALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,813 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = RISCV64;
|
||||
|
||||
static u32 bit(u32 val, i64 pos) {
|
||||
return (val >> pos) & 1;
|
||||
}
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u32 bits(u32 val, i64 hi, i64 lo) {
|
||||
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
static u32 itype(u32 val) {
|
||||
return val << 20;
|
||||
}
|
||||
|
||||
static u32 stype(u32 val) {
|
||||
return bits(val, 11, 5) << 25 | bits(val, 4, 0) << 7;
|
||||
}
|
||||
|
||||
static u32 btype(u32 val) {
|
||||
return bit(val, 12) << 31 | bits(val, 10, 5) << 25 |
|
||||
bits(val, 4, 1) << 8 | bit(val, 11) << 7;
|
||||
}
|
||||
|
||||
static u32 utype(u32 val) {
|
||||
// U-type instructions are used in combination with I-type
|
||||
// instructions. U-type insn sets an immediate to the upper 20-bits
|
||||
// of a register. I-type insn sign-extends a 12-bits immediate and
|
||||
// add it to a register value to construct a complete value. 0x800
|
||||
// is added here to compensate for the sign-extension.
|
||||
return bits(val + 0x800, 31, 12) << 12;
|
||||
}
|
||||
|
||||
static u32 jtype(u32 val) {
|
||||
return bit(val, 20) << 31 | bits(val, 10, 1) << 21 |
|
||||
bit(val, 11) << 20 | bits(val, 19, 12) << 12;
|
||||
}
|
||||
|
||||
static u32 cbtype(u32 val) {
|
||||
return bit(val, 8) << 12 | bit(val, 4) << 11 | bit(val, 3) << 10 |
|
||||
bit(val, 7) << 6 | bit(val, 6) << 5 | bit(val, 2) << 4 |
|
||||
bit(val, 1) << 3 | bit(val, 5) << 2;
|
||||
}
|
||||
|
||||
static u32 cjtype(u32 val) {
|
||||
return bit(val, 11) << 12 | bit(val, 4) << 11 | bit(val, 9) << 10 |
|
||||
bit(val, 8) << 9 | bit(val, 10) << 8 | bit(val, 6) << 7 |
|
||||
bit(val, 7) << 6 | bit(val, 3) << 5 | bit(val, 2) << 4 |
|
||||
bit(val, 1) << 3 | bit(val, 5) << 2;
|
||||
}
|
||||
|
||||
static void write_itype(u32 *loc, u32 val) {
|
||||
u32 mask = 0b000000'00000'11111'111'11111'1111111;
|
||||
*loc = (*loc & mask) | itype(val);
|
||||
}
|
||||
|
||||
static void write_stype(u32 *loc, u32 val) {
|
||||
u32 mask = 0b000000'11111'11111'111'00000'1111111;
|
||||
*loc = (*loc & mask) | stype(val);
|
||||
}
|
||||
|
||||
static void write_btype(u32 *loc, u32 val) {
|
||||
u32 mask = 0b000000'11111'11111'111'00000'1111111;
|
||||
*loc = (*loc & mask) | btype(val);
|
||||
}
|
||||
|
||||
static void write_utype(u32 *loc, u32 val) {
|
||||
u32 mask = 0b000000'00000'00000'000'11111'1111111;
|
||||
*loc = (*loc & mask) | utype(val);
|
||||
}
|
||||
|
||||
static void write_jtype(u32 *loc, u32 val) {
|
||||
u32 mask = 0b000000'00000'00000'000'11111'1111111;
|
||||
*loc = (*loc & mask) | jtype(val);
|
||||
}
|
||||
|
||||
static void write_cbtype(u16 *loc, u32 val) {
|
||||
u32 mask = 0b1110001110000011;
|
||||
*loc = (*loc & mask) | cbtype(val);
|
||||
}
|
||||
|
||||
static void write_cjtype(u16 *loc, u32 val) {
|
||||
u32 mask = 0b1110000000000011;
|
||||
*loc = (*loc & mask) | cjtype(val);
|
||||
}
|
||||
|
||||
static void write_plt_header(Context<E> &ctx) {
|
||||
u32 *buf = (u32 *)(ctx.buf + ctx.plt->shdr.sh_offset);
|
||||
|
||||
static const u32 plt0[] = {
|
||||
0x00000397, // auipc t2, %pcrel_hi(.got.plt)
|
||||
0x41c30333, // sub t1, t1, t3 # .plt entry + hdr + 12
|
||||
0x0003be03, // ld t3, %pcrel_lo(1b)(t2) # _dl_runtime_resolve
|
||||
0xfd430313, // addi t1, t1, -44 # .plt entry
|
||||
0x00038293, // addi t0, t2, %pcrel_lo(1b) # &.got.plt
|
||||
0x00135313, // srli t1, t1, 1 # .plt entry offset
|
||||
0x0082b283, // ld t0, 8(t0) # link map
|
||||
0x000e0067, // jr t3
|
||||
};
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
write_utype(buf, gotplt - plt);
|
||||
write_itype(buf + 2, gotplt - plt);
|
||||
write_itype(buf + 4, gotplt - plt);
|
||||
}
|
||||
|
||||
static void write_plt_entry(Context<E> &ctx, Symbol<E> &sym) {
|
||||
u32 *ent = (u32 *)(ctx.buf + ctx.plt->shdr.sh_offset + ctx.plt_hdr_size +
|
||||
sym.get_plt_idx(ctx) * ctx.plt_size);
|
||||
|
||||
static const u32 data[] = {
|
||||
0x00000e17, // auipc t3, %pcrel_hi([email protected])
|
||||
0x000e3e03, // ld t3, %pcrel_lo(1b)(t3)
|
||||
0x000e0367, // jalr t1, t3
|
||||
0x00000013, // nop
|
||||
};
|
||||
|
||||
u64 gotplt = sym.get_gotplt_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_utype(ent, gotplt - plt);
|
||||
write_itype(ent + 1, gotplt - plt);
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
write_plt_header(ctx);
|
||||
for (Symbol<E> *sym : symbols)
|
||||
write_plt_entry(ctx, *sym);
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u32 *buf = (u32 *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
static const u32 data[] = {
|
||||
0x00000e17, // auipc t3, %pcrel_hi([email protected])
|
||||
0x000e3e03, // ld t3, %pcrel_lo(1b)(t3)
|
||||
0x000e0367, // jalr t1, t3
|
||||
0x00000013, // nop
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u32 *ent = buf + sym->get_pltgot_idx(ctx) * 4;
|
||||
u64 got = sym->get_got_addr(ctx);
|
||||
u64 plt = sym->get_plt_addr(ctx);
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_utype(ent, got - plt);
|
||||
write_itype(ent + 1, got - plt);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_ADD32:
|
||||
*(u32 *)loc += val;
|
||||
return;
|
||||
case R_RISCV_SUB8:
|
||||
*loc -= val;
|
||||
return;
|
||||
case R_RISCV_SUB16:
|
||||
*(u16 *)loc -= val;
|
||||
return;
|
||||
case R_RISCV_SUB32:
|
||||
*(u32 *)loc -= val;
|
||||
return;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - val) & 0b0011'1111);
|
||||
return;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | (val & 0b0011'1111);
|
||||
return;
|
||||
case R_RISCV_SET8:
|
||||
*loc = val;
|
||||
return;
|
||||
case R_RISCV_SET16:
|
||||
*(u16 *)loc = val;
|
||||
return;
|
||||
case R_RISCV_32_PCREL:
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<i32> r_deltas = get_r_deltas();
|
||||
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_RISCV_NONE || rel.r_type == R_RISCV_RELAX ||
|
||||
rel.r_type == R_RISCV_ALIGN)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
i64 r_offset = rel.r_offset + r_deltas[i];
|
||||
u8 *loc = base + r_offset;
|
||||
|
||||
const SectionFragmentRef<E> *frag_ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
frag_ref = &rel_fragments[frag_idx++];
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
*(u32 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
*(u64 *)loc = S + A;
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_RISCV_64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
*(u64 *)loc = A;
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_RISCV_RELATIVE, 0, (i64)(S + A)};
|
||||
*(u64 *)loc = S + A;
|
||||
}
|
||||
break;
|
||||
case R_RISCV_TLS_DTPMOD32:
|
||||
case R_RISCV_TLS_DTPMOD64:
|
||||
case R_RISCV_TLS_DTPREL32:
|
||||
case R_RISCV_TLS_DTPREL64:
|
||||
case R_RISCV_TLS_TPREL32:
|
||||
case R_RISCV_TLS_TPREL64:
|
||||
Error(ctx) << *this << ": unsupported relocation: " << rel;
|
||||
break;
|
||||
case R_RISCV_BRANCH:
|
||||
write_btype((u32 *)loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_JAL:
|
||||
write_jtype((u32 *)loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT: {
|
||||
if (r_deltas[i + 1] - r_deltas[i] != 0) {
|
||||
// auipc + jalr -> jal
|
||||
assert(r_deltas[i + 1] - r_deltas[i] == -4);
|
||||
u32 jalr = *(u32 *)&contents[rels[i].r_offset + 4];
|
||||
*(u32 *)loc = (0b11111'000000 & jalr) | 0b101111;
|
||||
write_jtype((u32 *)loc, S + A - P);
|
||||
} else {
|
||||
u64 val = sym.esym().is_undef_weak() ? 0 : S + A - P;
|
||||
write_utype((u32 *)loc, val);
|
||||
write_itype((u32 *)(loc + 4), val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_GOT_HI20:
|
||||
*(u32 *)loc = G + GOT + A - P;
|
||||
break;
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
*(u32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_RISCV_TLS_GD_HI20:
|
||||
*(u32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_RISCV_PCREL_HI20:
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// Calling an undefined weak symbol does not make sense.
|
||||
// We make such call into an infinite loop. This should
|
||||
// help debugging of a faulty program.
|
||||
*(u32 *)loc = P;
|
||||
} else {
|
||||
*(u32 *)loc = S + A - P;
|
||||
}
|
||||
break;
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
assert(sym.get_input_section() == this);
|
||||
assert(sym.value < r_offset);
|
||||
write_itype((u32 *)loc, *(u32 *)(base + sym.value));
|
||||
break;
|
||||
case R_RISCV_LO12_I:
|
||||
case R_RISCV_TPREL_LO12_I:
|
||||
write_itype((u32 *)loc, S + A);
|
||||
break;
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
assert(sym.get_input_section() == this);
|
||||
assert(sym.value < r_offset);
|
||||
write_stype((u32 *)loc, *(u32 *)(base + sym.value));
|
||||
break;
|
||||
case R_RISCV_LO12_S:
|
||||
case R_RISCV_TPREL_LO12_S:
|
||||
write_stype((u32 *)loc, S + A);
|
||||
break;
|
||||
case R_RISCV_HI20:
|
||||
write_utype((u32 *)loc, S + A);
|
||||
break;
|
||||
case R_RISCV_TPREL_HI20:
|
||||
write_utype((u32 *)loc, S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_RISCV_TPREL_ADD:
|
||||
break;
|
||||
case R_RISCV_ADD8:
|
||||
loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD16:
|
||||
*(u16 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD32:
|
||||
*(u32 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD64:
|
||||
*(u64 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_SUB8:
|
||||
loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB16:
|
||||
*(u16 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB32:
|
||||
*(u32 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB64:
|
||||
*(u64 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_RVC_BRANCH:
|
||||
write_cbtype((u16 *)loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_RVC_JUMP:
|
||||
write_cjtype((u16 *)loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_RVC_LUI:
|
||||
Error(ctx) << *this << ": unsupported relocation: " << rel;
|
||||
break;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | ((S + A) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET8:
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET16:
|
||||
*(u16 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET32:
|
||||
*(u32 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_32_PCREL:
|
||||
*(u32 *)loc = S + A - P;
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
|
||||
// In the above loop, PC-relative HI20 relocations overwrote
|
||||
// instructions with full 32-bit values to allow their corresponding
|
||||
// PCREL_LO12 relocations to read their values. This loop restore
|
||||
// the original instructions.
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
switch (rels[i].r_type) {
|
||||
case R_RISCV_GOT_HI20:
|
||||
case R_RISCV_PCREL_HI20:
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
case R_RISCV_TLS_GD_HI20: {
|
||||
u32 *loc = (u32 *)(base + rels[i].r_offset + r_deltas[i]);
|
||||
u32 val = *loc;
|
||||
*loc = *(u32 *)&contents[rels[i].r_offset];
|
||||
write_utype(loc, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_RISCV_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 addend;
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
*(u32 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_ADD8:
|
||||
*loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD16:
|
||||
*(u16 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD32:
|
||||
*(u32 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD64:
|
||||
*(u64 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_SUB8:
|
||||
*loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB16:
|
||||
*(u16 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB32:
|
||||
*(u32 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB64:
|
||||
*(u64 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | ((S + A) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET8:
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET16:
|
||||
*(u16 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET32:
|
||||
*(u32 *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
|
||||
// A non-alloc section isn't relaxed, so just copy it as one big chunk.
|
||||
if (!(shdr().sh_flags & SHF_ALLOC)) {
|
||||
if (is_compressed())
|
||||
uncompress(ctx, buf);
|
||||
else
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
return;
|
||||
}
|
||||
|
||||
// Memory-allocated sections may be relaxed, so copy each segment
|
||||
// individually.
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<i32> r_deltas = get_r_deltas();
|
||||
i64 pos = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
i64 delta = r_deltas[i + 1] - r_deltas[i];
|
||||
if (delta == 0)
|
||||
continue;
|
||||
assert(delta < 0);
|
||||
|
||||
const ElfRel<E> &r = rels[i];
|
||||
memcpy(buf, contents.data() + pos, r.r_offset - pos);
|
||||
buf += r.r_offset - pos;
|
||||
pos = r.r_offset - delta;
|
||||
}
|
||||
|
||||
memcpy(buf, contents.data() + pos, contents.size() - pos);
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_RISCV_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
case R_RISCV_HI20: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // DSO
|
||||
{ NONE, ERROR, ERROR, ERROR }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_64: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_TLS_DTPMOD32:
|
||||
case R_RISCV_TLS_DTPMOD64:
|
||||
case R_RISCV_TLS_DTPREL32:
|
||||
case R_RISCV_TLS_DTPREL64:
|
||||
case R_RISCV_TLS_TPREL32:
|
||||
case R_RISCV_TLS_TPREL64:
|
||||
Error(ctx) << *this << ": unsupported relocation: " << rel;
|
||||
break;
|
||||
case R_RISCV_BRANCH:
|
||||
case R_RISCV_JAL:
|
||||
break;
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_RISCV_GOT_HI20:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
ctx.has_gottp_rel = true;
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_RISCV_TLS_GD_HI20:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_RISCV_PCREL_HI20:
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
case R_RISCV_LO12_I:
|
||||
case R_RISCV_LO12_S:
|
||||
case R_RISCV_TPREL_HI20:
|
||||
case R_RISCV_TPREL_LO12_I:
|
||||
case R_RISCV_TPREL_LO12_S:
|
||||
case R_RISCV_TPREL_ADD:
|
||||
case R_RISCV_ADD8:
|
||||
case R_RISCV_ADD16:
|
||||
case R_RISCV_ADD32:
|
||||
case R_RISCV_ADD64:
|
||||
case R_RISCV_SUB8:
|
||||
case R_RISCV_SUB16:
|
||||
case R_RISCV_SUB32:
|
||||
case R_RISCV_SUB64:
|
||||
case R_RISCV_ALIGN:
|
||||
break;
|
||||
case R_RISCV_RVC_BRANCH:
|
||||
case R_RISCV_RVC_JUMP:
|
||||
break;
|
||||
case R_RISCV_RVC_LUI:
|
||||
Error(ctx) << *this << ": unsupported relocation: " << rel;
|
||||
break;
|
||||
case R_RISCV_RELAX:
|
||||
case R_RISCV_SUB6:
|
||||
case R_RISCV_SET6:
|
||||
case R_RISCV_SET8:
|
||||
case R_RISCV_SET16:
|
||||
case R_RISCV_SET32:
|
||||
break;
|
||||
case R_RISCV_32_PCREL: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_resizable(Context<E> &ctx, InputSection<E> *isec) {
|
||||
return isec && (isec->shdr().sh_flags & SHF_ALLOC);
|
||||
}
|
||||
|
||||
// Initializes r_deltas and sorted_symbols.
|
||||
static void initialize_storage(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->r_deltas.resize(file->sections.size());
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (is_resizable(ctx, isec.get()))
|
||||
isec->get_r_deltas().resize(isec->get_rels(ctx).size() + 1);
|
||||
|
||||
file->sorted_symbols.resize(file->sections.size());
|
||||
|
||||
for (Symbol<E> *sym : file->symbols)
|
||||
if (sym->file == file)
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
file->sorted_symbols[isec->shndx].push_back(sym);
|
||||
|
||||
for (std::vector<Symbol<E> *> &vec : file->sorted_symbols)
|
||||
sort(vec, [](Symbol<E> *a, Symbol<E> *b) { return a->value < b->value; });
|
||||
});
|
||||
}
|
||||
|
||||
// Returns the distance between a relocated place and a symbol.
|
||||
static i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
|
||||
InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
// We handle absolute symbols as if they were infinitely far away
|
||||
// because `relax_section` may increase a distance between a branch
|
||||
// instruction and an absolute symbol. Branching to an absolute
|
||||
// location is extremely rare in real code, though.
|
||||
if (sym.is_absolute())
|
||||
return INT32_MAX;
|
||||
|
||||
// Likewise, relocations against weak undefined symbols won't be relaxed.
|
||||
if (sym.esym().is_undef_weak())
|
||||
return INT32_MAX;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol.
|
||||
i64 S = sym.get_addr(ctx);
|
||||
i64 A = rel.r_addend;
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
return S + A - P;
|
||||
}
|
||||
|
||||
// Relax R_RISCV_CALL and R_RISCV_CALL_PLT relocations.
|
||||
static void relax_section(Context<E> &ctx, InputSection<E> &isec) {
|
||||
std::span<i32> r_deltas = isec.get_r_deltas();
|
||||
std::span<Symbol<E> *> syms = isec.get_sorted_symbols();
|
||||
i64 delta = 0;
|
||||
|
||||
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &r = rels[i];
|
||||
i64 delta2 = 0;
|
||||
|
||||
r_deltas[i] += delta;
|
||||
|
||||
switch (r.r_type) {
|
||||
case R_RISCV_ALIGN: {
|
||||
// R_RISCV_ALIGN refers NOP instructions. We need to eliminate
|
||||
// some or all of the instructions so that the instruction that
|
||||
// immediately follows the NOPs is aligned to a specified
|
||||
// alignment boundary.
|
||||
u64 loc = isec.get_addr() + r.r_offset + delta;
|
||||
|
||||
// The total bytes of NOPs is stored to r_addend, so the next
|
||||
// instruction is r_addend away.
|
||||
u64 next_loc = loc + r.r_addend;
|
||||
|
||||
u64 alignment = (std::popcount<u64>(r.r_addend) == 1)
|
||||
? r.r_addend : next_power_of_two(r.r_addend);
|
||||
|
||||
if (next_loc % alignment)
|
||||
delta2 = align_to(loc, alignment) - next_loc;
|
||||
break;
|
||||
}
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT:
|
||||
if (ctx.arg.relax) {
|
||||
if (i == rels.size() - 1 || rels[i + 1].r_type != R_RISCV_RELAX)
|
||||
break;
|
||||
|
||||
// If the jump target is within ±1 MiB, we can replace AUIPC+JALR
|
||||
// with JAL, saving 4 bytes.
|
||||
Symbol<E> &sym = *isec.file.symbols[r.r_sym];
|
||||
i64 dist = compute_distance(ctx, sym, isec, r);
|
||||
if (dist % 2 == 0 && -(1 << 20) <= dist && dist < (1 << 20))
|
||||
delta2 = -4;
|
||||
}
|
||||
}
|
||||
|
||||
if (delta2 == 0)
|
||||
continue;
|
||||
|
||||
while (!syms.empty() && syms[0]->value <= r.r_offset) {
|
||||
syms[0]->value += delta;
|
||||
syms = syms.subspan(1);
|
||||
}
|
||||
|
||||
delta += delta2;
|
||||
}
|
||||
|
||||
for (Symbol<E> *sym : syms)
|
||||
sym->value += delta;
|
||||
r_deltas[rels.size()] += delta;
|
||||
|
||||
isec.sh_size += delta;
|
||||
}
|
||||
|
||||
// RISC-V instructions are 16 or 32 bits long, so immediates encoded
|
||||
// in instructions can't be 32 bits long. Therefore, branch and load
|
||||
// instructions can't refer the 4 GiB address space unlike x86-64.
|
||||
// In fact, JAL (jump and link) instruction can jump to only within
|
||||
// ±1 MiB as their immediate is only 21 bits long.
|
||||
//
|
||||
// If you want to jump to somewhere further than that, you need to
|
||||
// construct a full 32-bit offset using multiple instruction and
|
||||
// branch to that place (e.g. AUIPC and JALR instead of JAL).
|
||||
// In this comment, we refer instructions such as JAL as the short
|
||||
// encoding and ones such as AUIPC+JALR as the long encoding.
|
||||
//
|
||||
// By default, compiler always uses the long encoding so that branch
|
||||
// targets are always encodable. This is a safe bet for them but
|
||||
// may result in inefficient code. Therefore, the RISC-V psABI defines
|
||||
// a mechanism for the linker to replace long encoding instructions
|
||||
// with short ones, shrinking the section and increasing the code
|
||||
// density.
|
||||
//
|
||||
// This is contrary to the psABIs for the other RISC processors such as
|
||||
// ARM64. Typically, they use short instructions by default, and a
|
||||
// linker creates so-called "thunks" to extend ranges of short jumps.
|
||||
// On RISC-V, instructions are in the long encoding by default, and
|
||||
// the linker shrinks them if it can.
|
||||
//
|
||||
// When we shrink a section, we need to adjust relocation offsets and
|
||||
// symbol values. For example, if we replace AUIPC+JALR with JAL
|
||||
// (which saves 4 bytes), all relocations pointing to anywhere after
|
||||
// that location need to be shifted by 4. In addition to that, any
|
||||
// symbol that refers anywhere after that locatioin need to be shifted
|
||||
// by 4 bytes as well.
|
||||
//
|
||||
// For relocations, we use `r_deltas` array to memorize how many bytes
|
||||
// have be adjusted. For symbols, we directly mutate their `value`
|
||||
// member.
|
||||
//
|
||||
// This operation seems to be optional, as by default instructions are
|
||||
// using the long encoding, but calling this function is actually
|
||||
// mandatory because of R_RISCV_ALIGN. R_RISCV_ALIGN relocation is a
|
||||
// directive to the linker to align the location referred to by the
|
||||
// relocation to a specified byte boundary. We at least have to
|
||||
// interpret them satisfy the constraints imposed by R_RISCV_ALIGN
|
||||
// relocations.
|
||||
i64 riscv_resize_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "riscv_resize_sections");
|
||||
initialize_storage(ctx);
|
||||
|
||||
// Find R_RISCV_CALL AND R_RISCV_CALL_PLT that can be relaxed.
|
||||
// This step should only shrink sections.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (is_resizable(ctx, isec.get()))
|
||||
relax_section(ctx, *isec);
|
||||
});
|
||||
|
||||
// Re-compute section offset again to finalize them.
|
||||
compute_section_sizes(ctx);
|
||||
return set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,830 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = X86_64;
|
||||
|
||||
// The compact PLT format is used when `-z now` is given. If the flag
|
||||
// is given, all PLT symbols are resolved eagerly on startup, so we
|
||||
// can omit code for lazy symbol resolution from PLT in that case.
|
||||
static void write_compact_plt(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + ctx.plt->shdr.sh_offset;
|
||||
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : ctx.plt->symbols) {
|
||||
u8 *ent = buf + sym->get_plt_idx(ctx) * ctx.plt_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
}
|
||||
}
|
||||
|
||||
// The IBTPLT is a security-enhanced version of the regular PLT.
|
||||
// It uses Indirect Branch Tracking (IBT) feature which is part of
|
||||
// Intel Control-Flow Enforcement (CET).
|
||||
//
|
||||
// Note that our IBTPLT instruction sequence is different from the one
|
||||
// used in GNU ld. GNU's IBTPLT implementation uses two separate
|
||||
// sections (.plt and .plt.sec) in which one PLT entry takes 32 bytes
|
||||
// in total. Our PLT consists of just .plt and each entry is 16 bytes
|
||||
// long.
|
||||
//
|
||||
// Our PLT entry clobbers r11, but that's fine because the resolver
|
||||
// function (_dl_runtime_resolve) does not preserve r11 anyway.
|
||||
static void write_ibtplt(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + ctx.plt->shdr.sh_offset;
|
||||
|
||||
// Write PLT header
|
||||
static const u8 plt0[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0x53, // push %r11
|
||||
0xff, 0x35, 0, 0, 0, 0, // push GOTPLT+8(%rip)
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+16(%rip)
|
||||
0x0f, 0x1f, 0x40, 0x00, // nop
|
||||
0x0f, 0x1f, 0x40, 0x00, // nop
|
||||
0x0f, 0x1f, 0x40, 0x00, // nop
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 4;
|
||||
*(u32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 2;
|
||||
|
||||
// Write PLT entries
|
||||
i64 relplt_idx = 0;
|
||||
|
||||
static const u8 data[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : ctx.plt->symbols) {
|
||||
u8 *ent = buf + ctx.plt_hdr_size + sym->get_plt_idx(ctx) * ctx.plt_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 6) = relplt_idx++;
|
||||
*(u32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 16;
|
||||
}
|
||||
}
|
||||
|
||||
// The regular PLT.
|
||||
static void write_plt(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + ctx.plt->shdr.sh_offset;
|
||||
|
||||
// Write PLT header
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0x35, 0, 0, 0, 0, // pushq GOTPLT+8(%rip)
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+16(%rip)
|
||||
0x0f, 0x1f, 0x40, 0x00, // nop
|
||||
};
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 2;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4;
|
||||
|
||||
// Write PLT entries
|
||||
i64 relplt_idx = 0;
|
||||
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
0x68, 0, 0, 0, 0, // push $index_in_relplt
|
||||
0xe9, 0, 0, 0, 0, // jmp PLT[0]
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : ctx.plt->symbols) {
|
||||
u8 *ent = buf + ctx.plt_hdr_size + sym->get_plt_idx(ctx) * ctx.plt_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
*(u32 *)(ent + 7) = relplt_idx++;
|
||||
*(u32 *)(ent + 12) = ctx.plt->shdr.sh_addr - sym->get_plt_addr(ctx) - 16;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
if (ctx.arg.z_now)
|
||||
write_compact_plt(ctx);
|
||||
else if (ctx.arg.z_ibtplt)
|
||||
write_ibtplt(ctx);
|
||||
else
|
||||
write_plt(ctx);
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * X86_64::pltgot_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_NONE:
|
||||
return;
|
||||
case R_X86_64_32:
|
||||
*(u32 *)loc = val;
|
||||
return;
|
||||
case R_X86_64_64:
|
||||
*(u64 *)loc = val;
|
||||
return;
|
||||
case R_X86_64_PC32:
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
case R_X86_64_PC64:
|
||||
*(u64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
static u32 relax_gotpcrelx(u8 *loc) {
|
||||
switch ((loc[0] << 8) | loc[1]) {
|
||||
case 0xff15: return 0x90e8; // call *0(%rip) -> call 0
|
||||
case 0xff25: return 0x90e9; // jmp *0(%rip) -> jmp 0
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_rex_gotpcrelx(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488b05: return 0x488d05; // mov 0(%rip), %rax -> lea 0(%rip), %rax
|
||||
case 0x488b0d: return 0x488d0d; // mov 0(%rip), %rcx -> lea 0(%rip), %rcx
|
||||
case 0x488b15: return 0x488d15; // mov 0(%rip), %rdx -> lea 0(%rip), %rdx
|
||||
case 0x488b1d: return 0x488d1d; // mov 0(%rip), %rbx -> lea 0(%rip), %rbx
|
||||
case 0x488b25: return 0x488d25; // mov 0(%rip), %rsp -> lea 0(%rip), %rsp
|
||||
case 0x488b2d: return 0x488d2d; // mov 0(%rip), %rbp -> lea 0(%rip), %rbp
|
||||
case 0x488b35: return 0x488d35; // mov 0(%rip), %rsi -> lea 0(%rip), %rsi
|
||||
case 0x488b3d: return 0x488d3d; // mov 0(%rip), %rdi -> lea 0(%rip), %rdi
|
||||
case 0x4c8b05: return 0x4c8d05; // mov 0(%rip), %r8 -> lea 0(%rip), %r8
|
||||
case 0x4c8b0d: return 0x4c8d0d; // mov 0(%rip), %r9 -> lea 0(%rip), %r9
|
||||
case 0x4c8b15: return 0x4c8d15; // mov 0(%rip), %r10 -> lea 0(%rip), %r10
|
||||
case 0x4c8b1d: return 0x4c8d1d; // mov 0(%rip), %r11 -> lea 0(%rip), %r11
|
||||
case 0x4c8b25: return 0x4c8d25; // mov 0(%rip), %r12 -> lea 0(%rip), %r12
|
||||
case 0x4c8b2d: return 0x4c8d2d; // mov 0(%rip), %r13 -> lea 0(%rip), %r13
|
||||
case 0x4c8b35: return 0x4c8d35; // mov 0(%rip), %r14 -> lea 0(%rip), %r14
|
||||
case 0x4c8b3d: return 0x4c8d3d; // mov 0(%rip), %r15 -> lea 0(%rip), %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_gottpoff(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488b05: return 0x48c7c0; // mov 0(%rip), %rax -> mov $0, %rax
|
||||
case 0x488b0d: return 0x48c7c1; // mov 0(%rip), %rcx -> mov $0, %rcx
|
||||
case 0x488b15: return 0x48c7c2; // mov 0(%rip), %rdx -> mov $0, %rdx
|
||||
case 0x488b1d: return 0x48c7c3; // mov 0(%rip), %rbx -> mov $0, %rbx
|
||||
case 0x488b25: return 0x48c7c4; // mov 0(%rip), %rsp -> mov $0, %rsp
|
||||
case 0x488b2d: return 0x48c7c5; // mov 0(%rip), %rbp -> mov $0, %rbp
|
||||
case 0x488b35: return 0x48c7c6; // mov 0(%rip), %rsi -> mov $0, %rsi
|
||||
case 0x488b3d: return 0x48c7c7; // mov 0(%rip), %rdi -> mov $0, %rdi
|
||||
case 0x4c8b05: return 0x49c7c0; // mov 0(%rip), %r8 -> mov $0, %r8
|
||||
case 0x4c8b0d: return 0x49c7c1; // mov 0(%rip), %r9 -> mov $0, %r9
|
||||
case 0x4c8b15: return 0x49c7c2; // mov 0(%rip), %r10 -> mov $0, %r10
|
||||
case 0x4c8b1d: return 0x49c7c3; // mov 0(%rip), %r11 -> mov $0, %r11
|
||||
case 0x4c8b25: return 0x49c7c4; // mov 0(%rip), %r12 -> mov $0, %r12
|
||||
case 0x4c8b2d: return 0x49c7c5; // mov 0(%rip), %r13 -> mov $0, %r13
|
||||
case 0x4c8b35: return 0x49c7c6; // mov 0(%rip), %r14 -> mov $0, %r14
|
||||
case 0x4c8b3d: return 0x49c7c7; // mov 0(%rip), %r15 -> mov $0, %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_gotpc32_tlsdesc(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488d05: return 0x48c7c0; // lea 0(%rip), %rax -> mov $0, %rax
|
||||
case 0x488d0d: return 0x48c7c1; // lea 0(%rip), %rcx -> mov $0, %rcx
|
||||
case 0x488d15: return 0x48c7c2; // lea 0(%rip), %rdx -> mov $0, %rdx
|
||||
case 0x488d1d: return 0x48c7c3; // lea 0(%rip), %rbx -> mov $0, %rbx
|
||||
case 0x488d25: return 0x48c7c4; // lea 0(%rip), %rsp -> mov $0, %rsp
|
||||
case 0x488d2d: return 0x48c7c5; // lea 0(%rip), %rbp -> mov $0, %rbp
|
||||
case 0x488d35: return 0x48c7c6; // lea 0(%rip), %rsi -> mov $0, %rsi
|
||||
case 0x488d3d: return 0x48c7c7; // lea 0(%rip), %rdi -> mov $0, %rdi
|
||||
case 0x4c8d05: return 0x49c7c0; // lea 0(%rip), %r8 -> mov $0, %r8
|
||||
case 0x4c8d0d: return 0x49c7c1; // lea 0(%rip), %r9 -> mov $0, %r9
|
||||
case 0x4c8d15: return 0x49c7c2; // lea 0(%rip), %r10 -> mov $0, %r10
|
||||
case 0x4c8d1d: return 0x49c7c3; // lea 0(%rip), %r11 -> mov $0, %r11
|
||||
case 0x4c8d25: return 0x49c7c4; // lea 0(%rip), %r12 -> mov $0, %r12
|
||||
case 0x4c8d2d: return 0x49c7c5; // lea 0(%rip), %r13 -> mov $0, %r13
|
||||
case 0x4c8d35: return 0x49c7c6; // lea 0(%rip), %r14 -> mov $0, %r14
|
||||
case 0x4c8d3d: return 0x49c7c7; // lea 0(%rip), %r15 -> mov $0, %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Apply relocations to SHF_ALLOC sections (i.e. sections that are
|
||||
// mapped to memory at runtime) based on the result of
|
||||
// scan_relocations().
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_X86_64_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
const SectionFragmentRef<E> *frag_ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
frag_ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto write8 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write8s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(u16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write16s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 15), 1 << 15);
|
||||
*(u16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
overflow_check(val, 0, (i64)1 << 32);
|
||||
*(u32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32s = [&](u64 val) {
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
|
||||
*(u32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write64 = [&](u64 val) {
|
||||
*(u64 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr)
|
||||
#define GOT ctx.gotplt->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
write8(S + A);
|
||||
continue;
|
||||
case R_X86_64_16:
|
||||
write16(S + A);
|
||||
continue;
|
||||
case R_X86_64_32:
|
||||
write32(S + A);
|
||||
continue;
|
||||
case R_X86_64_32S:
|
||||
write32s(S + A);
|
||||
continue;
|
||||
case R_X86_64_64:
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
write64(S + A);
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_X86_64_64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
write64(A);
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_X86_64_RELATIVE, 0, (i64)(S + A)};
|
||||
write64(S + A);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_PC8:
|
||||
write8s(S + A - P);
|
||||
continue;
|
||||
case R_X86_64_PC16:
|
||||
write16s(S + A - P);
|
||||
continue;
|
||||
case R_X86_64_PC32:
|
||||
write32s(S + A - P);
|
||||
continue;
|
||||
case R_X86_64_PC64:
|
||||
if (sym.is_absolute() || !sym.is_imported || !ctx.arg.shared) {
|
||||
write64(S + A - P);
|
||||
} else {
|
||||
*dynrel++ = {P, R_X86_64_64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
write64(A);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_PLT32:
|
||||
write32s(S + A - P);
|
||||
continue;
|
||||
case R_X86_64_PLTOFF64:
|
||||
write64(S + A - GOT);
|
||||
break;
|
||||
case R_X86_64_GOT32:
|
||||
write32s(G + A);
|
||||
continue;
|
||||
case R_X86_64_GOT64:
|
||||
write64(G + A);
|
||||
continue;
|
||||
case R_X86_64_GOTOFF64:
|
||||
write64(S + A - GOT);
|
||||
continue;
|
||||
case R_X86_64_GOTPC32:
|
||||
write32s(GOT + A - P);
|
||||
continue;
|
||||
case R_X86_64_GOTPC64:
|
||||
write64(GOT + A - P);
|
||||
continue;
|
||||
case R_X86_64_GOTPCREL:
|
||||
write32s(G + GOT + A - P);
|
||||
continue;
|
||||
case R_X86_64_GOTPCREL64:
|
||||
write64(G + GOT + A - P);
|
||||
continue;
|
||||
case R_X86_64_GOTPCRELX:
|
||||
if (sym.get_got_idx(ctx) == -1) {
|
||||
u32 insn = relax_gotpcrelx(loc - 2);
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S + A - P);
|
||||
} else {
|
||||
write32s(G + GOT + A - P);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
if (sym.get_got_idx(ctx) == -1) {
|
||||
u32 insn = relax_rex_gotpcrelx(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S + A - P);
|
||||
} else {
|
||||
write32s(G + GOT + A - P);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_TLSGD:
|
||||
if (sym.get_tlsgd_idx(ctx) == -1) {
|
||||
// Relax GD to LE
|
||||
i64 val = S - ctx.tls_end + A + 4;
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
|
||||
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 8) = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
|
||||
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 9) = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
} else {
|
||||
write32s(sym.get_tlsgd_addr(ctx) + A - P);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_TLSLD:
|
||||
if (ctx.got->tlsld_idx == -1) {
|
||||
// Relax LD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_X86_64_PLT32: {
|
||||
static const u8 insn[] = {
|
||||
0x66, 0x66, 0x66, // (padding)
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
static const u8 insn[] = {
|
||||
0x66, 0x66, 0x66, // (padding)
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
static const u8 insn[] = {
|
||||
0x66, 0x66, 0x66, // (padding)
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x66, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
} else {
|
||||
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_DTPOFF32:
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
write32s(S + A - ctx.tls_end);
|
||||
else
|
||||
write32s(S + A - ctx.tls_begin);
|
||||
continue;
|
||||
case R_X86_64_DTPOFF64:
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
write64(S + A - ctx.tls_end);
|
||||
else
|
||||
write64(S + A - ctx.tls_begin);
|
||||
continue;
|
||||
case R_X86_64_TPOFF32:
|
||||
write32s(S + A - ctx.tls_end);
|
||||
continue;
|
||||
case R_X86_64_TPOFF64:
|
||||
write64(S + A - ctx.tls_end);
|
||||
continue;
|
||||
case R_X86_64_GOTTPOFF:
|
||||
if (sym.get_gottp_idx(ctx) == -1) {
|
||||
u32 insn = relax_gottpoff(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S + A - ctx.tls_end + 4);
|
||||
} else {
|
||||
write32s(sym.get_gottp_addr(ctx) + A - P);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
u32 insn = relax_gotpc32_tlsdesc(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S + A - ctx.tls_end + 4);
|
||||
} else {
|
||||
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
|
||||
}
|
||||
continue;
|
||||
case R_X86_64_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
continue;
|
||||
case R_X86_64_SIZE64:
|
||||
write64(sym.esym().st_size + A);
|
||||
continue;
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
// This function is responsible for applying relocations against
|
||||
// non-SHF_ALLOC sections (i.e. sections that are not mapped to memory
|
||||
// at runtime).
|
||||
//
|
||||
// Relocations against non-SHF_ALLOC sections are much easier to
|
||||
// handle than that against SHF_ALLOC sections. It is because, since
|
||||
// they are not mapped to memory, they don't contain any variable or
|
||||
// function and never need PLT or GOT. Non-SHF_ALLOC sections are
|
||||
// mostly debug info sections.
|
||||
//
|
||||
// Relocations against non-SHF_ALLOC sections are not scanned by
|
||||
// scan_relocations.
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_X86_64_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 addend;
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto write8 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(u16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
overflow_check(val, 0, (i64)1 << 32);
|
||||
*(u32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32s = [&](u64 val) {
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
|
||||
*(u32 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
write8(S + A);
|
||||
break;
|
||||
case R_X86_64_16:
|
||||
write16(S + A);
|
||||
break;
|
||||
case R_X86_64_32:
|
||||
write32(S + A);
|
||||
break;
|
||||
case R_X86_64_32S:
|
||||
write32s(S + A);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A;
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
write32s(S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_X86_64_DTPOFF64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
break;
|
||||
case R_X86_64_SIZE64:
|
||||
*(u64 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
}
|
||||
}
|
||||
|
||||
// Linker has to create data structures in an output file to apply
|
||||
// some type of relocations. For example, if a relocation refers a GOT
|
||||
// or a PLT entry of a symbol, linker has to create an entry in .got
|
||||
// or in .plt for that symbol. In order to fix the file layout, we
|
||||
// need to scan relocations.
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_X86_64_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
case R_X86_64_16:
|
||||
case R_X86_64_32:
|
||||
case R_X86_64_32S: {
|
||||
// Dynamic linker does not support 8, 16 or 32-bit dynamic
|
||||
// relocations for these types of relocations. We report an
|
||||
// error if we cannot relocate them even at load-time.
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // DSO
|
||||
{ NONE, ERROR, ERROR, ERROR }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_64: {
|
||||
// Unlike the above, we can use R_X86_64_RELATIVE and R_86_64_64
|
||||
// relocations.
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, COPYREL, CPLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC8:
|
||||
case R_X86_64_PC16:
|
||||
case R_X86_64_PC32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC64: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, DYNREL, DYNREL }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOT32:
|
||||
case R_X86_64_GOT64:
|
||||
case R_X86_64_GOTPC32:
|
||||
case R_X86_64_GOTPC64:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCREL64:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPCRELX";
|
||||
|
||||
bool do_relax = ctx.arg.relax && !sym.is_imported &&
|
||||
sym.is_relative() && relax_gotpcrelx(loc - 2);
|
||||
if (!do_relax)
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_REX_GOTPCRELX: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_REX_GOTPCRELX";
|
||||
|
||||
bool do_relax = ctx.arg.relax && !sym.is_imported &&
|
||||
sym.is_relative() && relax_rex_gotpcrelx(loc - 3);
|
||||
if (!do_relax)
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PLTOFF64: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, NONE, PLT, PLT }, // DSO
|
||||
{ NONE, NONE, PLT, PLT }, // PIE
|
||||
{ NONE, NONE, PLT, PLT }, // PDE
|
||||
};
|
||||
dispatch(ctx, table, i, rel, sym);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_TLSGD: {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PLTOFF64 &&
|
||||
ty != R_X86_64_GOTPCREL && ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported)
|
||||
i++;
|
||||
else
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_TLSLD: {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PLTOFF64 &&
|
||||
ty != R_X86_64_GOTPCREL && ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTTPOFF: {
|
||||
ctx.has_gottp_rel = true;
|
||||
|
||||
bool do_relax = ctx.arg.relax && !ctx.arg.shared &&
|
||||
!sym.is_imported && relax_gottpoff(loc - 3);
|
||||
if (!do_relax)
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPC32_TLSDESC: {
|
||||
if (relax_gotpc32_tlsdesc(loc - 3) == 0)
|
||||
Fatal(ctx) << *this << ": GOTPC32_TLSDESC relocation is used"
|
||||
<< " against an invalid code sequence";
|
||||
|
||||
bool do_relax = ctx.relax_tlsdesc && !sym.is_imported;
|
||||
if (!do_relax)
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTOFF64:
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_DTPOFF64:
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_TPOFF64:
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_SIZE64:
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+409
-145
@@ -3,10 +3,11 @@
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <tbb/global_control.h>
|
||||
#include <unistd.h>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
static const char helpmsg[] = R"(
|
||||
Options:
|
||||
--help Report usage information
|
||||
@@ -35,17 +36,22 @@ Options:
|
||||
-h LIBNAME, --soname LIBNAME
|
||||
Set shared library name
|
||||
-l LIBNAME Search for a given library
|
||||
-m EMULATION Ignored
|
||||
-m TARGET Set target
|
||||
-o FILE, --output FILE Set output filename
|
||||
-q, --emit-relocs Leaves relocation sections in the output
|
||||
-r, --relocatable Generate relocatable output
|
||||
-s, --strip-all Strip .symtab section
|
||||
-u SYMBOL, --undefined SYMBOL
|
||||
Force to resolve SYMBOL
|
||||
--Bdynamic Link against shared libraries (default)
|
||||
--Bstatic Do not link against shared libraries
|
||||
--Bdynamic, --dy Link against shared libraries (default)
|
||||
--Bstatic, --dn, --static Do not link against shared libraries
|
||||
--Bsymbolic Bind global symbols locally
|
||||
--Bsymbolic-functions Bind global functions locally
|
||||
--Bno-symbolic Cancel --Bsymbolic and --Bsymbolic-functions
|
||||
--Map FILE Write map file to a given file
|
||||
--Tbss=ADDR Set address to .bss
|
||||
--Tdata Set address to .data
|
||||
--Ttext Set address to .text
|
||||
--allow-multiple-definition Allow multiple definitions
|
||||
--as-needed Only set DT_NEEDED if used
|
||||
--no-as-needed
|
||||
@@ -53,12 +59,19 @@ Options:
|
||||
Generate build ID
|
||||
--no-build-id
|
||||
--chroot DIR Set a given path to root directory
|
||||
--color-diagnostics Ignored
|
||||
--color-diagnostics=[auto,always,never]
|
||||
Use colors in diagnostics
|
||||
--color-diagnostics Alias for --color-diagnostics=always
|
||||
--compress-debug-sections [none,zlib,zlib-gabi,zlib-gnu]
|
||||
Compress .debug_* sections
|
||||
--dc Ignored
|
||||
--dependency-file=FILE Write Makefile-style dependency rules to FILE
|
||||
--defsym=SYMBOL=VALUE Define a symbol alias
|
||||
--demangle Demangle C++ symbols in log messages (default)
|
||||
--no-demangle
|
||||
--disable-new-dtags Ignored
|
||||
--enable-new-dtags Emit DT_RUNPATH for --rpath (default)
|
||||
--disable-new-dtags Emit DT_RPATH for --rpath
|
||||
--dp Ignored
|
||||
--dynamic-list Read a list of dynamic symbols
|
||||
--eh-frame-hdr Create .eh_frame_hdr section
|
||||
--no-eh-frame-hdr
|
||||
@@ -71,21 +84,24 @@ Options:
|
||||
--no-fork
|
||||
--gc-sections Remove unreferenced sections
|
||||
--no-gc-sections
|
||||
--gdb-index Ignored
|
||||
--gdb-index Create .gdb_index for faster gdb startup
|
||||
--hash-style [sysv,gnu,both]
|
||||
Set hash style
|
||||
--icf Fold identical code
|
||||
--icf=[all,none] Fold identical code
|
||||
--no-icf
|
||||
--image-base ADDR Set the base address to a given value
|
||||
--init SYMBOL Call SYMBOl at load-time
|
||||
--no-undefined Report undefined symbols (even with --shared)
|
||||
--noinhibit-exec Create an output file even if errors occur
|
||||
--oformat=binary Omit ELF, section and program headers
|
||||
--pack-dyn-relocs=[relr,none]
|
||||
Pack dynamic relocations
|
||||
--perf Print performance statistics
|
||||
--pie, --pic-executable Create a position independent executable
|
||||
--no-pie, --no-pic-executable
|
||||
--plugin Ignored
|
||||
--plugin-opt Ignored
|
||||
--pop-state Pop state of flags governing input file handling
|
||||
--preload
|
||||
--no-preload
|
||||
--print-gc-sections Print removed unreferenced sections
|
||||
--no-print-gc-sections
|
||||
--print-icf-sections Print folded identical sections
|
||||
@@ -96,18 +112,24 @@ Options:
|
||||
--relax Optimize instructions (default)
|
||||
--no-relax
|
||||
--repro Embed input files to .repro section
|
||||
--require-defined SYMBOL Require SYMBOL be defined in the final output
|
||||
--retain-symbols-file FILE Keep only symbols listed in FILE
|
||||
--reverse-sections Reverses input sections in the output file
|
||||
--rpath DIR Add DIR to runtime search path
|
||||
--rpath-link DIR Ignored
|
||||
--run COMMAND ARG... Run COMMAND with mold as /usr/bin/ld
|
||||
--section-start=SECTION=ADDR Set address to section
|
||||
--shared, --Bshareable Create a share library
|
||||
--shuffle-sections[=SEED] Randomize the output by shuffling input sections
|
||||
--sort-common Ignored
|
||||
--sort-section Ignored
|
||||
--spare-dynamic-tags NUMBER Reserve give number of tags in .dynamic section
|
||||
--static Do not link against shared libraries
|
||||
--start-lib Give following object files in-archive-file semantics
|
||||
--end-lib End the effect of --start-lib
|
||||
--stats Print input statistics
|
||||
--sysroot DIR Set target system root directory
|
||||
--thread-count COUNT Use COUNT number of threads
|
||||
--thread-count COUNT, --threads=COUNT
|
||||
Use COUNT number of threads
|
||||
--threads Use multiple threads (default)
|
||||
--no-threads
|
||||
--trace Print name of each input file
|
||||
@@ -117,6 +139,9 @@ Options:
|
||||
--version-script FILE Read version script
|
||||
--warn-common Warn about common symbols
|
||||
--no-warn-common
|
||||
--warn-once Only warn once for each undefined symbol
|
||||
--warn-shared-textrel Warn if the output .so needs text relocations
|
||||
--warn-textrel Warn if the output file needs text relocations
|
||||
--warn-unresolved-symbols Report unresolved symbols as warnings
|
||||
--error-unresolved-symbols
|
||||
Report unresolved symbols as errors (default)
|
||||
@@ -125,6 +150,7 @@ Options:
|
||||
--wrap SYMBOL Use wrapper function for a given symbol
|
||||
-z defs Report undefined symbols (even with --shared)
|
||||
-z nodefs
|
||||
-z common-page-size=VALUE Ignored
|
||||
-z execstack Require executable stack
|
||||
-z noexecstack
|
||||
-z initfirst Mark DSO to be initialized first at runtime
|
||||
@@ -132,79 +158,26 @@ Options:
|
||||
-z keep-text-section-prefix Keep .text.{hot,unknown,unlikely,startup,exit} as separate sections in the final binary
|
||||
-z nokeep-text-section-prefix
|
||||
-z lazy Enable lazy function resolution (default)
|
||||
-z max-page-size=VALUE Use VALUE as the memory page size
|
||||
-z nocopyreloc Do not create copy relocations
|
||||
-z nodefaultlib Make the dynamic loader to ignore default search paths
|
||||
-z nodelete Mark DSO non-deletable at runtime
|
||||
-z nodlopen Mark DSO not available to dlopen
|
||||
-z nodump Mark DSO not available to dldump
|
||||
-z now Disable lazy function resolution
|
||||
-z origin Mark object requiring immediate $ORIGIN processing at runtime
|
||||
-z separate-loadable-segments
|
||||
Separate all loadable segments to different pages
|
||||
-z separate-code Separate code and data into different pages
|
||||
-z noseparate-code Allow overlap in pages
|
||||
-z relro Make some sections read-only after relocation (default)
|
||||
-z norelro
|
||||
-z text Report error if DT_TEXTREL is set
|
||||
-z notext
|
||||
-z textoff
|
||||
|
||||
mold: supported targets: elf32-i386 elf64-x86-64
|
||||
mold: supported emulations: elf_i386 elf_x86_64)";
|
||||
|
||||
template <typename E>
|
||||
static std::vector<std::string_view>
|
||||
read_response_file(Context<E> &ctx, std::string_view path) {
|
||||
std::vector<std::string_view> vec;
|
||||
MemoryMappedFile<E> *mb =
|
||||
MemoryMappedFile<E>::must_open(ctx, std::string(path));
|
||||
u8 *data = mb->data(ctx);
|
||||
|
||||
auto read_quoted = [&](i64 i, char quote) {
|
||||
std::string buf;
|
||||
while (i < mb->size() && data[i] != quote) {
|
||||
if (data[i] == '\\') {
|
||||
buf.append(1, data[i + 1]);
|
||||
i += 2;
|
||||
} else {
|
||||
buf.append(1, data[i++]);
|
||||
}
|
||||
}
|
||||
if (i >= mb->size())
|
||||
Fatal(ctx) << path << ": premature end of input";
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i + 1;
|
||||
};
|
||||
|
||||
auto read_unquoted = [&](i64 i) {
|
||||
std::string buf;
|
||||
while (i < mb->size() && !isspace(data[i]))
|
||||
buf.append(1, data[i++]);
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i;
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < mb->size();) {
|
||||
if (isspace(data[i]))
|
||||
i++;
|
||||
else if (data[i] == '\'')
|
||||
i = read_quoted(i + 1, '\'');
|
||||
else if (data[i] == '\"')
|
||||
i = read_quoted(i + 1, '\"');
|
||||
else
|
||||
i = read_unquoted(i);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<std::string_view>
|
||||
expand_response_files(Context<E> &ctx, char **argv) {
|
||||
std::vector<std::string_view> vec;
|
||||
|
||||
for (i64 i = 0; argv[i]; i++) {
|
||||
if (argv[i][0] == '@')
|
||||
append(vec, read_response_file(ctx, argv[i] + 1));
|
||||
else
|
||||
vec.push_back(argv[i]);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
mold: supported targets: elf32-i386 elf64-x86-64 elf64-littleaarch64
|
||||
mold: supported emulations: elf_i386 elf_x86_64 aarch64linux aarch64elf)";
|
||||
|
||||
static std::vector<std::string> add_dashes(std::string name) {
|
||||
// Multi-letter linker options can be preceded by either a single
|
||||
@@ -255,6 +228,19 @@ bool read_arg(Context<E> &ctx, std::span<std::string_view> &args,
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool read_eq(Context<E> &ctx, std::span<std::string_view> &args,
|
||||
std::string_view &arg, std::string name) {
|
||||
for (std::string opt : add_dashes(name)) {
|
||||
if (args[0].starts_with(opt + "=")) {
|
||||
arg = args[0].substr(opt.size() + 1);
|
||||
args = args.subspan(1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool read_flag(std::span<std::string_view> &args, std::string name) {
|
||||
for (std::string opt : add_dashes(name)) {
|
||||
if (args[0] == opt) {
|
||||
@@ -280,50 +266,45 @@ static bool read_z_flag(std::span<std::string_view> &args, std::string name) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::string create_response_file(Context<E> &ctx) {
|
||||
std::string buf;
|
||||
std::stringstream out;
|
||||
|
||||
std::string cwd = get_current_dir();
|
||||
out << "-C " << cwd.substr(1) << "\n";
|
||||
|
||||
if (cwd != "/") {
|
||||
out << "--chroot ..";
|
||||
i64 depth = std::count(cwd.begin(), cwd.end(), '/');
|
||||
for (i64 i = 1; i < depth; i++)
|
||||
out << "/..";
|
||||
out << "\n";
|
||||
bool read_z_arg(Context<E> &ctx, std::span<std::string_view> &args,
|
||||
std::string_view &arg, std::string name) {
|
||||
if (args.size() >= 2 && args[0] == "-z" && args[1].starts_with(name + "=")) {
|
||||
arg = args[1].substr(name.size() + 1);
|
||||
args = args.subspan(2);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (i64 i = 1; i < ctx.cmdline_args.size(); i++) {
|
||||
std::string_view arg = ctx.cmdline_args[i];
|
||||
|
||||
if (arg == "-repro" || arg == "--repro") {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
out << arg << "\n";
|
||||
if (!args.empty() && args[0].starts_with("-z" + name + "=")) {
|
||||
arg = args[0].substr(name.size() + 3);
|
||||
args = args.subspan(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return out.str();
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_hex(Context<E> &ctx, std::string opt, std::string_view value) {
|
||||
if (!value.starts_with("0x") && !value.starts_with("0X"))
|
||||
Fatal(ctx) << "option -" << opt << ": not a hexadecimal number";
|
||||
value = value.substr(2);
|
||||
if (value.starts_with("0x") || value.starts_with("0X"))
|
||||
value = value.substr(2);
|
||||
if (value.find_first_not_of("0123456789abcdefABCDEF") != value.npos)
|
||||
Fatal(ctx) << "option -" << opt << ": not a hexadecimal number";
|
||||
return std::stol(std::string(value), nullptr, 16);
|
||||
return std::stoul(std::string(value), nullptr, 16);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_number(Context<E> &ctx, std::string opt,
|
||||
std::string_view value) {
|
||||
size_t nread;
|
||||
i64 ret = std::stol(std::string(value), &nread, 0);
|
||||
|
||||
if (value.starts_with('-')) {
|
||||
i64 ret = std::stoul(std::string(value.substr(1)), &nread, 0);
|
||||
if (value.size() - 1 != nread)
|
||||
Fatal(ctx) << "option -" << opt << ": not a number: " << value;
|
||||
return -ret;
|
||||
}
|
||||
|
||||
i64 ret = std::stoul(std::string(value), &nread, 0);
|
||||
if (value.size() != nread)
|
||||
Fatal(ctx) << "option -" << opt << ": not a number: " << value;
|
||||
return ret;
|
||||
@@ -372,13 +353,6 @@ split_by_comma_or_colon(std::string_view str) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
static i64 get_default_thread_count() {
|
||||
// mold doesn't scale above 32 threads.
|
||||
int n = tbb::global_control::active_value(
|
||||
tbb::global_control::max_allowed_parallelism);
|
||||
return std::min(n, 32);
|
||||
}
|
||||
|
||||
static std::string_view trim(std::string_view str) {
|
||||
size_t pos = str.find_first_not_of(" \t");
|
||||
if (pos == str.npos)
|
||||
@@ -393,9 +367,9 @@ static std::string_view trim(std::string_view str) {
|
||||
|
||||
template <typename E>
|
||||
static void read_retain_symbols_file(Context<E> &ctx, std::string_view path) {
|
||||
MemoryMappedFile<E> *mb =
|
||||
MemoryMappedFile<E>::must_open(ctx, std::string(path));
|
||||
std::string_view data((char *)mb->data(ctx), mb->size());
|
||||
MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::must_open(ctx, std::string(path));
|
||||
std::string_view data((char *)mf->data, mf->size);
|
||||
|
||||
ctx.arg.retain_symbols_file.reset(new std::unordered_set<std::string_view>);
|
||||
|
||||
@@ -423,14 +397,60 @@ static bool is_file(std::string_view path) {
|
||||
(st.st_mode & S_IFMT) != S_IFDIR;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::variant<Symbol<E> *, u64>
|
||||
parse_defsym_value(Context<E> &ctx, std::string_view s) {
|
||||
if (s.starts_with("0x") || s.starts_with("0X")) {
|
||||
size_t nread;
|
||||
u64 addr = std::stoull(std::string(s), &nread, 16);
|
||||
if (s.size() != nread)
|
||||
return {};
|
||||
return addr;
|
||||
}
|
||||
|
||||
if (s.find_first_not_of("0123456789") == s.npos)
|
||||
return (u64)std::stoull(std::string(s), nullptr, 10);
|
||||
return get_symbol(ctx, s);
|
||||
}
|
||||
|
||||
// Returns a PLT header size and a PLT entry size.
|
||||
template <typename E>
|
||||
static std::pair<i64, i64> get_plt_size(Context<E> &ctx) {
|
||||
if constexpr (std::is_same_v<E, X86_64>) {
|
||||
if (ctx.arg.z_now)
|
||||
return {0, 8};
|
||||
if (ctx.arg.z_ibtplt)
|
||||
return {32, 16};
|
||||
return {16, 16};
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<E, I386>)
|
||||
return {16, 16};
|
||||
if constexpr (std::is_same_v<E, ARM64>)
|
||||
return {32, 16};
|
||||
if constexpr (std::is_same_v<E, ARM32>)
|
||||
return {20, 16};
|
||||
if constexpr (std::is_same_v<E, RISCV64>)
|
||||
return {32, 16};
|
||||
unreachable();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_nonpositional_args(Context<E> &ctx,
|
||||
std::vector<std::string_view> &remaining) {
|
||||
std::span<std::string_view> args = ctx.cmdline_args;
|
||||
args = args.subspan(1);
|
||||
|
||||
ctx.arg.thread_count = get_default_thread_count();
|
||||
ctx.arg.color_diagnostics = isatty(STDERR_FILENO);
|
||||
ctx.page_size = E::page_size;
|
||||
|
||||
bool version_shown = false;
|
||||
bool warn_shared_textrel = false;
|
||||
|
||||
// RISC-V object files contains lots of local symbols, so by default
|
||||
// we discard them. This is compatible with GNU ld.
|
||||
if constexpr (std::is_same_v<E, RISCV64>)
|
||||
ctx.arg.discard_locals = true;
|
||||
|
||||
while (!args.empty()) {
|
||||
std::string_view arg;
|
||||
@@ -446,18 +466,34 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
} else if (read_arg(ctx, args, arg, "dynamic-linker") ||
|
||||
read_arg(ctx, args, arg, "I")) {
|
||||
ctx.arg.dynamic_linker = arg;
|
||||
} else if (read_arg(ctx, args, arg, "no-dynamic-linker")) {
|
||||
} else if (read_flag(args, "no-dynamic-linker")) {
|
||||
ctx.arg.dynamic_linker = "";
|
||||
} else if (read_flag(args, "v")) {
|
||||
SyncOut(ctx) << get_version_string();
|
||||
SyncOut(ctx) << mold_version;
|
||||
version_shown = true;
|
||||
} else if (read_flag(args, "version")) {
|
||||
SyncOut(ctx) << get_version_string();
|
||||
SyncOut(ctx) << mold_version;
|
||||
exit(0);
|
||||
} else if (read_flag(args, "V")) {
|
||||
SyncOut(ctx) << get_version_string()
|
||||
SyncOut(ctx) << mold_version
|
||||
<< "\n Supported emulations:\n elf_x86_64\n elf_i386";
|
||||
version_shown = true;
|
||||
} else if (read_arg(ctx, args, arg, "m")) {
|
||||
if (arg == "elf_x86_64") {
|
||||
ctx.arg.emulation = EM_X86_64;
|
||||
} else if (arg == "elf_i386") {
|
||||
ctx.arg.emulation = EM_386;
|
||||
} else if (arg == "aarch64linux") {
|
||||
ctx.arg.emulation = EM_AARCH64;
|
||||
} else if (arg == "armelf_linux_eabi") {
|
||||
ctx.arg.emulation = EM_ARM;
|
||||
} else if (arg == "elf64lriscv") {
|
||||
ctx.arg.emulation = EM_RISCV;
|
||||
} else {
|
||||
Fatal(ctx) << "unknown -m argument: " << arg;
|
||||
}
|
||||
} else if (read_flag(args, "end-lib")) {
|
||||
remaining.push_back("-end-lib");
|
||||
} else if (read_flag(args, "export-dynamic") || read_flag(args, "E")) {
|
||||
ctx.arg.export_dynamic = true;
|
||||
} else if (read_flag(args, "no-export-dynamic")) {
|
||||
@@ -466,30 +502,64 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.Bsymbolic = true;
|
||||
} else if (read_flag(args, "Bsymbolic-functions")) {
|
||||
ctx.arg.Bsymbolic_functions = true;
|
||||
} else if (read_flag(args, "Bno-symbolic")) {
|
||||
ctx.arg.Bsymbolic = false;
|
||||
ctx.arg.Bsymbolic_functions = false;
|
||||
} else if (read_arg(ctx, args, arg, "exclude-libs")) {
|
||||
append(ctx.arg.exclude_libs, split_by_comma_or_colon(arg));
|
||||
} else if (read_flag(args, "q") || read_flag(args, "emit-relocs")) {
|
||||
ctx.arg.emit_relocs = true;
|
||||
} else if (read_arg(ctx, args, arg, "e") ||
|
||||
read_arg(ctx, args, arg, "entry")) {
|
||||
ctx.arg.entry = arg;
|
||||
} else if (read_arg(ctx, args, arg, "Map")) {
|
||||
ctx.arg.Map = arg;
|
||||
ctx.arg.print_map = true;
|
||||
} else if (read_flag(args, "print-dependencies")) {
|
||||
ctx.arg.print_dependencies = 1;
|
||||
} else if (read_flag(args, "print-dependencies=full")) {
|
||||
ctx.arg.print_dependencies = 2;
|
||||
} else if (read_flag(args, "print-map") || read_flag(args, "M")) {
|
||||
ctx.arg.print_map = true;
|
||||
} else if (read_flag(args, "static") || read_flag(args, "Bstatic")) {
|
||||
} else if (read_flag(args, "Bstatic") || read_flag(args, "dn") || read_flag(args, "static")) {
|
||||
ctx.arg.is_static = true;
|
||||
remaining.push_back("-Bstatic");
|
||||
} else if (read_flag(args, "Bdynamic")) {
|
||||
} else if (read_flag(args, "Bdynamic") || read_flag(args, "dy")) {
|
||||
ctx.arg.is_static = false;
|
||||
remaining.push_back("-Bdynamic");
|
||||
} else if (read_flag(args, "shared") || read_flag(args, "Bshareable")) {
|
||||
ctx.arg.shared = true;
|
||||
} else if (read_arg(ctx, args, arg, "spare-dynamic-tags")) {
|
||||
ctx.arg.spare_dynamic_tags = parse_number(ctx, "spare-dynamic-tags", arg);
|
||||
} else if (read_flag(args, "start-lib")) {
|
||||
remaining.push_back("-start-lib");
|
||||
} else if (read_arg(ctx, args, arg, "dependency-file")) {
|
||||
ctx.arg.dependency_file = arg;
|
||||
} else if (read_arg(ctx, args, arg, "defsym")) {
|
||||
size_t pos = arg.find('=');
|
||||
if (pos == arg.npos || pos == arg.size() - 1)
|
||||
Fatal(ctx) << "-defsym: syntax error: " << arg;
|
||||
ctx.arg.defsyms.emplace_back(get_symbol(ctx, arg.substr(0, pos)),
|
||||
parse_defsym_value(ctx, arg.substr(pos + 1)));
|
||||
} else if (read_flag(args, ":lto-pass2")) {
|
||||
ctx.arg.lto_pass2 = true;
|
||||
} else if (read_arg(ctx, args, arg, ":ignore-ir-file")) {
|
||||
ctx.arg.ignore_ir_file.insert(arg);
|
||||
} else if (read_flag(args, "demangle")) {
|
||||
ctx.arg.demangle = true;
|
||||
} else if (read_flag(args, "no-demangle")) {
|
||||
ctx.arg.demangle = false;
|
||||
} else if (read_flag(args, "default-symver")) {
|
||||
ctx.arg.default_symver = true;
|
||||
} else if (read_flag(args, "noinhibit-exec")) {
|
||||
ctx.arg.noinhibit_exec = true;
|
||||
} else if (read_flag(args, "shuffle-sections")) {
|
||||
ctx.arg.shuffle_sections = SHUFFLE_SECTIONS_SHUFFLE;
|
||||
} else if (read_eq(ctx, args, arg, "shuffle-sections")) {
|
||||
ctx.arg.shuffle_sections = SHUFFLE_SECTIONS_SHUFFLE;
|
||||
ctx.arg.shuffle_sections_seed = parse_number(ctx, "shuffle-sections", arg);
|
||||
} else if (read_flag(args, "reverse-sections")) {
|
||||
ctx.arg.shuffle_sections = SHUFFLE_SECTIONS_REVERSE;
|
||||
} else if (read_arg(ctx, args, arg, "y") ||
|
||||
read_arg(ctx, args, arg, "trace-symbol")) {
|
||||
ctx.arg.trace_symbol.push_back(arg);
|
||||
@@ -497,21 +567,26 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.filler = parse_hex(ctx, "filler", arg);
|
||||
} else if (read_arg(ctx, args, arg, "L") ||
|
||||
read_arg(ctx, args, arg, "library-path")) {
|
||||
ctx.arg.library_paths.push_back(arg);
|
||||
ctx.arg.library_paths.push_back(std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "sysroot")) {
|
||||
ctx.arg.sysroot = arg;
|
||||
} else if (read_arg(ctx, args, arg, "unique")) {
|
||||
ctx.arg.unique.reset(new std::regex(glob_to_regex(arg)));
|
||||
std::optional<GlobPattern> pat = GlobPattern::compile(arg);
|
||||
if (!pat)
|
||||
Fatal(ctx) << "-unique: invalid glob pattern: " << arg;
|
||||
ctx.arg.unique = std::move(*pat);
|
||||
} else if (read_arg(ctx, args, arg, "unresolved-symbols")) {
|
||||
if (arg == "report-all" || arg == "ignore-in-shared-libs")
|
||||
ctx.arg.unresolved_symbols = UnresolvedKind::ERROR;
|
||||
ctx.arg.unresolved_symbols = UNRESOLVED_ERROR;
|
||||
else if (arg == "ignore-all" || arg == "ignore-in-object-files")
|
||||
ctx.arg.unresolved_symbols = UnresolvedKind::IGNORE;
|
||||
ctx.arg.unresolved_symbols = UNRESOLVED_IGNORE;
|
||||
else
|
||||
Fatal(ctx) << "unknown --unresolved-symbols argument: " << arg;
|
||||
} else if (read_arg(ctx, args, arg, "u") ||
|
||||
read_arg(ctx, args, arg, "undefined")) {
|
||||
ctx.arg.undefined.push_back(arg);
|
||||
} else if (read_arg(ctx, args, arg, "require-defined")) {
|
||||
ctx.arg.require_defined.push_back(arg);
|
||||
} else if (read_arg(ctx, args, arg, "init")) {
|
||||
ctx.arg.init = arg;
|
||||
} else if (read_arg(ctx, args, arg, "fini")) {
|
||||
@@ -551,10 +626,18 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.relax = true;
|
||||
} else if (read_flag(args, "no-relax")) {
|
||||
ctx.arg.relax = false;
|
||||
} else if (read_flag(args, "gdb-index")) {
|
||||
ctx.arg.gdb_index = true;
|
||||
} else if (read_flag(args, "no-gdb-index")) {
|
||||
ctx.arg.gdb_index = false;
|
||||
} else if (read_flag(args, "r") || read_flag(args, "relocatable")) {
|
||||
ctx.arg.relocatable = true;
|
||||
} else if (read_flag(args, "perf")) {
|
||||
ctx.arg.perf = true;
|
||||
} else if (read_flag(args, "pack-dyn-relocs=relr")) {
|
||||
ctx.arg.pack_dyn_relocs_relr = true;
|
||||
} else if (read_flag(args, "pack-dyn-relocs=none")) {
|
||||
ctx.arg.pack_dyn_relocs_relr = false;
|
||||
} else if (read_flag(args, "stats")) {
|
||||
ctx.arg.stats = true;
|
||||
Counter::enabled = true;
|
||||
@@ -563,10 +646,34 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.directory = arg;
|
||||
} else if (read_arg(ctx, args, arg, "chroot")) {
|
||||
ctx.arg.chroot = arg;
|
||||
} else if (args[0] == "-color-diagnostics=auto" ||
|
||||
args[0] == "--color-diagnostics=auto") {
|
||||
ctx.arg.color_diagnostics = isatty(STDERR_FILENO);
|
||||
args = args.subspan(1);
|
||||
} else if (args[0] == "-color-diagnostics=always" ||
|
||||
args[0] == "--color-diagnostics=always") {
|
||||
ctx.arg.color_diagnostics = true;
|
||||
args = args.subspan(1);
|
||||
} else if (args[0] == "-color-diagnostics=never" ||
|
||||
args[0] == "--color-diagnostics=never") {
|
||||
ctx.arg.color_diagnostics = false;
|
||||
args = args.subspan(1);
|
||||
} else if (read_flag(args, "color-diagnostics")) {
|
||||
ctx.arg.color_diagnostics = true;
|
||||
} else if (read_flag(args, "warn-common")) {
|
||||
ctx.arg.warn_common = true;
|
||||
} else if (read_flag(args, "no-warn-common")) {
|
||||
ctx.arg.warn_common = false;
|
||||
} else if (read_flag(args, "warn-once")) {
|
||||
ctx.arg.warn_once = true;
|
||||
} else if (read_flag(args, "warn-shared-textrel")) {
|
||||
warn_shared_textrel = true;
|
||||
} else if (read_flag(args, "warn-textrel")) {
|
||||
ctx.arg.warn_textrel = true;
|
||||
} else if (read_flag(args, "enable-new-dtags")) {
|
||||
ctx.arg.enable_new_dtags = true;
|
||||
} else if (read_flag(args, "disable-new-dtags")) {
|
||||
ctx.arg.enable_new_dtags = false;
|
||||
} else if (read_arg(ctx, args, arg, "compress-debug-sections")) {
|
||||
if (arg == "zlib" || arg == "zlib-gabi")
|
||||
ctx.arg.compress_debug_sections = COMPRESS_GABI;
|
||||
@@ -583,16 +690,42 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.is_static = true;
|
||||
} else if (read_flag(args, "no-omagic")) {
|
||||
ctx.arg.omagic = false;
|
||||
} else if (read_arg(ctx, args, arg, "oformat")) {
|
||||
if (arg != "binary")
|
||||
Fatal(ctx) << "-oformat: " << arg << " is not supported";
|
||||
ctx.arg.oformat_binary = true;
|
||||
} else if (read_arg(ctx, args, arg, "retain-symbols-file")) {
|
||||
read_retain_symbols_file(ctx, arg);
|
||||
} else if (read_arg(ctx, args, arg, "section-start")) {
|
||||
size_t pos = arg.find('=');
|
||||
if (pos == arg.npos || pos == arg.size() - 1)
|
||||
Fatal(ctx) << "-section-start: syntax error: " << arg;
|
||||
ctx.arg.section_start[arg.substr(0, pos)] =
|
||||
parse_hex(ctx, "section-start", arg.substr(pos + 1));
|
||||
} else if (read_arg(ctx, args, arg, "Tbss")) {
|
||||
ctx.arg.section_start[".bss"] = parse_hex(ctx, "Tbss", arg);
|
||||
} else if (read_arg(ctx, args, arg, "Tdata")) {
|
||||
ctx.arg.section_start[".data"] = parse_hex(ctx, "Tdata", arg);
|
||||
} else if (read_arg(ctx, args, arg, "Ttext")) {
|
||||
ctx.arg.section_start[".text"] = parse_hex(ctx, "Ttext", arg);
|
||||
} else if (read_flag(args, "repro")) {
|
||||
ctx.arg.repro = true;
|
||||
} else if (read_z_flag(args, "now")) {
|
||||
ctx.arg.z_now = true;
|
||||
} else if (read_z_flag(args, "lazy")) {
|
||||
ctx.arg.z_now = false;
|
||||
} else if (read_z_flag(args, "cet-report=none")) {
|
||||
ctx.arg.z_cet_report = CET_REPORT_NONE;
|
||||
} else if (read_z_flag(args, "cet-report=warning")) {
|
||||
ctx.arg.z_cet_report = CET_REPORT_WARNING;
|
||||
} else if (read_z_flag(args, "cet-report=error")) {
|
||||
ctx.arg.z_cet_report = CET_REPORT_ERROR;
|
||||
} else if (read_z_flag(args, "execstack")) {
|
||||
ctx.arg.z_execstack = true;
|
||||
} else if (read_z_arg(ctx, args, arg, "max-page-size")) {
|
||||
ctx.page_size = parse_number(ctx, "-z max-page-size", arg);
|
||||
if (std::popcount<u64>(ctx.page_size) != 1)
|
||||
Fatal(ctx) << "-z max-page-size " << arg << ": value must be a power of 2";
|
||||
} else if (read_z_flag(args, "noexecstack")) {
|
||||
ctx.arg.z_execstack = false;
|
||||
} else if (read_z_flag(args, "relro")) {
|
||||
@@ -615,18 +748,33 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.z_initfirst = true;
|
||||
} else if (read_z_flag(args, "interpose")) {
|
||||
ctx.arg.z_interpose = true;
|
||||
} else if (read_z_flag(args, "ibt")) {
|
||||
ctx.arg.z_ibt = true;
|
||||
ctx.arg.z_ibtplt = true;
|
||||
} else if (read_z_flag(args, "ibtplt")) {
|
||||
ctx.arg.z_ibtplt = true;
|
||||
} else if (read_z_flag(args, "muldefs")) {
|
||||
ctx.arg.allow_multiple_definition = true;
|
||||
} else if (read_z_flag(args, "keep-text-section-prefix")) {
|
||||
ctx.arg.z_keep_text_section_prefix = true;
|
||||
} else if (read_z_flag(args, "nokeep-text-section-prefix")) {
|
||||
ctx.arg.z_keep_text_section_prefix = false;
|
||||
} else if (read_z_flag(args, "shstk")) {
|
||||
ctx.arg.z_shstk = true;
|
||||
} else if (read_z_flag(args, "text")) {
|
||||
ctx.arg.z_text = true;
|
||||
} else if (read_z_flag(args, "notext") || read_z_flag(args, "textoff")) {
|
||||
ctx.arg.z_text = false;
|
||||
} else if (read_z_flag(args, "origin")) {
|
||||
ctx.arg.z_origin = true;
|
||||
} else if (read_z_flag(args, "nodefaultlib")) {
|
||||
ctx.arg.z_nodefaultlib = true;
|
||||
} else if (read_z_flag(args, "separate-loadable-segments")) {
|
||||
ctx.arg.z_separate_code = SEPARATE_LOADABLE_SEGMENTS;
|
||||
} else if (read_z_flag(args, "separate-code")) {
|
||||
ctx.arg.z_separate_code = SEPARATE_CODE;
|
||||
} else if (read_z_flag(args, "noseparate-code")) {
|
||||
ctx.arg.z_separate_code = NOSEPARATE_CODE;
|
||||
} else if (read_flag(args, "no-undefined")) {
|
||||
ctx.arg.z_defs = true;
|
||||
} else if (read_flag(args, "fatal-warnings")) {
|
||||
@@ -656,10 +804,6 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.icf = false;
|
||||
} else if (read_arg(ctx, args, arg, "image-base")) {
|
||||
ctx.arg.image_base = parse_number(ctx, "image-base", arg);
|
||||
} else if (read_flag(args, "quick-exit")) {
|
||||
ctx.arg.quick_exit = true;
|
||||
} else if (read_flag(args, "no-quick-exit")) {
|
||||
ctx.arg.quick_exit = false;
|
||||
} else if (read_flag(args, "print-icf-sections")) {
|
||||
ctx.arg.print_icf_sections = true;
|
||||
} else if (read_flag(args, "no-print-icf-sections")) {
|
||||
@@ -668,12 +812,72 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.quick_exit = true;
|
||||
} else if (read_flag(args, "no-quick-exit")) {
|
||||
ctx.arg.quick_exit = false;
|
||||
} else if (read_arg(ctx, args, arg, "plugin")) {
|
||||
ctx.arg.plugin = arg;
|
||||
} else if (read_arg(ctx, args, arg, "plugin-opt")) {
|
||||
ctx.arg.plugin_opt.push_back(std::string(arg));
|
||||
} else if (read_flag(args, "lto-cs-profile-generate")) {
|
||||
ctx.arg.plugin_opt.push_back("cs-profile-generate");
|
||||
} else if (read_arg(ctx, args, arg, "lto-cs-profile-file")) {
|
||||
ctx.arg.plugin_opt.push_back("cs-profile-path=" + std::string(arg));
|
||||
} else if (read_flag(args, "lto-debug-pass-manager")) {
|
||||
ctx.arg.plugin_opt.push_back("debug-pass-manager");
|
||||
} else if (read_flag(args, "disable-verify")) {
|
||||
ctx.arg.plugin_opt.push_back("disable-verify");
|
||||
} else if (read_flag(args, "lto-emit-asm")) {
|
||||
ctx.arg.plugin_opt.push_back("emit-asm");
|
||||
} else if (read_arg(ctx, args, arg, "thinlto-jobs")) {
|
||||
ctx.arg.plugin_opt.push_back("jobs=" + std::string(arg));
|
||||
} else if (read_flag(args, "no-legacy-pass-manager")) {
|
||||
ctx.arg.plugin_opt.push_back("legacy-pass-manager");
|
||||
} else if (read_arg(ctx, args, arg, "lto-partitions")) {
|
||||
ctx.arg.plugin_opt.push_back("lto-partitions=" + std::string(arg));
|
||||
} else if (read_flag(args, "no-lto-legacy-pass-manager")) {
|
||||
ctx.arg.plugin_opt.push_back("new-pass-manager");
|
||||
} else if (read_arg(ctx, args, arg, "lto-obj-path")) {
|
||||
ctx.arg.plugin_opt.push_back("obj-path=" + std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "opt-remarks-filename")) {
|
||||
ctx.arg.plugin_opt.push_back("opt-remarks-filename=" + std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "opt-remarks-format")) {
|
||||
ctx.arg.plugin_opt.push_back("opt-remarks-format=" + std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "opt-remarks-hotness-threshold")) {
|
||||
ctx.arg.plugin_opt.push_back("opt-remarks-hotness-threshold=" + std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "opt-remarks-passes")) {
|
||||
ctx.arg.plugin_opt.push_back("opt-remarks-passes=" + std::string(arg));
|
||||
} else if (read_flag(args, "opt-remarks-with_hotness")) {
|
||||
ctx.arg.plugin_opt.push_back("opt-remarks-with-hotness");
|
||||
} else if (args[0].starts_with("-lto-O")) {
|
||||
ctx.arg.plugin_opt.push_back("O" + std::string(args[0].substr(6)));
|
||||
args = args.subspan(1);
|
||||
} else if (args[0].starts_with("--lto-O")) {
|
||||
ctx.arg.plugin_opt.push_back("O" + std::string(args[0].substr(7)));
|
||||
args = args.subspan(1);
|
||||
} else if (read_arg(ctx, args, arg, "lto-pseudo-probe-for-profiling")) {
|
||||
ctx.arg.plugin_opt.push_back("pseudo-probe-for-profiling=" + std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "lto-sample-profile")) {
|
||||
ctx.arg.plugin_opt.push_back("sample-profile=" + std::string(arg));
|
||||
} else if (read_flag(args, "save-temps")) {
|
||||
ctx.arg.plugin_opt.push_back("save-temps");
|
||||
} else if (read_flag(args, "thinlto-emit-imports-files")) {
|
||||
ctx.arg.plugin_opt.push_back("thinlto-emit-imports-files");
|
||||
} else if (read_arg(ctx, args, arg, "thinlto-index-only")) {
|
||||
ctx.arg.plugin_opt.push_back("thinlto-index-only=" + std::string(arg));
|
||||
} else if (read_flag(args, "thinlto-index-only")) {
|
||||
ctx.arg.plugin_opt.push_back("thinlto-index-only");
|
||||
} else if (read_arg(ctx, args, arg, "thinlto-object-suffix-replace")) {
|
||||
ctx.arg.plugin_opt.push_back("thinlto-object-suffix-replace=" + std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "thinlto-prefix-replace")) {
|
||||
ctx.arg.plugin_opt.push_back("thinlto-prefix-replace=" + std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "thinlto-jobs")) {
|
||||
ctx.arg.plugin_opt.push_back("jobs=" + std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "thread-count")) {
|
||||
ctx.arg.thread_count = parse_number(ctx, "thread-count", arg);
|
||||
} else if (read_flag(args, "threads")) {
|
||||
ctx.arg.thread_count = get_default_thread_count();
|
||||
ctx.arg.thread_count = 0;
|
||||
} else if (read_flag(args, "no-threads")) {
|
||||
ctx.arg.thread_count = 1;
|
||||
} else if (read_eq(ctx, args, arg, "threads")) {
|
||||
ctx.arg.thread_count = parse_number(ctx, "threads", arg);
|
||||
} else if (read_flag(args, "discard-all") || read_flag(args, "x")) {
|
||||
ctx.arg.discard_all = true;
|
||||
} else if (read_flag(args, "discard-locals") || read_flag(args, "X")) {
|
||||
@@ -683,9 +887,9 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
} else if (read_flag(args, "strip-debug") || read_flag(args, "S")) {
|
||||
ctx.arg.strip_all = true;
|
||||
} else if (read_flag(args, "warn-unresolved-symbols")) {
|
||||
ctx.arg.unresolved_symbols = UnresolvedKind::WARN;
|
||||
ctx.arg.unresolved_symbols = UNRESOLVED_WARN;
|
||||
} else if (read_flag(args, "error-unresolved-symbols")) {
|
||||
ctx.arg.unresolved_symbols = UnresolvedKind::ERROR;
|
||||
ctx.arg.unresolved_symbols = UNRESOLVED_ERROR;
|
||||
} else if (read_arg(ctx, args, arg, "rpath")) {
|
||||
if (!ctx.arg.rpaths.empty())
|
||||
ctx.arg.rpaths += ":";
|
||||
@@ -723,6 +927,14 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
}
|
||||
} else if (read_flag(args, "no-build-id")) {
|
||||
ctx.arg.build_id.kind = BuildId::NONE;
|
||||
} else if (read_arg(ctx, args, arg, "format") ||
|
||||
read_arg(ctx, args, arg, "b")) {
|
||||
if (arg == "binary")
|
||||
Fatal(ctx)
|
||||
<< "mold does not suppor `-b binary`. If you want to convert a binary"
|
||||
<< " file into an object file, use `objcopy -I binary -O default"
|
||||
<< " <input-file> <output-file.o>` instead.";
|
||||
Fatal(ctx) << "unknown command line option: -b " << arg;
|
||||
} else if (read_arg(ctx, args, arg, "auxiliary") ||
|
||||
read_arg(ctx, args, arg, "f")) {
|
||||
ctx.arg.auxiliary.push_back(arg);
|
||||
@@ -730,17 +942,18 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
read_arg(ctx, args, arg, "F")) {
|
||||
ctx.arg.filter.push_back(arg);
|
||||
} else if (read_flag(args, "preload")) {
|
||||
Warn(ctx) << "--preload is deprecated and will be removed in a"
|
||||
<< " future version";
|
||||
ctx.arg.preload = true;
|
||||
} else if (read_flag(args, "no-preload")) {
|
||||
ctx.arg.preload = false;
|
||||
} else if (read_flag(args, "apply-dynamic-relocs")) {
|
||||
} else if (read_arg(ctx, args, arg, "O")) {
|
||||
} else if (read_flag(args, "O0")) {
|
||||
} else if (read_flag(args, "O1")) {
|
||||
} else if (read_flag(args, "O2")) {
|
||||
} else if (read_flag(args, "verbose")) {
|
||||
} else if (read_arg(ctx, args, arg, "plugin")) {
|
||||
} else if (read_arg(ctx, args, arg, "plugin-opt")) {
|
||||
} else if (read_flag(args, "color-diagnostics")) {
|
||||
} else if (read_flag(args, "gdb-index")) {
|
||||
} else if (read_arg(ctx, args, arg, "m")) {
|
||||
} else if (read_flag(args, "eh-frame-hdr")) {
|
||||
} else if (read_flag(args, "start-group")) {
|
||||
} else if (read_flag(args, "end-group")) {
|
||||
@@ -753,16 +966,25 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
} else if (read_flag(args, "allow-shlib-undefined")) {
|
||||
} else if (read_flag(args, "no-allow-shlib-undefined")) {
|
||||
} else if (read_flag(args, "no-add-needed")) {
|
||||
} else if (read_flag(args, "no-call-graph-profile-sort")) {
|
||||
} else if (read_flag(args, "no-copy-dt-needed-entries")) {
|
||||
} else if (read_flag(args, "no-undefined-version")) {
|
||||
} else if (read_arg(ctx, args, arg, "sort-section")) {
|
||||
} else if (read_flag(args, "sort-common")) {
|
||||
} else if (read_flag(args, "dc")) {
|
||||
} else if (read_flag(args, "dp")) {
|
||||
} else if (read_flag(args, "fix-cortex-a53-835769")) {
|
||||
} else if (read_flag(args, "fix-cortex-a53-843419")) {
|
||||
} else if (read_flag(args, "EL")) {
|
||||
} else if (read_flag(args, "warn-once")) {
|
||||
} else if (read_flag(args, "nodefaultlibs")) {
|
||||
} else if (read_flag(args, "warn-constructors")) {
|
||||
} else if (read_flag(args, "warn-execstack")) {
|
||||
} else if (read_flag(args, "no-warn-execstack")) {
|
||||
} else if (read_arg(ctx, args, arg, "rpath-link")) {
|
||||
} else if (read_z_flag(args, "combreloc")) {
|
||||
} else if (read_z_flag(args, "nocombreloc")) {
|
||||
} else if (read_z_arg(ctx, args, arg, "common-page-size")) {
|
||||
} else if (read_arg(ctx, args, arg, "version-script")) {
|
||||
remaining.push_back("--version-script");
|
||||
remaining.push_back(arg);
|
||||
@@ -787,21 +1009,36 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
remaining.push_back("-push-state");
|
||||
} else if (read_flag(args, "pop-state")) {
|
||||
remaining.push_back("-pop-state");
|
||||
} else if (args[0].starts_with("-z") && args[0].size() > 2) {
|
||||
Warn(ctx) << "unknown command line option: " << args[0];
|
||||
args = args.subspan(1);
|
||||
} else if (args[0] == "-z" && args.size() >= 2) {
|
||||
Warn(ctx) << "unknown command line option: -z " << args[1];
|
||||
args = args.subspan(2);
|
||||
} else {
|
||||
if (args[0] == "-z" && args.size() >= 2)
|
||||
Fatal(ctx) << "mold: unknown command line option: -z " << args[1];
|
||||
if (args[0][0] == '-')
|
||||
Fatal(ctx) << "mold: unknown command line option: " << args[0];
|
||||
Fatal(ctx) << "unknown command line option: " << args[0];
|
||||
remaining.push_back(args[0]);
|
||||
args = args.subspan(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.arg.shared) {
|
||||
ctx.arg.pic = true;
|
||||
ctx.arg.dynamic_linker = "";
|
||||
if (!ctx.arg.sysroot.empty()) {
|
||||
for (std::string &path : ctx.arg.library_paths) {
|
||||
if (std::string_view(path).starts_with('='))
|
||||
path = ctx.arg.sysroot + path.substr(1);
|
||||
else if (std::string_view(path).starts_with("$SYSROOT"))
|
||||
path = ctx.arg.sysroot + path.substr(8);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove redundant `/..` or `/.` from library paths.
|
||||
for (std::string &path : ctx.arg.library_paths)
|
||||
path = path_clean(path);
|
||||
|
||||
if (ctx.arg.shared)
|
||||
ctx.arg.pic = true;
|
||||
|
||||
if (ctx.arg.pic)
|
||||
ctx.arg.image_base = 0;
|
||||
|
||||
@@ -820,31 +1057,58 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
Fatal(ctx) << "-auxiliary may not be used without -shared";
|
||||
}
|
||||
|
||||
if (ctx.arg.image_base % ctx.page_size)
|
||||
Fatal(ctx) << "-image-base must be a multiple of -max-page-size";
|
||||
|
||||
if (char *env = getenv("MOLD_REPRO"); env && env[0])
|
||||
ctx.arg.repro = true;
|
||||
|
||||
if (ctx.arg.output.empty())
|
||||
ctx.arg.output = "a.out";
|
||||
|
||||
if (ctx.arg.shared || ctx.arg.export_dynamic)
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.default_version = VER_NDX_LOCAL;
|
||||
|
||||
if (ctx.arg.default_symver) {
|
||||
std::string ver = ctx.arg.soname.empty() ?
|
||||
filepath(ctx.arg.output).filename().string() : std::string(ctx.arg.soname);
|
||||
ctx.arg.version_definitions.push_back(ver);
|
||||
ctx.default_version = VER_NDX_LAST_RESERVED + 1;
|
||||
}
|
||||
|
||||
if (ctx.arg.shared && warn_shared_textrel)
|
||||
ctx.arg.warn_textrel = true;
|
||||
|
||||
std::tie(ctx.plt_hdr_size, ctx.plt_size) = get_plt_size(ctx);
|
||||
|
||||
ctx.arg.undefined.push_back(ctx.arg.entry);
|
||||
|
||||
// TLSDESC relocs must be always relaxed for statically-linked
|
||||
// executables even if -no-relax is given. It is because a
|
||||
// statically-linked executable doesn't contain a tranpoline
|
||||
// function needed for TLSDESC.
|
||||
ctx.relax_tlsdesc = ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared);
|
||||
|
||||
if (version_shown && remaining.empty())
|
||||
exit(0);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template \
|
||||
std::vector<std::string_view> \
|
||||
expand_response_files(Context<E> &ctx, char **argv); \
|
||||
\
|
||||
template \
|
||||
bool read_arg(Context<E> &ctx, std::span<std::string_view> &args, \
|
||||
std::string_view &arg, \
|
||||
std::string name); \
|
||||
\
|
||||
template std::string create_response_file(Context<E> &ctx); \
|
||||
\
|
||||
template \
|
||||
void parse_nonpositional_args(Context<E> &ctx, \
|
||||
std::vector<std::string_view> &remaining)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -7,45 +7,48 @@
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
template <typename E>
|
||||
using Feeder = tbb::parallel_do_feeder<InputSection<E> *>;
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
static bool is_init_fini(const InputSection<E> &isec) {
|
||||
return isec.shdr.sh_type == SHT_INIT_ARRAY ||
|
||||
isec.shdr.sh_type == SHT_FINI_ARRAY ||
|
||||
isec.shdr.sh_type == SHT_PREINIT_ARRAY ||
|
||||
isec.name().starts_with(".ctors") ||
|
||||
isec.name().starts_with(".dtors") ||
|
||||
isec.name().starts_with(".init") ||
|
||||
isec.name().starts_with(".fini");
|
||||
u32 type = isec.shdr().sh_type;
|
||||
std::string_view name = isec.name();
|
||||
|
||||
return type == SHT_INIT_ARRAY ||
|
||||
type == SHT_FINI_ARRAY ||
|
||||
type == SHT_PREINIT_ARRAY ||
|
||||
(std::is_same_v<E, ARM32> && type == SHT_ARM_EXIDX) ||
|
||||
name.starts_with(".ctors") ||
|
||||
name.starts_with(".dtors") ||
|
||||
name.starts_with(".init") ||
|
||||
name.starts_with(".fini");
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool mark_section(InputSection<E> *isec) {
|
||||
return isec && isec->is_alive && !isec->is_visited.exchange(true);
|
||||
return isec && isec->is_alive && !isec->extra().is_visited.exchange(true);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
Feeder<E> &feeder, i64 depth) {
|
||||
assert(isec->is_visited);
|
||||
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
|
||||
assert(isec->extra().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
|
||||
// section fragments as alive.
|
||||
if (isec->rel_fragments)
|
||||
for (i64 i = 0; isec->rel_fragments[i].idx >= 0; i++)
|
||||
isec->rel_fragments[i].frag->is_alive = true;
|
||||
if (SectionFragmentRef<E> *refs = isec->rel_fragments.get())
|
||||
for (i64 i = 0; refs[i].idx >= 0; i++)
|
||||
refs[i].frag->is_alive.store(true, std::memory_order_relaxed);
|
||||
|
||||
// If this is a text section, .eh_frame may contain records
|
||||
// describing how to handle exceptions for that function.
|
||||
// We want to keep associated .eh_frame records.
|
||||
for (FdeRecord<E> &fde : isec->get_fdes())
|
||||
for (ElfRel<E> &rel : fde.get_rels().subspan(1))
|
||||
for (ElfRel<E> &rel : fde.get_rels(isec->file).subspan(1))
|
||||
if (Symbol<E> *sym = isec->file.symbols[rel.r_sym])
|
||||
if (mark_section(sym->input_section))
|
||||
feeder.add(sym->input_section);
|
||||
if (mark_section(sym->get_input_section()))
|
||||
feeder.add(sym->get_input_section());
|
||||
|
||||
for (ElfRel<E> &rel : isec->get_rels(ctx)) {
|
||||
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
|
||||
@@ -53,19 +56,19 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
// Symbol can refer either a section fragment or an input section.
|
||||
// Mark a fragment as alive.
|
||||
if (SectionFragment<E> *frag = sym.get_frag()) {
|
||||
frag->is_alive = true;
|
||||
frag->is_alive.store(true, std::memory_order_relaxed);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mark_section(sym.input_section))
|
||||
if (!mark_section(sym.get_input_section()))
|
||||
continue;
|
||||
|
||||
// Mark a section alive. For better performacne, we don't call
|
||||
// `feeder.add` too often.
|
||||
if (depth < 3)
|
||||
visit(ctx, sym.input_section, feeder, depth + 1);
|
||||
visit(ctx, sym.get_input_section(), feeder, depth + 1);
|
||||
else
|
||||
feeder.add(sym.input_section);
|
||||
feeder.add(sym.get_input_section());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,19 +76,19 @@ template <typename E>
|
||||
static tbb::concurrent_vector<InputSection<E> *>
|
||||
collect_root_set(Context<E> &ctx) {
|
||||
Timer t(ctx, "collect_root_set");
|
||||
tbb::concurrent_vector<InputSection<E> *> roots;
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset;
|
||||
|
||||
auto enqueue_section = [&](InputSection<E> *isec) {
|
||||
if (mark_section(isec))
|
||||
roots.push_back(isec);
|
||||
rootset.push_back(isec);
|
||||
};
|
||||
|
||||
auto enqueue_symbol = [&](Symbol<E> *sym) {
|
||||
if (sym) {
|
||||
if (SectionFragment<E> *frag = sym->get_frag())
|
||||
frag->is_alive = true;
|
||||
frag->is_alive.store(true, std::memory_order_relaxed);
|
||||
else
|
||||
enqueue_section(sym->input_section);
|
||||
enqueue_section(sym->get_input_section());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -99,11 +102,11 @@ 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->is_visited = true;
|
||||
if (!(isec->shdr().sh_flags & SHF_ALLOC))
|
||||
isec->extra().is_visited = true;
|
||||
|
||||
if (is_init_fini(*isec) || is_c_identifier(isec->name()) ||
|
||||
isec->shdr.sh_type == SHT_NOTE)
|
||||
isec->shdr().sh_type == SHT_NOTE)
|
||||
enqueue_section(isec.get());
|
||||
}
|
||||
});
|
||||
@@ -116,10 +119,13 @@ collect_root_set(Context<E> &ctx) {
|
||||
});
|
||||
|
||||
// Add sections referenced by root symbols.
|
||||
enqueue_symbol(Symbol<E>::intern(ctx, ctx.arg.entry));
|
||||
enqueue_symbol(get_symbol(ctx, ctx.arg.entry));
|
||||
|
||||
for (std::string_view name : ctx.arg.undefined)
|
||||
enqueue_symbol(Symbol<E>::intern(ctx, name));
|
||||
enqueue_symbol(get_symbol(ctx, name));
|
||||
|
||||
for (std::string_view name : ctx.arg.require_defined)
|
||||
enqueue_symbol(get_symbol(ctx, name));
|
||||
|
||||
// .eh_frame consists of variable-length records called CIE and FDE
|
||||
// records, and they are a unit of inclusion or exclusion.
|
||||
@@ -130,16 +136,17 @@ collect_root_set(Context<E> &ctx) {
|
||||
enqueue_symbol(file->symbols[rel.r_sym]);
|
||||
});
|
||||
|
||||
return roots;
|
||||
return rootset;
|
||||
}
|
||||
|
||||
// Mark all reachable sections
|
||||
template <typename E>
|
||||
static void mark(Context<E> &ctx,
|
||||
tbb::concurrent_vector<InputSection<E> *> &roots) {
|
||||
tbb::concurrent_vector<InputSection<E> *> &rootset) {
|
||||
Timer t(ctx, "mark");
|
||||
|
||||
tbb::parallel_do(roots, [&](InputSection<E> *isec, Feeder<E> &feeder) {
|
||||
tbb::parallel_for_each(rootset, [&](InputSection<E> *isec,
|
||||
tbb::feeder<InputSection<E> *> &feeder) {
|
||||
visit(ctx, isec, feeder, 0);
|
||||
});
|
||||
}
|
||||
@@ -151,10 +158,8 @@ static void sweep(Context<E> &ctx) {
|
||||
static Counter counter("garbage_sections");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (i64 i = 0; i < file->sections.size(); i++) {
|
||||
std::unique_ptr<InputSection<E>> &isec = file->sections[i];
|
||||
|
||||
if (isec && isec->is_alive && !isec->is_visited) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && !isec->extra().is_visited) {
|
||||
if (ctx.arg.print_gc_sections)
|
||||
SyncOut(ctx) << "removing unused section " << *isec;
|
||||
isec->kill();
|
||||
@@ -171,9 +176,11 @@ static void mark_nonalloc_fragments(Context<E> &ctx) {
|
||||
Timer t(ctx, "mark_nonalloc_fragments");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
for (SectionFragment<E> *frag : file->fragments)
|
||||
if (!(frag->output_section.shdr.sh_flags & SHF_ALLOC))
|
||||
frag->is_alive = true;
|
||||
for (std::unique_ptr<MergeableSection<E>> &m : file->mergeable_sections)
|
||||
if (m)
|
||||
for (SectionFragment<E> *frag : m->fragments)
|
||||
if (!(frag->output_section.shdr.sh_flags & SHF_ALLOC))
|
||||
frag->is_alive.store(true, std::memory_order_relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -181,12 +188,23 @@ 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> *> roots = collect_root_set(ctx);
|
||||
mark(ctx, roots);
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset = collect_root_set(ctx);
|
||||
mark(ctx, rootset);
|
||||
sweep(ctx);
|
||||
}
|
||||
|
||||
template void gc_sections(Context<X86_64> &ctx);
|
||||
template void gc_sections(Context<I386> &ctx);
|
||||
#define INSTANTIATE(E) \
|
||||
template void gc_sections(Context<E> &ctx);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
std::optional<GlobPattern> GlobPattern::compile(std::string_view pat) {
|
||||
std::vector<Element> vec;
|
||||
|
||||
while (!pat.empty()) {
|
||||
u8 c = pat[0];
|
||||
pat = pat.substr(1);
|
||||
|
||||
switch (c) {
|
||||
case '[': {
|
||||
// Here are a few bracket pattern examples:
|
||||
//
|
||||
// [abc]: a, b or c
|
||||
// [$\]!]: $, ] or !
|
||||
// [a-czg-i]: a, b, c, z, g, h, or i
|
||||
// [^a-z]: Any character except lowercase letters
|
||||
vec.push_back({BRACKET});
|
||||
std::vector<bool> &bitmap = vec.back().bitmap;
|
||||
bitmap.resize(256);
|
||||
|
||||
bool negate = false;
|
||||
if (!pat.empty() && pat[0] == '^') {
|
||||
negate = true;
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
|
||||
bool closed = false;
|
||||
|
||||
while (!pat.empty()) {
|
||||
if (pat[0] == ']') {
|
||||
pat = pat.substr(1);
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pat[0] == '\\') {
|
||||
pat = pat.substr(1);
|
||||
if (pat.empty())
|
||||
return {};
|
||||
}
|
||||
|
||||
if (pat.size() >= 3 && pat[1] == '-') {
|
||||
u8 start = pat[0];
|
||||
u8 end = pat[2];
|
||||
pat = pat.substr(3);
|
||||
|
||||
if (end == '\\') {
|
||||
if (pat.empty())
|
||||
return {};
|
||||
end = pat[0];
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
|
||||
if (end < start)
|
||||
return {};
|
||||
|
||||
for (i64 i = start; i <= end; i++)
|
||||
bitmap[i] = true;
|
||||
} else {
|
||||
bitmap[(u8)pat[0]] = true;
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!closed)
|
||||
return {};
|
||||
|
||||
if (negate)
|
||||
for (i64 i = 0; i < 256; i++)
|
||||
bitmap[i] = !bitmap[i];
|
||||
break;
|
||||
}
|
||||
case '?':
|
||||
vec.push_back({QUESTION});
|
||||
break;
|
||||
case '*':
|
||||
vec.push_back({STAR});
|
||||
break;
|
||||
default:
|
||||
if (vec.empty() || vec.back().kind != STRING)
|
||||
vec.push_back({STRING});
|
||||
vec.back().str += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {GlobPattern{std::move(vec)}};
|
||||
}
|
||||
|
||||
bool GlobPattern::match(std::string_view str) {
|
||||
return do_match(str, elements);
|
||||
}
|
||||
|
||||
bool GlobPattern::do_match(std::string_view str, std::span<Element> elements) {
|
||||
while (!elements.empty()) {
|
||||
Element &e = elements[0];
|
||||
elements = elements.subspan(1);
|
||||
|
||||
switch (e.kind) {
|
||||
case STRING:
|
||||
if (str.empty() || !str.starts_with(e.str))
|
||||
return false;
|
||||
str = str.substr(e.str.size());
|
||||
break;
|
||||
case STAR:
|
||||
if (elements.empty())
|
||||
return true;
|
||||
|
||||
// Patterns like "*foo*bar*" should be much more common than more
|
||||
// complex ones like "*foo*[abc]*" or "*foo**?bar*", so we optimize
|
||||
// the former case here.
|
||||
if (elements[0].kind == STRING) {
|
||||
for (;;) {
|
||||
size_t pos = str.find(elements[0].str);
|
||||
if (pos == str.npos)
|
||||
break;
|
||||
if (do_match(str.substr(pos + elements[0].str.size()),
|
||||
elements.subspan(1)))
|
||||
return true;
|
||||
str = str.substr(pos + 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Other cases are handled here.
|
||||
for (i64 j = 0; j < str.size(); j++)
|
||||
if (do_match(str.substr(j), elements))
|
||||
return true;
|
||||
return false;
|
||||
case QUESTION:
|
||||
if (str.empty())
|
||||
return false;
|
||||
str = str.substr(1);
|
||||
break;
|
||||
case BRACKET:
|
||||
if (str.empty() || !e.bitmap[str[0]])
|
||||
return false;
|
||||
str = str.substr(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return str.empty();
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+76
-57
@@ -35,8 +35,10 @@
|
||||
// This problem boils down to one in graph theory. Input to ICF can be
|
||||
// considered as a directed graph in which vertices are sections and edges
|
||||
// are relocations. Vertices have labels (section contents, etc.), and so
|
||||
// are edges (relocation offsets, etc.). Given this formulation, we want to
|
||||
// find as many isomorphic subgraphs as possible.
|
||||
// are edges (relocation offsets, etc.). Two vertices are considered
|
||||
// identical if and only if the (possibly infinite) their unfoldings into
|
||||
// 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.
|
||||
@@ -51,9 +53,9 @@
|
||||
// thread and even better with multiple threads.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../sha.h"
|
||||
|
||||
#include <array>
|
||||
#include <openssl/sha.h>
|
||||
#include <tbb/concurrent_unordered_map.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/enumerable_thread_specific.h>
|
||||
@@ -61,18 +63,20 @@
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <tbb/parallel_sort.h>
|
||||
|
||||
static constexpr i64 HASH_SIZE = 16;
|
||||
static constexpr int64_t HASH_SIZE = 16;
|
||||
|
||||
typedef std::array<u8, HASH_SIZE> Digest;
|
||||
typedef std::array<uint8_t, HASH_SIZE> Digest;
|
||||
|
||||
namespace tbb {
|
||||
template<> struct tbb_hash<Digest> {
|
||||
namespace std {
|
||||
template<> struct hash<Digest> {
|
||||
size_t operator()(const Digest &k) const {
|
||||
return *(i64 *)&k[0];
|
||||
return *(int64_t *)&k[0];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
static void uniquify_cies(Context<E> &ctx) {
|
||||
Timer t(ctx, "uniquify_cies");
|
||||
@@ -95,7 +99,7 @@ static void uniquify_cies(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
static bool is_eligible(InputSection<E> &isec) {
|
||||
const ElfShdr<E> &shdr = isec.shdr;
|
||||
const ElfShdr<E> &shdr = isec.shdr();
|
||||
std::string_view name = isec.name();
|
||||
|
||||
bool is_alloc = (shdr.sh_flags & SHF_ALLOC);
|
||||
@@ -129,7 +133,7 @@ static bool is_leaf(Context<E> &ctx, InputSection<E> &isec) {
|
||||
return false;
|
||||
|
||||
for (FdeRecord<E> &fde : isec.get_fdes())
|
||||
if (fde.get_rels().size() > 1)
|
||||
if (fde.get_rels(isec.file).size() > 1)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@@ -141,10 +145,10 @@ static u64 combine_hash(u64 a, u64 b) {
|
||||
|
||||
template <typename E>
|
||||
struct LeafHasher {
|
||||
size_t operator()(const InputSection<E> *isec) const {
|
||||
size_t operator()(InputSection<E> *isec) const {
|
||||
u64 h = hash_string(isec->contents);
|
||||
for (FdeRecord<E> &fde : isec->get_fdes()) {
|
||||
u64 h2 = hash_string(fde.get_contents().substr(8));
|
||||
u64 h2 = hash_string(fde.get_contents(isec->file).substr(8));
|
||||
h = combine_hash(h, h2);
|
||||
}
|
||||
return h;
|
||||
@@ -153,7 +157,7 @@ struct LeafHasher {
|
||||
|
||||
template <typename E>
|
||||
struct LeafEq {
|
||||
bool operator()(const InputSection<E> *a, const InputSection<E> *b) const {
|
||||
bool operator()(InputSection<E> *a, InputSection<E> *b) const {
|
||||
if (a->contents != b->contents)
|
||||
return false;
|
||||
|
||||
@@ -164,7 +168,8 @@ struct LeafEq {
|
||||
return false;
|
||||
|
||||
for (i64 i = 0; i < x.size(); i++)
|
||||
if (x[i].get_contents().substr(8) != y[i].get_contents().substr(8))
|
||||
if (x[i].get_contents(a->file).substr(8) !=
|
||||
y[i].get_contents(b->file).substr(8))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@@ -193,23 +198,23 @@ static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
|
||||
if (is_leaf(ctx, *isec)) {
|
||||
leaf++;
|
||||
isec->icf_leaf = true;
|
||||
isec->extra().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->icf_eligible = true;
|
||||
isec->extra().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->icf_leaf) {
|
||||
if (isec && isec->is_alive && isec->extra().icf_leaf) {
|
||||
auto it = map.find(isec.get());
|
||||
assert(it != map.end());
|
||||
isec->leader = it->second;
|
||||
isec->extra().leader = it->second;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -220,8 +225,6 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
SHA256_CTX sha;
|
||||
SHA256_Init(&sha);
|
||||
|
||||
u8 *buf = (u8 *)isec.contents.data();
|
||||
|
||||
auto hash = [&](auto val) {
|
||||
SHA256_Update(&sha, &val, sizeof(val));
|
||||
};
|
||||
@@ -232,20 +235,20 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
};
|
||||
|
||||
auto hash_symbol = [&](Symbol<E> &sym) {
|
||||
InputSection<E> *isec = sym.input_section;
|
||||
InputSection<E> *isec = sym.get_input_section();
|
||||
|
||||
if (!sym.file) {
|
||||
hash('1');
|
||||
hash((u64)&sym);
|
||||
} else if (SectionFragment<E> *frag = sym.get_frag()) {
|
||||
hash('2');
|
||||
hash_string(frag->data);
|
||||
hash((u64)frag);
|
||||
} else if (!isec) {
|
||||
hash('3');
|
||||
} else if (isec->leader) {
|
||||
} else if (isec->extra().leader) {
|
||||
hash('4');
|
||||
hash((u64)isec->leader);
|
||||
} else if (isec->icf_eligible) {
|
||||
hash((u64)isec->extra().leader);
|
||||
} else if (isec->extra().icf_eligible) {
|
||||
hash('5');
|
||||
} else {
|
||||
hash('6');
|
||||
@@ -255,24 +258,24 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
};
|
||||
|
||||
hash_string(isec.contents);
|
||||
hash(isec.shdr.sh_flags);
|
||||
hash(isec.shdr().sh_flags);
|
||||
hash(isec.get_fdes().size());
|
||||
hash(isec.get_rels(ctx).size());
|
||||
|
||||
for (FdeRecord<E> &fde : isec.get_fdes()) {
|
||||
hash(fde.cie->icf_idx);
|
||||
hash(isec.file.cies[fde.cie_idx].icf_idx);
|
||||
|
||||
// Bytes 0 to 4 contain the length of this record, and
|
||||
// bytes 4 to 8 contain an offset to CIE.
|
||||
hash_string(fde.get_contents().substr(8));
|
||||
hash_string(fde.get_contents(isec.file).substr(8));
|
||||
|
||||
hash(fde.get_rels().size());
|
||||
hash(fde.get_rels(isec.file).size());
|
||||
|
||||
for (ElfRel<E> &rel : fde.get_rels().subspan(1)) {
|
||||
for (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);
|
||||
hash(fde.cie->input_section.get_addend(rel));
|
||||
hash(isec.file.cies[fde.cie_idx].input_section.get_addend(rel));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +291,7 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
SectionFragmentRef<E> &ref = isec.rel_fragments[frag_idx++];
|
||||
hash('a');
|
||||
isec.get_addend(rel);
|
||||
hash_string(ref.frag->data);
|
||||
hash((u64)ref.frag);
|
||||
} else {
|
||||
hash_symbol(*isec.file.symbols[rel.r_sym]);
|
||||
}
|
||||
@@ -306,7 +309,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->icf_eligible)
|
||||
if (isec && isec->is_alive && isec->extra().icf_eligible)
|
||||
num_sections[i]++;
|
||||
});
|
||||
|
||||
@@ -321,12 +324,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->icf_eligible)
|
||||
if (isec && isec->is_alive && isec->extra().icf_eligible)
|
||||
sections[idx++] = isec.get();
|
||||
});
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
|
||||
sections[i]->icf_idx = i;
|
||||
sections[i]->extra().icf_idx = i;
|
||||
});
|
||||
|
||||
return sections;
|
||||
@@ -351,12 +354,15 @@ static void gather_edges(Context<E> &ctx,
|
||||
std::vector<u32> &edge_indices) {
|
||||
Timer t(ctx, "gather_edges");
|
||||
|
||||
if (sections.empty())
|
||||
return;
|
||||
|
||||
std::vector<i64> num_edges(sections.size());
|
||||
edge_indices.resize(sections.size());
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
|
||||
InputSection<E> &isec = *sections[i];
|
||||
assert(isec.icf_eligible);
|
||||
assert(isec.extra().icf_eligible);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 j = 0; j < isec.get_rels(ctx).size(); j++) {
|
||||
@@ -365,9 +371,10 @@ static void gather_edges(Context<E> &ctx,
|
||||
} else {
|
||||
ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.get_frag() && sym.input_section &&
|
||||
sym.input_section->icf_eligible)
|
||||
num_edges[i]++;
|
||||
if (!sym.get_frag())
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
if (isec->extra().icf_eligible)
|
||||
num_edges[i]++;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -387,9 +394,10 @@ static void gather_edges(Context<E> &ctx,
|
||||
frag_idx++;
|
||||
ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.get_frag() && sym.input_section &&
|
||||
sym.input_section->icf_eligible)
|
||||
edges[idx++] = sym.input_section->icf_idx;
|
||||
if (!sym.get_frag())
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
if (isec->extra().icf_eligible)
|
||||
edges[idx++] = isec->extra().icf_idx;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -416,8 +424,8 @@ static i64 propagate(std::span<std::vector<Digest>> digests,
|
||||
i64 begin = edge_indices[i];
|
||||
i64 end = (i + 1 == num_digests) ? edges.size() : edge_indices[i + 1];
|
||||
|
||||
for (i64 j = begin; j < end; j++)
|
||||
SHA256_Update(&sha, digests[slot][edges[j]].data(), HASH_SIZE);
|
||||
for (i64 j : edges.subspan(begin, end - begin))
|
||||
SHA256_Update(&sha, digests[slot][j].data(), HASH_SIZE);
|
||||
|
||||
digests[!slot][i] = digest_final(sha);
|
||||
|
||||
@@ -450,11 +458,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->leader) {
|
||||
if (isec.get() == isec->leader)
|
||||
if (isec && isec->is_alive && isec->extra().leader) {
|
||||
if (isec.get() == isec->extra().leader)
|
||||
leaders.push_back(isec.get());
|
||||
else
|
||||
map.insert({isec->leader, isec.get()});
|
||||
map.insert({isec->extra().leader, isec.get()});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -488,6 +496,9 @@ 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());
|
||||
|
||||
uniquify_cies(ctx);
|
||||
merge_leaf_nodes(ctx);
|
||||
|
||||
@@ -547,11 +558,11 @@ 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]->leader = it->second;
|
||||
sections[i]->extra().leader = it->second;
|
||||
});
|
||||
|
||||
// Since free'ing the map is slow, postpone it.
|
||||
ctx.on_exit.push_back([=]() { delete map; });
|
||||
ctx.on_exit.push_back([=] { delete map; });
|
||||
}
|
||||
|
||||
if (ctx.arg.print_icf_sections)
|
||||
@@ -562,17 +573,25 @@ void icf_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "reassign");
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file != file)
|
||||
continue;
|
||||
InputSection<E> *isec = sym->input_section;
|
||||
if (isec && isec->leader && isec->leader != isec) {
|
||||
sym->input_section = isec->leader;
|
||||
isec->kill();
|
||||
if (sym->file == file) {
|
||||
InputSection<E> *isec = sym->get_input_section();
|
||||
if (isec && isec->extra().leader && isec->extra().leader != isec) {
|
||||
isec->kill();
|
||||
isec->killed_by_icf = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template void icf_sections(Context<X86_64> &ctx);
|
||||
template void icf_sections(Context<I386> &ctx);
|
||||
#define INSTANTIATE(E) \
|
||||
template void icf_sections(Context<E> &ctx);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+579
-603
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,258 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <limits>
|
||||
#include <zlib.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
bool CieRecord<E>::equals(const CieRecord<E> &other) const {
|
||||
if (get_contents() != other.get_contents())
|
||||
return false;
|
||||
|
||||
std::span<ElfRel<E>> x = get_rels();
|
||||
std::span<ElfRel<E>> y = other.get_rels();
|
||||
if (x.size() != y.size())
|
||||
return false;
|
||||
|
||||
for (i64 i = 0; i < x.size(); i++) {
|
||||
if (x[i].r_offset - input_offset != y[i].r_offset - other.input_offset ||
|
||||
x[i].r_type != y[i].r_type ||
|
||||
file.symbols[x[i].r_sym] != other.file.symbols[y[i].r_sym] ||
|
||||
input_section.get_addend(x[i]) != other.input_section.get_addend(y[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline i64 to_p2align(u64 alignment) {
|
||||
if (alignment == 0)
|
||||
return 0;
|
||||
return std::countr_zero(alignment);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
std::string_view name, i64 shndx)
|
||||
: file(file), shndx(shndx) {
|
||||
if (shndx < file.elf_sections.size())
|
||||
contents = {(char *)file.mf->data + shdr().sh_offset, shdr().sh_size};
|
||||
|
||||
bool compressed;
|
||||
|
||||
if (name.starts_with(".zdebug")) {
|
||||
sh_size = *(ubig64 *)&contents[4];
|
||||
p2align = to_p2align(shdr().sh_addralign);
|
||||
compressed = true;
|
||||
} else if (shdr().sh_flags & SHF_COMPRESSED) {
|
||||
ElfChdr<E> &chdr = *(ElfChdr<E> *)&contents[0];
|
||||
sh_size = chdr.ch_size;
|
||||
p2align = to_p2align(chdr.ch_addralign);
|
||||
compressed = true;
|
||||
} else {
|
||||
sh_size = shdr().sh_size;
|
||||
p2align = to_p2align(shdr().sh_addralign);
|
||||
compressed = false;
|
||||
}
|
||||
|
||||
// Uncompress early if the relocation is REL-type so that we can read
|
||||
// addends from section contents. If RELA-type, we don't need to do this
|
||||
// because addends are in relocations.
|
||||
if (compressed && E::is_rel) {
|
||||
u8 *buf = new u8[sh_size];
|
||||
uncompress(ctx, buf);
|
||||
contents = {(char *)buf, sh_size};
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
}
|
||||
|
||||
output_section =
|
||||
OutputSection<E>::get_instance(ctx, name, shdr().sh_type, shdr().sh_flags);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool InputSection<E>::is_compressed() {
|
||||
return !E::is_rel &&
|
||||
(name().starts_with(".zdebug") || (shdr().sh_flags & SHF_COMPRESSED));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::uncompress(Context<E> &ctx, u8 *buf) {
|
||||
auto do_uncompress = [&](std::string_view data) {
|
||||
unsigned long size = sh_size;
|
||||
if (::uncompress(buf, &size, (u8 *)data.data(), data.size()) != Z_OK)
|
||||
Fatal(ctx) << *this << ": uncompress failed";
|
||||
assert(size == sh_size);
|
||||
};
|
||||
|
||||
if (name().starts_with(".zdebug")) {
|
||||
// Old-style compressed section
|
||||
if (!contents.starts_with("ZLIB") || contents.size() <= 12)
|
||||
Fatal(ctx) << *this << ": corrupted compressed section";
|
||||
do_uncompress(contents.substr(12));
|
||||
return;
|
||||
}
|
||||
|
||||
assert(shdr().sh_flags & SHF_COMPRESSED);
|
||||
|
||||
// New-style compressed section
|
||||
if (contents.size() < sizeof(ElfChdr<E>))
|
||||
Fatal(ctx) << *this << ": corrupted compressed section";
|
||||
|
||||
ElfChdr<E> &hdr = *(ElfChdr<E> *)&contents[0];
|
||||
if (hdr.ch_type != ELFCOMPRESS_ZLIB)
|
||||
Fatal(ctx) << *this << ": unsupported compression type";
|
||||
do_uncompress(contents.substr(sizeof(ElfChdr<E>)));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 get_output_type(Context<E> &ctx) {
|
||||
if (ctx.arg.shared)
|
||||
return 0;
|
||||
if (ctx.arg.pie)
|
||||
return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 get_sym_type(Symbol<E> &sym) {
|
||||
if (sym.is_absolute())
|
||||
return 0;
|
||||
if (!sym.is_imported)
|
||||
return 1;
|
||||
if (sym.get_type() != STT_FUNC)
|
||||
return 2;
|
||||
return 3;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::dispatch(Context<E> &ctx, Action table[3][4], i64 i,
|
||||
const ElfRel<E> &rel, Symbol<E> &sym) {
|
||||
Action action = table[get_output_type(ctx)][get_sym_type(sym)];
|
||||
bool is_writable = (shdr().sh_flags & SHF_WRITE);
|
||||
|
||||
auto error = [&] {
|
||||
std::string msg = sym.is_absolute() ? "-fno-PIC" : "-fPIC";
|
||||
Error(ctx) << *this << ": " << rel << " relocation at offset 0x"
|
||||
<< std::hex << rel.r_offset << " against symbol `"
|
||||
<< sym << "' can not be used; recompile with " << msg;
|
||||
};
|
||||
|
||||
auto warn_textrel = [&] {
|
||||
if (ctx.arg.warn_textrel)
|
||||
Warn(ctx) << *this << ": relocation against symbol `" << sym
|
||||
<< "' in read-only section";
|
||||
};
|
||||
|
||||
switch (action) {
|
||||
case NONE:
|
||||
return;
|
||||
case ERROR:
|
||||
error();
|
||||
return;
|
||||
case COPYREL:
|
||||
if (!ctx.arg.z_copyreloc) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
|
||||
if (sym.esym().st_visibility == STV_PROTECTED) {
|
||||
Error(ctx) << *this
|
||||
<< ": cannot make copy relocation for protected symbol '"
|
||||
<< sym << "', defined in " << *sym.file
|
||||
<< "; recompile with -fPIC";
|
||||
return;
|
||||
}
|
||||
|
||||
sym.flags |= NEEDS_COPYREL;
|
||||
return;
|
||||
case PLT:
|
||||
sym.flags |= NEEDS_PLT;
|
||||
return;
|
||||
case CPLT: {
|
||||
std::scoped_lock lock(sym.mu);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
sym.is_canonical = true;
|
||||
return;
|
||||
}
|
||||
case DYNREL:
|
||||
if (!is_writable) {
|
||||
if (ctx.arg.z_text) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
warn_textrel();
|
||||
ctx.has_textrel = true;
|
||||
}
|
||||
|
||||
assert(sym.is_imported);
|
||||
file.num_dynrel++;
|
||||
return;
|
||||
case BASEREL:
|
||||
if (!is_writable) {
|
||||
if (ctx.arg.z_text) {
|
||||
error();
|
||||
return;
|
||||
}
|
||||
warn_textrel();
|
||||
ctx.has_textrel = true;
|
||||
}
|
||||
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
file.num_dynrel++;
|
||||
return;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
if (shdr().sh_type == SHT_NOBITS || sh_size == 0)
|
||||
return;
|
||||
|
||||
// Copy data
|
||||
if constexpr (std::is_same_v<E, RISCV64>) {
|
||||
copy_contents_riscv(ctx, buf);
|
||||
} else if (is_compressed()) {
|
||||
uncompress(ctx, buf);
|
||||
} else {
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
}
|
||||
|
||||
// Apply relocations
|
||||
if (shdr().sh_flags & SHF_ALLOC)
|
||||
apply_reloc_alloc(ctx, buf);
|
||||
else
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void report_undef(Context<E> &ctx, InputFile<E> &file, Symbol<E> &sym) {
|
||||
if (ctx.arg.warn_once && !ctx.warned.insert({(void *)&sym, 1}))
|
||||
return;
|
||||
|
||||
switch (ctx.arg.unresolved_symbols) {
|
||||
case UNRESOLVED_ERROR:
|
||||
Error(ctx) << "undefined symbol: " << file << ": " << sym;
|
||||
break;
|
||||
case UNRESOLVED_WARN:
|
||||
Warn(ctx) << "undefined symbol: " << file << ": " << sym;
|
||||
break;
|
||||
case UNRESOLVED_IGNORE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template struct CieRecord<E>; \
|
||||
template class InputSection<E>; \
|
||||
template void report_undef(Context<E> &, InputFile<E> &, Symbol<E> &)
|
||||
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -8,8 +8,13 @@
|
||||
#include <cctype>
|
||||
#include <iomanip>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
static thread_local MemoryMappedFile<E> *current_file;
|
||||
static thread_local MappedFile<Context<E>> *current_file;
|
||||
|
||||
template <typename E>
|
||||
void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok);
|
||||
|
||||
static std::string_view get_line(std::string_view input, const char *pos) {
|
||||
assert(input.data() <= pos);
|
||||
@@ -32,7 +37,7 @@ template <typename E>
|
||||
class SyntaxError {
|
||||
public:
|
||||
SyntaxError(Context<E> &ctx, std::string_view errpos) : out(ctx) {
|
||||
std::string_view contents = current_file<E>->get_contents(ctx);
|
||||
std::string_view contents = current_file<E>->get_contents();
|
||||
std::string_view line = get_line(contents, errpos.data());
|
||||
|
||||
i64 lineno = 1;
|
||||
@@ -56,7 +61,7 @@ public:
|
||||
|
||||
[[noreturn]] ~SyntaxError() = default;
|
||||
|
||||
Fatal<E> out;
|
||||
Fatal<Context<E>> out;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -140,27 +145,40 @@ read_output_format(Context<E> &ctx, std::span<std::string_view> tok) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static MemoryMappedFile<E> *resolve_path(Context<E> &ctx, std::string_view tok) {
|
||||
static bool is_in_sysroot(Context<E> &ctx, std::string path) {
|
||||
std::string rel =
|
||||
to_abs_path(path).lexically_relative(to_abs_path(ctx.arg.sysroot));
|
||||
return rel != "." && !rel.starts_with("../");
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static MappedFile<Context<E>> *resolve_path(Context<E> &ctx, std::string_view tok) {
|
||||
std::string str(unquote(tok));
|
||||
|
||||
if (str.starts_with("/"))
|
||||
return MemoryMappedFile<E>::must_open(ctx, ctx.arg.sysroot + str);
|
||||
// GNU ld prepends the sysroot if a pathname starts with '/' and the
|
||||
// script being processed is in the sysroot. We do the same.
|
||||
if (str.starts_with('/') && is_in_sysroot(ctx, current_file<E>->name))
|
||||
return MappedFile<Context<E>>::must_open(ctx, ctx.arg.sysroot + str);
|
||||
|
||||
if (str.starts_with('=')) {
|
||||
std::string path;
|
||||
if (ctx.arg.sysroot.empty())
|
||||
path = str.substr(1);
|
||||
else
|
||||
path = ctx.arg.sysroot + str.substr(1);
|
||||
return MappedFile<Context<E>>::must_open(ctx, path);
|
||||
}
|
||||
|
||||
if (str.starts_with("-l"))
|
||||
return find_library(ctx, str.substr(2));
|
||||
|
||||
if (std::string dir(path_dirname(current_file<E>->name));
|
||||
MemoryMappedFile<E> *mb = MemoryMappedFile<E>::open(ctx, dir + "/" + str))
|
||||
return mb;
|
||||
|
||||
if (MemoryMappedFile<E> *mb = MemoryMappedFile<E>::open(ctx, str))
|
||||
return mb;
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, str))
|
||||
return mf;
|
||||
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string root = dir.starts_with("/") ? ctx.arg.sysroot : "";
|
||||
std::string path = root + std::string(dir) + "/" + str;
|
||||
if (MemoryMappedFile<E> *mb = MemoryMappedFile<E>::open(ctx, path))
|
||||
return mb;
|
||||
std::string path = std::string(dir) + "/" + str;
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, path))
|
||||
return mf;
|
||||
}
|
||||
|
||||
SyntaxError(ctx, tok) << "library not found: " << str;
|
||||
@@ -180,8 +198,8 @@ read_group(Context<E> &ctx, std::span<std::string_view> tok) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MemoryMappedFile<E> *mb = resolve_path(ctx, tok[0]);
|
||||
read_file(ctx, mb);
|
||||
MappedFile<Context<E>> *mf = resolve_path(ctx, tok[0]);
|
||||
read_file(ctx, mf);
|
||||
tok = tok.subspan(1);
|
||||
}
|
||||
|
||||
@@ -191,27 +209,35 @@ read_group(Context<E> &ctx, std::span<std::string_view> tok) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_linker_script(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
current_file<E> = mb;
|
||||
void parse_linker_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mb->get_contents(ctx));
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
|
||||
while (!tok.empty()) {
|
||||
if (tok[0] == "OUTPUT_FORMAT")
|
||||
if (tok[0] == "OUTPUT_FORMAT") {
|
||||
tok = read_output_format(ctx, tok.subspan(1));
|
||||
else if (tok[0] == "INPUT" || tok[0] == "GROUP")
|
||||
} else if (tok[0] == "INPUT" || tok[0] == "GROUP") {
|
||||
tok = read_group(ctx, tok.subspan(1));
|
||||
else
|
||||
SyntaxError(ctx, tok[0]) << "unknown token";
|
||||
} else if (tok[0] == "VERSION") {
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script(ctx, tok);
|
||||
tok = skip(ctx, tok, "}");
|
||||
} else if (tok[0] == ";") {
|
||||
tok = tok.subspan(1);
|
||||
} else {
|
||||
SyntaxError(ctx, tok[0]) << "unknown linker script token";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 get_script_output_type(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
current_file<E> = mb;
|
||||
i64 get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mb->get_contents(ctx));
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
|
||||
if (tok.size() >= 3 && tok[0] == "OUTPUT_FORMAT" && tok[1] == "(") {
|
||||
@@ -238,9 +264,9 @@ static bool read_label(std::span<std::string_view> &tok,
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void parse_version_script_commands(Context<E> &ctx,
|
||||
std::span<std::string_view> &tok,
|
||||
i16 &ver, bool is_extern_cpp) {
|
||||
static void
|
||||
read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
u16 ver_idx, bool is_cpp) {
|
||||
bool is_global = true;
|
||||
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
@@ -257,26 +283,29 @@ static void parse_version_script_commands(Context<E> &ctx,
|
||||
if (tok[0] == "extern") {
|
||||
tok = tok.subspan(1);
|
||||
|
||||
bool is_cpp;
|
||||
if (!tok.empty() && tok[0] == "\"C\"") {
|
||||
tok = tok.subspan(1);
|
||||
is_cpp = false;
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands( ctx, tok, ver_idx, false);
|
||||
} else {
|
||||
tok = skip(ctx, tok, "\"C++\"");
|
||||
is_cpp = true;
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands(ctx, tok, ver_idx, true);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "{");
|
||||
parse_version_script_commands(ctx, tok, ver, is_cpp);
|
||||
tok = skip(ctx, tok, "}");
|
||||
tok = skip(ctx, tok, ";");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tok[0] == "*")
|
||||
ctx.arg.default_version = (is_global ? ver : VER_NDX_LOCAL);
|
||||
else
|
||||
ctx.arg.version_patterns.push_back({tok[0], ver, is_extern_cpp});
|
||||
if (tok[0] == "*") {
|
||||
ctx.default_version = (is_global ? ver_idx : VER_NDX_LOCAL);
|
||||
} else if (is_global) {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), ver_idx, is_cpp});
|
||||
} else {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), VER_NDX_LOCAL, is_cpp});
|
||||
}
|
||||
|
||||
tok = tok.subspan(1);
|
||||
|
||||
if (!tok.empty() && tok[0] == "}")
|
||||
@@ -286,63 +315,79 @@ static void parse_version_script_commands(Context<E> &ctx,
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_version_script(Context<E> &ctx, std::string path) {
|
||||
current_file<E> = MemoryMappedFile<E>::must_open(ctx, path);
|
||||
std::vector<std::string_view> vec =
|
||||
tokenize(ctx, current_file<E>->get_contents(ctx));
|
||||
void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok) {
|
||||
u16 next_ver = VER_NDX_LAST_RESERVED + ctx.arg.version_definitions.size() + 1;
|
||||
|
||||
std::span<std::string_view> tok = vec;
|
||||
i16 next_ver = VER_NDX_LAST_RESERVED + 1;
|
||||
|
||||
while (!tok.empty()) {
|
||||
i16 ver = VER_NDX_GLOBAL;
|
||||
if (tok[0] != "{") {
|
||||
ver = next_ver++;
|
||||
ctx.arg.version_definitions.push_back(tok[0]);
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
u16 ver_idx;
|
||||
if (tok[0] == "{") {
|
||||
ver_idx = VER_NDX_GLOBAL;
|
||||
} else {
|
||||
ver_idx = next_ver++;
|
||||
ctx.arg.version_definitions.push_back(std::string(tok[0]));
|
||||
tok = tok.subspan(1);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "{");
|
||||
parse_version_script_commands(ctx, tok, ver, false);
|
||||
read_version_script_commands(ctx, tok, ver_idx, false);
|
||||
tok = skip(ctx, tok, "}");
|
||||
if (!tok.empty() && tok[0] != ";")
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(ctx, tok, ";");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_version_script(Context<E> &ctx, std::string path) {
|
||||
current_file<E> = MappedFile<Context<E>>::must_open(ctx, path);
|
||||
std::vector<std::string_view> vec =
|
||||
tokenize(ctx, current_file<E>->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
read_version_script(ctx, tok);
|
||||
if (!tok.empty())
|
||||
SyntaxError(ctx, tok[0]) << "trailing garbage token";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_dynamic_list(Context<E> &ctx, std::string path) {
|
||||
current_file<E> = MemoryMappedFile<E>::must_open(ctx, path);
|
||||
std::vector<std::string_view> vec =
|
||||
tokenize(ctx, current_file<E>->get_contents(ctx));
|
||||
|
||||
std::span<std::string_view> tok = vec;
|
||||
|
||||
tok = skip(ctx, tok, "{");
|
||||
i16 ver = VER_NDX_GLOBAL;
|
||||
|
||||
void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
bool is_cpp) {
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
if (read_label(tok, "global")) {
|
||||
ver = VER_NDX_GLOBAL;
|
||||
continue;
|
||||
}
|
||||
if (tok[0] == "extern") {
|
||||
tok = tok.subspan(1);
|
||||
|
||||
if (read_label(tok, "local")) {
|
||||
ver = VER_NDX_LOCAL;
|
||||
if (!tok.empty() && tok[0] == "\"C\"") {
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, false);
|
||||
} else {
|
||||
tok = skip(ctx, tok, "\"C++\"");
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, true);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "}");
|
||||
tok = skip(ctx, tok, ";");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tok[0] == "*")
|
||||
ctx.arg.default_version = ver;
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.arg.version_patterns.push_back({tok[0], ver, false});
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), VER_NDX_GLOBAL, is_cpp});
|
||||
|
||||
tok = skip(ctx, tok.subspan(1), ";");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_dynamic_list(Context<E> &ctx, std::string path) {
|
||||
current_file<E> = MappedFile<Context<E>>::must_open(ctx, path);
|
||||
std::vector<std::string_view> vec =
|
||||
tokenize(ctx, current_file<E>->get_contents());
|
||||
|
||||
std::span<std::string_view> tok = vec;
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, false);
|
||||
tok = skip(ctx, tok, "}");
|
||||
tok = skip(ctx, tok, ";");
|
||||
|
||||
@@ -352,9 +397,9 @@ void parse_dynamic_list(Context<E> &ctx, std::string path) {
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template \
|
||||
void parse_linker_script(Context<E> &ctx, MemoryMappedFile<E> *mb); \
|
||||
void parse_linker_script(Context<E> &ctx, MappedFile<Context<E>> *mf); \
|
||||
template \
|
||||
i64 get_script_output_type(Context<E> &ctx, MemoryMappedFile<E> *mb); \
|
||||
i64 get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf); \
|
||||
template \
|
||||
void parse_version_script(Context<E> &ctx, std::string path); \
|
||||
template \
|
||||
@@ -362,3 +407,8 @@ void parse_dynamic_list(Context<E> &ctx, std::string path) {
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+661
@@ -0,0 +1,661 @@
|
||||
// This file handles the linker plugin to support LTO (Link-Time
|
||||
// Optimization).
|
||||
//
|
||||
// LTO is a technique to do whole-program optimization to a program. Since
|
||||
// a linker sees the whole program as opposed to a single compilation
|
||||
// unit, it in theory can do some optimizations that cannot be done in the
|
||||
// usual separate compilation model. For example, LTO should be able to
|
||||
// inline functions that are defined in other compilation unit.
|
||||
//
|
||||
// In GCC and Clang, all you have to do to enable LTO is adding the
|
||||
// `-flto` flag to the compiler and the linker command lines. If `-flto`
|
||||
// is given, the compiler generates a file that contains not machine code
|
||||
// but the compiler's IR (intermediate representation). In GCC, the output
|
||||
// is an ELF file which wraps GCC's IR. In LLVM, it's not even an ELF file
|
||||
// but just a raw LLVM IR file.
|
||||
//
|
||||
// Here is what we have to do if at least one input file is not a usual
|
||||
// ELF file but an IR object file:
|
||||
//
|
||||
// 1. Read symbols both from usual ELF files and from IR object files and
|
||||
// resolve symbols as usual.
|
||||
//
|
||||
// 2. Pass all IR objects to the compiler backend. The compiler backend
|
||||
// compiles the IRs and returns a few big ELF object files as a
|
||||
// result.
|
||||
//
|
||||
// 3. Parse the returned ELF files and overwrite IR object symbols with
|
||||
// the returned ones, discarding IR object files.
|
||||
//
|
||||
// 4. Continue the rest of the linking process as usual.
|
||||
//
|
||||
// When gcc or clang inovkes ld, they pass `-plugin linker-plugin.so` to
|
||||
// the linker. The given .so file provides a way to call the compiler
|
||||
// backend.
|
||||
//
|
||||
// The linker plugin API is documented at
|
||||
// https://gcc.gnu.org/wiki/whopr/driver, though the document is a bit
|
||||
// outdated.
|
||||
//
|
||||
// Frankly, the linker plugin API is peculiar and is not very easy to use.
|
||||
// For some reason, the API functions don't return the result of a
|
||||
// function call as a return value but instead calls other function with
|
||||
// the result as its argument to "return" the result.
|
||||
//
|
||||
// For example, the first thing you need to do after dlopen()'ing a linker
|
||||
// plugin .so is to call `onload` function with a list of callback
|
||||
// functions. `onload` calls callbacks to notify about the pointers to
|
||||
// other functions the linker plugin provides. I don't know why `onload`
|
||||
// can't just return a list of functions or why the linker plugin can't
|
||||
// define not only `onload` but other functions, but that's how it works.
|
||||
//
|
||||
// Here is the steps to use the linker plugin:
|
||||
//
|
||||
// 1. dlopen() the linker plugin .so and call `onload` to obtain pointers
|
||||
// to other functions provided by the plugin.
|
||||
//
|
||||
// 2. Call `claim_file_hook` with an IR object file to read its symbol
|
||||
// table. `claim_file_hook` calls the `add_symbols` callback to
|
||||
// "return" a list of symbols.
|
||||
//
|
||||
// 3. `claim_file_hook` returns LDPT_OK only when the plugin wants to
|
||||
// handle a given file. Since we pass only IR object files to the
|
||||
// plugin in mold, it always returns LDPT_OK in our case.
|
||||
//
|
||||
// 4. Once we made a decision as to which object file to include into the
|
||||
// output file, we call `all_symbols_read_hook` to compile IR objects
|
||||
// into a few big ELF files. That function calls the `get_symbols`
|
||||
// callback to ask us about the symbol resolution results. (The
|
||||
// compiler backend needs to know whether an undefined symbol in an IR
|
||||
// object was resolved to a regular object file or a shared object to
|
||||
// do whole program optimization, for example.)
|
||||
//
|
||||
// 5. `all_symbols_read_hook` "returns" the result by calling the
|
||||
// `add_input_file` callback. The callback is called with a path to an
|
||||
// LTO'ed ELF file. We parse that ELF file and override symbols
|
||||
// defined by IR objects with the ELF file's ones.
|
||||
//
|
||||
// 6. Lastly, we call `cleanup_hook` to remove temporary files created by
|
||||
// the compiler backend.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../lto.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <dlfcn.h>
|
||||
#include <fcntl.h>
|
||||
#include <sstream>
|
||||
#include <unistd.h>
|
||||
|
||||
#if 0
|
||||
# define LOG std::cerr
|
||||
#else
|
||||
# define LOG std::ostringstream()
|
||||
#endif
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Global variables
|
||||
// We store LTO-related information to global variables,
|
||||
// as the LTO plugin is not thread-safe by design anyway.
|
||||
|
||||
template <typename E> static Context<E> *gctx;
|
||||
template <typename E> static std::vector<ObjectFile<E> *> lto_objects;
|
||||
|
||||
static int phase = 0;
|
||||
static std::vector<PluginSymbol> plugin_symbols;
|
||||
static ClaimFileHandler *claim_file_hook;
|
||||
static AllSymbolsReadHandler *all_symbols_read_hook;
|
||||
static CleanupHandler *cleanup_hook;
|
||||
|
||||
// Event handlers
|
||||
|
||||
static PluginStatus message(int level, const char *fmt, ...) {
|
||||
LOG << "message\n";
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
fprintf(stderr, "mold: ");
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fprintf(stderr, "\n");
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus register_claim_file_hook(ClaimFileHandler fn) {
|
||||
LOG << "register_claim_file_hook\n";
|
||||
claim_file_hook = fn;
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
register_all_symbols_read_hook(AllSymbolsReadHandler fn) {
|
||||
LOG << "register_all_symbols_read_hook\n";
|
||||
all_symbols_read_hook = fn;
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus register_cleanup_hook(CleanupHandler fn) {
|
||||
LOG << "register_cleanup_hook\n";
|
||||
cleanup_hook = fn;
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
add_symbols(void *handle, int nsyms, const PluginSymbol *psyms) {
|
||||
LOG << "add_symbols: " << nsyms << "\n";
|
||||
assert(phase == 1);
|
||||
plugin_symbols = {psyms, psyms + nsyms};
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus add_input_file(const char *path) {
|
||||
LOG << "add_input_file: " << path << "\n";
|
||||
|
||||
Context<E> &ctx = *gctx<E>;
|
||||
static i64 file_priority = 100;
|
||||
|
||||
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::must_open(ctx, path);
|
||||
|
||||
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mf, "", false);
|
||||
ctx.obj_pool.emplace_back(file);
|
||||
lto_objects<E>.push_back(file);
|
||||
|
||||
file->priority = file_priority++;
|
||||
file->is_alive = true;
|
||||
file->parse(ctx);
|
||||
file->resolve_symbols(ctx);
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_file(const void *handle, struct PluginInputFile *file) {
|
||||
LOG << "get_input_file\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus release_input_file(const void *handle) {
|
||||
LOG << "release_input_file\n";
|
||||
|
||||
ObjectFile<E> &file = *(ObjectFile<E> *)handle;
|
||||
if (file.mf->fd != -1) {
|
||||
close(file.mf->fd);
|
||||
file.mf->fd = -1;
|
||||
}
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus add_input_library(const char *path) {
|
||||
LOG << "add_input_library\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus set_extra_library_path(const char *path) {
|
||||
LOG << "set_extra_library_path\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus get_view(const void *handle, const void **view) {
|
||||
LOG << "get_view\n";
|
||||
|
||||
ObjectFile<E> &file = *(ObjectFile<E> *)handle;
|
||||
*view = (void *)file.mf->data;
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_count(const void *handle, int *count) {
|
||||
LOG << "get_input_section_count\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_type(const PluginSection section, int *type) {
|
||||
LOG << "get_input_section_type\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_name(const PluginSection section,
|
||||
char **section_name) {
|
||||
LOG << "get_input_section_name\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_contents(const PluginSection section,
|
||||
const char **section_contents,
|
||||
size_t *len) {
|
||||
LOG << "get_input_section_contents\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
update_section_order(const PluginSection *section_list,
|
||||
int num_sections) {
|
||||
LOG << "update_section_order\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus allow_section_ordering() {
|
||||
LOG << "allow_section_ordering\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_symbols_v1(const void *handle, int nsyms, PluginSymbol *psyms) {
|
||||
unreachable();
|
||||
}
|
||||
|
||||
// get_symbols teaches the LTO plugin as to how we resolved symbols.
|
||||
// The plugin uses the symbol resolution info to optimizes the program.
|
||||
//
|
||||
// For example, if a definition in an IR file is not referenced by
|
||||
// non-IR objects at all, the plugin may choose to completely inline
|
||||
// that definition within the IR objects and remove the symbol from the
|
||||
// LTO result. On the other hand, if a definition is referenced by a
|
||||
// non-IR object, it has to keep the symbol in the LTO result.
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
get_symbols(const void *handle, int nsyms, PluginSymbol *psyms, bool is_v2) {
|
||||
ObjectFile<E> &file = *(ObjectFile<E> *)handle;
|
||||
assert(file.is_lto_obj);
|
||||
|
||||
// If file is an archive member which was not chose to be included in
|
||||
// to the final result, we need to make the plugin to ignore all
|
||||
// symbols.
|
||||
if (!file.is_alive) {
|
||||
assert(!is_v2);
|
||||
for (int i = 0; i < nsyms; i++)
|
||||
psyms[i].resolution = LDPR_PREEMPTED_REG;
|
||||
return LDPS_NO_SYMS;
|
||||
}
|
||||
|
||||
auto get_resolution = [&](ElfSym<E> &esym, Symbol<E> &sym) {
|
||||
if (!sym.file)
|
||||
return LDPR_UNDEF;
|
||||
|
||||
if (sym.file == &file) {
|
||||
if (sym.referenced_by_regular_obj)
|
||||
return LDPR_PREVAILING_DEF;
|
||||
if (sym.is_exported)
|
||||
return is_v2 ? LDPR_PREVAILING_DEF : LDPR_PREVAILING_DEF_IRONLY_EXP;
|
||||
return LDPR_PREVAILING_DEF_IRONLY;
|
||||
}
|
||||
|
||||
if (sym.file->is_dso)
|
||||
return LDPR_RESOLVED_DYN;
|
||||
if (((ObjectFile<E> *)sym.file)->is_lto_obj)
|
||||
return esym.is_undef() ? LDPR_RESOLVED_IR : LDPR_PREEMPTED_IR;
|
||||
return esym.is_undef() ? LDPR_RESOLVED_EXEC : LDPR_PREEMPTED_REG;
|
||||
};
|
||||
|
||||
// Set the symbol resolution results to psyms.
|
||||
for (i64 i = 0; i < nsyms; i++) {
|
||||
ElfSym<E> &esym = file.elf_syms[i + 1];
|
||||
Symbol<E> &sym = *file.symbols[i + 1];
|
||||
psyms[i].resolution = get_resolution(esym, sym);
|
||||
}
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
get_symbols_v2(const void *handle, int nsyms, PluginSymbol *psyms) {
|
||||
LOG << "get_symbols_v2\n";
|
||||
return get_symbols<E>(handle, nsyms, psyms, true);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
get_symbols_v3(const void *handle, int nsyms, PluginSymbol *psyms) {
|
||||
LOG << "get_symbols_v3\n";
|
||||
return get_symbols<E>(handle, nsyms, psyms, false);
|
||||
}
|
||||
|
||||
static PluginStatus allow_unique_segment_for_sections() {
|
||||
LOG << "allow_unique_segment_for_sections\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
unique_segment_for_sections(const char *segment_name,
|
||||
uint64_t flags,
|
||||
uint64_t align,
|
||||
const PluginSection *section_list,
|
||||
int num_sections) {
|
||||
LOG << "unique_segment_for_sections\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_alignment(const PluginSection section,
|
||||
int *addralign) {
|
||||
LOG << "get_input_section_alignment\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_size(const PluginSection section, uint64_t *size) {
|
||||
LOG << "get_input_section_size\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
register_new_input_hook(NewInputHandler fn) {
|
||||
LOG << "register_new_input_hook\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_wrap_symbols(uint64_t *num_symbols, const char ***wrap_symbols) {
|
||||
LOG << "get_wrap_symbols\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void load_plugin(Context<E> &ctx) {
|
||||
assert(phase == 0);
|
||||
phase = 1;
|
||||
gctx<E> = &ctx;
|
||||
|
||||
void *handle = dlopen(ctx.arg.plugin.c_str(), RTLD_NOW | RTLD_GLOBAL);
|
||||
if (!handle)
|
||||
Fatal(ctx) << "could not open plugin file: " << dlerror();
|
||||
|
||||
OnloadFn *onload = (OnloadFn *)dlsym(handle, "onload");
|
||||
if (!onload)
|
||||
Fatal(ctx) << "failed to load plugin " << ctx.arg.plugin << ": "
|
||||
<< dlerror();
|
||||
|
||||
auto save = [&](std::string_view str) {
|
||||
return save_string(ctx, std::string(str).c_str()).data();
|
||||
};
|
||||
|
||||
std::vector<PluginTagValue> tv;
|
||||
tv.emplace_back(LDPT_MESSAGE, message);
|
||||
|
||||
if (ctx.arg.shared)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_DYN);
|
||||
else if (ctx.arg.pie)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_PIE);
|
||||
else
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_EXEC);
|
||||
|
||||
for (std::string_view opt : ctx.arg.plugin_opt)
|
||||
tv.emplace_back(LDPT_OPTION, save(opt));
|
||||
|
||||
tv.emplace_back(LDPT_REGISTER_CLAIM_FILE_HOOK, register_claim_file_hook<E>);
|
||||
tv.emplace_back(LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK,
|
||||
register_all_symbols_read_hook<E>);
|
||||
tv.emplace_back(LDPT_REGISTER_CLEANUP_HOOK, register_cleanup_hook<E>);
|
||||
tv.emplace_back(LDPT_ADD_SYMBOLS, add_symbols);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS, get_symbols_v1);
|
||||
tv.emplace_back(LDPT_ADD_INPUT_FILE, add_input_file<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_FILE, get_input_file);
|
||||
tv.emplace_back(LDPT_RELEASE_INPUT_FILE, release_input_file<E>);
|
||||
tv.emplace_back(LDPT_ADD_INPUT_LIBRARY, add_input_library);
|
||||
tv.emplace_back(LDPT_OUTPUT_NAME, save(ctx.arg.output));
|
||||
tv.emplace_back(LDPT_SET_EXTRA_LIBRARY_PATH, set_extra_library_path);
|
||||
tv.emplace_back(LDPT_GET_VIEW, get_view<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_COUNT, get_input_section_count);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_TYPE, get_input_section_type);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_NAME, get_input_section_name);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_CONTENTS, get_input_section_contents);
|
||||
tv.emplace_back(LDPT_UPDATE_SECTION_ORDER, update_section_order);
|
||||
tv.emplace_back(LDPT_ALLOW_SECTION_ORDERING, allow_section_ordering);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V2, get_symbols_v2<E>);
|
||||
tv.emplace_back(LDPT_ALLOW_UNIQUE_SEGMENT_FOR_SECTIONS,
|
||||
allow_unique_segment_for_sections);
|
||||
tv.emplace_back(LDPT_UNIQUE_SEGMENT_FOR_SECTIONS, unique_segment_for_sections);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V3, get_symbols_v3<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_ALIGNMENT, get_input_section_alignment);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_SIZE, get_input_section_size);
|
||||
tv.emplace_back(LDPT_REGISTER_NEW_INPUT_HOOK, register_new_input_hook<E>);
|
||||
tv.emplace_back(LDPT_GET_WRAP_SYMBOLS, get_wrap_symbols);
|
||||
tv.emplace_back(LDPT_NULL, 0);
|
||||
|
||||
onload(tv.data());
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static ElfSym<E> to_elf_sym(PluginSymbol &psym) {
|
||||
ElfSym<E> esym = {};
|
||||
|
||||
switch (psym.def) {
|
||||
case LDPK_DEF:
|
||||
esym.st_shndx = SHN_ABS;
|
||||
break;
|
||||
case LDPK_WEAKDEF:
|
||||
esym.st_shndx = SHN_ABS;
|
||||
esym.st_bind = STB_WEAK;
|
||||
break;
|
||||
case LDPK_UNDEF:
|
||||
esym.st_shndx = SHN_UNDEF;
|
||||
break;
|
||||
case LDPK_WEAKUNDEF:
|
||||
esym.st_shndx = SHN_UNDEF;
|
||||
esym.st_bind = STB_WEAK;
|
||||
break;
|
||||
case LDPK_COMMON:
|
||||
esym.st_shndx = SHN_COMMON;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (psym.symbol_type) {
|
||||
case LDST_UNKNOWN:
|
||||
break;
|
||||
case LDST_FUNCTION:
|
||||
esym.st_type = STT_FUNC;
|
||||
break;
|
||||
case LDST_VARIABLE:
|
||||
esym.st_type = STT_OBJECT;
|
||||
break;
|
||||
};
|
||||
|
||||
switch (psym.visibility) {
|
||||
case LDPV_DEFAULT:
|
||||
break;
|
||||
case LDPV_PROTECTED:
|
||||
esym.st_visibility = STV_PROTECTED;
|
||||
break;
|
||||
case LDPV_INTERNAL:
|
||||
esym.st_visibility = STV_INTERNAL;
|
||||
break;
|
||||
case LDPV_HIDDEN:
|
||||
esym.st_visibility = STV_HIDDEN;
|
||||
break;
|
||||
}
|
||||
|
||||
esym.st_size = psym.size;
|
||||
return esym;
|
||||
}
|
||||
|
||||
// Returns true if a given linker plugin looks like LLVM's one.
|
||||
// Returns false if it's GCC.
|
||||
template <typename E>
|
||||
static bool is_llvm(Context<E> &ctx) {
|
||||
return ctx.arg.plugin.ends_with("LLVMgold.so");
|
||||
}
|
||||
|
||||
// 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.
|
||||
template <typename E>
|
||||
static bool suppots_v3_api(Context<E> &ctx) {
|
||||
return 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";
|
||||
|
||||
if (ctx.arg.plugin.empty())
|
||||
Fatal(ctx) << mf->name << ": don't know how to handle this LTO object file "
|
||||
<< "because no -plugin option was given. Please make sure you "
|
||||
<< "added -flto not only for creating object files but also for "
|
||||
<< "creating the final executable.";
|
||||
|
||||
// dlopen the linker plugin file
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [&] { load_plugin(ctx); });
|
||||
|
||||
// Create mold's object instance
|
||||
ObjectFile<E> *obj = new ObjectFile<E>;
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
|
||||
obj->filename = mf->name;
|
||||
obj->symbols.push_back(new Symbol<E>);
|
||||
obj->first_global = 1;
|
||||
obj->is_lto_obj = true;
|
||||
obj->mf = mf;
|
||||
|
||||
// Create plugin's object instance
|
||||
PluginInputFile file = {};
|
||||
|
||||
MappedFile<Context<E>> *mf2 = mf->parent ? mf->parent : mf;
|
||||
file.name = save_string(ctx, mf2->name).data();
|
||||
if (mf2->fd == -1)
|
||||
mf2->fd = open(file.name, O_RDONLY);
|
||||
file.fd = mf2->fd;
|
||||
if (file.fd == -1)
|
||||
Fatal(ctx) << "cannot open " << file.name << ": " << errno_string();
|
||||
|
||||
if (mf->parent)
|
||||
obj->archive_name = mf->parent->name;
|
||||
|
||||
file.offset = mf->get_offset();
|
||||
file.filesize = mf->size;
|
||||
file.handle = (void *)obj;
|
||||
|
||||
LOG << "read_lto_symbols: "<< mf->name << "\n";
|
||||
|
||||
// claim_file_hook() calls add_symbols() which initializes `plugin_symbols`
|
||||
int claimed = false;
|
||||
claim_file_hook(&file, &claimed);
|
||||
if (!claimed)
|
||||
Fatal(ctx) << mf->name << ": not claimed by the LTO plugin";
|
||||
|
||||
// It looks like GCC doesn't need fd after claim_file_hook() while
|
||||
// LLVM needs it and takes the ownership of fd. To prevent "too many
|
||||
// open files" issue, we close fd only for GCC. This is ugly, though.
|
||||
if (!is_llvm(ctx)) {
|
||||
close(mf2->fd);
|
||||
mf2->fd = -1;
|
||||
}
|
||||
|
||||
// Initialize object symbols
|
||||
std::vector<ElfSym<E>> *esyms = new std::vector<ElfSym<E>>(1);
|
||||
|
||||
for (PluginSymbol &psym : plugin_symbols) {
|
||||
esyms->push_back(to_elf_sym<E>(psym));
|
||||
obj->symbols.push_back(get_symbol(ctx, save_string(ctx, psym.name)));
|
||||
}
|
||||
|
||||
obj->elf_syms = *esyms;
|
||||
obj->sym_fragments.resize(esyms->size());
|
||||
obj->symvers.resize(esyms->size());
|
||||
plugin_symbols.clear();
|
||||
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) {
|
||||
Timer t(ctx, "do_lto");
|
||||
|
||||
if (!ctx.arg.lto_pass2 && !suppots_v3_api(ctx))
|
||||
restart_process(ctx);
|
||||
|
||||
assert(phase == 1);
|
||||
phase = 2;
|
||||
|
||||
// Set `referenced_by_regular_obj` bit.
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file->is_lto_obj)
|
||||
continue;
|
||||
|
||||
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) {
|
||||
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";
|
||||
if (PluginStatus st = all_symbols_read_hook(); st != LDPS_OK)
|
||||
Fatal(ctx) << "LTO: all_symbols_read_hook returns " << st;
|
||||
|
||||
return lto_objects<E>;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void lto_cleanup(Context<E> &ctx) {
|
||||
Timer t(ctx, "lto_cleanup");
|
||||
|
||||
if (cleanup_hook)
|
||||
cleanup_hook();
|
||||
}
|
||||
|
||||
#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(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+772
@@ -0,0 +1,772 @@
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
#include "../cmdline.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <tbb/global_control.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <unistd.h>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
static ObjectFile<E> *new_object_file(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
if (i64 type = ((ElfEhdr<E> *)mf->data)->e_machine; type != E::e_machine)
|
||||
Fatal(ctx) << mf->name << ": incompatible file type: " << type;
|
||||
|
||||
static Counter count("parsed_objs");
|
||||
count++;
|
||||
|
||||
bool in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
|
||||
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mf, archive_name, in_lib);
|
||||
file->priority = ctx.file_priority++;
|
||||
ctx.tg.run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static ObjectFile<E> *new_lto_obj(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
static Counter count("parsed_lto_objs");
|
||||
count++;
|
||||
|
||||
if (ctx.arg.ignore_ir_file.count(mf->get_identifier()))
|
||||
return new ObjectFile<E>;
|
||||
|
||||
ObjectFile<E> *file = read_lto_object(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
file->is_in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
|
||||
file->is_alive = !file->is_in_lib;
|
||||
ctx.has_lto_object = true;
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static SharedFile<E> *
|
||||
new_shared_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
if (i64 type = ((ElfEhdr<E> *)mf->data)->e_machine; type != E::e_machine)
|
||||
Fatal(ctx) << mf->name << ": incompatible file type: " << type;
|
||||
|
||||
SharedFile<E> *file = SharedFile<E>::create(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
ctx.tg.run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
if (ctx.visited.contains(mf->name))
|
||||
return;
|
||||
|
||||
FileType type = get_file_type(mf);
|
||||
switch (type) {
|
||||
case FileType::ELF_OBJ:
|
||||
ctx.objs.push_back(new_object_file(ctx, mf, ""));
|
||||
return;
|
||||
case FileType::ELF_DSO:
|
||||
ctx.dsos.push_back(new_shared_file(ctx, mf));
|
||||
ctx.visited.insert(mf->name);
|
||||
return;
|
||||
case FileType::AR:
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf)) {
|
||||
switch (get_file_type(child)) {
|
||||
case FileType::ELF_OBJ:
|
||||
ctx.objs.push_back(new_object_file(ctx, child, mf->name));
|
||||
break;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
ctx.objs.push_back(new_lto_obj(ctx, child, mf->name));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
ctx.visited.insert(mf->name);
|
||||
return;
|
||||
case FileType::TEXT:
|
||||
parse_linker_script(ctx, mf);
|
||||
return;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
ctx.objs.push_back(new_lto_obj(ctx, mf, ""));
|
||||
return;
|
||||
default:
|
||||
Fatal(ctx) << mf->name << ": unknown file type: " << type;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the beginning of a given file and returns its machine type
|
||||
// (e.g. EM_X86_64 or EM_386). Return -1 if unknown.
|
||||
template <typename E>
|
||||
static i64 get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
switch (get_file_type(mf)) {
|
||||
case FileType::ELF_OBJ:
|
||||
case FileType::ELF_DSO:
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
return ((ElfEhdr<E> *)mf->data)->e_machine;
|
||||
case FileType::AR:
|
||||
for (MappedFile<Context<E>> *child : read_fat_archive_members(ctx, mf))
|
||||
if (get_file_type(child) == FileType::ELF_OBJ)
|
||||
return ((ElfEhdr<E> *)child->data)->e_machine;
|
||||
return -1;
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_thin_archive_members(ctx, mf))
|
||||
if (get_file_type(child) == FileType::ELF_OBJ)
|
||||
return ((ElfEhdr<E> *)child->data)->e_machine;
|
||||
return -1;
|
||||
case FileType::TEXT:
|
||||
return get_script_output_type(ctx, mf);
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64
|
||||
deduce_machine_type(Context<E> &ctx, std::span<std::string_view> args) {
|
||||
for (std::string_view arg : args)
|
||||
if (!arg.starts_with('-'))
|
||||
if (auto *mf = MappedFile<Context<E>>::open(ctx, std::string(arg)))
|
||||
if (i64 type = get_machine_type(ctx, mf); type != -1)
|
||||
return type;
|
||||
Fatal(ctx) << "-m option is missing";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *open_library(Context<E> &ctx, std::string path) {
|
||||
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, path);
|
||||
if (!mf)
|
||||
return nullptr;
|
||||
|
||||
i64 type = get_machine_type(ctx, mf);
|
||||
if (type == -1 || type == E::e_machine)
|
||||
return mf;
|
||||
Warn(ctx) << path << ": skipping incompatible file " << (int)type
|
||||
<< " " << (int)E::e_machine;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *find_library(Context<E> &ctx, std::string name) {
|
||||
if (name.starts_with(':')) {
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string path = std::string(dir) + "/" + name.substr(1);
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, path))
|
||||
return mf;
|
||||
}
|
||||
Fatal(ctx) << "library not found: " << name;
|
||||
}
|
||||
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string stem = std::string(dir) + "/lib" + name;
|
||||
if (!ctx.is_static)
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, stem + ".so"))
|
||||
return mf;
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, stem + ".a"))
|
||||
return mf;
|
||||
}
|
||||
Fatal(ctx) << "library not found: " << name;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_input_files(Context<E> &ctx, std::span<std::string_view> args) {
|
||||
Timer t(ctx, "read_input_files");
|
||||
|
||||
std::vector<std::tuple<bool, bool, bool, bool>> state;
|
||||
ctx.is_static = ctx.arg.is_static;
|
||||
|
||||
while (!args.empty()) {
|
||||
std::string_view arg;
|
||||
|
||||
if (read_flag(args, "as-needed")) {
|
||||
ctx.as_needed = true;
|
||||
} else if (read_flag(args, "no-as-needed")) {
|
||||
ctx.as_needed = false;
|
||||
} else if (read_flag(args, "whole-archive")) {
|
||||
ctx.whole_archive = true;
|
||||
} else if (read_flag(args, "no-whole-archive")) {
|
||||
ctx.whole_archive = false;
|
||||
} else if (read_flag(args, "Bstatic")) {
|
||||
ctx.is_static = true;
|
||||
} else if (read_flag(args, "Bdynamic")) {
|
||||
ctx.is_static = false;
|
||||
} else if (read_flag(args, "start-lib")) {
|
||||
ctx.in_lib = true;
|
||||
} else if (read_flag(args, "end-lib")) {
|
||||
ctx.in_lib = false;
|
||||
} else if (read_arg(ctx, args, arg, "version-script")) {
|
||||
parse_version_script(ctx, std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "dynamic-list")) {
|
||||
parse_dynamic_list(ctx, std::string(arg));
|
||||
} else if (read_flag(args, "push-state")) {
|
||||
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static,
|
||||
ctx.in_lib});
|
||||
} else if (read_flag(args, "pop-state")) {
|
||||
if (state.empty())
|
||||
Fatal(ctx) << "no state pushed before popping";
|
||||
std::tie(ctx.as_needed, ctx.whole_archive, ctx.is_static, ctx.in_lib) =
|
||||
state.back();
|
||||
state.pop_back();
|
||||
} else if (read_arg(ctx, args, arg, "l")) {
|
||||
MappedFile<Context<E>> *mf = find_library(ctx, std::string(arg));
|
||||
mf->given_fullpath = false;
|
||||
read_file(ctx, mf);
|
||||
} else {
|
||||
read_file(ctx, MappedFile<Context<E>>::must_open(ctx, std::string(args[0])));
|
||||
args = args.subspan(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.objs.empty())
|
||||
Fatal(ctx) << "no input files";
|
||||
|
||||
ctx.tg.wait();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 get_mtime(Context<E> &ctx, std::string path) {
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) < 0)
|
||||
Fatal(ctx) << path << ": stat failed: " << errno_string();
|
||||
return st.st_mtime;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool reload_input_files(Context<E> &ctx) {
|
||||
Timer t(ctx, "reload_input_files");
|
||||
|
||||
std::vector<ObjectFile<E> *> objs;
|
||||
std::vector<SharedFile<E> *> dsos;
|
||||
|
||||
// Reload updated .o files
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file->mf->parent) {
|
||||
if (get_mtime(ctx, file->mf->parent->name) != file->mf->parent->mtime)
|
||||
return false;
|
||||
objs.push_back(file);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (get_mtime(ctx, file->mf->name) == file->mf->mtime) {
|
||||
objs.push_back(file);
|
||||
continue;
|
||||
}
|
||||
|
||||
MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::must_open(ctx, file->mf->name);
|
||||
objs.push_back(new_object_file(ctx, mf, file->mf->name));
|
||||
}
|
||||
|
||||
// Reload updated .so files
|
||||
for (SharedFile<E> *file : ctx.dsos) {
|
||||
if (get_mtime(ctx, file->mf->name) == file->mf->mtime) {
|
||||
dsos.push_back(file);
|
||||
} else {
|
||||
MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::must_open(ctx, file->mf->name);
|
||||
dsos.push_back(new_shared_file(ctx, mf));
|
||||
}
|
||||
}
|
||||
|
||||
ctx.objs = objs;
|
||||
ctx.dsos = dsos;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void show_stats(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *obj : ctx.objs) {
|
||||
static Counter defined("defined_syms");
|
||||
defined += obj->first_global - 1;
|
||||
|
||||
static Counter undefined("undefined_syms");
|
||||
undefined += obj->symbols.size() - obj->first_global;
|
||||
|
||||
for (std::unique_ptr<InputSection<E>> &sec : obj->sections) {
|
||||
if (!sec || !sec->is_alive)
|
||||
continue;
|
||||
|
||||
static Counter alloc("reloc_alloc");
|
||||
static Counter nonalloc("reloc_nonalloc");
|
||||
|
||||
if (sec->shdr().sh_flags & SHF_ALLOC)
|
||||
alloc += sec->get_rels(ctx).size();
|
||||
else
|
||||
nonalloc += sec->get_rels(ctx).size();
|
||||
}
|
||||
|
||||
static Counter comdats("comdats");
|
||||
comdats += obj->comdat_groups.size();
|
||||
|
||||
static Counter removed_comdats("removed_comdat_mem");
|
||||
for (auto &pair : obj->comdat_groups)
|
||||
if (ComdatGroup *group = pair.first; group->owner != obj->priority)
|
||||
removed_comdats += pair.second.size();
|
||||
|
||||
static Counter num_cies("num_cies");
|
||||
num_cies += obj->cies.size();
|
||||
|
||||
static Counter num_unique_cies("num_unique_cies");
|
||||
for (CieRecord<E> &cie : obj->cies)
|
||||
if (cie.is_leader)
|
||||
num_unique_cies++;
|
||||
|
||||
static Counter num_fdes("num_fdes");
|
||||
num_fdes += obj->fdes.size();
|
||||
}
|
||||
|
||||
static Counter num_bytes("total_input_bytes");
|
||||
for (std::unique_ptr<MappedFile<Context<E>>> &mf : ctx.mf_pool)
|
||||
num_bytes += mf->size;
|
||||
|
||||
static Counter num_input_sections("input_sections");
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
num_input_sections += file->sections.size();
|
||||
|
||||
static Counter num_output_chunks("output_chunks", ctx.chunks.size());
|
||||
static Counter num_objs("num_objs", ctx.objs.size());
|
||||
static Counter num_dsos("num_dsos", ctx.dsos.size());
|
||||
|
||||
if constexpr (std::is_same_v<E, ARM64>) {
|
||||
static Counter num_thunks("num_thunks");
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
for (std::unique_ptr<RangeExtensionThunk<E>> &thunk : osec->thunks)
|
||||
num_thunks += thunk->symbols.size();
|
||||
}
|
||||
|
||||
Counter::print();
|
||||
|
||||
for (std::unique_ptr<MergedSection<E>> &sec : ctx.merged_sections)
|
||||
sec->print_stats(ctx);
|
||||
}
|
||||
|
||||
static i64 get_default_thread_count() {
|
||||
// mold doesn't scale well above 32 threads.
|
||||
int n = tbb::global_control::active_value(
|
||||
tbb::global_control::max_allowed_parallelism);
|
||||
return std::min(n, 32);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static int elf_main(int argc, char **argv) {
|
||||
Context<E> ctx;
|
||||
|
||||
// Process -run option first. process_run_subcommand() does not return.
|
||||
if (argc >= 2)
|
||||
if (argv[1] == "-run"sv || argv[1] == "--run"sv)
|
||||
process_run_subcommand(ctx, argc, argv);
|
||||
|
||||
// Parse non-positional command line options
|
||||
ctx.cmdline_args = expand_response_files(ctx, argv);
|
||||
std::vector<std::string_view> file_args;
|
||||
parse_nonpositional_args(ctx, file_args);
|
||||
|
||||
// If no -m option is given, deduce it from input files.
|
||||
if (ctx.arg.emulation == -1)
|
||||
ctx.arg.emulation = deduce_machine_type(ctx, file_args);
|
||||
|
||||
// Redo if -m is not x86-64.
|
||||
if (ctx.arg.emulation != E::e_machine) {
|
||||
switch (ctx.arg.emulation) {
|
||||
case EM_386:
|
||||
return elf_main<I386>(argc, argv);
|
||||
case EM_AARCH64:
|
||||
return elf_main<ARM64>(argc, argv);
|
||||
case EM_ARM:
|
||||
return elf_main<ARM32>(argc, argv);
|
||||
case EM_RISCV:
|
||||
return elf_main<RISCV64>(argc, argv);
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
Timer t_all(ctx, "all");
|
||||
|
||||
if (ctx.arg.relocatable) {
|
||||
combine_objects(ctx, file_args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ctx.arg.preload)
|
||||
try_resume_daemon(ctx);
|
||||
|
||||
i64 thread_count = ctx.arg.thread_count;
|
||||
if (thread_count == 0)
|
||||
thread_count = get_default_thread_count();
|
||||
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
|
||||
thread_count);
|
||||
|
||||
install_signal_handler();
|
||||
|
||||
if (!ctx.arg.directory.empty())
|
||||
if (chdir(ctx.arg.directory.c_str()) == -1)
|
||||
Fatal(ctx) << "chdir failed: " << ctx.arg.directory
|
||||
<< ": " << errno_string();
|
||||
|
||||
// Handle --wrap options if any.
|
||||
for (std::string_view name : ctx.arg.wrap)
|
||||
get_symbol(ctx, name)->wrap = true;
|
||||
|
||||
// Handle --retain-symbols-file options if any.
|
||||
if (ctx.arg.retain_symbols_file)
|
||||
for (std::string_view name : *ctx.arg.retain_symbols_file)
|
||||
get_symbol(ctx, name)->write_to_symtab = true;
|
||||
|
||||
// Preload input files
|
||||
std::function<void()> on_complete;
|
||||
std::function<void()> wait_for_client;
|
||||
|
||||
if (ctx.arg.preload)
|
||||
daemonize(ctx, &wait_for_client, &on_complete);
|
||||
else if (ctx.arg.fork)
|
||||
on_complete = fork_child();
|
||||
|
||||
for (std::string_view arg : ctx.arg.trace_symbol)
|
||||
get_symbol(ctx, arg)->traced = true;
|
||||
|
||||
// Parse input files
|
||||
read_input_files(ctx, file_args);
|
||||
|
||||
if (ctx.arg.preload) {
|
||||
wait_for_client();
|
||||
if (!reload_input_files(ctx)) {
|
||||
std::vector<char *> args(argv, argv + argc);
|
||||
args.push_back((char *)"--no-preload");
|
||||
args.push_back(nullptr);
|
||||
return elf_main<E>(argc + 1, args.data());
|
||||
}
|
||||
}
|
||||
|
||||
// Uniquify shared object files by soname
|
||||
{
|
||||
std::unordered_set<std::string_view> seen;
|
||||
std::erase_if(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
return !seen.insert(file->soname).second;
|
||||
});
|
||||
}
|
||||
|
||||
Timer t_total(ctx, "total");
|
||||
Timer t_before_copy(ctx, "before_copy");
|
||||
|
||||
// Apply -exclude-libs
|
||||
apply_exclude_libs(ctx);
|
||||
|
||||
// Resolve symbols and fix the set of object files that are
|
||||
// included to the final output.
|
||||
resolve_symbols(ctx);
|
||||
|
||||
// Resolve mergeable section pieces to merge them.
|
||||
register_section_pieces(ctx);
|
||||
|
||||
// Remove redundant comdat sections (e.g. duplicate inline functions).
|
||||
eliminate_comdats(ctx);
|
||||
|
||||
// Create .bss sections for common symbols.
|
||||
convert_common_symbols(ctx);
|
||||
|
||||
// Apply version scripts.
|
||||
apply_version_script(ctx);
|
||||
|
||||
// Parse symbol version suffixes (e.g. "foo@ver1").
|
||||
parse_symbol_version(ctx);
|
||||
|
||||
// Set is_import and is_export bits for each symbol.
|
||||
compute_import_export(ctx);
|
||||
|
||||
// Garbage-collect unreachable sections.
|
||||
if (ctx.arg.gc_sections)
|
||||
gc_sections(ctx);
|
||||
|
||||
// Merge identical read-only sections.
|
||||
if (ctx.arg.icf)
|
||||
icf_sections(ctx);
|
||||
|
||||
// Compute sizes of sections containing mergeable strings.
|
||||
compute_merged_section_sizes(ctx);
|
||||
|
||||
// Create instances of linker-synthesized sections such as
|
||||
// .got or .plt.
|
||||
create_synthetic_sections(ctx);
|
||||
|
||||
// Bin input sections into output sections.
|
||||
bin_sections(ctx);
|
||||
|
||||
// Get a list of output sections.
|
||||
append(ctx.chunks, collect_output_sections(ctx));
|
||||
|
||||
// Create a dummy file containing linker-synthesized symbols
|
||||
// (e.g. `__bss_start`).
|
||||
ctx.internal_obj = create_internal_file(ctx);
|
||||
ctx.objs.push_back(ctx.internal_obj);
|
||||
|
||||
// Beyond this point, no new files will be added to ctx.objs
|
||||
// or ctx.dsos.
|
||||
|
||||
// Handle `-z cet-report`.
|
||||
if (ctx.arg.z_cet_report != CET_REPORT_NONE)
|
||||
check_cet_errors(ctx);
|
||||
|
||||
// If we are linking a .so file, remaining undefined symbols does
|
||||
// not cause a linker error. Instead, they are treated as if they
|
||||
// were imported symbols.
|
||||
//
|
||||
// If we are linking an executable, weak undefs are converted to
|
||||
// weakly imported symbol so that they'll have another chance to be
|
||||
// resolved.
|
||||
claim_unresolved_symbols(ctx);
|
||||
|
||||
// Beyond this point, no new symbols will be added to the result.
|
||||
|
||||
// Handle -repro
|
||||
if (ctx.arg.repro)
|
||||
write_repro_file(ctx);
|
||||
|
||||
// Make sure that all symbols have been resolved.
|
||||
if (!ctx.arg.allow_multiple_definition)
|
||||
check_duplicate_symbols(ctx);
|
||||
|
||||
// Handle --require-defined
|
||||
for (std::string_view name : ctx.arg.require_defined)
|
||||
if (!get_symbol(ctx, name)->file)
|
||||
Error(ctx) << "--require-defined: undefined symbol: " << name;
|
||||
|
||||
// .init_array and .fini_array contents have to be sorted by
|
||||
// a special rule. Sort them.
|
||||
sort_init_fini(ctx);
|
||||
|
||||
// Likewise, .ctors and .dtors have to be sorted. They are rare
|
||||
// because they are superceded by .init_array/.fini_array, though.
|
||||
sort_ctor_dtor(ctx);
|
||||
|
||||
// Handle --shuffle-sections
|
||||
if (ctx.arg.shuffle_sections != SHUFFLE_SECTIONS_NONE)
|
||||
shuffle_sections(ctx);
|
||||
|
||||
// Compute sizes of output sections while assigning offsets
|
||||
// within an output section to input sections.
|
||||
compute_section_sizes(ctx);
|
||||
|
||||
// Sort sections by section attributes so that we'll have to
|
||||
// create as few segments as possible.
|
||||
sort(ctx.chunks, [&](Chunk<E> *a, Chunk<E> *b) {
|
||||
return get_section_rank(ctx, a) < get_section_rank(ctx, b);
|
||||
});
|
||||
|
||||
// Copy string referred by .dynamic to .dynstr.
|
||||
for (SharedFile<E> *file : ctx.dsos)
|
||||
ctx.dynstr->add_string(file->soname);
|
||||
for (std::string_view str : ctx.arg.auxiliary)
|
||||
ctx.dynstr->add_string(str);
|
||||
for (std::string_view str : ctx.arg.filter)
|
||||
ctx.dynstr->add_string(str);
|
||||
if (!ctx.arg.rpaths.empty())
|
||||
ctx.dynstr->add_string(ctx.arg.rpaths);
|
||||
if (!ctx.arg.soname.empty())
|
||||
ctx.dynstr->add_string(ctx.arg.soname);
|
||||
|
||||
// Scan relocations to find symbols that need entries in .got, .plt,
|
||||
// .got.plt, .dynsym, .dynstr, etc.
|
||||
scan_rels(ctx);
|
||||
|
||||
// If --packed_dyn_relocs=relr was given, base relocations are stored
|
||||
// to a .relr.dyn section in a compressed form. Construct a compressed
|
||||
// relocations now so that we can fix section sizes and file layout.
|
||||
if (ctx.arg.pack_dyn_relocs_relr)
|
||||
construct_relr(ctx);
|
||||
|
||||
// Reserve a space for dynamic symbol strings in .dynstr and sort
|
||||
// .dynsym contents if necessary. Beyond this point, no symbol will
|
||||
// be added to .dynsym.
|
||||
ctx.dynsym->finalize(ctx);
|
||||
|
||||
// Fill .gnu.version_d section contents.
|
||||
if (ctx.verdef)
|
||||
ctx.verdef->construct(ctx);
|
||||
|
||||
// Fill .gnu.version_r section contents.
|
||||
ctx.verneed->construct(ctx);
|
||||
|
||||
// Compute .symtab and .strtab sizes for each file.
|
||||
create_output_symtab(ctx);
|
||||
|
||||
// .eh_frame is a special section from the linker's point of view,
|
||||
// as its contents are parsed and reconstructed by the linker,
|
||||
// unlike other sections that are regarded as opaque bytes.
|
||||
// Here, we construct output .eh_frame contents.
|
||||
ctx.eh_frame->construct(ctx);
|
||||
|
||||
// Handle --gdb-index.
|
||||
if (ctx.arg.gdb_index)
|
||||
ctx.gdb_index->construct(ctx);
|
||||
|
||||
// If --emit-relocs is given, we'll copy relocation sections from input
|
||||
// files to an output file.
|
||||
if (ctx.arg.emit_relocs)
|
||||
create_reloc_sections(ctx);
|
||||
|
||||
// Update sh_size for each chunk and remove empty ones.
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
std::erase_if(ctx.chunks, [](Chunk<E> *chunk) {
|
||||
return !chunk->is_output_section() && chunk->shdr.sh_size == 0;
|
||||
});
|
||||
|
||||
// Set section indices.
|
||||
for (i64 i = 0, shndx = 1; i < ctx.chunks.size(); i++)
|
||||
if (!ctx.chunks[i]->is_header())
|
||||
ctx.chunks[i]->shndx = shndx++;
|
||||
|
||||
// Some types of section header refer other section by index.
|
||||
// Recompute the section header to fill such fields with correct values.
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
// Assign offsets to output sections
|
||||
i64 filesize = set_osec_offsets(ctx);
|
||||
|
||||
// On ARM64, we may need to create so-called "range extension thunks"
|
||||
// to extend branch instructions reach, as they can jump only to
|
||||
// ±128 MiB.
|
||||
if constexpr (std::is_same_v<E, ARM64>)
|
||||
filesize = create_range_extension_thunks(ctx);
|
||||
|
||||
// On RISC-V, branches are encode using multiple instructions so
|
||||
// that they can jump to anywhere in ±2 GiB by default. They may
|
||||
// be replaced with shorter instruction sequences if destinations
|
||||
// are close enough. Do this optimization.
|
||||
if constexpr (std::is_same_v<E, RISCV64>)
|
||||
filesize = riscv_resize_sections(ctx);
|
||||
|
||||
// Fix linker-synthesized symbol addresses.
|
||||
fix_synthetic_symbols(ctx);
|
||||
|
||||
// Beyond this, you can assume that symbol addresses including their
|
||||
// GOT or PLT addresses have a correct final value.
|
||||
|
||||
// If --compress-debug-sections is given, compress .debug_* sections
|
||||
// using zlib.
|
||||
if (ctx.arg.compress_debug_sections != COMPRESS_NONE)
|
||||
filesize = compress_debug_sections(ctx);
|
||||
|
||||
// At this point, file layout is fixed.
|
||||
|
||||
t_before_copy.stop();
|
||||
|
||||
// Create an output file
|
||||
ctx.output_file = OutputFile<E>::open(ctx, ctx.arg.output, filesize, 0777);
|
||||
ctx.buf = ctx.output_file->buf;
|
||||
|
||||
Timer t_copy(ctx, "copy");
|
||||
|
||||
// Copy input sections to the output file
|
||||
{
|
||||
Timer t(ctx, "copy_buf");
|
||||
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
std::string name =
|
||||
chunk->name.empty() ? "(header)" : std::string(chunk->name);
|
||||
Timer t2(ctx, name, &t);
|
||||
chunk->copy_buf(ctx);
|
||||
});
|
||||
|
||||
ctx.checkpoint();
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<E, ARM64>)
|
||||
write_thunks(ctx);
|
||||
|
||||
if constexpr (std::is_same_v<E, ARM32>)
|
||||
sort_arm_exidx(ctx);
|
||||
|
||||
// Some part of .gdb_index couldn't be computed until other debug
|
||||
// sections are complete. We have complete debug sections now, so
|
||||
// write the rest of .gdb_index.
|
||||
if (ctx.gdb_index)
|
||||
ctx.gdb_index->write_address_areas(ctx);
|
||||
|
||||
// Dynamic linker works better with sorted .rela.dyn section,
|
||||
// so we sort them.
|
||||
ctx.reldyn->sort(ctx);
|
||||
|
||||
// Zero-clear paddings between sections
|
||||
clear_padding(ctx);
|
||||
|
||||
if (ctx.buildid) {
|
||||
Timer t(ctx, "build_id");
|
||||
ctx.buildid->write_buildid(ctx);
|
||||
}
|
||||
|
||||
t_copy.stop();
|
||||
|
||||
// Close the output file. This is the end of the linker's main job.
|
||||
ctx.output_file->close(ctx);
|
||||
|
||||
// Handle --dependency-file
|
||||
if (!ctx.arg.dependency_file.empty())
|
||||
write_dependency_file(ctx);
|
||||
|
||||
// Handle --print-dependencies
|
||||
if (ctx.arg.print_dependencies == 1)
|
||||
print_dependencies(ctx);
|
||||
else if (ctx.arg.print_dependencies == 2)
|
||||
print_dependencies_full(ctx);
|
||||
|
||||
if (ctx.has_lto_object)
|
||||
lto_cleanup(ctx);
|
||||
|
||||
t_total.stop();
|
||||
t_all.stop();
|
||||
|
||||
if (ctx.arg.print_map)
|
||||
print_map(ctx);
|
||||
|
||||
// Show stats numbers
|
||||
if (ctx.arg.stats)
|
||||
show_stats(ctx);
|
||||
|
||||
if (ctx.arg.perf)
|
||||
print_timer_records(ctx.timer_records);
|
||||
|
||||
std::cout << std::flush;
|
||||
std::cerr << std::flush;
|
||||
if (on_complete)
|
||||
on_complete();
|
||||
|
||||
if (ctx.arg.quick_exit)
|
||||
_exit(0);
|
||||
|
||||
for (std::function<void()> &fn : ctx.on_exit)
|
||||
fn();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
return elf_main<X86_64>(argc, argv);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void read_file(Context<E> &, MappedFile<Context<E>> *);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+31
-17
@@ -7,6 +7,8 @@
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
using Map =
|
||||
tbb::concurrent_hash_map<InputSection<E> *, std::vector<Symbol<E> *>>;
|
||||
@@ -16,7 +18,7 @@ static std::unique_ptr<std::ofstream> open_output_file(Context<E> &ctx) {
|
||||
std::unique_ptr<std::ofstream> file(new std::ofstream);
|
||||
file->open(ctx.arg.Map.c_str());
|
||||
if (!file->is_open())
|
||||
Fatal(ctx) << "cannot open " << ctx.arg.Map << ": " << strerror(errno);
|
||||
Fatal(ctx) << "cannot open " << ctx.arg.Map << ": " << errno_string();
|
||||
return file;
|
||||
}
|
||||
|
||||
@@ -26,12 +28,13 @@ static Map<E> get_map(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file == file && sym->input_section &&
|
||||
sym->get_type() != STT_SECTION) {
|
||||
assert(file == &sym->input_section->file);
|
||||
if (sym->file != file || sym->get_type() == STT_SECTION)
|
||||
continue;
|
||||
|
||||
if (InputSection<E> *isec = sym->get_input_section()) {
|
||||
assert(file == &isec->file);
|
||||
typename Map<E>::accessor acc;
|
||||
map.insert(acc, {sym->input_section, {}});
|
||||
map.insert(acc, {isec, {}});
|
||||
acc->second.push_back(sym);
|
||||
}
|
||||
}
|
||||
@@ -48,7 +51,6 @@ static Map<E> get_map(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void print_map(Context<E> &ctx) {
|
||||
|
||||
std::ostream *out = &std::cout;
|
||||
std::unique_ptr<std::ofstream> file;
|
||||
|
||||
@@ -61,15 +63,16 @@ void print_map(Context<E> &ctx) {
|
||||
Map<E> map = get_map(ctx);
|
||||
|
||||
// Print a mapfile.
|
||||
*out << " VMA Size Align Out In Symbol\n";
|
||||
*out << " VMA Size Align Out In Symbol\n";
|
||||
|
||||
for (OutputChunk<E> *osec : ctx.chunks) {
|
||||
*out << std::setw(16) << (u64)osec->shdr.sh_addr
|
||||
for (Chunk<E> *osec : ctx.chunks) {
|
||||
*out << std::showbase
|
||||
<< std::setw(18) << std::hex << (u64)osec->shdr.sh_addr << std::dec
|
||||
<< std::setw(11) << (u64)osec->shdr.sh_size
|
||||
<< std::setw(6) << (u64)osec->shdr.sh_addralign
|
||||
<< " " << osec->name << "\n";
|
||||
|
||||
if (osec->kind != OutputChunk<E>::REGULAR)
|
||||
if (!osec->is_output_section())
|
||||
continue;
|
||||
|
||||
std::span<InputSection<E> *> members = ((OutputSection<E> *)osec)->members;
|
||||
@@ -79,20 +82,23 @@ void print_map(Context<E> &ctx) {
|
||||
InputSection<E> *mem = members[i];
|
||||
std::ostringstream ss;
|
||||
opt_demangle = ctx.arg.demangle;
|
||||
u64 addr = osec->shdr.sh_addr + mem->offset;
|
||||
|
||||
ss << std::setw(16) << (osec->shdr.sh_addr + mem->offset)
|
||||
<< std::setw(11) << (u64)mem->shdr.sh_size
|
||||
<< std::setw(6) << (u64)mem->shdr.sh_addralign
|
||||
ss << std::showbase
|
||||
<< std::setw(18) << std::hex << addr << std::dec
|
||||
<< std::setw(11) << (u64)mem->sh_size
|
||||
<< std::setw(6) << (1 << (u64)mem->p2align)
|
||||
<< " " << *mem << "\n";
|
||||
|
||||
typename Map<E>::const_accessor acc;
|
||||
if (map.find(acc, mem))
|
||||
for (Symbol<E> *sym : acc->second)
|
||||
ss << std::setw(16) << sym->get_addr(ctx)
|
||||
ss << std::showbase
|
||||
<< std::setw(18) << std::hex << sym->get_addr(ctx) << std::dec
|
||||
<< " 0 0 "
|
||||
<< *sym << "\n";
|
||||
|
||||
bufs[i] = std::move(ss.str());
|
||||
bufs[i] = ss.str();
|
||||
});
|
||||
|
||||
for (std::string &str : bufs)
|
||||
@@ -100,5 +106,13 @@ void print_map(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
template void print_map(Context<X86_64> &ctx);
|
||||
template void print_map(Context<I386> &ctx);
|
||||
#define INSTANTIATE(E) \
|
||||
template void print_map(Context<E> &ctx);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -31,9 +31,9 @@ static void debug_print(char *fmt, ...) {
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static void get_args(va_list ap, int argc, char **argv) {
|
||||
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 *);
|
||||
char *arg = va_arg(*ap, char *);
|
||||
if (!arg)
|
||||
break;
|
||||
argv[i] = arg;
|
||||
@@ -41,9 +41,27 @@ static void get_args(va_list ap, int argc, char **argv) {
|
||||
}
|
||||
|
||||
static bool is_ld(const char *path) {
|
||||
return !strcmp(path, "/usr/bin/ld") ||
|
||||
!strcmp(path, "/usr/bin/ld.lld") ||
|
||||
!strcmp(path, "/usr/bin/ld.gold");
|
||||
const char *ptr = path + strlen(path);
|
||||
while (path < ptr && ptr[-1] != '/')
|
||||
ptr--;
|
||||
|
||||
return !strcmp(ptr, "ld") || !strcmp(ptr, "ld.lld") ||
|
||||
!strcmp(ptr, "ld.gold") || !strcmp(ptr, "ld.bfd");
|
||||
}
|
||||
|
||||
int execvpe(const char *file, char *const *argv, char *const *envp) {
|
||||
debug_print("execvpe %s\n", 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]);
|
||||
|
||||
typeof(execvpe) *real = dlsym(RTLD_NEXT, "execvp");
|
||||
return real(file, argv, environ);
|
||||
}
|
||||
|
||||
int execve(const char *path, char *const *argv, char *const *envp) {
|
||||
@@ -62,7 +80,8 @@ 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);
|
||||
get_args(&ap, 4096, argv);
|
||||
va_end(ap);
|
||||
return execve(path, argv, environ);
|
||||
}
|
||||
|
||||
@@ -70,7 +89,8 @@ 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);
|
||||
get_args(&ap, 4096, argv);
|
||||
va_end(ap);
|
||||
return execvpe(file, argv, environ);
|
||||
}
|
||||
|
||||
@@ -78,8 +98,9 @@ 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);
|
||||
get_args(&ap, 4096, argv);
|
||||
char **env = va_arg(ap, char **);
|
||||
va_end(ap);
|
||||
return execve(path, argv, env);
|
||||
}
|
||||
|
||||
@@ -91,18 +112,6 @@ int execvp(const char *file, char *const *argv) {
|
||||
return execvpe(file, argv, environ);
|
||||
}
|
||||
|
||||
int execvpe(const char *file, char *const *argv, char *const *envp) {
|
||||
debug_print("execvpe %s\n", file);
|
||||
|
||||
if (!strcmp(file, "ld") || is_ld(file)) {
|
||||
file = get_mold_path();
|
||||
((const char **)argv)[0] = file;
|
||||
}
|
||||
|
||||
typeof(execvpe) *real = dlsym(RTLD_NEXT, "execvpe");
|
||||
return real(file, argv, environ);
|
||||
}
|
||||
|
||||
int posix_spawn(pid_t *pid, const char *path,
|
||||
const posix_spawn_file_actions_t *file_actions,
|
||||
const posix_spawnattr_t *attrp,
|
||||
+2652
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -5,59 +5,69 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
static u32 get_umask() {
|
||||
namespace mold::elf {
|
||||
|
||||
inline u32 get_umask() {
|
||||
u32 orig_umask = umask(0);
|
||||
umask(orig_umask);
|
||||
return orig_umask;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
static std::pair<i64, char *>
|
||||
open_or_create_file(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
std::string tmpl = filepath(path).parent_path() / ".mold-XXXXXX";
|
||||
char *path2 = (char *)save_string(ctx, tmpl).data();
|
||||
|
||||
i64 fd = mkstemp(path2);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << path2 << ": " << errno_string();
|
||||
|
||||
// Reuse an existing file if exists and writable.
|
||||
if (rename(path.c_str(), path2) == 0) {
|
||||
::close(fd);
|
||||
fd = ::open(path2, O_RDWR | O_CREAT, perm);
|
||||
if (fd != -1 && !ftruncate(fd, filesize) && !fchmod(fd, perm & ~get_umask()))
|
||||
return {fd, path2};
|
||||
|
||||
unlink(path2);
|
||||
fd = ::open(path2, O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << path2 << ": " << errno_string();
|
||||
}
|
||||
|
||||
if (ftruncate(fd, filesize))
|
||||
Fatal(ctx) << "ftruncate failed: " << errno_string();
|
||||
|
||||
if (fchmod(fd, (perm & ~get_umask())) == -1)
|
||||
Fatal(ctx) << "fchmod failed: " << errno_string();
|
||||
return {fd, path2};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
class MemoryMappedOutputFile : public OutputFile<E> {
|
||||
public:
|
||||
MemoryMappedOutputFile(Context<E> &ctx, std::string path, i64 filesize,
|
||||
i64 perm)
|
||||
MemoryMappedOutputFile(Context<E> &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<E>(path, filesize, true) {
|
||||
std::string dir(path_dirname(path));
|
||||
this->tmpfile = (char *)save_string(ctx, dir + "/.mold-XXXXXX").data();
|
||||
i64 fd = mkstemp(this->tmpfile);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->tmpfile << ": " << strerror(errno);
|
||||
|
||||
if (rename(path.c_str(), this->tmpfile) == 0) {
|
||||
::close(fd);
|
||||
fd = ::open(this->tmpfile, O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1) {
|
||||
if (errno != ETXTBSY)
|
||||
Fatal(ctx) << "cannot open " << path << ": " << strerror(errno);
|
||||
unlink(this->tmpfile);
|
||||
fd = ::open(this->tmpfile, O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << path << ": " << strerror(errno);
|
||||
}
|
||||
}
|
||||
|
||||
if (ftruncate(fd, filesize))
|
||||
Fatal(ctx) << "ftruncate failed";
|
||||
|
||||
if (fchmod(fd, (perm & ~get_umask())) == -1)
|
||||
Fatal(ctx) << "fchmod failed";
|
||||
i64 fd;
|
||||
std::tie(fd, output_tmpfile) = open_or_create_file(ctx, path, filesize, perm);
|
||||
|
||||
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, fd, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << strerror(errno);
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
::close(fd);
|
||||
}
|
||||
|
||||
void close(Context<E> &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (!ctx.buildid)
|
||||
if (!this->is_unmapped)
|
||||
munmap(this->buf, this->filesize);
|
||||
|
||||
if (rename(this->tmpfile, this->path.c_str()) == -1)
|
||||
Fatal(ctx) << this->path << ": rename failed: " << strerror(errno);
|
||||
this->tmpfile = nullptr;
|
||||
if (rename(output_tmpfile, this->path.c_str()) == -1)
|
||||
Fatal(ctx) << this->path << ": rename failed: " << errno_string();
|
||||
output_tmpfile = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -69,7 +79,7 @@ public:
|
||||
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: " << strerror(errno);
|
||||
Fatal(ctx) << "mmap failed: " << errno_string();
|
||||
}
|
||||
|
||||
void close(Context<E> &ctx) override {
|
||||
@@ -83,7 +93,7 @@ public:
|
||||
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << strerror(errno);
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
|
||||
FILE *fp = fdopen(fd, "w");
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
@@ -122,10 +132,13 @@ OutputFile<E>::open(Context<E> &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
return file;
|
||||
}
|
||||
|
||||
template
|
||||
std::unique_ptr<OutputFile<X86_64>>
|
||||
OutputFile<X86_64>::open(Context<X86_64> &, std::string, i64, i64);
|
||||
#define INSTANTIATE(E) \
|
||||
template class OutputFile<E>;
|
||||
|
||||
template
|
||||
std::unique_ptr<OutputFile<I386>>
|
||||
OutputFile<I386>::open(Context<I386> &, std::string, i64, i64);
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+1681
File diff suppressed because it is too large.
Load diff
@@ -27,20 +27,23 @@
|
||||
// symbol indices.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E> class RObjectFile;
|
||||
|
||||
template <typename E>
|
||||
class ROutputChunk {
|
||||
class RChunk {
|
||||
public:
|
||||
ROutputChunk() {
|
||||
RChunk() {
|
||||
out_shdr.sh_addralign = 1;
|
||||
}
|
||||
|
||||
virtual ~ROutputChunk() = default;
|
||||
virtual ~RChunk() = default;
|
||||
virtual void update_shdr(Context<E> &ctx) {}
|
||||
virtual void write_to(Context<E> &ctx) = 0;
|
||||
|
||||
@@ -51,7 +54,7 @@ public:
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RInputSection : public ROutputChunk<E> {
|
||||
class RInputSection : public RChunk<E> {
|
||||
public:
|
||||
RInputSection(Context<E> &ctx, RObjectFile<E> &file, const ElfShdr<E> &shdr);
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
@@ -61,13 +64,13 @@ public:
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RSymtabSection : public ROutputChunk<E> {
|
||||
class RSymtabSection : public RChunk<E> {
|
||||
public:
|
||||
RSymtabSection() {
|
||||
this->name = ".symtab";
|
||||
this->out_shdr.sh_type = SHT_SYMTAB;
|
||||
this->out_shdr.sh_entsize = sizeof(ElfSym<E>);
|
||||
this->out_shdr.sh_addralign = E::wordsize;
|
||||
this->out_shdr.sh_addralign = E::word_size;
|
||||
}
|
||||
|
||||
void add_local_symbol(Context<E> &ctx, RObjectFile<E> &file, i64 idx);
|
||||
@@ -80,7 +83,7 @@ public:
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RStrtabSection : public ROutputChunk<E> {
|
||||
class RStrtabSection : public RChunk<E> {
|
||||
public:
|
||||
RStrtabSection(std::string_view name) {
|
||||
this->name = name;
|
||||
@@ -95,7 +98,7 @@ public:
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class ROutputEhdr : public ROutputChunk<E> {
|
||||
class ROutputEhdr : public RChunk<E> {
|
||||
public:
|
||||
ROutputEhdr() {
|
||||
this->out_shdr.sh_size = sizeof(ElfEhdr<E>);
|
||||
@@ -105,7 +108,7 @@ public:
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class ROutputShdr : public ROutputChunk<E> {
|
||||
class ROutputShdr : public RChunk<E> {
|
||||
public:
|
||||
ROutputShdr() {
|
||||
this->out_shdr.sh_size = sizeof(ElfShdr<E>);
|
||||
@@ -118,7 +121,7 @@ public:
|
||||
template <typename E>
|
||||
class RObjectFile {
|
||||
public:
|
||||
RObjectFile(Context<E> &ctx, MemoryMappedFile<E> &mb, bool is_alive);
|
||||
RObjectFile(Context<E> &ctx, MappedFile<Context<E>> &mf, bool is_alive);
|
||||
|
||||
void remove_comdats(Context<E> &ctx,
|
||||
std::unordered_set<std::string_view> &groups);
|
||||
@@ -126,7 +129,7 @@ public:
|
||||
template <typename T>
|
||||
std::span<T> get_data(Context<E> &ctx, const ElfShdr<E> &shdr);
|
||||
|
||||
MemoryMappedFile<E> &mb;
|
||||
MappedFile<Context<E>> &mf;
|
||||
std::span<ElfShdr<E>> elf_sections;
|
||||
std::vector<std::unique_ptr<RInputSection<E>>> sections;
|
||||
std::span<const ElfSym<E>> syms;
|
||||
@@ -294,7 +297,7 @@ 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::wordsize == 8) ? ELFCLASS64 : ELFCLASS32;
|
||||
hdr.e_ident[EI_CLASS] = (E::word_size == 8) ? ELFCLASS64 : ELFCLASS32;
|
||||
hdr.e_ident[EI_DATA] = E::is_le ? ELFDATA2LSB : ELFDATA2MSB;
|
||||
hdr.e_ident[EI_VERSION] = EV_CURRENT;
|
||||
hdr.e_type = ET_REL;
|
||||
@@ -310,7 +313,7 @@ void ROutputEhdr<E>::write_to(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void ROutputShdr<E>::update_shdr(Context<E> &ctx) {
|
||||
for (ROutputChunk<E> *chunk : ctx.r_chunks)
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk->shndx)
|
||||
this->out_shdr.sh_size += sizeof(ElfShdr<E>);
|
||||
}
|
||||
@@ -318,18 +321,18 @@ void ROutputShdr<E>::update_shdr(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
void ROutputShdr<E>::write_to(Context<E> &ctx) {
|
||||
ElfShdr<E> *hdr = (ElfShdr<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
for (ROutputChunk<E> *chunk : ctx.r_chunks)
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk->shndx)
|
||||
hdr[chunk->shndx] = chunk->out_shdr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
RObjectFile<E>::RObjectFile(Context<E> &ctx, MemoryMappedFile<E> &mb,
|
||||
RObjectFile<E>::RObjectFile(Context<E> &ctx, MappedFile<Context<E>> &mf,
|
||||
bool is_alive)
|
||||
: mb(mb), is_alive(is_alive) {
|
||||
: mf(mf), is_alive(is_alive) {
|
||||
// Read ELF header and section header
|
||||
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)mb.data(ctx);
|
||||
ElfShdr<E> *sh_begin = (ElfShdr<E> *)(mb.data(ctx) + ehdr.e_shoff);
|
||||
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)mf.data;
|
||||
ElfShdr<E> *sh_begin = (ElfShdr<E> *)(mf.data + ehdr.e_shoff);
|
||||
i64 num_sections = (ehdr.e_shnum == 0) ? sh_begin->sh_size : ehdr.e_shnum;
|
||||
elf_sections = {sh_begin, sh_begin + num_sections};
|
||||
sections.resize(num_sections);
|
||||
@@ -337,14 +340,14 @@ RObjectFile<E>::RObjectFile(Context<E> &ctx, MemoryMappedFile<E> &mb,
|
||||
// Read .shstrtab
|
||||
i64 shstrtab_idx = (ehdr.e_shstrndx == SHN_XINDEX)
|
||||
? sh_begin->sh_link : ehdr.e_shstrndx;
|
||||
shstrtab = (char *)(mb.data(ctx) + elf_sections[shstrtab_idx].sh_offset);
|
||||
shstrtab = (char *)(mf.data + elf_sections[shstrtab_idx].sh_offset);
|
||||
|
||||
// Read .symtab
|
||||
for (i64 i = 1; i < elf_sections.size(); i++) {
|
||||
ElfShdr<E> &shdr = elf_sections[i];
|
||||
if (shdr.sh_type == SHT_SYMTAB) {
|
||||
syms = get_data<const ElfSym<E>>(ctx, shdr);
|
||||
strtab = (char *)(mb.data(ctx) + elf_sections[shdr.sh_link].sh_offset);
|
||||
strtab = (char *)(mf.data + elf_sections[shdr.sh_link].sh_offset);
|
||||
symtab_shndx = i;
|
||||
first_global = shdr.sh_info;
|
||||
break;
|
||||
@@ -403,8 +406,8 @@ void RObjectFile<E>::remove_comdats(Context<E> &ctx,
|
||||
template <typename E>
|
||||
template <typename T>
|
||||
std::span<T> RObjectFile<E>::get_data(Context<E> &ctx, const ElfShdr<E> &shdr) {
|
||||
T *begin = (T *)(mb.data(ctx) + shdr.sh_offset);
|
||||
T *end = (T *)(mb.data(ctx) + shdr.sh_offset + shdr.sh_size);
|
||||
T *begin = (T *)(mf.data + shdr.sh_offset);
|
||||
T *end = (T *)(mf.data + shdr.sh_offset + shdr.sh_size);
|
||||
return {begin, end};
|
||||
}
|
||||
|
||||
@@ -430,26 +433,26 @@ open_files(Context<E> &ctx, std::span<std::string_view> args) {
|
||||
read_arg(ctx, args, arg, "dynamic-list"))
|
||||
continue;
|
||||
|
||||
MemoryMappedFile<E> *mb = nullptr;
|
||||
MappedFile<Context<E>> *mf = nullptr;
|
||||
|
||||
if (read_arg(ctx, args, arg, "l")) {
|
||||
mb = find_library(ctx, std::string(arg));
|
||||
mf = find_library(ctx, std::string(arg));
|
||||
} else {
|
||||
if (arg.starts_with('-'))
|
||||
continue;
|
||||
arg = args[0];
|
||||
args = args.subspan(1);
|
||||
mb = MemoryMappedFile<E>::must_open(ctx, std::string(arg));
|
||||
mf = MappedFile<Context<E>>::must_open(ctx, std::string(arg));
|
||||
}
|
||||
|
||||
switch (get_file_type(ctx, mb)) {
|
||||
case FileType::OBJ:
|
||||
files.emplace_back(new RObjectFile<E>(ctx, *mb, true));
|
||||
switch (get_file_type(mf)) {
|
||||
case FileType::ELF_OBJ:
|
||||
files.emplace_back(new RObjectFile<E>(ctx, *mf, true));
|
||||
break;
|
||||
case FileType::AR:
|
||||
case FileType::THIN_AR:
|
||||
for (MemoryMappedFile<E> *child : read_archive_members(ctx, mb))
|
||||
if (get_file_type(ctx, child) == FileType::OBJ)
|
||||
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf))
|
||||
if (get_file_type(child) == FileType::ELF_OBJ)
|
||||
files.emplace_back(new RObjectFile<E>(ctx, *child, whole_archive));
|
||||
break;
|
||||
default:
|
||||
@@ -462,7 +465,7 @@ open_files(Context<E> &ctx, std::span<std::string_view> args) {
|
||||
template <typename E>
|
||||
static i64 assign_offsets(Context<E> &ctx) {
|
||||
i64 offset = 0;
|
||||
for (ROutputChunk<E> *chunk : ctx.r_chunks) {
|
||||
for (RChunk<E> *chunk : ctx.r_chunks) {
|
||||
offset = align_to(offset, chunk->out_shdr.sh_addralign);
|
||||
chunk->out_shdr.sh_offset = offset;
|
||||
offset += chunk->out_shdr.sh_size;
|
||||
@@ -508,11 +511,9 @@ void combine_objects(Context<E> &ctx, std::span<std::string_view> file_args) {
|
||||
break;
|
||||
}
|
||||
|
||||
files.erase(std::remove_if(files.begin(), files.end(),
|
||||
[](std::unique_ptr<RObjectFile<E>> &file) {
|
||||
return !file->is_alive;
|
||||
}),
|
||||
files.end());
|
||||
std::erase_if(files, [](std::unique_ptr<RObjectFile<E>> &file) {
|
||||
return !file->is_alive;
|
||||
});
|
||||
|
||||
// Remove duplicate comdat groups
|
||||
std::unordered_set<std::string_view> comdat_groups;
|
||||
@@ -547,12 +548,12 @@ void combine_objects(Context<E> &ctx, std::span<std::string_view> file_args) {
|
||||
|
||||
// Assign output section indices
|
||||
i64 shndx = 1;
|
||||
for (ROutputChunk<E> *chunk : ctx.r_chunks)
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk != &ehdr && chunk != &shdr)
|
||||
chunk->shndx = shndx++;
|
||||
|
||||
// Add section names to .shstrtab
|
||||
for (ROutputChunk<E> *chunk : ctx.r_chunks)
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk->shndx)
|
||||
chunk->out_shdr.sh_name = shstrtab.add_string(chunk->name);
|
||||
|
||||
@@ -568,7 +569,7 @@ void combine_objects(Context<E> &ctx, std::span<std::string_view> file_args) {
|
||||
symtab.add_global_symbol(ctx, *file, i);
|
||||
|
||||
// Finalize section header
|
||||
for (ROutputChunk<E> *chunk : ctx.r_chunks)
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
// Open an output file
|
||||
@@ -579,10 +580,18 @@ void combine_objects(Context<E> &ctx, std::span<std::string_view> file_args) {
|
||||
ctx.buf = out->buf;
|
||||
|
||||
// Write to the output file
|
||||
for (ROutputChunk<E> *chunk : ctx.r_chunks)
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
chunk->write_to(ctx);
|
||||
out->close(ctx);
|
||||
}
|
||||
|
||||
template void combine_objects(Context<X86_64> &, std::span<std::string_view>);
|
||||
template void combine_objects(Context<I386> &, std::span<std::string_view>);
|
||||
#define INSTANTIATE(E) \
|
||||
template void combine_objects(Context<E> &, std::span<std::string_view>);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -1,9 +1,12 @@
|
||||
#include "mold.h"
|
||||
#include "../sha.h"
|
||||
|
||||
#include <openssl/sha.h>
|
||||
#include <sys/signal.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>
|
||||
@@ -11,6 +14,8 @@
|
||||
|
||||
#define DAEMON_TIMEOUT 30
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Exiting from a program with large memory usage is slow --
|
||||
// it may take a few hundred milliseconds. To hide the latency,
|
||||
// we fork a child and let it do the actual linking work.
|
||||
@@ -48,7 +53,7 @@ std::function<void()> fork_child() {
|
||||
// Child
|
||||
close(pipefd[0]);
|
||||
|
||||
return [=]() {
|
||||
return [=] {
|
||||
char buf[] = {1};
|
||||
int n = write(pipefd[1], buf, 1);
|
||||
assert(n == 1);
|
||||
@@ -118,7 +123,7 @@ static void send_fd(Context<E> &ctx, i64 conn, i64 fd) {
|
||||
*(int *)CMSG_DATA(cmsg) = fd;
|
||||
|
||||
if (sendmsg(conn, &msg, 0) == -1)
|
||||
Fatal(ctx) << "sendmsg failed: " << strerror(errno);
|
||||
Fatal(ctx) << "sendmsg failed: " << errno_string();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -138,7 +143,7 @@ static i64 recv_fd(Context<E> &ctx, i64 conn) {
|
||||
|
||||
i64 len = recvmsg(conn, &msg, 0);
|
||||
if (len <= 0)
|
||||
Fatal(ctx) << "recvmsg failed: " << strerror(errno);
|
||||
Fatal(ctx) << "recvmsg failed: " << errno_string();
|
||||
|
||||
struct cmsghdr *cmsg;
|
||||
cmsg = CMSG_FIRSTHDR(&msg);
|
||||
@@ -149,7 +154,7 @@ 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: " << strerror(errno);
|
||||
Fatal(ctx) << "socket failed: " << errno_string();
|
||||
|
||||
std::string path = "/tmp/mold-" + compute_sha256(ctx.cmdline_args);
|
||||
|
||||
@@ -176,11 +181,11 @@ 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: " << strerror(errno);
|
||||
Fatal(ctx) << "daemon failed: " << errno_string();
|
||||
|
||||
i64 sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sock == -1)
|
||||
Fatal(ctx) << "socket failed: " << strerror(errno);
|
||||
Fatal(ctx) << "socket failed: " << errno_string();
|
||||
|
||||
socket_tmpfile =
|
||||
strdup(("/tmp/mold-" + compute_sha256(ctx.cmdline_args)).c_str());
|
||||
@@ -193,21 +198,21 @@ void daemonize(Context<E> &ctx, std::function<void()> *wait_for_client,
|
||||
|
||||
if (bind(sock, (struct sockaddr *)&name, sizeof(name)) == -1) {
|
||||
if (errno != EADDRINUSE)
|
||||
Fatal(ctx) << "bind failed: " << strerror(errno);
|
||||
Fatal(ctx) << "bind failed: " << errno_string();
|
||||
|
||||
unlink(socket_tmpfile);
|
||||
if (bind(sock, (struct sockaddr *)&name, sizeof(name)) == -1)
|
||||
Fatal(ctx) << "bind failed: " << strerror(errno);
|
||||
Fatal(ctx) << "bind failed: " << errno_string();
|
||||
}
|
||||
|
||||
umask(orig_mask);
|
||||
|
||||
if (listen(sock, 0) == -1)
|
||||
Fatal(ctx) << "listen failed: " << strerror(errno);
|
||||
Fatal(ctx) << "listen failed: " << errno_string();
|
||||
|
||||
static i64 conn = -1;
|
||||
|
||||
*wait_for_client = [=, &ctx]() {
|
||||
*wait_for_client = [=, &ctx] {
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(sock, &rfds);
|
||||
@@ -218,7 +223,7 @@ void daemonize(Context<E> &ctx, std::function<void()> *wait_for_client,
|
||||
|
||||
i64 res = select(sock + 1, &rfds, NULL, NULL, &tv);
|
||||
if (res == -1)
|
||||
Fatal(ctx) << "select failed: " << strerror(errno);
|
||||
Fatal(ctx) << "select failed: " << errno_string();
|
||||
|
||||
if (res == 0) {
|
||||
std::cout << "timeout\n";
|
||||
@@ -227,29 +232,46 @@ void daemonize(Context<E> &ctx, std::function<void()> *wait_for_client,
|
||||
|
||||
conn = accept(sock, NULL, NULL);
|
||||
if (conn == -1)
|
||||
Fatal(ctx) << "accept failed: " << strerror(errno);
|
||||
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 = [=]() {
|
||||
*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>
|
||||
static std::string get_self_path(Context<E> &ctx) {
|
||||
char buf[4096];
|
||||
size_t n = readlink("/proc/self/exe", buf, sizeof(buf));
|
||||
if (n == -1)
|
||||
Fatal(ctx) << "readlink(\"/proc/self/exe\") failed: " << strerror(errno);
|
||||
if (n == sizeof(buf))
|
||||
Fatal(ctx) << "readlink: path too long";
|
||||
return {buf, n};
|
||||
std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
|
||||
// Look for mold-wrapper.so from the same directory as the executable is.
|
||||
std::filesystem::path path = self.parent_path() / "mold-wrapper.so";
|
||||
std::error_code ec;
|
||||
if (std::filesystem::is_regular_file(path, ec) && !ec)
|
||||
return path;
|
||||
|
||||
#ifdef LIBDIR
|
||||
// If not found, search $(LIBDIR)/mold, which is /usr/local/lib/mold
|
||||
// by default.
|
||||
path = LIBDIR "/mold/mold-wrapper.so";
|
||||
if (std::filesystem::is_regular_file(path, ec) && !ec)
|
||||
return path;
|
||||
#endif
|
||||
|
||||
// Look for ../lib/mold/mold-wrapper.so
|
||||
path = self.parent_path() / "../lib/mold/mold-wrapper.so";
|
||||
if (std::filesystem::is_regular_file(path, ec) && !ec)
|
||||
return path;
|
||||
|
||||
Fatal(ctx) << "mold-wrapper.so is missing";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -261,34 +283,27 @@ 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(ctx);
|
||||
std::string dso_path;
|
||||
if (self == "/usr/bin/mold")
|
||||
dso_path = "/usr/lib/mold/mold-wrapper.so";
|
||||
else if (self == "/usr/local/bin/mold")
|
||||
dso_path = "/usr/local/lib/mold/mold-wrapper.so";
|
||||
else
|
||||
dso_path = std::string(path_dirname(self)) + "/mold-wrapper.so";
|
||||
|
||||
struct stat st;
|
||||
if (stat(dso_path.c_str(), &st) || (st.st_mode & S_IFMT) != S_IFREG)
|
||||
Fatal(ctx) << dso_path << " is missing";
|
||||
std::string self = get_self_path();
|
||||
std::string dso_path = find_dso(ctx, self);
|
||||
|
||||
// Set environment variables
|
||||
putenv(strdup(("LD_PRELOAD=" + dso_path).c_str()));
|
||||
putenv(strdup(("MOLD_PATH=" + self).c_str()));
|
||||
|
||||
// If /usr/bin/ld{,lld,gold} is specified, run mold itself
|
||||
if (std::string_view cmd = argv[2]; cmd == "ld" ||
|
||||
cmd == "/usr/bin/ld" || cmd == "/usr/bin/ld.lld" ||
|
||||
cmd == "/usr/bin/ld.gold") {
|
||||
execv(self.c_str(), argv + 2);
|
||||
Fatal(ctx) << "mold -run failed: " << self << ": " << strerror(errno);
|
||||
// If ld, ld.lld or ld.gold is specified, run mold itself
|
||||
if (std::string cmd = filepath(argv[2]).filename();
|
||||
cmd == "ld" || cmd == "ld.lld" || cmd == "ld.gold") {
|
||||
std::vector<char *> args;
|
||||
args.push_back(argv[0]);
|
||||
args.insert(args.end(), argv + 3, argv + argc);
|
||||
args.push_back(nullptr);
|
||||
execv(self.c_str(), args.data());
|
||||
Fatal(ctx) << "mold -run failed: " << self << ": " << errno_string();
|
||||
}
|
||||
|
||||
// Execute a given command
|
||||
execvp(argv[2], argv + 2);
|
||||
Fatal(ctx) << "mold -run failed: " << argv[2] << ": " << strerror(errno);
|
||||
Fatal(ctx) << "mold -run failed: " << argv[2] << ": " << errno_string();
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
@@ -299,3 +314,8 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,166 @@
|
||||
// This file implements the Aho-Corasick algorithm to match version
|
||||
// script patterns to symbol strings as quickly as possible.
|
||||
//
|
||||
// Here are some examples of version script 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.
|
||||
//
|
||||
// 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.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
std::optional<u16> VersionMatcher::find(std::string_view str) {
|
||||
u32 idx = UINT32_MAX;
|
||||
|
||||
if (root) {
|
||||
std::call_once(once_flag, [&] { compile(); });
|
||||
|
||||
// Match against simple glob patterns
|
||||
TrieNode *node = root.get();
|
||||
|
||||
auto walk = [&](u8 c) {
|
||||
for (;;) {
|
||||
if (node->children[c]) {
|
||||
node = node->children[c].get();
|
||||
idx = std::min(idx, node->value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node->suffix_link)
|
||||
return;
|
||||
node = node->suffix_link;
|
||||
}
|
||||
};
|
||||
|
||||
walk('\0');
|
||||
for (u8 c : str)
|
||||
walk(c);
|
||||
walk('\0');
|
||||
}
|
||||
|
||||
// Match against complex glob patterns
|
||||
for (std::pair<GlobPattern, u32> &glob : globs)
|
||||
if (glob.first.match(str))
|
||||
idx = std::min(idx, glob.second);
|
||||
|
||||
if (idx == UINT32_MAX)
|
||||
return {};
|
||||
return versions[idx];
|
||||
}
|
||||
|
||||
static bool is_simple_pattern(std::string_view pat) {
|
||||
static std::regex re(R"(\*?[^*[?]+\*?)", std::regex_constants::optimize);
|
||||
return std::regex_match(pat.begin(), pat.end(), re);
|
||||
}
|
||||
|
||||
static std::string handle_stars(std::string_view pat) {
|
||||
std::string str(pat);
|
||||
|
||||
// Convert "foo" -> "\0foo\0", "*foo" -> "foo\0", "foo*" -> "\0foo"
|
||||
// and "*foo*" -> "foo". Aho-Corasick can do only substring matching,
|
||||
// so we use \0 as beginning/end-of-string markers.
|
||||
if (str.starts_with('*') && str.ends_with('*'))
|
||||
return str.substr(1, str.size() - 2);
|
||||
if (str.starts_with('*'))
|
||||
return str.substr(1) + "\0"s;
|
||||
if (str.ends_with('*'))
|
||||
return "\0"s + str.substr(0, str.size() - 1);
|
||||
return "\0"s + str + "\0"s;
|
||||
}
|
||||
|
||||
bool VersionMatcher::add(std::string_view pat, u16 ver) {
|
||||
assert(!compiled);
|
||||
assert(!pat.empty());
|
||||
|
||||
u32 idx = strings.size();
|
||||
strings.push_back(std::string(pat));
|
||||
versions.push_back(ver);
|
||||
|
||||
// Complex glob pattern
|
||||
if (!is_simple_pattern(pat)) {
|
||||
if (std::optional<GlobPattern> glob = GlobPattern::compile(pat)) {
|
||||
globs.push_back({std::move(*glob), idx});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Simple glob pattern
|
||||
if (!root)
|
||||
root.reset(new TrieNode);
|
||||
TrieNode *node = root.get();
|
||||
|
||||
for (u8 c : handle_stars(pat)) {
|
||||
if (!node->children[c])
|
||||
node->children[c].reset(new TrieNode);
|
||||
node = node->children[c].get();
|
||||
}
|
||||
|
||||
node->value = std::min(node->value, idx);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VersionMatcher::compile() {
|
||||
fix_suffix_links(*root);
|
||||
fix_values();
|
||||
compiled = true;
|
||||
}
|
||||
|
||||
void VersionMatcher::fix_suffix_links(TrieNode &node) {
|
||||
for (i64 i = 0; i < 256; i++) {
|
||||
if (!node.children[i])
|
||||
continue;
|
||||
|
||||
TrieNode &child = *node.children[i];
|
||||
|
||||
TrieNode *cur = node.suffix_link;
|
||||
for (;;) {
|
||||
if (!cur) {
|
||||
child.suffix_link = root.get();
|
||||
break;
|
||||
}
|
||||
|
||||
if (cur->children[i]) {
|
||||
child.suffix_link = cur->children[i].get();
|
||||
break;
|
||||
}
|
||||
|
||||
cur = cur->suffix_link;
|
||||
}
|
||||
|
||||
fix_suffix_links(child);
|
||||
}
|
||||
}
|
||||
|
||||
void VersionMatcher::fix_values() {
|
||||
std::queue<TrieNode *> queue;
|
||||
queue.push(root.get());
|
||||
|
||||
do {
|
||||
TrieNode *node = queue.front();
|
||||
queue.pop();
|
||||
|
||||
for (std::unique_ptr<TrieNode> &child : node->children) {
|
||||
if (!child)
|
||||
continue;
|
||||
child->value = std::min(child->value, child->suffix_link->value);
|
||||
queue.push(child.get());
|
||||
}
|
||||
} while (!queue.empty());
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+16
-94
@@ -1,105 +1,27 @@
|
||||
#include "mold.h"
|
||||
|
||||
std::string get_current_dir() {
|
||||
std::string buf(8192, '\0');
|
||||
getcwd(buf.data(), buf.size());
|
||||
buf.resize(buf.find_first_of('\0'));
|
||||
return buf;
|
||||
}
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
|
||||
// Returns the directory part of a given path.
|
||||
// Returns '.' if path doesn't contain '/'.
|
||||
std::string_view path_dirname(std::string_view path) {
|
||||
i64 pos = path.find_last_of('/');
|
||||
if (pos == path.npos)
|
||||
return ".";
|
||||
return path.substr(0, pos);
|
||||
}
|
||||
namespace mold {
|
||||
|
||||
// Returns the filename part of a given path.
|
||||
// Returns '/' if path reperesents the root directory.
|
||||
// Returns '.' if path is empty.
|
||||
std::string_view path_filename(std::string_view path) {
|
||||
if (path.empty())
|
||||
return ".";
|
||||
|
||||
while (path.ends_with('/'))
|
||||
path = path.substr(0, path.size() - 2);
|
||||
|
||||
if (path.empty())
|
||||
return "/";
|
||||
|
||||
i64 pos = path.find_last_of('/');
|
||||
if (pos == path.npos)
|
||||
return path;
|
||||
return path.substr(pos + 1);
|
||||
}
|
||||
|
||||
// Returns the filename part of a given path without the file
|
||||
// extension.
|
||||
std::string_view path_basename(std::string_view path) {
|
||||
std::string_view name = path_filename(path);
|
||||
|
||||
i64 pos = name.find_last_of('.');
|
||||
if (pos == name.npos)
|
||||
return name;
|
||||
return name.substr(0, pos);
|
||||
}
|
||||
|
||||
std::string path_to_absolute(std::string_view path) {
|
||||
if (path.starts_with('/'))
|
||||
return std::string(path);
|
||||
return get_current_dir() + "/" + std::string(path);
|
||||
std::string get_realpath(std::string_view path) {
|
||||
std::error_code ec;
|
||||
std::string ret = std::filesystem::canonical(path, ec);
|
||||
return ec ? std::string(path) : ret;
|
||||
}
|
||||
|
||||
// Removes redundant '/..' or '/.' from a given path.
|
||||
// The transformation is done purely by lexical processing.
|
||||
// This function does not access file system.
|
||||
std::string path_clean(std::string_view path) {
|
||||
std::vector<std::string_view> components;
|
||||
bool is_rooted = path.starts_with('/');
|
||||
|
||||
while (!path.empty()) {
|
||||
size_t pos = path.find('/');
|
||||
std::string_view elem;
|
||||
|
||||
if (pos == path.npos) {
|
||||
elem = path;
|
||||
path = "";
|
||||
} else {
|
||||
elem = path.substr(0, pos);
|
||||
path = path.substr(pos + 1);
|
||||
}
|
||||
|
||||
if (elem.empty() || elem == ".")
|
||||
continue;
|
||||
|
||||
if (elem == "..") {
|
||||
if (components.empty()) {
|
||||
if (is_rooted)
|
||||
continue;
|
||||
components.push_back("..");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (components.back() == "..")
|
||||
components.push_back("..");
|
||||
else
|
||||
components.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
components.push_back(elem);
|
||||
}
|
||||
|
||||
std::string ret;
|
||||
if (is_rooted)
|
||||
ret = "/";
|
||||
|
||||
for (i64 i = 0, end = components.size(); i < end; i++) {
|
||||
ret += components[i];
|
||||
if (i != end - 1)
|
||||
ret += "/";
|
||||
}
|
||||
return ret;
|
||||
return filepath(path).lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path to_abs_path(std::filesystem::path path) {
|
||||
if (path.is_absolute())
|
||||
return path;
|
||||
return (std::filesystem::current_path() / path).lexically_normal();
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
#include "elf/elf.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
enum class FileType {
|
||||
UNKNOWN,
|
||||
EMPTY,
|
||||
ELF_OBJ,
|
||||
ELF_DSO,
|
||||
MACH_OBJ,
|
||||
MACH_DYLIB,
|
||||
MACH_UNIVERSAL,
|
||||
AR,
|
||||
THIN_AR,
|
||||
TAPI,
|
||||
TEXT,
|
||||
GCC_LTO_OBJ,
|
||||
LLVM_BITCODE,
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
bool is_text_file(MappedFile<C> *mf) {
|
||||
u8 *data = mf->data;
|
||||
return mf->size >= 4 && isprint(data[0]) && isprint(data[1]) &&
|
||||
isprint(data[2]) && isprint(data[3]);
|
||||
}
|
||||
|
||||
template <typename E, typename C>
|
||||
inline bool is_gcc_lto_obj(MappedFile<C> *mf) {
|
||||
using namespace mold::elf;
|
||||
|
||||
const char *data = mf->get_contents().data();
|
||||
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)data;
|
||||
std::span<ElfShdr<E>> shdrs{(ElfShdr<E> *)(data + ehdr.e_shoff), ehdr.e_shnum};
|
||||
|
||||
for (ElfShdr<E> &sec : shdrs) {
|
||||
if (sec.sh_type != SHT_SYMTAB)
|
||||
continue;
|
||||
|
||||
std::span<ElfSym<E>> elf_syms{(ElfSym<E> *)(data + sec.sh_offset),
|
||||
sec.sh_size / sizeof(ElfSym<E>)};
|
||||
|
||||
auto skip = [](u8 type) {
|
||||
return type == STT_NOTYPE || type == STT_FILE || type == STT_SECTION;
|
||||
};
|
||||
|
||||
// GCC LTO object contains only sections symbols followed by a common
|
||||
// symbol whose name is `__gnu_lto_v1` or `__gnu_lto_slim`.
|
||||
i64 i = 1;
|
||||
while (i < elf_syms.size() && skip(elf_syms[i].st_type))
|
||||
i++;
|
||||
|
||||
if (i < elf_syms.size() && elf_syms[i].st_shndx == SHN_COMMON) {
|
||||
std::string_view name =
|
||||
data + shdrs[sec.sh_link].sh_offset + elf_syms[i].st_name;
|
||||
if (name.starts_with("__gnu_lto_"))
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
FileType get_file_type(MappedFile<C> *mf) {
|
||||
std::string_view data = mf->get_contents();
|
||||
|
||||
if (data.empty())
|
||||
return FileType::EMPTY;
|
||||
|
||||
if (data.starts_with("\177ELF")) {
|
||||
switch (*(u16 *)(data.data() + 16)) {
|
||||
case 1: {
|
||||
// ET_REL
|
||||
elf::Elf32Ehdr &ehdr = *(elf::Elf32Ehdr *)data.data();
|
||||
|
||||
if (ehdr.e_ident[elf::EI_CLASS] == elf::ELFCLASS32) {
|
||||
if (is_gcc_lto_obj<elf::I386>(mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
} else {
|
||||
if (is_gcc_lto_obj<elf::X86_64>(mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
}
|
||||
|
||||
return FileType::ELF_OBJ;
|
||||
}
|
||||
case 3: // ET_DYN
|
||||
return FileType::ELF_DSO;
|
||||
}
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
if (data.starts_with("\xcf\xfa\xed\xfe")) {
|
||||
switch (*(u32 *)(data.data() + 12)) {
|
||||
case 1: // MH_OBJECT
|
||||
return FileType::MACH_OBJ;
|
||||
case 6: // MH_DYLIB
|
||||
return FileType::MACH_DYLIB;
|
||||
}
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
if (data.starts_with("!<arch>\n"))
|
||||
return FileType::AR;
|
||||
if (data.starts_with("!<thin>\n"))
|
||||
return FileType::THIN_AR;
|
||||
if (data.starts_with("--- !tapi-tbd"))
|
||||
return FileType::TAPI;
|
||||
if (data.starts_with("\xca\xfe\xba\xbe"))
|
||||
return FileType::MACH_UNIVERSAL;
|
||||
if (is_text_file(mf))
|
||||
return FileType::TEXT;
|
||||
if (data.starts_with("\xde\xc0\x17\x0b"))
|
||||
return FileType::LLVM_BITCODE;
|
||||
if (data.starts_with("BC\xc0\xde"))
|
||||
return FileType::LLVM_BITCODE;
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
inline std::string filetype_to_string(FileType type) {
|
||||
switch (type) {
|
||||
case FileType::UNKNOWN: return "UNKNOWN";
|
||||
case FileType::EMPTY: return "EMPTY";
|
||||
case FileType::ELF_OBJ: return "ELF_OBJ";
|
||||
case FileType::ELF_DSO: return "ELF_DSO";
|
||||
case FileType::MACH_OBJ: return "MACH_OBJ";
|
||||
case FileType::MACH_DYLIB: return "MACH_DYLIB";
|
||||
case FileType::MACH_UNIVERSAL: return "MACH_UNIVERSAL";
|
||||
case FileType::AR: return "AR";
|
||||
case FileType::THIN_AR: return "THIN_AR";
|
||||
case FileType::TAPI: return "TAPI";
|
||||
case FileType::TEXT: return "TEXT";
|
||||
case FileType::GCC_LTO_OBJ: return "GCC_LTO_OBJ";
|
||||
case FileType::LLVM_BITCODE: return "LLVM_BITCODE";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &out, FileType type) {
|
||||
out << filetype_to_string(type);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,20 @@
|
||||
// This file implements HyperLogLog algorithm, which estimates
|
||||
// the number of unique items in a given multiset.
|
||||
//
|
||||
// For more info, read
|
||||
// https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace mold {
|
||||
|
||||
i64 HyperLogLog::get_cardinality() const {
|
||||
double z = 0;
|
||||
for (i64 val : buckets)
|
||||
z += pow(2, -val);
|
||||
return ALPHA * NBUCKETS * NBUCKETS / z;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,154 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
template <typename E>
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
const ElfShdr<E> &shdr, std::string_view name,
|
||||
std::string_view contents, i64 section_idx)
|
||||
: file(file), shdr(shdr), nameptr(name.data()), namelen(name.size()),
|
||||
contents(contents), section_idx(section_idx) {
|
||||
// As a special case, we want to map .ctors and .dtors to
|
||||
// .init_array and .fini_array, respectively. However, old CRT
|
||||
// object files are not compatible with this translation, so we need
|
||||
// to keep them as-is if a section came from crtbegin.o or crtend.o.
|
||||
//
|
||||
// Yeah, this is an ugly hack, but the fundamental problem is that
|
||||
// we have two different mechanism, ctors/dtors and init_array/fini_array
|
||||
// for the same purpose. The latter was introduced to replace the
|
||||
// former, but as it is often the case, the former still lingers
|
||||
// around, so we need to keep this code to conver the old mechanism
|
||||
// to the new one.
|
||||
std::string_view stem = path_filename(file.filename);
|
||||
if (stem != "crtbegin.o" && stem != "crtend.o" &&
|
||||
stem != "crtbeginS.o" && stem != "crtendS.o") {
|
||||
if (name == ".ctors" || name.starts_with(".ctors."))
|
||||
name = ".init_array";
|
||||
else if (name == ".dtors" || name.starts_with(".dtors."))
|
||||
name = ".fini_array";
|
||||
}
|
||||
|
||||
output_section =
|
||||
OutputSection<E>::get_instance(ctx, name, shdr.sh_type, shdr.sh_flags);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
if (shdr.sh_type == SHT_NOBITS || shdr.sh_size == 0)
|
||||
return;
|
||||
|
||||
// Copy data
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
|
||||
// Apply relocations
|
||||
if (shdr.sh_flags & SHF_ALLOC)
|
||||
apply_reloc_alloc(ctx, buf);
|
||||
else
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
|
||||
// As a special case, .ctors and .dtors section contents are
|
||||
// reversed. These sections are now obsolete and mapped to
|
||||
// .init_array and .fini_array, but they have to be reversed to
|
||||
// maintain the original semantics.
|
||||
bool init_fini = output_section->name == ".init_array" ||
|
||||
output_section->name == ".fini_array";
|
||||
bool ctors_dtors = name().starts_with(".ctors") ||
|
||||
name().starts_with(".dtors");
|
||||
if (init_fini && ctors_dtors)
|
||||
std::reverse((typename E::WordTy *)buf,
|
||||
(typename E::WordTy *)(buf + shdr.sh_size));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 get_output_type(Context<E> &ctx) {
|
||||
if (ctx.arg.shared)
|
||||
return 0;
|
||||
if (ctx.arg.pie)
|
||||
return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 get_sym_type(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (sym.is_absolute(ctx))
|
||||
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],
|
||||
u16 rel_type, i64 i) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
bool is_writable = (shdr.sh_flags & SHF_WRITE);
|
||||
Action action = table[get_output_type(ctx)][get_sym_type(ctx, sym)];
|
||||
|
||||
switch (action) {
|
||||
case NONE:
|
||||
rel_types[i] = rel_type;
|
||||
return;
|
||||
case ERROR:
|
||||
break;
|
||||
case COPYREL:
|
||||
if (!ctx.arg.z_copyreloc && !sym.esym().is_undef_weak())
|
||||
break;
|
||||
if (sym.esym().st_visibility == STV_PROTECTED)
|
||||
Error(ctx) << *this << ": cannot make copy relocation for "
|
||||
<< " protected symbol '" << sym << "', defined in "
|
||||
<< *sym.file;
|
||||
sym.flags |= NEEDS_COPYREL;
|
||||
rel_types[i] = rel_type;
|
||||
return;
|
||||
case PLT:
|
||||
sym.flags |= NEEDS_PLT;
|
||||
rel_types[i] = rel_type;
|
||||
return;
|
||||
case DYNREL:
|
||||
if (!is_writable) {
|
||||
if (ctx.arg.z_text)
|
||||
break;
|
||||
ctx.has_textrel = true;
|
||||
}
|
||||
sym.flags |= NEEDS_DYNSYM;
|
||||
rel_types[i] = R_DYN;
|
||||
file.num_dynrel++;
|
||||
return;
|
||||
case BASEREL:
|
||||
if (!is_writable) {
|
||||
if (ctx.arg.z_text)
|
||||
break;
|
||||
ctx.has_textrel = true;
|
||||
}
|
||||
rel_types[i] = R_BASEREL;
|
||||
file.num_dynrel++;
|
||||
return;
|
||||
default:
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
Error(ctx) << *this << ": " << rel_to_string<E>(rel.r_type)
|
||||
<< " relocation against symbol `" << sym
|
||||
<< "' can not be used; recompile with -fPIE";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::report_undef(Context<E> &ctx, Symbol<E> &sym) {
|
||||
switch (ctx.arg.unresolved_symbols) {
|
||||
case UnresolvedKind::ERROR:
|
||||
Error(ctx) << "undefined symbol: " << file << ": " << sym;
|
||||
break;
|
||||
case UnresolvedKind::WARN:
|
||||
Warn(ctx) << "undefined symbol: " << file << ": " << sym;
|
||||
break;
|
||||
case UnresolvedKind::IGNORE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template class InputSection<X86_64>;
|
||||
template class InputSection<I386>;
|
||||
@@ -0,0 +1,150 @@
|
||||
#pragma once
|
||||
|
||||
namespace mold {
|
||||
|
||||
enum PluginStatus {
|
||||
LDPS_OK,
|
||||
LDPS_NO_SYMS,
|
||||
LDPS_BAD_HANDLE,
|
||||
LDPS_ERR,
|
||||
};
|
||||
|
||||
enum PluginTag {
|
||||
LDPT_NULL,
|
||||
LDPT_API_VERSION,
|
||||
LDPT_GOLD_VERSION,
|
||||
LDPT_LINKER_OUTPUT,
|
||||
LDPT_OPTION,
|
||||
LDPT_REGISTER_CLAIM_FILE_HOOK,
|
||||
LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK,
|
||||
LDPT_REGISTER_CLEANUP_HOOK,
|
||||
LDPT_ADD_SYMBOLS,
|
||||
LDPT_GET_SYMBOLS,
|
||||
LDPT_ADD_INPUT_FILE,
|
||||
LDPT_MESSAGE,
|
||||
LDPT_GET_INPUT_FILE,
|
||||
LDPT_RELEASE_INPUT_FILE,
|
||||
LDPT_ADD_INPUT_LIBRARY,
|
||||
LDPT_OUTPUT_NAME,
|
||||
LDPT_SET_EXTRA_LIBRARY_PATH,
|
||||
LDPT_GNU_LD_VERSION,
|
||||
LDPT_GET_VIEW,
|
||||
LDPT_GET_INPUT_SECTION_COUNT,
|
||||
LDPT_GET_INPUT_SECTION_TYPE,
|
||||
LDPT_GET_INPUT_SECTION_NAME,
|
||||
LDPT_GET_INPUT_SECTION_CONTENTS,
|
||||
LDPT_UPDATE_SECTION_ORDER,
|
||||
LDPT_ALLOW_SECTION_ORDERING,
|
||||
LDPT_GET_SYMBOLS_V2,
|
||||
LDPT_ALLOW_UNIQUE_SEGMENT_FOR_SECTIONS,
|
||||
LDPT_UNIQUE_SEGMENT_FOR_SECTIONS,
|
||||
LDPT_GET_SYMBOLS_V3,
|
||||
LDPT_GET_INPUT_SECTION_ALIGNMENT,
|
||||
LDPT_GET_INPUT_SECTION_SIZE,
|
||||
LDPT_REGISTER_NEW_INPUT_HOOK,
|
||||
LDPT_GET_WRAP_SYMBOLS,
|
||||
LDPT_ADD_SYMBOLS_V2,
|
||||
};
|
||||
|
||||
enum PluginApiVersion {
|
||||
LD_PLUGIN_API_VERSION = 1,
|
||||
};
|
||||
|
||||
struct PluginTagValue {
|
||||
PluginTagValue(PluginTag tag, int val) : tag(tag), val(val) {}
|
||||
|
||||
template <typename T>
|
||||
PluginTagValue(PluginTag tag, T *ptr) : tag(tag), ptr((void *)ptr) {}
|
||||
|
||||
PluginTag tag;
|
||||
union {
|
||||
int val;
|
||||
void *ptr;
|
||||
};
|
||||
};
|
||||
|
||||
enum PluginOutputFileType {
|
||||
LDPO_REL,
|
||||
LDPO_EXEC,
|
||||
LDPO_DYN,
|
||||
LDPO_PIE,
|
||||
};
|
||||
|
||||
struct PluginInputFile {
|
||||
const char *name;
|
||||
int fd;
|
||||
off_t offset;
|
||||
off_t filesize;
|
||||
void *handle;
|
||||
};
|
||||
|
||||
struct PluginSection {
|
||||
const void *handle;
|
||||
int shndx;
|
||||
};
|
||||
|
||||
struct PluginSymbol {
|
||||
char *name;
|
||||
char *version;
|
||||
char def;
|
||||
char symbol_type;
|
||||
char section_kind;
|
||||
int visibility;
|
||||
uint64_t size;
|
||||
char *comdat_key;
|
||||
int resolution;
|
||||
};
|
||||
|
||||
enum PluginSymbolKind {
|
||||
LDPK_DEF,
|
||||
LDPK_WEAKDEF,
|
||||
LDPK_UNDEF,
|
||||
LDPK_WEAKUNDEF,
|
||||
LDPK_COMMON,
|
||||
};
|
||||
|
||||
enum PluginSymbolVisibility {
|
||||
LDPV_DEFAULT,
|
||||
LDPV_PROTECTED,
|
||||
LDPV_INTERNAL,
|
||||
LDPV_HIDDEN,
|
||||
};
|
||||
|
||||
enum PluginSymbolType {
|
||||
LDST_UNKNOWN,
|
||||
LDST_FUNCTION,
|
||||
LDST_VARIABLE,
|
||||
};
|
||||
|
||||
enum PluginSymbolSectionKind {
|
||||
LDSSK_DEFAULT,
|
||||
LDSSK_BSS,
|
||||
};
|
||||
|
||||
enum PluginSymbolResolution {
|
||||
LDPR_UNKNOWN,
|
||||
LDPR_UNDEF,
|
||||
LDPR_PREVAILING_DEF,
|
||||
LDPR_PREVAILING_DEF_IRONLY,
|
||||
LDPR_PREEMPTED_REG,
|
||||
LDPR_PREEMPTED_IR,
|
||||
LDPR_RESOLVED_IR,
|
||||
LDPR_RESOLVED_EXEC,
|
||||
LDPR_RESOLVED_DYN,
|
||||
LDPR_PREVAILING_DEF_IRONLY_EXP,
|
||||
};
|
||||
|
||||
enum PluginLevel {
|
||||
LDPL_INFO,
|
||||
LDPL_WARNING,
|
||||
LDPL_ERROR,
|
||||
LDPL_FATAL,
|
||||
};
|
||||
|
||||
typedef PluginStatus OnloadFn(PluginTagValue *tv);
|
||||
typedef PluginStatus ClaimFileHandler(const PluginInputFile *, int *);
|
||||
typedef PluginStatus AllSymbolsReadHandler();
|
||||
typedef PluginStatus CleanupHandler();
|
||||
typedef PluginStatus NewInputHandler(const PluginInputFile *);
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,258 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u64 bits(u64 val, u64 hi, u64 lo) {
|
||||
return (val >> lo) & (((u64)1 << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
static u64 page(u64 val) {
|
||||
return val & 0xffff'ffff'ffff'f000;
|
||||
}
|
||||
|
||||
static u64 encode_page(u64 val) {
|
||||
return (bits(val, 13, 12) << 29) | (bits(val, 32, 14) << 5);
|
||||
}
|
||||
|
||||
template <>
|
||||
void StubsSection<ARM64>::copy_buf(Context<ARM64> &ctx) {
|
||||
u32 *buf = (u32 *)(ctx.buf + this->hdr.offset);
|
||||
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
static const u32 insn[] = {
|
||||
0x90000010, // adrp x16, $ptr@PAGE
|
||||
0xf9400210, // ldr x16, [x16, $ptr@PAGEOFF]
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == ARM64::stub_size);
|
||||
|
||||
u64 la_addr = ctx.lazy_symbol_ptr.hdr.addr + ARM64::word_size * i;
|
||||
u64 this_addr = this->hdr.addr + ARM64::stub_size * i;
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
buf[0] |= encode_page(page(la_addr) - page(this_addr));
|
||||
buf[1] |= bits(la_addr, 11, 3) << 10;
|
||||
buf += 3;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void StubHelperSection<ARM64>::copy_buf(Context<ARM64> &ctx) {
|
||||
u32 *start = (u32 *)(ctx.buf + this->hdr.offset);
|
||||
u32 *buf = start;
|
||||
|
||||
static const u32 insn0[] = {
|
||||
0x90000011, // adrp x17, $__dyld_private@PAGE
|
||||
0x91000231, // add x17, x17, $__dyld_private@PAGEOFF
|
||||
0xa9bf47f0, // stp x16, x17, [sp, #-16]!
|
||||
0x90000010, // adrp x16, $dyld_stub_binder@PAGE
|
||||
0xf9400210, // ldr x16, [x16, $dyld_stub_binder@PAGEOFF]
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn0) == ARM64::stub_helper_hdr_size);
|
||||
memcpy(buf, insn0, sizeof(insn0));
|
||||
|
||||
u64 dyld_private = get_symbol(ctx, "__dyld_private")->get_addr(ctx);
|
||||
buf[0] |= encode_page(page(dyld_private) - page(this->hdr.addr));
|
||||
buf[1] |= bits(dyld_private, 11, 0) << 10;
|
||||
|
||||
u64 stub_binder = get_symbol(ctx, "dyld_stub_binder")->get_addr(ctx);
|
||||
buf[3] |= encode_page(page(stub_binder) - page(this->hdr.addr - 12));
|
||||
buf[4] |= bits(stub_binder, 11, 0) << 10;
|
||||
|
||||
buf += 6;
|
||||
|
||||
for (i64 i = 0; i < ctx.stubs.syms.size(); i++) {
|
||||
static const u32 insn[] = {
|
||||
0x18000050, // ldr w16, addr
|
||||
0x14000000, // b stubHelperHeader
|
||||
0x00000000, // addr: .long <idx>
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == ARM64::stub_helper_size);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
buf[1] |= bits((start - buf - 1) * 4, 27, 2);
|
||||
buf[2] = ctx.stubs.bind_offsets[i];
|
||||
buf += 3;
|
||||
}
|
||||
}
|
||||
|
||||
static i64 read_addend(u8 *buf, const MachRel &r) {
|
||||
if (r.p2size == 2)
|
||||
return *(i32 *)(buf + r.offset);
|
||||
if (r.p2size == 3)
|
||||
return *(i64 *)(buf + r.offset);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
static Relocation<ARM64>
|
||||
read_reloc(Context<ARM64> &ctx, ObjectFile<ARM64> &file,
|
||||
const MachSection &hdr, MachRel *rels, i64 &idx) {
|
||||
i64 addend = 0;
|
||||
|
||||
switch (rels[idx].type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_SUBTRACTOR:
|
||||
addend = read_addend((u8 *)file.mf->data + hdr.offset, rels[idx]);
|
||||
break;
|
||||
case ARM64_RELOC_ADDEND:
|
||||
addend = rels[idx++].offset;
|
||||
break;
|
||||
}
|
||||
|
||||
MachRel &r = rels[idx];
|
||||
Relocation<ARM64> rel{r.offset, (u8)r.type, (u8)r.p2size, (bool)r.is_pcrel};
|
||||
|
||||
if (r.is_extern) {
|
||||
rel.sym = file.syms[r.idx];
|
||||
rel.addend = addend;
|
||||
return rel;
|
||||
}
|
||||
|
||||
u32 addr;
|
||||
if (r.is_pcrel)
|
||||
addr = hdr.addr + r.offset + addend;
|
||||
else
|
||||
addr = addend;
|
||||
|
||||
Subsection<ARM64> *target = file.find_subsection(ctx, addr);
|
||||
if (!target)
|
||||
Fatal(ctx) << file << ": bad relocation: " << r.offset;
|
||||
|
||||
rel.subsec = target;
|
||||
rel.addend = addr - target->input_addr;
|
||||
return rel;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::vector<Relocation<ARM64>>
|
||||
read_relocations(Context<ARM64> &ctx, ObjectFile<ARM64> &file,
|
||||
const MachSection &hdr) {
|
||||
std::vector<Relocation<ARM64>> vec;
|
||||
|
||||
MachRel *rels = (MachRel *)(file.mf->data + hdr.reloff);
|
||||
for (i64 i = 0; i < hdr.nreloc; i++)
|
||||
vec.push_back(read_reloc(ctx, file, hdr, rels, i));
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<ARM64>::scan_relocations(Context<ARM64> &ctx) {
|
||||
for (Relocation<ARM64> &r : get_rels()) {
|
||||
Symbol<ARM64> *sym = r.sym;
|
||||
if (!sym)
|
||||
continue;
|
||||
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
sym->flags |= NEEDS_GOT;
|
||||
break;
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12:
|
||||
sym->flags |= NEEDS_THREAD_PTR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sym->file && sym->file->is_dylib()) {
|
||||
sym->flags |= NEEDS_STUB;
|
||||
((DylibFile<ARM64> *)sym->file)->is_needed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<ARM64>::apply_reloc(Context<ARM64> &ctx, u8 *buf) {
|
||||
std::span<Relocation<ARM64>> rels = get_rels();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
Relocation<ARM64> &r = rels[i];
|
||||
|
||||
if (r.sym && !r.sym->file) {
|
||||
Error(ctx) << "undefined symbol: " << isec.file << ": " << *r.sym;
|
||||
continue;
|
||||
}
|
||||
|
||||
u64 val = 0;
|
||||
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_BRANCH26:
|
||||
case ARM64_RELOC_PAGE21:
|
||||
case ARM64_RELOC_PAGEOFF12:
|
||||
val = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
break;
|
||||
case ARM64_RELOC_SUBTRACTOR: {
|
||||
Relocation<ARM64> s = rels[++i];
|
||||
assert(s.type == ARM64_RELOC_UNSIGNED);
|
||||
u64 val1 = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
u64 val2 = s.sym ? s.sym->get_addr(ctx) : s.subsec->get_addr(ctx);
|
||||
val = val2 - val1;
|
||||
break;
|
||||
}
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
val = r.sym->get_got_addr(ctx);
|
||||
break;
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12:
|
||||
val = r.sym->get_tlv_addr(ctx);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
|
||||
}
|
||||
|
||||
val += r.addend;
|
||||
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_SUBTRACTOR:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
if (r.is_pcrel)
|
||||
val -= get_addr(ctx) + r.offset;
|
||||
|
||||
if (r.p2size == 2)
|
||||
*(i32 *)(buf + r.offset) = val;
|
||||
else if (r.p2size == 3)
|
||||
*(i64 *)(buf + r.offset) = val;
|
||||
else
|
||||
unreachable();
|
||||
break;
|
||||
case ARM64_RELOC_BRANCH26:
|
||||
if (r.is_pcrel)
|
||||
val -= get_addr(ctx) + r.offset;
|
||||
*(u32 *)(buf + r.offset) |= bits(val, 27, 2);
|
||||
break;
|
||||
case ARM64_RELOC_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21:
|
||||
assert(r.is_pcrel);
|
||||
*(u32 *)(buf + r.offset) |=
|
||||
encode_page(page(val) - page(get_addr(ctx) + r.offset));
|
||||
break;
|
||||
case ARM64_RELOC_PAGEOFF12:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12: {
|
||||
if (r.is_pcrel)
|
||||
val -= get_addr(ctx) + r.offset;
|
||||
|
||||
u32 insn = *(u32 *)(buf + r.offset);
|
||||
i64 scale = 0;
|
||||
if ((insn & 0x3b000000) == 0x39000000)
|
||||
scale = insn >> 30;
|
||||
*(u32 *)(buf + r.offset) |= bits(val, 11, scale) << 10;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,222 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <>
|
||||
void StubsSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
u8 *buf = ctx.buf + this->hdr.offset;
|
||||
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
// `ff 25 xx xx xx xx` is a RIP-relative indirect jump instruction,
|
||||
// i.e., `jmp *IMM(%rip)`. It loads an address from la_symbol_ptr
|
||||
// and jump there.
|
||||
static_assert(X86_64::stub_size == 6);
|
||||
buf[i * 6] = 0xff;
|
||||
buf[i * 6 + 1] = 0x25;
|
||||
*(u32 *)(buf + i * 6 + 2) =
|
||||
ctx.lazy_symbol_ptr.hdr.addr + i * X86_64::word_size -
|
||||
(this->hdr.addr + i * 6 + 6);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void StubHelperSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
u8 *start = ctx.buf + this->hdr.offset;
|
||||
u8 *buf = start;
|
||||
|
||||
static const u8 insn0[] = {
|
||||
0x4c, 0x8d, 0x1d, 0, 0, 0, 0, // lea $__dyld_private(%rip), %r11
|
||||
0x41, 0x53, // push %r11
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *$dyld_stub_binder@GOT(%rip)
|
||||
0x90, // nop
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn0) == X86_64::stub_helper_hdr_size);
|
||||
|
||||
memcpy(buf, insn0, sizeof(insn0));
|
||||
*(u32 *)(buf + 3) =
|
||||
get_symbol(ctx, "__dyld_private")->get_addr(ctx) - this->hdr.addr - 7;
|
||||
*(u32 *)(buf + 11) =
|
||||
get_symbol(ctx, "dyld_stub_binder")->get_got_addr(ctx) - this->hdr.addr - 15;
|
||||
|
||||
buf += 16;
|
||||
|
||||
for (i64 i = 0; i < ctx.stubs.syms.size(); i++) {
|
||||
u8 insn[] = {
|
||||
0x68, 0, 0, 0, 0, // push $bind_offset
|
||||
0xe9, 0, 0, 0, 0, // jmp $__stub_helper
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == X86_64::stub_helper_size);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(u32 *)(buf + 1) = ctx.stubs.bind_offsets[i];
|
||||
*(u32 *)(buf + 6) = start - buf - 10;
|
||||
buf += 10;
|
||||
}
|
||||
}
|
||||
|
||||
static i64 get_reloc_addend(u32 type) {
|
||||
switch (type) {
|
||||
case X86_64_RELOC_SIGNED_1:
|
||||
return 1;
|
||||
case X86_64_RELOC_SIGNED_2:
|
||||
return 2;
|
||||
case X86_64_RELOC_SIGNED_4:
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static i64 read_addend(u8 *buf, const MachRel &r) {
|
||||
if (r.p2size == 2)
|
||||
return *(i32 *)(buf + r.offset) + get_reloc_addend(r.type);
|
||||
if (r.p2size == 3)
|
||||
return *(i64 *)(buf + r.offset) + get_reloc_addend(r.type);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
static Relocation<X86_64>
|
||||
read_reloc(Context<X86_64> &ctx, ObjectFile<X86_64> &file,
|
||||
const MachSection &hdr, MachRel &r) {
|
||||
if (r.p2size != 2 && r.p2size != 3)
|
||||
Fatal(ctx) << file << ": invalid r.p2size: " << (u32)r.p2size;
|
||||
|
||||
if (r.is_pcrel) {
|
||||
if (r.p2size != 2)
|
||||
Fatal(ctx) << file << ": invalid PC-relative reloc: " << r.offset;
|
||||
} else {
|
||||
if (r.p2size != 3)
|
||||
Fatal(ctx) << file << ": invalid non-PC-relative reloc: " << r.offset;
|
||||
}
|
||||
|
||||
u8 *buf = (u8 *)file.mf->data + hdr.offset;
|
||||
Relocation<X86_64> rel{r.offset, (u8)r.type, (u8)r.p2size, (bool)r.is_pcrel};
|
||||
i64 addend = read_addend(buf, r);
|
||||
|
||||
if (r.is_extern) {
|
||||
rel.sym = file.syms[r.idx];
|
||||
rel.addend = addend;
|
||||
return rel;
|
||||
}
|
||||
|
||||
u32 addr;
|
||||
if (r.is_pcrel)
|
||||
addr = hdr.addr + r.offset + 4 + addend;
|
||||
else
|
||||
addr = addend;
|
||||
|
||||
Subsection<X86_64> *target = file.find_subsection(ctx, addr);
|
||||
if (!target)
|
||||
Fatal(ctx) << file << ": bad relocation: " << r.offset;
|
||||
|
||||
rel.subsec = target;
|
||||
rel.addend = addr - target->input_addr;
|
||||
return rel;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::vector<Relocation<X86_64>>
|
||||
read_relocations(Context<X86_64> &ctx, ObjectFile<X86_64> &file,
|
||||
const MachSection &hdr) {
|
||||
std::vector<Relocation<X86_64>> vec;
|
||||
vec.reserve(hdr.nreloc);
|
||||
|
||||
MachRel *rels = (MachRel *)(file.mf->data + hdr.reloff);
|
||||
for (i64 i = 0; i < hdr.nreloc; i++)
|
||||
vec.push_back(read_reloc(ctx, file, hdr, rels[i]));
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<X86_64>::scan_relocations(Context<X86_64> &ctx) {
|
||||
for (Relocation<X86_64> &r : get_rels()) {
|
||||
Symbol<X86_64> *sym = r.sym;
|
||||
if (!sym)
|
||||
continue;
|
||||
|
||||
switch (r.type) {
|
||||
case X86_64_RELOC_GOT_LOAD:
|
||||
if (sym->file && sym->file->is_dylib())
|
||||
sym->flags |= NEEDS_GOT;
|
||||
break;
|
||||
case X86_64_RELOC_GOT:
|
||||
sym->flags |= NEEDS_GOT;
|
||||
break;
|
||||
case X86_64_RELOC_TLV:
|
||||
if (sym->file && sym->file->is_dylib())
|
||||
sym->flags |= NEEDS_THREAD_PTR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sym->file && sym->file->is_dylib()) {
|
||||
sym->flags |= NEEDS_STUB;
|
||||
((DylibFile<X86_64> *)sym->file)->is_needed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<X86_64>::apply_reloc(Context<X86_64> &ctx, u8 *buf) {
|
||||
for (const Relocation<X86_64> &r : get_rels()) {
|
||||
if (r.sym && !r.sym->file) {
|
||||
Error(ctx) << "undefined symbol: " << isec.file << ": " << *r.sym;
|
||||
continue;
|
||||
}
|
||||
|
||||
u64 val = 0;
|
||||
|
||||
switch (r.type) {
|
||||
case X86_64_RELOC_UNSIGNED:
|
||||
case X86_64_RELOC_SIGNED:
|
||||
case X86_64_RELOC_BRANCH:
|
||||
case X86_64_RELOC_SIGNED_1:
|
||||
case X86_64_RELOC_SIGNED_2:
|
||||
case X86_64_RELOC_SIGNED_4:
|
||||
val = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
break;
|
||||
case X86_64_RELOC_GOT_LOAD:
|
||||
if (r.sym->got_idx != -1) {
|
||||
val = r.sym->get_got_addr(ctx);
|
||||
} else {
|
||||
// Relax MOVQ into LEAQ
|
||||
if (buf[r.offset - 2] != 0x8b)
|
||||
Error(ctx) << isec << ": invalid GOT_LOAD relocation";
|
||||
buf[r.offset - 2] = 0x8d;
|
||||
val = r.sym->get_addr(ctx);
|
||||
}
|
||||
break;
|
||||
case X86_64_RELOC_GOT:
|
||||
val = r.sym->get_got_addr(ctx);
|
||||
break;
|
||||
case X86_64_RELOC_TLV:
|
||||
if (r.sym->tlv_idx != -1) {
|
||||
val = r.sym->get_tlv_addr(ctx);
|
||||
} else {
|
||||
// Relax MOVQ into LEAQ
|
||||
if (buf[r.offset - 2] != 0x8b)
|
||||
Error(ctx) << isec << ": invalid TLV relocation";
|
||||
buf[r.offset - 2] = 0x8d;
|
||||
val = r.sym->get_addr(ctx);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
|
||||
}
|
||||
|
||||
val += r.addend;
|
||||
|
||||
if (r.is_pcrel)
|
||||
val -= get_addr(ctx) + r.offset + 4 + get_reloc_addend(r.type);
|
||||
|
||||
if (r.p2size == 2)
|
||||
*(u32 *)(buf + r.offset) = val;
|
||||
else if (r.p2size == 3)
|
||||
*(u64 *)(buf + r.offset) = val;
|
||||
else
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,322 @@
|
||||
#include "mold.h"
|
||||
#include "../cmdline.h"
|
||||
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <regex>
|
||||
#include <unistd.h>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
static const char helpmsg[] = R"(
|
||||
Options:
|
||||
-F<PATH> Add DIR to framework search path
|
||||
-L<PATH> Add DIR to library search path
|
||||
-Z Do not search the standard directories when
|
||||
searching for libraries and frameworks
|
||||
-ObjC Load all static archive members that implement
|
||||
an Objective-C class or category
|
||||
-adhoc_codesign Add ad-hoc code signature to the output file
|
||||
-no_adhoc_codesign
|
||||
-arch <ARCH_NAME> Specify target architecture
|
||||
-bundle Produce a mach-o bundle
|
||||
-dead_strip Remove unreachable functions and data
|
||||
-dead_strip_dylibs Remove unreachable dylibs from dependencies
|
||||
-demangle Demangle C++ symbols in log messages (default)
|
||||
-dylib Produce a dynamic library
|
||||
-dynamic Link against dylibs (default)
|
||||
-e <SYMBOL> Specify the entry point of a main executable
|
||||
-execute Produce an executable (default)
|
||||
-filelist <FILE>[,<DIR>] Specify the list of input file names
|
||||
-framework <NAME>,[,<SUFFIX>]
|
||||
Search for a given framework
|
||||
-headerpad <SIZE> Allocate the size of padding after load commands
|
||||
-headerpad_max_install_names
|
||||
Allocate MAXPATHLEN byte padding after load commands
|
||||
-help Report usage information
|
||||
-l<LIB> Search for a given library
|
||||
-lto_library <FILE> Ignored
|
||||
-map <FILE> Write map file to a given file
|
||||
-needed-l<LIB> Search for a given library
|
||||
-needed-framework <NAME>[,<SUFFIX>]
|
||||
Search for a given framework
|
||||
-no_deduplicate Ignored
|
||||
-o <FILE> Set output filename
|
||||
-pagezero_size <SIZE> Specify the size of the __PAGEZERO segment
|
||||
-platform_version <PLATFORM> <MIN_VERSION> <SDK_VERSION>
|
||||
Set platform, platform version and SDK version
|
||||
-rpath <PATH> Add PATH to the runpath search path list
|
||||
-syslibroot <DIR> Prepend DIR to library search paths
|
||||
-t Print out each file the linker loads
|
||||
-v Report version information)";
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_platform(Context<E> &ctx, std::string_view arg) {
|
||||
static std::regex re(R"(\d+)", std::regex_constants::ECMAScript);
|
||||
if (std::regex_match(arg.begin(), arg.end(), re))
|
||||
return stoi(std::string(arg));
|
||||
|
||||
if (arg == "macos")
|
||||
return PLATFORM_MACOS;
|
||||
if (arg == "ios")
|
||||
return PLATFORM_IOS;
|
||||
if (arg == "tvos")
|
||||
return PLATFORM_TVOS;
|
||||
if (arg == "watchos")
|
||||
return PLATFORM_WATCHOS;
|
||||
if (arg == "bridgeos")
|
||||
return PLATFORM_BRIDGEOS;
|
||||
if (arg == "mac-catalyst")
|
||||
return PLATFORM_MACCATALYST;
|
||||
if (arg == "ios-simulator")
|
||||
return PLATFORM_IOSSIMULATOR;
|
||||
if (arg == "tvos-simulator")
|
||||
return PLATFORM_TVOSSIMULATOR;
|
||||
if (arg == "watchos-simulator")
|
||||
return PLATFORM_WATCHOSSIMULATOR;
|
||||
if (arg == "driverkit")
|
||||
return PLATFORM_DRIVERKIT;
|
||||
Fatal(ctx) << "unknown -platform_version name: " << arg;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_version(Context<E> &ctx, std::string_view arg) {
|
||||
static std::regex re(R"((\d+)(?:\.(\d+))?(?:\.(\d+))?)",
|
||||
std::regex_constants::ECMAScript);
|
||||
std::cmatch m;
|
||||
if (!std::regex_match(arg.begin(), arg.end(), m, re))
|
||||
Fatal(ctx) << "malformed version number: " << arg;
|
||||
|
||||
i64 major = (m[1].length() == 0) ? 0 : stoi(m[1]);
|
||||
i64 minor = (m[2].length() == 0) ? 0 : stoi(m[2]);
|
||||
i64 patch = (m[3].length() == 0) ? 0 : stoi(m[3]);
|
||||
return (major << 16) | (minor << 8) | patch;
|
||||
}
|
||||
|
||||
static bool is_directory(std::filesystem::path path) {
|
||||
std::error_code ec;
|
||||
return std::filesystem::is_directory(path, ec) && !ec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_nonpositional_args(Context<E> &ctx,
|
||||
std::vector<std::string> &remaining) {
|
||||
std::vector<std::string_view> &args = ctx.cmdline_args;
|
||||
i64 i = 1;
|
||||
|
||||
std::vector<std::string> framework_paths;
|
||||
std::vector<std::string> library_paths;
|
||||
bool nostdlib = false;
|
||||
std::optional<i64> pagezero_size;
|
||||
|
||||
while (i < args.size()) {
|
||||
std::string_view arg;
|
||||
std::string_view arg2;
|
||||
std::string_view arg3;
|
||||
|
||||
auto read_arg = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
if (args.size() <= i + 1)
|
||||
Fatal(ctx) << "option -" << name << ": argument missing";
|
||||
arg = args[i + 1];
|
||||
i += 2;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_arg3 = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
if (args.size() <= i + 3)
|
||||
Fatal(ctx) << "option -" << name << ": argument missing";
|
||||
arg = args[i + 1];
|
||||
arg2 = args[i + 2];
|
||||
arg3 = args[i + 3];
|
||||
i += 4;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_joined = [&](std::string name) {
|
||||
if (read_arg(name))
|
||||
return true;
|
||||
if (args[i].starts_with(name)) {
|
||||
arg = args[i].substr(name.size());
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_flag = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (args[i].starts_with('@')) {
|
||||
std::vector<std::string_view> vec =
|
||||
read_response_file(ctx, args[i].substr(1));
|
||||
args.erase(args.begin() + i);
|
||||
args.insert(args.begin() + i, vec.begin(), vec.end());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read_flag("-help") || read_flag("--help")) {
|
||||
SyncOut(ctx) << "Usage: " << ctx.cmdline_args[i]
|
||||
<< " [options] file...\n" << helpmsg;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (read_joined("-F")) {
|
||||
framework_paths.push_back(std::string(arg));
|
||||
} else if (read_joined("-L")) {
|
||||
library_paths.push_back(std::string(arg));
|
||||
} else if (read_flag("-Z")) {
|
||||
nostdlib = true;
|
||||
} else if (read_flag("-ObjC")) {
|
||||
ctx.arg.ObjC = true;
|
||||
} else if (read_flag("-adhoc_codesign")) {
|
||||
ctx.arg.adhoc_codesign = true;
|
||||
} else if (read_flag("-no_adhoc_codesign")) {
|
||||
ctx.arg.adhoc_codesign = false;
|
||||
} else if (read_arg("-arch")) {
|
||||
if (arg == "x86_64")
|
||||
ctx.arg.arch = CPU_TYPE_X86_64;
|
||||
else if (arg == "arm64")
|
||||
ctx.arg.arch = CPU_TYPE_ARM64;
|
||||
else
|
||||
Fatal(ctx) << "unknown -arch: " << arg;
|
||||
} else if (read_flag("-bundle")) {
|
||||
ctx.output_type = MH_BUNDLE;
|
||||
} else if (read_flag("-color-diagnostics") ||
|
||||
read_flag("--color-diagnostics")) {
|
||||
} else if (read_flag("-dead_strip")) {
|
||||
ctx.arg.dead_strip = true;
|
||||
} else if (read_flag("-dead_strip_dylibs")) {
|
||||
ctx.arg.dead_strip_dylibs = true;
|
||||
} else if (read_flag("-demangle")) {
|
||||
ctx.arg.demangle = true;
|
||||
} else if (read_flag("-dylib")) {
|
||||
ctx.output_type = MH_DYLIB;
|
||||
} else if (read_arg("-headerpad")) {
|
||||
size_t pos;
|
||||
ctx.arg.headerpad = std::stol(std::string(arg), &pos, 16);
|
||||
if (pos != arg.size())
|
||||
Fatal(ctx) << "malformed -headerpad: " << arg;
|
||||
} else if (read_flag("-headerpad_max_install_names")) {
|
||||
ctx.arg.headerpad = 1024;
|
||||
} else if (read_flag("-dynamic")) {
|
||||
ctx.arg.dynamic = true;
|
||||
} else if (read_arg("-e")) {
|
||||
ctx.arg.entry = arg;
|
||||
} else if (read_flag("-execute")) {
|
||||
ctx.output_type = MH_EXECUTE;
|
||||
} else if (read_arg("-fatal_warnings")) {
|
||||
} else if (read_arg("-filelist")) {
|
||||
remaining.push_back("-filelist");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-framework")) {
|
||||
remaining.push_back("-framework");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-lto_library")) {
|
||||
} else if (read_joined("-l")) {
|
||||
remaining.push_back("-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-map")) {
|
||||
ctx.arg.map = arg;
|
||||
} else if (read_joined("-needed-l")) {
|
||||
remaining.push_back("-needed-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-needed_framework")) {
|
||||
remaining.push_back("-needed_framework");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_flag("-no_deduplicate")) {
|
||||
} else if (read_arg("-o")) {
|
||||
ctx.arg.output = arg;
|
||||
} else if (read_arg("-pagezero_size")) {
|
||||
size_t pos;
|
||||
pagezero_size = std::stol(std::string(arg), &pos, 16);
|
||||
if (pos != arg.size())
|
||||
Fatal(ctx) << "malformed -pagezero_size: " << arg;
|
||||
} else if (read_arg3("-platform_version")) {
|
||||
ctx.arg.platform = parse_platform(ctx, arg);
|
||||
ctx.arg.platform_min_version = parse_version(ctx, arg2);
|
||||
ctx.arg.platform_sdk_version = parse_version(ctx, arg3);
|
||||
} else if (read_arg("-rpath")) {
|
||||
ctx.arg.rpath.push_back(std::string(arg));
|
||||
} else if (read_flag("-search_dylibs_first")) {
|
||||
Fatal(ctx) << "-search_dylibs_first is not supported";
|
||||
} else if (read_flag("-search_paths_first")) {
|
||||
} else if (read_arg("-syslibroot")) {
|
||||
ctx.arg.syslibroot.push_back(std::string(arg));
|
||||
} else if (read_flag("-t")) {
|
||||
ctx.arg.trace = true;
|
||||
} else if (read_flag("-v")) {
|
||||
SyncOut(ctx) << mold_version;
|
||||
} else {
|
||||
if (args[i][0] == '-')
|
||||
Fatal(ctx) << "unknown command line option: " << args[i];
|
||||
remaining.push_back(std::string(args[i]));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
auto add_search_path = [&](std::vector<std::string> &vec, std::string path) {
|
||||
if (!path.starts_with('/') || ctx.arg.syslibroot.empty()) {
|
||||
if (is_directory(path))
|
||||
vec.push_back(path);
|
||||
return;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (std::string &dir : ctx.arg.syslibroot) {
|
||||
std::string str = path_clean(dir + "/" + path);
|
||||
if (is_directory(str)) {
|
||||
vec.push_back(str);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found && is_directory(path))
|
||||
vec.push_back(path);
|
||||
};
|
||||
|
||||
for (std::string &path : library_paths)
|
||||
add_search_path(ctx.arg.library_paths, path);
|
||||
|
||||
if (!nostdlib) {
|
||||
add_search_path(ctx.arg.library_paths, "/usr/lib");
|
||||
add_search_path(ctx.arg.library_paths, "/usr/local/lib");
|
||||
}
|
||||
|
||||
for (std::string &path : framework_paths)
|
||||
add_search_path(ctx.arg.framework_paths, path);
|
||||
|
||||
if (!nostdlib) {
|
||||
add_search_path(ctx.arg.framework_paths, "/Library/Frameworks");
|
||||
add_search_path(ctx.arg.framework_paths, "/System/Library/Frameworks");
|
||||
}
|
||||
|
||||
if (pagezero_size.has_value()) {
|
||||
if (ctx.output_type != MH_EXECUTE)
|
||||
Error(ctx) << "-pagezero_size option can only be used when"
|
||||
<< " linking a main executable";
|
||||
ctx.arg.pagezero_size = *pagezero_size;
|
||||
} else {
|
||||
ctx.arg.pagezero_size = (ctx.output_type == MH_EXECUTE) ? 0x100000000 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void parse_nonpositional_args(Context<E> &, std::vector<std::string> &)
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,112 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
static std::vector<Subsection<E> *> collect_root_set(Context<E> &ctx) {
|
||||
std::vector<Subsection<E> *> rootset;
|
||||
|
||||
auto mark = [&](Symbol<E> *sym) {
|
||||
if (sym && sym->subsec)
|
||||
rootset.push_back(sym->subsec);
|
||||
};
|
||||
|
||||
mark(get_symbol(ctx, ctx.arg.entry));
|
||||
|
||||
if (ctx.output_type == MH_DYLIB || ctx.output_type == MH_BUNDLE)
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (Symbol<E> *sym : file->syms)
|
||||
if (sym->file == file && sym->is_extern)
|
||||
mark(sym);
|
||||
|
||||
return rootset;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void visit(Context<E> &ctx, Subsection<E> &subsec) {
|
||||
if (subsec.is_alive.exchange(true))
|
||||
return;
|
||||
|
||||
for (Relocation<E> &rel : subsec.get_rels()) {
|
||||
if (rel.sym) {
|
||||
if (rel.sym->subsec)
|
||||
visit(ctx, *rel.sym->subsec);
|
||||
} else {
|
||||
visit(ctx, *rel.subsec);
|
||||
}
|
||||
}
|
||||
|
||||
for (UnwindRecord<E> &rec : subsec.get_unwind_records()) {
|
||||
rec.is_alive = true;
|
||||
visit(ctx, *rec.subsec);
|
||||
if (rec.lsda)
|
||||
visit(ctx, *rec.lsda);
|
||||
if (Symbol<E> *sym = rec.personality; sym && sym->subsec)
|
||||
visit(ctx, *sym->subsec);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool refers_live_subsection(Subsection<E> &subsec) {
|
||||
for (Relocation<E> &rel : subsec.get_rels()) {
|
||||
if (rel.sym) {
|
||||
if (!rel.sym->subsec || rel.sym->subsec->is_alive)
|
||||
return true;
|
||||
} else {
|
||||
if (rel.subsec->is_alive)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void mark(Context<E> &ctx, const std::vector<Subsection<E> *> &rootset) {
|
||||
for (Subsection<E> *subsec : rootset)
|
||||
visit(ctx, *subsec);
|
||||
|
||||
bool repeat;
|
||||
do {
|
||||
repeat = false;
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
for (std::unique_ptr<Subsection<E>> &subsec : file->subsections) {
|
||||
if ((subsec->isec.hdr.attr & S_ATTR_LIVE_SUPPORT) &&
|
||||
!subsec->is_alive &&
|
||||
refers_live_subsection(*subsec)) {
|
||||
visit(ctx, *subsec);
|
||||
repeat = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (repeat);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void sweep(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
std::erase_if(file->subsections,
|
||||
[](const std::unique_ptr<Subsection<E>> &subsec) {
|
||||
return !subsec->is_alive;
|
||||
});
|
||||
}
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (Symbol<E> *&sym : file->syms)
|
||||
if (sym->file == file && sym->subsec && !sym->subsec->is_alive)
|
||||
sym = nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void dead_strip(Context<E> &ctx) {
|
||||
std::vector<Subsection<E> *> rootset = collect_root_set(ctx);
|
||||
mark(ctx, rootset);
|
||||
sweep(ctx);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void dead_strip(Context<E> &)
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
|
||||
} // namespace mold::macho
|
||||
+511
@@ -0,0 +1,511 @@
|
||||
#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,60 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
const MachSection &hdr)
|
||||
: file(file), hdr(hdr),
|
||||
osec(*OutputSection<E>::get_instance(ctx, hdr.get_segname(),
|
||||
hdr.get_sectname())) {
|
||||
if (hdr.type != S_ZEROFILL)
|
||||
contents = file.mf->get_contents().substr(hdr.offset, hdr.size);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::parse_relocations(Context<E> &ctx) {
|
||||
rels.reserve(hdr.nreloc);
|
||||
|
||||
// Parse mach-o relocations to fill `rels` vector
|
||||
rels = read_relocations(ctx, file, hdr);
|
||||
|
||||
// Sort `rels` vector
|
||||
sort(rels, [](const Relocation<E> &a, const Relocation<E> &b) {
|
||||
return a.offset < b.offset;
|
||||
});
|
||||
|
||||
// Find subsections for this section
|
||||
auto begin = std::lower_bound(
|
||||
file.subsections.begin(), file.subsections.end(), hdr.addr,
|
||||
[](std::unique_ptr<Subsection<E>> &subsec, u32 addr) {
|
||||
return subsec->input_addr < addr;
|
||||
});
|
||||
|
||||
auto end = std::lower_bound(
|
||||
begin, file.subsections.end(), hdr.addr + hdr.size,
|
||||
[](std::unique_ptr<Subsection<E>> &subsec, u32 addr) {
|
||||
return subsec->input_addr < addr;
|
||||
});
|
||||
|
||||
// Assign each subsection a group of relocations
|
||||
i64 i = 0;
|
||||
for (auto it = begin; it < end; it++) {
|
||||
Subsection<E> &subsec = **it;
|
||||
subsec.rel_offset = i;
|
||||
while (i < rels.size() &&
|
||||
rels[i].offset < subsec.input_offset + subsec.input_size) {
|
||||
rels[i].offset -= subsec.input_offset;
|
||||
i++;
|
||||
}
|
||||
subsec.nrels = i - subsec.rel_offset;
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class InputSection<E>
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
|
||||
} // namespace mold::macho
|
||||
+693
@@ -0,0 +1,693 @@
|
||||
#pragma once
|
||||
|
||||
#include "../big-endian.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string_view>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
|
||||
static constexpr u32 FAT_MAGIC = 0xcafebabe;
|
||||
|
||||
static constexpr u32 MH_OBJECT = 0x1;
|
||||
static constexpr u32 MH_EXECUTE = 0x2;
|
||||
static constexpr u32 MH_FVMLIB = 0x3;
|
||||
static constexpr u32 MH_CORE = 0x4;
|
||||
static constexpr u32 MH_PRELOAD = 0x5;
|
||||
static constexpr u32 MH_DYLIB = 0x6;
|
||||
static constexpr u32 MH_DYLINKER = 0x7;
|
||||
static constexpr u32 MH_BUNDLE = 0x8;
|
||||
static constexpr u32 MH_DYLIB_STUB = 0x9;
|
||||
static constexpr u32 MH_DSYM = 0xa;
|
||||
static constexpr u32 MH_KEXT_BUNDLE = 0xb;
|
||||
|
||||
static constexpr u32 MH_NOUNDEFS = 0x1;
|
||||
static constexpr u32 MH_INCRLINK = 0x2;
|
||||
static constexpr u32 MH_DYLDLINK = 0x4;
|
||||
static constexpr u32 MH_BINDATLOAD = 0x8;
|
||||
static constexpr u32 MH_PREBOUND = 0x10;
|
||||
static constexpr u32 MH_SPLIT_SEGS = 0x20;
|
||||
static constexpr u32 MH_LAZY_INIT = 0x40;
|
||||
static constexpr u32 MH_TWOLEVEL = 0x80;
|
||||
static constexpr u32 MH_FORCE_FLAT = 0x100;
|
||||
static constexpr u32 MH_NOMULTIDEFS = 0x200;
|
||||
static constexpr u32 MH_NOFIXPREBINDING = 0x400;
|
||||
static constexpr u32 MH_PREBINDABLE = 0x800;
|
||||
static constexpr u32 MH_ALLMODSBOUND = 0x1000;
|
||||
static constexpr u32 MH_SUBSECTIONS_VIA_SYMBOLS = 0x2000;
|
||||
static constexpr u32 MH_CANONICAL = 0x4000;
|
||||
static constexpr u32 MH_WEAK_DEFINES = 0x8000;
|
||||
static constexpr u32 MH_BINDS_TO_WEAK = 0x10000;
|
||||
static constexpr u32 MH_ALLOW_STACK_EXECUTION = 0x20000;
|
||||
static constexpr u32 MH_ROOT_SAFE = 0x40000;
|
||||
static constexpr u32 MH_SETUID_SAFE = 0x80000;
|
||||
static constexpr u32 MH_NO_REEXPORTED_DYLIBS = 0x100000;
|
||||
static constexpr u32 MH_PIE = 0x200000;
|
||||
static constexpr u32 MH_DEAD_STRIPPABLE_DYLIB = 0x400000;
|
||||
static constexpr u32 MH_HAS_TLV_DESCRIPTORS = 0x800000;
|
||||
static constexpr u32 MH_NO_HEAP_EXECUTION = 0x1000000;
|
||||
static constexpr u32 MH_APP_EXTENSION_SAFE = 0x02000000;
|
||||
static constexpr u32 MH_NLIST_OUTOFSYNC_WITH_DYLDINFO = 0x04000000;
|
||||
static constexpr u32 MH_SIM_SUPPORT = 0x08000000;
|
||||
|
||||
static constexpr u32 VM_PROT_READ = 0x1;
|
||||
static constexpr u32 VM_PROT_WRITE = 0x2;
|
||||
static constexpr u32 VM_PROT_EXECUTE = 0x4;
|
||||
static constexpr u32 VM_PROT_NO_CHANGE = 0x8;
|
||||
static constexpr u32 VM_PROT_COPY = 0x10;
|
||||
static constexpr u32 VM_PROT_WANTS_COPY = 0x10;
|
||||
|
||||
static constexpr u32 LC_REQ_DYLD = 0x80000000;
|
||||
|
||||
static constexpr u32 LC_SEGMENT = 0x1;
|
||||
static constexpr u32 LC_SYMTAB = 0x2;
|
||||
static constexpr u32 LC_SYMSEG = 0x3;
|
||||
static constexpr u32 LC_THREAD = 0x4;
|
||||
static constexpr u32 LC_UNIXTHREAD = 0x5;
|
||||
static constexpr u32 LC_LOADFVMLIB = 0x6;
|
||||
static constexpr u32 LC_IDFVMLIB = 0x7;
|
||||
static constexpr u32 LC_IDENT = 0x8;
|
||||
static constexpr u32 LC_FVMFILE = 0x9;
|
||||
static constexpr u32 LC_PREPAGE = 0xa;
|
||||
static constexpr u32 LC_DYSYMTAB = 0xb;
|
||||
static constexpr u32 LC_LOAD_DYLIB = 0xc;
|
||||
static constexpr u32 LC_ID_DYLIB = 0xd;
|
||||
static constexpr u32 LC_LOAD_DYLINKER = 0xe;
|
||||
static constexpr u32 LC_ID_DYLINKER = 0xf;
|
||||
static constexpr u32 LC_PREBOUND_DYLIB = 0x10;
|
||||
static constexpr u32 LC_ROUTINES = 0x11;
|
||||
static constexpr u32 LC_SUB_FRAMEWORK = 0x12;
|
||||
static constexpr u32 LC_SUB_UMBRELLA = 0x13;
|
||||
static constexpr u32 LC_SUB_CLIENT = 0x14;
|
||||
static constexpr u32 LC_SUB_LIBRARY = 0x15;
|
||||
static constexpr u32 LC_TWOLEVEL_HINTS = 0x16;
|
||||
static constexpr u32 LC_PREBIND_CKSUM = 0x17;
|
||||
static constexpr u32 LC_LOAD_WEAK_DYLIB = (0x18 | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_SEGMENT_64 = 0x19;
|
||||
static constexpr u32 LC_ROUTINES_64 = 0x1a;
|
||||
static constexpr u32 LC_UUID = 0x1b;
|
||||
static constexpr u32 LC_RPATH = (0x1c | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_CODE_SIGNATURE = 0x1d;
|
||||
static constexpr u32 LC_SEGMENT_SPLIT_INFO = 0x1e;
|
||||
static constexpr u32 LC_REEXPORT_DYLIB = (0x1f | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_LAZY_LOAD_DYLIB = 0x20;
|
||||
static constexpr u32 LC_ENCRYPTION_INFO = 0x21;
|
||||
static constexpr u32 LC_DYLD_INFO = 0x22;
|
||||
static constexpr u32 LC_DYLD_INFO_ONLY = (0x22 | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_LOAD_UPWARD_DYLIB = (0x23 | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_VERSION_MIN_MACOSX = 0x24;
|
||||
static constexpr u32 LC_VERSION_MIN_IPHONEOS = 0x25;
|
||||
static constexpr u32 LC_FUNCTION_STARTS = 0x26;
|
||||
static constexpr u32 LC_DYLD_ENVIRONMENT = 0x27;
|
||||
static constexpr u32 LC_MAIN = (0x28 | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_DATA_IN_CODE = 0x29;
|
||||
static constexpr u32 LC_SOURCE_VERSION = 0x2A;
|
||||
static constexpr u32 LC_DYLIB_CODE_SIGN_DRS = 0x2B;
|
||||
static constexpr u32 LC_ENCRYPTION_INFO_64 = 0x2C;
|
||||
static constexpr u32 LC_LINKER_OPTION = 0x2D;
|
||||
static constexpr u32 LC_LINKER_OPTIMIZATION_HINT = 0x2E;
|
||||
static constexpr u32 LC_VERSION_MIN_TVOS = 0x2F;
|
||||
static constexpr u32 LC_VERSION_MIN_WATCHOS = 0x30;
|
||||
static constexpr u32 LC_NOTE = 0x31;
|
||||
static constexpr u32 LC_BUILD_VERSION = 0x32;
|
||||
static constexpr u32 LC_DYLD_EXPORTS_TRIE = (0x33 | LC_REQ_DYLD);
|
||||
static constexpr u32 LC_DYLD_CHAINED_FIXUPS = (0x34 | LC_REQ_DYLD);
|
||||
|
||||
static constexpr u32 SG_HIGHVM = 0x1;
|
||||
static constexpr u32 SG_FVMLIB = 0x2;
|
||||
static constexpr u32 SG_NORELOC = 0x4;
|
||||
static constexpr u32 SG_PROTECTED_VERSION_1 = 0x8;
|
||||
static constexpr u32 SG_READ_ONLY = 0x10;
|
||||
|
||||
static constexpr u32 S_REGULAR = 0x0;
|
||||
static constexpr u32 S_ZEROFILL = 0x1;
|
||||
static constexpr u32 S_CSTRING_LITERALS = 0x2;
|
||||
static constexpr u32 S_4BYTE_LITERALS = 0x3;
|
||||
static constexpr u32 S_8BYTE_LITERALS = 0x4;
|
||||
static constexpr u32 S_LITERAL_POINTERS = 0x5;
|
||||
static constexpr u32 S_NON_LAZY_SYMBOL_POINTERS = 0x6;
|
||||
static constexpr u32 S_LAZY_SYMBOL_POINTERS = 0x7;
|
||||
static constexpr u32 S_SYMBOL_STUBS = 0x8;
|
||||
static constexpr u32 S_MOD_INIT_FUNC_POINTERS = 0x9;
|
||||
static constexpr u32 S_MOD_TERM_FUNC_POINTERS = 0xa;
|
||||
static constexpr u32 S_COALESCED = 0xb;
|
||||
static constexpr u32 S_GB_ZEROFILL = 0xc;
|
||||
static constexpr u32 S_INTERPOSING = 0xd;
|
||||
static constexpr u32 S_16BYTE_LITERALS = 0xe;
|
||||
static constexpr u32 S_DTRACE_DOF = 0xf;
|
||||
static constexpr u32 S_LAZY_DYLIB_SYMBOL_POINTERS = 0x10;
|
||||
static constexpr u32 S_THREAD_LOCAL_REGULAR = 0x11;
|
||||
static constexpr u32 S_THREAD_LOCAL_ZEROFILL = 0x12;
|
||||
static constexpr u32 S_THREAD_LOCAL_VARIABLES = 0x13;
|
||||
static constexpr u32 S_THREAD_LOCAL_VARIABLE_POINTERS = 0x14;
|
||||
static constexpr u32 S_THREAD_LOCAL_INIT_FUNCTION_POINTERS = 0x15;
|
||||
static constexpr u32 S_INIT_FUNC_OFFSETS = 0x16;
|
||||
|
||||
static constexpr u32 S_ATTR_LOC_RELOC = 0x000001;
|
||||
static constexpr u32 S_ATTR_EXT_RELOC = 0x000002;
|
||||
static constexpr u32 S_ATTR_SOME_INSTRUCTIONS = 0x000004;
|
||||
|
||||
static constexpr u32 S_ATTR_DEBUG = 0x020000;
|
||||
static constexpr u32 S_ATTR_SELF_MODIFYING_CODE = 0x040000;
|
||||
static constexpr u32 S_ATTR_LIVE_SUPPORT = 0x080000;
|
||||
static constexpr u32 S_ATTR_NO_DEAD_STRIP = 0x100000;
|
||||
static constexpr u32 S_ATTR_STRIP_STATIC_SYMS = 0x200000;
|
||||
static constexpr u32 S_ATTR_NO_TOC = 0x400000;
|
||||
static constexpr u32 S_ATTR_PURE_INSTRUCTIONS = 0x800000;
|
||||
|
||||
static constexpr u32 CPU_TYPE_X86_64 = 0x1000007;
|
||||
static constexpr u32 CPU_TYPE_ARM64 = 0x100000c;
|
||||
|
||||
static constexpr u32 CPU_SUBTYPE_X86_64_ALL = 3;
|
||||
static constexpr u32 CPU_SUBTYPE_ARM64_ALL = 0;
|
||||
|
||||
static constexpr u32 REBASE_TYPE_POINTER = 1;
|
||||
static constexpr u32 REBASE_TYPE_TEXT_ABSOLUTE32 = 2;
|
||||
static constexpr u32 REBASE_TYPE_TEXT_PCREL32 = 3;
|
||||
|
||||
static constexpr u32 REBASE_OPCODE_MASK = 0xf0;
|
||||
static constexpr u32 REBASE_IMMEDIATE_MASK = 0x0f;
|
||||
static constexpr u32 REBASE_OPCODE_DONE = 0x00;
|
||||
static constexpr u32 REBASE_OPCODE_SET_TYPE_IMM = 0x10;
|
||||
static constexpr u32 REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = 0x20;
|
||||
static constexpr u32 REBASE_OPCODE_ADD_ADDR_ULEB = 0x30;
|
||||
static constexpr u32 REBASE_OPCODE_ADD_ADDR_IMM_SCALED = 0x40;
|
||||
static constexpr u32 REBASE_OPCODE_DO_REBASE_IMM_TIMES = 0x50;
|
||||
static constexpr u32 REBASE_OPCODE_DO_REBASE_ULEB_TIMES = 0x60;
|
||||
static constexpr u32 REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB = 0x70;
|
||||
static constexpr u32 REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB = 0x80;
|
||||
|
||||
static constexpr u32 BIND_TYPE_POINTER = 1;
|
||||
static constexpr u32 BIND_TYPE_TEXT_ABSOLUTE32 = 2;
|
||||
static constexpr u32 BIND_TYPE_TEXT_PCREL32 = 3;
|
||||
|
||||
static constexpr u32 BIND_SPECIAL_DYLIB_SELF = 0;
|
||||
static constexpr u32 BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE = -1;
|
||||
static constexpr u32 BIND_SPECIAL_DYLIB_FLAT_LOOKUP = -2;
|
||||
static constexpr u32 BIND_SPECIAL_DYLIB_WEAK_LOOKUP = -3;
|
||||
|
||||
static constexpr u32 BIND_SYMBOL_FLAGS_WEAK_IMPORT = 0x1;
|
||||
static constexpr u32 BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION = 0x8;
|
||||
|
||||
static constexpr u32 BIND_OPCODE_MASK = 0xF0;
|
||||
static constexpr u32 BIND_IMMEDIATE_MASK = 0x0F;
|
||||
static constexpr u32 BIND_OPCODE_DONE = 0x00;
|
||||
static constexpr u32 BIND_OPCODE_SET_DYLIB_ORDINAL_IMM = 0x10;
|
||||
static constexpr u32 BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB = 0x20;
|
||||
static constexpr u32 BIND_OPCODE_SET_DYLIB_SPECIAL_IMM = 0x30;
|
||||
static constexpr u32 BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM = 0x40;
|
||||
static constexpr u32 BIND_OPCODE_SET_TYPE_IMM = 0x50;
|
||||
static constexpr u32 BIND_OPCODE_SET_ADDEND_SLEB = 0x60;
|
||||
static constexpr u32 BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = 0x70;
|
||||
static constexpr u32 BIND_OPCODE_ADD_ADDR_ULEB = 0x80;
|
||||
static constexpr u32 BIND_OPCODE_DO_BIND = 0x90;
|
||||
static constexpr u32 BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB = 0xA0;
|
||||
static constexpr u32 BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED = 0xB0;
|
||||
static constexpr u32 BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB = 0xC0;
|
||||
static constexpr u32 BIND_OPCODE_THREADED = 0xD0;
|
||||
static constexpr u32 BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB = 0x00;
|
||||
static constexpr u32 BIND_SUBOPCODE_THREADED_APPLY = 0x01;
|
||||
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_KIND_MASK = 0x03;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_KIND_REGULAR = 0x00;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL = 0x01;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE = 0x02;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION = 0x04;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_REEXPORT = 0x08;
|
||||
static constexpr u32 EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER = 0x10;
|
||||
|
||||
static constexpr u32 DICE_KIND_DATA = 1;
|
||||
static constexpr u32 DICE_KIND_JUMP_TABLE8 = 2;
|
||||
static constexpr u32 DICE_KIND_JUMP_TABLE16 = 3;
|
||||
static constexpr u32 DICE_KIND_JUMP_TABLE32 = 4;
|
||||
static constexpr u32 DICE_KIND_ABS_JUMP_TABLE32 = 5;
|
||||
|
||||
static constexpr u32 N_UNDF = 0;
|
||||
static constexpr u32 N_ABS = 1;
|
||||
static constexpr u32 N_INDR = 5;
|
||||
static constexpr u32 N_PBUD = 6;
|
||||
static constexpr u32 N_SECT = 7;
|
||||
|
||||
static constexpr u32 N_GSYM = 0x20;
|
||||
static constexpr u32 N_FNAME = 0x22;
|
||||
static constexpr u32 N_FUN = 0x24;
|
||||
static constexpr u32 N_STSYM = 0x26;
|
||||
static constexpr u32 N_LCSYM = 0x28;
|
||||
static constexpr u32 N_BNSYM = 0x2e;
|
||||
static constexpr u32 N_AST = 0x32;
|
||||
static constexpr u32 N_OPT = 0x3c;
|
||||
static constexpr u32 N_RSYM = 0x40;
|
||||
static constexpr u32 N_SLINE = 0x44;
|
||||
static constexpr u32 N_ENSYM = 0x4e;
|
||||
static constexpr u32 N_SSYM = 0x60;
|
||||
static constexpr u32 N_SO = 0x64;
|
||||
static constexpr u32 N_OSO = 0x66;
|
||||
static constexpr u32 N_LSYM = 0x80;
|
||||
static constexpr u32 N_BINCL = 0x82;
|
||||
static constexpr u32 N_SOL = 0x84;
|
||||
static constexpr u32 N_PARAMS = 0x86;
|
||||
static constexpr u32 N_VERSION = 0x88;
|
||||
static constexpr u32 N_OLEVEL = 0x8A;
|
||||
static constexpr u32 N_PSYM = 0xa0;
|
||||
static constexpr u32 N_EINCL = 0xa2;
|
||||
static constexpr u32 N_ENTRY = 0xa4;
|
||||
static constexpr u32 N_LBRAC = 0xc0;
|
||||
static constexpr u32 N_EXCL = 0xc2;
|
||||
static constexpr u32 N_RBRAC = 0xe0;
|
||||
static constexpr u32 N_BCOMM = 0xe2;
|
||||
static constexpr u32 N_ECOMM = 0xe4;
|
||||
static constexpr u32 N_ECOML = 0xe8;
|
||||
static constexpr u32 N_LENG = 0xfe;
|
||||
static constexpr u32 N_PC = 0x30;
|
||||
|
||||
static constexpr u32 REFERENCE_TYPE = 0xf;
|
||||
static constexpr u32 REFERENCE_FLAG_UNDEFINED_NON_LAZY = 0;
|
||||
static constexpr u32 REFERENCE_FLAG_UNDEFINED_LAZY = 1;
|
||||
static constexpr u32 REFERENCE_FLAG_DEFINED = 2;
|
||||
static constexpr u32 REFERENCE_FLAG_PRIVATE_DEFINED = 3;
|
||||
static constexpr u32 REFERENCE_FLAG_PRIVATE_UNDEFINED_NON_LAZY = 4;
|
||||
static constexpr u32 REFERENCE_FLAG_PRIVATE_UNDEFINED_LAZY = 5;
|
||||
|
||||
static constexpr u32 REFERENCED_DYNAMICALLY = 0x0010;
|
||||
|
||||
static constexpr u32 N_NO_DEAD_STRIP = 0x0020;
|
||||
static constexpr u32 N_DESC_DISCARDED = 0x0020;
|
||||
static constexpr u32 N_WEAK_REF = 0x0040;
|
||||
static constexpr u32 N_WEAK_DEF = 0x0080;
|
||||
static constexpr u32 N_REF_TO_WEAK = 0x0080;
|
||||
static constexpr u32 N_ARM_THUMB_DEF = 0x0008;
|
||||
static constexpr u32 N_SYMBOL_RESOLVER = 0x0100;
|
||||
static constexpr u32 N_ALT_ENTRY = 0x0200;
|
||||
|
||||
static constexpr u32 PLATFORM_MACOS = 1;
|
||||
static constexpr u32 PLATFORM_IOS = 2;
|
||||
static constexpr u32 PLATFORM_TVOS = 3;
|
||||
static constexpr u32 PLATFORM_WATCHOS = 4;
|
||||
static constexpr u32 PLATFORM_BRIDGEOS = 5;
|
||||
static constexpr u32 PLATFORM_MACCATALYST = 6;
|
||||
static constexpr u32 PLATFORM_IOSSIMULATOR = 7;
|
||||
static constexpr u32 PLATFORM_TVOSSIMULATOR = 8;
|
||||
static constexpr u32 PLATFORM_WATCHOSSIMULATOR = 9;
|
||||
static constexpr u32 PLATFORM_DRIVERKIT = 10;
|
||||
|
||||
static constexpr u32 TOOL_CLANG = 1;
|
||||
static constexpr u32 TOOL_SWIFT = 2;
|
||||
static constexpr u32 TOOL_LD = 3;
|
||||
|
||||
static constexpr u32 ARM64_RELOC_UNSIGNED = 0;
|
||||
static constexpr u32 ARM64_RELOC_SUBTRACTOR = 1;
|
||||
static constexpr u32 ARM64_RELOC_BRANCH26 = 2;
|
||||
static constexpr u32 ARM64_RELOC_PAGE21 = 3;
|
||||
static constexpr u32 ARM64_RELOC_PAGEOFF12 = 4;
|
||||
static constexpr u32 ARM64_RELOC_GOT_LOAD_PAGE21 = 5;
|
||||
static constexpr u32 ARM64_RELOC_GOT_LOAD_PAGEOFF12 = 6;
|
||||
static constexpr u32 ARM64_RELOC_POINTER_TO_GOT = 7;
|
||||
static constexpr u32 ARM64_RELOC_TLVP_LOAD_PAGE21 = 8;
|
||||
static constexpr u32 ARM64_RELOC_TLVP_LOAD_PAGEOFF12 = 9;
|
||||
static constexpr u32 ARM64_RELOC_ADDEND = 10;
|
||||
|
||||
static constexpr u32 X86_64_RELOC_UNSIGNED = 0;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED = 1;
|
||||
static constexpr u32 X86_64_RELOC_BRANCH = 2;
|
||||
static constexpr u32 X86_64_RELOC_GOT_LOAD = 3;
|
||||
static constexpr u32 X86_64_RELOC_GOT = 4;
|
||||
static constexpr u32 X86_64_RELOC_SUBTRACTOR = 5;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED_1 = 6;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED_2 = 7;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED_4 = 8;
|
||||
static constexpr u32 X86_64_RELOC_TLV = 9;
|
||||
|
||||
struct FatHeader {
|
||||
ubig32 magic;
|
||||
ubig32 nfat_arch;
|
||||
};
|
||||
|
||||
struct FatArch {
|
||||
ubig32 cputype;
|
||||
ubig32 cpusubtype;
|
||||
ubig32 offset;
|
||||
ubig32 size;
|
||||
ubig32 align;
|
||||
};
|
||||
|
||||
struct MachHeader {
|
||||
u32 magic;
|
||||
u32 cputype;
|
||||
u32 cpusubtype;
|
||||
u32 filetype;
|
||||
u32 ncmds;
|
||||
u32 sizeofcmds;
|
||||
u32 flags;
|
||||
u32 reserved;
|
||||
};
|
||||
|
||||
struct LoadCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
};
|
||||
|
||||
struct SegmentCommand {
|
||||
std::string_view get_segname() const {
|
||||
return {segname, strnlen(segname, sizeof(segname))};
|
||||
}
|
||||
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
char segname[16];
|
||||
u64 vmaddr;
|
||||
u64 vmsize;
|
||||
u64 fileoff;
|
||||
u64 filesize;
|
||||
u32 maxprot;
|
||||
u32 initprot;
|
||||
u32 nsects;
|
||||
u32 flags;
|
||||
};
|
||||
|
||||
struct MachSection {
|
||||
void set_segname(std::string_view name) {
|
||||
assert(name.size() <= sizeof(segname));
|
||||
memcpy(segname, name.data(), name.size());
|
||||
}
|
||||
|
||||
std::string_view get_segname() const {
|
||||
return {segname, strnlen(segname, sizeof(segname))};
|
||||
}
|
||||
|
||||
void set_sectname(std::string_view name) {
|
||||
assert(name.size() <= sizeof(sectname));
|
||||
memcpy(sectname, name.data(), name.size());
|
||||
}
|
||||
|
||||
std::string_view get_sectname() const {
|
||||
return {sectname, strnlen(sectname, sizeof(sectname))};
|
||||
}
|
||||
|
||||
bool match(std::string_view segname, std::string_view sectname) const {
|
||||
return get_segname() == segname && get_sectname() == sectname;
|
||||
}
|
||||
|
||||
char sectname[16];
|
||||
char segname[16];
|
||||
u64 addr;
|
||||
u64 size;
|
||||
u32 offset;
|
||||
u32 p2align;
|
||||
u32 reloff;
|
||||
u32 nreloc;
|
||||
u32 type : 8;
|
||||
u32 attr : 24;
|
||||
u32 reserved1;
|
||||
u32 reserved2;
|
||||
u32 reserved3;
|
||||
};
|
||||
|
||||
struct DylibCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 nameoff;
|
||||
u32 timestamp;
|
||||
u32 current_version;
|
||||
u32 compatibility_version;
|
||||
};
|
||||
|
||||
struct DylinkerCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 nameoff;
|
||||
};
|
||||
|
||||
struct SymtabCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 symoff;
|
||||
u32 nsyms;
|
||||
u32 stroff;
|
||||
u32 strsize;
|
||||
};
|
||||
|
||||
struct DysymtabCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 ilocalsym;
|
||||
u32 nlocalsym;
|
||||
u32 iextdefsym;
|
||||
u32 nextdefsym;
|
||||
u32 iundefsym;
|
||||
u32 nundefsym;
|
||||
u32 tocoff;
|
||||
u32 ntoc;
|
||||
u32 modtaboff;
|
||||
u32 nmodtab;
|
||||
u32 extrefsymoff;
|
||||
u32 nextrefsyms;
|
||||
u32 indirectsymoff;
|
||||
u32 nindirectsyms;
|
||||
u32 extreloff;
|
||||
u32 nextrel;
|
||||
u32 locreloff;
|
||||
u32 nlocrel;
|
||||
};
|
||||
|
||||
struct VersionMinCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 version;
|
||||
u32 sdk;
|
||||
};
|
||||
|
||||
struct DyldInfoCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 rebase_off;
|
||||
u32 rebase_size;
|
||||
u32 bind_off;
|
||||
u32 bind_size;
|
||||
u32 weak_bind_off;
|
||||
u32 weak_bind_size;
|
||||
u32 lazy_bind_off;
|
||||
u32 lazy_bind_size;
|
||||
u32 export_off;
|
||||
u32 export_size;
|
||||
};
|
||||
|
||||
struct UUIDCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u8 uuid[16];
|
||||
};
|
||||
|
||||
struct RpathCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 path_off;
|
||||
};
|
||||
|
||||
struct LinkEditDataCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 dataoff;
|
||||
u32 datasize;
|
||||
};
|
||||
|
||||
struct BuildToolVersion {
|
||||
u32 tool;
|
||||
u32 version;
|
||||
};
|
||||
|
||||
struct BuildVersionCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 platform;
|
||||
u32 minos;
|
||||
u32 sdk;
|
||||
u32 ntools;
|
||||
};
|
||||
|
||||
struct EntryPointCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u64 entryoff;
|
||||
u64 stacksize;
|
||||
};
|
||||
|
||||
struct SourceVersionCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u64 version;
|
||||
};
|
||||
|
||||
struct DataInCodeEntry {
|
||||
u32 offset;
|
||||
u16 length;
|
||||
u16 kind;
|
||||
};
|
||||
|
||||
// This struct is named `n_list` on BSD and macOS.
|
||||
struct MachSym {
|
||||
bool is_undef() const {
|
||||
return type == N_UNDF && !is_common();
|
||||
}
|
||||
|
||||
bool is_common() const {
|
||||
return type == N_UNDF && ext && value;
|
||||
}
|
||||
|
||||
u32 stroff;
|
||||
u8 ext : 1;
|
||||
u8 type : 3;
|
||||
u8 pext : 1;
|
||||
u8 stub : 3;
|
||||
u8 sect;
|
||||
union {
|
||||
u16 desc;
|
||||
struct {
|
||||
u16 : 8;
|
||||
u16 p2align : 4;
|
||||
u16 : 4;
|
||||
};
|
||||
};
|
||||
u64 value;
|
||||
};
|
||||
|
||||
// This struct is named `relocation_info` on BSD and macOS.
|
||||
struct MachRel {
|
||||
u32 offset;
|
||||
u32 idx : 24;
|
||||
u32 is_pcrel : 1;
|
||||
u32 p2size : 2;
|
||||
u32 is_extern : 1;
|
||||
u32 type : 4;
|
||||
};
|
||||
|
||||
// __TEXT,__unwind_info section contents
|
||||
|
||||
static constexpr u32 UNWIND_SECTION_VERSION = 1;
|
||||
static constexpr u32 UNWIND_SECOND_LEVEL_REGULAR = 2;
|
||||
static constexpr u32 UNWIND_SECOND_LEVEL_COMPRESSED = 3;
|
||||
static constexpr u32 UNWIND_PERSONALITY_MASK = 0x30000000;
|
||||
|
||||
struct UnwindSectionHeader {
|
||||
u32 version;
|
||||
u32 encoding_offset;
|
||||
u32 encoding_count;
|
||||
u32 personality_offset;
|
||||
u32 personality_count;
|
||||
u32 page_offset;
|
||||
u32 page_count;
|
||||
};
|
||||
|
||||
struct UnwindFirstLevelPage {
|
||||
u32 func_addr;
|
||||
u32 page_offset;
|
||||
u32 lsda_offset;
|
||||
};
|
||||
|
||||
struct UnwindSecondLevelPage {
|
||||
u32 kind;
|
||||
u16 page_offset;
|
||||
u16 page_count;
|
||||
u16 encoding_offset;
|
||||
u16 encoding_count;
|
||||
};
|
||||
|
||||
struct UnwindLsdaEntry {
|
||||
u32 func_addr;
|
||||
u32 lsda_addr;
|
||||
};
|
||||
|
||||
struct UnwindPageEntry {
|
||||
u32 func_addr : 24;
|
||||
u32 encoding : 8;
|
||||
};
|
||||
|
||||
// __LD,__compact_unwind section contents
|
||||
|
||||
struct CompactUnwindEntry {
|
||||
u64 code_start;
|
||||
u32 code_len;
|
||||
u32 encoding;
|
||||
u64 personality;
|
||||
u64 lsda;
|
||||
};
|
||||
|
||||
// __LINKEDIT,__code_signature
|
||||
|
||||
static constexpr u32 CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0;
|
||||
static constexpr u32 CS_SUPPORTSEXECSEG = 0x20400;
|
||||
static constexpr u32 CSMAGIC_CODEDIRECTORY = 0xfade0c02;
|
||||
static constexpr u32 CSSLOT_CODEDIRECTORY = 0;
|
||||
static constexpr u32 CS_ADHOC = 0x00000002;
|
||||
static constexpr u32 CS_LINKER_SIGNED = 0x00020000;
|
||||
static constexpr u32 CS_EXECSEG_MAIN_BINARY = 1;
|
||||
static constexpr u32 CS_HASHTYPE_SHA256 = 2;
|
||||
|
||||
struct CodeSignatureHeader {
|
||||
ubig32 magic;
|
||||
ubig32 length;
|
||||
ubig32 count;
|
||||
};
|
||||
|
||||
struct CodeSignatureBlobIndex {
|
||||
ubig32 type;
|
||||
ubig32 offset;
|
||||
u32 padding;
|
||||
};
|
||||
|
||||
struct CodeSignatureDirectory {
|
||||
ubig32 magic;
|
||||
ubig32 length;
|
||||
ubig32 version;
|
||||
ubig32 flags;
|
||||
ubig32 hash_offset;
|
||||
ubig32 ident_offset;
|
||||
ubig32 n_special_slots;
|
||||
ubig32 n_code_slots;
|
||||
ubig32 code_limit;
|
||||
u8 hash_size;
|
||||
u8 hash_type;
|
||||
u8 platform;
|
||||
u8 page_size;
|
||||
ubig32 spare2;
|
||||
ubig32 scatter_offset;
|
||||
ubig32 team_offset;
|
||||
ubig32 spare3;
|
||||
ubig64 code_limit64;
|
||||
ubig64 exec_seg_base;
|
||||
ubig64 exec_seg_limit;
|
||||
ubig64 exec_seg_flags;
|
||||
};
|
||||
|
||||
struct ARM64 {
|
||||
static constexpr u32 cputype = CPU_TYPE_ARM64;
|
||||
static constexpr u32 cpusubtype = CPU_SUBTYPE_ARM64_ALL;
|
||||
static constexpr u32 abs_rel = ARM64_RELOC_UNSIGNED;
|
||||
static constexpr u32 word_size = 8;
|
||||
static constexpr u32 stub_size = 12;
|
||||
static constexpr u32 stub_helper_hdr_size = 24;
|
||||
static constexpr u32 stub_helper_size = 12;
|
||||
};
|
||||
|
||||
struct X86_64 {
|
||||
static constexpr u32 cputype = CPU_TYPE_X86_64;
|
||||
static constexpr u32 cpusubtype = CPU_SUBTYPE_X86_64_ALL;
|
||||
static constexpr u32 abs_rel = X86_64_RELOC_UNSIGNED;
|
||||
static constexpr u32 word_size = 8;
|
||||
static constexpr u32 stub_size = 6;
|
||||
static constexpr u32 stub_helper_hdr_size = 16;
|
||||
static constexpr u32 stub_helper_size = 10;
|
||||
};
|
||||
|
||||
} // namespace mold::macho
|
||||
+489
@@ -0,0 +1,489 @@
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
#include "../cmdline.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
static std::pair<std::string_view, std::string_view>
|
||||
split_string(std::string_view str, char sep) {
|
||||
size_t pos = str.find(sep);
|
||||
if (pos == str.npos)
|
||||
return {str, ""};
|
||||
return {str.substr(0, pos), str.substr(pos + 1)};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void create_internal_file(Context<E> &ctx) {
|
||||
ObjectFile<E> *obj = new ObjectFile<E>;
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
ctx.objs.push_back(obj);
|
||||
|
||||
auto add = [&](std::string_view name) {
|
||||
Symbol<E> *sym = get_symbol(ctx, name);
|
||||
sym->file = obj;
|
||||
obj->syms.push_back(sym);
|
||||
return sym;
|
||||
};
|
||||
|
||||
add("__dyld_private");
|
||||
|
||||
switch (ctx.output_type) {
|
||||
case MH_EXECUTE: {
|
||||
Symbol<E> *sym = add("__mh_execute_header");
|
||||
sym->is_extern = true;
|
||||
sym->referenced_dynamically = true;
|
||||
sym->value = ctx.arg.pagezero_size;
|
||||
break;
|
||||
}
|
||||
case MH_DYLIB:
|
||||
add("__mh_dylib_header");
|
||||
break;
|
||||
case MH_BUNDLE:
|
||||
add("__mh_bundle_header");
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool compare_segments(const std::unique_ptr<OutputSegment<E>> &a,
|
||||
const std::unique_ptr<OutputSegment<E>> &b) {
|
||||
// We want to sort output segments in the following order:
|
||||
// __TEXT, __DATA_CONST, __DATA, <other segments>, __LINKEDIT
|
||||
auto get_rank = [](std::string_view name) {
|
||||
if (name == "__TEXT")
|
||||
return 0;
|
||||
if (name == "__DATA_CONST")
|
||||
return 1;
|
||||
if (name == "__DATA")
|
||||
return 2;
|
||||
return INT_MAX;
|
||||
};
|
||||
|
||||
std::string_view na = a->cmd.get_segname();
|
||||
std::string_view nb = b->cmd.get_segname();
|
||||
i64 ra = get_rank(na);
|
||||
i64 rb = get_rank(nb);
|
||||
if (ra != INT_MAX || rb != INT_MAX)
|
||||
return ra < rb;
|
||||
|
||||
if (na == "__LINKEDIT" || nb == "__LINKEDIT")
|
||||
return na != "__LINKEDIT";
|
||||
return na < nb;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool compare_chunks(const Chunk<E> *a, const Chunk<E> *b) {
|
||||
assert(a->hdr.get_segname() == b->hdr.get_segname());
|
||||
|
||||
if ((a->hdr.type == S_ZEROFILL) != (b->hdr.type == S_ZEROFILL))
|
||||
return a->hdr.type != S_ZEROFILL;
|
||||
|
||||
static const std::string_view rank[] = {
|
||||
// __TEXT
|
||||
"__mach_header",
|
||||
"__text",
|
||||
"__stubs",
|
||||
"__stub_helper",
|
||||
"__gcc_except_tab",
|
||||
"__cstring",
|
||||
"__unwind_info",
|
||||
// __DATA_CONST
|
||||
"__got",
|
||||
// __DATA
|
||||
"__la_symbol_ptr",
|
||||
"__data",
|
||||
"__thread_ptr",
|
||||
"__thread_data",
|
||||
"__thread_vars",
|
||||
"__thread_bss",
|
||||
"__common",
|
||||
"__bss",
|
||||
// __LINKEDIT
|
||||
"__rebase",
|
||||
"__binding",
|
||||
"__weak_binding",
|
||||
"__lazy_binding",
|
||||
"__export",
|
||||
"__func_starts",
|
||||
"__data_in_code",
|
||||
"__symbol_table",
|
||||
"__ind_sym_tab",
|
||||
"__string_table",
|
||||
"__code_signature",
|
||||
};
|
||||
|
||||
auto get_rank = [](const Chunk<E> *chunk) -> i64 {
|
||||
std::string_view name = chunk->hdr.get_sectname();
|
||||
i64 i = 0;
|
||||
for (; i < sizeof(rank) / sizeof(rank[0]); i++)
|
||||
if (name == rank[i])
|
||||
return i;
|
||||
return INT_MAX;
|
||||
};
|
||||
|
||||
i64 ra = get_rank(a);
|
||||
i64 rb = get_rank(b);
|
||||
if (ra == INT_MAX && rb == INT_MAX)
|
||||
return a->hdr.get_sectname() < b->hdr.get_sectname();
|
||||
return ra < rb;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void create_synthetic_chunks(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (std::unique_ptr<Subsection<E>> &subsec : file->subsections)
|
||||
subsec->isec.osec.add_subsec(subsec.get());
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (chunk != ctx.data && chunk->is_regular &&
|
||||
((OutputSection<E> *)chunk)->members.empty())
|
||||
continue;
|
||||
|
||||
OutputSegment<E> *seg =
|
||||
OutputSegment<E>::get_instance(ctx, chunk->hdr.get_segname());
|
||||
seg->chunks.push_back(chunk);
|
||||
}
|
||||
|
||||
sort(ctx.segments, compare_segments<E>);
|
||||
|
||||
for (std::unique_ptr<OutputSegment<E>> &seg : ctx.segments)
|
||||
sort(seg->chunks, compare_chunks<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void scan_unwind_info(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (UnwindRecord<E> &rec : file->unwind_records)
|
||||
if (!ctx.arg.dead_strip || rec.is_alive)
|
||||
if (rec.personality)
|
||||
rec.personality->flags |= NEEDS_GOT;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void export_symbols(Context<E> &ctx) {
|
||||
ctx.got.add(ctx, get_symbol(ctx, "dyld_stub_binder"));
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
for (Symbol<E> *sym : file->syms) {
|
||||
if (sym && sym->file == file) {
|
||||
if (sym->flags & NEEDS_GOT)
|
||||
ctx.got.add(ctx, sym);
|
||||
if (sym->flags & NEEDS_THREAD_PTR)
|
||||
ctx.thread_ptrs.add(ctx, sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (DylibFile<E> *file : ctx.dylibs) {
|
||||
for (Symbol<E> *sym : file->syms) {
|
||||
if (sym && sym->file == file) {
|
||||
if (sym->flags & NEEDS_STUB)
|
||||
ctx.stubs.add(ctx, sym);
|
||||
if (sym->flags & NEEDS_GOT)
|
||||
ctx.got.add(ctx, sym);
|
||||
if (sym->flags & NEEDS_THREAD_PTR)
|
||||
ctx.thread_ptrs.add(ctx, sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 assign_offsets(Context<E> &ctx) {
|
||||
i64 sect_idx = 1;
|
||||
for (std::unique_ptr<OutputSegment<E>> &seg : ctx.segments)
|
||||
for (Chunk<E> *chunk : seg->chunks)
|
||||
if (!chunk->is_hidden)
|
||||
chunk->sect_idx = sect_idx++;
|
||||
|
||||
i64 fileoff = 0;
|
||||
i64 vmaddr = ctx.arg.pagezero_size;
|
||||
|
||||
for (std::unique_ptr<OutputSegment<E>> &seg : ctx.segments) {
|
||||
seg->set_offset(ctx, fileoff, vmaddr);
|
||||
fileoff += seg->cmd.filesize;
|
||||
vmaddr += seg->cmd.vmsize;
|
||||
}
|
||||
return fileoff;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void fix_synthetic_symbol_values(Context<E> &ctx) {
|
||||
get_symbol(ctx, "__dyld_private")->value = ctx.data->hdr.addr;
|
||||
get_symbol(ctx, "__mh_dylib_header")->value = ctx.data->hdr.addr;
|
||||
get_symbol(ctx, "__mh_bundle_header")->value = ctx.data->hdr.addr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *find_framework(Context<E> &ctx, std::string name) {
|
||||
std::string suffix;
|
||||
std::tie(name, suffix) = split_string(name, ',');
|
||||
|
||||
for (std::string path : ctx.arg.framework_paths) {
|
||||
path = get_realpath(path + "/" + name + ".framework/" + name);
|
||||
|
||||
if (!suffix.empty())
|
||||
if (auto *mf = MappedFile<Context<E>>::open(ctx, path + suffix))
|
||||
return mf;
|
||||
|
||||
if (auto *mf = MappedFile<Context<E>>::open(ctx, path + ".tbd"))
|
||||
return mf;
|
||||
|
||||
if (auto *mf = MappedFile<Context<E>>::open(ctx, path))
|
||||
return mf;
|
||||
}
|
||||
Fatal(ctx) << "-framework not found: " << name;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *find_library(Context<E> &ctx, std::string name) {
|
||||
for (std::string dir : ctx.arg.library_paths) {
|
||||
for (std::string ext : {".tbd", ".dylib", ".a"}) {
|
||||
std::string path = dir + "/lib" + name + ext;
|
||||
if (MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, path))
|
||||
return mf;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static MappedFile<Context<E>> *
|
||||
strip_universal_header(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
FatHeader &hdr = *(FatHeader *)mf->data;
|
||||
assert(hdr.magic == FAT_MAGIC);
|
||||
|
||||
FatArch *arch = (FatArch *)(mf->data + sizeof(hdr));
|
||||
for (i64 i = 0; i < hdr.nfat_arch; i++)
|
||||
if (arch[i].cputype == E::cputype)
|
||||
return mf->slice(ctx, mf->name, arch[i].offset, arch[i].size);
|
||||
Fatal(ctx) << mf->name << ": fat file contains no matching file";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
bool is_needed = false) {
|
||||
if (get_file_type(mf) == FileType::MACH_UNIVERSAL)
|
||||
mf = strip_universal_header(ctx, mf);
|
||||
|
||||
switch (get_file_type(mf)) {
|
||||
case FileType::TAPI:
|
||||
case FileType::MACH_DYLIB:
|
||||
ctx.dylibs.push_back(DylibFile<E>::create(ctx, mf));
|
||||
if (is_needed || !ctx.arg.dead_strip_dylibs)
|
||||
ctx.dylibs.back()->is_needed = true;
|
||||
break;
|
||||
case FileType::MACH_OBJ:
|
||||
ctx.objs.push_back(ObjectFile<E>::create(ctx, mf, ""));
|
||||
break;
|
||||
case FileType::AR:
|
||||
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf))
|
||||
if (get_file_type(child) == FileType::MACH_OBJ)
|
||||
ctx.objs.push_back(ObjectFile<E>::create(ctx, child, mf->name));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
// Fatal(ctx) << mf->name << ": unknown file type";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<std::string>
|
||||
read_filelist(Context<E> &ctx, std::string arg) {
|
||||
std::string path;
|
||||
std::string dir;
|
||||
|
||||
if (size_t pos = arg.find(','); pos != arg.npos) {
|
||||
path = arg.substr(0, pos);
|
||||
dir = arg.substr(pos + 1) + "/";
|
||||
} else {
|
||||
path = arg;
|
||||
}
|
||||
|
||||
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, path);
|
||||
if (!mf)
|
||||
Fatal(ctx) << "-filepath: cannot open " << path;
|
||||
|
||||
std::vector<std::string> vec;
|
||||
for (std::string_view str = mf->get_contents(); !str.empty();) {
|
||||
std::string_view path;
|
||||
std::tie(path, str) = split_string(str, '\n');
|
||||
vec.push_back(path_clean(dir + std::string(path)));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
while (!args.empty()) {
|
||||
if (args[0].starts_with("-filelist")) {
|
||||
for (std::string &path : read_filelist(ctx, args[1])) {
|
||||
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, path);
|
||||
if (!mf)
|
||||
Fatal(ctx) << "-filepath " << args[1] << ": cannot open file: " << path;
|
||||
read_file(ctx, mf);
|
||||
}
|
||||
args = args.subspan(2);
|
||||
} else if (args[0] == "-framework" || args[0] == "-needed_framework") {
|
||||
bool needed = (args[0] == "-needed_framework");
|
||||
read_file(ctx, find_framework(ctx, args[1]), needed);
|
||||
args = args.subspan(2);
|
||||
} else if (args[0] == "-l" || args[0] == "-needed-l") {
|
||||
MappedFile<Context<E>> *mf = find_library(ctx, args[1]);
|
||||
if (!mf)
|
||||
Fatal(ctx) << "library not found: -l" << args[1];
|
||||
bool needed = (args[0] == "-needed-l");
|
||||
read_file(ctx, mf, needed);
|
||||
args = args.subspan(2);
|
||||
} else {
|
||||
read_file(ctx, MappedFile<Context<E>>::must_open(ctx, args[0]));
|
||||
args = args.subspan(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.objs.empty())
|
||||
Fatal(ctx) << "no input files";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static int do_main(int argc, char **argv) {
|
||||
Context<E> ctx;
|
||||
|
||||
if (argc > 1 && std::string_view(argv[1]) == "-dump") {
|
||||
if (argc != 3)
|
||||
Fatal(ctx) << "usage: ld64.mold -dump <executable-name>\n";
|
||||
dump_file(argv[2]);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
for (i64 i = 0; i < argc; i++)
|
||||
ctx.cmdline_args.push_back(argv[i]);
|
||||
|
||||
std::vector<std::string> file_args;
|
||||
parse_nonpositional_args(ctx, file_args);
|
||||
|
||||
if (ctx.arg.arch != E::cputype) {
|
||||
if (ctx.arg.arch == CPU_TYPE_X86_64)
|
||||
return do_main<X86_64>(argc, argv);
|
||||
}
|
||||
|
||||
read_input_files(ctx, file_args);
|
||||
|
||||
i64 priority = 1;
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->priority = priority++;
|
||||
for (DylibFile<E> *dylib : ctx.dylibs)
|
||||
dylib->priority = priority++;
|
||||
|
||||
for (i64 i = 0; i < ctx.dylibs.size(); i++)
|
||||
ctx.dylibs[i]->dylib_idx = i + 1;
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->parse(ctx);
|
||||
for (DylibFile<E> *dylib : ctx.dylibs)
|
||||
dylib->parse(ctx);
|
||||
|
||||
if (ctx.arg.ObjC)
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (!file->archive_name.empty() && file->is_objc_object(ctx))
|
||||
file->is_alive = true;
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file->is_alive)
|
||||
file->resolve_regular_symbols(ctx);
|
||||
else
|
||||
file->resolve_lazy_symbols(ctx);
|
||||
}
|
||||
|
||||
std::vector<ObjectFile<E> *> live_objs;
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->is_alive)
|
||||
live_objs.push_back(file);
|
||||
|
||||
for (i64 i = 0; i < live_objs.size(); i++)
|
||||
append(live_objs, live_objs[i]->mark_live_objects(ctx));
|
||||
|
||||
for (DylibFile<E> *dylib : ctx.dylibs)
|
||||
dylib->resolve_symbols(ctx);
|
||||
|
||||
if (ctx.output_type == MH_EXECUTE && !get_symbol(ctx, ctx.arg.entry)->file)
|
||||
Error(ctx) << "undefined entry point symbol: " << ctx.arg.entry;
|
||||
|
||||
create_internal_file(ctx);
|
||||
|
||||
std::erase_if(ctx.objs, [](ObjectFile<E> *file) { return !file->is_alive; });
|
||||
std::erase_if(ctx.dylibs, [](DylibFile<E> *file) { return !file->is_alive; });
|
||||
|
||||
if (ctx.arg.trace) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
SyncOut(ctx) << *file;
|
||||
for (DylibFile<E> *file : ctx.dylibs)
|
||||
SyncOut(ctx) << *file;
|
||||
}
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->convert_common_symbols(ctx);
|
||||
|
||||
if (ctx.arg.dead_strip)
|
||||
dead_strip(ctx);
|
||||
|
||||
create_synthetic_chunks(ctx);
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->check_duplicate_symbols(ctx);
|
||||
|
||||
for (i64 i = 0; i < ctx.segments.size(); i++)
|
||||
ctx.segments[i]->seg_idx = i + 1;
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (std::unique_ptr<Subsection<E> > &subsec : file->subsections)
|
||||
subsec->scan_relocations(ctx);
|
||||
|
||||
scan_unwind_info(ctx);
|
||||
|
||||
if (ctx.arg.dead_strip_dylibs)
|
||||
std::erase_if(ctx.dylibs, [](DylibFile<E> *file) { return !file->is_needed; });
|
||||
|
||||
export_symbols(ctx);
|
||||
i64 output_size = assign_offsets(ctx);
|
||||
fix_synthetic_symbol_values(ctx);
|
||||
|
||||
ctx.output_file = OutputFile<E>::open(ctx, ctx.arg.output, output_size, 0777);
|
||||
ctx.buf = ctx.output_file->buf;
|
||||
|
||||
for (std::unique_ptr<OutputSegment<E>> &seg : ctx.segments)
|
||||
seg->copy_buf(ctx);
|
||||
ctx.code_sig.write_signature(ctx);
|
||||
|
||||
ctx.output_file->close(ctx);
|
||||
ctx.checkpoint();
|
||||
|
||||
if (!ctx.arg.map.empty())
|
||||
print_map(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (!getenv("MOLD_SUPPRESS_MACHO_WARNING")) {
|
||||
std::cerr <<
|
||||
R"(********************************************************************************
|
||||
mold for macOS is pre-alpha. Do not use unless you know what you are doing.
|
||||
Do not report bugs because it's too early to manage missing features as bugs.
|
||||
********************************************************************************
|
||||
)";
|
||||
}
|
||||
|
||||
return do_main<ARM64>(argc, argv);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
struct Sym {
|
||||
u64 addr;
|
||||
u32 size;
|
||||
u32 fileidx;
|
||||
std::string_view name;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void print_map(Context<E> &ctx) {
|
||||
std::ofstream out(ctx.arg.map.c_str());
|
||||
if (!out.is_open())
|
||||
Fatal(ctx) << "cannot open " << ctx.arg.map << ": " << errno_string();
|
||||
|
||||
out << "# Path: " << ctx.arg.output << "\n"
|
||||
<< "# Arch: x86-64\n"
|
||||
<< "# Object files:\n";
|
||||
|
||||
std::vector<Sym> syms;
|
||||
|
||||
for (i64 i = 0; i < ctx.objs.size(); i++){
|
||||
ObjectFile<E> &file = *ctx.objs[i];
|
||||
|
||||
if (file.is_alive) {
|
||||
out << "[" << std::setw(3) << i << "] " << file << "\n";
|
||||
for (Symbol<E> *sym : file.syms)
|
||||
if (sym->file == &file)
|
||||
syms.push_back({sym->get_addr(ctx), 0, (u32)i, sym->name});
|
||||
}
|
||||
}
|
||||
|
||||
sort(syms, [](const Sym &a, const Sym &b) {
|
||||
return a.addr < b.addr;
|
||||
});
|
||||
|
||||
out << "# Sections:\n"
|
||||
<< "# Address Size Segment Section\n";
|
||||
|
||||
for (std::unique_ptr<OutputSegment<E>> &seg : ctx.segments) {
|
||||
for (Chunk<E> *chunk : seg->chunks) {
|
||||
if (!chunk->is_hidden) {
|
||||
out << "0x" << std::hex
|
||||
<< std::setw(8) << std::setfill('0') << chunk->hdr.addr
|
||||
<< " 0x"
|
||||
<< std::setw(8) << std::setfill('0') << chunk->hdr.size
|
||||
<< " " << std::setw(7) << std::left << std::setfill(' ')
|
||||
<< chunk->hdr.get_segname()
|
||||
<< " " << chunk->hdr.get_sectname() << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out << "# Symbols:\n"
|
||||
<< "# Address Size File Name\n";
|
||||
|
||||
for (Sym &sym : syms) {
|
||||
out << "0x" << std::hex
|
||||
<< std::setw(8) << std::setfill('0') << sym.addr
|
||||
<< " 0x"
|
||||
<< std::setw(8) << std::setfill('0') << 0
|
||||
<< " ["
|
||||
<< std::setw(3) << std::right << std::setfill(' ') << std::dec
|
||||
<< sym.fileidx
|
||||
<< "] " << sym.name << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void print_map(Context<E> &)
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
|
||||
} // namespace mold::macho
|
||||
+920
@@ -0,0 +1,920 @@
|
||||
#pragma once
|
||||
|
||||
#include "macho.h"
|
||||
#include "../mold.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <tbb/concurrent_hash_map.h>
|
||||
#include <tbb/spin_mutex.h>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
static constexpr i64 COMMON_PAGE_SIZE = 0x4000;
|
||||
static constexpr i64 SHA256_SIZE = 32;
|
||||
|
||||
template <typename E> class Chunk;
|
||||
template <typename E> class InputSection;
|
||||
template <typename E> class OutputSection;
|
||||
template <typename E> class Subsection;
|
||||
template <typename E> struct Context;
|
||||
template <typename E> struct Symbol;
|
||||
|
||||
//
|
||||
// object-file.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
struct Relocation {
|
||||
u32 offset = 0;
|
||||
u8 type = -1;
|
||||
u8 p2size = 0;
|
||||
bool is_pcrel = false;
|
||||
i64 addend = 0;
|
||||
Symbol<E> *sym = nullptr;
|
||||
Subsection<E> *subsec = nullptr;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct UnwindRecord {
|
||||
UnwindRecord(u32 len, u32 enc) : code_len(len), encoding(enc) {}
|
||||
|
||||
inline u64 get_func_raddr(Context<E> &ctx) const;
|
||||
|
||||
Subsection<E> *subsec = nullptr;
|
||||
u32 offset = 0;
|
||||
u32 code_len;
|
||||
u32 encoding;
|
||||
Symbol<E> *personality = nullptr;
|
||||
Subsection<E> *lsda = nullptr;
|
||||
u32 lsda_offset = 0;
|
||||
bool is_alive = false;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class InputFile {
|
||||
public:
|
||||
virtual ~InputFile() = default;
|
||||
|
||||
MappedFile<Context<E>> *mf = nullptr;
|
||||
std::vector<Symbol<E> *> syms;
|
||||
i64 priority = 0;
|
||||
std::atomic_bool is_alive = false;
|
||||
std::string archive_name;
|
||||
|
||||
virtual bool is_dylib() const = 0;
|
||||
|
||||
protected:
|
||||
InputFile() = default;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class ObjectFile : public InputFile<E> {
|
||||
public:
|
||||
ObjectFile() = default;
|
||||
|
||||
static ObjectFile *create(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name);
|
||||
bool is_dylib() const override { return false; }
|
||||
void parse(Context<E> &ctx);
|
||||
Subsection<E> *find_subsection(Context<E> &ctx, u32 addr);
|
||||
void parse_compact_unwind(Context<E> &ctx, MachSection &hdr);
|
||||
void resolve_regular_symbols(Context<E> &ctx);
|
||||
void resolve_lazy_symbols(Context<E> &ctx);
|
||||
bool is_objc_object(Context<E> &ctx);
|
||||
std::vector<ObjectFile *> mark_live_objects(Context<E> &ctx);
|
||||
void convert_common_symbols(Context<E> &ctx);
|
||||
void check_duplicate_symbols(Context<E> &ctx);
|
||||
|
||||
Relocation<E> read_reloc(Context<E> &ctx, const MachSection &hdr, MachRel r);
|
||||
|
||||
std::vector<std::unique_ptr<InputSection<E>>> sections;
|
||||
std::vector<std::unique_ptr<Subsection<E>>> subsections;
|
||||
std::vector<u32> sym_to_subsec;
|
||||
std::span<MachSym> mach_syms;
|
||||
std::vector<Symbol<E>> local_syms;
|
||||
std::vector<UnwindRecord<E>> unwind_records;
|
||||
std::span<DataInCodeEntry> data_in_code_entries;
|
||||
|
||||
private:
|
||||
void parse_sections(Context<E> &ctx);
|
||||
void parse_symtab(Context<E> &ctx);
|
||||
void split_subsections(Context<E> &ctx);
|
||||
void parse_data_in_code(Context<E> &ctx);
|
||||
LoadCommand *find_load_command(Context<E> &ctx, u32 type);
|
||||
i64 find_subsection_idx(Context<E> &ctx, u32 addr);
|
||||
void override_symbol(Context<E> &ctx, i64 symidx);
|
||||
InputSection<E> *get_common_sec(Context<E> &ctx);
|
||||
|
||||
MachSection *unwind_sec = nullptr;
|
||||
std::unique_ptr<MachSection> common_hdr;
|
||||
InputSection<E> *common_sec = nullptr;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class DylibFile : public InputFile<E> {
|
||||
public:
|
||||
static DylibFile *create(Context<E> &ctx, MappedFile<Context<E>> *mf);
|
||||
void parse(Context<E> &ctx);
|
||||
void resolve_symbols(Context<E> &ctx);
|
||||
|
||||
std::string_view install_name;
|
||||
i64 dylib_idx = 0;
|
||||
std::atomic_bool is_needed = false;
|
||||
|
||||
bool is_dylib() const override { return true; }
|
||||
|
||||
private:
|
||||
void parse_dylib(Context<E> &ctx);
|
||||
void read_trie(Context<E> &ctx, u8 *start, i64 offset = 0,
|
||||
const std::string &prefix = "");
|
||||
|
||||
DylibFile() {
|
||||
this->is_alive = true;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const InputFile<E> &file);
|
||||
|
||||
//
|
||||
// input-sections.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
class InputSection {
|
||||
public:
|
||||
InputSection(Context<E> &ctx, ObjectFile<E> &file, const MachSection &hdr);
|
||||
void parse_relocations(Context<E> &ctx);
|
||||
|
||||
ObjectFile<E> &file;
|
||||
const MachSection &hdr;
|
||||
OutputSection<E> &osec;
|
||||
std::string_view contents;
|
||||
std::vector<Symbol<E> *> syms;
|
||||
std::vector<Relocation<E>> rels;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const InputSection<E> &sec);
|
||||
|
||||
template <typename E>
|
||||
class Subsection {
|
||||
public:
|
||||
inline u64 get_addr(Context<E> &ctx) const;
|
||||
|
||||
std::string_view get_contents() {
|
||||
assert(isec.hdr.type != S_ZEROFILL);
|
||||
return isec.contents.substr(input_offset, input_size);
|
||||
}
|
||||
|
||||
std::span<UnwindRecord<E>> get_unwind_records() {
|
||||
return std::span<UnwindRecord<E>>(isec.file.unwind_records)
|
||||
.subspan(unwind_offset, nunwind);
|
||||
}
|
||||
|
||||
std::span<Relocation<E>> get_rels() const {
|
||||
return std::span<Relocation<E>>(isec.rels).subspan(rel_offset, nrels);
|
||||
}
|
||||
|
||||
void scan_relocations(Context<E> &ctx);
|
||||
void apply_reloc(Context<E> &ctx, u8 *buf);
|
||||
|
||||
InputSection<E> &isec;
|
||||
u32 input_offset = 0;
|
||||
u32 input_size = 0;
|
||||
u32 input_addr = 0;
|
||||
u32 rel_offset = 0;
|
||||
u32 nrels = 0;
|
||||
u32 unwind_offset = 0;
|
||||
u32 nunwind = 0;
|
||||
u32 raddr = -1;
|
||||
u16 p2align = 0;
|
||||
std::atomic_bool is_alive = false;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::vector<Relocation<E>>
|
||||
read_relocations(Context<E> &ctx, ObjectFile<E> &file, const MachSection &hdr);
|
||||
|
||||
//
|
||||
// Symbol
|
||||
//
|
||||
|
||||
enum {
|
||||
NEEDS_GOT = 1 << 0,
|
||||
NEEDS_STUB = 1 << 1,
|
||||
NEEDS_THREAD_PTR = 1 << 2,
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct Symbol {
|
||||
Symbol() = default;
|
||||
Symbol(std::string_view name) : name(name) {}
|
||||
Symbol(const Symbol<E> &other) : name(other.name) {}
|
||||
|
||||
std::string_view name;
|
||||
InputFile<E> *file = nullptr;
|
||||
Subsection<E> *subsec = nullptr;
|
||||
u64 value = 0;
|
||||
|
||||
i32 stub_idx = -1;
|
||||
i32 got_idx = -1;
|
||||
i32 tlv_idx = -1;
|
||||
|
||||
tbb::spin_mutex mu;
|
||||
|
||||
std::atomic_uint8_t flags = 0;
|
||||
|
||||
u8 is_extern : 1 = false;
|
||||
u8 is_lazy : 1 = false;
|
||||
u8 is_common : 1 = false;
|
||||
u8 referenced_dynamically : 1 = false;
|
||||
|
||||
inline u64 get_addr(Context<E> &ctx) const;
|
||||
inline u64 get_got_addr(Context<E> &ctx) const;
|
||||
inline u64 get_tlv_addr(Context<E> &ctx) const;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym);
|
||||
|
||||
//
|
||||
// output-chunks.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
class OutputSegment {
|
||||
public:
|
||||
static OutputSegment<E> *
|
||||
get_instance(Context<E> &ctx, std::string_view name);
|
||||
|
||||
void set_offset(Context<E> &ctx, i64 fileoff, u64 vmaddr);
|
||||
void copy_buf(Context<E> &ctx);
|
||||
|
||||
std::string_view name;
|
||||
SegmentCommand cmd = {};
|
||||
i32 seg_idx = -1;
|
||||
std::vector<Chunk<E> *> chunks;
|
||||
|
||||
private:
|
||||
OutputSegment(std::string_view name);
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class Chunk {
|
||||
public:
|
||||
inline Chunk(Context<E> &ctx, std::string_view segname,
|
||||
std::string_view sectname);
|
||||
|
||||
virtual ~Chunk() = default;
|
||||
virtual void compute_size(Context<E> &ctx) {};
|
||||
virtual void copy_buf(Context<E> &ctx) {}
|
||||
|
||||
MachSection hdr = {};
|
||||
u32 sect_idx = 0;
|
||||
bool is_hidden = false;
|
||||
bool is_regular = false;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const Chunk<E> &chunk);
|
||||
|
||||
template <typename E>
|
||||
class OutputMachHeader : public Chunk<E> {
|
||||
public:
|
||||
OutputMachHeader(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__TEXT", "__mach_header") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputSection : public Chunk<E> {
|
||||
public:
|
||||
static OutputSection<E> *
|
||||
get_instance(Context<E> &ctx, std::string_view segname,
|
||||
std::string_view sectname);
|
||||
|
||||
OutputSection(Context<E> &ctx, std::string_view segname,
|
||||
std::string_view sectname)
|
||||
: Chunk<E>(ctx, segname, sectname) {
|
||||
this->is_regular = true;
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
void add_subsec(Subsection<E> *subsec) {
|
||||
members.push_back(subsec);
|
||||
this->hdr.p2align = std::max<u32>(this->hdr.p2align, subsec->p2align);
|
||||
this->hdr.attr |= subsec->isec.hdr.attr;
|
||||
this->hdr.type = subsec->isec.hdr.type;
|
||||
}
|
||||
|
||||
std::vector<Subsection<E> *> members;
|
||||
};
|
||||
|
||||
class RebaseEncoder {
|
||||
public:
|
||||
RebaseEncoder();
|
||||
void add(i64 seg_idx, i64 offset);
|
||||
void flush();
|
||||
void finish();
|
||||
|
||||
std::vector<u8> buf;
|
||||
|
||||
private:
|
||||
i64 cur_seg = -1;
|
||||
i64 cur_off = 0;
|
||||
i64 times = 0;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputRebaseSection : public Chunk<E> {
|
||||
public:
|
||||
OutputRebaseSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__rebase") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<u8> contents;
|
||||
};
|
||||
|
||||
class BindEncoder {
|
||||
public:
|
||||
BindEncoder();
|
||||
void add(i64 dylib_idx, std::string_view sym, i64 flags, i64 seg_idx,
|
||||
i64 offset);
|
||||
void finish();
|
||||
|
||||
std::vector<u8> buf;
|
||||
|
||||
private:
|
||||
std::string_view last_sym;
|
||||
i64 last_flags = -1;
|
||||
i64 last_dylib = -1;
|
||||
i64 last_seg = -1;
|
||||
i64 last_off = -1;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputBindSection : public Chunk<E> {
|
||||
public:
|
||||
OutputBindSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__binding") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<u8> contents;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputLazyBindSection : public Chunk<E> {
|
||||
public:
|
||||
OutputLazyBindSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__lazy_binding") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
}
|
||||
|
||||
void add(Context<E> &ctx, Symbol<E> &sym, i64 flags);
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<u8> contents;
|
||||
};
|
||||
|
||||
class ExportEncoder {
|
||||
public:
|
||||
void add(std::string_view name, u32 flags, u64 addr);
|
||||
i64 finish();
|
||||
|
||||
void write_trie(u8 *buf) {
|
||||
write_trie(buf, root);
|
||||
}
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
std::string_view name;
|
||||
u32 flags;
|
||||
u64 addr;
|
||||
};
|
||||
|
||||
struct TrieNode {
|
||||
std::string_view prefix;
|
||||
bool is_leaf = false;
|
||||
u32 flags = 0;
|
||||
u64 addr = 0;
|
||||
u32 offset = -1;
|
||||
std::vector<TrieNode> children;
|
||||
};
|
||||
|
||||
static i64 common_prefix_len(std::span<Entry> entries, i64 len);
|
||||
static TrieNode construct_trie(std::span<Entry> entries, i64 len);
|
||||
static i64 set_offset(TrieNode &node, i64 offset);
|
||||
void write_trie(u8 *buf, TrieNode &node);
|
||||
|
||||
TrieNode root;
|
||||
std::vector<Entry> entries;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputExportSection : public Chunk<E> {
|
||||
public:
|
||||
OutputExportSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__export") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
private:
|
||||
ExportEncoder enc;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputFunctionStartsSection : public Chunk<E> {
|
||||
public:
|
||||
OutputFunctionStartsSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__func_starts") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<u8> contents;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputSymtabSection : public Chunk<E> {
|
||||
public:
|
||||
OutputSymtabSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__symbol_table") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
struct Entry {
|
||||
Symbol<E> *sym;
|
||||
i64 stroff;
|
||||
};
|
||||
|
||||
std::vector<Entry> locals;
|
||||
std::vector<Entry> globals;
|
||||
std::vector<Entry> undefs;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputStrtabSection : public Chunk<E> {
|
||||
public:
|
||||
OutputStrtabSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__string_table") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
}
|
||||
|
||||
i64 add_string(std::string_view str);
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::string contents{1, '\0'};
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputIndirectSymtabSection : public Chunk<E> {
|
||||
public:
|
||||
OutputIndirectSymtabSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__ind_sym_tab") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
|
||||
static constexpr i64 ENTRY_SIZE = 4;
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
struct Entry {
|
||||
Symbol<E> *sym;
|
||||
i64 symtab_idx;
|
||||
};
|
||||
|
||||
std::vector<Entry> stubs;
|
||||
std::vector<Entry> gots;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class CodeSignatureSection : public Chunk<E> {
|
||||
public:
|
||||
CodeSignatureSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__code_signature") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = std::countr_zero(16U);
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void write_signature(Context<E> &ctx);
|
||||
|
||||
static constexpr i64 BLOCK_SIZE = 4096;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class DataInCodeSection : public Chunk<E> {
|
||||
public:
|
||||
DataInCodeSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__data_in_code") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = std::countr_zero(alignof(DataInCodeEntry));
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<DataInCodeEntry> contents;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class StubsSection : public Chunk<E> {
|
||||
public:
|
||||
StubsSection(Context<E> &ctx) : Chunk<E>(ctx, "__TEXT", "__stubs") {
|
||||
this->hdr.p2align = std::countr_zero(2U);
|
||||
this->hdr.type = S_SYMBOL_STUBS;
|
||||
this->hdr.attr = S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS;
|
||||
this->hdr.reserved2 = E::stub_size;
|
||||
}
|
||||
|
||||
void add(Context<E> &ctx, Symbol<E> *sym);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<Symbol<E> *> syms;
|
||||
std::vector<u32> bind_offsets;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class StubHelperSection : public Chunk<E> {
|
||||
public:
|
||||
StubHelperSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__TEXT", "__stub_helper") {
|
||||
this->hdr.p2align = std::countr_zero(4U);
|
||||
this->hdr.attr = S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS;
|
||||
}
|
||||
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class UnwindEncoder {
|
||||
public:
|
||||
std::vector<u8> encode(Context<E> &ctx, std::span<UnwindRecord<E>> records);
|
||||
|
||||
private:
|
||||
u32 encode_personality(Context<E> &ctx, Symbol<E> *sym);
|
||||
|
||||
std::vector<std::span<UnwindRecord<E>>>
|
||||
split_records(Context<E> &ctx, std::span<UnwindRecord<E>>);
|
||||
|
||||
std::vector<Symbol<E> *> personalities;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class UnwindInfoSection : public Chunk<E> {
|
||||
public:
|
||||
UnwindInfoSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__TEXT", "__unwind_info") {
|
||||
this->hdr.p2align = std::countr_zero(4U);
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<u8> contents;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class GotSection : public Chunk<E> {
|
||||
public:
|
||||
GotSection(Context<E> &ctx) : Chunk<E>(ctx, "__DATA_CONST", "__got") {
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
this->hdr.type = S_NON_LAZY_SYMBOL_POINTERS;
|
||||
}
|
||||
|
||||
void add(Context<E> &ctx, Symbol<E> *sym);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<Symbol<E> *> syms;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class LazySymbolPtrSection : public Chunk<E> {
|
||||
public:
|
||||
LazySymbolPtrSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__DATA", "__la_symbol_ptr") {
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
this->hdr.type = S_LAZY_SYMBOL_POINTERS;
|
||||
}
|
||||
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class ThreadPtrsSection : public Chunk<E> {
|
||||
public:
|
||||
ThreadPtrsSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__DATA", "__thread_ptrs") {
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
this->hdr.type = S_THREAD_LOCAL_VARIABLE_POINTERS;
|
||||
}
|
||||
|
||||
void add(Context<E> &ctx, Symbol<E> *sym);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<Symbol<E> *> syms;
|
||||
};
|
||||
|
||||
//
|
||||
// mapfile.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
void print_map(Context<E> &ctx);
|
||||
|
||||
//
|
||||
// dumper.cc
|
||||
//
|
||||
|
||||
void dump_file(std::string path);
|
||||
|
||||
//
|
||||
// output-file.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
class OutputFile {
|
||||
public:
|
||||
static std::unique_ptr<OutputFile>
|
||||
open(Context<E> &ctx, std::string path, i64 filesize, i64 perm);
|
||||
|
||||
virtual void close(Context<E> &ctx) = 0;
|
||||
virtual ~OutputFile() {}
|
||||
|
||||
u8 *buf = nullptr;
|
||||
std::string path;
|
||||
i64 filesize;
|
||||
bool is_mmapped;
|
||||
bool is_unmapped = false;
|
||||
|
||||
protected:
|
||||
OutputFile(std::string path, i64 filesize, bool is_mmapped)
|
||||
: path(path), filesize(filesize), is_mmapped(is_mmapped) {}
|
||||
};
|
||||
|
||||
//
|
||||
// yaml.cc
|
||||
//
|
||||
|
||||
struct YamlNode {
|
||||
std::variant<std::string_view,
|
||||
std::vector<YamlNode>,
|
||||
std::map<std::string_view, YamlNode>> data;
|
||||
};
|
||||
|
||||
struct YamlError {
|
||||
std::string msg;
|
||||
i64 pos;
|
||||
};
|
||||
|
||||
std::variant<std::vector<YamlNode>, YamlError>
|
||||
parse_yaml(std::string_view str);
|
||||
|
||||
//
|
||||
// tapi.cc
|
||||
//
|
||||
|
||||
struct TextDylib {
|
||||
std::string_view uuid;
|
||||
std::string_view install_name;
|
||||
std::string_view current_version = "1.0";
|
||||
std::string_view parent_umbrella;
|
||||
std::vector<std::string_view> reexported_libs;
|
||||
std::vector<std::string_view> exports;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
TextDylib parse_tbd(Context<E> &ctx, MappedFile<Context<E>> *mf);
|
||||
|
||||
//
|
||||
// cmdline.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
void parse_nonpositional_args(Context<E> &ctx,
|
||||
std::vector<std::string> &remaining);
|
||||
|
||||
//
|
||||
// dead-strip.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
void dead_strip(Context<E> &ctx);
|
||||
|
||||
//
|
||||
// main.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
struct Context {
|
||||
Context() {
|
||||
text_seg = OutputSegment<E>::get_instance(*this, "__TEXT");
|
||||
data_const_seg = OutputSegment<E>::get_instance(*this, "__DATA_CONST");
|
||||
data_seg = OutputSegment<E>::get_instance(*this, "__DATA");
|
||||
linkedit_seg = OutputSegment<E>::get_instance(*this, "__LINKEDIT");
|
||||
|
||||
text = OutputSection<E>::get_instance(*this, "__TEXT", "__text");
|
||||
data = OutputSection<E>::get_instance(*this, "__DATA", "__data");
|
||||
bss = OutputSection<E>::get_instance(*this, "__DATA", "__bss");
|
||||
cstring = OutputSection<E>::get_instance(*this, "__TEXT", "__cstring");
|
||||
common = OutputSection<E>::get_instance(*this, "__DATA", "__common");
|
||||
|
||||
bss->hdr.type = S_ZEROFILL;
|
||||
cstring->hdr.type = S_CSTRING_LITERALS;
|
||||
common->hdr.type = S_ZEROFILL;
|
||||
}
|
||||
|
||||
Context(const Context<E> &) = delete;
|
||||
|
||||
void checkpoint() {
|
||||
if (has_error) {
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Command-line arguments
|
||||
struct {
|
||||
bool ObjC = false;
|
||||
bool adhoc_codesign = true;
|
||||
bool color_diagnostics = false;
|
||||
bool dead_strip = true;
|
||||
bool dead_strip_dylibs = false;
|
||||
bool deduplicate = true;
|
||||
bool demangle = false;
|
||||
bool dylib = false;
|
||||
bool dynamic = true;
|
||||
bool fatal_warnings = false;
|
||||
bool noinhibit_exec = false;
|
||||
bool trace = false;
|
||||
i64 arch = CPU_TYPE_ARM64;
|
||||
i64 headerpad = 256;
|
||||
i64 pagezero_size = 0;
|
||||
i64 platform = PLATFORM_MACOS;
|
||||
i64 platform_min_version = 0;
|
||||
i64 platform_sdk_version = 0;
|
||||
std::string chroot;
|
||||
std::string entry = "_main";
|
||||
std::string map;
|
||||
std::string output = "a.out";
|
||||
std::vector<std::string> framework_paths;
|
||||
std::vector<std::string> library_paths;
|
||||
std::vector<std::string> rpath;
|
||||
std::vector<std::string> syslibroot;
|
||||
} arg;
|
||||
|
||||
std::vector<std::string_view> cmdline_args;
|
||||
u32 output_type = MH_EXECUTE;
|
||||
|
||||
bool has_error = false;
|
||||
|
||||
tbb::concurrent_hash_map<std::string_view, Symbol<E>> symbol_map;
|
||||
|
||||
std::unique_ptr<OutputFile<E>> output_file;
|
||||
u8 *buf;
|
||||
|
||||
tbb::concurrent_vector<std::unique_ptr<ObjectFile<E>>> obj_pool;
|
||||
tbb::concurrent_vector<std::unique_ptr<DylibFile<E>>> dylib_pool;
|
||||
tbb::concurrent_vector<std::unique_ptr<u8[]>> string_pool;
|
||||
tbb::concurrent_vector<std::unique_ptr<MappedFile<Context<E>>>> mf_pool;
|
||||
std::vector<std::unique_ptr<OutputSection<E>>> osec_pool;
|
||||
|
||||
tbb::concurrent_vector<std::unique_ptr<TimerRecord>> timer_records;
|
||||
|
||||
std::vector<ObjectFile<E> *> objs;
|
||||
std::vector<DylibFile<E> *> dylibs;
|
||||
|
||||
OutputSegment<E> *text_seg = nullptr;
|
||||
OutputSegment<E> *data_const_seg = nullptr;
|
||||
OutputSegment<E> *data_seg = nullptr;
|
||||
OutputSegment<E> *linkedit_seg = nullptr;
|
||||
|
||||
std::vector<std::unique_ptr<OutputSegment<E>>> segments;
|
||||
std::vector<Chunk<E> *> chunks;
|
||||
|
||||
OutputMachHeader<E> mach_hdr{*this};
|
||||
StubsSection<E> stubs{*this};
|
||||
StubHelperSection<E> stub_helper{*this};
|
||||
UnwindInfoSection<E> unwind_info{*this};
|
||||
GotSection<E> got{*this};
|
||||
LazySymbolPtrSection<E> lazy_symbol_ptr{*this};
|
||||
CodeSignatureSection<E> code_sig{*this};
|
||||
DataInCodeSection<E> data_in_code{*this};
|
||||
ThreadPtrsSection<E> thread_ptrs{*this};
|
||||
|
||||
OutputRebaseSection<E> rebase{*this};
|
||||
OutputBindSection<E> bind{*this};
|
||||
OutputLazyBindSection<E> lazy_bind{*this};
|
||||
OutputExportSection<E> export_{*this};
|
||||
OutputFunctionStartsSection<E> function_starts{*this};
|
||||
OutputSymtabSection<E> symtab{*this};
|
||||
OutputIndirectSymtabSection<E> indir_symtab{*this};
|
||||
OutputStrtabSection<E> strtab{*this};
|
||||
|
||||
OutputSection<E> *text = nullptr;
|
||||
OutputSection<E> *data = nullptr;
|
||||
OutputSection<E> *bss = nullptr;
|
||||
OutputSection<E> *cstring = nullptr;
|
||||
OutputSection<E> *common = nullptr;
|
||||
};
|
||||
|
||||
int main(int argc, char **argv);
|
||||
|
||||
//
|
||||
// Inline functions
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const InputSection<E> &sec) {
|
||||
out << sec.file << "(" << sec.hdr.get_segname() << ","
|
||||
<< sec.hdr.get_sectname() << ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 Subsection<E>::get_addr(Context<E> &ctx) const {
|
||||
return ctx.arg.pagezero_size + raddr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 Symbol<E>::get_addr(Context<E> &ctx) const {
|
||||
if (subsec)
|
||||
return subsec->get_addr(ctx) + value;
|
||||
if (stub_idx != -1)
|
||||
return ctx.stubs.hdr.addr + stub_idx * E::stub_size;
|
||||
return value;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 Symbol<E>::get_got_addr(Context<E> &ctx) const {
|
||||
assert(got_idx != -1);
|
||||
return ctx.got.hdr.addr + got_idx * E::word_size;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 Symbol<E>::get_tlv_addr(Context<E> &ctx) const {
|
||||
assert(tlv_idx != -1);
|
||||
return ctx.thread_ptrs.hdr.addr + tlv_idx * E::word_size;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline Symbol<E> *get_symbol(Context<E> &ctx, std::string_view name) {
|
||||
typename decltype(ctx.symbol_map)::const_accessor acc;
|
||||
ctx.symbol_map.insert(acc, {name, Symbol<E>(name)});
|
||||
return (Symbol<E> *)(&acc->second);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym) {
|
||||
out << sym.name;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Chunk<E>::Chunk(Context<E> &ctx, std::string_view segname,
|
||||
std::string_view sectname) {
|
||||
ctx.chunks.push_back(this);
|
||||
hdr.set_segname(segname);
|
||||
hdr.set_sectname(sectname);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 UnwindRecord<E>::get_func_raddr(Context<E> &ctx) const {
|
||||
return subsec->raddr + offset;
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,622 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include "../archive-file.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const InputFile<E> &file) {
|
||||
if (file.archive_name.empty())
|
||||
out << path_clean(file.mf->name);
|
||||
else
|
||||
out << path_clean(file.archive_name) << "(" << path_clean(file.mf->name) + ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *
|
||||
ObjectFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
ObjectFile<E> *obj = new ObjectFile<E>;
|
||||
obj->mf = mf;
|
||||
obj->archive_name = archive_name;
|
||||
obj->is_alive = archive_name.empty();
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
return obj;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
parse_sections(ctx);
|
||||
parse_symtab(ctx);
|
||||
split_subsections(ctx);
|
||||
parse_data_in_code(ctx);
|
||||
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections)
|
||||
if (isec)
|
||||
isec->parse_relocations(ctx);
|
||||
|
||||
if (unwind_sec)
|
||||
parse_compact_unwind(ctx, *unwind_sec);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_sections(Context<E> &ctx) {
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
// Read all but a symtab section
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
p += lc.cmdsize;
|
||||
if (lc.cmd != LC_SEGMENT_64)
|
||||
continue;
|
||||
|
||||
SegmentCommand &cmd = *(SegmentCommand *)&lc;
|
||||
MachSection *mach_sec = (MachSection *)((u8 *)&lc + sizeof(cmd));
|
||||
|
||||
for (MachSection &msec : std::span(mach_sec, mach_sec + cmd.nsects)) {
|
||||
sections.push_back(nullptr);
|
||||
|
||||
if (msec.match("__LD", "__compact_unwind")) {
|
||||
unwind_sec = &msec;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msec.attr & S_ATTR_DEBUG)
|
||||
continue;
|
||||
|
||||
sections.back().reset(new InputSection<E>(ctx, *this, msec));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_symtab(Context<E> &ctx) {
|
||||
SymtabCommand *cmd = (SymtabCommand *)find_load_command(ctx, LC_SYMTAB);
|
||||
if (!cmd)
|
||||
return;
|
||||
|
||||
mach_syms = {(MachSym *)(this->mf->data + cmd->symoff), cmd->nsyms};
|
||||
this->syms.reserve(mach_syms.size());
|
||||
|
||||
i64 nlocal = 0;
|
||||
for (MachSym &msym : mach_syms)
|
||||
if (!msym.ext)
|
||||
nlocal++;
|
||||
local_syms.reserve(nlocal);
|
||||
|
||||
for (MachSym &msym : mach_syms) {
|
||||
std::string_view name = (char *)(this->mf->data + cmd->stroff + msym.stroff);
|
||||
|
||||
if (msym.ext) {
|
||||
this->syms.push_back(get_symbol(ctx, name));
|
||||
} else {
|
||||
local_syms.emplace_back(name);
|
||||
this->syms.push_back(&local_syms.back());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SplitRegion {
|
||||
u32 offset;
|
||||
u32 size;
|
||||
u32 symidx;
|
||||
bool is_alt_entry;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct SplitInfo {
|
||||
SplitInfo(InputSection<E> *isec) : isec(isec) {}
|
||||
InputSection<E> *isec;
|
||||
std::vector<SplitRegion> regions;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
static std::vector<SplitInfo<E>> split(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
std::vector<SplitInfo<E>> vec;
|
||||
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file.sections)
|
||||
vec.push_back({isec.get()});
|
||||
|
||||
for (i64 i = 0; i < file.mach_syms.size(); i++) {
|
||||
MachSym &msym = file.mach_syms[i];
|
||||
if (msym.type == N_SECT && file.sections[msym.sect - 1]) {
|
||||
SplitRegion r;
|
||||
r.offset = msym.value - file.sections[msym.sect - 1]->hdr.addr;
|
||||
r.symidx = i;
|
||||
r.is_alt_entry = (msym.desc & N_ALT_ENTRY);
|
||||
vec[msym.sect - 1].regions.push_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
std::erase_if(vec, [](const SplitInfo<E> &info) { return !info.isec; });
|
||||
|
||||
sort(vec, [](const SplitInfo<E> &a, const SplitInfo<E> &b) {
|
||||
return a.isec->hdr.addr < b.isec->hdr.addr;
|
||||
});
|
||||
|
||||
for (SplitInfo<E> &info : vec) {
|
||||
std::vector<SplitRegion> &r = info.regions;
|
||||
|
||||
if (r.empty()) {
|
||||
r.push_back({0, (u32)info.isec->hdr.size, (u32)-1, false});
|
||||
continue;
|
||||
}
|
||||
|
||||
sort(r, [](const SplitRegion &a, const SplitRegion &b) {
|
||||
return a.offset < b.offset;
|
||||
});
|
||||
|
||||
if (r[0].offset > 0)
|
||||
r.insert(r.begin(), {0, r[0].offset, (u32)-1, false});
|
||||
|
||||
for (i64 i = 1; i < r.size(); i++)
|
||||
if (r[i - 1].offset == r[i].offset)
|
||||
r[i++].is_alt_entry = true;
|
||||
|
||||
i64 last = -1;
|
||||
for (i64 i = 0; i < r.size(); i++) {
|
||||
if (!r[i].is_alt_entry) {
|
||||
if (last != -1)
|
||||
r[last].size = r[i].offset - r[last].offset;
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (last != -1)
|
||||
r[last].size = info.isec->hdr.size - r[last].offset;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::split_subsections(Context<E> &ctx) {
|
||||
sym_to_subsec.resize(mach_syms.size());
|
||||
|
||||
for (SplitInfo<E> &info : split(ctx, *this)) {
|
||||
InputSection<E> &isec = *info.isec;
|
||||
|
||||
for (SplitRegion &r : info.regions) {
|
||||
if (!r.is_alt_entry) {
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = isec,
|
||||
.input_offset = r.offset,
|
||||
.input_size = r.size,
|
||||
.input_addr = (u32)(isec.hdr.addr + r.offset),
|
||||
.p2align = (u8)isec.hdr.p2align,
|
||||
};
|
||||
subsections.emplace_back(subsec);
|
||||
}
|
||||
|
||||
if (r.symidx != -1)
|
||||
sym_to_subsec[r.symidx] = subsections.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (i64 i = 0; i < mach_syms.size(); i++)
|
||||
if (!mach_syms[i].ext)
|
||||
override_symbol(ctx, i);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_data_in_code(Context<E> &ctx) {
|
||||
if (auto *cmd = (LinkEditDataCommand *)find_load_command(ctx, LC_DATA_IN_CODE))
|
||||
data_in_code_entries = {
|
||||
(DataInCodeEntry *)(this->mf->data + cmd->dataoff),
|
||||
cmd->datasize / sizeof(DataInCodeEntry)
|
||||
};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
LoadCommand *ObjectFile<E>::find_load_command(Context<E> &ctx, u32 type) {
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
if (lc.cmd == type)
|
||||
return &lc;
|
||||
p += lc.cmdsize;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 ObjectFile<E>::find_subsection_idx(Context<E> &ctx, u32 addr) {
|
||||
auto it = std::upper_bound(
|
||||
subsections.begin(), subsections.end(), addr,
|
||||
[&](u32 addr, const std::unique_ptr<Subsection<E>> &subsec) {
|
||||
return addr < subsec->input_addr;
|
||||
});
|
||||
|
||||
if (it == subsections.begin())
|
||||
return -1;
|
||||
return it - subsections.begin() - 1;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Subsection<E> *ObjectFile<E>::find_subsection(Context<E> &ctx, u32 addr) {
|
||||
i64 i = find_subsection_idx(ctx, addr);
|
||||
return (i == -1) ? nullptr : subsections[i].get();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_compact_unwind(Context<E> &ctx, MachSection &hdr) {
|
||||
if (hdr.size % sizeof(CompactUnwindEntry))
|
||||
Fatal(ctx) << *this << ": invalid __compact_unwind section size";
|
||||
|
||||
i64 num_entries = hdr.size / sizeof(CompactUnwindEntry);
|
||||
unwind_records.reserve(num_entries);
|
||||
|
||||
CompactUnwindEntry *src = (CompactUnwindEntry *)(this->mf->data + hdr.offset);
|
||||
|
||||
// Read compact unwind entries
|
||||
for (i64 i = 0; i < num_entries; i++)
|
||||
unwind_records.emplace_back(src[i].code_len, src[i].encoding);
|
||||
|
||||
// Read relocations
|
||||
MachRel *mach_rels = (MachRel *)(this->mf->data + hdr.reloff);
|
||||
for (i64 i = 0; i < hdr.nreloc; i++) {
|
||||
MachRel &r = mach_rels[i];
|
||||
if (r.offset >= hdr.size)
|
||||
Fatal(ctx) << *this << ": relocation offset too large: " << i;
|
||||
|
||||
i64 idx = r.offset / sizeof(CompactUnwindEntry);
|
||||
UnwindRecord<E> &dst = unwind_records[idx];
|
||||
|
||||
auto error = [&] {
|
||||
Fatal(ctx) << *this << ": __compact_unwind: unsupported relocation: " << i;
|
||||
};
|
||||
|
||||
switch (r.offset % sizeof(CompactUnwindEntry)) {
|
||||
case offsetof(CompactUnwindEntry, code_start): {
|
||||
if (r.is_pcrel || r.p2size != 3 || r.is_extern || r.type)
|
||||
error();
|
||||
|
||||
Subsection<E> *target = find_subsection(ctx, src[idx].code_start);
|
||||
if (!target)
|
||||
error();
|
||||
dst.subsec = target;
|
||||
dst.offset = src[idx].code_start - target->input_addr;
|
||||
break;
|
||||
}
|
||||
case offsetof(CompactUnwindEntry, personality):
|
||||
if (r.is_pcrel || r.p2size != 3 || !r.is_extern || r.type)
|
||||
error();
|
||||
dst.personality = this->syms[r.idx];
|
||||
break;
|
||||
case offsetof(CompactUnwindEntry, lsda): {
|
||||
if (r.is_pcrel || r.p2size != 3 || r.is_extern || r.type)
|
||||
error();
|
||||
|
||||
i32 addr = *(i32 *)((u8 *)this->mf->data + hdr.offset + r.offset);
|
||||
Subsection<E> *target = find_subsection(ctx, addr);
|
||||
if (!target)
|
||||
error();
|
||||
dst.lsda = target;
|
||||
dst.lsda_offset = addr - target->input_addr;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << *this << ": __compact_unwind: unsupported relocation: " << i;
|
||||
}
|
||||
}
|
||||
|
||||
for (i64 i = 0; i < num_entries; i++)
|
||||
if (!unwind_records[i].subsec)
|
||||
Fatal(ctx) << ": __compact_unwind: missing relocation at " << i;
|
||||
|
||||
// Sort unwind entries by offset
|
||||
sort(unwind_records, [](const UnwindRecord<E> &a, const UnwindRecord<E> &b) {
|
||||
return std::tuple(a.subsec->input_addr, a.offset) <
|
||||
std::tuple(b.subsec->input_addr, b.offset);
|
||||
});
|
||||
|
||||
// Associate unwind entries to subsections
|
||||
for (i64 i = 0; i < num_entries;) {
|
||||
Subsection<E> &subsec = *unwind_records[i].subsec;
|
||||
subsec.unwind_offset = i;
|
||||
|
||||
i64 j = i + 1;
|
||||
while (j < num_entries && unwind_records[j].subsec == &subsec)
|
||||
j++;
|
||||
subsec.nunwind = j - i;
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
// Symbols with higher priorities overwrites symbols with lower priorities.
|
||||
// Here is the list of priorities, from the highest to the lowest.
|
||||
//
|
||||
// 1. Strong defined symbol
|
||||
// 2. Weak defined symbol
|
||||
// 3. Strong defined symbol in a DSO
|
||||
// 4. Weak defined symbol in a DSO
|
||||
// 5. Strong or weak defined symbol in an archive
|
||||
// 6. Common symbol
|
||||
// 7. Unclaimed (nonexistent) symbol
|
||||
//
|
||||
// Ties are broken by file priority.
|
||||
template <typename E>
|
||||
static u64 get_rank(InputFile<E> *file, MachSym &msym, bool is_lazy) {
|
||||
if (msym.is_common())
|
||||
return (6 << 24) + file->priority;
|
||||
if (is_lazy)
|
||||
return (5 << 24) + file->priority;
|
||||
if (file->is_dylib())
|
||||
return (3 << 24) + file->priority;
|
||||
return (1 << 24) + file->priority;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static u64 get_rank(Symbol<E> &sym) {
|
||||
InputFile<E> *file = sym.file;
|
||||
if (!file)
|
||||
return 7 << 24;
|
||||
if (sym.is_common)
|
||||
return (6 << 24) + file->priority;
|
||||
if (!file->archive_name.empty())
|
||||
return (5 << 24) + file->priority;
|
||||
if (file->is_dylib())
|
||||
return (3 << 24) + file->priority;
|
||||
return (1 << 24) + file->priority;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::override_symbol(Context<E> &ctx, i64 idx) {
|
||||
Symbol<E> &sym = *this->syms[idx];
|
||||
MachSym &msym = mach_syms[idx];
|
||||
|
||||
sym.file = this;
|
||||
sym.is_extern = msym.ext;
|
||||
sym.is_lazy = false;
|
||||
|
||||
switch (msym.type) {
|
||||
case N_UNDF:
|
||||
assert(msym.is_common());
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
sym.is_common = true;
|
||||
break;
|
||||
case N_ABS:
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
sym.is_common = false;
|
||||
break;
|
||||
case N_SECT:
|
||||
sym.subsec = subsections[sym_to_subsec[idx]].get();
|
||||
sym.value = msym.value - sym.subsec->input_addr;
|
||||
sym.is_common = false;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << sym << ": unknown symbol type: " << (u64)msym.type;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::resolve_regular_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.ext || msym.is_undef())
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
if (get_rank(this, msym, false) < get_rank(sym))
|
||||
override_symbol(ctx, i);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::resolve_lazy_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.ext || msym.is_undef() || msym.is_common())
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
if (get_rank(this, msym, true) < get_rank(sym)) {
|
||||
sym.file = this;
|
||||
sym.subsec = nullptr;
|
||||
sym.value = 0;
|
||||
sym.is_extern = false;
|
||||
sym.is_lazy = true;
|
||||
sym.is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool ObjectFile<E>::is_objc_object(Context<E> &ctx) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections)
|
||||
if (isec->hdr.match("__DATA", "__objc_catlist") ||
|
||||
isec->hdr.match("__TEXT", "__swift"))
|
||||
return true;
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++)
|
||||
if (!mach_syms[i].is_undef() && mach_syms[i].ext &&
|
||||
this->syms[i]->name.starts_with("_OBJC_CLASS_$_"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<ObjectFile<E> *> ObjectFile<E>::mark_live_objects(Context<E> &ctx) {
|
||||
std::vector<ObjectFile<E> *> vec;
|
||||
assert(this->is_alive);
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.ext)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
if (msym.is_undef()) {
|
||||
if (sym.file && !sym.file->is_alive.exchange(true))
|
||||
vec.push_back((ObjectFile<E> *)sym.file);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (get_rank(this, msym, false) < get_rank(sym))
|
||||
override_symbol(ctx, i);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
MachSym &msym = mach_syms[i];
|
||||
|
||||
if (sym.file == this && sym.is_common) {
|
||||
InputSection<E> *isec = get_common_sec(ctx);
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = *isec,
|
||||
.input_size = (u32)msym.value,
|
||||
.p2align = (u8)msym.p2align,
|
||||
};
|
||||
|
||||
subsections.emplace_back(subsec);
|
||||
|
||||
sym.subsec = subsec;
|
||||
sym.value = 0;
|
||||
sym.is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::check_duplicate_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
Symbol<E> *sym = this->syms[i];
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (sym && !msym.is_undef() && !msym.is_common() && sym->file != this)
|
||||
Error(ctx) << "duplicate symbol: " << *this << ": " << *sym->file
|
||||
<< ": " << *sym;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E> *ObjectFile<E>::get_common_sec(Context<E> &ctx) {
|
||||
if (!common_sec) {
|
||||
MachSection *hdr = new MachSection;
|
||||
common_hdr.reset(hdr);
|
||||
|
||||
memset(hdr, 0, sizeof(*hdr));
|
||||
hdr->set_segname("__DATA");
|
||||
hdr->set_sectname("__common");
|
||||
hdr->type = S_ZEROFILL;
|
||||
|
||||
common_sec = new InputSection<E>(ctx, *this, *hdr);
|
||||
sections.emplace_back(common_sec);
|
||||
}
|
||||
return common_sec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
DylibFile<E> *DylibFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
DylibFile<E> *dylib = new DylibFile<E>;
|
||||
dylib->mf = mf;
|
||||
ctx.dylib_pool.emplace_back(dylib);
|
||||
return dylib;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::read_trie(Context<E> &ctx, u8 *start, i64 offset,
|
||||
const std::string &prefix) {
|
||||
u8 *buf = start + offset;
|
||||
|
||||
if (*buf) {
|
||||
read_uleb(buf); // size
|
||||
read_uleb(buf); // flags
|
||||
read_uleb(buf); // addr
|
||||
this->syms.push_back(get_symbol(ctx, prefix));
|
||||
} else {
|
||||
buf++;
|
||||
}
|
||||
|
||||
i64 nchild = *buf++;
|
||||
|
||||
for (i64 i = 0; i < nchild; i++) {
|
||||
std::string suffix((char *)buf);
|
||||
buf += suffix.size() + 1;
|
||||
i64 off = read_uleb(buf);
|
||||
read_trie(ctx, start, off, prefix + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse_dylib(Context<E> &ctx) {
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
|
||||
switch (lc.cmd) {
|
||||
case LC_ID_DYLIB: {
|
||||
DylibCommand &cmd = *(DylibCommand *)p;
|
||||
install_name = (char *)p + cmd.nameoff;
|
||||
break;
|
||||
}
|
||||
case LC_DYLD_INFO_ONLY: {
|
||||
DyldInfoCommand &cmd = *(DyldInfoCommand *)p;
|
||||
if (cmd.export_off)
|
||||
read_trie(ctx, this->mf->data + cmd.export_off);
|
||||
break;
|
||||
}
|
||||
case LC_DYLD_EXPORTS_TRIE: {
|
||||
LinkEditDataCommand &cmd = *(LinkEditDataCommand *)p;
|
||||
read_trie(ctx, this->mf->data + cmd.dataoff);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
p += lc.cmdsize;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse(Context<E> &ctx) {
|
||||
switch (get_file_type(this->mf)) {
|
||||
case FileType::TAPI: {
|
||||
TextDylib tbd = parse_tbd(ctx, this->mf);
|
||||
for (std::string_view sym : tbd.exports)
|
||||
this->syms.push_back(get_symbol(ctx, sym));
|
||||
install_name = tbd.install_name;
|
||||
break;
|
||||
}
|
||||
case FileType::MACH_DYLIB:
|
||||
parse_dylib(ctx);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << this->mf->name << ": is not a dylib";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
for (Symbol<E> *sym : this->syms) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
if (sym->file && sym->file->priority < this->priority)
|
||||
continue;
|
||||
sym->file = this;
|
||||
sym->is_extern = true;
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class ObjectFile<E>; \
|
||||
template class DylibFile<E>; \
|
||||
template std::ostream &operator<<(std::ostream &, const InputFile<E> &)
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
|
||||
} // namespace mold::macho
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,120 @@
|
||||
// This file implements the same functionality as elf/output-file.cc.
|
||||
// We have two different implemetntations for ELF and Mach-O because
|
||||
// we implemented a special optimization for ELF or Linux.
|
||||
//
|
||||
// On Linux, it is faster to write to an existing file than creating a
|
||||
// new file and writing to it. So, we overwrite an existing executable
|
||||
// if exists.
|
||||
//
|
||||
// On macOS, it looks like the system does not assume that an executable
|
||||
// is mutated once it is created. Due to some code signing mechanism or
|
||||
// something, we always have to create a fresh file.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
class MemoryMappedOutputFile : public OutputFile<E> {
|
||||
public:
|
||||
MemoryMappedOutputFile(Context<E> &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<E>(path, filesize, true) {
|
||||
std::string tmp_path = filepath(path).parent_path() / ".mold-XXXXXX";
|
||||
output_tmpfile = (char *)save_string(ctx, tmp_path).data();
|
||||
|
||||
i64 fd = mkstemp(output_tmpfile);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << output_tmpfile << ": " << errno_string();
|
||||
|
||||
if (ftruncate(fd, filesize))
|
||||
Fatal(ctx) << "ftruncate failed: " << errno_string();
|
||||
if (fchmod(fd, perm) == -1)
|
||||
Fatal(ctx) << "fchmod failed: " << errno_string();
|
||||
|
||||
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, fd, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
::close(fd);
|
||||
}
|
||||
|
||||
void close(Context<E> &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
munmap(this->buf, this->filesize);
|
||||
if (rename(output_tmpfile, this->path.c_str()) == -1)
|
||||
Fatal(ctx) << this->path << ": rename failed: " << errno_string();
|
||||
output_tmpfile = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class MallocOutputFile : public OutputFile<E> {
|
||||
public:
|
||||
MallocOutputFile(Context<E> &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<E>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)mmap(NULL, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << "mmap failed: " << errno_string();
|
||||
}
|
||||
|
||||
void close(Context<E> &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (this->path == "-") {
|
||||
fwrite(this->buf, this->filesize, 1, stdout);
|
||||
fclose(stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
|
||||
FILE *fp = fdopen(fd, "w");
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::unique_ptr<OutputFile<E>>
|
||||
OutputFile<E>::open(Context<E> &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
Timer t(ctx, "open_file");
|
||||
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
bool is_special = false;
|
||||
if (path == "-") {
|
||||
is_special = true;
|
||||
} else {
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) != S_IFREG)
|
||||
is_special = true;
|
||||
}
|
||||
|
||||
std::unique_ptr<OutputFile> file;
|
||||
if (is_special)
|
||||
file = std::make_unique<MallocOutputFile<E>>(ctx, path, filesize, perm);
|
||||
else
|
||||
file = std::make_unique<MemoryMappedOutputFile<E>>(ctx, path, filesize, perm);
|
||||
return file;
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class OutputFile<E>
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
|
||||
} // namespace mold::macho
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
static std::vector<YamlNode>
|
||||
get_vector(YamlNode &node, std::string_view key) {
|
||||
if (auto *map = std::get_if<std::map<std::string_view, YamlNode>>(&node.data))
|
||||
if (auto it = map->find(key); it != map->end())
|
||||
if (auto *vec = std::get_if<std::vector<YamlNode>>(&it->second.data))
|
||||
return *vec;
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::optional<std::string_view>
|
||||
get_string(YamlNode &node, std::string_view key) {
|
||||
if (auto *map = std::get_if<std::map<std::string_view, YamlNode>>(&node.data))
|
||||
if (auto it = map->find(key); it != map->end())
|
||||
if (auto *str = std::get_if<std::string_view>(&it->second.data))
|
||||
return *str;
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool contains(const std::vector<YamlNode> &vec, std::string_view key) {
|
||||
for (const YamlNode &mem : vec)
|
||||
if (const std::string_view *s = std::get_if<std::string_view>(&mem.data))
|
||||
if (*s == key)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::optional<TextDylib> to_tbd(YamlNode &node, std::string_view arch) {
|
||||
if (!contains(get_vector(node, "targets"), arch))
|
||||
return {};
|
||||
|
||||
TextDylib tbd;
|
||||
|
||||
for (YamlNode &mem : get_vector(node, "uuids"))
|
||||
if (auto target = get_string(mem, "target"))
|
||||
if (*target == arch)
|
||||
if (auto value = get_string(mem, "value"))
|
||||
tbd.uuid = *value;
|
||||
|
||||
if (auto val = get_string(node, "install-name"))
|
||||
tbd.install_name = *val;
|
||||
|
||||
if (auto val = get_string(node, "current-version"))
|
||||
tbd.current_version = *val;
|
||||
|
||||
for (YamlNode &mem : get_vector(node, "parent-umbrella"))
|
||||
if (contains(get_vector(mem, "targets"), arch))
|
||||
if (auto val = get_string(mem, "umbrella"))
|
||||
tbd.parent_umbrella = *val;
|
||||
|
||||
for (YamlNode &mem : get_vector(node, "reexported-libraries"))
|
||||
if (contains(get_vector(mem, "targets"), arch))
|
||||
for (YamlNode &mem : get_vector(mem, "libraries"))
|
||||
if (auto *lib = std::get_if<std::string_view>(&mem.data))
|
||||
tbd.reexported_libs.push_back(*lib);
|
||||
|
||||
for (std::string_view key : {"exports", "reexports"})
|
||||
for (YamlNode &mem : get_vector(node, key))
|
||||
if (contains(get_vector(mem, "targets"), arch))
|
||||
for (YamlNode &mem : get_vector(mem, "symbols"))
|
||||
if (auto *sym = std::get_if<std::string_view>(&mem.data))
|
||||
tbd.exports.push_back(*sym);
|
||||
|
||||
return tbd;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static TextDylib squash(Context<E> &ctx, std::span<TextDylib> tbds) {
|
||||
std::unordered_map<std::string_view, TextDylib> map;
|
||||
|
||||
TextDylib main = std::move(tbds[0]);
|
||||
for (TextDylib &tbd : tbds.subspan(1))
|
||||
map[tbd.install_name] = std::move(tbd);
|
||||
|
||||
std::vector<std::string_view> libs;
|
||||
|
||||
for (std::string_view lib : main.reexported_libs) {
|
||||
auto it = map.find(lib);
|
||||
if (it != map.end())
|
||||
append(main.exports, it->second.exports);
|
||||
else
|
||||
libs.push_back(lib);
|
||||
}
|
||||
|
||||
main.reexported_libs = std::move(libs);
|
||||
return main;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static TextDylib parse(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string_view arch) {
|
||||
std::string_view contents = mf->get_contents();
|
||||
std::variant<std::vector<YamlNode>, YamlError> res = parse_yaml(contents);
|
||||
|
||||
if (YamlError *err = std::get_if<YamlError>(&res)) {
|
||||
i64 lineno = std::count(contents.begin(), contents.begin() + err->pos, '\n');
|
||||
Fatal(ctx) << mf->name << ":" << (lineno + 1)
|
||||
<< ": YAML parse error: " << err->msg;
|
||||
}
|
||||
|
||||
std::vector<YamlNode> &nodes = std::get<std::vector<YamlNode>>(res);
|
||||
if (nodes.empty())
|
||||
Fatal(ctx) << mf->name << ": malformed TBD file";
|
||||
|
||||
std::vector<TextDylib> vec;
|
||||
for (YamlNode &node : nodes)
|
||||
if (std::optional<TextDylib> dylib = to_tbd(node, arch))
|
||||
vec.push_back(*dylib);
|
||||
return squash(ctx, vec);
|
||||
}
|
||||
|
||||
template <>
|
||||
TextDylib parse_tbd(Context<ARM64> &ctx, MappedFile<Context<ARM64>> *mf) {
|
||||
return parse(ctx, mf, "arm64-macos");
|
||||
}
|
||||
|
||||
template <>
|
||||
TextDylib parse_tbd(Context<X86_64> &ctx, MappedFile<Context<X86_64>> *mf) {
|
||||
return parse(ctx, mf, "x86_64-macos");
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
struct Token {
|
||||
enum { STRING = 1, INDENT, DEDENT, END };
|
||||
|
||||
u8 kind = 0;
|
||||
std::string_view str;
|
||||
};
|
||||
|
||||
class YamlParser {
|
||||
public:
|
||||
YamlParser(std::string_view input) : input(input) {}
|
||||
|
||||
std::variant<std::vector<YamlNode>, YamlError> parse();
|
||||
|
||||
private:
|
||||
std::optional<YamlError> tokenize();
|
||||
|
||||
void tokenize_bare_string(std::string_view &str);
|
||||
|
||||
std::optional<YamlError> tokenize_list(std::string_view &str);
|
||||
std::optional<YamlError> tokenize_string(std::string_view &str, char end);
|
||||
|
||||
std::variant<YamlNode, YamlError> parse_element(std::span<Token> &tok);
|
||||
std::variant<YamlNode, YamlError> parse_list(std::span<Token> &tok);
|
||||
std::variant<YamlNode, YamlError> parse_map(std::span<Token> &tok);
|
||||
std::variant<YamlNode, YamlError> parse_flow_element(std::span<Token> &tok);
|
||||
std::variant<YamlNode, YamlError> parse_flow_list(std::span<Token> &tok);
|
||||
|
||||
std::string_view input;
|
||||
std::vector<Token> tokens;
|
||||
};
|
||||
|
||||
std::optional<YamlError> YamlParser::tokenize() {
|
||||
std::vector<i64> indents = {0};
|
||||
|
||||
auto indent = [&](std::string_view str, i64 depth) {
|
||||
tokens.push_back({Token::INDENT, str});
|
||||
indents.push_back(depth);
|
||||
};
|
||||
|
||||
auto dedent = [&](std::string_view str) {
|
||||
assert(indents.size() > 1);
|
||||
tokens.push_back({Token::DEDENT, str});
|
||||
indents.pop_back();
|
||||
};
|
||||
|
||||
auto skip_line = [](std::string_view &str) {
|
||||
size_t pos = str.find('\n');
|
||||
if (pos == str.npos)
|
||||
str = str.substr(str.size());
|
||||
else
|
||||
str = str.substr(pos + 1);
|
||||
};
|
||||
|
||||
auto tokenize_line = [&](std::string_view &str) -> std::optional<YamlError> {
|
||||
const char *start = str.data();
|
||||
|
||||
if (str.starts_with("---")) {
|
||||
while (indents.size() > 1)
|
||||
dedent(str);
|
||||
tokens.push_back({Token::END, str.substr(0, 3)});
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (str.starts_with("...")) {
|
||||
while (indents.size() > 1)
|
||||
dedent(str);
|
||||
tokens.push_back({Token::END, str.substr(0, 3)});
|
||||
str = str.substr(str.size());
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t pos = str.find_first_not_of(" \t");
|
||||
if (pos == str.npos || str[pos] == '#' || str[pos] == '\n') {
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (indents.back() != pos) {
|
||||
if (indents.back() < pos) {
|
||||
indent(str, pos);
|
||||
} else {
|
||||
while (indents.back() != pos) {
|
||||
if (pos < indents.back())
|
||||
dedent(str);
|
||||
else
|
||||
return YamlError{"bad indentation", start - input.data()};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
str = str.substr(pos);
|
||||
|
||||
while (!str.empty()) {
|
||||
if (str[0] == '\n') {
|
||||
str = str.substr(1);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (str.starts_with("- ")) {
|
||||
tokens.push_back({'-', str.substr(0, 1)});
|
||||
|
||||
size_t pos = str.find_first_not_of(" \t", 1);
|
||||
if (pos == str.npos || str[pos] == '\n') {
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
|
||||
str = str.substr(pos);
|
||||
indent(str, str.data() - start);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with('['))
|
||||
return tokenize_list(str);
|
||||
|
||||
if (str.starts_with('\'')) {
|
||||
if (std::optional<YamlError> err = tokenize_string(str, '\''))
|
||||
return err;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with('"')) {
|
||||
if (std::optional<YamlError> err = tokenize_string(str, '"'))
|
||||
return err;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with('#')) {
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (str.starts_with(':')) {
|
||||
tokens.push_back({':', str.substr(0, 1)});
|
||||
|
||||
size_t pos = str.find_first_not_of(" \t", 1);
|
||||
if (pos == str.npos || str[pos] == '\n') {
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
|
||||
str = str.substr(pos);
|
||||
continue;
|
||||
}
|
||||
|
||||
tokenize_bare_string(str);
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
std::string_view str = input;
|
||||
while (!str.empty())
|
||||
if (std::optional<YamlError> err = tokenize_line(str))
|
||||
return err;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<YamlError> YamlParser::tokenize_list(std::string_view &str) {
|
||||
const char *start = str.data();
|
||||
|
||||
tokens.push_back({'[', str.substr(0, 1)});
|
||||
str = str.substr(1);
|
||||
|
||||
while (!str.empty() && str[0] != ']') {
|
||||
if (size_t pos = str.find_first_not_of(" \t\n"); pos) {
|
||||
str = str.substr(pos);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with('\'')) {
|
||||
if (std::optional<YamlError> err = tokenize_string(str, '\''))
|
||||
return err;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with('"')) {
|
||||
if (std::optional<YamlError> err = tokenize_string(str, '"'))
|
||||
return err;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str.starts_with(',')) {
|
||||
tokens.push_back({',', str.substr(0, 1)});
|
||||
str = str.substr(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
tokenize_bare_string(str);
|
||||
}
|
||||
|
||||
if (str.empty())
|
||||
return YamlError{"unclosed list", start - input.data()};
|
||||
|
||||
const char *bracket = str.data();
|
||||
tokens.push_back({']', str.substr(0, 1)});
|
||||
str = str.substr(1);
|
||||
|
||||
while (!str.empty() && (str[0] == ' ' || str[0] == '\t'))
|
||||
str = str.substr(1);
|
||||
if (str.empty() || str[0] != '\n')
|
||||
return YamlError{"no newline after ']'", bracket - input.data()};
|
||||
str = str.substr(1);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<YamlError>
|
||||
YamlParser::tokenize_string(std::string_view &str, char end) {
|
||||
const char *start = str.data();
|
||||
size_t pos = str.find(end, 1);
|
||||
if (pos == str.npos)
|
||||
return YamlError{"unterminated string literal", start - input.data()};
|
||||
|
||||
tokens.push_back({Token::STRING, str.substr(1, pos - 1)});
|
||||
str = str.substr(pos + 1);
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
YamlParser::tokenize_bare_string(std::string_view &str) {
|
||||
size_t pos = str.find_first_not_of(
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-/.");
|
||||
if (pos == str.npos)
|
||||
pos = str.size();
|
||||
tokens.push_back({Token::STRING, str.substr(0, pos)});
|
||||
str = str.substr(pos);
|
||||
}
|
||||
|
||||
std::variant<std::vector<YamlNode>, YamlError> YamlParser::parse() {
|
||||
if (std::optional<YamlError> err = tokenize())
|
||||
return *err;
|
||||
|
||||
std::span<Token> tok(tokens);
|
||||
std::vector<YamlNode> vec;
|
||||
|
||||
while (!tok.empty()) {
|
||||
if (tok[0].kind == Token::END) {
|
||||
tok = tok.subspan(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError> elem = parse_element(tok);
|
||||
if (YamlError *err = std::get_if<YamlError>(&elem))
|
||||
return *err;
|
||||
vec.push_back(std::get<YamlNode>(elem));
|
||||
|
||||
if (tok[0].kind != Token::END)
|
||||
return YamlError{"stray token", tok[0].str.data() - input.data()};
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_element(std::span<Token> &tok) {
|
||||
if (tok[0].kind == Token::INDENT) {
|
||||
tok = tok.subspan(1);
|
||||
|
||||
std::variant<YamlNode, YamlError> elem = parse_element(tok);
|
||||
assert(tok[0].kind == Token::DEDENT);
|
||||
tok = tok.subspan(1);
|
||||
return elem;
|
||||
}
|
||||
|
||||
if (tok[0].kind == '-')
|
||||
return parse_list(tok);
|
||||
|
||||
if (tok.size() > 2 && tok[0].kind == Token::STRING && tok[1].kind == ':')
|
||||
return parse_map(tok);
|
||||
|
||||
return parse_flow_element(tok);
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_list(std::span<Token> &tok) {
|
||||
std::vector<YamlNode> vec;
|
||||
|
||||
while (tok[0].kind != Token::END && tok[0].kind != Token::DEDENT) {
|
||||
if (tok[0].kind != '-')
|
||||
return YamlError{"list element expected", tok[0].str.data() - input.data()};
|
||||
tok = tok.subspan(1);
|
||||
|
||||
std::variant<YamlNode, YamlError> elem = parse_element(tok);
|
||||
if (YamlError *err = std::get_if<YamlError>(&elem))
|
||||
return *err;
|
||||
vec.push_back(std::get<YamlNode>(elem));
|
||||
}
|
||||
return YamlNode{vec};
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_map(std::span<Token> &tok) {
|
||||
std::map<std::string_view, YamlNode> map;
|
||||
|
||||
while (tok[0].kind != Token::END && tok[0].kind != Token::DEDENT) {
|
||||
if (tok.size() < 2 || tok[0].kind != Token::STRING || tok[1].kind != ':')
|
||||
return YamlError{"map key expected", tok[0].str.data() - input.data()};
|
||||
|
||||
std::string_view key = tok[0].str;
|
||||
tok = tok.subspan(2);
|
||||
|
||||
std::variant<YamlNode, YamlError> elem = parse_element(tok);
|
||||
if (YamlError *err = std::get_if<YamlError>(&elem))
|
||||
return *err;
|
||||
map[key] = std::get<YamlNode>(elem);
|
||||
}
|
||||
return YamlNode{map};
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_flow_element(std::span<Token> &tok) {
|
||||
if (tok[0].kind == '[') {
|
||||
tok = tok.subspan(1);
|
||||
return parse_flow_list(tok);
|
||||
}
|
||||
|
||||
if (tok[0].kind != Token::STRING)
|
||||
return YamlError{"scalar expected", tok[0].str.data() - input.data()};
|
||||
|
||||
std::string_view val = tok[0].str;
|
||||
tok = tok.subspan(1);
|
||||
return YamlNode{val};
|
||||
}
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_flow_list(std::span<Token> &tok) {
|
||||
std::vector<YamlNode> vec;
|
||||
const char *start = tok[0].str.data();
|
||||
|
||||
while (tok[0].kind != ']' && tok[0].kind != Token::END) {
|
||||
std::variant<YamlNode, YamlError> elem = parse_flow_element(tok);
|
||||
if (YamlError *err = std::get_if<YamlError>(&elem))
|
||||
return *err;
|
||||
vec.push_back(std::get<YamlNode>(elem));
|
||||
|
||||
if (tok[0].kind == ']')
|
||||
break;
|
||||
if (tok[0].kind != ',')
|
||||
return YamlError{"comma expected", tok[0].str.data() - input.data()};
|
||||
tok = tok.subspan(1);
|
||||
}
|
||||
|
||||
if (tok[0].kind == Token::END)
|
||||
return YamlError{"unterminated flow list", start - input.data()};
|
||||
|
||||
tok = tok.subspan(1);
|
||||
return YamlNode{vec};
|
||||
}
|
||||
|
||||
std::variant<std::vector<YamlNode>, YamlError>
|
||||
parse_yaml(std::string_view str) {
|
||||
return YamlParser(str).parse();
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -1,682 +1,47 @@
|
||||
#include "mold.h"
|
||||
#include "elf/mold.h"
|
||||
#include "macho/mold.h"
|
||||
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <cstring>
|
||||
#include <signal.h>
|
||||
#include <tbb/global_control.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <unistd.h>
|
||||
#include <unordered_set>
|
||||
|
||||
template <typename E>
|
||||
std::string_view save_string(Context<E> &ctx, const std::string &str) {
|
||||
u8 *buf = new u8[str.size() + 1];
|
||||
memcpy(buf, str.data(), str.size());
|
||||
buf[str.size()] = '\0';
|
||||
ctx.owning_bufs.push_back(std::unique_ptr<u8[]>(buf));
|
||||
return {(char *)buf, str.size()};
|
||||
}
|
||||
#ifdef USE_SYSTEM_MIMALLOC
|
||||
#include <mimalloc-new-delete.h>
|
||||
#endif
|
||||
|
||||
std::string get_version_string() {
|
||||
if (strlen(GIT_HASH) == 0)
|
||||
return "mold " MOLD_VERSION " (compatible with GNU ld and GNU gold)";
|
||||
return "mold " MOLD_VERSION " (" GIT_HASH
|
||||
"; compatible with GNU ld and GNU gold)";
|
||||
}
|
||||
namespace mold {
|
||||
|
||||
std::regex glob_to_regex(std::string_view pattern) {
|
||||
std::stringstream ss;
|
||||
for (u8 c : pattern) {
|
||||
if (c == '*')
|
||||
ss << ".*";
|
||||
else
|
||||
ss << "\\x" << std::hex << std::setw(2) << std::setfill('0') << (int)c;
|
||||
}
|
||||
return std::regex(ss.str(), std::regex::optimize);
|
||||
}
|
||||
#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
|
||||
|
||||
template <typename E>
|
||||
static ObjectFile<E> *new_object_file(Context<E> &ctx, MemoryMappedFile<E> *mb,
|
||||
std::string archive_name) {
|
||||
static Counter count("parsed_objs");
|
||||
count++;
|
||||
|
||||
bool in_lib = (!archive_name.empty() && !ctx.whole_archive);
|
||||
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mb, archive_name, in_lib);
|
||||
file->priority = ctx.file_priority++;
|
||||
ctx.tg.run([file, &ctx]() { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static SharedFile<E> *new_shared_file(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
SharedFile<E> *file = SharedFile<E>::create(ctx, mb);
|
||||
file->priority = ctx.file_priority++;
|
||||
ctx.tg.run([file, &ctx]() { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void read_file(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
if (ctx.visited.contains(mb->name))
|
||||
return;
|
||||
|
||||
if (ctx.is_preloading) {
|
||||
switch (get_file_type(ctx, mb)) {
|
||||
case FileType::OBJ:
|
||||
ctx.obj_cache.store(mb, new_object_file(ctx, mb, ""));
|
||||
return;
|
||||
case FileType::DSO:
|
||||
ctx.dso_cache.store(mb, new_shared_file(ctx, mb));
|
||||
return;
|
||||
case FileType::AR:
|
||||
for (MemoryMappedFile<E> *child : read_fat_archive_members(ctx, mb))
|
||||
if (get_file_type(ctx, child) == FileType::OBJ)
|
||||
ctx.obj_cache.store(mb, new_object_file(ctx, child, mb->name));
|
||||
return;
|
||||
case FileType::THIN_AR:
|
||||
for (MemoryMappedFile<E> *child : read_thin_archive_members(ctx, mb))
|
||||
if (get_file_type(ctx, child) == FileType::OBJ)
|
||||
ctx.obj_cache.store(child, new_object_file(ctx, child, mb->name));
|
||||
return;
|
||||
case FileType::TEXT:
|
||||
parse_linker_script(ctx, mb);
|
||||
return;
|
||||
case FileType::LLVM_BITCODE:
|
||||
Fatal(ctx) << mb->name << ": looks like this is an LLVM bitcode, but"
|
||||
<< " mold does not support LTO";
|
||||
default:
|
||||
Fatal(ctx) << mb->name << ": unknown file type";
|
||||
}
|
||||
}
|
||||
|
||||
switch (get_file_type(ctx, mb)) {
|
||||
case FileType::OBJ:
|
||||
if (ObjectFile<E> *obj = ctx.obj_cache.get_one(mb))
|
||||
ctx.objs.push_back(obj);
|
||||
else
|
||||
ctx.objs.push_back(new_object_file(ctx, mb, ""));
|
||||
return;
|
||||
case FileType::DSO:
|
||||
if (SharedFile<E> *obj = ctx.dso_cache.get_one(mb))
|
||||
ctx.dsos.push_back(obj);
|
||||
else
|
||||
ctx.dsos.push_back(new_shared_file(ctx, mb));
|
||||
ctx.visited.insert(mb->name);
|
||||
return;
|
||||
case FileType::AR: {
|
||||
std::vector<ObjectFile<E> *> objs = ctx.obj_cache.get(mb);
|
||||
if (!objs.empty()) {
|
||||
append(ctx.objs, objs);
|
||||
} else {
|
||||
for (MemoryMappedFile<E> *child : read_fat_archive_members(ctx, mb))
|
||||
if (get_file_type(ctx, child) == FileType::OBJ)
|
||||
ctx.objs.push_back(new_object_file(ctx, child, mb->name));
|
||||
}
|
||||
ctx.visited.insert(mb->name);
|
||||
return;
|
||||
}
|
||||
case FileType::THIN_AR:
|
||||
for (MemoryMappedFile<E> *child : read_thin_archive_members(ctx, mb)) {
|
||||
if (ObjectFile<E> *obj = ctx.obj_cache.get_one(child))
|
||||
ctx.objs.push_back(obj);
|
||||
else if (get_file_type(ctx, child) == FileType::OBJ)
|
||||
ctx.objs.push_back(new_object_file(ctx, child, mb->name));
|
||||
}
|
||||
ctx.visited.insert(mb->name);
|
||||
return;
|
||||
case FileType::TEXT:
|
||||
parse_linker_script(ctx, mb);
|
||||
return;
|
||||
case FileType::LLVM_BITCODE:
|
||||
Fatal(ctx) << mb->name << ": looks like this is an LLVM bitcode, "
|
||||
<< "but mold does not support LTO";
|
||||
default:
|
||||
Fatal(ctx) << mb->name << ": unknown file type";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void cleanup() {
|
||||
if (OutputFile<E>::tmpfile)
|
||||
unlink(OutputFile<E>::tmpfile);
|
||||
if (output_tmpfile)
|
||||
unlink(output_tmpfile);
|
||||
if (socket_tmpfile)
|
||||
unlink(socket_tmpfile);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void signal_handler(int) {
|
||||
cleanup<E>();
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
// 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, MemoryMappedFile<E> *mb) {
|
||||
switch (get_file_type(ctx, mb)) {
|
||||
case FileType::DSO:
|
||||
return ((ElfEhdr<E> *)mb->data(ctx))->e_machine;
|
||||
case FileType::AR:
|
||||
for (MemoryMappedFile<E> *child : read_fat_archive_members(ctx, mb))
|
||||
if (get_file_type(ctx, child) == FileType::OBJ)
|
||||
return ((ElfEhdr<E> *)child->data(ctx))->e_machine;
|
||||
return -1;
|
||||
case FileType::THIN_AR:
|
||||
for (MemoryMappedFile<E> *child : read_thin_archive_members(ctx, mb))
|
||||
if (get_file_type(ctx, child) == FileType::OBJ)
|
||||
return ((ElfEhdr<E> *)child->data(ctx))->e_machine;
|
||||
return -1;
|
||||
case FileType::TEXT:
|
||||
return get_script_output_type(ctx, mb);
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
void install_signal_handler() {
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static MemoryMappedFile<E> *open_library(Context<E> &ctx, std::string path) {
|
||||
MemoryMappedFile<E> *mb = MemoryMappedFile<E>::open(ctx, path);
|
||||
if (!mb)
|
||||
return nullptr;
|
||||
|
||||
i64 type = get_machine_type(ctx, mb);
|
||||
if (type == -1 || type == E::e_machine)
|
||||
return mb;
|
||||
Warn(ctx) << path << ": skipping incompatible file";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MemoryMappedFile<E> *find_library(Context<E> &ctx, std::string name) {
|
||||
if (name.starts_with(':')) {
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string root = dir.starts_with("/") ? ctx.arg.sysroot : "";
|
||||
std::string path = root + std::string(dir) + "/" + name.substr(1);
|
||||
if (MemoryMappedFile<E> *mb = open_library(ctx, path))
|
||||
return mb;
|
||||
}
|
||||
Fatal(ctx) << "library not found: " << name;
|
||||
}
|
||||
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string root = dir.starts_with("/") ? ctx.arg.sysroot : "";
|
||||
std::string stem = root + std::string(dir) + "/lib" + name;
|
||||
if (!ctx.is_static)
|
||||
if (MemoryMappedFile<E> *mb = open_library(ctx, stem + ".so"))
|
||||
return mb;
|
||||
if (MemoryMappedFile<E> *mb = open_library(ctx, stem + ".a"))
|
||||
return mb;
|
||||
}
|
||||
Fatal(ctx) << "library not found: " << name;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_input_files(Context<E> &ctx, std::span<std::string_view> args) {
|
||||
std::vector<std::tuple<bool, bool, bool>> state;
|
||||
|
||||
while (!args.empty()) {
|
||||
std::string_view arg;
|
||||
|
||||
if (read_flag(args, "as-needed")) {
|
||||
ctx.as_needed = true;
|
||||
} else if (read_flag(args, "no-as-needed")) {
|
||||
ctx.as_needed = false;
|
||||
} else if (read_flag(args, "whole-archive")) {
|
||||
ctx.whole_archive = true;
|
||||
} else if (read_flag(args, "no-whole-archive")) {
|
||||
ctx.whole_archive = false;
|
||||
} else if (read_flag(args, "Bstatic")) {
|
||||
ctx.is_static = true;
|
||||
} else if (read_flag(args, "Bdynamic")) {
|
||||
ctx.is_static = false;
|
||||
} else if (read_arg(ctx, args, arg, "version-script")) {
|
||||
parse_version_script(ctx, std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "dynamic-list")) {
|
||||
parse_dynamic_list(ctx, std::string(arg));
|
||||
} else if (read_flag(args, "push-state")) {
|
||||
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static});
|
||||
} else if (read_flag(args, "pop-state")) {
|
||||
if (state.empty())
|
||||
Fatal(ctx) << "no state pushed before popping";
|
||||
std::tie(ctx.as_needed, ctx.whole_archive, ctx.is_static) = state.back();
|
||||
state.pop_back();
|
||||
} else if (read_arg(ctx, args, arg, "l")) {
|
||||
MemoryMappedFile<E> *mb = find_library(ctx, std::string(arg));
|
||||
mb->given_fullpath = false;
|
||||
read_file(ctx, mb);
|
||||
} else {
|
||||
read_file(ctx, MemoryMappedFile<E>::must_open(ctx, std::string(args[0])));
|
||||
args = args.subspan(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void show_stats(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *obj : ctx.objs) {
|
||||
static Counter defined("defined_syms");
|
||||
defined += obj->first_global - 1;
|
||||
|
||||
static Counter undefined("undefined_syms");
|
||||
undefined += obj->symbols.size() - obj->first_global;
|
||||
|
||||
for (std::unique_ptr<InputSection<E>> &sec : obj->sections) {
|
||||
if (!sec || !sec->is_alive)
|
||||
continue;
|
||||
|
||||
static Counter alloc("reloc_alloc");
|
||||
static Counter nonalloc("reloc_nonalloc");
|
||||
|
||||
if (sec->shdr.sh_flags & SHF_ALLOC)
|
||||
alloc += sec->get_rels(ctx).size();
|
||||
else
|
||||
nonalloc += sec->get_rels(ctx).size();
|
||||
}
|
||||
|
||||
static Counter comdats("comdats");
|
||||
comdats += obj->comdat_groups.size();
|
||||
|
||||
static Counter removed_comdats("removed_comdat_mem");
|
||||
for (auto &pair : obj->comdat_groups)
|
||||
if (ComdatGroup *group = pair.first; group->owner != obj->priority)
|
||||
removed_comdats += pair.second.size();
|
||||
|
||||
static Counter num_cies("num_cies");
|
||||
num_cies += obj->cies.size();
|
||||
|
||||
static Counter num_unique_cies("num_unique_cies");
|
||||
for (CieRecord<E> &cie : obj->cies)
|
||||
if (cie.is_leader)
|
||||
num_unique_cies++;
|
||||
|
||||
static Counter num_fdes("num_fdes");
|
||||
num_fdes += obj->fdes.size();
|
||||
}
|
||||
|
||||
static Counter num_bytes("total_input_bytes");
|
||||
for (std::unique_ptr<MemoryMappedFile<E>> &mb : ctx.owning_mbs)
|
||||
num_bytes += mb->size();
|
||||
|
||||
static Counter num_input_sections("input_sections");
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
num_input_sections += file->sections.size();
|
||||
|
||||
static Counter num_output_chunks("output_chunks", ctx.chunks.size());
|
||||
static Counter num_objs("num_objs", ctx.objs.size());
|
||||
static Counter num_dsos("num_dsos", ctx.dsos.size());
|
||||
|
||||
Counter::print();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
int do_main(int argc, char **argv) {
|
||||
Context<E> ctx;
|
||||
|
||||
// Process -run option first. process_run_subcommand() does not return.
|
||||
if (argc >= 2)
|
||||
if (std::string_view arg = argv[1]; arg == "-run" || arg == "--run")
|
||||
process_run_subcommand(ctx, argc, argv);
|
||||
|
||||
Timer t_all(ctx, "all");
|
||||
|
||||
// Parse non-positional command line options
|
||||
ctx.cmdline_args = expand_response_files(ctx, argv);
|
||||
std::vector<std::string_view> file_args;
|
||||
parse_nonpositional_args(ctx, file_args);
|
||||
|
||||
if (ctx.arg.relocatable) {
|
||||
combine_objects(ctx, file_args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ctx.arg.preload)
|
||||
try_resume_daemon(ctx);
|
||||
|
||||
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
|
||||
ctx.arg.thread_count);
|
||||
|
||||
signal(SIGINT, signal_handler<E>);
|
||||
signal(SIGTERM, signal_handler<E>);
|
||||
|
||||
if (!ctx.arg.directory.empty() && chdir(ctx.arg.directory.c_str()) == -1)
|
||||
Fatal(ctx) << "chdir failed: " << ctx.arg.directory
|
||||
<< ": " << strerror(errno);
|
||||
|
||||
// Handle --wrap options if any.
|
||||
for (std::string_view name : ctx.arg.wrap)
|
||||
Symbol<E>::intern(ctx, name)->wrap = true;
|
||||
|
||||
// Handle --retain-symbols-file options if any.
|
||||
if (ctx.arg.retain_symbols_file)
|
||||
for (std::string_view name : *ctx.arg.retain_symbols_file)
|
||||
Symbol<E>::intern(ctx, name)->write_to_symtab = true;
|
||||
|
||||
// Preload input files
|
||||
std::function<void()> on_complete;
|
||||
|
||||
if (ctx.arg.preload) {
|
||||
Timer t(ctx, "preload");
|
||||
std::function<void()> wait_for_client;
|
||||
daemonize(ctx, &wait_for_client, &on_complete);
|
||||
|
||||
ctx.reset_reader_context(true);
|
||||
read_input_files(ctx, file_args);
|
||||
ctx.tg.wait();
|
||||
t.stop();
|
||||
|
||||
Timer t2(ctx, "wait_for_client");
|
||||
wait_for_client();
|
||||
} else if (ctx.arg.fork) {
|
||||
on_complete = fork_child();
|
||||
}
|
||||
|
||||
for (std::string_view arg : ctx.arg.trace_symbol)
|
||||
Symbol<E>::intern(ctx, arg)->traced = true;
|
||||
|
||||
// Parse input files
|
||||
{
|
||||
Timer t(ctx, "parse");
|
||||
ctx.reset_reader_context(false);
|
||||
read_input_files(ctx, file_args);
|
||||
ctx.tg.wait();
|
||||
}
|
||||
|
||||
if (ctx.objs.empty())
|
||||
Fatal(ctx) << "no input files";
|
||||
|
||||
// Uniquify shared object files by soname
|
||||
{
|
||||
std::unordered_set<std::string_view> seen;
|
||||
erase(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
return !seen.insert(file->soname).second;
|
||||
});
|
||||
}
|
||||
|
||||
Timer t_total(ctx, "total");
|
||||
Timer t_before_copy(ctx, "before_copy");
|
||||
|
||||
// Apply -exclude-libs
|
||||
apply_exclude_libs(ctx);
|
||||
|
||||
// Create instances of linker-synthesized sections such as
|
||||
// .got or .plt.
|
||||
create_synthetic_sections(ctx);
|
||||
|
||||
// Resolve symbols and fix the set of object files that are
|
||||
// included to the final output.
|
||||
resolve_symbols(ctx);
|
||||
|
||||
// Remove redundant comdat sections (e.g. duplicate inline functions).
|
||||
eliminate_comdats(ctx);
|
||||
|
||||
// Create .bss sections for common symbols.
|
||||
convert_common_symbols(ctx);
|
||||
|
||||
// Apply version scripts.
|
||||
apply_version_script(ctx);
|
||||
|
||||
// Parse symbol version suffixes (e.g. "foo@ver1").
|
||||
parse_symbol_version(ctx);
|
||||
|
||||
// Set is_import and is_export bits for each symbol.
|
||||
compute_import_export(ctx);
|
||||
|
||||
// Garbage-collect unreachable sections.
|
||||
if (ctx.arg.gc_sections)
|
||||
gc_sections(ctx);
|
||||
|
||||
// Merge identical read-only sections.
|
||||
if (ctx.arg.icf)
|
||||
icf_sections(ctx);
|
||||
|
||||
// Compute sizes of sections containing mergeable strings.
|
||||
compute_merged_section_sizes(ctx);
|
||||
|
||||
// ctx input sections into output sections
|
||||
bin_sections(ctx);
|
||||
|
||||
// Get a list of output sections.
|
||||
append(ctx.chunks, collect_output_sections(ctx));
|
||||
|
||||
// Create a dummy file containing linker-synthesized symbols
|
||||
// (e.g. `__bss_start`).
|
||||
ctx.internal_obj = ObjectFile<E>::create_internal_file(ctx);
|
||||
ctx.internal_obj->resolve_regular_symbols(ctx);
|
||||
ctx.objs.push_back(ctx.internal_obj);
|
||||
|
||||
// Beyond this point, no new files will be added to ctx.objs
|
||||
// or ctx.dsos.
|
||||
|
||||
// If we are linking a .so file, remaining undefined symbols does
|
||||
// not cause a linker error. Instead, they are treated as if they
|
||||
// were imported symbols.
|
||||
//
|
||||
// If we are linking an executable, weak undefs are converted to
|
||||
// weakly imported symbol so that they'll have another chance to be
|
||||
claim_unresolved_symbols(ctx);
|
||||
|
||||
// Beyond this point, no new symbols will be added to the result.
|
||||
|
||||
// Make sure that all symbols have been resolved.
|
||||
if (!ctx.arg.allow_multiple_definition)
|
||||
check_duplicate_symbols(ctx);
|
||||
|
||||
// .init_array and .fini_array contents have to be sorted by
|
||||
// a special rule. Sort them.
|
||||
sort_init_fini(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, [&](OutputChunk<E> *a, OutputChunk<E> *b) {
|
||||
return get_section_rank(ctx, a) < get_section_rank(ctx, b);
|
||||
});
|
||||
|
||||
// Copy string referred by .dynamic to .dynstr.
|
||||
for (SharedFile<E> *file : ctx.dsos)
|
||||
ctx.dynstr->add_string(file->soname);
|
||||
for (std::string_view str : ctx.arg.auxiliary)
|
||||
ctx.dynstr->add_string(str);
|
||||
for (std::string_view str : ctx.arg.filter)
|
||||
ctx.dynstr->add_string(str);
|
||||
if (!ctx.arg.rpaths.empty())
|
||||
ctx.dynstr->add_string(ctx.arg.rpaths);
|
||||
if (!ctx.arg.soname.empty())
|
||||
ctx.dynstr->add_string(ctx.arg.soname);
|
||||
|
||||
// Scan relocations to find symbols that need entries in .got, .plt,
|
||||
// .got.plt, .dynsym, .dynstr, etc.
|
||||
scan_rels(ctx);
|
||||
|
||||
// Reserve a space for dynamic symbol strings in .dynstr and sort
|
||||
// .dynsym contents if necessary. Beyond this point, no symbol will
|
||||
// be added to .dynsym.
|
||||
ctx.dynsym->finalize(ctx);
|
||||
|
||||
// Fill .gnu.version_d section contents.
|
||||
ctx.verdef->construct(ctx);
|
||||
|
||||
// Fill .gnu.version_r section contents.
|
||||
ctx.verneed->construct(ctx);
|
||||
|
||||
// Compute .symtab and .strtab sizes for each file.
|
||||
{
|
||||
Timer t(ctx, "compute_symtab");
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->compute_symtab(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
// .eh_frame is a special section from the linker's point of view,
|
||||
// as its contents are parsed and reconstructed by the linker,
|
||||
// unlike other sections that are regarded as opaque bytes.
|
||||
// Here, we transplant .eh_frame sections from a regular output
|
||||
// section to the special EHFrameSection.
|
||||
{
|
||||
Timer t(ctx, "eh_frame");
|
||||
erase(ctx.chunks, [](OutputChunk<E> *chunk) {
|
||||
return chunk->kind == OutputChunk<E>::REGULAR &&
|
||||
chunk->name == ".eh_frame";
|
||||
});
|
||||
ctx.eh_frame->construct(ctx);
|
||||
}
|
||||
|
||||
// Now that we have computed sizes for all sections and assigned
|
||||
// section indices to them, so we can fix section header contents
|
||||
// for all output sections.
|
||||
for (OutputChunk<E> *chunk : ctx.chunks)
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
erase(ctx.chunks, [](OutputChunk<E> *chunk) {
|
||||
return chunk->kind == OutputChunk<E>::SYNTHETIC &&
|
||||
chunk->shdr.sh_size == 0;
|
||||
});
|
||||
|
||||
// Set section indices.
|
||||
for (i64 i = 0, shndx = 1; i < ctx.chunks.size(); i++)
|
||||
if (ctx.chunks[i]->kind != OutputChunk<E>::HEADER)
|
||||
ctx.chunks[i]->shndx = shndx++;
|
||||
|
||||
for (OutputChunk<E> *chunk : ctx.chunks)
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
// Assign offsets to output sections
|
||||
i64 filesize = set_osec_offsets(ctx);
|
||||
|
||||
// Fix linker-synthesized symbol addresses.
|
||||
fix_synthetic_symbols(ctx);
|
||||
|
||||
// If --compress-debug-sections is given, compress .debug_* sections
|
||||
// using zlib.
|
||||
if (ctx.arg.compress_debug_sections != COMPRESS_NONE) {
|
||||
compress_debug_sections(ctx);
|
||||
filesize = set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
// At this point, file layout is fixed.
|
||||
|
||||
// Beyond this, you can assume that symbol addresses including their
|
||||
// GOT or PLT addresses have a correct final value.
|
||||
|
||||
// Some types of relocations for TLS symbols need the TLS segment
|
||||
// address. Find it out now.
|
||||
for (ElfPhdr<E> phdr : create_phdr(ctx)) {
|
||||
if (phdr.p_type == PT_TLS) {
|
||||
ctx.tls_begin = phdr.p_vaddr;
|
||||
ctx.tls_end = align_to(phdr.p_vaddr + phdr.p_memsz, phdr.p_align);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
t_before_copy.stop();
|
||||
|
||||
// Create an output file
|
||||
ctx.output_file = OutputFile<E>::open(ctx, ctx.arg.output, filesize, 0777);
|
||||
ctx.buf = ctx.output_file->buf;
|
||||
|
||||
Timer t_copy(ctx, "copy");
|
||||
|
||||
// Copy input sections to the output file
|
||||
{
|
||||
Timer t(ctx, "copy_buf");
|
||||
|
||||
tbb::parallel_for_each(ctx.chunks, [&](OutputChunk<E> *chunk) {
|
||||
std::string name(chunk->name);
|
||||
if (name.empty())
|
||||
name = "(header)";
|
||||
Timer t2(ctx, name, &t);
|
||||
|
||||
chunk->copy_buf(ctx);
|
||||
});
|
||||
|
||||
Error<E>::checkpoint(ctx);
|
||||
}
|
||||
|
||||
// Dynamic linker works better with sorted .rela.dyn section,
|
||||
// so we sort them.
|
||||
ctx.reldyn->sort(ctx);
|
||||
|
||||
// Zero-clear paddings between sections
|
||||
clear_padding(ctx);
|
||||
|
||||
if (ctx.buildid) {
|
||||
Timer t(ctx, "build_id");
|
||||
ctx.buildid->write_buildid(ctx);
|
||||
}
|
||||
|
||||
t_copy.stop();
|
||||
|
||||
// Commit
|
||||
ctx.output_file->close(ctx);
|
||||
|
||||
t_total.stop();
|
||||
t_all.stop();
|
||||
|
||||
if (ctx.arg.print_map)
|
||||
print_map(ctx);
|
||||
|
||||
// Show stats numbers
|
||||
if (ctx.arg.stats)
|
||||
show_stats(ctx);
|
||||
|
||||
if (ctx.arg.perf)
|
||||
Timer<E>::print(ctx);
|
||||
|
||||
std::cout << std::flush;
|
||||
std::cerr << std::flush;
|
||||
if (on_complete)
|
||||
on_complete();
|
||||
|
||||
if (ctx.arg.quick_exit)
|
||||
std::quick_exit(0);
|
||||
|
||||
for (std::function<void()> &fn : ctx.on_exit)
|
||||
fn();
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum class MachineType { X86_64, I386 };
|
||||
|
||||
static MachineType get_machine_type(int argc, char **argv) {
|
||||
for (i64 i = 1; i < argc; i++) {
|
||||
if (std::string_view(argv[i]) == "-m") {
|
||||
if (i + 1 == argc)
|
||||
break;
|
||||
i++;
|
||||
|
||||
std::string_view val(argv[i]);
|
||||
if (val == "elf_x86_64")
|
||||
return MachineType::X86_64;
|
||||
if (val == "elf_i386")
|
||||
return MachineType::I386;
|
||||
std::cerr << "unknown -m argument: " << val << "\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
return MachineType::X86_64;
|
||||
}
|
||||
} // namespace mold
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
switch (get_machine_type(argc, argv)) {
|
||||
case MachineType::X86_64:
|
||||
return do_main<X86_64>(argc, argv);
|
||||
case MachineType::I386:
|
||||
return do_main<I386>(argc, argv);
|
||||
}
|
||||
std::string cmd = mold::filepath(argv[0]).filename();
|
||||
|
||||
if (cmd == "ld64" || cmd == "ld64.mold")
|
||||
return mold::macho::main(argc, argv);
|
||||
|
||||
return mold::elf::main(argc, argv);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void read_file(Context<E> &, MemoryMappedFile<E> *); \
|
||||
template std::string_view save_string(Context<E> &, const std::string &)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
@@ -1,118 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
template <typename E>
|
||||
MemoryMappedFile<E> *
|
||||
MemoryMappedFile<E>::open(Context<E> &ctx, std::string path) {
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) == -1)
|
||||
return nullptr;
|
||||
u64 mtime = (u64)st.st_mtim.tv_sec * 1000000000 + st.st_mtim.tv_nsec;
|
||||
|
||||
u8 *data = nullptr;
|
||||
if (st.st_size == 0) {
|
||||
// mmap(2) doesn't allow 0 as an argument for length parameter,
|
||||
// so this case has to be handled as a special case.
|
||||
static u8 dummy[1];
|
||||
data = dummy;
|
||||
}
|
||||
|
||||
MemoryMappedFile *mb = new MemoryMappedFile(path, data, st.st_size, mtime);
|
||||
ctx.owning_mbs.push_back(std::unique_ptr<MemoryMappedFile>(mb));
|
||||
return mb;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MemoryMappedFile<E> *
|
||||
MemoryMappedFile<E>::must_open(Context<E> &ctx, std::string path) {
|
||||
if (MemoryMappedFile *mb = MemoryMappedFile::open(ctx, path))
|
||||
return mb;
|
||||
Fatal(ctx) << "cannot open " << path;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u8 *MemoryMappedFile<E>::data(Context<E> &ctx) {
|
||||
if (data_)
|
||||
return data_;
|
||||
|
||||
std::lock_guard lock(mu);
|
||||
if (data_)
|
||||
return data_;
|
||||
|
||||
i64 fd = ::open(name.c_str(), O_RDONLY);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << name << ": cannot open: " << strerror(errno);
|
||||
|
||||
data_ = (u8 *)mmap(nullptr, size_, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (data_ == MAP_FAILED)
|
||||
Fatal(ctx) << name << ": mmap failed: " << strerror(errno);
|
||||
close(fd);
|
||||
return data_;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MemoryMappedFile<E> *
|
||||
MemoryMappedFile<E>::slice(Context<E> &ctx, std::string name, u64 start,
|
||||
u64 size) {
|
||||
MemoryMappedFile *mb = new MemoryMappedFile<E>(name, data_ + start, size);
|
||||
ctx.owning_mbs.push_back(std::unique_ptr<MemoryMappedFile>(mb));
|
||||
mb->parent = this;
|
||||
return mb;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MemoryMappedFile<E>::~MemoryMappedFile() {
|
||||
if (data_ && !parent)
|
||||
munmap(data_, size_);
|
||||
}
|
||||
|
||||
|
||||
template <typename E>
|
||||
static bool is_text_file(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
u8 *data = mb->data(ctx);
|
||||
return mb->size() >= 4 &&
|
||||
isprint(data[0]) &&
|
||||
isprint(data[1]) &&
|
||||
isprint(data[2]) &&
|
||||
isprint(data[3]);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
FileType get_file_type(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
u8 *data = mb->data(ctx);
|
||||
|
||||
if (mb->size() >= 20 && memcmp(data, "\177ELF", 4) == 0) {
|
||||
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)data;
|
||||
if (ehdr.e_type == ET_REL)
|
||||
return FileType::OBJ;
|
||||
if (ehdr.e_type == ET_DYN)
|
||||
return FileType::DSO;
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
if (mb->size() >= 8 && memcmp(data, "!<arch>\n", 8) == 0)
|
||||
return FileType::AR;
|
||||
if (mb->size() >= 8 && memcmp(data, "!<thin>\n", 8) == 0)
|
||||
return FileType::THIN_AR;
|
||||
if (is_text_file(ctx, mb))
|
||||
return FileType::TEXT;
|
||||
if (mb->size() >= 4 && memcmp(data, "\xDE\xC0\x17\x0B", 4) == 0)
|
||||
return FileType::LLVM_BITCODE;
|
||||
if (mb->size() >= 4 && memcmp(data, "BC\xC0\xDE", 4) == 0)
|
||||
return FileType::LLVM_BITCODE;
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class MemoryMappedFile<E>; \
|
||||
template FileType get_file_type(Context<E> &, MemoryMappedFile<E> *)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
@@ -1,2 +0,0 @@
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/mimalloc.cmake)
|
||||
get_filename_component(MIMALLOC_TARGET_DIR "${CMAKE_CURRENT_LIST_DIR}" PATH)
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 164 KiB |
Vendored
-87
@@ -1,87 +0,0 @@
|
||||
/*!
|
||||
* jQuery JavaScript Library v1.7.2
|
||||
* http://jquery.com/
|
||||
*
|
||||
* Copyright 2011, John Resig
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* Includes Sizzle.js
|
||||
* http://sizzlejs.com/
|
||||
* Copyright 2011, The Dojo Foundation
|
||||
* Released under the MIT, BSD, and GPL Licenses.
|
||||
*
|
||||
* Date: Wed Mar 21 12:46:34 2012 -0700
|
||||
*/
|
||||
(function(bd,L){var av=bd.document,bu=bd.navigator,bm=bd.location;var b=(function(){var bF=function(b0,b1){return new bF.fn.init(b0,b1,bD)},bU=bd.jQuery,bH=bd.$,bD,bY=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,bM=/\S/,bI=/^\s+/,bE=/\s+$/,bA=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,bN=/^[\],:{}\s]*$/,bW=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,bP=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,bJ=/(?:^|:|,)(?:\s*\[)+/g,by=/(webkit)[ \/]([\w.]+)/,bR=/(opera)(?:.*version)?[ \/]([\w.]+)/,bQ=/(msie) ([\w.]+)/,bS=/(mozilla)(?:.*? rv:([\w.]+))?/,bB=/-([a-z]|[0-9])/ig,bZ=/^-ms-/,bT=function(b0,b1){return(b1+"").toUpperCase()},bX=bu.userAgent,bV,bC,e,bL=Object.prototype.toString,bG=Object.prototype.hasOwnProperty,bz=Array.prototype.push,bK=Array.prototype.slice,bO=String.prototype.trim,bv=Array.prototype.indexOf,bx={};bF.fn=bF.prototype={constructor:bF,init:function(b0,b4,b3){var b2,b5,b1,b6;if(!b0){return this}if(b0.nodeType){this.context=this[0]=b0;this.length=1;return this}if(b0==="body"&&!b4&&av.body){this.context=av;this[0]=av.body;this.selector=b0;this.length=1;return this}if(typeof b0==="string"){if(b0.charAt(0)==="<"&&b0.charAt(b0.length-1)===">"&&b0.length>=3){b2=[null,b0,null]}else{b2=bY.exec(b0)}if(b2&&(b2[1]||!b4)){if(b2[1]){b4=b4 instanceof bF?b4[0]:b4;b6=(b4?b4.ownerDocument||b4:av);b1=bA.exec(b0);if(b1){if(bF.isPlainObject(b4)){b0=[av.createElement(b1[1])];bF.fn.attr.call(b0,b4,true)}else{b0=[b6.createElement(b1[1])]}}else{b1=bF.buildFragment([b2[1]],[b6]);b0=(b1.cacheable?bF.clone(b1.fragment):b1.fragment).childNodes}return bF.merge(this,b0)}else{b5=av.getElementById(b2[2]);if(b5&&b5.parentNode){if(b5.id!==b2[2]){return b3.find(b0)}this.length=1;this[0]=b5}this.context=av;this.selector=b0;return this}}else{if(!b4||b4.jquery){return(b4||b3).find(b0)}else{return this.constructor(b4).find(b0)}}}else{if(bF.isFunction(b0)){return b3.ready(b0)}}if(b0.selector!==L){this.selector=b0.selector;this.context=b0.context}return bF.makeArray(b0,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return bK.call(this,0)},get:function(b0){return b0==null?this.toArray():(b0<0?this[this.length+b0]:this[b0])},pushStack:function(b1,b3,b0){var b2=this.constructor();if(bF.isArray(b1)){bz.apply(b2,b1)}else{bF.merge(b2,b1)}b2.prevObject=this;b2.context=this.context;if(b3==="find"){b2.selector=this.selector+(this.selector?" ":"")+b0}else{if(b3){b2.selector=this.selector+"."+b3+"("+b0+")"}}return b2},each:function(b1,b0){return bF.each(this,b1,b0)},ready:function(b0){bF.bindReady();bC.add(b0);return this},eq:function(b0){b0=+b0;return b0===-1?this.slice(b0):this.slice(b0,b0+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(bK.apply(this,arguments),"slice",bK.call(arguments).join(","))},map:function(b0){return this.pushStack(bF.map(this,function(b2,b1){return b0.call(b2,b1,b2)}))},end:function(){return this.prevObject||this.constructor(null)},push:bz,sort:[].sort,splice:[].splice};bF.fn.init.prototype=bF.fn;bF.extend=bF.fn.extend=function(){var b9,b2,b0,b1,b6,b7,b5=arguments[0]||{},b4=1,b3=arguments.length,b8=false;if(typeof b5==="boolean"){b8=b5;b5=arguments[1]||{};b4=2}if(typeof b5!=="object"&&!bF.isFunction(b5)){b5={}}if(b3===b4){b5=this;--b4}for(;b4<b3;b4++){if((b9=arguments[b4])!=null){for(b2 in b9){b0=b5[b2];b1=b9[b2];if(b5===b1){continue}if(b8&&b1&&(bF.isPlainObject(b1)||(b6=bF.isArray(b1)))){if(b6){b6=false;b7=b0&&bF.isArray(b0)?b0:[]}else{b7=b0&&bF.isPlainObject(b0)?b0:{}}b5[b2]=bF.extend(b8,b7,b1)}else{if(b1!==L){b5[b2]=b1}}}}}return b5};bF.extend({noConflict:function(b0){if(bd.$===bF){bd.$=bH}if(b0&&bd.jQuery===bF){bd.jQuery=bU}return bF},isReady:false,readyWait:1,holdReady:function(b0){if(b0){bF.readyWait++}else{bF.ready(true)}},ready:function(b0){if((b0===true&&!--bF.readyWait)||(b0!==true&&!bF.isReady)){if(!av.body){return setTimeout(bF.ready,1)}bF.isReady=true;if(b0!==true&&--bF.readyWait>0){return}bC.fireWith(av,[bF]);if(bF.fn.trigger){bF(av).trigger("ready").off("ready")}}},bindReady:function(){if(bC){return}bC=bF.Callbacks("once memory");if(av.readyState==="complete"){return setTimeout(bF.ready,1)}if(av.addEventListener){av.addEventListener("DOMContentLoaded",e,false);bd.addEventListener("load",bF.ready,false)}else{if(av.attachEvent){av.attachEvent("onreadystatechange",e);bd.attachEvent("onload",bF.ready);var b0=false;try{b0=bd.frameElement==null}catch(b1){}if(av.documentElement.doScroll&&b0){bw()}}}},isFunction:function(b0){return bF.type(b0)==="function"},isArray:Array.isArray||function(b0){return bF.type(b0)==="array"},isWindow:function(b0){return b0!=null&&b0==b0.window},isNumeric:function(b0){return !isNaN(parseFloat(b0))&&isFinite(b0)},type:function(b0){return b0==null?String(b0):bx[bL.call(b0)]||"object"},isPlainObject:function(b2){if(!b2||bF.type(b2)!=="object"||b2.nodeType||bF.isWindow(b2)){return false}try{if(b2.constructor&&!bG.call(b2,"constructor")&&!bG.call(b2.constructor.prototype,"Line truncated
|
||||
/*!
|
||||
* Sizzle CSS Selector Engine
|
||||
* Copyright 2011, The Dojo Foundation
|
||||
* Released under the MIT, BSD, and GPL Licenses.
|
||||
* More information: http://sizzlejs.com/
|
||||
*/
|
||||
(function(){var bH=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,bC="sizcache"+(Math.random()+"").replace(".",""),bI=0,bL=Object.prototype.toString,bB=false,bA=true,bK=/\\/g,bO=/\r\n/g,bQ=/\W/;[0,0].sort(function(){bA=false;return 0});var by=function(bV,e,bY,bZ){bY=bY||[];e=e||av;var b1=e;if(e.nodeType!==1&&e.nodeType!==9){return[]}if(!bV||typeof bV!=="string"){return bY}var bS,b3,b6,bR,b2,b5,b4,bX,bU=true,bT=by.isXML(e),bW=[],b0=bV;do{bH.exec("");bS=bH.exec(b0);if(bS){b0=bS[3];bW.push(bS[1]);if(bS[2]){bR=bS[3];break}}}while(bS);if(bW.length>1&&bD.exec(bV)){if(bW.length===2&&bE.relative[bW[0]]){b3=bM(bW[0]+bW[1],e,bZ)}else{b3=bE.relative[bW[0]]?[e]:by(bW.shift(),e);while(bW.length){bV=bW.shift();if(bE.relative[bV]){bV+=bW.shift()}b3=bM(bV,b3,bZ)}}}else{if(!bZ&&bW.length>1&&e.nodeType===9&&!bT&&bE.match.ID.test(bW[0])&&!bE.match.ID.test(bW[bW.length-1])){b2=by.find(bW.shift(),e,bT);e=b2.expr?by.filter(b2.expr,b2.set)[0]:b2.set[0]}if(e){b2=bZ?{expr:bW.pop(),set:bF(bZ)}:by.find(bW.pop(),bW.length===1&&(bW[0]==="~"||bW[0]==="+")&&e.parentNode?e.parentNode:e,bT);b3=b2.expr?by.filter(b2.expr,b2.set):b2.set;if(bW.length>0){b6=bF(b3)}else{bU=false}while(bW.length){b5=bW.pop();b4=b5;if(!bE.relative[b5]){b5=""}else{b4=bW.pop()}if(b4==null){b4=e}bE.relative[b5](b6,b4,bT)}}else{b6=bW=[]}}if(!b6){b6=b3}if(!b6){by.error(b5||bV)}if(bL.call(b6)==="[object Array]"){if(!bU){bY.push.apply(bY,b6)}else{if(e&&e.nodeType===1){for(bX=0;b6[bX]!=null;bX++){if(b6[bX]&&(b6[bX]===true||b6[bX].nodeType===1&&by.contains(e,b6[bX]))){bY.push(b3[bX])}}}else{for(bX=0;b6[bX]!=null;bX++){if(b6[bX]&&b6[bX].nodeType===1){bY.push(b3[bX])}}}}}else{bF(b6,bY)}if(bR){by(bR,b1,bY,bZ);by.uniqueSort(bY)}return bY};by.uniqueSort=function(bR){if(bJ){bB=bA;bR.sort(bJ);if(bB){for(var e=1;e<bR.length;e++){if(bR[e]===bR[e-1]){bR.splice(e--,1)}}}}return bR};by.matches=function(e,bR){return by(e,null,null,bR)};by.matchesSelector=function(e,bR){return by(bR,null,null,[e]).length>0};by.find=function(bX,e,bY){var bW,bS,bU,bT,bV,bR;if(!bX){return[]}for(bS=0,bU=bE.order.length;bS<bU;bS++){bV=bE.order[bS];if((bT=bE.leftMatch[bV].exec(bX))){bR=bT[1];bT.splice(1,1);if(bR.substr(bR.length-1)!=="\\"){bT[1]=(bT[1]||"").replace(bK,"");bW=bE.find[bV](bT,e,bY);if(bW!=null){bX=bX.replace(bE.match[bV],"");break}}}}if(!bW){bW=typeof e.getElementsByTagName!=="undefined"?e.getElementsByTagName("*"):[]}return{set:bW,expr:bX}};by.filter=function(b1,b0,b4,bU){var bW,e,bZ,b6,b3,bR,bT,bV,b2,bS=b1,b5=[],bY=b0,bX=b0&&b0[0]&&by.isXML(b0[0]);while(b1&&b0.length){for(bZ in bE.filter){if((bW=bE.leftMatch[bZ].exec(b1))!=null&&bW[2]){bR=bE.filter[bZ];bT=bW[1];e=false;bW.splice(1,1);if(bT.substr(bT.length-1)==="\\"){continue}if(bY===b5){b5=[]}if(bE.preFilter[bZ]){bW=bE.preFilter[bZ](bW,bY,b4,b5,bU,bX);if(!bW){e=b6=true}else{if(bW===true){continue}}}if(bW){for(bV=0;(b3=bY[bV])!=null;bV++){if(b3){b6=bR(b3,bW,bV,bY);b2=bU^b6;if(b4&&b6!=null){if(b2){e=true}else{bY[bV]=false}}else{if(b2){b5.push(b3);e=true}}}}}if(b6!==L){if(!b4){bY=b5}b1=b1.replace(bE.match[bZ],"");if(!e){return[]}break}}}if(b1===bS){if(e==null){by.error(b1)}else{break}}bS=b1}return bY};by.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)};var bw=by.getText=function(bU){var bS,bT,e=bU.nodeType,bR="";if(e){if(e===1||e===9||e===11){if(typeof bU.textContent==="string"){return bU.textContent}else{if(typeof bU.innerText==="string"){return bU.innerText.replace(bO,"")}else{for(bU=bU.firstChild;bU;bU=bU.nextSibling){bR+=bw(bU)}}}}else{if(e===3||e===4){return bU.nodeValue}}}else{for(bS=0;(bT=bU[bS]);bS++){if(bT.nodeType!==8){bR+=bw(bT)}}}return bR};var bE=by.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(e){return e.getAttribute("href")},type:function(e){return e.getAttribute("type")}},relative:{"+":function(bW,bR){var bT=typeof bR==="string",bV=bT&&!bQ.test(bR),bX=bT&&!bV;if(bV){bR=bR.toLowerCase()}for(var bS=0,e=bW.length,bU;bS<e;bS++){if((bU=bW[bS])){while((bU=bU.previousSibling)&&bU.nodeType!==1){}bW[bS]=bX||bU&&bU.nodeName.toLowerCase()===bR?bU||false:bU===bR}}if(bX){by.filter(bR,bW,true)}},">":function(bW,bR){var bV,bU=typeof bR==="string",bS=0,e=bW.length;if(bU&&!bQ.test(bR)){bR=bR.toLowerCase();for(;bS<e;bS++){bV=bW[bS];if(bV){var bT=bV.parentNode;bW[bS]=bT.nodeName.toLowerCase()===bR?bT:falsLine truncated
|
||||
* jQuery UI 1.8.18
|
||||
*
|
||||
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://docs.jquery.com/UI
|
||||
*/
|
||||
(function(a,d){a.ui=a.ui||{};if(a.ui.version){return}a.extend(a.ui,{version:"1.8.18",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(e,f){return typeof e==="number"?this.each(function(){var g=this;setTimeout(function(){a(g).focus();if(f){f.call(g)}},e)}):this._focus.apply(this,arguments)},scrollParent:function(){var e;if((a.browser.msie&&(/(static|relative)/).test(this.css("position")))||(/absolute/).test(this.css("position"))){e=this.parents().filter(function(){return(/(relative|absolute|fixed)/).test(a.curCSS(this,"position",1))&&(/(auto|scroll)/).test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0)}else{e=this.parents().filter(function(){return(/(auto|scroll)/).test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0)}return(/fixed/).test(this.css("position"))||!e.length?a(document):e},zIndex:function(h){if(h!==d){return this.css("zIndex",h)}if(this.length){var f=a(this[0]),e,g;while(f.length&&f[0]!==document){e=f.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){g=parseInt(f.css("zIndex"),10);if(!isNaN(g)&&g!==0){return g}}f=f.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});a.each(["Width","Height"],function(g,e){var f=e==="Width"?["Left","Right"]:["Top","Bottom"],h=e.toLowerCase(),k={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};function j(m,l,i,n){a.each(f,function(){l-=parseFloat(a.curCSS(m,"padding"+this,true))||0;if(i){l-=parseFloat(a.curCSS(m,"border"+this+"Width",true))||0}if(n){l-=parseFloat(a.curCSS(m,"margin"+this,true))||0}});return l}a.fn["inner"+e]=function(i){if(i===d){return k["inner"+e].call(this)}return this.each(function(){a(this).css(h,j(this,i)+"px")})};a.fn["outer"+e]=function(i,l){if(typeof i!=="number"){return k["outer"+e].call(this,i)}return this.each(function(){a(this).css(h,j(this,i,true,l)+"px")})}});function c(g,e){var j=g.nodeName.toLowerCase();if("area"===j){var i=g.parentNode,h=i.name,f;if(!g.href||!h||i.nodeName.toLowerCase()!=="map"){return false}f=a("img[usemap=#"+h+"]")[0];return !!f&&b(f)}return(/input|select|textarea|button|object/.test(j)?!g.disabled:"a"==j?g.href||e:e)&&b(g)}function b(e){return !a(e).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}a.extend(a.expr[":"],{data:function(g,f,e){return !!a.data(g,e[3])},focusable:function(e){return c(e,!isNaN(a.attr(e,"tabindex")))},tabbable:function(g){var e=a.attr(g,"tabindex"),f=isNaN(e);return(f||e>=0)&&c(g,!f)}});a(function(){var e=document.body,f=e.appendChild(f=document.createElement("div"));f.offsetHeight;a.extend(f.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});a.support.minHeight=f.offsetHeight===100;a.support.selectstart="onselectstart" in f;e.removeChild(f).style.display="none"});a.extend(a.ui,{plugin:{add:function(f,g,j){var h=a.ui[f].prototype;for(var e in j){h.plugins[e]=h.plugins[e]||[];h.plugins[e].push([g,j[e]])}},call:function(e,g,f){var j=e.plugins[g];if(!j||!e.element[0].parentNode){return}for(var h=0;h<j.length;h++){if(e.options[j[h][0]]){j[h][1].apply(e.element,f)}}}},contains:function(f,e){return document.compareDocumentPosition?f.compareDocumentPosition(e)&16:f!==e&&f.contains(e)},hasScroll:function(h,f){if(a(h).css("overflow")==="hidden"){return false}var e=(f&&f==="left")?"scrollLeft":"scrollTop",g=false;if(h[e]>0){return true}h[e]=1;g=(h[e]>0);h[e]=0;return g},isOverAxis:function(f,e,g){return(f>e)&&(f<(e+g))},isOver:function(j,f,i,h,e,g){return a.ui.isOverAxis(j,i,e)&&a.ui.isOverAxis(f,h,g)}})})(jQuery);/*!
|
||||
* jQuery UI Widget 1.8.18
|
||||
*
|
||||
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://docs.jquery.com/UI/Widget
|
||||
*/
|
||||
(function(b,d){if(b.cleanData){var c=b.cleanData;b.cleanData=function(f){for(var g=0,h;(h=f[g])!=null;g++){try{b(h).triggerHandler("remove")}catch(j){}}c(f)}}else{var a=b.fn.remove;b.fn.remove=function(e,f){return this.each(function(){if(!f){if(!e||b.filter(e,[this]).length){b("*",this).add([this]).each(function(){try{b(this).triggerHandler("remove")}catch(g){}})}}return a.call(b(this),e,f)})}}b.widget=function(f,h,e){var g=f.split(".")[0],j;f=f.split(".")[1];j=g+"-"+f;if(!e){e=h;h=b.Widget}b.expr[":"][j]=function(k){return !!b.data(k,f)};b[g]=b[g]||{};b[g][f]=function(k,l){if(arguments.length){this._createWidget(k,l)}};var i=new h();i.options=b.extend(true,{},i.options);b[g][f].prototype=b.extend(true,i,{namespace:g,widgetName:f,widgetEventPrefix:b[g][f].prototype.widgetEventPrefix||f,widgetBaseClass:j},e);b.widget.bridge(f,b[g][f])};b.widget.bridge=function(f,e){b.fn[f]=function(i){var g=typeof i==="string",h=Array.prototype.slice.call(arguments,1),j=this;i=!g&&h.length?b.extend.apply(null,[true,i].concat(h)):i;if(g&&i.charAt(0)==="_"){return j}if(g){this.each(function(){var k=b.data(this,f),l=k&&b.isFunction(k[i])?k[i].apply(k,h):k;if(l!==k&&l!==d){j=l;return false}})}else{this.each(function(){var k=b.data(this,f);if(k){k.option(i||{})._init()}else{b.data(this,f,new e(i,this))}})}return j}};b.Widget=function(e,f){if(arguments.length){this._createWidget(e,f)}};b.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(f,g){b.data(g,this.widgetName,this);this.element=b(g);this.options=b.extend(true,{},this.options,this._getCreateOptions(),f);var e=this;this.element.bind("remove."+this.widgetName,function(){e.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){return b.metadata&&b.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(f,g){var e=f;if(arguments.length===0){return b.extend({},this.options)}if(typeof f==="string"){if(g===d){return this.options[f]}e={};e[f]=g}this._setOptions(e);return this},_setOptions:function(f){var e=this;b.each(f,function(g,h){e._setOption(g,h)});return this},_setOption:function(e,f){this.options[e]=f;if(e==="disabled"){this.widget()[f?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",f)}return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(e,f,g){var j,i,h=this.options[e];g=g||{};f=b.Event(f);f.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase();f.target=this.element[0];i=f.originalEvent;if(i){for(j in i){if(!(j in f)){f[j]=i[j]}}}this.element.trigger(f,g);return !(b.isFunction(h)&&h.call(this.element[0],f,g)===false||f.isDefaultPrevented())}}})(jQuery);/*!
|
||||
* jQuery UI Mouse 1.8.18
|
||||
*
|
||||
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://docs.jquery.com/UI/Mouse
|
||||
*
|
||||
* Depends:
|
||||
* jquery.ui.widget.js
|
||||
*/
|
||||
(function(b,c){var a=false;b(document).mouseup(function(d){a=false});b.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var d=this;this.element.bind("mousedown."+this.widgetName,function(e){return d._mouseDown(e)}).bind("click."+this.widgetName,function(e){if(true===b.data(e.target,d.widgetName+".preventClickEvent")){b.removeData(e.target,d.widgetName+".preventClickEvent");e.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName)},_mouseDown:function(f){if(a){return}(this._mouseStarted&&this._mouseUp(f));this._mouseDownEvent=f;var e=this,g=(f.which==1),d=(typeof this.options.cancel=="string"&&f.target.nodeName?b(f.target).closest(this.options.cancel).length:false);if(!g||d||!this._mouseCapture(f)){return true}this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet){this._mouseDelayTimer=setTimeout(function(){e.mouseDelayMet=true},this.options.delay)}if(this._mouseDistanceMet(f)&&this._mouseDelayMet(f)){this._mouseStarted=(this._mouseStart(f)!==false);if(!this._mouseStarted){f.preventDefault();return true}}if(true===b.data(f.target,this.widgetName+".preventClickEvent")){b.removeData(f.target,this.widgetName+".preventClickEvent")}this._mouseMoveDelegate=function(h){return e._mouseMove(h)};this._mouseUpDelegate=function(h){return e._mouseUp(h)};b(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);f.preventDefault();a=true;return true},_mouseMove:function(d){if(b.browser.msie&&!(document.documentMode>=9)&&!d.button){return this._mouseUp(d)}if(this._mouseStarted){this._mouseDrag(d);return d.preventDefault()}if(this._mouseDistanceMet(d)&&this._mouseDelayMet(d)){this._mouseStarted=(this._mouseStart(this._mouseDownEvent,d)!==false);(this._mouseStarted?this._mouseDrag(d):this._mouseUp(d))}return !this._mouseStarted},_mouseUp:function(d){b(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;if(d.target==this._mouseDownEvent.target){b.data(d.target,this.widgetName+".preventClickEvent",true)}this._mouseStop(d)}return false},_mouseDistanceMet:function(d){return(Math.max(Math.abs(this._mouseDownEvent.pageX-d.pageX),Math.abs(this._mouseDownEvent.pageY-d.pageY))>=this.options.distance)},_mouseDelayMet:function(d){return this.mouseDelayMet},_mouseStart:function(d){},_mouseDrag:function(d){},_mouseStop:function(d){},_mouseCapture:function(d){return true}})})(jQuery);(function(c,d){c.widget("ui.resizable",c.ui.mouse,{widgetEventPrefix:"resize",options:{alsoResize:false,animate:false,animateDuration:"slow",animateEasing:"swing",aspectRatio:false,autoHide:false,containment:false,ghost:false,grid:false,handles:"e,s,se",helper:false,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1000},_create:function(){var f=this,k=this.options;this.element.addClass("ui-resizable");c.extend(this,{_aspectRatio:!!(k.aspectRatio),aspectRatio:k.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:k.helper||k.ghost||k.animate?k.helper||"ui-resizable-helper":null});if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)){this.element.wrap(c('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle=this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=k.handles||(!c(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all"){this.handles="n,e,s,w,se,sw,ne,nw"}var l=this.handles.split(",");this.handles={};for(var g=0;g<l.length;g++){var j=c.trim(l[g]),e="ui-resizable-"+j;var h=c('<div class="ui-resizable-handle '+e+'"></div>');if(/sw|se|ne|nw/.test(j)){h.css({zIndex:++k.zIndex})}if("se"==j){h.addClass("ui-icon ui-icon-gripsmall-diagonal-se")}this.handles[j]=".ui-resizable-"+j;this.element.append(h)}}this._renderLine truncated
|
||||
* jQuery hashchange event - v1.3 - 7/21/2010
|
||||
* http://benalman.com/projects/jquery-hashchange-plugin/
|
||||
*
|
||||
* Copyright (c) 2010 "Cowboy" Ben Alman
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* http://benalman.com/about/license/
|
||||
*/
|
||||
(function($,e,b){var c="hashchange",h=document,f,g=$.event.special,i=h.documentMode,d="on"+c in e&&(i===b||i>7);function a(j){j=j||location.href;return"#"+j.replace(/^[^#]*#?(.*)$/,"$1")}$.fn[c]=function(j){return j?this.bind(c,j):this.trigger(c)};$.fn[c].delay=50;g[c]=$.extend(g[c],{setup:function(){if(d){return false}$(f.start)},teardown:function(){if(d){return false}$(f.stop)}});f=(function(){var j={},p,m=a(),k=function(q){return q},l=k,o=k;j.start=function(){p||n()};j.stop=function(){p&&clearTimeout(p);p=b};function n(){var r=a(),q=o(m);if(r!==m){l(m=r,q);$(e).trigger(c)}else{if(q!==m){location.href=location.href.replace(/#.*/,"")+q}}p=setTimeout(n,$.fn[c].delay)}$.browser.msie&&!d&&(function(){var q,r;j.start=function(){if(!q){r=$.fn[c].src;r=r&&r+a();q=$('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){r||l(a());n()}).attr("src",r||"javascript:0").insertAfter("body")[0].contentWindow;h.onpropertychange=function(){try{if(event.propertyName==="title"){q.document.title=h.title}}catch(s){}}}};j.stop=k;o=function(){return a(q.location.href)};l=function(v,s){var u=q.document,t=$.fn[c].domain;if(v!==s){u.title=h.title;u.open();t&&u.write('<script>document.domain="'+t+'"<\/script>');u.close();q.location.hash=v}}})();return j})()})(jQuery,this);(function(c){var a=c.scrollTo=function(f,e,d){c(window).scrollTo(f,e,d)};a.defaults={axis:"xy",duration:parseFloat(c.fn.jquery)>=1.3?0:1};a.window=function(d){return c(window)._scrollable()};c.fn._scrollable=function(){return this.map(function(){var e=this,d=!e.nodeName||c.inArray(e.nodeName.toLowerCase(),["iframe","#document","html","body"])!=-1;if(!d){return e}var f=(e.contentWindow||e).document||e.ownerDocument||e;return c.browser.safari||f.compatMode=="BackCompat"?f.body:f.documentElement})};c.fn.scrollTo=function(f,e,d){if(typeof e=="object"){d=e;e=0}if(typeof d=="function"){d={onAfter:d}}if(f=="max"){f=9000000000}d=c.extend({},a.defaults,d);e=e||d.speed||d.duration;d.queue=d.queue&&d.axis.length>1;if(d.queue){e/=2}d.offset=b(d.offset);d.over=b(d.over);return this._scrollable().each(function(){var l=this,j=c(l),k=f,i,g={},m=j.is("html,body");switch(typeof k){case"number":case"string":if(/^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(k)){k=b(k);break}k=c(k,this);case"object":if(k.is||k.style){i=(k=c(k)).offset()}}c.each(d.axis.split(""),function(q,r){var s=r=="x"?"Left":"Top",u=s.toLowerCase(),p="scroll"+s,o=l[p],n=a.max(l,r);if(i){g[p]=i[u]+(m?0:o-j.offset()[u]);if(d.margin){g[p]-=parseInt(k.css("margin"+s))||0;g[p]-=parseInt(k.css("border"+s+"Width"))||0}g[p]+=d.offset[u]||0;if(d.over[u]){g[p]+=k[r=="x"?"width":"height"]()*d.over[u]}}else{var t=k[u];g[p]=t.slice&&t.slice(-1)=="%"?parseFloat(t)/100*n:t}if(/^\d+$/.test(g[p])){g[p]=g[p]<=0?0:Math.min(g[p],n)}if(!q&&d.queue){if(o!=g[p]){h(d.onAfterFirst)}delete g[p]}});h(d.onAfter);function h(n){j.animate(g,e,d.easing,n&&function(){n.call(this,f,d)})}}).end()};a.max=function(j,i){var h=i=="x"?"Width":"Height",e="scroll"+h;if(!c(j).is("html,body")){return j[e]-c(j)[h.toLowerCase()]()}var g="client"+h,f=j.ownerDocument.documentElement,d=j.ownerDocument.body;return Math.max(f[e],d[e])-Math.min(f[g],d[g])};function b(d){return typeof d=="object"?d:{top:d,left:d}}})(jQuery);/*!
|
||||
PowerTip - v1.2.0 - 2013-04-03
|
||||
http://stevenbenner.github.com/jquery-powertip/
|
||||
Copyright (c) 2013 Steven Benner (http://stevenbenner.com/).
|
||||
Released under MIT license.
|
||||
https://raw.github.com/stevenbenner/jquery-powertip/master/LICENSE.txt
|
||||
*/
|
||||
(function(a){if(typeof define==="function"&&define.amd){define(["jquery"],a)}else{a(jQuery)}}(function(k){var A=k(document),s=k(window),w=k("body");var n="displayController",e="hasActiveHover",d="forcedOpen",u="hasMouseMove",f="mouseOnToPopup",g="originalTitle",y="powertip",o="powertipjq",l="powertiptarget",E=180/Math.PI;var c={isTipOpen:false,isFixedTipOpen:false,isClosing:false,tipOpenImminent:false,activeHover:null,currentX:0,currentY:0,previousX:0,previousY:0,desyncTimeout:null,mouseTrackingActive:false,delayInProgress:false,windowWidth:0,windowHeight:0,scrollTop:0,scrollLeft:0};var p={none:0,top:1,bottom:2,left:4,right:8};k.fn.powerTip=function(F,N){if(!this.length){return this}if(k.type(F)==="string"&&k.powerTip[F]){return k.powerTip[F].call(this,this,N)}var O=k.extend({},k.fn.powerTip.defaults,F),G=new x(O);h();this.each(function M(){var R=k(this),Q=R.data(y),P=R.data(o),T=R.data(l),S;if(R.data(n)){k.powerTip.destroy(R)}S=R.attr("title");if(!Q&&!T&&!P&&S){R.data(y,S);R.data(g,S);R.removeAttr("title")}R.data(n,new t(R,O,G))});if(!O.manual){this.on({"mouseenter.powertip":function J(P){k.powerTip.show(this,P)},"mouseleave.powertip":function L(){k.powerTip.hide(this)},"focus.powertip":function K(){k.powerTip.show(this)},"blur.powertip":function H(){k.powerTip.hide(this,true)},"keydown.powertip":function I(P){if(P.keyCode===27){k.powerTip.hide(this,true)}}})}return this};k.fn.powerTip.defaults={fadeInTime:200,fadeOutTime:100,followMouse:false,popupId:"powerTip",intentSensitivity:7,intentPollInterval:100,closeDelay:100,placement:"n",smartPlacement:false,offset:10,mouseOnToPopup:false,manual:false};k.fn.powerTip.smartPlacementLists={n:["n","ne","nw","s"],e:["e","ne","se","w","nw","sw","n","s","e"],s:["s","se","sw","n"],w:["w","nw","sw","e","ne","se","n","s","w"],nw:["nw","w","sw","n","s","se","nw"],ne:["ne","e","se","n","s","sw","ne"],sw:["sw","w","nw","s","n","ne","sw"],se:["se","e","ne","s","n","nw","se"],"nw-alt":["nw-alt","n","ne-alt","sw-alt","s","se-alt","w","e"],"ne-alt":["ne-alt","n","nw-alt","se-alt","s","sw-alt","e","w"],"sw-alt":["sw-alt","s","se-alt","nw-alt","n","ne-alt","w","e"],"se-alt":["se-alt","s","sw-alt","ne-alt","n","nw-alt","e","w"]};k.powerTip={show:function z(F,G){if(G){i(G);c.previousX=G.pageX;c.previousY=G.pageY;k(F).data(n).show()}else{k(F).first().data(n).show(true,true)}return F},reposition:function r(F){k(F).first().data(n).resetPosition();return F},hide:function D(G,F){if(G){k(G).first().data(n).hide(F)}else{if(c.activeHover){c.activeHover.data(n).hide(true)}}return G},destroy:function C(G){k(G).off(".powertip").each(function F(){var I=k(this),H=[g,n,e,d];if(I.data(g)){I.attr("title",I.data(g));H.push(y)}I.removeData(H)});return G}};k.powerTip.showTip=k.powerTip.show;k.powerTip.closeTip=k.powerTip.hide;function b(){var F=this;F.top="auto";F.left="auto";F.right="auto";F.bottom="auto";F.set=function(H,G){if(k.isNumeric(G)){F[H]=Math.round(G)}}}function t(K,N,F){var J=null;function L(P,Q){M();if(!K.data(e)){if(!P){c.tipOpenImminent=true;J=setTimeout(function O(){J=null;I()},N.intentPollInterval)}else{if(Q){K.data(d,true)}F.showTip(K)}}}function G(P){M();c.tipOpenImminent=false;if(K.data(e)){K.data(d,false);if(!P){c.delayInProgress=true;J=setTimeout(function O(){J=null;F.hideTip(K);c.delayInProgress=false},N.closeDelay)}else{F.hideTip(K)}}}function I(){var Q=Math.abs(c.previousX-c.currentX),O=Math.abs(c.previousY-c.currentY),P=Q+O;if(P<N.intentSensitivity){F.showTip(K)}else{c.previousX=c.currentX;c.previousY=c.currentY;L()}}function M(){J=clearTimeout(J);c.delayInProgress=false}function H(){F.resetPosition(K)}this.show=L;this.hide=G;this.cancel=M;this.resetPosition=H}function j(){function G(M,L,J,O,P){var K=L.split("-")[0],N=new b(),I;if(q(M)){I=H(M,K)}else{I=F(M,K)}switch(L){case"n":N.set("left",I.left-(J/2));N.set("bottom",c.windowHeight-I.top+P);break;case"e":N.set("left",I.left+P);N.set("top",I.top-(O/2));break;case"s":N.set("left",I.left-(J/2));N.set("top",I.top+P);break;case"w":N.set("top",I.top-(O/2));N.set("right",c.windowWidth-I.left+P);break;case"nw":N.set("bottom",c.windowHeight-I.top+P);N.set("right",c.windowWidth-I.left-20);break;case"nw-alt":N.set("left",I.left);N.set("bottom",c.windowHeight-I.top+P);break;case"ne":N.set("left",I.left-20);N.set("bottom",c.windowHeight-I.top+P);break;case"ne-alt":N.set("bottom",c.windowHeight-I.top+P);N.set("right",c.windowWidth-I.left);break;case"sw":N.set("top",I.top+P);N.set("right",c.windowWidth-I.left-20);break;case"sw-alt":N.set("left",I.left);N.set("top",I.top+P);break;case"se":N.set("left",I.left-20);N.set("top",I.top+P);break;case"se-alt":N.set("top",I.top+P);N.set("right",c.windowWidth-I.left);break}return N}function F(K,J){var O=K.offset(),N=K.outerWidth(),I=K.outerHeight(),M,L;switch(J){case"n":M=O.left+N/2;L=O.top;break;case"e":M=O.left+N;L=O.top+I/2;break;case"s":M=O.left+N/2;L=O.top+I;break;case"w":M=O.left;L=O.top+I/2;break;case"nw":M=O.left;L=O.top;break;case"ne":M=O.left+N;L=O.top;break;case"sw":M=O.left;L=O.top+I;bLine truncated
|
||||
* jQuery UI Touch Punch 0.2.3
|
||||
*
|
||||
* Copyright 2011–2014, Dave Furfero
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
*
|
||||
* Depends:
|
||||
* jquery.ui.widget.js
|
||||
* jquery.ui.mouse.js
|
||||
*/
|
||||
(function(b){b.support.touch="ontouchend" in document;if(!b.support.touch){return}var d=b.ui.mouse.prototype,f=d._mouseInit,c=d._mouseDestroy,a;function e(h,i){if(h.originalEvent.touches.length>1){return}h.preventDefault();var j=h.originalEvent.changedTouches[0],g=document.createEvent("MouseEvents");g.initMouseEvent(i,true,true,window,1,j.screenX,j.screenY,j.clientX,j.clientY,false,false,false,false,0,null);h.target.dispatchEvent(g)}d._touchStart=function(h){var g=this;if(a||!g._mouseCapture(h.originalEvent.changedTouches[0])){return}a=true;g._touchMoved=false;e(h,"mouseover");e(h,"mousemove");e(h,"mousedown")};d._touchMove=function(g){if(!a){return}this._touchMoved=true;e(g,"mousemove")};d._touchEnd=function(g){if(!a){return}e(g,"mouseup");e(g,"mouseout");if(!this._touchMoved){e(g,"click")}a=false};d._mouseInit=function(){var g=this;g.element.bind({touchstart:b.proxy(g,"_touchStart"),touchmove:b.proxy(g,"_touchMove"),touchend:b.proxy(g,"_touchEnd")});f.call(g)};d._mouseDestroy=function(){var g=this;g.element.unbind({touchstart:b.proxy(g,"_touchStart"),touchmove:b.proxy(g,"_touchMove"),touchend:b.proxy(g,"_touchEnd")});c.call(g)}})(jQuery);/*!
|
||||
* SmartMenus jQuery Plugin - v1.0.0 - January 27, 2016
|
||||
* http://www.smartmenus.org/
|
||||
*
|
||||
* Copyright Vasil Dinkov, Vadikom Web Ltd.
|
||||
* http://vadikom.com
|
||||
*
|
||||
* Licensed MIT
|
||||
*/
|
||||
(function(a){if(typeof define==="function"&&define.amd){define(["jquery"],a)}else{if(typeof module==="object"&&typeof module.exports==="object"){module.exports=a(require("jquery"))}else{a(jQuery)}}}(function(a){var b=[],e=!!window.createPopup,f=false,d="ontouchstart" in window,h=false,g=window.requestAnimationFrame||function(l){return setTimeout(l,1000/60)},c=window.cancelAnimationFrame||function(l){clearTimeout(l)};function k(m){var n=".smartmenus_mouse";if(!h&&!m){var o=true,l=null;a(document).bind(i([["mousemove",function(s){var t={x:s.pageX,y:s.pageY,timeStamp:new Date().getTime()};if(l){var q=Math.abs(l.x-t.x),p=Math.abs(l.y-t.y);if((q>0||p>0)&&q<=2&&p<=2&&t.timeStamp-l.timeStamp<=300){f=true;if(o){var r=a(s.target).closest("a");if(r.is("a")){a.each(b,function(){if(a.contains(this.$root[0],r[0])){this.itemEnter({currentTarget:r[0]});return false}})}o=false}}}l=t}],[d?"touchstart":"pointerover pointermove pointerout MSPointerOver MSPointerMove MSPointerOut",function(p){if(j(p.originalEvent)){f=false}}]],n));h=true}else{if(h&&m){a(document).unbind(n);h=false}}}function j(l){return !/^(4|mouse)$/.test(l.pointerType)}function i(l,n){if(!n){n=""}var m={};a.each(l,function(o,p){m[p[0].split(" ").join(n+" ")+n]=p[1]});return m}a.SmartMenus=function(m,l){this.$root=a(m);this.opts=l;this.rootId="";this.accessIdPrefix="";this.$subArrow=null;this.activatedItems=[];this.visibleSubMenus=[];this.showTimeout=0;this.hideTimeout=0;this.scrollTimeout=0;this.clickActivated=false;this.focusActivated=false;this.zIndexInc=0;this.idInc=0;this.$firstLink=null;this.$firstSub=null;this.disabled=false;this.$disableOverlay=null;this.$touchScrollingSub=null;this.cssTransforms3d="perspective" in m.style||"webkitPerspective" in m.style;this.wasCollapsible=false;this.init()};a.extend(a.SmartMenus,{hideAll:function(){a.each(b,function(){this.menuHideAll()})},destroy:function(){while(b.length){b[0].destroy()}k(true)},prototype:{init:function(n){var l=this;if(!n){b.push(this);this.rootId=(new Date().getTime()+Math.random()+"").replace(/\D/g,"");this.accessIdPrefix="sm-"+this.rootId+"-";if(this.$root.hasClass("sm-rtl")){this.opts.rightToLeftSubMenus=true}var r=".smartmenus";this.$root.data("smartmenus",this).attr("data-smartmenus-id",this.rootId).dataSM("level",1).bind(i([["mouseover focusin",a.proxy(this.rootOver,this)],["mouseout focusout",a.proxy(this.rootOut,this)],["keydown",a.proxy(this.rootKeyDown,this)]],r)).delegate("a",i([["mouseenter",a.proxy(this.itemEnter,this)],["mouseleave",a.proxy(this.itemLeave,this)],["mousedown",a.proxy(this.itemDown,this)],["focus",a.proxy(this.itemFocus,this)],["blur",a.proxy(this.itemBlur,this)],["click",a.proxy(this.itemClick,this)]],r));r+=this.rootId;if(this.opts.hideOnClick){a(document).bind(i([["touchstart",a.proxy(this.docTouchStart,this)],["touchmove",a.proxy(this.docTouchMove,this)],["touchend",a.proxy(this.docTouchEnd,this)],["click",a.proxy(this.docClick,this)]],r))}a(window).bind(i([["resize orientationchange",a.proxy(this.winResize,this)]],r));if(this.opts.subIndicators){this.$subArrow=a("<span/>").addClass("sub-arrow");if(this.opts.subIndicatorsText){this.$subArrow.html(this.opts.subIndicatorsText)}}k()}this.$firstSub=this.$root.find("ul").each(function(){l.menuInit(a(this))}).eq(0);this.$firstLink=this.$root.find("a").eq(0);if(this.opts.markCurrentItem){var p=/(index|default)\.[^#\?\/]*/i,m=/#.*/,q=window.location.href.replace(p,""),o=q.replace(m,"");this.$root.find("a").each(function(){var s=this.href.replace(p,""),t=a(this);if(s==q||s==o){t.addClass("current");if(l.opts.markCurrentTree){t.parentsUntil("[data-smartmenus-id]","ul").each(function(){a(this).dataSM("parent-a").addClass("current")})}}})}this.wasCollapsible=this.isCollapsible()},destroy:function(m){if(!m){var n=".smartmenus";this.$root.removeData("smartmenus").removeAttr("data-smartmenus-id").removeDataSM("level").unbind(n).undelegate(n);n+=this.rootId;a(document).unbind(n);a(window).unbind(n);if(this.opts.subIndicators){this.$subArrow=null}}this.menuHideAll();var l=this;this.$root.find("ul").each(function(){var o=a(this);if(o.dataSM("scroll-arrows")){o.dataSM("scroll-arrows").remove()}if(o.dataSM("shown-before")){if(l.opts.subMenusMinWidth||l.opts.subMenusMaxWidth){o.css({width:"",minWidth:"",maxWidth:""}).removeClass("sm-nowrap")}if(o.dataSM("scroll-arrows")){o.dataSM("scroll-arrows").remove()}o.css({zIndex:"",top:"",left:"",marginLeft:"",marginTop:"",display:""})}if((o.attr("id")||"").indexOf(l.accessIdPrefix)==0){o.removeAttr("id")}}).removeDataSM("in-mega").removeDataSM("shown-before").removeDataSM("ie-shim").removeDataSM("scroll-arrows").removeDataSM("parent-a").removeDataSM("level").removeDataSM("beforefirstshowfired").removeAttr("role").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeAttr("aria-expanded");this.$root.find("a.has-submenu").each(function(){var o=a(this);if(o.attr("id").indexOf(l.accessIdPrefix)==0){o.removeAttr("id")}}).removeClass("has-submenu").removeDataSM("sub").removeAttr("aria-haspLine truncated
|
||||
@@ -1,256 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen 1.8.15"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>mi-malloc: mimalloc-doc.h Source File</title>
|
||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="jquery.js"></script>
|
||||
<script type="text/javascript" src="dynsections.js"></script>
|
||||
<link href="navtree.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="resize.js"></script>
|
||||
<script type="text/javascript" src="navtreedata.js"></script>
|
||||
<script type="text/javascript" src="navtree.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(initResizable);
|
||||
/* @license-end */</script>
|
||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||
<script type="text/javascript" src="search/search.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function() { init_search(); });
|
||||
/* @license-end */
|
||||
</script>
|
||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
||||
<link href="mimalloc-doxygen.css" rel="stylesheet" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 56px;">
|
||||
<td id="projectlogo"><img alt="Logo" src="mimalloc-logo.svg"/></td>
|
||||
<td id="projectalign" style="padding-left: 0.5em;">
|
||||
<div id="projectname">mi-malloc
|
||||
 <span id="projectnumber">1.6</span>
|
||||
</div>
|
||||
</td>
|
||||
<td> <div id="MSearchBox" class="MSearchBoxInactive">
|
||||
<span class="left">
|
||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
alt=""/>
|
||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
||||
</span><span class="right">
|
||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- end header part -->
|
||||
<!-- Generated by Doxygen 1.8.15 -->
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
||||
/* @license-end */
|
||||
</script>
|
||||
</div><!-- top -->
|
||||
<div id="side-nav" class="ui-resizable side-nav-resizable">
|
||||
<div id="nav-tree">
|
||||
<div id="nav-tree-contents">
|
||||
<div id="nav-sync" class="sync"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="splitbar" style="-moz-user-select:none;"
|
||||
class="ui-resizable-handle">
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function(){initNavTree('mimalloc-doc_8h_source.html','');});
|
||||
/* @license-end */
|
||||
</script>
|
||||
<div id="doc-content">
|
||||
<!-- window showing the filter options -->
|
||||
<div id="MSearchSelectWindow"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
||||
</div>
|
||||
|
||||
<!-- iframe showing the search results (closed by default) -->
|
||||
<div id="MSearchResultsWindow">
|
||||
<iframe src="javascript:void(0)" frameborder="0"
|
||||
name="MSearchResults" id="MSearchResults">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<div class="headertitle">
|
||||
<div class="title">mimalloc-doc.h</div> </div>
|
||||
</div><!--header-->
|
||||
<div class="contents">
|
||||
<div class="fragment"><div class="line"><a name="l00001"></a><span class="lineno"> 1</span> <span class="comment">/* ----------------------------------------------------------------------------</span></div><div class="line"><a name="l00002"></a><span class="lineno"> 2</span> <span class="comment">Copyright (c) 2018, Microsoft Research, Daan Leijen</span></div><div class="line"><a name="l00003"></a><span class="lineno"> 3</span> <span class="comment">This is free software; you can redistribute it and/or modify it under the</span></div><div class="line"><a name="l00004"></a><span class="lineno"> 4</span> <span class="comment">terms of the MIT license. A copy of the license can be found in the file</span></div><div class="line"><a name="l00005"></a><span class="lineno"> 5</span> <span class="comment">"LICENSE" at the root of this distribution.</span></div><div class="line"><a name="l00006"></a><span class="lineno"> 6</span> <span class="comment">-----------------------------------------------------------------------------*/</span></div><div class="line"><a name="l00007"></a><span class="lineno"> 7</span> </div><div class="line"><a name="l00008"></a><span class="lineno"> 8</span> <span class="preprocessor">#error "documentation file only!"</span></div><div class="line"><a name="l00009"></a><span class="lineno"> 9</span> </div><div class="line"><a name="l00010"></a><span class="lineno"> 10</span> </div><div class="line"><a name="l00091"></a><span class="lineno"> 91</span> </div><div class="line"><a name="l00095"></a><span class="lineno"> 95</span> </div><div class="line"><a name="l00099"></a><span class="lineno"> 99</span> <span class="keywordtype">void</span> <a class="code" href="group__malloc.html#gaf2c7b89c327d1f60f59e68b9ea644d95">mi_free</a>(<span class="keywordtype">void</span>* p);</div><div class="line"><a name="l00100"></a><span class="lineno"> 100</span> </div><div class="line"><a name="l00105"></a><span class="lineno"> 105</span> <span class="keywordtype">void</span>* <a class="code" href="group__malloc.html#ga3406e8b168bc74c8637b11571a6da83a">mi_malloc</a>(<span class="keywordtype">size_t</span> size);</div><div class="line"><a name="l00106"></a><span class="lineno"> 106</span> </div><div class="line"><a name="l00111"></a><span class="lineno"> 111</span> <span class="keywordtype">void</span>* <a class="code" href="group__malloc.html#gafdd9d8bb2986e668ba9884f28af38000">mi_zalloc</a>(<span class="keywordtype">size_t</span> size);</div><div class="line"><a name="l00112"></a><span class="lineno"> 112</span> </div><div class="line"><a name="l00122"></a><span class="lineno"> 122</span> <span class="keywordtype">void</span>* <a class="code" href="group__malloc.html#ga97fedb4f7107c592fd7f0f0a8949a57d">mi_calloc</a>(<span class="keywordtype">size_t</span> count, <span class="keywordtype">size_t</span> size);</div><div class="line"><a name="l00123"></a><span class="lineno"> 123</span> </div><div class="line"><a name="l00136"></a><span class="lineno"> 136</span> <span class="keywordtype">void</span>* <a class="code" href="group__malloc.html#gaf11eb497da57bdfb2de65eb191c69db6">mi_realloc</a>(<span class="keywordtype">void</span>* p, <span class="keywordtype">size_t</span> newsize);</div><div class="line"><a name="l00137"></a><span class="lineno"> 137</span> </div><div class="line"><a name="l00148"></a><span class="lineno"> 148</span> <span class="keywordtype">void</span>* <a class="code" href="group__malloc.html#ga23a0fbb452b5dce8e31fab1a1958cacc">mi_recalloc</a>(<span class="keywordtype">void</span>* p, <span class="keywordtype">size_t</span> count, <span class="keywordtype">size_t</span> size);</div><div class="line"><a name="l00149"></a><span class="lineno"> 149</span> </div><div class="line"><a name="l00163"></a><span class="lineno"> 163</span> <span class="keywordtype">void</span>* <a class="code" href="group__malloc.html#gaaee66a1d483c3e28f585525fb96707e4">mi_expand</a>(<span class="keywordtype">void</span>* p, <span class="keywordtype">size_t</span> newsize);</div><div class="line"><a name="l00164"></a><span class="lineno"> 164</span> </div><div class="line"><a name="l00174"></a><span class="lineno"> 174</span> <span class="keywordtype">void</span>* <a class="code" href="group__malloc.html#ga0b05e2bf0f73e7401ae08597ff782ac6">mi_mallocn</a>(<span class="keywordtype">size_t</span> count, <span class="keywordtype">size_t</span> size);</div><div class="line"><a name="l00175"></a><span class="lineno"> 175</span> </div><div class="line"><a name="l00185"></a><span class="lineno"> 185</span> <span class="keywordtype">void</span>* <a class="code" href="group__malloc.html#ga61d57b4144ba24fba5c1e9b956d13853">mi_reallocn</a>(<span class="keywordtype">void</span>* p, <span class="keywordtype">size_t</spLine truncated
|
||||
<div class="ttc" id="group__cpp_html_gaeaded64eda71ed6b1d569d3e723abc4a"><div class="ttname"><a href="group__cpp.html#gaeaded64eda71ed6b1d569d3e723abc4a">mi_new_nothrow</a></div><div class="ttdeci">void * mi_new_nothrow(size_t n)</div><div class="ttdoc">like mi_malloc, but when out of memory, use std::get_new_handler but return NULL on failure.</div></div>
|
||||
<div class="ttc" id="group__malloc_html_ga61d57b4144ba24fba5c1e9b956d13853"><div class="ttname"><a href="group__malloc.html#ga61d57b4144ba24fba5c1e9b956d13853">mi_reallocn</a></div><div class="ttdeci">void * mi_reallocn(void *p, size_t count, size_t size)</div><div class="ttdoc">Re-allocate memory to count elements of size bytes.</div></div>
|
||||
<div class="ttc" id="group__aligned_html_ga68930196751fa2cca9e1fd0d71bade56"><div class="ttname"><a href="group__aligned.html#ga68930196751fa2cca9e1fd0d71bade56">mi_malloc_aligned</a></div><div class="ttdeci">void * mi_malloc_aligned(size_t size, size_t alignment)</div><div class="ttdoc">Allocate size bytes aligned by alignment.</div></div>
|
||||
<div class="ttc" id="group__zeroinit_html_ga4ff5e92ad73585418a072c9d059e5cf9"><div class="ttname"><a href="group__zeroinit.html#ga4ff5e92ad73585418a072c9d059e5cf9">mi_recalloc_aligned_at</a></div><div class="ttdeci">void * mi_recalloc_aligned_at(void *p, size_t newcount, size_t size, size_t alignment, size_t offset)</div></div>
|
||||
<div class="ttc" id="group__extended_html_ga3bb8468b8cfcc6e2a61d98aee85c5f99"><div class="ttname"><a href="group__extended.html#ga3bb8468b8cfcc6e2a61d98aee85c5f99">mi_stats_reset</a></div><div class="ttdeci">void mi_stats_reset(void)</div><div class="ttdoc">Reset statistics.</div></div>
|
||||
<div class="ttc" id="group__heap_html_gafc603b696bd14cae6da28658f950d98c"><div class="ttname"><a href="group__heap.html#gafc603b696bd14cae6da28658f950d98c">mi_heap_realloc_aligned</a></div><div class="ttdeci">void * mi_heap_realloc_aligned(mi_heap_t *heap, void *p, size_t newsize, size_t alignment)</div></div>
|
||||
<div class="ttc" id="group__options_html_ga459ad98f18b3fc9275474807fe0ca188"><div class="ttname"><a href="group__options.html#ga459ad98f18b3fc9275474807fe0ca188">mi_option_is_enabled</a></div><div class="ttdeci">bool mi_option_is_enabled(mi_option_t option)</div></div>
|
||||
<div class="ttc" id="group__cpp_html_gaab78a32f55149e9fbf432d5288e38e1e"><div class="ttname"><a href="group__cpp.html#gaab78a32f55149e9fbf432d5288e38e1e">mi_new_realloc</a></div><div class="ttdeci">void * mi_new_realloc(void *p, size_t newsize)</div><div class="ttdoc">like mi_realloc(), but when out of memory, use std::get_new_handler and raise std::bad_alloc exceptio...</div></div>
|
||||
<div class="ttc" id="group__malloc_html_ga23a0fbb452b5dce8e31fab1a1958cacc"><div class="ttname"><a href="group__malloc.html#ga23a0fbb452b5dce8e31fab1a1958cacc">mi_recalloc</a></div><div class="ttdeci">void * mi_recalloc(void *p, size_t count, size_t size)</div><div class="ttdoc">Re-allocate memory to count elements of size bytes, with extra memory initialized to zero.</div></div>
|
||||
<div class="ttc" id="group__malloc_html_ga0b05e2bf0f73e7401ae08597ff782ac6"><div class="ttname"><a href="group__malloc.html#ga0b05e2bf0f73e7401ae08597ff782ac6">mi_mallocn</a></div><div class="ttdeci">void * mi_mallocn(size_t count, size_t size)</div><div class="ttdoc">Allocate count elements of size bytes.</div></div>
|
||||
<div class="ttc" id="group__posix_html_ga4531c9e775bb3ae12db57c1ba8a5d7de"><div class="ttname"><a href="group__posix.html#ga4531c9e775bb3ae12db57c1ba8a5d7de">mi_malloc_size</a></div><div class="ttdeci">size_t mi_malloc_size(const void *p)</div></div>
|
||||
<div class="ttc" id="group__options_html_ga9a13d05fcb77489cb06d4d017ebd8bed"><div class="ttname"><a href="group__options.html#ga9a13d05fcb77489cb06d4d017ebd8bed">mi_option_set_enabled</a></div><div class="ttdeci">void mi_option_set_enabled(mi_option_t option, bool enable)</div></div>
|
||||
<div class="ttc" id="group__posix_html_gacff84f226ba9feb2031b8992e5579447"><div class="ttname"><a href="group__posix.html#gacff84f226ba9feb2031b8992e5579447">mi_posix_memalign</a></div><div class="ttdeci">int mi_posix_memalign(void **p, size_t alignment, size_t size)</div></div>
|
||||
<div class="ttc" id="group__extended_html_ga854b1de8cb067c7316286c28b2fcd3d1"><div class="ttname"><a href="group__extended.html#ga854b1de8cb067c7316286c28b2fcd3d1">mi_stats_merge</a></div><div class="ttdeci">void mi_stats_merge(void)</div><div class="ttdoc">Merge thread local statistics with the main statistics and reset.</div></div>
|
||||
<div class="ttc" id="group__cpp_html_gae7bc4f56cd57ed3359060ff4f38bda81"><div class="ttname"><a href="group__cpp.html#gae7bc4f56cd57ed3359060ff4f38bda81">mi_new_n</a></div><div class="ttdeci">void * mi_new_n(size_t count, size_t size) noexcept(false)</div><div class="ttdoc">like mi_mallocn(), but when out of memory, use std::get_new_handler and raise std::bad_alloc exceptio...</div></div>
|
||||
<div class="ttc" id="group__options_html_ga7ef623e440e6e5545cb08c94e71e4b90"><div class="ttname"><a href="group__options.html#ga7ef623e440e6e5545cb08c94e71e4b90">mi_option_set_default</a></div><div class="ttdeci">void mi_option_set_default(mi_option_t option, long value)</div></div>
|
||||
<div class="ttc" id="group__extended_html_ga537f13b299ddf801e49a5a94fde02c79"><div class="ttname"><a href="group__extended.html#ga537f13b299ddf801e49a5a94fde02c79">mi_stats_print_out</a></div><div class="ttdeci">void mi_stats_print_out(mi_output_fun *out, void *arg)</div><div class="ttdoc">Print the main statistics.</div></div>
|
||||
<div class="ttc" id="group__extended_html_ga251d369cda3f1c2a955c555486ed90e5"><div class="ttname"><a href="group__extended.html#ga251d369cda3f1c2a955c555486ed90e5">mi_error_fun</a></div><div class="ttdeci">void() mi_error_fun(int err, void *arg)</div><div class="ttdoc">Type of error callback functions.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:391</div></div>
|
||||
<div class="ttc" id="group__zeroinit_html_ga8c292e142110229a2980b37ab036dbc6"><div class="ttname"><a href="group__zeroinit.html#ga8c292e142110229a2980b37ab036dbc6">mi_rezalloc</a></div><div class="ttdeci">void * mi_rezalloc(void *p, size_t newsize)</div></div>
|
||||
<div class="ttc" id="group__options_html_ggafebf7ed116adb38ae5218bc3ce06884ca1e8de72c93da7ff22d91e1e27b52ac2b"><div class="ttname"><a href="group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca1e8de72c93da7ff22d91e1e27b52ac2b">mi_option_eager_commit</a></div><div class="ttdoc">Eagerly commit segments (4MiB) (enabled by default).</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:781</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga903104592c8ed53417a3762da6241133"><div class="ttname"><a href="group__heap.html#ga903104592c8ed53417a3762da6241133">mi_heap_zalloc</a></div><div class="ttdeci">void * mi_heap_zalloc(mi_heap_t *heap, size_t size)</div><div class="ttdoc">Allocate zero-initialized in a specific heap.</div></div>
|
||||
<div class="ttc" id="group__options_html_gaf84921c32375e25754dc2ee6a911fa60"><div class="ttname"><a href="group__options.html#gaf84921c32375e25754dc2ee6a911fa60">mi_option_set</a></div><div class="ttdeci">void mi_option_set(mi_option_t option, long value)</div></div>
|
||||
<div class="ttc" id="group__options_html_ggafebf7ed116adb38ae5218bc3ce06884ca32ce97ece29f69e82579679cf8a307ad"><div class="ttname"><a href="group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca32ce97ece29f69e82579679cf8a307ad">mi_option_eager_region_commit</a></div><div class="ttdoc">Eagerly commit large (256MiB) memory regions (enabled by default, except on Windows)</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:782</div></div>
|
||||
<div class="ttc" id="group__posix_html_ga705dc7a64bffacfeeb0141501a5c35d7"><div class="ttname"><a href="group__posix.html#ga705dc7a64bffacfeeb0141501a5c35d7">mi_cfree</a></div><div class="ttdeci">void mi_cfree(void *p)</div><div class="ttdoc">Just as free but also checks if the pointer p belongs to our heap.</div></div>
|
||||
<div class="ttc" id="group__zeroinit_html_ga3e7e5c291acf1c7fd7ffd9914a9f945f"><div class="ttname"><a href="group__zeroinit.html#ga3e7e5c291acf1c7fd7ffd9914a9f945f">mi_recalloc_aligned</a></div><div class="ttdeci">void * mi_recalloc_aligned(void *p, size_t newcount, size_t size, size_t alignment)</div></div>
|
||||
<div class="ttc" id="group__options_html_ggafebf7ed116adb38ae5218bc3ce06884ca5b4357b74be0d87568036c32eb1a2e4a"><div class="ttname"><a href="group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca5b4357b74be0d87568036c32eb1a2e4a">_mi_option_last</a></div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:793</div></div>
|
||||
<div class="ttc" id="group__aligned_html_gaf66a9ae6c6f08bd6be6fb6ea771faffb"><div class="ttname"><a href="group__aligned.html#gaf66a9ae6c6f08bd6be6fb6ea771faffb">mi_realloc_aligned_at</a></div><div class="ttdeci">void * mi_realloc_aligned_at(void *p, size_t newsize, size_t alignment, size_t offset)</div></div>
|
||||
<div class="ttc" id="group__analysis_html_ae0085e6e1cf059a4eb7767e30e9991b8"><div class="ttname"><a href="group__analysis.html#ae0085e6e1cf059a4eb7767e30e9991b8">mi_heap_area_t::blocks</a></div><div class="ttdeci">void * blocks</div><div class="ttdoc">start of the area containing heap blocks</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:737</div></div>
|
||||
<div class="ttc" id="group__aligned_html_ga4028d1cf4aa4c87c880747044a8322ae"><div class="ttname"><a href="group__aligned.html#ga4028d1cf4aa4c87c880747044a8322ae">mi_realloc_aligned</a></div><div class="ttdeci">void * mi_realloc_aligned(void *p, size_t newsize, size_t alignment)</div></div>
|
||||
<div class="ttc" id="group__options_html_ga04180ae41b0d601421dd62ced40ca050"><div class="ttname"><a href="group__options.html#ga04180ae41b0d601421dd62ced40ca050">mi_option_enable</a></div><div class="ttdeci">void mi_option_enable(mi_option_t option)</div></div>
|
||||
<div class="ttc" id="group__posix_html_gad5a69c8fea96aa2b7a7c818c2130090a"><div class="ttname"><a href="group__posix.html#gad5a69c8fea96aa2b7a7c818c2130090a">mi__posix_memalign</a></div><div class="ttdeci">int mi__posix_memalign(void **p, size_t alignment, size_t size)</div></div>
|
||||
<div class="ttc" id="group__malloc_html_gaf2c7b89c327d1f60f59e68b9ea644d95"><div class="ttname"><a href="group__malloc.html#gaf2c7b89c327d1f60f59e68b9ea644d95">mi_free</a></div><div class="ttdeci">void mi_free(void *p)</div><div class="ttdoc">Free previously allocated memory.</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga139d6b09dbf50c3c2523d0f4d1cfdeb5"><div class="ttname"><a href="group__heap.html#ga139d6b09dbf50c3c2523d0f4d1cfdeb5">mi_heap_strdup</a></div><div class="ttdeci">char * mi_heap_strdup(mi_heap_t *heap, const char *s)</div><div class="ttdoc">Duplicate a string in a specific heap.</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga00e95ba1e01acac3cfd95bb7a357a6f0"><div class="ttname"><a href="group__heap.html#ga00e95ba1e01acac3cfd95bb7a357a6f0">mi_heap_realpath</a></div><div class="ttdeci">char * mi_heap_realpath(mi_heap_t *heap, const char *fname, char *resolved_name)</div><div class="ttdoc">Resolve a file path name using a specific heap to allocate the result.</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga08ca6419a5c057a4d965868998eef487"><div class="ttname"><a href="group__heap.html#ga08ca6419a5c057a4d965868998eef487">mi_heap_calloc_aligned_at</a></div><div class="ttdeci">void * mi_heap_calloc_aligned_at(mi_heap_t *heap, size_t count, size_t size, size_t alignment, size_t offset)</div></div>
|
||||
<div class="ttc" id="group__extended_html_ga7d862c2affd5790381da14eb102a364d"><div class="ttname"><a href="group__extended.html#ga7d862c2affd5790381da14eb102a364d">mi_process_info</a></div><div class="ttdeci">void mi_process_info(size_t *elapsed_msecs, size_t *user_msecs, size_t *system_msecs, size_t *current_rss, size_t *peak_rss, size_t *current_commit, size_t *peak_commit, size_t *page_faults)</div><div class="ttdoc">Return process information (time and memory usage).</div></div>
|
||||
<div class="ttc" id="group__aligned_html_ga53dddb4724042a90315b94bc268fb4c9"><div class="ttname"><a href="group__aligned.html#ga53dddb4724042a90315b94bc268fb4c9">mi_calloc_aligned</a></div><div class="ttdeci">void * mi_calloc_aligned(size_t count, size_t size, size_t alignment)</div></div>
|
||||
<div class="ttc" id="group__heap_html_gaa450a59c6c7ae5fdbd1c2b80a8329ef0"><div class="ttname"><a href="group__heap.html#gaa450a59c6c7ae5fdbd1c2b80a8329ef0">mi_heap_zalloc_aligned</a></div><div class="ttdeci">void * mi_heap_zalloc_aligned(mi_heap_t *heap, size_t size, size_t alignment)</div></div>
|
||||
<div class="ttc" id="group__extended_html_ga220f29f40a44404b0061c15bc1c31152"><div class="ttname"><a href="group__extended.html#ga220f29f40a44404b0061c15bc1c31152">mi_zalloc_small</a></div><div class="ttdeci">void * mi_zalloc_small(size_t size)</div><div class="ttdoc">Allocate a zero initialized small object.</div></div>
|
||||
<div class="ttc" id="group__malloc_html_gaaabf971c2571891433477e2d21a35266"><div class="ttname"><a href="group__malloc.html#gaaabf971c2571891433477e2d21a35266">mi_strndup</a></div><div class="ttdeci">char * mi_strndup(const char *s, size_t n)</div><div class="ttdoc">Allocate and duplicate a string up to n bytes.</div></div>
|
||||
<div class="ttc" id="group__malloc_html_gaaee66a1d483c3e28f585525fb96707e4"><div class="ttname"><a href="group__malloc.html#gaaee66a1d483c3e28f585525fb96707e4">mi_expand</a></div><div class="ttdeci">void * mi_expand(void *p, size_t newsize)</div><div class="ttdoc">Try to re-allocate memory to newsize bytes in place.</div></div>
|
||||
<div class="ttc" id="group__posix_html_gaeb325c39b887d3b90d85d1eb1712fb1e"><div class="ttname"><a href="group__posix.html#gaeb325c39b887d3b90d85d1eb1712fb1e">mi_pvalloc</a></div><div class="ttdeci">void * mi_pvalloc(size_t size)</div></div>
|
||||
<div class="ttc" id="group__options_html_ga65518b69ec5d32336b50e07f74b3f629"><div class="ttname"><a href="group__options.html#ga65518b69ec5d32336b50e07f74b3f629">mi_option_set_enabled_default</a></div><div class="ttdeci">void mi_option_set_enabled_default(mi_option_t option, bool enable)</div></div>
|
||||
<div class="ttc" id="group__zeroinit_html_gac90da54fa7e5d10bdc97ce0b51dce2eb"><div class="ttname"><a href="group__zeroinit.html#gac90da54fa7e5d10bdc97ce0b51dce2eb">mi_heap_rezalloc_aligned_at</a></div><div class="ttdeci">void * mi_heap_rezalloc_aligned_at(mi_heap_t *heap, void *p, size_t newsize, size_t alignment, size_t offset)</div></div>
|
||||
<div class="ttc" id="group__malloc_html_gafdd9d8bb2986e668ba9884f28af38000"><div class="ttname"><a href="group__malloc.html#gafdd9d8bb2986e668ba9884f28af38000">mi_zalloc</a></div><div class="ttdeci">void * mi_zalloc(size_t size)</div><div class="ttdoc">Allocate zero-initialized size bytes.</div></div>
|
||||
<div class="ttc" id="group__zeroinit_html_gacfad83f14eb5d6a42a497a898e19fc76"><div class="ttname"><a href="group__zeroinit.html#gacfad83f14eb5d6a42a497a898e19fc76">mi_heap_rezalloc</a></div><div class="ttdeci">void * mi_heap_rezalloc(mi_heap_t *heap, void *p, size_t newsize)</div></div>
|
||||
<div class="ttc" id="group__options_html_ggafebf7ed116adb38ae5218bc3ce06884ca2ecbe7ef32f5c84de3739aa4f0b805a1"><div class="ttname"><a href="group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca2ecbe7ef32f5c84de3739aa4f0b805a1">mi_option_segment_cache</a></div><div class="ttdoc">The number of segments per thread to keep cached.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:785</div></div>
|
||||
<div class="ttc" id="group__heap_html_gaa6702b3c48e9e53e50e81b36f5011d55"><div class="ttname"><a href="group__heap.html#gaa6702b3c48e9e53e50e81b36f5011d55">mi_heap_calloc</a></div><div class="ttdeci">void * mi_heap_calloc(mi_heap_t *heap, size_t count, size_t size)</div><div class="ttdoc">Allocate count zero-initialized elements in a specific heap.</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga4af03a6e2b93fae77424d93f889705c3"><div class="ttname"><a href="group__heap.html#ga4af03a6e2b93fae77424d93f889705c3">mi_heap_calloc_aligned</a></div><div class="ttdeci">void * mi_heap_calloc_aligned(mi_heap_t *heap, size_t count, size_t size, size_t alignment)</div></div>
|
||||
<div class="ttc" id="group__extended_html_gaad25050b19f30cd79397b227e0157a3f"><div class="ttname"><a href="group__extended.html#gaad25050b19f30cd79397b227e0157a3f">mi_is_redirected</a></div><div class="ttdeci">bool mi_is_redirected()</div><div class="ttdoc">Is the C runtime malloc API redirected?</div></div>
|
||||
<div class="ttc" id="group__analysis_html_a332a6c14d736a99699d5453a1cb04b41"><div class="ttname"><a href="group__analysis.html#a332a6c14d736a99699d5453a1cb04b41">mi_heap_area_t::block_size</a></div><div class="ttdeci">size_t block_size</div><div class="ttdoc">size in bytes of one block</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:741</div></div>
|
||||
<div class="ttc" id="group__posix_html_ga48fad8648a2f1dab9c87ea9448a52088"><div class="ttname"><a href="group__posix.html#ga48fad8648a2f1dab9c87ea9448a52088">mi_reallocarray</a></div><div class="ttdeci">void * mi_reallocarray(void *p, size_t count, size_t size)</div></div>
|
||||
<div class="ttc" id="group__extended_html_ga3132f521fb756fc0e8ec0b74fb58df50"><div class="ttname"><a href="group__extended.html#ga3132f521fb756fc0e8ec0b74fb58df50">mi_reserve_huge_os_pages_interleave</a></div><div class="ttdeci">int mi_reserve_huge_os_pages_interleave(size_t pages, size_t numa_nodes, size_t timeout_msecs)</div><div class="ttdoc">Reserve pages of huge OS pages (1GiB) evenly divided over numa_nodes nodes, but stops after at most t...</div></div>
|
||||
<div class="ttc" id="group__extended_html_ga299dae78d25ce112e384a98b7309c5be"><div class="ttname"><a href="group__extended.html#ga299dae78d25ce112e384a98b7309c5be">mi_deferred_free_fun</a></div><div class="ttdeci">void() mi_deferred_free_fun(bool force, unsigned long long heartbeat, void *arg)</div><div class="ttdoc">Type of deferred free functions.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:352</div></div>
|
||||
<div class="ttc" id="group__extended_html_ga5f071b10d4df1c3658e04e7fd67a94e6"><div class="ttname"><a href="group__extended.html#ga5f071b10d4df1c3658e04e7fd67a94e6">mi_is_in_heap_region</a></div><div class="ttdeci">bool mi_is_in_heap_region(const void *p)</div><div class="ttdoc">Is a pointer part of our heap?</div></div>
|
||||
<div class="ttc" id="group__cpp_html_gaef2c2bdb4f70857902d3c8903ac095f3"><div class="ttname"><a href="group__cpp.html#gaef2c2bdb4f70857902d3c8903ac095f3">mi_new_aligned</a></div><div class="ttdeci">void * mi_new_aligned(std::size_t n, std::align_val_t alignment) noexcept(false)</div><div class="ttdoc">like mi_malloc_aligned(), but when out of memory, use std::get_new_handler and raise std::bad_alloc e...</div></div>
|
||||
<div class="ttc" id="group__malloc_html_gaf11eb497da57bdfb2de65eb191c69db6"><div class="ttname"><a href="group__malloc.html#gaf11eb497da57bdfb2de65eb191c69db6">mi_realloc</a></div><div class="ttdeci">void * mi_realloc(void *p, size_t newsize)</div><div class="ttdoc">Re-allocate memory to newsize bytes.</div></div>
|
||||
<div class="ttc" id="group__options_html_ggafebf7ed116adb38ae5218bc3ce06884caca7ed041be3b0b9d0b82432c7bf41af2"><div class="ttname"><a href="group__options.html#ggafebf7ed116adb38ae5218bc3ce06884caca7ed041be3b0b9d0b82432c7bf41af2">mi_option_reserve_huge_os_pages</a></div><div class="ttdoc">The number of huge OS pages (1GiB in size) to reserve at the start of the program.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:784</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga4a21070eb4e7cce018133c8d5f4b0527"><div class="ttname"><a href="group__heap.html#ga4a21070eb4e7cce018133c8d5f4b0527">mi_heap_reallocf</a></div><div class="ttdeci">void * mi_heap_reallocf(mi_heap_t *heap, void *p, size_t newsize)</div></div>
|
||||
<div class="ttc" id="group__posix_html_ga72e9d7ffb5fe94d69bc722c8506e27bc"><div class="ttname"><a href="group__posix.html#ga72e9d7ffb5fe94d69bc722c8506e27bc">mi_free_size_aligned</a></div><div class="ttdeci">void mi_free_size_aligned(void *p, size_t size, size_t alignment)</div></div>
|
||||
<div class="ttc" id="group__zeroinit_html_gae8b358c417e61d5307da002702b0a8e1"><div class="ttname"><a href="group__zeroinit.html#gae8b358c417e61d5307da002702b0a8e1">mi_rezalloc_aligned_at</a></div><div class="ttdeci">void * mi_rezalloc_aligned_at(void *p, size_t newsize, size_t alignment, size_t offset)</div></div>
|
||||
<div class="ttc" id="group__options_html_ggafebf7ed116adb38ae5218bc3ce06884cada854dd272c66342f18a93ee254a2968"><div class="ttname"><a href="group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cada854dd272c66342f18a93ee254a2968">mi_option_page_reset</a></div><div class="ttdoc">Reset page memory after mi_option_reset_delay milliseconds when it becomes free.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:786</div></div>
|
||||
<div class="ttc" id="group__extended_html_ga0ae4581e85453456a0d658b2b98bf7bf"><div class="ttname"><a href="group__extended.html#ga0ae4581e85453456a0d658b2b98bf7bf">mi_thread_done</a></div><div class="ttdeci">void mi_thread_done(void)</div><div class="ttdoc">Uninitialize mimalloc on a thread.</div></div>
|
||||
<div class="ttc" id="group__analysis_html_ga70c46687dc6e9dc98b232b02646f8bed"><div class="ttname"><a href="group__analysis.html#ga70c46687dc6e9dc98b232b02646f8bed">mi_heap_visit_blocks</a></div><div class="ttdeci">bool mi_heap_visit_blocks(const mi_heap_t *heap, bool visit_all_blocks, mi_block_visit_fun *visitor, void *arg)</div><div class="ttdoc">Visit all areas and blocks in a heap.</div></div>
|
||||
<div class="ttc" id="group__options_html_ggafebf7ed116adb38ae5218bc3ce06884ca0ac33a18f6b659fcfaf44efb0bab1b74"><div class="ttname"><a href="group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca0ac33a18f6b659fcfaf44efb0bab1b74">mi_option_use_numa_nodes</a></div><div class="ttdoc">Pretend there are at most N NUMA nodes.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:789</div></div>
|
||||
<div class="ttc" id="group__malloc_html_ga3406e8b168bc74c8637b11571a6da83a"><div class="ttname"><a href="group__malloc.html#ga3406e8b168bc74c8637b11571a6da83a">mi_malloc</a></div><div class="ttdeci">void * mi_malloc(size_t size)</div><div class="ttdoc">Allocate size bytes.</div></div>
|
||||
<div class="ttc" id="group__extended_html_gaa1d55e0e894be240827e5d87ec3a1f45"><div class="ttname"><a href="group__extended.html#gaa1d55e0e894be240827e5d87ec3a1f45">mi_register_error</a></div><div class="ttdeci">void mi_register_error(mi_error_fun *errfun, void *arg)</div><div class="ttdoc">Register an error callback function.</div></div>
|
||||
<div class="ttc" id="group__options_html_ggafebf7ed116adb38ae5218bc3ce06884cac81ee965b130fa81238913a3c239d536"><div class="ttname"><a href="group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cac81ee965b130fa81238913a3c239d536">mi_option_reset_decommits</a></div><div class="ttdoc">Experimental.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:790</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga8e3dbd46650dd26573cf307a2c8f1f5a"><div class="ttname"><a href="group__heap.html#ga8e3dbd46650dd26573cf307a2c8f1f5a">mi_heap_strndup</a></div><div class="ttdeci">char * mi_heap_strndup(mi_heap_t *heap, const char *s, size_t n)</div><div class="ttdoc">Duplicate a string of at most length n in a specific heap.</div></div>
|
||||
<div class="ttc" id="group__analysis_html_gadfa01e2900f0e5d515ad5506b26f6d65"><div class="ttname"><a href="group__analysis.html#gadfa01e2900f0e5d515ad5506b26f6d65">mi_block_visit_fun</a></div><div class="ttdeci">bool() mi_block_visit_fun(const mi_heap_t *heap, const mi_heap_area_t *area, void *block, size_t block_size, void *arg)</div><div class="ttdoc">Visitor function passed to mi_heap_visit_blocks()</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:751</div></div>
|
||||
<div class="ttc" id="group__zeroinit_html_ga8648c5fbb22a80f0262859099f06dfbd"><div class="ttname"><a href="group__zeroinit.html#ga8648c5fbb22a80f0262859099f06dfbd">mi_heap_recalloc</a></div><div class="ttdeci">void * mi_heap_recalloc(mi_heap_t *heap, void *p, size_t newcount, size_t size)</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga23acd7680fb0976dde3783254c6c874b"><div class="ttname"><a href="group__heap.html#ga23acd7680fb0976dde3783254c6c874b">mi_heap_malloc_aligned_at</a></div><div class="ttdeci">void * mi_heap_malloc_aligned_at(mi_heap_t *heap, size_t size, size_t alignment, size_t offset)</div></div>
|
||||
<div class="ttc" id="group__malloc_html_ga08cec32dd5bbe7da91c78d19f1b5bebe"><div class="ttname"><a href="group__malloc.html#ga08cec32dd5bbe7da91c78d19f1b5bebe">mi_realpath</a></div><div class="ttdeci">char * mi_realpath(const char *fname, char *resolved_name)</div><div class="ttdoc">Resolve a file path name.</div></div>
|
||||
<div class="ttc" id="group__options_html_ggafebf7ed116adb38ae5218bc3ce06884cafbf4822e5c00732c5984b32a032837f0"><div class="ttname"><a href="group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cafbf4822e5c00732c5984b32a032837f0">mi_option_show_errors</a></div><div class="ttdoc">Print error messages to stderr.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:777</div></div>
|
||||
<div class="ttc" id="group__options_html_ggafebf7ed116adb38ae5218bc3ce06884cafb121d30d87591850d5410ccc3a95c6d"><div class="ttname"><a href="group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cafb121d30d87591850d5410ccc3a95c6d">mi_option_segment_reset</a></div><div class="ttdoc">Experimental.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:787</div></div>
|
||||
<div class="ttc" id="group__zeroinit_html_ga375fa8a611c51905e592d5d467c49664"><div class="ttname"><a href="group__zeroinit.html#ga375fa8a611c51905e592d5d467c49664">mi_heap_rezalloc_aligned</a></div><div class="ttdeci">void * mi_heap_rezalloc_aligned(mi_heap_t *heap, void *p, size_t newsize, size_t alignment)</div></div>
|
||||
<div class="ttc" id="group__cpp_html_gab5e29558926d934c3f1cae8c815f942c"><div class="ttname"><a href="group__cpp.html#gab5e29558926d934c3f1cae8c815f942c">mi_new_aligned_nothrow</a></div><div class="ttdeci">void * mi_new_aligned_nothrow(size_t n, size_t alignment)</div><div class="ttdoc">like mi_malloc_aligned, but when out of memory, use std::get_new_handler but return NULL on failure.</div></div>
|
||||
<div class="ttc" id="group__posix_html_gaab7fa71ea93b96873f5d9883db57d40e"><div class="ttname"><a href="group__posix.html#gaab7fa71ea93b96873f5d9883db57d40e">mi_memalign</a></div><div class="ttdeci">void * mi_memalign(size_t alignment, size_t size)</div></div>
|
||||
<div class="ttc" id="group__zeroinit_html_gacd71a7bce96aab38ae6de17af2eb2cf0"><div class="ttname"><a href="group__zeroinit.html#gacd71a7bce96aab38ae6de17af2eb2cf0">mi_rezalloc_aligned</a></div><div class="ttdeci">void * mi_rezalloc_aligned(void *p, size_t newsize, size_t alignment)</div></div>
|
||||
<div class="ttc" id="group__analysis_html_gaa862aa8ed8d57d84cae41fc1022d71af"><div class="ttname"><a href="group__analysis.html#gaa862aa8ed8d57d84cae41fc1022d71af">mi_heap_contains_block</a></div><div class="ttdeci">bool mi_heap_contains_block(mi_heap_t *heap, const void *p)</div><div class="ttdoc">Does a heap contain a pointer to a previously allocated block?</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga7922f7495cde30b1984d0e6072419298"><div class="ttname"><a href="group__heap.html#ga7922f7495cde30b1984d0e6072419298">mi_heap_collect</a></div><div class="ttdeci">void mi_heap_collect(mi_heap_t *heap, bool force)</div><div class="ttdoc">Release outstanding resources in a specific heap.</div></div>
|
||||
<div class="ttc" id="group__zeroinit_html_ga496452c96f1de8c500be9fddf52edaf7"><div class="ttname"><a href="group__zeroinit.html#ga496452c96f1de8c500be9fddf52edaf7">mi_heap_recalloc_aligned_at</a></div><div class="ttdeci">void * mi_heap_recalloc_aligned_at(mi_heap_t *heap, void *p, size_t newcount, size_t size, size_t alignment, size_t offset)</div></div>
|
||||
<div class="ttc" id="group__options_html_ggafebf7ed116adb38ae5218bc3ce06884ca7c8b7bf5281c581bad64f5daa6442777"><div class="ttname"><a href="group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca7c8b7bf5281c581bad64f5daa6442777">mi_option_verbose</a></div><div class="ttdoc">Print verbose messages to stderr.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:779</div></div>
|
||||
<div class="ttc" id="group__aligned_html_ga5f8c2353766db522565e642fafd8a3f8"><div class="ttname"><a href="group__aligned.html#ga5f8c2353766db522565e642fafd8a3f8">mi_zalloc_aligned_at</a></div><div class="ttdeci">void * mi_zalloc_aligned_at(size_t size, size_t alignment, size_t offset)</div></div>
|
||||
<div class="ttc" id="group__aligned_html_ga5850da130c936bd77db039dcfbc8295d"><div class="ttname"><a href="group__aligned.html#ga5850da130c936bd77db039dcfbc8295d">mi_malloc_aligned_at</a></div><div class="ttdeci">void * mi_malloc_aligned_at(size_t size, size_t alignment, size_t offset)</div><div class="ttdoc">Allocate size bytes aligned by alignment at a specified offset.</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga2ab1af8d438819b55319c7ef51d1e409"><div class="ttname"><a href="group__heap.html#ga2ab1af8d438819b55319c7ef51d1e409">mi_heap_delete</a></div><div class="ttdeci">void mi_heap_delete(mi_heap_t *heap)</div><div class="ttdoc">Delete a previously allocated heap.</div></div>
|
||||
<div class="ttc" id="group__options_html_ggafebf7ed116adb38ae5218bc3ce06884ca4b74ae2a69e445de6c2361b73c1d14bf"><div class="ttname"><a href="group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca4b74ae2a69e445de6c2361b73c1d14bf">mi_option_os_tag</a></div><div class="ttdoc">OS tag to assign to mimalloc'd memory.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:792</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga8db4cbb87314a989a9a187464d6b5e05"><div class="ttname"><a href="group__heap.html#ga8db4cbb87314a989a9a187464d6b5e05">mi_heap_get_default</a></div><div class="ttdeci">mi_heap_t * mi_heap_get_default()</div><div class="ttdoc">Get the default heap that is used for mi_malloc() et al.</div></div>
|
||||
<div class="ttc" id="group__extended_html_ga7795a13d20087447281858d2c771cca1"><div class="ttname"><a href="group__extended.html#ga7795a13d20087447281858d2c771cca1">mi_reserve_huge_os_pages_at</a></div><div class="ttdeci">int mi_reserve_huge_os_pages_at(size_t pages, int numa_node, size_t timeout_msecs)</div><div class="ttdoc">Reserve pages of huge OS pages (1GiB) at a specific numa_node, but stops after at most timeout_msecs ...</div></div>
|
||||
<div class="ttc" id="group__options_html_gaebf6ff707a2e688ebb1a2296ca564054"><div class="ttname"><a href="group__options.html#gaebf6ff707a2e688ebb1a2296ca564054">mi_option_disable</a></div><div class="ttdeci">void mi_option_disable(mi_option_t option)</div></div>
|
||||
<div class="ttc" id="group__posix_html_ga1326d2e4388630b5f81ca7206318b8e5"><div class="ttname"><a href="group__posix.html#ga1326d2e4388630b5f81ca7206318b8e5">mi_aligned_alloc</a></div><div class="ttdeci">void * mi_aligned_alloc(size_t alignment, size_t size)</div></div>
|
||||
<div class="ttc" id="group__posix_html_ga73baaf5951f5165ba0763d0c06b6a93b"><div class="ttname"><a href="group__posix.html#ga73baaf5951f5165ba0763d0c06b6a93b">mi_valloc</a></div><div class="ttdeci">void * mi_valloc(size_t size)</div></div>
|
||||
<div class="ttc" id="group__extended_html_gaf8e73efc2cbca9ebfdfb166983a04c17"><div class="ttname"><a href="group__extended.html#gaf8e73efc2cbca9ebfdfb166983a04c17">mi_thread_init</a></div><div class="ttdeci">void mi_thread_init(void)</div><div class="ttdoc">Initialize mimalloc on a thread.</div></div>
|
||||
<div class="ttc" id="group__extended_html_gac057927cd06c854b45fe7847e921bd47"><div class="ttname"><a href="group__extended.html#gac057927cd06c854b45fe7847e921bd47">mi_good_size</a></div><div class="ttdeci">size_t mi_good_size(size_t size)</div><div class="ttdoc">Return the used allocation size.</div></div>
|
||||
<div class="ttc" id="group__extended_html_ga2d126e5c62d3badc35445e5d84166df2"><div class="ttname"><a href="group__extended.html#ga2d126e5c62d3badc35445e5d84166df2">mi_stats_print</a></div><div class="ttdeci">void mi_stats_print(void *out)</div><div class="ttdoc">Deprecated.</div></div>
|
||||
<div class="ttc" id="group__options_html_ggafebf7ed116adb38ae5218bc3ce06884ca17a190c25be381142d87e0468c4c068c"><div class="ttname"><a href="group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca17a190c25be381142d87e0468c4c068c">mi_option_eager_commit_delay</a></div><div class="ttdoc">Experimental.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:791</div></div>
|
||||
<div class="ttc" id="group__zeroinit_html_ga9f3f999396c8f77ca5e80e7b40ac29e3"><div class="ttname"><a href="group__zeroinit.html#ga9f3f999396c8f77ca5e80e7b40ac29e3">mi_heap_recalloc_aligned</a></div><div class="ttdeci">void * mi_heap_recalloc_aligned(mi_heap_t *heap, void *p, size_t newcount, size_t size, size_t alignment)</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga851da6c43fe0b71c1376cee8aef90db0"><div class="ttname"><a href="group__heap.html#ga851da6c43fe0b71c1376cee8aef90db0">mi_heap_mallocn</a></div><div class="ttdeci">void * mi_heap_mallocn(mi_heap_t *heap, size_t count, size_t size)</div><div class="ttdoc">Allocate count elements in a specific heap.</div></div>
|
||||
<div class="ttc" id="group__analysis_html_structmi__heap__area__t"><div class="ttname"><a href="group__analysis.html#structmi__heap__area__t">mi_heap_area_t</a></div><div class="ttdoc">An area of heap space contains blocks of a single size.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:736</div></div>
|
||||
<div class="ttc" id="group__extended_html_gab1dac8476c46cb9eecab767eb40c1525"><div class="ttname"><a href="group__extended.html#gab1dac8476c46cb9eecab767eb40c1525">mi_thread_stats_print_out</a></div><div class="ttdeci">void mi_thread_stats_print_out(mi_output_fun *out, void *arg)</div><div class="ttdoc">Print out heap statistics for this thread.</div></div>
|
||||
<div class="ttc" id="group__options_html_ggafebf7ed116adb38ae5218bc3ce06884ca0957ef73b2550764b4840edf48422fda"><div class="ttname"><a href="group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca0957ef73b2550764b4840edf48422fda">mi_option_show_stats</a></div><div class="ttdoc">Print statistics to stderr when the program is done.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:778</div></div>
|
||||
<div class="ttc" id="group__aligned_html_ga0cadbcf5b89a7b6fb171bc8df8734819"><div class="ttname"><a href="group__aligned.html#ga0cadbcf5b89a7b6fb171bc8df8734819">mi_zalloc_aligned</a></div><div class="ttdeci">void * mi_zalloc_aligned(size_t size, size_t alignment)</div></div>
|
||||
<div class="ttc" id="group__analysis_html_ae848a3e6840414891035423948ca0383"><div class="ttname"><a href="group__analysis.html#ae848a3e6840414891035423948ca0383">mi_heap_area_t::reserved</a></div><div class="ttdeci">size_t reserved</div><div class="ttdoc">bytes reserved for this area</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:738</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga34a47cde5a5b38c29f1aa3c5e76943c2"><div class="ttname"><a href="group__heap.html#ga34a47cde5a5b38c29f1aa3c5e76943c2">mi_heap_t</a></div><div class="ttdeci">struct mi_heap_s mi_heap_t</div><div class="ttdoc">Type of first-class heaps.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:529</div></div>
|
||||
<div class="ttc" id="group__analysis_html_ab820302c5cd0df133eb8e51650a008b4"><div class="ttname"><a href="group__analysis.html#ab820302c5cd0df133eb8e51650a008b4">mi_heap_area_t::used</a></div><div class="ttdeci">size_t used</div><div class="ttdoc">bytes in use by allocated blocks</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:740</div></div>
|
||||
<div class="ttc" id="group__extended_html_ga3460a6ca91af97be4058f523d3cb8ece"><div class="ttname"><a href="group__extended.html#ga3460a6ca91af97be4058f523d3cb8ece">mi_register_deferred_free</a></div><div class="ttdeci">void mi_register_deferred_free(mi_deferred_free_fun *deferred_free, void *arg)</div><div class="ttdoc">Register a deferred free function.</div></div>
|
||||
<div class="ttc" id="group__posix_html_gae01389eedab8d67341ff52e2aad80ebb"><div class="ttname"><a href="group__posix.html#gae01389eedab8d67341ff52e2aad80ebb">mi_free_size</a></div><div class="ttdeci">void mi_free_size(void *p, size_t size)</div></div>
|
||||
<div class="ttc" id="group__extended_html_ga421430e2226d7d468529cec457396756"><div class="ttname"><a href="group__extended.html#ga421430e2226d7d468529cec457396756">mi_collect</a></div><div class="ttdeci">void mi_collect(bool force)</div><div class="ttdoc">Eagerly free memory.</div></div>
|
||||
<div class="ttc" id="group__cpp_html_ga756f4b2bc6a7ecd0a90baea8e90c7907"><div class="ttname"><a href="group__cpp.html#ga756f4b2bc6a7ecd0a90baea8e90c7907">mi_new_reallocn</a></div><div class="ttdeci">void * mi_new_reallocn(void *p, size_t newcount, size_t size)</div><div class="ttdoc">like mi_reallocn(), but when out of memory, use std::get_new_handler and raise std::bad_alloc excepti...</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga9f9c0844edb9717f4feacd79116b8e0d"><div class="ttname"><a href="group__heap.html#ga9f9c0844edb9717f4feacd79116b8e0d">mi_heap_destroy</a></div><div class="ttdeci">void mi_heap_destroy(mi_heap_t *heap)</div><div class="ttdoc">Destroy a heap, freeing all its still allocated blocks.</div></div>
|
||||
<div class="ttc" id="group__aligned_html_ga08647c4593f3b2eef24a919a73eba3a3"><div class="ttname"><a href="group__aligned.html#ga08647c4593f3b2eef24a919a73eba3a3">mi_calloc_aligned_at</a></div><div class="ttdeci">void * mi_calloc_aligned_at(size_t count, size_t size, size_t alignment, size_t offset)</div></div>
|
||||
<div class="ttc" id="group__options_html_ggafebf7ed116adb38ae5218bc3ce06884ca4192d491200d0055df0554d4cf65054e"><div class="ttname"><a href="group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca4192d491200d0055df0554d4cf65054e">mi_option_large_os_pages</a></div><div class="ttdoc">Use large OS pages (2MiB in size) if possible.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:783</div></div>
|
||||
<div class="ttc" id="group__heap_html_gac74e94ad9b0c9b57c1c4d88b8825b7a8"><div class="ttname"><a href="group__heap.html#gac74e94ad9b0c9b57c1c4d88b8825b7a8">mi_heap_reallocn</a></div><div class="ttdeci">void * mi_heap_reallocn(mi_heap_t *heap, void *p, size_t count, size_t size)</div></div>
|
||||
<div class="ttc" id="group__extended_html_gae5b17ff027cd2150b43a33040250cf3f"><div class="ttname"><a href="group__extended.html#gae5b17ff027cd2150b43a33040250cf3f">mi_register_output</a></div><div class="ttdeci">void mi_register_output(mi_output_fun *out, void *arg)</div><div class="ttdoc">Register an output function.</div></div>
|
||||
<div class="ttc" id="group__cpp_html_structmi__stl__allocator"><div class="ttname"><a href="group__cpp.html#structmi__stl__allocator">mi_stl_allocator</a></div><div class="ttdoc">std::allocator implementation for mimalloc for use in STL containers.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:880</div></div>
|
||||
<div class="ttc" id="group__heap_html_gaa1a1c7a1f4da6826b5a25b70ef878368"><div class="ttname"><a href="group__heap.html#gaa1a1c7a1f4da6826b5a25b70ef878368">mi_heap_malloc_small</a></div><div class="ttdeci">void * mi_heap_malloc_small(mi_heap_t *heap, size_t size)</div><div class="ttdoc">Allocate a small object in a specific heap.</div></div>
|
||||
<div class="ttc" id="group__heap_html_gaaef3395f66be48f37bdc8322509c5d81"><div class="ttname"><a href="group__heap.html#gaaef3395f66be48f37bdc8322509c5d81">mi_heap_realloc</a></div><div class="ttdeci">void * mi_heap_realloc(mi_heap_t *heap, void *p, size_t newsize)</div></div>
|
||||
<div class="ttc" id="group__posix_html_ga06d07cf357bbac5c73ba5d0c0c421e17"><div class="ttname"><a href="group__posix.html#ga06d07cf357bbac5c73ba5d0c0c421e17">mi_malloc_usable_size</a></div><div class="ttdeci">size_t mi_malloc_usable_size(const void *p)</div></div>
|
||||
<div class="ttc" id="group__extended_html_gad823d23444a4b77a40f66bf075a98a0c"><div class="ttname"><a href="group__extended.html#gad823d23444a4b77a40f66bf075a98a0c">mi_output_fun</a></div><div class="ttdeci">void() mi_output_fun(const char *msg, void *arg)</div><div class="ttdoc">Type of output functions.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:376</div></div>
|
||||
<div class="ttc" id="group__malloc_html_gac7cffe13f1f458ed16789488bf92b9b2"><div class="ttname"><a href="group__malloc.html#gac7cffe13f1f458ed16789488bf92b9b2">mi_strdup</a></div><div class="ttdeci">char * mi_strdup(const char *s)</div><div class="ttdoc">Allocate and duplicate a string.</div></div>
|
||||
<div class="ttc" id="group__heap_html_gaf96c788a1bf553fe2d371de9365e047c"><div class="ttname"><a href="group__heap.html#gaf96c788a1bf553fe2d371de9365e047c">mi_heap_realloc_aligned_at</a></div><div class="ttdeci">void * mi_heap_realloc_aligned_at(mi_heap_t *heap, void *p, size_t newsize, size_t alignment, size_t offset)</div></div>
|
||||
<div class="ttc" id="group__malloc_html_gafe68ac7c5e24a65cd55c9d6b152211a0"><div class="ttname"><a href="group__malloc.html#gafe68ac7c5e24a65cd55c9d6b152211a0">mi_reallocf</a></div><div class="ttdeci">void * mi_reallocf(void *p, size_t newsize)</div><div class="ttdoc">Re-allocate memory to newsize bytes,.</div></div>
|
||||
<div class="ttc" id="group__malloc_html_ga97fedb4f7107c592fd7f0f0a8949a57d"><div class="ttname"><a href="group__malloc.html#ga97fedb4f7107c592fd7f0f0a8949a57d">mi_calloc</a></div><div class="ttdeci">void * mi_calloc(size_t count, size_t size)</div><div class="ttdoc">Allocate zero-initialized count elements of size bytes.</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga45fb43a62776fbebbdf1edd99b527954"><div class="ttname"><a href="group__heap.html#ga45fb43a62776fbebbdf1edd99b527954">mi_heap_zalloc_aligned_at</a></div><div class="ttdeci">void * mi_heap_zalloc_aligned_at(mi_heap_t *heap, size_t size, size_t alignment, size_t offset)</div></div>
|
||||
<div class="ttc" id="group__extended_html_ga7136c2e55cb22c98ecf95d08d6debb99"><div class="ttname"><a href="group__extended.html#ga7136c2e55cb22c98ecf95d08d6debb99">mi_malloc_small</a></div><div class="ttdeci">void * mi_malloc_small(size_t size)</div><div class="ttdoc">Allocate a small object.</div></div>
|
||||
<div class="ttc" id="group__analysis_html_ga628c237489c2679af84a4d0d143b3dd5"><div class="ttname"><a href="group__analysis.html#ga628c237489c2679af84a4d0d143b3dd5">mi_check_owned</a></div><div class="ttdeci">bool mi_check_owned(const void *p)</div><div class="ttdoc">Check safely if any pointer is part of the default heap of this thread.</div></div>
|
||||
<div class="ttc" id="group__heap_html_gab5b87e1805306f70df38789fcfcf6653"><div class="ttname"><a href="group__heap.html#gab5b87e1805306f70df38789fcfcf6653">mi_heap_malloc_aligned</a></div><div class="ttdeci">void * mi_heap_malloc_aligned(mi_heap_t *heap, size_t size, size_t alignment)</div></div>
|
||||
<div class="ttc" id="group__options_html_ga7e8af195cc81d3fa64ccf2662caa565a"><div class="ttname"><a href="group__options.html#ga7e8af195cc81d3fa64ccf2662caa565a">mi_option_get</a></div><div class="ttdeci">long mi_option_get(mi_option_t option)</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga5d03fbe062ffcf38f0f417fd968357fc"><div class="ttname"><a href="group__heap.html#ga5d03fbe062ffcf38f0f417fd968357fc">mi_heap_get_backing</a></div><div class="ttdeci">mi_heap_t * mi_heap_get_backing()</div><div class="ttdoc">Get the backing heap.</div></div>
|
||||
<div class="ttc" id="group__posix_html_ga0d28d5cf61e6bfbb18c63092939fe5c9"><div class="ttname"><a href="group__posix.html#ga0d28d5cf61e6bfbb18c63092939fe5c9">mi_free_aligned</a></div><div class="ttdeci">void mi_free_aligned(void *p, size_t alignment)</div></div>
|
||||
<div class="ttc" id="group__cpp_html_gaad048a9fce3d02c5909cd05c6ec24545"><div class="ttname"><a href="group__cpp.html#gaad048a9fce3d02c5909cd05c6ec24545">mi_new</a></div><div class="ttdeci">void * mi_new(std::size_t n) noexcept(false)</div><div class="ttdoc">like mi_malloc(), but when out of memory, use std::get_new_handler and raise std::bad_alloc exception...</div></div>
|
||||
<div class="ttc" id="group__options_html_ggafebf7ed116adb38ae5218bc3ce06884ca154fe170131d5212cff57e22b99523c5"><div class="ttname"><a href="group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca154fe170131d5212cff57e22b99523c5">mi_option_reset_delay</a></div><div class="ttdoc">Delay in milli-seconds before resetting a page (100ms by default)</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:788</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga766f672ba56f2fbfeb9d9dbb0b7f6b11"><div class="ttname"><a href="group__heap.html#ga766f672ba56f2fbfeb9d9dbb0b7f6b11">mi_heap_new</a></div><div class="ttdeci">mi_heap_t * mi_heap_new()</div><div class="ttdoc">Create a new heap that can be used for allocation.</div></div>
|
||||
<div class="ttc" id="group__heap_html_ga9cbed01e42c0647907295de92c3fa296"><div class="ttname"><a href="group__heap.html#ga9cbed01e42c0647907295de92c3fa296">mi_heap_malloc</a></div><div class="ttdeci">void * mi_heap_malloc(mi_heap_t *heap, size_t size)</div><div class="ttdoc">Allocate in a specific heap.</div></div>
|
||||
<div class="ttc" id="group__analysis_html_ab47526df656d8837ec3e97f11b83f835"><div class="ttname"><a href="group__analysis.html#ab47526df656d8837ec3e97f11b83f835">mi_heap_area_t::committed</a></div><div class="ttdeci">size_t committed</div><div class="ttdoc">current committed bytes of this area</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:739</div></div>
|
||||
<div class="ttc" id="group__options_html_gafebf7ed116adb38ae5218bc3ce06884c"><div class="ttname"><a href="group__options.html#gafebf7ed116adb38ae5218bc3ce06884c">mi_option_t</a></div><div class="ttdeci">mi_option_t</div><div class="ttdoc">Runtime options.</div><div class="ttdef"><b>Definition:</b> mimalloc-doc.h:775</div></div>
|
||||
<div class="ttc" id="group__analysis_html_ga0d67c1789faaa15ff366c024fcaf6377"><div class="ttname"><a href="group__analysis.html#ga0d67c1789faaa15ff366c024fcaf6377">mi_heap_check_owned</a></div><div class="ttdeci">bool mi_heap_check_owned(mi_heap_t *heap, const void *p)</div><div class="ttdoc">Check safely if any pointer is part of a heap.</div></div>
|
||||
<div class="ttc" id="group__heap_html_gab8631ec88c8d26641b68b5d25dcd4422"><div class="ttname"><a href="group__heap.html#gab8631ec88c8d26641b68b5d25dcd4422">mi_heap_set_default</a></div><div class="ttdeci">mi_heap_t * mi_heap_set_default(mi_heap_t *heap)</div><div class="ttdoc">Set the default heap to use for mi_malloc() et al.</div></div>
|
||||
</div><!-- fragment --></div><!-- contents -->
|
||||
</div><!-- doc-content -->
|
||||
<!-- start footer part -->
|
||||
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
<li class="navelem"><b>mimalloc-doc.h</b></li>
|
||||
<li class="footer">Generated by
|
||||
<a href="http://www.doxygen.org/index.html">
|
||||
<img class="footer" src="doxygen.png" alt="doxygen"/></a> 1.8.15 </li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
@ @licstart The following is the entire license notice for the
|
||||
JavaScript code in this file.
|
||||
|
||||
Copyright (C) 1997-2017 by Dimitri van Heesch
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
@licend The above is the entire license notice
|
||||
for the JavaScript code in this file
|
||||
*/
|
||||
var NAVTREE =
|
||||
[
|
||||
[ "mi-malloc", "index.html", [
|
||||
[ "Building", "build.html", null ],
|
||||
[ "Using the library", "using.html", null ],
|
||||
[ "Environment Options", "environment.html", null ],
|
||||
[ "Overriding Malloc", "overrides.html", null ],
|
||||
[ "Performance", "bench.html", null ],
|
||||
[ "Modules", "modules.html", "modules" ],
|
||||
[ "Data Structures", "annotated.html", [
|
||||
[ "Data Structures", "annotated.html", "annotated_dup" ],
|
||||
[ "Data Structure Index", "classes.html", null ],
|
||||
[ "Data Fields", "functions.html", [
|
||||
[ "All", "functions.html", null ],
|
||||
[ "Variables", "functions_vars.html", null ]
|
||||
] ]
|
||||
] ]
|
||||
] ]
|
||||
];
|
||||
|
||||
var NAVTREEINDEX =
|
||||
[
|
||||
"annotated.html"
|
||||
];
|
||||
|
||||
var SYNCONMSG = 'click to disable panel synchronisation';
|
||||
var SYNCOFFMSG = 'click to enable panel synchronisation';
|
||||
@@ -1,142 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen 1.8.15"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>mi-malloc: Overriding Malloc</title>
|
||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="jquery.js"></script>
|
||||
<script type="text/javascript" src="dynsections.js"></script>
|
||||
<link href="navtree.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="resize.js"></script>
|
||||
<script type="text/javascript" src="navtreedata.js"></script>
|
||||
<script type="text/javascript" src="navtree.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(initResizable);
|
||||
/* @license-end */</script>
|
||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||
<script type="text/javascript" src="search/search.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function() { init_search(); });
|
||||
/* @license-end */
|
||||
</script>
|
||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
||||
<link href="mimalloc-doxygen.css" rel="stylesheet" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 56px;">
|
||||
<td id="projectlogo"><img alt="Logo" src="mimalloc-logo.svg"/></td>
|
||||
<td id="projectalign" style="padding-left: 0.5em;">
|
||||
<div id="projectname">mi-malloc
|
||||
 <span id="projectnumber">1.6</span>
|
||||
</div>
|
||||
</td>
|
||||
<td> <div id="MSearchBox" class="MSearchBoxInactive">
|
||||
<span class="left">
|
||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
alt=""/>
|
||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
||||
</span><span class="right">
|
||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- end header part -->
|
||||
<!-- Generated by Doxygen 1.8.15 -->
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
||||
/* @license-end */
|
||||
</script>
|
||||
</div><!-- top -->
|
||||
<div id="side-nav" class="ui-resizable side-nav-resizable">
|
||||
<div id="nav-tree">
|
||||
<div id="nav-tree-contents">
|
||||
<div id="nav-sync" class="sync"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="splitbar" style="-moz-user-select:none;"
|
||||
class="ui-resizable-handle">
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function(){initNavTree('overrides.html','');});
|
||||
/* @license-end */
|
||||
</script>
|
||||
<div id="doc-content">
|
||||
<!-- window showing the filter options -->
|
||||
<div id="MSearchSelectWindow"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
||||
</div>
|
||||
|
||||
<!-- iframe showing the search results (closed by default) -->
|
||||
<div id="MSearchResultsWindow">
|
||||
<iframe src="javascript:void(0)" frameborder="0"
|
||||
name="MSearchResults" id="MSearchResults">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
<div class="PageDoc"><div class="header">
|
||||
<div class="headertitle">
|
||||
<div class="title">Overriding Malloc </div> </div>
|
||||
</div><!--header-->
|
||||
<div class="contents">
|
||||
<div class="textblock"><p>Overriding the standard <code>malloc</code> can be done either <em>dynamically</em> or <em>statically</em>.</p>
|
||||
<h2>Dynamic override</h2>
|
||||
<p>This is the recommended way to override the standard malloc interface.</p>
|
||||
<h3>Linux, BSD</h3>
|
||||
<p>On these systems we preload the mimalloc shared library so all calls to the standard <code>malloc</code> interface are resolved to the <em>mimalloc</em> library.</p>
|
||||
<ul>
|
||||
<li><code>env LD_PRELOAD=/usr/lib/libmimalloc.so myprogram</code></li>
|
||||
</ul>
|
||||
<p>You can set extra environment variables to check that mimalloc is running, like: </p><div class="fragment"><div class="line">env MIMALLOC_VERBOSE=1 LD_PRELOAD=/usr/lib/libmimalloc.so myprogram</div></div><!-- fragment --><p> or run with the debug version to get detailed statistics: </p><div class="fragment"><div class="line">env MIMALLOC_SHOW_STATS=1 LD_PRELOAD=/usr/lib/libmimalloc-debug.so myprogram</div></div><!-- fragment --><h3>MacOS</h3>
|
||||
<p>On macOS we can also preload the mimalloc shared library so all calls to the standard <code>malloc</code> interface are resolved to the <em>mimalloc</em> library.</p>
|
||||
<ul>
|
||||
<li><code>env DYLD_FORCE_FLAT_NAMESPACE=1 DYLD_INSERT_LIBRARIES=/usr/lib/libmimalloc.dylib myprogram</code></li>
|
||||
</ul>
|
||||
<p>Note that certain security restrictions may apply when doing this from the <a href="https://stackoverflow.com/questions/43941322/dyld-insert-libraries-ignored-when-calling-application-through-bash">shell</a>.</p>
|
||||
<p>(Note: macOS support for dynamic overriding is recent, please report any issues.)</p>
|
||||
<h3>Windows</h3>
|
||||
<p>Overriding on Windows is robust and has the particular advantage to be able to redirect all malloc/free calls that go through the (dynamic) C runtime allocator, including those from other DLL's or libraries.</p>
|
||||
<p>The overriding on Windows requires that you link your program explicitly with the mimalloc DLL and use the C-runtime library as a DLL (using the <code>/MD</code> or <code>/MDd</code> switch). Also, the <code>mimalloc-redirect.dll</code> (or <code>mimalloc-redirect32.dll</code>) must be available in the same folder as the main <code>mimalloc-override.dll</code> at runtime (as it is a dependency). The redirection DLL ensures that all calls to the C runtime malloc API get redirected to mimalloc (in <code>mimalloc-override.dll</code>).</p>
|
||||
<p>To ensure the mimalloc DLL is loaded at run-time it is easiest to insert some call to the mimalloc API in the <code>main</code> function, like <code>mi_version()</code> (or use the <code>/INCLUDE:mi_version</code> switch on the linker). See the <code>mimalloc-override-test</code> project for an example on how to use this. For best performance on Windows with C++, it is also recommended to also override the <code>new</code>/<code>delete</code> operations (by including <a href="https://github.com/microsoft/mimalloc/blob/master/include/mimalloc-new-delete.h"><code>mimalloc-new-delete.h</code></a> a single(!) source file in your project).</p>
|
||||
<p>The environment variable <code>MIMALLOC_DISABLE_REDIRECT=1</code> can be used to disable dynamic overriding at run-time. Use <code>MIMALLOC_VERBOSE=1</code> to check if mimalloc was successfully redirected.</p>
|
||||
<p>(Note: in principle, it is possible to even patch existing executables without any recompilation if they are linked with the dynamic C runtime (<code>ucrtbase.dll</code>) – just put the <code>mimalloc-override.dll</code> into the import table (and put <code>mimalloc-redirect.dll</code> in the same folder) Such patching can be done for example with <a href="https://ntcore.com/?page_id=388">CFF Explorer</a>).</p>
|
||||
<h2>Static override</h2>
|
||||
<p>On Unix systems, you can also statically link with <em>mimalloc</em> to override the standard malloc interface. The recommended way is to link the final program with the <em>mimalloc</em> single object file (<code>mimalloc-override.o</code>). We use an object file instead of a library file as linkers give preference to that over archives to resolve symbols. To ensure that the standard malloc interface resolves to the <em>mimalloc</em> library, link it as the first object file. For example:</p>
|
||||
<div class="fragment"><div class="line">gcc -o myprogram mimalloc-<span class="keyword">override</span>.o myfile1.c ...</div></div><!-- fragment --><h2>List of Overrides:</h2>
|
||||
<p>The specific functions that get redirected to the <em>mimalloc</em> library are:</p>
|
||||
<div class="fragment"><div class="line"><span class="comment">// C</span></div><div class="line"><span class="keywordtype">void</span>* malloc(<span class="keywordtype">size_t</span> size);</div><div class="line"><span class="keywordtype">void</span>* calloc(<span class="keywordtype">size_t</span> size, <span class="keywordtype">size_t</span> n);</div><div class="line"><span class="keywordtype">void</span>* realloc(<span class="keywordtype">void</span>* p, <span class="keywordtype">size_t</span> newsize);</div><div class="line"><span class="keywordtype">void</span> free(<span class="keywordtype">void</span>* p);</div><div class="line"></div><div class="line"><span class="comment">// C++</span></div><div class="line"><span class="keywordtype">void</span> <span class="keyword">operator</span> <span class="keyword">delete</span>(<span class="keywordtype">void</span>* p);</div><div class="line"><span class="keywordtype">void</span> <span class="keyword">operator</span> <span class="keyword">delete</span>[](<span class="keywordtype">void</span>* p);</div><div class="line"></div><div class="line"><span class="keywordtype">void</span>* <span class="keyword">operator</span> <span class="keyword">new</span>(std::size_t n) noexcept(<span class="keyword">false</span>);</div><div class="line"><span class="keywordtype">void</span>* <span class="keyword">operator</span> <span class="keyword">new</span>[](std::size_t n) noexcept(<span class="keyword">false</span>);</div><div class="line"><span class="keywordtype">void</span>* <span class="keyword">operator</span> <span class="keyword">new</span>( std::size_t n, std::align_val_t align) noexcept(<span class="keyword">false</span>);</div><div class="line"><span class="keywordtype">void</span>* <span class="keyword">operator</span> <span class="keyword">new</span>[]( std::size_t n, std::align_val_t align) noexcept(<span class="keyword">false</span>);</div><div class="line"></div><div class="line"><span class="keywordtype">void</span>* <span class="keyword">operator</span> <span class="keyword">new</span> ( std::size_t count, <span class="keyword">const</span> std::nothrow_t& tag);</div><div class="line"><span class="keywordtype">void</span>* <span class="keyword">operator</span> <span class="keyword">new</span>[]( std::size_t count, <span class="keyword">const</span> std::nothrow_t& tag);</div><div class="line"><span class="keywordtype">void</span>* <span class="keyword">operator</span> <span class="keyword">new</span> ( std::size_t count, std::align_val_t al, <span class="keyword">const</span> std::nothrow_t&);</div><div class="line"><span class="keywordtype">void</span>* <span class="keyword">operator</span> <span class="keyword">new</span>[]( std::size_t count, std::align_val_t al, <span class="keyword">const</span> std::nothrow_t&);</div><div class="line"></div><div class="line"><span class="comment">// Posix</span></div><div class="line"><span class="keywordtype">int</span> posix_memalign(<span class="keywordtype">void</span>** p, <span class="keywordtype">size_t</span> alignment, <span class="keywordtype">size_t</span> size);</div><div class="line"></div><div class="line"><span class="comment">// Linux</span></div><div class="line"><span class="keywordtype">void</span>* memalign(<span class="keywordtype">size_t</span> alignment, <span class="keywordtype">size_t</span> size);</div><div class="line"><span class="keywordtype">void</span>* aligned_alloc(<span class="keywordtype">size_t</span> alignment, <span class="keywordtype">size_t</span> size);</div><div class="line"><span class="keywordtype">void</span>* valloc(<span class="keywordtype">size_t</span> size);</div><div class="line"><span class="keywordtype">void</span>* pvalloc(<span class="keywordtype">size_t</span> size);</div><div class="line"><span class="keywordtype">size_t</span> malloc_usable_size(<span class="keywordtype">void</span> *p);</div><div class="line"></div><div class="line"><span class="comment">// BSD</span></div><div class="line"><span class="keywordtype">void</span>* reallocarray( <span class="keywordtype">void</span>* p, <span class="keywordtype">size_t</span> count, <span class="keywordtype">size_t</span> size );</div><div class="line"><span class="keywordtype">void</span>* reallocf(<span class="keywordtype">void</span>* p, <span class="keywordtype">size_t</span> newsize);</div><div class="line"><span class="keywordtype">void</span> cfree(<span class="keywordtype">void</span>* p);</div><div class="line"></div><div class="line"><span class="comment">// Windows</span></div><div class="line"><span class="keywordtype">void</span>* _expand(<span class="keywordtype">void</span>* p, <span class="keywordtype">size_t</span> newsize);</div><div class="line"><span class="keywordtype">size_t</span> _msize(<span class="keywordtype">void</span>* p);</div><div class="line"></div><div class="line"><span class="keywordtype">void</span>* _malloc_dbg(<span class="keyLine truncated
|
||||
</div><!-- contents -->
|
||||
</div><!-- doc-content -->
|
||||
<!-- start footer part -->
|
||||
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
<li class="footer">Generated by
|
||||
<a href="http://www.doxygen.org/index.html">
|
||||
<img class="footer" src="doxygen.png" alt="doxygen"/></a> 1.8.15 </li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['_5fmi_5foption_5flast',['_mi_option_last',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca5b4357b74be0d87568036c32eb1a2e4a',1,'mimalloc-doc.h']]]
|
||||
];
|
||||
@@ -1,4 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['aligned_20allocation',['Aligned Allocation',['../group__aligned.html',1,'']]]
|
||||
];
|
||||
@@ -1,7 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['block_5fsize',['block_size',['../group__analysis.html#a332a6c14d736a99699d5453a1cb04b41',1,'mi_heap_area_t']]],
|
||||
['blocks',['blocks',['../group__analysis.html#ae0085e6e1cf059a4eb7767e30e9991b8',1,'mi_heap_area_t']]],
|
||||
['building',['Building',['../build.html',1,'']]],
|
||||
['basic_20allocation',['Basic Allocation',['../group__malloc.html',1,'']]]
|
||||
];
|
||||
@@ -1,5 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['committed',['committed',['../group__analysis.html#ab47526df656d8837ec3e97f11b83f835',1,'mi_heap_area_t']]],
|
||||
['c_2b_2b_20wrappers',['C++ wrappers',['../group__cpp.html',1,'']]]
|
||||
];
|
||||
@@ -1,5 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['environment_20options',['Environment Options',['../environment.html',1,'']]],
|
||||
['extended_20functions',['Extended Functions',['../group__extended.html',1,'']]]
|
||||
];
|
||||
@@ -1,5 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['heap_20introspection',['Heap Introspection',['../group__analysis.html',1,'']]],
|
||||
['heap_20allocation',['Heap Allocation',['../group__heap.html',1,'']]]
|
||||
];
|
||||
@@ -1,148 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['mi_5f_5fposix_5fmemalign',['mi__posix_memalign',['../group__posix.html#gad5a69c8fea96aa2b7a7c818c2130090a',1,'mimalloc-doc.h']]],
|
||||
['mi_5faligned_5falloc',['mi_aligned_alloc',['../group__posix.html#ga1326d2e4388630b5f81ca7206318b8e5',1,'mimalloc-doc.h']]],
|
||||
['mi_5fblock_5fvisit_5ffun',['mi_block_visit_fun',['../group__analysis.html#gadfa01e2900f0e5d515ad5506b26f6d65',1,'mimalloc-doc.h']]],
|
||||
['mi_5fcalloc',['mi_calloc',['../group__malloc.html#ga97fedb4f7107c592fd7f0f0a8949a57d',1,'mimalloc-doc.h']]],
|
||||
['mi_5fcalloc_5faligned',['mi_calloc_aligned',['../group__aligned.html#ga53dddb4724042a90315b94bc268fb4c9',1,'mimalloc-doc.h']]],
|
||||
['mi_5fcalloc_5faligned_5fat',['mi_calloc_aligned_at',['../group__aligned.html#ga08647c4593f3b2eef24a919a73eba3a3',1,'mimalloc-doc.h']]],
|
||||
['mi_5fcalloc_5ftp',['mi_calloc_tp',['../group__typed.html#gae80c47c9d4cab10961fff1a8ac98fc07',1,'mimalloc-doc.h']]],
|
||||
['mi_5fcfree',['mi_cfree',['../group__posix.html#ga705dc7a64bffacfeeb0141501a5c35d7',1,'mimalloc-doc.h']]],
|
||||
['mi_5fcheck_5fowned',['mi_check_owned',['../group__analysis.html#ga628c237489c2679af84a4d0d143b3dd5',1,'mimalloc-doc.h']]],
|
||||
['mi_5fcollect',['mi_collect',['../group__extended.html#ga421430e2226d7d468529cec457396756',1,'mimalloc-doc.h']]],
|
||||
['mi_5fdeferred_5ffree_5ffun',['mi_deferred_free_fun',['../group__extended.html#ga299dae78d25ce112e384a98b7309c5be',1,'mimalloc-doc.h']]],
|
||||
['mi_5ferror_5ffun',['mi_error_fun',['../group__extended.html#ga251d369cda3f1c2a955c555486ed90e5',1,'mimalloc-doc.h']]],
|
||||
['mi_5fexpand',['mi_expand',['../group__malloc.html#gaaee66a1d483c3e28f585525fb96707e4',1,'mimalloc-doc.h']]],
|
||||
['mi_5ffree',['mi_free',['../group__malloc.html#gaf2c7b89c327d1f60f59e68b9ea644d95',1,'mimalloc-doc.h']]],
|
||||
['mi_5ffree_5faligned',['mi_free_aligned',['../group__posix.html#ga0d28d5cf61e6bfbb18c63092939fe5c9',1,'mimalloc-doc.h']]],
|
||||
['mi_5ffree_5fsize',['mi_free_size',['../group__posix.html#gae01389eedab8d67341ff52e2aad80ebb',1,'mimalloc-doc.h']]],
|
||||
['mi_5ffree_5fsize_5faligned',['mi_free_size_aligned',['../group__posix.html#ga72e9d7ffb5fe94d69bc722c8506e27bc',1,'mimalloc-doc.h']]],
|
||||
['mi_5fgood_5fsize',['mi_good_size',['../group__extended.html#gac057927cd06c854b45fe7847e921bd47',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5farea_5ft',['mi_heap_area_t',['../group__analysis.html#structmi__heap__area__t',1,'']]],
|
||||
['mi_5fheap_5fcalloc',['mi_heap_calloc',['../group__heap.html#gaa6702b3c48e9e53e50e81b36f5011d55',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fcalloc_5faligned',['mi_heap_calloc_aligned',['../group__heap.html#ga4af03a6e2b93fae77424d93f889705c3',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fcalloc_5faligned_5fat',['mi_heap_calloc_aligned_at',['../group__heap.html#ga08ca6419a5c057a4d965868998eef487',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fcalloc_5ftp',['mi_heap_calloc_tp',['../group__typed.html#ga4e5d1f1707c90e5f55e023ac5f45fe74',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fcheck_5fowned',['mi_heap_check_owned',['../group__analysis.html#ga0d67c1789faaa15ff366c024fcaf6377',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fcollect',['mi_heap_collect',['../group__heap.html#ga7922f7495cde30b1984d0e6072419298',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fcontains_5fblock',['mi_heap_contains_block',['../group__analysis.html#gaa862aa8ed8d57d84cae41fc1022d71af',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fdelete',['mi_heap_delete',['../group__heap.html#ga2ab1af8d438819b55319c7ef51d1e409',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fdestroy',['mi_heap_destroy',['../group__heap.html#ga9f9c0844edb9717f4feacd79116b8e0d',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fget_5fbacking',['mi_heap_get_backing',['../group__heap.html#ga5d03fbe062ffcf38f0f417fd968357fc',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fget_5fdefault',['mi_heap_get_default',['../group__heap.html#ga8db4cbb87314a989a9a187464d6b5e05',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fmalloc',['mi_heap_malloc',['../group__heap.html#ga9cbed01e42c0647907295de92c3fa296',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fmalloc_5faligned',['mi_heap_malloc_aligned',['../group__heap.html#gab5b87e1805306f70df38789fcfcf6653',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fmalloc_5faligned_5fat',['mi_heap_malloc_aligned_at',['../group__heap.html#ga23acd7680fb0976dde3783254c6c874b',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fmalloc_5fsmall',['mi_heap_malloc_small',['../group__heap.html#gaa1a1c7a1f4da6826b5a25b70ef878368',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fmalloc_5ftp',['mi_heap_malloc_tp',['../group__typed.html#ga653bcb24ac495bc19940ecd6898f9cd7',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fmallocn',['mi_heap_mallocn',['../group__heap.html#ga851da6c43fe0b71c1376cee8aef90db0',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fmallocn_5ftp',['mi_heap_mallocn_tp',['../group__typed.html#ga6b75cb9c4b9c647661d0924552dc6e83',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fnew',['mi_heap_new',['../group__heap.html#ga766f672ba56f2fbfeb9d9dbb0b7f6b11',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frealloc',['mi_heap_realloc',['../group__heap.html#gaaef3395f66be48f37bdc8322509c5d81',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frealloc_5faligned',['mi_heap_realloc_aligned',['../group__heap.html#gafc603b696bd14cae6da28658f950d98c',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frealloc_5faligned_5fat',['mi_heap_realloc_aligned_at',['../group__heap.html#gaf96c788a1bf553fe2d371de9365e047c',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5freallocf',['mi_heap_reallocf',['../group__heap.html#ga4a21070eb4e7cce018133c8d5f4b0527',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5freallocn',['mi_heap_reallocn',['../group__heap.html#gac74e94ad9b0c9b57c1c4d88b8825b7a8',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5freallocn_5ftp',['mi_heap_reallocn_tp',['../group__typed.html#gaf213d5422ec35e7f6caad827c79bc948',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frealpath',['mi_heap_realpath',['../group__heap.html#ga00e95ba1e01acac3cfd95bb7a357a6f0',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frecalloc',['mi_heap_recalloc',['../group__zeroinit.html#ga8648c5fbb22a80f0262859099f06dfbd',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frecalloc_5faligned',['mi_heap_recalloc_aligned',['../group__zeroinit.html#ga9f3f999396c8f77ca5e80e7b40ac29e3',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frecalloc_5faligned_5fat',['mi_heap_recalloc_aligned_at',['../group__zeroinit.html#ga496452c96f1de8c500be9fddf52edaf7',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frecalloc_5ftp',['mi_heap_recalloc_tp',['../group__typed.html#ga3e50a1600958fcaf1a7f3560c9174f9e',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frezalloc',['mi_heap_rezalloc',['../group__zeroinit.html#gacfad83f14eb5d6a42a497a898e19fc76',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frezalloc_5faligned',['mi_heap_rezalloc_aligned',['../group__zeroinit.html#ga375fa8a611c51905e592d5d467c49664',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frezalloc_5faligned_5fat',['mi_heap_rezalloc_aligned_at',['../group__zeroinit.html#gac90da54fa7e5d10bdc97ce0b51dce2eb',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fset_5fdefault',['mi_heap_set_default',['../group__heap.html#gab8631ec88c8d26641b68b5d25dcd4422',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fstrdup',['mi_heap_strdup',['../group__heap.html#ga139d6b09dbf50c3c2523d0f4d1cfdeb5',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fstrndup',['mi_heap_strndup',['../group__heap.html#ga8e3dbd46650dd26573cf307a2c8f1f5a',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5ft',['mi_heap_t',['../group__heap.html#ga34a47cde5a5b38c29f1aa3c5e76943c2',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fvisit_5fblocks',['mi_heap_visit_blocks',['../group__analysis.html#ga70c46687dc6e9dc98b232b02646f8bed',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fzalloc',['mi_heap_zalloc',['../group__heap.html#ga903104592c8ed53417a3762da6241133',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fzalloc_5faligned',['mi_heap_zalloc_aligned',['../group__heap.html#gaa450a59c6c7ae5fdbd1c2b80a8329ef0',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fzalloc_5faligned_5fat',['mi_heap_zalloc_aligned_at',['../group__heap.html#ga45fb43a62776fbebbdf1edd99b527954',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fzalloc_5ftp',['mi_heap_zalloc_tp',['../group__typed.html#gad6e87e86e994aa14416ae9b5d4c188fe',1,'mimalloc-doc.h']]],
|
||||
['mi_5fis_5fin_5fheap_5fregion',['mi_is_in_heap_region',['../group__extended.html#ga5f071b10d4df1c3658e04e7fd67a94e6',1,'mimalloc-doc.h']]],
|
||||
['mi_5fis_5fredirected',['mi_is_redirected',['../group__extended.html#gaad25050b19f30cd79397b227e0157a3f',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmalloc',['mi_malloc',['../group__malloc.html#ga3406e8b168bc74c8637b11571a6da83a',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmalloc_5faligned',['mi_malloc_aligned',['../group__aligned.html#ga68930196751fa2cca9e1fd0d71bade56',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmalloc_5faligned_5fat',['mi_malloc_aligned_at',['../group__aligned.html#ga5850da130c936bd77db039dcfbc8295d',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmalloc_5fsize',['mi_malloc_size',['../group__posix.html#ga4531c9e775bb3ae12db57c1ba8a5d7de',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmalloc_5fsmall',['mi_malloc_small',['../group__extended.html#ga7136c2e55cb22c98ecf95d08d6debb99',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmalloc_5ftp',['mi_malloc_tp',['../group__typed.html#ga0619a62c5fd886f1016030abe91f0557',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmalloc_5fusable_5fsize',['mi_malloc_usable_size',['../group__posix.html#ga06d07cf357bbac5c73ba5d0c0c421e17',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmallocn',['mi_mallocn',['../group__malloc.html#ga0b05e2bf0f73e7401ae08597ff782ac6',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmallocn_5ftp',['mi_mallocn_tp',['../group__typed.html#gae5cb6e0fafc9f23169c5622e077afe8b',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmemalign',['mi_memalign',['../group__posix.html#gaab7fa71ea93b96873f5d9883db57d40e',1,'mimalloc-doc.h']]],
|
||||
['mi_5fnew',['mi_new',['../group__cpp.html#gaad048a9fce3d02c5909cd05c6ec24545',1,'mimalloc-doc.h']]],
|
||||
['mi_5fnew_5faligned',['mi_new_aligned',['../group__cpp.html#gaef2c2bdb4f70857902d3c8903ac095f3',1,'mimalloc-doc.h']]],
|
||||
['mi_5fnew_5faligned_5fnothrow',['mi_new_aligned_nothrow',['../group__cpp.html#gab5e29558926d934c3f1cae8c815f942c',1,'mimalloc-doc.h']]],
|
||||
['mi_5fnew_5fn',['mi_new_n',['../group__cpp.html#gae7bc4f56cd57ed3359060ff4f38bda81',1,'mimalloc-doc.h']]],
|
||||
['mi_5fnew_5fnothrow',['mi_new_nothrow',['../group__cpp.html#gaeaded64eda71ed6b1d569d3e723abc4a',1,'mimalloc-doc.h']]],
|
||||
['mi_5fnew_5frealloc',['mi_new_realloc',['../group__cpp.html#gaab78a32f55149e9fbf432d5288e38e1e',1,'mimalloc-doc.h']]],
|
||||
['mi_5fnew_5freallocn',['mi_new_reallocn',['../group__cpp.html#ga756f4b2bc6a7ecd0a90baea8e90c7907',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fdisable',['mi_option_disable',['../group__options.html#gaebf6ff707a2e688ebb1a2296ca564054',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5feager_5fcommit',['mi_option_eager_commit',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca1e8de72c93da7ff22d91e1e27b52ac2b',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5feager_5fcommit_5fdelay',['mi_option_eager_commit_delay',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca17a190c25be381142d87e0468c4c068c',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5feager_5fregion_5fcommit',['mi_option_eager_region_commit',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca32ce97ece29f69e82579679cf8a307ad',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fenable',['mi_option_enable',['../group__options.html#ga04180ae41b0d601421dd62ced40ca050',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fget',['mi_option_get',['../group__options.html#ga7e8af195cc81d3fa64ccf2662caa565a',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fis_5fenabled',['mi_option_is_enabled',['../group__options.html#ga459ad98f18b3fc9275474807fe0ca188',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5flarge_5fos_5fpages',['mi_option_large_os_pages',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca4192d491200d0055df0554d4cf65054e',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fos_5ftag',['mi_option_os_tag',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca4b74ae2a69e445de6c2361b73c1d14bf',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fpage_5freset',['mi_option_page_reset',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cada854dd272c66342f18a93ee254a2968',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5freserve_5fhuge_5fos_5fpages',['mi_option_reserve_huge_os_pages',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884caca7ed041be3b0b9d0b82432c7bf41af2',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5freset_5fdecommits',['mi_option_reset_decommits',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cac81ee965b130fa81238913a3c239d536',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5freset_5fdelay',['mi_option_reset_delay',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca154fe170131d5212cff57e22b99523c5',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fsegment_5fcache',['mi_option_segment_cache',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca2ecbe7ef32f5c84de3739aa4f0b805a1',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fsegment_5freset',['mi_option_segment_reset',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cafb121d30d87591850d5410ccc3a95c6d',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fset',['mi_option_set',['../group__options.html#gaf84921c32375e25754dc2ee6a911fa60',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fset_5fdefault',['mi_option_set_default',['../group__options.html#ga7ef623e440e6e5545cb08c94e71e4b90',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fset_5fenabled',['mi_option_set_enabled',['../group__options.html#ga9a13d05fcb77489cb06d4d017ebd8bed',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fset_5fenabled_5fdefault',['mi_option_set_enabled_default',['../group__options.html#ga65518b69ec5d32336b50e07f74b3f629',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fshow_5ferrors',['mi_option_show_errors',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cafbf4822e5c00732c5984b32a032837f0',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fshow_5fstats',['mi_option_show_stats',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca0957ef73b2550764b4840edf48422fda',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5ft',['mi_option_t',['../group__options.html#gafebf7ed116adb38ae5218bc3ce06884c',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fuse_5fnuma_5fnodes',['mi_option_use_numa_nodes',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca0ac33a18f6b659fcfaf44efb0bab1b74',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fverbose',['mi_option_verbose',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca7c8b7bf5281c581bad64f5daa6442777',1,'mimalloc-doc.h']]],
|
||||
['mi_5foutput_5ffun',['mi_output_fun',['../group__extended.html#gad823d23444a4b77a40f66bf075a98a0c',1,'mimalloc-doc.h']]],
|
||||
['mi_5fposix_5fmemalign',['mi_posix_memalign',['../group__posix.html#gacff84f226ba9feb2031b8992e5579447',1,'mimalloc-doc.h']]],
|
||||
['mi_5fprocess_5finfo',['mi_process_info',['../group__extended.html#ga7d862c2affd5790381da14eb102a364d',1,'mimalloc-doc.h']]],
|
||||
['mi_5fpvalloc',['mi_pvalloc',['../group__posix.html#gaeb325c39b887d3b90d85d1eb1712fb1e',1,'mimalloc-doc.h']]],
|
||||
['mi_5frealloc',['mi_realloc',['../group__malloc.html#gaf11eb497da57bdfb2de65eb191c69db6',1,'mimalloc-doc.h']]],
|
||||
['mi_5frealloc_5faligned',['mi_realloc_aligned',['../group__aligned.html#ga4028d1cf4aa4c87c880747044a8322ae',1,'mimalloc-doc.h']]],
|
||||
['mi_5frealloc_5faligned_5fat',['mi_realloc_aligned_at',['../group__aligned.html#gaf66a9ae6c6f08bd6be6fb6ea771faffb',1,'mimalloc-doc.h']]],
|
||||
['mi_5freallocarray',['mi_reallocarray',['../group__posix.html#ga48fad8648a2f1dab9c87ea9448a52088',1,'mimalloc-doc.h']]],
|
||||
['mi_5freallocf',['mi_reallocf',['../group__malloc.html#gafe68ac7c5e24a65cd55c9d6b152211a0',1,'mimalloc-doc.h']]],
|
||||
['mi_5freallocn',['mi_reallocn',['../group__malloc.html#ga61d57b4144ba24fba5c1e9b956d13853',1,'mimalloc-doc.h']]],
|
||||
['mi_5freallocn_5ftp',['mi_reallocn_tp',['../group__typed.html#ga1158b49a55dfa81f58a4426a7578f523',1,'mimalloc-doc.h']]],
|
||||
['mi_5frealpath',['mi_realpath',['../group__malloc.html#ga08cec32dd5bbe7da91c78d19f1b5bebe',1,'mimalloc-doc.h']]],
|
||||
['mi_5frecalloc',['mi_recalloc',['../group__malloc.html#ga23a0fbb452b5dce8e31fab1a1958cacc',1,'mimalloc-doc.h']]],
|
||||
['mi_5frecalloc_5faligned',['mi_recalloc_aligned',['../group__zeroinit.html#ga3e7e5c291acf1c7fd7ffd9914a9f945f',1,'mimalloc-doc.h']]],
|
||||
['mi_5frecalloc_5faligned_5fat',['mi_recalloc_aligned_at',['../group__zeroinit.html#ga4ff5e92ad73585418a072c9d059e5cf9',1,'mimalloc-doc.h']]],
|
||||
['mi_5fregister_5fdeferred_5ffree',['mi_register_deferred_free',['../group__extended.html#ga3460a6ca91af97be4058f523d3cb8ece',1,'mimalloc-doc.h']]],
|
||||
['mi_5fregister_5ferror',['mi_register_error',['../group__extended.html#gaa1d55e0e894be240827e5d87ec3a1f45',1,'mimalloc-doc.h']]],
|
||||
['mi_5fregister_5foutput',['mi_register_output',['../group__extended.html#gae5b17ff027cd2150b43a33040250cf3f',1,'mimalloc-doc.h']]],
|
||||
['mi_5freserve_5fhuge_5fos_5fpages_5fat',['mi_reserve_huge_os_pages_at',['../group__extended.html#ga7795a13d20087447281858d2c771cca1',1,'mimalloc-doc.h']]],
|
||||
['mi_5freserve_5fhuge_5fos_5fpages_5finterleave',['mi_reserve_huge_os_pages_interleave',['../group__extended.html#ga3132f521fb756fc0e8ec0b74fb58df50',1,'mimalloc-doc.h']]],
|
||||
['mi_5frezalloc',['mi_rezalloc',['../group__zeroinit.html#ga8c292e142110229a2980b37ab036dbc6',1,'mimalloc-doc.h']]],
|
||||
['mi_5frezalloc_5faligned',['mi_rezalloc_aligned',['../group__zeroinit.html#gacd71a7bce96aab38ae6de17af2eb2cf0',1,'mimalloc-doc.h']]],
|
||||
['mi_5frezalloc_5faligned_5fat',['mi_rezalloc_aligned_at',['../group__zeroinit.html#gae8b358c417e61d5307da002702b0a8e1',1,'mimalloc-doc.h']]],
|
||||
['mi_5fsmall_5fsize_5fmax',['MI_SMALL_SIZE_MAX',['../group__extended.html#ga1ea64283508718d9d645c38efc2f4305',1,'mimalloc-doc.h']]],
|
||||
['mi_5fstats_5fmerge',['mi_stats_merge',['../group__extended.html#ga854b1de8cb067c7316286c28b2fcd3d1',1,'mimalloc-doc.h']]],
|
||||
['mi_5fstats_5fprint',['mi_stats_print',['../group__extended.html#ga2d126e5c62d3badc35445e5d84166df2',1,'mimalloc-doc.h']]],
|
||||
['mi_5fstats_5fprint_5fout',['mi_stats_print_out',['../group__extended.html#ga537f13b299ddf801e49a5a94fde02c79',1,'mimalloc-doc.h']]],
|
||||
['mi_5fstats_5freset',['mi_stats_reset',['../group__extended.html#ga3bb8468b8cfcc6e2a61d98aee85c5f99',1,'mimalloc-doc.h']]],
|
||||
['mi_5fstl_5fallocator',['mi_stl_allocator',['../group__cpp.html#structmi__stl__allocator',1,'']]],
|
||||
['mi_5fstrdup',['mi_strdup',['../group__malloc.html#gac7cffe13f1f458ed16789488bf92b9b2',1,'mimalloc-doc.h']]],
|
||||
['mi_5fstrndup',['mi_strndup',['../group__malloc.html#gaaabf971c2571891433477e2d21a35266',1,'mimalloc-doc.h']]],
|
||||
['mi_5fthread_5fdone',['mi_thread_done',['../group__extended.html#ga0ae4581e85453456a0d658b2b98bf7bf',1,'mimalloc-doc.h']]],
|
||||
['mi_5fthread_5finit',['mi_thread_init',['../group__extended.html#gaf8e73efc2cbca9ebfdfb166983a04c17',1,'mimalloc-doc.h']]],
|
||||
['mi_5fthread_5fstats_5fprint_5fout',['mi_thread_stats_print_out',['../group__extended.html#gab1dac8476c46cb9eecab767eb40c1525',1,'mimalloc-doc.h']]],
|
||||
['mi_5fusable_5fsize',['mi_usable_size',['../group__extended.html#ga089c859d9eddc5f9b4bd946cd53cebee',1,'mimalloc-doc.h']]],
|
||||
['mi_5fvalloc',['mi_valloc',['../group__posix.html#ga73baaf5951f5165ba0763d0c06b6a93b',1,'mimalloc-doc.h']]],
|
||||
['mi_5fzalloc',['mi_zalloc',['../group__malloc.html#gafdd9d8bb2986e668ba9884f28af38000',1,'mimalloc-doc.h']]],
|
||||
['mi_5fzalloc_5faligned',['mi_zalloc_aligned',['../group__aligned.html#ga0cadbcf5b89a7b6fb171bc8df8734819',1,'mimalloc-doc.h']]],
|
||||
['mi_5fzalloc_5faligned_5fat',['mi_zalloc_aligned_at',['../group__aligned.html#ga5f8c2353766db522565e642fafd8a3f8',1,'mimalloc-doc.h']]],
|
||||
['mi_5fzalloc_5fsmall',['mi_zalloc_small',['../group__extended.html#ga220f29f40a44404b0061c15bc1c31152',1,'mimalloc-doc.h']]],
|
||||
['mi_5fzalloc_5ftp',['mi_zalloc_tp',['../group__typed.html#gac77a61bdaf680a803785fe307820b48c',1,'mimalloc-doc.h']]]
|
||||
];
|
||||
@@ -1,4 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['overriding_20malloc',['Overriding Malloc',['../overrides.html',1,'']]]
|
||||
];
|
||||
@@ -1,5 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['performance',['Performance',['../bench.html',1,'']]],
|
||||
['posix',['Posix',['../group__posix.html',1,'']]]
|
||||
];
|
||||
@@ -1,5 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['runtime_20options',['Runtime Options',['../group__options.html',1,'']]],
|
||||
['reserved',['reserved',['../group__analysis.html#ae848a3e6840414891035423948ca0383',1,'mi_heap_area_t']]]
|
||||
];
|
||||
@@ -1,4 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['typed_20macros',['Typed Macros',['../group__typed.html',1,'']]]
|
||||
];
|
||||
@@ -1,5 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['used',['used',['../group__analysis.html#ab820302c5cd0df133eb8e51650a008b4',1,'mi_heap_area_t']]],
|
||||
['using_20the_20library',['Using the library',['../using.html',1,'']]]
|
||||
];
|
||||
@@ -1,4 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['zero_20initialized_20re_2dallocation',['Zero initialized re-allocation',['../group__zeroinit.html',1,'']]]
|
||||
];
|
||||
@@ -1,5 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['mi_5fheap_5farea_5ft',['mi_heap_area_t',['../group__analysis.html#structmi__heap__area__t',1,'']]],
|
||||
['mi_5fstl_5fallocator',['mi_stl_allocator',['../group__cpp.html#structmi__stl__allocator',1,'']]]
|
||||
];
|
||||
@@ -1,4 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['mi_5foption_5ft',['mi_option_t',['../group__options.html#gafebf7ed116adb38ae5218bc3ce06884c',1,'mimalloc-doc.h']]]
|
||||
];
|
||||
@@ -1,4 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['_5fmi_5foption_5flast',['_mi_option_last',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca5b4357b74be0d87568036c32eb1a2e4a',1,'mimalloc-doc.h']]]
|
||||
];
|
||||
@@ -1,18 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['mi_5foption_5feager_5fcommit',['mi_option_eager_commit',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca1e8de72c93da7ff22d91e1e27b52ac2b',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5feager_5fcommit_5fdelay',['mi_option_eager_commit_delay',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca17a190c25be381142d87e0468c4c068c',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5feager_5fregion_5fcommit',['mi_option_eager_region_commit',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca32ce97ece29f69e82579679cf8a307ad',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5flarge_5fos_5fpages',['mi_option_large_os_pages',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca4192d491200d0055df0554d4cf65054e',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fos_5ftag',['mi_option_os_tag',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca4b74ae2a69e445de6c2361b73c1d14bf',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fpage_5freset',['mi_option_page_reset',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cada854dd272c66342f18a93ee254a2968',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5freserve_5fhuge_5fos_5fpages',['mi_option_reserve_huge_os_pages',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884caca7ed041be3b0b9d0b82432c7bf41af2',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5freset_5fdecommits',['mi_option_reset_decommits',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cac81ee965b130fa81238913a3c239d536',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5freset_5fdelay',['mi_option_reset_delay',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca154fe170131d5212cff57e22b99523c5',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fsegment_5fcache',['mi_option_segment_cache',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca2ecbe7ef32f5c84de3739aa4f0b805a1',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fsegment_5freset',['mi_option_segment_reset',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cafb121d30d87591850d5410ccc3a95c6d',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fshow_5ferrors',['mi_option_show_errors',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cafbf4822e5c00732c5984b32a032837f0',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fshow_5fstats',['mi_option_show_stats',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca0957ef73b2550764b4840edf48422fda',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fuse_5fnuma_5fnodes',['mi_option_use_numa_nodes',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca0ac33a18f6b659fcfaf44efb0bab1b74',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fverbose',['mi_option_verbose',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca7c8b7bf5281c581bad64f5daa6442777',1,'mimalloc-doc.h']]]
|
||||
];
|
||||
@@ -1,113 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['mi_5f_5fposix_5fmemalign',['mi__posix_memalign',['../group__posix.html#gad5a69c8fea96aa2b7a7c818c2130090a',1,'mimalloc-doc.h']]],
|
||||
['mi_5faligned_5falloc',['mi_aligned_alloc',['../group__posix.html#ga1326d2e4388630b5f81ca7206318b8e5',1,'mimalloc-doc.h']]],
|
||||
['mi_5fcalloc',['mi_calloc',['../group__malloc.html#ga97fedb4f7107c592fd7f0f0a8949a57d',1,'mimalloc-doc.h']]],
|
||||
['mi_5fcalloc_5faligned',['mi_calloc_aligned',['../group__aligned.html#ga53dddb4724042a90315b94bc268fb4c9',1,'mimalloc-doc.h']]],
|
||||
['mi_5fcalloc_5faligned_5fat',['mi_calloc_aligned_at',['../group__aligned.html#ga08647c4593f3b2eef24a919a73eba3a3',1,'mimalloc-doc.h']]],
|
||||
['mi_5fcfree',['mi_cfree',['../group__posix.html#ga705dc7a64bffacfeeb0141501a5c35d7',1,'mimalloc-doc.h']]],
|
||||
['mi_5fcheck_5fowned',['mi_check_owned',['../group__analysis.html#ga628c237489c2679af84a4d0d143b3dd5',1,'mimalloc-doc.h']]],
|
||||
['mi_5fcollect',['mi_collect',['../group__extended.html#ga421430e2226d7d468529cec457396756',1,'mimalloc-doc.h']]],
|
||||
['mi_5fexpand',['mi_expand',['../group__malloc.html#gaaee66a1d483c3e28f585525fb96707e4',1,'mimalloc-doc.h']]],
|
||||
['mi_5ffree',['mi_free',['../group__malloc.html#gaf2c7b89c327d1f60f59e68b9ea644d95',1,'mimalloc-doc.h']]],
|
||||
['mi_5ffree_5faligned',['mi_free_aligned',['../group__posix.html#ga0d28d5cf61e6bfbb18c63092939fe5c9',1,'mimalloc-doc.h']]],
|
||||
['mi_5ffree_5fsize',['mi_free_size',['../group__posix.html#gae01389eedab8d67341ff52e2aad80ebb',1,'mimalloc-doc.h']]],
|
||||
['mi_5ffree_5fsize_5faligned',['mi_free_size_aligned',['../group__posix.html#ga72e9d7ffb5fe94d69bc722c8506e27bc',1,'mimalloc-doc.h']]],
|
||||
['mi_5fgood_5fsize',['mi_good_size',['../group__extended.html#gac057927cd06c854b45fe7847e921bd47',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fcalloc',['mi_heap_calloc',['../group__heap.html#gaa6702b3c48e9e53e50e81b36f5011d55',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fcalloc_5faligned',['mi_heap_calloc_aligned',['../group__heap.html#ga4af03a6e2b93fae77424d93f889705c3',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fcalloc_5faligned_5fat',['mi_heap_calloc_aligned_at',['../group__heap.html#ga08ca6419a5c057a4d965868998eef487',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fcheck_5fowned',['mi_heap_check_owned',['../group__analysis.html#ga0d67c1789faaa15ff366c024fcaf6377',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fcollect',['mi_heap_collect',['../group__heap.html#ga7922f7495cde30b1984d0e6072419298',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fcontains_5fblock',['mi_heap_contains_block',['../group__analysis.html#gaa862aa8ed8d57d84cae41fc1022d71af',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fdelete',['mi_heap_delete',['../group__heap.html#ga2ab1af8d438819b55319c7ef51d1e409',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fdestroy',['mi_heap_destroy',['../group__heap.html#ga9f9c0844edb9717f4feacd79116b8e0d',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fget_5fbacking',['mi_heap_get_backing',['../group__heap.html#ga5d03fbe062ffcf38f0f417fd968357fc',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fget_5fdefault',['mi_heap_get_default',['../group__heap.html#ga8db4cbb87314a989a9a187464d6b5e05',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fmalloc',['mi_heap_malloc',['../group__heap.html#ga9cbed01e42c0647907295de92c3fa296',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fmalloc_5faligned',['mi_heap_malloc_aligned',['../group__heap.html#gab5b87e1805306f70df38789fcfcf6653',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fmalloc_5faligned_5fat',['mi_heap_malloc_aligned_at',['../group__heap.html#ga23acd7680fb0976dde3783254c6c874b',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fmalloc_5fsmall',['mi_heap_malloc_small',['../group__heap.html#gaa1a1c7a1f4da6826b5a25b70ef878368',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fmallocn',['mi_heap_mallocn',['../group__heap.html#ga851da6c43fe0b71c1376cee8aef90db0',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fnew',['mi_heap_new',['../group__heap.html#ga766f672ba56f2fbfeb9d9dbb0b7f6b11',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frealloc',['mi_heap_realloc',['../group__heap.html#gaaef3395f66be48f37bdc8322509c5d81',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frealloc_5faligned',['mi_heap_realloc_aligned',['../group__heap.html#gafc603b696bd14cae6da28658f950d98c',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frealloc_5faligned_5fat',['mi_heap_realloc_aligned_at',['../group__heap.html#gaf96c788a1bf553fe2d371de9365e047c',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5freallocf',['mi_heap_reallocf',['../group__heap.html#ga4a21070eb4e7cce018133c8d5f4b0527',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5freallocn',['mi_heap_reallocn',['../group__heap.html#gac74e94ad9b0c9b57c1c4d88b8825b7a8',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frealpath',['mi_heap_realpath',['../group__heap.html#ga00e95ba1e01acac3cfd95bb7a357a6f0',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frecalloc',['mi_heap_recalloc',['../group__zeroinit.html#ga8648c5fbb22a80f0262859099f06dfbd',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frecalloc_5faligned',['mi_heap_recalloc_aligned',['../group__zeroinit.html#ga9f3f999396c8f77ca5e80e7b40ac29e3',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frecalloc_5faligned_5fat',['mi_heap_recalloc_aligned_at',['../group__zeroinit.html#ga496452c96f1de8c500be9fddf52edaf7',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frezalloc',['mi_heap_rezalloc',['../group__zeroinit.html#gacfad83f14eb5d6a42a497a898e19fc76',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frezalloc_5faligned',['mi_heap_rezalloc_aligned',['../group__zeroinit.html#ga375fa8a611c51905e592d5d467c49664',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5frezalloc_5faligned_5fat',['mi_heap_rezalloc_aligned_at',['../group__zeroinit.html#gac90da54fa7e5d10bdc97ce0b51dce2eb',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fset_5fdefault',['mi_heap_set_default',['../group__heap.html#gab8631ec88c8d26641b68b5d25dcd4422',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fstrdup',['mi_heap_strdup',['../group__heap.html#ga139d6b09dbf50c3c2523d0f4d1cfdeb5',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fstrndup',['mi_heap_strndup',['../group__heap.html#ga8e3dbd46650dd26573cf307a2c8f1f5a',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fvisit_5fblocks',['mi_heap_visit_blocks',['../group__analysis.html#ga70c46687dc6e9dc98b232b02646f8bed',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fzalloc',['mi_heap_zalloc',['../group__heap.html#ga903104592c8ed53417a3762da6241133',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fzalloc_5faligned',['mi_heap_zalloc_aligned',['../group__heap.html#gaa450a59c6c7ae5fdbd1c2b80a8329ef0',1,'mimalloc-doc.h']]],
|
||||
['mi_5fheap_5fzalloc_5faligned_5fat',['mi_heap_zalloc_aligned_at',['../group__heap.html#ga45fb43a62776fbebbdf1edd99b527954',1,'mimalloc-doc.h']]],
|
||||
['mi_5fis_5fin_5fheap_5fregion',['mi_is_in_heap_region',['../group__extended.html#ga5f071b10d4df1c3658e04e7fd67a94e6',1,'mimalloc-doc.h']]],
|
||||
['mi_5fis_5fredirected',['mi_is_redirected',['../group__extended.html#gaad25050b19f30cd79397b227e0157a3f',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmalloc',['mi_malloc',['../group__malloc.html#ga3406e8b168bc74c8637b11571a6da83a',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmalloc_5faligned',['mi_malloc_aligned',['../group__aligned.html#ga68930196751fa2cca9e1fd0d71bade56',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmalloc_5faligned_5fat',['mi_malloc_aligned_at',['../group__aligned.html#ga5850da130c936bd77db039dcfbc8295d',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmalloc_5fsize',['mi_malloc_size',['../group__posix.html#ga4531c9e775bb3ae12db57c1ba8a5d7de',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmalloc_5fsmall',['mi_malloc_small',['../group__extended.html#ga7136c2e55cb22c98ecf95d08d6debb99',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmalloc_5fusable_5fsize',['mi_malloc_usable_size',['../group__posix.html#ga06d07cf357bbac5c73ba5d0c0c421e17',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmallocn',['mi_mallocn',['../group__malloc.html#ga0b05e2bf0f73e7401ae08597ff782ac6',1,'mimalloc-doc.h']]],
|
||||
['mi_5fmemalign',['mi_memalign',['../group__posix.html#gaab7fa71ea93b96873f5d9883db57d40e',1,'mimalloc-doc.h']]],
|
||||
['mi_5fnew',['mi_new',['../group__cpp.html#gaad048a9fce3d02c5909cd05c6ec24545',1,'mimalloc-doc.h']]],
|
||||
['mi_5fnew_5faligned',['mi_new_aligned',['../group__cpp.html#gaef2c2bdb4f70857902d3c8903ac095f3',1,'mimalloc-doc.h']]],
|
||||
['mi_5fnew_5faligned_5fnothrow',['mi_new_aligned_nothrow',['../group__cpp.html#gab5e29558926d934c3f1cae8c815f942c',1,'mimalloc-doc.h']]],
|
||||
['mi_5fnew_5fn',['mi_new_n',['../group__cpp.html#gae7bc4f56cd57ed3359060ff4f38bda81',1,'mimalloc-doc.h']]],
|
||||
['mi_5fnew_5fnothrow',['mi_new_nothrow',['../group__cpp.html#gaeaded64eda71ed6b1d569d3e723abc4a',1,'mimalloc-doc.h']]],
|
||||
['mi_5fnew_5frealloc',['mi_new_realloc',['../group__cpp.html#gaab78a32f55149e9fbf432d5288e38e1e',1,'mimalloc-doc.h']]],
|
||||
['mi_5fnew_5freallocn',['mi_new_reallocn',['../group__cpp.html#ga756f4b2bc6a7ecd0a90baea8e90c7907',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fdisable',['mi_option_disable',['../group__options.html#gaebf6ff707a2e688ebb1a2296ca564054',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fenable',['mi_option_enable',['../group__options.html#ga04180ae41b0d601421dd62ced40ca050',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fget',['mi_option_get',['../group__options.html#ga7e8af195cc81d3fa64ccf2662caa565a',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fis_5fenabled',['mi_option_is_enabled',['../group__options.html#ga459ad98f18b3fc9275474807fe0ca188',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fset',['mi_option_set',['../group__options.html#gaf84921c32375e25754dc2ee6a911fa60',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fset_5fdefault',['mi_option_set_default',['../group__options.html#ga7ef623e440e6e5545cb08c94e71e4b90',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fset_5fenabled',['mi_option_set_enabled',['../group__options.html#ga9a13d05fcb77489cb06d4d017ebd8bed',1,'mimalloc-doc.h']]],
|
||||
['mi_5foption_5fset_5fenabled_5fdefault',['mi_option_set_enabled_default',['../group__options.html#ga65518b69ec5d32336b50e07f74b3f629',1,'mimalloc-doc.h']]],
|
||||
['mi_5fposix_5fmemalign',['mi_posix_memalign',['../group__posix.html#gacff84f226ba9feb2031b8992e5579447',1,'mimalloc-doc.h']]],
|
||||
['mi_5fprocess_5finfo',['mi_process_info',['../group__extended.html#ga7d862c2affd5790381da14eb102a364d',1,'mimalloc-doc.h']]],
|
||||
['mi_5fpvalloc',['mi_pvalloc',['../group__posix.html#gaeb325c39b887d3b90d85d1eb1712fb1e',1,'mimalloc-doc.h']]],
|
||||
['mi_5frealloc',['mi_realloc',['../group__malloc.html#gaf11eb497da57bdfb2de65eb191c69db6',1,'mimalloc-doc.h']]],
|
||||
['mi_5frealloc_5faligned',['mi_realloc_aligned',['../group__aligned.html#ga4028d1cf4aa4c87c880747044a8322ae',1,'mimalloc-doc.h']]],
|
||||
['mi_5frealloc_5faligned_5fat',['mi_realloc_aligned_at',['../group__aligned.html#gaf66a9ae6c6f08bd6be6fb6ea771faffb',1,'mimalloc-doc.h']]],
|
||||
['mi_5freallocarray',['mi_reallocarray',['../group__posix.html#ga48fad8648a2f1dab9c87ea9448a52088',1,'mimalloc-doc.h']]],
|
||||
['mi_5freallocf',['mi_reallocf',['../group__malloc.html#gafe68ac7c5e24a65cd55c9d6b152211a0',1,'mimalloc-doc.h']]],
|
||||
['mi_5freallocn',['mi_reallocn',['../group__malloc.html#ga61d57b4144ba24fba5c1e9b956d13853',1,'mimalloc-doc.h']]],
|
||||
['mi_5frealpath',['mi_realpath',['../group__malloc.html#ga08cec32dd5bbe7da91c78d19f1b5bebe',1,'mimalloc-doc.h']]],
|
||||
['mi_5frecalloc',['mi_recalloc',['../group__malloc.html#ga23a0fbb452b5dce8e31fab1a1958cacc',1,'mimalloc-doc.h']]],
|
||||
['mi_5frecalloc_5faligned',['mi_recalloc_aligned',['../group__zeroinit.html#ga3e7e5c291acf1c7fd7ffd9914a9f945f',1,'mimalloc-doc.h']]],
|
||||
['mi_5frecalloc_5faligned_5fat',['mi_recalloc_aligned_at',['../group__zeroinit.html#ga4ff5e92ad73585418a072c9d059e5cf9',1,'mimalloc-doc.h']]],
|
||||
['mi_5fregister_5fdeferred_5ffree',['mi_register_deferred_free',['../group__extended.html#ga3460a6ca91af97be4058f523d3cb8ece',1,'mimalloc-doc.h']]],
|
||||
['mi_5fregister_5ferror',['mi_register_error',['../group__extended.html#gaa1d55e0e894be240827e5d87ec3a1f45',1,'mimalloc-doc.h']]],
|
||||
['mi_5fregister_5foutput',['mi_register_output',['../group__extended.html#gae5b17ff027cd2150b43a33040250cf3f',1,'mimalloc-doc.h']]],
|
||||
['mi_5freserve_5fhuge_5fos_5fpages_5fat',['mi_reserve_huge_os_pages_at',['../group__extended.html#ga7795a13d20087447281858d2c771cca1',1,'mimalloc-doc.h']]],
|
||||
['mi_5freserve_5fhuge_5fos_5fpages_5finterleave',['mi_reserve_huge_os_pages_interleave',['../group__extended.html#ga3132f521fb756fc0e8ec0b74fb58df50',1,'mimalloc-doc.h']]],
|
||||
['mi_5frezalloc',['mi_rezalloc',['../group__zeroinit.html#ga8c292e142110229a2980b37ab036dbc6',1,'mimalloc-doc.h']]],
|
||||
['mi_5frezalloc_5faligned',['mi_rezalloc_aligned',['../group__zeroinit.html#gacd71a7bce96aab38ae6de17af2eb2cf0',1,'mimalloc-doc.h']]],
|
||||
['mi_5frezalloc_5faligned_5fat',['mi_rezalloc_aligned_at',['../group__zeroinit.html#gae8b358c417e61d5307da002702b0a8e1',1,'mimalloc-doc.h']]],
|
||||
['mi_5fstats_5fmerge',['mi_stats_merge',['../group__extended.html#ga854b1de8cb067c7316286c28b2fcd3d1',1,'mimalloc-doc.h']]],
|
||||
['mi_5fstats_5fprint',['mi_stats_print',['../group__extended.html#ga2d126e5c62d3badc35445e5d84166df2',1,'mimalloc-doc.h']]],
|
||||
['mi_5fstats_5fprint_5fout',['mi_stats_print_out',['../group__extended.html#ga537f13b299ddf801e49a5a94fde02c79',1,'mimalloc-doc.h']]],
|
||||
['mi_5fstats_5freset',['mi_stats_reset',['../group__extended.html#ga3bb8468b8cfcc6e2a61d98aee85c5f99',1,'mimalloc-doc.h']]],
|
||||
['mi_5fstrdup',['mi_strdup',['../group__malloc.html#gac7cffe13f1f458ed16789488bf92b9b2',1,'mimalloc-doc.h']]],
|
||||
['mi_5fstrndup',['mi_strndup',['../group__malloc.html#gaaabf971c2571891433477e2d21a35266',1,'mimalloc-doc.h']]],
|
||||
['mi_5fthread_5fdone',['mi_thread_done',['../group__extended.html#ga0ae4581e85453456a0d658b2b98bf7bf',1,'mimalloc-doc.h']]],
|
||||
['mi_5fthread_5finit',['mi_thread_init',['../group__extended.html#gaf8e73efc2cbca9ebfdfb166983a04c17',1,'mimalloc-doc.h']]],
|
||||
['mi_5fthread_5fstats_5fprint_5fout',['mi_thread_stats_print_out',['../group__extended.html#gab1dac8476c46cb9eecab767eb40c1525',1,'mimalloc-doc.h']]],
|
||||
['mi_5fusable_5fsize',['mi_usable_size',['../group__extended.html#ga089c859d9eddc5f9b4bd946cd53cebee',1,'mimalloc-doc.h']]],
|
||||
['mi_5fvalloc',['mi_valloc',['../group__posix.html#ga73baaf5951f5165ba0763d0c06b6a93b',1,'mimalloc-doc.h']]],
|
||||
['mi_5fzalloc',['mi_zalloc',['../group__malloc.html#gafdd9d8bb2986e668ba9884f28af38000',1,'mimalloc-doc.h']]],
|
||||
['mi_5fzalloc_5faligned',['mi_zalloc_aligned',['../group__aligned.html#ga0cadbcf5b89a7b6fb171bc8df8734819',1,'mimalloc-doc.h']]],
|
||||
['mi_5fzalloc_5faligned_5fat',['mi_zalloc_aligned_at',['../group__aligned.html#ga5f8c2353766db522565e642fafd8a3f8',1,'mimalloc-doc.h']]],
|
||||
['mi_5fzalloc_5fsmall',['mi_zalloc_small',['../group__extended.html#ga220f29f40a44404b0061c15bc1c31152',1,'mimalloc-doc.h']]]
|
||||
];
|
||||
@@ -1,4 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['aligned_20allocation',['Aligned Allocation',['../group__aligned.html',1,'']]]
|
||||
];
|
||||
@@ -1,4 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['basic_20allocation',['Basic Allocation',['../group__malloc.html',1,'']]]
|
||||
];
|
||||
@@ -1,4 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['c_2b_2b_20wrappers',['C++ wrappers',['../group__cpp.html',1,'']]]
|
||||
];
|
||||
@@ -1,4 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['extended_20functions',['Extended Functions',['../group__extended.html',1,'']]]
|
||||
];
|
||||
@@ -1,5 +0,0 @@
|
||||
var searchData=
|
||||
[
|
||||
['heap_20introspection',['Heap Introspection',['../group__analysis.html',1,'']]],
|
||||
['heap_20allocation',['Heap Allocation',['../group__heap.html',1,'']]]
|
||||
];
|
||||
Loaded 100 of 1815 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user