riscV

Table of Contents

gdb pipe

ご質問の現象 — ` pipe_write() `が全くヒットせず、` pipe_read() `が大量に発生する — について、gdbやOpenBSDカーネルの挙動を踏まえて技術的に考察します。

## 🔍 背景理解

あなたの環境:

  • OS: OpenBSD 7.7
  • GDB: 15.2
  • デバッグ対象: ユーザープロセス `/tmp/a.out`(gdbから起動)
  • カーネルブレークポイント:
    • ` pipe_read() `(15回ヒット)
    • ` pipe_write() `(0回ヒット)

`pipe`デバイスは、読み込み時にバッファが空なら書き込み完了を待つ仕様(通常はブロック)ですが、`pipewrite`が呼ばれずに`piperead`が発生しているのは一見矛盾して見えます。

## ✅ 考えられる原因とgdbの挙動

### 1. gdb自身が擬似端末(PTY)を使っている

  • あなたの`fstat`の出力で、gdb(PID 2820)は以下を保持しています:

    3~10番FD: 複数のpipe 0~2番FD: ttyp1(仮想端末)

    gdbは内部的にデバッグ対象との通信(制御、I/Oのリダイレクト、break通知など)に複数のpipeを使っています。

  • これらのpipeは**fork/exec後の子プロセス(デバッグ対象)との通信チャネル**や、**gdbのバックエンド制御**のために使われており、gdb内部のスレッドや処理ロジックが何かを\*\*「読む」ために`piperead`を呼んでいる**が、実際の**書き込みはkernel側でバッファ済み**または**別プロセス/カーネルスレッドにより既に供給済み\*\*である可能性があります。

### 2. gdbの`next`コマンドが内部的に`waitpid()`を繰り返す

  • `next`の実装では、以下のようなループが走ります:
while (!breakpoint_hit) {
    waitpid(child, ...);
    check_status();
    consume_io(); // ← ここでpipe_readが走る可能性あり
}
  • このような状態でgdbはしばしば**自分自身のスレッドがselect/pollでpipeを監視し、それを`read()`で取り出して処理**します。例えば以下のような箇所が該当します:

    • 子プロセスのstdout/stderr監視
    • 内部通信用pipe(たとえばPython拡張やTUIのバックエンド、イベントループ)

    → `piperead()`だけが走るのは**gdbが読み取り役のみであり、書き込みは外部要因**(別スレッドやカーネル)で済んでいるケースがあり得ます。

### 3. `pipewrite()`は`copyout`失敗時に再試行されない

OpenBSDの`pipewrite()`は、ユーザーデータのコピーが一気にできる場合、即座に成功して`write`側が一瞬で終了することがあります(そして、それにBPが間に合わない可能性もゼロではないが、通常は発生する)。

しかし、あなたの環境では明示的に \*\*「pipe\writeヒットなし」\*\*ということなので、

  • すでにpipeバッファに書かれており、それをgdbが読み出しているだけ
  • あるいは\*\*`pipewrite`を呼んでいるのがgdbプロセス以外(たとえばカーネルスレッドやlibthread)\*\*

という可能性が高いです。

### 4. gdbが複数のpipeを使い、read側だけがトリガーされている

`fstat`出力から、gdbが8本のpipe(4ペア)を開いています(fd 3〜10)。

FD 3,4: pipe 0xd4609adc FD 5,6: pipe 0xd4609c5c FD 7,8: pipe 0xd4609d1c FD 9,10: pipe 0xd4609a1c

このような複数pipe構成は、**非同期I/O処理ループやインターナルRPC実装**でよく見られる構成です。いずれも「**データがすでに存在しているpipeに対するread**」であれば、`pipewrite()`には至りません。

## 🧪 調査のための追加アプローチ

### ✔ 1. `ktrace`/`kdump`でgdbの`write()`呼び出しをトレース

doas ktrace -i -p <gdb-pid>

doas kdump -T

→ `write()`の呼び出しがどのFDに向いているかを確認します。

### ✔ 2. `dtrace`(OpenBSDでは`dtrace`は制限あり)または`ktrace`で`pipewrite`システムコールの呼び出しプロセスを限定トレース

### ✔ 3. ユーザーランドコードからpipeを明示的にwriteして再実験

  • `a.out`側にpipe-writeするコードを埋め込み、ユーザランド起点のpipe\writeを観測できるか確認します
  • 例:
int fds[2];
pipe(fds);
write(fds[1], "hello", 5); // pipe_write発生
read(fds[0], buf, 5);      // pipe_read発生

## 🧩 まとめ

現象 考えられる原因
------------------- ------------------------------------
`piperead()`が多数ヒット gdb内部の制御用pipeのread処理、非同期処理の影響
`pipewrite()`がゼロ 書き込みはgdb自身ではなく、他のエンティティ、またはバッファ済みデータ
`next`での大量発生 waitループ中のI/Oポーリング、あるいはスレッド間pipeの消費動作

必要なら、`a.out`のコードも提示いただければ、より対象を特定できます。

何かこじつけっぽい気がするけど、どうよ? AIも政治家と一緒で、自分の都合の良い 様に解釈してマウントを取ってくるんで、ある程度距離を置く事が大事だな。

資料

Rust の借用規則はなぜ必要?

risc-V on zenn.dev

わかりやすくQEMUを説明してみる:連載目次

RISC-V Doc (pdf)

sakae@lu:tmp$ qemu-system-riscv64 -nographic -machine help
Supported machines are:
microchip-icicle-kit Microchip PolarFire SoC Icicle Kit
none                 empty machine
shakti_c             RISC-V Board compatible with Shakti SDK
sifive_e             RISC-V Board compatible with SiFive E SDK
sifive_u             RISC-V Board compatible with SiFive U SDK
spike                RISC-V Spike board (default)
virt                 RISC-V VirtIO board

RustでRISC-V OS自作!はじめの一歩

教育用OS xv6-riscvをQEMUで動かす:設定と実行の手引き

debug octox

octoxをgdbしようとして、起動スクリプトに -s -S を追加しておいて起動。 やおらgdbしようとしたら、駄目って言われた。 gdb-multiarchは、いかにもって感じだけどな。まぎらわしいぞ。 gcc-riscv64-unknown-elfだと普通のC言語用だしなあ。

https://static.dev.sifive.com/dev-tools/freedom-tools/v2020.12/riscv64-unknown-elf-toolchain-10.2.0-2020.12.8-x86_64-linux-ubuntu14.tar.gz

これを頂いてきて、riscv64-unknown-elf-gdb を、ぶっこぬいて使う。 結構古い時代のものだけど、とりあえずって事です。

使い方は、最初にoctoxを起動します。

sakae@lu:octox$ cargo run --target riscv64gc-unknown-none-elf
Running `qemu-system-riscv64 -s -S ... target/riscv64gc-unknown-none-elf/debug/octox

それから、やおらgdbを起動します。

sakae@lu:octox$ riscv64-unknown-elf-gdb target/riscv64gc-unknown-none-elf/debug/octox
GNU gdb (SiFive GDB-Metal 10.1.0-2020.12.7) 10.1
 :
(gdb) source .gdbinit
0x0000000000001000 in ?? ()
(gdb) b fork
Breakpoint 1 at 0x80007ee8: fork. (2 locations)
(gdb) c
Continuing.
[Switching to Thread 1.2]

Thread 2 hit Breakpoint 1, kernel::syscall::SysCalls::fork ()
    at src/kernel/syscall.rs:334
334                 fork()
(gdb) bt
#0  kernel::syscall::SysCalls::fork () at src/kernel/syscall.rs:334
#1  0x0000000080006f94 in kernel::syscall::Fn::call (self=...)
    at src/kernel/syscall.rs:71
#2  0x0000000080007178 in kernel::syscall::syscall ()
    at src/kernel/syscall.rs:125
#3  0x000000008000de06 in kernel::trap::usertrap () at src/kernel/trap.rs:81
#4  0x00000000000034dc in ?? ()

.gdbinitはちゃんと設定すれば起動時に読み込んでくれるけど、面倒なので手動でロード してます。手入力って方法でも良いです。

sakae@lu:octox$ cat .gdbinit
target remote 127.0.0.1:1234

こんな程度ですから。

それからスレッドなんて言われたんで、一応確認。

(gdb) info threads
  Id   Target Id                    Frame
   1    Thread 1.1 (CPU#0 [running]) 0x00000000800185d2 in kernel::proc::Cpu::unlock (self=0x8005ec58 <kernel::proc::CPUS>) at src/kernel/proc.rs:116
 * 2    Thread 1.2 (CPU#1 [running]) kernel::proc::fork ()
    at src/kernel/proc.rs:815

ケチケチ作戦で -smp 2 にしてるんで、スレッドは2つなのかな。

octox userlang debug

どうやってユーザーランド側のアプリをgdbするか? 過去ログを漁ったら、こんなのが 出てきた。 xv6-public の sh とか xv6-armv7 (6) ね。進歩無いね。いや、アーキテクチャが次世代の石だし、今を ときめくRustなんで、少しは進歩してるか。

しかしRustを選んだ特有の難しさが有る。あの人に相談ですよ。

user/Cargo.tomlに追加して、デバッグ情報を付与する。

[profile.dev]
debug = true
opt-level = 0

[profile.release]
debug = true

topに有るCargo.tomlにも追加しておく。

[profile.release]
debug = true

確認。この状態だとカーネルを起動した時に、initが失敗してパニくってしまう。 よって面倒だけど、一度 _lsを保存。Cargo.tomlの追加した部分をコメントにして 再ビルド。それからデバッグ情報付きなlsを復帰させる事

sakae@lu:octox$ riscv64-unknown-elf-readelf -S target/riscv64gc-unknown-none-elf/release/_ls | grep debug
  [ 6] .debug_loc        PROGBITS         0000000000000000  00008000
  [ 7] .debug_abbrev     PROGBITS         0000000000000000  0003f9a3
  [ 8] .debug_info       PROGBITS         0000000000000000  00043854
  [ 9] .debug_aranges    PROGBITS         0000000000000000  000c575a
  [10] .debug_ranges     PROGBITS         0000000000000000  000c8e6a
  [11] .debug_str        PROGBITS         0000000000000000  000f2c2a
  [14] .debug_frame      PROGBITS         0000000000000000  0017f890
  [15] .debug_line       PROGBITS         0000000000000000  001817a0

こんな風にデバック情報がユーザーランドのアプリに付いている事が絶対条件。

それから、gdbにユーザーランドのシンボルを認識させる。0x0は仮想アドレス。

sakae@lu:release$ riscv64-unknown-elf-readelf -l _jell

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000001000 0x0000000000000000 0x0000000000000000
                 0x0000000000018f8c 0x0000000000018f8c  R E    0x1000

gdb file (add-symbol-file)

(gdb) add-symbol-file target/riscv64gc-unknown-none-elf/release/_jell 0x0
(gdb) b evaluate_list
Breakpoint 2 at 0x457e
(gdb) b evaluate_conj
Cannot access memory at address 0x9e98
(gdb) c
Continuing.

Breakpoint 2, 0x000000000000457e in _jell::Evaluator::evaluate_list ()
(gdb) bt
#0  0x000000000000457e in _jell::Evaluator::evaluate_list ()
#1  0x0000000000004270 in _jell::Evaluator::evaluate ()
Backtrace stopped: frame did not save the PC

ちゃんと停止してくれた。

(gdb) l
1       in /rustc/5e16c662062fd6dee91f0fe2a1580483488d80cf/library/core/src/num/
uint_macros.rs
(gdb) f 1
#1  0x0000000000004270 in _jell::Evaluator::evaluate ()
(gdb) l
1       in /rustc/5e16c662062fd6dee91f0fe2a1580483488d80cf/library/core/src/num/
uint_macros.rs

けど、マクロっぽくて、gdbするには絶望的だな。

(gdb) info source
Current source file is src/kernel/exec.rs
Compilation directory is /var/my/srcs/octox
Located in /var/my/srcs/octox/src/kernel/exec.rs
Contains 263 lines.
Source language is rust.
Producer is clang LLVM (rustc version 1.89.0-nightly (5e16c6620 2025-05-24)).
Compiled with DWARF 2 debugging format.
Does not include preprocessor macro info.
(gdb) directory src/user/bin/
Source directories searched: /var/my/srcs/octox/src/user/bin:$cdir:$cwd

更に悪い事に、ソースが正しく表示されない。一見、dirrectory指令が効力を発揮 してる様に見えるけど。そして更に更に悪い知らせは、ステップ実行もユーザーランド 側では、ままならない事実。こうなったらRust+riscV+gdbの次期黄金コンビは棄却 せざるを得ないな。

xv6-riscv

悔しいのでMIT提供のxv6-riscvでやってみる。勿論ターゲットはユーザーランド側ね。 手間いらずのlsを遡上にする。.gdbinitは一度make qemu-gdbすると自動作成される。

sakae@lu:xv6-riscv$ cat .gdbinit
set confirm off
set architecture riscv:rv64
target remote 127.0.0.1:26000
symbol-file kernel/kernel
set disassemble-next-line auto
set riscv use-compressed-breakpoints yes

先にqemuを起動。

sakae@lu:xv6-riscv$ make qemu-gdb
 *** Now run 'gdb' in another window.
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -global virtio-mmio.force-legacy=false -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 -S -gdb tcp::26000

指示された様にgdbを起動。

sakae@lu:xv6-riscv$ riscv64-unknown-elf-gdb -q
The target architecture is set to "riscv:rv64".
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0x0000000000001000 in ?? ()
(gdb) c
Continuing.

これでカーネルが起動して、その最後にinitが誕生しshが動作開始。

^C
Thread 3 received signal SIGINT, Interrupt.
[Switching to Thread 1.3]
scheduler () at kernel/proc.c:455
455         intr_on();
(gdb) b exec
Breakpoint 1 at 0x8000446e: file kernel/exec.c, line 24.
(gdb) c
Continuing.
[Switching to Thread 1.1]

Thread 1 hit Breakpoint 1, exec (path=path@entry=0x3fffff9f10 "ls",
    argv=argv@entry=0x3fffff9e10) at kernel/exec.c:24
24      {

Ctl-cでgdbを停止させ、execにBPを配置してから継続。sh側でlsするとブレークしてくれる。

(gdb) add-symbol-file user/_ls 0x0
add symbol table from file "user/_ls" at
        .text_addr = 0x0
Reading symbols from user/_ls...
(gdb) b ls
Breakpoint 2 at 0x98: file user/ls.c, line 28.
(gdb) c
Continuing.

Thread 1 hit Breakpoint 2, ls (path=path@entry=0xaf8 ".") at user/ls.c:28
28      {
(gdb) bt
#0  ls (path=path@entry=0xaf8 ".") at user/ls.c:28
#1  0x0000000000000262 in main (argc=<optimized out>, argv=<optimized out>)
    at user/ls.c:81
#2  0x0000000000000274 in start () at user/ulib.c:13
#3  0x00000000000000da in ls (
    path=0x63 <fmtname+99> "\205N\205", <incomplete sequence \357>)
    at user/ls.c:45
Backtrace stopped: Cannot access memory at address 0x4f98

シンボルテーブルに _ls を追加してから ls()にBPを設定。

(gdb) n
34        if((fd = open(path, O_RDONLY)) < 0){
(gdb)
39        if(fstat(fd, &st) < 0){
(gdb)
45        switch(st.type){
(gdb)
52          if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
(gdb) n
56          strcpy(buf, path);
(gdb)
57          p = buf+strlen(buf);
(gdb)
58          *p++ = '/';
(gdb)
59          while(read(fd, &de, sizeof(de)) == sizeof(de)){
(gdb)
60            if(de.inum == 0)
(gdb)
62            memmove(p, de.name, DIRSIZ);
(gdb)
63            p[DIRSIZ] = 0;
(gdb)
64            if(stat(buf, &st) < 0){
(gdb)
68            printf("%s %d %d %d\n", fmtname(buf), st.type, st.ino, (int) st.size);
(gdb)
59          while(read(fd, &de, sizeof(de)) == sizeof(de)){

後はステップ実行。

(gdb) info source
Current source file is user/ls.c
Compilation directory is /var/my/srcs/xv6-riscv
Located in /var/my/srcs/xv6-riscv/user/ls.c
Contains 87 lines.
Source language is c.
Producer is GNU C17 13.2.0 -mcmodel=medany -mabi=lp64d -misa-spec=20191213 -march=rv64imafdc_zicsr -ggdb -gdwarf-2 -O -fno-omit-frame-pointer -fno-common -fno-builtin-strncpy -fno-builtin-strncmp -fno-builtin-strlen -fno-builtin-memset -fno-builtin-memmove -fno-builtin-memcmp -fno-builtin-log -fno-builtin-bzero -fno-builtin-strchr -fno-builtin-exit -fno-builtin-malloc -fno-builtin-putc -fno-builtin-free -fno-builtin-memcpy -fno-builtin-printf -fno-builtin-fprintf -fno-builtin-vprintf -fno-stack-protector -fno-pie.
Compiled with DWARF 2 debugging format.
Does not include preprocessor macro info.

info sourceでちゃんとソースの場所を保持してる事が確認できた。

(gdb) f 2
#2  0x0000000000000274 in start () at user/ulib.c:13
13        main();
(gdb) l
8       //
9       void
10      start()
11      {
12        extern int main();
13        main();
14        exit(0);
15      }

ついでにフレーム2を確認。

Other's

README

タコペディア にっぽん なんて本を読んだ。

タコの足は何本 ? 正確に描くのはにっぽん人。西洋では余り気にせずに 7本とか6本とかが有るらしい。

タコは多幸に通じるので、願掛けの対称にもなり、その場合は7本で描き 、成願したら、もう一本加えて8本にする。達磨さんのアレと同じ発想 だわな。

タコは強力な吸盤を持っている。その吸盤にあやかって、出来物とか毒の 吸い出し方面のシンボルにもなった。

タコの吸い出しと言う膏薬とか薬ね。昔、お世話になったな。今だったら、 老人性のイボ退治かな。この間、首にできて、それが服に擦れて痛かった。 医者へ行って、取ってもらったぞ。タコの吸い出しではなくて、組織を 凍死させるって方法かな?

白い湯気がでてるディワー瓶に綿棒を浸して、それを1回、2回と数えながら 塗りたくる。十分に魔術的な治療であった。

何でも食べちゃう中国人。さぞかしタコも食べているかと思ったら、人気は 全く無いそうだ。10本足のイカは中国内部まで浸透してるのに、タコは沿岸 地方の極わずかの地域でのみ食されているそうだ。全世界で水揚げされるタコ の60%は日本人が消費してるそうなんで、このアンバランスはどういう訳? 著者も首を捻っておられたぞ。 オイラーも不思議に思うぞ。八が大好き中国人、何故に避けるの?


This year's Index

Home