golang
Table of Contents
swap取引の闇
前回ChatGPTに指摘された、ネットワーク・バイトオーダーの使用方法を勉強。
#include <stdio.h> #include <sys/time.h> #include <arpa/inet.h> int main(){ struct timeval tv; gettimeofday(&tv, NULL); printf("%lu\n", sizeof(tv.tv_sec)); tv.tv_sec = htons(tv.tv_sec); }
これが難なくコンパイル出来てしまい、結果は
ob$ ./a.out 8
8バイト表現の数値にもかかわらず、ネットワー ク・バイトオーダーの変換は2バイト分しか指示しないBug混入のコード。それ にも関わらず、コンパイラーが文句を言ってこない事。
SYNOPSIS #include <arpa/inet.h> uint32_t htonl(uint32_t host32); uint16_t htons(uint16_t host16); uint32_t ntohl(uint32_t net32); uint16_t ntohs(uint16_t net16);
大体、 uint64_t
に対する変換が定義されていないって、古めかしいな。
それにしても、これってコンパイラーの秘孔を突いたコードなのかな。このレ
ベルまで降りてくると、もうアセンブラの世界なんで、自己責任でお願いしま
すと、責任転嫁してるのだろう。
#ifndef htons #define htons(x) __htobe16(x) #define htonl(x) __htobe32(x) #define ntohs(x) __htobe16(x) #define ntohl(x) __htobe32(x) #endif
/usr/include/sys/_endian.h
にこんな定義が有り、あとはゴチャゴチャし
たコードが置いてある。面倒なので、gdbする。
9 tv.tv_sec = htons(tv.tv_sec); (gdb) s __swap16md (_x=8126) at /usr/include/machine/endian.h:37 37 __asm ("rorw $8, %w0" : "+r" (_x));
次に見るべき所が示された。
static __inline __uint16_t __swap16md(__uint16_t _x) { __asm ("rorw $8, %w0" : "+r" (_x)); return (_x); } static __inline __uint32_t __swap32md(__uint32_t _x) { __asm ("bswap %0" : "+r" (_x)); return (_x); } static __inline __uint64_t __swap64md(__uint64_t _x) { return ((__uint64_t)__swap32md(_x >> 32) | (__uint64_t)__swap32md(_x & 0xffffffff) << 32); }
64Bitの数値の場合、自前で呼べとな。
easy check
もう少し雑音を減らして、再現性のあるコードで検証する。
sakae@fb:~ $ cat z.c #include <arpa/inet.h> int main(){ int h=0x12345678; int n=0; n = htons(h); }
面倒なprintfなんてのは省略しちゃって、確認はgdbでって魂胆です。 これで、htonsがどうなるか、gdbでステップすると、潜っていかなかった。は て、マクロ展開してるのか? いや、そういう言い方はC語では不適切。プリプ ロセスって言え。
sakae@fb:~ $ cc -E z.c : int main(){ int h=0x12345678; int n=0; n = (__builtin_bswap16(h)); }
展開されて、後はコンパイラー内蔵の機構に移譲しちゃうんだな。ならば、究 極の手段である、逆アセンブルだな。
6 n = htons(h); (gdb) disassemble Dump of assembler code for function main: 0x00401690 <+0>: push %ebp 0x00401691 <+1>: mov %esp,%ebp 0x00401693 <+3>: sub $0x8,%esp 0x00401696 <+6>: movl $0x12345678,-0x4(%ebp) 0x0040169d <+13>: movl $0x0,-0x8(%ebp) => 0x004016a4 <+20>: mov -0x4(%ebp),%eax 0x004016a7 <+23>: rol $0x8,%ax 0x004016ab <+27>: movzwl %ax,%eax 0x004016ae <+30>: mov %eax,-0x8(%ebp) 0x004016b1 <+33>: xor %eax,%eax 0x004016b3 <+35>: add $0x8,%esp 0x004016b6 <+38>: pop %ebp 0x004016b7 <+39>: ret
そならOpenBSDでは、どうか?
ob$ cc -E z.c : int main(){ int h=0x12345678; int n=0; n = (__uint16_t)(__builtin_constant_p(h) ? (__uint16_t)(((__uint16_t)(h) & 0xffU) << 8 | ((__uint16_t)(h) & 0xff00U) >> 8) : __swap16md(h)); }
(判り易いように、3項演算子を分解表示してます )展開されるも、ちと迷いが有りますなあ。いや、融通性を確保してると理解せ い。
(gdb) s __swap16md (_x=22136) at /usr/include/machine/endian.h:37 37 __asm ("rorw $8, %w0" : "+r" (_x)); (gdb) disassemble Dump of assembler code for function __swap16md: 0x16dd1750 <+0>: push %ebp 0x16dd1751 <+1>: mov %esp,%ebp 0x16dd1753 <+3>: mov 0x8(%ebp),%ax => 0x16dd1757 <+7>: mov 0x8(%ebp),%ax 0x16dd175b <+11>: ror $0x8,%ax 0x16dd175f <+15>: mov %ax,0x8(%ebp) 0x16dd1763 <+19>: movzwl 0x8(%ebp),%eax 0x16dd1767 <+23>: pop %ebp 0x16dd1768 <+24>: ret
その結果が、サブルーチン・コールと言う形で表われました。
goクロス
たまには、OSベッタリな世界から離れて、応用系に触れてみたい。どうせやる なら、一度苦労したら(書いたら)、どこでも通用する、お得感に浸りたい。人 生短かくなりつつありますからね。年寄が、こう言うのは判るけど、若者も同 様に、タイパ、コスパが良いって言い出すのは、どうよ。苦労は、買ってでも しておけって気構え放棄の心根じゃ、日本の未来は無いな。
だから、NHKは、プロジェクト X を復活させて、鼓舞するんだな。納得です。 さらりと、リクエストに答えまして、アンコールって手で、手抜きも堂々と出 来るからね。これぞ、コスパですよ、なんちゃってね。
一度書いたらって昔はJavaのキャッチコピーだったけど、今なら、golangです よ。
ob$ go tool dist list | grep openbsd openbsd/386 openbsd/amd64 openbsd/arm openbsd/arm64 openbsd/mips64 openbsd/riscv64
辺境なOSに着目してみた。
ob$ go tool dist list | grep amd64 android/amd64 darwin/amd64 dragonfly/amd64 freebsd/amd64 illumos/amd64 ios/amd64 linux/amd64 netbsd/amd64 openbsd/amd64 plan9/amd64 solaris/amd64 windows/amd64
こちらは、石に着目だ。実際に使う場合は、OSと石を環境変数で指定するだけ。 これで、ピンポイントな環境で動作する、バイナリーを生成できる。
GOOS=windows GOARCH=amd64 go build -o hello.exe hello.go
Windows用を生成する場合、MS-DOS時代の名残である、尻尾(.exe)が必要にな るんで、注意せよ。エクスプローラーの表示では、恥かしいので、尻尾は隠さ れているけどね。
とほほのGo言語入門 module
go udp
ob$ cd go/src/ ob$ mkdir udp ob$ cd udp ob$ go mod init go: creating new go.mod: module udp ob$ emacs main.go // cp client ob$ go build
丁度良い例があったので、OpenBSD側をクライアントにして、FreeBSDにはサー バーになってもらいましょ。サーバーを起動しといて
sakae@fb:~ $ nc -u -l 12828 Hello UDP Server
クライアント側から、パケットを投げてみる。
ob$ ./udp fb:12828 send... ^C
妄想実現計画
とまあ、いきなりgolangが登場した訳であるが、ここに至る布石は前回打って おいた。
WindowsのスリープでFreeBSDも休眠。Windowsが起床すると、現実な時刻に同 期してくれるんだけど、FreeBSDの時刻は置いてきぼりを喰らってしまう。 そこで、FreeBSDの時刻も、現実な時刻に自動で合わせて欲しいなと。
Windowsから時刻を定期的にFreeBSDにパケットとして通知。FreeBSD側では、 時刻の差分を取って、余りにズレていたら、日本標準時刻製造所から時刻を取 り寄せして補正しちゃえ。これが、妄想のあらまし。
で、Windowsで走るアプリが必要。そんな訳で、移植自在なgolangに目をつけ た訳。
既にパケットを投げる部分は入手したんで、今度は時刻の扱い。"基礎からわ かるGo言語"なんて本のサンプルを、ちと拝借。
package main import ( "fmt" "time" ) func main() { ch := time.Tick(time.Second * 5) for i := 0; i < 5; i++ { t := <-ch fmt.Println(t.Unix()) } }
これ、5秒間隔で、unix時刻を表示するプログラム。
GOOS=windows GOARCH=amd64 go build -o win.exe tick.go sakae@atom:~/go/src/tick$ file win.exe win.exe: PE32+ executable (console) x86-64, for MS Windows, 15 sections
Windows用にコンパイルしてみた。
joint
材料は揃ったので、結合して、tick.goを作成。
package main import ( "fmt" "net" "time" ) func tell_fb(t int64) { conn, err := net.Dial("udp4", "fb:8888") if err != nil { panic(err) } defer conn.Close() str := fmt.Sprintf("%d\n", t) _, err = conn.Write([]byte(str)) if err != nil { panic(err) } } func main() { var old int64 = 0 ch := time.Tick(time.Second * 60) for { t := <-ch if t.Unix() - old > 100 { fmt.Println(t) tell_fb(t.Unix()) } old = t.Unix() } }
コンパイル用のMakefileも、ついでに作成。-gcflagsはgdb-infoを詳しくして の指示。-ldflags は、gdb-infoをstripしての指示になる。
ob$ cat Makefile ob: tick.go go build -gcflags=all="-N -l" win: tick.go GOOS=windows GOARCH=amd64 go build -ldflags=-w -o wtick.exe tick.go
tick.goのmainでやってる事は、時刻がジャンプしてる事を検出して、
tell_fb
をコールする事だ。time.Tickで60秒ごとに時刻が得られる。前の
時刻との差が100秒を越えてれば、時刻がジャンプ(その間にWindowsが休眠してた)と見
做している。forには、制約が付いていないので、永久に繰りかえす事になる。
tell_fb
は、引数のunixエポック秒を、文字列に変換して、UDPのパケット
として、送出。いつの間にか、エポック秒がint64扱いになってますねぇ。
そして、Windowsで起動。
C:\Users\sakae>wtick.exe 2024-04-08 08:28:08.5944815 +0900 JST m=+60.006203901 2024-04-08 12:56:37.8338945 +0900 JST m=+16169.245617101
アプリが起動してから60秒後の08:28:08にパケットを送出。その後の適当な時 刻に、Windowsを休眠。そして、午後の12:56頃から、またWindowsを使いはじ めたって事になる。
sakae@fb:~ $ nc -u -l 8888 1712532488
こちらは、 FreeBSD側の受信部。本当は、これをinetdの中に収納したいんだけ ど、その前の実験。Windows起床時のパケットが届いていないのは、ncの特性 みたい。普通に実験しても、2つ目のパケットは、なぜか受信してくれない。
後は、inetdを改造するだけだな。その前に、忘れない内に疑問を解消してお く。土地感が無いので、バイナリー・パケットの扱いをChatしてみた。
bin packet
int64型のバイナリーデータをネットワーク転送する場合、まず `encoding/binary` パッケージを使用してint64をバイトスライスにエンコードする必要があります。その後、バイトスライスをネットワーク経由で送信できます。
修正されたコードは次のようになります:
package main import ( "encoding/binary" _ "fmt" // _ is not using mark "net" ) func tell_fb(t int64) { conn, err := net.Dial("udp4", "fb:8888") if err != nil { panic(err) } defer conn.Close() // int64をバイトスライスにエンコードする buf := make([]byte, 8) // int64は8バイト binary.BigEndian.PutUint64(buf, uint64(t)) _, err = conn.Write(buf) if err != nil { panic(err) } } func main() { t := int64(1234567890) tell_fb(t) }
この修正では、`binary.BigEndian.PutUint64()` を使用してint64をバイトスライスにエンコードし、ネットワークを介して送信しています。データのエンディアンはビッグエンディアン(BigEndian)として指定されていますが、これはネットワークバイトオーダーとして一般的に使用されます。
受信側では、同じ方法でバイトスライスをint64にデコードする必要があります。
Windows常駐
実験してたwtick.exeを起動時から常駐させなければならない。いわゆるダエ モン君に昇格してもらいたい訳だ。そうでないと、鬱陶しい事になる。どうや る? 軽く調べてみると、オイラー的には、難易度が高い事がわかった。
超お手軽にPythonプログラムを自動で実行(起動)させる方法
初心者向けにPythonのプログラムをデーモン(常駐ソフト)化する方法
今回は、妄想、春の霞って事にしときましょ。
golang with gdb
んな訳で、少し別な遊び。
出来心で、OpenBSD用のアプリを確認。
ob$ file tick tick: ELF 32-bit LSB executable, Intel 80386, version 1
さらりとした報告なんで、これをFreeBSDに持っていったら、そのまま動くん ではないかと思った。
sakae@fb:~ $ file tick tick: ELF 32-bit LSB executable, Intel 80386, version 1 (OpenBSD), dynamically linked, interpreter /usr/libexec/ld.so, for OpenBSD, Go BuildID=uzzQnbQRlwS083UWw9Yq/Q9G6El0QaCusGF9wAswd/snM0_msf9KoUuawreg00/zJ6_SiLJ5YECeEatOqJI, with debug_info, not stripped
FreeBSDで確認すると、この通り。注目は、debug情報付きって所だ。 ちなみに、
ob$ ldd tick tick: Start End Type Open Ref GrpRef Name 08048000 0820f000 exe 2 0 0 tick 37c93000 57ca4000 rlib 0 1 0 /usr/lib/libc.so.97.1 1ea27000 3ea29000 rlib 0 1 0 /usr/lib/libpthread.so.27.1 1bc66000 1bc66000 ld.so 0 1 0 /usr/libexec/ld.so
これじゃ、OSのバージョン依存性が導入されてしまっていると思うんだけど、 どうよ? まあ、そんなのは、取りあえず無視しておく。
~/.gdbinit
add-auto-load-safe-path /usr/local/go/src/runtime/runtime-gdb.py
こんなのを追加。そして、gdbを起動。
ob$ gdb -q tick Reading symbols from tick... Loading Go Runtime support. (gdb) b main.tell_fb Breakpoint 1 at 0x8111d80: file /home/sakae/go/src/tick/tick.go, line 9. (gdb) r Starting program: /home/sakae/go/src/tick/tick [New thread 294264] [New thread 386879] [New thread 467444] [New thread 282170] Thread 1 hit Breakpoint 1, main.tell_fb (t=1712615995) at /home/sakae/go/src/tick/tick.go:9 9 func tell_fb(t int64){ (gdb) bt #0 main.tell_fb (t=1712615995) at /home/sakae/go/src/tick/tick.go:9 #1 0x081120dd in main.main () at /home/sakae/go/src/tick/tick.go:28
オプチィマイズされているも、gdbの遡上にかかりました。
(gdb) info threads Id Target Id Frame * 1 thread 574467 main.tell_fb (t=1712616160) at /home/sakae/go/src/tick/tick.go:9 2 thread 294264 _thread_sys___thrwakeup () at /tmp/-:3 3 thread 386879 _thread_sys___thrsleep () at /tmp/-:3 4 thread 467444 kevent () at /tmp/-:3 5 thread 282170 _thread_sys___thrsleep () at /tmp/-:3
スレッドも確認できます。
(gdb) thread 4 [Switching to thread 4 (thread 467444)] #0 kevent () at /tmp/-:3 3 /tmp/-: No such file or directory. (gdb) bt #0 kevent () at /tmp/-:3 #1 0x080b1907 in runtime.kevent_trampoline () at /usr/local/go/src/runtime/sys_openbsd_386.s:501 #2 0x00000000 in ?? ()
内部探検の入口が見付かりましたねぇ。
FreeBSDのEpoch
以前に調べた所によると、epochは32Bitだった。tickの作成するやつは、 64Bitになってる。ならば、FreeBSD用の奴を作成して、実行したらどうなる?
fb: tick.go GOOS=freebsd GOARCH=386 go build -o ftick tick.go
これで作成したものを、FreeBSD上で実行。
sakae@fb:~ $ file ftick ftick: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), statically linked, for FreeBSD 12.3, FreeBSD-style, Go BuildID=1ij241JmUeBMcV2arTMg/aoFQi4Xfgc4so0zGH332/TS8D1VA4MYxuZTNX-PyW/DNPrsa_SVZYO6K9i-F7H, with debug_info, not stripped
スタチック・リンクで、ちと古いバージョン用に作成されてる。やはりdebug 機能つき。
sakae@fb:~ $ gdb -q ftick Reading symbols from ftick... warning: Missing auto-load script at offset 0 in section .debug_gdb_scripts of file /usr/home/sakae/ftick. Use `info auto-load python-scripts [REGEXP]' to list them. (gdb) b main.tell_fb Breakpoint 1 at 0x810d4e0: file /home/sakae/go/src/tick/tick.go, line 9. (gdb) r Starting program: /usr/home/sakae/ftick [New LWP 100144 of process 2299] [New LWP 100145 of process 2299] Thread 1 hit Breakpoint 1, main.tell_fb (t=1712618182) at /home/sakae/go/src/tick/tick.go:9 9 func tell_fb(t int64){ (gdb) c Continuing. 2024-04-09 08:16:22.656484653 +0900 JST m=+6.036039869
ちゃんと動いておりますねぇ。
/usr/local/go/src/time/time.go
// Unix returns t as a Unix time, the number of seconds elapsed // since January 1, 1970 UTC. The result does not depend on the // location associated with t. // Unix-like operating systems often record time as a 32-bit // count of seconds, but since the method here returns a 64-bit // value it is valid for billions of years into the past or future. func (t Time) Unix() int64 { return t.unixSec() }
未来を見据えて、64Bitにしましたとな。このメソッドが有るおかげで、時刻 の扱いが、単純な数値演算になるのが、ありがたい。特にWindowsみたいに、 時刻の基準が違っても、それを吸収してくれからね。
それはそうと、OpenBSD 7.5が早々とリリースされたぞ。どうする? 執行猶予 は半年ついてるから、大人の余裕でいいんでないかい。どこかの、24.04LTSみ たいに、2年の辛抱なんて無いからね。
ChatGPT
今を盛んと咲き誇る桜を見ていたら、ふと疑問が湧いてきた。例によって聞い てみた。
Q: 植物学についての質問です。草のように1年周期で成長、枯れる物があるかと思うと、木のように何十年、何百年と枯れる事なく成長する物もあります。この違いは何ですか? 3,000文字で説明してください。
A: 植物界には、様々な寿命サイクルを持つ植物が存在します。一部の植物は短い一年周期で成長し、種子から発芽して成長し、種子を結実させ、枯れるサイクルを繰り返します。これに対して、他の植物は何十年、何百年と長い期間生き続け、成長を続けます。この違いは、植物の生活史戦略、適応戦略、生活環、そしてその生理学的特徴に関連しています。
まず、一年草と多年草の違いは、生活史戦略に関連しています。一年草は、短い生活サイクルで繁殖し、次の世代を残すことに焦点を当てています。これは、競争の激しい環境で生存競争に勝つための戦略です。彼らは迅速に成長し、種子を放出し、次の世代を生み出します。そのため、彼らの寿命は短く、一般的に1年未満です。
一方、多年草や木本植物は、より長い期間をかけて成長し、生殖を繰り返すことができます。彼らの生活史戦略は、安定した環境での長期的な生存と繁殖を目指しています。彼らはより長い期間をかけて成長し、資源を貯蔵し、より大きなサイズやより複雑な構造を発達させます。これにより、より安定した環境での生存が可能になります。
次に、これらの違いは植物の生活環にも関連しています。一年草は、種子から発芽して成長し、花を咲かせ、種子を結実させ、そして死に至るまでの短いサイクルを持ちます。対照的に、多年草や木本植物は、より長い生活環を持ち、成熟段階、繁殖段階、そして老齢段階があります。彼らは成長に時間をかけ、成熟するまでに数年から数十年かかることがあります。
また、植物の適応戦略も違いを生み出します。一年草は、短期間で多くの子孫を残すことに焦点を当てており、そのために迅速に成長し繁殖します。これは、変化の激しい環境での生存を助ける戦略です。一方、多年草や木本植物は、長期的な生存と繁殖に焦点を当てており、そのためにリソースを貯蔵し、構造を発達させ、長期的な成長を達成します。
最後に、これらの違いは植物の生理学的特徴にも関連しています。一年草は、迅速な成長と短い生活サイクルを可能にするように進化しており、その生理学的特徴はそれに適応しています。一方、多年草や木本植物は、より長い期間生存する必要があるため、リソースの効率的な利用やストレージ、耐久性のある構造の発達など、異なる生理学的特徴を持っています。
以上のように、植物の寿命サイクルの違いは、生活史戦略、適応戦略、生活環、そして生理学的特徴などの複数の要因によって引き起こされます。これらの違いは、植物がそれぞれの環境に最適化されるように進化した結果であり、地球上の生態系において多様性と安定性をもたらしています。