gdbの共演
gdb 15.2 + emacs 30.1
(gdb) b client_main Breakpoint 1 at 0x4ce76: file client.c, line 243. (gdb) r ls The target does not support running in non-stop mode. (gdb) q
gdb-mi.el
;; TODO Some commands can't be called with --all (give a notice about ;; it in setting doc) (defcustom gdb-gud-control-all-threads t "When non-nil, GUD execution commands affect all threads when in non-stop mod\ e. Otherwise, only current thread is affected." :type 'boolean :group 'gdb-non-stop :version "23.2")
最初これをnilにしたんだけど、効き目が無かったので、大局的に下記に設定した。
;; debug (setq gdb-non-stop-setting nil)
地味にemacsが更新されていくので、苦労するなあ。
.emacs.d/elpa/ddskk-20241227.2223/ddskk-autoloads.el: Warning: `defadvice' is an obsolete macro (as of 30.1); use `advice-add' or `define-advice' .emacs.d/elpa/ddskk-20241227.2223/ddskk-autoloads.el: Warning: `defadvice' is an obsolete macro (as of 30.1); use `advice-add' or `define-advice'
emacs 30.1になったら、こんなの出てくる様になった。最初 define-adviceを試したら 別の場所で酷いエラーが発生。しょうがないのので。もう一方のadvice-addを確認。 こちらで問題がなくなった。使い方の問題でも孕んでいるのでしょうか? 深入りはしない。
libevent
過去ログを調べたら、 systrace なんてのが有った。10年前には、OpenBSDにもsystraceなんてのが提供 されてたんですな。隔世の感がするぞ。今じゃktraceだもの。
tmuxでどんな風にlibeventを利用してるか軽く当ってみた。
ad$ grep event_set *.c control.c: bufferevent_setwatermark(cs->write_event, EV_WRITE, CONTROL_BUFFER_LOW, log.c: event_set_log_callback(log_event_cb); log.c: event_set_log_callback(NULL); proc.c: event_set(&peer->event, peer->ibuf.fd, events, proc_event_cb, peer); proc.c: event_set(&peer->event, fd, EV_READ, proc_event_cb, peer); server.c: event_set(&server_ev_accept, server_fd, EV_READ, server_accept, server.c: event_set(&server_ev_accept, server_fd, EV_TIMEOUT, tty.c: event_set(&tty->event_in, c->fd, EV_PERSIST|EV_READ, tty.c: event_set(&tty->event_out, c->fd, EV_WRITE, tty_write_callback, tty);
ad$ grep event_add *.c alerts.c: event_add(&w->alerts_timer, &tv); input.c: event_add(&ictx->timer, &tv); names.c: event_add(&w->name_event, &next); proc.c: event_add(&peer->event, NULL); server.c: event_add(&server_ev_accept, NULL); server.c: event_add(&server_ev_accept, &tv); tty.c: event_add(&tty->event_out, NULL); tty.c: event_add(&tty->event_in, NULL); tty.c: event_add(&tty->event_out, NULL);
/usr/include/event.hは必見! こんな親切はヘッダーファイルは初めて見たぞ。 これを参照するだけで、無味乾燥なmanより良く使い方が分る。今日から貴方もevent pro になれる事を保証しますよ。
DNS/http/RPCにも対応しますって、少々がんばりすぎな気がしないでもないですけど。 頭の隅に入れておこう。
tty/pty
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <termios.h> #include <util.h> #define NUM_PTY 3 int master_fd[NUM_PTY]; pid_t pids[NUM_PTY]; void spawn_shell(int index) { struct termios term; tcgetattr(STDIN_FILENO, &term); pids[index] = forkpty(&master_fd[index], NULL, &term, NULL); if (pids[index] == 0) { execlp("ksh", "ksh", NULL); perror("execlp"); exit(1); } } int main() { for (int i = 0; i < NUM_PTY; i++) { spawn_shell(i); } return 0; }
cc -g mini.c -lutil でコンパイル。
(gdb) p pids $1 = {89586, 75252, 53008} (gdb) p master_fd $2 = {4, 5, 6}
それぞれのプロセスが利用できるマスター・ファイルディスクリリプターが得られる。
ad$ fstat -p 53008 USER CMD PID FD MOUNT INUM MODE R/W SZ|DV sakae ksh 53008 text / 3395539 -r-xr-xr-x r 797712 sakae ksh 53008 wd /tmp 2 drwxrwxrwt r 512 sakae ksh 53008 0 / 5806587 crw--w---- rw ttyp6 sakae ksh 53008 1 / 5806587 crw--w---- rw ttyp6 sakae ksh 53008 2 / 5806587 crw--w---- rw ttyp6 sakae ksh 53008 4 / 5806582 crw-rw-rw- rw ptyp3 sakae ksh 53008 5 / 5806586 crw-rw-rw- rw ptyp5 sakae ksh 53008 10 / 5807247 crw-rw-rw- rwep tty
最後に作成されたプロセスを確認してみると、ttyp6が利用可能となっている事が 確認できる。詳細はpty(4)を参照。
(gdb) bt #0 fdopenpty (ptmfd=3, amaster=<optimized out>, aslave=<optimized out>, name=<\ optimized out>, termp=<optimized out>, winp=<optimized out>) at /usr/src/lib/li\ butil/pty.c:79 #1 fdforkpty (ptmfd=3, amaster=0x85832793f5c <master_fd+4>, name=0x0, termp=0x\ 7a986e673818, winp=0x0) at /usr/src/lib/libutil/pty.c:123 #2 0x0000085a34e2c717 in forkpty (amaster=0x85832793f5c <master_fd+4>, name=0x\ 0, termp=0x7a986e673818, winp=0x0) at /usr/src/lib/libutil/pty.c:108 #3 0x0000085832791ae9 in spawn_shell (index=1) at mini.c:16 #4 0x0000085832791b96 in main () at mini.c:26
実際はユーザーが呼び出した関数より更に深い部分で、/dev/ptmの助けを得て、fdを取得 している。
kernel側では、 tty_pty.c/ptcopen()
とかが、利用されるのかなあ? ユーザーランド
側とカーネル側でgdbを共演させてみる。OpenBSDを使っていれば容易い事だ。
(gdb) bt 8 #0 ptcopen (dev=1538, flag=<optimized out>, devtype=<optimized out>, p=<optimi\ zed out>) at /usr/src/sys/kern/tty_pty.c:407 #1 0xffffffff81fba9a5 in spec_open (v=0x3) at /usr/src/sys/kern/spec_vnops.c:1\ 50 #2 0xffffffff8205114e in VOP_OPEN (vp=<optimized out>, mode=3, cred=0x2000, p=\ 0xffff800023938cc0) at /usr/src/sys/kern/vfs_vops.c:138 #3 0xffffffff816d85e7 in ptm_vn_open (ndp=<optimized out>) at /usr/src/sys/ker\ n/tty_pty.c:1025 #4 0xffffffff816d80b5 in ptmioctl (dev=<optimized out>, cmd=<optimized out>, d\ ata=0xffff800023925460 "", flag=<optimized out>, p=0x3c801e1e20efc99e) at /usr/\ src/sys/kern/tty_pty.c:1116 #5 0xffffffff82051557 in VOP_IOCTL (vp=<optimized out>, command=3, data=0x2000\ , fflag=596872384, cred=0x68, p=0xffffffff826efe00 <rw_ops+32>) at /usr/src/sys\ /kern/vfs_vops.c:264 #6 0xffffffff81ec5070 in vn_ioctl (fp=0x5100, com=18446603336817988704, data=0\ x2000 <error: Cannot access memory at address 0x2000>, p=0xffff800023938cc0) at\ /usr/src/sys/kern/vfs_vnops.c:525 #7 0xffffffff8126b676 in sys_ioctl (p=0xffff800023925460, v=0xffff800023938cc0\ , retval=<optimized out>) at /usr/src/sys/kern/sys_generic.c:505 (More stack frames follow...)
3回コール(fork)されてるから間違い無いだろう。あれ? 標準入出力とエラーが有るけど、同一 デバイスだから、1回でいいのか。デバイスから読むのを入力、書くのを出力。若しくは エラーと、わざわざ3つに分類(FD 0,1,2)してるのは、利便性(shell)を考慮しての事だな。
BSDな人達が、unixにネットワークを追加した時は、そこまで親切にしなかった。通常の 出力とエラー出力の区別がつかないぞ。どうする? で、発明したのがタグなんだろうな。 ここからはエラーのデータですよとかで、受信した方も区別を付ける様に提案をした。 当然その提案は、世界に対して周知徹底されなければ意味をなさない。それで、RFC 文書で、ご意見を拝聴しますよ、とな。
for Linux
Q: unixの標準出力(fd 1)と標準エラー出力(fd 2)について質問します。 論理的にはfdで区別されてます。しかし実体は/dev/tty00の様に 同一デバイスです。
この出力を受け取る方は、どうやって標準出力だったかエラーだったかを 区別するのでしょうか?
A: とても良い質問です。Unix系OSでのファイルディスクリプタ(fd)とデバイスファイル(たとえば `/dev/tty0`)の関係についての理解が深いことがうかがえます。
### 結論:
出力先(たとえば `/dev/tty` や端末エミュレータ)は、標準出力(fd 1)と標準エラー出力(fd 2)の区別を基本的にはしていません。 区別されるのは、プロセス内部のファイルディスクリプタ番号(1か2)に基づいており、同じデバイスに送られても「どのfdから送られたか」はOSや端末には伝わりません。
—
## 詳しく解説:
### 1. fdはプロセス内の概念
`fd 1`(stdout)や `fd 2`(stderr)はプロセスが保持している「ファイルディスクリプタテーブル」に記録されています。
たとえば:
write(1, "hello\n", 6); // 標準出力へ write(2, "error!\n", 7); // 標準エラーへ
この2つは**論理的には別の出力先**かもしれませんが、たいていの場合、両方とも同じファイル(たとえば端末 `/dev/tty`)に出力されています。
### 2. デバイスは「どのfd経由か」を知らない
`/dev/tty` に出力されるとき、端末はただ「このバイト列を出力せよ」と言われるだけです。端末自身はその出力がfd 1から来たのかfd 2から来たのかは知りませんし、区別しません。
### 3. じゃあどうやって区別するの?
区別が必要な場合は、以下の方法が使われます。
#### a. リダイレクトで別のファイルに出力
command > out.txt 2> err.txt
この場合、`fd 1`と`fd 2`は異なるファイルに向いているので、後からログを見て区別できます。
#### b. プログラム内で明示的にマークする
たとえば、stderrに出す内容に `[ERROR]` をつけるなど。
fprintf(stderr, "[ERROR] something went wrong\n");
#### c. パイプやソケットを使うとfdごとに判別可能
`pipe()`や`dup2()`を使ってstdoutとstderrを別のパイプに割り当てると、読み手側でどちらから来たかを区別できます。
—
## 補足:端末上で見たときの違い
通常、stdoutは**バッファリングされて遅れて出る**のに対し、stderrは**即時に出る**(非バッファリング)ので、順番が逆転することもあります。これも「区別があるように見える」一因です。
色々とやりとりしてたら、Linuxでユーザーランドとカーネルをgdbで共演もできますよ、 なんて言い出したので、お願いしてみた。
以下に、**完全自動化された1本のセットアップスクリプト `setup.sh`** をご提供します。 これ1つで、Linuxカーネルのクローン、`initramfs`作成、QEMU起動、GDB接続まで一通り構築できます。
—
## ✅ `setup.sh`(完全自動セットアップスクリプト)
#!/bin/bash set -e # === 環境設定 === KERNEL_DIR=linux INITRAMFS_DIR=initramfs BUILD_DIR=build ARCH=x86_64 echo "🔧 環境初期化..." mkdir -p "$INITRAMFS_DIR/bin" "$BUILD_DIR" # === 1. Linuxカーネル取得・ビルド === if [ ! -d "$KERNEL_DIR" ]; then echo "📥 カーネル取得中..." git clone --depth=1 https://github.com/torvalds/linux.git "$KERNEL_DIR" fi cd "$KERNEL_DIR" echo "🛠 カーネル設定..." make defconfig scripts/config --enable CONFIG_DEBUG_INFO echo "🔨 カーネルビルド中..." make -j$(nproc) bzImage cd .. # === 2. initramfs の準備 === echo "📦 initramfs 初期化..." # init スクリプト cat > "$INITRAMFS_DIR/init" <<'EOF' #!/bin/sh mount -t proc none /proc mount -t sysfs none /sys echo "[INIT] Launching test..." /bin/test exec /bin/sh EOF chmod +x "$INITRAMFS_DIR/init" # test.c(stderr出力) cat > "$INITRAMFS_DIR/test.c" <<'EOF' #include <unistd.h> #include <string.h> int main() { const char *msg = "Hello from stderr\n"; write(2, msg, strlen(msg)); return 0; } EOF echo "🧪 test.c コンパイル..." gcc -static -o "$INITRAMFS_DIR/bin/test" "$INITRAMFS_DIR/test.c" echo "📦 initramfs アーカイブ作成..." cd "$INITRAMFS_DIR" find . | cpio -o -H newc | gzip > "../$BUILD_DIR/initramfs.cpio.gz" cd .. # === 3. gdb スクリプト生成 === cat > gdb-init <<EOF file $KERNEL_DIR/vmlinux target remote :1234 break tty_write continue EOF # === 4. QEMU 起動スクリプト === cat > run-qemu.sh <<EOF #!/bin/bash qemu-system-x86_64 \\ -kernel $KERNEL_DIR/arch/x86/boot/bzImage \\ -initrd $BUILD_DIR/initramfs.cpio.gz \\ -append "console=ttyS0 init=/init nokaslr" \\ -nographic \\ -s -S EOF chmod +x run-qemu.sh echo "✅ セットアップ完了" echo "" echo "次の手順で起動できます:" echo "" echo "1️⃣ ./run-qemu.sh" echo "2️⃣ 別のターミナルで: gdb -x gdb-init" echo "" ```
—
## 🧪 使用方法
chmod +x setup.sh ./setup.sh # 全自動セットアップ # 起動: ./run-qemu.sh # QEMU起動(gdb待機状態) # 別ターミナルで: gdb -x gdb-init # GDB起動・接続・tty_writeで停止
—
## 🧰 必要な依存パッケージ(事前に入れておいてください)
Ubuntu/Debian:
sudo apt install build-essential flex bison libncurses-dev \ gcc g++ make git gdb qemu-system-x86 cpio
—
## 📌 できること
- `write(2, …)` → `ttywrite()` の流れをgdbで完全に追跡可能
- stderr(fd=2)とstdout(fd=1)の挙動の違いをカーネルレベルで確認
- 必要に応じて `ksyswrite` や `vfswrite` にもブレーク可能
—
## 📁 生成されるファイル一覧
- `linux/` → カーネルソース&ビルド結果(`vmlinux`, `bzImage`)
- `initramfs/` → `/init`, `test.c`, `/bin/test`
- `build/initramfs.cpio.gz` → initramfs
- `run-qemu.sh` → QEMU起動スクリプト
- `gdb-init` → GDB自動ブレークスクリプト
—
これで準備は完了です! 環境のチューニングや特定関数の追跡など、ご希望があればさらに深掘りできます。どうしますか?
もっと手軽にやる方法として、gdbでカーネル空間の追跡をするには、gdb + qemu + カーネルシンボル が必要です。簡易な方法は kprobes や ftrace、あるいは bpftrace を使う なんて事もできるみたい。わざわざ苦労をしょいこむ事は無いけどね。 OpenBSD何それって言う人へのささやかなプレゼントです。
bpftrace
qemuを持ち出さなくても、こんな手軽な機構が用意されてて、即利用できるみたいだ。
これって源流はSolarisだよな。それがFreeBSDに移植された。そして権利の関係も あって、リナでも同様な機構を新たに実装したとな。
バグ調査やパフォーマンス改善に役立つ!eBPFを用いたトレーシングについて
さくらさんの所にも紹介があった。
README
新聞記者という第一の人生を上がって、バルセロナで第二の人生として 豆腐屋になった方の体験記である。
なんでバルセロナ? 記者時代に取材の関係で立ち寄ったバルセロナが 自分をそっと受け入れてくれた事。そんな記憶が残っていて、リフレッシュ 休暇で、奥さんと再び訪れたそうな。食い物に満足した。だったら住んで みてもいいな。奥さんも同感。ただし、大好きな豆腐や納豆、油あげは 現地でも手にはいったけど、中国人とかが作っていて信用ならん。 だったら、自分で豆腐屋を始めるしかないっしょ。
こうして第二の人生は始まる。それを称して長女は、豆腐アドベンチャーだと 言う。言いえて妙。やらないでクヨクヨするより、飛び込んでしまえ。
帰国して、こういう探検記を収録するサイトを作った。 一身ニ生倶楽部
番外編の記事に スペイン大停電は予想外の原因で起きた こんなのが出てた。日本で同様な事が 発生したらどうなるだろね?