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 by Example

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

go-tcp-udp

丁度良い例があったので、OpenBSD側をクライアントにして、FreeBSDにはサー バーになってもらいましょ。サーバーを起動しといて

sakae@fb:~ $ nc -u -l 12828
Hello UDP Server

クライアント側から、パケットを投げてみる。

ob$ ./udp fb:12828
send...
^C

UDPソケットをGoで叩く

妄想実現計画

とまあ、いきなり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を起動時から常駐させなければならない。いわゆるダエ モン君に昇格してもらいたい訳だ。そうでないと、鬱陶しい事になる。どうや る? 軽く調べてみると、オイラー的には、難易度が高い事がわかった。

C言語で常駐ソフトを作成

超お手軽に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のバージョン依存性が導入されてしまっていると思うんだけど、 どうよ? まあ、そんなのは、取りあえず無視しておく。

Debugging Go Code with GDB

~/.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年未満です。

一方、多年草や木本植物は、より長い期間をかけて成長し、生殖を繰り返すことができます。彼らの生活史戦略は、安定した環境での長期的な生存と繁殖を目指しています。彼らはより長い期間をかけて成長し、資源を貯蔵し、より大きなサイズやより複雑な構造を発達させます。これにより、より安定した環境での生存が可能になります。

次に、これらの違いは植物の生活環にも関連しています。一年草は、種子から発芽して成長し、花を咲かせ、種子を結実させ、そして死に至るまでの短いサイクルを持ちます。対照的に、多年草や木本植物は、より長い生活環を持ち、成熟段階、繁殖段階、そして老齢段階があります。彼らは成長に時間をかけ、成熟するまでに数年から数十年かかることがあります。

また、植物の適応戦略も違いを生み出します。一年草は、短期間で多くの子孫を残すことに焦点を当てており、そのために迅速に成長し繁殖します。これは、変化の激しい環境での生存を助ける戦略です。一方、多年草や木本植物は、長期的な生存と繁殖に焦点を当てており、そのためにリソースを貯蔵し、構造を発達させ、長期的な成長を達成します。

最後に、これらの違いは植物の生理学的特徴にも関連しています。一年草は、迅速な成長と短い生活サイクルを可能にするように進化しており、その生理学的特徴はそれに適応しています。一方、多年草や木本植物は、より長い期間生存する必要があるため、リソースの効率的な利用やストレージ、耐久性のある構造の発達など、異なる生理学的特徴を持っています。

以上のように、植物の寿命サイクルの違いは、生活史戦略、適応戦略、生活環、そして生理学的特徴などの複数の要因によって引き起こされます。これらの違いは、植物がそれぞれの環境に最適化されるように進化した結果であり、地球上の生態系において多様性と安定性をもたらしています。


This year's Index

Home