Yabu.log

ITなどの雑記

「はじめて読む486」を読んだ

tcfmのruiさんが勧めているのを見て買いましたが、

結果的に30日OS自作本*1の良い副読本になりました。

紙の本は500ページ近くあります。電子版を買って正解だと思います。 私が買ったのはKindle版ですが、本当は達人出版会から出てるDRM FREEのePubを買うべきでした。

tatsu-zine.com

古いのか?

この本を買おうかどうかで迷っている人が心配している点は

  • 486という古いCPUの知識なんか役に立つのか?
  • 20年前の本なので内容は流石に風化しているのではないか

という点でしょう。

また、この本の出版は1994年の本です。windows3.1が出始めて16bit -> 32bitの移行が進んでいる時期?だと思われます。 私が最初に触った記憶があるOSであるWindows95(1995)はまだ出現していません。

達人出版会の日付によると、電子版は2014年に発売したようです。 なんと初版発売から20年後です。 電子書籍についてよく調べられている方はご存知だと思いますが、古い本でリフローなePub形式のものを 販売しているものはほとんどありません

ここまで気合を入れて電子化している古い本は少ないと思います。 内容について、最新のCPU情報に詳しくないため、私が内容が古いかどうかの判断はできませんが、 電子書籍化している点から、記載内容の妥当性/有効性はまだまだ通用するぞ、という自信を感じます。

内容は古くないと思います!(多分)

30日OS自作本と比較して

  • 例外
  • プロテクトモード
  • コールゲート

などはこちらのほうが情報が充実しておりわかりやすかったです。 またページングはOS自作本では一切出てこないので精読してこんな記事を書きました。

yuyubu.hatenablog.com

同じようなことが買いてる部分もありましたが、別の視点からの説明を読み取ったり、 一方で間違えて覚えたものの誤解や不理解を他方で学び直すことで理解を深めることができました。

30日OS自作本との愛称はバッチリだと思います。 とにかく、この本と30日OS自作本で低レイヤーに対する基礎体力みたいなものはかなり身についたと思います。

30日でできる! OS自作入門

30日でできる! OS自作入門

本書中に書いてある面白いエピソード

クロックダブラー、クロックトリプラー

クロックの速度を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日目までのスクリーンショットはちょっと殺風景なので、見栄え的に必要な章だと思う。

www.youtube.com

圧縮することで逆に容量が増えることがある

圧縮なのに大きくなるなんておかしいと思うかもしれない。しかしこれはこれでいいのである。そもそも圧縮というのは言い換えをやっているだけで 確かにこれで大抵のものは短くなるのだが、言い換えたせいで長くなってしまうこともたまにはあるのだ。この性質はtek圧縮だけのものではなく、どんな圧縮にも言えることである。 p636より

どうやら調べてみる限りzipでも圧縮後に大きくなることがあるらしい。

http://q.hatena.ne.jp/1151986295

一応情報系の学部(笑)なので情報にもエントロピーがあってその大小で圧縮効率の高低が変わる、的なことを学んだが、思い出せない。

テキストビューア(tview)の実行に失敗する

file open error.

が出てしまう。なぜかipl20.nasだけオープンできなかった。他のファイルで実験したところ成功した。

ページングについて

ページングについて

ページングとは

  • ページングとは固定長にメモリを分割して管理する方法
    • 以下をページと呼ばれる単位に対して行う
      • メモリの割り当て
      • アドレス変換
  • セグメントとの違い
    • セグメントは可変長
    • ページングは固定長

アドレス参照方法

  • 32bitのアドレスを分割する
    • 上位20bitを論理ページとする
    • 下位12bitをページオフセットとする
  • 論理ページと物理ページを対応付けるページテーブルで物理ページのベースアドレスを得る
  • 486ではページテーブルが2段になっている。
    • そのまま20bitのページテーブルを作るとサイズが大きくなりすぎる
    • ページテーブルのエントリ(PTE) 4Byte * 論理アドレス総数 220 = 4,194,304 = 4MBも必要になる*1
    • 論理ページを半分に分けて、210件数分のページテーブル(1KB)を2段で構成する(最小2KB)
    • 上位10bitに対応したページテーブルをページディレクトリテーブルという。
      • 下位10bitに対応したページテーブルはページテーブルという呼称のまま。

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
  • デマンドページング
    • 必要になったページがメモリ上に存在しない時に適当に不要そうなページをスワップする方法
      • 用語
        • 必要になったページが存在しない*3:ページフォルト(例外)
        • 不要そうなページを2次記憶装置に退去させる:ページアウト
        • 必要なページを2時記憶装置からロードする:ページイン
    • 方法
      • ページフォルト時のハンドラ(0x0E)にページスワップのためのサブルーチンを登録
      • あまり使われていない論理ページを探してページアウト(PTEのPビットを0にセット)
      • ページフォルトの発生したページのメモリをページイン(PTEビットを1にセット)

  • ページングはデフォルトでOFFになっている。
  • ONにするにはCR0のPGビットを1に設定するが、その前に初期設定として以下の作業が必要
  • ページングが扱う論理アドレスは(リニアアドレス)という

    • アドレスは2段階で仮想化されていることになる。
  • ページフォルトの例外発生時に、リニアアドレスはCR2に格納される

    • CR2にはメモリからアクセスできない。
    • 普通にスタックに積んでくれればC言語から参照できるのでは?何かセキュリティ上の理由が?

はじめて読む486―32ビットコンピュータをやさしく語る

はじめて読む486―32ビットコンピュータをやさしく語る

*1:メモリの総量が1MBとか言ってた時代からすると多いと思う。

*2:ページングは仮想記憶のためだけの仕組みではないように思える

*3:存在しないかはPTEのPビットをみる

*4:セグメントレジスタのセグメントアドレス + オフセットアドレス

qemu monitorを使ってメモリ上にロードされている実行中のアプリケーションの機械語をダンプ

qemu monitorを使う記事の続きです。

前回記事はこちらになります。

yuyubu.hatenablog.com

本記事ではwalkアプリケーションを起動した状態でqemu monitorを使ってメモリ上にマッピングされたwalkの実行ファイルを探し当てるところまでをやります。

f:id:yuyubu:20180702234149p:plain
コンソール1によって起動された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倍されているこが腑に落ちない人は以下の記事にまとめてあるので参考にしてください。

yuyubu.hatenablog.com

 (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になります。

参考

30日OS自作本28日目2日本語対応

OS自作本28日日本語対応の内容です。

f:id:yuyubu:20180701184336p:plain

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ではeucsjisでの文字表示に対応させています(utfはありませんね。読者課題ってことでしょうか)

とりあえずeuc,sjis,デフォルトの組み込み半角英数字(16*8)のサイズ で切り替えるようなコマンドも作成しました。

langmode <数字>
  • 0:デフォルトの組み込み文字
  • 1:sjis
  • 2:euc

www.youtube.com

感想

文字コード,最近のマシンではutf8だけど、昔のlinuxeuc,windowssjis、くらいしかわかってないので、一度ちゃんと学んだほうがいいと思った。

  • utf8とutf16の違いなど
  • sjis,jisの違いなど

プログラマのための文字コード技術入門 (WEB+DB PRESS plus) (WEB+DB PRESS plusシリーズ)

プログラマのための文字コード技術入門 (WEB+DB PRESS plus) (WEB+DB PRESS plusシリーズ)

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の実装コードにコメントをつけて理解する
  • 全てのファイルAPIを使うアプリを作る

では、ファイル操作のための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のディレクトリに投入します。

f:id:yuyubu:20180628225014p:plain

本書掲載のプログラムでは

  • ファイルシーク
  • ファイルクローズ
  • ファイルサイズ

が使われていなかったので、使って見ました。

#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を表示するアプリのガワをそのまま流用しています。)

f:id:yuyubu:20180628225428p:plain

  • ファイルサイズは27バイトです。ローマ字は26文字ですが、ヌル文字を含んでいる?のでしょうか
  • ファイルシークにより11字目から出力が始まります。10文字目がなので次のKから始まっていることが確認できます。
  • クローズしなくてもOSがファイルクローズしますが、一応明示的にAPIを使って見ました。

まぁざっとこんな感じです。

感想

サンプルコード中にマジックナンバーなどよくわからない値が多数存在しています。 このアドレスの破片っぽいものは何だったっけ?と自分のブログを検索してみると以外と過去にメモった情報が見つかって助かることがあります。 今回は一番このブログに助けられた章になりました。 皆様も最終日が近付くと、初日辺りの細かいアドレスなど忘れてしまうので、メモなど取りながら進めることをお勧めします。

電子書籍の中をアドレスや変数名で検索できるとさらに良いのですが。