mirror of
https://github.com/rui314/mold.git
synced 2026-11-10 04:31:31 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c57eca7b0 | ||
|
|
d3e0ffe0b9 | ||
|
|
17a24cdd7e | ||
|
|
4a66d7a573 | ||
|
|
cbd88ed3f3 | ||
|
|
9157414cae | ||
|
|
6663adb700 | ||
|
|
7c64f67499 | ||
|
|
7539c3fd15 | ||
|
|
83f6bc18e1 | ||
|
|
ea4cb40131 | ||
|
|
bdc3d71a23 | ||
|
|
b15ce5203d | ||
|
|
4507e6e51d | ||
|
|
c86689b227 | ||
|
|
89156f3fcc | ||
|
|
d0abdf3bbe | ||
|
|
9615b8b962 | ||
|
|
608b328cbf | ||
|
|
cbc71ac363 | ||
|
|
df9e997cc3 | ||
|
|
abe7943c06 | ||
|
|
764f802180 | ||
|
|
651adad5f2 | ||
|
|
34bfbc0983 | ||
|
|
9c355b7765 | ||
|
|
ccd47db916 | ||
|
|
ad30db1102 | ||
|
|
b3db6c9976 | ||
|
|
02ca6cc643 | ||
|
|
d5a1c7a1b3 | ||
|
|
13a4cf9994 | ||
|
|
e63bf01704 | ||
|
|
f23c1c9b69 | ||
|
|
e454d54a8a | ||
|
|
33af22848f | ||
|
|
610d90fa69 | ||
|
|
9c62c55a2c | ||
|
|
34ca952a9b | ||
|
|
487ef64336 | ||
|
|
f33968c782 | ||
|
|
7c03f5e4c0 | ||
|
|
6ff6084ab8 | ||
|
|
326e2c6ba1 | ||
|
|
efe117d946 | ||
|
|
b39ee6a985 | ||
|
|
5f99006de3 | ||
|
|
033d5f3406 | ||
|
|
d96c9b8236 | ||
|
|
a45f97b474 | ||
|
|
10a1ca0d8e | ||
|
|
4362727cc8 | ||
|
|
40ddfa533b | ||
|
|
447bb8b036 | ||
|
|
6231510794 | ||
|
|
352c086047 | ||
|
|
fb1ca49d9b | ||
|
|
1b0979c795 | ||
|
|
5a38f70a37 | ||
|
|
4da942723e | ||
|
|
e26ea4ba05 | ||
|
|
8cfa64944e | ||
|
|
21855a194c | ||
|
|
3411e17d8b | ||
|
|
e1f587eafb | ||
|
|
215f351dcd | ||
|
|
a49a201695 | ||
|
|
e7d80bcf33 | ||
|
|
feaf2632c5 | ||
|
|
b510588677 | ||
|
|
aa51aea245 | ||
|
|
b70211ec08 | ||
|
|
30f79a0d62 | ||
|
|
a3a98ac5f5 | ||
|
|
ed235f3db6 | ||
|
|
ad79674ec5 | ||
|
|
2ab4175ac1 | ||
|
|
408bdc1e87 | ||
|
|
86e6fa17a5 | ||
|
|
57c5b9ea27 | ||
|
|
23fff53e94 | ||
|
|
c829aed2df | ||
|
|
e53df1deda | ||
|
|
1283061d2b | ||
|
|
5e976dbf97 | ||
|
|
a86d94cee7 | ||
|
|
365779fbe4 | ||
|
|
0c456efebb | ||
|
|
e2d7353213 | ||
|
|
864f9a0490 | ||
|
|
262bf67b20 | ||
|
|
bffc887dae | ||
|
|
262ef21f32 | ||
|
|
07d89116a8 | ||
|
|
cc3e5fd1a8 | ||
|
|
b2858d2680 | ||
|
|
293a4ca425 | ||
|
|
22c9ec8cb6 | ||
|
|
a260630f48 | ||
|
|
86d37b4720 | ||
|
|
099663aaae | ||
|
|
678ef917cc | ||
|
|
d05e83e40b | ||
|
|
a18fe8bccf | ||
|
|
56c56cc450 | ||
|
|
998c7afb67 | ||
|
|
a90e7ac782 | ||
|
|
bd584ac05e | ||
|
|
30aecda799 | ||
|
|
05ff76f361 | ||
|
|
83d8189c51 | ||
|
|
7c56dddc8c | ||
|
|
ba8a4d9b98 | ||
|
|
afe01a1367 | ||
|
|
bcc74d68ee | ||
|
|
a6c4530e43 | ||
|
|
f83581880d | ||
|
|
0d4193f564 | ||
|
|
88b4c1eb81 | ||
|
|
cd4fb9f26e | ||
|
|
3e23192256 | ||
|
|
589cb55516 | ||
|
|
b14727149b | ||
|
|
df8e2d859c | ||
|
|
faa71c6f1c | ||
|
|
3486f69fcf | ||
|
|
0f36a40a44 | ||
|
|
73f32940ab | ||
|
|
3d255b2cdf | ||
|
|
27b543a725 | ||
|
|
79483332b7 | ||
|
|
b09be697b5 | ||
|
|
b7439b5efc | ||
|
|
8c87f16592 | ||
|
|
e16328e790 | ||
|
|
bb5d96be6e | ||
|
|
3c1a0550da | ||
|
|
dd90ade29d | ||
|
|
5866c9e95a | ||
|
|
372f34300e | ||
|
|
6cb5b53d5d | ||
|
|
1d4b2071ca | ||
|
|
f0774d4e24 | ||
|
|
f007127ad3 | ||
|
|
38c1211dba | ||
|
|
bd46edf3f0 | ||
|
|
48b2c0f8a3 | ||
|
|
8975fc2ed2 | ||
|
|
594beb67a2 | ||
|
|
804b843983 | ||
|
|
032a44626f | ||
|
|
d2cfffc24d | ||
|
|
c2a0ae1c96 | ||
|
|
cf8cce6d17 | ||
|
|
0420a527c0 | ||
|
|
54e45746cc | ||
|
|
c0c863ff8b | ||
|
|
e08efab1e7 | ||
|
|
41bc68e26d | ||
|
|
8f682da608 | ||
|
|
72ac3f200d | ||
|
|
32dcda5aa9 | ||
|
|
26e3203e9a | ||
|
|
1969b52569 | ||
|
|
53a84a182c | ||
|
|
c1b57d9977 | ||
|
|
556a5cabf9 | ||
|
|
3a4166a066 | ||
|
|
85c3467147 | ||
|
|
7f124d4271 | ||
|
|
8d2ddf7038 | ||
|
|
3d0bd4dacf | ||
|
|
d5bbe347a1 | ||
|
|
f9c721f9a9 | ||
|
|
fc16eb4e34 | ||
|
|
683583a8c4 | ||
|
|
6524389621 | ||
|
|
75e059af5b | ||
|
|
ecd61a4fed | ||
|
|
8614fbbaff | ||
|
|
e1445e7a84 | ||
|
|
be01de4b45 | ||
|
|
968a8cf0be | ||
|
|
e5e3925d5b | ||
|
|
57b09ccfb7 | ||
|
|
50d0cc6959 | ||
|
|
f43c0fde26 | ||
|
|
3c4dd61710 | ||
|
|
ed8580f86a | ||
|
|
b2f44b07fc | ||
|
|
1ccf1f8ffb | ||
|
|
2e3e177a6d | ||
|
|
5e345b0f82 | ||
|
|
6fd2c69926 | ||
|
|
f8e7bace50 | ||
|
|
46def3b49f | ||
|
|
c9a7ae733c | ||
|
|
9d437dfd6f | ||
|
|
5d277c1b90 | ||
|
|
a8b070bc56 | ||
|
|
6eb6a93ced | ||
|
|
89ebf2179c | ||
|
|
22635e3716 | ||
|
|
2ae49688e8 | ||
|
|
988241975f | ||
|
|
f5fb6b4abb | ||
|
|
ada8dec8da | ||
|
|
a18316a83a | ||
|
|
3f81358b54 | ||
|
|
ce3ac5d2f4 | ||
|
|
a2c7ad822b | ||
|
|
72fffee54f | ||
|
|
c77c41eaf8 | ||
|
|
d1a0e2c2c5 | ||
|
|
bbb03c9096 | ||
|
|
0b9f4b0e45 | ||
|
|
f689d25798 | ||
|
|
dae1edc578 | ||
|
|
df00fe8a58 | ||
|
|
428218ce30 | ||
|
|
20eaa1d19c | ||
|
|
d30d743cb0 | ||
|
|
14a6e2012d | ||
|
|
94fb470e29 | ||
|
|
968e970adb | ||
|
|
2d0a38436c | ||
|
|
9513f63a29 | ||
|
|
8b373d38ce | ||
|
|
456d34bde3 | ||
|
|
622c2d479c | ||
|
|
d835d016e0 | ||
|
|
700a475597 | ||
|
|
c2a34cc1e3 | ||
|
|
1f67ccfa20 | ||
|
|
2b7b45c8d1 | ||
|
|
c9803c8984 | ||
|
|
c55931dae5 | ||
|
|
94122f4ffd | ||
|
|
22f3b7ed24 | ||
|
|
a67e9f54f8 | ||
|
|
63933a9221 | ||
|
|
e25802f84f | ||
|
|
73a467a3c9 | ||
|
|
91378b9c2f | ||
|
|
7f2a2c4233 | ||
|
|
ddd5264abd | ||
|
|
5797a59ab9 | ||
|
|
9875150677 | ||
|
|
c9ff2a21c5 | ||
|
|
bd555b33ab | ||
|
|
51427cd7d2 | ||
|
|
0e0bd8a4f0 | ||
|
|
a8321acb88 | ||
|
|
3127508040 | ||
|
|
4fc877779b | ||
|
|
dae7dca0b9 | ||
|
|
b43b31eae8 | ||
|
|
bedcb1bf56 | ||
|
|
9fdb09ae0e | ||
|
|
f1b50695e5 | ||
|
|
9fcd6e965d | ||
|
|
228b7464bf | ||
|
|
ce87053c78 | ||
|
|
554caeeb34 | ||
|
|
3bddfc5fd2 | ||
|
|
127758047c | ||
|
|
8449ead2a7 | ||
|
|
aab34e52cf | ||
|
|
8186b8a242 | ||
|
|
bd77bcdab4 | ||
|
|
f82c0c0f83 | ||
|
|
7607e3cbc2 | ||
|
|
00acf3d2f6 | ||
|
|
2942ba83d4 | ||
|
|
30bd1c26c8 | ||
|
|
ba640ceac6 | ||
|
|
097b872536 | ||
|
|
ab5d04b670 | ||
|
|
5fe60e3b6d | ||
|
|
e0ff005333 | ||
|
|
98df612168 | ||
|
|
15979f9a10 | ||
|
|
5218f73773 | ||
|
|
064ab23511 | ||
|
|
774e5c3bb6 | ||
|
|
28598c7703 | ||
|
|
91bcc51e60 | ||
|
|
b5a4d98ef2 | ||
|
|
b365d6ba1a | ||
|
|
2e9a4ccd10 | ||
|
|
2fbdf1f982 | ||
|
|
20173ad233 | ||
|
|
602f423241 | ||
|
|
460b443736 | ||
|
|
99b886f9c4 | ||
|
|
feb9cd5859 | ||
|
|
c921959b81 | ||
|
|
7816fd8249 | ||
|
|
1483ed9ac6 | ||
|
|
0cc131c065 | ||
|
|
2e11c57a6e | ||
|
|
67738238be | ||
|
|
467b08a782 | ||
|
|
903fff7701 | ||
|
|
430818b366 | ||
|
|
dda70ce920 | ||
|
|
f7876a9f2a | ||
|
|
303cba23b5 | ||
|
|
13eda8051a | ||
|
|
0763dd5639 | ||
|
|
95276c98fe | ||
|
|
5a0ad2aa59 | ||
|
|
4db7574e84 | ||
|
|
997e53472d | ||
|
|
3a308270bd | ||
|
|
af6475caa7 | ||
|
|
d0210b85b9 | ||
|
|
73cdbb320d | ||
|
|
6088d954f6 | ||
|
|
20ca4e38f4 | ||
|
|
616f8b2205 | ||
|
|
4379dc4afa | ||
|
|
c5106da010 | ||
|
|
0d6e373ffc | ||
|
|
6361875a15 | ||
|
|
9c7970f0af | ||
|
|
59ea52933d | ||
|
|
4fa41e1ad4 | ||
|
|
ce35e19c6d | ||
|
|
8643520464 | ||
|
|
d01deafff0 | ||
|
|
8f74e48dfb | ||
|
|
59a713fb4c | ||
|
|
626619a8b0 | ||
|
|
585eae2030 | ||
|
|
730e970e79 | ||
|
|
26c503ad30 | ||
|
|
1a99724eca | ||
|
|
03dad9ba14 | ||
|
|
e7a7f9e39a | ||
|
|
e228abfd5a | ||
|
|
61f28d270c | ||
|
|
9535e080e9 | ||
|
|
f9750d163d | ||
|
|
f8cb32e59a | ||
|
|
4508d93d08 | ||
|
|
fb4b67ae18 | ||
|
|
1e98ec0b38 | ||
|
|
0b50ebda47 | ||
|
|
5469f5ac1b | ||
|
|
068ce4654f | ||
|
|
800fcdc5d4 | ||
|
|
0d7a5a7284 | ||
|
|
7db72a871c | ||
|
|
9842388cd0 | ||
|
|
29c4403ec4 | ||
|
|
74124636dc | ||
|
|
193c245781 | ||
|
|
5cdc761746 | ||
|
|
d35d3922f0 | ||
|
|
c4850273c7 | ||
|
|
86f3130e44 | ||
|
|
2dc1fecb02 | ||
|
|
33e3a75a79 | ||
|
|
3fe46175f3 | ||
|
|
861bccce72 | ||
|
|
60a75d0549 | ||
|
|
610042a8e9 | ||
|
|
1cfa4a93ae | ||
|
|
23905bba19 | ||
|
|
eb391f99cf | ||
|
|
78713299e3 | ||
|
|
1f32a6daf6 | ||
|
|
6b00a6c2ca | ||
|
|
f18af9e2f4 | ||
|
|
288cd5b400 | ||
|
|
48dd520014 | ||
|
|
2aa24949c7 | ||
|
|
323ad30e25 | ||
|
|
96f7b664e9 | ||
|
|
a137bc4111 | ||
|
|
0f6a03a9ba | ||
|
|
17cb7d8dce | ||
|
|
0df3a2bf7b | ||
|
|
d5c59fc919 | ||
|
|
0364934636 | ||
|
|
edd10419dd | ||
|
|
c1c6749940 | ||
|
|
750d536401 | ||
|
|
e56a3751bf | ||
|
|
ad87ac28b7 | ||
|
|
7cb97e153a | ||
|
|
db4bcd4138 | ||
|
|
1493a5cfd3 | ||
|
|
04033a1b52 | ||
|
|
d2b7b04be3 | ||
|
|
bc22065684 | ||
|
|
bf2d6bda28 | ||
|
|
d1cc5844c6 | ||
|
|
c5e9c30ce8 | ||
|
|
2fb054d4a5 | ||
|
|
8fb6fb295b | ||
|
|
341705e4a2 | ||
|
|
53848be39f | ||
|
|
b3ed06aa39 | ||
|
|
207d8535f8 | ||
|
|
519595c0c3 | ||
|
|
916f8b196a | ||
|
|
38b2843f84 | ||
|
|
df371c392b | ||
|
|
5171c9164e | ||
|
|
c74486172b | ||
|
|
8fb9cb8815 | ||
|
|
dd4048b7c4 | ||
|
|
d3dc5e31eb | ||
|
|
dcc0dff67d | ||
|
|
b1382fd5e6 | ||
|
|
f4ce278729 | ||
|
|
30dd63cc0a | ||
|
|
2dbbc54c28 | ||
|
|
55552b1481 | ||
|
|
1e24ce1f50 | ||
|
|
687bd8ab65 | ||
|
|
08f34e3d67 | ||
|
|
83e0632935 | ||
|
|
2e7a0ad5f4 | ||
|
|
1c63e0577a | ||
|
|
c45db5c659 | ||
|
|
0d205aa5e8 | ||
|
|
501a9fb57d | ||
|
|
33d388cc48 | ||
|
|
5010c0e66a | ||
|
|
a484df6885 | ||
|
|
ce12303229 | ||
|
|
fb046cc6ed | ||
|
|
f172cada2f | ||
|
|
5a5d25895c | ||
|
|
bdecfeef7f | ||
|
|
86fd0241e0 | ||
|
|
c3e8fea746 | ||
|
|
336a7b89c3 | ||
|
|
0d368b44de | ||
|
|
12ff7bb067 | ||
|
|
305f0f801e | ||
|
|
e11aa75543 | ||
|
|
2d79d1f65c | ||
|
|
e4c682b645 | ||
|
|
cc3d9a274e | ||
|
|
2d5c9e0ff7 | ||
|
|
1a58a0a6d4 | ||
|
|
2e099a12a7 | ||
|
|
8352edfe94 | ||
|
|
87f0262ce8 | ||
|
|
3b987874f4 | ||
|
|
4d9de802fc | ||
|
|
6102ed531c | ||
|
|
c9ec169d50 | ||
|
|
944d0fd3f6 | ||
|
|
88e275f2b1 | ||
|
|
d91ebb6c0c | ||
|
|
e48b7e767b | ||
|
|
8d0f3ee849 | ||
|
|
fc66759444 | ||
|
|
8bedc4d7e7 | ||
|
|
c2baf10d6b | ||
|
|
5dc906dc6b | ||
|
|
8ea3316c7f | ||
|
|
5ff581b363 | ||
|
|
7778ef7fa3 | ||
|
|
ec4c1ae981 | ||
|
|
1906d210ab | ||
|
|
864d91569d | ||
|
|
c05e2691c0 | ||
|
|
d3ab0c732c | ||
|
|
bd16377bd8 | ||
|
|
c38bf979ce | ||
|
|
8251c0d467 | ||
|
|
52fc5877f7 | ||
|
|
e1275942c8 | ||
|
|
9e4e81b061 | ||
|
|
c7f181c30b | ||
|
|
03721f9abe | ||
|
|
a79a6059e2 | ||
|
|
1145900c51 | ||
|
|
6bbf82f3b0 | ||
|
|
7aae0bf7ba | ||
|
|
2b0ab0e974 | ||
|
|
4bf21122e1 | ||
|
|
b6b6c178b4 | ||
|
|
5eb81b4e00 | ||
|
|
f5aeeeb01d | ||
|
|
1efc14d45d | ||
|
|
da5d4380cf | ||
|
|
9d873f555e | ||
|
|
a39086fa3b | ||
|
|
ffac38ae9e | ||
|
|
182db15da7 | ||
|
|
4801234609 | ||
|
|
6ea03e6433 | ||
|
|
5000a5f05d | ||
|
|
89f8990dde | ||
|
|
e5adf2fe49 | ||
|
|
ed0996a590 | ||
|
|
f19c242901 | ||
|
|
443a9c6d2d | ||
|
|
d414f8ff6b | ||
|
|
1205b1c18b | ||
|
|
1b86708683 | ||
|
|
ef04befcba | ||
|
|
6c964df459 | ||
|
|
c74c001891 | ||
|
|
324658fe1c | ||
|
|
c89d742be6 | ||
|
|
0f611697e0 | ||
|
|
850627e7c9 | ||
|
|
c4e8fab106 | ||
|
|
a7f1e20594 | ||
|
|
48ee718f23 | ||
|
|
ae91e487df | ||
|
|
1709ad5996 | ||
|
|
424f43772a | ||
|
|
3663b60074 | ||
|
|
3c123cfe9d | ||
|
|
0fa4cff086 | ||
|
|
2f5366f764 | ||
|
|
92c2c9bc8f | ||
|
|
19db4bbe13 | ||
|
|
0929788ccc | ||
|
|
fb4c21a65a | ||
|
|
b36562fc94 | ||
|
|
5cf3ad6fe7 | ||
|
|
930964ab38 | ||
|
|
56de92f65f | ||
|
|
7d8bf817e9 | ||
|
|
438d67bcf5 | ||
|
|
020863f238 | ||
|
|
322ce8eb9d | ||
|
|
57f2099a36 | ||
|
|
2fcfb9b470 | ||
|
|
8ef01f87b1 | ||
|
|
5eed32bcf2 | ||
|
|
c7767dd36d | ||
|
|
5b147db113 | ||
|
|
313629e1af | ||
|
|
260182d439 | ||
|
|
458f88b6cf | ||
|
|
34ee49f3de | ||
|
|
5746060cde | ||
|
|
f7f2ef6182 | ||
|
|
886dbe6b24 | ||
|
|
d1d6c00e0c | ||
|
|
3bf3709799 | ||
|
|
d504b24419 | ||
|
|
b8ab2fd016 | ||
|
|
a5448f2a48 | ||
|
|
e541ad3811 | ||
|
|
db415be21c | ||
|
|
33de554f41 | ||
|
|
6abf26ef38 | ||
|
|
1ed941f3f0 | ||
|
|
e62de0b31f | ||
|
|
56b9ad9c62 | ||
|
|
9464a12960 | ||
|
|
7aa87220c4 | ||
|
|
95ad47ca7d | ||
|
|
9db16a87f5 | ||
|
|
3d33349691 | ||
|
|
5fab142c73 | ||
|
|
0a4ec45949 | ||
|
|
c9fbc48a6c | ||
|
|
14b2ba3b74 | ||
|
|
41c762b6c5 | ||
|
|
d22e85f345 | ||
|
|
07ecfbcfe1 | ||
|
|
c623b4e3f0 | ||
|
|
747c205773 | ||
|
|
2941d75c71 | ||
|
|
a2552b6fd1 | ||
|
|
919f58dc5d | ||
|
|
ef6e6ecdf4 | ||
|
|
9c7b95f108 | ||
|
|
6f6e0a6611 | ||
|
|
3c1425fe88 | ||
|
|
101c64b427 | ||
|
|
622e18bd40 | ||
|
|
652ab0c1da | ||
|
|
64f6a89a0d | ||
|
|
b93ad52a73 | ||
|
|
6b23821276 | ||
|
|
d37bc2eb2d | ||
|
|
b5f9d936da | ||
|
|
97bf734b67 | ||
|
|
38dfad1c6f | ||
|
|
302a9ed8d8 | ||
|
|
264ca5aab4 | ||
|
|
b2ffaf9294 | ||
|
|
a1e0ca8a7f | ||
|
|
54f50a7cb4 | ||
|
|
ae53c4202a | ||
|
|
1dd2b9e662 | ||
|
|
4d02762fd9 | ||
|
|
218038c7a4 | ||
|
|
c320f546fe | ||
|
|
068e52859b | ||
|
|
615978a132 | ||
|
|
867c285332 | ||
|
|
d92cef9559 | ||
|
|
b67335fadd | ||
|
|
18a424dbc9 | ||
|
|
d315ff2eff | ||
|
|
5c275ce843 | ||
|
|
46f74fe7ed | ||
|
|
8e922416ee | ||
|
|
24affd8ed3 | ||
|
|
0e4db53774 | ||
|
|
57442144ef | ||
|
|
3f45f68a5d | ||
|
|
3625c40f04 | ||
|
|
9094e2eee1 | ||
|
|
5e8e35940a | ||
|
|
3400567fee | ||
|
|
91ce29d30c | ||
|
|
3564b71c4f | ||
|
|
e9714be08f | ||
|
|
99d6478b35 | ||
|
|
321d8208cb | ||
|
|
76ab25d486 | ||
|
|
3b58bf52a7 | ||
|
|
ce85848867 | ||
|
|
9784632214 | ||
|
|
e6625d0c86 | ||
|
|
1f53fd2adf | ||
|
|
c0cd1f5d58 | ||
|
|
8c4d65179f | ||
|
|
f1ccc05c3f | ||
|
|
46c38ca738 | ||
|
|
546c552644 | ||
|
|
a1a8058b3d | ||
|
|
d8fa5343ec | ||
|
|
9dcc70f6ee | ||
|
|
139aae9444 | ||
|
|
956433bd8e | ||
|
|
6952ba3953 | ||
|
|
0788013cd8 | ||
|
|
39847a4a27 | ||
|
|
6f6a814439 | ||
|
|
a789e48452 | ||
|
|
7b6db0308e | ||
|
|
3e185623c1 | ||
|
|
459cdcb6f1 | ||
|
|
a9e697660b | ||
|
|
dbfb584ab9 | ||
|
|
154031af66 | ||
|
|
ee11979a76 | ||
|
|
b87398be69 | ||
|
|
982b06da2d | ||
|
|
5c8cd76d2e | ||
|
|
b28e771756 | ||
|
|
1174cf7406 | ||
|
|
190da9ce92 | ||
|
|
874876ab82 | ||
|
|
4698c1364a | ||
|
|
adde0130f5 | ||
|
|
562b52906f | ||
|
|
c56429b1d6 | ||
|
|
7cdf0abbb3 | ||
|
|
d4004cf054 | ||
|
|
c1bdbe2327 | ||
|
|
ce78dfd4b2 | ||
|
|
2331492840 | ||
|
|
a189dae361 | ||
|
|
d72e96f02e | ||
|
|
1fdb13c816 | ||
|
|
8af8eced16 | ||
|
|
d92d6c0cd7 | ||
|
|
20edf1b44b | ||
|
|
441d53c302 | ||
|
|
26168f662a | ||
|
|
2af061f7fd | ||
|
|
fea0734c41 | ||
|
|
f0f8b30a2d | ||
|
|
9d748ba967 | ||
|
|
37863ccbaf | ||
|
|
fb5b828996 | ||
|
|
7d36399035 | ||
|
|
0825208628 | ||
|
|
86d67b1b17 | ||
|
|
8612d411d1 | ||
|
|
3fd9ca553b | ||
|
|
62c077924a | ||
|
|
3795b71350 | ||
|
|
bcb21edcb1 | ||
|
|
30617cc632 | ||
|
|
183e4db696 | ||
|
|
1b9dc06150 | ||
|
|
34b63e7f92 | ||
|
|
045bc58da5 | ||
|
|
70b69b2698 | ||
|
|
0856d3f76e | ||
|
|
f8176051c1 | ||
|
|
80f2c66413 | ||
|
|
e00ddceda2 | ||
|
|
9e6e472b2a | ||
|
|
c55e1216b8 | ||
|
|
07636727c7 | ||
|
|
75c142f321 | ||
|
|
8e2e6d8603 | ||
|
|
fdcb3c2efe | ||
|
|
b4c148dd0c | ||
|
|
4e5fe38a2d | ||
|
|
9fd8313657 | ||
|
|
6a71ec6804 | ||
|
|
896e49cc9e | ||
|
|
258ec353b0 | ||
|
|
7b9bfe08a6 | ||
|
|
a3512e6636 | ||
|
|
145369e1f1 | ||
|
|
26591cfd41 | ||
|
|
c8abc0f189 | ||
|
|
ede7a5aaa3 | ||
|
|
5919914c54 | ||
|
|
34bd56e15b | ||
|
|
da550493b6 | ||
|
|
b1055b42b7 | ||
|
|
b941a37b1e | ||
|
|
c0ff4a246b | ||
|
|
ed1acd31c8 | ||
|
|
cda57cfe4a | ||
|
|
428aac7458 | ||
|
|
629f2157dd | ||
|
|
8dc8ddb5d4 | ||
|
|
d4c7062bd4 | ||
|
|
bbf591288e | ||
|
|
f702c398b2 | ||
|
|
a5eb8b9d24 | ||
|
|
56c7761485 | ||
|
|
6d588cc3a8 | ||
|
|
e01f8b4993 | ||
|
|
cbf786bb59 | ||
|
|
2ee1b530b6 | ||
|
|
b96a4150d9 | ||
|
|
a6777e9c50 | ||
|
|
20effdc21c | ||
|
|
c7ad760534 | ||
|
|
7406b18042 | ||
|
|
c97d6088e5 | ||
|
|
01240db538 | ||
|
|
055e2423e5 | ||
|
|
ac7a7cb1f3 | ||
|
|
fe59dc9dff | ||
|
|
3bf132f7ed | ||
|
|
100fcaebf4 | ||
|
|
1b3e0fe0fb | ||
|
|
b0776d9496 | ||
|
|
7b6dc763ad | ||
|
|
43e3aa24ef | ||
|
|
1cbae48a29 | ||
|
|
623549ff45 | ||
|
|
042910a848 | ||
|
|
0f7c87ca03 | ||
|
|
febb696a39 | ||
|
|
594dd50246 | ||
|
|
6aa2c56497 | ||
|
|
5ca3d4bf48 | ||
|
|
a3a0582ac4 | ||
|
|
6b5f0f1c92 | ||
|
|
5dcb2b634e | ||
|
|
aa9cab8323 | ||
|
|
d739547feb | ||
|
|
20d80b806a | ||
|
|
21be1d7d5e | ||
|
|
1227897ab0 | ||
|
|
3cd25344a8 | ||
|
|
3e02327858 | ||
|
|
c8013ae871 | ||
|
|
98b35d3793 | ||
|
|
5a8181f7ff | ||
|
|
f38e131242 | ||
|
|
2dd6bbbb3d | ||
|
|
87e2e06530 | ||
|
|
678dcfb748 | ||
|
|
7b4afc48ff | ||
|
|
fda84aa648 | ||
|
|
12b5699750 | ||
|
|
aa300ae973 | ||
|
|
c1de2d53dc | ||
|
|
da7c3f878f | ||
|
|
5bf05b5f45 | ||
|
|
410e1633e2 | ||
|
|
5871018d10 | ||
|
|
2940d93123 | ||
|
|
be0131fc63 | ||
|
|
4d8d3fda55 | ||
|
|
28f89b0a4f | ||
|
|
906abcedee | ||
|
|
1950def5f9 | ||
|
|
692fe9af1e | ||
|
|
ff6afd39e0 | ||
|
|
a712f91a03 | ||
|
|
8c7f31cafa | ||
|
|
eede5a08b0 | ||
|
|
ecdb6034af | ||
|
|
85d496dd45 | ||
|
|
00273f3d0c | ||
|
|
7cad171a34 | ||
|
|
1d0c1f8f32 | ||
|
|
7ffdcd49d8 | ||
|
|
bb0e601eaa | ||
|
|
f34c345c7c | ||
|
|
d2bba23541 | ||
|
|
fc49faa896 | ||
|
|
ddbf501a66 | ||
|
|
be13b5de4a | ||
|
|
72e26d9b69 | ||
|
|
6d51493e46 | ||
|
|
25c7aee1d9 | ||
|
|
fafc0d0b94 | ||
|
|
8d16301665 | ||
|
|
98042443ac | ||
|
|
2c86726c06 | ||
|
|
ba43a01c8c | ||
|
|
c1dc0e6c2d | ||
|
|
3ab548971f | ||
|
|
b6549580be | ||
|
|
807af97a4b | ||
|
|
682fc41c0d | ||
|
|
ff0679f7b8 | ||
|
|
e58f55959d | ||
|
|
9507f11f77 | ||
|
|
114fb21b8e |
No files matched your search
+21
-84
@@ -6,41 +6,21 @@ on:
|
||||
branches: [ main ]
|
||||
env:
|
||||
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
|
||||
MOLD_SUPPRESS_MACHO_WARNING: 1
|
||||
jobs:
|
||||
build-clang-make:
|
||||
build-clang:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- test
|
||||
- test-asan
|
||||
- test-tsan
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: rui314/setup-mold@staging
|
||||
- name: install-build-deps
|
||||
run: sudo apt-get update && sudo ./install-build-deps.sh
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make test
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
make -j$(nproc) CC=clang CXX=clang++ ${{ matrix.target }}
|
||||
|
||||
build-clang-cmake:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- ''
|
||||
# Disable PCH for the default configuration. This prevents relying on implicit includes.
|
||||
- '-DCMAKE_DISABLE_PRECOMPILE_HEADERS=On'
|
||||
- '-DMOLD_USE_ASAN=On'
|
||||
- '-DMOLD_USE_TSAN=On'
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: rui314/setup-mold@staging
|
||||
- name: install-build-deps
|
||||
run: sudo apt-get update && sudo ./install-build-deps.sh
|
||||
run: sudo ./install-build-deps.sh update
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
@@ -52,52 +32,25 @@ jobs:
|
||||
cmake --build . -j$(nproc)
|
||||
ctest . -j$(nproc)
|
||||
|
||||
build-gcc-make:
|
||||
build-gcc:
|
||||
runs-on: ubuntu-20.04
|
||||
container: gcc:11.1.0
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install-build-deps
|
||||
run: |
|
||||
dpkg --add-architecture i386
|
||||
apt-get update
|
||||
./install-build-deps.sh
|
||||
apt-get install -y sudo qemu-user gcc-10-i686-linux-gnu gcc-10-aarch64-linux-gnu gcc-10-riscv64-linux-gnu gcc-10-arm-linux-gnueabihf g++-10-i686-linux-gnu g++-10-aarch64-linux-gnu g++-10-riscv64-linux-gnu g++-10-arm-linux-gnueabihf
|
||||
ln -sf /usr/bin/i686-linux-gnu-gcc-10 /usr/bin/i686-linux-gnu-gcc
|
||||
ln -sf /usr/bin/i686-linux-gnu-g++-10 /usr/bin/i686-linux-gnu-g++
|
||||
ln -sf /usr/bin/aarch64-linux-gnu-gcc-10 /usr/bin/aarch64-linux-gnu-gcc
|
||||
ln -sf /usr/bin/aarch64-linux-gnu-g++-10 /usr/bin/aarch64-linux-gnu-g++
|
||||
ln -sf /usr/bin/arm-linux-gnueabihf-gcc-10 /usr/bin/arm-linux-gnueabihf-gcc
|
||||
ln -sf /usr/bin/arm-linux-gnueabihf-g++-10 /usr/bin/arm-linux-gnueabihf-g++
|
||||
ln -sf /usr/bin/riscv64-linux-gnu-gcc-10 /usr/bin/riscv64-linux-gnu-gcc
|
||||
ln -sf /usr/bin/riscv64-linux-gnu-g++-10 /usr/bin/riscv64-linux-gnu-g++
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make test-all
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
make -j$(nproc) CC=gcc CXX=g++ test-all
|
||||
|
||||
build-gcc-cmake:
|
||||
runs-on: ubuntu-20.04
|
||||
container: gcc:11.1.0
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: install-build-deps
|
||||
run: |
|
||||
# Install cross toolchains
|
||||
dpkg --add-architecture i386
|
||||
apt-get update
|
||||
./install-build-deps.sh
|
||||
apt-get install -y sudo qemu-user gcc-10-i686-linux-gnu gcc-10-aarch64-linux-gnu gcc-10-riscv64-linux-gnu gcc-10-arm-linux-gnueabihf g++-10-i686-linux-gnu g++-10-aarch64-linux-gnu g++-10-riscv64-linux-gnu g++-10-arm-linux-gnueabihf
|
||||
ln -sf /usr/bin/i686-linux-gnu-gcc-10 /usr/bin/i686-linux-gnu-gcc
|
||||
ln -sf /usr/bin/i686-linux-gnu-g++-10 /usr/bin/i686-linux-gnu-g++
|
||||
ln -sf /usr/bin/aarch64-linux-gnu-gcc-10 /usr/bin/aarch64-linux-gnu-gcc
|
||||
ln -sf /usr/bin/aarch64-linux-gnu-g++-10 /usr/bin/aarch64-linux-gnu-g++
|
||||
./install-build-deps.sh update
|
||||
apt-get install -y sudo qemu-user gdb zstd dwarfdump {gcc,g++}-10-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf
|
||||
|
||||
for i in i686 aarch64 riscv64 powerpc powerpc64 powerpc64le s390x sparc64 m68k sh4 alpha; do
|
||||
ln -sf /usr/bin/$i-linux-gnu-gcc-10 /usr/bin/$i-linux-gnu-gcc
|
||||
ln -sf /usr/bin/$i-linux-gnu-g++-10 /usr/bin/$i-linux-gnu-g++
|
||||
done
|
||||
|
||||
ln -sf /usr/bin/arm-linux-gnueabihf-gcc-10 /usr/bin/arm-linux-gnueabihf-gcc
|
||||
ln -sf /usr/bin/arm-linux-gnueabihf-g++-10 /usr/bin/arm-linux-gnueabihf-g++
|
||||
ln -sf /usr/bin/riscv64-linux-gnu-gcc-10 /usr/bin/riscv64-linux-gnu-gcc
|
||||
ln -sf /usr/bin/riscv64-linux-gnu-g++-10 /usr/bin/riscv64-linux-gnu-g++
|
||||
|
||||
# Install a RV32 toolchain from third party since it's not available
|
||||
# as an Ubuntu package.
|
||||
@@ -109,6 +62,7 @@ jobs:
|
||||
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
|
||||
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-g++ -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-g++
|
||||
chmod 755 /usr/bin/riscv32-linux-gnu-gcc /usr/bin/riscv32-linux-gnu-g++
|
||||
shell: bash
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
@@ -128,32 +82,16 @@ jobs:
|
||||
build/out
|
||||
build/Testing
|
||||
|
||||
build-macos-make:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- test
|
||||
- test-asan
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: rui314/setup-mold@staging
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make test
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
make -j$(sysctl -n hw.logicalcpu) ${{ matrix.target }}
|
||||
|
||||
build-macos-cmake:
|
||||
build-macos:
|
||||
runs-on: macos-11
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- ''
|
||||
# Disable PCH for the default configuration. This prevents relying on implicit includes.
|
||||
- '-DCMAKE_DISABLE_PRECOMPILE_HEADERS=On'
|
||||
- '-DMOLD_USE_ASAN=On'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
@@ -163,12 +101,11 @@ jobs:
|
||||
cd build
|
||||
cmake ${{ matrix.target }} ..
|
||||
cmake --build . -j$(sysctl -n hw.physicalcpu)
|
||||
ctest . -j$(sysctl -n hw.physicalcpu)
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
*.o
|
||||
*.so
|
||||
*~
|
||||
**/.DS_Store
|
||||
.cache
|
||||
compile_commands.json
|
||||
/mold
|
||||
|
||||
+269
-73
@@ -1,17 +1,58 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(mold VERSION 1.4.2)
|
||||
# You can customize a build by specifying CMake options. An option may be
|
||||
# given in the -Dvariable=value form. For a boolean variable, `ON` or `1`
|
||||
# means true while `OFF` or `0` means false.
|
||||
#
|
||||
# Here are a couple of common cmake options:
|
||||
#
|
||||
# -DCMAKE_C_COMPILER=<command-name>
|
||||
#
|
||||
# Specifies a C compiler name to use. The default value is `cc`.
|
||||
#
|
||||
# -DCMAKE_CXX_COMPILER=<command-name>
|
||||
#
|
||||
# Specifies a C++ compiler name to use. The default value is `c++`.
|
||||
#
|
||||
# -DCMAKE_INSTALL_PREFIX=<directory>
|
||||
#
|
||||
# Specifies an install target directory. The default value is `/usr/local`.
|
||||
#
|
||||
# -DCMAKE_BUILD_TYPE=[Debug | Release | RelWithDebInfo | MinSizeRel]
|
||||
#
|
||||
# Specifies a build type. The default is `Release` which is the right
|
||||
# option unless you are debugging mold.
|
||||
#
|
||||
# An example of a cmake command line is shown below:
|
||||
#
|
||||
# $ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_CXX_COMPILER=g++-12 ..
|
||||
#
|
||||
# where `..` refers this directory.
|
||||
#
|
||||
# With cmake, you may run `cmake --install .` instead of `make install` to
|
||||
# install build artifacts to system directories. If you want to install
|
||||
# artifacts to a temporary target directory, run `cmake --install . --prefix
|
||||
# <dir>`.
|
||||
#
|
||||
# You can see the current cmake variables and their values by running
|
||||
# `cmake -N -L .` in a build directory.
|
||||
#
|
||||
# Note that in this file, we provide various dials and knobs to configure
|
||||
# how to build mold. However, as a policy, we do not provide a way to
|
||||
# enable/disable any individual mold's feature. In other words, we do not
|
||||
# provide options like `--enable-foo` or `--disable-foo`. The motivation
|
||||
# behind it is build reproducibility. We want to guarantees that all builds
|
||||
# of the mold linker of the same version will have the exactly same set of
|
||||
# features and behave exactly the same.
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(mold VERSION 1.8.0)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# Add -fuse-ld=mold if accepted by the compiler.
|
||||
option(MOLD_USE_MOLD "Use mold to build mold" ON)
|
||||
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
|
||||
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
|
||||
if(MOLD_USE_MOLD)
|
||||
include(CheckLinkerFlag)
|
||||
check_linker_flag(CXX -fuse-ld=mold CXX_SUPPORTS_FUSE_MOLD)
|
||||
if(CXX_SUPPORTS_FUSE_MOLD)
|
||||
add_link_options(-fuse-ld=mold)
|
||||
endif()
|
||||
add_link_options(-fuse-ld=mold)
|
||||
endif()
|
||||
|
||||
add_executable(mold)
|
||||
@@ -22,36 +63,44 @@ if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
|
||||
target_compile_options(mold PRIVATE
|
||||
-fno-exceptions
|
||||
-fno-unwind-tables
|
||||
-fno-asynchronous-unwind-tables)
|
||||
-fno-asynchronous-unwind-tables
|
||||
-Wno-sign-compare
|
||||
-Wno-unused-function)
|
||||
endif()
|
||||
|
||||
# Build mold with -flto if MOLD_LTO=On
|
||||
# Build mold with -flto if -DMOLD_LTO=ON
|
||||
option(MOLD_LTO "Build mold with link-time optimization enabled")
|
||||
if(MOLD_LTO)
|
||||
set_property(TARGET mold PROPERTY INTERPROCEDURAL_OPTIMIZATION ON)
|
||||
endif()
|
||||
|
||||
# Handle MOLD_USE_ASAN and MOLD_USE_TSAN
|
||||
# Enable AddressSanitizer if -DMOLD_USE_ASAN=ON
|
||||
option(MOLD_USE_ASAN "Build mold with AddressSanitizer" OFF)
|
||||
if(MOLD_USE_ASAN)
|
||||
target_compile_options(mold PRIVATE -fsanitize=address -fsanitize=undefined)
|
||||
target_link_options(mold PRIVATE -fsanitize=address -fsanitize=undefined)
|
||||
endif()
|
||||
|
||||
# Enabled ThreadSanitizer if -DMOLD_USE_TSAN=ON
|
||||
option(MOLD_USE_TSAN "Build mold with ThreadSanitizer" OFF)
|
||||
if(MOLD_USE_TSAN)
|
||||
target_compile_options(mold PRIVATE -fsanitize=thread)
|
||||
target_link_options(mold PRIVATE -fsanitize=thread)
|
||||
endif()
|
||||
|
||||
# Static link libstdc++ and libcrypto if MOLD_MOSTLY_STATIC=On
|
||||
# Statically-link libstdc++ and libcrypto if -DMOLD_MOSTLY_STATIC=ON.
|
||||
#
|
||||
# This option is intended to be used by `./dist.sh` script to create a
|
||||
# mold binary that works on various Linux distros. You probably don't
|
||||
# need nor want to set this to ON.
|
||||
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and libcrypto" OFF)
|
||||
if(MOLD_MOSTLY_STATIC)
|
||||
target_link_options(mold PRIVATE -static-libstdc++)
|
||||
target_link_libraries(mold PRIVATE libcrypto.a)
|
||||
endif()
|
||||
|
||||
# Setup zlib
|
||||
# Find zlib. If libz.so is not found, we compile a bundled one and
|
||||
# statically-link it to mold.
|
||||
find_package(ZLIB QUIET)
|
||||
if(ZLIB_FOUND)
|
||||
target_link_libraries(mold PRIVATE ZLIB::ZLIB)
|
||||
@@ -62,13 +111,45 @@ else()
|
||||
target_link_libraries(mold PRIVATE zlibstatic)
|
||||
endif()
|
||||
|
||||
# Setup mimalloc
|
||||
# Find zstd compression library. Just like zlib, if libzstd.so is not
|
||||
# found, we compile a bundled one and statically-link it to mold.
|
||||
include(CheckIncludeFile)
|
||||
check_include_file(zstd.h HAVE_ZSTD_H)
|
||||
|
||||
if(HAVE_ZSTD_H)
|
||||
target_link_libraries(mold PRIVATE zstd)
|
||||
else()
|
||||
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(libzstd_static PRIVATE
|
||||
ZSTD_BUILD_STATIC=1
|
||||
ZSTD_BUILD_SHARED=0
|
||||
ZSTD_BUILD_PROGRAMS=0
|
||||
ZSTD_MULTITHREAD_SUPPORT=0
|
||||
ZSTD_BUILD_TESTS=0)
|
||||
target_include_directories(mold PUBLIC third-party/zstd/lib)
|
||||
target_link_libraries(mold PRIVATE libzstd_static)
|
||||
endif()
|
||||
|
||||
# Find mimalloc. mimalloc is an alternative malloc implementation
|
||||
# optimized for multi-threaded applications.
|
||||
#
|
||||
# If you want to use the usual libc's malloc, pass -DMOLD_USE_MIMALLOC=OFF.
|
||||
#
|
||||
# We enable mimalloc by default for 64-bit targets. It doesn't seem to
|
||||
# be stable on 32-bit targets.
|
||||
include(CheckCXXSourceCompiles)
|
||||
check_cxx_source_compiles(
|
||||
"static_assert(sizeof(void *) == 4); int main() {}" P32)
|
||||
|
||||
cmake_dependent_option(MOLD_USE_MIMALLOC "Use mimalloc" ON
|
||||
"NOT APPLE;NOT ANDROID" OFF)
|
||||
"NOT APPLE; NOT ANDROID; NOT P32" OFF)
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
|
||||
MOLD_USE_MIMALLOC OFF)
|
||||
|
||||
# By default, we build a bundled mimalloc and statically-link it to
|
||||
# mold. If you want to dynamically link to the system's
|
||||
# libmimalloc.so, pass -DMOLD_USE_SYSTEM_MIMALLOC=ON.
|
||||
if(MOLD_USE_MIMALLOC)
|
||||
if(MOLD_USE_SYSTEM_MIMALLOC)
|
||||
find_package(mimalloc REQUIRED)
|
||||
@@ -76,8 +157,8 @@ if(MOLD_USE_MIMALLOC)
|
||||
target_compile_definitions(mold PRIVATE USE_SYSTEM_MIMALLOC)
|
||||
else()
|
||||
function(mold_add_mimalloc)
|
||||
set(MI_BUILD_STATIC ON)
|
||||
option(MI_BUILD_TESTS "Build test executables" OFF)
|
||||
set(MI_BUILD_STATIC ON CACHE INTERNAL "")
|
||||
set(MI_BUILD_TESTS OFF CACHE INTERNAL "")
|
||||
add_subdirectory(third-party/mimalloc EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(mimalloc-static PRIVATE MI_USE_ENVIRON=0)
|
||||
target_link_libraries(mold PRIVATE mimalloc-static)
|
||||
@@ -87,7 +168,12 @@ if(MOLD_USE_MIMALLOC)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Setup TBB
|
||||
# Find TBB. TBB (OneTBB or Intel TBB) is a high-level threading library.
|
||||
# Use of this library is mandatory.
|
||||
#
|
||||
# By default, we build a bundled one and statically-link the library
|
||||
# to mold. If you want to link to the system's libtbb2.so, pass
|
||||
# -DMOLD_USE_SYSTEM_TBB=ON.
|
||||
option(MOLD_USE_SYSTEM_TBB "Use system or vendored TBB" OFF)
|
||||
if(MOLD_USE_SYSTEM_TBB)
|
||||
find_package(TBB REQUIRED)
|
||||
@@ -105,16 +191,12 @@ else()
|
||||
mold_add_tbb()
|
||||
endif()
|
||||
|
||||
# MOLD_X86_64_ONLY is a developer-only option. You should not use it
|
||||
# for creating an executable for production use.
|
||||
option(MOLD_X86_64_ONLY "Developer-only option" OFF)
|
||||
if(MOLD_X86_64_ONLY)
|
||||
set(CMAKE_BUILD_TYPE "Debug")
|
||||
target_compile_options(mold PRIVATE -ffunction-sections -fdata-sections
|
||||
-DMOLD_DEBUG_X86_64_ONLY)
|
||||
target_link_options(mold PRIVATE -Wl,--gc-sections)
|
||||
# Check if this is a commercial version of mold (a.k.a. "sold")
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/LICENSE.md")
|
||||
set(MOLD_IS_SOLD ON)
|
||||
endif()
|
||||
|
||||
# We always use Clang to build mold on Windows. MSVC can't compile mold.
|
||||
if(WIN32)
|
||||
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
message(FATAL_ERROR
|
||||
@@ -122,23 +204,15 @@ if(WIN32)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(mold PRIVATE NOGDI NOMINMAX)
|
||||
|
||||
target_sources(mold PRIVATE
|
||||
elf/lto-win32.cc
|
||||
macho/lto-win32.cc)
|
||||
else()
|
||||
include(CheckLibraryExists)
|
||||
check_library_exists(m pow "" LIBM_FOUND)
|
||||
if(LIBM_FOUND)
|
||||
target_link_libraries(mold PRIVATE m)
|
||||
endif()
|
||||
|
||||
target_sources(mold PRIVATE
|
||||
elf/lto-unix.cc
|
||||
elf/subprocess.cc
|
||||
macho/lto-unix.cc)
|
||||
endif()
|
||||
|
||||
# Build mold-wrapper.so
|
||||
if(NOT APPLE AND NOT WIN32)
|
||||
add_library(mold-wrapper SHARED)
|
||||
install(TARGETS mold-wrapper DESTINATION ${CMAKE_INSTALL_LIBDIR}/mold)
|
||||
@@ -154,13 +228,29 @@ if(NOT APPLE AND NOT WIN32 AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE OpenSSL::Crypto)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(riscv64|armv)")
|
||||
# If atomics doesn't work by default, add -latomic.
|
||||
# We need the flag on riscv, armv6 and m68k.
|
||||
include(CheckCXXSourceCompiles)
|
||||
check_cxx_source_compiles("#include <atomic>
|
||||
int main() {
|
||||
std::atomic_uint8_t a;
|
||||
std::atomic_uint16_t b;
|
||||
std::atomic_uint32_t c;
|
||||
std::atomic_uint64_t d;
|
||||
return ++a + ++b + ++c + ++d;
|
||||
}" HAVE_FULL_ATOMIC_SUPPORT)
|
||||
|
||||
if(NOT HAVE_FULL_ATOMIC_SUPPORT)
|
||||
target_link_libraries(mold PRIVATE atomic)
|
||||
endif()
|
||||
|
||||
set_property(SOURCE main.cc elf/lto-unix.cc macho/output-chunks.cc APPEND PROPERTY
|
||||
COMPILE_DEFINITIONS "MOLD_VERSION=\"${CMAKE_PROJECT_VERSION}\"")
|
||||
# Add -pthread
|
||||
if(NOT APPLE AND NOT WIN32)
|
||||
target_compile_options(mold PRIVATE -pthread)
|
||||
target_link_options(mold PRIVATE -pthread)
|
||||
endif()
|
||||
|
||||
# Create a .cc file containing the current git hash for `mold --version`.
|
||||
add_custom_target(git_hash
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
|
||||
@@ -172,14 +262,23 @@ add_custom_target(git_hash
|
||||
|
||||
add_dependencies(mold git_hash)
|
||||
|
||||
target_sources(mold PRIVATE
|
||||
compress.cc
|
||||
demangle.cc
|
||||
elf/arch-arm32.cc
|
||||
elf/arch-arm64.cc
|
||||
elf/arch-i386.cc
|
||||
elf/arch-riscv.cc
|
||||
elf/arch-x86-64.cc
|
||||
# Create config.h file
|
||||
configure_file(config.h.in config.h)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# Almost all functions are template in mold which take a target type
|
||||
# (e.g. X86_64) as its type parameter. Since we suport more than 10
|
||||
# targets, compiling a single source file for all the targets is very
|
||||
# slow.
|
||||
#
|
||||
# As a workaround, we create a .cc file for each target and spawn many
|
||||
# compiler instances. This is hacky but greatly reduces compile time
|
||||
# on a multicore machine.
|
||||
list(APPEND MOLD_ELF_TARGETS
|
||||
X86_64 I386 ARM64 ARM32 RV32LE RV32BE RV64LE RV64BE PPC32 PPC64V1 PPC64V2
|
||||
S390X SPARC64 M68K SH4 ALPHA HPPA32)
|
||||
|
||||
list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
elf/cmdline.cc
|
||||
elf/dwarf.cc
|
||||
elf/gc-sections.cc
|
||||
@@ -187,36 +286,109 @@ target_sources(mold PRIVATE
|
||||
elf/input-files.cc
|
||||
elf/input-sections.cc
|
||||
elf/linker-script.cc
|
||||
elf/lto.cc
|
||||
elf/main.cc
|
||||
elf/mapfile.cc
|
||||
elf/output-chunks.cc
|
||||
elf/passes.cc
|
||||
elf/relocatable.cc
|
||||
elf/subprocess.cc
|
||||
elf/thunks.cc
|
||||
filepath.cc
|
||||
git-hash.cc
|
||||
glob.cc
|
||||
hyperloglog.cc
|
||||
macho/arch-arm64.cc
|
||||
macho/arch-x86-64.cc
|
||||
)
|
||||
|
||||
list(APPEND MOLD_MACHO_TARGETS X86_64 ARM64)
|
||||
|
||||
list(APPEND MOLD_MACHO_TEMPLATE_FILES
|
||||
macho/cmdline.cc
|
||||
macho/dead-strip.cc
|
||||
macho/input-files.cc
|
||||
macho/input-sections.cc
|
||||
macho/lto.cc
|
||||
macho/main.cc
|
||||
macho/mapfile.cc
|
||||
macho/output-chunks.cc
|
||||
macho/tapi.cc
|
||||
macho/yaml.cc
|
||||
)
|
||||
|
||||
function(mold_instantiate_templates SOURCE TARGET)
|
||||
set(PATH ${CMAKE_BINARY_DIR}/${SOURCE}.${TARGET}.cc)
|
||||
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
|
||||
#define MOLD_TARGET ${TARGET}
|
||||
#include \"${CMAKE_SOURCE_DIR}/${SOURCE}\"
|
||||
")
|
||||
target_sources(mold PRIVATE ${PATH})
|
||||
endfunction()
|
||||
|
||||
foreach (SOURCE IN LISTS MOLD_ELF_TEMPLATE_FILES)
|
||||
foreach(TARGET IN LISTS MOLD_ELF_TARGETS)
|
||||
mold_instantiate_templates(${SOURCE} ${TARGET})
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
if(MOLD_IS_SOLD)
|
||||
foreach (SOURCE IN LISTS MOLD_MACHO_TEMPLATE_FILES)
|
||||
foreach(TARGET IN LISTS MOLD_MACHO_TARGETS)
|
||||
mold_instantiate_templates(${SOURCE} ${TARGET})
|
||||
endforeach()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Add other non-template source files.
|
||||
target_sources(mold PRIVATE
|
||||
compress.cc
|
||||
demangle.cc
|
||||
elf/arch-alpha.cc
|
||||
elf/arch-arm32.cc
|
||||
elf/arch-arm64.cc
|
||||
elf/arch-hppa32.cc
|
||||
elf/arch-i386.cc
|
||||
elf/arch-m68k.cc
|
||||
elf/arch-ppc32.cc
|
||||
elf/arch-ppc64v1.cc
|
||||
elf/arch-ppc64v2.cc
|
||||
elf/arch-riscv.cc
|
||||
elf/arch-s390x.cc
|
||||
elf/arch-sh4.cc
|
||||
elf/arch-sparc64.cc
|
||||
elf/arch-x86-64.cc
|
||||
elf/elf.cc
|
||||
filepath.cc
|
||||
git-hash.cc
|
||||
glob.cc
|
||||
hyperloglog.cc
|
||||
main.cc
|
||||
multi-glob.cc
|
||||
perf.cc
|
||||
strerror.cc
|
||||
tar.cc
|
||||
third-party/rust-demangle/rust-demangle.c
|
||||
uuid.cc
|
||||
)
|
||||
|
||||
if(MOLD_IS_SOLD)
|
||||
target_sources(mold PRIVATE
|
||||
macho/arch-arm64.cc
|
||||
macho/arch-x86-64.cc
|
||||
macho/yaml.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
# Add frequently included header files for pre-compiling.
|
||||
# target_precompile_headers is supported by CMake 3.16.0 or newer.
|
||||
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
|
||||
if(MOLD_IS_SOLD)
|
||||
target_precompile_headers(mold PRIVATE
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/elf/mold.h>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/macho/mold.h>")
|
||||
else()
|
||||
target_precompile_headers(mold PRIVATE
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/elf/mold.h>")
|
||||
endif()
|
||||
|
||||
# ccache needs this flag along with `sloppiness = pch_defines,time_macros`
|
||||
# to enable caching
|
||||
target_compile_options(mold PRIVATE -fpch-preprocess)
|
||||
endif()
|
||||
|
||||
include(CTest)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
@@ -225,35 +397,59 @@ if(BUILD_TESTING)
|
||||
add_custom_command(
|
||||
TARGET mold POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld64
|
||||
BYPRODUCTS ld ld64
|
||||
BYPRODUCTS ld
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
VERBATIM)
|
||||
|
||||
if(MOLD_IS_SOLD)
|
||||
add_custom_command(
|
||||
TARGET mold POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld64
|
||||
BYPRODUCTS ld64
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
VERBATIM)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(${APPLE})
|
||||
add_subdirectory(test/macho)
|
||||
if(MOLD_IS_SOLD)
|
||||
add_subdirectory(test/macho)
|
||||
endif()
|
||||
elseif(${UNIX})
|
||||
add_subdirectory(test/elf)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
install(TARGETS mold)
|
||||
install(TARGETS mold RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
install(FILES docs/mold.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)
|
||||
install(CODE "
|
||||
set(DEST \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}\")
|
||||
file(RELATIVE_PATH RELPATH
|
||||
/${CMAKE_INSTALL_LIBEXECDIR}/mold /${CMAKE_INSTALL_BINDIR}/mold)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||
\${DEST}/${CMAKE_INSTALL_LIBEXECDIR}/mold)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink \${RELPATH}
|
||||
\${DEST}/${CMAKE_INSTALL_LIBEXECDIR}/mold/ld)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink mold
|
||||
\${DEST}/${CMAKE_INSTALL_BINDIR}/ld.mold)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink mold
|
||||
\${DEST}/${CMAKE_INSTALL_BINDIR}/ld64.mold)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink mold.1
|
||||
\${DEST}/${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)")
|
||||
|
||||
function(mold_install_relative_symlink OLD NEW)
|
||||
install(CODE "
|
||||
get_filename_component(PREFIX_ABS \${CMAKE_INSTALL_PREFIX}/ ABSOLUTE)
|
||||
get_filename_component(OLD_ABS ${OLD} ABSOLUTE BASE_DIR \${PREFIX_ABS})
|
||||
get_filename_component(NEW_ABS ${NEW} ABSOLUTE BASE_DIR \${PREFIX_ABS})
|
||||
get_filename_component(NEW_DIR \${NEW_ABS} DIRECTORY)
|
||||
file(RELATIVE_PATH OLD_REL \${NEW_DIR} \${OLD_ABS})
|
||||
message(STATUS \"Installing symlink: \$ENV{DESTDIR}\${NEW_ABS} -> \${OLD_REL}\")
|
||||
file(MAKE_DIRECTORY \$ENV{DESTDIR}\${NEW_DIR})
|
||||
file(CREATE_LINK \${OLD_REL} \$ENV{DESTDIR}\${NEW_ABS} SYMBOLIC)")
|
||||
endfunction()
|
||||
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_BINDIR}/ld.mold)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
|
||||
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
|
||||
|
||||
if(MOLD_IS_SOLD)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_BINDIR}/ld64.mold)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_BINDIR}/ld.sold)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_BINDIR}/ld64.sold)
|
||||
endif()
|
||||
endif()
|
||||
+7
-21
@@ -1,30 +1,16 @@
|
||||
# Contributing to mold
|
||||
|
||||
Summary: When you send a patch that is longer than 10 lines, you must
|
||||
agree that the patch is released under the dual license of the GNU
|
||||
AGPLv3 and the MIT license. As an indication of agreement, please add
|
||||
a `Signed-off-by` line to your commit message using `git commit -s`.
|
||||
|
||||
Here is the long official version:
|
||||
|
||||
mold is currently licensed under the GNU AGPLv3, but I want to relicense
|
||||
it under a more liberal license in the future. To make this possible,
|
||||
I want all patches to be submitted under the dual license of the GNU
|
||||
AGPLv3 and the MIT license (the "Dual License") from the beginning.
|
||||
|
||||
To make it clear that contributors have agreed to release their
|
||||
patches under the Dual License, I introduced a sign-off procedure just
|
||||
like the Linux kernel project did. The sign-off is a simple line at
|
||||
the end of the git commit message, which certifies that you wrote it
|
||||
or otherwise have the right to pass it on as an open-source patch. The
|
||||
rules are pretty simple: if you can certify the below:
|
||||
We request all patches to mold to be released under the dual license
|
||||
of the GNU AGPLv3 and the MIT license. Please read the following and
|
||||
sign-off your patch as an indication of agreement.
|
||||
|
||||
> ## Developer's Certificate of Origin
|
||||
>
|
||||
> By making a contribution to this project, I certify that:
|
||||
>
|
||||
> 1. The contribution was created in whole or in part by me and I
|
||||
> have the right to submit it under the Dual License; or
|
||||
> have the right to submit it under the Dual License of the GNU
|
||||
> AGPLv3 and the MIT license; or
|
||||
>
|
||||
> 2. The contribution is based upon previous work that, to the best
|
||||
> of my knowledge, is covered under the the Dual License and I
|
||||
@@ -42,7 +28,7 @@ rules are pretty simple: if you can certify the below:
|
||||
> maintained indefinitely and may be redistributed consistent with
|
||||
> this project or the open source license(s) involved.
|
||||
|
||||
then you just add a line saying:
|
||||
If you agree, you just add a line saying:
|
||||
|
||||
```
|
||||
Signed-off-by: Random J Developer <[email protected]>
|
||||
@@ -55,4 +41,4 @@ for you.
|
||||
|
||||
We assume that any contribution that's 10 lines or less does not meet
|
||||
the threshold of originality and therefore copyright does not apply.
|
||||
You don't need to add a "Signed-off-by" line to such small commits.
|
||||
You don't need to add a "Signed-off-by" line to such small patches.
|
||||
+12
-10
@@ -1,7 +1,7 @@
|
||||
# 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)
|
||||
# $ docker buildx create --use
|
||||
# $ docker buildx build --platform x86_64,aarch64,arm,ppc64le,s390x -t rui314/mold-builder:latest --push .
|
||||
|
||||
FROM ubuntu:18.04
|
||||
ENV TZ=Europe/London
|
||||
@@ -9,13 +9,15 @@ RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends software-properties-common && \
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential wget \
|
||||
libstdc++-11-dev zlib1g-dev gcc-10 g++-10 python3 && \
|
||||
cd / && \
|
||||
wget -O- -q https://www.openssl.org/source/openssl-3.0.5.tar.gz | tar xzf - && \
|
||||
mv openssl-3.0.5 openssl && \
|
||||
cd openssl && \
|
||||
./Configure --prefix=/usr/local && \
|
||||
apt-get install -y --no-install-recommends build-essential wget libstdc++-11-dev zlib1g-dev gcc-10 g++-10 python3 && \
|
||||
\
|
||||
mkdir /openssl && cd /openssl && \
|
||||
wget -O- -q https://www.openssl.org/source/openssl-3.0.7.tar.gz | tar --strip-components=1 -xzf - && \
|
||||
./Configure --prefix=/usr/local --libdir=lib && \
|
||||
make -j$(nproc) && \
|
||||
make -j$(nproc) install && \
|
||||
wget -O- -q https://github.com/Kitware/CMake/releases/download/v3.24.0/cmake-3.24.0-linux-$(uname -m).tar.gz | tar -C /usr/local --strip-components=1 -xzf -
|
||||
ldconfig && \
|
||||
mkdir /cmake && cd /cmake && \
|
||||
wget -O- -q https://github.com/Kitware/CMake/releases/download/v3.24.2/cmake-3.24.2.tar.gz | tar --strip-components=1 -xzf - && \
|
||||
./bootstrap --parallel=$(nproc) && make -j$(nproc) && make -j$(nproc) install && \
|
||||
rm -rf /var/lib/apt/lists/* /cmake /openssl
|
||||
@@ -989,3 +989,37 @@ For the third-party/zlib component:
|
||||
|
||||
Jean-loup Gailly Mark Adler
|
||||
[email protected] [email protected]
|
||||
|
||||
|
||||
For the third-party/zstd component:
|
||||
|
||||
BSD License
|
||||
|
||||
For Zstandard software
|
||||
|
||||
Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name Facebook nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,253 +0,0 @@
|
||||
VERSION = 1.4.2
|
||||
|
||||
PREFIX = /usr/local
|
||||
BINDIR = $(PREFIX)/bin
|
||||
LIBDIR = $(PREFIX)/lib
|
||||
LIBEXECDIR = $(PREFIX)/libexec
|
||||
MANDIR = $(PREFIX)/share/man
|
||||
DOCDIR = $(PREFIX)/share/doc
|
||||
|
||||
INSTALL = install
|
||||
INSTALL_PROGRAM = $(INSTALL)
|
||||
INSTALL_DATA = $(INSTALL) -m 644
|
||||
|
||||
OS := $(shell uname -s)
|
||||
ARCH := $(shell uname -m)
|
||||
|
||||
ifeq ($(OS), Darwin)
|
||||
TESTS := $(wildcard test/macho/*.sh)
|
||||
else
|
||||
TESTS := $(wildcard test/elf/*.sh)
|
||||
endif
|
||||
|
||||
D = $(DESTDIR)
|
||||
|
||||
# CXX defaults to `g++`. Rewrite it with a vendor-neutral compiler
|
||||
# name `c++`.
|
||||
ifeq ($(origin CXX), default)
|
||||
CXX = c++
|
||||
endif
|
||||
|
||||
# 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) out/rust-demangle.o out/git-hash.o
|
||||
|
||||
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 \
|
||||
-DMOLD_VERSION=\"$(VERSION)\" -DLIBDIR="\"$(LIBDIR)\""
|
||||
|
||||
ifeq ($(OS), OpenBSD)
|
||||
MOLD_LDFLAGS := -pthread -lz -lm
|
||||
else
|
||||
MOLD_LDFLAGS := -pthread -lz -lm -ldl
|
||||
endif
|
||||
|
||||
LTO = 0
|
||||
ifeq ($(LTO), 1)
|
||||
CXXFLAGS += -flto -O3
|
||||
LDFLAGS += -flto
|
||||
endif
|
||||
|
||||
# By default, we want to use mimalloc as a memory allocator. mimalloc
|
||||
# is disabled on macOS and Android because it didn't work on those hosts.
|
||||
USE_MIMALLOC = 1
|
||||
ifeq ($(OS), Darwin)
|
||||
USE_MIMALLOC = 0
|
||||
else ifeq ($(IS_ANDROID), 1)
|
||||
USE_MIMALLOC = 0
|
||||
endif
|
||||
|
||||
ifeq ($(USE_MIMALLOC), 1)
|
||||
ifdef SYSTEM_MIMALLOC
|
||||
MOLD_CXXFLAGS += -DUSE_SYSTEM_MIMALLOC
|
||||
MOLD_LDFLAGS += -lmimalloc
|
||||
else
|
||||
MIMALLOC_LIB = out/mimalloc/libmimalloc.a
|
||||
MOLD_CXXFLAGS += -Ithird-party/mimalloc/include
|
||||
MOLD_LDFLAGS += -Wl,-whole-archive $(MIMALLOC_LIB) -Wl,-no-whole-archive
|
||||
endif
|
||||
endif
|
||||
|
||||
# Note: Do NOT specify `SYSTEM_TBB=1` unless your system-wide OneTBB
|
||||
# library is compiled with https://github.com/oneapi-src/oneTBB/pull/824.
|
||||
# mold with an unpatched OneTBB is unstable when doing LTO if the system
|
||||
# is under high load.
|
||||
ifdef SYSTEM_TBB
|
||||
MOLD_LDFLAGS += -ltbb
|
||||
else
|
||||
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 = 1
|
||||
ifeq ($(OS), Darwin)
|
||||
NEEDS_LIBCRYPTO = 0
|
||||
endif
|
||||
|
||||
ifeq ($(NEEDS_LIBCRYPTO), 1)
|
||||
MOLD_LDFLAGS += -lcrypto
|
||||
endif
|
||||
|
||||
# '-latomic' flag is needed building on armv6/riscv64 systems.
|
||||
# Seems like '-atomic' would be better but not working.
|
||||
ifneq (,$(filter armv6% riscv64, $(ARCH)))
|
||||
MOLD_LDFLAGS += -latomic
|
||||
endif
|
||||
|
||||
# -Wc++11-narrowing is a fatal error on Android, so disable it.
|
||||
ifeq ($(IS_ANDROID), 1)
|
||||
MOLD_CXXFLAGS += -Wno-c++11-narrowing
|
||||
endif
|
||||
|
||||
ifeq ($(OS), Linux)
|
||||
MOLD_WRAPPER_LDFLAGS = -Wl,-push-state -Wl,-no-as-needed -ldl -Wl,-pop-state
|
||||
endif
|
||||
|
||||
DEPFLAGS = -MT $@ -MMD -MP -MF out/$*.d
|
||||
|
||||
all: mold mold-wrapper.so
|
||||
|
||||
-include $(SRCS:%.cc=out/%.d)
|
||||
|
||||
out/git-hash.cc: FORCE
|
||||
cmake -DSOURCE_DIR=. -DOUTPUT_FILE=out/git-hash.cc -P update-git-hash.cmake
|
||||
|
||||
FORCE:
|
||||
|
||||
out/git-hash.o: out/git-hash.cc
|
||||
$(CXX) $(MOLD_CXXFLAGS) $(CXXFLAGS) -c -o $@ $<
|
||||
|
||||
mold: $(OBJS) $(MIMALLOC_LIB) $(TBB_LIB)
|
||||
$(CXX) $(OBJS) -o $@ $(MOLD_LDFLAGS) $(LDFLAGS)
|
||||
ln -sf mold ld
|
||||
ln -sf mold ld64
|
||||
|
||||
mold-wrapper.so: elf/mold-wrapper.c
|
||||
$(CC) $(DEPFLAGS) $(CFLAGS) -fPIC -shared -o $@ $< $(MOLD_WRAPPER_LDFLAGS) $(LDFLAGS)
|
||||
|
||||
out/rust-demangle.o: third-party/rust-demangle/rust-demangle.c
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
out/%.o: %.cc out/elf/.keep out/macho/.keep
|
||||
$(CXX) $(MOLD_CXXFLAGS) $(DEPFLAGS) $(CXXFLAGS) -c -o $@ $<
|
||||
|
||||
out/elf/.keep out/macho/.keep:
|
||||
mkdir -p $(@D)
|
||||
touch $@
|
||||
|
||||
$(MIMALLOC_LIB):
|
||||
mkdir -p out/mimalloc
|
||||
(cd out/mimalloc; CFLAGS=-DMI_USE_ENVIRON=0 cmake -G'Unix Makefiles' ../../third-party/mimalloc)
|
||||
$(MAKE) -C out/mimalloc mimalloc-static
|
||||
|
||||
$(TBB_LIB):
|
||||
mkdir -p out/tbb
|
||||
(cd out/tbb; cmake -G'Unix Makefiles' -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=OFF -DTBB_TEST=OFF -DCMAKE_CXX_FLAGS="$(CXXFLAGS) -D__TBB_DYNAMIC_LOAD_ENABLED=0" -DTBB_STRICT=OFF ../../third-party/tbb)
|
||||
$(MAKE) -C out/tbb tbb
|
||||
(cd out/tbb; ln -sf *_relwithdebinfo libs)
|
||||
|
||||
test tests check: all
|
||||
ifeq ($(OS), Darwin)
|
||||
@$(MAKE) $(TESTS) --no-print-directory
|
||||
else
|
||||
@$(MAKE) $(TESTS) --no-print-directory --output-sync
|
||||
endif
|
||||
|
||||
@if test -t 1; then \
|
||||
printf '\e[32mPassed all tests\e[0m\n'; \
|
||||
else \
|
||||
echo 'Passed all tests'; \
|
||||
fi
|
||||
|
||||
test-arch:
|
||||
TEST_CC=${TRIPLE}-gcc \
|
||||
TEST_CXX=${TRIPLE}-g++ \
|
||||
TEST_GCC=${TRIPLE}-gcc \
|
||||
TEST_GXX=${TRIPLE}-g++ \
|
||||
OBJDUMP=${TRIPLE}-objdump \
|
||||
MACHINE=${MACHINE} \
|
||||
QEMU="qemu-${MACHINE} -L /usr/${TRIPLE}" \
|
||||
$(MAKE) test
|
||||
|
||||
test-all: all
|
||||
$(MAKE) test-arch TRIPLE=x86_64-linux-gnu MACHINE=x86_64
|
||||
$(MAKE) test-arch TRIPLE=i686-linux-gnu MACHINE=i386
|
||||
$(MAKE) test-arch TRIPLE=aarch64-linux-gnu MACHINE=aarch64
|
||||
$(MAKE) test-arch TRIPLE=arm-linux-gnueabihf MACHINE=arm
|
||||
$(MAKE) test-arch TRIPLE=riscv64-linux-gnu MACHINE=riscv64
|
||||
|
||||
# macOS's GNU make hasn't been updated since 3.8.1 perhaps due a concern
|
||||
# of GPLv3. The --output-sync flag was introduced in GNU Make 4.0, so we
|
||||
# can't use that flag on macOS.
|
||||
#
|
||||
# `tail -r | tail -r` is a poor-man's way to enable full buffering on a
|
||||
# command output. `tail -r` outputs an input from the last line to the
|
||||
# first.
|
||||
$(TESTS):
|
||||
ifeq ($(OS), Darwin)
|
||||
@set -o pipefail; ./$@ 2>&1 | tail -r | tail -r
|
||||
else
|
||||
@./$@
|
||||
endif
|
||||
|
||||
install: all
|
||||
$(INSTALL) -d $D$(BINDIR)
|
||||
$(INSTALL_PROGRAM) mold $D$(BINDIR)
|
||||
$(STRIP) $D$(BINDIR)/mold
|
||||
|
||||
$(INSTALL) -d $D$(LIBDIR)/mold
|
||||
|
||||
ifneq ($(OS), Darwin)
|
||||
$(INSTALL_DATA) mold-wrapper.so $D$(LIBDIR)/mold
|
||||
$(STRIP) $D$(LIBDIR)/mold/mold-wrapper.so
|
||||
endif
|
||||
|
||||
$(INSTALL) -d $D$(LIBEXECDIR)/mold
|
||||
cmake -DSOURCE=$D$(BINDIR)/mold -DDEST=$D$(LIBEXECDIR)/mold/ld -P create-symlink.cmake
|
||||
|
||||
$(INSTALL) -d $D$(MANDIR)/man1
|
||||
$(INSTALL_DATA) docs/mold.1 $D$(MANDIR)/man1
|
||||
ln -sf mold.1 $D$(MANDIR)/man1/ld.mold.1
|
||||
|
||||
$(INSTALL) -d $D$(DOCDIR)/mold
|
||||
$(INSTALL_DATA) LICENSE $D$(DOCDIR)/mold/
|
||||
|
||||
ln -sf mold $D$(BINDIR)/ld.mold
|
||||
ln -sf mold $D$(BINDIR)/ld64.mold
|
||||
|
||||
uninstall:
|
||||
rm -f $D$(BINDIR)/mold $D$(BINDIR)/ld.mold $D$(BINDIR)/ld64.mold
|
||||
rm -f $D$(MANDIR)/man1/mold.1
|
||||
rm -rf $D$(LIBDIR)/mold
|
||||
|
||||
test-asan test-ubsan:
|
||||
$(MAKE) USE_MIMALLOC=0 CXXFLAGS='-fsanitize=address -fsanitize=undefined -O0 -g' LDFLAGS='-fsanitize=address -fsanitize=undefined' test
|
||||
|
||||
test-tsan:
|
||||
$(MAKE) USE_MIMALLOC=0 CXXFLAGS='-fsanitize=thread -O0 -g' LDFLAGS=-fsanitize=thread test
|
||||
|
||||
clean:
|
||||
rm -rf *~ mold mold-wrapper.so out ld ld64 mold-*-linux.tar.gz
|
||||
|
||||
.PHONY: all test tests check clean test-arch test-all test-asan test-ubsan test-tsan $(TESTS)
|
||||
@@ -1,5 +1,13 @@
|
||||
# mold: A Modern Linker
|
||||
|
||||
[](https://github.com/rui314/mold/actions/workflows/ci.yml)
|
||||
[](https://build.opensuse.org/package/show/home:marxin:mold/mold)
|
||||
|
||||
<i>This is a repo of a free, AGPL-licensed version of the linker.
|
||||
If you are looking for a commercial, non-AGPL version of the same linker,
|
||||
please visit the
|
||||
[repo of the sold linker](https://github.com/bluewhalesystems/sold).</i>
|
||||
|
||||
mold is a faster drop-in replacement for existing Unix linkers.
|
||||
It is several times faster than the LLVM lld linker, the second-fastest
|
||||
open-source linker which I originally created a few years ago.
|
||||
@@ -22,7 +30,12 @@ mold is so fast that it is only 2x _slower_ than `cp` on the same
|
||||
machine. Feel free to [file a bug](https://github.com/rui314/mold/issues)
|
||||
if you find mold is not faster than other linkers.
|
||||
|
||||
mold currently supports x86-64, i386, ARM32, ARM64 and 64-bit RISC-V.
|
||||
mold supports x86-64, i386, ARM64, ARM32, 64-bit/32-bit little/big-endian
|
||||
RISC-V, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian PowerPC ELFv2,
|
||||
s390x, SPARC64 and m68k.
|
||||
|
||||
mold/macOS is commercial software. For mold/macOS, please visit
|
||||
https://github.com/bluewhalesystems/sold.
|
||||
|
||||
## Why does the speed of linking matter?
|
||||
|
||||
@@ -60,25 +73,30 @@ necessary packages. You may want to run it as root.
|
||||
|
||||
```shell
|
||||
git clone https://github.com/rui314/mold.git
|
||||
cd mold
|
||||
git checkout v1.4.2
|
||||
make -j$(nproc) CXX=clang++
|
||||
sudo make install
|
||||
mkdir mold/build
|
||||
cd mold/build
|
||||
git checkout v1.8.0
|
||||
../install-build-deps.sh
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ ..
|
||||
cmake --build . -j $(nproc)
|
||||
sudo cmake --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,
|
||||
You may need to pass a C++20 compiler command name to `cmake`.
|
||||
In the above case, `c++` is passed. If it doesn't work for you,
|
||||
try a specific version of a compiler such as `g++-10` or `clang++-12`.
|
||||
|
||||
By default, `mold` is installed to `/usr/local/bin`.
|
||||
By default, `mold` is installed to `/usr/local/bin`. You can change
|
||||
that by passing `-DCMAKE_INSTALL_PREFIX=<directory>`. For other cmake
|
||||
options, see the comments in `CMakeLists.txt`.
|
||||
|
||||
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
|
||||
`cmake` in the above commands doesn't work for you, you can use Docker to
|
||||
build it in a Docker environment. To do so, just run `./dist.sh` in this
|
||||
directory instead of running `make -j$(nproc)`. The shell script pulls a
|
||||
Docker image, builds mold and auxiliary files inside it, and packs
|
||||
them into a single tar file `mold-$version-$arch-linux.tar.gz`.
|
||||
You can extract the tar file anywhere and use `mold` executable in it.
|
||||
directory instead of `cmake`. The shell script pulls a Docker image,
|
||||
builds mold and auxiliary files inside it, and packs them into a
|
||||
single tar file `mold-$version-$arch-linux.tar.gz`. You can extract
|
||||
the tar file anywhere and use `mold` executable in it.
|
||||
|
||||
## How to use
|
||||
|
||||
@@ -107,7 +125,7 @@ following flags to use `mold` instead of `/usr/bin/ld`:
|
||||
`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
|
||||
If you haven't installed `ld.mold` to any `$PATH`, you can still pass
|
||||
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. GCC does not
|
||||
take an absolute path as an argument for `-fuse-ld` though.
|
||||
|
||||
@@ -123,8 +141,15 @@ linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
|
||||
```
|
||||
|
||||
where `/path/to/mold` is an absolute path to `mold` exectuable.
|
||||
Please make sure you have installed `clang`.
|
||||
where `/path/to/mold` is an absolute path to `mold` exectuable. In the
|
||||
above example, we use `clang` as a linker driver as it can always take
|
||||
the `-fuse-ld` option. If your GCC is recent enough to recognize the
|
||||
option, you may be able to remove the `linker = "clang"` line.
|
||||
|
||||
```toml
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
|
||||
```
|
||||
|
||||
If you want to use mold for all projects, put the above snippet to
|
||||
`~/.cargo/config.toml`.
|
||||
@@ -144,6 +169,30 @@ rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>If you are using Nim</summary>
|
||||
|
||||
Create `config.nims` in your project directory with the following:
|
||||
|
||||
```nim
|
||||
when findExe("mold").len > 0 and defined(linux):
|
||||
switch("passL", "-fuse-ld=mold")
|
||||
```
|
||||
|
||||
where `mold` must be included in the PATH environment variable. In this example
|
||||
The above example uses `gcc` as the linker driver.
|
||||
Use the `fuse-ld` option. If your GCC is recent enough to recognize this option.
|
||||
|
||||
If you want to use mold for all projects, put the above snippet to `~/.config/config.nims`.
|
||||
|
||||
If you are using macOS, you can modify config.nims in a similar manner. Here is an example with mold installed via Homebrew.
|
||||
|
||||
```nim
|
||||
when findExe("ld64.mold").len > 0 and defined(macosx):
|
||||
switch("passL", "-fuse-ld=ld64.mold")
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>mold -run</summary>
|
||||
|
||||
It is sometimes very hard to pass an appropriate command line option
|
||||
@@ -251,7 +300,7 @@ concern. By purchasing a license, you are guaranteed that mold will be
|
||||
maintained for you. Please [contact us](mailto:[email protected])
|
||||
for a commercial license inquiry.
|
||||
|
||||
## Acknowledgement
|
||||
## Sponsors
|
||||
|
||||
We accept donations via [GitHub Sponsors](https://github.com/sponsors/rui314)
|
||||
and [OpenCollective](https://opencollective.com/mold-linker).
|
||||
@@ -259,7 +308,16 @@ We thank you to everybody who sponsors our project. In particular,
|
||||
we'd like to acknowledge the following people and organizations who
|
||||
have sponsored $128/month or more:
|
||||
|
||||
- [300baud](https://github.com/300baud)
|
||||
- [Mercury](https://github.com/MercuryTechnologies)
|
||||
- [Wei Wu](https://github.com/lazyparser)
|
||||
### Corporate sponsors
|
||||
|
||||
<a href="https://mercury.com/"><img src="docs/mercury-logo.png" align=center height=120 width=400 alt=Mercury></a>
|
||||
|
||||
- [Uber](https://uber.com)
|
||||
- [Signal Slot Inc.](https://github.com/signal-slot)
|
||||
|
||||
### Individual sponsors
|
||||
|
||||
- [300baud](https://github.com/300baud)
|
||||
- [Johan Andersson](https://github.com/repi)
|
||||
- [Wei Wu](https://github.com/lazyparser)
|
||||
- [kyle-elliott](https://github.com/kyle-elliott)
|
||||
+2
-2
@@ -20,7 +20,7 @@
|
||||
// that provides functions and variables used in your program get
|
||||
// linked. To make this efficient, static library functions are
|
||||
// usually separated to each object file in an archive file. You can
|
||||
// see the contents of libc.a by runnning `ar t
|
||||
// see the contents of libc.a by running `ar t
|
||||
// /usr/lib/x86_64-linux-gnu/libc.a`.
|
||||
|
||||
#pragma once
|
||||
@@ -166,7 +166,7 @@ read_fat_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
template <typename C>
|
||||
std::vector<MappedFile<C> *>
|
||||
read_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
switch (get_file_type(mf)) {
|
||||
switch (get_file_type(mf, !ctx.arg.plugin.empty())) {
|
||||
case FileType::AR:
|
||||
return read_fat_archive_members(ctx, mf);
|
||||
case FileType::THIN_AR:
|
||||
|
||||
@@ -29,8 +29,22 @@ read_response_file(C &ctx, std::string_view path) {
|
||||
|
||||
auto read_unquoted = [&](i64 i) {
|
||||
std::string buf;
|
||||
while (i < mf->size && !isspace(data[i]))
|
||||
buf.append(1, data[i++]);
|
||||
|
||||
while (i < mf->size) {
|
||||
if (data[i] == '\\' && i + 1 < mf->size) {
|
||||
buf.append(1, data[i + 1]);
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isspace(data[i])) {
|
||||
buf.append(1, data[i++]);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i;
|
||||
};
|
||||
|
||||
+42
-50
@@ -1,11 +1,13 @@
|
||||
// This file implements a multi-threaded zlib compression routine.
|
||||
// This file implements a multi-threaded zlib and zstd compression
|
||||
// routine.
|
||||
//
|
||||
// Multiple pieces of raw compressed data in zlib-format can be merged
|
||||
// just by concatenation as long as each zlib stream is flushed with
|
||||
// Z_SYNC_FLUSH. In this file, we split input data into multiple
|
||||
// shards, compress them individually and concatenate them. We then
|
||||
// append a header, a trailer and a checksum so that the concatenated
|
||||
// data is valid zlib-format data.
|
||||
// zlib-compressed data can be merged just by concatenation as long as
|
||||
// each piece of data is flushed with Z_SYNC_FLUSH. In this file, we
|
||||
// split input data into multiple shards, compress them individually
|
||||
// and concatenate them. We then append a header, a trailer and a
|
||||
// checksum so that the concatenated data is valid zlib-format data.
|
||||
//
|
||||
// zstd-compressed data can be merged in the same way.
|
||||
//
|
||||
// Using threads to compress data has a downside. Since the dictionary
|
||||
// is reset on boundaries of shards, compression ratio is sacrificed
|
||||
@@ -16,8 +18,13 @@
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
#define CHECK(fn) do { int r = (fn); assert(r == Z_OK); } while (0)
|
||||
#define CHECK(fn) \
|
||||
do { \
|
||||
[[maybe_unused]] int r = (fn); \
|
||||
assert(r == Z_OK); \
|
||||
} while (0)
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -35,7 +42,7 @@ static std::vector<std::string_view> split(std::string_view input) {
|
||||
return shards;
|
||||
}
|
||||
|
||||
static std::vector<u8> do_compress(std::string_view input) {
|
||||
static std::vector<u8> zlib_compress(std::string_view input) {
|
||||
// Initialize zlib stream. Since debug info is generally compressed
|
||||
// pretty well with lower compression levels, we chose compression
|
||||
// level 1.
|
||||
@@ -85,6 +92,7 @@ static std::vector<u8> do_compress(std::string_view input) {
|
||||
|
||||
assert(strm.avail_out > 0);
|
||||
buf.resize(buf.size() - strm.avail_out);
|
||||
buf.shrink_to_fit();
|
||||
deflateEnd(&strm);
|
||||
return buf;
|
||||
}
|
||||
@@ -98,20 +106,18 @@ ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
adlers[i] = adler32(1, (u8 *)inputs[i].data(), inputs[i].size());
|
||||
shards[i] = do_compress(inputs[i]);
|
||||
shards[i] = zlib_compress(inputs[i]);
|
||||
});
|
||||
|
||||
// Combine checksums
|
||||
checksum = adlers[0];
|
||||
for (i64 i = 1; i < inputs.size(); i++)
|
||||
checksum = adler32_combine(checksum, adlers[i], inputs[i].size());
|
||||
}
|
||||
|
||||
i64 ZlibCompressor::size() const {
|
||||
i64 size = 2; // +2 for header
|
||||
for (const std::vector<u8> &shard : shards)
|
||||
size += shard.size();
|
||||
return size + 6; // +6 for trailer and checksum
|
||||
// Comput the total size
|
||||
compressed_size = 8; // the header and the trailer
|
||||
for (std::vector<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
void ZlibCompressor::write_to(u8 *buf) {
|
||||
@@ -130,7 +136,7 @@ void ZlibCompressor::write_to(u8 *buf) {
|
||||
});
|
||||
|
||||
// Write a trailer
|
||||
u8 *end = buf + size();
|
||||
u8 *end = buf + compressed_size;
|
||||
end[-6] = 3;
|
||||
end[-5] = 0;
|
||||
|
||||
@@ -138,56 +144,42 @@ void ZlibCompressor::write_to(u8 *buf) {
|
||||
*(ub32 *)(end - 4) = checksum;
|
||||
}
|
||||
|
||||
GzipCompressor::GzipCompressor(std::string_view input) {
|
||||
static std::vector<u8> zstd_compress(std::string_view input) {
|
||||
std::vector<u8> buf(ZSTD_COMPRESSBOUND(input.size()));
|
||||
constexpr int level = 3; // compression level; must be between 1 to 22
|
||||
|
||||
size_t sz = ZSTD_compress(buf.data(), buf.size(), input.data(), input.size(),
|
||||
level);
|
||||
assert(!ZSTD_isError(sz));
|
||||
buf.resize(sz);
|
||||
buf.shrink_to_fit();
|
||||
return buf;
|
||||
}
|
||||
|
||||
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size) {
|
||||
std::string_view input{(char *)buf, (size_t)size};
|
||||
std::vector<std::string_view> inputs = split(input);
|
||||
std::vector<u32> crc(inputs.size());
|
||||
shards.resize(inputs.size());
|
||||
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
crc[i] = crc32(0, (u8 *)inputs[i].data(), inputs[i].size());
|
||||
shards[i] = do_compress(inputs[i]);
|
||||
shards[i] = zstd_compress(inputs[i]);
|
||||
});
|
||||
|
||||
// Combine checksums
|
||||
checksum = crc[0];
|
||||
for (i64 i = 1; i < inputs.size(); i++)
|
||||
checksum = crc32_combine(checksum, crc[i], inputs[i].size());
|
||||
|
||||
uncompressed_size = input.size();
|
||||
compressed_size = 0;
|
||||
for (std::vector<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
i64 GzipCompressor::size() const {
|
||||
i64 size = 10; // +10 for header
|
||||
for (const std::vector<u8> &shard : shards)
|
||||
size += shard.size();
|
||||
return size + 10; // +10 for trailer and checksum
|
||||
}
|
||||
|
||||
void GzipCompressor::write_to(u8 *buf) {
|
||||
// Write a zlib-format header
|
||||
memset(buf, 0, 10);
|
||||
buf[0] = 0x1f; // magic
|
||||
buf[1] = 0x8b; // magic
|
||||
buf[2] = 0x08; // compression method is zlib
|
||||
buf[9] = 0xff; // made on unknown OS
|
||||
|
||||
void ZstdCompressor::write_to(u8 *buf) {
|
||||
// Copy compressed data
|
||||
std::vector<i64> offsets(shards.size());
|
||||
offsets[0] = 10; // +10 for header
|
||||
for (i64 i = 1; i < shards.size(); i++)
|
||||
offsets[i] = offsets[i - 1] + shards[i - 1].size();
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
|
||||
memcpy(&buf[offsets[i]], shards[i].data(), shards[i].size());
|
||||
});
|
||||
|
||||
// Write a trailer
|
||||
u8 *end = buf + size();
|
||||
end[-10] = 3; // two-byte zlib stream terminator
|
||||
end[-9] = 0;
|
||||
*(ul32 *)(end - 8) = checksum;
|
||||
*(ul32 *)(end - 4) = uncompressed_size;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,3 @@
|
||||
#define MOLD_VERSION "@mold_VERSION@"
|
||||
#define MOLD_LIBDIR "@CMAKE_INSTALL_FULL_LIBDIR@"
|
||||
#cmakedefine01 MOLD_IS_SOLD
|
||||
@@ -1,7 +0,0 @@
|
||||
# Create a symbolic link with a relative path. GNU ln takes the --relative
|
||||
# option to do that, but that's not portable, so we do it in CMake.
|
||||
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
get_filename_component(DIR "${DEST}" DIRECTORY)
|
||||
file(RELATIVE_PATH PATH "/${DIR}" "/${SOURCE}")
|
||||
file(CREATE_LINK "${PATH}" "${DEST}" SYMBOLIC)
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash -x
|
||||
#!/bin/bash
|
||||
# This script creates a mold binary distribution. The output is
|
||||
# written in this directory as `mold-$version-$arch-linux.tar.gz`
|
||||
# (e.g. `mold-1.0.3-x86_64-linux.tar.gz`).
|
||||
@@ -7,29 +7,27 @@
|
||||
# libstdc++ and libcrypto but dynamically-linked to libc, libm, libz
|
||||
# and librt, as they almost always exist on any Linux systems.
|
||||
|
||||
set -e
|
||||
|
||||
# Unlike Linux, macOS's uname returns arm64 for aarch64.
|
||||
arch=$(uname -m)
|
||||
[ $arch = arm64 ] && arch=aarch64
|
||||
|
||||
if [ $arch != x86_64 -a $arch != aarch64 ]; then
|
||||
echo "Error: no docker image for $arch"
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | ppc64le | s390x ]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version=$(grep '^VERSION =' $(dirname $0)/Makefile | sed 's/.* = //')
|
||||
arch=$1
|
||||
echo $arch | grep -Eq '^(x86_64|aarch64|arm|ppc64le|s390x)$' || \
|
||||
{ echo "Error: no docker image for $arch"; exit 1; }
|
||||
|
||||
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' $(dirname $0)/CMakeLists.txt)
|
||||
dest=mold-$version-$arch-linux
|
||||
image=rui314/mold-builder:v1-$arch
|
||||
set -e -x
|
||||
|
||||
docker images -q $image 2> /dev/null || docker pull $image
|
||||
|
||||
docker run -it --rm -v "$(pwd):/mold" -e "OWNER=$(id -u):$(id -g)" $image \
|
||||
docker run --platform linux/$arch -it --rm -v "$(pwd):/mold" \
|
||||
-e "OWNER=$(id -u):$(id -g)" rui314/mold-builder:latest \
|
||||
bash -c "mkdir /tmp/build &&
|
||||
cd /tmp/build &&
|
||||
cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DMOLD_MOSTLY_STATIC=On -DCMAKE_BUILD_TYPE=Release /mold &&
|
||||
cmake --build . -j\$(nproc)
|
||||
cmake --install . --prefix $dest --strip
|
||||
cmake --build . -j\$(nproc) &&
|
||||
[ $arch = arm ] || ctest -j\$(nproc) &&
|
||||
cmake --install . --prefix $dest --strip &&
|
||||
tar czf /mold/$dest.tar.gz $dest &&
|
||||
cp mold mold-wrapper.so /mold &&
|
||||
chown \$OWNER /mold/mold /mold/mold-wrapper.so /mold/$dest.tar.gz"
|
||||
+4
-4
@@ -23,10 +23,10 @@ process.
|
||||
|
||||
To prevent this type of attack, the stack area is no longer executable
|
||||
since the early 2000s. On Linux, the stack's executable-ness is
|
||||
controlled by a bit in an executable, and the loader respect that
|
||||
controlled by a bit in an executable, and the loader respects that
|
||||
bit. The bit is set by the linker.
|
||||
|
||||
GCC had (and still have) a feature that depends on the executable
|
||||
GCC had (and still has) a feature that depends on the executable
|
||||
stack, so they invented a way to tell the linker to mark the stack
|
||||
executable. Specifically, if an object file contains a
|
||||
`.note.GNU-stack` section with the `SHF_EXECSTACK` bit, GNU linker
|
||||
@@ -48,8 +48,8 @@ feature which still depends on the executable stack.
|
||||
|
||||
# How to fix it?
|
||||
|
||||
If you know what you are doing, pass `-z execstack` to mold. Beaware
|
||||
If you know what you are doing, pass `-z execstack` to mold. Beware
|
||||
that this will significantly weaken your program's security.
|
||||
|
||||
If you don't want to pass `-z execstack`, rewrite your code so that
|
||||
your code does not depends on the executable stack.
|
||||
your code does not depend on the executable stack.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
+174
-101
@@ -229,7 +229,119 @@ such as system-dependent library search paths, are hard-coded.
|
||||
depends only on its command-line arguments.
|
||||
.
|
||||
.\"=============================================================================
|
||||
.Sh OPTIONS
|
||||
.Sh MOLD-SPECIFIC OPTIONS
|
||||
.Bl -tag -width 6n -compact
|
||||
.Pp
|
||||
.It Fl -chroot Ns = Ns Ar dir
|
||||
Set
|
||||
.Ar dir
|
||||
to root directory.
|
||||
.Pp
|
||||
.It Fl -color-diagnostics Ns = Ns Op Sy auto | always | never
|
||||
.It Fl -color-diagnostics
|
||||
.It Fl -no-color-diagnostics
|
||||
.Pp
|
||||
Show diagnostics messages in color using ANSI escape sequences.
|
||||
.Ar auto
|
||||
means that
|
||||
.Nm
|
||||
prints out messages in color only if the standard output is connected to a TTY. \
|
||||
Default is
|
||||
.Ar auto .
|
||||
.Pp
|
||||
.It Fl -fork
|
||||
.It Fl -no-fork
|
||||
Spawn a child process and let it do the actual linking. \
|
||||
When linking a large program, the OS kernel can take a few hundred \
|
||||
milliseconds to terminate a
|
||||
.Nm
|
||||
process.
|
||||
.Fl -fork
|
||||
hides that latency. By default, it does fork.
|
||||
.Pp
|
||||
.It Fl -perf
|
||||
Print performance statistics.
|
||||
.Pp
|
||||
.It Fl -repro
|
||||
Archive input files as a tar file.
|
||||
.Pp
|
||||
.It Fl -reverse-sections
|
||||
Reverses the order of input sections before assigning them the offsets \
|
||||
in the output file.
|
||||
.Pp
|
||||
This option is useful for finding a bug that depends on an initialization \
|
||||
order of global objects. In C++, constructors of global objects in a single \
|
||||
source file are guaranteed to be executed in the source order, but \
|
||||
there's no such guarantee across compilation units. Usually, constructors \
|
||||
are executed in the order given to the linker, but depending on it is a mistake.
|
||||
.Pp
|
||||
By reversing the order of input sections using
|
||||
.Fl -reverse-sections ,
|
||||
you can easily test that your program works in the reversed initialization order.
|
||||
.Pp
|
||||
.It Fl -run Cm command Ar arg Ar
|
||||
Run
|
||||
.Cm command
|
||||
with
|
||||
.Nm
|
||||
as
|
||||
.Pa /usr/bin/ld .
|
||||
.Pp
|
||||
.It Fl -shuffle-sections
|
||||
.It Fl -shuffle-sections Ns = Ns Ar number
|
||||
Randomizes the output by shuffling the order of input sections before \
|
||||
assigning them the offsets in the output file. If
|
||||
.Ar number
|
||||
is given, it's used as a seed for the random number generator, so that \
|
||||
the linker produces the same output as for the same seed. If no seed \
|
||||
is given, a random number is used as a seed.
|
||||
.Pp
|
||||
This option is useful for benchmarking. Modern CPUs are sensitive to program's \
|
||||
memory layout. A seeming benign change in program layout (such as a small \
|
||||
size increase of a function in the middle of a program) can affect program's \
|
||||
performance. Therefore, \
|
||||
even if you write new code and get a good benchmark result, it is hard \
|
||||
to say whether or not the new code improves the programs performance. \
|
||||
It is possible that the new memory layout happens to perform better.
|
||||
.Pp
|
||||
By running a benchmark multiple time with shuffling memory layout using
|
||||
.Fl -shuffle-sections ,
|
||||
you can isolate your program's real performance number from the randomness \
|
||||
caused by memory layout changes.
|
||||
.Pp
|
||||
.It Fl -stats
|
||||
Print input statistics.
|
||||
.Pp
|
||||
.It Fl -thread-count Ns = Ns Ar count
|
||||
Use
|
||||
.Ar count
|
||||
number of threads.
|
||||
.Pp
|
||||
.It Fl -threads
|
||||
.It Fl -no-threads
|
||||
Use multiple threads.
|
||||
By default,
|
||||
.Nm
|
||||
uses as many threads as the number of cores or 32, whichever is the smallest.
|
||||
The reason why it is capped to 32 is because
|
||||
.Nm
|
||||
doesn't scale well beyond that point.
|
||||
To use only one thread, pass
|
||||
.Fl -no-threads
|
||||
or
|
||||
.Fl -thread-count Ns = Ns Sy 1 .
|
||||
.Pp
|
||||
.Pp
|
||||
.It Fl -quick-exit
|
||||
.It Fl -no-quick-exit
|
||||
Use or do not use
|
||||
.Dv quick_exit
|
||||
to exit.
|
||||
.
|
||||
.El \" End of options list
|
||||
.
|
||||
.\"-----------------------------------------------------------------------------
|
||||
.Sh GNU-COMPATIBLE OPTIONS
|
||||
.Bl -tag -width 6n -compact
|
||||
.It Fl -help
|
||||
Report usage information to stdout and exit.
|
||||
@@ -366,6 +478,26 @@ Instead of generating an executable or a shared object file, combine
|
||||
input object files to generate another object file that can be used as
|
||||
an input to a linker.
|
||||
.Pp
|
||||
.It Fl -relocatable-merge-sections
|
||||
By default,
|
||||
.Nm
|
||||
doesn't merge input sections by name when merging input object files into a \
|
||||
single output object file for
|
||||
.Fl r .
|
||||
For example,
|
||||
.Ar .text.foo
|
||||
and
|
||||
.Ar .text.bar
|
||||
aren't merged for
|
||||
.Fl r
|
||||
even though they are merged into
|
||||
.Ar .text
|
||||
according to the default section merging rules.
|
||||
.Pp
|
||||
This option changes the behavior so that
|
||||
.Nm
|
||||
merges input sections by name by the default section merging rules.
|
||||
.Pp
|
||||
.It Fl s , Fl -strip-all
|
||||
Omit
|
||||
.Li \.symtab
|
||||
@@ -468,29 +600,15 @@ sets a random 128-bit UUID.
|
||||
sets
|
||||
.Ar hexstring .
|
||||
.Pp
|
||||
.It Fl -chroot Ns = Ns Ar dir
|
||||
Set
|
||||
.Ar dir
|
||||
to root directory.
|
||||
.Pp
|
||||
.It Fl -color-diagnostics Ns = Ns Op Sy auto | always | never
|
||||
.It Fl -color-diagnostics
|
||||
.It Fl -no-color-diagnostics
|
||||
.Pp
|
||||
Show diagnostics messages in color using ANSI escape sequences.
|
||||
.Ar auto
|
||||
means that
|
||||
.Nm
|
||||
prints out messages in color only if the standard output is connected to a TTY. \
|
||||
Default is
|
||||
.Ar auto .
|
||||
.Pp
|
||||
.It Fl -defsym Ns = Ns Ar symbol Ns = Ns Ar value
|
||||
.Pp
|
||||
.It Fl -compress-debug-sections Ns = Ns Op Sy none | zlib | zlib-gabi | zlib-gnu
|
||||
.It Fl -compress-debug-sections Ns = Ns Op Sy none | zlib | zlib-gabi | zstd
|
||||
Compress DWARF debug info
|
||||
.Pf ( Sy .debug_*
|
||||
sections) using the zlib compression algorithm.
|
||||
sections) using the zlib or zstd compression algorithm.
|
||||
.Fl zlib-gabi
|
||||
is an alias for
|
||||
.Fl zlib .
|
||||
.Pp
|
||||
.It Fl -defsym Ns = Ns Ar symbol Ns = Ns Ar value
|
||||
Define
|
||||
@@ -520,7 +638,7 @@ Write a dependency file to
|
||||
The contents of the written file is readable by
|
||||
.Cm make ,
|
||||
which defines only one rule with the linker's output file as a target \
|
||||
and all input fiels as its prerequisite. Users are expected to include \
|
||||
and all input files as its prerequisite. Users are expected to include \
|
||||
the generated dependency file into a Makefile to automate the \
|
||||
dependency management. This option is analogous to the compiler's
|
||||
.Fl MM Fl MF
|
||||
@@ -534,6 +652,11 @@ Same as
|
||||
except that it implies
|
||||
.Fl -Bsymbolic .
|
||||
.Pp
|
||||
If
|
||||
.Ar file
|
||||
does not exist in the current directory, it is searched from library \
|
||||
search paths for the sake of compatibility with GNU ld.
|
||||
.Pp
|
||||
.It Fl -eh-frame-hdr
|
||||
.It Fl -no-eh-frame-hdr
|
||||
Create
|
||||
@@ -551,10 +674,6 @@ instructs the linker to leave relocation sections in the output file. \
|
||||
Some post-link binary analysis or optimization tools such as LLVM Bolt \
|
||||
need them.
|
||||
.Pp
|
||||
.Nm
|
||||
always creates RELA-type relocation sections even if the native \
|
||||
ELF format is REL-type so that it is easy to read addends.
|
||||
.Pp
|
||||
.It Fl -enable-new-dtags
|
||||
.It Fl -disable-new-dtags
|
||||
By default,
|
||||
@@ -567,6 +686,19 @@ mold emits DT_RPATH for
|
||||
.Fl -rpath
|
||||
instead.
|
||||
.Pp
|
||||
.It Fl -execute-only
|
||||
Traditionally, most processors require both executable and readable bits to 1 \
|
||||
to make the page executable, which allows machine code to read itself as data
|
||||
at runtime. This is actually what an attacker often does after gaining a limited \
|
||||
control of a process to find pieces of machine code they can use to gain the \
|
||||
full control of the process.
|
||||
.Pp
|
||||
As a mitigation, some recent processors allows "execute-only" pages. \
|
||||
If a page is execute-only, you can call a function there as long as you know \
|
||||
its address but can't read it as data.
|
||||
.Pp
|
||||
This option marks text segments execute-only. This option currently works only \
|
||||
on some ARM64 processors.
|
||||
.Pp
|
||||
.It Fl -exclude-libs Ns = Ns Ar libraries Ns ...
|
||||
Mark all symbols in the given
|
||||
@@ -596,16 +728,6 @@ Call
|
||||
.Ar symbol
|
||||
at unload-time.
|
||||
.Pp
|
||||
.It Fl -fork
|
||||
.It Fl -no-fork
|
||||
Spawn a child process and let it do the actual linking.
|
||||
When linking a large program, the OS kernel can take a few hundred \
|
||||
milliseconds to terminate a
|
||||
.Nm
|
||||
process.
|
||||
.Fl -fork
|
||||
hides that latency.
|
||||
.Pp
|
||||
.It Fl -gc-sections
|
||||
.It Fl -no-gc-sections
|
||||
Remove unreferenced sections.
|
||||
@@ -641,7 +763,7 @@ is given,
|
||||
tries to merge all identical functions. This reduces the size of the output \
|
||||
most, but it is not
|
||||
.Dq safe
|
||||
optimization. It is guaranteed in C and C++ that two pointeres pointing two \
|
||||
optimization. It is guaranteed in C and C++ that two pointers pointing two \
|
||||
different functions will never be equal, but
|
||||
.Fl -icf=all
|
||||
breaks that assumption as two functions have the same address after merging. \
|
||||
@@ -719,16 +841,10 @@ package management command such as
|
||||
.Cm rpm
|
||||
to embed metadata regarding a package to each executable file.
|
||||
.Pp
|
||||
.It Fl -perf
|
||||
Print performance statistics.
|
||||
.Pp
|
||||
.It Fl -pie , -pic-executable
|
||||
.It Fl -no-pie , -no-pic-executable
|
||||
Create a position-independent executable.
|
||||
.Pp
|
||||
.It Fl -preload
|
||||
Preload object files.
|
||||
.Pp
|
||||
.It Fl -print-gc-sections
|
||||
.It Fl -no-print-gc-sections
|
||||
Print removed unreferenced sections.
|
||||
@@ -762,12 +878,6 @@ by as-needed basis but don't want to change the global state of
|
||||
you can append "--push-state --as-needed -lfoo --pop-state" to the \
|
||||
linker command line options.
|
||||
.Pp
|
||||
.It Fl -quick-exit
|
||||
.It Fl -no-quick-exit
|
||||
Use
|
||||
.Dv quick_exit
|
||||
to exit.
|
||||
.Pp
|
||||
.It Fl -relax
|
||||
.It Fl -no-relax
|
||||
Rewrite machine instructions with more efficient ones for some relocations.
|
||||
@@ -778,11 +888,6 @@ Like
|
||||
.Fl -undefined ,
|
||||
except the new symbol must be defined by the end of the link.
|
||||
.Pp
|
||||
.It Fl -repro
|
||||
Embed input files into
|
||||
.Dv .repro
|
||||
section.
|
||||
.Pp
|
||||
.It Fl -retain-symbols-file Ns = Ns Ar file
|
||||
Keep only symbols listed in
|
||||
.Ar file .
|
||||
@@ -792,7 +897,7 @@ is a text file
|
||||
containing a symbol name on each line.
|
||||
.Nm
|
||||
discards all local
|
||||
symbols as well as global sybmol that are not in
|
||||
symbols as well as global symbol that are not in
|
||||
.Ar file .
|
||||
Note that this option removes symbols only from
|
||||
.Dv .symtab
|
||||
@@ -800,23 +905,11 @@ section and does not affect
|
||||
.Dv .dynsym
|
||||
section, which is used for dynamic linking.
|
||||
.Pp
|
||||
.It Fl -reverse-sections
|
||||
Reverses the order of input sections before assigning them the offsets \
|
||||
in the output file.
|
||||
.Pp
|
||||
.It Fl -rpath Ns = Ns Ar dir
|
||||
Add
|
||||
.Ar dir
|
||||
to runtime search path.
|
||||
.Pp
|
||||
.It Fl -run Cm command Ar arg Ar
|
||||
Run
|
||||
.Cm command
|
||||
with
|
||||
.Nm
|
||||
as
|
||||
.Pa /usr/bin/ld .
|
||||
.Pp
|
||||
.It Fl -section-start Ns = Ns Ar section Ns = Ns Ar address
|
||||
Set
|
||||
.Ar address
|
||||
@@ -829,15 +922,6 @@ is a hexadecimal number that may start with an optional
|
||||
.It Fl -shared , -Bshareable
|
||||
Create a share library.
|
||||
.Pp
|
||||
.It Fl -shuffle-sections
|
||||
.It Fl -shuffle-sections Ns = Ns Ar number
|
||||
Randomizes the output by shuffleing the order of input sections before \
|
||||
assigning them the offsets in the output file. If
|
||||
.Ar number
|
||||
is given, it's used as a seed for the random number generator, so that \
|
||||
the linker produces the same output as for the same seed. If no seed \
|
||||
is given, it uses a random number as a seed.
|
||||
.Pp
|
||||
.It Fl -spare-dynamic-tags Ns = Ns Ar number
|
||||
Reserve given
|
||||
.Ar number
|
||||
@@ -860,35 +944,21 @@ needed but want to avoid the overhead of running
|
||||
.It Fl -static
|
||||
Do not link against shared libraries.
|
||||
.Pp
|
||||
.It Fl -stats
|
||||
Print input statistics.
|
||||
.Pp
|
||||
.It Fl -sysroot Ns = Ns Ar dir
|
||||
Set target system root directory to
|
||||
.Ar dir .
|
||||
.Pp
|
||||
.It Fl -thread-count Ns = Ns Ar count
|
||||
Use
|
||||
.Ar count
|
||||
number of threads.
|
||||
.Pp
|
||||
.It Fl -threads
|
||||
.It Fl -no-threads
|
||||
Use multiple threads.
|
||||
By default,
|
||||
.Nm
|
||||
uses as many threads as the number of cores or 32, whichever is the smallest.
|
||||
The reason why it is capped to 32 is because
|
||||
.Nm
|
||||
doesn't scale well beyond that point.
|
||||
To use only one thread, pass
|
||||
.Fl -no-threads
|
||||
or
|
||||
.Fl -thread-count Ns = Ns Sy 1 .
|
||||
.Pp
|
||||
.It Fl -trace
|
||||
Print name of each input file.
|
||||
.Pp
|
||||
.It Fl -undefined-version
|
||||
.It Fl -no-undefined-version
|
||||
By default,
|
||||
.Nm
|
||||
warns on a symbol specified by a version script or by
|
||||
.Fl -export-dynamic-symbol
|
||||
if it is not defined. You can silence the warning by
|
||||
.Fl -undefined-version .
|
||||
.Pp
|
||||
.It Fl -unique Ns = Ns Ar pattern
|
||||
Don't merge input sections that match
|
||||
.Ar pattern .
|
||||
@@ -900,6 +970,10 @@ How to handle undefined symbols.
|
||||
.It Fl -version-script Ns = Ns Ar file
|
||||
Read version script from
|
||||
.Ar file .
|
||||
If
|
||||
.Ar file
|
||||
does not exist in the current directory, it is searched from library search paths \
|
||||
for the sake of compatibility with GNU ld.
|
||||
.Pp
|
||||
.It Fl -warn-common
|
||||
.It Fl -no-warn-common
|
||||
@@ -1046,7 +1120,7 @@ disables the feature.
|
||||
.It Fl z Cm noseparate-code
|
||||
If one memory page contains multiple segments,
|
||||
the page protection bits are set in such a way that needed attributes \
|
||||
(writable or executable) are satisifed for all segments.
|
||||
(writable or executable) are satisfied for all segments.
|
||||
This usually happens at a boundary of two segments with two different \
|
||||
attributes.
|
||||
.Pp
|
||||
@@ -1132,7 +1206,6 @@ Mark object to interpose all DSOs but executable.
|
||||
.It Fl -no-allow-shlib-undefined
|
||||
.It Fl -no-copy-dt-needed-entries
|
||||
.It Fl -no-fatal-warnings
|
||||
.It Fl -no-undefined-version
|
||||
.It Fl -nostdlib
|
||||
.It Fl -rpath-link Ns = Ns Ar dir
|
||||
.It Fl -sort-common
|
||||
@@ -1154,7 +1227,7 @@ Ignored
|
||||
.Sh SEE ALSO
|
||||
.Xr gold 1 ,
|
||||
.Xr ld 1 ,
|
||||
.Xr elf 5
|
||||
.Xr elf 5 ,
|
||||
.Xr ld.so 8
|
||||
.
|
||||
.\"=============================================================================
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
// Alpha is a 64-bit RISC ISA developed by DEC (Digital Equipment
|
||||
// Corporation) in the early '90s. It aimed to be an ISA that would last
|
||||
// 25 years. DEC expected Alpha would become 1000x faster during that time
|
||||
// span. Since the ISA was developed from scratch for future machines,
|
||||
// it's 64-bit from the beginning. There's no 32-bit variant.
|
||||
//
|
||||
// DEC ported its own Unix (Tru64) to Alpha. Microsoft also ported Windows
|
||||
// NT to it. But it wasn't a huge commercial success.
|
||||
//
|
||||
// DEC was acquired by Compaq in 1997. In the late '90s, Intel and
|
||||
// Hewlett-Packard were advertising that their upcoming Itanium processor
|
||||
// would achieve significantly better performance than RISC processors, so
|
||||
// Compaq decided to discontinue the Alpha processor line to switch to
|
||||
// Itanium. Itanium resulted in a miserable failure, but it still suceeded
|
||||
// to wipe out several RISC processors just by promising overly optimistic
|
||||
// perf numbers. Alpha as an ISA would probably have been fine after 25
|
||||
// years since its introduction (which is 1992 + 25 = 2017), but the
|
||||
// company and its market didn't last that long.
|
||||
//
|
||||
// From the linker's point of view, there are a few peculiarities in its
|
||||
// psABI as shown below:
|
||||
//
|
||||
// - Alpha lacks PC-relative memory load/store instructions, so it uses
|
||||
// register-relative load/store instructions in position-independent
|
||||
// code. Specifically, GP (which is an alias for $r29) is always
|
||||
// maintained to refer to .got+0x8000, and global variables' addresses
|
||||
// are loaded in a GP-relative manner.
|
||||
//
|
||||
// - It looks like even function addresses are first loaded to register
|
||||
// in a GP-relative manner before calling it. We can relax it to
|
||||
// convert the instruction sequence with a direct branch instruction,
|
||||
// but by default, object files don't use a direct branch to call a
|
||||
// function. Therefore, by default, we don't need to create a PLT.
|
||||
// Any function call is made by first reading its address from GOT and
|
||||
// jump to the address.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = ALPHA;
|
||||
|
||||
// A 32-bit immediate can be materialized in a register with a "load high"
|
||||
// and a "load low" instruction sequence. The first instruction sets the
|
||||
// upper 16 bits in a register, and the second one set the lower 16
|
||||
// bits. When doing so, they sign-extend an immediate. Therefore, if the
|
||||
// 15th bit of an immediate happens to be 1, setting a "low half" value
|
||||
// negates the upper 16 bit values that has already been set in a
|
||||
// register. To compensate that, we need to add 0x8000 when setting the
|
||||
// upper 16 bits.
|
||||
static u32 hi(u32 val) {
|
||||
return bits(val + 0x8000, 31, 16);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_ALPHA_SREL32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
u64 GP = ctx.got->shdr.sh_addr + 0x8000;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ALPHA_REFQUAD:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_ALPHA_GPREL32:
|
||||
*(ul32 *)loc = S + A - GP;
|
||||
break;
|
||||
case R_ALPHA_LITERAL:
|
||||
if (A)
|
||||
*(ul16 *)loc = ctx.extra.got->get_addr(sym, A) - GP;
|
||||
else
|
||||
*(ul16 *)loc = GOT + G - GP;
|
||||
break;
|
||||
case R_ALPHA_BRSGP:
|
||||
*(ul32 *)loc |= bits(S + A - P - 4, 22, 0);
|
||||
break;
|
||||
case R_ALPHA_GPDISP:
|
||||
*(ul16 *)loc = hi(GP - P);
|
||||
*(ul16 *)(loc + A) = GP - P;
|
||||
break;
|
||||
case R_ALPHA_SREL32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_ALPHA_GPRELHIGH:
|
||||
*(ul16 *)loc = hi(S + A - GP);
|
||||
break;
|
||||
case R_ALPHA_GPRELLOW:
|
||||
*(ul16 *)loc = S + A - GP;
|
||||
break;
|
||||
case R_ALPHA_TLSGD:
|
||||
*(ul16 *)loc = sym.get_tlsgd_addr(ctx) - GP;
|
||||
break;
|
||||
case R_ALPHA_TLSLDM:
|
||||
*(ul16 *)loc = ctx.got->get_tlsld_addr(ctx) - GP;
|
||||
break;
|
||||
case R_ALPHA_DTPRELHI:
|
||||
*(ul16 *)loc = hi(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_ALPHA_DTPRELLO:
|
||||
*(ul16 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_ALPHA_GOTTPREL:
|
||||
*(ul16 *)loc = sym.get_gottp_addr(ctx) + A - GP;
|
||||
break;
|
||||
case R_ALPHA_TPRELHI:
|
||||
*(ul16 *)loc = hi(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_ALPHA_TPRELLO:
|
||||
*(ul16 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_ALPHA_LITUSE:
|
||||
case R_ALPHA_HINT:
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ALPHA_REFLONG:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_ALPHA_REFQUAD:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(ul64 *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on Alpha";
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ALPHA_REFQUAD:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ALPHA_LITERAL:
|
||||
if (rel.r_addend) {
|
||||
sym.flags.fetch_or(NEEDS_ALPHA_GOT, std::memory_order_relaxed);
|
||||
ctx.extra.got->add_symbol(sym, rel.r_addend);
|
||||
} else {
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
}
|
||||
break;
|
||||
case R_ALPHA_SREL32:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ALPHA_BRSGP:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_ALPHA_TLSGD:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_ALPHA_TLSLDM:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_ALPHA_GOTTPREL:
|
||||
case R_ALPHA_TPRELHI:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_ALPHA_GPREL32:
|
||||
case R_ALPHA_LITUSE:
|
||||
case R_ALPHA_GPDISP:
|
||||
case R_ALPHA_HINT:
|
||||
case R_ALPHA_GPRELHIGH:
|
||||
case R_ALPHA_GPRELLOW:
|
||||
case R_ALPHA_DTPRELHI:
|
||||
case R_ALPHA_DTPRELLO:
|
||||
case R_ALPHA_TPRELLO:
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// An R_ALPHA_LITERAL relocation may request the linker to create a GOT
|
||||
// entry for an external symbol with a non-zero addend. This is an unusual
|
||||
// request which we cannot find in any other targets.
|
||||
//
|
||||
// Referring an external symbol with a non-zero addend is a bad practice.
|
||||
// To see why, assume that we have two R_ALPHA_LITERAL relocs to symbol X
|
||||
// with addend 8 and 16. To satisfy that request, we need to create not
|
||||
// only one but two GOT entries for symbol X with different addends.
|
||||
//
|
||||
// We don't want to mess up the implementation of the common GOT section
|
||||
// for Alpha. So we create another GOT-like section, .alpha_got. Any GOT
|
||||
// entry for an R_ALPHA_LITERAL reloc with a non-zero addends is created
|
||||
// not in .got but in .alpha_got.
|
||||
//
|
||||
// Since .alpha_got entries are accessed relative to GP, .alpha_got
|
||||
// needs to be close enough to .got. It's actually placed next to .got.
|
||||
void AlphaGotSection::add_symbol(Symbol<E> &sym, i64 addend) {
|
||||
assert(addend);
|
||||
std::scoped_lock lock(mu);
|
||||
entries.push_back({&sym, addend});
|
||||
}
|
||||
|
||||
bool operator<(const AlphaGotSection::Entry &a, const AlphaGotSection::Entry &b) {
|
||||
return std::tuple(a.sym->file->priority, a.sym->sym_idx, a.addend) <
|
||||
std::tuple(b.sym->file->priority, b.sym->sym_idx, b.addend);
|
||||
};
|
||||
|
||||
u64 AlphaGotSection::get_addr(Symbol<E> &sym, i64 addend) {
|
||||
auto it = std::lower_bound(entries.begin(), entries.end(), Entry{&sym, addend});
|
||||
assert(it != entries.end());
|
||||
return this->shdr.sh_addr + (it - entries.begin()) * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
i64 AlphaGotSection::get_reldyn_size(Context<E> &ctx) {
|
||||
i64 n = 0;
|
||||
for (Entry &e : entries)
|
||||
if (e.sym->is_imported || (ctx.arg.pic && !e.sym->is_absolute()))
|
||||
n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
void AlphaGotSection::finalize() {
|
||||
sort(entries);
|
||||
remove_duplicates(entries);
|
||||
shdr.sh_size = entries.size() * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
void AlphaGotSection::copy_buf(Context<E> &ctx) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset + reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < entries.size(); i++) {
|
||||
Entry &e = entries[i];
|
||||
u64 P = this->shdr.sh_addr + sizeof(Word<E>) * i;
|
||||
ul64 *buf = (ul64 *)(ctx.buf + this->shdr.sh_offset + sizeof(Word<E>) * i);
|
||||
|
||||
if (e.sym->is_imported) {
|
||||
*buf = ctx.arg.apply_dynamic_relocs ? e.addend : 0;
|
||||
*dynrel++ = ElfRel<E>(P, E::R_ABS, e.sym->get_dynsym_idx(ctx), e.addend);
|
||||
} else {
|
||||
*buf = e.sym->get_addr(ctx) + e.addend;
|
||||
if (ctx.arg.pic && !e.sym->is_absolute())
|
||||
*dynrel++ = ElfRel<E>(P, E::R_RELATIVE, 0, *buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+339
-236
@@ -10,10 +10,9 @@
|
||||
//
|
||||
// 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
|
||||
// We need to use that instructions to, for example, call a function
|
||||
// encoded in Thumb from a function encoded in ARM. Sometimes, the
|
||||
// linker even has to emit an interworking thunk code to switch between
|
||||
// them.
|
||||
// linker even has to emit an interworking thunk code to switch mode.
|
||||
//
|
||||
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
|
||||
// byte boundaries.
|
||||
@@ -27,6 +26,8 @@
|
||||
// is 0x2000 (not 0x2001, as Thumb instructions are always 2-byte
|
||||
// aligned). Likewise, if a function pointer has value 0x2001, it
|
||||
// refers a Thumb function at 0x2000.
|
||||
//
|
||||
// https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -38,6 +39,71 @@ namespace mold::elf {
|
||||
|
||||
using E = ARM32;
|
||||
|
||||
template <>
|
||||
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_NONE:
|
||||
return 0;
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_BASE_PREL:
|
||||
case R_ARM_GOTOFF32:
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TLS_GD32:
|
||||
case R_ARM_TLS_LDM32:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_TLS_IE32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
case R_ARM_TARGET2:
|
||||
return *(il32 *)loc;
|
||||
case R_ARM_THM_JUMP11:
|
||||
return sign_extend(*(ul16 *)loc, 10) << 1;
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_THM_JUMP24:
|
||||
case R_ARM_THM_TLS_CALL: {
|
||||
u32 S = bit(*(ul16 *)loc, 10);
|
||||
u32 J1 = bit(*(ul16 *)(loc + 2), 13);
|
||||
u32 J2 = bit(*(ul16 *)(loc + 2), 11);
|
||||
u32 I1 = !(J1 ^ S);
|
||||
u32 I2 = !(J2 ^ S);
|
||||
u32 imm10 = bits(*(ul16 *)loc, 9, 0);
|
||||
u32 imm11 = bits(*(ul16 *)(loc + 2), 10, 0);
|
||||
u32 val = (S << 24) | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1);
|
||||
return sign_extend(val, 24);
|
||||
}
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_TLS_CALL:
|
||||
return sign_extend(*(ul32 *)loc & 0x00ff'ffff, 23) << 2;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_MOVT_ABS: {
|
||||
u32 imm12 = bits(*(ul32 *)loc, 11, 0);
|
||||
u32 imm4 = bits(*(ul32 *)loc, 19, 16);
|
||||
return sign_extend((imm4 << 12) | imm12, 15);
|
||||
}
|
||||
case R_ARM_PREL31:
|
||||
return sign_extend(*(ul32 *)loc, 30);
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_ABS: {
|
||||
u32 imm4 = bits(*(ul16 *)loc, 3, 0);
|
||||
u32 i = bit(*(ul16 *)loc, 10);
|
||||
u32 imm3 = bits(*(ul16 *)(loc + 2), 14, 12);
|
||||
u32 imm8 = bits(*(ul16 *)(loc + 2), 7, 0);
|
||||
u32 val = (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8;
|
||||
return sign_extend(val, 15);
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
static void write_mov_imm(u8 *loc, u32 val) {
|
||||
u32 imm12 = bits(val, 11, 0);
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
@@ -72,53 +138,93 @@ static void write_thm_mov_imm(u8 *loc, u32 val) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
static const u32 plt0[] = {
|
||||
0xe52de004, // push {lr}
|
||||
0xe59fe004, // ldr lr, 2f
|
||||
0xe08fe00e, // 1: add lr, pc, lr
|
||||
0xe5bef008, // ldr pc, [lr, #8]!
|
||||
0x00000000, // 2: .word .got.plt - 1b - 8
|
||||
0xe320f000, // nop
|
||||
0xe320f000, // nop
|
||||
0xe320f000, // nop
|
||||
};
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(ul32 *)(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));
|
||||
*(ul32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 12;
|
||||
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_NONE:
|
||||
break;
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_BASE_PREL:
|
||||
case R_ARM_GOTOFF32:
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TLS_GD32:
|
||||
case R_ARM_TLS_LDM32:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_TLS_IE32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
case R_ARM_TARGET2:
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
case R_ARM_THM_JUMP11:
|
||||
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 11, 1);
|
||||
break;
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_THM_JUMP24:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
write_thm_b_imm(loc, val);
|
||||
break;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_JUMP24:
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
break;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_MOVT_ABS:
|
||||
write_mov_imm(loc, val);
|
||||
break;
|
||||
case R_ARM_PREL31:
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
break;
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
write_thm_mov_imm(loc, val);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn[] = {
|
||||
0xe52d'e004, // push {lr}
|
||||
0xe59f'e004, // ldr lr, 2f
|
||||
0xe08f'e00e, // 1: add lr, pc, lr
|
||||
0xe5be'f008, // ldr pc, [lr, #8]!
|
||||
0x0000'0000, // 2: .word .got.plt - 1b - 8
|
||||
0xe320'f000, // nop
|
||||
0xe320'f000, // nop
|
||||
0xe320'f000, // nop
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
|
||||
}
|
||||
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * sizeof(plt);
|
||||
memcpy(ent, plt, sizeof(plt));
|
||||
*(ul32 *)(ent + 12) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 12;
|
||||
}
|
||||
static const ul32 plt_entry[] = {
|
||||
0xe59f'c004, // 1: ldr ip, 2f
|
||||
0xe08c'c00f, // add ip, ip, pc
|
||||
0xe59c'f000, // ldr pc, [ip]
|
||||
0x0000'0000, // 2: .word sym@GOT - 1b
|
||||
};
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(ul32 *)(buf + 12) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
// ARM does not use .eh_frame for exception handling. Instead, it uses
|
||||
@@ -134,79 +240,71 @@ static bool is_jump_reachable(i64 val) {
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
i64 frag_idx = 0;
|
||||
i64 trampoline_idx = 0;
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
std::span<std::unique_ptr<RangeExtensionThunk<E>>> thunks =
|
||||
output_section->thunks;
|
||||
|
||||
auto get_trampoline_addr = [&](u64 addr) {
|
||||
for (;;) {
|
||||
assert(!thunks.empty());
|
||||
i64 disp = output_section->shdr.sh_addr + thunks[0]->offset - addr;
|
||||
if (is_jump_reachable(disp))
|
||||
return disp;
|
||||
thunks = thunks.subspan(1);
|
||||
}
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_ARM_NONE || rel.r_type == R_ARM_V4BX)
|
||||
if (rel.r_type == R_NONE || rel.r_type == R_ARM_V4BX)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
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_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
auto get_thumb_thunk_addr = [&] {
|
||||
RangeExtensionRef ref = extra.range_extn[i];
|
||||
assert(ref.thunk_idx != -1);
|
||||
u64 addr = output_section->thunks[ref.thunk_idx]->get_addr(ref.sym_idx);
|
||||
assert(is_jump_reachable(addr + A - P));
|
||||
return addr;
|
||||
auto 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 get_arm_thunk_addr = [&] {
|
||||
return get_thumb_thunk_addr() + 4;
|
||||
};
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = get_addend(*this, rel);
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 T = S & 1;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto get_trampoline_addr = [&](u64 addr) {
|
||||
for (; trampoline_idx < output_section->thunks.size(); trampoline_idx++) {
|
||||
RangeExtensionThunk<E> &thunk = *output_section->thunks[trampoline_idx];
|
||||
i64 disp = output_section->shdr.sh_addr + thunk.offset - addr;
|
||||
if (is_jump_reachable(disp))
|
||||
return disp;
|
||||
}
|
||||
unreachable();
|
||||
};
|
||||
auto get_thumb_thunk_addr = [&] { return get_thunk_addr(i); };
|
||||
auto get_arm_thunk_addr = [&] { return get_thunk_addr(i) + 4; };
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_TARGET1:
|
||||
apply_abs_dyn_rel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
continue;
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
continue;
|
||||
break;
|
||||
case R_ARM_THM_CALL: {
|
||||
// THM_CALL relocation refers either BL or BLX instruction.
|
||||
// They are different in only one bit. We need to use BL if
|
||||
// the jump target is Thumb. Otherwise, use BLX.
|
||||
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
write_thm_b_imm(loc, 4);
|
||||
*(ul16 *)(loc + 2) |= 0x1000; // rewrite to BL
|
||||
continue;
|
||||
*(ul32 *)loc = 0x8000'f3af; // NOP.W
|
||||
break;
|
||||
}
|
||||
|
||||
u64 val = S + A - P;
|
||||
i64 val = S + A - P;
|
||||
if (is_jump_reachable(val)) {
|
||||
if (T) {
|
||||
write_thm_b_imm(loc, val);
|
||||
@@ -219,19 +317,23 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_thm_b_imm(loc, align_to(get_arm_thunk_addr() + A - P, 4));
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite to BLX
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_ARM_BASE_PREL:
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
continue;
|
||||
break;
|
||||
case R_ARM_GOTOFF32:
|
||||
*(ul32 *)loc = ((S + A) | T) - GOT;
|
||||
break;
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_TARGET2:
|
||||
*(ul32 *)loc = GOT + G + A - P;
|
||||
continue;
|
||||
break;
|
||||
case R_ARM_GOT_BREL:
|
||||
*(ul32 *)loc = G + A;
|
||||
continue;
|
||||
case R_ARM_CALL: {
|
||||
break;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_PLT32: {
|
||||
// Just like THM_CALL, ARM_CALL relocation refers either BL or
|
||||
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
|
||||
bool is_bl = ((*(ul32 *)loc & 0xff00'0000) == 0xeb00'0000);
|
||||
@@ -239,11 +341,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
if (!is_bl && !is_blx)
|
||||
Fatal(ctx) << *this << ": R_ARM_CALL refers neither BL nor BLX";
|
||||
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(ul32 *)loc = 0xeb00'0001;
|
||||
continue;
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
u64 val = S + A - P;
|
||||
@@ -255,11 +357,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
} else {
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(get_arm_thunk_addr() + A - P, 25, 2);
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_ARM_JUMP24:
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | 1;
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
} else {
|
||||
// Unlike BL and BLX, we can't rewrite B to BX because BX doesn't
|
||||
// takes an immediate; it takes only a register. So if mode switch
|
||||
@@ -270,14 +372,35 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
val = get_arm_thunk_addr() + A - P;
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
}
|
||||
continue;
|
||||
case R_ARM_THM_JUMP11:
|
||||
break;
|
||||
case R_ARM_THM_JUMP11: {
|
||||
assert(T);
|
||||
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(S + A - P, 11, 1);
|
||||
continue;
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 11), 1 << 11);
|
||||
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 11, 1);
|
||||
break;
|
||||
}
|
||||
case R_ARM_THM_JUMP19: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 19), 1 << 19);
|
||||
|
||||
// sign:J2:J1:imm6:imm11:'0'
|
||||
u32 sign = bit(val, 20);
|
||||
u32 J2 = bit(val, 19);
|
||||
u32 J1 = bit(val, 18);
|
||||
u32 imm6 = bits(val, 17, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
*(ul16 *)loc &= 0b1111'1011'1100'0000;
|
||||
*(ul16 *)loc |= (sign << 10) | imm6;
|
||||
|
||||
*(ul16 *)(loc + 2) &= 0b1101'0000'0000'0000;
|
||||
*(ul16 *)(loc + 2) |= (J2 << 13) | (J1 << 11) | imm11;
|
||||
break;
|
||||
}
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | 1;
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0x8000'f3af; // NOP.W
|
||||
} else {
|
||||
// Just like R_ARM_JUMP24, we need to jump to a thunk if we need to
|
||||
// switch processor mode.
|
||||
@@ -286,88 +409,85 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
val = get_thumb_thunk_addr() + A - P;
|
||||
write_thm_b_imm(loc, val);
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
write_mov_imm(loc, ((S + A) | T) - P);
|
||||
continue;
|
||||
break;
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
write_mov_imm(loc, (S + A) | T);
|
||||
continue;
|
||||
break;
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
write_thm_mov_imm(loc, ((S + A) | T) - P);
|
||||
continue;
|
||||
case R_ARM_PREL31:
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | ((S + A - P) & 0x7fff'ffff);
|
||||
continue;
|
||||
break;
|
||||
case R_ARM_PREL31: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 30), 1LL << 30);
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
break;
|
||||
}
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
write_thm_mov_imm(loc, (S + A) | T);
|
||||
continue;
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
write_mov_imm(loc, (S + A - P) >> 16);
|
||||
continue;
|
||||
break;
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
write_thm_mov_imm(loc, (S + A - P) >> 16);
|
||||
continue;
|
||||
break;
|
||||
case R_ARM_MOVT_ABS:
|
||||
write_mov_imm(loc, (S + A) >> 16);
|
||||
continue;
|
||||
break;
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
write_thm_mov_imm(loc, (S + A) >> 16);
|
||||
continue;
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
continue;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
continue;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin;
|
||||
continue;
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
continue;
|
||||
break;
|
||||
case R_ARM_TLS_LE32:
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin + E::tls_tp_offset;
|
||||
continue;
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
*(ul32 *)loc = S - ctx.tls_begin + E::tls_tp_offset;
|
||||
} else if (A & 1) {
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// A is odd if the corresponding TLS_CALL is Thumb.
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 6;
|
||||
if (A & 1)
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 6;
|
||||
else
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 4;
|
||||
} else {
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 4;
|
||||
*(ul32 *)loc = S - ctx.tp_addr;
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
case R_ARM_TLS_CALL:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
// BL -> NOP
|
||||
*(ul32 *)loc = 0xe320'f000;
|
||||
} else {
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// BL <tls_trampoline>
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(get_trampoline_addr(P + 8), 25, 2);
|
||||
}
|
||||
continue;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
// BL -> NOP
|
||||
*(ul32 *)loc = 0x8000'f3af;
|
||||
} else {
|
||||
// BL -> NOP
|
||||
*(ul32 *)loc = 0xe320'f000;
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
u64 val = align_to(get_trampoline_addr(P + 4), 4);
|
||||
write_thm_b_imm(loc, val);
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
|
||||
} else {
|
||||
// BL -> NOP.W
|
||||
*(ul32 *)loc = 0x8000'f3af;
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef T
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +497,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_ARM_NONE)
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
@@ -389,36 +509,30 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 addend;
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : this->get_addend(rel))
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : get_addend(*this, rel);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
if (!frag) {
|
||||
if (std::optional<u64> val = get_tombstone(sym)) {
|
||||
*(ul32 *)loc = *val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin;
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,7 +546,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_ARM_NONE)
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
@@ -442,49 +556,52 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC)
|
||||
sym.flags |= (NEEDS_GOT | NEEDS_PLT);
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_MOVT_ABS:
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
case R_ARM_TARGET1:
|
||||
scan_abs_dyn_rel(ctx, sym, rel);
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TARGET2:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
case R_ARM_PREL31:
|
||||
scan_pcrel_rel(ctx, sym, rel);
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
ctx.needs_tlsld = true;
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
if (!ctx.relax_tlsdesc || sym.is_imported)
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
case R_ARM_GOTOFF32:
|
||||
case R_ARM_THM_JUMP11:
|
||||
case R_ARM_THM_JUMP19:
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
@@ -501,41 +618,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// For range extension thunks
|
||||
template <>
|
||||
bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
|
||||
InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
// We pessimistically assume that PLT entries are unreacahble.
|
||||
if (sym.has_plt(ctx))
|
||||
return false;
|
||||
|
||||
// We create thunks with a pessimistic assumption that all
|
||||
// out-of-section relocations would be out-of-range.
|
||||
InputSection<E> *isec2 = sym.get_input_section();
|
||||
if (!isec2 || isec.output_section != isec2->output_section)
|
||||
return false;
|
||||
|
||||
// If the target section is in the same output section but hasn't got
|
||||
// any address yet, that's unreacahble.
|
||||
if (isec2->offset == -1)
|
||||
return false;
|
||||
|
||||
// Thumb and ARM B instructions cannot be converted to BX, so we
|
||||
// always have to make them jump to a thunk to switch processor mode
|
||||
// even if their destinations are within their ranges.
|
||||
bool is_thumb = sym.get_addr(ctx) & 1;
|
||||
if ((rel.r_type == R_ARM_THM_JUMP24 && !is_thumb) ||
|
||||
(rel.r_type == R_ARM_JUMP24 && is_thumb))
|
||||
return false;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol
|
||||
// and check if they are within reach.
|
||||
i64 S = sym.get_addr(ctx);
|
||||
i64 A = isec.get_addend(rel);
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
return is_jump_reachable(S + A - P);
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
@@ -543,10 +625,10 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// TLS trampoline code. ARM32's TLSDESC is designed so that this
|
||||
// common piece of code is factored out from object files to reduce
|
||||
// output size. Since no one provide, the linker has to synthesize it.
|
||||
static u32 hdr[] = {
|
||||
0xe08e0000, // add r0, lr, r0
|
||||
0xe5901004, // ldr r1, [r0, #4]
|
||||
0xe12fff11, // bx r1
|
||||
static ul32 hdr[] = {
|
||||
0xe08e'0000, // add r0, lr, r0
|
||||
0xe590'1004, // ldr r1, [r0, #4]
|
||||
0xe12f'ff11, // bx r1
|
||||
};
|
||||
|
||||
// This is a range extension and mode switch thunk.
|
||||
@@ -577,14 +659,6 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static OutputSection<E> *find_exidx_section(Context<E> &ctx) {
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
if (osec->shdr.sh_type == SHT_ARM_EXIDX)
|
||||
return osec.get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ARM executables use an .ARM.exidx section to look up an exception
|
||||
// handling record for the current instruction pointer. The table needs
|
||||
// to be sorted by their addresses.
|
||||
@@ -594,13 +668,7 @@ static OutputSection<E> *find_exidx_section(Context<E> &ctx) {
|
||||
// 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");
|
||||
|
||||
OutputSection<E> *osec = find_exidx_section(ctx);
|
||||
if (!osec)
|
||||
return;
|
||||
|
||||
static void sort_exidx(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
// .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
|
||||
@@ -620,11 +688,11 @@ void sort_arm_exidx(Context<E> &ctx) {
|
||||
ul32 val;
|
||||
};
|
||||
|
||||
if (osec->shdr.sh_size % sizeof(Entry))
|
||||
if (osec.shdr.sh_size % sizeof(Entry))
|
||||
Fatal(ctx) << "invalid .ARM.exidx section size";
|
||||
|
||||
Entry *ent = (Entry *)(ctx.buf + osec->shdr.sh_offset);
|
||||
i64 num_entries = osec->shdr.sh_size / sizeof(Entry);
|
||||
Entry *ent = (Entry *)(ctx.buf + osec.shdr.sh_offset);
|
||||
i64 num_entries = osec.shdr.sh_size / sizeof(Entry);
|
||||
|
||||
// Entry's addresses are relative to themselves. In order to sort
|
||||
// records by addresses, we first translate them so that the addresses
|
||||
@@ -637,7 +705,7 @@ void sort_arm_exidx(Context<E> &ctx) {
|
||||
i64 offset = sizeof(Entry) * i;
|
||||
ent[i].addr = sign_extend(ent[i].addr, 30) + offset;
|
||||
if (is_relative(ent[i].val))
|
||||
ent[i].val = 0x7fff'ffff & (sign_extend(ent[i].val, 30) + offset);
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
|
||||
});
|
||||
|
||||
tbb::parallel_sort(ent, ent + num_entries, [](const Entry &a, const Entry &b) {
|
||||
@@ -653,4 +721,39 @@ void sort_arm_exidx(Context<E> &ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
void fixup_arm_exidx_section(Context<E> &ctx) {
|
||||
Timer t(ctx, "fixup_arm_exidx_section");
|
||||
|
||||
auto find_exidx = [&]() -> OutputSection<E> * {
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
if (osec->shdr.sh_type == SHT_ARM_EXIDX)
|
||||
return osec;
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
OutputSection<E> *exidx = find_exidx();
|
||||
if (!exidx)
|
||||
return;
|
||||
|
||||
// Sort .ARM.exidx contents
|
||||
sort_exidx(ctx, *exidx);
|
||||
|
||||
// .ARM.exidx's sh_link should be set to the .text section index.
|
||||
// Runtime doesn't care about it, but the binutils's strip command does.
|
||||
if (ctx.shdr) {
|
||||
auto find_text = [&]() -> Chunk<E> * {
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (chunk->name == ".text")
|
||||
return chunk;
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
if (Chunk<E> *text = find_text()) {
|
||||
exidx->shdr.sh_link = text->shndx;
|
||||
ctx.shdr->copy_buf(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+237
-225
@@ -1,3 +1,22 @@
|
||||
// This file contains ARM64-specific code. Being new, the ARM64's ELF
|
||||
// psABI doesn't have anything peculiar. ARM64 is a clean RISC
|
||||
// instruction set that supports PC-relative load/store instructions.
|
||||
//
|
||||
// Unlike ARM32, instructions length doesn't vary. All ARM64
|
||||
// instructions are 4 bytes long.
|
||||
//
|
||||
// Branch instructions used for function call can jump within ±128 MiB.
|
||||
// We need to create range extension thunks to support binaries whose
|
||||
// .text is larger than that.
|
||||
//
|
||||
// Unlike most other targets, the TLSDESC access model is used by default
|
||||
// for -fPIC to access thread-local variables instead of the less
|
||||
// efficient GD model. You can still enable GD but it needs the
|
||||
// -mtls-dialect=trad flag. Since GD is used rarely, we don't need to
|
||||
// implement GD → LE relaxation.
|
||||
//
|
||||
// https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
@@ -5,93 +24,81 @@ namespace mold::elf {
|
||||
using E = ARM64;
|
||||
|
||||
static void write_adrp(u8 *buf, u64 val) {
|
||||
u32 hi = bits(val, 32, 14);
|
||||
u32 lo = bits(val, 13, 12);
|
||||
u32 op = *(ul32 *)buf & 0b1001'1111'0000'0000'0000'0000'0001'1111;
|
||||
*(ul32 *)buf = (lo << 29) | (hi << 5) | op;
|
||||
*(ul32 *)buf |= (bits(val, 13, 12) << 29) | (bits(val, 32, 14) << 5);
|
||||
}
|
||||
|
||||
static void write_adr(u8 *buf, u64 val) {
|
||||
u32 hi = bits(val, 20, 2);
|
||||
u32 lo = bits(val, 1, 0);
|
||||
u32 op = *(ul32 *)buf & 0b1001'1111'0000'0000'0000'0000'0001'1111;
|
||||
*(ul32 *)buf = (lo << 29) | (hi << 5) | op;
|
||||
*(ul32 *)buf |= (bits(val, 1, 0) << 29) | (bits(val, 20, 2) << 5);
|
||||
}
|
||||
|
||||
static void write_movn_movz(u8 *buf, i64 val) {
|
||||
*(ul32 *)buf &= 0b0000'0000'0110'0000'0000'0000'0001'1111;
|
||||
|
||||
if (val >= 0)
|
||||
*(ul32 *)buf |= 0xd280'0000 | (bits(val, 15, 0) << 5); // rewrite to movz
|
||||
else
|
||||
*(ul32 *)buf |= 0x9280'0000 | (bits(~val, 15, 0) << 5); // rewrite to movn
|
||||
}
|
||||
|
||||
static u64 page(u64 val) {
|
||||
return val & ~(u64)0xfff;
|
||||
return val & 0xffff'ffff'ffff'f000;
|
||||
}
|
||||
|
||||
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
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn[] = {
|
||||
0xa9bf'7bf0, // stp x16, x30, [sp,#-16]!
|
||||
0x9000'0010, // adrp x16, .got.plt[2]
|
||||
0xf940'0211, // ldr x17, [x16, .got.plt[2]]
|
||||
0x9100'0210, // add x16, x16, .got.plt[2]
|
||||
0xd61f'0220, // br x17
|
||||
0xd503'201f, // nop
|
||||
0xd503'201f, // nop
|
||||
0xd503'201f, // nop
|
||||
};
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr + 16;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_adrp(buf + 4, page(gotplt) - page(plt + 4));
|
||||
*(ul32 *)(buf + 8) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(buf + 12) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u8 *ent = buf + E::plt_hdr_size + sym.get_plt_idx(ctx) * E::plt_size;
|
||||
|
||||
static const u32 data[] = {
|
||||
0x90000010, // adrp x16, .got.plt[n]
|
||||
0xf9400211, // ldr x17, [x16, .got.plt[n]]
|
||||
0x91000210, // add x16, x16, .got.plt[n]
|
||||
0xd61f0220, // br x17
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const ul32 insn[] = {
|
||||
0x9000'0010, // adrp x16, .got.plt[n]
|
||||
0xf940'0211, // ldr x17, [x16, .got.plt[n]]
|
||||
0x9100'0210, // add x16, x16, .got.plt[n]
|
||||
0xd61f'0220, // br x17
|
||||
};
|
||||
|
||||
u64 gotplt = sym.get_gotplt_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_adrp(ent, page(gotplt) - page(plt));
|
||||
*(ul32 *)(ent + 4) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(ent + 8) |= (gotplt & 0xfff) << 10;
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_adrp(buf, page(gotplt) - page(plt));
|
||||
*(ul32 *)(buf + 4) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(buf + 8) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void 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);
|
||||
}
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const ul32 insn[] = {
|
||||
0x9000'0010, // adrp x16, GOT[n]
|
||||
0xf940'0211, // ldr x17, [x16, GOT[n]]
|
||||
0xd61f'0220, // br x17
|
||||
0xd503'201f, // nop
|
||||
};
|
||||
|
||||
template <>
|
||||
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
u64 got = sym.get_got_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
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_adrp(ent, page(got) - page(plt));
|
||||
*(ul32 *)(ent + 4) |= bits(got, 11, 3) << 10;
|
||||
}
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_adrp(buf, page(got) - page(plt));
|
||||
*(ul32 *)(buf + 4) |= bits(got, 11, 3) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -100,236 +107,258 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_AARCH64_ABS64:
|
||||
*(ul64 *)loc = val;
|
||||
return;
|
||||
break;
|
||||
case R_AARCH64_PREL32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
break;
|
||||
case R_AARCH64_PREL64:
|
||||
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
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<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
i64 frag_idx = 0;
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_AARCH64_NONE)
|
||||
if (rel.r_type == R_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) {
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? (u64)frag_ref->addend : (u64)rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
apply_abs_dyn_rel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
continue;
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
continue;
|
||||
break;
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 1) << 10;
|
||||
continue;
|
||||
break;
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 2) << 10;
|
||||
continue;
|
||||
break;
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 3) << 10;
|
||||
continue;
|
||||
break;
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 4) << 10;
|
||||
continue;
|
||||
break;
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G0: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul32 *)loc |= bits(val, 15, 0) << 5;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G1: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ul32 *)loc |= bits(val, 31, 16) << 5;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G2: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 48);
|
||||
*(ul32 *)loc |= bits(val, 47, 32) << 5;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
continue;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
*(ul32 *)loc |= bits(S + A, 63, 48) << 5;
|
||||
continue;
|
||||
break;
|
||||
case R_AARCH64_ADR_GOT_PAGE: {
|
||||
i64 val = page(G + GOT + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_PG_HI21: {
|
||||
i64 val = page(S + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_LO21: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 20), (i64)1 << 20);
|
||||
check(val, -(1LL << 20), 1LL << 20);
|
||||
write_adr(loc, val);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26: {
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(ul32 *)loc |= 1;
|
||||
continue;
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
break;
|
||||
}
|
||||
|
||||
i64 lo = -(1 << 27);
|
||||
i64 hi = 1 << 27;
|
||||
i64 val = S + A - P;
|
||||
|
||||
if (val < lo || hi <= val) {
|
||||
RangeExtensionRef ref = extra.range_extn[i];
|
||||
val = output_section->thunks[ref.thunk_idx]->get_addr(ref.sym_idx) + A - P;
|
||||
assert(lo <= val && val < hi);
|
||||
}
|
||||
|
||||
*(ul32 *)loc |= (val >> 2) & 0x3ffffff;
|
||||
continue;
|
||||
if (val < -(1 << 27) || (1 << 27) <= val)
|
||||
val = get_thunk_addr(i) + A - P;
|
||||
*(ul32 *)loc |= bits(val, 27, 2);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 20), (i64)1 << 20);
|
||||
check(val, -(1LL << 20), 1LL << 20);
|
||||
*(ul32 *)loc |= bits(val, 20, 2) << 5;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PREL16: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 15), (i64)1 << 15);
|
||||
check(val, -(1LL << 15), 1LL << 15);
|
||||
*(ul16 *)loc = val;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PREL32: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 32);
|
||||
check(val, -(1LL << 31), 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PREL64:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
continue;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
*(ul32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
|
||||
continue;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15: {
|
||||
i64 val = G + GOT + A - page(GOT);
|
||||
overflow_check(val, 0, 1 << 15);
|
||||
check(val, 0, 1 << 15);
|
||||
*(ul32 *)loc |= bits(val, 14, 3) << 10;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
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);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
continue;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
write_movn_movz(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
write_movn_movz(loc, val >> 16);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G2: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, -(1LL << 47), 1LL << 47);
|
||||
write_movn_movz(loc, val >> 32);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12: {
|
||||
i64 val = S + A - ctx.tls_begin + E::tls_tp_offset;
|
||||
overflow_check(val, 0, (i64)1 << 24);
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, 0, 1LL << 24);
|
||||
*(ul32 *)loc |= bits(val, 23, 12) << 10;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tls_begin + E::tls_tp_offset, 11, 0) << 10;
|
||||
continue;
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21: {
|
||||
i64 val = page(sym.get_tlsgd_addr(ctx) + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
*(ul32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21: {
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// adrp x0, 0 -> movz x0, #tls_ofset_hi, lsl #16
|
||||
i64 val = (S + A - ctx.tls_begin + E::tls_tp_offset);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
*(ul32 *)loc = 0xd2a00000 | (bits(val, 32, 16) << 5);
|
||||
} else {
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
} else {
|
||||
// adrp x0, 0 -> movz x0, #tls_ofset_hi, lsl #16
|
||||
i64 val = (S + A - ctx.tp_addr);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
*(ul32 *)loc = 0xd2a0'0000 | (bits(val, 32, 16) << 5);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
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 + E::tls_tp_offset) & 0xffff;
|
||||
*(ul32 *)loc = 0xf2800000 | (offset_lo << 5);
|
||||
} else {
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
|
||||
}
|
||||
continue;
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// add x0, x0, #0 -> nop
|
||||
*(ul32 *)loc = 0xd503201f;
|
||||
} else {
|
||||
// ldr x2, [x0] -> movk x0, #tls_ofset_lo
|
||||
u32 offset_lo = (S + A - ctx.tp_addr) & 0xffff;
|
||||
*(ul32 *)loc = 0xf280'0000 | (offset_lo << 5);
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
|
||||
} else {
|
||||
// add x0, x0, #0 -> nop
|
||||
*(ul32 *)loc = 0xd503'201f;
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// blr x2 -> nop
|
||||
*(ul32 *)loc = 0xd503201f;
|
||||
*(ul32 *)loc = 0xd503'201f;
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,7 +368,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_AARCH64_NONE)
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
@@ -350,34 +379,38 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 addend;
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? (u64)addend : (u64)rel.r_addend)
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
if (!frag) {
|
||||
if (std::optional<u64> val = get_tombstone(sym)) {
|
||||
*(ul64 *)loc = *val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*(ul64 *)loc = S + A;
|
||||
continue;
|
||||
case R_AARCH64_ABS32:
|
||||
*(ul32 *)loc = S + A;
|
||||
continue;
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(ul64 *)loc = S + A;
|
||||
break;
|
||||
case R_AARCH64_ABS32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +424,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_AARCH64_NONE)
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
@@ -401,38 +434,38 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC)
|
||||
sym.flags |= (NEEDS_GOT | NEEDS_PLT);
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
scan_abs_dyn_rel(ctx, sym, rel);
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_ADR_GOT_PAGE:
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_AARCH64_ADR_PREL_PG_HI21:
|
||||
scan_pcrel_rel(ctx, sym, rel);
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
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;
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
case R_AARCH64_ADR_PREL_LO21:
|
||||
@@ -453,6 +486,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_AARCH64_PREL16:
|
||||
case R_AARCH64_PREL32:
|
||||
case R_AARCH64_PREL64:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G2:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
@@ -465,40 +503,14 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// For range extension thunks
|
||||
template <>
|
||||
bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
|
||||
InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
// We pessimistically assume that PLT entries are unreacahble.
|
||||
if (sym.has_plt(ctx))
|
||||
return false;
|
||||
|
||||
// We create thunks with a pessimistic assumption that all
|
||||
// out-of-section relocations would be out-of-range.
|
||||
InputSection<E> *isec2 = sym.get_input_section();
|
||||
if (!isec2 || isec.output_section != isec2->output_section)
|
||||
return false;
|
||||
|
||||
if (isec2->offset == -1)
|
||||
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);
|
||||
}
|
||||
|
||||
template <>
|
||||
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 const ul32 data[] = {
|
||||
0x9000'0010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
|
||||
0x9100'0210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
|
||||
0xd61f'0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(data));
|
||||
|
||||
@@ -0,0 +1,506 @@
|
||||
// HP/PA (Hewlett-Packard Precision Architecture), also known as PA-RISC,
|
||||
// is a RISC ISA developed by HP in the '80s. It was replaced by Itanium
|
||||
// which was a joint project between Intel and HP in the early 2000s. No
|
||||
// new HP/PA processors have been created after 2005.
|
||||
//
|
||||
// Thread pointer (TP) is stored to the control register cr27, which is an
|
||||
// alias for tr3.
|
||||
//
|
||||
// $ltp (linkage table pointer) is $r19
|
||||
// $dp is $r27
|
||||
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = HPPA32;
|
||||
|
||||
static u64 RND(u64 x) { return (x + 0x1000) & ~(u64)0x1fff; }
|
||||
static u64 L(u64 x) { return x & 0xffff'f800; }
|
||||
static u64 R(u64 x) { return x & 0x0000'07ff; }
|
||||
static u64 LR(u64 x, u64 a) { return L(x + RND(a)); }
|
||||
static u64 RR(u64 x, u64 a) { return R(x + RND(a)) + a - RND(a); }
|
||||
|
||||
static u64 get_gp(Context<E> &ctx) {
|
||||
if (ctx.arg.pic)
|
||||
return ctx.gotplt->shdr.sh_addr;
|
||||
return ctx.extra.global->value;
|
||||
}
|
||||
|
||||
static u32 dis_assemble_17(u32 val) {
|
||||
return ((val & 0x10000) >> 16) |
|
||||
((val & 0x0f800) << 5) |
|
||||
((val & 0x00400) >> 8) |
|
||||
((val & 0x003ff) << 3);
|
||||
}
|
||||
|
||||
static u32 dis_assemble_21(u32 val) {
|
||||
return (bits(val, 6, 2) << 16) | (bits(val, 8, 7) << 14) |
|
||||
(bits(val, 1, 0) << 12) | (bits(val, 19, 9) << 1) |
|
||||
bit(val, 20);
|
||||
}
|
||||
|
||||
static u32 dis_low_sign_ext(u32 val, u32 len) {
|
||||
return (bits(val, len - 2, 0) << 1) | bit(val, len - 1);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_PARISC_PCREL32:
|
||||
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
case R_PARISC_SEGREL32:
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
u64 SB = 0;
|
||||
u64 GP = get_gp(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
#define S sym.get_addr(ctx)
|
||||
#define A get_addend(loc, rel)
|
||||
#define P (get_addr() + rel.r_offset)
|
||||
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PARISC_DIR32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_PARISC_DIR21L:
|
||||
*(ub32 *)loc |= dis_assemble_21(bits(LR(S, A), 31, 11));
|
||||
break;
|
||||
case R_PARISC_DIR14R:
|
||||
*(ub32 *)loc |= dis_low_sign_ext(RR(S, A), 14);
|
||||
break;
|
||||
case R_PARISC_PCREL32: {
|
||||
u64 addr = has_thunk(i) ? get_thunk_addr(i) : S;
|
||||
*(ub32 *)loc = addr + A - P - 8;
|
||||
break;
|
||||
}
|
||||
case R_PARISC_PCREL21L: {
|
||||
u64 addr = has_thunk(i) ? get_thunk_addr(i) : S;
|
||||
*(ub32 *)loc |= dis_assemble_21(bits(addr + A - P - 8, 31, 11));
|
||||
break;
|
||||
}
|
||||
case R_PARISC_PCREL17F: {
|
||||
u64 addr = has_thunk(i) ? get_thunk_addr(i) : S;
|
||||
*(ub32 *)loc |= dis_assemble_17((addr + A - P - 8) >> 2);
|
||||
break;
|
||||
}
|
||||
case R_PARISC_PCREL14R: {
|
||||
u64 addr = has_thunk(i) ? get_thunk_addr(i) : S;
|
||||
*(ub32 *)loc |= dis_low_sign_ext(R(addr - P - 8 + A), 14);
|
||||
break;
|
||||
}
|
||||
case R_PARISC_DPREL21L:
|
||||
*(ub32 *)loc |= dis_assemble_21(bits(LR(S - GP, A), 31, 11));
|
||||
break;
|
||||
case R_PARISC_DPREL14R:
|
||||
*(ub32 *)loc |= dis_low_sign_ext(RR(S - GP, A), 14);
|
||||
break;
|
||||
case R_PARISC_DLTIND21L:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |= dis_assemble_21(bits(G + GOT - GP, 31, 11));
|
||||
break;
|
||||
case R_PARISC_DLTIND14R:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |= dis_low_sign_ext(R(G + GOT - GP), 14);
|
||||
break;
|
||||
case R_PARISC_SEGREL32:
|
||||
*(ub32 *)loc = S + A - SB;
|
||||
break;
|
||||
case R_PARISC_PLABEL32:
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ub32 *)loc = 0;
|
||||
} else if (ctx.arg.pic) {
|
||||
if (sym.is_imported)
|
||||
*dynrel++ = ElfRel<E>(P, R_PARISC_PLABEL32, sym.get_dynsym_idx(ctx), 0);
|
||||
else
|
||||
*dynrel++ = ElfRel<E>(P, R_PARISC_PLABEL32, 0, sym.get_opd_addr(ctx) + 2);
|
||||
*(ub32 *)loc = 0;
|
||||
} else {
|
||||
*(ub32 *)loc = sym.get_opd_addr(ctx) + 2;
|
||||
}
|
||||
break;
|
||||
case R_PARISC_TPREL21L:
|
||||
*(ub32 *)loc |= dis_assemble_21(bits(LR(S - ctx.tp_addr, A), 31, 11));
|
||||
break;
|
||||
case R_PARISC_TPREL14R:
|
||||
*(ub32 *)loc |= dis_low_sign_ext(RR(S - ctx.tp_addr, A), 14);
|
||||
break;
|
||||
case R_PARISC_LTOFF_TP21L:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |=
|
||||
dis_assemble_21(bits(LR(sym.get_gottp_addr(ctx) - GP, A), 31, 11));
|
||||
break;
|
||||
case R_PARISC_LTOFF_TP14R:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |= dis_low_sign_ext(R(sym.get_gottp_addr(ctx) - GP), 14);
|
||||
break;
|
||||
case R_PARISC_TLS_GD21L:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |=
|
||||
dis_assemble_21(bits(LR(sym.get_tlsgd_addr(ctx) - GP, A), 31, 11));
|
||||
break;
|
||||
case R_PARISC_TLS_GD14R:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |= dis_low_sign_ext(R(sym.get_tlsgd_addr(ctx) - GP), 14);
|
||||
break;
|
||||
case R_PARISC_TLS_LDM21L:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |=
|
||||
dis_assemble_21(bits(LR(ctx.got->get_tlsld_addr(ctx) - GP, A), 31, 11));
|
||||
break;
|
||||
case R_PARISC_TLS_LDM14R:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |= dis_low_sign_ext(R(ctx.got->get_tlsld_addr(ctx) - GP), 14);
|
||||
break;
|
||||
case R_PARISC_TLS_LDO21L:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |= dis_assemble_21(bits(LR(S - ctx.dtp_addr, A), 31, 11));
|
||||
break;
|
||||
case R_PARISC_TLS_LDO14R:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |= dis_low_sign_ext(R(S - ctx.dtp_addr), 14);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? frag_addend : get_addend(loc, rel))
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PARISC_DIR32:
|
||||
case R_PARISC_SEGREL32:
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on PA-RISC";
|
||||
|
||||
if (sym.is_func()) {
|
||||
switch (rel.r_type) {
|
||||
case R_PARISC_PCREL32:
|
||||
case R_PARISC_PCREL21L:
|
||||
case R_PARISC_PCREL17F:
|
||||
case R_PARISC_PCREL14R:
|
||||
case R_PARISC_PLABEL32:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": " << rel
|
||||
<< " may not refer to a function symbol " << sym;
|
||||
}
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PARISC_DIR32:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PARISC_DIR21L:
|
||||
case R_PARISC_DIR14R:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PARISC_PCREL32:
|
||||
case R_PARISC_PCREL21L:
|
||||
case R_PARISC_PCREL17F:
|
||||
case R_PARISC_PCREL14R:
|
||||
case R_PARISC_DPREL21L:
|
||||
case R_PARISC_DPREL14R:
|
||||
if (sym.is_func() || sym.esym().is_undef_weak())
|
||||
sym.flags.fetch_or(NEEDS_OPD, std::memory_order_relaxed);
|
||||
else
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PARISC_DLTIND21L:
|
||||
case R_PARISC_DLTIND14R:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PARISC_PLABEL32:
|
||||
if (!sym.is_remaining_undef_weak()) {
|
||||
if (ctx.arg.pic)
|
||||
file.num_dynrel++;
|
||||
sym.flags.fetch_or(NEEDS_OPD, std::memory_order_relaxed);
|
||||
}
|
||||
break;
|
||||
case R_PARISC_LTOFF_TP21L:
|
||||
case R_PARISC_LTOFF_TP14R:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PARISC_TLS_GD21L:
|
||||
case R_PARISC_TLS_GD14R:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PARISC_TLS_LDM21L:
|
||||
case R_PARISC_TLS_LDM14R:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PARISC_SEGREL32:
|
||||
case R_PARISC_TPREL21L:
|
||||
case R_PARISC_TPREL14R:
|
||||
case R_PARISC_TLS_LDO21L:
|
||||
case R_PARISC_TLS_LDO14R:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 gp = get_gp(ctx);
|
||||
|
||||
static const ub32 opd_pic_entry[] = {
|
||||
0x2a60'0000, // addil LR'<OFFSET>, r19, r1
|
||||
0x3436'0000, // ldo RR'<OFFSET>(r1), r22
|
||||
0x0ec0'1095, // ldw 0(r22), r21
|
||||
0xeaa0'c000, // bv r0(r21)
|
||||
0x0ec8'1093, // ldw 4(r22), r19
|
||||
};
|
||||
|
||||
static const ub32 opd_nopic_entry[] = {
|
||||
0x2b60'0000, // addil LR'<OFFSET>, dp, r1
|
||||
0x3436'0000, // ldo RR'<OFFSET>(r1), r22
|
||||
0x0ec0'1095, // ldw 0(r22), r21
|
||||
0xeaa0'c000, // bv r0(r21)
|
||||
0x0ec8'1093, // ldw 4(r22), r19
|
||||
};
|
||||
|
||||
static const ub32 local_pic_entry[] = {
|
||||
0x2a60'0000, // addil L%0, r19, r1
|
||||
0x3435'0000, // ldo 0(r1), r21
|
||||
0xeaa0'c000, // bv r0(r21)
|
||||
0x0800'0240, // nop
|
||||
0x0800'0240, // nop
|
||||
};
|
||||
|
||||
static const ub32 local_nopic_entry[] = {
|
||||
0x2b60'0000, // addil L%0, dp, r1
|
||||
0x3435'0000, // ldo 0(r1), r21
|
||||
0xeaa0'c000, // bv r0(r21)
|
||||
0x0800'0240, // nop
|
||||
0x0800'0240, // nop
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(opd_pic_entry));
|
||||
static_assert(E::thunk_size == sizeof(opd_nopic_entry));
|
||||
static_assert(E::thunk_size == sizeof(local_pic_entry));
|
||||
static_assert(E::thunk_size == sizeof(local_nopic_entry));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
i64 val;
|
||||
|
||||
if (sym.has_opd(ctx)) {
|
||||
val = sym.get_opd_addr(ctx) - gp;
|
||||
if (ctx.arg.pic)
|
||||
memcpy(loc, opd_pic_entry, E::thunk_size);
|
||||
else
|
||||
memcpy(loc, opd_nopic_entry, E::thunk_size);
|
||||
} else {
|
||||
assert(!sym.is_imported);
|
||||
val = sym.get_addr(ctx) - gp;
|
||||
if (ctx.arg.pic)
|
||||
memcpy(loc, local_pic_entry, E::thunk_size);
|
||||
else
|
||||
memcpy(loc, local_nopic_entry, E::thunk_size);
|
||||
}
|
||||
|
||||
loc[0] |= dis_assemble_21(bits(LR(val, 0), 31, 11));
|
||||
loc[1] |= dis_low_sign_ext(RR(val, 0), 14);
|
||||
}
|
||||
}
|
||||
|
||||
void HppaOpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
sym->set_opd_idx(ctx, symbols.size());
|
||||
symbols.push_back(sym);
|
||||
}
|
||||
|
||||
void HppaOpdSection::update_shdr(Context<E> &ctx) {
|
||||
if (!symbols.empty())
|
||||
this->shdr.sh_size = symbols.size() * ENTRY_SIZE + TRAILER_SIZE;
|
||||
this->shdr.sh_link = ctx.extra.opd->shndx;
|
||||
}
|
||||
|
||||
void HppaOpdSection::copy_buf(Context<E> &ctx) {
|
||||
ub32 *buf = (ub32 *)(ctx.buf + this->shdr.sh_offset);
|
||||
u64 gp = get_gp(ctx);
|
||||
|
||||
memset(buf, 0, this->shdr.sh_size);
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
if (!ctx.arg.pic && !sym.is_imported) {
|
||||
buf[i * 2] = sym.get_addr(ctx, NO_OPD);
|
||||
buf[i * 2 + 1] = gp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HppaRelOpdSection::update_shdr(Context<E> &ctx) {
|
||||
this->shdr.sh_link = ctx.extra.opd->shndx;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
this->shdr.sh_size = ctx.extra.opd->symbols.size() * sizeof(ElfRel<E>);
|
||||
} else {
|
||||
this->shdr.sh_size = 0;
|
||||
for (Symbol<E> *sym : ctx.extra.opd->symbols)
|
||||
if (sym->is_imported)
|
||||
this->shdr.sh_size += sizeof(ElfRel<E>);
|
||||
}
|
||||
}
|
||||
|
||||
void HppaRelOpdSection::copy_buf(Context<E> &ctx) {
|
||||
ElfRel<E> *rel = (ElfRel<E> *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
for (Symbol<E> *sym : ctx.extra.opd->symbols) {
|
||||
if (ctx.arg.pic || sym->is_imported) {
|
||||
memset(rel, 0, sizeof(*rel));
|
||||
rel->r_offset = sym->get_opd_addr(ctx);
|
||||
rel->r_type = R_PARISC_IPLT;
|
||||
if (sym->is_imported)
|
||||
rel->r_sym = sym->get_dynsym_idx(ctx);
|
||||
else
|
||||
rel->r_addend = sym->get_addr(ctx, NO_OPD);
|
||||
rel++;
|
||||
}
|
||||
}
|
||||
|
||||
// This code stub must immediately follow the OPD element that the last
|
||||
// .rela.plt relocation refers to, because that's how the runtime finds
|
||||
// this piece of code on process startup.
|
||||
//
|
||||
// The code stub loads the _dl_runtime_resolve address to $r20 call the
|
||||
// function with its GP in $r19. The magic bytes, 0xc0ffee and 0xdeadbeef,
|
||||
// are used by the runtime to identify this code stub, so they must be
|
||||
// exactly that bytes.
|
||||
//
|
||||
// HPPA's runtime assumes that this stub is immediately followed by .got.
|
||||
// The files created by mold don't actually satisfy that constraint
|
||||
// because we don't intermix executable code and data. So, instead, we
|
||||
// reserve three padding words after the code stub and set
|
||||
// _GLOBAL_OFFSET_TABLE_ there as if that particular place is the
|
||||
// beginning of a .got. This trick works because the runtime only uses
|
||||
// _GLOBAL_OFFSET_TABLE_ to access GOT[1],
|
||||
//
|
||||
// Due to the existence of this code stub, .opd must be readable,
|
||||
// writable and executable. Writable executable segment is bad from the
|
||||
// security standpoint, but we have no choice other than doing it.
|
||||
//
|
||||
// https://sourceware.org/git/?p=glibc.git;f=sysdeps/hppa/dl-machine.h;h=1d5194856601e025c#l223
|
||||
ub32 insn[] = {
|
||||
0x0e80'1095, // 1: ldw 0(r20), r21
|
||||
0xeaa0'c000, // bv r0(r21)
|
||||
0x0e88'1095, // ldw 4(r20), r21
|
||||
0xea9f'1fdd, // b,l 1b, r20
|
||||
0xd680'1c1e, // depwi 0, 31, 2, r20
|
||||
0x00c0'ffee, // (_dl_runtime_resolve's address)
|
||||
0xdead'beef, // (%r19 for _dl_runtime_resolve)
|
||||
0x0000'0000, // (_GLOBAL_OFFSET_TABLE_ is set to here)
|
||||
0x0000'0000, // (runtime uses this word to identify this ELF module)
|
||||
0x0000'0000, // (padding)
|
||||
};
|
||||
|
||||
memcpy(ctx.buf + this->shdr.sh_offset +
|
||||
ctx.extra.opd->symbols.size() * sizeof(ElfRel<E>),
|
||||
insn, sizeof(insn));
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+306
-212
@@ -1,96 +1,179 @@
|
||||
// i386 is similar to x86-64 but lacks PC-relative memory access
|
||||
// instructions. So it's not straightforward to support position-
|
||||
// independent code (PIC) on that target.
|
||||
//
|
||||
// If an object file is compiled with -fPIC, a function that needs to load
|
||||
// a value from memory first obtains its own address with the following
|
||||
// code
|
||||
//
|
||||
// call __x86.get_pc_thunk.bx
|
||||
//
|
||||
// where __x86.get_pc_thunk.bx is defined as
|
||||
//
|
||||
// __x86.get_pc_thunk.bx:
|
||||
// mov (%esp), %ebx # move the return address to %ebx
|
||||
// ret
|
||||
//
|
||||
// . With the function's own address (or, more precisely, the address
|
||||
// immediately after the call instruction), the function can compute an
|
||||
// absolute address of a variable with its address + link-time constant.
|
||||
//
|
||||
// Executing call-mov-ret isn't very cheap, and allocating one register to
|
||||
// store PC isn't cheap too, especially given that i386 has only 8
|
||||
// general-purpose registers. But that's the cost of PIC on i386. You need
|
||||
// to pay it when creating a .so and a position-independent executable.
|
||||
//
|
||||
// When a position-independent function calls another function, it sets
|
||||
// %ebx to the address of .got. Position-independent PLT entries use that
|
||||
// register to load values from .got.plt/.got.
|
||||
//
|
||||
// If we are creating a position-dependent executable (PDE), we can't
|
||||
// assume that %ebx is set to .got. For PDE, we need to create position-
|
||||
// dependent PLT entries which don't use %ebx.
|
||||
//
|
||||
// https://github.com/rui314/mold/wiki/i386-psabi.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = I386;
|
||||
|
||||
// Emitting position-independent code (PIC) for i386 is a bit tricky
|
||||
// because i386 doesn't support PC-relative memory access instructions.
|
||||
// By default, i386 executables are not PIC. If PIC, %ebx is used to
|
||||
// store the location of .got and access all data with offsets from .got.
|
||||
static void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 plt0[] = {
|
||||
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));
|
||||
*(ul32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
|
||||
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 8;
|
||||
} else {
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0x35, 0, 0, 0, 0, // pushl GOTPLT+4
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+8
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(ul32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr + 8;
|
||||
template <>
|
||||
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_NONE:
|
||||
return 0;
|
||||
case R_386_8:
|
||||
case R_386_PC8:
|
||||
return *loc;
|
||||
case R_386_16:
|
||||
case R_386_PC16:
|
||||
return *(ul16 *)loc;
|
||||
case R_386_32:
|
||||
case R_386_PC32:
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
case R_386_PLT32:
|
||||
case R_386_GOTOFF:
|
||||
case R_386_GOTPC:
|
||||
case R_386_TLS_LDM:
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_IE:
|
||||
case R_386_TLS_GD:
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_GOTDESC:
|
||||
return *(ul32 *)loc;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_NONE:
|
||||
break;
|
||||
case R_386_8:
|
||||
case R_386_PC8:
|
||||
*loc = val;
|
||||
break;
|
||||
case R_386_16:
|
||||
case R_386_PC16:
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
case R_386_32:
|
||||
case R_386_PC32:
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
case R_386_PLT32:
|
||||
case R_386_GOTOFF:
|
||||
case R_386_GOTPC:
|
||||
case R_386_TLS_LDM:
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_IE:
|
||||
case R_386_TLS_GD:
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_GOTDESC:
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym,
|
||||
i64 idx) {
|
||||
u8 *ent = buf + E::plt_hdr_size + sym.get_plt_idx(ctx) * E::plt_size;
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 data[] = {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0x51, // push %ecx
|
||||
0x8d, 0x8b, 0, 0, 0, 0, // lea GOTPLT+4(%ebx), %ecx
|
||||
0xff, 0x31, // push (%ecx)
|
||||
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 7) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0x51, // push %ecx
|
||||
0xb9, 0, 0, 0, 0, // mov GOTPLT+4, %ecx
|
||||
0xff, 0x31, // push (%ecx)
|
||||
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
|
||||
0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
|
||||
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
|
||||
0xcc, // (padding)
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(ul32 *)(ent + 2) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x68, 0, 0, 0, 0, // pushl $reloc_offset
|
||||
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
|
||||
0xcc, // (padding)
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(ul32 *)(ent + 2) = sym.get_gotplt_addr(ctx);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx);
|
||||
}
|
||||
|
||||
*(ul32 *)(ent + 7) = idx * sizeof(ElfRel<E>);
|
||||
*(ul32 *)(ent + 12) = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 16;
|
||||
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 data[] = {
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0x66, 0x90, // nop
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(ul32 *)(ent + 2) = symbols[i]->get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
}
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.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
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(ul32 *)(ent + 2) = symbols[i]->get_got_addr(ctx);
|
||||
}
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,124 +183,141 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_NONE:
|
||||
return;
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_386_32:
|
||||
*(ul32 *)loc = val;
|
||||
return;
|
||||
break;
|
||||
case R_386_PC32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
static u32 relax_got32x(u8 *loc) {
|
||||
// mov imm(%reg1), %reg2 -> lea imm(%reg1), %reg2
|
||||
if (loc[0] == 0x8b)
|
||||
return 0x8d00 | loc[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_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)
|
||||
if (rel.r_type == R_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) {
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
#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_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = get_addend(*this, rel);
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8: {
|
||||
i64 val = S + A;
|
||||
overflow_check(val, 0, 1 << 8);
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_386_16: {
|
||||
i64 val = S + A;
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_386_32:
|
||||
apply_abs_dyn_rel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
continue;
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_386_PC8: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -(1 << 7), 1 << 7);
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
continue;
|
||||
};
|
||||
break;
|
||||
}
|
||||
case R_386_PC16: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -(1 << 15), 1 << 15);
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_386_PC32:
|
||||
case R_386_PLT32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
continue;
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
*(ul32 *)loc = G + A;
|
||||
continue;
|
||||
break;
|
||||
case R_386_GOT32X:
|
||||
if (sym.has_got(ctx)) {
|
||||
*(ul32 *)loc = G + A;
|
||||
} else {
|
||||
u32 insn = relax_got32x(loc - 2);
|
||||
assert(insn);
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
}
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
continue;
|
||||
break;
|
||||
case R_386_GOTPC:
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
continue;
|
||||
break;
|
||||
case R_386_TLS_GOTIE:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
continue;
|
||||
break;
|
||||
case R_386_TLS_LE:
|
||||
*(ul32 *)loc = S + A - ctx.tls_end;
|
||||
continue;
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_386_TLS_IE:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A;
|
||||
continue;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
if (sym.get_tlsgd_idx(ctx) == -1) {
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
// Relax GD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_386_PLT32: {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %rax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // add $0, %rax
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $val, %eax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = ctx.tls_end - S - A;
|
||||
*(ul32 *)(loc + 5) = ctx.tp_addr - S - A;
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32: {
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %rax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // add $0, %rax
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $val, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 6) = ctx.tls_end - S - A;
|
||||
*(ul32 *)(loc + 6) = ctx.tp_addr - S - A;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -225,77 +325,70 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
}
|
||||
|
||||
i++;
|
||||
} else {
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
if (ctx.got->tlsld_idx == -1) {
|
||||
if (ctx.got->has_tlsld(ctx)) {
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
// Relax LD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_386_PLT32: {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x8d, 0x74, 0x26, 0x00, // lea (%esi,1), %esi
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x65, 0x8b, 0x00, // mov %gs:(%eax), %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X: {
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x65, 0x8b, 0x00, // mov %gs:(%eax), %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
*(ul32 *)(loc + 5) = ctx.tp_addr - ctx.tls_begin;
|
||||
i++;
|
||||
} else {
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
if (ctx.got->tlsld_idx == -1)
|
||||
*(ul32 *)loc = S + A - ctx.tls_end;
|
||||
else
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin;
|
||||
continue;
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
continue;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x8d, 0x05, 0, 0, 0, 0, // lea 0, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)loc = S + A - ctx.tls_end;
|
||||
} else {
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
case R_386_TLS_DESC_CALL:
|
||||
if (ctx.arg.relax && !ctx.arg.shared) {
|
||||
// call *(%rax) -> nop
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// call *(%eax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,7 +398,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_386_NONE)
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
@@ -316,81 +409,73 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 addend;
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : this->get_addend(rel))
|
||||
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : get_addend(*this, rel);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8: {
|
||||
i64 val = S + A;
|
||||
overflow_check(val, 0, 1 << 8);
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_386_16: {
|
||||
i64 val = S + A;
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_386_32:
|
||||
if (!frag) {
|
||||
if (std::optional<u64> val = get_tombstone(sym)) {
|
||||
*(ul32 *)loc = *val;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
*(ul32 *)loc = S + A;
|
||||
continue;
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_386_PC8: {
|
||||
i64 val = S + A;
|
||||
overflow_check(val, -(1 << 7), 1 << 7);
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case R_386_PC16: {
|
||||
i64 val = S + A;
|
||||
overflow_check(val, -(1 << 15), 1 << 15);
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
continue;
|
||||
};
|
||||
break;
|
||||
}
|
||||
case R_386_PC32:
|
||||
*(ul32 *)loc = S + A;
|
||||
continue;
|
||||
break;
|
||||
case R_386_GOTPC:
|
||||
*(ul32 *)loc = GOT + A;
|
||||
continue;
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
continue;
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin;
|
||||
continue;
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
continue;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,75 +489,84 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_386_NONE)
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC)
|
||||
sym.flags |= (NEEDS_GOT | NEEDS_PLT);
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_16:
|
||||
scan_abs_rel(ctx, sym, rel);
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_32:
|
||||
scan_abs_dyn_rel(ctx, sym, rel);
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_PC8:
|
||||
case R_386_PC16:
|
||||
case R_386_PC32:
|
||||
scan_pcrel_rel(ctx, sym, rel);
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
case R_386_GOTPC:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_386_GOT32X: {
|
||||
bool do_relax = ctx.arg.relax && !sym.is_imported &&
|
||||
sym.is_relative() && relax_got32x(loc - 2);
|
||||
if (!do_relax)
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
case R_386_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_IE:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
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)
|
||||
ty != R_386_PLT32 && ty != R_386_PC32 &&
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported)
|
||||
if (relax_tlsgd(ctx, sym))
|
||||
i++;
|
||||
else
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
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)
|
||||
ty != R_386_PLT32 && ty != R_386_PC32 &&
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
if (relax_tlsld(ctx))
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld = true;
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
if (!ctx.arg.relax || ctx.arg.shared)
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
case R_386_TLS_LDO_32:
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
// This file contains code for the Motorola 68000 series microprocessors,
|
||||
// which is often abbreviated as m68k. Running a Unix-like system on a
|
||||
// m68k-based machine today is probably a retro-computing hobby activity,
|
||||
// but the processor was a popular choice to build Unix computers during
|
||||
// '80s. Early Sun workstations for example used m68k. Macintosh until
|
||||
// 1994 were based on m68k as well until they switched to PowerPC (and
|
||||
// then to x86 and to ARM.)
|
||||
//
|
||||
// From the linker's point of view, it is not hard to support m68k. It's
|
||||
// just a 32-bit big-endian CISC ISA. Compared to comtemporary i386,
|
||||
// m68k's psABI is actually simpler because m68k has PC-relative memory
|
||||
// access instructions and therefore can support position-independent
|
||||
// code without too much hassle.
|
||||
//
|
||||
// https://github.com/rui314/mold/wiki/m68k-psabi.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = M68K;
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const u8 insn[] = {
|
||||
0x2f, 0x00, // move.l %d0, -(%sp)
|
||||
0x2f, 0x3b, 0x01, 0x70, 0, 0, 0, 0, // move.l (GOTPLT+4, %pc), -(%sp)
|
||||
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOTPLT+8, %pc])
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 6) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr;
|
||||
*(ub32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 4;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0x20, 0x3c, 0, 0, 0, 0, // move.l PLT_OFFSET, %d0
|
||||
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOTPLT_ENTRY, %pc])
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 2) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ub32 *)(buf + 10) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOT_ENTRY, %pc])
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 4) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 2;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_68K_32:
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
case R_68K_PC32:
|
||||
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
check(val, 0, 1 << 16);
|
||||
*(ub16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write16s = [&](u64 val) {
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write8 = [&](u64 val) {
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write8s = [&](u64 val) {
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_68K_16:
|
||||
write16(S + A);
|
||||
break;
|
||||
case R_68K_8:
|
||||
write8(S + A);
|
||||
break;
|
||||
case R_68K_PC32:
|
||||
case R_68K_PLT32:
|
||||
*(ub32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_68K_PC16:
|
||||
case R_68K_PLT16:
|
||||
write16s(S + A - P);
|
||||
break;
|
||||
case R_68K_PC8:
|
||||
case R_68K_PLT8:
|
||||
write8s(S + A - P);
|
||||
break;
|
||||
case R_68K_GOTPCREL32:
|
||||
*(ub32 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_68K_GOTPCREL16:
|
||||
write16s(GOT + A - P);
|
||||
break;
|
||||
case R_68K_GOTPCREL8:
|
||||
write8s(GOT + A - P);
|
||||
break;
|
||||
case R_68K_GOTOFF32:
|
||||
*(ub32 *)loc = G + A;
|
||||
break;
|
||||
case R_68K_GOTOFF16:
|
||||
write16(G + A);
|
||||
break;
|
||||
case R_68K_GOTOFF8:
|
||||
write8(G + A);
|
||||
break;
|
||||
case R_68K_TLS_GD32:
|
||||
*(ub32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_68K_TLS_GD16:
|
||||
write16(sym.get_tlsgd_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_GD8:
|
||||
write8(sym.get_tlsgd_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LDM32:
|
||||
*(ub32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_68K_TLS_LDM16:
|
||||
write16(ctx.got->get_tlsld_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LDM8:
|
||||
write8(ctx.got->get_tlsld_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LDO32:
|
||||
*(ub32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_68K_TLS_LDO16:
|
||||
write16s(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_68K_TLS_LDO8:
|
||||
write8s(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_68K_TLS_IE32:
|
||||
*(ub32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_68K_TLS_IE16:
|
||||
write16(sym.get_gottp_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_IE8:
|
||||
write8(sym.get_gottp_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LE32:
|
||||
*(ub32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_68K_TLS_LE16:
|
||||
write16(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_68K_TLS_LE8:
|
||||
write8(S + A - ctx.tp_addr);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ub32 *)loc = *val;
|
||||
else
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on m68k";
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_32:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_16:
|
||||
case R_68K_8:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_PC32:
|
||||
case R_68K_PC16:
|
||||
case R_68K_PC8:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_GOTPCREL32:
|
||||
case R_68K_GOTPCREL16:
|
||||
case R_68K_GOTPCREL8:
|
||||
case R_68K_GOTOFF32:
|
||||
case R_68K_GOTOFF16:
|
||||
case R_68K_GOTOFF8:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_68K_PLT32:
|
||||
case R_68K_PLT16:
|
||||
case R_68K_PLT8:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_68K_TLS_GD32:
|
||||
case R_68K_TLS_GD16:
|
||||
case R_68K_TLS_GD8:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_68K_TLS_LDM32:
|
||||
case R_68K_TLS_LDM16:
|
||||
case R_68K_TLS_LDM8:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_68K_TLS_IE32:
|
||||
case R_68K_TLS_IE16:
|
||||
case R_68K_TLS_IE8:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_68K_TLS_LDO32:
|
||||
case R_68K_TLS_LDO16:
|
||||
case R_68K_TLS_LDO8:
|
||||
case R_68K_TLS_LE32:
|
||||
case R_68K_TLS_LE16:
|
||||
case R_68K_TLS_LE8:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,465 @@
|
||||
// This file implements the PowerPC 32-bit ISA. For 64-bit PowerPC, see
|
||||
// arch-ppc64v1.cpp and arch-ppc64v2.cpp.
|
||||
//
|
||||
// PPC32 is a RISC ISA. It has 32 general-purpose registers (GPRs).
|
||||
// r0, r11 and r12 are reserved for static linkers, so we can use these
|
||||
// registers in PLTs and range extension thunks. In addition to that, it
|
||||
// has a few special registers. Notable ones are LR which holds a return
|
||||
// address and CTR which we can use to store a branch target address.
|
||||
//
|
||||
// It feels that the PPC32 psABI is unnecessarily complicated at first
|
||||
// glance, but that is mainly stemmed from the fact that the ISA lacks
|
||||
// PC-relative load/store instructions. Since machine instructions cannot
|
||||
// load data relative to its own address, it is not straightforward to
|
||||
// support position-independent code (PIC) on PPC32.
|
||||
//
|
||||
// A position-independent function typically contains the following code
|
||||
// in the prologue to obtain its own address:
|
||||
//
|
||||
// mflr r0 // save the current return address to %r0
|
||||
// bcl 20, 31, 4 // call the next instruction as if it were a function
|
||||
// mtlr r12 // save the return address to %r12
|
||||
// mtlr r0 // restore the original return address
|
||||
//
|
||||
// An object file compiled with -fPIC contains a data section named
|
||||
// `.got2` to store addresses of locally-defined global variables and
|
||||
// constants. A PIC function usually computes its .got2+0x8000 and set it
|
||||
// to %r30. This scheme allows the function to access global objects
|
||||
// defined in the same input file with a single %r30-relative load/store
|
||||
// instruction with a 16-bit offset, given that .got2 is smaller than
|
||||
// 0x10000 (or 65536) bytes.
|
||||
//
|
||||
// Since each object file has its own .got2, %r30 refers to different
|
||||
// places in a merged .got2 for two functions that are came from different
|
||||
// input files. Therefore, %r30 makes sense only within a single function.
|
||||
//
|
||||
// Technically, we can reuse a %r30 value in our PLT if we create a PLT
|
||||
// _for each input file_ (that's what GNU ld seems to be doing), but that
|
||||
// doesn't seems to be worth its complexity. Our PLT simply doesn't rely
|
||||
// on a %r30 value.
|
||||
//
|
||||
// https://github.com/rui314/mold/wiki/ppc32-psabi.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = PPC32;
|
||||
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
|
||||
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
|
||||
static u64 highest(u64 x) { return x >> 48; }
|
||||
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ub32 insn[] = {
|
||||
// Get the address of this PLT section
|
||||
0x7c08'02a6, // mflr r0
|
||||
0x429f'0005, // bcl 20, 31, 4
|
||||
0x7d88'02a6, // 1: mflr r12
|
||||
0x7c08'03a6, // mtlr r0
|
||||
|
||||
// Compute the runtime address of GOTPLT+12
|
||||
0x3d8c'0000, // addis r12, r12, (GOTPLT - 1b)@higha
|
||||
0x398c'0000, // addi r12, r12, (GOTPLT - 1b)@lo
|
||||
|
||||
// Compute the PLT entry offset
|
||||
0x7d6c'5850, // sub r11, r11, r12
|
||||
0x1d6b'0003, // mulli r11, r11, 3
|
||||
|
||||
// Load GOTPLT[2] and branch to GOTPLT[1]
|
||||
0x800c'fff8, // lwz r0, -8(r12)
|
||||
0x7c09'03a6, // mtctr r0
|
||||
0x818c'fffc, // lwz r12, -4(r12)
|
||||
0x4e80'0420, // bctr
|
||||
0x6000'0000, // nop
|
||||
0x6000'0000, // nop
|
||||
0x6000'0000, // nop
|
||||
0x6000'0000, // nop
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::plt_hdr_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
|
||||
ub32 *loc = (ub32 *)buf;
|
||||
loc[4] |= higha(ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4);
|
||||
loc[5] |= lo(ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4);
|
||||
}
|
||||
|
||||
static const ub32 plt_entry[] = {
|
||||
// Get the address of this PLT entry
|
||||
0x7c08'02a6, // mflr r0
|
||||
0x429f'0005, // bcl 20, 31, 4
|
||||
0x7d88'02a6, // mflr r12
|
||||
0x7c08'03a6, // mtlr r0
|
||||
|
||||
// Load an address from the GOT/GOTPLT entry and jump to that address
|
||||
0x3d6c'0000, // addis r11, r12, OFFSET@higha
|
||||
0x396b'0000, // addi r11, r11, OFFSET@lo
|
||||
0x818b'0000, // lwz r12, 0(r11)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static_assert(E::plt_size == sizeof(plt_entry));
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
|
||||
ub32 *loc = (ub32 *)buf;
|
||||
i64 offset = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 8;
|
||||
loc[4] |= higha(offset);
|
||||
loc[5] |= lo(offset);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static_assert(E::pltgot_size == sizeof(plt_entry));
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
|
||||
ub32 *loc = (ub32 *)buf;
|
||||
i64 offset = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 8;
|
||||
loc[4] |= higha(offset);
|
||||
loc[5] |= lo(offset);
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_PPC_ADDR32:
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
case R_PPC_REL32:
|
||||
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
u64 got2 = 0;
|
||||
if (file.ppc32_got2)
|
||||
got2 = file.ppc32_got2->output_section->shdr.sh_addr + file.ppc32_got2->offset;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC_ADDR32:
|
||||
case R_PPC_UADDR32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_PPC_ADDR14:
|
||||
*(ub32 *)loc |= bits(S + A, 15, 2) << 2;
|
||||
break;
|
||||
case R_PPC_ADDR16:
|
||||
case R_PPC_UADDR16:
|
||||
case R_PPC_ADDR16_LO:
|
||||
*(ub16 *)loc = lo(S + A);
|
||||
break;
|
||||
case R_PPC_ADDR16_HI:
|
||||
*(ub16 *)loc = hi(S + A);
|
||||
break;
|
||||
case R_PPC_ADDR16_HA:
|
||||
*(ub16 *)loc = ha(S + A);
|
||||
break;
|
||||
case R_PPC_ADDR24:
|
||||
*(ub32 *)loc |= bits(S + A, 25, 2) << 2;
|
||||
break;
|
||||
case R_PPC_ADDR30:
|
||||
*(ub32 *)loc |= bits(S + A, 31, 2) << 2;
|
||||
break;
|
||||
case R_PPC_PLT16_LO:
|
||||
*(ub16 *)loc = lo(G + GOT - got2 - A);
|
||||
break;
|
||||
case R_PPC_PLT16_HI:
|
||||
*(ub16 *)loc = hi(G + GOT - got2 - A);
|
||||
break;
|
||||
case R_PPC_PLT16_HA:
|
||||
*(ub16 *)loc = ha(G + GOT - got2 - A);
|
||||
break;
|
||||
case R_PPC_PLT32:
|
||||
*(ub32 *)loc = G + GOT - got2 - A;
|
||||
break;
|
||||
case R_PPC_REL14:
|
||||
*(ub32 *)loc |= bits(S + A - P, 15, 2) << 2;
|
||||
break;
|
||||
case R_PPC_REL16:
|
||||
case R_PPC_REL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - P);
|
||||
break;
|
||||
case R_PPC_REL16_HI:
|
||||
*(ub16 *)loc = hi(S + A - P);
|
||||
break;
|
||||
case R_PPC_REL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - P);
|
||||
break;
|
||||
case R_PPC_REL24:
|
||||
case R_PPC_LOCAL24PC: {
|
||||
i64 val = S + A - P;
|
||||
if (sign_extend(val, 25) != val)
|
||||
val = get_thunk_addr(i) - P;
|
||||
*(ub32 *)loc |= bits(val, 25, 2) << 2;
|
||||
break;
|
||||
}
|
||||
case R_PPC_PLTREL24: {
|
||||
i64 val = S - P;
|
||||
if (sym.has_plt(ctx) || sign_extend(val, 25) != val)
|
||||
val = get_thunk_addr(i) - P;
|
||||
*(ub32 *)loc |= bits(val, 25, 2) << 2;
|
||||
break;
|
||||
}
|
||||
case R_PPC_REL32:
|
||||
case R_PPC_PLTREL32:
|
||||
*(ub32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_PPC_GOT16:
|
||||
case R_PPC_GOT16_LO:
|
||||
*(ub16 *)loc = lo(G + A);
|
||||
break;
|
||||
case R_PPC_GOT16_HI:
|
||||
*(ub16 *)loc = hi(G + A);
|
||||
break;
|
||||
case R_PPC_GOT16_HA:
|
||||
*(ub16 *)loc = ha(G + A);
|
||||
break;
|
||||
case R_PPC_TPREL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC_TPREL16_HI:
|
||||
*(ub16 *)loc = hi(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC_TPREL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC_DTPREL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC_DTPREL16_HI:
|
||||
*(ub16 *)loc = hi(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC_DTPREL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC_GOT_TLSGD16:
|
||||
*(ub16 *)loc = sym.get_tlsgd_addr(ctx) - GOT;
|
||||
break;
|
||||
case R_PPC_GOT_TLSLD16:
|
||||
*(ub16 *)loc = ctx.got->get_tlsld_addr(ctx) - GOT;
|
||||
break;
|
||||
case R_PPC_GOT_TPREL16:
|
||||
*(ub16 *)loc = sym.get_gottp_addr(ctx) - GOT;
|
||||
break;
|
||||
case R_PPC_TLS:
|
||||
case R_PPC_TLSGD:
|
||||
case R_PPC_TLSLD:
|
||||
case R_PPC_PLTSEQ:
|
||||
case R_PPC_PLTCALL:
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC_ADDR32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ub32 *)loc = *val;
|
||||
else
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC_ADDR32:
|
||||
case R_PPC_UADDR32:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC_ADDR14:
|
||||
case R_PPC_ADDR16:
|
||||
case R_PPC_UADDR16:
|
||||
case R_PPC_ADDR16_LO:
|
||||
case R_PPC_ADDR16_HI:
|
||||
case R_PPC_ADDR16_HA:
|
||||
case R_PPC_ADDR24:
|
||||
case R_PPC_ADDR30:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC_REL14:
|
||||
case R_PPC_REL16:
|
||||
case R_PPC_REL16_LO:
|
||||
case R_PPC_REL16_HI:
|
||||
case R_PPC_REL16_HA:
|
||||
case R_PPC_REL32:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC_GOT16:
|
||||
case R_PPC_GOT16_LO:
|
||||
case R_PPC_GOT16_HI:
|
||||
case R_PPC_GOT16_HA:
|
||||
case R_PPC_PLT16_LO:
|
||||
case R_PPC_PLT16_HI:
|
||||
case R_PPC_PLT16_HA:
|
||||
case R_PPC_PLT32:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PPC_REL24:
|
||||
case R_PPC_PLTREL24:
|
||||
case R_PPC_PLTREL32:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PPC_GOT_TLSGD16:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PPC_GOT_TLSLD16:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PPC_GOT_TPREL16:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PPC_LOCAL24PC:
|
||||
case R_PPC_TLS:
|
||||
case R_PPC_TLSGD:
|
||||
case R_PPC_TLSLD:
|
||||
case R_PPC_TPREL16_LO:
|
||||
case R_PPC_TPREL16_HI:
|
||||
case R_PPC_TPREL16_HA:
|
||||
case R_PPC_DTPREL16_LO:
|
||||
case R_PPC_DTPREL16_HI:
|
||||
case R_PPC_DTPREL16_HA:
|
||||
case R_PPC_PLTSEQ:
|
||||
case R_PPC_PLTCALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
static const ub32 local_thunk[] = {
|
||||
// Get this thunk's address
|
||||
0x7c08'02a6, // mflr r0
|
||||
0x429f'0005, // bcl 20, 31, 4
|
||||
0x7d88'02a6, // mflr r12
|
||||
0x7c08'03a6, // mtlr r0
|
||||
|
||||
// Materialize the destination's address in %r11 and jump to that address
|
||||
0x3d6c'0000, // addis r11, r12, OFFSET@higha
|
||||
0x396b'0000, // addi r11, r11, OFFSET@lo
|
||||
0x7d69'03a6, // mtctr r11
|
||||
0x4e80'0420, // bctr
|
||||
0x6000'0000, // nop
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(plt_entry));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
|
||||
if (sym.has_plt(ctx)) {
|
||||
memcpy(loc, plt_entry, sizeof(plt_entry));
|
||||
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
|
||||
i64 val = got - get_addr(i) - 8;
|
||||
loc[4] |= higha(val);
|
||||
loc[5] |= lo(val);
|
||||
} else {
|
||||
memcpy(loc, local_thunk, sizeof(local_thunk));
|
||||
i64 val = sym.get_addr(ctx) - get_addr(i) - 8;
|
||||
loc[4] |= higha(val);
|
||||
loc[5] |= lo(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,653 @@
|
||||
// This file contains code for the 64-bit PowerPC ELFv1 ABI that is
|
||||
// commonly used for big-endian PPC systems. Modern PPC systems that use
|
||||
// the processor in the little-endian mode use the ELFv2 ABI instead. For
|
||||
// ELFv2, see arch-ppc64v2.cc.
|
||||
//
|
||||
// Even though they are similiar, ELFv1 isn't only different from ELFv2 in
|
||||
// endianness. The most notable difference is, in ELFv1, a function
|
||||
// pointer doesn't directly refer the entry point of a function but
|
||||
// instead refers a data structure so-called "function descriptor".
|
||||
//
|
||||
// The function descriptor is essentially a pair of a function entry point
|
||||
// address and a value that should be set to %r2 before calling that
|
||||
// function. There is actually a third member, but we don't need to know
|
||||
// what that is. In total, the function descriptor is 24 bytes long. Here
|
||||
// is why we need it.
|
||||
//
|
||||
// PPC generally lacks PC-relative data access instructions. Position-
|
||||
// independent code sets GOT + 0x8000 to %r2 and access global variables
|
||||
// relative to %r2.
|
||||
//
|
||||
// Each ELF file has its own GOT. If a function calls another function in
|
||||
// the same ELF file, it doesn't have to reset %r2. However, if it is in
|
||||
// other file (e.g. other .so), it has to set a new value to %r2 so that
|
||||
// the register contains the callee's GOT + 0x8000.
|
||||
//
|
||||
// In this way, you can't call a function just by knowing the function's
|
||||
// entry point address. You also need to know a proper %r2 value for the
|
||||
// function. This is why a function pointer refers a tuple of an address
|
||||
// and a %r2 value.
|
||||
//
|
||||
// If a function call is made through PLT, PLT takes care of restoring %r2.
|
||||
// Therefore, the caller has to restore %r2 only for function calls
|
||||
// through function pointers.
|
||||
//
|
||||
// .opd (short for "official procedure descriptors") contains function
|
||||
// descriptors.
|
||||
//
|
||||
// You can think OPD as this: even in other targets, a function can have a
|
||||
// few different addresses for different purposes. It may not only have an
|
||||
// entry point address but may also have PLT and/or GOT addresses.
|
||||
// In PPCV1, it may have an OPD address in addition to these. OPD address
|
||||
// is used for relocations that refers the address of a function as a
|
||||
// function pointer.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = PPC64V1;
|
||||
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
|
||||
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
|
||||
static u64 highest(u64 x) { return x >> 48; }
|
||||
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
|
||||
|
||||
// .plt is used only for lazy symbol resolution on PPC64. All PLT
|
||||
// calls are made via range extension thunks even if they are within
|
||||
// reach. Thunks read addresses from .got.plt and jump there.
|
||||
// Therefore, once PLT symbols are resolved and final addresses are
|
||||
// written to .got.plt, thunks just skip .plt and directly jump to the
|
||||
// resolved addresses.
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ub32 insn[] = {
|
||||
0x7d88'02a6, // mflr r12
|
||||
0x429f'0005, // bcl 20, 31, 4 // obtain PC
|
||||
0x7d68'02a6, // mflr r11
|
||||
0xe84b'0024, // ld r2,36(r11)
|
||||
0x7d88'03a6, // mtlr r12
|
||||
0x7d62'5a14, // add r11,r2,r11
|
||||
0xe98b'0000, // ld r12,0(r11)
|
||||
0xe84b'0008, // ld r2,8(r11)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0xe96b'0010, // ld r11,16(r11)
|
||||
0x4e80'0420, // bctr
|
||||
// .quad .got.plt - .plt - 8
|
||||
0x0000'0000,
|
||||
0x0000'0000,
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::plt_hdr_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub64 *)(buf + 44) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
i64 offset = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 4;
|
||||
*(ub32 *)(buf + 0) = 0x3800'0000 | sym.get_plt_idx(ctx); // li %r0, PLT_INDEX
|
||||
*(ub32 *)(buf + 4) = 0x4b00'0000 | (offset & 0x00ff'ffff); // b plt0
|
||||
}
|
||||
|
||||
// .plt.got is not necessary on PPC64 because range extension thunks
|
||||
// directly read GOT entries and jump there.
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_PPC64_ADDR64:
|
||||
*(ub64 *)loc = val;
|
||||
break;
|
||||
case R_PPC64_REL32:
|
||||
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
case R_PPC64_REL64:
|
||||
*(ub64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
apply_toc_rel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_PPC64_TOC:
|
||||
apply_toc_rel(ctx, *ctx.extra.TOC, rel, loc, TOC, A, P, dynrel);
|
||||
break;
|
||||
case R_PPC64_TOC16_HA:
|
||||
*(ub16 *)loc = ha(S + A - TOC);
|
||||
break;
|
||||
case R_PPC64_TOC16_LO:
|
||||
*(ub16 *)loc = lo(S + A - TOC);
|
||||
break;
|
||||
case R_PPC64_TOC16_DS: {
|
||||
i64 val = S + A - TOC;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc |= val & 0xfffc;
|
||||
break;
|
||||
}
|
||||
case R_PPC64_TOC16_LO_DS:
|
||||
*(ub16 *)loc |= (S + A - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_REL24: {
|
||||
i64 val = sym.get_addr(ctx, NO_OPD) + A - P;
|
||||
if (sym.has_plt(ctx) || sign_extend(val, 25) != val)
|
||||
val = get_thunk_addr(i) + A - P;
|
||||
|
||||
check(val, -(1 << 25), 1 << 25);
|
||||
*(ub32 *)loc |= bits(val, 25, 2) << 2;
|
||||
|
||||
// If a callee is an external function, PLT saves %r2 to the
|
||||
// caller's r2 save slot. We need to restore it after function
|
||||
// return. To do so, there's usually a NOP as a placeholder
|
||||
// after a BL. 0x6000'0000 is a NOP.
|
||||
if (sym.has_plt(ctx) && *(ub32 *)(loc + 4) == 0x6000'0000)
|
||||
*(ub32 *)(loc + 4) = 0xe841'0028; // ld r2, 40(r1)
|
||||
break;
|
||||
}
|
||||
case R_PPC64_REL64:
|
||||
*(ub64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_PPC64_REL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - P);
|
||||
break;
|
||||
case R_PPC64_REL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - P);
|
||||
break;
|
||||
case R_PPC64_PLT16_HA:
|
||||
*(ub16 *)loc = ha(G + GOT - TOC);
|
||||
break;
|
||||
case R_PPC64_PLT16_HI:
|
||||
*(ub16 *)loc = hi(G + GOT - TOC);
|
||||
break;
|
||||
case R_PPC64_PLT16_LO:
|
||||
*(ub16 *)loc = lo(G + GOT - TOC);
|
||||
break;
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
*(ub16 *)loc |= (G + GOT - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
*(ub16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
*(ub16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
*(ub16 *)loc = lo(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
*(ub16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
*(ub16 *)loc = lo(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
*(ub16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_TLS:
|
||||
case R_PPC64_TLSGD:
|
||||
case R_PPC64_TLSLD:
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ub64 *)loc = *val;
|
||||
else
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
case R_PPC64_ADDR32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_PPC64_DTPREL64:
|
||||
*(ub64 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT | NEEDS_OPD,
|
||||
std::memory_order_relaxed);
|
||||
|
||||
// Any relocation except R_PPC64_REL24 is considered as an
|
||||
// address-taking relocation.
|
||||
if (rel.r_type != R_PPC64_REL24 && sym.get_type() == STT_FUNC)
|
||||
sym.flags.fetch_or(NEEDS_OPD, std::memory_order_relaxed);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
scan_toc_rel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_TOC:
|
||||
scan_toc_rel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PPC64_REL24:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PPC64_PLT16_HA:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PPC64_REL64:
|
||||
case R_PPC64_TOC16_HA:
|
||||
case R_PPC64_TOC16_LO:
|
||||
case R_PPC64_TOC16_LO_DS:
|
||||
case R_PPC64_TOC16_DS:
|
||||
case R_PPC64_REL16_HA:
|
||||
case R_PPC64_REL16_LO:
|
||||
case R_PPC64_PLT16_HI:
|
||||
case R_PPC64_PLT16_LO:
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
case R_PPC64_TLS:
|
||||
case R_PPC64_TLSGD:
|
||||
case R_PPC64_TLSLD:
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
// If the destination is .plt.got, we save the current r2, read an
|
||||
// address of a function descriptor from .got, restore %r2 and jump
|
||||
// to the function.
|
||||
static const ub32 pltgot_thunk[] = {
|
||||
// Store the caller's %r2
|
||||
0xf841'0028, // std %r2, 40(%r1)
|
||||
|
||||
// Load an address of a function descriptor
|
||||
0x3d82'0000, // addis %r12, %r2, foo@got@toc@ha
|
||||
0xe98c'0000, // ld %r12, foo@got@toc@lo(%r12)
|
||||
|
||||
// Restore the callee's %r2
|
||||
0xe84c'0008, // ld %r2, 8(%r12)
|
||||
|
||||
// Jump to the function
|
||||
0xe98c'0000, // ld %r12, 0(%r12)
|
||||
0x7d89'03a6, // mtctr %r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
// If the destination is .plt, read a function descriptor from .got.plt.
|
||||
static const ub32 plt_thunk[] = {
|
||||
// Store the caller's %r2
|
||||
0xf841'0028, // std %r2, 40(%r1)
|
||||
|
||||
// Materialize an address of a function descriptor
|
||||
0x3d82'0000, // addis %r12, %r2, foo@gotplt@toc@ha
|
||||
0x398c'0000, // addi %r12, %r12, foo@gotplt@toc@lo
|
||||
|
||||
// Restore the callee's %r2
|
||||
0xe84c'0008, // ld %r2, 8(%r12)
|
||||
|
||||
// Jump to the function
|
||||
0xe98c'0000, // ld %r12, 0(%r12)
|
||||
0x7d89'03a6, // mtctr %r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
// If the destination is a non-imported function, we directly jump
|
||||
// to the function entry address.
|
||||
static const ub32 local_thunk[] = {
|
||||
0x3d82'0000, // addis r12, r2, foo@toc@ha
|
||||
0x398c'0000, // addi r12, r12, foo@toc@lo
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
0x6000'0000, // nop
|
||||
0x6000'0000, // nop
|
||||
0x6000'0000, // nop
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(pltgot_thunk));
|
||||
static_assert(E::thunk_size == sizeof(plt_thunk));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
|
||||
|
||||
if (sym.has_got(ctx)) {
|
||||
memcpy(loc, pltgot_thunk, sizeof(pltgot_thunk));
|
||||
i64 val = sym.get_got_addr(ctx) - ctx.extra.TOC->value;
|
||||
loc[1] |= higha(val);
|
||||
loc[2] |= lo(val);
|
||||
} else if(sym.has_plt(ctx)) {
|
||||
memcpy(loc, plt_thunk, sizeof(plt_thunk));
|
||||
i64 val = sym.get_gotplt_addr(ctx) - ctx.extra.TOC->value;
|
||||
loc[1] |= higha(val);
|
||||
loc[2] |= lo(val);
|
||||
} else {
|
||||
memcpy(loc, local_thunk, sizeof(local_thunk));
|
||||
i64 val = sym.get_addr(ctx, NO_OPD) - ctx.extra.TOC->value;
|
||||
loc[0] |= higha(val);
|
||||
loc[1] |= lo(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static InputSection<E> *get_opd_section(ObjectFile<E> &file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file.sections)
|
||||
if (isec && isec->name() == ".opd")
|
||||
return isec.get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static ElfRel<E> *
|
||||
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, i64 offset) {
|
||||
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
|
||||
auto it = std::lower_bound(rels.begin(), rels.end(), offset,
|
||||
[](const ElfRel<E> &r, i64 offset) {
|
||||
return r.r_offset < offset;
|
||||
});
|
||||
|
||||
if (it == rels.end())
|
||||
return nullptr;
|
||||
if (it->r_offset != offset)
|
||||
return nullptr;
|
||||
return &*it;
|
||||
}
|
||||
|
||||
struct OpdSymbol {
|
||||
bool operator<(const OpdSymbol &x) const { return r_offset < x.r_offset; }
|
||||
|
||||
u64 r_offset = 0;
|
||||
Symbol<E> *sym = nullptr;
|
||||
};
|
||||
|
||||
static Symbol<E> *
|
||||
get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
|
||||
auto it = std::lower_bound(syms.begin(), syms.end(), OpdSymbol{offset});
|
||||
if (it == syms.end())
|
||||
return nullptr;
|
||||
if (it->r_offset != offset)
|
||||
return nullptr;
|
||||
return it->sym;
|
||||
}
|
||||
|
||||
// Compiler creates an .opd entry for each function symbol. The intention
|
||||
// is to make it possible to create an output .opd section just by linking
|
||||
// input .opd sections in the same manner as we do to other normal input
|
||||
// sections.
|
||||
//
|
||||
// However, in reality, .opd isn't a normal input section. It needs many
|
||||
// special treatments as follows:
|
||||
//
|
||||
// 1. A function symbol refers not a .text but an .opd. Its address works
|
||||
// fine for address-taking relocations such as R_PPC64_ADDR64. However,
|
||||
// R_PPC64_REL24 (which is used for branch instruction) needs a
|
||||
// function's real address instead of the function's .opd address.
|
||||
// We need to read .opd contents to find out a function entry point
|
||||
// address to apply R_PPC64_REL24.
|
||||
//
|
||||
// 2. Output .opd entries are needed only for functions whose addresses
|
||||
// are taken. Just copying input .opd sections to an output would
|
||||
// produces lots of dead .opd entries.
|
||||
//
|
||||
// 3. In this design, all function symbols refer an .opd section, and that
|
||||
// doesn't work well with graph traversal optimizations such as garbage
|
||||
// collection or identical comdat folding. For example, garbage
|
||||
// collector would mark an .opd alive which in turn mark all functions
|
||||
// thatare referenced by .opd as alive, effectively keeping all
|
||||
// functions as alive.
|
||||
//
|
||||
// The problem is that the compiler creates a half-baked .opd section, and
|
||||
// the linker has to figure out what all these .opd entries and
|
||||
// relocations are trying to achieve. It's like the compiler would emit a
|
||||
// half-baked .plt section in an object file and the linker has to deal
|
||||
// with that. That's not a good design.
|
||||
//
|
||||
// So, in this function, we undo what the compiler did to .opd. We remove
|
||||
// function symbols from .opd and reattach them to their function entry
|
||||
// points. We also rewrite relocations that directly refer an input .opd
|
||||
// section so that they refer function symbols instead. We then mark input
|
||||
// .opd sections as dead.
|
||||
//
|
||||
// After this function, we mark symbols with the NEEDS_OPD flag if the
|
||||
// symbol needs an .opd entry. We then create an output .opd just like we
|
||||
// do for .plt or .got.
|
||||
void ppc64v1_rewrite_opd(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
InputSection<E> *opd = get_opd_section(*file);
|
||||
if (!opd)
|
||||
return;
|
||||
opd->is_alive = false;
|
||||
|
||||
// Move symbols from .opd to .text.
|
||||
std::vector<OpdSymbol> opd_syms;
|
||||
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file != file || sym->get_input_section() != opd)
|
||||
continue;
|
||||
|
||||
if (u32 ty = sym->get_type(); ty != STT_FUNC && ty != STT_GNU_IFUNC)
|
||||
continue;
|
||||
|
||||
ElfRel<E> *rel = get_relocation_at(ctx, *opd, sym->value);
|
||||
if (!rel)
|
||||
Fatal(ctx) << *file << ": cannot find a relocation in .opd for "
|
||||
<< *sym << " at offset 0x" << std::hex << (u64)sym->value;
|
||||
|
||||
Symbol<E> *sym2 = file->symbols[rel->r_sym];
|
||||
if (sym2->get_type() != STT_SECTION)
|
||||
Fatal(ctx) << *file << ": bad relocation in .opd referring " << *sym2;
|
||||
|
||||
opd_syms.push_back({sym->value, sym});
|
||||
|
||||
sym->set_input_section(sym2->get_input_section());
|
||||
sym->value = rel->r_addend;
|
||||
}
|
||||
|
||||
// Sort symbols so that get_opd_sym_at() can do binary search.
|
||||
sort(opd_syms);
|
||||
|
||||
// Rewrite relocations so that they directly refer to .opd.
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (!isec || !isec->is_alive || isec.get() == opd)
|
||||
continue;
|
||||
|
||||
for (ElfRel<E> &r : isec->get_rels(ctx)) {
|
||||
Symbol<E> &sym = *file->symbols[r.r_sym];
|
||||
if (sym.get_input_section() != opd)
|
||||
continue;
|
||||
|
||||
Symbol<E> *real_sym = get_opd_sym_at(ctx, opd_syms, r.r_addend);
|
||||
if (!real_sym)
|
||||
Fatal(ctx) << *isec << ": cannot find a symbol in .opd for " << r
|
||||
<< " at offset 0x" << std::hex << (u64)r.r_addend;
|
||||
|
||||
r.r_sym = real_sym->sym_idx;
|
||||
r.r_addend = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// When a function is exported, the dynamic symbol for the function should
|
||||
// refers the function's .opd entry. This function marks such symbols with
|
||||
// NEEDS_OPD.
|
||||
void ppc64v1_scan_symbols(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols)
|
||||
if (sym->file == file && sym->is_exported)
|
||||
if (u32 ty = sym->get_type(); ty == STT_FUNC || ty == STT_GNU_IFUNC)
|
||||
sym->flags |= NEEDS_OPD;
|
||||
});
|
||||
|
||||
// Functions referenced by the ELF header also have to have .opd entries.
|
||||
auto mark = [&](std::string_view name) {
|
||||
if (!name.empty())
|
||||
if (Symbol<E> &sym = *get_symbol(ctx, name); !sym.is_imported)
|
||||
sym.flags |= NEEDS_OPD;
|
||||
};
|
||||
|
||||
mark(ctx.arg.entry);
|
||||
mark(ctx.arg.init);
|
||||
mark(ctx.arg.fini);
|
||||
}
|
||||
|
||||
void PPC64OpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
sym->set_opd_idx(ctx, symbols.size());
|
||||
symbols.push_back(sym);
|
||||
this->shdr.sh_size += ENTRY_SIZE;
|
||||
}
|
||||
|
||||
void PPC64OpdSection::copy_buf(Context<E> &ctx) {
|
||||
ub64 *buf = (ub64 *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
*buf++ = sym->get_addr(ctx, NO_PLT | NO_OPD);
|
||||
*buf++ = ctx.extra.TOC->value;
|
||||
*buf++ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,461 @@
|
||||
// This file implements the PowerPC ELFv2 ABI which was standardized in
|
||||
// 2014. Modern little-endian PowerPC systems are based on this ABI.
|
||||
// The ABI is often referred to as "ppc64le". This shouldn't be confused
|
||||
// with "ppc64" which refers the original, big-endian PowerPC systems.
|
||||
//
|
||||
// PPC64 is a bit tricky to support because PC-relative load/store
|
||||
// instructions are generally not available. Therefore, it's not easy
|
||||
// for position-independent code to load a value from, for example,
|
||||
// .got, as we can't do that with [PC + the offset to the .got entry].
|
||||
//
|
||||
// We can get the program counter by the following four instructions
|
||||
//
|
||||
// mflr r1 // save the current link register to r1
|
||||
// bl .+4 // branch to the next instruction as if it were a function
|
||||
// mflr r0 // copy the return address to r0
|
||||
// mtlr r1 // restore the original link register value
|
||||
//
|
||||
// , but that's too expensive to do if we do this for each load/store.
|
||||
//
|
||||
// As a workaround, most functions are compiled in such a way that r2 is
|
||||
// assumed to always contain the address of .got + 0x8000. With this, we
|
||||
// can for example load the first entry of .got with a single instruction
|
||||
// `lw r0, -0x8000(r2)`. r2 is called the TOC pointer.
|
||||
//
|
||||
// There's only one .got for each ELF module. Therefore, if a callee is in
|
||||
// the same ELF module, r2 doesn't have to be recomputed. Most function
|
||||
// calls are usually within the same ELF module, so this mechanism is
|
||||
// efficient.
|
||||
//
|
||||
// In PPC64, a function usually have two entry points, global and local.
|
||||
// The global entry point is usually 8 bytes precedes the local entry
|
||||
// point. In between is the following instructions:
|
||||
//
|
||||
// addis r2, r12, .TOC.@ha
|
||||
// addi r2, r2, .TOC.@lo + 4;
|
||||
//
|
||||
// The global entry point assumes that the address of itself is in r12,
|
||||
// and it computes its own TOC pointer from r12. It's easy to do so for
|
||||
// the callee because the offset between its .got + 0x8000 and the
|
||||
// function is known at link-time. The above code sequence then falls
|
||||
// through to the local entry point that assumes r2 is .got + 0x8000.
|
||||
//
|
||||
// So, if a callee's TOC pointer is different from the current one
|
||||
// (e.g. calling a function in another .so), we first load the callee's
|
||||
// address to r12 (e.g. from .got.plt with a r2-relative load) and branch
|
||||
// to that address. Then the callee computes its own TOC pointer using
|
||||
// r12.
|
||||
//
|
||||
// Note on section names: the PPC64 psABI uses a weird naming convention
|
||||
// which calls .got.plt .plt. We ignored that part because it's just
|
||||
// confusing. Since the runtime only cares about segments, we should be
|
||||
// able to name sections whatever we want.
|
||||
//
|
||||
// https://openpowerfoundation.org/specifications/64bitelfabi/
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = PPC64V2;
|
||||
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
|
||||
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
|
||||
static u64 highest(u64 x) { return x >> 48; }
|
||||
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
|
||||
|
||||
// .plt is used only for lazy symbol resolution on PPC64. All PLT
|
||||
// calls are made via range extension thunks even if they are within
|
||||
// reach. Thunks read addresses from .got.plt and jump there.
|
||||
// Therefore, once PLT symbols are resolved and final addresses are
|
||||
// written to .got.plt, thunks just skip .plt and directly jump to the
|
||||
// resolved addresses.
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn[] = {
|
||||
// Get PC
|
||||
0x7c08'02a6, // mflr r0
|
||||
0x429f'0005, // bcl 20, 31, 4 // obtain PC
|
||||
0x7d68'02a6, // mflr r11
|
||||
0x7c08'03a6, // mtlr r0
|
||||
|
||||
// Compute the PLT entry index
|
||||
0xe80b'002c, // ld r0, 44(r11)
|
||||
0x7d8b'6050, // subf r12, r11, r12
|
||||
0x7d60'5a14, // add r11, r0, r11
|
||||
0x380c'ffcc, // addi r0, r12, -52
|
||||
0x7800'f082, // rldicl r0, r0, 62, 2
|
||||
|
||||
// Load .got.plt[0] and .got.plt[1] and branch to .got.plt[0]
|
||||
0xe98b'0000, // ld r12, 0(r11)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0xe96b'0008, // ld r11, 8(r11)
|
||||
0x4e80'0420, // bctr
|
||||
|
||||
// .quad .got.plt - .plt - 8
|
||||
0x0000'0000,
|
||||
0x0000'0000,
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul64 *)(buf + 52) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
// When the control is transferred to a PLT entry, the PLT entry's
|
||||
// address is already set to %r12 by the caller.
|
||||
i64 offset = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx);
|
||||
*(ul32 *)buf = 0x4b00'0000 | (offset & 0x00ff'ffff); // b plt0
|
||||
}
|
||||
|
||||
// .plt.got is not necessary on PPC64 because range extension thunks
|
||||
// directly read GOT entries and jump there.
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_PPC64_ADDR64:
|
||||
*(ul64 *)loc = val;
|
||||
break;
|
||||
case R_PPC64_REL32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
case R_PPC64_REL64:
|
||||
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
static u64 get_local_entry_offset(Context<E> &ctx, Symbol<E> &sym) {
|
||||
i64 val = sym.esym().ppc_local_entry;
|
||||
assert(val <= 7);
|
||||
if (val == 0 || val == 1)
|
||||
return 0;
|
||||
if (val == 7)
|
||||
Fatal(ctx) << sym << ": local entry offset 7 is reserved";
|
||||
return 1 << val;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
if (name() == ".toc")
|
||||
apply_toc_rel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
else
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_PPC64_TOC16_HA:
|
||||
*(ul16 *)loc = ha(S + A - TOC);
|
||||
break;
|
||||
case R_PPC64_TOC16_LO:
|
||||
*(ul16 *)loc = lo(S + A - TOC);
|
||||
break;
|
||||
case R_PPC64_TOC16_DS:
|
||||
case R_PPC64_TOC16_LO_DS:
|
||||
*(ul16 *)loc |= (S + A - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_REL24: {
|
||||
i64 val = S + A - P + get_local_entry_offset(ctx, sym);
|
||||
if (sym.has_plt(ctx) || sign_extend(val, 25) != val)
|
||||
val = get_thunk_addr(i) + A - P;
|
||||
|
||||
check(val, -(1 << 25), 1 << 25);
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
|
||||
// If a callee is an external function, PLT saves %r2 to the
|
||||
// caller's r2 save slot. We need to restore it after function
|
||||
// return. To do so, there's usually a NOP as a placeholder
|
||||
// after a BL. 0x6000'0000 is a NOP.
|
||||
if (sym.has_plt(ctx) && *(ul32 *)(loc + 4) == 0x6000'0000)
|
||||
*(ul32 *)(loc + 4) = 0xe841'0018; // ld r2, 24(r1)
|
||||
break;
|
||||
}
|
||||
case R_PPC64_REL64:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_PPC64_REL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - P);
|
||||
break;
|
||||
case R_PPC64_REL16_LO:
|
||||
*(ul16 *)loc = lo(S + A - P);
|
||||
break;
|
||||
case R_PPC64_PLT16_HA:
|
||||
*(ul16 *)loc = ha(G + GOT - TOC);
|
||||
break;
|
||||
case R_PPC64_PLT16_HI:
|
||||
*(ul16 *)loc = hi(G + GOT - TOC);
|
||||
break;
|
||||
case R_PPC64_PLT16_LO:
|
||||
*(ul16 *)loc = lo(G + GOT - TOC);
|
||||
break;
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
*(ul16 *)loc |= (G + GOT - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
*(ul16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
*(ul16 *)loc = lo(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
*(ul16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
*(ul16 *)loc = lo(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
*(ul16 *)loc = lo(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_LO:
|
||||
*(ul16 *)loc = lo(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
*(ul16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_TLS:
|
||||
case R_PPC64_TLSGD:
|
||||
case R_PPC64_TLSLD:
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(ul64 *)loc = S + A;
|
||||
break;
|
||||
case R_PPC64_ADDR32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_PPC64_DTPREL64:
|
||||
*(ul64 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
if (name() == ".toc")
|
||||
scan_toc_rel(ctx, sym, rel);
|
||||
else
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PPC64_REL24:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PPC64_PLT16_HA:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PPC64_REL64:
|
||||
case R_PPC64_TOC16_HA:
|
||||
case R_PPC64_TOC16_LO:
|
||||
case R_PPC64_TOC16_LO_DS:
|
||||
case R_PPC64_TOC16_DS:
|
||||
case R_PPC64_REL16_HA:
|
||||
case R_PPC64_REL16_LO:
|
||||
case R_PPC64_PLT16_HI:
|
||||
case R_PPC64_PLT16_LO:
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
case R_PPC64_TLS:
|
||||
case R_PPC64_TLSGD:
|
||||
case R_PPC64_TLSLD:
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
// If the destination is PLT, we read an address from .got.plt or .got
|
||||
// and jump there.
|
||||
static const ul32 plt_thunk[] = {
|
||||
// Save r2 to the r2 save slot reserved in the caller's stack frame
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
|
||||
// Jump to a PLT entry
|
||||
0x3d82'0000, // addis r12, r2, foo@gotplt@toc@ha
|
||||
0xe98c'0000, // ld r12, foo@gotplt@toc@lo(r12)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
// If the destination is a non-imported function, we directly jump
|
||||
// to its local entry point.
|
||||
static const ul32 local_thunk[] = {
|
||||
// Jump to a local entry point
|
||||
0x3d82'0000, // addis r12, r2, foo@toc@ha
|
||||
0x398c'0000, // addi r12, r12, foo@toc@lo
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
0x6000'0000, // nop
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(plt_thunk));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
ul32 *loc = (ul32 *)(buf + i * E::thunk_size);
|
||||
|
||||
if (sym.has_plt(ctx)) {
|
||||
memcpy(loc, plt_thunk, sizeof(plt_thunk));
|
||||
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
|
||||
i64 val = got - ctx.extra.TOC->value;
|
||||
loc[1] |= higha(val);
|
||||
loc[2] |= lo(val);
|
||||
} else {
|
||||
memcpy(loc, local_thunk, sizeof(local_thunk));
|
||||
i64 val = sym.get_addr(ctx) + get_local_entry_offset(ctx, sym) -
|
||||
ctx.extra.TOC->value;
|
||||
loc[0] |= higha(val);
|
||||
loc[1] |= lo(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+356
-317
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,541 @@
|
||||
// This file contains code for the IBM z/Architecture 64-bit ISA, which is
|
||||
// commonly referred to as "s390x" on Linux.
|
||||
//
|
||||
// z/Architecture is a 64-bit CISC ISA developed by IBM around 2000 for
|
||||
// IBM's "big iron" mainframe computers. The computers are direct
|
||||
// descendents of IBM System/360 all the way back in 1966. I've never
|
||||
// actually seen a mainframe, and you probaly haven't either, but it looks
|
||||
// like the mainframe market is still large enough to sustain its ecosystem.
|
||||
// Ubuntu for example provides the official support for s390x as of 2022.
|
||||
// Since they are being actively maintained, we need to support them.
|
||||
//
|
||||
// As an instruction set, s390x isn't particularly odd. It has 16 general-
|
||||
// purpose registers. Instructions are 2, 4 or 6 bytes long and always
|
||||
// aligned to 2 bytes boundaries. Despite unfamiliarty, I found that it
|
||||
// just feels like a 64-bit i386 in a parallel universe.
|
||||
//
|
||||
// Here is the register usage in this ABI:
|
||||
//
|
||||
// r0-r1: reserved as scratch registers so we can use them in our PLT
|
||||
// r2: parameter passing and return values
|
||||
// r3-r6: parameter passing
|
||||
// r12: address of GOT if position-independent code
|
||||
// r14: return address
|
||||
// r15: stack pointer
|
||||
// a1: upper 32 bits of TP (thread pointer)
|
||||
// a2: lower 32 bits of TP (thread pointer)
|
||||
//
|
||||
// TLS is supported on s390x in the same way as it is on other targets
|
||||
// with one exeption. On other targets, __tls_get_addr is used to get an
|
||||
// address of a thread-local variable. On s390x, __tls_get_offset is used
|
||||
// instead. The difference is __tls_get_offset returns an address of a
|
||||
// thread-local variable as an offset from TP. So we need to add TP to a
|
||||
// return value before use. I don't know why it is different, but that is
|
||||
// the way it is.
|
||||
//
|
||||
// https://github.com/IBM/s390x-abi/releases/download/v1.6/lzsabi_s390x.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = S390X;
|
||||
|
||||
static void write_mid20(u8 *loc, u64 val) {
|
||||
*(ub32 *)loc |= (bits(val, 11, 0) << 16) | (bits(val, 19, 12) << 8);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static u8 insn[] = {
|
||||
0xe3, 0x00, 0xf0, 0x38, 0x00, 0x24, // stg %r0, 56(%r15)
|
||||
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_OFFSET
|
||||
0xd2, 0x07, 0xf0, 0x30, 0x10, 0x08, // mvc 48(8, %r15), 8(%r1)
|
||||
0xe3, 0x10, 0x10, 0x10, 0x00, 0x04, // lg %r1, 16(%r1)
|
||||
0x07, 0xf1, // br %r1
|
||||
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 8) = (ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 6) >> 1;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static u8 insn[] = {
|
||||
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_ENTRY_OFFSET
|
||||
0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg %r1, (%r1)
|
||||
0xc0, 0x01, 0, 0, 0, 0, // lgfi %r0, PLT_INDEX
|
||||
0x07, 0xf1, // br %r1
|
||||
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
|
||||
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 2) = (sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
|
||||
*(ub32 *)(buf + 14) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static u8 insn[] = {
|
||||
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOT_ENTRY_OFFSET
|
||||
0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg %r1, (%r1)
|
||||
0x07, 0xf1, // br %r1
|
||||
0x07, 0x00, // nopr
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 2) = (sym.get_got_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_390_PC32:
|
||||
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
case R_390_64:
|
||||
*(ub64 *)loc = val;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto check_dbl = [&](i64 val, i64 lo, i64 hi) {
|
||||
check(val, lo, hi);
|
||||
|
||||
// R_390_*DBL relocs should never refer a symbol at an odd address
|
||||
if (val & 1)
|
||||
Error(ctx) << *this << ": misaligned symbol " << sym
|
||||
<< " for relocation " << rel;
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_390_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_390_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_12: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 12);
|
||||
*(ul16 *)loc = bits(val, 11, 0);
|
||||
break;
|
||||
}
|
||||
case R_390_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ub16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_20: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 20);
|
||||
write_mid20(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_390_32:
|
||||
case R_390_PLT32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_PLT64:
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
case R_390_PC12DBL:
|
||||
case R_390_PLT12DBL: {
|
||||
i64 val = S + A - P;
|
||||
check_dbl(val, -(1 << 12), 1 << 12);
|
||||
*(ul16 *)loc = (val >> 1) & 0x0fff;
|
||||
break;
|
||||
}
|
||||
case R_390_PC16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_PC32: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_PC64:
|
||||
*(ub64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_390_PC16DBL:
|
||||
case R_390_PLT16DBL: {
|
||||
i64 val = S + A - P;
|
||||
check_dbl(val, -(1 << 16), 1 << 16);
|
||||
*(ub16 *)loc = val >> 1;
|
||||
break;
|
||||
}
|
||||
case R_390_PC24DBL:
|
||||
case R_390_PLT24DBL: {
|
||||
i64 val = S + A - P;
|
||||
check_dbl(val, -(1 << 24), 1 << 24);
|
||||
*(ub32 *)loc |= bits(val, 24, 1);
|
||||
break;
|
||||
}
|
||||
case R_390_PC32DBL:
|
||||
case R_390_PLT32DBL:
|
||||
if (ctx.is_static && &sym == ctx.tls_get_offset) {
|
||||
// __tls_get_offset() in libc.a is stub code that calls abort().
|
||||
// So we provide a replacement function.
|
||||
*(ub32 *)loc = (ctx.extra.tls_get_offset->shdr.sh_addr - P) >> 1;
|
||||
} else {
|
||||
i64 val = S + A - P;
|
||||
check_dbl(val, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = val >> 1;
|
||||
}
|
||||
break;
|
||||
case R_390_GOT12:
|
||||
case R_390_GOTPLT12: {
|
||||
i64 val = G + A;
|
||||
check(val, 0, 1 << 12);
|
||||
*(ul16 *)loc = bits(val, 11, 0);
|
||||
break;
|
||||
}
|
||||
case R_390_GOT16:
|
||||
case R_390_GOTPLT16: {
|
||||
i64 val = G + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ub16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_GOT20:
|
||||
case R_390_GOTPLT20: {
|
||||
i64 val = G + A;
|
||||
check(val, 0, 1 << 20);
|
||||
write_mid20(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_390_GOT32:
|
||||
case R_390_GOTPLT32: {
|
||||
i64 val = G + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_GOT64:
|
||||
case R_390_GOTPLT64:
|
||||
*(ub64 *)loc = G + A;
|
||||
break;
|
||||
case R_390_GOTOFF16:
|
||||
case R_390_PLTOFF16: {
|
||||
i64 val = S + A - GOT;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_GOTOFF32:
|
||||
case R_390_PLTOFF32: {
|
||||
i64 val = S + A - GOT;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_GOTOFF64:
|
||||
case R_390_PLTOFF64:
|
||||
*(ub64 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_390_GOTPC:
|
||||
*(ub64 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_390_GOTPCDBL: {
|
||||
i64 val = GOT + A - P;
|
||||
check_dbl(val, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = val >> 1;
|
||||
break;
|
||||
}
|
||||
case R_390_GOTENT: {
|
||||
i64 val = GOT + G + A - P;
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = val >> 1;
|
||||
break;
|
||||
}
|
||||
case R_390_TLS_LE32:
|
||||
*(ub32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_LE64:
|
||||
*(ub64 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_GOTIE20:
|
||||
write_mid20(loc, sym.get_gottp_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_390_TLS_IEENT:
|
||||
*(ub32 *)loc = (sym.get_gottp_addr(ctx) + A - P) >> 1;
|
||||
break;
|
||||
case R_390_TLS_GD32:
|
||||
if (sym.has_tlsgd(ctx))
|
||||
*(ub32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
else
|
||||
*(ub32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_GD64:
|
||||
if (sym.has_tlsgd(ctx))
|
||||
*(ub64 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
else
|
||||
*(ub64 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_GDCALL:
|
||||
if (!sym.has_tlsgd(ctx)) {
|
||||
static u8 nop[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
|
||||
memcpy(loc, nop, sizeof(nop));
|
||||
}
|
||||
break;
|
||||
case R_390_TLS_LDM32:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ub32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_390_TLS_LDM64:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ub64 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_390_TLS_LDO32:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ub32 *)loc = S + A - ctx.dtp_addr;
|
||||
else
|
||||
*(ub32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_LDO64:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ub64 *)loc = S + A - ctx.dtp_addr;
|
||||
else
|
||||
*(ub64 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_LDCALL:
|
||||
if (!ctx.got->has_tlsld(ctx)) {
|
||||
static u8 nop[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
|
||||
memcpy(loc, nop, sizeof(nop));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_390_32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_390_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ub64 *)loc = *val;
|
||||
else
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
case R_390_TLS_LDO64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ub64 *)loc = *val;
|
||||
else
|
||||
*(ub64 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": apply_reloc_nonalloc: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_390_64:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_390_8:
|
||||
case R_390_12:
|
||||
case R_390_16:
|
||||
case R_390_20:
|
||||
case R_390_32:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_390_PC16:
|
||||
case R_390_PC16DBL:
|
||||
case R_390_PC32:
|
||||
case R_390_PC32DBL:
|
||||
case R_390_PC64:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_390_GOT12:
|
||||
case R_390_GOT16:
|
||||
case R_390_GOT20:
|
||||
case R_390_GOT32:
|
||||
case R_390_GOT64:
|
||||
case R_390_GOTOFF16:
|
||||
case R_390_GOTOFF32:
|
||||
case R_390_GOTOFF64:
|
||||
case R_390_GOTPLT12:
|
||||
case R_390_GOTPLT16:
|
||||
case R_390_GOTPLT20:
|
||||
case R_390_GOTPLT32:
|
||||
case R_390_GOTPLT64:
|
||||
case R_390_GOTPC:
|
||||
case R_390_GOTPCDBL:
|
||||
case R_390_GOTENT:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_390_PLT12DBL:
|
||||
case R_390_PLT16DBL:
|
||||
case R_390_PLT24DBL:
|
||||
case R_390_PLT32:
|
||||
case R_390_PLT32DBL:
|
||||
case R_390_PLT64:
|
||||
case R_390_PLTOFF16:
|
||||
case R_390_PLTOFF32:
|
||||
case R_390_PLTOFF64:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_390_TLS_GOTIE20:
|
||||
case R_390_TLS_IEENT:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_390_TLS_GD32:
|
||||
case R_390_TLS_GD64:
|
||||
if (!relax_tlsgd(ctx, sym))
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_390_TLS_LDM32:
|
||||
case R_390_TLS_LDM64:
|
||||
if (!relax_tlsld(ctx))
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_390_TLS_LE32:
|
||||
case R_390_TLS_LE64:
|
||||
case R_390_TLS_LDO32:
|
||||
case R_390_TLS_LDO64:
|
||||
case R_390_TLS_GDCALL:
|
||||
case R_390_TLS_LDCALL:
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": scan_relocations: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// __tls_get_offset() in libc.a just calls abort(), assuming that the
|
||||
// linker always relaxes TLS calls for statically-linkd executables.
|
||||
// We don't always do that because we believe --relax and --static
|
||||
// should be orthogonal.
|
||||
//
|
||||
// This section provides a replacement for __tls_get_offset() in libc.a.
|
||||
void S390XTlsGetOffsetSection::copy_buf(Context<E> &ctx) {
|
||||
static const u8 insn[] = {
|
||||
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOT
|
||||
0xb9, 0x08, 0x00, 0x21, // agr %r2, %r1
|
||||
0xe3, 0x20, 0x20, 0x08, 0x00, 0x04, // lg %r2, 8(%r2)
|
||||
0xc0, 0x11, 0, 0, 0, 0, // lgfi %r1, TLS_BLOCK_SIZE
|
||||
0xb9, 0x09, 0x00, 0x21, // sgr %r2, %r1
|
||||
0x07, 0xfe, // br %r14
|
||||
};
|
||||
|
||||
assert(this->shdr.sh_size == sizeof(insn));
|
||||
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset;
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
*(ub32 *)(loc + 2) = (ctx.got->shdr.sh_addr - this->shdr.sh_addr) >> 1;
|
||||
*(ub32 *)(loc + 18) = ctx.tp_addr - ctx.tls_begin;
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+355
@@ -0,0 +1,355 @@
|
||||
// SH-4 (SuperH 4) is a 32-bit RISC ISA developed by Hitachi in the early
|
||||
// '90s. Some relatively powerful systems were developed with SH-4.
|
||||
// A notable example is Sega's Dreamcast game console which debuted in 1998.
|
||||
// Hitachi later spun off its semiconductor division as an independent
|
||||
// company, Renesas, and Renesas is still selling SH-4 processors for the
|
||||
// embedded market. It has never been as popular as ARM is, and its
|
||||
// popularity continues to decline though.
|
||||
//
|
||||
// SH-4's most distinctive feature compared to other RISC ISAs is that its
|
||||
// instructions are 16 bits in length instead of more common 32 bits for
|
||||
// better code density. This difference affects various aspects of its
|
||||
// instruction set as shown below:
|
||||
//
|
||||
// - SH-4 has 16 general-purpose registers (GPRs) instead of the most
|
||||
// commmon 32 GPR configuration to save one bit to specify a register.
|
||||
//
|
||||
// - Binary instructions such as ADD normally take three register in
|
||||
// RISC ISAs (e.g. x ← y ⊕ z where x, y and z are registers), but
|
||||
// SH-4's instructions take only two registers. The result of an
|
||||
// operation is written to one of the source registers (e.g. x ← x ⊕ y).
|
||||
//
|
||||
// - Usual RISC ISAs have "load high" and "load low" instructions to set
|
||||
// an immediate to most significant and least significant bits in a
|
||||
// register to construct a full 32-bit value in a register. This
|
||||
// technique is hard to use in SH-4, as 16 bit instructions are too
|
||||
// small to contain large immediates. On SH-4, large immediates are
|
||||
// loaded from memory using `mov.l` PC-relative load instruction.
|
||||
//
|
||||
// Beside these, SH-4 has a delay branch slot just like contemporary MIPS
|
||||
// and SPARC. That is, one instruction after a branch instruction will
|
||||
// always be executed even if the branch is taken. Delay branch slot allows
|
||||
// a pipelined CPU to start and finish executing an instruction after a
|
||||
// branch regardless of the branch's condition, simplifying the processor's
|
||||
// implementation. It's considered a bad premature optimization nowadays,
|
||||
// though. Modern RISC processors don't have it.
|
||||
//
|
||||
// Here are notes about the SH-4 psABI:
|
||||
//
|
||||
// - If a source file is compiled with -fPIC, each function starts
|
||||
// with a piece of code to store the address of .got to %r12.
|
||||
// We can use the register in our PLT for position-independent output.
|
||||
//
|
||||
// - Even though it uses the RELA-type relocations, relocation addends
|
||||
// are stored not to the r_addend field but to the relocated section
|
||||
// contents for some reason. Therefore, it's effectively REL.
|
||||
//
|
||||
// - It looks like the ecosystem has bit-rotted. Some tests, especially
|
||||
// one using C++ exceptions, don't pass even with GNU ld.
|
||||
//
|
||||
// - GCC/SH4 tends to write dynamically-relocated data into .text, so the
|
||||
// output from the linker contains lots of text relocations. That's not
|
||||
// a problem with embedded programming, I guess.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = SH4;
|
||||
|
||||
// Even though SH-4 uses RELA-type relocations, addends are stored to
|
||||
// relocated places for some reason.
|
||||
template <>
|
||||
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_SH_DIR32:
|
||||
case R_SH_REL32:
|
||||
case R_SH_TLS_GD_32:
|
||||
case R_SH_TLS_LD_32:
|
||||
case R_SH_TLS_LDO_32:
|
||||
case R_SH_TLS_IE_32:
|
||||
case R_SH_TLS_LE_32:
|
||||
case R_SH_TLS_DTPMOD32:
|
||||
case R_SH_TLS_DTPOFF32:
|
||||
case R_SH_TLS_TPOFF32:
|
||||
case R_SH_GOT32:
|
||||
case R_SH_PLT32:
|
||||
case R_SH_GOTOFF:
|
||||
case R_SH_GOTPC:
|
||||
case R_SH_GOTPLT32:
|
||||
return *(ul32 *)loc;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0x02, 0xd2, // mov.l 1f, r2
|
||||
0xcc, 0x32, // add r12, r2
|
||||
0x22, 0x50, // mov.l @(8, r2), r0
|
||||
0x21, 0x52, // mov.l @(4, r2), r2
|
||||
0x2b, 0x40, // jmp @r0
|
||||
0x00, 0xe0, // mov #0, r0
|
||||
0, 0, 0, 0, // 1: .long GOTPLT
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::plt_hdr_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 12) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x02, 0xd2, // mov.l 1f, r2
|
||||
0x22, 0x50, // mov.l @(8, r2), r0
|
||||
0x21, 0x52, // mov.l @(4, r2), r2
|
||||
0x2b, 0x40, // jmp @r0
|
||||
0x00, 0xe0, // mov #0, r0
|
||||
0x09, 0x00, // nop
|
||||
0, 0, 0, 0, // 1: .long GOTPLT
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::plt_hdr_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 12) = ctx.gotplt->shdr.sh_addr;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0x01, 0xd0, // mov.l 1f, r0
|
||||
0xce, 0x00, // mov.l @(r0, r12), r0
|
||||
0x2b, 0x40, // jmp @r0
|
||||
0x01, 0xd1, // mov.l 2f, r1
|
||||
0, 0, 0, 0, // 1: .long GOTPLT_ENTRY
|
||||
0, 0, 0, 0, // 2: .long INDEX_IN_RELPLT
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::plt_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 8) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
*(ul32 *)(buf + 12) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x01, 0xd0, // mov.l 1f, r0
|
||||
0x02, 0x60, // mov.l @r0, r0
|
||||
0x2b, 0x40, // jmp @r0
|
||||
0x01, 0xd1, // mov.l 2f, r1
|
||||
0, 0, 0, 0, // 1: .long GOTPLT_ENTRY
|
||||
0, 0, 0, 0, // 2: .long INDEX_IN_RELPLT
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::plt_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 8) = sym.get_gotplt_addr(ctx);
|
||||
*(ul32 *)(buf + 12) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0x01, 0xd0, // mov.l 1f, r0
|
||||
0xce, 0x00, // mov.l @(r0, r12), r0
|
||||
0x2b, 0x40, // jmp @r0
|
||||
0x09, 0x00, // nop
|
||||
0, 0, 0, 0, // 1: .long GOT_ENTRY
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::pltgot_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 8) = sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x01, 0xd0, // mov.l 1f, r0
|
||||
0x02, 0x60, // mov.l @r0, r0
|
||||
0x2b, 0x40, // jmp @r0
|
||||
0x09, 0x00, // nop
|
||||
0, 0, 0, 0, // 1: .long GOT_ENTRY
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::pltgot_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 8) = sym.get_got_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_SH_DIR32:
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
case R_SH_REL32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = get_addend(loc, rel);
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SH_DIR32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_SH_REL32:
|
||||
case R_SH_PLT32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_SH_GOT32:
|
||||
*(ul32 *)loc = G;
|
||||
break;
|
||||
case R_SH_GOTPC:
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_SH_GOTOFF:
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_SH_TLS_GD_32:
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_SH_TLS_LD_32:
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_SH_TLS_LDO_32:
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_SH_TLS_IE_32:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_SH_TLS_LE_32:
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : get_addend(loc, rel);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SH_DIR32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on sh4";
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SH_DIR32:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_SH_REL32:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_SH_GOT32:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_SH_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_SH_TLS_GD_32:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_SH_TLS_LD_32:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_SH_TLS_IE_32:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_SH_GOTPC:
|
||||
case R_SH_GOTOFF:
|
||||
case R_SH_TLS_LDO_32:
|
||||
case R_SH_TLS_LE_32:
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,658 @@
|
||||
// SPARC is a RISC ISA developed by Sun Microsystems.
|
||||
//
|
||||
// The byte order of the processor is big-endian. Anything larger than a
|
||||
// byte is stored in the "reverse" order compared to little-endian
|
||||
// processors such as x86-64.
|
||||
//
|
||||
// All instructions are 4 bytes long and aligned to 4 bytes boundaries.
|
||||
//
|
||||
// A notable feature of SPARC is that, unlike other RISC ISAs, it doesn't
|
||||
// need range extension thunks. It is because the SPARC's CALL instruction
|
||||
// contains a whopping 30 bits immediate. The processor scales it by 4 to
|
||||
// extend it to 32 bits (this is doable because all instructions are
|
||||
// aligned to 4 bytes boundaries, so the least significant two bits are
|
||||
// always zero). That means CALL's reach is PC ± 2 GiB, elinating the
|
||||
// need of range extension thunks. It comes with the cost that the CALL
|
||||
// instruction alone takes 1/4th of the instruction encoding space,
|
||||
// though.
|
||||
//
|
||||
// SPARC has 32 general purpose registers. CALL instruction saves a return
|
||||
// address to %o7, which is an alias for %r15. Thread pointer is stored to
|
||||
// %g7 which is %r7.
|
||||
//
|
||||
// SPARC does not have PC-relative load/store instructions. To access data
|
||||
// in the position-independent manner, we usually first set the address of
|
||||
// .got to, for example, %l7, with the following piece of code
|
||||
//
|
||||
// sethi %hi(. - _GLOBAL_OFFSET_TABLE_), %l7
|
||||
// add %l7, %lo(. - _GLOBAL_OFFSET_TABLE_), %l7
|
||||
// call __sparc_get_pc_thunk.l7
|
||||
// nop
|
||||
//
|
||||
// where __sparc_get_pc_thunk.l7 is defined as
|
||||
//
|
||||
// retl
|
||||
// add %o7, %l7, %l7
|
||||
//
|
||||
// . SETHI and the following ADD materialize a 32 bits offset to .got.
|
||||
// CALL instruction sets a return address to $o7, and the subsequent ADD
|
||||
// adds it to the GOT offset to materialize the absolute address of .got.
|
||||
//
|
||||
// Note that we have a NOP after CALL and an ADD after RETL because of
|
||||
// SPARC's delay branch slots. That is, the SPARC processor always
|
||||
// executes one instruction after a branch even if the branch is taken.
|
||||
// This may seem like an odd behavior, and indeed it is considered as such
|
||||
// (that's a premature optimization for the early pipelined SPARC
|
||||
// processors), but that's been a part of the ISA's spec so that's what it
|
||||
// is.
|
||||
//
|
||||
// Note also that the .got address obtained this way is not shared between
|
||||
// functions, so functions can use an arbitrary register to hold the .got
|
||||
// address. That also means each function needs to execute the above piece
|
||||
// of code to become position-independent.
|
||||
//
|
||||
// This scheme is very similar to i386. That may not be a coincidence
|
||||
// because the i386 ELF psABI is created by Sun Microsystems too.
|
||||
//
|
||||
// https://docs.oracle.com/cd/E36784_01/html/E36857/chapter6-62988.html
|
||||
// https://docs.oracle.com/cd/E19120-01/open.solaris/819-0690/chapter8-40/index.html
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = SPARC64;
|
||||
|
||||
// SPARC's PLT section is writable despite containing executable code.
|
||||
// We don't need to write the PLT header entry because the dynamic loader
|
||||
// will do that for us.
|
||||
//
|
||||
// We also don't need a .got.plt section to store the result of lazy PLT
|
||||
// symbol resolution because the dynamic symbol resolver directly mutates
|
||||
// instructions in PLT so that they jump to the right places next time.
|
||||
// That's why each PLT entry contains lots of NOPs; they are a placeholder
|
||||
// for the runtime to add more instructions.
|
||||
//
|
||||
// Self-modifying code is nowadays considered really bad from the security
|
||||
// point of view, though.
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
memset(buf, 0, E::plt_hdr_size);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static ub32 insn[] = {
|
||||
0x0300'0000, // sethi (. - .PLT0), %g1
|
||||
0x3068'0000, // ba,a %xcc, .PLT1
|
||||
0x0100'0000, // nop
|
||||
0x0100'0000, // nop
|
||||
0x0100'0000, // nop
|
||||
0x0100'0000, // nop
|
||||
0x0100'0000, // nop
|
||||
0x0100'0000, // nop
|
||||
};
|
||||
|
||||
u64 plt0 = ctx.plt->shdr.sh_addr;
|
||||
u64 plt1 = ctx.plt->shdr.sh_addr + E::plt_size;
|
||||
u64 entry = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)buf |= bits(entry - plt0, 21, 0);
|
||||
*(ub32 *)(buf + 4) |= bits(plt1 - entry - 4, 20, 2);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static ub32 entry[] = {
|
||||
0x8a10'000f, // mov %o7, %g5
|
||||
0x4000'0002, // call . + 8
|
||||
0xc25b'e014, // ldx [ %o7 + 20 ], %g1
|
||||
0xc25b'c001, // ldx [ %o7 + %g1 ], %g1
|
||||
0x81c0'4000, // jmp %g1
|
||||
0x9e10'0005, // mov %g5, %o7
|
||||
0x0000'0000, // .quad $plt_entry - $got_entry
|
||||
0x0000'0000,
|
||||
};
|
||||
|
||||
memcpy(buf, entry, sizeof(entry));
|
||||
*(ub64 *)(buf + 24) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 4;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_SPARC_64:
|
||||
case R_SPARC_UA64:
|
||||
*(ub64 *)loc = val;
|
||||
break;
|
||||
case R_SPARC_DISP32:
|
||||
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = (get_addr() + rel.r_offset);
|
||||
u64 G = (sym.get_got_idx(ctx) * sizeof(Word<E>));
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SPARC_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_SPARC_5: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 5);
|
||||
*(ub32 *)loc |= bits(val, 4, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_6: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 6);
|
||||
*(ub32 *)loc |= bits(val, 5, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_7: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 7);
|
||||
*(ub32 *)loc |= bits(val, 6, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*(u8 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_10: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 10);
|
||||
*(ub32 *)loc |= bits(val, 9, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_LO10:
|
||||
case R_SPARC_LOPLT10:
|
||||
*(ub32 *)loc |= bits(S + A, 9, 0);
|
||||
break;
|
||||
case R_SPARC_11: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 11);
|
||||
*(ub32 *)loc |= bits(val, 10, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_13: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 13);
|
||||
*(ub32 *)loc |= bits(val, 12, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_16:
|
||||
case R_SPARC_UA16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ub16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_22: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 22);
|
||||
*(ub32 *)loc |= bits(val, 21, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_32:
|
||||
case R_SPARC_UA32:
|
||||
case R_SPARC_PLT32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_PLT64:
|
||||
case R_SPARC_UA64:
|
||||
case R_SPARC_REGISTER:
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
case R_SPARC_DISP8: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*(u8 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_DISP16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_DISP32:
|
||||
case R_SPARC_PCPLT32: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_DISP64:
|
||||
*(ub64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_SPARC_WDISP16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 16), 1 << 16);
|
||||
*(ub16 *)loc |= (bit(val, 16) << 21) | bits(val, 15, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_WDISP19: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 20), 1 << 20);
|
||||
*(ub32 *)loc |= bits(val, 20, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_WDISP22: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 23), 1 << 23);
|
||||
*(ub32 *)loc |= bits(val, 23, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_WDISP30:
|
||||
case R_SPARC_WPLT30: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc |= bits(val, 31, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_HI22:
|
||||
case R_SPARC_HIPLT22:
|
||||
case R_SPARC_LM22:
|
||||
*(ub32 *)loc |= bits(S + A, 31, 10);
|
||||
break;
|
||||
case R_SPARC_GOT10:
|
||||
*(ub32 *)loc |= bits(G, 9, 0);
|
||||
break;
|
||||
case R_SPARC_GOT13:
|
||||
check(G, 0, 1 << 12);
|
||||
*(ub32 *)loc |= bits(G, 12, 0);
|
||||
break;
|
||||
case R_SPARC_GOT22:
|
||||
*(ub32 *)loc |= bits(G, 31, 10);
|
||||
break;
|
||||
case R_SPARC_GOTDATA_HIX22: {
|
||||
i64 val = S + A - GOT;
|
||||
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_GOTDATA_LOX10: {
|
||||
i64 val = S + A - GOT;
|
||||
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_GOTDATA_OP_HIX22:
|
||||
// We always have to relax a GOT load to a load immediate if a
|
||||
// symbol is local, because R_SPARC_GOTDATA_OP cannot represent
|
||||
// an addend for a local symbol.
|
||||
if (sym.is_imported || sym.is_ifunc()) {
|
||||
*(ub32 *)loc |= bits(G, 31, 10);
|
||||
} else if (sym.is_absolute()) {
|
||||
i64 val = S + A;
|
||||
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
|
||||
} else {
|
||||
i64 val = S + A - GOT;
|
||||
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
|
||||
}
|
||||
break;
|
||||
case R_SPARC_GOTDATA_OP_LOX10: {
|
||||
if (sym.is_imported || sym.is_ifunc()) {
|
||||
*(ub32 *)loc |= bits(G, 9, 0);
|
||||
} else if (sym.is_absolute()) {
|
||||
i64 val = S + A;
|
||||
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
|
||||
} else {
|
||||
i64 val = S + A - GOT;
|
||||
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_SPARC_GOTDATA_OP:
|
||||
if (sym.is_imported || sym.is_ifunc())
|
||||
break;
|
||||
|
||||
if (sym.is_absolute()) {
|
||||
// ldx [ %g2 + %g1 ], %g1 → nop
|
||||
*(ub32 *)loc = 0x0100'0000;
|
||||
} else {
|
||||
// ldx [ %g2 + %g1 ], %g1 → add %g2, %g1, %g1
|
||||
*(ub32 *)loc &= 0b00'11111'000000'11111'1'11111111'11111;
|
||||
*(ub32 *)loc |= 0b10'00000'000000'00000'0'00000000'00000;
|
||||
}
|
||||
break;
|
||||
case R_SPARC_PC10:
|
||||
case R_SPARC_PCPLT10:
|
||||
*(ub32 *)loc |= bits(S + A - P, 9, 0);
|
||||
break;
|
||||
case R_SPARC_PC22:
|
||||
case R_SPARC_PCPLT22:
|
||||
case R_SPARC_PC_LM22:
|
||||
*(ub32 *)loc |= bits(S + A - P, 31, 10);
|
||||
break;
|
||||
case R_SPARC_OLO10:
|
||||
*(ub32 *)loc |= bits(bits(S + A, 9, 0) + rel.r_type_data, 12, 0);
|
||||
break;
|
||||
case R_SPARC_HH22:
|
||||
*(ub32 *)loc |= bits(S + A, 63, 42);
|
||||
break;
|
||||
case R_SPARC_HM10:
|
||||
*(ub32 *)loc |= bits(S + A, 41, 32);
|
||||
break;
|
||||
case R_SPARC_PC_HH22:
|
||||
*(ub32 *)loc |= bits(S + A - P, 63, 42);
|
||||
break;
|
||||
case R_SPARC_PC_HM10:
|
||||
*(ub32 *)loc |= bits(S + A - P, 41, 32);
|
||||
break;
|
||||
case R_SPARC_HIX22:
|
||||
*(ub32 *)loc |= bits(~(S + A), 31, 10);
|
||||
break;
|
||||
case R_SPARC_LOX10:
|
||||
*(ub32 *)loc |= bits(S + A, 9, 0) | 0b1'1100'0000'0000;
|
||||
break;
|
||||
case R_SPARC_H44:
|
||||
*(ub32 *)loc |= bits(S + A, 43, 22);
|
||||
break;
|
||||
case R_SPARC_M44:
|
||||
*(ub32 *)loc |= bits(S + A, 21, 12);
|
||||
break;
|
||||
case R_SPARC_L44:
|
||||
*(ub32 *)loc |= bits(S + A, 11, 0);
|
||||
break;
|
||||
case R_SPARC_TLS_GD_HI22:
|
||||
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 31, 10);
|
||||
break;
|
||||
case R_SPARC_TLS_GD_LO10:
|
||||
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 9, 0);
|
||||
break;
|
||||
case R_SPARC_TLS_GD_CALL:
|
||||
case R_SPARC_TLS_LDM_CALL:
|
||||
if (ctx.arg.is_static)
|
||||
*(ub32 *)loc |= bits(ctx.extra.tls_get_addr->shdr.sh_addr + A - P, 31, 2);
|
||||
else
|
||||
*(ub32 *)loc |= bits(ctx.tls_get_addr->get_addr(ctx) + A - P, 31, 2);
|
||||
break;
|
||||
case R_SPARC_TLS_LDM_HI22:
|
||||
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 31, 10);
|
||||
break;
|
||||
case R_SPARC_TLS_LDM_LO10:
|
||||
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 9, 0);
|
||||
break;
|
||||
case R_SPARC_TLS_LDO_HIX22:
|
||||
*(ub32 *)loc |= bits(S + A - ctx.dtp_addr, 31, 10);
|
||||
break;
|
||||
case R_SPARC_TLS_LDO_LOX10:
|
||||
*(ub32 *)loc |= bits(S + A - ctx.dtp_addr, 9, 0);
|
||||
break;
|
||||
case R_SPARC_TLS_IE_HI22:
|
||||
*(ub32 *)loc |= bits(sym.get_gottp_addr(ctx) + A - GOT, 31, 10);
|
||||
break;
|
||||
case R_SPARC_TLS_IE_LO10:
|
||||
*(ub32 *)loc |= bits(sym.get_gottp_addr(ctx) + A - GOT, 9, 0);
|
||||
break;
|
||||
case R_SPARC_TLS_LE_HIX22:
|
||||
*(ub32 *)loc |= bits(~(S + A - ctx.tp_addr), 31, 10);
|
||||
break;
|
||||
case R_SPARC_TLS_LE_LOX10:
|
||||
*(ub32 *)loc |= bits(S + A - ctx.tp_addr, 9, 0) | 0b1'1100'0000'0000;
|
||||
break;
|
||||
case R_SPARC_SIZE32:
|
||||
*(ub32 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
case R_SPARC_TLS_GD_ADD:
|
||||
case R_SPARC_TLS_LDM_ADD:
|
||||
case R_SPARC_TLS_LDO_ADD:
|
||||
case R_SPARC_TLS_IE_LD:
|
||||
case R_SPARC_TLS_IE_LDX:
|
||||
case R_SPARC_TLS_IE_ADD:
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SPARC_64:
|
||||
case R_SPARC_UA64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ub64 *)loc = *val;
|
||||
else
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
case R_SPARC_32:
|
||||
case R_SPARC_UA32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_TLS_DTPOFF32:
|
||||
*(ub32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_SPARC_TLS_DTPOFF64:
|
||||
*(ub64 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": apply_reloc_nonalloc: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SPARC_64:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_SPARC_8:
|
||||
case R_SPARC_5:
|
||||
case R_SPARC_6:
|
||||
case R_SPARC_7:
|
||||
case R_SPARC_10:
|
||||
case R_SPARC_11:
|
||||
case R_SPARC_13:
|
||||
case R_SPARC_16:
|
||||
case R_SPARC_22:
|
||||
case R_SPARC_32:
|
||||
case R_SPARC_REGISTER:
|
||||
case R_SPARC_UA16:
|
||||
case R_SPARC_UA32:
|
||||
case R_SPARC_UA64:
|
||||
case R_SPARC_PC_HM10:
|
||||
case R_SPARC_OLO10:
|
||||
case R_SPARC_LOX10:
|
||||
case R_SPARC_HM10:
|
||||
case R_SPARC_M44:
|
||||
case R_SPARC_HIX22:
|
||||
case R_SPARC_LO10:
|
||||
case R_SPARC_L44:
|
||||
case R_SPARC_LM22:
|
||||
case R_SPARC_HI22:
|
||||
case R_SPARC_H44:
|
||||
case R_SPARC_HH22:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_SPARC_PLT32:
|
||||
case R_SPARC_WPLT30:
|
||||
case R_SPARC_WDISP30:
|
||||
case R_SPARC_HIPLT22:
|
||||
case R_SPARC_LOPLT10:
|
||||
case R_SPARC_PCPLT32:
|
||||
case R_SPARC_PCPLT22:
|
||||
case R_SPARC_PCPLT10:
|
||||
case R_SPARC_PLT64:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_SPARC_GOT13:
|
||||
case R_SPARC_GOT10:
|
||||
case R_SPARC_GOT22:
|
||||
case R_SPARC_GOTDATA_HIX22:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_SPARC_GOTDATA_OP_HIX22:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_SPARC_DISP16:
|
||||
case R_SPARC_DISP32:
|
||||
case R_SPARC_DISP64:
|
||||
case R_SPARC_DISP8:
|
||||
case R_SPARC_PC10:
|
||||
case R_SPARC_PC22:
|
||||
case R_SPARC_PC_LM22:
|
||||
case R_SPARC_WDISP16:
|
||||
case R_SPARC_WDISP19:
|
||||
case R_SPARC_WDISP22:
|
||||
case R_SPARC_PC_HH22:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_SPARC_TLS_GD_HI22:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_SPARC_TLS_LDM_HI22:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_SPARC_TLS_IE_HI22:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_SPARC_TLS_GD_CALL:
|
||||
case R_SPARC_TLS_LDM_CALL:
|
||||
if (!ctx.arg.is_static && ctx.tls_get_addr->is_imported)
|
||||
ctx.tls_get_addr->flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_SPARC_GOTDATA_OP_LOX10:
|
||||
case R_SPARC_GOTDATA_OP:
|
||||
case R_SPARC_GOTDATA_LOX10:
|
||||
case R_SPARC_TLS_GD_LO10:
|
||||
case R_SPARC_TLS_GD_ADD:
|
||||
case R_SPARC_TLS_LDM_LO10:
|
||||
case R_SPARC_TLS_LDM_ADD:
|
||||
case R_SPARC_TLS_LDO_HIX22:
|
||||
case R_SPARC_TLS_LDO_LOX10:
|
||||
case R_SPARC_TLS_LDO_ADD:
|
||||
case R_SPARC_TLS_IE_ADD:
|
||||
case R_SPARC_TLS_IE_LD:
|
||||
case R_SPARC_TLS_IE_LDX:
|
||||
case R_SPARC_TLS_IE_LO10:
|
||||
case R_SPARC_TLS_LE_HIX22:
|
||||
case R_SPARC_TLS_LE_LOX10:
|
||||
case R_SPARC_SIZE32:
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": scan_relocations: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// __tls_get_addr is not defined by libc.a, so we can't use that function
|
||||
// in statically-linked executables. This section provides a replacement.
|
||||
void SparcTlsGetAddrSection::copy_buf(Context<E> &ctx) {
|
||||
ub32 *buf = (ub32 *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
static const ub32 insn[] = {
|
||||
0x0300'0000, // sethi %hi(TP_SIZE), %g1
|
||||
0x8210'6000, // or %g1, %lo(TP_SIZE), %g1
|
||||
0x8221'c001, // sub %g7, %g1, %g1
|
||||
0xd05a'2008, // ldx [ %o0 + 8 ], %o0
|
||||
0x81c3'e008, // retl
|
||||
0x9000'4008, // add %g1, %o0, %o0
|
||||
};
|
||||
|
||||
assert(this->shdr.sh_size == sizeof(insn));
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
|
||||
buf[0] |= bits(ctx.tp_addr - ctx.tls_begin, 31, 10);
|
||||
buf[1] |= bits(ctx.tp_addr - ctx.tls_begin, 9, 0);
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+284
-245
@@ -1,3 +1,31 @@
|
||||
// Supporting x86-64 is straightforward. Unlike its predecessor, i386,
|
||||
// x86-64 supports PC-relative addressing for position-independent code.
|
||||
// Being CISC, its instructions are variable in size. Branch instructions
|
||||
// take 4 bytes offsets, so we don't need range extension thunks.
|
||||
//
|
||||
// The psABI specifies %r11 as neither caller- nor callee-saved. It's
|
||||
// intentionally left out so that we can use it as a scratch register in
|
||||
// PLT.
|
||||
//
|
||||
// Thread Pointer (TP) is stored not to a general-purpose register but to
|
||||
// FS segment register. Segment register is a 64-bits register which can
|
||||
// be used as a base address for memory access. Each thread has a unique
|
||||
// FS value, and they access their thread-local variables relative to FS
|
||||
// as %fs:offset_from_tp.
|
||||
//
|
||||
// The value of a segment register itself is not generally readable from
|
||||
// the user space. As a workaround, libc initializes %fs:0 (the first word
|
||||
// referenced by FS) to the value of %fs itself. So we can obtain TP just
|
||||
// by `mov %fs:0, %rax` if we need it.
|
||||
//
|
||||
// For historical reasons, TP points past the end of the TLS block on x86.
|
||||
// This is contrary to other psABIs which usually use the beginning of the
|
||||
// TLS block as TP (with some addend). As a result, offsets from TP to
|
||||
// thread-local variables (TLVs) in the main executable are all negative.
|
||||
//
|
||||
// https://github.com/rui314/mold/wiki/i386-psabi.pdf
|
||||
// https://github.com/rui314/mold/wiki/x86-64-psabi.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
@@ -17,53 +45,47 @@ using E = X86_64;
|
||||
// Our PLT entry clobbers %r11, but that's fine because the resolver
|
||||
// function (_dl_runtime_resolve) clobbers %r11 anyway.
|
||||
template <>
|
||||
void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
memset(buf, 0xcc, this->shdr.sh_size);
|
||||
|
||||
// Write PLT header
|
||||
static const u8 plt0[] = {
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const u8 insn[] = {
|
||||
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)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
0xcc, 0xcc, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr - 4;
|
||||
*(ul32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr - 2;
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 4;
|
||||
*(ul32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 2;
|
||||
}
|
||||
|
||||
// Write PLT entries
|
||||
static const u8 data[] = {
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
i64 idx = sym->get_plt_idx(ctx);
|
||||
u8 *ent = buf + E::plt_hdr_size + idx * E::plt_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(ul32 *)(ent + 6) = idx;
|
||||
*(ul32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 16;
|
||||
}
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_plt_idx(ctx);
|
||||
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 16;
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
memset(buf, 0xcc, this->shdr.sh_size);
|
||||
|
||||
static const u8 data[] = {
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
0xcc, 0xcc, // (padding)
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * E::pltgot_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(ul32 *)(ent + 6) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 10;
|
||||
}
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -72,22 +94,23 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_NONE:
|
||||
return;
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_X86_64_32:
|
||||
*(ul32 *)loc = val;
|
||||
return;
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
*(ul64 *)loc = val;
|
||||
return;
|
||||
break;
|
||||
case R_X86_64_PC32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
break;
|
||||
case R_X86_64_PC64:
|
||||
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
static u32 relax_gotpcrelx(u8 *loc) {
|
||||
@@ -169,171 +192,171 @@ static u32 relax_gotpc32_tlsdesc(u8 *loc) {
|
||||
// scan_relocations().
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_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)
|
||||
if (rel.r_type == R_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) {
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto 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);
|
||||
*(ul16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write16s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
overflow_check(val, 0, (i64)1 << 32);
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32s = [&](u64 val) {
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write64 = [&](u64 val) {
|
||||
*(ul64 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? (u64)frag_ref->addend : (u64)rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr)
|
||||
#define GOT ctx.gotplt->shdr.sh_addr
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr;
|
||||
u64 GOTPLT = 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_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_32:
|
||||
write32(S + A);
|
||||
continue;
|
||||
break;
|
||||
case R_X86_64_32S:
|
||||
write32s(S + A);
|
||||
continue;
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
apply_abs_dyn_rel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
continue;
|
||||
case R_X86_64_PC8:
|
||||
write8s(S + A - P);
|
||||
continue;
|
||||
case R_X86_64_PC16:
|
||||
write16s(S + A - P);
|
||||
continue;
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_X86_64_PC8: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC32:
|
||||
write32s(S + A - P);
|
||||
continue;
|
||||
case R_X86_64_PC64:
|
||||
write64(S + A - P);
|
||||
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_PC64:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_X86_64_GOT32:
|
||||
write32s(G + A);
|
||||
continue;
|
||||
break;
|
||||
case R_X86_64_GOT64:
|
||||
write64(G + A);
|
||||
continue;
|
||||
*(ul64 *)loc = G + A;
|
||||
break;
|
||||
case R_X86_64_GOTOFF64:
|
||||
write64(S + A - GOT);
|
||||
continue;
|
||||
case R_X86_64_PLTOFF64:
|
||||
*(ul64 *)loc = S + A - GOTPLT;
|
||||
break;
|
||||
case R_X86_64_GOTPC32:
|
||||
write32s(GOT + A - P);
|
||||
continue;
|
||||
write32s(GOTPLT + A - P);
|
||||
break;
|
||||
case R_X86_64_GOTPC64:
|
||||
write64(GOT + A - P);
|
||||
continue;
|
||||
*(ul64 *)loc = GOTPLT + A - P;
|
||||
break;
|
||||
case R_X86_64_GOTPCREL:
|
||||
write32s(G + GOT + A - P);
|
||||
continue;
|
||||
write32s(G + GOTPLT + A - P);
|
||||
break;
|
||||
case R_X86_64_GOTPCREL64:
|
||||
write64(G + GOT + A - P);
|
||||
continue;
|
||||
*(ul64 *)loc = G + GOTPLT + A - P;
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX:
|
||||
if (sym.get_got_idx(ctx) == -1) {
|
||||
if (sym.has_got(ctx)) {
|
||||
write32s(G + GOTPLT + A - P);
|
||||
} else {
|
||||
u32 insn = relax_gotpcrelx(loc - 2);
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S + A - P);
|
||||
} else {
|
||||
write32s(G + GOT + A - P);
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
if (sym.get_got_idx(ctx) == -1) {
|
||||
if (sym.has_got(ctx)) {
|
||||
write32s(G + GOTPLT + A - P);
|
||||
} else {
|
||||
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;
|
||||
break;
|
||||
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);
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
write32s(sym.get_tlsgd_addr(ctx) + A - P);
|
||||
} else {
|
||||
// Relax GD to LE. If we are creating an exectuable, the offset of
|
||||
// a thread-local variable from TP is a link-time constant. So we
|
||||
// don't need to call __tls_get_addr to obtain the address of a TLV.
|
||||
i64 val = S - ctx.tp_addr;
|
||||
assert(A == -4);
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 66 66 48 e8 00 00 00 00 call __tls_get_addr
|
||||
//
|
||||
// or
|
||||
//
|
||||
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 66 48 ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
|
||||
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $val, %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 8) = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr, %rax
|
||||
// 48 01 d8 add %rbx, %rax
|
||||
// ff d0 call *%rax
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
|
||||
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $val, %rax
|
||||
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
@@ -345,37 +368,60 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
}
|
||||
|
||||
i++;
|
||||
} else {
|
||||
write32s(sym.get_tlsgd_addr(ctx) + A - P);
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
case R_X86_64_TLSLD:
|
||||
if (ctx.got->tlsld_idx == -1) {
|
||||
// Relax LD to LE
|
||||
if (ctx.got->has_tlsld(ctx)) {
|
||||
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
|
||||
} else {
|
||||
// Relax LD to LE. If we are creating an executable, we don't need
|
||||
// to call __tls_get_addr to obtain the address of the beginning
|
||||
// of the current TLS block. TP points past the end of the TLS
|
||||
// block, and we know the exact size of the TLS block we have
|
||||
// created, so we can just subtract it from TP.
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_X86_64_PLT32: {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// e8 00 00 00 00 call __tls_get_addr
|
||||
static const u8 insn[] = {
|
||||
0x66, 0x66, 0x66, // (padding)
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
|
||||
static const u8 insn[] = {
|
||||
0x66, 0x66, 0x66, // (padding)
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x90, // nop
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr@GOTOFF, %rax
|
||||
// 48 01 d8 add %rbx, %rax
|
||||
// ff d0 call *%rax
|
||||
static const u8 insn[] = {
|
||||
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
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
@@ -384,73 +430,63 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
unreachable();
|
||||
}
|
||||
|
||||
*(ul32 *)(loc + 5) = ctx.tp_addr - ctx.tls_begin;
|
||||
assert(A == -4);
|
||||
i++;
|
||||
} else {
|
||||
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
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;
|
||||
write32s(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
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;
|
||||
*(ul64 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_X86_64_TPOFF32:
|
||||
write32s(S + A - ctx.tls_end);
|
||||
continue;
|
||||
write32s(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_X86_64_TPOFF64:
|
||||
write64(S + A - ctx.tls_end);
|
||||
continue;
|
||||
*(ul64 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_X86_64_GOTTPOFF:
|
||||
if (sym.get_gottp_idx(ctx) == -1) {
|
||||
if (sym.has_gottp(ctx)) {
|
||||
write32s(sym.get_gottp_addr(ctx) + A - P);
|
||||
} else {
|
||||
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);
|
||||
write32s(S - ctx.tp_addr);
|
||||
assert(A == -4);
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
|
||||
} else {
|
||||
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);
|
||||
write32s(S - ctx.tp_addr);
|
||||
assert(A == -4);
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
continue;
|
||||
break;
|
||||
case R_X86_64_SIZE64:
|
||||
write64(sym.esym().st_size + A);
|
||||
continue;
|
||||
*(ul64 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,7 +508,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_X86_64_NONE)
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
@@ -483,47 +519,43 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 addend;
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto write8 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
overflow_check(val, 0, (i64)1 << 32);
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32s = [&](u64 val) {
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? (u64)addend : (u64)rel.r_addend)
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
write8(S + A);
|
||||
case R_X86_64_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
break;
|
||||
case R_X86_64_16:
|
||||
write16(S + A);
|
||||
}
|
||||
case R_X86_64_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_32:
|
||||
write32(S + A);
|
||||
break;
|
||||
@@ -531,25 +563,22 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
write32s(S + A);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
if (!frag) {
|
||||
if (std::optional<u64> val = get_tombstone(sym)) {
|
||||
*(ul64 *)loc = *val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*(ul64 *)loc = S + A;
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
write32s(S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_X86_64_DTPOFF64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(ul64 *)loc = S + A - ctx.tls_begin;
|
||||
*(ul64 *)loc = S + A;
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
write32s(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_X86_64_DTPOFF64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(ul64 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
@@ -562,9 +591,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,7 +609,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_X86_64_NONE)
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
@@ -594,24 +620,24 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC)
|
||||
sym.flags |= (NEEDS_GOT | NEEDS_PLT);
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
case R_X86_64_16:
|
||||
case R_X86_64_32:
|
||||
case R_X86_64_32S:
|
||||
scan_abs_rel(ctx, sym, rel);
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
scan_abs_dyn_rel(ctx, sym, rel);
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_X86_64_PC8:
|
||||
case R_X86_64_PC16:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_PC64:
|
||||
scan_pcrel_rel(ctx, sym, rel);
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_X86_64_GOT32:
|
||||
case R_X86_64_GOT64:
|
||||
@@ -619,7 +645,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_X86_64_GOTPC64:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCREL64:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
if (rel.r_addend != -4)
|
||||
@@ -628,7 +654,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
bool do_relax = ctx.arg.relax && !sym.is_imported &&
|
||||
sym.is_relative() && relax_gotpcrelx(loc - 2);
|
||||
if (!do_relax)
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_REX_GOTPCRELX: {
|
||||
@@ -638,61 +664,74 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
bool do_relax = ctx.arg.relax && !sym.is_imported &&
|
||||
sym.is_relative() && relax_rex_gotpcrelx(loc - 3);
|
||||
if (!do_relax)
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PLTOFF64:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_X86_64_TLSGD: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSGD";
|
||||
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PLTOFF64 &&
|
||||
ty != R_X86_64_GOTPCREL && ty != R_X86_64_GOTPCRELX)
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
|
||||
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
|
||||
ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported)
|
||||
if (relax_tlsgd(ctx, sym))
|
||||
i++;
|
||||
else
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_TLSLD: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSLD";
|
||||
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PLTOFF64 &&
|
||||
ty != R_X86_64_GOTPCREL && ty != R_X86_64_GOTPCRELX)
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
|
||||
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
|
||||
ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
if (relax_tlsld(ctx))
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld = true;
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTTPOFF: {
|
||||
ctx.has_gottp_rel = true;
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTTPOFF";
|
||||
|
||||
ctx.has_gottp_rel.store(true, std::memory_order_relaxed);
|
||||
|
||||
bool do_relax = ctx.arg.relax && !ctx.arg.shared &&
|
||||
!sym.is_imported && relax_gottpoff(loc - 3);
|
||||
if (!do_relax)
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPC32_TLSDESC: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPC32_TLSDESC";
|
||||
|
||||
if (relax_gotpc32_tlsdesc(loc - 3) == 0)
|
||||
Fatal(ctx) << *this << ": GOTPC32_TLSDESC relocation is used"
|
||||
<< " against an invalid code sequence";
|
||||
|
||||
bool do_relax = ctx.relax_tlsdesc && !sym.is_imported;
|
||||
if (!do_relax)
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTOFF64:
|
||||
|
||||
+205
-44
@@ -16,7 +16,7 @@
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
static const char helpmsg[] = R"(
|
||||
inline const char helpmsg[] = R"(
|
||||
Options:
|
||||
--help Report usage information
|
||||
-v, --version Report version information
|
||||
@@ -61,6 +61,8 @@ Options:
|
||||
--Tdata Set address to .data
|
||||
--Ttext Set address to .text
|
||||
--allow-multiple-definition Allow multiple definitions
|
||||
--apply-dynamic-relocs Apply link-time values for dynamic relocations (defualt)
|
||||
--no-apply-dynamic-relocs
|
||||
--as-needed Only set DT_NEEDED if used
|
||||
--no-as-needed
|
||||
--build-id [none,md5,sha1,sha256,uuid,HEXSTRING]
|
||||
@@ -70,7 +72,7 @@ Options:
|
||||
--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 [none,zlib,zlib-gabi,zstd]
|
||||
Compress .debug_* sections
|
||||
--dc Ignored
|
||||
--dependency-file=FILE Write Makefile-style dependency rules to FILE
|
||||
@@ -79,11 +81,11 @@ Options:
|
||||
--no-demangle
|
||||
--enable-new-dtags Emit DT_RUNPATH for --rpath (default)
|
||||
--disable-new-dtags Emit DT_RPATH for --rpath
|
||||
--execute-only Make executable segments unreadable
|
||||
--dp Ignored
|
||||
--dynamic-list Read a list of dynamic symbols (implies -Bsymbolic)
|
||||
--eh-frame-hdr Create .eh_frame_hdr section
|
||||
--no-eh-frame-hdr
|
||||
--enable-new-dtags Ignored
|
||||
--exclude-libs LIB,LIB,.. Mark all symbols in given libraries hidden
|
||||
--export-dynamic-symbol Put symbols matching glob in the dynamic symbol table
|
||||
--export-dynamic-symbol-list
|
||||
@@ -103,7 +105,7 @@ Options:
|
||||
--ignore-data-address-equality
|
||||
Allow merging non-executable sections with --icf
|
||||
--image-base ADDR Set the base address to a given value
|
||||
--init SYMBOL Call SYMBOl at load-time
|
||||
--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
|
||||
@@ -113,12 +115,12 @@ Options:
|
||||
--perf Print performance statistics
|
||||
--pie, --pic-executable Create a position independent executable
|
||||
--no-pie, --no-pic-executable
|
||||
--pop-state Pop state of flags governing input file handling
|
||||
--pop-state Restore state of flags governing input file handling
|
||||
--print-gc-sections Print removed unreferenced sections
|
||||
--no-print-gc-sections
|
||||
--print-icf-sections Print folded identical sections
|
||||
--no-print-icf-sections
|
||||
--push-state Pop state of flags governing input file handling
|
||||
--push-state Save state of flags governing input file handling
|
||||
--quick-exit Use quick_exit to exit (default)
|
||||
--no-quick-exit
|
||||
--relax Optimize instructions (default)
|
||||
@@ -147,6 +149,8 @@ Options:
|
||||
--threads Use multiple threads (default)
|
||||
--no-threads
|
||||
--trace Print name of each input file
|
||||
--undefined-version Do not report version scripts that refer undefined symbols
|
||||
--no-undefined-version Report version scripts that refer undefined symbols (default)
|
||||
--unique PATTERN Don't merge input sections that match a given pattern
|
||||
--unresolved-symbols [report-all,ignore-all,ignore-in-object-files,ignore-in-shared-libs]
|
||||
How to handle unresolved symbols
|
||||
@@ -181,6 +185,8 @@ Options:
|
||||
-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 pack-relative-relocs Alias for --pack-dyn-relocs=relr
|
||||
-z nopack-relative-relocs
|
||||
-z separate-loadable-segments
|
||||
Separate all loadable segments to different pages
|
||||
-z separate-code Separate code and data into different pages
|
||||
@@ -191,8 +197,8 @@ Options:
|
||||
-z notext
|
||||
-z textoff
|
||||
|
||||
mold: supported targets: elf32-i386 elf64-x86-64 elf32-littlearm elf64-littleaarch64 elf32-littleriscv elf64-littleriscv
|
||||
mold: supported emulations: elf_i386 elf_x86_64 armelf_linux_eabi aarch64linux aarch64elf elf32lriscv elf64lriscv)";
|
||||
mold: supported targets: elf32-i386 elf64-x86-64 elf32-littlearm elf64-littleaarch64 elf32-littleriscv elf32-bigriscv elf64-littleriscv elf64-bigriscv elf32-powerpc elf64-powerpc elf64-powerpc elf64-powerpcle elf64-s390 elf64-sparc elf32-m68k elf32-sh-linux elf64-alpha elf32-hppa-linux
|
||||
mold: supported emulations: elf_i386 elf_x86_64 armelf_linux_eabi aarch64linux aarch64elf elf32lriscv elf32briscv elf64lriscv elf64briscv elf32ppc elf32ppclinux elf64ppc elf64lppc elf64_s390 elf64_sparc m68kelf shlelf_linux elf64alpha hppalinux)";
|
||||
|
||||
static std::vector<std::string> add_dashes(std::string name) {
|
||||
// Single-letter option
|
||||
@@ -311,6 +317,63 @@ static bool is_file(std::string_view path) {
|
||||
(st.st_mode & S_IFMT) != S_IFDIR;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<SectionOrder>
|
||||
parse_section_order(Context<E> &ctx, std::string_view arg) {
|
||||
auto flags = std::regex_constants::ECMAScript | std::regex_constants::icase |
|
||||
std::regex_constants::optimize;
|
||||
static std::regex re1(R"(^\s*(TEXT|DATA|RODATA|BSS)(?:\s|$))", flags);
|
||||
static std::regex re2(R"(^\s*([a-zA-Z0-9_.][^\s]*|EHDR|PHDR)(?:\s|$))", flags);
|
||||
static std::regex re3(R"(^\s*=(0x[0-9a-f]+|\d+)(?:\s|$))", flags);
|
||||
static std::regex re4(R"(^\s*%(0x[0-9a-f]+|\d*)(?:\s|$))", flags);
|
||||
static std::regex re5(R"(^\s*!(\S+)(?:\s|$))", flags);
|
||||
|
||||
std::vector<SectionOrder> vec;
|
||||
arg = string_trim(arg);
|
||||
|
||||
while (!arg.empty()) {
|
||||
SectionOrder ord;
|
||||
std::cmatch m;
|
||||
|
||||
if (std::regex_search(arg.data(), arg.data() + arg.size(), m, re1)) {
|
||||
ord.type = SectionOrder::GROUP;
|
||||
ord.name = m[1].str();
|
||||
} else if (std::regex_search(arg.data(), arg.data() + arg.size(), m, re2)) {
|
||||
ord.type = SectionOrder::SECTION;
|
||||
ord.name = m[1].str();
|
||||
} else if (std::regex_search(arg.data(), arg.data() + arg.size(), m, re3)) {
|
||||
ord.type = SectionOrder::ADDR;
|
||||
std::string s = m[1];
|
||||
ord.value = std::stoull(s, nullptr, s.starts_with("0x") ? 16 : 10);
|
||||
} else if (std::regex_search(arg.data(), arg.data() + arg.size(), m, re4)) {
|
||||
ord.type = SectionOrder::ALIGN;
|
||||
std::string s = m[1];
|
||||
ord.value = std::stoull(s, nullptr, s.starts_with("0x") ? 16 : 10);
|
||||
} else if (std::regex_search(arg.data(), arg.data() + arg.size(), m, re5)) {
|
||||
ord.type = SectionOrder::SYMBOL;
|
||||
ord.name = m[1].str();
|
||||
} else {
|
||||
Fatal(ctx) << "--section-order: parse error: " << arg;
|
||||
}
|
||||
|
||||
vec.push_back(ord);
|
||||
arg = arg.substr(m[0].length());
|
||||
}
|
||||
|
||||
bool is_first = true;
|
||||
for (SectionOrder &ord : vec) {
|
||||
if (ord.type == SectionOrder::SECTION) {
|
||||
if (is_first) {
|
||||
is_first = false;
|
||||
} else if (ord.name == "EHDR")
|
||||
Fatal(ctx) << "--section-order: EHDR must be the first "
|
||||
<< "section specifier: " << arg;
|
||||
}
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::variant<Symbol<E> *, u64>
|
||||
parse_defsym_value(Context<E> &ctx, std::string_view s) {
|
||||
@@ -340,12 +403,29 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
|
||||
bool version_shown = false;
|
||||
bool warn_shared_textrel = false;
|
||||
std::optional<SeparateCodeKind> z_separate_code;
|
||||
std::optional<bool> z_relro;
|
||||
std::unordered_set<std::string_view> rpaths;
|
||||
|
||||
auto add_rpath = [&](std::string_view arg) {
|
||||
if (rpaths.insert(arg).second) {
|
||||
if (!ctx.arg.rpaths.empty())
|
||||
ctx.arg.rpaths += ':';
|
||||
ctx.arg.rpaths += arg;
|
||||
}
|
||||
};
|
||||
|
||||
// RISC-V object files contains lots of local symbols, so by default
|
||||
// we discard them. This is compatible with GNU ld.
|
||||
if constexpr (is_riscv<E>)
|
||||
ctx.arg.discard_locals = true;
|
||||
|
||||
// It looks like the SPARC's dynamic linker takes both RELA's r_addend
|
||||
// and the value at the relocated place. So we don't want to write
|
||||
// values to relocated places.
|
||||
if (is_sparc<E>)
|
||||
ctx.arg.apply_dynamic_relocs = false;
|
||||
|
||||
auto read_arg = [&](std::string name) {
|
||||
for (std::string opt : add_dashes(name)) {
|
||||
if (args[0] == opt) {
|
||||
@@ -438,21 +518,46 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
SyncOut(ctx) << mold_version
|
||||
<< "\n Supported emulations:\n elf_x86_64\n elf_i386\n"
|
||||
<< " aarch64linux\n armelf_linux_eabi\n elf64lriscv\n"
|
||||
<< " elf32lriscv";
|
||||
<< " elf64briscv\n elf32lriscv\n elf32briscv\n"
|
||||
<< " elf32ppc\n elf64ppc\n elf64lppc\n elf64_s390\n"
|
||||
<< " elf64_sparc\n m68kelf\n shlelf_linux\n"
|
||||
<< " elf64alpha\n hppalinux";
|
||||
version_shown = true;
|
||||
} else if (read_arg("m")) {
|
||||
if (arg == "elf_x86_64") {
|
||||
ctx.arg.emulation = MachineType::X86_64;
|
||||
ctx.arg.emulation = X86_64::target_name;
|
||||
} else if (arg == "elf_i386") {
|
||||
ctx.arg.emulation = MachineType::I386;
|
||||
ctx.arg.emulation = I386::target_name;
|
||||
} else if (arg == "aarch64linux") {
|
||||
ctx.arg.emulation = MachineType::ARM64;
|
||||
ctx.arg.emulation = ARM64::target_name;
|
||||
} else if (arg == "armelf_linux_eabi") {
|
||||
ctx.arg.emulation = MachineType::ARM32;
|
||||
ctx.arg.emulation = ARM32::target_name;
|
||||
} else if (arg == "elf64lriscv") {
|
||||
ctx.arg.emulation = MachineType::RISCV64;
|
||||
ctx.arg.emulation = RV64LE::target_name;
|
||||
} else if (arg == "elf64briscv") {
|
||||
ctx.arg.emulation = RV64BE::target_name;
|
||||
} else if (arg == "elf32lriscv") {
|
||||
ctx.arg.emulation = MachineType::RISCV32;
|
||||
ctx.arg.emulation = RV32LE::target_name;
|
||||
} else if (arg == "elf32briscv") {
|
||||
ctx.arg.emulation = RV32BE::target_name;
|
||||
} else if (arg == "elf32ppc" || arg == "elf32ppclinux") {
|
||||
ctx.arg.emulation = PPC32::target_name;
|
||||
} else if (arg == "elf64ppc") {
|
||||
ctx.arg.emulation = PPC64V1::target_name;
|
||||
} else if (arg == "elf64lppc") {
|
||||
ctx.arg.emulation = PPC64V2::target_name;
|
||||
} else if (arg == "elf64_s390") {
|
||||
ctx.arg.emulation = S390X::target_name;
|
||||
} else if (arg == "elf64_sparc") {
|
||||
ctx.arg.emulation = SPARC64::target_name;
|
||||
} else if (arg == "m68kelf") {
|
||||
ctx.arg.emulation = M68K::target_name;
|
||||
} else if (arg == "shlelf_linux") {
|
||||
ctx.arg.emulation = SH4::target_name;
|
||||
} else if (arg == "elf64alpha") {
|
||||
ctx.arg.emulation = ALPHA::target_name;
|
||||
} else if (arg == "hppalinux") {
|
||||
ctx.arg.emulation = HPPA32::target_name;
|
||||
} else {
|
||||
Fatal(ctx) << "unknown -m argument: " << arg;
|
||||
}
|
||||
@@ -473,6 +578,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
append(ctx.arg.exclude_libs, split_by_comma_or_colon(arg));
|
||||
} else if (read_flag("q") || read_flag("emit-relocs")) {
|
||||
ctx.arg.emit_relocs = true;
|
||||
ctx.arg.discard_locals = false;
|
||||
} else if (read_arg("e") || read_arg("entry")) {
|
||||
ctx.arg.entry = arg;
|
||||
} else if (read_arg("Map")) {
|
||||
@@ -496,6 +602,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.spare_dynamic_tags = parse_number(ctx, "spare-dynamic-tags", arg);
|
||||
} else if (read_flag("start-lib")) {
|
||||
remaining.push_back("--start-lib");
|
||||
} else if (read_flag("start-stop")) {
|
||||
ctx.arg.start_stop = true;
|
||||
} else if (read_arg("dependency-file")) {
|
||||
ctx.arg.dependency_file = arg;
|
||||
} else if (read_arg("defsym")) {
|
||||
@@ -573,6 +681,10 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.soname = arg;
|
||||
} else if (read_flag("allow-multiple-definition")) {
|
||||
ctx.arg.allow_multiple_definition = true;
|
||||
} else if (read_flag("apply-dynamic-relocs")) {
|
||||
ctx.arg.apply_dynamic_relocs = true;
|
||||
} else if (read_flag("no-apply-dynamic-relocs")) {
|
||||
ctx.arg.apply_dynamic_relocs = false;
|
||||
} else if (read_flag("trace")) {
|
||||
ctx.arg.trace = true;
|
||||
} else if (read_flag("eh-frame-hdr")) {
|
||||
@@ -595,6 +707,10 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.gdb_index = false;
|
||||
} else if (read_flag("r") || read_flag("relocatable")) {
|
||||
ctx.arg.relocatable = true;
|
||||
ctx.arg.emit_relocs = true;
|
||||
ctx.arg.discard_locals = false;
|
||||
} else if (read_flag("relocatable-merge-sections")) {
|
||||
ctx.arg.relocatable_merge_sections = true;
|
||||
} else if (read_flag("perf")) {
|
||||
ctx.arg.perf = true;
|
||||
} else if (read_flag("pack-dyn-relocs=relr")) {
|
||||
@@ -631,11 +747,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.enable_new_dtags = true;
|
||||
} else if (read_flag("disable-new-dtags")) {
|
||||
ctx.arg.enable_new_dtags = false;
|
||||
} else if (read_flag("execute-only")) {
|
||||
ctx.arg.execute_only = true;
|
||||
} else if (read_arg("compress-debug-sections")) {
|
||||
if (arg == "zlib" || arg == "zlib-gabi")
|
||||
ctx.arg.compress_debug_sections = COMPRESS_GABI;
|
||||
else if (arg == "zlib-gnu")
|
||||
ctx.arg.compress_debug_sections = COMPRESS_GNU;
|
||||
ctx.arg.compress_debug_sections = COMPRESS_ZLIB;
|
||||
else if (arg == "zstd")
|
||||
ctx.arg.compress_debug_sections = COMPRESS_ZSTD;
|
||||
else if (arg == "none")
|
||||
ctx.arg.compress_debug_sections = COMPRESS_NONE;
|
||||
else
|
||||
@@ -653,12 +771,22 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.oformat_binary = true;
|
||||
} else if (read_arg("retain-symbols-file")) {
|
||||
read_retain_symbols_file(ctx, arg);
|
||||
} else if (read_arg("section-align")) {
|
||||
size_t pos = arg.find('=');
|
||||
if (pos == arg.npos || pos == arg.size() - 1)
|
||||
Fatal(ctx) << "--section-align: syntax error: " << arg;
|
||||
i64 value = parse_number(ctx, "section-align", arg.substr(pos + 1));
|
||||
if (!has_single_bit(value))
|
||||
Fatal(ctx) << "--section-align=" << arg << ": value must be a power of 2";
|
||||
ctx.arg.section_align[arg.substr(0, pos)] = value;
|
||||
} else if (read_arg("section-start")) {
|
||||
size_t pos = arg.find('=');
|
||||
if (pos == arg.npos || pos == arg.size() - 1)
|
||||
Fatal(ctx) << "-section-start: syntax error: " << arg;
|
||||
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("section-order")) {
|
||||
ctx.arg.section_order = parse_section_order(ctx, arg);
|
||||
} else if (read_arg("Tbss")) {
|
||||
ctx.arg.section_start[".bss"] = parse_hex(ctx, "Tbss", arg);
|
||||
} else if (read_arg("Tdata")) {
|
||||
@@ -685,15 +813,18 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.page_size = parse_number(ctx, "-z max-page-size", arg);
|
||||
if (!has_single_bit(ctx.page_size))
|
||||
Fatal(ctx) << "-z max-page-size " << arg << ": value must be a power of 2";
|
||||
} else if (read_z_arg("start-stop-visibility")) {
|
||||
if (arg != "hidden")
|
||||
Fatal(ctx) << "-z start-stop-visibility: unsupported visibility: " << arg;
|
||||
} else if (read_z_flag("noexecstack")) {
|
||||
ctx.arg.z_execstack = false;
|
||||
} else if (read_z_flag("relro")) {
|
||||
ctx.arg.z_relro = true;
|
||||
z_relro = true;
|
||||
} else if (read_z_flag("norelro")) {
|
||||
ctx.arg.z_relro = false;
|
||||
z_relro = false;
|
||||
} else if (read_z_flag("defs")) {
|
||||
ctx.arg.z_defs = true;
|
||||
} else if (read_z_flag("nodefs")) {
|
||||
} else if (read_z_flag("undefs")) {
|
||||
ctx.arg.z_defs = false;
|
||||
} else if (read_z_flag("nodlopen")) {
|
||||
ctx.arg.z_dlopen = false;
|
||||
@@ -726,12 +857,20 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.z_origin = true;
|
||||
} else if (read_z_flag("nodefaultlib")) {
|
||||
ctx.arg.z_nodefaultlib = true;
|
||||
} else if (read_z_flag("pack-relative-relocs")) {
|
||||
ctx.arg.pack_dyn_relocs_relr = true;
|
||||
} else if (read_z_flag("nopack-relative-relocs")) {
|
||||
ctx.arg.pack_dyn_relocs_relr = false;
|
||||
} else if (read_z_flag("separate-loadable-segments")) {
|
||||
ctx.arg.z_separate_code = SEPARATE_LOADABLE_SEGMENTS;
|
||||
z_separate_code = SEPARATE_LOADABLE_SEGMENTS;
|
||||
} else if (read_z_flag("separate-code")) {
|
||||
ctx.arg.z_separate_code = SEPARATE_CODE;
|
||||
z_separate_code = SEPARATE_CODE;
|
||||
} else if (read_z_flag("noseparate-code")) {
|
||||
ctx.arg.z_separate_code = NOSEPARATE_CODE;
|
||||
z_separate_code = NOSEPARATE_CODE;
|
||||
} else if (read_z_flag("dynamic-undefined-weak")) {
|
||||
ctx.arg.z_dynamic_undefined_weak = true;
|
||||
} else if (read_z_flag("nodynamic-undefined-weak")) {
|
||||
ctx.arg.z_dynamic_undefined_weak = false;
|
||||
} else if (read_flag("no-undefined")) {
|
||||
ctx.arg.z_defs = true;
|
||||
} else if (read_flag("fatal-warnings")) {
|
||||
@@ -767,6 +906,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.ignore_data_address_equality = true;
|
||||
} else if (read_arg("image-base")) {
|
||||
ctx.arg.image_base = parse_number(ctx, "image-base", arg);
|
||||
} else if (read_arg("physical-image-base")) {
|
||||
ctx.arg.physical_image_base = parse_number(ctx, "physical-image-base", arg);
|
||||
} else if (read_flag("print-icf-sections")) {
|
||||
ctx.arg.print_icf_sections = true;
|
||||
} else if (read_flag("no-print-icf-sections")) {
|
||||
@@ -861,17 +1002,16 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_flag("error-unresolved-symbols")) {
|
||||
ctx.arg.unresolved_symbols = UNRESOLVED_ERROR;
|
||||
} else if (read_arg("rpath")) {
|
||||
if (!ctx.arg.rpaths.empty())
|
||||
ctx.arg.rpaths += ":";
|
||||
ctx.arg.rpaths += arg;
|
||||
add_rpath(arg);
|
||||
} else if (read_arg("R")) {
|
||||
if (is_file(arg))
|
||||
Fatal(ctx) << "-R" << arg
|
||||
<< ": -R as an alias for --just-symbols is not supported";
|
||||
|
||||
if (!ctx.arg.rpaths.empty())
|
||||
ctx.arg.rpaths += ":";
|
||||
ctx.arg.rpaths += arg;
|
||||
add_rpath(arg);
|
||||
} else if (read_flag("undefined-version")) {
|
||||
ctx.arg.undefined_version = true;
|
||||
} else if (read_flag("no-undefined-version")) {
|
||||
ctx.arg.undefined_version = false;
|
||||
} else if (read_flag("build-id")) {
|
||||
ctx.arg.build_id.kind = BuildId::HASH;
|
||||
ctx.arg.build_id.hash_size = 20;
|
||||
@@ -908,7 +1048,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.auxiliary.push_back(arg);
|
||||
} else if (read_arg("filter") || read_arg("F")) {
|
||||
ctx.arg.filter.push_back(arg);
|
||||
} else if (read_flag("apply-dynamic-relocs")) {
|
||||
} else if (read_arg("O")) {
|
||||
} else if (read_flag("O0")) {
|
||||
} else if (read_flag("O1")) {
|
||||
@@ -929,7 +1068,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_flag("no-add-needed")) {
|
||||
} else if (read_flag("no-call-graph-profile-sort")) {
|
||||
} else if (read_flag("no-copy-dt-needed-entries")) {
|
||||
} else if (read_flag("no-undefined-version")) {
|
||||
} else if (read_arg("sort-section")) {
|
||||
} else if (read_flag("sort-common")) {
|
||||
} else if (read_flag("dc")) {
|
||||
@@ -942,11 +1080,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_flag("warn-constructors")) {
|
||||
} else if (read_flag("warn-execstack")) {
|
||||
} else if (read_flag("no-warn-execstack")) {
|
||||
} else if (read_flag("secure-plt")) {
|
||||
} else if (read_arg("rpath-link")) {
|
||||
} else if (read_z_flag("combreloc")) {
|
||||
} else if (read_z_flag("nocombreloc")) {
|
||||
} else if (read_z_arg("common-page-size")) {
|
||||
} else if (read_flag("no-keep-memory")) {
|
||||
} else if (read_arg("max-cache-size")) {
|
||||
} else if (read_arg("version-script")) {
|
||||
// --version-script, --dynamic-list and --export-dynamic-symbol[-list]
|
||||
// are treated as positional arguments even if they are actually not
|
||||
@@ -1026,6 +1166,18 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (ctx.arg.relocatable)
|
||||
ctx.arg.is_static = true;
|
||||
|
||||
// --section-order implies `-z separate-loadable-segments`
|
||||
if (z_separate_code)
|
||||
ctx.arg.z_separate_code = *z_separate_code;
|
||||
else if (!ctx.arg.section_order.empty())
|
||||
ctx.arg.z_separate_code = SEPARATE_LOADABLE_SEGMENTS;
|
||||
|
||||
// --section-order implies `-z norelro`
|
||||
if (z_relro)
|
||||
ctx.arg.z_relro = *z_relro;
|
||||
else if (!ctx.arg.section_order.empty())
|
||||
ctx.arg.z_relro = false;
|
||||
|
||||
if (!ctx.arg.shared) {
|
||||
if (!ctx.arg.filter.empty())
|
||||
Fatal(ctx) << "-filter may not be used without -shared";
|
||||
@@ -1033,12 +1185,22 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
Fatal(ctx) << "-auxiliary may not be used without -shared";
|
||||
}
|
||||
|
||||
if (ctx.arg.thread_count == 0)
|
||||
ctx.arg.thread_count = get_default_thread_count();
|
||||
if (!ctx.arg.apply_dynamic_relocs && !E::is_rela)
|
||||
Fatal(ctx) << "--no-apply-dynamic-relocs may not be used on "
|
||||
<< E::target_name;
|
||||
|
||||
if (is_sparc<E> && ctx.arg.apply_dynamic_relocs)
|
||||
Fatal(ctx) << "--apply-dynamic-relocs may not be used on SPARC64";
|
||||
|
||||
if (!ctx.arg.section_start.empty() && !ctx.arg.section_order.empty())
|
||||
Fatal(ctx) << "--section-start may not be used with --section-order";
|
||||
|
||||
if (ctx.arg.image_base % ctx.page_size)
|
||||
Fatal(ctx) << "-image-base must be a multiple of -max-page-size";
|
||||
|
||||
if (ctx.arg.thread_count == 0)
|
||||
ctx.arg.thread_count = get_default_thread_count();
|
||||
|
||||
if (char *env = getenv("MOLD_REPRO"); env && env[0])
|
||||
ctx.arg.repro = true;
|
||||
|
||||
@@ -1059,11 +1221,11 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &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);
|
||||
// --oformat=binary implies --strip-all because without a section
|
||||
// header, there's no way to identify the locations of a symbol
|
||||
// table in an output file in the first place.
|
||||
if (ctx.arg.oformat_binary)
|
||||
ctx.arg.strip_all = true;
|
||||
|
||||
// By default, mold tries to ovewrite to an output file if exists
|
||||
// because at least on Linux, writing to an existing file is much
|
||||
@@ -1083,9 +1245,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
return remaining;
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template std::vector<std::string> parse_nonpositional_args(Context<E> &ctx);
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
template std::vector<std::string> parse_nonpositional_args(Context<E> &ctx);
|
||||
|
||||
} // namespace mold::elf
|
||||
+20
-27
@@ -77,9 +77,9 @@ read_compunits(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
while (!data.empty()) {
|
||||
if (data.size() < 4)
|
||||
Fatal(ctx) << *file.debug_info << ": corrupted .debug_info";
|
||||
if (*(ul32 *)data.data() == 0xffffffff)
|
||||
if (*(U32<E> *)data.data() == 0xffffffff)
|
||||
Fatal(ctx) << *file.debug_info << ": --gdb-index: DWARF64 not supported";
|
||||
i64 len = *(ul32 *)data.data() + 4;
|
||||
i64 len = *(U32<E> *)data.data() + 4;
|
||||
vec.push_back(data.substr(0, len));
|
||||
data = data.substr(len);
|
||||
}
|
||||
@@ -116,15 +116,15 @@ std::vector<GdbIndexName> read_pubnames(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
if (contents.size() < 14)
|
||||
Fatal(ctx) << isec << ": corrupted header";
|
||||
|
||||
u32 len = *(ul32 *)contents.data() + 4;
|
||||
u32 debug_info_offset = *(ul32 *)(contents.data() + 6);
|
||||
u32 len = *(U32<E> *)contents.data() + 4;
|
||||
u32 debug_info_offset = *(U32<E> *)(contents.data() + 6);
|
||||
u32 cu_idx = get_cu_idx(isec, debug_info_offset);
|
||||
|
||||
std::string_view data = contents.substr(14, len - 14);
|
||||
contents = contents.substr(len);
|
||||
|
||||
while (!data.empty()) {
|
||||
u32 offset = *(ul32 *)data.data();
|
||||
u32 offset = *(U32<E> *)data.data();
|
||||
data = data.substr(4);
|
||||
if (offset == 0)
|
||||
break;
|
||||
@@ -176,7 +176,7 @@ static std::tuple<u8 *, u8 *, u32>
|
||||
find_compunit(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
// Read .debug_info to find the record at a given offset.
|
||||
u8 *cu = get_buffer(ctx, ctx.debug_info) + offset;
|
||||
u32 dwarf_version = *(ul16 *)(cu + 4);
|
||||
u32 dwarf_version = *(U16<E> *)(cu + 4);
|
||||
u32 abbrev_offset;
|
||||
|
||||
// Skip a header.
|
||||
@@ -184,14 +184,14 @@ find_compunit(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
abbrev_offset = *(ul32 *)(cu + 6);
|
||||
abbrev_offset = *(U32<E> *)(cu + 6);
|
||||
if (u32 address_size = cu[10]; address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
|
||||
<< address_size;
|
||||
cu += 11;
|
||||
break;
|
||||
case 5: {
|
||||
abbrev_offset = *(ul32 *)(cu + 8);
|
||||
abbrev_offset = *(U32<E> *)(cu + 8);
|
||||
if (u32 address_size = cu[7]; address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
|
||||
<< address_size;
|
||||
@@ -309,13 +309,13 @@ inline u64 DebugInfoReader<E>::read(u64 form) {
|
||||
case DW_FORM_strx2:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_ref2: {
|
||||
u64 val = *(ul16 *)cu;
|
||||
u64 val = *(U16<E> *)cu;
|
||||
cu += 2;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx3:
|
||||
case DW_FORM_addrx3: {
|
||||
u64 val = *(ul24 *)cu;
|
||||
u64 val = *(U24<E> *)cu;
|
||||
cu += 3;
|
||||
return val;
|
||||
}
|
||||
@@ -326,13 +326,13 @@ inline u64 DebugInfoReader<E>::read(u64 form) {
|
||||
case DW_FORM_strx4:
|
||||
case DW_FORM_addrx4:
|
||||
case DW_FORM_ref4: {
|
||||
u64 val = *(ul32 *)cu;
|
||||
u64 val = *(U32<E> *)cu;
|
||||
cu += 4;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data8:
|
||||
case DW_FORM_ref8: {
|
||||
u64 val = *(ul64 *)cu;
|
||||
u64 val = *(U64<E> *)cu;
|
||||
cu += 8;
|
||||
return val;
|
||||
}
|
||||
@@ -359,8 +359,7 @@ inline u64 DebugInfoReader<E>::read(u64 form) {
|
||||
}
|
||||
}
|
||||
|
||||
// Read a range list from .debug_ranges starting at the given offset
|
||||
// (until an end of list entry).
|
||||
// Read a range list from .debug_ranges starting at the given offset.
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_debug_range(Context<E> &ctx, ObjectFile<E> &file, Word<E> *range) {
|
||||
@@ -379,8 +378,7 @@ read_debug_range(Context<E> &ctx, ObjectFile<E> &file, Word<E> *range) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Read a range list from .debug_rnglists starting at the given offset
|
||||
// (until an end of list entry).
|
||||
// Read a range list from .debug_rnglists starting at the given offset.
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_rnglist_range(Context<E> &ctx, ObjectFile<E> &file, u8 *rnglist,
|
||||
@@ -501,7 +499,7 @@ read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
Fatal(ctx) << file << ": --gdb-index: missing DW_AT_rnglists_base";
|
||||
|
||||
u8 *base = buf + *rnglists_base;
|
||||
return read_rnglist_range(ctx, file, base + *(ul32 *)base, addrx);
|
||||
return read_rnglist_range(ctx, file, base + *(U32<E> *)base, addrx);
|
||||
}
|
||||
|
||||
// Handle a contiguous address range.
|
||||
@@ -546,16 +544,11 @@ read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
return {};
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template std::vector<std::string_view> \
|
||||
read_compunits(Context<E> &, ObjectFile<E> &); \
|
||||
template std::vector<GdbIndexName> \
|
||||
read_pubnames(Context<E> &, ObjectFile<E> &); \
|
||||
template i64 \
|
||||
estimate_address_areas(Context<E> &, ObjectFile<E> &); \
|
||||
template std::vector<u64> \
|
||||
read_address_areas(Context<E> &, ObjectFile<E> &, i64)
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
template std::vector<std::string_view> read_compunits(Context<E> &, ObjectFile<E> &);
|
||||
template std::vector<GdbIndexName> read_pubnames(Context<E> &, ObjectFile<E> &);
|
||||
template i64 estimate_address_areas(Context<E> &, ObjectFile<E> &);
|
||||
template std::vector<u64> read_address_areas(Context<E> &, ObjectFile<E> &, i64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+1042
File diff suppressed because it is too large.
Load diff
+12
-17
@@ -10,23 +10,27 @@
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
static bool is_init_fini(const InputSection<E> &isec) {
|
||||
static bool should_keep(const InputSection<E> &isec) {
|
||||
u32 type = isec.shdr().sh_type;
|
||||
u32 flags = isec.shdr().sh_flags;
|
||||
std::string_view name = isec.name();
|
||||
|
||||
return type == SHT_INIT_ARRAY ||
|
||||
return (flags & SHF_GNU_RETAIN) ||
|
||||
type == SHT_NOTE ||
|
||||
type == SHT_INIT_ARRAY ||
|
||||
type == SHT_FINI_ARRAY ||
|
||||
type == SHT_PREINIT_ARRAY ||
|
||||
(std::is_same_v<E, ARM32> && type == SHT_ARM_EXIDX) ||
|
||||
(is_arm32<E> && type == SHT_ARM_EXIDX) ||
|
||||
name.starts_with(".ctors") ||
|
||||
name.starts_with(".dtors") ||
|
||||
name.starts_with(".init") ||
|
||||
name.starts_with(".fini");
|
||||
name.starts_with(".fini") ||
|
||||
is_c_identifier(name);
|
||||
}
|
||||
|
||||
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 && fast_mark(isec->is_visited);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -34,13 +38,6 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
|
||||
assert(isec->is_visited);
|
||||
|
||||
// A relocation can refer either a section fragment (i.e. a piece of
|
||||
// string in a mergeable string section) or a symbol. Mark all
|
||||
// section fragments as alive.
|
||||
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.
|
||||
@@ -105,8 +102,7 @@ static void collect_root_set(Context<E> &ctx,
|
||||
if (!(flags & SHF_ALLOC))
|
||||
isec->is_visited = true;
|
||||
|
||||
if (is_init_fini(*isec) || is_c_identifier(isec->name()) ||
|
||||
(flags & SHF_GNU_RETAIN) || isec->shdr().sh_type == SHT_NOTE)
|
||||
if (should_keep(*isec))
|
||||
enqueue_section(isec.get());
|
||||
}
|
||||
});
|
||||
@@ -194,9 +190,8 @@ void gc_sections(Context<E> &ctx) {
|
||||
sweep(ctx);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void gc_sections(Context<E> &ctx);
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
template void gc_sections(Context<E> &ctx);
|
||||
|
||||
} // namespace mold::elf
|
||||
+57
-64
@@ -90,6 +90,16 @@ template<> struct hash<Digest> {
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
class BitVector {
|
||||
public:
|
||||
BitVector(u32 size) : vec((size + 7) / 8) {}
|
||||
bool get(u32 idx) const { return vec[idx / 8] & (1 << (idx % 8)); }
|
||||
void set(u32 idx) { vec[idx / 8] |= 1 << (idx % 8); }
|
||||
|
||||
private:
|
||||
std::vector<u8> vec;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
static void uniquify_cies(Context<E> &ctx) {
|
||||
Timer t(ctx, "uniquify_cies");
|
||||
@@ -153,10 +163,6 @@ static bool is_leaf(Context<E> &ctx, InputSection<E> &isec) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static u64 combine_hash(u64 a, u64 b) {
|
||||
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
struct LeafHasher {
|
||||
size_t operator()(InputSection<E> *isec) const {
|
||||
@@ -290,26 +296,16 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
hash_symbol(*isec.file.symbols[rel.r_sym]);
|
||||
hash(rel.r_type);
|
||||
hash(rel.r_offset - fde.input_offset);
|
||||
hash(isec.file.cies[fde.cie_idx].input_section.get_addend(rel));
|
||||
hash(get_addend(isec.file.cies[fde.cie_idx].input_section, rel));
|
||||
}
|
||||
}
|
||||
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 i = 0; i < isec.get_rels(ctx).size(); i++) {
|
||||
const ElfRel<E> &rel = isec.get_rels(ctx)[i];
|
||||
hash(rel.r_offset);
|
||||
hash(rel.r_type);
|
||||
hash(isec.get_addend(rel));
|
||||
|
||||
if (isec.rel_fragments && isec.rel_fragments[frag_idx].idx == i) {
|
||||
SectionFragmentRef<E> &ref = isec.rel_fragments[frag_idx++];
|
||||
hash('a');
|
||||
isec.get_addend(rel);
|
||||
hash((u64)ref.frag);
|
||||
} else {
|
||||
hash_symbol(*isec.file.symbols[rel.r_sym]);
|
||||
}
|
||||
hash(get_addend(isec, rel));
|
||||
hash_symbol(*isec.file.symbols[rel.r_sym]);
|
||||
}
|
||||
|
||||
return digest_final(sha);
|
||||
@@ -381,19 +377,14 @@ static void gather_edges(Context<E> &ctx,
|
||||
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
|
||||
InputSection<E> &isec = *sections[i];
|
||||
assert(isec.icf_eligible);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 j = 0; j < isec.get_rels(ctx).size(); j++) {
|
||||
if (isec.rel_fragments && isec.rel_fragments[frag_idx].idx == j) {
|
||||
frag_idx++;
|
||||
} else {
|
||||
const ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.get_frag())
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
if (isec->icf_eligible)
|
||||
num_edges[i]++;
|
||||
}
|
||||
const ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.get_frag())
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
if (isec->icf_eligible)
|
||||
num_edges[i]++;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -404,28 +395,22 @@ static void gather_edges(Context<E> &ctx,
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)num_edges.size(), [&](i64 i) {
|
||||
InputSection<E> &isec = *sections[i];
|
||||
i64 frag_idx = 0;
|
||||
i64 idx = edge_indices[i];
|
||||
|
||||
for (i64 j = 0; j < isec.get_rels(ctx).size(); j++) {
|
||||
if (isec.rel_fragments && isec.rel_fragments[frag_idx].idx == j) {
|
||||
frag_idx++;
|
||||
} else {
|
||||
const ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.get_frag())
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
if (isec->icf_eligible)
|
||||
edges[idx++] = isec->icf_idx;
|
||||
}
|
||||
}
|
||||
for (ElfRel<E> &rel : isec.get_rels(ctx)) {
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
if (isec->icf_eligible)
|
||||
edges[idx++] = isec->icf_idx;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 propagate(std::span<std::vector<Digest>> digests,
|
||||
std::span<u32> edges, std::span<u32> edge_indices,
|
||||
bool &slot, tbb::affinity_partitioner &ap) {
|
||||
bool &slot, BitVector &converged,
|
||||
tbb::affinity_partitioner &ap) {
|
||||
static Counter round("icf_round");
|
||||
round++;
|
||||
|
||||
@@ -433,7 +418,7 @@ static i64 propagate(std::span<std::vector<Digest>> digests,
|
||||
tbb::enumerable_thread_specific<i64> changed;
|
||||
|
||||
tbb::parallel_for((i64)0, num_digests, [&](i64 i) {
|
||||
if (digests[slot][i] == digests[!slot][i])
|
||||
if (converged.get(i))
|
||||
return;
|
||||
|
||||
SHA256Hash sha;
|
||||
@@ -442,14 +427,18 @@ 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 : edges.subspan(begin, end - begin)) {
|
||||
for (i64 j : edges.subspan(begin, end - begin))
|
||||
sha.update(digests[slot][j].data(), HASH_SIZE);
|
||||
}
|
||||
|
||||
digests[!slot][i] = digest_final(sha);
|
||||
|
||||
if (digests[slot][i] != digests[!slot][i])
|
||||
if (digests[slot][i] == digests[!slot][i]) {
|
||||
// This node has converged. Skip further iterations as it will
|
||||
// yield the same hash.
|
||||
converged.set(i);
|
||||
} else {
|
||||
changed.local()++;
|
||||
}
|
||||
}, ap);
|
||||
|
||||
slot = !slot;
|
||||
@@ -524,10 +513,14 @@ void icf_sections(Context<E> &ctx) {
|
||||
std::vector<InputSection<E> *> sections = gather_sections(ctx);
|
||||
|
||||
// We allocate 3 arrays to store hashes for each vertex.
|
||||
// Index 0 and 1 are used for tree hashes from the previous iteration and the current iteration.
|
||||
// They switch roles every iteration --- see `slot` below.
|
||||
// Index 2 stores the initial, single-vertex hash --- this is combined with hashes from the connected vertices to
|
||||
// form the tree hash described above.
|
||||
//
|
||||
// Index 0 and 1 are used for tree hashes from the previous
|
||||
// iteration and the current iteration. They switch roles every
|
||||
// iteration. See `slot` below.
|
||||
//
|
||||
// Index 2 stores the initial, single-vertex hash. This is combined
|
||||
// with hashes from the connected vertices to form the tree hash
|
||||
// described above.
|
||||
std::vector<std::vector<Digest>> digests(3);
|
||||
digests[0] = compute_digests<E>(ctx, sections);
|
||||
digests[1].resize(digests[0].size());
|
||||
@@ -537,6 +530,7 @@ void icf_sections(Context<E> &ctx) {
|
||||
std::vector<u32> edge_indices;
|
||||
gather_edges<E>(ctx, sections, edges, edge_indices);
|
||||
|
||||
BitVector converged(digests[0].size());
|
||||
bool slot = 0;
|
||||
|
||||
// Execute the propagation rounds until convergence is obtained.
|
||||
@@ -557,7 +551,7 @@ void icf_sections(Context<E> &ctx) {
|
||||
// which is a necessary (but not sufficient) condition for convergence.
|
||||
i64 num_changed = -1;
|
||||
for (;;) {
|
||||
i64 n = propagate<E>(digests, edges, edge_indices, slot, ap);
|
||||
i64 n = propagate<E>(digests, edges, edge_indices, slot, converged, ap);
|
||||
if (n == num_changed)
|
||||
break;
|
||||
num_changed = n;
|
||||
@@ -570,7 +564,7 @@ void icf_sections(Context<E> &ctx) {
|
||||
// count_num_classes requires sorting which is O(n log n), so do a little
|
||||
// more work beforehand to amortize that log factor.
|
||||
for (i64 i = 0; i < 10; i++)
|
||||
propagate<E>(digests, edges, edge_indices, slot, ap);
|
||||
propagate<E>(digests, edges, edge_indices, slot, converged, ap);
|
||||
|
||||
i64 n = count_num_classes<E>(digests[slot], ap);
|
||||
if (n == num_classes)
|
||||
@@ -606,26 +600,25 @@ void icf_sections(Context<E> &ctx) {
|
||||
if (ctx.arg.print_icf_sections)
|
||||
print_icf_sections(ctx);
|
||||
|
||||
// Re-assign input sections to symbols.
|
||||
// Eliminate duplicate sections.
|
||||
// Symbols pointing to eliminated sections will be redirected on the fly when
|
||||
// exporting to the symtab.
|
||||
{
|
||||
Timer t(ctx, "reassign");
|
||||
Timer t(ctx, "sweep");
|
||||
static Counter eliminated("icf_eliminated");
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file == file) {
|
||||
InputSection<E> *isec = sym->get_input_section();
|
||||
if (isec && isec->leader && isec->leader != isec) {
|
||||
isec->kill();
|
||||
isec->killed_by_icf = true;
|
||||
}
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && isec->is_killed_by_icf()) {
|
||||
isec->kill();
|
||||
eliminated++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void icf_sections(Context<E> &ctx);
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
template void icf_sections(Context<E> &ctx);
|
||||
|
||||
} // namespace mold::elf
|
||||
+345
-285
File diff suppressed because it is too large.
Load diff
+258
-159
@@ -2,9 +2,14 @@
|
||||
|
||||
#include <limits>
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
typedef enum {
|
||||
NONE, ERROR, COPYREL, DYN_COPYREL, PLT, CPLT, DYN_CPLT, DYNREL, BASEREL, IFUNC,
|
||||
} Action;
|
||||
|
||||
template <typename E>
|
||||
bool CieRecord<E>::equals(const CieRecord<E> &other) const {
|
||||
if (get_contents() != other.get_contents())
|
||||
@@ -19,13 +24,13 @@ bool CieRecord<E>::equals(const CieRecord<E> &other) const {
|
||||
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]))
|
||||
get_addend(input_section, x[i]) != get_addend(other.input_section, y[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline i64 to_p2align(u64 alignment) {
|
||||
static i64 to_p2align(u64 alignment) {
|
||||
if (alignment == 0)
|
||||
return 0;
|
||||
return std::countr_zero(alignment);
|
||||
@@ -38,19 +43,13 @@ InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
if (shndx < file.elf_sections.size())
|
||||
contents = {(char *)file.mf->data + shdr().sh_offset, (size_t)shdr().sh_size};
|
||||
|
||||
if (name.starts_with(".zdebug")) {
|
||||
sh_size = *(ub64 *)&contents[4];
|
||||
p2align = to_p2align(shdr().sh_addralign);
|
||||
compressed = true;
|
||||
} else if (shdr().sh_flags & SHF_COMPRESSED) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Sections may have been compressed. We usually uncompress them
|
||||
@@ -58,126 +57,56 @@ InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
// early for REL-type ELF types to read relocation addends from
|
||||
// section contents. For RELA-type, we don't need to do this because
|
||||
// addends are in relocations.
|
||||
if constexpr (!is_rela<E>)
|
||||
if constexpr (!E::is_rela)
|
||||
uncompress(ctx);
|
||||
|
||||
output_section =
|
||||
OutputSection<E>::get_instance(ctx, name, shdr().sh_type, shdr().sh_flags);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::uncompress(Context<E> &ctx) {
|
||||
if (!compressed || uncompressed)
|
||||
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed)
|
||||
return;
|
||||
|
||||
u8 *buf = new u8[sh_size];
|
||||
uncompress_to(ctx, buf);
|
||||
contents = {(char *)buf, sh_size};
|
||||
contents = std::string_view((char *)buf, sh_size);
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
uncompressed = true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::uncompress_to(Context<E> &ctx, u8 *buf) {
|
||||
if (!compressed || uncompressed) {
|
||||
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed) {
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
return;
|
||||
}
|
||||
|
||||
auto do_uncompress = [&](std::string_view data) {
|
||||
unsigned long size = sh_size;
|
||||
if (::uncompress(buf, &size, (u8 *)data.data(), data.size()) != Z_OK)
|
||||
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)
|
||||
std::string_view data = contents.substr(sizeof(ElfChdr<E>));
|
||||
|
||||
switch (hdr.ch_type) {
|
||||
case ELFCOMPRESS_ZLIB: {
|
||||
unsigned long size = sh_size;
|
||||
if (::uncompress(buf, &size, (u8 *)data.data(), data.size()) != Z_OK)
|
||||
Fatal(ctx) << *this << ": uncompress failed";
|
||||
assert(size == sh_size);
|
||||
break;
|
||||
}
|
||||
case ELFCOMPRESS_ZSTD:
|
||||
if (ZSTD_decompress(buf, sh_size, (u8 *)data.data(), data.size()) != sh_size)
|
||||
Fatal(ctx) << *this << ": ZSTD_decompress failed";
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": unsupported compression type: 0x"
|
||||
<< std::hex << hdr.ch_type;
|
||||
do_uncompress(contents.substr(sizeof(ElfChdr<E>)));
|
||||
}
|
||||
|
||||
typedef enum { NONE, ERROR, COPYREL, PLT, CPLT, DYNREL, BASEREL } Action;
|
||||
|
||||
template <typename E>
|
||||
static void
|
||||
dispatch(Context<E> &ctx, InputSection<E> &isec, Action action,
|
||||
Symbol<E> &sym, const ElfRel<E> &rel) {
|
||||
auto error = [&] {
|
||||
std::string msg = sym.is_absolute() ? "-fno-PIC" : "-fPIC";
|
||||
Error(ctx) << isec << ": " << rel << " relocation at offset 0x"
|
||||
<< std::hex << rel.r_offset << " against symbol `"
|
||||
<< sym << "' can not be used; recompile with " << msg;
|
||||
};
|
||||
|
||||
auto check_textrel = [&] {
|
||||
if (isec.shdr().sh_flags & SHF_WRITE)
|
||||
return;
|
||||
|
||||
if (ctx.arg.z_text) {
|
||||
error();
|
||||
} else if (ctx.arg.warn_textrel) {
|
||||
Warn(ctx) << isec << ": relocation against symbol `" << sym
|
||||
<< "' in read-only section";
|
||||
}
|
||||
ctx.has_textrel = true;
|
||||
};
|
||||
|
||||
switch (action) {
|
||||
case NONE:
|
||||
return;
|
||||
case ERROR:
|
||||
error();
|
||||
return;
|
||||
case COPYREL:
|
||||
if (!ctx.arg.z_copyreloc) {
|
||||
error();
|
||||
} else if (sym.esym().st_visibility == STV_PROTECTED) {
|
||||
Error(ctx) << isec << ": cannot make copy relocation for protected symbol '"
|
||||
<< sym << "', defined in " << *sym.file
|
||||
<< "; recompile with -fPIC";
|
||||
}
|
||||
sym.flags |= NEEDS_COPYREL;
|
||||
return;
|
||||
case PLT:
|
||||
sym.flags |= NEEDS_PLT;
|
||||
return;
|
||||
case CPLT:
|
||||
sym.flags |= NEEDS_CPLT;
|
||||
return;
|
||||
case DYNREL:
|
||||
assert(sym.is_imported);
|
||||
check_textrel();
|
||||
isec.file.num_dynrel++;
|
||||
return;
|
||||
case BASEREL:
|
||||
check_textrel();
|
||||
if (!isec.is_relr_reloc(ctx, rel))
|
||||
isec.file.num_dynrel++;
|
||||
return;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_rel_action(Context<E> &ctx, const Action table[3][4],
|
||||
Symbol<E> &sym) {
|
||||
static Action get_rel_action(Context<E> &ctx, Symbol<E> &sym,
|
||||
const Action table[3][4]) {
|
||||
auto get_output_type = [&] {
|
||||
if (ctx.arg.shared)
|
||||
return 0;
|
||||
@@ -200,84 +129,256 @@ static Action get_rel_action(Context<E> &ctx, const Action table[3][4],
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_abs_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
// This is a decision table for absolute relocations that is smaller
|
||||
// than the word size (e.g. R_X86_64_32). Since the dynamic linker
|
||||
// generally does not support dynamic relocations smaller than the
|
||||
// word size, we need to report an error if a relocation cannot be
|
||||
// resolved at link-time.
|
||||
constexpr Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // Shared object
|
||||
{ NONE, ERROR, ERROR, ERROR }, // Position-independent exec
|
||||
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
|
||||
static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, Action action) {
|
||||
bool writable = (isec.shdr().sh_flags & SHF_WRITE);
|
||||
|
||||
auto error = [&] {
|
||||
std::string msg = sym.is_absolute() ? "-fno-PIC" : "-fPIC";
|
||||
Error(ctx) << isec << ": " << rel << " relocation at offset 0x"
|
||||
<< std::hex << rel.r_offset << " against symbol `"
|
||||
<< sym << "' can not be used; recompile with " << msg;
|
||||
};
|
||||
Action action = get_rel_action(ctx, table, sym);
|
||||
dispatch(ctx, *this, action, sym, rel);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_abs_dyn_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
// This is a decision table for absolute relocations for the word
|
||||
// size data (e.g. R_X86_64_64). Unlike the above, we can emit a
|
||||
// dynamic relocation if we cannot resolve its address at link-time.
|
||||
constexpr Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
|
||||
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
|
||||
auto check_textrel = [&] {
|
||||
if (!writable) {
|
||||
if (ctx.arg.z_text) {
|
||||
error();
|
||||
} else if (ctx.arg.warn_textrel) {
|
||||
Warn(ctx) << isec << ": relocation against symbol `" << sym
|
||||
<< "' in read-only section";
|
||||
}
|
||||
ctx.has_textrel = true;
|
||||
}
|
||||
};
|
||||
return get_rel_action(ctx, table, sym);
|
||||
|
||||
auto copyrel = [&] {
|
||||
assert(sym.is_imported);
|
||||
if (sym.esym().st_visibility == STV_PROTECTED) {
|
||||
Error(ctx) << isec
|
||||
<< ": cannot make copy relocation for protected symbol '" << sym
|
||||
<< "', defined in " << *sym.file << "; recompile with -fPIC";
|
||||
}
|
||||
sym.flags |= NEEDS_COPYREL;
|
||||
};
|
||||
|
||||
auto dynrel = [&] {
|
||||
check_textrel();
|
||||
isec.file.num_dynrel++;
|
||||
};
|
||||
|
||||
switch (action) {
|
||||
case NONE:
|
||||
break;
|
||||
case ERROR:
|
||||
error();
|
||||
break;
|
||||
case COPYREL:
|
||||
if (!ctx.arg.z_copyreloc)
|
||||
error();
|
||||
copyrel();
|
||||
break;
|
||||
case DYN_COPYREL:
|
||||
if (writable || !ctx.arg.z_copyreloc)
|
||||
dynrel();
|
||||
else
|
||||
copyrel();
|
||||
break;
|
||||
case PLT:
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case CPLT:
|
||||
sym.flags |= NEEDS_CPLT;
|
||||
break;
|
||||
case DYN_CPLT:
|
||||
if (writable)
|
||||
dynrel();
|
||||
else
|
||||
sym.flags |= NEEDS_CPLT;
|
||||
break;
|
||||
case DYNREL:
|
||||
dynrel();
|
||||
break;
|
||||
case BASEREL:
|
||||
check_textrel();
|
||||
if (!isec.is_relr_reloc(ctx, rel))
|
||||
isec.file.num_dynrel++;
|
||||
break;
|
||||
case IFUNC:
|
||||
dynrel();
|
||||
ctx.num_ifunc_dynrels.fetch_add(1, std::memory_order_relaxed);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_abs_dyn_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
Action action = get_abs_dyn_action(ctx, sym);
|
||||
dispatch(ctx, *this, action, sym, rel);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_pcrel_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
static Action get_pcrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
// This is for PC-relative relocations (e.g. R_X86_64_PC32).
|
||||
// We cannot promote them to dynamic relocations because the dynamic
|
||||
// linker generally does not support PC-relative relocations.
|
||||
constexpr Action table[][4] = {
|
||||
constexpr static Action table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, PLT }, // Shared object
|
||||
{ ERROR, NONE, COPYREL, PLT }, // Position-independent exec
|
||||
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
|
||||
};
|
||||
Action action = get_rel_action(ctx, table, sym);
|
||||
dispatch(ctx, *this, action, sym, rel);
|
||||
|
||||
return get_rel_action(ctx, sym, table);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::apply_abs_dyn_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, u8 *loc,
|
||||
u64 S, i64 A, u64 P, ElfRel<E> *&dynrel) {
|
||||
switch (get_abs_dyn_action(ctx, sym)) {
|
||||
static Action get_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
// This is a decision table for absolute relocations that is smaller
|
||||
// than the word size (e.g. R_X86_64_32). Since the dynamic linker
|
||||
// generally does not support dynamic relocations smaller than the
|
||||
// word size, we need to report an error if a relocation cannot be
|
||||
// resolved at link-time.
|
||||
constexpr static Action table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // Shared object
|
||||
{ NONE, ERROR, ERROR, ERROR }, // Position-independent exec
|
||||
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
|
||||
};
|
||||
|
||||
return get_rel_action(ctx, sym, table);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_dyn_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (sym.is_ifunc())
|
||||
return IFUNC;
|
||||
|
||||
// This is a decision table for absolute relocations for the word
|
||||
// size data (e.g. R_X86_64_64). Unlike the absrel_table, we can emit
|
||||
// a dynamic relocation if we cannot resolve an address at link-time.
|
||||
constexpr static Action table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
|
||||
{ NONE, NONE, DYN_COPYREL, DYN_CPLT }, // Position-dependent exec
|
||||
};
|
||||
|
||||
return get_rel_action(ctx, sym, table);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_ppc64_toc_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (sym.is_ifunc())
|
||||
return IFUNC;
|
||||
|
||||
// As a special case, we do not create copy relocations nor canonical
|
||||
// PLTs for .toc sections. PPC64's .toc is a compiler-generated
|
||||
// GOT-like section, and no user-generated code directly uses values
|
||||
// in it.
|
||||
constexpr static Action table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
|
||||
{ NONE, NONE, DYNREL, DYNREL }, // Position-dependent exec
|
||||
};
|
||||
|
||||
return get_rel_action(ctx, sym, table);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_pcrel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
scan_rel(ctx, *this, sym, rel, get_pcrel_action(ctx, sym));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_absrel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
scan_rel(ctx, *this, sym, rel, get_absrel_action(ctx, sym));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_dyn_absrel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
scan_rel(ctx, *this, sym, rel, get_dyn_absrel_action(ctx, sym));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_toc_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
scan_rel(ctx, *this, sym, rel, get_ppc64_toc_action(ctx, sym));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
|
||||
Symbol<E> &sym, const ElfRel<E> &rel, u8 *loc,
|
||||
u64 S, i64 A, u64 P, ElfRel<E> *&dynrel,
|
||||
Action action) {
|
||||
bool writable = (isec.shdr().sh_flags & SHF_WRITE);
|
||||
|
||||
auto apply_dynrel = [&] {
|
||||
*dynrel++ = ElfRel<E>(P, E::R_ABS, sym.get_dynsym_idx(ctx), A);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = A;
|
||||
};
|
||||
|
||||
switch (action) {
|
||||
case COPYREL:
|
||||
case CPLT:
|
||||
case NONE:
|
||||
*(Word<E> *)loc = S + A;
|
||||
break;
|
||||
case BASEREL:
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
if (isec.is_relr_reloc(ctx, rel)) {
|
||||
*(Word<E> *)loc = S + A;
|
||||
} else {
|
||||
*dynrel++ = ElfRel<E>(P, E::R_RELATIVE, 0, S + A);
|
||||
*(Word<E> *)loc = S + A;
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = S + A;
|
||||
}
|
||||
break;
|
||||
case DYN_COPYREL:
|
||||
if (writable || !ctx.arg.z_copyreloc)
|
||||
apply_dynrel();
|
||||
else
|
||||
*(Word<E> *)loc = S + A;
|
||||
break;
|
||||
case DYN_CPLT:
|
||||
if (writable)
|
||||
apply_dynrel();
|
||||
else
|
||||
*(Word<E> *)loc = S + A;
|
||||
break;
|
||||
case DYNREL:
|
||||
*dynrel++ = ElfRel<E>(P, E::R_ABS, sym.get_dynsym_idx(ctx), A);
|
||||
*(Word<E> *)loc = A;
|
||||
apply_dynrel();
|
||||
break;
|
||||
case IFUNC: {
|
||||
u64 addr = sym.get_addr(ctx, NO_PLT) + A;
|
||||
*dynrel++ = ElfRel<E>(P, E::R_IRELATIVE, 0, addr);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = addr;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::apply_dyn_absrel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, u8 *loc,
|
||||
u64 S, i64 A, u64 P,
|
||||
ElfRel<E> *&dynrel) {
|
||||
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, dynrel,
|
||||
get_dyn_absrel_action(ctx, sym));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::apply_toc_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, u8 *loc,
|
||||
u64 S, i64 A, u64 P,
|
||||
ElfRel<E> *&dynrel) {
|
||||
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, dynrel,
|
||||
get_ppc64_toc_action(ctx, sym));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
if (shdr().sh_type == SHT_NOBITS || sh_size == 0)
|
||||
@@ -286,17 +387,17 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
// Copy data
|
||||
if constexpr (is_riscv<E>) {
|
||||
copy_contents_riscv(ctx, buf);
|
||||
} else if (compressed) {
|
||||
uncompress_to(ctx, buf);
|
||||
} else {
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
uncompress_to(ctx, buf);
|
||||
}
|
||||
|
||||
// Apply relocations
|
||||
if (shdr().sh_flags & SHF_ALLOC)
|
||||
apply_reloc_alloc(ctx, buf);
|
||||
else
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
if (!ctx.arg.relocatable) {
|
||||
if (shdr().sh_flags & SHF_ALLOC)
|
||||
apply_reloc_alloc(ctx, buf);
|
||||
else
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the name of a function containin a given offset.
|
||||
@@ -365,12 +466,10 @@ void report_undef_errors(Context<E> &ctx) {
|
||||
ctx.checkpoint();
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template struct CieRecord<E>; \
|
||||
template class InputSection<E>; \
|
||||
template void report_undef_errors(Context<E> &)
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
template struct CieRecord<E>;
|
||||
template class InputSection<E>;
|
||||
template void report_undef_errors(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
+40
-34
@@ -239,7 +239,8 @@ void parse_linker_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MachineType get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
std::string_view
|
||||
get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
@@ -247,11 +248,18 @@ MachineType get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf)
|
||||
|
||||
if (tok.size() >= 3 && tok[0] == "OUTPUT_FORMAT" && tok[1] == "(") {
|
||||
if (tok[2] == "elf64-x86-64")
|
||||
return MachineType::X86_64;
|
||||
return X86_64::target_name;
|
||||
if (tok[2] == "elf32-i386")
|
||||
return MachineType::I386;
|
||||
return I386::target_name;
|
||||
}
|
||||
return MachineType::NONE;
|
||||
|
||||
if (tok.size() >= 3 && (tok[0] == "INPUT" || tok[0] == "GROUP") &&
|
||||
tok[1] == "(")
|
||||
if (MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::open(ctx, std::string(unquote(tok[2]))))
|
||||
return get_machine_type(ctx, mf);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static bool read_label(std::span<std::string_view> &tok,
|
||||
@@ -271,8 +279,7 @@ static bool read_label(std::span<std::string_view> &tok,
|
||||
template <typename E>
|
||||
static void
|
||||
read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
u16 ver_idx, bool is_cpp) {
|
||||
ctx.version_specified = true;
|
||||
std::string_view ver_str, u16 ver_idx, bool is_cpp) {
|
||||
bool is_global = true;
|
||||
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
@@ -292,11 +299,11 @@ read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
if (!tok.empty() && tok[0] == "\"C\"") {
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands( ctx, tok, ver_idx, false);
|
||||
read_version_script_commands( ctx, tok, ver_str, ver_idx, false);
|
||||
} else {
|
||||
tok = skip(ctx, tok, "\"C++\"");
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands(ctx, tok, ver_idx, true);
|
||||
read_version_script_commands(ctx, tok, ver_str, ver_idx, true);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "}");
|
||||
@@ -305,11 +312,14 @@ read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
}
|
||||
|
||||
if (tok[0] == "*") {
|
||||
ctx.default_version = (is_global ? ver_idx : VER_NDX_LOCAL);
|
||||
ctx.default_version = (is_global ? ver_idx : (u32)VER_NDX_LOCAL);
|
||||
ctx.default_version_from_version_script = true;
|
||||
} else if (is_global) {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), ver_idx, is_cpp});
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
ver_str, ver_idx, is_cpp});
|
||||
} else {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), VER_NDX_LOCAL, is_cpp});
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
ver_str, VER_NDX_LOCAL, is_cpp});
|
||||
}
|
||||
|
||||
tok = tok.subspan(1);
|
||||
@@ -325,17 +335,21 @@ void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok) {
|
||||
u16 next_ver = VER_NDX_LAST_RESERVED + ctx.arg.version_definitions.size() + 1;
|
||||
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
std::string_view ver_str;
|
||||
u16 ver_idx;
|
||||
|
||||
if (tok[0] == "{") {
|
||||
ver_str = "global";
|
||||
ver_idx = VER_NDX_GLOBAL;
|
||||
} else {
|
||||
ver_str = tok[0];
|
||||
ver_idx = next_ver++;
|
||||
ctx.arg.version_definitions.push_back(std::string(tok[0]));
|
||||
tok = tok.subspan(1);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands(ctx, tok, ver_idx, false);
|
||||
read_version_script_commands(ctx, tok, ver_str, ver_idx, false);
|
||||
tok = skip(ctx, tok, "}");
|
||||
if (!tok.empty() && tok[0] != ";")
|
||||
tok = tok.subspan(1);
|
||||
@@ -344,10 +358,9 @@ void read_version_script(Context<E> &ctx, std::span<std::string_view> &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());
|
||||
void parse_version_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
read_version_script(ctx, tok);
|
||||
if (!tok.empty())
|
||||
@@ -357,8 +370,6 @@ void parse_version_script(Context<E> &ctx, std::string path) {
|
||||
template <typename E>
|
||||
void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
bool is_cpp) {
|
||||
ctx.version_specified = true;
|
||||
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
if (tok[0] == "extern") {
|
||||
tok = tok.subspan(1);
|
||||
@@ -381,19 +392,19 @@ void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &to
|
||||
if (tok[0] == "*")
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), VER_NDX_GLOBAL, is_cpp});
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
"global", VER_NDX_GLOBAL, is_cpp});
|
||||
|
||||
tok = skip(ctx, tok.subspan(1), ";");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_dynamic_list(Context<E> &ctx, 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());
|
||||
|
||||
void parse_dynamic_list(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, false);
|
||||
tok = skip(ctx, tok, "}");
|
||||
@@ -403,16 +414,11 @@ void parse_dynamic_list(Context<E> &ctx, std::string path) {
|
||||
SyntaxError(ctx, tok[0]) << "trailing garbage token";
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template \
|
||||
void parse_linker_script(Context<E> &, MappedFile<Context<E>> *); \
|
||||
template \
|
||||
MachineType get_script_output_type(Context<E> &, MappedFile<Context<E>> *); \
|
||||
template \
|
||||
void parse_version_script(Context<E> &, std::string); \
|
||||
template \
|
||||
void parse_dynamic_list(Context<E> &, std::string);
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
template void parse_linker_script(Context<E> &, MappedFile<Context<E>> *);
|
||||
template std::string_view get_script_output_type(Context<E> &, MappedFile<Context<E>> *);
|
||||
template void parse_version_script(Context<E> &, MappedFile<Context<E>> *);
|
||||
template void parse_dynamic_list(Context<E> &, MappedFile<Context<E>> *);
|
||||
|
||||
} // namespace mold::elf
|
||||
+37
-24
@@ -47,7 +47,7 @@
|
||||
// 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.
|
||||
// define not only `onload` but other functions, but that's what it is.
|
||||
//
|
||||
// Here is the steps to use the linker plugin:
|
||||
//
|
||||
@@ -248,14 +248,14 @@ get_input_section_name(const PluginSection section,
|
||||
static PluginStatus
|
||||
get_input_section_contents(const PluginSection section,
|
||||
const char **section_contents,
|
||||
size_t *len) {
|
||||
size_t *len) {
|
||||
LOG << "get_input_section_contents\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
update_section_order(const PluginSection *section_list,
|
||||
int num_sections) {
|
||||
int num_sections) {
|
||||
LOG << "update_section_order\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
@@ -270,8 +270,8 @@ 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.
|
||||
// get_symbols teaches the LTO plugin as to how we have resolved symbols.
|
||||
// The plugin uses the symbol resolution info to optimize 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
|
||||
@@ -308,7 +308,8 @@ get_symbols(const void *handle, int nsyms, PluginSymbol *psyms, bool is_v2) {
|
||||
|
||||
if (sym.file->is_dso)
|
||||
return LDPR_RESOLVED_DYN;
|
||||
if (((ObjectFile<E> *)sym.file)->is_lto_obj)
|
||||
|
||||
if (((ObjectFile<E> *)sym.file)->is_lto_obj && !sym.is_wrapped)
|
||||
return esym.is_undef() ? LDPR_RESOLVED_IR : LDPR_PREEMPTED_IR;
|
||||
return esym.is_undef() ? LDPR_RESOLVED_EXEC : LDPR_PREEMPTED_REG;
|
||||
};
|
||||
@@ -351,10 +352,10 @@ static void restart_process(Context<E> &ctx) {
|
||||
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;
|
||||
|
||||
std::string self = get_self_path();
|
||||
execv(self.c_str(), (char * const *)args.data());
|
||||
std::cerr << "execv failed: " << errno_string() << "\n";
|
||||
_exit(1);
|
||||
@@ -381,10 +382,10 @@ static PluginStatus allow_unique_segment_for_sections() {
|
||||
|
||||
static PluginStatus
|
||||
unique_segment_for_sections(const char *segment_name,
|
||||
uint64_t flags,
|
||||
uint64_t align,
|
||||
const PluginSection *section_list,
|
||||
int num_sections) {
|
||||
uint64_t flags,
|
||||
uint64_t align,
|
||||
const PluginSection *section_list,
|
||||
int num_sections) {
|
||||
LOG << "unique_segment_for_sections\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
@@ -426,8 +427,10 @@ get_api_version(const char *plugin_identifier,
|
||||
if (LAPI_V1 < minimal_api_supported)
|
||||
Fatal(*gctx<E>) << "LTO plugin does not support V0 or V1 API";
|
||||
|
||||
std::string version = mold_version + "\0"s;
|
||||
|
||||
*linker_identifier = "mold";
|
||||
*linker_version = MOLD_VERSION;
|
||||
*linker_version = version.data();
|
||||
|
||||
if (LAPI_V1 <= maximal_api_supported) {
|
||||
is_gcc_linker_api_v1 = true;
|
||||
@@ -500,13 +503,14 @@ static void load_plugin(Context<E> &ctx) {
|
||||
tv.emplace_back(LDPT_GET_API_VERSION, get_api_version<E>);
|
||||
tv.emplace_back(LDPT_NULL, 0);
|
||||
|
||||
PluginStatus status = onload(tv.data());
|
||||
[[maybe_unused]] PluginStatus status = onload(tv.data());
|
||||
assert(status == LDPS_OK);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static ElfSym<E> to_elf_sym(PluginSymbol &psym) {
|
||||
ElfSym<E> esym = {};
|
||||
ElfSym<E> esym;
|
||||
memset(&esym, 0, sizeof(esym));
|
||||
|
||||
switch (psym.def) {
|
||||
case LDPK_DEF:
|
||||
@@ -567,7 +571,7 @@ static bool is_llvm(Context<E> &ctx) {
|
||||
// Returns true if a given linker plugin supports the get_symbols_v3 API.
|
||||
// Any version of LLVM and GCC 12 or newer support it.
|
||||
template <typename E>
|
||||
static bool suppots_v3_api(Context<E> &ctx) {
|
||||
static bool supports_v3_api(Context<E> &ctx) {
|
||||
return is_gcc_linker_api_v1 || is_llvm(ctx);
|
||||
}
|
||||
|
||||
@@ -644,7 +648,6 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
}
|
||||
|
||||
obj->elf_syms = *esyms;
|
||||
obj->sym_fragments.resize(esyms->size());
|
||||
obj->symvers.resize(esyms->size());
|
||||
plugin_symbols.clear();
|
||||
return obj;
|
||||
@@ -655,7 +658,7 @@ 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))
|
||||
if (!ctx.arg.lto_pass2 && !supports_v3_api(ctx))
|
||||
restart_process(ctx);
|
||||
|
||||
assert(phase == 1);
|
||||
@@ -677,6 +680,18 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
}
|
||||
});
|
||||
|
||||
// Symbols specified by the --wrap option needs to be visible from
|
||||
// regular object files.
|
||||
for (std::string_view name : ctx.arg.wrap) {
|
||||
get_symbol(ctx, name)->referenced_by_regular_obj = true;
|
||||
|
||||
std::string_view x = save_string(ctx, "__wrap_" + std::string(name));
|
||||
std::string_view y = save_string(ctx, "__real_" + std::string(name));
|
||||
|
||||
get_symbol(ctx, x)->referenced_by_regular_obj = true;
|
||||
get_symbol(ctx, y)->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)
|
||||
@@ -693,12 +708,10 @@ void lto_cleanup(Context<E> &ctx) {
|
||||
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> &)
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
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> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
+4
-10
@@ -1,5 +1,3 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "mold.h"
|
||||
#include "lto.h"
|
||||
|
||||
@@ -18,14 +16,10 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
void lto_cleanup(Context<E> &ctx) {}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template ObjectFile<E> * \
|
||||
read_lto_object(Context<E> &, MappedFile<Context<E>> *); \
|
||||
template std::vector<ObjectFile<E> *> do_lto(Context<E> &); \
|
||||
template void lto_cleanup(Context<E> &)
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
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> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,5 @@
|
||||
#ifdef _WIN32
|
||||
# include "lto-win32.cc"
|
||||
#else
|
||||
# include "lto-unix.cc"
|
||||
#endif
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../integers.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
enum PluginStatus {
|
||||
@@ -73,27 +75,35 @@ enum PluginOutputFileType {
|
||||
|
||||
struct PluginInputFile {
|
||||
const char *name;
|
||||
int32_t fd;
|
||||
uint64_t offset;
|
||||
uint64_t filesize;
|
||||
i32 fd;
|
||||
u64 offset;
|
||||
u64 filesize;
|
||||
void *handle;
|
||||
};
|
||||
|
||||
struct PluginSection {
|
||||
const void *handle;
|
||||
uint32_t shndx;
|
||||
u32 shndx;
|
||||
};
|
||||
|
||||
struct PluginSymbol {
|
||||
char *name;
|
||||
char *version;
|
||||
char def;
|
||||
char symbol_type;
|
||||
char section_kind;
|
||||
int32_t visibility;
|
||||
uint64_t size;
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
u8 def;
|
||||
u8 symbol_type;
|
||||
u8 section_kind;
|
||||
u8 padding;
|
||||
#else
|
||||
u8 padding;
|
||||
u8 section_kind;
|
||||
u8 symbol_type;
|
||||
u8 def;
|
||||
#endif
|
||||
i32 visibility;
|
||||
u64 size;
|
||||
char *comdat_key;
|
||||
int32_t resolution;
|
||||
i32 resolution;
|
||||
};
|
||||
|
||||
enum PluginSymbolKind {
|
||||
@@ -143,7 +153,7 @@ enum PluginLevel {
|
||||
};
|
||||
|
||||
enum PluginLinkerAPIVersion {
|
||||
LAPI_V0 = 0,
|
||||
LAPI_V0,
|
||||
LAPI_V1,
|
||||
};
|
||||
|
||||
|
||||
+211
-185
@@ -7,6 +7,7 @@
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <regex>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
@@ -26,54 +27,87 @@ namespace mold::elf {
|
||||
// Read the beginning of a given file and returns its machine type
|
||||
// (e.g. EM_X86_64 or EM_386).
|
||||
template <typename E>
|
||||
static MachineType get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
auto get_elf_type = [](u8 *buf) {
|
||||
switch (ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)buf; ehdr.e_machine) {
|
||||
std::string_view get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
auto get_elf_type = [&](u8 *buf) -> std::string_view {
|
||||
bool is_le = (((ElfEhdr<I386> *)buf)->e_ident[EI_DATA] == ELFDATA2LSB);
|
||||
bool is_64;
|
||||
u32 e_machine;
|
||||
|
||||
if (is_le) {
|
||||
auto &ehdr = *(ElfEhdr<I386> *)buf;
|
||||
is_64 = (ehdr.e_ident[EI_CLASS] == ELFCLASS64);
|
||||
e_machine = ehdr.e_machine;
|
||||
} else {
|
||||
auto &ehdr = *(ElfEhdr<M68K> *)buf;
|
||||
is_64 = (ehdr.e_ident[EI_CLASS] == ELFCLASS64);
|
||||
e_machine = ehdr.e_machine;
|
||||
}
|
||||
|
||||
switch (e_machine) {
|
||||
case EM_386:
|
||||
return MachineType::I386;
|
||||
return I386::target_name;
|
||||
case EM_X86_64:
|
||||
return MachineType::X86_64;
|
||||
return X86_64::target_name;
|
||||
case EM_ARM:
|
||||
return MachineType::ARM32;
|
||||
return ARM32::target_name;
|
||||
case EM_AARCH64:
|
||||
return MachineType::ARM64;
|
||||
return ARM64::target_name;
|
||||
case EM_RISCV:
|
||||
return (ehdr.e_ident[EI_CLASS] == ELFCLASS64)
|
||||
? MachineType::RISCV64 : MachineType::RISCV32;
|
||||
if (is_le)
|
||||
return is_64 ? RV64LE::target_name : RV32LE::target_name;
|
||||
return is_64 ? RV64BE::target_name : RV32BE::target_name;
|
||||
case EM_PPC:
|
||||
return PPC32::target_name;
|
||||
case EM_PPC64:
|
||||
return is_le ? PPC64V2::target_name : PPC64V1::target_name;
|
||||
case EM_S390X:
|
||||
return S390X::target_name;
|
||||
case EM_SPARC64:
|
||||
return SPARC64::target_name;
|
||||
case EM_68K:
|
||||
return M68K::target_name;
|
||||
case EM_SH:
|
||||
return SH4::target_name;
|
||||
case EM_ALPHA:
|
||||
return ALPHA::target_name;
|
||||
case EM_PARISC:
|
||||
return HPPA32::target_name;
|
||||
default:
|
||||
return MachineType::NONE;
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
switch (get_file_type(mf)) {
|
||||
bool opt_plugin = !ctx.arg.plugin.empty();
|
||||
|
||||
switch (get_file_type(mf, opt_plugin)) {
|
||||
case FileType::ELF_OBJ:
|
||||
case FileType::ELF_DSO:
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
return get_elf_type(mf->data);
|
||||
case FileType::AR:
|
||||
for (MappedFile<Context<E>> *child : read_fat_archive_members(ctx, mf))
|
||||
if (get_file_type(child) == FileType::ELF_OBJ)
|
||||
if (get_file_type(child, opt_plugin) == FileType::ELF_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return MachineType::NONE;
|
||||
return "";
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_thin_archive_members(ctx, mf))
|
||||
if (get_file_type(child) == FileType::ELF_OBJ)
|
||||
if (get_file_type(child, opt_plugin) == FileType::ELF_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return MachineType::NONE;
|
||||
return "";
|
||||
case FileType::TEXT:
|
||||
return get_script_output_type(ctx, mf);
|
||||
default:
|
||||
return MachineType::NONE;
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void
|
||||
check_file_compatibility(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
MachineType mt = get_machine_type(ctx, mf);
|
||||
if (mt != ctx.arg.emulation)
|
||||
std::string_view target = get_machine_type(ctx, mf);
|
||||
if (target != ctx.arg.emulation)
|
||||
Fatal(ctx) << mf->name << ": incompatible file type: "
|
||||
<< ctx.arg.emulation << " is expected but got " << mt;
|
||||
<< ctx.arg.emulation << " is expected but got " << target;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -131,7 +165,8 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
if (ctx.visited.contains(mf->name))
|
||||
return;
|
||||
|
||||
FileType type = get_file_type(mf);
|
||||
bool opt_plugin = !ctx.arg.plugin.empty();
|
||||
FileType type = get_file_type(mf, opt_plugin);
|
||||
switch (type) {
|
||||
case FileType::ELF_OBJ:
|
||||
ctx.objs.push_back(new_object_file(ctx, mf, ""));
|
||||
@@ -143,7 +178,7 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
case FileType::AR:
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf)) {
|
||||
switch (get_file_type(child)) {
|
||||
switch (get_file_type(child, opt_plugin)) {
|
||||
case FileType::ELF_OBJ:
|
||||
ctx.objs.push_back(new_object_file(ctx, child, mf->name));
|
||||
break;
|
||||
@@ -172,13 +207,14 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static MachineType
|
||||
static std::string_view
|
||||
deduce_machine_type(Context<E> &ctx, std::span<std::string> args) {
|
||||
for (std::string_view arg : args)
|
||||
if (!arg.starts_with('-'))
|
||||
if (auto *mf = MappedFile<Context<E>>::open(ctx, std::string(arg)))
|
||||
if (MachineType ty = get_machine_type(ctx, mf); ty != MachineType::NONE)
|
||||
return ty;
|
||||
if (std::string_view target = get_machine_type(ctx, mf);
|
||||
!target.empty())
|
||||
return target;
|
||||
Fatal(ctx) << "-m option is missing";
|
||||
}
|
||||
|
||||
@@ -188,10 +224,10 @@ MappedFile<Context<E>> *open_library(Context<E> &ctx, std::string path) {
|
||||
if (!mf)
|
||||
return nullptr;
|
||||
|
||||
MachineType ty = get_machine_type(ctx, mf);
|
||||
if (ty == MachineType::NONE || ty == E::machine_type)
|
||||
std::string_view target = get_machine_type(ctx, mf);
|
||||
if (target.empty() || target == E::target_name)
|
||||
return mf;
|
||||
Warn(ctx) << path << ": skipping incompatible file " << (int)ty
|
||||
Warn(ctx) << path << ": skipping incompatible file " << target
|
||||
<< " " << (int)E::e_machine;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -218,6 +254,18 @@ MappedFile<Context<E>> *find_library(Context<E> &ctx, std::string name) {
|
||||
Fatal(ctx) << "library not found: " << name;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *find_from_search_paths(Context<E> &ctx, std::string name) {
|
||||
if (MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, name))
|
||||
return mf;
|
||||
|
||||
for (std::string_view dir : ctx.arg.library_paths)
|
||||
if (MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::open(ctx, std::string(dir) + "/" + name))
|
||||
return mf;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
Timer t(ctx, "read_input_files");
|
||||
@@ -246,17 +294,26 @@ static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
} else if (arg == "--end-lib") {
|
||||
ctx.in_lib = false;
|
||||
} else if (remove_prefix(arg, "--version-script=")) {
|
||||
parse_version_script(ctx, std::string(arg));
|
||||
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--version-script: file not found: " << arg;
|
||||
parse_version_script(ctx, mf);
|
||||
} else if (remove_prefix(arg, "--dynamic-list=")) {
|
||||
parse_dynamic_list(ctx, std::string(arg));
|
||||
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--dynamic-list: file not found: " << arg;
|
||||
parse_dynamic_list(ctx, mf);
|
||||
} else if (remove_prefix(arg, "--export-dynamic-symbol=")) {
|
||||
if (arg == "*")
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.version_patterns.push_back({arg, VER_NDX_GLOBAL, false});
|
||||
ctx.version_specified = true;
|
||||
ctx.version_patterns.push_back({arg, "--export-dynamic-symbol",
|
||||
"global", VER_NDX_GLOBAL, false});
|
||||
} else if (remove_prefix(arg, "--export-dynamic-symbol-list=")) {
|
||||
parse_dynamic_list(ctx, std::string(arg));
|
||||
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--export-dynamic-symbol-list: file not found: " << arg;
|
||||
parse_dynamic_list(ctx, mf);
|
||||
} else if (arg == "--push-state") {
|
||||
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static,
|
||||
ctx.in_lib});
|
||||
@@ -281,81 +338,54 @@ static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
ctx.tg.wait();
|
||||
}
|
||||
|
||||
// Since elf_main is a template, we can't run it without a type parameter.
|
||||
// We speculatively run elf_main with X86_64, and if the speculation was
|
||||
// wrong, re-run it with an actual machine type.
|
||||
template <typename E>
|
||||
static 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 (needs_thunk<E>) {
|
||||
static Counter num_thunks("num_thunks");
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
for (std::unique_ptr<RangeExtensionThunk<E>> &thunk : osec->thunks)
|
||||
num_thunks += thunk->symbols.size();
|
||||
}
|
||||
|
||||
Counter::print();
|
||||
|
||||
for (std::unique_ptr<MergedSection<E>> &sec : ctx.merged_sections)
|
||||
sec->print_stats(ctx);
|
||||
static int redo_main(int argc, char **argv, std::string_view target) {
|
||||
if (target == I386::target_name)
|
||||
return elf_main<I386>(argc, argv);
|
||||
if (target == ARM64::target_name)
|
||||
return elf_main<ARM64>(argc, argv);
|
||||
if (target == ARM32::target_name)
|
||||
return elf_main<ARM32>(argc, argv);
|
||||
if (target == RV64LE::target_name)
|
||||
return elf_main<RV64LE>(argc, argv);
|
||||
if (target == RV64BE::target_name)
|
||||
return elf_main<RV64BE>(argc, argv);
|
||||
if (target == RV32LE::target_name)
|
||||
return elf_main<RV32LE>(argc, argv);
|
||||
if (target == RV32BE::target_name)
|
||||
return elf_main<RV32BE>(argc, argv);
|
||||
if (target == PPC32::target_name)
|
||||
return elf_main<PPC32>(argc, argv);
|
||||
if (target == PPC64V1::target_name)
|
||||
return elf_main<PPC64V1>(argc, argv);
|
||||
if (target == PPC64V2::target_name)
|
||||
return elf_main<PPC64V2>(argc, argv);
|
||||
if (target == S390X::target_name)
|
||||
return elf_main<S390X>(argc, argv);
|
||||
if (target == SPARC64::target_name)
|
||||
return elf_main<SPARC64>(argc, argv);
|
||||
if (target == M68K::target_name)
|
||||
return elf_main<M68K>(argc, argv);
|
||||
if (target == SH4::target_name)
|
||||
return elf_main<SH4>(argc, argv);
|
||||
if (target == ALPHA::target_name)
|
||||
return elf_main<ALPHA>(argc, argv);
|
||||
if (target == HPPA32::target_name)
|
||||
return elf_main<HPPA32>(argc, argv);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static int elf_main(int argc, char **argv) {
|
||||
int elf_main(int argc, char **argv) {
|
||||
Context<E> ctx;
|
||||
|
||||
// Process -run option first. process_run_subcommand() does not return.
|
||||
if (argc >= 2 && (argv[1] == "-run"sv || argv[1] == "--run"sv)) {
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
Fatal(ctx) << ": -run is supported only on Unix";
|
||||
Fatal(ctx) << "-run is supported only on Unix";
|
||||
#endif
|
||||
process_run_subcommand(ctx, argc, argv);
|
||||
}
|
||||
@@ -365,29 +395,13 @@ static int elf_main(int argc, char **argv) {
|
||||
std::vector<std::string> file_args = parse_nonpositional_args(ctx);
|
||||
|
||||
// If no -m option is given, deduce it from input files.
|
||||
if (ctx.arg.emulation == MachineType::NONE)
|
||||
if (ctx.arg.emulation.empty())
|
||||
ctx.arg.emulation = deduce_machine_type(ctx, file_args);
|
||||
|
||||
// Redo if -m is not x86-64.
|
||||
if (ctx.arg.emulation != E::machine_type) {
|
||||
#ifndef MOLD_DEBUG_X86_64_ONLY
|
||||
switch (ctx.arg.emulation) {
|
||||
case MachineType::I386:
|
||||
return elf_main<I386>(argc, argv);
|
||||
case MachineType::ARM64:
|
||||
return elf_main<ARM64>(argc, argv);
|
||||
case MachineType::ARM32:
|
||||
return elf_main<ARM32>(argc, argv);
|
||||
case MachineType::RISCV64:
|
||||
return elf_main<RISCV64>(argc, argv);
|
||||
case MachineType::RISCV32:
|
||||
return elf_main<RISCV32>(argc, argv);
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
#endif
|
||||
unreachable();
|
||||
}
|
||||
if constexpr (is_x86_64<E>)
|
||||
if (ctx.arg.emulation != X86_64::target_name)
|
||||
return redo_main<E>(argc, argv, ctx.arg.emulation);
|
||||
|
||||
Timer t_all(ctx, "all");
|
||||
|
||||
@@ -398,11 +412,6 @@ static int elf_main(int argc, char **argv) {
|
||||
Fatal(ctx) << "chdir failed: " << ctx.arg.directory
|
||||
<< ": " << errno_string();
|
||||
|
||||
if (ctx.arg.relocatable) {
|
||||
combine_objects(ctx, file_args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fork a subprocess unless --no-fork is given.
|
||||
std::function<void()> on_complete;
|
||||
|
||||
@@ -416,7 +425,7 @@ static int elf_main(int argc, char **argv) {
|
||||
|
||||
// Handle --wrap options if any.
|
||||
for (std::string_view name : ctx.arg.wrap)
|
||||
get_symbol(ctx, name)->wrap = true;
|
||||
get_symbol(ctx, name)->is_wrapped = true;
|
||||
|
||||
// Handle --retain-symbols-file options if any.
|
||||
if (ctx.arg.retain_symbols_file)
|
||||
@@ -424,7 +433,7 @@ static int elf_main(int argc, char **argv) {
|
||||
get_symbol(ctx, name)->write_to_symtab = true;
|
||||
|
||||
for (std::string_view arg : ctx.arg.trace_symbol)
|
||||
get_symbol(ctx, arg)->traced = true;
|
||||
get_symbol(ctx, arg)->is_traced = true;
|
||||
|
||||
// Parse input files
|
||||
read_input_files(ctx, file_args);
|
||||
@@ -444,17 +453,31 @@ static int elf_main(int argc, char **argv) {
|
||||
apply_exclude_libs(ctx);
|
||||
|
||||
// Create a dummy file containing linker-synthesized symbols.
|
||||
create_internal_file(ctx);
|
||||
if (!ctx.arg.relocatable)
|
||||
create_internal_file(ctx);
|
||||
|
||||
// Resolve symbols and fix the set of object files that are
|
||||
// included to the final output.
|
||||
// resolve_symbols is 4 things in 1 phase:
|
||||
//
|
||||
// - Determine the set of object files to extract from archives.
|
||||
// - Remove redundant COMDAT sections (e.g. duplicate inline functions).
|
||||
// - Finally, the actual symbol resolution.
|
||||
// - LTO, which requires preliminary symbol resolution before running
|
||||
// and a follow-up re-resolution after the LTO objects are emitted.
|
||||
//
|
||||
// These passes have complex interactions, and unfortunately has to be
|
||||
// put together in a single phase.
|
||||
resolve_symbols(ctx);
|
||||
|
||||
// Resolve mergeable section pieces to merge them.
|
||||
register_section_pieces(ctx);
|
||||
resolve_section_pieces(ctx);
|
||||
|
||||
// Remove redundant comdat sections (e.g. duplicate inline functions).
|
||||
eliminate_comdats(ctx);
|
||||
// Handle --relocatable. Since the linker's behavior is quite different
|
||||
// from the normal one when the option is given, the logic is implemented
|
||||
// to a separate file.
|
||||
if (ctx.arg.relocatable) {
|
||||
combine_objects(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Create .bss sections for common symbols.
|
||||
convert_common_symbols(ctx);
|
||||
@@ -465,7 +488,7 @@ static int elf_main(int argc, char **argv) {
|
||||
// Parse symbol version suffixes (e.g. "foo@ver1").
|
||||
parse_symbol_version(ctx);
|
||||
|
||||
// Set is_import and is_export bits for each symbol.
|
||||
// Set is_imported and is_exported bits for each symbol.
|
||||
compute_import_export(ctx);
|
||||
|
||||
// Read address-significant section information.
|
||||
@@ -483,15 +506,21 @@ static int elf_main(int argc, char **argv) {
|
||||
// Compute sizes of sections containing mergeable strings.
|
||||
compute_merged_section_sizes(ctx);
|
||||
|
||||
// Create instances of linker-synthesized sections such as
|
||||
// .got or .plt.
|
||||
// Create linker-synthesized sections such as .got or .plt.
|
||||
create_synthetic_sections(ctx);
|
||||
|
||||
// Bin input sections into output sections.
|
||||
bin_sections(ctx);
|
||||
// Make sure that there's no duplicate symbol
|
||||
if (!ctx.arg.allow_multiple_definition)
|
||||
check_duplicate_symbols(ctx);
|
||||
|
||||
// Get a list of output sections.
|
||||
append(ctx.chunks, collect_output_sections(ctx));
|
||||
// Warn if symbols with different types are defined under the same name.
|
||||
check_symbol_types(ctx);
|
||||
|
||||
if constexpr (is_ppc64v1<E>)
|
||||
ppc64v1_rewrite_opd(ctx);
|
||||
|
||||
// Bin input sections into output sections.
|
||||
create_output_sections(ctx);
|
||||
|
||||
// Add synthetic symbols such as __ehdr_start or __end.
|
||||
add_synthetic_symbols(ctx);
|
||||
@@ -514,7 +543,7 @@ static int elf_main(int argc, char **argv) {
|
||||
// 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
|
||||
// weakly imported symbols so that they'll have another chance to be
|
||||
// resolved.
|
||||
claim_unresolved_symbols(ctx);
|
||||
|
||||
@@ -530,10 +559,6 @@ static int elf_main(int argc, char **argv) {
|
||||
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)
|
||||
@@ -563,9 +588,12 @@ static int elf_main(int argc, char **argv) {
|
||||
if (!ctx.arg.soname.empty())
|
||||
ctx.dynstr->add_string(ctx.arg.soname);
|
||||
|
||||
if constexpr (is_ppc64v1<E>)
|
||||
ppc64v1_scan_symbols(ctx);
|
||||
|
||||
// Scan relocations to find symbols that need entries in .got, .plt,
|
||||
// .got.plt, .dynsym, .dynstr, etc.
|
||||
scan_rels(ctx);
|
||||
scan_relocations(ctx);
|
||||
|
||||
// Compute sizes of output sections while assigning offsets
|
||||
// within an output section to input sections.
|
||||
@@ -573,9 +601,7 @@ static int elf_main(int argc, char **argv) {
|
||||
|
||||
// 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);
|
||||
});
|
||||
sort_output_sections(ctx);
|
||||
|
||||
// If --packed_dyn_relocs=relr was given, base relocations are stored
|
||||
// to a .relr.dyn section in a compressed form. Construct a compressed
|
||||
@@ -617,23 +643,8 @@ static int elf_main(int argc, char **argv) {
|
||||
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->kind() != OUTPUT_SECTION && chunk->shdr.sh_size == 0;
|
||||
});
|
||||
|
||||
// Set section indices.
|
||||
for (i64 i = 0, shndx = 1; i < ctx.chunks.size(); i++)
|
||||
if (ctx.chunks[i]->kind() != 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);
|
||||
// Compute the section header values for all sections.
|
||||
compute_section_headers(ctx);
|
||||
|
||||
// Assign offsets to output sections
|
||||
i64 filesize = set_osec_offsets(ctx);
|
||||
@@ -645,7 +656,9 @@ static int elf_main(int argc, char **argv) {
|
||||
if constexpr (is_riscv<E>)
|
||||
filesize = riscv_resize_sections(ctx);
|
||||
|
||||
// Fix linker-synthesized symbol addresses.
|
||||
// At this point, memory layout is fixed.
|
||||
|
||||
// Set actual addresses to linker-synthesized symbols.
|
||||
fix_synthetic_symbols(ctx);
|
||||
|
||||
// Beyond this, you can assume that symbol addresses including their
|
||||
@@ -656,7 +669,7 @@ static int elf_main(int argc, char **argv) {
|
||||
if (ctx.arg.compress_debug_sections != COMPRESS_NONE)
|
||||
filesize = compress_debug_sections(ctx);
|
||||
|
||||
// At this point, file layout is fixed.
|
||||
// At this point, both memory and file layouts are fixed.
|
||||
|
||||
t_before_copy.stop();
|
||||
|
||||
@@ -667,22 +680,8 @@ static int elf_main(int argc, char **argv) {
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
report_undef_errors(ctx);
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<E, ARM32>)
|
||||
sort_arm_exidx(ctx);
|
||||
// Copy input sections to the output file and apply relocations.
|
||||
copy_chunks(ctx);
|
||||
|
||||
// Some part of .gdb_index couldn't be computed until other debug
|
||||
// sections are complete. We have complete debug sections now, so
|
||||
@@ -697,6 +696,9 @@ static int elf_main(int argc, char **argv) {
|
||||
// Zero-clear paddings between sections
|
||||
clear_padding(ctx);
|
||||
|
||||
// .note.gnu.build-id section contains a cryptographic hash of the
|
||||
// entire output file. Now that we wrote everything except build-id,
|
||||
// we can compute it.
|
||||
if (ctx.buildid)
|
||||
ctx.buildid->write_buildid(ctx);
|
||||
|
||||
@@ -740,13 +742,37 @@ static int elf_main(int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void read_file(Context<E> &, MappedFile<Context<E>> *);
|
||||
|
||||
#ifdef MOLD_X86_64
|
||||
|
||||
extern template int elf_main<I386>(int, char **);
|
||||
extern template int elf_main<ARM32>(int, char **);
|
||||
extern template int elf_main<ARM64>(int, char **);
|
||||
extern template int elf_main<RV32BE>(int, char **);
|
||||
extern template int elf_main<RV32LE>(int, char **);
|
||||
extern template int elf_main<RV64LE>(int, char **);
|
||||
extern template int elf_main<RV64BE>(int, char **);
|
||||
extern template int elf_main<PPC32>(int, char **);
|
||||
extern template int elf_main<PPC64V1>(int, char **);
|
||||
extern template int elf_main<PPC64V2>(int, char **);
|
||||
extern template int elf_main<S390X>(int, char **);
|
||||
extern template int elf_main<SPARC64>(int, char **);
|
||||
extern template int elf_main<M68K>(int, char **);
|
||||
extern template int elf_main<SH4>(int, char **);
|
||||
extern template int elf_main<ALPHA>(int, char **);
|
||||
extern template int elf_main<HPPA32>(int, char **);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
return elf_main<X86_64>(argc, argv);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void read_file(Context<E> &, MappedFile<Context<E>> *);
|
||||
#else
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
template int elf_main<E>(int, char **);
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace mold::elf
|
||||
+2
-3
@@ -109,9 +109,8 @@ void print_map(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void print_map(Context<E> &ctx);
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
template void print_map(Context<E> &ctx);
|
||||
|
||||
} // namespace mold::elf
|
||||
+1
-1
@@ -22,7 +22,7 @@ static char *get_mold_path() {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void debug_print(char *fmt, ...) {
|
||||
static void debug_print(const char *fmt, ...) {
|
||||
if (!getenv("MOLD_WRAPPER_DEBUG"))
|
||||
return;
|
||||
|
||||
|
||||
+736
-375
File diff suppressed because it is too large.
Load diff
+1227
-570
File diff suppressed because it is too large.
Load diff
+1255
-410
File diff suppressed because it is too large.
Load diff
+144
-543
@@ -1,596 +1,197 @@
|
||||
// This file implements the -r or ---relocatable option. If the option
|
||||
// is given to a linker, the linker creates not an executable nor a
|
||||
// shared library file but instead create another object file by
|
||||
// combining input object files.
|
||||
// This file implements -r or --relocatable. That option forces the linker
|
||||
// to combine input object files into another single large object file.
|
||||
// Since the behavior of the linker when the option is given is quite
|
||||
// different from that of the normal execution mode, we separate code for
|
||||
// the feature into this separate file.
|
||||
//
|
||||
// The behavior of a linker with -r is pretty different from the one
|
||||
// without -r, and adding code for -r to the regular code path could
|
||||
// severely complicate it. Therefore, we implement -r as an
|
||||
// independent feature from others. That's why this file share only a
|
||||
// small amount of code with other files.
|
||||
// The --relocatable option isn't used very often. After all, if you want
|
||||
// to combine object files into a single file, you could use `ar`.
|
||||
// However, some programs use it in a creative manner which is hard to be
|
||||
// substituted with static archives, so we need to support this option in
|
||||
// the same way as GNU ld does. A notable example is GHC (Glasgow Haskell
|
||||
// Compiler). GHC has its own dynamic linker which can load a .o file (as
|
||||
// opposed to a .so) into memory. GHC's module is not a shared object file
|
||||
// but a combined object file.
|
||||
//
|
||||
// Since the -r option is a minor feature, we don't aim for speed in
|
||||
// this file. This is a "better than nothing" implementation of the -r
|
||||
// option.
|
||||
// There are many different ways to combine object files into a single file.
|
||||
// The simplest approach would be to just copy all sections from input files
|
||||
// to an output file as-is with a few exceptions for singleton sections such
|
||||
// as the symbol table or the string table. That works, but that's not
|
||||
// compatible with GNU ld.
|
||||
//
|
||||
// Here is the strategy as to how to combine multiple object files
|
||||
// into one:
|
||||
// To be compatible with GNU ld, we need to do the followings:
|
||||
//
|
||||
// - Regular sections containing opaque data (e.g. ".text" or
|
||||
// ".data") are just copied as-is without merging.
|
||||
// - Regular sections containing opaque data (e.g. ".text" or ".data")
|
||||
// are just copied as-is. Two sections with the same name are merged.
|
||||
//
|
||||
// - .symtab, .strtab and .shstrtab are merged.
|
||||
//
|
||||
// - COMDAT groups are uniquified.
|
||||
//
|
||||
// - Relocation sections are copied one by one, but we need to fix
|
||||
// symbol indices.
|
||||
// - Relocations are copied, but we need to fix symbol indices.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
#include "../output-file.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E> class RObjectFile;
|
||||
|
||||
// Create linker-synthesized sections
|
||||
template <typename E>
|
||||
class RChunk {
|
||||
public:
|
||||
RChunk() {
|
||||
out_shdr.sh_addralign = 1;
|
||||
}
|
||||
static void r_create_synthetic_sections(Context<E> &ctx) {
|
||||
auto push = [&]<typename T>(T *x) {
|
||||
ctx.chunks.push_back(x);
|
||||
ctx.chunk_pool.emplace_back(x);
|
||||
return x;
|
||||
};
|
||||
|
||||
virtual ~RChunk() = default;
|
||||
virtual void update_shdr(Context<E> &ctx) {}
|
||||
virtual void write_to(Context<E> &ctx) = 0;
|
||||
|
||||
std::string_view name;
|
||||
i64 shndx = 0;
|
||||
ElfShdr<E> in_shdr = {};
|
||||
ElfShdr<E> out_shdr = {};
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RInputSection : public RChunk<E> {
|
||||
public:
|
||||
RInputSection(Context<E> &ctx, RObjectFile<E> &file, const ElfShdr<E> &shdr);
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void write_to(Context<E> &ctx) override;
|
||||
|
||||
RObjectFile<E> &file;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RSymtabSection : public RChunk<E> {
|
||||
public:
|
||||
RSymtabSection() {
|
||||
this->name = ".symtab";
|
||||
this->out_shdr.sh_type = SHT_SYMTAB;
|
||||
this->out_shdr.sh_entsize = sizeof(ElfSym<E>);
|
||||
this->out_shdr.sh_addralign = sizeof(Word<E>);
|
||||
}
|
||||
|
||||
void add_local_symbol(Context<E> &ctx, RObjectFile<E> &file, i64 idx);
|
||||
void add_global_symbol(Context<E> &ctx, RObjectFile<E> &file, i64 idx);
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void write_to(Context<E> &ctx) override;
|
||||
|
||||
std::unordered_map<std::string_view, i64> sym_map;
|
||||
std::vector<ElfSym<E>> syms{1};
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RStrtabSection : public RChunk<E> {
|
||||
public:
|
||||
RStrtabSection(std::string_view name) {
|
||||
this->name = name;
|
||||
this->out_shdr.sh_type = SHT_STRTAB;
|
||||
this->out_shdr.sh_size = 1;
|
||||
}
|
||||
|
||||
i64 add_string(std::string_view str);
|
||||
void write_to(Context<E> &ctx) override;
|
||||
|
||||
std::unordered_map<std::string_view, i64> strings;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class ROutputEhdr : public RChunk<E> {
|
||||
public:
|
||||
ROutputEhdr() {
|
||||
this->out_shdr.sh_size = sizeof(ElfEhdr<E>);
|
||||
}
|
||||
|
||||
void write_to(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class ROutputShdr : public RChunk<E> {
|
||||
public:
|
||||
ROutputShdr() {
|
||||
this->out_shdr.sh_size = sizeof(ElfShdr<E>);
|
||||
}
|
||||
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void write_to(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RObjectFile {
|
||||
public:
|
||||
RObjectFile(Context<E> &ctx, MappedFile<Context<E>> &mf, bool is_alive);
|
||||
|
||||
void remove_comdats(Context<E> &ctx,
|
||||
std::unordered_set<std::string_view> &groups);
|
||||
|
||||
template <typename T>
|
||||
std::span<T> get_data(Context<E> &ctx, const ElfShdr<E> &shdr);
|
||||
|
||||
MappedFile<Context<E>> &mf;
|
||||
std::span<ElfShdr<E>> elf_sections;
|
||||
std::vector<std::unique_ptr<RInputSection<E>>> sections;
|
||||
std::span<const ElfSym<E>> syms;
|
||||
std::vector<i64> symidx;
|
||||
std::unordered_set<std::string_view> defined_syms;
|
||||
std::unordered_set<std::string_view> undef_syms;
|
||||
i64 symtab_shndx = 0;
|
||||
i64 first_global = 0;
|
||||
bool is_alive;
|
||||
const char *strtab = nullptr;
|
||||
const char *shstrtab = nullptr;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void RSymtabSection<E>::add_local_symbol(Context<E> &ctx, RObjectFile<E> &file,
|
||||
i64 idx) {
|
||||
ElfSym<E> sym = file.syms[idx];
|
||||
assert(sym.st_bind == STB_LOCAL);
|
||||
|
||||
if (!sym.is_undef() && !sym.is_abs() && !sym.is_common()) {
|
||||
if (!file.sections[sym.st_shndx])
|
||||
return;
|
||||
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
|
||||
}
|
||||
|
||||
std::string_view name = file.strtab + sym.st_name;
|
||||
sym.st_name = ctx.r_strtab->add_string(name);
|
||||
|
||||
file.symidx[idx] = syms.size();
|
||||
syms.push_back(sym);
|
||||
ctx.ehdr = push(new OutputEhdr<E>(0));
|
||||
ctx.shdr = push(new OutputShdr<E>);
|
||||
ctx.eh_frame = push(new EhFrameSection<E>);
|
||||
ctx.eh_frame_reloc = push(new EhFrameRelocSection<E>);
|
||||
ctx.strtab = push(new StrtabSection<E>);
|
||||
ctx.symtab = push(new SymtabSection<E>);
|
||||
ctx.shstrtab = push(new ShstrtabSection<E>);
|
||||
ctx.note_property = push(new NotePropertySection<E>);
|
||||
}
|
||||
|
||||
// Create SHT_GROUP (i.e. comdat group) sections. We uniquify comdat
|
||||
// sections by signature. We want to propagate input comdat groups as
|
||||
// output comdat groups if they are still alive after uniquification.
|
||||
template <typename E>
|
||||
void RSymtabSection<E>::add_global_symbol(Context<E> &ctx, RObjectFile<E> &file,
|
||||
i64 idx) {
|
||||
ElfSym<E> sym = file.syms[idx];
|
||||
assert(sym.st_bind != STB_LOCAL);
|
||||
static void create_comdat_group_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "create_comdat_group_sections");
|
||||
|
||||
std::string_view name = file.strtab + sym.st_name;
|
||||
auto [it, inserted] = sym_map.insert({name, syms.size()});
|
||||
std::vector<std::vector<Chunk<E> *>> vec{ctx.objs.size()};
|
||||
|
||||
if (inserted) {
|
||||
if (!sym.is_undef() && !sym.is_abs() && !sym.is_common())
|
||||
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
|
||||
sym.st_name = ctx.r_strtab->add_string(name);
|
||||
file.symidx[idx] = syms.size();
|
||||
syms.push_back(sym);
|
||||
return;
|
||||
}
|
||||
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
|
||||
ObjectFile<E> &file = *ctx.objs[i];
|
||||
|
||||
file.symidx[idx] = it->second;
|
||||
|
||||
ElfSym<E> &existing = syms[it->second];
|
||||
if (existing.is_undef() && !sym.is_undef()) {
|
||||
if (!sym.is_abs() && !sym.is_common())
|
||||
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
|
||||
sym.st_name = existing.st_name;
|
||||
existing = sym;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RSymtabSection<E>::update_shdr(Context<E> &ctx) {
|
||||
this->out_shdr.sh_size = syms.size() * sizeof(ElfSym<E>);
|
||||
this->out_shdr.sh_link = ctx.r_strtab->shndx;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RSymtabSection<E>::write_to(Context<E> &ctx) {
|
||||
ElfSym<E> *buf = (ElfSym<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
for (i64 i = 1; i < syms.size(); i++)
|
||||
buf[i] = syms[i];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
RInputSection<E>::RInputSection(Context<E> &ctx, RObjectFile<E> &file,
|
||||
const ElfShdr<E> &shdr)
|
||||
: file(file) {
|
||||
this->name = file.shstrtab + shdr.sh_name;
|
||||
this->in_shdr = shdr;
|
||||
this->out_shdr = shdr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RInputSection<E>::update_shdr(Context<E> &ctx) {
|
||||
switch (this->in_shdr.sh_type) {
|
||||
case SHT_GROUP:
|
||||
this->out_shdr.sh_link = ctx.r_symtab->shndx;
|
||||
this->out_shdr.sh_info = file.symidx[this->in_shdr.sh_info];
|
||||
break;
|
||||
case SHT_REL:
|
||||
case SHT_RELA:
|
||||
this->out_shdr.sh_link = ctx.r_symtab->shndx;
|
||||
this->out_shdr.sh_info = file.sections[this->in_shdr.sh_info]->shndx;
|
||||
break;
|
||||
default:
|
||||
if (this->in_shdr.sh_link) {
|
||||
std::unique_ptr<RInputSection<E>> &sec =
|
||||
file.sections[this->in_shdr.sh_info];
|
||||
|
||||
if (sec)
|
||||
this->out_shdr.sh_link = sec->shndx;
|
||||
else if (this->in_shdr.sh_link == file.symtab_shndx)
|
||||
this->out_shdr.sh_link = ctx.r_symtab->shndx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RInputSection<E>::write_to(Context<E> &ctx) {
|
||||
if (this->in_shdr.sh_type == SHT_NOBITS)
|
||||
return;
|
||||
|
||||
std::span<u8> contents = file.template get_data<u8>(ctx, this->in_shdr);
|
||||
memcpy(ctx.buf + this->out_shdr.sh_offset, contents.data(), contents.size());
|
||||
|
||||
switch (this->in_shdr.sh_type) {
|
||||
case SHT_GROUP: {
|
||||
u32 *mem = (u32 *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
for (i64 i = 1; i < this->out_shdr.sh_size / sizeof(u32); i++)
|
||||
mem[i] = file.sections[mem[i]]->shndx;
|
||||
break;
|
||||
}
|
||||
case SHT_REL:
|
||||
case SHT_RELA: {
|
||||
ElfRel<E> *rel = (ElfRel<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
i64 size = this->out_shdr.sh_size / sizeof(ElfRel<E>);
|
||||
|
||||
for (i64 i = 0; i < size; i++) {
|
||||
const ElfSym<E> &sym = file.syms[rel[i].r_sym];
|
||||
if (sym.is_undef() || sym.is_abs() || sym.is_common() ||
|
||||
file.sections[sym.st_shndx])
|
||||
rel[i].r_sym = file.symidx[rel[i].r_sym];
|
||||
else
|
||||
memset(rel + i, 0, sizeof(ElfRel<E>));
|
||||
}
|
||||
|
||||
i64 i = 0;
|
||||
i64 j = 0;
|
||||
for (; j < size; j++)
|
||||
if (rel[j].r_type)
|
||||
rel[i++] = rel[j];
|
||||
for (; i < size; i++)
|
||||
memset(rel + i, 0, sizeof(ElfRel<E>));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 RStrtabSection<E>::add_string(std::string_view str) {
|
||||
auto [it, inserted] = strings.insert({str, this->out_shdr.sh_size});
|
||||
if (inserted)
|
||||
this->out_shdr.sh_size += str.size() + 1;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RStrtabSection<E>::write_to(Context<E> &ctx) {
|
||||
for (auto [str, offset] : strings)
|
||||
memcpy(ctx.buf + this->out_shdr.sh_offset + offset, str.data(), str.size());
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ROutputEhdr<E>::write_to(Context<E> &ctx) {
|
||||
ElfEhdr<E> &hdr = *(ElfEhdr<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
memcpy(&hdr.e_ident, "\177ELF", 4);
|
||||
hdr.e_ident[EI_CLASS] = (sizeof(Word<E>) == 8) ? ELFCLASS64 : ELFCLASS32;
|
||||
hdr.e_ident[EI_DATA] = ELFDATA2LSB;
|
||||
hdr.e_ident[EI_VERSION] = EV_CURRENT;
|
||||
hdr.e_type = ET_REL;
|
||||
hdr.e_machine = E::e_machine;
|
||||
hdr.e_version = EV_CURRENT;
|
||||
hdr.e_shoff = ctx.r_shdr->out_shdr.sh_offset;
|
||||
hdr.e_ehsize = sizeof(ElfEhdr<E>);
|
||||
hdr.e_shentsize = sizeof(ElfShdr<E>);
|
||||
hdr.e_shstrndx = ctx.r_shstrtab->shndx;
|
||||
hdr.e_shnum = ctx.r_shdr->out_shdr.sh_size / sizeof(ElfShdr<E>);
|
||||
hdr.e_shstrndx = ctx.r_shstrtab->shndx;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ROutputShdr<E>::update_shdr(Context<E> &ctx) {
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk->shndx)
|
||||
this->out_shdr.sh_size += sizeof(ElfShdr<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ROutputShdr<E>::write_to(Context<E> &ctx) {
|
||||
ElfShdr<E> *hdr = (ElfShdr<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk->shndx)
|
||||
hdr[chunk->shndx] = chunk->out_shdr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
RObjectFile<E>::RObjectFile(Context<E> &ctx, MappedFile<Context<E>> &mf,
|
||||
bool is_alive)
|
||||
: mf(mf), is_alive(is_alive) {
|
||||
// Read ELF header and section header
|
||||
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)mf.data;
|
||||
ElfShdr<E> *sh_begin = (ElfShdr<E> *)(mf.data + ehdr.e_shoff);
|
||||
i64 num_sections = (ehdr.e_shnum == 0) ? sh_begin->sh_size : ehdr.e_shnum;
|
||||
elf_sections = {sh_begin, sh_begin + num_sections};
|
||||
sections.resize(num_sections);
|
||||
|
||||
// Read .shstrtab
|
||||
i64 shstrtab_idx = (ehdr.e_shstrndx == SHN_XINDEX)
|
||||
? sh_begin->sh_link : ehdr.e_shstrndx;
|
||||
shstrtab = (char *)(mf.data + elf_sections[shstrtab_idx].sh_offset);
|
||||
|
||||
// Read .symtab
|
||||
for (i64 i = 1; i < elf_sections.size(); i++) {
|
||||
ElfShdr<E> &shdr = elf_sections[i];
|
||||
if (shdr.sh_type == SHT_SYMTAB) {
|
||||
syms = get_data<const ElfSym<E>>(ctx, shdr);
|
||||
strtab = (char *)(mf.data + elf_sections[shdr.sh_link].sh_offset);
|
||||
symtab_shndx = i;
|
||||
first_global = shdr.sh_info;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
symidx.resize(syms.size());
|
||||
|
||||
// Read sections
|
||||
for (i64 i = 1; i < elf_sections.size(); i++) {
|
||||
ElfShdr<E> &shdr = elf_sections[i];
|
||||
switch (shdr.sh_type) {
|
||||
case SHT_NULL:
|
||||
case SHT_SYMTAB:
|
||||
case SHT_STRTAB:
|
||||
break;
|
||||
default:
|
||||
sections[i].reset(new RInputSection(ctx, *this, shdr));
|
||||
}
|
||||
}
|
||||
|
||||
// Read global symbols
|
||||
for (i64 i = first_global; i < syms.size(); i++) {
|
||||
std::string_view name = strtab + syms[i].st_name;
|
||||
if (syms[i].is_defined())
|
||||
defined_syms.insert(name);
|
||||
else
|
||||
undef_syms.insert(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicate comdat groups
|
||||
template <typename E>
|
||||
void RObjectFile<E>::remove_comdats(Context<E> &ctx,
|
||||
std::unordered_set<std::string_view> &groups) {
|
||||
for (i64 i = 1; i < sections.size(); i++) {
|
||||
if (!sections[i])
|
||||
continue;
|
||||
ElfShdr<E> &shdr = sections[i]->in_shdr;
|
||||
if (shdr.sh_type != SHT_GROUP)
|
||||
continue;
|
||||
|
||||
// Get a comdat group signature and insert it into a set.
|
||||
const ElfSym<E> &sym = syms[shdr.sh_info];
|
||||
std::string_view signature = strtab + sym.st_name;
|
||||
if (groups.insert(signature).second)
|
||||
continue;
|
||||
|
||||
// If it is a duplicate, remove it and its members.
|
||||
for (i64 j : this->template get_data<u32>(ctx, shdr).subspan(1))
|
||||
sections[j] = nullptr;
|
||||
sections[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
template <typename T>
|
||||
std::span<T> RObjectFile<E>::get_data(Context<E> &ctx, const ElfShdr<E> &shdr) {
|
||||
T *begin = (T *)(mf.data + shdr.sh_offset);
|
||||
T *end = (T *)(mf.data + shdr.sh_offset + shdr.sh_size);
|
||||
return {begin, end};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<std::unique_ptr<RObjectFile<E>>>
|
||||
open_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
std::vector<std::unique_ptr<RObjectFile<E>>> files;
|
||||
bool whole_archive = false;
|
||||
|
||||
while (!args.empty()) {
|
||||
const std::string &arg = args[0];
|
||||
args = args.subspan(1);
|
||||
|
||||
if (arg == "--whole-archive") {
|
||||
whole_archive = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == "--no-whole-archive") {
|
||||
whole_archive = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.starts_with("--version-script=") ||
|
||||
arg.starts_with("--dynamic-list=") ||
|
||||
arg.starts_with("--export-dynamic-symbol=") ||
|
||||
arg.starts_with("--export-dynamic-symbol-list="))
|
||||
continue;
|
||||
|
||||
MappedFile<Context<E>> *mf = nullptr;
|
||||
|
||||
if (arg == "-l") {
|
||||
mf = find_library(ctx, arg.substr(2));
|
||||
} else {
|
||||
if (arg.starts_with('-'))
|
||||
for (ComdatGroupRef<E> &ref : file.comdat_groups) {
|
||||
if (ref.group->owner != file.priority)
|
||||
continue;
|
||||
mf = MappedFile<Context<E>>::must_open(ctx, arg);
|
||||
}
|
||||
|
||||
switch (get_file_type(mf)) {
|
||||
case FileType::ELF_OBJ:
|
||||
files.emplace_back(new RObjectFile<E>(ctx, *mf, true));
|
||||
break;
|
||||
case FileType::AR:
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf))
|
||||
if (get_file_type(child) == FileType::ELF_OBJ)
|
||||
files.emplace_back(new RObjectFile<E>(ctx, *child, whole_archive));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
Symbol<E> *sym = file.symbols[file.elf_sections[ref.sect_idx].sh_info];
|
||||
assert(sym);
|
||||
|
||||
std::vector<Chunk<E> *> members;
|
||||
for (u32 j : ref.members) {
|
||||
const ElfShdr<E> &shdr = file.elf_sections[j];
|
||||
if (shdr.sh_type == (E::is_rela ? SHT_RELA : SHT_REL)) {
|
||||
InputSection<E> &isec = *file.sections[shdr.sh_info];
|
||||
members.push_back(isec.output_section->reloc_sec.get());
|
||||
} else {
|
||||
InputSection<E> &isec = *file.sections[j];
|
||||
members.push_back(isec.output_section);
|
||||
}
|
||||
}
|
||||
|
||||
vec[i].push_back(new ComdatGroupSection<E>(*sym, std::move(members)));
|
||||
}
|
||||
});
|
||||
|
||||
for (std::vector<Chunk<E> *> &vec2 : vec) {
|
||||
for (Chunk<E> *chunk : vec2) {
|
||||
ctx.chunks.push_back(chunk);
|
||||
ctx.chunk_pool.emplace_back(chunk);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
// Unresolved undefined symbols in the -r mode are simply propagated to an
|
||||
// output file as undefined symbols. This function guarantees that
|
||||
// unresolved undefined symbols belongs to some input file.
|
||||
template <typename E>
|
||||
static i64 assign_offsets(Context<E> &ctx) {
|
||||
i64 offset = 0;
|
||||
for (RChunk<E> *chunk : ctx.r_chunks) {
|
||||
offset = align_to(offset, chunk->out_shdr.sh_addralign);
|
||||
chunk->out_shdr.sh_offset = offset;
|
||||
offset += chunk->out_shdr.sh_size;
|
||||
static void r_claim_unresolved_symbols(Context<E> &ctx) {
|
||||
Timer t(ctx, "r_claim_unresolved_symbols");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
if (!file->is_alive)
|
||||
return;
|
||||
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
|
||||
const ElfSym<E> &esym = file->elf_syms[i];
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
if (!esym.is_undef())
|
||||
continue;
|
||||
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
if (sym.file &&
|
||||
(!sym.esym().is_undef() || sym.file->priority <= file->priority))
|
||||
continue;
|
||||
|
||||
sym.file = file;
|
||||
sym.origin = 0;
|
||||
sym.value = 0;
|
||||
sym.sym_idx = i;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set output section in-file offsets. Output section memory addresses
|
||||
// are left as zero.
|
||||
template <typename E>
|
||||
static u64 r_set_osec_offsets(Context<E> &ctx) {
|
||||
u64 offset = 0;
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
offset = align_to(offset, chunk->shdr.sh_addralign);
|
||||
chunk->shdr.sh_offset = offset;
|
||||
offset += chunk->shdr.sh_size;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
static bool contains(std::unordered_set<std::string_view> &a,
|
||||
std::unordered_set<std::string_view> &b) {
|
||||
for (std::string_view x : b)
|
||||
if (a.contains(x))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void combine_objects(Context<E> &ctx, std::span<std::string> file_args) {
|
||||
// Read object files
|
||||
std::vector<std::unique_ptr<RObjectFile<E>>> files = open_files(ctx, file_args);
|
||||
void combine_objects(Context<E> &ctx) {
|
||||
compute_merged_section_sizes(ctx);
|
||||
|
||||
// Identify needed objects
|
||||
std::unordered_set<std::string_view> undef_syms;
|
||||
auto add_syms = [&](RObjectFile<E> &file) {
|
||||
undef_syms.insert(file.undef_syms.begin(), file.undef_syms.end());
|
||||
for (std::string_view name : file.defined_syms)
|
||||
undef_syms.erase(name);
|
||||
file.is_alive = true;
|
||||
};
|
||||
create_output_sections(ctx);
|
||||
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
if (file->is_alive)
|
||||
add_syms(*file);
|
||||
r_create_synthetic_sections(ctx);
|
||||
|
||||
for (;;) {
|
||||
bool added = false;
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files) {
|
||||
if (!file->is_alive && contains(undef_syms, file->defined_syms)) {
|
||||
add_syms(*file);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
if (!added)
|
||||
break;
|
||||
}
|
||||
r_claim_unresolved_symbols(ctx);
|
||||
|
||||
std::erase_if(files, [](std::unique_ptr<RObjectFile<E>> &file) {
|
||||
return !file->is_alive;
|
||||
});
|
||||
compute_section_sizes(ctx);
|
||||
|
||||
// Remove duplicate comdat groups
|
||||
std::unordered_set<std::string_view> comdat_groups;
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
file->remove_comdats(ctx, comdat_groups);
|
||||
sort_output_sections(ctx);
|
||||
|
||||
// Create headers and linker-synthesized sections
|
||||
ROutputEhdr<E> ehdr;
|
||||
ROutputShdr<E> shdr;
|
||||
RSymtabSection<E> symtab;
|
||||
RStrtabSection<E> shstrtab(".shstrtab");
|
||||
RStrtabSection<E> strtab(".strtab");
|
||||
create_output_symtab(ctx);
|
||||
|
||||
ctx.r_chunks.push_back(&ehdr);
|
||||
ctx.r_chunks.push_back(&shstrtab);
|
||||
ctx.r_chunks.push_back(&strtab);
|
||||
ctx.eh_frame->construct(ctx);
|
||||
|
||||
ctx.r_ehdr = &ehdr;
|
||||
ctx.r_shdr = &shdr;
|
||||
ctx.r_shstrtab = &shstrtab;
|
||||
ctx.r_strtab = &strtab;
|
||||
ctx.r_symtab = &symtab;
|
||||
create_reloc_sections(ctx);
|
||||
|
||||
// Add input sections to output sections
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
for (std::unique_ptr<RInputSection<E>> &sec : file->sections)
|
||||
if (sec)
|
||||
ctx.r_chunks.push_back(sec.get());
|
||||
create_comdat_group_sections(ctx);
|
||||
|
||||
ctx.r_chunks.push_back(&symtab);
|
||||
ctx.r_chunks.push_back(&shdr);
|
||||
compute_section_headers(ctx);
|
||||
|
||||
// Assign output section indices
|
||||
i64 shndx = 1;
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk != &ehdr && chunk != &shdr)
|
||||
chunk->shndx = shndx++;
|
||||
|
||||
// Add section names to .shstrtab
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk->shndx)
|
||||
chunk->out_shdr.sh_name = shstrtab.add_string(chunk->name);
|
||||
|
||||
// Copy symbols from input objects to an output object
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
for (i64 i = 1; i < file->first_global; i++)
|
||||
symtab.add_local_symbol(ctx, *file, i);
|
||||
|
||||
symtab.out_shdr.sh_info = symtab.syms.size();
|
||||
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
for (i64 i = file->first_global; i < file->syms.size(); i++)
|
||||
symtab.add_global_symbol(ctx, *file, i);
|
||||
|
||||
// Finalize section header
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
// Open an output file
|
||||
i64 filesize = assign_offsets(ctx);
|
||||
std::unique_ptr<OutputFile<Context<E>>> out =
|
||||
i64 filesize = r_set_osec_offsets(ctx);
|
||||
ctx.output_file =
|
||||
OutputFile<Context<E>>::open(ctx, ctx.arg.output, filesize, 0666);
|
||||
memset(out->buf, 0, filesize);
|
||||
ctx.buf = out->buf;
|
||||
ctx.buf = ctx.output_file->buf;
|
||||
|
||||
// Write to the output file
|
||||
for (RChunk<E> *chunk : ctx.r_chunks)
|
||||
chunk->write_to(ctx);
|
||||
out->close(ctx);
|
||||
copy_chunks(ctx);
|
||||
clear_padding(ctx);
|
||||
ctx.output_file->close(ctx);
|
||||
ctx.checkpoint();
|
||||
|
||||
if (ctx.arg.print_map)
|
||||
print_map(ctx);
|
||||
|
||||
if (ctx.arg.stats)
|
||||
show_stats(ctx);
|
||||
|
||||
if (ctx.arg.perf)
|
||||
print_timer_records(ctx.timer_records);
|
||||
|
||||
if (ctx.arg.quick_exit)
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void combine_objects(Context<E> &, std::span<std::string>);
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
template void combine_objects(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
+15
-11
@@ -1,4 +1,7 @@
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
|
||||
#include "mold.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <signal.h>
|
||||
@@ -10,6 +13,7 @@
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
#ifdef MOLD_X86_64
|
||||
// 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.
|
||||
@@ -49,10 +53,11 @@ std::function<void()> fork_child() {
|
||||
|
||||
return [=] {
|
||||
char buf[] = {1};
|
||||
int n = write(pipefd[1], buf, 1);
|
||||
[[maybe_unused]] int n = write(pipefd[1], buf, 1);
|
||||
assert(n == 1);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename E>
|
||||
static std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
|
||||
@@ -62,13 +67,11 @@ static std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
|
||||
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
|
||||
// If not found, search $(MOLD_LIBDIR)/mold, which is /usr/local/lib/mold
|
||||
// by default.
|
||||
path = LIBDIR "/mold/mold-wrapper.so";
|
||||
path = MOLD_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";
|
||||
@@ -81,13 +84,13 @@ static std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
|
||||
template <typename E>
|
||||
[[noreturn]]
|
||||
void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
std::string_view arg1 = argv[1];
|
||||
assert(arg1 == "-run" || arg1 == "--run");
|
||||
assert(argv[1] == "-run"s || argv[1] == "--run"s);
|
||||
|
||||
if (!argv[2])
|
||||
Fatal(ctx) << "-run: argument missing";
|
||||
|
||||
// Get the mold-wrapper.so path
|
||||
std::string self = std::filesystem::read_symlink("/proc/self/exe");
|
||||
std::string self = get_self_path();
|
||||
std::string dso_path = find_dso(ctx, self);
|
||||
|
||||
// Set environment variables
|
||||
@@ -110,9 +113,10 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
Fatal(ctx) << "mold -run failed: " << argv[2] << ": " << errno_string();
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void process_run_subcommand(Context<E> &, int, char **)
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
template void process_run_subcommand(Context<E> &, int, char **);
|
||||
|
||||
} // namespace mold::elf
|
||||
|
||||
#endif
|
||||
+200
-85
@@ -1,15 +1,24 @@
|
||||
// RISC instructions are usually up to 4 bytes long, so the immediates
|
||||
// of their branch instructions are naturally smaller than 32 bits.
|
||||
// This is contrary to x86-64 on which branch instructions take 4
|
||||
// bytes immediates and can jump to anywhere within PC ± 2 GiB.
|
||||
// RISC instructions are usually up to 4 bytes long, so the immediates of
|
||||
// their branch instructions are naturally smaller than 32 bits. This is
|
||||
// contrary to x86-64 on which branch instructions take 4 bytes immediates
|
||||
// and can jump to anywhere within PC ± 2 GiB.
|
||||
//
|
||||
// In fact, ARM32's branch instructions can jump only within ±16 MiB
|
||||
// and ARM64's ±128 MiB. If a branch target is further than that, we
|
||||
// need to let it branch to a linker-synthesized code sequence that
|
||||
// In fact, ARM32's branch instructions can jump only within ±16 MiB and
|
||||
// ARM64's ±128 MiB, for example. If a branch target is further than that,
|
||||
// we need to let it branch to a linker-synthesized code sequence that
|
||||
// construct a full 32 bit address in a register and jump there. That
|
||||
// linker-synthesized code is called "thunk".
|
||||
//
|
||||
// The function in this file creates thunks.
|
||||
//
|
||||
// Note that although thunks play an important role in an executable, they
|
||||
// don't take up too much space in it. For example, among the clang-16's
|
||||
// text segment whose size is ~300 MiB on ARM64, thunks in total occupy
|
||||
// only ~30 KiB or 0.01%. Of course the number depends on an ISA; we would
|
||||
// need more thunks on ARM32 whose branch range is shorter than ARM64.
|
||||
// That said, the total size of thunks still isn't that much. Therefore,
|
||||
// we don't need to try too hard to reduce thunk size to the absolute
|
||||
// minimum.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -18,6 +27,111 @@
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Branch reach in bytes.
|
||||
//
|
||||
// ARM64's branch has 26 bits immediate, and it's scaled by 4 because all
|
||||
// instructions are 4 bytes aligned, so it's effectively 28 bits long.
|
||||
// That means the range is [-2^27, 2^27).
|
||||
//
|
||||
// ARM32's Thumb branch has 24 bits immediate, and the instructions are
|
||||
// aligned to 2, so it's effectively 25 bits. ARM32's non-Thumb branches
|
||||
// have twice longer range than its Thumb counterparts, but we
|
||||
// conservatively use the Thumb's limitation.
|
||||
//
|
||||
// PPC's branch has 24 bits immediate, and the instructions are aligned
|
||||
// to 4, therefore 26.
|
||||
//
|
||||
// Here is the summary of branch instructions reaches:
|
||||
//
|
||||
// ARM64: PC ± 128 MiB
|
||||
// ARM32: PC ± 16 MiB
|
||||
// PPC: PC ± 32 MiB
|
||||
template <typename E>
|
||||
static consteval i64 max_distance() {
|
||||
if constexpr (is_arm64<E>) {
|
||||
return 1LL << 27;
|
||||
} else if constexpr (is_arm32<E>) {
|
||||
return 1LL << 24;
|
||||
} else if constexpr (is_ppc<E>) {
|
||||
return 1LL << 25;
|
||||
} else {
|
||||
static_assert(is_hppa<E>);
|
||||
return 1LL << 18;
|
||||
}
|
||||
}
|
||||
|
||||
// We create thunks for each 12.8/1.6/3.2 MiB code block for
|
||||
// ARM64/ARM32/PPC, respectively.
|
||||
template <typename E>
|
||||
static constexpr i64 batch_size = max_distance<E>() / 10;
|
||||
|
||||
// We assume that a single thunk group is smaller than 100 KiB.
|
||||
static constexpr i64 max_thunk_size = 102400;
|
||||
|
||||
// Returns true if a given relocation is of type used for function calls.
|
||||
template <typename E>
|
||||
static bool needs_thunk_rel(const ElfRel<E> &r) {
|
||||
u32 ty = r.r_type;
|
||||
|
||||
if constexpr (is_arm64<E>) {
|
||||
return ty == R_AARCH64_JUMP26 || ty == R_AARCH64_CALL26;
|
||||
} else if constexpr (is_arm32<E>) {
|
||||
return ty == R_ARM_JUMP24 || ty == R_ARM_THM_JUMP24 ||
|
||||
ty == R_ARM_CALL || ty == R_ARM_THM_CALL;
|
||||
} else if constexpr (is_ppc32<E>) {
|
||||
return ty == R_PPC_REL24 || ty == R_PPC_PLTREL24 || ty == R_PPC_LOCAL24PC;
|
||||
} else if constexpr (is_ppc64<E>) {
|
||||
return ty == R_PPC64_REL24;
|
||||
} else {
|
||||
static_assert(is_hppa<E>);
|
||||
return ty == R_PARISC_PCREL17F || ty == R_PARISC_PCREL32;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
|
||||
Symbol<E> &sym, const ElfRel<E> &rel) {
|
||||
// We create thunks with a pessimistic assumption that all
|
||||
// out-of-section relocations would be out-of-range.
|
||||
InputSection<E> *isec2 = sym.get_input_section();
|
||||
if (!isec2 || isec.output_section != isec2->output_section)
|
||||
return false;
|
||||
|
||||
// Even if the target is the same section, we branch to its PLT
|
||||
// if it has one. So a symbol with a PLT is also considered an
|
||||
// out-of-section reference.
|
||||
if (sym.has_plt(ctx))
|
||||
return false;
|
||||
|
||||
// If the target section is in the same output section but
|
||||
// hasn't got any address yet, that's unreacahble.
|
||||
if (isec2->offset == -1)
|
||||
return false;
|
||||
|
||||
// Thumb and ARM B instructions cannot be converted to BX, so we
|
||||
// always have to make them jump to a thunk to switch processor mode
|
||||
// even if their destinations are within their ranges.
|
||||
if constexpr (is_arm32<E>) {
|
||||
bool is_thumb = sym.get_addr(ctx) & 1;
|
||||
if ((rel.r_type == R_ARM_THM_JUMP24 && !is_thumb) ||
|
||||
(rel.r_type == R_ARM_JUMP24 && is_thumb))
|
||||
return false;
|
||||
}
|
||||
|
||||
// (TODO)
|
||||
if constexpr (is_hppa<E>)
|
||||
if (sym.has_opd(ctx))
|
||||
return false;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol
|
||||
// and check if they are within reach.
|
||||
i64 S = sym.get_addr(ctx, NO_OPD);
|
||||
i64 A = get_addend(isec, rel);
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
i64 val = S + A - P;
|
||||
return -max_distance<E>() <= val && val < max_distance<E>();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
for (Symbol<E> *sym : thunk.symbols) {
|
||||
@@ -27,19 +141,61 @@ static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
}
|
||||
}
|
||||
|
||||
// Scan relocations to collect symbols that need thunks.
|
||||
template <typename E>
|
||||
static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
|
||||
RangeExtensionThunk<E> &thunk) {
|
||||
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
std::vector<RangeExtensionRef> &range_extn = isec.extra.range_extn;
|
||||
range_extn.resize(rels.size());
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (!needs_thunk_rel(rel))
|
||||
continue;
|
||||
|
||||
// Skip if the symbol is undefined. apply_reloc() will report an error.
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.file)
|
||||
continue;
|
||||
|
||||
// Skip if the destination is within reach.
|
||||
if (is_reachable(ctx, isec, sym, rel))
|
||||
continue;
|
||||
|
||||
// This relocation needs a thunk. If the symbol is already in a
|
||||
// previous thunk, reuse it.
|
||||
if (sym.extra.thunk_idx != -1) {
|
||||
range_extn[i].thunk_idx = sym.extra.thunk_idx;
|
||||
range_extn[i].sym_idx = sym.extra.thunk_sym_idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the symbol to the current thunk if it's not
|
||||
// added already.
|
||||
range_extn[i].thunk_idx = thunk.thunk_idx;
|
||||
range_extn[i].sym_idx = -1;
|
||||
|
||||
if (sym.flags.exchange(-1) == 0) {
|
||||
std::scoped_lock lock(thunk.mu);
|
||||
thunk.symbols.push_back(&sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
std::span<InputSection<E> *> members = osec.members;
|
||||
if (members.empty())
|
||||
std::span<InputSection<E> *> m = osec.members;
|
||||
if (m.empty())
|
||||
return;
|
||||
|
||||
members[0]->offset = 0;
|
||||
m[0]->offset = 0;
|
||||
|
||||
// Initialize input sections with a dummy offset so that we can
|
||||
// distinguish sections that have got an address with the one who
|
||||
// haven't.
|
||||
tbb::parallel_for((i64)1, (i64)members.size(), [&](i64 i) {
|
||||
members[i]->offset = -1;
|
||||
tbb::parallel_for((i64)1, (i64)m.size(), [&](i64 i) {
|
||||
m[i]->offset = -1;
|
||||
});
|
||||
|
||||
// We create thunks from the beginning of the section to the end.
|
||||
@@ -51,28 +207,30 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
i64 d = 0;
|
||||
i64 offset = 0;
|
||||
|
||||
while (b < members.size()) {
|
||||
// Move D foward as far as we can jump from B to D.
|
||||
while (d < members.size() &&
|
||||
offset - members[b]->offset < E::thunk_max_distance) {
|
||||
offset = align_to(offset, 1 << members[d]->p2align);
|
||||
members[d]->offset = offset;
|
||||
offset += members[d]->sh_size;
|
||||
while (b < m.size()) {
|
||||
// Move D foward as far as we can jump from B to anywhere in a thunk after D.
|
||||
while (d < m.size() &&
|
||||
align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size + max_thunk_size <
|
||||
m[b]->offset + max_distance<E>()) {
|
||||
offset = align_to(offset, 1 << m[d]->p2align);
|
||||
m[d]->offset = offset;
|
||||
offset += m[d]->sh_size;
|
||||
d++;
|
||||
}
|
||||
|
||||
// Move C forward so that C is apart from B by GROUP_SIZE.
|
||||
while (c < members.size() &&
|
||||
members[c]->offset - members[b]->offset < E::thunk_group_size)
|
||||
// Move C forward so that C is apart from B by BATCH_SIZE. We want
|
||||
// to make sure that there's at least one section between B and C
|
||||
// to ensure progress.
|
||||
c = b + 1;
|
||||
while (c < m.size() &&
|
||||
m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size<E>)
|
||||
c++;
|
||||
|
||||
// Move A forward so that A is reachable from C.
|
||||
if (c > 0) {
|
||||
i64 c_end = members[c - 1]->offset + members[c - 1]->sh_size;
|
||||
while (a < osec.thunks.size() &&
|
||||
osec.thunks[a]->offset < c_end - E::thunk_max_distance)
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
}
|
||||
i64 c_offset = (c == m.size()) ? offset : m[c]->offset;
|
||||
while (a < osec.thunks.size() &&
|
||||
osec.thunks[a]->offset + max_distance<E>() < c_offset)
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
|
||||
// Create a thunk for input sections between B and C and place it at D.
|
||||
osec.thunks.emplace_back(new RangeExtensionThunk<E>{osec});
|
||||
@@ -82,49 +240,14 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
offset = align_to(offset, thunk.alignment);
|
||||
thunk.offset = offset;
|
||||
|
||||
std::mutex mu;
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need thunks.
|
||||
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
|
||||
[&](InputSection<E> *isec) {
|
||||
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::vector<RangeExtensionRef> &range_extn = isec->extra.range_extn;
|
||||
range_extn.resize(rels.size());
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (!needs_thunk_rel(rel))
|
||||
continue;
|
||||
|
||||
// Skip if the symbol is undefined. apply_reloc() will report an error.
|
||||
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
|
||||
if (!sym.file)
|
||||
continue;
|
||||
|
||||
// Skip if the destination is within reach.
|
||||
if (is_reachable(ctx, sym, *isec, rel))
|
||||
continue;
|
||||
|
||||
// If the symbol is already in another thunk, reuse it.
|
||||
if (sym.extra.thunk_idx != -1) {
|
||||
range_extn[i].thunk_idx = sym.extra.thunk_idx;
|
||||
range_extn[i].sym_idx = sym.extra.thunk_sym_idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the symbol to this thunk if it's not added already.
|
||||
range_extn[i].thunk_idx = thunk.thunk_idx;
|
||||
range_extn[i].sym_idx = -1;
|
||||
|
||||
if (sym.flags.exchange(-1) == 0) {
|
||||
std::scoped_lock lock(mu);
|
||||
thunk.symbols.push_back(&sym);
|
||||
}
|
||||
}
|
||||
tbb::parallel_for_each(m.begin() + b, m.begin() + c, [&](InputSection<E> *isec) {
|
||||
scan_rels(ctx, *isec, thunk);
|
||||
});
|
||||
|
||||
// Now that we know the number of symbols in the thunk, we can compute
|
||||
// its size.
|
||||
assert(thunk.size() < max_thunk_size);
|
||||
offset += thunk.size();
|
||||
|
||||
// Sort symbols added to the thunk to make the output deterministic.
|
||||
@@ -140,20 +263,17 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
}
|
||||
|
||||
// Scan relocations again to fix symbol offsets in the last thunk.
|
||||
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
|
||||
[&](InputSection<E> *isec) {
|
||||
tbb::parallel_for_each(m.begin() + b, m.begin() + c, [&](InputSection<E> *isec) {
|
||||
std::span<Symbol<E> *> syms = isec->file.symbols;
|
||||
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::vector<RangeExtensionRef> &range_extn = isec->extra.range_extn;
|
||||
std::span<RangeExtensionRef> range_extn = isec->extra.range_extn;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
if (range_extn[i].thunk_idx == thunk.thunk_idx) {
|
||||
Symbol<E> &sym = *isec->file.symbols[rels[i].r_sym];
|
||||
range_extn[i].sym_idx = sym.extra.thunk_sym_idx;
|
||||
}
|
||||
}
|
||||
for (i64 i = 0; i < rels.size(); i++)
|
||||
if (range_extn[i].thunk_idx == thunk.thunk_idx)
|
||||
range_extn[i].sym_idx = syms[rels[i].r_sym]->extra.thunk_sym_idx;
|
||||
});
|
||||
|
||||
// Move B forward to point to the begining of the next group.
|
||||
// Move B forward to point to the begining of the next batch.
|
||||
b = c;
|
||||
}
|
||||
|
||||
@@ -161,16 +281,11 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
|
||||
osec.shdr.sh_size = offset;
|
||||
|
||||
for (InputSection<E> *isec : members)
|
||||
osec.shdr.sh_addralign =
|
||||
std::max<u32>(osec.shdr.sh_addralign, 1 << isec->p2align);
|
||||
}
|
||||
|
||||
template
|
||||
void create_range_extension_thunks(Context<ARM64> &, OutputSection<ARM64> &);
|
||||
|
||||
template
|
||||
void create_range_extension_thunks(Context<ARM32> &, OutputSection<ARM32> &);
|
||||
#if MOLD_ARM32 || MOLD_ARM64 || MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2 || MOLD_HPPA32
|
||||
using E = MOLD_TARGET;
|
||||
template void create_range_extension_thunks(Context<E> &, OutputSection<E> &);
|
||||
#endif
|
||||
|
||||
} // namespace mold::elf
|
||||
+1
-1
@@ -20,7 +20,7 @@ std::string path_clean(std::string_view path) {
|
||||
|
||||
std::filesystem::path to_abs_path(std::filesystem::path path) {
|
||||
if (path.is_absolute())
|
||||
return path;
|
||||
return path.lexically_normal();
|
||||
return (std::filesystem::current_path() / path).lexically_normal();
|
||||
}
|
||||
|
||||
|
||||
+53
-18
@@ -31,14 +31,32 @@ bool is_text_file(MappedFile<C> *mf) {
|
||||
}
|
||||
|
||||
template <typename E, typename C>
|
||||
inline bool is_gcc_lto_obj(MappedFile<C> *mf) {
|
||||
inline bool is_gcc_lto_obj(MappedFile<C> *mf, bool opt_plugin) {
|
||||
using namespace mold::elf;
|
||||
|
||||
const char *data = mf->get_contents().data();
|
||||
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)data;
|
||||
ElfShdr<E> *sh_begin = (ElfShdr<E> *)(data + ehdr.e_shoff);
|
||||
std::span<ElfShdr<E>> shdrs{(ElfShdr<E> *)(data + ehdr.e_shoff), ehdr.e_shnum};
|
||||
|
||||
// e_shstrndx is a 16-bit field. If .shstrtab's section index is
|
||||
// too large, the actual number is stored to sh_link field.
|
||||
i64 shstrtab_idx = (ehdr.e_shstrndx == SHN_XINDEX)
|
||||
? sh_begin->sh_link : ehdr.e_shstrndx;
|
||||
|
||||
for (ElfShdr<E> &sec : shdrs) {
|
||||
// GCC LTO object contains only sections symbols followed by a common
|
||||
// symbol whose name is `__gnu_lto_slim` (or `__gnu_lto_v1` for older
|
||||
// GCC releases).
|
||||
//
|
||||
// However, FAT LTO objects don't have any of the above mentioned symbols
|
||||
// and can identify LTO by `.gnu.lto_.symtab.` section, similarly
|
||||
// to what lto-plugin does. However, if LTO linker plug-in is not available,
|
||||
// use the emitted assembly instead.
|
||||
std::string_view name = data + shdrs[shstrtab_idx].sh_offset + sec.sh_name;
|
||||
if (opt_plugin && name.starts_with (".gnu.lto_.symtab."))
|
||||
return true;
|
||||
|
||||
if (sec.sh_type != SHT_SYMTAB)
|
||||
continue;
|
||||
|
||||
@@ -49,8 +67,6 @@ inline bool is_gcc_lto_obj(MappedFile<C> *mf) {
|
||||
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++;
|
||||
@@ -68,30 +84,49 @@ inline bool is_gcc_lto_obj(MappedFile<C> *mf) {
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
FileType get_file_type(MappedFile<C> *mf) {
|
||||
FileType get_file_type(MappedFile<C> *mf, bool opt_plugin) {
|
||||
using namespace elf;
|
||||
|
||||
std::string_view data = mf->get_contents();
|
||||
|
||||
if (data.empty())
|
||||
return FileType::EMPTY;
|
||||
|
||||
if (data.starts_with("\177ELF")) {
|
||||
switch (*(ul16 *)(data.data() + 16)) {
|
||||
case 1: {
|
||||
// ET_REL
|
||||
elf::Elf32Ehdr &ehdr = *(elf::Elf32Ehdr *)data.data();
|
||||
u8 byte_order = ((ElfEhdr<I386> *)data.data())->e_ident[EI_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;
|
||||
if (byte_order == ELFDATA2LSB) {
|
||||
auto &ehdr = *(ElfEhdr<I386> *)data.data();
|
||||
|
||||
if (ehdr.e_type == ET_REL) {
|
||||
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
|
||||
if (is_gcc_lto_obj<I386>(mf, opt_plugin))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
} else {
|
||||
if (is_gcc_lto_obj<X86_64>(mf, opt_plugin))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
}
|
||||
return FileType::ELF_OBJ;
|
||||
}
|
||||
|
||||
return FileType::ELF_OBJ;
|
||||
}
|
||||
case 3: // ET_DYN
|
||||
return FileType::ELF_DSO;
|
||||
if (ehdr.e_type == ET_DYN)
|
||||
return FileType::ELF_DSO;
|
||||
} else {
|
||||
auto &ehdr = *(ElfEhdr<M68K> *)data.data();
|
||||
|
||||
if (ehdr.e_type == ET_REL) {
|
||||
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
|
||||
if (is_gcc_lto_obj<M68K>(mf, opt_plugin))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
} else {
|
||||
if (is_gcc_lto_obj<SPARC64>(mf, opt_plugin))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
}
|
||||
return FileType::ELF_OBJ;
|
||||
}
|
||||
|
||||
if (ehdr.e_type == ET_DYN)
|
||||
return FileType::ELF_DSO;
|
||||
}
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
+13
-7
@@ -1,24 +1,28 @@
|
||||
#!/bin/bash -x
|
||||
#!/bin/bash
|
||||
set -e
|
||||
source /etc/os-release
|
||||
|
||||
set -x
|
||||
|
||||
# The first line for each distro installs a build dependency.
|
||||
# The second line installs extra packages for `make test`.
|
||||
# The second line installs extra packages for unittests.
|
||||
#
|
||||
# Feel free to send me a PR if you find a missing OS.
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
case "$ID-$VERSION_ID" in
|
||||
ubuntu-20.* | pop-20.*)
|
||||
apt-get update
|
||||
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++ g++-10
|
||||
apt-get install -y file bsdmainutils
|
||||
apt-get install -y file
|
||||
;;
|
||||
ubuntu-22.* | pop-22.* | debian-11 | raspbian-*)
|
||||
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
|
||||
apt-get update
|
||||
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++
|
||||
apt-get install -y file bsdmainutils
|
||||
apt-get install -y file
|
||||
;;
|
||||
fedora-*)
|
||||
dnf install -y gcc-g++ cmake openssl-devel zlib-devel
|
||||
dnf install -y glibc-static file libstdc++-static diffutils
|
||||
dnf install -y glibc-static file libstdc++-static diffutils util-linux
|
||||
;;
|
||||
opensuse-leap-*)
|
||||
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++ gcc11-c++
|
||||
@@ -29,9 +33,11 @@ opensuse-tumbleweed-*)
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
gentoo-*)
|
||||
emerge-webrsync
|
||||
emerge dev-util/cmake sys-libs/zlib
|
||||
;;
|
||||
arch-*)
|
||||
pacman -Sy
|
||||
pacman -S --needed --noconfirm base-devel zlib openssl cmake util-linux
|
||||
;;
|
||||
*)
|
||||
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
// This file defines integral types for file input/output. We need to use
|
||||
// these types instead of the plain integers (such as uint32_t or int32_t)
|
||||
// when reading from/writing to an mmap'ed file area for the following
|
||||
// reasons:
|
||||
//
|
||||
// 1. mold is always a cross linker and should not depend on what host it
|
||||
// is running on. Users should be able to run mold on a big-endian
|
||||
// SPARC machine to create a little-endian RV64 binary, for example.
|
||||
//
|
||||
// 2. Even though data members in all ELF data strucutres are naturally
|
||||
// aligned, they are not guaranteed to be aligned on memory. Because
|
||||
// archive file (.a file) aligns each member only to a 2 byte boundary,
|
||||
// anything larger than 2 bytes may be unaligned in an mmap'ed memory.
|
||||
// Unaligned access is an undefined behavior in C/C++, so we shouldn't
|
||||
// cast an arbitrary pointer to a uint32_t, for example, to read a
|
||||
// 32-bits value.
|
||||
//
|
||||
// The data types defined in this file don't depend on host byte order and
|
||||
// don't do unaligned access.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
|
||||
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
# define __LITTLE_ENDIAN__ 1
|
||||
# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
# define __BIG_ENDIAN__ 1
|
||||
# else
|
||||
# error "unknown host byte order"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
|
||||
template <typename T>
|
||||
static inline T bswap(T val) {
|
||||
switch (sizeof(T)) {
|
||||
case 2: return __builtin_bswap16(val);
|
||||
case 4: return __builtin_bswap32(val);
|
||||
case 8: return __builtin_bswap64(val);
|
||||
default: __builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, int SIZE = sizeof(T)>
|
||||
class LittleEndian {
|
||||
public:
|
||||
LittleEndian() = default;
|
||||
LittleEndian(T x) { *this = x; }
|
||||
|
||||
operator T() const {
|
||||
if constexpr (sizeof(T) == SIZE) {
|
||||
T x;
|
||||
memcpy(&x, val, sizeof(T));
|
||||
if constexpr (std::endian::native == std::endian::big)
|
||||
x = bswap(x);
|
||||
return x;
|
||||
} else {
|
||||
static_assert(SIZE == 3);
|
||||
return (val[2] << 16) | (val[1] << 8) | val[0];
|
||||
}
|
||||
}
|
||||
|
||||
LittleEndian &operator=(T x) {
|
||||
if constexpr (sizeof(T) == SIZE) {
|
||||
if constexpr (std::endian::native == std::endian::big)
|
||||
x = bswap(x);
|
||||
memcpy(val, &x, sizeof(T));
|
||||
} else {
|
||||
static_assert(SIZE == 3);
|
||||
val[2] = x >> 16;
|
||||
val[1] = x >> 8;
|
||||
val[0] = x;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
LittleEndian &operator++() {
|
||||
return *this = *this + 1;
|
||||
}
|
||||
|
||||
LittleEndian operator++(int) {
|
||||
T ret = *this;
|
||||
*this = *this + 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LittleEndian &operator--() {
|
||||
return *this = *this - 1;
|
||||
}
|
||||
|
||||
LittleEndian operator--(int) {
|
||||
T ret = *this;
|
||||
*this = *this - 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LittleEndian &operator+=(T x) {
|
||||
return *this = *this + x;
|
||||
}
|
||||
|
||||
LittleEndian &operator-=(T x) {
|
||||
return *this = *this - x;
|
||||
}
|
||||
|
||||
LittleEndian &operator&=(T x) {
|
||||
return *this = *this & x;
|
||||
}
|
||||
|
||||
LittleEndian &operator|=(T x) {
|
||||
return *this = *this | x;
|
||||
}
|
||||
|
||||
private:
|
||||
u8 val[SIZE];
|
||||
};
|
||||
|
||||
using il16 = LittleEndian<i16>;
|
||||
using il32 = LittleEndian<i32>;
|
||||
using il64 = LittleEndian<i64>;
|
||||
using ul16 = LittleEndian<u16>;
|
||||
using ul24 = LittleEndian<u32, 3>;
|
||||
using ul32 = LittleEndian<u32>;
|
||||
using ul64 = LittleEndian<u64>;
|
||||
|
||||
template <typename T, int SIZE = sizeof(T)>
|
||||
class BigEndian {
|
||||
public:
|
||||
BigEndian() = default;
|
||||
BigEndian(T x) { *this = x; }
|
||||
|
||||
operator T() const {
|
||||
if constexpr (sizeof(T) == SIZE) {
|
||||
T x;
|
||||
memcpy(&x, val, sizeof(T));
|
||||
if constexpr (std::endian::native == std::endian::little)
|
||||
x = bswap(x);
|
||||
return x;
|
||||
} else {
|
||||
static_assert(SIZE == 3);
|
||||
return (val[0] << 16) | (val[1] << 8) | val[2];
|
||||
}
|
||||
}
|
||||
|
||||
BigEndian &operator=(T x) {
|
||||
if constexpr (sizeof(T) == SIZE) {
|
||||
if constexpr (std::endian::native == std::endian::little)
|
||||
x = bswap(x);
|
||||
memcpy(val, &x, sizeof(T));
|
||||
} else {
|
||||
static_assert(SIZE == 3);
|
||||
val[0] = x >> 16;
|
||||
val[1] = x >> 8;
|
||||
val[2] = x;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigEndian &operator++() {
|
||||
return *this = *this + 1;
|
||||
}
|
||||
|
||||
BigEndian operator++(int) {
|
||||
T ret = *this;
|
||||
*this = *this + 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator--() {
|
||||
return *this = *this - 1;
|
||||
}
|
||||
|
||||
BigEndian operator--(int) {
|
||||
T ret = *this;
|
||||
*this = *this - 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator+=(T x) {
|
||||
return *this = *this + x;
|
||||
}
|
||||
|
||||
BigEndian &operator-=(T x) {
|
||||
return *this = *this - x;
|
||||
}
|
||||
|
||||
BigEndian &operator&=(T x) {
|
||||
return *this = *this & x;
|
||||
}
|
||||
|
||||
BigEndian &operator|=(T x) {
|
||||
return *this = *this | x;
|
||||
}
|
||||
|
||||
private:
|
||||
u8 val[SIZE];
|
||||
};
|
||||
|
||||
using ib16 = BigEndian<i16>;
|
||||
using ib32 = BigEndian<i32>;
|
||||
using ib64 = BigEndian<i64>;
|
||||
using ub16 = BigEndian<u16>;
|
||||
using ub24 = BigEndian<u32, 3>;
|
||||
using ub32 = BigEndian<u32>;
|
||||
using ub64 = BigEndian<u64>;
|
||||
|
||||
} // namespace mold
|
||||
-181
@@ -1,181 +0,0 @@
|
||||
// This file defines integral types for file input/output. You should
|
||||
// use these types instead of the plain integers (such as u32 or i32)
|
||||
// when reading from/writing to an mmap'ed file area.
|
||||
//
|
||||
// Here is why you need these types. In C/C++, all data accesses must
|
||||
// be aligned. That is, if you read an N byte value from memory, the
|
||||
// address of that value must be a multiple of N. For example, reading
|
||||
// an u32 value from address 16 is legal, while reading from 18 is not
|
||||
// (since 18 is not a multiple of 4.) An unaligned access yields an
|
||||
// undefined behavior.
|
||||
//
|
||||
// All data members of the ELF data structures are naturally aligned,
|
||||
// so it may look like we don't need the integral types defined in
|
||||
// this file to access them. But there's a catch; if an object file is
|
||||
// in an archive file (a .a file), the beginning of the file is
|
||||
// guaranteed to be aligned only to a 2 bytes boundary, because ar
|
||||
// aligns each member only to a 2 byte boundary. Therefore, any data
|
||||
// members larger than 2 bytes may be unaligned in an mmap'ed memory,
|
||||
// and thus you always need to use the integral types in this file to
|
||||
// access it.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef __BIG_ENDIAN__
|
||||
#error "mold does not support big-endian hosts"
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename T, size_t SIZE = sizeof(T)>
|
||||
class LittleEndian {
|
||||
public:
|
||||
LittleEndian() { *this = 0; }
|
||||
LittleEndian(T x) { *this = x; }
|
||||
|
||||
operator T() const {
|
||||
T x = 0;
|
||||
memcpy(&x, val, SIZE);
|
||||
return x;
|
||||
}
|
||||
|
||||
LittleEndian &operator=(T x) {
|
||||
memcpy(&val, &x, SIZE);
|
||||
return *this;
|
||||
}
|
||||
|
||||
LittleEndian &operator++() {
|
||||
return *this = *this + 1;
|
||||
}
|
||||
|
||||
LittleEndian operator++(int) {
|
||||
T ret = *this;
|
||||
*this = *this + 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LittleEndian &operator--() {
|
||||
return *this = *this - 1;
|
||||
}
|
||||
|
||||
LittleEndian operator--(int) {
|
||||
T ret = *this;
|
||||
*this = *this - 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LittleEndian &operator+=(T x) {
|
||||
return *this = *this + x;
|
||||
}
|
||||
|
||||
LittleEndian &operator-=(T x) {
|
||||
return *this = *this - x;
|
||||
}
|
||||
|
||||
LittleEndian &operator&=(T x) {
|
||||
return *this = *this & x;
|
||||
}
|
||||
|
||||
LittleEndian &operator|=(T x) {
|
||||
return *this = *this | x;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t val[SIZE];
|
||||
};
|
||||
|
||||
using il16 = LittleEndian<int16_t>;
|
||||
using il32 = LittleEndian<int32_t>;
|
||||
using il64 = LittleEndian<int64_t>;
|
||||
using ul16 = LittleEndian<uint16_t>;
|
||||
using ul24 = LittleEndian<uint32_t, 3>;
|
||||
using ul32 = LittleEndian<uint32_t>;
|
||||
using ul64 = LittleEndian<uint64_t>;
|
||||
|
||||
template <typename T>
|
||||
class BigEndian {
|
||||
public:
|
||||
BigEndian() = delete;
|
||||
|
||||
operator T() const {
|
||||
T x;
|
||||
memcpy(&x, val, sizeof(T));
|
||||
return bswap(x);
|
||||
}
|
||||
|
||||
BigEndian &operator=(T x) {
|
||||
x = bswap(x);
|
||||
memcpy(&val, &x, sizeof(T));
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigEndian &operator++() {
|
||||
return *this = *this + 1;
|
||||
}
|
||||
|
||||
BigEndian operator++(int) {
|
||||
T ret = *this;
|
||||
*this = *this + 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator--() {
|
||||
return *this = *this - 1;
|
||||
}
|
||||
|
||||
BigEndian operator--(int) {
|
||||
T ret = *this;
|
||||
*this = *this - 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator+=(T x) {
|
||||
return *this = *this + x;
|
||||
}
|
||||
|
||||
BigEndian &operator&=(T x) {
|
||||
return *this = *this & x;
|
||||
}
|
||||
|
||||
BigEndian &operator|=(T x) {
|
||||
return *this = *this | x;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t val[sizeof(T)];
|
||||
|
||||
static T bswap(T x) {
|
||||
// Compiler is usually smart enough to compile the following code into
|
||||
// a single bswap instruction. See https://godbolt.org/z/7nvaM7qab
|
||||
if constexpr (sizeof(T) == 2) {
|
||||
return ((x & 0xff00) >> 8) |
|
||||
((x & 0x00ff) << 8);
|
||||
} else if constexpr (sizeof(T) == 4) {
|
||||
return ((x & 0xff000000) >> 24) |
|
||||
((x & 0x00ff0000) >> 8) |
|
||||
((x & 0x0000ff00) << 8) |
|
||||
((x & 0x000000ff) << 24);
|
||||
} else {
|
||||
return ((x & 0xff000000'00000000) >> 56) |
|
||||
((x & 0x00ff0000'00000000) >> 40) |
|
||||
((x & 0x0000ff00'00000000) >> 24) |
|
||||
((x & 0x000000ff'00000000) >> 8) |
|
||||
((x & 0x00000000'ff000000) << 8) |
|
||||
((x & 0x00000000'00ff0000) << 24) |
|
||||
((x & 0x00000000'0000ff00) << 40) |
|
||||
((x & 0x00000000'000000ff) << 56);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using ib16 = BigEndian<int16_t>;
|
||||
using ib32 = BigEndian<int32_t>;
|
||||
using ib64 = BigEndian<int64_t>;
|
||||
using ub16 = BigEndian<uint16_t>;
|
||||
using ub32 = BigEndian<uint32_t>;
|
||||
using ub64 = BigEndian<uint64_t>;
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,709 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
using E = ARM64;
|
||||
|
||||
static u64 page(u64 val) {
|
||||
return val & 0xffff'ffff'ffff'f000;
|
||||
}
|
||||
|
||||
static u64 page_offset(u64 hi, u64 lo) {
|
||||
u64 val = page(hi) - page(lo);
|
||||
return (bits(val, 13, 12) << 29) | (bits(val, 32, 14) << 5);
|
||||
}
|
||||
|
||||
template <>
|
||||
void StubsSection<E>::copy_buf(Context<E> &ctx) {
|
||||
ul32 *buf = (ul32 *)(ctx.buf + this->hdr.offset);
|
||||
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
static const u32 insn[] = {
|
||||
0x90000010, // adrp x16, $ptr@PAGE
|
||||
0xf9400210, // ldr x16, [x16, $ptr@PAGEOFF]
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::stub_size);
|
||||
|
||||
u64 la_addr = ctx.lazy_symbol_ptr.hdr.addr + word_size * i;
|
||||
u64 this_addr = this->hdr.addr + E::stub_size * i;
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
buf[0] |= page_offset(la_addr, this_addr);
|
||||
buf[1] |= bits(la_addr, 11, 3) << 10;
|
||||
buf += 3;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void StubHelperSection<E>::copy_buf(Context<E> &ctx) {
|
||||
ul32 *start = (ul32 *)(ctx.buf + this->hdr.offset);
|
||||
ul32 *buf = start;
|
||||
|
||||
static const u32 insn0[] = {
|
||||
0x90000011, // adrp x17, $__dyld_private@PAGE
|
||||
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) == E::stub_helper_hdr_size);
|
||||
memcpy(buf, insn0, sizeof(insn0));
|
||||
|
||||
u64 dyld_private = get_symbol(ctx, "__dyld_private")->get_addr(ctx);
|
||||
buf[0] |= page_offset(dyld_private, this->hdr.addr);
|
||||
buf[1] |= bits(dyld_private, 11, 0) << 10;
|
||||
|
||||
u64 stub_binder = get_symbol(ctx, "dyld_stub_binder")->get_got_addr(ctx);
|
||||
buf[3] |= page_offset(stub_binder, 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) == E::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;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
std::vector<Relocation<E>>
|
||||
read_relocations(Context<E> &ctx, ObjectFile<E> &file, const MachSection &hdr) {
|
||||
std::vector<Relocation<E>> vec;
|
||||
vec.reserve(hdr.nreloc);
|
||||
|
||||
MachRel *rels = (MachRel *)(file.mf->data + hdr.reloff);
|
||||
|
||||
for (i64 i = 0; i < hdr.nreloc; i++) {
|
||||
i64 addend = 0;
|
||||
|
||||
switch (rels[i].type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_SUBTRACTOR:
|
||||
switch (rels[i].p2size) {
|
||||
case 2:
|
||||
addend = *(il32 *)((u8 *)file.mf->data + hdr.offset + rels[i].offset);
|
||||
break;
|
||||
case 3:
|
||||
addend = *(il64 *)((u8 *)file.mf->data + hdr.offset + rels[i].offset);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
break;
|
||||
case ARM64_RELOC_ADDEND:
|
||||
addend = rels[i++].idx;
|
||||
break;
|
||||
}
|
||||
|
||||
MachRel &r = rels[i];
|
||||
vec.push_back({r.offset, (u8)r.type, (u8)r.p2size});
|
||||
|
||||
Relocation<E> &rel = vec.back();
|
||||
|
||||
if (i > 0 && rels[i - 1].type == ARM64_RELOC_SUBTRACTOR)
|
||||
rel.is_subtracted = true;
|
||||
|
||||
if (!rel.is_subtracted && rels[i].type != ARM64_RELOC_SUBTRACTOR)
|
||||
rel.is_pcrel = r.is_pcrel;
|
||||
|
||||
if (r.is_extern) {
|
||||
rel.sym = file.syms[r.idx];
|
||||
rel.addend = addend;
|
||||
continue;
|
||||
}
|
||||
|
||||
u64 addr = r.is_pcrel ? (hdr.addr + r.offset + addend) : addend;
|
||||
Subsection<E> *target = file.find_subsection(ctx, r.idx - 1, addr);
|
||||
if (!target)
|
||||
Fatal(ctx) << file << ": bad relocation: " << r.offset;
|
||||
|
||||
rel.subsec = target;
|
||||
rel.addend = addr - target->input_addr;
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<E>::scan_relocations(Context<E> &ctx) {
|
||||
for (Relocation<E> &r : get_rels()) {
|
||||
Symbol<E> *sym = r.sym;
|
||||
if (!sym)
|
||||
continue;
|
||||
|
||||
if (sym->is_imported && sym->file->is_dylib)
|
||||
((DylibFile<E> *)sym->file)->is_alive = true;
|
||||
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
if (sym->is_imported) {
|
||||
if (r.p2size != 3) {
|
||||
Error(ctx) << this->isec << ": " << r << " relocation at offset 0x"
|
||||
<< std::hex << r.offset << " against symbol `"
|
||||
<< *sym << "' can not be used";
|
||||
}
|
||||
r.needs_dynrel = true;
|
||||
}
|
||||
break;
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
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->is_imported)
|
||||
sym->flags |= NEEDS_STUB;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<E>::apply_reloc(Context<E> &ctx, u8 *buf) {
|
||||
std::span<Relocation<E>> rels = get_rels();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
Relocation<E> &r = rels[i];
|
||||
u8 *loc = buf + r.offset;
|
||||
i64 val = r.addend;
|
||||
u64 pc = get_addr(ctx) + r.offset;
|
||||
|
||||
if (r.sym && !r.sym->file) {
|
||||
Error(ctx) << "undefined symbol: " << isec.file << ": " << *r.sym;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compute a relocated value.
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_BRANCH26:
|
||||
case ARM64_RELOC_PAGE21:
|
||||
case ARM64_RELOC_PAGEOFF12:
|
||||
val += r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
break;
|
||||
case ARM64_RELOC_SUBTRACTOR: {
|
||||
Relocation<E> s = rels[++i];
|
||||
assert(s.type == ARM64_RELOC_UNSIGNED);
|
||||
u64 val1 = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
u64 val2 = s.sym ? s.sym->get_addr(ctx) : s.subsec->get_addr(ctx);
|
||||
val += val2 - val1;
|
||||
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;
|
||||
}
|
||||
|
||||
// An address of a thread-local variable is computed as an offset
|
||||
// to the beginning of the first thread-local section.
|
||||
if (isec.hdr.type == S_THREAD_LOCAL_VARIABLES)
|
||||
val -= ctx.tls_begin;
|
||||
|
||||
// Write a computed value to the output buffer.
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_SUBTRACTOR:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
if (r.is_pcrel)
|
||||
val -= pc;
|
||||
|
||||
if (r.p2size == 2)
|
||||
*(ul32 *)loc = val;
|
||||
else if (r.p2size == 3)
|
||||
*(ul64 *)loc = val;
|
||||
else
|
||||
unreachable();
|
||||
break;
|
||||
case ARM64_RELOC_BRANCH26: {
|
||||
assert(r.is_pcrel);
|
||||
val -= pc;
|
||||
|
||||
i64 lo = -(1 << 27);
|
||||
i64 hi = 1 << 27;
|
||||
|
||||
if (val < lo || hi <= val) {
|
||||
val = isec.osec.thunks[r.thunk_idx]->get_addr(r.thunk_sym_idx) - pc;
|
||||
assert(lo <= val && val < hi);
|
||||
}
|
||||
|
||||
*(ul32 *)loc |= bits(val, 27, 2);
|
||||
break;
|
||||
}
|
||||
case ARM64_RELOC_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21:
|
||||
assert(r.is_pcrel);
|
||||
*(ul32 *)loc |= page_offset(val, pc);
|
||||
break;
|
||||
case ARM64_RELOC_PAGEOFF12:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12: {
|
||||
assert(!r.is_pcrel);
|
||||
u32 insn = *(ul32 *)loc;
|
||||
i64 scale = 0;
|
||||
if ((insn & 0x3b000000) == 0x39000000) {
|
||||
scale = bits(insn, 31, 30);
|
||||
if (scale == 0 && (insn & 0x04800000) == 0x04800000)
|
||||
scale = 4;
|
||||
}
|
||||
*(ul32 *)loc |= bits(val, 11, scale) << 10;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
for (Symbol<E> *sym : thunk.symbols) {
|
||||
sym->thunk_idx = -1;
|
||||
sym->thunk_sym_idx = -1;
|
||||
sym->flags &= (u8)~NEEDS_RANGE_EXTN_THUNK;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
|
||||
Subsection<E> &subsec, Relocation<E> &rel) {
|
||||
// We pessimistically assume that PLT entries are unreacahble.
|
||||
if (sym.stub_idx != -1)
|
||||
return false;
|
||||
|
||||
// We create thunks with a pessimistic assumption that all
|
||||
// out-of-section relocations would be out-of-range.
|
||||
if (!sym.subsec || &sym.subsec->isec.osec != &subsec.isec.osec)
|
||||
return false;
|
||||
|
||||
if (sym.subsec->output_offset == -1)
|
||||
return false;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol
|
||||
// and check if they are within reach.
|
||||
i64 addr = sym.get_addr(ctx);
|
||||
i64 pc = subsec.get_addr(ctx) + rel.offset;
|
||||
i64 val = addr + rel.addend - pc;
|
||||
return -(1 << 27) <= val && val < (1 << 27);
|
||||
}
|
||||
|
||||
// We create a thunk no further than 100 MiB from any section.
|
||||
static constexpr i64 MAX_DISTANCE = 100 * 1024 * 1024;
|
||||
|
||||
// We create a thunk for each 10 MiB input sections.
|
||||
static constexpr i64 GROUP_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
// ARM64's call/jump instructions take 27 bits displacement, so they
|
||||
// can refer only up to ±128 MiB. If a branch target is further than
|
||||
// that, we need to let it branch to a linker-synthesized code
|
||||
// sequence that construct a full 32 bit address in a register and
|
||||
// jump there. That linker-synthesized code is called "thunk".
|
||||
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
std::span<Subsection<E> *> members = osec.members;
|
||||
if (members.empty())
|
||||
return;
|
||||
|
||||
members[0]->output_offset = 0;
|
||||
|
||||
// Initialize input sections with a dummy offset so that we can
|
||||
// distinguish sections that have got an address with the one who
|
||||
// haven't.
|
||||
tbb::parallel_for((i64)1, (i64)members.size(), [&](i64 i) {
|
||||
members[i]->output_offset = -1;
|
||||
});
|
||||
|
||||
// We create thunks from the beginning of the section to the end.
|
||||
// We manage progress using four offsets which increase monotonically.
|
||||
// The locations they point to are always A <= B <= C <= D.
|
||||
i64 a = 0;
|
||||
i64 b = 0;
|
||||
i64 c = 0;
|
||||
i64 d = 0;
|
||||
i64 offset = 0;
|
||||
|
||||
while (b < members.size()) {
|
||||
// Move D foward as far as we can jump from B to D.
|
||||
while (d < members.size() &&
|
||||
offset - members[b]->output_offset < MAX_DISTANCE) {
|
||||
offset = align_to(offset, 1 << members[d]->p2align);
|
||||
members[d]->output_offset = offset;
|
||||
offset += members[d]->input_size;
|
||||
d++;
|
||||
}
|
||||
|
||||
// Move C forward so that C is apart from B by GROUP_SIZE.
|
||||
while (c < members.size() &&
|
||||
members[c]->output_offset - members[b]->output_offset < GROUP_SIZE)
|
||||
c++;
|
||||
|
||||
// Move A forward so that A is reachable from C.
|
||||
if (c > 0) {
|
||||
i64 c_end = members[c - 1]->output_offset + members[c - 1]->input_size;
|
||||
while (a < osec.thunks.size() &&
|
||||
osec.thunks[a]->offset < c_end - MAX_DISTANCE)
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
}
|
||||
|
||||
// Create a thunk for input sections between B and C and place it at D.
|
||||
osec.thunks.emplace_back(new RangeExtensionThunk<E>{osec});
|
||||
|
||||
RangeExtensionThunk<E> &thunk = *osec.thunks.back();
|
||||
thunk.thunk_idx = osec.thunks.size() - 1;
|
||||
thunk.offset = offset;
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need thunks.
|
||||
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
|
||||
[&](Subsection<E> *subsec) {
|
||||
std::span<Relocation<E>> rels = subsec->get_rels();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
Relocation<E> &r = rels[i];
|
||||
if (!r.sym->file || r.type != ARM64_RELOC_BRANCH26)
|
||||
continue;
|
||||
|
||||
// Skip if the destination is within reach.
|
||||
if (is_reachable(ctx, *r.sym, *subsec, r))
|
||||
continue;
|
||||
|
||||
// If the symbol is already in another thunk, reuse it.
|
||||
if (r.sym->thunk_idx != -1) {
|
||||
r.thunk_idx = r.sym->thunk_idx;
|
||||
r.thunk_sym_idx = r.sym->thunk_sym_idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the symbol to this thunk if it's not added already.
|
||||
r.thunk_idx = thunk.thunk_idx;
|
||||
r.thunk_sym_idx = -1;
|
||||
|
||||
if (!(r.sym->flags.fetch_or(NEEDS_RANGE_EXTN_THUNK) &
|
||||
NEEDS_RANGE_EXTN_THUNK)) {
|
||||
std::scoped_lock lock(thunk.mu);
|
||||
thunk.symbols.push_back(r.sym);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Now that we know the number of symbols in the thunk, we can compute
|
||||
// its size.
|
||||
offset += thunk.size();
|
||||
|
||||
// Sort symbols added to the thunk to make the output deterministic.
|
||||
sort(thunk.symbols, [](Symbol<E> *a, Symbol<E> *b) {
|
||||
return std::tuple{a->file->priority, a->value} <
|
||||
std::tuple{b->file->priority, b->value};
|
||||
});
|
||||
|
||||
// Assign offsets within the thunk to the symbols.
|
||||
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
|
||||
sym->thunk_idx = thunk.thunk_idx;
|
||||
sym->thunk_sym_idx = i++;
|
||||
}
|
||||
|
||||
// Scan relocations again to fix symbol offsets in the last thunk.
|
||||
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
|
||||
[&](Subsection<E> *subsec) {
|
||||
for (Relocation<E> &r : subsec->get_rels())
|
||||
if (r.thunk_idx == thunk.thunk_idx)
|
||||
r.thunk_sym_idx = r.sym->thunk_sym_idx;
|
||||
});
|
||||
|
||||
// Move B forward to point to the begining of the next group.
|
||||
b = c;
|
||||
}
|
||||
|
||||
while (a < osec.thunks.size())
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
|
||||
osec.hdr.size = offset;
|
||||
}
|
||||
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.hdr.offset + offset;
|
||||
|
||||
static const u32 data[] = {
|
||||
0x90000010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
|
||||
0x91000210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(ENTRY_SIZE == sizeof(data));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 addr = symbols[i]->get_addr(ctx);
|
||||
u64 pc = output_section.hdr.addr + offset + i * ENTRY_SIZE;
|
||||
|
||||
u8 *loc = buf + i * ENTRY_SIZE;
|
||||
memcpy(loc , data, sizeof(data));
|
||||
*(ul32 *)loc |= page_offset(addr, pc);
|
||||
*(ul32 *)(loc + 4) |= bits(addr, 11, 0) << 10;
|
||||
}
|
||||
}
|
||||
|
||||
#define ASSERT_RANGE(val, start, size) \
|
||||
assert((start) <= (val) && (val) < ((start) + (size)))
|
||||
|
||||
// Rewrite
|
||||
// ldr Xb/Wb, [Xa, #imm]
|
||||
// with
|
||||
// ldr Xb/Wb, [#base_addr + #imm]
|
||||
// if we can.
|
||||
static bool relax_ldr(ul32 *loc, u64 loc_addr, u64 base_addr) {
|
||||
switch (*loc & 0xffc0'0000) {
|
||||
case 0xf940'0000: { // ldr Xb, [Xa, #imm]
|
||||
u64 imm = bits(*loc, 21, 10) * 8;
|
||||
i64 disp = base_addr + imm - loc_addr;
|
||||
if (disp == sign_extend(disp, 20) && (disp & 0b11) == 0) {
|
||||
// ldr Xc, [#base_addr + #imm]
|
||||
*loc = 0x5800'0000 | (bits(disp, 20, 2) << 5) | bits(*loc, 4, 0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case 0xb940'0000: { // ldr Wb, [Xa, #imm]
|
||||
u64 imm = bits(*loc, 21, 10) * 4;
|
||||
i64 disp = base_addr + imm - loc_addr;
|
||||
if (disp == sign_extend(disp, 20) && (disp & 0b11) == 0) {
|
||||
// ldr Wc, [#base_addr + #imm]
|
||||
*loc = 0x1800'0000 | (bits(disp, 20, 2) << 5) | bits(*loc, 4, 0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_adrp(ul32 *loc) {
|
||||
return (*loc & 0x9f00'0000) == 0x9000'0000;
|
||||
}
|
||||
|
||||
static u64 get_adrp_imm(ul32 *loc) {
|
||||
return (bits(*loc, 23, 5) << 14) + (bits(*loc, 30, 29) << 12);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void relax_adrp_ldr_got_ldr(Context<E> &ctx,
|
||||
ul32 *loc1, ul32 *loc2, ul32 *loc3,
|
||||
u64 addr1, u64 addr2, u64 addr3,
|
||||
ObjectFile<E> *file) {
|
||||
// We expect the following instructions for a GOT-indirect load:
|
||||
//
|
||||
// adrp Xa, _foo@GOTPAGE
|
||||
// ldr Xb, [Xa, _foo@GOTPAGEOFF]
|
||||
// ldr/ldrb/ldrsb/ldrsh/... Xc/Wc, [Xb, #imm]
|
||||
if (!is_adrp(loc1) || (*loc2 & 0xffc0'0000) != 0xf940'0000)
|
||||
return;
|
||||
|
||||
u64 got_page = page(addr1) + get_adrp_imm(loc1);
|
||||
u64 pageoff = bits(*loc2, 21, 10) << 3;
|
||||
|
||||
if (got_page + pageoff < ctx.got.hdr.addr ||
|
||||
ctx.got.hdr.addr + ctx.got.hdr.size <= got_page + pageoff)
|
||||
Fatal(ctx) << *file << ": LDR_GOT_LDR out of range";
|
||||
|
||||
ASSERT_RANGE(got_page + pageoff, ctx.got.hdr.addr, ctx.got.hdr.size);
|
||||
|
||||
u64 got_value = *(ul64 *)(ctx.buf + ctx.got.hdr.offset + got_page + pageoff -
|
||||
ctx.got.hdr.addr);
|
||||
|
||||
// If the GOT slot is already filled with a value and we can
|
||||
// inline the value, do it.
|
||||
if (got_value && relax_ldr(loc3, addr3, got_value)) {
|
||||
*loc1 = 0xd503'201f; // nop
|
||||
*loc2 = 0xd503'201f; // nop
|
||||
return;
|
||||
}
|
||||
|
||||
// If the GOT slot is close enough to PC, we can eliminate ADRP.
|
||||
if (relax_ldr(loc2, addr2, got_page))
|
||||
*loc1 = 0xd503'201f; // nop
|
||||
}
|
||||
|
||||
static void relax_adrp_adrp(Context<E> &ctx, ul32 *loc1, ul32 *loc2,
|
||||
u64 addr1, u64 addr2) {
|
||||
// adrp Xa, _foo@PAGE
|
||||
// adrp Xa, _bar@PAGE
|
||||
// ->
|
||||
// ldr Xa, _foo@PAGE
|
||||
// nop
|
||||
|
||||
if (is_adrp(loc1) && is_adrp(loc2) &&
|
||||
page(addr1) + get_adrp_imm(loc1) == page(addr2) + get_adrp_imm(loc2) &&
|
||||
bits(*loc1, 4, 0) == bits(*loc2, 4, 0))
|
||||
*loc2 = 0xd503'201f; // nop
|
||||
}
|
||||
|
||||
static void relax_adrp_ldr(Context<E> &ctx, ul32 *loc1, ul32 *loc2,
|
||||
u64 addr1, u64 addr2) {
|
||||
// adrp Xa, _foo@PAGE
|
||||
// ldr Xb/Wb, [Xa, _foo@PAGEOFF]
|
||||
// ->
|
||||
// nop
|
||||
// ldr Xb/Wb, [_foo]
|
||||
if (is_adrp(loc1) && relax_ldr(loc2, addr2, page(addr1) + get_adrp_imm(loc1)))
|
||||
*loc1 = 0xd503'201f; // nop
|
||||
}
|
||||
|
||||
static void relax_adrp_add(Context<E> &ctx, ul32 *loc1, ul32 *loc2,
|
||||
u64 addr1, u64 addr2) {
|
||||
// adrp Xa, _foo@PAGE
|
||||
// add Xb, Xa, _foo@PAGEOFF
|
||||
// ->
|
||||
// nop
|
||||
// adr Xb, _foo
|
||||
if (!is_adrp(loc1) || (*loc2 & 0xffc0'0000) != 0x9100'0000)
|
||||
return;
|
||||
|
||||
i64 disp = page(addr1) + get_adrp_imm(loc1) + bits(*loc2, 21, 10) - addr2;
|
||||
|
||||
if (disp == sign_extend(disp, 20)) {
|
||||
*loc1 = 0xd503'201f; // nop
|
||||
*loc2 = 0x1000'0000 | (bits(disp, 1, 0) << 29) |
|
||||
(bits(disp, 20, 2) << 5) | bits(*loc2, 4, 0); // adr Xb, _foo
|
||||
}
|
||||
}
|
||||
|
||||
static Subsection<E> *find_subsection_by_addr(ObjectFile<E> *file, u32 addr) {
|
||||
for (Subsection<E> *subsec : file->subsections)
|
||||
if (subsec->input_addr <= addr &&
|
||||
addr < subsec->input_addr + subsec->input_size)
|
||||
return subsec;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// On ARM, we generally need two or more instructions to materialize
|
||||
// an address of an object in a register or jump to a function.
|
||||
// However, if an object or a function is close enough to PC, a single
|
||||
// instruction suffices.
|
||||
//
|
||||
// This function replaces such redundant instruction sequences with
|
||||
// shorter instruction sequences. We pad holes with NOPs, so the total
|
||||
// number of instructions won't change by this relaxation, but replacing
|
||||
// instructions with NOPs generally makes program faster since the CPU
|
||||
// has a special logic to skip NOPs as quickly as possible.
|
||||
//
|
||||
// Locations of relaxable instructions are in the
|
||||
// LC_LINKER_OPTIMIZATION_HINT segment. That segment contains a
|
||||
// sequence of ULEB-encoded integers.
|
||||
//
|
||||
// This pass is optional; an output file works correctly without
|
||||
// applying these optimizations.
|
||||
void apply_linker_optimization_hints(Context<E> &ctx) {
|
||||
Timer t(ctx, "apply_linker_optimization_hints");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
std::string_view hints = file->get_linker_optimization_hints(ctx);
|
||||
|
||||
while (!hints.empty()) {
|
||||
i64 type = read_uleb(hints);
|
||||
if (type == 0)
|
||||
return;
|
||||
|
||||
i64 nargs = read_uleb(hints);
|
||||
|
||||
switch (type) {
|
||||
case LOH_ARM64_ADRP_LDR_GOT_LDR: {
|
||||
assert(nargs == 3);
|
||||
i64 input_addr1 = read_uleb(hints);
|
||||
i64 input_addr2 = read_uleb(hints);
|
||||
i64 input_addr3 = read_uleb(hints);
|
||||
|
||||
Subsection<E> *subsec = find_subsection_by_addr(file, input_addr1);
|
||||
if (!subsec || !subsec->is_alive)
|
||||
return;
|
||||
|
||||
ASSERT_RANGE(input_addr1, subsec->input_addr, subsec->input_size);
|
||||
ASSERT_RANGE(input_addr2, subsec->input_addr, subsec->input_size);
|
||||
ASSERT_RANGE(input_addr3, subsec->input_addr, subsec->input_size);
|
||||
|
||||
i64 offset1 = subsec->input_addr - input_addr1;
|
||||
i64 offset2 = subsec->input_addr - input_addr2;
|
||||
i64 offset3 = subsec->input_addr - input_addr3;
|
||||
|
||||
u8 *loc = ctx.buf + subsec->isec.osec.hdr.offset + subsec->output_offset;
|
||||
ul32 *loc1 = (ul32 *)(loc + offset1);
|
||||
ul32 *loc2 = (ul32 *)(loc + offset2);
|
||||
ul32 *loc3 = (ul32 *)(loc + offset3);
|
||||
|
||||
u64 addr1 = subsec->get_addr(ctx) + offset1;
|
||||
u64 addr2 = subsec->get_addr(ctx) + offset2;
|
||||
u64 addr3 = subsec->get_addr(ctx) + offset3;
|
||||
|
||||
relax_adrp_ldr_got_ldr(ctx, loc1, loc2, loc3, addr1, addr2, addr3,
|
||||
file);
|
||||
break;
|
||||
}
|
||||
case LOH_ARM64_ADRP_ADRP:
|
||||
case LOH_ARM64_ADRP_LDR:
|
||||
case LOH_ARM64_ADRP_ADD: {
|
||||
assert(nargs == 2);
|
||||
i64 input_addr1 = read_uleb(hints);
|
||||
i64 input_addr2 = read_uleb(hints);
|
||||
|
||||
Subsection<E> *subsec = find_subsection_by_addr(file, input_addr1);
|
||||
if (!subsec || !subsec->is_alive)
|
||||
return;
|
||||
|
||||
ASSERT_RANGE(input_addr1, subsec->input_addr, subsec->input_size);
|
||||
ASSERT_RANGE(input_addr2, subsec->input_addr, subsec->input_size);
|
||||
|
||||
i64 offset1 = subsec->input_addr - input_addr1;
|
||||
i64 offset2 = subsec->input_addr - input_addr2;
|
||||
|
||||
u8 *loc = ctx.buf + subsec->isec.osec.hdr.offset + subsec->output_offset;
|
||||
ul32 *loc1 = (ul32 *)(loc + offset1);
|
||||
ul32 *loc2 = (ul32 *)(loc + offset2);
|
||||
|
||||
u64 addr1 = subsec->get_addr(ctx) + offset1;
|
||||
u64 addr2 = subsec->get_addr(ctx) + offset2;
|
||||
|
||||
switch (type) {
|
||||
case LOH_ARM64_ADRP_ADRP:
|
||||
relax_adrp_adrp(ctx, loc1, loc2, addr1, addr2);
|
||||
break;
|
||||
case LOH_ARM64_ADRP_LDR:
|
||||
relax_adrp_ldr(ctx, loc1, loc2, addr1, addr2);
|
||||
break;
|
||||
case LOH_ARM64_ADRP_ADD:
|
||||
relax_adrp_add(ctx, loc1, loc2, addr1, addr2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Skip unsupported optimizations hints.
|
||||
for (i64 i = 0; i < nargs; i++)
|
||||
read_uleb(hints);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -1,216 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
using E = X86_64;
|
||||
|
||||
template <>
|
||||
void StubsSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->hdr.offset;
|
||||
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
// `ff 25 xx xx xx xx` is a RIP-relative indirect jump instruction,
|
||||
// i.e., `jmp *IMM(%rip)`. It loads an address from la_symbol_ptr
|
||||
// and jump there.
|
||||
static_assert(E::stub_size == 6);
|
||||
buf[i * 6] = 0xff;
|
||||
buf[i * 6 + 1] = 0x25;
|
||||
*(ul32 *)(buf + i * 6 + 2) =
|
||||
ctx.lazy_symbol_ptr.hdr.addr + i * word_size - (this->hdr.addr + i * 6 + 6);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void StubHelperSection<E>::copy_buf(Context<E> &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) == E::stub_helper_hdr_size);
|
||||
|
||||
memcpy(buf, insn0, sizeof(insn0));
|
||||
*(ul32 *)(buf + 3) =
|
||||
get_symbol(ctx, "__dyld_private")->get_addr(ctx) - this->hdr.addr - 7;
|
||||
*(ul32 *)(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) == E::stub_helper_size);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 1) = ctx.stubs.bind_offsets[i];
|
||||
*(ul32 *)(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 *(il32 *)(buf + r.offset) + get_reloc_addend(r.type);
|
||||
if (r.p2size == 3)
|
||||
return *(il64 *)(buf + r.offset) + get_reloc_addend(r.type);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
template <>
|
||||
std::vector<Relocation<E>>
|
||||
read_relocations(Context<E> &ctx, ObjectFile<E> &file,
|
||||
const MachSection &hdr) {
|
||||
std::vector<Relocation<E>> vec;
|
||||
vec.reserve(hdr.nreloc);
|
||||
|
||||
MachRel *rels = (MachRel *)(file.mf->data + hdr.reloff);
|
||||
|
||||
for (i64 i = 0; i < hdr.nreloc; i++) {
|
||||
MachRel &r = rels[i];
|
||||
i64 addend = read_addend((u8 *)file.mf->data + hdr.offset, r);
|
||||
|
||||
vec.push_back({r.offset, (u8)r.type, (u8)r.p2size});
|
||||
Relocation<E> &rel = vec.back();
|
||||
|
||||
if (i > 0 && rels[i - 1].type == X86_64_RELOC_SUBTRACTOR)
|
||||
rel.is_subtracted = true;
|
||||
|
||||
if (!rel.is_subtracted && rels[i].type != X86_64_RELOC_SUBTRACTOR)
|
||||
rel.is_pcrel = r.is_pcrel;
|
||||
|
||||
if (r.is_extern) {
|
||||
rel.sym = file.syms[r.idx];
|
||||
rel.addend = addend;
|
||||
continue;
|
||||
}
|
||||
|
||||
u64 addr = r.is_pcrel ? (hdr.addr + r.offset + addend + 4) : addend;
|
||||
Subsection<E> *target = file.find_subsection(ctx, r.idx - 1, addr);
|
||||
if (!target)
|
||||
Fatal(ctx) << file << ": bad relocation: " << r.offset;
|
||||
|
||||
rel.subsec = target;
|
||||
rel.addend = addr - target->input_addr;
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<E>::scan_relocations(Context<E> &ctx) {
|
||||
for (Relocation<E> &r : get_rels()) {
|
||||
Symbol<E> *sym = r.sym;
|
||||
if (!sym)
|
||||
continue;
|
||||
|
||||
if (sym->is_imported && sym->file->is_dylib)
|
||||
((DylibFile<E> *)sym->file)->is_alive = true;
|
||||
|
||||
switch (r.type) {
|
||||
case X86_64_RELOC_UNSIGNED:
|
||||
case X86_64_RELOC_SUBTRACTOR:
|
||||
if (sym->is_imported) {
|
||||
if (r.p2size != 3) {
|
||||
Error(ctx) << this->isec << ": " << r << " relocation at offset 0x"
|
||||
<< std::hex << r.offset << " against symbol `"
|
||||
<< *sym << "' can not be used";
|
||||
}
|
||||
r.needs_dynrel = true;
|
||||
}
|
||||
break;
|
||||
case X86_64_RELOC_GOT:
|
||||
case X86_64_RELOC_GOT_LOAD:
|
||||
sym->flags |= NEEDS_GOT;
|
||||
break;
|
||||
case X86_64_RELOC_TLV:
|
||||
sym->flags |= NEEDS_THREAD_PTR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sym->is_imported)
|
||||
sym->flags |= NEEDS_STUB;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<E>::apply_reloc(Context<E> &ctx, u8 *buf) {
|
||||
std::span<Relocation<E>> rels = get_rels();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
Relocation<E> &r = rels[i];
|
||||
u64 val = r.addend;
|
||||
|
||||
if (r.sym && !r.sym->file) {
|
||||
Error(ctx) << "undefined symbol: " << isec.file << ": " << *r.sym;
|
||||
continue;
|
||||
}
|
||||
|
||||
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_SUBTRACTOR: {
|
||||
Relocation<E> s = rels[++i];
|
||||
assert(s.type == X86_64_RELOC_UNSIGNED);
|
||||
assert(r.p2size == s.p2size);
|
||||
u64 val1 = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
u64 val2 = s.sym ? s.sym->get_addr(ctx) : s.subsec->get_addr(ctx);
|
||||
val += val2 - val1;
|
||||
break;
|
||||
}
|
||||
case X86_64_RELOC_GOT:
|
||||
case X86_64_RELOC_GOT_LOAD:
|
||||
val += r.sym->get_got_addr(ctx);
|
||||
break;
|
||||
case X86_64_RELOC_TLV:
|
||||
val += r.sym->get_tlv_addr(ctx);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
|
||||
}
|
||||
|
||||
if (isec.hdr.type == S_THREAD_LOCAL_VARIABLES) {
|
||||
// An address of a thread-local variable is computed as an offset
|
||||
// to the beginning of the first thread-local section.
|
||||
val -= ctx.tls_begin;
|
||||
} else if (r.is_pcrel) {
|
||||
val -= get_addr(ctx) + r.offset + 4 + get_reloc_addend(r.type);
|
||||
}
|
||||
|
||||
if (r.p2size == 2)
|
||||
*(ul32 *)(buf + r.offset) = val;
|
||||
else if (r.p2size == 3)
|
||||
*(ul64 *)(buf + r.offset) = val;
|
||||
else
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -1,573 +0,0 @@
|
||||
#include "mold.h"
|
||||
#include "../cmdline.h"
|
||||
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unordered_set>
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
static const char helpmsg[] = R"(
|
||||
Options:
|
||||
-F<PATH> Add DIR to framework search path
|
||||
-L<PATH> Add DIR to library search path
|
||||
-ObjC Load all static archive members that implement
|
||||
an Objective-C class or category
|
||||
-U <SYMBOL> Allow a symbol to be undefined
|
||||
-Z Do not search the standard directories when
|
||||
searching for libraries and frameworks
|
||||
-add_ast_path <FILE> Add a N_AST symbol with the given filename
|
||||
-add_empty_section <SEGNAME> <SECTNAME>
|
||||
Add an empty section
|
||||
-adhoc_codesign Add ad-hoc code signature to the output file
|
||||
-no_adhoc_codesign
|
||||
-all_load Include all objects from static archives
|
||||
-noall_load
|
||||
-application_extension Verify that all dylibs are extension-safe
|
||||
-no_application_extension
|
||||
-arch <ARCH_NAME> Specify target architecture
|
||||
-bundle Produce a mach-o bundle
|
||||
-bundle_loader <EXECUTABLE> Resolve undefined symbols using the given executable
|
||||
-compatibility_version <VERSION>
|
||||
Specifies the compatibility version number of the library
|
||||
-current_version <VERSION> Specifies the current version number of the library.
|
||||
-dead_strip Remove unreachable functions and data
|
||||
-dead_strip_dylibs Remove unreachable dylibs from dependencies
|
||||
-debug_variant Ignored
|
||||
-demangle Demangle C++ symbols in log messages (default)
|
||||
-dependency_info <FILE> Ignored
|
||||
-dylib Produce a dynamic library
|
||||
-dylib_compatibility_version <VERSION>
|
||||
Alias for -compatibility_version
|
||||
-dylib_current_version <VERSION>
|
||||
Alias for -current_version
|
||||
-dylib_install_name Alias for -install_name
|
||||
-dynamic Link against dylibs (default)
|
||||
-e <SYMBOL> Specify the entry point of a main executable
|
||||
-execute Produce an executable (default)
|
||||
-export_dynamic Preserves all global symbols in main executables during LTO
|
||||
-exported_symbol <SYMBOL> Export a given symbol
|
||||
-exported_symbols_list <FILE>
|
||||
Read a list of exported symbols from a given file
|
||||
-filelist <FILE>[,<DIR>] Specify the list of input file names
|
||||
-final_output <NAME>
|
||||
-force_load <FILE> Include all objects from a given static archive
|
||||
-framework <NAME>,[,<SUFFIX>]
|
||||
Search for a given framework
|
||||
-headerpad <SIZE> Allocate the size of padding after load commands
|
||||
-headerpad_max_install_names
|
||||
Allocate MAXPATHLEN byte padding after load commands
|
||||
-help Report usage information
|
||||
-hidden-l<LIB>
|
||||
-ignore_optimization_hints Do not rewrite instructions as optimization (default)
|
||||
-enable_optimization_hints
|
||||
-install_name <NAME>
|
||||
-l<LIB> Search for a given library
|
||||
-lto_library <FILE> Ignored
|
||||
-macos_version_min <VERSION>
|
||||
-map <FILE> Write map file to a given file
|
||||
-mark_dead_strippable_dylib Mark the output as dead-strippable
|
||||
-needed-l<LIB> Search for a given library
|
||||
-needed-framework <NAME>[,<SUFFIX>]
|
||||
Search for a given framework
|
||||
-no_deduplicate Ignored
|
||||
-no_function_starts Do not generate an LC_FUNCTION_STARTS load command
|
||||
-no_uuid Do not generate an LC_UUID load command
|
||||
-o <FILE> Set output filename
|
||||
-objc_abi_version <VERSION> Ignored
|
||||
-object_path_lto <FILE> Write a LTO temporary file to a given path
|
||||
-order_file <FILE> Ignored
|
||||
-pagezero_size <SIZE> Specify the size of the __PAGEZERO segment
|
||||
-platform_version <PLATFORM> <MIN_VERSION> <SDK_VERSION>
|
||||
Set platform, platform version and SDK version
|
||||
-random_uuid Generate a random LC_UUID load command
|
||||
-reexport-l<LIB> Search for a given library
|
||||
-rpath <PATH> Add PATH to the runpath search path list
|
||||
-search_dylibs_first
|
||||
-search_paths_first
|
||||
-sectalign <SEGNAME> <SECTNAME> <VALUE>
|
||||
Set a section's alignment to a given value
|
||||
-sectcreate <SEGNAME> <SECTNAME> <FILE>
|
||||
-stack_size <SIZE>
|
||||
-stats Show statistics info
|
||||
-syslibroot <DIR> Prepend DIR to library search paths
|
||||
-t Print out each file the linker loads
|
||||
-thread_count <NUMBER> Use given number of threads
|
||||
-u <SYMBOL> Force load a given symbol from archive if necessary
|
||||
-unexported_symbol <SYMBOL> Export all but a given symbol
|
||||
-unexported_symbols_list <FILE>
|
||||
Read a list of unexported symbols from a given file
|
||||
-v Report version information
|
||||
-weak_framework <NAME>[,<SUFFIX>]
|
||||
Search for a given framework
|
||||
-weak-l<LIB> Search for a given library)";
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_platform(Context<E> &ctx, std::string_view arg) {
|
||||
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>
|
||||
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.data(), arg.data() + arg.size(), 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;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 parse_hex(Context<E> &ctx, std::string_view arg) {
|
||||
auto flags = std::regex_constants::ECMAScript | std::regex_constants::icase;
|
||||
static std::regex re(R"((?:0x)?[0-9a-f]+)", flags);
|
||||
|
||||
std::cmatch m;
|
||||
if (!std::regex_match(arg.begin(), arg.end(), re))
|
||||
Fatal(ctx) << "malformed hexadecimal number: " << arg;
|
||||
|
||||
if (arg.starts_with("0x") || arg.starts_with("0X"))
|
||||
return std::stoll(std::string(arg.substr(2)), nullptr, 16);
|
||||
return std::stoll(std::string(arg), nullptr, 16);
|
||||
}
|
||||
|
||||
static bool is_directory(std::filesystem::path path) {
|
||||
std::error_code ec;
|
||||
return std::filesystem::is_directory(path, ec) && !ec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<std::string>
|
||||
read_lines(Context<E> &ctx, std::string_view path) {
|
||||
MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::must_open(ctx, std::string(path));
|
||||
std::string_view data((char *)mf->data, mf->size);
|
||||
|
||||
std::vector<std::string> vec;
|
||||
|
||||
while (!data.empty()) {
|
||||
size_t pos = data.find('\n');
|
||||
std::string_view line;
|
||||
|
||||
if (pos == data.npos) {
|
||||
line = data;
|
||||
data = "";
|
||||
} else {
|
||||
line = data.substr(0, pos);
|
||||
data = data.substr(pos + 1);
|
||||
}
|
||||
|
||||
line = string_trim(line);
|
||||
if (!line.empty() && !line.starts_with('#'))
|
||||
vec.push_back(std::string(line));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
std::vector<std::string_view> &args = ctx.cmdline_args;
|
||||
std::vector<std::string> remaining;
|
||||
i64 i = 1;
|
||||
|
||||
std::vector<std::string> framework_paths;
|
||||
std::vector<std::string> library_paths;
|
||||
bool nostdlib = false;
|
||||
bool version_shown = false;
|
||||
std::optional<i64> pagezero_size;
|
||||
|
||||
while (i < args.size()) {
|
||||
std::string_view arg;
|
||||
std::string_view arg2;
|
||||
std::string_view arg3;
|
||||
u64 hex_arg;
|
||||
|
||||
auto read_arg = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
if (args.size() <= i + 1)
|
||||
Fatal(ctx) << "option -" << name << ": argument missing";
|
||||
arg = args[i + 1];
|
||||
i += 2;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_arg2 = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
if (args.size() <= i + 2)
|
||||
Fatal(ctx) << "option -" << name << ": argument missing";
|
||||
arg = args[i + 1];
|
||||
arg2 = args[i + 2];
|
||||
i += 3;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_arg3 = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
if (args.size() <= i + 3)
|
||||
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;
|
||||
};
|
||||
|
||||
auto read_hex = [&](std::string name) {
|
||||
if (!read_arg(name))
|
||||
return false;
|
||||
|
||||
size_t pos;
|
||||
hex_arg = std::stol(std::string(arg), &pos, 16);
|
||||
if (pos != arg.size())
|
||||
Fatal(ctx) << "malformed " << name << ": " << arg;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (args[i].starts_with('@')) {
|
||||
std::vector<std::string_view> vec =
|
||||
read_response_file(ctx, args[i].substr(1));
|
||||
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[0]
|
||||
<< " [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_arg("-U")) {
|
||||
ctx.arg.U.push_back(std::string(arg));
|
||||
} else if (read_arg("-add_ast_path")) {
|
||||
ctx.arg.add_ast_path.push_back(std::string(arg));
|
||||
} else if (read_arg2("-add_empty_section")) {
|
||||
ctx.arg.add_empty_section.push_back({arg, arg2});
|
||||
} else if (read_flag("-adhoc_codesign")) {
|
||||
ctx.arg.adhoc_codesign = true;
|
||||
} else if (read_flag("-no_adhoc_codesign")) {
|
||||
ctx.arg.adhoc_codesign = false;
|
||||
} else if (read_flag("-all_load")) {
|
||||
remaining.push_back("-all_load");
|
||||
} else if (read_flag("-noall_load")) {
|
||||
remaining.push_back("-noall_load");
|
||||
} else if (read_flag("-application_extension")) {
|
||||
ctx.arg.application_extension = true;
|
||||
} else if (read_flag("-no_application_extension")) {
|
||||
ctx.arg.application_extension = false;
|
||||
} else if (read_arg("-arch")) {
|
||||
if (arg == "x86_64")
|
||||
ctx.arg.arch = CPU_TYPE_X86_64;
|
||||
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_arg("-bundle_loader")) {
|
||||
ctx.arg.bundle_loader = arg;
|
||||
} else if (read_arg("-compatibility_version") ||
|
||||
read_arg("-dylib_compatibility_version")) {
|
||||
ctx.arg.compatibility_version = parse_version(ctx, arg);
|
||||
} else if (read_arg("-current_version") ||
|
||||
read_arg("-dylib_current_version")) {
|
||||
ctx.arg.current_version = parse_version(ctx, arg);
|
||||
} else if (read_flag("-color-diagnostics") ||
|
||||
read_flag("--color-diagnostics")) {
|
||||
ctx.arg.color_diagnostics = true;
|
||||
} else if (read_flag("-dead_strip")) {
|
||||
ctx.arg.dead_strip = true;
|
||||
} else if (read_flag("-dead_strip_dylibs")) {
|
||||
ctx.arg.dead_strip_dylibs = true;
|
||||
} else if (read_flag("-debug_variant")) {
|
||||
} else if (read_flag("-demangle")) {
|
||||
ctx.arg.demangle = true;
|
||||
} else if (read_arg("-dependency_info")) {
|
||||
ctx.arg.dependency_info = arg;
|
||||
} else if (read_flag("-dylib")) {
|
||||
ctx.output_type = MH_DYLIB;
|
||||
} else if (read_hex("-headerpad")) {
|
||||
ctx.arg.headerpad = hex_arg;
|
||||
} else if (read_flag("-headerpad_max_install_names")) {
|
||||
ctx.arg.headerpad = 1024;
|
||||
} else if (read_flag("-dynamic")) {
|
||||
ctx.arg.dynamic = true;
|
||||
} else if (read_arg("-e")) {
|
||||
ctx.arg.entry = get_symbol(ctx, arg);
|
||||
} else if (read_flag("-execute")) {
|
||||
ctx.output_type = MH_EXECUTE;
|
||||
} else if (read_flag("-export_dynamic")) {
|
||||
ctx.arg.export_dynamic = true;
|
||||
} else if (read_arg("-exported_symbol")) {
|
||||
if (!ctx.arg.exported_symbols_list.add(arg, 1))
|
||||
Fatal(ctx) << "-exported_symbol: invalid glob pattern: " << arg;
|
||||
} else if (read_arg("-exported_symbols_list")) {
|
||||
for (std::string_view pat : read_lines(ctx, arg))
|
||||
if (!ctx.arg.exported_symbols_list.add(pat, 1))
|
||||
Fatal(ctx) << "-exported_symbols_list: " << arg
|
||||
<< ": invalid glob pattern: " << pat;
|
||||
} else if (read_arg("-fatal_warnings")) {
|
||||
} else if (read_arg("-filelist")) {
|
||||
remaining.push_back("-filelist");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-final_output")) {
|
||||
ctx.arg.final_output = arg;
|
||||
} else if (read_arg("-force_load")) {
|
||||
remaining.push_back("-force_load");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-framework")) {
|
||||
remaining.push_back("-framework");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-lto_library")) {
|
||||
ctx.arg.lto_library = arg;
|
||||
} else if (read_arg("-macos_version_min")) {
|
||||
ctx.arg.platform = PLATFORM_MACOS;
|
||||
ctx.arg.platform_min_version = parse_version(ctx, arg);
|
||||
} else if (read_joined("-hidden-l")) {
|
||||
remaining.push_back("-hidden-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_flag("-ignore_optimization_hints")) {
|
||||
ctx.arg.ignore_optimization_hints = true;
|
||||
} else if (read_flag("-enable_optimization_hints")) {
|
||||
ctx.arg.ignore_optimization_hints = false;
|
||||
} else if (read_arg("-install_name") || read_arg("-dylib_install_name")) {
|
||||
ctx.arg.install_name = arg;
|
||||
} else if (read_joined("-l")) {
|
||||
remaining.push_back("-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-map")) {
|
||||
ctx.arg.map = arg;
|
||||
} else if (read_flag("-mark_dead_strippable_dylib")) {
|
||||
ctx.arg.mark_dead_strippable_dylib = true;
|
||||
} else if (read_arg("-mllvm")) {
|
||||
ctx.arg.mllvm.push_back(std::string(arg));
|
||||
} else if (read_joined("-needed-l")) {
|
||||
remaining.push_back("-needed-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} 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_flag("-no_function_starts")) {
|
||||
ctx.arg.function_starts = false;
|
||||
} else if (read_flag("-no_uuid")) {
|
||||
ctx.arg.uuid = UUID_NONE;
|
||||
} else if (read_arg("-o")) {
|
||||
ctx.arg.output = arg;
|
||||
} else if (read_arg("-objc_abi_version")) {
|
||||
} else if (read_arg("-object_path_lto")) {
|
||||
ctx.arg.object_path_lto = arg;
|
||||
} else if (read_arg("-order_file")) {
|
||||
ctx.arg.order_file = read_lines(ctx, arg);
|
||||
} else if (read_hex("-pagezero_size")) {
|
||||
pagezero_size = hex_arg;
|
||||
} else if (read_flag("-perf")) {
|
||||
ctx.arg.perf = true;
|
||||
} else if (read_arg3("-platform_version")) {
|
||||
ctx.arg.platform = parse_platform(ctx, arg);
|
||||
ctx.arg.platform_min_version = parse_version(ctx, arg2);
|
||||
ctx.arg.platform_sdk_version = parse_version(ctx, arg3);
|
||||
} else if (read_flag("-quick_exit")) {
|
||||
ctx.arg.quick_exit = true;
|
||||
} else if (read_flag("-no_quick_exit")) {
|
||||
ctx.arg.quick_exit = false;
|
||||
} else if (read_flag("-random_uuid")) {
|
||||
ctx.arg.uuid = UUID_RANDOM;
|
||||
} else if (read_joined("-reexport-l")) {
|
||||
remaining.push_back("-reexport-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-rpath")) {
|
||||
ctx.arg.rpath.push_back(std::string(arg));
|
||||
} else if (read_flag("-search_paths_first")) {
|
||||
ctx.arg.search_paths_first = true;
|
||||
} else if (read_flag("-search_dylibs_first")) {
|
||||
ctx.arg.search_paths_first = false;
|
||||
} else if (read_arg3("-sectalign")) {
|
||||
u64 val = parse_hex(ctx, arg3);
|
||||
std::string key = std::string(arg) + "," + std::string(arg2);
|
||||
if (!has_single_bit(val))
|
||||
Fatal(ctx) << "-sectalign: invalid alignment value: " << arg3;
|
||||
ctx.arg.sectalign.push_back({arg, arg2, (u8)std::countl_zero(val)});
|
||||
} else if (read_arg3("-sectcreate")) {
|
||||
ctx.arg.sectcreate.push_back({arg, arg2, arg3});
|
||||
} else if (read_hex("-stack_size")) {
|
||||
ctx.arg.stack_size = hex_arg;
|
||||
} else if (read_flag("-stats")) {
|
||||
ctx.arg.stats = true;
|
||||
Counter::enabled = true;
|
||||
} else if (read_arg("-syslibroot")) {
|
||||
ctx.arg.syslibroot.push_back(std::string(arg));
|
||||
} else if (read_flag("-t")) {
|
||||
ctx.arg.trace = true;
|
||||
} else if (read_arg("-thread_count")) {
|
||||
ctx.arg.thread_count = std::stoi(std::string(arg));
|
||||
} else if (read_arg("-u")) {
|
||||
ctx.arg.u.push_back(std::string(arg));
|
||||
} else if (read_arg("-unexported_symbol")) {
|
||||
if (!ctx.arg.unexported_symbols_list.add(arg, 1))
|
||||
Fatal(ctx) << "-unexported_symbol: invalid glob pattern: " << arg;
|
||||
} else if (read_arg("-unexported_symbols_list")) {
|
||||
for (std::string_view pat : read_lines(ctx, arg))
|
||||
if (!ctx.arg.unexported_symbols_list.add(pat, 1))
|
||||
Fatal(ctx) << "-unexported_symbols_list: " << arg
|
||||
<< ": invalid glob pattern: " << pat;
|
||||
} else if (read_flag("-v")) {
|
||||
SyncOut(ctx) << mold_version;
|
||||
version_shown = true;
|
||||
} else if (read_arg("-weak_framework")) {
|
||||
remaining.push_back("-weak_framework");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_joined("-weak-l")) {
|
||||
remaining.push_back("-weak-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else {
|
||||
if (args[i][0] == '-')
|
||||
Fatal(ctx) << "unknown command line option: " << args[i];
|
||||
remaining.push_back(std::string(args[i]));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx.arg.entry)
|
||||
ctx.arg.entry = get_symbol(ctx, "_main");
|
||||
|
||||
if (ctx.arg.thread_count == 0)
|
||||
ctx.arg.thread_count = get_default_thread_count();
|
||||
|
||||
if (!ctx.arg.bundle_loader.empty() && ctx.output_type != MH_BUNDLE)
|
||||
Fatal(ctx) << "-bundle_loader cannot be specified without -bundle";
|
||||
|
||||
auto add_search_path = [&](std::vector<std::string> &vec, std::string path) {
|
||||
if (!path.starts_with('/') || ctx.arg.syslibroot.empty()) {
|
||||
if (is_directory(path))
|
||||
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)
|
||||
Fatal(ctx) << "-pagezero_size option can only be used when"
|
||||
<< " linking a main executable";
|
||||
ctx.arg.pagezero_size = *pagezero_size;
|
||||
} else {
|
||||
ctx.arg.pagezero_size = (ctx.output_type == MH_EXECUTE) ? 0x100000000 : 0;
|
||||
}
|
||||
|
||||
if (ctx.arg.final_output == "") {
|
||||
if (ctx.arg.install_name != "")
|
||||
ctx.arg.final_output = ctx.arg.install_name;
|
||||
else
|
||||
ctx.arg.final_output = ctx.arg.output;
|
||||
}
|
||||
|
||||
if (ctx.arg.uuid == UUID_RANDOM)
|
||||
memcpy(ctx.uuid, get_uuid_v4().data(), 16);
|
||||
|
||||
if (version_shown && remaining.empty())
|
||||
exit(0);
|
||||
return remaining;
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template i64 parse_version(Context<E> &, std::string_view); \
|
||||
template std::vector<std::string> parse_nonpositional_args(Context<E> &)
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -1,147 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
static std::vector<Subsection<E> *> collect_root_set(Context<E> &ctx) {
|
||||
Timer t(ctx, "collect_root_set");
|
||||
|
||||
std::vector<Subsection<E> *> rootset;
|
||||
|
||||
auto add = [&](Symbol<E> *sym) {
|
||||
if (sym->subsec)
|
||||
rootset.push_back(sym->subsec);
|
||||
};
|
||||
|
||||
auto keep = [&](Symbol<E> *sym) {
|
||||
if (sym->no_dead_strip)
|
||||
return true;
|
||||
if (ctx.output_type == MH_DYLIB || ctx.output_type == MH_BUNDLE)
|
||||
if (sym->scope == SCOPE_EXTERN || sym->referenced_dynamically)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
for (Symbol<E> *sym : file->syms)
|
||||
if (sym->file == file && keep(sym))
|
||||
add(sym);
|
||||
|
||||
for (Subsection<E> *subsec : file->subsections)
|
||||
if (const MachSection &hdr = subsec->isec.hdr;
|
||||
(hdr.attr & S_ATTR_NO_DEAD_STRIP) ||
|
||||
hdr.type == S_MOD_INIT_FUNC_POINTERS ||
|
||||
hdr.type == S_MOD_TERM_FUNC_POINTERS)
|
||||
rootset.push_back(subsec);
|
||||
}
|
||||
|
||||
for (std::string_view name : ctx.arg.u)
|
||||
if (Symbol<E> *sym = get_symbol(ctx, name); sym->file)
|
||||
add(sym);
|
||||
|
||||
add(ctx.arg.entry);
|
||||
add(get_symbol(ctx, "dyld_stub_binder"));
|
||||
return rootset;
|
||||
}
|
||||
|
||||
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()) {
|
||||
visit(ctx, *rec.subsec);
|
||||
if (rec.lsda)
|
||||
visit(ctx, *rec.lsda);
|
||||
if (rec.personality && rec.personality->subsec)
|
||||
visit(ctx, *rec.personality->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) {
|
||||
Timer t(ctx, "mark");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
for (Subsection<E> *subsec : file->subsections)
|
||||
subsec->is_alive.store(false, std::memory_order_relaxed);
|
||||
});
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
|
||||
for (Subsection<E> *subsec : rootset)
|
||||
visit(ctx, *subsec);
|
||||
|
||||
bool repeat;
|
||||
do {
|
||||
repeat = false;
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
for (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) {
|
||||
Timer t(ctx, "sweep");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *&sym : file->syms)
|
||||
if (sym->file == file && sym->subsec && !sym->subsec->is_alive)
|
||||
sym = nullptr;
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
std::erase_if(file->subsections, [](Subsection<E> *subsec) {
|
||||
return !subsec->is_alive;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void dead_strip(Context<E> &ctx) {
|
||||
Timer t(ctx, "dead_strip");
|
||||
|
||||
std::vector<Subsection<E> *> rootset = collect_root_set(ctx);
|
||||
mark(ctx, rootset);
|
||||
sweep(ctx);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void dead_strip(Context<E> &)
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -1,983 +0,0 @@
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const InputFile<E> &file) {
|
||||
if (file.archive_name.empty())
|
||||
out << path_clean(file.filename);
|
||||
else
|
||||
out << path_clean(file.archive_name) << "(" << path_clean(file.filename) + ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputFile<E>::clear_symbols() {
|
||||
for (Symbol<E> *sym : syms) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
if (sym->file == this) {
|
||||
sym->file = nullptr;
|
||||
sym->scope = SCOPE_LOCAL;
|
||||
sym->is_imported = false;
|
||||
sym->is_weak = false;
|
||||
sym->no_dead_strip = false;
|
||||
sym->subsec = nullptr;
|
||||
sym->value = 0;
|
||||
sym->is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *
|
||||
ObjectFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
ObjectFile<E> *obj = new ObjectFile<E>(mf);
|
||||
obj->archive_name = archive_name;
|
||||
obj->is_alive = archive_name.empty() || ctx.all_load;
|
||||
obj->is_hidden = ctx.hidden_l;
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
return obj;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
if (get_file_type(this->mf) == FileType::LLVM_BITCODE) {
|
||||
// Open an compiler IR file
|
||||
load_lto_plugin(ctx);
|
||||
this->lto_module =
|
||||
ctx.lto.module_create_from_memory(this->mf->data, this->mf->size);
|
||||
if (!this->lto_module)
|
||||
Fatal(ctx) << *this << ": lto_module_create_from_memory failed";
|
||||
|
||||
// Read a symbol table
|
||||
parse_lto_symbols(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
parse_sections(ctx);
|
||||
parse_symbols(ctx);
|
||||
|
||||
MachHeader &mach_hdr = *(MachHeader *)this->mf->data;
|
||||
if (mach_hdr.flags & MH_SUBSECTIONS_VIA_SYMBOLS)
|
||||
split_subsections_via_symbols(ctx);
|
||||
else
|
||||
init_subsections(ctx);
|
||||
|
||||
sort(subsections, [](Subsection<E> *a, Subsection<E> *b) {
|
||||
return a->input_addr < b->input_addr;
|
||||
});
|
||||
|
||||
fix_subsec_members(ctx);
|
||||
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections)
|
||||
if (isec)
|
||||
isec->parse_relocations(ctx);
|
||||
|
||||
if (unwind_sec)
|
||||
parse_compact_unwind(ctx, *unwind_sec);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_sections(Context<E> &ctx) {
|
||||
SegmentCommand *cmd = (SegmentCommand *)find_load_command(ctx, LC_SEGMENT_64);
|
||||
if (!cmd)
|
||||
return;
|
||||
|
||||
MachSection *mach_sec = (MachSection *)((u8 *)cmd + sizeof(*cmd));
|
||||
|
||||
for (i64 i = 0; i < cmd->nsects; i++) {
|
||||
MachSection &msec = mach_sec[i];
|
||||
sections.push_back(nullptr);
|
||||
|
||||
if (msec.match("__LD", "__compact_unwind")) {
|
||||
unwind_sec = &msec;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msec.match("__DATA", "__objc_imageinfo") ||
|
||||
msec.match("__DATA_CONST", "__objc_imageinfo")) {
|
||||
if (msec.size != sizeof(ObjcImageInfo))
|
||||
Fatal(ctx) << *this << ": __objc_imageinfo: invalid size";
|
||||
|
||||
objc_image_info =
|
||||
(ObjcImageInfo *)(this->mf->get_contents().data() + msec.offset);
|
||||
|
||||
if (objc_image_info->version != 0)
|
||||
Fatal(ctx) << *this << ": __objc_imageinfo: unknown version: "
|
||||
<< (u32)objc_image_info->version;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msec.attr & S_ATTR_DEBUG)
|
||||
continue;
|
||||
|
||||
sections.back().reset(new InputSection<E>(ctx, *this, msec, i));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_symbols(Context<E> &ctx) {
|
||||
SymtabCommand *cmd = (SymtabCommand *)find_load_command(ctx, LC_SYMTAB);
|
||||
if (!cmd)
|
||||
return;
|
||||
|
||||
mach_syms = {(MachSym *)(this->mf->data + cmd->symoff), cmd->nsyms};
|
||||
this->syms.reserve(mach_syms.size());
|
||||
|
||||
i64 nlocal = 0;
|
||||
for (MachSym &msym : mach_syms)
|
||||
if (!msym.is_extern)
|
||||
nlocal++;
|
||||
local_syms.reserve(nlocal);
|
||||
|
||||
for (i64 i = 0; i < mach_syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
std::string_view name = (char *)(this->mf->data + cmd->stroff + msym.stroff);
|
||||
|
||||
if (msym.is_extern) {
|
||||
this->syms.push_back(get_symbol(ctx, name));
|
||||
} else {
|
||||
local_syms.emplace_back(name);
|
||||
Symbol<E> &sym = local_syms.back();
|
||||
|
||||
sym.file = this;
|
||||
sym.subsec = nullptr;
|
||||
sym.scope = SCOPE_LOCAL;
|
||||
sym.is_common = false;
|
||||
sym.is_weak = false;
|
||||
if (msym.type == N_ABS)
|
||||
sym.value = msym.value;
|
||||
sym.no_dead_strip = (msym.desc & N_NO_DEAD_STRIP);
|
||||
this->syms.push_back(&sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SplitRegion {
|
||||
u32 offset;
|
||||
u32 size;
|
||||
u32 symidx;
|
||||
bool is_alt_entry;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct SplitInfo {
|
||||
InputSection<E> *isec;
|
||||
std::vector<SplitRegion> regions;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
static std::vector<SplitInfo<E>>
|
||||
split_regular_sections(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
std::vector<SplitInfo<E>> vec(file.sections.size());
|
||||
|
||||
for (i64 i = 0; i < file.sections.size(); i++)
|
||||
if (InputSection<E> *isec = file.sections[i].get())
|
||||
if (isec->hdr.type != S_CSTRING_LITERALS)
|
||||
vec[i].isec = isec;
|
||||
|
||||
// Find all symbols whose type is N_SECT.
|
||||
for (i64 i = 0; i < file.mach_syms.size(); i++) {
|
||||
MachSym &msym = file.mach_syms[i];
|
||||
if (!msym.stab && msym.type == N_SECT && vec[msym.sect - 1].isec) {
|
||||
SplitRegion r;
|
||||
r.offset = msym.value - vec[msym.sect - 1].isec->hdr.addr;
|
||||
r.symidx = i;
|
||||
r.is_alt_entry = (msym.desc & N_ALT_ENTRY);
|
||||
vec[msym.sect - 1].regions.push_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
std::erase_if(vec, [](const SplitInfo<E> &info) { return !info.isec; });
|
||||
|
||||
sort(vec, [](const SplitInfo<E> &a, const SplitInfo<E> &b) {
|
||||
return a.isec->hdr.addr < b.isec->hdr.addr;
|
||||
});
|
||||
|
||||
for (SplitInfo<E> &info : vec) {
|
||||
sort(info.regions, [](const SplitRegion &a, const SplitRegion &b) {
|
||||
return a.offset < b.offset;
|
||||
});
|
||||
}
|
||||
|
||||
// If two symbols point to the same location, we create only one
|
||||
// subsection.
|
||||
for (SplitInfo<E> &info : vec) {
|
||||
i64 last = -1;
|
||||
for (SplitRegion &r : info.regions) {
|
||||
if (!r.is_alt_entry) {
|
||||
if (r.offset == last)
|
||||
r.is_alt_entry = true;
|
||||
last = r.offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix regions so that they cover the entire section without overlapping.
|
||||
for (SplitInfo<E> &info : vec) {
|
||||
std::vector<SplitRegion> &r = info.regions;
|
||||
|
||||
if (r.empty()) {
|
||||
r.push_back({0, (u32)info.isec->hdr.size, (u32)-1, false});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r[0].offset > 0)
|
||||
r.insert(r.begin(), {0, r[0].offset, (u32)-1, false});
|
||||
|
||||
for (i64 i = 1; i < r.size(); i++)
|
||||
if (r[i - 1].offset == r[i].offset)
|
||||
r[i++].is_alt_entry = true;
|
||||
|
||||
i64 last = -1;
|
||||
for (i64 i = 0; i < r.size(); i++) {
|
||||
if (!r[i].is_alt_entry) {
|
||||
if (last != -1)
|
||||
r[last].size = r[i].offset - r[last].offset;
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (last != -1)
|
||||
r[last].size = info.isec->hdr.size - r[last].offset;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::split_subsections_via_symbols(Context<E> &ctx) {
|
||||
sym_to_subsec.resize(mach_syms.size());
|
||||
|
||||
auto add = [&](InputSection<E> &isec, u32 offset, u32 size, u8 p2align,
|
||||
bool is_cstring) {
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = isec,
|
||||
.input_offset = offset,
|
||||
.input_size = size,
|
||||
.input_addr = (u32)(isec.hdr.addr + offset),
|
||||
.p2align = p2align,
|
||||
.is_cstring = is_cstring,
|
||||
};
|
||||
|
||||
subsec_pool.emplace_back(subsec);
|
||||
subsections.push_back(subsec);
|
||||
};
|
||||
|
||||
// Split regular sections into subsections.
|
||||
for (SplitInfo<E> &info : split_regular_sections(ctx, *this)) {
|
||||
InputSection<E> &isec = *info.isec;
|
||||
for (SplitRegion &r : info.regions) {
|
||||
if (!r.is_alt_entry)
|
||||
add(isec, r.offset, r.size, isec.hdr.p2align, false);
|
||||
if (r.symidx != -1)
|
||||
sym_to_subsec[r.symidx] = subsections.back();
|
||||
}
|
||||
}
|
||||
|
||||
// Split __cstring section.
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections) {
|
||||
if (isec && isec->hdr.type == S_CSTRING_LITERALS) {
|
||||
std::string_view str = isec->contents;
|
||||
size_t pos = 0;
|
||||
|
||||
while (pos < str.size()) {
|
||||
size_t end = str.find('\0', pos);
|
||||
if (end == str.npos)
|
||||
Fatal(ctx) << *this << " corruupted cstring section: " << *isec;
|
||||
|
||||
end = str.find_first_not_of('\0', end);
|
||||
if (end == str.npos)
|
||||
end = str.size();
|
||||
|
||||
// A constant string in __cstring has no alignment info, so we
|
||||
// need to infer it.
|
||||
u8 p2align = std::min<u8>(isec->hdr.p2align, std::countr_zero(pos));
|
||||
add(*isec, pos, end - pos, p2align, true);
|
||||
pos = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::init_subsections(Context<E> &ctx) {
|
||||
subsections.resize(sections.size());
|
||||
|
||||
for (i64 i = 0; i < sections.size(); i++) {
|
||||
if (InputSection<E> *isec = sections[i].get()) {
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = *isec,
|
||||
.input_offset = 0,
|
||||
.input_size = (u32)isec->hdr.size,
|
||||
.input_addr = (u32)isec->hdr.addr,
|
||||
.p2align = (u8)isec->hdr.p2align,
|
||||
};
|
||||
subsec_pool.emplace_back(subsec);
|
||||
subsections[i] = subsec;
|
||||
}
|
||||
}
|
||||
|
||||
sym_to_subsec.resize(mach_syms.size());
|
||||
|
||||
for (i64 i = 0; i < mach_syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.stab && msym.type == N_SECT)
|
||||
sym_to_subsec[i] = subsections[msym.sect - 1];
|
||||
}
|
||||
|
||||
std::erase(subsections, nullptr);
|
||||
}
|
||||
|
||||
// Fix local symbols `subsec` members.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::fix_subsec_members(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < mach_syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
|
||||
if (!msym.stab && !msym.is_extern && msym.type == N_SECT) {
|
||||
Subsection<E> *subsec = sym_to_subsec[i];
|
||||
if (!subsec)
|
||||
subsec = find_subsection(ctx, msym.sect - 1, msym.value);
|
||||
|
||||
if (subsec) {
|
||||
sym.subsec = subsec;
|
||||
sym.value = msym.value - subsec->input_addr;
|
||||
} else {
|
||||
// Subsec is null if a symbol is in a __compact_unwind.
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_data_in_code(Context<E> &ctx) {
|
||||
if (auto *cmd = (LinkEditDataCommand *)find_load_command(ctx, LC_DATA_IN_CODE)) {
|
||||
data_in_code_entries = {
|
||||
(DataInCodeEntry *)(this->mf->data + cmd->dataoff),
|
||||
cmd->datasize / sizeof(DataInCodeEntry),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<std::string> ObjectFile<E>::get_linker_options(Context<E> &ctx) {
|
||||
if (get_file_type(this->mf) == FileType::LLVM_BITCODE)
|
||||
return {};
|
||||
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
std::vector<std::string> vec;
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
p += lc.cmdsize;
|
||||
|
||||
if (lc.cmd == LC_LINKER_OPTION) {
|
||||
LinkerOptionCommand *cmd = (LinkerOptionCommand *)&lc;
|
||||
char *buf = (char *)cmd + sizeof(*cmd);
|
||||
for (i64 i = 0; i < cmd->count; i++) {
|
||||
vec.push_back(buf);
|
||||
buf += vec.back().size() + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
LoadCommand *ObjectFile<E>::find_load_command(Context<E> &ctx, u32 type) {
|
||||
if (!this->mf)
|
||||
return nullptr;
|
||||
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
if (lc.cmd == type)
|
||||
return &lc;
|
||||
p += lc.cmdsize;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Subsection<E> *
|
||||
ObjectFile<E>::find_subsection(Context<E> &ctx, u32 secidx, u32 addr) {
|
||||
Subsection<E> *ret = nullptr;
|
||||
for (Subsection<E> *subsec : subsections)
|
||||
if (subsec->isec.secidx == secidx && subsec->input_addr <= addr)
|
||||
ret = subsec;
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Symbol<E> *ObjectFile<E>::find_symbol(Context<E> &ctx, u32 addr) {
|
||||
for (i64 i = 0; i < mach_syms.size(); i++)
|
||||
if (MachSym &msym = mach_syms[i]; msym.is_extern && msym.value == addr)
|
||||
return this->syms[i];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_compact_unwind(Context<E> &ctx, MachSection &hdr) {
|
||||
if (hdr.size % sizeof(CompactUnwindEntry))
|
||||
Fatal(ctx) << *this << ": invalid __compact_unwind section size";
|
||||
|
||||
i64 num_entries = hdr.size / sizeof(CompactUnwindEntry);
|
||||
unwind_records.reserve(num_entries);
|
||||
|
||||
CompactUnwindEntry *src = (CompactUnwindEntry *)(this->mf->data + hdr.offset);
|
||||
|
||||
// Read compact unwind entries
|
||||
for (i64 i = 0; i < num_entries; i++)
|
||||
unwind_records.emplace_back(src[i].code_len, src[i].encoding);
|
||||
|
||||
// Read relocations
|
||||
MachRel *mach_rels = (MachRel *)(this->mf->data + hdr.reloff);
|
||||
for (i64 i = 0; i < hdr.nreloc; i++) {
|
||||
MachRel &r = mach_rels[i];
|
||||
if (r.offset >= hdr.size)
|
||||
Fatal(ctx) << *this << ": relocation offset too large: " << i;
|
||||
|
||||
i64 idx = r.offset / sizeof(CompactUnwindEntry);
|
||||
UnwindRecord<E> &dst = unwind_records[idx];
|
||||
|
||||
auto error = [&] {
|
||||
Fatal(ctx) << *this << ": __compact_unwind: unsupported relocation: " << i
|
||||
<< " " << *this->syms[r.idx];
|
||||
};
|
||||
|
||||
if (r.is_pcrel || r.p2size != 3 || r.type)
|
||||
error();
|
||||
|
||||
switch (r.offset % sizeof(CompactUnwindEntry)) {
|
||||
case offsetof(CompactUnwindEntry, code_start): {
|
||||
Subsection<E> *target;
|
||||
if (r.is_extern)
|
||||
target = sym_to_subsec[r.idx];
|
||||
else
|
||||
target = find_subsection(ctx, r.idx - 1, src[idx].code_start);
|
||||
|
||||
if (!target)
|
||||
error();
|
||||
|
||||
dst.subsec = target;
|
||||
dst.offset = src[idx].code_start - target->input_addr;
|
||||
break;
|
||||
}
|
||||
case offsetof(CompactUnwindEntry, personality):
|
||||
if (r.is_extern) {
|
||||
dst.personality = this->syms[r.idx];
|
||||
} else {
|
||||
u32 addr = *(ul32 *)((u8 *)this->mf->data + hdr.offset + r.offset);
|
||||
dst.personality = find_symbol(ctx, addr);
|
||||
}
|
||||
|
||||
if (!dst.personality)
|
||||
Fatal(ctx) << *this << ": __compact_unwind: unsupported "
|
||||
<< "personality reference: " << i;
|
||||
break;
|
||||
case offsetof(CompactUnwindEntry, lsda): {
|
||||
u32 addr = *(ul32 *)((u8 *)this->mf->data + hdr.offset + r.offset);
|
||||
|
||||
Subsection<E> *target;
|
||||
if (r.is_extern)
|
||||
target = sym_to_subsec[r.idx];
|
||||
else
|
||||
target = find_subsection(ctx, r.idx - 1, addr);
|
||||
|
||||
if (!target)
|
||||
error();
|
||||
|
||||
dst.lsda = target;
|
||||
dst.lsda_offset = addr - target->input_addr;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
error();
|
||||
}
|
||||
}
|
||||
|
||||
for (i64 i = 0; i < num_entries; i++)
|
||||
if (!unwind_records[i].subsec)
|
||||
Fatal(ctx) << *this << "_: _compact_unwind: missing relocation at " << i;
|
||||
|
||||
// Sort unwind entries by offset
|
||||
sort(unwind_records, [](const UnwindRecord<E> &a, const UnwindRecord<E> &b) {
|
||||
return std::tuple(a.subsec->input_addr, a.offset) <
|
||||
std::tuple(b.subsec->input_addr, b.offset);
|
||||
});
|
||||
|
||||
// Associate unwind entries to subsections
|
||||
for (i64 i = 0; i < num_entries;) {
|
||||
Subsection<E> &subsec = *unwind_records[i].subsec;
|
||||
subsec.unwind_offset = i;
|
||||
|
||||
i64 j = i + 1;
|
||||
while (j < num_entries && unwind_records[j].subsec == &subsec)
|
||||
j++;
|
||||
subsec.nunwind = j - i;
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
// Symbols with higher priorities overwrites symbols with lower priorities.
|
||||
// Here is the list of priorities, from the highest to the lowest.
|
||||
//
|
||||
// 1. Strong defined symbol
|
||||
// 2. Weak defined symbol
|
||||
// 3. Strong defined symbol in a DSO/archive
|
||||
// 4. Weak Defined symbol in a DSO/archive
|
||||
// 5. Common symbol
|
||||
// 6. Common symbol in an archive
|
||||
// 7. Unclaimed (nonexistent) symbol
|
||||
//
|
||||
// Ties are broken by file priority.
|
||||
template <typename E>
|
||||
static u64 get_rank(InputFile<E> *file, bool is_common, bool is_weak) {
|
||||
if (is_common) {
|
||||
assert(!file->is_dylib);
|
||||
if (!file->is_alive)
|
||||
return (6 << 24) + file->priority;
|
||||
return (5 << 24) + file->priority;
|
||||
}
|
||||
|
||||
if (file->is_dylib || !file->is_alive) {
|
||||
if (is_weak)
|
||||
return (4 << 24) + file->priority;
|
||||
return (3 << 24) + file->priority;
|
||||
}
|
||||
|
||||
if (is_weak)
|
||||
return (2 << 24) + file->priority;
|
||||
return (1 << 24) + file->priority;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static u64 get_rank(Symbol<E> &sym) {
|
||||
if (!sym.file)
|
||||
return 7 << 24;
|
||||
return get_rank(sym.file, sym.is_common, sym.is_weak);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
auto is_private_extern = [&](MachSym &msym) {
|
||||
return this->is_hidden || msym.is_private_extern ||
|
||||
((msym.desc & N_WEAK_REF) && (msym.desc & N_WEAK_DEF));
|
||||
};
|
||||
|
||||
auto merge_scope = [&](Symbol<E> &sym, MachSym &msym) {
|
||||
// If at least one symbol defines it as an EXTERN symbol,
|
||||
// the result is an EXTERN symbol instead of PRIVATE_EXTERN,
|
||||
// so that the symbol is exported.
|
||||
if (sym.scope == SCOPE_EXTERN)
|
||||
return SCOPE_EXTERN;
|
||||
return is_private_extern(msym) ? SCOPE_PRIVATE_EXTERN : SCOPE_EXTERN;
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.is_extern || msym.is_undef())
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
bool is_weak = (msym.desc & N_WEAK_DEF);
|
||||
|
||||
sym.scope = merge_scope(sym, msym);
|
||||
|
||||
if (get_rank(this, msym.is_common(), is_weak) < get_rank(sym)) {
|
||||
sym.file = this;
|
||||
sym.is_imported = false;
|
||||
sym.is_weak = is_weak;
|
||||
sym.no_dead_strip = (msym.desc & N_NO_DEAD_STRIP);
|
||||
|
||||
switch (msym.type) {
|
||||
case N_UNDF:
|
||||
assert(msym.is_common());
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
sym.is_common = true;
|
||||
break;
|
||||
case N_ABS:
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
sym.is_common = false;
|
||||
break;
|
||||
case N_SECT:
|
||||
sym.subsec = sym_to_subsec[i];
|
||||
sym.value = msym.value - sym.subsec->input_addr;
|
||||
sym.is_common = false;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << sym << ": unknown symbol type: " << (u64)msym.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool ObjectFile<E>::is_objc_object(Context<E> &ctx) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections)
|
||||
if (isec)
|
||||
if (isec->hdr.match("__DATA", "__objc_catlist") ||
|
||||
(isec->hdr.get_segname() == "__TEXT" &&
|
||||
isec->hdr.get_sectname().starts_with("__swift")))
|
||||
return true;
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++)
|
||||
if (!mach_syms[i].is_undef() && mach_syms[i].is_extern &&
|
||||
this->syms[i]->name.starts_with("_OBJC_CLASS_$_"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void
|
||||
ObjectFile<E>::mark_live_objects(Context<E> &ctx,
|
||||
std::function<void(ObjectFile<E> *)> feeder) {
|
||||
assert(this->is_alive);
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.is_extern)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
if (!sym.file)
|
||||
continue;
|
||||
|
||||
if (msym.is_undef() || (msym.is_common() && !sym.is_common))
|
||||
if (InputFile<E> *file = sym.file)
|
||||
if (!file->is_alive.exchange(true) && !file->is_dylib)
|
||||
feeder((ObjectFile<E> *)file);
|
||||
}
|
||||
|
||||
for (Subsection<E> *subsec : subsections)
|
||||
for (UnwindRecord<E> &rec : subsec->get_unwind_records())
|
||||
if (Symbol<E> *sym = rec.personality)
|
||||
if (InputFile<E> *file = sym->file)
|
||||
if (!file->is_alive.exchange(true) && !file->is_dylib)
|
||||
feeder((ObjectFile<E> *)file);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->mach_syms.size(); i++) {
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
MachSym &msym = mach_syms[i];
|
||||
|
||||
if (sym.file == this && sym.is_common) {
|
||||
InputSection<E> *isec = get_common_sec(ctx);
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = *isec,
|
||||
.input_size = (u32)msym.value,
|
||||
.p2align = (u8)msym.common_p2align,
|
||||
};
|
||||
|
||||
subsections.emplace_back(subsec);
|
||||
|
||||
sym.is_imported = false;
|
||||
sym.is_weak = false;
|
||||
sym.no_dead_strip = (msym.desc & N_NO_DEAD_STRIP);
|
||||
sym.subsec = subsec;
|
||||
sym.value = 0;
|
||||
sym.is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::check_duplicate_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->mach_syms.size(); i++) {
|
||||
Symbol<E> *sym = this->syms[i];
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (sym && sym->file && sym->file != this && !msym.is_undef() &&
|
||||
!msym.is_common() && !(msym.desc & N_WEAK_DEF))
|
||||
Error(ctx) << "duplicate symbol: " << *this << ": " << *sym->file
|
||||
<< ": " << *sym;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E> *ObjectFile<E>::get_common_sec(Context<E> &ctx) {
|
||||
if (!common_sec) {
|
||||
MachSection *hdr = new MachSection;
|
||||
common_hdr.reset(hdr);
|
||||
|
||||
memset(hdr, 0, sizeof(*hdr));
|
||||
hdr->set_segname("__DATA");
|
||||
hdr->set_sectname("__common");
|
||||
hdr->type = S_ZEROFILL;
|
||||
|
||||
common_sec = new InputSection<E>(ctx, *this, *hdr, sections.size());
|
||||
sections.emplace_back(common_sec);
|
||||
}
|
||||
return common_sec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_lto_symbols(Context<E> &ctx) {
|
||||
i64 nsyms = ctx.lto.module_get_num_symbols(this->lto_module);
|
||||
this->syms.reserve(nsyms);
|
||||
this->mach_syms2.reserve(nsyms);
|
||||
|
||||
for (i64 i = 0; i < nsyms; i++) {
|
||||
std::string_view name = ctx.lto.module_get_symbol_name(this->lto_module, i);
|
||||
this->syms.push_back(get_symbol(ctx, name));
|
||||
|
||||
u32 attr = ctx.lto.module_get_symbol_attribute(this->lto_module, i);
|
||||
MachSym msym = {};
|
||||
|
||||
switch (attr & LTO_SYMBOL_DEFINITION_MASK) {
|
||||
case LTO_SYMBOL_DEFINITION_REGULAR:
|
||||
case LTO_SYMBOL_DEFINITION_TENTATIVE:
|
||||
case LTO_SYMBOL_DEFINITION_WEAK:
|
||||
msym.type = N_ABS;
|
||||
break;
|
||||
case LTO_SYMBOL_DEFINITION_UNDEFINED:
|
||||
case LTO_SYMBOL_DEFINITION_WEAKUNDEF:
|
||||
msym.type = N_UNDF;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
switch (attr & LTO_SYMBOL_SCOPE_MASK) {
|
||||
case 0:
|
||||
case LTO_SYMBOL_SCOPE_INTERNAL:
|
||||
case LTO_SYMBOL_SCOPE_HIDDEN:
|
||||
break;
|
||||
case LTO_SYMBOL_SCOPE_DEFAULT:
|
||||
case LTO_SYMBOL_SCOPE_PROTECTED:
|
||||
case LTO_SYMBOL_SCOPE_DEFAULT_CAN_BE_HIDDEN:
|
||||
msym.is_extern = true;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
mach_syms2.push_back(msym);
|
||||
}
|
||||
|
||||
mach_syms = mach_syms2;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::string_view ObjectFile<E>::get_linker_optimization_hints(Context<E> &ctx) {
|
||||
LinkEditDataCommand *cmd =
|
||||
(LinkEditDataCommand *)find_load_command(ctx, LC_LINKER_OPTIMIZATION_HINT);
|
||||
|
||||
if (cmd)
|
||||
return {(char *)this->mf->data + cmd->dataoff, cmd->datasize};
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
DylibFile<E>::DylibFile(Context<E> &ctx, MappedFile<Context<E>> *mf)
|
||||
: InputFile<E>(mf) {
|
||||
this->is_dylib = true;
|
||||
this->is_alive = (ctx.needed_l || !ctx.arg.dead_strip_dylibs);
|
||||
this->is_weak = ctx.weak_l;
|
||||
this->is_reexported = ctx.reexport_l;
|
||||
ctx.dylib_pool.emplace_back(this);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
DylibFile<E> *DylibFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
DylibFile<E> *file = new DylibFile<E>(ctx, mf);
|
||||
file->parse(ctx);
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static MappedFile<Context<E>> *
|
||||
find_external_lib(Context<E> &ctx, std::string_view parent, std::string path) {
|
||||
if (!path.starts_with('/'))
|
||||
return MappedFile<Context<E>>::open(ctx, path);
|
||||
|
||||
for (const std::string &root : ctx.arg.syslibroot) {
|
||||
if (path.ends_with(".tbd")) {
|
||||
if (auto *file = MappedFile<Context<E>>::open(ctx, root + path))
|
||||
return file;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path.ends_with(".dylib")) {
|
||||
std::string stem(path.substr(0, path.size() - 6));
|
||||
if (auto *file = MappedFile<Context<E>>::open(ctx, root + stem + ".tbd"))
|
||||
return file;
|
||||
if (auto *file = MappedFile<Context<E>>::open(ctx, root + path))
|
||||
return file;
|
||||
}
|
||||
|
||||
for (std::string extn : {".tbd", ".dylib"})
|
||||
if (auto *file = MappedFile<Context<E>>::open(ctx, root + path + extn))
|
||||
return file;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse(Context<E> &ctx) {
|
||||
switch (get_file_type(this->mf)) {
|
||||
case FileType::TAPI:
|
||||
parse_tapi(ctx);
|
||||
break;
|
||||
case FileType::MACH_DYLIB:
|
||||
parse_dylib(ctx);
|
||||
break;
|
||||
case FileType::MACH_EXE:
|
||||
parse_dylib(ctx);
|
||||
dylib_idx = BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": is not a dylib";
|
||||
}
|
||||
|
||||
// Read reexported libraries if any
|
||||
for (std::string_view path : reexported_libs) {
|
||||
MappedFile<Context<E>> *mf =
|
||||
find_external_lib(ctx, install_name, std::string(path));
|
||||
if (!mf)
|
||||
Fatal(ctx) << install_name << ": cannot open reexported library " << path;
|
||||
|
||||
DylibFile<E> *child = DylibFile<E>::create(ctx, mf);
|
||||
exports.merge(child->exports);
|
||||
weak_exports.merge(child->weak_exports);
|
||||
}
|
||||
|
||||
// Initialize syms and is_weak_symbols vectors
|
||||
for (std::string_view s : exports) {
|
||||
this->syms.push_back(get_symbol(ctx, s));
|
||||
is_weak_symbol.push_back(false);
|
||||
}
|
||||
|
||||
for (std::string_view s : weak_exports) {
|
||||
if (!exports.contains(s)) {
|
||||
this->syms.push_back(get_symbol(ctx, s));
|
||||
is_weak_symbol.push_back(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::read_trie(Context<E> &ctx, u8 *start, i64 offset,
|
||||
const std::string &prefix) {
|
||||
u8 *buf = start + offset;
|
||||
|
||||
if (*buf) {
|
||||
read_uleb(buf); // size
|
||||
i64 flags = read_uleb(buf) & ~EXPORT_SYMBOL_FLAGS_KIND_MASK;
|
||||
read_uleb(buf); // addr
|
||||
|
||||
std::string_view name = save_string(ctx, prefix);
|
||||
if (flags & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION)
|
||||
weak_exports.insert(name);
|
||||
else
|
||||
exports.insert(name);
|
||||
|
||||
if (flags & EXPORT_SYMBOL_FLAGS_REEXPORT)
|
||||
read_uleb(buf); // skip a library ordinal
|
||||
} else {
|
||||
buf++;
|
||||
}
|
||||
|
||||
i64 nchild = *buf++;
|
||||
|
||||
for (i64 i = 0; i < nchild; i++) {
|
||||
std::string suffix((char *)buf);
|
||||
buf += suffix.size() + 1;
|
||||
i64 off = read_uleb(buf);
|
||||
read_trie(ctx, start, off, prefix + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse_tapi(Context<E> &ctx) {
|
||||
TextDylib tbd = parse_tbd(ctx, this->mf);
|
||||
|
||||
install_name = tbd.install_name;
|
||||
reexported_libs = std::move(tbd.reexported_libs);
|
||||
exports = std::move(tbd.exports);
|
||||
weak_exports = std::move(tbd.weak_exports);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse_dylib(Context<E> &ctx) {
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
if (ctx.arg.application_extension && !(hdr.flags & MH_APP_EXTENSION_SAFE))
|
||||
Warn(ctx) << "linking against a dylib which is not safe for use in "
|
||||
<< "application extensions: " << *this;
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
|
||||
switch (lc.cmd) {
|
||||
case LC_ID_DYLIB: {
|
||||
DylibCommand &cmd = *(DylibCommand *)p;
|
||||
install_name = (char *)p + cmd.nameoff;
|
||||
break;
|
||||
}
|
||||
case LC_DYLD_INFO_ONLY: {
|
||||
DyldInfoCommand &cmd = *(DyldInfoCommand *)p;
|
||||
if (cmd.export_off)
|
||||
read_trie(ctx, this->mf->data + cmd.export_off);
|
||||
break;
|
||||
}
|
||||
case LC_DYLD_EXPORTS_TRIE: {
|
||||
LinkEditDataCommand &cmd = *(LinkEditDataCommand *)p;
|
||||
read_trie(ctx, this->mf->data + cmd.dataoff);
|
||||
break;
|
||||
}
|
||||
case LC_REEXPORT_DYLIB:
|
||||
if (!(hdr.flags & MH_NO_REEXPORTED_DYLIBS)) {
|
||||
DylibCommand &cmd = *(DylibCommand *)p;
|
||||
reexported_libs.push_back((char *)p + cmd.nameoff);
|
||||
}
|
||||
break;
|
||||
}
|
||||
p += lc.cmdsize;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
if (get_rank(this, false, false) < get_rank(sym)) {
|
||||
sym.file = this;
|
||||
sym.scope = SCOPE_LOCAL;
|
||||
sym.is_imported = true;
|
||||
sym.is_weak = (this->is_weak || is_weak_symbol[i]);
|
||||
sym.no_dead_strip = false;
|
||||
sym.subsec = nullptr;
|
||||
sym.value = 0;
|
||||
sym.is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class InputFile<E>; \
|
||||
template class ObjectFile<E>; \
|
||||
template class DylibFile<E>; \
|
||||
template std::ostream &operator<<(std::ostream &, const InputFile<E> &)
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -1,72 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
OutputSection<E> &get_output_section(Context<E> &ctx, const MachSection &hdr) {
|
||||
static std::unordered_set<std::string_view> set = {
|
||||
"__got", "__auth_got", "__auth_ptr", "__nl_symbol_ptr", "__const",
|
||||
"__cfstring", "__mod_init_func", "__mod_term_func", "__objc_classlist",
|
||||
"__objc_nlclslist", "__objc_catlist", "__objc_nlcatlist", "__objc_protolist",
|
||||
};
|
||||
|
||||
std::string_view seg = hdr.get_segname();
|
||||
std::string_view sect = hdr.get_sectname();
|
||||
|
||||
if (seg == "__DATA" && set.contains(sect)) {
|
||||
seg = "__DATA_CONST";
|
||||
} else if (seg == "__TEXT" && sect == "__StaticInit") {
|
||||
sect = "__text";
|
||||
}
|
||||
|
||||
return *OutputSection<E>::get_instance(ctx, seg, sect);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
const MachSection &hdr, u32 secidx)
|
||||
: file(file), hdr(hdr), secidx(secidx), osec(get_output_section(ctx, hdr)) {
|
||||
if (hdr.type != S_ZEROFILL)
|
||||
contents = file.mf->get_contents().substr(hdr.offset, hdr.size);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::parse_relocations(Context<E> &ctx) {
|
||||
// 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 this relocation section refers to
|
||||
auto cmp = [](Subsection<E> *subsec, u32 addr) {
|
||||
return subsec->input_addr < addr;
|
||||
};
|
||||
|
||||
auto begin = std::lower_bound(file.subsections.begin(),
|
||||
file.subsections.end(), hdr.addr, cmp);
|
||||
auto end = std::lower_bound(begin, file.subsections.end(),
|
||||
hdr.addr + hdr.size, cmp);
|
||||
|
||||
// Assign each subsection a group of relocations
|
||||
i64 i = 0;
|
||||
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_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -1,164 +0,0 @@
|
||||
#include "lto.h"
|
||||
#include "mold.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
LTOPlugin::~LTOPlugin() {
|
||||
if (dlopen_handle)
|
||||
dlclose(dlopen_handle);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void do_load_plugin(Context<E> &ctx) {
|
||||
void *handle = dlopen(ctx.arg.lto_library.c_str(), RTLD_NOW | RTLD_GLOBAL);
|
||||
if (!handle)
|
||||
Fatal(ctx) << "could not open plugin file: " << dlerror();
|
||||
|
||||
ctx.lto.dlopen_handle = handle;
|
||||
|
||||
// I don't want to use a macro, but I couldn't find an easy way to
|
||||
// write the same thing without a macro.
|
||||
#define DLSYM(x) ctx.lto.x = (decltype(ctx.lto.x))dlsym(handle, "lto_" #x)
|
||||
DLSYM(get_version);
|
||||
DLSYM(get_error_message);
|
||||
DLSYM(module_is_object_file);
|
||||
DLSYM(module_is_object_file_for_target);
|
||||
DLSYM(module_has_objc_category);
|
||||
DLSYM(module_is_object_file_in_memory);
|
||||
DLSYM(module_is_object_file_in_memory_for_target);
|
||||
DLSYM(module_create);
|
||||
DLSYM(module_create_from_memory);
|
||||
DLSYM(module_create_from_memory_with_path);
|
||||
DLSYM(module_create_in_local_context);
|
||||
DLSYM(module_create_in_codegen_context);
|
||||
DLSYM(module_create_from_fd);
|
||||
DLSYM(module_create_from_fd_at_offset);
|
||||
DLSYM(module_dispose);
|
||||
DLSYM(module_get_target_triple);
|
||||
DLSYM(module_set_target_triple);
|
||||
DLSYM(module_get_num_symbols);
|
||||
DLSYM(module_get_symbol_name);
|
||||
DLSYM(module_get_symbol_attribute);
|
||||
DLSYM(module_get_linkeropts);
|
||||
DLSYM(module_get_macho_cputype);
|
||||
DLSYM(module_has_ctor_dtor);
|
||||
DLSYM(codegen_set_diagnostic_handler);
|
||||
DLSYM(codegen_create);
|
||||
DLSYM(codegen_create_in_local_context);
|
||||
DLSYM(codegen_dispose);
|
||||
DLSYM(codegen_add_module);
|
||||
DLSYM(codegen_set_module);
|
||||
DLSYM(codegen_set_debug_model);
|
||||
DLSYM(codegen_set_pic_model);
|
||||
DLSYM(codegen_set_cpu);
|
||||
DLSYM(codegen_set_assembler_path);
|
||||
DLSYM(codegen_set_assembler_args);
|
||||
DLSYM(codegen_add_must_preserve_symbol);
|
||||
DLSYM(codegen_write_merged_modules);
|
||||
DLSYM(codegen_compile);
|
||||
DLSYM(codegen_compile_to_file);
|
||||
DLSYM(codegen_optimize);
|
||||
DLSYM(codegen_compile_optimized);
|
||||
DLSYM(api_version);
|
||||
DLSYM(set_debug_options);
|
||||
DLSYM(codegen_debug_options);
|
||||
DLSYM(codegen_debug_options_array);
|
||||
DLSYM(initialize_disassembler);
|
||||
DLSYM(codegen_set_should_internalize);
|
||||
DLSYM(codegen_set_should_embed_uselists);
|
||||
#undef DLSYM
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void load_lto_plugin(Context<E> &ctx) {
|
||||
std::call_once(ctx.lto_plugin_loaded, [&]() { do_load_plugin(ctx); });
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void do_lto(Context<E> &ctx) {
|
||||
LTOCodeGen *cg = ctx.lto.codegen_create();
|
||||
|
||||
// Add bitcode files to CodeGen.
|
||||
for (const std::string &opt : ctx.arg.mllvm)
|
||||
ctx.lto.codegen_debug_options(cg, opt.c_str());
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->lto_module)
|
||||
ctx.lto.codegen_add_module(cg, file->lto_module);
|
||||
|
||||
// Mark symbols that have to be preserved. All symbols that are not
|
||||
// marked here may be internalized and deleted as an extenrally-
|
||||
// visible symbol.
|
||||
if (ctx.output_type == MH_DYLIB || ctx.arg.export_dynamic) {
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (!file->lto_module) {
|
||||
for (i64 i = 0; i < file->mach_syms.size(); i++) {
|
||||
MachSym &msym = file->mach_syms[i];
|
||||
Symbol<E> &sym = *file->syms[i];
|
||||
if (msym.is_undef() && !sym.file->is_dylib &&
|
||||
((ObjectFile<E> *)sym.file)->lto_module)
|
||||
ctx.lto.codegen_add_must_preserve_symbol(cg, sym.name.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->lto_module)
|
||||
for (Symbol<E> *sym : file->syms)
|
||||
if (sym->file == file && sym->scope != SCOPE_LOCAL)
|
||||
ctx.lto.codegen_add_must_preserve_symbol(cg, sym->name.data());
|
||||
}
|
||||
|
||||
if (ctx.arg.entry->file)
|
||||
ctx.lto.codegen_add_must_preserve_symbol(cg, ctx.arg.entry->name.data());
|
||||
|
||||
// Run the compiler backend to do LTO.
|
||||
size_t size;
|
||||
u8 *data = (u8 *)ctx.lto.codegen_compile(cg, &size);
|
||||
if (!data)
|
||||
Fatal(ctx) << "lto_codegen_compile failed: " << ctx.lto.get_error_message();
|
||||
|
||||
if (!ctx.arg.object_path_lto.empty()) {
|
||||
FILE *out = fopen(ctx.arg.object_path_lto.c_str(), "w");
|
||||
if (!out)
|
||||
Fatal(ctx) << "-object_path_lto: cannot open " << ctx.arg.object_path_lto
|
||||
<< ": " << errno_string();
|
||||
fwrite(data, size, 1, out);
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
// Remove bitcode object files from ctx.objs.
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file->lto_module) {
|
||||
file->clear_symbols();
|
||||
file->is_alive = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a result of LTO as a new object file.
|
||||
MappedFile<Context<E>> *mf = new MappedFile<Context<E>>;
|
||||
mf->name = "<LTO>";
|
||||
mf->data = data;
|
||||
mf->size = size;
|
||||
ctx.mf_pool.emplace_back(mf);
|
||||
|
||||
ObjectFile<E> *obj = ObjectFile<E>::create(ctx, mf, "");
|
||||
obj->parse(ctx);
|
||||
obj->is_alive = true;
|
||||
obj->priority = 100; // regular file starts with priority 10,000
|
||||
obj->resolve_symbols(ctx);
|
||||
ctx.objs.push_back(obj);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void load_lto_plugin(Context<E> &); \
|
||||
template void do_lto(Context<E> &);
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -1,26 +0,0 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "lto.h"
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
LTOPlugin::~LTOPlugin() {}
|
||||
|
||||
template <typename E>
|
||||
void load_lto_plugin(Context<E> &ctx) {
|
||||
Fatal(ctx) << "LTO is not supported on Windows";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void do_lto(Context<E> &ctx) {}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void load_lto_plugin(Context<E> &); \
|
||||
template void do_lto(Context<E> &);
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
|
||||
#endif
|
||||
-176
@@ -1,176 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
enum LTOSymbolAttributes {
|
||||
LTO_SYMBOL_ALIGNMENT_MASK = 0x0000001F,
|
||||
LTO_SYMBOL_PERMISSIONS_MASK = 0x000000E0,
|
||||
LTO_SYMBOL_PERMISSIONS_CODE = 0x000000A0,
|
||||
LTO_SYMBOL_PERMISSIONS_DATA = 0x000000C0,
|
||||
LTO_SYMBOL_PERMISSIONS_RODATA = 0x00000080,
|
||||
LTO_SYMBOL_DEFINITION_MASK = 0x00000700,
|
||||
LTO_SYMBOL_DEFINITION_REGULAR = 0x00000100,
|
||||
LTO_SYMBOL_DEFINITION_TENTATIVE = 0x00000200,
|
||||
LTO_SYMBOL_DEFINITION_WEAK = 0x00000300,
|
||||
LTO_SYMBOL_DEFINITION_UNDEFINED = 0x00000400,
|
||||
LTO_SYMBOL_DEFINITION_WEAKUNDEF = 0x00000500,
|
||||
LTO_SYMBOL_SCOPE_MASK = 0x00003800,
|
||||
LTO_SYMBOL_SCOPE_INTERNAL = 0x00000800,
|
||||
LTO_SYMBOL_SCOPE_HIDDEN = 0x00001000,
|
||||
LTO_SYMBOL_SCOPE_DEFAULT = 0x00001800,
|
||||
LTO_SYMBOL_SCOPE_PROTECTED = 0x00002000,
|
||||
LTO_SYMBOL_SCOPE_DEFAULT_CAN_BE_HIDDEN = 0x00002800,
|
||||
LTO_SYMBOL_COMDAT = 0x00004000,
|
||||
LTO_SYMBOL_ALIAS = 0x00008000,
|
||||
};
|
||||
|
||||
enum LTODebugModel {
|
||||
LTO_DEBUG_MODEL_NONE = 0,
|
||||
LTO_DEBUG_MODEL_DWARF = 1,
|
||||
};
|
||||
|
||||
enum LTOCodegenModel {
|
||||
LTO_CODEGEN_PIC_MODEL_STATIC = 0,
|
||||
LTO_CODEGEN_PIC_MODEL_DYNAMIC = 1,
|
||||
LTO_CODEGEN_PIC_MODEL_DYNAMIC_NO_PIC = 2,
|
||||
LTO_CODEGEN_PIC_MODEL_DEFAULT = 3,
|
||||
};
|
||||
|
||||
enum LTOCodegenDiagnosticSeverity {
|
||||
LTO_DS_ERROR = 0,
|
||||
LTO_DS_WARNING = 1,
|
||||
LTO_DS_REMARK = 3,
|
||||
LTO_DS_NOTE = 2,
|
||||
};
|
||||
|
||||
struct LTOObjectBuffer {
|
||||
const char *buffer;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
using LTOModule = void;
|
||||
using LTOCodeGen = void;
|
||||
|
||||
typedef void LTODiagnosticHandler(LTOCodegenDiagnosticSeverity severity,
|
||||
const char *diag, void *ctxt);
|
||||
|
||||
struct LTOPlugin {
|
||||
void *dlopen_handle = nullptr;
|
||||
~LTOPlugin();
|
||||
|
||||
char (*get_version)();
|
||||
|
||||
char (*get_error_message)();
|
||||
|
||||
bool (*module_is_object_file)(const char *path);
|
||||
|
||||
bool (*module_is_object_file_for_target)(const char *path,
|
||||
const char *target_triple_prefix);
|
||||
|
||||
bool (*module_has_objc_category)(const void *mem, size_t length);
|
||||
|
||||
bool (*module_is_object_file_in_memory)(const void *mem, size_t length);
|
||||
|
||||
bool (*module_is_object_file_in_memory_for_target)(
|
||||
const void *mem, size_t length, const char *target_triple_prefix);
|
||||
|
||||
LTOModule *(*module_create)(const char *path);
|
||||
|
||||
LTOModule *(*module_create_from_memory)(const void *mem, size_t length);
|
||||
|
||||
LTOModule *(*module_create_from_memory_with_path)(const void *mem,
|
||||
size_t length,
|
||||
const char *path);
|
||||
|
||||
LTOModule *(*module_create_in_local_context)(const void *mem, size_t length,
|
||||
const char *path);
|
||||
|
||||
LTOModule *(*module_create_in_codegen_context)(const void *mem, size_t length,
|
||||
const char *path,
|
||||
LTOCodeGen *cg);
|
||||
|
||||
LTOModule *(*module_create_from_fd)(int fd, const char *path,
|
||||
size_t file_size);
|
||||
|
||||
LTOModule *(*module_create_from_fd_at_offset)(int fd, const char *path,
|
||||
size_t file_size,
|
||||
size_t map_size, uint64_t offset);
|
||||
|
||||
void (*module_dispose)(LTOModule *mod);
|
||||
|
||||
const char *(*module_get_target_triple)(LTOModule *mod);
|
||||
|
||||
void (*module_set_target_triple)(LTOModule *mod, const char *triple);
|
||||
|
||||
unsigned (*module_get_num_symbols)(LTOModule *mod);
|
||||
|
||||
const char *(*module_get_symbol_name)(LTOModule *mod, unsigned index);
|
||||
|
||||
uint32_t (*module_get_symbol_attribute)(LTOModule *mod, unsigned index);
|
||||
|
||||
const char *(*module_get_linkeropts)(LTOModule *mod);
|
||||
|
||||
bool (*module_get_macho_cputype)(LTOModule *mod, unsigned *out_cputype,
|
||||
unsigned *out_cpusubtype);
|
||||
|
||||
bool (*module_has_ctor_dtor)(LTOModule *mod);
|
||||
|
||||
void (*codegen_set_diagnostic_handler)(LTOCodeGen *cg, LTODiagnosticHandler,
|
||||
void *);
|
||||
|
||||
LTOCodeGen *(*codegen_create)();
|
||||
|
||||
LTOCodeGen *(*codegen_create_in_local_context)();
|
||||
|
||||
void (*codegen_dispose)(LTOCodeGen *cg);
|
||||
|
||||
bool (*codegen_add_module)(LTOCodeGen *cg, LTOModule *mod);
|
||||
|
||||
void (*codegen_set_module)(LTOCodeGen *cg, LTOModule *mod);
|
||||
|
||||
bool (*codegen_set_debug_model)(LTOCodeGen *cg, LTODebugModel);
|
||||
|
||||
bool (*codegen_set_pic_model)(LTOCodeGen *cg, LTOCodegenModel);
|
||||
|
||||
void (*codegen_set_cpu)(LTOCodeGen *cg, const char *cpu);
|
||||
|
||||
void (*codegen_set_assembler_path)(LTOCodeGen *cg, const char *path);
|
||||
|
||||
void (*codegen_set_assembler_args)(LTOCodeGen *cg, const char **args,
|
||||
int nargs);
|
||||
|
||||
void (*codegen_add_must_preserve_symbol)(LTOCodeGen *cg, const char *symbol);
|
||||
|
||||
bool (*codegen_write_merged_modules)(LTOCodeGen *cg, const char *path);
|
||||
|
||||
const void *(*codegen_compile)(LTOCodeGen *cg, size_t *length);
|
||||
|
||||
bool (*codegen_compile_to_file)(LTOCodeGen *cg, const char **name);
|
||||
|
||||
bool (*codegen_optimize)(LTOCodeGen *cg);
|
||||
|
||||
const void *(*codegen_compile_optimized)(LTOCodeGen *cg, size_t *length);
|
||||
|
||||
unsigned (*api_version)();
|
||||
|
||||
void (*set_debug_options)(const char *const *options, int number);
|
||||
|
||||
void (*codegen_debug_options)(LTOCodeGen *cg, const char *);
|
||||
|
||||
void (*codegen_debug_options_array)(LTOCodeGen *cg, const char *const *,
|
||||
int number);
|
||||
|
||||
void (*initialize_disassembler)();
|
||||
|
||||
void (*codegen_set_should_internalize)(LTOCodeGen *cg,
|
||||
bool should_internalize);
|
||||
|
||||
void (*codegen_set_should_embed_uselists)(LTOCodeGen *cg,
|
||||
bool should_embed_use_lists);
|
||||
};
|
||||
|
||||
} // namespace mold::macho
|
||||
-781
@@ -1,781 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../inttypes.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#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;
|
||||
|
||||
struct ARM64;
|
||||
struct X86_64;
|
||||
|
||||
template <typename E>
|
||||
std::string rel_to_string(u8 r_type);
|
||||
|
||||
static constexpr u32 FAT_MAGIC = 0xcafebabe;
|
||||
|
||||
static constexpr u32 MH_OBJECT = 0x1;
|
||||
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 = 0x1;
|
||||
static constexpr u32 S_ATTR_EXT_RELOC = 0x2;
|
||||
static constexpr u32 S_ATTR_SOME_INSTRUCTIONS = 0x4;
|
||||
|
||||
static constexpr u32 S_ATTR_DEBUG = 0x20000;
|
||||
static constexpr u32 S_ATTR_SELF_MODIFYING_CODE = 0x40000;
|
||||
static constexpr u32 S_ATTR_LIVE_SUPPORT = 0x80000;
|
||||
static constexpr u32 S_ATTR_NO_DEAD_STRIP = 0x100000;
|
||||
static constexpr u32 S_ATTR_STRIP_STATIC_SYMS = 0x200000;
|
||||
static constexpr u32 S_ATTR_NO_TOC = 0x400000;
|
||||
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 i32 BIND_SPECIAL_DYLIB_SELF = 0;
|
||||
static constexpr i32 BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE = -1;
|
||||
static constexpr i32 BIND_SPECIAL_DYLIB_FLAT_LOOKUP = -2;
|
||||
static constexpr i32 BIND_SPECIAL_DYLIB_WEAK_LOOKUP = -3;
|
||||
|
||||
static constexpr u32 BIND_SYMBOL_FLAGS_WEAK_IMPORT = 0x1;
|
||||
static constexpr u32 BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION = 0x8;
|
||||
|
||||
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 SELF_LIBRARY_ORDINAL = 0x0;
|
||||
static constexpr u32 MAX_LIBRARY_ORDINAL = 0xfd;
|
||||
static constexpr u32 DYNAMIC_LOOKUP_ORDINAL = 0xfe;
|
||||
static constexpr u32 EXECUTABLE_ORDINAL = 0xff;
|
||||
|
||||
static constexpr u32 N_NO_DEAD_STRIP = 0x0020;
|
||||
static constexpr u32 N_DESC_DISCARDED = 0x0020;
|
||||
static constexpr u32 N_WEAK_REF = 0x0040;
|
||||
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 TOOL_MOLD = 54321; // Randomly chosen!
|
||||
|
||||
static constexpr u32 OBJC_IMAGE_SUPPORTS_GC = 1 << 1;
|
||||
static constexpr u32 OBJC_IMAGE_REQUIRES_GC = 1 << 2;
|
||||
static constexpr u32 OBJC_IMAGE_OPTIMIZED_BY_DYLD = 1 << 3;
|
||||
static constexpr u32 OBJC_IMAGE_SUPPORTS_COMPACTION = 1 << 4;
|
||||
static constexpr u32 OBJC_IMAGE_IS_SIMULATED = 1 << 5;
|
||||
static constexpr u32 OBJC_IMAGE_HAS_CATEGORY_CLASS_PROPERTIES = 1 << 6;
|
||||
|
||||
static constexpr u32 LOH_ARM64_ADRP_ADRP = 1;
|
||||
static constexpr u32 LOH_ARM64_ADRP_LDR = 2;
|
||||
static constexpr u32 LOH_ARM64_ADRP_ADD_LDR = 3;
|
||||
static constexpr u32 LOH_ARM64_ADRP_LDR_GOT_LDR = 4;
|
||||
static constexpr u32 LOH_ARM64_ADRP_ADD_STR = 5;
|
||||
static constexpr u32 LOH_ARM64_ADRP_LDR_GOT_STR = 6;
|
||||
static constexpr u32 LOH_ARM64_ADRP_ADD = 7;
|
||||
static constexpr u32 LOH_ARM64_ADRP_LDR_GOT = 8;
|
||||
|
||||
static constexpr u32 ARM64_RELOC_UNSIGNED = 0;
|
||||
static constexpr u32 ARM64_RELOC_SUBTRACTOR = 1;
|
||||
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;
|
||||
|
||||
template <>
|
||||
inline std::string rel_to_string<ARM64>(u8 type) {
|
||||
switch (type) {
|
||||
case ARM64_RELOC_UNSIGNED: return "ARM64_RELOC_UNSIGNED";
|
||||
case ARM64_RELOC_SUBTRACTOR: return "ARM64_RELOC_SUBTRACTOR";
|
||||
case ARM64_RELOC_BRANCH26: return "ARM64_RELOC_BRANCH26";
|
||||
case ARM64_RELOC_PAGE21: return "ARM64_RELOC_PAGE21";
|
||||
case ARM64_RELOC_PAGEOFF12: return "ARM64_RELOC_PAGEOFF12";
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21: return "ARM64_RELOC_GOT_LOAD_PAGE21";
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12: return "ARM64_RELOC_GOT_LOAD_PAGEOFF12";
|
||||
case ARM64_RELOC_POINTER_TO_GOT: return "ARM64_RELOC_POINTER_TO_GOT";
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21: return "ARM64_RELOC_TLVP_LOAD_PAGE21";
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12: return "ARM64_RELOC_TLVP_LOAD_PAGEOFF12";
|
||||
case ARM64_RELOC_ADDEND: return "ARM64_RELOC_ADDEND";
|
||||
}
|
||||
return "unknown (" + std::to_string(type) + ")";
|
||||
}
|
||||
|
||||
static constexpr u32 X86_64_RELOC_UNSIGNED = 0;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED = 1;
|
||||
static constexpr u32 X86_64_RELOC_BRANCH = 2;
|
||||
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;
|
||||
|
||||
template <>
|
||||
inline std::string rel_to_string<X86_64>(u8 type) {
|
||||
switch (type) {
|
||||
case X86_64_RELOC_UNSIGNED: return "X86_64_RELOC_UNSIGNED";
|
||||
case X86_64_RELOC_SIGNED: return "X86_64_RELOC_SIGNED";
|
||||
case X86_64_RELOC_BRANCH: return "X86_64_RELOC_BRANCH";
|
||||
case X86_64_RELOC_GOT_LOAD: return "X86_64_RELOC_GOT_LOAD";
|
||||
case X86_64_RELOC_GOT: return "X86_64_RELOC_GOT";
|
||||
case X86_64_RELOC_SUBTRACTOR: return "X86_64_RELOC_SUBTRACTOR";
|
||||
case X86_64_RELOC_SIGNED_1: return "X86_64_RELOC_SIGNED_1";
|
||||
case X86_64_RELOC_SIGNED_2: return "X86_64_RELOC_SIGNED_2";
|
||||
case X86_64_RELOC_SIGNED_4: return "X86_64_RELOC_SIGNED_4";
|
||||
case X86_64_RELOC_TLV: return "X86_64_RELOC_TLV";
|
||||
}
|
||||
return "unknown (" + std::to_string(type) + ")";
|
||||
}
|
||||
|
||||
struct FatHeader {
|
||||
ub32 magic;
|
||||
ub32 nfat_arch;
|
||||
};
|
||||
|
||||
struct FatArch {
|
||||
ub32 cputype;
|
||||
ub32 cpusubtype;
|
||||
ub32 offset;
|
||||
ub32 size;
|
||||
ub32 align;
|
||||
};
|
||||
|
||||
struct MachHeader {
|
||||
ul32 magic;
|
||||
ul32 cputype;
|
||||
ul32 cpusubtype;
|
||||
ul32 filetype;
|
||||
ul32 ncmds;
|
||||
ul32 sizeofcmds;
|
||||
ul32 flags;
|
||||
ul32 reserved;
|
||||
};
|
||||
|
||||
struct LoadCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
};
|
||||
|
||||
struct SegmentCommand {
|
||||
std::string_view get_segname() const {
|
||||
return {segname, strnlen(segname, sizeof(segname))};
|
||||
}
|
||||
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
char segname[16];
|
||||
ul64 vmaddr;
|
||||
ul64 vmsize;
|
||||
ul64 fileoff;
|
||||
ul64 filesize;
|
||||
ul32 maxprot;
|
||||
ul32 initprot;
|
||||
ul32 nsects;
|
||||
ul32 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];
|
||||
ul64 addr;
|
||||
ul64 size;
|
||||
ul32 offset;
|
||||
ul32 p2align;
|
||||
ul32 reloff;
|
||||
ul32 nreloc;
|
||||
u8 type;
|
||||
ul24 attr;
|
||||
ul32 reserved1;
|
||||
ul32 reserved2;
|
||||
ul32 reserved3;
|
||||
};
|
||||
|
||||
struct DylibCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 nameoff;
|
||||
ul32 timestamp;
|
||||
ul32 current_version;
|
||||
ul32 compatibility_version;
|
||||
};
|
||||
|
||||
struct DylinkerCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 nameoff;
|
||||
};
|
||||
|
||||
struct SymtabCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 symoff;
|
||||
ul32 nsyms;
|
||||
ul32 stroff;
|
||||
ul32 strsize;
|
||||
};
|
||||
|
||||
struct DysymtabCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 ilocalsym;
|
||||
ul32 nlocalsym;
|
||||
ul32 iextdefsym;
|
||||
ul32 nextdefsym;
|
||||
ul32 iundefsym;
|
||||
ul32 nundefsym;
|
||||
ul32 tocoff;
|
||||
ul32 ntoc;
|
||||
ul32 modtaboff;
|
||||
ul32 nmodtab;
|
||||
ul32 extrefsymoff;
|
||||
ul32 nextrefsyms;
|
||||
ul32 indirectsymoff;
|
||||
ul32 nindirectsyms;
|
||||
ul32 extreloff;
|
||||
ul32 nextrel;
|
||||
ul32 locreloff;
|
||||
ul32 nlocrel;
|
||||
};
|
||||
|
||||
struct VersionMinCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 version;
|
||||
ul32 sdk;
|
||||
};
|
||||
|
||||
struct DyldInfoCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 rebase_off;
|
||||
ul32 rebase_size;
|
||||
ul32 bind_off;
|
||||
ul32 bind_size;
|
||||
ul32 weak_bind_off;
|
||||
ul32 weak_bind_size;
|
||||
ul32 lazy_bind_off;
|
||||
ul32 lazy_bind_size;
|
||||
ul32 export_off;
|
||||
ul32 export_size;
|
||||
};
|
||||
|
||||
struct UUIDCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
u8 uuid[16];
|
||||
};
|
||||
|
||||
struct RpathCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 path_off;
|
||||
};
|
||||
|
||||
struct LinkEditDataCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 dataoff;
|
||||
ul32 datasize;
|
||||
};
|
||||
|
||||
struct BuildToolVersion {
|
||||
ul32 tool;
|
||||
ul32 version;
|
||||
};
|
||||
|
||||
struct BuildVersionCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 platform;
|
||||
ul32 minos;
|
||||
ul32 sdk;
|
||||
ul32 ntools;
|
||||
};
|
||||
|
||||
struct EntryPointCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul64 entryoff;
|
||||
ul64 stacksize;
|
||||
};
|
||||
|
||||
struct SourceVersionCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul64 version;
|
||||
};
|
||||
|
||||
struct DataInCodeEntry {
|
||||
ul32 offset;
|
||||
ul16 length;
|
||||
ul16 kind;
|
||||
};
|
||||
|
||||
struct LinkerOptionCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 count;
|
||||
};
|
||||
|
||||
// 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 && is_extern && value;
|
||||
}
|
||||
|
||||
ul32 stroff;
|
||||
|
||||
union {
|
||||
u8 n_type;
|
||||
struct {
|
||||
u8 is_extern : 1;
|
||||
u8 type : 3;
|
||||
u8 is_private_extern : 1;
|
||||
u8 stab : 3;
|
||||
};
|
||||
};
|
||||
|
||||
u8 sect;
|
||||
|
||||
union {
|
||||
ul16 desc;
|
||||
struct {
|
||||
u8 padding;
|
||||
u8 common_p2align : 4;
|
||||
};
|
||||
};
|
||||
|
||||
ul64 value;
|
||||
};
|
||||
|
||||
// This struct is named `relocation_info` on BSD and macOS.
|
||||
struct MachRel {
|
||||
ul32 offset;
|
||||
ul24 idx;
|
||||
u8 is_pcrel : 1;
|
||||
u8 p2size : 2;
|
||||
u8 is_extern : 1;
|
||||
u8 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 {
|
||||
ul32 version;
|
||||
ul32 encoding_offset;
|
||||
ul32 encoding_count;
|
||||
ul32 personality_offset;
|
||||
ul32 personality_count;
|
||||
ul32 page_offset;
|
||||
ul32 page_count;
|
||||
};
|
||||
|
||||
struct UnwindFirstLevelPage {
|
||||
ul32 func_addr;
|
||||
ul32 page_offset;
|
||||
ul32 lsda_offset;
|
||||
};
|
||||
|
||||
struct UnwindSecondLevelPage {
|
||||
ul32 kind;
|
||||
ul16 page_offset;
|
||||
ul16 page_count;
|
||||
ul16 encoding_offset;
|
||||
ul16 encoding_count;
|
||||
};
|
||||
|
||||
struct UnwindLsdaEntry {
|
||||
ul32 func_addr;
|
||||
ul32 lsda_addr;
|
||||
};
|
||||
|
||||
struct UnwindPageEntry {
|
||||
ul24 func_addr;
|
||||
u8 encoding;
|
||||
};
|
||||
|
||||
// __LD,__compact_unwind section contents
|
||||
|
||||
struct CompactUnwindEntry {
|
||||
ul64 code_start;
|
||||
ul32 code_len;
|
||||
ul32 encoding;
|
||||
ul64 personality;
|
||||
ul64 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 {
|
||||
ub32 magic;
|
||||
ub32 length;
|
||||
ub32 count;
|
||||
};
|
||||
|
||||
struct CodeSignatureBlobIndex {
|
||||
ub32 type;
|
||||
ub32 offset;
|
||||
ub32 padding;
|
||||
};
|
||||
|
||||
struct CodeSignatureDirectory {
|
||||
ub32 magic;
|
||||
ub32 length;
|
||||
ub32 version;
|
||||
ub32 flags;
|
||||
ub32 hash_offset;
|
||||
ub32 ident_offset;
|
||||
ub32 n_special_slots;
|
||||
ub32 n_code_slots;
|
||||
ub32 code_limit;
|
||||
u8 hash_size;
|
||||
u8 hash_type;
|
||||
u8 platform;
|
||||
u8 page_size;
|
||||
ub32 spare2;
|
||||
ub32 scatter_offset;
|
||||
ub32 team_offset;
|
||||
ub32 spare3;
|
||||
ub64 code_limit64;
|
||||
ub64 exec_seg_base;
|
||||
ub64 exec_seg_limit;
|
||||
ub64 exec_seg_flags;
|
||||
};
|
||||
|
||||
// __DATA,__objc_imageinfo
|
||||
struct ObjcImageInfo {
|
||||
ul32 version = 0;
|
||||
u8 flags = 0;
|
||||
u8 swift_version = 0;
|
||||
ul16 swift_lang_version = 0;
|
||||
};
|
||||
|
||||
struct ARM64 {
|
||||
static constexpr u32 cputype = CPU_TYPE_ARM64;
|
||||
static constexpr u32 cpusubtype = CPU_SUBTYPE_ARM64_ALL;
|
||||
static constexpr u32 page_size = 16384;
|
||||
static constexpr u32 abs_rel = ARM64_RELOC_UNSIGNED;
|
||||
static constexpr u32 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 page_size = 4096;
|
||||
static constexpr u32 abs_rel = X86_64_RELOC_UNSIGNED;
|
||||
static constexpr u32 stub_size = 6;
|
||||
static constexpr u32 stub_helper_hdr_size = 16;
|
||||
static constexpr u32 stub_helper_size = 10;
|
||||
};
|
||||
|
||||
inline constexpr size_t word_size = 8;
|
||||
|
||||
} // namespace mold::macho
|
||||
-1125
File diff suppressed because it is too large.
Load diff
@@ -1,80 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
struct Sym {
|
||||
u64 addr;
|
||||
u32 size;
|
||||
u32 fileidx;
|
||||
std::string_view name;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void print_map(Context<E> &ctx) {
|
||||
std::ofstream out(ctx.arg.map.c_str());
|
||||
if (!out.is_open())
|
||||
Fatal(ctx) << "cannot open " << ctx.arg.map << ": " << errno_string();
|
||||
|
||||
out << "# Path: " << ctx.arg.output << "\n"
|
||||
<< "# Arch: x86-64\n"
|
||||
<< "# Object files:\n";
|
||||
|
||||
std::vector<Sym> syms;
|
||||
|
||||
for (i64 i = 0; i < ctx.objs.size(); i++){
|
||||
ObjectFile<E> &file = *ctx.objs[i];
|
||||
|
||||
if (file.is_alive) {
|
||||
out << "[" << std::setw(3) << i << "] " << file << "\n";
|
||||
for (Symbol<E> *sym : file.syms)
|
||||
if (sym && sym->file == &file)
|
||||
if (!sym->subsec || sym->subsec->is_alive)
|
||||
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_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
-1052
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
-294
@@ -1,294 +0,0 @@
|
||||
// On macOS, you can pass a test file describing a dylib instead of an
|
||||
// actual dylib file to link against a dynamic library. Such text file
|
||||
// should be in the YAML format and contains dylib's exported symbols
|
||||
// as well as the file's various attributes. The extension of the text
|
||||
// file is `.tbd`.
|
||||
//
|
||||
// .tbd files allows users to link against a library without
|
||||
// distributing the binary of the library file itself.
|
||||
//
|
||||
// This file contains functions to parse the .tbd file.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename T, typename U>
|
||||
static void merge(std::set<T> &a, U &&b) {
|
||||
a.insert(b.begin(), b.end());
|
||||
}
|
||||
|
||||
static std::vector<YamlNode>
|
||||
get_vector(YamlNode &node, std::string_view key) {
|
||||
if (auto *map = std::get_if<std::map<std::string_view, YamlNode>>(&node.data))
|
||||
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::vector<std::string_view>
|
||||
get_string_vector(YamlNode &node, std::string_view key) {
|
||||
std::vector<std::string_view> vec;
|
||||
for (YamlNode &mem : get_vector(node, key))
|
||||
if (auto val = std::get_if<std::string_view>(&mem.data))
|
||||
vec.push_back(*val);
|
||||
return vec;
|
||||
}
|
||||
|
||||
static std::optional<std::string_view>
|
||||
get_string(YamlNode &node, std::string_view key) {
|
||||
if (auto *map = std::get_if<std::map<std::string_view, YamlNode>>(&node.data))
|
||||
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 (auto val = std::get_if<std::string_view>(&mem.data))
|
||||
if (*val == key)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool match_arch(const std::vector<YamlNode> &vec, std::string_view arch) {
|
||||
for (const YamlNode &mem : vec)
|
||||
if (auto val = std::get_if<std::string_view>(&mem.data))
|
||||
if (*val == arch || val->starts_with(std::string(arch) + "-"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::optional<TextDylib>
|
||||
to_tbd(Context<E> &ctx, YamlNode &node, std::string_view arch,
|
||||
std::string_view filename) {
|
||||
if (!match_arch(get_vector(node, "targets"), arch))
|
||||
return {};
|
||||
|
||||
if (ctx.arg.application_extension &&
|
||||
contains(get_vector(node, "flags"), "not_app_extension_safe"))
|
||||
Warn(ctx) << "linking against a dylib which is not safe for use in "
|
||||
<< "application extensions: " << filename;
|
||||
|
||||
TextDylib tbd;
|
||||
|
||||
if (auto val = get_string(node, "install-name"))
|
||||
tbd.install_name = *val;
|
||||
|
||||
for (YamlNode &mem : get_vector(node, "reexported-libraries"))
|
||||
if (match_arch(get_vector(mem, "targets"), arch))
|
||||
append(tbd.reexported_libs, get_string_vector(mem, "libraries"));
|
||||
|
||||
auto concat = [&](const std::string &x, std::string_view y) {
|
||||
return save_string(ctx, x + std::string(y));
|
||||
};
|
||||
|
||||
for (std::string_view key : {"exports", "reexports"}) {
|
||||
for (YamlNode &mem : get_vector(node, key)) {
|
||||
if (match_arch(get_vector(mem, "targets"), arch)) {
|
||||
merge(tbd.exports, get_string_vector(mem, "symbols"));
|
||||
merge(tbd.weak_exports, get_string_vector(mem, "weak-symbols"));
|
||||
|
||||
for (std::string_view s : get_string_vector(mem, "objc-classes")) {
|
||||
tbd.exports.insert(concat("_OBJC_CLASS_$_", s));
|
||||
tbd.exports.insert(concat("_OBJC_METACLASS_$_", s));
|
||||
}
|
||||
|
||||
for (std::string_view s : get_string_vector(mem, "objc-eh-types"))
|
||||
tbd.exports.insert(concat("_OBJC_EHTYPE_$_", s));
|
||||
|
||||
for (std::string_view s : get_string_vector(mem, "objc-ivars"))
|
||||
tbd.exports.insert(concat("_OBJC_IVAR_$_", s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tbd;
|
||||
}
|
||||
|
||||
static i64 parse_version(const std::string &arg) {
|
||||
auto flags = std::regex_constants::ECMAScript | std::regex_constants::optimize;
|
||||
static std::regex re(R"((\d+)(?:\.(\d+))?(?:\.(\d+))?)", flags);
|
||||
|
||||
std::smatch m;
|
||||
bool ok = std::regex_match(arg, m, re);
|
||||
assert(ok);
|
||||
|
||||
i64 major = (m[1].length() == 0) ? 0 : stoi(m[1]);
|
||||
i64 minor = (m[2].length() == 0) ? 0 : stoi(m[2]);
|
||||
i64 patch = (m[3].length() == 0) ? 0 : stoi(m[3]);
|
||||
return (major << 16) | (minor << 8) | patch;
|
||||
}
|
||||
|
||||
// Dylib can contain special symbols whose name starts with "$ld$".
|
||||
// Such symbols aren't actually symbols but linker directives.
|
||||
// We interpret such symbols in this function.
|
||||
template <typename E>
|
||||
static void interpret_ld_symbols(Context<E> &ctx, TextDylib &tbd) {
|
||||
std::set<std::string_view> syms;
|
||||
std::unordered_set<std::string_view> hidden_syms;
|
||||
|
||||
auto string_view = [](const std::csub_match &sub) {
|
||||
return std::string_view{sub.first, (size_t)sub.length()};
|
||||
};
|
||||
|
||||
for (std::string_view s : tbd.exports) {
|
||||
if (!s.starts_with("$ld$"))
|
||||
continue;
|
||||
|
||||
auto flags = std::regex_constants::ECMAScript | std::regex_constants::optimize;
|
||||
std::cmatch m;
|
||||
|
||||
// $ld$previous$ symbol replaces the default install name with a
|
||||
// specified one if the platform OS version is in a specified range.
|
||||
static std::regex previous_re(
|
||||
R"(\$ld\$previous\$([^$]+)\$([\d.]*)\$(\d+)\$([\d.]+)\$([\d.]+)\$(.*)\$)",
|
||||
flags);
|
||||
|
||||
if (std::regex_match(s.data(), s.data() + s.size(), m, previous_re)) {
|
||||
std::string_view install_name = string_view(m[1]);
|
||||
i64 platform = std::stoi(m[3].str());
|
||||
i64 min_version = parse_version(m[4]);
|
||||
i64 max_version = parse_version(m[5]);
|
||||
std::string_view symbol_name = string_view(m[6]);
|
||||
|
||||
if (!symbol_name.empty()) {
|
||||
// ld64 source seems to have implemented a feature to give an
|
||||
// alternative install name for a matching symbol, but it didn't
|
||||
// work in practice (or I may be using the feature in a wrong way.)
|
||||
// Ignore such symbol for now.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (platform == ctx.arg.platform &&
|
||||
min_version <= ctx.arg.platform_min_version &&
|
||||
ctx.arg.platform_min_version < max_version) {
|
||||
tbd.install_name = install_name;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// $ld$add$os_version$symbol adds a symbol if the given OS version
|
||||
// matches.
|
||||
static std::regex add_re(R"(\$ld\$add\$os([\d.]+)\$(.+))", flags);
|
||||
|
||||
if (std::regex_match(s.data(), s.data() + s.size(), m, add_re)) {
|
||||
if (ctx.arg.platform_min_version == parse_version(m[1]))
|
||||
syms.insert(string_view(m[2]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// $ld$hide$os_version$symbol hides a symbol if the given OS version
|
||||
// matches.
|
||||
static std::regex hidden_re(R"(\$ld\$hide\$os([\d.]+)\$(.+))", flags);
|
||||
|
||||
if (std::regex_match(s.data(), s.data() + s.size(), m, hidden_re)) {
|
||||
if (ctx.arg.platform_min_version == parse_version(m[1]))
|
||||
hidden_syms.insert(string_view(m[2]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// $ld$install_name$os_version$name changes the install name to a
|
||||
// given name.
|
||||
static std::regex
|
||||
install_name_re(R"(\$ld\$install_name\$os([\d.]+)\$(.+))", flags);
|
||||
|
||||
if (std::regex_match(s.data(), s.data() + s.size(), m, install_name_re)) {
|
||||
if (ctx.arg.platform_min_version == parse_version(m[1]))
|
||||
tbd.install_name = string_view(m[2]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::string_view s : tbd.exports)
|
||||
if (!s.starts_with("$ld$") && !hidden_syms.contains(s))
|
||||
syms.insert(s);
|
||||
|
||||
tbd.exports = std::move(syms);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static TextDylib parse(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string_view arch);
|
||||
|
||||
// A single YAML file may contain multiple text dylibs. The first text
|
||||
// dylib is the main file followed by optional other text dylibs for
|
||||
// re-exported libraries.
|
||||
//
|
||||
// This fucntion squashes multiple text dylibs into a single text dylib
|
||||
// by copying symbols of re-exported text dylibs to the main text dylib.
|
||||
template <typename E>
|
||||
static TextDylib
|
||||
squash(Context<E> &ctx, std::span<TextDylib> tbds, std::string_view arch) {
|
||||
std::unordered_map<std::string_view, TextDylib> map;
|
||||
std::vector<std::string_view> remainings;
|
||||
|
||||
TextDylib main = std::move(tbds[0]);
|
||||
for (TextDylib &tbd : tbds.subspan(1))
|
||||
map[tbd.install_name] = std::move(tbd);
|
||||
|
||||
std::function<void(TextDylib &)> visit = [&](TextDylib &tbd) {
|
||||
for (std::string_view lib : tbd.reexported_libs) {
|
||||
auto it = map.find(lib);
|
||||
|
||||
if (it != map.end()) {
|
||||
TextDylib &child = it->second;
|
||||
merge(main.exports, child.exports);
|
||||
merge(main.weak_exports, child.weak_exports);
|
||||
visit(child);
|
||||
} else {
|
||||
remainings.push_back(lib);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
visit(main);
|
||||
main.reexported_libs = remainings;
|
||||
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(ctx, node, arch, mf->name))
|
||||
vec.push_back(*dylib);
|
||||
|
||||
for (TextDylib &tbd : vec)
|
||||
interpret_ld_symbols(ctx, tbd);
|
||||
|
||||
return squash(ctx, vec, arch);
|
||||
}
|
||||
|
||||
template <>
|
||||
TextDylib parse_tbd(Context<ARM64> &ctx, MappedFile<Context<ARM64>> *mf) {
|
||||
return parse(ctx, mf, "arm64");
|
||||
}
|
||||
|
||||
template <>
|
||||
TextDylib parse_tbd(Context<X86_64> &ctx, MappedFile<Context<X86_64>> *mf) {
|
||||
return parse(ctx, mf, "x86_64");
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
-378
@@ -1,378 +0,0 @@
|
||||
// On macOS, a .tbd file can be used instead of a .dylib to link
|
||||
// against a dynamic library. A .tbd file is a text file in the YAML
|
||||
// format, so the linker has to be able to parse a YAML file. This
|
||||
// file implements a YAML parser.
|
||||
//
|
||||
// We implemented a YAML parser ourselves instead of using an external
|
||||
// library. We didn't want to add an dependency to an external library
|
||||
// just to read .tdb files.
|
||||
//
|
||||
// YAML as a format is very complicated. For example, the official
|
||||
// YAML specification allows embedding a JSON string into YAML; so it
|
||||
// is strictly larger than JSON. It has surprisingly many features
|
||||
// that most users are not aware of. Fortunately, we have to support
|
||||
// only a small portion of the spec to read a .tbd file.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
enum { STRING = 1, INDENT, DEDENT, END };
|
||||
|
||||
struct Token {
|
||||
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;
|
||||
};
|
||||
|
||||
// A tokenizer for YAML. YAML represents blocks by indentation. This
|
||||
// tokenizer inserts INDENT and DEDENT special tokens before and after
|
||||
// each indented text block.
|
||||
std::optional<YamlError> YamlParser::tokenize() {
|
||||
std::vector<i64> indents = {0};
|
||||
|
||||
auto indent = [&](std::string_view str, i64 depth) {
|
||||
tokens.push_back({INDENT, str});
|
||||
indents.push_back(depth);
|
||||
};
|
||||
|
||||
auto dedent = [&](std::string_view str) {
|
||||
assert(indents.size() > 1);
|
||||
tokens.push_back({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({END, str.substr(0, 3)});
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (str.starts_with("...")) {
|
||||
while (indents.size() > 1)
|
||||
dedent(str);
|
||||
tokens.push_back({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({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({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 == 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 != 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 == INDENT) {
|
||||
tok = tok.subspan(1);
|
||||
|
||||
std::variant<YamlNode, YamlError> elem = parse_element(tok);
|
||||
assert(tok[0].kind == DEDENT);
|
||||
tok = tok.subspan(1);
|
||||
return elem;
|
||||
}
|
||||
|
||||
if (tok[0].kind == '-')
|
||||
return parse_list(tok);
|
||||
|
||||
if (tok.size() > 2 && tok[0].kind == 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 != END && tok[0].kind != 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 != END && tok[0].kind != DEDENT) {
|
||||
if (tok.size() < 2 || tok[0].kind != STRING || tok[1].kind != ':')
|
||||
return YamlError{"map key expected", tok[0].str.data() - input.data()};
|
||||
|
||||
std::string_view key = tok[0].str;
|
||||
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 != 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 != 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 == 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,6 +1,8 @@
|
||||
#include "mold.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <signal.h>
|
||||
#include <tbb/global_control.h>
|
||||
|
||||
@@ -8,8 +10,15 @@
|
||||
#include <mimalloc-new-delete.h>
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
# include <sys/sysctl.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string mold_version_string = MOLD_VERSION;
|
||||
|
||||
namespace elf {
|
||||
int main(int argc, char **argv);
|
||||
}
|
||||
@@ -19,16 +28,45 @@ int main(int argc, char **argv);
|
||||
}
|
||||
|
||||
static std::string get_mold_version() {
|
||||
std::string name = MOLD_IS_SOLD ? "mold (sold) " : "mold ";
|
||||
if (mold_git_hash.empty())
|
||||
return "mold " MOLD_VERSION " (compatible with GNU ld)";
|
||||
return "mold " MOLD_VERSION " (" + mold_git_hash + "; compatible with GNU ld)";
|
||||
return name + MOLD_VERSION + " (compatible with GNU ld)";
|
||||
return name + MOLD_VERSION + " (" + mold_git_hash + "; compatible with GNU ld)";
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
if (output_tmpfile)
|
||||
unlink(output_tmpfile);
|
||||
if (socket_tmpfile)
|
||||
unlink(socket_tmpfile);
|
||||
}
|
||||
|
||||
std::string errno_string() {
|
||||
// strerror is not thread-safe, so guard it with a lock.
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
return strerror(errno);
|
||||
}
|
||||
|
||||
// Returns the path of the mold executable itself
|
||||
std::string get_self_path() {
|
||||
#ifdef __FreeBSD__
|
||||
// /proc may not be mounted on FreeBSD. The proper way to get the
|
||||
// current executable's path is to use sysctl(2).
|
||||
int mib[4];
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_PATHNAME;
|
||||
mib[3] = -1;
|
||||
|
||||
size_t size;
|
||||
sysctl(mib, 4, NULL, &size, NULL, 0);
|
||||
|
||||
std::string path;
|
||||
path.resize(size);
|
||||
sysctl(mib, 4, path.data(), &size, NULL, 0);
|
||||
return path;
|
||||
#else
|
||||
return std::filesystem::read_symlink("/proc/self/exe").string();
|
||||
#endif
|
||||
}
|
||||
|
||||
// mold mmap's an output file, and the mmap succeeds even if there's
|
||||
@@ -108,8 +146,11 @@ i64 get_default_thread_count() {
|
||||
int main(int argc, char **argv) {
|
||||
mold::mold_version = mold::get_mold_version();
|
||||
|
||||
#if MOLD_IS_SOLD
|
||||
std::string cmd = mold::filepath(argv[0]).filename().string();
|
||||
if (cmd == "ld64" || cmd == "ld64.mold")
|
||||
return mold::macho::main(argc, argv);
|
||||
#endif
|
||||
|
||||
return mold::elf::main(argc, argv);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "inttypes.h"
|
||||
#include "integers.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
@@ -60,33 +60,28 @@ namespace mold {
|
||||
using namespace std::literals::string_literals;
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
|
||||
template <typename C> class OutputFile;
|
||||
|
||||
inline char *output_tmpfile;
|
||||
inline char *socket_tmpfile;
|
||||
inline thread_local bool opt_demangle;
|
||||
|
||||
inline u8 *output_buffer_start = nullptr;
|
||||
inline u8 *output_buffer_end = nullptr;
|
||||
|
||||
inline std::string mold_version;
|
||||
extern std::string mold_version_string;
|
||||
extern std::string mold_git_hash;
|
||||
|
||||
std::string_view errno_string();
|
||||
std::string errno_string();
|
||||
std::string get_self_path();
|
||||
void cleanup();
|
||||
void install_signal_handler();
|
||||
i64 get_default_thread_count();
|
||||
|
||||
static u64 combine_hash(u64 a, u64 b) {
|
||||
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
|
||||
}
|
||||
|
||||
//
|
||||
// Error output
|
||||
//
|
||||
@@ -199,7 +194,7 @@ inline bool has_single_bit(u64 val) {
|
||||
inline u64 bit_ceil(u64 val) {
|
||||
if (has_single_bit(val))
|
||||
return val;
|
||||
return (u64)1 << (64 - std::countl_zero(val));
|
||||
return 1LL << (64 - std::countl_zero(val));
|
||||
}
|
||||
|
||||
inline u64 align_to(u64 val, u64 align) {
|
||||
@@ -220,7 +215,7 @@ inline u64 bit(u64 val, i64 pos) {
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
inline u64 bits(u64 val, u64 hi, u64 lo) {
|
||||
return (val >> lo) & (((u64)1 << (hi - lo + 1)) - 1);
|
||||
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
inline i64 sign_extend(u64 val, i64 size) {
|
||||
@@ -229,16 +224,29 @@ inline i64 sign_extend(u64 val, i64 size) {
|
||||
|
||||
template <typename T, typename Compare = std::less<T>>
|
||||
void update_minimum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
T old_val = atomic;
|
||||
T old_val = atomic.load(std::memory_order_relaxed);
|
||||
while (cmp(new_val, old_val) &&
|
||||
!atomic.compare_exchange_weak(old_val, new_val));
|
||||
!atomic.compare_exchange_weak(old_val, new_val,
|
||||
std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
template <typename T, typename Compare = std::less<T>>
|
||||
void update_maximum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
T old_val = atomic;
|
||||
T old_val = atomic.load(std::memory_order_relaxed);
|
||||
while (cmp(old_val, new_val) &&
|
||||
!atomic.compare_exchange_weak(old_val, new_val));
|
||||
!atomic.compare_exchange_weak(old_val, new_val,
|
||||
std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
// An optimized "mark" operation for parallel mark-and-sweep algorithms.
|
||||
// Returns true if `visited` was false and updated to true.
|
||||
inline bool fast_mark(std::atomic<bool> &visited) {
|
||||
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
|
||||
// while an atomic RMW can easily take hundreds on x86. We note that it's
|
||||
// common that another thread beat us in marking, so doing an optimistic
|
||||
// early test tends to improve performance in the ~20% ballpark.
|
||||
return !visited.load(std::memory_order_relaxed) &&
|
||||
!visited.exchange(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
@@ -343,7 +351,7 @@ inline i64 uleb_size(u64 val) {
|
||||
#pragma GCC unroll 8
|
||||
#endif
|
||||
for (int i = 1; i < 9; i++)
|
||||
if (val < ((u64)1 << (7 * i)))
|
||||
if (val < (1LL << (7 * i)))
|
||||
return i;
|
||||
return 9;
|
||||
}
|
||||
@@ -411,18 +419,19 @@ public:
|
||||
i64 retry = 0;
|
||||
|
||||
while (retry < MAX_RETRY) {
|
||||
const char *ptr = keys[idx];
|
||||
const char *ptr = keys[idx].load(std::memory_order_acquire);
|
||||
if (ptr == marker) {
|
||||
pause();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ptr == nullptr) {
|
||||
if (!keys[idx].compare_exchange_weak(ptr, marker))
|
||||
if (!keys[idx].compare_exchange_weak(ptr, marker,
|
||||
std::memory_order_acq_rel))
|
||||
continue;
|
||||
new (values + idx) T(val);
|
||||
key_sizes[idx] = key.size();
|
||||
keys[idx] = key.data();
|
||||
keys[idx].store(key.data(), std::memory_order_release);
|
||||
return {values + idx, true};
|
||||
}
|
||||
|
||||
@@ -563,7 +572,6 @@ private:
|
||||
std::vector<std::string> strings;
|
||||
std::unique_ptr<TrieNode> root;
|
||||
std::vector<std::pair<Glob, u32>> globs;
|
||||
std::vector<u32> values;
|
||||
std::once_flag once;
|
||||
bool is_compiled = false;
|
||||
};
|
||||
@@ -598,27 +606,30 @@ std::optional<std::string_view> cpp_demangle(std::string_view name);
|
||||
// compress.cc
|
||||
//
|
||||
|
||||
class ZlibCompressor {
|
||||
class Compressor {
|
||||
public:
|
||||
virtual void write_to(u8 *buf) = 0;
|
||||
virtual ~Compressor() {}
|
||||
i64 compressed_size = 0;
|
||||
};
|
||||
|
||||
class ZlibCompressor : public Compressor {
|
||||
public:
|
||||
ZlibCompressor(u8 *buf, i64 size);
|
||||
void write_to(u8 *buf);
|
||||
i64 size() const;
|
||||
void write_to(u8 *buf) override;
|
||||
|
||||
private:
|
||||
std::vector<std::vector<u8>> shards;
|
||||
u64 checksum = 0;
|
||||
};
|
||||
|
||||
class GzipCompressor {
|
||||
class ZstdCompressor : public Compressor {
|
||||
public:
|
||||
GzipCompressor(std::string_view input);
|
||||
void write_to(u8 *buf);
|
||||
i64 size() const;
|
||||
ZstdCompressor(u8 *buf, i64 size);
|
||||
void write_to(u8 *buf) override;
|
||||
|
||||
private:
|
||||
std::vector<std::vector<u8>> shards;
|
||||
u32 checksum = 0;
|
||||
u32 uncompressed_size = 0;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -738,7 +749,8 @@ public:
|
||||
static MappedFile *open(C &ctx, std::string path);
|
||||
static MappedFile *must_open(C &ctx, std::string path);
|
||||
|
||||
~MappedFile();
|
||||
~MappedFile() { unmap(); }
|
||||
void unmap();
|
||||
|
||||
MappedFile *slice(C &ctx, std::string name, u64 start, u64 size);
|
||||
|
||||
@@ -785,7 +797,6 @@ MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
|
||||
i64 fd;
|
||||
#ifdef _WIN32
|
||||
fd = ::_open(path.c_str(), O_RDONLY);
|
||||
@@ -858,8 +869,8 @@ MappedFile<C>::slice(C &ctx, std::string name, u64 start, u64 size) {
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
MappedFile<C>::~MappedFile() {
|
||||
if (size == 0 || parent)
|
||||
void MappedFile<C>::unmap() {
|
||||
if (size == 0 || parent || !data)
|
||||
return;
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -869,6 +880,8 @@ MappedFile<C>::~MappedFile() {
|
||||
#else
|
||||
munmap(data, size);
|
||||
#endif
|
||||
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+7
-9
@@ -26,7 +26,7 @@ namespace mold {
|
||||
|
||||
std::optional<u32> MultiGlob::find(std::string_view str) {
|
||||
std::call_once(once, [&] { compile(); });
|
||||
u32 idx = UINT32_MAX;
|
||||
u32 val = UINT32_MAX;
|
||||
|
||||
if (root) {
|
||||
// Match against simple glob patterns
|
||||
@@ -36,7 +36,7 @@ std::optional<u32> MultiGlob::find(std::string_view str) {
|
||||
for (;;) {
|
||||
if (node->children[c]) {
|
||||
node = node->children[c].get();
|
||||
idx = std::min(idx, node->value);
|
||||
val = std::min(val, node->value);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -55,11 +55,11 @@ std::optional<u32> MultiGlob::find(std::string_view str) {
|
||||
// Match against complex glob patterns
|
||||
for (std::pair<Glob, u32> &glob : globs)
|
||||
if (glob.first.match(str))
|
||||
idx = std::min(idx, glob.second);
|
||||
val = std::min(val, glob.second);
|
||||
|
||||
if (idx == UINT32_MAX)
|
||||
if (val == UINT32_MAX)
|
||||
return {};
|
||||
return values[idx];
|
||||
return val;
|
||||
}
|
||||
|
||||
static bool is_simple_pattern(std::string_view pat) {
|
||||
@@ -86,14 +86,12 @@ bool MultiGlob::add(std::string_view pat, u32 val) {
|
||||
assert(!is_compiled);
|
||||
assert(!pat.empty());
|
||||
|
||||
u32 idx = strings.size();
|
||||
strings.push_back(std::string(pat));
|
||||
values.push_back(val);
|
||||
|
||||
// Complex glob pattern
|
||||
if (!is_simple_pattern(pat)) {
|
||||
if (std::optional<Glob> glob = Glob::compile(pat)) {
|
||||
globs.push_back({std::move(*glob), idx});
|
||||
globs.push_back({std::move(*glob), val});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -110,7 +108,7 @@ bool MultiGlob::add(std::string_view pat, u32 val) {
|
||||
node = node->children[c].get();
|
||||
}
|
||||
|
||||
node->value = std::min(node->value, idx);
|
||||
node->value = std::min(node->value, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,16 +65,31 @@ public:
|
||||
mold::output_buffer_end = this->buf + filesize;
|
||||
}
|
||||
|
||||
~MemoryMappedOutputFile() {
|
||||
if (fd2 != -1)
|
||||
::close(fd2);
|
||||
}
|
||||
|
||||
void close(C &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (!this->is_unmapped)
|
||||
munmap(this->buf, this->filesize);
|
||||
|
||||
// If an output file already exists, open a file and then remove it.
|
||||
// This is the fastest way to unlink a file, as it does not make the
|
||||
// system to immediately release disk blocks occupied by the file.
|
||||
fd2 = ::open(this->path.c_str(), O_RDONLY);
|
||||
if (fd2 != -1)
|
||||
unlink(this->path.c_str());
|
||||
|
||||
if (rename(output_tmpfile, this->path.c_str()) == -1)
|
||||
Fatal(ctx) << this->path << ": rename failed: " << errno_string();
|
||||
output_tmpfile = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
int fd2 = -1;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
@@ -133,6 +148,19 @@ OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
else
|
||||
file = new MemoryMappedOutputFile<C>(ctx, path, filesize, perm);
|
||||
|
||||
#ifdef MADV_HUGEPAGE
|
||||
// Enable transparent huge page for an output memory-mapped file.
|
||||
// On Linux, it has an effect only on tmpfs mounted with `huge=advise`,
|
||||
// but it can make the linker ~10% faster. You can try it by creating
|
||||
// a tmpfs with the following commands
|
||||
//
|
||||
// $ mkdir tmp
|
||||
// $ sudo mount -t tmpfs -o size=2G,huge=advise none tmp
|
||||
//
|
||||
// and then specifying a path under the directory as an output file.
|
||||
madvise(file->buf, filesize, MADV_HUGEPAGE);
|
||||
#endif
|
||||
|
||||
if (ctx.arg.filler != -1)
|
||||
memset(file->buf, ctx.arg.filler, filesize);
|
||||
return std::unique_ptr<OutputFile<C>>(file);
|
||||
|
||||
@@ -16,7 +16,7 @@ inline static BCRYPT_ALG_HANDLE get_sha256_handle() {
|
||||
static std::once_flag once;
|
||||
static BCRYPT_ALG_HANDLE alg;
|
||||
|
||||
std::call_once(once, [&]() {
|
||||
std::call_once(once, [&] {
|
||||
BCryptOpenAlgorithmProvider(&alg, BCRYPT_SHA256_ALGORITHM, nullptr, 0);
|
||||
});
|
||||
return alg;
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
// GNU's strerror_r is different from POSIX's strerror_r.
|
||||
// In this file, we explicitly undefine _GNU_SOURCE to always
|
||||
// use the POSIX version.
|
||||
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#undef _GNU_SOURCE
|
||||
|
||||
#include <cstring>
|
||||
#include <errno.h>
|
||||
#include <string_view>
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string_view errno_string() {
|
||||
static thread_local char buf[200];
|
||||
#ifdef _WIN32
|
||||
strerror_s(buf, errno);
|
||||
#else
|
||||
strerror_r(errno, buf, sizeof(buf));
|
||||
#endif
|
||||
return buf;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -24,8 +24,8 @@ struct UstarHeader {
|
||||
int sum = 0;
|
||||
for (i64 i = 0; i < sizeof(*this); i++)
|
||||
sum += ((u8 *)this)[i];
|
||||
assert(sum < 1000000);
|
||||
sprintf(checksum, "%06o", sum);
|
||||
assert(sum < 01000000);
|
||||
snprintf(checksum, sizeof(checksum), "%06o", sum);
|
||||
}
|
||||
|
||||
char name[100];
|
||||
@@ -77,7 +77,7 @@ void TarWriter::append(std::string path, std::string_view data) {
|
||||
UstarHeader pax;
|
||||
|
||||
std::string attr = encode_path(basedir, path);
|
||||
sprintf(pax.size, "%011zo", attr.size());
|
||||
snprintf(pax.size, sizeof(pax.size), "%011zo", attr.size());
|
||||
pax.typeflag[0] = 'x';
|
||||
pax.finalize();
|
||||
fwrite(&pax, sizeof(pax), 1, out);
|
||||
@@ -89,7 +89,7 @@ void TarWriter::append(std::string path, std::string_view data) {
|
||||
// Write Ustar header
|
||||
UstarHeader ustar;
|
||||
memcpy(ustar.mode, "0000664", 8);
|
||||
sprintf(ustar.size, "%011zo", data.size());
|
||||
snprintf(ustar.size, sizeof(ustar.size), "%011zo", data.size());
|
||||
ustar.finalize();
|
||||
fwrite(&ustar, sizeof(ustar), 1, out);
|
||||
|
||||
|
||||
+56
-35
@@ -1,43 +1,64 @@
|
||||
option(MOLD_ENABLE_QEMU_TESTS "Enable tests on non-native targets" OFF)
|
||||
option(MOLD_ENABLE_QEMU_TESTS_RV32 "Enable tests on RV32" OFF)
|
||||
option(MOLD_ENABLE_QEMU_TESTS_RV32 "Enable tests for RV32" OFF)
|
||||
|
||||
file(GLOB TESTS
|
||||
RELATIVE ${CMAKE_CURRENT_LIST_DIR}
|
||||
CONFIGURE_DEPENDS
|
||||
"*.sh")
|
||||
function(add_target TRIPLE)
|
||||
set(HOST ${CMAKE_HOST_SYSTEM_PROCESSOR})
|
||||
if(${HOST} MATCHES "arm.*")
|
||||
set(HOST arm)
|
||||
endif()
|
||||
|
||||
function(add_qemu_test TEST TRIPLE MACHINE)
|
||||
if(NOT ${CMAKE_HOST_SYSTEM_PROCESSOR} STREQUAL ${MACHINE})
|
||||
add_test(NAME "${MACHINE}-${TEST}"
|
||||
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
|
||||
WORKING_DIRECTORY ${mold_BINARY_DIR})
|
||||
if(${TRIPLE} MATCHES "${HOST}-.*")
|
||||
set(IS_NATIVE 1)
|
||||
endif()
|
||||
|
||||
set(ENV
|
||||
TEST_CC=${TRIPLE}-gcc
|
||||
TEST_CXX=${TRIPLE}-g++
|
||||
TEST_GCC=${TRIPLE}-gcc
|
||||
TEST_GXX=${TRIPLE}-g++
|
||||
OBJDUMP=${TRIPLE}-objdump
|
||||
MACHINE=${MACHINE}
|
||||
"QEMU=qemu-${MACHINE} -L /usr/${TRIPLE}")
|
||||
set_tests_properties("${MACHINE}-${TEST}" PROPERTIES ENVIRONMENT "${ENV}")
|
||||
if(${TRIPLE} MATCHES "([^-]+)-.")
|
||||
set(MACHINE ${CMAKE_MATCH_1})
|
||||
endif()
|
||||
|
||||
if(IS_NATIVE OR MOLD_ENABLE_QEMU_TESTS)
|
||||
file(GLOB ALL_TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
|
||||
"*.sh")
|
||||
|
||||
list(FILTER ALL_TESTS EXCLUDE REGEX "_")
|
||||
|
||||
file(GLOB TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
|
||||
"${MACHINE}_*.sh")
|
||||
|
||||
list(APPEND TESTS ${ALL_TESTS})
|
||||
|
||||
foreach(TEST IN LISTS TESTS)
|
||||
string(REGEX REPLACE "\\.sh$" "" TESTNAME "${MACHINE}-${TEST}")
|
||||
|
||||
add_test(NAME ${TESTNAME}
|
||||
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
|
||||
WORKING_DIRECTORY ${mold_BINARY_DIR})
|
||||
|
||||
if(IS_NATIVE)
|
||||
set_tests_properties(${TESTNAME} PROPERTIES
|
||||
SKIP_REGULAR_EXPRESSION "skipped")
|
||||
else()
|
||||
set_tests_properties(${TESTNAME} PROPERTIES
|
||||
ENVIRONMENT "TRIPLE=${TRIPLE}")
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
foreach(TEST IN LISTS TESTS)
|
||||
add_test(NAME "${CMAKE_HOST_SYSTEM_PROCESSOR}-${TEST}"
|
||||
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
|
||||
WORKING_DIRECTORY ${mold_BINARY_DIR})
|
||||
add_target(x86_64-linux-gnu)
|
||||
add_target(i686-linux-gnu)
|
||||
add_target(aarch64-linux-gnu)
|
||||
add_target(arm-linux-gnueabihf)
|
||||
add_target(riscv64-linux-gnu)
|
||||
add_target(powerpc-linux-gnu)
|
||||
add_target(powerpc64-linux-gnu)
|
||||
add_target(powerpc64le-linux-gnu)
|
||||
add_target(sparc64-linux-gnu)
|
||||
add_target(s390x-linux-gnu)
|
||||
add_target(m68k-linux-gnu)
|
||||
add_target(sh4-linux-gnu)
|
||||
add_target(alpha-linux-gnu)
|
||||
add_target(hppa-linux-gnu)
|
||||
|
||||
if(MOLD_ENABLE_QEMU_TESTS)
|
||||
add_qemu_test(${TEST} x86_64-linux-gnu x86_64)
|
||||
add_qemu_test(${TEST} i686-linux-gnu i386)
|
||||
add_qemu_test(${TEST} aarch64-linux-gnu aarch64)
|
||||
add_qemu_test(${TEST} arm-linux-gnueabihf arm)
|
||||
add_qemu_test(${TEST} riscv64-linux-gnu riscv64)
|
||||
endif()
|
||||
|
||||
if (MOLD_ENABLE_QEMU_TESTS_RV32)
|
||||
add_qemu_test(${TEST} riscv32-linux-gnu riscv32)
|
||||
endif()
|
||||
endforeach()
|
||||
if(MOLD_ENABLE_QEMU_TESTS_RV32)
|
||||
add_target(riscv32-linux-gnu)
|
||||
endif()
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = aarch64 ] || skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -fPIC -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
void fn1();
|
||||
void fn2();
|
||||
|
||||
__attribute__((section(".low"))) void fn1() { fn2(); }
|
||||
__attribute__((section(".high"))) void fn2() { fn1(); }
|
||||
|
||||
int main() {
|
||||
fn1();
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o \
|
||||
-Wl,--section-start=.low=0x10000000,--section-start=.high=0x20000000
|
||||
|
||||
$OBJDUMP -dr $t/exe | grep -Fq '<fn1$thunk>:'
|
||||
+6
-15
@@ -1,18 +1,11 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = aarch64 ] && { echo skipped; exit; }
|
||||
[ $MACHINE = aarch64 ] && skip
|
||||
[ $MACHINE = ppc64 ] && skip
|
||||
[ $MACHINE = ppc64le ] && skip
|
||||
[ $MACHINE = s390x ] && skip
|
||||
[ $MACHINE = alpha ] && skip
|
||||
|
||||
cat <<EOF | $CC -fPIC -c -o $t/a.o -xassembler -
|
||||
.globl foo
|
||||
@@ -27,5 +20,3 @@ EOF
|
||||
|
||||
! $CC -B. -o $t/exe -pie $t/a.o $t/b.o -Wl,-z,text >& $t/log
|
||||
grep -q 'recompile with -fPIC' $t/log
|
||||
|
||||
echo OK
|
||||
@@ -1,22 +1,14 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = x86_64 ] || { echo skipped; exit; }
|
||||
# This test crashes only on qemu-sparc64 running on GitHub Actions,
|
||||
# even though it works on a local x86-64 machine and on an actual
|
||||
# SPARC machine.
|
||||
[ $MACHINE = sparc64 ] && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -x assembler -
|
||||
.globl foo
|
||||
foo = 0x800000
|
||||
foo = 0x800008
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -fno-PIC -xc -
|
||||
@@ -27,12 +19,11 @@ cat <<EOF | $CC -o $t/b.o -c -fno-PIC -xc -
|
||||
#include <ucontext.h>
|
||||
|
||||
void handler(int signum, siginfo_t *info, void *ptr) {
|
||||
ucontext_t *u = (ucontext_t *)ptr;
|
||||
printf("ip=0x%llx\n", u->uc_mcontext.gregs[REG_RIP]);
|
||||
printf("ip=%p\n", info->si_addr);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void foo();
|
||||
extern volatile int foo;
|
||||
|
||||
int main() {
|
||||
struct sigaction act;
|
||||
@@ -40,11 +31,9 @@ int main() {
|
||||
act.sa_sigaction = handler;
|
||||
sigemptyset(&act.sa_mask);
|
||||
sigaction(SIGSEGV, &act, 0);
|
||||
foo();
|
||||
foo = 5;
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe -no-pie $t/a.o $t/b.o
|
||||
$QEMU $t/exe | grep -q '^ip=0x800000$'
|
||||
|
||||
echo OK
|
||||
$QEMU $t/exe | grep -q '^ip=0x80000.$'
|
||||
@@ -1,16 +1,5 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
echo 'int main() { return 0; }' | $CC -c -o $t/a.o -xc -
|
||||
echo 'int main() { return 1; }' | $CC -c -o $t/b.o -xc -
|
||||
@@ -18,5 +7,3 @@ echo 'int main() { return 1; }' | $CC -c -o $t/b.o -xc -
|
||||
! $CC -B. -o $t/exe $t/a.o $t/b.o 2> /dev/null || false
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-allow-multiple-definition
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-z,muldefs
|
||||
|
||||
echo OK
|
||||
@@ -1,16 +1,5 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int two() { return 2; }
|
||||
@@ -37,5 +26,3 @@ rm -f $t/d.a
|
||||
ar rcs $t/d.a $t/a.o $t/b.o
|
||||
|
||||
$CC -B. -o $t/exe $t/c.o $t/d.a
|
||||
|
||||
echo OK
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = arm ] || skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -fPIC -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
void fn1();
|
||||
void fn2();
|
||||
|
||||
__attribute__((section(".low"))) void fn1() { fn2(); }
|
||||
__attribute__((section(".high"))) void fn2() { fn1(); }
|
||||
|
||||
int main() {
|
||||
fn1();
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o \
|
||||
-Wl,--section-start=.low=0x10000000,--section-start=.high=0x20000000
|
||||
|
||||
$OBJDUMP -dr $t/exe | grep -F -A7 '<fn1$thunk>:' > $t/log
|
||||
|
||||
grep -Eq 'mov\s+ip, pc' $t/log
|
||||
grep -Eq 'bx\s+ip' $t/log
|
||||
grep -Eq 'add\s+ip, ip, pc' $t/log
|
||||
@@ -1,21 +1,10 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[[ $MACHINE = arm* ]] || { echo skipped; exit; }
|
||||
[ $MACHINE = arm ] || skip
|
||||
|
||||
echo 'int main() {}' | $CC -c -o /dev/null -xc - -O0 -mthumb >& /dev/null \
|
||||
|| { echo skipped; exit; }
|
||||
|| skip
|
||||
|
||||
cat <<EOF > $t/a.c
|
||||
#include <stdio.h>
|
||||
@@ -56,5 +45,3 @@ $CC -c -o $t/f.o $t/b.c -O2 -marm
|
||||
$CC -B. -o $t/exe $t/e.o $t/f.o \
|
||||
-Wl,--section-start=.low=0x10000000,--section-start=.high=0x20000000
|
||||
$QEMU $t/exe | grep -q 'main fn1 fn3 fn2 fn4'
|
||||
|
||||
echo OK
|
||||
@@ -1,20 +1,9 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[[ $MACHINE == arm* ]] || { echo skipped; exit; }
|
||||
[[ $MACHINE == arm* ]] || skip
|
||||
|
||||
echo 'int foo() { return 0; }' | $CC -o /dev/null -c -xc - -mthumb 2> /dev/null || { echo skipped; exit; }
|
||||
echo 'int foo() { return 0; }' | $CC -o /dev/null -c -xc - -mthumb 2> /dev/null || skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -mthumb
|
||||
#include <stdio.h>
|
||||
@@ -41,5 +30,3 @@ EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o
|
||||
$QEMU $t/exe | grep -q 'main foo bar'
|
||||
|
||||
echo OK
|
||||
@@ -1,21 +1,10 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[[ $MACHINE = arm* ]] || { echo skipped; exit; }
|
||||
[ $MACHINE = arm ] || skip
|
||||
|
||||
echo 'int main() {}' | $GCC -c -o /dev/null -xc - -O0 -mthumb >& /dev/null \
|
||||
|| { echo skipped; exit; }
|
||||
|| skip
|
||||
|
||||
cat <<EOF > $t/a.c
|
||||
extern _Thread_local int foo;
|
||||
@@ -74,5 +63,3 @@ $QEMU $t/exe5 | grep -q '42 5'
|
||||
$CC -B. -o $t/exe6 $t/e.o $t/f.o -Wl,-no-relax \
|
||||
-Wl,--section-start=.low=0x10000000,--section-start=.high=0x20000000
|
||||
$QEMU $t/exe6 | grep -q '42 5'
|
||||
|
||||
echo OK
|
||||
@@ -1,16 +1,5 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -fPIC -o $t/a.o -c -xc -
|
||||
__attribute__((weak)) int fn1();
|
||||
@@ -31,13 +20,11 @@ EOF
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,-no-as-needed $t/b.so $t/c.so
|
||||
|
||||
readelf --dynamic $t/exe > $t/readelf
|
||||
fgrep -q 'Shared library: [libfoo.so]' $t/readelf
|
||||
fgrep -q 'Shared library: [libbar.so]' $t/readelf
|
||||
grep -Fq 'Shared library: [libfoo.so]' $t/readelf
|
||||
grep -Fq 'Shared library: [libbar.so]' $t/readelf
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,-as-needed $t/b.so $t/c.so
|
||||
|
||||
readelf --dynamic $t/exe > $t/readelf
|
||||
! fgrep -q 'Shared library: [libfoo.so]' $t/readelf || false
|
||||
! fgrep -q 'Shared library: [libbar.so]' $t/readelf || false
|
||||
|
||||
echo OK
|
||||
! grep -Fq 'Shared library: [libfoo.so]' $t/readelf || false
|
||||
! grep -Fq 'Shared library: [libbar.so]' $t/readelf || false
|
||||
+5
-18
@@ -1,16 +1,5 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
void fn1();
|
||||
@@ -30,13 +19,11 @@ EOF
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,--no-as-needed $t/b.so $t/c.so
|
||||
|
||||
readelf --dynamic $t/exe > $t/readelf
|
||||
fgrep -q 'Shared library: [libfoo.so]' $t/readelf
|
||||
fgrep -q 'Shared library: [libbar.so]' $t/readelf
|
||||
grep -Fq 'Shared library: [libfoo.so]' $t/readelf
|
||||
grep -Fq 'Shared library: [libbar.so]' $t/readelf
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,--as-needed $t/b.so $t/c.so
|
||||
|
||||
readelf --dynamic $t/exe > $t/readelf
|
||||
fgrep -q 'Shared library: [libfoo.so]' $t/readelf
|
||||
! fgrep -q 'Shared library: [libbar.so]' $t/readelf || false
|
||||
|
||||
echo OK
|
||||
grep -Fq 'Shared library: [libfoo.so]' $t/readelf
|
||||
! grep -Fq 'Shared library: [libbar.so]' $t/readelf || false
|
||||
+8
-19
@@ -1,16 +1,5 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -shared -fPIC -o $t/a.so -xc -
|
||||
int foo() { return 3; }
|
||||
@@ -20,11 +9,13 @@ cat <<EOF | $CC -shared -fPIC -o $t/b.so -xc -
|
||||
int bar() { return 3; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -shared -fPIC -o $t/c.so -xc -
|
||||
cat <<EOF | $CC -fPIC -c -o $t/c.o -xc -
|
||||
int foo();
|
||||
int baz() { return foo(); }
|
||||
EOF
|
||||
|
||||
$CC -shared -o $t/c.so $t/c.o $t/a.so
|
||||
|
||||
cat <<EOF | $CC -c -o $t/d.o -xc -
|
||||
#include <stdio.h>
|
||||
int baz();
|
||||
@@ -33,12 +24,10 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/d.o -Wl,--as-needed \
|
||||
$t/c.so $t/b.so $t/a.so
|
||||
$CC -B. -o $t/exe $t/d.o -Wl,--as-needed $t/c.so $t/b.so $t/a.so
|
||||
$QEMU $t/exe | grep -q '^3$'
|
||||
|
||||
readelf --dynamic $t/exe > $t/log
|
||||
grep -q /a.so $t/log
|
||||
grep -q /c.so $t/log
|
||||
! grep -q /a.so $t/log || false
|
||||
grep -q /c.so $t/log || false
|
||||
! grep -q /b.so $t/log || false
|
||||
|
||||
echo OK
|
||||
+3
-16
@@ -1,16 +1,5 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -x assembler -
|
||||
.text
|
||||
@@ -22,7 +11,5 @@ EOF
|
||||
./mold -o $t/b.so $t/a.o -auxiliary foo -f bar -shared
|
||||
|
||||
readelf --dynamic $t/b.so > $t/log
|
||||
fgrep -q 'Auxiliary library: [foo]' $t/log
|
||||
fgrep -q 'Auxiliary library: [bar]' $t/log
|
||||
|
||||
echo OK
|
||||
grep -Fq 'Auxiliary library: [foo]' $t/log
|
||||
grep -Fq 'Auxiliary library: [bar]' $t/log
|
||||
@@ -1,23 +0,0 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
|
||||
[ $MACHINE = x86_64 ] || { echo skipped; exit; }
|
||||
|
||||
echo '.globl _start; _start: jmp loop' | $CC -o $t/a.o -c -x assembler -
|
||||
echo '.globl loop; loop: jmp loop' | $CC -o $t/b.o -c -x assembler -
|
||||
./mold -static -o $t/exe $t/a.o $t/b.o
|
||||
$OBJDUMP -d $t/exe > /dev/null
|
||||
file $t/exe | grep -q ELF
|
||||
|
||||
echo OK
|
||||
+13
-15
@@ -1,16 +1,16 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# GCC produces buggy code for this test case on s390x.
|
||||
# https://sourceware.org/bugzilla/show_bug.cgi?id=29655
|
||||
[ $MACHINE = s390x ] && $CC -v 2>&1 | grep -E '^gcc version 1[0-3]\.' && skip
|
||||
|
||||
# This test does not pass even with GNU ld, which means function pointer
|
||||
# equality is not guaranteed on HP/PA. I'm not sure if this is an bug in
|
||||
# QEMU or this test really fails on a real HP/PA machine, but it's quite
|
||||
# surprising. Many programs could silently break if function pointer
|
||||
# equality is not guaranteed.
|
||||
[ $MACHINE = hppa ] && skip
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o$t/a.o -xc -
|
||||
int foo = 4;
|
||||
@@ -26,7 +26,7 @@ EOF
|
||||
|
||||
$CC -B. -shared -fPIC -o $t/b.so $t/a.o -Wl,-Bsymbolic -Wl,-Bno-symbolic
|
||||
|
||||
cat <<EOF | $CC -c -o $t/c.o -xc - -fno-PIE
|
||||
cat <<EOF | $CC -B. -c -o $t/c.o -xc - -fno-PIE
|
||||
#include <stdio.h>
|
||||
|
||||
extern int foo;
|
||||
@@ -41,5 +41,3 @@ EOF
|
||||
|
||||
$CC -B. -no-pie -o $t/exe $t/c.o $t/b.so
|
||||
$QEMU $t/exe | grep -q '3 3 1'
|
||||
|
||||
echo OK
|
||||
@@ -1,27 +1,11 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -fPIC -xc -
|
||||
int foo = 4;
|
||||
|
||||
int get_foo() {
|
||||
return foo;
|
||||
}
|
||||
|
||||
void *bar() {
|
||||
return bar;
|
||||
}
|
||||
int get_foo() { return foo; }
|
||||
void *bar() { return bar; }
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o -Wl,-Bsymbolic-functions
|
||||
@@ -29,17 +13,15 @@ $CC -B. -shared -o $t/b.so $t/a.o -Wl,-Bsymbolic-functions
|
||||
cat <<EOF | $CC -c -o $t/c.o -xc - -fno-PIE
|
||||
#include <stdio.h>
|
||||
|
||||
extern int foo;
|
||||
int foo = 3;
|
||||
int x = 5;
|
||||
int get_foo();
|
||||
void *bar();
|
||||
void *bar() { return &x; }
|
||||
|
||||
int main() {
|
||||
foo = 3;
|
||||
printf("%d %d %d\n", foo, get_foo(), bar == bar());
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -no-pie -o $t/exe $t/c.o $t/b.so
|
||||
$QEMU $t/exe | grep -q '3 3 0'
|
||||
|
||||
echo OK
|
||||
+5
-27
@@ -1,27 +1,9 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o$t/a.o -xc -
|
||||
int foo = 4;
|
||||
|
||||
int get_foo() {
|
||||
return foo;
|
||||
}
|
||||
|
||||
void *bar() {
|
||||
return bar;
|
||||
}
|
||||
int get_foo() { return foo; }
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -fPIC -o $t/b.so $t/a.o -Wl,-Bsymbolic
|
||||
@@ -29,17 +11,13 @@ $CC -B. -shared -fPIC -o $t/b.so $t/a.o -Wl,-Bsymbolic
|
||||
cat <<EOF | $CC -c -o $t/c.o -xc - -fno-PIE
|
||||
#include <stdio.h>
|
||||
|
||||
extern int foo;
|
||||
int foo = 3;
|
||||
int get_foo();
|
||||
void *bar();
|
||||
|
||||
int main() {
|
||||
foo = 3;
|
||||
printf("%d %d %d\n", foo, get_foo(), bar == bar());
|
||||
printf("%d %d\n", foo, get_foo());
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -no-pie -o $t/exe $t/c.o $t/b.so
|
||||
$QEMU $t/exe | grep -q '3 4 0'
|
||||
|
||||
echo OK
|
||||
$QEMU $t/exe | grep -q '3 4'
|
||||
+1
-14
@@ -1,16 +1,5 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
mkdir -p $t
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# Verify that mold does not crash if no object file is included
|
||||
# in the output. The resulting executable doesn't contain any
|
||||
@@ -25,5 +14,3 @@ rm -f $t/a.a
|
||||
ar rcs $t/a.a $t/a.o
|
||||
|
||||
./mold -o $t/exe $t/a.a
|
||||
|
||||
echo OK
|
||||
Loaded 100 of 1487 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user