i386 on vmm
Table of Contents
i386 on vmm
環境の作成
クライアントはi386にする。だってqemuと違って、gdbでカーネル観光なんで 出来無いから使えるのはddbだけだ。ならば、アドレス表示は短かいのに限るからね。
doas rcctl -f start vmd doas vmctl create -s 2G i386.img doas vmctl start -c -i 1 -L -m 1G -B cdrom -r install78.iso -d i386.img i386 doas vmctl start -c -i 1 -L -m 1G -d i386.img i386
ダエモン君を手動で起動。2Gの仮想DISKを作成。そしてインストール。次は仮想DISK から起動。インストール時の設定がメールになってた。今迄気にする事はなかったな。
Subject: i386.my.domain install response file System hostname = i386 Network interface to configure = vio0 IPv4 address for vio0 = autoconf IPv6 address for vio0 = none Network interface to configure = done Start sshd(8) by default = no Do you want the X Window System to be started by xenodm(1) = no Change the default console to com0 = yes Which speed should com0 use = 115200 Setup a user = sakae Full name for user sakae = sakae Which disk is the root disk = sd0 Encrypt the root disk with a (p)assphrase or (k)eydisk = no Use (W)hole disk or (E)dit the MBR = whole Use (A)uto layout, (E)dit auto layout, or create (C)ustom layout = c Location of sets = cd0 Pathname to the sets = 7.8/i386 Set name(s) = -x* Set name(s) = -g* Set name(s) = done Directory does not contain SHA256.sig. Continue without verification = yes Location of sets = done What timezone are you in = ? What timezone are you in = Japan
下記はホスト側の状況
24234 ?? Ip 0:00.00 /usr/sbin/vmd
16247 ?? Ic 0:00.00 vmd: priv (vmd)
7120 ?? Ipc 0:00.00 vmd: control (vmd)
69141 ?? IpUc 0:00.01 vmd: vmm (vmd)
2213 ?? Ipc 0:00.00 vmd: agentx (vmd)
fu$ vmctl stat
ID PID VCPUS MAXMEM CURMEM TTY OWNER STATE NAME
1 - 1 1.0G - - root stopped i386
起動後の状況
fu$ vmctl stat
ID PID VCPUS MAXMEM CURMEM TTY OWNER STATE NAME
1 3620 1 1.0G 192M ttyp4 root running i386
上記では、起動時にオプションを色々と指定した。これをエイリアスにしとくって手 も有るけど、vm.confにパラメータを設定しておくと、vmdの起動時に、それを読み こんで、環境を用意してくれるぞ。
cat /etc/vm.conf
switch "uplink" {
interface veb0
}
vm "i386" {
disable
memory 1G
disk "/ram/home/sakae/MY/home/VMM/i386.img"
interfaces 1
interface { switch "uplink" }
}
後はダエモン君のリモコンを使って、起動/停止するだけだ。
doas vmctl start -cL i386 doas vmctl console i386 doas vmctl stop i386
ブリッジの設定
fu$ cat /etc/hostname.veb0 add em0 add vport0 up
仮想NICの設定
fu$ cat /etc/hostname.vport0 inet autoconf
クライアント側のネットワーク状況
i386$ ifconfig
vio0: flags=808b43<UP,BROADCAST,RUNNING,PROMISC,ALLMULTI,SIMPLEX,MULTICAST,AUTOCONF4> mtu 1500
lladdr fe:e1:bb:d1:05:0a
index 1 priority 0 llprio 3
groups: egress
media: Ethernet autoselect
status: active
inet 100.64.1.3 netmask 0xfffffffe
ホスト側のネットワーク状況
fu$ ifconfig
veb0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST>
description: switch1-uplink
index 5 llprio 3
groups: veb
em0 flags=3<LEARNING,DISCOVER>
port 1 ifpriority 0 ifcost 0
vport0 flags=3<LEARNING,DISCOVER>
port 6 ifpriority 0 ifcost 0
vport0: flags=808943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST,AUTOCONF4> mtu 1500
lladdr fe:e1:ba:d0:e1:c1
index 6 priority 0 llprio 3
groups: vport
tap0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
lladdr fe:e1:ba:d1:1c:9b
description: vm1-if0-i386
index 8 priority 0 llprio 3
groups: tap
status: active
inet 100.64.1.2 netmask 0xfffffffe
ddb
何も手を加えていないカーネルでddbできるか確認。
>> OpenBSD/i386 BOOT 3.67 \ com0: 115200 baud switching console to com0 >> OpenBSD/i386 BOOT 3.67 boot> -d booting hd0a:/bsd: 10889747+2712580+208904+0+1146880 [796202+107+637824+699166]=0x104ef74 entry point at 0x201000 [ using 2133876 bytes of bsd ELF symbol table ] Stopped at db_enter+0x4: popl %ebp ddb> c
起動時5秒以内に、-d して、カーネルが起動した早い段階でddbに入る様に指示。 ddbがシンボルテーブルを検出し、バナーが出る前に、ddbに突入してくれた。
i386# sysctl -a | grep ddb ddb.radix=16 ddb.max_width=80 ddb.max_line=25 ddb.tab_stop_width=8 ddb.panic=1 ddb.console=1 ddb.log=1 ddb.trigger=0 ddb.suspend=0
ddb.console=1は、/etc/sysctl.confに記述しとくメインSWだな。
i386# sysctl ddb.trigger=1 Stopped at db_enter+0x4: popl %ebp ddb> bt db_enter() at db_enter+0x4 ddb_sysctl(f6657cb8,1,cf7c3210,f6657cb0,cf7c1210,4,d348f69c) at ddb_sysctl+0xc2 sys_sysctl(d348f69c,f6657d50,f6657d48) at sys_sysctl+0x169 syscall(f6657d90) at syscall+0x3a3 Xsyscall_untramp() at Xsyscall_untramp+0xa9 end of kernel ddb> c
メインSWがONになっていれば、任意のタイミングでddbをトリガーできるとな。 これは便利なんで、alias ddbon='sysctl ddb.trigger=1' と、登録しておこう。
huffman-morse.py
C言語とシェルスクリプトの「隙間」から生まれたのがPythonだそうだ。そして、 この前ひょんな事からnumpyに誘導されて、その威力を知った。もうpythonは避けて 通れないので、そこはかとなく触れてみる。そんな事なんで、楽しい課題を思い ついた。gzipの関連をチャッピーに指示した時の話題です。
Q: ハフマン復号なんて語句が出てきたので、モールス符号を思いだしました。 ハフマン・アルゴリズムを使って、pythonのソースコードを効率よく送信 する為のモールス符号を発明して下さい。 条件は、次の通りとします。
1. pythonに許される文字種 2. pythonで予約された語 3. pydoc用の """ 4. ソースの検索対象は、標準添付モジュール
ソースをスキャン後、頻度を報告し、対応するハフマン符号を、モールス風に、 '.'と'-' で提示してください。
A: いいですね — 面白い課題です。
要求どおり、/標準添付モジュール(ローカルの Python 標準ライブラリ)を走査して頻度を集計し、その頻度に基づいてハフマン符号を生成(モールス風に . と - で表現)するスクリプト/を作りました。
設計方針の要点(あなたの条件に合わせた扱い):
- 1. pythonに許される文字種 → 実際のソースから現れたすべての文字(Unicode を含む)を「文字シンボル」として集計します(=CHAR:<c>=)。
- 2. pythonで予約された語
→
keyword.kwlistにある予約語は/単独のトークン/(=KW:<word>=)としてカウントします(キーワードを原子化して短い符号を割り当てられるように)。 - 3. pydoc用の
"""→ モジュール・クラス・関数の docstring はDOCSTRINGトークン(開始・終了の"""を含む意味で)としてカウントし、さらに docstring 本文の文字は通常の文字として別集計します(つまり docstring 自体を明示的に区切れるように)。 - 4. ソースの検索対象は、標準添付モジュール → デフォルトでは実行中の Python の標準ライブラリ(=sysconfig.getpaths()["stdlib"]= / =sys.baseprefix=)を再帰走査します。別パスを与えれば任意のディレクトリを走査できます。
スクリプトは次のことをします:
- 指定ディレクトリ内の
*.pyを再帰的に読み出す(デフォルトは実行中の Python の標準ライブラリ)。 astで AST を作り、モジュール/クラス/関数の docstring を検出してその位置の文字列をDOCSTRINGとして数える。tokenizeでトークンを走査し、/予約語/(=KW:…=)を個別に数え、それ以外の文字は /1文字ずつ/(=CHAR:<c>=)でカウント。STRING トークンが docstring に相当する場合はDOCSTRINGをインクリメント(実体の文字は別にカウント)。- 得られたシンボル頻度でハフマン木を構築し、=0->'.'
、=1->'-'のルールでモールス風コードを生成。 - 頻度テーブルと符号表を出力(上位 N を見やすく表示、全出力は CSV に保存可能)。
- スクリプトを保存して実行(標準ライブラリを走査):
python huffman_morse_for_python.py --top 60 --out stdlib_freq.csv
- ハイブリッド設計(キーワードをトークン化)
キーワードはソース中で非常に頻出し、かつ分離可能(単語境界がある)なので /個別トークンとして短いコード/を割り当てると効率が良くなります。モールス風に短い
.や-の列で表現できれば転送効率が上がります。 - DOCSTRING トークン docstring の開始・終了を特別に扱えると、転送側で docstring ボディを別の圧縮手段(例えばそのまま gzip/deflate など)で送ることも可能になり、柔軟性が出ます。
- 文字単位の符号化 残る部分(識別子、演算子、数値リテラル、文字列本文など)は文字単位でハフマン化します。これは変数名が多様でトークン化が困難なためです。
- 符号の表現
ハフマン木の左枝を
.=、右枝を =-にして「モールス風」にしています。ツリー形状により短い記号列が頻度の高いシンボルに割り当てられます。
code
csvの出力中に、不正なutf-8だと言われて数度追ちる事が当った。標準添付でも きちんとしていないのね。しっかりガードしたコードです。
import os
import argparse
import tokenize
import token
import csv
import ast
from collections import Counter
import heapq
# ============================================================
# 文字列を安全化(surrogate を完全排除)
# ============================================================
def safe_str(x):
"""Convert x to a UTF-8 safe printable string by replacing surrogates."""
if not isinstance(x, str):
x = str(x)
return x.encode("utf-8", "replace").decode("utf-8", "replace")
# ============================================================
# Huffman Tree
# ============================================================
class HuffmanNode:
def __init__(self, symbol=None, freq=0, left=None, right=None):
self.symbol = symbol
self.freq = freq
self.left = left
self.right = right
def __lt__(self, other):
return self.freq < other.freq
def build_huffman_tree(freqs):
pq = [HuffmanNode(symbol=s, freq=f) for s, f in freqs.items()]
heapq.heapify(pq)
if len(pq) == 1:
node = pq[0]
return HuffmanNode(symbol=node.symbol, freq=node.freq)
while len(pq) > 1:
a = heapq.heappop(pq)
b = heapq.heappop(pq)
merged = HuffmanNode(symbol=None, freq=a.freq + b.freq, left=a, right=b)
heapq.heappush(pq, merged)
return pq[0]
def generate_codes(node, prefix="", code_map=None):
if code_map is None:
code_map = {}
if node.symbol is not None:
code_map[node.symbol] = prefix
return code_map
if node.left:
generate_codes(node.left, prefix + ".", code_map)
if node.right:
generate_codes(node.right, prefix + "-", code_map)
return code_map
# ============================================================
# Python .py ファイルスキャン
# ============================================================
def scan_python_sources(base_path):
keywords = Counter()
chars = Counter()
doc_count = 0
file_count = 0
total_bytes = 0
for root, _, files in os.walk(base_path):
for fn in files:
if fn.endswith(".py"):
path = os.path.join(root, fn)
file_count += 1
try:
with open(path, "rb") as f:
data = f.read()
total_bytes += len(data)
text = data.decode("utf-8", "replace")
chars.update(list(text))
for tok in tokenize.generate_tokens(lambda L=iter(text.splitlines(keepends=True)): next(L)):
if tok.type == token.NAME and token.tok_name.get(tok.type) == "NAME":
if tok.string in python_keywords:
keywords[tok.string] += 1
elif tok.type == token.STRING:
if tok.string.startswith('"""') or tok.string.startswith("'''"):
doc_count += 1
except Exception as e:
print(f"[WARN] Cannot read {path}: {e}")
return keywords, chars, doc_count, file_count, total_bytes
python_keywords = {
"def", "if", "for", "while", "return", "class", "try", "except",
"in", "is", "not", "and", "or", "None", "True", "False", "with",
"as", "elif", "else", "yield", "lambda", "from", "import", "global",
"nonlocal", "assert", "async", "await", "raise", "del", "pass",
"break", "continue"
}
# ============================================================
# CSV と標準出力のレポート
# ============================================================
def pretty_report(keywords, chars, doc_count, codes, out_csv):
# print summary
print("\n-- Keywords (top) --")
for k, v in keywords.most_common(10):
print(f"{k:<15} {v}")
print("\nDOCSTRING occurrences:", doc_count)
print("\n-- Characters (top) --")
for c, v in chars.most_common(10):
print(f"{safe_str(c)!r:<6} {v}")
print("\n-- Top symbols with codes --")
sym_freq = Counter()
sym_freq.update(keywords)
sym_freq.update(chars)
for sym, cnt in sym_freq.most_common(20):
print(f"{safe_str(sym):<18} {cnt:<8} {codes.get(sym, '')}")
# CSV output (safe)
if out_csv:
with open(out_csv, "w", encoding="utf-8", errors="replace") as f:
w = csv.writer(f)
w.writerow(["symbol", "count", "code"])
for sym, cnt in sym_freq.items():
s = safe_str(sym)
c = safe_str(codes.get(sym, ""))
w.writerow([s, cnt, c])
print(f"\nCSV written to: {out_csv}")
# ============================================================
# Main
# ============================================================
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--path", default="/usr/lib/python3.12", help="scan base path")
ap.add_argument("--out", help="output CSV file")
ap.add_argument("--top", type=int, default=10)
args = ap.parse_args()
print("Scanning path:", args.path)
keywords, chars, doc_count, file_count, total_bytes = scan_python_sources(args.path)
print(f"Scanned {file_count} .py files, {total_bytes} bytes")
total_syms = sum(keywords.values()) + sum(chars.values())
print(f"Total symbol occurrences: {total_syms:,}")
# merge for Huffman input
combined = Counter()
combined.update(keywords)
combined.update(chars)
# build Huffman codes
print("\nBuilding Huffman tree and generating codes...")
tree = build_huffman_tree(combined)
codes = generate_codes(tree)
pretty_report(keywords, chars, doc_count, codes, args.out)
if __name__ == "__main__":
main()
more code
おまけでpureなASCIIファイル用のコード。
import sys
import heapq
from collections import Counter
def build_huffman_tree(frequencies):
"""
文字の頻度辞書からハフマン木を構築し、ルートノードを返します。
"""
# ヒープキューは (頻度, 文字または内部ノードのタプル) を格納する
# heapqは最初の要素(頻度)を優先して最小ヒープを構築する
heap = [[freq, char] for char, freq in frequencies.items()]
heapq.heapify(heap)
while len(heap) > 1:
# 頻度が最も低い2つのノードを取り出す
low1 = heapq.heappop(heap)
low2 = heapq.heappop(heap)
# 新しい内部ノード(親)を作成する
# ノード構造: [合計頻度, [左の子], [右の子]]
# 識別子としてタプルを使用し、ノードの比較でエラーが出ないようにする
parent = [low1[0] + low2[0], low1, low2]
# 親ノードをヒープに戻す
heapq.heappush(heap, parent)
return heap[0] # 最後に残ったルートノードを返す
def generate_huffman_codes(node, prefix="", codes={}):
"""
ハフマン木をトラバースして、各文字のハフマン符号を生成します。
"""
if len(node) == 2 and isinstance(node[1], str):
# リーフノード(文字)に到達したら、符号を辞書に格納する
# このスクリプトでは、出力形式に合わせて0を'.'、1を'-'に変換する
codes[node[1]] = prefix.replace('0', '.').replace('1', '-')
else:
# 内部ノードの場合、左右の子を再帰的に探索する
# 左の子には '0' を、右の子には '1' をプレフィックスに追加する
generate_huffman_codes(node[1], prefix + "0", codes)
generate_huffman_codes(node[2], prefix + "1", codes)
return codes
def main():
# 標準入力からテキスト全体を読み込む
text = sys.stdin.read()
if not text:
return
# 文字の出現頻度を計算する
# collections.Counterを使用すると効率的
frequencies = Counter(text)
# ハフマン木を構築する
root_node = build_huffman_tree(frequencies)
# ハフマン符号を生成する
huffman_codes = generate_huffman_codes(root_node)
# 結果を指定された形式で出力する
# 文字コード順(ASCII/Unicode順)でソートして見やすくする
for char in sorted(huffman_codes.keys()):
freq = frequencies[char]
code = huffman_codes[char]
# タブ区切りで出力: 文字 頻度 ハフマンBit
print(f"{char}\t{freq}\t{code}")
if __name__ == "__main__":
main()
下記は英単語辞書の変換例。最後の234937は単語数(改行の個数)。
sakae@lu:t$ cat /usr/share/dict/words | tr "A-Z" "a-z" | python3 gen.py | sort -k 2 -nr
e 234805 ...
i 200614 ---.
a 198939 --.-
o 170392 -.--
:
x 6926 -.-.-----
q 3729 -.-.----.-
j 3075 -.-.----..
234937 ..-
モールス符号ってハフマン符号そのものだと、今まで思っていたけど、違うのね。そりゃ そうだわさ。モールス符号が発明された時期には、コンピュータは出現していなかったから ね。新聞だとか本から地道に文字頻度を抽出して、コードを発明するって凄いな。 和文のモールス符号って、そういう形跡が全くないんだよな(どなたか、ご存知なら教え てください)。
python heapq
heapqって何だ? って、調べていたら、こんなのに出会った。
array/tuple/set/queueの魅力と使い分けフローチャート
チャッピーなり grok なり gemini にコードを作成させると、有用なモジュールを 利用してくれる。これを丁寧に見ていけば、ためになるぞ。
README
今、自分の蔵書を開いている。2008年にオライリーから出版された、 ビューティフルコードって本だ。どうだ、俺のコード凄いだろうって 自慢のエッセイを33人から寄稿してもらったもの。出版時の講演だかで 訳者の久野先生からサインを頂いた希少な本だ。
この中に、python凄いだろってのが2編有ったので読み返した次第。 1編は、辞書の実装について。もう一編は、Numpyでの配列イテレータの実装。 昔は、pythonに興味が無かったから、ほとんで読み飛ばし状態だったけど、 本腰を入れて読むと面白いな。
難しかったのは、配列のスライスが有る事。スライスすると新らしい配列が 論理的に誕生するんだけど、内部実装ではコピーを避けて共有する構造に なってるそうだ。それもイテレータで扱かえないと話にならない。 何とか、マクロを使って実現できたとか。人知れず苦労が有るんですなあ。
この他、matzさんのruby楽だ編とか、haskellの関数透明性を利用した機能とか 改めて読み直したい編が多数出てきたぞ。思わぬクリスマス・プレゼントだな。 そう言えば、恒例のrubyからのクリスマス・リリースも近いな。