目次: C言語とlibc
Cライブラリのデバッグをしたいときはあまりないと思いますが、Cライブラリにデバッグ情報やprintを追加して実行する方法を紹介します。今回使用するのはDebian Testingが採用しているglibc-2.40です。
バージョンはよほど古いバージョンでなければ動くと思いますが、最初はシステムにインストールされているバージョンと同じにしたほうがトラブルが少ないと思います。システムにインストールされているCライブラリの確認方法はディストリビューションによって違いますが、DebianやUbuntuならば下記のように確認できます。
$ dpkg -l libc6:amd64 Desired=Unknown/Install/Remove/Purge/Hold | Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend |/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad) ||/ Name Version Architecture Description +++-===============-============-============-================================= ii libc6:amd64 2.40-7 amd64 GNU C Library: Shared libraries
バージョンは2.40でした。次にglibc-2.40のソースコードをもってきてビルドします。ソースコードは~/work/の下にあるとします。
$ git clone git://sourceware.org/git/glibc.git $ cd glibc $ git checkout glibc-2.40 $ mkdir build $ cd build $ ../configure --disable-sanity-checks --enable-debug $ make -j16
実行したときに自分でビルドしたライブラリかどうか簡単にわかるようにpthread_sigmask()にprintf()を追加してからビルドします。
$ git diff
diff --git a/nptl/pthread_sigmask.c b/nptl/pthread_sigmask.c
index a39f3ca335..4c725592b6 100644
--- a/nptl/pthread_sigmask.c
+++ b/nptl/pthread_sigmask.c
@@ -19,12 +19,14 @@
#include <pthreadP.h>
#include <sysdep.h>
#include <shlib-compat.h>
+#include <stdio.h>
int
__pthread_sigmask (int how, const sigset_t *newmask, sigset_t *oldmask)
{
sigset_t local_newmask;
+ printf("hoge\n");
/* The only thing we have to make sure here is that SIGCANCEL and
SIGSETXID is not blocked. */
if (newmask != NULL
ビルドしたglibcとテストプログラムをスタティックリンクします。-Lオプションには先程glibcをビルドしたディレクトリを指定し、-Dオプションには先程改変したpthread_sigmask()を呼ぶためのコンパイルスイッチを指定します。テストプログラムは以前紹介したものです(2025年5月12日の日記参照)。
$ g++ -g -O2 -Wall -static -L ~/work/glibc/build -DUSE_PTHREADSIGMASK 20250512_signal_thread.cpp
実行すると追加したhogeが出力されるはずです。
$ ./a.out Use pthread_sigmask th 0: sub (child ) thread start th 0: pthread_sigmask(block) hoge th 1: sub (child ) thread start th 1: pthread_sigmask(block) hoge th 2: sub (child ) thread start th 2: pthread_sigmask(block) hoge th 4: main (parent) thread start th 4: pthread_sigmask(block) th 3: sub (child ) thread start th 3: pthread_sigmask(block) hoge hoge th 4: loop start th 1: loop start th 3: loop start th 2: loop start th 0: loop start
最後にデバッグしてソースコードが表示されるか試しましょう。
$ gdb a.out (gdb) b pthread_sigmask (gdb) run Thread 2 "a.out" hit Breakpoint 1, __pthread_sigmask (how=0, newmask=0x7ffff7ff7120, oldmask=0x7ffff7ff71a0) at pthread_sigmask.c:29 29 printf("hoge\n"); (gdb) bt #0 __pthread_sigmask (how=0, newmask=0x7ffff7ff7120, oldmask=0x7ffff7ff71a0) at pthread_sigmask.c:29 #1 0x0000000000401c43 in thread_main (arg=0x7fffffffc900) at signal_thread.cpp:81 #2 0x00000000004109cf in start_thread (arg=<optimized out>) at pthread_create.c:447 #3 0x0000000000422968 in __clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:78
ソースコードが表示されること、先程追加したprintf()が見えることも確認できました。良さそうです。
目次: C言語とlibc
以前、シグナルマスク(sigprocmaskのマニュアル)の「規定されていない」使い方をするとどうなるか?を見ました。前回までに確認したことは、5つのマルチスレッド(親スレッド+4つの子スレッド)で全員でsigwait()するのは共通で、誰がsigprocmask()を呼ぶか?を変えながら、下記4パターンを試しました。
結果は1と2はabortし、4は「正しい方法」なので正しく動くとして、なぜか3も正しく動いていました。予想としてはsigprocmask()とpthread_sigmask()は実装が似ていて偶然こうなるのでしょう。
今回はGNU libc(glibc-2.41)のソースコードを確認して予想の答え合わせをします。繰り返しますが、マルチスレッドでsigprocmask()未定義動作なので3番の方法が正しく動く保証はないし、将来実装が変わって動かなくなる可能性があります。
まずpthread_sigmask()のコードを見ます。pthreadの制御に使うシグナルのマスクを消して、rt_sigprocmaskシステムコールを呼ぶだけです。
// glibc/nptl/pthread_sigmask.c
int
__pthread_sigmask (int how, const sigset_t *newmask, sigset_t *oldmask)
{
sigset_t local_newmask;
/* The only thing we have to make sure here is that SIGCANCEL and
SIGSETXID is not blocked. */
if (newmask != NULL
&& (__glibc_unlikely (__sigismember (newmask, SIGCANCEL))
|| __glibc_unlikely (__sigismember (newmask, SIGSETXID))))
{
local_newmask = *newmask;
clear_internal_signals (&local_newmask);
newmask = &local_newmask;
}
/* We know that realtime signals are available if NPTL is used. */
int result = INTERNAL_SYSCALL_CALL (rt_sigprocmask, how, newmask,
oldmask, __NSIG_BYTES);
return (INTERNAL_SYSCALL_ERROR_P (result)
? INTERNAL_SYSCALL_ERRNO (result)
: 0);
}
libc_hidden_def (__pthread_sigmask)
versioned_symbol (libc, __pthread_sigmask, pthread_sigmask, GLIBC_2_32);
次にsigprocmask()のコードを見ます。見ての通りpthread_sigmask()を呼び出しているだけです。
// glibc/sysdeps/unix/sysv/linux/sigprocmask.c
/* Get and/or change the set of blocked signals. */
int
__sigprocmask (int how, const sigset_t *set, sigset_t *oset)
{
int result = __pthread_sigmask (how, set, oset);
if (result == 0)
return 0;
__set_errno (result);
return -1;
}
libc_hidden_def (__sigprocmask)
weak_alias (__sigprocmask, sigprocmask)
似ているどころか完全に一致していました。道理で同じ動作になるわけですね。
目次: 自宅サーバー
このブログ、フォント設定が2つほどおかしかったので直しました。今まで見づらくてすみません。
1つ目は一部の環境(Linux向けChromeなど)からこのサイトを見るとサンセリフ体(ゴシック体)ではなくセリフ体(明朝体)で表示される現象を直しました。セリフ体になってしまう現象は以前から認識していて不思議だな?とは思ったものの、原因が良くわからず放置していました。
Developer toolsでCSSを確認していたら、font-family: sans-serif, serifと指定していた箇所がありました。原因も何も、自分で指定してただけだったのか……。こんな単純なことに10年以上気づいていなかった。
2つ目はmonospace系の文字が表示されるpreタグなどの文字が小さかったのを直しました。今までfont-size: smallerにしていたんですが、monospaceは元々少し小さいサイズで表示されるブラウザが多くて、めちゃくちゃ字が小さくなっていました。
しかもこちらの現象は認識していませんでした。自分のFirefox環境はmonospaceのフォントサイズを少し大き目に指定している&そのことを忘れており、デフォルト設定のブラウザで見ると字が小さすぎることに気付いてなかったです。すまねぇ。
目次: C言語とlibc
シグナルマスク(sigprocmaskのマニュアル)の「規定されていない」使い方をするとどうなるか?の続きです。5つのマルチスレッド(親スレッド+4つの子スレッド)で全員でsigwait()するのは共通で、誰がsigprocmask()を呼ぶか?を変えながら、下記4パターンを試します。
今回は結果3と4を紹介します。1〜3は定義されていない動作ですが、4は比較用に実施する「正しい方法」です。
全スレッドがsigprocmask()した場合です。ゆっくり5回シグナルを送ると、親スレッド(th 4)のsigwait()がシグナルを受け取り、子スレッド(th 0)のsigwait()はEINTRが返ります。
$ g++ -Wall -g -O2 -DUSE_SIGPROCMASK_ALL -DID_MAINTHREAD=1 signal_thread.cpp && ./a.out Use sigprocmask th 0: sub (child ) thread start th 0: sigprocmask(block) th 1: main (child ) thread start th 1: sigprocmask(block) th 2: sub (child ) thread start th 2: sigprocmask(block) th 4: sub (parent) thread start th 4: sigprocmask(block) th 3: sub (child ) thread start th 3: sigprocmask(block) th 3: loop start th 0: loop start th 2: loop start th 1: loop start th 4: loop start th 4: got SIGUSR1 th 0: sigwait failed (Interrupted system call) th 4: got SIGUSR1 th 0: sigwait failed (Interrupted system call) th 4: got SIGUSR1 th 0: sigwait failed (Interrupted system call) th 4: got SIGUSR1 th 0: sigwait failed (Interrupted system call) th 4: got SIGUSR1 th 0: sigwait failed (Interrupted system call)
良い感じです。また結果1(親スレッドがsigprocmask())と異なり、大量にシグナルを送りつけてもabortしないのも良いです。
$ g++ -Wall -g -O2 -DUSE_SIGPROCMASK_ALL -DID_MAINTHREAD=1 signal_thread.cpp && ./a.out Use sigprocmask th 0: sub (child ) thread start th 0: sigprocmask(block) th 1: main (child ) thread start th 1: sigprocmask(block) th 2: sub (child ) thread start th 2: sigprocmask(block) th 4: sub (parent) thread start th 4: sigprocmask(block) th 3: sub (child ) thread start th 3: sigprocmask(block) th 3: loop start th 0: loop start th 2: loop start th 1: loop start th 4: loop start (...略...) th 2: sigwait failed (Interrupted system call) th 0: sigwait failed (Interrupted system call) th 0: got SIGUSR1 th 1: got SIGUSR1 th 2: got SIGUSR1 th 2: got SIGUSR1 th 1: sigwait failed (Interrupted system call) th 0: got SIGUSR1 th 4: sigwait failed (Interrupted system call) th 3: sigwait failed (Interrupted system call) th 2: sigwait failed (Interrupted system call) th 1: got SIGUSR1 (別ターミナルから) $ while :; do kill -USR1 123450; if [ $? -ne 0 ]; then break; fi; done
良さそうですね。
正しい方法(全スレッドがpthread_sigmask())はどんな動きでしょうか?結果だけ先に書いてしまうと、全スレッドがsigprocmask()したときと同じ動きをするようです。
$ g++ -Wall -g -O2 -DUSE_PTHREADSIGMASK -DID_MAINTHREAD=1 signal_thread.cpp && ./a.out Use pthread_sigmask th 0: sub (child ) thread start th 0: pthread_sigmask(block) th 1: main (child ) thread start th 1: pthread_sigmask(block) th 2: sub (child ) thread start th 2: pthread_sigmask(block) th 4: sub (parent) thread start th 4: pthread_sigmask(block) th 3: sub (child ) thread start th 3: pthread_sigmask(block) th 3: loop start th 2: loop start th 0: loop start th 1: loop start th 4: loop start th 4: got SIGUSR1 th 0: sigwait failed (Interrupted system call) th 4: got SIGUSR1 th 0: sigwait failed (Interrupted system call) th 4: got SIGUSR1 th 0: sigwait failed (Interrupted system call) th 4: got SIGUSR1 th 0: sigwait failed (Interrupted system call) th 4: got SIGUSR1 th 0: sigwait failed (Interrupted system call)
結果3(全員sigprocmask())と同じ動きをしています。当然ながら、大量にシグナルを送りつけてもabortしません。
$ g++ -Wall -g -O2 -DUSE_PTHREADSIGMASK -DID_MAINTHREAD=1 signal_thread.cpp && ./a.out Use pthread_sigmask th 0: sub (child ) thread start th 0: pthread_sigmask(block) th 1: main (child ) thread start th 1: pthread_sigmask(block) th 2: sub (child ) thread start th 2: pthread_sigmask(block) th 4: sub (parent) thread start th 4: pthread_sigmask(block) th 3: sub (child ) thread start th 3: pthread_sigmask(block) th 3: loop start th 2: loop start th 0: loop start th 1: loop start th 4: loop start (...略...) th 2: got SIGUSR1 th 0: sigwait failed (Interrupted system call) th 1: sigwait failed (Interrupted system call) th 4: sigwait failed (Interrupted system call) th 0: got SIGUSR1 th 4: got SIGUSR1 th 0: got SIGUSR1 th 3: sigwait failed (Interrupted system call) th 3: got SIGUSR1 th 2: sigwait failed (Interrupted system call) th 2: got SIGUSR1 th 0: sigwait failed (Interrupted system call) th 0: got SIGUSR1 th 1: sigwait failed (Interrupted system call) th 3: sigwait failed (Interrupted system call) th 3: got SIGUSR1 th 0: got SIGUSR1 th 4: sigwait failed (Interrupted system call) th 2: sigwait failed (Interrupted system call) th 1: sigwait failed (Interrupted system call) (別ターミナルから) $ while :; do kill -USR1 123450; if [ $? -ne 0 ]; then break; fi; done
4つの結果から推測するにpthread_sigmask()とsigprocmask()は同じシステムコールを使っているかもしれません。libcのソースコードを見ればわかるはずなので、また今度に見ようと思います。
こちらからどうぞ。
目次: C言語とlibc
シグナルマスクのマニュアル(sigprocmaskのマニュアル)を見ると下記のように「規定されていない」とあります。実際Linuxだとどうなるか気になります。
マルチスレッドのプロセスでsigprocmask()を使用した場合の動作は規定されていない。
実行環境は下記のとおりです。他のプラットフォームや過去/将来のバージョンのLinuxで今回の実験結果と同じ動作をするとは限りませんのでご注意ください。
実験内容は5つのマルチスレッド(親スレッド+4つの子スレッド)で全員でsigwait()するのは共通、誰がsigprocmask()を呼ぶか?を変えながら、下記4パターンを試します。
最後のパターンはマルチスレッドでsigwait()する場合の正しい方法(全スレッドがpthread_sigmask())で、他の3つと動作を比較するためのものです。
コードは長くなってしまったので最後にファイルへのリンクを張っておきます。
実験方法はコンパイル時にマクロを適宜切り替えて、生成された./a.outを起動し、別のターミナルからkillコマンドなどでSIGUSR1を送るだけです。
最初のスレッドがsigprocmask()した場合です。ゆっくり5回シグナルを送ると、親スレッド(th 4)のsigwait()がシグナルを受け取り、子スレッド(th 0)のsigwait()はEINTRが返ってきます。
$ g++ -Wall -g -O2 -DUSE_SIGPROCMASK -DID_MAINTHREAD=4 signal_thread.cpp && ./a.out Use sigprocmask th 0: sub (child ) thread start th 1: sub (child ) thread start th 2: sub (child ) thread start th 4: main (parent) thread start th 4: sigprocmask(block) th 3: sub (child ) thread start th 3: loop start th 2: loop start th 4: loop start th 0: loop start th 1: loop start th 4: got SIGUSR1 th 0: sigwait failed (Interrupted system call) th 4: got SIGUSR1 th 0: sigwait failed (Interrupted system call) th 4: got SIGUSR1 th 0: sigwait failed (Interrupted system call) th 4: got SIGUSR1 th 0: sigwait failed (Interrupted system call) th 4: got SIGUSR1 th 0: sigwait failed (Interrupted system call)
一見すると良い感じに動くように見えますが、大量にシグナルを送りつけるとabortします。ありゃりゃ。
$ g++ -Wall -g -O2 -DUSE_SIGPROCMASK -DID_MAINTHREAD=4 signal_thread.cpp && ./a.out Use sigprocmask th 0: sub (child ) thread start th 1: sub (child ) thread start th 2: sub (child ) thread start th 4: main (parent) thread start th 4: sigprocmask(block) th 3: sub (child ) thread start th 3: loop start th 1: loop start th 0: loop start th 2: loop start th 4: loop start ユーザー定義シグナル1 (別ターミナルから) $ while :; do kill -USR1 123450; if [ $? -ne 0 ]; then break; fi; done
一見動くように見えて、だめなパターンですね。
最初のスレッド「以外」の1スレッドがsigprocmask()した場合です。1回シグナルを送っただけでabortしました。
$ g++ -Wall -g -O2 -DUSE_SIGPROCMASK -DID_MAINTHREAD=0 signal_thread.cpp && ./a.out Use sigprocmask th 0: main (child ) thread start th 0: sigprocmask(block) th 1: sub (child ) thread start th 2: sub (child ) thread start th 4: sub (parent) thread start th 3: sub (child ) thread start th 3: loop start th 0: loop start th 2: loop start th 1: loop start th 4: loop start ユーザー定義シグナル1
結果1の動きを見る限り納得の結果と言えるでしょう。シグナルは常に親スレッドにも飛んでいたので、シグナルをマスクしてない親スレッドはabortするのはそりゃそうだなと思います。
続きはまた今度やります。
こちらからどうぞ。
目次: Arduino
M5Stamp C3 + Raspberry Piを組み合わせて作った的あてゲーム、秋葉原のTarget-1(お店のサイト)で壊れることなく1年ほど安定稼働しているようです。良かった良かった。しかし最近、故障ではないもののタイトル画面に戻ってしまう頻度が増えているそうです。
以前に日記で紹介したとおり、的あてゲームのシステムはM5Stamp C3をBluetooth LEデバイスにして、Linux PCもしくはRaspberry PiなどのLinux SBCと通信しています。Bluetoothの接続が切れるとタイトル画面に戻って再接続する実装にしていますので、不意にタイトル画面に戻る = Bluetoothの接続が切断されていることを意味します。
お店での運用を見ていると、ゲームリザルト画面で放置されることが多いです。今の実装ではリザルト画面でBluetoothの通信を一切行いません。あまりにも長い間通信しないとBluetooth接続が切れてしまう?のかもしれません。
とりあえず今回は小手先の対処として、リザルト画面でM5Stamp C3本体のLEDを点滅させる指令を送り続け、Bluetooth接続を維持するようにしました。家でテストしてみたところ1日以上放置しても接続を維持できています。
代償として消費電力が0.1Wくらい増えますが、何度もタイトル画面に戻されるよりはマシでしょう……。
先代のTSS(ターゲットシューティングシステム、作者さんの紹介サイト)は5年位稼働していたらしい(すごい!)ので、追いつくにはあと4年ですか、長いな〜……。
4年後を考えてみると、Linux側のマシンROCK 3Cはほぼ確実にEOL(End Of Life、生産終了、販売終了)だと思います。Linux側のシステムはHW依存は少ないし、SWも枯れたやつが多いので、そのとき販売されているお買い得なARM SBCボード(Raspberry Pi 6とか7とか?ROCK 3C後継のボードとか)への乗り換えは容易だと思います。
困るのはM5Stamp C3ですね。EOLになると別ボードへのSW移植とドッキングするためのボード再設計が必要でしょう。M5Stampは安くて良いんですけど、世代ごとに形がガンガン変わって互換性ゼロなのが良くない点ですね。作り直すとしたら、今のM5Stamp C3の2枚使い設計はダサいので、I/Oピン数の多いボード1枚に改めると思います。
< | 2025 | > | ||||
<< | < | 05 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
合計:
本日: