+++ title = "یک هسته مینیمال با Rust" weight = 2 path = "fa/minimal-rust-kernel" date = 2018-02-10 [extra] chapter = "Bare Bones" # Please update this when updating the translation translation_based_on_commit = "7212ffaa8383122b1eb07fe1854814f99d2e1af4" # GitHub usernames of the people that translated this post translators = ["hamidrezakp", "MHBahrampour"] rtl = true +++ در این پست ما برای معماری x86 یک هسته مینیمال ۶۴ بیتی به زبان راست میسازیم. با استفاده از باینری مستقل Rust از پست قبل، یک دیسک ایمیج قابل بوت میسازیم، که متنی را در صفحه چاپ کند. [باینری مستقل Rust]: @/edition-2/posts/01-freestanding-rust-binary/index.md این بلاگ بصورت آزاد روی [گیتهاب] توسعه داده شده است. اگر شما مشکل یا سوالی دارید، لطفاً آنجا یک ایشو باز کنید. شما همچنین میتوانید [در زیر] این پست کامنت بگذارید. منبع کد کامل این پست را میتوانید در بِرَنچ [`post-02`][post branch] پیدا کنید. [گیتهاب]: https://github.com/phil-opp/blog_os [در زیر]: #comments [post branch]: https://github.com/phil-opp/blog_os/tree/post-02 ## فرآیند بوت شدن وقتی یک رایانه را روشن میکنید، شروع به اجرای کد فِرْموِر (کلمه: firmware) ذخیره شده در [ROM] مادربرد میکند. این کد یک [power-on self-test] انجام میدهد، رم موجود را تشخیص داده، و پردازنده و سخت افزار را پیش مقداردهی اولیه میکند. پس از آن به یک دنبال دیسک قابل بوت میگردد و شروع به بوت کردن هسته سیستم عامل میکند. [ROM]: https://en.wikipedia.org/wiki/Read-only_memory [power-on self-test]: https://en.wikipedia.org/wiki/Power-on_self-test در x86، دو استاندارد فِرْموِر (کلمه: firmware) وجود دارد: «سامانهٔ ورودی/خروجیِ پایه» (**[BIOS]**) و استاندارد جدیدتر «رابط فِرْموِر توسعه یافته یکپارچه» (**[UEFI]**). استاندارد BIOS قدیمی و منسوخ است، اما ساده است و از دهه ۱۹۸۰ تاکنون در هر دستگاه x86 کاملاً پشتیبانی میشود. در مقابل، UEFI مدرنتر است و ویژگیهای بسیار بیشتری دارد، اما راه اندازی آن پیچیدهتر است (حداقل به نظر من). [BIOS]: https://en.wikipedia.org/wiki/BIOS [UEFI]: https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface در حال حاضر، ما فقط پشتیبانی BIOS را ارائه میدهیم، اما پشتیبانی از UEFI نیز برنامهریزی شده است. اگر میخواهید در این زمینه به ما کمک کنید، [ایشو گیتهاب](https://github.com/phil-opp/blog_os/issues/349) را بررسی کنید. ### بوت شدن BIOS تقریباً همه سیستمهای x86 از بوت شدن BIOS پشتیبانی میکنند، از جمله سیستمهای جدیدترِ مبتنی بر UEFI که از BIOS شبیهسازی شده استفاده میکنند. این عالی است، زیرا شما میتوانید از منطق بوت یکسانی در تمام سیستمهای قرنهای گذشته استفاده کنید. اما این سازگاری گسترده در عین حال بزرگترین نقطه ضعف راهاندازی BIOS است، زیرا این بدان معناست که پردازنده قبل از بوت شدن در یک حالت سازگاری 16 بیتی به نام [real mode] قرار داده میشود تا بوتلودرهای قدیمی از دهه 1980 همچنان کار کنند. اما بیایید از ابتدا شروع کنیم: وقتی یک رایانه را روشن میکنید، BIOS را از حافظه فلش مخصوصی که روی مادربرد قرار دارد بارگذاری میکند. BIOS روالهای خودآزمایی و مقداردهی اولیه سخت افزار را اجرا می کند، سپس به دنبال دیسکهای قابل بوت میگردد. اگر یکی را پیدا کند، کنترل به _بوتلودرِ_ آن منتقل میشود، که یک قسمت ۵۱۲ بایتی از کد اجرایی است و در ابتدای دیسک ذخیره شده است. بیشتر بوتلودرها از ۵۱۲ بایت بزرگتر هستند، بنابراین بوتلودرها معمولاً به یک قسمت کوچک ابتدایی تقسیم میشوند که در ۵۱۲ بایت جای میگیرد و قسمت دوم که متعاقباً توسط قسمت اول بارگذاری میشود. بوتلودر باید محل ایمیج هسته را بر روی دیسک تعیین کرده و آن را در حافظه بارگذاری کند. همچنین ابتدا باید CPU را از [real mode] (ترجمه: حالت واقعی) 16 بیتی به [protected mode] (ترجمه: حالت محافظت شده) 32 بیتی و سپس به [long mode] (ترجمه: حالت طولانی) 64 بیتی سوییچ کند، جایی که ثباتهای 64 بیتی و کل حافظه اصلی در آن در دسترس هستند. کار سوم آن پرسوجو درباره اطلاعات خاص (مانند نگاشت حافظه) از BIOS و انتقال آن به هسته سیستم عامل است. [real mode]: https://en.wikipedia.org/wiki/Real_mode [protected mode]: https://en.wikipedia.org/wiki/Protected_mode [long mode]: https://en.wikipedia.org/wiki/Long_mode [memory segmentation]: https://en.wikipedia.org/wiki/X86_memory_segmentation نوشتن بوتلودر کمی دشوار است زیرا به زبان اسمبلی و بسیاری از مراحل غیر بصیرانه مانند "نوشتن این مقدار جادویی در این ثبات پردازنده" نیاز دارد. بنابراین ما در این پست ایجاد بوتلودر را پوشش نمیدهیم و در عوض ابزاری به نام [bootimage] را ارائه میدهیم که بوتلودر را به طور خودکار به هسته شما اضافه میکند. [bootimage]: https://github.com/rust-osdev/bootimage اگر علاقهمند به ساخت بوتلودر هستید: با ما همراه باشید، مجموعهای از پستها در این زمینه از قبل برنامهریزی شده است! #### استاندارد بوت چندگانه برای جلوگیری از این که هر سیستم عاملی بوتلودر خود را پیادهسازی کند، که فقط با یک سیستم عامل سازگار است، [بنیاد نرم افزار آزاد] در سال 1995 یک استاندارد بوتلودر آزاد به نام [Multiboot] ایجاد کرد. این استاندارد یک رابط بین بوتلودر و سیستم عامل را تعریف میکند، به طوری که هر بوتلودر سازگار با Multiboot میتواند هر سیستم عامل سازگار با Multiboot را بارگذاری کند. پیادهسازی مرجع [GNU GRUB] است که محبوبترین بوتلودر برای سیستمهای لینوکس است. [بنیاد نرم افزار آزاد]: https://en.wikipedia.org/wiki/Free_Software_Foundation [Multiboot]: https://wiki.osdev.org/Multiboot [GNU GRUB]: https://en.wikipedia.org/wiki/GNU_GRUB برای سازگار کردن هسته با Multiboot، کافیست یک به اصطلاح [Multiboot header] را در ابتدای فایل هسته اضافه کنید. با این کار بوت کردن سیستم عامل در GRUB بسیار آسان خواهد شد. با این حال، GRUB و استاندارد Multiboot نیز دارای برخی مشکلات هستند: [Multiboot header]: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#OS-image-format - آنها فقط از حالت محافظت شده 32 بیتی پشتیبانی میکنند. این بدان معناست که شما برای تغییر به حالت طولانی 64 بیتی هنوز باید پیکربندی CPU را انجام دهید. - آنها برای ساده سازی بوتلودر طراحی شدهاند نه برای ساده سازی هسته. به عنوان مثال، هسته باید با [اندازه صفحه پیش فرض تنظیم شده] پیوند داده شود، زیرا GRUB در غیر اینصورت نمیتواند هدر Multiboot را پیدا کند. مثال دیگر این است که [اطلاعات بوت]، که به هسته منتقل میشوند، به جای ارائه انتزاعات تمیز و واضح، شامل ساختارها با وابستگی زیاد به معماری هستند. - هر دو استاندارد GRUB و Multiboot بصورت ناقص مستند شدهاند. - برای ایجاد یک ایمیج دیسکِ قابل بوت از فایل هسته، GRUB باید روی سیستم میزبان نصب شود. این امر باعث دشوارتر شدنِ توسعه در ویندوز یا Mac میشود. [اندازه صفحه پیش فرض تنظیم شده]: https://wiki.osdev.org/Multiboot#Multiboot_2 [اطلاعات بوت]: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format به دلیل این اشکالات ما تصمیم گرفتیم از GRUB یا استاندارد Multiboot استفاده نکنیم. با این حال، ما قصد داریم پشتیبانی Multiboot را به ابزار [bootimage] خود اضافه کنیم، به طوری که امکان بارگذاری هسته شما بر روی یک سیستم با بوتلودر GRUB نیز وجود داشته باشد. اگر علاقهمند به نوشتن هسته سازگار با Multiboot هستید، [نسخه اول] مجموعه پستهای این وبلاگ را بررسی کنید. [نسخه اول]: @/edition-1/_index.md ### UEFI (ما در حال حاضر پشتیبانی UEFI را ارائه نمیدهیم، اما خیلی دوست داریم این کار را انجام دهیم! اگر میخواهید کمک کنید، لطفاً در [ایشو گیتهاب](https://github.com/phil-opp/blog_os/issues/349) به ما بگویید.) ## یک هسته مینیمال اکنون که تقریباً میدانیم چگونه یک کامپیوتر بوت میشود، وقت آن است که هسته مینیمال خودمان را ایجاد کنیم. هدف ما ایجاد دیسک ایمیجی میباشد که “!Hello World” را هنگام بوت شدن چاپ کند. برای این منظور از [باینری مستقل Rust] که در پست قبل دیدید استفاده میکنیم. همانطور که ممکن است به یاد داشته باشید، باینری مستقل را از طریق `cargo` ایجاد کردیم، اما با توجه به سیستم عامل، به نامهای ورودی و پرچمهای کامپایل مختلف نیاز داشتیم. به این دلیل که `cargo` به طور پیش فرض برای سیستم میزبان بیلد میکند، بطور مثال سیستمی که از آن برای نوشتن هسته استفاده میکنید. این چیزی نیست که ما برای هسته خود بخواهیم، زیرا منطقی نیست که هسته سیستم عاملمان را روی یک سیستم عامل دیگر اجرا کنیم. در عوض، ما میخواهیم هسته را برای یک _سیستم هدف_ کاملاً مشخص کامپایل کنیم. ### نصب Rust Nightly {#installing-rust-nightly} راست دارای سه کانال انتشار است: _stable_, _beta_, and _nightly_ (ترجمه از چپ به راست: پایدار، بتا و شبانه). کتاب Rust تفاوت بین این کانالها را به خوبی توضیح میدهد، بنابراین یک دقیقه وقت بگذارید و [آن را بررسی کنید](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html#choo-choo-release-channels-and-riding-the-trains). برای ساخت یک سیستم عامل به برخی از ویژگیهای آزمایشی نیاز داریم که فقط در کانال شبانه موجود است، بنابراین باید نسخه شبانه Rust را نصب کنیم. برای مدیریت نصبهای Rust من به شدت [rustup] را توصیه میکنم. به شما این امکان را میدهد که کامپایلرهای شبانه، بتا و پایدار را در کنار هم نصب کنید و بروزرسانی آنها را آسان میکند. با rustup شما میتوانید از یک کامپایلر شبانه برای دایرکتوری جاری استفاده کنید، کافیست دستور `rustup override set nightly` را اجرا کنید. همچنین میتوانید فایلی به نام `rust-toolchain` را با محتوای `nightly` در دایرکتوری ریشه پروژه اضافه کنید. با اجرای `rustc --version` میتوانید چک کنید که نسخه شبانه را دارید یا نه. شماره نسخه باید در پایان شامل `nightly-` باشد. [rustup]: https://www.rustup.rs/ کامپایلر شبانه به ما امکان میدهد با استفاده از به اصطلاح _feature flags_ در بالای فایل، از ویژگیهای مختلف آزمایشی استفاده کنیم. به عنوان مثال، میتوانیم [`asm!` macro] آزمایشی را برای اجرای دستورات اسمبلیِ اینلاین (تلفظ: inline) با اضافه کردن `[feature(asm)]!#` به بالای فایل `main.rs` فعال کنیم. توجه داشته باشید که این ویژگیهای آزمایشی، کاملاً ناپایدار هستند، به این معنی که نسخههای آتی Rust ممکن است بدون هشدار قبلی آنها را تغییر داده یا حذف کند. به همین دلیل ما فقط در صورت لزوم از آنها استفاده خواهیم کرد. [`asm!` macro]: https://doc.rust-lang.org/stable/reference/inline-assembly.html ### مشخصات هدف کارگو (کلمه: cargo) سیستمهای هدف مختلف را از طریق `target--` پشتیبانی میکند. سیستم هدف توسط یک به اصطلاح _[target triple]_ (ترجمه: هدف سه گانه) توصیف شده است، که معماری CPU، فروشنده، سیستم عامل، و [ABI] را شامل میشود. برای مثال، هدف سه گانه `x86_64-unknown-linux-gnu` یک سیستم را توصیف میکند که دارای سیپییو `x86_64`، بدون فروشنده مشخص و یک سیستم عامل لینوکس با GNU ABI است. Rust از [هدفهای سه گانه مختلفی][platform-support] پشتیبانی میکند، شامل `arm-linux-androideabi` برای اندروید یا [`wasm32-unknown-unknown` برای وباسمبلی](https://www.hellorust.com/setup/wasm-target/). [target triple]: https://clang.llvm.org/docs/CrossCompilation.html#target-triple [ABI]: https://stackoverflow.com/a/2456882 [platform-support]: https://forge.rust-lang.org/release/platform-support.html [custom-targets]: https://doc.rust-lang.org/nightly/rustc/targets/custom.html برای سیستم هدف خود، به برخی از پارامترهای خاص پیکربندی نیاز داریم (به عنوان مثال، فاقد سیستم عامل زیرین)، بنابراین هیچ یک از [اهداف سه گانه موجود][platform-support] مناسب نیست. خوشبختانه Rust به ما اجازه میدهد تا [هدف خود][custom-targets] را از طریق یک فایل JSON تعریف کنیم. به عنوان مثال، یک فایل JSON که هدف `x86_64-unknown-linux-gnu` را توصیف میکند به این شکل است: ```json { "llvm-target": "x86_64-unknown-linux-gnu", "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", "arch": "x86_64", "target-endian": "little", "target-pointer-width": "64", "target-c-int-width": "32", "os": "linux", "executables": true, "linker-flavor": "gcc", "pre-link-args": ["-m64"], "morestack": false } ``` اکثر فیلدها برای LLVM مورد نیاز هستند تا بتواند کد را برای آن پلتفرم ایجاد کند. برای مثال، فیلد [`data-layout`] اندازه انواع مختلف عدد صحیح، مُمَیزِ شناور و انواع اشارهگر را تعریف میکند. سپس فیلدهایی وجود دارد که Rust برای کامپایل شرطی از آنها استفاده میکند، مانند `target-pointer-width`. نوع سوم فیلدها نحوه ساخت crate (تلفظ: کرِیت) را تعریف میکنند. مثلا، فیلد `pre-link-args` آرگومانهای منتقل شده به [لینکر] را مشخص میکند. [`data-layout`]: https://llvm.org/docs/LangRef.html#data-layout [لینکر]: https://en.wikipedia.org/wiki/Linker_(computing) ما همچنین سیستمهای `x86_64` را با هسته خود مورد هدف قرار میدهیم، بنابراین مشخصات هدف ما بسیار شبیه به مورد بالا خواهد بود. بیایید با ایجاد یک فایل `x86_64-blog_os.json` شروع کنیم (هر اسمی را که دوست دارید انتخاب کنید) با محتوای مشترک: ```json { "llvm-target": "x86_64-unknown-none", "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", "arch": "x86_64", "target-endian": "little", "target-pointer-width": "64", "target-c-int-width": "32", "os": "none", "executables": true } ``` توجه داشته باشید که ما OS را در `llvm-target` و همچنین فیلد `os` را به `none` تغییر دادیم، زیرا ما هسته را روی یک bare metal اجرا میکنیم. همچنین موارد زیر که مربوط به ساخت (ترجمه: build-related) هستند را اضافه میکنیم: ```json "linker-flavor": "ld.lld", "linker": "rust-lld", ``` به جای استفاده از لینکر پیش فرض پلتفرم (که ممکن است از اهداف لینوکس پشتیبانی نکند)، ما از لینکر کراس پلتفرم [LLD] استفاده میکنیم که برای پیوند دادن هسته ما با Rust ارائه میشود. [LLD]: https://lld.llvm.org/ ```json "panic-strategy": "abort", ``` این تنظیم مشخص میکند که هدف از [stack unwinding] درهنگام panic پشتیبانی نمیکند، بنابراین به جای آن خود برنامه باید مستقیماً متوقف شود. این همان اثر است که آپشن `panic = "abort"` در فایل Cargo.toml دارد، پس میتوانیم آن را از فایل Cargo.toml حذف کنیم.(توجه داشته باشید که این آپشنِ هدف همچنین زمانی اعمال میشود که ما کتابخانه `هسته` را مجددا در ادامه همین پست کامپایل میکنیم. بنابراین حتماً این گزینه را اضافه کنید، حتی اگر ترجیح می دهید گزینه Cargo.toml را حفظ کنید.) [stack unwinding]: https://www.bogotobogo.com/cplusplus/stackunwinding.php ```json "disable-redzone": true, ``` ما در حال نوشتن یک هسته هستیم، بنابراین بالاخره باید وقفهها را مدیریت کنیم. برای انجام ایمن آن، باید بهینهسازی اشارهگر پشتهای خاصی به نام _“red zone”_ (ترجمه: منطقه قرمز) را غیرفعال کنیم، زیرا در غیر این صورت باعث خراب شدن پشته میشود. برای اطلاعات بیشتر، به پست جداگانه ما در مورد [غیرفعال کردن منطقه قرمز] مراجعه کنید. [غیرفعال کردن منطقه قرمز]: @/edition-2/posts/02-minimal-rust-kernel/disable-red-zone/index.md ```json "features": "-mmx,-sse,+soft-float", ``` فیلد `features` ویژگیهای هدف را فعال/غیرفعال میکند. ما ویژگیهای `mmx` و `sse` را با گذاشتن یک منفی در ابتدای آنها غیرفعال کردیم و ویژگی `soft-float` را با اضافه کردن یک مثبت به ابتدای آن فعال کردیم. توجه داشته باشید که بین پرچمهای مختلف نباید فاصلهای وجود داشته باشد، در غیر این صورت LLVM قادر به تفسیر رشته ویژگیها نیست. ویژگیهای `mmx` و `sse` پشتیبانی از دستورالعملهای [Single Instruction Multiple Data (SIMD)] را تعیین میکنند، که اغلب میتواند سرعت برنامهها را به میزان قابل توجهی افزایش دهد. با این حال، استفاده از ثباتهای بزرگ SIMD در هسته سیستم عامل منجر به مشکلات عملکردی میشود. دلیل آن این است که هسته قبل از ادامه یک برنامهی متوقف شده، باید تمام رجیسترها را به حالت اولیه خود برگرداند. این بدان معناست که هسته در هر فراخوانی سیستم یا وقفه سخت افزاری باید حالت کامل SIMD را در حافظه اصلی ذخیره کند. از آنجا که حالت SIMD بسیار بزرگ است (512-1600 بایت) و وقفهها ممکن است اغلب اتفاق بیفتند، این عملیات ذخیره و بازیابی اضافی به طور قابل ملاحظهای به عملکرد آسیب میرساند. برای جلوگیری از این، SIMD را برای هسته خود غیرفعال میکنیم (نه برای برنامههایی که از روی آن اجرا می شوند!). [Single Instruction Multiple Data (SIMD)]: https://en.wikipedia.org/wiki/SIMD یک مشکل در غیرفعال کردن SIMD این است که عملیاتهای مُمَیزِ شناور (ترجمه: floating point) در `x86_64` به طور پیش فرض به ثباتهای SIMD نیاز دارد. برای حل این مشکل، ویژگی `soft-float` را اضافه میکنیم، که از طریق عملکردهای نرمافزاری مبتنی بر اعداد صحیح عادی، تمام عملیات مُمَیزِ شناور را شبیهسازی میکند. For more information, see our post on [disabling SIMD](@/edition-2/posts/02-minimal-rust-kernel/disable-simd/index.md). #### کنار هم قرار دادن فایل مشخصات هدف ما اکنون به این شکل است: ```json { "llvm-target": "x86_64-unknown-none", "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", "arch": "x86_64", "target-endian": "little", "target-pointer-width": "64", "target-c-int-width": "32", "os": "none", "executables": true, "linker-flavor": "ld.lld", "linker": "rust-lld", "panic-strategy": "abort", "disable-redzone": true, "features": "-mmx,-sse,+soft-float" } ``` ### ساخت هسته عملیات کامپایل کردن برای هدف جدید ما از قراردادهای لینوکس استفاده خواهد کرد (کاملاً مطمئن نیستم که چرا، تصور میکنم این فقط پیش فرض LLVM باشد). این بدان معنی است که ما به یک نقطه ورود به نام `start_` نیاز داریم همانطور که در [پست قبلی] توضیح داده شد: [پست قبلی]: @/edition-2/posts/01-freestanding-rust-binary/index.md ```rust // src/main.rs #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points use core::panic::PanicInfo; /// This function is called on panic. #[panic_handler] fn panic(_info: &PanicInfo) -> ! { loop {} } #[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 {} } ``` توجه داشته باشید که بدون توجه به سیستم عامل میزبان، باید نقطه ورود را `start_` بنامید. اکنون میتوانیم با نوشتن نام فایل JSON بعنوان `target--`، هسته خود را برای هدف جدید بسازیم: ``` > cargo build --target x86_64-blog_os.json error[E0463]: can't find crate for `core` ``` شکست میخورد! این خطا به ما میگوید که کامپایلر Rust دیگر [کتابخانه `core`] را پیدا نمیکند. این کتابخانه شامل انواع اساسی Rust مانند `Result` ، `Option` و iterators است، و به طور ضمنی به همه کریتهای `no_std` لینک است. [کتابخانه `core`]: https://doc.rust-lang.org/nightly/core/index.html مشکل این است که کتابخانه core همراه با کامپایلر Rust به عنوان یک کتابخانه _precompiled_ (ترجمه: از پیش کامپایل شده) توزیع میشود. بنابراین فقط برای میزبانهای سهگانه پشتیبانی شده مجاز است (مثلا، `x86_64-unknown-linux-gnu`) اما برای هدف سفارشی ما صدق نمیکند. اگر میخواهیم برای سیستمهای هدف دیگر کدی را کامپایل کنیم، ابتدا باید `core` را برای این اهداف دوباره کامپایل کنیم. #### آپشن `build-std` اینجاست که [ویژگی `build-std`] کارگو وارد میشود. این امکان را میدهد تا بجای استفاده از نسخههای از پیش کامپایل شده با نصب Rust، بتوانیم `core` و کریت سایر کتابخانههای استاندارد را در صورت نیاز دوباره کامپایل کنیم. این ویژگی بسیار جدید بوده و هنوز تکمیل نشده است، بنابراین بعنوان «ناپایدار» علامت گذاری شده و فقط در [نسخه شبانه کامپایلر Rust] در دسترس میباشد. [ویژگی `build-std`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std [نسخه شبانه کامپایلر Rust]: #installing-rust-nightly برای استفاده از این ویژگی، ما نیاز داریم تا یک فایل [پیکربندی کارگو] در `cargo/config.toml.` با محتوای زیر بسازیم: ```toml # in .cargo/config.toml [unstable] build-std = ["core", "compiler_builtins"] ``` این به کارگو میگوید که باید `core` و کتابخانه `compiler_builtins` را دوباره کامپایل کند. مورد دوم لازم است زیرا یک وابستگی از `core` است. به منظور کامپایل مجدد این کتابخانهها، کارگو نیاز به دسترسی به کد منبع Rust دارد که میتوانیم آن را با `rustup component add rust-src` نصب کنیم.