久々のwindows
Powershell7
インストール
GitHub - PowerShell/PowerShell: PowerShell for every system!
リリースからダウンロードしてインストール
keybindをlinuxっぽく
PSReadLine は最初から入ってたっぽいので↓実行する
Set-PSReadLineOption -EditMode Emacs
ってやってて気づいたけどこれだと永続化しないので, $PROFILEに書く。
新しいPowerShellで$PROFILEの場所が変わってたので作り直しておく
New-Item -Name (Get-Item $PROFILE).Directory -Type dir New-Item -path $PROFILE -ItemType file vim $PROFILE cat $PROFILE Import-Module PSReadLine Set-PSReadLineOption -EditMode Emacs
Windows Terminal
とりあえず入れた
WSL2
WSL2対応版のDocker Desktopで必要だったのでUbuntu 18.04 入れた
WSL 2 のインストール | Microsoft Docs
この手順通りでいけたけど、
wsl --set-version <Distro> 2
をやらないと変換されないのに、
wsl --set-default-version 2
こっちをずっと打ってて変換されなくて頭抱えてた
Docker for Desktop
Docker Desktop for WSL2 を使い快適にWindowsでサーバ開発をしよう! - Qiita
これみてやった。(けど、後述だけど↑これはもう古かった。変なところからインストールしたせいでおかしな物が入ってた)
インストールは普通にできたが
PS C:\Users\totem> docker run hello-world C:\Program Files\Docker\Docker\Resources\bin\docker.exe: error during connect: Post https://192.168.99.100:2376/v1.40/containers/create: dial tcp 192.168.99.100:2376: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
Windows側からはつながらない.
WSL2側では動いてる。そりゃそうだけど。
totem@xxx:/home/totem$ docker run hello-world Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/
defaultのほう見てたっぽい。とはいえこっちも起動してるはずなんだけどなぁ
PS C:\Users\totem> docker context ls NAME DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHE STRATOR default * Current DOCKER_HOST based configuration tcp://192.168.99.100:2376 swarm wsl Docker daemon hosted in WSL 2 npipe:////./pipe/docker_wsl Warning: DOCKER_HOST environment variable overrides the active context. To use a context, either set the global --conte xt flag, or unset DOCKER_HOST environment variable.
とまぁ色々悩んでたんだけど、これなんか古いバージョン入れてたせいでおかしくなってた。docker context の wsl ってのはもう使わないっぽい(?)
あと、古いdocker toolboxみたいなの入れてたから変に環境変数が設定されてて動かなかった。
最新のdocker desktopにアップデートして、環境変数消したら問題なくなった。
Visual Studio 2019
2017入ってたけど入れ直した。何が違うのか知らんけど。
クリップボード
前から使えたはずだけど知らなかったので windows + V で clipboard history 有効にしてclibor サヨナラ
そんなことにはまるんだってやつ C++編その1
久々にビビるレベルの内容で本当にわからなくて長時間ハマったので記念にメモ。本当にポインタがわか
これがコンパイルエラーで小一時間ハマったんだけど、is
はmyarray
じゃなくてmyarray*
なのでオーバーロードした[]
が呼ばれてるわけじゃないので当然…ポインタに対して添字演算しているのでis[0]
はそのままその位置を返すな。はい。
#include <iostream> template<typename T> struct myarray { T *a; size_t length; myarray(size_t n) { a = new T[n]; length = n; } T & operator[](size_t i) { return a[i]; } }; int main(int argc, char const* argv[]) { myarray<int> *is = new myarray<int>(5); is[0] = 1; std::cout << "i: " << is[0] << std::endl; return 0; }
systemtap で udp の daddr, dport が 0.0.0.0 や 0 に見えることがあるのはなんでなのか
(udp だけど)tcpdump すれば当然宛先の ip address や port が見えるが、systemtap で daddr, dport を printf してみると daddr が 0.0.0.0, dport が 0 に見えることがある。
名前解決のトレースをしていて、通常名前解決をしているときは宛先ポートが 53 になので、 probe udp.sendmsg
で if (dport == 53)
としておけば名前解決に関係する udp の送信だけトレースできるのだけど、たとえば dig で名前を引いたときにはこれだと引っかからなかったことで気付いた。
実際にトレースしたいプログラムで名前解決が実行される場合は普通に dport == 53
で引っかかるので特に問題はなさそうだが、どういうケースで 0 になってしまうのか確認した。
結論から言うと、(正確にはわかっていないが)
- systemtap は socket 構造体の daddr, dportを見ているので、
- データを送信(受信)している socket が
--
connect
していればconnect
したアドレスが daddr, dport として見える --connect
していなければ daddr, dport は 0
ということのようだった。
確認した環境は
ーーー
systemtap の udp.sendmsg の probe の daddr, dport の定義を見てみる。
systemtap自体の中身を追うのはかなりしんどいはずだが、probeについて調べるなら簡単だった。tapset 以下に stp ファイルがあり、そこに定義されている。tapset/linux/udp.stpを見てみると、udp.sendmsg
はこのように定義されている。
sourceware.org Git - systemtap.git/blob - tapset/linux/udp.stp
39 probe udp.sendmsg = kernel.function("udp_sendmsg") { 40 name = "udp.sendmsg" 41 sock = $sk 42 size = $len 43 %( systemtap_v >= "2.3" %? 44 family = __ip_sock_family($sk) 45 saddr = format_ipaddr(__ip_sock_saddr($sk), __ip_sock_family($sk)) 46 daddr = format_ipaddr(__ip_sock_daddr($sk), __ip_sock_family($sk)) 47 sport = __udp_sock_sport($sk) 48 dport = __udp_sock_dport($sk) 49 %) 50 }
確認したい dport
は __udp_sock_dport($sk)
の部分。これはもっと上に定義されていて、
sourceware.org Git - systemtap.git/blob - tapset/linux/udp.stp
15 @__private30 function __udp_sock_sport (sock) { return __tcp_sock_sport (sock) } 16 @__private30 function __udp_sock_dport (sock) { return __tcp_sock_dport (sock) }
sourceware.org Git - systemtap.git/blob - tapset/linux/tcp.stp
82 /* return the TCP destination port for a given sock */ 83 function __tcp_sock_dport:long (sock:long) 84 { 85 port = @choose_defined(@inet_sock_cast(sock)->sk->__sk_common->skc_dport, # kernel >= 3.8 86 @choose_defined(@inet_sock_cast(sock)->inet_dport, # kernel >= 2.6.33 87 @choose_defined(@inet_sock_cast(sock)->dport, # kernel >= 2.6.11 88 @inet_sock_cast(sock)->inet->dport))) 89 return ntohs(port) 90 }
@hogehogeはよくわからないが、要するに skc_dport
を見てるんだなとわかる。ような気がする。
これは大体 inet_dport
と同じようなものだろう。
udp の場合、 connect
で呼ばれるのは おそらく ip4_datagram_connect
のはずで、
https://elixir.bootlin.com/linux/v4.0/source/net/ipv4/datagram.c#L23
その中でソケットに inet_daddr
や inet_dport
を設定している。
https://elixir.bootlin.com/linux/v4.0/source/net/ipv4/datagram.c#L76
int ip4_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) { struct inet_sock *inet = inet_sk(sk); struct sockaddr_in *usin = (struct sockaddr_in *) uaddr; ... inet->inet_daddr = fl4->daddr; inet->inet_dport = usin->sin_port; sk->sk_state = TCP_ESTABLISHED;
これは、 udp の send
で宛先を指定しなかった場合に宛先として利用される箇所と一致している、はず…。下記は udp_sendmsg
の該当箇所。 msg
が send
にわたす宛先などの情報で、それがない場合は else
の方に入り、 inet->inet_daddr
や inet->inet_dport
をローカル変数の daddr
や dport
に代入している。
https://elixir.bootlin.com/linux/v4.0/source/net/ipv4/udp.c#L932
int udp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t len) { struct inet_sock *inet = inet_sk(sk); ... ... /* * Get and verify the address. */ if (msg->msg_name) { DECLARE_SOCKADDR(struct sockaddr_in *, usin, msg->msg_name); if (msg->msg_namelen < sizeof(*usin)) return -EINVAL; if (usin->sin_family != AF_INET) { if (usin->sin_family != AF_UNSPEC) return -EAFNOSUPPORT; } daddr = usin->sin_addr.s_addr; dport = usin->sin_port; if (dport == 0) return -EINVAL; } else { if (sk->sk_state != TCP_ESTABLISHED) return -EDESTADDRREQ; daddr = inet->inet_daddr; dport = inet->inet_dport; /* Open fast path for connected socket. Route will not be used, if at least one option is set. */ connected = 1; }
ということで、
- systemtap で udp.sendmsg の probe 内の daddr, dport は実際にパケットを送信した宛先が見えているのではない
- 使える変数は probe にもともと定義されている
- udp.sendmsg の場合、socket から宛先の情報を取得している。
- udpの場合、socket を
connect
してsend
のときには宛先を指定しないこともできるし、connect
せずsend
時に宛先を指定することもできる connect
した場合は socket に宛先情報が設定されているので systemtap の probe 内から参照できる。connect
していない場合は socket の宛先情報とは関係なく送信するので、 systemtap の probe 内からは宛先を正しく参照することができない。
ということのようだ。
ちなみに glibc の getaddrinfo
では connect
しているっぽくて、実際 systemtap でトレースしていても dport がちゃんと取れている。
dig ではどうかと思ったが dig はソースコード読むのが難しかったので諦めた
stackprof の wall mode で rails のプロファイルをとろうとしたら mysql の接続エラーがたくさん出てホストが mysql にブロックされてしまう話
起きたこととしては
- stackprofをrack-stackprofで使って rails アプリのプロファイルを取ろうとした
- stackprof は wall mode で動いてる
- すると、Host 'x.x.x.x' is blocked because of many connection errors が発生した
- 起動後しばらくは正常に動いているが、しばらくするとエラーが連続して発生してしまうようで mysql サーバにブロックされアプリが動かなくなる。
原因は
- stackprof は profile を取るために
setitimer
で定期的にシグナルを送る - wall モードの場合、
setitimer
がITIMER_REAL
で動くので、実時間で定期的にシグナルを送る。 - libmysql は mysql に接続後
poll
でデータを待ち受ける。 poll
で待っている間にシグナルを受け取ると、poll
はEINTR
で失敗して connection を切ろうとする。- ここで、サーバが既に Server Greeting を送っているのに切断されてしまうと、サーバとしては接続が不正に中断されたとしてエラーだと判断する。
- (このあたりの詳細はよくわかってない。)
- mysql には max_connect_errors という値があり、それを超える回数連続で接続が失敗するとそのホストをブロックする。
- ってことで連続で失敗してブロックされてしまうってことみたいだった。
原因はわかったものの、世の中の人は stackprof で rails アプリケーションのプロファイリングをしているのにこういうことで困ってないということはなにかが間違っているような気もするし、mysql が localhost だとブロックされることはないとか、普通は mysql で skip-name-resolve
を設定していて max_connect_errors
は関係なくなるので気付かないとかそういう話なのかもしれない。(会社でもオンプレのときは skip-name-resolve
は設定していたような気がする)
それぞれもう少し詳しく
stackprof
今回はじめて中身見た。 setitimer
もはじめて知った。プロファイラってこうやってやるんだな〜という。
setitimer
して定期的にシグナルを受け取って処理することで時間を測る。
計測モードにはいくつか種類があるが、README を見ると
four sampling modes are supported:
:wall (using ITIMER_REAL and SIGALRM)
:cpu (using ITIMER_PROF and SIGPROF) [default mode]
ということで、問題の wall モードでは ITIMER_REAL
が使われている。 ITIMER_PROF
だと poll
の間は時間が進まないのでシグナルが送られることがなく、問題が起きないのだろう。きっと。
- Man page of GETITIMER
- stackprof/stackprof.c at fd6b83295b92f49bc4ba76049cfbc9cb28bf0079 · tmm1/stackprof · GitHub
mysql2
問題の Mysql2::Client.new
が実際に何をやってるかというと、initialize
の最後で connect
している
この connect
メソッドは native extension の方で定義されている。実際には rb_mysql_connect
実際に接続しているのはここで、nogvl_connect
というメソッド。
こいつが libmysql の mysql_real_connect を呼んで接続する。
クライアント側が接続失敗に気付かないのは、この rb_mysql_connect
の中で、エラーが EINTR
であればリトライしているから。↓この部分。
mysql2/client.c at 3b9a26708fa86aba23763626331eb317ed457cc1 · brianmario/mysql2 · GitHub
libmysql
バージョンは 5.7.24。(そういえば、地味に cmake のときに boost が見つからないっていう問題があり解決できなかったなぁ。メッセージに従って -DDDOWNLOAD_BOOST=1 したんだけど)
いろいろ見たけど、 poll
しているのは vio/viosocket.c の vio_io_wait
の中 786 行目から。print 入れまくってここでエラーが出ているのにたどり着いた。
switch ((ret= poll(&pfd, 1, timeout))) { case -1: /* On error, -1 is returned. */ break; case 0: /* Set errno to indicate a timeout error. (This is not compiled in on WIN32.) */ errno= SOCKET_ETIMEDOUT; break; default: /* Ensure that the requested I/O event has completed. */ DBUG_ASSERT(pfd.revents & revents); break; }
poll
Man page of POLL のエラーのところを見ると
EINTR 要求されたイベントのどれかが起こる前にシグナルが発生した。 signal(7) 参照。
エラーの原因はこいつです。多分
RSTが起きるのはなぜか
tcpdump の結果はこういう感じで、ハンドシェイク直後にクライアントが FIN を送っていて、サーバは Greeting を送っており、その後クライアントは RST を返す。これはなんでなのかよくわからなかった。
どう解決すべきなのか?
本当はクライアント側のもう少し早いところで EINTR
のリトライをすればいいのでは?という気がする。けどまぁものによるだろうから仕方ないんだろうなぁ。
libmysql の mysql 8 のソースコードを見ると、この部分には ppoll
が使われるようになっていて、シグナルがマスクできるようになっている。
しかし、それを設定できるのはサーバ側だけで、クライアント側からは設定できない。残念。ここを設定できるようにすればいいよね。
実際に、 5.7 のソースコードで ppoll
に切り替えて SIGALRM
をマスクするようにしたところ問題は発生しなくなった。プロファイルの結果には微妙に影響があるだろうけど、エラーが出るよりは全然良い。
ただ、さすがに libmysql にパッチを当てるのは厳しいのでクライアント側で対応するのは難しい。
とするとサーバ側でなんとかするしかない。
で、↓を見た。 max_connect_errors
回連続した接続失敗でホストをブロックするのをやめよう。ほんとにエラーでなくするだけだけど………
MySQL 5.6のインストール後にチューニングすべき項目 | Yakst
max_connect_errors
というのは skip-name-resolve
を有効にしていると無効になるようだ。skip-name-resolve
は名前解決を利用しない限り設定することが推奨される項目であり、この度エラーが出ていた環境では設定していなかったが経験上本番環境でも設定されていた。
つまり, max_connect_errors
回を超える接続失敗によって接続を拒否するなんてしなくてもいいんだ。そもそも mysql を外部に晒すなんてことはないわけで、攻撃を受けるとしたら内部から。内部に侵入されているのにこれで検知できるような攻撃されるってことはないだろう…多分。ってことでセキュリティ的にはあまり懸念はないはず。
あとはこういう微妙なケースで実は接続エラーが発生しているということに気付かないという可能性はあるが、めったにあることではないので、 SHOW GLOBAL STATUS
の Aborted_connects
でも監視しておけばいいでしょう。
時系列
ここからは調べた履歴。行き当たりばったりで調べて時間を費やしたので自戒
続きを読むmac で dev_appserver.py を再起動しないと変更が反映されなかった
OSは mac, runtime は python27 のお話。 Google Cloud SDK のバージョンは 228.0.0。 components のバージョンは最後に書いた
結論から
起動してるGAEアプリケーションが . で始まるディレクトリ以下にあるとダメ
僕の場合は ghq を使って $HOME/.ghq/hoge/fuga 以下にアプリを clone して作業してたのでダメだった。
昔はいろいろあったようだが、現在の dev_appserver はファイルの変更を検知して変更を反映してくれるようになっている。
macの場合は inotify が使えないので、ファイルの mtime をポーリングして変更を検出している。ポーリング対象から除外する条件みたいなのが用意されており、恐らく意図としては .git とかを除外するために、 . から始まるディレクトリは除外するようになっている。
しかし、実際には working directory 以下の . 始まりのディレクトリを無視するのではなく、絶対パスで途中に . 始まりのディレクトリがあるとまるっとwatch対象から除外されてしまう。(inotify watcherでも同じ気はするが確認はしてない)
ということで ghq のルートディレクトリを $HOME/.ghq から $HOME/ghq にすることで問題は解決した。
おしまい
補足。 Google Cloud SDK の version
❯ gcloud --version Google Cloud SDK 228.0.0 alpha 2018.11.09 app-engine-python 1.9.80 app-engine-python-extras 1.9.74 beta 2018.11.09 bq 2.0.39 cloud-datastore-emulator 2.0.2 core 2018.12.07 gsutil 4.34
facebookの自動テストツールの論文を読んだメモ
Facebook がバグの自動修正ツールを発表したという話があり、 code.fb.com
その紹介記事を見て、 Sapienz のほうは論文が発表されているという話だったので読んでみた。軽く。
バグの自動修正ツールのほうはSapFixというやつで、バグを修正するためにはまずバグを見つけなければいけないが、そのバグを検知するために使われているのがSapienz。
で、SapienzというのはAndroidアプリ向けの自動テストツール。
この論文を書いた一人のMark Harmanという方(とMcMinnさんという方)が2001年にSearch-based software engineering (SBSE) という手法を提案しており(参考: http://a-lifelong-tester.cocolog-nifty.com/publications/STM07_Notes_on_new_software_testing_techniques.pdf)、 曰くSBSEとは、
手法であり、
このサーチ技法をテストの分野に適用するのがSearch-based testingと呼ばれる技法
である。そのSearch-based testingの手法をAndroidのテストに応用して成果が出たよんということのようだ.
上で参考にあげた資料に書いてあるようにサーチ技法として用いられるアルゴリズムにはいくつかの種類があるが、Sapienzで用いられているのは Evolutionary Algorigthm (進化的アルゴリズム) である。 また、Androidのテストにおいては、多目的最適化を使ったsearch-based testingの初めてのアプローチだぜ、と言っているし、論文のタイトルからしてMulti-objective Automated Testing for Android Applicationsであるように、多目的最適化問題として扱っている。
進化的アルゴリズムでは、解きたい問題の解を個体として表現する(多分)。
個体の性質は染色体によって決まり、染色体は複数の遺伝子から成る。
そして個体同士で交叉して子を作ったり突然変異したり一部の個体は淘汰されたりして進化をして世代を重ねていくことで、よりいい感じの解を探す。
詳しくや正確にはもちろん他の資料を参考に。
で、Sapienzで具体的にどんな風に問題が表現されているか。
Sapienzで最適化したい問題というのは、
- より多くの範囲をテストし(coverage)
- より多くの不具合を(fault revelation)
- より短い手順で(shorter test sequence)
発見するようなテストを生成したいということ。
この3つを同時に最適化するので、Multi-objectiveな最適化という話。
論文中でも言及されているが、多目的最適化においては一般にパレート最適な解を見つけることが目的になる。 この場合上記の3つの要素についてパレート最適な解を探す。
Sapienzでは、進化的アルゴリズムにおける個体などの表現は下記のようになっている。
- 個体: テストスイート
- 染色体:テストケース
- 遺伝子:なんらかのイベント
で、遺伝子には2種類ある。Atomic genesとMotif genes.
Atomic genesのほうはその名の通りAtomicであってそれ以上分割することのできない単位のイベント。 たとえばなんかのキーを押すとかどこかをタップするとか音量を調節するとかそういうの。
Motif genesのほうはAtomic genesの組み合わせ。
なんで組み合わせを使うのかというと、UIというのは複雑なものなので、状態やその時のコンテクストの知識なしに単純にAtomic genesだけ組み合わせているとちゃんとした動作を組み立てるのが難しいからだそう。まぁそれはそうっぽい感じがする。しかしこの論文の時点では、Motif genesは汎用的なもの1種類しか使っていないようだ。Motif geneをたくさん使うということはそれだけ人間の手がかかるということで、自動化を妨げることになるからということ。
個体や遺伝子についてはそれで、個体を進化させるためのアルゴリズムの話
下記のいずれかを確率的に個体に適用する。
- 交叉(crossover)
- 変異(mutation)
- 繁殖(reproduction)
個体間では一様交叉を行う。個体内では変異するが、変異は少し複雑。
個体はテストケースを複数含むテストスイートなので、まずテストケースの順序をランダムにシャッフルする。このシャッフルは交叉の際の多様性を増すことを目的にしている。シャッフルしたあとに2つの隣合うテストケース同士で, qの確率で1点交叉を行う。 更に、テストケース内のイベントをqの確率でシャッフルする。Atomic eventsにはパラメータがあるので、それを変異させることもできるが、操作を単純にするためにイベントの並び順を変えることにしたようだ。 繁殖は単にランダムに選ばれた個体をそのまま使うらしい。
淘汰(selection)にはNSGA-Ⅱというアルゴリズムを使う。雰囲気しか理解できてない。
個体の評価は、さっきも書いたが下記の組で行われる。
- カバレッジ
- テストシーケンスの短さ
- 発見したクラッシュの多さ
これらが最も望ましいケースと個体との距離で個体がランク付けされ、ランクが良いものが選ばれる。同じランクではより混雑距離が大きいものが選ばれる。混雑距離というのはランク内において隣接する個体への距離の和のこと。より混雑していないもののほうがが多様性を確保することができるため良い。
ここまで読むとなんとなく進化的アルゴリズムを適用してある程度良いものができそうな気がしてくる。むしろ、実際多様なアプリケーションに対してどのようにテストのイベントを生成するのかとかが気になってくるが、そのへんについては特に細かくは触れられてない。多分他にもテストを生成するツールは既にあるのでそこはこの論文にとってはあんまり重要ではないだろう。
ちなみにこのSapienzはDEAPという, python製の進化的アルゴリズムのためのフレームワークを使って構築されていたが、このDEAPはすごく簡単に使えて便利。自分の場合使う予定は特にないけど。NSGA-Ⅱを用いた多目的最適化とかもできる様子。
参考にした資料など
基本的に検索してすぐ出てきたもの
- wikipedia https://ja.wikipedia.org/wiki/%E9%80%B2%E5%8C%96%E7%9A%84%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0
- http://mikilab.doshisha.ac.jp/dia/monthly/monthly2014/mlm152/syagami/syagami.pdf
- https://www.sist.ac.jp/~suganuma/kougi/other_lecture/SE/opt/GA/GA.htm
- http://tech.mof-mof.co.jp/blog/ga-one-max-problem.html
- https://qiita.com/neka-nat@github/items/0cb8955bd85027d58c8e
- DEAP https://deap.readthedocs.io/en/master/index.html
- Monkey https://developer.android.com/studio/test/monkey
- https://qiita.com/Taichi-Furukawa/items/a7a0982cc20a401133c0
- http://www.kurims.kyoto-u.ac.jp/~kyodo/kokyuroku/contents/pdf/1589-07.pdf
- http://mikilab.doshisha.ac.jp/dia/research/mop_ga/moga/3/3-5-5.html
- http://bulletin.soe.u-tokai.ac.jp/vol53_no1_2013/P22_28.pdf
setxkbmapがわからない
xmodmapがもうダメダメなので、 setxkbmap を使うことにした。
しかし、man を読んでも自分で設定する方法がよくわからん…
一応安定のArchLinux wikiで設定方法は載ってるんだが…
XkbOptionsに色々書き足すか、設定ファイルを書き換えるかする方法。
後者はまぁもちろんそれは動くだろう。 /usr/share/X11/xkb以下にxkbで使われるcomponentが用意してある。それを書き換えればまぁ動く。結局わからなかったので最終的にこれで解決した。
前者は、ubuntuだと/etc/default/keyboardのXKBOPTIONSを書き換えてdpkg-reconfiugreすることになる。
これはまぁ動くんだろうけど、問題はoption(というかsymbol)をどうやって自分で定義し、システムに読み込ませるか。
rulesファイルにoptionに関する記述はあり、caps lockを無効にしてctrlとして使うオプションであるctrl:nocapsの定義とかを見て$HOME以下に書いてみたりしたんだけどうーんうまくいかない。
-Iでinclude pathを指定する方法でうまくいかないって言ってる人はいるみたい。
ちなみに、 /etc/gdm3/XSession で $HOME/.Xkbdmap が使われるようになっているので、このファイルで -model ctrl:nocaps を指定してみたが効かず。
これは多分、起動後にコマンド叩くのを自動化する系の解決法の場合によく見る sleep 入れないとだめっていう問題のような気がする。わからんけど。
結局何もわからなかった
まぁ こういう方法→Ubuntu 16.04 で XKB を使ってキーマップをカスタマイズする を使えば自分でいくらでも設定をかけるはずだが、なんかなぁ。自分で定義したオプションをさくっと追加してオプションを追加するだけの方法が知りたかった