ddb
前回からRTCの事を、チマチマと調べている。手持ちの資料が無いかと当たった所、 『FreeBSDのブートプロセスをみる』なんていう、今は亡きユニマガの連載をまとめた 物が出てきた。
このムックの80頁の所に、cpu_startupを読むが有り、その前振りとして3つの時計が 出てた。基本はリアルタイムクロック(RTC)である。オイラーが追ってたやつ。 MC146818Aというモトローラ社のやつが元祖。
こいつにアクセスするには、I/Oポートの70Hにアクセスして、石内部のレジスタ番号を 書き込み、続いて71Hにアクセスして、データを読み書きするとな。
二番目は、プログラマブル・インターバルタイマー。巷では8254と言う24pinのICが 有名。内部には16Bitのカウンターが3本収納されるとか。1番目が、100Hzで割り込みを かけてくるように使う。カーネル変数で言うHZってやつかな。2番目は、DRAMの リフレッシュ用。3番目は、ビープ音の発信器として使ってるらしい。
三番目はタイムスタンプ・カウンター。略してTSC。CPU内に収納されてる64Bitの カウンター。CPUがリセットされるとZEROに初期化され、CPUクロックでインクリメント される。 パソコンの寿命が尽きても、オーバーフローする事が無いという強力なやつ。 RDTSCって命令で、カウンター値を読み出すそうな。
ムックをブラウジングしてたら、127ページにRTCの持ってるエラーステータスの 事が出てた。8Bitのステータスのうち上位6Bitが定義されてる。vmmな仮想PCを OpenBSDで立ち上げた時のエラーは、この表に当てはめると、構成情報エラーって 事になる。
こういった情報をWebで探し回ると大変。手元にこういう資料が有ると助かるな。
そして、このムック本の最後に、カーネルデバッグって解説が載ってた。 FreeBSDでdebuggerと言うと、アセンブラーレベルを対象にするddbとソースレベルで debug出来る、kgdb(実は普通のgdb)が有るとな。両方を上手く使い分けると いいよと、アドバイスされてた。
ddb
そんな訳で、OpenBSDにもddbを入れて野郎。えと、どうやるんだ? そんな時は迷わずに man ddbだな。それによると、ddbがイネーブルになったカーネルが標準らしい。
普段は隠れているけど、OSがパニクッた時に、牙を剥いてくるらしい。証拠物件を押さえる 積りなんだな。
その他にも、sysctl ddb.console=1 にしておくと、キーコンビネーションで、ddbに 落ちる事が出来るとか。
# sysctl ddb.console=1 sysctl: ddb.console: Operation not permitted
あれれ、拒否されたぞ。何故? sysctlのソースをざっと見したけど、該当箇所を発見 出来ず。多分、使ってる端末がコンソールじゃなくてはいけないとかだろうな。
もし、そうだとすると、VMWAREのコンソールって小さくて、年よりには見にくいのよね。 15インチのパソコン画面で、解像度がipad並みとはいかないけど、1900x1024ぐらい 有ったから、小さくなっちゃうんだな。
だったら、vboxではどうか? こちらの方には、今の所Debianを入れているけど、 若干、画面が広かったように思う。でも、32Bit時代にOpenBSDを入れようとして、 ことごとく失敗してたからなあ。
悩む前に手を動かせ!! 入れるOSはOpenBSDですって指定しただけで、64Bit版の OpenBSDですねって言ってきた。脈が有りそう。すんなり入った。あの32Bit時代の 失敗につぐ失敗は何だったんだろう?
折角なので、カーネルを作る時のオプションを options(4)を見て、設定してから カーネルを作り直そう。
makeoptions DEBUG="-g option DDB_SAFE_CONSOLE option DDB_STRUCT
残念ながら、gdb似のKGDBとは排他の関係にあるので、FreeBSDみたいに同時使用は不可 だった。なお、一番大事な、option DDB は、デフォで定義されてるので、自分のconfigに 書くと、律儀な事に2重定義だって文句を言われる。テオ親分の何が何でも、ddb入りの カーネルを作れって言う、強い意志が見え隠れしてますなあ。
で、嬉しい事に、vboxのコンソールに表示メニューで、表示倍率が変えられるように なってた。150%を選んでおいた。
Alt+Ctl+Esc で、何時でもddbに落ちる事が出来たよ。
ddbのコマンド
debuggerなんで、使い方はgdbとかと一緒。BP貼って、走らせて、止まったら、どういう 経路でやって来た? 今の状況は? 次は一歩づつ進むか、次のBPに出会うまで、一気に 走れかぐらい。細かい事は、ddb(4)ね。
break hoge BPをhogeに置く。hogeは補完してくれないので、別OSを立ち上げておいて 調べておく事 delete hoge hogeに置いたBPを取り下げる show breaks BPの一覧表示 show registers レジスタの値表示 print hoge hogeの内容表示、特定regを見たい時は、$eipとかとする x/i hoge hoge番地を逆アセンブル x/w hoge hoge番地の内容表示 trace callの履歴表示、gdbで言うbt c 次のBPまで走れ s ステップ実行 n 今のサブルーチンの最後まで実行
取り合えず、これぐらい知ってれば、gdbユーザーがddbに乗り換え可能だな。
ddbへの突入方法
上でもちょっと書いたけど、ddbへ突入する方法は色々ある。manから拾ってみると、 ddb.consoleが1にセットされてる時に、rootがddbを呼び出せるとな。
このddb.consoleが1を満たす為には、DDB_SAFE_CONSOLをイネーブルにしてカーネルを コンパイルする必要が有る。下記は、それをやってないもの。
# sysctl | grep ddb ddb.radix=16 ddb.max_width=80 ddb.max_line=24 ddb.tab_stop_width=8 ddb.panic=1 ddb.console=0 ddb.log=1 ddb.trigger=0 # sysctl ddb.console=1 sysctl: ddb.console: Operation not permitted
どうも、ddb.consoleの値は、コンパイル時に埋め込まれる定数になってるようだ。 このメインSWが1(on)になっていないと、ddbに気軽には入りこめない。
この事に気が付いたのは、32Bit機で実験してみたから。イネーブルになってると、 Ctrl-Alt-Escでddbが起動してくる。VMWAREのコンソールでこのコンビネーションを 試すと、VMWAREに横取りされちゃって、キーのフォーカスが外れてしまう。
Ctrl-Alt-Deleteでも、条件を満たせば、ddbに入れるそうだけど、このコンビネーションは、 今度はWindowsに横取りされちゃって、ログアウトするか等と言った無骨な画面が出て しまい、途方に暮れる事になる。
最後の手段として、sysctl ddb.trigger=1 がある。これなら、文句なくddbへ突入 出来るぞ。(但し、実行は、rootユーザーがconsoleからって条件が付くけど) aliasにでも、登録しておくと奇知。
# sysctl ddb.trigger=1 sysctl: ddb.trigger: Operation not supported by device # tty /dev/ttyp0
これ、sshした端末での出来事。 そりゃそうだ。ddbに落ちている間、sshでの通信が 途絶えてしまいますからねぇ。がっつり、物理的に接続された端末から実行しろってのは、 頷けますよ。
また、ddbの大きなアドバンテージとして挙げられるのが、カーネル起動の非常に早い段階 から使える事だ。それをやるには、bootのプロンプトが出てる時、すかさず、-dを入力 する事。そんなのトロいオイラーには無理。OpenBSDの名だたる開発者達の集合写真を 見た事があるけど、正直言って、ロートルばっかり。
で、彼らも考えた。生命線のddbに楽に突入する方法を。
$ cat /etc/boot.conf set timeout 10
こんな風に書いておくと、10秒の猶予が与えられるとな。また、boot時から、いきなりddbに 入りたい時は、set debug と書いておけば良いらしいんだけど、オイラーが試したら、 シンタックスエラーで、機能しなかった。まあ、そんなものだろう。特殊機能ですから。
Loading..... probing: pc0 mem[639k 254M a20=on] disk: hd0+ >> OpenBSD/amd64 BOOT 3.31 boot> -d booting hd0a:/bsd: 6853429+.... entry point at 0x1001000 [7205c766, .....] [ using 1165688 bytes of bsd ELF symbol table ] Stopped at Debugger+0x9: leave ddb> trace Debugger() at Debugger+0x9 init_x86_64() at init_x86_64+0x740 end trace frame: 0x0, count: -2 ddb>
この表示は、コンソールに出てきたのを、手コピペしたもの。だから数字の羅列は、...で お茶を濁している。全く、VGAの端末はコピペもすんなり出来ず、困ったものです。 だったら、写真に取れと言うのは激しく拒否します。
探偵ものの小説を読んでいると、探偵が変な理屈を付けて、容疑者とおぼしきアジトに 侵入して、証拠書類をスマホ(or携帯)で、カシャカシャ撮りまくるってのが、よく出て くる。もう、写真を撮るってのは、当たり前なんだなあ。そんな事からすると、オイラーは 紛れもなく、昭和の人間。そう、昭和40年代ですかね。
あの頃は、テレタイプががちゃがちゃ動いて、何でも紙に記録してたなあ。へまをすると コンピュータが暴走して、ロール紙を1巻ぐらいあっと言う間に使い果たした。紙の 海に埋もれてしまう事有ったぞ。
ddb のログ
このblogに是非、ddbのセッション結果を張り付けてみたい。と言ってもmanを調べる 限りでは、
$log Controls whether the output of ddb will also appear in the system message buffer.
これしか説明が無い。 $logを調べても、無常にも1って表示されるだけ。ちゃんと、説明を 嫁よ。system message bufferって書いてあるじゃん。これって、dmsgで読めるやつ かいな。ビンゴでしたよ。
下記は、OSが起動した後、ddbに落ちて、traceした結果。
Stopped at Debugger+0x9: leave ddb> machine print examine x search set write w delete d break dwatch watch step s continue c until next match trace call ps callout show boot help hangman dmesg ddb> Debugger() at Debugger+0x9 internal_command() at internal_command+0x155 wskbd_translate() at wskbd_translate+0x90 wskbd_input() at wskbd_input+0x55 pckbd_input() at pckbd_input+0xb4 pckbcintr_internal() at pckbcintr_internal+0x8e intr_handler() at intr_handler+0x28 Xintr_ioapic_edge1() at Xintr_ioapic_edge1+0xc9 --- interrupt --- acpicpu_idle() at acpicpu_idle+0xcb cpu_idle_cycle() at cpu_idle_cycle+0x10 end trace frame: 0x0, count: -10 ddb>
何処を回っていたかが表示されているので、これを手掛かりにして、カーネルソースを 摘み読み出来るな。
hangman
上のhelpで、ddbコマンドとは似ても似つかないものが混じっている事に気付いた。 hangmanって、単語あてゲームだったよな。提示された、ブランクを埋めて、正しい 単語を推測するやつ。
期限まで推測出来ないと、縛り首されると言う、恐ろしいゲーム。まるで、地獄で 閻魔様に裁きを受けているみたい。正解すると、放免されるけど、不正解だと 一生地獄で虐められる。
hangman [/s[0-9]] This is a tiny and handy tool for random kernel hangs analysis, of which its depth is controlled by the optional argument of the default value of five. It uses some sophisticated heuristics to spot the global symbol that caused the hang. Since the discovering algorithm is a probabilistic one, you may spend substantial time to figure the exact symbol name. This smart thing requires a little of your attention, the input it accepts is mostly of the same format as that of the famous hangman(6) game, to which it, apparently, is obliged by the name. Hint: the nm(1) utility might help.
ddbの中だとあれなんで、明るい所でプレーしてみる。
[ob: ~]$ hangman ______ | | | O Guessed: efhnst | | | Word #: 1 | Current Average: 2.000 __|_____ Overall Average: 0.000 | |___ |_________| Word: --n-e-s-t--n--e Guess: Sorry, the word was "conversationize" Another word?
最初、何も無い所からスタートするので、ヒットしそうな文字から入力してみるのが定石。 ヒットしそうな文字って何だ? 英文の文字出現頻度だな。これを題材にした小説は、ポーの 『黄金虫』だったような。
どんな文字が高頻度だろう?そんなのググルしてみろ。ネットに毒されてますなあ。 無線屋なら、欧文モールス符号を思い出せ。頻度順にト・ツーを決めると言う合理的 な方法を取っていたぞ。
一番短いモールス符号。トがeで、トトがiでトトトがsか。ツーがtで、ツーツーがmか。 それより、トツーの方が短い? トツーならaだな。ツートがnとな。
より正確な頻度は、プログラミングHaskellの暗号解読に利用されてたな。
> table = [8.2, 1.5, 2.8, 4.3, 12.7, 2.2, 2.0, > 6.1, 7.0, 0.2, 0.8, 4.0, 2.4, 6.7, > 7.5, 1.9, 0.1, 6.0, 6.3, 9.1, 2.8, > 1.0, 2.4, 0.2, 2.0, 0.1]
浮動小数点の配列。最初のエレメントがaの頻度。最後がzの頻度のようにアルファベット順に 頻度が書かれている。これによると、aの頻度は、8.2%とな。 eは12.7%で一番高いのかな。
話は戻って、途中まで推測出来た場合(上のような時)、カンニングしちゃうって手も あるぞ。
[ob: ~]$ egrep '..n.e.s.t..n..e' /usr/share/dict/words conversationize
閻魔様だって、全ての単語を知ってる訳ではない。単語帳からこっそり単語を選んで きて、偉そうに出題してるだけ。だったら、こちとらも、文明の利器を使って、 探しだしちゃえ。まあ、バレたら、どうなるかは知らないけど。
ddbのhngmanの出題範囲はnmによると、手をばらしている。一体、nmにはいくつの単語が 登録されてるの?
[ob: ~]$ nm /bsd | wc 30359 91077 1052773 [ob: ~]$ nm /bsd | fgrep ' T ' | wc 20928 62784 742807
全体では3万を超える。グローバルな関数だけでも2万個以上。残りは、定数とか変数名 になるかな。
[ob: z]$ nm /bsd | fgrep ' T ' | cut -d ' ' -f 3 | ./cnt.rb | sort -nr 38882 _ 28691 e 23808 t 21169 i 20928 20836 a 18358 r 17216 s 16695 c 13259 p 12691 d 12666 n 12612 o 11086 m 10143 l : 13 G 12 Y 5 W 4 Q
これ、関数名に使われている文字の出現頻度。アンダーバーが一番多いって、ラクダ型の 名前付けじゃなくて、スネーク型なのね。次からは、e,t,iの順で、20928の所は、空白は ありえないので、改行コードか。そうでっしゃろか > rubyさん。
cnt.rbは、遥か昔を思い出して書いたもの。
#! /usr/local/bin/ruby cnt = Hash.new(0) while ln = ARGF.gets() ln.split('').each {|c| cnt[c] += 1 } end for k in cnt.keys do printf("%d\t%s\n", cnt[k], k) end
それじゃ、合成語で出来た関数名がどれぐらいあるか、調べてみる。合成語は、アンダーバー で語を接続してるんで、各行毎にアンダーバーの個数を数えればいいんだな。
[ob: z]$ nm /bsd | fgrep ' T ' | cut -d ' ' -f 3 | ./comp.rb | sort -nr 8030 2 6725 1 3281 3 1440 0 1119 4 229 5 96 6 7 7 1 8
一番多いのは、3語の単語を、アンダーバー2個で接続してるやつか。これは意外な 結果だな。まてまて、説明的な関数名を積極的に採用してるって事だ。
[ob: z]$ cat comp.rb #! /usr/local/bin/ruby cnt = Hash.new(0) while ln = ARGF.gets() cnt[ ln.count('_') ] += 1 end for k in cnt.keys do printf("%d\t%s\n", cnt[k], k) end
一つ、ひな型を作っておくと、ちょっと変形するだけで、色々使えて便利だな。 matzさん、ありがとう。
[ob: ~]$ nm /bsd|fgrep ' T '|cut -d ' ' -f 3|egrep '.*_.*_.*_.*_.*_.*_.*_.*' ixgbe_set_lan_id_multi_port_pcie_82598 radeon_connector_encoder_get_dp_bridge_encoder_id radeon_get_atom_connector_info_from_object_table radeon_get_atom_connector_info_from_supported_devices_table radeon_legacy_get_ext_tmds_info_from_combios radeon_legacy_get_ext_tmds_info_from_table ttm_bo_device_buffer_objects_RB_INSERT_COLOR ttm_bo_device_buffer_objects_RB_REMOVE_COLOR [ob: ~]$ nm /bsd|fgrep ' T '|cut -d ' ' -f 3|egrep '.*_.*_.*_.*_.*_.*_.*_.*_.*' radeon_get_atom_connector_info_from_supported_devices_table
もう、笑っちゃうぐらい素敵な(病的な)関数名を炙り出してみました。こんな問題を、 hangmanが出してきたら、絶対に答えられないよ。ddbでhangmanは封印しとけ。 触るな危険 だから。。。。
おまけで、Linux方面はどうなってるかちょっと当たってみた。 リナのカーネルは圧縮されてて nm なんでやるもんじゃないぞってスタンス。 その代わり、Debian君では、System.mapが提供されてた。これをnmの代わりにしろって事だ。
sakae@debian:/boot$ cat System.map-3.16.0-4-amd64|awk '$2 ~ /T/ {print $3}'|wc 13423 13423 256786
カーネルの機能がモジュールに追い出されているんで、以外にすっきりしてるっぽいな。
そして、冒頭からアンダーラインで始まる(例えば、__check_regionみたいなの)やつが 結構居るな。OpenBSDには、こういうの居なかったような。文化の違いなのかな。
etc
久しぶりにawkを使ってみたけど、なかなか良いね。BSDの正統なawkとごちゃごちゃ機能 てんこ盛りのgawkがあるけど、gawkに毒されるなってのが、今のオイラーの主張。 gawkを使いたくなったら、それはrubyにスイッチしろと言う赤信号が点灯したと 思っておこう。
上で書いたawkの一行野郎は、ちょっと冗長だった。awk '/ T /{print $3}' の方が スマートだな。/ T / が、grep相当で、対象行の選択。大カッコの中が、cut相当。
printは長すぎるんで、pぐらいでも良いようにして欲しかったけど、そこまでやると、 後継のperlや、そのまた後継のrubyは、この世に誕生していなかったかも。そうすると、 何処を向いても、python一色の世界で、つまらない世になったかも。