Compare commits
850 Commits
threads
...
8fac34760c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fac34760c | ||
|
|
95d4fbd54c | ||
|
|
b3e0b5e139 | ||
|
|
33b7979468 | ||
|
|
a539c3b814 | ||
|
|
1338660a81 | ||
|
|
21b2c1e198 | ||
|
|
f2966a5348 | ||
|
|
687226e9c1 | ||
|
|
7b54d2cde1 | ||
|
|
b45bd8fada | ||
|
|
de8054095c | ||
|
|
7c795ebdd1 | ||
|
|
4e4c30deaa | ||
|
|
fcd3eca133 | ||
|
|
a03eaffe98 | ||
|
|
ccfa6f500d | ||
|
|
67b3ac65dc | ||
|
|
7262bf0f90 | ||
|
|
9894147b30 | ||
|
|
5b4cb532e8 | ||
|
|
9e325de9c7 | ||
|
|
ad888bcb9b | ||
|
|
7b9891b2b5 | ||
|
|
359f457e53 | ||
|
|
336391b9c9 | ||
|
|
2de7654f05 | ||
|
|
1b0c9752e6 | ||
|
|
30bbcb94cf | ||
|
|
187839ab53 | ||
|
|
4e51284661 | ||
|
|
3c8d567c1e | ||
|
|
8b702b732e | ||
|
|
02adcf94ee | ||
|
|
d5e981bca9 | ||
|
|
2edf0221a3 | ||
|
|
32f629fb2d | ||
|
|
98f616964d | ||
|
|
557da037c7 | ||
|
|
5a849b05d6 | ||
|
|
9da248032f | ||
|
|
52b31ded4d | ||
|
|
88fd5aabdd | ||
|
|
411d42ac8d | ||
|
|
ae810ce83d | ||
|
|
d4daf93050 | ||
|
|
c0fc0bed9e | ||
|
|
9753695744 | ||
|
|
725085abea | ||
|
|
98cf0d4850 | ||
|
|
b9b1ae8c08 | ||
|
|
cf91244de6 | ||
|
|
ffd0555a5a | ||
|
|
0e9f50d773 | ||
|
|
9345886d44 | ||
|
|
b1bf2e51d6 | ||
|
|
176d4a7783 | ||
|
|
b69f96e0ae | ||
|
|
450afac4a1 | ||
|
|
fba3bedf39 | ||
|
|
8b341c96c1 | ||
|
|
7f1f658358 | ||
|
|
0417fd22ef | ||
|
|
69a498c6a3 | ||
|
|
c9307a422f | ||
|
|
d9de86f6df | ||
|
|
8971334f57 | ||
|
|
ab232d3051 | ||
|
|
4de9527ef8 | ||
|
|
c46922678e | ||
|
|
146e694d08 | ||
|
|
b797cc805f | ||
|
|
63cd95b612 | ||
|
|
28753d6a18 | ||
|
|
68698fde20 | ||
|
|
3bb6671dd9 | ||
|
|
a16900efe4 | ||
|
|
55b5cbaba3 | ||
|
|
e0ab3f257d | ||
|
|
2b6fc68ad1 | ||
|
|
e2f2f4c533 | ||
|
|
6a27ab2c2d | ||
|
|
4d3b246ef6 | ||
|
|
e037dcfdae | ||
|
|
c87cff0cdd | ||
|
|
e4dd739d72 | ||
|
|
9a6039237d | ||
|
|
94f1c4503a | ||
|
|
7095876dca | ||
|
|
6d4c5c5c4e | ||
|
|
134e938c9d | ||
|
|
d10dbc19a4 | ||
|
|
64cc41f49c | ||
|
|
33d73cbdbc | ||
|
|
807fc91bf8 | ||
|
|
d67f9ee8ce | ||
|
|
0a9024f1bd | ||
|
|
b991afebac | ||
|
|
7927beeffa | ||
|
|
cd348bbe3c | ||
|
|
d2ee259bfd | ||
|
|
9150885e82 | ||
|
|
fcf63e42aa | ||
|
|
087a464ed7 | ||
|
|
96dc9ed668 | ||
|
|
e6486aad6d | ||
|
|
f578336cd7 | ||
|
|
1ba8a4b012 | ||
|
|
69a0f31747 | ||
|
|
e4c25a3612 | ||
|
|
978fc6b174 | ||
|
|
f192d8ae4e | ||
|
|
1e108fba64 | ||
|
|
9c05cc9bd5 | ||
|
|
4d7b78069f | ||
|
|
56d66b6b24 | ||
|
|
ed38ea1aa0 | ||
|
|
d8204b05cc | ||
|
|
5f00ca206d | ||
|
|
52856ea30e | ||
|
|
a611ca7bfe | ||
|
|
9b0a0a3fbb | ||
|
|
54010c3653 | ||
|
|
b8be0c5a5d | ||
|
|
9271bb104c | ||
|
|
5b5e541b1e | ||
|
|
6d20ba47fa | ||
|
|
a3bbd5ab55 | ||
|
|
88e473433b | ||
|
|
3527693aeb | ||
|
|
4376233ec3 | ||
|
|
1f6402f746 | ||
|
|
3556211904 | ||
|
|
c31dcb48e5 | ||
|
|
1a0254b977 | ||
|
|
05dadc1302 | ||
|
|
6041d119db | ||
|
|
258426b787 | ||
|
|
8e6c4caffc | ||
|
|
59f84c2a45 | ||
|
|
6bc593be79 | ||
|
|
3d337e5f80 | ||
|
|
9691bd5dc8 | ||
|
|
c0fbb10907 | ||
|
|
f9f1b8e7b9 | ||
|
|
bde9fd0262 | ||
|
|
bd361ee25a | ||
|
|
f13ccee48b | ||
|
|
e4316a8a16 | ||
|
|
3eeb25c946 | ||
|
|
813f434ecd | ||
|
|
71f5d220ee | ||
|
|
71e5ea0268 | ||
|
|
da82fe8a0f | ||
|
|
2b444d3262 | ||
|
|
b118828956 | ||
|
|
4595872c6b | ||
|
|
1dced13b29 | ||
|
|
4b023bb432 | ||
|
|
b1b35833d6 | ||
|
|
34120a0409 | ||
|
|
6367e931e5 | ||
|
|
02fe09d56f | ||
|
|
f4ab296b8b | ||
|
|
db4068826b | ||
|
|
9b1791a48d | ||
|
|
61d074cc6c | ||
|
|
417c22556d | ||
|
|
5b4d04e337 | ||
|
|
684ef64767 | ||
|
|
87d0ce5fa2 | ||
|
|
5b67cb05ff | ||
|
|
81b7829657 | ||
|
|
1ddeb129ac | ||
|
|
53d181d57b | ||
|
|
b634a24f4b | ||
|
|
4ef59648be | ||
|
|
73628c1d05 | ||
|
|
2e3230eca2 | ||
|
|
63dc179cc7 | ||
|
|
211544af00 | ||
|
|
1ff26bb4b6 | ||
|
|
e2a4464835 | ||
|
|
f0fe3929ab | ||
|
|
c728cf8225 | ||
|
|
f38c11ae8e | ||
|
|
a1b195ede0 | ||
|
|
cfd31a977d | ||
|
|
06dd5edb3f | ||
|
|
685f55dd31 | ||
|
|
0b6b053c54 | ||
|
|
e0464fbd44 | ||
|
|
5e88b86d1e | ||
|
|
e6b507e6d2 | ||
|
|
5baf50a5b4 | ||
|
|
5d63300512 | ||
|
|
739d9e1a3c | ||
|
|
8a1063df5f | ||
|
|
d2c6b281c8 | ||
|
|
3900ddeb2c | ||
|
|
a7f4647e73 | ||
|
|
a7cfc562e9 | ||
|
|
5f5320f8e0 | ||
|
|
6f6e6698c4 | ||
|
|
1d0e7950fd | ||
|
|
dd4e872f82 | ||
|
|
d8ad83528d | ||
|
|
7c1b57a663 | ||
|
|
559b2a195d | ||
|
|
e56c635c13 | ||
|
|
6cf6e32ee9 | ||
|
|
ac94309114 | ||
|
|
3183dc92e9 | ||
|
|
19150682df | ||
|
|
912566167c | ||
|
|
82f205956a | ||
|
|
d456410da2 | ||
|
|
48c3532c40 | ||
|
|
0c79f42e39 | ||
|
|
399eee2e19 | ||
|
|
2a338cf045 | ||
|
|
31eb517b4d | ||
|
|
2844d0fc8c | ||
|
|
b63ce2dc3c | ||
|
|
184db36e7c | ||
|
|
a108367d71 | ||
|
|
c1639cb9c2 | ||
|
|
2f1918bf71 | ||
|
|
7a44cd1f60 | ||
|
|
15421d988a | ||
|
|
1e2ae0e2ee | ||
|
|
56ec24cbe1 | ||
|
|
c786f14b8a | ||
|
|
4abdc3ea43 | ||
|
|
f6546321a3 | ||
|
|
98514773d0 | ||
|
|
b0884bd879 | ||
|
|
633bc2e032 | ||
|
|
745d12d7f5 | ||
|
|
3fef39211e | ||
|
|
f905e7aa2b | ||
|
|
d17048e054 | ||
|
|
e0db01a59d | ||
|
|
cca2886907 | ||
|
|
77221b833b | ||
|
|
db64d2876d | ||
|
|
bde26a617e | ||
|
|
d70afb7ecc | ||
|
|
cc100920d8 | ||
|
|
209fd7c312 | ||
|
|
a86304d932 | ||
|
|
7f5859f8ee | ||
|
|
09aa0347d1 | ||
|
|
bbc59d23e8 | ||
|
|
c2c6d64452 | ||
|
|
1c66190ac2 | ||
|
|
90a92f04a6 | ||
|
|
7de8c2aa37 | ||
|
|
3f5c404609 | ||
|
|
44eaeca650 | ||
|
|
d52116d446 | ||
|
|
0268d6b9c6 | ||
|
|
001231b53f | ||
|
|
a366cdf277 | ||
|
|
80be2f0a9e | ||
|
|
4337df6236 | ||
|
|
f0ab5e01c7 | ||
|
|
f339bca0bf | ||
|
|
a95c6d1745 | ||
|
|
1030e82621 | ||
|
|
ea70ed128c | ||
|
|
5b5e72425e | ||
|
|
7332d3d332 | ||
|
|
20b306d7a4 | ||
|
|
dee0ee61fa | ||
|
|
c689ecf810 | ||
|
|
a9c8b51c0e | ||
|
|
a9aa4219e6 | ||
|
|
fe387b5fa6 | ||
|
|
567ace4f8d | ||
|
|
00b068a6eb | ||
|
|
cfe743ad9e | ||
|
|
81d4f49f15 | ||
|
|
9440629176 | ||
|
|
096c044b4f | ||
|
|
987f8e7f17 | ||
|
|
6060898753 | ||
|
|
c8688719ee | ||
|
|
fcc20806a7 | ||
|
|
3b6064bbe0 | ||
|
|
ba7ec89c35 | ||
|
|
6680ecbfeb | ||
|
|
9c7c62754b | ||
|
|
9c5890d409 | ||
|
|
f927a863f2 | ||
|
|
4d178a2a35 | ||
|
|
dabc17bc39 | ||
|
|
1dd8ea1dba | ||
|
|
9d079e6d3e | ||
|
|
d07b3d089e | ||
|
|
cc68aaf453 | ||
|
|
410f1df3da | ||
|
|
d915443930 | ||
|
|
ff512edb54 | ||
|
|
aa389dae4b | ||
|
|
4905a41af0 | ||
|
|
ad89055521 | ||
|
|
5d0f200b61 | ||
|
|
2f9836b407 | ||
|
|
6dc78627f7 | ||
|
|
ef19dbce1d | ||
|
|
c0f111aaee | ||
|
|
1662aaaea7 | ||
|
|
1f27d18427 | ||
|
|
3c3a37a48c | ||
|
|
87794f2240 | ||
|
|
341e05b843 | ||
|
|
01975be7ac | ||
|
|
eeac9e880c | ||
|
|
2eb5147675 | ||
|
|
b05c07d21c | ||
|
|
385d0e1306 | ||
|
|
1c9b5edd6a | ||
|
|
eab9b70c68 | ||
|
|
ad8e0b4783 | ||
|
|
c2d3f2704c | ||
|
|
9025695ebf | ||
|
|
5a1b841711 | ||
|
|
21c5b62c38 | ||
|
|
884247cb1d | ||
|
|
d78dd1582b | ||
|
|
1a750f5374 | ||
|
|
afeed7477b | ||
|
|
611698ec5f | ||
|
|
c34db52262 | ||
|
|
71870a05b0 | ||
|
|
1519a17803 | ||
|
|
d8dc4f5395 | ||
|
|
b13f261770 | ||
|
|
5217bc27b7 | ||
|
|
f365ade976 | ||
|
|
f6777e621d | ||
|
|
ee893a7ba0 | ||
|
|
58eca98ae1 | ||
|
|
f56b4a0a32 | ||
|
|
c393e4442d | ||
|
|
7954a9f424 | ||
|
|
0873c3ae1d | ||
|
|
daafb244d9 | ||
|
|
b8272b53cf | ||
|
|
7ce356f99d | ||
|
|
5b2c60ece3 | ||
|
|
c1af4e31b1 | ||
|
|
c7eced8b49 | ||
|
|
6b241de81b | ||
|
|
09e0c5915b | ||
|
|
64c1130908 | ||
|
|
b24122a604 | ||
|
|
a26eeae82e | ||
|
|
0d42178666 | ||
|
|
05acd376e9 | ||
|
|
0d40ee3750 | ||
|
|
ce51e833a8 | ||
|
|
c4f8477310 | ||
|
|
44f51402f7 | ||
|
|
5c750985a6 | ||
|
|
d6bf1b2271 | ||
|
|
7eb1426e1f | ||
|
|
ebbc6381d7 | ||
|
|
456ed398e8 | ||
|
|
195d40080a | ||
|
|
18f7cb16b9 | ||
|
|
1095dc33f4 | ||
|
|
a41d3236b8 | ||
|
|
53e3578e34 | ||
|
|
ac8f0175a7 | ||
|
|
0fa31a0e15 | ||
|
|
5ff1aab7b5 | ||
|
|
76b6c445e4 | ||
|
|
69917e234c | ||
|
|
3281df3a41 | ||
|
|
96ab77fd1e | ||
|
|
9d7c0ead07 | ||
|
|
adaaa3238a | ||
|
|
d8c27c7fcc | ||
|
|
990169b631 | ||
|
|
9b02a5e77a | ||
|
|
874f79a8f9 | ||
|
|
faae44a477 | ||
|
|
b16c0c861f | ||
|
|
a8a111c6ee | ||
|
|
5155e18ba1 | ||
|
|
ff5ac93908 | ||
|
|
d3e8714a80 | ||
|
|
5654201a09 | ||
|
|
279ca5dad7 | ||
|
|
ff10c51d11 | ||
|
|
daf15c993e | ||
|
|
fbff83e498 | ||
|
|
1bc5422049 | ||
|
|
f5c111f649 | ||
|
|
9dd6588edf | ||
|
|
526c7a3dca | ||
|
|
893c1cf447 | ||
|
|
f57bded691 | ||
|
|
f30ebe878c | ||
|
|
c987148dc2 | ||
|
|
c430d1d113 | ||
|
|
68212e8bc4 | ||
|
|
ae72448f89 | ||
|
|
6622e583da | ||
|
|
8e506fc34a | ||
|
|
38027670fd | ||
|
|
7d7130bc0f | ||
|
|
f881645991 | ||
|
|
3e87916b6c | ||
|
|
dbe43c203d | ||
|
|
4472eed23c | ||
|
|
0fe6d6aaec | ||
|
|
282ed23a99 | ||
|
|
559f1fbecd | ||
|
|
575547963d | ||
|
|
803d2e6c7a | ||
|
|
ed14ee779d | ||
|
|
fcabba0751 | ||
|
|
21516fe821 | ||
|
|
841b9deea3 | ||
|
|
27ab4518ac | ||
|
|
c1e6a66e35 | ||
|
|
d8eff6ba3e | ||
|
|
eb1e22582b | ||
|
|
f9fb942ba8 | ||
|
|
0edd05ccac | ||
|
|
6fbcd8527a | ||
|
|
906a0580d0 | ||
|
|
6ac7b5bb9d | ||
|
|
dcef701d04 | ||
|
|
3c0060dacf | ||
|
|
4653711620 | ||
|
|
7f36fe6693 | ||
|
|
8270e1e12e | ||
|
|
ff9a7c10d3 | ||
|
|
c2d6645392 | ||
|
|
8b2ee6a4b8 | ||
|
|
4942a8e50e | ||
|
|
782d5d6d5e | ||
|
|
218a2754c7 | ||
|
|
c7743a23ed | ||
|
|
afb20aae74 | ||
|
|
27b1528a65 | ||
|
|
03853d15f4 | ||
|
|
963eb3e659 | ||
|
|
8eba8dab7c | ||
|
|
47292beaad | ||
|
|
90c4e2bb93 | ||
|
|
a46c20ef93 | ||
|
|
762beaa59e | ||
|
|
b0f0a4c5e2 | ||
|
|
b956c0c591 | ||
|
|
8cb4cf16df | ||
|
|
7fb46ee069 | ||
|
|
54ca8bbe67 | ||
|
|
bbf3c03cce | ||
|
|
732014c148 | ||
|
|
7992ce6a21 | ||
|
|
34a3066cae | ||
|
|
248bc1cf05 | ||
|
|
5ef39591f6 | ||
|
|
b070fa8ee6 | ||
|
|
01e254db8c | ||
|
|
c85e40b9ab | ||
|
|
c7deb06fe8 | ||
|
|
178c90ad74 | ||
|
|
bf4f881079 | ||
|
|
5d5e51400e | ||
|
|
6d02874049 | ||
|
|
d3f5bb5fcb | ||
|
|
f2ca9f282a | ||
|
|
fc81c27fec | ||
|
|
02b80acbd7 | ||
|
|
2d6108dc2f | ||
|
|
ff43048585 | ||
|
|
3f1a451e2b | ||
|
|
51f401a56c | ||
|
|
8bcfc7043f | ||
|
|
c515590c1b | ||
|
|
3315bfe2f6 | ||
|
|
ec4863ff93 | ||
|
|
5577f19859 | ||
|
|
6a17e8a7f4 | ||
|
|
7ac5fc903b | ||
|
|
05b22bfa69 | ||
|
|
bdf6f14b9e | ||
|
|
f87cc129fc | ||
|
|
3f29b7b582 | ||
|
|
42c550fcde | ||
|
|
e2ea67a826 | ||
|
|
a9df1f2ec2 | ||
|
|
a0368be408 | ||
|
|
5dbdd8c11d | ||
|
|
2ce8169e64 | ||
|
|
be2e130de9 | ||
|
|
616376fbd7 | ||
|
|
0779767355 | ||
|
|
987d695ed2 | ||
|
|
a8a6b725cf | ||
|
|
15ae425a6b | ||
|
|
9d381723ed | ||
|
|
88f32ffcb5 | ||
|
|
54a0c2b0ad | ||
|
|
16cc7048b0 | ||
|
|
d4034ee3d9 | ||
|
|
6e6d9cbe05 | ||
|
|
3c75f84581 | ||
|
|
563689dba5 | ||
|
|
6d648457b1 | ||
|
|
00986e8876 | ||
|
|
c5eeea29e2 | ||
|
|
18930ccad7 | ||
|
|
cd8e139ab0 | ||
|
|
27ac0e1acc | ||
|
|
c242563a13 | ||
|
|
27b5f5ee83 | ||
|
|
749d9d3793 | ||
|
|
904d203f36 | ||
|
|
611bb91141 | ||
|
|
1839f98be2 | ||
|
|
ec0d60e6aa | ||
|
|
0cb7977025 | ||
|
|
a1b496fab5 | ||
|
|
16b9e30d5f | ||
|
|
3efca6dbbc | ||
|
|
23e8cb3b5d | ||
|
|
70fa363658 | ||
|
|
d697c6caf2 | ||
|
|
b7416af316 | ||
|
|
aa33626a03 | ||
|
|
761b5de93d | ||
|
|
bb39724f72 | ||
|
|
a28ad5eba2 | ||
|
|
247965c1b7 | ||
|
|
f60883071e | ||
|
|
3ca2ef760f | ||
|
|
f779303472 | ||
|
|
fad609f744 | ||
|
|
478b4cb808 | ||
|
|
63d9828213 | ||
|
|
c20d7b534d | ||
|
|
b9cc0a47c6 | ||
|
|
4d4a2e5ae8 | ||
|
|
36c4e6a7fe | ||
|
|
d2daf9dc48 | ||
|
|
5a86a715c9 | ||
|
|
aa227c7dc6 | ||
|
|
ee1ca9fa61 | ||
|
|
22255d12a9 | ||
|
|
68d12a7839 | ||
|
|
a3bb0d3469 | ||
|
|
924da318c0 | ||
|
|
ceb52ac411 | ||
|
|
b6ff79ac32 | ||
|
|
85d97bd362 | ||
|
|
d9f3558b34 | ||
|
|
3ef243f342 | ||
|
|
5f5fdaf902 | ||
|
|
009d0ceaa5 | ||
|
|
dce5c9825b | ||
|
|
b5a1360a72 | ||
|
|
183872e362 | ||
|
|
9279c75160 | ||
|
|
f692c5b377 | ||
|
|
4a9ea6c503 | ||
|
|
e6c510cba6 | ||
|
|
d36c041a9c | ||
|
|
f6416c1e6b | ||
|
|
e8f5ee95c6 | ||
|
|
a081faf3cc | ||
|
|
8f023f8e70 | ||
|
|
165a83efae | ||
|
|
a6be039bdb | ||
|
|
6b0f9290ea | ||
|
|
1ba70ef398 | ||
|
|
c39b1e2b3c | ||
|
|
a1bb75850d | ||
|
|
19bf93a59d | ||
|
|
106be64a1a | ||
|
|
8fd8e42490 | ||
|
|
656b1c1ab2 | ||
|
|
668c903c02 | ||
|
|
307916d1fe | ||
|
|
caecc2c98e | ||
|
|
b17b3d40da | ||
|
|
85b7d4b5c1 | ||
|
|
c9fae31766 | ||
|
|
7212ffaa83 | ||
|
|
a65bf32d57 | ||
|
|
5184a2f7ea | ||
|
|
fb8b03e82d | ||
|
|
4213609b1e | ||
|
|
114140ab7c | ||
|
|
d007af4811 | ||
|
|
1a63bbe28d | ||
|
|
80136cc047 | ||
|
|
68d59147e7 | ||
|
|
3ac8291712 | ||
|
|
8ff1aeb96d | ||
|
|
685b8a94ac | ||
|
|
00ebabddc2 | ||
|
|
3c0e8dc56a | ||
|
|
b978c72e03 | ||
|
|
f198d45a59 | ||
|
|
10d84faa92 | ||
|
|
9b61e061a0 | ||
|
|
e159804492 | ||
|
|
0425bd3c81 | ||
|
|
e80864b64d | ||
|
|
90b5a958d3 | ||
|
|
30650ab52b | ||
|
|
3cdf51aaca | ||
|
|
bff9944b02 | ||
|
|
3650a48c51 | ||
|
|
57855e23cb | ||
|
|
277368537b | ||
|
|
3c070c6dc0 | ||
|
|
6233747299 | ||
|
|
96edb8909f | ||
|
|
4717834ad0 | ||
|
|
06d235f8af | ||
|
|
36a0ae4737 | ||
|
|
4cde35b674 | ||
|
|
68b2b7737b | ||
|
|
3096baf462 | ||
|
|
f0443b7bfe | ||
|
|
e407ec0ee0 | ||
|
|
ed339ee2ce | ||
|
|
1a52165e25 | ||
|
|
de07416085 | ||
|
|
3c723b428e | ||
|
|
580f58f7ab | ||
|
|
6e2edf03a9 | ||
|
|
6f1f872158 | ||
|
|
b7eb093b42 | ||
|
|
797e87e169 | ||
|
|
9985e4a699 | ||
|
|
64501e46f5 | ||
|
|
ee33868068 | ||
|
|
4540a2e725 | ||
|
|
0b15229a5e | ||
|
|
7f9ff717b4 | ||
|
|
aa895e271d | ||
|
|
6194a43724 | ||
|
|
ec90a6a958 | ||
|
|
9980989f7f | ||
|
|
e1228d8708 | ||
|
|
c081c3f51f | ||
|
|
0fa30e24c7 | ||
|
|
a3dcc3095e | ||
|
|
433fd26173 | ||
|
|
59a7f96ae8 | ||
|
|
788d6a7e22 | ||
|
|
1f8a388bdc | ||
|
|
524fa5330f | ||
|
|
c7c7f64f79 | ||
|
|
fddab6967d | ||
|
|
5e37a0ed5d | ||
|
|
5caf136bc2 | ||
|
|
2cb6a0ffe7 | ||
|
|
fbaa641841 | ||
|
|
1a5d91be4b | ||
|
|
ee09a70d40 | ||
|
|
a8c85afeb0 | ||
|
|
70b2f07694 | ||
|
|
a79cea1cd6 | ||
|
|
052fc405ad | ||
|
|
36e8c16a2c | ||
|
|
199c3b467c | ||
|
|
516121b698 | ||
|
|
389b97f13e | ||
|
|
1adfacf21d | ||
|
|
a7a57578e9 | ||
|
|
0b957688b7 | ||
|
|
ed3eaacb44 | ||
|
|
355d3f6681 | ||
|
|
e46d1f3455 | ||
|
|
cf1e447d9c | ||
|
|
b2b58278be | ||
|
|
a947956616 | ||
|
|
9b3ab1bba5 | ||
|
|
cbfd467011 | ||
|
|
a237cd2777 | ||
|
|
f13fc3062a | ||
|
|
392cbc2d41 | ||
|
|
f8f9a02d56 | ||
|
|
47969a618e | ||
|
|
a96a5ca8d7 | ||
|
|
b6d09c8a70 | ||
|
|
a98de2a76c | ||
|
|
8aea96a4e3 | ||
|
|
7cf7646ed0 | ||
|
|
5c617f311a | ||
|
|
023a18014e | ||
|
|
c67da817b7 | ||
|
|
0512a65c42 | ||
|
|
101a0c8648 | ||
|
|
2966752b73 | ||
|
|
a04926ae4d | ||
|
|
b82d0bdefb | ||
|
|
f32ee7fbbb | ||
|
|
a83a946cda | ||
|
|
84f726edd6 | ||
|
|
1aa1a0c0d5 | ||
|
|
e7a35086f7 | ||
|
|
8a7e8665d2 | ||
|
|
74969cd1be | ||
|
|
6163821401 | ||
|
|
4f8858f75d | ||
|
|
fb2b6f3685 | ||
|
|
5286828cb8 | ||
|
|
d29a28591e | ||
|
|
da58c31ed4 | ||
|
|
55bfb1d550 | ||
|
|
4d326ef806 | ||
|
|
117fcbddd4 | ||
|
|
46fbd2454c | ||
|
|
fe0c8ccb0c | ||
|
|
e76e71f285 | ||
|
|
358a05c0fa | ||
|
|
1264a44aa0 | ||
|
|
9c96651e70 | ||
|
|
8e758c383c | ||
|
|
886d7411ae | ||
|
|
7600a763a2 | ||
|
|
8d3cdf62e3 | ||
|
|
9e5e993a1b | ||
|
|
5a879058c9 | ||
|
|
c26d36ebce | ||
|
|
89f5350ac4 | ||
|
|
c423363266 | ||
|
|
000adfb2be | ||
|
|
4f29fdea72 | ||
|
|
c3648e4b20 | ||
|
|
dd83feec2d | ||
|
|
45afd2032b | ||
|
|
744314cb3a | ||
|
|
1907e5d3ce | ||
|
|
50db561774 | ||
|
|
326a35939a | ||
|
|
ae167faee5 | ||
|
|
fb0f30b9f0 | ||
|
|
75e2626dc0 | ||
|
|
def0e6762d | ||
|
|
ba6452c5b0 | ||
|
|
817c0c56ab | ||
|
|
81f71982f4 | ||
|
|
bf07f26e73 | ||
|
|
642ff0f27f | ||
|
|
58faf5adf0 | ||
|
|
868a6f03ec | ||
|
|
2ff011ffba | ||
|
|
7ce491df53 | ||
|
|
3d89841a51 | ||
|
|
3cff5d0961 | ||
|
|
51a02a4064 | ||
|
|
752accdd33 | ||
|
|
bdcd392dbf | ||
|
|
6f7c5a35dd | ||
|
|
4feaffe102 | ||
|
|
e0686209f4 | ||
|
|
e061557eea | ||
|
|
aeafe57c5d | ||
|
|
e84b6f4084 | ||
|
|
82c6a5dd60 | ||
|
|
f9002f9f9b | ||
|
|
bf4c928214 | ||
|
|
bdf161aa7d | ||
|
|
bfdc1e96a6 | ||
|
|
368b5445e4 | ||
|
|
6b76338407 | ||
|
|
6beedbdba9 | ||
|
|
fb920af5dc | ||
|
|
acea700708 | ||
|
|
0619f3a9e7 | ||
|
|
361108b88e | ||
|
|
b03a368191 | ||
|
|
516bc38b2b | ||
|
|
b532c052ad | ||
|
|
a392065ca8 | ||
|
|
0195d2ec43 | ||
|
|
cc27cf5c24 | ||
|
|
fa5b500751 | ||
|
|
2c9226e4b1 | ||
|
|
6908e297f6 | ||
|
|
8ff09b3a62 | ||
|
|
ea3e89dd00 | ||
|
|
b8e8370a4c | ||
|
|
2cb6d2f3d0 | ||
|
|
71dee66573 | ||
|
|
65da18d143 | ||
|
|
f21bef93b1 | ||
|
|
3f43994766 | ||
|
|
06862d354e | ||
|
|
06aded3ce4 | ||
|
|
cdb6aabdbf | ||
|
|
c23e8f8c65 | ||
|
|
db69d016a9 | ||
|
|
9e5e3fba16 | ||
|
|
135d6e510e | ||
|
|
0e3635f98b | ||
|
|
127f446856 | ||
|
|
0ae8c972bb | ||
|
|
03d4cc43ec | ||
|
|
04fae1194f | ||
|
|
f6867a914e | ||
|
|
0f8bd7fab8 | ||
|
|
7124ce742f | ||
|
|
1ea72a0080 | ||
|
|
2b20528eee | ||
|
|
a3d73fa5ff | ||
|
|
9547b0fc72 | ||
|
|
bccfef77c9 | ||
|
|
b01c6b68b6 | ||
|
|
9c6cdff785 | ||
|
|
6d4f1d6c43 | ||
|
|
4784ec9915 | ||
|
|
55f19fdcdc | ||
|
|
60ce0e0cd4 | ||
|
|
001e041855 | ||
|
|
b31b2af58b | ||
|
|
6e04648284 | ||
|
|
59fe01cef6 | ||
|
|
e0a823bf58 | ||
|
|
3ec59540bd | ||
|
|
01c887530b | ||
|
|
5824c9de52 | ||
|
|
c3805b6b2f | ||
|
|
469add4b6b | ||
|
|
a66fe41977 | ||
|
|
63e53724c4 | ||
|
|
85bd909af2 | ||
|
|
94a19f627b | ||
|
|
e0d25951a2 | ||
|
|
c14adf700a | ||
|
|
8e2e4e7c30 | ||
|
|
a1020cde7d | ||
|
|
d1da7f4a47 | ||
|
|
55a45c7673 | ||
|
|
e9361e74e8 | ||
|
|
c33c08392c | ||
|
|
dcacc7969f | ||
|
|
63110b204d |
@@ -1,4 +1,4 @@
|
||||
name: Build Site
|
||||
name: Blog
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -20,9 +20,7 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: 'Download Zola'
|
||||
run: curl -sL https://github.com/getzola/zola/releases/download/v0.9.0/zola-v0.9.0-x86_64-unknown-linux-gnu.tar.gz | tar zxv
|
||||
- name: "Install Python Tools"
|
||||
run: python -m pip install --upgrade pip setuptools wheel
|
||||
run: curl -sL https://github.com/getzola/zola/releases/download/v0.19.0/zola-v0.19.0-x86_64-unknown-linux-gnu.tar.gz | tar zxv
|
||||
- name: 'Install Python Libraries'
|
||||
run: python -m pip install --user -r requirements.txt
|
||||
working-directory: "blog"
|
||||
@@ -35,26 +33,11 @@ jobs:
|
||||
working-directory: "blog"
|
||||
|
||||
- name: Upload Generated Site
|
||||
uses: actions/upload-artifact@v1.0.0
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: generated_site
|
||||
path: blog/public
|
||||
|
||||
zola_check:
|
||||
name: "Zola Check"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: 'Download Zola'
|
||||
run: curl -sL https://github.com/getzola/zola/releases/download/v0.9.0/zola-v0.9.0-x86_64-unknown-linux-gnu.tar.gz | tar zxv
|
||||
|
||||
- name: "Run zola check"
|
||||
run: ../zola check
|
||||
working-directory: "blog"
|
||||
continue-on-error: true
|
||||
|
||||
check_spelling:
|
||||
name: "Check Spelling"
|
||||
runs-on: ubuntu-latest
|
||||
@@ -62,22 +45,23 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- run: curl -L https://git.io/misspell | bash
|
||||
name: "Install misspell"
|
||||
- run: bin/misspell -error blog/content
|
||||
name: "Check for common typos"
|
||||
- name: Typo Check
|
||||
uses: crate-ci/typos@v1.1.9
|
||||
with:
|
||||
files: blog
|
||||
|
||||
deploy_site:
|
||||
name: "Deploy Generated Site"
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_site, check_spelling]
|
||||
if: github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'schedule')
|
||||
if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'schedule')
|
||||
|
||||
steps:
|
||||
- name: "Download Generated Site"
|
||||
uses: actions/download-artifact@v1
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: generated_site
|
||||
path: generated_site
|
||||
|
||||
- name: Setup SSH Keys and known_hosts
|
||||
run: |
|
||||
@@ -85,7 +69,7 @@ jobs:
|
||||
ssh-keyscan github.com >> ~/.ssh/known_hosts
|
||||
ssh-agent -a $SSH_AUTH_SOCK > /dev/null
|
||||
ssh-add - <<< "$deploy_key"
|
||||
echo ::set-env name=SSH_AUTH_SOCK::$SSH_AUTH_SOCK
|
||||
echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" >> $GITHUB_ENV
|
||||
env:
|
||||
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||
deploy_key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
27
.github/workflows/check-links.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Check Links
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "*"
|
||||
- "!staging.tmp"
|
||||
tags:
|
||||
- "*"
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: "0 0 1/4 * *" # every 4 days
|
||||
|
||||
jobs:
|
||||
zola_check:
|
||||
name: "Zola Link Check"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: "Download Zola"
|
||||
run: curl -sL https://github.com/getzola/zola/releases/download/v0.19.0/zola-v0.19.0-x86_64-unknown-linux-gnu.tar.gz | tar zxv
|
||||
|
||||
- name: "Run zola check"
|
||||
run: ../zola check
|
||||
working-directory: "blog"
|
||||
33
.github/workflows/scheduled-builds.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Build code on schedule
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '40 1 * * *' # every day at 1:40
|
||||
|
||||
jobs:
|
||||
trigger-build:
|
||||
name: Trigger Build
|
||||
strategy:
|
||||
matrix:
|
||||
branch: [
|
||||
post-01,
|
||||
post-02,
|
||||
post-03,
|
||||
post-04,
|
||||
post-05,
|
||||
post-06,
|
||||
post-07,
|
||||
post-08,
|
||||
post-09,
|
||||
post-10,
|
||||
post-11,
|
||||
post-12,
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Invoke workflow
|
||||
uses: benc-uk/workflow-dispatch@v1.1
|
||||
with:
|
||||
workflow: Code
|
||||
token: ${{ secrets.SCHEDULED_BUILDS_TOKEN }}
|
||||
ref: ${{ matrix.branch }}
|
||||
@@ -1,6 +1,6 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
@@ -174,28 +174,3 @@
|
||||
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.
|
||||
|
||||
15
README.md
@@ -10,7 +10,7 @@ The code for each post lives in a separate git branch. This makes it possible to
|
||||
|
||||
**The code for the latest post is available [here][latest-post].**
|
||||
|
||||
[latest-post]: https://github.com/phil-opp/blog_os/tree/post-11
|
||||
[latest-post]: https://github.com/phil-opp/blog_os/tree/post-12
|
||||
|
||||
You can find the branch for each post by following the `(source code)` link in the [post list](#posts) below. The branches are named `post-XX` where `XX` is the post number, for example `post-03` for the _VGA Text Mode_ post or `post-07` for the _Hardware Interrupts_ post. For build instructions, see the Readme of the respective branch.
|
||||
|
||||
@@ -59,10 +59,17 @@ The goal of this project is to provide step-by-step tutorials in individual blog
|
||||
- [Allocator Designs](https://os.phil-opp.com/allocator-designs/)
|
||||
([source code](https://github.com/phil-opp/blog_os/tree/post-11))
|
||||
|
||||
**Multitasking**:
|
||||
|
||||
- [Async/Await](https://os.phil-opp.com/async-await/)
|
||||
([source code](https://github.com/phil-opp/blog_os/tree/post-12))
|
||||
|
||||
## First Edition Posts
|
||||
|
||||
The current version of the blog is already the second edition. The first edition is outdated and no longer maintained, but might still be useful. The posts of the first edition are:
|
||||
|
||||
<details><summary><i>Click to expand</i></summary>
|
||||
|
||||
**Bare Bones:**
|
||||
|
||||
- [A Minimal x86 Kernel](https://os.phil-opp.com/multiboot-kernel.html)
|
||||
@@ -105,13 +112,15 @@ The current version of the blog is already the second edition. The first edition
|
||||
- [Returning from Exceptions](https://os.phil-opp.com/returning-from-exceptions.html)
|
||||
([source code](https://github.com/phil-opp/blog_os/tree/returning_from_exceptions))
|
||||
|
||||
</details>
|
||||
|
||||
## License
|
||||
|
||||
This project, with exception of the `blog/content` folder, is licensed under either of
|
||||
|
||||
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||
http://www.apache.org/licenses/LICENSE-2.0)
|
||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
https://www.apache.org/licenses/LICENSE-2.0)
|
||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
|
||||
1
blog/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/public
|
||||
zola
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import io
|
||||
import urllib
|
||||
import datetime
|
||||
from github import Github
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
g = Github()
|
||||
|
||||
one_month_ago = datetime.now() - timedelta(days=32)
|
||||
one_month_ago = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=32)
|
||||
|
||||
def filter_date(issue):
|
||||
return issue.closed_at > one_month_ago
|
||||
@@ -21,20 +22,20 @@ def format_number(number):
|
||||
with io.open("templates/auto/recent-updates.html", 'w', encoding='utf8') as recent_updates:
|
||||
recent_updates.truncate()
|
||||
|
||||
relnotes_issues = g.search_issues("is:merged", repo="phil-opp/blog_os", type="pr", label="relnotes")[:10]
|
||||
recent_relnotes_issues = filter(filter_date, relnotes_issues)
|
||||
relnotes_issues = g.search_issues("is:merged", repo="phil-opp/blog_os", type="pr", label="relnotes")[:100]
|
||||
recent_relnotes_issues = list(filter(filter_date, relnotes_issues))
|
||||
|
||||
if len(recent_relnotes_issues) == 0:
|
||||
recent_updates.write(u"No notable updates recently.")
|
||||
else:
|
||||
recent_updates.write(u"<ul>\n")
|
||||
|
||||
for pr in recent_relnotes_issues:
|
||||
for pr in sorted(recent_relnotes_issues, key=lambda issue: issue.closed_at, reverse=True):
|
||||
link = '<a href="' + pr.html_url + '">' + pr.title + "</a> "
|
||||
iso_date = pr.closed_at.isoformat()
|
||||
readable_date = pr.closed_at.strftime("%b %d")
|
||||
datetime = '<time datetime="' + iso_date + '">' + readable_date + '</time>'
|
||||
recent_updates.write(u" <li>" + link + datetime + "</li>\n")
|
||||
datetime_str = '<time datetime="' + iso_date + '">' + readable_date + '</time>'
|
||||
recent_updates.write(u" <li>" + link + datetime_str + "</li>\n")
|
||||
|
||||
recent_updates.write(u"</ul>")
|
||||
|
||||
@@ -47,3 +48,42 @@ with io.open("templates/auto/stars.html", 'w', encoding='utf8') as stars:
|
||||
with io.open("templates/auto/forks.html", 'w', encoding='utf8') as forks:
|
||||
forks.truncate()
|
||||
forks.write(format_number(repo.forks_count))
|
||||
|
||||
|
||||
# query "This week in Rust OSDev posts"
|
||||
|
||||
lines = []
|
||||
year = 2020
|
||||
month = 4
|
||||
while True:
|
||||
url = "https://rust-osdev.com/this-month/" + str(year) + "-" + str(month).zfill(2) + "/"
|
||||
try:
|
||||
urllib.request.urlopen(url)
|
||||
except urllib.error.HTTPError as e:
|
||||
break
|
||||
|
||||
month_str = datetime.date(1900, month, 1).strftime('%B')
|
||||
|
||||
link = '<a href="' + url + '">This Month in Rust OSDev (' + month_str + " " + str(year) + ")</a> "
|
||||
lines.append(u" <li><b>" + link + "</b></li>\n")
|
||||
|
||||
month = month + 1
|
||||
if month > 12:
|
||||
month = 1
|
||||
year = year + 1
|
||||
|
||||
lines.reverse()
|
||||
|
||||
with io.open("templates/auto/status-updates.html", 'w', encoding='utf8') as status_updates:
|
||||
status_updates.truncate()
|
||||
|
||||
for line in lines:
|
||||
status_updates.write(line)
|
||||
|
||||
with io.open("templates/auto/status-updates-truncated.html", 'w', encoding='utf8') as status_updates:
|
||||
status_updates.truncate()
|
||||
|
||||
for index, line in enumerate(lines):
|
||||
if index == 5:
|
||||
break
|
||||
status_updates.write(line)
|
||||
|
||||
274
blog/config.toml
@@ -1,13 +1,281 @@
|
||||
title = "Writing an OS in Rust"
|
||||
base_url = "https://os.phil-opp.com"
|
||||
title = "Writing an OS in Rust"
|
||||
description = "This blog series creates a small operating system in the Rust programming language. Each post is a small tutorial and includes all needed code."
|
||||
|
||||
generate_feeds = true
|
||||
feed_filenames = ["rss.xml"]
|
||||
compile_sass = true
|
||||
minify_html = false
|
||||
|
||||
ignored_content = ["*/README.md", "*/LICENSE-CC-BY-NC"]
|
||||
|
||||
[markdown]
|
||||
highlight_code = true
|
||||
highlight_theme = "visual-studio-dark"
|
||||
generate_rss = true
|
||||
smart_punctuation = true
|
||||
|
||||
ignored_content = ["*/README.md"]
|
||||
[link_checker]
|
||||
skip_prefixes = [
|
||||
"https://crates.io/crates", # see https://github.com/rust-lang/crates.io/issues/788
|
||||
"https://www.amd.com/system/files/TechDocs/", # seems to have problems with PDFs
|
||||
"https://developer.apple.com/library/archive/qa/qa1118/_index.html", # results in a 401 (I don't know why)
|
||||
"https://github.com", # rate limiting often leads to "Error 429 Too Many Requests"
|
||||
"https://www.linkedin.com/", # seems to send invalid HTTP status codes
|
||||
]
|
||||
skip_anchor_prefixes = [
|
||||
"https://github.com/", # see https://github.com/getzola/zola/issues/805
|
||||
"https://docs.rs/x86_64/0.1.2/src/", # source code highlight
|
||||
"https://doc.rust-jp.rs/book-ja/", # seems like Zola has problems with Japanese anchor names
|
||||
"https://doc.rust-jp.rs/edition-guide/rust-2018", # seems like Zola has problems with Japanese anchor names
|
||||
"https://doc.rust-jp.rs/rust-nomicon-ja/", # seems like Zola has problems with Japanese anchor names
|
||||
]
|
||||
|
||||
[extra]
|
||||
subtitle = "Philipp Oppermann's blog"
|
||||
author = { name = "Philipp Oppermann" }
|
||||
default_language = "en"
|
||||
languages = ["en", "zh-CN", "zh-TW", "fr", "ja", "fa", "ru", "ko", "ar", "es"]
|
||||
|
||||
[translations]
|
||||
lang_name = "English (original)"
|
||||
toc = "Table of Contents"
|
||||
all_posts = "« All Posts"
|
||||
comments = "Comments"
|
||||
comments_notice = "Please leave your comments in English if possible."
|
||||
readmore = "read more »"
|
||||
not_translated = "(This post is not translated yet.)"
|
||||
translated_content = "Translated Content:"
|
||||
translated_content_notice = "This is a community translation of the <strong><a href=\"_original.permalink_\">_original.title_</a></strong> post. It might be incomplete, outdated or contain errors. Please report any issues!"
|
||||
translated_by = "Translation by"
|
||||
translation_contributors = "With contributions from"
|
||||
word_separator = "and"
|
||||
support_me = """
|
||||
<h2>Support Me</h2>
|
||||
<p>Creating and maintaining this blog and the associated libraries is a lot of work, but I really enjoy doing it. By supporting me, you allow me to invest more time in new content, new features, and continuous maintenance. The best way to support me is to <a href="https://github.com/sponsors/phil-opp"><em>sponsor me on GitHub</em></a>. Thank you!</p>
|
||||
"""
|
||||
comment_note = """
|
||||
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="_discussion_url_"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
|
||||
"""
|
||||
|
||||
# Chinese (simplified)
|
||||
[languages.zh-CN]
|
||||
title = "Writing an OS in Rust"
|
||||
description = "This blog series creates a small operating system in the Rust programming language. Each post is a small tutorial and includes all needed code."
|
||||
[languages.zh-CN.translations]
|
||||
lang_name = "Chinese (simplified)"
|
||||
toc = "目录"
|
||||
all_posts = "« 所有文章"
|
||||
comments = "评论"
|
||||
comments_notice = "请尽可能使用英语评论。"
|
||||
readmore = "更多 »"
|
||||
not_translated = "(该文章还没有被翻译。)"
|
||||
translated_content = "翻译内容:"
|
||||
translated_content_notice = "这是对原文章 <strong><a href=\"_original.permalink_\">_original.title_</a></strong> 的社区中文翻译。它可能不完整,过时或者包含错误。可以在 <a href=\"https://github.com/phil-opp/blog_os/issues/961\">这个 Issue</a> 上评论和提问!"
|
||||
translated_by = "翻译者:"
|
||||
translation_contributors = "With contributions from"
|
||||
word_separator = "和"
|
||||
support_me = """
|
||||
<h2>支持我</h2>
|
||||
<p>创建和维护这个博客以及相关的库带来了十分庞大的工作量,即便我十分热爱它们,仍然需要你们的支持。通过赞助我,可以让我有能投入更多时间与精力在创造新内容,开发新功能上。赞助我最好的办法是通过<a href="https://github.com/sponsors/phil-opp"><em>sponsor me on GitHub</em></a>. 十分感谢各位!</p>
|
||||
"""
|
||||
comment_note = """
|
||||
你有问题需要解决,想要分享反馈,或者讨论更多的想法吗?请随时在这里留下评论!请使用尽量使用英文并遵循 Rust 的 <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. 这个讨论串将与 <a href="_discussion_url_"><em>discussion on GitHub</em></a> 直接连接,所以你也可以直接在那边发表评论
|
||||
"""
|
||||
|
||||
# Chinese (traditional)
|
||||
[languages.zh-TW]
|
||||
title = "Writing an OS in Rust"
|
||||
description = "This blog series creates a small operating system in the Rust programming language. Each post is a small tutorial and includes all needed code."
|
||||
[languages.zh-TW.translations]
|
||||
lang_name = "Chinese (traditional)"
|
||||
toc = "目錄"
|
||||
all_posts = "« 所有文章"
|
||||
comments = "評論"
|
||||
comments_notice = "請儘可能使用英語評論。"
|
||||
readmore = "更多 »"
|
||||
not_translated = "(該文章還沒有被翻譯。)"
|
||||
translated_content = "翻譯內容:"
|
||||
translated_content_notice = "這是對原文章 <strong><a href=\"_original.permalink_\">_original.title_</a></strong> 的社區中文翻譯。它可能不完整,過時或者包含錯誤。可以在 <a href=\"https://github.com/phil-opp/blog_os/issues/961\">這個 Issue</a> 上評論和提問!"
|
||||
translated_by = "翻譯者:"
|
||||
translation_contributors = "With contributions from"
|
||||
word_separator = "和"
|
||||
support_me = """
|
||||
<h2>Support Me</h2>
|
||||
<p>Creating and maintaining this blog and the associated libraries is a lot of work, but I really enjoy doing it. By supporting me, you allow me to invest more time in new content, new features, and continuous maintenance. The best way to support me is to <a href="https://github.com/sponsors/phil-opp"><em>sponsor me on GitHub</em></a>. Thank you!</p>
|
||||
"""
|
||||
comment_note = """
|
||||
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="_discussion_url_"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
|
||||
"""
|
||||
|
||||
# Japanese
|
||||
[languages.ja]
|
||||
title = "Writing an OS in Rust"
|
||||
description = "This blog series creates a small operating system in the Rust programming language. Each post is a small tutorial and includes all needed code."
|
||||
[languages.ja.translations]
|
||||
lang_name = "Japanese"
|
||||
toc = "目次"
|
||||
all_posts = "« すべての記事へ"
|
||||
comments = "コメント"
|
||||
comments_notice = "可能な限りコメントは英語で残すようにしてください。"
|
||||
readmore = "もっと読む »"
|
||||
not_translated = "(この記事はまだ翻訳されていません。)"
|
||||
translated_content = "この記事は翻訳されたものです:"
|
||||
translated_content_notice = "この記事は<strong><a href=\"_original.permalink_\">_original.title_</a></strong>をコミュニティの手により翻訳したものです。そのため、翻訳が完全・最新でなかったり、原文にない誤りを含んでいる可能性があります。問題があれば<a href=\"https://github.com/phil-opp/blog_os/issues/906\">このissue</a>上で報告してください!"
|
||||
translated_by = "翻訳者:"
|
||||
translation_contributors = "With contributions from"
|
||||
word_separator = "及び"
|
||||
support_me = """
|
||||
<h2>Support Me</h2>
|
||||
<p>Creating and maintaining this blog and the associated libraries is a lot of work, but I really enjoy doing it. By supporting me, you allow me to invest more time in new content, new features, and continuous maintenance. The best way to support me is to <a href="https://github.com/sponsors/phil-opp"><em>sponsor me on GitHub</em></a>. Thank you!</p>
|
||||
"""
|
||||
comment_note = """
|
||||
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="_discussion_url_"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
|
||||
"""
|
||||
|
||||
# Persian
|
||||
[languages.fa]
|
||||
title = "Writing an OS in Rust"
|
||||
description = "This blog series creates a small operating system in the Rust programming language. Each post is a small tutorial and includes all needed code."
|
||||
[languages.fa.translations]
|
||||
lang_name = "Persian"
|
||||
toc = "فهرست مطالب"
|
||||
all_posts = "« همه پستها"
|
||||
comments = "نظرات"
|
||||
comments_notice = "لطفا نظرات خود را در صورت امکان به انگلیسی بنویسید."
|
||||
readmore = "ادامهمطلب»"
|
||||
not_translated = "(.این پست هنوز ترجمه نشده است)"
|
||||
translated_content = "محتوای ترجمه شده:"
|
||||
translated_content_notice = "این یک ترجمه از جامعه کاربران برای پست <strong><a href=\"_original.permalink_\">_original.title_</a></strong> است. ممکن است ناقص، منسوخ شده یا دارای خطا باشد. لطفا هر گونه مشکل را در <a href=\"https://github.com/phil-opp/blog_os/issues/908\">این ایشو</a> گزارش دهید!"
|
||||
translated_by = "ترجمه توسط"
|
||||
translation_contributors = "With contributions from"
|
||||
word_separator = "و"
|
||||
support_me = """
|
||||
<h2>Support Me</h2>
|
||||
<p>Creating and maintaining this blog and the associated libraries is a lot of work, but I really enjoy doing it. By supporting me, you allow me to invest more time in new content, new features, and continuous maintenance. The best way to support me is to <a href="https://github.com/sponsors/phil-opp"><em>sponsor me on GitHub</em></a>. Thank you!</p>
|
||||
"""
|
||||
comment_note = """
|
||||
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="_discussion_url_"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
|
||||
"""
|
||||
|
||||
# Russian
|
||||
[languages.ru]
|
||||
title = "Writing an OS in Rust"
|
||||
description = "This blog series creates a small operating system in the Rust programming language. Each post is a small tutorial and includes all needed code."
|
||||
[languages.ru.translations]
|
||||
lang_name = "Russian"
|
||||
toc = "Содержание"
|
||||
all_posts = "« Все посты"
|
||||
comments = "Комментарии"
|
||||
comments_notice = "Пожалуйста, оставляйте комментарии на английском по возможности."
|
||||
readmore = "читать дальше »"
|
||||
not_translated = "(Этот пост еще не переведен.)"
|
||||
translated_content = "Переведенное содержание:"
|
||||
translated_content_notice = "Это перевод сообщества поста <strong><a href=\"_original.permalink_\">_original.title_</a></strong>. Он может быть неполным, устаревшим или содержать ошибки. Пожалуйста, сообщайте о любых проблемах!"
|
||||
translated_by = "Перевод сделан"
|
||||
translation_contributors = "With contributions from"
|
||||
word_separator = "и"
|
||||
support_me = """
|
||||
<h2>Support Me</h2>
|
||||
<p>Creating and maintaining this blog and the associated libraries is a lot of work, but I really enjoy doing it. By supporting me, you allow me to invest more time in new content, new features, and continuous maintenance. The best way to support me is to <a href="https://github.com/sponsors/phil-opp"><em>sponsor me on GitHub</em></a>. Thank you!</p>
|
||||
"""
|
||||
comment_note = """
|
||||
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="_discussion_url_"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
|
||||
"""
|
||||
|
||||
# French
|
||||
[languages.fr]
|
||||
title = "Writing an OS in Rust"
|
||||
description = "This blog series creates a small operating system in the Rust programming language. Each post is a small tutorial and includes all needed code."
|
||||
[languages.fr.translations]
|
||||
lang_name = "French"
|
||||
toc = "Table des matières"
|
||||
all_posts = "« Tous les articles"
|
||||
comments = "Commentaires"
|
||||
comments_notice = "Veuillez commenter en Anglais si possible."
|
||||
readmore = "Voir plus »"
|
||||
not_translated = "(Cet article n'est pas encore traduit.)"
|
||||
translated_content = "Contenu traduit : "
|
||||
translated_content_notice = "Ceci est une traduction communautaire de l'article <strong><a href=\"_original.permalink_\">_original.title_</a></strong>. Il peut être incomplet, obsolète ou contenir des erreurs. Veuillez signaler les quelconques problèmes !"
|
||||
translated_by = "Traduit par : "
|
||||
translation_contributors = "With contributions from"
|
||||
word_separator = "et"
|
||||
support_me = """
|
||||
<h2>Support Me</h2>
|
||||
<p>Creating and maintaining this blog and the associated libraries is a lot of work, but I really enjoy doing it. By supporting me, you allow me to invest more time in new content, new features, and continuous maintenance. The best way to support me is to <a href="https://github.com/sponsors/phil-opp"><em>sponsor me on GitHub</em></a>. Thank you!</p>
|
||||
"""
|
||||
comment_note = """
|
||||
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="_discussion_url_"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
|
||||
"""
|
||||
|
||||
# Korean
|
||||
[languages.ko]
|
||||
title = "Writing an OS in Rust"
|
||||
description = "This blog series creates a small operating system in the Rust programming language. Each post is a small tutorial and includes all needed code."
|
||||
[languages.ko.translations]
|
||||
lang_name = "Korean"
|
||||
toc = "목차"
|
||||
all_posts = "« 모든 게시글"
|
||||
comments = "댓글"
|
||||
comments_notice = "댓글은 가능하면 영어로 작성해주세요."
|
||||
readmore = "더 읽기 »"
|
||||
not_translated = "(아직 번역이 완료되지 않은 게시글입니다)"
|
||||
translated_content = "번역된 내용 : "
|
||||
translated_content_notice = "이것은 커뮤니티 멤버가 <strong><a href=\"_original.permalink_\">_original.title_</a></strong> 포스트를 번역한 글입니다. 부족한 설명이나 오류, 혹은 시간이 지나 더 이상 유효하지 않은 정보를 발견하시면 제보해주세요!"
|
||||
translated_by = "번역한 사람 : "
|
||||
translation_contributors = "With contributions from"
|
||||
word_separator = "와"
|
||||
support_me = """
|
||||
<h2>Support Me</h2>
|
||||
<p>Creating and maintaining this blog and the associated libraries is a lot of work, but I really enjoy doing it. By supporting me, you allow me to invest more time in new content, new features, and continuous maintenance. The best way to support me is to <a href="https://github.com/sponsors/phil-opp"><em>sponsor me on GitHub</em></a>. Thank you!</p>
|
||||
"""
|
||||
comment_note = """
|
||||
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="_discussion_url_"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
|
||||
"""
|
||||
|
||||
[languages.ar]
|
||||
title = "Writing an OS in Rust"
|
||||
[languages.ar.translations]
|
||||
lang_name = "Arabic"
|
||||
toc = "Table of Contents"
|
||||
all_posts = "« All Posts"
|
||||
comments = "Comments"
|
||||
comments_notice = "Please leave your comments in English if possible."
|
||||
readmore = "read more »"
|
||||
not_translated = "(This post is not translated yet.)"
|
||||
translated_content = "Translated Content:"
|
||||
translated_content_notice = "This is a community translation of the <strong><a href=\"_original.permalink_\">_original.title_</a></strong> post. It might be incomplete, outdated or contain errors. Please report any issues!"
|
||||
translated_by = "Translation by"
|
||||
translation_contributors = "With contributions from"
|
||||
word_separator = "and"
|
||||
support_me = """
|
||||
<h2>Support Me</h2>
|
||||
<p>Creating and maintaining this blog and the associated libraries is a lot of work, but I really enjoy doing it. By supporting me, you allow me to invest more time in new content, new features, and continuous maintenance. The best way to support me is to <a href="https://github.com/sponsors/phil-opp"><em>sponsor me on GitHub</em></a>. Thank you!</p>
|
||||
"""
|
||||
comment_note = """
|
||||
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="_discussion_url_"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
|
||||
"""
|
||||
|
||||
# Spanish
|
||||
[languages.es]
|
||||
title = "Writing an OS in Rust"
|
||||
description = "This blog series creates a small operating system in the Rust programming language. Each post is a small tutorial and includes all needed code."
|
||||
[languages.es.translations]
|
||||
lang_name = "Spanish"
|
||||
toc = "Tabla de Contenidos"
|
||||
all_posts = "« Todos los Posts"
|
||||
comments = "Comentarios"
|
||||
comments_notice = "Por favor deja tus comentarios en inglés si es posible."
|
||||
readmore = "leer más »"
|
||||
not_translated = "(Este post aún no está traducido.)"
|
||||
translated_content = "Contenido Traducido:"
|
||||
translated_content_notice = "Esta es una traducción comunitaria del post <strong><a href=\"_original.permalink_\">_original.title_</a></strong>. Puede estar incompleta, desactualizada o contener errores. ¡Por favor reporta cualquier problema!"
|
||||
translated_by = "Traducción por"
|
||||
translation_contributors = "Con contribuciones de"
|
||||
word_separator = "y"
|
||||
support_me = """
|
||||
<h2>Apóyame</h2>
|
||||
<p>Crear y mantener este blog y las bibliotecas asociadas es mucho trabajo, pero realmente disfruto haciéndolo. Al apoyarme, me permites invertir más tiempo en nuevo contenido, nuevas características y mantenimiento continuo. La mejor manera de apoyarme es <a href=\"https://github.com/sponsors/phil-opp\"><em>patrocinarme en GitHub</em></a>. ¡Gracias!</p>
|
||||
"""
|
||||
comment_note = """
|
||||
¿Tienes algún problema, quieres compartir comentarios o discutir más ideas? ¡No dudes en dejar un comentario aquí! Por favor, utiliza inglés y sigue el <a href=\"https://www.rust-lang.org/policies/code-of-conduct\">código de conducta</a> de Rust. Este hilo de comentarios se vincula directamente con una <a href=\"_discussion_url_\"><em>discusión en GitHub</em></a>, así que también puedes comentar allí si lo prefieres.
|
||||
"""
|
||||
|
||||
@@ -4,13 +4,13 @@ This folder contains the content for the _"Writing an OS in Rust"_ blog.
|
||||
|
||||
## License
|
||||
|
||||
This folder is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License, available in [LICENSE-CC-BY-NC](LICENSE-CC-BY-NC) or under <http://creativecommons.org/licenses/by-nc/4.0/>.
|
||||
This folder is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License, available in [LICENSE-CC-BY-NC](LICENSE-CC-BY-NC) or under <https://creativecommons.org/licenses/by-nc/4.0/>.
|
||||
|
||||
All _code examples_ between markdown code blocks denoted by three backticks (<code>\`\`\`</code>) are additionally licensed under either of
|
||||
|
||||
- Apache License, Version 2.0 ([LICENSE-APACHE](../../LICENSE-APACHE) or
|
||||
http://www.apache.org/licenses/LICENSE-2.0)
|
||||
- MIT license ([LICENSE-MIT](../../LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
https://www.apache.org/licenses/LICENSE-2.0)
|
||||
- MIT license ([LICENSE-MIT](../../LICENSE-MIT) or https://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
|
||||
12
blog/content/_index.ar.md
Normal file
@@ -0,0 +1,12 @@
|
||||
+++
|
||||
template = "edition-2/index.html"
|
||||
+++
|
||||
|
||||
|
||||
<h1 style="visibility: hidden; height: 0px; margin: 0px; padding: 0px;">كتابة نظام تشغيل بلغة Rust </h1>
|
||||
<div class="front-page-introduction">
|
||||
|
||||
تنشئ سلسلة المدونات هذه نظام تشغيل صغير بلغة البرمجة [Rust ](https://www.rust-lang.org/). كل منشور هو عبارة عن برنامج تعليمي صغير ويتضمن كل الشيفرة المطلوبة، لذا يمكنك المتابعة إذا أردت. الكود المصدري متاح أيضًا في مستودع [Github ](https://github.com/phil-opp/blog_os) المقابل.
|
||||
|
||||
آخر منشور: <!-- latest-post -->
|
||||
</div>
|
||||
13
blog/content/_index.es.md
Normal file
@@ -0,0 +1,13 @@
|
||||
+++
|
||||
template = "edition-2/index.html"
|
||||
+++
|
||||
|
||||
<h1 style="visibility: hidden; height: 0px; margin: 0px; padding: 0px;">Escribiendo un sistema operativo en Rust</h1>
|
||||
|
||||
<div class="front-page-introduction">
|
||||
|
||||
Esta serie de blogs crea un pequeño sistema operativo en el [lenguaje de programación Rust](https://www.rust-lang.org/). Cada publicación es un pequeño tutorial e incluye todo el código necesario, para que puedas seguir los pasos si lo deseas. El código fuente también está disponible en el [repositorio correspondiente de Github](https://github.com/phil-opp/blog_os).
|
||||
|
||||
Última publicación: <!-- latest-post -->
|
||||
|
||||
</div>
|
||||
13
blog/content/_index.fa.md
Normal file
@@ -0,0 +1,13 @@
|
||||
+++
|
||||
template = "edition-2/index.html"
|
||||
+++
|
||||
|
||||
<h1 style="visibility: hidden; height: 0px; margin: 0px; padding: 0px;">نوشتن یک سیستم عامل با راست</h1>
|
||||
|
||||
<div class="front-page-introduction right-to-left">
|
||||
|
||||
این مجموعه بلاگ یک سیستم عامل کوچک در [زبان برنامه نویسی Rust](https://www.rust-lang.org/) ایجاد می کند. هر پست یک آموزش کوچک است و شامل تمام کدهای مورد نیاز است ، بنابراین اگر دوست دارید می توانید آن را دنبال کنید. کد منبع نیز در [مخزن گیتهاب](https://github.com/phil-opp/blog_os) مربوطه موجود است.
|
||||
|
||||
اخرین پست: <!-- latest-post -->
|
||||
|
||||
</div>
|
||||
13
blog/content/_index.fr.md
Normal file
@@ -0,0 +1,13 @@
|
||||
+++
|
||||
template = "edition-2/index.html"
|
||||
+++
|
||||
|
||||
<h1 style="visibility: hidden; height: 0px; margin: 0px; padding: 0px;">Écrire un OS en Rust</h1>
|
||||
|
||||
<div class="front-page-introduction">
|
||||
|
||||
L'objectif de ce blog est de créer un petit système d'exploitation avec le [langage de programmation Rust](https://www.rust-lang.org/). Chaque article est un petit tutoriel et comprend tout le code nécessaire, vous pouvez donc essayer en même temps si vous le souhaitez. Le code source est aussi disponible dans le [dépôt GitHub](https://github.com/phil-opp/blog_os) correspondant.
|
||||
|
||||
Dernier article : <!-- latest-post -->
|
||||
|
||||
</div>
|
||||
13
blog/content/_index.ja.md
Normal file
@@ -0,0 +1,13 @@
|
||||
+++
|
||||
template = "edition-2/index.html"
|
||||
+++
|
||||
|
||||
<h1 style="visibility: hidden; height: 0px; margin: 0px; padding: 0px;">RustでOSを書く</h1>
|
||||
|
||||
<div class="front-page-introduction">
|
||||
|
||||
このブログシリーズでは、ちょっとしたオペレーティングシステムを[Rustプログラミング言語](https://www.rust-lang.org/)を使って作ります。それぞれの記事が小さなチュートリアルになっており、必要なコードも全て記事内に記されているので、一つずつ読み進めて行けば理解できるでしょう。対応した[Githubリポジトリ](https://github.com/phil-opp/blog_os)でソースコードを見ることもできます。
|
||||
|
||||
最新記事: <!-- latest-post -->
|
||||
|
||||
</div>
|
||||
14
blog/content/_index.ko.md
Normal file
@@ -0,0 +1,14 @@
|
||||
+++
|
||||
template = "edition-2/index.html"
|
||||
+++
|
||||
|
||||
<h1 style="visibility: hidden; height: 0px; margin: 0px; padding: 0px;">Rust로 OS 구현하기</h1>
|
||||
|
||||
<div class="front-page-introduction">
|
||||
|
||||
이 블로그 시리즈는 [Rust 프로그래밍 언어](https://www.rust-lang.org/)로 작은 OS를 구현하는 것을 주제로 합니다.
|
||||
각 포스트는 구현에 필요한 소스 코드를 포함한 작은 튜토리얼 형식으로 구성되어 있습니다. 소스 코드는 이 블로그의 [Github 저장소](https://github.com/phil-opp/blog_os)에서도 확인하실 수 있습니다.
|
||||
|
||||
최신 포스트: <!-- latest-post -->
|
||||
|
||||
</div>
|
||||
13
blog/content/_index.md
Normal file
@@ -0,0 +1,13 @@
|
||||
+++
|
||||
template = "edition-2/index.html"
|
||||
+++
|
||||
|
||||
<h1 style="visibility: hidden; height: 0px; margin: 0px; padding: 0px;">Writing an OS in Rust</h1>
|
||||
|
||||
<div class="front-page-introduction">
|
||||
|
||||
This blog series creates a small operating system in the [Rust programming language](https://www.rust-lang.org/). Each post is a small tutorial and includes all needed code, so you can follow along if you like. The source code is also available in the corresponding [Github repository](https://github.com/phil-opp/blog_os).
|
||||
|
||||
Latest post: <!-- latest-post -->
|
||||
|
||||
</div>
|
||||
13
blog/content/_index.ru.md
Normal file
@@ -0,0 +1,13 @@
|
||||
+++
|
||||
template = "edition-2/index.html"
|
||||
+++
|
||||
|
||||
<h1 style="visibility: hidden; height: 0px; margin: 0px; padding: 0px;">Собственная операционная система на Rust</h1>
|
||||
|
||||
<div class="front-page-introduction">
|
||||
|
||||
Этот блог посвящен написанию маленькой операционной системы на [языке программирования Rust](https://www.rust-lang.org/). Каждый пост — это маленькое руководство, включающее в себя весь необходимый код, — вы сможете следовать ему, если пожелаете. Исходный код также доступен в соотвестующем [репозитории на Github](https://github.com/phil-opp/blog_os).
|
||||
|
||||
Последний пост: <!-- latest-post -->
|
||||
|
||||
</div>
|
||||
13
blog/content/_index.zh-CN.md
Normal file
@@ -0,0 +1,13 @@
|
||||
+++
|
||||
template = "edition-2/index.html"
|
||||
+++
|
||||
|
||||
<h1 style="visibility: hidden; height: 0px; margin: 0px; padding: 0px;">用Rust写一个操作系统</h1>
|
||||
|
||||
<div class="front-page-introduction">
|
||||
|
||||
这个博客系列用[Rust编程语言](https://www.rust-lang.org/)编写了一个小操作系统。每篇文章都是一个小教程,并且包含了所有代码,你可以跟着一起学习。源代码也放在了[Github 仓库](https://github.com/phil-opp/blog_os)。
|
||||
|
||||
最新文章: <!-- latest-post -->
|
||||
|
||||
</div>
|
||||
13
blog/content/_index.zh-TW.md
Normal file
@@ -0,0 +1,13 @@
|
||||
+++
|
||||
template = "edition-2/index.html"
|
||||
+++
|
||||
|
||||
<h1 style="visibility: hidden; height: 0px; margin: 0px; padding: 0px;">Writing an OS in Rust</h1>
|
||||
|
||||
<div class="front-page-introduction">
|
||||
|
||||
This blog series creates a small operating system in the [Rust programming language](https://www.rust-lang.org/). Each post is a small tutorial and includes all needed code, so you can follow along if you like. The source code is also available in the corresponding [Github repository](https://github.com/phil-opp/blog_os).
|
||||
|
||||
Latest post: <!-- latest-post -->
|
||||
|
||||
</div>
|
||||
5
blog/content/edition-1/_index.md
Normal file
@@ -0,0 +1,5 @@
|
||||
+++
|
||||
title = "First Edition"
|
||||
template = "edition-1/index.html"
|
||||
aliases = ["first-edition/index.html"]
|
||||
+++
|
||||
@@ -4,7 +4,7 @@ weight = 1
|
||||
path = "catching-exceptions"
|
||||
aliases = ["catching-exceptions.html"]
|
||||
date = 2016-05-28
|
||||
template = "first-edition/page.html"
|
||||
template = "edition-1/page.html"
|
||||
[extra]
|
||||
updated = "2016-06-25"
|
||||
+++
|
||||
@@ -20,8 +20,8 @@ As always, the complete source code is on [GitHub]. Please file [issues] for any
|
||||
|
||||
> **Note**: This post describes how to handle exceptions using naked functions (see [“Handling Exceptions with Naked Functions”] for an overview). Our new way of handling exceptions can be found in the [“Handling Exceptions”] post.
|
||||
|
||||
[“Handling Exceptions with Naked Functions”]: @/first-edition/extra/naked-exceptions/_index.md
|
||||
[“Handling Exceptions”]: @/first-edition/posts/09-handling-exceptions/index.md
|
||||
[“Handling Exceptions with Naked Functions”]: @/edition-1/extra/naked-exceptions/_index.md
|
||||
[“Handling Exceptions”]: @/edition-1/posts/09-handling-exceptions/index.md
|
||||
|
||||
## Exceptions
|
||||
An exception signals that something is wrong with the current instruction. For example, the CPU issues an exception if the current instruction tries to divide by 0. When an exception occurs, the CPU interrupts its current work and immediately calls a specific exception handler function, depending on the exception type.
|
||||
@@ -35,7 +35,7 @@ We've already seen several types of exceptions in our kernel:
|
||||
|
||||
For the full list of exceptions check out the [OSDev wiki][exceptions].
|
||||
|
||||
[exceptions]: http://wiki.osdev.org/Exceptions
|
||||
[exceptions]: https://wiki.osdev.org/Exceptions
|
||||
|
||||
### The Interrupt Descriptor Table
|
||||
In order to catch and handle exceptions, we have to set up a so-called _Interrupt Descriptor Table_ (IDT). In this table we can specify a handler function for each CPU exception. The hardware uses this table directly, so we need to follow a predefined format. Each entry must have the following 16-byte structure:
|
||||
@@ -245,7 +245,7 @@ It needs to be a function with a defined [calling convention], as it called dire
|
||||
|
||||
It is important that the function is [diverging], i.e. it must never return. The reason is that the hardware doesn't _call_ the handler functions, it just _jumps_ to them after pushing some values to the stack. So our stack might look different:
|
||||
|
||||
[diverging]: https://doc.rust-lang.org/book/functions.html#diverging-functions
|
||||
[diverging]: https://doc.rust-lang.org/rust-by-example/fn/diverging.html
|
||||
|
||||

|
||||
|
||||
@@ -311,8 +311,8 @@ u64 | Offset | Virtual start address of the table.
|
||||
|
||||
This structure is already contained [in the x86_64 crate], so we don't need to create it ourselves. The same is true for the [lidt function]. So we just need to put the pieces together to create a `load` method:
|
||||
|
||||
[in the x86_64 crate]: http://docs.rs/x86_64/0.1.0/x86_64/instructions/tables/struct.DescriptorTablePointer.html
|
||||
[lidt function]: http://docs.rs/x86_64/0.1.0/x86_64/instructions/tables/fn.lidt.html
|
||||
[in the x86_64 crate]: https://docs.rs/x86_64/0.1.0/x86_64/instructions/tables/struct.DescriptorTablePointer.html
|
||||
[lidt function]: https://docs.rs/x86_64/0.1.0/x86_64/instructions/tables/fn.lidt.html
|
||||
|
||||
```rust
|
||||
impl Idt {
|
||||
@@ -422,7 +422,7 @@ extern "C" fn divide_by_zero_handler() -> ! {
|
||||
```
|
||||
We register a single handler function for a [divide by zero error] \(index 0). Like the name says, this exception occurs when dividing a number by 0. Thus we have an easy way to test our new exception handler.
|
||||
|
||||
[divide by zero error]: http://wiki.osdev.org/Exceptions#Divide-by-zero_Error
|
||||
[divide by zero error]: https://wiki.osdev.org/Exceptions#Division_Error
|
||||
|
||||
However, it doesn't work this way:
|
||||
|
||||
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@@ -4,7 +4,7 @@ weight = 2
|
||||
path = "better-exception-messages"
|
||||
aliases = ["better-exception-messages.html"]
|
||||
date = 2016-08-03
|
||||
template = "first-edition/page.html"
|
||||
template = "edition-1/page.html"
|
||||
[extra]
|
||||
updated = "2016-11-01"
|
||||
+++
|
||||
@@ -21,8 +21,8 @@ As always, the complete source code is on [GitHub]. Please file [issues] for any
|
||||
|
||||
> **Note**: This post describes how to handle exceptions using naked functions (see [“Handling Exceptions with Naked Functions”] for an overview). Our new way of handling exceptions can be found in the [“Handling Exceptions”] post.
|
||||
|
||||
[“Handling Exceptions with Naked Functions”]: @/first-edition/extra/naked-exceptions/_index.md
|
||||
[“Handling Exceptions”]: @/first-edition/posts/09-handling-exceptions/index.md
|
||||
[“Handling Exceptions with Naked Functions”]: @/edition-1/extra/naked-exceptions/_index.md
|
||||
[“Handling Exceptions”]: @/edition-1/posts/09-handling-exceptions/index.md
|
||||
|
||||
## Exceptions in Detail
|
||||
An exception signals that something is wrong with the currently-executed instruction. Whenever an exception occurs, the CPU interrupts its current work and starts an internal exception routine.
|
||||
@@ -394,7 +394,7 @@ The base pointer is initialized directly from the stack pointer (`rsp`) after pu
|
||||
The reason is that our exception handler is defined as `extern "C" function`, which specifies that it's using the C [calling convention]. On x86_64 Linux, the C calling convention is specified by the System V AMD64 ABI ([PDF][system v abi]). Section 3.2.2 defines the following:
|
||||
|
||||
[calling convention]: https://en.wikipedia.org/wiki/X86_calling_conventions
|
||||
[system v abi]: http://web.archive.org/web/20160801075139/http://www.x86-64.org/documentation/abi.pdf
|
||||
[system v abi]: https://web.archive.org/web/20160801075139/https://www.x86-64.org/documentation/abi.pdf
|
||||
|
||||
> The end of the input argument area shall be aligned on a 16 byte boundary. In other words, the value (%rsp + 8) is always a multiple of 16 when control is transferred to the function entry point.
|
||||
|
||||
@@ -628,7 +628,7 @@ bitflags! {
|
||||
|
||||
- When the `PROTECTION_VIOLATION` flag is set, the page fault was caused e.g. by a write to a read-only page. If it's not set, it was caused by accessing a non-present page.
|
||||
- The `CAUSED_BY_WRITE` flag specifies if the fault was caused by a write (if set) or a read (if not set).
|
||||
- The `USER_MODE` flag is set when the fault occurred in non-priviledged mode.
|
||||
- The `USER_MODE` flag is set when the fault occurred in non-privileged mode.
|
||||
- The `MALFORMED_TABLE` flag is set when the page table entry has a 1 in a reserved field.
|
||||
- When the `INSTRUCTION_FETCH` flag is set, the page fault occurred while fetching the next instruction.
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
@@ -4,7 +4,7 @@ weight = 3
|
||||
path = "returning-from-exceptions"
|
||||
aliases = ["returning-from-exceptions.html"]
|
||||
date = 2016-09-21
|
||||
template = "first-edition/page.html"
|
||||
template = "edition-1/page.html"
|
||||
[extra]
|
||||
updated = "2016-11-01"
|
||||
+++
|
||||
@@ -21,8 +21,8 @@ As always, the complete source code is on [GitHub]. Please file [issues] for any
|
||||
|
||||
> **Note**: This post describes how to handle exceptions using naked functions (see [“Handling Exceptions with Naked Functions”] for an overview). Our new way of handling exceptions can be found in the [“Handling Exceptions”] post.
|
||||
|
||||
[“Handling Exceptions with Naked Functions”]: @/first-edition/extra/naked-exceptions/_index.md
|
||||
[“Handling Exceptions”]: @/first-edition/posts/09-handling-exceptions/index.md
|
||||
[“Handling Exceptions with Naked Functions”]: @/edition-1/extra/naked-exceptions/_index.md
|
||||
[“Handling Exceptions”]: @/edition-1/posts/09-handling-exceptions/index.md
|
||||
|
||||
## Introduction
|
||||
Most exceptions are fatal and can't be resolved. For example, we can't return from a divide-by-zero exception in a reasonable way. However, there are some exceptions that we can resolve:
|
||||
@@ -38,11 +38,11 @@ The breakpoint exception is the perfect exception to test our upcoming return-fr
|
||||
|
||||
The breakpoint exception is commonly used in debuggers: When the user sets a breakpoint, the debugger overwrites the corresponding instruction with the `int3` instruction so that the CPU throws the breakpoint exception when it reaches that line. When the user wants to continue the program, the debugger replaces the `int3` instruction with the original instruction again and continues the program. For more details, see the [How debuggers work] series.
|
||||
|
||||
[How debuggers work]: http://eli.thegreenplace.net/2011/01/27/how-debuggers-work-part-2-breakpoints
|
||||
[How debuggers work]: https://eli.thegreenplace.net/2011/01/27/how-debuggers-work-part-2-breakpoints
|
||||
|
||||
For our use case, we don't need to overwrite any instructions (it wouldn't even be possible since we [set the page table flags] to read-only). Instead, we just want to print a message when the breakpoint instruction is executed and then continue the program.
|
||||
|
||||
[set the page table flags]: @/first-edition/posts/07-remap-the-kernel/index.md#using-the-correct-flags
|
||||
[set the page table flags]: @/edition-1/posts/07-remap-the-kernel/index.md#using-the-correct-flags
|
||||
|
||||
### Catching Breakpoints
|
||||
Let's start by defining a handler function for the breakpoint exception:
|
||||
@@ -126,9 +126,9 @@ Instead of pushing a return address, the CPU pushes the stack and instruction po
|
||||
So we can't use a normal `ret` instruction, since it expects a different stack frame layout. Instead, there is a special instruction for returning from exceptions: `iretq`.
|
||||
|
||||
### The `iretq` Instruction
|
||||
The `iretq` instruction is the one and only way to return from exceptions and is specifically designed for this purpose. The AMD64 manual ([PDF][amd-manual]) even demands that `iretq` “_must_ be used to terminate the exception or interrupt handler associated with the exception”.
|
||||
The `iretq` instruction is the one and only way to return from exceptions and is specifically designed for this purpose. The AMD64 instruction manual ([PDF][amd-manual]) even demands that `iretq` “_must_ be used to terminate the exception or interrupt handler associated with the exception”.
|
||||
|
||||
[amd-manual]: https://support.amd.com/TechDocs/24594.pdf
|
||||
[amd-manual]: https://www.amd.com/system/files/TechDocs/24594.pdf
|
||||
|
||||
IRETQ restores `rip`, `cs`, `rflags`, `rsp`, and `ss` from the values saved on the stack and thus continues the interrupted program. The instruction does not handle the optional error code, so it must be popped from the stack before.
|
||||
|
||||
@@ -144,7 +144,7 @@ EXCEPTION: BREAKPOINT at 0x110970
|
||||
|
||||
So let's disassemble the instruction at `0x110970` and its predecessor:
|
||||
|
||||
```shell
|
||||
```bash
|
||||
> objdump -d build/kernel-x86_64.bin | grep -B1 "110970:"
|
||||
11096f: cc int3
|
||||
110970: 48 c7 01 2a 00 00 00 movq $0x2a,(%rcx)
|
||||
@@ -216,7 +216,7 @@ Instead of the expected _“It did not crash”_ message after the breakpoint ex
|
||||
### Debugging
|
||||
Let's debug it using GDB. For that we execute `make debug` in one terminal (which starts QEMU with the `-s -S` flags) and then `make gdb` (which starts and connects GDB) in a second terminal. For more information about GDB debugging, check out our [Set Up GDB] guide.
|
||||
|
||||
[Set Up GDB]: @/first-edition/extra/set-up-gdb/index.md
|
||||
[Set Up GDB]: @/edition-1/extra/set-up-gdb/index.md
|
||||
|
||||
First we want to check if our `iretq` was successful. Therefore we set a breakpoint on the `println!("It did not crash line!")` statement in `src/lib.rs`. Let's assume that it's on line 61:
|
||||
|
||||
@@ -250,7 +250,7 @@ However, there is a major difference between exceptions and function calls: A fu
|
||||
[Calling conventions] specify the details of a function call. For example, they specify where function parameters are placed (e.g. in registers or on the stack) and how results are returned. On x86_64 Linux, the following rules apply for C functions (specified in the [System V ABI]):
|
||||
|
||||
[Calling conventions]: https://en.wikipedia.org/wiki/Calling_convention
|
||||
[System V ABI]: http://refspecs.linuxbase.org/elf/gabi41.pdf
|
||||
[System V ABI]: https://refspecs.linuxbase.org/elf/gabi41.pdf
|
||||
|
||||
- the first six integer arguments are passed in registers `rdi`, `rsi`, `rdx`, `rcx`, `r8`, `r9`
|
||||
- additional arguments are passed on the stack
|
||||
@@ -304,7 +304,7 @@ Unfortunately, Rust does not support such a calling convention. It was [proposed
|
||||
|
||||
[interrupt calling conventions]: https://github.com/rust-lang/rfcs/pull/1275
|
||||
[Naked functions]: https://github.com/rust-lang/rfcs/blob/master/text/1201-naked-fns.md
|
||||
[naked fn post]: @/first-edition/extra/naked-exceptions/02-better-exception-messages/index.md#naked-functions
|
||||
[naked fn post]: @/edition-1/extra/naked-exceptions/02-better-exception-messages/index.md#naked-functions
|
||||
|
||||
### A naked wrapper function
|
||||
|
||||
@@ -426,7 +426,7 @@ The page fault is gone and we see the _“It did not crash”_ message again!
|
||||
So the page fault occurred because our exception handler didn't preserve the scratch register `rax`. Our new `handler!` macro fixes this problem by saving all scratch registers (including `rax`) before calling exception handlers. Thus, `rax` still contains the valid memory address when `rust-main` continues execution.
|
||||
|
||||
## Multimedia Registers
|
||||
When we discussed calling conventions above, we assummed that a x86_64 CPU only has the following 16 registers: `rax`, `rbx`, `rcx`, `rdx`, `rsi`, `rdi`, `rsp`, `rbp`, `r8`, `r9`, `r10`, `r11`.`r12`, `r13`, `r14`, and `r15`. These registers are called _general purpose registers_ since each of them can be used for arithmetic and load/store instructions.
|
||||
When we discussed calling conventions above, we assumed that a x86_64 CPU only has the following 16 registers: `rax`, `rbx`, `rcx`, `rdx`, `rsi`, `rdi`, `rsp`, `rbp`, `r8`, `r9`, `r10`, `r11`.`r12`, `r13`, `r14`, and `r15`. These registers are called _general purpose registers_ since each of them can be used for arithmetic and load/store instructions.
|
||||
|
||||
However, modern CPUs also have a set of _special purpose registers_, which can be used to improve performance in several use cases. On x86_64, the most important set of special purpose registers are the _multimedia registers_. These registers are larger than the general purpose registers and can be used to speed up audio/video processing or matrix calculations. For example, we could use them to add two 4-dimensional vectors _in a single CPU instruction_:
|
||||
|
||||
@@ -505,7 +505,7 @@ A minimal target specification that describes the `x86_64-unknown-linux-gnu` tar
|
||||
```json
|
||||
{
|
||||
"llvm-target": "x86_64-unknown-linux-gnu",
|
||||
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
|
||||
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
|
||||
"target-endian": "little",
|
||||
"target-pointer-width": "64",
|
||||
"target-c-int-width": "32",
|
||||
@@ -518,7 +518,7 @@ The `llvm-target` field specifies the target triple that is passed to LLVM. We w
|
||||
|
||||
The other fields are used for conditional compilation. This allows crate authors to use `cfg` variables to write special code for depending on the OS or the architecture. There isn't any up-to-date documentation about these fields but the [corresponding source code][target specification] is quite readable.
|
||||
|
||||
[data layout]: http://llvm.org/docs/LangRef.html#data-layout
|
||||
[data layout]: https://llvm.org/docs/LangRef.html#data-layout
|
||||
[target specification]: https://github.com/rust-lang/rust/blob/c772948b687488a087356cb91432425662e034b9/src/librustc_back/target/mod.rs#L194-L214
|
||||
|
||||
#### Disabling MMX and SSE
|
||||
@@ -527,7 +527,7 @@ In order to disable the multimedia extensions, we create a new target named `x86
|
||||
```json
|
||||
{
|
||||
"llvm-target": "x86_64-unknown-linux-gnu",
|
||||
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
|
||||
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
|
||||
"target-endian": "little",
|
||||
"target-pointer-width": "64",
|
||||
"target-c-int-width": "32",
|
||||
@@ -574,7 +574,7 @@ It doesn't compile anymore. The error tells us that the Rust compiler no longer
|
||||
The [core library] is implicitly linked to all `no_std` crates and contains things such as `Result`, `Option`, and iterators. We've used that library without problems since [the very beginning], so why is it no longer available?
|
||||
|
||||
[core library]: https://doc.rust-lang.org/nightly/core/index.html
|
||||
[the very beginning]: @/first-edition/posts/03-set-up-rust/index.md
|
||||
[the very beginning]: @/edition-1/posts/03-set-up-rust/index.md
|
||||
|
||||
The problem is that the core library is distributed together with the Rust compiler as a _precompiled_ library. So it is only valid for the host triple, which is `x86_64-unknown-linux-gnu` in our case. If we want to compile code for other targets, we need to recompile `core` for these targets first.
|
||||
|
||||
@@ -695,7 +695,7 @@ So now our return-from-exception logic works without problems in _most_ cases. H
|
||||
## The Red Zone
|
||||
The [red zone] is an optimization of the [System V ABI] that allows functions to temporary use the 128 bytes below its stack frame without adjusting the stack pointer:
|
||||
|
||||
[red zone]: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64#the-red-zone
|
||||
[red zone]: https://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64#the-red-zone
|
||||
|
||||

|
||||
|
||||
@@ -709,7 +709,7 @@ However, this optimization leads to huge problems with exceptions. Let's assume
|
||||
|
||||
The CPU and the exception handler overwrite the data in red zone. But this data is still needed by the interrupted function. So the function won't work correctly anymore when we return from the exception handler. It might fail or cause another exception, but it could also lead to strange bugs that [take weeks to debug].
|
||||
|
||||
[take weeks to debug]: http://forum.osdev.org/viewtopic.php?t=21720
|
||||
[take weeks to debug]: https://forum.osdev.org/viewtopic.php?t=21720
|
||||
|
||||
### Adjusting our Exception Handler?
|
||||
The problem is that the [System V ABI] demands that the red zone _“shall not be modified by signal or interrupt handlers.”_ Our current exception handlers do not respect this. We could try to fix it by subtracting 128 from the stack pointer before pushing anything:
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
7
blog/content/edition-1/extra/naked-exceptions/_index.md
Normal file
@@ -0,0 +1,7 @@
|
||||
+++
|
||||
title = "Handling Exceptions using naked Functions"
|
||||
sort_by = "weight"
|
||||
template = "edition-1/handling-exceptions-with-naked-fns.html"
|
||||
insert_anchor_links = "left"
|
||||
aliases = ["first-edition/extra/naked-exceptions/index.html"]
|
||||
+++
|
||||
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
@@ -2,6 +2,7 @@
|
||||
title = "Set Up GDB"
|
||||
template = "plain.html"
|
||||
path = "set-up-gdb"
|
||||
aliases = ["set-up-gdb.html"]
|
||||
weight = 4
|
||||
+++
|
||||
|
||||
@@ -32,7 +33,7 @@ Remote 'g' packet reply is too long: [a very long number]
|
||||
```
|
||||
This issue is known [since 2012][gdb issue patch] but it is still not fixed. Maybe we find the reason in the [issue thread][gdb issue thread]:
|
||||
|
||||
[gdb issue patch]: http://www.cygwin.com/ml/gdb-patches/2012-03/msg00116.html
|
||||
[gdb issue patch]: https://web.archive.org/web/20190114181420/https://www.cygwin.com/ml/gdb-patches/2012-03/msg00116.html
|
||||
[gdb issue thread]: https://sourceware.org/bugzilla/show_bug.cgi?id=13984#c11
|
||||
|
||||
> from my (limited) experience, unless you ping the gdb-patches list weekly, this patch is more likely to remain forgotten :-)
|
||||
@@ -73,5 +74,5 @@ After connecting to QEMU, you can use various gdb commands to control execution
|
||||
|
||||
Of course there are many more commands. Feel free to send a PR if you think this list is missing something important. For a more complete GDB overview, check out [Beej's Quick Guide][bggdb] or the [website for Harvard's CS161 course][CS161].
|
||||
|
||||
[bggdb]: http://beej.us/guide/bggdb/
|
||||
[CS161]: http://www.eecs.harvard.edu/~cs161/resources/gdb.html
|
||||
[bggdb]: https://beej.us/guide/bggdb/
|
||||
[CS161]: https://www.eecs.harvard.edu/~cs161/resources/gdb.html
|
||||
@@ -4,12 +4,12 @@ weight = 1
|
||||
path = "multiboot-kernel"
|
||||
aliases = ["multiboot-kernel.html", "/2015/08/18/multiboot-kernel/", "/rust-os/multiboot-kernel.html"]
|
||||
date = 2015-08-18
|
||||
template = "first-edition/page.html"
|
||||
template = "edition-1/page.html"
|
||||
+++
|
||||
|
||||
This post explains how to create a minimal x86 operating system kernel using the Multiboot standard. In fact, it will just boot and print `OK` to the screen. In subsequent blog posts we will extend it using the [Rust] programming language.
|
||||
|
||||
[Rust]: http://www.rust-lang.org/
|
||||
[Rust]: https://www.rust-lang.org/
|
||||
|
||||
<!-- more -->
|
||||
|
||||
@@ -28,19 +28,19 @@ When you turn on a computer, it loads the [BIOS] from some special flash memory.
|
||||
|
||||
[BIOS]: https://en.wikipedia.org/wiki/BIOS
|
||||
[protected mode]: https://en.wikipedia.org/wiki/Protected_mode
|
||||
[real mode]: http://wiki.osdev.org/Real_Mode
|
||||
[real mode]: https://wiki.osdev.org/Real_Mode
|
||||
|
||||
We won't write a bootloader because that would be a complex project on its own (if you really want to do it, check out [_Rolling Your Own Bootloader_]). Instead we will use one of the [many well-tested bootloaders][bootloader comparison] out there to boot our kernel from a CD-ROM. But which one?
|
||||
|
||||
[_Rolling Your Own Bootloader_]: http://wiki.osdev.org/Rolling_Your_Own_Bootloader
|
||||
[_Rolling Your Own Bootloader_]: https://wiki.osdev.org/Rolling_Your_Own_Bootloader
|
||||
[bootloader comparison]: https://en.wikipedia.org/wiki/Comparison_of_boot_loaders
|
||||
|
||||
## Multiboot
|
||||
Fortunately there is a bootloader standard: the [Multiboot Specification][multiboot]. Our kernel just needs to indicate that it supports Multiboot and every Multiboot-compliant bootloader can boot it. We will use the Multiboot 2 specification ([PDF][Multiboot 2]) together with the well-known [GRUB 2] bootloader.
|
||||
|
||||
[multiboot]: https://en.wikipedia.org/wiki/Multiboot_Specification
|
||||
[multiboot 2]: http://nongnu.askapache.com/grub/phcoder/multiboot.pdf
|
||||
[grub 2]: http://wiki.osdev.org/GRUB_2
|
||||
[multiboot 2]: https://nongnu.askapache.com/grub/phcoder/multiboot.pdf
|
||||
[grub 2]: https://wiki.osdev.org/GRUB_2
|
||||
|
||||
To indicate our Multiboot 2 support to the bootloader, our kernel must start with a _Multiboot Header_, which has the following format:
|
||||
|
||||
@@ -130,7 +130,7 @@ Through assembling, viewing and disassembling we can see the CPU [Opcodes] in ac
|
||||
To boot our executable later through GRUB, it should be an [ELF] executable. So we want `nasm` to create ELF [object files] instead of plain binaries. To do that, we simply pass the `‑f elf64` argument to it.
|
||||
|
||||
[ELF]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
|
||||
[object files]: http://wiki.osdev.org/Object_Files
|
||||
[object files]: https://wiki.osdev.org/Object_Files
|
||||
|
||||
To create the ELF _executable_, we need to [link] the object files together. We use a custom [linker script] named `linker.ld`:
|
||||
|
||||
@@ -187,7 +187,7 @@ Idx Name Size VMA LMA File off Algn
|
||||
```
|
||||
_Note_: The `ld` and `objdump` commands are platform specific. If you're _not_ working on x86_64 architecture, you will need to [cross compile binutils]. Then use `x86_64‑elf‑ld` and `x86_64‑elf‑objdump` instead of `ld` and `objdump`.
|
||||
|
||||
[cross compile binutils]: @/first-edition/extra/cross-compile-binutils.md
|
||||
[cross compile binutils]: @/edition-1/extra/cross-compile-binutils.md
|
||||
|
||||
## Creating the ISO
|
||||
All PC BIOSes know how to boot from a CD-ROM, so we want to create a bootable CD-ROM image, containing our kernel and the GRUB bootloader's files, in a single file called an [ISO](https://en.wikipedia.org/wiki/ISO_image). Make the following directory structure and copy the `kernel.bin` to the right place:
|
||||
@@ -315,7 +315,7 @@ Now we can invoke `make` and all updated assembly files are compiled and linked.
|
||||
|
||||
In the [next post] we will create a page table and do some CPU configuration to switch to the 64-bit [long mode].
|
||||
|
||||
[next post]: @/first-edition/posts/02-entering-longmode/index.md
|
||||
[next post]: @/edition-1/posts/02-entering-longmode/index.md
|
||||
[long mode]: https://en.wikipedia.org/wiki/Long_mode
|
||||
|
||||
## Footnotes
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
@@ -4,15 +4,15 @@ weight = 2
|
||||
path = "entering-longmode"
|
||||
aliases = ["entering-longmode.html", "/2015/08/25/entering-longmode/", "/rust-os/entering-longmode.html"]
|
||||
date = 2015-08-25
|
||||
template = "first-edition/page.html"
|
||||
template = "edition-1/page.html"
|
||||
[extra]
|
||||
updated = "2015-10-29"
|
||||
+++
|
||||
|
||||
In the [previous post] we created a minimal multiboot kernel. It just prints `OK` and hangs. The goal is to extend it and call 64-bit [Rust] code. But the CPU is currently in [protected mode] and allows only 32-bit instructions and up to 4GiB memory. So we need to set up _Paging_ and switch to the 64-bit [long mode] first.
|
||||
|
||||
[previous post]: @/first-edition/posts/01-multiboot-kernel/index.md
|
||||
[Rust]: http://www.rust-lang.org/
|
||||
[previous post]: @/edition-1/posts/01-multiboot-kernel/index.md
|
||||
[Rust]: https://www.rust-lang.org/
|
||||
[protected mode]: https://en.wikipedia.org/wiki/Protected_mode
|
||||
[long mode]: https://en.wikipedia.org/wiki/Long_mode
|
||||
|
||||
@@ -39,7 +39,7 @@ error:
|
||||
At address `0xb8000` begins the so-called [VGA text buffer]. It's an array of screen characters that are displayed by the graphics card. A [future post] will cover the VGA buffer in detail and create a Rust interface to it. But for now, manual bit-fiddling is the easiest option.
|
||||
|
||||
[VGA text buffer]: https://en.wikipedia.org/wiki/VGA-compatible_text_mode
|
||||
[future post]: @/first-edition/posts/04-printing-to-screen/index.md
|
||||
[future post]: @/edition-1/posts/04-printing-to-screen/index.md
|
||||
|
||||
A screen character consists of a 8 bit color code and a 8 bit [ASCII] character. We used the color code `4f` for all characters, which means white text on red background. `0x52` is an ASCII `R`, `0x45` is an `E`, `0x3a` is a `:`, and `0x20` is a space. The second space is overwritten by the given ASCII byte. Finally the CPU is stopped with the `hlt` instruction.
|
||||
|
||||
@@ -47,7 +47,7 @@ A screen character consists of a 8 bit color code and a 8 bit [ASCII] character.
|
||||
|
||||
Now we can add some check _functions_. A function is just a normal label with an `ret` (return) instruction at the end. The `call` instruction can be used to call it. Unlike the `jmp` instruction that just jumps to a memory address, the `call` instruction will push a return address to the stack (and the `ret` will jump to this address). But we don't have a stack yet. The [stack pointer] in the esp register could point to some important data or even invalid memory. So we need to update it and point it to some valid stack memory.
|
||||
|
||||
[stack pointer]: http://stackoverflow.com/a/1464052/866447
|
||||
[stack pointer]: https://stackoverflow.com/a/1464052/866447
|
||||
|
||||
### Creating a Stack
|
||||
To create stack memory we reserve some bytes at the end of our `boot.asm`:
|
||||
@@ -96,14 +96,14 @@ We use the `cmp` instruction to compare the value in `eax` to the magic value. I
|
||||
|
||||
In `no_multiboot`, we use the `jmp` (“jump”) instruction to jump to our error function. We could just as well use the `call` instruction, which additionally pushes the return address. But the return address is not needed because `error` never returns. To pass `0` as error code to the `error` function, we move it into `al` before the jump (`error` will read it from there).
|
||||
|
||||
[Multiboot specification]: http://nongnu.askapache.com/grub/phcoder/multiboot.pdf
|
||||
[Multiboot specification]: https://nongnu.askapache.com/grub/phcoder/multiboot.pdf
|
||||
[FLAGS register]: https://en.wikipedia.org/wiki/FLAGS_register
|
||||
|
||||
### CPUID check
|
||||
[CPUID] is a CPU instruction that can be used to get various information about the CPU. But not every processor supports it. CPUID detection is quite laborious, so we just copy a detection function from the [OSDev wiki][CPUID detection]:
|
||||
|
||||
[CPUID]: http://wiki.osdev.org/CPUID
|
||||
[CPUID detection]: http://wiki.osdev.org/Setting_Up_Long_Mode#Detection_of_CPUID
|
||||
[CPUID]: https://wiki.osdev.org/CPUID
|
||||
[CPUID detection]: https://wiki.osdev.org/Setting_Up_Long_Mode#Detection_of_CPUID
|
||||
|
||||
```nasm
|
||||
check_cpuid:
|
||||
@@ -151,7 +151,7 @@ Don't worry, you don't need to understand the details.
|
||||
### Long Mode check
|
||||
Now we can use CPUID to detect whether long mode can be used. I use code from [OSDev][long mode detection] again:
|
||||
|
||||
[long mode detection]: http://wiki.osdev.org/Setting_Up_Long_Mode#x86_or_x86-64
|
||||
[long mode detection]: https://wiki.osdev.org/Setting_Up_Long_Mode#x86_or_x86-64
|
||||
|
||||
```nasm
|
||||
check_long_mode:
|
||||
@@ -400,7 +400,7 @@ Bit(s) | Name | Meaning
|
||||
54 | 32-bit | must be 0 for 64-bit segments
|
||||
55-63 | ignored | ignored in 64-bit mode
|
||||
|
||||
[ring level]: http://wiki.osdev.org/Security#Rings
|
||||
[ring level]: https://wiki.osdev.org/Security#Rings
|
||||
|
||||
We need one code segment, a data segment is not necessary in 64-bit mode. Code segments have the following bits set: _descriptor type_, _present_, _executable_ and the _64-bit_ flag. Translated to assembly the long mode GDT looks like this:
|
||||
|
||||
@@ -451,7 +451,7 @@ gdt64:
|
||||
```
|
||||
We can't just use a normal label here, since we need the table _offset_. We calculate this offset using the current address `$` and set the label to this value using [equ]. Now we can use `gdt64.code` instead of 8 and this label will still work if we modify the GDT.
|
||||
|
||||
[equ]: http://www.nasm.us/doc/nasmdoc3.html#section-3.2.4
|
||||
[equ]: https://www.nasm.us/doc/nasmdoc3.html#section-3.2.4
|
||||
|
||||
In order to finally enter the true 64-bit mode, we need to load `cs` with `gdt64.code`. But we can't do it through `mov`. The only way to reload the code selector is a _far jump_ or a _far return_. These instructions work like a normal jump/return but change the code selector. We use a far jump to a long mode label:
|
||||
|
||||
@@ -492,8 +492,8 @@ _Congratulations_! You have successfully wrestled through this CPU configuration
|
||||
#### One Last Thing
|
||||
Above, we reloaded the code segment register `cs` with the new GDT offset. However, the data segment registers `ss`, `ds`, `es`, `fs`, and `gs` still contain the data segment offsets of the old GDT. This isn't necessarily bad, since they're ignored by almost all instructions in 64-bit mode. However, there are a few instructions that expect a valid data segment descriptor _or the null descriptor_ in those registers. An example is the the [iretq] instruction that we'll need in the [_Returning from Exceptions_] post.
|
||||
|
||||
[iretq]: @/first-edition/extra/naked-exceptions/03-returning-from-exceptions/index.md#the-iretq-instruction
|
||||
[_Returning from Exceptions_]: @/first-edition/extra/naked-exceptions/03-returning-from-exceptions/index.md
|
||||
[iretq]: @/edition-1/extra/naked-exceptions/03-returning-from-exceptions/index.md#the-iretq-instruction
|
||||
[_Returning from Exceptions_]: @/edition-1/extra/naked-exceptions/03-returning-from-exceptions/index.md
|
||||
|
||||
To avoid future problems, we reload all data segment registers with null:
|
||||
|
||||
@@ -515,7 +515,7 @@ long_mode_start:
|
||||
It's time to finally leave assembly behind and switch to [Rust]. Rust is a systems language without garbage collections that guarantees memory safety. Through a real type system and many abstractions it feels like a high-level language but can still be low-level enough for OS development. The [next post] describes the Rust setup.
|
||||
|
||||
[Rust]: https://www.rust-lang.org/
|
||||
[next post]: @/first-edition/posts/03-set-up-rust/index.md
|
||||
[next post]: @/edition-1/posts/03-set-up-rust/index.md
|
||||
|
||||
## Footnotes
|
||||
[^hardware_lookup]: In the x86 architecture, the page tables are _hardware walked_, so the CPU will look at the table on its own when it needs a translation. Other architectures, for example MIPS, just throw an exception and let the OS translate the virtual address.
|
||||
@@ -4,15 +4,15 @@ weight = 3
|
||||
path = "set-up-rust"
|
||||
aliases = ["set-up-rust.html", "setup-rust.html", "/2015/09/02/setup-rust/", "/rust-os/setup-rust.html"]
|
||||
date = 2015-09-02
|
||||
template = "first-edition/page.html"
|
||||
template = "edition-1/page.html"
|
||||
[extra]
|
||||
updated = "2017-04-12"
|
||||
+++
|
||||
|
||||
In the previous posts we created a [minimal Multiboot kernel][multiboot post] and [switched to Long Mode][long mode post]. Now we can finally switch to [Rust] code. Rust is a high-level language without runtime. It allows us to not link the standard library and write bare metal code. Unfortunately the setup is not quite hassle-free yet.
|
||||
|
||||
[multiboot post]: @/first-edition/posts/01-multiboot-kernel/index.md
|
||||
[long mode post]: @/first-edition/posts/02-entering-longmode/index.md
|
||||
[multiboot post]: @/edition-1/posts/01-multiboot-kernel/index.md
|
||||
[long mode post]: @/edition-1/posts/02-entering-longmode/index.md
|
||||
[Rust]: https://www.rust-lang.org/
|
||||
|
||||
<!-- more -->
|
||||
@@ -31,12 +31,10 @@ nightly
|
||||
|
||||
[rustup]: https://www.rustup.rs/
|
||||
|
||||
The code from this post (and all following) is [automatically tested](https://travis-ci.org/phil-opp/blog_os) every day and should always work for the newest nightly. If it doesn't, please [file an issue](https://github.com/phil-opp/blog_os/issues).
|
||||
|
||||
## Creating a Cargo project
|
||||
[Cargo] is Rust's excellent package manager. Normally you would call `cargo new` when you want to create a new project folder. We can't use it because our folder already exists, so we need to do it manually. Fortunately we only need to add a cargo configuration file named `Cargo.toml`:
|
||||
|
||||
[Cargo]: http://doc.crates.io/guide.html
|
||||
[Cargo]: https://doc.crates.io/guide.html
|
||||
|
||||
```toml
|
||||
[package]
|
||||
@@ -49,7 +47,7 @@ crate-type = ["staticlib"]
|
||||
```
|
||||
The `package` section contains required project metadata such as the [semantic crate version]. The `lib` section specifies that we want to build a static library, i.e. a library that contains all of its dependencies. This is required to link the Rust project with our kernel.
|
||||
|
||||
[semantic crate version]: http://doc.crates.io/manifest.html#the-package-section
|
||||
[semantic crate version]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-package-section
|
||||
|
||||
Now we place our root source file in `src/lib.rs`:
|
||||
|
||||
@@ -92,7 +90,7 @@ Let's define some properties of our target system:
|
||||
- **No SSE**: Our target might not have [SSE] support. Even if it does, we probably don't want to use SSE instructions in our kernel, because it makes interrupt handling much slower. We will explain this in detail in the [“Handling Exceptions”] post.
|
||||
- **No hardware floats**: The `x86_64` architecture uses SSE instructions for floating point operations, which we don't want to use (see the previous point). So we also need to avoid hardware floating point operations in our kernel. Instead, we will use _soft floats_, which are basically software functions that emulate floating point operations using normal integers.
|
||||
|
||||
[“Handling Exceptions”]: @/first-edition/posts/09-handling-exceptions/index.md
|
||||
[“Handling Exceptions”]: @/edition-1/posts/09-handling-exceptions/index.md
|
||||
|
||||
### Target Specifications
|
||||
Rust allows us to define [custom targets] through a JSON configuration file. A minimal target specification equal to `x86_64-unknown-linux-gnu` (the default 64-bit Linux target) looks like this:
|
||||
@@ -100,7 +98,7 @@ Rust allows us to define [custom targets] through a JSON configuration file. A m
|
||||
```json
|
||||
{
|
||||
"llvm-target": "x86_64-unknown-linux-gnu",
|
||||
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
|
||||
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
|
||||
"linker-flavor": "gcc",
|
||||
"target-endian": "little",
|
||||
"target-pointer-width": "64",
|
||||
@@ -114,7 +112,7 @@ Rust allows us to define [custom targets] through a JSON configuration file. A m
|
||||
|
||||
The `llvm-target` field specifies the target triple that is passed to LLVM. [Target triples] are a naming convention that define the CPU architecture (e.g., `x86_64` or `arm`), the vendor (e.g., `apple` or `unknown`), the operating system (e.g., `windows` or `linux`), and the [ABI] \(e.g., `gnu` or `msvc`). For example, the target triple for 64-bit Linux is `x86_64-unknown-linux-gnu` and for 32-bit Windows the target triple is `i686-pc-windows-msvc`.
|
||||
|
||||
[Target triples]: http://llvm.org/docs/LangRef.html#target-triple
|
||||
[Target triples]: https://llvm.org/docs/LangRef.html#target-triple
|
||||
[ABI]: https://en.wikipedia.org/wiki/Application_binary_interface
|
||||
|
||||
The `data-layout` field is also passed to LLVM and specifies how data should be laid out in memory. It consists of various specifications separated by a `-` character. For example, the `e` means little endian and `S128` specifies that the stack should be 128 bits (= 16 byte) aligned. The format is described in detail in the [LLVM documentation][data layout] but there shouldn't be a reason to change this string.
|
||||
@@ -126,7 +124,7 @@ The `linker-flavor` field was recently introduced in [#40018] with the intention
|
||||
|
||||
The other fields are used for conditional compilation. This allows crate authors to use `cfg` variables to write special code for depending on the OS or the architecture. There isn't any up-to-date documentation about these fields but the [corresponding source code][target specification] is quite readable.
|
||||
|
||||
[data layout]: http://llvm.org/docs/LangRef.html#data-layout
|
||||
[data layout]: https://llvm.org/docs/LangRef.html#data-layout
|
||||
[target specification]: https://github.com/rust-lang/rust/blob/c772948b687488a087356cb91432425662e034b9/src/librustc_back/target/mod.rs#L194-L214
|
||||
|
||||
### A Kernel Target Specification
|
||||
@@ -135,7 +133,7 @@ For our target system, we define the following JSON configuration in a file name
|
||||
```json
|
||||
{
|
||||
"llvm-target": "x86_64-unknown-none",
|
||||
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
|
||||
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
|
||||
"linker-flavor": "gcc",
|
||||
"target-endian": "little",
|
||||
"target-pointer-width": "64",
|
||||
@@ -152,8 +150,8 @@ As `llvm-target` we use `x86_64-unknown-none`, which defines the `x86_64` archit
|
||||
#### The Red Zone
|
||||
The [red zone] is an optimization of the [System V ABI] that allows functions to temporary use the 128 bytes below its stack frame without adjusting the stack pointer:
|
||||
|
||||
[red zone]: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64#the-red-zone
|
||||
[System V ABI]: http://wiki.osdev.org/System_V_ABI
|
||||
[red zone]: https://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64#the-red-zone
|
||||
[System V ABI]: https://wiki.osdev.org/System_V_ABI
|
||||
|
||||

|
||||
|
||||
@@ -167,7 +165,7 @@ However, this optimization leads to huge problems with exceptions or hardware in
|
||||
|
||||
The CPU and the exception handler overwrite the data in red zone. But this data is still needed by the interrupted function. So the function won't work correctly anymore when we return from the exception handler. This might lead to strange bugs that [take weeks to debug].
|
||||
|
||||
[take weeks to debug]: http://forum.osdev.org/viewtopic.php?t=21720
|
||||
[take weeks to debug]: https://forum.osdev.org/viewtopic.php?t=21720
|
||||
|
||||
To avoid such bugs when we implement exception handling in the future, we disable the red zone right from the beginning. This is achieved by adding the `"disable-redzone": true` line to our target configuration file.
|
||||
|
||||
@@ -406,9 +404,7 @@ So the linker can't find a function named `_Unwind_Resume` that is referenced e.
|
||||
|
||||
[iterator.rs:389]: https://github.com/rust-lang/rust/blob/c58c928e658d2e45f816fd05796a964aa83759da/src/libcore/iter/iterator.rs#L389
|
||||
|
||||
By default, the destructors of all stack variables are run when a `panic` occurs. This is called _unwinding_ and allows parent threads to [recover from panics]. However, it requires a platform specific gcc library, which isn't available in our kernel.
|
||||
|
||||
[recover from panics]: https://doc.rust-lang.org/book/concurrency.html#panics
|
||||
By default, the destructors of all stack variables are run when a `panic` occurs. This is called _unwinding_ and allows parent threads to recover from panics. However, it requires a platform specific gcc library, which isn't available in our kernel.
|
||||
|
||||
Fortunately, Rust allows us to disable unwinding for our target. For that we add the following line to our `x86_64-blog_os.json` file:
|
||||
|
||||
@@ -479,17 +475,17 @@ Some notes:
|
||||
- `buffer_ptr` is a [raw pointer] that points to the center of the VGA text buffer
|
||||
- Rust doesn't know the VGA buffer and thus can't guarantee that writing to the `buffer_ptr` is safe (it could point to important data). So we need to tell Rust that we know what we are doing by using an [unsafe block].
|
||||
|
||||
[byte string]: https://doc.rust-lang.org/reference.html#characters-and-strings
|
||||
[byte string]: https://doc.rust-lang.org/reference/tokens.html#characters-and-strings
|
||||
[enumerate]: https://doc.rust-lang.org/nightly/core/iter/trait.Iterator.html#method.enumerate
|
||||
[unsafe block]: https://doc.rust-lang.org/book/unsafe.html
|
||||
|
||||
### Stack Overflows
|
||||
Since we still use the small 64 byte [stack from the last post], we must be careful not to [overflow] it. Normally, Rust tries to avoid stack overflows through _guard pages_: The page below the stack isn't mapped and such a stack overflow triggers a page fault (instead of silently overwriting random memory). But we can't unmap the page below our stack right now since we currently use only a single big page. Fortunately the stack is located just above the page tables. So some important page table entry would probably get overwritten on stack overflow and then a page fault occurs, too.
|
||||
|
||||
[stack from the last post]: @/first-edition/posts/02-entering-longmode/index.md#creating-a-stack
|
||||
[stack from the last post]: @/edition-1/posts/02-entering-longmode/index.md#creating-a-stack
|
||||
[overflow]: https://en.wikipedia.org/wiki/Stack_overflow
|
||||
|
||||
## What's next?
|
||||
Until now we write magic bits to some memory location when we want to print something to screen. In the [next post] we create a abstraction for the VGA text buffer that allows us to print strings in different colors and provides a simple interface.
|
||||
|
||||
[next post]: @/first-edition/posts/04-printing-to-screen/index.md
|
||||
[next post]: @/edition-1/posts/04-printing-to-screen/index.md
|
||||
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
@@ -4,14 +4,14 @@ weight = 4
|
||||
path = "printing-to-screen"
|
||||
aliases = ["printing-to-screen.html", "/2015/10/23/printing-to-screen/", "/rust-os/printing-to-screen.html"]
|
||||
date = 2015-10-23
|
||||
template = "first-edition/page.html"
|
||||
template = "edition-1/page.html"
|
||||
[extra]
|
||||
updated = "2016-10-31"
|
||||
+++
|
||||
|
||||
In the [previous post] we switched from assembly to [Rust], a systems programming language that provides great safety. But so far we are using unsafe features like [raw pointers] whenever we want to print to screen. In this post we will create a Rust module that provides a safe and easy-to-use interface for the VGA text buffer. It will support Rust's [formatting macros], too.
|
||||
|
||||
[previous post]: @/first-edition/posts/03-set-up-rust/index.md
|
||||
[previous post]: @/edition-1/posts/03-set-up-rust/index.md
|
||||
[Rust]: https://www.rust-lang.org/
|
||||
[raw pointers]: https://doc.rust-lang.org/book/raw-pointers.html
|
||||
[formatting macros]: https://doc.rust-lang.org/std/fmt/#related-macros
|
||||
@@ -190,7 +190,7 @@ When printing a byte, the writer checks if the current line is full. In that cas
|
||||
|
||||
The `buffer()` auxiliary method converts the raw pointer in the `buffer` field into a safe mutable buffer reference. The unsafe block is needed because the [as_mut()] method of `Unique` is unsafe. But our `buffer()` method itself isn't marked as unsafe, so it must not introduce any unsafety (e.g. cause segfaults). To guarantee that, it's very important that the `buffer` field always points to a valid `Buffer`. It's like a contract that we must stand to every time we create a `Writer`. To ensure that it's not possible to create an invalid `Writer` from outside of the module, the struct must have at least one private field and public creation functions are not allowed either.
|
||||
|
||||
[as_mut()]: https://doc.rust-lang.org/1.10.0/core/ptr/struct.Unique.html#method.as_mut
|
||||
[as_mut()]: https://doc.rust-lang.org/1.26.0/core/ptr/struct.Unique.html#method.as_mut
|
||||
|
||||
### Cannot Move out of Borrowed Content
|
||||
When we try to compile it, we get the following error:
|
||||
@@ -205,7 +205,7 @@ error[E0507]: cannot move out of borrowed content
|
||||
The reason it that Rust _moves_ values by default instead of copying them like other languages. And we cannot move `color_code` out of `self` because we only borrowed `self`. For more information check out the [ownership section] in the Rust book.
|
||||
|
||||
[ownership section]: https://doc.rust-lang.org/book/ownership.html
|
||||
[by reference]: http://rust-lang.github.io/book/ch04-02-references-and-borrowing.html
|
||||
[by reference]: https://rust-lang.github.io/book/ch04-02-references-and-borrowing.html
|
||||
|
||||
To fix it, we can implement the [Copy] trait for the `ColorCode` type. The easiest way to do this is to use the built-in [derive macro]:
|
||||
|
||||
@@ -257,7 +257,7 @@ It just creates a new Writer that points to the VGA buffer at `0xb8000`. To use
|
||||
|
||||
Then it writes the byte `b'H'` to it. The `b` prefix creates a [byte character], which represents an ASCII code point. When we call `vga_buffer::print_something` in main, a `H` should be printed in the _lower_ left corner of the screen in light green:
|
||||
|
||||
[byte character]: https://doc.rust-lang.org/reference.html#characters-and-strings
|
||||
[byte character]: https://doc.rust-lang.org/reference/tokens.html#characters-and-strings
|
||||
|
||||

|
||||
|
||||
@@ -287,8 +287,8 @@ volatile = "0.1.0"
|
||||
|
||||
The `0.1.0` is the [semantic] version number. For more information, see the [Specifying Dependencies] guide of the cargo documentation.
|
||||
|
||||
[semantic]: http://semver.org/
|
||||
[Specifying Dependencies]: http://doc.crates.io/specifying-dependencies.html
|
||||
[semantic]: https://semver.org/
|
||||
[Specifying Dependencies]: https://doc.crates.io/specifying-dependencies.html
|
||||
|
||||
Now we've declared that our project depends on the `volatile` crate and are able to import it in `src/lib.rs`:
|
||||
|
||||
@@ -354,7 +354,7 @@ You can try it yourself in the `print_something` function.
|
||||
When you print strings with some special characters like `ä` or `λ`, you'll notice that they cause weird symbols on screen. That's because they are represented by multiple bytes in [UTF-8]. By converting them to bytes, we of course get strange results. But since the VGA buffer doesn't support UTF-8, it's not possible to display these characters anyway.
|
||||
|
||||
[core tracking issue]: https://github.com/rust-lang/rust/issues/27701
|
||||
[UTF-8]: http://www.fileformat.info/info/unicode/utf8.htm
|
||||
[UTF-8]: https://www.fileformat.info/info/unicode/utf8.htm
|
||||
|
||||
### Support Formatting Macros
|
||||
It would be nice to support Rust's formatting macros, too. That way, we can easily print different types like integers or floats. To support them, we need to implement the [core::fmt::Write] trait. The only required method of this trait is `write_str` that looks quite similar to our `write_str` method. To implement the trait, we just need to move it into an `impl fmt::Write for Writer` block and add a return type:
|
||||
@@ -441,14 +441,14 @@ But we can't use it to print anything! You can try it yourself in the `print_som
|
||||
|
||||
To resolve it, we could use a [mutable static]. But then every read and write to it would be unsafe since it could easily introduce data races and other bad things. Using `static mut` is highly discouraged, there are even proposals to [remove it][remove static mut].
|
||||
|
||||
[mutable static]: https://doc.rust-lang.org/book/second-edition/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable
|
||||
[mutable static]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable
|
||||
[remove static mut]: https://internals.rust-lang.org/t/pre-rfc-remove-static-mut/1437
|
||||
|
||||
But what are the alternatives? We could try to use a cell type like [RefCell] or even [UnsafeCell] to provide [interior mutability]. But these types aren't [Sync] \(with good reason), so we can't use them in statics.
|
||||
|
||||
[RefCell]: https://doc.rust-lang.org/nightly/core/cell/struct.RefCell.html
|
||||
[UnsafeCell]: https://doc.rust-lang.org/nightly/core/cell/struct.UnsafeCell.html
|
||||
[interior mutability]: https://doc.rust-lang.org/book/mutability.html#interior-vs.-exterior-mutability
|
||||
[interior mutability]: https://doc.rust-lang.org/1.30.0/book/first-edition/mutability.html#interior-vs-exterior-mutability
|
||||
[Sync]: https://doc.rust-lang.org/nightly/core/marker/trait.Sync.html
|
||||
|
||||
To get synchronized interior mutability, users of the standard library can use [Mutex]. It provides mutual exclusion by blocking threads when the resource is already locked. But our basic kernel does not have any blocking support or even a concept of threads, so we can't use it either. However there is a really basic kind of mutex in computer science that requires no operating system features: the [spinlock]. Instead of blocking, the threads simply try to lock it again and again in a tight loop and thus burn CPU time until the mutex is free again.
|
||||
@@ -529,7 +529,7 @@ The macro expands to a call of the [`_print` function] in the `io` module. The [
|
||||
The [`format_args` macro] builds a [fmt::Arguments] type from the passed arguments, which is passed to `_print`. The [`_print` function] of libstd is rather complicated, as it supports different `Stdout` devices. We don't need that complexity since we just want to print to the VGA buffer.
|
||||
|
||||
[`_print` function]: https://github.com/rust-lang/rust/blob/46d39f3329487115e7d7dcd37bc64eea6ef9ba4e/src/libstd/io/stdio.rs#L631
|
||||
[`$crate` variable]: https://doc.rust-lang.org/book/macros.html#the-variable-crate
|
||||
[`$crate` variable]: https://doc.rust-lang.org/1.30.0/book/first-edition/macros.html#the-variable-crate
|
||||
[`format_args` macro]: https://doc.rust-lang.org/nightly/std/macro.format_args.html
|
||||
[fmt::Arguments]: https://doc.rust-lang.org/nightly/core/fmt/struct.Arguments.html
|
||||
|
||||
@@ -642,7 +642,7 @@ In the next posts we will map the kernel pages correctly so that accessing `0x0`
|
||||
|
||||
The [next post] describes the Multiboot information structure and creates a frame allocator using the information about memory areas.
|
||||
|
||||
[next post]: @/first-edition/posts/05-allocating-frames/index.md
|
||||
[next post]: @/edition-1/posts/05-allocating-frames/index.md
|
||||
|
||||
## Other Rust OS Projects
|
||||
Now that you know the very basics of OS development in Rust, you should also check out the following projects:
|
||||
@@ -657,8 +657,8 @@ _Note_: You need to [cross compile binutils] to build it (or you create some sym
|
||||
- [Redox]: Probably the most complete Rust OS today. It has an active community and over 1000 Github stars. File systems, network, an audio player, a picture viewer, and much more. Just take a look at the [screenshots][redox screenshots].
|
||||
|
||||
[Rust Bare-Bones Kernel]: https://github.com/thepowersgang/rust-barebones-kernel
|
||||
[higher half]: http://wiki.osdev.org/Higher_Half_Kernel
|
||||
[cross compile binutils]: @/first-edition/extra/cross-compile-binutils.md
|
||||
[higher half]: https://wiki.osdev.org/Higher_Half_Kernel
|
||||
[cross compile binutils]: @/edition-1/extra/cross-compile-binutils.md
|
||||
[RustOS]: https://github.com/RustOS-Fork-Holding-Ground/RustOS
|
||||
["Tifflin" Experimental Kernel]:https://github.com/thepowersgang/rust_os
|
||||
[Redox]: https://github.com/redox-os/redox
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
@@ -4,7 +4,7 @@ weight = 5
|
||||
path = "allocating-frames"
|
||||
aliases = ["allocating-frames.html"]
|
||||
date = 2015-11-15
|
||||
template = "first-edition/page.html"
|
||||
template = "edition-1/page.html"
|
||||
+++
|
||||
|
||||
In this post we create an allocator that provides free physical frames for a future paging module. To get the required information about available and used memory we use the Multiboot information structure. Additionally, we improve the `panic` handler to print the corresponding message and source line.
|
||||
@@ -71,7 +71,7 @@ Now we can use it to print available memory areas.
|
||||
### Available Memory
|
||||
The boot information structure consists of various _tags_. See section 3.4 of the Multiboot specification ([PDF][multiboot specification]) for a complete list. The _memory map_ tag contains a list of all available RAM areas. Special areas such as the VGA text buffer at `0xb8000` are not available. Note that some of the available memory is already used by our kernel and by the multiboot information structure itself.
|
||||
|
||||
[multiboot specification]: http://nongnu.askapache.com/grub/phcoder/multiboot.pdf
|
||||
[multiboot specification]: https://nongnu.askapache.com/grub/phcoder/multiboot.pdf
|
||||
|
||||
To print all available memory areas, we can use the `multiboot2` crate in our `rust_main` as follows:
|
||||
|
||||
@@ -100,7 +100,7 @@ So we have one area from `0x0` to `0x9fc00`, which is a bit below the 1MiB mark.
|
||||
|
||||
If you give QEMU more than 4GiB of memory by passing `-m 5G`, you get another unusable area below the 4GiB mark. This memory is normally mapped to some hardware devices. See the [OSDev Wiki][Memory_map] for more information.
|
||||
|
||||
[Memory_map]: http://wiki.osdev.org/Memory_Map_(x86)
|
||||
[Memory_map]: https://wiki.osdev.org/Memory_Map_(x86)
|
||||
|
||||
### Handling Panics
|
||||
We used `expect` in the code above, which will panic if there is no memory map tag. But our current panic handler just loops without printing any error message. Of course we could replace `expect` by a `match`, but we should fix the panic handler nonetheless:
|
||||
@@ -217,7 +217,7 @@ We could create some kind of linked list from the free frames. For example, each
|
||||
|
||||
Another approach is to create some kind of data structure such as a [bitmap or a stack] to manage free frames. We could place it in the already identity mapped area right behind the kernel or multiboot structure. That way we would not need to (temporary) map each free frame. But it has the same problem of the slow initial creating/filling. In fact, we will use this approach in a future post to manage frames that are freed again. But for the initial management of free frames, we use a different method.
|
||||
|
||||
[bitmap or a stack]: http://wiki.osdev.org/Page_Frame_Allocation#Physical_Memory_Allocators
|
||||
[bitmap or a stack]: https://wiki.osdev.org/Page_Frame_Allocation#Physical_Memory_Allocators
|
||||
|
||||
In the following, we will use Multiboot's memory map directly. The idea is to maintain a simple counter that starts at frame 0 and is increased constantly. If the current frame is available (part of an available area in the memory map) and not used by the kernel or the multiboot structure (we know their start and end addresses), we know that it's free and return it. Else, we increase the counter to the next possibly free frame. That way, we don't need to create a data structure when booting and the physical frames can remain unmapped. The only problem is that we cannot reasonably free frames again, but we will solve that problem in a future post (by adding an intermediate frame stack that saves freed frames).
|
||||
|
||||
@@ -394,7 +394,7 @@ Note that we call `choose_next_area` manually here because `allocate_frame` retu
|
||||
### Testing it
|
||||
In order to test it in main, we need to [re-export] the `AreaFrameAllocator` in the `memory` module. Then we can create a new allocator:
|
||||
|
||||
[re-export]: https://doc.rust-lang.org/book/crates-and-modules.html#re-exporting-with-pub-use
|
||||
[re-export]: https://doc.rust-lang.org/1.30.0/book/first-edition/crates-and-modules.html#re-exporting-with-pub-use
|
||||
|
||||
```rust
|
||||
let mut frame_allocator = memory::AreaFrameAllocator::new(
|
||||
@@ -421,7 +421,7 @@ for i in 0.. {
|
||||
```
|
||||
You can try different amounts of memory by passing e.g. `-m 500M` to QEMU. To compare these numbers, [WolframAlpha] can be very helpful.
|
||||
|
||||
[WolframAlpha]: http://www.wolframalpha.com/input/?i=%2832605+*+4096%29+bytes+in+MiB
|
||||
[WolframAlpha]: https://www.wolframalpha.com/input/?i=%2832605+*+4096%29+bytes+in+MiB
|
||||
|
||||
## Conclusion
|
||||
|
||||
@@ -430,10 +430,10 @@ Now we have a working frame allocator. It is a bit rudimentary and cannot free f
|
||||
## What's next?
|
||||
The [next post] will be about paging again. We will use the frame allocator to create a safe module that allows us to switch page tables and map pages. Then we will use this module and the information from the Elf-sections tag to remap the kernel correctly.
|
||||
|
||||
[next post]: @/first-edition/posts/06-page-tables/index.md
|
||||
[next post]: @/edition-1/posts/06-page-tables/index.md
|
||||
|
||||
## Recommended Posts
|
||||
Eric Kidd started the [Bare Metal Rust] series last week. Like this post, it builds upon the code from [Printing to Screen], but tries to support keyboard input instead of wrestling through memory management details.
|
||||
|
||||
[Bare Metal Rust]: http://www.randomhacks.net/bare-metal-rust/
|
||||
[Printing to Screen]: @/first-edition/posts/04-printing-to-screen/index.md
|
||||
[Printing to Screen]: @/edition-1/posts/04-printing-to-screen/index.md
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
@@ -4,7 +4,7 @@ weight = 6
|
||||
path = "page-tables"
|
||||
aliases = ["page-tables.html", "modifying-page-tables.html"]
|
||||
date = 2015-12-09
|
||||
template = "first-edition/page.html"
|
||||
template = "edition-1/page.html"
|
||||
+++
|
||||
|
||||
In this post we will create a paging module, which allows us to access and modify the 4-level page table. We will explore recursive page table mapping and use some Rust features to make it safe. Finally we will create functions to translate virtual addresses and to map and unmap pages.
|
||||
@@ -52,7 +52,7 @@ pub struct Page {
|
||||
```
|
||||
We import the `PAGE_SIZE` and define a constant for the number of entries per table. To make future function signatures more expressive, we can use the type aliases `PhysicalAddress` and `VirtualAddress`. The `Page` struct is similar to the `Frame` struct in the [previous post], but represents a virtual page instead of a physical frame.
|
||||
|
||||
[previous post]: @/first-edition/posts/05-allocating-frames/index.md#a-memory-module
|
||||
[previous post]: @/edition-1/posts/05-allocating-frames/index.md#a-memory-module
|
||||
|
||||
### Page Table Entries
|
||||
To model page table entries, we create a new `entry` submodule:
|
||||
@@ -323,7 +323,7 @@ We convert the address into raw pointers through `as` casts and then convert the
|
||||
|
||||
Note that `self` stays borrowed as long as the returned reference is valid. This is because of Rust's [lifetime elision] rules. Basically, these rules say that the lifetime of an output reference is the same as the lifetime of the input reference by default. So the above function signatures are expanded to:
|
||||
|
||||
[lifetime elision]: https://doc.rust-lang.org/book/lifetimes.html#lifetime-elision
|
||||
[lifetime elision]: https://doc.rust-lang.org/1.30.0/book/first-edition/lifetimes.html#lifetime-elision
|
||||
|
||||
```rust
|
||||
pub fn next_table<'a>(&'a self, index: usize) -> Option<&'a Table> {...}
|
||||
@@ -652,7 +652,7 @@ pub struct ActivePageTable {
|
||||
```
|
||||
We can't store the `Table<Level4>` directly because it needs to be at a special memory location (like the [VGA text buffer]). We could use a raw pointer or `&mut` instead of [Unique], but Unique indicates ownership better.
|
||||
|
||||
[VGA text buffer]: @/first-edition/posts/04-printing-to-screen/index.md#the-text-buffer
|
||||
[VGA text buffer]: @/edition-1/posts/04-printing-to-screen/index.md#the-text-buffer
|
||||
[Unique]: https://doc.rust-lang.org/1.10.0/core/ptr/struct.Unique.html
|
||||
|
||||
Because the `ActivePageTable` owns the unique recursive mapped P4 table, there must be only one `ActivePageTable` instance. Thus we make the constructor function unsafe:
|
||||
@@ -881,7 +881,7 @@ This post has become pretty long. So let's summarize what we've done:
|
||||
## What's next?
|
||||
In the [next post] we will extend this module and add a function to modify inactive page tables. Through that function, we will create a new page table hierarchy that maps the kernel correctly using 4KiB pages. Then we will switch to the new table to get a safer kernel environment.
|
||||
|
||||
[next post]: @/first-edition/posts/07-remap-the-kernel/index.md
|
||||
[next post]: @/edition-1/posts/07-remap-the-kernel/index.md
|
||||
|
||||
Afterwards, we will use this paging module to build a heap allocator. This will allow us to use allocation and collection types such as `Box` and `Vec`.
|
||||
|
||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
@@ -4,7 +4,7 @@ weight = 7
|
||||
path = "remap-the-kernel"
|
||||
aliases = ["remap-the-kernel.html"]
|
||||
date = 2016-01-01
|
||||
template = "first-edition/page.html"
|
||||
template = "edition-1/page.html"
|
||||
[extra]
|
||||
updated = "2016-03-06"
|
||||
+++
|
||||
@@ -21,18 +21,18 @@ As always, you can find the source code on [GitHub]. Don't hesitate to file issu
|
||||
|
||||
In the [previous post], we had a strange bug in the `unmap` function. Its reason was a silent stack overflow, which corrupted the page tables. Fortunately, our kernel stack is right above the page tables so that we noticed the overflow relatively quickly. This won't be the case when we add threads with new stacks in the future. Then a silent stack overflow could overwrite some data without us noticing. But eventually some completely unrelated function fails because a variable changed its value.
|
||||
|
||||
[previous post]: @/first-edition/posts/06-page-tables/index.md
|
||||
[previous post]: @/edition-1/posts/06-page-tables/index.md
|
||||
|
||||
As you can imagine, these kinds of bugs are horrendous to debug. For that reason we will create a new hierarchical page table in this post, which has _guard page_ below the stack. A guard page is basically an unmapped page that causes a page fault when accessed. Thus we can catch stack overflows right when they happen.
|
||||
|
||||
Also, we will use the [information about kernel sections] to map the various sections individually instead of blindly mapping the first gigabyte. To improve safety even further, we will set the correct page table flags for the various sections. Thus it won't be possible to modify the contents of `.text` or to execute code from `.data` anymore.
|
||||
|
||||
[information about kernel sections]: @/first-edition/posts/05-allocating-frames/index.md#kernel-elf-sections
|
||||
[information about kernel sections]: @/edition-1/posts/05-allocating-frames/index.md#kernel-elf-sections
|
||||
|
||||
## Preparation
|
||||
There are many things that can go wrong when we switch to a new table. Therefore it's a good idea to [set up a debugger][set up gdb]. You should not need it when you follow this post, but it's good to know how to debug a problem when it occurs[^fn-debug-notes].
|
||||
|
||||
[set up gdb]: @/first-edition/extra/set-up-gdb/index.md
|
||||
[set up gdb]: @/edition-1/extra/set-up-gdb/index.md
|
||||
|
||||
We also update the `Page` and `Frame` types to make our lives easier. The `Page` struct gets some derived traits:
|
||||
|
||||
@@ -281,7 +281,7 @@ pub fn map_table_frame(&mut self,
|
||||
```
|
||||
This function interprets the given frame as a page table frame and returns a `Table` reference. We return a table of level 1 because it [forbids calling the `next_table` methods][some clever solution]. Calling `next_table` must not be possible since it's not a page of the recursive mapping. To be able to return a `Table<Level1>`, we need to make the `Level1` enum in `memory/paging/table.rs` public.
|
||||
|
||||
[some clever solution]: @/first-edition/posts/06-page-tables/index.md#some-clever-solution
|
||||
[some clever solution]: @/edition-1/posts/06-page-tables/index.md#some-clever-solution
|
||||
|
||||
|
||||
The `unsafe` block is safe since the `VirtualAddress` returned by the `map` function is always valid and the type cast just reinterprets the frame's content.
|
||||
@@ -368,7 +368,7 @@ pub fn with<F>(&mut self,
|
||||
```
|
||||
It overwrites the 511th P4 entry and points it to the inactive table frame. Then it flushes the [translation lookaside buffer (TLB)][TLB], which still contains some old translations. We need to flush all pages that are part of the recursive mapping, so the easiest way is to flush the TLB completely.
|
||||
|
||||
[TLB]: http://wiki.osdev.org/TLB
|
||||
[TLB]: https://wiki.osdev.org/TLB
|
||||
|
||||
|
||||
Now that the recursive mapping points to the given inactive table, we execute the closure in the new context. The closure can call all active table methods such as `translate` or `map_to`. It could even call `with` again and chain another inactive table! Wait… that would not work:
|
||||
@@ -466,7 +466,7 @@ let backup = Frame::containing_address(
|
||||
```
|
||||
Why is it unsafe? Because reading the CR3 register leads to a CPU exception if the processor is not running in kernel mode ([Ring 0]). But this code will always run in kernel mode, so the `unsafe` block is completely safe here.
|
||||
|
||||
[Ring 0]: http://wiki.osdev.org/Security#Low-level_Protection_Mechanisms
|
||||
[Ring 0]: https://wiki.osdev.org/Security#Low-level_Protection_Mechanisms
|
||||
|
||||
Now that we have a backup of the original P4 frame, we need a way to restore it after the closure has run. So we need to somehow modify the 511th entry of the original P4 frame, which is still the active table in the CPU. But we can't access it because the recursive mapping now points to the inactive table:
|
||||
|
||||
@@ -554,7 +554,7 @@ First, we create a temporary page at page number `0xcafebabe`. We could use `0xd
|
||||
|
||||
Then we use the `with` function to temporary change the recursive mapping and execute the closure as if the `new_table` were active. This allows us to map the sections in the new table without changing the active mapping. To get the kernel sections, we use the [Multiboot information structure].
|
||||
|
||||
[Multiboot information structure]: @/first-edition/posts/05-allocating-frames/index.md#the-multiboot-information-structure
|
||||
[Multiboot information structure]: @/edition-1/posts/05-allocating-frames/index.md#the-multiboot-information-structure
|
||||
|
||||
Let's resolve the above `TODO` by identity mapping the sections:
|
||||
|
||||
@@ -641,7 +641,7 @@ SECTIONS {
|
||||
```
|
||||
The `.` is the “current location counter” and represents the current virtual address. At the beginning of the `SECTIONS` tag we set it to `1M`, so our kernel starts at 1MiB. We use the [ALIGN][linker align] function to align the current location counter to the next `4K` boundary (`4K` is the page size). Thus the end of the `.text` section – and the beginning of the next section – are page aligned.
|
||||
|
||||
[linker align]: http://www.math.utah.edu/docs/info/ld_3.html#SEC12
|
||||
[linker align]: https://www.math.utah.edu/docs/info/ld_3.html#SEC12
|
||||
|
||||
To put all sections on their own page, we add the `ALIGN` statement to all of them:
|
||||
|
||||
@@ -826,11 +826,11 @@ These lines are the important ones. We can read many useful information from the
|
||||
|
||||
- `CR2=00000000000b8f00`: Finally the most useful register. It tells us which virtual address caused the page fault. In our case it's `0xb8f00`, which is part of the [VGA text buffer].
|
||||
|
||||
[osdev exception overview]: http://wiki.osdev.org/Exceptions
|
||||
[page fault]: http://wiki.osdev.org/Exceptions#Page_Fault
|
||||
[page fault error code]: http://wiki.osdev.org/Exceptions#Error_code
|
||||
[GDT segment]: @/first-edition/posts/02-entering-longmode/index.md#loading-the-gdt
|
||||
[VGA text buffer]: @/first-edition/posts/04-printing-to-screen/index.md#the-vga-text-buffer
|
||||
[osdev exception overview]: https://wiki.osdev.org/Exceptions
|
||||
[page fault]: https://wiki.osdev.org/Exceptions#Page_Fault
|
||||
[page fault error code]: https://wiki.osdev.org/Exceptions#Error_code
|
||||
[GDT segment]: @/edition-1/posts/02-entering-longmode/index.md#loading-the-gdt
|
||||
[VGA text buffer]: @/edition-1/posts/04-printing-to-screen/index.md#the-vga-text-buffer
|
||||
|
||||
So let's find out which function caused the exception:
|
||||
|
||||
@@ -882,7 +882,7 @@ Now we should see the `NEW TABLE!!!` message (and also the `It did not crash!` l
|
||||
### Fixing the Frame Allocator
|
||||
The same problem as above occurs when we try to use our [AreaFrameAllocator] again. Try to add the following to `rust_main` after switching to the new table:
|
||||
|
||||
[AreaFrameAllocator]: http://os.phil-opp.com/allocating-frames.html#the-allocator
|
||||
[AreaFrameAllocator]: @/edition-1/posts/05-allocating-frames/index.md#the-allocator
|
||||
|
||||
```rust
|
||||
// in src/lib.rs
|
||||
@@ -1028,7 +1028,7 @@ The final step is to create a guard page for our kernel stack.
|
||||
|
||||
The decision to place the kernel stack right above the page tables was already useful to detect a silent stack overflow in the [previous post][silent stack overflow]. Now we profit from it again. Let's look at our assembly `.bss` section again to understand why:
|
||||
|
||||
[silent stack overflow]: @/first-edition/posts/06-page-tables/index.md
|
||||
[silent stack overflow]: @/edition-1/posts/06-page-tables/index.md
|
||||
|
||||
```nasm
|
||||
; in src/arch/x86_64/boot.asm
|
||||
@@ -1089,7 +1089,7 @@ Unfortunately stack probes require compiler support. They already work on Window
|
||||
## What's next?
|
||||
Now that we have a (mostly) safe kernel stack and a working page table module, we can add a virtual memory allocator. The [next post] will explore Rust's allocator API and create a very basic allocator. At the end of that post, we will be able to use Rust's allocation and collections types such as [Box], [Vec], or even [BTreeMap].
|
||||
|
||||
[next post]: @/first-edition/posts/08-kernel-heap/index.md
|
||||
[next post]: @/edition-1/posts/08-kernel-heap/index.md
|
||||
[Box]: https://doc.rust-lang.org/nightly/alloc/boxed/struct.Box.html
|
||||
[Vec]: https://doc.rust-lang.org/1.10.0/collections/vec/struct.Vec.html
|
||||
[BTreeMap]: https://doc.rust-lang.org/1.10.0/collections/btree_map/struct.BTreeMap.html
|
||||
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
@@ -4,14 +4,15 @@ weight = 8
|
||||
path = "kernel-heap"
|
||||
aliases = ["kernel-heap.html"]
|
||||
date = 2016-04-11
|
||||
template = "edition-1/page.html"
|
||||
[extra]
|
||||
updated = "2017-11-19"
|
||||
template = "first-edition/page.html"
|
||||
+++
|
||||
|
||||
In the previous posts we created a [frame allocator] and a [page table module]. Now we are ready to create a kernel heap and a memory allocator. Thus, we will unlock `Box`, `Vec`, `BTreeMap`, and the rest of the [alloc] crate.
|
||||
|
||||
[frame allocator]: @/first-edition/posts/05-allocating-frames/index.md
|
||||
[page table module]: @/first-edition/posts/06-page-tables/index.md
|
||||
[frame allocator]: @/edition-1/posts/05-allocating-frames/index.md
|
||||
[page table module]: @/edition-1/posts/06-page-tables/index.md
|
||||
[alloc]: https://doc.rust-lang.org/nightly/alloc/index.html
|
||||
|
||||
<!-- more -->
|
||||
@@ -32,9 +33,9 @@ The _heap_ is the memory area for long-lived allocations. The programmer can acc
|
||||
|
||||
A good allocator is fast and reliable. It also effectively utilizes the available memory and keeps [fragmentation] low. Furthermore, it works well for concurrent applications and scales to any number of processors. It even optimizes the memory layout with respect to the CPU caches to improve [cache locality] and avoid [false sharing].
|
||||
|
||||
[cache locality]: http://docs.cray.com/books/S-2315-50/html-S-2315-50/qmeblljm.html
|
||||
[cache locality]: https://www.geeksforgeeks.org/locality-of-reference-and-cache-operation-in-cache-memory/
|
||||
[fragmentation]: https://en.wikipedia.org/wiki/Fragmentation_(computing)
|
||||
[false sharing]: http://mechanical-sympathy.blogspot.de/2011/07/false-sharing.html
|
||||
[false sharing]: https://mechanical-sympathy.blogspot.de/2011/07/false-sharing.html
|
||||
|
||||
These requirements make good allocators pretty complex. For example, [jemalloc] has over 30.000 lines of code. This complexity is out of scope for our kernel, so we will create a much simpler allocator. Nevertheless, it should suffice for the foreseeable future, since we'll allocate only when it's absolutely necessary.
|
||||
|
||||
@@ -332,7 +333,7 @@ let heap_test = Box::new(42);
|
||||
|
||||
When we run it, a triple fault occurs and causes permanent rebooting. Let's try debug it using QEMU and objdump as described [in the previous post][qemu debugging]:
|
||||
|
||||
[qemu debugging]: http://os.phil-opp.com/remap-the-kernel.html#debugging
|
||||
[qemu debugging]: @/edition-1/posts/07-remap-the-kernel/index.md#debugging
|
||||
|
||||
```
|
||||
> qemu-system-x86_64 -d int -no-reboot -cdrom build/os-x86_64.iso
|
||||
@@ -344,7 +345,7 @@ check_exception old: 0xffffffff new 0xe
|
||||
```
|
||||
Aha! It's a [page fault] \(`v=0e`) and was caused by the code at `0x102860`. The code tried to write (`e=0002`) to address `0x40000000`. This address is `0o_000_001_000_000_0000` in octal, which is the `HEAP_START` address defined above. Of course it page-faults: We have forgotten to map the heap memory to some physical memory.
|
||||
|
||||
[page fault]: http://wiki.osdev.org/Exceptions#Page_Fault
|
||||
[page fault]: https://wiki.osdev.org/Exceptions#Page_Fault
|
||||
|
||||
### Some Refactoring
|
||||
In order to map the heap cleanly, we do a bit of refactoring first. We move all memory initialization from our `rust_main` to a new `memory::init` function. Now our `rust_main` looks like this:
|
||||
@@ -414,7 +415,7 @@ pub fn init(boot_info: &BootInformation) {
|
||||
We've just moved the code to a new function. However, we've sneaked some improvements in:
|
||||
|
||||
- An additional `.filter(|s| s.is_allocated())` in the calculation of `kernel_start` and `kernel_end`. This ignores all sections that aren't loaded to memory (such as debug sections). Thus, the kernel end address is no longer artificially increased by such sections.
|
||||
- We use the `start_address()` and `end_address()` methods of `boot_info` instead of calculating the adresses manually.
|
||||
- We use the `start_address()` and `end_address()` methods of `boot_info` instead of calculating the addresses manually.
|
||||
- We use the alternate `{:#x}` form when printing kernel/multiboot addresses. Before, we used `0x{:x}`, which leads to the same result. For a complete list of these “alternate” formatting forms, check out the [std::fmt documentation].
|
||||
|
||||
[std::fmt documentation]: https://doc.rust-lang.org/nightly/std/fmt/index.html#sign0
|
||||
@@ -460,8 +461,8 @@ That's it. Now our `memory::init` function can only be called once. The macro wo
|
||||
### Mapping the Heap
|
||||
Now we're ready to map the heap pages. In order to do it, we need access to the `ActivePageTable` or `Mapper` instance (see the [page table] and [kernel remapping] posts). For that we return it from the `paging::remap_the_kernel` function:
|
||||
|
||||
[page table]: @/first-edition/posts/06-page-tables/index.md
|
||||
[kernel remapping]: @/first-edition/posts/07-remap-the-kernel/index.md
|
||||
[page table]: @/edition-1/posts/06-page-tables/index.md
|
||||
[kernel remapping]: @/edition-1/posts/07-remap-the-kernel/index.md
|
||||
|
||||
```rust
|
||||
// in src/memory/paging/mod.rs
|
||||
@@ -674,7 +675,7 @@ I created the [linked_list_allocator] crate to handle all of these cases. It con
|
||||
|
||||
We need to add the extern crate to our `Cargo.toml` and our `lib.rs`:
|
||||
|
||||
``` shell
|
||||
``` bash
|
||||
> cargo add linked_list_allocator
|
||||
```
|
||||
|
||||
@@ -736,4 +737,4 @@ Now we're able to use heap storage in our kernel without leaking memory. This al
|
||||
## What's next?
|
||||
This post concludes the section about memory management for now. We will revisit this topic eventually, but now it's time to explore other topics. The upcoming posts will be about CPU exceptions and interrupts. We will catch all page, double, and triple faults and create a driver to read keyboard input. The [next post] starts by setting up a so-called _Interrupt Descriptor Table_.
|
||||
|
||||
[next post]: @/first-edition/posts/09-handling-exceptions/index.md
|
||||
[next post]: @/edition-1/posts/09-handling-exceptions/index.md
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
@@ -4,12 +4,12 @@ weight = 9
|
||||
path = "handling-exceptions"
|
||||
aliases = ["handling-exceptions.html"]
|
||||
date = 2017-03-26
|
||||
template = "first-edition/page.html"
|
||||
template = "edition-1/page.html"
|
||||
+++
|
||||
|
||||
In this post, we start exploring CPU exceptions. Exceptions occur in various erroneous situations, for example when accessing an invalid memory address or when dividing by zero. To catch them, we have to set up an _interrupt descriptor table_ that provides handler functions. At the end of this post, our kernel will be able to catch [breakpoint exceptions] and to resume normal execution afterwards.
|
||||
|
||||
[breakpoint exceptions]: http://wiki.osdev.org/Exceptions#Breakpoint
|
||||
[breakpoint exceptions]: https://wiki.osdev.org/Exceptions#Breakpoint
|
||||
|
||||
<!-- more -->
|
||||
|
||||
@@ -30,7 +30,7 @@ We've already seen several types of exceptions in our kernel:
|
||||
|
||||
For the full list of exceptions check out the [OSDev wiki][exceptions].
|
||||
|
||||
[exceptions]: http://wiki.osdev.org/Exceptions
|
||||
[exceptions]: https://wiki.osdev.org/Exceptions
|
||||
|
||||
### The Interrupt Descriptor Table
|
||||
In order to catch and handle exceptions, we have to set up a so-called _Interrupt Descriptor Table_ (IDT). In this table we can specify a handler function for each CPU exception. The hardware uses this table directly, so we need to follow a predefined format. Each entry must have the following 16-byte structure:
|
||||
@@ -118,7 +118,7 @@ type HandlerFunc = extern "x86-interrupt" fn(_: &mut ExceptionStackFrame);
|
||||
It's a [type alias] for an `extern "x86-interrupt" fn` type. The `extern` keyword defines a function with a [foreign calling convention] and is often used to communicate with C code (`extern "C" fn`). But what is the `x86-interrupt` calling convention?
|
||||
|
||||
[type alias]: https://doc.rust-lang.org/book/type-aliases.html
|
||||
[foreign calling convention]: https://doc.rust-lang.org/book/ffi.html#foreign-calling-conventions
|
||||
[foreign calling convention]: https://doc.rust-lang.org/1.30.0/book/first-edition/ffi.html#foreign-calling-conventions
|
||||
|
||||
## The Interrupt Calling Convention
|
||||
Exceptions are quite similar to function calls: The CPU jumps to the first instruction of the called function and executes it. Afterwards, if the function is not diverging, the CPU jumps to the return address and continues the execution of the parent function.
|
||||
@@ -128,7 +128,7 @@ However, there is a major difference between exceptions and function calls: A fu
|
||||
[Calling conventions] specify the details of a function call. For example, they specify where function parameters are placed (e.g. in registers or on the stack) and how results are returned. On x86_64 Linux, the following rules apply for C functions (specified in the [System V ABI]):
|
||||
|
||||
[Calling conventions]: https://en.wikipedia.org/wiki/Calling_convention
|
||||
[System V ABI]: http://refspecs.linuxbase.org/elf/gabi41.pdf
|
||||
[System V ABI]: https://refspecs.linuxbase.org/elf/gabi41.pdf
|
||||
|
||||
- the first six integer arguments are passed in registers `rdi`, `rsi`, `rdx`, `rcx`, `r8`, `r9`
|
||||
- additional arguments are passed on the stack
|
||||
@@ -221,15 +221,15 @@ pub fn init() {
|
||||
|
||||
Now we can add handler functions. We start by adding a handler for the [breakpoint exception]. The breakpoint exception is the perfect exception to test exception handling. Its only purpose is to temporary pause a program when the breakpoint instruction `int3` is executed.
|
||||
|
||||
[breakpoint exception]: http://wiki.osdev.org/Exceptions#Breakpoint
|
||||
[breakpoint exception]: https://wiki.osdev.org/Exceptions#Breakpoint
|
||||
|
||||
The breakpoint exception is commonly used in debuggers: When the user sets a breakpoint, the debugger overwrites the corresponding instruction with the `int3` instruction so that the CPU throws the breakpoint exception when it reaches that line. When the user wants to continue the program, the debugger replaces the `int3` instruction with the original instruction again and continues the program. For more details, see the ["_How debuggers work_"] series.
|
||||
|
||||
["_How debuggers work_"]: http://eli.thegreenplace.net/2011/01/27/how-debuggers-work-part-2-breakpoints
|
||||
["_How debuggers work_"]: https://eli.thegreenplace.net/2011/01/27/how-debuggers-work-part-2-breakpoints
|
||||
|
||||
For our use case, we don't need to overwrite any instructions (it wouldn't even be possible since we [set the page table flags] to read-only). Instead, we just want to print a message when the breakpoint instruction is executed and then continue the program.
|
||||
|
||||
[set the page table flags]: @/first-edition/posts/07-remap-the-kernel/index.md#using-the-correct-flags
|
||||
[set the page table flags]: @/edition-1/posts/07-remap-the-kernel/index.md#using-the-correct-flags
|
||||
|
||||
So let's create a simple `breakpoint_handler` function and add it to our IDT:
|
||||
|
||||
@@ -317,7 +317,7 @@ pub fn init() {
|
||||
There are two problems with this. First, statics are immutable, so we can't modify the breakpoint entry from our `init` function. Second, the `Idt::new` function is not a [`const` function], so it can't be used to initialize a `static`. We could solve this problem by using a [`static mut`] of type `Option<Idt>`:
|
||||
|
||||
[`const` function]: https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md
|
||||
[`static mut`]: https://doc.rust-lang.org/book/second-edition/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable
|
||||
[`static mut`]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable
|
||||
|
||||
```rust
|
||||
static mut IDT: Option<Idt> = None;
|
||||
@@ -334,7 +334,7 @@ pub fn init() {
|
||||
|
||||
This variant compiles without errors but it's far from idiomatic. `static mut`s are very prone to data races, so we need an [`unsafe` block] on each access. Also, we need to explicitly `unwrap` the `IDT` on each use, since might be `None`.
|
||||
|
||||
[`unsafe` block]: https://doc.rust-lang.org/book/second-edition/ch19-01-unsafe-rust.html#unsafe-superpowers
|
||||
[`unsafe` block]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#unsafe-superpowers
|
||||
|
||||
#### Lazy Statics to the Rescue
|
||||
The one-time initialization of statics with non-const functions is a common problem in Rust. Fortunately, there already exists a good solution in a crate named [lazy_static]. This crate provides a `lazy_static!` macro that defines a lazily initialized `static`. Instead of computing its value at compile time, the `static` laziliy initializes itself when it's accessed the first time. Thus, the initialization happens at runtime so that arbitrarily complex initialization code is possible.
|
||||
@@ -379,7 +379,7 @@ Note how this solution requires no `unsafe` blocks or `unwrap` calls.
|
||||
|
||||
> ##### Aside: How does the `lazy_static!` macro work?
|
||||
>
|
||||
> The macro generates a `static` of type `Once<Idt>`. The [`Once`][spin::Once] type is provided by the `spin` crate and allows deferred one-time initialization. It is implemented using an [`AtomicUsize`] for synchronization and an [`UnsafeCell`] for storing the (possibly unitialized) value. So this solution also uses `unsafe` behind the scenes, but it is abstracted away in a safe interface.
|
||||
> The macro generates a `static` of type `Once<Idt>`. The [`Once`][spin::Once] type is provided by the `spin` crate and allows deferred one-time initialization. It is implemented using an [`AtomicUsize`] for synchronization and an [`UnsafeCell`] for storing the (possibly uninitialized) value. So this solution also uses `unsafe` behind the scenes, but it is abstracted away in a safe interface.
|
||||
|
||||
[spin::Once]: https://docs.rs/spin/0.4.5/spin/struct.Once.html
|
||||
[`AtomicUsize`]: https://doc.rust-lang.org/nightly/core/sync/atomic/struct.AtomicUsize.html
|
||||
@@ -439,16 +439,16 @@ The answer is that the stored instruction pointer only points to the causing ins
|
||||
- **Aborts** are fatal exceptions that can't be recovered. Examples are [machine check exception] or the [double fault].
|
||||
- **Traps** are only reported to the kernel, but don't hinder the continuation of the program. Examples are the breakpoint exception and the [overflow exception].
|
||||
|
||||
[page fault]: http://wiki.osdev.org/Exceptions#Page_Fault
|
||||
[machine check exception]: http://wiki.osdev.org/Exceptions#Machine_Check
|
||||
[double fault]: http://wiki.osdev.org/Exceptions#Double_Fault
|
||||
[overflow exception]: http://wiki.osdev.org/Exceptions#Overflow
|
||||
[page fault]: https://wiki.osdev.org/Exceptions#Page_Fault
|
||||
[machine check exception]: https://wiki.osdev.org/Exceptions#Machine_Check
|
||||
[double fault]: https://wiki.osdev.org/Exceptions#Double_Fault
|
||||
[overflow exception]: https://wiki.osdev.org/Exceptions#Overflow
|
||||
|
||||
The reason for the diffent instruction pointer values is that the stored value is also the return address. So for faults, the instruction that caused the exception is restarted and might cause the same exception again if it's not resolved. This would not make much sense for traps, since invoking the breakpoint exception again would just cause another breakpoint exception[^fn-breakpoint-restart-use-cases]. Thus the instruction pointer points to the _next_ instruction for these exceptions.
|
||||
|
||||
In some cases, the distinction between faults and traps is vague. For example, the [debug exception] behaves like a fault in some cases, but like a trap in others. So to find out the meaning of the saved instruction pointer, it is a good idea to read the official documentation for the exception, which can be found in the [AMD64 manual] in Section 8.2. For example, for the breakpoint exception it says:
|
||||
|
||||
[debug exception]: http://wiki.osdev.org/Exceptions#Debug
|
||||
[debug exception]: https://wiki.osdev.org/Exceptions#Debug
|
||||
[AMD64 manual]: https://www.amd.com/system/files/TechDocs/24593.pdf
|
||||
|
||||
> `#BP` is a trap-type exception. The saved instruction pointer points to the byte after the `INT3` instruction.
|
||||
@@ -456,18 +456,18 @@ In some cases, the distinction between faults and traps is vague. For example, t
|
||||
The documentation of the [`Idt`] struct and the [OSDev Wiki][osdev wiki exceptions] also contain this information.
|
||||
|
||||
[`Idt`]: https://docs.rs/x86_64/0.1.1/x86_64/structures/idt/struct.Idt.html
|
||||
[osdev wiki exceptions]: http://wiki.osdev.org/Exceptions
|
||||
[osdev wiki exceptions]: https://wiki.osdev.org/Exceptions
|
||||
|
||||
## Too much Magic?
|
||||
The `x86-interrupt` calling convention and the [`Idt`] type made the exception handling process relatively straightforward and painless. If this was too much magic for you and you like to learn all the gory details of exception handling, we got you covered: Our [“Handling Exceptions with Naked Functions”] series shows how to handle exceptions without the `x86-interrupt` calling convention and also creates its own `Idt` type. Historically, these posts were the main exception handling posts before the `x86-interrupt` calling convention and the `x86_64` crate existed.
|
||||
|
||||
[“Handling Exceptions with Naked Functions”]: @/first-edition/extra/naked-exceptions/_index.md
|
||||
[“Handling Exceptions with Naked Functions”]: @/edition-1/extra/naked-exceptions/_index.md
|
||||
|
||||
## What's next?
|
||||
We've successfully caught our first exception and returned from it! The next step is to add handlers for other common exceptions such as page faults. We also need to make sure that we never cause a [triple fault], since it causes a complete system reset. The next post explains how we can avoid this by correctly catching [double faults].
|
||||
|
||||
[triple fault]: http://wiki.osdev.org/Triple_Fault
|
||||
[double faults]: http://wiki.osdev.org/Double_Fault#Double_Fault
|
||||
[triple fault]: https://wiki.osdev.org/Triple_Fault
|
||||
[double faults]: https://wiki.osdev.org/Double_Fault#Double_Fault
|
||||
|
||||
## Footnotes
|
||||
[^fn-breakpoint-restart-use-cases]: There are valid use cases for restarting an instruction that caused a breakpoint. The most common use case is a debugger: When setting a breakpoint on some code line, the debugger overwrites the corresponding instruction with an `int3` instruction, so that the CPU traps when that line is executed. When the user continues execution, the debugger swaps in the original instruction and continues the program from the replaced instruction.
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
@@ -4,7 +4,7 @@ weight = 10
|
||||
path = "double-faults"
|
||||
aliases = ["double-faults.html"]
|
||||
date = 2017-01-02
|
||||
template = "first-edition/page.html"
|
||||
template = "edition-1/page.html"
|
||||
+++
|
||||
|
||||
In this post we explore double faults in detail. We also set up an _Interrupt Stack Table_ to catch double faults on a separate kernel stack. This way, we can completely prevent triple faults, even on kernel stack overflow.
|
||||
@@ -20,7 +20,7 @@ As always, the complete source code is available on [GitHub]. Please file [issue
|
||||
## What is a Double Fault?
|
||||
In simplified terms, a double fault is a special exception that occurs when the CPU fails to invoke an exception handler. For example, it occurs when a page fault is triggered but there is no page fault handler registered in the [Interrupt Descriptor Table][IDT] (IDT). So it's kind of similar to catch-all blocks in programming languages with exceptions, e.g. `catch(...)` in C++ or `catch(Exception e)` in Java or C#.
|
||||
|
||||
[IDT]: @/first-edition/posts/09-handling-exceptions/index.md#the-interrupt-descriptor-table
|
||||
[IDT]: @/edition-1/posts/09-handling-exceptions/index.md#the-interrupt-descriptor-table
|
||||
|
||||
A double fault behaves like a normal exception. It has the vector number `8` and we can define a normal handler function for it in the IDT. It is really important to provide a double fault handler, because if a double fault is unhandled a fatal _triple fault_ occurs. Triple faults can't be caught and most hardware reacts with a system reset.
|
||||
|
||||
@@ -119,7 +119,7 @@ For example, what happens if… :
|
||||
3. a divide-by-zero handler causes a breakpoint exception, but the breakpoint handler is swapped out?
|
||||
4. our kernel overflows its stack and the [guard page] is hit?
|
||||
|
||||
[guard page]: @/first-edition/posts/07-remap-the-kernel/index.md#creating-a-guard-page
|
||||
[guard page]: @/edition-1/posts/07-remap-the-kernel/index.md#creating-a-guard-page
|
||||
|
||||
Fortunately, the AMD64 manual ([PDF][AMD64 manual]) has an exact definition (in Section 8.2.9). According to it, a “double fault exception _can_ occur when a second exception occurs during the handling of a prior (first) exception handler”. The _“can”_ is important: Only very specific combinations of exceptions lead to a double fault. These combinations are:
|
||||
|
||||
@@ -128,12 +128,12 @@ First Exception | Second Exception
|
||||
[Divide-by-zero],<br>[Invalid TSS],<br>[Segment Not Present],<br>[Stack-Segment Fault],<br>[General Protection Fault] | [Invalid TSS],<br>[Segment Not Present],<br>[Stack-Segment Fault],<br>[General Protection Fault]
|
||||
[Page Fault] | [Page Fault],<br>[Invalid TSS],<br>[Segment Not Present],<br>[Stack-Segment Fault],<br>[General Protection Fault]
|
||||
|
||||
[Divide-by-zero]: http://wiki.osdev.org/Exceptions#Divide-by-zero_Error
|
||||
[Invalid TSS]: http://wiki.osdev.org/Exceptions#Invalid_TSS
|
||||
[Segment Not Present]: http://wiki.osdev.org/Exceptions#Segment_Not_Present
|
||||
[Stack-Segment Fault]: http://wiki.osdev.org/Exceptions#Stack-Segment_Fault
|
||||
[General Protection Fault]: http://wiki.osdev.org/Exceptions#General_Protection_Fault
|
||||
[Page Fault]: http://wiki.osdev.org/Exceptions#Page_Fault
|
||||
[Divide-by-zero]: https://wiki.osdev.org/Exceptions#Division_Error
|
||||
[Invalid TSS]: https://wiki.osdev.org/Exceptions#Invalid_TSS
|
||||
[Segment Not Present]: https://wiki.osdev.org/Exceptions#Segment_Not_Present
|
||||
[Stack-Segment Fault]: https://wiki.osdev.org/Exceptions#Stack-Segment_Fault
|
||||
[General Protection Fault]: https://wiki.osdev.org/Exceptions#General_Protection_Fault
|
||||
[Page Fault]: https://wiki.osdev.org/Exceptions#Page_Fault
|
||||
|
||||
|
||||
[AMD64 manual]: https://www.amd.com/system/files/TechDocs/24593.pdf
|
||||
@@ -155,7 +155,7 @@ Let's look at the fourth question:
|
||||
|
||||
When our kernel overflows its stack and hits the guard page, a _page fault_ occurs. The CPU looks up the page fault handler in the IDT and tries to push the [exception stack frame] onto the stack. However, our current stack pointer still points to the non-present guard page. Thus, a second page fault occurs, which causes a double fault (according to the above table).
|
||||
|
||||
[exception stack frame]: http://os.phil-opp.com/better-exception-messages.html#exceptions-in-detail
|
||||
[exception stack frame]: @/edition-1/posts/09-handling-exceptions/index.md#the-exception-stack-frame
|
||||
|
||||
So the CPU tries to call our _double fault handler_ now. However, on a double fault the CPU tries to push the exception stack frame, too. Our stack pointer still points to the guard page, so a _third_ page fault occurs, which causes a _triple fault_ and a system reboot. So our current double fault handler can't avoid a triple fault in this case.
|
||||
|
||||
@@ -199,7 +199,7 @@ struct InterruptStackTable {
|
||||
|
||||
For each exception handler, we can choose a stack from the IST through the `options` field in the corresponding [IDT entry]. For example, we could use the first stack in the IST for our double fault handler. Then the CPU would automatically switch to this stack whenever a double fault occurs. This switch would happen before anything is pushed, so it would prevent the triple fault.
|
||||
|
||||
[IDT entry]: @/first-edition/posts/09-handling-exceptions/index.md#the-interrupt-descriptor-table
|
||||
[IDT entry]: @/edition-1/posts/09-handling-exceptions/index.md#the-interrupt-descriptor-table
|
||||
|
||||
### Allocating a new Stack
|
||||
In order to fill an Interrupt Stack Table later, we need a way to allocate new stacks. Therefore we extend our `memory` module with a new `stack_allocator` submodule:
|
||||
@@ -230,7 +230,7 @@ impl StackAllocator {
|
||||
```
|
||||
We create a simple `StackAllocator` that allocates stacks from a given range of pages (`PageIter` is an Iterator over a range of pages; we introduced it [in the kernel heap post].).
|
||||
|
||||
[in the kernel heap post]: @/first-edition/posts/08-kernel-heap/index.md#mapping-the-heap
|
||||
[in the kernel heap post]: @/edition-1/posts/08-kernel-heap/index.md#mapping-the-heap
|
||||
|
||||
We add a `alloc_stack` method that allocates a new stack:
|
||||
|
||||
@@ -285,8 +285,8 @@ impl StackAllocator {
|
||||
```
|
||||
The method takes mutable references to the [ActivePageTable] and a [FrameAllocator], since it needs to map the new virtual stack pages to physical frames. We define that the stack size is a multiple of the page size.
|
||||
|
||||
[ActivePageTable]: @/first-edition/posts/06-page-tables/index.md#page-table-ownership
|
||||
[FrameAllocator]: @/first-edition/posts/05-allocating-frames/index.md#a-frame-allocator
|
||||
[ActivePageTable]: @/edition-1/posts/06-page-tables/index.md#page-table-ownership
|
||||
[FrameAllocator]: @/edition-1/posts/05-allocating-frames/index.md#a-frame-allocator
|
||||
|
||||
Instead of operating directly on `self.range`, we [clone] it and only write it back on success. This way, subsequent stack allocations can still succeed if there are pages left (e.g., a call with `size_in_pages = 3` can still succeed after a failed call with `size_in_pages = 100`).
|
||||
|
||||
@@ -297,7 +297,7 @@ In order to be able to clone `PageIter`, we add a `#[derive(Clone)]` to its defi
|
||||
The actual allocation is straightforward: First, we choose the next page as [guard page]. Then we choose the next `size_in_pages` pages as stack pages using [Iterator::nth]. If all three variables are `Some`, the allocation succeeded and we map the stack pages to physical frames using [ActivePageTable::map]. The guard page remains unmapped.
|
||||
|
||||
[Iterator::nth]: https://doc.rust-lang.org/nightly/core/iter/trait.Iterator.html#method.nth
|
||||
[ActivePageTable::map]: @/first-edition/posts/06-page-tables/index.md#more-mapping-functions
|
||||
[ActivePageTable::map]: @/edition-1/posts/06-page-tables/index.md#more-mapping-functions
|
||||
|
||||
Finally, we create and return a new `Stack`, which we define as follows:
|
||||
|
||||
@@ -438,7 +438,7 @@ We allocate a 4096 bytes stack (one page) for our double fault handler. Now we j
|
||||
The Interrupt Stack Table (IST) is part of an old legacy structure called _[Task State Segment]_ \(TSS). The TSS used to hold various information (e.g. processor register state) about a task in 32-bit mode and was for example used for [hardware context switching]. However, hardware context switching is no longer supported in 64-bit mode and the format of the TSS changed completely.
|
||||
|
||||
[Task State Segment]: https://en.wikipedia.org/wiki/Task_state_segment
|
||||
[hardware context switching]: http://wiki.osdev.org/Context_Switching#Hardware_Context_Switching
|
||||
[hardware context switching]: https://wiki.osdev.org/Context_Switching#Hardware_Context_Switching
|
||||
|
||||
On x86_64, the TSS no longer holds any task specific information at all. Instead, it holds two stack tables (the IST is one of them). The only common field between the 32-bit and 64-bit TSS is the pointer to the [I/O port permissions bitmap].
|
||||
|
||||
@@ -495,7 +495,7 @@ We define that the 0th IST entry is the double fault stack (any other IST index
|
||||
#### Loading the TSS
|
||||
Now that we created a new TSS, we need a way to tell the CPU that it should use it. Unfortunately, this is a bit cumbersome, since the TSS is a Task State _Segment_ (for historical reasons). So instead of loading the table directly, we need to add a new segment descriptor to the [Global Descriptor Table] \(GDT). Then we can load our TSS invoking the [`ltr` instruction] with the respective GDT index.
|
||||
|
||||
[Global Descriptor Table]: http://www.flingos.co.uk/docs/reference/Global-Descriptor-Table/
|
||||
[Global Descriptor Table]: https://web.archive.org/web/20190217233448/https://www.flingos.co.uk/docs/reference/Global-Descriptor-Table/
|
||||
[`ltr` instruction]: https://www.felixcloutier.com/x86/ltr
|
||||
|
||||
### The Global Descriptor Table (again)
|
||||
@@ -505,7 +505,7 @@ The Global Descriptor Table (GDT) is a relict that was used for [memory segmenta
|
||||
|
||||
We already created a GDT [when switching to long mode]. Back then, we used assembly to create valid code and data segment descriptors, which were required to enter 64-bit mode. We could just edit that assembly file and add an additional TSS descriptor. However, we now have the expressiveness of Rust, so let's do it in Rust instead.
|
||||
|
||||
[when switching to long mode]: @/first-edition/posts/02-entering-longmode/index.md#the-global-descriptor-table
|
||||
[when switching to long mode]: @/edition-1/posts/02-entering-longmode/index.md#the-global-descriptor-table
|
||||
|
||||
We start by creating a new `interrupts::gdt` submodule. For that we need to rename the `src/interrupts.rs` file to `src/interrupts/mod.rs`. Then we can create a new submodule:
|
||||
|
||||
@@ -608,7 +608,7 @@ Bit(s) | Name | Meaning
|
||||
64-95 | **base 32-63** | the last four bytes of the base address
|
||||
96-127 | ignored/must be zero | bits 104-108 must be zero, the rest is ignored
|
||||
|
||||
[ring level]: http://wiki.osdev.org/Security#Rings
|
||||
[ring level]: https://wiki.osdev.org/Security#Rings
|
||||
|
||||
We only need the bold fields for our TSS descriptor. For example, we don't need the `limit 16-19` field since a TSS has a fixed size that is smaller than `2^16`.
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@@ -1,4 +1,5 @@
|
||||
+++
|
||||
title = "Second Edition"
|
||||
template = "redirect-to-frontpage.html"
|
||||
aliases = ["second-edition/index.html"]
|
||||
+++
|
||||
@@ -3,5 +3,5 @@ title = "Extra Content"
|
||||
insert_anchor_links = "left"
|
||||
render = false
|
||||
sort_by = "weight"
|
||||
page_template = "second-edition/extra.html"
|
||||
page_template = "edition-2/extra.html"
|
||||
+++
|
||||
|
Before Width: | Height: | Size: 287 KiB After Width: | Height: | Size: 287 KiB |
@@ -1,19 +1,25 @@
|
||||
+++
|
||||
title = "Building on Android"
|
||||
weight = 3
|
||||
|
||||
aliases = ["second-edition/extra/building-on-android/index.html"]
|
||||
+++
|
||||
|
||||
I finally managed to get `blog_os` building on my Android phone using [termux](https://termux.com/). This post explains the necessary steps to set it up.
|
||||
|
||||
<!-- more -->
|
||||
|
||||
<div class = "warning">
|
||||
|
||||
This post is outdated and the instructions provided here might not work anymore.
|
||||
|
||||
</div>
|
||||
|
||||
<img src="building-on-android.png" alt="Screenshot of the compilation output from android" style="height: 50rem;" >
|
||||
|
||||
|
||||
### Install Termux and Nightly Rust
|
||||
|
||||
First, install [termux](https://termux.com/) from the [Google Play Store](https://play.google.com/store/apps/details?id=com.termux) or from [F-Droid](https://f-droid.org/packages/com.termux/). After installing, open it and perform the following steps:
|
||||
First, install [termux](https://termux.com/) from the [Google Play Store](https://play.google.com/store/apps/details?id=com.termux) or from F-Droid. After installing, open it and perform the following steps:
|
||||
|
||||
- Install fish shell, set as default shell, and launch it:
|
||||
```
|
||||
@@ -29,7 +35,7 @@ First, install [termux](https://termux.com/) from the [Google Play Store](https:
|
||||
pkg install wget tar
|
||||
```
|
||||
|
||||
- Add the [community repository by its-pointless](https://wiki.termux.com/wiki/Package_Management#By_its-pointless_.28live_the_dream.29:):
|
||||
- Add the [community repository by its-pointless](https://wiki.termux.com/wiki/Package_Management):
|
||||
```
|
||||
wget https://its-pointless.github.io/setup-pointless-repo.sh
|
||||
bash setup-pointless-repo.sh
|
||||
@@ -0,0 +1,129 @@
|
||||
+++
|
||||
title = "A Freestanding Rust Binary"
|
||||
weight = 1
|
||||
path = "ar/freestanding-rust-binary"
|
||||
date = 2018-02-10
|
||||
|
||||
[extra]
|
||||
# Please update this when updating the translation
|
||||
translation_based_on_commit = "087a464ed77361cff6c459fb42fc655cb9eacbea"
|
||||
# GitHub usernames of the people that translated this post
|
||||
translators = ["ZAAFHachemrachid"]
|
||||
+++
|
||||
|
||||
تتمثل الخطوة الأولى في إنشاء نواة نظام التشغيل الخاصة بنا في إنشاء ملف Rust قابل للتنفيذ لا يربط المكتبة القياسية. هذا يجعل من الممكن تشغيل شيفرة Rust على [bare metal] دون نظام تشغيل أساسي.
|
||||
|
||||
[bare metal]: https://en.wikipedia.org/wiki/Bare_machine
|
||||
<!-- more -->
|
||||
|
||||
تم تطوير هذه المدونة بشكل مفتوح على [GitHub]. إذا كان لديك أي مشاكل أو أسئلة، يرجى فتح مشكلة هناك. يمكنك أيضًا ترك تعليقات [في الأسفل]. يمكن العثور على الشيفرة المصدرية الكاملة لهذا المنشور في فرع [post-01][post branch].
|
||||
|
||||
|
||||
[GitHub]: https://github.com/phil-opp/blog_os
|
||||
[at the bottom]: #comments
|
||||
<!-- fix for zola anchor checker (target is in template): <a id="comments"> -->
|
||||
[post branch]: https://github.com/phil-opp/blog_os/tree/post-01
|
||||
<!-- toc -->
|
||||
|
||||
|
||||
## مقدمة
|
||||
لكتابة نواة نظام تشغيل، نحتاج إلى شيفرة لا تعتمد على أي ميزات نظام تشغيل. هذا يعني أنه لا يمكننا استخدام سلاسل الرسائل(threads) أو الملفات(File System) أو Heap ram أو الشبكة أو الأرقام العشوائية أو الإخراج القياسي(I/O) أو أي ميزات أخرى تتطلب تجريدات نظام التشغيل أو أجهزة معينة. وهذا منطقي، لأننا نحاول كتابة نظام التشغيل الخاص بنا (OS) وبرامج التشغيل الخاصة بنا (drivers).
|
||||
|
||||
هذا يعني أنه لا يمكننا استخدام معظم [Rust standard library]، ولكن هناك الكثير من ميزات Rust التي _يمكننا استخدامها. على سبيل المثال، يمكننا استخدام [iterators] و [closures] و [pattern matching] و [option] و [اresult] و [string formatting] وبالطبع [ownership system]. هذه الميزات تجعل من الممكن كتابة نواة بطريقة معبرة جدًا وعالية المستوى دون القلق بشأن [undefined behavior] أو [memory safety].
|
||||
|
||||
|
||||
[option]: https://doc.rust-lang.org/core/option/
|
||||
[result]:https://doc.rust-lang.org/core/result/
|
||||
[Rust standard library]: https://doc.rust-lang.org/std/
|
||||
[iterators]: https://doc.rust-lang.org/book/ch13-02-iterators.html
|
||||
[closures]: https://doc.rust-lang.org/book/ch13-01-closures.html
|
||||
[pattern matching]: https://doc.rust-lang.org/book/ch06-00-enums.html
|
||||
[string formatting]: https://doc.rust-lang.org/core/macro.write.html
|
||||
[ownership system]: https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html
|
||||
[undefined behavior]: https://www.nayuki.io/page/undefined-behavior-in-c-and-cplusplus-programs
|
||||
[memory safety]: https://tonyarcieri.com/it-s-time-for-a-memory-safety-intervention
|
||||
|
||||
|
||||
من أجل إنشاء نواة نظام تشغيل في Rust، نحتاج إلى إنشاء ملف قابل للتنفيذ يمكن تشغيله بدون نظام تشغيل أساسي. غالبًا ما يُطلق على هذا الملف القابل للتنفيذ اسم الملف القابل للتنفيذ ”القائم بذاته“ أو ”المعدني العاري“.
|
||||
|
||||
يصف هذا المنشور الخطوات اللازمة لإنشاء ثنائي Rust قائم بذاته ويشرح سبب الحاجة إلى هذه الخطوات. إذا كنت مهتمًا بمثال بسيط فقط، يمكنك **[الانتقال إلى الملخص] (#ملخص)**.
|
||||
|
||||
|
||||
|
||||
## تعطيل المكتبة القياسية
|
||||
بشكل افتراضي، تربط جميع صناديق Rust [standard library]، والتي تعتمد على نظام التشغيل لميزات (مثل threads, files, or networking). كما أنها تعتمد أيضًا على مكتبة C القياسية 'libc'، والتي تتفاعل بشكل وثيق مع خدمات نظام التشغيل. نظرًا لأن خطتنا هي كتابة نظام تشغيل، لا يمكننا استخدام أي مكتبات تعتمد على نظام التشغيل. لذا يجب علينا تعطيل التضمين التلقائي للمكتبة القياسية من خلال سمة [no_std].
|
||||
|
||||
|
||||
[standard library]: https://doc.rust-lang.org/std/
|
||||
[`no_std` attribute]: https://doc.rust-lang.org/1.30.0/book/first-edition/using-rust-without-the-standard-library.html
|
||||
|
||||
```
|
||||
cargo new blog_os --bin --edition 2018
|
||||
```
|
||||
|
||||
لقد أطلقتُ على المشروع اسم ”Blog_os“، ولكن بالطبع يمكنك اختيار اسمك الخاص. تُحدّد علامة ”bin“ أننا نريد إنشاء نسخة binary قابلة للتنفيذ (على عكس المكتبة) وتحدّد علامة ”--- Edition 2018“ أننا نريد استخدام [2018 edition] من Rust لصندوقنا. عندما نُشغّل الأمر، تُنشئ لنا الشحنة بنية الدليل التالية:
|
||||
|
||||
[2018 edition]: https://doc.rust-lang.org/nightly/edition-guide/rust-2018/index.html
|
||||
|
||||
```
|
||||
blog_os
|
||||
├── Cargo.toml
|
||||
└── src
|
||||
└── main.rs
|
||||
```
|
||||
يحتوي ملف 'Cargo.toml' على تكوين الصندوق، على سبيل المثال اسم الصندوق، والمؤلف، ورقم [semantic version]، والتبعيات. يحتوي الملف 'src/main.rs' على الوحدة النمطية الجذرية للصندوق والدالة 'الرئيسية'. يمكنك تجميع قفصك من خلال 'cargo build' ثم تشغيل الملف الثنائي 'blog_os' المجمّع في المجلد الفرعي 'target/debug'.
|
||||
|
||||
[semantic version]: https://semver.org/
|
||||
|
||||
### السمة 'no_std'
|
||||
|
||||
يربط صندوقنا الآن المكتبة القياسية ضمنيًا بالمكتبة القياسية. دعونا نحاول تعطيل ذلك بإضافة سمة [no_std]:
|
||||
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
عندما نحاول بناءه الآن (عن طريق تشغيل ”cargo build“)، يحدث الخطأ التالي:
|
||||
|
||||
```
|
||||
error: cannot find macro `println!` in this scope
|
||||
--> src/main.rs:4:5
|
||||
|
|
||||
4 | println!("Hello, world!");
|
||||
| ^^^^^^^
|
||||
```
|
||||
|
||||
والسبب في هذا الخطأ هو أن [`println` macro] هو جزء من المكتبة القياسية، والتي لم نعد نضمّنها. لذا لم يعد بإمكاننا طباعة الأشياء. هذا أمر منطقي، لأن 'println' يكتب إلى [standard output]، وهو واصف ملف خاص يوفره نظام التشغيل.
|
||||
|
||||
|
||||
[`println` macro]: https://doc.rust-lang.org/std/macro.println.html
|
||||
[standard output]: https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29
|
||||
|
||||
لذا دعنا نحذف الطباعة ونحاول مرة أخرى بدالة رئيسية فارغة:
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
fn main() {}
|
||||
```
|
||||
|
||||
```
|
||||
> cargo build
|
||||
error: `#[panic_handler]` function required, but not found
|
||||
error: language item required, but not found: `eh_personality`
|
||||
```
|
||||
|
||||
|
||||
يفتقد بناء المترجمات البرمجية الآن إلى `#[panic_handler]` دالة و _language item_.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,522 @@
|
||||
+++
|
||||
title = "Un Binario Rust Autónomo"
|
||||
weight = 1
|
||||
path = "es/freestanding-rust-binary"
|
||||
date = 2018-02-10
|
||||
|
||||
[extra]
|
||||
chapter = "Bare Bones"
|
||||
|
||||
# GitHub usernames of the people that translated this post
|
||||
translators = ["dobleuber"]
|
||||
+++
|
||||
|
||||
El primer paso para crear nuestro propio kernel de sistema operativo es crear un ejecutable en Rust que no enlace con la biblioteca estándar. Esto hace posible ejecutar código Rust directamente en el [bare metal] sin un sistema operativo subyacente.
|
||||
|
||||
[bare metal]: https://en.wikipedia.org/wiki/Bare_machine
|
||||
|
||||
<!-- more -->
|
||||
|
||||
Este blog se desarrolla abiertamente en [GitHub]. Si tienes algún problema o pregunta, por favor abre un issue allí. También puedes dejar comentarios [al final]. El código fuente completo para esta publicación se encuentra en la rama [`post-01`][post branch].
|
||||
|
||||
[GitHub]: https://github.com/phil-opp/blog_os
|
||||
[al final]: #comments
|
||||
<!-- solución para el verificador de anclajes de zola (el objetivo está en la plantilla): <a id="comments"> -->
|
||||
[post branch]: https://github.com/phil-opp/blog_os/tree/post-01
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## Introducción
|
||||
Para escribir un kernel de sistema operativo, necesitamos código que no dependa de características del sistema operativo. Esto significa que no podemos usar hilos, archivos, memoria dinámica, redes, números aleatorios, salida estándar ni ninguna otra característica que requiera abstracciones de sistema operativo o hardware específico. Esto tiene sentido, ya que estamos intentando escribir nuestro propio sistema operativo y nuestros propios controladores.
|
||||
|
||||
Esto implica que no podemos usar la mayor parte de la [biblioteca estándar de Rust], pero hay muchas características de Rust que sí _podemos_ usar. Por ejemplo, podemos utilizar [iteradores], [closures], [pattern matching], [option] y [result], [formateo de cadenas] y, por supuesto, el [sistema de ownership]. Estas características hacen posible escribir un kernel de una manera muy expresiva y de alto nivel, sin preocuparnos por el [comportamiento indefinido] o la [seguridad de la memoria].
|
||||
|
||||
[option]: https://doc.rust-lang.org/core/option/
|
||||
[result]: https://doc.rust-lang.org/core/result/
|
||||
[biblioteca estándar de Rust]: https://doc.rust-lang.org/std/
|
||||
[iteradores]: https://doc.rust-lang.org/book/ch13-02-iterators.html
|
||||
[closures]: https://doc.rust-lang.org/book/ch13-01-closures.html
|
||||
[pattern matching]: https://doc.rust-lang.org/book/ch06-00-enums.html
|
||||
[formateo de cadenas]: https://doc.rust-lang.org/core/macro.write.html
|
||||
[sistema de ownership]: https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html
|
||||
[comportamiento indefinido]: https://www.nayuki.io/page/undefined-behavior-in-c-and-cplusplus-programs
|
||||
[seguridad de la memoria]: https://tonyarcieri.com/it-s-time-for-a-memory-safety-intervention
|
||||
|
||||
Para crear un kernel de sistema operativo en Rust, necesitamos crear un ejecutable que pueda ejecutarse sin un sistema operativo subyacente. Dicho ejecutable se llama frecuentemente un ejecutable “autónomo” o de “bare metal”.
|
||||
|
||||
Esta publicación describe los pasos necesarios para crear un binario autónomo en Rust y explica por qué son necesarios. Si solo te interesa un ejemplo mínimo, puedes **[saltar al resumen](#resumen)**.
|
||||
|
||||
## Deshabilitando la Biblioteca Estándar
|
||||
Por defecto, todos los crates de Rust enlazan con la [biblioteca estándar], que depende del sistema operativo para características como hilos, archivos o redes. También depende de la biblioteca estándar de C, `libc`, que interactúa estrechamente con los servicios del sistema operativo. Como nuestro plan es escribir un sistema operativo, no podemos usar ninguna biblioteca que dependa del sistema operativo. Por lo tanto, tenemos que deshabilitar la inclusión automática de la biblioteca estándar mediante el atributo [`no_std`].
|
||||
|
||||
[biblioteca estándar]: https://doc.rust-lang.org/std/
|
||||
[`no_std`]: https://doc.rust-lang.org/1.30.0/book/first-edition/using-rust-without-the-standard-library.html
|
||||
|
||||
Comenzamos creando un nuevo proyecto de aplicación en Cargo. La forma más fácil de hacerlo es a través de la línea de comandos:
|
||||
|
||||
|
||||
```
|
||||
cargo new blog_os --bin --edition 2018
|
||||
```
|
||||
|
||||
Nombré el proyecto `blog_os`, pero, por supuesto, puedes elegir tu propio nombre. La bandera `--bin` especifica que queremos crear un binario ejecutable (en contraste con una biblioteca), y la bandera `--edition 2018` indica que queremos usar la [edición 2018] de Rust para nuestro crate. Al ejecutar el comando, Cargo crea la siguiente estructura de directorios para nosotros:
|
||||
|
||||
[2018 edition]: https://doc.rust-lang.org/nightly/edition-guide/rust-2018/index.html
|
||||
|
||||
```
|
||||
blog_os
|
||||
├── Cargo.toml
|
||||
└── src
|
||||
└── main.rs
|
||||
```
|
||||
|
||||
El archivo `Cargo.toml` contiene la configuración del crate, como el nombre del crate, el autor, el número de [versión semántica] y las dependencias. El archivo `src/main.rs` contiene el módulo raíz de nuestro crate y nuestra función `main`. Puedes compilar tu crate utilizando `cargo build` y luego ejecutar el binario compilado `blog_os` ubicado en la subcarpeta `target/debug`.
|
||||
|
||||
[semantic version]: https://semver.org/
|
||||
|
||||
### El Atributo `no_std`
|
||||
|
||||
Actualmente, nuestro crate enlaza implícitamente con la biblioteca estándar. Intentemos deshabilitar esto añadiendo el [`atributo no_std`]:
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
Cuando intentamos compilarlo ahora (ejecutando `cargo build`), ocurre el siguiente error:
|
||||
|
||||
```
|
||||
error: cannot find macro `println!` in this scope
|
||||
--> src/main.rs:4:5
|
||||
|
|
||||
4 | println!("Hello, world!");
|
||||
| ^^^^^^^
|
||||
```
|
||||
|
||||
La razón de este error es que la [macro `println`] forma parte de la biblioteca estándar, la cual ya no estamos incluyendo. Por lo tanto, ya no podemos imprimir cosas. Esto tiene sentido, ya que `println` escribe en la [salida estándar], que es un descriptor de archivo especial proporcionado por el sistema operativo.
|
||||
|
||||
[macro `println`]: https://doc.rust-lang.org/std/macro.println.html
|
||||
[salida estándar]: https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29
|
||||
|
||||
Así que eliminemos la impresión e intentemos de nuevo con una función `main` vacía:
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
fn main() {}
|
||||
```
|
||||
|
||||
```
|
||||
> cargo build
|
||||
error: `#[panic_handler]` function required, but not found
|
||||
error: language item required, but not found: `eh_personality`
|
||||
```
|
||||
|
||||
Ahora el compilador indica que falta una función `#[panic_handler]` y un _elemento de lenguaje_ (_language item_).
|
||||
|
||||
## Implementación de Panic
|
||||
|
||||
El atributo `panic_handler` define la función que el compilador invoca cuando ocurre un [panic]. La biblioteca estándar proporciona su propia función de panico, pero en un entorno `no_std` debemos definirla nosotros mismos:
|
||||
|
||||
[panic]: https://doc.rust-lang.org/stable/book/ch09-01-unrecoverable-errors-with-panic.html
|
||||
|
||||
```rust
|
||||
// en main.rs
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
/// Esta función se llama cuando ocurre un pánico.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
El [parámetro `PanicInfo`][PanicInfo] contiene el archivo y la línea donde ocurrió el panic, así como el mensaje opcional del panic. La función no debería retornar nunca, por lo que se marca como una [función divergente][diverging function] devolviendo el [tipo “never”][“never” type] `!`. Por ahora, no hay mucho que podamos hacer en esta función, así que simplemente entramos en un bucle infinito.
|
||||
|
||||
[PanicInfo]: https://doc.rust-lang.org/nightly/core/panic/struct.PanicInfo.html
|
||||
[diverging function]: https://doc.rust-lang.org/1.30.0/book/first-edition/functions.html#diverging-functions
|
||||
[“never” type]: https://doc.rust-lang.org/nightly/std/primitive.never.html
|
||||
|
||||
## El Elemento de Lenguaje `eh_personality`
|
||||
|
||||
Los elementos de lenguaje son funciones y tipos especiales que el compilador requiere internamente. Por ejemplo, el trait [`Copy`] es un elemento de lenguaje que indica al compilador qué tipos tienen [_semántica de copia_][`Copy`]. Si observamos su [implementación][copy code], veremos que tiene el atributo especial `#[lang = "copy"]`, que lo define como un elemento de lenguaje.
|
||||
|
||||
[`Copy`]: https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html
|
||||
[copy code]: https://github.com/rust-lang/rust/blob/485397e49a02a3b7ff77c17e4a3f16c653925cb3/src/libcore/marker.rs#L296-L299
|
||||
|
||||
Aunque es posible proporcionar implementaciones personalizadas de elementos de lenguaje, esto debería hacerse solo como último recurso. La razón es que los elementos de lenguaje son detalles de implementación altamente inestables y ni siquiera están verificados por tipos (el compilador no comprueba si una función tiene los tipos de argumento correctos). Afortunadamente, hay una forma más estable de solucionar el error relacionado con el elemento de lenguaje mencionado.
|
||||
|
||||
El [elemento de lenguaje `eh_personality`][`eh_personality` language item] marca una función utilizada para implementar el [desenrollado de pila][stack unwinding]. Por defecto, Rust utiliza unwinding para ejecutar los destructores de todas las variables de pila activas en caso de un [pánico][panic]. Esto asegura que toda la memoria utilizada sea liberada y permite que el hilo principal capture el pánico y continúe ejecutándose. Sin embargo, el unwinding es un proceso complicado y requiere algunas bibliotecas específicas del sistema operativo (por ejemplo, [libunwind] en Linux o [manejadores estructurados de excepciones][structured exception handling] en Windows), por lo que no queremos usarlo en nuestro sistema operativo.
|
||||
|
||||
[`eh_personality` language item]: https://github.com/rust-lang/rust/blob/edb368491551a77d77a48446d4ee88b35490c565/src/libpanic_unwind/gcc.rs#L11-L45
|
||||
[stack unwinding]: https://www.bogotobogo.com/cplusplus/stackunwinding.php
|
||||
[libunwind]: https://www.nongnu.org/libunwind/
|
||||
[structured exception handling]: https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling
|
||||
[panic]: https://doc.rust-lang.org/book/ch09-01-unrecoverable-errors-with-panic.html
|
||||
|
||||
### Deshabilitando el Unwinding
|
||||
|
||||
Existen otros casos de uso en los que el no es deseable, por lo que Rust proporciona una opción para [abortar en caso de pánico][abort on panic]. Esto desactiva la generación de información de símbolos de unwinding y, por lo tanto, reduce considerablemente el tamaño del binario. Hay múltiples lugares donde podemos deshabilitar el unwinding. La forma más sencilla es agregar las siguientes líneas a nuestro archivo `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
```
|
||||
|
||||
Esto establece la estrategia de pánico en `abort` tanto para el perfil `dev` (utilizado en `cargo build`) como para el perfil `release` (utilizado en `cargo build --release`). Ahora, el elemento de lenguaje `eh_personality` ya no debería ser necesario.
|
||||
|
||||
[abort on panic]: https://github.com/rust-lang/rust/pull/32900
|
||||
|
||||
Ahora hemos solucionado ambos errores anteriores. Sin embargo, si intentamos compilarlo ahora, ocurre otro error:
|
||||
|
||||
```
|
||||
> cargo build
|
||||
error: requires `start` lang_item
|
||||
```
|
||||
|
||||
Nuestro programa carece del elemento de lenguaje `start`, que define el punto de entrada.
|
||||
|
||||
## El Atributo `start`
|
||||
|
||||
Podría pensarse que la función `main` es la primera que se ejecuta al correr un programa. Sin embargo, la mayoría de los lenguajes tienen un [sistema de tiempo de ejecución][runtime system], encargado de tareas como la recolección de basura (por ejemplo, en Java) o los hilos de software (por ejemplo, goroutines en Go). Este sistema de tiempo de ejecución necesita ejecutarse antes de `main`, ya que debe inicializarse.
|
||||
|
||||
[runtime system]: https://en.wikipedia.org/wiki/Runtime_system
|
||||
|
||||
En un binario típico de Rust que enlaza con la biblioteca estándar, la ejecución comienza en una biblioteca de tiempo de ejecución de C llamada `crt0` ("C runtime zero"), que configura el entorno para una aplicación en C. Esto incluye la creación de una pila y la colocación de los argumentos en los registros adecuados. Luego, el tiempo de ejecución de C invoca el [punto de entrada del tiempo de ejecución de Rust][rt::lang_start], que está marcado por el elemento de lenguaje `start`. Rust tiene un tiempo de ejecución muy minimalista, que se encarga de tareas menores como configurar los guardias de desbordamiento de pila o imprimir un backtrace en caso de pánico. Finalmente, el tiempo de ejecución llama a la función `main`.
|
||||
|
||||
[rt::lang_start]: https://github.com/rust-lang/rust/blob/bb4d1491466d8239a7a5fd68bd605e3276e97afb/src/libstd/rt.rs#L32-L73
|
||||
|
||||
Nuestro ejecutable autónomo no tiene acceso al tiempo de ejecución de Rust ni a `crt0`, por lo que necesitamos definir nuestro propio punto de entrada. Implementar el elemento de lenguaje `start` no ayudaría, ya que aún requeriría `crt0`. En su lugar, debemos sobrescribir directamente el punto de entrada de `crt0`.
|
||||
|
||||
### Sobrescribiendo el Punto de Entrada
|
||||
|
||||
Para indicar al compilador de Rust que no queremos usar la cadena normal de puntos de entrada, agregamos el atributo `#![no_main]`:
|
||||
|
||||
```rust
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
/// Esta función se llama cuando ocurre un pánico.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
Podrás notar que eliminamos la función `main`. La razón es que una función `main` no tiene sentido sin un sistema de tiempo de ejecución subyacente que la invoque. En su lugar, estamos sobrescribiendo el punto de entrada del sistema operativo con nuestra propia función `_start`:
|
||||
|
||||
```rust
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _start() -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
Al usar el atributo `#[no_mangle]`, deshabilitamos el [name mangling] para asegurarnos de que el compilador de Rust realmente genere una función con el nombre `_start`. Sin este atributo, el compilador generaría un símbolo críptico como `_ZN3blog_os4_start7hb173fedf945531caE` para dar un nombre único a cada función. Este atributo es necesario porque necesitamos informar al enlazador el nombre de la función de punto de entrada en el siguiente paso.
|
||||
|
||||
También debemos marcar la función como `extern "C"` para indicar al compilador que debe usar la [convención de llamadas en C][C calling convention] para esta función (en lugar de la convención de llamadas de Rust, que no está especificada). El motivo para nombrar la función `_start` es que este es el nombre predeterminado del punto de entrada en la mayoría de los sistemas.
|
||||
|
||||
[name mangling]: https://en.wikipedia.org/wiki/Name_mangling
|
||||
[C calling convention]: https://en.wikipedia.org/wiki/Calling_convention
|
||||
|
||||
El tipo de retorno `!` significa que la función es divergente, es decir, no está permitido que retorne nunca. Esto es necesario porque el punto de entrada no es llamado por ninguna función, sino que es invocado directamente por el sistema operativo o el bootloader. En lugar de retornar, el punto de entrada debería, por ejemplo, invocar la [llamada al sistema `exit`][`exit` system call] del sistema operativo. En nuestro caso, apagar la máquina podría ser una acción razonable, ya que no queda nada por hacer si un binario autónomo regresa. Por ahora, cumplimos con este requisito entrando en un bucle infinito.
|
||||
|
||||
[`exit` system call]: https://en.wikipedia.org/wiki/Exit_(system_call)
|
||||
|
||||
Cuando ejecutamos `cargo build` ahora, obtenemos un feo error del _linker_ (enlazador).
|
||||
|
||||
## Errores del Enlazador
|
||||
|
||||
El enlazador es un programa que combina el código generado en un ejecutable. Dado que el formato del ejecutable varía entre Linux, Windows y macOS, cada sistema tiene su propio enlazador que lanza errores diferentes. Sin embargo, la causa fundamental de los errores es la misma: la configuración predeterminada del enlazador asume que nuestro programa depende del tiempo de ejecución de C, lo cual no es cierto.
|
||||
|
||||
Para solucionar los errores, necesitamos informar al enlazador que no debe incluir el tiempo de ejecución de C. Esto puede hacerse pasando un conjunto específico de argumentos al enlazador o construyendo para un destino de bare metal.
|
||||
|
||||
### Construyendo para un Destino de Bare Metal
|
||||
|
||||
Por defecto, Rust intenta construir un ejecutable que pueda ejecutarse en el entorno actual de tu sistema. Por ejemplo, si estás usando Windows en `x86_64`, Rust intenta construir un ejecutable `.exe` para Windows que utilice instrucciones `x86_64`. Este entorno se llama tu sistema "host".
|
||||
|
||||
Para describir diferentes entornos, Rust utiliza una cadena llamada [_target triple_]. Puedes ver el _target triple_ de tu sistema host ejecutando:
|
||||
|
||||
```
|
||||
rustc 1.35.0-nightly (474e7a648 2019-04-07)
|
||||
binary: rustc
|
||||
commit-hash: 474e7a6486758ea6fc761893b1a49cd9076fb0ab
|
||||
commit-date: 2019-04-07
|
||||
host: x86_64-unknown-linux-gnu
|
||||
release: 1.35.0-nightly
|
||||
LLVM version: 8.0
|
||||
```
|
||||
|
||||
El resultado anterior es de un sistema Linux `x86_64`. Vemos que la tripleta del `host` es `x86_64-unknown-linux-gnu`, lo que incluye la arquitectura de la CPU (`x86_64`), el proveedor (`unknown`), el sistema operativo (`linux`) y el [ABI] (`gnu`).
|
||||
|
||||
[ABI]: https://en.wikipedia.org/wiki/Application_binary_interface
|
||||
|
||||
Al compilar para la tripleta del host, el compilador de Rust y el enlazador asumen que hay un sistema operativo subyacente como Linux o Windows que utiliza el tiempo de ejecución de C de forma predeterminada, lo que provoca los errores del enlazador. Para evitar estos errores, podemos compilar para un entorno diferente que no tenga un sistema operativo subyacente.
|
||||
|
||||
Un ejemplo de este tipo de entorno bare metal es la tripleta de destino `thumbv7em-none-eabihf`, que describe un sistema [embebido][embedded] basado en [ARM]. Los detalles no son importantes, lo que importa es que la tripleta de destino no tiene un sistema operativo subyacente, lo cual se indica por el `none` en la tripleta de destino. Para poder compilar para este destino, necesitamos agregarlo usando `rustup`:
|
||||
|
||||
```
|
||||
rustup target add thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
Esto descarga una copia de las bibliotecas estándar (y core) para el sistema. Ahora podemos compilar nuestro ejecutable autónomo para este destino:
|
||||
|
||||
```
|
||||
cargo build --target thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
Al pasar un argumento `--target`, realizamos un [compilado cruzado][cross compile] de nuestro ejecutable para un sistema bare metal. Dado que el sistema de destino no tiene un sistema operativo, el enlazador no intenta enlazar con el tiempo de ejecución de C, y nuestra compilación se completa sin errores del enlazador.
|
||||
|
||||
[cross compile]: https://en.wikipedia.org/wiki/Cross_compiler
|
||||
|
||||
Este es el enfoque que utilizaremos para construir nuestro kernel de sistema operativo. En lugar de `thumbv7em-none-eabihf`, utilizaremos un [destino personalizado][custom target] que describa un entorno bare metal `x86_64`. Los detalles se explicarán en la siguiente publicación.
|
||||
|
||||
[custom target]: https://doc.rust-lang.org/rustc/targets/custom.html
|
||||
|
||||
### Argumentos del Enlazador
|
||||
|
||||
En lugar de compilar para un sistema bare metal, también es posible resolver los errores del enlazador pasando un conjunto específico de argumentos al enlazador. Este no es el enfoque que usaremos para nuestro kernel, por lo tanto, esta sección es opcional y se proporciona solo para completar. Haz clic en _"Argumentos del Enlazador"_ a continuación para mostrar el contenido opcional.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Argumentos del Enlazador</summary>
|
||||
|
||||
En esta sección discutimos los errores del enlazador que ocurren en Linux, Windows y macOS, y explicamos cómo resolverlos pasando argumentos adicionales al enlazador. Ten en cuenta que el formato del ejecutable y el enlazador varían entre sistemas operativos, por lo que se requiere un conjunto diferente de argumentos para cada sistema.
|
||||
|
||||
#### Linux
|
||||
|
||||
En Linux ocurre el siguiente error del enlazador (resumido):
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: /usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x12): undefined reference to `__libc_csu_fini'
|
||||
/usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x19): undefined reference to `__libc_csu_init'
|
||||
/usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x25): undefined reference to `__libc_start_main'
|
||||
collect2: error: ld returned 1 exit status
|
||||
```
|
||||
|
||||
El problema es que el enlazador incluye por defecto la rutina de inicio del tiempo de ejecución de C, que también se llama `_start`. Esta rutina requiere algunos símbolos de la biblioteca estándar de C `libc` que no incluimos debido al atributo `no_std`, por lo que el enlazador no puede resolver estas referencias. Para solucionar esto, podemos indicar al enlazador que no enlace la rutina de inicio de C pasando la bandera `-nostartfiles`.
|
||||
|
||||
Una forma de pasar atributos al enlazador a través de Cargo es usar el comando `cargo rustc`. Este comando se comporta exactamente como `cargo build`, pero permite pasar opciones a `rustc`, el compilador subyacente de Rust. `rustc` tiene la bandera `-C link-arg`, que pasa un argumento al enlazador. Combinados, nuestro nuevo comando de compilación se ve así:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-arg=-nostartfiles
|
||||
```
|
||||
|
||||
¡Ahora nuestro crate se compila como un ejecutable autónomo en Linux!
|
||||
|
||||
No fue necesario especificar explícitamente el nombre de nuestra función de punto de entrada, ya que el enlazador busca una función con el nombre `_start` por defecto.
|
||||
|
||||
#### Windows
|
||||
|
||||
En Windows, ocurre un error del enlazador diferente (resumido):
|
||||
|
||||
```
|
||||
error: linking with `link.exe` failed: exit code: 1561
|
||||
|
|
||||
= note: "C:\\Program Files (x86)\\…\\link.exe" […]
|
||||
= note: LINK : fatal error LNK1561: entry point must be defined
|
||||
```
|
||||
|
||||
El error "entry point must be defined" significa que el enlazador no puede encontrar el punto de entrada. En Windows, el nombre predeterminado del punto de entrada [depende del subsistema utilizado][windows-subsystems]. Para el subsistema `CONSOLE`, el enlazador busca una función llamada `mainCRTStartup`, y para el subsistema `WINDOWS`, busca una función llamada `WinMainCRTStartup`. Para anular este comportamiento predeterminado y decirle al enlazador que busque nuestra función `_start`, podemos pasar un argumento `/ENTRY` al enlazador:
|
||||
|
||||
[windows-subsystems]: https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-arg=/ENTRY:_start
|
||||
```
|
||||
|
||||
Por el formato diferente del argumento, podemos ver claramente que el enlazador de Windows es un programa completamente distinto al enlazador de Linux.
|
||||
|
||||
Ahora ocurre un error diferente del enlazador:
|
||||
|
||||
```
|
||||
error: linking with `link.exe` failed: exit code: 1221
|
||||
|
|
||||
= note: "C:\\Program Files (x86)\\…\\link.exe" […]
|
||||
= note: LINK : fatal error LNK1221: a subsystem can't be inferred and must be
|
||||
defined
|
||||
```
|
||||
|
||||
Este error ocurre porque los ejecutables de Windows pueden usar diferentes [subsistemas][windows-subsystems]. En programas normales, se infieren dependiendo del nombre del punto de entrada: si el punto de entrada se llama `main`, se usa el subsistema `CONSOLE`, y si el punto de entrada se llama `WinMain`, se usa el subsistema `WINDOWS`. Dado que nuestra función `_start` tiene un nombre diferente, necesitamos especificar el subsistema explícitamente:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
||||
```
|
||||
|
||||
Aquí usamos el subsistema `CONSOLE`, pero el subsistema `WINDOWS` también funcionaría. En lugar de pasar `-C link-arg` varias veces, podemos usar `-C link-args`, que acepta una lista de argumentos separados por espacios.
|
||||
|
||||
Con este comando, nuestro ejecutable debería compilarse exitosamente en Windows.
|
||||
|
||||
#### macOS
|
||||
|
||||
En macOS, ocurre el siguiente error del enlazador (resumido):
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: entry point (_main) undefined. for architecture x86_64
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
Este mensaje de error nos indica que el enlazador no puede encontrar una función de punto de entrada con el nombre predeterminado `main` (por alguna razón, en macOS todas las funciones tienen un prefijo `_`). Para establecer el punto de entrada en nuestra función `_start`, pasamos el argumento del enlazador `-e`:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start"
|
||||
```
|
||||
|
||||
La bandera `-e` especifica el nombre de la función de punto de entrada. Dado que en macOS todas las funciones tienen un prefijo adicional `_`, necesitamos establecer el punto de entrada en `__start` en lugar de `_start`.
|
||||
|
||||
Ahora ocurre el siguiente error del enlazador:
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: dynamic main executables must link with libSystem.dylib
|
||||
for architecture x86_64
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
macOS [no admite oficialmente binarios enlazados estáticamente] y requiere que los programas enlacen la biblioteca `libSystem` por defecto. Para anular esto y enlazar un binario estático, se pasa la bandera `-static` al enlazador:
|
||||
|
||||
[no admite oficialmente binarios enlazados estáticamente]: https://developer.apple.com/library/archive/qa/qa1118/_index.html
|
||||
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start -static"
|
||||
```
|
||||
|
||||
Esto aún no es suficiente, ya que ocurre un tercer error del enlazador:
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: library not found for -lcrt0.o
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
Este error ocurre porque los programas en macOS enlazan con `crt0` (“C runtime zero”) por defecto. Esto es similar al error que tuvimos en Linux y también se puede resolver añadiendo el argumento del enlazador `-nostartfiles`:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||
```
|
||||
|
||||
Ahora nuestro programa debería compilarse exitosamente en macOS.
|
||||
|
||||
#### Unificando los Comandos de Construcción
|
||||
|
||||
Actualmente, tenemos diferentes comandos de construcción dependiendo de la plataforma host, lo cual no es ideal. Para evitar esto, podemos crear un archivo llamado `.cargo/config.toml` que contenga los argumentos específicos de cada plataforma:
|
||||
|
||||
```toml
|
||||
# en .cargo/config.toml
|
||||
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-nostartfiles"]
|
||||
|
||||
[target.'cfg(target_os = "windows")']
|
||||
rustflags = ["-C", "link-args=/ENTRY:_start /SUBSYSTEM:console"]
|
||||
|
||||
[target.'cfg(target_os = "macos")']
|
||||
rustflags = ["-C", "link-args=-e __start -static -nostartfiles"]
|
||||
```
|
||||
|
||||
La clave `rustflags` contiene argumentos que se añaden automáticamente a cada invocación de `rustc`. Para más información sobre el archivo `.cargo/config.toml`, consulta la [documentación oficial](https://doc.rust-lang.org/cargo/reference/config.html).
|
||||
|
||||
Ahora nuestro programa debería poder construirse en las tres plataformas con un simple `cargo build`.
|
||||
|
||||
#### ¿Deberías Hacer Esto?
|
||||
|
||||
Aunque es posible construir un ejecutable autónomo para Linux, Windows y macOS, probablemente no sea una buena idea. La razón es que nuestro ejecutable aún espera varias cosas, por ejemplo, que una pila esté inicializada cuando se llama a la función `_start`. Sin el tiempo de ejecución de C, algunos de estos requisitos podrían no cumplirse, lo que podría hacer que nuestro programa falle, por ejemplo, con un error de segmentación.
|
||||
|
||||
Si deseas crear un binario mínimo que se ejecute sobre un sistema operativo existente, incluir `libc` y configurar el atributo `#[start]` como se describe [aquí](https://doc.rust-lang.org/1.16.0/book/no-stdlib.html) probablemente sea una mejor idea.
|
||||
|
||||
</details>
|
||||
|
||||
## Resumen {#resumen}
|
||||
|
||||
Un binario mínimo autónomo en Rust se ve así:
|
||||
|
||||
`src/main.rs`:
|
||||
|
||||
```rust
|
||||
#![no_std] // no enlazar con la biblioteca estándar de Rust
|
||||
#![no_main] // deshabilitar todos los puntos de entrada a nivel de Rust
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
#[no_mangle] // no modificar el nombre de esta función
|
||||
pub extern "C" fn _start() -> ! {
|
||||
// esta función es el punto de entrada, ya que el enlazador busca una función
|
||||
// llamada `_start` por defecto
|
||||
loop {}
|
||||
}
|
||||
|
||||
/// Esta función se llama cuando ocurre un pánico.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
`Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "crate_name"
|
||||
version = "0.1.0"
|
||||
authors = ["Author Name <author@example.com>"]
|
||||
|
||||
# el perfil usado para `cargo build`
|
||||
[profile.dev]
|
||||
panic = "abort" # deshabilitar el desenrollado de la pila en caso de pánico
|
||||
|
||||
# el perfil usado para `cargo build --release`
|
||||
[profile.release]
|
||||
panic = "abort" # deshabilitar el desenrollado de la pila en caso de pánico
|
||||
```
|
||||
|
||||
Para construir este binario, necesitamos compilar para un destino bare metal, como `thumbv7em-none-eabihf`:
|
||||
|
||||
```
|
||||
cargo build --target thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
Alternativamente, podemos compilarlo para el sistema host pasando argumentos adicionales al enlazador:
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
cargo rustc -- -C link-arg=-nostartfiles
|
||||
# Windows
|
||||
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
||||
# macOS
|
||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||
```
|
||||
|
||||
Ten en cuenta que este es solo un ejemplo mínimo de un binario autónomo en Rust. Este binario espera varias cosas, por ejemplo, que una pila esté inicializada cuando se llama a la función `_start`. **Por lo tanto, para cualquier uso real de un binario como este, se requieren más pasos**.
|
||||
|
||||
## ¿Qué sigue?
|
||||
|
||||
La [próxima publicación][next post] explica los pasos necesarios para convertir nuestro binario autónomo en un kernel de sistema operativo mínimo. Esto incluye crear un destino personalizado, combinar nuestro ejecutable con un bootloader y aprender cómo imprimir algo en la pantalla.
|
||||
|
||||
[next post]: @/edition-2/posts/02-minimal-rust-kernel/index.md
|
||||
@@ -0,0 +1,526 @@
|
||||
+++
|
||||
title = " یک باینری مستقل Rust"
|
||||
weight = 1
|
||||
path = "fa/freestanding-rust-binary"
|
||||
date = 2018-02-10
|
||||
|
||||
[extra]
|
||||
# Please update this when updating the translation
|
||||
translation_based_on_commit = "80136cc0474ae8d2da04f391b5281cfcda068c1a"
|
||||
# GitHub usernames of the people that translated this post
|
||||
translators = ["hamidrezakp", "MHBahrampour"]
|
||||
rtl = true
|
||||
+++
|
||||
|
||||
اولین قدم برای نوشتن سیستمعامل، ساخت یک باینری راست (کلمه: Rust) هست که به کتابخانه استاندارد نیازمند نباشد. این باعث میشود تا بتوانیم کد راست را بدون سیستمعامل زیرین، بر روی سخت افزار [bare metal] اجرا کنیم.
|
||||
|
||||
[bare metal]: https://en.wikipedia.org/wiki/Bare_machine
|
||||
|
||||
<!-- more -->
|
||||
|
||||
این بلاگ بصورت آزاد بر روی [گیتهاب] توسعه داده شده. اگر مشکل یا سوالی دارید، لطفاً آنجا یک ایشو باز کنید. همچنین میتوانید [در زیر] این پست کامنت بگذارید. سورس کد کامل این پست را میتوانید در بِرَنچ [`post-01`][post branch] پیدا کنید.
|
||||
|
||||
[گیتهاب]: https://github.com/phil-opp/blog_os
|
||||
[در زیر]: #comments
|
||||
<!-- fix for zola anchor checker (target is in template): <a id="comments"> -->
|
||||
[post branch]: https://github.com/phil-opp/blog_os/tree/post-01
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## مقدمه
|
||||
برای نوشتن هسته سیستمعامل، ما به کدی نیاز داریم که به هیچ یک از ویژگیهای سیستمعامل نیازی نداشته باشد. یعنی نمیتوانیم از نخها (ترجمه: Threads)، فایلها، حافظه هیپ (کلمه: Heap)، شبکه، اعداد تصادفی، ورودی استاندارد، یا هر ویژگی دیگری که نیاز به انتزاعات سیستمعامل یا سختافزار خاصی داشته، استفاده کنیم. منطقی هم به نظر میرسد، چون ما سعی داریم سیستمعامل و درایورهای خودمان را بنویسیم.
|
||||
|
||||
نداشتن انتزاعات سیستمعامل به این معنی هست که نمیتوانیم از بخش زیادی از [کتابخانه استاندارد راست] استفاده کنیم، اما هنوز بسیاری از ویژگیهای راست هستند که میتوانیم از آنها استفاده کنیم. به عنوان مثال، میتوانیم از [iterator] ها، [closure] ها، [pattern matching]، [option]، [result]، [string formatting] و البته [سیستم ownership] استفاده کنیم. این ویژگیها به ما امکان نوشتن هسته به طور رسا، سطح بالا و بدون نگرانی درباره [رفتار تعریف نشده] و [امنیت حافظه] را میدهند.
|
||||
|
||||
[option]: https://doc.rust-lang.org/core/option/
|
||||
[result]:https://doc.rust-lang.org/core/result/
|
||||
[کتابخانه استاندارد راست]: https://doc.rust-lang.org/std/
|
||||
[iterators]: https://doc.rust-lang.org/book/ch13-02-iterators.html
|
||||
[closures]: https://doc.rust-lang.org/book/ch13-01-closures.html
|
||||
[pattern matching]: https://doc.rust-lang.org/book/ch06-00-enums.html
|
||||
[string formatting]: https://doc.rust-lang.org/core/macro.write.html
|
||||
[سیستم ownership]: https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html
|
||||
[رفتار تعریف نشده]: https://www.nayuki.io/page/undefined-behavior-in-c-and-cplusplus-programs
|
||||
[امنیت حافظه]: https://tonyarcieri.com/it-s-time-for-a-memory-safety-intervention
|
||||
|
||||
برای ساختن یک هسته سیستمعامل به زبان راست، باید فایل اجراییای بسازیم که بتواند بدون سیستمعامل زیرین اجرا بشود. چنین فایل اجرایی، فایل اجرایی مستقل (ترجمه: freestanding) یا فایل اجرایی “bare-metal” نامیده میشود.
|
||||
|
||||
این پست قدمهای لازم برای ساخت یک باینری مستقل راست و اینکه چرا این قدمها نیاز هستند را توضیح میدهد. اگر علاقهایی به خواندن کل توضیحات ندارید، میتوانید **[به قسمت خلاصه مراجعه کنید](#summary)**.
|
||||
|
||||
## غیر فعال کردن کتابخانه استاندارد
|
||||
به طور پیشفرض تمام کِرِیتهای راست، از [کتابخانه استاندارد] استفاده میکنند(لینک به آن دارند)، که به سیستمعامل برای قابلیتهایی مثل نخها، فایلها یا شبکه وابستگی دارد. همچنین به کتابخانه استاندارد زبان سی، `libc` هم وابسطه هست که با سرویسهای سیستمعامل تعامل نزدیکی دارند. از آنجا که قصد داریم یک سیستمعامل بنویسیم، نمیتوانیم از هیچ کتابخانهایی که به سیستمعامل نیاز داشته باشد استفاده کنیم. بنابراین باید اضافه شدن خودکار کتابخانه استاندارد را از طریق [خاصیت `no_std`] غیر فعال کنیم.
|
||||
|
||||
[کتابخانه استاندارد]: https://doc.rust-lang.org/std/
|
||||
[خاصیت `no_std`]: https://doc.rust-lang.org/1.30.0/book/first-edition/using-rust-without-the-standard-library.html
|
||||
|
||||
|
||||
با ساخت یک اپلیکیشن جدید کارگو شروع میکنیم. سادهترین راه برای انجام این کار از طریق خط فرمان است:
|
||||
|
||||
```
|
||||
cargo new blog_os --bin --edition 2018
|
||||
```
|
||||
|
||||
نام پروژه را `blog_os` گذاشتم، اما شما میتوانید نام دلخواه خود را انتخاب کنید. پرچمِ (ترجمه: Flag) `bin--` مشخص میکند که ما میخواهیم یک فایل اجرایی ایجاد کنیم (به جای یک کتابخانه) و پرچمِ `edition 2018--` مشخص میکند که میخواهیم از [ویرایش 2018] زبان راست برای کریت خود استفاده کنیم. وقتی دستور را اجرا میکنیم، کارگو ساختار پوشههای زیر را برای ما ایجاد میکند:
|
||||
|
||||
[ویرایش 2018]: https://doc.rust-lang.org/nightly/edition-guide/rust-2018/index.html
|
||||
|
||||
```
|
||||
blog_os
|
||||
├── Cargo.toml
|
||||
└── src
|
||||
└── main.rs
|
||||
```
|
||||
|
||||
فایل `Cargo.toml` شامل تنظیمات کریت میباشد، به عنوان مثال نام کریت، نام نویسنده، شماره [نسخه سمنتیک] و وابستگیها. فایل `src/main.rs` شامل ماژول ریشه برای کریت ما و تابع `main` است. میتوانید کریت خود را با دستور `cargo build` کامپایل کنید و سپس باینری کامپایل شده `blog_os` را در زیرپوشه `target/debug` اجرا کنید.
|
||||
|
||||
[نسخه سمنتیک]: https://semver.org/
|
||||
|
||||
### خاصیت `no_std`
|
||||
|
||||
در حال حاظر کریت ما بطور ضمنی به کتابخانه استاندارد لینک دارد. بیایید تا سعی کنیم آن را با اضافه کردن [خاصیت `no_std`] غیر فعال کنیم:
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
حالا وقتی سعی میکنیم تا بیلد کنیم (با اجرای دستور `cargo build`)، خطای زیر رخ میدهد:
|
||||
|
||||
```
|
||||
error: cannot find macro `println!` in this scope
|
||||
--> src/main.rs:4:5
|
||||
|
|
||||
4 | println!("Hello, world!");
|
||||
| ^^^^^^^
|
||||
```
|
||||
|
||||
دلیل این خطا این هست که [ماکروی `println`]\(ترجمه: macro) جزوی از کتابخانه استاندارد است، که ما دیگر آن را نداریم. بنابراین نمیتوانیم چیزی را چاپ کنیم. منطقی هست زیرا `println` در [خروجی استاندارد] مینویسد، که یک توصیف کننده فایل (ترجمه: File Descriptor) خاص است که توسط سیستمعامل ارائه میشود.
|
||||
|
||||
[ماکروی `println`]: https://doc.rust-lang.org/std/macro.println.html
|
||||
[خروجی استاندارد]: https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29
|
||||
|
||||
پس بیایید قسمت مروبط به چاپ را پاک کرده و این بار با یک تابع main خالی امتحان کنیم:
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
fn main() {}
|
||||
```
|
||||
|
||||
```
|
||||
> cargo build
|
||||
error: `#[panic_handler]` function required, but not found
|
||||
error: language item required, but not found: `eh_personality`
|
||||
```
|
||||
|
||||
حالا کامپایلر با کمبود یک تابع `#[panic_handler]` و یک _language item_ روبرو است.
|
||||
|
||||
## پیادهسازی پنیک (کلمه: Panic)
|
||||
|
||||
خاصیت `panic_handler` تابعی را تعریف میکند که کامپایلر باید در هنگام رخ دادن یک [پنیک] اجرا کند. کتابخانه استاندارد تابع مدیریت پنیک خود را ارائه میدهد، اما در یک محیط `no_std` ما باید خودمان آن را تعریف کنیم.
|
||||
|
||||
[پنیک]: https://doc.rust-lang.org/stable/book/ch09-01-unrecoverable-errors-with-panic.html
|
||||
|
||||
```rust
|
||||
// in main.rs
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
/// This function is called on panic.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
[پارامتر `PanicInfo`][PanicInfo] شامل فایل و شماره خطی که پنیک رخ داده و پیام پنیکِ اختیاری میباشد. تابع هیچ وقت نباید چیزی را برگرداند به همین دلیل به عنوان یک [تابع واگرا]\(ترجمه: diverging function) بوسیله نوع برگشتی `!` [نوع ”هرگز“] علامتگذاری شده است. فعلا کار زیادی نیست که بتوانیم در این تابع انجام دهیم، بنابراین فقط یک حلقه بینهایت مینویسیم.
|
||||
|
||||
[PanicInfo]: https://doc.rust-lang.org/nightly/core/panic/struct.PanicInfo.html
|
||||
[تابع واگرا]: https://doc.rust-lang.org/1.30.0/book/first-edition/functions.html#diverging-functions
|
||||
[نوع ”هرگز“]: https://doc.rust-lang.org/nightly/std/primitive.never.html
|
||||
|
||||
## آیتم زبان `eh_personality`
|
||||
|
||||
آیتمهای زبان، توابع و انواع خاصی هستند که برای استفاده درون کامپایلر ضروریاند. به عنوان مثال، تِرِیت [`Copy`]\(کلمه: Trait) یک آیتم زبان است که به کامپایلر میگوید کدام انواع دارای [_مفهوم کپی_][`Copy`] هستند. وقتی به [پیادهسازی][copy code] آن نگاه میکنیم، میبینیم که یک خاصیت ویژه `#[lang = "copy"]` دارد که آن را به عنوان یک آیتم زبان تعریف میکند.
|
||||
|
||||
[`Copy`]: https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html
|
||||
[copy code]: https://github.com/rust-lang/rust/blob/485397e49a02a3b7ff77c17e4a3f16c653925cb3/src/libcore/marker.rs#L296-L299
|
||||
|
||||
درحالی که میتوان پیادهسازی خاص برای آیتمهای زبان فراهم کرد، فقط باید به عنوان آخرین راه حل از آن استفاده کرد. زیرا آیتمهای زبان بسیار در جزئیات پیادهسازی ناپایدار هستند و حتی انواع آنها نیز چک نمیشود (بنابراین کامپایلر حتی چک نمیکند که آرگومان تابع نوع درست را دارد). خوشبختانه یک راه پایدارتر برای حل مشکل آیتم زبان بالا وجود دارد.
|
||||
|
||||
[آیتم زبان `eh_personality`] یک تابع را به عنوان تابعی که برای پیادهسازی [بازکردن پشته (Stack Unwinding)] استفاده شده، علامتگذاری میکند. راست بطور پیشفرض از _بازکردن_ (ترجمه: unwinding) برای اجرای نابودگرهای (ترجمه: Destructors) تمام متغیرهای زنده درون استک در مواقع [پنیک] استفاده میکند. این تضمین میکند که تمام حافظه استفاده شده آزاد میشود و به نخ اصلی اجازه میدهد پنیک را دریافت کرده و اجرا را ادامه دهد. باز کردن، یک فرآیند پیچیده است و به برخی از کتابخانههای خاص سیستمعامل (به عنوان مثال [libunwind] در لینوکس یا [مدیریت اکسپشن ساخت یافته] در ویندوز) نیاز دارد، بنابراین ما نمیخواهیم از آن برای سیستمعامل خود استفاده کنیم.
|
||||
|
||||
[آیتم زبان `eh_personality`]: https://github.com/rust-lang/rust/blob/edb368491551a77d77a48446d4ee88b35490c565/src/libpanic_unwind/gcc.rs#L11-L45
|
||||
[بازکردن پشته (Stack Unwinding)]: https://www.bogotobogo.com/cplusplus/stackunwinding.php
|
||||
[libunwind]: https://www.nongnu.org/libunwind/
|
||||
[مدیریت اکسپشن ساخت یافته]: https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling
|
||||
|
||||
### غیرفعال کردن Unwinding
|
||||
|
||||
موارد استفاده دیگری نیز وجود دارد که باز کردن نامطلوب است، بنابراین راست به جای آن گزینه [قطع در پنیک] را فراهم میکند. این امر تولید اطلاعات نمادها (ترجمه: Symbol) را از بین میبرد و بنابراین اندازه باینری را بطور قابل توجهی کاهش میدهد. چندین مکان وجود دارد که می توانیم باز کردن را غیرفعال کنیم. سادهترین راه این است که خطوط زیر را به `Cargo.toml` اضافه کنید:
|
||||
|
||||
```toml
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
```
|
||||
|
||||
این استراتژی پنیک را برای دو پروفایل `dev` (در `cargo build` استفاده میشود) و پروفایل `release` (در ` cargo build --release` استفاده میشود) تنظیم میکند. اکنون آیتم زبان `eh_personality` نباید دیگر لازم باشد.
|
||||
|
||||
[قطع در پنیک]: https://github.com/rust-lang/rust/pull/32900
|
||||
|
||||
اکنون هر دو خطای فوق را برطرف کردیم. با این حال، اگر اکنون بخواهیم آن را کامپایل کنیم، خطای دیگری رخ میدهد:
|
||||
|
||||
```
|
||||
> cargo build
|
||||
error: requires `start` lang_item
|
||||
```
|
||||
|
||||
برنامه ما آیتم زبان `start` که نقطه ورود را مشخص میکند، را ندارد.
|
||||
|
||||
## خاصیت `start`
|
||||
|
||||
ممکن است تصور شود که تابع `main` اولین تابعی است که هنگام اجرای یک برنامه فراخوانی میشود. با این حال، بیشتر زبانها دارای [سیستم رانتایم] هستند که مسئول مواردی مانند جمع آوری زباله (به عنوان مثال در جاوا) یا نخهای نرمافزار (به عنوان مثال goroutines در Go) است. این رانتایم باید قبل از `main` فراخوانی شود، زیرا باید خود را مقداردهی اولیه و آماده کند.
|
||||
|
||||
[سیستم رانتایم]: https://en.wikipedia.org/wiki/Runtime_system
|
||||
|
||||
در یک باینری معمولی راست که از کتابخانه استاندارد استفاده میکند، اجرا در یک کتابخانه رانتایم C به نام `crt0` ("زمان اجرا صفر C") شروع میشود، که محیط را برای یک برنامه C تنظیم میکند. این شامل ایجاد یک پشته و قرار دادن آرگومانها در رجیسترهای مناسب است. سپس رانتایم C [ورودی رانتایم راست][rt::lang_start] را فراخوانی میکند، که با آیتم زبان `start` مشخص شده است. راست فقط یک رانتایم بسیار کوچک دارد، که مواظب برخی از کارهای کوچک مانند راهاندازی محافظهای سرریز پشته یا چاپ backtrace با پنیک میباشد. رانتایم در نهایت تابع `main` را فراخوانی میکند.
|
||||
|
||||
[rt::lang_start]: https://github.com/rust-lang/rust/blob/bb4d1491466d8239a7a5fd68bd605e3276e97afb/src/libstd/rt.rs#L32-L73
|
||||
|
||||
برنامه اجرایی مستقل ما به رانتایم و `crt0` دسترسی ندارد، بنابراین باید نقطه ورود را مشخص کنیم. پیادهسازی آیتم زبان `start` کمکی نخواهد کرد، زیرا همچنان به `crt0` نیاز دارد. در عوض، باید نقطه ورود `crt0` را مستقیماً بازنویسی کنیم.
|
||||
|
||||
### بازنویسی نقطه ورود
|
||||
|
||||
برای اینکه به کامپایلر راست بگوییم که نمیخواهیم از زنجیره نقطه ورودی عادی استفاده کنیم، ویژگی `#![no_main]` را اضافه میکنیم.
|
||||
|
||||
```rust
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
/// This function is called on panic.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
ممکن است متوجه شده باشید که ما تابع `main` را حذف کردیم. دلیل این امر این است که `main` بدون یک رانتایم اساسی که آن را صدا کند معنی ندارد. در عوض، ما در حال بازنویسی نقطه ورود سیستمعامل با تابع `start_` خود هستیم:
|
||||
|
||||
```rust
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _start() -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
با استفاده از ویژگی `[no_mangle]#` ما [name mangling] را غیرفعال می کنیم تا اطمینان حاصل کنیم که کامپایلر راست تابعی با نام `start_` را خروجی میدهد. بدون این ویژگی، کامپایلر برخی از نمادهای رمزنگاری شده `ZN3blog_os4_start7hb173fedf945531caE_` را تولید میکند تا به هر تابع یک نام منحصر به فرد بدهد. این ویژگی لازم است زیرا در مرحله بعدی باید نام تایع نقطه ورود را به لینکر (کلمه: linker) بگوییم.
|
||||
|
||||
ما همچنین باید تابع را به عنوان `"extern "C` علامتگذاری کنیم تا به کامپایلر بگوییم که باید از [قرارداد فراخوانی C] برای این تابع استفاده کند (به جای قرارداد مشخص نشده فراخوانی راست). دلیل نامگذاری تابع `start_` این است که این نام نقطه پیشفرض ورودی برای اکثر سیستمها است.
|
||||
|
||||
[name mangling]: https://en.wikipedia.org/wiki/Name_mangling
|
||||
[قرارداد فراخوانی C]: https://en.wikipedia.org/wiki/Calling_convention
|
||||
|
||||
نوع بازگشت `!` به این معنی است که تایع واگرا است، یعنی اجازه بازگشت ندارد. این مورد لازم است زیرا نقطه ورود توسط هیچ تابعی فراخوانی نمیشود، بلکه مستقیماً توسط سیستمعامل یا بوتلودر فراخوانی میشود. بنابراین به جای بازگشت، نقطه ورود باید به عنوان مثال [فراخوان سیستمی `exit`] از سیستمعامل را فراخوانی کند. در مورد ما، خاموش کردن دستگاه میتواند اقدامی منطقی باشد، زیرا در صورت بازگشت باینری مستقل دیگر کاری برای انجام دادن وجود ندارد. در حال حاضر، ما این نیاز را با حلقههای بیپایان انجام میدهیم.
|
||||
|
||||
[فراخوان سیستمی `exit`]: https://en.wikipedia.org/wiki/Exit_(system_call)
|
||||
|
||||
حالا وقتی `cargo build` را اجرا میکنیم، با یک خطای _لینکر_ زشت مواجه میشویم.
|
||||
|
||||
## خطاهای لینکر (Linker)
|
||||
|
||||
لینکر برنامهای است که کد تولید شده را ترکیب کرده و یک فایل اجرایی میسازد. از آنجا که فرمت اجرایی بین لینوکس، ویندوز و macOS متفاوت است، هر سیستم لینکر خود را دارد که خطای متفاوتی ایجاد میکند. علت اصلی خطاها یکسان است: پیکربندی پیشفرض لینکر فرض میکند که برنامه ما به رانتایم C وابسته است، که این طور نیست.
|
||||
|
||||
برای حل خطاها، باید به لینکر بگوییم که نباید رانتایم C را اضافه کند. ما میتوانیم این کار را با اضافه کردن مجموعهای از آرگمانها به لینکر یا با ساختن یک هدف (ترجمه: Target) bare metal انجام دهیم.
|
||||
|
||||
### بیلد کردن برای یک هدف bare metal
|
||||
|
||||
راست به طور پیشفرض سعی در ایجاد یک اجرایی دارد که بتواند در محیط سیستم فعلی شما اجرا شود. به عنوان مثال، اگر از ویندوز در `x86_64` استفاده میکنید، راست سعی در ایجاد یک `exe.` اجرایی ویندوز دارد که از دستورالعملهای `x86_64` استفاده میکند. به این محیط سیستم "میزبان" شما گفته میشود.
|
||||
|
||||
راست برای توصیف محیطهای مختلف، از رشتهای به نام [_target triple_]\(سهگانه هدف) استفاده میکند. با اجرای `rustc --version --verbose` میتوانید target triple را برای سیستم میزبان خود مشاهده کنید:
|
||||
|
||||
[_target triple_]: https://clang.llvm.org/docs/CrossCompilation.html#target-triple
|
||||
|
||||
```
|
||||
rustc 1.35.0-nightly (474e7a648 2019-04-07)
|
||||
binary: rustc
|
||||
commit-hash: 474e7a6486758ea6fc761893b1a49cd9076fb0ab
|
||||
commit-date: 2019-04-07
|
||||
host: x86_64-unknown-linux-gnu
|
||||
release: 1.35.0-nightly
|
||||
LLVM version: 8.0
|
||||
```
|
||||
|
||||
خروجی فوق از یک سیستم لینوکس `x86_64` است. میبینیم که سهگانه میزبان `x86_64-unknown-linux-gnu` است که شامل معماری پردازنده (`x86_64`)، فروشنده (`ناشناخته`)، سیستمعامل (` linux`) و [ABI] (`gnu`) است.
|
||||
|
||||
[ABI]: https://en.wikipedia.org/wiki/Application_binary_interface
|
||||
|
||||
با کامپایل کردن برای سهگانه میزبانمان، کامپایلر راست و لینکر فرض میکنند که یک سیستمعامل زیرین مانند Linux یا Windows وجود دارد که به طور پیشفرض از رانتایم C استفاده میکند، که باعث خطاهای لینکر میشود. بنابراین برای جلوگیری از خطاهای لینکر، میتوانیم برای محیطی متفاوت و بدون سیستمعامل زیرین کامپایل کنیم.
|
||||
|
||||
یک مثال برای چنین محیطِ bare metal ی، سهگانه هدف `thumbv7em-none-eabihf` است، که یک سیستم [تعبیه شده][ARM] را توصیف میکند. جزئیات مهم نیستند، مهم این است که سهگانه هدف فاقد سیستمعامل زیرین باشد، که با `none` در سهگانه هدف نشان داده میشود. برای این که بتوانیم برای این هدف کامپایل کنیم، باید آن را به rustup اضافه کنیم:
|
||||
|
||||
[تعبیه شده]: https://en.wikipedia.org/wiki/Embedded_system
|
||||
[ARM]: https://en.wikipedia.org/wiki/ARM_architecture
|
||||
|
||||
```
|
||||
rustup target add thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
با این کار نسخهای از کتابخانه استاندارد (و core) برای سیستم بارگیری میشود. اکنون میتوانیم برای این هدف اجرایی مستقل خود را بسازیم:
|
||||
|
||||
```
|
||||
cargo build --target thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
با استفاده از یک آرگومان `target--`، ما اجرایی خود را برای یک سیستم هدف bare metal [کراس کامپایل] میکنیم. از آنجا که سیستم هدف فاقد سیستمعامل است، لینکر سعی نمیکند رانتایم C را به آن پیوند دهد و بیلد ما بدون هیچ گونه خطای لینکر با موفقیت انجام میشود.
|
||||
|
||||
[کراس کامپایل]: https://en.wikipedia.org/wiki/Cross_compiler
|
||||
|
||||
این روشی است که ما برای ساخت هسته سیستمعامل خود استفاده خواهیم کرد. به جای `thumbv7em-none-eabihf`، ما از یک [هدف سفارشی] استفاده خواهیم کرد که یک محیط `x86_64` bare metal را توصیف میکند. جزئیات در پست بعدی توضیح داده خواهد شد.
|
||||
|
||||
[هدف سفارشی]: https://doc.rust-lang.org/rustc/targets/custom.html
|
||||
|
||||
### آرگومانهای لینکر
|
||||
|
||||
به جای کامپایل کردن برای یک سیستم bare metal، میتوان خطاهای لینکر را با استفاده از مجموعه خاصی از آرگومانها به لینکر حل کرد. این روشی نیست که ما برای هسته خود استفاده کنیم، بنابراین این بخش اختیاری است و فقط برای کامل بودن ارائه میشود. برای نشان دادن محتوای اختیاری، روی _"آرگومانهای لینکر"_ در زیر کلیک کنید.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>آرگومانهای لینکر</summary>
|
||||
|
||||
در این بخش، ما در مورد خطاهای لینکر که در لینوکس، ویندوز و macOS رخ میدهد بحث میکنیم و نحوه حل آنها را با استفاده از آرگومانهای اضافی به لینکر توضیح میدهیم. توجه داشته باشید که فرمت اجرایی و لینکر بین سیستمعاملها متفاوت است، بنابراین برای هر سیستم مجموعهای متفاوت از آرگومانها مورد نیاز است.
|
||||
|
||||
#### لینوکس
|
||||
|
||||
در لینوکس خطای لینکر زیر رخ میدهد (کوتاه شده):
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: /usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x12): undefined reference to `__libc_csu_fini'
|
||||
/usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x19): undefined reference to `__libc_csu_init'
|
||||
/usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x25): undefined reference to `__libc_start_main'
|
||||
collect2: error: ld returned 1 exit status
|
||||
```
|
||||
|
||||
مشکل این است که لینکر به طور پیشفرض شامل روال راهاندازی رانتایم C است که به آن `start_` نیز گفته میشود. به برخی از نمادهای کتابخانه استاندارد C یعنی `libc` نیاز دارد که به دلیل ویژگی`no_std` آنها را نداریم، بنابراین لینکر نمیتواند این مراجع را پیدا کند. برای حل این مسئله، با استفاده از پرچم `nostartfiles-` میتوانیم به لینکر بگوییم که نباید روال راهاندازی C را لینک دهد.
|
||||
|
||||
یکی از راههای عبور صفات لینکر از طریق cargo، دستور `cargo rustc` است. این دستور دقیقاً مانند `cargo build` رفتار میکند، اما اجازه میدهد گزینهها را به `rustc`، کامپایلر اصلی راست انتقال دهید. `rustc` دارای پرچم`C link-arg-` است که آرگومان را به لینکر منتقل میکند. با ترکیب همه اینها، دستور بیلد جدید ما به این شکل است:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-arg=-nostartfiles
|
||||
```
|
||||
|
||||
اکنون کریت ما بصورت اجرایی مستقل در لینوکس ساخته میشود!
|
||||
|
||||
لازم نیست که صریحاً نام تابع نقطه ورود را مشخص کنیم، زیرا لینکر به طور پیشفرض به دنبال تابعی با نام `start_` میگردد.
|
||||
|
||||
#### ویندوز
|
||||
|
||||
در ویندوز، یک خطای لینکر متفاوت رخ میدهد (کوتاه شده):
|
||||
|
||||
```
|
||||
error: linking with `link.exe` failed: exit code: 1561
|
||||
|
|
||||
= note: "C:\\Program Files (x86)\\…\\link.exe" […]
|
||||
= note: LINK : fatal error LNK1561: entry point must be defined
|
||||
```
|
||||
|
||||
خطای "entry point must be defined" به این معنی است که لینکر نمیتواند نقطه ورود را پیدا کند. در ویندوز، نام پیشفرض نقطه ورود [بستگی به زیر سیستم استفاده شده دارد] [windows-subsystem]. برای زیر سیستم `CONSOLE` لینکر به دنبال تابعی به نام `mainCRTStartup` و برای زیر سیستم `WINDOWS` به دنبال تابعی به نام `WinMainCRTStartup` میگردد. برای بازنویسی این پیشفرض و به لینکر گفتن که در عوض به دنبال تابع `_start` ما باشد ، می توانیم یک آرگومان `ENTRY/` را به لینکر ارسال کنیم:
|
||||
|
||||
[windows-subsystems]: https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-arg=/ENTRY:_start
|
||||
```
|
||||
|
||||
از متفاوت بودن فرمت آرگومان، به وضوح میفهمیم که لینکر ویندوز یک برنامه کاملاً متفاوت از لینکر Linux است.
|
||||
|
||||
اکنون یک خطای لینکر متفاوت رخ داده است:
|
||||
|
||||
```
|
||||
error: linking with `link.exe` failed: exit code: 1221
|
||||
|
|
||||
= note: "C:\\Program Files (x86)\\…\\link.exe" […]
|
||||
= note: LINK : fatal error LNK1221: a subsystem can't be inferred and must be
|
||||
defined
|
||||
```
|
||||
|
||||
این خطا به این دلیل رخ میدهد که برنامههای اجرایی ویندوز میتوانند از [زیر سیستم های][windows-subsystems] مختلف استفاده کنند. برای برنامههای عادی، بسته به نام نقطه ورود استنباط می شوند: اگر نقطه ورود `main` نامگذاری شود، از زیر سیستم `CONSOLE` و اگر نقطه ورود `WinMain` نامگذاری شود، از زیر سیستم `WINDOWS` استفاده میشود. از آنجا که تابع `start_` ما نام دیگری دارد، باید زیر سیستم را صریحاً مشخص کنیم:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
||||
```
|
||||
|
||||
ما در اینجا از زیر سیستم `CONSOLE` استفاده میکنیم، اما زیر سیستم `WINDOWS` نیز کار خواهد کرد. به جای اینکه چند بار از `C link-arg-` استفاده کنیم، از`C link-args-` استفاده میکنیم که لیستی از آرگومانها به صورت جدا شده با فاصله را دریافت میکند.
|
||||
|
||||
با استفاده از این دستور، اجرایی ما باید با موفقیت بر روی ویندوز ساخته شود.
|
||||
|
||||
#### macOS
|
||||
|
||||
در macOS، خطای لینکر زیر رخ میدهد (کوتاه شده):
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: entry point (_main) undefined. for architecture x86_64
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
این پیام خطا به ما میگوید که لینکر نمیتواند یک تابع نقطه ورود را با نام پیشفرض `main` پیدا کند (به دلایلی همه توابع در macOS دارای پیشوند `_` هستند). برای تنظیم نقطه ورود به تابع `start_` ، آرگومان لینکر `e-` را استفاده میکنیم:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start"
|
||||
```
|
||||
|
||||
پرچم `e-` نام تابع نقطه ورود را مشخص میکند. از آنجا که همه توابع در macOS دارای یک پیشوند اضافی `_` هستند، ما باید به جای `start_` نقطه ورود را روی `start__` تنظیم کنیم.
|
||||
|
||||
اکنون خطای لینکر زیر رخ میدهد:
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: dynamic main executables must link with libSystem.dylib
|
||||
for architecture x86_64
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
سیستمعامل مک [رسماً باینریهایی را که بطور استاتیک با هم پیوند دارند پشتیبانی نمیکند] و بطور پیشفرض به برنامههایی برای پیوند دادن کتابخانه `libSystem` نیاز دارد. برای تغییر این حالت و پیوند دادن یک باینری استاتیک، پرچم `static-` را به لینکر ارسال میکنیم:
|
||||
|
||||
[باینریهایی را که بطور استاتیک با هم پیوند دارند پشتیبانی نمیکند]: https://developer.apple.com/library/archive/qa/qa1118/_index.html
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start -static"
|
||||
```
|
||||
|
||||
این نیز کافی نیست، سومین خطای لینکر رخ میدهد:
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: library not found for -lcrt0.o
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
این خطا رخ میدهد زیرا برنامه های موجود در macOS به طور پیشفرض به `crt0` ("رانتایم صفر C") پیوند دارند. این همان خطایی است که در لینوکس داشتیم و با افزودن آرگومان لینکر `nostartfiles-` نیز قابل حل است:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||
```
|
||||
|
||||
اکنون برنامه ما باید با موفقیت بر روی macOS ساخته شود.
|
||||
|
||||
#### متحد کردن دستورات Build
|
||||
|
||||
در حال حاضر بسته به سیستمعامل میزبان، دستورات ساخت متفاوتی داریم که ایده آل نیست. برای جلوگیری از این، میتوانیم فایلی با نام `cargo/config.toml.` ایجاد کنیم که حاوی آرگومانهای خاص هر پلتفرم است:
|
||||
|
||||
```toml
|
||||
# in .cargo/config.toml
|
||||
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-nostartfiles"]
|
||||
|
||||
[target.'cfg(target_os = "windows")']
|
||||
rustflags = ["-C", "link-args=/ENTRY:_start /SUBSYSTEM:console"]
|
||||
|
||||
[target.'cfg(target_os = "macos")']
|
||||
rustflags = ["-C", "link-args=-e __start -static -nostartfiles"]
|
||||
```
|
||||
|
||||
کلید `rustflags` شامل آرگومانهایی است که بطور خودکار به هر فراخوانی `rustc` اضافه میشوند. برای کسب اطلاعات بیشتر در مورد فایل `cargo/config.toml.` به [اسناد رسمی](https://doc.rust-lang.org/cargo/reference/config.html) مراجعه کنید.
|
||||
|
||||
اکنون برنامه ما باید در هر سه سیستمعامل با یک `cargo build` ساده قابل بیلد باشد.
|
||||
|
||||
#### آیا شما باید این کار را انجام دهید؟
|
||||
|
||||
اگرچه ساخت یک اجرایی مستقل برای لینوکس، ویندوز و macOS امکان پذیر است، اما احتمالاً ایده خوبی نیست. چرا که اجرایی ما هنوز انتظار موارد مختلفی را دارد، به عنوان مثال با فراخوانی تابع `start_` یک پشته مقداردهی اولیه شده است. بدون رانتایم C، ممکن است برخی از این الزامات برآورده نشود، که ممکن است باعث شکست برنامه ما شود، به عنوان مثال از طریق `segmentation fault`.
|
||||
|
||||
اگر می خواهید یک باینری کوچک ایجاد کنید که بر روی سیستمعامل موجود اجرا شود، اضافه کردن `libc` و تنظیم ویژگی `[start]#` همانطور که [اینجا](https://doc.rust-lang.org/1.16.0/book/no-stdlib.html) شرح داده شده است، احتمالاً ایده بهتری است.
|
||||
|
||||
</details>
|
||||
|
||||
## خلاصه {#summary}
|
||||
|
||||
یک باینری مستقل مینیمال راست مانند این است:
|
||||
|
||||
`src/main.rs`:
|
||||
|
||||
```rust
|
||||
#![no_std] // don't link the Rust standard library
|
||||
#![no_main] // disable all Rust-level entry points
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
#[unsafe(no_mangle)] // don't mangle the name of this function
|
||||
pub extern "C" fn _start() -> ! {
|
||||
// this function is the entry point, since the linker looks for a function
|
||||
// named `_start` by default
|
||||
loop {}
|
||||
}
|
||||
|
||||
/// This function is called on panic.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
`Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "crate_name"
|
||||
version = "0.1.0"
|
||||
authors = ["Author Name <author@example.com>"]
|
||||
|
||||
# the profile used for `cargo build`
|
||||
[profile.dev]
|
||||
panic = "abort" # disable stack unwinding on panic
|
||||
|
||||
# the profile used for `cargo build --release`
|
||||
[profile.release]
|
||||
panic = "abort" # disable stack unwinding on panic
|
||||
```
|
||||
|
||||
برای ساخت این باینری، ما باید برای یک هدف bare metal مانند `thumbv7em-none-eabihf` کامپایل کنیم:
|
||||
|
||||
```
|
||||
cargo build --target thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
یک راه دیگر این است که میتوانیم آن را برای سیستم میزبان با استفاده از آرگومانهای اضافی لینکر کامپایل کنیم:
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
cargo rustc -- -C link-arg=-nostartfiles
|
||||
# Windows
|
||||
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
||||
# macOS
|
||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||
```
|
||||
|
||||
توجه داشته باشید که این فقط یک نمونه حداقلی از باینری مستقل راست است. این باینری انتظار چیزهای مختلفی را دارد، به عنوان مثال با فراخوانی تابع `start_` یک پشته مقداردهی اولیه میشود. **بنابراین برای هر گونه استفاده واقعی از چنین باینری، مراحل بیشتری لازم است**.
|
||||
|
||||
## بعدی چیست؟
|
||||
|
||||
[پست بعدی] مراحل مورد نیاز برای تبدیل باینری مستقل به حداقل هسته سیستمعامل را توضیح میدهد. که شامل ایجاد یک هدف سفارشی، ترکیب اجرایی ما با بوتلودر و یادگیری نحوه چاپ چیزی در صفحه است.
|
||||
|
||||
[پست بعدی]: @/edition-2/posts/02-minimal-rust-kernel/index.fa.md
|
||||
@@ -0,0 +1,523 @@
|
||||
+++
|
||||
title = "Un binaire Rust autoporté"
|
||||
weight = 1
|
||||
path = "fr/freestanding-rust-binary"
|
||||
date = 2018-02-10
|
||||
|
||||
[extra]
|
||||
# Please update this when updating the translation
|
||||
translation_based_on_commit = "3e87916b6c2ed792d1bdb8c0947906aef9013ac1"
|
||||
# GitHub usernames of the people that translated this post
|
||||
translators = ["AlexandreMarcq", "alaincao"]
|
||||
+++
|
||||
|
||||
La première étape pour créer notre propre noyau de système d'exploitation est de créer un exécutable Rust qui ne relie pas la bibliothèque standard. Cela rend possible l'exécution du code Rust sur la ["bare machine"][machine nue] sans système d'exploitation sous-jacent.
|
||||
|
||||
[machine nue]: https://en.wikipedia.org/wiki/Bare_machine
|
||||
|
||||
<!-- more -->
|
||||
|
||||
Ce blog est développé sur [GitHub]. Si vous avez un problème ou une question, veuillez ouvrir une issue. Vous pouvez aussi laisser un commentaire [en bas de page]. Le code source complet de cet article est disponible sur la branche [`post-01`][post branch].
|
||||
|
||||
[GitHub]: https://github.com/phil-opp/blog_os
|
||||
[en bas de page]: #comments
|
||||
<!-- fix for zola anchor checker (target is in template): <a id="comments"> -->
|
||||
[post branch]: https://github.com/phil-opp/blog_os/tree/post-01
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## Introduction
|
||||
Pour écrire un noyau de système d'exploitation, nous avons besoin d'un code qui ne dépend pas de fonctionnalités de système d'exploitation. Cela signifie que nous ne pouvons pas utiliser les fils d'exécution, les fichiers, la mémoire sur le tas, le réseau, les nombres aléatoires, la sortie standard ou tout autre fonctionnalité nécessitant une abstraction du système d'exploitation ou un matériel spécifique. Cela a du sens, étant donné que nous essayons d'écrire notre propre OS et nos propres pilotes.
|
||||
Cela signifie que nous ne pouvons pas utiliser la majeure partie de la [bibliothèque standard de Rust]. Il y a néanmoins beaucoup de fonctionnalités de Rust que nous _pouvons_ utiliser. Par exemple, nous pouvons utiliser les [iterators], les [closures], le [pattern matching], l'[option] et le [result], le [string formatting], et bien sûr l'[ownership system]. Ces fonctionnalités permettent l'écriture d'un noyau d'une façon expressive et haut-niveau sans se soucier des [comportements indéfinis] ou de la [sécurité de la mémoire].
|
||||
|
||||
[option]: https://doc.rust-lang.org/core/option/
|
||||
[result]:https://doc.rust-lang.org/core/result/
|
||||
[bibliothèque standard de Rust]: https://doc.rust-lang.org/std/
|
||||
[iterators]: https://doc.rust-lang.org/book/ch13-02-iterators.html
|
||||
[closures]: https://doc.rust-lang.org/book/ch13-01-closures.html
|
||||
[pattern matching]: https://doc.rust-lang.org/book/ch06-00-enums.html
|
||||
[string formatting]: https://doc.rust-lang.org/core/macro.write.html
|
||||
[ownership system]: https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html
|
||||
[comportement non-défini]: https://www.nayuki.io/page/undefined-behavior-in-c-and-cplusplus-programs
|
||||
[sécurité de la mémoire]: https://tonyarcieri.com/it-s-time-for-a-memory-safety-intervention
|
||||
|
||||
Pour créer un noyau d'OS en Rust, nous devons créer un exécutable qui peut tourner sans système d'exploitation sous-jacent. Un tel exécutable est appelé “freestanding” (autoporté) ou “bare-metal”.
|
||||
Cet article décrit les étapes nécessaires pour créer un exécutable Rust autoporté et explique pourquoi ces étapes sont importantes. Si vous n'êtes intéressé que par un exemple minimal, vous pouvez **[aller au résumé](#resume)**.
|
||||
|
||||
## Désactiver la Bibliothèque Standard
|
||||
|
||||
Par défaut, toutes les crates Rust sont liées à la bibliothèque standard, qui repose sur les fonctionnalités du système d’exploitation telles que les fils d'exécution, les fichiers et la connectivité réseau. Elle est également liée à la bibliothèque standard C `libc`, qui interagit étroitement avec les services fournis par l'OS. Comme notre plan est d'écrire un système d'exploitation, nous ne pouvons pas utiliser des bibliothèques dépendant de l'OS. Nous devons donc désactiver l'inclusion automatique de la bibliothèque standard en utilisant l'[attribut `no std`].
|
||||
|
||||
[bibliothèque standard]: https://doc.rust-lang.org/std/
|
||||
[attribut `no std`]: https://doc.rust-lang.org/1.30.0/book/first-edition/using-rust-without-the-standard-library.html
|
||||
|
||||
Nous commençons par créer un nouveau projet d'application cargo. La manière la plus simple de faire est avec la ligne de commande :
|
||||
|
||||
```
|
||||
cargo new blog_os --bin --edition 2018
|
||||
```
|
||||
|
||||
J'ai nommé le projet `blog_os`, mais vous pouvez évidemment choisir le nom qu'il vous convient. Le flag `--bin` indique que nous voulons créer un exécutable (contrairement à une bibliothèque) et le flag `--edition 2018` indique que nous voulons utiliser l'[édition 2018] de Rust pour notre crate. Quand nous lançons la commande, cargo crée la structure de répertoire suivante pour nous :
|
||||
|
||||
[édition 2018]: https://doc.rust-lang.org/nightly/edition-guide/rust-2018/index.html
|
||||
|
||||
```
|
||||
blog_os
|
||||
├── Cargo.toml
|
||||
└── src
|
||||
└── main.rs
|
||||
```
|
||||
|
||||
Le fichier `Cargo.toml` contient la configuration de la crate, par exemple le nom de la crate, l'auteur, le numéro de [versionnage sémantique] et les dépendances. Le fichier `src/main.rs` contient le module racine de notre crate et notre fonction `main`. Vous pouvez compiler votre crate avec `cargo build` et ensuite exécuter l'exécutable compilé `blog_os` dans le sous-dossier `target/debug`.
|
||||
|
||||
[versionnage sémantique]: https://semver.org/
|
||||
|
||||
### L'Attribut `no_std`
|
||||
|
||||
Pour l'instant, notre crate relie la bibliothèque standard implicitement. Désactivons cela en ajoutant l'[attribut `no std`] :
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
Quand nous essayons maintenant de compiler (avec `cargo build)`, l'erreur suivante se produit :
|
||||
|
||||
```
|
||||
error: cannot find macro `println!` in this scope
|
||||
--> src/main.rs:4:5
|
||||
|
|
||||
4 | println!("Hello, world!");
|
||||
| ^^^^^^^
|
||||
```
|
||||
|
||||
La raison est que la [macro `println`] fait partie de la bibliothèque standard, que nous ne pouvons plus utiliser. Nous ne pouvons donc plus afficher de texte avec. Cela est logique, car `println` écrit dans la [sortie standard], qui est un descripteur de fichier spécial fourni par le système d'eploitation.
|
||||
|
||||
[macro `println`]: https://doc.rust-lang.org/std/macro.println.html
|
||||
[sortie standard]: https://fr.wikipedia.org/wiki/Flux_standard#Sortie_standard
|
||||
|
||||
Supprimons l'affichage et essayons à nouveau avec une fonction main vide :
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
fn main() {}
|
||||
```
|
||||
|
||||
```
|
||||
> cargo build
|
||||
error: `#[panic_handler]` function required, but not found
|
||||
error: language item required, but not found: `eh_personality`
|
||||
```
|
||||
|
||||
Maintenant le compilateur a besoin d'une fonction `#[panic_handler]` et d'un _objet de langage_.
|
||||
|
||||
## Implémentation de Panic
|
||||
|
||||
L'attribut `panic_handler` définit la fonction que le compilateur doit appeler lorsqu'un [panic] arrive. La bibliothèque standard fournit sa propre fonction de gestion de panic mais dans un environnement `no_std`, nous avons besoin de le définir nous-mêmes :
|
||||
|
||||
[panic]: https://doc.rust-lang.org/stable/book/ch09-01-unrecoverable-errors-with-panic.html
|
||||
|
||||
```rust
|
||||
// dans main.rs
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
/// Cette fonction est appelée à chaque panic.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
Le [paramètre `PanicInfo`][PanicInfo] contient le fichier et la ligne où le panic a eu lieu et le message optionnel de panic. La fonction ne devrait jamais retourner quoi que ce soit, elle est donc marquée comme [fonction divergente] en retournant le [type “never”] `!`. Nous ne pouvons pas faire grand chose dans cette fonction pour le moment, nous bouclons donc indéfiniment.
|
||||
|
||||
[PanicInfo]: https://doc.rust-lang.org/nightly/core/panic/struct.PanicInfo.html
|
||||
[fonction divergente]: https://doc.rust-lang.org/1.30.0/book/first-edition/functions.html#diverging-functions
|
||||
[type “never”]: https://doc.rust-lang.org/nightly/std/primitive.never.html
|
||||
|
||||
## L'Objet de Langage `eh_personality`
|
||||
|
||||
Les objets de langage sont des fonctions et des types spéciaux qui sont requis par le compilateur de manière interne. Par exemple, le trait [`Copy`] est un objet de langage qui indique au compilateur quels types possèdent la [sémantique copy][`Copy`]. Quand nous regardons l'[implémentation][copy code] du code, nous pouvons voir qu'il possède l'attribut spécial `#[lang = copy]` qui le définit comme étant un objet de langage.
|
||||
|
||||
[`Copy`]: https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html
|
||||
[copy code]: https://github.com/rust-lang/rust/blob/485397e49a02a3b7ff77c17e4a3f16c653925cb3/src/libcore/marker.rs#L296-L299
|
||||
|
||||
Bien qu'il soit possible de fournir des implémentations personnalisées des objets de langage, cela ne devrait être fait qu'en dernier recours. La raison est que les objets de langages sont des détails d'implémentation très instables et qui ne sont même pas vérifiés au niveau de leur type (donc le compilateur ne vérifie même pas qu'une fonction possède les bons types d'arguments). Heureusement, il y a une manière plus robuste de corriger l'erreur d'objet de langage ci-dessus.
|
||||
|
||||
L'[objet de langage `eh_personality`] marque une fonction qui est utilisée pour l'implémentation du [déroulement de pile]. Par défaut, Rust utilise le déroulement de pile pour exécuter les destructeurs de chaque variable vivante sur la pile en cas de [panic]. Cela assure que toute la mémoire utilisée est libérée et permet au fil d'exécution parent d'attraper la panic et de continuer l'exécution. Le déroulement toutefois est un processus compliqué et nécessite des bibliothèques spécifiques à l'OS ([libunwind] pour Linux ou [gestion structurée des erreurs] pour Windows), nous ne voulons donc pas l'utiliser pour notre système d'exploitation.
|
||||
|
||||
[objet de langage `eh_personality`]: https://github.com/rust-lang/rust/blob/edb368491551a77d77a48446d4ee88b35490c565/src/libpanic_unwind/gcc.rs#L11-L45
|
||||
[déroulement de pile]: https://docs.microsoft.com/fr-fr/cpp/cpp/exceptions-and-stack-unwinding-in-cpp?view=msvc-160
|
||||
[libunwind]: https://www.nongnu.org/libunwind/
|
||||
[gestion structurée des erreurs]: https://docs.microsoft.com/fr-fr/windows/win32/debug/structured-exception-handling
|
||||
|
||||
### Désactiver le Déroulement
|
||||
|
||||
Il y a d'autres cas d'utilisation pour lesquels le déroulement n'est pas souhaité. Rust offre donc une option pour [interrompre après un panic]. Cela désactive la génération de symboles de déroulement et ainsi réduit considérablement la taille de l'exécutable. Il y a de multiples endroit où nous pouvons désactiver le déroulement. Le plus simple est d'ajouter les lignes suivantes dans notre `Cargo.toml` :
|
||||
|
||||
```toml
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
```
|
||||
|
||||
Cela configure la stratégie de panic à `abort` pour le profil `dev` (utilisé pour `cargo build`) et le profil `release` (utilisé pour `cargo build --release`). Maintenant l'objet de langage `eh_personality` ne devrait plus être requis.
|
||||
|
||||
[interrompre après un panic]: https://github.com/rust-lang/rust/pull/32900
|
||||
|
||||
Nous avons dorénavant corrigé les deux erreurs ci-dessus. Toutefois, si nous essayons de compiler, une autre erreur apparaît :
|
||||
|
||||
```
|
||||
> cargo build
|
||||
error: requires `start` lang_item
|
||||
```
|
||||
|
||||
L'objet de langage `start` manque à notre programme. Il définit le point d'entrée.
|
||||
|
||||
## L'attribut `start`
|
||||
|
||||
On pourrait penser que la fonction `main` est la première fonction appelée lorsqu'un programme est exécuté. Toutefois, la plupart des langages ont un [environnement d'exécution] qui est responsable des tâches telles que le ramassage des miettes (ex: dans Java) ou les fils d'exécution logiciel (ex: les goroutines dans Go). Cet environnement doit être appelé avant `main` puisqu'il a besoin de s'initialiser.
|
||||
|
||||
[environnement d'exécution]: https://fr.wikipedia.org/wiki/Environnement_d%27ex%C3%A9cution
|
||||
|
||||
Dans un exécutable Rust classique qui relie la bibliothèque standard, l'exécution commence dans une bibliothèque d'environnement d'exécution C appelé `crt0` (“C runtime zero”). Elle configure l'environnement pour une application C. Cela comprend la création d'une pile et le placement des arguments dans les bons registres. L'environnement d'exécution C appelle ensuite [le point d'entrée de l'environnement d'exécution de Rust][rt::lang_start], qui est marqué par l'objet de langage `start`. Rust possède un environnement d'exécution très minime, qui se charge de petites tâches telles que la configuration des guardes de dépassement de pile ou l'affichage de la trace d'appels lors d'un panic. L'environnement d'exécution finit par appeler la fonction `main`.
|
||||
|
||||
[rt::lang_start]: https://github.com/rust-lang/rust/blob/bb4d1491466d8239a7a5fd68bd605e3276e97afb/src/libstd/rt.rs#L32-L73
|
||||
|
||||
Notre exécutable autoporté n'a pas accès à l'environnement d'exécution de Rust ni à `crt0`. Nous avons donc besoin de définir notre propre point d'entrée. Implémenter l'objet de langage `start` n'aiderait pas car nous aurions toujours besoin de `crt0`. Nous avons plutôt besoin de réécrire le point d'entrée de `crt0` directement.
|
||||
|
||||
### Réécrire le Point d'Entrée
|
||||
|
||||
Pour indiquer au compilateur que nous ne voulons pas utiliser la chaîne de point d'entrée normale, nous ajoutons l'attribut `#![no_main]`.
|
||||
|
||||
```rust
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
/// Cette fonction est appelée à chaque panic.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
Vous remarquerez peut-être que nous avons retiré la fonction `main`. La raison est que la présence de cette fonction n'a pas de sens sans un environnement d'exécution sous-jacent qui l'appelle. À la place, nous réécrivons le point d'entrée du système d'exploitation avec notre propre fonction `_start` :
|
||||
|
||||
```rust
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _start() -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
En utilisant l'attribut `#[unsafe(no_mangle)]`, nous désactivons la [décoration de nom] pour assurer que le compilateur Rust crée une fonction avec le nom `_start`. Sans cet attribut, le compilateur génèrerait un symbol obscure `_ZN3blog_os4_start7hb173fedf945531caE` pour donner un nom unique à chaque fonction. L'attribut est nécessaire car nous avons besoin d'indiquer le nom de la fonction de point d'entrée à l'éditeur de lien (*linker*) dans l'étape suivante.
|
||||
|
||||
Nous devons aussi marquer la fonction avec `extern C` pour indiquer au compilateur qu'il devrait utiliser la [convention de nommage] de C pour cette fonction (au lieu de la convention de nommage de Rust non-spécifiée). Cette fonction se nomme `_start` car c'est le nom par défaut des points d'entrée pour la plupart des systèmes.
|
||||
|
||||
[décoration de nom]: https://fr.wikipedia.org/wiki/D%C3%A9coration_de_nom
|
||||
[convention de nommage]: https://fr.wikipedia.org/wiki/Convention_de_nommage
|
||||
|
||||
Le type de retour `!` signifie que la fonction est divergente, c-à-d qu'elle n'a pas le droit de retourner quoi que ce soit. Cela est nécessaire car le point d'entrée n'est pas appelé par une fonction, mais invoqué directement par le système d'exploitation ou par le chargeur d'amorçage. Donc au lieu de retourner une valeur, le point d'entrée doit invoquer l'[appel système `exit`] du système d'exploitation. Dans notre cas, arrêter la machine pourrait être une action convenable, puisqu'il ne reste rien d'autre à faire si un exécutable autoporté s'arrête. Pour l'instant, nous remplissons la condition en bouclant indéfiniement.
|
||||
|
||||
[appel système `exit`]: https://fr.wikipedia.org/wiki/Appel_syst%C3%A8me
|
||||
|
||||
Quand nous lançons `cargo build`, nous obtenons une erreur de _linker_.
|
||||
|
||||
## Erreurs de Linker
|
||||
|
||||
Le linker est un programme qui va transformer le code généré en exécutable. Comme le format de l'exécutable differt entre Linux, Windows et macOS, chaque système possède son propre linker qui lève une erreur différente. La cause fondamentale de cette erreur est la même : la configuration par défaut du linker part du principe que notre programme dépend de l'environnement d'exécution de C, ce qui n'est pas le cas.
|
||||
|
||||
Pour résoudre les erreurs, nous devons indiquer au linker qu'il ne doit pas inclure l'environnement d'exécution de C. Nous pouvons faire cela soit en passant un ensemble précis d'arguments, soit en compilant pour une cible bare metal.
|
||||
|
||||
### Compiler pour une Cible Bare Metal
|
||||
|
||||
Par défaut Rust essaie de compiler un exécutable qui est compatible avec l'environnment du système actuel. Par exemple, si vous utilisez Windows avec `x86_64`, Rust essaie de compiler un exécutable Windows `.exe` qui utilises des instructions `x86_64`. Cet environnement est appelé système "hôte".
|
||||
|
||||
Pour décrire plusieurs environnements, Rust utilise une chaîne de caractères appelée [_triplé cible_]. Vous pouvez voir le triplé cible de votre système hôte en lançant la commande `rustc --version --verbose` :
|
||||
|
||||
[_triplé cible_]: https://clang.llvm.org/docs/CrossCompilation.html#target-triple
|
||||
|
||||
```
|
||||
rustc 1.35.0-nightly (474e7a648 2019-04-07)
|
||||
binary: rustc
|
||||
commit-hash: 474e7a6486758ea6fc761893b1a49cd9076fb0ab
|
||||
commit-date: 2019-04-07
|
||||
host: x86_64-unknown-linux-gnu
|
||||
release: 1.35.0-nightly
|
||||
LLVM version: 8.0
|
||||
```
|
||||
|
||||
La sortie ci-dessus provient d'un système Linux `x86_64`. Nous pouvons voir que le triplé `host` est `x86_64-unknown-linux-gnu`, qui inclut l'architecture du CPU (`x86_64`), le vendeur (`unknown`), le système d'exploitation (`linux`) et l'[ABI] (`gnu`).
|
||||
|
||||
[ABI]: https://fr.wikipedia.org/wiki/Application_binary_interface
|
||||
|
||||
En compilant pour notre triplé hôte, le compilateur Rust ainsi que le linker supposent qu'il y a un système d'exploitation sous-jacent comme Linux ou Windows qui utilise l'environnement d'exécution C par défaut, ce qui cause les erreurs de linker. Donc pour éviter ces erreurs, nous pouvons compiler pour un environnement différent sans système d'exploitation sous-jacent.
|
||||
|
||||
Un exemple d'un tel envrironnement est le triplé cible `thumbv7em-none-eabihf`, qui décrit un système [ARM] [embarqué]. Les détails ne sont pas importants, tout ce qui compte est que le triplé cible n'a pas de système d'exploitation sous-jacent, ce qui est indiqué par le `none` dans le triplé cible. Pour pouvoir compiler pour cette cible, nous avons besoin de l'ajouter dans rustup :
|
||||
|
||||
[embarqué]: https://fr.wikipedia.org/wiki/Syst%C3%A8me_embarqu%C3%A9
|
||||
[ARM]: https://fr.wikipedia.org/wiki/Architecture_ARM
|
||||
|
||||
```
|
||||
rustup target add thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
Cela télécharge une copie de la bibliothèque standard (et core) pour le système. Maintenant nous pouvons compiler notre exécutable autoporté pour cette cible :
|
||||
|
||||
```
|
||||
cargo build --target thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
En donnant un argument `--target`, nous effectuons une [compilation croisée][cross_compile] de notre exécutable pour un système bare metal. Comme le système cible n'a pas de système d'exploitation, le linker n'essaie pas de lier l'environnement d'exécution C et notre compilation réussit sans erreur de linker.
|
||||
|
||||
[cross compile]: https://en.wikipedia.org/wiki/Cross_compiler
|
||||
|
||||
C'est l'approche que nous allons utiliser pour construire notre noyau d'OS. Plutôt que `thumbv7em-none-eabihf`, nous allons utiliser une [cible personnalisée][custom target] qui décrit un environnement bare metal `x86_64`. Les détails seront expliqués dans le prochain article.
|
||||
|
||||
[custom target]: https://doc.rust-lang.org/rustc/targets/custom.html
|
||||
|
||||
### Arguments du Linker
|
||||
|
||||
Au lieu de compiler pour un système bare metal, il est aussi possible de résoudre les erreurs de linker en passant un ensemble précis d'arguments au linker. Ce n'est pas l'approche que nous allons utiliser pour notre noyau. Cette section est donc optionnelle et fournis uniquement à titre de complétude. Cliquez sur _"Arguments du Linker"_ ci-dessous pour montrer le contenu optionel.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Arguments du Linker</summary>
|
||||
|
||||
Dans cette section nous allons parler des erreurs de linker qui se produisent sur Linux, Windows et macOS. Nous allons aussi apprendre à résoudre ces erreurs en passant des arguments complémentaires au linker. À noter que le format de l'exécutable et le linker diffèrent entre les systèmes d'exploitation. Il faut donc un ensemble d'arguments différent pour chaque système.
|
||||
|
||||
#### Linux
|
||||
|
||||
Sur Linux, voici l'erreur de linker qui se produit (raccourcie) :
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: /usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x12): undefined reference to `__libc_csu_fini'
|
||||
/usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x19): undefined reference to `__libc_csu_init'
|
||||
/usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x25): undefined reference to `__libc_start_main'
|
||||
collect2: error: ld returned 1 exit status
|
||||
```
|
||||
|
||||
Le problème est que le linker inclut par défaut la routine de démarrage de l'environnement d'exécution de C, qui est aussi appelée `_start`. Elle requiert des symboles de la bibliothèque standard de C `libc` que nous n'incluons pas à cause de l'attribut `no_std`. Le linker ne peut donc pas résoudre ces références. Pour résoudre cela, nous pouvons indiquer au linker qu'il ne devrait pas lier la routine de démarrage de C en passant l'argument `-nostartfiles`.
|
||||
|
||||
Une façon de passer des attributs au linker via cargo est la commande `cargo rustc`. Cette commande se comporte exactement comme `cargo build`, mais permet aussi de donner des options à `rustc`, le compilateur Rust sous-jacent. `rustc` possède le flag `-C link-arg`, qui donne un argument au linker. Combinés, notre nouvelle commande ressemble à ceci :
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-arg=-nostartfiles
|
||||
```
|
||||
|
||||
Dorénavant notre crate compile en tant qu'exécutable Linux autoporté !
|
||||
|
||||
Nous n'avions pas besoin de spécifier le nom de notre point d'entrée de façon explicite car le linker cherche par défaut une fonction nommée `_start`.
|
||||
|
||||
#### Windows
|
||||
|
||||
Sur Windows, une erreur de linker différente se produit (raccourcie) :
|
||||
|
||||
```
|
||||
error: linking with `link.exe` failed: exit code: 1561
|
||||
|
|
||||
= note: "C:\\Program Files (x86)\\…\\link.exe" […]
|
||||
= note: LINK : fatal error LNK1561: entry point must be defined
|
||||
```
|
||||
|
||||
Cette erreur signifie que le linker ne peut pas trouver le point d'entrée. Sur Windows, le nom par défaut du point d'entrée [dépend du sous-système utilisé][windows-subsystems]. Pour le sous-système `CONSOLE`, le linker cherche une fonction nommée `mainCRTStartup` et pour le sous-système `WINDOWS`, il cherche une fonction nomée `WinMainCRTStartup`. Pour réécrire la valeur par défaut et indiquer au linker de chercher notre fonction `_start` à la place, nous pouvons donner l'argument `/ENTRY` au linker :
|
||||
|
||||
[windows-subsystems]: https://docs.microsoft.com/fr-fr/cpp/build/reference/entry-entry-point-symbol?view=msvc-160
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-arg=/ENTRY:_start
|
||||
```
|
||||
|
||||
Vu le format d'argument différent nous pouvons clairement voir que le linker Windows est un programme totalement différent du linker Linux.
|
||||
|
||||
Maintenant une erreur de linker différente se produit :
|
||||
|
||||
```
|
||||
error: linking with `link.exe` failed: exit code: 1221
|
||||
|
|
||||
= note: "C:\\Program Files (x86)\\…\\link.exe" […]
|
||||
= note: LINK : fatal error LNK1221: a subsystem can't be inferred and must be
|
||||
defined
|
||||
```
|
||||
|
||||
Cette erreur se produit car les exécutables Windows peuvent utiliser différents [sous-systèmes][windows-subsystems]. Pour les programmes normaux, ils sont inférés en fonction du nom du point d'entrée : s'il est nommé `main`, le sous-système `CONSOLE` est utilisé. Si le point d'entrée est nommé `WinMain`, alors le sous-sytème `WINDOWS` est utilisé. Comme notre fonction `_start` possède un nom différent, nous devons préciser le sous-système explicitement :
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
||||
```
|
||||
|
||||
Ici nous utilisons le sous-système `CONSOLE`, mais le sous-système `WINDOWS` pourrait fonctionner aussi. Au lieu de donner `-C link-arg` plusieurs fois, nous utilisons `-C link-args` qui utilise des arguments séparés par des espaces.
|
||||
|
||||
Avec cette commande, notre exécutable devrait compiler avec succès sous Windows.
|
||||
|
||||
#### macOS
|
||||
|
||||
Sur macOS, voici l'erreur de linker qui se produit (raccourcie) :
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: entry point (_main) undefined. for architecture x86_64
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
Cette erreur nous indique que le linker ne peut pas trouver une fonction de point d'entrée avec le nom par défaut `main` (pour une quelconque raison, toutes les fonctions sur macOS sont précédées de `_`). Pour configurer le point d'entrée sur notre fonction `_start`, nous donnons l'argument `-e` au linker :
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start"
|
||||
```
|
||||
|
||||
L'argument `-e` spécifie le nom de la fonction de point d'entrée. Comme toutes les fonctions ont un préfixe supplémentaire `_` sur macOS, nous devons configurer le point d'entrée comme étant `__start` au lieu de `_start`.
|
||||
|
||||
Maintenant l'erreur de linker suivante se produit :
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: dynamic main executables must link with libSystem.dylib
|
||||
for architecture x86_64
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
macOS [ne supporte pas officiellement les bibliothèques liées de façon statique] et necéessite que les programmes lient la bibliothèque `libSystem` par défaut. Pour réécrire ceci et lier une bibliothèque statique, nous donnons l'argument `-static` au linker :
|
||||
|
||||
[ne supporte pas officiellement les bibliothèques liées de façon statique]: https://developer.apple.com/library/archive/qa/qa1118/_index.html
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start -static"
|
||||
```
|
||||
|
||||
Cela ne suffit toujours pas, une troisième erreur de linker se produit :
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: library not found for -lcrt0.o
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
Cette erreur se produit car les programmes sous macOS lient `crt0` (“C runtime zero”) par défaut. Ceci est similaire à l'erreur que nous avions eu sous Linux et peut aussi être résolue en ajoutant l'argument `-nostartfiles` au linker :
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||
```
|
||||
|
||||
Maintenant notre programme compile avec succès sous macOS.
|
||||
|
||||
#### Unifier les Commandes de Compilation
|
||||
|
||||
À cet instant nous avons différentes commandes de compilation en fonction de la plateforme hôte, ce qui n'est pas idéal. Pour éviter cela, nous pouvons créer un ficher nommé `.cargo/config.toml` qui contient les arguments spécifiques aux plateformes :
|
||||
|
||||
```toml
|
||||
# dans .cargo/config.toml
|
||||
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-nostartfiles"]
|
||||
|
||||
[target.'cfg(target_os = "windows")']
|
||||
rustflags = ["-C", "link-args=/ENTRY:_start /SUBSYSTEM:console"]
|
||||
|
||||
[target.'cfg(target_os = "macos")']
|
||||
rustflags = ["-C", "link-args=-e __start -static -nostartfiles"]
|
||||
```
|
||||
|
||||
La clé `rustflags` contient des arguments qui sont automatiquement ajoutés à chaque appel de `rustc`. Pour plus d'informations sur le fichier `.cargo/config.toml`, allez voir la [documentation officielle](https://doc.rust-lang.org/cargo/reference/config.html)
|
||||
|
||||
Maintenant notre programme devrait être compilable sur les trois plateformes avec un simple `cargo build`.
|
||||
|
||||
#### Devriez-vous Faire Ça ?
|
||||
|
||||
Bien qu'il soit possible de compiler un exécutable autoporté pour Linux, Windows et macOS, ce n'est probablement pas une bonne idée. La raison est que notre exécutable s'attend toujours à trouver certaines choses, par exemple une pile initialisée lorsque la fonction `_start` est appelée. Sans l'environnement d'exécution C, certaines de ces conditions peuvent ne pas être remplies, ce qui pourrait faire planter notre programme, avec par exemple une erreur de segmentation.
|
||||
|
||||
Si vous voulez créer un exécutable minimal qui tourne sur un système d'exploitation existant, include `libc` et mettre l'attribut `#[start]` come décrit [ici](https://doc.rust-lang.org/1.16.0/book/no-stdlib.html) semble être une meilleure idée.
|
||||
|
||||
</details>
|
||||
|
||||
## Résumé
|
||||
|
||||
Un exécutable Rust autoporté minimal ressemble à ceci :
|
||||
|
||||
`src/main.rs`:
|
||||
|
||||
```rust
|
||||
#![no_std] // ne pas lier la bibliothèque standard Rust
|
||||
#![no_main] // désactiver tous les points d'entrée au niveau de Rust
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
#[unsafe(no_mangle)] // ne pas décorer le nom de cette fonction
|
||||
pub extern "C" fn _start() -> ! {
|
||||
// cette fonction est le point d'entrée, comme le linker cherche une fonction
|
||||
// nomée `_start` par défaut
|
||||
loop {}
|
||||
}
|
||||
|
||||
/// Cette fonction est appelée à chaque panic.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
`Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "crate_name"
|
||||
version = "0.1.0"
|
||||
authors = ["Author Name <author@example.com>"]
|
||||
|
||||
# le profile utilisé pour `cargo build`
|
||||
[profile.dev]
|
||||
panic = "abort" # désactive le déroulement de la pile lors d'un panic
|
||||
|
||||
# le profile utilisé pour `cargo build --release`
|
||||
[profile.release]
|
||||
panic = "abort" # désactive le déroulement de la pile lors d'un panic
|
||||
```
|
||||
|
||||
Pour compiler cet exécutable, nous devons compiler pour une cible bare metal telle que `thumbv7em-none-eabihf` :
|
||||
|
||||
```
|
||||
cargo build --target thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
Sinon, nous pouvons aussi compiler pour le système hôte en donnant des arguments supplémentaires pour le linker :
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
cargo rustc -- -C link-arg=-nostartfiles
|
||||
# Windows
|
||||
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
||||
# macOS
|
||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||
```
|
||||
|
||||
À noter que ceci est juste un exemple minimal d'un exécutable Rust autoporté. Cet exécutable s'attend à de nombreuses choses, comme par exemple le fait qu'une pile soit initialisée lorsque la fonction `_start` est appelée. **Donc pour une réelle utilisation d'un tel exécutable, davantages d'étapes sont requises.**
|
||||
|
||||
## Et ensuite ?
|
||||
|
||||
Le [poste suivant][next post] explique les étapes nécessaires pour transformer notre exécutable autoporté minimal en noyau de système d'opération. Cela comprend la création d'une cible personnalisée, l'intégration de notre exécutable avec un chargeur d'amorçage et l'apprentissage de comment imprimer quelque chose sur l'écran.
|
||||
|
||||
[next post]: @/edition-2/posts/02-minimal-rust-kernel/index.md
|
||||
@@ -0,0 +1,530 @@
|
||||
+++
|
||||
title = "フリースタンディングな Rust バイナリ"
|
||||
weight = 1
|
||||
path = "ja/freestanding-rust-binary"
|
||||
date = 2018-02-10
|
||||
|
||||
[extra]
|
||||
# Please update this when updating the translation
|
||||
translation_based_on_commit = "6f1f87215892c2be12c6973a6f753c9a25c34b7e"
|
||||
# GitHub usernames of the people that translated this post
|
||||
translators = ["JohnTitor"]
|
||||
+++
|
||||
|
||||
私達自身のオペレーティングシステム(以下、OS)カーネルを作っていく最初のステップは標準ライブラリとリンクしない Rust の実行可能ファイルをつくることです。これにより、基盤となる OS がない[ベアメタル][bare metal]上で Rust のコードを実行することができるようになります。
|
||||
|
||||
[bare metal]: https://en.wikipedia.org/wiki/Bare_machine
|
||||
|
||||
<!-- more -->
|
||||
|
||||
このブログの内容は [GitHub] 上で公開・開発されています。何か問題や質問などがあれば issue をたててください (訳注: リンクは原文(英語)のものになります)。また[こちら][comments]にコメントを残すこともできます。この記事の完全なソースコードは[`post-01` ブランチ][post branch]にあります。
|
||||
|
||||
[GitHub]: https://github.com/phil-opp/blog_os
|
||||
[comments]: #comments
|
||||
<!-- fix for zola anchor checker (target is in template): <a id="comments"> -->
|
||||
[post branch]: https://github.com/phil-opp/blog_os/tree/post-01
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## 導入
|
||||
|
||||
OS カーネルを書くためには、いかなる OS の機能にも依存しないコードが必要となります。つまり、スレッドやヒープメモリ、ネットワーク、乱数、標準出力、その他 OS による抽象化や特定のハードウェアを必要とする機能は使えません。私達は自分自身で OS とそのドライバを書こうとしているので、これは理にかなっています。
|
||||
|
||||
これは [Rust の標準ライブラリ][Rust standard library]をほとんど使えないということを意味しますが、それでも私達が使うことのできる Rust の機能はたくさんあります。例えば、[イテレータ][iterators]や[クロージャ][closures]、[パターンマッチング][pattern matching]、 [`Option`][option] や [`Result`][result] 型に[文字列フォーマット][string formatting]、そしてもちろん[所有権システム][ownership system]を使うことができます。これらの機能により、[未定義動作][undefined behavior]や[メモリ安全性][memory safety]を気にせずに、高い水準で表現力豊かにカーネルを書くことができます。
|
||||
|
||||
[option]: https://doc.rust-lang.org/core/option/
|
||||
[result]:https://doc.rust-lang.org/core/result/
|
||||
[Rust standard library]: https://doc.rust-lang.org/std/
|
||||
[iterators]: https://doc.rust-lang.org/book/ch13-02-iterators.html
|
||||
[closures]: https://doc.rust-lang.org/book/ch13-01-closures.html
|
||||
[pattern matching]: https://doc.rust-lang.org/book/ch06-00-enums.html
|
||||
[string formatting]: https://doc.rust-lang.org/core/macro.write.html
|
||||
[ownership system]: https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html
|
||||
[undefined behavior]: https://www.nayuki.io/page/undefined-behavior-in-c-and-cplusplus-programs
|
||||
[memory safety]: https://tonyarcieri.com/it-s-time-for-a-memory-safety-intervention
|
||||
|
||||
Rust で OS カーネルを書くには、基盤となる OS なしで動く実行環境をつくる必要があります。そのような実行環境はフリースタンディング環境やベアメタルのように呼ばれます。
|
||||
|
||||
この記事では、フリースタンディングな Rust のバイナリをつくるために必要なステップを紹介し、なぜそれが必要なのかを説明します。もし最小限の説明だけを読みたいのであれば **[概要](#summary)** まで飛ばしてください。
|
||||
|
||||
## 標準ライブラリの無効化
|
||||
|
||||
デフォルトでは、全ての Rust クレートは[標準ライブラリ][standard library]にリンクされています。標準ライブラリはスレッドやファイル、ネットワークのような OS の機能に依存しています。また OS と密接な関係にある C の標準ライブラリ(`libc`)にも依存しています。私達の目的は OS を書くことなので、 OS 依存のライブラリを使うことはできません。そのため、 [`no_std` attribute] を使って標準ライブラリが自動的にリンクされるのを無効にします。
|
||||
|
||||
[standard library]: https://doc.rust-lang.org/std/
|
||||
[`no_std` attribute]: https://doc.rust-lang.org/1.30.0/book/first-edition/using-rust-without-the-standard-library.html
|
||||
|
||||
新しい Cargo プロジェクトをつくるところから始めましょう。もっとも簡単なやり方はコマンドラインで以下を実行することです。
|
||||
|
||||
```bash
|
||||
cargo new blog_os --bin --edition 2018
|
||||
```
|
||||
|
||||
プロジェクト名を `blog_os` としましたが、もちろんお好きな名前をつけていただいても大丈夫です。`--bin`フラグは実行可能バイナリを作成することを、 `--edition 2018` は[2018エディション][2018 edition]を使用することを明示的に指定します。コマンドを実行すると、 Cargoは以下のようなディレクトリ構造を作成します:
|
||||
|
||||
[2018 edition]: https://doc.rust-lang.org/nightly/edition-guide/rust-2018/index.html
|
||||
|
||||
```bash
|
||||
blog_os
|
||||
├── Cargo.toml
|
||||
└── src
|
||||
└── main.rs
|
||||
```
|
||||
|
||||
`Cargo.toml` にはクレートの名前や作者名、[セマンティックバージョニング][semantic version]に基づくバージョンナンバーや依存関係などが書かれています。`src/main.rs` には私達のクレートのルートモジュールと `main` 関数が含まれています。`cargo build` コマンドでこのクレートをコンパイルして、 `target/debug` ディレクトリの中にあるコンパイルされた `blog_os` バイナリを実行することができます。
|
||||
|
||||
[semantic version]: https://semver.org/
|
||||
|
||||
### `no_std` Attribute
|
||||
|
||||
今のところ私達のクレートは暗黙のうちに標準ライブラリをリンクしています。[`no_std` attribute]を追加してこれを無効にしてみましょう:
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
(`cargo build` を実行して)ビルドしようとすると、次のようなエラーが発生します:
|
||||
|
||||
```bash
|
||||
error: cannot find macro `println!` in this scope
|
||||
--> src/main.rs:4:5
|
||||
|
|
||||
4 | println!("Hello, world!");
|
||||
| ^^^^^^^
|
||||
```
|
||||
|
||||
これは [`println` マクロ][`println` macro]が標準ライブラリに含まれているためです。`no_std` で標準ライブラリを無効にしたので、何かをプリントすることはできなくなりました。`println` は標準出力に書き込むのでこれは理にかなっています。[標準出力][standard output]は OS によって提供される特別なファイル記述子であるためです。
|
||||
|
||||
[`println` macro]: https://doc.rust-lang.org/std/macro.println.html
|
||||
[standard output]: https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29
|
||||
|
||||
では、 `println` を削除し `main` 関数を空にしてもう一度ビルドしてみましょう:
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
fn main() {}
|
||||
```
|
||||
|
||||
```bash
|
||||
> cargo build
|
||||
error: `#[panic_handler]` function required, but not found
|
||||
error: language item required, but not found: `eh_personality`
|
||||
```
|
||||
|
||||
この状態では `#[panic_handler]` 関数と `language item` がないというエラーが発生します。
|
||||
|
||||
## Panic の実装
|
||||
|
||||
`panic_handler` attribute は[パニック]が発生したときにコンパイラが呼び出す関数を定義します。標準ライブラリには独自のパニックハンドラー関数がありますが、 `no_std` 環境では私達の手でそれを実装する必要があります:
|
||||
|
||||
[パニック]: https://doc.rust-lang.org/stable/book/ch09-01-unrecoverable-errors-with-panic.html
|
||||
|
||||
```rust
|
||||
// in main.rs
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
/// この関数はパニック時に呼ばれる
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
[`PanicInfo` パラメータ]には、パニックが発生したファイルと行、およびオプションでパニックメッセージが含まれます。この関数は戻り値を取るべきではないので、["never" 型(`!`)][“never” type]を返すことで[発散する関数][diverging function]となります。今のところこの関数でできることは多くないので、無限にループするだけです。
|
||||
|
||||
[`PanicInfo` パラメータ]: https://doc.rust-lang.org/nightly/core/panic/struct.PanicInfo.html
|
||||
[diverging function]: https://doc.rust-lang.org/1.30.0/book/first-edition/functions.html#diverging-functions
|
||||
[“never” type]: https://doc.rust-lang.org/nightly/std/primitive.never.html
|
||||
|
||||
## `eh_personality` Language Item
|
||||
|
||||
language item はコンパイラによって内部的に必要とされる特別な関数や型です。例えば、[`Copy`] トレイトはどの型が[コピーセマンティクス][`Copy`]を持っているかをコンパイラに伝える language item です。[実装][copy code]を見てみると、 language item として定義されている特別な `#[lang = "copy"]` attribute を持っていることが分かります。
|
||||
|
||||
[`Copy`]: https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html
|
||||
[copy code]: https://github.com/rust-lang/rust/blob/485397e49a02a3b7ff77c17e4a3f16c653925cb3/src/libcore/marker.rs#L296-L299
|
||||
|
||||
独自に language item を実装することもできますが、これは最終手段として行われるべきでしょう。というのも、language item は非常に不安定な実装であり型検査も行われないからです(なので、コンパイラは関数が正しい引数の型を取っているかさえ検査しません)。幸い、上記の language item のエラーを修正するためのより安定した方法があります。
|
||||
|
||||
[`eh_personality` language item] は[スタックアンワインド][stack unwinding] を実装するための関数を定義します。デフォルトでは、パニックが起きた場合には Rust はアンワインドを使用してすべてのスタックにある変数のデストラクタを実行します。これにより、使用されている全てのメモリが確実に解放され、親スレッドはパニックを検知して実行を継続できます。しかしアンワインドは複雑であり、いくつかの OS 特有のライブラリ(例えば、Linux では [libunwind] 、Windows では[構造化例外][structured exception handling])を必要とするので、私達の OS には使いたくありません。
|
||||
|
||||
[`eh_personality` language item]: https://github.com/rust-lang/rust/blob/edb368491551a77d77a48446d4ee88b35490c565/src/libpanic_unwind/gcc.rs#L11-L45
|
||||
[stack unwinding]: https://www.bogotobogo.com/cplusplus/stackunwinding.php
|
||||
[libunwind]: https://www.nongnu.org/libunwind/
|
||||
[structured exception handling]: https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling
|
||||
|
||||
### アンワインドの無効化
|
||||
|
||||
他にもアンワインドが望ましくないユースケースがあります。そのため、Rust には代わりに[パニックで中止する][abort on panic]オプションがあります。これにより、アンワインドのシンボル情報の生成が無効になり、バイナリサイズが大幅に削減されます。アンワインドを無効にする方法は複数あります。もっとも簡単な方法は、`Cargo.toml` に次の行を追加することです:
|
||||
|
||||
```toml
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
```
|
||||
|
||||
これは dev プロファイル(`cargo build` に使用される)と release プロファイル(`cargo build --release` に使用される)の両方でパニックで中止するようにするための設定です。これで `eh_personality` language item が不要になりました。
|
||||
|
||||
[abort on panic]: https://github.com/rust-lang/rust/pull/32900
|
||||
|
||||
これで上の2つのエラーを修正しました。しかし、コンパイルしようとすると別のエラーが発生します:
|
||||
|
||||
```bash
|
||||
> cargo build
|
||||
error: requires `start` lang_item
|
||||
```
|
||||
|
||||
私達のプログラムにはエントリポイントを定義する `start` language item がありません。
|
||||
|
||||
## `start` attribute
|
||||
|
||||
`main` 関数はプログラムを実行したときに最初に呼び出される関数であると思うかもしれません。しかし、ほとんどの言語には[ランタイムシステム][runtime system]があり、これはガベージコレクション(Java など)やソフトウェアスレッド(Go のゴルーチン)などを処理します。ランタイムは自身を初期化する必要があるため、`main` 関数の前に呼び出す必要があります。これにはスタック領域の作成と正しいレジスタへの引数の配置が含まれます。
|
||||
|
||||
[runtime system]: https://en.wikipedia.org/wiki/Runtime_system
|
||||
|
||||
標準ライブラリをリンクする一般的な Rust バイナリでは、`crt0` ("C runtime zero")と呼ばれる C のランタイムライブラリで実行が開始され、C アプリケーションの環境が設定されます。その後 C ランタイムは、`start` language item で定義されている [Rust ランタイムのエントリポイント][rt::lang_start]を呼び出します。Rust にはごくわずかなランタイムしかありません。これは、スタックオーバーフローを防ぐ設定やパニック時のバックトレースの表示など、いくつかの小さな処理を行います。最後に、ランタイムは `main` 関数を呼び出します。
|
||||
|
||||
[rt::lang_start]: https://github.com/rust-lang/rust/blob/bb4d1491466d8239a7a5fd68bd605e3276e97afb/src/libstd/rt.rs#L32-L73
|
||||
|
||||
私達のフリースタンディングな実行可能ファイルは今のところ Rust ランタイムと `crt0` へアクセスできません。なので、私達は自身でエントリポイントを定義する必要があります。`start` language item を実装することは `crt0` を必要とするのでここではできません。代わりに `crt0` エントリポイントを直接上書きしなければなりません。
|
||||
|
||||
### エントリポイントの上書き
|
||||
|
||||
Rust コンパイラに通常のエントリポイントを使いたくないことを伝えるために、`#![no_main]` attribute を追加します。
|
||||
|
||||
```rust
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
/// This function is called on panic.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
`main` 関数を削除したことに気付いたかもしれません。`main` 関数を呼び出す基盤となるランタイムなしには置いていても意味がないからです。代わりに、OS のエントリポイントを独自の `_start` 関数で上書きしていきます:
|
||||
|
||||
```rust
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _start() -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
Rust コンパイラが `_start` という名前の関数を実際に出力するように、`#[unsafe(no_mangle)]` attributeを用いて[名前修飾][name mangling]を無効にします。この attribute がないと、コンパイラはすべての関数にユニークな名前をつけるために、 `_ZN3blog_os4_start7hb173fedf945531caE` のようなシンボルを生成します。次のステップでエントリポイントとなる関数の名前をリンカに伝えるため、この属性が必要となります。
|
||||
|
||||
また、(指定されていない Rust の呼び出し規約の代わりに)この関数に [C の呼び出し規約][C calling convention]を使用するようコンパイラに伝えるために、関数を `extern "C"` として定義する必要があります。`_start`という名前をつける理由は、これがほとんどのシステムのデフォルトのエントリポイント名だからです。
|
||||
|
||||
[name mangling]: https://en.wikipedia.org/wiki/Name_mangling
|
||||
[C calling convention]: https://en.wikipedia.org/wiki/Calling_convention
|
||||
|
||||
戻り値の型である `!` は関数が発散している、つまり値を返すことができないことを意味しています。エントリポイントはどの関数からも呼び出されず、OS またはブートローダから直接呼び出されるので、これは必須です。なので、値を返す代わりに、エントリポイントは例えば OS の [`exit` システムコール][`exit` system call]を呼び出します。今回はフリースタンディングなバイナリが返されたときマシンをシャットダウンするようにすると良いでしょう。今のところ、私達は無限ループを起こすことで要件を満たします。
|
||||
|
||||
[`exit` system call]: https://en.wikipedia.org/wiki/Exit_(system_call)
|
||||
|
||||
`cargo build` を実行すると、見づらいリンカエラーが発生します。
|
||||
|
||||
## リンカエラー
|
||||
|
||||
リンカは、生成されたコードを実行可能ファイルに紐付けるプログラムです。実行可能ファイルの形式は Linux や Windows、macOS でそれぞれ異なるため、各システムにはそれぞれ異なるエラーを発生させる独自のリンカがあります。エラーの根本的な原因は同じです。リンカのデフォルト設定では、プログラムが C ランタイムに依存していると仮定していますが、実際にはしていません。
|
||||
|
||||
エラーを回避するためにはリンカに C ランタイムに依存しないことを伝える必要があります。これはリンカに一連の引数を渡すか、ベアメタルターゲット用にビルドすることで可能となります。
|
||||
|
||||
### ベアメタルターゲット用にビルドする
|
||||
|
||||
デフォルトでは、Rust は現在のシステム環境に合った実行可能ファイルをビルドしようとします。例えば、`x86_64` で Windows を使用している場合、Rust は `x86_64` 用の `.exe` Windows 実行可能ファイルをビルドしようとします。このような環境は「ホスト」システムと呼ばれます。
|
||||
|
||||
様々な環境を表現するために、Rust は [_target triple_] という文字列を使います。`rustc --version --verbose` を実行すると、ホストシステムの target triple を確認できます:
|
||||
|
||||
[_target triple_]: https://clang.llvm.org/docs/CrossCompilation.html#target-triple
|
||||
|
||||
```bash
|
||||
rustc 1.35.0-nightly (474e7a648 2019-04-07)
|
||||
binary: rustc
|
||||
commit-hash: 474e7a6486758ea6fc761893b1a49cd9076fb0ab
|
||||
commit-date: 2019-04-07
|
||||
host: x86_64-unknown-linux-gnu
|
||||
release: 1.35.0-nightly
|
||||
LLVM version: 8.0
|
||||
```
|
||||
|
||||
上記の出力は `x86_64` の Linux によるものです。`host` は `x86_64-unknown-linux-gnu` です。これには CPU アーキテクチャ(`x86_64`)、ベンダー(`unknown`)、OS(`Linux`)、そして [ABI] (`gnu`)が含まれています。
|
||||
|
||||
[ABI]: https://en.wikipedia.org/wiki/Application_binary_interface
|
||||
|
||||
ホストの triple 用にコンパイルすることで、Rust コンパイラとリンカは、デフォルトで C ランタイムを使用する Linux や Windows のような基盤となる OS があると想定し、それによってリンカエラーが発生します。なのでリンカエラーを回避するために、基盤となる OS を使用せずに異なる環境用にコンパイルします。
|
||||
|
||||
このようなベアメタル環境の例としては、`thumbv7em-none-eabihf` target triple があります。これは、[組込みシステム][embedded]を表しています。詳細は省きますが、重要なのは `none` という文字列からわかるように、 この target triple に基盤となる OS がないことです。このターゲット用にコンパイルできるようにするには、 rustup にこれを追加する必要があります:
|
||||
|
||||
[embedded]: https://en.wikipedia.org/wiki/Embedded_system
|
||||
|
||||
```bash
|
||||
rustup target add thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
これにより、この target triple 用の標準(およびコア)ライブラリのコピーがダウンロードされます。これで、このターゲット用にフリースタンディングな実行可能ファイルをビルドできます:
|
||||
|
||||
```bash
|
||||
cargo build --target thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
`--target` 引数を渡すことで、ベアメタルターゲット用に実行可能ファイルを[クロスコンパイル][cross compile]します。このターゲットシステムには OS がないため、リンカは C ランタイムをリンクしようとせず、ビルドはリンカエラーなしで成功します。
|
||||
|
||||
[cross compile]: https://en.wikipedia.org/wiki/Cross_compiler
|
||||
|
||||
これが私達の OS カーネルを書くために使うアプローチです。`thumbv7em-none-eabihf` の代わりに、`x86_64` のベアメタル環境を表す[カスタムターゲット][custom target]を使用することもできます。詳細は次のセクションで説明します。
|
||||
|
||||
[custom target]: https://doc.rust-lang.org/rustc/targets/custom.html
|
||||
|
||||
### リンカへの引数
|
||||
|
||||
ベアメタルターゲット用にコンパイルする代わりに、特定の引数のセットをリンカにわたすことでリンカエラーを回避することもできます。これは私達がカーネルに使用するアプローチではありません。したがって、このセクションはオプションであり、選択肢を増やすために書かれています。表示するには以下の「リンカへの引数」をクリックしてください。
|
||||
|
||||
<details>
|
||||
|
||||
<summary>リンカへの引数</summary>
|
||||
|
||||
このセクションでは、Linux、Windows、および macOS で発生するリンカエラーについてと、リンカに追加の引数を渡すことによってそれらを解決する方法を説明します。実行可能ファイルの形式とリンカは OS によって異なるため、システムごとに異なる引数のセットが必要です。
|
||||
|
||||
#### Linux
|
||||
|
||||
Linux では以下のようなエラーが発生します(抜粋):
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: /usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x12): undefined reference to `__libc_csu_fini'
|
||||
/usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x19): undefined reference to `__libc_csu_init'
|
||||
/usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x25): undefined reference to `__libc_start_main'
|
||||
collect2: error: ld returned 1 exit status
|
||||
```
|
||||
|
||||
問題は、デフォルトで C ランタイムの起動ルーチンがリンカに含まれていることです。これは `_start` とも呼ばれます。`no_std` attribute により、C 標準ライブラリ `libc` のいくつかのシンボルが必要となります。なので、リンカはこれらの参照を解決できません。これを解決するために、リンカに `-nostartfiles` フラグを渡して、C の起動ルーチンをリンクしないようにします。
|
||||
|
||||
Cargo を通してリンカの attribute を渡す方法の一つに、`cargo rustc` コマンドがあります。このコマンドは `cargo build` と全く同じように動作しますが、基本となる Rust コンパイラである `rustc` にオプションを渡すことができます。`rustc` にはリンカに引数を渡す `-C link-arg` フラグがあります。新しいビルドコマンドは次のようになります:
|
||||
|
||||
```bash
|
||||
cargo rustc -- -C link-arg=-nostartfiles
|
||||
```
|
||||
|
||||
これで crate を Linux 上で独立した実行ファイルとしてビルドできます!
|
||||
|
||||
リンカはデフォルトで `_start` という名前の関数を探すので、エントリポイントとなる関数の名前を明示的に指定する必要はありません。
|
||||
|
||||
#### Windows
|
||||
|
||||
Windows では別のリンカエラーが発生します(抜粋):
|
||||
|
||||
```
|
||||
error: linking with `link.exe` failed: exit code: 1561
|
||||
|
|
||||
= note: "C:\\Program Files (x86)\\…\\link.exe" […]
|
||||
= note: LINK : fatal error LNK1561: entry point must be defined
|
||||
```
|
||||
|
||||
"entry point must be defined" というエラーは、リンカがエントリポイントを見つけられていないことを意味します。Windows では、デフォルトのエントリポイント名は[使用するサブシステム][windows-subsystems]によって異なります。`CONSOLE` サブシステムの場合、リンカは `mainCRTStartup` という名前の関数を探し、`WINDOWS` サブシステムの場合は、`WinMainCRTStartup` という名前の関数を探します。デフォルトの動作を無効にし、代わりに `_start` 関数を探すようにリンカに指示するには、`/ENTRY` 引数をリンカに渡します:
|
||||
|
||||
[windows-subsystems]: https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol
|
||||
|
||||
```bash
|
||||
cargo rustc -- -C link-arg=/ENTRY:_start
|
||||
```
|
||||
|
||||
引数の形式が異なることから、Windows のリンカは Linux のリンカとは全く異なるプログラムであることが分かります。
|
||||
|
||||
これにより、別のリンカエラーが発生します:
|
||||
|
||||
```
|
||||
error: linking with `link.exe` failed: exit code: 1221
|
||||
|
|
||||
= note: "C:\\Program Files (x86)\\…\\link.exe" […]
|
||||
= note: LINK : fatal error LNK1221: a subsystem can't be inferred and must be
|
||||
defined
|
||||
```
|
||||
|
||||
このエラーは Windows での実行可能ファイルが異なる [subsystems][windows-subsystems] を使用することができるために発生します。通常のプログラムでは、エントリポイント名に基づいて推定されます。エントリポイントが `main` という名前の場合は `CONSOLE` サブシステムが使用され、エントリポイント名が `WinMain` である場合には `WINDOWS` サブシステムが使用されます。`_start` 関数は別の名前を持っているので、サブシステムを明示的に指定する必要があります:
|
||||
|
||||
This error occurs because Windows executables can use different [subsystems][windows-subsystems]. For normal programs they are inferred depending on the entry point name: If the entry point is named `main`, the `CONSOLE` subsystem is used, and if the entry point is named `WinMain`, the `WINDOWS` subsystem is used. Since our `_start` function has a different name, we need to specify the subsystem explicitly:
|
||||
|
||||
```bash
|
||||
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
||||
```
|
||||
|
||||
ここでは `CONSOLE` サブシステムを使用しますが、`WINDOWS` サブシステムを使うこともできます。`-C link-arg` を複数渡す代わりに、スペースで区切られたリストを引数として取る `-C link-args` を渡します。
|
||||
|
||||
このコマンドで、実行可能ファイルが Windows 上で正しくビルドされます。
|
||||
|
||||
#### macOS
|
||||
|
||||
macOS では次のようなリンカエラーが発生します(抜粋):
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: entry point (_main) undefined. for architecture x86_64
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
このエラーメッセージは、リンカがデフォルト名が `main` (いくつかの理由で、macOS 上ではすべての関数の前には `_` が付きます) であるエントリポイントとなる関数を見つけられないことを示しています。`_start` 関数をエントリポイントとして設定するには、`-e` というリンカ引数を渡します:
|
||||
|
||||
```bash
|
||||
cargo rustc -- -C link-args="-e __start"
|
||||
```
|
||||
|
||||
`-e` というフラグでエントリポイントとなる関数の名前を指定できます。macOS 上では全ての関数には `_` というプレフィックスが追加されるので、`_start` ではなく `__start` にエントリポイントを設定する必要があります。
|
||||
|
||||
これにより、次のようなリンカエラーが発生します:
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: dynamic main executables must link with libSystem.dylib
|
||||
for architecture x86_64
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
macOS は[正式には静的にリンクされたバイナリをサポートしておらず][does not officially support statically linked binaries]、プログラムはデフォルトで `libSystem` ライブラリにリンクされる必要があります。これを無効にして静的バイナリをリンクするには、`-static` フラグをリンカに渡します:
|
||||
|
||||
[does not officially support statically linked binaries]: https://developer.apple.com/library/archive/qa/qa1118/_index.html
|
||||
|
||||
```bash
|
||||
cargo rustc -- -C link-args="-e __start -static"
|
||||
```
|
||||
|
||||
これでもまだ十分ではありません、3つ目のリンカエラーが発生します:
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: library not found for -lcrt0.o
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
このエラーは、macOS 上のプログラムがデフォルトで `crt0` ("C runtime zero") にリンクされるために発生します。これは Linux 上で起きたエラーと似ており、`-nostartfiles` というリンカ引数を追加することで解決できます:
|
||||
|
||||
```bash
|
||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||
```
|
||||
|
||||
これで 私達のプログラムを macOS 上で正しくビルドできます。
|
||||
|
||||
#### ビルドコマンドの統一
|
||||
|
||||
現時点では、ホストプラットフォームによって異なるビルドコマンドを使っていますが、これは理想的ではありません。これを回避するために、プラットフォーム固有の引数を含む `.cargo/config.toml` というファイルを作成します:
|
||||
|
||||
```toml
|
||||
# in .cargo/config.toml
|
||||
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-nostartfiles"]
|
||||
|
||||
[target.'cfg(target_os = "windows")']
|
||||
rustflags = ["-C", "link-args=/ENTRY:_start /SUBSYSTEM:console"]
|
||||
|
||||
[target.'cfg(target_os = "macos")']
|
||||
rustflags = ["-C", "link-args=-e __start -static -nostartfiles"]
|
||||
```
|
||||
|
||||
`rustflags` には `rustc` を呼び出すたびに自動的に追加される引数が含まれています。`.cargo/config.toml` についての詳細は[公式のドキュメント][official documentation]を確認してください。
|
||||
|
||||
[official documentation]: https://doc.rust-lang.org/cargo/reference/config.html
|
||||
|
||||
これで私達のプログラムは3つすべてのプラットフォーム上で、シンプルに `cargo build` のみでビルドすることができるようになります。
|
||||
|
||||
#### 私達はこれをすべきですか?
|
||||
|
||||
これらの手順で Linux、Windows および macOS 用の独立した実行可能ファイルをビルドすることはできますが、おそらく良い方法ではありません。その理由は、例えば `_start` 関数が呼ばれたときにスタックが初期化されるなど、まだ色々なことを前提としているからです。C ランタイムがなければ、これらの要件のうちいくつかが満たされない可能性があり、セグメンテーション違反(segfault)などによってプログラムが失敗する可能性があります。
|
||||
|
||||
もし既存の OS 上で動作する最小限のバイナリを作成したいなら、`libc` を使って `#[start]` attribute を[ここ][no-stdlib]で説明するとおりに設定するのが良いでしょう。
|
||||
|
||||
[no-stdlib]: https://doc.rust-lang.org/1.16.0/book/no-stdlib.html
|
||||
|
||||
</details>
|
||||
|
||||
## 概要 {#summary}
|
||||
|
||||
最小限の独立した Rust バイナリは次のようになります:
|
||||
|
||||
`src/main.rs`:
|
||||
|
||||
```rust
|
||||
#![no_std] // Rust の標準ライブラリにリンクしない
|
||||
#![no_main] // 全ての Rust レベルのエントリポイントを無効にする
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
#[unsafe(no_mangle)] // この関数の名前修飾をしない
|
||||
pub extern "C" fn _start() -> ! {
|
||||
// リンカはデフォルトで `_start` という名前の関数を探すので、
|
||||
// この関数がエントリポイントとなる
|
||||
loop {}
|
||||
}
|
||||
|
||||
/// この関数はパニック時に呼ばれる
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
`Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "crate_name"
|
||||
version = "0.1.0"
|
||||
authors = ["Author Name <author@example.com>"]
|
||||
|
||||
# the profile used for `cargo build`
|
||||
[profile.dev]
|
||||
panic = "abort" # disable stack unwinding on panic
|
||||
|
||||
# the profile used for `cargo build --release`
|
||||
[profile.release]
|
||||
panic = "abort" # disable stack unwinding on panic
|
||||
```
|
||||
|
||||
このバイナリをビルドするために、`thumbv7em-none-eabihf` のようなベアメタルターゲット用にコンパイルする必要があります:
|
||||
|
||||
```bash
|
||||
cargo build --target thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
あるいは、追加のリンカ引数を渡してホストシステム用にコンパイルすることもできます:
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
cargo rustc -- -C link-arg=-nostartfiles
|
||||
# Windows
|
||||
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
||||
# macOS
|
||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||
```
|
||||
|
||||
これは独立した Rust バイナリの最小の例にすぎないことに注意してください。このバイナリは `_start` 関数が呼び出されたときにスタックが初期化されるなど、さまざまなことを前提としています。**そのため、このようなバイナリを実際に使用するには、より多くの手順が必要となります**。
|
||||
|
||||
## 次は?
|
||||
|
||||
[次の記事][next post]では、この独立したバイナリを最小限の OS カーネルにするために必要なステップを説明しています。カスタムターゲットの作成、実行可能ファイルとブートローダの組み合わせ、画面に何か文字を表示する方法について説明しています。
|
||||
|
||||
[next post]: @/edition-2/posts/02-minimal-rust-kernel/index.ja.md
|
||||
@@ -0,0 +1,545 @@
|
||||
+++
|
||||
title = "Rust로 'Freestanding 실행파일' 만들기"
|
||||
weight = 1
|
||||
path = "ko/freestanding-rust-binary"
|
||||
date = 2018-02-10
|
||||
|
||||
[extra]
|
||||
# Please update this when updating the translation
|
||||
translation_based_on_commit = "c1af4e31b14e562826029999b9ab1dce86396b93"
|
||||
# GitHub usernames of the people that translated this post
|
||||
translators = ["JOE1994", "Quqqu"]
|
||||
+++
|
||||
|
||||
운영체제 커널을 만드는 첫 단계는 표준 라이브러리(standard library)를 링크하지 않는 Rust 실행파일을 만드는 것입니다. 이 실행파일은 운영체제가 없는 [bare metal] 시스템에서 동작할 수 있습니다.
|
||||
|
||||
[bare metal]: https://en.wikipedia.org/wiki/Bare_machine
|
||||
|
||||
<!-- more -->
|
||||
|
||||
이 블로그는 [GitHub 저장소][GitHub]에서 오픈 소스로 개발되고 있으니, 문제나 문의사항이 있다면 저장소의 'Issue' 기능을 이용해 제보해주세요. [페이지 맨 아래][at the bottom]에 댓글을 남기실 수도 있습니다. 이 포스트와 관련된 모든 소스 코드는 저장소의 [`post-01 브랜치`][post branch]에서 확인하실 수 있습니다.
|
||||
|
||||
[GitHub]: https://github.com/phil-opp/blog_os
|
||||
[at the bottom]: #comments
|
||||
<!-- fix for zola anchor checker (target is in template): <a id="comments"> -->
|
||||
[post branch]: https://github.com/phil-opp/blog_os/tree/post-01
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## 소개
|
||||
운영체제 커널을 만드려면 운영체제에 의존하지 않는 코드가 필요합니다. 자세히 설명하자면, 스레드, 파일, 동적 메모리, 네트워크, 난수 생성기, 표준 출력 및 기타 운영체제의 추상화 또는 특정 하드웨어의 기능을 필요로 하는 것들은 전부 사용할 수 없다는 뜻입니다. 우리는 스스로 운영체제 및 드라이버를 직접 구현하려는 상황이니 어찌 보면 당연한 조건입니다.
|
||||
|
||||
운영체제에 의존하지 않으려면 [Rust 표준 라이브러리][Rust standard library]의 많은 부분을 사용할 수 없습니다.
|
||||
그래도 우리가 이용할 수 있는 Rust 언어 자체의 기능들은 많이 남아 있습니다. 예를 들어 [반복자][iterators], [클로저][closures], [패턴 매칭][pattern matching], [option] / [result], [문자열 포맷 설정][string formatting], 그리고 [소유권 시스템][ownership system] 등이 있습니다. 이러한 기능들은 우리가 커널을 작성할 때 [undefined behavior]나 [메모리 안전성][memory safety]에 대한 걱정 없이 큰 흐름 단위의 코드를 작성하는 데에 집중할 수 있도록 해줍니다.
|
||||
|
||||
[option]: https://doc.rust-lang.org/core/option/
|
||||
[result]:https://doc.rust-lang.org/core/result/
|
||||
[Rust standard library]: https://doc.rust-lang.org/std/
|
||||
[iterators]: https://doc.rust-lang.org/book/ch13-02-iterators.html
|
||||
[closures]: https://doc.rust-lang.org/book/ch13-01-closures.html
|
||||
[pattern matching]: https://doc.rust-lang.org/book/ch06-00-enums.html
|
||||
[string formatting]: https://doc.rust-lang.org/core/macro.write.html
|
||||
[ownership system]: https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html
|
||||
[undefined behavior]: https://www.nayuki.io/page/undefined-behavior-in-c-and-cplusplus-programs
|
||||
[memory safety]: https://tonyarcieri.com/it-s-time-for-a-memory-safety-intervention
|
||||
|
||||
Rust로 운영체제 커널을 작성하려면, 운영체제 없이도 실행가능한 실행파일이 필요합니다. 이러한 실행파일은
|
||||
보통 "freestanding 실행파일" 혹은 "bare-metal 실행파일" 이라고 불립니다.
|
||||
|
||||
이 포스트에서는 "freestanding 실행 파일" 을 만드는 데 필요한 것들을 여러 단계로 나누고, 각 단계가 왜 필요한지에 대해 설명해드립니다. 중간 과정은 생략하고 그저 최소한의 예제 코드만 확인하고 싶으시면 **[요약 섹션으로 넘어가시면 됩니다](#summary)**.
|
||||
|
||||
## Rust 표준 라이브러리 링크 해제하기
|
||||
모든 Rust 프로그램들은 Rust 표준 라이브러리를 링크하는데, 이 라이브러리는 스레드, 파일, 네트워킹 등의 기능을 제공하기 위해 운영체제에 의존합니다. Rust 표준 라이브러리는 또한 C 표준 라이브러리인 `libc`에도 의존합니다 (`libc`는 운영체제의 여러 기능들을 이용합니다).
|
||||
우리가 운영체제를 직접 구현하기 위해서는 운영체제를 이용하는 라이브러리들은 사용할 수 없습니다. 그렇기에 우선 [`no_std` 속성][`no_std` attribute]을 이용해 자동으로 Rust 표준 라이브러리가 링크되는 것을 막아야 합니다.
|
||||
|
||||
[standard library]: https://doc.rust-lang.org/std/
|
||||
[`no_std` attribute]: https://doc.rust-lang.org/1.30.0/book/first-edition/using-rust-without-the-standard-library.html
|
||||
|
||||
제일 먼저 아래의 명령어를 통해 새로운 cargo 애플리케이션 크레이트를 만듭니다.
|
||||
|
||||
```
|
||||
cargo new blog_os --bin --edition 2018
|
||||
```
|
||||
|
||||
프로젝트 이름은 `blog_os` 또는 원하시는 이름으로 정해주세요. `--bin` 인자는 우리가 cargo에게 실행 파일 (라이브러리와 대조됨)을 만들겠다고 알려주고, `--edition 2018` 인자는 cargo에게 우리가 [Rust 2018 에디션][2018 edition]을 사용할 것이라고 알려줍니다.
|
||||
위 명령어를 실행하고 나면, cargo가 아래와 같은 크레이트 디렉토리를 만들어줍니다.
|
||||
|
||||
[2018 edition]: https://doc.rust-lang.org/nightly/edition-guide/rust-2018/index.html
|
||||
|
||||
```
|
||||
blog_os
|
||||
├── Cargo.toml
|
||||
└── src
|
||||
└── main.rs
|
||||
```
|
||||
|
||||
크레이트 설정은 `Cargo.toml`에 전부 기록해야 합니다 (크레이트 이름, 크레이트 원작자, [semantic version] 번호, 의존 라이브러리 목록 등).
|
||||
`src/main.rs` 파일에 크레이트 실행 시 맨 처음 호출되는 `main` 함수를 포함한 중추 모듈이 있습니다.
|
||||
`cargo build` 명령어를 통해 크레이트를 빌드하면 `target/debug` 디렉토리에 `blog_os` 실행파일이 생성됩니다.
|
||||
|
||||
[semantic version]: https://semver.org/
|
||||
|
||||
### `no_std` 속성
|
||||
|
||||
현재 우리가 만든 크레이트는 암시적으로 Rust 표준 라이브러리를 링크합니다. 아래와 같이 [`no_std` 속성]을 이용해 더 이상 표준 라이브러리가 링크되지 않게 해줍니다.
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
이제 `cargo build` 명령어를 다시 실행하면 아래와 같은 오류 메세지가 뜰 것입니다:
|
||||
|
||||
```
|
||||
error: cannot find macro `println!` in this scope
|
||||
--> src/main.rs:4:5
|
||||
|
|
||||
4 | println!("Hello, world!");
|
||||
| ^^^^^^^
|
||||
```
|
||||
|
||||
이 오류가 뜨는 이유는 [`println` 매크로][`println` macro]를 제공하는 Rust 표준 라이브러리를 우리의 크레이트에 링크하지 않게 되었기 때문입니다.
|
||||
`println`은 [표준 입출력][standard output] (운영체제가 제공하는 특별한 파일 서술자)으로 데이터를 쓰기 때문에, 우리는 이제 `println`을 이용해 메세지를 출력할 수 없습니다.
|
||||
|
||||
[`println` macro]: https://doc.rust-lang.org/std/macro.println.html
|
||||
[standard output]: https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29
|
||||
|
||||
`println` 매크로 호출 코드를 지운 후 크레이트를 다시 빌드해봅시다.
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
fn main() {}
|
||||
```
|
||||
|
||||
```
|
||||
> cargo build
|
||||
error: `#[panic_handler]` function required, but not found
|
||||
error: language item required, but not found: `eh_personality`
|
||||
```
|
||||
|
||||
오류 메세지를 통해 컴파일러가 `#[panic_handler]` 함수와 _language item_ 을 필요로 함을 확인할 수 있습니다.
|
||||
|
||||
## 패닉 (Panic) 시 호출되는 함수 구현하기
|
||||
|
||||
컴파일러는 [패닉][panic]이 일어날 경우 `panic_handler` 속성이 적용된 함수가 호출되도록 합니다. 표준 라이브러리는 패닉 시 호출되는 함수가 제공되지만, `no_std` 환경에서는 우리가 패닉 시 호출될 함수를 직접 설정해야 합니다.
|
||||
|
||||
[panic]: https://doc.rust-lang.org/stable/book/ch09-01-unrecoverable-errors-with-panic.html
|
||||
|
||||
```rust
|
||||
// in main.rs
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
/// 패닉이 일어날 경우, 이 함수가 호출됩니다.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
[`PanicInfo` 인자][PanicInfo]는 패닉이 일어난 파일명, 패닉이 파일 내 몇 번째 줄에서 일어났는지, 그리고 패닉시 전달된 메세지에 대한 정보를 가진 구조체입니다.
|
||||
위 `panic` 함수는 절대로 반환하지 않기에 ["never" 타입][“never” type] `!`을 반환하도록 적어 컴파일러에게 이 함수가 [반환 함수][diverging function]임을 알립니다.
|
||||
당장 이 함수에서 우리가 하고자 하는 일은 없기에 그저 함수가 반환하지 않도록 무한루프를 넣어줍니다.
|
||||
|
||||
[PanicInfo]: https://doc.rust-lang.org/nightly/core/panic/struct.PanicInfo.html
|
||||
[diverging function]: https://doc.rust-lang.org/1.30.0/book/first-edition/functions.html#diverging-functions
|
||||
[“never” type]: https://doc.rust-lang.org/nightly/std/primitive.never.html
|
||||
|
||||
## `eh_personality` Language Item
|
||||
|
||||
Language item은 컴파일러가 내부적으로 요구하는 특별한 함수 및 타입들을 가리킵니다. 예를 들어 [`Copy`] 트레잇은 어떤 타입들이 [_copy semantics_][`Copy`] 를 가지는지 컴파일러에게 알려주는 language item 입니다.
|
||||
[`Copy` 트레잇이 구현된 코드][copy code]에 있는 `#[lang = "copy"]` 속성을 통해 이 트레잇이 language item으로 선언되어 있음을 확인할 수 있습니다.
|
||||
|
||||
[`Copy`]: https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html
|
||||
[copy code]: https://github.com/rust-lang/rust/blob/485397e49a02a3b7ff77c17e4a3f16c653925cb3/src/libcore/marker.rs#L296-L299
|
||||
|
||||
임의로 구현한 language item을 사용할 수는 있지만, 위험할 수도 있기에 주의해야 합니다.
|
||||
그 이유는 language item의 구현 코드는 매우 자주 변경되어 불안정하며, language item에 대해서 컴파일러가 타입 체크 조차 하지 않습니다 (예시: language item 함수의 인자 타입이 정확한지 조차 체크하지 않습니다).
|
||||
임의로 구현한 language item을 이용하는 것보다 더 안정적으로 위의 language item 오류를 고칠 방법이 있습니다.
|
||||
|
||||
[`eh_personality` language item]은 [스택 되감기 (stack unwinding)][stack unwinding]을 구현하는 함수를 가리킵니다. 기본적으로 Rust는 [패닉][panic]이 일어났을 때 스택 되감기를 통해 스택에 살아있는 각 변수의 소멸자를 호출합니다. 이를 통해 자식 스레드에서 사용 중이던 모든 메모리 리소스가 반환되고, 부모 스레드가 패닉에 대처한 후 계속 실행될 수 있게 합니다. 스택 되감기는 복잡한 과정으로 이루어지며 운영체제마다 특정한 라이브러리를 필요로 하기에 (예: Linux는 [libunwind], Windows는 [structured exception handling]), 우리가 구현할 운영체제에서는 이 기능을 사용하지 않을 것입니다.
|
||||
|
||||
[`eh_personality` language item]: https://github.com/rust-lang/rust/blob/edb368491551a77d77a48446d4ee88b35490c565/src/libpanic_unwind/gcc.rs#L11-L45
|
||||
[stack unwinding]: https://www.bogotobogo.com/cplusplus/stackunwinding.php
|
||||
[libunwind]: https://www.nongnu.org/libunwind/
|
||||
[structured exception handling]: https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling
|
||||
|
||||
### 스택 되감기를 해제하는 방법
|
||||
|
||||
스택 되감기가 불필요한 상황들이 여럿 있기에, Rust 언어는 [패닉 시 실행 종료][abort on panic] 할 수 있는 선택지를 제공합니다. 이는 스택 되감기에 필요한 심볼 정보 생성을 막아주어 실행 파일의 크기 자체도 많이 줄어들게 됩니다. 스택 되감기를 해제하는 방법은 여러가지 있지만, 가장 쉬운 방법은 `Cargo.toml` 파일에 아래의 코드를 추가하는 것입니다.
|
||||
|
||||
```toml
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
```
|
||||
|
||||
위의 코드를 통해 `dev` 빌드 (`cargo build` 실행)와 `release` 빌드 (`cargo build --release` 실행) 에서 모두 패닉 시 실행이 종료되도록 설정되었습니다.
|
||||
이제 더 이상 컴파일러가 `eh_personality` language item을 필요로 하지 않습니다.
|
||||
|
||||
[abort on panic]: https://github.com/rust-lang/rust/pull/32900
|
||||
|
||||
위에서 본 오류들을 고쳤지만, 크레이트를 빌드하려고 하면 새로운 오류가 뜰 것입니다:
|
||||
|
||||
```
|
||||
> cargo build
|
||||
error: requires `start` lang_item
|
||||
```
|
||||
|
||||
우리의 프로그램에는 프로그램 실행 시 최초 실행 시작 지점을 지정해주는 `start` language item이 필요합니다.
|
||||
|
||||
## `start` 속성
|
||||
|
||||
혹자는 프로그램 실행 시 언제나 `main` 함수가 가장 먼저 호출된다고 생각할지도 모릅니다. 대부분의 프로그래밍 언어들은 [런타임 시스템][runtime system]을 가지고 있는데, 이는 가비지 컬렉션 (예시: Java) 혹은 소프트웨어 스레드 (예시: GoLang의 goroutine) 등의 기능을 담당합니다.
|
||||
이러한 런타임 시스템은 프로그램 실행 이전에 초기화 되어야 하기에 `main` 함수 호출 이전에 먼저 호출됩니다.
|
||||
|
||||
[runtime system]: https://en.wikipedia.org/wiki/Runtime_system
|
||||
|
||||
러스트 표준 라이브러리를 링크하는 전형적인 러스트 실행 파일의 경우, 프로그램 실행 시 C 런타임 라이브러리인 `crt0` (“C runtime zero”) 에서 실행이 시작됩니다. `crt0`는 C 프로그램의 환경을 설정하고 초기화하는 런타임 시스템으로, 스택을 만들고 프로그램에 주어진 인자들을 적절한 레지스터에 배치합니다. `crt0`가 작업을 마친 후 `start` language item으로 지정된 [Rust 런타임의 실행 시작 함수][rt::lang_start]를 호출합니다.
|
||||
Rust는 최소한의 런타임 시스템을 가지며, 주요 기능은 스택 오버플로우 가드를 초기화하고 패닉 시 역추적 (backtrace) 정보를 출력하는 것입니다. Rust 런타임의 초기화 작업이 끝난 후에야 `main` 함수가 호출됩니다.
|
||||
|
||||
[rt::lang_start]: https://github.com/rust-lang/rust/blob/bb4d1491466d8239a7a5fd68bd605e3276e97afb/src/libstd/rt.rs#L32-L73
|
||||
|
||||
우리의 "freestanding 실행 파일" 은 Rust 런타임이나 `crt0`에 접근할 수 없기에, 우리가 직접 프로그램 실행 시작 지점을 지정해야 합니다.
|
||||
`crt0`가 `start` language item을 호출해주는 방식으로 동작하기에, `start` language item을 구현하고 지정하는 것만으로는 문제를 해결할 수 없습니다.
|
||||
대신 우리가 직접 `crt0`의 시작 지점을 대체할 새로운 실행 시작 지점을 제공해야 합니다.
|
||||
|
||||
### 실행 시작 지점 덮어쓰기
|
||||
`#![no_main]` 속성을 이용해 Rust 컴파일러에게 우리가 일반적인 실행 시작 호출 단계를 이용하지 않겠다고 선언합니다.
|
||||
|
||||
```rust
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
/// 패닉이 일어날 경우, 이 함수가 호출됩니다.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
`main` 함수가 사라진 것을 눈치채셨나요? `main` 함수를 호출해주는 런타임 시스템이 없는 이상 `main` 함수의 존재도 더 이상 의미가 없습니다.
|
||||
우리는 운영체제가 호출하는 프로그램 실행 시작 지점 대신 우리의 새로운 `_start` 함수를 실행 시작 지점으로 대체할 것입니다.
|
||||
|
||||
```rust
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _start() -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
`#[unsafe(no_mangle)]` 속성을 통해 [name mangling]을 해제하여 Rust 컴파일러가 `_start` 라는 이름 그대로 함수를 만들도록 합니다. 이 속성이 없다면, 컴파일러가 각 함수의 이름을 고유하게 만드는 과정에서 이 함수의 실제 이름을 `_ZN3blog_os4_start7hb173fedf945531caE` 라는 이상한 이름으로 바꿔 생성합니다. 우리가 원하는 실제 시작 지점 함수의 이름을 정확히 알고 있어야 링커 (linker)에도 그 이름을 정확히 전달할 수 있기에 (후속 단계에서 진행) `#[unsafe(no_mangle)]` 속성이 필요합니다.
|
||||
|
||||
또한 우리는 이 함수에 `extern "C"`라는 표시를 추가하여 이 함수가 Rust 함수 호출 규약 대신에 [C 함수 호출 규약][C calling convention]을 사용하도록 합니다. 함수의 이름을 `_start`로 지정한 이유는 그저 런타임 시스템들의 실행 시작 함수 이름이 대부분 `_start`이기 때문입니다.
|
||||
|
||||
[name mangling]: https://en.wikipedia.org/wiki/Name_mangling
|
||||
[C calling convention]: https://en.wikipedia.org/wiki/Calling_convention
|
||||
|
||||
`!` 반환 타입은 이 함수가 발산 함수라는 것을 의미합니다. 시작 지점 함수는 오직 운영체제나 부트로더에 의해서만 직접 호출됩니다. 따라서 시작 지점 함수는 반환하는 대신 운영체제의 [`exit` 시스템콜][`exit` system call]을 이용해 종료됩니다. 우리의 "freestanding 실행 파일" 은 실행 종료 후 더 이상 실행할 작업이 없기에, 시작 지점 함수가 작업을 마친 후 기기를 종료하는 것이 합리적입니다. 여기서는 일단 `!` 타입의 조건을 만족시키기 위해 무한루프를 넣어 줍니다.
|
||||
|
||||
[`exit` system call]: https://en.wikipedia.org/wiki/Exit_(system_call)
|
||||
|
||||
다시 `cargo build`를 실행하면, 끔찍한 _링커_ 오류를 마주하게 됩니다.
|
||||
|
||||
## 링커 오류
|
||||
|
||||
링커는 컴파일러가 생성한 코드들을 묶어 실행파일로 만드는 프로그램입니다. 실행 파일 형식은 Linux, Windows, macOS 마다 전부 다르기에 각 운영체제는 자신만의 링커가 있고 링커마다 다른 오류 메세지를 출력할 것입니다.
|
||||
오류가 나는 근본적인 원인은 모두 동일한데, 링커는 주어진 프로그램이 C 런타임 시스템을 이용할 것이라고 가정하는 반면 우리의 크레이트는 그렇지 않기 때문입니다.
|
||||
|
||||
이 링커 오류를 해결하려면 링커에게 C 런타임을 링크하지 말라고 알려줘야 합니다. 두 가지 방법이 있는데, 하나는 링커에 특정 인자들을 주는 것이고, 또다른 하나는 크레이트 컴파일 대상 기기를 bare metal 기기로 설정하는 것입니다.
|
||||
|
||||
### Bare Metal 시스템을 목표로 빌드하기
|
||||
|
||||
기본적으로 Rust는 당신의 현재 시스템 환경에서 실행할 수 있는 실행파일을 생성하고자 합니다. 예를 들어 Windows `x86_64` 사용자의 경우, Rust는 `x86_64` 명령어 셋을 사용하는 `.exe` 확장자 실행파일을 생성합니다. 사용자의 기본 시스템 환경을 "호스트" 시스템이라고 부릅니다.
|
||||
|
||||
여러 다른 시스템 환경들을 표현하기 위해 Rust는 [_target triple_]이라는 문자열을 이용합니다. 현재 호스트 시스템의 target triple이 궁금하시다면 `rustc --version --verbose` 명령어를 실행하여 확인 가능합니다.
|
||||
|
||||
[_target triple_]: https://clang.llvm.org/docs/CrossCompilation.html#target-triple
|
||||
|
||||
```
|
||||
rustc 1.35.0-nightly (474e7a648 2019-04-07)
|
||||
binary: rustc
|
||||
commit-hash: 474e7a6486758ea6fc761893b1a49cd9076fb0ab
|
||||
commit-date: 2019-04-07
|
||||
host: x86_64-unknown-linux-gnu
|
||||
release: 1.35.0-nightly
|
||||
LLVM version: 8.0
|
||||
```
|
||||
|
||||
위의 출력 내용은 `x86_64` Linux 시스템에서 얻은 것입니다. 호스트 target triple이 `x86_64-unknown-linux-gnu`으로 나오는데, 이는 CPU 아키텍쳐 정보 (`x86_64`)와 하드웨어 판매자 (`unknown`), 운영체제 (`linux`) 그리고 [응용 프로그램 이진 인터페이스 (ABI)][ABI] (`gnu`) 정보를 모두 담고 있습니다.
|
||||
|
||||
[ABI]: https://en.wikipedia.org/wiki/Application_binary_interface
|
||||
|
||||
우리의 호스트 시스템 triple을 위해 컴파일하는 경우, Rust 컴파일러와 링커는 Linux나 Windows와 같은 운영체제가 있다고 가정하고 또한 운영체제가 C 런타임 시스템을 사용할 것이라고 가정하기 때문에 링커 오류 메세지가 출력된 것입니다. 이런 링커 오류를 피하려면 운영체제가 없는 시스템 환경에서 코드가 구동하는 것을 목표로 컴파일해야 합니다.
|
||||
|
||||
운영체제가 없는 bare metal 시스템 환경의 한 예시로 `thumbv7em-none-eabihf` target triple이 있습니다 (이는 [임베디드][embedded] [ARM] 시스템을 가리킵니다). Target triple의 `none`은 시스템에 운영체제가 동작하지 않음을 의미하며, 이 target triple의 나머지 부분의 의미는 아직 모르셔도 괜찮습니다. 이 시스템 환경에서 구동 가능하도록 컴파일하려면 rustup에서 해당 시스템 환경을 추가해야 합니다.
|
||||
|
||||
[embedded]: https://en.wikipedia.org/wiki/Embedded_system
|
||||
[ARM]: https://en.wikipedia.org/wiki/ARM_architecture
|
||||
|
||||
```
|
||||
rustup target add thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
위 명령어를 실행하면 해당 시스템을 위한 Rust 표준 라이브러리 및 코어 라이브러리를 설치합니다. 이제 해당 target triple을 목표로 하는 freestanding 실행파일을 만들 수 있습니다.
|
||||
|
||||
```
|
||||
cargo build --target thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
`--target` 인자를 통해 우리가 해당 bare metal 시스템을 목표로 [크로스 컴파일][cross compile]할 것이라는 것을 cargo에게 알려줍니다. 목표 시스템 환경에 운영체제가 없는 것을 링커도 알기 때문에 C 런타임을 링크하려고 시도하지 않으며 이제는 링커 에러 없이 빌드가 성공할 것입니다.
|
||||
|
||||
[cross compile]: https://en.wikipedia.org/wiki/Cross_compiler
|
||||
|
||||
우리는 이 방법을 이용하여 우리의 운영체제 커널을 빌드해나갈 것입니다. 위에서 보인 `thumbv7em-none-eabihf` 시스템 환경 대신 bare metal `x86_64` 시스템 환경을 묘사하는 [커스텀 시스템 환경][custom target]을 설정하여 빌드할 것입니다. 더 자세한 내용은 다음 포스트에서 더 설명하겠습니다.
|
||||
|
||||
[custom target]: https://doc.rust-lang.org/rustc/targets/custom.html
|
||||
|
||||
### 링커 인자
|
||||
|
||||
Bare metal 시스템을 목표로 컴파일하는 대신, 링커에게 특정 인자들을 추가로 주어 링커 오류를 해결하는 방법도 있습니다.
|
||||
이 방법은 앞으로 우리가 작성해나갈 커널 코드를 빌드할 때는 사용하지 않을 것이지만, 더 알고싶어 하실 분들을 위해서 이 섹션을 준비했습니다.
|
||||
아래의 _"링커 인자"_ 텍스트를 눌러 이 섹션의 내용을 확인하세요.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>링커 인자</summary>
|
||||
|
||||
이 섹션에서는 Linux, Windows 그리고 macOS 각각의 운영체제에서 나타나는 링커 오류에 대해 다루고 각 운영체제마다 링커에 어떤 추가 인자들을 주어 링커 오류를 해결할 수 있는지 설명할 것입니다.
|
||||
|
||||
#### Linux
|
||||
|
||||
Linux 에서는 아래와 같은 링커 오류 메세지가 출력됩니다 (일부 생략됨):
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: /usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x12): undefined reference to `__libc_csu_fini'
|
||||
/usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x19): undefined reference to `__libc_csu_init'
|
||||
/usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x25): undefined reference to `__libc_start_main'
|
||||
collect2: error: ld returned 1 exit status
|
||||
```
|
||||
|
||||
이 상황을 설명하자면 링커가 기본적으로 C 런타임의 실행 시작 루틴을 링크하는데, 이 루틴 역시 `_start`라는 이름을 가집니다. 이 `_start` 루틴은 C 표준 라이브러리 (`libc`)가 포함하는 여러 symbol들을 필요로 하지만, 우리는 `no_std` 속성을 이용해 크레이트에서 `libc`를 링크하지 않기 때문에 링커가 몇몇 symbol들의 출처를 찾지 못하여 위와 같은 링커 오류 메세지가 출력되는 것입니다. 이 문제를 해결하려면, 링커에게 `--nostartfiles` 인자를 전달하여 더 이상 링커가 C 런타임의 실행 시작 루틴을 링크하지 않도록 해야 합니다.
|
||||
|
||||
링커에 인자를 전달하는 한 방법은 `cargo rustc` 명령어를 이용하는 것입니다. 이 명령어는 `cargo build`와 유사하게 동작하나, `rustc`(Rust 컴파일러)에 직접 인자를 전달할 수 있게 해줍니다. `rustc`는 `-C link-arg` 인자를 통해 링커에게 인자를 전달할 수 있게 해줍니다. 우리가 이용할 새로운 빌드 명령어는 아래와 같습니다:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-arg=-nostartfiles
|
||||
```
|
||||
|
||||
이제 우리의 크레이트가 성공적으로 빌드되고 Linux에서 동작하는 freestanding 실행파일이 생성됩니다!
|
||||
|
||||
우리는 위의 빌드 명령어에서 실행 시작 함수의 이름을 명시적으로 전달하지 않았는데, 그 이유는 링커가 기본적으로 `_start` 라는 이름의 함수를 찾아 그 함수를 실행 시작 함수로 이용하기 때문입니다.
|
||||
|
||||
#### Windows
|
||||
|
||||
Windows에서는 다른 링커 오류를 마주하게 됩니다 (일부 생략):
|
||||
|
||||
```
|
||||
error: linking with `link.exe` failed: exit code: 1561
|
||||
|
|
||||
= note: "C:\\Program Files (x86)\\…\\link.exe" […]
|
||||
= note: LINK : fatal error LNK1561: entry point must be defined
|
||||
```
|
||||
|
||||
오류 메세지 "entry point must be defined"는 링커가 실행 시작 지점을 찾을 수 없다는 것을 알려줍니다. Windows에서는 기본 실행 시작 지점의 이름이 [사용 중인 서브시스템(subsystem)에 따라 다릅니다][windows-subsystems]. `CONSOLE` 서브시스템의 경우 링커가 `mainCRTStartup`이라는 함수를 실행 시작 지점으로 간주하고, `WINDOWS` 서브시스템의 경우 링커가 `WinMainCRTStartup`이라는 이름의 함수를 실행 시작 지점으로 간주합니다. 이러한 기본값을 변경하여 링커가 `_start`라는 이름의 함수를 실행 시작 지점으로 간주하도록 만드려면 링커에 `/ENTRY` 인자를 넘겨주어야 합니다:
|
||||
|
||||
[windows-subsystems]: https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-arg=/ENTRY:_start
|
||||
```
|
||||
|
||||
Linux에서와는 다른 인자 형식을 통해 Windows의 링커는 Linux의 링커와 완전히 다른 프로그램이라는 것을 유추할 수 있습니다.
|
||||
|
||||
이제 또 다른 링커 오류가 발생합니다:
|
||||
|
||||
```
|
||||
error: linking with `link.exe` failed: exit code: 1221
|
||||
|
|
||||
= note: "C:\\Program Files (x86)\\…\\link.exe" […]
|
||||
= note: LINK : fatal error LNK1221: a subsystem can't be inferred and must be
|
||||
defined
|
||||
```
|
||||
|
||||
이 오류가 뜨는 이유는 Windows 실행파일들은 여러 가지 [서브시스템][windows-subsystems]을 사용할 수 있기 때문입니다. 일반적인 프로그램들의 경우, 실행 시작 지점 함수의 이름에 따라 어떤 서브시스템을 사용하는지 추론합니다: 실행 시작 지점의 이름이 `main`인 경우 `CONSOLE` 서브시스템이 사용 중이라는 것을 알 수 있으며, 실행 시작 지점의 이름이 `WinMain`인 경우 `WINDOWS` 서브시스템이 사용 중이라는 것을 알 수 있습니다. 우리는 `_start`라는 새로운 이름의 실행 시작 지점을 이용할 것이기에, 우리가 어떤 서브시스템을 사용할 것인지 인자를 통해 명시적으로 링커에게 알려줘야 합니다:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
||||
```
|
||||
|
||||
위 명령어에서는 `CONSOLE` 서브시스템을 서용했지만, `WINDOWS` 서브시스템을 적용해도 괜찮습니다. `-C link-arg` 인자를 반복해서 쓰는 대신, `-C link-args`인자를 이용해 여러 인자들을 빈칸으로 구분하여 전달할 수 있습니다.
|
||||
|
||||
이 명령어를 통해 우리의 실행 파일을 Windows에서도 성공적으로 빌드할 수 있을 것입니다.
|
||||
|
||||
#### macOS
|
||||
|
||||
macOS에서는 아래와 같은 링커 오류가 출력됩니다 (일부 생략):
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: entry point (_main) undefined. for architecture x86_64
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
위 오류 메세지는 우리에게 링커가 실행 시작 지점 함수의 기본값 이름 `main`을 찾지 못했다는 것을 알려줍니다 (macOS에서는 무슨 이유에서인지 모든 함수들의 이름 맨 앞에 `_` 문자가 앞에 붙습니다). 실행 시작 지점 함수의 이름을 `_start`로 새롭게 지정해주기 위해 아래와 같이 링커 인자 `-e`를 이용합니다:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start"
|
||||
```
|
||||
|
||||
`-e` 인자를 통해 실행 시작 지점 함수 이름을 설정합니다. macOS에서는 모든 함수의 이름 앞에 추가로 `_` 문자가 붙기에, 실행 시작 지점 함수의 이름을 `_start` 대신 `__start`로 지정해줍니다.
|
||||
|
||||
이제 아래와 같은 링커 오류가 나타날 것입니다:
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: dynamic main executables must link with libSystem.dylib
|
||||
for architecture x86_64
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
macOS는 [공식적으로는 정적으로 링크된 실행파일을 지원하지 않으며][does not officially support statically linked binaries], 기본적으로 모든 프로그램이 `libSystem` 라이브러리를 링크하도록 요구합니다. 이러한 기본 요구사항을 무시하고 정적으로 링크된 실행 파일을 만드려면 링커에게 `-static` 인자를 주어야 합니다:
|
||||
|
||||
[does not officially support statically linked binaries]: https://developer.apple.com/library/archive/qa/qa1118/_index.html
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start -static"
|
||||
```
|
||||
|
||||
아직도 충분하지 않았는지, 세 번째 링커 오류가 아래와 같이 출력됩니다:
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: library not found for -lcrt0.o
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
이 오류가 뜨는 이유는 macOS에서 모든 프로그램은 기본적으로 `crt0` (“C runtime zero”)를 링크하기 때문입니다. 이 오류는 우리가 Linux에서 봤던 오류와 유사한 것으로, 똑같이 링커에 `-nostartfiles` 인자를 주어 해결할 수 있습니다:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||
```
|
||||
|
||||
이제는 우리의 프로그램을 macOS에서 성공적으로 빌드할 수 있을 것입니다.
|
||||
|
||||
#### 플랫폼 별 빌드 명령어들을 하나로 통합하기
|
||||
|
||||
위에서 살펴본 대로 호스트 플랫폼 별로 상이한 빌드 명령어가 필요한데, `.cargo/config.toml` 이라는 파일을 만들고 플랫폼 마다 필요한 상이한 인자들을 명시하여 여러 빌드 명령어들을 하나로 통합할 수 있습니다.
|
||||
|
||||
```toml
|
||||
# in .cargo/config.toml
|
||||
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-nostartfiles"]
|
||||
|
||||
[target.'cfg(target_os = "windows")']
|
||||
rustflags = ["-C", "link-args=/ENTRY:_start /SUBSYSTEM:console"]
|
||||
|
||||
[target.'cfg(target_os = "macos")']
|
||||
rustflags = ["-C", "link-args=-e __start -static -nostartfiles"]
|
||||
```
|
||||
|
||||
`rustflags`에 포함된 인자들은 `rustc`가 실행될 때마다 자동적으로 `rustc`에 인자로 전달됩니다. `.cargo/config.toml`에 대한 더 자세한 정보는 [공식 안내 문서](https://doc.rust-lang.org/cargo/reference/config.html)를 통해 확인해주세요.
|
||||
|
||||
이제 `cargo build` 명령어 만으로 세 가지 플랫폼 어디에서도 우리의 프로그램을 성공적으로 빌드할 수 있습니다.
|
||||
|
||||
#### 이렇게 하는 것이 괜찮나요?
|
||||
|
||||
Linux, Windows 또는 macOS 위에서 동작하는 freestanding 실행파일을 빌드하는 것이 가능하긴 해도 좋은 방법은 아닙니다. 운영체제가 갖춰진 환경을 목표로 빌드를 한다면, 실행 파일 동작 시 다른 많은 조건들이 런타임에 의해 제공될 것이라는 가정 하에 빌드가 이뤄지기 때문입니다 (예: 실행 파일이 `_start` 함수가 호출되는 시점에 이미 스택이 초기화되어있을 것이라고 간주하고 작동합니다). C 런타임 없이는 실행 파일이 필요로 하는 조건들이 갖춰지지 않아 결국 세그멘테이션 오류가 나는 등 프로그램이 제대로 실행되지 못할 수 있습니다.
|
||||
|
||||
이미 존재하는 운영체제 위에서 동작하는 최소한의 실행 파일을 만들고 싶다면, `libc`를 링크하고 [이 곳의 설명](https://doc.rust-lang.org/1.16.0/book/no-stdlib.html)에 따라 `#[start]` 속성을 설정하는 것이 더 좋은 방법일 것입니다.
|
||||
|
||||
</details>
|
||||
|
||||
## 요약 {#summary}
|
||||
|
||||
아래와 같은 최소한의 코드로 "freestanding" Rust 실행파일을 만들 수 있습니다:
|
||||
|
||||
`src/main.rs`:
|
||||
|
||||
```rust
|
||||
#![no_std] // Rust 표준 라이브러리를 링크하지 않도록 합니다
|
||||
#![no_main] // Rust 언어에서 사용하는 실행 시작 지점 (main 함수)을 사용하지 않습니다
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
#[unsafe(no_mangle)] // 이 함수의 이름을 mangle하지 않습니다
|
||||
pub extern "C" fn _start() -> ! {
|
||||
// 링커는 기본적으로 '_start' 라는 이름을 가진 함수를 실행 시작 지점으로 삼기에,
|
||||
// 이 함수는 실행 시작 지점이 됩니다
|
||||
loop {}
|
||||
}
|
||||
|
||||
/// 패닉이 일어날 경우, 이 함수가 호출됩니다.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
`Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "crate_name"
|
||||
version = "0.1.0"
|
||||
authors = ["Author Name <author@example.com>"]
|
||||
|
||||
# `cargo build` 실행 시 이용되는 빌드 설정
|
||||
[profile.dev]
|
||||
panic = "abort" # 패닉 시 스택 되감기를 하지 않고 바로 프로그램 종료
|
||||
|
||||
# `cargo build --release` 실행 시 이용되는 빌드 설정
|
||||
[profile.release]
|
||||
panic = "abort" # 패닉 시 스택 되감기를 하지 않고 바로 프로그램 종료
|
||||
```
|
||||
|
||||
이 실행 파일을 빌드하려면, `thumbv7em-none-eabihf`와 같은 bare metal 시스템 환경을 목표로 컴파일해야 합니다:
|
||||
|
||||
```
|
||||
cargo build --target thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
또다른 방법으로, 각 호스트 시스템마다 추가적인 링커 인자들을 전달해주어 호스트 시스템 환경을 목표로 컴파일할 수도 있습니다:
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
cargo rustc -- -C link-arg=-nostartfiles
|
||||
# Windows
|
||||
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
||||
# macOS
|
||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||
```
|
||||
|
||||
주의할 것은 이것이 정말 최소한의 freestanding Rust 실행 파일이라는 것입니다. 실행 파일은 여러 가지 조건들을 가정하는데, 그 예로 실행파일 동작 시 `_start` 함수가 호출될 때 스택이 초기화되어 있을 것을 가정합니다. **이 freestanding 실행 파일을 이용해 실제로 유용한 작업을 처리하려면 아직 더 많은 코드 구현이 필요합니다**.
|
||||
|
||||
## 다음 단계는 무엇일까요?
|
||||
|
||||
[다음 포스트][next post]에서는 우리의 freestanding 실행 파일을 최소한의 기능을 갖춘 운영체제 커널로 만드는 과정을 단게별로 설명할 것입니다.
|
||||
예시로 커스텀 시스템 환경을 설정하는 방법, 우리의 실행 파일을 부트로더와 합치는 방법, 그리고 화면에 메세지를 출력하는 방법 등에 대해 다루겠습니다.
|
||||
|
||||
[next post]: @/edition-2/posts/02-minimal-rust-kernel/index.md
|
||||
@@ -6,6 +6,9 @@ date = 2018-02-10
|
||||
|
||||
[extra]
|
||||
chapter = "Bare Bones"
|
||||
|
||||
# GitHub usernames of the people that translated this post
|
||||
translators = ["dobleuber"]
|
||||
+++
|
||||
|
||||
The first step in creating our own operating system kernel is to create a Rust executable that does not link the standard library. This makes it possible to run Rust code on the [bare metal] without an underlying operating system.
|
||||
@@ -18,6 +21,7 @@ This blog is openly developed on [GitHub]. If you have any problems or questions
|
||||
|
||||
[GitHub]: https://github.com/phil-opp/blog_os
|
||||
[at the bottom]: #comments
|
||||
<!-- fix for zola anchor checker (target is in template): <a id="comments"> -->
|
||||
[post branch]: https://github.com/phil-opp/blog_os/tree/post-01
|
||||
|
||||
<!-- toc -->
|
||||
@@ -43,7 +47,7 @@ In order to create an OS kernel in Rust, we need to create an executable that ca
|
||||
This post describes the necessary steps to create a freestanding Rust binary and explains why the steps are needed. If you're just interested in a minimal example, you can **[jump to the summary](#summary)**.
|
||||
|
||||
## Disabling the Standard Library
|
||||
By default, all Rust crates link the [standard library], which depends on the operating system for features such as threads, files, or networking. It also depends on the C standard library `libc`, which closely interacts with OS services. Since our plan is to write an operating system, we can not use any OS-dependent libraries. So we have to disable the automatic inclusion of the standard library through the [`no_std` attribute].
|
||||
By default, all Rust crates link the [standard library], which depends on the operating system for features such as threads, files, or networking. It also depends on the C standard library `libc`, which closely interacts with OS services. Since our plan is to write an operating system, we can't use any OS-dependent libraries. So we have to disable the automatic inclusion of the standard library through the [`no_std` attribute].
|
||||
|
||||
[standard library]: https://doc.rust-lang.org/std/
|
||||
[`no_std` attribute]: https://doc.rust-lang.org/1.30.0/book/first-edition/using-rust-without-the-standard-library.html
|
||||
@@ -56,7 +60,7 @@ cargo new blog_os --bin --edition 2018
|
||||
|
||||
I named the project `blog_os`, but of course you can choose your own name. The `--bin` flag specifies that we want to create an executable binary (in contrast to a library) and the `--edition 2018` flag specifies that we want to use the [2018 edition] of Rust for our crate. When we run the command, cargo creates the following directory structure for us:
|
||||
|
||||
[2018 edition]: https://rust-lang-nursery.github.io/edition-guide/rust-2018/index.html
|
||||
[2018 edition]: https://doc.rust-lang.org/nightly/edition-guide/rust-2018/index.html
|
||||
|
||||
```
|
||||
blog_os
|
||||
@@ -67,7 +71,7 @@ blog_os
|
||||
|
||||
The `Cargo.toml` contains the crate configuration, for example the crate name, the author, the [semantic version] number, and dependencies. The `src/main.rs` file contains the root module of our crate and our `main` function. You can compile your crate through `cargo build` and then run the compiled `blog_os` binary in the `target/debug` subfolder.
|
||||
|
||||
[semantic version]: http://semver.org/
|
||||
[semantic version]: https://semver.org/
|
||||
|
||||
### The `no_std` Attribute
|
||||
|
||||
@@ -149,12 +153,12 @@ Language items are special functions and types that are required internally by t
|
||||
|
||||
While providing custom implementations of language items is possible, it should only be done as a last resort. The reason is that language items are highly unstable implementation details and not even type checked (so the compiler doesn't even check if a function has the right argument types). Fortunately, there is a more stable way to fix the above language item error.
|
||||
|
||||
The [`eh_personality` language item] marks a function that is used for implementing [stack unwinding]. By default, Rust uses unwinding to run the destructors of all live stack variables in case of a [panic]. This ensures that all used memory is freed and allows the parent thread to catch the panic and continue execution. Unwinding, however, is a complicated process and requires some OS specific libraries (e.g. [libunwind] on Linux or [structured exception handling] on Windows), so we don't want to use it for our operating system.
|
||||
The [`eh_personality` language item] marks a function that is used for implementing [stack unwinding]. By default, Rust uses unwinding to run the destructors of all live stack variables in case of a [panic]. This ensures that all used memory is freed and allows the parent thread to catch the panic and continue execution. Unwinding, however, is a complicated process and requires some OS-specific libraries (e.g. [libunwind] on Linux or [structured exception handling] on Windows), so we don't want to use it for our operating system.
|
||||
|
||||
[`eh_personality` language item]: https://github.com/rust-lang/rust/blob/edb368491551a77d77a48446d4ee88b35490c565/src/libpanic_unwind/gcc.rs#L11-L45
|
||||
[stack unwinding]: http://www.bogotobogo.com/cplusplus/stackunwinding.php
|
||||
[libunwind]: http://www.nongnu.org/libunwind/
|
||||
[structured exception handling]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680657(v=vs.85).aspx
|
||||
[stack unwinding]: https://www.bogotobogo.com/cplusplus/stackunwinding.php
|
||||
[libunwind]: https://www.nongnu.org/libunwind/
|
||||
[structured exception handling]: https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling
|
||||
|
||||
### Disabling Unwinding
|
||||
|
||||
@@ -212,13 +216,13 @@ fn panic(_info: &PanicInfo) -> ! {
|
||||
You might notice that we removed the `main` function. The reason is that a `main` doesn't make sense without an underlying runtime that calls it. Instead, we are now overwriting the operating system entry point with our own `_start` function:
|
||||
|
||||
```rust
|
||||
#[no_mangle]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _start() -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
By using the `#[no_mangle]` attribute we disable the [name mangling] to ensure that the Rust compiler really outputs a function with the name `_start`. Without the attribute, the compiler would generate some cryptic `_ZN3blog_os4_start7hb173fedf945531caE` symbol to give every function an unique name. The attribute is required because we need to tell the name of the entry point function to the linker in the next step.
|
||||
By using the `#[unsafe(no_mangle)]` attribute, we disable [name mangling] to ensure that the Rust compiler really outputs a function with the name `_start`. Without the attribute, the compiler would generate some cryptic `_ZN3blog_os4_start7hb173fedf945531caE` symbol to give every function a unique name. The attribute is required because we need to tell the name of the entry point function to the linker in the next step.
|
||||
|
||||
We also have to mark the function as `extern "C"` to tell the compiler that it should use the [C calling convention] for this function (instead of the unspecified Rust calling convention). The reason for naming the function `_start` is that this is the default entry point name for most systems.
|
||||
|
||||
@@ -239,7 +243,7 @@ To solve the errors, we need to tell the linker that it should not include the C
|
||||
|
||||
### Building for a Bare Metal Target
|
||||
|
||||
By default Rust tries to build an executable that is able to run in your current system environment. For example, if you're using Windows on `x86_64`, Rust tries to build a `.exe` Windows executable that uses `x86_64` instructions. This environment is called your "host" system.
|
||||
By default Rust tries to build an executable that is able to run in your current system environment. For example, if you're using Windows on `x86_64`, Rust tries to build an `.exe` Windows executable that uses `x86_64` instructions. This environment is called your "host" system.
|
||||
|
||||
To describe different environments, Rust uses a string called [_target triple_]. You can see the target triple for your host system by running `rustc --version --verbose`:
|
||||
|
||||
@@ -259,9 +263,9 @@ The above output is from a `x86_64` Linux system. We see that the `host` triple
|
||||
|
||||
[ABI]: https://en.wikipedia.org/wiki/Application_binary_interface
|
||||
|
||||
By compiling for our host triple, the Rust compiler and the linker assume that there is an underlying operating system such as Linux or Windows that use the C runtime by default, which causes the linker errors. So to avoid the linker errors, we can compile for a different environment with no underlying operating system.
|
||||
By compiling for our host triple, the Rust compiler and the linker assume that there is an underlying operating system such as Linux or Windows that uses the C runtime by default, which causes the linker errors. So, to avoid the linker errors, we can compile for a different environment with no underlying operating system.
|
||||
|
||||
An example for such a bare metal environment is the `thumbv7em-none-eabihf` target triple, which describes an [embedded] [ARM] system. The details are not important, all that matters is that the target triple has no underlying operating system, which is indicated by the `none` in the target triple. To be able to compile for this target, we need to add it in rustup:
|
||||
An example of such a bare metal environment is the `thumbv7em-none-eabihf` target triple, which describes an [embedded] [ARM] system. The details are not important, all that matters is that the target triple has no underlying operating system, which is indicated by the `none` in the target triple. To be able to compile for this target, we need to add it in rustup:
|
||||
|
||||
[embedded]: https://en.wikipedia.org/wiki/Embedded_system
|
||||
[ARM]: https://en.wikipedia.org/wiki/ARM_architecture
|
||||
@@ -334,7 +338,7 @@ error: linking with `link.exe` failed: exit code: 1561
|
||||
= note: LINK : fatal error LNK1561: entry point must be defined
|
||||
```
|
||||
|
||||
The "entry point must be defined" error means that the linker can't find the entry point. On Windows, the default entry point name [depends on the used subsystem][windows-subsystems]. For the `CONSOLE` subsystem the linker looks for a function named `mainCRTStartup` and for the `WINDOWS` subsystem it looks for a function named `WinMainCRTStartup`. To override the default and tell the linker to look for our `_start` function instead, we can pass an `/ENTRY` argument to the linker:
|
||||
The "entry point must be defined" error means that the linker can't find the entry point. On Windows, the default entry point name [depends on the used subsystem][windows-subsystems]. For the `CONSOLE` subsystem, the linker looks for a function named `mainCRTStartup` and for the `WINDOWS` subsystem, it looks for a function named `WinMainCRTStartup`. To override the default and tell the linker to look for our `_start` function instead, we can pass an `/ENTRY` argument to the linker:
|
||||
|
||||
[windows-subsystems]: https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol
|
||||
|
||||
@@ -354,7 +358,7 @@ error: linking with `link.exe` failed: exit code: 1221
|
||||
defined
|
||||
```
|
||||
|
||||
This error occurs because Windows executables can use different [subsystems][windows-subsystems]. For normal programs they are inferred depending on the entry point name: If the entry point is named `main`, the `CONSOLE` subsystem is used, and if the entry point is named `WinMain`, the `WINDOWS` subsystem is used. Since our `_start` function has a different name, we need to specify the subsystem explicitly:
|
||||
This error occurs because Windows executables can use different [subsystems][windows-subsystems]. For normal programs, they are inferred depending on the entry point name: If the entry point is named `main`, the `CONSOLE` subsystem is used, and if the entry point is named `WinMain`, the `WINDOWS` subsystem is used. Since our `_start` function has a different name, we need to specify the subsystem explicitly:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
||||
@@ -376,7 +380,7 @@ error: linking with `cc` failed: exit code: 1
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
This error message tells us that the linker can't find an entry point function with the default name `main` (for some reason all functions are prefixed with a `_` on macOS). To set the entry point to our `_start` function, we pass the `-e` linker argument:
|
||||
This error message tells us that the linker can't find an entry point function with the default name `main` (for some reason, all functions are prefixed with a `_` on macOS). To set the entry point to our `_start` function, we pass the `-e` linker argument:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start"
|
||||
@@ -397,13 +401,13 @@ error: linking with `cc` failed: exit code: 1
|
||||
|
||||
macOS [does not officially support statically linked binaries] and requires programs to link the `libSystem` library by default. To override this and link a static binary, we pass the `-static` flag to the linker:
|
||||
|
||||
[does not officially support statically linked binaries]: https://developer.apple.com/library/content/qa/qa1118/_index.html
|
||||
[does not officially support statically linked binaries]: https://developer.apple.com/library/archive/qa/qa1118/_index.html
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start -static"
|
||||
```
|
||||
|
||||
This still not suffices, as a third linker error occurs:
|
||||
This still does not suffice, as a third linker error occurs:
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
@@ -413,7 +417,7 @@ error: linking with `cc` failed: exit code: 1
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
This error occurs because programs on macOS link to `crt0` (“C runtime zero”) by default. This is similar to the error we had on Linux and can be also solved by adding the `-nostartfiles` linker argument:
|
||||
This error occurs because programs on macOS link to `crt0` (“C runtime zero”) by default. This is similar to the error we had on Linux and can also be solved by adding the `-nostartfiles` linker argument:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||
@@ -423,10 +427,10 @@ Now our program should build successfully on macOS.
|
||||
|
||||
#### Unifying the Build Commands
|
||||
|
||||
Right now we have different build commands depending on the host platform, which is not ideal. To avoid this, we can create a file named `.cargo/config` that contains the platform specific arguments:
|
||||
Right now we have different build commands depending on the host platform, which is not ideal. To avoid this, we can create a file named `.cargo/config.toml` that contains the platform-specific arguments:
|
||||
|
||||
```toml
|
||||
# in .cargo/config
|
||||
# in .cargo/config.toml
|
||||
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-nostartfiles"]
|
||||
@@ -438,7 +442,7 @@ rustflags = ["-C", "link-args=/ENTRY:_start /SUBSYSTEM:console"]
|
||||
rustflags = ["-C", "link-args=-e __start -static -nostartfiles"]
|
||||
```
|
||||
|
||||
The `rustflags` key contains arguments that are automatically added to every invocation of `rustc`. For more information on the `.cargo/config` file check out the [official documentation](https://doc.rust-lang.org/cargo/reference/config.html).
|
||||
The `rustflags` key contains arguments that are automatically added to every invocation of `rustc`. For more information on the `.cargo/config.toml` file, check out the [official documentation](https://doc.rust-lang.org/cargo/reference/config.html).
|
||||
|
||||
Now our program should be buildable on all three platforms with a simple `cargo build`.
|
||||
|
||||
@@ -462,7 +466,7 @@ A minimal freestanding Rust binary looks like this:
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
#[no_mangle] // don't mangle the name of this function
|
||||
#[unsafe(no_mangle)] // don't mangle the name of this function
|
||||
pub extern "C" fn _start() -> ! {
|
||||
// this function is the entry point, since the linker looks for a function
|
||||
// named `_start` by default
|
||||
@@ -510,10 +514,82 @@ cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||
```
|
||||
|
||||
Note that this is just a minimal example of a freestanding Rust binary. This binary expects various things, for example that a stack is initialized when the `_start` function is called. **So for any real use of such a binary, more steps are required**.
|
||||
Note that this is just a minimal example of a freestanding Rust binary. This binary expects various things, for example, that a stack is initialized when the `_start` function is called. **So for any real use of such a binary, more steps are required**.
|
||||
|
||||
## Making `rust-analyzer` happy
|
||||
|
||||
The [`rust-analyzer`](https://rust-analyzer.github.io/) project is a great way to get code completion and "go to definition" support (and many other features) for Rust code in your editor.
|
||||
It works really well for `#![no_std]` projects too, so I recommend using it for kernel development!
|
||||
|
||||
If you're using the [`checkOnSave`](https://rust-analyzer.github.io/book/configuration.html#checkOnSave) feature of `rust-analyzer` (enabled by default), it might report an error for the panic function of our kernel:
|
||||
|
||||
```
|
||||
found duplicate lang item `panic_impl`
|
||||
```
|
||||
|
||||
The reason for this error is that `rust-analyzer` invokes `cargo check --all-targets` by default, which also tries to build the binary in [test](https://doc.rust-lang.org/book/ch11-01-writing-tests.html) and [benchmark](https://doc.rust-lang.org/rustc/tests/index.html#benchmarks) mode.
|
||||
|
||||
<div class="note">
|
||||
|
||||
### The two meanings of "target"
|
||||
|
||||
The `--all-targets` flag is completely unrelated to the `--target` argument.
|
||||
There are two different meanings of the term "target" in `cargo`:
|
||||
|
||||
- The `--target` flag specifies the **[_compilation target_]** that should be passed to the `rustc` compiler. This should be set to the [target triple] of the machine that should run our code.
|
||||
- The `--all-targets` flag references the **[_package target_]** of Cargo. Cargo packages can be a library and binary at the same time, so you can specify in which way you like to build your crate. In addition, Cargo also has package targets for [examples](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#examples), [tests](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#tests), and [benchmarks](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#benchmarks). These package targets can co-exist, so you can build/check the same crate in e.g. library or test mode.
|
||||
|
||||
[_compilation target_]: https://doc.rust-lang.org/rustc/targets/index.html
|
||||
[target triple]: https://clang.llvm.org/docs/CrossCompilation.html#target-triple
|
||||
[_package target_]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html
|
||||
|
||||
</div>
|
||||
|
||||
By default, `cargo check` only builds the _library_ and _binary_ package targets.
|
||||
However, `rust-analyzer` chooses to check all package targets by default when [`checkOnSave`](https://rust-analyzer.github.io/book/configuration.html#checkOnSave) is enabled.
|
||||
This is the reason that `rust-analyzer` reports the above `lang item` error that we don't see in `cargo check`.
|
||||
If we run `cargo check --all-targets`, we see the error too:
|
||||
|
||||
```
|
||||
error[E0152]: found duplicate lang item `panic_impl`
|
||||
--> src/main.rs:13:1
|
||||
|
|
||||
13 | / fn panic(_info: &PanicInfo) -> ! {
|
||||
14 | | loop {}
|
||||
15 | | }
|
||||
| |_^
|
||||
|
|
||||
= note: the lang item is first defined in crate `std` (which `test` depends on)
|
||||
= note: first definition in `std` loaded from /home/[...]/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-8df6be531efb3fd0.rlib
|
||||
= note: second definition in the local crate (`blog_os`)
|
||||
```
|
||||
|
||||
The first `note` tells us that the panic language item is already defined in the `std` crate, which is a dependency of the `test` crate.
|
||||
The `test` crate is automatically included when building a crate in [test mode](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#tests).
|
||||
This does not make sense for our `#![no_std]` kernel as there is no way to support the standard library on bare metal.
|
||||
So this error is not relevant to our project and we can safely ignore it.
|
||||
|
||||
The proper way to avoid this error is to specify in our `Cargo.toml` that our binary does not support building in `test` and `bench` modes.
|
||||
We can do that by adding a `[[bin]]` section to our `Cargo.toml` to [configure the build](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#configuring-a-target) of our binary:
|
||||
|
||||
```toml
|
||||
# in Cargo.toml
|
||||
|
||||
[[bin]]
|
||||
name = "blog_os"
|
||||
test = false
|
||||
bench = false
|
||||
```
|
||||
|
||||
The double-brackets around `bin` are not a mistake, this is how the TOML format defines keys that can appear multiple times.
|
||||
Since a crate can have multiple binaries, the `[[bin]]` section can appear multiple times in the `Cargo.toml` as well.
|
||||
This is also the reason for the mandatory `name` field, which needs to match the name of the binary (so that `cargo` knows which settings should be applied to which binary).
|
||||
|
||||
By setting the [`test`](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-test-field) and [`bench` ](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-bench-field) fields to `false`, we instruct `cargo` to not build our binary in test or benchmark mode.
|
||||
Now `cargo check --all-targets` should not throw any errors anymore, and the `checkOnSave` implementation of `rust-analyzer` should be happy too.
|
||||
|
||||
## What's next?
|
||||
|
||||
The [next post] explains the steps needed for turning our freestanding binary into a minimal operating system kernel. This includes creating a custom target, combining our executable with a bootloader, and learning how to print something to the screen.
|
||||
|
||||
[next post]: @/second-edition/posts/02-minimal-rust-kernel/index.md
|
||||
[next post]: @/edition-2/posts/02-minimal-rust-kernel/index.md
|
||||
@@ -0,0 +1,522 @@
|
||||
+++
|
||||
title = "Независимый бинарный файл на Rust"
|
||||
weight = 1
|
||||
path = "ru/freestanding-rust-binary"
|
||||
date = 2018-02-10
|
||||
|
||||
[extra]
|
||||
translators = ["MrZloHex"]
|
||||
+++
|
||||
|
||||
Первый шаг в создании собственного ядра операционной системы — это создание исполняемого файла на Rust, который не будет подключать стандартную библиотеку. Именно это дает возможность запускать Rust код на [голом железе][bare metal] без слоя операционной системы.
|
||||
|
||||
[bare metal]: https://en.wikipedia.org/wiki/Bare_machine
|
||||
|
||||
<!-- more -->
|
||||
|
||||
Этот блог открыто разрабатывается на [GitHub]. Если у вас возникли какие-либо проблемы или вопросы, пожалуйста, создайте _issue_. Также вы можете оставлять комментарии [в конце страницы][at the bottom]. Полный исходный код для этого поста вы можете найти в репозитории в ветке [`post-01`][post branch].
|
||||
|
||||
[GitHub]: https://github.com/phil-opp/blog_os
|
||||
[at the bottom]: #comments
|
||||
<!-- fix for zola anchor checker (target is in template): <a id="comments"> -->
|
||||
[post branch]: https://github.com/phil-opp/blog_os/tree/post-01
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## Введение
|
||||
Для того, чтобы написать ядро операционной системы, нужен код, который не зависит от операционной системы и ее возможностей. Это означает, что нельзя использовать потоки, файлы, [кучу][heap], сети, случайные числа, стандартный вывод или другие возможности, которые зависят от ОС или определённого железа.
|
||||
|
||||
[heap]: https://en.wikipedia.org/wiki/Heap_(data_structure)
|
||||
|
||||
Это значит, что нельзя использовать большую часть [стандартной библиотеки Rust][Rust Standard library], но остается множество других возможностей Rust, которые _можно использовать_. Например, [итераторы][iterators], [замыкания][closures], [сопоставление с образцом][pattern matching], [`Option`][option] и [`Result`][result], [форматирование строк][string formatting] и, конечно же, [систему владения][ownership system]. Эти функции дают возможность для написания ядра в очень выразительном и высоко-уровневом стиле, не беспокоясь о [неопределенном поведении][undefined behavior] или [сохранности памяти][memory safety].
|
||||
|
||||
[option]: https://doc.rust-lang.org/core/option/
|
||||
[result]:https://doc.rust-lang.org/core/result/
|
||||
[Rust standard library]: https://doc.rust-lang.org/std/
|
||||
[iterators]: https://doc.rust-lang.org/book/ch13-02-iterators.html
|
||||
[closures]: https://doc.rust-lang.org/book/ch13-01-closures.html
|
||||
[pattern matching]: https://doc.rust-lang.org/book/ch06-00-enums.html
|
||||
[string formatting]: https://doc.rust-lang.org/core/macro.write.html
|
||||
[ownership system]: https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html
|
||||
[undefined behavior]: https://www.nayuki.io/page/undefined-behavior-in-c-and-cplusplus-programs
|
||||
[memory safety]: https://tonyarcieri.com/it-s-time-for-a-memory-safety-intervention
|
||||
|
||||
Чтобы создать ядро ОС на Rust, нужно создать исполняемый файл, который мог бы запускаться без ОС.
|
||||
|
||||
Этот пост описывает необходимые шаги для создания независимого исполняемого файла на Rust и объясняет, почему эти шаги нужны. Если вам интересен только минимальный пример, можете сразу перейти к __[итогам](#summary)__.
|
||||
|
||||
## Отключение стандартной библиотеки
|
||||
По умолчанию, все Rust-крейты подключают [стандартную библиотеку][standard library], которая зависит от возможностей операционной системы, таких как потоки, файлы, сети. Она также зависит от стандартной библиотки C `libc`, которая очень тесно взаимодействует с возможностями ОС. Так как мы хотим написать операционную систему, мы не можем использовать библиотеки, которые зависят от операционной системы. Поэтому необходимо отключить автоматические подключение стандартной библиотеки через [атрибут `no_std`][attribute].
|
||||
|
||||
[standard library]: https://doc.rust-lang.org/std/
|
||||
[attribute]: https://doc.rust-lang.org/1.30.0/book/first-edition/using-rust-without-the-standard-library.html
|
||||
|
||||
Мы начнем с создания нового проекта cargo. Самый простой способ сделать это — через командную строку:
|
||||
|
||||
```
|
||||
cargo new blog_os --bin -- edition 2018
|
||||
```
|
||||
|
||||
Я назвал этот проект `blog_os`, но вы можете назвать как вам угодно. Флаг `--bin` указывает на то, что мы хотим создать исполняемый файл (а не библиотеку), а флаг `--edition 2018` указывает, что мы хотим использовать [редакцию Rust 2018][edition] для нашего крейта. После выполнения команды cargo создаст каталог со следующей структурой:
|
||||
|
||||
[edition]: https://doc.rust-lang.org/nightly/edition-guide/rust-2018/index.html
|
||||
|
||||
```
|
||||
blog_os
|
||||
├── Cargo.toml
|
||||
└── src
|
||||
└── main.rs
|
||||
```
|
||||
|
||||
`Cargo.toml` содержит данные и конфигурацию крейта, такие как _название, автор, [семантическую версию][semantic version]_ и _зависимости_ от других крейтов. Файл `src/main.rs` содержит корневой модуль нашего крейта и функцию `main`. Можно скомпилировать крейт с помощью `cargo build` и запустить скомпилированную программу `blog_os` в поддиректории `target/debug`.
|
||||
|
||||
[semantic version]: https://semver.org/
|
||||
|
||||
### Атрибут `no_std`
|
||||
|
||||
В данный момент наш крейт неявно подключает стандартную библиотеку. Это можно исправить путем добавления [атрибута `no_std`][attribute]:
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
Если сейчас попробовать скомпилировать программу (с помоцью команды `cargo build`), то появится следующая ошибка:
|
||||
|
||||
```
|
||||
error: cannot find macro `println!` in this scope
|
||||
--> src/main.rs:4:5
|
||||
|
|
||||
4 | println!("Hello, world!");
|
||||
| ^^^^^^^
|
||||
```
|
||||
|
||||
Эта ошибка объясняется тем, что [макрос `println`][macro] — часть стандартной библиотеки, которая была отключена. Поэтому у нас больше нет возможность выводить что-либо на экран. Это логично, так как `println` печатает через [стандартный вывод][standard output], который, в свою очередь, является специальным файловым дескриптором, предоставляемым операционной системой.
|
||||
|
||||
[macro]: https://doc.rust-lang.org/std/macro.println.html
|
||||
[standard output]: https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29
|
||||
|
||||
Давайте уберем макрос `println` и попробуем скомпилировать еще раз:
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
fn main() {}
|
||||
```
|
||||
|
||||
```
|
||||
> cargo build
|
||||
error: `#[panic_handler]` function required, but not found
|
||||
error: language item required, but not found: `eh_personality`
|
||||
```
|
||||
|
||||
Сейчас компилятор не может найти функцию `#[panic_handler]` и «элемент языка».
|
||||
|
||||
## Реализация _паники_
|
||||
|
||||
Атрибут `pаnic_handler` определяет функцию, которая должна вызываться, когда происходит [паника (panic)][panic]. Стандартная библиотека предоставляет собственную функцию обработчика паники, но после отключения стандартной библиотеки мы должны написать собственный обработчик:
|
||||
|
||||
[panic]: https://doc.rust-lang.org/stable/book/ch09-01-unrecoverable-errors-with-panic.html
|
||||
|
||||
```rust
|
||||
// in main.rs
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
/// This function is called on panic.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
Параметр [`PanicInfo`][PanicInfo] содержит название файла и строку, где произошла паника, и дополнительное сообщение с пояснением. Эта функция никогда не должна возвратиться, и такая функция называется [расходящейся][diverging functions] и она возращает [пустой тип]["never" type] `!`. Пока что мы ничего не можем сделать в этой функции, поэтому мы просто войдем в бесконечный цикл.
|
||||
|
||||
[PanicInfo]: https://doc.rust-lang.org/nightly/core/panic/struct.PanicInfo.html
|
||||
[diverging functions]: https://doc.rust-lang.org/1.30.0/book/first-edition/functions.html#diverging-functions
|
||||
["never" type]: https://doc.rust-lang.org/nightly/std/primitive.never.html
|
||||
|
||||
## Элемент языка `eh_personality`
|
||||
|
||||
Элементы языка — это специальные функции и типы, которые необходимы компилятору. Например, трейт [`Copy`] указывает компилятору, у каких типов есть [_семантика копирования_][`Copy`]. Если мы посмотрим на [реализацию][copy code] этого трейта, то увидим специальный атрибут `#[lang = "copy"]`, который говорит, что этот трейт является элементом языка.
|
||||
|
||||
[`Copy`]: https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html
|
||||
[copy code]: https://github.com/rust-lang/rust/blob/485397e49a02a3b7ff77c17e4a3f16c653925cb3/src/libcore/marker.rs#L296-L299
|
||||
|
||||
Несмотря на то, что можно предоставить свою реализацию элементов языка, это следует делать только в крайних случаях. Причина в том, что элементы языка являются крайне нестабильными деталями реализации, и компилятор даже не проверяет в них согласованность типов (поэтому он даже не проверяет, имеет ли функция правильные типы аргументов). К счастью, существует более стабильный способ исправить вышеупомянутую ошибку.
|
||||
|
||||
Элемент языка [`eh_personality`][language item] указывает на функцию, которая используется для реализации [раскрутки стека][stack unwinding]. По умолчанию, Rust использует раскрутку для запуска деструктуров для всех _живых_ переменных на стеке в случае [паники][panic]. Это гарантирует, что вся использованная память будет освобождена, и позволяет родительскому потоку перехватить панику и продолжить выполнение. Раскрутка — очень сложный процесс и требует некоторых специльных библиотек ОС (например, [libunwind] для Linux или [structured exception handling] для Windows), так что мы не должны использовать её для нашей операционной системы.
|
||||
|
||||
[language item]: https://github.com/rust-lang/rust/blob/edb368491551a77d77a48446d4ee88b35490c565/src/libpanic_unwind/gcc.rs#L11-L45
|
||||
[stack unwinding]: https://www.bogotobogo.com/cplusplus/stackunwinding.php
|
||||
[libunwind]: https://www.nongnu.org/libunwind/
|
||||
[structured exception handling]: https://docs.microsoft.com/ru-ru/windows/win32/debug/structured-exception-handling
|
||||
|
||||
### Отключение раскрутки
|
||||
|
||||
Существуют и другие случаи использования, для которых раскрутка нежелательна, поэтому Rust предоставляет опцию [прерывания выполнения при панике][abort on panic]. Это отключает генерацию информации о символах раскрутки и, таким образом, значительно уменьшает размер бинарного файла. Есть несколько мест, где мы можем отключить раскрутку. Самый простой способ — добавить следующие строки в наш `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
```
|
||||
|
||||
Это устанавливает стратегию паники на `abort` (прерывание) как для профиля `dev` (используемого для `cargo build`), так и для профиля `release` (используемого для `cargo build --release`). Теперь элемент языка `eh_personality` больше не должен требоваться.
|
||||
|
||||
[abort on panic]: https://github.com/rust-lang/rust/pull/32900
|
||||
|
||||
Теперь мы исправили обе вышеуказанные ошибки. Однако, если мы сейчас попытаемся скомпилировать программу, возникнет другая ошибка:
|
||||
|
||||
```
|
||||
> cargo build
|
||||
error: requires `start` lang_item
|
||||
```
|
||||
|
||||
В нашей программе отсутствует элемент языка `start`, который определяет начальную точку входа программы.
|
||||
|
||||
## Аттрибут `start`
|
||||
|
||||
Можно подумать, что функция `main` — это первая функция, вызываемая при запуске программы. Однако в большинстве языков есть [среда выполнения][runtime system], которая отвечает за такие вещи, как сборка мусора (например, в Java) или программные потоки (например, goroutines в Go). Эта система выполнения должна быть вызвана до `main`, поскольку ей необходимо инициализировать себя.
|
||||
|
||||
[runtime system]: https://en.wikipedia.org/wiki/Runtime_system
|
||||
|
||||
В типичном исполнимом файле Rust, который использует стандартную библиотеку, выполнение начинается в runtime-библиотеке C под названием `crt0` ("C runtime zero"), которая создает окружение для C-приложения. Это включает создание стека и размещение аргументов в нужных регистрах. Затем C runtime вызывает [точку входа для Rust-приложения][rt::lang_start], которая обозначается элементом языка `start`. Rust имеет очень маленький runtime, который заботится о некоторых мелочах, таких как установка защиты от переполнения стека или вывод сообщения при панике. Затем рантайм вызывает функцию `main`.
|
||||
|
||||
[rt::lang_start]: https://github.com/rust-lang/rust/blob/bb4d1491466d8239a7a5fd68bd605e3276e97afb/src/libstd/rt.rs#L32-L73
|
||||
|
||||
Наш независимый исполняемый файл не имеет доступа к runtime Rust и `crt0`, поэтому нам нужно определить собственную точку входа. Реализация языкового элемента `start` не поможет, поскольку он все равно потребует `crt0`. Вместо этого нам нужно напрямую переопределить точку входа `crt0`.
|
||||
|
||||
### Переопределение точки входа
|
||||
|
||||
Чтобы сообщить компилятору Rust, что мы не хотим использовать стандартную цепочку точек входа, мы добавляем атрибут `#![no_main]`.
|
||||
|
||||
```rust
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
/// This function is called on panic.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
Можно заметить, что мы удалили функцию `main`. Причина в том, что `main` не имеет смысла без стандартного runtime, которая ее вызывает. Вместо этого мы переопределим точку входа операционной системы с помощью нашей собственной функции `_start`:
|
||||
|
||||
```rust
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _start() -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
Используя атрибут `#[unsafe(no_mangle)]`, мы отключаем [искажение имен][name mangling], чтобы гарантировать, что компилятор Rust сгенерирует функцию с именем `_start`. Без этого атрибута компилятор генерировал бы какой-нибудь загадочный символ `_ZN3blog_os4_start7hb173fedf945531caE`, чтобы дать каждой функции уникальное имя. Атрибут необходим, потому что на следующем этапе нам нужно сообщить имя функции точки входа компоновщику.
|
||||
|
||||
Мы также должны пометить функцию как `extern "C"`, чтобы указать компилятору, что он должен использовать [соглашение о вызове C][C calling convention] для этой функции (вместо неопределенного соглашения о вызове Rust). Причина именования функции `_start` в том, что это имя точки входа по умолчанию для большинства систем.
|
||||
|
||||
[name mangling]: https://en.wikipedia.org/wiki/Name_mangling
|
||||
[C calling convention]: https://en.wikipedia.org/wiki/Calling_convention
|
||||
|
||||
Возвращаемый `!` означает, что функция является расходящейся, т.е. не имеет права возвращаться. Это необходимо, поскольку точка входа не вызывается никакой функцией, а вызывается непосредственно операционной системой или загрузчиком. Поэтому вместо возврата точка входа должна, например, вызвать [системный вызов `exit`][`exit` system call] операционной системы. В нашем случае разумным действием может быть выключение машины, поскольку ничего не останется делать, если независимый исполнимый файл завершит исполнение. Пока что мы выполняем это требование путем бесконечного цикла.
|
||||
|
||||
[`exit` system call]: https://en.wikipedia.org/wiki/Exit_(system_call)
|
||||
|
||||
Если мы выполним `cargo build` сейчас, мы получим ошибку компоновщика (_linker_ error).
|
||||
|
||||
## Ошибки компоновщика
|
||||
|
||||
Компоновщик — это программа, которая объединяет сгенерированный код в исполняемый файл. Поскольку формат исполняемого файла отличается в Linux, Windows и macOS, в каждой системе есть свой компоновщик, и каждый покажет свою ошибку. Основная причина ошибок одна и та же: конфигурация компоновщика по умолчанию предполагает, что наша программа зависит от C runtime, а это не так.
|
||||
|
||||
Чтобы устранить ошибки, нам нужно сообщить компоновщику, что он не должен включать C runtime. Мы можем сделать это, передав компоновщику определенный набор аргументов или выполнив компиляцию для голого железа.
|
||||
|
||||
### Компиляция для голого железа
|
||||
|
||||
По умолчанию Rust пытается создать исполняемый файл, который может быть запущен в окружении вашей текущей системы. Например, если вы используете Windows на `x86_64`, Rust пытается создать исполняемый файл Windows `.exe`, который использует инструкции `x86_64`. Это окружение называется вашей "хост-системой".
|
||||
|
||||
Для описания различных окружений Rust использует строку [_target triple_]. Вы можете узнать тройку вашей хост-системы, выполнив команду `rustc --version --verbose`:
|
||||
|
||||
[_target triple_]: https://clang.llvm.org/docs/CrossCompilation.html#target-triple
|
||||
|
||||
```
|
||||
rustc 1.35.0-nightly (474e7a648 2019-04-07)
|
||||
binary: rustc
|
||||
commit-hash: 474e7a6486758ea6fc761893b1a49cd9076fb0ab
|
||||
commit-date: 2019-04-07
|
||||
host: x86_64-unknown-linux-gnu
|
||||
release: 1.35.0-nightly
|
||||
LLVM version: 8.0
|
||||
```
|
||||
|
||||
Приведенный выше результат получен от системы `x86_64` Linux. Мы видим, что тройка `host` — это `x86_64-unknown-linux-gnu`, которая включает архитектуру процессора (`x86_64`), производителя (`unknown`), операционную систему (`linux`) и [ABI] (`gnu`).
|
||||
|
||||
[ABI]: https://en.wikipedia.org/wiki/Application_binary_interface
|
||||
|
||||
Компилируя для тройки нашего хоста, компилятор Rust и компоновщик предполагают наличие базовой операционной системы, такой как Linux или Windows, которая по умолчанию использует C runtime, что вызывает ошибки компоновщика. Поэтому, чтобы избежать ошибок компоновщика, мы можем настроить компиляцию для другого окружения без базовой операционной системы.
|
||||
|
||||
Примером такого "голого" окружения является тройка `thumbv7em-none-eabihf`, которая описывает [ARM] архитектуру. Детали не важны, важно лишь то, что тройка не имеет базовой операционной системы, на что указывает `none` в тройке. Чтобы иметь возможность компилировать для этой системы, нам нужно добавить ее в rustup:
|
||||
|
||||
[ARM]: https://en.wikipedia.org/wiki/ARM_architecture
|
||||
|
||||
```
|
||||
rustup target add thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
Это загружает копию стандартной библиотеки (и `core`) для системы. Теперь мы можем собрать наш независимый исполняемый файл для этой системы:
|
||||
|
||||
```
|
||||
cargo build --target thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
Передавая аргумент `--target`, мы [кросс-компилируем][cross compile] наш исполняемый файл для голого железа. Поскольку система, под которую мы компилируем, не имеет операционной системы, компоновщик не пытается компоновать C runtime, и наша компиляция проходит успешно без каких-либо ошибок компоновщика.
|
||||
|
||||
[cross compile]: https://en.wikipedia.org/wiki/Cross_compiler
|
||||
|
||||
Именно этот подход мы будем использовать для сборки ядра нашей ОС. Вместо `thumbv7em-none-eabihf` мы будем использовать [custom target], который описывает окружение для архитектуры `x86_64`. Подробности будут описаны в следующем посте.
|
||||
|
||||
[custom target]: https://doc.rust-lang.org/rustc/targets/custom.html
|
||||
|
||||
### Аргументы компоновщика
|
||||
|
||||
Вместо компиляции под голое железо, ошибки компоновщика можно исправить, передав ему определенный набор аргументов. Мы не будем использовать этот подход для нашего ядра, поэтому данный раздел является необязательным и приводится только для полноты картины. Щелкните на _"Аргументы компоновщика"_ ниже, чтобы показать необязательное содержание.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Аргументы компоновщика</summary>
|
||||
|
||||
В этом разделе мы рассмотрим ошибки компоновщика, возникающие в Linux, Windows и macOS, и объясним, как их решить, передав компоновщику дополнительные аргументы. Обратите внимание, что формат исполняемого файла и компоновщик отличаются в разных операционных системах, поэтому для каждой системы требуется свой набор аргументов.
|
||||
|
||||
#### Linux
|
||||
|
||||
На Linux возникает следующая ошибка компоновщика (сокращенно):
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: /usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x12): undefined reference to `__libc_csu_fini'
|
||||
/usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x19): undefined reference to `__libc_csu_init'
|
||||
/usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x25): undefined reference to `__libc_start_main'
|
||||
collect2: error: ld returned 1 exit status
|
||||
```
|
||||
|
||||
Проблема заключается в том, что компоновщик по умолчанию включает процедуру запуска C runtime, которая также называется `_start`. Она требует некоторых символов стандартной библиотеки C `libc`, которые мы не включаем из-за атрибута `no_std`, поэтому компоновщик не может подключить эти библиотеки, поэтому появляются ошибки. Чтобы решить эту проблему, мы можем сказать компоновщику, что он не должен компоновать процедуру запуска C, передав флаг `-nostartfiles`.
|
||||
|
||||
Одним из способов передачи атрибутов компоновщика через cargo является команда `cargo rustc`. Команда ведет себя точно так же, как `cargo build`, но позволяет передавать опции `rustc`, базовому компилятору Rust. У `rustc` есть флаг `-C link-arg`, который передает аргумент компоновщику. В совокупности наша новая команда сборки выглядит следующим образом:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-arg=-nostartfiles
|
||||
```
|
||||
|
||||
Теперь наш крейт собирается как независимый исполняемый файл в Linux!
|
||||
|
||||
Нам не нужно было явно указывать имя нашей функции точки входа, поскольку компоновщик по умолчанию ищет функцию с именем `_start`.
|
||||
|
||||
#### Windows
|
||||
|
||||
В Windows возникает другая ошибка компоновщика (сокращенно):
|
||||
|
||||
```
|
||||
error: linking with `link.exe` failed: exit code: 1561
|
||||
|
|
||||
= note: "C:\\Program Files (x86)\\…\\link.exe" […]
|
||||
= note: LINK : fatal error LNK1561: entry point must be defined
|
||||
```
|
||||
|
||||
Ошибка "точка входа должна быть определена" (_"entry point must be defined"_) означает, что компоновщик не может найти точку входа. В Windows имя точки входа по умолчанию [зависит от используемой подсистемы][windows-subsystems]. Для подсистемы `CONSOLE` компоновщик ищет функцию с именем `mainCRTStartup`, а для подсистемы `WINDOWS` - функцию с именем `WinMainCRTStartup`. Чтобы переопределить названия точки входа на `_start`, мы можем передать компоновщику аргумент `/ENTRY`:
|
||||
|
||||
[windows-subsystems]: https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-arg=/ENTRY:_start
|
||||
```
|
||||
|
||||
Из разного формата аргументов мы ясно видим, что компоновщик Windows - это совершенно другая программа, чем компоновщик Linux.
|
||||
|
||||
Теперь возникает другая ошибка компоновщика:
|
||||
|
||||
```
|
||||
error: linking with `link.exe` failed: exit code: 1221
|
||||
|
|
||||
= note: "C:\\Program Files (x86)\\…\\link.exe" […]
|
||||
= note: LINK : fatal error LNK1221: a subsystem can't be inferred and must be
|
||||
defined
|
||||
```
|
||||
|
||||
Эта ошибка возникает из-за того, что исполняемые файлы Windows могут использовать различные [подсистемы][windows-subsystems]. Для обычных программ они определяются в зависимости от имени точки входа: если точка входа называется `main`, то используется подсистема `CONSOLE`, а если точка входа называется `WinMain`, то используется подсистема `WINDOWS`. Поскольку наша функция `_start` имеет другое имя, нам нужно явно указать подсистему:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
||||
```
|
||||
|
||||
Здесь мы используем подсистему `CONSOLE`, но подойдет и подсистема `WINDOWS`. Вместо того, чтобы передавать `-C link-arg` несколько раз, мы используем `-C link-args`, который принимает список аргументов, разделенных пробелами.
|
||||
|
||||
С помощью этой команды наш исполняемый файл должен успешно скомпилироваться под Windows.
|
||||
|
||||
#### macOS
|
||||
|
||||
На macOS возникает следующая ошибка компоновщика (сокращенно):
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: entry point (_main) undefined. for architecture x86_64
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
Это сообщение об ошибке говорит нам, что компоновщик не может найти функцию точки входа с именем по умолчанию `main` (по какой-то причине в macOS все функции имеют префикс `_`). Чтобы установить точку входа в нашу функцию `_start`, мы передаем аргумент компоновщика `-e`:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start"
|
||||
```
|
||||
|
||||
Флаг `-e` задает имя функции точки входа. Поскольку в macOS все функции имеют дополнительный префикс `_`, нам нужно установить точку входа на `__start` вместо `_start`.
|
||||
|
||||
Теперь возникает следующая ошибка компоновщика:
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: dynamic main executables must link with libSystem.dylib
|
||||
for architecture x86_64
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
macOS [официально не поддерживает статически скомпонованные исполняемые файлы][static binary] и по умолчанию требует от программ компоновки библиотеки `libSystem`. Чтобы переопределить это поведение и скомпоновать статический исполняемый файл, передадим компоновщику флаг `-static`:
|
||||
|
||||
[static binary]: https://developer.apple.com/library/archive/qa/qa1118/_index.html
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start -static"
|
||||
```
|
||||
|
||||
Этого все равно недостаточно, так как возникает третья ошибка компоновщика:
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" […]
|
||||
= note: ld: library not found for -lcrt0.o
|
||||
clang: error: linker command failed with exit code 1 […]
|
||||
```
|
||||
|
||||
Эта ошибка возникает из-за того, что программы на macOS по умолчанию ссылаются на `crt0` ("C runtime zero"). Она похожа на ошибку под Linux и тоже может быть решена добавлением аргумента компоновщика `-nostartfiles`:
|
||||
|
||||
```
|
||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||
```
|
||||
|
||||
Теперь наша программа должна успешно скомпилироваться на macOS.
|
||||
|
||||
#### Объединение команд сборки
|
||||
|
||||
Сейчас у нас разные команды сборки в зависимости от платформы хоста, что не идеально. Чтобы избежать этого, мы можем создать файл с именем `.cargo/config.toml`, который будет содержать аргументы для конкретной платформы:
|
||||
|
||||
```toml
|
||||
# in .cargo/config.toml
|
||||
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-nostartfiles"]
|
||||
|
||||
[target.'cfg(target_os = "windows")']
|
||||
rustflags = ["-C", "link-args=/ENTRY:_start /SUBSYSTEM:console"]
|
||||
|
||||
[target.'cfg(target_os = "macos")']
|
||||
rustflags = ["-C", "link-args=-e __start -static -nostartfiles"]
|
||||
```
|
||||
|
||||
Ключ `rustflags` содержит аргументы, которые автоматически добавляются к каждому вызову `rustc`. Более подробную информацию о файле `.cargo/config.toml` можно найти в [официальной документации](https://doc.rust-lang.org/cargo/reference/config.html).
|
||||
|
||||
Теперь наша программа должна собираться на всех трех платформах с помощью простой `cargo build`.
|
||||
|
||||
#### Должны ли вы это делать?
|
||||
|
||||
Хотя можно создать независимый исполняемый файл для Linux, Windows и macOS, это, вероятно, не очень хорошая идея. Причина в том, что наш исполняемый файл все еще ожидает различных вещей, например, инициализации стека при вызове функции `_start`. Без C runtime некоторые из этих требований могут быть не выполнены, что может привести к сбою нашей программы, например, из-за ошибки сегментации.
|
||||
|
||||
Если вы хотите создать минимальный исполняемый файл, запускаемый поверх существующей операционной системы, то включение `libc` и установка атрибута `#[start]`, как описано [здесь] (https://doc.rust-lang.org/1.16.0/book/no-stdlib.html), вероятно, будет идеей получше.
|
||||
|
||||
</details>
|
||||
|
||||
## Итоги {#summary}
|
||||
|
||||
Минимальный независимый исполняемый бинарный файл Rust выглядит примерно так:
|
||||
|
||||
`src/main.rs`:
|
||||
|
||||
```rust
|
||||
#![no_std] // don't link the Rust standard library
|
||||
#![no_main] // disable all Rust-level entry points
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
#[unsafe(no_mangle)] // don't mangle the name of this function
|
||||
pub extern "C" fn _start() -> ! {
|
||||
// this function is the entry point, since the linker looks for a function
|
||||
// named `_start` by default
|
||||
loop {}
|
||||
}
|
||||
|
||||
/// This function is called on panic.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
`Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "crate_name"
|
||||
version = "0.1.0"
|
||||
authors = ["Author Name <author@example.com>"]
|
||||
|
||||
# the profile used for `cargo build`
|
||||
[profile.dev]
|
||||
panic = "abort" # disable stack unwinding on panic
|
||||
|
||||
# the profile used for `cargo build --release`
|
||||
[profile.release]
|
||||
panic = "abort" # disable stack unwinding on panic
|
||||
```
|
||||
|
||||
Чтобы собрать этот исполняемый файл, его надо скомпилировать для голого железа, например, `thumbv7em-none-eabihf`:
|
||||
|
||||
```
|
||||
cargo build --target thumbv7em-none-eabihf
|
||||
```
|
||||
|
||||
В качестве альтернативы, мы можем скомпилировать его для хост-системы, передав дополнительные аргументы компоновщика:
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
cargo rustc -- -C link-arg=-nostartfiles
|
||||
# Windows
|
||||
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
||||
# macOS
|
||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||
```
|
||||
|
||||
Обратите внимание, что это лишь минимальный пример независимого бинарного файла Rust. Этот бинарник ожидает различных вещей, например, инициализацию стека при вызове функции `_start`. **Поэтому для любого реального использования такого бинарного файла потребуется совершить еще больше действий**.
|
||||
|
||||
## Что дальше?
|
||||
|
||||
В [следующем посте][next post] описаны шаги, необходимые для превращения нашего независимого бинарного файла в минимальное ядро операционной системы. Сюда входит создание custom target, объединение нашего исполняемого файла с загрузчиком и изучение, как вывести что-то на экран.
|
||||
|
||||
[next post]: @/edition-2/posts/02-minimal-rust-kernel/index.ru.md
|
||||