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 | ||
|
|
b7bfe418ba | ||
|
|
05f8bcff31 | ||
|
|
d148db3c2d | ||
|
|
e58f55959d | ||
|
|
9507f11f77 | ||
|
|
114fb21b8e | ||
|
|
29f054c49c | ||
|
|
fc38c15897 | ||
|
|
4528abc1c9 | ||
|
|
a83985c3b3 | ||
|
|
0beba29082 | ||
|
|
62ee0b8d72 | ||
|
|
0b42416868 | ||
|
|
6cd5eb93b6 | ||
|
|
ef7c698713 | ||
|
|
97bea18e5a | ||
|
|
b8855f0749 | ||
|
|
78d65510ff | ||
|
|
13808663cc | ||
|
|
645c8f9931 | ||
|
|
1f7553d53b | ||
|
|
cf208a531e | ||
|
|
2fae57c4d2 | ||
|
|
1408e6ab6c | ||
|
|
994af8db20 | ||
|
|
fccf3902e0 | ||
|
|
f667b0c4b9 | ||
|
|
66dfce5d03 | ||
|
|
7c32e613e8 | ||
|
|
a0dc74bb87 | ||
|
|
96524e48fd | ||
|
|
b48d3d3da3 | ||
|
|
899e6c5e6e | ||
|
|
6ae335f379 | ||
|
|
0d2a258e77 | ||
|
|
df521f2a04 | ||
|
|
fb71467574 | ||
|
|
62511ae2c3 | ||
|
|
b23ae9de2a | ||
|
|
ceeec0e1da | ||
|
|
56bacad887 | ||
|
|
a6c0aba80b | ||
|
|
e989b57f8f | ||
|
|
5403df8cfa | ||
|
|
0069fff8a5 | ||
|
|
4a2f6574f4 | ||
|
|
b6bd7b83f0 | ||
|
|
017f2492bf | ||
|
|
c7534f49f1 | ||
|
|
eb2f02c2e2 | ||
|
|
9e303fd8ac | ||
|
|
df1b8d9f56 | ||
|
|
ea779c36a4 | ||
|
|
1a0a242c45 | ||
|
|
2fa850ceeb | ||
|
|
7e56070c77 | ||
|
|
a557877fb5 | ||
|
|
5f2a4a0498 | ||
|
|
099f85a29d | ||
|
|
f0d0cd5b1c | ||
|
|
a96657bcac | ||
|
|
d887569522 | ||
|
|
c625e28e1c | ||
|
|
8b45ffe17a | ||
|
|
c79f58856f | ||
|
|
78f8832acf | ||
|
|
4c760cd87f | ||
|
|
e06e6b474d | ||
|
|
10a9a6bafb | ||
|
|
e6b1e6d7ec | ||
|
|
34bdfeb69f | ||
|
|
4c5cad43fb | ||
|
|
09d12639b4 | ||
|
|
2f38ebd1cc | ||
|
|
136f32ac2e | ||
|
|
cddbdba091 | ||
|
|
99090d0e37 | ||
|
|
9598ba4d25 | ||
|
|
c43f648fa1 | ||
|
|
82564cb571 | ||
|
|
e791b34d50 | ||
|
|
584837e294 | ||
|
|
037d6e6d22 | ||
|
|
f08bc94618 | ||
|
|
14bf89918c | ||
|
|
33d8657270 | ||
|
|
9de22f8945 | ||
|
|
bd5fb9ec9b | ||
|
|
e8453216cf | ||
|
|
c39b9bb100 | ||
|
|
6eac1e60ad | ||
|
|
00aa3124cd | ||
|
|
3ad19a7555 | ||
|
|
d1babd0682 | ||
|
|
8999732344 | ||
|
|
667d1e7d6e | ||
|
|
f189ea805c | ||
|
|
1fc86d7905 | ||
|
|
e54d7e9761 | ||
|
|
538239e1c2 | ||
|
|
3de6eb920c | ||
|
|
91d40e07fc | ||
|
|
70b5a69f31 | ||
|
|
0604f638ea | ||
|
|
00a004d698 | ||
|
|
cef99ce48f | ||
|
|
6f78946a15 | ||
|
|
0a4586ab96 | ||
|
|
ec5da29d17 | ||
|
|
63115cfa34 | ||
|
|
ce849a2e5f | ||
|
|
bbafdc3d23 | ||
|
|
29c4a9c996 | ||
|
|
42e8f9a439 | ||
|
|
e65cc7feea | ||
|
|
340d8b3742 | ||
|
|
8a3b291ee7 | ||
|
|
2d364b0e78 | ||
|
|
a0dffce882 | ||
|
|
694f973dc5 | ||
|
|
8c4109389d | ||
|
|
800d246ec3 | ||
|
|
2eb0258991 | ||
|
|
70bdc9bc94 | ||
|
|
a8bd32d2ea | ||
|
|
ac705872dd | ||
|
|
a3da318ff9 | ||
|
|
a2c3b6fa1c | ||
|
|
5e65a4fa77 | ||
|
|
89038c2c27 | ||
|
|
e2ca959bed | ||
|
|
f787f4bdac | ||
|
|
c760c5daf7 | ||
|
|
4cb4a93d3d | ||
|
|
f6051993e4 | ||
|
|
277d56d6f4 | ||
|
|
a31f95bd9a | ||
|
|
b9b000bed0 | ||
|
|
45b3fd5ecf | ||
|
|
1992af8b51 | ||
|
|
159d4401df | ||
|
|
e6e68945cf | ||
|
|
cb6e1a5b9a | ||
|
|
c794112836 | ||
|
|
7bd86d9c48 | ||
|
|
a9b34e6f03 | ||
|
|
c4b3c28445 | ||
|
|
4682f02a45 | ||
|
|
88edf02912 | ||
|
|
b184ebb73d | ||
|
|
f8d81f0981 | ||
|
|
c945c855ad | ||
|
|
7af3664791 | ||
|
|
ba3903e969 | ||
|
|
f26f1e6192 | ||
|
|
29ca848dc9 | ||
|
|
abb3ee0a62 | ||
|
|
95863f9ccf | ||
|
|
647395ecff | ||
|
|
ab1ee42a9e | ||
|
|
784eaa2d39 | ||
|
|
19a681e06b | ||
|
|
a3b9c26657 | ||
|
|
9a7b63b55a | ||
|
|
15dbcb6d52 | ||
|
|
bdf5373119 | ||
|
|
300f051939 | ||
|
|
507fb3f445 | ||
|
|
63552afa91 | ||
|
|
d47a18bde2 | ||
|
|
454fd916ff | ||
|
|
0f3437a0f0 | ||
|
|
1d09662818 | ||
|
|
cbfc042dae | ||
|
|
14c8a119f4 | ||
|
|
00d9017088 | ||
|
|
71daaa22df | ||
|
|
7d91c754dd | ||
|
|
6901c1ff45 | ||
|
|
8eaf81b0bd | ||
|
|
112b52c300 | ||
|
|
c661a467cb | ||
|
|
9c61fc648d | ||
|
|
e2730c6e0e | ||
|
|
7890bf36b7 | ||
|
|
58fd94d96d | ||
|
|
5a422bd8a1 | ||
|
|
0ef1e49134 | ||
|
|
49ec4671fb | ||
|
|
2406e4cd5f | ||
|
|
0a80016276 | ||
|
|
9005e163bd | ||
|
|
a653478d85 | ||
|
|
a817f8d576 | ||
|
|
b613a4192b | ||
|
|
a5dd93ea7b | ||
|
|
35d94cc046 | ||
|
|
60070e0c13 | ||
|
|
04d76d7929 | ||
|
|
38d2b22f9b | ||
|
|
a436ba36e5 | ||
|
|
17f0f7a7c2 | ||
|
|
41cb428550 | ||
|
|
431211321c | ||
|
|
f6fb88a3ec | ||
|
|
771a97822b | ||
|
|
c1bc12b2db | ||
|
|
4636580508 | ||
|
|
156aad6354 | ||
|
|
6d8dcc9cea | ||
|
|
f341fbfdc9 | ||
|
|
0515b4b100 | ||
|
|
c8ae70d591 | ||
|
|
3a6e1cd79a | ||
|
|
b871a4ff66 | ||
|
|
97737a32ec | ||
|
|
dea48143db | ||
|
|
f57d10dcda | ||
|
|
a4a0592795 | ||
|
|
1bc11579e7 | ||
|
|
19d9255d72 | ||
|
|
6d5e3e7d18 | ||
|
|
053b90b489 | ||
|
|
64d7532b75 | ||
|
|
a8d1e293fe | ||
|
|
b6f4e171c5 | ||
|
|
980219b435 | ||
|
|
bfba468ddd | ||
|
|
f9e3a31d25 | ||
|
|
a2c06b2975 | ||
|
|
a65d1a58be | ||
|
|
bbae3116e4 | ||
|
|
810eac0ff8 | ||
|
|
d0ec2243b1 | ||
|
|
9666041de7 | ||
|
|
6b27a7e9b3 | ||
|
|
000b51a60c | ||
|
|
3c236bdcfb | ||
|
|
b1242f55e9 | ||
|
|
bce2170cfe | ||
|
|
da4c309071 | ||
|
|
47793b0aa4 | ||
|
|
5dd282ded5 | ||
|
|
a595c4874d | ||
|
|
0a8431b930 | ||
|
|
3fbb604577 | ||
|
|
70a64b7391 | ||
|
|
76f6959a8e | ||
|
|
0902dd731f | ||
|
|
18ca557fbd | ||
|
|
9eeae5b295 | ||
|
|
9ea9a3312d | ||
|
|
59542519eb | ||
|
|
a2262989f9 | ||
|
|
0bbad93d02 | ||
|
|
d51c5a197d | ||
|
|
446474e46e | ||
|
|
9afbd99d88 | ||
|
|
f5b0c8d47a | ||
|
|
7d5e4bdd48 | ||
|
|
b575d2ba79 | ||
|
|
dcaef43283 | ||
|
|
1bec466d3b | ||
|
|
b439a2ba36 | ||
|
|
f77220c9fa | ||
|
|
168cc16de2 | ||
|
|
1df6fa8855 | ||
|
|
22c78e112b | ||
|
|
6abf01597e | ||
|
|
e8332bb052 | ||
|
|
01bf7847d3 | ||
|
|
94d6edb7eb | ||
|
|
c434c2ce94 | ||
|
|
ed1166a08f | ||
|
|
76f4c28a1b | ||
|
|
9f05c9e26f | ||
|
|
6bbc3a56ca | ||
|
|
5eaeed25aa | ||
|
|
93e476796b | ||
|
|
f70a86fe45 | ||
|
|
802aad8891 | ||
|
|
3b796819e5 | ||
|
|
4050f5241f | ||
|
|
2ed36aef23 | ||
|
|
10fa6c87ef | ||
|
|
f72b93cc66 | ||
|
|
9daf9f942c | ||
|
|
73ee17337c | ||
|
|
7908eae30a | ||
|
|
e79f21d2e3 | ||
|
|
d35d7bf918 | ||
|
|
27ef381242 | ||
|
|
0bf4d90b9b | ||
|
|
340a302e3c | ||
|
|
6780da9eb0 | ||
|
|
9aa1eb50f4 | ||
|
|
43231abc5c | ||
|
|
d5f9ca608a | ||
|
|
fbd75e2831 | ||
|
|
688ded77ac | ||
|
|
630acaea46 | ||
|
|
9774ff6134 | ||
|
|
c3a901e0f2 | ||
|
|
2721c45b41 | ||
|
|
f0e1237b00 | ||
|
|
5dd122710c | ||
|
|
71c8e5d8c0 | ||
|
|
cd46476c76 | ||
|
|
965b562cdf | ||
|
|
f7703dacd8 | ||
|
|
1c75580a24 | ||
|
|
61dc019c05 | ||
|
|
a681e3add3 | ||
|
|
fde9e852f3 | ||
|
|
e8a915e0dd | ||
|
|
996dc7c9a3 | ||
|
|
6df1f30750 | ||
|
|
c8af11588a | ||
|
|
246f1a1b49 | ||
|
|
08a83c7aaa | ||
|
|
ee68d1f2ff | ||
|
|
8524705a48 | ||
|
|
2ade8e950f | ||
|
|
daf2a5173c | ||
|
|
ae2e177cb4 | ||
|
|
a7d09b7149 | ||
|
|
051dbc0bef | ||
|
|
c3a86f5b24 | ||
|
|
d8ad53cc6d | ||
|
|
b1fff13659 | ||
|
|
78e676cdd0 | ||
|
|
46be0e152b | ||
|
|
b55cd9be14 | ||
|
|
16752b82a3 | ||
|
|
5dbb996b36 | ||
|
|
d0e2437f4a | ||
|
|
b16306806d | ||
|
|
910c82a4f4 | ||
|
|
3a1a8b0dc7 | ||
|
|
635c017400 | ||
|
|
b2c6206aa9 | ||
|
|
e15cdbfad2 | ||
|
|
f7f257a663 | ||
|
|
415673a49e | ||
|
|
0c1c48c3b2 | ||
|
|
eab4f663bf | ||
|
|
3041714a16 | ||
|
|
c622da4a62 | ||
|
|
6ed017919e | ||
|
|
eee1888d2c | ||
|
|
b6eaa8505b | ||
|
|
a998d5e845 | ||
|
|
83e4163ed4 | ||
|
|
6a613a12e2 | ||
|
|
3987ebaf34 | ||
|
|
f8a1d6b3f1 | ||
|
|
863c454979 | ||
|
|
023f1f3596 | ||
|
|
00e7119440 | ||
|
|
cdd10d4e2e | ||
|
|
a937e8fe55 | ||
|
|
3ee4611640 | ||
|
|
76b1845732 | ||
|
|
1a041ffc62 | ||
|
|
b3bc37c479 | ||
|
|
2ea5bd3b02 | ||
|
|
56e82cfc50 | ||
|
|
fb91f8dfc2 | ||
|
|
d4ccc10ed4 | ||
|
|
add94b8626 | ||
|
|
6d4f228238 | ||
|
|
b42d039bd5 | ||
|
|
12ec2b8acc | ||
|
|
637e05bed3 | ||
|
|
e30e8e363a | ||
|
|
4739e5f724 | ||
|
|
5f8609fe7a | ||
|
|
76cb2f917f | ||
|
|
627cb0664c | ||
|
|
9bcf974a11 | ||
|
|
a8345e83c5 | ||
|
|
7177f7442b | ||
|
|
da2f3fe12c | ||
|
|
10c211f1ab | ||
|
|
135e6465c9 | ||
|
|
d3cf7cd239 | ||
|
|
989dacf6d2 | ||
|
|
04e3492d77 | ||
|
|
456071dafd | ||
|
|
ab93c8c1e3 | ||
|
|
2a8eb67089 | ||
|
|
aa78db8193 | ||
|
|
1709fc3ca0 | ||
|
|
7d91674a98 | ||
|
|
8f2768904f | ||
|
|
04dc1a74db | ||
|
|
d553cea606 | ||
|
|
71886e5e2a | ||
|
|
d9db6bcf49 | ||
|
|
123dde1e8f | ||
|
|
172c7f6bb4 | ||
|
|
0ce50e1cf8 | ||
|
|
1121879199 | ||
|
|
d2829e1806 | ||
|
|
0988565c2f | ||
|
|
a70b1eb58b | ||
|
|
bb999c5e93 | ||
|
|
b23c47ae6d | ||
|
|
8cd699c110 | ||
|
|
e3495efad7 | ||
|
|
5414ae7a36 | ||
|
|
bc81472adb | ||
|
|
b83cb924a5 | ||
|
|
536e0e7dca | ||
|
|
b19599f81d | ||
|
|
bbbda7925e | ||
|
|
467294eb17 | ||
|
|
13ef968fb1 | ||
|
|
3deaafca30 | ||
|
|
f998104bb0 | ||
|
|
4a73ca28ca | ||
|
|
070907e367 | ||
|
|
ed72ca3509 | ||
|
|
5a493d4868 | ||
|
|
b2b16e66b4 | ||
|
|
4ed7d72372 | ||
|
|
8143fe2fcc | ||
|
|
65b0aec355 | ||
|
|
12f941c58d | ||
|
|
08cf940a7a | ||
|
|
0e1b60dd6e | ||
|
|
68bd00caa7 | ||
|
|
d10b129b13 | ||
|
|
bedbb1f4e5 | ||
|
|
59f4197edb | ||
|
|
390d0dc634 | ||
|
|
8fb3082529 | ||
|
|
f81337cc8b | ||
|
|
62ee59ecc6 | ||
|
|
54399fe567 | ||
|
|
d3d0c81bc6 | ||
|
|
18063f3ac9 | ||
|
|
5b8f9f1a7a | ||
|
|
a6d9955a08 | ||
|
|
92cbaada34 | ||
|
|
e9b07df4fb | ||
|
|
04909597e9 | ||
|
|
e2c43dcb5e | ||
|
|
853d1f8a6f | ||
|
|
339eeef435 | ||
|
|
4a21d5dca3 | ||
|
|
b824f73912 | ||
|
|
b5ed4a67cb | ||
|
|
0a57aec7bb | ||
|
|
e0906564dc | ||
|
|
1c8778b6e3 | ||
|
|
58f393ac8b | ||
|
|
cd25f6b6c7 | ||
|
|
e115aaef4c | ||
|
|
85217abc7a | ||
|
|
b48ff94ad1 | ||
|
|
23b43d29c5 | ||
|
|
c0e4de2feb | ||
|
|
5900ee701c | ||
|
|
fe09596d7c | ||
|
|
c1195691f3 | ||
|
|
bb1d2147a7 | ||
|
|
b80b94bd96 | ||
|
|
ff087b26a2 | ||
|
|
0a73cf1bfc | ||
|
|
3c56ade25f | ||
|
|
e6a0e67a38 | ||
|
|
d7b42f29f2 | ||
|
|
852bd457d8 | ||
|
|
b66e55c7e5 | ||
|
|
3ba30f7f09 | ||
|
|
f6a85acc07 | ||
|
|
990b0ac4cd | ||
|
|
29af9b5177 | ||
|
|
a8559be1b1 | ||
|
|
c8b0522fa6 | ||
|
|
70d5f60aa1 | ||
|
|
4335732d5d | ||
|
|
fc98d73d7e | ||
|
|
1275b80eb8 | ||
|
|
dabe283e4a | ||
|
|
b8a4df51ca | ||
|
|
2af7e73258 | ||
|
|
ed7e7c7ae0 | ||
|
|
152ee5a261 | ||
|
|
b33c3eb010 | ||
|
|
5a190422cd | ||
|
|
c4eefe267d | ||
|
|
b8dbaff539 | ||
|
|
dd32de31c8 | ||
|
|
9f771dd129 | ||
|
|
764d7575f7 | ||
|
|
04efce2d29 | ||
|
|
e3e371dcd3 | ||
|
|
4f1211f7fb | ||
|
|
4d3e59f716 | ||
|
|
816835e25c | ||
|
|
01f89730e1 | ||
|
|
55379e1142 | ||
|
|
df3f6999a3 | ||
|
|
d9cb06c270 | ||
|
|
31d2a902e5 | ||
|
|
593f2cfdbc | ||
|
|
6a44eadd2f | ||
|
|
0cc9715df1 | ||
|
|
50ccbcb444 | ||
|
|
12c9c53b90 | ||
|
|
4a9e1439f6 | ||
|
|
4aca9d471e | ||
|
|
af4ef3c76c | ||
|
|
a4cc7f358a | ||
|
|
be5fe4c727 | ||
|
|
5634b2287a | ||
|
|
5d5f75c41d | ||
|
|
256c467b05 | ||
|
|
5071c42e72 | ||
|
|
15f1139c31 | ||
|
|
d6526ed9da | ||
|
|
4928e3276d | ||
|
|
2bfc724e42 | ||
|
|
8bd705a3cb | ||
|
|
691892f48a | ||
|
|
22e1bba58e | ||
|
|
808f1240ac | ||
|
|
0e88fd047e | ||
|
|
80dc5cf1fb | ||
|
|
53e07d3a89 | ||
|
|
5fe5294133 | ||
|
|
4af6ff614c | ||
|
|
eaf52ee844 | ||
|
|
ab81c9e51d | ||
|
|
95d0af2958 | ||
|
|
9056011f67 | ||
|
|
ff45af0f34 | ||
|
|
4df062c72e | ||
|
|
721c59aea9 | ||
|
|
9ddbfa863a | ||
|
|
cd2b8f93e4 | ||
|
|
e2e025ed29 | ||
|
|
15441b60d3 | ||
|
|
71c4c90922 | ||
|
|
8d29501887 | ||
|
|
ea308bc77b | ||
|
|
ad427f8629 | ||
|
|
23b90dfc38 | ||
|
|
7f7d10cc89 | ||
|
|
14392e7720 | ||
|
|
3b75398786 | ||
|
|
5688115bb6 | ||
|
|
b1d951a96f | ||
|
|
ff1d5cd245 | ||
|
|
3bd972ad85 | ||
|
|
575a59ef75 | ||
|
|
9a0b7e826b | ||
|
|
ded9cd4ffa | ||
|
|
556e953045 | ||
|
|
4a1d0dfc31 | ||
|
|
73074389ed | ||
|
|
b0c6aa9a0f | ||
|
|
883320d5e7 | ||
|
|
b81f3c22be | ||
|
|
26d45ed4c1 | ||
|
|
b5d7335e35 | ||
|
|
bdee09ad02 | ||
|
|
a6a03b5851 | ||
|
|
8aa05de5e3 | ||
|
|
0e1226287b | ||
|
|
685085f6fe | ||
|
|
582350cc81 | ||
|
|
335698851b | ||
|
|
7b5b1a340b | ||
|
|
3e6f6fe8b5 | ||
|
|
c001b75d90 | ||
|
|
48b782ac6d | ||
|
|
0be1318b7e | ||
|
|
1524746a80 | ||
|
|
2963ab672c | ||
|
|
fe72463f55 | ||
|
|
831f872886 | ||
|
|
d9a28221dd | ||
|
|
62be1fcdb0 | ||
|
|
a7b8da9345 | ||
|
|
9e2e70ce7d | ||
|
|
21a2d6bb6c | ||
|
|
9a3188bcf5 | ||
|
|
9cd811393f | ||
|
|
8b86c49f42 | ||
|
|
52943009b7 | ||
|
|
00c16556de | ||
|
|
b8f6fe1a1a | ||
|
|
a9f063a394 | ||
|
|
b0ad0048b2 | ||
|
|
37d798f064 | ||
|
|
a9a8205be2 | ||
|
|
0bbc1808cc | ||
|
|
cd3d82c89e | ||
|
|
52c8e09ddb | ||
|
|
c214bfc96b | ||
|
|
7b8d7a6417 | ||
|
|
cfb7c268f5 | ||
|
|
285ea7ba26 | ||
|
|
603a831f02 | ||
|
|
edfe9f30f2 | ||
|
|
5228fa2d78 | ||
|
|
e4e267cc8b | ||
|
|
75ac73809b | ||
|
|
1b89d52504 | ||
|
|
b05a671789 | ||
|
|
30222dfb3f | ||
|
|
91aca3b7cf | ||
|
|
610e839e9e | ||
|
|
dcbc85b412 | ||
|
|
7133f8a547 | ||
|
|
792773be20 | ||
|
|
13816a14e1 | ||
|
|
8f02509ba6 | ||
|
|
87163c667a | ||
|
|
e45bebd1c7 | ||
|
|
d0cc19735d | ||
|
|
2c9e97c768 | ||
|
|
114cb8e9c8 | ||
|
|
2af30acbb2 | ||
|
|
6f9eedf98a | ||
|
|
e60bf30513 | ||
|
|
610ce3c307 | ||
|
|
f3e1f728d3 | ||
|
|
6f04ef9cf2 | ||
|
|
880204ebe5 | ||
|
|
406d8786c9 | ||
|
|
e11e68d508 | ||
|
|
3af44d08db | ||
|
|
d5b5b9afe1 | ||
|
|
0b537ae54c | ||
|
|
b364b3f453 | ||
|
|
ea31281149 | ||
|
|
ce34654924 | ||
|
|
2d34ba13cb | ||
|
|
eee315ea52 | ||
|
|
d2d73cbcbc | ||
|
|
c786f55370 | ||
|
|
8ff4bd5e0f | ||
|
|
169a22d643 | ||
|
|
4a8cda75ad | ||
|
|
1ebaf5be84 | ||
|
|
a05aae7d7a | ||
|
|
f9ea7890a8 | ||
|
|
0488d95f38 | ||
|
|
6d0d19dffb | ||
|
|
e3e0593162 | ||
|
|
7511aee558 | ||
|
|
6a3c35d97c | ||
|
|
032b4d4cc5 | ||
|
|
3fb8b483b4 | ||
|
|
e14788102c | ||
|
|
96d9859f45 | ||
|
|
56e6c038dc | ||
|
|
1f5da3d313 | ||
|
|
fc1aed3e99 | ||
|
|
4b754348f5 | ||
|
|
eee1a87742 | ||
|
|
1b52d228d4 | ||
|
|
3842222956 | ||
|
|
dce00d511b | ||
|
|
0de862191b | ||
|
|
485877a826 | ||
|
|
7478c27ce8 | ||
|
|
a5c216310b | ||
|
|
c55c98c4f2 | ||
|
|
6b3fc59143 | ||
|
|
6779b2bdfe | ||
|
|
7097946eee | ||
|
|
67ec8f3376 | ||
|
|
0723476fa5 | ||
|
|
a50173c259 | ||
|
|
280782b3ae | ||
|
|
5621394dc3 | ||
|
|
fa420c38d4 | ||
|
|
ae33fd278f | ||
|
|
68a779f76b | ||
|
|
f730dde6e7 | ||
|
|
8cf514408b | ||
|
|
72c8446eb7 | ||
|
|
f5d85e854f | ||
|
|
4e845f3972 | ||
|
|
03af112272 | ||
|
|
66c1433be1 | ||
|
|
621e2edd65 | ||
|
|
03045cb2d0 | ||
|
|
a572b803d2 | ||
|
|
7c52c41d7b | ||
|
|
21fea57d99 | ||
|
|
965fa17b7b | ||
|
|
826bb56d59 | ||
|
|
3f12a843a6 | ||
|
|
e895351481 | ||
|
|
1eac1e6cb4 | ||
|
|
0fab94639f | ||
|
|
348f2bb9da | ||
|
|
2f78d2eb94 | ||
|
|
4826d393d1 | ||
|
|
b82aa99bca | ||
|
|
080ebb9ee4 | ||
|
|
4f77af8e76 | ||
|
|
cffcea8d44 | ||
|
|
4622b6b14c | ||
|
|
395b962c3d | ||
|
|
f0f194e373 | ||
|
|
45615c9d81 | ||
|
|
d9e2669063 | ||
|
|
d31b63e48b | ||
|
|
5612636fe4 | ||
|
|
be19241630 | ||
|
|
2f243d2f52 | ||
|
|
8911458961 | ||
|
|
4aeee88ef8 | ||
|
|
936d3b3c30 | ||
|
|
0a89a07e6d | ||
|
|
79654bfde0 | ||
|
|
379ff3d781 | ||
|
|
c797408167 | ||
|
|
20ad945fbf | ||
|
|
702ec961ff | ||
|
|
5e4f8924fd | ||
|
|
f55fd7d9f2 | ||
|
|
ae29091aa6 | ||
|
|
e9f6715c6e | ||
|
|
7730cb3d25 | ||
|
|
c36291be60 | ||
|
|
c1164cda77 | ||
|
|
e770df42d9 | ||
|
|
14c717d698 | ||
|
|
b32bc66901 | ||
|
|
9e09a21eac | ||
|
|
f5c8d7509e | ||
|
|
95164b3556 | ||
|
|
4d06199e0d | ||
|
|
bd292ef782 | ||
|
|
b626d74a5f | ||
|
|
e7d4d2c39b | ||
|
|
48c9157fb1 | ||
|
|
e37fe163b7 | ||
|
|
4ca2d82da8 | ||
|
|
2850c5cfe6 | ||
|
|
75df1c82f1 | ||
|
|
754b995e1c | ||
|
|
bafa43d4c7 | ||
|
|
760f39d129 | ||
|
|
99b622ffd1 | ||
|
|
7b097e51c9 | ||
|
|
16735e1e68 | ||
|
|
18e24a6e4d | ||
|
|
29e15384ed | ||
|
|
229f610825 | ||
|
|
e9def90e9a | ||
|
|
f844179fd3 | ||
|
|
ccf6b0aa33 | ||
|
|
ca3cd7c3fa | ||
|
|
b5587d930e | ||
|
|
4c034da565 | ||
|
|
a1711b97b3 | ||
|
|
e293d56cc1 | ||
|
|
7f84e4242c | ||
|
|
ffb18422c1 | ||
|
|
33e00f2fb5 | ||
|
|
5c3bbb801d | ||
|
|
def3c95b7f | ||
|
|
bd3fa9a3db | ||
|
|
ef053c41fe | ||
|
|
03cda81af6 | ||
|
|
51f99db269 | ||
|
|
94c100c211 | ||
|
|
943cb99a84 | ||
|
|
eb90d1bcbb | ||
|
|
7156ef7daa | ||
|
|
fb89e06e2b | ||
|
|
6b77349b4a | ||
|
|
2c5582b9e9 | ||
|
|
7ce2c7edb5 | ||
|
|
b5bfe4c607 | ||
|
|
b9827f29e7 | ||
|
|
3ec1e8d9df | ||
|
|
55eb8dee4d | ||
|
|
da22677bf9 | ||
|
|
c654d42c14 | ||
|
|
4b945c839f | ||
|
|
e4b8fa3c26 | ||
|
|
6d67614004 | ||
|
|
1052b93c4f | ||
|
|
0692db3481 | ||
|
|
68f4891107 | ||
|
|
37a50f8996 | ||
|
|
352ab1a3f9 | ||
|
|
519de432c7 | ||
|
|
4d380ec645 | ||
|
|
0326d66b24 | ||
|
|
f392b4aeb3 | ||
|
|
a21a79e846 | ||
|
|
ca6d4a53c4 | ||
|
|
66e585eebe | ||
|
|
deebb497d3 | ||
|
|
466e93ea46 | ||
|
|
3008879d23 | ||
|
|
e8166bcb1b | ||
|
|
27908af67d | ||
|
|
c9288c5b92 | ||
|
|
188f17524f | ||
|
|
2b86786419 | ||
|
|
bcc8375958 | ||
|
|
f0bb588e32 | ||
|
|
3cf8fc4b35 | ||
|
|
64f0ba4a9a | ||
|
|
611fa3c7a4 | ||
|
|
69d23bc10f | ||
|
|
a6c3f3c082 | ||
|
|
64b8168916 | ||
|
|
27409530d7 | ||
|
|
b1c0a13386 | ||
|
|
fcb3bedf0d | ||
|
|
e68fdc396e | ||
|
|
a98a19acfa | ||
|
|
1a8ebf89db | ||
|
|
f086145fc4 | ||
|
|
8cbcd9777f | ||
|
|
2e47fe5815 | ||
|
|
61d19e65e2 | ||
|
|
c64fc2ea6d | ||
|
|
5b4c698a9c | ||
|
|
7f3a05ad0f | ||
|
|
a5c7c98af8 | ||
|
|
00148f4d5e | ||
|
|
9459b0f4f5 | ||
|
|
ee96875fcc | ||
|
|
4bc287ff48 | ||
|
|
73a9b6ec7d | ||
|
|
5cf365b4f5 | ||
|
|
5a239ef4b2 | ||
|
|
344c027a91 | ||
|
|
c1409d95a4 | ||
|
|
adb1ee4c0a | ||
|
|
5e389f99d4 | ||
|
|
c054e3d197 | ||
|
|
53fd03e115 | ||
|
|
99155920af | ||
|
|
05acf9da2c | ||
|
|
4ef90d4316 | ||
|
|
4d61d462ef | ||
|
|
d68c7eb5e4 | ||
|
|
3c284f09ab | ||
|
|
f59bd5ea23 | ||
|
|
b6e9c6da66 | ||
|
|
f8296ba0fc | ||
|
|
90e0228601 | ||
|
|
15eb278ab1 | ||
|
|
c1ba1707fc | ||
|
|
4ae6091442 | ||
|
|
5c63d2e031 | ||
|
|
166a67d5ee | ||
|
|
6b5c47882a | ||
|
|
b8b6d0d3ae | ||
|
|
144c156dc6 | ||
|
|
edc1c68efb | ||
|
|
e6446b3931 | ||
|
|
d1ade871f0 | ||
|
|
72ef8fc73a | ||
|
|
abaceb458f | ||
|
|
d22cdf8540 | ||
|
|
7f9d16e971 | ||
|
|
f48895cbf3 | ||
|
|
bd12a98533 | ||
|
|
ab63994c97 | ||
|
|
537a062d25 | ||
|
|
5d7064c01e | ||
|
|
f822898c2f | ||
|
|
b2cf48de33 | ||
|
|
188a0f1b3c | ||
|
|
e4d6c8c382 | ||
|
|
f787e99f16 | ||
|
|
128b345b9a | ||
|
|
4c758a62b1 | ||
|
|
2a672254a2 | ||
|
|
159d89036e | ||
|
|
407846a8c6 | ||
|
|
d8a8877875 | ||
|
|
448a9ec4e3 | ||
|
|
4819d3dc53 | ||
|
|
f8361f6389 | ||
|
|
596926008a | ||
|
|
9fbd71ec6b | ||
|
|
727d34d727 | ||
|
|
6d1f67fc59 | ||
|
|
6910f8e590 | ||
|
|
ca25f1fd20 | ||
|
|
0dc5ed38ed | ||
|
|
3694ffe61d | ||
|
|
2d3b410a3c | ||
|
|
38f2b965db | ||
|
|
cde4b0fb45 | ||
|
|
ed9a29b713 | ||
|
|
06cdd7e951 | ||
|
|
232dafa8d0 | ||
|
|
31ba99a7cd | ||
|
|
885b5c0e10 | ||
|
|
cd54cacb1b | ||
|
|
eb7433f3ac | ||
|
|
86ad2c5b4d | ||
|
|
68bcc8ea40 | ||
|
|
44228d91b0 | ||
|
|
dbf1040245 | ||
|
|
f223b9c292 | ||
|
|
3e18883b4b | ||
|
|
5090ad6111 | ||
|
|
11cbf9571b | ||
|
|
a15f387526 | ||
|
|
d56ba46cb2 | ||
|
|
dd8e15952f | ||
|
|
22a8b3095c | ||
|
|
5d68c800ed | ||
|
|
a5ff3c8287 | ||
|
|
06405073a9 | ||
|
|
0abd0a40db | ||
|
|
7cfc894914 | ||
|
|
230976a139 | ||
|
|
74ad5da477 | ||
|
|
d0d7f948f0 | ||
|
|
da7b5db350 | ||
|
|
bf902e1dd2 | ||
|
|
12cfddc611 | ||
|
|
1d8c0567a8 | ||
|
|
a9f2a8ddfe | ||
|
|
b5a1d3ace2 | ||
|
|
3499ee6ac7 | ||
|
|
eae062f815 | ||
|
|
2ce5e22193 | ||
|
|
c1c20aa427 | ||
|
|
af592f303c | ||
|
|
d0fc8fb005 | ||
|
|
a85b1f539a | ||
|
|
d3130f3648 | ||
|
|
f48825decb | ||
|
|
920266bce4 | ||
|
|
d714819759 | ||
|
|
517cce0371 | ||
|
|
e5bcbdf487 | ||
|
|
62b3a3dca9 | ||
|
|
fafb75b0e5 | ||
|
|
43cd7a4035 | ||
|
|
708ad63474 | ||
|
|
3f545317c9 | ||
|
|
c8d8f86a52 | ||
|
|
f1f2d40469 | ||
|
|
a15b73a6ee | ||
|
|
bcac1a3c53 | ||
|
|
a63c25b020 | ||
|
|
668b17b3fa | ||
|
|
3b9dc73210 | ||
|
|
1bd48be605 | ||
|
|
eca94d9081 | ||
|
|
615687b2ed | ||
|
|
ee95df673b | ||
|
|
e429773794 | ||
|
|
e89ec11427 | ||
|
|
43ab68b95d | ||
|
|
3455e8bd82 | ||
|
|
9899769713 | ||
|
|
8b584f8297 | ||
|
|
e088db72e0 | ||
|
|
8298c0ad16 | ||
|
|
f378fdc495 | ||
|
|
897f969017 | ||
|
|
17907964ac | ||
|
|
9431704230 | ||
|
|
1b5735543f | ||
|
|
886695d914 | ||
|
|
0fd6a6ca90 | ||
|
|
d391fd9a59 | ||
|
|
face536c39 | ||
|
|
3015f5e418 | ||
|
|
fc9f9e570e | ||
|
|
f1e925d416 | ||
|
|
a761f6a75c | ||
|
|
ea600a752b | ||
|
|
fa6d0c33d7 | ||
|
|
b516828b37 | ||
|
|
5a2589bd94 | ||
|
|
4f4bfdbba0 | ||
|
|
a830fcdb58 | ||
|
|
fd8a6a2c5d | ||
|
|
ac244ede10 | ||
|
|
5d4e7a9e31 | ||
|
|
1cce39f113 | ||
|
|
f9768c6352 | ||
|
|
fe134ab3ab | ||
|
|
f97ff51a72 | ||
|
|
b05bf30e57 | ||
|
|
6baef60a48 | ||
|
|
b101bfbf5f | ||
|
|
f3adbe7c8f | ||
|
|
a8c4c71dd7 | ||
|
|
eb5de9c12f | ||
|
|
23218e7ae2 | ||
|
|
8c5e4df741 | ||
|
|
92505f4aea | ||
|
|
d1a723a53a | ||
|
|
de45e4af6d | ||
|
|
ad3487376c | ||
|
|
3b3333a27f | ||
|
|
9c865b976b | ||
|
|
494b28cfb3 | ||
|
|
d4d1f2c697 | ||
|
|
00381ea64b | ||
|
|
9781d35aba | ||
|
|
ead0c96187 | ||
|
|
eed62bd9d4 | ||
|
|
6d5c44ab58 | ||
|
|
5b28bfd6c8 | ||
|
|
bad0cbc72a | ||
|
|
b6fb244f14 | ||
|
|
1c84145810 | ||
|
|
f945077e42 | ||
|
|
e0dd53b4ad | ||
|
|
267a1ff5b7 | ||
|
|
93cefc5280 | ||
|
|
b3e0955d88 | ||
|
|
c7339c33e0 | ||
|
|
1ac4030556 | ||
|
|
478871c06e | ||
|
|
d18d971962 | ||
|
|
b26e1a3c32 | ||
|
|
f12a9bb5ff | ||
|
|
cf850f8993 | ||
|
|
ec062f7241 | ||
|
|
fc798bc4ea | ||
|
|
bd65e0908f | ||
|
|
8a5a9e1ffe | ||
|
|
c6dace2335 | ||
|
|
32e0863675 | ||
|
|
8398d592de | ||
|
|
fa2ff3b9e9 | ||
|
|
3fb93c7cc8 | ||
|
|
8dddd98dfd | ||
|
|
17f16d016c | ||
|
|
21e1a80cec | ||
|
|
d4cc0ed3fb | ||
|
|
a4398c7c32 | ||
|
|
7ebd071273 | ||
|
|
369e8a19c5 | ||
|
|
d73587033f | ||
|
|
70bd1adbab | ||
|
|
f675faade2 | ||
|
|
3c895abefa | ||
|
|
ff1b97bcdb | ||
|
|
19ad74ac52 | ||
|
|
45df585268 | ||
|
|
deb212bbfc | ||
|
|
1fa6b3cd17 | ||
|
|
b0596ab8fb | ||
|
|
1e59bb518a | ||
|
|
efcbb02e83 | ||
|
|
d8a12490f5 | ||
|
|
b5a75c4215 | ||
|
|
5803c3c200 | ||
|
|
c90801e629 | ||
|
|
8073a92614 | ||
|
|
c4c7e45886 | ||
|
|
6a6ae217a3 | ||
|
|
30a0fac952 | ||
|
|
133946af0c | ||
|
|
c6ae4234b8 | ||
|
|
1f55e40a8a | ||
|
|
f86868cb38 | ||
|
|
421289445a | ||
|
|
100922b650 | ||
|
|
0daf6233fb | ||
|
|
d0e4eee204 | ||
|
|
a7475dd5e3 | ||
|
|
c4af13889d | ||
|
|
7aaa4afce7 | ||
|
|
2fb96ad83f | ||
|
|
a2fa4e5ecc | ||
|
|
68877ab6ca | ||
|
|
f62777e606 | ||
|
|
c80e316649 | ||
|
|
d1b9549817 | ||
|
|
01d2dd8dd9 | ||
|
|
6109154a07 | ||
|
|
49d4dac7e6 | ||
|
|
87a5f238dd | ||
|
|
e448e0eb2b | ||
|
|
4df5d6497a | ||
|
|
e90ad9be16 | ||
|
|
b5788e0caa | ||
|
|
9178e43681 | ||
|
|
5feab826de | ||
|
|
2fc0231348 | ||
|
|
804ae412ed | ||
|
|
f0a030cbcd | ||
|
|
ba6347996b | ||
|
|
a151f4180f | ||
|
|
2c947ce2a9 | ||
|
|
63b7102a4c | ||
|
|
731dd0ed25 | ||
|
|
3105195364 | ||
|
|
23045c6f95 | ||
|
|
2d29f234e2 | ||
|
|
4edaf82691 | ||
|
|
49aa10318c | ||
|
|
2c03ed5673 | ||
|
|
e0bc74ad8a | ||
|
|
3768ca612e | ||
|
|
86d59c29eb | ||
|
|
96a4a1b635 | ||
|
|
006b13aec1 | ||
|
|
91947405fe | ||
|
|
b7123bded5 | ||
|
|
0884f279e0 | ||
|
|
615a24d1dd | ||
|
|
e1e4e9fd9e | ||
|
|
ceed69ff49 | ||
|
|
d90f7e162b | ||
|
|
340528bdc3 | ||
|
|
392781a0e2 | ||
|
|
4e10ee7e50 | ||
|
|
8d94fd6ca3 | ||
|
|
358f244247 | ||
|
|
7a1cbb9b34 | ||
|
|
e6d4154688 | ||
|
|
60eae9ed20 | ||
|
|
7fb1b1b9ec | ||
|
|
0ecd72900e | ||
|
|
9eb4e76daf | ||
|
|
b6876b5640 | ||
|
|
f4753b34bf | ||
|
|
8196199cac | ||
|
|
2a3ee11b7d | ||
|
|
68f2a26987 | ||
|
|
227a6c8a88 | ||
|
|
fe617c3618 | ||
|
|
e3a7c9e017 | ||
|
|
5ee6f89544 | ||
|
|
40c2ad7839 | ||
|
|
5edd07efd9 | ||
|
|
8e5def28ce | ||
|
|
b534d5af27 | ||
|
|
673f531adb | ||
|
|
66d3de438c | ||
|
|
d9ea875677 | ||
|
|
577361478a | ||
|
|
408e2bfee8 | ||
|
|
9ac23730b7 | ||
|
|
bf26753bbd | ||
|
|
8e3cee958b | ||
|
|
05f4a43c8f | ||
|
|
ca227fb8da | ||
|
|
78e4b06cb8 | ||
|
|
94cace7fc1 | ||
|
|
812c59fd5c | ||
|
|
8770bd775b | ||
|
|
89d43f2673 | ||
|
|
632a2ec1b4 | ||
|
|
6af78bf17b | ||
|
|
9360267cc3 | ||
|
|
66b995b5a9 | ||
|
|
94e2d7d6dc | ||
|
|
759aa95618 | ||
|
|
575a604d5d | ||
|
|
ac9a1c4048 | ||
|
|
d5cf590498 | ||
|
|
18656091f6 | ||
|
|
ead32811b3 | ||
|
|
7cdfa31e6c | ||
|
|
a6e4676a71 | ||
|
|
0c88d323c8 | ||
|
|
326150a182 | ||
|
|
339816702d | ||
|
|
146ddd711c | ||
|
|
32d1006e29 | ||
|
|
66a057cce7 | ||
|
|
619ff869e4 | ||
|
|
e7c071ea36 | ||
|
|
3d68824dea | ||
|
|
c98d276916 | ||
|
|
446c461201 | ||
|
|
98edd441ce | ||
|
|
37f6ef2081 | ||
|
|
41cf6d7814 | ||
|
|
bb5a596bc5 | ||
|
|
1b6f301644 | ||
|
|
8a089fee45 | ||
|
|
82e80721ac | ||
|
|
d3dea2f435 | ||
|
|
a20e3b8004 | ||
|
|
c1af9c97e7 | ||
|
|
3999aa8b18 | ||
|
|
b4d9db9db8 | ||
|
|
95c7871c6e | ||
|
|
3f7bffe93c | ||
|
|
ec473c74d7 | ||
|
|
9e11b40a99 | ||
|
|
a4fd7e3819 | ||
|
|
aae3dc3c1e | ||
|
|
ba7967152d | ||
|
|
d6dae3be6d | ||
|
|
83aaa63a49 | ||
|
|
b0d91b9967 | ||
|
|
4ea052591a | ||
|
|
2f03e46bb3 | ||
|
|
6205a1edc3 | ||
|
|
4dea134949 | ||
|
|
eb79859ea2 | ||
|
|
8714b78d48 | ||
|
|
86b142e555 | ||
|
|
e5549c73f0 | ||
|
|
4dae896726 | ||
|
|
83e05dae28 | ||
|
|
99a0aaf133 | ||
|
|
7068c0cd0f | ||
|
|
f49e4024e9 | ||
|
|
567a78dbd5 | ||
|
|
f2d27d8d24 | ||
|
|
dc9573989c | ||
|
|
e469b04f0d | ||
|
|
32e158d340 | ||
|
|
18b6f96756 | ||
|
|
2b7dbb01c4 | ||
|
|
c1c9bdf363 | ||
|
|
d4bbf31db2 | ||
|
|
9b5d404d1e | ||
|
|
4caadefac6 | ||
|
|
8f21cc386e | ||
|
|
f7de9571f8 | ||
|
|
e413433752 | ||
|
|
e67f460bfd | ||
|
|
e281ed88bb | ||
|
|
41986279de | ||
|
|
480efdedf0 | ||
|
|
99e3f959fc | ||
|
|
4ec0bbf048 | ||
|
|
f4c0e321db | ||
|
|
31a5cdf86f | ||
|
|
a44de89991 | ||
|
|
30683645c2 | ||
|
|
ac3ae60184 | ||
|
|
9f34830882 | ||
|
|
4aa4bfa4a6 | ||
|
|
fb6967c0cf | ||
|
|
385c1c494d | ||
|
|
4667be6134 | ||
|
|
3f9868238b | ||
|
|
3e8c519d4e | ||
|
|
aa27d674c1 | ||
|
|
3cbc57f9af | ||
|
|
a1e6d8e8df | ||
|
|
a054bcd4ff | ||
|
|
e47c67727c | ||
|
|
16bdf4009a | ||
|
|
c173f9c0ab | ||
|
|
89612b7096 | ||
|
|
d258140d54 | ||
|
|
6ce142d73a | ||
|
|
3a15c54f10 | ||
|
|
4a6e71096c | ||
|
|
ec9ac181f8 | ||
|
|
ccd475e41d | ||
|
|
7dc6ce72dd | ||
|
|
064be343f5 | ||
|
|
c8853223a3 | ||
|
|
0b7d210f99 | ||
|
|
b22e6f80fc | ||
|
|
15fa05eff6 | ||
|
|
61e0311889 | ||
|
|
825b918146 | ||
|
|
19587d43ee | ||
|
|
f4a9b05114 | ||
|
|
7735cf5b95 | ||
|
|
fcb28161b1 | ||
|
|
b57da5413c | ||
|
|
630b262d2b | ||
|
|
8f0acbf413 | ||
|
|
a3f2a5c7ec | ||
|
|
b5db63f4bb | ||
|
|
c325cc039a | ||
|
|
0b7edc07f4 | ||
|
|
da48efe63b | ||
|
|
3f1b071ae5 | ||
|
|
444cb7a566 | ||
|
|
889f9f3a36 | ||
|
|
e4c4ab053e | ||
|
|
10ff2d705c | ||
|
|
31f7427859 | ||
|
|
c338540885 | ||
|
|
d908d4c61c | ||
|
|
b94dd6dbeb | ||
|
|
a7333bdb4e | ||
|
|
881b55d691 | ||
|
|
28236b1b34 | ||
|
|
7e62ce4bd8 | ||
|
|
b0d75f8957 | ||
|
|
68a35bfa03 | ||
|
|
70ec56f8bb | ||
|
|
e18eaac3eb | ||
|
|
5f17da80d0 | ||
|
|
1663bcf932 | ||
|
|
16a3c9491f | ||
|
|
bab74c0c7e | ||
|
|
5c5d50929e | ||
|
|
ce744aeca4 | ||
|
|
0222dc6f39 | ||
|
|
46f954d23d | ||
|
|
42b3038463 | ||
|
|
c9612d3120 | ||
|
|
60d0900496 | ||
|
|
c1963ce2ac | ||
|
|
1dcd20b401 | ||
|
|
7e91897154 | ||
|
|
61eb265cf4 | ||
|
|
46995bcfc3 | ||
|
|
dd8002f092 | ||
|
|
6ee5aea1bf | ||
|
|
81cf1171a9 | ||
|
|
955c6d1fe4 | ||
|
|
7546b44111 | ||
|
|
d4679063fc | ||
|
|
e8c72767fa | ||
|
|
440e4c4ade | ||
|
|
70afbd1ca7 | ||
|
|
5e9238e3f0 | ||
|
|
7f6bfc1bdb | ||
|
|
ebc2bfafdd | ||
|
|
249c331117 | ||
|
|
818c59e4dd | ||
|
|
ddcb7b4197 | ||
|
|
4a0eef3b14 | ||
|
|
5850f237fe | ||
|
|
0f8bf23bcf | ||
|
|
a2fa0934d0 | ||
|
|
214c35e05d | ||
|
|
e5f4b967ed | ||
|
|
73159e241d | ||
|
|
a1287c26b9 | ||
|
|
36fe7511f1 | ||
|
|
4551fffe69 | ||
|
|
bef545bdb9 | ||
|
|
ab2db49fbe | ||
|
|
e65c5d2a1c | ||
|
|
27c50e8158 | ||
|
|
07a480c43c | ||
|
|
6ffcae4286 | ||
|
|
03e0cef726 | ||
|
|
a4d9bc7550 | ||
|
|
f24b997501 | ||
|
|
f72999a1df | ||
|
|
b02c8cb8f5 | ||
|
|
579fafc04b | ||
|
|
77716836be | ||
|
|
017082f22f | ||
|
|
1abc1f9275 | ||
|
|
12df85ad8c | ||
|
|
1b2b20423e | ||
|
|
afca9d55aa | ||
|
|
043449125d | ||
|
|
26fe71d64c | ||
|
|
354d1b7d2f | ||
|
|
dca8d58b51 | ||
|
|
1550b5afff | ||
|
|
e4c03c238e | ||
|
|
0542a2254b | ||
|
|
d0ab7d9e17 | ||
|
|
dbe99c2884 | ||
|
|
c6ddcc4344 | ||
|
|
bfa4ac2ef8 | ||
|
|
818971841f | ||
|
|
5c123e2c68 | ||
|
|
dc7eedee74 | ||
|
|
2359222723 | ||
|
|
9b90f642a1 | ||
|
|
0acd4b9d2f | ||
|
|
7fcff98f29 | ||
|
|
3c25434202 | ||
|
|
e98aab7ea9 | ||
|
|
a380c1675f | ||
|
|
3232c1f884 | ||
|
|
c6b34c2b77 | ||
|
|
d3140a25f6 | ||
|
|
5859be88b9 | ||
|
|
35c0d4ae9a | ||
|
|
7aa35d12c3 | ||
|
|
8465438051 | ||
|
|
d301b8a3fe | ||
|
|
3f442402f8 | ||
|
|
705451d21e | ||
|
|
f7b2ce278b | ||
|
|
c6e4fb56b4 | ||
|
|
e630dea2ab | ||
|
|
2afe571c07 | ||
|
|
1de4874478 | ||
|
|
00203dbc7c | ||
|
|
190003dac7 | ||
|
|
dcdffbdab7 | ||
|
|
80008796de | ||
|
|
5c89a6d08c | ||
|
|
0c4f8bed1b | ||
|
|
8a0bf96f6d | ||
|
|
7718000dba | ||
|
|
e061e3ca70 | ||
|
|
e06396dd79 | ||
|
|
77d979f112 | ||
|
|
57f996f806 | ||
|
|
81e6a88d58 | ||
|
|
e76f7c0d47 | ||
|
|
32a7e7cb17 | ||
|
|
379ee22af3 | ||
|
|
b879bade6a | ||
|
|
bc66d9a684 | ||
|
|
59826c197b | ||
|
|
33a5888725 | ||
|
|
0d542fb3bc | ||
|
|
229ee85890 | ||
|
|
110a29552a | ||
|
|
6c9bb0d7ff | ||
|
|
6a86370b0e | ||
|
|
cb94a4ab1b | ||
|
|
76b127e824 | ||
|
|
51f7caf263 | ||
|
|
e74760e780 | ||
|
|
614b6c0e38 | ||
|
|
df75bbee0e | ||
|
|
220161b6c6 | ||
|
|
b23f8e067c | ||
|
|
a38111d6bb | ||
|
|
daccce7bb8 | ||
|
|
4fc27d2195 | ||
|
|
708112cd16 | ||
|
|
3322ff6e7c | ||
|
|
42b7eb87a1 | ||
|
|
b4fa51e981 | ||
|
|
dc7432a6dd | ||
|
|
0a0f9b3ad5 | ||
|
|
b2ef045c03 | ||
|
|
e83d1616c8 | ||
|
|
d196ec43e6 | ||
|
|
03ba8f9325 | ||
|
|
79f83561bd | ||
|
|
0653716408 | ||
|
|
6fb64656b9 | ||
|
|
21dd8834d3 | ||
|
|
c6dc8da704 | ||
|
|
f8e7b157c7 | ||
|
|
9ddc188f97 | ||
|
|
6671d5b03b | ||
|
|
10e666ce0f | ||
|
|
27f5b12ee2 | ||
|
|
f7b00b7adb | ||
|
|
ca3d5f49d2 | ||
|
|
1d0a854957 | ||
|
|
d1151a9528 | ||
|
|
1ae6c55c52 | ||
|
|
f7ce2f6748 | ||
|
|
c6b89f9766 | ||
|
|
8eae808223 | ||
|
|
31ca2cc2ae | ||
|
|
5cf2328a8b | ||
|
|
ac93f8dfd1 | ||
|
|
28c36e58b7 | ||
|
|
ba6ccd109c | ||
|
|
e9a5d1cee3 | ||
|
|
4ee3062054 | ||
|
|
3f4e570320 | ||
|
|
d0c1c4db19 | ||
|
|
05e1054058 | ||
|
|
036fdf0b29 | ||
|
|
2b034335ec | ||
|
|
be6ae07ac1 | ||
|
|
e3359ea415 | ||
|
|
2cc85cc21d | ||
|
|
9c0a55a2eb | ||
|
|
8b1905453d | ||
|
|
fa0cb30d8f | ||
|
|
2a4d8b4f72 | ||
|
|
92876820cb | ||
|
|
02c9fa82d4 | ||
|
|
1b01448f01 | ||
|
|
dfeb17a5c8 | ||
|
|
d8e3980b86 | ||
|
|
0540f2c10b | ||
|
|
809652d740 | ||
|
|
be41fdbc21 | ||
|
|
fbd4e559e6 | ||
|
|
c34c77b9a5 | ||
|
|
a4d81fb723 | ||
|
|
948248b732 | ||
|
|
807679cb0a | ||
|
|
67179950d0 | ||
|
|
af05107964 | ||
|
|
fe209f3f4c | ||
|
|
e6d1d011ef | ||
|
|
fe7bc2a846 | ||
|
|
9453488424 | ||
|
|
267125e5b1 | ||
|
|
43b2a1e423 | ||
|
|
04ba8f4cbd | ||
|
|
c747162717 | ||
|
|
3ad29b230c | ||
|
|
7a8c14a634 | ||
|
|
021463c628 | ||
|
|
1152b6eb38 | ||
|
|
27be74e800 | ||
|
|
709ed92ac0 | ||
|
|
faa75ee9e7 | ||
|
|
3411511a8b | ||
|
|
12fcc76ff0 | ||
|
|
0c19046721 | ||
|
|
bd6afa1b23 | ||
|
|
5fc0e503e1 | ||
|
|
8e709d2b62 | ||
|
|
7fa15fd17e | ||
|
|
a80524b478 | ||
|
|
2ee2243c63 | ||
|
|
bba506d6b4 | ||
|
|
d8dc8a6787 | ||
|
|
a9cca00933 | ||
|
|
d3cd2e780f | ||
|
|
1e81c772ae | ||
|
|
d2363f1819 | ||
|
|
c3466b9021 | ||
|
|
011a7d35d6 | ||
|
|
9d27ee0839 | ||
|
|
299078aefe | ||
|
|
74e0e4f6d0 | ||
|
|
60c134893c | ||
|
|
e78608a37a | ||
|
|
d2c57e99ca | ||
|
|
bb18b29e13 | ||
|
|
4b6ed1dbf7 | ||
|
|
cea6a569af | ||
|
|
205138f16e | ||
|
|
342ab00ac3 | ||
|
|
24b629a216 | ||
|
|
cafc57fa65 | ||
|
|
ce5749ce2f | ||
|
|
b6316ef085 | ||
|
|
2464a5551a | ||
|
|
cf7cc2bd52 | ||
|
|
b18d488104 | ||
|
|
9ca6a9dc5e | ||
|
|
17bdd95936 | ||
|
|
31a43a7ba6 | ||
|
|
778f8629eb | ||
|
|
e29bd8f42b | ||
|
|
013e6ea06e | ||
|
|
27d836180f | ||
|
|
9b710cf832 | ||
|
|
0370e7f1b3 | ||
|
|
fbfa01dcd1 | ||
|
|
18367e69a8 | ||
|
|
badc733488 | ||
|
|
54e71f43d4 | ||
|
|
329e382c6d | ||
|
|
644fdc8a2f | ||
|
|
c9352710f7 | ||
|
|
f4a37b2e95 | ||
|
|
3dd76cd953 | ||
|
|
d86a774ebf | ||
|
|
e7decd2c77 | ||
|
|
4324cb2998 | ||
|
|
4348417dd8 | ||
|
|
0e17dbeda8 | ||
|
|
3b33420fe5 | ||
|
|
e075e622ba | ||
|
|
092286fc1d | ||
|
|
9004c514da | ||
|
|
a27c113d35 | ||
|
|
1acabd65e3 | ||
|
|
2f86fef3a0 | ||
|
|
79e397ea03 | ||
|
|
daa88f2f06 | ||
|
|
404fa1288b | ||
|
|
c33df4771f | ||
|
|
8f23d17ece | ||
|
|
09b9d0af1d | ||
|
|
43fa021d48 | ||
|
|
907f713e51 | ||
|
|
276b9d005d | ||
|
|
fe14a08761 | ||
|
|
3865a10600 | ||
|
|
df865725ed | ||
|
|
be49d673a5 | ||
|
|
31a6a1164e | ||
|
|
e8bdbaab22 | ||
|
|
285c9e2c32 | ||
|
|
a5029d19a8 | ||
|
|
0565a6a6ff | ||
|
|
6987f06151 | ||
|
|
a2839f60dd | ||
|
|
1bc7c332ab | ||
|
|
8ffe4cd472 | ||
|
|
70704c7c09 | ||
|
|
5029a6d044 | ||
|
|
9894b3173b | ||
|
|
4b2f10d009 | ||
|
|
8590fd9947 | ||
|
|
7bb478b683 | ||
|
|
ce2fee10b5 | ||
|
|
4033b53d7f | ||
|
|
8c0e2378c9 | ||
|
|
3fb54162d6 | ||
|
|
571a4f72a4 | ||
|
|
2b818dbe44 | ||
|
|
19dce5342f | ||
|
|
632fe22240 | ||
|
|
5618038463 | ||
|
|
9178b762fc | ||
|
|
fea59750ed | ||
|
|
fba4cb9114 | ||
|
|
b82badd127 | ||
|
|
d3dc8a2377 | ||
|
|
df24372775 | ||
|
|
0a8231b59d | ||
|
|
1f7727975c | ||
|
|
68fa44d399 | ||
|
|
ab83ed8691 | ||
|
|
ebe982c443 | ||
|
|
4ccbd24c60 | ||
|
|
da0d0a7b52 | ||
|
|
a537509aa6 | ||
|
|
865b6fed89 | ||
|
|
d100a735a0 | ||
|
|
902f23c456 | ||
|
|
c92e192e56 | ||
|
|
440ff27e58 | ||
|
|
085e3c3abe | ||
|
|
58e43633fc | ||
|
|
d743acb6cd | ||
|
|
cb2b4b8f72 | ||
|
|
f0874f187a | ||
|
|
c029521b24 | ||
|
|
7a8beac1c2 | ||
|
|
b5172caeb5 | ||
|
|
baff2948d4 | ||
|
|
66c9659f5a | ||
|
|
9697febd83 | ||
|
|
f29a85f20a | ||
|
|
cc9f6f430b | ||
|
|
7776165ef7 | ||
|
|
f6e91df440 | ||
|
|
3f6e889449 | ||
|
|
8d838e4fd1 | ||
|
|
68c4b76352 | ||
|
|
f6e800655b | ||
|
|
35ca107361 | ||
|
|
3f8f2e89e5 | ||
|
|
d49c50ddaf | ||
|
|
44ae92c4ed | ||
|
|
ec628cb0bf | ||
|
|
0dcc910223 | ||
|
|
1c6c8c3479 | ||
|
|
99866f01b2 | ||
|
|
f3766cda81 | ||
|
|
946e0c3acd | ||
|
|
e1ce8994b9 | ||
|
|
b151de6684 | ||
|
|
4bfaa964a7 | ||
|
|
cfed33ee42 | ||
|
|
aa972e5351 | ||
|
|
27509f0408 | ||
|
|
f943da88d7 | ||
|
|
e8dcecfff0 | ||
|
|
cfccfc247d | ||
|
|
04ad22d326 | ||
|
|
1228bf3865 | ||
|
|
31b0248b05 | ||
|
|
d1161137d9 | ||
|
|
888474b59c | ||
|
|
5c35d2a41f | ||
|
|
eaa8bf8b30 | ||
|
|
0d3c9811c1 | ||
|
|
6e290aab3e | ||
|
|
d1a47ee57b | ||
|
|
7aa5c393e4 | ||
|
|
7b3c640472 | ||
|
|
d9606d65b5 | ||
|
|
13245fd9a8 | ||
|
|
04ccd4dbdd | ||
|
|
5601cf4236 | ||
|
|
088912e2fc | ||
|
|
29d956a7b0 | ||
|
|
1ba6cc9188 | ||
|
|
27f0e8c0e5 | ||
|
|
92bc33eed4 | ||
|
|
6160f3874f | ||
|
|
135f17c5aa | ||
|
|
d60e302f70 | ||
|
|
d858068975 | ||
|
|
8c86c28496 | ||
|
|
76407a6646 | ||
|
|
56f63dd295 | ||
|
|
c8cec61e1c | ||
|
|
feb63f7b5b | ||
|
|
4d5e4730a2 | ||
|
|
24bf92c27c | ||
|
|
8bc57363c8 | ||
|
|
e65560f963 | ||
|
|
9326f90d14 | ||
|
|
390e737dea | ||
|
|
358464d5ad | ||
|
|
5bc8bc9d64 | ||
|
|
74fdf90d70 | ||
|
|
530568e662 | ||
|
|
f9ff04866e | ||
|
|
96c44be33d | ||
|
|
2bd3bc6d47 | ||
|
|
fb89ce22fd | ||
|
|
3e397d6e78 | ||
|
|
c824e7831a | ||
|
|
8f1c328d1a | ||
|
|
4d78510cb0 | ||
|
|
dbc9869d7e | ||
|
|
27147dfe32 | ||
|
|
665bffa52f | ||
|
|
65dc32fdb8 | ||
|
|
0621402e0a | ||
|
|
1d6d0e2fe3 | ||
|
|
4f37c3d024 | ||
|
|
072cf0eb30 | ||
|
|
455e729393 | ||
|
|
fbee0ec588 | ||
|
|
9a96dc1c33 | ||
|
|
36633895e5 | ||
|
|
1baac27e8c | ||
|
|
a9f9163d2c | ||
|
|
e4fc96f691 | ||
|
|
d3adb6ca09 | ||
|
|
46f22a2778 | ||
|
|
75bd303b15 | ||
|
|
1c9eea0033 | ||
|
|
c77271aa56 | ||
|
|
04d5abc9eb | ||
|
|
031ee33093 | ||
|
|
307552a70c | ||
|
|
f17a2eb941 | ||
|
|
613ca6fc80 | ||
|
|
e610f62818 | ||
|
|
6fd76f2619 | ||
|
|
ed9924895d | ||
|
|
27f72ac320 | ||
|
|
ba1b63eb76 | ||
|
|
9c651d513a | ||
|
|
d9903d72c7 | ||
|
|
c9a2fd1d7d | ||
|
|
e99baebc49 | ||
|
|
0c6458da26 | ||
|
|
770b7312f5 | ||
|
|
f05c3162cd | ||
|
|
d821445304 | ||
|
|
100ed91a78 | ||
|
|
733bb6354f | ||
|
|
10a318d5ba | ||
|
|
e394fa73d4 | ||
|
|
54d75153d2 | ||
|
|
84cd674ff1 | ||
|
|
58b74bf961 | ||
|
|
381ff9ee48 | ||
|
|
b6549eb4c9 | ||
|
|
22116629f1 | ||
|
|
0328f37b2a | ||
|
|
c5f5e6153f | ||
|
|
6fae1d8555 | ||
|
|
f6609474b6 | ||
|
|
83721464ed | ||
|
|
da13b1d3a6 | ||
|
|
efefba2bf3 | ||
|
|
cc35d725f2 | ||
|
|
a3e0e9f197 | ||
|
|
a3fd0ea5a4 | ||
|
|
6a3ddf1ab4 | ||
|
|
a7cae9b2a4 | ||
|
|
d8e25b9753 | ||
|
|
a415accbc3 | ||
|
|
d6a9005b14 | ||
|
|
bf7366438b | ||
|
|
412e3aa136 | ||
|
|
d23183ed31 | ||
|
|
fa8d5d97d0 | ||
|
|
16921c78ea | ||
|
|
790703b5fa | ||
|
|
4878b8bca2 | ||
|
|
bdb01432df | ||
|
|
6dfede035b | ||
|
|
4bd92c0d46 | ||
|
|
57d7a2e8dd | ||
|
|
8ceaf53728 | ||
|
|
ee05f3a65f | ||
|
|
7156dd265f | ||
|
|
9e7e41cfac | ||
|
|
4998a130e5 | ||
|
|
39fbd5b29c | ||
|
|
e99727f87f | ||
|
|
0ee3986da6 | ||
|
|
1219d3250c | ||
|
|
8ae43210e7 | ||
|
|
5deb697ec4 | ||
|
|
a4a21b6a92 | ||
|
|
c3c832fffa | ||
|
|
b32f4592fa | ||
|
|
c8f1766b71 | ||
|
|
411706d147 | ||
|
|
72cea9a0bf | ||
|
|
5804d6b4b2 | ||
|
|
af4a4af038 | ||
|
|
602ec8d0c0 | ||
|
|
efb6e6729a | ||
|
|
6087782660 | ||
|
|
cee6412e3d | ||
|
|
1e4262753a | ||
|
|
39676ef7a9 | ||
|
|
8b41bb923a | ||
|
|
e2e344ea7a | ||
|
|
94889d7a02 | ||
|
|
cd404f72cd | ||
|
|
c13da97044 | ||
|
|
0ac8821015 | ||
|
|
40b0e08d35 | ||
|
|
15215b1f71 | ||
|
|
18cfe1f4ca | ||
|
|
750d6ca3ce | ||
|
|
06bc828a9b | ||
|
|
91b9f925d6 | ||
|
|
433666a470 | ||
|
|
d0138a404d | ||
|
|
75c516f3cd | ||
|
|
fcd592e53d | ||
|
|
4a1ec53c24 | ||
|
|
2b807ddf83 | ||
|
|
66b95013f9 | ||
|
|
a5e3c681bc | ||
|
|
2d5cb51fe2 | ||
|
|
03b9e1b914 | ||
|
|
132dd7a7f2 | ||
|
|
25a902c45a | ||
|
|
be6824b14d | ||
|
|
026f7b9abc | ||
|
|
bec8166a77 | ||
|
|
9f6db83054 | ||
|
|
87c4c1df15 | ||
|
|
92f56aa73f | ||
|
|
3a24532d2a | ||
|
|
cd73b40635 | ||
|
|
72587e643f | ||
|
|
ea6d310576 | ||
|
|
0cb34615d9 | ||
|
|
c89448d17c | ||
|
|
0f0f747135 | ||
|
|
fd5d95f9d0 | ||
|
|
a9fea29aaf | ||
|
|
6d709de396 | ||
|
|
b78a62c7ab | ||
|
|
d88b4c1e86 | ||
|
|
a4bdd81966 | ||
|
|
97ed33758e | ||
|
|
f7b2558b0e | ||
|
|
f232e857f0 | ||
|
|
977ebe5256 | ||
|
|
cbc990d459 | ||
|
|
cd47a3b2be | ||
|
|
f5dbb8112c | ||
|
|
247655179f | ||
|
|
3f2124e612 | ||
|
|
27ed76ddac | ||
|
|
f90a55cec9 | ||
|
|
06f377806b | ||
|
|
32f30c304e | ||
|
|
3d02a3b20b | ||
|
|
0f2dbbc09d | ||
|
|
bfa50ac015 | ||
|
|
23bbb0fcd2 | ||
|
|
32cdec42cb | ||
|
|
ac8597dee0 | ||
|
|
9cef61debe | ||
|
|
2524c6448e | ||
|
|
c5f3a92f51 | ||
|
|
00e45a4ba1 | ||
|
|
51779b2a35 | ||
|
|
76a92b1a10 | ||
|
|
2af5ec006e | ||
|
|
581c445062 | ||
|
|
28984b5362 | ||
|
|
4de3bb9a78 | ||
|
|
95f8035ec0 | ||
|
|
fe9756c9b4 | ||
|
|
9cc0a2e5be | ||
|
|
bfec2435da | ||
|
|
fa5688737c | ||
|
|
1f5400071e | ||
|
|
fb31927a33 | ||
|
|
9820a60374 | ||
|
|
3c297c2254 | ||
|
|
ec5c85dcc7 | ||
|
|
1ab9f39f70 | ||
|
|
01fb761d98 | ||
|
|
3b47e90274 | ||
|
|
631412844c | ||
|
|
0a6d3986cd | ||
|
|
4afc68d74d | ||
|
|
5ec27ff6c8 | ||
|
|
c227286dd5 | ||
|
|
16d904a198 | ||
|
|
e1ab83155a | ||
|
|
3fba4dabd6 | ||
|
|
d3ef7e754d | ||
|
|
4c60f05ca5 | ||
|
|
e1909a541f | ||
|
|
b1440067cc | ||
|
|
58f6502a1c | ||
|
|
9973e51b92 | ||
|
|
30dd7b0230 | ||
|
|
bfc43ef52b | ||
|
|
495df9ea00 | ||
|
|
4bf1ba34f6 | ||
|
|
7236d14650 | ||
|
|
04fcb66580 | ||
|
|
6b02b3d9aa | ||
|
|
6a83705a68 | ||
|
|
2ec3b16ffd | ||
|
|
0bdb744918 | ||
|
|
cd3726b394 | ||
|
|
6cdfd45134 | ||
|
|
5549a005f1 | ||
|
|
5837fe75eb | ||
|
|
d7fb95fa63 | ||
|
|
8471f47abb | ||
|
|
48e7616263 | ||
|
|
2589190724 | ||
|
|
e12055de49 | ||
|
|
986516545a | ||
|
|
ee0ee2375d | ||
|
|
ed2a7ba1ed | ||
|
|
07bf64d9cb | ||
|
|
d6657a3cb0 | ||
|
|
5817265e91 | ||
|
|
a86ad2771a | ||
|
|
31e49a08a0 | ||
|
|
2e07e62ffd | ||
|
|
638bcfe311 | ||
|
|
5450debfce | ||
|
|
948f4d2605 | ||
|
|
cb9d159738 | ||
|
|
53fe663791 | ||
|
|
e7f465bf16 | ||
|
|
5c8481f1ca | ||
|
|
2e92fe2e2a | ||
|
|
9d81e7e1a3 | ||
|
|
78ff2bb73a | ||
|
|
31373b1d6d | ||
|
|
c56a83c405 | ||
|
|
94cda25277 | ||
|
|
b3528e0aee | ||
|
|
ecf17abfd3 | ||
|
|
be147aea80 | ||
|
|
07f6e9517d | ||
|
|
ba8e305fdb | ||
|
|
0fbbef7858 | ||
|
|
857aef3244 | ||
|
|
1f5bc4b3ee | ||
|
|
ef943d5f9a | ||
|
|
a70fa4f6b1 | ||
|
|
8c8aaf4133 | ||
|
|
0b233ee1e6 | ||
|
|
1a70f94dcd | ||
|
|
e66be5010f | ||
|
|
7de2e86045 | ||
|
|
dc22d42e96 | ||
|
|
bab46b211a | ||
|
|
4b6407dfc0 | ||
|
|
59139a778d | ||
|
|
a599ce318e | ||
|
|
5bac5f3c60 | ||
|
|
794882435f | ||
|
|
d599ebe3b9 | ||
|
|
834d0045ae | ||
|
|
0430c806c4 | ||
|
|
12ea4e69f1 | ||
|
|
43ff20c0a8 | ||
|
|
d29e75ac7a | ||
|
|
ee158a031d | ||
|
|
95ae34887c | ||
|
|
3b357976df | ||
|
|
577199ce64 | ||
|
|
71a51d5775 | ||
|
|
815707e66c | ||
|
|
e7cc301f17 | ||
|
|
2e32498ef1 | ||
|
|
dbf3594146 | ||
|
|
baec02dc03 | ||
|
|
7769a6d2bc | ||
|
|
951eca1680 | ||
|
|
e28838031c | ||
|
|
5d910222bf | ||
|
|
86255ca6bb | ||
|
|
d200431602 | ||
|
|
6f2a13544b | ||
|
|
761a4d953b | ||
|
|
6d23937bc7 | ||
|
|
b7993a5f74 | ||
|
|
88e714db09 | ||
|
|
b66f6e0e61 | ||
|
|
9605844ccf | ||
|
|
45406823c6 | ||
|
|
465ff1e238 | ||
|
|
43a1742460 | ||
|
|
075ac54cd0 | ||
|
|
1ec2e78306 | ||
|
|
1e39d9f8c8 | ||
|
|
42258f0225 | ||
|
|
983cf8f203 | ||
|
|
63b4e583dc | ||
|
|
0393fc2fda | ||
|
|
67f8782b53 | ||
|
|
fa10e60189 | ||
|
|
e218692672 | ||
|
|
4fee9df38d | ||
|
|
de34da86a0 | ||
|
|
97ab328bd7 | ||
|
|
4e30c31e17 | ||
|
|
1e63f6fbf6 | ||
|
|
c3e3cfaf44 | ||
|
|
0426f3d1de | ||
|
|
31e9cea7d2 | ||
|
|
06404b96ba | ||
|
|
964702c385 | ||
|
|
3faa340895 | ||
|
|
369c19fdf6 | ||
|
|
4ab5e09974 | ||
|
|
f8ff631605 | ||
|
|
2fcbb582ed | ||
|
|
f382337eb5 | ||
|
|
f64446c06f | ||
|
|
d8d733d211 | ||
|
|
81581a475d | ||
|
|
8124a07e07 | ||
|
|
d72c1bf62c | ||
|
|
34485cc1ec | ||
|
|
8097c09c85 | ||
|
|
a89c7e528f | ||
|
|
8639fc8a9b | ||
|
|
031de0df32 | ||
|
|
43f2e46f34 | ||
|
|
bb67c78df8 | ||
|
|
a0a162a48c | ||
|
|
d176ed8fbb | ||
|
|
96b07d875d | ||
|
|
c1c8270e45 | ||
|
|
e6c614d69c | ||
|
|
ffb8e03c97 | ||
|
|
06c237838c | ||
|
|
e942f5e28e | ||
|
|
038f42a579 | ||
|
|
69ce692671 | ||
|
|
d76c6966b0 | ||
|
|
f7d3fda88a | ||
|
|
45fdfd4e87 | ||
|
|
8ecb117997 | ||
|
|
0b5b5dbcf5 | ||
|
|
071c74acb3 | ||
|
|
86c8b77dd6 | ||
|
|
91ec5ae972 | ||
|
|
14cadf44ac | ||
|
|
a414c03bf3 | ||
|
|
a8b2f521f7 | ||
|
|
d71c33b3e8 | ||
|
|
301d45b2fa | ||
|
|
f49265c788 | ||
|
|
510ec4b003 | ||
|
|
45788bd8ad | ||
|
|
e2d7cd7b03 | ||
|
|
627fbc3ec0 | ||
|
|
3c513a31cc | ||
|
|
7fc365a657 | ||
|
|
dc9cfc4f41 | ||
|
|
e36cf0271f | ||
|
|
46c20da35e | ||
|
|
27e52a430b | ||
|
|
46d3e12946 | ||
|
|
a861f192ec | ||
|
|
bd259a795f | ||
|
|
2da5057d58 | ||
|
|
d111361a1f | ||
|
|
46868805e2 | ||
|
|
e1e603f633 | ||
|
|
0a130212cb | ||
|
|
7dd9a38b3b | ||
|
|
e7f3e94f30 | ||
|
|
2dc8ac0bdd | ||
|
|
4932daadba | ||
|
|
74244c3c9c | ||
|
|
8965de697b | ||
|
|
89d5e9e3b4 | ||
|
|
fd62bbfacb | ||
|
|
e6edf2002d | ||
|
|
f68e301c30 | ||
|
|
745792b34d | ||
|
|
7933e44493 | ||
|
|
840d19bb5e | ||
|
|
ca9a6d0843 | ||
|
|
de1db98cde | ||
|
|
7b251e3d7b | ||
|
|
30b45d2263 | ||
|
|
3fc9c6c3ed | ||
|
|
97ff050396 | ||
|
|
e89af3ee94 | ||
|
|
77069156cb | ||
|
|
0c42930d55 | ||
|
|
928c39937a | ||
|
|
001cf042d9 | ||
|
|
fcd10254b7 | ||
|
|
bc3f556055 | ||
|
|
0426ec6d2f | ||
|
|
19b1b0252c | ||
|
|
ebc8a68cb3 | ||
|
|
4a65748851 | ||
|
|
56b7c42fae | ||
|
|
52c7793326 | ||
|
|
7c9205f68d | ||
|
|
ffd34678e0 | ||
|
|
a6c75e969d | ||
|
|
a2c9d0ad4d | ||
|
|
aa46df7735 | ||
|
|
d123009460 | ||
|
|
cc5cd031ab | ||
|
|
e3d47ddafa | ||
|
|
4f69f53cae | ||
|
|
99fba5995a | ||
|
|
9d805f5d26 | ||
|
|
5e3f526bc1 | ||
|
|
c74b2197f9 | ||
|
|
dad3306d1f | ||
|
|
2844f1f573 | ||
|
|
bc2bd0e26a | ||
|
|
3d95b0e1d4 | ||
|
|
5bd9edeeed | ||
|
|
c40b9aea5f | ||
|
|
1fb421d82c | ||
|
|
541399911d | ||
|
|
67990f775b | ||
|
|
58f1fbadc3 | ||
|
|
6536663130 | ||
|
|
774fc0f340 | ||
|
|
a5d02b18c7 | ||
|
|
e4861bb3e1 | ||
|
|
d11ecda08c | ||
|
|
23ab97351f | ||
|
|
326831837d | ||
|
|
bdd458241b | ||
|
|
fc2ee0d0b0 | ||
|
|
c002284cd8 | ||
|
|
56fb6ba443 | ||
|
|
c66bd302bd | ||
|
|
17c67418f6 | ||
|
|
935194d5ae | ||
|
|
02ce171aba | ||
|
|
a52179d090 | ||
|
|
1390a7f622 | ||
|
|
a05d073018 | ||
|
|
c0daab6075 | ||
|
|
18fb0d5d3f | ||
|
|
5f5139ad6c | ||
|
|
046324b64c | ||
|
|
48c16571d1 | ||
|
|
1127061d54 | ||
|
|
fa2f54b358 | ||
|
|
afbc46e135 | ||
|
|
e3cebd981e | ||
|
|
9881a61cb3 | ||
|
|
43e513d7af | ||
|
|
08f049434f | ||
|
|
a71f70708c | ||
|
|
7f671f5390 | ||
|
|
d84b813cb3 | ||
|
|
fabd425f67 | ||
|
|
fbf3a1a24e | ||
|
|
76874d84ba | ||
|
|
ded5d5e6a1 | ||
|
|
2d5e6888f9 | ||
|
|
3a503ce2cc | ||
|
|
1f3f019a59 | ||
|
|
f55fc5f082 | ||
|
|
fb0b316c81 | ||
|
|
1ebfc28aea | ||
|
|
da5effd52d | ||
|
|
e8aa31cd8b | ||
|
|
c7243f6c5c | ||
|
|
2500ae7e27 | ||
|
|
5af8995471 | ||
|
|
aa5061d784 | ||
|
|
4b79c858dd | ||
|
|
d2aec43b66 | ||
|
|
76b767df4b | ||
|
|
1dc41ab2d0 | ||
|
|
c6d3370f07 | ||
|
|
40a689011e | ||
|
|
03d73f2e9e | ||
|
|
142a9c2703 | ||
|
|
49233b1ef6 | ||
|
|
958d79fa6b | ||
|
|
079900fa84 | ||
|
|
211bc1810b | ||
|
|
0d60940b40 | ||
|
|
1e3aff2d71 | ||
|
|
7bab3aa994 | ||
|
|
bfdf9ecf7b | ||
|
|
4933af3550 | ||
|
|
474dc4a123 | ||
|
|
aea0022c5c | ||
|
|
a1ec749dfe | ||
|
|
6b477e37ca | ||
|
|
28092ffe9d | ||
|
|
5e83481ffb | ||
|
|
7232178513 | ||
|
|
aaf04a515b | ||
|
|
5ea876ff2f | ||
|
|
2fedb900d3 | ||
|
|
150565601b | ||
|
|
6ab3ddaf8e | ||
|
|
cae5a93c7e | ||
|
|
955f35e977 | ||
|
|
a504ef9ba7 | ||
|
|
dcc3cbf1b1 | ||
|
|
6aed43b89c | ||
|
|
8035024531 | ||
|
|
90380ed232 | ||
|
|
568bba75c6 | ||
|
|
f81c869d8f | ||
|
|
f546e7e696 | ||
|
|
730e030994 | ||
|
|
2e24055f5c | ||
|
|
e9caa18ff9 | ||
|
|
93bc0cf38a | ||
|
|
1fe9055055 | ||
|
|
79f2b2bbdc | ||
|
|
fbc2ea478e | ||
|
|
7ffc3a4545 | ||
|
|
e051ad2a9a | ||
|
|
02b9744d5d | ||
|
|
08b61f29d2 | ||
|
|
1cb750a649 | ||
|
|
730105898b | ||
|
|
0a4305f82a | ||
|
|
2e5f480a97 | ||
|
|
0001cb2818 | ||
|
|
8558a98bf6 | ||
|
|
c42bd19c49 | ||
|
|
68ff4a8408 | ||
|
|
dc7e0ca4bf | ||
|
|
78bddacd5b | ||
|
|
8f10ac5047 | ||
|
|
5dab8612c8 | ||
|
|
bfa24a3205 | ||
|
|
cab0ccf0bd | ||
|
|
6cf0a010ec | ||
|
|
b7be2e6d3b | ||
|
|
aed968ebe6 | ||
|
|
087d4300f2 | ||
|
|
dc672ad821 | ||
|
|
b39a47a759 | ||
|
|
c21302e28a | ||
|
|
76018da068 | ||
|
|
b4953951f0 | ||
|
|
459b5973bb | ||
|
|
1103b14683 | ||
|
|
177dc71335 | ||
|
|
f7a4aceeae | ||
|
|
fd391b03ef | ||
|
|
1be5d61bb4 | ||
|
|
4678fcee52 | ||
|
|
e7537116b3 | ||
|
|
16d48eae6f | ||
|
|
0ce0ceb2c2 | ||
|
|
0bac54baaa | ||
|
|
d994020807 | ||
|
|
6a33e7eaa1 | ||
|
|
2a2da35c25 | ||
|
|
b3d0ee093b | ||
|
|
d3577550d2 | ||
|
|
16c02c2cc3 | ||
|
|
92855aa6ee | ||
|
|
e69182d7bc | ||
|
|
4734007ec9 | ||
|
|
ed230a12cc | ||
|
|
0b2584018d | ||
|
|
9501d2cc67 | ||
|
|
c50dd18881 | ||
|
|
d06e2f71f3 | ||
|
|
d43e43adbb | ||
|
|
59c4fc06f3 | ||
|
|
5016240d14 | ||
|
|
2032b51dac | ||
|
|
06b11da76d | ||
|
|
475a250ad4 | ||
|
|
b99626fbf2 | ||
|
|
046cdbe724 | ||
|
|
5d53ed9eae | ||
|
|
b933cbbae6 | ||
|
|
92616474e4 | ||
|
|
ffb32fe73e | ||
|
|
e4e618628f | ||
|
|
be4f2f52b2 | ||
|
|
05ecceb436 | ||
|
|
81cb2fab89 | ||
|
|
8bb9fe654e | ||
|
|
684962b84c | ||
|
|
75b85b2e8f | ||
|
|
51705d90c4 | ||
|
|
5912a0022f | ||
|
|
fe9e52b184 | ||
|
|
a4aa3b93cb | ||
|
|
f0f40d073b | ||
|
|
bb1ec6177f | ||
|
|
7f943d1445 | ||
|
|
abc2ffc185 | ||
|
|
4c710df347 | ||
|
|
3c79d672dc | ||
|
|
a82bc71088 | ||
|
|
96589d4acf | ||
|
|
771fc02b6d | ||
|
|
f570c4e9e8 | ||
|
|
2a3639a7d8 | ||
|
|
e4547c31d3 | ||
|
|
91d14191c7 | ||
|
|
34558f410d | ||
|
|
aea7fac8f0 | ||
|
|
4bd77f1d78 | ||
|
|
a46ba486a8 | ||
|
|
f9336832d4 | ||
|
|
cc7dc1cf0b | ||
|
|
631913599e | ||
|
|
184583f1ac | ||
|
|
f6c92c2eae | ||
|
|
f448fddd50 | ||
|
|
a44ca755d2 | ||
|
|
3720528354 | ||
|
|
ef69e9e736 | ||
|
|
31be7ba0d9 | ||
|
|
91428eaa96 | ||
|
|
05b532af79 | ||
|
|
035aa73c43 | ||
|
|
d25c2553ad | ||
|
|
965da0af4a | ||
|
|
73d1dcac5d | ||
|
|
2d003c2546 | ||
|
|
79787360c6 | ||
|
|
6b0d599a31 | ||
|
|
0f82b352c7 | ||
|
|
fbdb8367ef | ||
|
|
982544a63d | ||
|
|
ec3319b37f | ||
|
|
6fa659c41b | ||
|
|
e4e8535345 | ||
|
|
d1422a03e2 | ||
|
|
e13b1180af | ||
|
|
33fef23ffb | ||
|
|
9e78e4a792 | ||
|
|
800aba8312 | ||
|
|
6254d7def3 | ||
|
|
81ea1d407a | ||
|
|
271c9151f4 | ||
|
|
59822bf07e | ||
|
|
fe26dad744 | ||
|
|
4d81fdd4d8 | ||
|
|
2bf2da4822 | ||
|
|
e9c6a08e1e | ||
|
|
afc1fe6645 | ||
|
|
c0c2e1409c | ||
|
|
bb989791a7 | ||
|
|
04e9aa4ebc | ||
|
|
abb1258ee2 | ||
|
|
c220909e1b | ||
|
|
cce1cb353f | ||
|
|
c44fa6172b | ||
|
|
280892f061 | ||
|
|
bedb2f55f9 | ||
|
|
dca0e6b7ad | ||
|
|
1713f6e2f7 | ||
|
|
3a7b0525c7 | ||
|
|
7937360c6a | ||
|
|
fb100f21b3 | ||
|
|
fa543ae350 | ||
|
|
d8ad600b6a | ||
|
|
d79cdb8371 | ||
|
|
41b2fa7375 | ||
|
|
6bb091595e | ||
|
|
ecbd3eae14 | ||
|
|
5fb7ecbd03 | ||
|
|
e0d44b6678 | ||
|
|
3204bb01d9 | ||
|
|
faac31f217 | ||
|
|
e855b90344 | ||
|
|
63b6d30876 | ||
|
|
3acb7c8e2f | ||
|
|
50b7497e19 | ||
|
|
64913ba610 | ||
|
|
f9a5e9171e | ||
|
|
67948ac222 | ||
|
|
84d9e4d5ac | ||
|
|
72cc07fb42 | ||
|
|
057fffb1dd | ||
|
|
6ae115ac69 | ||
|
|
1aec54efdb | ||
|
|
e3dccd0157 | ||
|
|
048a478bab | ||
|
|
36853d1669 | ||
|
|
6d6df45a23 | ||
|
|
d4a67f6f38 | ||
|
|
b9c35e0009 | ||
|
|
4dffb74762 | ||
|
|
ce06ea80ca | ||
|
|
41497d7990 | ||
|
|
fc4aafa974 | ||
|
|
d0265554a7 | ||
|
|
713b3a9925 | ||
|
|
b596248a1e | ||
|
|
426a689d22 | ||
|
|
b721514f98 | ||
|
|
4282ef24cd | ||
|
|
7bbfaac67a | ||
|
|
11f7653daf | ||
|
|
19d29f25f2 | ||
|
|
32dff0b877 | ||
|
|
f3a7f86abb | ||
|
|
be6d443bc8 | ||
|
|
70d1acd744 | ||
|
|
30ecba45c6 | ||
|
|
accf96877f | ||
|
|
d502b6ec60 | ||
|
|
546df46eaf | ||
|
|
fe3af50fb8 | ||
|
|
6935272033 | ||
|
|
3c992c02ea | ||
|
|
4c4bfca20e | ||
|
|
2abca8b5c1 | ||
|
|
662e8aa60a | ||
|
|
3208ceaac6 | ||
|
|
c278c66e16 | ||
|
|
a48eee9179 | ||
|
|
475fad3a78 | ||
|
|
4749764e3e | ||
|
|
9c541ad461 | ||
|
|
2f681babf5 | ||
|
|
3e3d58b6f1 | ||
|
|
a32c81f94c | ||
|
|
c3c791efcb | ||
|
|
d19c7dbafc | ||
|
|
29a7d06dd4 | ||
|
|
cd55d22611 | ||
|
|
5c846496a2 | ||
|
|
0fd62ce693 | ||
|
|
18156f3965 | ||
|
|
4efc795520 | ||
|
|
9abfeee0e3 | ||
|
|
44bdbbb164 | ||
|
|
b044ac844c | ||
|
|
34a75dee02 | ||
|
|
da619e8615 | ||
|
|
aa7d0f5cca | ||
|
|
b6e97e5fb5 | ||
|
|
6432114e2a | ||
|
|
a89df30537 | ||
|
|
9a09c777d9 | ||
|
|
a1073e69b0 | ||
|
|
93845d6a2c |
No files matched your search
@@ -0,0 +1,13 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [rui314] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Delete this message and describe the bug here. Note that we only fix bugs in the most recent release of mold, so please make sure that you are using the most recent version.
|
||||
@@ -4,14 +4,111 @@ on:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
env:
|
||||
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
|
||||
jobs:
|
||||
build:
|
||||
build-clang:
|
||||
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
|
||||
- name: apt-get
|
||||
run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install build-essential libstdc++-10-dev cmake clang libssl-dev zlib1g-dev libxxhash-dev libtbb-dev git dwarfdump libc6-dev:i386 lib32gcc-10-dev gcc-multilib
|
||||
- name: make
|
||||
run: make -j$(nproc)
|
||||
- name: make test
|
||||
run: make test
|
||||
- uses: actions/checkout@v3
|
||||
- uses: rui314/setup-mold@staging
|
||||
- name: install-build-deps
|
||||
run: sudo ./install-build-deps.sh update
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ${{ matrix.target }} ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest . -j$(nproc)
|
||||
|
||||
build-gcc:
|
||||
runs-on: ubuntu-20.04
|
||||
container: gcc:11.1.0
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: install-build-deps
|
||||
run: |
|
||||
# Install cross toolchains
|
||||
dpkg --add-architecture i386
|
||||
./install-build-deps.sh update
|
||||
apt-get install -y sudo qemu-user gdb zstd dwarfdump {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++
|
||||
|
||||
# Install a RV32 toolchain from third party since it's not available
|
||||
# as an Ubuntu package.
|
||||
mkdir /usr/local/rv32
|
||||
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2022.08.26/riscv32-glibc-ubuntu-20.04-nightly-2022.08.26-nightly.tar.gz | tar -C /usr/local/rv32 --strip-components=1 -xzf -
|
||||
|
||||
ln -sf /usr/local/rv32/sysroot /usr/riscv32-linux-gnu
|
||||
ln -sf /usr/local/rv32/bin/riscv32-unknown-linux-gnu-objdump /usr/bin/riscv32-linux-gnu-objdump
|
||||
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
|
||||
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-g++ -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-g++
|
||||
chmod 755 /usr/bin/riscv32-linux-gnu-gcc /usr/bin/riscv32-linux-gnu-g++
|
||||
shell: bash
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DMOLD_ENABLE_QEMU_TESTS=On -DMOLD_ENABLE_QEMU_TESTS_RV32=On ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest . -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: test-results
|
||||
path: |
|
||||
build/out
|
||||
build/Testing
|
||||
|
||||
build-macos:
|
||||
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@v3
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ${{ matrix.target }} ..
|
||||
cmake --build . -j$(sysctl -n hw.physicalcpu)
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -T clangcl ..
|
||||
cmake --build . -j $Env:NUMBER_OF_PROCESSORS
|
||||
@@ -1,10 +1,17 @@
|
||||
*.o
|
||||
*.so
|
||||
*~
|
||||
**/.DS_Store
|
||||
.cache
|
||||
compile_commands.json
|
||||
/mold
|
||||
/test/tmp
|
||||
options.inc
|
||||
core
|
||||
ld
|
||||
oneTBB
|
||||
ld64
|
||||
ld64.mold
|
||||
gentoo
|
||||
out
|
||||
/mold-*.tar.gz
|
||||
/build*
|
||||
@@ -0,0 +1,455 @@
|
||||
# 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)
|
||||
|
||||
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
|
||||
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
|
||||
if(MOLD_USE_MOLD)
|
||||
add_link_options(-fuse-ld=mold)
|
||||
endif()
|
||||
|
||||
add_executable(mold)
|
||||
target_compile_features(mold PRIVATE cxx_std_20)
|
||||
target_link_libraries(mold PRIVATE ${CMAKE_DL_LIBS})
|
||||
|
||||
if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
|
||||
target_compile_options(mold PRIVATE
|
||||
-fno-exceptions
|
||||
-fno-unwind-tables
|
||||
-fno-asynchronous-unwind-tables
|
||||
-Wno-sign-compare
|
||||
-Wno-unused-function)
|
||||
endif()
|
||||
|
||||
# Build mold with -flto if -DMOLD_LTO=ON
|
||||
option(MOLD_LTO "Build mold with link-time optimization enabled")
|
||||
if(MOLD_LTO)
|
||||
set_property(TARGET mold PROPERTY INTERPROCEDURAL_OPTIMIZATION ON)
|
||||
endif()
|
||||
|
||||
# Enable AddressSanitizer if -DMOLD_USE_ASAN=ON
|
||||
option(MOLD_USE_ASAN "Build mold with AddressSanitizer" OFF)
|
||||
if(MOLD_USE_ASAN)
|
||||
target_compile_options(mold PRIVATE -fsanitize=address -fsanitize=undefined)
|
||||
target_link_options(mold PRIVATE -fsanitize=address -fsanitize=undefined)
|
||||
endif()
|
||||
|
||||
# Enabled ThreadSanitizer if -DMOLD_USE_TSAN=ON
|
||||
option(MOLD_USE_TSAN "Build mold with ThreadSanitizer" OFF)
|
||||
if(MOLD_USE_TSAN)
|
||||
target_compile_options(mold PRIVATE -fsanitize=thread)
|
||||
target_link_options(mold PRIVATE -fsanitize=thread)
|
||||
endif()
|
||||
|
||||
# Statically-link libstdc++ 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()
|
||||
|
||||
# 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)
|
||||
else()
|
||||
add_subdirectory(third-party/zlib EXCLUDE_FROM_ALL)
|
||||
target_include_directories(zlibstatic INTERFACE third-party/zlib
|
||||
$<TARGET_PROPERTY:zlibstatic,BINARY_DIR>)
|
||||
target_link_libraries(mold PRIVATE zlibstatic)
|
||||
endif()
|
||||
|
||||
# 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; 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)
|
||||
target_link_libraries(mold PRIVATE mimalloc)
|
||||
target_compile_definitions(mold PRIVATE USE_SYSTEM_MIMALLOC)
|
||||
else()
|
||||
function(mold_add_mimalloc)
|
||||
set(MI_BUILD_STATIC ON CACHE INTERNAL "")
|
||||
set(MI_BUILD_TESTS OFF CACHE INTERNAL "")
|
||||
add_subdirectory(third-party/mimalloc EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(mimalloc-static PRIVATE MI_USE_ENVIRON=0)
|
||||
target_link_libraries(mold PRIVATE mimalloc-static)
|
||||
endfunction()
|
||||
|
||||
mold_add_mimalloc()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Find TBB. TBB (OneTBB or Intel TBB) is a high-level threading library.
|
||||
# Use of this library is mandatory.
|
||||
#
|
||||
# By default, we build a bundled one and statically-link the library
|
||||
# to mold. If you want to link to the system's libtbb2.so, pass
|
||||
# -DMOLD_USE_SYSTEM_TBB=ON.
|
||||
option(MOLD_USE_SYSTEM_TBB "Use system or vendored TBB" OFF)
|
||||
if(MOLD_USE_SYSTEM_TBB)
|
||||
find_package(TBB REQUIRED)
|
||||
target_link_libraries(mold PRIVATE TBB::tbb)
|
||||
else()
|
||||
function(mold_add_tbb)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
set(TBB_TEST OFF CACHE INTERNAL "")
|
||||
set(TBB_STRICT OFF CACHE INTERNAL "")
|
||||
add_subdirectory(third-party/tbb EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(tbb PRIVATE __TBB_DYNAMIC_LOAD_ENABLED=0)
|
||||
target_link_libraries(mold PRIVATE TBB::tbb)
|
||||
endfunction()
|
||||
|
||||
mold_add_tbb()
|
||||
endif()
|
||||
|
||||
# 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
|
||||
"Your compiler is not supported; install Clang from Visual Studio Installer and re-run cmake with '-T clangcl'")
|
||||
endif()
|
||||
|
||||
target_compile_definitions(mold PRIVATE NOGDI NOMINMAX)
|
||||
else()
|
||||
include(CheckLibraryExists)
|
||||
check_library_exists(m pow "" LIBM_FOUND)
|
||||
if(LIBM_FOUND)
|
||||
target_link_libraries(mold PRIVATE m)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Build mold-wrapper.so
|
||||
if(NOT APPLE AND NOT WIN32)
|
||||
add_library(mold-wrapper SHARED)
|
||||
install(TARGETS mold-wrapper DESTINATION ${CMAKE_INSTALL_LIBDIR}/mold)
|
||||
|
||||
# Remove the default `lib` prefix
|
||||
set_target_properties(mold-wrapper PROPERTIES PREFIX "")
|
||||
target_link_libraries(mold-wrapper PRIVATE ${CMAKE_DL_LIBS})
|
||||
target_sources(mold-wrapper PRIVATE elf/mold-wrapper.c)
|
||||
endif()
|
||||
|
||||
if(NOT APPLE AND NOT WIN32 AND NOT MOLD_MOSTLY_STATIC)
|
||||
find_package(OpenSSL REQUIRED COMPONENTS Crypto)
|
||||
target_link_libraries(mold PRIVATE OpenSSL::Crypto)
|
||||
endif()
|
||||
|
||||
# If atomics doesn't work by default, add -latomic.
|
||||
# We need the flag on riscv, armv6 and m68k.
|
||||
include(CheckCXXSourceCompiles)
|
||||
check_cxx_source_compiles("#include <atomic>
|
||||
int main() {
|
||||
std::atomic_uint8_t a;
|
||||
std::atomic_uint16_t b;
|
||||
std::atomic_uint32_t c;
|
||||
std::atomic_uint64_t d;
|
||||
return ++a + ++b + ++c + ++d;
|
||||
}" HAVE_FULL_ATOMIC_SUPPORT)
|
||||
|
||||
if(NOT HAVE_FULL_ATOMIC_SUPPORT)
|
||||
target_link_libraries(mold PRIVATE atomic)
|
||||
endif()
|
||||
|
||||
# Add -pthread
|
||||
if(NOT APPLE AND NOT 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}
|
||||
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/git-hash.cc
|
||||
-P ${CMAKE_SOURCE_DIR}/update-git-hash.cmake
|
||||
DEPENDS update-git-hash.cmake
|
||||
BYPRODUCTS git-hash.cc
|
||||
VERBATIM)
|
||||
|
||||
add_dependencies(mold git_hash)
|
||||
|
||||
# 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
|
||||
elf/icf.cc
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
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)
|
||||
# Create the ld and ld64 symlinks required for testing
|
||||
if(NOT WIN32)
|
||||
add_custom_command(
|
||||
TARGET mold POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld
|
||||
BYPRODUCTS ld
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
VERBATIM)
|
||||
|
||||
if(MOLD_IS_SOLD)
|
||||
add_custom_command(
|
||||
TARGET mold POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld64
|
||||
BYPRODUCTS ld64
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
VERBATIM)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(${APPLE})
|
||||
if(MOLD_IS_SOLD)
|
||||
add_subdirectory(test/macho)
|
||||
endif()
|
||||
elseif(${UNIX})
|
||||
add_subdirectory(test/elf)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
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/)
|
||||
|
||||
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()
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "x64-Debug",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Debug",
|
||||
"inheritEnvironments": [ "clang_cl_x64" ],
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,30 +1,16 @@
|
||||
# Contributing to mold
|
||||
|
||||
Summary: When you send a patch, you must agree that the patch is
|
||||
released under the dual license of the GNU AGPLv3 and the MIT license.
|
||||
As an indication of agreement, please add a `Signed-off-by` line to
|
||||
your commit message using `git commit -s`.
|
||||
|
||||
Here is the long official version:
|
||||
|
||||
mold is currently licensed under the GNU AGPLv3, but I want to relicense
|
||||
it under a more liberal license in the future. To make this possible,
|
||||
I want all patches to be submitted under the dual license of the GNU
|
||||
AGPLv3 and the MIT license (the "Dual License") from the beginning.
|
||||
|
||||
To make it clear that contributors have agreed to release their
|
||||
patches under the Dual License, I introduced a sign-off procedure just
|
||||
like the Linux kernel project did. The sign-off is a simple line at
|
||||
the end of the git commit message, which certifies that you wrote it
|
||||
or otherwise have the right to pass it on as an open-source patch. The
|
||||
rules are pretty simple: if you can certify the below:
|
||||
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]>
|
||||
@@ -52,3 +38,7 @@ using your real name (sorry, no pseudonyms or anonymous contributions.)
|
||||
This will be done for you automatically if you use ``git commit -s``.
|
||||
Reverts should also include "Signed-off-by". ``git revert -s`` does that
|
||||
for you.
|
||||
|
||||
We assume that any contribution that's 10 lines or less does not meet
|
||||
the threshold of originality and therefore copyright does not apply.
|
||||
You don't need to add a "Signed-off-by" line to such small patches.
|
||||
@@ -0,0 +1,23 @@
|
||||
# This dockerfile creates a reproducible build environment for mold.
|
||||
#
|
||||
# $ 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
|
||||
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 && \
|
||||
\
|
||||
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 && \
|
||||
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
|
||||
@@ -4,9 +4,9 @@ the MIT license. For now, mold is released under the GNU AGPL v3.
|
||||
|
||||
If you just want to use mold but do not want to use AGPL software
|
||||
in your organization, a commercial license is available. Please
|
||||
contact rui[email protected].
|
||||
contact rui[email protected].
|
||||
|
||||
|
||||
--
|
||||
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
@@ -669,3 +669,357 @@ specific requirements.
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
|
||||
|
||||
MOLD SUBCOMPONENTS:
|
||||
|
||||
Mold includes a number of subcomponents with separate copyright notices
|
||||
and license terms. Your use of the source code for the these subcomponents
|
||||
is subject to the terms and conditions of the following licenses.
|
||||
|
||||
|
||||
For the third-party/mimalloc component:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2021 Microsoft Corporation, Daan Leijen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
For the third-party/rust-demangle component:
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
For the third-party/tbb component:
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
For the third-party/xxhash component:
|
||||
|
||||
xxHash Library
|
||||
Copyright (c) 2012-2020 Yann Collet
|
||||
All rights reserved.
|
||||
|
||||
BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
For the third-party/zlib component:
|
||||
|
||||
(C) 1995-2022 Jean-loup Gailly and Mark Adler
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
Jean-loup Gailly Mark Adler
|
||||
[email protected] [email protected]
|
||||
|
||||
|
||||
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,89 +0,0 @@
|
||||
CC = clang
|
||||
CXX = clang++
|
||||
|
||||
MIMALLOC_LIB = mimalloc/out/release/libmimalloc.a
|
||||
GIT_HASH = $(shell [ -d .git ] && git rev-parse HEAD)
|
||||
|
||||
CPPFLAGS = -g -Imimalloc/include -pthread -std=c++20 \
|
||||
-Wno-deprecated-volatile \
|
||||
-DMOLD_VERSION=\"0.1.1\" \
|
||||
-DGIT_HASH=\"$(GIT_HASH)\" \
|
||||
$(EXTRA_CPPFLAGS)
|
||||
LDFLAGS = $(EXTRA_LDFLAGS)
|
||||
LIBS = -Wl,-as-needed -lcrypto -pthread -ltbb -lz -lxxhash -ldl
|
||||
OBJS = main.o object_file.o input_sections.o output_chunks.o \
|
||||
mapfile.o perf.o linker_script.o archive_file.o output_file.o \
|
||||
subprocess.o gc_sections.o icf.o symbols.o cmdline.o filepath.o \
|
||||
passes.o tar.o compress.o memory_mapped_file.o relocatable.o \
|
||||
arch_x86_64.o arch_i386.o
|
||||
|
||||
PREFIX ?= /usr
|
||||
DEBUG ?= 0
|
||||
LTO ?= 0
|
||||
ASAN ?= 0
|
||||
TSAN ?= 0
|
||||
|
||||
ifeq ($(DEBUG), 1)
|
||||
CPPFLAGS += -O0
|
||||
else
|
||||
CPPFLAGS += -O2
|
||||
endif
|
||||
|
||||
ifeq ($(LTO), 1)
|
||||
CPPFLAGS += -flto -O3
|
||||
LDFLAGS += -flto
|
||||
endif
|
||||
|
||||
ifeq ($(ASAN), 1)
|
||||
CPPFLAGS += -fsanitize=address
|
||||
LDFLAGS += -fsanitize=address
|
||||
else
|
||||
# By default, we want to use mimalloc as a memory allocator.
|
||||
# Since replacing the standard malloc is not compatible with ASAN,
|
||||
# we do that only when ASAN is not enabled.
|
||||
LDFLAGS += -Wl,-whole-archive $(MIMALLOC_LIB) -Wl,-no-whole-archive
|
||||
endif
|
||||
|
||||
ifeq ($(TSAN), 1)
|
||||
CPPFLAGS += -fsanitize=thread
|
||||
LDFLAGS += -fsanitize=thread
|
||||
endif
|
||||
|
||||
all: mold mold-wrapper.so
|
||||
|
||||
mold: $(OBJS) $(MIMALLOC_LIB)
|
||||
$(CXX) $(CFLAGS) $(OBJS) -o $@ $(LDFLAGS) $(LIBS)
|
||||
|
||||
mold-wrapper.so: mold-wrapper.c Makefile
|
||||
$(CC) -fPIC -shared -o $@ $< -ldl
|
||||
|
||||
$(OBJS): mold.h elf.h Makefile
|
||||
|
||||
$(MIMALLOC_LIB):
|
||||
mkdir -p mimalloc/out/release
|
||||
(cd mimalloc/out/release; CFLAGS=-DMI_USE_ENVIRON=0 cmake ../..)
|
||||
$(MAKE) -C mimalloc/out/release
|
||||
|
||||
test tests check: all
|
||||
$(MAKE) -C test --output-sync --no-print-directory
|
||||
|
||||
install: all
|
||||
install -m 755 mold $(PREFIX)/bin
|
||||
strip $(PREFIX)/bin/mold
|
||||
|
||||
install -m 755 -d $(PREFIX)/lib/mold
|
||||
install -m 644 mold-wrapper.so $(PREFIX)/lib/mold
|
||||
strip $(PREFIX)/lib/mold/mold-wrapper.so
|
||||
|
||||
install -m 644 docs/mold.1 $(PREFIX)/share/man/man1
|
||||
rm -f $(PREFIX)/share/man/man1/mold.1.gz
|
||||
gzip -9 $(PREFIX)/share/man/man1/mold.1
|
||||
|
||||
uninstall:
|
||||
rm -rf $(PREFIX)/bin/mold $(PREFIX)/share/man/man1/mold.1.gz \
|
||||
$(PREFIX)/lib/mold
|
||||
|
||||
clean:
|
||||
rm -rf *.o *~ mold mold-wrapper.so test/tmp
|
||||
|
||||
.PHONY: all test tests check clean $(TESTS)
|
||||
@@ -1,115 +1,247 @@
|
||||
# mold: A Modern Linker
|
||||
|
||||

|
||||
[](https://github.com/rui314/mold/actions/workflows/ci.yml)
|
||||
[](https://build.opensuse.org/package/show/home:marxin:mold/mold)
|
||||
|
||||
mold is a high performance drop-in replacement for existing Unix
|
||||
linkers. It is several times faster than LLVM lld linker, the (then-)
|
||||
fastest open-source linker which I originally created a few years ago.
|
||||
Here is a performance comparison of GNU gold, LLVM lld and mold for
|
||||
linking final executables of major large programs.
|
||||
<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>
|
||||
|
||||
| Program (linker output size) | GNU gold | LLVM lld | mold | mold w/ preloading
|
||||
|-------------------------------|----------|----------|-------|-------------------
|
||||
| Firefox 87 (1.6 GiB) | 29.2s | 6.16s | 1.69s | 0.79s
|
||||
| Chrome 86 (1.9 GiB) | 54.5s | 11.7s | 1.85s | 0.97s
|
||||
| Clang 13 (3.1 GiB) | 59.4s | 5.68s | 2.76s | 0.86s
|
||||
mold is a faster drop-in replacement for existing Unix linkers.
|
||||
It is several times faster than the LLVM lld linker, the second-fastest
|
||||
open-source linker which I originally created a few years ago.
|
||||
mold is designed to increase developer productivity by reducing
|
||||
build time, especially in rapid debug-edit-rebuild cycles.
|
||||
|
||||
(These nubmers are measured on an AMD Threadripper 3990X 64-core
|
||||
machine with 32 threads enabled. All programs are built with debug
|
||||
info enabled.)
|
||||
Here is a performance comparison of GNU gold, LLVM lld, and mold for
|
||||
linking final debuginfo-enabled executables of major large programs
|
||||
on a simulated 8-core 16-threads machine.
|
||||
|
||||
Let me explain the "w/ preloading" column. mold supports the file
|
||||
preloading feature. That is, if you run mold with `-preload` flag
|
||||
along with other command line flags, it becomes a daemon and halts
|
||||
after parsing input files. Then, if you invoke mold with the same
|
||||
command line options (except `-preload` flag), it tells the daemon to
|
||||
reload only updated files and proceed. With this feature enabled, and
|
||||
if most of the input files haven't been updated, mold achieve a
|
||||
near-`cp` performance or even exceeds it, as the throughput of file
|
||||
copy using the `cp` command is about 2 GiB/s on my machine.
|
||||

|
||||
|
||||
So, mold is extremely fast per-se and even faster with a bit of cheating.
|
||||
| Program (linker output size) | GNU gold | LLVM lld | mold
|
||||
|-------------------------------|----------|----------|--------
|
||||
| Chrome 96 (1.89 GiB) | 53.86s | 11.74s | 2.21s
|
||||
| Clang 13 (3.18 GiB) | 64.12s | 5.82s | 2.90s
|
||||
| Firefox 89 libxul (1.64 GiB) | 32.95s | 6.80s | 1.42s
|
||||
|
||||
Why is mold so fast? One reason is because it simply uses faster
|
||||
algorithms and efficient data structures than other linkers do.
|
||||
The other reason is that the new linker is highly parallelized.
|
||||
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.
|
||||
|
||||
Here is a side-by-side comparison of per-core CPU usage of lld (left)
|
||||
and mold (right). They are linking the same program, Chromium
|
||||
executable.
|
||||
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.
|
||||
|
||||
As you can see, mold uses all available cores throughout its execution
|
||||
and finishes quickly. On the other hand, lld failed to use available
|
||||
cores most of the time. On this demo, the maximum parallelism is
|
||||
artificially capped to 16 so that the bars fit in the GIF.
|
||||
## Why does the speed of linking matter?
|
||||
|
||||
Currently, mold is being developed with Linux/x86-64 as the primary
|
||||
target platform. mold can link many user-land programs including large
|
||||
ones such as web browsers for that target. It also has preliminary
|
||||
Linux/i386 support. Supporting other OSes and ISAs are planned after
|
||||
Linux/x86-64 support is complete.
|
||||
If you are using a compiled language such as C, C++ or Rust, a build
|
||||
consists of two phases. In the first phase, a compiler compiles
|
||||
source files into object files (`.o` files). In the second phase,
|
||||
a linker takes all object files to combine them into a single executable
|
||||
or a shared library file.
|
||||
|
||||
The second phase takes a long time if your build output is large.
|
||||
mold can make it faster, saving your time and keeping you from being
|
||||
distracted while waiting for a long build to finish. The difference is
|
||||
most noticeable when you are in rapid debug-edit-rebuild cycles.
|
||||
|
||||
## Install
|
||||
|
||||
Binary packages for the following systems are currently available.
|
||||
|
||||
[](https://repology.org/project/mold/versions)
|
||||
|
||||
## How to build
|
||||
|
||||
mold is written in C++20, so you need a very recent version of GCC or
|
||||
Clang. I'm using Ubuntu 20.04 as a development platform. In that
|
||||
environment, you can build mold by the following commands.
|
||||
mold is written in C++20, so if you build mold yourself, you need a
|
||||
recent version of a C++ compiler and a C++ standard library. GCC 10.2
|
||||
or Clang 12.0.0 (or later) as well as libstdc++ 10 or libc++ 7 (or
|
||||
later) are recommended.
|
||||
|
||||
```
|
||||
$ sudo apt-get install build-essential libstdc++-10-dev cmake clang libssl-dev zlib1g-dev libxxhash-dev libtbb-dev git
|
||||
$ git clone https://github.com/rui314/mold.git
|
||||
$ cd mold
|
||||
$ make
|
||||
### Install dependencies
|
||||
|
||||
To install build dependencies, run `./install-build-deps.sh` in this
|
||||
directory. It recognizes your Linux distribution and tries to install
|
||||
necessary packages. You may want to run it as root.
|
||||
|
||||
### Compile mold
|
||||
|
||||
```shell
|
||||
git clone https://github.com/rui314/mold.git
|
||||
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 .
|
||||
```
|
||||
|
||||
The last `make` command creates `mold` executable.
|
||||
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`.
|
||||
|
||||
If you don't have Ubuntu 20.04, or if for any reason `make` in the
|
||||
above commands doesn't work for you, you can use Docker to build it in
|
||||
a Docker environment. To do so, just run `./build-static.sh` in this
|
||||
directory. The script creates a Ubuntu 20.04 Docker image, install
|
||||
necessary tools and libraries to it and build mold as a static binary.
|
||||
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`.
|
||||
|
||||
`make test` depends on a few more packages. To install, run the following commands:
|
||||
|
||||
```
|
||||
$ sudo dpkg --add-architecture i386
|
||||
$ sudo apt-get install dwarfdump libc6-dev:i386 lib32gcc-10-dev gcc-multilib
|
||||
```
|
||||
If you don't use a recent enough Linux distribution, or if for any reason
|
||||
`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 `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
|
||||
|
||||
<details><summary>A classic way to use mold</summary>
|
||||
|
||||
On Unix, the linker command (which is usually `/usr/bin/ld`) is
|
||||
invoked indirectly by `cc` (or `gcc` or `clang`), which is typically
|
||||
in turn indirectly invoked by `make` or some other build system
|
||||
command. It is sometimes very hard to pass an appropriate command line
|
||||
option to `cc` to specify an alternative linker.
|
||||
invoked indirectly by the compiler driver (which is usually `cc`,
|
||||
`gcc` or `clang`), which is typically in turn indirectly invoked by
|
||||
`make` or some other build system command.
|
||||
|
||||
To deal with the situation, mold has a feature to intercept all
|
||||
invocations of `/usr/bin/ld`, `/usr/bin/ld.lld` or `/usr/bin/ld.gold`
|
||||
and redirect it to itself. To use the feature, run `make` (or other
|
||||
build command) as a subcommand of mold as follows:
|
||||
If you can specify an additional command line option to your compiler
|
||||
driver by modifying build system's config files, add one of the
|
||||
following flags to use `mold` instead of `/usr/bin/ld`:
|
||||
|
||||
- Clang: pass `-fuse-ld=mold`
|
||||
|
||||
- GCC 12.1.0 or later: pass `-fuse-ld=mold`
|
||||
|
||||
- GCC before 12.1.0: `-fuse-ld` does not accept `mold` as a valid
|
||||
argument, so you need to use `-B` option instead. `-B` is an option
|
||||
to tell GCC where to look for external commands such as `ld`.
|
||||
|
||||
If you have installed mold with `make install`, there should be a
|
||||
directory named `/usr/libexec/mold` (or `/usr/local/libexec/mold`,
|
||||
depending on your `$PREFIX`), and `ld` command should be there. The
|
||||
`ld` is actually a symlink to `mold`. So, all you need is to pass
|
||||
`-B/usr/libexec/mold` (or `-B/usr/local/libexec/mold`) to GCC.
|
||||
|
||||
If you haven't installed `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.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>If you are using Rust</summary>
|
||||
|
||||
Create `.cargo/config.toml` in your project directory with the following:
|
||||
|
||||
```toml
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
|
||||
```
|
||||
$ path/to/mold -run make <make-options-if-any>
|
||||
|
||||
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`.
|
||||
|
||||
If you are using macOS, you can modify `config.toml` in a similar manner.
|
||||
Here is an example with `mold` installed via [Homebrew](https://brew.sh).
|
||||
|
||||
```toml
|
||||
[target.x86_64-apple-darwin]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
|
||||
[target.aarch64-apple-darwin]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>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
|
||||
to `cc` to specify an alternative linker. To deal with the situation,
|
||||
mold has a feature to intercept all invocations of `ld`, `ld.lld` or
|
||||
`ld.gold` and redirect it to itself. To use the feature, run `make`
|
||||
(or another build command) as a subcommand of mold as follows:
|
||||
|
||||
```shell
|
||||
mold -run make <make-options-if-any>
|
||||
```
|
||||
|
||||
Internally, mold invokes a given command with `LD_PRELOAD` environment
|
||||
variable set to its companion shared object file. The shared object
|
||||
file intercepts all function calls to exec-family functions to replace
|
||||
`argv[0]` with `mold` if it is `/usr/bin/ld`, `/usr/bin/ld.gold` or
|
||||
`/usr/bin/ld.lld`.
|
||||
file intercepts all function calls to `exec(3)`-family functions to
|
||||
replace `argv[0]` with `mold` if it is `ld`, `ld.gold` or `ld.lld`.
|
||||
|
||||
Alternatively, you can pass `-fuse-ld=<absolute-path-to-mold-executable>`
|
||||
to a linker command line. Since GCC doesn't support that option,
|
||||
I recommend using clang.
|
||||
</details>
|
||||
|
||||
<details><summary>On macOS</summary>
|
||||
|
||||
mold/macOS is available as an alpha version. It can be used to build not
|
||||
only macOS apps but also iOS apps because their binary formats are the same.
|
||||
|
||||
The command name of mold/macOS is `ld64.mold`. If you build mold on macOS,
|
||||
it still produces `mold` and `ld.mold`, but these executables are useful
|
||||
only for cross compilation (i.e. building Linux apps on macOS.)
|
||||
|
||||
If you find any issue with mold/macOS, please file it to
|
||||
<a href=https://github.com/rui314/mold/issues>our GitHub Issues</a>.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>GitHub Actions</summary>
|
||||
|
||||
You can use our <a href=https://github.com/rui314/setup-mold>setup-mold</a>
|
||||
GitHub Action to speed up GitHub-hosted continuous build. GitHub Actions
|
||||
runs on a two-core machine, but mold is still significantly faster than
|
||||
the default GNU linker there especially when a program being linked is
|
||||
large.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Verify that you are using mold</summary>
|
||||
|
||||
mold leaves its identification string in `.comment` section in an output
|
||||
file. You can print it out to verify that you are actually using mold.
|
||||
|
||||
```
|
||||
```shell
|
||||
$ readelf -p .comment <executable-file>
|
||||
|
||||
String dump of section '.comment':
|
||||
@@ -119,503 +251,73 @@ String dump of section '.comment':
|
||||
|
||||
If `mold` is in `.comment`, the file is created by mold.
|
||||
|
||||
# Design and implementation of mold
|
||||
</details>
|
||||
|
||||
For the rest of this documentation, I'll explain the design and the
|
||||
implementation of mold. If you are only interested in using mold, you
|
||||
don't need to read the below.
|
||||
<details><summary>Online manual</summary>
|
||||
|
||||
## Motivation
|
||||
Since mold is a drop-in replacement, you should be able to use it
|
||||
without reading its manual. But just in case you need it, it's available
|
||||
online at <a href=https://rui314.github.io/mold.html>here</a>.
|
||||
You can also read the same manual by `man mold`.
|
||||
|
||||
Here is why I'm writing a new linker:
|
||||
</details>
|
||||
|
||||
- Even though lld has significantly improved the situation, linking is
|
||||
still one of the slowest steps in a build. It is especially
|
||||
annoying when I changed one line of code and had to wait for a few
|
||||
seconds or even more for a linker to complete. It should be
|
||||
instantaneous. There's a need for a faster linker.
|
||||
## Why is mold so fast?
|
||||
|
||||
- The number of cores on a PC has increased a lot lately, and this
|
||||
trend is expected to continue. However, the existing linkers can't
|
||||
take the advantage of the trend because they don't scale well for more
|
||||
cores. I have a 64-core/128-thread machine, so my goal is to create
|
||||
a linker that uses the CPU nicely. mold should be much faster than
|
||||
other linkers on 4 or 8-core machines too, though.
|
||||
One reason is because it simply uses faster algorithms and efficient
|
||||
data structures than other linkers do. The other reason is that the
|
||||
new linker is highly parallelized.
|
||||
|
||||
- It looks to me that the designs of the existing linkers are somewhat
|
||||
too similar, and I believe there are a lot of drastically different
|
||||
designs that haven't been explored yet. Developers generally don't
|
||||
care about linkers as long as they work correctly, and they don't
|
||||
even think about creating a new one. So there may be lots of low
|
||||
hanging fruits there in this area.
|
||||
Here is a side-by-side comparison of per-core CPU usage of lld (left)
|
||||
and mold (right). They are linking the same program, Chromium
|
||||
executable.
|
||||
|
||||
## Basic design
|
||||

|
||||
|
||||
- In order to achieve a `cp`-like performance, the most important
|
||||
thing is to fix the layout of an output file as quickly as possible, so
|
||||
that we can start copying actual data from input object files to an
|
||||
output file as soon as possible.
|
||||
As you can see, mold uses all available cores throughout its execution
|
||||
and finishes quickly. On the other hand, lld failed to use available
|
||||
cores most of the time. In this demo, the maximum parallelism is
|
||||
artificially capped to 16 so that the bars fit in the GIF.
|
||||
|
||||
- Copying data from input files to an output file is I/O-bounded, so
|
||||
there should be room for doing computationally-intensive tasks while
|
||||
copying data from one file to another.
|
||||
For details, please read [design notes](docs/design.md).
|
||||
|
||||
- We should allow the linker to preload object files from disk and
|
||||
parse them in memory before a complete set of input object files
|
||||
is ready. To do so, we need
|
||||
to split the linker into two in such a way that the latter half of
|
||||
the process finishes as quickly as possible by speculatively parsing
|
||||
and preprocessing input files in the first half of the process.
|
||||
## License
|
||||
|
||||
- One of the most computationally-intensive stage among linker stages
|
||||
is symbol resolution. To resolve symbols, we basically have to throw
|
||||
all symbol strings into a hash table to match undefined symbols with
|
||||
defined symbols. But this can be done in the preloading stage using
|
||||
[string interning](https://en.wikipedia.org/wiki/String_interning).
|
||||
mold is available under AGPL. Note that that does not mean that you
|
||||
have to license your program under AGPL if you use mold to link your
|
||||
program. An output of the mold linker is a derived work of the object
|
||||
files and libraries you pass to the linker but not a derived work of
|
||||
the mold linker itself.
|
||||
|
||||
- Object files may contain a special section called a mergeable string
|
||||
section. The section contains lots of null-terminated strings, and
|
||||
the linker is expected to gather all mergeable string sections and
|
||||
merge their contents. So, if two object files contain the same
|
||||
string literal, for example, the resulting output will contain a
|
||||
single merged string. This step is computationally-intensive, but string
|
||||
merging can be done in the preloading stage using string interning.
|
||||
Besides that, you can also buy a commercial, non-AGPL license with
|
||||
technical support from our company, Blue Whale Systems PTE LTD. If you
|
||||
are a big company, please consider obtaining it before making hundreds
|
||||
or thousands of developers of your company to depend on mold. mold is
|
||||
mostly a single-person open-source project, and just like other
|
||||
open-source projects, we are not legally obligated to keep maintaining
|
||||
it. A legally-binding commercial license contract addresses the
|
||||
concern. By purchasing a license, you are guaranteed that mold will be
|
||||
maintained for you. Please [contact us](mailto:[email protected])
|
||||
for a commercial license inquiry.
|
||||
|
||||
- Static archives (.a files) contain object files, but the static
|
||||
archive's string table contains only defined symbols of member
|
||||
object files and lacks other types of symbols. That makes static
|
||||
archives unsuitable for speculative parsing. Therefore, the linker
|
||||
should ignore the symbol table of static archive and directly read
|
||||
static archive members.
|
||||
## Sponsors
|
||||
|
||||
- If there's a relocation that uses a GOT of a symbol, then we have to
|
||||
create a GOT entry for that symbol. Otherwise, we shouldn't. That
|
||||
means we need to scan all relocation tables to fix the length and
|
||||
the contents of a .got section. This is computationally intensive,
|
||||
but this step is parallelizable.
|
||||
We accept donations via [GitHub Sponsors](https://github.com/sponsors/rui314)
|
||||
and [OpenCollective](https://opencollective.com/mold-linker).
|
||||
We thank you to everybody who sponsors our project. In particular,
|
||||
we'd like to acknowledge the following people and organizations who
|
||||
have sponsored $128/month or more:
|
||||
|
||||
## Compatibility
|
||||
### Corporate sponsors
|
||||
|
||||
- GNU ld, GNU gold and LLVM lld support essentially the same set of
|
||||
command line options and features. mold doesn't have to be
|
||||
completely compatible with them. As long as it can be used for
|
||||
linking large user-land programs, I'm fine with that. It is OK to
|
||||
leave some command line options unimplemented; if mold is blazingly
|
||||
fast, other projects would still be happy to adopt it by modifying
|
||||
their projects' build files.
|
||||
<a href="https://mercury.com/"><img src="docs/mercury-logo.png" align=center height=120 width=400 alt=Mercury></a>
|
||||
|
||||
- mold emits Linux executables and runs only on Linux. I won't avoid
|
||||
Unix-ism when writing code. I don't want to think about portability
|
||||
until mold becomes a thing that's worth to be ported.
|
||||
- [Uber](https://uber.com)
|
||||
- [Signal Slot Inc.](https://github.com/signal-slot)
|
||||
|
||||
## Linker Script
|
||||
### Individual sponsors
|
||||
|
||||
Linker script is an embedded language for the linker. It is mainly
|
||||
used to control how input sections are mapped to output sections and
|
||||
the layout of the output, but it can also do a lot of tricky stuff.
|
||||
Its feature is useful especially for embedded programming, but it's
|
||||
also an awfully underdocumented and complex language.
|
||||
|
||||
We have to implement a subset of the linker script language anwyay,
|
||||
because on Linux, /usr/lib/x86_64-linux-gnu/libc.so is (despite its
|
||||
name) not a shared object file but actually an ASCII file containing
|
||||
linker script code to load the _actual_ libc.so file. But the feature
|
||||
set for this purpose is very limited, and it is okay to implement them
|
||||
to mold.
|
||||
|
||||
Besides that, we really don't want to implement the linker script
|
||||
langauge. But at the same time, we want to satisfy the user needs that
|
||||
are currently satisfied with the linker script langauge. So, what
|
||||
should we do? Here is my observation:
|
||||
|
||||
- Linker script allows to do a lot of tricky stuff, such as specifying
|
||||
the exact layout of a file, inserting arbitrary bytes between
|
||||
sections, etc. But most of them can be done with a post-link binary
|
||||
editing tool (such as `objcopy`).
|
||||
|
||||
- It looks like there are two things that truely cannot be done by a
|
||||
post-link editing tool: (a) mapping input sections to output
|
||||
sections, and (b) applying relocations.
|
||||
|
||||
From the above observation, I believe we need to provide only the
|
||||
following features instead of the entire linker script langauge:
|
||||
|
||||
- A method to specify how input sections are mapped to output
|
||||
sections, and
|
||||
|
||||
- a method to set addresses to output sections, so that relocations
|
||||
are applied based on desired adddresses.
|
||||
|
||||
I believe everything else can be done with a post-link binary editing
|
||||
tool.
|
||||
|
||||
## Details
|
||||
|
||||
- As we aim to the 1 second goal for Chromium, every millisecond
|
||||
counts. We can't ignore the latency of process exit. If we mmap a
|
||||
lot of files, \_exit(2) is not instantaneous but takes a few hundred
|
||||
milliseconds because the kernel has to clean up a lot of
|
||||
resources. As a workaround, we should organize the linker command as
|
||||
two processes; the first process forks the second process, and the
|
||||
second process does the actual work. As soon as the second process
|
||||
writes a result file to a filesystem, it notifies the first process,
|
||||
and the first process exits. The second process can take time to
|
||||
exit, because it is not an interactive process.
|
||||
|
||||
- At least on Linux, it looks like the filesystem's performance to
|
||||
allocate new blocks to a new file is the limiting factor when
|
||||
creating a new large file and filling its contents using mmap.
|
||||
If you already have a large file in the buffer cache, writing to it is
|
||||
much faster than creating a new fresh file and writing to it.
|
||||
Based on this observation, mold overwrites to an existing
|
||||
executable file if exists. My quick benchmark showed that I could
|
||||
save 300 milliseconds when creating a 2 GiB output file.
|
||||
Linux doesn't allow to open an executable for writing if it is
|
||||
running (you'll get "text busy" error if you attempt). mold
|
||||
falls back to the usual way if it fails to open an output file.
|
||||
|
||||
- The output from the linker should be deterministic for the sake of
|
||||
[build reproducibility](https://en.wikipedia.org/wiki/Reproducible_builds)
|
||||
and ease of debugging. This might add a little bit of overhead to
|
||||
the linker, but that shouldn't be too much.
|
||||
|
||||
- A .build-id, a unique ID embedded to an output file, is usually
|
||||
computed by applying a cryptographic hash function (e.g. SHA-1) to
|
||||
an output file. This is a slow step, but we can speed it up by
|
||||
splitting a file into small chunks, computing SHA-1 for each chunk,
|
||||
and then computing SHA-1 of the concatenated SHA-1 hashes
|
||||
(i.e. constructing a [Markle
|
||||
Tree](https://en.wikipedia.org/wiki/Merkle_tree) of height 2).
|
||||
Modern x86 processors have purpose-built instructions for SHA-1 and
|
||||
can compute SHA-1 pretty quickly at about 2 GiB/s rate. Using 16
|
||||
cores, a build-id for a 2 GiB executable can be computed in 60 to 70
|
||||
milliseconds.
|
||||
|
||||
- BFD, gold, and lld support section garbage collection. That is, a
|
||||
linker runs a mark-sweep garbage collection on an input graph, where
|
||||
sections are vertices and relocations are edges, to discard all
|
||||
sections that are not reachable from the entry point symbol
|
||||
(i.e. `_start`) or a few other root sections. In mold, we are using
|
||||
multiple threads to mark sections concurrently.
|
||||
|
||||
- Similarly, BFD, gold an lld support Identical Comdat Folding (ICF)
|
||||
as a yet another size optimization. ICF merges two or more read-only
|
||||
sections that happen to have the same contents and relocations.
|
||||
To do that, we have to find isomorphic subgraphs from larger graphs.
|
||||
I implemented a new algorithm for mold, which is 5x faster than lld
|
||||
to do ICF for Chromium (from 5 seconds to 1 second).
|
||||
|
||||
- [Intel Threading Building
|
||||
Blocks](https://github.com/oneapi-src/oneTBB) (TBB) is a good
|
||||
library for parallel execution and has several concurrent
|
||||
containers. We are particularly interested in using
|
||||
`parallel_for_each` and `concurrent_hash_map`.
|
||||
|
||||
- TBB provides `tbbmalloc` which works better for multi-threaded
|
||||
applications than the glib'c malloc, but it looks like
|
||||
[jemalloc](https://github.com/jemalloc/jemalloc) and
|
||||
[mimalloc](https://github.com/microsoft/mimalloc) are a little bit
|
||||
more scalable than `tbbmalloc`.
|
||||
|
||||
## Size of the problem
|
||||
|
||||
When linking Chrome, a linker reads 3,430,966,844 bytes of data in
|
||||
total. The data contains the following items:
|
||||
|
||||
| Data item | Number
|
||||
| ------------------------ | ------
|
||||
| Object files | 30,723
|
||||
| Public undefined symbols | 1,428,149
|
||||
| Mergeable strings | 1,579,996
|
||||
| Comdat groups | 9,914,510
|
||||
| Regular sections¹ | 10,345,314
|
||||
| Public defined symbols | 10,512,135
|
||||
| Symbols | 23,953,607
|
||||
| Sections | 27,543,225
|
||||
| Relocations against SHF_ALLOC sections | 39,496,375
|
||||
| Relocations | 62,024,719
|
||||
|
||||
¹ Sections that have to be copied from input object files to an
|
||||
output file. Sections that contain relocations or symbols are for
|
||||
example excluded.
|
||||
|
||||
## Internals
|
||||
|
||||
In this section, I'll explain the internals of mold linker.
|
||||
|
||||
### A brief history of Unix and the Unix linker
|
||||
|
||||
Conceptually, what a linker does is pretty simple. A compiler compiles
|
||||
a fragment of a program (a single source file) into a fragment of
|
||||
machine code and data (an object file, which typically has the .o
|
||||
extension), and a linker stiches them together into a single
|
||||
executable or a shared library image.
|
||||
|
||||
In reality, modern linkers for Unix-like systems are much more
|
||||
compilcated than the naive understanding because they have gradually
|
||||
gained one feature at a time over the 50 years history of Unix, and
|
||||
they are now something like a bag of lots of miscellaneous features in
|
||||
which none of the features is more important than the others. It is
|
||||
very easy to miss the forest for the trees, since for those who don't
|
||||
know the details of the Unix linker, it is not clear which feature is
|
||||
essential and which is not.
|
||||
|
||||
That being said, one thing is clear that at any point of Unix history,
|
||||
a Unix linker has a coherent feature set for the Unix of that age. So,
|
||||
let me entangle the history to see how the operating system, runtime
|
||||
and linker have gained features that we see today. That should give
|
||||
you an idea why a particular feature has been added to a linker in the
|
||||
first place.
|
||||
|
||||
1. Original Unix didn't support shared library, and a program was
|
||||
always loaded to a fixed address. An executable was something like
|
||||
a memory dump which was just loaded to a particular address by the
|
||||
kernel. After loading, the kernel started executing the program by
|
||||
setting the instruction pointer to a particular address.
|
||||
|
||||
The most essential feature for any linker is relocation processing.
|
||||
The original Unix linker of course supported that. Let me explain
|
||||
what that is.
|
||||
|
||||
Individual object files are inevitably incomplete as a program,
|
||||
because when a compiler created them, it only see a part of an
|
||||
entire program. For example, if an object file contains a function
|
||||
call that refers other object file, the `call` instruction in the
|
||||
object cannot be complete, as the compiler has no idea as to what
|
||||
is the called function's address. To deal with this, the compiler
|
||||
emits a placeholder value (typically just zero) instead of a real
|
||||
address and leave a metadata in an object file saying "fix offset X
|
||||
of this file with an address of Y". That metadata is called
|
||||
"relocation". Relocations are typically processed by the linker.
|
||||
|
||||
It is easy for a linker to apply relocations for the original Unix
|
||||
because a program is always loaded to a fixed address. It exactly
|
||||
knows the addresses of all functions and data when linking a
|
||||
program.
|
||||
|
||||
Static library support, which is still an important feature of Unix
|
||||
linker, also dates back to this early period of Unix history.
|
||||
To understand what it is, imagine that you are trying to compile
|
||||
a program for the early Unix. You don't want to waste time to
|
||||
compile libc functions every time you compile your program (the
|
||||
computers of the era was incredibly slow), so you have already
|
||||
placed each libc function into a separate source file and compiled
|
||||
them individually. That means, you have object files for each libc
|
||||
function, e.g., printf.o, scanf.o, atoi.o, write.o, etc.
|
||||
|
||||
Given this configuration, all you have to do to link your program
|
||||
against libc functions is to pick up a right set of libc object
|
||||
files and give them to the linker along with the object files of your
|
||||
program. But, keeping the linker command line in sync with the
|
||||
libc functions you are using in your program is bothersome. You can
|
||||
be conservative; you can specify all libc object files to the
|
||||
command line, but that leads to program bloat because the linker
|
||||
unconditionally link all object files given to it no matter whether
|
||||
they are used or not. So, a new feature was added to the linker to
|
||||
fix the problem. That is the static library, which is also called
|
||||
the archive file.
|
||||
|
||||
An archive file is just a bundle of object files, just like zip
|
||||
file but in an uncompressed form. An achive file typically has the
|
||||
.a file extension and named after its contents. For example, the
|
||||
archive file containing all libc objects is named `libc.a`.
|
||||
|
||||
If you pass an archive file along with other object files to the
|
||||
linker, the linker pulls out an object file from the archive _only
|
||||
when_ it is referenced by other object files. In other words,
|
||||
unlike object files directly given to a linker, object files
|
||||
wrapped in an archive are not linked to an output by default.
|
||||
An archive works as supplements to complete your program.
|
||||
|
||||
Even today, you can still find a libc archive file. Run `ar t
|
||||
/usr/lib/x86_64-linux-gnu/libc.a` on Linux should give you a list
|
||||
of object files in the libc archive.
|
||||
|
||||
2. In '80s, Sun Microsystems, a leading commercial Unix vendor at the
|
||||
time, added a shared library support to their Unix variant, SunOS.
|
||||
|
||||
(This section is incomplete.)
|
||||
|
||||
## Concurrency strategy
|
||||
|
||||
In this section, I'll explain the high level concurrency strategy of
|
||||
mold.
|
||||
|
||||
In most places, mold adopts data parallelism. That is, we have a huge
|
||||
number of piece of data of the same kind, and we process each of them
|
||||
individually using parallel for-loop. For example, after identifying
|
||||
the exact set of input object files, we need to scan all relocation
|
||||
tables to determine the sizes of .got and .plt sections. We do that
|
||||
using a parallel for-loop. The granularity of parallel processing in
|
||||
this case is the relocation table.
|
||||
|
||||
Data parallelism is very efficient and scalable because there's no
|
||||
need for threads to communicate with each other while working on each
|
||||
element of data. In addition to that, data parallelism is easy to
|
||||
understand, as it is just a for-loop in which multiple iterations may
|
||||
be executed in parallel. We don't use high-level communication or
|
||||
synchronization mechanisms such as channels, futures, promises,
|
||||
latches or something like that in mold.
|
||||
|
||||
In some cases, we need to share a little bit of data between threads
|
||||
while executing a parallel for-loop. For example, the loop to scan
|
||||
relocations turns on "requires GOT" or "requires PLT" flags in a
|
||||
symbol. Symbol is a shared resource, and writing to them from multiple
|
||||
threads without synchronization is unsafe. To deal with it, we made
|
||||
the flag an atomic variable.
|
||||
|
||||
The other common pattern you can find in mold which is build on top of
|
||||
the parallel for-loop is the map-reduce pattern. That is, we run a
|
||||
parallel for-loop on a large data set to produce a small data set and
|
||||
process the small data set with a single thread. Let me take a
|
||||
build-id computation as an example. Build-id is typically computed by
|
||||
applying a cryptographic hash function such as SHA-1 on a linker's
|
||||
output file. To compute it, we first consider an output as a sequence
|
||||
of 1 MiB blocks and compute a SHA-1 hash for each block in parallel.
|
||||
Then, we concatenate the SHA-1 hashes and compute a SHA-1 hash on the
|
||||
hashes to get a final build-id.
|
||||
|
||||
Finally, we use concurrent hashmap at a few places in mold. Concurrent
|
||||
hashmap is a hashmap to which multiple threads can safely insert items
|
||||
in parallel. We use it in the symbol resolution stage, for example.
|
||||
To resolve symbols, we basically have to throw in all defined symbols
|
||||
into a hash table, so that we can find a matching defined symbol for
|
||||
an undefined symbol by name. We do the hash table insertion from a
|
||||
parallel for-loop which iterates over a list of input files.
|
||||
|
||||
Overall, even though mold is highly scalable, it succeeded to avoid
|
||||
complexties you often find in complex parallel programs. From high
|
||||
level, mold just serially executes linker's internal passes one by
|
||||
one. Each pass is parallelized using parallel for-loops.
|
||||
|
||||
## Rejected ideas
|
||||
|
||||
In this section, I'll explain the alternative designs I currently do
|
||||
not plan to implement and why I turned them down.
|
||||
|
||||
- Placing variable-length sections at end of an output file and start
|
||||
copying file contents before fixing the output file layout
|
||||
|
||||
Idea: Fixing the layout of regular sections seems easy, and if we
|
||||
place them at beginning of a file, we can start copying their
|
||||
contents from their input files to an output file. While copying
|
||||
file contents, we can compute the sizes of variable-length sections
|
||||
such as .got or .plt and place them at end of the file.
|
||||
|
||||
Reason for rejection: I did not choose this design because I doubt
|
||||
if it could actually shorten link time and I think I don't need it
|
||||
anyway.
|
||||
|
||||
The linker has to de-duplicate comdat sections (i.e. inline
|
||||
functions that are included into multiple object files), so we
|
||||
cannot compute the layout of regular sections until we resolve all
|
||||
symbols and de-duplicate comdats. That takes a few hundred
|
||||
milliseconds. After that, we can compute the sizes of
|
||||
variable-length sections in less than 100 milliseconds. It's quite
|
||||
fast, so it doesn't seem to make much sense to proceed without
|
||||
fixing the final file layout.
|
||||
|
||||
The other reason to reject this idea is because there's good a
|
||||
chance for this idea to have a negative impact on linker's overall
|
||||
performance. If we copy file contents before fixing the layout, we
|
||||
can't apply relocations to them while copying because symbol
|
||||
addresses are not available yet. If we fix the file layout first, we
|
||||
can apply relocations while copying, which is effectively zero-cost
|
||||
due to a very good data locality. On the other hand, if we apply
|
||||
relocations long after we copy file contents, it's pretty expensive
|
||||
because section contents are very likely to have been evicted from
|
||||
CPU cache.
|
||||
|
||||
- Incremental linking
|
||||
|
||||
Idea: Incremental linking is a technique to patch a previous linker's
|
||||
output file so that only functions or data that are updated from the
|
||||
previous build are written to it. It is expected to significantly
|
||||
reduce the amount of data copied from input files to an output file
|
||||
and thus speed up linking. GNU BFD and gold linkers support it.
|
||||
|
||||
Reason for rejection: I turned it down because it (1) is
|
||||
complicated, (2) doesn't seem to speed it up that much and (3) has
|
||||
several practical issues. Let me explain each of them.
|
||||
|
||||
First, incremental linking for real C/C++ programs is not as easy as
|
||||
one might think. Let me take malloc as an example. malloc is usually
|
||||
defined by libc, but you can implement it in your program, and if
|
||||
that's the case, the symbol `malloc` will be resolved to your
|
||||
function instead of the one in libc. If you include a library that
|
||||
defines malloc (such as libjemalloc or libtbbmallc) before libc,
|
||||
their malloc will override libc's malloc.
|
||||
|
||||
Assume that you are using a nonstandard malloc. What if you remove
|
||||
your malloc from your code, or remove `-ljemalloc` from your
|
||||
Makefile? The linker has to include a malloc from libc, which may
|
||||
include more object files to satisfy its dependencies. Such code
|
||||
change can affect the entire program rather than just replacing one
|
||||
function. The same is true to adding malloc to your program. Making
|
||||
a local change doesn't necessarily result in a local change in the
|
||||
binary level. It can easily have cascading effects.
|
||||
|
||||
Some ELF fancy features make incremental linking even harder to
|
||||
implement. Take the weak symbol as an example. If you define `atoi`
|
||||
as a weak symbol in your program, and if you are not using `atoi`
|
||||
at all in your program, that symbol will be resolved to address
|
||||
0. But if you start using some libc function that indirectly calls
|
||||
`atoi`, then `atoi` will be included to your program, and your weak
|
||||
symbol will be resolved to that function. I don't know how to
|
||||
efficiently fix up a binary for this case.
|
||||
|
||||
This is a hard problem, so existing linkers don't try too hard to
|
||||
solve it. For example, IIRC, gold falls back to full link if any
|
||||
function is removed from a previous build. If you want to not annoy
|
||||
users in the fallback case, you need to make full link fast anyway.
|
||||
|
||||
Second, incremental linking itself has an overhead. It has to detect
|
||||
updated files, patch an existing output file and write additional
|
||||
data to an output file for future incremental linking. GNU gold, for
|
||||
instance, takes almost 30 seconds on my machine to do a null
|
||||
incremental link (i.e. no object files are updated from a previous
|
||||
build) for chrome. It's just too slow.
|
||||
|
||||
Third, there are other practical issues in incremental linking. It's
|
||||
not reproducible, so your binary isn't going to be the same as other
|
||||
binaries even if you are compiling the same source tree using the
|
||||
same compiler toolchain. Or, it is complex and there might be a bug
|
||||
in it. If something doesn't work correctly, "remove --incremental
|
||||
from your Makefile and try again" could be a piece of advise, but
|
||||
that isn't ideal.
|
||||
|
||||
So, all in all, incremental linking is tricky. I wanted to make full
|
||||
link as fast as possible, so that we don't have to think about how
|
||||
to workaround the slowness of full link.
|
||||
|
||||
- Defining a completely new file format and use it
|
||||
|
||||
Idea: Sometimes, the ELF file format itself seems to be a limiting
|
||||
factor of improving linker's performance. We might be able to make a
|
||||
far better one if we create a new file format.
|
||||
|
||||
Reason for rejection: I rejected the idea because it apparently has
|
||||
a practical issue (backward compatibility issue) and also doesn't
|
||||
seem to improve performance of linkers that much. As clearly
|
||||
demonstrated by mold, we can create a fast linker for ELF. I believe
|
||||
ELF isn't that bad, after all. The semantics of the existing Unix
|
||||
linkers, such as the name resolution algorithm or the linker script,
|
||||
have slowed the linkers down, but that's not a problem of the file
|
||||
format itself.
|
||||
|
||||
- Watching object files using inotify(2)
|
||||
|
||||
Idea: When mold is running as a daemon for preloading, use
|
||||
inotify(2) to watch file system updates so that it can reload files
|
||||
as soon as they are updated.
|
||||
|
||||
Reason for rejection: Just like the maximum number of files you can
|
||||
simultaneously open, the maximum number of files you can watch using
|
||||
inotify(2) isn't that large. Maybe just a single instance of mold is
|
||||
fine with inotify(2), but it may fail if you run multiple of it.
|
||||
|
||||
The other reason for not doing it is because mold is quite fast
|
||||
without it anyway. Invoking stat(2) on each file for file update
|
||||
check takes less than 100 milliseconds for Chrome, and if most of
|
||||
the input files are not updated, parsing updated files takes almost
|
||||
no time.
|
||||
- [300baud](https://github.com/300baud)
|
||||
- [Johan Andersson](https://github.com/repi)
|
||||
- [Wei Wu](https://github.com/lazyparser)
|
||||
- [kyle-elliott](https://github.com/kyle-elliott)
|
||||
@@ -1,441 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
enum {
|
||||
R_TLS_GOTIE = R_END + 1,
|
||||
R_TLS_LE,
|
||||
R_TLS_GD,
|
||||
R_TLS_LD,
|
||||
R_TLS_GOTDESC,
|
||||
R_TLS_GOTDESC_RELAX,
|
||||
R_TLS_DESC_CALL_RELAX,
|
||||
R_TPOFF,
|
||||
};
|
||||
|
||||
template <>
|
||||
void PltSection<I386>::copy_buf(Context<I386> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
// Write PLT header
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0xb3, 0x04, 0, 0, 0, // pushl 4(%ebx)
|
||||
0xff, 0xa3, 0x08, 0, 0, 0, // jmp *8(%ebx)
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
} else {
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0x35, 0, 0, 0, 0, // pushl (GOTPLT+4)
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *(GOTPLT+8)
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr + 8;
|
||||
}
|
||||
|
||||
// Write PLT entries
|
||||
i64 relplt_idx = 0;
|
||||
|
||||
for (Symbol<I386> *sym : symbols) {
|
||||
u8 *ent = buf + sym->get_plt_idx(ctx) * I386::plt_size;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 data[] = {
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0x68, 0, 0, 0, 0, // pushl $reloc_offset
|
||||
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx) - ctx.gotplt->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x68, 0, 0, 0, 0, // pushl $reloc_offset
|
||||
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx);
|
||||
}
|
||||
|
||||
*(u32 *)(ent + 7) = relplt_idx++ * sizeof(ElfRel<I386>);
|
||||
*(u32 *)(ent + 12) = this->shdr.sh_addr - sym->get_plt_addr(ctx) - 16;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<I386>::copy_buf(Context<I386> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 data[] = {
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr;
|
||||
}
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<I386>::apply_reloc(Context<I386> &ctx,
|
||||
ElfRel<I386> &rel,
|
||||
u64 loc, u64 val) {
|
||||
u8 *base = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_NONE:
|
||||
return;
|
||||
case R_386_32:
|
||||
*(u32 *)(base + loc) = val;
|
||||
return;
|
||||
case R_386_PC32:
|
||||
*(u32 *)(base + loc) = val - this->shdr.sh_addr - loc;
|
||||
return;
|
||||
}
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
static void write_val(Context<I386> &ctx, u64 r_type, u8 *loc, u64 val) {
|
||||
switch (r_type) {
|
||||
case R_386_NONE:
|
||||
return;
|
||||
case R_386_8:
|
||||
case R_386_PC8:
|
||||
*loc += val;
|
||||
return;
|
||||
case R_386_16:
|
||||
case R_386_PC16:
|
||||
*(u16 *)loc += val;
|
||||
return;
|
||||
case R_386_32:
|
||||
case R_386_PC32:
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
case R_386_PLT32:
|
||||
case R_386_GOTOFF:
|
||||
case R_386_GOTPC:
|
||||
case R_386_TLS_LDM:
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_GD:
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_GOTDESC:
|
||||
*(u32 *)loc += val;
|
||||
return;
|
||||
}
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<I386>::apply_reloc_alloc(Context<I386> &ctx, u8 *base) {
|
||||
ElfRel<I386> *dynrel = nullptr;
|
||||
std::span<ElfRel<I386>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<I386> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<I386> &rel = rels[i];
|
||||
Symbol<I386> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
const SectionFragmentRef<I386> *ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto write = [&](u64 val) {
|
||||
write_val(ctx, rel.r_type, loc, val);
|
||||
};
|
||||
|
||||
#define S (ref ? ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (ref ? ref->addend : 0)
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOTPLT ctx.gotplt->shdr.sh_addr
|
||||
|
||||
switch (rel_types[i]) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_ABS:
|
||||
write(S + A);
|
||||
break;
|
||||
case R_BASEREL:
|
||||
*dynrel++ = {P, R_386_RELATIVE, 0};
|
||||
*(u32 *)loc += S + A;
|
||||
break;
|
||||
case R_DYN:
|
||||
*dynrel++ = {P, R_386_32, (u32)sym.get_dynsym_idx(ctx)};
|
||||
*(u32 *)loc += A;
|
||||
break;
|
||||
case R_PC:
|
||||
write(S + A - P);
|
||||
break;
|
||||
case R_GOT:
|
||||
write(sym.get_got_addr(ctx) + A - GOTPLT);
|
||||
break;
|
||||
case R_GOTOFF:
|
||||
write(S + A - GOTPLT);
|
||||
break;
|
||||
case R_GOTPC:
|
||||
write(GOTPLT + A - P);
|
||||
break;
|
||||
case R_GOTPCREL:
|
||||
write(G + GOTPLT + A - P);
|
||||
break;
|
||||
case R_TLS_GOTIE:
|
||||
write(sym.get_gottp_addr(ctx) + A - GOTPLT);
|
||||
break;
|
||||
case R_TLS_LE:
|
||||
write(S + A - ctx.tls_end);
|
||||
break;
|
||||
case R_TLS_GD:
|
||||
write(sym.get_tlsgd_addr(ctx) + A - GOTPLT);
|
||||
break;
|
||||
case R_TLS_LD:
|
||||
write(ctx.got->get_tlsld_addr(ctx) + A - GOTPLT);
|
||||
break;
|
||||
case R_TPOFF:
|
||||
write(S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_TLS_GOTDESC:
|
||||
write(sym.get_tlsdesc_addr(ctx) + A - GOTPLT);
|
||||
break;
|
||||
case R_TLS_GOTDESC_RELAX: {
|
||||
static const u8 insn[] = {
|
||||
0x8d, 0x05, 0, 0, 0, 0, // lea 0, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
write(S + A - ctx.tls_end);
|
||||
break;
|
||||
}
|
||||
case R_TLS_DESC_CALL_RELAX:
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
break;
|
||||
case R_SIZE:
|
||||
write(sym.esym().st_size + A);
|
||||
break;
|
||||
default:
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOTPLT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<I386>::apply_reloc_nonalloc(Context<I386> &ctx, u8 *base) {
|
||||
std::span<ElfRel<I386>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<I386> &rel = rels[i];
|
||||
if (rel.r_type == R_386_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<I386> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
const SectionFragmentRef<I386> *ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto write = [&](u64 val) {
|
||||
write_val(ctx, rel.r_type, loc, val);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_16:
|
||||
case R_386_32:
|
||||
case R_386_PC8:
|
||||
case R_386_PC16:
|
||||
case R_386_PC32:
|
||||
case R_386_GOTPC:
|
||||
if (ref)
|
||||
write(ref->frag->get_addr(ctx) + ref->addend);
|
||||
else
|
||||
write(sym.get_addr(ctx));
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
write(sym.get_addr(ctx) - ctx.got->shdr.sh_addr);
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
write(sym.get_addr(ctx) - ctx.tls_begin);
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
write(sym.esym().st_size);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel_to_string<I386>(rel.r_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<I386>::scan_relocations(Context<I386> &ctx) {
|
||||
assert(shdr.sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<I386>);
|
||||
std::span<ElfRel<I386>> rels = get_rels(ctx);
|
||||
bool is_writable = (shdr.sh_flags & SHF_WRITE);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<I386> &rel = rels[i];
|
||||
|
||||
if (rel.r_type == R_386_NONE) {
|
||||
rel_types[i] = R_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
Symbol<I386> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_16: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // DSO
|
||||
{ NONE, ERROR, ERROR, ERROR }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_ABS, i);
|
||||
break;
|
||||
}
|
||||
case R_386_32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, DYNREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_ABS, i);
|
||||
break;
|
||||
}
|
||||
case R_386_PC8:
|
||||
case R_386_PC16: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_PC, i);
|
||||
break;
|
||||
}
|
||||
case R_386_PC32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ BASEREL, NONE, ERROR, ERROR }, // DSO
|
||||
{ BASEREL, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_PC, i);
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOT;
|
||||
break;
|
||||
case R_386_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
rel_types[i] = R_PC;
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
rel_types[i] = R_GOTOFF;
|
||||
break;
|
||||
case R_386_GOTPC:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOTPC;
|
||||
break;
|
||||
case R_386_TLS_GOTIE:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
rel_types[i] = R_TLS_GOTIE;
|
||||
break;
|
||||
case R_386_TLS_LE:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
rel_types[i] = R_TLS_LE;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
rel_types[i] = R_TLS_GD;
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
sym.flags |= NEEDS_TLSLD;
|
||||
rel_types[i] = R_TLS_LD;
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
rel_types[i] = R_TPOFF;
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
rel_types[i] = R_SIZE;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
if (ctx.arg.relax && !ctx.arg.shared) {
|
||||
rel_types[i] = R_TLS_GOTDESC_RELAX;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
rel_types[i] = R_TLS_GOTDESC;
|
||||
}
|
||||
break;
|
||||
case R_386_TLS_DESC_CALL:
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
rel_types[i] = R_TLS_DESC_CALL_RELAX;
|
||||
else
|
||||
rel_types[i] = R_NONE;
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: "
|
||||
<< rel_to_string<I386>(rel.r_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,687 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
enum {
|
||||
R_GOTPCRELX_RELAX = R_END + 1,
|
||||
R_REX_GOTPCRELX_RELAX,
|
||||
R_TLSGD,
|
||||
R_TLSGD_RELAX_LE,
|
||||
R_TLSLD,
|
||||
R_TLSLD_RELAX_LE,
|
||||
R_DTPOFF,
|
||||
R_DTPOFF_RELAX,
|
||||
R_TPOFF,
|
||||
R_GOTTPOFF,
|
||||
R_GOTTPOFF_RELAX,
|
||||
R_GOTPC_TLSDESC,
|
||||
R_GOTPC_TLSDESC_RELAX_LE,
|
||||
R_TLSDESC_CALL_RELAX,
|
||||
};
|
||||
|
||||
template <>
|
||||
void PltSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
// Write PLT header
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0x35, 0, 0, 0, 0, // pushq GOTPLT+8(%rip)
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+16(%rip)
|
||||
0x0f, 0x1f, 0x40, 0x00, // nop
|
||||
};
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr + 2;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr + 4;
|
||||
|
||||
// Write PLT entries
|
||||
i64 relplt_idx = 0;
|
||||
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
0x68, 0, 0, 0, 0, // push $index_in_relplt
|
||||
0xe9, 0, 0, 0, 0, // jmp PLT[0]
|
||||
};
|
||||
|
||||
for (Symbol<X86_64> *sym : symbols) {
|
||||
u8 *ent = buf + sym->get_plt_idx(ctx) * X86_64::plt_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
*(u32 *)(ent + 7) = relplt_idx++;
|
||||
*(u32 *)(ent + 12) = this->shdr.sh_addr - sym->get_plt_addr(ctx) - 16;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (Symbol<X86_64> *sym : symbols) {
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * X86_64::pltgot_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<X86_64>::apply_reloc(Context<X86_64> &ctx,
|
||||
ElfRel<X86_64> &rel,
|
||||
u64 loc, u64 val) {
|
||||
u8 *base = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_NONE:
|
||||
return;
|
||||
case R_X86_64_32:
|
||||
*(u32 *)(base + loc) = val;
|
||||
return;
|
||||
case R_X86_64_64:
|
||||
*(u64 *)(base + loc) = val;
|
||||
return;
|
||||
case R_X86_64_PC32:
|
||||
*(u32 *)(base + loc) = val - this->shdr.sh_addr - loc;
|
||||
return;
|
||||
case R_X86_64_PC64:
|
||||
*(u64 *)(base + loc) = val - this->shdr.sh_addr - loc;
|
||||
return;
|
||||
}
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
static void overflow_check(Context<X86_64> &ctx, InputSection<X86_64> *sec,
|
||||
Symbol<X86_64> &sym, u64 r_type, u64 val) {
|
||||
switch (r_type) {
|
||||
case R_X86_64_8:
|
||||
if (val != (u8)val)
|
||||
Error(ctx) << *sec << ": relocation R_X86_64_8 against " << sym
|
||||
<< " out of range: " << val << " is not in [0, 255]";
|
||||
return;
|
||||
case R_X86_64_PC8:
|
||||
if (val != (i8)val)
|
||||
Error(ctx) << *sec << ": relocation R_X86_64_PC8 against " << sym
|
||||
<< " out of range: " << (i64)val << " is not in [-128, 127]";
|
||||
return;
|
||||
case R_X86_64_16:
|
||||
if (val != (u16)val)
|
||||
Error(ctx) << *sec << ": relocation R_X86_64_16 against " << sym
|
||||
<< " out of range: " << val << " is not in [0, 65535]";
|
||||
return;
|
||||
case R_X86_64_PC16:
|
||||
if (val != (i16)val)
|
||||
Error(ctx) << *sec << ": relocation R_X86_64_PC16 against " << sym
|
||||
<< " out of range: " << (i64)val
|
||||
<< " is not in [-32768, 32767]";
|
||||
return;
|
||||
case R_X86_64_32:
|
||||
if (val != (u32)val)
|
||||
Error(ctx) << *sec << ": relocation R_X86_64_32 against " << sym
|
||||
<< " out of range: " << val << " is not in [0, 4294967296]";
|
||||
return;
|
||||
case R_X86_64_32S:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_GOT32:
|
||||
case R_X86_64_GOTPC32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX:
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_TLSGD:
|
||||
case R_X86_64_TLSLD:
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_GOTTPOFF:
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
if (val != (i32)val)
|
||||
Error(ctx) << *sec << ": relocation " << rel_to_string<X86_64>(r_type)
|
||||
<< " against " << sym << " out of range: " << (i64)val
|
||||
<< " is not in [-2147483648, 2147483647]";
|
||||
return;
|
||||
case R_X86_64_NONE:
|
||||
case R_X86_64_64:
|
||||
case R_X86_64_PC64:
|
||||
case R_X86_64_TPOFF64:
|
||||
case R_X86_64_DTPOFF64:
|
||||
case R_X86_64_GOT64:
|
||||
case R_X86_64_GOTPCREL64:
|
||||
case R_X86_64_GOTPC64:
|
||||
case R_X86_64_SIZE64:
|
||||
return;
|
||||
}
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
static void write_val(Context<X86_64> &ctx, u64 r_type, u8 *loc, u64 val) {
|
||||
switch (r_type) {
|
||||
case R_X86_64_NONE:
|
||||
return;
|
||||
case R_X86_64_8:
|
||||
case R_X86_64_PC8:
|
||||
*loc = val;
|
||||
return;
|
||||
case R_X86_64_16:
|
||||
case R_X86_64_PC16:
|
||||
*(u16 *)loc = val;
|
||||
return;
|
||||
case R_X86_64_32:
|
||||
case R_X86_64_32S:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_GOT32:
|
||||
case R_X86_64_GOTPC32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX:
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_TLSGD:
|
||||
case R_X86_64_TLSLD:
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_GOTTPOFF:
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
*(u32 *)loc = val;
|
||||
return;
|
||||
case R_X86_64_64:
|
||||
case R_X86_64_PC64:
|
||||
case R_X86_64_TPOFF64:
|
||||
case R_X86_64_DTPOFF64:
|
||||
case R_X86_64_GOT64:
|
||||
case R_X86_64_GOTPCREL64:
|
||||
case R_X86_64_GOTPC64:
|
||||
case R_X86_64_SIZE64:
|
||||
*(u64 *)loc = val;
|
||||
return;
|
||||
}
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
static u32 relax_gotpcrelx(u8 *loc) {
|
||||
switch ((loc[0] << 8) | loc[1]) {
|
||||
case 0xff15: return 0x90e8; // call *0(%rip) -> call 0
|
||||
case 0xff25: return 0x90e9; // jmp *0(%rip) -> jmp 0
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_rex_gotpcrelx(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488b05: return 0x488d05; // mov 0(%rip), %rax -> lea 0(%rip), %rax
|
||||
case 0x488b0d: return 0x488d0d; // mov 0(%rip), %rcx -> lea 0(%rip), %rcx
|
||||
case 0x488b15: return 0x488d15; // mov 0(%rip), %rdx -> lea 0(%rip), %rdx
|
||||
case 0x488b1d: return 0x488d1d; // mov 0(%rip), %rbx -> lea 0(%rip), %rbx
|
||||
case 0x488b25: return 0x488d25; // mov 0(%rip), %rsp -> lea 0(%rip), %rsp
|
||||
case 0x488b2d: return 0x488d2d; // mov 0(%rip), %rbp -> lea 0(%rip), %rbp
|
||||
case 0x488b35: return 0x488d35; // mov 0(%rip), %rsi -> lea 0(%rip), %rsi
|
||||
case 0x488b3d: return 0x488d3d; // mov 0(%rip), %rdi -> lea 0(%rip), %rdi
|
||||
case 0x4c8b05: return 0x4c8d05; // mov 0(%rip), %r8 -> lea 0(%rip), %r8
|
||||
case 0x4c8b0d: return 0x4c8d0d; // mov 0(%rip), %r9 -> lea 0(%rip), %r9
|
||||
case 0x4c8b15: return 0x4c8d15; // mov 0(%rip), %r10 -> lea 0(%rip), %r10
|
||||
case 0x4c8b1d: return 0x4c8d1d; // mov 0(%rip), %r11 -> lea 0(%rip), %r11
|
||||
case 0x4c8b25: return 0x4c8d25; // mov 0(%rip), %r12 -> lea 0(%rip), %r12
|
||||
case 0x4c8b2d: return 0x4c8d2d; // mov 0(%rip), %r13 -> lea 0(%rip), %r13
|
||||
case 0x4c8b35: return 0x4c8d35; // mov 0(%rip), %r14 -> lea 0(%rip), %r14
|
||||
case 0x4c8b3d: return 0x4c8d3d; // mov 0(%rip), %r15 -> lea 0(%rip), %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_gottpoff(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488b05: return 0x48c7c0; // mov 0(%rip), %rax -> mov $0, %rax
|
||||
case 0x488b0d: return 0x48c7c1; // mov 0(%rip), %rcx -> mov $0, %rcx
|
||||
case 0x488b15: return 0x48c7c2; // mov 0(%rip), %rdx -> mov $0, %rdx
|
||||
case 0x488b1d: return 0x48c7c3; // mov 0(%rip), %rbx -> mov $0, %rbx
|
||||
case 0x488b25: return 0x48c7c4; // mov 0(%rip), %rsp -> mov $0, %rsp
|
||||
case 0x488b2d: return 0x48c7c5; // mov 0(%rip), %rbp -> mov $0, %rbp
|
||||
case 0x488b35: return 0x48c7c6; // mov 0(%rip), %rsi -> mov $0, %rsi
|
||||
case 0x488b3d: return 0x48c7c7; // mov 0(%rip), %rdi -> mov $0, %rdi
|
||||
case 0x4c8b05: return 0x49c7c0; // mov 0(%rip), %r8 -> mov $0, %r8
|
||||
case 0x4c8b0d: return 0x49c7c1; // mov 0(%rip), %r9 -> mov $0, %r9
|
||||
case 0x4c8b15: return 0x49c7c2; // mov 0(%rip), %r10 -> mov $0, %r10
|
||||
case 0x4c8b1d: return 0x49c7c3; // mov 0(%rip), %r11 -> mov $0, %r11
|
||||
case 0x4c8b25: return 0x49c7c4; // mov 0(%rip), %r12 -> mov $0, %r12
|
||||
case 0x4c8b2d: return 0x49c7c5; // mov 0(%rip), %r13 -> mov $0, %r13
|
||||
case 0x4c8b35: return 0x49c7c6; // mov 0(%rip), %r14 -> mov $0, %r14
|
||||
case 0x4c8b3d: return 0x49c7c7; // mov 0(%rip), %r15 -> mov $0, %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Apply relocations to SHF_ALLOC sections (i.e. sections that are
|
||||
// mapped to memory at runtime) based on the result of
|
||||
// scan_relocations().
|
||||
template <>
|
||||
void InputSection<X86_64>::apply_reloc_alloc(Context<X86_64> &ctx, u8 *base) {
|
||||
ElfRel<X86_64> *dynrel = nullptr;
|
||||
std::span<ElfRel<X86_64>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<X86_64> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<X86_64> &rel = rels[i];
|
||||
Symbol<X86_64> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
const SectionFragmentRef<X86_64> *ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto write = [&](u64 val) {
|
||||
overflow_check(ctx, this, sym, rel.r_type, val);
|
||||
write_val(ctx, rel.r_type, loc, val);
|
||||
};
|
||||
|
||||
#define S (ref ? ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (ref ? ref->addend : rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel_types[i]) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_ABS:
|
||||
write(S + A);
|
||||
break;
|
||||
case R_BASEREL:
|
||||
*dynrel++ = {P, R_X86_64_RELATIVE, 0, (i64)(S + A)};
|
||||
*(u64 *)loc = S + A;
|
||||
break;
|
||||
case R_DYN:
|
||||
*dynrel++ = {P, R_X86_64_64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
*(u64 *)loc = A;
|
||||
break;
|
||||
case R_PC:
|
||||
write(S + A - P);
|
||||
break;
|
||||
case R_GOT:
|
||||
write(G + A);
|
||||
break;
|
||||
case R_GOTPC:
|
||||
write(GOT + A - P);
|
||||
break;
|
||||
case R_GOTPCREL:
|
||||
write(G + GOT + A - P);
|
||||
break;
|
||||
case R_GOTPCRELX_RELAX: {
|
||||
u32 insn = relax_gotpcrelx(loc - 2);
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write(S + A - P);
|
||||
break;
|
||||
}
|
||||
case R_REX_GOTPCRELX_RELAX: {
|
||||
u32 insn = relax_rex_gotpcrelx(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write(S + A - P);
|
||||
break;
|
||||
}
|
||||
case R_TLSGD:
|
||||
write(sym.get_tlsgd_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_TLSGD_RELAX_LE: {
|
||||
// Relax GD to LE
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 8) = S - ctx.tls_end + A + 4;
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
case R_TLSLD:
|
||||
write(ctx.got->get_tlsld_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_TLSLD_RELAX_LE: {
|
||||
// Relax LD to LE
|
||||
static const u8 insn[] = {
|
||||
0x66, 0x66, 0x66, // (padding)
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
case R_DTPOFF:
|
||||
write(S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_DTPOFF_RELAX:
|
||||
write(S + A - ctx.tls_end);
|
||||
break;
|
||||
case R_TPOFF:
|
||||
write(S + A - ctx.tls_end);
|
||||
break;
|
||||
case R_GOTTPOFF:
|
||||
write(sym.get_gottp_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_GOTTPOFF_RELAX: {
|
||||
u32 insn = relax_gottpoff(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write(S + A - ctx.tls_end + 4);
|
||||
break;
|
||||
}
|
||||
case R_GOTPC_TLSDESC:
|
||||
write(sym.get_tlsdesc_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_GOTPC_TLSDESC_RELAX_LE: {
|
||||
static const u8 insn[] = {
|
||||
0x48, 0xc7, 0xc0, 0, 0, 0, 0, // mov $0, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
write(S + A - ctx.tls_end + 4);
|
||||
break;
|
||||
}
|
||||
case R_SIZE:
|
||||
write(sym.esym().st_size + A);
|
||||
break;
|
||||
case R_TLSDESC_CALL_RELAX:
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
break;
|
||||
default:
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
// This function is responsible for applying relocations against
|
||||
// non-SHF_ALLOC sections (i.e. sections that are not mapped to memory
|
||||
// at runtime).
|
||||
//
|
||||
// Relocations against non-SHF_ALLOC sections are much easier to
|
||||
// handle than that against SHF_ALLOC sections. It is because, since
|
||||
// they are not mapped to memory, they don't contain any variable or
|
||||
// function and never need PLT or GOT. Non-SHF_ALLOC sections are
|
||||
// mostly debug info sections.
|
||||
//
|
||||
// Relocations against non-SHF_ALLOC sections are not scanned by
|
||||
// scan_relocations.
|
||||
template <>
|
||||
void InputSection<X86_64>::apply_reloc_nonalloc(Context<X86_64> &ctx, u8 *base) {
|
||||
std::span<ElfRel<X86_64>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<X86_64> &rel = rels[i];
|
||||
if (rel.r_type == R_X86_64_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<X86_64> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
const SectionFragmentRef<X86_64> *ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto write = [&](u64 val) {
|
||||
overflow_check(ctx, this, sym, rel.r_type, val);
|
||||
write_val(ctx, rel.r_type, loc, val);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
case R_X86_64_16:
|
||||
case R_X86_64_32:
|
||||
case R_X86_64_32S:
|
||||
case R_X86_64_64:
|
||||
if (ref)
|
||||
write(ref->frag->get_addr(ctx) + ref->addend);
|
||||
else
|
||||
write(sym.get_addr(ctx) + rel.r_addend);
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_DTPOFF64:
|
||||
write(sym.get_addr(ctx) + rel.r_addend - ctx.tls_begin);
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_SIZE64:
|
||||
write(sym.esym().st_size + rel.r_addend);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel_to_string<X86_64>(rel.r_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Linker has to create data structures in an output file to apply
|
||||
// some type of relocations. For example, if a relocation refers a GOT
|
||||
// or a PLT entry of a symbol, linker has to create an entry in .got
|
||||
// or in .plt for that symbol. In order to fix the file layout, we
|
||||
// need to scan relocations.
|
||||
template <>
|
||||
void InputSection<X86_64>::scan_relocations(Context<X86_64> &ctx) {
|
||||
assert(shdr.sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<X86_64>);
|
||||
std::span<ElfRel<X86_64>> rels = get_rels(ctx);
|
||||
bool is_writable = (shdr.sh_flags & SHF_WRITE);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<X86_64> &rel = rels[i];
|
||||
|
||||
if (rel.r_type == R_X86_64_NONE) {
|
||||
rel_types[i] = R_NONE;
|
||||
continue;
|
||||
}
|
||||
|
||||
Symbol<X86_64> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
case R_X86_64_16:
|
||||
case R_X86_64_32:
|
||||
case R_X86_64_32S: {
|
||||
// Dynamic linker does not support 8, 16 or 32-bit dynamic
|
||||
// relocations for these types of relocations. We report an
|
||||
// error if we cannot relocate them even at load-time.
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // DSO
|
||||
{ NONE, ERROR, ERROR, ERROR }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_ABS, i);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_64: {
|
||||
// Unlike the above, we can use R_X86_64_RELATIVE and R_86_64_64
|
||||
// relocations.
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, DYNREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_ABS, i);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC8:
|
||||
case R_X86_64_PC16:
|
||||
case R_X86_64_PC32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_PC, i);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC64: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ BASEREL, NONE, ERROR, ERROR }, // DSO
|
||||
{ BASEREL, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_PC, i);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOT32:
|
||||
case R_X86_64_GOT64:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOT;
|
||||
break;
|
||||
case R_X86_64_GOTPC32:
|
||||
case R_X86_64_GOTPC64:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOTPC;
|
||||
break;
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCREL64:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOTPCREL;
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPCRELX";
|
||||
|
||||
if (ctx.arg.relax && !sym.is_imported && sym.is_relative(ctx) &&
|
||||
relax_gotpcrelx(loc - 2)) {
|
||||
rel_types[i] = R_GOTPCRELX_RELAX;
|
||||
} else {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOTPCREL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_REX_GOTPCRELX";
|
||||
|
||||
if (ctx.arg.relax && !sym.is_imported && sym.is_relative(ctx) &&
|
||||
relax_rex_gotpcrelx(loc - 3)) {
|
||||
rel_types[i] = R_REX_GOTPCRELX_RELAX;
|
||||
} else {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOTPCREL;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
rel_types[i] = R_PC;
|
||||
break;
|
||||
case R_X86_64_TLSGD: {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this
|
||||
<< ": TLSGD reloc must be followed by PLT32 or GOTPCREL";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported) {
|
||||
rel_types[i++] = R_TLSGD_RELAX_LE;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
rel_types[i] = R_TLSGD;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_X86_64_TLSLD:
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this
|
||||
<< ": TLSGD reloc must be followed by PLT32 or GOTPCREL";
|
||||
if (sym.is_imported)
|
||||
Fatal(ctx) << *this << ": TLSLD reloc refers external symbol " << sym;
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared) {
|
||||
rel_types[i++] = R_TLSLD_RELAX_LE;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSLD;
|
||||
rel_types[i] = R_TLSLD;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_DTPOFF64:
|
||||
if (sym.is_imported)
|
||||
Fatal(ctx) << *this << ": DTPOFF reloc refers external symbol " << sym;
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
rel_types[i] = R_DTPOFF_RELAX;
|
||||
else
|
||||
rel_types[i] = R_DTPOFF;
|
||||
break;
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_TPOFF64:
|
||||
rel_types[i] = R_TPOFF;
|
||||
break;
|
||||
case R_X86_64_GOTTPOFF:
|
||||
ctx.has_gottp_rel = true;
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported &&
|
||||
relax_gottpoff(loc - 3)) {
|
||||
rel_types[i] = R_GOTTPOFF_RELAX;
|
||||
} else {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
rel_types[i] = R_GOTTPOFF;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
if (memcmp(loc - 3, "\x48\x8d\x05", 3))
|
||||
Fatal(ctx) << *this << ": GOTPC32_TLSDESC relocation is used"
|
||||
<< " against an invalid code sequence";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared) {
|
||||
rel_types[i] = R_GOTPC_TLSDESC_RELAX_LE;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
rel_types[i] = R_GOTPC_TLSDESC;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_SIZE64:
|
||||
rel_types[i] = R_SIZE;
|
||||
break;
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
rel_types[i] = R_TLSDESC_CALL_RELAX;
|
||||
else
|
||||
rel_types[i] = R_NONE;
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: "
|
||||
<< rel_to_string<X86_64>(rel.r_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
// This file contains functions to read an archive file (.a file).
|
||||
// An archive file is just a bundle of object files. It's similar to
|
||||
// tar or zip, but the contents are not compressed.
|
||||
//
|
||||
// An archive file is either "regular" or "thin". A regular archive
|
||||
// contains object files directly, while a thin archive contains only
|
||||
// pathnames. In the latter case, actual file contents have to be read
|
||||
// from given pathnames. A regular archive is sometimes called "fat"
|
||||
// archive as opposed to "thin".
|
||||
//
|
||||
// If an archive file is given to the linker, the linker pulls out
|
||||
// object files that are needed to resolve undefined symbols. So,
|
||||
// bunding object files as an archive and giving that archive to the
|
||||
// linker has a different meaning than directly giving the same set of
|
||||
// object files to the linker. The former links only needed object
|
||||
// files, while the latter links all the given object files.
|
||||
//
|
||||
// Therefore, if you link libc.a for example, not all the libc
|
||||
// functions are linked to your binary. Instead, only object files
|
||||
// that provides functions and variables used in your program get
|
||||
// linked. To make this efficient, static library functions are
|
||||
// usually separated to each object file in an archive file. You can
|
||||
// see the contents of libc.a by running `ar t
|
||||
// /usr/lib/x86_64-linux-gnu/libc.a`.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
#include "filetype.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
struct ArHdr {
|
||||
char ar_name[16];
|
||||
char ar_date[12];
|
||||
char ar_uid[6];
|
||||
char ar_gid[6];
|
||||
char ar_mode[8];
|
||||
char ar_size[10];
|
||||
char ar_fmag[2];
|
||||
|
||||
bool starts_with(std::string_view s) const {
|
||||
return std::string_view(ar_name, s.size()) == s;
|
||||
}
|
||||
|
||||
bool is_strtab() const {
|
||||
return starts_with("// ");
|
||||
}
|
||||
|
||||
bool is_symtab() const {
|
||||
return starts_with("/ ") || starts_with("/SYM64/ ");
|
||||
}
|
||||
|
||||
std::string read_name(std::string_view strtab, u8 *&ptr) const {
|
||||
// BSD-style long filename
|
||||
if (starts_with("#1/")) {
|
||||
int namelen = atoi(ar_name + 3);
|
||||
std::string name{(char *)ptr, (size_t)namelen};
|
||||
ptr += namelen;
|
||||
|
||||
if (size_t pos = name.find('\0'))
|
||||
name = name.substr(0, pos);
|
||||
return name;
|
||||
}
|
||||
|
||||
// SysV-style long filename
|
||||
if (starts_with("/")) {
|
||||
const char *start = strtab.data() + atoi(ar_name + 1);
|
||||
return {start, (const char *)strstr(start, "/\n")};
|
||||
}
|
||||
|
||||
// Short fileanme
|
||||
if (const char *end = (char *)memchr(ar_name, '/', sizeof(ar_name)))
|
||||
return {ar_name, end};
|
||||
return {ar_name, sizeof(ar_name)};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
std::vector<MappedFile<C> *>
|
||||
read_thin_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
u8 *begin = mf->data;
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MappedFile<C> *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (data < begin + mf->size) {
|
||||
// Each header is aligned to a 2 byte boundary.
|
||||
if ((begin - data) % 2)
|
||||
data++;
|
||||
|
||||
ArHdr &hdr = *(ArHdr *)data;
|
||||
u8 *body = data + sizeof(hdr);
|
||||
u64 size = atol(hdr.ar_size);
|
||||
|
||||
// Read a string table.
|
||||
if (hdr.is_strtab()) {
|
||||
strtab = {(char *)body, (size_t)size};
|
||||
data = body + size;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip a symbol table.
|
||||
if (hdr.is_symtab()) {
|
||||
data = body + size;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!hdr.starts_with("#1/") && !hdr.starts_with("/"))
|
||||
Fatal(ctx) << mf->name << ": filename is not stored as a long filename";
|
||||
|
||||
std::string name = hdr.read_name(strtab, body);
|
||||
|
||||
// Skip if symbol table
|
||||
if (name == "__.SYMDEF" || name == "__.SYMDEF SORTED")
|
||||
continue;
|
||||
|
||||
std::string path = name.starts_with('/') ?
|
||||
name : (filepath(mf->name).parent_path() / name).string();
|
||||
vec.push_back(MappedFile<C>::must_open(ctx, path));
|
||||
vec.back()->thin_parent = mf;
|
||||
data = body;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
std::vector<MappedFile<C> *>
|
||||
read_fat_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
u8 *begin = mf->data;
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MappedFile<C> *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (begin + mf->size - data >= 2) {
|
||||
if ((begin - data) % 2)
|
||||
data++;
|
||||
|
||||
ArHdr &hdr = *(ArHdr *)data;
|
||||
u8 *body = data + sizeof(hdr);
|
||||
u64 size = atol(hdr.ar_size);
|
||||
data = body + size;
|
||||
|
||||
// Read if string table
|
||||
if (hdr.is_strtab()) {
|
||||
strtab = {(char *)body, (size_t)size};
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if symbol table
|
||||
if (hdr.is_symtab())
|
||||
continue;
|
||||
|
||||
// Read the name field
|
||||
std::string name = hdr.read_name(strtab, body);
|
||||
|
||||
// Skip if symbol table
|
||||
if (name == "__.SYMDEF" || name == "__.SYMDEF SORTED")
|
||||
continue;
|
||||
|
||||
vec.push_back(mf->slice(ctx, name, body - begin, data - body));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
std::vector<MappedFile<C> *>
|
||||
read_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
switch (get_file_type(mf, !ctx.arg.plugin.empty())) {
|
||||
case FileType::AR:
|
||||
return read_fat_archive_members(ctx, mf);
|
||||
case FileType::THIN_AR:
|
||||
return read_thin_archive_members(ctx, mf);
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,118 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
struct ArHdr {
|
||||
char ar_name[16];
|
||||
char ar_date[12];
|
||||
char ar_uid[6];
|
||||
char ar_gid[6];
|
||||
char ar_mode[8];
|
||||
char ar_size[10];
|
||||
char ar_fmag[2];
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::vector<MemoryMappedFile<E> *>
|
||||
read_thin_archive_members(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
u8 *begin = mb->data(ctx);
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MemoryMappedFile<E> *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (data < begin + mb->size()) {
|
||||
// Each header is aligned to a 2 byte boundary.
|
||||
if ((begin - data) % 2)
|
||||
data++;
|
||||
|
||||
ArHdr &hdr = *(ArHdr *)data;
|
||||
u8 *body = data + sizeof(hdr);
|
||||
u64 size = atol(hdr.ar_size);
|
||||
|
||||
// Read a string table.
|
||||
if (!memcmp(hdr.ar_name, "// ", 3)) {
|
||||
strtab = {(char *)body, size};
|
||||
data = body + size;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip a symbol table.
|
||||
if (!memcmp(hdr.ar_name, "/ ", 2)) {
|
||||
data = body + size;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hdr.ar_name[0] != '/')
|
||||
Fatal(ctx) << mb->name << ": filename is not stored as a long filename";
|
||||
|
||||
const char *start = strtab.data() + atoi(hdr.ar_name + 1);
|
||||
std::string name(start, (const char *)strstr(start, "/\n"));
|
||||
std::string path = name.starts_with('/') ?
|
||||
name : std::string(path_dirname(mb->name)) + "/" + name;
|
||||
vec.push_back(MemoryMappedFile<E>::must_open(ctx, path));
|
||||
data = body;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<MemoryMappedFile<E> *>
|
||||
read_fat_archive_members(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
u8 *begin = mb->data(ctx);
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MemoryMappedFile<E> *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (begin + mb->size() - data >= 2) {
|
||||
if ((begin - data) % 2)
|
||||
data++;
|
||||
|
||||
ArHdr &hdr = *(ArHdr *)data;
|
||||
u8 *body = data + sizeof(hdr);
|
||||
u64 size = atol(hdr.ar_size);
|
||||
data = body + size;
|
||||
|
||||
if (!memcmp(hdr.ar_name, "// ", 3)) {
|
||||
strtab = {(char *)body, size};
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!memcmp(hdr.ar_name, "/ ", 2) ||
|
||||
!memcmp(hdr.ar_name, "__.SYMDEF/", 10))
|
||||
continue;
|
||||
|
||||
std::string name;
|
||||
|
||||
if (hdr.ar_name[0] == '/') {
|
||||
const char *start = strtab.data() + atoi(hdr.ar_name + 1);
|
||||
name = {start, (const char *)strstr(start, "/\n")};
|
||||
} else {
|
||||
name = {hdr.ar_name, strchr(hdr.ar_name, '/')};
|
||||
}
|
||||
|
||||
vec.push_back(mb->slice(ctx, name, body - begin, size));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<MemoryMappedFile<E> *>
|
||||
read_archive_members(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
switch (get_file_type(ctx, mb)) {
|
||||
case FileType::AR:
|
||||
return read_fat_archive_members(ctx, mb);
|
||||
case FileType::THIN_AR:
|
||||
return read_thin_archive_members(ctx, mb);
|
||||
default:
|
||||
unreachable(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template std::vector<MemoryMappedFile<E> *> \
|
||||
read_fat_archive_members(Context<E> &, MemoryMappedFile<E> *); \
|
||||
template std::vector<MemoryMappedFile<E> *> \
|
||||
read_thin_archive_members(Context<E> &, MemoryMappedFile<E> *); \
|
||||
template std::vector<MemoryMappedFile<E> *> \
|
||||
read_archive_members(Context<E> &, MemoryMappedFile<E> *);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/bin/bash -x
|
||||
# This is a shell script to build a statically-linked mold
|
||||
# executable using Docker, so that it is easy to build mold
|
||||
# on non-Ubuntu 20.04 machines.
|
||||
#
|
||||
# A docker image used for building mold is persistent.
|
||||
# Run `docker image rm mold-build-ubuntu20` to remove the image
|
||||
# from disk.
|
||||
|
||||
set -e
|
||||
|
||||
# If the existing file is not statically-linked, remove it.
|
||||
[ -f mold ] && ! ldd mold 2>&1 | grep -q 'not a dynamic executable' && \
|
||||
rm mold
|
||||
|
||||
cat <<EOF | docker build -t mold-build-ubuntu20 -
|
||||
FROM ubuntu:20.04
|
||||
RUN apt-get update && \
|
||||
TZ=Europe/London apt-get install -y tzdata && \
|
||||
apt-get install -y build-essential git clang lld cmake \
|
||||
libstdc++-10-dev libxxhash-dev zlib1g-dev libssl-dev && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
EOF
|
||||
|
||||
if ! [ -d oneTBB ]; then
|
||||
git clone https://github.com/oneapi-src/oneTBB.git
|
||||
(cd oneTBB; git checkout --quiet v2020.3)
|
||||
fi
|
||||
|
||||
docker run -it --rm -v `pwd`/oneTBB:/oneTBB -u $(id -u):$(id -g) \
|
||||
mold-build-ubuntu20 \
|
||||
make -C /oneTBB -j$(nproc) extra_inc=big_iron.inc
|
||||
|
||||
tbb_bindir=$(ls -d oneTBB/build/linux_intel64_*_release)
|
||||
|
||||
docker run -it --rm -v `pwd`:/mold -u $(id -u):$(id -g) \
|
||||
mold-build-ubuntu20 \
|
||||
make -C /mold -j$(nproc) \
|
||||
EXTRA_CPPFLAGS=-IoneTBB/include \
|
||||
EXTRA_LDFLAGS="-fuse-ld=lld -static -L$tbb_bindir"
|
||||
@@ -1,850 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <tbb/global_control.h>
|
||||
#include <unistd.h>
|
||||
#include <unordered_set>
|
||||
|
||||
static const char helpmsg[] = R"(
|
||||
Options:
|
||||
--help Report usage information
|
||||
-v, --version Report version information
|
||||
-V Report version and target information
|
||||
-(, --start-group Ignored
|
||||
-), --end-group Ignored
|
||||
-C DIR, --directory DIR Change to DIR before doing anything
|
||||
-E, --export-dynamic Put symbols in the dynamic symbol table
|
||||
--no-export-dynamic
|
||||
-F LIBNAME, --filter LIBNAME
|
||||
Set DT_FILTER to the specified value
|
||||
-I FILE, --dynamic-linker FILE
|
||||
Set dynamic linker path
|
||||
--no-dynamic-linker
|
||||
-L DIR, --library-path DIR Add DIR to library search path
|
||||
-M, --print-map Write map file to stdout
|
||||
-N, --omagic Do not page align data, do not make text readonly
|
||||
--no-omagic
|
||||
-O NUMBER Ignored
|
||||
-S, --strip-debug Strip .debug_* sections
|
||||
-T FILE, --script FILE Read linker script
|
||||
-X, --discard-locals Discard temporary local symbols
|
||||
-e SYMBOL, --entry SYMBOL Set program entry point
|
||||
-f SHLIB, --auxiliary SHLIB Set DT_AUXILIARY to the specified value
|
||||
-h LIBNAME, --soname LIBNAME
|
||||
Set shared library name
|
||||
-l LIBNAME Search for a given library
|
||||
-m EMULATION Ignored
|
||||
-o FILE, --output FILE Set output filename
|
||||
-r, --relocatable Generate relocatable output
|
||||
-s, --strip-all Strip .symtab section
|
||||
-u SYMBOL, --undefined SYMBOL
|
||||
Force to resolve SYMBOL
|
||||
--Bdynamic Link against shared libraries (default)
|
||||
--Bstatic Do not link against shared libraries
|
||||
--Bsymbolic Bind global symbols locally
|
||||
--Bsymbolic-functions Bind global functions locally
|
||||
--Map FILE Write map file to a given file
|
||||
--allow-multiple-definition Allow multiple definitions
|
||||
--as-needed Only set DT_NEEDED if used
|
||||
--no-as-needed
|
||||
--build-id [none,md5,sha1,sha256,uuid,HEXSTRING]
|
||||
Generate build ID
|
||||
--no-build-id
|
||||
--chroot DIR Set a given path to root directory
|
||||
--color-diagnostics Ignored
|
||||
--compress-debug-sections [none,zlib,zlib-gabi,zlib-gnu]
|
||||
Compress .debug_* sections
|
||||
--demangle Demangle C++ symbols in log messages (default)
|
||||
--no-demangle
|
||||
--disable-new-dtags Ignored
|
||||
--dynamic-list Read a list of dynamic symbols
|
||||
--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
|
||||
--fatal-warnings Ignored
|
||||
--no-fatal-warnings Ignored
|
||||
--fini SYMBOL Call SYMBOL at unload-time
|
||||
--fork Spawn a child process (default)
|
||||
--no-fork
|
||||
--gc-sections Remove unreferenced sections
|
||||
--no-gc-sections
|
||||
--gdb-index Ignored
|
||||
--hash-style [sysv,gnu,both]
|
||||
Set hash style
|
||||
--icf Fold identical code
|
||||
--no-icf
|
||||
--image-base ADDR Set the base address to a given value
|
||||
--init SYMBOL Call SYMBOl at load-time
|
||||
--no-undefined Report undefined symbols (even with --shared)
|
||||
--perf Print performance statistics
|
||||
--pie, --pic-executable Create a position independent executable
|
||||
--no-pie, --no-pic-executable
|
||||
--plugin Ignored
|
||||
--plugin-opt Ignored
|
||||
--pop-state Pop state of flags governing input file handling
|
||||
--preload
|
||||
--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
|
||||
--quick-exit Use quick_exit to exit (default)
|
||||
--no-quick-exit
|
||||
--relax Optimize instructions (default)
|
||||
--no-relax
|
||||
--repro Embed input files to .repro section
|
||||
--retain-symbols-file FILE Keep only symbols listed in FILE
|
||||
--rpath DIR Add DIR to runtime search path
|
||||
--rpath-link DIR Ignored
|
||||
--run COMMAND ARG... Run COMMAND with mold as /usr/bin/ld
|
||||
--shared, --Bshareable Create a share library
|
||||
--sort-common Ignored
|
||||
--sort-section Ignored
|
||||
--spare-dynamic-tags NUMBER Reserve give number of tags in .dynamic section
|
||||
--static Do not link against shared libraries
|
||||
--stats Print input statistics
|
||||
--sysroot DIR Set target system root directory
|
||||
--thread-count COUNT Use COUNT number of threads
|
||||
--threads Use multiple threads (default)
|
||||
--no-threads
|
||||
--trace Print name of each input file
|
||||
--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
|
||||
--version-script FILE Read version script
|
||||
--warn-common Warn about common symbols
|
||||
--no-warn-common
|
||||
--warn-unresolved-symbols Report unresolved symbols as warnings
|
||||
--error-unresolved-symbols
|
||||
Report unresolved symbols as errors (default)
|
||||
--whole-archive Include all objects from static archives
|
||||
--no-whole-archive
|
||||
--wrap SYMBOL Use wrapper function for a given symbol
|
||||
-z defs Report undefined symbols (even with --shared)
|
||||
-z nodefs
|
||||
-z execstack Require executable stack
|
||||
-z noexecstack
|
||||
-z initfirst Mark DSO to be initialized first at runtime
|
||||
-z interpose Mark object to interpose all DSOs but executable
|
||||
-z keep-text-section-prefix Keep .text.{hot,unknown,unlikely,startup,exit} as separate sections in the final binary
|
||||
-z nokeep-text-section-prefix
|
||||
-z lazy Enable lazy function resolution (default)
|
||||
-z nocopyreloc Do not create copy relocations
|
||||
-z nodelete Mark DSO non-deletable at runtime
|
||||
-z nodlopen Mark DSO not available to dlopen
|
||||
-z nodump Mark DSO not available to dldump
|
||||
-z now Disable lazy function resolution
|
||||
-z origin Mark object requiring immediate $ORIGIN processing at runtime
|
||||
-z relro Make some sections read-only after relocation (default)
|
||||
-z norelro
|
||||
-z text Report error if DT_TEXTREL is set
|
||||
-z notext
|
||||
-z textoff
|
||||
|
||||
mold: supported targets: elf32-i386 elf64-x86-64
|
||||
mold: supported emulations: elf_i386 elf_x86_64)";
|
||||
|
||||
template <typename E>
|
||||
static std::vector<std::string_view>
|
||||
read_response_file(Context<E> &ctx, std::string_view path) {
|
||||
std::vector<std::string_view> vec;
|
||||
MemoryMappedFile<E> *mb =
|
||||
MemoryMappedFile<E>::must_open(ctx, std::string(path));
|
||||
u8 *data = mb->data(ctx);
|
||||
|
||||
auto read_quoted = [&](i64 i, char quote) {
|
||||
std::string buf;
|
||||
while (i < mb->size() && data[i] != quote) {
|
||||
if (data[i] == '\\') {
|
||||
buf.append(1, data[i + 1]);
|
||||
i += 2;
|
||||
} else {
|
||||
buf.append(1, data[i++]);
|
||||
}
|
||||
}
|
||||
if (i >= mb->size())
|
||||
Fatal(ctx) << path << ": premature end of input";
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i + 1;
|
||||
};
|
||||
|
||||
auto read_unquoted = [&](i64 i) {
|
||||
std::string buf;
|
||||
while (i < mb->size() && !isspace(data[i]))
|
||||
buf.append(1, data[i++]);
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i;
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < mb->size();) {
|
||||
if (isspace(data[i]))
|
||||
i++;
|
||||
else if (data[i] == '\'')
|
||||
i = read_quoted(i + 1, '\'');
|
||||
else if (data[i] == '\"')
|
||||
i = read_quoted(i + 1, '\"');
|
||||
else
|
||||
i = read_unquoted(i);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<std::string_view>
|
||||
expand_response_files(Context<E> &ctx, char **argv) {
|
||||
std::vector<std::string_view> vec;
|
||||
|
||||
for (i64 i = 0; argv[i]; i++) {
|
||||
if (argv[i][0] == '@')
|
||||
append(vec, read_response_file(ctx, argv[i] + 1));
|
||||
else
|
||||
vec.push_back(argv[i]);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
static std::vector<std::string> add_dashes(std::string name) {
|
||||
// Multi-letter linker options can be preceded by either a single
|
||||
// dash or double dashes except ones starting with "o", which must
|
||||
// be preceded by double dashes. For example, "-omagic" is
|
||||
// interpreted as "-o magic". If you really want to specify the
|
||||
// "omagic" option, you have to pass "--omagic".
|
||||
if (name[0] == 'o')
|
||||
return {"--" + name};
|
||||
return {"-" + name, "--" + name};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool read_arg(Context<E> &ctx, std::span<std::string_view> &args,
|
||||
std::string_view &arg, std::string name) {
|
||||
if (name.size() == 1) {
|
||||
if (args[0] == "-" + name) {
|
||||
if (args.size() == 1)
|
||||
Fatal(ctx) << "option -" << name << ": argument missing";
|
||||
arg = args[1];
|
||||
args = args.subspan(2);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args[0].starts_with("-" + name)) {
|
||||
arg = args[0].substr(name.size() + 1);
|
||||
args = args.subspan(1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (std::string opt : add_dashes(name)) {
|
||||
if (args[0] == opt) {
|
||||
if (args.size() == 1)
|
||||
Fatal(ctx) << "option -" << name << ": argument missing";
|
||||
arg = args[1];
|
||||
args = args.subspan(2);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args[0].starts_with(opt + "=")) {
|
||||
arg = args[0].substr(opt.size() + 1);
|
||||
args = args.subspan(1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool read_flag(std::span<std::string_view> &args, std::string name) {
|
||||
for (std::string opt : add_dashes(name)) {
|
||||
if (args[0] == opt) {
|
||||
args = args.subspan(1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool read_z_flag(std::span<std::string_view> &args, std::string name) {
|
||||
if (args.size() >= 2 && args[0] == "-z" && args[1] == name) {
|
||||
args = args.subspan(2);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!args.empty() && args[0] == "-z" + name) {
|
||||
args = args.subspan(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::string create_response_file(Context<E> &ctx) {
|
||||
std::string buf;
|
||||
std::stringstream out;
|
||||
|
||||
std::string cwd = get_current_dir();
|
||||
out << "-C " << cwd.substr(1) << "\n";
|
||||
|
||||
if (cwd != "/") {
|
||||
out << "--chroot ..";
|
||||
i64 depth = std::count(cwd.begin(), cwd.end(), '/');
|
||||
for (i64 i = 1; i < depth; i++)
|
||||
out << "/..";
|
||||
out << "\n";
|
||||
}
|
||||
|
||||
for (i64 i = 1; i < ctx.cmdline_args.size(); i++) {
|
||||
std::string_view arg = ctx.cmdline_args[i];
|
||||
|
||||
if (arg == "-repro" || arg == "--repro") {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
out << arg << "\n";
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_hex(Context<E> &ctx, std::string opt, std::string_view value) {
|
||||
if (!value.starts_with("0x") && !value.starts_with("0X"))
|
||||
Fatal(ctx) << "option -" << opt << ": not a hexadecimal number";
|
||||
value = value.substr(2);
|
||||
if (value.find_first_not_of("0123456789abcdefABCDEF") != value.npos)
|
||||
Fatal(ctx) << "option -" << opt << ": not a hexadecimal number";
|
||||
return std::stol(std::string(value), nullptr, 16);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_number(Context<E> &ctx, std::string opt,
|
||||
std::string_view value) {
|
||||
size_t nread;
|
||||
i64 ret = std::stol(std::string(value), &nread, 0);
|
||||
if (value.size() != nread)
|
||||
Fatal(ctx) << "option -" << opt << ": not a number: " << value;
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<u8> parse_hex_build_id(Context<E> &ctx,
|
||||
std::string_view arg) {
|
||||
assert(arg.starts_with("0x") || arg.starts_with("0X"));
|
||||
|
||||
if (arg.size() % 2)
|
||||
Fatal(ctx) << "invalid build-id: " << arg;
|
||||
if (arg.substr(2).find_first_not_of("0123456789abcdefABCDEF") != arg.npos)
|
||||
Fatal(ctx) << "invalid build-id: " << arg;
|
||||
|
||||
arg = arg.substr(2);
|
||||
|
||||
auto fn = [](char c) {
|
||||
if ('0' <= c && c <= '9')
|
||||
return c - '0';
|
||||
if ('a' <= c && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
assert('A' <= c && c <= 'F');
|
||||
return c - 'A' + 10;
|
||||
};
|
||||
|
||||
std::vector<u8> vec(arg.size() / 2);
|
||||
for (i64 i = 0; i < vec.size(); i++)
|
||||
vec[i] = (fn(arg[i * 2]) << 4) | fn(arg[i * 2 + 1]);
|
||||
return vec;
|
||||
}
|
||||
|
||||
static std::vector<std::string_view>
|
||||
split_by_comma_or_colon(std::string_view str) {
|
||||
std::vector<std::string_view> vec;
|
||||
|
||||
for (;;) {
|
||||
i64 pos = str.find_first_of(",:");
|
||||
if (pos == str.npos) {
|
||||
vec.push_back(str);
|
||||
break;
|
||||
}
|
||||
vec.push_back(str.substr(0, pos));
|
||||
str = str.substr(pos);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
static i64 get_default_thread_count() {
|
||||
// mold doesn't scale above 32 threads.
|
||||
int n = tbb::global_control::active_value(
|
||||
tbb::global_control::max_allowed_parallelism);
|
||||
return std::min(n, 32);
|
||||
}
|
||||
|
||||
static std::string_view trim(std::string_view str) {
|
||||
size_t pos = str.find_first_not_of(" \t");
|
||||
if (pos == str.npos)
|
||||
return "";
|
||||
str = str.substr(pos);
|
||||
|
||||
pos = str.find_last_not_of(" \t");
|
||||
if (pos == str.npos)
|
||||
return str;
|
||||
return str.substr(0, pos + 1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_retain_symbols_file(Context<E> &ctx, std::string_view path) {
|
||||
MemoryMappedFile<E> *mb =
|
||||
MemoryMappedFile<E>::must_open(ctx, std::string(path));
|
||||
std::string_view data((char *)mb->data(ctx), mb->size());
|
||||
|
||||
ctx.arg.retain_symbols_file.reset(new std::unordered_set<std::string_view>);
|
||||
|
||||
while (!data.empty()) {
|
||||
size_t pos = data.find('\n');
|
||||
std::string_view name;
|
||||
|
||||
if (pos == data.npos) {
|
||||
name = data;
|
||||
data = "";
|
||||
} else {
|
||||
name = data.substr(0, pos);
|
||||
data = data.substr(pos + 1);
|
||||
}
|
||||
|
||||
name = trim(name);
|
||||
if (!name.empty())
|
||||
ctx.arg.retain_symbols_file->insert(name);
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_file(std::string_view path) {
|
||||
struct stat st;
|
||||
return stat(std::string(path).c_str(), &st) == 0 &&
|
||||
(st.st_mode & S_IFMT) != S_IFDIR;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_nonpositional_args(Context<E> &ctx,
|
||||
std::vector<std::string_view> &remaining) {
|
||||
std::span<std::string_view> args = ctx.cmdline_args;
|
||||
args = args.subspan(1);
|
||||
|
||||
ctx.arg.thread_count = get_default_thread_count();
|
||||
bool version_shown = false;
|
||||
|
||||
while (!args.empty()) {
|
||||
std::string_view arg;
|
||||
|
||||
if (read_flag(args, "help")) {
|
||||
SyncOut(ctx) << "Usage: " << ctx.cmdline_args[0]
|
||||
<< " [options] file...\n" << helpmsg;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (read_arg(ctx, args, arg, "o") || read_arg(ctx, args, arg, "output")) {
|
||||
ctx.arg.output = arg;
|
||||
} else if (read_arg(ctx, args, arg, "dynamic-linker") ||
|
||||
read_arg(ctx, args, arg, "I")) {
|
||||
ctx.arg.dynamic_linker = arg;
|
||||
} else if (read_arg(ctx, args, arg, "no-dynamic-linker")) {
|
||||
ctx.arg.dynamic_linker = "";
|
||||
} else if (read_flag(args, "v")) {
|
||||
SyncOut(ctx) << get_version_string();
|
||||
version_shown = true;
|
||||
} else if (read_flag(args, "version")) {
|
||||
SyncOut(ctx) << get_version_string();
|
||||
exit(0);
|
||||
} else if (read_flag(args, "V")) {
|
||||
SyncOut(ctx) << get_version_string()
|
||||
<< "\n Supported emulations:\n elf_x86_64\n elf_i386";
|
||||
version_shown = true;
|
||||
} else if (read_flag(args, "export-dynamic") || read_flag(args, "E")) {
|
||||
ctx.arg.export_dynamic = true;
|
||||
} else if (read_flag(args, "no-export-dynamic")) {
|
||||
ctx.arg.export_dynamic = false;
|
||||
} else if (read_flag(args, "Bsymbolic")) {
|
||||
ctx.arg.Bsymbolic = true;
|
||||
} else if (read_flag(args, "Bsymbolic-functions")) {
|
||||
ctx.arg.Bsymbolic_functions = true;
|
||||
} else if (read_arg(ctx, args, arg, "exclude-libs")) {
|
||||
append(ctx.arg.exclude_libs, split_by_comma_or_colon(arg));
|
||||
} else if (read_arg(ctx, args, arg, "e") ||
|
||||
read_arg(ctx, args, arg, "entry")) {
|
||||
ctx.arg.entry = arg;
|
||||
} else if (read_arg(ctx, args, arg, "Map")) {
|
||||
ctx.arg.Map = arg;
|
||||
ctx.arg.print_map = true;
|
||||
} else if (read_flag(args, "print-map") || read_flag(args, "M")) {
|
||||
ctx.arg.print_map = true;
|
||||
} else if (read_flag(args, "static") || read_flag(args, "Bstatic")) {
|
||||
ctx.arg.is_static = true;
|
||||
remaining.push_back("-Bstatic");
|
||||
} else if (read_flag(args, "Bdynamic")) {
|
||||
ctx.arg.is_static = false;
|
||||
remaining.push_back("-Bdynamic");
|
||||
} else if (read_flag(args, "shared") || read_flag(args, "Bshareable")) {
|
||||
ctx.arg.shared = true;
|
||||
} else if (read_arg(ctx, args, arg, "spare-dynamic-tags")) {
|
||||
ctx.arg.spare_dynamic_tags = parse_number(ctx, "spare-dynamic-tags", arg);
|
||||
} else if (read_flag(args, "demangle")) {
|
||||
ctx.arg.demangle = true;
|
||||
} else if (read_flag(args, "no-demangle")) {
|
||||
ctx.arg.demangle = false;
|
||||
} else if (read_arg(ctx, args, arg, "y") ||
|
||||
read_arg(ctx, args, arg, "trace-symbol")) {
|
||||
ctx.arg.trace_symbol.push_back(arg);
|
||||
} else if (read_arg(ctx, args, arg, "filler")) {
|
||||
ctx.arg.filler = parse_hex(ctx, "filler", arg);
|
||||
} else if (read_arg(ctx, args, arg, "L") ||
|
||||
read_arg(ctx, args, arg, "library-path")) {
|
||||
ctx.arg.library_paths.push_back(arg);
|
||||
} else if (read_arg(ctx, args, arg, "sysroot")) {
|
||||
ctx.arg.sysroot = arg;
|
||||
} else if (read_arg(ctx, args, arg, "unique")) {
|
||||
ctx.arg.unique.reset(new std::regex(glob_to_regex(arg)));
|
||||
} else if (read_arg(ctx, args, arg, "unresolved-symbols")) {
|
||||
if (arg == "report-all" || arg == "ignore-in-shared-libs")
|
||||
ctx.arg.unresolved_symbols = UnresolvedKind::ERROR;
|
||||
else if (arg == "ignore-all" || arg == "ignore-in-object-files")
|
||||
ctx.arg.unresolved_symbols = UnresolvedKind::IGNORE;
|
||||
else
|
||||
Fatal(ctx) << "unknown --unresolved-symbols argument: " << arg;
|
||||
} else if (read_arg(ctx, args, arg, "u") ||
|
||||
read_arg(ctx, args, arg, "undefined")) {
|
||||
ctx.arg.undefined.push_back(arg);
|
||||
} else if (read_arg(ctx, args, arg, "init")) {
|
||||
ctx.arg.init = arg;
|
||||
} else if (read_arg(ctx, args, arg, "fini")) {
|
||||
ctx.arg.fini = arg;
|
||||
} else if (read_arg(ctx, args, arg, "hash-style")) {
|
||||
if (arg == "sysv") {
|
||||
ctx.arg.hash_style_sysv = true;
|
||||
ctx.arg.hash_style_gnu = false;
|
||||
} else if (arg == "gnu") {
|
||||
ctx.arg.hash_style_sysv = false;
|
||||
ctx.arg.hash_style_gnu = true;
|
||||
} else if (arg == "both") {
|
||||
ctx.arg.hash_style_sysv = true;
|
||||
ctx.arg.hash_style_gnu = true;
|
||||
} else {
|
||||
Fatal(ctx) << "invalid --hash-style argument: " << arg;
|
||||
}
|
||||
} else if (read_arg(ctx, args, arg, "soname") ||
|
||||
read_arg(ctx, args, arg, "h")) {
|
||||
ctx.arg.soname = arg;
|
||||
} else if (read_flag(args, "allow-multiple-definition")) {
|
||||
ctx.arg.allow_multiple_definition = true;
|
||||
} else if (read_flag(args, "trace")) {
|
||||
ctx.arg.trace = true;
|
||||
} else if (read_flag(args, "eh-frame-hdr")) {
|
||||
ctx.arg.eh_frame_hdr = true;
|
||||
} else if (read_flag(args, "no-eh-frame-hdr")) {
|
||||
ctx.arg.eh_frame_hdr = false;
|
||||
} else if (read_flag(args, "pie") || read_flag(args, "pic-executable")) {
|
||||
ctx.arg.pic = true;
|
||||
ctx.arg.pie = true;
|
||||
} else if (read_flag(args, "no-pie") ||
|
||||
read_flag(args, "no-pic-executable")) {
|
||||
ctx.arg.pic = false;
|
||||
ctx.arg.pie = false;
|
||||
} else if (read_flag(args, "relax")) {
|
||||
ctx.arg.relax = true;
|
||||
} else if (read_flag(args, "no-relax")) {
|
||||
ctx.arg.relax = false;
|
||||
} else if (read_flag(args, "r") || read_flag(args, "relocatable")) {
|
||||
ctx.arg.relocatable = true;
|
||||
} else if (read_flag(args, "perf")) {
|
||||
ctx.arg.perf = true;
|
||||
} else if (read_flag(args, "stats")) {
|
||||
ctx.arg.stats = true;
|
||||
Counter::enabled = true;
|
||||
} else if (read_arg(ctx, args, arg, "C") ||
|
||||
read_arg(ctx, args, arg, "directory")) {
|
||||
ctx.arg.directory = arg;
|
||||
} else if (read_arg(ctx, args, arg, "chroot")) {
|
||||
ctx.arg.chroot = arg;
|
||||
} else if (read_flag(args, "warn-common")) {
|
||||
ctx.arg.warn_common = true;
|
||||
} else if (read_flag(args, "no-warn-common")) {
|
||||
ctx.arg.warn_common = false;
|
||||
} else if (read_arg(ctx, args, 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;
|
||||
else if (arg == "none")
|
||||
ctx.arg.compress_debug_sections = COMPRESS_NONE;
|
||||
else
|
||||
Fatal(ctx) << "invalid --compress-debug-sections argument: " << arg;
|
||||
} else if (read_arg(ctx, args, arg, "wrap")) {
|
||||
ctx.arg.wrap.insert(arg);
|
||||
} else if (read_flag(args, "omagic") || read_flag(args, "N")) {
|
||||
ctx.arg.omagic = true;
|
||||
ctx.arg.is_static = true;
|
||||
} else if (read_flag(args, "no-omagic")) {
|
||||
ctx.arg.omagic = false;
|
||||
} else if (read_arg(ctx, args, arg, "retain-symbols-file")) {
|
||||
read_retain_symbols_file(ctx, arg);
|
||||
} else if (read_flag(args, "repro")) {
|
||||
ctx.arg.repro = true;
|
||||
} else if (read_z_flag(args, "now")) {
|
||||
ctx.arg.z_now = true;
|
||||
} else if (read_z_flag(args, "lazy")) {
|
||||
ctx.arg.z_now = false;
|
||||
} else if (read_z_flag(args, "execstack")) {
|
||||
ctx.arg.z_execstack = true;
|
||||
} else if (read_z_flag(args, "noexecstack")) {
|
||||
ctx.arg.z_execstack = false;
|
||||
} else if (read_z_flag(args, "relro")) {
|
||||
ctx.arg.z_relro = true;
|
||||
} else if (read_z_flag(args, "norelro")) {
|
||||
ctx.arg.z_relro = false;
|
||||
} else if (read_z_flag(args, "defs")) {
|
||||
ctx.arg.z_defs = true;
|
||||
} else if (read_z_flag(args, "nodefs")) {
|
||||
ctx.arg.z_defs = false;
|
||||
} else if (read_z_flag(args, "nodlopen")) {
|
||||
ctx.arg.z_dlopen = false;
|
||||
} else if (read_z_flag(args, "nodelete")) {
|
||||
ctx.arg.z_delete = false;
|
||||
} else if (read_z_flag(args, "nocopyreloc")) {
|
||||
ctx.arg.z_copyreloc = false;
|
||||
} else if (read_z_flag(args, "nodump")) {
|
||||
ctx.arg.z_dump = false;
|
||||
} else if (read_z_flag(args, "initfirst")) {
|
||||
ctx.arg.z_initfirst = true;
|
||||
} else if (read_z_flag(args, "interpose")) {
|
||||
ctx.arg.z_interpose = true;
|
||||
} else if (read_z_flag(args, "muldefs")) {
|
||||
ctx.arg.allow_multiple_definition = true;
|
||||
} else if (read_z_flag(args, "keep-text-section-prefix")) {
|
||||
ctx.arg.z_keep_text_section_prefix = true;
|
||||
} else if (read_z_flag(args, "nokeep-text-section-prefix")) {
|
||||
ctx.arg.z_keep_text_section_prefix = false;
|
||||
} else if (read_z_flag(args, "text")) {
|
||||
ctx.arg.z_text = true;
|
||||
} else if (read_z_flag(args, "notext") || read_z_flag(args, "textoff")) {
|
||||
ctx.arg.z_text = false;
|
||||
} else if (read_z_flag(args, "origin")) {
|
||||
ctx.arg.z_origin = true;
|
||||
} else if (read_flag(args, "no-undefined")) {
|
||||
ctx.arg.z_defs = true;
|
||||
} else if (read_flag(args, "fatal-warnings")) {
|
||||
ctx.arg.fatal_warnings = true;
|
||||
} else if (read_flag(args, "no-fatal-warnings")) {
|
||||
ctx.arg.fatal_warnings = false;
|
||||
} else if (read_flag(args, "fork")) {
|
||||
ctx.arg.fork = true;
|
||||
} else if (read_flag(args, "no-fork")) {
|
||||
ctx.arg.fork = false;
|
||||
} else if (read_flag(args, "gc-sections")) {
|
||||
ctx.arg.gc_sections = true;
|
||||
} else if (read_flag(args, "no-gc-sections")) {
|
||||
ctx.arg.gc_sections = false;
|
||||
} else if (read_flag(args, "print-gc-sections")) {
|
||||
ctx.arg.print_gc_sections = true;
|
||||
} else if (read_flag(args, "no-print-gc-sections")) {
|
||||
ctx.arg.print_gc_sections = false;
|
||||
} else if (read_arg(ctx, args, arg, "icf")) {
|
||||
if (arg == "all")
|
||||
ctx.arg.icf = true;
|
||||
else if (arg == "none")
|
||||
ctx.arg.icf = false;
|
||||
else
|
||||
Fatal(ctx) << "unknown --icf argument: " << arg;
|
||||
} else if (read_flag(args, "no-icf")) {
|
||||
ctx.arg.icf = false;
|
||||
} else if (read_arg(ctx, args, arg, "image-base")) {
|
||||
ctx.arg.image_base = parse_number(ctx, "image-base", arg);
|
||||
} else if (read_flag(args, "quick-exit")) {
|
||||
ctx.arg.quick_exit = true;
|
||||
} else if (read_flag(args, "no-quick-exit")) {
|
||||
ctx.arg.quick_exit = false;
|
||||
} else if (read_flag(args, "print-icf-sections")) {
|
||||
ctx.arg.print_icf_sections = true;
|
||||
} else if (read_flag(args, "no-print-icf-sections")) {
|
||||
ctx.arg.print_icf_sections = false;
|
||||
} else if (read_flag(args, "quick-exit")) {
|
||||
ctx.arg.quick_exit = true;
|
||||
} else if (read_flag(args, "no-quick-exit")) {
|
||||
ctx.arg.quick_exit = false;
|
||||
} else if (read_arg(ctx, args, arg, "thread-count")) {
|
||||
ctx.arg.thread_count = parse_number(ctx, "thread-count", arg);
|
||||
} else if (read_flag(args, "threads")) {
|
||||
ctx.arg.thread_count = get_default_thread_count();
|
||||
} else if (read_flag(args, "no-threads")) {
|
||||
ctx.arg.thread_count = 1;
|
||||
} else if (read_flag(args, "discard-all") || read_flag(args, "x")) {
|
||||
ctx.arg.discard_all = true;
|
||||
} else if (read_flag(args, "discard-locals") || read_flag(args, "X")) {
|
||||
ctx.arg.discard_locals = true;
|
||||
} else if (read_flag(args, "strip-all") || read_flag(args, "s")) {
|
||||
ctx.arg.strip_all = true;
|
||||
} else if (read_flag(args, "strip-debug") || read_flag(args, "S")) {
|
||||
ctx.arg.strip_all = true;
|
||||
} else if (read_flag(args, "warn-unresolved-symbols")) {
|
||||
ctx.arg.unresolved_symbols = UnresolvedKind::WARN;
|
||||
} else if (read_flag(args, "error-unresolved-symbols")) {
|
||||
ctx.arg.unresolved_symbols = UnresolvedKind::ERROR;
|
||||
} else if (read_arg(ctx, args, arg, "rpath")) {
|
||||
if (!ctx.arg.rpaths.empty())
|
||||
ctx.arg.rpaths += ":";
|
||||
ctx.arg.rpaths += arg;
|
||||
} else if (read_arg(ctx, args, 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;
|
||||
} else if (read_flag(args, "build-id")) {
|
||||
ctx.arg.build_id.kind = BuildId::HASH;
|
||||
ctx.arg.build_id.hash_size = 20;
|
||||
} else if (read_arg(ctx, args, arg, "build-id")) {
|
||||
if (arg == "none") {
|
||||
ctx.arg.build_id.kind = BuildId::NONE;
|
||||
} else if (arg == "uuid") {
|
||||
ctx.arg.build_id.kind = BuildId::UUID;
|
||||
} else if (arg == "md5") {
|
||||
ctx.arg.build_id.kind = BuildId::HASH;
|
||||
ctx.arg.build_id.hash_size = 16;
|
||||
} else if (arg == "sha1") {
|
||||
ctx.arg.build_id.kind = BuildId::HASH;
|
||||
ctx.arg.build_id.hash_size = 20;
|
||||
} else if (arg == "sha256") {
|
||||
ctx.arg.build_id.kind = BuildId::HASH;
|
||||
ctx.arg.build_id.hash_size = 32;
|
||||
} else if (arg.starts_with("0x") || arg.starts_with("0X")) {
|
||||
ctx.arg.build_id.kind = BuildId::HEX;
|
||||
ctx.arg.build_id.value = parse_hex_build_id(ctx, arg);
|
||||
} else {
|
||||
Fatal(ctx) << "invalid --build-id argument: " << arg;
|
||||
}
|
||||
} else if (read_flag(args, "no-build-id")) {
|
||||
ctx.arg.build_id.kind = BuildId::NONE;
|
||||
} else if (read_arg(ctx, args, arg, "auxiliary") ||
|
||||
read_arg(ctx, args, arg, "f")) {
|
||||
ctx.arg.auxiliary.push_back(arg);
|
||||
} else if (read_arg(ctx, args, arg, "filter") ||
|
||||
read_arg(ctx, args, arg, "F")) {
|
||||
ctx.arg.filter.push_back(arg);
|
||||
} else if (read_flag(args, "preload")) {
|
||||
ctx.arg.preload = true;
|
||||
} else if (read_arg(ctx, args, arg, "O")) {
|
||||
} else if (read_flag(args, "O0")) {
|
||||
} else if (read_flag(args, "O1")) {
|
||||
} else if (read_flag(args, "O2")) {
|
||||
} else if (read_flag(args, "verbose")) {
|
||||
} else if (read_arg(ctx, args, arg, "plugin")) {
|
||||
} else if (read_arg(ctx, args, arg, "plugin-opt")) {
|
||||
} else if (read_flag(args, "color-diagnostics")) {
|
||||
} else if (read_flag(args, "gdb-index")) {
|
||||
} else if (read_arg(ctx, args, arg, "m")) {
|
||||
} else if (read_flag(args, "eh-frame-hdr")) {
|
||||
} else if (read_flag(args, "start-group")) {
|
||||
} else if (read_flag(args, "end-group")) {
|
||||
} else if (read_flag(args, "(")) {
|
||||
} else if (read_flag(args, ")")) {
|
||||
} else if (read_flag(args, "fatal-warnings")) {
|
||||
} else if (read_flag(args, "enable-new-dtags")) {
|
||||
} else if (read_flag(args, "disable-new-dtags")) {
|
||||
} else if (read_flag(args, "nostdlib")) {
|
||||
} else if (read_flag(args, "allow-shlib-undefined")) {
|
||||
} else if (read_flag(args, "no-allow-shlib-undefined")) {
|
||||
} else if (read_flag(args, "no-add-needed")) {
|
||||
} else if (read_flag(args, "no-copy-dt-needed-entries")) {
|
||||
} else if (read_flag(args, "no-undefined-version")) {
|
||||
} else if (read_arg(ctx, args, arg, "sort-section")) {
|
||||
} else if (read_flag(args, "sort-common")) {
|
||||
} else if (read_flag(args, "warn-once")) {
|
||||
} else if (read_flag(args, "nodefaultlibs")) {
|
||||
} else if (read_flag(args, "warn-constructors")) {
|
||||
} else if (read_arg(ctx, args, arg, "rpath-link")) {
|
||||
} else if (read_z_flag(args, "combreloc")) {
|
||||
} else if (read_z_flag(args, "nocombreloc")) {
|
||||
} else if (read_arg(ctx, args, arg, "version-script")) {
|
||||
remaining.push_back("--version-script");
|
||||
remaining.push_back(arg);
|
||||
} else if (read_arg(ctx, args, arg, "dynamic-list")) {
|
||||
remaining.push_back("--dynamic-list");
|
||||
remaining.push_back(arg);
|
||||
} else if (read_flag(args, "as-needed")) {
|
||||
remaining.push_back("-as-needed");
|
||||
} else if (read_flag(args, "no-as-needed")) {
|
||||
remaining.push_back("-no-as-needed");
|
||||
} else if (read_flag(args, "whole-archive")) {
|
||||
remaining.push_back("-whole-archive");
|
||||
} else if (read_flag(args, "no-whole-archive")) {
|
||||
remaining.push_back("-no-whole-archive");
|
||||
} else if (read_arg(ctx, args, arg, "l")) {
|
||||
remaining.push_back("-l");
|
||||
remaining.push_back(arg);
|
||||
} else if (read_arg(ctx, args, arg, "script") ||
|
||||
read_arg(ctx, args, arg, "T")) {
|
||||
remaining.push_back(arg);
|
||||
} else if (read_flag(args, "push-state")) {
|
||||
remaining.push_back("-push-state");
|
||||
} else if (read_flag(args, "pop-state")) {
|
||||
remaining.push_back("-pop-state");
|
||||
} else {
|
||||
if (args[0] == "-z" && args.size() >= 2)
|
||||
Fatal(ctx) << "mold: unknown command line option: -z " << args[1];
|
||||
if (args[0][0] == '-')
|
||||
Fatal(ctx) << "mold: unknown command line option: " << args[0];
|
||||
remaining.push_back(args[0]);
|
||||
args = args.subspan(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.arg.shared) {
|
||||
ctx.arg.pic = true;
|
||||
ctx.arg.dynamic_linker = "";
|
||||
}
|
||||
|
||||
if (ctx.arg.pic)
|
||||
ctx.arg.image_base = 0;
|
||||
|
||||
if (ctx.arg.retain_symbols_file) {
|
||||
ctx.arg.strip_all = false;
|
||||
ctx.arg.discard_all = false;
|
||||
}
|
||||
|
||||
if (ctx.arg.relocatable)
|
||||
ctx.arg.is_static = true;
|
||||
|
||||
if (!ctx.arg.shared) {
|
||||
if (!ctx.arg.filter.empty())
|
||||
Fatal(ctx) << "-filter may not be used without -shared";
|
||||
if (!ctx.arg.auxiliary.empty())
|
||||
Fatal(ctx) << "-auxiliary may not be used without -shared";
|
||||
}
|
||||
|
||||
if (char *env = getenv("MOLD_REPRO"); env && env[0])
|
||||
ctx.arg.repro = true;
|
||||
|
||||
if (ctx.arg.output.empty())
|
||||
ctx.arg.output = "a.out";
|
||||
|
||||
if (version_shown && remaining.empty())
|
||||
exit(0);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template \
|
||||
std::vector<std::string_view> \
|
||||
expand_response_files(Context<E> &ctx, char **argv); \
|
||||
\
|
||||
template \
|
||||
bool read_arg(Context<E> &ctx, std::span<std::string_view> &args, \
|
||||
std::string_view &arg, \
|
||||
std::string name); \
|
||||
\
|
||||
template std::string create_response_file(Context<E> &ctx); \
|
||||
\
|
||||
template \
|
||||
void parse_nonpositional_args(Context<E> &ctx, \
|
||||
std::vector<std::string_view> &remaining)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename C>
|
||||
std::vector<std::string_view>
|
||||
read_response_file(C &ctx, std::string_view path) {
|
||||
std::vector<std::string_view> vec;
|
||||
MappedFile<C> *mf = MappedFile<C>::must_open(ctx, std::string(path));
|
||||
u8 *data = mf->data;
|
||||
|
||||
auto read_quoted = [&](i64 i, char quote) {
|
||||
std::string buf;
|
||||
while (i < mf->size && data[i] != quote) {
|
||||
if (data[i] == '\\') {
|
||||
buf.append(1, data[i + 1]);
|
||||
i += 2;
|
||||
} else {
|
||||
buf.append(1, data[i++]);
|
||||
}
|
||||
}
|
||||
if (i >= mf->size)
|
||||
Fatal(ctx) << path << ": premature end of input";
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i + 1;
|
||||
};
|
||||
|
||||
auto read_unquoted = [&](i64 i) {
|
||||
std::string buf;
|
||||
|
||||
while (i < mf->size) {
|
||||
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;
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < mf->size;) {
|
||||
if (isspace(data[i]))
|
||||
i++;
|
||||
else if (data[i] == '\'')
|
||||
i = read_quoted(i + 1, '\'');
|
||||
else if (data[i] == '\"')
|
||||
i = read_quoted(i + 1, '\"');
|
||||
else
|
||||
i = read_unquoted(i);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Replace "@path/to/some/text/file" with its file contents.
|
||||
template <typename C>
|
||||
std::vector<std::string_view> expand_response_files(C &ctx, char **argv) {
|
||||
std::vector<std::string_view> vec;
|
||||
for (i64 i = 0; argv[i]; i++) {
|
||||
if (argv[i][0] == '@')
|
||||
append(vec, read_response_file(ctx, argv[i] + 1));
|
||||
else
|
||||
vec.push_back(argv[i]);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
static inline std::string_view string_trim(std::string_view str) {
|
||||
size_t pos = str.find_first_not_of(" \t");
|
||||
if (pos == str.npos)
|
||||
return "";
|
||||
str = str.substr(pos);
|
||||
|
||||
pos = str.find_last_not_of(" \t");
|
||||
if (pos == str.npos)
|
||||
return str;
|
||||
return str.substr(0, pos + 1);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -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,6 +18,15 @@
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
#define CHECK(fn) \
|
||||
do { \
|
||||
[[maybe_unused]] int r = (fn); \
|
||||
assert(r == Z_OK); \
|
||||
} while (0)
|
||||
|
||||
namespace mold {
|
||||
|
||||
static constexpr i64 SHARD_SIZE = 1024 * 1024;
|
||||
|
||||
@@ -31,15 +42,16 @@ 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, we chose compression level 3.
|
||||
// pretty well with lower compression levels, we chose compression
|
||||
// level 1.
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
int r = deflateInit2(&strm, 3, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
|
||||
assert(r == Z_OK);
|
||||
|
||||
CHECK(deflateInit2(&strm, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
|
||||
|
||||
// Set an input buffer
|
||||
strm.avail_in = input.size();
|
||||
@@ -49,19 +61,44 @@ static std::vector<u8> do_compress(std::string_view input) {
|
||||
// on the compression size. +16 for Z_SYNC_FLUSH.
|
||||
std::vector<u8> buf(deflateBound(&strm, strm.avail_in) + 16);
|
||||
|
||||
// Compress data. It writes all compressed bytes except the last
|
||||
// partial byte, so up to 7 bits can be held to be written to the
|
||||
// buffer.
|
||||
strm.avail_out = buf.size();
|
||||
strm.next_out = buf.data();
|
||||
CHECK(deflate(&strm, Z_BLOCK));
|
||||
|
||||
// This is a workaround for libbacktrace before 2022-04-06.
|
||||
//
|
||||
// Zlib is a bit stream, and what Z_SYNC_FLUSH does is to write a
|
||||
// three bit value indicating the start of an uncompressed data
|
||||
// block followed by four byte data 00 00 ff ff which indicates that
|
||||
// the length of the block is zero. libbacktrace uses its own zlib
|
||||
// inflate routine, and it had a bug that if that particular three
|
||||
// bit value happens to end at a byte boundary, it accidentally
|
||||
// skipped the next byte.
|
||||
//
|
||||
// In order to avoid triggering that bug, we should avoid calling
|
||||
// deflate() with Z_SYNC_FLUSH if the current bit position is 5.
|
||||
// If it's 5, we insert an empty block consisting of 10 bits so
|
||||
// that the bit position is 7 in the next byte.
|
||||
//
|
||||
// https://github.com/ianlancetaylor/libbacktrace/pull/87
|
||||
int nbits;
|
||||
deflatePending(&strm, Z_NULL, &nbits);
|
||||
if (nbits == 5)
|
||||
CHECK(deflatePrime(&strm, 10, 2));
|
||||
CHECK(deflate(&strm, Z_SYNC_FLUSH));
|
||||
|
||||
r = deflate(&strm, Z_SYNC_FLUSH);
|
||||
assert(r == Z_OK);
|
||||
assert(strm.avail_out > 0);
|
||||
|
||||
buf.resize(buf.size() - strm.avail_out);
|
||||
buf.shrink_to_fit();
|
||||
deflateEnd(&strm);
|
||||
return buf;
|
||||
}
|
||||
|
||||
Compressor::Compressor(std::string_view input) {
|
||||
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
|
||||
std::string_view input{(char *)buf, (size_t)size};
|
||||
std::vector<std::string_view> inputs = split(input);
|
||||
std::vector<u64> adlers(inputs.size());
|
||||
shards.resize(inputs.size());
|
||||
@@ -69,23 +106,21 @@ Compressor::Compressor(std::string_view input) {
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
adlers[i] = adler32(1, (u8 *)inputs[i].data(), inputs[i].size());
|
||||
shards[i] = do_compress(inputs[i]);
|
||||
shards[i] = zlib_compress(inputs[i]);
|
||||
});
|
||||
|
||||
// Combine checksums
|
||||
checksum = adlers[0];
|
||||
for (i64 i = 1; i < inputs.size(); i++)
|
||||
checksum = adler32_combine(checksum, adlers[i], inputs[i].size());
|
||||
|
||||
// Comput the total size
|
||||
compressed_size = 8; // the header and the trailer
|
||||
for (std::vector<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
i64 Compressor::size() const {
|
||||
i64 size = 2; // +2 for header
|
||||
for (const std::vector<u8> &shard : shards)
|
||||
size += shard.size();
|
||||
return size + 6; // +6 for trailer and checksum
|
||||
}
|
||||
|
||||
void Compressor::write_to(u8 *buf) {
|
||||
void ZlibCompressor::write_to(u8 *buf) {
|
||||
// Write a zlib-format header
|
||||
buf[0] = 0x78;
|
||||
buf[1] = 0x9c;
|
||||
@@ -101,10 +136,50 @@ void Compressor::write_to(u8 *buf) {
|
||||
});
|
||||
|
||||
// Write a trailer
|
||||
u8 *end = buf + size();
|
||||
u8 *end = buf + compressed_size;
|
||||
end[-6] = 3;
|
||||
end[-5] = 0;
|
||||
|
||||
// Write a checksum
|
||||
write32be(end - 4, checksum);
|
||||
*(ub32 *)(end - 4) = checksum;
|
||||
}
|
||||
|
||||
static std::vector<u8> zstd_compress(std::string_view input) {
|
||||
std::vector<u8> buf(ZSTD_COMPRESSBOUND(input.size()));
|
||||
constexpr int level = 3; // compression level; must be between 1 to 22
|
||||
|
||||
size_t sz = ZSTD_compress(buf.data(), buf.size(), input.data(), input.size(),
|
||||
level);
|
||||
assert(!ZSTD_isError(sz));
|
||||
buf.resize(sz);
|
||||
buf.shrink_to_fit();
|
||||
return buf;
|
||||
}
|
||||
|
||||
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size) {
|
||||
std::string_view input{(char *)buf, (size_t)size};
|
||||
std::vector<std::string_view> inputs = split(input);
|
||||
shards.resize(inputs.size());
|
||||
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
shards[i] = zstd_compress(inputs[i]);
|
||||
});
|
||||
|
||||
compressed_size = 0;
|
||||
for (std::vector<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
void ZstdCompressor::write_to(u8 *buf) {
|
||||
// Copy compressed data
|
||||
std::vector<i64> offsets(shards.size());
|
||||
for (i64 i = 1; i < shards.size(); i++)
|
||||
offsets[i] = offsets[i - 1] + shards[i - 1].size();
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
|
||||
memcpy(&buf[offsets[i]], shards[i].data(), shards[i].size());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,3 @@
|
||||
#define MOLD_VERSION "@mold_VERSION@"
|
||||
#define MOLD_LIBDIR "@CMAKE_INSTALL_FULL_LIBDIR@"
|
||||
#cmakedefine01 MOLD_IS_SOLD
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
#include "third-party/rust-demangle/rust-demangle.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string_view demangle(std::string_view name) {
|
||||
static thread_local char *p;
|
||||
if (p)
|
||||
free(p);
|
||||
|
||||
// Try to demangle as a Rust symbol. Since legacy-style Rust symbols
|
||||
// are also valid as a C++ mangled name, we need to call this before
|
||||
// cpp_demangle.
|
||||
p = rust_demangle(std::string(name).c_str(), 0);
|
||||
if (p)
|
||||
return p;
|
||||
|
||||
// Try to demangle as a C++ symbol.
|
||||
if (std::optional<std::string_view> s = cpp_demangle(name))
|
||||
return *s;
|
||||
return name;
|
||||
}
|
||||
|
||||
std::optional<std::string_view> cpp_demangle(std::string_view name) {
|
||||
static thread_local char *buf;
|
||||
static thread_local size_t buflen;
|
||||
|
||||
// TODO(cwasser): Actually demangle Symbols on Windows using e.g.
|
||||
// `UnDecorateSymbolName` from Dbghelp, maybe even Itanium symbols?
|
||||
#ifndef _WIN32
|
||||
if (name.starts_with("_Z")) {
|
||||
int status;
|
||||
char *p = abi::__cxa_demangle(std::string(name).c_str(), buf, &buflen, &status);
|
||||
if (status == 0) {
|
||||
buf = p;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/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`).
|
||||
#
|
||||
# The mold executable created by this script is statically-linked to
|
||||
# libstdc++ and libcrypto but dynamically-linked to libc, libm, libz
|
||||
# and librt, as they almost always exist on any Linux systems.
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | ppc64le | s390x ]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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
|
||||
set -e -x
|
||||
|
||||
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) &&
|
||||
[ $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"
|
||||
@@ -31,7 +31,7 @@ If a program is statically-linked, there's no dynamic loader that
|
||||
rewrites the GOT entries. Therefore, if a program is
|
||||
statically-linked, a libc's startup routine does that on behalf of the
|
||||
dynamic loader. Concretely, a startup routine interprets all dynamic
|
||||
relocations between `__rela_iplt_start` and `__rela_iplt_start`
|
||||
relocations between `__rela_iplt_start` and `__rela_iplt_stop`
|
||||
symbols. It is linker's responsibility to emit dynamic relocations
|
||||
for IFUNC symbols even if it is linking a statically-linked program
|
||||
and mark the beginning and the ending of a `.rela.dyn` section with
|
||||
@@ -41,7 +41,7 @@ The bug was my linker didn't define `__rela_iplt_start` and
|
||||
`__rela_iplt_stop` symbols. Since these symbols are weak, they are
|
||||
initialized to zero. From the point of the initializer function,
|
||||
there's no dynamic relocations between `__rela_iplt_start` and
|
||||
`__rela_iplt_start` symbols. That left GOT entries for IFUNC symbols
|
||||
`__rela_iplt_stop` symbols. That left GOT entries for IFUNC symbols
|
||||
untouched.
|
||||
|
||||
The proper fix was to emit dynamic relocations for IFUNC symbols and
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# Coding Guidelines
|
||||
|
||||
mold is written in C++20, but as is the case with every C++ project,
|
||||
it has local coding rules. In this document, I'll explain some of them
|
||||
and try to give justifications for why I chose such rules.
|
||||
|
||||
## DOs
|
||||
|
||||
- Always use `i64` (which is a type alias for `int64_t` in mold) for
|
||||
integers unless you have a reason not to. For example, even if you know
|
||||
that a loop counter won't exceed 100, you should stop thinking about it
|
||||
and just use `i64`.
|
||||
|
||||
Justification: Local variables are usually on CPU registers, so on
|
||||
64-bit CPUs, there's no performance peanlty on choosing `i64` over
|
||||
`i32`. Even if a compiler has to spill register values to the stack,
|
||||
I don't think there's an observable difference between `i32` and
|
||||
`i64`. Therefore, extra 32 bits are essentially free. On 32-bit CPUs,
|
||||
they are not free, but that's OK because we are writing mold for modern
|
||||
computers. mold will still run on 32-bit computers but a bit slowly.
|
||||
|
||||
By always using `i64`, we can eliminate the need to think about the
|
||||
"right" size for each variable. It also reduces the risk of integer
|
||||
overflow.
|
||||
|
||||
Exceptions: If you have to allocate a very large number (e.g. millions)
|
||||
of the same object, its size matters. In that case, use a smaller type.
|
||||
|
||||
## DON'Ts
|
||||
|
||||
- Don't use `auto` unless its actual type is obvious in the very narrow
|
||||
context. Currently, we use `auto` only for lambdas.
|
||||
|
||||
Justification: I think `auto` makes code writing easier but code reading
|
||||
harder, because readers have to make a guess as to what is the actual
|
||||
type of `auto`. If you are already familiar with the existing codebase,
|
||||
you may be able to guess it easily, but that's not always the case.
|
||||
I want to keep the mold codebase friendly to first-time readers.
|
||||
|
||||
- Don't over-use inheritance. In mold, most classes don't have parents,
|
||||
and even if they do, their class hierarchy is very shallow. Currently
|
||||
its height is just two (i.e. abstract classes and their implementations).
|
||||
|
||||
Justification: Designing class hierarchies is fun as it feels like
|
||||
taxonomy, but I don't think that always help writing code. It looks like
|
||||
simpler class hierarchy makes its code simpler.
|
||||
|
After Width: | Height: | Size: 70 KiB |
@@ -0,0 +1,486 @@
|
||||
## Design and implementation of mold
|
||||
|
||||
For the rest of this documentation, I'll explain the design and the
|
||||
implementation of mold. If you are only interested in using mold, you
|
||||
don't need to read the below.
|
||||
|
||||
### Motivation
|
||||
|
||||
Here is why I'm writing a new linker:
|
||||
|
||||
- Even though lld has significantly improved the situation, linking is
|
||||
still one of the slowest steps in a build. It is especially
|
||||
annoying when I changed one line of code and had to wait for a few
|
||||
seconds or even more for a linker to complete. It should be
|
||||
instantaneous. There's a need for a faster linker.
|
||||
|
||||
- The number of cores on a PC has increased a lot lately, and this
|
||||
trend is expected to continue. However, the existing linkers can't
|
||||
take the advantage of the trend because they don't scale well for more
|
||||
cores. I have a 64-core/128-thread machine, so my goal is to create
|
||||
a linker that uses the CPU nicely. mold should be much faster than
|
||||
other linkers on 4 or 8-core machines too, though.
|
||||
|
||||
- It looks to me that the designs of the existing linkers are somewhat
|
||||
too similar, and I believe there are a lot of drastically different
|
||||
designs that haven't been explored yet. Developers generally don't
|
||||
care about linkers as long as they work correctly, and they don't
|
||||
even think about creating a new one. So there may be lots of low
|
||||
hanging fruits there in this area.
|
||||
|
||||
### Basic design
|
||||
|
||||
- In order to achieve a `cp`-like performance, the most important
|
||||
thing is to fix the layout of an output file as quickly as possible, so
|
||||
that we can start copying actual data from input object files to an
|
||||
output file as soon as possible.
|
||||
|
||||
- Copying data from input files to an output file is I/O-bounded, so
|
||||
there should be room for doing computationally-intensive tasks while
|
||||
copying data from one file to another.
|
||||
|
||||
- We should allow the linker to preload object files from disk and
|
||||
parse them in memory before a complete set of input object files
|
||||
is ready. To do so, we need
|
||||
to split the linker into two in such a way that the latter half of
|
||||
the process finishes as quickly as possible by speculatively parsing
|
||||
and preprocessing input files in the first half of the process.
|
||||
|
||||
- One of the most computationally-intensive stage among linker stages
|
||||
is symbol resolution. To resolve symbols, we basically have to throw
|
||||
all symbol strings into a hash table to match undefined symbols with
|
||||
defined symbols. But this can be done in the preloading stage using
|
||||
[string interning](https://en.wikipedia.org/wiki/String_interning).
|
||||
|
||||
- Object files may contain a special section called a mergeable string
|
||||
section. The section contains lots of null-terminated strings, and
|
||||
the linker is expected to gather all mergeable string sections and
|
||||
merge their contents. So, if two object files contain the same
|
||||
string literal, for example, the resulting output will contain a
|
||||
single merged string. This step is computationally intensive, but string
|
||||
merging can be done in the preloading stage using string interning.
|
||||
|
||||
- Static archives (.a files) contain object files, but the static
|
||||
archive's string table contains only defined symbols of member
|
||||
object files and lacks other types of symbols. That makes static
|
||||
archives unsuitable for speculative parsing. Therefore, the linker
|
||||
should ignore the symbol table of static archive and directly read
|
||||
static archive members.
|
||||
|
||||
- If there's a relocation that uses a GOT of a symbol, then we have to
|
||||
create a GOT entry for that symbol. Otherwise, we shouldn't. That
|
||||
means we need to scan all relocation tables to fix the length and
|
||||
the contents of a .got section. This is computationally intensive,
|
||||
but this step is parallelizable.
|
||||
|
||||
### Linker Script
|
||||
|
||||
Linker script is an embedded language for the linker. It is mainly
|
||||
used to control how input sections are mapped to output sections and
|
||||
the layout of the output, but it can also do a lot of tricky stuff.
|
||||
Its feature is useful especially for embedded programming, but it's
|
||||
also an awfully underdocumented and complex language.
|
||||
|
||||
We have to implement a subset of the linker script language anwyay,
|
||||
because on Linux, /usr/lib/x86_64-linux-gnu/libc.so is (despite its
|
||||
name) not a shared object file but actually an ASCII file containing
|
||||
linker script code to load the _actual_ libc.so file. But the feature
|
||||
set for this purpose is very limited, and it is okay to implement them
|
||||
to mold.
|
||||
|
||||
Besides that, we really don't want to implement the linker script
|
||||
language. But at the same time, we want to satisfy the user needs that
|
||||
are currently satisfied with the linker script language. So, what
|
||||
should we do? Here is my observation:
|
||||
|
||||
- Linker script allows doing a lot of tricky stuff, such as specifying
|
||||
the exact layout of a file, inserting arbitrary bytes between
|
||||
sections, etc. But most of them can be done with a post-link binary
|
||||
editing tool (such as `objcopy`).
|
||||
|
||||
- It looks like there are two things that truly cannot be done by a
|
||||
post-link editing tool: (a) mapping input sections to output
|
||||
sections, and (b) applying relocations.
|
||||
|
||||
From the above observation, I believe we need to provide only the
|
||||
following features instead of the entire linker script language:
|
||||
|
||||
- A method to specify how input sections are mapped to output
|
||||
sections, and
|
||||
|
||||
- a method to set addresses to output sections, so that relocations
|
||||
are applied based on desired addresses.
|
||||
|
||||
I believe everything else can be done with a post-link binary editing
|
||||
tool.
|
||||
|
||||
### Details
|
||||
|
||||
- As we aim to the 1-second goal for Chromium, every millisecond
|
||||
counts. We can't ignore the latency of process exit. If we mmap a
|
||||
lot of files, \_exit(2) is not instantaneous but takes a few hundred
|
||||
milliseconds because the kernel has to clean up a lot of
|
||||
resources. As a workaround, we should organize the linker command as
|
||||
two processes; the first process forks the second process, and the
|
||||
second process does the actual work. As soon as the second process
|
||||
writes a result file to a filesystem, it notifies the first process,
|
||||
and the first process exits. The second process can take time to
|
||||
exit, because it is not an interactive process.
|
||||
|
||||
- At least on Linux, it looks like the filesystem's performance to
|
||||
allocate new blocks to a new file is the limiting factor when
|
||||
creating a new large file and filling its contents using mmap.
|
||||
If you already have a large file in the buffer cache, writing to it is
|
||||
much faster than creating a new fresh file and writing to it.
|
||||
Based on this observation, mold overwrites to an existing
|
||||
executable file if exists. My quick benchmark showed that I could
|
||||
save 300 milliseconds when creating a 2 GiB output file.
|
||||
Linux doesn't allow to open an executable for writing if it is
|
||||
running (you'll get a "text busy" error if you attempt). mold
|
||||
falls back to the usual way if it fails to open an output file.
|
||||
|
||||
- The output from the linker should be deterministic for the sake of
|
||||
[build reproducibility](https://en.wikipedia.org/wiki/Reproducible_builds)
|
||||
and ease of debugging. This might add a little bit of overhead to
|
||||
the linker, but that shouldn't be too much.
|
||||
|
||||
- A .build-id, a unique ID embedded to an output file, is usually
|
||||
computed by applying a cryptographic hash function (e.g. SHA-1) to
|
||||
an output file. This is a slow step, but we can speed it up by
|
||||
splitting a file into small chunks, computing SHA-1 for each chunk,
|
||||
and then computing SHA-1 of the concatenated SHA-1 hashes
|
||||
(i.e. constructing a [Merkle
|
||||
Tree](https://en.wikipedia.org/wiki/Merkle_tree) of height 2).
|
||||
Modern x86 processors have purpose-built instructions for SHA-1 and
|
||||
can compute SHA-1 pretty quickly at about 2 GiB/s. Using 16
|
||||
cores, a build-id for a 2 GiB executable can be computed in 60 to 70
|
||||
milliseconds.
|
||||
|
||||
- BFD, gold, and lld support section garbage collection. That is, a
|
||||
linker runs a mark-sweep garbage collection on an input graph, where
|
||||
sections are vertices and relocations are edges, to discard all
|
||||
sections that are not reachable from the entry point symbol
|
||||
(i.e. `_start`) or a few other root sections. In mold, we are using
|
||||
multiple threads to mark sections concurrently.
|
||||
|
||||
- Similarly, BFD, gold an lld support Identical Comdat Folding (ICF)
|
||||
as yet another size optimization. ICF merges two or more read-only
|
||||
sections that happen to have the same contents and relocations.
|
||||
To do that, we have to find isomorphic subgraphs from larger graphs.
|
||||
I implemented a new algorithm for mold, which is 5x faster than lld
|
||||
to do ICF for Chromium (from 5 seconds to 1 second).
|
||||
|
||||
- [Intel Threading Building
|
||||
Blocks](https://github.com/oneapi-src/oneTBB) (TBB) is a good
|
||||
library for parallel execution and has several concurrent
|
||||
containers. We are particularly interested in using
|
||||
`parallel_for_each` and `concurrent_hash_map`.
|
||||
|
||||
- TBB provides `tbbmalloc` which works better for multi-threaded
|
||||
applications than the glib'c malloc, but it looks like
|
||||
[jemalloc](https://github.com/jemalloc/jemalloc) and
|
||||
[mimalloc](https://github.com/microsoft/mimalloc) are a little bit
|
||||
more scalable than `tbbmalloc`.
|
||||
|
||||
### Size of the problem
|
||||
|
||||
When linking Chrome, a linker reads 3,430,966,844 bytes of data in
|
||||
total. The data contains the following items:
|
||||
|
||||
| Data item | Number
|
||||
| ------------------------ | ------
|
||||
| Object files | 30,723
|
||||
| Public undefined symbols | 1,428,149
|
||||
| Mergeable strings | 1,579,996
|
||||
| Comdat groups | 9,914,510
|
||||
| Regular sections¹ | 10,345,314
|
||||
| Public defined symbols | 10,512,135
|
||||
| Symbols | 23,953,607
|
||||
| Sections | 27,543,225
|
||||
| Relocations against SHF_ALLOC sections | 39,496,375
|
||||
| Relocations | 62,024,719
|
||||
|
||||
¹ Sections that have to be copied from input object files to an
|
||||
output file. Sections that contain relocations or symbols are for
|
||||
example excluded.
|
||||
|
||||
### Internals
|
||||
|
||||
In this section, I'll explain the internals of mold linker.
|
||||
|
||||
#### A brief history of Unix and the Unix linker
|
||||
|
||||
Conceptually, what a linker does is pretty simple. A compiler compiles
|
||||
a fragment of a program (a single source file) into a fragment of
|
||||
machine code and data (an object file, which typically has the .o
|
||||
extension), and a linker stitches them together into a single
|
||||
executable or a shared library image.
|
||||
|
||||
In reality, modern linkers for Unix-like systems are much more
|
||||
complicated than the naive understanding because they have gradually
|
||||
gained one feature at a time over the 50 years history of Unix, and
|
||||
they are now something like a bag of lots of miscellaneous features in
|
||||
which none of the features is more important than the others. It is
|
||||
very easy to miss the forest for the trees, since for those who don't
|
||||
know the details of the Unix linker, it is not clear which feature is
|
||||
essential and which is not.
|
||||
|
||||
That being said, one thing is clear that at any point of Unix history,
|
||||
a Unix linker has a coherent feature set for the Unix of that age. So,
|
||||
let me entangle the history to see how the operating system, runtime,
|
||||
and linker have gained features that we see today. That should give
|
||||
you an idea of why a particular feature has been added to a linker in the
|
||||
first place.
|
||||
|
||||
1. Original Unix didn't support shared libraries, and a program was
|
||||
always loaded to a fixed address. An executable was something like
|
||||
a memory dump that was just loaded to a particular address by the
|
||||
kernel. After loading, the kernel started executing the program by
|
||||
setting the instruction pointer to a particular address.
|
||||
|
||||
The most essential feature for any linker is relocation processing.
|
||||
The original Unix linker of course supported that. Let me explain
|
||||
what that is.
|
||||
|
||||
Individual object files are inevitably incomplete as a program,
|
||||
because when a compiler created them, it only see a part of an
|
||||
entire program. For example, if an object file contains a function
|
||||
call that refers to another object file, the `call` instruction in the
|
||||
object cannot be complete, as the compiler has no idea as to what
|
||||
is the called function's address. To deal with this, the compiler
|
||||
emits a placeholder value (typically just zero) instead of a real
|
||||
address and leaves metadata in an object file saying "fix offset X
|
||||
of this file with an address of Y". That metadata is called
|
||||
"relocation". Relocations are typically processed by the linker.
|
||||
|
||||
It is easy for a linker to apply relocations for the original Unix
|
||||
because a program is always loaded to a fixed address. It exactly
|
||||
knows the addresses of all functions and data when linking a
|
||||
program.
|
||||
|
||||
Static library support, which is still an important feature of Unix
|
||||
linker, also dates back to this early period of Unix history.
|
||||
To understand what it is, imagine that you are trying to compile
|
||||
a program for the early Unix. You don't want to waste time to
|
||||
compile libc functions every time you compile your program (the
|
||||
computers of the era were incredibly slow), so you have already
|
||||
placed each libc function into a separate source file and compiled
|
||||
them individually. That means you have object files for each libc
|
||||
function, e.g., printf.o, scanf.o, atoi.o, write.o, etc.
|
||||
|
||||
Given this configuration, all you have to do to link your program
|
||||
against libc functions is to pick up the right set of libc object
|
||||
files and give them to the linker along with the object files of your
|
||||
program. But, keeping the linker command line in sync with the
|
||||
libc functions you are using in your program is bothersome. You can
|
||||
be conservative; you can specify all libc object files to the
|
||||
command line, but that leads to program bloat because the linker
|
||||
unconditionally link all object files given to it no matter whether
|
||||
they are used or not. So, a new feature was added to the linker to
|
||||
fix the problem. That is the static library, which is also called
|
||||
the archive file.
|
||||
|
||||
An archive file is just a bundle of object files, just like zip
|
||||
file but in an uncompressed form. An archive file typically has the
|
||||
.a file extension and named after its contents. For example, the
|
||||
archive file containing all libc objects is named `libc.a`.
|
||||
|
||||
If you pass an archive file along with other object files to the
|
||||
linker, the linker pulls out an object file from the archive _only
|
||||
when_ it is referenced by other object files. In other words,
|
||||
unlike object files directly given to a linker, object files
|
||||
wrapped in an archive are not linked to the output by default.
|
||||
An archive works as a supplement to complete your program.
|
||||
|
||||
Even today, you can still find a libc archive file. Run `ar t
|
||||
/usr/lib/x86_64-linux-gnu/libc.a` on Linux should give you a list
|
||||
of object files in the libc archive.
|
||||
|
||||
2. In the '80s, Sun Microsystems, a leading commercial Unix vendor at the
|
||||
time, added shared library support to their Unix variant, SunOS.
|
||||
|
||||
(This section is incomplete.)
|
||||
|
||||
### Concurrency strategy
|
||||
|
||||
In this section, I'll explain the high-level concurrency strategy of
|
||||
mold.
|
||||
|
||||
In most places, mold adopts data parallelism. That is, we have a huge
|
||||
number of pieces of data of the same kind, and we process each of them
|
||||
individually using parallel for-loop. For example, after identifying
|
||||
the exact set of input object files, we need to scan all relocation
|
||||
tables to determine the sizes of .got and .plt sections. We do that
|
||||
using a parallel for-loop. The granularity of parallel processing in
|
||||
this case is the relocation table.
|
||||
|
||||
Data parallelism is very efficient and scalable because there's no
|
||||
need for threads to communicate with each other while working on each
|
||||
element of data. In addition to that, data parallelism is easy to
|
||||
understand, as it is just a for-loop in which multiple iterations may
|
||||
be executed in parallel. We don't use high-level communication or
|
||||
synchronization mechanisms such as channels, futures, promises,
|
||||
latches or something like that in mold.
|
||||
|
||||
In some cases, we need to share a little bit of data between threads
|
||||
while executing a parallel for-loop. For example, the loop to scan
|
||||
relocations turns on "requires GOT" or "requires PLT" flags in a
|
||||
symbol. Symbol is a shared resource, and writing to them from multiple
|
||||
threads without synchronization is unsafe. To deal with it, we made
|
||||
the flag an atomic variable.
|
||||
|
||||
The other common pattern you can find in mold which is build on top of
|
||||
the parallel for-loop is the map-reduce pattern. That is, we run a
|
||||
parallel for-loop on a large data set to produce a small data set and
|
||||
process the small data set with a single thread. Let me take a
|
||||
build-id computation as an example. Build-id is typically computed by
|
||||
applying a cryptographic hash function such as SHA-1 on a linker's
|
||||
output file. To compute it, we first consider an output as a sequence
|
||||
of 1 MiB blocks and compute a SHA-1 hash for each block in parallel.
|
||||
Then, we concatenate the SHA-1 hashes and compute a SHA-1 hash on the
|
||||
hashes to get a final build-id.
|
||||
|
||||
Finally, we use concurrent hashmap at a few places in mold. Concurrent
|
||||
hashmap is a hashmap to which multiple threads can safely insert items
|
||||
in parallel. We use it in the symbol resolution stage, for example.
|
||||
To resolve symbols, we basically have to throw in all defined symbols
|
||||
into a hash table, so that we can find a matching defined symbol for
|
||||
an undefined symbol by name. We do the hash table insertion from a
|
||||
parallel for-loop which iterates over a list of input files.
|
||||
|
||||
Overall, even though mold is highly scalable, it succeeded to avoid
|
||||
complexties you often find in complex parallel programs. From high
|
||||
level, mold just serially executes the linker's internal passes one by
|
||||
one. Each pass is parallelized using parallel for-loops.
|
||||
|
||||
### Rejected ideas
|
||||
|
||||
In this section, I'll explain the alternative designs I currently do
|
||||
not plan to implement and why I turned them down.
|
||||
|
||||
- Placing variable-length sections at end of an output file and start
|
||||
copying file contents before fixing the output file layout
|
||||
|
||||
Idea: Fixing the layout of regular sections seems easy, and if we
|
||||
place them at beginning of a file, we can start copying their
|
||||
contents from their input files to an output file. While copying
|
||||
file contents, we can compute the sizes of variable-length sections
|
||||
such as .got or .plt and place them at end of the file.
|
||||
|
||||
Reason for rejection: I did not choose this design because I doubt
|
||||
if it could actually shorten link time and I think I don't need it
|
||||
anyway.
|
||||
|
||||
The linker has to de-duplicate comdat sections (i.e. inline
|
||||
functions that are included in multiple object files), so we
|
||||
cannot compute the layout of regular sections until we resolve all
|
||||
symbols and de-duplicate comdats. That takes a few hundred
|
||||
milliseconds. After that, we can compute the sizes of
|
||||
variable-length sections in less than 100 milliseconds. It's quite
|
||||
fast, so it doesn't seem to make much sense to proceed without
|
||||
fixing the final file layout.
|
||||
|
||||
The other reason to reject this idea is because there's good a
|
||||
chance for this idea to have a negative impact on linker's overall
|
||||
performance. If we copy file contents before fixing the layout, we
|
||||
can't apply relocations to them while copying because symbol
|
||||
addresses are not available yet. If we fix the file layout first, we
|
||||
can apply relocations while copying, which is effectively zero-cost
|
||||
due to a very good data locality. On the other hand, if we apply
|
||||
relocations long after we copy file contents, it's pretty expensive
|
||||
because section contents are very likely to have been evicted from
|
||||
CPU cache.
|
||||
|
||||
- Incremental linking
|
||||
|
||||
Idea: Incremental linking is a technique to patch a previous linker's
|
||||
output file so that only functions or data that are updated from the
|
||||
previous build are written to it. It is expected to significantly
|
||||
reduce the amount of data copied from input files to an output file
|
||||
and thus speed up linking. GNU BFD and gold linkers support it.
|
||||
|
||||
Reason for rejection: I turned it down because it (1) is
|
||||
complicated, (2) doesn't seem to speed it up that much and (3) has
|
||||
several practical issues. Let me explain each of them.
|
||||
|
||||
First, incremental linking for real C/C++ programs is not as easy as
|
||||
one might think. Let me take malloc as an example. malloc is usually
|
||||
defined by libc, but you can implement it in your program, and if
|
||||
that's the case, the symbol `malloc` will be resolved to your
|
||||
function instead of the one in libc. If you include a library that
|
||||
defines malloc (such as libjemalloc or libtbbmallc) before libc,
|
||||
their malloc will override libc's malloc.
|
||||
|
||||
Assume that you are using a nonstandard malloc. What if you remove
|
||||
your malloc from your code, or remove `-ljemalloc` from your
|
||||
Makefile? The linker has to include a malloc from libc, which may
|
||||
include more object files to satisfy its dependencies. Such code
|
||||
change can affect the entire program rather than just replacing one
|
||||
function. The same is true for adding malloc to your program. Making
|
||||
a local change doesn't necessarily result in a local change in the
|
||||
binary level. It can easily have cascading effects.
|
||||
|
||||
Some ELF fancy features make incremental linking even harder to
|
||||
implement. Take the weak symbol as an example. If you define `atoi`
|
||||
as a weak symbol in your program, and if you are not using `atoi`
|
||||
at all in your program, that symbol will be resolved to address
|
||||
0. But if you start using some libc function that indirectly calls
|
||||
`atoi`, then `atoi` will be included in your program, and your weak
|
||||
symbol will be resolved to that function. I don't know how to
|
||||
efficiently fix up a binary for this case.
|
||||
|
||||
This is a hard problem, so existing linkers don't try too hard to
|
||||
solve it. For example, IIRC, gold falls back to full link if any
|
||||
function is removed from a previous build. If you want to not annoy
|
||||
users in the fallback case, you need to make full link fast anyway.
|
||||
|
||||
Second, incremental linking itself has an overhead. It has to detect
|
||||
updated files, patch an existing output file and write additional
|
||||
data to an output file for future incremental linking. GNU gold, for
|
||||
instance, takes almost 30 seconds on my machine to do a null
|
||||
incremental link (i.e. no object files are updated from a previous
|
||||
build) for chrome. It's just too slow.
|
||||
|
||||
Third, there are other practical issues in incremental linking. It's
|
||||
not reproducible, so your binary isn't going to be the same as other
|
||||
binaries even if you are compiling the same source tree using the
|
||||
same compiler toolchain. Or, it is complex and there might be a bug
|
||||
in it. If something doesn't work correctly, "remove --incremental
|
||||
from your Makefile and try again" could be a piece of advice, but
|
||||
that isn't ideal.
|
||||
|
||||
So, all in all, incremental linking is tricky. I wanted to make full
|
||||
link as fast as possible, so that we don't have to think about how
|
||||
to work around the slowness of full link.
|
||||
|
||||
- Defining a completely new file format and use it
|
||||
|
||||
Idea: Sometimes, the ELF file format itself seems to be a limiting
|
||||
factor in improving the linker's performance. We might be able to make a
|
||||
far better one if we create a new file format.
|
||||
|
||||
Reason for rejection: I rejected the idea because it apparently has
|
||||
a practical issue (backward compatibility issue) and also doesn't
|
||||
seem to improve the performance of linkers that much. As clearly
|
||||
demonstrated by mold, we can create a fast linker for ELF. I believe
|
||||
ELF isn't that bad, after all. The semantics of the existing Unix
|
||||
linkers, such as the name resolution algorithm or the linker script,
|
||||
have slowed the linkers down, but that's not a problem of the file
|
||||
format itself.
|
||||
|
||||
- Watching object files using inotify(2)
|
||||
|
||||
Idea: When mold is running as a daemon for preloading, use
|
||||
inotify(2) to watch file system updates so that it can reload files
|
||||
as soon as they are updated.
|
||||
|
||||
Reason for rejection: Just like the maximum number of files you can
|
||||
simultaneously open, the maximum number of files you can watch using
|
||||
inotify(2) isn't that large. Maybe just a single instance of mold is
|
||||
fine with inotify(2), but it may fail if you run multiple of it.
|
||||
|
||||
The other reason for not doing it is because mold is quite fast
|
||||
without it anyway. Invoking stat(2) on each file for file update
|
||||
check takes less than 100 milliseconds for Chrome, and if most of
|
||||
the input files are not updated, parsing updated files takes almost
|
||||
no time.
|
||||
@@ -0,0 +1,55 @@
|
||||
This page explains the following warning message and how to fix it.
|
||||
mold emits this message when it sees an object file that may not be
|
||||
compatible with mold.
|
||||
|
||||
|
||||
```
|
||||
mold: warning: foo.o: this file may cause a segmentation fault because it requires an executable stack. See https://github.com/rui314/mold/tree/main/docs/execstack.md for more info.
|
||||
```
|
||||
|
||||
# Background
|
||||
|
||||
On modern computers, the stack area (to which local variables are
|
||||
stored) cannot contain executable code. If the control reaches the
|
||||
stack area, the CPU refuses to execute any code there and the program
|
||||
is usually terminated due to segmentation fault.
|
||||
|
||||
This is a security measure. The stack area used to be executable (old
|
||||
CPUs generally execute any code as long as it is in a readable memory
|
||||
region), but that provided an easy attack vector to a malicious user.
|
||||
They wrote executable code to the stack area using some buffer
|
||||
overflow bug and jumped there to run arbitrary code in a remote server
|
||||
process.
|
||||
|
||||
To prevent this type of attack, the stack area is no longer executable
|
||||
since the early 2000s. On Linux, the stack's executable-ness is
|
||||
controlled by a bit in an executable, and the loader respects that
|
||||
bit. The bit is set by the linker.
|
||||
|
||||
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
|
||||
silently makes the stack of an output file executable.
|
||||
|
||||
But the GNU linker's behavior is dangerous. If you accidentally link
|
||||
an object file that has that marker section, the entire stack area
|
||||
silently becomes executable, disabling the security mechanism.
|
||||
|
||||
Therefore, mold simply ignores that marker section. If you are using
|
||||
mold, you need to explicitly pass `-z execstack` to the linker to make
|
||||
the stack executable.
|
||||
|
||||
# What caused this issue?
|
||||
|
||||
You are likely to use GCC's [Nested
|
||||
Functions](https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html)
|
||||
feature which still depends on the executable stack.
|
||||
|
||||
# How to fix it?
|
||||
|
||||
If you know what you are doing, pass `-z execstack` to mold. 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 depend on the executable stack.
|
||||
@@ -0,0 +1,158 @@
|
||||
The very concept of linking is simple: a compiler compiles a piece of
|
||||
source code into an object file (a file containing machine code), and
|
||||
a linker combines object files into a single executable or a shared
|
||||
library file. However, the actual implementation of the linker for
|
||||
modern systems is much more complicated because hardware, operating
|
||||
system, compiler and linker all have many more features.
|
||||
|
||||
In this file, I'll explain random topics in the glossary format that
|
||||
you need to understand to read mold code.
|
||||
|
||||
## DSO
|
||||
|
||||
A .so file. Short for Dynamic Shared Object. Often called as a
|
||||
shared library, a dynamic libray or a shared object as well.
|
||||
|
||||
An DSO contains common functions and data that are used by multiple
|
||||
executables and/or other DSOs. At runtime, a DSO is loaded to a
|
||||
contiguous region in the virtual address.
|
||||
|
||||
## Object file
|
||||
|
||||
A .o file. An object file contains machine code and data, but it
|
||||
cannot be executed because it's not self-contained. For example,
|
||||
if you compile a C source file containing a call of `printf`,
|
||||
the actual function code of `printf` is not included in the resulting
|
||||
object file. You include `stdio.h`, but that teaches the compiler
|
||||
only about `printf`'s type, and the compiler still don't know what
|
||||
`printf` actually does. Therefore, it cannot emit code for `printf`.
|
||||
|
||||
You need to link an object file with other object file or a shared
|
||||
library to make it exectuable.
|
||||
|
||||
## Virtual address space
|
||||
|
||||
A pointer has a value like 0x803020 which is an address of the
|
||||
pointee. But it doesn't mean that the pointee resides at the
|
||||
physical memory address 0x803020 on the computer. Modern CPUs
|
||||
contains so-called Mmeory Management Unit (MMU), and all access to
|
||||
the memory are first translated by MMU to the physical address.
|
||||
The address before translation is called the "virtual address".
|
||||
Unless you are doing the kernel programming, all addresses you
|
||||
handle are virtual addresses.
|
||||
|
||||
The OS kernel controls the MMU so that each process owns the entire
|
||||
virtual address space. So, even if two process uses the same virtual
|
||||
address, they don't conflict. They are mapped to different physical
|
||||
addresses.
|
||||
|
||||
The existence of MMU has several implications to the linker. First,
|
||||
we can link the main executable to a specific address. On process
|
||||
startup, there's no code or data in the virtual address space, so
|
||||
the mapping of the main executable always succeed. However, it's not
|
||||
true to DSOs because they are loaded after the main executable and
|
||||
possibly other DSOs. Therefore, shared libraries must be linked in a
|
||||
way that they can be loaded to any address in the virtual address
|
||||
space.
|
||||
|
||||
## Relocation
|
||||
|
||||
A piece of information for the linker as to how to link object files
|
||||
or a dynamic objects.
|
||||
|
||||
Object files can refer functions or data in other object files. For
|
||||
example, if you compile a function which calls a non-local function
|
||||
`foo`, the resulting code contains something like this:
|
||||
|
||||
```
|
||||
26: e8 00 00 00 00 callq 2b <bar+0xb>
|
||||
27: R_X86_64_PLT32 foo-0x4
|
||||
```
|
||||
|
||||
The above `callq` is the instruction to call a function at the
|
||||
machine code level. It's opcode is `0xe8` in x86-64, so the
|
||||
instruction begins with `0xe8`. The following four bytes are
|
||||
displacement; that is, the address of the branch target relative to
|
||||
the end of this `callq` instruction. Notice that the displacement is
|
||||
0. The compiler couldn't fill the displacement because it has no
|
||||
idea as to where `foo` will be at runtime. So, the compiler write 0
|
||||
as a placeholder and instead write a relocation `R_X86_64_PLT32`
|
||||
with `foo` as its associated symbol. The linker reads this
|
||||
relocation, computes the offsets between this call instruction and
|
||||
function `foo` and overwrite the placeholder value 0 with an actual
|
||||
displacement.
|
||||
|
||||
There are many different types of relocations. For example, if you
|
||||
want to fix up not with a displacement but with an absolute address
|
||||
of a symbol, you need to use `R_X86_64_ABS64` instead.
|
||||
|
||||
## Static library
|
||||
|
||||
A .a file. Often called as an archive file or just archive as well.
|
||||
|
||||
A static library is a container just like tar or zip. Actually,
|
||||
there's no technical reason to not use tar or (uncompressed) zip,
|
||||
but traditionally the .a file format is used by the linker.
|
||||
|
||||
A static library contains object files and can be passed to the
|
||||
linker along with other object files and/or archives.
|
||||
|
||||
A linker pulls out object files from an archive only if it is needed
|
||||
to resolve undefined symbols. In other words, object files in an
|
||||
archive are not linked by default and used as a complement to supply
|
||||
missing definitions. This is ideal for a library because you don't
|
||||
want to link library functions unless you are actually using them.
|
||||
|
||||
Contrary to archive files, object files directly given to a linker
|
||||
are always linked to the output.
|
||||
|
||||
To maximize the benefit of archive files, a library often used as a
|
||||
static library is broken down to small files to separate each
|
||||
function individually (for example, look at
|
||||
https://git.musl-libc.org/cgit/musl/tree/src/stdio). By doing this,
|
||||
you import only used functions.
|
||||
|
||||
A static file is created by `ar`, whose command line arguments are
|
||||
similar to `tar`. A static library contains the symbol table which
|
||||
offers a quick way to look up an object file for a defined symbol,
|
||||
but mold does not use the static library's symbol table. mold
|
||||
doesdn't need a symbol table to exist in an archive, and if exists,
|
||||
mold just ignores it.
|
||||
|
||||
See also: DSO (dynamic library)
|
||||
|
||||
## Symbol
|
||||
|
||||
A symbol is a label assigned to a specific location in an input file
|
||||
or an output file. For example, if you define function `foo` and
|
||||
compile it, the resulting object file contains a symbol `foo`
|
||||
pointing to the beginning of the machine code for `foo`.
|
||||
|
||||
Usually, a symbol name is a function or a variable name. If an
|
||||
object is anonymous (such the one for a string literal), a compiler
|
||||
generated a unique symbol, which often starts with `.` to avoid
|
||||
conflict with user-defined symbols.
|
||||
|
||||
For C++, symbol name is a complex "mangled" name. We need to mangle
|
||||
identifiers because a simple name such as `foo` cannot be uniquely
|
||||
identify a function or a data in C++, because for example `foo` may
|
||||
be in a namespace or defined as a static member in some class. If
|
||||
`foo` is an overloaded function, we need to distinguish different
|
||||
`foo`s by its type. Therefore, C++ compiler mangles an identifier by
|
||||
appending nmaepsace names, type information and such so that
|
||||
different things get different names.
|
||||
|
||||
For example, a function `int foo(int)` in a namespace `bar` is
|
||||
mangled as `_ZN3bar3fooEi`.
|
||||
|
||||
A symbol can be either defined or undefined. A defined symbol points
|
||||
to some location in a file which may contain the function's machine
|
||||
code or the variable's initial value. An undefined symbol does not
|
||||
point to anywhere. It needs to be merged with a defined symbol with
|
||||
the same name at link-time. This merging process is called "name
|
||||
resolution".
|
||||
|
||||
For example, if your program is using `printf`, it usually contains
|
||||
`printf` as an undefined symbol. You need to link it with `libc.a`
|
||||
or `libc.so`, which contain a defined symbol of `printf`, to make a
|
||||
complete program.
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 95 KiB |
@@ -1,702 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <endian.h>
|
||||
#include <string>
|
||||
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
#error "mold does not support big-endian hosts"
|
||||
#endif
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
|
||||
template <typename E>
|
||||
static std::string rel_to_string(u32 r_type);
|
||||
|
||||
struct X86_64;
|
||||
struct I386;
|
||||
|
||||
static constexpr u32 SHN_UNDEF = 0;
|
||||
static constexpr u32 SHN_ABS = 0xfff1;
|
||||
static constexpr u32 SHN_COMMON = 0xfff2;
|
||||
static constexpr u32 SHN_XINDEX = 0xffff;
|
||||
|
||||
static constexpr u32 SHT_NULL = 0;
|
||||
static constexpr u32 SHT_PROGBITS = 1;
|
||||
static constexpr u32 SHT_SYMTAB = 2;
|
||||
static constexpr u32 SHT_STRTAB = 3;
|
||||
static constexpr u32 SHT_RELA = 4;
|
||||
static constexpr u32 SHT_HASH = 5;
|
||||
static constexpr u32 SHT_DYNAMIC = 6;
|
||||
static constexpr u32 SHT_NOTE = 7;
|
||||
static constexpr u32 SHT_NOBITS = 8;
|
||||
static constexpr u32 SHT_REL = 9;
|
||||
static constexpr u32 SHT_SHLIB = 10;
|
||||
static constexpr u32 SHT_DYNSYM = 11;
|
||||
static constexpr u32 SHT_INIT_ARRAY = 14;
|
||||
static constexpr u32 SHT_FINI_ARRAY = 15;
|
||||
static constexpr u32 SHT_PREINIT_ARRAY = 16;
|
||||
static constexpr u32 SHT_GROUP = 17;
|
||||
static constexpr u32 SHT_SYMTAB_SHNDX = 18;
|
||||
static constexpr u32 SHT_GNU_HASH = 0x6ffffff6;
|
||||
static constexpr u32 SHT_GNU_VERDEF = 0x6ffffffd;
|
||||
static constexpr u32 SHT_GNU_VERNEED = 0x6ffffffe;
|
||||
static constexpr u32 SHT_GNU_VERSYM = 0x6fffffff;
|
||||
static constexpr u32 SHT_X86_64_UNWIND = 0x70000001;
|
||||
|
||||
static constexpr u32 SHF_WRITE = 0x1;
|
||||
static constexpr u32 SHF_ALLOC = 0x2;
|
||||
static constexpr u32 SHF_EXECINSTR = 0x4;
|
||||
static constexpr u32 SHF_MERGE = 0x10;
|
||||
static constexpr u32 SHF_STRINGS = 0x20;
|
||||
static constexpr u32 SHF_INFO_LINK = 0x40;
|
||||
static constexpr u32 SHF_LINK_ORDER = 0x80;
|
||||
static constexpr u32 SHF_GROUP = 0x200;
|
||||
static constexpr u32 SHF_TLS = 0x400;
|
||||
static constexpr u32 SHF_COMPRESSED = 0x800;
|
||||
static constexpr u32 SHF_EXCLUDE = 0x80000000;
|
||||
|
||||
static constexpr u32 GRP_COMDAT = 1;
|
||||
|
||||
static constexpr u32 STT_NOTYPE = 0;
|
||||
static constexpr u32 STT_OBJECT = 1;
|
||||
static constexpr u32 STT_FUNC = 2;
|
||||
static constexpr u32 STT_SECTION = 3;
|
||||
static constexpr u32 STT_FILE = 4;
|
||||
static constexpr u32 STT_COMMON = 5;
|
||||
static constexpr u32 STT_TLS = 6;
|
||||
static constexpr u32 STT_GNU_IFUNC = 10;
|
||||
|
||||
static constexpr u32 STB_LOCAL = 0;
|
||||
static constexpr u32 STB_GLOBAL = 1;
|
||||
static constexpr u32 STB_WEAK = 2;
|
||||
static constexpr u32 STB_GNU_UNIQUE = 10;
|
||||
|
||||
static constexpr u32 STV_DEFAULT = 0;
|
||||
static constexpr u32 STV_INTERNAL = 1;
|
||||
static constexpr u32 STV_HIDDEN = 2;
|
||||
static constexpr u32 STV_PROTECTED = 3;
|
||||
|
||||
static constexpr u32 VER_NDX_LOCAL = 0;
|
||||
static constexpr u32 VER_NDX_GLOBAL = 1;
|
||||
static constexpr u32 VER_NDX_LAST_RESERVED = 1;
|
||||
|
||||
static constexpr u32 VER_FLG_BASE = 1;
|
||||
static constexpr u32 VER_FLG_WEAK = 2;
|
||||
static constexpr u32 VER_FLG_INFO = 4;
|
||||
|
||||
static constexpr u32 VERSYM_HIDDEN = 0x8000;
|
||||
|
||||
static constexpr u32 PT_NULL = 0;
|
||||
static constexpr u32 PT_LOAD = 1;
|
||||
static constexpr u32 PT_DYNAMIC = 2;
|
||||
static constexpr u32 PT_INTERP = 3;
|
||||
static constexpr u32 PT_NOTE = 4;
|
||||
static constexpr u32 PT_SHLIB = 5;
|
||||
static constexpr u32 PT_PHDR = 6;
|
||||
static constexpr u32 PT_TLS = 7;
|
||||
static constexpr u32 PT_GNU_EH_FRAME = 0x6474e550;
|
||||
static constexpr u32 PT_GNU_STACK = 0x6474e551;
|
||||
static constexpr u32 PT_GNU_RELRO = 0x6474e552;
|
||||
|
||||
static constexpr u32 PF_X = 1;
|
||||
static constexpr u32 PF_W = 2;
|
||||
static constexpr u32 PF_R = 4;
|
||||
|
||||
static constexpr u32 ET_NONE = 0;
|
||||
static constexpr u32 ET_REL = 1;
|
||||
static constexpr u32 ET_EXEC = 2;
|
||||
static constexpr u32 ET_DYN = 3;
|
||||
|
||||
static constexpr u32 ELFDATA2LSB = 1;
|
||||
static constexpr u32 ELFDATA2MSB = 2;
|
||||
|
||||
static constexpr u32 ELFCLASS32 = 1;
|
||||
static constexpr u32 ELFCLASS64 = 2;
|
||||
|
||||
static constexpr u32 EV_CURRENT = 1;
|
||||
|
||||
static constexpr u32 EM_386 = 3;
|
||||
static constexpr u32 EM_X86_64 = 62;
|
||||
|
||||
static constexpr u32 EI_CLASS = 4;
|
||||
static constexpr u32 EI_DATA = 5;
|
||||
static constexpr u32 EI_VERSION = 6;
|
||||
static constexpr u32 EI_OSABI = 7;
|
||||
static constexpr u32 EI_ABIVERSION = 8;
|
||||
|
||||
static constexpr u32 ELFOSABI_NONE = 0;
|
||||
static constexpr u32 ELFOSABI_GNU = 3;
|
||||
|
||||
static constexpr u32 DT_NULL = 0;
|
||||
static constexpr u32 DT_NEEDED = 1;
|
||||
static constexpr u32 DT_PLTRELSZ = 2;
|
||||
static constexpr u32 DT_PLTGOT = 3;
|
||||
static constexpr u32 DT_HASH = 4;
|
||||
static constexpr u32 DT_STRTAB = 5;
|
||||
static constexpr u32 DT_SYMTAB = 6;
|
||||
static constexpr u32 DT_RELA = 7;
|
||||
static constexpr u32 DT_RELASZ = 8;
|
||||
static constexpr u32 DT_RELAENT = 9;
|
||||
static constexpr u32 DT_STRSZ = 10;
|
||||
static constexpr u32 DT_SYMENT = 11;
|
||||
static constexpr u32 DT_INIT = 12;
|
||||
static constexpr u32 DT_FINI = 13;
|
||||
static constexpr u32 DT_SONAME = 14;
|
||||
static constexpr u32 DT_RPATH = 15;
|
||||
static constexpr u32 DT_SYMBOLIC = 16;
|
||||
static constexpr u32 DT_REL = 17;
|
||||
static constexpr u32 DT_RELSZ = 18;
|
||||
static constexpr u32 DT_RELENT = 19;
|
||||
static constexpr u32 DT_PLTREL = 20;
|
||||
static constexpr u32 DT_DEBUG = 21;
|
||||
static constexpr u32 DT_TEXTREL = 22;
|
||||
static constexpr u32 DT_JMPREL = 23;
|
||||
static constexpr u32 DT_BIND_NOW = 24;
|
||||
static constexpr u32 DT_INIT_ARRAY = 25;
|
||||
static constexpr u32 DT_FINI_ARRAY = 26;
|
||||
static constexpr u32 DT_INIT_ARRAYSZ = 27;
|
||||
static constexpr u32 DT_FINI_ARRAYSZ = 28;
|
||||
static constexpr u32 DT_RUNPATH = 29;
|
||||
static constexpr u32 DT_FLAGS = 30;
|
||||
static constexpr u32 DT_GNU_HASH = 0x6ffffef5;
|
||||
static constexpr u32 DT_VERSYM = 0x6ffffff0;
|
||||
static constexpr u32 DT_RELACOUNT = 0x6ffffff9;
|
||||
static constexpr u32 DT_RELCOUNT = 0x6ffffffa;
|
||||
static constexpr u32 DT_FLAGS_1 = 0x6ffffffb;
|
||||
static constexpr u32 DT_VERDEF = 0x6ffffffc;
|
||||
static constexpr u32 DT_VERDEFNUM = 0x6ffffffd;
|
||||
static constexpr u32 DT_VERNEED = 0x6ffffffe;
|
||||
static constexpr u32 DT_VERNEEDNUM = 0x6fffffff;
|
||||
static constexpr u32 DT_AUXILIARY = 0x7ffffffd;
|
||||
static constexpr u32 DT_FILTER = 0x7fffffff;
|
||||
|
||||
static constexpr u32 DF_ORIGIN = 0x01;
|
||||
static constexpr u32 DF_SYMBOLIC = 0x02;
|
||||
static constexpr u32 DF_TEXTREL = 0x04;
|
||||
static constexpr u32 DF_BIND_NOW = 0x08;
|
||||
static constexpr u32 DF_STATIC_TLS = 0x10;
|
||||
|
||||
static constexpr u32 DF_1_NOW = 0x00000001;
|
||||
static constexpr u32 DF_1_NODELETE = 0x00000008;
|
||||
static constexpr u32 DF_1_INITFIRST = 0x00000020;
|
||||
static constexpr u32 DF_1_NOOPEN = 0x00000040;
|
||||
static constexpr u32 DF_1_ORIGIN = 0x00000080;
|
||||
static constexpr u32 DF_1_INTERPOSE = 0x00000400;
|
||||
static constexpr u32 DF_1_NODUMP = 0x00001000;
|
||||
static constexpr u32 DF_1_PIE = 0x08000000;
|
||||
|
||||
static constexpr u32 NT_GNU_ABI_TAG = 1;
|
||||
static constexpr u32 NT_GNU_HWCAP = 2;
|
||||
static constexpr u32 NT_GNU_BUILD_ID = 3;
|
||||
static constexpr u32 NT_GNU_GOLD_VERSION = 4;
|
||||
static constexpr u32 NT_GNU_PROPERTY_TYPE_0 = 5;
|
||||
|
||||
static constexpr u32 GNU_PROPERTY_AARCH64_FEATURE_1_AND = 0xc0000000;
|
||||
static constexpr u32 GNU_PROPERTY_X86_FEATURE_1_AND = 0xc0000002;
|
||||
|
||||
static constexpr u32 ELFCOMPRESS_ZLIB = 1;
|
||||
|
||||
static constexpr u32 R_X86_64_NONE = 0;
|
||||
static constexpr u32 R_X86_64_64 = 1;
|
||||
static constexpr u32 R_X86_64_PC32 = 2;
|
||||
static constexpr u32 R_X86_64_GOT32 = 3;
|
||||
static constexpr u32 R_X86_64_PLT32 = 4;
|
||||
static constexpr u32 R_X86_64_COPY = 5;
|
||||
static constexpr u32 R_X86_64_GLOB_DAT = 6;
|
||||
static constexpr u32 R_X86_64_JUMP_SLOT = 7;
|
||||
static constexpr u32 R_X86_64_RELATIVE = 8;
|
||||
static constexpr u32 R_X86_64_GOTPCREL = 9;
|
||||
static constexpr u32 R_X86_64_32 = 10;
|
||||
static constexpr u32 R_X86_64_32S = 11;
|
||||
static constexpr u32 R_X86_64_16 = 12;
|
||||
static constexpr u32 R_X86_64_PC16 = 13;
|
||||
static constexpr u32 R_X86_64_8 = 14;
|
||||
static constexpr u32 R_X86_64_PC8 = 15;
|
||||
static constexpr u32 R_X86_64_DTPMOD64 = 16;
|
||||
static constexpr u32 R_X86_64_DTPOFF64 = 17;
|
||||
static constexpr u32 R_X86_64_TPOFF64 = 18;
|
||||
static constexpr u32 R_X86_64_TLSGD = 19;
|
||||
static constexpr u32 R_X86_64_TLSLD = 20;
|
||||
static constexpr u32 R_X86_64_DTPOFF32 = 21;
|
||||
static constexpr u32 R_X86_64_GOTTPOFF = 22;
|
||||
static constexpr u32 R_X86_64_TPOFF32 = 23;
|
||||
static constexpr u32 R_X86_64_PC64 = 24;
|
||||
static constexpr u32 R_X86_64_GOTOFF64 = 25;
|
||||
static constexpr u32 R_X86_64_GOTPC32 = 26;
|
||||
static constexpr u32 R_X86_64_GOT64 = 27;
|
||||
static constexpr u32 R_X86_64_GOTPCREL64 = 28;
|
||||
static constexpr u32 R_X86_64_GOTPC64 = 29;
|
||||
static constexpr u32 R_X86_64_GOTPLT64 = 30;
|
||||
static constexpr u32 R_X86_64_PLTOFF64 = 31;
|
||||
static constexpr u32 R_X86_64_SIZE32 = 32;
|
||||
static constexpr u32 R_X86_64_SIZE64 = 33;
|
||||
static constexpr u32 R_X86_64_GOTPC32_TLSDESC = 34;
|
||||
static constexpr u32 R_X86_64_TLSDESC_CALL = 35;
|
||||
static constexpr u32 R_X86_64_TLSDESC = 36;
|
||||
static constexpr u32 R_X86_64_IRELATIVE = 37;
|
||||
static constexpr u32 R_X86_64_GOTPCRELX = 41;
|
||||
static constexpr u32 R_X86_64_REX_GOTPCRELX = 42;
|
||||
|
||||
template <>
|
||||
inline std::string rel_to_string<X86_64>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
case R_X86_64_NONE: return "R_X86_64_NONE";
|
||||
case R_X86_64_64: return "R_X86_64_64";
|
||||
case R_X86_64_PC32: return "R_X86_64_PC32";
|
||||
case R_X86_64_GOT32: return "R_X86_64_GOT32";
|
||||
case R_X86_64_PLT32: return "R_X86_64_PLT32";
|
||||
case R_X86_64_COPY: return "R_X86_64_COPY";
|
||||
case R_X86_64_GLOB_DAT: return "R_X86_64_GLOB_DAT";
|
||||
case R_X86_64_JUMP_SLOT: return "R_X86_64_JUMP_SLOT";
|
||||
case R_X86_64_RELATIVE: return "R_X86_64_RELATIVE";
|
||||
case R_X86_64_GOTPCREL: return "R_X86_64_GOTPCREL";
|
||||
case R_X86_64_32: return "R_X86_64_32";
|
||||
case R_X86_64_32S: return "R_X86_64_32S";
|
||||
case R_X86_64_16: return "R_X86_64_16";
|
||||
case R_X86_64_PC16: return "R_X86_64_PC16";
|
||||
case R_X86_64_8: return "R_X86_64_8";
|
||||
case R_X86_64_PC8: return "R_X86_64_PC8";
|
||||
case R_X86_64_DTPMOD64: return "R_X86_64_DTPMOD64";
|
||||
case R_X86_64_DTPOFF64: return "R_X86_64_DTPOFF64";
|
||||
case R_X86_64_TPOFF64: return "R_X86_64_TPOFF64";
|
||||
case R_X86_64_TLSGD: return "R_X86_64_TLSGD";
|
||||
case R_X86_64_TLSLD: return "R_X86_64_TLSLD";
|
||||
case R_X86_64_DTPOFF32: return "R_X86_64_DTPOFF32";
|
||||
case R_X86_64_GOTTPOFF: return "R_X86_64_GOTTPOFF";
|
||||
case R_X86_64_TPOFF32: return "R_X86_64_TPOFF32";
|
||||
case R_X86_64_PC64: return "R_X86_64_PC64";
|
||||
case R_X86_64_GOTOFF64: return "R_X86_64_GOTOFF64";
|
||||
case R_X86_64_GOTPC32: return "R_X86_64_GOTPC32";
|
||||
case R_X86_64_GOT64: return "R_X86_64_GOT64";
|
||||
case R_X86_64_GOTPCREL64: return "R_X86_64_GOTPCREL64";
|
||||
case R_X86_64_GOTPC64: return "R_X86_64_GOTPC64";
|
||||
case R_X86_64_GOTPLT64: return "R_X86_64_GOTPLT64";
|
||||
case R_X86_64_PLTOFF64: return "R_X86_64_PLTOFF64";
|
||||
case R_X86_64_SIZE32: return "R_X86_64_SIZE32";
|
||||
case R_X86_64_SIZE64: return "R_X86_64_SIZE64";
|
||||
case R_X86_64_GOTPC32_TLSDESC: return "R_X86_64_GOTPC32_TLSDESC";
|
||||
case R_X86_64_TLSDESC_CALL: return "R_X86_64_TLSDESC_CALL";
|
||||
case R_X86_64_TLSDESC: return "R_X86_64_TLSDESC";
|
||||
case R_X86_64_IRELATIVE: return "R_X86_64_IRELATIVE";
|
||||
case R_X86_64_GOTPCRELX: return "R_X86_64_GOTPCRELX";
|
||||
case R_X86_64_REX_GOTPCRELX: return "R_X86_64_REX_GOTPCRELX";
|
||||
}
|
||||
return "unknown (" + std::to_string(r_type) + ")";
|
||||
}
|
||||
|
||||
static constexpr u32 R_386_NONE = 0;
|
||||
static constexpr u32 R_386_32 = 1;
|
||||
static constexpr u32 R_386_PC32 = 2;
|
||||
static constexpr u32 R_386_GOT32 = 3;
|
||||
static constexpr u32 R_386_PLT32 = 4;
|
||||
static constexpr u32 R_386_COPY = 5;
|
||||
static constexpr u32 R_386_GLOB_DAT = 6;
|
||||
static constexpr u32 R_386_JUMP_SLOT = 7;
|
||||
static constexpr u32 R_386_RELATIVE = 8;
|
||||
static constexpr u32 R_386_GOTOFF = 9;
|
||||
static constexpr u32 R_386_GOTPC = 10;
|
||||
static constexpr u32 R_386_32PLT = 11;
|
||||
static constexpr u32 R_386_TLS_TPOFF = 14;
|
||||
static constexpr u32 R_386_TLS_IE = 15;
|
||||
static constexpr u32 R_386_TLS_GOTIE = 16;
|
||||
static constexpr u32 R_386_TLS_LE = 17;
|
||||
static constexpr u32 R_386_TLS_GD = 18;
|
||||
static constexpr u32 R_386_TLS_LDM = 19;
|
||||
static constexpr u32 R_386_16 = 20;
|
||||
static constexpr u32 R_386_PC16 = 21;
|
||||
static constexpr u32 R_386_8 = 22;
|
||||
static constexpr u32 R_386_PC8 = 23;
|
||||
static constexpr u32 R_386_TLS_GD_32 = 24;
|
||||
static constexpr u32 R_386_TLS_GD_PUSH = 25;
|
||||
static constexpr u32 R_386_TLS_GD_CALL = 26;
|
||||
static constexpr u32 R_386_TLS_GD_POP = 27;
|
||||
static constexpr u32 R_386_TLS_LDM_32 = 28;
|
||||
static constexpr u32 R_386_TLS_LDM_PUSH = 29;
|
||||
static constexpr u32 R_386_TLS_LDM_CALL = 30;
|
||||
static constexpr u32 R_386_TLS_LDM_POP = 31;
|
||||
static constexpr u32 R_386_TLS_LDO_32 = 32;
|
||||
static constexpr u32 R_386_TLS_IE_32 = 33;
|
||||
static constexpr u32 R_386_TLS_LE_32 = 34;
|
||||
static constexpr u32 R_386_TLS_DTPMOD32 = 35;
|
||||
static constexpr u32 R_386_TLS_DTPOFF32 = 36;
|
||||
static constexpr u32 R_386_TLS_TPOFF32 = 37;
|
||||
static constexpr u32 R_386_SIZE32 = 38;
|
||||
static constexpr u32 R_386_TLS_GOTDESC = 39;
|
||||
static constexpr u32 R_386_TLS_DESC_CALL = 40;
|
||||
static constexpr u32 R_386_TLS_DESC = 41;
|
||||
static constexpr u32 R_386_IRELATIVE = 42;
|
||||
static constexpr u32 R_386_GOT32X = 43;
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<I386>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
case R_386_NONE: return "R_386_NONE";
|
||||
case R_386_32: return "R_386_32";
|
||||
case R_386_PC32: return "R_386_PC32";
|
||||
case R_386_GOT32: return "R_386_GOT32";
|
||||
case R_386_PLT32: return "R_386_PLT32";
|
||||
case R_386_COPY: return "R_386_COPY";
|
||||
case R_386_GLOB_DAT: return "R_386_GLOB_DAT";
|
||||
case R_386_JUMP_SLOT: return "R_386_JUMP_SLOT";
|
||||
case R_386_RELATIVE: return "R_386_RELATIVE";
|
||||
case R_386_GOTOFF: return "R_386_GOTOFF";
|
||||
case R_386_GOTPC: return "R_386_GOTPC";
|
||||
case R_386_32PLT: return "R_386_32PLT";
|
||||
case R_386_TLS_TPOFF: return "R_386_TLS_TPOFF";
|
||||
case R_386_TLS_IE: return "R_386_TLS_IE";
|
||||
case R_386_TLS_GOTIE: return "R_386_TLS_GOTIE";
|
||||
case R_386_TLS_LE: return "R_386_TLS_LE";
|
||||
case R_386_TLS_GD: return "R_386_TLS_GD";
|
||||
case R_386_TLS_LDM: return "R_386_TLS_LDM";
|
||||
case R_386_16: return "R_386_16";
|
||||
case R_386_PC16: return "R_386_PC16";
|
||||
case R_386_8: return "R_386_8";
|
||||
case R_386_PC8: return "R_386_PC8";
|
||||
case R_386_TLS_GD_32: return "R_386_TLS_GD_32";
|
||||
case R_386_TLS_GD_PUSH: return "R_386_TLS_GD_PUSH";
|
||||
case R_386_TLS_GD_CALL: return "R_386_TLS_GD_CALL";
|
||||
case R_386_TLS_GD_POP: return "R_386_TLS_GD_POP";
|
||||
case R_386_TLS_LDM_32: return "R_386_TLS_LDM_32";
|
||||
case R_386_TLS_LDM_PUSH: return "R_386_TLS_LDM_PUSH";
|
||||
case R_386_TLS_LDM_CALL: return "R_386_TLS_LDM_CALL";
|
||||
case R_386_TLS_LDM_POP: return "R_386_TLS_LDM_POP";
|
||||
case R_386_TLS_LDO_32: return "R_386_TLS_LDO_32";
|
||||
case R_386_TLS_IE_32: return "R_386_TLS_IE_32";
|
||||
case R_386_TLS_LE_32: return "R_386_TLS_LE_32";
|
||||
case R_386_TLS_DTPMOD32: return "R_386_TLS_DTPMOD32";
|
||||
case R_386_TLS_DTPOFF32: return "R_386_TLS_DTPOFF32";
|
||||
case R_386_TLS_TPOFF32: return "R_386_TLS_TPOFF32";
|
||||
case R_386_SIZE32: return "R_386_SIZE32";
|
||||
case R_386_TLS_GOTDESC: return "R_386_TLS_GOTDESC";
|
||||
case R_386_TLS_DESC_CALL: return "R_386_TLS_DESC_CALL";
|
||||
case R_386_TLS_DESC: return "R_386_TLS_DESC";
|
||||
case R_386_IRELATIVE: return "R_386_IRELATIVE";
|
||||
case R_386_GOT32X: return "R_386_GOT32X";
|
||||
}
|
||||
return "unknown (" + std::to_string(r_type) + ")";
|
||||
}
|
||||
|
||||
static constexpr u32 DW_EH_PE_absptr = 0;
|
||||
static constexpr u32 DW_EH_PE_omit = 0xff;
|
||||
static constexpr u32 DW_EH_PE_uleb128 = 0x01;
|
||||
static constexpr u32 DW_EH_PE_udata2 = 0x02;
|
||||
static constexpr u32 DW_EH_PE_udata4 = 0x03;
|
||||
static constexpr u32 DW_EH_PE_udata8 = 0x04;
|
||||
static constexpr u32 DW_EH_PE_signed = 0x08;
|
||||
static constexpr u32 DW_EH_PE_sleb128 = 0x09;
|
||||
static constexpr u32 DW_EH_PE_sdata2 = 0x0a;
|
||||
static constexpr u32 DW_EH_PE_sdata4 = 0x0b;
|
||||
static constexpr u32 DW_EH_PE_sdata8 = 0x0c;
|
||||
static constexpr u32 DW_EH_PE_pcrel = 0x10;
|
||||
static constexpr u32 DW_EH_PE_textrel = 0x20;
|
||||
static constexpr u32 DW_EH_PE_datarel = 0x30;
|
||||
static constexpr u32 DW_EH_PE_funcrel = 0x40;
|
||||
static constexpr u32 DW_EH_PE_aligned = 0x50;
|
||||
|
||||
struct Elf64Sym {
|
||||
bool is_defined() const { return !is_undef(); }
|
||||
bool is_undef() const { return st_shndx == SHN_UNDEF; }
|
||||
bool is_abs() const { return st_shndx == SHN_ABS; }
|
||||
bool is_common() const { return st_shndx == SHN_COMMON; }
|
||||
bool is_weak() const { return st_bind == STB_WEAK; }
|
||||
|
||||
bool is_undef_strong() const {
|
||||
return st_shndx == SHN_UNDEF && st_bind != STB_WEAK;
|
||||
}
|
||||
|
||||
bool is_undef_weak() const {
|
||||
return st_shndx == SHN_UNDEF && st_bind == STB_WEAK;
|
||||
}
|
||||
|
||||
u32 st_name;
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u16 st_shndx;
|
||||
u64 st_value;
|
||||
u64 st_size;
|
||||
};
|
||||
|
||||
struct Elf32Sym {
|
||||
bool is_defined() const { return !is_undef(); }
|
||||
bool is_undef() const { return st_shndx == SHN_UNDEF; }
|
||||
bool is_abs() const { return st_shndx == SHN_ABS; }
|
||||
bool is_common() const { return st_shndx == SHN_COMMON; }
|
||||
bool is_weak() const { return st_bind == STB_WEAK; }
|
||||
|
||||
bool is_undef_strong() const {
|
||||
return st_shndx == SHN_UNDEF && st_bind != STB_WEAK;
|
||||
}
|
||||
|
||||
bool is_undef_weak() const {
|
||||
return st_shndx == SHN_UNDEF && st_bind == STB_WEAK;
|
||||
}
|
||||
|
||||
u32 st_name;
|
||||
u32 st_value;
|
||||
u32 st_size;
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u16 st_shndx;
|
||||
};
|
||||
|
||||
struct Elf64Shdr {
|
||||
u32 sh_name;
|
||||
u32 sh_type;
|
||||
u64 sh_flags;
|
||||
u64 sh_addr;
|
||||
u64 sh_offset;
|
||||
u64 sh_size;
|
||||
u32 sh_link;
|
||||
u32 sh_info;
|
||||
u64 sh_addralign;
|
||||
u64 sh_entsize;
|
||||
};
|
||||
|
||||
struct Elf32Shdr {
|
||||
u32 sh_name;
|
||||
u32 sh_type;
|
||||
u32 sh_flags;
|
||||
u32 sh_addr;
|
||||
u32 sh_offset;
|
||||
u32 sh_size;
|
||||
u32 sh_link;
|
||||
u32 sh_info;
|
||||
u32 sh_addralign;
|
||||
u32 sh_entsize;
|
||||
};
|
||||
|
||||
struct Elf64Ehdr {
|
||||
u8 e_ident[16];
|
||||
u16 e_type;
|
||||
u16 e_machine;
|
||||
u32 e_version;
|
||||
u64 e_entry;
|
||||
u64 e_phoff;
|
||||
u64 e_shoff;
|
||||
u32 e_flags;
|
||||
u16 e_ehsize;
|
||||
u16 e_phentsize;
|
||||
u16 e_phnum;
|
||||
u16 e_shentsize;
|
||||
u16 e_shnum;
|
||||
u16 e_shstrndx;
|
||||
};
|
||||
|
||||
struct Elf32Ehdr {
|
||||
u8 e_ident[16];
|
||||
u16 e_type;
|
||||
u16 e_machine;
|
||||
u32 e_version;
|
||||
u32 e_entry;
|
||||
u32 e_phoff;
|
||||
u32 e_shoff;
|
||||
u32 e_flags;
|
||||
u16 e_ehsize;
|
||||
u16 e_phentsize;
|
||||
u16 e_phnum;
|
||||
u16 e_shentsize;
|
||||
u16 e_shnum;
|
||||
u16 e_shstrndx;
|
||||
};
|
||||
|
||||
struct Elf64Phdr {
|
||||
u32 p_type;
|
||||
u32 p_flags;
|
||||
u64 p_offset;
|
||||
u64 p_vaddr;
|
||||
u64 p_paddr;
|
||||
u64 p_filesz;
|
||||
u64 p_memsz;
|
||||
u64 p_align;
|
||||
};
|
||||
|
||||
struct Elf32Phdr {
|
||||
u32 p_type;
|
||||
u32 p_offset;
|
||||
u32 p_vaddr;
|
||||
u32 p_paddr;
|
||||
u32 p_filesz;
|
||||
u32 p_memsz;
|
||||
u32 p_flags;
|
||||
u32 p_align;
|
||||
};
|
||||
|
||||
struct Elf64Rel {
|
||||
u64 r_offset;
|
||||
u32 r_type;
|
||||
u32 r_sym;
|
||||
};
|
||||
|
||||
struct Elf32Rel {
|
||||
u32 r_offset;
|
||||
u32 r_type : 8;
|
||||
u32 r_sym : 24;
|
||||
};
|
||||
|
||||
struct Elf64Rela {
|
||||
u64 r_offset;
|
||||
u32 r_type;
|
||||
u32 r_sym;
|
||||
i64 r_addend;
|
||||
};
|
||||
|
||||
struct Elf32Rela {
|
||||
u32 r_offset;
|
||||
u32 r_type : 8;
|
||||
u32 r_sym : 24;
|
||||
i32 r_addend;
|
||||
};
|
||||
|
||||
struct Elf64Dyn {
|
||||
u64 d_tag;
|
||||
u64 d_val;
|
||||
};
|
||||
|
||||
struct Elf32Dyn {
|
||||
u32 d_tag;
|
||||
u32 d_val;
|
||||
};
|
||||
|
||||
struct Elf64Verneed {
|
||||
u16 vn_version;
|
||||
u16 vn_cnt;
|
||||
u32 vn_file;
|
||||
u32 vn_aux;
|
||||
u32 vn_next;
|
||||
};
|
||||
|
||||
struct Elf64Vernaux {
|
||||
u32 vna_hash;
|
||||
u16 vna_flags;
|
||||
u16 vna_other;
|
||||
u32 vna_name;
|
||||
u32 vna_next;
|
||||
};
|
||||
|
||||
struct Elf64Verdef {
|
||||
u16 vd_version;
|
||||
u16 vd_flags;
|
||||
u16 vd_ndx;
|
||||
u16 vd_cnt;
|
||||
u32 vd_hash;
|
||||
u32 vd_aux;
|
||||
u32 vd_next;
|
||||
};
|
||||
|
||||
struct Elf64Verdaux {
|
||||
u32 vda_name;
|
||||
u32 vda_next;
|
||||
};
|
||||
|
||||
struct Elf64Chdr {
|
||||
u32 ch_type;
|
||||
u32 ch_reserved;
|
||||
u64 ch_size;
|
||||
u64 ch_addralign;
|
||||
};
|
||||
|
||||
struct Elf32Chdr {
|
||||
u32 ch_type;
|
||||
u32 ch_size;
|
||||
u32 ch_addralign;
|
||||
};
|
||||
|
||||
struct Elf64Nhdr {
|
||||
u32 n_namesz;
|
||||
u32 n_descsz;
|
||||
u32 n_type;
|
||||
};
|
||||
|
||||
template <typename E> struct ElfSym;
|
||||
template <typename E> struct ElfShdr;
|
||||
template <typename E> struct ElfEhdr;
|
||||
template <typename E> struct ElfPhdr;
|
||||
template <typename E> struct ElfRel;
|
||||
template <typename E> struct ElfDyn;
|
||||
template <typename E> struct ElfVerneed;
|
||||
template <typename E> struct ElfVernaux;
|
||||
template <typename E> struct ElfVerdef;
|
||||
template <typename E> struct ElfVerdaux;
|
||||
template <typename E> struct ElfChdr;
|
||||
template <typename E> struct ElfNhdr;
|
||||
|
||||
struct X86_64 {
|
||||
typedef u64 WordTy;
|
||||
|
||||
static constexpr u32 R_NONE = R_X86_64_NONE;
|
||||
static constexpr u32 R_COPY = R_X86_64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_X86_64_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_X86_64_JUMP_SLOT;
|
||||
static constexpr u32 R_RELATIVE = R_X86_64_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_X86_64_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_X86_64_DTPOFF64;
|
||||
static constexpr u32 R_TPOFF = R_X86_64_TPOFF64;
|
||||
static constexpr u32 R_DTPMOD = R_X86_64_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_X86_64_TLSDESC;
|
||||
|
||||
static constexpr u32 wordsize = 8;
|
||||
static constexpr u32 e_machine = EM_X86_64;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr bool is_rel = false;
|
||||
static constexpr bool is_le = true;
|
||||
};
|
||||
|
||||
template <> struct ElfSym<X86_64> : public Elf64Sym {};
|
||||
template <> struct ElfShdr<X86_64> : public Elf64Shdr {};
|
||||
template <> struct ElfEhdr<X86_64> : public Elf64Ehdr {};
|
||||
template <> struct ElfPhdr<X86_64> : public Elf64Phdr {};
|
||||
template <> struct ElfRel<X86_64> : public Elf64Rela {};
|
||||
template <> struct ElfDyn<X86_64> : public Elf64Dyn {};
|
||||
template <> struct ElfVerneed<X86_64> : public Elf64Verneed {};
|
||||
template <> struct ElfVernaux<X86_64> : public Elf64Vernaux {};
|
||||
template <> struct ElfVerdef<X86_64> : public Elf64Verdef {};
|
||||
template <> struct ElfVerdaux<X86_64> : public Elf64Verdaux {};
|
||||
template <> struct ElfChdr<X86_64> : public Elf64Chdr {};
|
||||
template <> struct ElfNhdr<X86_64> : public Elf64Nhdr {};
|
||||
|
||||
struct I386 {
|
||||
typedef u32 WordTy;
|
||||
|
||||
static constexpr u32 R_NONE = R_386_NONE;
|
||||
static constexpr u32 R_COPY = R_386_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_386_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_386_JUMP_SLOT;
|
||||
static constexpr u32 R_RELATIVE = R_386_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_386_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_386_TLS_DTPOFF32;
|
||||
static constexpr u32 R_TPOFF = R_386_TLS_TPOFF;
|
||||
static constexpr u32 R_DTPMOD = R_386_TLS_DTPMOD32;
|
||||
static constexpr u32 R_TLSDESC = R_386_TLS_DESC;
|
||||
|
||||
static constexpr u32 wordsize = 4;
|
||||
static constexpr u32 e_machine = EM_386;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr bool is_rel = true;
|
||||
static constexpr bool is_le = true;
|
||||
};
|
||||
|
||||
template <> struct ElfSym<I386> : public Elf32Sym {};
|
||||
template <> struct ElfShdr<I386> : public Elf32Shdr {};
|
||||
template <> struct ElfEhdr<I386> : public Elf32Ehdr {};
|
||||
template <> struct ElfPhdr<I386> : public Elf32Phdr {};
|
||||
template <> struct ElfRel<I386> : public Elf32Rel {};
|
||||
template <> struct ElfDyn<I386> : public Elf32Dyn {};
|
||||
template <> struct ElfVerneed<I386> : public Elf64Verneed {};
|
||||
template <> struct ElfVernaux<I386> : public Elf64Vernaux {};
|
||||
template <> struct ElfVerdef<I386> : public Elf64Verdef {};
|
||||
template <> struct ElfVerdaux<I386> : public Elf64Verdaux {};
|
||||
template <> struct ElfChdr<I386> : public Elf32Chdr {};
|
||||
template <> struct ElfNhdr<I386> : public Elf64Nhdr {};
|
||||
@@ -0,0 +1,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
|
||||
@@ -0,0 +1,759 @@
|
||||
// ARM32 is a bit special from the linker's viewpoint because ARM
|
||||
// processors support two different instruction encodings: Thumb and
|
||||
// ARM (in a narrower sense). Thumb instructions are either 16 bits or
|
||||
// 32 bits, while ARM instructions are all 32 bits. Feature-wise,
|
||||
// thumb is a subset of ARM, so not all ARM instructions are
|
||||
// representable in Thumb.
|
||||
//
|
||||
// ARM processors originally supported only ARM instructions. Thumb
|
||||
// instructions were later added to increase code density.
|
||||
//
|
||||
// ARM processors runs in either ARM mode or Thumb mode. The mode can
|
||||
// be switched using BX (branch and mode exchange)-family instructions.
|
||||
// We need to use that instructions to, for example, call a function
|
||||
// encoded in Thumb from a function encoded in ARM. Sometimes, the
|
||||
// linker even has to emit an interworking thunk code to switch mode.
|
||||
//
|
||||
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
|
||||
// byte boundaries.
|
||||
//
|
||||
// You can distinguish Thumb functions from ARM functions by looking
|
||||
// at the least significant bit (LSB) of its "address". If LSB is 0,
|
||||
// it's ARM; otherwise, Thumb.
|
||||
//
|
||||
// For example, if a symbol `foo` is of type STT_FUNC and has value
|
||||
// 0x2001, `foo` is a function using Thumb instructions whose address
|
||||
// is 0x2000 (not 0x2001, as Thumb instructions are always 2-byte
|
||||
// aligned). Likewise, if a function pointer has value 0x2001, it
|
||||
// refers a Thumb function at 0x2000.
|
||||
//
|
||||
// https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <tbb/parallel_sort.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = ARM32;
|
||||
|
||||
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);
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xfff0f000) | (imm4 << 16) | imm12;
|
||||
}
|
||||
|
||||
static void write_thm_b_imm(u8 *loc, u32 val) {
|
||||
// https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/BL--BLX--immediate-
|
||||
u32 sign = bit(val, 24);
|
||||
u32 I1 = bit(val, 23);
|
||||
u32 I2 = bit(val, 22);
|
||||
u32 J1 = !I1 ^ sign;
|
||||
u32 J2 = !I2 ^ sign;
|
||||
u32 imm10 = bits(val, 21, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
ul16 *buf = (ul16 *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1000'0000'0000) | (sign << 10) | imm10;
|
||||
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
|
||||
static void write_thm_mov_imm(u8 *loc, u32 val) {
|
||||
// https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/MOVT
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
u32 i = bit(val, 11);
|
||||
u32 imm3 = bits(val, 10, 8);
|
||||
u32 imm8 = bits(val, 7, 0);
|
||||
|
||||
ul16 *buf = (ul16 *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1011'1111'0000) | (i << 10) | imm4;
|
||||
buf[1] = (buf[1] & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
|
||||
}
|
||||
|
||||
template <>
|
||||
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 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
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
|
||||
}
|
||||
|
||||
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
|
||||
// .ARM.exidx and .ARM.extab. So this function is empty.
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {}
|
||||
|
||||
// ARM and Thumb branch instructions can jump within ±16 MiB.
|
||||
static bool is_jump_reachable(i64 val) {
|
||||
return sign_extend(val, 24) == val;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
std::span<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_NONE || rel.r_type == R_ARM_V4BX)
|
||||
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 = 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_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_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
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.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(ul32 *)loc = 0x8000'f3af; // NOP.W
|
||||
break;
|
||||
}
|
||||
|
||||
i64 val = S + A - P;
|
||||
if (is_jump_reachable(val)) {
|
||||
if (T) {
|
||||
write_thm_b_imm(loc, val);
|
||||
*(ul16 *)(loc + 2) |= 0x1000; // rewrite to BL
|
||||
} else {
|
||||
write_thm_b_imm(loc, align_to(val, 4));
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite to BLX
|
||||
}
|
||||
} else {
|
||||
write_thm_b_imm(loc, align_to(get_arm_thunk_addr() + A - P, 4));
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite to BLX
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_ARM_BASE_PREL:
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
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;
|
||||
break;
|
||||
case R_ARM_GOT_BREL:
|
||||
*(ul32 *)loc = G + A;
|
||||
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);
|
||||
bool is_blx = ((*(ul32 *)loc & 0xfe00'0000) == 0xfa00'0000);
|
||||
if (!is_bl && !is_blx)
|
||||
Fatal(ctx) << *this << ": R_ARM_CALL refers neither BL nor BLX";
|
||||
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
u64 val = S + A - P;
|
||||
if (is_jump_reachable(val)) {
|
||||
if (T)
|
||||
*(ul32 *)loc = 0xfa00'0000 | (bit(val, 1) << 24) | bits(val, 25, 2);
|
||||
else
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(val, 25, 2);
|
||||
} else {
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(get_arm_thunk_addr() + A - P, 25, 2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_ARM_JUMP24:
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
} else {
|
||||
// Unlike BL and BLX, we can't rewrite B to BX because BX doesn't
|
||||
// takes an immediate; it takes only a register. So if mode switch
|
||||
// is required, we jump to a linker-synthesized thunk which constructs
|
||||
// a branch destination in a register and branches to that address.
|
||||
u64 val = S + A - P;
|
||||
if (!is_jump_reachable(val) || T)
|
||||
val = get_arm_thunk_addr() + A - P;
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_JUMP11: {
|
||||
assert(T);
|
||||
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.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.
|
||||
u64 val = S + A - P;
|
||||
if (!is_jump_reachable(val) || !T)
|
||||
val = get_thumb_thunk_addr() + A - P;
|
||||
write_thm_b_imm(loc, val);
|
||||
}
|
||||
break;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
write_mov_imm(loc, ((S + A) | T) - P);
|
||||
break;
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
write_mov_imm(loc, (S + A) | T);
|
||||
break;
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
write_thm_mov_imm(loc, ((S + A) | T) - P);
|
||||
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);
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
write_mov_imm(loc, (S + A - P) >> 16);
|
||||
break;
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
write_thm_mov_imm(loc, (S + A - P) >> 16);
|
||||
break;
|
||||
case R_ARM_MOVT_ABS:
|
||||
write_mov_imm(loc, (S + A) >> 16);
|
||||
break;
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
write_thm_mov_imm(loc, (S + A) >> 16);
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LE32:
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// A is odd if the corresponding TLS_CALL is Thumb.
|
||||
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 = S - ctx.tp_addr;
|
||||
}
|
||||
break;
|
||||
case R_ARM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// BL <tls_trampoline>
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(get_trampoline_addr(P + 8), 25, 2);
|
||||
} 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;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
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(*this, rel);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_ARM_ABS32:
|
||||
case R_ARM_MOVT_ABS:
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
case R_ARM_TARGET1:
|
||||
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.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.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(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
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:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_TLS_CALL:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
case R_ARM_V4BX:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
// TLS trampoline code. ARM32's TLSDESC is designed so that this
|
||||
// common piece of code is factored out from object files to reduce
|
||||
// output size. Since no one provide, the linker has to synthesize it.
|
||||
static ul32 hdr[] = {
|
||||
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.
|
||||
// It has two entry points: +0 for Thumb and +4 for ARM.
|
||||
const u8 entry[] = {
|
||||
// .thumb
|
||||
0xfc, 0x46, // mov ip, pc
|
||||
0x60, 0x47, // bx ip # jumps to the following `ldr` insn
|
||||
// .arm
|
||||
0x04, 0xc0, 0x9f, 0xe5, // ldr ip, 2f
|
||||
0x0f, 0xc0, 0x8c, 0xe0, // 1: add ip, ip, pc
|
||||
0x1c, 0xff, 0x2f, 0xe1, // bx ip
|
||||
0x00, 0x00, 0x00, 0x00, // 2: .word sym - 1b
|
||||
};
|
||||
|
||||
static_assert(E::thunk_hdr_size == sizeof(hdr));
|
||||
static_assert(E::thunk_size == sizeof(entry));
|
||||
|
||||
memcpy(buf, hdr, sizeof(hdr));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *loc = buf + sizeof(hdr) + i * sizeof(entry);
|
||||
memcpy(loc, entry, sizeof(entry));
|
||||
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = output_section.shdr.sh_addr + offset + sizeof(hdr) + i * sizeof(entry);
|
||||
*(ul32 *)(loc + 16) = S - P - 16;
|
||||
}
|
||||
}
|
||||
|
||||
// ARM executables use an .ARM.exidx section to look up an exception
|
||||
// handling record for the current instruction pointer. The table needs
|
||||
// to be sorted by their addresses.
|
||||
//
|
||||
// Other target uses .eh_frame_hdr instead for the same purpose.
|
||||
// I don't know why only ARM uses the different mechanism, but it's
|
||||
// likely that it's due to some historical reason.
|
||||
//
|
||||
// This function sorts .ARM.exidx records.
|
||||
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
|
||||
// the followings:
|
||||
//
|
||||
// 1. CANTUNWIND indicating that there's no unwinding info for the function,
|
||||
// 2. a compact unwinding record encoded into a 32-bit value, or
|
||||
// 3. a 31-bit relative address which points to a larger record in
|
||||
// the .ARM.extab section.
|
||||
//
|
||||
// CANTUNWIND is value 1. The most significant bit is set in (2) but
|
||||
// not in (3). So we can distinguished them just by looking at a value.
|
||||
const u32 EXIDX_CANTUNWIND = 1;
|
||||
|
||||
struct Entry {
|
||||
ul32 addr;
|
||||
ul32 val;
|
||||
};
|
||||
|
||||
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's addresses are relative to themselves. In order to sort
|
||||
// records by addresses, we first translate them so that the addresses
|
||||
// are relative to the beginning of the section.
|
||||
auto is_relative = [](u32 val) {
|
||||
return val != EXIDX_CANTUNWIND && !(val & 0x8000'0000);
|
||||
};
|
||||
|
||||
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
|
||||
i64 offset = sizeof(Entry) * i;
|
||||
ent[i].addr = sign_extend(ent[i].addr, 30) + offset;
|
||||
if (is_relative(ent[i].val))
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
|
||||
});
|
||||
|
||||
tbb::parallel_sort(ent, ent + num_entries, [](const Entry &a, const Entry &b) {
|
||||
return a.addr < b.addr;
|
||||
});
|
||||
|
||||
// Make addresses relative to themselves.
|
||||
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
|
||||
i64 offset = sizeof(Entry) * i;
|
||||
ent[i].addr = 0x7fff'ffff & (ent[i].addr - offset);
|
||||
if (is_relative(ent[i].val))
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val - offset);
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
@@ -0,0 +1,529 @@
|
||||
// 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 {
|
||||
|
||||
using E = ARM64;
|
||||
|
||||
static void write_adrp(u8 *buf, u64 val) {
|
||||
*(ul32 *)buf |= (bits(val, 13, 12) << 29) | (bits(val, 32, 14) << 5);
|
||||
}
|
||||
|
||||
static void write_adr(u8 *buf, u64 val) {
|
||||
*(ul32 *)buf |= (bits(val, 1, 0) << 29) | (bits(val, 20, 2) << 5);
|
||||
}
|
||||
|
||||
static void write_movn_movz(u8 *buf, i64 val) {
|
||||
*(ul32 *)buf &= 0b0000'0000'0110'0000'0000'0000'0001'1111;
|
||||
|
||||
if (val >= 0)
|
||||
*(ul32 *)buf |= 0xd280'0000 | (bits(val, 15, 0) << 5); // rewrite to movz
|
||||
else
|
||||
*(ul32 *)buf |= 0x9280'0000 | (bits(~val, 15, 0) << 5); // rewrite to movn
|
||||
}
|
||||
|
||||
static u64 page(u64 val) {
|
||||
return val & 0xffff'ffff'ffff'f000;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
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, insn, sizeof(insn));
|
||||
write_adrp(buf + 4, page(gotplt) - page(plt + 4));
|
||||
*(ul32 *)(buf + 8) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(buf + 12) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
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(buf, insn, sizeof(insn));
|
||||
write_adrp(buf, page(gotplt) - page(plt));
|
||||
*(ul32 *)(buf + 4) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(buf + 8) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const ul32 insn[] = {
|
||||
0x9000'0010, // adrp x16, GOT[n]
|
||||
0xf940'0211, // ldr x17, [x16, GOT[n]]
|
||||
0xd61f'0220, // br x17
|
||||
0xd503'201f, // nop
|
||||
};
|
||||
|
||||
u64 got = sym.get_got_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_adrp(buf, page(got) - page(plt));
|
||||
*(ul32 *)(buf + 4) |= bits(got, 11, 3) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_AARCH64_ABS64:
|
||||
*(ul64 *)loc = val;
|
||||
break;
|
||||
case R_AARCH64_PREL32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
case R_AARCH64_PREL64:
|
||||
*(ul64 *)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_AARCH64_ABS64:
|
||||
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;
|
||||
break;
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 1) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 2) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 3) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 4) << 10;
|
||||
break;
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
*(ul32 *)loc |= bits(S + A, 63, 48) << 5;
|
||||
break;
|
||||
case R_AARCH64_ADR_GOT_PAGE: {
|
||||
i64 val = page(G + GOT + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_PG_HI21: {
|
||||
i64 val = page(S + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_LO21: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 20), 1LL << 20);
|
||||
write_adr(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
break;
|
||||
}
|
||||
|
||||
i64 val = S + A - P;
|
||||
if (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;
|
||||
check(val, -(1LL << 20), 1LL << 20);
|
||||
*(ul32 *)loc |= bits(val, 20, 2) << 5;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PREL16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 15), 1LL << 15);
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PREL32: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PREL64:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
*(ul32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15: {
|
||||
i64 val = G + GOT + A - page(GOT);
|
||||
check(val, 0, 1 << 15);
|
||||
*(ul32 *)loc |= bits(val, 14, 3) << 10;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: {
|
||||
i64 val = page(sym.get_gottp_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
write_movn_movz(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
write_movn_movz(loc, val >> 16);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G2: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, -(1LL << 47), 1LL << 47);
|
||||
write_movn_movz(loc, val >> 32);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, 0, 1LL << 24);
|
||||
*(ul32 *)loc |= bits(val, 23, 12) << 10;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21: {
|
||||
i64 val = page(sym.get_tlsgd_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
*(ul32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
} else {
|
||||
// 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);
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
|
||||
} else {
|
||||
// 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;
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// blr x2 -> nop
|
||||
*(ul32 *)loc = 0xd503'201f;
|
||||
}
|
||||
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_AARCH64_ABS64:
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_AARCH64_ABS64:
|
||||
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.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26:
|
||||
if (sym.is_imported)
|
||||
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.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_AARCH64_ADR_PREL_PG_HI21:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21:
|
||||
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 (!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:
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19:
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
case R_AARCH64_PREL16:
|
||||
case R_AARCH64_PREL32:
|
||||
case R_AARCH64_PREL64:
|
||||
case R_AARCH64_TLSLE_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:
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
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 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));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = output_section.shdr.sh_addr + offset + i * E::thunk_size;
|
||||
|
||||
u8 *loc = buf + i * E::thunk_size;
|
||||
memcpy(loc , data, sizeof(data));
|
||||
write_adrp(loc, page(S) - page(P));
|
||||
*(ul32 *)(loc + 4) |= bits(S, 11, 0) << 10;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -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
|
||||
@@ -0,0 +1,582 @@
|
||||
// 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;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0x51, // push %ecx
|
||||
0x8d, 0x8b, 0, 0, 0, 0, // lea GOTPLT+4(%ebx), %ecx
|
||||
0xff, 0x31, // push (%ecx)
|
||||
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
|
||||
};
|
||||
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)
|
||||
0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx);
|
||||
}
|
||||
|
||||
*(ul32 *)(buf + 5) = 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[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = 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_386_32:
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
case R_386_PC32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 relax_got32x(u8 *loc) {
|
||||
// mov imm(%reg1), %reg2 -> lea imm(%reg1), %reg2
|
||||
if (loc[0] == 0x8b)
|
||||
return 0x8d00 | loc[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 = 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;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
break;
|
||||
}
|
||||
case R_386_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_386_32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_386_PC8: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
break;
|
||||
}
|
||||
case R_386_PC16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_386_PC32:
|
||||
case R_386_PLT32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
*(ul32 *)loc = G + A;
|
||||
break;
|
||||
case R_386_GOT32X:
|
||||
if (sym.has_got(ctx)) {
|
||||
*(ul32 *)loc = G + A;
|
||||
} else {
|
||||
u32 insn = relax_got32x(loc - 2);
|
||||
assert(insn);
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
}
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_386_GOTPC:
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_386_TLS_GOTIE:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_386_TLS_LE:
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_386_TLS_IE:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
// Relax GD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32: {
|
||||
static const u8 insn[] = {
|
||||
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.tp_addr - S - A;
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $val, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 6) = ctx.tp_addr - S - A;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
if (ctx.got->has_tlsld(ctx)) {
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
// Relax LD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32: {
|
||||
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
|
||||
};
|
||||
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;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
*(ul32 *)(loc + 5) = ctx.tp_addr - ctx.tls_begin;
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
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.tp_addr;
|
||||
}
|
||||
break;
|
||||
case R_386_TLS_DESC_CALL:
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// call *(%eax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
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 : get_addend(*this, rel);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
break;
|
||||
}
|
||||
case R_386_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_386_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_386_PC8: {
|
||||
i64 val = S + A;
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
break;
|
||||
}
|
||||
case R_386_PC16: {
|
||||
i64 val = S + A;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
}
|
||||
case R_386_PC32:
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_386_GOTPC:
|
||||
*(ul32 *)loc = GOT + A;
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
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];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
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_386_8:
|
||||
case R_386_16:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_32:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_PC8:
|
||||
case R_386_PC16:
|
||||
case R_386_PC32:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOTPC:
|
||||
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.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.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_PC32 &&
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (relax_tlsgd(ctx, sym))
|
||||
i++;
|
||||
else
|
||||
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_PC32 &&
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (relax_tlsld(ctx))
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
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:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_DESC_CALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -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
|
||||
@@ -0,0 +1,955 @@
|
||||
// RISC-V is a clean RISC ISA. It supports PC-relative load/store for
|
||||
// position-independent code. Its 32-bit and 64-bit ISAs are almost
|
||||
// identical. That is, you can think RV32 as a RV64 without 64-bit
|
||||
// operations. In this file, we support both RV64 and RV32.
|
||||
//
|
||||
// RISC-V is essentially little-endian, but the big-endian version is
|
||||
// available as an extension. GCC supports `-mbig-endian` to generate
|
||||
// big-endian code. Even in big-endian mode, machine instructions are
|
||||
// defined to be encoded in little-endian, though. Only the behavior of
|
||||
// load/store instructions are different between LE RISC-V and BE RISC-V.
|
||||
//
|
||||
// From the linker's point of view, the RISC-V's psABI is unique because
|
||||
// sections in input object files can be shrunk while being copied to the
|
||||
// output file. That is contrary to other psABIs in which sections are an
|
||||
// atomic unit of copying. Let me explain it in more details.
|
||||
//
|
||||
// Since RISC-V instructions are 16-bit or 32-bit long, there's no way to
|
||||
// embed a very large immediate into a branch instruction. In fact, JAL
|
||||
// (jump and link) instruction can jump to only within PC ± 1 MiB because
|
||||
// its immediate is only 21 bits long. If the destination is out of its
|
||||
// reach, we need to use two instructions instead; the first instruction
|
||||
// being AUIPC which sets upper 20 bits to a register and the second being
|
||||
// JALR with a 12-bit immediate and the register. Combined, they specify a
|
||||
// 32 bits displacement.
|
||||
//
|
||||
// Other RISC ISAs have the same limitation, and they solved the problem by
|
||||
// letting the linker create so-called "range extension thunks". It works as
|
||||
// follows: the compiler optimistically emits single jump instructions for
|
||||
// function calls. If the linker finds that a branch target is out of reach,
|
||||
// it emits a small piece of machine code near the branch instruction and
|
||||
// redirect the branch to the linker-synthesized code. The code constructs a
|
||||
// full 32-bit address in a register and jump to the destination. That
|
||||
// linker-synthesized code is called "range extension thunks" or just
|
||||
// "thunks".
|
||||
//
|
||||
// The RISC-V psABI is unique that it works the other way around. That is,
|
||||
// for RISC-V, the compiler always emits two instructions (AUIPC + JAL) for
|
||||
// function calls. If the linker finds the destination is reachable with a
|
||||
// single instruction, it replaces the two instructions with the one and
|
||||
// shrink the section size by one instruction length, instead of filling the
|
||||
// gap with a nop.
|
||||
//
|
||||
// With the presence of this relaxation, sections can no longer be
|
||||
// considered as an atomic unit. If we delete 4 bytes from the middle of a
|
||||
// section, all contents after that point needs to be shifted by 4. Symbol
|
||||
// values and relocation offsets have to be adjusted accordingly if they
|
||||
// refer past the deleted bytes.
|
||||
//
|
||||
// In mold, we use `r_deltas` to memorize how many bytes have be adjusted
|
||||
// for relocations. For symbols, we directly mutate their `value` member.
|
||||
//
|
||||
// RISC-V object files tend to have way more relocations than those for
|
||||
// other targets. This is because all branches, including ones that jump
|
||||
// within the same section, are explicitly expressed with relocations.
|
||||
// Here is why we need them: all control-flow statements such as `if` or
|
||||
// `for` are implemented using branch instructions. For other targets, the
|
||||
// compiler doesn't emit relocations for such branches because they know
|
||||
// at compile-time exactly how many bytes has to be skipped. That's not
|
||||
// true to RISC-V because the linker may delete bytes between a branch and
|
||||
// its destination. Therefore, all branches including in-section ones have
|
||||
// to be explicitly expressed with relocations.
|
||||
//
|
||||
// Note that this mechanism only shrink sections and never enlarge, as
|
||||
// the compiler always emits the longest instruction sequence. This
|
||||
// makes the linker implementation a bit simpler because we don't need
|
||||
// to worry about oscillation.
|
||||
//
|
||||
// https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
static u32 itype(u32 val) {
|
||||
return val << 20;
|
||||
}
|
||||
|
||||
static u32 stype(u32 val) {
|
||||
return bits(val, 11, 5) << 25 | bits(val, 4, 0) << 7;
|
||||
}
|
||||
|
||||
static u32 btype(u32 val) {
|
||||
return bit(val, 12) << 31 | bits(val, 10, 5) << 25 |
|
||||
bits(val, 4, 1) << 8 | bit(val, 11) << 7;
|
||||
}
|
||||
|
||||
static u32 utype(u32 val) {
|
||||
// U-type instructions are used in combination with I-type
|
||||
// instructions. U-type insn sets an immediate to the upper 20-bits
|
||||
// of a register. I-type insn sign-extends a 12-bits immediate and
|
||||
// adds it to a register value to construct a complete value. 0x800
|
||||
// is added here to compensate for the sign-extension.
|
||||
return (val + 0x800) & 0xffff'f000;
|
||||
}
|
||||
|
||||
static u32 jtype(u32 val) {
|
||||
return bit(val, 20) << 31 | bits(val, 10, 1) << 21 |
|
||||
bit(val, 11) << 20 | bits(val, 19, 12) << 12;
|
||||
}
|
||||
|
||||
static u32 cbtype(u32 val) {
|
||||
return bit(val, 8) << 12 | bit(val, 4) << 11 | bit(val, 3) << 10 |
|
||||
bit(val, 7) << 6 | bit(val, 6) << 5 | bit(val, 2) << 4 |
|
||||
bit(val, 1) << 3 | bit(val, 5) << 2;
|
||||
}
|
||||
|
||||
static u32 cjtype(u32 val) {
|
||||
return bit(val, 11) << 12 | bit(val, 4) << 11 | bit(val, 9) << 10 |
|
||||
bit(val, 8) << 9 | bit(val, 10) << 8 | bit(val, 6) << 7 |
|
||||
bit(val, 7) << 6 | bit(val, 3) << 5 | bit(val, 2) << 4 |
|
||||
bit(val, 1) << 3 | bit(val, 5) << 2;
|
||||
}
|
||||
|
||||
static void write_itype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'00000'11111'111'11111'1111111;
|
||||
*(ul32 *)loc |= itype(val);
|
||||
}
|
||||
|
||||
static void write_stype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'11111'11111'111'00000'1111111;
|
||||
*(ul32 *)loc |= stype(val);
|
||||
}
|
||||
|
||||
static void write_btype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'11111'11111'111'00000'1111111;
|
||||
*(ul32 *)loc |= btype(val);
|
||||
}
|
||||
|
||||
static void write_utype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'00000'00000'000'11111'1111111;
|
||||
*(ul32 *)loc |= utype(val);
|
||||
}
|
||||
|
||||
static void write_jtype(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc &= 0b000000'00000'00000'000'11111'1111111;
|
||||
*(ul32 *)loc |= jtype(val);
|
||||
}
|
||||
|
||||
static void write_cbtype(u8 *loc, u32 val) {
|
||||
*(ul16 *)loc &= 0b111'000'111'00000'11;
|
||||
*(ul16 *)loc |= cbtype(val);
|
||||
}
|
||||
|
||||
static void write_cjtype(u8 *loc, u32 val) {
|
||||
*(ul16 *)loc &= 0b111'00000000000'11;
|
||||
*(ul16 *)loc |= cjtype(val);
|
||||
}
|
||||
|
||||
// Returns the rd register of an R/I/U/J-type instruction.
|
||||
static u32 get_rd(u32 val) {
|
||||
return bits(val, 11, 7);
|
||||
}
|
||||
|
||||
static void set_rs1(u8 *loc, u32 rs1) {
|
||||
assert(rs1 < 32);
|
||||
*(ul32 *)loc &= 0b111111'11111'00000'111'11111'1111111;
|
||||
*(ul32 *)loc |= rs1 << 15;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn_64[] = {
|
||||
0x0000'0397, // auipc t2, %pcrel_hi(.got.plt)
|
||||
0x41c3'0333, // sub t1, t1, t3 # .plt entry + hdr + 12
|
||||
0x0003'be03, // ld t3, %pcrel_lo(1b)(t2) # _dl_runtime_resolve
|
||||
0xfd43'0313, // addi t1, t1, -44 # .plt entry
|
||||
0x0003'8293, // addi t0, t2, %pcrel_lo(1b) # &.got.plt
|
||||
0x0013'5313, // srli t1, t1, 1 # .plt entry offset
|
||||
0x0082'b283, // ld t0, 8(t0) # link map
|
||||
0x000e'0067, // jr t3
|
||||
};
|
||||
|
||||
static const ul32 insn_32[] = {
|
||||
0x0000'0397, // auipc t2, %pcrel_hi(.got.plt)
|
||||
0x41c3'0333, // sub t1, t1, t3 # .plt entry + hdr + 12
|
||||
0x0003'ae03, // lw t3, %pcrel_lo(1b)(t2) # _dl_runtime_resolve
|
||||
0xfd43'0313, // addi t1, t1, -44 # .plt entry
|
||||
0x0003'8293, // addi t0, t2, %pcrel_lo(1b) # &.got.plt
|
||||
0x0023'5313, // srli t1, t1, 2 # .plt entry offset
|
||||
0x0042'a283, // lw t0, 4(t0) # link map
|
||||
0x000e'0067, // jr t3
|
||||
};
|
||||
|
||||
if constexpr (E::is_64)
|
||||
memcpy(buf, insn_64, sizeof(insn_64));
|
||||
else
|
||||
memcpy(buf, insn_32, sizeof(insn_32));
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
write_utype(buf, gotplt - plt);
|
||||
write_itype(buf + 8, gotplt - plt);
|
||||
write_itype(buf + 16, gotplt - plt);
|
||||
}
|
||||
|
||||
static const ul32 plt_entry_64[] = {
|
||||
0x0000'0e17, // auipc t3, %pcrel_hi([email protected])
|
||||
0x000e'3e03, // ld t3, %pcrel_lo(1b)(t3)
|
||||
0x000e'0367, // jalr t1, t3
|
||||
0x0000'0013, // nop
|
||||
};
|
||||
|
||||
static const ul32 plt_entry_32[] = {
|
||||
0x0000'0e17, // auipc t3, %pcrel_hi([email protected])
|
||||
0x000e'2e03, // lw t3, %pcrel_lo(1b)(t3)
|
||||
0x000e'0367, // jalr t1, t3
|
||||
0x0000'0013, // nop
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if constexpr (E::is_64)
|
||||
memcpy(buf, plt_entry_64, sizeof(plt_entry_64));
|
||||
else
|
||||
memcpy(buf, plt_entry_32, sizeof(plt_entry_32));
|
||||
|
||||
u64 gotplt = sym.get_gotplt_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
write_utype(buf, gotplt - plt);
|
||||
write_itype(buf + 4, gotplt - plt);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if constexpr (E::is_64)
|
||||
memcpy(buf, plt_entry_64, sizeof(plt_entry_64));
|
||||
else
|
||||
memcpy(buf, plt_entry_32, sizeof(plt_entry_32));
|
||||
|
||||
u64 got = sym.get_got_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
write_utype(buf, got - plt);
|
||||
write_itype(buf + 4, got - plt);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
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_RISCV_ADD32:
|
||||
*(U32<E> *)loc += val;
|
||||
break;
|
||||
case R_RISCV_SUB8:
|
||||
*loc -= val;
|
||||
break;
|
||||
case R_RISCV_SUB16:
|
||||
*(U16<E> *)loc -= val;
|
||||
break;
|
||||
case R_RISCV_SUB32:
|
||||
*(U32<E> *)loc -= val;
|
||||
break;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - val) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | (val & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET8:
|
||||
*loc = val;
|
||||
break;
|
||||
case R_RISCV_SET16:
|
||||
*(U16<E> *)loc = val;
|
||||
break;
|
||||
case R_RISCV_SET32:
|
||||
*(U32<E> *)loc = val;
|
||||
break;
|
||||
case R_RISCV_32_PCREL:
|
||||
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
auto get_r_delta = [&](i64 idx) {
|
||||
return extra.r_deltas.empty() ? 0 : extra.r_deltas[idx];
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || rel.r_type == R_RISCV_RELAX)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
i64 r_offset = rel.r_offset - get_r_delta(i);
|
||||
i64 removed_bytes = get_r_delta(i + 1) - get_r_delta(i);
|
||||
u8 *loc = base + 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() + 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_RISCV_32:
|
||||
if constexpr (E::is_64)
|
||||
*(U32<E> *)loc = S + A;
|
||||
else
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
assert(E::is_64);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_RISCV_BRANCH: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 12), 1 << 12);
|
||||
write_btype(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_JAL: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 20), 1 << 20);
|
||||
write_jtype(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT: {
|
||||
u32 rd = get_rd(*(ul32 *)(contents.data() + rel.r_offset + 4));
|
||||
|
||||
if (removed_bytes == 4) {
|
||||
// auipc + jalr -> jal
|
||||
*(ul32 *)loc = (rd << 7) | 0b1101111;
|
||||
write_jtype(loc, S + A - P);
|
||||
} else if (removed_bytes == 6 && rd == 0) {
|
||||
// auipc + jalr -> c.j
|
||||
*(ul16 *)loc = 0b101'00000000000'01;
|
||||
write_cjtype(loc, S + A - P);
|
||||
} else if (removed_bytes == 6 && rd == 1) {
|
||||
// auipc + jalr -> c.jal
|
||||
assert(!E::is_64);
|
||||
*(ul16 *)loc = 0b001'00000000000'01;
|
||||
write_cjtype(loc, S + A - P);
|
||||
} else {
|
||||
assert(removed_bytes == 0);
|
||||
u64 val = sym.esym().is_undef_weak() ? 0 : S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
write_utype(loc, val);
|
||||
write_itype(loc + 4, val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_GOT_HI20:
|
||||
*(ul32 *)loc = G + GOT + A - P;
|
||||
break;
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_RISCV_TLS_GD_HI20:
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_RISCV_PCREL_HI20:
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// Calling an undefined weak symbol does not make sense.
|
||||
// We make such call into an infinite loop. This should
|
||||
// help debugging of a faulty program.
|
||||
*(ul32 *)loc = 0;
|
||||
} else {
|
||||
*(ul32 *)loc = S + A - P;
|
||||
}
|
||||
break;
|
||||
case R_RISCV_HI20: {
|
||||
i64 val = S + A;
|
||||
if (removed_bytes == 0) {
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
write_utype(loc, val);
|
||||
} else {
|
||||
assert(removed_bytes == 4);
|
||||
assert(sign_extend(val, 11) == val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_LO12_I:
|
||||
case R_RISCV_LO12_S: {
|
||||
i64 val = S + A;
|
||||
if (rel.r_type == R_RISCV_LO12_I)
|
||||
write_itype(loc, val);
|
||||
else
|
||||
write_stype(loc, val);
|
||||
|
||||
// Rewrite `lw t1, 0(t0)` with `lw t1, 0(x0)` if the address is
|
||||
// accessible relative to the zero register. If the upper 20 bits
|
||||
// are all zero, the corresponding LUI might have been removed.
|
||||
if (sign_extend(val, 11) == val)
|
||||
set_rs1(loc, 0);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_TPREL_HI20:
|
||||
assert(removed_bytes == 0 || removed_bytes == 4);
|
||||
if (removed_bytes == 0)
|
||||
write_utype(loc, S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_RISCV_TPREL_ADD:
|
||||
break;
|
||||
case R_RISCV_TPREL_LO12_I:
|
||||
case R_RISCV_TPREL_LO12_S: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
if (rel.r_type == R_RISCV_TPREL_LO12_I)
|
||||
write_itype(loc, val);
|
||||
else
|
||||
write_stype(loc, val);
|
||||
|
||||
// Rewrite `lw t1, 0(t0)` with `lw t1, 0(tp)` if the address is
|
||||
// directly accessible using tp. tp is x4.
|
||||
if (sign_extend(val, 11) == val)
|
||||
set_rs1(loc, 4);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_ADD8:
|
||||
loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD16:
|
||||
*(U16<E> *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD32:
|
||||
*(U32<E> *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD64:
|
||||
*(U64<E> *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_SUB8:
|
||||
loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB16:
|
||||
*(U16<E> *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB32:
|
||||
*(U32<E> *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB64:
|
||||
*(U64<E> *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_ALIGN: {
|
||||
// A R_RISCV_ALIGN is followed by a NOP sequence. We need to remove
|
||||
// zero or more bytes so that the instruction after R_RISCV_ALIGN is
|
||||
// aligned to a given alignment boundary.
|
||||
//
|
||||
// We need to guarantee that the NOP sequence is valid after byte
|
||||
// removal (e.g. we can't remove the first 2 bytes of a 4-byte NOP).
|
||||
// For the sake of simplicity, we always rewrite the entire NOP sequence.
|
||||
i64 padding_bytes = rel.r_addend - removed_bytes;
|
||||
assert((padding_bytes & 1) == 0);
|
||||
|
||||
i64 i = 0;
|
||||
for (; i <= padding_bytes - 4; i += 4)
|
||||
*(ul32 *)(loc + i) = 0x0000'0013; // nop
|
||||
if (i < padding_bytes)
|
||||
*(ul16 *)(loc + i) = 0x0001; // c.nop
|
||||
break;
|
||||
}
|
||||
case R_RISCV_RVC_BRANCH: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 8), 1 << 8);
|
||||
write_cbtype(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_RVC_JUMP: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 11), 1 << 11);
|
||||
write_cjtype(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | ((S + A) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET8:
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET16:
|
||||
*(U16<E> *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET32:
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_32_PCREL:
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
// These relocations are handled in the next loop.
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle PC-relative LO12 relocations. In the above loop, pcrel HI20
|
||||
// relocations overwrote instructions with full 32-bit values to allow
|
||||
// their corresponding pcrel LO12 relocations to read their values.
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
switch (rels[i].r_type) {
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
case R_RISCV_PCREL_LO12_S: {
|
||||
Symbol<E> &sym = *file.symbols[rels[i].r_sym];
|
||||
assert(sym.get_input_section() == this);
|
||||
|
||||
u8 *loc = base + rels[i].r_offset - get_r_delta(i);
|
||||
u32 val = *(ul32 *)(base + sym.value);
|
||||
|
||||
if (rels[i].r_type == R_RISCV_PCREL_LO12_I)
|
||||
write_itype(loc, val);
|
||||
else
|
||||
write_stype(loc, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the original instructions pcrel HI20 relocations overwrote.
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
switch (rels[i].r_type) {
|
||||
case R_RISCV_GOT_HI20:
|
||||
case R_RISCV_PCREL_HI20:
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
case R_RISCV_TLS_GD_HI20: {
|
||||
u8 *loc = base + rels[i].r_offset - get_r_delta(i);
|
||||
u32 val = *(ul32 *)loc;
|
||||
memcpy(loc, contents.data() + rels[i].r_offset, 4);
|
||||
write_utype(loc, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
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_RISCV_32:
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(U64<E> *)loc = *val;
|
||||
else
|
||||
*(U64<E> *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_ADD8:
|
||||
*loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD16:
|
||||
*(U16<E> *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD32:
|
||||
*(U32<E> *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD64:
|
||||
*(U64<E> *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_SUB8:
|
||||
*loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB16:
|
||||
*(U16<E> *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB32:
|
||||
*(U32<E> *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB64:
|
||||
*(U64<E> *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET6:
|
||||
*loc = (*loc & 0b1100'0000) | ((S + A) & 0b0011'1111);
|
||||
break;
|
||||
case R_RISCV_SET8:
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET16:
|
||||
*(U16<E> *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET32:
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
|
||||
// If a section is not relaxed, we can copy it as a one big chunk.
|
||||
if (extra.r_deltas.empty()) {
|
||||
uncompress_to(ctx, buf);
|
||||
return;
|
||||
}
|
||||
|
||||
// Memory-allocated sections may be relaxed, so copy each segment
|
||||
// individually.
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
i64 pos = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
i64 delta = extra.r_deltas[i + 1] - extra.r_deltas[i];
|
||||
if (delta == 0)
|
||||
continue;
|
||||
assert(delta > 0);
|
||||
|
||||
const ElfRel<E> &r = rels[i];
|
||||
memcpy(buf, contents.data() + pos, r.r_offset - pos);
|
||||
buf += r.r_offset - pos;
|
||||
pos = r.r_offset + delta;
|
||||
}
|
||||
|
||||
memcpy(buf, contents.data() + pos, contents.size() - pos);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
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_RISCV_32:
|
||||
if constexpr (E::is_64)
|
||||
scan_absrel(ctx, sym, rel);
|
||||
else
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_RISCV_HI20:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
if constexpr (!E::is_64)
|
||||
Fatal(ctx) << *this << ": R_RISCV_64 cannot be used on RV32";
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_RISCV_GOT_HI20:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
ctx.has_gottp_rel.store(true, std::memory_order_relaxed);
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_RISCV_TLS_GD_HI20:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_RISCV_32_PCREL:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_RISCV_BRANCH:
|
||||
case R_RISCV_JAL:
|
||||
case R_RISCV_PCREL_HI20:
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
case R_RISCV_LO12_I:
|
||||
case R_RISCV_LO12_S:
|
||||
case R_RISCV_TPREL_HI20:
|
||||
case R_RISCV_TPREL_LO12_I:
|
||||
case R_RISCV_TPREL_LO12_S:
|
||||
case R_RISCV_TPREL_ADD:
|
||||
case R_RISCV_ADD8:
|
||||
case R_RISCV_ADD16:
|
||||
case R_RISCV_ADD32:
|
||||
case R_RISCV_ADD64:
|
||||
case R_RISCV_SUB8:
|
||||
case R_RISCV_SUB16:
|
||||
case R_RISCV_SUB32:
|
||||
case R_RISCV_SUB64:
|
||||
case R_RISCV_ALIGN:
|
||||
case R_RISCV_RVC_BRANCH:
|
||||
case R_RISCV_RVC_JUMP:
|
||||
case R_RISCV_RELAX:
|
||||
case R_RISCV_SUB6:
|
||||
case R_RISCV_SET6:
|
||||
case R_RISCV_SET8:
|
||||
case R_RISCV_SET16:
|
||||
case R_RISCV_SET32:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool is_resizable(Context<E> &ctx, InputSection<E> *isec) {
|
||||
return isec && isec->is_alive && (isec->shdr().sh_flags & SHF_ALLOC) &&
|
||||
(isec->shdr().sh_flags & SHF_EXECINSTR);
|
||||
}
|
||||
|
||||
// Returns the distance between a relocated place and a symbol.
|
||||
template <typename E>
|
||||
static i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
|
||||
InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
// We handle absolute symbols as if they were infinitely far away
|
||||
// because `shrink_section` may increase a distance between a branch
|
||||
// instruction and an absolute symbol. Branching to an absolute
|
||||
// location is extremely rare in real code, though.
|
||||
if (sym.is_absolute())
|
||||
return INT32_MAX;
|
||||
|
||||
// Likewise, relocations against weak undefined symbols won't be relaxed.
|
||||
if (sym.esym().is_undef_weak())
|
||||
return INT32_MAX;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol.
|
||||
i64 S = sym.get_addr(ctx);
|
||||
i64 A = rel.r_addend;
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
return S + A - P;
|
||||
}
|
||||
|
||||
// Scan relocations to shrink sections.
|
||||
template <typename E>
|
||||
static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc) {
|
||||
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
isec.extra.r_deltas.resize(rels.size() + 1);
|
||||
|
||||
i64 delta = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &r = rels[i];
|
||||
Symbol<E> &sym = *isec.file.symbols[r.r_sym];
|
||||
isec.extra.r_deltas[i] = delta;
|
||||
|
||||
// Handling R_RISCV_ALIGN is mandatory.
|
||||
//
|
||||
// R_RISCV_ALIGN refers NOP instructions. We need to eliminate some
|
||||
// or all of the instructions so that the instruction that immediately
|
||||
// follows the NOPs is aligned to a specified alignment boundary.
|
||||
if (r.r_type == R_RISCV_ALIGN) {
|
||||
// The total bytes of NOPs is stored to r_addend, so the next
|
||||
// instruction is r_addend away.
|
||||
u64 loc = isec.get_addr() + r.r_offset - delta;
|
||||
u64 next_loc = loc + r.r_addend;
|
||||
u64 alignment = bit_ceil(r.r_addend + 1);
|
||||
assert(alignment <= (1 << isec.p2align));
|
||||
delta += next_loc - align_to(loc, alignment);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handling other relocations is optional.
|
||||
if (!ctx.arg.relax || i == rels.size() - 1 ||
|
||||
rels[i + 1].r_type != R_RISCV_RELAX)
|
||||
continue;
|
||||
|
||||
// Linker-synthesized symbols haven't been assigned their final
|
||||
// values when we are shrinking sections because actual values can
|
||||
// be computed only after we fix the file layout. Therefore, we
|
||||
// assume that relocations against such symbols are always
|
||||
// non-relaxable.
|
||||
if (sym.file == ctx.internal_obj)
|
||||
continue;
|
||||
|
||||
switch (r.r_type) {
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT: {
|
||||
// These relocations refer an AUIPC + JALR instruction pair to
|
||||
// allow to jump to anywhere in PC ± 2 GiB. If the jump target is
|
||||
// close enough to PC, we can use C.J, C.JAL or JAL instead.
|
||||
i64 dist = compute_distance(ctx, sym, isec, r);
|
||||
if (dist & 1)
|
||||
break;
|
||||
|
||||
std::string_view contents = isec.contents;
|
||||
i64 rd = get_rd(*(ul32 *)(contents.data() + r.r_offset + 4));
|
||||
|
||||
if (rd == 0 && sign_extend(dist, 11) == dist && use_rvc) {
|
||||
// If rd is x0 and the jump target is within ±2 KiB, we can use
|
||||
// C.J, saving 6 bytes.
|
||||
delta += 6;
|
||||
} else if (rd == 1 && sign_extend(dist, 11) == dist && use_rvc && !E::is_64) {
|
||||
// If rd is x1 and the jump target is within ±2 KiB, we can use
|
||||
// C.JAL. This is RV32 only because C.JAL is RV32-only instruction.
|
||||
delta += 6;
|
||||
} else if (sign_extend(dist, 20) == dist) {
|
||||
// If the jump target is within ±1 MiB, we can use JAL.
|
||||
delta += 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_HI20: {
|
||||
// If the upper 20 bits are all zero, we can remove LUI.
|
||||
// The corresponding instructions referred by LO12_I/LO12_S
|
||||
// relocations will use the zero register instead.
|
||||
i64 val = sym.get_addr(ctx);
|
||||
if (sign_extend(val, 11) == val)
|
||||
delta += 4;
|
||||
break;
|
||||
}
|
||||
case R_RISCV_TPREL_HI20:
|
||||
case R_RISCV_TPREL_ADD: {
|
||||
// These relocations are used to materialize the upper 20 bits of
|
||||
// an address relative to the thread pointer as follows:
|
||||
//
|
||||
// lui a5,%tprel_hi(foo) # R_RISCV_TPREL_HI20 (symbol)
|
||||
// add a5,a5,tp,%tprel_add(foo) # R_RISCV_TPREL_ADD (symbol)
|
||||
//
|
||||
// Then thread-local variable `foo` is accessed with a 12-bit offset
|
||||
// like this:
|
||||
//
|
||||
// sw t0,%tprel_lo(foo)(a5) # R_RISCV_TPREL_LO12_S (symbol)
|
||||
//
|
||||
// However, if the offset is ±2 KiB, we don't need to materialize
|
||||
// the upper 20 bits in a register. We can instead access the
|
||||
// thread-local variable directly with TP like this:
|
||||
//
|
||||
// sw t0,%tprel_lo(foo)(tp)
|
||||
//
|
||||
// Here, we remove `lui` and `add` if the offset is within ±2 KiB.
|
||||
i64 val = sym.get_addr(ctx) + r.r_addend - ctx.tp_addr;
|
||||
if (sign_extend(val, 11) == val)
|
||||
delta += 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isec.extra.r_deltas[rels.size()] = delta;
|
||||
isec.sh_size -= delta;
|
||||
}
|
||||
|
||||
// Shrink sections by interpreting relocations.
|
||||
//
|
||||
// This operation seems to be optional, because by default longest
|
||||
// instructions are being used. However, calling this function is actually
|
||||
// mandatory because of R_RISCV_ALIGN. R_RISCV_ALIGN is a directive to the
|
||||
// linker to align the location referred to by the relocation to a
|
||||
// specified byte boundary. We at least have to interpret them to satisfy
|
||||
// the alignment constraints.
|
||||
template <typename E>
|
||||
i64 riscv_resize_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "riscv_resize_sections");
|
||||
|
||||
// True if we can use the 2-byte instructions. This is usually true on
|
||||
// Unix because RV64GC is generally considered the baseline hardware.
|
||||
bool use_rvc = get_eflags(ctx) & EF_RISCV_RVC;
|
||||
|
||||
// Find all the relocations that can be relaxed.
|
||||
// This step should only shrink sections.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (is_resizable(ctx, isec.get()))
|
||||
shrink_section(ctx, *isec, use_rvc);
|
||||
});
|
||||
|
||||
// Fix symbol values.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file != file)
|
||||
continue;
|
||||
|
||||
InputSection<E> *isec = sym->get_input_section();
|
||||
if (!isec || isec->extra.r_deltas.empty())
|
||||
continue;
|
||||
|
||||
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
auto it = std::lower_bound(rels.begin(), rels.end(), sym->value,
|
||||
[&](const ElfRel<E> &r, u64 val) {
|
||||
return r.r_offset < val;
|
||||
});
|
||||
|
||||
sym->value -= isec->extra.r_deltas[it - rels.begin()];
|
||||
}
|
||||
});
|
||||
|
||||
// Re-compute section offset again to finalize them.
|
||||
compute_section_sizes(ctx);
|
||||
return set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void write_plt_header(Context<E> &, u8 *); \
|
||||
template void write_plt_entry(Context<E> &, u8 *, Symbol<E> &); \
|
||||
template void write_pltgot_entry(Context<E> &, u8 *, Symbol<E> &); \
|
||||
template void \
|
||||
EhFrameSection<E>::apply_reloc(Context<E> &, const ElfRel<E> &, u64, u64); \
|
||||
template void InputSection<E>::apply_reloc_alloc(Context<E> &, u8 *); \
|
||||
template void InputSection<E>::apply_reloc_nonalloc(Context<E> &, u8 *); \
|
||||
template void InputSection<E>::copy_contents_riscv(Context<E> &, u8 *); \
|
||||
template void InputSection<E>::scan_relocations(Context<E> &); \
|
||||
template i64 riscv_resize_sections(Context<E> &);
|
||||
|
||||
INSTANTIATE(RV64LE);
|
||||
INSTANTIATE(RV64BE);
|
||||
INSTANTIATE(RV32LE);
|
||||
INSTANTIATE(RV32BE);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,752 @@
|
||||
// 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 {
|
||||
|
||||
using E = X86_64;
|
||||
|
||||
// This is a security-enhanced version of the regular PLT. The PLT
|
||||
// header and each PLT entry starts with endbr64 for the Intel's
|
||||
// control-flow enforcement security mechanism.
|
||||
//
|
||||
// Note that our IBT-enabled PLT instruction sequence is different
|
||||
// from the one used in GNU ld. GNU's IBTPLT implementation uses two
|
||||
// separate sections (.plt and .plt.sec) in which one PLT entry takes
|
||||
// 32 bytes in total. Our IBTPLT consists of just .plt and each entry
|
||||
// is 16 bytes long.
|
||||
//
|
||||
// Our PLT entry clobbers %r11, but that's fine because the resolver
|
||||
// function (_dl_runtime_resolve) clobbers %r11 anyway.
|
||||
template <>
|
||||
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, 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;
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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 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)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 10;
|
||||
}
|
||||
|
||||
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_X86_64_32:
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
*(ul64 *)loc = val;
|
||||
break;
|
||||
case R_X86_64_PC32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
case R_X86_64_PC64:
|
||||
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 relax_gotpcrelx(u8 *loc) {
|
||||
switch ((loc[0] << 8) | loc[1]) {
|
||||
case 0xff15: return 0x90e8; // call *0(%rip) -> call 0
|
||||
case 0xff25: return 0x90e9; // jmp *0(%rip) -> jmp 0
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_rex_gotpcrelx(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488b05: return 0x488d05; // mov 0(%rip), %rax -> lea 0(%rip), %rax
|
||||
case 0x488b0d: return 0x488d0d; // mov 0(%rip), %rcx -> lea 0(%rip), %rcx
|
||||
case 0x488b15: return 0x488d15; // mov 0(%rip), %rdx -> lea 0(%rip), %rdx
|
||||
case 0x488b1d: return 0x488d1d; // mov 0(%rip), %rbx -> lea 0(%rip), %rbx
|
||||
case 0x488b25: return 0x488d25; // mov 0(%rip), %rsp -> lea 0(%rip), %rsp
|
||||
case 0x488b2d: return 0x488d2d; // mov 0(%rip), %rbp -> lea 0(%rip), %rbp
|
||||
case 0x488b35: return 0x488d35; // mov 0(%rip), %rsi -> lea 0(%rip), %rsi
|
||||
case 0x488b3d: return 0x488d3d; // mov 0(%rip), %rdi -> lea 0(%rip), %rdi
|
||||
case 0x4c8b05: return 0x4c8d05; // mov 0(%rip), %r8 -> lea 0(%rip), %r8
|
||||
case 0x4c8b0d: return 0x4c8d0d; // mov 0(%rip), %r9 -> lea 0(%rip), %r9
|
||||
case 0x4c8b15: return 0x4c8d15; // mov 0(%rip), %r10 -> lea 0(%rip), %r10
|
||||
case 0x4c8b1d: return 0x4c8d1d; // mov 0(%rip), %r11 -> lea 0(%rip), %r11
|
||||
case 0x4c8b25: return 0x4c8d25; // mov 0(%rip), %r12 -> lea 0(%rip), %r12
|
||||
case 0x4c8b2d: return 0x4c8d2d; // mov 0(%rip), %r13 -> lea 0(%rip), %r13
|
||||
case 0x4c8b35: return 0x4c8d35; // mov 0(%rip), %r14 -> lea 0(%rip), %r14
|
||||
case 0x4c8b3d: return 0x4c8d3d; // mov 0(%rip), %r15 -> lea 0(%rip), %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_gottpoff(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488b05: return 0x48c7c0; // mov 0(%rip), %rax -> mov $0, %rax
|
||||
case 0x488b0d: return 0x48c7c1; // mov 0(%rip), %rcx -> mov $0, %rcx
|
||||
case 0x488b15: return 0x48c7c2; // mov 0(%rip), %rdx -> mov $0, %rdx
|
||||
case 0x488b1d: return 0x48c7c3; // mov 0(%rip), %rbx -> mov $0, %rbx
|
||||
case 0x488b25: return 0x48c7c4; // mov 0(%rip), %rsp -> mov $0, %rsp
|
||||
case 0x488b2d: return 0x48c7c5; // mov 0(%rip), %rbp -> mov $0, %rbp
|
||||
case 0x488b35: return 0x48c7c6; // mov 0(%rip), %rsi -> mov $0, %rsi
|
||||
case 0x488b3d: return 0x48c7c7; // mov 0(%rip), %rdi -> mov $0, %rdi
|
||||
case 0x4c8b05: return 0x49c7c0; // mov 0(%rip), %r8 -> mov $0, %r8
|
||||
case 0x4c8b0d: return 0x49c7c1; // mov 0(%rip), %r9 -> mov $0, %r9
|
||||
case 0x4c8b15: return 0x49c7c2; // mov 0(%rip), %r10 -> mov $0, %r10
|
||||
case 0x4c8b1d: return 0x49c7c3; // mov 0(%rip), %r11 -> mov $0, %r11
|
||||
case 0x4c8b25: return 0x49c7c4; // mov 0(%rip), %r12 -> mov $0, %r12
|
||||
case 0x4c8b2d: return 0x49c7c5; // mov 0(%rip), %r13 -> mov $0, %r13
|
||||
case 0x4c8b35: return 0x49c7c6; // mov 0(%rip), %r14 -> mov $0, %r14
|
||||
case 0x4c8b3d: return 0x49c7c7; // mov 0(%rip), %r15 -> mov $0, %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_gotpc32_tlsdesc(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488d05: return 0x48c7c0; // lea 0(%rip), %rax -> mov $0, %rax
|
||||
case 0x488d0d: return 0x48c7c1; // lea 0(%rip), %rcx -> mov $0, %rcx
|
||||
case 0x488d15: return 0x48c7c2; // lea 0(%rip), %rdx -> mov $0, %rdx
|
||||
case 0x488d1d: return 0x48c7c3; // lea 0(%rip), %rbx -> mov $0, %rbx
|
||||
case 0x488d25: return 0x48c7c4; // lea 0(%rip), %rsp -> mov $0, %rsp
|
||||
case 0x488d2d: return 0x48c7c5; // lea 0(%rip), %rbp -> mov $0, %rbp
|
||||
case 0x488d35: return 0x48c7c6; // lea 0(%rip), %rsi -> mov $0, %rsi
|
||||
case 0x488d3d: return 0x48c7c7; // lea 0(%rip), %rdi -> mov $0, %rdi
|
||||
case 0x4c8d05: return 0x49c7c0; // lea 0(%rip), %r8 -> mov $0, %r8
|
||||
case 0x4c8d0d: return 0x49c7c1; // lea 0(%rip), %r9 -> mov $0, %r9
|
||||
case 0x4c8d15: return 0x49c7c2; // lea 0(%rip), %r10 -> mov $0, %r10
|
||||
case 0x4c8d1d: return 0x49c7c3; // lea 0(%rip), %r11 -> mov $0, %r11
|
||||
case 0x4c8d25: return 0x49c7c4; // lea 0(%rip), %r12 -> mov $0, %r12
|
||||
case 0x4c8d2d: return 0x49c7c5; // lea 0(%rip), %r13 -> mov $0, %r13
|
||||
case 0x4c8d35: return 0x49c7c6; // lea 0(%rip), %r14 -> mov $0, %r14
|
||||
case 0x4c8d3d: return 0x49c7c7; // lea 0(%rip), %r15 -> mov $0, %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Apply relocations to SHF_ALLOC sections (i.e. sections that are
|
||||
// mapped to memory at runtime) based on the result of
|
||||
// scan_relocations().
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
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 write32 = [&](u64 val) {
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32s = [&](u64 val) {
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr;
|
||||
u64 GOTPLT = ctx.gotplt->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
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);
|
||||
break;
|
||||
case R_X86_64_32S:
|
||||
write32s(S + A);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_X86_64_PC8: {
|
||||
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:
|
||||
case R_X86_64_PLT32:
|
||||
write32s(S + A - P);
|
||||
break;
|
||||
case R_X86_64_PC64:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_X86_64_GOT32:
|
||||
write32s(G + A);
|
||||
break;
|
||||
case R_X86_64_GOT64:
|
||||
*(ul64 *)loc = G + A;
|
||||
break;
|
||||
case R_X86_64_GOTOFF64:
|
||||
case R_X86_64_PLTOFF64:
|
||||
*(ul64 *)loc = S + A - GOTPLT;
|
||||
break;
|
||||
case R_X86_64_GOTPC32:
|
||||
write32s(GOTPLT + A - P);
|
||||
break;
|
||||
case R_X86_64_GOTPC64:
|
||||
*(ul64 *)loc = GOTPLT + A - P;
|
||||
break;
|
||||
case R_X86_64_GOTPCREL:
|
||||
write32s(G + GOTPLT + A - P);
|
||||
break;
|
||||
case R_X86_64_GOTPCREL64:
|
||||
*(ul64 *)loc = G + GOTPLT + A - P;
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX:
|
||||
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);
|
||||
}
|
||||
break;
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
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);
|
||||
}
|
||||
break;
|
||||
case R_X86_64_TLSGD:
|
||||
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, 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, 0x81, 0xc0, 0, 0, 0, 0, // add $val, %rax
|
||||
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 9) = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_TLSLD:
|
||||
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_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[] = {
|
||||
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[] = {
|
||||
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[] = {
|
||||
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;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
*(ul32 *)(loc + 5) = ctx.tp_addr - ctx.tls_begin;
|
||||
assert(A == -4);
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
write32s(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_X86_64_DTPOFF64:
|
||||
*(ul64 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_X86_64_TPOFF32:
|
||||
write32s(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_X86_64_TPOFF64:
|
||||
*(ul64 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_X86_64_GOTTPOFF:
|
||||
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 - ctx.tp_addr);
|
||||
assert(A == -4);
|
||||
}
|
||||
break;
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
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 - ctx.tp_addr);
|
||||
assert(A == -4);
|
||||
}
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
break;
|
||||
case R_X86_64_SIZE64:
|
||||
*(ul64 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function is responsible for applying relocations against
|
||||
// non-SHF_ALLOC sections (i.e. sections that are not mapped to memory
|
||||
// at runtime).
|
||||
//
|
||||
// Relocations against non-SHF_ALLOC sections are much easier to
|
||||
// handle than that against SHF_ALLOC sections. It is because, since
|
||||
// they are not mapped to memory, they don't contain any variable or
|
||||
// function and never need PLT or GOT. Non-SHF_ALLOC sections are
|
||||
// mostly debug info sections.
|
||||
//
|
||||
// Relocations against non-SHF_ALLOC sections are not scanned by
|
||||
// scan_relocations.
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<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 << ")";
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32s = [&](u64 val) {
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8: {
|
||||
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);
|
||||
break;
|
||||
case R_X86_64_32S:
|
||||
write32s(S + A);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(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);
|
||||
break;
|
||||
case R_X86_64_SIZE64:
|
||||
*(ul64 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Linker has to create data structures in an output file to apply
|
||||
// some type of relocations. For example, if a relocation refers a GOT
|
||||
// or a PLT entry of a symbol, linker has to create an entry in .got
|
||||
// or in .plt for that symbol. In order to fix the file layout, we
|
||||
// need to scan relocations.
|
||||
template <>
|
||||
void InputSection<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];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
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_X86_64_8:
|
||||
case R_X86_64_16:
|
||||
case R_X86_64_32:
|
||||
case R_X86_64_32S:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_X86_64_PC8:
|
||||
case R_X86_64_PC16:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_PC64:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_X86_64_GOT32:
|
||||
case R_X86_64_GOT64:
|
||||
case R_X86_64_GOTPC32:
|
||||
case R_X86_64_GOTPC64:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCREL64:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPCRELX";
|
||||
|
||||
bool do_relax = ctx.arg.relax && !sym.is_imported &&
|
||||
sym.is_relative() && relax_gotpcrelx(loc - 2);
|
||||
if (!do_relax)
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_REX_GOTPCRELX: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_REX_GOTPCRELX";
|
||||
|
||||
bool do_relax = ctx.arg.relax && !sym.is_imported &&
|
||||
sym.is_relative() && relax_rex_gotpcrelx(loc - 3);
|
||||
if (!do_relax)
|
||||
sym.flags.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.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_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 (relax_tlsgd(ctx, sym))
|
||||
i++;
|
||||
else
|
||||
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_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 (relax_tlsld(ctx))
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTTPOFF: {
|
||||
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.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";
|
||||
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTOFF64:
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_DTPOFF64:
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_TPOFF64:
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_SIZE64:
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,554 @@
|
||||
// This file contains code to read DWARF debug info to create .gdb_index.
|
||||
//
|
||||
// .gdb_index is an optional section to speed up GNU debugger. It contains
|
||||
// two maps: 1) a map from function/variable/type names to compunits, and
|
||||
// 2) a map from function address ranges to compunits. gdb uses these
|
||||
// maps to quickly find a compunit given a name or an instruction pointer.
|
||||
//
|
||||
// (Terminology: a compilation unit, which often abbreviated as compunit
|
||||
// or cu, is a unit of debug info. An input .debug_info section usually
|
||||
// contains one compunit, and thus an output .debug_info contains as
|
||||
// many compunits as the number of input files.)
|
||||
//
|
||||
// .gdb_index is not mandatory. All the information in .gdb_index is
|
||||
// also in other debug info sections. You can actually create an
|
||||
// executable without .gdb_index and later add it using `gdb-add-index`
|
||||
// post-processing tool that comes with gdb.
|
||||
//
|
||||
// The mapping from names to compunits is 1:n while the mapping from
|
||||
// address ranges to compunits is 1:1. That is, two object files may
|
||||
// define the same type name (with the same definition), while there
|
||||
// should be no two functions that overlap with each other in memory.
|
||||
//
|
||||
// .gdb_index contains an on-disk hash table for names, so gdb can
|
||||
// lookup names without loading all strings into memory and construct an
|
||||
// in-memory hash table.
|
||||
//
|
||||
// Names are in .debug_gnu_pubnames and .debug_gnu_pubtypes input
|
||||
// sections. These sections are created if `-ggnu-pubnames` is given.
|
||||
// Besides names, these sections contains attributes for each name so
|
||||
// that gdb can distinguish type names from function names, for example.
|
||||
//
|
||||
// A compunit contains one or more function address ranges. If an
|
||||
// object file is compiled without -ffunction-sections, it contains
|
||||
// only one .text section and therefore contains a single address range.
|
||||
// Such range is typically stored directly to the compunit.
|
||||
//
|
||||
// If an object file is compiled with -fucntion-sections, it contains
|
||||
// more than one .text section, and it has as many address ranges as
|
||||
// the number of .text sections. Such discontiguous address ranges are
|
||||
// stored to .debug_ranges in DWARF 2/3/4/5 and
|
||||
// .debug_rnglists/.debug_addr in DWARF 5.
|
||||
//
|
||||
// .debug_info section contains DWARF debug info. Although we don't need
|
||||
// to parse the whole .debug_info section to read address ranges, we
|
||||
// have to do a little bit. DWARF is complicated and often handled using
|
||||
// a library such as libdwarf. But we don't use any library because we
|
||||
// don't want to add an extra run-time dependency just for --gdb-index.
|
||||
//
|
||||
// This page explains the format of .gdb_index:
|
||||
// https://sourceware.org/gdb/onlinedocs/gdb/Index-Section-Format.html
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// The hash function for .gdb_index.
|
||||
static u32 gdb_hash(std::string_view name) {
|
||||
u32 h = 0;
|
||||
for (u8 c : name) {
|
||||
if ('A' <= c && c <= 'Z')
|
||||
c = 'a' + c - 'A';
|
||||
h = h * 67 + c - 113;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
// Split .debug_info into so-called "compilation units". A .debug_info
|
||||
// section usually contains one compunit unless it was created by `ld -r`.
|
||||
// This is for --gdb-index.
|
||||
template <typename E>
|
||||
std::vector<std::string_view>
|
||||
read_compunits(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
file.debug_info->uncompress(ctx);
|
||||
std::string_view data = file.debug_info->contents;
|
||||
std::vector<std::string_view> vec;
|
||||
|
||||
while (!data.empty()) {
|
||||
if (data.size() < 4)
|
||||
Fatal(ctx) << *file.debug_info << ": corrupted .debug_info";
|
||||
if (*(U32<E> *)data.data() == 0xffffffff)
|
||||
Fatal(ctx) << *file.debug_info << ": --gdb-index: DWARF64 not supported";
|
||||
i64 len = *(U32<E> *)data.data() + 4;
|
||||
vec.push_back(data.substr(0, len));
|
||||
data = data.substr(len);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Parses .debug_gnu_pubnames and .debug_gnu_pubtypes. These sections
|
||||
// start with a 14 bytes header followed by (4-byte offset, 1-byte type,
|
||||
// null-terminated string) tuples.
|
||||
//
|
||||
// The 4-byte offset is an offset into .debug_info that contains details
|
||||
// about the name. The 1-byte type is a type of the corresponding name
|
||||
// (e.g. function, variable or datatype). The string is a name of a
|
||||
// function, a variable or a type.
|
||||
template <typename E>
|
||||
std::vector<GdbIndexName> read_pubnames(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
std::vector<GdbIndexName> vec;
|
||||
|
||||
auto get_cu_idx = [&](InputSection<E> &isec, i64 offset) {
|
||||
i64 off = 0;
|
||||
for (i64 i = 0; i < file.compunits.size(); i++) {
|
||||
if (offset == off)
|
||||
return file.compunits_idx + i;
|
||||
off += file.compunits[i].size();
|
||||
}
|
||||
Fatal(ctx) << isec << ": corrupted debug_info_offset";
|
||||
};
|
||||
|
||||
auto read = [&](InputSection<E> &isec) {
|
||||
isec.uncompress(ctx);
|
||||
std::string_view contents = isec.contents;
|
||||
|
||||
while (!contents.empty()) {
|
||||
if (contents.size() < 14)
|
||||
Fatal(ctx) << isec << ": corrupted header";
|
||||
|
||||
u32 len = *(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 = *(U32<E> *)data.data();
|
||||
data = data.substr(4);
|
||||
if (offset == 0)
|
||||
break;
|
||||
|
||||
u8 type = data[0];
|
||||
data = data.substr(1);
|
||||
|
||||
std::string_view name = data.data();
|
||||
data = data.substr(name.size() + 1);
|
||||
|
||||
vec.push_back({name, gdb_hash(name), (type << 24) | cu_idx});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (file.debug_pubnames)
|
||||
read(*file.debug_pubnames);
|
||||
if (file.debug_pubtypes)
|
||||
read(*file.debug_pubtypes);
|
||||
|
||||
// Uniquify elements because GCC 11 seems to emit one record for each
|
||||
// comdat group which results in having a lot of duplicate records.
|
||||
auto less = [](const GdbIndexName &a, const GdbIndexName &b) {
|
||||
return std::tuple{a.hash, a.attr, a.name} <
|
||||
std::tuple{b.hash, b.attr, b.name};
|
||||
};
|
||||
|
||||
auto equal = [](const GdbIndexName &a, const GdbIndexName &b) {
|
||||
return std::tuple{a.hash, a.attr, a.name} ==
|
||||
std::tuple{b.hash, b.attr, b.name};
|
||||
};
|
||||
|
||||
std::sort(vec.begin(), vec.end(), less);
|
||||
vec.erase(std::unique(vec.begin(), vec.end(), equal), vec.end());
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static u8 *get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
if (u8 *buf = chunk->get_uncompressed_data())
|
||||
return buf;
|
||||
return ctx.buf + chunk->shdr.sh_offset;
|
||||
}
|
||||
|
||||
// Try to find a compilation unit from .debug_info and its
|
||||
// corresponding record from .debug_abbrev and returns them.
|
||||
template <typename E>
|
||||
static std::tuple<u8 *, u8 *, u32>
|
||||
find_compunit(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
// Read .debug_info to find the record at a given offset.
|
||||
u8 *cu = get_buffer(ctx, ctx.debug_info) + offset;
|
||||
u32 dwarf_version = *(U16<E> *)(cu + 4);
|
||||
u32 abbrev_offset;
|
||||
|
||||
// Skip a header.
|
||||
switch (dwarf_version) {
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
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 = *(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;
|
||||
|
||||
switch (u32 unit_type = cu[6]; unit_type) {
|
||||
case DW_UT_compile:
|
||||
case DW_UT_partial:
|
||||
cu += 12;
|
||||
break;
|
||||
case DW_UT_skeleton:
|
||||
case DW_UT_split_compile:
|
||||
cu += 20;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unknown DW_UT_* value: 0x"
|
||||
<< std::hex << unit_type;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unknown DWARF version: "
|
||||
<< dwarf_version;
|
||||
}
|
||||
|
||||
u32 abbrev_code = read_uleb(cu);
|
||||
|
||||
// Find a .debug_abbrev record corresponding to the .debug_info record.
|
||||
// We assume the .debug_info record at a given offset is of
|
||||
// DW_TAG_compile_unit which describes a compunit.
|
||||
u8 *abbrev = get_buffer(ctx, ctx.debug_abbrev) + abbrev_offset;
|
||||
|
||||
for (;;) {
|
||||
u32 code = read_uleb(abbrev);
|
||||
if (code == 0)
|
||||
Fatal(ctx) << file << ": --gdb-index: .debug_abbrev does not contain"
|
||||
<< " a record for the first .debug_info record";
|
||||
|
||||
if (code == abbrev_code) {
|
||||
// Found a record
|
||||
u64 abbrev_tag = read_uleb(abbrev);
|
||||
if (abbrev_tag != DW_TAG_compile_unit && abbrev_tag != DW_TAG_skeleton_unit)
|
||||
Fatal(ctx) << file << ": --gdb-index: the first entry's tag is not"
|
||||
<< " DW_TAG_compile_unit/DW_TAG_skeleton_unit but 0x"
|
||||
<< std::hex << abbrev_tag;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip an uninteresting record
|
||||
read_uleb(abbrev); // tag
|
||||
abbrev++; // has_children byte
|
||||
for (;;) {
|
||||
u64 name = read_uleb(abbrev);
|
||||
u64 form = read_uleb(abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
if (form == DW_FORM_implicit_const)
|
||||
read_uleb(abbrev);
|
||||
}
|
||||
}
|
||||
|
||||
abbrev++; // skip has_children byte
|
||||
return {cu, abbrev, dwarf_version};
|
||||
}
|
||||
|
||||
// Estimate the number of address ranges contained in a given file.
|
||||
// It may over-estimate but never under-estimate.
|
||||
template <typename E>
|
||||
i64 estimate_address_areas(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
// Each CU contains zero or one address area.
|
||||
i64 ret = file.compunits.size();
|
||||
|
||||
// In DWARF 4, a CU can refer address ranges in .debug_ranges.
|
||||
// .debug_ranges contains a vector of [begin, end) address pairs.
|
||||
// The last entry must be a null terminator, so we do -1.
|
||||
if (file.debug_ranges)
|
||||
ret += file.debug_ranges->sh_size / sizeof(Word<E>) / 2 - 1;
|
||||
|
||||
// In DWARF 5, a CU can refer address ranges in .debug_rnglists, which
|
||||
// contains variable-length entries. The smallest possible range entry
|
||||
// is one byte for the code and two ULEB128 values (each can be as
|
||||
// small as one byte), so 3 bytes.
|
||||
if (file.debug_rnglists)
|
||||
ret += file.debug_rnglists->sh_size / 3;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// .debug_info contains variable-length fields. This class reads them.
|
||||
template <typename E>
|
||||
class DebugInfoReader {
|
||||
public:
|
||||
DebugInfoReader(Context<E> &ctx, ObjectFile<E> &file, u8 *cu)
|
||||
: ctx(ctx), file(file), cu(cu) {}
|
||||
|
||||
u64 read(u64 form);
|
||||
|
||||
Context<E> &ctx;
|
||||
ObjectFile<E> &file;
|
||||
u8 *cu;
|
||||
};
|
||||
|
||||
// Read value of the given DW_FORM_* form. If a value is not scalar,
|
||||
// returns a dummy value 0.
|
||||
template <typename E>
|
||||
inline u64 DebugInfoReader<E>::read(u64 form) {
|
||||
switch (form) {
|
||||
case DW_FORM_flag_present:
|
||||
return 0;
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_flag:
|
||||
case DW_FORM_strx1:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_ref1:
|
||||
return *cu++;
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_strx2:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_ref2: {
|
||||
u64 val = *(U16<E> *)cu;
|
||||
cu += 2;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx3:
|
||||
case DW_FORM_addrx3: {
|
||||
u64 val = *(U24<E> *)cu;
|
||||
cu += 3;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_strp:
|
||||
case DW_FORM_sec_offset:
|
||||
case DW_FORM_line_strp:
|
||||
case DW_FORM_strx4:
|
||||
case DW_FORM_addrx4:
|
||||
case DW_FORM_ref4: {
|
||||
u64 val = *(U32<E> *)cu;
|
||||
cu += 4;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data8:
|
||||
case DW_FORM_ref8: {
|
||||
u64 val = *(U64<E> *)cu;
|
||||
cu += 8;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_addr:
|
||||
case DW_FORM_ref_addr: {
|
||||
u64 val = *(Word<E> *)cu;
|
||||
cu += sizeof(Word<E>);
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx:
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_ref_udata:
|
||||
case DW_FORM_loclistx:
|
||||
case DW_FORM_rnglistx:
|
||||
return read_uleb(cu);
|
||||
case DW_FORM_string:
|
||||
cu += strlen((char *)cu) + 1;
|
||||
return 0;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled debug info form: 0x"
|
||||
<< std::hex << form;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Read a range list from .debug_ranges starting at the given offset.
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_debug_range(Context<E> &ctx, ObjectFile<E> &file, Word<E> *range) {
|
||||
std::vector<u64> vec;
|
||||
u64 base = 0;
|
||||
|
||||
for (i64 i = 0; range[i] || range[i + 1]; i += 2) {
|
||||
if (range[i] + 1 == 0) {
|
||||
// base address selection entry
|
||||
base = range[i + 1];
|
||||
} else {
|
||||
vec.push_back(range[i] + base);
|
||||
vec.push_back(range[i + 1] + base);
|
||||
}
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Read a range list from .debug_rnglists starting at the given offset.
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_rnglist_range(Context<E> &ctx, ObjectFile<E> &file, u8 *rnglist,
|
||||
Word<E> *addrx) {
|
||||
std::vector<u64> vec;
|
||||
u64 base = 0;
|
||||
|
||||
for (;;) {
|
||||
switch (*rnglist++) {
|
||||
case DW_RLE_end_of_list:
|
||||
return vec;
|
||||
case DW_RLE_base_addressx:
|
||||
base = addrx[read_uleb(rnglist)];
|
||||
break;
|
||||
case DW_RLE_startx_endx:
|
||||
vec.push_back(addrx[read_uleb(rnglist)]);
|
||||
vec.push_back(addrx[read_uleb(rnglist)]);
|
||||
break;
|
||||
case DW_RLE_startx_length:
|
||||
vec.push_back(addrx[read_uleb(rnglist)]);
|
||||
vec.push_back(vec.back() + read_uleb(rnglist));
|
||||
break;
|
||||
case DW_RLE_offset_pair:
|
||||
vec.push_back(base + read_uleb(rnglist));
|
||||
vec.push_back(base + read_uleb(rnglist));
|
||||
break;
|
||||
case DW_RLE_base_address:
|
||||
base = *(Word<E> *)rnglist;
|
||||
rnglist += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_end:
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_length:
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
vec.push_back(vec.back() + read_uleb(rnglist));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a list of address ranges explained by a compunit at the
|
||||
// `offset` in an output .debug_info section.
|
||||
//
|
||||
// .debug_info contains DWARF debug info records, so this function
|
||||
// parses DWARF. If a designated compunit contains multiple ranges, the
|
||||
// ranges are read from .debug_ranges (or .debug_rnglists for DWARF5).
|
||||
// Otherwise, a range is read directly from .debug_info (or possibly
|
||||
// from .debug_addr for DWARF5).
|
||||
template <typename E>
|
||||
std::vector<u64>
|
||||
read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
u8 *cu;
|
||||
u8 *abbrev;
|
||||
u32 dwarf_version;
|
||||
std::tie(cu, abbrev, dwarf_version) = find_compunit(ctx, file, offset);
|
||||
|
||||
DebugInfoReader<E> reader{ctx, file, cu};
|
||||
|
||||
struct Record {
|
||||
u64 form = 0;
|
||||
u64 value = 0;
|
||||
};
|
||||
|
||||
Record low_pc;
|
||||
Record high_pc;
|
||||
Record ranges;
|
||||
std::optional<u64> rnglists_base;
|
||||
Word<E> *addrx = nullptr;
|
||||
|
||||
// Read all interesting debug records.
|
||||
for (;;) {
|
||||
u64 name = read_uleb(abbrev);
|
||||
u64 form = read_uleb(abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
|
||||
u64 val = reader.read(form);
|
||||
|
||||
switch (name) {
|
||||
case DW_AT_low_pc:
|
||||
low_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_high_pc:
|
||||
high_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_rnglists_base:
|
||||
rnglists_base = val;
|
||||
break;
|
||||
case DW_AT_addr_base:
|
||||
addrx = (Word<E> *)(get_buffer(ctx, ctx.debug_addr) + val);
|
||||
break;
|
||||
case DW_AT_ranges:
|
||||
ranges = {form, val};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-contiguous address ranges.
|
||||
if (ranges.form) {
|
||||
if (dwarf_version <= 4) {
|
||||
Word<E> *range_begin =
|
||||
(Word<E> *)(get_buffer(ctx, ctx.debug_ranges) + ranges.value);
|
||||
return read_debug_range(ctx, file, range_begin);
|
||||
}
|
||||
|
||||
assert(dwarf_version == 5);
|
||||
|
||||
u8 *buf = get_buffer(ctx, ctx.debug_rnglists);
|
||||
if (ranges.form == DW_FORM_sec_offset)
|
||||
return read_rnglist_range(ctx, file, buf + ranges.value, addrx);
|
||||
|
||||
if (!rnglists_base)
|
||||
Fatal(ctx) << file << ": --gdb-index: missing DW_AT_rnglists_base";
|
||||
|
||||
u8 *base = buf + *rnglists_base;
|
||||
return read_rnglist_range(ctx, file, base + *(U32<E> *)base, addrx);
|
||||
}
|
||||
|
||||
// Handle a contiguous address range.
|
||||
if (low_pc.form && high_pc.form) {
|
||||
u64 lo;
|
||||
|
||||
switch (low_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
lo = low_pc.value;
|
||||
break;
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
lo = addrx[low_pc.value];
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled form for DW_AT_low_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
|
||||
switch (high_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
return {lo, high_pc.value};
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
return {lo, addrx[high_pc.value]};
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_data8:
|
||||
return {lo, lo + high_pc.value};
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled form for DW_AT_high_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
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
|
||||
@@ -7,85 +7,84 @@
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
template <typename E>
|
||||
using Feeder = tbb::parallel_do_feeder<InputSection<E> *>;
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
static bool is_init_fini(const InputSection<E> &isec) {
|
||||
return isec.shdr.sh_type == SHT_INIT_ARRAY ||
|
||||
isec.shdr.sh_type == SHT_FINI_ARRAY ||
|
||||
isec.shdr.sh_type == SHT_PREINIT_ARRAY ||
|
||||
isec.name().starts_with(".ctors") ||
|
||||
isec.name().starts_with(".dtors") ||
|
||||
isec.name().starts_with(".init") ||
|
||||
isec.name().starts_with(".fini");
|
||||
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 (flags & SHF_GNU_RETAIN) ||
|
||||
type == SHT_NOTE ||
|
||||
type == SHT_INIT_ARRAY ||
|
||||
type == SHT_FINI_ARRAY ||
|
||||
type == SHT_PREINIT_ARRAY ||
|
||||
(is_arm32<E> && type == SHT_ARM_EXIDX) ||
|
||||
name.starts_with(".ctors") ||
|
||||
name.starts_with(".dtors") ||
|
||||
name.starts_with(".init") ||
|
||||
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>
|
||||
static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
Feeder<E> &feeder, i64 depth) {
|
||||
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 (isec->rel_fragments)
|
||||
for (i64 i = 0; isec->rel_fragments[i].idx >= 0; i++)
|
||||
isec->rel_fragments[i].frag->is_alive = true;
|
||||
|
||||
// If this is a text section, .eh_frame may contain records
|
||||
// describing how to handle exceptions for that function.
|
||||
// We want to keep associated .eh_frame records.
|
||||
for (FdeRecord<E> &fde : isec->get_fdes())
|
||||
for (ElfRel<E> &rel : fde.get_rels().subspan(1))
|
||||
for (const ElfRel<E> &rel : fde.get_rels(isec->file).subspan(1))
|
||||
if (Symbol<E> *sym = isec->file.symbols[rel.r_sym])
|
||||
if (mark_section(sym->input_section))
|
||||
feeder.add(sym->input_section);
|
||||
if (mark_section(sym->get_input_section()))
|
||||
feeder.add(sym->get_input_section());
|
||||
|
||||
for (ElfRel<E> &rel : isec->get_rels(ctx)) {
|
||||
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
|
||||
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
|
||||
|
||||
// Symbol can refer either a section fragment or an input section.
|
||||
// Mark a fragment as alive.
|
||||
if (SectionFragment<E> *frag = sym.get_frag()) {
|
||||
frag->is_alive = true;
|
||||
frag->is_alive.store(true, std::memory_order_relaxed);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mark_section(sym.input_section))
|
||||
if (!mark_section(sym.get_input_section()))
|
||||
continue;
|
||||
|
||||
// Mark a section alive. For better performacne, we don't call
|
||||
// `feeder.add` too often.
|
||||
if (depth < 3)
|
||||
visit(ctx, sym.input_section, feeder, depth + 1);
|
||||
visit(ctx, sym.get_input_section(), feeder, depth + 1);
|
||||
else
|
||||
feeder.add(sym.input_section);
|
||||
feeder.add(sym.get_input_section());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static tbb::concurrent_vector<InputSection<E> *>
|
||||
collect_root_set(Context<E> &ctx) {
|
||||
static void collect_root_set(Context<E> &ctx,
|
||||
tbb::concurrent_vector<InputSection<E> *> &rootset) {
|
||||
Timer t(ctx, "collect_root_set");
|
||||
tbb::concurrent_vector<InputSection<E> *> roots;
|
||||
|
||||
auto enqueue_section = [&](InputSection<E> *isec) {
|
||||
if (mark_section(isec))
|
||||
roots.push_back(isec);
|
||||
rootset.push_back(isec);
|
||||
};
|
||||
|
||||
auto enqueue_symbol = [&](Symbol<E> *sym) {
|
||||
if (sym) {
|
||||
if (SectionFragment<E> *frag = sym->get_frag())
|
||||
frag->is_alive = true;
|
||||
frag->is_alive.store(true, std::memory_order_relaxed);
|
||||
else
|
||||
enqueue_section(sym->input_section);
|
||||
enqueue_section(sym->get_input_section());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -99,11 +98,11 @@ collect_root_set(Context<E> &ctx) {
|
||||
// reduce the amount of non-memory-mapped segments, you should
|
||||
// use `strip` command, compile without debug info or use
|
||||
// -strip-all linker option.
|
||||
if (!(isec->shdr.sh_flags & SHF_ALLOC))
|
||||
u32 flags = isec->shdr().sh_flags;
|
||||
if (!(flags & SHF_ALLOC))
|
||||
isec->is_visited = true;
|
||||
|
||||
if (is_init_fini(*isec) || is_c_identifier(isec->name()) ||
|
||||
isec->shdr.sh_type == SHT_NOTE)
|
||||
if (should_keep(*isec))
|
||||
enqueue_section(isec.get());
|
||||
}
|
||||
});
|
||||
@@ -116,30 +115,32 @@ collect_root_set(Context<E> &ctx) {
|
||||
});
|
||||
|
||||
// Add sections referenced by root symbols.
|
||||
enqueue_symbol(Symbol<E>::intern(ctx, ctx.arg.entry));
|
||||
enqueue_symbol(get_symbol(ctx, ctx.arg.entry));
|
||||
|
||||
for (std::string_view name : ctx.arg.undefined)
|
||||
enqueue_symbol(Symbol<E>::intern(ctx, name));
|
||||
enqueue_symbol(get_symbol(ctx, name));
|
||||
|
||||
for (std::string_view name : ctx.arg.require_defined)
|
||||
enqueue_symbol(get_symbol(ctx, name));
|
||||
|
||||
// .eh_frame consists of variable-length records called CIE and FDE
|
||||
// records, and they are a unit of inclusion or exclusion.
|
||||
// We just keep all CIEs and everything that are referenced by them.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (CieRecord<E> &cie : file->cies)
|
||||
for (ElfRel<E> &rel : cie.get_rels())
|
||||
for (const ElfRel<E> &rel : cie.get_rels())
|
||||
enqueue_symbol(file->symbols[rel.r_sym]);
|
||||
});
|
||||
|
||||
return roots;
|
||||
}
|
||||
|
||||
// Mark all reachable sections
|
||||
template <typename E>
|
||||
static void mark(Context<E> &ctx,
|
||||
tbb::concurrent_vector<InputSection<E> *> &roots) {
|
||||
tbb::concurrent_vector<InputSection<E> *> &rootset) {
|
||||
Timer t(ctx, "mark");
|
||||
|
||||
tbb::parallel_do(roots, [&](InputSection<E> *isec, Feeder<E> &feeder) {
|
||||
tbb::parallel_for_each(rootset, [&](InputSection<E> *isec,
|
||||
tbb::feeder<InputSection<E> *> &feeder) {
|
||||
visit(ctx, isec, feeder, 0);
|
||||
});
|
||||
}
|
||||
@@ -151,9 +152,7 @@ static void sweep(Context<E> &ctx) {
|
||||
static Counter counter("garbage_sections");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (i64 i = 0; i < file->sections.size(); i++) {
|
||||
std::unique_ptr<InputSection<E>> &isec = file->sections[i];
|
||||
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && !isec->is_visited) {
|
||||
if (ctx.arg.print_gc_sections)
|
||||
SyncOut(ctx) << "removing unused section " << *isec;
|
||||
@@ -171,9 +170,11 @@ static void mark_nonalloc_fragments(Context<E> &ctx) {
|
||||
Timer t(ctx, "mark_nonalloc_fragments");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
for (SectionFragment<E> *frag : file->fragments)
|
||||
if (!(frag->output_section.shdr.sh_flags & SHF_ALLOC))
|
||||
frag->is_alive = true;
|
||||
for (std::unique_ptr<MergeableSection<E>> &m : file->mergeable_sections)
|
||||
if (m)
|
||||
for (SectionFragment<E> *frag : m->fragments)
|
||||
if (!(frag->output_section.shdr.sh_flags & SHF_ALLOC))
|
||||
frag->is_alive.store(true, std::memory_order_relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -183,10 +184,14 @@ void gc_sections(Context<E> &ctx) {
|
||||
|
||||
mark_nonalloc_fragments(ctx);
|
||||
|
||||
tbb::concurrent_vector<InputSection<E> *> roots = collect_root_set(ctx);
|
||||
mark(ctx, roots);
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset;
|
||||
collect_root_set(ctx, rootset);
|
||||
mark(ctx, rootset);
|
||||
sweep(ctx);
|
||||
}
|
||||
|
||||
template void gc_sections(Context<X86_64> &ctx);
|
||||
template void gc_sections(Context<I386> &ctx);
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void gc_sections(Context<E> &ctx);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -35,25 +35,40 @@
|
||||
// This problem boils down to one in graph theory. Input to ICF can be
|
||||
// considered as a directed graph in which vertices are sections and edges
|
||||
// are relocations. Vertices have labels (section contents, etc.), and so
|
||||
// are edges (relocation offsets, etc.). Given this formulation, we want to
|
||||
// find as many isomorphic subgraphs as possible.
|
||||
// are edges (relocation offsets, etc.). Two vertices are considered
|
||||
// identical if and only if the (possibly infinite) their unfoldings into
|
||||
// regular trees are equal. Given this formulation, we want to find as
|
||||
// many identical vertices as possible.
|
||||
//
|
||||
// Solving such problem is computationally intensive, but mold is quite fast.
|
||||
// For Chromium, mold's ICF finishes in less than 1 second with 20 threads.
|
||||
// This is contrary to lld and gold, which take about 5 and 50 seconds to
|
||||
// run ICF under the same condition, respectively.
|
||||
// Just like a lot of problems with graph, this problem doesn't have a
|
||||
// straightforward "optimal" solution, and we need to resort to heuristics.
|
||||
//
|
||||
// mold's ICF is faster because we are using a better algorithm.
|
||||
// It's actually me who developed and implemented the lld's ICF algorithm,
|
||||
// and I can say that mold's algorithm is better than that in all aspects.
|
||||
// It scales better for number of available cores, require less overall
|
||||
// computation, and has a smaller working set. So, it's better with a single
|
||||
// thread and even better with multiple threads.
|
||||
// mold approaches this problem by hashing program trees with increasing depth
|
||||
// on each iteration.
|
||||
// For example, when we start, we only hash individual functions with
|
||||
// their call into other functions omitted. From the second iteration, we
|
||||
// put the function they call into the hash by appending the hash of those
|
||||
// functions from the previous iteration. This means that the nth iteration
|
||||
// hashes call chain up to (n-1) levels deep.
|
||||
// We use a cryptographic hash function, so the unique number of hashes will
|
||||
// only monotonically increase as we take into account of deeper trees with
|
||||
// iterations (otherwise, that means we have found a hash collision). We stop
|
||||
// when the unique number of hashes stop increasing; this is based on the fact
|
||||
// that once we observe an iteration with the same amount of unique hashes as
|
||||
// the previous iteration, it will remain unchanged for further iterations.
|
||||
// This is provable, but here we omit the proof for brevity.
|
||||
//
|
||||
// When compared to other approaches, mold's approach has a relatively cheaper
|
||||
// cost per iteration, and as a bonus, is highly parallelizable.
|
||||
// For Chromium, mold's ICF finishes in less than 1 second with 20 threads,
|
||||
// whereas lld takes 5 seconds and gold takes 50 seconds under the same
|
||||
// conditions.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../sha.h"
|
||||
|
||||
#include <array>
|
||||
#include <openssl/sha.h>
|
||||
#include <cstdio>
|
||||
#include <tbb/concurrent_unordered_map.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/enumerable_thread_specific.h>
|
||||
@@ -61,18 +76,30 @@
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <tbb/parallel_sort.h>
|
||||
|
||||
static constexpr i64 HASH_SIZE = 16;
|
||||
static constexpr int64_t HASH_SIZE = 16;
|
||||
|
||||
typedef std::array<u8, HASH_SIZE> Digest;
|
||||
typedef std::array<uint8_t, HASH_SIZE> Digest;
|
||||
|
||||
namespace tbb {
|
||||
template<> struct tbb_hash<Digest> {
|
||||
namespace std {
|
||||
template<> struct hash<Digest> {
|
||||
size_t operator()(const Digest &k) const {
|
||||
return *(i64 *)&k[0];
|
||||
return *(int64_t *)&k[0];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
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");
|
||||
@@ -94,12 +121,13 @@ static void uniquify_cies(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool is_eligible(InputSection<E> &isec) {
|
||||
const ElfShdr<E> &shdr = isec.shdr;
|
||||
static bool is_eligible(Context<E> &ctx, InputSection<E> &isec) {
|
||||
const ElfShdr<E> &shdr = isec.shdr();
|
||||
std::string_view name = isec.name();
|
||||
|
||||
bool is_alloc = (shdr.sh_flags & SHF_ALLOC);
|
||||
bool is_executable = (shdr.sh_flags & SHF_EXECINSTR);
|
||||
bool is_exec = (shdr.sh_flags & SHF_EXECINSTR) ||
|
||||
ctx.arg.ignore_data_address_equality;
|
||||
bool is_relro = (name == ".data.rel.ro" ||
|
||||
name.starts_with(".data.rel.ro."));
|
||||
bool is_readonly = !(shdr.sh_flags & SHF_WRITE) || is_relro;
|
||||
@@ -108,15 +136,15 @@ static bool is_eligible(InputSection<E> &isec) {
|
||||
bool is_init = (shdr.sh_type == SHT_INIT_ARRAY || name == ".init");
|
||||
bool is_fini = (shdr.sh_type == SHT_FINI_ARRAY || name == ".fini");
|
||||
bool is_enumerable = is_c_identifier(name);
|
||||
bool is_addr_taken = !ctx.arg.icf_all && isec.address_significant;
|
||||
|
||||
return is_alloc && is_executable && is_readonly && !is_bss &&
|
||||
!is_empty && !is_init && !is_fini && !is_enumerable;
|
||||
return is_alloc && is_exec && is_readonly && !is_bss && !is_empty &&
|
||||
!is_init && !is_fini && !is_enumerable && !is_addr_taken;
|
||||
}
|
||||
|
||||
static Digest digest_final(SHA256_CTX &sha) {
|
||||
static Digest digest_final(SHA256Hash &sha) {
|
||||
u8 buf[SHA256_SIZE];
|
||||
int res = SHA256_Final(buf, &sha);
|
||||
assert(res == 1);
|
||||
sha.finish(buf);
|
||||
|
||||
Digest digest;
|
||||
memcpy(digest.data(), buf, HASH_SIZE);
|
||||
@@ -129,22 +157,18 @@ static bool is_leaf(Context<E> &ctx, InputSection<E> &isec) {
|
||||
return false;
|
||||
|
||||
for (FdeRecord<E> &fde : isec.get_fdes())
|
||||
if (fde.get_rels().size() > 1)
|
||||
if (fde.get_rels(isec.file).size() > 1)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static u64 combine_hash(u64 a, u64 b) {
|
||||
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
struct LeafHasher {
|
||||
size_t operator()(const InputSection<E> *isec) const {
|
||||
size_t operator()(InputSection<E> *isec) const {
|
||||
u64 h = hash_string(isec->contents);
|
||||
for (FdeRecord<E> &fde : isec->get_fdes()) {
|
||||
u64 h2 = hash_string(fde.get_contents().substr(8));
|
||||
u64 h2 = hash_string(fde.get_contents(isec->file).substr(8));
|
||||
h = combine_hash(h, h2);
|
||||
}
|
||||
return h;
|
||||
@@ -153,7 +177,7 @@ struct LeafHasher {
|
||||
|
||||
template <typename E>
|
||||
struct LeafEq {
|
||||
bool operator()(const InputSection<E> *a, const InputSection<E> *b) const {
|
||||
bool operator()(InputSection<E> *a, InputSection<E> *b) const {
|
||||
if (a->contents != b->contents)
|
||||
return false;
|
||||
|
||||
@@ -164,12 +188,15 @@ struct LeafEq {
|
||||
return false;
|
||||
|
||||
for (i64 i = 0; i < x.size(); i++)
|
||||
if (x[i].get_contents().substr(8) != y[i].get_contents().substr(8))
|
||||
if (x[i].get_contents(a->file).substr(8) !=
|
||||
y[i].get_contents(b->file).substr(8))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Early merge of leaf nodes, which can be processed without constructing the
|
||||
// entire graph. This reduces the vertex count and improves memory efficiency.
|
||||
template <typename E>
|
||||
static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
Timer t(ctx, "merge_leaf_nodes");
|
||||
@@ -186,7 +213,7 @@ static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
if (!isec || !isec->is_alive)
|
||||
continue;
|
||||
|
||||
if (!is_eligible(*isec)) {
|
||||
if (!is_eligible(ctx, *isec)) {
|
||||
non_eligible++;
|
||||
continue;
|
||||
}
|
||||
@@ -217,29 +244,26 @@ static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
SHA256_CTX sha;
|
||||
SHA256_Init(&sha);
|
||||
|
||||
u8 *buf = (u8 *)isec.contents.data();
|
||||
SHA256Hash sha;
|
||||
|
||||
auto hash = [&](auto val) {
|
||||
SHA256_Update(&sha, &val, sizeof(val));
|
||||
sha.update((u8 *)&val, sizeof(val));
|
||||
};
|
||||
|
||||
auto hash_string = [&](std::string_view str) {
|
||||
hash(str.size());
|
||||
SHA256_Update(&sha, str.data(), str.size());
|
||||
sha.update((u8 *)str.data(), str.size());
|
||||
};
|
||||
|
||||
auto hash_symbol = [&](Symbol<E> &sym) {
|
||||
InputSection<E> *isec = sym.input_section;
|
||||
InputSection<E> *isec = sym.get_input_section();
|
||||
|
||||
if (!sym.file) {
|
||||
hash('1');
|
||||
hash((u64)&sym);
|
||||
} else if (SectionFragment<E> *frag = sym.get_frag()) {
|
||||
hash('2');
|
||||
hash_string(frag->data);
|
||||
hash((u64)frag);
|
||||
} else if (!isec) {
|
||||
hash('3');
|
||||
} else if (isec->leader) {
|
||||
@@ -255,43 +279,33 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
};
|
||||
|
||||
hash_string(isec.contents);
|
||||
hash(isec.shdr.sh_flags);
|
||||
hash(isec.shdr().sh_flags);
|
||||
hash(isec.get_fdes().size());
|
||||
hash(isec.get_rels(ctx).size());
|
||||
|
||||
for (FdeRecord<E> &fde : isec.get_fdes()) {
|
||||
hash(fde.cie->icf_idx);
|
||||
hash(isec.file.cies[fde.cie_idx].icf_idx);
|
||||
|
||||
// Bytes 0 to 4 contain the length of this record, and
|
||||
// bytes 4 to 8 contain an offset to CIE.
|
||||
hash_string(fde.get_contents().substr(8));
|
||||
hash_string(fde.get_contents(isec.file).substr(8));
|
||||
|
||||
hash(fde.get_rels().size());
|
||||
hash(fde.get_rels(isec.file).size());
|
||||
|
||||
for (ElfRel<E> &rel : fde.get_rels().subspan(1)) {
|
||||
for (const ElfRel<E> &rel : fde.get_rels(isec.file).subspan(1)) {
|
||||
hash_symbol(*isec.file.symbols[rel.r_sym]);
|
||||
hash(rel.r_type);
|
||||
hash(rel.r_offset - fde.input_offset);
|
||||
hash(fde.cie->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++) {
|
||||
ElfRel<E> &rel = isec.get_rels(ctx)[i];
|
||||
const ElfRel<E> &rel = isec.get_rels(ctx)[i];
|
||||
hash(rel.r_offset);
|
||||
hash(rel.r_type);
|
||||
hash(isec.get_addend(rel));
|
||||
|
||||
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_string(ref.frag->data);
|
||||
} 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);
|
||||
@@ -344,6 +358,9 @@ compute_digests(Context<E> &ctx, std::span<InputSection<E> *> sections) {
|
||||
return digests;
|
||||
}
|
||||
|
||||
// Build a graph, treating every function as a vertex and every function call
|
||||
// as an edge. See the description at the top for a more detailed formulation.
|
||||
// We use u32 indices here to improve cache locality.
|
||||
template <typename E>
|
||||
static void gather_edges(Context<E> &ctx,
|
||||
std::span<InputSection<E> *> sections,
|
||||
@@ -351,24 +368,23 @@ static void gather_edges(Context<E> &ctx,
|
||||
std::vector<u32> &edge_indices) {
|
||||
Timer t(ctx, "gather_edges");
|
||||
|
||||
if (sections.empty())
|
||||
return;
|
||||
|
||||
std::vector<i64> num_edges(sections.size());
|
||||
edge_indices.resize(sections.size());
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
|
||||
InputSection<E> &isec = *sections[i];
|
||||
assert(isec.icf_eligible);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 j = 0; j < isec.get_rels(ctx).size(); j++) {
|
||||
if (isec.rel_fragments && isec.rel_fragments[frag_idx].idx == j) {
|
||||
frag_idx++;
|
||||
} else {
|
||||
ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.get_frag() && sym.input_section &&
|
||||
sym.input_section->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]++;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -379,26 +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++;
|
||||
ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.get_frag() && sym.input_section &&
|
||||
sym.input_section->icf_eligible)
|
||||
edges[idx++] = sym.input_section->icf_idx;
|
||||
}
|
||||
}
|
||||
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++;
|
||||
|
||||
@@ -406,23 +418,27 @@ 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;
|
||||
|
||||
SHA256_CTX sha;
|
||||
SHA256_Init(&sha);
|
||||
SHA256_Update(&sha, digests[2][i].data(), HASH_SIZE);
|
||||
SHA256Hash sha;
|
||||
sha.update(digests[2][i].data(), HASH_SIZE);
|
||||
|
||||
i64 begin = edge_indices[i];
|
||||
i64 end = (i + 1 == num_digests) ? edges.size() : edge_indices[i + 1];
|
||||
|
||||
for (i64 j = begin; j < end; j++)
|
||||
SHA256_Update(&sha, digests[slot][edges[j]].data(), HASH_SIZE);
|
||||
for (i64 j : edges.subspan(begin, end - begin))
|
||||
sha.update(digests[slot][j].data(), HASH_SIZE);
|
||||
|
||||
digests[!slot][i] = digest_final(sha);
|
||||
|
||||
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;
|
||||
@@ -487,6 +503,8 @@ static void print_icf_sections(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
void icf_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "icf");
|
||||
if (ctx.objs.empty())
|
||||
return;
|
||||
|
||||
uniquify_cies(ctx);
|
||||
merge_leaf_nodes(ctx);
|
||||
@@ -494,6 +512,15 @@ void icf_sections(Context<E> &ctx) {
|
||||
// Prepare for the propagation rounds.
|
||||
std::vector<InputSection<E> *> sections = gather_sections(ctx);
|
||||
|
||||
// We allocate 3 arrays to store hashes for each vertex.
|
||||
//
|
||||
// Index 0 and 1 are used for tree hashes from the previous
|
||||
// iteration and the current iteration. They switch roles every
|
||||
// iteration. See `slot` below.
|
||||
//
|
||||
// Index 2 stores the initial, single-vertex hash. This is combined
|
||||
// with hashes from the connected vertices to form the tree hash
|
||||
// described above.
|
||||
std::vector<std::vector<Digest>> digests(3);
|
||||
digests[0] = compute_digests<E>(ctx, sections);
|
||||
digests[1].resize(digests[0].size());
|
||||
@@ -503,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.
|
||||
@@ -510,18 +538,33 @@ void icf_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "propagate");
|
||||
tbb::affinity_partitioner ap;
|
||||
|
||||
// A cheap test that the graph hasn't converged yet.
|
||||
// The loop after this one uses a strict condition, but it's expensive
|
||||
// as it requires sorting the entire hash collection.
|
||||
//
|
||||
// For nodes that have a cycle in downstream (i.e. recursive
|
||||
// functions and functions that calls recursive functions) will always
|
||||
// change with the iterations. Nodes that doesn't (i.e. non-recursive
|
||||
// functions) will stop changing as soon as the propagation depth reaches
|
||||
// the call tree depth.
|
||||
// Here, we test whether we have reached sufficient depth for the latter,
|
||||
// which is a necessary (but not sufficient) condition for convergence.
|
||||
i64 num_changed = -1;
|
||||
for (;;) {
|
||||
i64 n = propagate<E>(digests, edges, edge_indices, slot, ap);
|
||||
i64 n = propagate<E>(digests, edges, edge_indices, slot, converged, ap);
|
||||
if (n == num_changed)
|
||||
break;
|
||||
num_changed = n;
|
||||
}
|
||||
|
||||
// Run the pass until the unique number of hashes stop increasing, at which
|
||||
// point we have achieved convergence (proof omitted for brevity).
|
||||
i64 num_classes = -1;
|
||||
for (;;) {
|
||||
// count_num_classes requires sorting which is O(n log n), so do a little
|
||||
// more work beforehand to amortize that log factor.
|
||||
for (i64 i = 0; i < 10; i++)
|
||||
propagate<E>(digests, edges, edge_indices, slot, ap);
|
||||
propagate<E>(digests, edges, edge_indices, slot, converged, ap);
|
||||
|
||||
i64 n = count_num_classes<E>(digests[slot], ap);
|
||||
if (n == num_classes)
|
||||
@@ -551,28 +594,31 @@ void icf_sections(Context<E> &ctx) {
|
||||
});
|
||||
|
||||
// Since free'ing the map is slow, postpone it.
|
||||
ctx.on_exit.push_back([=]() { delete map; });
|
||||
ctx.on_exit.push_back([=] { delete map; });
|
||||
}
|
||||
|
||||
if (ctx.arg.print_icf_sections)
|
||||
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)
|
||||
continue;
|
||||
InputSection<E> *isec = sym->input_section;
|
||||
if (isec && isec->leader && isec->leader != isec) {
|
||||
sym->input_section = isec->leader;
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && isec->is_killed_by_icf()) {
|
||||
isec->kill();
|
||||
eliminated++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template void icf_sections(Context<X86_64> &ctx);
|
||||
template void icf_sections(Context<I386> &ctx);
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void icf_sections(Context<E> &ctx);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,475 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <limits>
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
typedef enum {
|
||||
NONE, ERROR, COPYREL, DYN_COPYREL, PLT, CPLT, DYN_CPLT, DYNREL, BASEREL, IFUNC,
|
||||
} Action;
|
||||
|
||||
template <typename E>
|
||||
bool CieRecord<E>::equals(const CieRecord<E> &other) const {
|
||||
if (get_contents() != other.get_contents())
|
||||
return false;
|
||||
|
||||
std::span<const ElfRel<E>> x = get_rels();
|
||||
std::span<const ElfRel<E>> y = other.get_rels();
|
||||
if (x.size() != y.size())
|
||||
return false;
|
||||
|
||||
for (i64 i = 0; i < x.size(); i++) {
|
||||
if (x[i].r_offset - input_offset != y[i].r_offset - other.input_offset ||
|
||||
x[i].r_type != y[i].r_type ||
|
||||
file.symbols[x[i].r_sym] != other.file.symbols[y[i].r_sym] ||
|
||||
get_addend(input_section, x[i]) != get_addend(other.input_section, y[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static i64 to_p2align(u64 alignment) {
|
||||
if (alignment == 0)
|
||||
return 0;
|
||||
return std::countr_zero(alignment);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
std::string_view name, i64 shndx)
|
||||
: file(file), shndx(shndx) {
|
||||
if (shndx < file.elf_sections.size())
|
||||
contents = {(char *)file.mf->data + shdr().sh_offset, (size_t)shdr().sh_size};
|
||||
|
||||
if (shdr().sh_flags & SHF_COMPRESSED) {
|
||||
ElfChdr<E> &chdr = *(ElfChdr<E> *)&contents[0];
|
||||
sh_size = chdr.ch_size;
|
||||
p2align = to_p2align(chdr.ch_addralign);
|
||||
} else {
|
||||
sh_size = shdr().sh_size;
|
||||
p2align = to_p2align(shdr().sh_addralign);
|
||||
}
|
||||
|
||||
// Sections may have been compressed. We usually uncompress them
|
||||
// directly into the mmap'ed output file, but we want to uncompress
|
||||
// early for REL-type ELF types to read relocation addends from
|
||||
// section contents. For RELA-type, we don't need to do this because
|
||||
// addends are in relocations.
|
||||
if constexpr (!E::is_rela)
|
||||
uncompress(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::uncompress(Context<E> &ctx) {
|
||||
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed)
|
||||
return;
|
||||
|
||||
u8 *buf = new u8[sh_size];
|
||||
uncompress_to(ctx, buf);
|
||||
contents = std::string_view((char *)buf, sh_size);
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
uncompressed = true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::uncompress_to(Context<E> &ctx, u8 *buf) {
|
||||
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed) {
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
return;
|
||||
}
|
||||
|
||||
if (contents.size() < sizeof(ElfChdr<E>))
|
||||
Fatal(ctx) << *this << ": corrupted compressed section";
|
||||
|
||||
ElfChdr<E> &hdr = *(ElfChdr<E> *)&contents[0];
|
||||
std::string_view data = contents.substr(sizeof(ElfChdr<E>));
|
||||
|
||||
switch (hdr.ch_type) {
|
||||
case ELFCOMPRESS_ZLIB: {
|
||||
unsigned long size = sh_size;
|
||||
if (::uncompress(buf, &size, (u8 *)data.data(), data.size()) != Z_OK)
|
||||
Fatal(ctx) << *this << ": uncompress failed";
|
||||
assert(size == sh_size);
|
||||
break;
|
||||
}
|
||||
case ELFCOMPRESS_ZSTD:
|
||||
if (ZSTD_decompress(buf, sh_size, (u8 *)data.data(), data.size()) != sh_size)
|
||||
Fatal(ctx) << *this << ": ZSTD_decompress failed";
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": unsupported compression type: 0x"
|
||||
<< std::hex << hdr.ch_type;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static 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;
|
||||
if (ctx.arg.pie)
|
||||
return 1;
|
||||
return 2;
|
||||
};
|
||||
|
||||
auto get_sym_type = [&] {
|
||||
if (sym.is_absolute())
|
||||
return 0;
|
||||
if (!sym.is_imported)
|
||||
return 1;
|
||||
if (sym.get_type() != STT_FUNC)
|
||||
return 2;
|
||||
return 3;
|
||||
};
|
||||
|
||||
return table[get_output_type()][get_sym_type()];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, Action action) {
|
||||
bool writable = (isec.shdr().sh_flags & SHF_WRITE);
|
||||
|
||||
auto error = [&] {
|
||||
std::string msg = sym.is_absolute() ? "-fno-PIC" : "-fPIC";
|
||||
Error(ctx) << isec << ": " << rel << " relocation at offset 0x"
|
||||
<< std::hex << rel.r_offset << " against symbol `"
|
||||
<< sym << "' can not be used; recompile with " << msg;
|
||||
};
|
||||
|
||||
auto check_textrel = [&] {
|
||||
if (!writable) {
|
||||
if (ctx.arg.z_text) {
|
||||
error();
|
||||
} else if (ctx.arg.warn_textrel) {
|
||||
Warn(ctx) << isec << ": relocation against symbol `" << sym
|
||||
<< "' in read-only section";
|
||||
}
|
||||
ctx.has_textrel = true;
|
||||
}
|
||||
};
|
||||
|
||||
auto copyrel = [&] {
|
||||
assert(sym.is_imported);
|
||||
if (sym.esym().st_visibility == STV_PROTECTED) {
|
||||
Error(ctx) << isec
|
||||
<< ": cannot make copy relocation for protected symbol '" << sym
|
||||
<< "', defined in " << *sym.file << "; recompile with -fPIC";
|
||||
}
|
||||
sym.flags |= NEEDS_COPYREL;
|
||||
};
|
||||
|
||||
auto dynrel = [&] {
|
||||
check_textrel();
|
||||
isec.file.num_dynrel++;
|
||||
};
|
||||
|
||||
switch (action) {
|
||||
case NONE:
|
||||
break;
|
||||
case ERROR:
|
||||
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>
|
||||
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 static Action table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, PLT }, // Shared object
|
||||
{ ERROR, NONE, COPYREL, PLT }, // Position-independent exec
|
||||
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
|
||||
};
|
||||
|
||||
return get_rel_action(ctx, sym, table);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
// This is a decision table for absolute relocations that is smaller
|
||||
// than the 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 (isec.is_relr_reloc(ctx, rel)) {
|
||||
*(Word<E> *)loc = S + A;
|
||||
} else {
|
||||
*dynrel++ = ElfRel<E>(P, E::R_RELATIVE, 0, S + A);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = S + A;
|
||||
}
|
||||
break;
|
||||
case DYN_COPYREL:
|
||||
if (writable || !ctx.arg.z_copyreloc)
|
||||
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:
|
||||
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)
|
||||
return;
|
||||
|
||||
// Copy data
|
||||
if constexpr (is_riscv<E>) {
|
||||
copy_contents_riscv(ctx, buf);
|
||||
} else {
|
||||
uncompress_to(ctx, buf);
|
||||
}
|
||||
|
||||
// Apply relocations
|
||||
if (!ctx.arg.relocatable) {
|
||||
if (shdr().sh_flags & SHF_ALLOC)
|
||||
apply_reloc_alloc(ctx, buf);
|
||||
else
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the name of a function containin a given offset.
|
||||
template <typename E>
|
||||
std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) {
|
||||
for (const ElfSym<E> &esym : file.elf_syms) {
|
||||
if (esym.st_shndx == shndx && esym.st_type == STT_FUNC &&
|
||||
esym.st_value <= offset && offset < esym.st_value + esym.st_size) {
|
||||
std::string_view name = file.symbol_strtab.data() + esym.st_name;
|
||||
if (ctx.arg.demangle)
|
||||
return demangle(name);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Record an undefined symbol error which will be displayed all at
|
||||
// once by report_undef_errors().
|
||||
template <typename E>
|
||||
void InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel) {
|
||||
std::stringstream ss;
|
||||
if (std::string_view source = file.get_source_name(); !source.empty())
|
||||
ss << ">>> referenced by " << source << "\n";
|
||||
else
|
||||
ss << ">>> referenced by " << *this << "\n";
|
||||
|
||||
ss << ">>> " << file;
|
||||
if (std::string_view func = get_func_name(ctx, rel.r_offset); !func.empty())
|
||||
ss << ":(" << func << ")";
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
typename decltype(ctx.undef_errors)::accessor acc;
|
||||
ctx.undef_errors.insert(acc, {sym.name(), {}});
|
||||
acc->second.push_back(ss.str());
|
||||
}
|
||||
|
||||
// Report all undefined symbols, grouped by symbol.
|
||||
template <typename E>
|
||||
void report_undef_errors(Context<E> &ctx) {
|
||||
constexpr i64 max_errors = 3;
|
||||
|
||||
for (auto &pair : ctx.undef_errors) {
|
||||
std::string_view sym_name = pair.first;
|
||||
std::span<std::string> errors = pair.second;
|
||||
|
||||
if (ctx.arg.demangle)
|
||||
sym_name = demangle(sym_name);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "undefined symbol: " << sym_name << "\n";
|
||||
|
||||
for (i64 i = 0; i < errors.size() && i < max_errors; i++)
|
||||
ss << errors[i];
|
||||
|
||||
if (errors.size() > max_errors)
|
||||
ss << ">>> referenced " << (errors.size() - max_errors) << " more times\n";
|
||||
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
|
||||
Error(ctx) << ss.str();
|
||||
else if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
|
||||
Warn(ctx) << ss.str();
|
||||
}
|
||||
|
||||
ctx.checkpoint();
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template struct CieRecord<E>;
|
||||
template class InputSection<E>;
|
||||
template void report_undef_errors(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -8,8 +8,13 @@
|
||||
#include <cctype>
|
||||
#include <iomanip>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
static thread_local MemoryMappedFile<E> *current_file;
|
||||
static thread_local MappedFile<Context<E>> *current_file;
|
||||
|
||||
template <typename E>
|
||||
void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok);
|
||||
|
||||
static std::string_view get_line(std::string_view input, const char *pos) {
|
||||
assert(input.data() <= pos);
|
||||
@@ -32,7 +37,7 @@ template <typename E>
|
||||
class SyntaxError {
|
||||
public:
|
||||
SyntaxError(Context<E> &ctx, std::string_view errpos) : out(ctx) {
|
||||
std::string_view contents = current_file<E>->get_contents(ctx);
|
||||
std::string_view contents = current_file<E>->get_contents();
|
||||
std::string_view line = get_line(contents, errpos.data());
|
||||
|
||||
i64 lineno = 1;
|
||||
@@ -56,7 +61,7 @@ public:
|
||||
|
||||
[[noreturn]] ~SyntaxError() = default;
|
||||
|
||||
Fatal<E> out;
|
||||
Fatal<Context<E>> out;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -140,27 +145,41 @@ read_output_format(Context<E> &ctx, std::span<std::string_view> tok) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static MemoryMappedFile<E> *resolve_path(Context<E> &ctx, std::string_view tok) {
|
||||
static bool is_in_sysroot(Context<E> &ctx, std::string path) {
|
||||
std::string rel = to_abs_path(path)
|
||||
.lexically_relative(to_abs_path(ctx.arg.sysroot))
|
||||
.string();
|
||||
return rel != "." && !rel.starts_with("../");
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static MappedFile<Context<E>> *resolve_path(Context<E> &ctx, std::string_view tok) {
|
||||
std::string str(unquote(tok));
|
||||
|
||||
if (str.starts_with("/"))
|
||||
return MemoryMappedFile<E>::must_open(ctx, ctx.arg.sysroot + str);
|
||||
// GNU ld prepends the sysroot if a pathname starts with '/' and the
|
||||
// script being processed is in the sysroot. We do the same.
|
||||
if (str.starts_with('/') && is_in_sysroot(ctx, current_file<E>->name))
|
||||
return MappedFile<Context<E>>::must_open(ctx, ctx.arg.sysroot + str);
|
||||
|
||||
if (str.starts_with('=')) {
|
||||
std::string path;
|
||||
if (ctx.arg.sysroot.empty())
|
||||
path = str.substr(1);
|
||||
else
|
||||
path = ctx.arg.sysroot + str.substr(1);
|
||||
return MappedFile<Context<E>>::must_open(ctx, path);
|
||||
}
|
||||
|
||||
if (str.starts_with("-l"))
|
||||
return find_library(ctx, str.substr(2));
|
||||
|
||||
if (std::string dir(path_dirname(current_file<E>->name));
|
||||
MemoryMappedFile<E> *mb = MemoryMappedFile<E>::open(ctx, dir + "/" + str))
|
||||
return mb;
|
||||
|
||||
if (MemoryMappedFile<E> *mb = MemoryMappedFile<E>::open(ctx, str))
|
||||
return mb;
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, str))
|
||||
return mf;
|
||||
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string root = dir.starts_with("/") ? ctx.arg.sysroot : "";
|
||||
std::string path = root + std::string(dir) + "/" + str;
|
||||
if (MemoryMappedFile<E> *mb = MemoryMappedFile<E>::open(ctx, path))
|
||||
return mb;
|
||||
std::string path = std::string(dir) + "/" + str;
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, path))
|
||||
return mf;
|
||||
}
|
||||
|
||||
SyntaxError(ctx, tok) << "library not found: " << str;
|
||||
@@ -180,8 +199,8 @@ read_group(Context<E> &ctx, std::span<std::string_view> tok) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MemoryMappedFile<E> *mb = resolve_path(ctx, tok[0]);
|
||||
read_file(ctx, mb);
|
||||
MappedFile<Context<E>> *mf = resolve_path(ctx, tok[0]);
|
||||
read_file(ctx, mf);
|
||||
tok = tok.subspan(1);
|
||||
}
|
||||
|
||||
@@ -191,36 +210,56 @@ read_group(Context<E> &ctx, std::span<std::string_view> tok) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_linker_script(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
current_file<E> = mb;
|
||||
void parse_linker_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mb->get_contents(ctx));
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
|
||||
while (!tok.empty()) {
|
||||
if (tok[0] == "OUTPUT_FORMAT")
|
||||
if (tok[0] == "OUTPUT_FORMAT") {
|
||||
tok = read_output_format(ctx, tok.subspan(1));
|
||||
else if (tok[0] == "INPUT" || tok[0] == "GROUP")
|
||||
} else if (tok[0] == "INPUT" || tok[0] == "GROUP") {
|
||||
tok = read_group(ctx, tok.subspan(1));
|
||||
else
|
||||
SyntaxError(ctx, tok[0]) << "unknown token";
|
||||
} else if (tok[0] == "VERSION") {
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script(ctx, tok);
|
||||
tok = skip(ctx, tok, "}");
|
||||
} else if (tok.size() > 3 && tok[1] == "=" && tok[3] == ";") {
|
||||
ctx.arg.defsyms.emplace_back(get_symbol(ctx, unquote(tok[0])),
|
||||
get_symbol(ctx, unquote(tok[2])));
|
||||
tok = tok.subspan(4);
|
||||
} else if (tok[0] == ";") {
|
||||
tok = tok.subspan(1);
|
||||
} else {
|
||||
SyntaxError(ctx, tok[0]) << "unknown linker script token";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 get_script_output_type(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
current_file<E> = mb;
|
||||
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, mb->get_contents(ctx));
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
|
||||
if (tok.size() >= 3 && tok[0] == "OUTPUT_FORMAT" && tok[1] == "(") {
|
||||
if (tok[2] == "elf64-x86-64")
|
||||
return EM_X86_64;
|
||||
return X86_64::target_name;
|
||||
if (tok[2] == "elf32-i386")
|
||||
return EM_386;
|
||||
return I386::target_name;
|
||||
}
|
||||
return -1;
|
||||
|
||||
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,
|
||||
@@ -238,9 +277,9 @@ static bool read_label(std::span<std::string_view> &tok,
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void parse_version_script_commands(Context<E> &ctx,
|
||||
std::span<std::string_view> &tok,
|
||||
i16 &ver, bool is_extern_cpp) {
|
||||
static void
|
||||
read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
std::string_view ver_str, u16 ver_idx, bool is_cpp) {
|
||||
bool is_global = true;
|
||||
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
@@ -257,26 +296,32 @@ static void parse_version_script_commands(Context<E> &ctx,
|
||||
if (tok[0] == "extern") {
|
||||
tok = tok.subspan(1);
|
||||
|
||||
bool is_cpp;
|
||||
if (!tok.empty() && tok[0] == "\"C\"") {
|
||||
tok = tok.subspan(1);
|
||||
is_cpp = false;
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands( ctx, tok, ver_str, ver_idx, false);
|
||||
} else {
|
||||
tok = skip(ctx, tok, "\"C++\"");
|
||||
is_cpp = true;
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands(ctx, tok, ver_str, ver_idx, true);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "{");
|
||||
parse_version_script_commands(ctx, tok, ver, is_cpp);
|
||||
tok = skip(ctx, tok, "}");
|
||||
tok = skip(ctx, tok, ";");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tok[0] == "*")
|
||||
ctx.arg.default_version = (is_global ? ver : VER_NDX_LOCAL);
|
||||
else
|
||||
ctx.arg.version_patterns.push_back({tok[0], ver, is_extern_cpp});
|
||||
if (tok[0] == "*") {
|
||||
ctx.default_version = (is_global ? ver_idx : (u32)VER_NDX_LOCAL);
|
||||
ctx.default_version_from_version_script = true;
|
||||
} else if (is_global) {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
ver_str, ver_idx, is_cpp});
|
||||
} else {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
ver_str, VER_NDX_LOCAL, is_cpp});
|
||||
}
|
||||
|
||||
tok = tok.subspan(1);
|
||||
|
||||
if (!tok.empty() && tok[0] == "}")
|
||||
@@ -286,63 +331,82 @@ static void parse_version_script_commands(Context<E> &ctx,
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_version_script(Context<E> &ctx, std::string path) {
|
||||
current_file<E> = MemoryMappedFile<E>::must_open(ctx, path);
|
||||
std::vector<std::string_view> vec =
|
||||
tokenize(ctx, current_file<E>->get_contents(ctx));
|
||||
void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok) {
|
||||
u16 next_ver = VER_NDX_LAST_RESERVED + ctx.arg.version_definitions.size() + 1;
|
||||
|
||||
std::span<std::string_view> tok = vec;
|
||||
i16 next_ver = VER_NDX_LAST_RESERVED + 1;
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
std::string_view ver_str;
|
||||
u16 ver_idx;
|
||||
|
||||
while (!tok.empty()) {
|
||||
i16 ver = VER_NDX_GLOBAL;
|
||||
if (tok[0] != "{") {
|
||||
ver = next_ver++;
|
||||
ctx.arg.version_definitions.push_back(tok[0]);
|
||||
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, "{");
|
||||
parse_version_script_commands(ctx, tok, ver, 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);
|
||||
tok = skip(ctx, tok, ";");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_version_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
read_version_script(ctx, tok);
|
||||
if (!tok.empty())
|
||||
SyntaxError(ctx, tok[0]) << "trailing garbage token";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_dynamic_list(Context<E> &ctx, std::string path) {
|
||||
current_file<E> = MemoryMappedFile<E>::must_open(ctx, path);
|
||||
std::vector<std::string_view> vec =
|
||||
tokenize(ctx, current_file<E>->get_contents(ctx));
|
||||
|
||||
std::span<std::string_view> tok = vec;
|
||||
|
||||
tok = skip(ctx, tok, "{");
|
||||
i16 ver = VER_NDX_GLOBAL;
|
||||
|
||||
void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
bool is_cpp) {
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
if (read_label(tok, "global")) {
|
||||
ver = VER_NDX_GLOBAL;
|
||||
continue;
|
||||
}
|
||||
if (tok[0] == "extern") {
|
||||
tok = tok.subspan(1);
|
||||
|
||||
if (read_label(tok, "local")) {
|
||||
ver = VER_NDX_LOCAL;
|
||||
if (!tok.empty() && tok[0] == "\"C\"") {
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, false);
|
||||
} else {
|
||||
tok = skip(ctx, tok, "\"C++\"");
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, true);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "}");
|
||||
tok = skip(ctx, tok, ";");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tok[0] == "*")
|
||||
ctx.arg.default_version = ver;
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.arg.version_patterns.push_back({tok[0], ver, false});
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
"global", VER_NDX_GLOBAL, is_cpp});
|
||||
|
||||
tok = skip(ctx, tok.subspan(1), ";");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_dynamic_list(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, false);
|
||||
tok = skip(ctx, tok, "}");
|
||||
tok = skip(ctx, tok, ";");
|
||||
|
||||
@@ -350,15 +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> &ctx, MemoryMappedFile<E> *mb); \
|
||||
template \
|
||||
i64 get_script_output_type(Context<E> &ctx, MemoryMappedFile<E> *mb); \
|
||||
template \
|
||||
void parse_version_script(Context<E> &ctx, std::string path); \
|
||||
template \
|
||||
void parse_dynamic_list(Context<E> &ctx, std::string path)
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
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
|
||||
@@ -0,0 +1,717 @@
|
||||
// This file handles the linker plugin to support LTO (Link-Time
|
||||
// Optimization).
|
||||
//
|
||||
// LTO is a technique to do whole-program optimization to a program. Since
|
||||
// a linker sees the whole program as opposed to a single compilation
|
||||
// unit, it in theory can do some optimizations that cannot be done in the
|
||||
// usual separate compilation model. For example, LTO should be able to
|
||||
// inline functions that are defined in other compilation unit.
|
||||
//
|
||||
// In GCC and Clang, all you have to do to enable LTO is adding the
|
||||
// `-flto` flag to the compiler and the linker command lines. If `-flto`
|
||||
// is given, the compiler generates a file that contains not machine code
|
||||
// but the compiler's IR (intermediate representation). In GCC, the output
|
||||
// is an ELF file which wraps GCC's IR. In LLVM, it's not even an ELF file
|
||||
// but just a raw LLVM IR file.
|
||||
//
|
||||
// Here is what we have to do if at least one input file is not a usual
|
||||
// ELF file but an IR object file:
|
||||
//
|
||||
// 1. Read symbols both from usual ELF files and from IR object files and
|
||||
// resolve symbols as usual.
|
||||
//
|
||||
// 2. Pass all IR objects to the compiler backend. The compiler backend
|
||||
// compiles the IRs and returns a few big ELF object files as a
|
||||
// result.
|
||||
//
|
||||
// 3. Parse the returned ELF files and overwrite IR object symbols with
|
||||
// the returned ones, discarding IR object files.
|
||||
//
|
||||
// 4. Continue the rest of the linking process as usual.
|
||||
//
|
||||
// When gcc or clang inovkes ld, they pass `-plugin linker-plugin.so` to
|
||||
// the linker. The given .so file provides a way to call the compiler
|
||||
// backend.
|
||||
//
|
||||
// The linker plugin API is documented at
|
||||
// https://gcc.gnu.org/wiki/whopr/driver, though the document is a bit
|
||||
// outdated.
|
||||
//
|
||||
// Frankly, the linker plugin API is peculiar and is not very easy to use.
|
||||
// For some reason, the API functions don't return the result of a
|
||||
// function call as a return value but instead calls other function with
|
||||
// the result as its argument to "return" the result.
|
||||
//
|
||||
// For example, the first thing you need to do after dlopen()'ing a linker
|
||||
// plugin .so is to call `onload` function with a list of callback
|
||||
// functions. `onload` calls callbacks to notify about the pointers to
|
||||
// other functions the linker plugin provides. I don't know why `onload`
|
||||
// can't just return a list of functions or why the linker plugin can't
|
||||
// define not only `onload` but other functions, but that's what it is.
|
||||
//
|
||||
// Here is the steps to use the linker plugin:
|
||||
//
|
||||
// 1. dlopen() the linker plugin .so and call `onload` to obtain pointers
|
||||
// to other functions provided by the plugin.
|
||||
//
|
||||
// 2. Call `claim_file_hook` with an IR object file to read its symbol
|
||||
// table. `claim_file_hook` calls the `add_symbols` callback to
|
||||
// "return" a list of symbols.
|
||||
//
|
||||
// 3. `claim_file_hook` returns LDPT_OK only when the plugin wants to
|
||||
// handle a given file. Since we pass only IR object files to the
|
||||
// plugin in mold, it always returns LDPT_OK in our case.
|
||||
//
|
||||
// 4. Once we made a decision as to which object file to include into the
|
||||
// output file, we call `all_symbols_read_hook` to compile IR objects
|
||||
// into a few big ELF files. That function calls the `get_symbols`
|
||||
// callback to ask us about the symbol resolution results. (The
|
||||
// compiler backend needs to know whether an undefined symbol in an IR
|
||||
// object was resolved to a regular object file or a shared object to
|
||||
// do whole program optimization, for example.)
|
||||
//
|
||||
// 5. `all_symbols_read_hook` "returns" the result by calling the
|
||||
// `add_input_file` callback. The callback is called with a path to an
|
||||
// LTO'ed ELF file. We parse that ELF file and override symbols
|
||||
// defined by IR objects with the ELF file's ones.
|
||||
//
|
||||
// 6. Lastly, we call `cleanup_hook` to remove temporary files created by
|
||||
// the compiler backend.
|
||||
|
||||
#include "mold.h"
|
||||
#include "lto.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <dlfcn.h>
|
||||
#include <fcntl.h>
|
||||
#include <sstream>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if 0
|
||||
# define LOG std::cerr
|
||||
#else
|
||||
# define LOG std::ostringstream()
|
||||
#endif
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Global variables
|
||||
// We store LTO-related information to global variables,
|
||||
// as the LTO plugin is not thread-safe by design anyway.
|
||||
|
||||
template <typename E> static Context<E> *gctx;
|
||||
template <typename E> static std::vector<ObjectFile<E> *> lto_objects;
|
||||
|
||||
static int phase = 0;
|
||||
static std::vector<PluginSymbol> plugin_symbols;
|
||||
static ClaimFileHandler *claim_file_hook;
|
||||
static AllSymbolsReadHandler *all_symbols_read_hook;
|
||||
static CleanupHandler *cleanup_hook;
|
||||
static bool is_gcc_linker_api_v1 = false;
|
||||
|
||||
// Event handlers
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus message(PluginLevel level, const char *fmt, ...) {
|
||||
LOG << "message\n";
|
||||
Context<E> &ctx = *gctx<E>;
|
||||
|
||||
char buf[1000];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
switch (level) {
|
||||
case LDPL_INFO:
|
||||
SyncOut(ctx) << buf;
|
||||
break;
|
||||
case LDPL_WARNING:
|
||||
Warn(ctx) << buf;
|
||||
break;
|
||||
case LDPL_ERROR:
|
||||
case LDPL_FATAL:
|
||||
Fatal(ctx) << buf;
|
||||
}
|
||||
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus register_claim_file_hook(ClaimFileHandler fn) {
|
||||
LOG << "register_claim_file_hook\n";
|
||||
claim_file_hook = fn;
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
register_all_symbols_read_hook(AllSymbolsReadHandler fn) {
|
||||
LOG << "register_all_symbols_read_hook\n";
|
||||
all_symbols_read_hook = fn;
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus register_cleanup_hook(CleanupHandler fn) {
|
||||
LOG << "register_cleanup_hook\n";
|
||||
cleanup_hook = fn;
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
add_symbols(void *handle, int nsyms, const PluginSymbol *psyms) {
|
||||
LOG << "add_symbols: " << nsyms << "\n";
|
||||
assert(phase == 1);
|
||||
plugin_symbols = {psyms, psyms + nsyms};
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus add_input_file(const char *path) {
|
||||
LOG << "add_input_file: " << path << "\n";
|
||||
|
||||
Context<E> &ctx = *gctx<E>;
|
||||
static i64 file_priority = 100;
|
||||
|
||||
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::must_open(ctx, path);
|
||||
|
||||
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mf, "", false);
|
||||
ctx.obj_pool.emplace_back(file);
|
||||
lto_objects<E>.push_back(file);
|
||||
|
||||
file->priority = file_priority++;
|
||||
file->is_alive = true;
|
||||
file->parse(ctx);
|
||||
file->resolve_symbols(ctx);
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_file(const void *handle, struct PluginInputFile *file) {
|
||||
LOG << "get_input_file\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus release_input_file(const void *handle) {
|
||||
LOG << "release_input_file\n";
|
||||
|
||||
ObjectFile<E> &file = *(ObjectFile<E> *)handle;
|
||||
if (file.mf->fd != -1) {
|
||||
close(file.mf->fd);
|
||||
file.mf->fd = -1;
|
||||
}
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus add_input_library(const char *path) {
|
||||
LOG << "add_input_library\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus set_extra_library_path(const char *path) {
|
||||
LOG << "set_extra_library_path\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus get_view(const void *handle, const void **view) {
|
||||
LOG << "get_view\n";
|
||||
|
||||
ObjectFile<E> &file = *(ObjectFile<E> *)handle;
|
||||
*view = (void *)file.mf->data;
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_count(const void *handle, int *count) {
|
||||
LOG << "get_input_section_count\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_type(const PluginSection section, int *type) {
|
||||
LOG << "get_input_section_type\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_name(const PluginSection section,
|
||||
char **section_name) {
|
||||
LOG << "get_input_section_name\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_contents(const PluginSection section,
|
||||
const char **section_contents,
|
||||
size_t *len) {
|
||||
LOG << "get_input_section_contents\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
update_section_order(const PluginSection *section_list,
|
||||
int num_sections) {
|
||||
LOG << "update_section_order\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus allow_section_ordering() {
|
||||
LOG << "allow_section_ordering\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_symbols_v1(const void *handle, int nsyms, PluginSymbol *psyms) {
|
||||
unreachable();
|
||||
}
|
||||
|
||||
// get_symbols teaches the LTO plugin as to how we 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
|
||||
// that definition within the IR objects and remove the symbol from the
|
||||
// LTO result. On the other hand, if a definition is referenced by a
|
||||
// non-IR object, it has to keep the symbol in the LTO result.
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
get_symbols(const void *handle, int nsyms, PluginSymbol *psyms, bool is_v2) {
|
||||
ObjectFile<E> &file = *(ObjectFile<E> *)handle;
|
||||
assert(file.is_lto_obj);
|
||||
|
||||
// If file is an archive member which was not chose to be included in
|
||||
// to the final result, we need to make the plugin to ignore all
|
||||
// symbols.
|
||||
if (!file.is_alive) {
|
||||
assert(!is_v2);
|
||||
for (int i = 0; i < nsyms; i++)
|
||||
psyms[i].resolution = LDPR_PREEMPTED_REG;
|
||||
return LDPS_NO_SYMS;
|
||||
}
|
||||
|
||||
auto get_resolution = [&](ElfSym<E> &esym, Symbol<E> &sym) {
|
||||
if (!sym.file)
|
||||
return LDPR_UNDEF;
|
||||
|
||||
if (sym.file == &file) {
|
||||
if (sym.referenced_by_regular_obj)
|
||||
return LDPR_PREVAILING_DEF;
|
||||
if (sym.is_exported)
|
||||
return is_v2 ? LDPR_PREVAILING_DEF : LDPR_PREVAILING_DEF_IRONLY_EXP;
|
||||
return LDPR_PREVAILING_DEF_IRONLY;
|
||||
}
|
||||
|
||||
if (sym.file->is_dso)
|
||||
return LDPR_RESOLVED_DYN;
|
||||
|
||||
if (((ObjectFile<E> *)sym.file)->is_lto_obj && !sym.is_wrapped)
|
||||
return esym.is_undef() ? LDPR_RESOLVED_IR : LDPR_PREEMPTED_IR;
|
||||
return esym.is_undef() ? LDPR_RESOLVED_EXEC : LDPR_PREEMPTED_REG;
|
||||
};
|
||||
|
||||
// Set the symbol resolution results to psyms.
|
||||
for (i64 i = 0; i < nsyms; i++) {
|
||||
ElfSym<E> &esym = file.elf_syms[i + 1];
|
||||
Symbol<E> &sym = *file.symbols[i + 1];
|
||||
psyms[i].resolution = get_resolution(esym, sym);
|
||||
}
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
// This function restarts mold itself with `--:lto-pass2` and
|
||||
// `--:ignore-ir-file` flags. We do this as a workaround for the old
|
||||
// linker plugins that do not support the get_symbols_v3 API.
|
||||
//
|
||||
// get_symbols_v1 and get_symbols_v2 don't provide a way to ignore an
|
||||
// object file we previously passed to the linker plugin. So we can't
|
||||
// "unload" object files in archives that we ended up not choosing to
|
||||
// include into the final output.
|
||||
//
|
||||
// As a workaround, we restart the linker with a list of object files
|
||||
// the linker has to ignore, so that it won't read the object files
|
||||
// from archives next time.
|
||||
//
|
||||
// This is an ugly hack and should be removed once GCC adopts the v3 API.
|
||||
template <typename E>
|
||||
static void restart_process(Context<E> &ctx) {
|
||||
std::vector<const char *> args;
|
||||
|
||||
for (std::string_view arg : ctx.cmdline_args)
|
||||
args.push_back(strdup(std::string(arg).c_str()));
|
||||
|
||||
for (std::unique_ptr<ObjectFile<E>> &file : ctx.obj_pool)
|
||||
if (file->is_lto_obj && !file->is_alive)
|
||||
args.push_back(strdup(("--:ignore-ir-file=" +
|
||||
file->mf->get_identifier()).c_str()));
|
||||
|
||||
args.push_back("--:lto-pass2");
|
||||
args.push_back(nullptr);
|
||||
|
||||
std::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);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
get_symbols_v2(const void *handle, int nsyms, PluginSymbol *psyms) {
|
||||
LOG << "get_symbols_v2\n";
|
||||
return get_symbols<E>(handle, nsyms, psyms, true);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
get_symbols_v3(const void *handle, int nsyms, PluginSymbol *psyms) {
|
||||
LOG << "get_symbols_v3\n";
|
||||
return get_symbols<E>(handle, nsyms, psyms, false);
|
||||
}
|
||||
|
||||
static PluginStatus allow_unique_segment_for_sections() {
|
||||
LOG << "allow_unique_segment_for_sections\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
unique_segment_for_sections(const char *segment_name,
|
||||
uint64_t flags,
|
||||
uint64_t align,
|
||||
const PluginSection *section_list,
|
||||
int num_sections) {
|
||||
LOG << "unique_segment_for_sections\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_alignment(const PluginSection section,
|
||||
int *addralign) {
|
||||
LOG << "get_input_section_alignment\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_input_section_size(const PluginSection section, uint64_t *size) {
|
||||
LOG << "get_input_section_size\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
register_new_input_hook(NewInputHandler fn) {
|
||||
LOG << "register_new_input_hook\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
static PluginStatus
|
||||
get_wrap_symbols(uint64_t *num_symbols, const char ***wrap_symbols) {
|
||||
LOG << "get_wrap_symbols\n";
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginLinkerAPIVersion
|
||||
get_api_version(const char *plugin_identifier,
|
||||
unsigned plugin_version,
|
||||
int minimal_api_supported,
|
||||
int maximal_api_supported,
|
||||
const char **linker_identifier,
|
||||
const char **linker_version) {
|
||||
if (LAPI_V1 < minimal_api_supported)
|
||||
Fatal(*gctx<E>) << "LTO plugin does not support V0 or V1 API";
|
||||
|
||||
std::string version = mold_version + "\0"s;
|
||||
|
||||
*linker_identifier = "mold";
|
||||
*linker_version = version.data();
|
||||
|
||||
if (LAPI_V1 <= maximal_api_supported) {
|
||||
is_gcc_linker_api_v1 = true;
|
||||
return LAPI_V1;
|
||||
}
|
||||
return LAPI_V0;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void load_plugin(Context<E> &ctx) {
|
||||
assert(phase == 0);
|
||||
phase = 1;
|
||||
gctx<E> = &ctx;
|
||||
|
||||
void *handle = dlopen(ctx.arg.plugin.c_str(), RTLD_NOW | RTLD_GLOBAL);
|
||||
if (!handle)
|
||||
Fatal(ctx) << "could not open plugin file: " << dlerror();
|
||||
|
||||
OnloadFn *onload = (OnloadFn *)dlsym(handle, "onload");
|
||||
if (!onload)
|
||||
Fatal(ctx) << "failed to load plugin " << ctx.arg.plugin << ": "
|
||||
<< dlerror();
|
||||
|
||||
auto save = [&](std::string_view str) {
|
||||
return save_string(ctx, std::string(str).c_str()).data();
|
||||
};
|
||||
|
||||
std::vector<PluginTagValue> tv;
|
||||
tv.emplace_back(LDPT_MESSAGE, message<E>);
|
||||
|
||||
if (ctx.arg.shared)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_DYN);
|
||||
else if (ctx.arg.pie)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_PIE);
|
||||
else
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_EXEC);
|
||||
|
||||
for (std::string_view opt : ctx.arg.plugin_opt)
|
||||
tv.emplace_back(LDPT_OPTION, save(opt));
|
||||
|
||||
tv.emplace_back(LDPT_REGISTER_CLAIM_FILE_HOOK, register_claim_file_hook<E>);
|
||||
tv.emplace_back(LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK,
|
||||
register_all_symbols_read_hook<E>);
|
||||
tv.emplace_back(LDPT_REGISTER_CLEANUP_HOOK, register_cleanup_hook<E>);
|
||||
tv.emplace_back(LDPT_ADD_SYMBOLS, add_symbols);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS, get_symbols_v1);
|
||||
tv.emplace_back(LDPT_ADD_INPUT_FILE, add_input_file<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_FILE, get_input_file);
|
||||
tv.emplace_back(LDPT_RELEASE_INPUT_FILE, release_input_file<E>);
|
||||
tv.emplace_back(LDPT_ADD_INPUT_LIBRARY, add_input_library);
|
||||
tv.emplace_back(LDPT_OUTPUT_NAME, save(ctx.arg.output));
|
||||
tv.emplace_back(LDPT_SET_EXTRA_LIBRARY_PATH, set_extra_library_path);
|
||||
tv.emplace_back(LDPT_GET_VIEW, get_view<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_COUNT, get_input_section_count);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_TYPE, get_input_section_type);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_NAME, get_input_section_name);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_CONTENTS, get_input_section_contents);
|
||||
tv.emplace_back(LDPT_UPDATE_SECTION_ORDER, update_section_order);
|
||||
tv.emplace_back(LDPT_ALLOW_SECTION_ORDERING, allow_section_ordering);
|
||||
tv.emplace_back(LDPT_ADD_SYMBOLS_V2, add_symbols);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V2, get_symbols_v2<E>);
|
||||
tv.emplace_back(LDPT_ALLOW_UNIQUE_SEGMENT_FOR_SECTIONS,
|
||||
allow_unique_segment_for_sections);
|
||||
tv.emplace_back(LDPT_UNIQUE_SEGMENT_FOR_SECTIONS, unique_segment_for_sections);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V3, get_symbols_v3<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_ALIGNMENT, get_input_section_alignment);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_SIZE, get_input_section_size);
|
||||
tv.emplace_back(LDPT_REGISTER_NEW_INPUT_HOOK, register_new_input_hook<E>);
|
||||
tv.emplace_back(LDPT_GET_WRAP_SYMBOLS, get_wrap_symbols);
|
||||
tv.emplace_back(LDPT_GET_API_VERSION, get_api_version<E>);
|
||||
tv.emplace_back(LDPT_NULL, 0);
|
||||
|
||||
[[maybe_unused]] PluginStatus status = onload(tv.data());
|
||||
assert(status == LDPS_OK);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static ElfSym<E> to_elf_sym(PluginSymbol &psym) {
|
||||
ElfSym<E> esym;
|
||||
memset(&esym, 0, sizeof(esym));
|
||||
|
||||
switch (psym.def) {
|
||||
case LDPK_DEF:
|
||||
esym.st_shndx = SHN_ABS;
|
||||
break;
|
||||
case LDPK_WEAKDEF:
|
||||
esym.st_shndx = SHN_ABS;
|
||||
esym.st_bind = STB_WEAK;
|
||||
break;
|
||||
case LDPK_UNDEF:
|
||||
esym.st_shndx = SHN_UNDEF;
|
||||
break;
|
||||
case LDPK_WEAKUNDEF:
|
||||
esym.st_shndx = SHN_UNDEF;
|
||||
esym.st_bind = STB_WEAK;
|
||||
break;
|
||||
case LDPK_COMMON:
|
||||
esym.st_shndx = SHN_COMMON;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (psym.symbol_type) {
|
||||
case LDST_UNKNOWN:
|
||||
break;
|
||||
case LDST_FUNCTION:
|
||||
esym.st_type = STT_FUNC;
|
||||
break;
|
||||
case LDST_VARIABLE:
|
||||
esym.st_type = STT_OBJECT;
|
||||
break;
|
||||
};
|
||||
|
||||
switch (psym.visibility) {
|
||||
case LDPV_DEFAULT:
|
||||
break;
|
||||
case LDPV_PROTECTED:
|
||||
esym.st_visibility = STV_PROTECTED;
|
||||
break;
|
||||
case LDPV_INTERNAL:
|
||||
esym.st_visibility = STV_INTERNAL;
|
||||
break;
|
||||
case LDPV_HIDDEN:
|
||||
esym.st_visibility = STV_HIDDEN;
|
||||
break;
|
||||
}
|
||||
|
||||
esym.st_size = psym.size;
|
||||
return esym;
|
||||
}
|
||||
|
||||
// Returns true if a given linker plugin looks like LLVM's one.
|
||||
// Returns false if it's GCC.
|
||||
template <typename E>
|
||||
static bool is_llvm(Context<E> &ctx) {
|
||||
return ctx.arg.plugin.ends_with("LLVMgold.so");
|
||||
}
|
||||
|
||||
// Returns true if a given linker plugin supports the get_symbols_v3 API.
|
||||
// Any version of LLVM and GCC 12 or newer support it.
|
||||
template <typename E>
|
||||
static bool supports_v3_api(Context<E> &ctx) {
|
||||
return is_gcc_linker_api_v1 || is_llvm(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
// V0 API's claim_file is not thread-safe.
|
||||
static std::mutex mu;
|
||||
std::unique_lock lock(mu, std::defer_lock);
|
||||
if (!is_gcc_linker_api_v1)
|
||||
lock.lock();
|
||||
|
||||
if (ctx.arg.plugin.empty())
|
||||
Fatal(ctx) << mf->name << ": don't know how to handle this LTO object file "
|
||||
<< "because no -plugin option was given. Please make sure you "
|
||||
<< "added -flto not only for creating object files but also for "
|
||||
<< "creating the final executable.";
|
||||
|
||||
// dlopen the linker plugin file
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [&] { load_plugin(ctx); });
|
||||
|
||||
// Create mold's object instance
|
||||
ObjectFile<E> *obj = new ObjectFile<E>;
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
|
||||
obj->filename = mf->name;
|
||||
obj->symbols.push_back(new Symbol<E>);
|
||||
obj->first_global = 1;
|
||||
obj->is_lto_obj = true;
|
||||
obj->mf = mf;
|
||||
|
||||
// Create plugin's object instance
|
||||
PluginInputFile file = {};
|
||||
|
||||
MappedFile<Context<E>> *mf2 = mf->parent ? mf->parent : mf;
|
||||
file.name = save_string(ctx, mf2->name).data();
|
||||
if (mf2->fd == -1)
|
||||
mf2->fd = open(file.name, O_RDONLY);
|
||||
file.fd = mf2->fd;
|
||||
if (file.fd == -1)
|
||||
Fatal(ctx) << "cannot open " << file.name << ": " << errno_string();
|
||||
|
||||
if (mf->parent)
|
||||
obj->archive_name = mf->parent->name;
|
||||
|
||||
file.offset = mf->get_offset();
|
||||
file.filesize = mf->size;
|
||||
file.handle = (void *)obj;
|
||||
|
||||
LOG << "read_lto_symbols: "<< mf->name << "\n";
|
||||
|
||||
// claim_file_hook() calls add_symbols() which initializes `plugin_symbols`
|
||||
int claimed = false;
|
||||
claim_file_hook(&file, &claimed);
|
||||
if (!claimed)
|
||||
Fatal(ctx) << mf->name << ": not claimed by the LTO plugin;"
|
||||
<< " please make sure you are using the same compiler of the"
|
||||
<< " same version for all object files";
|
||||
|
||||
// It looks like GCC doesn't need fd after claim_file_hook() while
|
||||
// LLVM needs it and takes the ownership of fd. To prevent "too many
|
||||
// open files" issue, we close fd only for GCC. This is ugly, though.
|
||||
if (!is_llvm(ctx)) {
|
||||
close(mf2->fd);
|
||||
mf2->fd = -1;
|
||||
}
|
||||
|
||||
// Initialize object symbols
|
||||
std::vector<ElfSym<E>> *esyms = new std::vector<ElfSym<E>>(1);
|
||||
|
||||
for (PluginSymbol &psym : plugin_symbols) {
|
||||
esyms->push_back(to_elf_sym<E>(psym));
|
||||
obj->symbols.push_back(get_symbol(ctx, save_string(ctx, psym.name)));
|
||||
}
|
||||
|
||||
obj->elf_syms = *esyms;
|
||||
obj->symvers.resize(esyms->size());
|
||||
plugin_symbols.clear();
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Entry point
|
||||
template <typename E>
|
||||
std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
Timer t(ctx, "do_lto");
|
||||
|
||||
if (!ctx.arg.lto_pass2 && !supports_v3_api(ctx))
|
||||
restart_process(ctx);
|
||||
|
||||
assert(phase == 1);
|
||||
phase = 2;
|
||||
|
||||
// Set `referenced_by_regular_obj` bit.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
if (file->is_lto_obj)
|
||||
return;
|
||||
|
||||
for (i64 i = file->first_global; i < file->symbols.size(); i++) {
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
|
||||
if (sym.file && !sym.file->is_dso &&
|
||||
((ObjectFile<E> *)sym.file)->is_lto_obj) {
|
||||
std::scoped_lock lock(sym.mu);
|
||||
sym.referenced_by_regular_obj = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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)
|
||||
Fatal(ctx) << "LTO: all_symbols_read_hook returns " << st;
|
||||
|
||||
return lto_objects<E>;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void lto_cleanup(Context<E> &ctx) {
|
||||
Timer t(ctx, "lto_cleanup");
|
||||
|
||||
if (cleanup_hook)
|
||||
cleanup_hook();
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile<Context<E>> *);
|
||||
template std::vector<ObjectFile<E> *> do_lto(Context<E> &);
|
||||
template void lto_cleanup(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "mold.h"
|
||||
#include "lto.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
Fatal(ctx) << "LTO is not supported on Windows";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void lto_cleanup(Context<E> &ctx) {}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile<Context<E>> *);
|
||||
template std::vector<ObjectFile<E> *> do_lto(Context<E> &);
|
||||
template void lto_cleanup(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,5 @@
|
||||
#ifdef _WIN32
|
||||
# include "lto-win32.cc"
|
||||
#else
|
||||
# include "lto-unix.cc"
|
||||
#endif
|
||||
@@ -0,0 +1,166 @@
|
||||
#pragma once
|
||||
|
||||
#include "../integers.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
enum PluginStatus {
|
||||
LDPS_OK,
|
||||
LDPS_NO_SYMS,
|
||||
LDPS_BAD_HANDLE,
|
||||
LDPS_ERR,
|
||||
};
|
||||
|
||||
enum PluginTag {
|
||||
LDPT_NULL,
|
||||
LDPT_API_VERSION,
|
||||
LDPT_GOLD_VERSION,
|
||||
LDPT_LINKER_OUTPUT,
|
||||
LDPT_OPTION,
|
||||
LDPT_REGISTER_CLAIM_FILE_HOOK,
|
||||
LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK,
|
||||
LDPT_REGISTER_CLEANUP_HOOK,
|
||||
LDPT_ADD_SYMBOLS,
|
||||
LDPT_GET_SYMBOLS,
|
||||
LDPT_ADD_INPUT_FILE,
|
||||
LDPT_MESSAGE,
|
||||
LDPT_GET_INPUT_FILE,
|
||||
LDPT_RELEASE_INPUT_FILE,
|
||||
LDPT_ADD_INPUT_LIBRARY,
|
||||
LDPT_OUTPUT_NAME,
|
||||
LDPT_SET_EXTRA_LIBRARY_PATH,
|
||||
LDPT_GNU_LD_VERSION,
|
||||
LDPT_GET_VIEW,
|
||||
LDPT_GET_INPUT_SECTION_COUNT,
|
||||
LDPT_GET_INPUT_SECTION_TYPE,
|
||||
LDPT_GET_INPUT_SECTION_NAME,
|
||||
LDPT_GET_INPUT_SECTION_CONTENTS,
|
||||
LDPT_UPDATE_SECTION_ORDER,
|
||||
LDPT_ALLOW_SECTION_ORDERING,
|
||||
LDPT_GET_SYMBOLS_V2,
|
||||
LDPT_ALLOW_UNIQUE_SEGMENT_FOR_SECTIONS,
|
||||
LDPT_UNIQUE_SEGMENT_FOR_SECTIONS,
|
||||
LDPT_GET_SYMBOLS_V3,
|
||||
LDPT_GET_INPUT_SECTION_ALIGNMENT,
|
||||
LDPT_GET_INPUT_SECTION_SIZE,
|
||||
LDPT_REGISTER_NEW_INPUT_HOOK,
|
||||
LDPT_GET_WRAP_SYMBOLS,
|
||||
LDPT_ADD_SYMBOLS_V2,
|
||||
LDPT_GET_API_VERSION,
|
||||
};
|
||||
|
||||
enum PluginApiVersion {
|
||||
LD_PLUGIN_API_VERSION = 1,
|
||||
};
|
||||
|
||||
struct PluginTagValue {
|
||||
PluginTagValue(PluginTag tag, int val) : tag(tag), val(val) {}
|
||||
|
||||
template <typename T>
|
||||
PluginTagValue(PluginTag tag, T *ptr) : tag(tag), ptr((void *)ptr) {}
|
||||
|
||||
PluginTag tag;
|
||||
union {
|
||||
int val;
|
||||
void *ptr;
|
||||
};
|
||||
};
|
||||
|
||||
enum PluginOutputFileType {
|
||||
LDPO_REL,
|
||||
LDPO_EXEC,
|
||||
LDPO_DYN,
|
||||
LDPO_PIE,
|
||||
};
|
||||
|
||||
struct PluginInputFile {
|
||||
const char *name;
|
||||
i32 fd;
|
||||
u64 offset;
|
||||
u64 filesize;
|
||||
void *handle;
|
||||
};
|
||||
|
||||
struct PluginSection {
|
||||
const void *handle;
|
||||
u32 shndx;
|
||||
};
|
||||
|
||||
struct PluginSymbol {
|
||||
char *name;
|
||||
char *version;
|
||||
#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;
|
||||
i32 resolution;
|
||||
};
|
||||
|
||||
enum PluginSymbolKind {
|
||||
LDPK_DEF,
|
||||
LDPK_WEAKDEF,
|
||||
LDPK_UNDEF,
|
||||
LDPK_WEAKUNDEF,
|
||||
LDPK_COMMON,
|
||||
};
|
||||
|
||||
enum PluginSymbolVisibility {
|
||||
LDPV_DEFAULT,
|
||||
LDPV_PROTECTED,
|
||||
LDPV_INTERNAL,
|
||||
LDPV_HIDDEN,
|
||||
};
|
||||
|
||||
enum PluginSymbolType {
|
||||
LDST_UNKNOWN,
|
||||
LDST_FUNCTION,
|
||||
LDST_VARIABLE,
|
||||
};
|
||||
|
||||
enum PluginSymbolSectionKind {
|
||||
LDSSK_DEFAULT,
|
||||
LDSSK_BSS,
|
||||
};
|
||||
|
||||
enum PluginSymbolResolution {
|
||||
LDPR_UNKNOWN,
|
||||
LDPR_UNDEF,
|
||||
LDPR_PREVAILING_DEF,
|
||||
LDPR_PREVAILING_DEF_IRONLY,
|
||||
LDPR_PREEMPTED_REG,
|
||||
LDPR_PREEMPTED_IR,
|
||||
LDPR_RESOLVED_IR,
|
||||
LDPR_RESOLVED_EXEC,
|
||||
LDPR_RESOLVED_DYN,
|
||||
LDPR_PREVAILING_DEF_IRONLY_EXP,
|
||||
};
|
||||
|
||||
enum PluginLevel {
|
||||
LDPL_INFO,
|
||||
LDPL_WARNING,
|
||||
LDPL_ERROR,
|
||||
LDPL_FATAL,
|
||||
};
|
||||
|
||||
enum PluginLinkerAPIVersion {
|
||||
LAPI_V0,
|
||||
LAPI_V1,
|
||||
};
|
||||
|
||||
typedef PluginStatus OnloadFn(PluginTagValue *tv);
|
||||
typedef PluginStatus ClaimFileHandler(const PluginInputFile *, int *);
|
||||
typedef PluginStatus AllSymbolsReadHandler();
|
||||
typedef PluginStatus CleanupHandler();
|
||||
typedef PluginStatus NewInputHandler(const PluginInputFile *);
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,778 @@
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
#include "../cmdline.h"
|
||||
#include "../output-file.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <regex>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <tbb/global_control.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <unordered_set>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <direct.h>
|
||||
# define _chdir chdir
|
||||
#else
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Read the beginning of a given file and returns its machine type
|
||||
// (e.g. EM_X86_64 or EM_386).
|
||||
template <typename E>
|
||||
std::string_view get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
auto get_elf_type = [&](u8 *buf) -> std::string_view {
|
||||
bool is_le = (((ElfEhdr<I386> *)buf)->e_ident[EI_DATA] == ELFDATA2LSB);
|
||||
bool is_64;
|
||||
u32 e_machine;
|
||||
|
||||
if (is_le) {
|
||||
auto &ehdr = *(ElfEhdr<I386> *)buf;
|
||||
is_64 = (ehdr.e_ident[EI_CLASS] == ELFCLASS64);
|
||||
e_machine = ehdr.e_machine;
|
||||
} else {
|
||||
auto &ehdr = *(ElfEhdr<M68K> *)buf;
|
||||
is_64 = (ehdr.e_ident[EI_CLASS] == ELFCLASS64);
|
||||
e_machine = ehdr.e_machine;
|
||||
}
|
||||
|
||||
switch (e_machine) {
|
||||
case EM_386:
|
||||
return I386::target_name;
|
||||
case EM_X86_64:
|
||||
return X86_64::target_name;
|
||||
case EM_ARM:
|
||||
return ARM32::target_name;
|
||||
case EM_AARCH64:
|
||||
return ARM64::target_name;
|
||||
case EM_RISCV:
|
||||
if (is_le)
|
||||
return is_64 ? RV64LE::target_name : RV32LE::target_name;
|
||||
return is_64 ? RV64BE::target_name : RV32BE::target_name;
|
||||
case EM_PPC:
|
||||
return PPC32::target_name;
|
||||
case EM_PPC64:
|
||||
return is_le ? PPC64V2::target_name : PPC64V1::target_name;
|
||||
case EM_S390X:
|
||||
return S390X::target_name;
|
||||
case EM_SPARC64:
|
||||
return SPARC64::target_name;
|
||||
case EM_68K:
|
||||
return M68K::target_name;
|
||||
case EM_SH:
|
||||
return SH4::target_name;
|
||||
case EM_ALPHA:
|
||||
return ALPHA::target_name;
|
||||
case EM_PARISC:
|
||||
return HPPA32::target_name;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
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, opt_plugin) == FileType::ELF_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return "";
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_thin_archive_members(ctx, mf))
|
||||
if (get_file_type(child, opt_plugin) == FileType::ELF_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return "";
|
||||
case FileType::TEXT:
|
||||
return get_script_output_type(ctx, mf);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void
|
||||
check_file_compatibility(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
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 " << target;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static ObjectFile<E> *new_object_file(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
static Counter count("parsed_objs");
|
||||
count++;
|
||||
|
||||
check_file_compatibility(ctx, mf);
|
||||
|
||||
bool in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
|
||||
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mf, archive_name, in_lib);
|
||||
file->priority = ctx.file_priority++;
|
||||
ctx.tg.run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static ObjectFile<E> *new_lto_obj(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
static Counter count("parsed_lto_objs");
|
||||
count++;
|
||||
|
||||
if (ctx.arg.ignore_ir_file.count(mf->get_identifier()))
|
||||
return nullptr;
|
||||
|
||||
ObjectFile<E> *file = read_lto_object(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
file->archive_name = archive_name;
|
||||
file->is_in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
|
||||
file->is_alive = !file->is_in_lib;
|
||||
ctx.has_lto_object = true;
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static SharedFile<E> *
|
||||
new_shared_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
check_file_compatibility(ctx, mf);
|
||||
|
||||
SharedFile<E> *file = SharedFile<E>::create(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
ctx.tg.run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
if (ctx.visited.contains(mf->name))
|
||||
return;
|
||||
|
||||
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, ""));
|
||||
return;
|
||||
case FileType::ELF_DSO:
|
||||
ctx.dsos.push_back(new_shared_file(ctx, mf));
|
||||
ctx.visited.insert(mf->name);
|
||||
return;
|
||||
case FileType::AR:
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf)) {
|
||||
switch (get_file_type(child, opt_plugin)) {
|
||||
case FileType::ELF_OBJ:
|
||||
ctx.objs.push_back(new_object_file(ctx, child, mf->name));
|
||||
break;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
if (ObjectFile<E> *file = new_lto_obj(ctx, child, mf->name))
|
||||
ctx.objs.push_back(file);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
ctx.visited.insert(mf->name);
|
||||
return;
|
||||
case FileType::TEXT:
|
||||
parse_linker_script(ctx, mf);
|
||||
return;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
if (ObjectFile<E> *file = new_lto_obj(ctx, mf, ""))
|
||||
ctx.objs.push_back(file);
|
||||
return;
|
||||
default:
|
||||
Fatal(ctx) << mf->name << ": unknown file type";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::string_view
|
||||
deduce_machine_type(Context<E> &ctx, std::span<std::string> args) {
|
||||
for (std::string_view arg : args)
|
||||
if (!arg.starts_with('-'))
|
||||
if (auto *mf = MappedFile<Context<E>>::open(ctx, std::string(arg)))
|
||||
if (std::string_view target = get_machine_type(ctx, mf);
|
||||
!target.empty())
|
||||
return target;
|
||||
Fatal(ctx) << "-m option is missing";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *open_library(Context<E> &ctx, std::string path) {
|
||||
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, path);
|
||||
if (!mf)
|
||||
return nullptr;
|
||||
|
||||
std::string_view target = get_machine_type(ctx, mf);
|
||||
if (target.empty() || target == E::target_name)
|
||||
return mf;
|
||||
Warn(ctx) << path << ": skipping incompatible file " << target
|
||||
<< " " << (int)E::e_machine;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *find_library(Context<E> &ctx, std::string name) {
|
||||
if (name.starts_with(':')) {
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string path = std::string(dir) + "/" + name.substr(1);
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, path))
|
||||
return mf;
|
||||
}
|
||||
Fatal(ctx) << "library not found: " << name;
|
||||
}
|
||||
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string stem = std::string(dir) + "/lib" + name;
|
||||
if (!ctx.is_static)
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, stem + ".so"))
|
||||
return mf;
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, stem + ".a"))
|
||||
return mf;
|
||||
}
|
||||
Fatal(ctx) << "library not found: " << name;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *find_from_search_paths(Context<E> &ctx, std::string name) {
|
||||
if (MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, name))
|
||||
return mf;
|
||||
|
||||
for (std::string_view dir : ctx.arg.library_paths)
|
||||
if (MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::open(ctx, std::string(dir) + "/" + name))
|
||||
return mf;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
Timer t(ctx, "read_input_files");
|
||||
|
||||
std::vector<std::tuple<bool, bool, bool, bool>> state;
|
||||
ctx.is_static = ctx.arg.is_static;
|
||||
|
||||
while (!args.empty()) {
|
||||
std::string_view arg = args[0];
|
||||
args = args.subspan(1);
|
||||
|
||||
if (arg == "--as-needed") {
|
||||
ctx.as_needed = true;
|
||||
} else if (arg == "--no-as-needed") {
|
||||
ctx.as_needed = false;
|
||||
} else if (arg == "--whole-archive") {
|
||||
ctx.whole_archive = true;
|
||||
} else if (arg == "--no-whole-archive") {
|
||||
ctx.whole_archive = false;
|
||||
} else if (arg == "--Bstatic") {
|
||||
ctx.is_static = true;
|
||||
} else if (arg == "--Bdynamic") {
|
||||
ctx.is_static = false;
|
||||
} else if (arg == "--start-lib") {
|
||||
ctx.in_lib = true;
|
||||
} else if (arg == "--end-lib") {
|
||||
ctx.in_lib = false;
|
||||
} else if (remove_prefix(arg, "--version-script=")) {
|
||||
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--version-script: file not found: " << arg;
|
||||
parse_version_script(ctx, mf);
|
||||
} else if (remove_prefix(arg, "--dynamic-list=")) {
|
||||
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--dynamic-list: file not found: " << arg;
|
||||
parse_dynamic_list(ctx, mf);
|
||||
} else if (remove_prefix(arg, "--export-dynamic-symbol=")) {
|
||||
if (arg == "*")
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.version_patterns.push_back({arg, "--export-dynamic-symbol",
|
||||
"global", VER_NDX_GLOBAL, false});
|
||||
} else if (remove_prefix(arg, "--export-dynamic-symbol-list=")) {
|
||||
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--export-dynamic-symbol-list: file not found: " << arg;
|
||||
parse_dynamic_list(ctx, mf);
|
||||
} else if (arg == "--push-state") {
|
||||
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static,
|
||||
ctx.in_lib});
|
||||
} else if (arg == "--pop-state") {
|
||||
if (state.empty())
|
||||
Fatal(ctx) << "no state pushed before popping";
|
||||
std::tie(ctx.as_needed, ctx.whole_archive, ctx.is_static, ctx.in_lib) =
|
||||
state.back();
|
||||
state.pop_back();
|
||||
} else if (remove_prefix(arg, "-l")) {
|
||||
MappedFile<Context<E>> *mf = find_library(ctx, std::string(arg));
|
||||
mf->given_fullpath = false;
|
||||
read_file(ctx, mf);
|
||||
} else {
|
||||
read_file(ctx, MappedFile<Context<E>>::must_open(ctx, std::string(arg)));
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.objs.empty())
|
||||
Fatal(ctx) << "no input files";
|
||||
|
||||
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 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>
|
||||
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";
|
||||
#endif
|
||||
process_run_subcommand(ctx, argc, argv);
|
||||
}
|
||||
|
||||
// Parse non-positional command line options
|
||||
ctx.cmdline_args = expand_response_files(ctx, argv);
|
||||
std::vector<std::string> file_args = parse_nonpositional_args(ctx);
|
||||
|
||||
// If no -m option is given, deduce it from input files.
|
||||
if (ctx.arg.emulation.empty())
|
||||
ctx.arg.emulation = deduce_machine_type(ctx, file_args);
|
||||
|
||||
// Redo if -m is not x86-64.
|
||||
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");
|
||||
|
||||
install_signal_handler();
|
||||
|
||||
if (!ctx.arg.directory.empty())
|
||||
if (chdir(ctx.arg.directory.c_str()) == -1)
|
||||
Fatal(ctx) << "chdir failed: " << ctx.arg.directory
|
||||
<< ": " << errno_string();
|
||||
|
||||
// Fork a subprocess unless --no-fork is given.
|
||||
std::function<void()> on_complete;
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
if (ctx.arg.fork)
|
||||
on_complete = fork_child();
|
||||
#endif
|
||||
|
||||
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
|
||||
ctx.arg.thread_count);
|
||||
|
||||
// Handle --wrap options if any.
|
||||
for (std::string_view name : ctx.arg.wrap)
|
||||
get_symbol(ctx, name)->is_wrapped = true;
|
||||
|
||||
// Handle --retain-symbols-file options if any.
|
||||
if (ctx.arg.retain_symbols_file)
|
||||
for (std::string_view name : *ctx.arg.retain_symbols_file)
|
||||
get_symbol(ctx, name)->write_to_symtab = true;
|
||||
|
||||
for (std::string_view arg : ctx.arg.trace_symbol)
|
||||
get_symbol(ctx, arg)->is_traced = true;
|
||||
|
||||
// Parse input files
|
||||
read_input_files(ctx, file_args);
|
||||
|
||||
// Uniquify shared object files by soname
|
||||
{
|
||||
std::unordered_set<std::string_view> seen;
|
||||
std::erase_if(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
return !seen.insert(file->soname).second;
|
||||
});
|
||||
}
|
||||
|
||||
Timer t_total(ctx, "total");
|
||||
Timer t_before_copy(ctx, "before_copy");
|
||||
|
||||
// Apply -exclude-libs
|
||||
apply_exclude_libs(ctx);
|
||||
|
||||
// Create a dummy file containing linker-synthesized symbols.
|
||||
if (!ctx.arg.relocatable)
|
||||
create_internal_file(ctx);
|
||||
|
||||
// resolve_symbols is 4 things in 1 phase:
|
||||
//
|
||||
// - Determine the set of object files to extract from archives.
|
||||
// - Remove redundant COMDAT sections (e.g. duplicate inline functions).
|
||||
// - Finally, the actual symbol resolution.
|
||||
// - LTO, which requires preliminary symbol resolution before running
|
||||
// and a follow-up re-resolution after the LTO objects are emitted.
|
||||
//
|
||||
// These passes have complex interactions, and unfortunately has to be
|
||||
// put together in a single phase.
|
||||
resolve_symbols(ctx);
|
||||
|
||||
// Resolve mergeable section pieces to merge them.
|
||||
resolve_section_pieces(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);
|
||||
|
||||
// Apply version scripts.
|
||||
apply_version_script(ctx);
|
||||
|
||||
// Parse symbol version suffixes (e.g. "foo@ver1").
|
||||
parse_symbol_version(ctx);
|
||||
|
||||
// Set is_imported and is_exported bits for each symbol.
|
||||
compute_import_export(ctx);
|
||||
|
||||
// Read address-significant section information.
|
||||
if (ctx.arg.icf && !ctx.arg.icf_all)
|
||||
mark_addrsig(ctx);
|
||||
|
||||
// Garbage-collect unreachable sections.
|
||||
if (ctx.arg.gc_sections)
|
||||
gc_sections(ctx);
|
||||
|
||||
// Merge identical read-only sections.
|
||||
if (ctx.arg.icf)
|
||||
icf_sections(ctx);
|
||||
|
||||
// Compute sizes of sections containing mergeable strings.
|
||||
compute_merged_section_sizes(ctx);
|
||||
|
||||
// Create linker-synthesized sections such as .got or .plt.
|
||||
create_synthetic_sections(ctx);
|
||||
|
||||
// Make sure that there's no duplicate symbol
|
||||
if (!ctx.arg.allow_multiple_definition)
|
||||
check_duplicate_symbols(ctx);
|
||||
|
||||
// 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);
|
||||
|
||||
// Beyond this point, no new files will be added to ctx.objs
|
||||
// or ctx.dsos.
|
||||
|
||||
// Handle `-z cet-report`.
|
||||
if (ctx.arg.z_cet_report != CET_REPORT_NONE)
|
||||
check_cet_errors(ctx);
|
||||
|
||||
// Handle `-z execstack-if-needed`.
|
||||
if (ctx.arg.z_execstack_if_needed)
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->needs_executable_stack)
|
||||
ctx.arg.z_execstack = true;
|
||||
|
||||
// If we are linking a .so file, remaining undefined symbols does
|
||||
// not cause a linker error. Instead, they are treated as if they
|
||||
// were imported symbols.
|
||||
//
|
||||
// If we are linking an executable, weak undefs are converted to
|
||||
// weakly imported symbols so that they'll have another chance to be
|
||||
// resolved.
|
||||
claim_unresolved_symbols(ctx);
|
||||
|
||||
// Beyond this point, no new symbols will be added to the result.
|
||||
|
||||
// Handle --print-dependencies
|
||||
if (ctx.arg.print_dependencies == 1)
|
||||
print_dependencies(ctx);
|
||||
else if (ctx.arg.print_dependencies == 2)
|
||||
print_dependencies_full(ctx);
|
||||
|
||||
// Handle -repro
|
||||
if (ctx.arg.repro)
|
||||
write_repro_file(ctx);
|
||||
|
||||
// Handle --require-defined
|
||||
for (std::string_view name : ctx.arg.require_defined)
|
||||
if (!get_symbol(ctx, name)->file)
|
||||
Error(ctx) << "--require-defined: undefined symbol: " << name;
|
||||
|
||||
// .init_array and .fini_array contents have to be sorted by
|
||||
// a special rule. Sort them.
|
||||
sort_init_fini(ctx);
|
||||
|
||||
// Likewise, .ctors and .dtors have to be sorted. They are rare
|
||||
// because they are superceded by .init_array/.fini_array, though.
|
||||
sort_ctor_dtor(ctx);
|
||||
|
||||
// Handle --shuffle-sections
|
||||
if (ctx.arg.shuffle_sections != SHUFFLE_SECTIONS_NONE)
|
||||
shuffle_sections(ctx);
|
||||
|
||||
// Copy string referred by .dynamic to .dynstr.
|
||||
for (SharedFile<E> *file : ctx.dsos)
|
||||
ctx.dynstr->add_string(file->soname);
|
||||
for (std::string_view str : ctx.arg.auxiliary)
|
||||
ctx.dynstr->add_string(str);
|
||||
for (std::string_view str : ctx.arg.filter)
|
||||
ctx.dynstr->add_string(str);
|
||||
if (!ctx.arg.rpaths.empty())
|
||||
ctx.dynstr->add_string(ctx.arg.rpaths);
|
||||
if (!ctx.arg.soname.empty())
|
||||
ctx.dynstr->add_string(ctx.arg.soname);
|
||||
|
||||
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_relocations(ctx);
|
||||
|
||||
// Compute sizes of output sections while assigning offsets
|
||||
// within an output section to input sections.
|
||||
compute_section_sizes(ctx);
|
||||
|
||||
// Sort sections by section attributes so that we'll have to
|
||||
// create as few segments as possible.
|
||||
sort_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
|
||||
// relocations now so that we can fix section sizes and file layout.
|
||||
if (ctx.arg.pack_dyn_relocs_relr)
|
||||
construct_relr(ctx);
|
||||
|
||||
// Reserve a space for dynamic symbol strings in .dynstr and sort
|
||||
// .dynsym contents if necessary. Beyond this point, no symbol will
|
||||
// be added to .dynsym.
|
||||
ctx.dynsym->finalize(ctx);
|
||||
|
||||
// Print reports about undefined symbols, if needed.
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
|
||||
report_undef_errors(ctx);
|
||||
|
||||
// Fill .gnu.version_d section contents.
|
||||
if (ctx.verdef)
|
||||
ctx.verdef->construct(ctx);
|
||||
|
||||
// Fill .gnu.version_r section contents.
|
||||
ctx.verneed->construct(ctx);
|
||||
|
||||
// Compute .symtab and .strtab sizes for each file.
|
||||
create_output_symtab(ctx);
|
||||
|
||||
// .eh_frame is a special section from the linker's point of view,
|
||||
// as its contents are parsed and reconstructed by the linker,
|
||||
// unlike other sections that are regarded as opaque bytes.
|
||||
// Here, we construct output .eh_frame contents.
|
||||
ctx.eh_frame->construct(ctx);
|
||||
|
||||
// Handle --gdb-index.
|
||||
if (ctx.arg.gdb_index)
|
||||
ctx.gdb_index->construct(ctx);
|
||||
|
||||
// If --emit-relocs is given, we'll copy relocation sections from input
|
||||
// files to an output file.
|
||||
if (ctx.arg.emit_relocs)
|
||||
create_reloc_sections(ctx);
|
||||
|
||||
// Compute the section header values for all sections.
|
||||
compute_section_headers(ctx);
|
||||
|
||||
// Assign offsets to output sections
|
||||
i64 filesize = set_osec_offsets(ctx);
|
||||
|
||||
// On RISC-V, branches are encode using multiple instructions so
|
||||
// that they can jump to anywhere in ±2 GiB by default. They may
|
||||
// be replaced with shorter instruction sequences if destinations
|
||||
// are close enough. Do this optimization.
|
||||
if constexpr (is_riscv<E>)
|
||||
filesize = riscv_resize_sections(ctx);
|
||||
|
||||
// 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
|
||||
// GOT or PLT addresses have a correct final value.
|
||||
|
||||
// If --compress-debug-sections is given, compress .debug_* sections
|
||||
// using zlib.
|
||||
if (ctx.arg.compress_debug_sections != COMPRESS_NONE)
|
||||
filesize = compress_debug_sections(ctx);
|
||||
|
||||
// At this point, both memory and file layouts are fixed.
|
||||
|
||||
t_before_copy.stop();
|
||||
|
||||
// Create an output file
|
||||
ctx.output_file =
|
||||
OutputFile<Context<E>>::open(ctx, ctx.arg.output, filesize, 0777);
|
||||
ctx.buf = ctx.output_file->buf;
|
||||
|
||||
Timer t_copy(ctx, "copy");
|
||||
|
||||
// Copy input sections to the output file 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
|
||||
// write the rest of .gdb_index.
|
||||
if (ctx.gdb_index)
|
||||
ctx.gdb_index->write_address_areas(ctx);
|
||||
|
||||
// Dynamic linker works better with sorted .rela.dyn section,
|
||||
// so we sort them.
|
||||
ctx.reldyn->sort(ctx);
|
||||
|
||||
// Zero-clear paddings between sections
|
||||
clear_padding(ctx);
|
||||
|
||||
// .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);
|
||||
|
||||
t_copy.stop();
|
||||
ctx.checkpoint();
|
||||
|
||||
// Close the output file. This is the end of the linker's main job.
|
||||
ctx.output_file->close(ctx);
|
||||
|
||||
// Handle --dependency-file
|
||||
if (!ctx.arg.dependency_file.empty())
|
||||
write_dependency_file(ctx);
|
||||
|
||||
if (ctx.has_lto_object)
|
||||
lto_cleanup(ctx);
|
||||
|
||||
t_total.stop();
|
||||
t_all.stop();
|
||||
|
||||
if (ctx.arg.print_map)
|
||||
print_map(ctx);
|
||||
|
||||
// Show stats numbers
|
||||
if (ctx.arg.stats)
|
||||
show_stats(ctx);
|
||||
|
||||
if (ctx.arg.perf)
|
||||
print_timer_records(ctx.timer_records);
|
||||
|
||||
std::cout << std::flush;
|
||||
std::cerr << std::flush;
|
||||
if (on_complete)
|
||||
on_complete();
|
||||
|
||||
if (ctx.arg.quick_exit)
|
||||
_exit(0);
|
||||
|
||||
for (std::function<void()> &fn : ctx.on_exit)
|
||||
fn();
|
||||
ctx.checkpoint();
|
||||
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);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
template int elf_main<E>(int, char **);
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
using Map =
|
||||
tbb::concurrent_hash_map<InputSection<E> *, std::vector<Symbol<E> *>>;
|
||||
@@ -16,7 +18,7 @@ static std::unique_ptr<std::ofstream> open_output_file(Context<E> &ctx) {
|
||||
std::unique_ptr<std::ofstream> file(new std::ofstream);
|
||||
file->open(ctx.arg.Map.c_str());
|
||||
if (!file->is_open())
|
||||
Fatal(ctx) << "cannot open " << ctx.arg.Map << ": " << strerror(errno);
|
||||
Fatal(ctx) << "cannot open " << ctx.arg.Map << ": " << errno_string();
|
||||
return file;
|
||||
}
|
||||
|
||||
@@ -26,17 +28,21 @@ static Map<E> get_map(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file == file && sym->input_section &&
|
||||
sym->get_type() != STT_SECTION) {
|
||||
assert(file == &sym->input_section->file);
|
||||
if (sym->file != file || sym->get_type() == STT_SECTION)
|
||||
continue;
|
||||
|
||||
if (InputSection<E> *isec = sym->get_input_section()) {
|
||||
assert(file == &isec->file);
|
||||
typename Map<E>::accessor acc;
|
||||
map.insert(acc, {sym->input_section, {}});
|
||||
map.insert(acc, {isec, {}});
|
||||
acc->second.push_back(sym);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (map.size() <= 1)
|
||||
return map;
|
||||
|
||||
tbb::parallel_for(map.range(), [](const typename Map<E>::range_type &range) {
|
||||
for (auto it = range.begin(); it != range.end(); it++) {
|
||||
std::vector<Symbol<E> *> &vec = it->second;
|
||||
@@ -48,7 +54,6 @@ static Map<E> get_map(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void print_map(Context<E> &ctx) {
|
||||
|
||||
std::ostream *out = &std::cout;
|
||||
std::unique_ptr<std::ofstream> file;
|
||||
|
||||
@@ -61,15 +66,16 @@ void print_map(Context<E> &ctx) {
|
||||
Map<E> map = get_map(ctx);
|
||||
|
||||
// Print a mapfile.
|
||||
*out << " VMA Size Align Out In Symbol\n";
|
||||
*out << " VMA Size Align Out In Symbol\n";
|
||||
|
||||
for (OutputChunk<E> *osec : ctx.chunks) {
|
||||
*out << std::setw(16) << (u64)osec->shdr.sh_addr
|
||||
for (Chunk<E> *osec : ctx.chunks) {
|
||||
*out << std::showbase
|
||||
<< std::setw(18) << std::hex << (u64)osec->shdr.sh_addr << std::dec
|
||||
<< std::setw(11) << (u64)osec->shdr.sh_size
|
||||
<< std::setw(6) << (u64)osec->shdr.sh_addralign
|
||||
<< " " << osec->name << "\n";
|
||||
|
||||
if (osec->kind != OutputChunk<E>::REGULAR)
|
||||
if (osec->kind() != OUTPUT_SECTION)
|
||||
continue;
|
||||
|
||||
std::span<InputSection<E> *> members = ((OutputSection<E> *)osec)->members;
|
||||
@@ -79,20 +85,23 @@ void print_map(Context<E> &ctx) {
|
||||
InputSection<E> *mem = members[i];
|
||||
std::ostringstream ss;
|
||||
opt_demangle = ctx.arg.demangle;
|
||||
u64 addr = osec->shdr.sh_addr + mem->offset;
|
||||
|
||||
ss << std::setw(16) << (osec->shdr.sh_addr + mem->offset)
|
||||
<< std::setw(11) << (u64)mem->shdr.sh_size
|
||||
<< std::setw(6) << (u64)mem->shdr.sh_addralign
|
||||
ss << std::showbase
|
||||
<< std::setw(18) << std::hex << addr << std::dec
|
||||
<< std::setw(11) << (u64)mem->sh_size
|
||||
<< std::setw(6) << (1 << (u64)mem->p2align)
|
||||
<< " " << *mem << "\n";
|
||||
|
||||
typename Map<E>::const_accessor acc;
|
||||
if (map.find(acc, mem))
|
||||
for (Symbol<E> *sym : acc->second)
|
||||
ss << std::setw(16) << sym->get_addr(ctx)
|
||||
ss << std::showbase
|
||||
<< std::setw(18) << std::hex << sym->get_addr(ctx) << std::dec
|
||||
<< " 0 0 "
|
||||
<< *sym << "\n";
|
||||
|
||||
bufs[i] = std::move(ss.str());
|
||||
bufs[i] = ss.str();
|
||||
});
|
||||
|
||||
for (std::string &str : bufs)
|
||||
@@ -100,5 +109,8 @@ void print_map(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
template void print_map(Context<X86_64> &ctx);
|
||||
template void print_map(Context<I386> &ctx);
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void print_map(Context<E> &ctx);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -1,5 +1,8 @@
|
||||
#define _GNU_SOURCE 1
|
||||
|
||||
#if !defined(__OpenBSD__) && !defined(__FreeBSD__)
|
||||
# include <alloca.h>
|
||||
#endif
|
||||
#include <dlfcn.h>
|
||||
#include <spawn.h>
|
||||
#include <stdarg.h>
|
||||
@@ -19,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;
|
||||
|
||||
@@ -31,29 +34,53 @@ static void debug_print(char *fmt, ...) {
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static void get_args(va_list ap, int argc, char **argv) {
|
||||
for (int i = 1; i < argc - 1; i++) {
|
||||
char *arg = va_arg(ap, char *);
|
||||
if (!arg)
|
||||
break;
|
||||
argv[i] = arg;
|
||||
}
|
||||
static int count_args(va_list *ap) {
|
||||
va_list aq;
|
||||
va_copy(aq, *ap);
|
||||
|
||||
int i = 0;
|
||||
while (va_arg(aq, char *))
|
||||
i++;
|
||||
va_end(aq);
|
||||
return i;
|
||||
}
|
||||
|
||||
static void copy_args(char **argv, const char *arg0, va_list *ap) {
|
||||
int i = 1;
|
||||
char *arg;
|
||||
while ((arg = va_arg(*ap, char *)))
|
||||
argv[i++] = arg;
|
||||
|
||||
((const char **)argv)[0] = arg0;
|
||||
((const char **)argv)[i] = NULL;
|
||||
}
|
||||
|
||||
static bool is_ld(const char *path) {
|
||||
return !strcmp(path, "/usr/bin/ld") ||
|
||||
!strcmp(path, "/usr/bin/ld.lld") ||
|
||||
!strcmp(path, "/usr/bin/ld.gold");
|
||||
const char *ptr = path + strlen(path);
|
||||
while (path < ptr && ptr[-1] != '/')
|
||||
ptr--;
|
||||
|
||||
return !strcmp(ptr, "ld") || !strcmp(ptr, "ld.lld") ||
|
||||
!strcmp(ptr, "ld.gold") || !strcmp(ptr, "ld.bfd");
|
||||
}
|
||||
|
||||
int execvpe(const char *file, char *const *argv, char *const *envp) {
|
||||
debug_print("execvpe %s\n", file);
|
||||
|
||||
if (!strcmp(file, "ld") || is_ld(file))
|
||||
file = get_mold_path();
|
||||
|
||||
for (int i = 0; envp[i]; i++)
|
||||
putenv(envp[i]);
|
||||
|
||||
typeof(execvpe) *real = dlsym(RTLD_NEXT, "execvp");
|
||||
return real(file, argv, environ);
|
||||
}
|
||||
|
||||
int execve(const char *path, char *const *argv, char *const *envp) {
|
||||
debug_print("execve %s\n", path);
|
||||
|
||||
if (is_ld(path)) {
|
||||
if (is_ld(path))
|
||||
path = get_mold_path();
|
||||
((const char **)argv)[0] = path;
|
||||
}
|
||||
|
||||
typeof(execve) *real = dlsym(RTLD_NEXT, "execve");
|
||||
return real(path, argv, envp);
|
||||
}
|
||||
@@ -61,25 +88,28 @@ int execve(const char *path, char *const *argv, char *const *envp) {
|
||||
int execl(const char *path, const char *arg0, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, arg0);
|
||||
char *argv[4096] = {(char *)arg0};
|
||||
get_args(ap, 4096, argv);
|
||||
char **argv = alloca((count_args(&ap) + 2) * sizeof(char *));
|
||||
copy_args(argv, arg0, &ap);
|
||||
va_end(ap);
|
||||
return execve(path, argv, environ);
|
||||
}
|
||||
|
||||
int execlp(const char *file, const char *arg0, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, arg0);
|
||||
char *argv[4096] = {(char *)arg0};
|
||||
get_args(ap, 4096, argv);
|
||||
char **argv = alloca((count_args(&ap) + 2) * sizeof(char *));
|
||||
copy_args(argv, arg0, &ap);
|
||||
va_end(ap);
|
||||
return execvpe(file, argv, environ);
|
||||
}
|
||||
|
||||
int execle(const char *path, const char *arg0, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, arg0);
|
||||
char *argv[4096] = {(char *)arg0};
|
||||
get_args(ap, 4096, argv);
|
||||
char **argv = alloca((count_args(&ap) + 2) * sizeof(char *));
|
||||
copy_args(argv, arg0, &ap);
|
||||
char **env = va_arg(ap, char **);
|
||||
va_end(ap);
|
||||
return execve(path, argv, env);
|
||||
}
|
||||
|
||||
@@ -91,29 +121,13 @@ int execvp(const char *file, char *const *argv) {
|
||||
return execvpe(file, argv, environ);
|
||||
}
|
||||
|
||||
int execvpe(const char *file, char *const *argv, char *const *envp) {
|
||||
debug_print("execvpe %s\n", file);
|
||||
|
||||
if (!strcmp(file, "ld") || is_ld(file)) {
|
||||
file = get_mold_path();
|
||||
((const char **)argv)[0] = file;
|
||||
}
|
||||
|
||||
typeof(execvpe) *real = dlsym(RTLD_NEXT, "execvpe");
|
||||
return real(file, argv, environ);
|
||||
}
|
||||
|
||||
int posix_spawn(pid_t *pid, const char *path,
|
||||
const posix_spawn_file_actions_t *file_actions,
|
||||
const posix_spawnattr_t *attrp,
|
||||
char *const *argv, char *const *envp) {
|
||||
debug_print("posix_spawn %s\n", path);
|
||||
|
||||
if (is_ld(path)) {
|
||||
if (is_ld(path))
|
||||
path = get_mold_path();
|
||||
((const char **)argv)[0] = path;
|
||||
}
|
||||
|
||||
typeof(posix_spawn) *real = dlsym(RTLD_NEXT, "posix_spawn");
|
||||
return real(pid, path, file_actions, attrp, argv, envp);
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// 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 --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.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// 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. Two sections with the same name are merged.
|
||||
//
|
||||
// - .symtab, .strtab and .shstrtab are merged.
|
||||
//
|
||||
// - COMDAT groups are uniquified.
|
||||
//
|
||||
// - Relocations are copied, but we need to fix symbol indices.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Create linker-synthesized sections
|
||||
template <typename E>
|
||||
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;
|
||||
};
|
||||
|
||||
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>
|
||||
static void create_comdat_group_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "create_comdat_group_sections");
|
||||
|
||||
std::vector<std::vector<Chunk<E> *>> vec{ctx.objs.size()};
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
|
||||
ObjectFile<E> &file = *ctx.objs[i];
|
||||
|
||||
for (ComdatGroupRef<E> &ref : file.comdat_groups) {
|
||||
if (ref.group->owner != file.priority)
|
||||
continue;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 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;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void combine_objects(Context<E> &ctx) {
|
||||
compute_merged_section_sizes(ctx);
|
||||
|
||||
create_output_sections(ctx);
|
||||
|
||||
r_create_synthetic_sections(ctx);
|
||||
|
||||
r_claim_unresolved_symbols(ctx);
|
||||
|
||||
compute_section_sizes(ctx);
|
||||
|
||||
sort_output_sections(ctx);
|
||||
|
||||
create_output_symtab(ctx);
|
||||
|
||||
ctx.eh_frame->construct(ctx);
|
||||
|
||||
create_reloc_sections(ctx);
|
||||
|
||||
create_comdat_group_sections(ctx);
|
||||
|
||||
compute_section_headers(ctx);
|
||||
|
||||
i64 filesize = r_set_osec_offsets(ctx);
|
||||
ctx.output_file =
|
||||
OutputFile<Context<E>>::open(ctx, ctx.arg.output, filesize, 0666);
|
||||
ctx.buf = ctx.output_file->buf;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void combine_objects(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -0,0 +1,122 @@
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
|
||||
#include "mold.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
#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.
|
||||
std::function<void()> fork_child() {
|
||||
int pipefd[2];
|
||||
if (pipe(pipefd) == -1) {
|
||||
perror("pipe");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
perror("fork");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (pid > 0) {
|
||||
// Parent
|
||||
close(pipefd[1]);
|
||||
|
||||
char buf[1];
|
||||
if (read(pipefd[0], buf, 1) == 1)
|
||||
_exit(0);
|
||||
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
if (WIFEXITED(status))
|
||||
_exit(WEXITSTATUS(status));
|
||||
if (WIFSIGNALED(status))
|
||||
raise(WTERMSIG(status));
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
// Child
|
||||
close(pipefd[0]);
|
||||
|
||||
return [=] {
|
||||
char buf[] = {1};
|
||||
[[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) {
|
||||
// Look for mold-wrapper.so from the same directory as the executable is.
|
||||
std::filesystem::path path = self.parent_path() / "mold-wrapper.so";
|
||||
std::error_code ec;
|
||||
if (std::filesystem::is_regular_file(path, ec) && !ec)
|
||||
return path;
|
||||
|
||||
// If not found, search $(MOLD_LIBDIR)/mold, which is /usr/local/lib/mold
|
||||
// by default.
|
||||
path = MOLD_LIBDIR "/mold/mold-wrapper.so";
|
||||
if (std::filesystem::is_regular_file(path, ec) && !ec)
|
||||
return path;
|
||||
|
||||
// Look for ../lib/mold/mold-wrapper.so
|
||||
path = self.parent_path() / "../lib/mold/mold-wrapper.so";
|
||||
if (std::filesystem::is_regular_file(path, ec) && !ec)
|
||||
return path;
|
||||
|
||||
Fatal(ctx) << "mold-wrapper.so is missing";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
[[noreturn]]
|
||||
void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
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 = get_self_path();
|
||||
std::string dso_path = find_dso(ctx, self);
|
||||
|
||||
// Set environment variables
|
||||
putenv(strdup(("LD_PRELOAD=" + dso_path).c_str()));
|
||||
putenv(strdup(("MOLD_PATH=" + self).c_str()));
|
||||
|
||||
// If ld, ld.lld or ld.gold is specified, run mold itself
|
||||
if (std::string cmd = filepath(argv[2]).filename();
|
||||
cmd == "ld" || cmd == "ld.lld" || cmd == "ld.gold") {
|
||||
std::vector<char *> args;
|
||||
args.push_back(argv[0]);
|
||||
args.insert(args.end(), argv + 3, argv + argc);
|
||||
args.push_back(nullptr);
|
||||
execv(self.c_str(), args.data());
|
||||
Fatal(ctx) << "mold -run failed: " << self << ": " << errno_string();
|
||||
}
|
||||
|
||||
// Execute a given command
|
||||
execvp(argv[2], argv + 2);
|
||||
Fatal(ctx) << "mold -run failed: " << argv[2] << ": " << errno_string();
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void process_run_subcommand(Context<E> &, int, char **);
|
||||
|
||||
} // namespace mold::elf
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,291 @@
|
||||
// RISC instructions are usually up to 4 bytes long, so the immediates of
|
||||
// their branch instructions are naturally smaller than 32 bits. This is
|
||||
// contrary to x86-64 on which branch instructions take 4 bytes immediates
|
||||
// and can jump to anywhere within PC ± 2 GiB.
|
||||
//
|
||||
// In fact, ARM32's branch instructions can jump only within ±16 MiB and
|
||||
// ARM64's ±128 MiB, for example. If a branch target is further than that,
|
||||
// we need to let it branch to a linker-synthesized code sequence that
|
||||
// construct a full 32 bit address in a register and jump there. That
|
||||
// linker-synthesized code is called "thunk".
|
||||
//
|
||||
// The function in this file creates thunks.
|
||||
//
|
||||
// Note that although thunks play an important role in an executable, they
|
||||
// don't take up too much space in it. For example, among the clang-16's
|
||||
// text segment whose size is ~300 MiB on ARM64, thunks in total occupy
|
||||
// only ~30 KiB or 0.01%. Of course the number depends on an ISA; we would
|
||||
// need more thunks on ARM32 whose branch range is shorter than ARM64.
|
||||
// That said, the total size of thunks still isn't that much. Therefore,
|
||||
// we don't need to try too hard to reduce thunk size to the absolute
|
||||
// minimum.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
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) {
|
||||
sym->extra.thunk_idx = -1;
|
||||
sym->extra.thunk_sym_idx = -1;
|
||||
sym->flags = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 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> *> m = osec.members;
|
||||
if (m.empty())
|
||||
return;
|
||||
|
||||
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)m.size(), [&](i64 i) {
|
||||
m[i]->offset = -1;
|
||||
});
|
||||
|
||||
// We create thunks from the beginning of the section to the end.
|
||||
// We manage progress using four offsets which increase monotonically.
|
||||
// The locations they point to are always A <= B <= C <= D.
|
||||
i64 a = 0;
|
||||
i64 b = 0;
|
||||
i64 c = 0;
|
||||
i64 d = 0;
|
||||
i64 offset = 0;
|
||||
|
||||
while (b < 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 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.
|
||||
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});
|
||||
|
||||
RangeExtensionThunk<E> &thunk = *osec.thunks.back();
|
||||
thunk.thunk_idx = osec.thunks.size() - 1;
|
||||
offset = align_to(offset, thunk.alignment);
|
||||
thunk.offset = offset;
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need thunks.
|
||||
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.
|
||||
sort(thunk.symbols, [](Symbol<E> *a, Symbol<E> *b) {
|
||||
return std::tuple{a->file->priority, a->sym_idx} <
|
||||
std::tuple{b->file->priority, b->sym_idx};
|
||||
});
|
||||
|
||||
// Assign offsets within the thunk to the symbols.
|
||||
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
|
||||
sym->extra.thunk_idx = thunk.thunk_idx;
|
||||
sym->extra.thunk_sym_idx = i++;
|
||||
}
|
||||
|
||||
// Scan relocations again to fix symbol offsets in the last thunk.
|
||||
tbb::parallel_for_each(m.begin() + b, m.begin() + c, [&](InputSection<E> *isec) {
|
||||
std::span<Symbol<E> *> syms = isec->file.symbols;
|
||||
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = isec->extra.range_extn;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++)
|
||||
if (range_extn[i].thunk_idx == thunk.thunk_idx)
|
||||
range_extn[i].sym_idx = syms[rels[i].r_sym]->extra.thunk_sym_idx;
|
||||
});
|
||||
|
||||
// Move B forward to point to the begining of the next batch.
|
||||
b = c;
|
||||
}
|
||||
|
||||
while (a < osec.thunks.size())
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
|
||||
osec.shdr.sh_size = offset;
|
||||
}
|
||||
|
||||
#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,105 +1,27 @@
|
||||
#include "mold.h"
|
||||
|
||||
std::string get_current_dir() {
|
||||
std::string buf(8192, '\0');
|
||||
getcwd(buf.data(), buf.size());
|
||||
buf.resize(buf.find_first_of('\0'));
|
||||
return buf;
|
||||
}
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
|
||||
// Returns the directory part of a given path.
|
||||
// Returns '.' if path doesn't contain '/'.
|
||||
std::string_view path_dirname(std::string_view path) {
|
||||
i64 pos = path.find_last_of('/');
|
||||
if (pos == path.npos)
|
||||
return ".";
|
||||
return path.substr(0, pos);
|
||||
}
|
||||
namespace mold {
|
||||
|
||||
// Returns the filename part of a given path.
|
||||
// Returns '/' if path reperesents the root directory.
|
||||
// Returns '.' if path is empty.
|
||||
std::string_view path_filename(std::string_view path) {
|
||||
if (path.empty())
|
||||
return ".";
|
||||
|
||||
while (path.ends_with('/'))
|
||||
path = path.substr(0, path.size() - 2);
|
||||
|
||||
if (path.empty())
|
||||
return "/";
|
||||
|
||||
i64 pos = path.find_last_of('/');
|
||||
if (pos == path.npos)
|
||||
return path;
|
||||
return path.substr(pos + 1);
|
||||
}
|
||||
|
||||
// Returns the filename part of a given path without the file
|
||||
// extension.
|
||||
std::string_view path_basename(std::string_view path) {
|
||||
std::string_view name = path_filename(path);
|
||||
|
||||
i64 pos = name.find_last_of('.');
|
||||
if (pos == name.npos)
|
||||
return name;
|
||||
return name.substr(0, pos);
|
||||
}
|
||||
|
||||
std::string path_to_absolute(std::string_view path) {
|
||||
if (path.starts_with('/'))
|
||||
return std::string(path);
|
||||
return get_current_dir() + "/" + std::string(path);
|
||||
std::string get_realpath(std::string_view path) {
|
||||
std::error_code ec;
|
||||
std::string ret = std::filesystem::canonical(path, ec).string();
|
||||
return ec ? std::string(path) : ret;
|
||||
}
|
||||
|
||||
// Removes redundant '/..' or '/.' from a given path.
|
||||
// The transformation is done purely by lexical processing.
|
||||
// This function does not access file system.
|
||||
std::string path_clean(std::string_view path) {
|
||||
std::vector<std::string_view> components;
|
||||
bool is_rooted = path.starts_with('/');
|
||||
|
||||
while (!path.empty()) {
|
||||
size_t pos = path.find('/');
|
||||
std::string_view elem;
|
||||
|
||||
if (pos == path.npos) {
|
||||
elem = path;
|
||||
path = "";
|
||||
} else {
|
||||
elem = path.substr(0, pos);
|
||||
path = path.substr(pos + 1);
|
||||
}
|
||||
|
||||
if (elem.empty() || elem == ".")
|
||||
continue;
|
||||
|
||||
if (elem == "..") {
|
||||
if (components.empty()) {
|
||||
if (is_rooted)
|
||||
continue;
|
||||
components.push_back("..");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (components.back() == "..")
|
||||
components.push_back("..");
|
||||
else
|
||||
components.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
components.push_back(elem);
|
||||
}
|
||||
|
||||
std::string ret;
|
||||
if (is_rooted)
|
||||
ret = "/";
|
||||
|
||||
for (i64 i = 0, end = components.size(); i < end; i++) {
|
||||
ret += components[i];
|
||||
if (i != end - 1)
|
||||
ret += "/";
|
||||
}
|
||||
return ret;
|
||||
return filepath(path).lexically_normal().string();
|
||||
}
|
||||
|
||||
std::filesystem::path to_abs_path(std::filesystem::path path) {
|
||||
if (path.is_absolute())
|
||||
return path.lexically_normal();
|
||||
return (std::filesystem::current_path() / path).lexically_normal();
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,191 @@
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
#include "elf/elf.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
enum class FileType {
|
||||
UNKNOWN,
|
||||
EMPTY,
|
||||
ELF_OBJ,
|
||||
ELF_DSO,
|
||||
MACH_OBJ,
|
||||
MACH_EXE,
|
||||
MACH_DYLIB,
|
||||
MACH_BUNDLE,
|
||||
MACH_UNIVERSAL,
|
||||
AR,
|
||||
THIN_AR,
|
||||
TAPI,
|
||||
TEXT,
|
||||
GCC_LTO_OBJ,
|
||||
LLVM_BITCODE,
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
bool is_text_file(MappedFile<C> *mf) {
|
||||
u8 *data = mf->data;
|
||||
return mf->size >= 4 && isprint(data[0]) && isprint(data[1]) &&
|
||||
isprint(data[2]) && isprint(data[3]);
|
||||
}
|
||||
|
||||
template <typename E, typename C>
|
||||
inline bool is_gcc_lto_obj(MappedFile<C> *mf, 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;
|
||||
|
||||
std::span<ElfSym<E>> elf_syms{(ElfSym<E> *)(data + sec.sh_offset),
|
||||
(size_t)sec.sh_size / sizeof(ElfSym<E>)};
|
||||
|
||||
auto skip = [](u8 type) {
|
||||
return type == STT_NOTYPE || type == STT_FILE || type == STT_SECTION;
|
||||
};
|
||||
|
||||
i64 i = 1;
|
||||
while (i < elf_syms.size() && skip(elf_syms[i].st_type))
|
||||
i++;
|
||||
|
||||
if (i < elf_syms.size() && elf_syms[i].st_shndx == SHN_COMMON) {
|
||||
std::string_view name =
|
||||
data + shdrs[sec.sh_link].sh_offset + elf_syms[i].st_name;
|
||||
if (name.starts_with("__gnu_lto_"))
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
FileType get_file_type(MappedFile<C> *mf, bool opt_plugin) {
|
||||
using namespace elf;
|
||||
|
||||
std::string_view data = mf->get_contents();
|
||||
|
||||
if (data.empty())
|
||||
return FileType::EMPTY;
|
||||
|
||||
if (data.starts_with("\177ELF")) {
|
||||
u8 byte_order = ((ElfEhdr<I386> *)data.data())->e_ident[EI_DATA];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (data.starts_with("\xcf\xfa\xed\xfe")) {
|
||||
switch (*(ul32 *)(data.data() + 12)) {
|
||||
case 1: // MH_OBJECT
|
||||
return FileType::MACH_OBJ;
|
||||
case 2: // MH_EXECUTE
|
||||
return FileType::MACH_EXE;
|
||||
case 6: // MH_DYLIB
|
||||
return FileType::MACH_DYLIB;
|
||||
case 8: // MH_BUNDLE
|
||||
return FileType::MACH_BUNDLE;
|
||||
}
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
if (data.starts_with("!<arch>\n"))
|
||||
return FileType::AR;
|
||||
if (data.starts_with("!<thin>\n"))
|
||||
return FileType::THIN_AR;
|
||||
if (data.starts_with("--- !tapi-tbd"))
|
||||
return FileType::TAPI;
|
||||
if (data.starts_with("\xca\xfe\xba\xbe"))
|
||||
return FileType::MACH_UNIVERSAL;
|
||||
if (is_text_file(mf))
|
||||
return FileType::TEXT;
|
||||
if (data.starts_with("\xde\xc0\x17\x0b"))
|
||||
return FileType::LLVM_BITCODE;
|
||||
if (data.starts_with("BC\xc0\xde"))
|
||||
return FileType::LLVM_BITCODE;
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
inline std::string filetype_to_string(FileType type) {
|
||||
switch (type) {
|
||||
case FileType::UNKNOWN: return "UNKNOWN";
|
||||
case FileType::EMPTY: return "EMPTY";
|
||||
case FileType::ELF_OBJ: return "ELF_OBJ";
|
||||
case FileType::ELF_DSO: return "ELF_DSO";
|
||||
case FileType::MACH_EXE: return "MACH_EXE";
|
||||
case FileType::MACH_OBJ: return "MACH_OBJ";
|
||||
case FileType::MACH_DYLIB: return "MACH_DYLIB";
|
||||
case FileType::MACH_BUNDLE: return "MACH_BUNDLE";
|
||||
case FileType::MACH_UNIVERSAL: return "MACH_UNIVERSAL";
|
||||
case FileType::AR: return "AR";
|
||||
case FileType::THIN_AR: return "THIN_AR";
|
||||
case FileType::TAPI: return "TAPI";
|
||||
case FileType::TEXT: return "TEXT";
|
||||
case FileType::GCC_LTO_OBJ: return "GCC_LTO_OBJ";
|
||||
case FileType::LLVM_BITCODE: return "LLVM_BITCODE";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &out, FileType type) {
|
||||
out << filetype_to_string(type);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This test script takes a Gentoo package name and tries to build it
|
||||
# using mold in a Docker environment. We chose Gentoo Linux as a test
|
||||
# target, because its source-based package allows us to build programs
|
||||
# locally and run their test suites without any hassle.
|
||||
#
|
||||
# You can get a complete list of Gentoo packages availalbe for testing
|
||||
# with the following command:
|
||||
#
|
||||
# docker run --rm mold-gentoo emerge --color n -s '' | \
|
||||
# perl -ne 'next unless m!^\*\s+(\S+/\S+)!; print "$1\n"'
|
||||
|
||||
if [ "$1" = "" ]; then
|
||||
echo "Usage: $0 gentoo-package-name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -x
|
||||
|
||||
# Create a Docker image
|
||||
if ! docker image ls mold-gentoo | grep -q mold-gentoo; then
|
||||
set -e
|
||||
cat <<EOF | docker build -t mold-gentoo -
|
||||
FROM gentoo/stage3
|
||||
RUN emerge-webrsync
|
||||
RUN echo 'USE="elogind -systemd corefonts truetype jpeg jpeg2k tiff zstd static-libs binary"' >> /etc/portage/make.conf && \
|
||||
echo 'ACCEPT_LICENSE="* -@EULA"' >> /etc/portage/make.conf && \
|
||||
echo 'FEATURES="${FEATURE} noclean nostrip -ipc-sandbox -network-sandbox -pid-sandbox -sandbox"' >> /etc/portage/make.conf
|
||||
RUN emerge gdb lld vim strace && rm -rf /var/tmp/portage
|
||||
EOF
|
||||
set +e
|
||||
fi
|
||||
|
||||
# Build mold as a statically-linked executable
|
||||
if ! [ -f mold ] || ! ldd mold 2>&1 | grep -q 'not a dynamic executable'; then
|
||||
make clean
|
||||
./build-static.sh
|
||||
fi
|
||||
|
||||
git_hash=$(./mold --version | perl -ne '/\((\w+)/; print $1;')
|
||||
|
||||
# Build a given package in Docker
|
||||
build() {
|
||||
package="$1"
|
||||
cmd1="ln -sf /mold/mold /usr/x86_64-pc-linux-gnu/bin/ld"
|
||||
cmd2="MAKEOPTS=-j8 emerge --onlydeps $package"
|
||||
cmd3="MAKEOPTS=-j8 FEATURES=test emerge $package"
|
||||
filename=`echo "$package" | sed 's!/!_!g'`
|
||||
docker="docker run --rm --cap-add=SYS_PTRACE -v `pwd`:/mold mold-gentoo timeout -v -k 15s 1h"
|
||||
dir=gentoo/$git_hash
|
||||
|
||||
mkdir -p $dir/success $dir/failure
|
||||
|
||||
$docker nice -n 19 bash -c "$cmd1 && $cmd2 && $cmd3" >& $dir/$filename.mold
|
||||
if [ $? = 0 ]; then
|
||||
mv $dir/$filename.mold $dir/success
|
||||
else
|
||||
mv $dir/$filename.mold $dir/failure
|
||||
fi
|
||||
|
||||
$docker nice -n 19 bash -c "$cmd2 && $cmd3" >& $dir/$filename.ld
|
||||
if [ $? = 0 ]; then
|
||||
mv $dir/$filename.ld $dir/success
|
||||
else
|
||||
mv $dir/$filename.ld $dir/failure
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$1" = dev-libs/concurrencykit ]; then
|
||||
echo "Skipping known broken package: $1"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build a package
|
||||
build "$1"
|
||||
@@ -0,0 +1,149 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::optional<Glob> Glob::compile(std::string_view pat) {
|
||||
std::vector<Element> vec;
|
||||
|
||||
while (!pat.empty()) {
|
||||
u8 c = pat[0];
|
||||
pat = pat.substr(1);
|
||||
|
||||
switch (c) {
|
||||
case '[': {
|
||||
// Here are a few bracket pattern examples:
|
||||
//
|
||||
// [abc]: a, b or c
|
||||
// [$\]!]: $, ] or !
|
||||
// [a-czg-i]: a, b, c, z, g, h, or i
|
||||
// [^a-z]: Any character except lowercase letters
|
||||
vec.push_back({BRACKET});
|
||||
std::bitset<256> &bitset = vec.back().bitset;
|
||||
|
||||
bool negate = false;
|
||||
if (!pat.empty() && pat[0] == '^') {
|
||||
negate = true;
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
|
||||
bool closed = false;
|
||||
|
||||
while (!pat.empty()) {
|
||||
if (pat[0] == ']') {
|
||||
pat = pat.substr(1);
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pat[0] == '\\') {
|
||||
pat = pat.substr(1);
|
||||
if (pat.empty())
|
||||
return {};
|
||||
}
|
||||
|
||||
if (pat.size() >= 3 && pat[1] == '-') {
|
||||
u8 start = pat[0];
|
||||
u8 end = pat[2];
|
||||
pat = pat.substr(3);
|
||||
|
||||
if (end == '\\') {
|
||||
if (pat.empty())
|
||||
return {};
|
||||
end = pat[0];
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
|
||||
if (end < start)
|
||||
return {};
|
||||
|
||||
for (i64 i = start; i <= end; i++)
|
||||
bitset[i] = true;
|
||||
} else {
|
||||
bitset[(u8)pat[0]] = true;
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!closed)
|
||||
return {};
|
||||
|
||||
if (negate)
|
||||
bitset.flip();
|
||||
break;
|
||||
}
|
||||
case '?':
|
||||
vec.push_back({QUESTION});
|
||||
break;
|
||||
case '*':
|
||||
vec.push_back({STAR});
|
||||
break;
|
||||
default:
|
||||
if (vec.empty() || vec.back().kind != STRING)
|
||||
vec.push_back({STRING});
|
||||
vec.back().str += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {Glob{std::move(vec)}};
|
||||
}
|
||||
|
||||
bool Glob::match(std::string_view str) {
|
||||
return do_match(str, elements);
|
||||
}
|
||||
|
||||
bool Glob::do_match(std::string_view str, std::span<Element> elements) {
|
||||
while (!elements.empty()) {
|
||||
Element &e = elements[0];
|
||||
elements = elements.subspan(1);
|
||||
|
||||
switch (e.kind) {
|
||||
case STRING:
|
||||
if (str.empty() || !str.starts_with(e.str))
|
||||
return false;
|
||||
str = str.substr(e.str.size());
|
||||
break;
|
||||
case STAR:
|
||||
if (elements.empty())
|
||||
return true;
|
||||
|
||||
// Patterns like "*foo*bar*" should be much more common than more
|
||||
// complex ones like "*foo*[abc]*" or "*foo**?bar*", so we optimize
|
||||
// the former case here.
|
||||
if (elements[0].kind == STRING) {
|
||||
for (;;) {
|
||||
size_t pos = str.find(elements[0].str);
|
||||
if (pos == str.npos)
|
||||
break;
|
||||
if (do_match(str.substr(pos + elements[0].str.size()),
|
||||
elements.subspan(1)))
|
||||
return true;
|
||||
str = str.substr(pos + 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Other cases are handled here.
|
||||
for (i64 j = 0; j < str.size(); j++)
|
||||
if (do_match(str.substr(j), elements))
|
||||
return true;
|
||||
return false;
|
||||
case QUESTION:
|
||||
if (str.empty())
|
||||
return false;
|
||||
str = str.substr(1);
|
||||
break;
|
||||
case BRACKET:
|
||||
if (str.empty() || !e.bitset[str[0]])
|
||||
return false;
|
||||
str = str.substr(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return str.empty();
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,20 @@
|
||||
// This file implements HyperLogLog algorithm, which estimates
|
||||
// the number of unique items in a given multiset.
|
||||
//
|
||||
// For more info, read
|
||||
// https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace mold {
|
||||
|
||||
i64 HyperLogLog::get_cardinality() const {
|
||||
double z = 0;
|
||||
for (i64 val : buckets)
|
||||
z += pow(2, -val);
|
||||
return ALPHA * NBUCKETS * NBUCKETS / z;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,154 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
template <typename E>
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
const ElfShdr<E> &shdr, std::string_view name,
|
||||
std::string_view contents, i64 section_idx)
|
||||
: file(file), shdr(shdr), nameptr(name.data()), namelen(name.size()),
|
||||
contents(contents), section_idx(section_idx) {
|
||||
// As a special case, we want to map .ctors and .dtors to
|
||||
// .init_array and .fini_array, respectively. However, old CRT
|
||||
// object files are not compatible with this translation, so we need
|
||||
// to keep them as-is if a section came from crtbegin.o or crtend.o.
|
||||
//
|
||||
// Yeah, this is an ugly hack, but the fundamental problem is that
|
||||
// we have two different mechanism, ctors/dtors and init_array/fini_array
|
||||
// for the same purpose. The latter was introduced to replace the
|
||||
// former, but as it is often the case, the former still lingers
|
||||
// around, so we need to keep this code to conver the old mechanism
|
||||
// to the new one.
|
||||
std::string_view stem = path_filename(file.filename);
|
||||
if (stem != "crtbegin.o" && stem != "crtend.o" &&
|
||||
stem != "crtbeginS.o" && stem != "crtendS.o") {
|
||||
if (name == ".ctors" || name.starts_with(".ctors."))
|
||||
name = ".init_array";
|
||||
else if (name == ".dtors" || name.starts_with(".dtors."))
|
||||
name = ".fini_array";
|
||||
}
|
||||
|
||||
output_section =
|
||||
OutputSection<E>::get_instance(ctx, name, shdr.sh_type, shdr.sh_flags);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
if (shdr.sh_type == SHT_NOBITS || shdr.sh_size == 0)
|
||||
return;
|
||||
|
||||
// Copy data
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
|
||||
// Apply relocations
|
||||
if (shdr.sh_flags & SHF_ALLOC)
|
||||
apply_reloc_alloc(ctx, buf);
|
||||
else
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
|
||||
// As a special case, .ctors and .dtors section contents are
|
||||
// reversed. These sections are now obsolete and mapped to
|
||||
// .init_array and .fini_array, but they have to be reversed to
|
||||
// maintain the original semantics.
|
||||
bool init_fini = output_section->name == ".init_array" ||
|
||||
output_section->name == ".fini_array";
|
||||
bool ctors_dtors = name().starts_with(".ctors") ||
|
||||
name().starts_with(".dtors");
|
||||
if (init_fini && ctors_dtors)
|
||||
std::reverse((typename E::WordTy *)buf,
|
||||
(typename E::WordTy *)(buf + shdr.sh_size));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 get_output_type(Context<E> &ctx) {
|
||||
if (ctx.arg.shared)
|
||||
return 0;
|
||||
if (ctx.arg.pie)
|
||||
return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 get_sym_type(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (sym.is_absolute(ctx))
|
||||
return 0;
|
||||
if (!sym.is_imported)
|
||||
return 1;
|
||||
if (sym.get_type() != STT_FUNC)
|
||||
return 2;
|
||||
return 3;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::dispatch(Context<E> &ctx, Action table[3][4],
|
||||
u16 rel_type, i64 i) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
bool is_writable = (shdr.sh_flags & SHF_WRITE);
|
||||
Action action = table[get_output_type(ctx)][get_sym_type(ctx, sym)];
|
||||
|
||||
switch (action) {
|
||||
case NONE:
|
||||
rel_types[i] = rel_type;
|
||||
return;
|
||||
case ERROR:
|
||||
break;
|
||||
case COPYREL:
|
||||
if (!ctx.arg.z_copyreloc && !sym.esym().is_undef_weak())
|
||||
break;
|
||||
if (sym.esym().st_visibility == STV_PROTECTED)
|
||||
Error(ctx) << *this << ": cannot make copy relocation for "
|
||||
<< " protected symbol '" << sym << "', defined in "
|
||||
<< *sym.file;
|
||||
sym.flags |= NEEDS_COPYREL;
|
||||
rel_types[i] = rel_type;
|
||||
return;
|
||||
case PLT:
|
||||
sym.flags |= NEEDS_PLT;
|
||||
rel_types[i] = rel_type;
|
||||
return;
|
||||
case DYNREL:
|
||||
if (!is_writable) {
|
||||
if (ctx.arg.z_text)
|
||||
break;
|
||||
ctx.has_textrel = true;
|
||||
}
|
||||
sym.flags |= NEEDS_DYNSYM;
|
||||
rel_types[i] = R_DYN;
|
||||
file.num_dynrel++;
|
||||
return;
|
||||
case BASEREL:
|
||||
if (!is_writable) {
|
||||
if (ctx.arg.z_text)
|
||||
break;
|
||||
ctx.has_textrel = true;
|
||||
}
|
||||
rel_types[i] = R_BASEREL;
|
||||
file.num_dynrel++;
|
||||
return;
|
||||
default:
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
Error(ctx) << *this << ": " << rel_to_string<E>(rel.r_type)
|
||||
<< " relocation against symbol `" << sym
|
||||
<< "' can not be used; recompile with -fPIE";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::report_undef(Context<E> &ctx, Symbol<E> &sym) {
|
||||
switch (ctx.arg.unresolved_symbols) {
|
||||
case UnresolvedKind::ERROR:
|
||||
Error(ctx) << "undefined symbol: " << file << ": " << sym;
|
||||
break;
|
||||
case UnresolvedKind::WARN:
|
||||
Warn(ctx) << "undefined symbol: " << file << ": " << sym;
|
||||
break;
|
||||
case UnresolvedKind::IGNORE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template class InputSection<X86_64>;
|
||||
template class InputSection<I386>;
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/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 unittests.
|
||||
#
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
case "$ID-$VERSION_ID" in
|
||||
ubuntu-20.* | pop-20.*)
|
||||
apt-get update
|
||||
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++ g++-10
|
||||
apt-get install -y file
|
||||
;;
|
||||
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
|
||||
apt-get update
|
||||
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++
|
||||
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 util-linux
|
||||
;;
|
||||
opensuse-leap-*)
|
||||
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++ gcc11-c++
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
opensuse-tumbleweed-*)
|
||||
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
gentoo-*)
|
||||
emerge-webrsync
|
||||
emerge dev-util/cmake sys-libs/zlib
|
||||
;;
|
||||
arch-*)
|
||||
pacman -Sy
|
||||
pacman -S --needed --noconfirm base-devel zlib openssl cmake util-linux
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
|
||||
exit 1
|
||||
esac
|
||||
@@ -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
|
||||
@@ -1,682 +1,156 @@
|
||||
#include "mold.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <signal.h>
|
||||
#include <tbb/global_control.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <unistd.h>
|
||||
#include <unordered_set>
|
||||
|
||||
template <typename E>
|
||||
std::string_view save_string(Context<E> &ctx, const std::string &str) {
|
||||
u8 *buf = new u8[str.size() + 1];
|
||||
memcpy(buf, str.data(), str.size());
|
||||
buf[str.size()] = '\0';
|
||||
ctx.owning_bufs.push_back(std::unique_ptr<u8[]>(buf));
|
||||
return {(char *)buf, str.size()};
|
||||
#ifdef USE_SYSTEM_MIMALLOC
|
||||
#include <mimalloc-new-delete.h>
|
||||
#endif
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
std::string get_version_string() {
|
||||
if (strlen(GIT_HASH) == 0)
|
||||
return "mold " MOLD_VERSION " (compatible with GNU ld and GNU gold)";
|
||||
return "mold " MOLD_VERSION " (" GIT_HASH
|
||||
"; compatible with GNU ld and GNU gold)";
|
||||
namespace macho {
|
||||
int main(int argc, char **argv);
|
||||
}
|
||||
|
||||
std::regex glob_to_regex(std::string_view pattern) {
|
||||
std::stringstream ss;
|
||||
for (u8 c : pattern) {
|
||||
if (c == '*')
|
||||
ss << ".*";
|
||||
else
|
||||
ss << "\\x" << std::hex << std::setw(2) << std::setfill('0') << (int)c;
|
||||
}
|
||||
return std::regex(ss.str(), std::regex::optimize);
|
||||
static std::string get_mold_version() {
|
||||
std::string name = MOLD_IS_SOLD ? "mold (sold) " : "mold ";
|
||||
if (mold_git_hash.empty())
|
||||
return name + MOLD_VERSION + " (compatible with GNU ld)";
|
||||
return name + MOLD_VERSION + " (" + mold_git_hash + "; compatible with GNU ld)";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static ObjectFile<E> *new_object_file(Context<E> &ctx, MemoryMappedFile<E> *mb,
|
||||
std::string archive_name) {
|
||||
static Counter count("parsed_objs");
|
||||
count++;
|
||||
|
||||
bool in_lib = (!archive_name.empty() && !ctx.whole_archive);
|
||||
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mb, archive_name, in_lib);
|
||||
file->priority = ctx.file_priority++;
|
||||
ctx.tg.run([file, &ctx]() { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static SharedFile<E> *new_shared_file(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
SharedFile<E> *file = SharedFile<E>::create(ctx, mb);
|
||||
file->priority = ctx.file_priority++;
|
||||
ctx.tg.run([file, &ctx]() { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void read_file(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
if (ctx.visited.contains(mb->name))
|
||||
return;
|
||||
|
||||
if (ctx.is_preloading) {
|
||||
switch (get_file_type(ctx, mb)) {
|
||||
case FileType::OBJ:
|
||||
ctx.obj_cache.store(mb, new_object_file(ctx, mb, ""));
|
||||
return;
|
||||
case FileType::DSO:
|
||||
ctx.dso_cache.store(mb, new_shared_file(ctx, mb));
|
||||
return;
|
||||
case FileType::AR:
|
||||
for (MemoryMappedFile<E> *child : read_fat_archive_members(ctx, mb))
|
||||
if (get_file_type(ctx, child) == FileType::OBJ)
|
||||
ctx.obj_cache.store(mb, new_object_file(ctx, child, mb->name));
|
||||
return;
|
||||
case FileType::THIN_AR:
|
||||
for (MemoryMappedFile<E> *child : read_thin_archive_members(ctx, mb))
|
||||
if (get_file_type(ctx, child) == FileType::OBJ)
|
||||
ctx.obj_cache.store(child, new_object_file(ctx, child, mb->name));
|
||||
return;
|
||||
case FileType::TEXT:
|
||||
parse_linker_script(ctx, mb);
|
||||
return;
|
||||
case FileType::LLVM_BITCODE:
|
||||
Fatal(ctx) << mb->name << ": looks like this is an LLVM bitcode, but"
|
||||
<< " mold does not support LTO";
|
||||
default:
|
||||
Fatal(ctx) << mb->name << ": unknown file type";
|
||||
}
|
||||
}
|
||||
|
||||
switch (get_file_type(ctx, mb)) {
|
||||
case FileType::OBJ:
|
||||
if (ObjectFile<E> *obj = ctx.obj_cache.get_one(mb))
|
||||
ctx.objs.push_back(obj);
|
||||
else
|
||||
ctx.objs.push_back(new_object_file(ctx, mb, ""));
|
||||
return;
|
||||
case FileType::DSO:
|
||||
if (SharedFile<E> *obj = ctx.dso_cache.get_one(mb))
|
||||
ctx.dsos.push_back(obj);
|
||||
else
|
||||
ctx.dsos.push_back(new_shared_file(ctx, mb));
|
||||
ctx.visited.insert(mb->name);
|
||||
return;
|
||||
case FileType::AR: {
|
||||
std::vector<ObjectFile<E> *> objs = ctx.obj_cache.get(mb);
|
||||
if (!objs.empty()) {
|
||||
append(ctx.objs, objs);
|
||||
} else {
|
||||
for (MemoryMappedFile<E> *child : read_fat_archive_members(ctx, mb))
|
||||
if (get_file_type(ctx, child) == FileType::OBJ)
|
||||
ctx.objs.push_back(new_object_file(ctx, child, mb->name));
|
||||
}
|
||||
ctx.visited.insert(mb->name);
|
||||
return;
|
||||
}
|
||||
case FileType::THIN_AR:
|
||||
for (MemoryMappedFile<E> *child : read_thin_archive_members(ctx, mb)) {
|
||||
if (ObjectFile<E> *obj = ctx.obj_cache.get_one(child))
|
||||
ctx.objs.push_back(obj);
|
||||
else if (get_file_type(ctx, child) == FileType::OBJ)
|
||||
ctx.objs.push_back(new_object_file(ctx, child, mb->name));
|
||||
}
|
||||
ctx.visited.insert(mb->name);
|
||||
return;
|
||||
case FileType::TEXT:
|
||||
parse_linker_script(ctx, mb);
|
||||
return;
|
||||
case FileType::LLVM_BITCODE:
|
||||
Fatal(ctx) << mb->name << ": looks like this is an LLVM bitcode, "
|
||||
<< "but mold does not support LTO";
|
||||
default:
|
||||
Fatal(ctx) << mb->name << ": unknown file type";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void cleanup() {
|
||||
if (OutputFile<E>::tmpfile)
|
||||
unlink(OutputFile<E>::tmpfile);
|
||||
if (socket_tmpfile)
|
||||
unlink(socket_tmpfile);
|
||||
if (output_tmpfile)
|
||||
unlink(output_tmpfile);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void signal_handler(int) {
|
||||
cleanup<E>();
|
||||
std::string errno_string() {
|
||||
// strerror is not thread-safe, so guard it with a lock.
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
return strerror(errno);
|
||||
}
|
||||
|
||||
// Returns the path of the mold executable itself
|
||||
std::string get_self_path() {
|
||||
#ifdef __FreeBSD__
|
||||
// /proc may not be mounted on FreeBSD. The proper way to get the
|
||||
// current executable's path is to use sysctl(2).
|
||||
int mib[4];
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_PATHNAME;
|
||||
mib[3] = -1;
|
||||
|
||||
size_t size;
|
||||
sysctl(mib, 4, NULL, &size, NULL, 0);
|
||||
|
||||
std::string path;
|
||||
path.resize(size);
|
||||
sysctl(mib, 4, path.data(), &size, NULL, 0);
|
||||
return path;
|
||||
#else
|
||||
return std::filesystem::read_symlink("/proc/self/exe").string();
|
||||
#endif
|
||||
}
|
||||
|
||||
// mold mmap's an output file, and the mmap succeeds even if there's
|
||||
// no enough space left on the filesystem. The actual disk blocks are
|
||||
// not allocated on the mmap call but when the program writes to it
|
||||
// for the first time.
|
||||
//
|
||||
// If a disk becomes full as a result of a write to an mmap'ed memory
|
||||
// region, the failure of the write is reported as a SIGBUS or structured
|
||||
// exeption with code EXCEPTION_IN_PAGE_ERROR on Windows. This
|
||||
// signal handler catches that signal and prints out a user-friendly
|
||||
// error message. Without this, it is very hard to realize that the
|
||||
// disk might be full.
|
||||
#ifdef _WIN32
|
||||
|
||||
static LONG WINAPI vectored_handler(_EXCEPTION_POINTERS *exception_info) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock{mu};
|
||||
|
||||
PEXCEPTION_RECORD exception_record = exception_info->ExceptionRecord;
|
||||
ULONG_PTR *exception_information = exception_record->ExceptionInformation;
|
||||
if (exception_record->ExceptionCode == EXCEPTION_IN_PAGE_ERROR &&
|
||||
(ULONG_PTR)output_buffer_start <= exception_information[1] &&
|
||||
exception_information[1] < (ULONG_PTR)output_buffer_end) {
|
||||
|
||||
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
|
||||
(void)!write(_fileno(stderr), msg, sizeof(msg) - 1);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
// Read the beginning of a given file and returns its machine type
|
||||
// (e.g. EM_X86_64 or EM_386). Return -1 if unknown.
|
||||
template <typename E>
|
||||
static i64 get_machine_type(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
switch (get_file_type(ctx, mb)) {
|
||||
case FileType::DSO:
|
||||
return ((ElfEhdr<E> *)mb->data(ctx))->e_machine;
|
||||
case FileType::AR:
|
||||
for (MemoryMappedFile<E> *child : read_fat_archive_members(ctx, mb))
|
||||
if (get_file_type(ctx, child) == FileType::OBJ)
|
||||
return ((ElfEhdr<E> *)child->data(ctx))->e_machine;
|
||||
return -1;
|
||||
case FileType::THIN_AR:
|
||||
for (MemoryMappedFile<E> *child : read_thin_archive_members(ctx, mb))
|
||||
if (get_file_type(ctx, child) == FileType::OBJ)
|
||||
return ((ElfEhdr<E> *)child->data(ctx))->e_machine;
|
||||
return -1;
|
||||
case FileType::TEXT:
|
||||
return get_script_output_type(ctx, mb);
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
void install_signal_handler() {
|
||||
AddVectoredExceptionHandler(0, vectored_handler);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static MemoryMappedFile<E> *open_library(Context<E> &ctx, std::string path) {
|
||||
MemoryMappedFile<E> *mb = MemoryMappedFile<E>::open(ctx, path);
|
||||
if (!mb)
|
||||
return nullptr;
|
||||
#else
|
||||
|
||||
i64 type = get_machine_type(ctx, mb);
|
||||
if (type == -1 || type == E::e_machine)
|
||||
return mb;
|
||||
Warn(ctx) << path << ": skipping incompatible file";
|
||||
return nullptr;
|
||||
static void sighandler(int signo, siginfo_t *info, void *ucontext) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock{mu};
|
||||
|
||||
if ((signo == SIGSEGV || signo == SIGBUS) &&
|
||||
output_buffer_start <= info->si_addr &&
|
||||
info->si_addr < output_buffer_end) {
|
||||
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
|
||||
(void)!write(STDERR_FILENO, msg, sizeof(msg) - 1);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MemoryMappedFile<E> *find_library(Context<E> &ctx, std::string name) {
|
||||
if (name.starts_with(':')) {
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string root = dir.starts_with("/") ? ctx.arg.sysroot : "";
|
||||
std::string path = root + std::string(dir) + "/" + name.substr(1);
|
||||
if (MemoryMappedFile<E> *mb = open_library(ctx, path))
|
||||
return mb;
|
||||
}
|
||||
Fatal(ctx) << "library not found: " << name;
|
||||
}
|
||||
void install_signal_handler() {
|
||||
struct sigaction action;
|
||||
action.sa_sigaction = sighandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string root = dir.starts_with("/") ? ctx.arg.sysroot : "";
|
||||
std::string stem = root + std::string(dir) + "/lib" + name;
|
||||
if (!ctx.is_static)
|
||||
if (MemoryMappedFile<E> *mb = open_library(ctx, stem + ".so"))
|
||||
return mb;
|
||||
if (MemoryMappedFile<E> *mb = open_library(ctx, stem + ".a"))
|
||||
return mb;
|
||||
}
|
||||
Fatal(ctx) << "library not found: " << name;
|
||||
sigaction(SIGINT, &action, NULL);
|
||||
sigaction(SIGTERM, &action, NULL);
|
||||
sigaction(SIGBUS, &action, NULL);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_input_files(Context<E> &ctx, std::span<std::string_view> args) {
|
||||
std::vector<std::tuple<bool, bool, bool>> state;
|
||||
#endif
|
||||
|
||||
while (!args.empty()) {
|
||||
std::string_view arg;
|
||||
|
||||
if (read_flag(args, "as-needed")) {
|
||||
ctx.as_needed = true;
|
||||
} else if (read_flag(args, "no-as-needed")) {
|
||||
ctx.as_needed = false;
|
||||
} else if (read_flag(args, "whole-archive")) {
|
||||
ctx.whole_archive = true;
|
||||
} else if (read_flag(args, "no-whole-archive")) {
|
||||
ctx.whole_archive = false;
|
||||
} else if (read_flag(args, "Bstatic")) {
|
||||
ctx.is_static = true;
|
||||
} else if (read_flag(args, "Bdynamic")) {
|
||||
ctx.is_static = false;
|
||||
} else if (read_arg(ctx, args, arg, "version-script")) {
|
||||
parse_version_script(ctx, std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "dynamic-list")) {
|
||||
parse_dynamic_list(ctx, std::string(arg));
|
||||
} else if (read_flag(args, "push-state")) {
|
||||
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static});
|
||||
} else if (read_flag(args, "pop-state")) {
|
||||
if (state.empty())
|
||||
Fatal(ctx) << "no state pushed before popping";
|
||||
std::tie(ctx.as_needed, ctx.whole_archive, ctx.is_static) = state.back();
|
||||
state.pop_back();
|
||||
} else if (read_arg(ctx, args, arg, "l")) {
|
||||
MemoryMappedFile<E> *mb = find_library(ctx, std::string(arg));
|
||||
mb->given_fullpath = false;
|
||||
read_file(ctx, mb);
|
||||
} else {
|
||||
read_file(ctx, MemoryMappedFile<E>::must_open(ctx, std::string(args[0])));
|
||||
args = args.subspan(1);
|
||||
}
|
||||
}
|
||||
i64 get_default_thread_count() {
|
||||
// mold doesn't scale well above 32 threads.
|
||||
int n = tbb::global_control::active_value(
|
||||
tbb::global_control::max_allowed_parallelism);
|
||||
return std::min(n, 32);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void show_stats(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *obj : ctx.objs) {
|
||||
static Counter defined("defined_syms");
|
||||
defined += obj->first_global - 1;
|
||||
|
||||
static Counter undefined("undefined_syms");
|
||||
undefined += obj->symbols.size() - obj->first_global;
|
||||
|
||||
for (std::unique_ptr<InputSection<E>> &sec : obj->sections) {
|
||||
if (!sec || !sec->is_alive)
|
||||
continue;
|
||||
|
||||
static Counter alloc("reloc_alloc");
|
||||
static Counter nonalloc("reloc_nonalloc");
|
||||
|
||||
if (sec->shdr.sh_flags & SHF_ALLOC)
|
||||
alloc += sec->get_rels(ctx).size();
|
||||
else
|
||||
nonalloc += sec->get_rels(ctx).size();
|
||||
}
|
||||
|
||||
static Counter comdats("comdats");
|
||||
comdats += obj->comdat_groups.size();
|
||||
|
||||
static Counter removed_comdats("removed_comdat_mem");
|
||||
for (auto &pair : obj->comdat_groups)
|
||||
if (ComdatGroup *group = pair.first; group->owner != obj->priority)
|
||||
removed_comdats += pair.second.size();
|
||||
|
||||
static Counter num_cies("num_cies");
|
||||
num_cies += obj->cies.size();
|
||||
|
||||
static Counter num_unique_cies("num_unique_cies");
|
||||
for (CieRecord<E> &cie : obj->cies)
|
||||
if (cie.is_leader)
|
||||
num_unique_cies++;
|
||||
|
||||
static Counter num_fdes("num_fdes");
|
||||
num_fdes += obj->fdes.size();
|
||||
}
|
||||
|
||||
static Counter num_bytes("total_input_bytes");
|
||||
for (std::unique_ptr<MemoryMappedFile<E>> &mb : ctx.owning_mbs)
|
||||
num_bytes += mb->size();
|
||||
|
||||
static Counter num_input_sections("input_sections");
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
num_input_sections += file->sections.size();
|
||||
|
||||
static Counter num_output_chunks("output_chunks", ctx.chunks.size());
|
||||
static Counter num_objs("num_objs", ctx.objs.size());
|
||||
static Counter num_dsos("num_dsos", ctx.dsos.size());
|
||||
|
||||
Counter::print();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
int do_main(int argc, char **argv) {
|
||||
Context<E> ctx;
|
||||
|
||||
// Process -run option first. process_run_subcommand() does not return.
|
||||
if (argc >= 2)
|
||||
if (std::string_view arg = argv[1]; arg == "-run" || arg == "--run")
|
||||
process_run_subcommand(ctx, argc, argv);
|
||||
|
||||
Timer t_all(ctx, "all");
|
||||
|
||||
// Parse non-positional command line options
|
||||
ctx.cmdline_args = expand_response_files(ctx, argv);
|
||||
std::vector<std::string_view> file_args;
|
||||
parse_nonpositional_args(ctx, file_args);
|
||||
|
||||
if (ctx.arg.relocatable) {
|
||||
combine_objects(ctx, file_args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ctx.arg.preload)
|
||||
try_resume_daemon(ctx);
|
||||
|
||||
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
|
||||
ctx.arg.thread_count);
|
||||
|
||||
signal(SIGINT, signal_handler<E>);
|
||||
signal(SIGTERM, signal_handler<E>);
|
||||
|
||||
if (!ctx.arg.directory.empty() && chdir(ctx.arg.directory.c_str()) == -1)
|
||||
Fatal(ctx) << "chdir failed: " << ctx.arg.directory
|
||||
<< ": " << strerror(errno);
|
||||
|
||||
// Handle --wrap options if any.
|
||||
for (std::string_view name : ctx.arg.wrap)
|
||||
Symbol<E>::intern(ctx, name)->wrap = true;
|
||||
|
||||
// Handle --retain-symbols-file options if any.
|
||||
if (ctx.arg.retain_symbols_file)
|
||||
for (std::string_view name : *ctx.arg.retain_symbols_file)
|
||||
Symbol<E>::intern(ctx, name)->write_to_symtab = true;
|
||||
|
||||
// Preload input files
|
||||
std::function<void()> on_complete;
|
||||
|
||||
if (ctx.arg.preload) {
|
||||
Timer t(ctx, "preload");
|
||||
std::function<void()> wait_for_client;
|
||||
daemonize(ctx, &wait_for_client, &on_complete);
|
||||
|
||||
ctx.reset_reader_context(true);
|
||||
read_input_files(ctx, file_args);
|
||||
ctx.tg.wait();
|
||||
t.stop();
|
||||
|
||||
Timer t2(ctx, "wait_for_client");
|
||||
wait_for_client();
|
||||
} else if (ctx.arg.fork) {
|
||||
on_complete = fork_child();
|
||||
}
|
||||
|
||||
for (std::string_view arg : ctx.arg.trace_symbol)
|
||||
Symbol<E>::intern(ctx, arg)->traced = true;
|
||||
|
||||
// Parse input files
|
||||
{
|
||||
Timer t(ctx, "parse");
|
||||
ctx.reset_reader_context(false);
|
||||
read_input_files(ctx, file_args);
|
||||
ctx.tg.wait();
|
||||
}
|
||||
|
||||
if (ctx.objs.empty())
|
||||
Fatal(ctx) << "no input files";
|
||||
|
||||
// Uniquify shared object files by soname
|
||||
{
|
||||
std::unordered_set<std::string_view> seen;
|
||||
erase(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
return !seen.insert(file->soname).second;
|
||||
});
|
||||
}
|
||||
|
||||
Timer t_total(ctx, "total");
|
||||
Timer t_before_copy(ctx, "before_copy");
|
||||
|
||||
// Apply -exclude-libs
|
||||
apply_exclude_libs(ctx);
|
||||
|
||||
// Create instances of linker-synthesized sections such as
|
||||
// .got or .plt.
|
||||
create_synthetic_sections(ctx);
|
||||
|
||||
// Resolve symbols and fix the set of object files that are
|
||||
// included to the final output.
|
||||
resolve_symbols(ctx);
|
||||
|
||||
// Remove redundant comdat sections (e.g. duplicate inline functions).
|
||||
eliminate_comdats(ctx);
|
||||
|
||||
// Create .bss sections for common symbols.
|
||||
convert_common_symbols(ctx);
|
||||
|
||||
// Apply version scripts.
|
||||
apply_version_script(ctx);
|
||||
|
||||
// Parse symbol version suffixes (e.g. "foo@ver1").
|
||||
parse_symbol_version(ctx);
|
||||
|
||||
// Set is_import and is_export bits for each symbol.
|
||||
compute_import_export(ctx);
|
||||
|
||||
// Garbage-collect unreachable sections.
|
||||
if (ctx.arg.gc_sections)
|
||||
gc_sections(ctx);
|
||||
|
||||
// Merge identical read-only sections.
|
||||
if (ctx.arg.icf)
|
||||
icf_sections(ctx);
|
||||
|
||||
// Compute sizes of sections containing mergeable strings.
|
||||
compute_merged_section_sizes(ctx);
|
||||
|
||||
// ctx input sections into output sections
|
||||
bin_sections(ctx);
|
||||
|
||||
// Get a list of output sections.
|
||||
append(ctx.chunks, collect_output_sections(ctx));
|
||||
|
||||
// Create a dummy file containing linker-synthesized symbols
|
||||
// (e.g. `__bss_start`).
|
||||
ctx.internal_obj = ObjectFile<E>::create_internal_file(ctx);
|
||||
ctx.internal_obj->resolve_regular_symbols(ctx);
|
||||
ctx.objs.push_back(ctx.internal_obj);
|
||||
|
||||
// Beyond this point, no new files will be added to ctx.objs
|
||||
// or ctx.dsos.
|
||||
|
||||
// If we are linking a .so file, remaining undefined symbols does
|
||||
// not cause a linker error. Instead, they are treated as if they
|
||||
// were imported symbols.
|
||||
//
|
||||
// If we are linking an executable, weak undefs are converted to
|
||||
// weakly imported symbol so that they'll have another chance to be
|
||||
claim_unresolved_symbols(ctx);
|
||||
|
||||
// Beyond this point, no new symbols will be added to the result.
|
||||
|
||||
// Make sure that all symbols have been resolved.
|
||||
if (!ctx.arg.allow_multiple_definition)
|
||||
check_duplicate_symbols(ctx);
|
||||
|
||||
// .init_array and .fini_array contents have to be sorted by
|
||||
// a special rule. Sort them.
|
||||
sort_init_fini(ctx);
|
||||
|
||||
// Compute sizes of output sections while assigning offsets
|
||||
// within an output section to input sections.
|
||||
compute_section_sizes(ctx);
|
||||
|
||||
// Sort sections by section attributes so that we'll have to
|
||||
// create as few segments as possible.
|
||||
sort(ctx.chunks, [&](OutputChunk<E> *a, OutputChunk<E> *b) {
|
||||
return get_section_rank(ctx, a) < get_section_rank(ctx, b);
|
||||
});
|
||||
|
||||
// Copy string referred by .dynamic to .dynstr.
|
||||
for (SharedFile<E> *file : ctx.dsos)
|
||||
ctx.dynstr->add_string(file->soname);
|
||||
for (std::string_view str : ctx.arg.auxiliary)
|
||||
ctx.dynstr->add_string(str);
|
||||
for (std::string_view str : ctx.arg.filter)
|
||||
ctx.dynstr->add_string(str);
|
||||
if (!ctx.arg.rpaths.empty())
|
||||
ctx.dynstr->add_string(ctx.arg.rpaths);
|
||||
if (!ctx.arg.soname.empty())
|
||||
ctx.dynstr->add_string(ctx.arg.soname);
|
||||
|
||||
// Scan relocations to find symbols that need entries in .got, .plt,
|
||||
// .got.plt, .dynsym, .dynstr, etc.
|
||||
scan_rels(ctx);
|
||||
|
||||
// Reserve a space for dynamic symbol strings in .dynstr and sort
|
||||
// .dynsym contents if necessary. Beyond this point, no symbol will
|
||||
// be added to .dynsym.
|
||||
ctx.dynsym->finalize(ctx);
|
||||
|
||||
// Fill .gnu.version_d section contents.
|
||||
ctx.verdef->construct(ctx);
|
||||
|
||||
// Fill .gnu.version_r section contents.
|
||||
ctx.verneed->construct(ctx);
|
||||
|
||||
// Compute .symtab and .strtab sizes for each file.
|
||||
{
|
||||
Timer t(ctx, "compute_symtab");
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->compute_symtab(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
// .eh_frame is a special section from the linker's point of view,
|
||||
// as its contents are parsed and reconstructed by the linker,
|
||||
// unlike other sections that are regarded as opaque bytes.
|
||||
// Here, we transplant .eh_frame sections from a regular output
|
||||
// section to the special EHFrameSection.
|
||||
{
|
||||
Timer t(ctx, "eh_frame");
|
||||
erase(ctx.chunks, [](OutputChunk<E> *chunk) {
|
||||
return chunk->kind == OutputChunk<E>::REGULAR &&
|
||||
chunk->name == ".eh_frame";
|
||||
});
|
||||
ctx.eh_frame->construct(ctx);
|
||||
}
|
||||
|
||||
// Now that we have computed sizes for all sections and assigned
|
||||
// section indices to them, so we can fix section header contents
|
||||
// for all output sections.
|
||||
for (OutputChunk<E> *chunk : ctx.chunks)
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
erase(ctx.chunks, [](OutputChunk<E> *chunk) {
|
||||
return chunk->kind == OutputChunk<E>::SYNTHETIC &&
|
||||
chunk->shdr.sh_size == 0;
|
||||
});
|
||||
|
||||
// Set section indices.
|
||||
for (i64 i = 0, shndx = 1; i < ctx.chunks.size(); i++)
|
||||
if (ctx.chunks[i]->kind != OutputChunk<E>::HEADER)
|
||||
ctx.chunks[i]->shndx = shndx++;
|
||||
|
||||
for (OutputChunk<E> *chunk : ctx.chunks)
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
// Assign offsets to output sections
|
||||
i64 filesize = set_osec_offsets(ctx);
|
||||
|
||||
// Fix linker-synthesized symbol addresses.
|
||||
fix_synthetic_symbols(ctx);
|
||||
|
||||
// If --compress-debug-sections is given, compress .debug_* sections
|
||||
// using zlib.
|
||||
if (ctx.arg.compress_debug_sections != COMPRESS_NONE) {
|
||||
compress_debug_sections(ctx);
|
||||
filesize = set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
// At this point, file layout is fixed.
|
||||
|
||||
// Beyond this, you can assume that symbol addresses including their
|
||||
// GOT or PLT addresses have a correct final value.
|
||||
|
||||
// Some types of relocations for TLS symbols need the TLS segment
|
||||
// address. Find it out now.
|
||||
for (ElfPhdr<E> phdr : create_phdr(ctx)) {
|
||||
if (phdr.p_type == PT_TLS) {
|
||||
ctx.tls_begin = phdr.p_vaddr;
|
||||
ctx.tls_end = align_to(phdr.p_vaddr + phdr.p_memsz, phdr.p_align);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
t_before_copy.stop();
|
||||
|
||||
// Create an output file
|
||||
ctx.output_file = OutputFile<E>::open(ctx, ctx.arg.output, filesize, 0777);
|
||||
ctx.buf = ctx.output_file->buf;
|
||||
|
||||
Timer t_copy(ctx, "copy");
|
||||
|
||||
// Copy input sections to the output file
|
||||
{
|
||||
Timer t(ctx, "copy_buf");
|
||||
|
||||
tbb::parallel_for_each(ctx.chunks, [&](OutputChunk<E> *chunk) {
|
||||
std::string name(chunk->name);
|
||||
if (name.empty())
|
||||
name = "(header)";
|
||||
Timer t2(ctx, name, &t);
|
||||
|
||||
chunk->copy_buf(ctx);
|
||||
});
|
||||
|
||||
Error<E>::checkpoint(ctx);
|
||||
}
|
||||
|
||||
// Dynamic linker works better with sorted .rela.dyn section,
|
||||
// so we sort them.
|
||||
ctx.reldyn->sort(ctx);
|
||||
|
||||
// Zero-clear paddings between sections
|
||||
clear_padding(ctx);
|
||||
|
||||
if (ctx.buildid) {
|
||||
Timer t(ctx, "build_id");
|
||||
ctx.buildid->write_buildid(ctx);
|
||||
}
|
||||
|
||||
t_copy.stop();
|
||||
|
||||
// Commit
|
||||
ctx.output_file->close(ctx);
|
||||
|
||||
t_total.stop();
|
||||
t_all.stop();
|
||||
|
||||
if (ctx.arg.print_map)
|
||||
print_map(ctx);
|
||||
|
||||
// Show stats numbers
|
||||
if (ctx.arg.stats)
|
||||
show_stats(ctx);
|
||||
|
||||
if (ctx.arg.perf)
|
||||
Timer<E>::print(ctx);
|
||||
|
||||
std::cout << std::flush;
|
||||
std::cerr << std::flush;
|
||||
if (on_complete)
|
||||
on_complete();
|
||||
|
||||
if (ctx.arg.quick_exit)
|
||||
std::quick_exit(0);
|
||||
|
||||
for (std::function<void()> &fn : ctx.on_exit)
|
||||
fn();
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum class MachineType { X86_64, I386 };
|
||||
|
||||
static MachineType get_machine_type(int argc, char **argv) {
|
||||
for (i64 i = 1; i < argc; i++) {
|
||||
if (std::string_view(argv[i]) == "-m") {
|
||||
if (i + 1 == argc)
|
||||
break;
|
||||
i++;
|
||||
|
||||
std::string_view val(argv[i]);
|
||||
if (val == "elf_x86_64")
|
||||
return MachineType::X86_64;
|
||||
if (val == "elf_i386")
|
||||
return MachineType::I386;
|
||||
std::cerr << "unknown -m argument: " << val << "\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
return MachineType::X86_64;
|
||||
}
|
||||
} // namespace mold
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
switch (get_machine_type(argc, argv)) {
|
||||
case MachineType::X86_64:
|
||||
return do_main<X86_64>(argc, argv);
|
||||
case MachineType::I386:
|
||||
return do_main<I386>(argc, argv);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void read_file(Context<E> &, MemoryMappedFile<E> *); \
|
||||
template std::string_view save_string(Context<E> &, const std::string &)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
@@ -1,118 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
template <typename E>
|
||||
MemoryMappedFile<E> *
|
||||
MemoryMappedFile<E>::open(Context<E> &ctx, std::string path) {
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) == -1)
|
||||
return nullptr;
|
||||
u64 mtime = (u64)st.st_mtim.tv_sec * 1000000000 + st.st_mtim.tv_nsec;
|
||||
|
||||
u8 *data = nullptr;
|
||||
if (st.st_size == 0) {
|
||||
// mmap(2) doesn't allow 0 as an argument for length parameter,
|
||||
// so this case has to be handled as a special case.
|
||||
static u8 dummy[1];
|
||||
data = dummy;
|
||||
}
|
||||
|
||||
MemoryMappedFile *mb = new MemoryMappedFile(path, data, st.st_size, mtime);
|
||||
ctx.owning_mbs.push_back(std::unique_ptr<MemoryMappedFile>(mb));
|
||||
return mb;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MemoryMappedFile<E> *
|
||||
MemoryMappedFile<E>::must_open(Context<E> &ctx, std::string path) {
|
||||
if (MemoryMappedFile *mb = MemoryMappedFile::open(ctx, path))
|
||||
return mb;
|
||||
Fatal(ctx) << "cannot open " << path;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u8 *MemoryMappedFile<E>::data(Context<E> &ctx) {
|
||||
if (data_)
|
||||
return data_;
|
||||
|
||||
std::lock_guard lock(mu);
|
||||
if (data_)
|
||||
return data_;
|
||||
|
||||
i64 fd = ::open(name.c_str(), O_RDONLY);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << name << ": cannot open: " << strerror(errno);
|
||||
|
||||
data_ = (u8 *)mmap(nullptr, size_, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (data_ == MAP_FAILED)
|
||||
Fatal(ctx) << name << ": mmap failed: " << strerror(errno);
|
||||
close(fd);
|
||||
return data_;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MemoryMappedFile<E> *
|
||||
MemoryMappedFile<E>::slice(Context<E> &ctx, std::string name, u64 start,
|
||||
u64 size) {
|
||||
MemoryMappedFile *mb = new MemoryMappedFile<E>(name, data_ + start, size);
|
||||
ctx.owning_mbs.push_back(std::unique_ptr<MemoryMappedFile>(mb));
|
||||
mb->parent = this;
|
||||
return mb;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MemoryMappedFile<E>::~MemoryMappedFile() {
|
||||
if (data_ && !parent)
|
||||
munmap(data_, size_);
|
||||
}
|
||||
|
||||
|
||||
template <typename E>
|
||||
static bool is_text_file(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
u8 *data = mb->data(ctx);
|
||||
return mb->size() >= 4 &&
|
||||
isprint(data[0]) &&
|
||||
isprint(data[1]) &&
|
||||
isprint(data[2]) &&
|
||||
isprint(data[3]);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
FileType get_file_type(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
u8 *data = mb->data(ctx);
|
||||
|
||||
if (mb->size() >= 20 && memcmp(data, "\177ELF", 4) == 0) {
|
||||
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)data;
|
||||
if (ehdr.e_type == ET_REL)
|
||||
return FileType::OBJ;
|
||||
if (ehdr.e_type == ET_DYN)
|
||||
return FileType::DSO;
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
if (mb->size() >= 8 && memcmp(data, "!<arch>\n", 8) == 0)
|
||||
return FileType::AR;
|
||||
if (mb->size() >= 8 && memcmp(data, "!<thin>\n", 8) == 0)
|
||||
return FileType::THIN_AR;
|
||||
if (is_text_file(ctx, mb))
|
||||
return FileType::TEXT;
|
||||
if (mb->size() >= 4 && memcmp(data, "\xDE\xC0\x17\x0B", 4) == 0)
|
||||
return FileType::LLVM_BITCODE;
|
||||
if (mb->size() >= 4 && memcmp(data, "BC\xC0\xDE", 4) == 0)
|
||||
return FileType::LLVM_BITCODE;
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class MemoryMappedFile<E>; \
|
||||
template FileType get_file_type(Context<E> &, MemoryMappedFile<E> *)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
@@ -1,2 +0,0 @@
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/mimalloc.cmake)
|
||||
get_filename_component(MIMALLOC_TARGET_DIR "${CMAKE_CURRENT_LIST_DIR}" PATH)
|
||||
|
Before Width: | Height: | Size: 164 KiB |
@@ -1,122 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen 1.8.15"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>mi-malloc: Data Structures</title>
|
||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="jquery.js"></script>
|
||||
<script type="text/javascript" src="dynsections.js"></script>
|
||||
<link href="navtree.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="resize.js"></script>
|
||||
<script type="text/javascript" src="navtreedata.js"></script>
|
||||
<script type="text/javascript" src="navtree.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(initResizable);
|
||||
/* @license-end */</script>
|
||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||
<script type="text/javascript" src="search/search.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function() { init_search(); });
|
||||
/* @license-end */
|
||||
</script>
|
||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
||||
<link href="mimalloc-doxygen.css" rel="stylesheet" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 56px;">
|
||||
<td id="projectlogo"><img alt="Logo" src="mimalloc-logo.svg"/></td>
|
||||
<td id="projectalign" style="padding-left: 0.5em;">
|
||||
<div id="projectname">mi-malloc
|
||||
 <span id="projectnumber">1.6</span>
|
||||
</div>
|
||||
</td>
|
||||
<td> <div id="MSearchBox" class="MSearchBoxInactive">
|
||||
<span class="left">
|
||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
alt=""/>
|
||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
||||
</span><span class="right">
|
||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- end header part -->
|
||||
<!-- Generated by Doxygen 1.8.15 -->
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
||||
/* @license-end */
|
||||
</script>
|
||||
</div><!-- top -->
|
||||
<div id="side-nav" class="ui-resizable side-nav-resizable">
|
||||
<div id="nav-tree">
|
||||
<div id="nav-tree-contents">
|
||||
<div id="nav-sync" class="sync"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="splitbar" style="-moz-user-select:none;"
|
||||
class="ui-resizable-handle">
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function(){initNavTree('annotated.html','');});
|
||||
/* @license-end */
|
||||
</script>
|
||||
<div id="doc-content">
|
||||
<!-- window showing the filter options -->
|
||||
<div id="MSearchSelectWindow"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
||||
</div>
|
||||
|
||||
<!-- iframe showing the search results (closed by default) -->
|
||||
<div id="MSearchResultsWindow">
|
||||
<iframe src="javascript:void(0)" frameborder="0"
|
||||
name="MSearchResults" id="MSearchResults">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<div class="headertitle">
|
||||
<div class="title">Data Structures</div> </div>
|
||||
</div><!--header-->
|
||||
<div class="contents">
|
||||
<div class="textblock">Here are the data structures with brief descriptions:</div><div class="directory">
|
||||
<table class="directory">
|
||||
<tr id="row_0_" class="even"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icona"><span class="icon">C</span></span><a class="el" href="group__analysis.html#structmi__heap__area__t" target="_self">mi_heap_area_t</a></td><td class="desc">An area of heap space contains blocks of a single size </td></tr>
|
||||
<tr id="row_1_"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icona"><span class="icon">C</span></span><a class="el" href="group__cpp.html#structmi__stl__allocator" target="_self">mi_stl_allocator</a></td><td class="desc"><em>std::allocator</em> implementation for mimalloc for use in STL containers </td></tr>
|
||||
</table>
|
||||
</div><!-- directory -->
|
||||
</div><!-- contents -->
|
||||
</div><!-- doc-content -->
|
||||
<!-- start footer part -->
|
||||
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
<li class="footer">Generated by
|
||||
<a href="http://www.doxygen.org/index.html">
|
||||
<img class="footer" src="doxygen.png" alt="doxygen"/></a> 1.8.15 </li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +0,0 @@
|
||||
var annotated_dup =
|
||||
[
|
||||
[ "mi_heap_area_t", "group__analysis.html#structmi__heap__area__t", "group__analysis_structmi__heap__area__t" ],
|
||||
[ "mi_stl_allocator", "group__cpp.html#structmi__stl__allocator", null ]
|
||||
];
|
||||
|
Before Width: | Height: | Size: 657 B |
|
Before Width: | Height: | Size: 142 B |
@@ -1,120 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen 1.8.15"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>mi-malloc: Performance</title>
|
||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="jquery.js"></script>
|
||||
<script type="text/javascript" src="dynsections.js"></script>
|
||||
<link href="navtree.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="resize.js"></script>
|
||||
<script type="text/javascript" src="navtreedata.js"></script>
|
||||
<script type="text/javascript" src="navtree.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(initResizable);
|
||||
/* @license-end */</script>
|
||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||
<script type="text/javascript" src="search/search.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function() { init_search(); });
|
||||
/* @license-end */
|
||||
</script>
|
||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
||||
<link href="mimalloc-doxygen.css" rel="stylesheet" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 56px;">
|
||||
<td id="projectlogo"><img alt="Logo" src="mimalloc-logo.svg"/></td>
|
||||
<td id="projectalign" style="padding-left: 0.5em;">
|
||||
<div id="projectname">mi-malloc
|
||||
 <span id="projectnumber">1.6</span>
|
||||
</div>
|
||||
</td>
|
||||
<td> <div id="MSearchBox" class="MSearchBoxInactive">
|
||||
<span class="left">
|
||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
alt=""/>
|
||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
||||
</span><span class="right">
|
||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- end header part -->
|
||||
<!-- Generated by Doxygen 1.8.15 -->
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
||||
/* @license-end */
|
||||
</script>
|
||||
</div><!-- top -->
|
||||
<div id="side-nav" class="ui-resizable side-nav-resizable">
|
||||
<div id="nav-tree">
|
||||
<div id="nav-tree-contents">
|
||||
<div id="nav-sync" class="sync"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="splitbar" style="-moz-user-select:none;"
|
||||
class="ui-resizable-handle">
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function(){initNavTree('bench.html','');});
|
||||
/* @license-end */
|
||||
</script>
|
||||
<div id="doc-content">
|
||||
<!-- window showing the filter options -->
|
||||
<div id="MSearchSelectWindow"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
||||
</div>
|
||||
|
||||
<!-- iframe showing the search results (closed by default) -->
|
||||
<div id="MSearchResultsWindow">
|
||||
<iframe src="javascript:void(0)" frameborder="0"
|
||||
name="MSearchResults" id="MSearchResults">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
<div class="PageDoc"><div class="header">
|
||||
<div class="headertitle">
|
||||
<div class="title">Performance </div> </div>
|
||||
</div><!--header-->
|
||||
<div class="contents">
|
||||
<div class="textblock"><p>We tested <em>mimalloc</em> against many other top allocators over a wide range of benchmarks, ranging from various real world programs to synthetic benchmarks that see how the allocator behaves under more extreme circumstances.</p>
|
||||
<p>In our benchmarks, <em>mimalloc</em> always outperforms all other leading allocators (<em>jemalloc</em>, <em>tcmalloc</em>, <em>Hoard</em>, etc) (Apr 2019), and usually uses less memory (up to 25% more in the worst case). A nice property is that it does <em>consistently</em> well over the wide range of benchmarks.</p>
|
||||
<p>See the <a href="https://github.com/microsoft/mimalloc#Performance">Performance</a> section in the <em>mimalloc</em> repository for benchmark results, or the the technical report for detailed benchmark results. </p>
|
||||
</div></div><!-- PageDoc -->
|
||||
</div><!-- contents -->
|
||||
</div><!-- doc-content -->
|
||||
<!-- start footer part -->
|
||||
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
<li class="footer">Generated by
|
||||
<a href="http://www.doxygen.org/index.html">
|
||||
<img class="footer" src="doxygen.png" alt="doxygen"/></a> 1.8.15 </li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,130 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen 1.8.15"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>mi-malloc: Building</title>
|
||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="jquery.js"></script>
|
||||
<script type="text/javascript" src="dynsections.js"></script>
|
||||
<link href="navtree.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="resize.js"></script>
|
||||
<script type="text/javascript" src="navtreedata.js"></script>
|
||||
<script type="text/javascript" src="navtree.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(initResizable);
|
||||
/* @license-end */</script>
|
||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||
<script type="text/javascript" src="search/search.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function() { init_search(); });
|
||||
/* @license-end */
|
||||
</script>
|
||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
||||
<link href="mimalloc-doxygen.css" rel="stylesheet" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 56px;">
|
||||
<td id="projectlogo"><img alt="Logo" src="mimalloc-logo.svg"/></td>
|
||||
<td id="projectalign" style="padding-left: 0.5em;">
|
||||
<div id="projectname">mi-malloc
|
||||
 <span id="projectnumber">1.6</span>
|
||||
</div>
|
||||
</td>
|
||||
<td> <div id="MSearchBox" class="MSearchBoxInactive">
|
||||
<span class="left">
|
||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
alt=""/>
|
||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
||||
</span><span class="right">
|
||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- end header part -->
|
||||
<!-- Generated by Doxygen 1.8.15 -->
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
||||
/* @license-end */
|
||||
</script>
|
||||
</div><!-- top -->
|
||||
<div id="side-nav" class="ui-resizable side-nav-resizable">
|
||||
<div id="nav-tree">
|
||||
<div id="nav-tree-contents">
|
||||
<div id="nav-sync" class="sync"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="splitbar" style="-moz-user-select:none;"
|
||||
class="ui-resizable-handle">
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function(){initNavTree('build.html','');});
|
||||
/* @license-end */
|
||||
</script>
|
||||
<div id="doc-content">
|
||||
<!-- window showing the filter options -->
|
||||
<div id="MSearchSelectWindow"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
||||
</div>
|
||||
|
||||
<!-- iframe showing the search results (closed by default) -->
|
||||
<div id="MSearchResultsWindow">
|
||||
<iframe src="javascript:void(0)" frameborder="0"
|
||||
name="MSearchResults" id="MSearchResults">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
<div class="PageDoc"><div class="header">
|
||||
<div class="headertitle">
|
||||
<div class="title">Building </div> </div>
|
||||
</div><!--header-->
|
||||
<div class="contents">
|
||||
<div class="textblock"><p>Checkout the sources from Github: </p><div class="fragment"><div class="line">git clone https:<span class="comment">//github.com/microsoft/mimalloc</span></div></div><!-- fragment --><h2>Windows</h2>
|
||||
<p>Open <code>ide/vs2019/mimalloc.sln</code> in Visual Studio 2019 and build (or <code>ide/vs2017/mimalloc.sln</code>). The <code>mimalloc</code> project builds a static library (in <code>out/msvc-x64</code>), while the <code>mimalloc-override</code> project builds a DLL for overriding malloc in the entire program.</p>
|
||||
<h2>macOS, Linux, BSD, etc.</h2>
|
||||
<p>We use <a href="https://cmake.org"><code>cmake</code></a><sup>1</sup> as the build system:</p>
|
||||
<div class="fragment"><div class="line">> mkdir -p out/release</div><div class="line">> cd out/release</div><div class="line">> cmake ../..</div><div class="line">> make</div></div><!-- fragment --><p> This builds the library as a shared (dynamic) library (<code>.so</code> or <code>.dylib</code>), a static library (<code>.a</code>), and as a single object file (<code>.o</code>).</p>
|
||||
<p><code>> sudo make install</code> (install the library and header files in <code>/usr/local/lib</code> and <code>/usr/local/include</code>)</p>
|
||||
<p>You can build the debug version which does many internal checks and maintains detailed statistics as:</p>
|
||||
<div class="fragment"><div class="line">> mkdir -p out/debug</div><div class="line">> cd out/debug</div><div class="line">> cmake -DCMAKE_BUILD_TYPE=Debug ../..</div><div class="line">> make</div></div><!-- fragment --><p> This will name the shared library as <code>libmimalloc-debug.so</code>.</p>
|
||||
<p>Finally, you can build a <em>secure</em> version that uses guard pages, encrypted free lists, etc, as: </p><div class="fragment"><div class="line">> mkdir -p out/secure</div><div class="line">> cd out/secure</div><div class="line">> cmake -DMI_SECURE=ON ../..</div><div class="line">> make</div></div><!-- fragment --><p> This will name the shared library as <code>libmimalloc-secure.so</code>. Use <code>ccmake</code><sup>2</sup> instead of <code>cmake</code> to see and customize all the available build options.</p>
|
||||
<p>Notes:</p><ol type="1">
|
||||
<li>Install CMake: <code>sudo apt-get install cmake</code></li>
|
||||
<li>Install CCMake: <code>sudo apt-get install cmake-curses-gui</code> </li>
|
||||
</ol>
|
||||
</div></div><!-- PageDoc -->
|
||||
</div><!-- contents -->
|
||||
</div><!-- doc-content -->
|
||||
<!-- start footer part -->
|
||||
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
<li class="footer">Generated by
|
||||
<a href="http://www.doxygen.org/index.html">
|
||||
<img class="footer" src="doxygen.png" alt="doxygen"/></a> 1.8.15 </li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,125 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen 1.8.15"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>mi-malloc: Data Structure Index</title>
|
||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="jquery.js"></script>
|
||||
<script type="text/javascript" src="dynsections.js"></script>
|
||||
<link href="navtree.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="resize.js"></script>
|
||||
<script type="text/javascript" src="navtreedata.js"></script>
|
||||
<script type="text/javascript" src="navtree.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(initResizable);
|
||||
/* @license-end */</script>
|
||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||
<script type="text/javascript" src="search/search.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function() { init_search(); });
|
||||
/* @license-end */
|
||||
</script>
|
||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
||||
<link href="mimalloc-doxygen.css" rel="stylesheet" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 56px;">
|
||||
<td id="projectlogo"><img alt="Logo" src="mimalloc-logo.svg"/></td>
|
||||
<td id="projectalign" style="padding-left: 0.5em;">
|
||||
<div id="projectname">mi-malloc
|
||||
 <span id="projectnumber">1.6</span>
|
||||
</div>
|
||||
</td>
|
||||
<td> <div id="MSearchBox" class="MSearchBoxInactive">
|
||||
<span class="left">
|
||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
alt=""/>
|
||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
||||
</span><span class="right">
|
||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- end header part -->
|
||||
<!-- Generated by Doxygen 1.8.15 -->
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
||||
/* @license-end */
|
||||
</script>
|
||||
</div><!-- top -->
|
||||
<div id="side-nav" class="ui-resizable side-nav-resizable">
|
||||
<div id="nav-tree">
|
||||
<div id="nav-tree-contents">
|
||||
<div id="nav-sync" class="sync"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="splitbar" style="-moz-user-select:none;"
|
||||
class="ui-resizable-handle">
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function(){initNavTree('classes.html','');});
|
||||
/* @license-end */
|
||||
</script>
|
||||
<div id="doc-content">
|
||||
<!-- window showing the filter options -->
|
||||
<div id="MSearchSelectWindow"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
||||
</div>
|
||||
|
||||
<!-- iframe showing the search results (closed by default) -->
|
||||
<div id="MSearchResultsWindow">
|
||||
<iframe src="javascript:void(0)" frameborder="0"
|
||||
name="MSearchResults" id="MSearchResults">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<div class="headertitle">
|
||||
<div class="title">Data Structure Index</div> </div>
|
||||
</div><!--header-->
|
||||
<div class="contents">
|
||||
<div class="qindex"><a class="qindex" href="#letter_m">m</a></div>
|
||||
<table class="classindex">
|
||||
<tr><td rowspan="2" valign="bottom"><a name="letter_m"></a><table border="0" cellspacing="0" cellpadding="0"><tr><td><div class="ah">  m  </div></td></tr></table>
|
||||
</td><td valign="top"><a class="el" href="group__cpp.html#structmi__stl__allocator">mi_stl_allocator</a>   </td><td></td></tr>
|
||||
<tr><td></td><td></td><td></td></tr>
|
||||
<tr><td valign="top"><a class="el" href="group__analysis.html#structmi__heap__area__t">mi_heap_area_t</a>   </td><td></td><td></td></tr>
|
||||
<tr><td></td><td></td><td></td></tr>
|
||||
</table>
|
||||
<div class="qindex"><a class="qindex" href="#letter_m">m</a></div>
|
||||
</div><!-- contents -->
|
||||
</div><!-- doc-content -->
|
||||
<!-- start footer part -->
|
||||
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
<li class="footer">Generated by
|
||||
<a href="http://www.doxygen.org/index.html">
|
||||
<img class="footer" src="doxygen.png" alt="doxygen"/></a> 1.8.15 </li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 133 B |
|
Before Width: | Height: | Size: 858 B |
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
@licstart The following is the entire license notice for the
|
||||
JavaScript code in this file.
|
||||
|
||||
Copyright (C) 1997-2017 by Dimitri van Heesch
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
@licend The above is the entire license notice
|
||||
for the JavaScript code in this file
|
||||
*/
|
||||
function toggleVisibility(linkObj)
|
||||
{
|
||||
var base = $(linkObj).attr('id');
|
||||
var summary = $('#'+base+'-summary');
|
||||
var content = $('#'+base+'-content');
|
||||
var trigger = $('#'+base+'-trigger');
|
||||
var src=$(trigger).attr('src');
|
||||
if (content.is(':visible')===true) {
|
||||
content.hide();
|
||||
summary.show();
|
||||
$(linkObj).addClass('closed').removeClass('opened');
|
||||
$(trigger).attr('src',src.substring(0,src.length-8)+'closed.png');
|
||||
} else {
|
||||
content.show();
|
||||
summary.hide();
|
||||
$(linkObj).removeClass('closed').addClass('opened');
|
||||
$(trigger).attr('src',src.substring(0,src.length-10)+'open.png');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function updateStripes()
|
||||
{
|
||||
$('table.directory tr').
|
||||
removeClass('even').filter(':visible:even').addClass('even');
|
||||
}
|
||||
|
||||
function toggleLevel(level)
|
||||
{
|
||||
$('table.directory tr').each(function() {
|
||||
var l = this.id.split('_').length-1;
|
||||
var i = $('#img'+this.id.substring(3));
|
||||
var a = $('#arr'+this.id.substring(3));
|
||||
if (l<level+1) {
|
||||
i.removeClass('iconfopen iconfclosed').addClass('iconfopen');
|
||||
a.html('▼');
|
||||
$(this).show();
|
||||
} else if (l==level+1) {
|
||||
i.removeClass('iconfclosed iconfopen').addClass('iconfclosed');
|
||||
a.html('►');
|
||||
$(this).show();
|
||||
} else {
|
||||
$(this).hide();
|
||||
}
|
||||
});
|
||||
updateStripes();
|
||||
}
|
||||
|
||||
function toggleFolder(id)
|
||||
{
|
||||
// the clicked row
|
||||
var currentRow = $('#row_'+id);
|
||||
|
||||
// all rows after the clicked row
|
||||
var rows = currentRow.nextAll("tr");
|
||||
|
||||
var re = new RegExp('^row_'+id+'\\d+_$', "i"); //only one sub
|
||||
|
||||
// only match elements AFTER this one (can't hide elements before)
|
||||
var childRows = rows.filter(function() { return this.id.match(re); });
|
||||
|
||||
// first row is visible we are HIDING
|
||||
if (childRows.filter(':first').is(':visible')===true) {
|
||||
// replace down arrow by right arrow for current row
|
||||
var currentRowSpans = currentRow.find("span");
|
||||
currentRowSpans.filter(".iconfopen").removeClass("iconfopen").addClass("iconfclosed");
|
||||
currentRowSpans.filter(".arrow").html('►');
|
||||
rows.filter("[id^=row_"+id+"]").hide(); // hide all children
|
||||
} else { // we are SHOWING
|
||||
// replace right arrow by down arrow for current row
|
||||
var currentRowSpans = currentRow.find("span");
|
||||
currentRowSpans.filter(".iconfclosed").removeClass("iconfclosed").addClass("iconfopen");
|
||||
currentRowSpans.filter(".arrow").html('▼');
|
||||
// replace down arrows by right arrows for child rows
|
||||
var childRowsSpans = childRows.find("span");
|
||||
childRowsSpans.filter(".iconfopen").removeClass("iconfopen").addClass("iconfclosed");
|
||||
childRowsSpans.filter(".arrow").html('►');
|
||||
childRows.show(); //show all children
|
||||
}
|
||||
updateStripes();
|
||||
}
|
||||
|
||||
|
||||
function toggleInherit(id)
|
||||
{
|
||||
var rows = $('tr.inherit.'+id);
|
||||
var img = $('tr.inherit_header.'+id+' img');
|
||||
var src = $(img).attr('src');
|
||||
if (rows.filter(':first').is(':visible')===true) {
|
||||
rows.css('display','none');
|
||||
$(img).attr('src',src.substring(0,src.length-8)+'closed.png');
|
||||
} else {
|
||||
rows.css('display','table-row'); // using show() causes jump in firefox
|
||||
$(img).attr('src',src.substring(0,src.length-10)+'open.png');
|
||||
}
|
||||
}
|
||||
/* @license-end */
|
||||
@@ -1,127 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen 1.8.15"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>mi-malloc: Environment Options</title>
|
||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="jquery.js"></script>
|
||||
<script type="text/javascript" src="dynsections.js"></script>
|
||||
<link href="navtree.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="resize.js"></script>
|
||||
<script type="text/javascript" src="navtreedata.js"></script>
|
||||
<script type="text/javascript" src="navtree.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(initResizable);
|
||||
/* @license-end */</script>
|
||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||
<script type="text/javascript" src="search/search.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function() { init_search(); });
|
||||
/* @license-end */
|
||||
</script>
|
||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
||||
<link href="mimalloc-doxygen.css" rel="stylesheet" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 56px;">
|
||||
<td id="projectlogo"><img alt="Logo" src="mimalloc-logo.svg"/></td>
|
||||
<td id="projectalign" style="padding-left: 0.5em;">
|
||||
<div id="projectname">mi-malloc
|
||||
 <span id="projectnumber">1.6</span>
|
||||
</div>
|
||||
</td>
|
||||
<td> <div id="MSearchBox" class="MSearchBoxInactive">
|
||||
<span class="left">
|
||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
alt=""/>
|
||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
||||
</span><span class="right">
|
||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- end header part -->
|
||||
<!-- Generated by Doxygen 1.8.15 -->
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
||||
/* @license-end */
|
||||
</script>
|
||||
</div><!-- top -->
|
||||
<div id="side-nav" class="ui-resizable side-nav-resizable">
|
||||
<div id="nav-tree">
|
||||
<div id="nav-tree-contents">
|
||||
<div id="nav-sync" class="sync"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="splitbar" style="-moz-user-select:none;"
|
||||
class="ui-resizable-handle">
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function(){initNavTree('environment.html','');});
|
||||
/* @license-end */
|
||||
</script>
|
||||
<div id="doc-content">
|
||||
<!-- window showing the filter options -->
|
||||
<div id="MSearchSelectWindow"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
||||
</div>
|
||||
|
||||
<!-- iframe showing the search results (closed by default) -->
|
||||
<div id="MSearchResultsWindow">
|
||||
<iframe src="javascript:void(0)" frameborder="0"
|
||||
name="MSearchResults" id="MSearchResults">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
<div class="PageDoc"><div class="header">
|
||||
<div class="headertitle">
|
||||
<div class="title">Environment Options </div> </div>
|
||||
</div><!--header-->
|
||||
<div class="contents">
|
||||
<div class="textblock"><p>You can set further options either programmatically (using <a href="https://microsoft.github.io/mimalloc/group__options.html"><code>mi_option_set</code></a>), or via environment variables.</p>
|
||||
<ul>
|
||||
<li><code>MIMALLOC_SHOW_STATS=1</code>: show statistics when the program terminates.</li>
|
||||
<li><code>MIMALLOC_VERBOSE=1</code>: show verbose messages.</li>
|
||||
<li><code>MIMALLOC_SHOW_ERRORS=1</code>: show error and warning messages.</li>
|
||||
<li><code>MIMALLOC_PAGE_RESET=0</code>: by default, mimalloc will reset (or purge) OS pages when not in use to signal to the OS that the underlying physical memory can be reused. This can reduce memory fragmentation in long running (server) programs. By setting it to <code>0</code> no such page resets will be done which can improve performance for programs that are not long running. As an alternative, the <code>MIMALLOC_RESET_DELAY=</code><msecs> can be set higher (100ms by default) to make the page reset occur less frequently instead of turning it off completely.</li>
|
||||
<li><code>MIMALLOC_LARGE_OS_PAGES=1</code>: use large OS pages (2MiB) when available; for some workloads this can significantly improve performance. Use <code>MIMALLOC_VERBOSE</code> to check if the large OS pages are enabled – usually one needs to explicitly allow large OS pages (as on <a href="https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows?view=sql-server-2017">Windows</a> and <a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/5/html/tuning_and_optimizing_red_hat_enterprise_linux_for_oracle_9i_and_10g_databases/sect-oracle_9i_and_10g_tuning_guide-large_memory_optimization_big_pages_and_huge_pages-configuring_huge_pages_in_red_hat_enterprise_linux_4_or_5">Linux</a>). However, sometimes the OS is very slow to reserve contiguous physical memory for large OS pages so use with care on systems that can have fragmented memory (for that reason, we generally recommend to use <code>MIMALLOC_RESERVE_HUGE_OS_PAGES</code> instead when possible).</li>
|
||||
<li><code>MIMALLOC_RESERVE_HUGE_OS_PAGES=N</code>: where N is the number of 1GiB <em>huge</em> OS pages. This reserves the huge pages at startup and sometimes this can give a large (latency) performance improvement on big workloads. Usually it is better to not use <code>MIMALLOC_LARGE_OS_PAGES</code> in combination with this setting. Just like large OS pages, use with care as reserving contiguous physical memory can take a long time when memory is fragmented (but reserving the huge pages is done at startup only once). Note that we usually need to explicitly enable huge OS pages (as on <a href="https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows?view=sql-server-2017">Windows</a> and <a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/5/html/tuning_and_optimizing_red_hat_enterprise_linux_for_oracle_9i_and_10g_databases/sect-oracle_9i_and_10g_tuning_guide-large_memory_optimization_big_pages_and_huge_pages-configuring_huge_pages_in_red_hat_enterprise_linux_4_or_5">Linux</a>)). With huge OS pages, it may be beneficial to set the setting <code>MIMALLOC_EAGER_COMMIT_DELAY=N</code> (<code>N</code> is 1 by default) to delay the initial <code>N</code> segments (of 4MiB) of a thread to not allocate in the huge OS pages; this prevents threads that are short lived and allocate just a little to take up space in the huge OS page area (which cannot be reset).</li>
|
||||
</ul>
|
||||
<p>Use caution when using <code>fork</code> in combination with either large or huge OS pages: on a fork, the OS uses copy-on-write for all pages in the original process including the huge OS pages. When any memory is now written in that area, the OS will copy the entire 1GiB huge page (or 2MiB large page) which can cause the memory usage to grow in big increments. </p>
|
||||
</div></div><!-- PageDoc -->
|
||||
</div><!-- contents -->
|
||||
</div><!-- doc-content -->
|
||||
<!-- start footer part -->
|
||||
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
<li class="footer">Generated by
|
||||
<a href="http://www.doxygen.org/index.html">
|
||||
<img class="footer" src="doxygen.png" alt="doxygen"/></a> 1.8.15 </li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 617 B |
|
Before Width: | Height: | Size: 687 B |
@@ -1,129 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen 1.8.15"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>mi-malloc: Data Fields</title>
|
||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="jquery.js"></script>
|
||||
<script type="text/javascript" src="dynsections.js"></script>
|
||||
<link href="navtree.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="resize.js"></script>
|
||||
<script type="text/javascript" src="navtreedata.js"></script>
|
||||
<script type="text/javascript" src="navtree.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(initResizable);
|
||||
/* @license-end */</script>
|
||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||
<script type="text/javascript" src="search/search.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function() { init_search(); });
|
||||
/* @license-end */
|
||||
</script>
|
||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
||||
<link href="mimalloc-doxygen.css" rel="stylesheet" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 56px;">
|
||||
<td id="projectlogo"><img alt="Logo" src="mimalloc-logo.svg"/></td>
|
||||
<td id="projectalign" style="padding-left: 0.5em;">
|
||||
<div id="projectname">mi-malloc
|
||||
 <span id="projectnumber">1.6</span>
|
||||
</div>
|
||||
</td>
|
||||
<td> <div id="MSearchBox" class="MSearchBoxInactive">
|
||||
<span class="left">
|
||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
alt=""/>
|
||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
||||
</span><span class="right">
|
||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- end header part -->
|
||||
<!-- Generated by Doxygen 1.8.15 -->
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
||||
/* @license-end */
|
||||
</script>
|
||||
</div><!-- top -->
|
||||
<div id="side-nav" class="ui-resizable side-nav-resizable">
|
||||
<div id="nav-tree">
|
||||
<div id="nav-tree-contents">
|
||||
<div id="nav-sync" class="sync"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="splitbar" style="-moz-user-select:none;"
|
||||
class="ui-resizable-handle">
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function(){initNavTree('functions.html','');});
|
||||
/* @license-end */
|
||||
</script>
|
||||
<div id="doc-content">
|
||||
<!-- window showing the filter options -->
|
||||
<div id="MSearchSelectWindow"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
||||
</div>
|
||||
|
||||
<!-- iframe showing the search results (closed by default) -->
|
||||
<div id="MSearchResultsWindow">
|
||||
<iframe src="javascript:void(0)" frameborder="0"
|
||||
name="MSearchResults" id="MSearchResults">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
<div class="contents">
|
||||
<div class="textblock">Here is a list of all struct and union fields with links to the structures/unions they belong to:</div><ul>
|
||||
<li>block_size
|
||||
: <a class="el" href="group__analysis.html#a332a6c14d736a99699d5453a1cb04b41">mi_heap_area_t</a>
|
||||
</li>
|
||||
<li>blocks
|
||||
: <a class="el" href="group__analysis.html#ae0085e6e1cf059a4eb7767e30e9991b8">mi_heap_area_t</a>
|
||||
</li>
|
||||
<li>committed
|
||||
: <a class="el" href="group__analysis.html#ab47526df656d8837ec3e97f11b83f835">mi_heap_area_t</a>
|
||||
</li>
|
||||
<li>reserved
|
||||
: <a class="el" href="group__analysis.html#ae848a3e6840414891035423948ca0383">mi_heap_area_t</a>
|
||||
</li>
|
||||
<li>used
|
||||
: <a class="el" href="group__analysis.html#ab820302c5cd0df133eb8e51650a008b4">mi_heap_area_t</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div><!-- contents -->
|
||||
</div><!-- doc-content -->
|
||||
<!-- start footer part -->
|
||||
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
<li class="footer">Generated by
|
||||
<a href="http://www.doxygen.org/index.html">
|
||||
<img class="footer" src="doxygen.png" alt="doxygen"/></a> 1.8.15 </li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,129 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen 1.8.15"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>mi-malloc: Data Fields - Variables</title>
|
||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="jquery.js"></script>
|
||||
<script type="text/javascript" src="dynsections.js"></script>
|
||||
<link href="navtree.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="resize.js"></script>
|
||||
<script type="text/javascript" src="navtreedata.js"></script>
|
||||
<script type="text/javascript" src="navtree.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(initResizable);
|
||||
/* @license-end */</script>
|
||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||
<script type="text/javascript" src="search/search.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function() { init_search(); });
|
||||
/* @license-end */
|
||||
</script>
|
||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
||||
<link href="mimalloc-doxygen.css" rel="stylesheet" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 56px;">
|
||||
<td id="projectlogo"><img alt="Logo" src="mimalloc-logo.svg"/></td>
|
||||
<td id="projectalign" style="padding-left: 0.5em;">
|
||||
<div id="projectname">mi-malloc
|
||||
 <span id="projectnumber">1.6</span>
|
||||
</div>
|
||||
</td>
|
||||
<td> <div id="MSearchBox" class="MSearchBoxInactive">
|
||||
<span class="left">
|
||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
alt=""/>
|
||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
||||
</span><span class="right">
|
||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- end header part -->
|
||||
<!-- Generated by Doxygen 1.8.15 -->
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
||||
/* @license-end */
|
||||
</script>
|
||||
</div><!-- top -->
|
||||
<div id="side-nav" class="ui-resizable side-nav-resizable">
|
||||
<div id="nav-tree">
|
||||
<div id="nav-tree-contents">
|
||||
<div id="nav-sync" class="sync"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="splitbar" style="-moz-user-select:none;"
|
||||
class="ui-resizable-handle">
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function(){initNavTree('functions_vars.html','');});
|
||||
/* @license-end */
|
||||
</script>
|
||||
<div id="doc-content">
|
||||
<!-- window showing the filter options -->
|
||||
<div id="MSearchSelectWindow"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
||||
</div>
|
||||
|
||||
<!-- iframe showing the search results (closed by default) -->
|
||||
<div id="MSearchResultsWindow">
|
||||
<iframe src="javascript:void(0)" frameborder="0"
|
||||
name="MSearchResults" id="MSearchResults">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
<div class="contents">
|
||||
 <ul>
|
||||
<li>block_size
|
||||
: <a class="el" href="group__analysis.html#a332a6c14d736a99699d5453a1cb04b41">mi_heap_area_t</a>
|
||||
</li>
|
||||
<li>blocks
|
||||
: <a class="el" href="group__analysis.html#ae0085e6e1cf059a4eb7767e30e9991b8">mi_heap_area_t</a>
|
||||
</li>
|
||||
<li>committed
|
||||
: <a class="el" href="group__analysis.html#ab47526df656d8837ec3e97f11b83f835">mi_heap_area_t</a>
|
||||
</li>
|
||||
<li>reserved
|
||||
: <a class="el" href="group__analysis.html#ae848a3e6840414891035423948ca0383">mi_heap_area_t</a>
|
||||
</li>
|
||||
<li>used
|
||||
: <a class="el" href="group__analysis.html#ab820302c5cd0df133eb8e51650a008b4">mi_heap_area_t</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div><!-- contents -->
|
||||
</div><!-- doc-content -->
|
||||
<!-- start footer part -->
|
||||
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
<li class="footer">Generated by
|
||||
<a href="http://www.doxygen.org/index.html">
|
||||
<img class="footer" src="doxygen.png" alt="doxygen"/></a> 1.8.15 </li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,447 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen 1.8.15"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>mi-malloc: Aligned Allocation</title>
|
||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="jquery.js"></script>
|
||||
<script type="text/javascript" src="dynsections.js"></script>
|
||||
<link href="navtree.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="resize.js"></script>
|
||||
<script type="text/javascript" src="navtreedata.js"></script>
|
||||
<script type="text/javascript" src="navtree.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(initResizable);
|
||||
/* @license-end */</script>
|
||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||
<script type="text/javascript" src="search/search.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function() { init_search(); });
|
||||
/* @license-end */
|
||||
</script>
|
||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
||||
<link href="mimalloc-doxygen.css" rel="stylesheet" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 56px;">
|
||||
<td id="projectlogo"><img alt="Logo" src="mimalloc-logo.svg"/></td>
|
||||
<td id="projectalign" style="padding-left: 0.5em;">
|
||||
<div id="projectname">mi-malloc
|
||||
 <span id="projectnumber">1.6</span>
|
||||
</div>
|
||||
</td>
|
||||
<td> <div id="MSearchBox" class="MSearchBoxInactive">
|
||||
<span class="left">
|
||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
alt=""/>
|
||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
||||
</span><span class="right">
|
||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- end header part -->
|
||||
<!-- Generated by Doxygen 1.8.15 -->
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
||||
/* @license-end */
|
||||
</script>
|
||||
</div><!-- top -->
|
||||
<div id="side-nav" class="ui-resizable side-nav-resizable">
|
||||
<div id="nav-tree">
|
||||
<div id="nav-tree-contents">
|
||||
<div id="nav-sync" class="sync"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="splitbar" style="-moz-user-select:none;"
|
||||
class="ui-resizable-handle">
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function(){initNavTree('group__aligned.html','');});
|
||||
/* @license-end */
|
||||
</script>
|
||||
<div id="doc-content">
|
||||
<!-- window showing the filter options -->
|
||||
<div id="MSearchSelectWindow"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
||||
</div>
|
||||
|
||||
<!-- iframe showing the search results (closed by default) -->
|
||||
<div id="MSearchResultsWindow">
|
||||
<iframe src="javascript:void(0)" frameborder="0"
|
||||
name="MSearchResults" id="MSearchResults">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<div class="summary">
|
||||
<a href="#func-members">Functions</a> </div>
|
||||
<div class="headertitle">
|
||||
<div class="title">Aligned Allocation</div> </div>
|
||||
</div><!--header-->
|
||||
<div class="contents">
|
||||
|
||||
<p>Allocating aligned memory blocks.
|
||||
<a href="#details">More...</a></p>
|
||||
<table class="memberdecls">
|
||||
<tr class="heading"><td colspan="2"><h2 class="groupheader"><a name="func-members"></a>
|
||||
Functions</h2></td></tr>
|
||||
<tr class="memitem:ga68930196751fa2cca9e1fd0d71bade56"><td class="memItemLeft" align="right" valign="top">void * </td><td class="memItemRight" valign="bottom"><a class="el" href="group__aligned.html#ga68930196751fa2cca9e1fd0d71bade56">mi_malloc_aligned</a> (size_t size, size_t alignment)</td></tr>
|
||||
<tr class="memdesc:ga68930196751fa2cca9e1fd0d71bade56"><td class="mdescLeft"> </td><td class="mdescRight">Allocate <em>size</em> bytes aligned by <em>alignment</em>. <a href="#ga68930196751fa2cca9e1fd0d71bade56">More...</a><br /></td></tr>
|
||||
<tr class="separator:ga68930196751fa2cca9e1fd0d71bade56"><td class="memSeparator" colspan="2"> </td></tr>
|
||||
<tr class="memitem:ga0cadbcf5b89a7b6fb171bc8df8734819"><td class="memItemLeft" align="right" valign="top">void * </td><td class="memItemRight" valign="bottom"><a class="el" href="group__aligned.html#ga0cadbcf5b89a7b6fb171bc8df8734819">mi_zalloc_aligned</a> (size_t size, size_t alignment)</td></tr>
|
||||
<tr class="separator:ga0cadbcf5b89a7b6fb171bc8df8734819"><td class="memSeparator" colspan="2"> </td></tr>
|
||||
<tr class="memitem:ga53dddb4724042a90315b94bc268fb4c9"><td class="memItemLeft" align="right" valign="top">void * </td><td class="memItemRight" valign="bottom"><a class="el" href="group__aligned.html#ga53dddb4724042a90315b94bc268fb4c9">mi_calloc_aligned</a> (size_t count, size_t size, size_t alignment)</td></tr>
|
||||
<tr class="separator:ga53dddb4724042a90315b94bc268fb4c9"><td class="memSeparator" colspan="2"> </td></tr>
|
||||
<tr class="memitem:ga4028d1cf4aa4c87c880747044a8322ae"><td class="memItemLeft" align="right" valign="top">void * </td><td class="memItemRight" valign="bottom"><a class="el" href="group__aligned.html#ga4028d1cf4aa4c87c880747044a8322ae">mi_realloc_aligned</a> (void *p, size_t newsize, size_t alignment)</td></tr>
|
||||
<tr class="separator:ga4028d1cf4aa4c87c880747044a8322ae"><td class="memSeparator" colspan="2"> </td></tr>
|
||||
<tr class="memitem:ga5850da130c936bd77db039dcfbc8295d"><td class="memItemLeft" align="right" valign="top">void * </td><td class="memItemRight" valign="bottom"><a class="el" href="group__aligned.html#ga5850da130c936bd77db039dcfbc8295d">mi_malloc_aligned_at</a> (size_t size, size_t alignment, size_t offset)</td></tr>
|
||||
<tr class="memdesc:ga5850da130c936bd77db039dcfbc8295d"><td class="mdescLeft"> </td><td class="mdescRight">Allocate <em>size</em> bytes aligned by <em>alignment</em> at a specified <em>offset</em>. <a href="#ga5850da130c936bd77db039dcfbc8295d">More...</a><br /></td></tr>
|
||||
<tr class="separator:ga5850da130c936bd77db039dcfbc8295d"><td class="memSeparator" colspan="2"> </td></tr>
|
||||
<tr class="memitem:ga5f8c2353766db522565e642fafd8a3f8"><td class="memItemLeft" align="right" valign="top">void * </td><td class="memItemRight" valign="bottom"><a class="el" href="group__aligned.html#ga5f8c2353766db522565e642fafd8a3f8">mi_zalloc_aligned_at</a> (size_t size, size_t alignment, size_t offset)</td></tr>
|
||||
<tr class="separator:ga5f8c2353766db522565e642fafd8a3f8"><td class="memSeparator" colspan="2"> </td></tr>
|
||||
<tr class="memitem:ga08647c4593f3b2eef24a919a73eba3a3"><td class="memItemLeft" align="right" valign="top">void * </td><td class="memItemRight" valign="bottom"><a class="el" href="group__aligned.html#ga08647c4593f3b2eef24a919a73eba3a3">mi_calloc_aligned_at</a> (size_t count, size_t size, size_t alignment, size_t offset)</td></tr>
|
||||
<tr class="separator:ga08647c4593f3b2eef24a919a73eba3a3"><td class="memSeparator" colspan="2"> </td></tr>
|
||||
<tr class="memitem:gaf66a9ae6c6f08bd6be6fb6ea771faffb"><td class="memItemLeft" align="right" valign="top">void * </td><td class="memItemRight" valign="bottom"><a class="el" href="group__aligned.html#gaf66a9ae6c6f08bd6be6fb6ea771faffb">mi_realloc_aligned_at</a> (void *p, size_t newsize, size_t alignment, size_t offset)</td></tr>
|
||||
<tr class="separator:gaf66a9ae6c6f08bd6be6fb6ea771faffb"><td class="memSeparator" colspan="2"> </td></tr>
|
||||
</table>
|
||||
<a name="details" id="details"></a><h2 class="groupheader">Detailed Description</h2>
|
||||
<p>Allocating aligned memory blocks. </p>
|
||||
<h2 class="groupheader">Function Documentation</h2>
|
||||
<a id="ga53dddb4724042a90315b94bc268fb4c9"></a>
|
||||
<h2 class="memtitle"><span class="permalink"><a href="#ga53dddb4724042a90315b94bc268fb4c9">◆ </a></span>mi_calloc_aligned()</h2>
|
||||
|
||||
<div class="memitem">
|
||||
<div class="memproto">
|
||||
<table class="memname">
|
||||
<tr>
|
||||
<td class="memname">void* mi_calloc_aligned </td>
|
||||
<td>(</td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>count</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>size</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>alignment</em> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>)</td>
|
||||
<td></td><td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><div class="memdoc">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<a id="ga08647c4593f3b2eef24a919a73eba3a3"></a>
|
||||
<h2 class="memtitle"><span class="permalink"><a href="#ga08647c4593f3b2eef24a919a73eba3a3">◆ </a></span>mi_calloc_aligned_at()</h2>
|
||||
|
||||
<div class="memitem">
|
||||
<div class="memproto">
|
||||
<table class="memname">
|
||||
<tr>
|
||||
<td class="memname">void* mi_calloc_aligned_at </td>
|
||||
<td>(</td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>count</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>size</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>alignment</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>offset</em> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>)</td>
|
||||
<td></td><td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><div class="memdoc">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<a id="ga68930196751fa2cca9e1fd0d71bade56"></a>
|
||||
<h2 class="memtitle"><span class="permalink"><a href="#ga68930196751fa2cca9e1fd0d71bade56">◆ </a></span>mi_malloc_aligned()</h2>
|
||||
|
||||
<div class="memitem">
|
||||
<div class="memproto">
|
||||
<table class="memname">
|
||||
<tr>
|
||||
<td class="memname">void* mi_malloc_aligned </td>
|
||||
<td>(</td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>size</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>alignment</em> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>)</td>
|
||||
<td></td><td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><div class="memdoc">
|
||||
|
||||
<p>Allocate <em>size</em> bytes aligned by <em>alignment</em>. </p>
|
||||
<dl class="params"><dt>Parameters</dt><dd>
|
||||
<table class="params">
|
||||
<tr><td class="paramname">size</td><td>number of bytes to allocate. </td></tr>
|
||||
<tr><td class="paramname">alignment</td><td>the minimal alignment of the allocated memory. </td></tr>
|
||||
</table>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="section return"><dt>Returns</dt><dd>pointer to the allocated memory or <em>NULL</em> if out of memory. The returned pointer is aligned by <em>alignment</em>, i.e. <code>(uintptr_t)p % alignment == 0</code>.</dd></dl>
|
||||
<p>Returns a unique pointer if called with <em>size</em> 0. </p><dl class="section see"><dt>See also</dt><dd><a href="https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-malloc?view=vs-2017">_aligned_malloc</a> (on Windows) </dd>
|
||||
<dd>
|
||||
<a href="http://man.openbsd.org/reallocarray">aligned_alloc</a> (on BSD, with switched arguments!) </dd>
|
||||
<dd>
|
||||
<a href="https://linux.die.net/man/3/posix_memalign">posix_memalign</a> (on Posix, with switched arguments!) </dd>
|
||||
<dd>
|
||||
<a href="https://linux.die.net/man/3/posix_memalign">memalign</a> (on Linux, with switched arguments!) </dd></dl>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<a id="ga5850da130c936bd77db039dcfbc8295d"></a>
|
||||
<h2 class="memtitle"><span class="permalink"><a href="#ga5850da130c936bd77db039dcfbc8295d">◆ </a></span>mi_malloc_aligned_at()</h2>
|
||||
|
||||
<div class="memitem">
|
||||
<div class="memproto">
|
||||
<table class="memname">
|
||||
<tr>
|
||||
<td class="memname">void* mi_malloc_aligned_at </td>
|
||||
<td>(</td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>size</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>alignment</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>offset</em> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>)</td>
|
||||
<td></td><td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><div class="memdoc">
|
||||
|
||||
<p>Allocate <em>size</em> bytes aligned by <em>alignment</em> at a specified <em>offset</em>. </p>
|
||||
<dl class="params"><dt>Parameters</dt><dd>
|
||||
<table class="params">
|
||||
<tr><td class="paramname">size</td><td>number of bytes to allocate. </td></tr>
|
||||
<tr><td class="paramname">alignment</td><td>the minimal alignment of the allocated memory at <em>offset</em>. </td></tr>
|
||||
<tr><td class="paramname">offset</td><td>the offset that should be aligned. </td></tr>
|
||||
</table>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="section return"><dt>Returns</dt><dd>pointer to the allocated memory or <em>NULL</em> if out of memory. The returned pointer is aligned by <em>alignment</em> at <em>offset</em>, i.e. <code>((uintptr_t)p + offset) % alignment == 0</code>.</dd></dl>
|
||||
<p>Returns a unique pointer if called with <em>size</em> 0. </p><dl class="section see"><dt>See also</dt><dd><a href="https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-offset-malloc?view=vs-2017">_aligned_offset_malloc</a> (on Windows) </dd></dl>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<a id="ga4028d1cf4aa4c87c880747044a8322ae"></a>
|
||||
<h2 class="memtitle"><span class="permalink"><a href="#ga4028d1cf4aa4c87c880747044a8322ae">◆ </a></span>mi_realloc_aligned()</h2>
|
||||
|
||||
<div class="memitem">
|
||||
<div class="memproto">
|
||||
<table class="memname">
|
||||
<tr>
|
||||
<td class="memname">void* mi_realloc_aligned </td>
|
||||
<td>(</td>
|
||||
<td class="paramtype">void * </td>
|
||||
<td class="paramname"><em>p</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>newsize</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>alignment</em> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>)</td>
|
||||
<td></td><td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><div class="memdoc">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<a id="gaf66a9ae6c6f08bd6be6fb6ea771faffb"></a>
|
||||
<h2 class="memtitle"><span class="permalink"><a href="#gaf66a9ae6c6f08bd6be6fb6ea771faffb">◆ </a></span>mi_realloc_aligned_at()</h2>
|
||||
|
||||
<div class="memitem">
|
||||
<div class="memproto">
|
||||
<table class="memname">
|
||||
<tr>
|
||||
<td class="memname">void* mi_realloc_aligned_at </td>
|
||||
<td>(</td>
|
||||
<td class="paramtype">void * </td>
|
||||
<td class="paramname"><em>p</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>newsize</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>alignment</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>offset</em> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>)</td>
|
||||
<td></td><td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><div class="memdoc">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<a id="ga0cadbcf5b89a7b6fb171bc8df8734819"></a>
|
||||
<h2 class="memtitle"><span class="permalink"><a href="#ga0cadbcf5b89a7b6fb171bc8df8734819">◆ </a></span>mi_zalloc_aligned()</h2>
|
||||
|
||||
<div class="memitem">
|
||||
<div class="memproto">
|
||||
<table class="memname">
|
||||
<tr>
|
||||
<td class="memname">void* mi_zalloc_aligned </td>
|
||||
<td>(</td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>size</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>alignment</em> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>)</td>
|
||||
<td></td><td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><div class="memdoc">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<a id="ga5f8c2353766db522565e642fafd8a3f8"></a>
|
||||
<h2 class="memtitle"><span class="permalink"><a href="#ga5f8c2353766db522565e642fafd8a3f8">◆ </a></span>mi_zalloc_aligned_at()</h2>
|
||||
|
||||
<div class="memitem">
|
||||
<div class="memproto">
|
||||
<table class="memname">
|
||||
<tr>
|
||||
<td class="memname">void* mi_zalloc_aligned_at </td>
|
||||
<td>(</td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>size</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>alignment</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">size_t </td>
|
||||
<td class="paramname"><em>offset</em> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>)</td>
|
||||
<td></td><td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><div class="memdoc">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- contents -->
|
||||
</div><!-- doc-content -->
|
||||
<!-- start footer part -->
|
||||
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
<li class="footer">Generated by
|
||||
<a href="http://www.doxygen.org/index.html">
|
||||
<img class="footer" src="doxygen.png" alt="doxygen"/></a> 1.8.15 </li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,11 +0,0 @@
|
||||
var group__aligned =
|
||||
[
|
||||
[ "mi_calloc_aligned", "group__aligned.html#ga53dddb4724042a90315b94bc268fb4c9", null ],
|
||||
[ "mi_calloc_aligned_at", "group__aligned.html#ga08647c4593f3b2eef24a919a73eba3a3", null ],
|
||||
[ "mi_malloc_aligned", "group__aligned.html#ga68930196751fa2cca9e1fd0d71bade56", null ],
|
||||
[ "mi_malloc_aligned_at", "group__aligned.html#ga5850da130c936bd77db039dcfbc8295d", null ],
|
||||
[ "mi_realloc_aligned", "group__aligned.html#ga4028d1cf4aa4c87c880747044a8322ae", null ],
|
||||
[ "mi_realloc_aligned_at", "group__aligned.html#gaf66a9ae6c6f08bd6be6fb6ea771faffb", null ],
|
||||
[ "mi_zalloc_aligned", "group__aligned.html#ga0cadbcf5b89a7b6fb171bc8df8734819", null ],
|
||||
[ "mi_zalloc_aligned_at", "group__aligned.html#ga5f8c2353766db522565e642fafd8a3f8", null ]
|
||||
];
|
||||
@@ -1,385 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen 1.8.15"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>mi-malloc: Heap Introspection</title>
|
||||
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="jquery.js"></script>
|
||||
<script type="text/javascript" src="dynsections.js"></script>
|
||||
<link href="navtree.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="resize.js"></script>
|
||||
<script type="text/javascript" src="navtreedata.js"></script>
|
||||
<script type="text/javascript" src="navtree.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(initResizable);
|
||||
/* @license-end */</script>
|
||||
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||
<script type="text/javascript" src="search/search.js"></script>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function() { init_search(); });
|
||||
/* @license-end */
|
||||
</script>
|
||||
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
||||
<link href="mimalloc-doxygen.css" rel="stylesheet" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 56px;">
|
||||
<td id="projectlogo"><img alt="Logo" src="mimalloc-logo.svg"/></td>
|
||||
<td id="projectalign" style="padding-left: 0.5em;">
|
||||
<div id="projectname">mi-malloc
|
||||
 <span id="projectnumber">1.6</span>
|
||||
</div>
|
||||
</td>
|
||||
<td> <div id="MSearchBox" class="MSearchBoxInactive">
|
||||
<span class="left">
|
||||
<img id="MSearchSelect" src="search/mag_sel.png"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
alt=""/>
|
||||
<input type="text" id="MSearchField" value="Search" accesskey="S"
|
||||
onfocus="searchBox.OnSearchFieldFocus(true)"
|
||||
onblur="searchBox.OnSearchFieldFocus(false)"
|
||||
onkeyup="searchBox.OnSearchFieldChange(event)"/>
|
||||
</span><span class="right">
|
||||
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- end header part -->
|
||||
<!-- Generated by Doxygen 1.8.15 -->
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
||||
/* @license-end */
|
||||
</script>
|
||||
</div><!-- top -->
|
||||
<div id="side-nav" class="ui-resizable side-nav-resizable">
|
||||
<div id="nav-tree">
|
||||
<div id="nav-tree-contents">
|
||||
<div id="nav-sync" class="sync"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="splitbar" style="-moz-user-select:none;"
|
||||
class="ui-resizable-handle">
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
||||
$(document).ready(function(){initNavTree('group__analysis.html','');});
|
||||
/* @license-end */
|
||||
</script>
|
||||
<div id="doc-content">
|
||||
<!-- window showing the filter options -->
|
||||
<div id="MSearchSelectWindow"
|
||||
onmouseover="return searchBox.OnSearchSelectShow()"
|
||||
onmouseout="return searchBox.OnSearchSelectHide()"
|
||||
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
||||
</div>
|
||||
|
||||
<!-- iframe showing the search results (closed by default) -->
|
||||
<div id="MSearchResultsWindow">
|
||||
<iframe src="javascript:void(0)" frameborder="0"
|
||||
name="MSearchResults" id="MSearchResults">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<div class="summary">
|
||||
<a href="#nested-classes">Data Structures</a> |
|
||||
<a href="#typedef-members">Typedefs</a> |
|
||||
<a href="#func-members">Functions</a> </div>
|
||||
<div class="headertitle">
|
||||
<div class="title">Heap Introspection</div> </div>
|
||||
</div><!--header-->
|
||||
<div class="contents">
|
||||
|
||||
<p>Inspect the heap at runtime.
|
||||
<a href="#details">More...</a></p>
|
||||
<table class="memberdecls">
|
||||
<tr class="heading"><td colspan="2"><h2 class="groupheader"><a name="nested-classes"></a>
|
||||
Data Structures</h2></td></tr>
|
||||
<tr class="memitem:structmi__heap__area__t"><td class="memItemLeft" align="right" valign="top">struct  </td><td class="memItemRight" valign="bottom"><a class="el" href="group__analysis.html#structmi__heap__area__t">mi_heap_area_t</a></td></tr>
|
||||
<tr class="memdesc:structmi__heap__area__t"><td class="mdescLeft"> </td><td class="mdescRight">An area of heap space contains blocks of a single size. <a href="group__analysis.html#structmi__heap__area__t">More...</a><br /></td></tr>
|
||||
<tr class="separator:structmi__heap__area__t"><td class="memSeparator" colspan="2"> </td></tr>
|
||||
</table><table class="memberdecls">
|
||||
<tr class="heading"><td colspan="2"><h2 class="groupheader"><a name="typedef-members"></a>
|
||||
Typedefs</h2></td></tr>
|
||||
<tr class="memitem:gadfa01e2900f0e5d515ad5506b26f6d65"><td class="memItemLeft" align="right" valign="top">typedef bool() </td><td class="memItemRight" valign="bottom"><a class="el" href="group__analysis.html#gadfa01e2900f0e5d515ad5506b26f6d65">mi_block_visit_fun</a>(const <a class="el" href="group__heap.html#ga34a47cde5a5b38c29f1aa3c5e76943c2">mi_heap_t</a> *heap, const <a class="el" href="group__analysis.html#structmi__heap__area__t">mi_heap_area_t</a> *area, void *block, size_t block_size, void *arg)</td></tr>
|
||||
<tr class="memdesc:gadfa01e2900f0e5d515ad5506b26f6d65"><td class="mdescLeft"> </td><td class="mdescRight">Visitor function passed to <a class="el" href="group__analysis.html#ga70c46687dc6e9dc98b232b02646f8bed" title="Visit all areas and blocks in a heap.">mi_heap_visit_blocks()</a> <a href="#gadfa01e2900f0e5d515ad5506b26f6d65">More...</a><br /></td></tr>
|
||||
<tr class="separator:gadfa01e2900f0e5d515ad5506b26f6d65"><td class="memSeparator" colspan="2"> </td></tr>
|
||||
</table><table class="memberdecls">
|
||||
<tr class="heading"><td colspan="2"><h2 class="groupheader"><a name="func-members"></a>
|
||||
Functions</h2></td></tr>
|
||||
<tr class="memitem:gaa862aa8ed8d57d84cae41fc1022d71af"><td class="memItemLeft" align="right" valign="top">bool </td><td class="memItemRight" valign="bottom"><a class="el" href="group__analysis.html#gaa862aa8ed8d57d84cae41fc1022d71af">mi_heap_contains_block</a> (<a class="el" href="group__heap.html#ga34a47cde5a5b38c29f1aa3c5e76943c2">mi_heap_t</a> *heap, const void *p)</td></tr>
|
||||
<tr class="memdesc:gaa862aa8ed8d57d84cae41fc1022d71af"><td class="mdescLeft"> </td><td class="mdescRight">Does a heap contain a pointer to a previously allocated block? <a href="#gaa862aa8ed8d57d84cae41fc1022d71af">More...</a><br /></td></tr>
|
||||
<tr class="separator:gaa862aa8ed8d57d84cae41fc1022d71af"><td class="memSeparator" colspan="2"> </td></tr>
|
||||
<tr class="memitem:ga0d67c1789faaa15ff366c024fcaf6377"><td class="memItemLeft" align="right" valign="top">bool </td><td class="memItemRight" valign="bottom"><a class="el" href="group__analysis.html#ga0d67c1789faaa15ff366c024fcaf6377">mi_heap_check_owned</a> (<a class="el" href="group__heap.html#ga34a47cde5a5b38c29f1aa3c5e76943c2">mi_heap_t</a> *heap, const void *p)</td></tr>
|
||||
<tr class="memdesc:ga0d67c1789faaa15ff366c024fcaf6377"><td class="mdescLeft"> </td><td class="mdescRight">Check safely if any pointer is part of a heap. <a href="#ga0d67c1789faaa15ff366c024fcaf6377">More...</a><br /></td></tr>
|
||||
<tr class="separator:ga0d67c1789faaa15ff366c024fcaf6377"><td class="memSeparator" colspan="2"> </td></tr>
|
||||
<tr class="memitem:ga628c237489c2679af84a4d0d143b3dd5"><td class="memItemLeft" align="right" valign="top">bool </td><td class="memItemRight" valign="bottom"><a class="el" href="group__analysis.html#ga628c237489c2679af84a4d0d143b3dd5">mi_check_owned</a> (const void *p)</td></tr>
|
||||
<tr class="memdesc:ga628c237489c2679af84a4d0d143b3dd5"><td class="mdescLeft"> </td><td class="mdescRight">Check safely if any pointer is part of the default heap of this thread. <a href="#ga628c237489c2679af84a4d0d143b3dd5">More...</a><br /></td></tr>
|
||||
<tr class="separator:ga628c237489c2679af84a4d0d143b3dd5"><td class="memSeparator" colspan="2"> </td></tr>
|
||||
<tr class="memitem:ga70c46687dc6e9dc98b232b02646f8bed"><td class="memItemLeft" align="right" valign="top">bool </td><td class="memItemRight" valign="bottom"><a class="el" href="group__analysis.html#ga70c46687dc6e9dc98b232b02646f8bed">mi_heap_visit_blocks</a> (const <a class="el" href="group__heap.html#ga34a47cde5a5b38c29f1aa3c5e76943c2">mi_heap_t</a> *heap, bool visit_all_blocks, <a class="el" href="group__analysis.html#gadfa01e2900f0e5d515ad5506b26f6d65">mi_block_visit_fun</a> *visitor, void *arg)</td></tr>
|
||||
<tr class="memdesc:ga70c46687dc6e9dc98b232b02646f8bed"><td class="mdescLeft"> </td><td class="mdescRight">Visit all areas and blocks in a heap. <a href="#ga70c46687dc6e9dc98b232b02646f8bed">More...</a><br /></td></tr>
|
||||
<tr class="separator:ga70c46687dc6e9dc98b232b02646f8bed"><td class="memSeparator" colspan="2"> </td></tr>
|
||||
</table>
|
||||
<a name="details" id="details"></a><h2 class="groupheader">Detailed Description</h2>
|
||||
<p>Inspect the heap at runtime. </p>
|
||||
<hr/><h2 class="groupheader">Data Structure Documentation</h2>
|
||||
<a name="structmi__heap__area__t" id="structmi__heap__area__t"></a>
|
||||
<h2 class="memtitle"><span class="permalink"><a href="#structmi__heap__area__t">◆ </a></span>mi_heap_area_t</h2>
|
||||
|
||||
<div class="memitem">
|
||||
<div class="memproto">
|
||||
<table class="memname">
|
||||
<tr>
|
||||
<td class="memname">struct mi_heap_area_t</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><div class="memdoc">
|
||||
<div class="textblock"><p>An area of heap space contains blocks of a single size. </p>
|
||||
<p>The bytes in freed blocks are <code>committed - used</code>. </p>
|
||||
</div><table class="fieldtable">
|
||||
<tr><th colspan="3">Data Fields</th></tr>
|
||||
<tr><td class="fieldtype">
|
||||
<a id="a332a6c14d736a99699d5453a1cb04b41"></a>size_t</td>
|
||||
<td class="fieldname">
|
||||
block_size</td>
|
||||
<td class="fielddoc">
|
||||
size in bytes of one block </td></tr>
|
||||
<tr><td class="fieldtype">
|
||||
<a id="ae0085e6e1cf059a4eb7767e30e9991b8"></a>void *</td>
|
||||
<td class="fieldname">
|
||||
blocks</td>
|
||||
<td class="fielddoc">
|
||||
start of the area containing heap blocks </td></tr>
|
||||
<tr><td class="fieldtype">
|
||||
<a id="ab47526df656d8837ec3e97f11b83f835"></a>size_t</td>
|
||||
<td class="fieldname">
|
||||
committed</td>
|
||||
<td class="fielddoc">
|
||||
current committed bytes of this area </td></tr>
|
||||
<tr><td class="fieldtype">
|
||||
<a id="ae848a3e6840414891035423948ca0383"></a>size_t</td>
|
||||
<td class="fieldname">
|
||||
reserved</td>
|
||||
<td class="fielddoc">
|
||||
bytes reserved for this area </td></tr>
|
||||
<tr><td class="fieldtype">
|
||||
<a id="ab820302c5cd0df133eb8e51650a008b4"></a>size_t</td>
|
||||
<td class="fieldname">
|
||||
used</td>
|
||||
<td class="fielddoc">
|
||||
bytes in use by allocated blocks </td></tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="groupheader">Typedef Documentation</h2>
|
||||
<a id="gadfa01e2900f0e5d515ad5506b26f6d65"></a>
|
||||
<h2 class="memtitle"><span class="permalink"><a href="#gadfa01e2900f0e5d515ad5506b26f6d65">◆ </a></span>mi_block_visit_fun</h2>
|
||||
|
||||
<div class="memitem">
|
||||
<div class="memproto">
|
||||
<table class="memname">
|
||||
<tr>
|
||||
<td class="memname">typedef bool() mi_block_visit_fun(const <a class="el" href="group__heap.html#ga34a47cde5a5b38c29f1aa3c5e76943c2">mi_heap_t</a> *heap, const <a class="el" href="group__analysis.html#structmi__heap__area__t">mi_heap_area_t</a> *area, void *block, size_t block_size, void *arg)</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><div class="memdoc">
|
||||
|
||||
<p>Visitor function passed to <a class="el" href="group__analysis.html#ga70c46687dc6e9dc98b232b02646f8bed" title="Visit all areas and blocks in a heap.">mi_heap_visit_blocks()</a> </p>
|
||||
<dl class="section return"><dt>Returns</dt><dd><em>true</em> if ok, <em>false</em> to stop visiting (i.e. break)</dd></dl>
|
||||
<p>This function is always first called for every <em>area</em> with <em>block</em> as a <em>NULL</em> pointer. If <em>visit_all_blocks</em> was <em>true</em>, the function is then called for every allocated block in that area. </p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="groupheader">Function Documentation</h2>
|
||||
<a id="ga628c237489c2679af84a4d0d143b3dd5"></a>
|
||||
<h2 class="memtitle"><span class="permalink"><a href="#ga628c237489c2679af84a4d0d143b3dd5">◆ </a></span>mi_check_owned()</h2>
|
||||
|
||||
<div class="memitem">
|
||||
<div class="memproto">
|
||||
<table class="memname">
|
||||
<tr>
|
||||
<td class="memname">bool mi_check_owned </td>
|
||||
<td>(</td>
|
||||
<td class="paramtype">const void * </td>
|
||||
<td class="paramname"><em>p</em></td><td>)</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><div class="memdoc">
|
||||
|
||||
<p>Check safely if any pointer is part of the default heap of this thread. </p>
|
||||
<dl class="params"><dt>Parameters</dt><dd>
|
||||
<table class="params">
|
||||
<tr><td class="paramname">p</td><td>Any pointer – not required to be previously allocated by us. </td></tr>
|
||||
</table>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="section return"><dt>Returns</dt><dd><em>true</em> if <em>p</em> points to a block in default heap of this thread.</dd></dl>
|
||||
<p>Note: expensive function, linear in the pages in the heap. </p><dl class="section see"><dt>See also</dt><dd><a class="el" href="group__analysis.html#gaa862aa8ed8d57d84cae41fc1022d71af" title="Does a heap contain a pointer to a previously allocated block?">mi_heap_contains_block()</a> </dd>
|
||||
<dd>
|
||||
<a class="el" href="group__heap.html#ga8db4cbb87314a989a9a187464d6b5e05" title="Get the default heap that is used for mi_malloc() et al.">mi_heap_get_default()</a> </dd></dl>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<a id="ga0d67c1789faaa15ff366c024fcaf6377"></a>
|
||||
<h2 class="memtitle"><span class="permalink"><a href="#ga0d67c1789faaa15ff366c024fcaf6377">◆ </a></span>mi_heap_check_owned()</h2>
|
||||
|
||||
<div class="memitem">
|
||||
<div class="memproto">
|
||||
<table class="memname">
|
||||
<tr>
|
||||
<td class="memname">bool mi_heap_check_owned </td>
|
||||
<td>(</td>
|
||||
<td class="paramtype"><a class="el" href="group__heap.html#ga34a47cde5a5b38c29f1aa3c5e76943c2">mi_heap_t</a> * </td>
|
||||
<td class="paramname"><em>heap</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">const void * </td>
|
||||
<td class="paramname"><em>p</em> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>)</td>
|
||||
<td></td><td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><div class="memdoc">
|
||||
|
||||
<p>Check safely if any pointer is part of a heap. </p>
|
||||
<dl class="params"><dt>Parameters</dt><dd>
|
||||
<table class="params">
|
||||
<tr><td class="paramname">heap</td><td>The heap. </td></tr>
|
||||
<tr><td class="paramname">p</td><td>Any pointer – not required to be previously allocated by us. </td></tr>
|
||||
</table>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="section return"><dt>Returns</dt><dd><em>true</em> if <em>p</em> points to a block in <em>heap</em>.</dd></dl>
|
||||
<p>Note: expensive function, linear in the pages in the heap. </p><dl class="section see"><dt>See also</dt><dd><a class="el" href="group__analysis.html#gaa862aa8ed8d57d84cae41fc1022d71af" title="Does a heap contain a pointer to a previously allocated block?">mi_heap_contains_block()</a> </dd>
|
||||
<dd>
|
||||
<a class="el" href="group__heap.html#ga8db4cbb87314a989a9a187464d6b5e05" title="Get the default heap that is used for mi_malloc() et al.">mi_heap_get_default()</a> </dd></dl>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<a id="gaa862aa8ed8d57d84cae41fc1022d71af"></a>
|
||||
<h2 class="memtitle"><span class="permalink"><a href="#gaa862aa8ed8d57d84cae41fc1022d71af">◆ </a></span>mi_heap_contains_block()</h2>
|
||||
|
||||
<div class="memitem">
|
||||
<div class="memproto">
|
||||
<table class="memname">
|
||||
<tr>
|
||||
<td class="memname">bool mi_heap_contains_block </td>
|
||||
<td>(</td>
|
||||
<td class="paramtype"><a class="el" href="group__heap.html#ga34a47cde5a5b38c29f1aa3c5e76943c2">mi_heap_t</a> * </td>
|
||||
<td class="paramname"><em>heap</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">const void * </td>
|
||||
<td class="paramname"><em>p</em> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>)</td>
|
||||
<td></td><td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><div class="memdoc">
|
||||
|
||||
<p>Does a heap contain a pointer to a previously allocated block? </p>
|
||||
<dl class="params"><dt>Parameters</dt><dd>
|
||||
<table class="params">
|
||||
<tr><td class="paramname">heap</td><td>The heap. </td></tr>
|
||||
<tr><td class="paramname">p</td><td>Pointer to a previously allocated block (in any heap)– cannot be some random pointer! </td></tr>
|
||||
</table>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="section return"><dt>Returns</dt><dd><em>true</em> if the block pointed to by <em>p</em> is in the <em>heap</em>. </dd></dl>
|
||||
<dl class="section see"><dt>See also</dt><dd><a class="el" href="group__analysis.html#ga0d67c1789faaa15ff366c024fcaf6377" title="Check safely if any pointer is part of a heap.">mi_heap_check_owned()</a> </dd></dl>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<a id="ga70c46687dc6e9dc98b232b02646f8bed"></a>
|
||||
<h2 class="memtitle"><span class="permalink"><a href="#ga70c46687dc6e9dc98b232b02646f8bed">◆ </a></span>mi_heap_visit_blocks()</h2>
|
||||
|
||||
<div class="memitem">
|
||||
<div class="memproto">
|
||||
<table class="memname">
|
||||
<tr>
|
||||
<td class="memname">bool mi_heap_visit_blocks </td>
|
||||
<td>(</td>
|
||||
<td class="paramtype">const <a class="el" href="group__heap.html#ga34a47cde5a5b38c29f1aa3c5e76943c2">mi_heap_t</a> * </td>
|
||||
<td class="paramname"><em>heap</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">bool </td>
|
||||
<td class="paramname"><em>visit_all_blocks</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype"><a class="el" href="group__analysis.html#gadfa01e2900f0e5d515ad5506b26f6d65">mi_block_visit_fun</a> * </td>
|
||||
<td class="paramname"><em>visitor</em>, </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paramkey"></td>
|
||||
<td></td>
|
||||
<td class="paramtype">void * </td>
|
||||
<td class="paramname"><em>arg</em> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>)</td>
|
||||
<td></td><td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div><div class="memdoc">
|
||||
|
||||
<p>Visit all areas and blocks in a heap. </p>
|
||||
<dl class="params"><dt>Parameters</dt><dd>
|
||||
<table class="params">
|
||||
<tr><td class="paramname">heap</td><td>The heap to visit. </td></tr>
|
||||
<tr><td class="paramname">visit_all_blocks</td><td>If <em>true</em> visits all allocated blocks, otherwise <em>visitor</em> is only called for every heap area. </td></tr>
|
||||
<tr><td class="paramname">visitor</td><td>This function is called for every area in the heap (with <em>block</em> as <em>NULL</em>). If <em>visit_all_blocks</em> is <em>true</em>, <em>visitor</em> is also called for every allocated block in every area (with <code>block!=NULL</code>). return <em>false</em> from this function to stop visiting early. </td></tr>
|
||||
<tr><td class="paramname">arg</td><td>Extra argument passed to <em>visitor</em>. </td></tr>
|
||||
</table>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="section return"><dt>Returns</dt><dd><em>true</em> if all areas and blocks were visited. </dd></dl>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- contents -->
|
||||
</div><!-- doc-content -->
|
||||
<!-- start footer part -->
|
||||
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
<li class="footer">Generated by
|
||||
<a href="http://www.doxygen.org/index.html">
|
||||
<img class="footer" src="doxygen.png" alt="doxygen"/></a> 1.8.15 </li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||