bochs (2)

「PCデスクトップ画面が狭くてイライラ」から脱却するには

仮想デスクトップってのが標準で装備されてるとな。Xなんかのウィンドウ・マネージャには これが付いてる確率が高かった。CUIの端末でもtmuxで、複数の端末を運用出来たな。やっと、Windows10でも標準で使えるようになったんかい。

新しいデスクトップを作る(Ctl + Windows + d)
仮想デスクトップを俯瞰する(Windows + TAB)
切り替え (Ctl + Windows + 左右矢印Key)
削除 (Ctl + Windows + F4)
モノクロ化 (Ctl + Windows + c)

まだ他にも機能が有るようだけど、取り合えずこれでtmuxでやってた事は出来るな。ポイントは、Windows10なんで、Windowsキーを使って、Xとかでの切り替えと差別化してるんだな。

この機能、使うか使わないかは、あなた次第。まあ、便利なemacsのキーバインドを覚えても、 使わないうちに風化して忘れ去られる運命になるか? 神のみぞ知る!

bochs debugger

bochsのdebuggerでmainにBPを置こうとすると、kernel.asmを参照して、<main> のアドレスを割り出さなければならない。そしてそれを下記のように設定する。

<bochs:1> lb 0x80102e80
<bochs:2> c
(0) Breakpoint 1, 0x0000000080102e80 in ?? ()
Next at t=2263724

仮想記憶がここでは既に有効になってるので、ただのbを指定するんじゃなくて、lbとする。lは、inear addressの意味らしい。他にもvbってのが有ってこちらは、virtual addressを意味 するとか。それじゃ、lとかvの接頭語が付かない場合は? physical addressを意味するとか。混乱してくるな。取り合えず、lbと覚えておこう。

で、ふと思ったね。Hexの塊でアドレスを表現するんじゃなくて、名前で呼びたい。つらつらと 説明書を見てたら、期待を持たせるような記述を発見。

4.3.44. debug_symbols
Example:

  debug_symbols: file=mysymbols.sym
  debug_symbols: file=mysymbols.sym, offset=0x1000
 This loads symbols from the specified file for use in Bochs' internal
 debugger. Symbols are loaded into global context. This is equivalent to
 issuing ldsym debugger command at start up.

 The symbol file consists of zero or more lines of the format

 "%x %s"

Hexの塊であるアドレスと名前が対になったファイルを用意すればいいんだな。そんなの簡単だ。

$ nm kernel | fgrep 'T ' | cut -d' ' -f1,3  >xv6.sym

取り合えずグローバルな関数だけを選んでみたけど、これで良かったかな。

で、いざBPの設定に使おうとしたら、シンタックスエラーになっちゃった。使えないの不便だな。これは諦めるしかないか。

(0) [0x000000102ea1] 0008:0000000080102ea1 (main+21): call .-2790 (0x801023c0)  ; e81af5ffff
<bochs:14>
Next at t=2263736
(0) [0x0000001023c0] 0008:00000000801023c0 (kinit1+0): push ebp                 ; 55

と思ったら、表示系に使われていた。Hexの塊を見せられるよりは、いくらか便利になったな。

<bochs:17> show ret
Unrecognized arg: ret (only 'mode', 'int', 'softint', 'extint', 'iret', 'call', 'all', 'off', 'dbg_all' and 'dbg_none' are valid)

Webの説明と少し乖離してるな。

<bochs:20> show int
show interrupts tracing (extint/softint/iret): ON
show mask is: softint extint iret
<bochs:21> c
00002263738: softint 0008:801023c3 (0x801023c3)
00002263738: iret 0008:801023c3 (0x801023c3)
00069824531: exception (not softint) 0008:80105bd9 (0x80105bd9)
00069826552: iret 0008:80103a74 (0x80103a74)
00069826553: exception (not softint) 0008:80105ee8 (0x80105ee8)
^CNext at t=443296189
(0) [0x0000001003e3] 0008:00000000801003e3 (panic+73): jmp .-2 (0x801003e3)     ; ebfe

この他に便利そうなのとして、print-stackが有ったけど、

<bochs:2> print-stack
Stack address size 4
 | STACK 0x8010b48c [0x80112d20]
 | STACK 0x8010b490 [0x80112780]
 | STACK 0x8010b494 [0x8010593f]
 | STACK 0x8010b498 [0x8010561f]
 | STACK 0x8010b49c [0x80102e5f]
 | STACK 0x8010b4a0 [0x80102f9f]
 | STACK 0x8010b4a4 [0x00000000]
 | STACK 0x8010b4a8 [0x00000000]
 | STACK 0x8010b4ac [0x00000000]
 | STACK 0x8010b4b0 [0x00000000]
 | STACK 0x8010b4b4 [0x00000000]
 | STACK 0x8010b4b8 [0x00000000]
 | STACK 0x8010b4bc [0x80103a74]
 | STACK 0x8010b4c0 [0x00000000]
 | STACK 0x8010b4c4 [0x8010b504]
 | STACK 0x8010b4c8 [0x8010593f]

これを使いこなすのは、きついな。

CPUのモードが変わるとブレークしてくれる。

<bochs:1> modebp
mode switch break enabled
<bochs:2> c
(0) Caught mode switch breakpoint switching to 'protected mode'
Next at t=330299
(0) [0x0000000f99b7] f000:00000000000099b7 (_start+ffff99ab): jmpf 0x0010:000f99bf      ; 66eabf990f001000
<bochs:3> c
(0) Caught mode switch breakpoint switching to 'real mode'
Next at t=1334076
(0) [0x000000038006] 3000:8006 (_start+fff37ffa): mov al, byte ptr ds:[ebx] ; 678a03
<bochs:4> c
(0) Caught mode switch breakpoint switching to 'protected mode'
Next at t=1334086
(0) [0x0000000e020a] 0010:00000000000e020a (_start+fffe01fe): mov dl, 0xb3              ; b2b3
<bochs:5> c
(0) Caught mode switch breakpoint switching to 'real mode'
Next at t=1522803
(0) [0x0000000f9a0d] 0020:9a0d (_start+ffff9a01): jmpf 0xf000:9a12          ; ea129a00f0
<bochs:6> c
(0) Caught mode switch breakpoint switching to 'protected mode'
Next at t=2187790
(0) [0x000000007c2c] 0000:0000000000007c2c (_start+fff07c20): jmpf 0x0008:7c31          ; ea317c0800
<bochs:7> c

何故こんなに、行ったり来たりしてるのだろう?

show call
show calls/returns: ON
show mask is: call
c
00000000161: call f000:168c (0x000f168c) (phy: 0x0000000f168c) _start+ffff1680
00000000170: call f000:092a (0x000f092a) (phy: 0x0000000f092a) _start+ffff091e

これは、膨大なログが出て来るので封印だな。

diff debian vs openbsd

OpenBSDで上手く仮想記憶が動かない件の原因を調べてみる。こういう時は、ちゃんと動くやつと比較するのが楽だ。オシロスコープで波形比較をする時は、波形の引き算をするのが楽だ。 高級なオシロスコープだと、この機能が付いている。新人にやらせると、一生懸命に波形を 見比べていて微笑ましい。結構年期の入った技術者でも、こういう使い方をする人はなかなかいない。

debianで、仮想記憶が働き出した直後の状態

(0) [0x000000100028] 0008:0000000000100028 (_start+1c): mov esp, 0x8010b5c0       ; bcc0b51080
<bochs:11> creg
CR0=0xe0010011: PG CD NW ac WP ne ET ts em mp PE
CR2=page fault laddr=0x0000000000000000
CR3=0x000000109000
    PCD=page-level cache disable=0
    PWT=page-level write-through=0
CR4=0x00000010: pke smap smep osxsave pcid fsgsbase smx vmx osxmmexcpt umip osfxsr pce pge mce pae PSE de tsd pvi vme
CR8: 0x0
EFER=0x00000000: ffxsr nxe lma lme sce

openbsdで仮想記憶が失敗した直後

<bochs:11> creg
CR0=0xe0010011: PG CD NW ac WP ne ET ts em mp PE
CR2=page fault laddr=0x0000000000000000
CR3=0x00000010a000
    PCD=page-level cache disable=0
    PWT=page-level write-through=0
CR4=0x00000010: pke smap smep osxsave pcid fsgsbase smx vmx osxmmexcpt umip osfxsr pce pge mce pae PSE de tsd pvi vme
CR8: 0x0
EFER=0x00000000: ffxsr nxe lma lme sce

debianでの変換状態

<bochs:12> info tab
cr3: 0x000000109000
0x00000000-0x003fffff -> 0x000000000000-0x0000003fffff
0x80000000-0x803fffff -> 0x000000000000-0x0000003fffff
<bochs:13> xp/x 0x000000109000
[bochs]:
0x0000000000109000 <bogus+       0>:    0x00000083

openbsdでの変換状態

<bochs:12> info tab
cr3: 0x00000010a000
<bochs:13> xp/x 0x00000010a000
[bochs]:
0x000000000010a000 <bogus+       0>:    0x00000000

Page table directoryを確認した。debianのそれは、0x83になってる。対してopenbsdは0x0。これらが何処で設定されるかと言うと、main.cの最後の所にある定義による。

__attribute__((__aligned__(PGSIZE)))
pde_t entrypgdir[NPDENTRIES] = {
  // Map VA's [0, 4MB) to PA's [0, 4MB)
  [0] = (0) | PTE_P | PTE_W | PTE_PS,
  // Map VA's [KERNBASE, KERNBASE+4MB) to PA's [0, 4MB)
  [KERNBASE>>PDXSHIFT] = (0) | PTE_P | PTE_W | PTE_PS,
};

mmu.h

#define PTE_P           0x001   // Present
#define PTE_W           0x002   // Writeable
#define PTE_PS          0x080   // Page Size

そのページは存在(Present)します。そして書き込み可能(Writeable)です。ページサイズは4kじゃありません。(この場合は4Mのジャンボサイズ)という設定が書き込まれているはずだけど、OpenBSDではそうなっていない。(インテルマニュアルだと、図3-15)

ページが存在しませんって事で、ページフォルト例外になったんだな。ただ、この段階では例外処理が動いていないので、暴走したんだな。寒い所を抜け出して房総へ行きたいな。これ、雪国の人の切なる願い。

ちゃんと動いているdebianで、少し先まで実行して、再びPDEを観測してみる。

<bochs:34> xp/x 0x000000109000
[bochs]:
0x0000000000109000 <bogus+       0>:    0x000000a3

先ほどは0x83だったのだけど、今度は0xa3になってる。

#define PTE_A           0x020   // Accessed
#define PTE_D           0x040   // Dirty

bitの並びから、このページがアクセスされましたって分かる。その他に、このページに書き込みがなされると、Dirtyのbitが立つ。

これらのフラグは、実メモリーが足りなくなった時に、どのページを破棄もしくはHDDに書き戻す(Dirtyの場合)かの目安に使われる。

なんて事を知ったのは、昔々にz80あたりを使っていた頃、なんでこの石に仮想記憶が付いて いないんだよぅって不貞腐れて、大型コンピュータの仮想記憶の仕組みを一生懸命に勉強してた時に知った知識だ。

こういう機構がついたコンピュータが机上にのり、しかもエミュレータで体験出来るって、思えば遠くに来たものだ。(と、遠くを見る目ですよ)

上では、PDEの内容を調べるのにbochsを使ったけど、gdbだと下記のようにする。PDEの真ん中は、配列のindexだと512だけど、intの配列だとbyte変位は2048(4x512)となる。

(gdb) x/wx 0x109000
0x109000:       0x00000083
(gdb) x/wx 0x109000+2048
0x109800:       0x00000083

ちゃんと動かないopenbsdで、gdbを使ってメモリーにpatch(書き換え)をしてみる。

(gdb) x/wx 0x10a000
0x10a000:       0x00000000
(gdb) set {int}0x10a000 = 0x83
(gdb) set {int}(0x10a000+2048) = 0x83
(gdb) x/wx 0x10a000
0x10a000:       0x00000083
(gdb)
  :
=> 0x100025:    mov    %eax,%cr0
0x00100025 in ?? ()
(gdb)
=> 0x100028:    mov    $0x8010c870,%esp
0x00100028 in ?? ()
(gdb)
=> 0x10002d:    mov    $0x80103380,%eax
0x0010002d in ?? ()
(gdb)
=> 0x100032:    jmp    *%eax
0x00100032 in ?? ()
(gdb)
=> 0x80103380 <main>:   lea    0x4(%esp),%ecx
main () at main.c:19
19      {

今度は、ページフォルトする事なく、仮想記憶が働いてちゃんとmainまで到達した。 contするとパニ喰った。

(gdb) bt
#0  0x801004e4 in panic (s=0x801079e6 "") at console.c:121
#1  0x801028ab in kfree (v=0x0) at kalloc.c:65
#2  0x80102942 in freerange (vstart=0x0, vend=0x80400000) at kalloc.c:52
#3  0x801033b5 in main () at main.c:20

make bochs

bochsが丁寧に(きっと石のBUGを含めて)エミュレーションしてくれるんで、FreeBSDにも 入れておこうと思った。(昔のミニコンPDP11はもう退役でいいよね)

portsを探ったら

If you want to change the processor level to emulate (default is
5, aka Pentium), set WITH_CPU_LEVEL to the desired value. Choices
are 3, 4, 5 and 6 which mean target 386, 486, Pentium or Pentium
Pro emulation.

色々なバージョンに対応してるそうだ。

make patch してpatchを充てた後、workの下でconfigしてbochsを作ってみた。まあ素のtar玉を展開して作るよりは、安心だろう。機能は最低限に抑えておいた。

なんだかんだ言いながらコンパイルすると、約1分でbochsが出来上がった。起動してみると

/usr/local/lib/compat/libstdc++.so.6: version CXXABI_1.3.8 required by /usr/ports/emulators/bochs/work/bochs-2.6.9/bochs not found

なんかバージョンが違うものが取り込まれたみたい。

[fb11: bochs-2.6.9]$ locate libstdc++.so
/usr/local/lib/compat/libstdc++.so.6
/usr/local/lib/gcc5/libstdc++.so
/usr/local/lib/gcc5/libstdc++.so.6
/usr/local/lib/gcc5/libstdc++.so.6.0.21
/usr/local/lib/gcc5/libstdc++.so.6.0.21-gdb.py
/usr/local/lib/gcc6/libstdc++.so
/usr/local/lib/gcc6/libstdc++.so.6
/usr/local/lib/gcc6/libstdc++.so.6.0.22
/usr/local/lib/gcc6/libstdc++.so.6.0.22-gdb.py
/usr/local/lib32/compat/libstdc++.so.6

不思議に思ってlibstdc++を検索してみると、色々あるな。gcc6を使ってるのでgcc6配下のものを使って欲しいんだけどな。(多分ね)

長年の勘で、 gccとccのせめぎ合いで勝つのはgccなんだな。ならばclang由来のccを使わせよう。下記のように指定。でコンパイルを始めたら、readline/readline.hが見つからないと言われた。portsで入れてあるやつは、見てくれない、つれない仕様のようだ。bx_debug/Makefileを修正したら、無事にコンパイル成功。

[fb11: bochs-2.6.9]# CXX=c++ ./configure  --enable-debugger --with-nogui
[fb11: sakae]$ bochs --help cpu
========================================================================
                       Bochs x86 Emulator 2.6.9
               Built from SVN snapshot on April 9, 2017
                  Compiled on Feb  9 2018 at 15:51:28
========================================================================
Supported CPU models:

bx_generic
pentium
pentium_mmx
amd_k6_2_chomper
p2_klamath
p3_katmai
p4_willamette
core_duo_t2400_yonah
atom_n270

00000000000i[SIM   ] quit_sim called with exit code 0

糞石と言っても、これだけのお仲間が居るのか。Intelも昔は陣営の拡大を狙ってセカンドソースを認めていたんだな。そのおかげで、悪貨は良貨を駆逐する状態を作り出したとな。我が世の春を貪っている間に、携帯用の石を孫親分に占領されてしまいましたと。ついこの間の発表では、売り上げでサムソンに抜かれたとか。

でも、サムスンは自前では何も新しいものをやってないんで、そのうちに勢いを失うだろうね。 で、インテルさんはAIに目覚めるか量子コンピューターに打って出る? でも、石と言う部品を 作っているだけじゃただの部品屋。なんとかググルにすり寄っていかなければ。ああ、無駄話。

早速走らせてみると、

<bochs:1> c
========================================================================
Bochs is exiting with the following message:
[BIOS  ] No bootable device.
========================================================================
(0).[47110057] [0x0000000f054a] f000:054a (unk. ctxt): out dx, al      ; ee

わけわかめなエラーに見舞われたぞ。ほー、bootデバイスが無いとな。sign.plでMBR風にしてる。それが実行されていないんだなと検討を付けてパあるファイルを見たら、なんと/usr/bin/perlになってた。FreeBSDでは、/usr/local/bin/perlなんだけどな。

これと、vectors.plを修正したらちゃんと走り出した。仮想記憶も有効に動いているしね。 ただ、途中でトラップを食らって落ちるのはdebianのそれと一緒。

使ったgccは、6.4.0だった。OpneBSDはgccが古いから?それともセキュリティチェックで特殊な事をやってて、その影響かな。深く追うのは止めよう。

qemu tips

わざわざbochsでi386の石を作っておいてあれだけど、qemuでも特権レジスター類の値がどうなってるか、調べられるのね。

QEMU/Monitor

xv6...
cpu1: starting 1
cpu0: starting 0
sb: size 1000 nblocks 941 ninodes 200 nlog 30 logstart 2 inodestart 32 bmap star
t 58
init: starting sh
$ QEMU 2.10.0 monitor - type 'help' for more information  ;; Ctl-a c
(qemu) info cpus
* CPU #0: pc=0x00000000801042e0 thread_id=46470
  CPU #1: pc=0x0000000080102700 thread_id=46470
(qemu) info registers
EAX=fee00000 EBX=00000200 ECX=00000001 EDX=00000000
ESI=80112780 EDI=80112784 EBP=8010b518 ESP=8010b518
EIP=80102700 EFL=00000086 [--S--P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0000 00000000 00000000 00000000
GS =0000 00000000 00000000 00000000
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0028 80112788 00000067 00408900 DPL=0 TSS32-avl
GDT=     801127f0 0000002f
IDT=     80114ca0 000007ff
CR0=80010011 CR2=00000000 CR3=003ff000 CR4=00000010
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
EFER=0000000000000000
 :

xv6を起動しておいて、Ctl-a c で、qumeのモニターに入り、用を足す。

(qemu) xp/10i 0x10000c
0x0010000c:  mov    %cr4,%eax
0x0010000f:  or     $0x10,%eax
0x00100012:  mov    %eax,%cr4
0x00100015:  mov    $0x109000,%eax
0x0010001a:  mov    %eax,%cr3
0x0010001d:  mov    %cr0,%eax
0x00100020:  or     $0x80010000,%eax
0x00100025:  mov    %eax,%cr0
0x00100028:  mov    $0x8010b5c0,%esp
0x0010002d:  mov    $0x80102e80,%eax
(qemu)   ;; Ctl-a c
pwd
exec: fail
exec pwd failed
$        ;; Ctl-a h
C-a h    print this help
C-a x    exit emulator
C-a s    save disk data back to file (if -snapshot)
C-a t    toggle console timestamps
C-a b    send break (magic sysrq)
C-a c    switch between console and monitor
C-a C-a  sends C-a     ;; Ctl-a x
QEMU: Terminated

逆アセンブルとかを楽しんだら、Ctl-a c で、Monitorを抜ける。(xv6に復帰) そして、今度はCtl-a hで、キーボードコマンドを確認してみた。こういう事が出来るのは、良い事だ。

(qemu) info mem
0000000080000000-0000000080100000 0000000000100000 -rw
0000000080100000-0000000080108000 0000000000008000 -r-
0000000080108000-000000008e000000 000000000def8000 -rw
00000000fe000000-0000000100000000 0000000002000000 -rw

info tlb なんてのも有るぞ。