OpenBSD/riscV64
Table of Contents
timeout
前回やった、時間切れを引き起こすコマンドがどうなってるか調べてみる。 軽くmanすると、-f を付けてあげると子供に波及しなくなる。-s signalで、お望み の信号を送出出来る。それでも終了しない頑固な奴には、-k timeで、time時間だけ 更に猶予を与えてから、伝家の宝刀のKILLを発出する。あのトランプ爺の手口そっくり だな。
pid = fork(); if (pid == -1) err(1, "fork"); else if (pid == 0) { /* child process */ signal(SIGTTIN, SIG_DFL); signal(SIGTTOU, SIG_DFL); execvp(argv[0], argv); warn("%s", argv[0]); _exit(errno == ENOENT ? 127 : 126); } : set_interval(first_kill); : } else if (sig_term) { if (!foreground) killpg(pgid, killsig); else kill(pid, (int)sig_term); if (do_second_kill) { set_interval(second_kill); second_kill = 0; sig_ign = killsig; killsig = SIGKILL; } else
指定されたコマンドを起動。そのコマンドを first_kill
時間だけ実行。
その後、killを実行。まだ生きていて、最終通告時間が設定されてたら、強権発動
。
STANDARDS The timeout utility is compliant with the IEEE Std 1003.1-2024 (“POSIX.1”) specification. HISTORY The timeout utility first appeared in GNU Coreutils 7.0 and has been available since OpenBSD 7.0.
このコマンドはIEEEの提案 (compliantって、人の言いなりたなるって意味も有るぞ) で標準になった。つられて、GNUも同調。OpenBSDにも 波及した。IEEEってトランプ爺の国だからね。国際化って美辞麗句だけど、俺の言う 事を黙って聞けって事よ。
この賞金の原資は誰が提供してる? それは、技術のルールを決めて覇者になりたい メーカーに決っているだろう(俺がルールだ、って漫画にも出てくるな)。少額な投資で多大なリターンが約束されますから。
riscv Regster
gp,tpってのがABIで規定されてるけど、どんな用途が目的? 昔のCPUには無かったぞ。
A: はい、あなたの理解はかなり正確です。RISC-Vの`gp`(`x3`)と`tp`(`x4`)は、それぞれ以下のような役割で使用されることが多いです。
—
### 🔹 `gp`(Global Pointer、x3)
- 用途: グローバル変数へのアクセス高速化
- 主な利用場面:
- コンパイラ(GCCなど)は、`.sdata`(small data)や`.sbss`(small BSS)セクションの**中小規模のグローバル変数**を、`gp`を基点とした**オフセットアクセス**で行います。
- 通常のグローバル変数のうち、サイズが小さくアクセス頻度が高いものは、このようにして効率的にアクセスされます。
#### ✅ 具体例:
int gcounter = 0;
このような変数が`.sdata`に置かれると、 アセンブリでは以下のように`gp`からのオフセットでアクセスされることがあります。
lw a0, offset(gp) # 例: lw a0, 16(gp)
#### 📌 備考:
- `gp`の初期値は、OSのリンカスクリプト(`ld`)によって決定される`.sdata`セクションの**中心あたり**。
- アクセス範囲は ±2048バイト(12ビット即値)に制限されます。これを超えると通常のグローバル変数アクセス(`auipc+ld/st`)に切り替えられます。
—
### 🔹 `tp`(Thread Pointer、x4)
- 用途: スレッドローカルストレージ(TLS)やスレッドコンテキストの基点
- 主な利用場面:
- **マルチスレッド環境**で、スレッドごとに独立なデータ(例: errno、TLS変数、スレッド管理構造など)を扱うための基準ポインタ。
- OSカーネルやランタイム(glibc、muslなど)は、`tp`を使って現在スレッドのPCBやTLS領域の先頭を指すように設定します。
#### ✅ 具体例:
- ユーザ空間のTLS変数:
_thread int localvar;
は、RISC-VのABIに従って`tp`を基準にしたオフセットアクセスにコンパイルされます。
- カーネル空間でも:
- `tp` = currentスレッドのPCBのアドレス
- PCBの中にカーネルスタックや各種スケジューラ情報が格納されており、`tp`を通してアクセス
—
### 💡まとめ
レジスタ | 名称 | 主な用途 | アクセス例 |
---- | -------------- | -------------------------- | ------------------- |
`gp` | Global Pointer | `.sdata/.sbss`中の変数への高速アクセス | `lw a0, offset(gp)` |
`tp` | Thread Pointer | TLS変数、スレッド固有情報(PCBなど)の参照基点 | `lw a0, offset(tp)` |
—
### 余談
- ARMやx86\64でも似た用途のレジスタがあります(例:x86\64では`fs`や`gs`がTLSに使われます)。
- RISC-Vでは、TLSモデルとして IE (initial exec) や LE (local exec) モデルが用いられますが、いずれも`tp`をベースとするアクセスが行われます。
OpenBSD/riscV64
riscvでちゃんとしたOSを動作させたい。それにはOpenBSDに限る。稼動ホストは 前回のインチキ・コンペで好成績だったLubuntuにする。
Let's install OpenBSD/riscv64 on QEMU (on windows)
Setup an OpenBSD RISCV64 VM in QEMU (on openbsd)
sakae@lu:rv$ cat MKDISK qemu-img create -f qcow2 riscv64.img 8G sakae@lu:rv$ cat INS qemu-system-riscv64 \ -M virt -m 1G -bios fw_jump.bin -kernel u-boot.bin \ -device virtio-net-device,netdev=net -netdev user,id=net \ -device virtio-blk-device,drive=sd -drive file=miniroot76.img,if=none,id=sd \ -device virtio-blk-device,drive=hd -drive file=riscv64.img,if=none,id=hd
訳あってOpenBSD 7.6を入れてみた。 起動時にモニター画面になるけど、viewメニューから、シリアルを選択すると、見慣れ たインストール画面が出てくるぞ。
インストール先は、上記ではhdとなってるが、それ はsd1になるんで、sd1を指定する事。httpから本体を入れるんだけど、その筆頭は、あ の北陸の大学を提示してきた。ちゃんとローカライズしてるのね。いつもはCDから なんで、気がつかなかった。
sakae@lu:rv$ cat boot qemu-system-riscv64 \ -M virt -m 1G -bios fw_jump.bin -kernel u-boot.bin \ -device virtio-net-device,netdev=net \ -netdev user,hostfwd=tcp::2022-:22,id=net \ -device virtio-blk-device,drive=hd \ -drive file=riscv64.img,if=none,id=hd \ -nographic
こちらは常用するbootスクリプトだ。ネットは外側から接続可能としてある。 普段はコンソール画面だけど、非常用って事です。
デフォではgdbが用意されていないので、pkgから入れた。
rv$ gdb a.out GNU gdb (GDB) 9.2 : Breakpoint 1, main () at t.c:4 4 printf("hello riscV\n"); (gdb) disassemble Dump of assembler code for function main: 0x00000001197ed7f8 <+0>: addi sp,sp,-16 => 0x00000001197ed7fa <+2>: sd ra,8(sp) 0x00000001197ed7fc <+4>: sd s0,0(sp) 0x00000001197ed7fe <+6>: addi s0,sp,16 0x00000001197ed800 <+8>: auipc a0,0xfffff 0x00000001197ed804 <+12>: addi a0,a0,-760 # 0x1197ec508 0x00000001197ed808 <+16>: auipc ra,0x0 0x00000001197ed80c <+20>: jalr 184(ra) # 0x1197ed8c0 <printf@plt> 0x00000001197ed810 <+24>: li a0,0 0x00000001197ed812 <+26>: ld ra,8(sp) 0x00000001197ed814 <+28>: ld s0,0(sp) 0x00000001197ed816 <+30>: addi sp,sp,16 0x00000001197ed818 <+32>: ret End of assembler dump.
一応動作してる。objdump -d は、まだ未実装らしいので、llvm-objdump を利用して くれとの事。
my OpenBSD
自前のカーネルを作ってみる。
makeoptions DEBUG="-g"
何はともあれ、設定に入れておく事。
rv# time make : ld -T ld.script -X --warn-common -nopie -o bsd ${SYSTEM_HEAD} vers.o ${OBJS} text data bss dec hex 4629672 194060 667896 5491628 53cbac mv bsd bsd.gdb ctfstrip -S -o bsd bsd.gdb 70m35.52s real 47m47.74s user 18m34.00s system
qemu上でのセルフ・ビルドです。
rv$ ls *.o | wc 652 652 7054 rv$ ls -l bsd* -rwxrwx--- 1 root wobj 5667334 Jun 25 06:14 bsd* -rwxrwx--- 1 root wobj 23987358 Jun 25 06:14 bsd.gdb*
ファイル数が少な目になる様に調整。
rv# du -sh /usr/obj/sys/arch/riscv64/compile/RISCV64/ 151M /usr/obj/sys/arch/riscv64/compile/RISCV64/
そのおかげか、コンパイル現場は、こんな容量だった。
rv# grep '##' /sys/conf/GENERIC ##option CRYPTO # Cryptographic framework ##option QUOTA # UFS quotas ##option EXT2FS # Second Extended Filesystem ##option NFSCLIENT # Network File System client ##option NFSSERVER # Network File System server ##option CD9660 # ISO 9660 + Rock Ridge file system ##option INET6 # IPv6 ##pseudo-device pf # packet filter ##pseudo-device pflog # pf log if ##pseudo-device pfsync # pf sync if ##pseudo-device pflow # pflow export if
こんな具合にOSの機能を削った。失敗するとmake cleanしてからやり直し。削るなら こちらの方が無難だろう。configした時に、検出してくれる可能性が高いからね。 まあ、デバイス・ツリーの剪定だからね。
rv# grep '##' /sys/arch/riscv64/conf/RISCV64 ##radeondrm* at pci? ##drm* at radeondrm? ##wsdisplay* at radeondrm? ##iwm* at pci? # Intel WiFi Link 7xxx : ## wi* at uhub? # WaveLAN IEEE 802.11DS ## udsbr* at uhub? # D-Link DSB-R100 radio ##radio* at udsbr? # USB radio
頑張ればUSBの所をもっと削れるだろうけど、程々にしといた。
rv# ls -l /bsd* -rwx------ 2 root wheel 5667334 Jun 25 06:38 /bsd -rwx------ 2 root wheel 5667334 Jun 25 06:38 /bsd.booted -rwx------ 1 root wheel 8763415 Jun 24 14:01 /bsd.org -rw------- 1 root wheel 12349853 Jun 23 07:16 /bsd.rd rv# uname -a OpenBSD rv.my.domain 7.6 RISCV64#1 riscv64
後は、Lubuntu側に、下記を配置するだけだな。 /usr/obj/sys/arch/riscv64/compile/RISCV64/bsd.gdb
break
Lubuntuには、OpenBSD 7.6のソースが鎮座してて、amd64,i386のカーネルをbreakできる 様にしてるんで、今度はriscV64だ。起動スクリプトに -s を付けておいてBSDを起動。 それから、おもむろにgdbを起動する。
sakae@lu:RISCV64$ riscv64-unknown-elf-gdb bsd.gdb GNU gdb (SiFive GDB-Metal 10.1.0-2020.12.7) 10.1 : Reading symbols from bsd.gdb... cpu_idle_cycle () at /usr/src/sys/arch/riscv64/riscv64/machdep.c:204 204 } (gdb) bt #0 cpu_idle_cycle () at /usr/src/sys/arch/riscv64/riscv64/machdep.c:204 #1 0xffffffc0004ebb52 in sched_idle (v=0xffffffc000894780 <cpu_info_primary>) at /usr/src/sys/kern/kern_sched.c:194 #2 0xffffffc00020fdec in proc_trampoline () Backtrace stopped: frame did not save the PC : (gdb) bt #0 sys_execve (p=0xffffffc051ff6008, v=0xffffffc04c775f78, retval=<optimized out>) at /usr/src/sys/kern/kern_exec.c:259 #1 0xffffffc00020fc30 in mi_syscall (p=0xffffffc051ff6008, code=<optimized out>, argp=0xffffffc04c775f78, retval=<optimized out>, callp=<optimized out>) at /usr/src/sys/sys/syscall_mi.h:179 #2 svc_handler (frame=0xffffffc04c775ec0) at /usr/src/sys/arch/riscv64/riscv64/syscall.c:57 #3 0xffffffc0002708f4 in do_trap_user (frame=0xffffffc04c775ec0) at /usr/src/sys/arch/riscv64/riscv64/trap.c:149 #4 0xffffffc000223f92 in cpu_exception_handler_user () Backtrace stopped: frame did not save the PC
sys_execve
でも停止するか確認した。frame 2,3,4がマシン依存性のあるコード
なんだな。frame 1は、 mi_syscall
となってる。miはマシン・インディペンデント
(被依存)を示している。mdは依存ね。
(gdb) bt #0 sys_execve (p=0xffff800023938018, v=0xffff800023925bb0, retval=0xffff800023925b30) at /usr/src/sys/kern/kern_exec.c:259 #1 0xffffffff81ee33d8 in mi_syscall (p=0xffff800023925b30, code=-140736891561040, argp=0x5d55b1dd41f9336a, retval=<optimized out>, callp=<optimized out>) at /usr/src/sys/sys/syscall_mi.h:179 #2 syscall (frame=0x5d55b1dd41f9336a) at /usr/src/sys/arch/amd64/amd64/trap.c:577 #3 0xffffffff822fa134 in Xsyscall ()
こちらは参考までに、amd64の場合。
一方、起動したOS上では、Ctl+a c でqemuのモニターに入る事ができる(出る時は、 もう一度Ctl+a c)。
rv$ (qemu) info registers CPU#0 V = 0 pc ffffffc000349d74 mhartid 0000000000000000 mstatus 0000000a000000a2 hstatus 0000000200000000 vsstatus 0000000a00000000 mip 0000000000000000 mie 00000000000002a8 mideleg 0000000000001666 hideleg 0000000000000000 medeleg 0000000000f0b509 hedeleg 0000000000000000 mtvec 0000000080000408 stvec ffffffc000223e38 vstvec 0000000000000000 mepc ffffffc00048a3fc sepc ffffffc000349d74 vsepc 0000000000000000 mcause 0000000000000009 scause 8000000000000005 vscause 0000000000000000 mtval 0000000000000000 stval 0000000000000000 htval 0000000000000000 mtval2 0000000000000000 mscratch 0000000080034000 sscratch 0000000000000000 satp 8000000000080b9d x0/zero 0000000000000000 x1/ra ffffffc0004ebb52 x2/sp ffffffc04c724e50 x3/gp ffffffc00082cd38 x4/tp ffffffc000894780 x5/t0 0000000000000001 x6/t1 ffffffc00020ff74 x7/t2 0000003e86fcbb18 x8/s0 ffffffc04c724e60 x9/s1 ffffffffffffffff x10/a0 0000000000000000 x11/a1 0000000000000000 x12/a2 0000000000000000 x13/a3 0000000000000000 x14/a4 fffffffffffffffc x15/a5 ffffffc000348eb0 x16/a6 0000000000000001 x17/a7 ffffffc04c75bd80 x18/s2 ffffffc0006439bd x19/s3 ffffffc00087e600 x20/s4 ffffffc051ffef00 x21/s5 ffffffc00082e188 x22/s6 ffffffc00082cab2 x23/s7 0000000000000001 x24/s8 fffffffffffffffe x25/s9 0000000000000003 x26/s10 ffffffc000894780 x27/s11 0000000000000000 x28/t3 0000000c0c71c2ec x29/t4 0000000000000059 x30/t5 00000000051eb850 x31/t6 00000000028f5c28 f0/ft0 416cd6d010000000 f1/ft1 416cd6d000000000 f2/ft2 0000000000000000 f3/ft3 0000000000000000 f4/ft4 0000000000000000 f5/ft5 0000000000000000 f6/ft6 0000000000000000 f7/ft7 0000000000000000 f8/fs0 0000000000000000 f9/fs1 0000000000000000 f10/fa0 0000000000000000 f11/fa1 0000000000000000 f12/fa2 0000000000000000 f13/fa3 0000000000000000 f14/fa4 0000000000000000 f15/fa5 0000000000000000 f16/fa6 0000000000000000 f17/fa7 0000000000000000 f18/fs2 0000000000000000 f19/fs3 0000000000000000 f20/fs4 0000000000000000 f21/fs5 0000000000000000 f22/fs6 0000000000000000 f23/fs7 0000000000000000 f24/fs8 0000000000000000 f25/fs9 0000000000000000 f26/fs10 0000000000000000 f27/fs11 0000000000000000 f28/ft8 0000000000000000 f29/ft9 0000000000000000 f30/ft10 0000000000000000 f31/ft11 0000000000000000
CSR'sを見ると、カーネルはS-modeで動作してるっぽいな。正式にはコード嫁。
gp,tp
gp,tpがどんな風に利用されてるか簡単に調べてみる。C言語の中にasm構文を埋め込む のはOpenBSDの方々は嫌い(でも若干は利用してる)のようなんでオイラーは嬉しい。マシン依存部分。
rv$ cd /sys/arch/riscv64/riscv64/ rv$ grep gp *.S exception.S: sd gp, (TF_GP)(sp) exception.S: la gp, __global_pointer$ exception.S: ld gp, (TF_GP)(sp) locore.S: lla gp, __global_pointer$ locore.S: lla gp, __global_pointer$ locore.S: lla gp, __global_pointer$ locore.S: lla gp, __global_pointer$ rv$ grep tp *.S cpuswitch.S: ld a5, CI_CURPCB(tp) cpuswitch.S: sd tp, P_CPU(a1) // Store curcpu cpuswitch.S: sd a5, CI_CURPCB(tp) cpuswitch.S: sd a1, CI_CURPROC(tp) cpuswitch.S: call pmap_set_satp exception.S: sd tp, (TF_TP)(sp) exception.S: ld tp, (TRAPFRAME_SIZEOF)(sp) exception.S: sd tp, (TRAPFRAME_SIZEOF)(sp) exception.S: ld tp, (TF_TP)(sp) exception.S: /* Check for astpending */ exception.S: ld a1, CI_CURPROC(tp) locore.S: ld tp, CI_SELF(a1) locore.S: csrw satp, s2 locore.S: csrw satp, a2
tpの方は地道に追跡しないと詳細不明だけど、それっぽい利用をしてるみたいだ。 gpの方は、すっきりしてるね。
rv$ nm bsd | grep __global_pointer ffffffc00082cd38 D __global_pointer$ rv$ readelf -a bsd|grep __global_pointer Symbol table '.symtab' contains 15094 entries: Num: Value Size Type Bind Vis Ndx Name 2738: ffffffc00082cd38 0 NOTYPE GLOBAL DEFAULT 5 __global_pointer$
リンカーが作り出したシンボルなのかな? Section Headers:を見ると、 .sdata あたりに なるっぽい。でも、りよう してる場所が無いぞ。何故だ?
ChatGPTとあれこれやりながら調査したけど、gpを利用していない、形式的に設定 だけして、お茶を濁しているって結論になった。
speed test
3.5Gのriscv DISKのバックアップをi386マシンにした。ここでもriscv64が動作 するはずなんで、コンパイルの時間を計測。
rv$ time make cc -g -O0 -MD -MP -c cmp.c cc -g -O0 -MD -MP -c ls.c cc -g -O0 -MD -MP -c main.c cc -g -O0 -MD -MP -c print.c cc -g -O0 -MD -MP -c util.c cc -g -O0 -MD -MP -c utf8.c cc -o ls cmp.o ls.o main.o print.o util.o utf8.o -lutil 1m55.05s real 1m10.51s user 0m36.47s system
Lubuntuな環境にあるriscv64確認。
rv$ time make cc -g -O0 -MD -MP -c cmp.c : cc -o ls cmp.o ls.o main.o print.o util.o utf8.o -lutil 0m14.60s real 0m05.41s user 0m07.55s system
KVMと言うamd64な石。
ad$ time make cc -g -O0 -MD -MP -c cmp.c : cc -o ls cmp.o ls.o main.o print.o util.o utf8.o -lutil 0m00.60s real 0m00.19s user 0m00.32s system
容量比べ
amd64とriscV64のバイナリーサイズの比較。amd64
ad$ ls -l /bin/tar /bin/sh /usr/bin/vi -r-xr-xr-x 3 root bin 797712 Sep 30 2024 /bin/sh* -r-xr-xr-x 3 root bin 453304 Sep 30 2024 /bin/tar* -r-xr-xr-x 3 root bin 364000 Sep 30 2024 /usr/bin/vi*
こちらは、riscV64。
rv$ ls -l /bin/tar /bin/sh /usr/bin/vi -r-xr-xr-x 3 root bin 617680 Sep 30 2024 /bin/sh* -r-xr-xr-x 3 root bin 334728 Sep 30 2024 /bin/tar* -r-xr-xr-x 3 root bin 290048 Sep 30 2024 /usr/bin/vi*
比較対称は適当に選んだもの。公平な選択と思ってくれ。統計のサンプリング理論を 持ち出すまでもなく、riscV64の方がコンパクトだな。
USB-HDD
TVに取り付けている、録画用のHDDをアクオスが認識しなくなった。放っておくと 普通に使える事がある。そんな不安定な状況を経て、とうとう認識しなくなった。 もう5年ぐらいなるんで、寿命だろう。
USB 3.2(Gen 1)接続な4TByteのHDDを購入。14K円だった。普通録画で500時間、4KBSで 350時間見当らしい。見たら消すって運用をすれば、2TByteで十分と思うんだけど、 どうよ。大事なのを保存しときたいらしいんで4Tになった。でも、データの移動は できないからなあ。オイラー的には無駄だと思うんだけど。2Tだと10K円ぐらい。バッファロー強いですねぇ。 今迄使ってたのもバッファロー製でしたよ。
取り外したHDDが本当に死んでいるか、OpenBSDなマシンに接続して確認。結果は、 全く認識しなかった。もう粗大ゴミで出すしかないな。長年ご苦労様でした。
今迄使ってた河豚板のHDDが認識されなくなっちゃったんで点検。from dmesg
sd1 at scsibus4 targ 1 lun 0: <USB-HS, SAMSUNG SP1604N, 0.01> serial.0411002a000001112523 sd1: 152627MB, 512 bytes/sector, 312581808 sectors WARNING: R/W mount of /mnt denied. Filesystem is not clean - run fsck sd1(umass0:1:0): Check Condition (error 0x70) on opcode 0x28 SENSE KEY: Media Error ASC/ASCQ: Unrecovered Read Error sd1(umass0:1:0): Check Condition (error 0x70) on opcode 0x28 SENSE KEY: Media Error ASC/ASCQ: Unrecovered Read Error
この所の暑さのせいでHDDもやる気が失せたのかな?
ob$ doas fsck /dev/sd1d CANNOT READ: BLK 3400 THE FOLLOWING DISK SECTORS COULD NOT BE READ: 3404, BAD SUMMARY INFORMATION CONTINUE? yes ** Last Mounted on /mnt ** Phase 1 - Check Blocks and Sizes ** Phase 2 - Check Pathnames ** Phase 3 - Check Connectivity ** Phase 4 - Check Reference Counts ** Phase 5 - Check Cyl groups FREE BLK COUNT(S) WRONG IN SUPERBLK SALVAGE? yes 280050 files, 83939288 used, 187158349 free (20013 frags, 23392292 blocks, 0.0% fragmentation) MARK FILE SYSTEM CLEAN? yes ***** FILE SYSTEM WAS MODIFIED *****
README
獣医さんがゆく なんて本を読んだ。
医者の免許は、厚生労働省ってのは知ってたけど、獣医さんは、農林水産省と 環境省のコラボだそうだ。競馬はJRAなんで関連で農林水産省ってのは頷ける。 いや、競馬なんてより、畜産の方がずっと範囲が広い。じゃ、環境省は何で からんで来る? 利権か? 野生動物も含めた世界を扱かう必要が声高に なってきているので、連名になったそうだ。
獣医と言えば、町の動物病院が身近だろう。犬よりも猫の方が人気ってのは 知っていたけど、大学で学ぶのは犬だけだそうだ。ってか、動物界を代表して 馬、牛、豚、うさぎ、犬と、もう1種(何だっけ、忘れた)の6種を勉強するそうだ。 後は、十把一絡で、推測してください、らしい。そんなアバウトな事でいいのか?
家畜って、凄い英語なのね。
Translate from Japanese to English: 家畜 livestock 名詞 1. livestock (家畜) 2. domestic animals (家畜) 3. cattle (牛, 家畜)
これemacsからググるへの解い合わせ結果。奴らの脳味噌って、直接的な解釈なんだなあ。 リスが冬籠りの為に、ドングリを備蓄するのと一緒な発想。まあ、妥当なネーミングだ わな。こんな単語を獣医さん本で知れるってのが楽しいぞ。
獣医さんは、動物園とか水族館にも居る。ダチョウを診察する時は、頭にすっぽりと 袋をかぶせてしまうと、手もとえ足も出なくなるそうだ。奴らの足蹴りは凶器ですから。 鳥は恐竜の末裔。獣医さんの一部な方は、恐竜医でもあるわけか。