目次: 射的 - まとめリンク
秋葉原のトリガーハッピーでシューティングレンジに 3回目のチャレンジ。7m くらい先の的 9個を撃つゲームです。
前回はアベレージ 15秒、ベスト 12秒くらい、今回はアベレージ 13〜14秒、ベスト 10秒くらいでした。一度だけマグレで 8秒台が出ましたが、その後は二度と 10秒を切ることはありませんでした。
前回はダブルアクションで 1発目がブレがちなベレッタ 92 だったので、今回はグロック 19 で挑みました。グロック系はセーフティー操作が不要(トリガーセーフティーのみ)で扱いが楽ですね。
構え方の問題なのか、狙いを上下に移動させたときに外しやすいようです。サイトは同じように狙っているつもりなんですが、ズレてる?難しいなー。
スチールチャレンジやアンリミテッドシューティングチャレンジであれば、下記のようなルールのためセーフティー操作とタイムはダイレクトに影響するはずです。
シューティングバーでやるような射的はノールールです。ホルスターなんて持って行きませんし、セーフティー解除してから撃ち始めますからセーフティー操作とタイムもほとんど関係ありません。超適当です。当たる、楽しい!で良いのだ!
エアガンを使った趣味として代表的なモノはサバゲー(サバイバルゲーム)です。サバゲーはやったことありませんが、みなさんでワイワイやるのは楽しそうです。難点は何だろうな?フィールドで移動する最低限の体力が必要ですね。あとは相手に撃たれたら痛いくらい……?
シューティングはほぼ動きませんし撃たれません。シューティング大会に出るなどのガチ系の人は別として、他人の動向を気にする必要はなくて気楽です。難点は何だろうな。
ランニングコストに関してはバカスカ撃たなければ常識の範囲内だと思いますし、もし多人数で交流したいなら練習会に行くのも良いでしょう。
私にとっては一生モノの気楽に遊べる趣味にできそうです。
奥さんと銀座の SUPER HOTEL に来ました。現在 SUPER HOTEL と進撃の巨人がコラボしていて、泊まるとノベルティが 1人 1つ貰えるそうです。ノベルティは全部で 2種類あり、両方欲しいので 2人で泊まりたかったんですって。へー。
部屋にはタブレットとテレビがありました。タブレットは ONKYO TA2C-74Z8A という古い業務用のタブレットでした。Atom 機で x86 Android です。バージョンは Android 5.1 とかなり古いですね。
戻るボタン、ホームボタンは画面に出せますが、ホームアプリが差し替えられているため基本的には他のアプリは起動しません。
さらに Perfect AppLock なるロックアプリが入っています。通知バーから設定アプリを起動しようとするなど、ホテル業務と関係ないアプリを起動しようとすると妨害されるようです。デフォルトのホームアプリ(Launcher3)はなぜか起動妨害対象に入っておらず、ロックアプリのアイコンが拝めます。
これ以上は何も弄れませんでしたと言いたいところですが、このロックアプリはバグってるみたいで、画面遷移の瞬間に連打するとロックの設定を削除できてしまいました。名前に反して全然パーフェクトじゃないんだが……??設定アプリも拝めましたし、開発者モードも ON にできました。ここまで到達できるなら adb を繋ぎたかったですね。パソコンを持ってこなかったのが惜しまれます。
タブレットはこれくらいにして、テレビを見ると普通のブラビア(SONY KJ-49X8000E)でした。
サイドの木箱に STB が隠してありました、業務用の STB + 普通のテレビというホテルではありがちな構成ですね。残念なことにリモコンは STB 用のリモコンしかなく、ブラビアのリモコンは見あたりませんでした。
おかげでブラビアのメニュー画面が出せなくて特に何も弄れませんでした。つまんないですね……。
メモ: 技術系の話は Facebook から転記しておくことにした。加筆、写真追加。
遊ばずに放置していた Timberborn をしばらくやっていました。まだ発展途上の Early Access 版でもありますし、私の遊び方が良くないのもありそうですが、ゲームの前半は非常に面白いのですが、後半がダレ気味です。
前半はいかに効率よく水を確保してビーバー達を救うかがキモです。水源開発(ダムなど)を優先しすぎると容易に木材、食料、住宅不足に陥って街の開発が詰んでしまいます。かといって街の開発を優先しすぎると、水が足りなくなってビーバー達が干からびて死んでしまいます。
この辺りのバランスの取り方が難しくも面白いです。干ばつまでの時間と戦いつつ街もダムも拡張させる、ギリギリの街作りを進める楽しさがあります。
しかし一度でも最長期間の干ばつ(Normal 難易度なら 9日間)に耐えられる街ができると、非常に安定してしまい、人口を極端に変えない限りはやることがなくなります。特にダイナマイト量産に成功した後は「山崩しゲーム」と化します。
これが延々と続きます。広いマップの巨大な山を切り崩すのは辛いんで、とりあえず一番狭い Diorama(50x50)マップを使い、山を 2つ(右の山と、左の手前の小さい山)ほど潰して更地にしてみました。こんな小さい山を潰すだけでもかなり時間が掛かります。ひたすらダルいです。
最後の山も残っていますが……もういいや……。
メモ: 技術系?の話は Facebook から転記しておくことにした。加筆。
たまに最適化を無効にするとビルドできなくなるソフトウェアがあります。大抵はバグです。しかし何か目的があって、あえてやっている場合もあります。C 言語標準ライブラリの実装で有名な glibc もその一つです。
最適化をわざと無効にして glibc をビルドするとエラーが発生します。コードは glibc-2.35 を使いました。
$ cd glibc $ mkdir _build $ cd _build $ ../configure CPPFLAGS="-O2 -g" --disable-sanity-checks $ make (成功する、ログは省略) $ cd ../ $ rm -rf _build $ mkdir _build $ cd _build $ ../configure CPPFLAGS="-O0 -g" --disable-sanity-checks $ make In file included from <command-line>: ./../include/libc-symbols.h:75:3: error: #error "glibc cannot be compiled without optimization" 75 | # error "glibc cannot be compiled without optimization" | ^~~~~
最適化を無効にするなと怒られました。コードを見ると __OPTIMIZE__ マクロの定義/未定義を見て検出していました。へえー。
// glibc/include/libc-symbols.h
/* Some files must be compiled with optimization on. */
#if !defined __ASSEMBLER__ && !defined __OPTIMIZE__
# error "glibc cannot be compiled without optimization"
#endif
このチェックをごまかして、最適化を有効にしつつも特定の最適化だけ無効にするとどうなるでしょう?
$ cd glibc $ mkdir __build $ cd __build $ ../configure CPPFLAGS="-O2 -g -fno-inline" --disable-sanity-checks --disable-werror $ make (...略...) gcc -nostdlib -nostartfiles -r -o glibc/__build/elf/librtld.os '-Wl,-(' glibc/__build/elf/dl-allobjs.os glibc/__build/elf/rtld-libc.a -lgcc '-Wl,-)' \ -Wl,-Map,glibc/__build/elf/librtld.os.map gcc -nostdlib -nostartfiles -shared -o glibc/__build/elf/ld.so.new \ -Wl,-z,combreloc -Wl,-z,relro -Wl,--hash-style=both -Wl,-z,defs \ glibc/__build/elf/librtld.os -Wl,--version-script=glibc/__build/ld.map \ -Wl,-soname=ld-linux-x86-64.so.2 \ -Wl,-defsym=_begin=0 /usr/bin/ld: glibc/__build/elf/librtld.os: in function `_dl_close_worker': glibc/elf/dl-close.c:383: undefined reference to `malloc' /usr/bin/ld: glibc/elf/dl-close.c:689: undefined reference to `free' /usr/bin/ld: glibc/elf/dl-close.c:691: undefined reference to `free' /usr/bin/ld: glibc/elf/dl-close.c:693: undefined reference to `free' /usr/bin/ld: glibc/elf/dl-close.c:701: undefined reference to `free' /usr/bin/ld: glibc/elf/dl-close.c:710: undefined reference to `free' /usr/bin/ld: glibc/__build/elf/librtld.os:glibc/elf/dl-close.c:715: more undefined references to `free' follow /usr/bin/ld: glibc/__build/elf/librtld.os: in function `_dl_map_object_deps': glibc/elf/dl-deps.c:438: undefined reference to `malloc' /usr/bin/ld: glibc/elf/dl-deps.c:479: undefined reference to `malloc' /usr/bin/ld: glibc/elf/dl-deps.c:571: undefined reference to `malloc' /usr/bin/ld: glibc/elf/dl-deps.c:543: undefined reference to `malloc' /usr/bin/ld: glibc/__build/elf/librtld.os: in function `_dl_error_free': glibc/elf/dl-exception.c:41: undefined reference to `free' /usr/bin/ld: glibc/__build/elf/librtld.os: in function `__GI__dl_exception_create': glibc/elf/dl-exception.c:89: undefined reference to `malloc' (...略...) /usr/bin/ld: glibc/elf/../sysdeps/posix/getcwd.c:249: undefined reference to `malloc' /usr/bin/ld: glibc/elf/../sysdeps/posix/getcwd.c:493: undefined reference to `free' /usr/bin/ld: glibc/elf/../sysdeps/posix/getcwd.c:469: undefined reference to `realloc' /usr/bin/ld: glibc/__build/elf/librtld.os: in function `__alloc_dir': glibc/elf/../sysdeps/unix/sysv/linux/opendir.c:115: undefined reference to `malloc' /usr/bin/ld: glibc/elf/../sysdeps/unix/sysv/linux/opendir.c:115: undefined reference to `malloc' /usr/bin/ld: glibc/__build/elf/librtld.os: in function `__closedir': glibc/dirent/../sysdeps/unix/sysv/linux/closedir.c:50: undefined reference to `free' /usr/bin/ld: glibc/__build/elf/librtld.os: in function `scratch_buffer_free': glibc/malloc/../include/scratch_buffer.h:86: undefined reference to `free' /usr/bin/ld: glibc/__build/elf/librtld.os: in function `__libc_scratch_buffer_set_array_size': glibc/malloc/scratch_buffer_set_array_size.c:51: undefined reference to `malloc' /usr/bin/ld: glibc/__build/elf/librtld.os: in function `__strdup': glibc/string/strdup.c:42: undefined reference to `malloc' collect2: error: ld returned 1 exit status make[2]: *** [Makefile:1234: glibc/__build/elf/ld.so] エラー 1 make[2]: ディレクトリ 'glibc/elf' から出ます make[1]: *** [Makefile:483: elf/subdir_lib] エラー 2 make[1]: ディレクトリ 'glibc' から出ます make: *** [Makefile:9: all] エラー 2
リンク時に激しく怒られてしまいダメでした。小細工は効きませんね。
デバッグ時に見やすくするために Og や O1 といった最適化レベルを使うことは珍しくないと思いますが、glibc は Og や O1 だとおかしなビルドエラーが発生します。
$ ../configure CPPFLAGS="-Og -g" --disable-sanity-checks (...略...) canonicalize.c: In function ‘realpath_stk’: canonicalize.c:424:50: error: ‘dest’ may be used uninitialized in this function [-Werror=maybe-uninitialized] 424 | return scratch_buffer_dupfree (rname_buf, dest - rname); | ~~~~~^~~~~~~ cc1: all warnings being treated as errors
警告がエラー扱いされただけなので、configure に --disable-werror を渡して警告をエラーと見なさないようにするとビルドは成功します(実は前節でもしれっと使っていました)。
しかし良く考えると O2 では無警告なのに Og では警告が出るとは?なかなか興味深い現象です。警告が出ている realpath_stk() のコードを見ます。
// glibc/stdlib/canonicalize.c
static char *
realpath_stk (const char *name, char *resolved,
struct scratch_buffer *rname_buf)
{
char *dest;
char const *start;
char const *end;
int num_links = 0;
//...
struct scratch_buffer extra_buffer, link_buffer;
scratch_buffer_init (&extra_buffer);
scratch_buffer_init (&link_buffer);
scratch_buffer_init (rname_buf);
char *rname_on_stack = rname_buf->data;
char *rname = rname_on_stack;
bool end_in_extra_buffer = false;
bool failed = true;
/* This is always zero for Posix hosts, but can be 2 for MS-Windows
and MS-DOS X:/foo/bar file names. */
idx_t prefix_len = FILE_SYSTEM_PREFIX_LEN (name);
if (!IS_ABSOLUTE_FILE_NAME (name)) //★★この条件が成立、かつ★★
{
while (!__getcwd (rname, rname_buf->length)) //★★この条件が成立、かつ★★
{
if (errno != ERANGE) //★★この条件が不成立、かつ★★
{
dest = rname;
goto error;
}
if (!scratch_buffer_grow (rname_buf)) //★★この条件が成立した場合を考えると★★
goto error_nomem; //★★dest が未定義のまま、この goto に到達する★★
rname = rname_buf->data;
}
dest = __rawmemchr (rname, '\0');
start = name;
prefix_len = FILE_SYSTEM_PREFIX_LEN (rname);
}
//...
error_nomem:
scratch_buffer_free (&extra_buffer);
scratch_buffer_free (&link_buffer);
if (failed || rname == resolved) //★★しかし、前半の failed = true の条件が必ず成立していて、未定義変数の使用は発生しない★★
{
scratch_buffer_free (rname_buf);
return failed ? NULL : resolved;
}
return scratch_buffer_dupfree (rname_buf, dest - rname); //★★ここで未定義変数の使用の警告が出る★★
}
コード上は dest が未定義のまま使われる可能性があるように見えますし、Og の警告はもっともに思えます。しかしコードを良く見ると dest が未定義のまま error_nomem に来る場合、failed = true が必ず成立して警告が出る行には到達しません。他にも goto error_nomem する箇所はありますが、同様に必ず failed = true が成立します。
O2 だと if 文は必ず成立する場合(異常パス)と、しない場合(正常パス)でコードが複製されるようです。if 文が成立する側のコードでは、if 文の後は不要 = 未使用コードを消去 = dest - rname というコードそのものがなかったことになる、というメカニズムが働いて警告が出ないのでしょう。たぶん。
GCC の警告は大体合っていますが、これはコンパイラが誤警告を出してしまうちょっと珍しい例でした。個人的には紛らわしいコードを書くんじゃねえ!と思いますが、歴史あるコードですし……何か理由があってこうなっているんでしょう。
Linux はプロセスの起動時に、引数や環境変数に加えて Auxiliary Vector(補助ベクタ−)というデータを渡します。ダンプするには LD_SHOW_AUXV を定義してから起動すると良いです。
$ LD_SHOW_AUXV=1 /bin/echo AT_SYSINFO_EHDR: 0x7ffec8bcc000 AT_??? (0x33): 0x6f0 AT_HWCAP: 178bfbff AT_PAGESZ: 4096 AT_CLKTCK: 100 AT_PHDR: 0x5650e2ab4040 AT_PHENT: 56 AT_PHNUM: 11 AT_BASE: 0x7f5d30b65000 AT_FLAGS: 0x0 AT_ENTRY: 0x5650e2ab6980 AT_UID: 1000 AT_EUID: 1000 AT_GID: 1000 AT_EGID: 1000 AT_SECURE: 0 AT_RANDOM: 0x7ffec8bb3499 AT_HWCAP2: 0x2 AT_EXECFN: /bin/echo AT_PLATFORM: x86_64
補助ベクターは基本的には存在するかしないか定かではなく任意です。が、どうも glibc はいくつかの補助ベクターの存在を前提としていて、存在しない場合は起動すらしないようです。
補助ベクターを変更するスマートな方法がありそうですが、わかりませんでした。仕方ないのでデバッガを使って力尽くで書き換えます。まずはテストプログラムを書きます。main に到達する前のことを扱うためプログラムの中身は何でも良いです。
// a.c
#include <stdio.h>
int main(int argc, char *argv[], char *envp[])
{
printf("hello\n");
return 0;
}
実行して main でブレークし、環境変数の配列 envp をダンプします。envp の終端は NULL ポインタですが、実はその先に補助ベクターが置かれています。
$ gcc -Wall -g -O0 -static a.c $ gdb a.out ... (gdb) b main Breakpoint 1 at 0x4017d8: file a.c, line 5. (gdb) r Starting program: /home/katsuhiro/share/a/a.out Breakpoint 1, main (argc=1, argv=0x7fffffffdcf8, env=0x7fffffffdd08) at a.c:5 5 printf("hello\n"); (gdb) x/32x envp 0x7fffffffdd08: 0xffffe039 0x00007fff 0xffffe049 0x00007fff 0x7fffffffdd18: 0xffffe05c 0x00007fff 0xffffe070 0x00007fff 0x7fffffffdd28: 0xffffe089 0x00007fff 0xffffe09f 0x00007fff 0x7fffffffdd38: 0xffffe0b3 0x00007fff 0xffffe495 0x00007fff 0x7fffffffdd48: 0xffffe4a9 0x00007fff 0xffffe4d8 0x00007fff 0x7fffffffdd58: 0xffffe503 0x00007fff 0xffffe50c 0x00007fff 0x7fffffffdd68: 0xffffe534 0x00007fff 0xffffe549 0x00007fff 0x7fffffffdd78: 0xffffe55e 0x00007fff 0xffffe571 0x00007fff (...略...) 0x7fffffffde88: 0xffffefb1 0x00007fff 0x00000000 0x00000000 ★★envp の終端★★ 0x7fffffffde98: 0x00000021 0x00000000 0xf7ffd000 0x00007fff ★★補助ベクターの始まり★★ ...
補助ベクターは下記のような構造です。
typedef struct
{
long int a_type; /* Entry type */
union
{
long int a_val; /* Integer value */
void *a_ptr; /* Pointer value */
void (*a_fcn) (void); /* Function pointer value */
} a_un;
} auxv_t;
簡単に言えば x86_64 の場合 8バイトの type と、8バイトの union が並んでいるだけです。最初のデータ(アドレス 0x7fffffffde98)を例に取ると type = 0x21, data = 0x7fff_f7ff_d000 です。わかりやすいですね。
デバッガから起動する場合は argv, envp, 補助ベクターのアドレスは常に同じです。そのため、
このような手順を取って glibc が補助ベクターを見る前に補助ベクターの値を好きに変更できます。試しに glibc が起動時に参照していて NULL にすることが許されない AT_RANDOM を書き換えます。AT_RANDOM は type = 0x19 です。
(gdb) b _start Breakpoint 2 at 0x4016a0 (gdb) r The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/katsuhiro/share/a/a.out Breakpoint 2, 0x00000000004016a0 in _start () (gdb) x/32x 0x7fffffffde98 0x7fffffffde98: 0x00000021 0x00000000 0xf7ffd000 0x00007fff 0x7fffffffdea8: 0x00000033 0x00000000 0x000006f0 0x00000000 (...略...) 0x7fffffffdf98: 0x00000019 0x00000000 0xffffdfe9 0x00007fff ★★書き換え対象 AT_RANDOM★★ 0x7fffffffdfa8: 0x0000001a 0x00000000 0x00000002 0x00000000 0x7fffffffdfb8: 0x0000001f 0x00000000 0xffffefce 0x00007fff 0x7fffffffdfc8: 0x0000000f 0x00000000 0xffffdff9 0x00007fff 0x7fffffffdfd8: 0x00000000 0x00000000 0x00000000 0x00000000 ★★補助ベクターの終端 type = 0★★ 0x7fffffffdfe8: 0xf8e1f900 0x056adb4e 0xb6df71ae 0x67d4fca2 ... (gdb) set *0x7fffffffdfa0=0 (gdb) set *0x7fffffffdfa4=0 (gdb) x/32x 0x7fffffffdf98 0x7fffffffdf98: 0x00000019 0x00000000 0x00000000 0x00000000 ★★NULL ポインタに書き換えた★★ 0x7fffffffdfa8: 0x0000001a 0x00000000 0x00000002 0x00000000 0x7fffffffdfb8: 0x0000001f 0x00000000 0xffffefce 0x00007fff ... (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0x00000000004032fd in __libc_start_main ()
実行を継続すると main に到達することなく SEGV でクラッシュします。glibc は起動時に AT_RANDOM の指す配列を参照していて、アドレスを NULL ポインタに書き換えたからです。glibc は Linux 専用の libc ですから、AT_RANDOM が存在しない場合を考慮する必要はないとはいえ、なんか微妙な動作ですね……うーん。
一覧が欲しくなってきたので作りました。
スレッドごとに独立したデータを保持する空間を TLS(Thread Local Storage)といいます。日本語だと「スレッド局所記憶」というそうです。TLS へのアクセス方法、TLS の初期化方法などはアーキテクチャ依存ですので、今回は glibc の RISC-V 版の実装を観察します。
変数宣言は __libc_tsd_define() マクロを使います。例として文字処理に使われる ctype 関連の変数を見ます。
// glibc/ctype/ctype-info.c
__libc_tsd_define (, const uint16_t *, CTYPE_B)
__libc_tsd_define (, const int32_t *, CTYPE_TOLOWER)
__libc_tsd_define (, const int32_t *, CTYPE_TOUPPER)
// glibc/sysdeps/generic/libc-tsd.h
#define __libc_tsd_define(CLASS, TYPE, KEY) \
CLASS __thread TYPE __libc_tsd_##KEY attribute_tls_model_ie;
// glibc/include/libc-symbols.h
#define attribute_tls_model_ie __attribute__ ((tls_model ("initial-exec")))
マクロは下記のように展開されます。
// glibc/ctype/ctype-info.c
__libc_tsd_define (, const uint16_t *, CTYPE_B)
↓
__thread const uint16_t * __libc_tsd_CTYPE_B __attribute__ ((tls_model ("initial-exec")));
// glibc/include/ctype.h
__libc_tsd_define (extern, const uint16_t *, CTYPE_B)
↓
extern __thread const uint16_t * __libc_tsd_CTYPE_B __attribute__ ((tls_model ("initial-exec")));
ごちゃごちゃして見えますが、普通の変数宣言との違いは __thread 指定子と tls_model("initial-exec") 属性の 2つです。
これだけだと「そうですか」で終わってしまうので、もう少し詳しく調べます。
スレッドローカル変数のモデルは、Common Variable Attributes - Using the GNU Compiler Collection (GCC) を見る限り 4種類あるようです。
それぞれの意味は悲しいことに GCC のマニュアルに書いていないのです。どうして……。参考になるマニュアルとしては、-ftls-model (-qtls) - XL C/C++ for Linux - IBM Documentation もしくは スレッド固有ストレージのアクセスモデル - Oracle Solaris 11.1 リンカーとライブラリガイドがわかりやすいです。
Solaris のリンカーのモデル名は GCC と少し違いますが、一見して対応がわかる程度の差でしょう。
次回は変数のアドレス取得について見たいと思います。
前回はスレッドローカル変数宣言のコードを見ました。今回は変数のアドレス取得のコードを見ます。
初期化の際はスレッドローカル変数のアドレスを取得する必要があります。アドレスの取得には __libc_tsd_address() というマクロを使います。
// glibc/ctype/ctype-info.c
void
__ctype_init (void)
{
const uint16_t **bp = __libc_tsd_address (const uint16_t *, CTYPE_B);
*bp = (const uint16_t *) _NL_CURRENT (LC_CTYPE, _NL_CTYPE_CLASS) + 128;
const int32_t **up = __libc_tsd_address (const int32_t *, CTYPE_TOUPPER);
*up = ((int32_t *) _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TOUPPER) + 128);
const int32_t **lp = __libc_tsd_address (const int32_t *, CTYPE_TOLOWER);
*lp = ((int32_t *) _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TOLOWER) + 128);
}
libc_hidden_def (__ctype_init)
// glibc/sysdeps/generic/libc-tsd.h
#define __libc_tsd_address(TYPE, KEY) (&__libc_tsd_##KEY)
#define __libc_tsd_get(TYPE, KEY) (__libc_tsd_##KEY)
#define __libc_tsd_set(TYPE, KEY, VALUE) (__libc_tsd_##KEY = (VALUE))
先ほど宣言した変数のアドレスを返すだけの単純なコードです(例えば KEY が CTYPE_B なら &__libc_tsd_CTYPE_B を返す)。
実際はどのようにアドレスを得るのでしょう?下記のようにコードを書き換えて、
// glibc/ctype/ctype-info.c
void
__ctype_init (void)
{
const uint16_t **bp = __libc_tsd_address (const uint16_t *, CTYPE_B);
*bp = NULL;
}
libc_hidden_def (__ctype_init)
空の main 関数のみのコードをスタティックリンクでコンパイルし、実行ファイルを逆アセンブルします。
000000000002c0c6 <__ctype_init>:
void
__ctype_init (void)
{
const uint16_t **bp = __libc_tsd_address (const uint16_t *, CTYPE_B);
*bp = NULL;
2c0c6: 00045797 auipc a5,0x45
2c0ca: 7227b783 ld a5,1826(a5) # 717e8 <_GLOBAL_OFFSET_TABLE_+0x70>
2c0ce: 9792 add a5,a5,tp
2c0d0: 0007b023 sd zero,0(a5)
}
2c0d4: 8082 ret
逆アセンブルを見ると、GOT のエントリからロードしたオフセット+tp レジスタの値 = アドレス、という実装になっています。RISC-V において tp レジスタは「スレッドポインタ」レジスタといって、まさにスレッドローカルストレージのためのレジスタです。
次回はスレッドポインタがいつどこで初期化されるかを見たいと思います。
前回はスレッドローカル変数のアドレス取得部分のコードを見て、tp(スレッドポインタ)レジスタが重要な役割をしていることがわかりました。今回は tp レジスタの初期化コードを見ます。
レジスタの初期化は __libc_setup_tls() で行われます。少し長いですがコードを見ましょう。
// glibc/sysdeps/riscv/nptl/tls.h
/* The TP points to the start of the thread blocks. */
# define TLS_DTV_AT_TP 1
# define TLS_TCB_AT_TP 0
// glibc/csu/libc-tls.c
void
__libc_setup_tls (void)
{
void *tlsblock;
//...
/* We have to set up the TCB block which also (possibly) contains
'errno'. Therefore we avoid 'malloc' which might touch 'errno'.
Instead we use 'sbrk' which would only uses 'errno' if it fails.
In this case we are right away out of memory and the user gets
what she/he deserves. */
#if TLS_TCB_AT_TP
//...
#elif TLS_DTV_AT_TP //★★RISC-V ではこちらのコードが使われる★★
tcb_offset = roundup (TLS_INIT_TCB_SIZE, align ?: 1);
tlsblock = __sbrk (tcb_offset + memsz + max_align
+ TLS_PRE_TCB_SIZE + GLRO(dl_tls_static_surplus));
tlsblock += TLS_PRE_TCB_SIZE;
#else
/* In case a model with a different layout for the TCB and DTV
is defined add another #elif here and in the following #ifs. */
# error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
#endif
//...
/* Initialize the thread pointer. */
#if TLS_TCB_AT_TP
//...
#elif TLS_DTV_AT_TP //★★RISC-V ではこちらのコードが使われる★★
INSTALL_DTV (tlsblock, _dl_static_dtv);
const char *lossage = TLS_INIT_TP (tlsblock); //★★tp レジスタ初期化★★
#else
# error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
#endif
// glibc/sysdeps/riscv/nptl/tls.h
register void *__thread_self asm ("tp");
# define READ_THREAD_POINTER() ({ __thread_self; })
//...
/* Code to initially initialize the thread pointer. */
# define TLS_INIT_TP(tcbp) \
({ __thread_self = (char*)tcbp + TLS_TCB_OFFSET; NULL; })
// glibc/sysdeps/riscv/nptl/tls.h
/* The thread pointer tp points to the end of the TCB.
The pthread_descr structure is immediately in front of the TCB. */
# define TLS_TCB_OFFSET 0
メモリ領域を __sbrk() で確保してアドレスを tp に格納しています。#ifdef で多少見づらいですが、そんなに難しくないはずです。
今までは TLS に含まれるスレッドローカル変数のうち、実行時に値を初期化する変数について見てきました。スレッドローカル変数は実行時に初期化するだけではなく、起動時に初期化される変数もあります。起動時に初期化される変数は、実行ファイルに初期値が含まれていて TLS の初期化時に値がコピーされる仕組みです。
次回は TLS の初期化を見たいと思います。
SNS の利用者層を語るとき Twitter はオジサンばかり、若者は Instagram などと言われますが、SNS の利用率は総務省が調査していて、情報通信メディアの利用時間と情報行動に関する調査 - 総務省から見ることができます。
令和 2年のデータだけを抜き出してグラフにしてみました。結構世代で違うもんですね。
今のところの傾向としてはこんなもんでしょうか。大体イメージ通りの結果でしたが、個人的に意外だったのはニコニコ動画です。ここまで利用者層が若返っているのは意外でした。
私は日本語圏のゲーム系動画を見るならなんだかんだでニコ動が一番面白いと思うので、いまだにプレミアム会員のまま使っていますが、利用者の私ですらもうニコ動には懐ゲー好きのオジサンしかいないのかな?と思っていたくらいでした……。
一昔前、ニコ動のプレミアム会員数が減り始めたくらいから「オワコン」呼ばわりする人達が増えた記憶がありますが、復活したんですね。以前のように皆が戻って来ると良いですねえ。
前回は tp(スレッドポインタ)レジスタの初期化を調べました。今回は TLS の初期化を見たいと思います。起動時に値が設定されている変数の初期化に関わる部分です。
初期化関数 __libc_setup_tls() では tp の指す先(__sbrk() で確保したメモリ領域)も初期化しています。しかしこちらは結構複雑です。大雑把に言うと、
このような処理が行われます。コードも見ておきましょう。
// glibc/csu/libc-start.c
STATIC int
LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
int argc, char **argv,
#ifdef LIBC_START_MAIN_AUXVEC_ARG
ElfW(auxv_t) *auxvec,
#endif
__typeof (main) init,
void (*fini) (void),
void (*rtld_fini) (void), void *stack_end)
{
//...
# ifdef HAVE_AUX_VECTOR
/* First process the auxiliary vector since we need to find the
program header to locate an eventually present PT_TLS entry. */
# ifndef LIBC_START_MAIN_AUXVEC_ARG
ElfW(auxv_t) *auxvec;
{
char **evp = ev;
while (*evp++ != NULL)
;
auxvec = (ElfW(auxv_t) *) evp;
}
# endif
_dl_aux_init (auxvec); //★これ★
if (GL(dl_phdr) == NULL)
# endif
// glibc/elf/dl-support.c
void
_dl_aux_init (ElfW(auxv_t) *av)
{
//...
_dl_auxv = av;
for (; av->a_type != AT_NULL; ++av)
switch (av->a_type)
{
case AT_PAGESZ:
if (av->a_un.a_val != 0)
GLRO(dl_pagesize) = av->a_un.a_val;
break;
case AT_CLKTCK:
GLRO(dl_clktck) = av->a_un.a_val;
break;
case AT_PHDR:
GL(dl_phdr) = (const void *) av->a_un.a_val; //★これ★
break;
case AT_PHNUM:
GL(dl_phnum) = av->a_un.a_val;
break;
// glibc/csu/libc-tls.c
void
__libc_setup_tls (void)
{
//...
/* Look through the TLS segment if there is any. */
if (_dl_phdr != NULL)
for (phdr = _dl_phdr; phdr < &_dl_phdr[_dl_phnum]; ++phdr)
if (phdr->p_type == PT_TLS)
{
/* Remember the values we need. */
memsz = phdr->p_memsz;
filesz = phdr->p_filesz;
initimage = (void *) phdr->p_vaddr + main_map->l_addr;
align = phdr->p_align;
if (phdr->p_align > max_align)
max_align = phdr->p_align;
break;
}
//...
/* Initialize the TLS block. */
#if TLS_TCB_AT_TP
//...
#elif TLS_DTV_AT_TP //★★RISC-V ではこちらのコードが使われる★★
_dl_static_dtv[2].pointer.val = (char *) tlsblock + tcb_offset; //★★TLS のアドレス★★
main_map->l_tls_offset = tcb_offset;
#else
# error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
#endif
_dl_static_dtv[2].pointer.to_free = NULL;
/* sbrk gives us zero'd memory, so we don't need to clear the remainder. */
memcpy (_dl_static_dtv[2].pointer.val, initimage, filesz); //★★TLS に初期データをコピー★★
PT_TLS タイプのプログラムヘッダーがどこに配置されているか?など調べたい場合は、実行ファイルを readelf で見るとわかりやすいです。該当するヘッダがある場合は、Type 欄が TLS となるはずです。
Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000010000 0x0000000000010000 0x000000000005d944 0x000000000005d944 R E 0x1000 LOAD 0x000000000005e4d0 0x000000000006f4d0 0x000000000006f4d0 0x0000000000002500 0x0000000000007938 RW 0x1000 NOTE 0x0000000000000190 0x0000000000010190 0x0000000000010190 0x0000000000000020 0x0000000000000020 R 0x4 TLS 0x000000000005e4d0 0x000000000006f4d0 0x000000000006f4d0 ★★TLS 初期化に使われるセグメント★★ 0x0000000000000020 0x0000000000000068 R 0x8 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x000000000005e4d0 0x000000000006f4d0 0x000000000006f4d0 0x0000000000000b30 0x0000000000000b30 R 0x1
PT_TLS が 2つある場合はどうなるでしょう?実は PT_TLS は実行ファイルに 1つまでなので気にしなくて良いです。プログラムヘッダは同属性のセクションをまとめて 1つのセグメントにします。PT_TLS セグメントを 1つだけにするには、TLS に関わるセクションを連続して配置する必要があります。
もし TLS 関係のセクションを分散させて配置するとリンク時エラーになります。試しにリンカースクリプトを書き換え TLS 関係の .tdata と .tbss セクションの間に、TLS と無関係の .data セクションを挟む形にすると、
riscv64-unknown-linux-gnu/bin/ld: hello: TLS sections are not adjacent: riscv64-unknown-linux-gnu/bin/ld: TLS: .tdata riscv64-unknown-linux-gnu/bin/ld: non-TLS: .data riscv64-unknown-linux-gnu/bin/ld: TLS: .tbss riscv64-unknown-linux-gnu/bin/ld: map sections to segments failed: bad value collect2: error: ld returned 1 exit status make: *** [Makefile:22: hello] エラー 1
リンカーに "TLS sections are not adjacent" と怒られました。
目次: ALSA - まとめリンク
はるか昔、もう 8年も前に見つけた alsa-lib のバグ(2014年 7月 9日の日記参照)、今日見てもまだ直ってなかったっぽいです。激しくノイズが載ってしまう割と致命的なバグですが、発生条件が珍しくてあまり使われないコードのため、未だに誰も直すモチベーションがないと推測されます。せっかく思い出したので、パッチをぶん投げておきました。
パッチの投稿先がわからなくて困ったのですが、どうも alsa-lib は GitHub を(リポジトリへのリンク)使っていて、Issue や Pull Request 管理しているようです。GitHub 使ってるんですね。へー。
ALSA はユーザ空間の alsa-lib の他に、カーネル空間のコードもあります。カーネル空間のコードは Linux Kernel の一部として開発されています。Linux Kernel は GitHub をコード管理に使っておらず、ミラーだけしています(つまり GitHub の Pull Request や Issue は使わない)。
当然ながら Linux Kernel メンテナ達のリポジトリも GitHub ではなく git.kernel.org に置かれていることが多いです。ALSA のカーネル側コードも例外ではなく git.kernel.org の下に置かれて(リポジトリへのリンク)います。
メモ: 技術系の話は Facebook から転記しておくことにした。合併と加筆。
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2021.
Powered by PHP 5.2.17.
using GD bundled (2.0.34 compatible)(png support.)