ksh with gdb
Table of Contents
gdb and target
Q: gdbとそのターゲットの関係について質問します。 ターゲットはdashとkshです。下記の様に、ターゲットを起動した後、Ctl-Cで gdbに制御権が移行する(dash)ものと、しない(ksh)ものがあります。 何故この様な違いが発生するか、gdbの制御機構をふまえて説明してください。 環境は、GNU gdb (GDB) 16.3 と、OpenBSD 7.8(ksh)です。
ob$ gdb -q dash Reading symbols from dash... (gdb) r Starting program: /home/sakae/dash-0.5.12/src/dash $ ^C Program received signal SIGINT, Interrupt. _thread_sys_read () at /tmp/-:2 warning: 2 /tmp/-: No such file or directory
ob$ gdb -q ksh Reading symbols from ksh... (gdb) r Starting program: /home/sakae/my-ksh/ksh ob$ ### ^C ignore, don't switch gdb ob$
ob$ pstree : | \-+= 37903 sakae -ksh (ksh) | \-+= 00506 sakae gdb ksh (egdb) | \--= 93037 sakae /home/sakae/my-ksh/ksh
ターゲットはgdbの子供になっている。
A: いい質問 — 振る舞いの差は 「Ctrl-C(SIGINT)がどこに届くか」 と ターゲット自身がそのシグナルをどう扱うか の組合せに由来します。以下、gdb の制御機構(端末・プロセス群・ptrace/wait)を踏まえて順を追って説明します。最後に確認用の実験コマンドと回避策も書きます。
## 要点(結論)
- 端末からの
Ctrl-Cはカーネルが foreground process group(フォアグラウンド pgid) にSIGINTを送る。 - gdb は通常、実行中のインフェリア(ターゲット)に端末の制御を渡し(=tcsetpgrp= 相当の操作を行う)、かつ =ptrace=(もしくは OS の追跡機構)で子の状態変化(停止やシグナル)を監視する。
- /dash の場合/:=SIGINT= が子(dash)で「止まる/デフォルト動作で止まる」ため、ptrace 経由で gdb にその停止が伝わり
Program received signal SIGINTと報告され、gdb に制御が戻る。 - ksh の場合/:ksh が
SIGINTを自分で /捕まえて(ハンドラで処理し) 継続する/シグナルを吸収する(たとえばプロンプト再表示、=read()= を再開する、あるいはSA_RESTART相当でシステムコールを再開する)ため、プロセスが止まらず /gdb に「停止通知」が来ない/。結果として gdb 側は「切り替わらない」ように見える。
## もう少し詳しく:仕組みを順に追う
- 端末と SIGINT の発生
- ユーザーが端末で
Ctrl-Cを押すと、端末ドライバがその端末の フォアグラウンドプロセスグループ (fg pgrp) 全体にSIGINTを送る。 - どのプロセスがその fg-pgrp を持っているかが重要。gdb が端末を持っている時と、gdb が子に端末を渡している時で挙動は異なる。
- ユーザーが端末で
- gdb と端末制御(tcsetpgrp)
- 通常 gdb は
run時に(子を)フォアグラウンドにし、端末制御を子に渡すので(子が端末を所有)、=Ctrl-C= は子に届く。 - その上で gdb は ptrace/wait 系で子のイベント(停止やシグナル)を待ち、子がシグナルで停止すると wait が返って gdb がその事実を報告する。
- 通常 gdb は
- ptrace(=デバッガが子を監視する仕組み)とシグナルの伝播
- 子に
SIGINTが来て 子が停止状態(SIGINT によって止まる) になれば、ptrace/wait によって親(gdb)にイベントが届き、gdb がProgram received signal SIGINTを出す。これが dash のケース。 - /一方、子が SIGINT を捕まえて自前の処理で継続する場合/(シェルがプロンプトを再表示する、read を再開する等)、子は停止しないので gdb に停止イベントが届かない。従って gdb のプロンプトに切り替わったように見えない(ksh のケース)。
- 子に
- シェル固有の振る舞い(ksh vs dash)
- シェルは /インタラクティブな振る舞い/(プロンプト表示、行編集、子プロセスの起動時の信号伝播など)を実装しており、/SIGINT に対するデフォルト動作を自前で処理する/ことが多い。
- 例えば「プロンプト待ちのときに Ctrl-C を押すとプロンプトを再表示する」等はシェル側でシグナルを捕まえて処理しているため、プロセスが停止しない。
- 実装差で dash は
read()を割り込ませて停止したり(結果として gdb がシグナルを検知する状況になる)、ksh はSIGINTをハンドラで処理してreadを再スタートする(=SARESTART=)ため見た目が違っている――という可能性が高いです。
## 実験・検証方法(推奨)
次のコマンドで「どのプロセスがどの pgid を持っているか」「端末は誰のものか」「シグナルが到達しているか」を調べられます(OpenBSD 想定の例)。
- 実行前後のプロセス群情報:
# gdb を起動・run の最中に別端末から: ps -o pid,ppid,pgrp,sid,tty,stat,command -ww -p <gdb-pid>,<child-pid>
(=pgrp= が誰のものかを確認)
- OpenBSD なら
trussでシグナルの様子を見る:
# 別端末で truss を attach truss -p <child-pid> # Ctrl-C を押して、child に SIGINT が到っているか/ハンドラが走っているかを確認
- gdb 側で端末割当を明示してみる(別 pty を与える):
(gdb) tty /dev/pts/5 (gdb) run
これで gdb が子に別の pty を与え、端末制御の振る舞いを変えられます(比較実験に有用)。
- シェル内で
trapを確認(ksh の場合): ksh のソースや実行時トラップ設定を見たいなら、=trap= コマンドや起動オプション/ソースを確認します(ソースコードレベルでSIGINTの扱いを見るのが確実)。
## 回避策(期待する動作に合わせる)
- /常に gdb に戻したい(Ctrl-C を gdb で捕りたい)/なら、gdb 側で端末を渡さない/別の pty を使う、あるいは gdb のシグナルハンドリングを変更する方法を検討します。ただし普段は「端末を child に渡す」方がユーザに自然です。
- /子の SIGINT を確実に gdb に通知させたい/場合:子を停止状態にするようにシグナルを送る(例:キーボードからではなく gdb の
interrupt=/=signalを使う)、あるいはtrussで挙動確認する。 - gdb のコマンドで
handle SIGINT stop等の設定で動作を調整できます(gdb が受け取るべきシグナル動作の設定;ただしこれは主に gdb が ptrace で検出したシグナルをどう扱うかの指定です)。
## 補足(短く)
- 本質は「/誰が端末のフォアグラウンド pgid を持っているか/」と「/ターゲットが SIGINT を止める(stop)か自力で処理するか/」の組合せです。
- dash 側では
SIGINTによってプロセス停止扱い→ gdb が報告。ksh 側ではシェルがSIGINTを捕まえて吸収/継続→ gdb に停止イベントが来ない。これが観測された違いの原因として最も妥当です。
もしよければ、あなたの環境で試せる具体的コマンドをこちらで整えます。たとえば
A: 出してくれた ps の出力は決定的です — これで動きの違いがほぼ説明できます。まず出力を手短に解釈して、なぜ Ctrl-C が gdb に戻ってこないかを説明します。
PID PPID PGID TTY STAT COMMAND 506 37903 506 ttyp2 I egdb 93037 506 93037 ttyp2 IX+p ksh
### 重要な観察点(要点)
- =egdb=(gdb)のプロセスグループ(PGID)は =506=。
kshの PGID は93037— /gdb と別のプロセスグループ/になっている。kshのSTATに+が付いている (IX+p)。BSD 系のpsではこの+は そのプロセスが端末のフォアグラウンドプロセスグループに属している ことを示します。 → つまり現在端末(=ttyp2=)のフォアグラウンド pgid は =93037=(ksh)になっている。
### 端末と Ctrl-C(SIGINT)の振る舞い
- 端末で
Ctrl-Cを押すと、カーネルはその端末の フォアグラウンドプロセスグループ全体 にSIGINTを送ります。 - 今回のケースではフォアグラウンド pgid が
ksh=(93037)なので、=SIGINTは /ksh に届く/(gdb には届かない/gdb はそのシグナルを受けない)。 - さらに重要なのは ksh がその
SIGINTをどう扱うか です:- ksh が
SIGINTを捕まえて(ハンドラで)自前に処理し、プロセスを停止させない場合、プロセスは停止しないので gdb(親)は ptrace/wait で停止イベントを受け取らず、結果として gdb のプロンプトに「切り替わらない」ように見えます。 - dash のときは(あなたの最初の例)子が
SIGINTによって停止扱いになったため、ptrace/wait が返り、gdb がProgram received signal SIGINTと表示した —— という違いです。
- ksh が
### まとめ(なぜ ksh では切り替わらないか)
- 今回
kshは端末のフォアグラウンド(=+=)になっている →Ctrl-Cは ksh に送られる。 - ksh がその
SIGINTを吸収/処理して停止しない → gdb に停止通知が来ない。 - 結果:gdb 側には制御が戻らず、観測された通り
Ctrl-Cを押しても gdb プロンプトに切り替わらない。
(対照的に dash のケースはプロセスが停止扱いになったため gdb が通知を受け取り、=Program received signal SIGINT= を出した。)
### 実験/確認に有用なコマンド(そのまま試せます)
- フォアグラウンドプロセスグループの確認(既に得られていますが補助):
ps -o pid,ppid,pgid,tt,stat,comm -p 506,93037
- 別ターミナルから
SIGINTを直接送って挙動を見る(gdb が検知するか確認):
kill -SIGINT 93037 # ksh に直接 SIGINT を送る # あるいは子を一度停止させてから gdb が拾うか見る: kill -STOP 93037 # (gdb が ptrace で attach しているなら wait イベントが来るはず)
trussでシグナル処理を観察(OpenBSD):
truss -p 93037 # Ctrl-C を端末で押して、ksh がどのようにシグナルを扱うか確認する
- gdb に別の pty を与えて試す(gdb が端末を渡していない状態にする):
(gdb) tty /dev/ttypX # 適当な未使用の pty を指定 (gdb) run
→ こうするとフォアグラウンドが誰かが変わり、Ctrl-C の行き先が変わります。
### 回避案(目的別)
- 「Ctrl-C を押したら必ず gdb に戻したい」なら:
- gdb がターゲットに端末を渡さない(あるいは別 pty を使う)ようにする。
- または別端末から
killでシグナルを送る(デバッグ用)。
- 「ターゲットの SIGINT 挙動を確認したい」なら
trussやstrace相当でハンドラの動きを追うと確実。
try STOP
SIGINTが駄目なら、変わりにSIGSTOPで代行したらいいじゃんって提案してきた。 やってみるか。
te$ gdb -q /home/sakae/ksh-7.7/ksh Reading symbols from /home/sakae/ksh-7.7/ksh... (gdb) r Starting program: /home/sakae/ksh-7.7/ksh te$
こんな風にターゲットのkshを起動する。
te$ ps PID TT STAT TIME COMMAND 79597 p1 S 0:01.26 gdb -q /home/sakae/ksh-7.7/ksh (egdb) 4634 p1 SX+p 0:00.05 /home/sakae/ksh-7.7/ksh te$ kill -STOP 4634
別端末で、ターゲットのkshのPIDを確認。そしてそいつにSIGSTOPを投げる。
Program received signal SIGSTOP, Stopped (signal).
_thread_sys_read () at /tmp/-:2
warning: 2 /tmp/-: No such file or directory
(gdb) bt
#0 _thread_sys_read () at /tmp/-:2
#1 0x03510aa6 in _libc_read_cancel (fd=0, buf=0xcf7c79b3, nbytes=1)
at /usr/src/lib/libc/sys/w_read.c:27
#2 0x146efc32 in blocking_read (fd=0,
buf=0xcf7c79b3 "4\226R\225d\004:k4\320y|\317\006\"m\024\004:k4\027",
nbytes=1) at misc.c:1092
#3 0x146ca01f in x_getc () at edit.c:123
#4 0x146d2206 in x_e_getc () at emacs.c:1835
#5 0x146cd086 in x_e_getu8 (buf=0xcf7c7a3f "", off=0) at emacs.c:1845
#6 0x146cca58 in x_emacs (buf=0x42f94008 "", len=4096) at emacs.c:319
#7 0x146c9cf5 in x_read (buf=0x42f94008 "", len=4096) at edit.c:101
#8 0x146ea896 in getsc_line (s=0x6f2650f8) at lex.c:1099
#9 0x146ea482 in getsc__ () at lex.c:988
#10 0x146e83e9 in getsc_bn () at lex.c:1659
#11 0x146e4f41 in yylex (cf=44) at lex.c:167
#12 0x146f4256 in get_command (cf=0) at syn.c:206
#13 0x146f3fef in pipeline (cf=0) at syn.c:77
#14 0x146f3df9 in andor () at syn.c:98
#15 0x146f392f in c_list (multi=0) at syn.c:118
#16 0x146f3850 in yyparse () at syn.c:64
#17 0x146f37fd in compile (s=0x6f2650f8) at syn.c:769
#18 0x146ecbc7 in shell (s=0x6f2650f8, toplevel=1) at main.c:628
#19 0x146ec43e in main (argc=1, argv=0xcf7c9324) at main.c:452
SIGSTOPはkshが注目していないので(食べちゃわない)、gdbがキャッチしてくれた。 ハンドラーのテーブルを確認。
(gdb) p sigtraps[2]
$5 = {
signal = 2,
name = 0x3473a8b "INT",
mess = 0x3471815 "Interrupt",
trap = 0x0,
set = 0,
flags = 192, # = xc0
cursig = 0x0,
shtrap = 0x0
}
(gdb) p sigtraps[17]
$6 = {
signal = 17,
name = 0x3472162 "STOP",
mess = 0x3471107 "Suspended (signal)",
trap = 0x0,
set = 0,
flags = 0,
cursig = 0x0,
shtrap = 0x0
}
sh.h
/* values for Trap.flags */ #define TF_SHELL_USES BIT(0) /* shell uses signal, user can't change */ #define TF_USER_SET BIT(1) /* user has (tried to) set trap */ #define TF_ORIG_IGN BIT(2) /* original action was SIG_IGN */ #define TF_ORIG_DFL BIT(3) /* original action was SIG_DFL */ #define TF_EXEC_IGN BIT(4) /* restore SIG_IGN just before exec */ #define TF_EXEC_DFL BIT(5) /* restore SIG_DFL just before exec */ #define TF_DFL_INTR BIT(6) /* when received, default action is LINTR */ #define TF_TTY_INTR BIT(7) /* tty generated signal (see j_waitj) */ #define TF_CHANGED BIT(8) /* used by runtrap() to detect trap changes */ #define TF_FATAL BIT(9) /* causes termination if not trapped */
trap.flagsを変更したぐらいでは、SIGINTがgdbに届かないなあ。
Linux シグナルの基本と仕組み (カーネル v5.5 時点) こちらを見れば参考になるかな?
trap
linuxな人に教えられて、sigaction(int sig, const struct sigaction *act, struct sigaction *oact); なんてのを知った。オイラーの持っている本では、こんなの無かったぞ。進化してん ですかね。関連品には、
SEE ALSO
kill(1), kill(2), ptrace(2), sigaltstack(2), sigprocmask(2),
sigsuspend(2), wait(2), setjmp(3), sigaddset(3), sigblock(3),
sigpause(3), sigvec(3), tty(4)
こんなのが有るとな。このコマンド(システムコール)は、割り込みハンドラーの登録 なんだな。プチ、低位の奴で嬉しいぞ。kshの中で、そのハンドラーを探して、そこに BPを置いてみた。
(gdb) bt 5 #0 trapsig (i=2) at trap.c:117 #1 0x19c81d59 in x_emacs (buf=0x4eeec008 "", len=4096) at emacs.c:385 #2 0x19c7ecf5 in x_read (buf=0x4eeec008 "", len=4096) at edit.c:101 #3 0x19c9f896 in getsc_line (s=0x51fefaf8) at lex.c:1099 #4 0x19c9f482 in getsc__ () at lex.c:988 (More stack frames follow...)
そしてCtl-CでSIGINTを送信。ちゃんとハンドラーに制御が渡ってきた。中にやってる 事は、きわめて僅か。長居は禁物です。フラグをセットしてるだけ。
実働部分は、 emacs.c に有った。
case KINTR:
trapsig(SIGINT);
=> x_mode(false);
unwind(LSHELL);
x_arg = -1;
break;
emacs似の入力編集を実現する為のキー入力の一部になってた。そして、それを追跡 してくと unwindの中でsigsetjmp.sに突入してる。これで準初期化をしてんだな。こういう仕組み だと、gdbが付け入る隙が無いな。
stty
まあ、Ctl-C なんて文字は、emacs(もどき)にとっては、普通の文字だ。にも かかわらず、特別扱かいしてるのは、こんなアプリのせい。特殊文字ってか コントロール文字に対して、特別扱いしてねって設定がされているからに杉ない。
ad$ stty -e : discard dsusp eof eol eol2 erase intr kill lnext ^O ^Y ^D <undef> <undef> ^? ^C ^U ^V min quit reprint start status stop susp time werase 1 ^\ ^R ^Q <undef> ^S ^Z 0 ^W
Ctl-CでSIGINTを発生させてねって事だ。これって世界的(posix的)に決められて いる事なんだろうね。オイラー的には、
stty intr ^P
なんてやると、対応を変更できるぞ。
ad$ kill -l 1 HUP Hangup 17 STOP Suspended (signal) 2 INT Interrupt 18 TSTP Suspended 3 QUIT Quit 19 CONT Continued :
また、シグナル名は、こんな風にして確認できる。sttyで表示されるのとは、ちと 違うけど、名前が統一されていないのは、sttyの方言だ(統一性が無いな)。
syscall sigaction
あちら側(kernel)を追ってみる。kshを起動。
(gdb) bt 3 #0 sys_sigaction (p=0xd4614328, v=0xf57633b0, retval=0xf57633a8) at /usr/src/s\ ys/kern/kern_sig.c:264 #1 0xd06be629 in mi_syscall (p=0xd4614328, code=<optimized out>, argp=<optimiz\ ed out>, retval=<optimized out>, callp=<optimized out>) at /usr/src/sys/sys/sys\ call_mi.h:179 #2 syscall (frame=0xf57633f0) at /usr/src/sys/arch/i386/i386/trap.c:558 (More stack frames follow...)
ハンドラーの設置が開始されました。
signum = SCARG(uap, signum);
nsa = SCARG(uap, nsa);
osa = SCARG(uap, osa);
if (signum <= 0 || signum >= NSIG ||
(nsa && (signum == SIGKILL || signum == SIGSTOP)))
return (EINVAL);
=> sa = &vec;
if (osa) {
mtx_enter(&p->p_p->ps_mtx);
sa->sa_handler = ps->ps_sigact[signum];
sa->sa_mask = ps->ps_catchmask[signum];
最初の設置は、こんなのでした。ご丁寧な事に、昔の設置情報もご希望とあらば、取得(osa) できるみたいです。
(gdb) p/x signum $2 = 0xd
kill
殺人である。普通の人が聞いたらちょっと吃驚する様な語句。まあ業界用語だから その積りで。これって、プロセスを指定して、そいつに刺客を送るコマンドだ。
sys_kill にBPを置いて、起動してるdashに刺客を送信。ad$ kill -INT 21825
=> if (pid > 0) {
if ((pr = prfind(pid)) == NULL) {
if ((pr = zombiefind(pid)) == NULL)
return (ESRCH);
else
zombie = 1;
}
(gdb) p pid $1 = 21825 (gdb) p signum $2 = 2
本当に面白い(読むべき)部分は、こちらだ。
/* * type = SPROCESS process signal, can be diverted (sigwait()) * type = STHREAD thread signal, but should be propagated if unhandled * type = SPROPAGATED propagated to this thread, so don't propagate again */ void ptsignal(struct proc *p, int signum, enum signal_type type) =>
README
日本手話がおしえてくれること なんて本を読んだ。NHKの手話ニュースとかで どんな風なものかは見ていたけど、それはうわべだけだった。
日本語 英語 ドイツ語 、、日本手話 アメリカ手話、、、のように、れっきとした 言語。日本語を誰が作った? 自然発生。じゃ、日本手話は? 自然発生。
私はパンを食べる って日本語は、手話で、私 パン 食べる の様に、てにをはが無い 表現。単語を提示してく形になるそうな。そして、その単語は、手の形 位置 動き の組み合わせで表現するそうだ。
それから、それらを補って、顔の表情や、頭の素振りで、疑問とか否定とか、時制 (過去、現在、未来)を表現するそうだ。
昔は多数の話者が居たけど、どんどん話者が減ってきて、絶滅言語になるのではと 心配されている。
ろう者のオリンピック、いわゆるデフリンピックが11月20日だったかに、 日本では初めて開催 されるそうだ。選手は公平の為、練習中及び試合中は、補聴器の使用が禁止だ そうだ。どんな展開になるか、観戦してみたいものだ。応援で大声を出しても それは選手に届かない。身振り手振りのジェスチャーは手話では無いからなあ。 それとも、手話方言が新らしく誕生する?