アセンブラを再実装してOS自作入門の4日目に到達
nantonaku-shiawase.hatenablog.com
以前進捗報告したのが2016-06-09、それから5ヶ月ぐらい経っているわけですが、ようやく3日目が終了しました。長かった。
これまでの流れ
- harib00iに出てくるアセンブリの実装に手間取る → harib00iの実装 · Issue #4 · HariboteLikeHobbyOS/opennask · GitHub
- harib00jに出てくるPEヘッダの出力に手間取る → PE(Portable Executable)ヘッダの出力 · Issue #12 · HariboteLikeHobbyOS/opennask · GitHub
- CMakeでqemuを実行するスクリプトを準備しまくる → Mocking cyberbird by Hiroyuki-Nagata · Pull Request #8 · HariboteLikeHobbyOS/opennask · GitHub
- 先人のサイトを見つけて、リンカスクリプトを使えばショートカットできることに気づく ← 今
2016/11/23時点では、書籍の3日目までのアセンブラ(harib00jまで)はバイナリに変換できるようになった。
使用できるオペコード(部分的に実装しているものも含む)は以下の通り:
コンパイル〜img書き込みまでの概要
ここまででようやくC言語をリンクしながら、naskのスクリプトをアセンブルしてimgに書き込みという一連の動作が完成しました。以下がオリジナルのosaskと、私が先人のページを見ながらCMakeで動かす動作の比較です。
図からわかるように、まずオリジナルのosaskは様々な独自ツール、独自フォーマットを使っています。
それらは以下のように代用できることがこれまでの先人の知恵によってわかっています。
edimg
- GNU謹製のmtoolで代用できます
コンパイル〜img書き込みまでのコマンド
私のopennaskを使うと、imgファイル作成までコマンドで7工程です
$ opennask ipl10.nas ipl.bin $ opennask asmhead.nas asmhead.bin $ opennask naskfunc.nas naskfunc.o $ gcc -m32 -nostdlib -Wl,--oformat=binary bootpack.c naskfunc.o -T os.lds -o boot.bin $ cat asmhead.bin boot.bin > haribote.sys $ mformat -f 1440 -l HARIBOTEOS -N 0xffffffff -C -B ipl.bin -i haribote.sys $ mcopy -i haribote.img haribote.sys
感謝
以下の3サイトに非常にお世話になりました
x86 汎用命令 - ModR/M の解説
ModR/Mについては前もちょっと記事を書いたけど、まだちょっと中途半端だったのでもう一度まとめます。
nantonaku-shiawase.hatenablog.com
ModR/Mの実際の例
例えば以下のような命令がある、 /r の意図がみなさんわからないのではないでしょうか?
; IMUL r32, imm32 0x69 /r iw
また、次のような命令、 /7 の意図がみなさんわからないのではないでしょうか?
; CMP r/m8, imm8 0x80 /7 ib
ModR/Mの構造
全体の概要についてはこのページをみたほうがよい
また、このページも役立つ
'''/r'''および'''/7'''の意味
解説を Assembly Programming on x86-64 Linux (06) から引用する
この記事で注目している項目を赤字で表現。
記法 | 解説 |
---|---|
/0 - /7 | ModR/M バイト の reg フィールドの 0 から 7 の数字はオペコードの拡張用に使われる。r/m フィールドだけをオペランドに使用する。 |
/r | 命令には ModR/M バイトが続き、レジスタオペランドと r/m オペランドの両方を使う。 |
cb、cw、cd、cp, co, ct | オペコードの後に 1 バイト(cb)、2 バイト(cw)、4 バイト(cd)、6 バイト(cp)、 8 バイト(co)または 10 バイト(ct)が続く。 |
ib, iw, id, io | オペコード、ModM/R バイト、または SIB の後に続く 1 バイト(ib)、2 バイト(iw)、4 バイト(id)または 8 バイト(io)の定数(即値)。 |
+rb, +rw, +rd, +ro | + の左側のオペコードに加算される 0 から 7 までのレジスタコード。 結果として 1 バイトのオペコードとなる。 |
reg(/0 - /7指定の場合)
- regは、/0 - /7を指定された場合はそれをそのまま使う
もちろんこれは10進数表記なので、2進数に直すと以下のようになる。ModR/Mバイトのregフィールドはこれで埋まるわけだ。
/0 - /7 | 2進数表記 |
---|---|
/0 | 000 |
/1 | 001 |
/2 | 010 |
/3 | 011 |
/4 | 100 |
/5 | 101 |
/6 | 110 |
/7 | 111 |
reg(/0 - /7指定の場合)の実例
- 0x80 /7 ib をアセンブル
; 0x80 /7 ib CMP r/m8, imm8 ; 例 CMP CL,18 ; オペコードは0x80で確定 ; --------------------- ; ModR/Mの値は ; [mod] 11 ; [reg] 111 ; [r/m] 001 ; => "11111001" ; => 0xf9 ; --------------------- ; 18は16進数で0x12 ; --------------------- ; よって、以下がアセンブルされると CMP CL,18 ; 以下のバイナリが出力される 0x80, 0xf9, 0x12
reg('''/r'''指定の場合)
- regは、レジスタコードを指定する
つまり、mod2bitに続いて、regとr/mが3bitずつ続くことになる
reg(/r指定の場合)の実例
- '''0x69 /r iw''' をアセンブル
; 0x69 /r iw IMUL r32, imm32 ; 例 IMUL ECX,4608 ; オペコードは0x69で確定 ; --------------------- ; ModR/Mの値は ; [mod] 11 ; [reg] 001 ; [r/m] 001 ; => "11001001" ; => 0xc9 ; --------------------- ; 4608は16進数で0x1200 ; リトルエンディアンのため、 0x00, 0x12と並ぶ ; --------------------- ; よって、以下がアセンブルされると IMUL ECX,4608 ; 以下のバイナリが出力される 0x69, 0xc9, 0x00, 0x12
/r指定の場合のレジスタの細かい指定
さて、実はここまでregとr/mのどちらがどちらのレジスタを指定しているのか気になった人がいるかもしれない。その指摘はめっちゃ正しい。
ここのサイトに答えが書いてある。
The d bit in the opcode determines which operand is the source, and which is the destination:
d=0: MOD R/M <- REG, REG is the source
d=1: REG <- MOD R/M, REG is the destination
"d"bitというのは、1byte(8bit)のデータを先頭から数えて4番目のbitである。*1
つまり、"d"bitが0か1かによってmodとr/mの3bitをそれぞれ入れ替えなければいけない。
C++の疑似コード、std::bitsetを使うとやりやすい
uint8_t op = 0x8e; std::bitset<8> bsl(op); // オペコード if (bsl[3]) { // d=0 or 1 // d=1: REG <- MOD R/M, REG is the destination } else { // d=0: MOD R/M <- REG, REG is the source }
はじめて読む486、ModRMとかに関してはほとんど役に立たず…
- 作者: 蒲地輝尚
- 出版社/メーカー: アスキー
- 発売日: 1994/09
- メディア: 単行本
- 購入: 20人 クリック: 165回
- この商品を含むブログ (84件) を見る
*1:わかるか…!こんな細かい仕様…!
Let's Encryptを手動更新
だいたいここの指示にしたがえばよし
こんなエラーが出た。
Cleaning up challenges Attempting to renew cert from /etc/letsencrypt/renewal/freestylewiki.xyz.conf produced an unexpected error: At least one of the required ports is already taken.. Skipping.
httpdを一回停止させればOK
# systemctl stop httpd
成功時のログ
* Pythonのスクリプトがいろいろやってる(秘密鍵更新+CSR作成+証明書作成(中間証明書も))
[root@freestylewiki letsencrypt]# ./certbot-auto renew --force-renew Saving debug log to /var/log/letsencrypt/letsencrypt.log ------------------------------------------------------------------------------- Processing /etc/letsencrypt/renewal/freestylewiki.xyz.conf ------------------------------------------------------------------------------- Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org Renewing an existing certificate Performing the following challenges: tls-sni-01 challenge for freestylewiki.xyz Waiting for verification... Cleaning up challenges Generating key (2048 bits): /etc/letsencrypt/keys/0001_key-certbot.pem Creating CSR: /etc/letsencrypt/csr/0001_csr-certbot.pem ------------------------------------------------------------------------------- new certificate deployed without reload, fullchain is /etc/letsencrypt/live/freestylewiki.xyz/fullchain.pem ------------------------------------------------------------------------------- Congratulations, all renewals succeeded. The following certs have been renewed: /etc/letsencrypt/live/freestylewiki.xyz/fullchain.pem (success)