目次: GCC
以前(2020年3月27日の日記参照)ベクトルレジスタの定義を追加したとき、riscv_hard_regno_mode_ok() を変更しました。machine modeは無視して、ベクトルレジスタを指すレジスタ番号だったらとにかくtrueを返すようにしましたが、実はこの実装は正しくありません。
例えば以下のようなプログラムを書くと、おかしなことが起こります。
void _start(void)
{
static volatile float gf[60];
float f00, f01, f02, f03, f04, f05, f06, f07, f08, f09;
float f10, f11, f12, f13, f14, f15, f16, f17, f18, f19;
float f20, f21, f22, f23, f24, f25, f26, f27, f28, f29;
float f30, f31, f32, f33, f34, f35, f36, f37, f38, f39;
float f40, f41, f42, f43, f44, f45, f46, f47, f48, f49;
float f50, f51, f52, f53, f54, f55, f56, f57, f58, f59;
__asm__ volatile("nop;\n");
f00 = gf[ 0]; f01 = gf[ 1]; f02 = gf[ 2]; f03 = gf[ 3]; f04 = gf[ 4];
f05 = gf[ 5]; f06 = gf[ 6]; f07 = gf[ 7]; f08 = gf[ 8]; f09 = gf[ 9];
f10 = gf[10]; f11 = gf[11]; f12 = gf[12]; f13 = gf[13]; f14 = gf[14];
f15 = gf[15]; f16 = gf[16]; f17 = gf[17]; f18 = gf[18]; f19 = gf[19];
f20 = gf[20]; f21 = gf[21]; f22 = gf[22]; f23 = gf[23]; f24 = gf[24];
f25 = gf[25]; f26 = gf[26]; f27 = gf[27]; f28 = gf[28]; f29 = gf[29];
f30 = gf[30]; f31 = gf[31]; f32 = gf[32]; f33 = gf[33]; f34 = gf[34];
f35 = gf[35]; f36 = gf[36]; f37 = gf[37]; f38 = gf[38]; f39 = gf[39];
f40 = gf[40]; f41 = gf[41]; f42 = gf[42]; f43 = gf[43]; f44 = gf[44];
f45 = gf[45]; f46 = gf[46]; f47 = gf[47]; f48 = gf[48]; f49 = gf[49];
f50 = gf[59]; f51 = gf[51]; f52 = gf[52]; f53 = gf[53]; f54 = gf[54];
f55 = gf[55]; f56 = gf[56]; f57 = gf[57]; f58 = gf[58]; f59 = gf[59];
__asm__ volatile("nop;\n");
gf[ 0] = f00; gf[ 1] = f01; gf[ 2] = f02; gf[ 3] = f03; gf[ 4] = f04;
gf[ 5] = f05; gf[ 6] = f06; gf[ 7] = f07; gf[ 8] = f08; gf[ 9] = f09;
gf[10] = f10; gf[11] = f11; gf[12] = f12; gf[13] = f13; gf[14] = f14;
gf[15] = f15; gf[16] = f16; gf[17] = f17; gf[18] = f18; gf[19] = f19;
gf[20] = f20; gf[21] = f21; gf[22] = f22; gf[23] = f23; gf[24] = f24;
gf[25] = f25; gf[26] = f26; gf[27] = f27; gf[28] = f28; gf[29] = f29;
gf[30] = f30; gf[31] = f31; gf[32] = f32; gf[33] = f33; gf[34] = f34;
gf[35] = f35; gf[36] = f36; gf[37] = f37; gf[38] = f38; gf[39] = f39;
gf[40] = f40; gf[41] = f41; gf[42] = f42; gf[43] = f43; gf[44] = f44;
gf[45] = f45; gf[46] = f46; gf[47] = f47; gf[48] = f48; gf[49] = f49;
gf[50] = f50; gf[51] = f51; gf[52] = f52; gf[53] = f53; gf[54] = f54;
gf[55] = f55; gf[56] = f56; gf[57] = f57; gf[58] = f58; gf[59] = f59;
__asm__ volatile("nop;\n");
}
最適化O1以上でビルドするとinternal compile errorが発生します。
$ riscv32-unknown-elf-gcc b.c -march=rv32imafcv -mabi=ilp32f -O3 -Wall -fno-builtin -fno-inline -nostdlib -g b.c: In function '_start': b.c:52:1: error: insn does not satisfy its constraints: 52 | } | ^ (insn 562 17 18 2 (set (reg/v:SF 65 v1 [orig:104 f00 ] [104]) (reg/v:SF 47 fa5 [orig:104 f00 ] [104])) "b.c":23:6 153 {*movsf_hardfloat} (nil)) during RTL pass: reload dump file: b.c.282r.reload b.c:52:1: internal compiler error: in extract_constrain_insn, at recog.c:2195 0x11b9e4d _fatal_insn(char const*, rtx_def const*, char const*, int, char const*) gcc/gcc/rtl-error.c:108 0x11b9ed1 _fatal_insn_not_found(rtx_def const*, char const*, int, char const*) gcc/gcc/rtl-error.c:118 0x1130298 extract_constrain_insn(rtx_insn*) gcc/gcc/recog.c:2195 0xeabdd8 check_rtl gcc/gcc/lra.c:2167 0xead5c0 lra(_IO_FILE*) gcc/gcc/lra.c:2604 0xe1a63b do_reload gcc/gcc/ira.c:5523 0xe1b078 execute gcc/gcc/ira.c:5709
エラーが発生している箇所はreloadで、エラーの原因となったRTLも出力されています。RTLのsetを見ると、宛先がreg/v:SF 65 v1になっています。浮動小数点の処理のはずなのに、どうしてベクトルレジスタが選択されているのでしょうか?
エラーを出力するのは関数check_rtl() です。lra() の最初あたりcheck_rtl(false) と、最後あたりcheck_rtl(true) の2回呼ばれます。エラーが発生しているのは、2回目の呼び出しです。
// gcc/lra.c
/* Function checks RTL for correctness. If FINAL_P is true, it is
done at the end of LRA and the check is more rigorous. */
static void
check_rtl (bool final_p)
{
basic_block bb;
rtx_insn *insn;
lra_assert (! final_p || reload_completed);
FOR_EACH_BB_FN (bb, cfun)
FOR_BB_INSNS (bb, insn)
if (NONDEBUG_INSN_P (insn)
&& GET_CODE (PATTERN (insn)) != USE
&& GET_CODE (PATTERN (insn)) != CLOBBER
&& GET_CODE (PATTERN (insn)) != ASM_INPUT)
{
if (final_p)
{
extract_constrain_insn (insn); //★★これ
continue;
}
// gcc/recog.c
/* Do uncached extract_insn, constrain_operands and complain about failures.
This should be used when extracting a pre-existing constrained instruction
if the caller wants to know which alternative was chosen. */
void
extract_constrain_insn (rtx_insn *insn)
{
extract_insn (insn); //★★INSN RTLからrecog_dataを作成する
if (!constrain_operands (reload_completed, get_enabled_alternatives (insn))) //★★ここでfalseが返る
fatal_insn_not_found (insn); //★★internal compile errorが出力される
}
おさらいですが、エラーが発生するときのinsnの先頭は下記のようなRTLでした。
(insn 562 17 18 2 (set (reg/v:SF 65 v1 [orig:104 f00 ] [104])
(reg/v:SF 47 fa5 [orig:104 f00 ] [104])) "b.c":23:6 145 {*movsf_hardfloat}
(nil))
関数constrain_operands() は何度も呼ばれるので、エラーが起きるRTLが来たときUID 562で止めます。INSN_UID(insn) == 562が来たときに止められるようにコードを少し改変し、ブレークポイントを仕掛けます。
もしくはINSN_UID(insn) はデバッガから参照するときはinsn->u2.insn_uidで見えますから、条件ブレークでも止められると思います。
// gcc/lra.c
/* Function checks RTL for correctness. If FINAL_P is true, it is
done at the end of LRA and the check is more rigorous. */
static void
check_rtl (bool final_p)
{
basic_block bb;
rtx_insn *insn;
lra_assert (! final_p || reload_completed);
FOR_EACH_BB_FN (bb, cfun)
FOR_BB_INSNS (bb, insn)
if (NONDEBUG_INSN_P (insn)
&& GET_CODE (PATTERN (insn)) != USE
&& GET_CODE (PATTERN (insn)) != CLOBBER
&& GET_CODE (PATTERN (insn)) != ASM_INPUT)
{
if (INSN_UID(insn) == 562) //★★ブレークポイントを仕掛けるために追加した部分
printf("check point!!\n"); //★★ブレークポイントを仕掛けるために追加した部分
if (final_p)
{
extract_constrain_insn (insn);
continue;
}
関数constrain_operands() を追ってみると、RTLが取るオペランド(今回は2つ取っている)に対応するconstraintsをチェックしています。recog_data.constraintsを見るとconstraintsがわかります。
コードは以前(2020年3月29日の日記参照)見たprocess_alt_operands() の前半部分に似た作りとなっています。同じようなコードを色々なところに書くのはやめてほしいですね……。
(gdb) p recog_data.constraints $6 = { 0x2d717f9 "=f,f,f,m,m,*f,*r,*r,*r,*m", 0x2d71813 "f,G,m,f,G,*r,*f,*G*r,*m,*r", ...
このconstraintsは降って湧いたものではなくて、下記の *movsf_hardfloatの定義が由来です。
// gcc/config/riscv/riscv.md
(define_insn "*movsf_hardfloat"
[(set (match_operand:SF 0 "nonimmediate_operand" "=f,f,f,m,m,*f,*r, *r,*r,*m")
(match_operand:SF 1 "move_operand" " f,G,m,f,G,*r,*f,*G*r,*m,*r"))]
...
浮動小数点の移動の定義ですから当たり前なんですけど、ベクトルレジスタを受け付けるconstraints 'v' は含まれません。なのにベクトルレジスタを渡そうとしているのでconstrain_operands() が怒っているわけです。
と、ここまでエラー出力に至るまでを追いましたが、残念なことにエラーの原因はreloadパスにはありません。エラーが出ている箇所と、エラーの原因が全く関係ない現象はGCCでは珍しくなくて、いつも解析が難しくて困っています。
振り出しに戻ってしまいました。続きは次回。
目次: ベンチマーク
昨日(2020年7月4日の日記参照)はSEGVするELFバイナリサイズを92バイトまで削ることができました。
その後、色々弄っていて見つけたのですが、プログラムヘッダのtypeをNULLにしておけば、ファイルサイズやオフセットがめちゃくちゃでもexecveは文句を言わないっぽいみたいです。
ELFヘッダのe_identの後半8バイトは0(前半を書き換えるとELFと認識されなくなります)なので、この部分をプログラムヘッダだよ、と指定すればtype NULLに解釈されます。
これで64バイト、つまりELFヘッダしかない実行ファイルができました。何の役にも立たないですけどね……。
$ ls -la a.out -rwxrwxr-x+ 1 katsuhiro katsuhiro 64 Jul 3 06:39 a.out
これ以上1バイトでも削るとELFヘッダの長さを下回るため、ELFバイナリとして成立しません。よって64バイトが最短だと思いますが、私が気づいていない裏技があるかもしれません。
このファイルを実行してみると、システムコールexecveがエラーを返さないで進むので、ELFファイルとして認識してもらえているようです。
$ strace ./a.out execve("./a.out", ["./a.out"], 0x7ffedf7dfa90 /* 47 vars */) = 0 --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x400000001} --- +++ killed by SIGSEGV +++ Segmentation fault
$ readelf -a a.out ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x400000001 Start of program headers: 8 (bytes into file) Start of section headers: 0 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 1 Size of section headers: 0 (bytes) Number of section headers: 0 Section header string table index: 0 There are no sections in this file. There are no sections to group in this file. Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align NULL 0x00000001003e0002 0x0000000400000001 0x0000000000000008 0x0000000000000000 0x0038004000000000 0x1 There is no dynamic section in this file. There are no relocations in this file. The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not curr ently supported. Dynamic symbol information is not available for displaying symbols. No version information found in this file.
一応readelfでも読めますが、プログラムヘッダのオフセットやアドレスはめちゃくちゃです。
目次: ベンチマーク
昨日(2020年7月3日の日記参照)試したところによると、C言語は0文字でSEGVするプログラムを書けました。
では、出力されたバイナリだと最短はいくつでしょうか?とりあえずベースとしてC言語の0文字SEGVプログラムの出力を見ます。
$ gcc -nostdlib a.c /usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000001000 $ ls -la a.out -rwxr-xr-x 1 katsuhiro katsuhiro 9528 Jul 3 04:11 a.out
なんと9KBもあります。readelfで見るとダイナミックリンカー関連のシンボルが含まれているようなので、staticオプションを付けてダイナミックリンカー関連のシンボルを消します。
$ echo -n > a.c && gcc -nostdlib -static a.c /usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000 $ ls -la a.out -rwxr-xr-x 1 katsuhiro katsuhiro 968 Jul 3 04:12 a.out $ strace ./a.out execve("./a.out", ["./a.out"], 0x7ffc92c2b390 /* 46 vars */) = 0 --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x401000} --- +++ killed by SIGSEGV +++ Segmentation fault
ログが大量に出るはずのstraceも、わずか1行しかログが出ません。何もしないバイナリにも関わらずa.outのサイズは1KB近くあります。
お手軽なバイナリのダイエットとして、実行に不要なセクションをstripします。
$ strip -R ".note.gnu.build-id" -R ".comment" a.out $ ls -la a.out -rwxr-xr-x 1 katsuhiro katsuhiro 376 Jul 3 04:18 a.out
それでも376バイト。まだでかいですね。
この376バイトのファイルを元にして、バイナリを手で削ります。加工の方針としては、
結果92バイトになりました。
$ ls -la a.out -rwxrwxr-x+ 1 katsuhiro katsuhiro 92 Jul 3 05:43 a.out
もっとアグレッシブにELFヘッダとプログラムヘッダを重ねれば、64バイトにできるかもしれません。
このファイルを実行してみると、システムコールexecveがエラーを返さないで進むので、ELFファイルとして認識してもらえているようです。
$ strace ./a.out execve("./a.out", ["./a.out"], 0x7fff2b4f74a0 /* 47 vars */) = 0 --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x400000001} --- +++ killed by SIGSEGV +++ Segmentation fault
$ readelf -a a.out ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x400000001 Start of program headers: 36 (bytes into file) Start of section headers: 0 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 1 Size of section headers: 0 (bytes) Number of section headers: 0 Section header string table index: 0 There are no sections in this file. There are no sections to group in this file. Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align NULL 0x0000000000000000 0x0000000100380040 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x1000 There is no dynamic section in this file. There are no relocations in this file. The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported. Dynamic symbol information is not available for displaying symbols. No version information found in this file.
一応readelfでも読めますし、そこそこ真っ当なファイルをキープできていると思います。
SEGVを出すのがTwitterで流行しているみたいなので、少し考えてみました。Twitterで見かけたのは、*a;main(){*a=0;}(16文字)です。最適化オプションにもよりますが、異常なアドレスへのmovか、未定義命令ud2が出力されてSEGVします。
$ echo -n '*a;main(){*a=0;}' > a.c && gcc a.c a.c:1:1: warning: data definition has no type or storage class 1 | *a;main(){*a=0;} | ^ a.c:1:2: warning: type defaults to 'int' in declaration of 'a' [-Wimplicit-int] 1 | *a;main(){*a=0;} | ^ a.c:1:4: warning: return type defaults to 'int' [-Wimplicit-int] 1 | *a;main(){*a=0;} | ^~~~ $ ./a.out Segmentation fault
ただSEGVするだけで良ければ、mainのアドレスを .textではないアドレスにすれば良いので、main;(5文字)でも、達成できます。
$ echo -n 'main;' > a.c && gcc a.c a.c:1:1: warning: data definition has no type or storage class 1 | main; | ^~~~ a.c:1:1: warning: type defaults to 'int' in declaration of 'main' [-Wimplicit-int] $ ./a.out Segmentation fault
この例はmainを関数ではなく変数として定義し、mainをbssセクションに配置します。最近のLinuxならばデータが置かれているセグメントは実行禁止にするはずなので、mainが指すデータが何であろうと、ジャンプした瞬間にSEGV します。
趣旨とは外れますがSEGVを避けて正常終了させたければ、
$ echo -n 'main=0xc3;' > a.c && gcc -z execstack a.c a.c:1:1: warning: data definition has no type or storage class 1 | main=0xc3; | ^~~~ a.c:1:1: warning: type defaults to 'int' in declaration of 'main' [-Wimplicit-int] $ ./a.out (SEGVしない)
変数mainが指す位置にretq命令(0xc3)を置いて、-z execstackオプションでデータ領域を実行可能にしています。attributeで .text領域に置いても実行できますが、そんなことするくらいならmain() 関数を書いた方が短いです。
変化球を使ってよければ0文字でSEGVできます。nostdlibオプションを使います。
$ echo -n > a.c && gcc -nostdlib a.c /usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000001000 $ ./a.out Segmentation fault
GNU ld(他のリンカーでも同じだと思いますけど)はELFのエントリアドレスに _startというシンボルのアドレスを使います。通常はcrt.oなど、リンク時に自動的に追加されるオブジェクトが _startを定義しますが、nostdlibオプションによりcrt.oがリンクされなくなって、_startは未定義になります。
するとldは _startを適当なアドレスに設定(上記の例では0x1000に)します。当然 _startが指すアドレスにコードはありませんから、実行するとクラッシュします。
目次: ゲーム
Steamの買い物カートの中身一覧を見る方法を探していたんですが、全くわかりませんでした。だいぶ彷徨いましたが、未だトップ画面からカートを見る方法はわからないままです。Steamは本当にUIがひどい。Amazonを見習ってくれ……。
その際に副産物として、秘密の実績を確認する方法を見つけたのでメモしておきます。
と辿ると自分が所持しているゲームの一覧が出ますから、実績を見たいゲームの
と辿ると全ての実績が表示されます。
秘密の実績の意味とは一体……??
目次: ゲーム
STATIONflowのランクが100になりました。何か実績と紐づいているかなと思いましたが、特に何も起きませんでした。しょんぼり。
普通のマップだと重いし、時間も掛かりすぎてやってられないので、専用の軽量マップを作ってひたすら待ちました。それでも相当時間が掛かります。普通に遊ぶ分にはランク50で十分ですね。
STATIONflowでランク100にする途中でいくつかわかったことがありました。
ランク100まで行ってもまだ取れない実績が残っていて(ラッキーセブン、トウキョウ)、なかなか気が遠くなるゲームです。
(※)出入口が6 x 9 = 54、乗り場が6 x 2 = 12、集客レベルは2段階上がるため (54 + 12) x 2 = 132です。
STATIONflowの実績にtypoがありました。
漢字だけ間違ってます。惜しい……!
目次: ゲーム
以前(2020年5月28日の日記参照)STATIONflowで速度3にすると駅の評価が下がることをお伝えしましたが、若干間違っていて「画面の処理落ちで評価が下がる」方が実態に近そうです。
速度2でA+ 評価の駅を使って実験したところ、ノートPCのクロック周波数を低くするだけで、駅の評価がAに下がりました。
駅の評価と見せかけて、実はマシンの評価も入ってるんでしょうか?余計なお世話なので、勘弁してくれよ。
目次: Windows
その2はこちら。
ノートPCでゲームをしていると、筐体が焼けそうなほど熱くなり、キーボードまで熱くなってキーを打つのが辛いです。
発熱の原因はグラフィックスチップですが、どうもCPUも無罪ではないらしく、TurboBoostを無効にするとややマシになることがわかりました。
私のマシンでは「電源オプション」 - 「プロセッサの電源管理」 - 「最大のプロセッサの状態」にて、87%以下にするとTurboBoostがOFFになりました。
下記は87%設定(1.49GHz)と88%設定(3.38GHz)にしたときのCPU動作周波数の変化です。
当然ながら1.49GHzと3.38GHzでは性能に天と地ほどの差があって、1.49GHzだとSTATIONflowの画面はめちゃくちゃカクつきます。
しかしシミュレーションゲームでは、待っているだけの時もありますし、常に爆熱で動いてくれる必要はありません。TurboBoostを任意にOFFにできるのは非常に便利です。
CPUのクロック周波数の上限は何段階かあるようなので、変化点を調べました。
こんな感じでした。Core i5-8250Uはベース周波数1.6GHz、ブースト周波数3.4GHzなのにベース周波数である1.6GHzに張り付く設定は存在しません。謎です。
タスクマネージャーに「基本速度1.8GHz」と表示されているのも謎です。どこから1.8GHz出てきた……??
目次: ゲーム
STATIONflowのしょうもない小技 その2です。
実績の解除が非常に難しい「エレベスト」や「効率の鬼」のような難しい実績が、超簡単に取れる方法です。
例えば、エレベストの条件は「駅ランク20以上で階段とエスカレーターを設置せずにA+ 評価」です。前回(2020年6月27日の日記参照)お伝えした通り、エレベーターと通勤客の変な挙動が合わさって、まともにやるとかなり難しいです。
しかしSTATIONflowの実績判定は甘々で「00:00になった瞬間」しか見ません。従って、
これだけで達成できます。正直、こんな低レベルな小細工が通じると思わなかったので、逆にびっくりしました……。
類似した実績「階段抜き」「ノンエスカレーター」「エレベスト」「効率の鬼」ならば同じ手が通用するはずです。
作成者の想定とは違うだろうという意味で「邪道」な感じはしますが、バグを突いた挙動でもなさそうだし、早解きしたい方は利用してみても良いでしょう。
目次: ゲーム
STATIONflowの基本は理解したつもりなので、実績解除に挑んでますが、難しくて取れないものがいくつかあります。
今のところ一番難しいと思うのは「エレベスト」です。条件は「駅ランク20以上で階段とエスカレーターを設置せずにA+ 評価」です。
ありがちな条件ですが、STATIONflowのエレベーターはとてもヘボいため達成は困難です。
一番問題なのは3つ目の条件です。通常、待ち行列から離脱した客は階段やエスカレーターを利用しますが、移動手段がエレベーターしかない場合、不可解な動きをします。
(※)彷徨っている間はエレベーターが到着しても乗りません。一番訳が分からない動きです。しかもこの彷徨う時間が長い。
このようなおかしな行動をし続けた挙句、勝手に怒り始めて、駅の評価を下げてきます。理不尽極まりないですね。
ランク20到達後も駅と人が増え続けるため、どんどん客が捌ききれなくなり条件が悪くなる一方ですから、「エレベスト」実績を達成する最大のチャンスは「ランク20になる瞬間」でしょう。
が、私の腕では評価Aが限界でした。
もっと頑張ればできるのかもしれないですが、何回も同じマップをランク20までやり直すのは時間掛かって辛いし面白くないのでもうやりません……。
目次: ゲーム
STATIONflowのしょうもない小技。
将来的に、どこに駅の入り口と電車の乗り場が出てくるか?を見たい場合は「マップ作成」で見たいマップを「複製」「マップ編集」すれば、全ての入り口、乗り場を見ることができます。
STATIONflowの面白さは、どこに増設されるかわからない入り口や乗り場に対応することではあるのですが、記録を狙ったりするときや、事前に完璧な駅建築計画を立てたい人は、覗いてみると役立つかもしれません。
< | 2020 | > | ||||
<< | < | 07 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | 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 | - |
合計:
本日: