Showing preview only (5,440K chars total). Download the full file or copy to clipboard to get everything.
Repository: phil-opp/blog_os
Branch: main
Commit: 4f524ff4167c
Files: 240
Total size: 5.1 MB
Directory structure:
gitextract_p9hgi7es/
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ ├── blog.yml
│ ├── check-links.yml
│ └── scheduled-builds.yml
├── .gitignore
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── blog/
│ ├── .gitignore
│ ├── before_build.py
│ ├── config.toml
│ ├── content/
│ │ ├── LICENSE-CC-BY-NC
│ │ ├── README.md
│ │ ├── _index.ar.md
│ │ ├── _index.es.md
│ │ ├── _index.fa.md
│ │ ├── _index.fr.md
│ │ ├── _index.ja.md
│ │ ├── _index.ko.md
│ │ ├── _index.md
│ │ ├── _index.pt-BR.md
│ │ ├── _index.ru.md
│ │ ├── _index.zh-CN.md
│ │ ├── _index.zh-TW.md
│ │ ├── edition-1/
│ │ │ ├── _index.md
│ │ │ ├── extra/
│ │ │ │ ├── _index.md
│ │ │ │ ├── cross-compile-binutils.md
│ │ │ │ ├── cross-compile-libcore.md
│ │ │ │ ├── naked-exceptions/
│ │ │ │ │ ├── 01-catching-exceptions/
│ │ │ │ │ │ └── index.md
│ │ │ │ │ ├── 02-better-exception-messages/
│ │ │ │ │ │ └── index.md
│ │ │ │ │ ├── 03-returning-from-exceptions/
│ │ │ │ │ │ └── index.md
│ │ │ │ │ └── _index.md
│ │ │ │ ├── set-up-gdb/
│ │ │ │ │ └── index.md
│ │ │ │ └── talks.md
│ │ │ └── posts/
│ │ │ ├── 01-multiboot-kernel/
│ │ │ │ └── index.md
│ │ │ ├── 02-entering-longmode/
│ │ │ │ └── index.md
│ │ │ ├── 03-set-up-rust/
│ │ │ │ └── index.md
│ │ │ ├── 04-printing-to-screen/
│ │ │ │ └── index.md
│ │ │ ├── 05-allocating-frames/
│ │ │ │ └── index.md
│ │ │ ├── 06-page-tables/
│ │ │ │ └── index.md
│ │ │ ├── 07-remap-the-kernel/
│ │ │ │ └── index.md
│ │ │ ├── 08-kernel-heap/
│ │ │ │ └── index.md
│ │ │ ├── 09-handling-exceptions/
│ │ │ │ └── index.md
│ │ │ ├── 10-double-faults/
│ │ │ │ └── index.md
│ │ │ └── _index.md
│ │ ├── edition-2/
│ │ │ ├── _index.md
│ │ │ ├── extra/
│ │ │ │ ├── _index.md
│ │ │ │ └── building-on-android/
│ │ │ │ └── index.md
│ │ │ └── posts/
│ │ │ ├── 01-freestanding-rust-binary/
│ │ │ │ ├── index.ar.md
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.fa.md
│ │ │ │ ├── index.fr.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.ko.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ ├── index.ru.md
│ │ │ │ ├── index.zh-CN.md
│ │ │ │ └── index.zh-TW.md
│ │ │ ├── 02-minimal-rust-kernel/
│ │ │ │ ├── _index.md
│ │ │ │ ├── disable-red-zone/
│ │ │ │ │ ├── index.ko.md
│ │ │ │ │ ├── index.md
│ │ │ │ │ ├── index.pt-BR.md
│ │ │ │ │ ├── index.ru.md
│ │ │ │ │ └── index.zh-CN.md
│ │ │ │ ├── disable-simd/
│ │ │ │ │ ├── index.ko.md
│ │ │ │ │ ├── index.md
│ │ │ │ │ ├── index.pt-BR.md
│ │ │ │ │ ├── index.ru.md
│ │ │ │ │ └── index.zh-CN.md
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.fa.md
│ │ │ │ ├── index.fr.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.ko.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ ├── index.ru.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 03-vga-text-buffer/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.fa.md
│ │ │ │ ├── index.fr.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.ko.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 04-testing/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.fa.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.ko.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 05-cpu-exceptions/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.fa.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.ko.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 06-double-faults/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.fa.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.ko.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 07-hardware-interrupts/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.fa.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.ko.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 08-paging-introduction/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.fa.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 09-paging-implementation/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 10-heap-allocation/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 11-allocator-designs/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ ├── index.ru.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 12-async-await/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ ├── index.ru.md
│ │ │ │ ├── index.zh-CN.md
│ │ │ │ ├── index.zh-TW.md
│ │ │ │ └── scancode-queue.drawio
│ │ │ ├── _index.ar.md
│ │ │ ├── _index.es.md
│ │ │ ├── _index.fa.md
│ │ │ ├── _index.fr.md
│ │ │ ├── _index.ja.md
│ │ │ ├── _index.ko.md
│ │ │ ├── _index.md
│ │ │ ├── _index.pt-BR.md
│ │ │ ├── _index.ru.md
│ │ │ ├── _index.zh-CN.md
│ │ │ ├── _index.zh-TW.md
│ │ │ └── deprecated/
│ │ │ ├── 04-unit-testing/
│ │ │ │ └── index.md
│ │ │ ├── 05-integration-tests/
│ │ │ │ └── index.md
│ │ │ ├── 10-advanced-paging/
│ │ │ │ └── index.md
│ │ │ └── _index.md
│ │ ├── news/
│ │ │ ├── 2018-03-09-pure-rust.md
│ │ │ └── _index.md
│ │ ├── pages/
│ │ │ ├── _index.md
│ │ │ └── contact.md
│ │ └── status-update/
│ │ ├── 2019-05-01.md
│ │ ├── 2019-06-03.md
│ │ ├── 2019-07-06.md
│ │ ├── 2019-08-02.md
│ │ ├── 2019-09-09.md
│ │ ├── 2019-10-06.md
│ │ ├── 2019-12-02.md
│ │ ├── 2020-01-07.md
│ │ ├── 2020-02-01.md
│ │ ├── 2020-03-02.md
│ │ ├── 2020-04-01/
│ │ │ └── index.md
│ │ └── _index.md
│ ├── diagrams/
│ │ ├── red-zone-overwrite.dia
│ │ ├── red-zone.dia
│ │ └── xmm-overwrite.dia
│ ├── requirements.txt
│ ├── sass/
│ │ └── css/
│ │ └── edition-2/
│ │ └── main.scss
│ ├── static/
│ │ ├── CNAME
│ │ ├── atom.xml/
│ │ │ └── index.html
│ │ ├── css/
│ │ │ └── edition-1/
│ │ │ ├── isso.css
│ │ │ ├── main.css
│ │ │ └── poole.css
│ │ ├── handling-exceptions-with-naked-fns.html
│ │ └── js/
│ │ ├── edition-1/
│ │ │ └── main.js
│ │ └── edition-2/
│ │ └── main.js
│ ├── templates/
│ │ ├── 404.html
│ │ ├── auto/
│ │ │ ├── forks.html
│ │ │ ├── recent-updates.html
│ │ │ ├── stars.html
│ │ │ ├── status-updates-truncated.html
│ │ │ └── status-updates.html
│ │ ├── base.html
│ │ ├── edition-1/
│ │ │ ├── base.html
│ │ │ ├── comments/
│ │ │ │ ├── allocating-frames.html
│ │ │ │ ├── better-exception-messages.html
│ │ │ │ ├── catching-exceptions.html
│ │ │ │ ├── double-faults.html
│ │ │ │ ├── entering-longmode.html
│ │ │ │ ├── handling-exceptions.html
│ │ │ │ ├── kernel-heap.html
│ │ │ │ ├── multiboot-kernel.html
│ │ │ │ ├── page-tables.html
│ │ │ │ ├── printing-to-screen.html
│ │ │ │ ├── remap-the-kernel.html
│ │ │ │ ├── returning-from-exceptions.html
│ │ │ │ └── set-up-rust.html
│ │ │ ├── comments.html
│ │ │ ├── handling-exceptions-with-naked-fns.html
│ │ │ ├── index.html
│ │ │ ├── macros.html
│ │ │ ├── page.html
│ │ │ └── section.html
│ │ ├── edition-2/
│ │ │ ├── base.html
│ │ │ ├── extra.html
│ │ │ ├── index.html
│ │ │ ├── macros.html
│ │ │ ├── page.html
│ │ │ └── section.html
│ │ ├── index.html
│ │ ├── news-page.html
│ │ ├── news-section.html
│ │ ├── plain.html
│ │ ├── redirect-to-frontpage.html
│ │ ├── rss.xml
│ │ ├── section.html
│ │ ├── snippets.html
│ │ ├── status-update-page.html
│ │ └── status-update-section.html
│ └── typos.toml
├── docker/
│ ├── .bash_aliases
│ ├── Dockerfile
│ ├── README.md
│ └── entrypoint.sh
├── giscus.json
└── scripts/
├── merge.fish
└── push.fish
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [phil-opp] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
custom: ['https://donorbox.org/phil-opp'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
patreon: phil_opp # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: phil-opp # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
================================================
FILE: .github/workflows/blog.yml
================================================
name: Blog
on:
push:
branches:
- '*'
- '!staging.tmp'
tags:
- '*'
pull_request:
schedule:
- cron: '0 0 1/4 * *' # every 4 days
jobs:
build_site:
name: "Zola Build"
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: 'Install Python Libraries'
run: python -m pip install --user -r requirements.txt
working-directory: "blog"
- name: "Run before_build.py script"
run: python before_build.py
working-directory: "blog"
- name: "Build Site"
run: ../zola build
working-directory: "blog"
- name: Upload Generated Site
uses: actions/upload-artifact@v4
with:
name: generated_site
path: blog/public
check_spelling:
name: "Check Spelling"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- 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/main' && (github.event_name == 'push' || github.event_name == 'schedule')
steps:
- name: "Download Generated Site"
uses: actions/download-artifact@v4
with:
name: generated_site
path: generated_site
- name: Setup SSH Keys and known_hosts
run: |
mkdir -p ~/.ssh
ssh-keyscan github.com >> ~/.ssh/known_hosts
ssh-agent -a $SSH_AUTH_SOCK > /dev/null
ssh-add - <<< "$deploy_key"
echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" >> $GITHUB_ENV
env:
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
deploy_key: ${{ secrets.DEPLOY_SSH_KEY }}
- name: "Clone blog_os_deploy Repo"
run: git clone git@github.com:phil-opp/blog_os_deploy.git --branch gh-pages
- name: "Set Up Git Identity"
run: |
git config --local user.name "GitHub Actions Deploy"
git config --local user.email "github-actions-deploy@phil-opp.com"
working-directory: "blog_os_deploy"
- name: "Delete Old Content"
run: "rm -r ./*"
working-directory: "blog_os_deploy"
- name: "Add New Content"
run: cp -r generated_site/* blog_os_deploy
- name: "Commit New Content"
run: |
git add .
git commit --allow-empty -m "Deploy ${GITHUB_SHA}
Deploy of commit https://github.com/phil-opp/blog_os/commit/${GITHUB_SHA}"
working-directory: "blog_os_deploy"
- name: "Show Changes"
run: "git show"
working-directory: "blog_os_deploy"
- name: "Push Changes"
run: "git push"
working-directory: "blog_os_deploy"
================================================
FILE: .github/workflows/check-links.yml
================================================
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"
================================================
FILE: .github/workflows/scheduled-builds.yml
================================================
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 }}
================================================
FILE: .gitignore
================================================
code
================================================
FILE: LICENSE-APACHE
================================================
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
================================================
FILE: LICENSE-MIT
================================================
The MIT License (MIT)
Copyright (c) 2019 Philipp Oppermann
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# Blog OS
This repository contains the source code for the _Writing an OS in Rust_ series at [os.phil-opp.com](https://os.phil-opp.com).
If you have questions, open an issue or chat with us [on Gitter](https://gitter.im/phil-opp/blog_os).
## Where is the code?
The code for each post lives in a separate git branch. This makes it possible to see the intermediate state after each post.
**The code for the latest post is available [here][latest-post].**
[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.
You can check out a branch in a subdirectory using [git worktree]:
[git worktree]: https://git-scm.com/docs/git-worktree
```
git worktree add code post-10
```
The above command creates a subdirectory named `code` that contains the code for the 10th post ("Heap Allocation").
## Posts
The goal of this project is to provide step-by-step tutorials in individual blog posts. We currently have the following set of posts:
**Bare Bones:**
- [A Freestanding Rust Binary](https://os.phil-opp.com/freestanding-rust-binary/)
([source code](https://github.com/phil-opp/blog_os/tree/post-01))
- [A Minimal Rust Kernel](https://os.phil-opp.com/minimal-rust-kernel/)
([source code](https://github.com/phil-opp/blog_os/tree/post-02))
- [VGA Text Mode](https://os.phil-opp.com/vga-text-mode/)
([source code](https://github.com/phil-opp/blog_os/tree/post-03))
- [Testing](https://os.phil-opp.com/testing/)
([source code](https://github.com/phil-opp/blog_os/tree/post-04))
**Interrupts:**
- [CPU Exceptions](https://os.phil-opp.com/cpu-exceptions/)
([source code](https://github.com/phil-opp/blog_os/tree/post-05))
- [Double Faults](https://os.phil-opp.com/double-fault-exceptions/)
([source code](https://github.com/phil-opp/blog_os/tree/post-06))
- [Hardware Interrupts](https://os.phil-opp.com/hardware-interrupts/)
([source code](https://github.com/phil-opp/blog_os/tree/post-07))
**Memory Management:**
- [Introduction to Paging](https://os.phil-opp.com/paging-introduction/)
([source code](https://github.com/phil-opp/blog_os/tree/post-08))
- [Paging Implementation](https://os.phil-opp.com/paging-implementation/)
([source code](https://github.com/phil-opp/blog_os/tree/post-09))
- [Heap Allocation](https://os.phil-opp.com/heap-allocation/)
([source code](https://github.com/phil-opp/blog_os/tree/post-10))
- [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)
([source code](https://github.com/phil-opp/blog_os/tree/first_edition_post_1))
- [Entering Long Mode](https://os.phil-opp.com/entering-longmode.html)
([source code](https://github.com/phil-opp/blog_os/tree/first_edition_post_2))
- [Set Up Rust](https://os.phil-opp.com/set-up-rust.html)
([source code](https://github.com/phil-opp/blog_os/tree/first_edition_post_3))
- [Printing to Screen](https://os.phil-opp.com/printing-to-screen.html)
([source code](https://github.com/phil-opp/blog_os/tree/first_edition_post_4))
**Memory Management:**
- [Allocating Frames](https://os.phil-opp.com/allocating-frames.html)
([source code](https://github.com/phil-opp/blog_os/tree/first_edition_post_5))
- [Page Tables](https://os.phil-opp.com/modifying-page-tables.html)
([source code](https://github.com/phil-opp/blog_os/tree/first_edition_post_6))
- [Remap the Kernel](https://os.phil-opp.com/remap-the-kernel.html)
([source code](https://github.com/phil-opp/blog_os/tree/first_edition_post_7))
- [Kernel Heap](https://os.phil-opp.com/kernel-heap.html)
([source code](https://github.com/phil-opp/blog_os/tree/first_edition_post_8))
**Exceptions:**
- [Handling Exceptions](https://os.phil-opp.com/handling-exceptions.html)
([source code](https://github.com/phil-opp/blog_os/tree/first_edition_post_9))
- [Double Faults](https://os.phil-opp.com/double-faults.html)
([source code](https://github.com/phil-opp/blog_os/tree/first_edition_post_10))
**Additional Resources:**
- [Cross Compile Binutils](https://os.phil-opp.com/cross-compile-binutils.html)
- [Cross Compile libcore](https://os.phil-opp.com/cross-compile-libcore.html)
- [Set Up GDB](https://os.phil-opp.com/set-up-gdb)
- [Handling Exceptions using Naked Functions](https://os.phil-opp.com/handling-exceptions-with-naked-fns.html)
- [Catching Exceptions](https://os.phil-opp.com/catching-exceptions.html)
([source code](https://github.com/phil-opp/blog_os/tree/catching_exceptions))
- [Better Exception Messages](https://os.phil-opp.com/better-exception-messages.html)
([source code](https://github.com/phil-opp/blog_os/tree/better_exception_messages))
- [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
https://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)
at your option.
For licensing of the `blog/content` folder, see the [`blog/content/README.md`](blog/content/README.md).
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
================================================
FILE: blog/.gitignore
================================================
/public
zola
================================================
FILE: blog/before_build.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import io
import urllib
import datetime
from github import Github
g = Github()
one_month_ago = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=32)
def filter_date(issue):
return issue.closed_at > one_month_ago
def format_number(number):
if number > 1000:
return u"{:.1f}k".format(float(number) / 1000)
else:
return u"{}".format(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")[: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 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_str = '<time datetime="' + iso_date + '">' + readable_date + '</time>'
recent_updates.write(u" <li>" + link + datetime_str + "</li>\n")
recent_updates.write(u"</ul>")
repo = g.get_repo("phil-opp/blog_os")
with io.open("templates/auto/stars.html", 'w', encoding='utf8') as stars:
stars.truncate()
stars.write(format_number(repo.stargazers_count))
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)
================================================
FILE: blog/config.toml
================================================
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"
smart_punctuation = true
[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",
"ar",
"es",
"fa",
"fr",
"ja",
"ko",
"pt-BR",
"ru",
"zh-CN",
"zh-TW",
]
[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.
"""
# Portuguese (Brazil)
[languages.pt-BR]
title = "Escrevendo um OS em Rust"
description = "Esta série de blog cria um pequeno sistema operacional na linguagem de programação Rust. Cada post é um pequeno tutorial e inclui todo o código necessário."
[languages.pt-BR.translations]
lang_name = "Português (Brasil)"
toc = "Tabela de Conteúdos"
all_posts = "« Todos os Posts"
comments = "Comentários"
comments_notice = "Por favor, deixe seus comentários em inglês se possível."
readmore = "ler mais »"
not_translated = "(Esta postagem ainda não foi traduzida.)"
translated_content = "Conteúdo Traduzido:"
translated_content_notice = "Esta é uma tradução comunitária do post <strong><a href=\"_original.permalink_\">_original.title_</a></strong>. Pode estar incompleta, desatualizada ou conter erros. Por favor, reporte qualquer problema!"
translated_by = "Traduzido por"
translation_contributors = "Com contribuições de"
word_separator = "e"
support_me = """
<h2>Apoie-me</h2>
<p>Criar e manter este blog e as bibliotecas associadas dá muito trabalho, mas eu realmente gosto de fazê-lo. Ao me apoiar, você me permite investir mais tempo em novo conteúdo, novos recursos e manutenção contínua. A melhor forma de me apoiar é <a href="https://github.com/sponsors/phil-opp"><em>me patrocinar no GitHub</em></a>. Obrigado!</p>
"""
comment_note = """
Teve algum problema, quer deixar um feedback ou discutir mais ideias? Fique à vontade para deixar um comentário aqui! Por favor, use o inglês e siga o <a href="https://www.rust-lang.org/policies/code-of-conduct">código de conduta</a> do Rust. Este tópico de comentários está diretamente vinculado a uma <a href="_discussion_url_"><em>discussão no GitHub</em></a>, então você também pode comentar lá se preferir.
"""
================================================
FILE: blog/content/LICENSE-CC-BY-NC
================================================
Creative Commons Attribution-NonCommercial 4.0 International Public License
By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
Section 1 – Definitions.
Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
Licensor means the individual(s) or entity(ies) granting rights under this Public License.
NonCommercial means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange.
Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.
Section 2 – Scope.
License grant.
Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and
produce, reproduce, and Share Adapted Material for NonCommercial purposes only.
Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
Term. The term of this Public License is specified in Section 6(a).
Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.
Downstream recipients.
Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
Other rights.
Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
Patent and trademark rights are not licensed under this Public License.
To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes.
Section 3 – License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the following conditions.
Attribution.
If You Share the Licensed Material (including in modified form), You must:
retain the following if it is supplied by the Licensor with the Licensed Material:
identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);
a copyright notice;
a notice that refers to this Public License;
a notice that refers to the disclaimer of warranties;
a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License.
Section 4 – Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only;
if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and
You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
Section 5 – Disclaimer of Warranties and Limitation of Liability.
Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.
To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.
The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.
Section 6 – Term and Termination.
This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
Section 7 – Other Terms and Conditions.
The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.
Section 8 – Interpretation.
For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.
================================================
FILE: blog/content/README.md
================================================
# Blog Content
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 <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
https://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](../../LICENSE-MIT) or https://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be licensed as above, without any additional terms or conditions.
================================================
FILE: blog/content/_index.ar.md
================================================
+++
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>
================================================
FILE: blog/content/_index.es.md
================================================
+++
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>
================================================
FILE: blog/content/_index.fa.md
================================================
+++
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>
================================================
FILE: blog/content/_index.fr.md
================================================
+++
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>
================================================
FILE: blog/content/_index.ja.md
================================================
+++
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>
================================================
FILE: blog/content/_index.ko.md
================================================
+++
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>
================================================
FILE: blog/content/_index.md
================================================
+++
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>
================================================
FILE: blog/content/_index.pt-BR.md
================================================
+++
template = "edition-2/index.html"
+++
<h1 style="visibility: hidden; height: 0px; margin: 0px; padding: 0px;">Escrevendo um OS em Rust</h1>
<div class="front-page-introduction">
Esta série de posts do blog cria um pequeno sistema operacional na [linguagem de programação Rust](https://www.rust-lang.org/). Cada post é um pequeno tutorial e inclui todo o código necessário, então você pode acompanhar se quiser. O código-fonte também está disponível no [repositório Github](https://github.com/phil-opp/blog_os) correspondente.
Último post: <!-- latest-post -->
</div>
================================================
FILE: blog/content/_index.ru.md
================================================
+++
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>
================================================
FILE: blog/content/_index.zh-CN.md
================================================
+++
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>
================================================
FILE: blog/content/_index.zh-TW.md
================================================
+++
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>
================================================
FILE: blog/content/edition-1/_index.md
================================================
+++
title = "First Edition"
template = "edition-1/index.html"
aliases = ["first-edition/index.html"]
+++
================================================
FILE: blog/content/edition-1/extra/_index.md
================================================
+++
title = "Extra Content"
insert_anchor_links = "left"
render = false
sort_by = "weight"
+++
================================================
FILE: blog/content/edition-1/extra/cross-compile-binutils.md
================================================
+++
title = "Cross Compile Binutils"
template = "plain.html"
path = "cross-compile-binutils"
weight = 2
+++
The [GNU Binutils] are a collection of various binary tools such as `ld`, `as`, `objdump`, or `readelf`. These tools are platform-specific, so you need to compile them again if your host system and target system are different. In our case, we need `ld` and `objdump` for the x86_64 architecture.
[GNU Binutils]: https://www.gnu.org/software/binutils/
## Building Setup
First, you need to download a current binutils version from [here][download] \(the latest one is near the bottom). After extracting, you should have a folder named `binutils-2.X` where `X` is for example `25.1`. Now can create and switch to a new folder for building (recommended):
[download]: ftp://sourceware.org/pub/binutils/snapshots
```bash
mkdir build-binutils
cd build-binutils
```
## Configuration
We execute binutils's `configure` and pass a lot of arguments to it (replace the `X` with the version number):
```bash
../binutils-2.X/configure --target=x86_64-elf --prefix="$HOME/opt/cross" \
--disable-nls --disable-werror \
--disable-gdb --disable-libdecnumber --disable-readline --disable-sim
```
- The `target` argument specifies the the x86_64 target architecture.
- The `prefix` argument selects the installation directory, you can change it if you like. But be careful that you do not overwrite your system's binutils.
- The `disable-nls` flag disables native language support (so you'll get the same english error messages). It also reduces build dependencies.
- The `disable-werror` turns all warnings into errors.
- The last line disables features we don't need to reduce compile time.
## Building it
Now we can build and install it to the location supplied as `prefix` (it will take a while):
```bash
make
make install
```
Now you should have multiple `x86_64-elf-XXX` files in `$HOME/opt/cross/bin`.
## Adding it to the PATH
To use the tools from the command line easily, you should add the `bin` folder to your PATH:
```bash
export PATH="$HOME/opt/cross/bin:$PATH"
```
If you add this line to your e.g. `.bashrc`, the `x86_64-elf-XXX` commands are always available.
================================================
FILE: blog/content/edition-1/extra/cross-compile-libcore.md
================================================
+++
title = "Cross Compiling: libcore"
template = "plain.html"
path = "cross-compile-libcore"
weight = 3
+++
If you get an `error: can't find crate for 'core'`, you're probably compiling for a different target (e.g. you're passing the `target` option to `cargo build`). Now the compiler complains that it can't find the `core` library. This document gives a quick overview how to fix this problem. For more details, see the [rust-cross] project.
[rust-cross]: https://github.com/japaric/rust-cross
## Libcore
The core library is a dependency-free library that is added implicitly when using `#![no_std]`. It provides basic standard library features like Option or Iterator. The core library is installed together with the rust compiler (just like the std library). But the installed libcore is specific to your architecture. If you aren't working on x86_64 Linux and pass `‑‑target x86_64‑unknown‑linux‑gnu` to cargo, it can't find a x86_64 libcore. To fix this, you can either use `rustup` or `xargo`.
## rustup
Thanks to [rustup], cross-compilation for [official target triples] is pretty easy today: Just execute `rustup target add x86_64-unknown-linux-gnu`.
[rustup]: https://rustup.rs
[official target triples]: https://github.com/japaric/rust-cross#the-target-triple
## xargo
If you're using a _custom target specification_, the `rustup` method doesn't work. Instead, you can use [xargo]. Xargo is a wrapper for cargo that eases cross compilation. We can install it by executing:
```
cargo install xargo
```
If the installation fails, make sure that you have `cmake` and the OpenSSL headers installed. For more details, see the xargo's [dependency section].
[xargo]: https://github.com/japaric/xargo
[dependency section]: https://github.com/japaric/xargo#dependencies
Xargo is “a drop-in replacement for cargo”, so every cargo command also works with `xargo`. You can do e.g. `xargo --help`, `xargo clean`, or `xargo doc`. However, the `build` command gains additional functionality: `xargo build` will automatically cross compile the `core` library (and a few other libraries such as `alloc`) when compiling for custom targets.
[xargo]: https://github.com/japaric/xargo
So if your custom target file is named `your-cool-target.json`, you can compile your code using xargo through `xargo build --target your-cool-target` (note the omitted extension).
================================================
FILE: blog/content/edition-1/extra/naked-exceptions/01-catching-exceptions/index.md
================================================
+++
title = "Catching Exceptions"
weight = 1
path = "catching-exceptions"
aliases = ["catching-exceptions.html"]
date = 2016-05-28
template = "edition-1/page.html"
[extra]
updated = "2016-06-25"
+++
In this post, we start exploring exceptions. We set up an interrupt descriptor table and add handler functions. At the end of this post, our kernel will be able to catch divide-by-zero faults.
<!-- more -->
As always, the complete source code is on [GitHub]. Please file [issues] for any problems, questions, or improvement suggestions. There is also a comment section at the end of this page.
[GitHub]: https://github.com/phil-opp/blog_os/tree/catching_exceptions
[issues]: https://github.com/phil-opp/blog_os/issues
> **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”]: @/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.
We've already seen several types of exceptions in our kernel:
- **Invalid Opcode**: This exception occurs when the current instruction is invalid. For example, this exception occurred when we tried to use SSE instructions before enabling SSE. Without SSE, the CPU didn't know the `movups` and `movaps` instructions, so it throws an exception when it stumbles over them.
- **Page Fault**: A page fault occurs on illegal memory accesses. For example, if the current instruction tries to read from an unmapped page or tries to write to a read-only page.
- **Double Fault**: When an exception occurs, the CPU tries to call the corresponding handler function. If another exception exception occurs _while calling the exception handler_, the CPU raises a double fault exception. This exception also occurs when there is no handler function registered for an exception.
- **Triple Fault**: If an exception occurs while the CPU tries to call the double fault handler function, it issues a fatal _triple fault_. We can't catch or handle a triple fault. Most processors react by resetting themselves and rebooting the operating system. This causes the bootloops we experienced in the previous posts.
For the full list of exceptions check out the [OSDev wiki][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:
Type| Name | Description
----|--------------------------|-----------------------------------
u16 | Function Pointer [0:15] | The lower bits of the pointer to the handler function.
u16 | GDT selector | Selector of a code segment in the GDT.
u16 | Options | (see below)
u16 | Function Pointer [16:31] | The middle bits of the pointer to the handler function.
u32 | Function Pointer [32:63] | The remaining bits of the pointer to the handler function.
u32 | Reserved |
The options field has the following format:
Bits | Name | Description
------|-----------------------------------|-----------------------------------
0-2 | Interrupt Stack Table Index | 0: Don't switch stacks, 1-7: Switch to the n-th stack in the Interrupt Stack Table when this handler is called.
3-7 | Reserved |
8 | 0: Interrupt Gate, 1: Trap Gate | If this bit is 0, interrupts are disabled when this handler is called.
9-11 | must be one |
12 | must be zero |
13‑14 | Descriptor Privilege Level (DPL) | The minimal privilege level required for calling this handler.
15 | Present |
Each exception has a predefined IDT index. For example the invalid opcode exception has table index 6 and the page fault exception has table index 14. Thus, the hardware can automatically load the corresponding IDT entry for each exception. The [Exception Table][exceptions] in the OSDev wiki shows the IDT indexes of all exceptions in the “Vector nr.” column.
When an exception occurs, the CPU roughly does the following:
1. Read the corresponding entry from the Interrupt Descriptor Table (IDT). For example, the CPU reads the 14-th entry when a page fault occurs.
2. Check if the entry is present. Raise a double fault if not.
3. Push some registers on the stack, including the instruction pointer and the [EFLAGS] register. (We will use these values in a future post.)
4. Disable interrupts if the entry is an interrupt gate (bit 40 not set).
5. Load the specified GDT selector into the CS segment.
6. Jump to the specified handler function.
[EFLAGS]: https://en.wikipedia.org/wiki/FLAGS_register
## Handling Exceptions
Let's try to catch and handle CPU exceptions. We start by creating a new `interrupts` module with an `idt` submodule:
``` rust
// in src/lib.rs
...
mod interrupts;
...
```
``` rust
// src/interrupts/mod.rs
mod idt;
```
Now we create types for the IDT and its entries:
```rust
// src/interrupts/idt.rs
use x86_64::instructions::segmentation;
use x86_64::structures::gdt::SegmentSelector;
use x86_64::PrivilegeLevel;
pub struct Idt([Entry; 16]);
#[derive(Debug, Clone, Copy)]
#[repr(C, packed)]
pub struct Entry {
pointer_low: u16,
gdt_selector: SegmentSelector,
options: EntryOptions,
pointer_middle: u16,
pointer_high: u32,
reserved: u32,
}
```
The IDT is variable sized and can have up to 256 entries. We only need the first 16 entries in this post, so we define the table as `[Entry; 16]`. The remaining 240 handlers are treated as non-present by the CPU.
The `Entry` type is the translation of the above table to Rust. The `repr(C, packed)` attribute ensures that the compiler keeps the field ordering and does not add any padding between them. Instead of describing the `gdt_selector` as a plain `u16`, we use the `SegmentSelector` type of the `x86` crate. We also merge bits 32 to 47 into an `option` field, because Rust has no `u3` or `u1` type. The `EntryOptions` type is described below:
### Entry Options
The `EntryOptions` type has the following skeleton:
``` rust
#[derive(Debug, Clone, Copy)]
pub struct EntryOptions(u16);
impl EntryOptions {
fn new() -> Self {...}
pub fn set_present(&mut self, present: bool) {...}
pub fn disable_interrupts(&mut self, disable: bool) {...}
pub fn set_privilege_level(&mut self, dpl: u16) {...}
pub fn set_stack_index(&mut self, index: u16) {...}
}
```
The implementations of these methods need to modify the correct bits of the `u16` without touching the other bits. For example, we would need the following bit-fiddling to set the stack index:
``` rust
self.0 = (self.0 & 0xfff8) | stack_index;
```
Or alternatively:
``` rust
self.0 = (self.0 & (!0b111)) | stack_index;
```
Or:
``` rust
self.0 = ((self.0 >> 3) << 3) | stack_index;
```
Well, none of these variants is really _readable_ and it's very easy to make mistakes somewhere. Therefore I created a `BitField` trait that provides the following [Range]-based API:
[Range]: https://doc.rust-lang.org/nightly/core/ops/struct.Range.html
``` rust
self.0.set_bits(0..3, stack_index);
```
I think it is much more readable, since we abstracted away all bit-masking details. The `BitField` trait is contained in the [bit_field] crate. (It's pretty new, so it might still contain bugs.) To add it as dependency, we run `cargo add bit_field` and add `extern crate bit_field;` to our `src/lib.rs`.
[bit_field]: https://crates.io/crates/bit_field
Now we can use the trait to implement the methods of `EntryOptions`:
```rust
// in src/interrupts/idt.rs
use bit_field::BitField;
#[derive(Debug, Clone, Copy)]
pub struct EntryOptions(u16);
impl EntryOptions {
fn minimal() -> Self {
let mut options = 0;
options.set_bits(9..12, 0b111); // 'must-be-one' bits
EntryOptions(options)
}
fn new() -> Self {
let mut options = Self::minimal();
options.set_present(true).disable_interrupts(true);
options
}
pub fn set_present(&mut self, present: bool) -> &mut Self {
self.0.set_bit(15, present);
self
}
pub fn disable_interrupts(&mut self, disable: bool) -> &mut Self {
self.0.set_bit(8, !disable);
self
}
pub fn set_privilege_level(&mut self, dpl: u16) -> &mut Self {
self.0.set_bits(13..15, dpl);
self
}
pub fn set_stack_index(&mut self, index: u16) -> &mut Self {
self.0.set_bits(0..3, index);
self
}
}
```
Note that the ranges are _exclusive_ the upper bound. The `minimal` function creates an `EntryOptions` type with only the “must-be-one” bits set. The `new` function, on the other hand, chooses reasonable defaults: It sets the present bit (why would you want to create a non-present entry?) and disables interrupts (normally we don't want that our exception handlers can be interrupted). By returning the self pointer from the `set_*` methods, we allow easy method chaining such as `options.set_present(true).disable_interrupts(true)`.
### Creating IDT Entries
Now we can add a function to create new IDT entries:
```rust
impl Entry {
fn new(gdt_selector: SegmentSelector, handler: HandlerFunc) -> Self {
let pointer = handler as u64;
Entry {
gdt_selector: gdt_selector,
pointer_low: pointer as u16,
pointer_middle: (pointer >> 16) as u16,
pointer_high: (pointer >> 32) as u32,
options: EntryOptions::new(),
reserved: 0,
}
}
}
```
We take a GDT selector and a handler function as arguments and create a new IDT entry for it. The `HandlerFunc` type is described below. It is a function pointer that can be converted to an `u64`. We choose the lower 16 bits for `pointer_low`, the next 16 bits for `pointer_middle` and the remaining 32 bits for `pointer_high`. For the options field we choose our default options, i.e. present and disabled interrupts.
### The Handler Function Type
The `HandlerFunc` type is a type alias for a function type:
``` rust
pub type HandlerFunc = extern "C" fn() -> !;
```
It needs to be a function with a defined [calling convention], as it called directly by the hardware. The C calling convention is the de facto standard in OS development, so we're using it, too. The function takes no arguments, since the hardware doesn't supply any arguments when jumping to the handler function.
[calling convention]: https://en.wikipedia.org/wiki/Calling_convention
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/rust-by-example/fn/diverging.html

If our handler function returned normally, it would try to pop the return address from the stack. But it might get some completely different value then. For example, the CPU pushes an error code for some exceptions. Bad things would happen if we interpreted this error code as return address and jumped to it. Therefore interrupt handler functions must diverge[^fn-must-diverge].
[^fn-must-diverge]: Another reason is that we overwrite the current register values by executing the handler function. Thus, the interrupted function looses its state and can't proceed anyway.
### IDT methods
Let's add a function to create new interrupt descriptor tables:
```rust
impl Idt {
pub fn new() -> Idt {
Idt([Entry::missing(); 16])
}
}
impl Entry {
fn missing() -> Self {
Entry {
gdt_selector: SegmentSelector::new(0, PrivilegeLevel::Ring0),
pointer_low: 0,
pointer_middle: 0,
pointer_high: 0,
options: EntryOptions::minimal(),
reserved: 0,
}
}
}
```
The `missing` function creates a non-present Entry. We could choose any values for the pointer and GDT selector fields as long as the present bit is not set.
However, a table with non-present entries is not very useful. So we create a `set_handler` method to add new handler functions:
```rust
impl Idt {
pub fn set_handler(&mut self, entry: u8, handler: HandlerFunc)
-> &mut EntryOptions
{
self.0[entry as usize] = Entry::new(segmentation::cs(), handler);
&mut self.0[entry as usize].options
}
}
```
The method overwrites the specified entry with the given handler function. We use the `segmentation::cs` function of the [x86_64 crate] to get the current code segment descriptor. There's no need for different kernel code segments in long mode, so the current `cs` value should be always the right choice.
[x86_64 crate]: https://docs.rs/x86_64
By returning a mutual reference to the entry's options, we allow the caller to override the default settings. For example, the caller could add a non-present entry by executing: `idt.set_handler(11, handler_fn).set_present(false)`.
### Loading the IDT
Now we're able to create new interrupt descriptor tables with registered handler functions. We just need a way to load an IDT, so that the CPU uses it. The x86 architecture uses a special register to store the active IDT and its length. In order to load a new IDT we need to update this register through the [lidt] instruction.
[lidt]: https://www.felixcloutier.com/x86/lgdt:lidt
The `lidt` instruction expects a pointer to a special data structure, which specifies the start address of the IDT and its length:
Type | Name | Description
--------|---------|-----------------------------------
u16 | Limit | The maximum addressable byte in the table. Equal to the table size in bytes minus 1.
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]: 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 {
pub fn load(&self) {
use x86_64::instructions::tables::{DescriptorTablePointer, lidt};
use core::mem::size_of;
let ptr = DescriptorTablePointer {
base: self as *const _ as u64,
limit: (size_of::<Self>() - 1) as u16,
};
unsafe { lidt(&ptr) };
}
}
```
The method does not need to modify the IDT, so it takes `self` by immutable reference. First, we create a `DescriptorTablePointer` and then we pass it to `lidt`. The `lidt` function expects that the `base` field has the type `u64`, therefore we need to cast the `self` pointer. For calculating the `limit` we use [mem::size_of]. The additional `-1` is needed because the limit field has to be the maximum addressable byte (inclusive bound). We need an unsafe block around `lidt`, because the function assumes that the specified handler addresses are valid.
[mem::size_of]: https://doc.rust-lang.org/nightly/core/mem/fn.size_of.html
#### Safety
But can we really guarantee that handler addresses are always valid? Let's see:
- The `Idt::new` function creates a new table populated with non-present entries. There's no way to set these entries to present from outside of this module, so this function is fine.
- The `set_handler` method allows us to overwrite a specified entry and point it to some handler function. Rust's type system guarantees that function pointers are always valid (as long as no `unsafe` is involved), so this function is fine, too.
There are no other public functions in the `idt` module (except `load`), so it should be safe… right?
Wrong! Imagine the following scenario:
```rust
pub fn init() {
load_idt();
cause_page_fault();
}
fn load_idt() {
let mut idt = idt::Idt::new();
idt.set_handler(14, page_fault_handler);
idt.load();
}
fn cause_page_fault() {
let x = [1,2,3,4,5,6,7,8,9];
unsafe{ *(0xdeadbeaf as *mut u64) = x[4] };
}
```
This won't work. If we're lucky, we get a triple fault and a boot loop. If we're unlucky, our kernel does strange things and fails at some completely unrelated place. So what's the problem here?
Well, we construct an IDT _on the stack_ and load it. It is perfectly valid until the end of the `load_idt` function. But as soon as the function returns, its stack frame can be reused by other functions. Thus, the IDT gets overwritten by the stack frame of the `cause_page_fault` function. So when the page fault occurs and the CPU tries to read the entry, it only sees some garbage values and issues a double fault, which escalates to a triple fault and a CPU reset.
Now imagine that the `cause_page_fault` function declared an array of pointers instead. If the present was coincidentally set, the CPU would jump to some random pointer and interpret random memory as code. This would be a clear violation of memory safety.
#### Fixing the load method
So how do we fix it? We could make the load function itself `unsafe` and push the unsafety to the caller. However, there is a much better solution in this case. In order to see it, we formulate the requirement for the `load` method:
> The referenced IDT must be valid until a new IDT is loaded.
We can't know when the next IDT will be loaded. Maybe never. So in the worst case:
> The referenced IDT must be valid as long as our kernel runs.
This is exactly the definition of a [static lifetime]. So we can easily ensure that the IDT lives long enough by adding a `'static` requirement to the signature of the `load` function:
[static lifetime]: https://doc.rust-lang.org/rust-by-example/scope/lifetime/static_lifetime.html
```rust
pub fn load(&'static self) {...}
// ^^^^^^^ ensure that the IDT reference has the 'static lifetime
```
That's it! Now the Rust compiler ensures that the above error can't happen anymore:
```
error: `idt` does not live long enough
--> src/interrupts/mod.rs:78:5
78 |> idt.load();
|> ^^^
note: reference must be valid for the static lifetime...
note: ...but borrowed value is only valid for the block suffix following
statement 0 at 75:34
--> src/interrupts/mod.rs:75:35
75 |> let mut idt = idt::Idt::new();
|> ^
```
### A static IDT
So a valid IDT needs to have the `'static` lifetime. We can either create a `static` IDT or [deliberately leak a Box][into_raw]. We will most likely only need a single IDT for the foreseeable future, so let's try the `static` approach:
[into_raw]: https://doc.rust-lang.org/nightly/alloc/boxed/struct.Box.html#method.into_raw
```rust
// in src/interrupts/mod.rs
static IDT: idt::Idt = {
let mut idt = idt::Idt::new();
idt.set_handler(0, divide_by_zero_handler);
idt
};
extern "C" fn divide_by_zero_handler() -> ! {
println!("EXCEPTION: DIVIDE BY ZERO");
loop {}
}
```
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]: https://wiki.osdev.org/Exceptions#Division_Error
However, it doesn't work this way:
```
error: calls in statics are limited to constant functions, struct and enum
constructors [E0015]
...
error: blocks in statics are limited to items and tail expressions [E0016]
...
error: references in statics may only refer to immutable values [E0017]
...
```
The reason is that the Rust compiler is not able to evaluate the value of the `static` at compile time. Maybe it will work someday when `const` functions become more powerful. But until then, we have to find another solution.
#### Lazy Statics to the Rescue
Fortunately the `lazy_static` macro exists. Instead of evaluating a `static` at compile time, the macro performs the initialization when the `static` is referenced the first time. Thus, we can do almost everything in the initialization block and are even able to read runtime values.
Let's add the `lazy_static` crate to our project:
```rust
// in src/lib.rs
#[macro_use]
extern crate lazy_static;
```
```toml
# in Cargo.toml
[dependencies.lazy_static]
version = "0.2.1"
features = ["spin_no_std"]
```
We need the `spin_no_std` feature, since we don't link the standard library.
With `lazy_static`, we can define our IDT without problems:
```rust
// in src/interrupts/mod.rs
lazy_static! {
static ref IDT: idt::Idt = {
let mut idt = idt::Idt::new();
idt.set_handler(0, divide_by_zero_handler);
idt
};
}
```
Now we're ready to load our IDT! Therefore we add a `interrupts::init` function:
```rust
// in src/interrupts/mod.rs
pub fn init() {
IDT.load();
}
```
We don't need our `assert_has_not_been_called` macro here, since nothing bad happens when `init` is called twice. It just reloads the same IDT again.
## Testing it
Now we should be able to catch page faults! Let's try it in our `rust_main`:
```rust
// in src/lib.rs
pub extern "C" fn rust_main(...) {
...
memory::init(boot_info);
// initialize our IDT
interrupts::init();
// provoke a divide-by-zero fault
42 / 0;
println!("It did not crash!");
loop {}
}
```
When we run it, we get a runtime panic:
```
PANIC in src/lib.rs at line 57:
attempted to divide by zero
```
That's a not our exception handler. The reason is that Rust itself checks for a possible division by zero and panics in that case. So in order to raise a divide-by-zero error in the CPU, we need to bypass the Rust compiler somehow.
### Inline Assembly
In order to cause a divide-by-zero exception, we need to execute a [div] or [idiv] assembly instruction with operand 0. We could write a small assembly function and call it from our Rust code. An easier way is to use Rust's [inline assembly] macro.
[div]: https://www.felixcloutier.com/x86/div
[idiv]: https://www.felixcloutier.com/x86/idiv
[inline assembly]: https://doc.rust-lang.org/1.10.0/book/inline-assembly.html
Inline assembly allows us to write raw x86 assembly within a Rust function. The feature is unstable, so we need to add `#![feature(asm)]` to our `src/lib.rs`. Then we're able to write a `divide_by_zero` function:
```rust
fn divide_by_zero() {
unsafe {
asm!("mov dx, 0; div dx" ::: "ax", "dx" : "volatile", "intel")
}
}
```
Let's try to decode it:
- The `asm!` macro emits raw assembly instructions, so it's `unsafe` to use it.
- We insert two assembly instructions here: `mov dx, 0` and `div dx`. The former loads a 0 into the `dx` register (a subset of `rdx`) and the latter divides the `ax` register by `dx`. (The `div` instruction always implicitly operates on the `ax` register).
- The colons are separators. After the first `:` we could specify output operands and after the second `:` we could specify input operands. We need neither, so we leave these areas empty.
- After the third colon, we specify the so-called _clobbers_. These tell the compiler that our assembly modifies the values of some registers. Otherwise, the compiler assumes that the registers preserve their value. In our case, we clobber `dx` (we load 0 to it) and `ax` (the `div` instruction places the result in it).
- The last block (after the 4th colon) specifies some options. The `volatile` option tells the compiler: “This code has side effects. Do not delete it and do not move it elsewhere”. In our case, the “side effect” is the divide-by-zero exception. Finally, the `intel` option allows us to use the Intel assembly syntax instead of the default AT&T syntax.
Let's use our new `divide_by_zero` function to raise a CPU exception:
```rust
// in src/lib.rs
pub extern "C" fn rust_main(...) {
...
// provoke a divide-by-zero fault
divide_by_zero();
println!("It did not crash!");
loop {}
}
```
It works! We see a `EXCEPTION: DIVIDE BY ZERO` message at the bottom of our screen:

## What's next?
We've successfully caught our first exception! However, our `EXCEPTION: DIVIDE BY ZERO` message doesn't contain much information about the cause of the exception. The next post improves the situation by printing i.a. the current stack pointer and address of the causing instruction. We will also explore other exceptions such as page faults, for which the CPU pushes an _error code_ on the stack.
================================================
FILE: blog/content/edition-1/extra/naked-exceptions/02-better-exception-messages/index.md
================================================
+++
title = "Better Exception Messages"
weight = 2
path = "better-exception-messages"
aliases = ["better-exception-messages.html"]
date = 2016-08-03
template = "edition-1/page.html"
[extra]
updated = "2016-11-01"
+++
In this post, we explore exceptions in more detail. Our goal is to print additional information when an exception occurs, for example the values of the instruction and stack pointer. In the course of this, we will explore inline assembly and naked functions. We will also add a handler function for page faults and read the associated error code.
<!-- more -->
As always, the complete source code is on [GitHub]. Please file [issues] for any problems, questions, or improvement suggestions. There is also a [gitter chat] and a comment section at the end of this page.
[GitHub]: https://github.com/phil-opp/blog_os/tree/better_exception_messages
[issues]: https://github.com/phil-opp/blog_os/issues
[gitter chat]: https://gitter.im/phil-opp/blog_os
> **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”]: @/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.
This routine involves reading the interrupt descriptor table and invoking the registered handler function. But first, the CPU pushes various information onto the stack, which describe the current state and provide information about the cause of the exception:

The pushed information contain the instruction and stack pointer, the current CPU flags, and (for some exceptions) an error code, which contains further information about the cause of the exception. Let's look at the fields in detail:
- First, the CPU aligns the stack pointer on a 16-byte boundary. This allows the handler function to use SSE instructions, which partly expect such an alignment.
- After that, the CPU pushes the stack segment descriptor (SS) and the old stack pointer (from before the alignment) onto the stack. This allows us to restore the previous stack pointer when we want to resume the interrupted program.
- Then the CPU pushes the contents of the [RFLAGS] register. This register contains various state information of the interrupted program. For example, it indicates if interrupts were enabled and whether the last executed instruction returned zero.
- Next the CPU pushes the instruction pointer and its code segment descriptor onto the stack. This tells us the address of the last executed instruction, which caused the exception.
- Finally, the CPU pushes an error code for some exceptions. This error code only exists for exceptions such as page faults or general protection faults and provides additional information. For example, it tells us whether a page fault was caused by a read or a write request.
[RFLAGS]: https://en.wikipedia.org/wiki/FLAGS_register
## Printing the Exception Stack Frame
Let's create a struct that represents the exception stack frame:
```rust
// in src/interrupts/mod.rs
#[derive(Debug)]
#[repr(C)]
struct ExceptionStackFrame {
instruction_pointer: u64,
code_segment: u64,
cpu_flags: u64,
stack_pointer: u64,
stack_segment: u64,
}
```
The divide-by-zero fault pushes no error code, so we leave it out for now. Note that the stack grows downwards in memory, so we need to declare the fields in reverse order (compared to the figure above).
Now we need a way to find the memory address of this stack frame. When we look at the above graphic again, we see that the start address of the exception stack frame is the new stack pointer. So we just need to read the value of `rsp` at the very beginning of our handler function:
```rust
// in src/interrupts/mod.rs
extern "C" fn divide_by_zero_handler() -> ! {
let stack_frame: &ExceptionStackFrame;
unsafe {
asm!("mov $0, rsp" : "=r"(stack_frame) ::: "intel");
}
println!("\nEXCEPTION: DIVIDE BY ZERO\n{:#?}", stack_frame);
loop {}
}
```
We're using [inline assembly] here to load the value from the `rsp` register into `stack_frame`. The syntax is a bit strange, so here's a quick explanation:
[inline assembly]: https://doc.rust-lang.org/1.10.0/book/inline-assembly.html
- The `asm!` macro emits raw assembly instructions. This is the only way to read raw register values in Rust.
- We insert a single assembly instruction: `mov $0, rsp`. It moves the value of `rsp` to some register (the `$0` is a placeholder for an arbitrary register, which gets filled by the compiler).
- The colons are separators. After the first colon, the `asm!` macro expects output operands. We're specifying our `stack_frame` variable as a single output operand here. The `=r` tells the compiler that it should use any register for the first placeholder `$0`.
- After the second colon, we can specify input operands. We don't need any, therefore we leave it empty.
- After the third colon, the macro expects so called [clobbers]. We don't change any register values, so we leave it empty too.
- The last block (after the 4th colon) specifies options. The `intel` option tells the compiler that our code is in Intel assembly syntax (instead of the default AT&T syntax).
[clobbers]: https://doc.rust-lang.org/1.10.0/book/inline-assembly.html#clobbers
So the inline assembly loads the stack pointer value to `stack_frame` at the very beginning of our function. Thus we have a pointer to the exception stack frame and are able to pretty-print its `Debug` formatting through the `{:#?}` argument.
### Testing it
Let's try it by executing `make run`:

Those `ExceptionStackFrame` values look very wrong. The instruction pointer definitely shouldn't be 1 and the code segment should be `0x8` instead of some big number. So what's going on here?
### Debugging
It seems like we somehow got the pointer wrong. The `ExceptionStackFrame` type and our inline assembly seem correct, so something must be modifying `rsp` before we load it into `stack_frame`.
Let's see what's happening by looking at the disassembly of our function:
```
> objdump -d build/kernel-x86_64.bin | grep -A20 "divide_by_zero_handler"
[...]
000000000010ced0 <_ZN7blog_os10interrupts22divide_by_zero_handler17h62189e8E>:
10ced0: 55 push %rbp
10ced1: 48 89 e5 mov %rsp,%rbp
10ced4: 48 81 ec b0 00 00 00 sub $0xb0,%rsp
10cedb: 48 8d 45 98 lea -0x68(%rbp),%rax
10cedf: 48 b9 1d 1d 1d 1d 1d movabs $0x1d1d1d1d1d1d1d1d,%rcx
10cee6: 1d 1d 1d
10cee9: 48 89 4d 98 mov %rcx,-0x68(%rbp)
10ceed: 48 89 4d f8 mov %rcx,-0x8(%rbp)
10cef1: 48 89 e1 mov %rsp,%rcx
10cef4: 48 89 4d f8 mov %rcx,-0x8(%rbp)
10cef8: ...
[...]
```
Our `divide_by_zero_handler` starts at address `0x10ced0`. Let's look at the instruction at address `0x10cef1`:
```
mov %rsp,%rcx
```
This is our inline assembly instruction, which loads the stack pointer into the `stack_frame` variable. It just looks a bit different, since it's in AT&T syntax and contains `rcx` instead of our `$0` placeholder. It moves `rsp` to `rcx`, and then the next instruction (`mov %rcx,-0x8(%rbp)`) moves `rcx` to the variable on the stack.
We can clearly see the problem here: The compiler inserted various other instructions before our inline assembly. These instructions modify the stack pointer so that we don't read the original `rsp` value and get a wrong pointer. But why is the compiler doing this?
The reason is that we need some place on the stack to store things like variables. Therefore the compiler inserts a so-called _[function prologue]_, which prepares the stack and reserves space for all variables. In our case, the compiler subtracts from the stack pointer to make room for i.a. our `stack_frame` variable. This prologue is the first thing in every function and comes before every other code.
So in order to correctly load the exception frame pointer, we need some way to circumvent the automatic prologue generation.
[function prologue]: https://en.wikipedia.org/wiki/Function_prologue
### Naked Functions
Fortunately there is a way to disable the prologue: [naked functions]. A naked function has no prologue and immediately starts with the first instruction of its body. However, most Rust code requires the prologue. Therefore naked functions should only contain inline assembly.
[naked functions]: https://github.com/rust-lang/rfcs/blob/master/text/1201-naked-fns.md
A naked function looks like this (note the `#[naked]` attribute):
```rust
#[naked]
extern "C" fn naked_function_example() {
unsafe {
asm!("mov rax, 0x42" ::: "rax" : "intel");
};
}
```
Naked functions are highly unstable, so we need to add `#![feature(naked_functions)]` to our `src/lib.rs`.
If you want to try it, insert it in `src/lib.rs` and call it from `rust_main`. When we inspect the disassembly, we see that the function prologue is missing:
```
> objdump -d build/kernel-x86_64.bin | grep -A5 "naked_function_example"
[...]
000000000010df90 <_ZN7blog_os22naked_function_example17ha9f733dfe42b595dE>:
10df90: 48 c7 c0 2a 00 00 00 mov $0x42,%rax
10df97: c3 retq
10df98: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
10df9f: 00
```
It contains just the specified inline assembly and a return instruction (you can ignore the junk values after the return statement). So let's try to use a naked function to retrieve the exception frame pointer.
### A Naked Exception Handler
We can't use Rust code in naked functions, but we still want to use Rust in our exception handler. Therefore we split our handler function in two parts. A main exception handler in Rust and a small naked wrapper function, which just loads the exception frame pointer and then calls the main handler.
Our new two-stage exception handler looks like this:
```rust
// in src/interrupts/mod.rs
#[naked]
extern "C" fn divide_by_zero_wrapper() -> ! {
unsafe {
asm!(/* load exception frame pointer and call main handler */);
}
}
extern "C" fn divide_by_zero_handler(stack_frame: &ExceptionStackFrame)
-> !
{
println!("\nEXCEPTION: DIVIDE BY ZERO\n{:#?}",
unsafe { &*stack_frame });
loop {}
}
```
The naked wrapper function retrieves the exception stack frame pointer and then calls the `divide_by_zero_handler` with the pointer as argument. We can't use Rust code in naked functions, so we need to do both things in inline assembly.
Retrieving the pointer to the exception stack frame is easy: We just need to load it from the `rsp` register. Our wrapper function has no prologue (it's naked), so we can be sure that nothing modifies the register before.
Calling the main handler is a bit more complicated, since we need to pass the argument correctly. Our main handler uses the C calling convention, which specifies that the the first argument is passed in the `rdi` register. So we need to load the pointer value into `rdi` and then use the `call` instruction to call `divide_by_zero_handler`.
Translated to assembly, it looks like this:
```nasm
mov rdi, rsp
call divide_by_zero_handler
```
It moves the exception stack frame pointer from `rsp` to `rdi`, where the first argument is expected, and then calls the main handler. Let's create the corresponding inline assembly to complete our wrapper function:
```rust
#[naked]
extern "C" fn divide_by_zero_wrapper() -> ! {
unsafe {
asm!("mov rdi, rsp; call $0"
:: "i"(divide_by_zero_handler as extern "C" fn(_) -> !)
: "rdi" : "intel");
}
}
```
Instead of `call divide_by_zero_handler`, we use a placeholder again. The reason is Rust's name mangling, which changes the name of the `divide_by_zero_handler` function. To circumvent this, we pass a function pointer as input parameter (after the second colon). The `"i"` tells the compiler that it is an immediate value, which can be directly inserted for the placeholder. We also specify a clobber after the third colon, which tells the compiler that we change the value of the `rdi` register.
### Intrinsics::Unreachable
When we try to compile it, we get the following error:
```
error: computation may converge in a function marked as diverging
--> src/interrupts/mod.rs:23:1
|>
23 |> extern "C" fn divide_by_zero_wrapper() -> ! {
|> ^
```
The reason is that we marked our `divide_by_zero_wrapper` function as diverging (the `!`). We call another diverging function in inline assembly, so it is clear that the function diverges. However, the Rust compiler doesn't understand inline assembly, so it doesn't know that. To fix this, we tell the compiler that all code after the `asm!` macro is unreachable:
```rust
#[naked]
extern "C" fn divide_by_zero_wrapper() -> ! {
unsafe {
asm!("mov rdi, rsp; call $0"
:: "i"(divide_by_zero_handler as extern "C" fn(_) -> !)
: "rdi" : "intel");
::core::intrinsics::unreachable();
}
}
```
The [intrinsics::unreachable] function is unstable, so we need to add `#![feature(core_intrinsics)]` to our `src/lib.rs`. It is just an annotation for the compiler and produces no real code. (Not to be confused with the [unreachable!] macro, which is completely different!)
[intrinsics::unreachable]: https://doc.rust-lang.org/nightly/core/intrinsics/fn.unreachable.html
[unreachable!]: https://doc.rust-lang.org/nightly/core/macro.unreachable!.html
### It works!
The last step is to update the interrupt descriptor table (IDT) to use our new wrapper function:
```rust
// in src/interrupts/mod.rs
lazy_static! {
static ref IDT: idt::Idt = {
let mut idt = idt::Idt::new();
idt.set_handler(0, divide_by_zero_wrapper); // changed
idt
};
}
```
Now we see a correct exception stack frame when we execute `make run`:

## Testing on real Hardware
Virtual machines such as QEMU are very convenient to quickly test our kernel. However, they might behave a bit different than real hardware in some situations. So we should test our kernel on real hardware, too.
Let's do it by burning it to an USB stick:
```
> sudo dd if=build/os-x86_64.iso of=/dev/sdX; and sync
```
Replace `sdX` by the device name of your USB stick. But **be careful**! The command will erase everything on that device.
Now we should be able to boot from this USB stick. When we do it, we see that it works fine on real hardware, too. Great!
However, this section wouldn't exist if there weren't a problem. To trigger this problem, we add some example code to the start of our `divide_by_zero_handler`:
```rust
// in src/interrupts/mod.rs
extern "C" fn divide_by_zero_handler(...) {
let x = (1u64, 2u64, 3u64);
let y = Some(x);
for i in (0..100).map(|z| (z, z - 1)) {}
println!(...);
loop {}
}
```
This is just some garbage code that doesn't do anything useful. When we try it in QEMU using `make run`, it still works fine. However, when we burn it to an USB stick again and boot from it on real hardware, we see that our computer reboots just before printing the exception message.
So our code, which worked well in QEMU, _causes a triple fault_ on real hardware. What's happening?
### Reproducing the Bug in QEMU
Debugging on a real machine is difficult. Fortunately there is a way to reproduce this bug in QEMU: We use Linux's [Kernel-based Virtual Machine] \(KVM) by passing the `‑enable-kvm` flag:
[Kernel-based Virtual Machine]: https://en.wikipedia.org/wiki/Kernel-based_Virtual_Machine
```
> qemu-system-x86_64 -cdrom build/os-x86_64.iso -enable-kvm
```
Now QEMU triple faults as well. This should make debugging much easier.
### Debugging
QEMU's `-d int`, which prints every exception, doesn't seem to work in KVM mode. However `-d cpu_reset` still works. It prints the complete CPU state whenever the CPU resets. Let's try it:
```
> qemu-system-x86_64 -cdrom build/os-x86_64.iso -enable-kvm -d cpu_reset
CPU Reset (CPU 0)
EAX=00000000 EBX=00000000 ECX=00000000 EDX=00000000
ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000000
EIP=00000000 EFL=00000000 [-------] CPL=0 II=0 A20=0 SMM=0 HLT=0
[...]
CPU Reset (CPU 0)
EAX=00000000 EBX=00000000 ECX=00000000 EDX=00000663
ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000000
EIP=0000fff0 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
[...]
CPU Reset (CPU 0)
RAX=0000000000118cb8 RBX=0000000000000800 RCX=1d1d1d1d1d1d1d1d RDX=0..0000000
RSI=0000000000112cd0 RDI=0000000000118d38 RBP=0000000000118d28 RSP=0..0118c68
R8 =0000000000000000 R9 =0000000000000100 R10=0000000000118700 R11=0..0118a00
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0..0000000
RIP=000000000010cf08 RFL=00210002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
[...]
```
The first two resets occur while the CPU is still in 32-bit mode (`EAX` instead of `RAX`), so we ignore them. The third reset is the interesting one, because it occurs in 64-bit mode. The register dump tells us that the instruction pointer (`rip`) was `0x10cf08` just before the reset. This might be the address of the instruction that caused the triple fault.
We can find the corresponding instruction by disassembling our kernel:
```
objdump -d build/kernel-x86_64.bin | grep "10cf08:"
10cf08: 0f 29 45 b0 movaps %xmm0,-0x50(%rbp)
```
The [movaps] instruction is an [SSE] instruction that moves aligned 128bit values. It can fail for a number of reasons:
[movaps]: https://www.felixcloutier.com/x86/movaps
[SSE]: https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions
1. For an illegal memory operand effective address in the CS, DS, ES, FS or GS segments.
2. For an illegal address in the SS segment.
3. If a memory operand is not aligned on a 16-byte boundary.
4. For a page fault.
5. If TS in CR0 is set.
The segment registers contain no meaningful values in long mode, so they can't contain illegal addresses. We did not change the TS bit in [CR0] and there is no reason for a page fault either. So it has to be option 3.
[CR0]: https://en.wikipedia.org/wiki/Control_register#CR0
### 16-byte Alignment
Some SSE instructions such as `movaps` require that memory operands are 16-byte aligned. In our case, the instruction is `movaps %xmm0,-0x50(%rbp)`, which writes to address `rbp - 0x50`. Therefore `rbp` needs to be 16-byte aligned.
Let's look at the above `-d cpu_reset` dump again and check the value of `rbp`:
```
CPU Reset (CPU 0)
RAX=[...] RBX=[...] RCX=[...] RDX=[...]
RSI=[...] RDI=[...] RBP=0000000000118d28 RSP=[...]
...
```
`RBP` is `0x118d28`, which is _not_ 16-byte aligned. So this is the reason for the triple fault. (It seems like QEMU doesn't check the alignment for `movaps`, but real hardware of course does.)
But how did we end up with a misaligned `rbp` register?
### The Base Pointer
In order to solve this mystery, we need to look at the disassembly of the preceding code:
```
> objdump -d build/kernel-x86_64.bin | grep -B10 "10cf08:"
000000000010cee0 <_ZN7blog_os10interrupts22divide_by_zero_handler17hE>:
10cee0: 55 push %rbp
10cee1: 48 89 e5 mov %rsp,%rbp
10cee4: 48 81 ec c0 00 00 00 sub $0xc0,%rsp
10ceeb: 48 8d 45 90 lea -0x70(%rbp),%rax
10ceef: 48 b9 1d 1d 1d 1d 1d movabs $0x1d1d1d1d1d1d1d1d,%rcx
10cef6: 1d 1d 1d
10cef9: 48 89 4d 90 mov %rcx,-0x70(%rbp)
10cefd: 48 89 7d f8 mov %rdi,-0x8(%rbp)
10cf01: 0f 10 05 a8 51 00 00 movups 0x51a8(%rip),%xmm0
10cf08: 0f 29 45 b0 movaps %xmm0,-0x50(%rbp)
```
At the last line we have the `movaps` instruction, which caused the triple fault. The exception occurs inside our `divide_by_zero_handler` function. We see that `rbp` is loaded with the value of `rsp` at the beginning (at `0x10cee1`). The `rbp` register holds the so-called _base pointer_, which points to the beginning of the stack frame. It is used in the rest of the function to address variables and other values on the stack.
The base pointer is initialized directly from the stack pointer (`rsp`) after pushing the old base pointer. There is no special alignment code, so the compiler blindly assumes that `(rsp - 8)`[^fn-rsp-8] is always 16-byte aligned. This seems to be wrong in our case. But why does the compiler assume this?
[^fn-rsp-8]: By pushing the old base pointer, `rsp` is updated to `rsp-8`.
### Calling Conventions
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]: 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.
The “end of the input argument area” refers to the last stack-passed argument (in our case there aren't any). So the stack pointer must be 16 byte aligned whenever we `call` a C-compatible function. The `call` instruction then pushes the return value on the stack so that “the value (%rsp + 8) is a multiple of 16 when control is transferred to the function entry point”.
_Summary_: The calling convention requires a 16 byte aligned stack pointer before `call` instructions. The compiler relies on this requirement, but we broke it somehow. Thus the generated code triple faults due to a misaligned memory address in the `movaps` instruction.
### Fixing the Alignment
In order to fix this bug, we need to make sure that the stack pointer is correctly aligned before calling `extern "C"` functions. Let's summarize the stack pointer modifications that occur before the exception handler is called:
1. The CPU aligns the stack pointer to a 16 byte boundary.
2. The CPU pushes `ss`, `rsp`, `rflags`, `cs`, and `rip`. So it pushes five 8 byte registers, which makes `rsp` misaligned.
3. The wrapper function calls `divide_by_zero_handler` with a misaligned stack pointer.
The problem is that we're pushing an uneven number of 8 byte registers. Thus we need to align the stack pointer again before the `call` instruction:
```rust
#[naked]
extern "C" fn divide_by_zero_wrapper() -> ! {
unsafe {
asm!("mov rdi, rsp
sub rsp, 8 // align the stack pointer
call $0"
:: "i"(divide_by_zero_handler as extern "C" fn(_) -> !)
: "rdi" : "intel");
::core::intrinsics::unreachable();
}
}
```
The additional `sub rsp, 8` instruction aligns the stack pointer to a 16 byte boundary. Now it should work on real hardware (and in QEMU KVM mode) again.
## A Handler Macro
The next step is to add handlers for other exceptions. However, we would need wrapper functions for them too. To avoid this code duplication, we create a `handler` macro that creates the wrapper functions for us:
```rust
// in src/interrupts/mod.rs
macro_rules! handler {
($name: ident) => {{
#[naked]
extern "C" fn wrapper() -> ! {
unsafe {
asm!("mov rdi, rsp
sub rsp, 8 // align the stack pointer
call $0"
:: "i"($name as extern "C" fn(
&ExceptionStackFrame) -> !)
: "rdi" : "intel");
::core::intrinsics::unreachable();
}
}
wrapper
}}
}
```
The macro takes a single Rust identifier (`ident`) as argument and expands to a `{}` block (hence the double braces). The block defines a new wrapper function that calls the function `$name` and passes a pointer to the exception stack frame. Note that we're fixing the argument type to `&ExceptionStackFrame`. If we used a `_` like before, the passed function could accept an arbitrary argument, which would lead to ugly bugs at runtime.
Now we can remove the `divide_by_zero_wrapper` and use our new `handler!` macro instead:
```rust
// in src/interrupts/mod.rs
lazy_static! {
static ref IDT: idt::Idt = {
let mut idt = idt::Idt::new();
idt.set_handler(0, handler!(divide_by_zero_handler)); // new
idt
};
}
```
Note that the `handler!` macro needs to be defined above the static `IDT`, because macros are only available after their definition.
### Invalid Opcode Exception
With the `handler!` macro we can create new handler functions easily. For example, we can add a handler for the invalid opcode exception as follows:
```rust
// in src/interrupts/mod.rs
lazy_static! {
static ref IDT: idt::Idt = {
let mut idt = idt::Idt::new();
idt.set_handler(0, handler!(divide_by_zero_handler));
idt.set_handler(6, handler!(invalid_opcode_handler)); // new
idt
};
}
extern "C" fn invalid_opcode_handler(stack_frame: &ExceptionStackFrame)
-> !
{
let stack_frame = unsafe { &*stack_frame };
println!("\nEXCEPTION: INVALID OPCODE at {:#x}\n{:#?}",
stack_frame.instruction_pointer, stack_frame);
loop {}
}
```
Invalid opcode faults have the vector number 6, so we set the 6th IDT entry. This time we additionally print the address of the invalid instruction.
We can test our new handler with the special [ud2] instruction, which generates a invalid opcode:
[ud2]: https://www.felixcloutier.com/x86/ud
```rust
// in src/lib.rs
#[no_mangle]
pub extern "C" fn rust_main(multiboot_information_address: usize) {
...
// initialize our IDT
interrupts::init();
// provoke a invalid opcode exception
unsafe { asm!("ud2") };
println!("It did not crash!");
loop {}
}
```
## Exceptions with Error Codes
When a divide-by-zero exception occurs, we immediately know the reason: Someone tried to divide by zero. In contrast, there are faults with many possible causes. For example, a page fault occurs in many occasions: When accessing a non-present page, when writing to a read-only page, when the page table is malformed, etc. In order to differentiate these causes, the CPU pushes an additional error code onto the stack for such exceptions, which gives additional information.
### A new Macro
Since the CPU pushes an additional error code, the stack frame is different and our `handler!` macro is not applicable. Therefore we create a new `handler_with_error_code!` macro for them:
```rust
// in src/interrupts/mod.rs
macro_rules! handler_with_error_code {
($name: ident) => {{
#[naked]
extern "C" fn wrapper() -> ! {
unsafe {
asm!("pop rsi // pop error code into rsi
mov rdi, rsp
sub rsp, 8 // align the stack pointer
call $0"
:: "i"($name as extern "C" fn(
&ExceptionStackFrame, u64) -> !)
: "rdi","rsi" : "intel");
::core::intrinsics::unreachable();
}
}
wrapper
}}
}
```
The difference to the `handler!` macro is the additional error code argument. The CPU pushes the error code last, so we pop it right at the beginning of the wrapper function. We pop it into `rsi` because the C calling convention expects the second argument in it.
### A Page Fault Handler
Let's write a page fault handler which analyzes and prints the error code:
```rust
// in src/interrupts/mod.rs
extern "C" fn page_fault_handler(stack_frame: &ExceptionStackFrame,
error_code: u64) -> !
{
println!(
"\nEXCEPTION: PAGE FAULT with error code {:?}\n{:#?}",
error_code, unsafe { &*stack_frame });
loop {}
}
```
We need to register our new handler function in the static interrupt descriptor table (IDT):
```rust
// in src/interrupts/mod.rs
lazy_static! {
static ref IDT: idt::Idt = {
let mut idt = idt::Idt::new();
idt.set_handler(0, handler!(divide_by_zero_handler));
idt.set_handler(6, handler!(invalid_opcode_handler));
// new
idt.set_handler(14, handler_with_error_code!(page_fault_handler));
idt
};
}
```
Page faults have the vector number 14, so we set the 14th IDT entry.
#### Testing it
Let's test our new page fault handler by provoking a page fault in our main function:
```rust
// in src/lib.rs
#[no_mangle]
pub extern "C" fn rust_main(multiboot_information_address: usize) {
...
// initialize our IDT
interrupts::init();
// provoke a page fault
unsafe { *(0xdeadbeaf as *mut u64) = 42 };
println!("It did not crash!");
loop {}
}
```
We get the following output:

### The Page Fault Error Code
“Error code 2” is not really an useful error message. Let's improve this by creating a `PageFaultErrorCode` type:
```rust
// in src/interrupts/mod.rs
bitflags! {
struct PageFaultErrorCode: u64 {
const PROTECTION_VIOLATION = 1 << 0;
const CAUSED_BY_WRITE = 1 << 1;
const USER_MODE = 1 << 2;
const MALFORMED_TABLE = 1 << 3;
const INSTRUCTION_FETCH = 1 << 4;
}
}
```
- 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-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.
Now we can improve our page fault error message by using the new `PageFaultErrorCode`. We also print the accessed memory address:
```rust
extern "C" fn page_fault_handler(stack_frame: &ExceptionStackFrame,
error_code: u64) -> !
{
use x86_64::registers::control_regs;
println!(
"\nEXCEPTION: PAGE FAULT while accessing {:#x}\
\nerror code: {:?}\n{:#?}",
unsafe { control_regs::cr2() },
PageFaultErrorCode::from_bits(error_code).unwrap(),
unsafe { &*stack_frame });
loop {}
}
```
The `from_bits` function tries to convert the `u64` into a `PageFaultErrorCode`. We use `unwrap` to panic if the error code has invalid bits set, since this indicates an error in our `PageFaultErrorCode` definition or a stack corruption. We also print the contents of the `cr2` register. It contains the accessed memory address, which was the cause of the page fault.
Now we get a useful error message when a page fault occurs, which allows us to debug it more easily:

As expected, the page fault was caused by write to `0xdeadbeaf`. The `PROTECTION_VIOLATION` flag is not set, so the accessed page was not present.
## What's next?
Now we're able to catch and analyze various exceptions. The next step is to _resolve_ exceptions, if possible. An example is [demand paging]: The OS swaps out memory pages to disk so that a page fault occurs when the page is accessed the next time. In that case, the OS can resolve the exception by bringing the page back into memory. Afterwards, the OS resumes the interrupted program as if nothing had happened.
[demand paging]: https://en.wikipedia.org/wiki/Demand_paging
The next post will implement the first portion of demand paging: saving and restoring the complete state of an program. This will allow us to transparently interrupt and resume programs in the future.
================================================
FILE: blog/content/edition-1/extra/naked-exceptions/03-returning-from-exceptions/index.md
================================================
+++
title = "Returning from Exceptions"
weight = 3
path = "returning-from-exceptions"
aliases = ["returning-from-exceptions.html"]
date = 2016-09-21
template = "edition-1/page.html"
[extra]
updated = "2016-11-01"
+++
In this post, we learn how to return from exceptions correctly. In the course of this, we will explore the `iretq` instruction, the C calling convention, multimedia registers, and the red zone.
<!-- more -->
As always, the complete source code is on [GitHub]. Please file [issues] for any problems, questions, or improvement suggestions. There is also a [gitter chat] and a comment section at the end of this page.
[GitHub]: https://github.com/phil-opp/blog_os/tree/returning_from_exceptions
[issues]: https://github.com/phil-opp/blog_os/issues
[gitter chat]: https://gitter.im/phil-opp/blog_os
> **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”]: @/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:
Imagine a system that uses [memory mapped files]: We map a file into the virtual address space without loading it into memory. Whenever we access a part of the file for the first time, a page fault occurs. However, this page fault is not fatal. We can resolve it by loading the corresponding page from disk into memory and setting the `present` flag in the page table. Then we can return from the page fault handler and restart the failed instruction, which now successfully accesses the file data.
[memory mapped files]: https://en.wikipedia.org/wiki/Memory-mapped_file
Memory mapped files are completely out of scope for us right now (we have neither a file concept nor a hard disk driver). So we need an exception that we can resolve easily so that we can return from it in a reasonable way. Fortunately, there is an exception that needs no resolution at all: the breakpoint exception.
## The Breakpoint Exception
The breakpoint exception is the perfect exception to test our upcoming return-from-exception logic. Its only purpose is to temporary pause a program when the breakpoint instruction `int3` is executed.
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]: 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]: @/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:
```rust
// in src/interrupts/mod.rs
extern "C" fn breakpoint_handler(stack_frame: &ExceptionStackFrame) -> !
{
let stack_frame = unsafe { &*stack_frame };
println!("\nEXCEPTION: BREAKPOINT at {:#x}\n{:#?}",
stack_frame.instruction_pointer, stack_frame);
loop {}
}
```
We print an error message and also output the instruction pointer and the rest of the stack frame. Note that this function does _not_ return yet, since our `handler!` macro still requires a diverging function.
We need to register our new handler function in the interrupt descriptor table (IDT):
```rust
// in src/interrupts/mod.rs
lazy_static! {
static ref IDT: idt::Idt = {
let mut idt = idt::Idt::new();
idt.set_handler(0, handler!(divide_by_zero_handler));
idt.set_handler(3, handler!(breakpoint_handler)); // new
idt.set_handler(6, handler!(invalid_opcode_handler));
idt.set_handler(14, handler_with_error_code!(page_fault_handler));
idt
};
}
```
We set the IDT entry with number 3 since it's the vector number of the breakpoint exception.
#### Testing it
In order to test it, we insert an `int3` instruction in our `rust_main`:
```rust
// in src/lib.rs
...
#[macro_use] // needed for the `int!` macro
extern crate x86_64;
...
#[no_mangle]
pub extern "C" fn rust_main(...) {
...
interrupts::init();
// trigger a breakpoint exception
unsafe { int!(3) };
println!("It did not crash!");
loop {}
}
```
When we execute `make run`, we see the following:

It works! Now we “just” need to return from the breakpoint handler somehow so that we see the `It did not crash` message again.
## Returning from Exceptions
So how do we return from exceptions? To make it easier, we look at a normal function return first:

When calling a function, the `call` instruction pushes the return address on the stack. When the called function is finished, it can return to the parent function through the `ret` instruction, which pops the return address from the stack and then jumps to it.
The exception stack frame, in contrast, looks a bit different:

Instead of pushing a return address, the CPU pushes the stack and instruction pointers (with their segment descriptors), the RFLAGS register, and an optional error code. It also aligns the stack pointer to a 16 byte boundary before pushing values.
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 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://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.
We see that `iretq` treats the stored instruction pointer as return address. For most exceptions, the stored `rip` points to the instruction that caused the fault. So by executing `iretq`, we restart the failing instruction. This makes sense because we should have resolved the exception when returning from it, so the instruction should no longer fail (e.g. the accessed part of the memory mapped file is now present in memory).
The situation is a bit different for the breakpoint exception, since it needs no resolution. Restarting the `int3` instruction wouldn't make sense, since it would cause a new breakpoint exception and we would enter an endless loop. For this reason the hardware designers decided that the stored `rip` should point to the next instruction after the `int3` instruction.
Let's check this for our breakpoint handler. Remember, the handler printed the following message (see the image above):
```
EXCEPTION: BREAKPOINT at 0x110970
```
So let's disassemble the instruction at `0x110970` and its predecessor:
```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)
```
We see that `0x110970` indeed points to the next instruction after `int3`. So we can simply jump to the stored instruction pointer when we want to return from the breakpoint exception.
### Implementation
Let's update our `handler!` macro to support non-diverging exception handlers:
```rust
// in src/interrupts/mod.rs
macro_rules! handler {
($name: ident) => {{
#[naked]
extern "C" fn wrapper() -> ! {
unsafe {
asm!("mov rdi, rsp
sub rsp, 8 // align the stack pointer
call $0"
:: "i"($name as extern "C" fn(
&ExceptionStackFrame)) // no longer diverging
: "rdi" : "intel", "volatile");
// new
asm!("add rsp, 8 // undo stack pointer alignment
iretq"
:::: "intel", "volatile");
::core::intrinsics::unreachable();
}
}
wrapper
}}
}
```
When an exception handler returns from the `call` instruction, we use the `iretq` instruction to continue the interrupted program. Note that we need to undo the stack pointer alignment before, so that `rsp` points to the end of the exception stack frame again.
We've changed the handler function type, so we need to adjust our existing exception handlers:
```diff
// in src/interrupts/mod.rs
extern "C" fn divide_by_zero_handler(
- stack_frame: &ExceptionStackFrame) -> ! {...}
+ stack_frame: &ExceptionStackFrame) {...}
extern "C" fn invalid_opcode_handler(
- stack_frame: &ExceptionStackFrame) -> ! {...}
+ stack_frame: &ExceptionStackFrame) {...}
extern "C" fn breakpoint_handler(
- stack_frame: &ExceptionStackFrame) -> ! {
+ stack_frame: &ExceptionStackFrame) {
println!(...);
- loop {}
}
```
Note that we also removed the `loop {}` at the end of our `breakpoint_handler` so that it no longer diverges. The `divide_by_zero_handler` and the `invalid_opcode_handler` still diverge (albeit the new function type would allow a return).
### Testing
Let's try our new `iretq` logic:

Instead of the expected _“It did not crash”_ message after the breakpoint exception, we get a page fault. The strange thing is that our kernel tried to access address `0x1`, which should never happen. So it seems like we messed up something important.
### 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]: @/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:
```
(gdb) break blog_os/src/lib.rs:61
Breakpoint 1 at 0x110a95: file /home/.../blog_os/src/lib.rs, line 61.
```
This line is after the `int3` instruction, so we know that the `iretq` succeeded when the breakpoint is hit. To test this, we continue the execution:
```
(gdb) continue
Continuing.
Breakpoint 1, blog_os::rust_main (multiboot_information_address=1539136)
at /home/.../blog_os/src/lib.rs:61
61 println!("It did not crash!");
```
It worked! So our kernel successfully returned from the `int3` instruction, which means that the `iretq` itself works.
However, when we `continue` the execution again, we get the page fault. So the exception occurs somewhere in the `println` logic. This means that it occurs in code generated by the compiler (and not e.g. in inline assembly). But the compiler should never access `0x1`, so how is this happening?
The answer is that we've used the wrong _calling convention_ for our exception handlers. Thus, we violate some compiler invariants so that the code that works fine without intermediate exceptions starts to violate memory safety when it's executed after a breakpoint exception.
## Calling Conventions
Exceptions are quite similar to function calls: The CPU jumps to the first instruction of the (handler) function and executes the function. Afterwards, if the function is not diverging, the CPU jumps to the return address and continues the execution of the parent function.
However, there is a major difference between exceptions and function calls: A function call is invoked voluntary by a compiler inserted `call` instruction, while an exception might occur at _any_ instruction. In order to understand the consequences of this difference, we need to examine function calls in more detail.
[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]: 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
- results are returned in `rax` and `rdx`
Note that Rust does not follow the C ABI (in fact, [there isn't even a Rust ABI yet][rust abi]). So these rules apply only to functions declared as `extern "C" fn`.
[rust abi]: https://github.com/rust-lang/rfcs/issues/600
### Preserved and Scratch Registers
The calling convention divides the registers in two parts: _preserved_ and _scratch_ registers.
The values of the preserved register must remain unchanged across function calls. So a called function (the _“callee”_) is only allowed to overwrite these registers if it restores their original values before returning. Therefore these registers are called _“callee-saved”_. A common pattern is to save these registers to the stack at the function's beginning and restore them just before returning.
In contrast, a called function is allowed to overwrite scratch registers without restrictions. If the caller wants to preserve the value of a scratch register across a function call, it needs to backup and restore it (e.g. by pushing it to the stack before the function call). So the scratch registers are _caller-saved_.
On x86_64, the C calling convention specifies the following preserved and scratch registers:
preserved registers | scratch registers
---|---
`rbp`, `rbx`, `rsp`, `r12`, `r13`, `r14`, `r15` | `rax`, `rcx`, `rdx`, `rsi`, `rdi`, `r8`, `r9`, `r10`, `r11`
_callee-saved_ | _caller-saved_
The compiler knows these rules, so it generates the code accordingly. For example, most functions begin with a `push rbp`, which backups `rbp` on the stack (because it's a callee-saved register).
### The Exception Calling Convention
In contrast to function calls, exceptions can occur on _any_ instruction. In most cases we don't even know at compile time if the generated code will cause an exception. For example, the compiler can't know if an instruction causes a stack overflow or an other page fault.
Since we don't know when an exception occurs, we can't backup any registers before. This means that we can't use a calling convention that relies on caller-saved registers for our exception handlers. But we do so at the moment: Our exception handlers are declared as `extern "C" fn` and thus use the C calling convention.
So here is what happens:
- `rust_main` is executing; it writes some memory address into `rax`.
- The `int3` instruction causes a breakpoint exception.
- Our `breakpoint_handler` prints to the screen and assumes that it can overwrite `rax` freely (since it's a scratch register). Somehow the value `0` ends up in `rax`.
- We return from the breakpoint exception using `iretq`.
- `rust_main` continues and accesses the memory address in `rax`.
- The CPU tries to access address `0x1`, which causes a page fault.
So our exception handler erroneously assumes that the scratch registers were saved by the caller. But the caller (`rust_main`) couldn't save any registers since it didn't know that an exception occurs. So nobody saves `rax` and the other scratch registers, which leads to the page fault.
The problem is that we use a calling convention with caller-saved registers for our exception handlers. Instead, we need a calling convention means that preserves _all registers_. In other words, all registers must be callee-saved:
```rust
extern "all-registers-callee-saved" fn exception_handler() {...}
```
Unfortunately, Rust does not support such a calling convention. It was [proposed once][interrupt calling conventions], but did not get accepted for various reasons. The primary reason was that such calling conventions can be simulated by writing a naked wrapper function.
(Remember: [Naked functions] are functions without prologue and can contain only inline assembly. They were discussed in the [previous post][naked fn post].)
[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]: @/edition-1/extra/naked-exceptions/02-better-exception-messages/index.md#naked-functions
### A naked wrapper function
Such a naked wrapper function might look like this:
```rust
#[naked]
extern "C" fn calling_convention_wrapper() {
unsafe {
asm!("
push rax
push rcx
push rdx
push rsi
push rdi
push r8
push r9
push r10
push r11
// TODO: call exception handler with C calling convention
pop r11
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rdx
pop rcx
pop rax
" :::: "intel", "volatile");
}
}
```
This wrapper function saves all _scratch_ registers to the stack before calling the exception handler and restores them afterwards. Note that we `pop` the registers in reverse order.
We don't need to backup _preserved_ registers since they are callee-saved in the C calling convention. Thus, the compiler already takes care of preserving their values.
### Fixing our Handler Macro
Let's update our handler macro to fix the calling convention problem. Therefore we need to backup and restore all scratch registers. For that we create two new macros:
```rust
// in src/interrupts/mod.rs
macro_rules! save_scratch_registers {
() => {
asm!("push rax
push rcx
push rdx
push rsi
push rdi
push r8
push r9
push r10
push r11
" :::: "intel", "volatile");
}
}
macro_rules! restore_scratch_registers {
() => {
asm!("pop r11
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rdx
pop rcx
pop rax
" :::: "intel", "volatile");
}
}
```
We need to declare these macros _above_ our `handler` macro, since macros are only available after their declaration.
Now we can use these macros to fix our `handler!` macro:
```rust
// in src/interrupts/mod.rs
macro_rules! handler {
($name: ident) => {{
#[naked]
extern "C" fn wrapper() -> ! {
unsafe {
save_scratch_registers!();
asm!("mov rdi, rsp
add rdi, 9*8 // calculate exception stack frame pointer
// sub rsp, 8 (stack is aligned already)
call $0"
:: "i"($name as
extern "C" fn(&ExceptionStackFrame))
: "rdi" : "intel", "volatile");
restore_scratch_registers!();
asm!("
// add rsp, 8 (undo stack alignment; not needed anymore)
iretq"
:::: "intel", "volatile");
::core::intrinsics::unreachable();
}
}
wrapper
}}
}
```
It's important that we save the registers first, before we modify any of them. After the `call` instruction (but before `iretq`) we restore the registers again. Because we're now changing `rsp` (by pushing the register values) before we load it into `rdi`, we would get a wrong exception stack frame pointer. Therefore we need to adjust it by adding the number of bytes we push. We push 9 registers that are 8 bytes each, so `9 * 8` bytes in total.
Note that we no longer need to manually align the stack pointer, because we're pushing an uneven number of registers in `save_scratch_registers`. Thus the stack pointer already has the required 16-byte alignment.
### Testing it again
Let's test it again with our corrected `handler!` macro:

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 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_:

Such multimedia instructions are called [Single Instruction Multiple Data (SIMD)] instructions, because they simultaneously perform an operation (e.g. addition) on multiple data words. Good compilers are able to transform normal loops into such SIMD code automatically. This process is called [auto-vectorization] and can lead to huge performance improvements.
[Single Instruction Multiple Data (SIMD)]: https://en.wikipedia.org/wiki/SIMD
[auto-vectorization]: https://en.wikipedia.org/wiki/Automatic_vectorization
However, auto-vectorization causes a problem for us: Most of the multimedia registers are caller-saved. According to our discussion of calling conventions above, this means that our exception handlers erroneously assume that they are allowed to overwrite them without preserving their values.
We don't use any multimedia registers explicitly, but the Rust compiler might auto-vectorize our code (including the exception handlers). Thus we could silently clobber the multimedia registers, which leads to the same problems as above:

This example shows a program that is using the first three multimedia registers (`mm0` to `mm2`). At some point, an exception occurs and control is transferred to the exception handler. The exception handler uses `mm1` for its own data and thus overwrites the previous value. When the exception is resolved, the CPU continues the interrupted program again. However, the program is now corrupt since it relies on the original `mm1` value.
### Saving and Restoring Multimedia Registers
In order to fix this problem, we need to backup all caller-saved multimedia registers before we call the exception handler. The problem is that the set of multimedia registers varies between CPUs. There are different standards:
- [MMX]: The MMX instruction set was introduced in 1997 and defines eight 64 bit registers called `mm0` through `mm7`. These registers are just aliases for the registers of the [x87 floating point unit].
- [SSE]: The _Streaming SIMD Extensions_ instruction set was introduced in 1999. Instead of re-using the floating point registers, it adds a completely new register set. The sixteen new registers are called `xmm0` through `xmm15` and are 128 bits each.
- [AVX]: The _Advanced Vector Extensions_ are extensions that further increase the size of the multimedia registers. The new registers are called `ymm0` through `ymm15` and are 256 bits each. They extend the `xmm` registers, so e.g. `xmm0` is the lower (or upper?) half of `ymm0`.
[MMX]: https://en.wikipedia.org/wiki/MMX_(instruction_set)
[x87 floating point unit]: https://en.wikipedia.org/wiki/X87
[SSE]: https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions
[AVX]: https://en.wikipedia.org/wiki/Advanced_Vector_Extensions
The Rust compiler (and LLVM) assume that the `x86_64-unknown-linux-gnu` target supports only MMX and SSE, so we don't need to save the `ymm0` through `ymm15`. But we need to save `xmm0` through `xmm15` and also `mm0` through `mm7`. There is a special instruction to do this: [fxsave]. This instruction saves the floating point and multimedia state to a given address. It needs _512 bytes_ to store that state.
[fxsave]: https://www.felixcloutier.com/x86/fxsave
In order to save/restore the multimedia registers, we _could_ add new macros:
```rust
macro_rules! save_multimedia_registers {
() => {
asm!("sub rsp, 512
fxsave [rsp]
" :::: "intel", "volatile");
}
}
macro_rules! restore_multimedia_registers {
() => {
asm!("fxrstor [rsp]
add rsp, 512
" :::: "intel", "volatile");
}
}
```
First, we reserve the 512 bytes on the stack and then we use `fxsave` to backup the multimedia registers. In order to restore them later, we use the [fxrstor] instruction. Note that `fxsave` and `fxrstor` require a 16 byte aligned memory address.
[fxrstor]: https://www.felixcloutier.com/x86/fxrstor
However, _we won't do it that way_. The problem is the large amount of memory required. We will reuse the same code when we handle hardware interrupts in a future post. So for each mouse click, pressed key, or arrived network package we need to write 512 bytes to memory. This would be a huge performance problem.
Fortunately, there exists an alternative solution.
### Disabling Multimedia Extensions
We just disable MMX, SSE, and all the other fancy multimedia extensions in our kernel[^fn-userspace-sse]. This way, our exception handlers won't clobber the multimedia registers because they won't use them at all.
[^fn-userspace-sse]: Userspace programs will still be able to use the multimedia registers.
This solution has its own disadvantages, of course. For example, it leads to slower kernel code because the compiler can't perform any auto-vectorization optimizations. But it's still the faster solution (since we save many memory accesses) and most kernels do it this way (including Linux).
So how do we disable MMX and SSE? Well, we just tell the compiler that our target system doesn't support it. Since the very beginning, we're compiling our kernel for the `x86_64-unknown-linux-gnu` target. This worked fine so far, but now we want a different target without support for multimedia extensions. We can do so by creating a _target configuration file_.
### Target Specifications
In order to disable the multimedia extensions for our kernel, we need to compile for a custom target. We want a target that is equal to `x86_64-unknown-linux-gnu`, but without MMX and SSE support. Rust allows us to specify such a target using a JSON configuration file.
A minimal target specification that describes the `x86_64-unknown-linux-gnu` target looks like this:
```json
{
"llvm-target": "x86_64-unknown-linux-gnu",
"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",
"arch": "x86_64",
"os": "none"
}
```
The `llvm-target` field specifies the target triple that is passed to LLVM. We want to derive a 64-bit Linux target, so we choose `x86_64-unknown-linux-gnu`. 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.
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]: 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
In order to disable the multimedia extensions, we create a new target named `x86_64-blog_os`. To describe this target, we create a file named `x86_64-blog_os.json` in the project root with the following content:
```json
{
"llvm-target": "x86_64-unknown-linux-gnu",
"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",
"arch": "x86_64",
"os": "none",
"features": "-mmx,-sse"
}
```
It's equal to `x86_64-unknown-linux-gnu` target but has one additional option: `"features": "-mmx,-sse"`. So we added two target _features_: `-mmx` and `-sse`. The minus prefix defines that our target does _not_ support this feature. So by specifying `-mmx` and `-sse`, we disable the default `mmx` and `sse` features.
In order to compile for the new target, we need to adjust our Makefile:
```diff
# in `Makefile`
arch ?= x86_64
-target ?= $(arch)-unknown-linux-gnu
+target ?= $(arch)-blog_os
...
```
The new target name (`x86_64-blog_os`) is the file name of the JSON configuration file without the `.json` extension.
### Cross compilation
Let's try if our kernel still works with the new target:
```
> make run
Compiling raw-cpuid v2.0.1
Compiling rlibc v0.1.5
Compiling x86 v0.7.1
Compiling spin v0.3.5
error[E0463]: can't find crate for `core`
error: aborting due to previous error
Build failed, waiting for other jobs to finish...
...
Makefile:52: recipe for target 'cargo' failed
make: *** [cargo] Error 101
```
It doesn't compile anymore. The error tells us that the Rust compiler no longer finds the core library.
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]: @/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.
#### Xargo
That's where [xargo] comes in. It is a wrapper for cargo that eases cross compilation. We can install it by executing:
[xargo]: https://github.com/japaric/xargo
```
cargo install xargo
```
Xargo depends on the rust source code, which we can install with `rustup component add rust-src`.
Xargo is “a drop-in replacement for cargo”, so every cargo command also works with `xargo`. You can do e.g. `xargo --help`, `xargo clean`, or `xargo doc`. However, the `build` command gains additional functionality: `xargo build` will automatically cross compile the `core` library when compiling for custom targets.
That's exactly what we want, so we change one letter in our Makefile:
```diff
# in `Makefile`
...
cargo:
- @cargo build --target $(target)
+ @xargo build --target $(target)
...
```
Now the build goes through `xargo`, which should fix the compilation error. Let's try it out:
```
> make run
Compiling core v0.0.0 (file:///home/…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore)
LLVM ERROR: SSE register return with SSE disabled
error: Could not compile `core`.
```
Well, we get a different error now, so it seems like we're making progress :). It seems like there is a “SSE register return” although SSE is disabled. But what's an “SSE register return”?
### SSE Register Return
Remember when we discussed calling conventions above? The calling convention defines which registers are used for return values. Well, the [System V ABI] defines that `xmm0` should be used for returning floating point values. So somewhere in the `core` library a function returns a float and LLVM doesn't know what to do. The ABI says “use `xmm0`” but the target specification says “don't use `xmm` registers”.
In order to fix this problem, we need to change our float ABI. The idea is to avoid normal hardware-supported floats and use a pure software implementation instead. We can do so by enabling the `soft-float` feature for our target. For that, we edit `x86_64-blog_os.json`:
```json
{
"llvm-target": "x86_64-unknown-linux-gnu",
...
"features": "-mmx,-sse,+soft-float"
}
```
The plus prefix tells LLVM to enable the `soft-float` feature.
Let's try `make run` again:
```
> make run
Compiling core v0.0.0 (file:///…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore)
Finished release [optimized] target(s) in 21.95 secs
Compiling spin v0.4.5
Compiling once v0.3.2
Compiling x86 v0.8.0
Compiling bitflags v0.9.1
Compiling raw-cpuid v2.0.1
Compiling rlibc v0.1.5
Compiling linked_list_allocator v0.2.3
Compiling volatile v0.1.0
Compiling bitflags v0.4.0
Compiling bit_field v0.5.0
Compiling spin v0.3.5
Compiling multiboot2 v0.1.0
Compiling lazy_static v0.2.2
Compiling hole_list_allocator v0.1.0 (file:///…/libs/hole_list_allocator)
Compiling blog_os v0.1.0 (file:///…)
error[E0463]: can't find crate for `alloc`
--> src/lib.rs:33:1
|
33 | extern crate alloc;
| ^^^^^^^^^^^^^^^^^^^ can't find crate
error: aborting due to previous error
```
We see that `xargo` now compiles the `core` crate in release mode. Then it starts the normal cargo build. Cargo then recompiles all dependencies, since it needs to generate different code for the new target.
However, the build still fails. The reason is that xargo only installs `core` by default, but we also need the `alloc` crate. We can enable it by creating a file named `Xargo.toml` with the following contents:
```toml
# Xargo.toml
[target.x86_64-blog_os.dependencies]
alloc = {}
```
Now xargo compiles `alloc`, too:
```
> make run
Compiling core v0.0.0 (file:///…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore)
Compiling std_unicode v0.0.0 (file:///…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd_unicode)
Compiling alloc v0.0.0 (file:///…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc)
Finished release [optimized] target(s) in 28.84 secs
Compiling blog_os v0.1.0 (file:///…/Documents/blog_os/master)
warning: unused variable: `allocator` […]
warning: unused variable: `frame` […]
Finished debug [unoptimized + debuginfo] target(s) in 1.75 secs
```
It worked! Now we have a kernel that never touches the multimedia registers! We can verify this by executing:
```
> objdump -d build/kernel-x86_64.bin | grep "mm[0-9]"
```
If the command produces no output, our kernel uses neither MMX (`mm0` – `mm7`) nor SSE (`xmm0` – `xmm15`) registers.
So now our return-from-exception logic works without problems in _most_ cases. However, there is still a pitfall hidden in the C calling convention, which might cause hideous bugs in some rare cases.
## 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]: https://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64#the-red-zone

The image shows the stack frame of a function with `n` local variables. On function entry, the stack pointer is adjusted to make room on the stack for the local variables.
The red zone is defined as the 128 bytes below the adjusted stack pointer. The function can use this area for temporary data that's not needed across function calls. Thus, the two instructions for adjusting the stack pointer can be avoided in some cases (e.g. in small leaf functions).
However, this optimization leads to huge problems with exceptions. Let's assume that an exception occurs while a function uses the red zone:

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]: 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:
```nasm
sub rsp, 128
save_scratch_registers()
...
call ...
...
restore_scratch_registers()
add rsp, 128
iretq
```
_This will not work._ The problem is that the CPU pushes the exception stack frame before even calling our handler function. So the CPU itself will clobber the red zone and there is nothing we can do about that. So our only chance is to disable the red zone.
### Disabling the Red Zone
The red zone is a property of our target, so in order to disable it we edit our `x86_64-blog_os.json` a last time:
```json
{
"llvm-target": "x86_64-unknown-linux-gnu",
...
"features": "-mmx,-sse,+soft-float",
"disable-redzone": true
}
```
We add one additional option at the end: `"disable-redzone": true`. As you might guess, this option disables the red zone optimization.
Now we have a red zone free kernel!
## Exceptions with Error Codes
We're now able to correctly return from exceptions without error codes. However, we still can't return from exceptions that push an error code (e.g. page faults). Let's fix that by updating our `handler_with_error_code` macro:
```rust
// in src/interrupts/mod.rs
macro_rules! handler_with_error_code {
($name: ident) => {{
#[naked]
extern "C" fn wrapper() -> ! {
unsafe {
asm!("pop rsi // pop error code into rsi
mov rdi, rsp
sub rsp, 8 // align the stack pointer
call $0"
:: "i"($name as extern "C" fn(
&ExceptionStackFrame, u64))
: "rdi","rsi" : "intel");
asm!("iretq" :::: "intel", "volatile");
::core::intrinsics::unreachable();
}
}
wrapper
}}
}
```
First, we change the type of the handler function: no more `-> !`, so it no longer needs to diverge. We also add an `iretq` instruction at the end.
Now we can make our `page_fault_handler` non-diverging:
```diff
// in src/interrupts/mod.rs
extern "C" fn page_fault_handler(stack_frame: &ExceptionStackFrame,
- error_code: u64) -> ! { ... }
+ error_code: u64) { ... }
```
However, now we have the same problem as above: The handler function will overwrite the scratch registers and cause bugs when returning. Let's fix this by invoking `save_scratch_registers` at the beginning:
```rust
// in src/interrupts/mod.rs
macro_rules! handler_with_error_code {
($name: ident) => {{
#[naked]
extern "C" fn wrapper() -> ! {
unsafe {
save_scratch_registers!();
asm!("pop rsi // pop error code into rsi
mov rdi, rsp
add rdi, 10*8 // calculate exception stack frame pointer
sub rsp, 8 // align the stack pointer
call $0
add rsp, 8 // undo stack pointer alignment
" :: "i"($name as extern "C" fn(
&ExceptionStackFrame, u64))
: "rdi","rsi" : "intel");
restore_scratch_registers!();
asm!("iretq" :::: "intel", "volatile");
::core::intrinsics::unreachable();
}
}
wrapper
}}
}
```
Now we backup the scratch registers to the stack right at the beginning and restore them just before the `iretq`. Like in the `handler` macro, we now need to add `10*8` to `rdi` in order to get the correct exception stack frame pointer (`save_scratch_registers` pushes nine 8 byte registers, plus the error code). We also need to undo the stack pointer alignment after the `call` [^fn-stack-alignment].
[^fn-stack-alignment]: The stack alignment is actually wrong here, since we additionally pushed an uneven number of registers. However, the `pop rsi` is wrong too, since the error code is no longer at the top of the stack. When we fix that problem, the stack alignment becomes correct again. So I left it in to keep things simple.
Now we have one last bug: We `pop` the error code into `rsi`, but the error code is no longer at the top of the stack (since `save_scratch_registers` pushed 9 registers on top of it). So we need to do it differently:
```rust
// in src/interrupts/mod.rs
macro_rules! handler_with_error_code {
($name: ident) => {{
#[naked]
extern "C" fn wrapper() -> ! {
unsafe {
save_scratch_registers!();
asm!("mov rsi, [rsp + 9*8] // load error code into rsi
mov rdi, rsp
add rdi, 10*8 // calculate exception stack frame pointer
sub rsp, 8 // align the stack pointer
call $0
add rsp, 8 // undo stack pointer alignment
" :: "i"($name as extern "C" fn(
&ExceptionStackFrame, u64))
: "rdi","rsi" : "intel");
restore_scratch_registers!();
asm!("add rsp, 8 // pop error code
iretq" :::: "intel", "volatile");
::core::intrinsics::unreachable();
}
}
wrapper
}}
}
```
Instead of using `pop`, we're calculating the error code address manually (`save_scratch_registers` pushes nine 8 byte registers) and load it into `rsi` using a `mov`. So now the error code stays on the stack. But `iretq` doesn't handle the error code, so we need to pop it before invoking `iretq`.
Phew! That was a lot of fiddling with assembly. Let's test if it still works.
### Testing
First, we test if the exception stack frame pointer and the error code are still correct:
```rust
// in rust_main in src/lib.rs
...
unsafe { int!(3) };
// provoke a page fault
unsafe { *(0xdeadbeaf as *mut u64) = 42; }
println!("It did not crash!");
loop {}
```
This should cause the following error message:
```
EXCEPTION: PAGE FAULT while accessing 0xdeadbeaf
error code: CAUSED_BY_WRITE
ExceptionStackFrame {
instruction_pointer: 1114753,
code_segment: 8,
cpu_flags: 2097158,
stack_pointer: 1171104,
stack_segment: 16
}
```
The error code should still be `CAUSED_BY_WRITE` and the exception stack frame values should also be correct (e.g. `code_segment` should be 8 and `stack_segment` should be 16).
#### Returning from Page Faults
Let's see what happens if we comment out the trailing `loop` in our page fault handler:

We see that the same error message is printed over and over again. Here is what happens:
- The CPU executes `rust_main` and tries to access `0xdeadbeaf`. This causes a page fault.
- The page fault handler prints an error message and returns without fixing the cause of the exception (`0xdeadbeaf` is still unaccessible).
- The CPU restarts the instruction that caused the page fault and thus tries to access `0xdeadbeaf` again. Of course, this causes a page fault again.
- The page fault handler prints the error message and returns.
… and so on. Thus, our code indefinitely jumps between the page fault handler and the instruction that accesses `0xdeadbeaf`.
This is a good thing! It means that our `iretq` logic is working correctly, since it returns to the correct instruction every time. So our `handler_with_error_code` macro seems to be correct.
## What's next?
We are now able to catch exceptions and to return from them. However, there are still exceptions that completely crash our kernel by causing a [triple fault]. In the next post, we will fix this issue by handling a special type of exception: the [double fault]. Thus, we will be able to avoid random reboots in our kernel.
[triple fault]: https://en.wikipedia.org/wiki/Triple_fault
[double fault]: https://en.wikipedia.org/wiki/Double_fault
================================================
FILE: blog/content/edition-1/extra/naked-exceptions/_index.md
================================================
+++
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"]
+++
================================================
FILE: blog/content/edition-1/extra/set-up-gdb/index.md
================================================
+++
title = "Set Up GDB"
template = "plain.html"
path = "set-up-gdb"
aliases = ["set-up-gdb.html"]
weight = 4
+++
There are a lot of things that can go wrong when developing an OS. So it's a good idea to add a debugger to our toolset, which allows us to set breakpoints and examine variables. We will use [GDB](https://www.gnu.org/software/gdb/) as QEMU supports it out of the box.
### QEMU parameters
To make QEMU listen for a gdb connection, we add the `-s` flag to the `run` target in our Makefile:
```make
run: $(iso)
@qemu-system-x86_64 -cdrom $(iso) -s
```
This allows us to connect a debugger at any time, for example to investigate why a panic occurred.
To wait for a debugger connection on startup, we add a `debug` target to the Makefile:
```make
debug: $(iso)
@qemu-system-x86_64 -cdrom $(iso) -s -S
```
It is identical to the `run` target except for the additional `-S` flag. This flag causes QEMU to freeze on startup and wait until a debugger is connected. Now it _should_ be possible to connect gdb.
### The annoying issue
Unfortunately gdb has an issue with the switch to long mode. If we connect when the CPU is already in long mode, everything works fine. But if we use `make debug` and thus connect right at the start, we get an error when we set a breakpoint in 64-bit mode:
```
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]: 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 :-)
Pretty frustrating, especially since the patch is [very small][gdb patch commit].
[gdb patch commit]: https://github.com/phil-opp/binutils-gdb/commit/9e88c451844ad38bb82fe77d1f388c87c41b4520
### Building the patched GDB
So the only way to use gdb with `make debug` is to build a modified gdb version that includes the patch. I created a repository with the patched GDB to make this easy. Just follow [the build instructions].
[the build instructions]: https://github.com/phil-opp/binutils-gdb#gdb-for-64-bit-rust-operating-systems
### Connecting GDB
Now you should have a `rust-os-gdb` subfolder. In its `bin` directory you find the `gdb` executable and the `rust-gdb` script, which [improves rendering of Rust types]. To make it easy to use it for our OS, we add a `make gdb` target to our Makefile:
[improves rendering of Rust types]: https://michaelwoerister.github.io/2015/03/27/rust-xxdb.html
```make
gdb:
@rust-os-gdb/bin/rust-gdb "build/kernel-x86_64.bin" -ex "target remote :1234"
```
It loads the debug information from our kernel binary and connects to the `localhost:1234` port, on which QEMU listens by default.
### Using GDB
After connecting to QEMU, you can use various gdb commands to control execution and examine data. All commands can be abbreviated as long they are still unique. For example, you can write `c` or `cont` instead of `continue`. The most important commands are:
- `help` or `h`: Show the help.
- `break` or `b`: Set a breakpoint. It possible to break on functions such as `rust_main` or on source lines such as `lib.rs:42`. You can use tab for autocompletion and omit parts of the path as long it's still unique. To modify breakpoints, you can use `disable`, `enable`, and `delete` plus the breakpoint number.
- `continue` or `c`: Continue execution until a breakpoint is reached.
- `next` or `n`: Step over the current line and break on the next line of the function. Sometimes this doesn't work in Rust OSes.
- `step` or `s`: Step into the current line, i.e. jump to the called function. Sometimes this doesn't work in Rust OSes.
- `list` or `l`: Shows the source code around the current position.
- `print` or `p`: Prints the value of a variable. You can use Cs `*` and `&` operators. To print in hexadecimal, use `p/x`.
- `tui enable`: Enables the text user interface, which provides a graphical interface (see below). To disable it again, run `tui disable`.

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]: https://beej.us/guide/bggdb/
[CS161]: https://www.eecs.harvard.edu/~cs161/resources/gdb.html
================================================
FILE: blog/content/edition-1/extra/talks.md
================================================
+++
title = "Talks"
path = "talks"
template = "plain.html"
weight = 1
+++
## 2018
- “The Rust Way Of OS Development” at HTWG Konstanz, May 30, 2018: [slides](https://phil-opp.github.io/talk-konstanz-may-2018/) [pdf](https://phil-opp.github.io/talk-konstanz-may-2018/talk.pdf)
## 2017
- “Open Source OS Development in Rust” at HTWG Konstanz, May 22, 2017: [slides](https://phil-opp.github.io/talk-konstanz-may-2017/)
================================================
FILE: blog/content/edition-1/posts/01-multiboot-kernel/index.md
================================================
+++
title = "A minimal Multiboot Kernel"
weight = 1
path = "multiboot-kernel"
aliases = ["multiboot-kernel.html", "/2015/08/18/multiboot-kernel/", "/rust-os/multiboot-kernel.html"]
date = 2015-08-18
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]: https://www.rust-lang.org/
<!-- more -->
I tried to explain everything in detail and to keep the code as simple as possible. If you have any questions, suggestions or other issues, please leave a comment or [create an issue] on Github. The source code is available in a [repository][source code], too.
[create an issue]: https://github.com/phil-opp/blog_os/issues
[source code]: https://github.com/phil-opp/blog_os/tree/first_edition_post_1/src/arch/x86_64
Note that this tutorial is written mainly for Linux. For some known problems on OS X see the comment section and [this issue][mac os issue]. If you want to use a virtual Linux machine, you can find instructions and a Vagrantfile in Ashley Willams's [x86-kernel repository].
[mac os issue]: https://github.com/phil-opp/blog_os/issues/55
[x86-kernel repository]: https://github.com/ashleygwilliams/x86-kernel
## Overview
When you turn on a computer, it loads the [BIOS] from some special flash memory. The BIOS runs self test and initialization routines of the hardware, then it looks for bootable devices. If it finds one, the control is transferred to its _bootloader_, which is a small portion of executable code stored at the device's beginning. The bootloader has to determine the location of the kernel image on the device and load it into memory. It also needs to switch the CPU to the so-called [protected mode] because x86 CPUs start in the very limited [real mode] by default (to be compatible to programs from 1978).
[BIOS]: https://en.wikipedia.org/wiki/BIOS
[protected mode]: https://en.wikipedia.org/wiki/Protected_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_]: 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]: 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:
Field | Type | Value
------------- | --------------- | ----------------------------------------
magic number | u32 | `0xE85250D6`
architecture | u32 | `0` for i386, `4` for MIPS
header length | u32 | total header size, including tags
checksum | u32 | `-(magic + architecture + header_length)`
tags | variable |
end tag | (u16, u16, u32) | `(0, 0, 8)`
Converted to a x86 assembly file it looks like this (Intel syntax):
```nasm
section .multiboot_header
header_start:
dd 0xe85250d6 ; magic number (multiboot 2)
dd 0 ; architecture 0 (protected mode i386)
dd header_end - header_start ; header length
; checksum
dd 0x100000000 - (0xe85250d6 + 0 + (header_end - header_start))
; insert optional multiboot tags here
; required end tag
dw 0 ; type
dw 0 ; flags
dd 8 ; size
header_end:
```
If you don't know x86 assembly, here is some quick guide:
- the header will be written to a section named `.multiboot_header` (we need this later)
- `header_start` and `header_end` are _labels_ that mark a memory location, we use them to calculate the header length easily
- `dd` stands for `define double` (32bit) and `dw` stands for `define word` (16bit). They just output the specified 32bit/16bit constant.
- the additional `0x100000000` in the checksum calculation is a small hack[^fn-checksum_hack] to avoid a compiler warning
We can already _assemble_ this file (which I called `multiboot_header.asm`) using `nasm`. It produces a flat binary by default, so the resulting file just contains our 24 bytes (in little endian if you work on a x86 machine):
```
> nasm multiboot_header.asm
> hexdump -x multiboot_header
0000000 50d6 e852 0000 0000 0018 0000 af12 17ad
0000010 0000 0000 0008 0000
0000018
```
## The Boot Code
To boot our kernel, we must add some code that the bootloader can call. Let's create a file named `boot.asm`:
```nasm
global start
section .text
bits 32
start:
; print `OK` to screen
mov dword [0xb8000], 0x2f4b2f4f
hlt
```
There are some new commands:
- `global` exports a label (makes it public). As `start` will be the entry point of our kernel, it needs to be public.
- the `.text` section is the default section for executable code
- `bits 32` specifies that the following lines are 32-bit instructions. It's needed because the CPU is still in [Protected mode] when GRUB starts our kernel. When we switch to [Long mode] in the [next post] we can use `bits 64` (64-bit instructions).
- the `mov dword` instruction moves the 32bit constant `0x2f4b2f4f` to the memory at address `b8000` (it prints `OK` to the screen, an explanation follows in the next posts)
- `hlt` is the halt instruction and causes the CPU to stop
Through assembling, viewing and disassembling we can see the CPU [Opcodes] in action:
[Opcodes]: https://en.wikipedia.org/wiki/Opcode
```
> nasm boot.asm
> hexdump -x boot
0000000 05c7 8000 000b 2f4b 2f4f 00f4
000000b
> ndisasm -b 32 boot
00000000 C70500800B004B2F mov dword [dword 0xb8000],0x2f4b2f4f
-4F2F
0000000A F4 hlt
```
## Building the Executable
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]: 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`:
[link]: https://en.wikipedia.org/wiki/Linker_(computing)
[linker script]: https://sourceware.org/binutils/docs/ld/Scripts.html
```ld
ENTRY(start)
SECTIONS {
. = 1M;
.boot :
{
/* ensure that the multiboot header is at the beginning */
*(.multiboot_header)
}
.text :
{
*(.text)
}
}
```
Let's translate it:
- `start` is the entry point, the bootloader will jump to it after loading the kernel
- `. = 1M;` sets the load address of the first section to 1 MiB, which is a conventional place to load a kernel[^Linker 1M]
- the executable will have two sections: `.boot` at the beginning and `.text` afterwards
- the `.text` output section contains all input sections named `.text`
- Sections named `.multiboot_header` are added to the first output section (`.boot`) to ensure they are at the beginning of the executable. This is necessary because GRUB expects to find the Multiboot header very early in the file.
So let's create the ELF object files and link them using our new linker script:
```
> nasm -f elf64 multiboot_header.asm
> nasm -f elf64 boot.asm
> ld -n -o kernel.bin -T linker.ld multiboot_header.o boot.o
```
It's important to pass the `-n` (or `--nmagic`) flag to the linker, which disables the automatic section alignment in the executable. Otherwise the linker may page align the `.boot` section in the executable file. If that happens, GRUB isn't able to find the Multiboot header because it isn't at the beginning anymore.
We can use `objdump` to print the sections of the generated executable and verify that the `.boot` section has a low file offset:
```
> objdump -h kernel.bin
kernel.bin: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .boot 00000018 0000000000100000 0000000000100000 00000080 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .text 0000000b 0000000000100020 0000000000100020 000000a0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
```
_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]: @/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:
```
isofiles
└── boot
├── grub
│ └── grub.cfg
└── kernel.bin
```
The `grub.cfg` specifies the file name of our kernel and its Multiboot 2 compliance. It looks like this:
```
set timeout=0
set default=0
menuentry "my os" {
multiboot2 /boot/kernel.bin
boot
}
```
Now we can create a bootable image using the command:
```
grub-mkrescue -o os.iso isofiles
```
_Note_: `grub-mkrescue` causes problems on some platforms. If it does not work for you, try the following steps:
- try to run it with `--verbose`
- make sure `xorriso` is installed (`xorriso` or `libisoburn` package)
- If you're using an EFI-system, `grub-mkrescue` tries to create an EFI image by default. You can either pass `-d /usr/lib/grub/i386-pc` to avoid EFI or install the `mtools` package to get a working EFI image
- on some system the command is named `grub2-mkrescue`
## Booting
Now it's time to boot our OS. We will use [QEMU]:
[QEMU]: https://en.wikipedia.org/wiki/QEMU
```
qemu-system-x86_64 -cdrom os.iso
```

Notice the green `OK` in the upper left corner. If it does not work for you, take a look at the comment section.
Let's summarize what happens:
1. the BIOS loads the bootloader (GRUB) from the virtual CD-ROM (the ISO)
2. the bootloader reads the kernel executable and finds the Multiboot header
3. it copies the `.boot` and `.text` sections to memory (to addresses `0x100000` and `0x100020`)
4. it jumps to the entry point (`0x100020`, you can obtain it through `objdump -f`)
5. our kernel prints the green `OK` and stops the CPU
You can test it on real hardware, too. Just burn the ISO to a disk or USB stick and boot from it.
## Build Automation
Right now we need to execute 4 commands in the right order every time we change a file. That's bad. So let's automate the build using a `Makefile`. But first we should create some clean directory structure for our source files to separate the architecture specific files:
```
…
├── Makefile
└── src
└── arch
└── x86_64
├── multiboot_header.asm
├── boot.asm
├── linker.ld
└── grub.cfg
```
The Makefile looks like this (indented with tabs instead of spaces):
```Makefile
arch ?= x86_64
kernel := build/kernel-$(arch).bin
iso := build/os-$(arch).iso
linker_script := src/arch/$(arch)/linker.ld
grub_cfg := src/arch/$(arch)/grub.cfg
assembly_source_files := $(wildcard src/arch/$(arch)/*.asm)
assembly_object_files := $(patsubst src/arch/$(arch)/%.asm, \
build/arch/$(arch)/%.o, $(assembly_source_files))
.PHONY: all clean run iso
all: $(kernel)
clean:
@rm -r build
run: $(iso)
@qemu-system-x86_64 -cdrom $(iso)
iso: $(iso)
$(iso): $(kernel) $(grub_cfg)
@mkdir -p build/isofiles/boot/grub
@cp $(kernel) build/isofiles/boot/kernel.bin
@cp $(grub_cfg) build/isofiles/boot/grub
@grub-mkrescue -o $(iso) build/isofiles 2> /dev/null
@rm -r build/isofiles
$(kernel): $(assembly_object_files) $(linker_script)
@ld -n -T $(linker_script) -o $(kernel) $(assembly_object_files)
# compile assembly files
build/arch/$(arch)/%.o: src/arch/$(arch)/%.asm
@mkdir -p $(shell dirname $@)
@nasm -felf64 $< -o $@
```
Some comments (see the [Makefile tutorial] if you don't know `make`):
- the `$(wildcard src/arch/$(arch)/*.asm)` chooses all assembly files in the src/arch/$(arch)` directory, so you don't have to update the Makefile when you add a file
- the `patsubst` operation for `assembly_object_files` just translates `src/arch/$(arch)/XYZ.asm` to `build/arch/$(arch)/XYZ.o`
- the `$<` and `$@` in the assembly target are [automatic variables]
- if you're using [cross-compiled binutils][cross compile binutils] just replace `ld` with `x86_64‑elf‑ld`
[automatic variables]: https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html
Now we can invoke `make` and all updated assembly files are compiled and linked. The `make iso` command also creates the ISO image and `make run` will additionally start QEMU.
## What's next?
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]: @/edition-1/posts/02-entering-longmode/index.md
[long mode]: https://en.wikipedia.org/wiki/Long_mode
## Footnotes
[^fn-checksum_hack]: The formula from the table, `-(magic + architecture + header_length)`, creates a negative value that doesn't fit into 32bit. By subtracting from `0x100000000` (= 2^(32)) instead, we keep the value positive without changing its truncated value. Without the additional sign bit(s) the result fits into 32bit and the compiler is happy :).
[^Linker 1M]: We don't want to load the kernel to e.g. `0x0` because there are many special memory areas below the 1MB mark (for example the so-called VGA buffer at `0xb8000`, that we use to print `OK` to the screen).
================================================
FILE: blog/content/edition-1/posts/02-entering-longmode/index.md
================================================
+++
title = "Entering Long Mode"
weight = 2
path = "entering-longmode"
aliases = ["entering-longmode.html", "/2015/08/25/entering-longmode/", "/rust-os/entering-longmode.html"]
date = 2015-08-25
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]: @/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
<!-- more -->
I tried to explain everything in detail and to keep the code as simple as possible. If you have any questions, suggestions, or issues, please leave a comment or [create an issue] on
gitextract_p9hgi7es/
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ ├── blog.yml
│ ├── check-links.yml
│ └── scheduled-builds.yml
├── .gitignore
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── blog/
│ ├── .gitignore
│ ├── before_build.py
│ ├── config.toml
│ ├── content/
│ │ ├── LICENSE-CC-BY-NC
│ │ ├── README.md
│ │ ├── _index.ar.md
│ │ ├── _index.es.md
│ │ ├── _index.fa.md
│ │ ├── _index.fr.md
│ │ ├── _index.ja.md
│ │ ├── _index.ko.md
│ │ ├── _index.md
│ │ ├── _index.pt-BR.md
│ │ ├── _index.ru.md
│ │ ├── _index.zh-CN.md
│ │ ├── _index.zh-TW.md
│ │ ├── edition-1/
│ │ │ ├── _index.md
│ │ │ ├── extra/
│ │ │ │ ├── _index.md
│ │ │ │ ├── cross-compile-binutils.md
│ │ │ │ ├── cross-compile-libcore.md
│ │ │ │ ├── naked-exceptions/
│ │ │ │ │ ├── 01-catching-exceptions/
│ │ │ │ │ │ └── index.md
│ │ │ │ │ ├── 02-better-exception-messages/
│ │ │ │ │ │ └── index.md
│ │ │ │ │ ├── 03-returning-from-exceptions/
│ │ │ │ │ │ └── index.md
│ │ │ │ │ └── _index.md
│ │ │ │ ├── set-up-gdb/
│ │ │ │ │ └── index.md
│ │ │ │ └── talks.md
│ │ │ └── posts/
│ │ │ ├── 01-multiboot-kernel/
│ │ │ │ └── index.md
│ │ │ ├── 02-entering-longmode/
│ │ │ │ └── index.md
│ │ │ ├── 03-set-up-rust/
│ │ │ │ └── index.md
│ │ │ ├── 04-printing-to-screen/
│ │ │ │ └── index.md
│ │ │ ├── 05-allocating-frames/
│ │ │ │ └── index.md
│ │ │ ├── 06-page-tables/
│ │ │ │ └── index.md
│ │ │ ├── 07-remap-the-kernel/
│ │ │ │ └── index.md
│ │ │ ├── 08-kernel-heap/
│ │ │ │ └── index.md
│ │ │ ├── 09-handling-exceptions/
│ │ │ │ └── index.md
│ │ │ ├── 10-double-faults/
│ │ │ │ └── index.md
│ │ │ └── _index.md
│ │ ├── edition-2/
│ │ │ ├── _index.md
│ │ │ ├── extra/
│ │ │ │ ├── _index.md
│ │ │ │ └── building-on-android/
│ │ │ │ └── index.md
│ │ │ └── posts/
│ │ │ ├── 01-freestanding-rust-binary/
│ │ │ │ ├── index.ar.md
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.fa.md
│ │ │ │ ├── index.fr.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.ko.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ ├── index.ru.md
│ │ │ │ ├── index.zh-CN.md
│ │ │ │ └── index.zh-TW.md
│ │ │ ├── 02-minimal-rust-kernel/
│ │ │ │ ├── _index.md
│ │ │ │ ├── disable-red-zone/
│ │ │ │ │ ├── index.ko.md
│ │ │ │ │ ├── index.md
│ │ │ │ │ ├── index.pt-BR.md
│ │ │ │ │ ├── index.ru.md
│ │ │ │ │ └── index.zh-CN.md
│ │ │ │ ├── disable-simd/
│ │ │ │ │ ├── index.ko.md
│ │ │ │ │ ├── index.md
│ │ │ │ │ ├── index.pt-BR.md
│ │ │ │ │ ├── index.ru.md
│ │ │ │ │ └── index.zh-CN.md
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.fa.md
│ │ │ │ ├── index.fr.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.ko.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ ├── index.ru.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 03-vga-text-buffer/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.fa.md
│ │ │ │ ├── index.fr.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.ko.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 04-testing/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.fa.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.ko.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 05-cpu-exceptions/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.fa.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.ko.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 06-double-faults/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.fa.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.ko.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 07-hardware-interrupts/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.fa.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.ko.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 08-paging-introduction/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.fa.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 09-paging-implementation/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 10-heap-allocation/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 11-allocator-designs/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ ├── index.ru.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── 12-async-await/
│ │ │ │ ├── index.es.md
│ │ │ │ ├── index.ja.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.pt-BR.md
│ │ │ │ ├── index.ru.md
│ │ │ │ ├── index.zh-CN.md
│ │ │ │ ├── index.zh-TW.md
│ │ │ │ └── scancode-queue.drawio
│ │ │ ├── _index.ar.md
│ │ │ ├── _index.es.md
│ │ │ ├── _index.fa.md
│ │ │ ├── _index.fr.md
│ │ │ ├── _index.ja.md
│ │ │ ├── _index.ko.md
│ │ │ ├── _index.md
│ │ │ ├── _index.pt-BR.md
│ │ │ ├── _index.ru.md
│ │ │ ├── _index.zh-CN.md
│ │ │ ├── _index.zh-TW.md
│ │ │ └── deprecated/
│ │ │ ├── 04-unit-testing/
│ │ │ │ └── index.md
│ │ │ ├── 05-integration-tests/
│ │ │ │ └── index.md
│ │ │ ├── 10-advanced-paging/
│ │ │ │ └── index.md
│ │ │ └── _index.md
│ │ ├── news/
│ │ │ ├── 2018-03-09-pure-rust.md
│ │ │ └── _index.md
│ │ ├── pages/
│ │ │ ├── _index.md
│ │ │ └── contact.md
│ │ └── status-update/
│ │ ├── 2019-05-01.md
│ │ ├── 2019-06-03.md
│ │ ├── 2019-07-06.md
│ │ ├── 2019-08-02.md
│ │ ├── 2019-09-09.md
│ │ ├── 2019-10-06.md
│ │ ├── 2019-12-02.md
│ │ ├── 2020-01-07.md
│ │ ├── 2020-02-01.md
│ │ ├── 2020-03-02.md
│ │ ├── 2020-04-01/
│ │ │ └── index.md
│ │ └── _index.md
│ ├── diagrams/
│ │ ├── red-zone-overwrite.dia
│ │ ├── red-zone.dia
│ │ └── xmm-overwrite.dia
│ ├── requirements.txt
│ ├── sass/
│ │ └── css/
│ │ └── edition-2/
│ │ └── main.scss
│ ├── static/
│ │ ├── CNAME
│ │ ├── atom.xml/
│ │ │ └── index.html
│ │ ├── css/
│ │ │ └── edition-1/
│ │ │ ├── isso.css
│ │ │ ├── main.css
│ │ │ └── poole.css
│ │ ├── handling-exceptions-with-naked-fns.html
│ │ └── js/
│ │ ├── edition-1/
│ │ │ └── main.js
│ │ └── edition-2/
│ │ └── main.js
│ ├── templates/
│ │ ├── 404.html
│ │ ├── auto/
│ │ │ ├── forks.html
│ │ │ ├── recent-updates.html
│ │ │ ├── stars.html
│ │ │ ├── status-updates-truncated.html
│ │ │ └── status-updates.html
│ │ ├── base.html
│ │ ├── edition-1/
│ │ │ ├── base.html
│ │ │ ├── comments/
│ │ │ │ ├── allocating-frames.html
│ │ │ │ ├── better-exception-messages.html
│ │ │ │ ├── catching-exceptions.html
│ │ │ │ ├── double-faults.html
│ │ │ │ ├── entering-longmode.html
│ │ │ │ ├── handling-exceptions.html
│ │ │ │ ├── kernel-heap.html
│ │ │ │ ├── multiboot-kernel.html
│ │ │ │ ├── page-tables.html
│ │ │ │ ├── printing-to-screen.html
│ │ │ │ ├── remap-the-kernel.html
│ │ │ │ ├── returning-from-exceptions.html
│ │ │ │ └── set-up-rust.html
│ │ │ ├── comments.html
│ │ │ ├── handling-exceptions-with-naked-fns.html
│ │ │ ├── index.html
│ │ │ ├── macros.html
│ │ │ ├── page.html
│ │ │ └── section.html
│ │ ├── edition-2/
│ │ │ ├── base.html
│ │ │ ├── extra.html
│ │ │ ├── index.html
│ │ │ ├── macros.html
│ │ │ ├── page.html
│ │ │ └── section.html
│ │ ├── index.html
│ │ ├── news-page.html
│ │ ├── news-section.html
│ │ ├── plain.html
│ │ ├── redirect-to-frontpage.html
│ │ ├── rss.xml
│ │ ├── section.html
│ │ ├── snippets.html
│ │ ├── status-update-page.html
│ │ └── status-update-section.html
│ └── typos.toml
├── docker/
│ ├── .bash_aliases
│ ├── Dockerfile
│ ├── README.md
│ └── entrypoint.sh
├── giscus.json
└── scripts/
├── merge.fish
└── push.fish
SYMBOL INDEX (11 symbols across 3 files)
FILE: blog/before_build.py
function filter_date (line 13) | def filter_date(issue):
function format_number (line 16) | def format_number(number):
FILE: blog/static/js/edition-1/main.js
function resize_toc (line 13) | function resize_toc(container) {
function toc_scroll_position (line 32) | function toc_scroll_position(container) {
function show_lang_selector (line 67) | function show_lang_selector() {
FILE: blog/static/js/edition-2/main.js
function resize_toc (line 17) | function resize_toc(container) {
function toc_scroll_position (line 36) | function toc_scroll_position(container) {
function toggle_lights (line 70) | function toggle_lights() {
function set_theme (line 80) | function set_theme(theme) {
function clear_theme_override (line 86) | function clear_theme_override() {
function set_giscus_theme (line 92) | function set_giscus_theme(theme) {
Condensed preview — 240 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (5,597K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 775,
"preview": "# These are supported funding model platforms\n\ngithub: [phil-opp] # Replace with up to 4 GitHub Sponsors-enabled usernam"
},
{
"path": ".github/workflows/blog.yml",
"chars": 2850,
"preview": "name: Blog\n\non:\n push:\n branches:\n - '*'\n - '!staging.tmp'\n tags:\n - '*'\n pull_request:\n schedul"
},
{
"path": ".github/workflows/check-links.yml",
"chars": 552,
"preview": "name: Check Links\n\non:\n push:\n branches:\n - \"*\"\n - \"!staging.tmp\"\n tags:\n - \"*\"\n pull_request:\n "
},
{
"path": ".github/workflows/scheduled-builds.yml",
"chars": 668,
"preview": "name: Build code on schedule\n\non:\n schedule:\n - cron: '40 1 * * *' # every day at 1:40\n\njobs:\n trigger-build:\n "
},
{
"path": ".gitignore",
"chars": 5,
"preview": "code\n"
},
{
"path": "LICENSE-APACHE",
"chars": 10174,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "LICENSE-MIT",
"chars": 1084,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2019 Philipp Oppermann\n\nPermission is hereby granted, free of charge, to any person"
},
{
"path": "README.md",
"chars": 6398,
"preview": "# Blog OS\n\nThis repository contains the source code for the _Writing an OS in Rust_ series at [os.phil-opp.com](https://"
},
{
"path": "blog/.gitignore",
"chars": 13,
"preview": "/public\nzola\n"
},
{
"path": "blog/before_build.py",
"chars": 2762,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport io\nimport urllib\nimport datetime\nfrom github import Github\n\ng = Gi"
},
{
"path": "blog/config.toml",
"chars": 19467,
"preview": "base_url = \"https://os.phil-opp.com\"\ntitle = \"Writing an OS in Rust\"\ndescription = \"This blog series creates a small ope"
},
{
"path": "blog/content/LICENSE-CC-BY-NC",
"chars": 13722,
"preview": "Creative Commons Attribution-NonCommercial 4.0 International Public License\n\nBy exercising the Licensed Rights (defined "
},
{
"path": "blog/content/README.md",
"chars": 885,
"preview": "# Blog Content\n\nThis folder contains the content for the _\"Writing an OS in Rust\"_ blog.\n\n## License\n\nThis folder is lic"
},
{
"path": "blog/content/_index.ar.md",
"chars": 506,
"preview": "+++\ntemplate = \"edition-2/index.html\"\n+++\n\n\n<h1 style=\"visibility: hidden; height: 0px; margin: 0px; padding: 0px;\">كتاب"
},
{
"path": "blog/content/_index.es.md",
"chars": 620,
"preview": "+++\ntemplate = \"edition-2/index.html\"\n+++\n\n<h1 style=\"visibility: hidden; height: 0px; margin: 0px; padding: 0px;\">Escri"
},
{
"path": "blog/content/_index.fa.md",
"chars": 544,
"preview": "+++\ntemplate = \"edition-2/index.html\"\n+++\n\n<h1 style=\"visibility: hidden; height: 0px; margin: 0px; padding: 0px;\">نوشتن"
},
{
"path": "blog/content/_index.fr.md",
"chars": 615,
"preview": "+++\ntemplate = \"edition-2/index.html\"\n+++\n\n<h1 style=\"visibility: hidden; height: 0px; margin: 0px; padding: 0px;\">Écrir"
},
{
"path": "blog/content/_index.ja.md",
"chars": 432,
"preview": "+++\ntemplate = \"edition-2/index.html\"\n+++\n\n<h1 style=\"visibility: hidden; height: 0px; margin: 0px; padding: 0px;\">Rustで"
},
{
"path": "blog/content/_index.ko.md",
"chars": 422,
"preview": "+++\ntemplate = \"edition-2/index.html\"\n+++\n\n<h1 style=\"visibility: hidden; height: 0px; margin: 0px; padding: 0px;\">Rust로"
},
{
"path": "blog/content/_index.md",
"chars": 549,
"preview": "+++\ntemplate = \"edition-2/index.html\"\n+++\n\n<h1 style=\"visibility: hidden; height: 0px; margin: 0px; padding: 0px;\">Writi"
},
{
"path": "blog/content/_index.pt-BR.md",
"chars": 575,
"preview": "+++\ntemplate = \"edition-2/index.html\"\n+++\n\n<h1 style=\"visibility: hidden; height: 0px; margin: 0px; padding: 0px;\">Escre"
},
{
"path": "blog/content/_index.ru.md",
"chars": 612,
"preview": "+++\ntemplate = \"edition-2/index.html\"\n+++\n\n<h1 style=\"visibility: hidden; height: 0px; margin: 0px; padding: 0px;\">Собст"
},
{
"path": "blog/content/_index.zh-CN.md",
"chars": 356,
"preview": "+++\ntemplate = \"edition-2/index.html\"\n+++\n\n<h1 style=\"visibility: hidden; height: 0px; margin: 0px; padding: 0px;\">用Rust"
},
{
"path": "blog/content/_index.zh-TW.md",
"chars": 549,
"preview": "+++\ntemplate = \"edition-2/index.html\"\n+++\n\n<h1 style=\"visibility: hidden; height: 0px; margin: 0px; padding: 0px;\">Writi"
},
{
"path": "blog/content/edition-1/_index.md",
"chars": 105,
"preview": "+++\ntitle = \"First Edition\"\ntemplate = \"edition-1/index.html\"\naliases = [\"first-edition/index.html\"]\n+++\n"
},
{
"path": "blog/content/edition-1/extra/_index.md",
"chars": 95,
"preview": "+++\ntitle = \"Extra Content\"\ninsert_anchor_links = \"left\"\nrender = false\nsort_by = \"weight\"\n+++\n"
},
{
"path": "blog/content/edition-1/extra/cross-compile-binutils.md",
"chars": 2182,
"preview": "+++\ntitle = \"Cross Compile Binutils\"\ntemplate = \"plain.html\"\npath = \"cross-compile-binutils\"\nweight = 2\n+++\n\nThe [GNU Bi"
},
{
"path": "blog/content/edition-1/extra/cross-compile-libcore.md",
"chars": 2368,
"preview": "+++\ntitle = \"Cross Compiling: libcore\"\ntemplate = \"plain.html\"\npath = \"cross-compile-libcore\"\nweight = 3\n+++\n\nIf you get"
},
{
"path": "blog/content/edition-1/extra/naked-exceptions/01-catching-exceptions/index.md",
"chars": 25406,
"preview": "+++\ntitle = \"Catching Exceptions\"\nweight = 1\npath = \"catching-exceptions\"\naliases = [\"catching-exceptions.html\"]\ndate ="
},
{
"path": "blog/content/edition-1/extra/naked-exceptions/02-better-exception-messages/index.md",
"chars": 32384,
"preview": "+++\ntitle = \"Better Exception Messages\"\nweight = 2\npath = \"better-exception-messages\"\naliases = [\"better-exception-messa"
},
{
"path": "blog/content/edition-1/extra/naked-exceptions/03-returning-from-exceptions/index.md",
"chars": 46478,
"preview": "+++\ntitle = \"Returning from Exceptions\"\nweight = 3\npath = \"returning-from-exceptions\"\naliases = [\"returning-from-excepti"
},
{
"path": "blog/content/edition-1/extra/naked-exceptions/_index.md",
"chars": 233,
"preview": "+++\ntitle = \"Handling Exceptions using naked Functions\"\nsort_by = \"weight\"\ntemplate = \"edition-1/handling-exceptions-wit"
},
{
"path": "blog/content/edition-1/extra/set-up-gdb/index.md",
"chars": 4647,
"preview": "+++\ntitle = \"Set Up GDB\"\ntemplate = \"plain.html\"\npath = \"set-up-gdb\"\naliases = [\"set-up-gdb.html\"]\nweight = 4\n+++\n\nThere"
},
{
"path": "blog/content/edition-1/extra/talks.md",
"chars": 420,
"preview": "+++\ntitle = \"Talks\"\npath = \"talks\"\ntemplate = \"plain.html\"\nweight = 1\n+++\n\n## 2018\n\n- “The Rust Way Of OS Development” a"
},
{
"path": "blog/content/edition-1/posts/01-multiboot-kernel/index.md",
"chars": 14476,
"preview": "+++\ntitle = \"A minimal Multiboot Kernel\"\nweight = 1\npath = \"multiboot-kernel\"\naliases = [\"multiboot-kernel.html\", \"/2015"
},
{
"path": "blog/content/edition-1/posts/02-entering-longmode/index.md",
"chars": 29108,
"preview": "+++\ntitle = \"Entering Long Mode\"\nweight = 2\npath = \"entering-longmode\"\naliases = [\"entering-longmode.html\", \"/2015/08/25"
},
{
"path": "blog/content/edition-1/posts/03-set-up-rust/index.md",
"chars": 28705,
"preview": "+++\ntitle = \"Set Up Rust\"\nweight = 3\npath = \"set-up-rust\"\naliases = [\"set-up-rust.html\", \"setup-rust.html\", \"/2015/09/02"
},
{
"path": "blog/content/edition-1/posts/04-printing-to-screen/index.md",
"chars": 30317,
"preview": "+++\ntitle = \"Printing to Screen\"\nweight = 4\npath = \"printing-to-screen\"\naliases = [\"printing-to-screen.html\", \"/2015/10/"
},
{
"path": "blog/content/edition-1/posts/05-allocating-frames/index.md",
"chars": 20435,
"preview": "+++\ntitle = \"Allocating Frames\"\nweight = 5\npath = \"allocating-frames\"\naliases = [\"allocating-frames.html\"]\ndate = 2015-"
},
{
"path": "blog/content/edition-1/posts/06-page-tables/index.md",
"chars": 39515,
"preview": "+++\ntitle = \"Page Tables\"\nweight = 6\npath = \"page-tables\"\naliases = [\"page-tables.html\", \"modifying-page-tables.html\"]\nd"
},
{
"path": "blog/content/edition-1/posts/07-remap-the-kernel/index.md",
"chars": 46435,
"preview": "+++\ntitle = \"Remap the Kernel\"\nweight = 7\npath = \"remap-the-kernel\"\naliases = [\"remap-the-kernel.html\"]\ndate = 2016-01-"
},
{
"path": "blog/content/edition-1/posts/08-kernel-heap/index.md",
"chars": 37953,
"preview": "+++\ntitle = \"Kernel Heap\"\nweight = 8\npath = \"kernel-heap\"\naliases = [\"kernel-heap.html\"]\ndate = 2016-04-11\ntemplate = \""
},
{
"path": "blog/content/edition-1/posts/09-handling-exceptions/index.md",
"chars": 31804,
"preview": "+++\ntitle = \"Handling Exceptions\"\nweight = 9\npath = \"handling-exceptions\"\naliases = [\"handling-exceptions.html\"]\ndate ="
},
{
"path": "blog/content/edition-1/posts/10-double-faults/index.md",
"chars": 40278,
"preview": "+++\ntitle = \"Double Faults\"\nweight = 10\npath = \"double-faults\"\naliases = [\"double-faults.html\"]\ndate = 2017-01-02\ntempl"
},
{
"path": "blog/content/edition-1/posts/_index.md",
"chars": 87,
"preview": "+++\ntitle = \"Posts\"\nsort_by = \"weight\"\ninsert_anchor_links = \"left\"\nrender = false\n+++\n"
},
{
"path": "blog/content/edition-2/_index.md",
"chars": 113,
"preview": "+++\ntitle = \"Second Edition\"\ntemplate = \"redirect-to-frontpage.html\"\naliases = [\"second-edition/index.html\"]\n+++\n"
},
{
"path": "blog/content/edition-2/extra/_index.md",
"chars": 134,
"preview": "+++\ntitle = \"Extra Content\"\ninsert_anchor_links = \"left\"\nrender = false\nsort_by = \"weight\"\npage_template = \"edition-2/ex"
},
{
"path": "blog/content/edition-2/extra/building-on-android/index.md",
"chars": 3849,
"preview": "+++\ntitle = \"Building on Android\"\nweight = 3\naliases = [\"second-edition/extra/building-on-android/index.html\"]\n+++\n\nI fi"
},
{
"path": "blog/content/edition-2/posts/01-freestanding-rust-binary/index.ar.md",
"chars": 5487,
"preview": "+++\ntitle = \"A Freestanding Rust Binary\"\nweight = 1\npath = \"ar/freestanding-rust-binary\"\ndate = 2018-02-10\n\n[extra]\n# Pl"
},
{
"path": "blog/content/edition-2/posts/01-freestanding-rust-binary/index.es.md",
"chars": 29489,
"preview": "+++\ntitle = \"Un Binario Rust Autónomo\"\nweight = 1\npath = \"es/freestanding-rust-binary\"\ndate = 2018-02-10\n\n[extra]\nchapte"
},
{
"path": "blog/content/edition-2/posts/01-freestanding-rust-binary/index.fa.md",
"chars": 26557,
"preview": "+++\ntitle = \" یک باینری مستقل Rust\"\nweight = 1\npath = \"fa/freestanding-rust-binary\"\ndate = 2018-02-10\n\n[extra]\n# Please "
},
{
"path": "blog/content/edition-2/posts/01-freestanding-rust-binary/index.fr.md",
"chars": 30158,
"preview": "+++\ntitle = \"Un binaire Rust autoporté\"\nweight = 1\npath = \"fr/freestanding-rust-binary\"\ndate = 2018-02-10\n\n[extra]\n# Ple"
},
{
"path": "blog/content/edition-2/posts/01-freestanding-rust-binary/index.ja.md",
"chars": 23501,
"preview": "+++\ntitle = \"フリースタンディングな Rust バイナリ\"\nweight = 1\npath = \"ja/freestanding-rust-binary\"\ndate = 2018-02-10\n\n[extra]\n# Please "
},
{
"path": "blog/content/edition-2/posts/01-freestanding-rust-binary/index.ko.md",
"chars": 21543,
"preview": "+++\ntitle = \"Rust로 'Freestanding 실행파일' 만들기\"\nweight = 1\npath = \"ko/freestanding-rust-binary\"\ndate = 2018-02-10\n\n[extra]\n#"
},
{
"path": "blog/content/edition-2/posts/01-freestanding-rust-binary/index.md",
"chars": 31548,
"preview": "+++\ntitle = \"A Freestanding Rust Binary\"\nweight = 1\npath = \"freestanding-rust-binary\"\ndate = 2018-02-10\n\n[extra]\nchapter"
},
{
"path": "blog/content/edition-2/posts/01-freestanding-rust-binary/index.pt-BR.md",
"chars": 32865,
"preview": "+++\ntitle = \"Um Binário Rust Independente\"\nweight = 1\npath = \"pt-BR/freestanding-rust-binary\"\ndate = 2018-02-10\n\n[extra]"
},
{
"path": "blog/content/edition-2/posts/01-freestanding-rust-binary/index.ru.md",
"chars": 28152,
"preview": "+++\ntitle = \"Независимый бинарный файл на Rust\"\nweight = 1\npath = \"ru/freestanding-rust-binary\"\ndate = 2018-02-10\n\n[extr"
},
{
"path": "blog/content/edition-2/posts/01-freestanding-rust-binary/index.zh-CN.md",
"chars": 19928,
"preview": "+++\ntitle = \"独立式可执行程序\"\nweight = 1\npath = \"zh-CN/freestanding-rust-binary\"\ndate = 2018-02-10\n\n[extra]\n# Please update thi"
},
{
"path": "blog/content/edition-2/posts/01-freestanding-rust-binary/index.zh-TW.md",
"chars": 16582,
"preview": "+++\ntitle = \"獨立的 Rust 二進制檔\"\nweight = 1\npath = \"zh-TW/freestanding-rust-binary\"\ndate = 2018-02-10\n\n[extra]\n# Please updat"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/_index.md",
"chars": 117,
"preview": "+++\ntitle = \"Extra Posts for Minimal Rust Kernel\"\nsort_by = \"weight\"\ninsert_anchor_links = \"left\"\nrender = false\n+++\n"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/disable-red-zone/index.ko.md",
"chars": 1402,
"preview": "+++\ntitle = \"Red Zone 기능 해제하기\"\nweight = 1\npath = \"ko/red-zone\"\ntemplate = \"edition-2/extra.html\"\n+++\n\n[red zone]은 [Syste"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/disable-red-zone/index.md",
"chars": 1766,
"preview": "+++\ntitle = \"Disable the Red Zone\"\nweight = 1\npath = \"red-zone\"\ntemplate = \"edition-2/extra.html\"\n+++\n\nThe [red zone] is"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/disable-red-zone/index.pt-BR.md",
"chars": 2055,
"preview": "+++\ntitle = \"Desabilitando a Red Zone\"\nweight = 1\npath = \"pt-BR/red-zone\"\ntemplate = \"edition-2/extra.html\"\n\n[extra]\n# P"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/disable-red-zone/index.ru.md",
"chars": 1896,
"preview": "+++\ntitle = \"Отключение красной зоны\"\nweight = 1\npath = \"ru/red-zone\"\ntemplate = \"edition-2/extra.html\"\n+++\n\n[Красная зо"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/disable-red-zone/index.zh-CN.md",
"chars": 947,
"preview": "+++\ntitle = \"Disable the Red Zone\"\nweight = 1\npath = \"zh-CN/red-zone\"\ntemplate = \"edition-2/extra.html\"\n+++\n\n[红区][red zo"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/disable-simd/index.ko.md",
"chars": 2484,
"preview": "+++\ntitle = \"SIMD 해제하기\"\nweight = 2\npath = \"ko/disable-simd\"\ntemplate = \"edition-2/extra.html\"\n+++\n\n[Single Instruction M"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/disable-simd/index.md",
"chars": 3210,
"preview": "+++\ntitle = \"Disable SIMD\"\nweight = 2\npath = \"disable-simd\"\ntemplate = \"edition-2/extra.html\"\n+++\n\n[Single Instruction M"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/disable-simd/index.pt-BR.md",
"chars": 3685,
"preview": "+++\ntitle = \"Desabilitando SIMD\"\nweight = 2\npath = \"pt-BR/disable-simd\"\ntemplate = \"edition-2/extra.html\"\n\n[extra]\n# Ple"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/disable-simd/index.ru.md",
"chars": 3485,
"preview": "+++\ntitle = \"Отключение SIMD\"\nweight = 2\npath = \"ru/disable-simd\"\ntemplate = \"edition-2/extra.html\"\n+++\n\nИнструкции [Sin"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/disable-simd/index.zh-CN.md",
"chars": 1712,
"preview": "+++\ntitle = \"Disable SIMD\"\nweight = 2\npath = \"zh-CN/disable-simd\"\ntemplate = \"edition-2/extra.html\"\n+++\n\n[单指令多数据][Single"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/index.es.md",
"chars": 37858,
"preview": "+++\ntitle = \"Un Kernel Mínimo en Rust\"\nweight = 2\npath = \"es/minimal-rust-kernel\"\ndate = 2018-02-10\n\n[extra]\nchapter = \""
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/index.fa.md",
"chars": 35123,
"preview": "+++\ntitle = \"یک هسته مینیمال با Rust\"\nweight = 2\npath = \"fa/minimal-rust-kernel\"\ndate = 2018-02-10\n\n[extra]\n# Please upd"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/index.fr.md",
"chars": 39447,
"preview": "+++\ntitle = \"Un noyau Rust minimal\"\nweight = 2\npath = \"fr/minimal-rust-kernel\"\ndate = 2018-02-10\n\n[extra]\n# Please updat"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/index.ja.md",
"chars": 25847,
"preview": "+++\ntitle = \"Rustでつくる最小のカーネル\"\nweight = 2\npath = \"ja/minimal-rust-kernel\"\ndate = 2018-02-10\n\n[extra]\n# Please update this"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/index.ko.md",
"chars": 26586,
"preview": "+++\ntitle = \"최소 기능을 갖춘 커널\"\nweight = 2\npath = \"ko/minimal-rust-kernel\"\ndate = 2018-02-10\n\n[extra]\n# Please update this wh"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/index.md",
"chars": 34470,
"preview": "+++\ntitle = \"A Minimal Rust Kernel\"\nweight = 2\npath = \"minimal-rust-kernel\"\ndate = 2018-02-10\n\n[extra]\nchapter = \"Bare B"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/index.pt-BR.md",
"chars": 36422,
"preview": "+++\ntitle = \"Um Kernel Rust Mínimo\"\nweight = 2\npath = \"pt-BR/minimal-rust-kernel\"\ndate = 2018-02-10\n\n[extra]\nchapter = \""
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/index.ru.md",
"chars": 36421,
"preview": "+++\ntitle = \"Минимально возможное ядро на Rust\"\nweight = 2\npath = \"ru/minimal-rust-kernel\"\ndate = 2018-02-10\n\n[extra]\ntr"
},
{
"path": "blog/content/edition-2/posts/02-minimal-rust-kernel/index.zh-CN.md",
"chars": 21256,
"preview": "+++\ntitle = \"最小内核\"\nweight = 2\npath = \"zh-CN/minimal-rust-kernel\"\ndate = 2018-02-10\n\n[extra]\n# Please update this when up"
},
{
"path": "blog/content/edition-2/posts/03-vga-text-buffer/index.es.md",
"chars": 16585,
"preview": "+++\ntitle = \"Modo de Texto VGA\"\nweight = 3\npath = \"es/modo-texto-vga\"\ndate = 2018-02-26\n\n[extra]\nchapter = \"Fundamentos"
},
{
"path": "blog/content/edition-2/posts/03-vga-text-buffer/index.fa.md",
"chars": 32151,
"preview": "+++\ntitle = \"حالت متن VGA\"\nweight = 3\npath = \"fa/vga-text-mode\"\ndate = 2018-02-26\n\n[extra]\n# Please update this when up"
},
{
"path": "blog/content/edition-2/posts/03-vga-text-buffer/index.fr.md",
"chars": 36099,
"preview": "+++\ntitle = \"Mode Texte VGA\"\nweight = 3\npath = \"fr/vga-text-mode\"\ndate = 2018-02-26\n\n[extra]\nchapter = \"Bare Bones\"\n# Pl"
},
{
"path": "blog/content/edition-2/posts/03-vga-text-buffer/index.ja.md",
"chars": 26504,
"preview": "+++\ntitle = \"VGAテキストモード\"\nweight = 3\npath = \"ja/vga-text-mode\"\ndate = 2018-02-26\n\n[extra]\n# Please update this when upda"
},
{
"path": "blog/content/edition-2/posts/03-vga-text-buffer/index.ko.md",
"chars": 27230,
"preview": "+++\ntitle = \"VGA 텍스트 모드\"\nweight = 3\npath = \"ko/vga-text-mode\"\ndate = 2018-02-26\n\n[extra]\n# Please update this when upda"
},
{
"path": "blog/content/edition-2/posts/03-vga-text-buffer/index.md",
"chars": 32124,
"preview": "+++\ntitle = \"VGA Text Mode\"\nweight = 3\npath = \"vga-text-mode\"\ndate = 2018-02-26\n\n[extra]\nchapter = \"Bare Bones\"\n+++\n\nTh"
},
{
"path": "blog/content/edition-2/posts/03-vga-text-buffer/index.pt-BR.md",
"chars": 33345,
"preview": "+++\ntitle = \"Modo de Texto VGA\"\nweight = 3\npath = \"pt-BR/vga-text-mode\"\ndate = 2018-02-26\n\n[extra]\nchapter = \"O Básico\""
},
{
"path": "blog/content/edition-2/posts/03-vga-text-buffer/index.zh-CN.md",
"chars": 22848,
"preview": "+++\ntitle = \"VGA 字符模式\"\nweight = 3\npath = \"zh-CN/vga-text-mode\"\ndate = 2018-02-26\n\n[extra]\n# Please update this when upd"
},
{
"path": "blog/content/edition-2/posts/04-testing/index.es.md",
"chars": 51926,
"preview": "+++\ntitle = \"Pruebas\"\nweight = 4\npath = \"es/testing\"\ndate = 2019-04-27\n\n[extra]\nchapter = \"Fundamentos\"\ncomments_search_"
},
{
"path": "blog/content/edition-2/posts/04-testing/index.fa.md",
"chars": 47251,
"preview": "+++\ntitle = \"تست کردن\"\nweight = 4\npath = \"fa/testing\"\ndate = 2019-04-27\n\n[extra]\n# Please update this when updating the "
},
{
"path": "blog/content/edition-2/posts/04-testing/index.ja.md",
"chars": 35252,
"preview": "+++\ntitle = \"テスト\"\nweight = 4\npath = \"ja/testing\"\ndate = 2019-04-27\n\n[extra]\n# Please update this when updating the trans"
},
{
"path": "blog/content/edition-2/posts/04-testing/index.ko.md",
"chars": 36325,
"preview": "+++\ntitle = \"커널을 위한 테스트 작성 및 실행하기\"\nweight = 4\npath = \"ko/testing\"\ndate = 2019-04-27\n\n[extra]\n# Please update this when u"
},
{
"path": "blog/content/edition-2/posts/04-testing/index.md",
"chars": 48021,
"preview": "+++\ntitle = \"Testing\"\nweight = 4\npath = \"testing\"\ndate = 2019-04-27\n\n[extra]\nchapter = \"Bare Bones\"\ncomments_search_term"
},
{
"path": "blog/content/edition-2/posts/04-testing/index.pt-BR.md",
"chars": 50315,
"preview": "+++\ntitle = \"Testes\"\nweight = 4\npath = \"pt-BR/testing\"\ndate = 2019-04-27\n\n[extra]\nchapter = \"O Básico\"\ncomments_search_t"
},
{
"path": "blog/content/edition-2/posts/04-testing/index.zh-CN.md",
"chars": 31216,
"preview": "+++\ntitle = \"内核测试\"\nweight = 4\npath = \"zh-CN/testing\"\ndate = 2019-04-27\n\n[extra]\n# Please update this when updating the t"
},
{
"path": "blog/content/edition-2/posts/05-cpu-exceptions/index.es.md",
"chars": 32834,
"preview": "+++\ntitle = \"Excepciones de CPU\"\nweight = 5\npath = \"es/cpu-exceptions\"\ndate = 2018-06-17\n\n[extra]\nchapter = \"Interrupci"
},
{
"path": "blog/content/edition-2/posts/05-cpu-exceptions/index.fa.md",
"chars": 29396,
"preview": "+++\ntitle = \"استثناهای پردازنده\"\nweight = 5\npath = \"fa/cpu-exceptions\"\ndate = 2018-06-17\n\n[extra]\n# Please update this "
},
{
"path": "blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md",
"chars": 22764,
"preview": "+++\ntitle = \"CPU例外\"\nweight = 5\npath = \"ja/cpu-exceptions\"\ndate = 2018-06-17\n\n[extra]\n# Please update this when updating"
},
{
"path": "blog/content/edition-2/posts/05-cpu-exceptions/index.ko.md",
"chars": 23871,
"preview": "+++\ntitle = \"CPU 예외 (Exception)\"\nweight = 5\npath = \"ko/cpu-exceptions\"\ndate = 2018-06-17\n\n[extra]\n# Please update this "
},
{
"path": "blog/content/edition-2/posts/05-cpu-exceptions/index.md",
"chars": 29548,
"preview": "+++\ntitle = \"CPU Exceptions\"\nweight = 5\npath = \"cpu-exceptions\"\ndate = 2018-06-17\n\n[extra]\nchapter = \"Interrupts\"\n+++\n\n"
},
{
"path": "blog/content/edition-2/posts/05-cpu-exceptions/index.pt-BR.md",
"chars": 31060,
"preview": "+++\ntitle = \"Exceções de CPU\"\nweight = 5\npath = \"pt-BR/cpu-exceptions\"\ndate = 2018-06-17\n\n[extra]\nchapter = \"Interrupçõe"
},
{
"path": "blog/content/edition-2/posts/05-cpu-exceptions/index.zh-CN.md",
"chars": 17827,
"preview": "+++\ntitle = \"CPU异常处理\"\nweight = 5\npath = \"zh-CN/cpu-exceptions\"\ndate = 2018-06-17\n\n[extra]\n# Please update this when upd"
},
{
"path": "blog/content/edition-2/posts/06-double-faults/index.es.md",
"chars": 32109,
"preview": "+++\ntitle = \"Excepciones de Doble Fallo\"\nweight = 6\npath = \"es/double-fault-exceptions\"\ndate = 2018-06-18\n\n[extra]\nchap"
},
{
"path": "blog/content/edition-2/posts/06-double-faults/index.fa.md",
"chars": 28657,
"preview": "+++\ntitle = \"خطاهای دوگانه\"\nweight = 6\npath = \"fa/double-fault-exceptions\"\ndate = 2018-06-18\n\n[extra]\n# Please update t"
},
{
"path": "blog/content/edition-2/posts/06-double-faults/index.ja.md",
"chars": 19867,
"preview": "+++\ntitle = \"Double Faults\"\nweight = 6\npath = \"ja/double-fault-exceptions\"\ndate = 2018-06-18\n\n[extra]\n# Please update t"
},
{
"path": "blog/content/edition-2/posts/06-double-faults/index.ko.md",
"chars": 22318,
"preview": "+++\ntitle = \"더블 폴트 (Double Fault)\"\nweight = 6\npath = \"ko/double-fault-exceptions\"\ndate = 2018-06-18\n\n[extra]\n# Please u"
},
{
"path": "blog/content/edition-2/posts/06-double-faults/index.md",
"chars": 28870,
"preview": "+++\ntitle = \"Double Faults\"\nweight = 6\npath = \"double-fault-exceptions\"\ndate = 2018-06-18\n\n[extra]\nchapter = \"Interrupt"
},
{
"path": "blog/content/edition-2/posts/06-double-faults/index.pt-BR.md",
"chars": 30564,
"preview": "+++\ntitle = \"Double Faults\"\nweight = 6\npath = \"pt-BR/double-fault-exceptions\"\ndate = 2018-06-18\n\n[extra]\nchapter = \"Inte"
},
{
"path": "blog/content/edition-2/posts/06-double-faults/index.zh-CN.md",
"chars": 18710,
"preview": "+++\ntitle = \"Double Faults\"\nweight = 6\npath = \"zh-CN/double-fault-exceptions\"\ndate = 2018-06-18\n\n[extra]\n# Please updat"
},
{
"path": "blog/content/edition-2/posts/07-hardware-interrupts/index.es.md",
"chars": 37906,
"preview": "+++\ntitle = \"Interrupciones de Hardware\"\nweight = 7\npath = \"es/hardware-interrupts\"\ndate = 2018-10-22\n\n[extra]\nchapter ="
},
{
"path": "blog/content/edition-2/posts/07-hardware-interrupts/index.fa.md",
"chars": 34320,
"preview": "+++\ntitle = \"وقفههای سختافزاری\"\nweight = 7\npath = \"fa/hardware-interrupts\"\ndate = 2018-10-22\n\n[extra]\n# Please update "
},
{
"path": "blog/content/edition-2/posts/07-hardware-interrupts/index.ja.md",
"chars": 26154,
"preview": "+++\ntitle = \"ハードウェア割り込み\"\nweight = 7\npath = \"ja/hardware-interrupts\"\ndate = 2018-10-22\n\n[extra]\n# Please update this when"
},
{
"path": "blog/content/edition-2/posts/07-hardware-interrupts/index.ko.md",
"chars": 27527,
"preview": "+++\ntitle = \"하드웨어 인터럽트\"\nweight = 7\npath = \"ko/hardware-interrupts\"\ndate = 2018-10-22\n\n[extra]\n# Please update this when "
},
{
"path": "blog/content/edition-2/posts/07-hardware-interrupts/index.md",
"chars": 34307,
"preview": "+++\ntitle = \"Hardware Interrupts\"\nweight = 7\npath = \"hardware-interrupts\"\ndate = 2018-10-22\n\n[extra]\nchapter = \"Interrup"
},
{
"path": "blog/content/edition-2/posts/07-hardware-interrupts/index.pt-BR.md",
"chars": 35979,
"preview": "+++\ntitle = \"Interrupções de Hardware\"\nweight = 7\npath = \"pt-BR/hardware-interrupts\"\ndate = 2018-10-22\n\n[extra]\nchapter "
},
{
"path": "blog/content/edition-2/posts/07-hardware-interrupts/index.zh-CN.md",
"chars": 22377,
"preview": "+++\ntitle = \"硬件中断\"\nweight = 7\npath = \"zh-CN/hardware-interrupts\"\ndate = 2018-10-22\n\n[extra]\n# Please update this when up"
},
{
"path": "blog/content/edition-2/posts/08-paging-introduction/index.es.md",
"chars": 40771,
"preview": "+++\ntitle = \"Introducción a la Paginación\"\nweight = 8\npath = \"es/paging-introduction\"\ndate = 2019-01-14\n\n[extra]\nchapter"
},
{
"path": "blog/content/edition-2/posts/08-paging-introduction/index.fa.md",
"chars": 33942,
"preview": "+++\ntitle = \"مقدمهای بر صفحهبندی\"\nweight = 8\npath = \"fa/paging-introduction\"\ndate = 2019-01-14\n\n[extra]\n# Please updat"
},
{
"path": "blog/content/edition-2/posts/08-paging-introduction/index.ja.md",
"chars": 24347,
"preview": "+++\ntitle = \"ページング入門\"\nweight = 8\npath = \"ja/paging-introduction\"\ndate = 2019-01-14\n\n[extra]\n# Please update this when up"
},
{
"path": "blog/content/edition-2/posts/08-paging-introduction/index.md",
"chars": 34701,
"preview": "+++\ntitle = \"Introduction to Paging\"\nweight = 8\npath = \"paging-introduction\"\ndate = 2019-01-14\n\n[extra]\nchapter = \"Memor"
},
{
"path": "blog/content/edition-2/posts/08-paging-introduction/index.pt-BR.md",
"chars": 37156,
"preview": "+++\ntitle = \"Introdução à Paginação\"\nweight = 8\npath = \"pt-BR/paging-introduction\"\ndate = 2019-01-14\n\n[extra]\nchapter = "
},
{
"path": "blog/content/edition-2/posts/08-paging-introduction/index.zh-CN.md",
"chars": 18567,
"preview": "+++\ntitle = \"内存分页初探\"\nweight = 8\npath = \"zh-CN/paging-introduction\"\ndate = 2019-01-14\n\n[extra]\n# Please update this when "
},
{
"path": "blog/content/edition-2/posts/09-paging-implementation/index.es.md",
"chars": 70046,
"preview": "+++\ntitle = \"Implementación de Paginación\"\nweight = 9\npath = \"es/implementacion-de-paginacion\"\ndate = 2019-03-14\n\n[extra"
},
{
"path": "blog/content/edition-2/posts/09-paging-implementation/index.ja.md",
"chars": 47080,
"preview": "+++\ntitle = \"ページングの実装\"\nweight = 9\npath = \"ja/paging-implementation\"\ndate = 2019-03-14\n\n[extra]\ntranslation_based_on_comm"
},
{
"path": "blog/content/edition-2/posts/09-paging-implementation/index.md",
"chars": 68679,
"preview": "+++\ntitle = \"Paging Implementation\"\nweight = 9\npath = \"paging-implementation\"\ndate = 2019-03-14\n\n[extra]\nchapter = \"Memo"
},
{
"path": "blog/content/edition-2/posts/09-paging-implementation/index.pt-BR.md",
"chars": 72200,
"preview": "+++\ntitle = \"Implementação de Paginação\"\nweight = 9\npath = \"pt-BR/paging-implementation\"\ndate = 2019-03-14\n\n[extra]\nchap"
},
{
"path": "blog/content/edition-2/posts/09-paging-implementation/index.zh-CN.md",
"chars": 37235,
"preview": "+++\ntitle = \"分页实现\"\nweight = 9\npath = \"zh-CN/paging-implementation\"\ndate = 2019-03-14\n\n[extra]\n# Please update this when "
},
{
"path": "blog/content/edition-2/posts/10-heap-allocation/index.es.md",
"chars": 49395,
"preview": "+++\ntitle = \"Asignación en el Heap\"\nweight = 10\npath = \"es/heap-allocation\"\ndate = 2019-06-26\n\n[extra]\nchapter = \"Gestió"
},
{
"path": "blog/content/edition-2/posts/10-heap-allocation/index.ja.md",
"chars": 35138,
"preview": "+++\ntitle = \"ヒープ割り当て\"\nweight = 10\npath = \"ja/heap-allocation\"\ndate = 2019-06-26\n\n[extra]\n# Please update this when updat"
},
{
"path": "blog/content/edition-2/posts/10-heap-allocation/index.md",
"chars": 45942,
"preview": "+++\ntitle = \"Heap Allocation\"\nweight = 10\npath = \"heap-allocation\"\ndate = 2019-06-26\n\n[extra]\nchapter = \"Memory Manageme"
},
{
"path": "blog/content/edition-2/posts/10-heap-allocation/index.pt-BR.md",
"chars": 47842,
"preview": "+++\ntitle = \"Alocação no Heap\"\nweight = 10\npath = \"pt-BR/heap-allocation\"\ndate = 2019-06-26\n\n[extra]\nchapter = \"Gerencia"
},
{
"path": "blog/content/edition-2/posts/10-heap-allocation/index.zh-CN.md",
"chars": 26566,
"preview": "+++\ntitle = \"堆分配\"\nweight = 10\npath = \"zh-CN/heap-allocation\"\ndate = 2019-06-26\n\n[extra]\nchapter = \"Memory Management\"\n# "
},
{
"path": "blog/content/edition-2/posts/11-allocator-designs/index.es.md",
"chars": 70821,
"preview": "+++\ntitle = \"Diseños de Allocadores\"\nweight = 11\npath = \"es/allocator-designs\"\ndate = 2020-01-20\n\n[extra]\nchapter = \"Ges"
},
{
"path": "blog/content/edition-2/posts/11-allocator-designs/index.ja.md",
"chars": 51860,
"preview": "+++\ntitle = \"アロケータの設計\"\nweight = 11\npath = \"allocator-designs/ja\"\ndate = 2020-01-20\n\n[extra]\n# Please update this when up"
},
{
"path": "blog/content/edition-2/posts/11-allocator-designs/index.md",
"chars": 77419,
"preview": "+++\ntitle = \"Allocator Designs\"\nweight = 11\npath = \"allocator-designs\"\ndate = 2020-01-20\n\n[extra]\nchapter = \"Memory Mana"
},
{
"path": "blog/content/edition-2/posts/11-allocator-designs/index.pt-BR.md",
"chars": 80965,
"preview": "+++\ntitle = \"Designs de Alocadores\"\nweight = 11\npath = \"pt-BR/allocator-designs\"\ndate = 2020-01-20\n\n[extra]\nchapter = \"G"
},
{
"path": "blog/content/edition-2/posts/11-allocator-designs/index.ru.md",
"chars": 81333,
"preview": "+++\ntitle = \"Архитектуры Аллокаторов\"\nweight = 11\npath = \"ru/allocator-designs\"\ndate = 2020-01-20\n\n[extra]\nchapter = \"Me"
},
{
"path": "blog/content/edition-2/posts/11-allocator-designs/index.zh-CN.md",
"chars": 44500,
"preview": "+++\ntitle = \"分配器设计\"\nweight = 11\npath = \"zh-CN/allocator-designs\"\ndate = 2020-01-20\n\n[extra]\nchapter = \"Memory Management"
},
{
"path": "blog/content/edition-2/posts/12-async-await/index.es.md",
"chars": 70204,
"preview": "+++\ntitle = \"Async/Aait\"\nweight = 12\npath = \"es/async-await\"\ndate = 2020-03-27\n\n[extra]\nchapter = \"Multitasking\"\n\n# GitH"
},
{
"path": "blog/content/edition-2/posts/12-async-await/index.ja.md",
"chars": 80443,
"preview": "+++\ntitle = \"Async/Await\"\nweight = 12\npath = \"ja/async-await\"\ndate = 2020-03-27\n\n[extra]\n# Please update this when updat"
},
{
"path": "blog/content/edition-2/posts/12-async-await/index.md",
"chars": 118251,
"preview": "+++\ntitle = \"Async/Await\"\nweight = 12\npath = \"async-await\"\ndate = 2020-03-27\n\n[extra]\nchapter = \"Multitasking\"\n+++\n\nIn t"
},
{
"path": "blog/content/edition-2/posts/12-async-await/index.pt-BR.md",
"chars": 122912,
"preview": "+++\ntitle = \"Async/Await\"\nweight = 12\npath = \"pt-BR/async-await\"\ndate = 2020-03-27\n\n[extra]\nchapter = \"Multitasking\"\n# P"
},
{
"path": "blog/content/edition-2/posts/12-async-await/index.ru.md",
"chars": 122491,
"preview": "+++\ntitle = \"Async/Await\"\nweight = 12\npath = \"ru/async-await\"\ndate = 2020-03-27\n\n[extra]\nchapter = \"Multitasking\"\n# Plea"
},
{
"path": "blog/content/edition-2/posts/12-async-await/index.zh-CN.md",
"chars": 68188,
"preview": "+++\ntitle = \"Async/Await\"\nweight = 12\npath = \"zh-CN/async-await\"\ndate = 2020-03-27\n\n[extra]\nchapter = \"Multitasking\"\n\n# "
},
{
"path": "blog/content/edition-2/posts/12-async-await/index.zh-TW.md",
"chars": 69481,
"preview": "+++\ntitle = \"Async/Await\"\nweight = 12\npath = \"zh-TW/async-await\"\ndate = 2020-03-27\n\n[extra]\ntranslators = [\"ssrlive\"]\n++"
},
{
"path": "blog/content/edition-2/posts/12-async-await/scancode-queue.drawio",
"chars": 1542,
"preview": "<mxfile host=\"Electron\" modified=\"2020-03-20T10:35:53.357Z\" agent=\"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (K"
},
{
"path": "blog/content/edition-2/posts/_index.ar.md",
"chars": 125,
"preview": "+++\ntitle = \"Posts\"\nsort_by = \"weight\"\ninsert_anchor_links = \"left\"\nrender = false\npage_template = \"edition-2/page.html\""
},
{
"path": "blog/content/edition-2/posts/_index.es.md",
"chars": 125,
"preview": "+++\ntitle = \"Posts\"\nsort_by = \"weight\"\ninsert_anchor_links = \"left\"\nrender = false\npage_template = \"edition-2/page.html\""
},
{
"path": "blog/content/edition-2/posts/_index.fa.md",
"chars": 125,
"preview": "+++\ntitle = \"Posts\"\nsort_by = \"weight\"\ninsert_anchor_links = \"left\"\nrender = false\npage_template = \"edition-2/page.html\""
},
{
"path": "blog/content/edition-2/posts/_index.fr.md",
"chars": 125,
"preview": "+++\ntitle = \"Posts\"\nsort_by = \"weight\"\ninsert_anchor_links = \"left\"\nrender = false\npage_template = \"edition-2/page.html\""
},
{
"path": "blog/content/edition-2/posts/_index.ja.md",
"chars": 125,
"preview": "+++\ntitle = \"Posts\"\nsort_by = \"weight\"\ninsert_anchor_links = \"left\"\nrender = false\npage_template = \"edition-2/page.html\""
},
{
"path": "blog/content/edition-2/posts/_index.ko.md",
"chars": 125,
"preview": "+++\ntitle = \"Posts\"\nsort_by = \"weight\"\ninsert_anchor_links = \"left\"\nrender = false\npage_template = \"edition-2/page.html\""
},
{
"path": "blog/content/edition-2/posts/_index.md",
"chars": 125,
"preview": "+++\ntitle = \"Posts\"\nsort_by = \"weight\"\ninsert_anchor_links = \"left\"\nrender = false\npage_template = \"edition-2/page.html\""
},
{
"path": "blog/content/edition-2/posts/_index.pt-BR.md",
"chars": 125,
"preview": "+++\ntitle = \"Posts\"\nsort_by = \"weight\"\ninsert_anchor_links = \"left\"\nrender = false\npage_template = \"edition-2/page.html\""
},
{
"path": "blog/content/edition-2/posts/_index.ru.md",
"chars": 125,
"preview": "+++\ntitle = \"Posts\"\nsort_by = \"weight\"\ninsert_anchor_links = \"left\"\nrender = false\npage_template = \"edition-2/page.html\""
},
{
"path": "blog/content/edition-2/posts/_index.zh-CN.md",
"chars": 125,
"preview": "+++\ntitle = \"Posts\"\nsort_by = \"weight\"\ninsert_anchor_links = \"left\"\nrender = false\npage_template = \"edition-2/page.html\""
},
{
"path": "blog/content/edition-2/posts/_index.zh-TW.md",
"chars": 125,
"preview": "+++\ntitle = \"Posts\"\nsort_by = \"weight\"\ninsert_anchor_links = \"left\"\nrender = false\npage_template = \"edition-2/page.html\""
},
{
"path": "blog/content/edition-2/posts/deprecated/04-unit-testing/index.md",
"chars": 17886,
"preview": "+++\ntitle = \"Unit Testing\"\nweight = 4\npath = \"unit-testing\"\ndate = 2018-04-29\n\n[extra]\nwarning_short = \"Deprecated: \"\nw"
},
{
"path": "blog/content/edition-2/posts/deprecated/05-integration-tests/index.md",
"chars": 24269,
"preview": "+++\ntitle = \"Integration Tests\"\nweight = 5\npath = \"integration-tests\"\ndate = 2018-06-15\n\n[extra]\nwarning_short = \"Depre"
},
{
"path": "blog/content/edition-2/posts/deprecated/10-advanced-paging/index.md",
"chars": 45981,
"preview": "+++\ntitle = \"Advanced Paging\"\nweight = 10\npath = \"advanced-paging\"\ndate = 2019-01-28\n\n[extra]\nwarning_short = \"Deprecate"
},
{
"path": "blog/content/edition-2/posts/deprecated/_index.md",
"chars": 98,
"preview": "+++\ntitle = \"Deprecated Posts\"\nsort_by = \"weight\"\ninsert_anchor_links = \"left\"\nrender = false\n+++\n"
},
{
"path": "blog/content/news/2018-03-09-pure-rust.md",
"chars": 4733,
"preview": "+++\ntitle = \"Writing an OS in pure Rust\"\ndate = 2018-03-09\naliases = [\"news/2018-03-09-pure-rust\"]\n+++\n\nOver the past si"
},
{
"path": "blog/content/news/_index.md",
"chars": 104,
"preview": "+++\ntitle = \"News\"\ntemplate = \"news-section.html\"\npage_template = \"news-page.html\"\nsort_by = \"date\"\n+++\n"
},
{
"path": "blog/content/pages/_index.md",
"chars": 39,
"preview": "+++\ntitle = \"Pages\"\nrender = false\n+++\n"
},
{
"path": "blog/content/pages/contact.md",
"chars": 181,
"preview": "+++\ntitle = \"Contact\"\npath = \"contact\"\ntemplate = \"plain.html\"\n+++\n\nPhilipp Oppermann\n\n<big>contact@phil-opp.com</big>\n\n"
},
{
"path": "blog/content/status-update/2019-05-01.md",
"chars": 5907,
"preview": "+++\ntitle = \"Updates in April 2019\"\ndate = 2019-05-01\n+++\n\nLot's of things changed in the _Writing an OS in Rust_ series"
},
{
"path": "blog/content/status-update/2019-06-03.md",
"chars": 4076,
"preview": "+++\ntitle = \"Updates in May 2019\"\ndate = 2019-06-03\n+++\n\nThis post gives an overview of the recent updates to the _Writi"
},
{
"path": "blog/content/status-update/2019-07-06.md",
"chars": 2048,
"preview": "+++\ntitle = \"Updates in June 2019\"\ndate = 2019-07-06\naliases = [\"status-update/2019-06-04/index.html\"]\n+++\n\nThis post gi"
},
{
"path": "blog/content/status-update/2019-08-02.md",
"chars": 3279,
"preview": "+++\ntitle = \"Updates in July 2019\"\ndate = 2019-08-02\n+++\n\nThis post gives an overview of the recent updates to the _Writ"
},
{
"path": "blog/content/status-update/2019-09-09.md",
"chars": 3254,
"preview": "+++\ntitle = \"Updates in August 2019\"\ndate = 2019-09-09\n+++\n\nThis post gives an overview of the recent updates to the _Wr"
},
{
"path": "blog/content/status-update/2019-10-06.md",
"chars": 2218,
"preview": "+++\ntitle = \"Updates in September 2019\"\ndate = 2019-10-06\n+++\n\nThis post gives an overview of the recent updates to the "
},
{
"path": "blog/content/status-update/2019-12-02.md",
"chars": 2835,
"preview": "+++\ntitle = \"Updates in October and November 2019\"\ndate = 2019-12-02\n+++\n\nThis post gives an overview of the recent upda"
},
{
"path": "blog/content/status-update/2020-01-07.md",
"chars": 3523,
"preview": "+++\ntitle = \"Updates in December 2019\"\ndate = 2020-01-07\n+++\n\nHappy New Year!\n\nThis post gives an overview of the recent"
},
{
"path": "blog/content/status-update/2020-02-01.md",
"chars": 3907,
"preview": "+++\ntitle = \"Updates in January 2020\"\ndate = 2020-02-01\n+++\n\nThis post gives an overview of the recent updates to the _W"
},
{
"path": "blog/content/status-update/2020-03-02.md",
"chars": 6940,
"preview": "+++\ntitle = \"Updates in February 2020\"\ndate = 2020-03-02\n+++\n\nThis post gives an overview of the recent updates to the _"
},
{
"path": "blog/content/status-update/2020-04-01/index.md",
"chars": 3706,
"preview": "+++\ntitle = \"Updates in March 2020\"\ndate = 2020-04-01\n+++\n\nThis post gives an overview of the recent updates to the _Wri"
},
{
"path": "blog/content/status-update/_index.md",
"chars": 273,
"preview": "+++\ntitle = \"Status Updates\"\ntemplate = \"status-update-section.html\"\npage_template = \"status-update-page.html\"\nsort_by ="
},
{
"path": "blog/requirements.txt",
"chars": 9,
"preview": "PyGithub\n"
},
{
"path": "blog/sass/css/edition-2/main.scss",
"chars": 19960,
"preview": "/*\n * CSS file for the second edition of os.phil-opp.com. \n *\n * Based on `poole`which was designed, built, and release"
},
{
"path": "blog/static/CNAME",
"chars": 15,
"preview": "os.phil-opp.com"
},
{
"path": "blog/static/atom.xml/index.html",
"chars": 243,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <link rel=\"canonical\" href=\"/rss.xml\" />\n <meta http-equiv=\"content-typ"
},
{
"path": "blog/static/css/edition-1/isso.css",
"chars": 3287,
"preview": "#isso-thread * {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n#iss"
},
{
"path": "blog/static/css/edition-1/main.css",
"chars": 6255,
"preview": "h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {\n padding: 0;\n color: #a0565c;\n font-size: 95%;\n background-co"
},
{
"path": "blog/static/css/edition-1/poole.css",
"chars": 6429,
"preview": "/*\n * ___\n * /\\_ \\\n * _____ ___ ___\\//\\ \\ __\n * /\\ '__`\\ / _"
},
{
"path": "blog/static/handling-exceptions-with-naked-fns.html",
"chars": 295,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <link rel=\"canonical\" href=\"/edition-1/extra/naked-exceptions/\" />\n <me"
},
{
"path": "blog/static/js/edition-1/main.js",
"chars": 2170,
"preview": "window.onload = function() {\n show_lang_selector();\n\n var container = document.querySelector('#toc-aside');\n\n if (con"
},
{
"path": "blog/static/js/edition-2/main.js",
"chars": 2693,
"preview": "window.onload = function () {\n let container = document.querySelector('#toc-aside');\n if (container != null) {\n res"
},
{
"path": "blog/templates/404.html",
"chars": 412,
"preview": "{% extends \"base.html\" %}\n\n{% block title %}Page not found | {{ config.title }}{% endblock title %}\n\n{% block main %}\n "
},
{
"path": "blog/templates/auto/forks.html",
"chars": 0,
"preview": ""
},
{
"path": "blog/templates/auto/recent-updates.html",
"chars": 0,
"preview": ""
},
{
"path": "blog/templates/auto/stars.html",
"chars": 0,
"preview": ""
},
{
"path": "blog/templates/auto/status-updates-truncated.html",
"chars": 0,
"preview": ""
},
{
"path": "blog/templates/auto/status-updates.html",
"chars": 0,
"preview": ""
},
{
"path": "blog/templates/base.html",
"chars": 36,
"preview": "{% extends \"edition-2/base.html\" %}\n"
},
{
"path": "blog/templates/edition-1/base.html",
"chars": 1433,
"preview": "<!doctype html>\n\n<html lang=\"{{ lang }}\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"apple-mobile-web-app-capabl"
},
{
"path": "blog/templates/edition-1/comments/allocating-frames.html",
"chars": 61543,
"preview": "{% raw %}\n<div id=\"isso-root\"><div id=\"isso-32\" class=\"isso-comment\"><div class=\"avatar\"><svg version=\"1.1\" viewBox=\"0 0"
},
{
"path": "blog/templates/edition-1/comments/better-exception-messages.html",
"chars": 38970,
"preview": "{% raw %}\n<div id=\"isso-root\"><div id=\"isso-204\" class=\"isso-comment isso-no-votes\"><div class=\"avatar\"><svg version=\"1."
},
{
"path": "blog/templates/edition-1/comments/catching-exceptions.html",
"chars": 43485,
"preview": "{% raw %}\n<div id=\"isso-root\"><div id=\"isso-190\" class=\"isso-comment isso-no-votes\"><div class=\"avatar\"><svg version=\"1."
},
{
"path": "blog/templates/edition-1/comments/double-faults.html",
"chars": 74647,
"preview": "{% raw %}\n<div id=\"isso-root\"><div id=\"isso-270\" class=\"isso-comment\"><div class=\"avatar\"><svg version=\"1.1\" viewBox=\"0 "
},
{
"path": "blog/templates/edition-1/comments/entering-longmode.html",
"chars": 223043,
"preview": "{% raw %}\n<div id=\"isso-root\"><div id=\"isso-227\" class=\"isso-comment isso-no-votes\"><div class=\"avatar\"><svg version=\"1."
},
{
"path": "blog/templates/edition-1/comments/handling-exceptions.html",
"chars": 15449,
"preview": "{% raw %}\n<div id=\"isso-root\"><div id=\"isso-276\" class=\"isso-comment isso-no-votes\"><div class=\"avatar\"><svg version=\"1."
}
]
// ... and 40 more files (download for full content)
About this extraction
This page contains the full source code of the phil-opp/blog_os GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 240 files (5.1 MB), approximately 1.4M tokens, and a symbol index with 11 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.