sim-gdb.sh with tmux
Table of Contents
アイデア
前回、カーネルのバックトレースを眺めていて、後で簡単にソースをトレース できないかと、思ったものだ。散歩の途中で、ふとアイデアが浮んだので、蒸発 してしまう前に記録しとく。
そんなの簡単。vscodeを利用して、それぞれのファイルを読み込んでしまえ。 後は、それらをタブで切り替えるだけ。猫も杓子もvscodeって嫌いだ。大体 余計なものは入れない主義に反するぞ。却下であります。それじゃJK法の ルールに違反するじゃん。アイデアを否定しないルールね。
でも、これがヒントになって、tmuxなら何とかなるんじゃねぇ。丁度tmuxを 初めたばかりなんで、仕込みと疑うなかれ。NHKの人気番組、家族に乾杯で、 旨い具合に話が繋がるんで視聴者はきっと仕込みや台本が有るに違いないと 思っちゃう。釣瓶は、絶対に仕込みはやってない、と言う。今はまさに釣瓶の 心境ですよ。
tmuxを利用して、それぞれのファイルをviなりemacsで開いてしまえ。1画面に 1ファイルを割り当てる豪華な方法。viは軽いだろう。emacsもサーバー/クライアント モードで使っているから、軽いだろう。多分ファイルを10個ぐらい開くなら 平気の平座だと思う。後はgdb操作上のフレームのup/downはファイルの切り替え 、すなわち窓の切り替えで誤魔化せる。
[0] 0:ksh 1:emacsclient- 2:vi*
これ、tmuxのステータス・ラインだ。ここに開いているファイル名を表示させる? 1とか2はフレーム番号と読み替えればいいな。そして'*'は現在のフレーム。 もう、ピッタリのおあつらえな機能が用意されてるな。
これを実現するにはtmuxの森に踏み入る必要が有る。そこんとこを注意しながら、 そろりそろりと探検開始。
tmux(1)
tmux-home(git) OpenBSDは独自進化してるけど、本家はこちらみたいだ
大事なのは、セッション(session) ー ウィンドウ(window) ー ペイン(pane) と言う3階層になってるって事。 セッションは sshした先でtmuxした場合の親分に相当する。ウィンドウは、tmuxで、 shellを幾つも起動した場合に割り当てられる端末と言うか、それを収容する窓だ。 ペインは、その窓を上下とか左右に分割した場合の、それぞれのエリアを差す用語。
tmux command option って形式を使って、tmuxを操る。丁度gitと同じだな。って事は 山程、コマンドが存在するって事だ。そしてご多分に漏れず、それらのコマンドを キーボードのシーケンスで指定できる様になってる。マウスでグリグリするのは性に 合わないと言うCUI屋さんの方を向いた機構になってる。
manの説明が豊富すぎて圧倒されるって向きには、
ad$ tmux lscm attach-session (attach) [-dErx] [-c working-directory] [-f flags] [-t target-session] bind-key (bind) [-nr] [-T key-table] [-N note] key [command [arguments]] : unlink-window (unlinkw) [-k] [-t target-window] wait-for (wait) [-L|-S|-U] channel
アルファベット順のコマンド リストを眺めて当りを付ければいいだろう。コマンド名の すぐ右に有る括弧で囲まれたワードは、正式名の省略形になる。多少は入力の手間が 省けるかな。
ad$ tmux lsk -N C-t ! Break pane to a new window C-t " Split window vertically C-t # List all paste buffers : C-t S-Down Move the visible part of the window down C-t S-Left Move the visible part of the window left C-t S-Right Move the visible part of the window right
また、こんな風にすると、現在のキーバインドが確認できる(C-t ?で何時でも確認 できます)。これって、emacsからの 輸入ですかい? 詳細は、tmux(1)で、list-keys を検索、ってCMみたいだな。
.tmux.conf
上記のキーバインドが、C-t で初まっていた。本当は ! とかで機能を呼び出したいん だけど、それじゃ普通の ! と区別できない。そこで、C-t を前置してるんだ。これを プレフィックスと呼ぶ。丁度エスケープ・シーケンスみたいなものだ。
デフォルトでは、C-b が割り当てられている。けど、オイラーは押し易いって事で、 C-tにしてるんだ。こういった事は、~/.tmux.confに記述しておく事になってる。
set -g prefix C-t unbind C-b bind C-t send-prefix
上記はその設定部分だ。グローバルで使うよ。デフォの設定は取り消すよ。その設定 を有効にするよって意味になる。
bind-key -T prefix Space next-window
ウィンドウ間を渡り歩くキーシケンスとして、C-t n/C-t pがデフォで定義されてるけど、 ちと押しにくいので、C-t nと同様の機能をC-t Spaceにも割り当ててみた。
set-option -g history-limit 2500 bind-key C-p pipe-pane -o '/bin/sh -c "while read -r LINE; do echo \"\${LINE}\" >> /tmp/TMUX.log; done "' \; display-message "Logging start."
これは、tmuxが過去ログを保持するリミット値の設定。それから、TMUX.logとしてファイルに ロギングを開始させるキーストロークの登録。
bind-key C-q pipe-pane \; display-message "Logging end."
停止キーはこちら。
C-t C-p この間の画面情報は、ずっとロギングされます。 C-t C-q
の様に使う。うっかり停止するのを忘れると大変な事になるんで、普段は
tlog='tmux capture-pane -p -S -1500 > TMUX.log'
こんなエイリアスで、行数を限定して$HOMEに保存してる。
sim-gdb.sh
上のアイデアをChatGPTに発注した。今やMSあたりでも30%ぐらいはAIがコードを 作成してるからね。時流に乗り遅れるな、です。 発注書のあらましを挙げておきます。
OpenBSDのカーネルをgdbしてます。gdb中はソースをじっくり眺める余裕が ありません。そこで、得られるバックトレースをログに収集しておいて、 後でtmux(terminal multiplexer)の機能を利用して、ファイルを展開。 gdbでのフレームのup/downをtmuxでのウィンドウの移動(C-bn, C-bp)で 模倣するシュミレータを作成してください。 起動したウィンドウ(0:ksh)でqを入力 したら、開いているファイル(window 1以降)を安全に閉じて終了させたい。
シュミレータの冒頭部分と、ファイルを開く方法は、作りかけの下記を使って ください。
vm$ cat sim-gdb.sh #! /bin/sh cat backtrace.log | sed 's/.\* at //' | : tmux neww emacsclient +1869 /usr/src/sys/kern/tty.c :
参考までにsedからのパイプ出力は下記の様になっています。
/usr/src/sys/kern/tty.c:1869 /usr/src/sys/dev/wscons/wsdisplay.c:1031
紆余曲折ののち、下記のコードを 使う事にした。何が問題だったかと言うと、同名なファイルが有った場合 (よく有る事です)、emacsclientやviではブロックされちゃう事。それを 回避する為、view(samae as vi -R)を使う様に協議した。
code
#!/bin/sh SESSION="simgdb" LISTFILE="/tmp/simgdb_list.txt" tmux new-session -d -s "$SESSION" -n "ksh" # 最初に表示されるファイルリストを準備 i=1 titles="" > "$LISTFILE" sed 's/.* at //' backtrace.log | while IFS=: read -r file line; do base=$(basename "$file") echo "$i : $base $line" >> "$LISTFILE" i=$((i+1)) done # ファイルを tmux ウィンドウに順次開く i=1 sed 's/.* at //' backtrace.log | while IFS=: read -r file line; do base=$(basename "$file") tmux new-window -t "$SESSION:$i" -n "$base" "view +$line $file" titles="$titles $i:$base" i=$((i+1)) done # quit処理とリスト表示を組み合わせたウィンドウ tmux send-keys -t "$SESSION:0" " echo 'Backtrace List:'; cat $LISTFILE echo while read -r cmd; do [ \"\$cmd\" = \"q\" ] && tmux kill-session -t simgdb && break done " C-m # tmux ステータスライン設定 tmux set-option -t "$SESSION" status on tmux set-option -t "$SESSION" status-left "[#S]" tmux set-option -t "$SESSION" status-right "$titles" # 最初のウィンドウへ tmux select-window -t "$SESSION:0" tmux attach-session -t "$SESSION"
send-keysの扱いが良く理解できなかったので、下記で実験。
ad$ tmux send-keys "pwd; ls" C-m ;;普通にRET pwd; ls ad$ pwd; ls /home/sakae backtrace.log sim-gdb.sh* tcloop.c ad$ ad$ tmux send-keys "pwd; ls" ;;普通にRET pwd; lsad$ pwd; ls ;;ここで停止、RETキーを叩くと /home/sakae backtrace.log sim-gdb.sh* tcloop.c
C-m ってキャリッジ・リターンの事なのね。に、しても、もう一つ疑問が有る。 whileループで終了を待っている前方にコードが有る。そして、それが動作してる。
|-+= 77494 sakae tmux: server (/tmp/tmux-1000/default) (tmux) | |--= 43826 sakae -ksh (ksh) | |-+= 59607 sakae ksh -c view +512 /usr/src/sys/dev/wscons/wsemul_vt100.c | | \--- 34379 sakae view -c512 /usr/src/sys/dev/wscons/wsemul_vt100.c | |-+= 36880 sakae ksh -c view +1228 /usr/src/sys/dev/wscons/wsemul_vt100.c | | \--- 66874 sakae view -c1228 /usr/src/sys/dev/wscons/wsemul_vt100.c : \-+= 70751 sakae -ksh (ksh) \-+= 82261 sakae /bin/sh ./sim-gdb.sh \--- 87422 sakae tmux: client (/tmp/tmux-1000/default) (tmux)
pstreeを実行したら、sim-gdb.shからtmux clientが生まれてた。一方server側で 43826ってプロセスも動作してる。このshell内で、whileループしてるのだろう。 そのshellは、qで終了する時に、セッションをkillするとな。それを看取って クライアント側も終了する、かな。
ChatGPTって知恵者だな。こんな連携方法を一瞬で提示してくるんだから。もうオイラーを 十分に凌駕してるぞ。せめて自分で出来る事は奴がどう考えたか確認するぐらいしか無いな。
利用法と実行例
まず、gdbのバックトレース・ログ(backtrace.log)を用意する。
: #3 0xd075394c in ttstart (tp=0xd23a3600) at /usr/src/sys/kern/tty.c:1370
こんな形式のファイルね。意味は左から、フレーム番号、アドレス、関数名と引数、 ソースファイルと呼び出した行番号。このうち利用してるのは at の右側のみだ。
sakae@lu:~$ cat ~/.vimrc set number set ruler set nowrap
行番号やルーラーの表示をしてくれる様に設定しとく。 OpenBSD上でdebugしてたんだけど、最終利用場所はlubuntu上なんで、vim用のrcファイルを 用意する。OpenBSDの場合は、~/.exrcだ。
端末(mltermなりxterm等)を用意して、sim-gdb.sh で起動する。
sakae@lu:~$ echo 'Backtrace List:'; cat /tmp/simgdb_list.txt Backtrace List: 1 : wsemul_vt100.c 512 2 : wsemul_vt100.c 1228 : 11 : sys_generic.c 300 12 : syscall_mi.h 179 sakae@lu:~$ echo sakae@lu:~$ while read -r cmd; do > [ "$cmd" = "q" ] && tmux kill-session -t simgdb && break > done
これで、12個のファイルがそれぞれのウィンドウに表示される。2番のファイルを参照 したければC-t 2するだけ。上の起動画面にはC-t 0で移動できる。元居た場所には、 C-t l (エル)で戻れる。
frameのup/downに相当する操作は、C-t n/C-t pだ。極めて簡単な操作で、ファイル間を 渡り歩ける。番号指定は9までしかtmuxがサポートしていないので、そのつもりで。
終了はqだ。lubuntuの場合は、2回qしないと終了しない。tmuxの実装の違いかな。
[simgdb]0:ksh- 1:wsemul_vt100.c 2:wsemul_vt100.c* 3:wsdisplay.c 4:tty.c 5:tt>
上記はtmux-status(緑色の帯の部分)になる。'*' が付いてるのが今表示されている窓だ。
C-t w すると、もっともっとgdbしてる雰囲気が味わえる。
(0) - simgdb: 13 windows (attached) (1) ├─> + 0: ksh*: "te.localdomain" (2) ├─> + 1: wsemul_vt100.c: "te.localdomain" (3) ├─> + 2: wsemul_vt100.c: "te.localdomain" ;;この行が着色 (4) ├─> + 3: wsdisplay.c: "te.localdomain" (5) ├─> + 4: tty.c: "te.localdomain" : ┌ 2 (sort: index) ─────────────────────────────────────────────────────────────┐ │ 1223 break; │ │ 1224 instate->last_output = instate->inchar; │ │ 1225 processed += prev_count - count; │ │ 1226 continue; │ │ 1227 } │ │ 1228 #ifdef DIAGNOSTIC │ │ 1229 if (edp->state > nitems(vt100_output))
こんな具合に、画面が上下に分割される。上部はリスト。選ばれているのがオレンジ色で 着色される。上下の矢印キーで着色行を移動。それにつれて下部も追従する。終了はq。 もう、gdbしてるのと遜色無いですよ。
view
強制的にviを使わされるはめになったので、簡単操作を思い出しておく。
- C-d/C-u 画面を進める/後退する
- /hoge hogeを検索。続けて検索するならn。逆方向検索ならN
- mb/'b カーソルの位置を変数bに記憶。後で元の場所に戻る。
これぐらいで十分だろう。
status
sim-gdb.shを起動してる時の外観。
sakae@lu:~$ tmux ls 0: 2 windows (created Sun May 4 05:04:38 2025) (attached) simgdb: 13 windows (created Sun May 4 06:43:14 2025) (attached) sakae@lu:~$ ps a | grep view 4700 pts/5 Ssl+ 0:00 view +512 /usr/src/sys/dev/wscons/wsemul_vt100.c 4705 pts/6 Ssl+ 0:00 view +1228 /usr/src/sys/dev/wscons/wsemul_vt100.c 4709 pts/7 Ssl+ 0:00 view +1534 /usr/src/sys/dev/wscons/wsdisplay.c :
tmux lsで、セッションの状況が分る。psで確認すると、viewが沢山居ますなあ。
探検するぞ with gdb
tmux(1) すると、-V で身分を提示してくれる。-v で詳細なログを残してくれるなんて 記載が有った。
vm$ tmux -V tmux openbsd-7.7
最新式
sakae@lu:~$ tmux -V tmux 3.4
本家やFreeBSDでは、3.5a なんだけど、リナではGnu screenに遠慮してか、遅れた バージョンしか提供していない。3.4になったのは、Tue Feb 13 10:17:07 2024だから Ubuntu 24.04 LTSが作成された当時のものなんだね。
vm$ tmux -v : vm$ wc tmux-*.log 247 2148 17099 tmux-client-81266.log 20339 111745 1191831 tmux-server-60729.log
とっても長大なログが生成される。これを精査するか? いや、もっと楽しい事を しよう。
例に出てきたgdbもどきの画面になる、C-t w に感激したんで、どうなってるか追跡 したい。C-t ? すると、現在のキーバインドをさっと確認できる。
C-t t Show a clock C-t w Choose a window from a list
時計って、どこにも有るのねと感心しながら、説明文を検索すると
key-bindings.c: "bind -N 'Choose a window from a list' w { choose-tree -Zw }",
どうやら、このファイルはデフォのキーバインドとかを設定してるっぽい。choose-tree って関数が起動されるんだな。cmd-choose-tree.c あたりが本拠地だろう。
static enum cmd_retval cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item)
実質の関数は、これだけなんですけど。
sim-gdb.shから学んだ事である、tmux関係はサーバー側が一手に引き受けている事を 思い出し、サーバーにgdbをアタッチする。
te$ ps awx | grep tmux 38221 ?? Sp 0:01.13 tmux: server (/tmp/tmux-1000/default) (tmux) 97697 p0 R+ 0:00.01 grep tmux 47575 p1 I+p 0:00.09 tmux: client (/tmp/tmux-1000/default) (tmux) te$ gdb -q tmux -p 38221 Reading symbols from tmux... Attaching to program: /home/sakae/tmux, process 38221 Reading symbols from /usr/lib/libutil.so.20.0... Reading symbols from /usr/lib/libcurses.so.15.0... Reading symbols from /usr/lib/libevent.so.4.1... Reading symbols from /usr/lib/libm.so.10.1... Reading symbols from /usr/lib/libc.so.100.3... Reading symbols from /usr/libexec/ld.so... kevent () at /tmp/-:2 warning: 2 /tmp/-: No such file or directory (gdb) bt #0 kevent () at /tmp/-:2 #1 0x008dbff8 in kq_dispatch (base=<optimized out>, arg=0x697e2780, tv=0xcf7f8388) at /usr/src/lib/libevent/kqueue.c:198 #2 0x008da247 in event_base_loop (base=0x44599500, flags=1) at /usr/src/lib/libevent/event.c:474 #3 0x008d9ff4 in event_loop (flags=1) at /usr/src/lib/libevent/event.c:409 #4 0x18853956 in proc_loop (tp=0x445a7000, loopcb=0x18875d00 <server_loop>) at proc.c:209 #5 0x1887579b in server_start (client=0x697fda00, flags=402653184, base=0x44599500, lockfd=5, lockfile=0x445bfc00 "\002") at server.c:247 #6 0x187d214d in client_connect (base=0x44599500, path=0x445acc00 "/tmp/tmux-1000/default", flags=402653184) at client.c:165 #7 0x187d10b5 in client_main (base=0x44599500, argc=0, argv=0xcf7f86e8, flags=402653184, feat=0) at client.c:290 #8 0x1888794b in main (argc=0, argv=0xcf7f86e8) at tmux.c:549
keventなんてライブラリィーが底に潜んでキー入力を監視してるのね。細かい事は 後にして、ターゲットと思われる場所にBPを設定してみる。そして、tmuxな端末上で、 C-t wしてみる。
(gdb) b cmd_choose_tree_exec Breakpoint 1 at 0x187d5702: file cmd-choose-tree.c, line 97. (gdb) c Continuing. Breakpoint 1, cmd_choose_tree_exec (self=0x445b88e0, item=0x445bf980) at cmd-choose-tree.c:97 97 struct args *args = cmd_get_args(self); (gdb) bt #0 cmd_choose_tree_exec (self=0x445b88e0, item=0x445bf980) at cmd-choose-tree.c:97 #1 0x187ec64d in cmdq_fire_command (item=0x445bf980) at cmd-queue.c:650 #2 0x187ec23e in cmdq_next (c=0x7f8d9000) at cmd-queue.c:775 #3 0x18875d88 in server_loop () at server.c:268 #4 0x1885397d in proc_loop (tp=0x445a7000, loopcb=0x18875d00 <server_loop>) at proc.c:210 #5 0x1887579b in server_start (client=0x697fda00, flags=402653184, base=0x44599500, lockfd=5, lockfile=0x445bfc00 "less") at server.c:247 #6 0x187d214d in client_connect (base=0x44599500, path=0x445acc00 "/tmp/tmux-1000/default", flags=402653184) at client.c:165 #7 0x187d10b5 in client_main (base=0x44599500, argc=0, argv=0xcf7f86e8, flags=402653184, feat=0) at client.c:290 #8 0x1888794b in main (argc=0, argv=0xcf7f86e8) at tmux.c:549
見事にヒットしましたなあ。コマンドの取りこぼしが無いようにキューにコマンドを 突っ込む。それを順番に取り出してきて、コマンドを着火(実行)させてるんだな。 この後gdbでステップ実行してみたけど、直ぐに該当コマンドが実行される事は 無いみたい。少し回りの状況を観察しながら、くすぶっていて、好機を見て、 コマンドが本格的に実行されるみたいだ。 いわゆる非同期実行になるんかな。
(gdb) 102 if (cmd_get_entry(self) == &cmd_choose_buffer_entry) { (gdb) 113 mode = &window_tree_mode; (gdb) 115 window_pane_set_mode(wp, NULL, mode, target, args); (gdb) 116 return (CMD_RETURN_NORMAL); (gdb) 117 } (gdb) cmdq_fire_command (item=0x7f8c0d80) at cmd-queue.c:651
gdbを起動してる端末はリナ側からsshしたtmux仮想端末だ。だからtmuxで画面を遡る
事ができる(C-t [ M-v)。上記は実行の歴史になる。modeを決めてから window_pane_set_mode
を実行して、それで cmd_choose_tree_exec
は終了とな。追跡するのはチト大変そうで
あります。
少し気分を変えて別な事に注目する。こういうのを注意散漫モードと申します。 C-t / すると key って聞いてきて、キーストロークを入力すると説明が一瞬出て くる。emacsのC-h k を真似したな。それより、もっと長めに表示して欲しいぞ。
tmux set-option display-time 5000
5000ms間も表示してくれれば、十分だろう。この設定を.tmux.confに記述しとこう。 ちなみみ、デフォはどれぐらい?
gdbでプロセスを追跡する設定 set follow-fork-mode child が必要になるかと思ったら、そこまでは踏み込んで いないな。表面をなぞっただけだ。
sim-gdb.sh for user land
ユーザーランドでのバックトレースでは、そのままsim-gdb.shを利用できない。 バックトレースに表われるファイル名が絶対パスになっていないからだ。 下記の様にちょっと細工してあげれば良い。
vm$ diff sim-gdb.sh ul-gdb.sh 2c2 < --- > ABS="/usr/src/usr.bin/tmux" 20c20 < tmux new-window -t "$SESSION:$i" -n "$base" "view +$line $file" --- > tmux new-window -t "$SESSION:$i" -n "$base" "view +$line $ABS/$file"
但し、これだけじゃ、ライブラリィのファイルは補正できないので、それは諦めて 削除しとく事。
tpm
tmuxで検索してると、tpmなんてのを散見するんで、調べておく。
Tmux Plugin Manager(tpm) 簡単な紹介
tmuxとtpmを使っていますか? tmuxを拡張して格好良くいこう
tmux-plugins/tmux-prefix-highlight
なんてのを見ると、shell scriptじゃん。 こういう場合には、pythonなんて全く出番が無いぞ。あちらは独自な世界を 作っていてunix文化とは親和性が無いからね。
tmuxを使いこなす / プラグイン開発で機能を拡張 あっ、こんな解説記事もあった。
ask ChatGPT
最近ニュートン法より収束の速い方法が発明されたとか。そもそもニュートン法を 良く知らないので聞いてみた。これって、突然SICPに出てくるんだよな。
Q: 近似値を求めるニュートン法を説明して。 例は2の平方根でお願いします。
A: ニュートン法(Newton's method)は、関数の零点(つまり、関数 \( f(x) = 0 \) の解)を数値的に近似する反復法です。簡単に言えば、ある関数のグラフに対して、接線を使って零点をどんどん近づけていく方法です。
—
### ニュートン法の基本式
与えられた関数 \( f(x) \) に対して、零点を求める反復式は次のようになります:
\[ x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)} \]
ここで:
- \( x_n \) は現在の近似値
- \( x_{n+1} \) は次の近似値
- \( f'(x_n) \) は \( f(x) \) の導関数(微分)を \( x_n \) における値
—
### 例:2の平方根を求める
2の平方根 \( \sqrt{2} \) を求めたいとします。これは、
\[ x^2 = 2 \]
を満たす \( x \) を求める問題です。これを関数にすると:
\[ f(x) = x^2 - 2 \]
その導関数は:
\[ f'(x) = 2x \]
これをニュートン法の式に当てはめると:
\[ x_{n+1} = x_n - \frac{x_n^2 - 2}{2x_n} \]
\[ = \frac{1}{2} \left( x_n + \frac{2}{x_n} \right) \]
これは、平方根を求める古典的な方法(バビロニア法)と同じ形になります。
—
### 実際の計算例
初期値として \( x_0 = 1 \) を使って反復します。
#### 1回目: \[ x_1 = \frac{1}{2} \left( 1 + \frac{2}{1} \right) = \frac{1}{2}(1 + 2) = \frac{3}{2} = 1.5 \]
#### 2回目: \[ x_2 = \frac{1}{2} \left( 1.5 + \frac{2}{1.5} \right) = \frac{1}{2}(1.5 + 1.\overline{3}) \approx \frac{2.833...}{2} \approx 1.4167 \]
#### 3回目: \[ x_3 = \frac{1}{2} \left( 1.4167 + \frac{2}{1.4167} \right) \approx \frac{1.4167 + 1.4118}{2} \approx \frac{2.8285}{2} \approx 1.4142 \]
実際の \( \sqrt{2} \approx 1.41421356 \) にかなり近づいています。
—
### まとめ
- ニュートン法は、関数の解を効率よく近似できる反復法。
- \( x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)} \) が基本式。
- 今回の例では、平方根の計算として非常に速く収束することがわかります。
何か他の数値の平方根でも試してみますか?
README
中華料理 5000年の文化史 なんて言う、おいしい本を読んだ。
昔の中国では、カエルは勿論、犬やキョンまで食べていたそうだ。キョンって動物園を逃げ出して 千葉県あたりでは、大問題になってる迷惑動物ですかね。是非本場の方を招聘 して退治してもらいたいです。四足で食べられないのは、椅子や机の足だけだと 巷では言われてますから。
満漢全席って、中国の贅をつくした宴会だってのは知ってたけど、こういう風に つづるのね。中国の長い歴史の中で、満族が主流を占めたり、漢族が主流になったり と言う時期が有った。両者の融合の為、皇帝は年に一度、両者を集めて宴会を張った。 食べる事はしあわせな事ですから。もちろん、満族用の卓と漢族用の卓が提供 されたそうな。
そのうちに漢族用が発展してって、フカヒレの姿煮とかツバメの巣とかが提供 されて、それを食する人は高いステータスを得られるとなった。
オイラーは昔川崎に住んでいたんだけど、夏はよく三浦半島まで海水浴に行ってた。 帰りは、必ず横浜の中華街で打ち上げをやった。その時にフカヒレを食べたなあ。 独身貴族だったから、散財も許されたんだね。
今じゃ、町中華の豚肉とキャベツの炒め物用の醤を買うぐらいが関の山ですよ。 回鍋肉ってりっぱな名前が付いてるね。まあ、これぐらいなら庶民でもOKかな。