tmux

携帯、変えた

オイラーが使ってるのは、3G電波しか扱えない10年選手。携帯屋から、来年3月で使えなくなるから、新しいのにしてくださいって、再三案内が送られてきていた。ガン無視してたよ。そしたら、頻繁に催促メールや電話が来るようになった。うざいんでほってた。

とうとう、無料で新しいのにしてやるから、今使ってる携帯持って出頭せいって通告が来た。 そんな事で、新しい(けど、型落ち)のに交換してきたよ。新な契約になるんで、最低の契約(電話だけ)にした。

マニュアル見ながら、カスタマイズしてたら、着信バイブレーションが無駄に5パターンも選べるようになってた。順番に試してみたら、モールス信号で、A文字の振動と、N文字の振動パターンが含まれていた。

あれ、着メロディーは、登録出来ないのかな? 設定出来るなら、電話の場合はbreakぐらいにしたいな。SMSだったら、lazyぐらいが良い。ggして、先駆者が居ないか調べてみるか。

接続切れたを救う tmux

あるー日、森の中、くまさんが、、、じゃなくて、Windows側のPuTTYから接続してたdebian機への接続が途絶えた。以前にも発生した事があるんだ。2度有る事は3度以上有るって言うから、こういう時に備えて、防災訓練をやっておく。

debian:~$ tmux
debian:~$ emacs
......
コーヒーブレークして帰ってきたら、PuTTYが、接続切れたぞーと言ってた。
再度、PuTTYを起動して login。そして、

debian:~$ tmux ls
0: 3 windows (created Mon Mar  1 05:23:37 2021) [80x31] (attached)
debian:~$ tmux attach -t 0
これで、元の(emacs)画面に復帰した。

もっと簡単にやるには、下記で良いそうな。

debian:~$ tmux a #0

この際だからtmuxのマニュアルをチラ見したよ。時計モードなんてのが紹介されてた。

sakae@pen:~$ tmux clock-mode

これで、端末がクリアされて、デジタル時計が表示される。ESCキーを推すと、時計が消えて、元の画面が復帰されるよ。

防災訓練と言うか、防災備蓄が推奨されてた。 災害用の非常食にポテトチップス採用 ですって。ポテチなら無理なく備蓄出来るな。そして、古い奴から消費して、減った分は補充しておけ。ローリング・ストックとは良いネーミング。

そんな事より、災害救助に当たる自衛隊のコマンダーが言うには、1に体温の保存、2に水の確保らしい。食料より水が大事なんだな。オイラーは、腹が減ると性格が変わる(粗暴モードへ移行)らしいんで、ポテチの準備も宜しく。>女房

もう少しtmux

基本的なtmuxの使い方は上記通りなんだけど、もう少し突っ込んでみる。

debian:~$ tmux ls
1: 3 windows (created Mon Mar  1 06:14:15 2021) [80x31] (attached)
3: 1 windows (created Mon Mar  1 07:04:32 2021) [118x40] (attached)

今度は一度、debian側のLXDEなGUIにログインして、そこの端末でtmuxした。次にPuTTY側からもtmuxした(3画面分を確保)。そして3枚の画面を作った。 次に、GUI側のtmuxを終了。またtmuxを起動。

1: とか 3: とかは、tmuxのセッション番号なんだな。3 Windowsとかは、仮想端末の数、後に続くのは起動日時とか、端末のstty値だな。GUIの端末は画面が広いなんて事が分かる。

一度tmuxのセッションを起動すると、tmuxのサーバーが立ち上がってて、それが一手に管理をしてくれてるとな。クライアントは、/tmux/tmux-1000/配下に出来る、ソケットを頼りに、サーバーとネゴシエーションするんだな。1000って数字はuidなんだな。

クライアント/サーバーモデルって分かれば、理解が進むな。サーバーと切り離すには、Prefix+dのキーを押す。

debian:~$  ;; tmux の Prefix + d
[detached (from session 1)]
debian:~$ tmux ls
1: 3 windows (created Mon Mar  1 06:14:15 2021) [80x31]
3: 1 windows (created Mon Mar  1 07:04:32 2021) [118x40] (attached)

これで、セッションがデタッチされた。アタッチは上でやったように、tmux a だ。番号を指定しない限り、直前のセッションに接続される。

debian:~$  tmux new -s works
debian:~$ tmux ls
1: 3 windows (created Mon Mar  1 06:14:15 2021) [80x31]
3: 1 windows (created Mon Mar  1 07:04:32 2021) [118x40] (attached)
works: 1 windows (created Mon Mar  1 07:43:55 2021) [80x31] (attached)

このように起動すると、新しいセッションを初められる。余り作り過ぎると、わけわかめになるぞ。程々が良い。

debian:~$ tmux a -t 1
[detached (from session 1)]
debian:~$ tmux a -t works
[detached (from session works)]

使うなら、ちゃんと名前を付けておいて、きちんとセッション管理しよう。

tmux resource

tmux read

上記のHomeの所でmanしたら、OpenBSDのそれに案内された。OpenBSDはデフォルトで入っているからね。と、言う事はソース観光しれって事だな。

まずはソースの外観検査だな。/usr/src/usr.bin/tmux に多数のファイルが置いてある。数えてみたら127個も有った。とても見て行く気にはならない。

でも、ファイル名だけを確認してみると、cmd-xxx.cって名前が付いてるのが約半数あった。tmuxの各種コマンドを処理するんだな。注目は、cmd-parse.yってやつ。コマンドを解析してくれるやつだな。

ちょいと、上でやったclock-modeなんてのが、どの辺に潜んでいるか探ってみて、土地勘を養ってみる。

vbox$ grep clock-mode *.c
cmd-copy-mode.c:        .name = "clock-mode",
key-bindings.c:         "bind -N 'Show a clock' t clock-mode",
options-table.c:        { "clock-mode-color", "clock-mode-colour" },
options-table.c:        { .name = "clock-mode-colour",
options-table.c:        { .name = "clock-mode-style",
window-clock.c: .name = "clock-mode",
window-clock.c: colour = options_get_number(wp->window->options, "clock-mode-colour");
window-clock.c: style = options_get_number(wp->window->options, "clock-mode-style");

cmd-copy-mode.cの中に対応するコードを押し込んでおきます。実際の下請けはwindow-clock.cです、って構図かな。デカの勘ですけどもね。

ええい、取り合えずgdbにかけられるようにコンパイルしてみるか。

gdbで閲覧作戦発動

取り合えずソース群を/tmpの下に移動。そしてMakefileを変更

CFLAGS = -O0 -g -I${.CURDIR}
# CFLAGS += -I${.CURDIR}

全くのソースからだと、configureする時、–enable-debug とかってやるのかな。OpenBSDに収録されてるソースとMakefileは綺麗に整理されているので、ちょいと変更するだけ。

vbox$ ldd ./tmux
./tmux:
        Start    End      Type  Open Ref GrpRef Name
        163e4000 363f3000 exe   1    0   0      ./tmux
        0dc6c000 2dc6e000 rlib  0    1   0      /usr/lib/libutil.so.15.0
        0a67d000 2a681000 rlib  0    1   0      /usr/lib/libcurses.so.14.0
        0018a000 2018c000 rlib  0    1   0      /usr/lib/libevent.so.4.1
        047e1000 247e3000 rlib  0    1   0      /usr/lib/libm.so.10.1
        07375000 27386000 rlib  0    1   0      /usr/lib/libc.so.96.0
        0105e000 0105e000 ld.so 0    1   0      /usr/libexec/ld.so

こんな構成だった。libevent.soって、珍しいやつを使ってるな。そんじゃ、gdbにかけてみる。

(gdb) b main
Breakpoint 1 at 0xc2432: file tmux.c, line 331.
(gdb) r
Starting program: /tmp/tmux/tmux

Breakpoint 1, main (argc=1, argv=0xcf7ea924) at tmux.c:331
331             char                                    *path = NULL, *label = NULL;

普通だったら、ここからステップ実行して行くんだけど、変な所に迷い込むとgdbの制御を失ってしまうので、要と思われる所にBPを置いてみる。

取り合えずlistって叩いて、要所と思われる所を見つける。

(gdb) l
456             /*
457              * The default shell comes from SHELL or from the user's passwdentry
458              * if available.
459              */
460             shell = getshell();
461             options_set_string(global_s_options, "default-shell", 0, "%s", shell);
462
463             /* Override keys to vi if VISUAL or EDITOR are set. */
464             if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) !=
NULL) {
465                     options_set_string(global_options, "editor", 0, "%s", s);
(gdb) tb 461
Temporary breakpoint 2 at 0x14111ca9: file tmux.c, line 461.

tbは、一度だけ効力を発するBPだ。

(gdb) c
Continuing.

Temporary breakpoint 2, main (argc=0, argv=0xcf7ea928) at tmux.c:461
461             options_set_string(global_s_options, "default-shell", 0, "%s", s
hell);
(gdb) p shell
$1 = 0xcf7eab72 "/bin/ksh"

OpenBSDのshellはkshなのさ。

(gdb)
496                     flags |= CLIENT_DEFAULTSOCKET;
497             }
498             socket_path = path;
499             free(label);
500
501             /* Pass control to the client. */
502             exit(client_main(event_init(), argc, argv, flags, feat));
503     }
(gdb) tb 502
Temporary breakpoint 3 at 0x1411201c: file tmux.c, line 502.
(gdb) c
Continuing.

Temporary breakpoint 3, main (argc=0, argv=0xcf7ea928) at tmux.c:502
502             exit(client_main(event_init(), argc, argv, flags, feat));
(gdb) s
event_init () at /usr/src/lib/libevent/event.c:99
99              struct event_base *base = event_base_new();
(gdb) finish
Run till exit from #0  event_init () at /usr/src/lib/libevent/event.c:99
0x14112021 in main (argc=0, argv=0xcf7ea928) at tmux.c:502
502             exit(client_main(event_init(), argc, argv, flags, feat));
Value returned is $2 = (struct event_base *) 0x61040400

502行目は、サーバー側の最後の所。ここで、クライアントが動き出すはず。なんだけどエラーだ。どんなエラーかな?

(gdb) c
Continuing.
sessions should be nested with care, unset $TMUX to force
[Inferior 1 (process 15545) exited with code 01]

このセッションは、既にtmuxが稼働してる所でやったので、多重起動は駄目って事なんだな。

tmux server/client

vbox$ ps awx | grep tmux
95225 ??  Rp       0:01.10 tmux: server (/tmp/tmux-1000/default) (tmux)
71158 p0  S+p      0:00.01 tmux: client (/tmp/tmux-1000/default) (tmux)

こんな風に、サーバー/クライアントのモードで動いている。

vbox$ gdb tmux -p 71158
 :
(gdb) bt
#0  kevent () at /tmp/-:3
#1  0x0b80ac10 in kq_dispatch (base=0x5fc7bc00, arg=0x5f629c00, tv=0x0)
    at /usr/src/lib/libevent/kqueue.c:198
#2  0x0b80bd94 in event_base_loop (base=0x5fc7bc00, flags=1)
    at /usr/src/lib/libevent/event.c:474
#3  0x0b80bb04 in event_loop (flags=1) at /usr/src/lib/libevent/event.c:409
#4  0x14ec6990 in proc_loop (tp=0x5fc7b400, loopcb=0x0) at proc.c:202
#5  0x14e54345 in client_main (base=0x5fc7bc00, argc=0, argv=0xcf7d6868,
    flags=402718720, feat=0) at client.c:387
#6  0x14ef205a in main (argc=0, argv=0xcf7d6868) at tmux.c:502

client_main の中で、アタッチ/出タッチの処理を行っている(ぽい)。libeventの力を借りているんだな。ってな事が分かる。

clock

clock-modeの実際の処理はwindows-clock.cの中でやってる。その証拠に、画面に表示される、数字とかのROMが格納されてて、それが使われいるから。

const char window_clock_table[14][5][5] = {
        { { 1,1,1,1,1 }, /* 0 */
          { 1,0,0,0,1 },
          { 1,0,0,0,1 },
          { 1,0,0,0,1 },
          { 1,1,1,1,1 } },
        { { 0,0,0,0,1 }, /* 1 */
          { 0,0,0,0,1 },
          { 0,0,0,0,1 },
          { 0,0,0,0,1 },
          { 0,0,0,0,1 } },
             :

5x5のドット文字が14種登録されてる。そしてこのROMを使って画面に描画するのが、 window_clock_draw_screen っていう関数。表示色(colour)や(style)の切り替えデータも取り込んで使っている(12表記だと、AM/PMの表示付き)。

この手続きを呼び出してのに、 window_clock_timer_callback 等が居るんだけど、BPを貼っても引っかからない。一体どういう風にやってるの?

more sesource

いい加減、迷子になったので、先駆者の研究を参照。論文書く時も、巻末には参考にした文献を挙げるからね。それらをヒントに、新たな研究をして、知見を得ましたって感謝の気持ちです。

tmux メモ

tmuxを使いこなす / プラグイン開発で機能を拡張

これらをヒントに、精進してみるか。

もっと証拠を

上記の解析メモさんを参考に、オイラーもやってみる。psのxjオプション知りませんでした。

vbox$ ps xj
USER       PID  PPID  PGID     SESS JOBC STAT   TT       TIME COMMAND
sakae    39421 94973 94973        0    0 S      ??    0:00.51 sshd: sakae@ttyp0
sakae     2529     1  2529        0    0 Sp     ??    0:04.88 tmux: server (/tm
sakae    87590 39421 87590        0    0 Ip     p0    0:00.06 -ksh (ksh)
sakae    79191 87590 79191        0    1 I+p    p0    0:00.01 tmux: client (/tm
sakae    66798  2529 66798        0    0 Sp     p1    0:00.04 -ksh (ksh)
sakae    31005 66798 31005        0    1 R+pU   p1    0:00.00 ps -xj
sakae    62832  2529 62832        0    0 Ip     p2    0:00.02 -ksh (ksh)
sakae    63551 62832 63551        0    1 I+     p2    0:01.34 emacs (emacs-27.1

これ、tmuxを起動してshellの端末が開いた状態で、もう一つ窓を作ってそこからemacsを起動した図。いや、図ではなくてテーブルだな。

vbox$ pstree
-+= 00001 root /sbin/init
 |-+= 57435 root sshd: /usr/sbin/sshd [listener] 0 of 10-100 startups (sshd)
 | \-+= 94973 root sshd: sakae [priv] (sshd)
 |   \-+- 39421 sakae sshd: sakae@ttyp0 (sshd)
 |     \-+= 87590 sakae -ksh (ksh)
 |       \--= 79191 sakae tmux: client (/tmp/tmux-1000/default) (tmux)
 |-+= 02529 sakae tmux: server (/tmp/tmux-1000/default) (tmux)
 | |-+= 66798 sakae -ksh (ksh)
 | | \-+= 05210 sakae pstree
 | |   \-+- 31232 sakae sh -c ps -kaxwwo user,pid,ppid,pgid,command
 | |     \--- 80594 sakae ps -kaxwwo user
 | \-+= 62832 sakae -ksh (ksh)
 |   \--= 63551 sakae emacs (emacs-27.1)

こちらが図になる(関係者以外は、消してあります)。

sshdで待ち構えている所に、puTTYでログイン、すかさずtmuxを起動。その流れが、pid 57435の幹だな。tmuxのクライアント起動で、サーバーが居なかったら、自動起動する。 普通は、サーバーを起動しておいてから、クライアントを起動ってばかり思っていたから、意表をつく動きだ。

サーバー側には、2つのshellが動いている。66798と62832だ。前者ではpstreeを実行。それが下請けを雇ったってな事が分かる。もう一方のshellはemacsに変身しているんだな。

この状態で、PuTTYを強制的に切断して(事故の真似)から、再度sshでログイン。すかさずpsすると、クライアントは居なくなってた。サーバーは、サーバーなんで、ちゃんと生きている。

vbox$ pstree
-+= 00001 root /sbin/init
 |-+= 57435 root sshd: /usr/sbin/sshd [listener] 0 of 10-100 startups (sshd)
 | \-+= 34346 root sshd: sakae [priv] (sshd)
 |   \-+- 15860 sakae sshd: sakae@ttyp0 (sshd)
 |     \-+= 22133 sakae -ksh (ksh)
 |       \-+= 61241 sakae pstree
 |         \-+- 97478 sakae sh -c ps -kaxwwo user,pid,ppid,pgid,command
 |           \--- 12684 sakae ps -kaxwwo user
 |-+= 02529 sakae tmux: server (/tmp/tmux-1000/default) (tmux)
 | |--= 66798 sakae -ksh (ksh)
 | \-+= 62832 sakae -ksh (ksh)
 |   \--= 63551 sakae emacs (emacs-27.1)

この状態から、tmux a すると、クライアントが動き出して、サーバー側に預けてある、居残り組のshellやら、emacsが復活するとな。尚、サーバー側で管理してる物が全て無くなると、サーバーを動かしておく意味が無いので、サーバーは消滅する。

初回のtmux起動時に、tmux -v とかすると、ログが、起動した所に出て来る。

vbox$ wc *.log
      42     322    2572 tmux-client-79191.log
  222306 1258567 17607754 tmux-server-2529.log

まずは、クライアントのログ

1614718861.209234 client started (79191): version openbsd-6.8, socket /tmp/tmux-1000/default, protocol 8
1614718861.209361 on OpenBSD 6.8 GENERIC#5; libevent 1.4.15-stable (kqueue)
1614718861.209605 flags are 0x18010000
1614718861.209667 socket is /tmp/tmux-1000/default
1614718861.209714 trying connect
1614718861.209816 connect failed: No such file or directory
1614718861.209886 lock file is /tmp/tmux-1000/default.lock
  :
1614718861.212840 client loop enter
1614718861.624550 peer 0x7e2fb000 message 207
1614718861.624586 sending message 208 to peer 0x7e2fb000 (0 bytes)
1614722606.516370 client loop exit

次は、長ったらしい、サーバーのログ

1614718861.217785 server started (2529): version openbsd-6.8, socket /tmp/tmux-1000/default, protocol 8
1614718861.217937 on OpenBSD 6.8 GENERIC#5; libevent 1.4.15-stable (kqueue)
1614718861.218302 input_key_build: 0x1000000003 (PasteStart) is \033[200~
1614718861.218333 input_key_build: 0x1000000004 (PasteEnd) is \033[201~
1614718861.218353 input_key_build: 0x1000000099 (F1) is \033OP
1614718861.218374 input_key_build: 0x100000009a (F2) is \033OQ
 :
1614723977.801724 server_client_reset_state: cursor to 0,30
1614723977.801753 server_client_check_pane_buffer: pane %0 is on
1614723977.801777 @0 active pane changed
1614723977.801810 @0 name timer already queued (73380 left)
1614723977.801834 server_client_check_pane_buffer: pane %1 is on
1614723977.801859 @1 active pane not changed

左側の巨大な数値はepocだろう。意味なく確認してみると

vbox$ date -r 1614718861
Wed Mar  3 06:01:01 JST 2021
vbox$ date -r 1614723977
Wed Mar  3 07:26:17 JST 2021

起動したのは、朝も早からの6時で、それからうだうだと7時半近くまでやってるとな。このログを精査すれば、挙動が分かるとな。

tail -f tmux-server-2529.log したら、再帰(こんな所でも)しちゃって、止まらなくなっちゃった。どんなログを吐いているかと言うと、

1614724465.174770 screen_write_collect_end: 80 1614724464.720199 input_parse_buffer: %0 ground, 8021 bytes: \\r\\n1614724464.6710 (at 0,30)
1614724465.174800 screen_write_collect_add: wrapped at 80,30
1614724465.174825 screen_write_linefeed: at 80,30 (region 0-30)
1614724465.174850 screen_write_collect_scroll: at 80,30 (region 0-30)

再帰の片鱗がみえまーーす。

watch server

長い調査を経て、サーバーのログを見て桶が結論ぽいけど、伝統の観光をする。 こんな事もあろうかと、シリアル接続出来る口を用意してあるのさ(無理しなくても、2個目のPuTTYでおk)。お世話になるのは、tera termのシリアル接続。rootでログインして、

vbox# gdb ./tmux -p 2529 -q
Reading symbols from ./tmux...done.
Attaching to program: /home/sakae/tmux, process 2529
Reading symbols from /usr/lib/libutil.so.15.0...done.
Reading symbols from /usr/lib/libcurses.so.14.0...done.
Reading symbols from /usr/lib/libevent.so.4.1...done.
Reading symbols from /usr/lib/libm.so.10.1...done.
Reading symbols from /usr/lib/libc.so.96.0...done.
Reading symbols from /usr/libexec/ld.so...done.
[Switching to thread 414703]
kevent () at /tmp/-:3
3       /tmp/-: No such file or directory.
(gdb) bt
#0  kevent () at /tmp/-:3
#1  0x07d13c10 in kq_dispatch (base=0x71f9ac00, arg=0x7c561600, tv=0xcf7c7a98)
    at /usr/src/lib/libevent/kqueue.c:198
#2  0x07d14d94 in event_base_loop (base=0x71f9ac00, flags=1)
    at /usr/src/lib/libevent/event.c:474
#3  0x07d14b04 in event_loop (flags=1) at /usr/src/lib/libevent/event.c:409
#4  0x17d36990 in proc_loop (tp=0x78a3e400, loopcb=0x17d515f0 <server_loop>)
    at proc.c:202
#5  0x17d50dd9 in server_start (client=0x60309c00, flags=402718720,
    base=0x71f9ac00, lockfd=6,
    lockfile=0x6ffa9b80 "server.c:py.c:dowhile (rswitch (fork()) {+ px, 1, &regmatch, eflags) == 0) {ct c", '\337' <repeats 48 times>, "\002") at server.c:235
#6  0x17cc4d41 in client_connect (base=0x71f9ac00,
    path=0x41cb4000 "/tmp/tmux-1000/default", flags=402718720) at client.c:161
#7  0x17cc3cb6 in client_main (base=0x71f9ac00, argc=0, argv=0xcf7c7edc,
    flags=402718720, feat=0) at client.c:292
#8  0x17d6205a in main (argc=0, argv=0xcf7c7edc) at tmux.c:502

これでやっと、観光出来るぞ。gdbのコマンド待ちになってる時は、当然、クライアント側ってか、緑の帯が出てる端末での入出力は、待ったをかけられているよ。

from log

観光も中々進まないので、ショートカットして、tmux clock-mode した時の挙動をログから抽出してみる。

vbox$ grep clock-mode tmux-server-5027.log
1614726467.258721 yylex_token: clock-mode
1614726467.258848 cmd_parse_build_commands: argv[4]=clock-mode
1614726467.259021 cmd_parse: bind-key: argv[4]=clock-mode
1614726467.259064 cmd_parse_build_commands: bind-key -N "Show a clock" t clock-mode
1614726467.448144 yylex_token: clock-mode
1614726467.448170 cmd_parse_build_commands: 0 clock-mode
1614726467.448193 cmd_parse_build_commands: argv[0]=clock-mode
1614726467.448218 cmd_parse: clock-mode: argv[0]=clock-mode
1614726467.448244 cmd_parse_build_commands: clock-mode
1614726487.855375 cmd_unpack_argv: argv[0]=clock-mode
1614726487.855434 cmd_parse_from_arguments: argv[0]=clock-mode
1614726487.855472 cmd_parse_from_arguments: at 0: argv[0]=clock-mode
1614726487.855519 cmd_parse_build_commands: 0 clock-mode
1614726487.855543 cmd_parse_build_commands: argv[0]=clock-mode
1614726487.855568 cmd_parse: clock-mode: argv[0]=clock-mode
1614726487.855600 cmd_parse_build_commands: clock-mode
1614726487.855628 cmdq_get_command: [clock-mode/0x4a99b180] group 1544
1614726487.855651 cmdq_append </dev/ttyp1>: [clock-mode/0x4a99b180]
1614726487.855769 cmdq_next </dev/ttyp1>: [clock-mode/0x4a99b180] (0), flags 0
1614726487.855803 message: /dev/ttyp1 command: clock-mode
1614726490.728035 /dev/ttyp0: vbox$ ./tmux clock-mode

まだまだ続きそうなので、次回に回す。

ruby debug

この間、目を付けておいた技術評論社の雑誌が、手に入らなかった。入荷数が少なくて売り切れちゃったのかな? ruby 3x3 の特集ですから。

本屋に行ったついでに、コンピュータ関係の棚を覗いたら、見事にpython一色でした。それも機械学習をターゲットに、数学と絡めたものとか。オイラーは図書館から借りてきた、中高生の数学特集ぐらいで、いいな。回転行列まで出て来ていたぞ。常識なんですな。

常識と言えば、rubyのデバッグの常識を知っておこう。 Rubyのいろいろなデバッグ方法

いつも、byebugが使えるとは限らないからね。こういう心がけも、災害対策の一環になるのかな。


This year's Index

Home