「はじめて読む486」を読んだ
tcfmのruiさんが勧めているのを見て買いましたが、
#tcfm 7みたいにメモリとかカーネルモードとかよくわからないという人には「はじめて読む486」が結構おすすめかも。相当古い本だけどここらへんはほとんど変わっていないし、読みやすくて今でも入門にとてもよい本だと思うなぁ。 https://t.co/N9gQoKJjXw
— Rui Ueyama (@rui314) 2018年2月28日
結果的に30日OS自作本*1の良い副読本になりました。
紙の本は500ページ近くあります。電子版を買って正解だと思います。 私が買ったのはKindle版ですが、本当は達人出版会から出てるDRM FREEのePubを買うべきでした。
32ビットコンピュータをやさしく語る はじめて読む486 (アスキー書籍)
- 作者: 蒲地輝尚
- 出版社/メーカー: KADOKAWA / アスキー・メディアワークス
- 発売日: 2014/10/21
- メディア: Kindle版
- この商品を含むブログを見る
古いのか?
この本を買おうかどうかで迷っている人が心配している点は
- 486という古いCPUの知識なんか役に立つのか?
- 20年前の本なので内容は流石に風化しているのではないか
という点でしょう。
また、この本の出版は1994年の本です。windows3.1が出始めて16bit -> 32bitの移行が進んでいる時期?だと思われます。 私が最初に触った記憶があるOSであるWindows95(1995)はまだ出現していません。
達人出版会の日付によると、電子版は2014年に発売したようです。 なんと初版発売から20年後です。 電子書籍についてよく調べられている方はご存知だと思いますが、古い本でリフローなePub形式のものを 販売しているものはほとんどありません
ここまで気合を入れて電子化している古い本は少ないと思います。 内容について、最新のCPU情報に詳しくないため、私が内容が古いかどうかの判断はできませんが、 電子書籍化している点から、記載内容の妥当性/有効性はまだまだ通用するぞ、という自信を感じます。
内容は古くないと思います!(多分)
30日OS自作本と比較して
- 例外
- プロテクトモード
- コールゲート
などはこちらのほうが情報が充実しておりわかりやすかったです。 またページングはOS自作本では一切出てこないので精読してこんな記事を書きました。
同じようなことが買いてる部分もありましたが、別の視点からの説明を読み取ったり、 一方で間違えて覚えたものの誤解や不理解を他方で学び直すことで理解を深めることができました。
30日OS自作本との愛称はバッチリだと思います。 とにかく、この本と30日OS自作本で低レイヤーに対する基礎体力みたいなものはかなり身についたと思います。
- 作者: 川合秀実
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2006/03/01
- メディア: 単行本
- 購入: 36人 クリック: 735回
- この商品を含むブログ (299件) を見る
本書中に書いてある面白いエピソード
クロックダブラー、クロックトリプラー
クロックの速度を2倍、3倍に加速させる装置がある メモリ等などのIOが発生せず、CPU内で完結させられる処理のみの場合、 この機能を使えます。
- 486Dx2/ODP:クロックダブラー 33MHz -> 66MHz
- DX4:クロックトリプラー 25MHz -> 75MHz
通常の3倍ってやつですね。赤いあれ(若い人がわからないやつ)
CPUの互換性はマルチタスク中にも利用できる
486には8086用の実行バイナリを動かす互換機能がありますので、 それを使って486で8086用の実行バイナリが動くのは、なんとなく想像できますが、
- 486用のコードと8086のコードをマルチタスクで並行して動作させることが可能
こんな機構があるのは想像できませんでした。
この機能を実現させるために、 486ではマルチタスクの際にレジスタ退避に利用するセグメントの中身に、 CPUの互換モードに関するフラグが含めています。(EFLAGSの17bit目のVMフラグ)
タスク切り替えの際にこのFlagを使ってCPU互換モードを切り替えつつ平行稼働できるようです。
A20制御回路:アドレスバスのオーバーフローを利用した古いコードをサポートするハードウェアがある
メモリバスが20bitであることを逆手に取った(オーバーフロー)プログラムが過去にあったそうです。
20bitを超えるようなアドレスを指定して、オーバーフローの結果出来上がる小さなアドレスを利用し、 先頭近くのアドレス、主にOSがシステム的に使っているメモリに不正に?アクセスするようなものだそうです。
本来ならセキュアな範囲のアドレスにアクセスするためには 所定の手続き(OSに代理でアクセスさせる命令を使う、権限を切り替える等) が必要になるとおもいますが、メモリバスを通す前はオーバーフロー前のアドレスで検査されるため この方法でそれを省略しています。hackな手法だとおもいますが、 個人的にセキュリティ的にもこのようなアドレスバスの利用は好ましくないものと思います。
メモリバスが20bitから23bitに拡張された際にこのオーバーフローを利用したプログラムは動かなくなりました。
そんな不届きな?プログラムの動作の互換性をサポートする仕組みが486に存在しているのです。それがA20制御回路です。
そもそもの仕様を逆手に取ったコードをサポートする必要があるのか、など思うところはありましたが、 CPUベンダがサポートを決断した、ということはサポートをせざるを得ない重要なプログラムが使っていたのかなと思います。
このA20回路のオーバーフローを利用したプログラムにはどんなものがあったのでしょうか。すごく気になります。
*1:同じintel86系なので
30日OS自作本29,30日目
30日OS自作本29,30日目
この2章はOSの改造はバグ対応や一部のサイズ縮小のための些細な回収のみで 基本的に今まで作ってきたOS上にアプリケーションをつけて乗せようという章。 OSのコアな技術などは特に触れていないが、 28日目までのスクリーンショットはちょっと殺風景なので、見栄え的に必要な章だと思う。
圧縮することで逆に容量が増えることがある
圧縮なのに大きくなるなんておかしいと思うかもしれない。しかしこれはこれでいいのである。そもそも圧縮というのは言い換えをやっているだけで 確かにこれで大抵のものは短くなるのだが、言い換えたせいで長くなってしまうこともたまにはあるのだ。この性質はtek圧縮だけのものではなく、どんな圧縮にも言えることである。 p636より
どうやら調べてみる限りzipでも圧縮後に大きくなることがあるらしい。
http://q.hatena.ne.jp/1151986295
一応情報系の学部(笑)なので情報にもエントロピーがあってその大小で圧縮効率の高低が変わる、的なことを学んだが、思い出せない。
テキストビューア(tview)の実行に失敗する
file open error.
が出てしまう。なぜかipl20.nasだけオープンできなかった。他のファイルで実験したところ成功した。
ページングについて
ページングについて
ページングとは
- ページングとは固定長にメモリを分割して管理する方法
- 以下をページと呼ばれる単位に対して行う
- メモリの割り当て
- アドレス変換
- 以下をページと呼ばれる単位に対して行う
- セグメントとの違い
- セグメントは可変長
- ページングは固定長
アドレス参照方法
- 32bitのアドレスを分割する
- 上位20bitを論理ページとする
- 下位12bitをページオフセットとする
- 論理ページと物理ページを対応付けるページテーブルで物理ページのベースアドレスを得る
- オフセットを加算して物理アドレスが完成する
- 486ではページテーブルが2段になっている。
PTE(Page Table Entory)
主要なものだけをピックアップ。ライトスルー、ライトバックのオプションに関するものや権限に関するものは省略している。
位置 | 役割 | 意味 |
---|---|---|
12~31bit | 物理ページ番号 | 物理ページ番号を表す。 |
6bit目 | Dビット | Dirty bit。write時に1。ページアウト時にディスク書き込みの必要有無の判断に利用 |
5bit目 | Aビット | Access bit。アクセス時に1。アクセス頻度からページアウトの判断に利用 |
0bit目 | Pビット | 1ならばページが存在している。0ならばアクセス時にページフォルトを発生させる |
- PTEはもちろんキャッシュされる。このキャッシュメモリをTLB(Translation Lookaside Buffer)という
仮想記憶
- ページング=仮想記憶ではない。
- ページング方式は仮想記憶に利用される方法の1つに過ぎない。*2
- デマンドページング
他
- ページングはデフォルトでOFFになっている。
- ONにするにはCR0のPGビットを1に設定するが、その前に初期設定として以下の作業が必要
ページングが扱う論理アドレスは(リニアアドレス)という
ページフォルトの例外発生時に、リニアアドレスはCR2に格納される
- CR2にはメモリからアクセスできない。
- 普通にスタックに積んでくれればC言語から参照できるのでは?何かセキュリティ上の理由が?
- 作者: 蒲地輝尚
- 出版社/メーカー: アスキー
- 発売日: 1994/09
- メディア: 単行本
- 購入: 20人 クリック: 165回
- この商品を含むブログ (83件) を見る
qemu monitorを使ってメモリ上にロードされている実行中のアプリケーションの機械語をダンプ
qemu monitorを使う記事の続きです。
前回記事はこちらになります。
本記事ではwalkアプリケーションを起動した状態でqemu monitorを使ってメモリ上にマッピングされたwalkの実行ファイルを探し当てるところまでをやります。
qemu monitorを使ってアプリケーションの実行コードがロードされている場所をのぞいて見ましょう。hariboteOSではGDTの格納内容は以下のようになっています。
index | content |
---|---|
1003 | taska用(アプリはないので使っていない) |
1004 | idle用(アプリはないので使っていない) |
1005 | 1個目に確保されるアプリのLDTへの参照 |
1006 | 2個目に確保されるプリのLDTへの参照 |
... | 省略 |
1エントリの登録内容はこんな感じ。
struct SEGMENT_DESCRIPTOR { short limit_low, base_low; char base_mid, access_right; char limit_high, base_high; };
構造体上での表現 63 55 47 39 31 23 15 7 0 +--------+--------+--------+--------+--------+--------+--------+--------+ |base_hi |limit_hi| ar |base_mid| base_low | limit_low | +--------+--------+--------+--------+--------+--------+--------+--------+ CPUの仕様上のデスクリプタ ※limit_highの上位4bitはアクセス属性の続きになります。だから実際はCPUは上の構造体を以下の形式で扱います。 63 55 47 39 31 23 15 7 0 +--------+----+----+--------+--------+--------+--------+--------+--------+ |base_hi |ar |l_hi| ar |base_mid| base_low | limit_low | +--------+----+----+--------+--------+--------+--------+--------+--------+ ※入りきらない変数名は省略しています
GDTは前回記事のinfo registersコマンドの出力にある通り、
GDT= 00270000 0000ffff
この場所に格納されています。
アプリケーションのLDTが登録されている場所に近い1000×8(0x1F40
)以降のエントリをダンプして見ます
GDTのindexが8倍されているこが腑に落ちない人は以下の記事にまとめてあるので参考にしてください。
(qemu) xp /10xg 0x271F40 0000000000271f40: 0x000089032c500067 0x000089032d0c0067 0000000000271f50: 0x000089032dc80067 0x00008200508c000f 0000000000271f60: 0x000082005148000f 0x000082005204000f 0000000000271f70: 0x0000820052c0000f 0x00008200537c000f 0000000000271f80: 0x000082005438000f 0x0000820054f4000f
これを表に起こして見ます。
idx | description | base_hi | ar | l_hi | ar | base_mid | base_low | limit_low | base | limi | ar |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0x000089032c500067 | 00 | 0 | 0 | 89 | 03 | 2c50 | 0067 | 00032c50 | 00067 | 089 |
1 | 0x000089032d0c0067 | 00 | 0 | 0 | 89 | 03 | 2d0c | 0067 | 00032d0c | 00067 | 089 |
2 | 0x000089032dc80067 | 00 | 0 | 0 | 89 | 03 | 2dc8 | 0067 | 00032dc8 | 00067 | 089 |
3 | 0x00008200508c000f | 00 | 0 | 0 | 82 | 00 | 508c | 000f | 0000508c | 0000f | 082 |
4 | 0x000082005148000f | 00 | 0 | 0 | 82 | 00 | 5148 | 000f | 00005148 | 0000f | 082 |
5 | 0x000082005204000f | 00 | 0 | 0 | 82 | 00 | 5204 | 000f | 00005204 | 0000f | 082 |
6 | 0x0000820052c0000f | 00 | 0 | 0 | 82 | 00 | 52c0 | 000f | 000052c0 | 0000f | 082 |
7 | 0x00008200537c000f | 00 | 0 | 0 | 82 | 00 | 537c | 000f | 0000537c | 0000f | 082 |
8 | 0x000082005438000f | 00 | 0 | 0 | 82 | 00 | 5438 | 000f | 00005438 | 0000f | 082 |
9 | 0x0000820054f4000f | 00 | 0 | 0 | 82 | 00 | 54f4 | 000f | 000054f4 | 0000f | 082 |
5番目のエントリのLDT00005204
を表示。なおこれはコンソール1で実行したアプリケーションのLDTになります。
(qemu) xp /2xg 0x00005204 0000000000005204: 0x0040fa06200001e6 0x0040f3063000cfff
idx | description | base | lim | ar |
---|---|---|---|---|
1 | 0x0040fa06200001e6 | 00062000 | 001e6 | 4fa |
2 | 0x0040f3063000cfff | 00063000 | 0cfff | 4f3 |
とりあえずコードセグメント00062000
をダンプ
(qemu) xp /486bc 0x00062000 0000000000062000: '\x00' '\xd0' '\x00' '\x00' 'H' 'a' 'r' 'i' 0000000000062008: '\x00' '\x00' '\x00' '\x00' '\x00' '\x04' '\x00' '\x00' 0000000000062010: '\x07' '\x00' '\x00' '\x00' '\xe0' '\x01' '\x00' '\x00' 0000000000062018: '\x00' '\x00' '\x00' '\xe9' '\xb7' '\x01' '\x00' '\x00' 0000000000062020: '\x10' '\x04' '\x00' '\x00' 'U' '\x89' '\xe5' 'W' 0000000000062028: 'V' '\xbf' 'L' '\x00' '\x00' '\x00' 'S' '\xbe' 0000000000062030: '8' '\x00' '\x00' '\x00' 'R' '\xe8' 'M' '\x01' 0000000000062038: '\x00' '\x00' 'h' '\x80' '>' '\x00' '\x00' '\xe8' 0000000000062040: 'd' '\x01' '\x00' '\x00' 'h' '\x00' '\x04' '\x00' 0000000000062048: '\x00' 'j' '\xff' 'j' 'd' 'h' '\xa0' '\x00' 0000000000062050: '\x00' '\x00' 'P' '\xe8' '\xbd' '\x00' '\x00' '\x00' 0000000000062058: '\x89' 'E' '\xf0' 'j' '\x00' 'j' '_' 'h' 0000000000062060: '\x9b' '\x00' '\x00' '\x00' 'j' '\x18' 'j' '\x04' 0000000000062068: 'P' '\xe8' '\xf1' '\x00' '\x00' '\x00' '\x83' '\xc4' 0000000000062070: '0' 'h' '\x05' '\x04' '\x00' '\x00' 'j' '\x01' 0000000000062078: 'j' '\x03' 'j' '8' 'j' 'L' '\xff' 'u' 0000000000062080: '\xf0' '\xe8' '\xb1' '\x00' '\x00' '\x00' '\x83' '\xc4' 0000000000062088: '\x18' 'j' '\x01' '\xe8' ';' '\x01' '\x00' '\x00' 0000000000062090: 'h' '\x05' '\x04' '\x00' '\x00' 'j' '\x01' '\x89' 0000000000062098: '\xc3' 'j' '\x00' 'V' 'W' '\xff' 'u' '\xf0' 00000000000620a0: '\xe8' '\x92' '\x00' '\x00' '\x00' '\x83' '\xc4' '\x1c' 00000000000620a8: '\x83' '\xfb' '4' 't' 'W' '\x83' '\xfb' '6' 00000000000620b0: 't' 'E' '\x83' '\xfb' '8' 't' '6' '\x83' 00000000000620b8: '\xfb' '2' 't' '\'' '\x83' '\xfb' '\n' 't' 00000000000620c0: '\r' 'h' '\x05' '\x04' '\x00' '\x00' 'j' '\x01' 00000000000620c8: 'j' '\x03' 'V' 'W' '\xeb' '\xb0' '\xff' 'u' 00000000000620d0: '\xf0' '\xe8' '\xe7' '\x00' '\x00' '\x00' 'X' '\x8d' 00000000000620d8: 'e' '\xf4' '[' '^' '_' ']' '\xe9' '+' 00000000000620e0: '\x00' '\x00' '\x00' '\x83' '\xfe' 'O' '\x7f' '\xd4' 00000000000620e8: '\x83' '\xc6' '\x08' '\xeb' '\xcf' '\x83' '\xfe' '\x18' 00000000000620f0: '~' '\xc5' '\x83' '\xee' '\x08' '\xeb' '\xc0' '\x81' 00000000000620f8: '\xff' '\x93' '\x00' '\x00' '\x00' '\x7f' '\xb3' '\x83' 0000000000062100: '\xc7' '\x08' '\xeb' '\xae' '\x83' '\xff' '\x04' '~' 0000000000062108: '\xa4' '\x83' '\xef' '\x08' '\xeb' '\x9f' '\xba' '\x04' 0000000000062110: '\x00' '\x00' '\x00' '\xcd' '@' 'W' 'V' 'S' 0000000000062118: '\xba' '\x05' '\x00' '\x00' '\x00' '\x8b' '\\' '$' 0000000000062120: '\x10' '\x8b' 't' '$' '\x14' '\x8b' '|' '$' 0000000000062128: '\x18' '\x8b' 'D' '$' '\x1c' '\x8b' 'L' '$' 0000000000062130: ' ' '\xcd' '@' '[' '^' '_' '\xc3' 'W' 0000000000062138: 'V' 'U' 'S' '\xba' '\x06' '\x00' '\x00' '\x00' 0000000000062140: '\x8b' '\\' '$' '\x14' '\x8b' 't' '$' '\x18' 0000000000062148: '\x8b' '|' '$' '\x1c' '\x8b' 'D' '$' ' ' 0000000000062150: '\x8b' 'L' '$' '$' '\x8b' 'l' '$' '(' 0000000000062158: '\xcd' '@' '[' ']' '^' '_' '\xc3' 'W' 0000000000062160: 'V' 'U' 'S' '\xba' '\x07' '\x00' '\x00' '\x00' 0000000000062168: '\x8b' '\\' '$' '\x14' '\x8b' 'D' '$' '\x18' 0000000000062170: '\x8b' 'L' '$' '\x1c' '\x8b' 't' '$' ' ' 0000000000062178: '\x8b' '|' '$' '$' '\x8b' 'l' '$' '(' 0000000000062180: '\xcd' '@' '[' ']' '^' '_' '\xc3' 'S' 0000000000062188: '\xba' '\x08' '\x00' '\x00' '\x00' '.' '\x8b' '\x1d' 0000000000062190: ' ' '\x00' '\x00' '\x00' '\x89' '\xd8' '\x05' '\x00' 0000000000062198: '\x80' '\x00' '\x00' '.' '\x8b' '\r' '\x00' '\x00' 00000000000621a0: '\x00' '\x00' ')' '\xc1' '\xcd' '@' '[' '\xc3' 00000000000621a8: 'S' '\xba' '\x09' '\x00' '\x00' '\x00' '.' '\x8b' 00000000000621b0: '\x1d' ' ' '\x00' '\x00' '\x00' '\x8b' 'L' '$' 00000000000621b8: '\x08' '\xcd' '@' '[' '\xc3' 'S' '\xba' '\x0e' 00000000000621c0: '\x00' '\x00' '\x00' '\x8b' '\\' '$' '\x08' '\xcd' 00000000000621c8: '@' '[' '\xc3' '\xba' '\x0f' '\x00' '\x00' '\x00' 00000000000621d0: '\x8b' 'D' '$' '\x04' '\xcd' '@' '\xc3' 'U' 00000000000621d8: '\x89' '\xe5' ']' '\xe9' 'D' '\xfe' '\xff' '\xff' 00000000000621e0: 'w' 'a' 'l' 'k' '\x00' '*'
ダンプ内容がwalk.hrbの実行バイナリと一致
$ hexdump -C walk.hrb 00000000 00 d0 00 00 48 61 72 69 00 00 00 00 00 04 00 00 |....Hari........| 00000010 07 00 00 00 e0 01 00 00 00 00 00 e9 b7 01 00 00 |................| 00000020 10 04 00 00 55 89 e5 57 56 bf 4c 00 00 00 53 be |....U..WV.L...S.| 00000030 38 00 00 00 52 e8 4d 01 00 00 68 80 3e 00 00 e8 |8...R.M...h.>...| 00000040 64 01 00 00 68 00 04 00 00 6a ff 6a 64 68 a0 00 |d...h....j.jdh..| 00000050 00 00 50 e8 bd 00 00 00 89 45 f0 6a 00 6a 5f 68 |..P......E.j.j_h| 00000060 9b 00 00 00 6a 18 6a 04 50 e8 f1 00 00 00 83 c4 |....j.j.P.......| 00000070 30 68 05 04 00 00 6a 01 6a 03 6a 38 6a 4c ff 75 |0h....j.j.j8jL.u| 00000080 f0 e8 b1 00 00 00 83 c4 18 6a 01 e8 3b 01 00 00 |.........j..;...| 00000090 68 05 04 00 00 6a 01 89 c3 6a 00 56 57 ff 75 f0 |h....j...j.VW.u.| 000000a0 e8 92 00 00 00 83 c4 1c 83 fb 34 74 57 83 fb 36 |..........4tW..6| 000000b0 74 45 83 fb 38 74 36 83 fb 32 74 27 83 fb 0a 74 |tE..8t6..2t'...t| 000000c0 0d 68 05 04 00 00 6a 01 6a 03 56 57 eb b0 ff 75 |.h....j.j.VW...u| 000000d0 f0 e8 e7 00 00 00 58 8d 65 f4 5b 5e 5f 5d e9 2b |......X.e.[^_].+| 000000e0 00 00 00 83 fe 4f 7f d4 83 c6 08 eb cf 83 fe 18 |.....O..........| 000000f0 7e c5 83 ee 08 eb c0 81 ff 93 00 00 00 7f b3 83 |~...............| 00000100 c7 08 eb ae 83 ff 04 7e a4 83 ef 08 eb 9f ba 04 |.......~........| 00000110 00 00 00 cd 40 57 56 53 ba 05 00 00 00 8b 5c 24 |....@WVS......\$| 00000120 10 8b 74 24 14 8b 7c 24 18 8b 44 24 1c 8b 4c 24 |..t$..|$..D$..L$| 00000130 20 cd 40 5b 5e 5f c3 57 56 55 53 ba 06 00 00 00 | .@[^_.WVUS.....| 00000140 8b 5c 24 14 8b 74 24 18 8b 7c 24 1c 8b 44 24 20 |.\$..t$..|$..D$ | 00000150 8b 4c 24 24 8b 6c 24 28 cd 40 5b 5d 5e 5f c3 57 |.L$$.l$(.@[]^_.W| 00000160 56 55 53 ba 07 00 00 00 8b 5c 24 14 8b 44 24 18 |VUS......\$..D$.| 00000170 8b 4c 24 1c 8b 74 24 20 8b 7c 24 24 8b 6c 24 28 |.L$..t$ .|$$.l$(| 00000180 cd 40 5b 5d 5e 5f c3 53 ba 08 00 00 00 2e 8b 1d |.@[]^_.S........| 00000190 20 00 00 00 89 d8 05 00 80 00 00 2e 8b 0d 00 00 | ...............| 000001a0 00 00 29 c1 cd 40 5b c3 53 ba 09 00 00 00 2e 8b |..)..@[.S.......| 000001b0 1d 20 00 00 00 8b 4c 24 08 cd 40 5b c3 53 ba 0e |. ....L$..@[.S..| 000001c0 00 00 00 8b 5c 24 08 cd 40 5b c3 ba 0f 00 00 00 |....\$..@[......| 000001d0 8b 44 24 04 cd 40 c3 55 89 e5 5d e9 44 fe ff ff |.D$..@.U..].D...| 000001e0 77 61 6c 6b 00 2a 00 |walk.*.|
hrbファイルの構造と照らし合わせて逆アセンブル
(qemu) xp /486bi 0x00062000 0x00062000: 00 d0 addb %dl, %al 0x00062002: 00 00 addb %al, (%eax) 0x00062004: 48 decl %eax 0x00062005: 61 popal 0x00062006: 72 69 jb 0x62071 0x00062008: 00 00 addb %al, (%eax) 0x0006200a: 00 00 addb %al, (%eax) 0x0006200c: 00 04 00 addb %al, (%eax, %eax) 0x0006200f: 00 07 addb %al, (%edi) 0x00062011: 00 00 addb %al, (%eax) 0x00062013: 00 e0 addb %ah, %al 0x00062015: 01 00 addl %eax, (%eax) 0x00062017: 00 00 addb %al, (%eax) 0x00062019: 00 00 addb %al, (%eax) 0x0006201b: e9 b7 01 00 00 jmp 0x621d7;!!!!! このジャンプ命令でアプリの実行開始番地に飛ぶ ・・・長すぎるので略・・・ 0x000621d7: 53 pushl %ebx;!!!!ここにジャンプ!!!!! 0x000621d8: ba 00 00 00 8b movl $0x8b000000, %edx 0x000621dd: 5c popl %esp 0x000621de: 24 08 andb $8, %al 0x000621e0: 00 8b 24 08 cd 08 addb %cl, 0x8cd0824(%ebx) 0x000621e6: 40 incl %eax 0x000621e7: 5b popl %ebx 0x000621e8: c3 retl 0x000621e9: ba 0f 00 00 8b movl $0x8b00000f, %edx 0x000621ee: 44 incl %esp 0x000621ef: 04 cd addb $0xcd, %al 0x000621f1: 40 incl %eax 0x000621f2: c3 retl 0x000621f3: 40 incl %eax 0x000621f4: c3 retl 0x000621f5: c3 retl 0x000621f6: 55 pushl %ebp 0x000621f7: 89 fe movl %edi, %esi 0x000621f9: ff .byte 0xff 0x000621fa: ff 77 61 pushl 0x61(%edi) 0x000621fd: ff 77 61 pushl 0x61(%edi) 0x00062200: 00 00 addb %al, (%eax) ・・・ずっと00なので省略 0x00062420: 00 00 addb %al, (%eax)
walkは多分処理がリバースエンジニアリングできるほど簡単ではないのでとりあえず今日はこのくらいにしておきます。(hello worldあたりを題材にしておいたほうがよかったなぁ)
わからないこと
このセグメント内容をメモリにロードし終わった後、EIPの初期値を何にするかよくわかりません。0x0006201b
までのバイナリは実行コードではなさそうですし、jumpした番地以外の場所も実行できるプログラムなのか怪しいです。
おまけ
セグメントディスクリプタのエントリは分解された状態で格納されていますが、 これを人間にとって読みやすい形に整形する正規表現でも貼っておきます。
置換前 (0x(.{2})(.{1})(.{1})(.{2})(.{2})(.{4})(.{4}).*) 置換後 $1,$2$6$7,$4$8,$3$5 置換前:0x0040fa06200001e6 置換後:0x0040fa06200001e6,00062000,001e6,4fa original,base,limit,access_right の順のcsvになります。
参考
- qemu monitor自体はosdev-jpのslackで知りました。
- http://www15.big.or.jp/~yamamori/sun/qemu/usage.html
- https://en.wikibooks.org/wiki/QEMU/Monitor#info
30日OS自作本28日目2日本語対応
OS自作本28日日本語対応の内容です。
JIS企画でも全角文字コード点、区、面
- 1つの点が、全角1文字に対応
- 1つの区には、94の点がある
- 1つの面には、94の区がある
面 - 区 - 点
という感じで 1:94:94の関係で構成されているようです。
感じには第一水準漢字~第四水準漢字があり、1~3水準まで1面に含まれているようです,2面には第四水準漢字がある。
https://ja.wikipedia.org/wiki/JIS漢字コード
あの文字コード0x2422
これを面-区-点の表記に治すには
- 上位1Byte -
0x20
= 区 - 下位1Byte -
0x20
= 点
で1面04区02点となります.
hariboteOSでは第一水準の漢字のみを使えるようにします。
sjis,unicode,eucなどと比べると・・・
$ echo あ|nkf -w|hexdump -C 00000000 e3 81 82 0a |....| 00000004
$ echo あ|nkf -s|hexdump -C 00000000 82 a0 0a |...| 00000003
$ echo あ|nkf -e|hexdump -C 00000000 a4 a2 0a |...| 00000003
一応nkf -jでjisも表示できるのですが、0x2422の周りに変なものがたくさん出てきました。何これ?
$echo あ|nkf -j|hexdump -C 00000000 1b 24 42 24 22 1b 28 42 0a |.$B$".(B.| 00000009
各コードそれぞれで文字コード → 面区点に変換する方法があるので(このルールが微妙に違うだけ) hariboteOSではeucとsjisでの文字表示に対応させています(utfはありませんね。読者課題ってことでしょうか)
とりあえずeuc,sjis,デフォルトの組み込み半角英数字(16*8)のサイズ で切り替えるようなコマンドも作成しました。
langmode <数字>
感想
文字コード,最近のマシンではutf8だけど、昔のlinuxはeuc,windowsはsjis、くらいしかわかってないので、一度ちゃんと学んだほうがいいと思った。
- utf8とutf16の違いなど
- sjis,jisの違いなど
プログラマのための文字コード技術入門 (WEB+DB PRESS plus) (WEB+DB PRESS plusシリーズ)
- 作者: 矢野啓介
- 出版社/メーカー: 技術評論社
- 発売日: 2010/02/18
- メディア: 単行本(ソフトカバー)
- 購入: 34人 クリック: 578回
- この商品を含むブログ (129件) を見る
qemu monitorを使って自作OSをデバッグする
qemuの起動オプションに-monitor stdio
を付け加えるとqemuを起動したコンソールでqemu monitorが使えるようです。
30日OS自作本の場合
※自分の環境はmacなのでWindows, Linuxの方は違いなどあれば適当に読み替えて実行して見てください。
HariboteOS/z_tools/qemu配下のMakefileに-monitor stdio
を書き足します。
QEMU = qemu-system-i386 QEMU_ARGS = -L . -m 32 -localtime -vga std -fda fdimage0.bin -monitor stdio default: $(QEMU) $(QEMU_ARGS)
info registers:レジスタの状態を表示
EFLAGSがないのが気になる。。。
(qemu) info registers EAX=00000000 EBX=00000000 ECX=00000000 EDX=00000000 ESI=00000000 EDI=00000000 EBP=00039ffc ESP=00039ff8 EIP=00000b00 EFL=00000202 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=1 ES =0008 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] CS =0010 00280000 0007ffff 00479a00 DPL=0 CS32 [-R-] SS =0008 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] DS =0008 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] FS =0008 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] GS =0008 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] LDT=0000 00000000 00000000 00000000 TR =0020 000050b8 00000067 00008900 DPL=0 TSS32-avl GDT= 00270000 0000ffff IDT= 0026f800 000007ff CR0=00000019 CR2=00000000 CR3=00000000 CR4=00000000 DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 DR6=ffff0ff0 DR7=00000400 EFER=0000000000000000 FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80 FPR0=0000000000000000 0000 FPR1=0000000000000000 0000 FPR2=0000000000000000 0000 FPR3=0000000000000000 0000 FPR4=0000000000000000 0000 FPR5=0000000000000000 0000 FPR6=0000000000000000 0000 FPR7=0000000000000000 0000 XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000 XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000 XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000 XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000 (qemu)
xpコマンド:メモリダンプ
(qemu) help xp xp /fmt addr -- physical memory dump starting at 'addr'
format: Used to specify the output format the displayed memory. The format is broken down as /[count][data_format][size]
- count: ダンプするデータ量。後述のsize * countで決まる
- data_format:データ形式を以下の文字で指定
letter | format |
---|---|
'x' | hex |
'd' | decimal |
'u' | unsigned decimal |
'o' | octal |
'c' | char |
'i' | (disassembled) processor instructions |
- size:
letter | size |
---|---|
'b' | 8 bit |
'h' | 16 bit |
'w' | 32 bit |
'g' | 64 bit |
hariboteOSのメモリマップを参考に早速メモリダンプをして見ます。
start | end | size | content |
---|---|---|---|
0x00000000 | 0x000fffff | 1MB | boot |
0x00100000 | 0x00267fff | 1440KB | floppy content |
0x00268000 | 0x0024f7ff | 30KB | empty |
0x0026f800 | 0x0026ffff | 2KB | IDT |
0x00270000 | 0x0027ffff | 64KB | GDT |
0x00280000 | 0x002fffff | 512KB | bootpack.hrb |
0x00300000 | 0x003fffff | 1MB | stack or etc |
0x00400000 | - | - | empty |
まずはフロッピーがロードされている0x00100000
のメモリ内容をダンプします。
(qemu) xp/200xc 0x00100000 0000000000100000: '\xeb' 'N' '\x90' 'H' 'A' 'R' 'I' 'B' 0000000000100008: 'O' 'T' 'E' '\x00' '\x02' '\x01' '\x01' '\x00' 0000000000100010: '\x02' '\xe0' '\x00' '@' '\x0b' '\xf0' '\x09' '\x00' 0000000000100018: '\x12' '\x00' '\x02' '\x00' '\x00' '\x00' '\x00' '\x00' 0000000000100020: '@' '\x0b' '\x00' '\x00' '\x00' '\x00' ')' '\xff' 0000000000100028: '\xff' '\xff' '\xff' 'H' 'A' 'R' 'I' 'B' 0000000000100030: 'O' 'T' 'E' 'O' 'S' ' ' 'F' 'A' 0000000000100038: 'T' '1' '2' ' ' ' ' ' ' '\x00' '\x00' 0000000000100040: '\x00' '\x00' '\x00' '\x00' '\x00' '\x00' '\x00' '\x00' 0000000000100048: '\x00' '\x00' '\x00' '\x00' '\x00' '\x00' '\x00' '\x00' 0000000000100050: '\xb8' '\x00' '\x00' '\x8e' '\xd0' '\xbc' '\x00' '|' 0000000000100058: '\x8e' '\xd8' '\xb8' ' ' '\x08' '\x8e' '\xc0' '\xb5' 0000000000100060: '\x00' '\xb6' '\x00' '\xb1' '\x02' '\xbe' '\x00' '\x00' 0000000000100068: '\xb4' '\x02' '\xb0' '\x01' '\xbb' '\x00' '\x00' '\xb2' 0000000000100070: '\x00' '\xcd' '\x13' 's' '\x10' '\x83' '\xc6' '\x01' 0000000000100078: '\x83' '\xfe' '\x05' 's' '2' '\xb4' '\x00' '\xb2' 0000000000100080: '\x00' '\xcd' '\x13' '\xeb' '\xe3' '\x8c' '\xc0' '\x05' 0000000000100088: ' ' '\x00' '\x8e' '\xc0' '\x80' '\xc1' '\x01' '\x80' 0000000000100090: '\xf9' '\x12' 'v' '\xd1' '\xb1' '\x01' '\x80' '\xc6' 0000000000100098: '\x01' '\x80' '\xfe' '\x02' 'r' '\xc7' '\xb6' '\x00' 00000000001000a0: '\x80' '\xc5' '\x01' '\x80' '\xfd' '\n' 'r' '\xbd' 00000000001000a8: '\x88' '.' '\xf0' '\x0f' '\xe9' 'Q' 'E' '\xbe' 00000000001000b0: '\xc7' '|' '\x8a' '\x04' '\x83' '\xc6' '\x01' '<' 00000000001000b8: '\x00' 't' '\x09' '\xb4' '\x0e' '\xbb' '\x0f' '\x00' 00000000001000c0: '\xcd' '\x10' '\xeb' '\xee' '\xf4' '\xeb' '\xfd' '\n'
もちろんこれはmakeで作成したOSイメージをバイナリエディタで表示したものと一致していることがわかります!!!
30日OS自作本28日目1ファイルAPI編
28日目の内容です。 - ファイルAPIの作成 - hariboteOSを日本語対応させます。
OS自体の開発はここでおしまい。なのです。😭 記事が長くなるので前半後半で分けます。本記事ではとりあえずpart1としてファイルAPIについて書きます。
ファイルAPI
ファイルAPIの具体的なイメージなどよくわからず、また本書掲載のコードが5つ用意したうち2つしか使っていなかったので、以下2点に挑戦して見ました。
では、ファイル操作のためのAPIを新たにOSに作ります。
struct TASK { //・・・無関係メンバ略 //新規追加 struct FILEHANDLE *fhandle; int *fat; };
struct FILEHANDLE { char *buf; //メモリにロードしたファイル内容への参照 int size; //ファイルサイズ int pos; //シークヘッダの位置(読み込み、書き込みに利用) };
edx | api |
---|---|
21 | ファイルオープン |
22 | ファイルクローズ |
23 | ファイルシーク |
24 | ファイルサイズの取得 |
25 | ファイルの読み込み |
コード解説
コードは大体、console.cより抜粋。(定義などは適当に.hファイルから拝借)
ファイルオープン
//定義メモ #define ADR_DISKIMG 0x00100000 //フロッピーの内容がロードされるメモリの位置 struct FILEINFO *file_search(char *name, struct FILEINFO *finfo, int max); void file_loadfile(int clustno, int size, char *buf, int *fat, char *img); ・・・省略・・・ //API引数 // EDX = 21 // EBX = ファイル名 // //API戻り値 // EAX = ファイルハンドル(失敗の場合0) } else if (edx == 21) { //空きのファイルバッファを探す for (i = 0; i < 8; i++) { //ここのiの最大値が8なのがよくわからない。 //一つのタスクが1度に扱えるファイル数が8個までという制限の意味? if (task->fhandle[i].buf == 0) { break; } } fh = &task->fhandle[i]; reg[7] = 0; //失敗時の戻り値で初期化 if (i < 8) { //file_search関数の引数について整理します。 //1.edx=文字列のアドレス、そこにタスクのベースアドレスを加算してリニアアドレスを算出 //2.ADR_DISKIMG + 0x002600で、フロッピーの内容がコピーされた場所の内、ルートディレクトリの開始位置ということになります。 //3.224 = ルートディレクト領域の大きさ(IPLで定義) finfo = file_search( (char *) ebx + ds_base, (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224 ); //file_search()の戻り値=0はファイルが見つからなかった時 if (finfo != 0) { //最終的にreg[7]はapiを読んだ時の戻り値(EAX)になります。 //そこにこれから作成するファイルハンドラを代入します。 reg[7] = (int) fh; //ファイルハンドラ初期化 fh->buf = (char *) memman_alloc_4k(memman, finfo->size); fh->size = finfo->size; fh->pos = 0; //ファイルハンドラのbufの位置にデータ読み込み //void file_loadfile(int clustno, int size, char *buf, int *fat, char *img)の引数整理 //引数1:finfo->clustno,そのファイルがどのクラスタ内にあるか、 //引数2:finfo->size,そのファイルのサイズ //引数3:fh->buf読み込んだファイルを格納するアドレス //引数4:task->fat fatの全データが格納されている //引数5:0x003e00,フロッピーからロードしたファイル実態が格納されているアドレス file_loadfile( finfo->clustno, finfo->size, fh->buf, task->fat, (char *) (ADR_DISKIMG + 0x003e00)); } }
ファイル名でヒットしたfatを元に、メモリ上にブート時に配置されたファイルをコピーしています。
ファイルクローズ
//API引数 // EDX = 22 // EAX = ファイルハンドル } else if (edx == 22) { fh = (struct FILEHANDLE *) eax; memman_free_4k(memman, (int) fh->buf, fh->size); fh->buf = 0;
コピーしていたファイル内容を破棄します。(メモリの解放) buf=0のものはクローズ扱いです。 逆にコマンドの処理が完了時にbuf=0になっていないものはOSが勝手に解放します。
OSによる自動解放
start_app(0x1b, 0 * 8 + 4, esp, 1 * 8 + 4, &(task->tss.esp0)); ・・・省略・・・ for (i = 0; i < 8; i++) { /* クローズしてないファイルをクローズ */ if (task->fhandle[i].buf != 0) { memman_free_4k(memman, (int) task->fhandle[i].buf, task->fhandle[i].size); task->fhandle[i].buf = 0; } }
コードの重複がありますね〜30日終わったらうまく抽象化したいですね〜
ファイルシーク
//API引数 // EDX = 23 // EAX = ファイルハンドルのアドレス // ECX = シークモード // 0:シークの原点はファイルの先頭 // 1:シークの原点は現在のアクセス位置 // 2:シークの原点はファイルの終端 // EBX = シーク量 } else if (edx == 23) { fh = (struct FILEHANDLE *) eax; if (ecx == 0) { //ECX = 0:シークの原点はファイルの先頭 //ファイル先頭位置から指定サイズ分(ebx)シーク fh->pos = ebx; } else if (ecx == 1) { //ECX = 1:シークの原点は現在のアクセス位置 //現在のposから指定サイズ分(ebx)シーク fh->pos += ebx; } else if (ecx == 2) { //2:シークの原点はファイルの終端 //ファイル末尾(fh->size)から指定サイズ分(ebx)シーク fh->pos = fh->size + ebx; } if (fh->pos < 0) { //posが0以下?なにこれ?オーバーフロー対策? fh->pos = 0; } if (fh->pos > fh->size) { //ファイルサイズを超えるようなシークを無効化している? //ecx==2の時のシーク意味ないかこれ? fh->pos = fh->size; }
ファイルサイズの取得
特に解説することがない。
//API引数 // EAX = ファイルハンドル // ECX = ファイルサイズ取得モード // 0:普通のファイルサイズ // 1:現在の読み込み位置はファイル先頭から何バイト目か // 2:ファイル終端から見た現在位置までのバイト数 // //API戻り値 // EAX = ファイルサイズ } else if (edx == 24) { fh = (struct FILEHANDLE *) eax; if (ecx == 0) { reg[7] = fh->size; } else if (ecx == 1) { reg[7] = fh->pos; } else if (ecx == 2) { reg[7] = fh->pos - fh->size; }
ファイルの読み込み
特に難しくないでしょう。
//API引数 // EDX = 25 // EAX = ファイルハンドル // EBX = バッファの番地 // ECX = 最大読み込みバイト数 // //API戻り値 // EAX = 今回読み込めたバイト数 } else if (edx == 25) { fh = (struct FILEHANDLE *) eax; for (i = 0; i < ecx; i++) { if (fh->pos == fh->size) { break; } *((char *) ebx + ds_base + i) = fh->buf[fh->pos]; fh->pos++; } reg[7] = i; }
- 現在のhariboteOSにはファイル書き込みの機能がないのでwriteは作らない
- そういえばhariboteOSにファイルシステムではないらしい。
- オートクローズの機構がある。
- コマンド実行終了後、開いたままのファイルがあればOSがクローズする
ファイル操作APIの利用
apiのライブラリに登録されているこれらのメソッドを読んで見ます。 ちなみに本記事で解説したAPIはアセンブリに定義されている以下の関数経由で読んでいます。
int api_fopen(char *fname); void api_fclose(int fhandle); void api_fseek(int fhandle, int offset, int mode); int api_fsize(int fhandle, int mode); int api_fread(char *buf, int maxsize, int fhandle);
サンプルとしてabc.txtというファイルを作ったのでこれで実験します。
$ cat txt/abc.txt ABCDEFGHIJKLMNOPQRSTUVWXYZ
こちらをhariboteOSのディレクトリに投入します。
本書掲載のプログラムでは
- ファイルシーク
- ファイルクローズ
- ファイルサイズ
が使われていなかったので、使って見ました。
#include "apilib.h" void HariMain(void) { int fh; //ファイルオープン fh = api_fopen("abc.txt"); char s[20]; //ファイルサイズを出力 int size = api_fsize(fh,0); sprintf(s,"size:%d\n",size); api_putstr0(s); //ファイルシーク(読み込み位置を10文字進める) api_fseek(fh,10,0); if (fh != 0) { char c; //読み取り文字格納 for (;;) { //ファイルリード(1文字づつ) if (api_fread(&c, 1, fh) == 0) { break; } api_putchar(c); } } //ファイルクローズ api_fclose(fh); api_end(); }
このアプリの実行結果は以下のようになりました。アプリ名はtypeiplです。(本書のipl.nasを表示するアプリのガワをそのまま流用しています。)
- ファイルサイズは27バイトです。ローマ字は26文字ですが、ヌル文字を含んでいる?のでしょうか
- ファイルシークにより11字目から出力が始まります。10文字目がなので次のKから始まっていることが確認できます。
- クローズしなくてもOSがファイルクローズしますが、一応明示的にAPIを使って見ました。
まぁざっとこんな感じです。
感想
サンプルコード中にマジックナンバーなどよくわからない値が多数存在しています。 このアドレスの破片っぽいものは何だったっけ?と自分のブログを検索してみると以外と過去にメモった情報が見つかって助かることがあります。 今回は一番このブログに助けられた章になりました。 皆様も最終日が近付くと、初日辺りの細かいアドレスなど忘れてしまうので、メモなど取りながら進めることをお勧めします。
電子書籍の中をアドレスや変数名で検索できるとさらに良いのですが。