gdmでwaylandを有効にしているときに起こるマウスカーソルの問題について

状況

僕は4kディスプレイ1枚とFullHDのディスプレイ1枚を所有しています。
4kディスプレイを3840x2160のまま使うのはつらいので.xprofilexrandr --output DisplayPort-0 --scale 0.8x0.8みたいなことをしていました。
wmはxmonadを利用していました。

症状

症状としてはログインしたときにマウスカーソルが画面上に残ってしまうというもの。
wmの問題かと思ってi3, awesome, xmonadを試してみたがダメだった。
画面上にマウスカーソルが残ってしまうのはxrandrでscaleオプションを設定したときだけでした。

原因

原因としてはgdmが使っているwaylandのバックエンド。
Bug 1199890 – Black flicker on gdm screen, affected by mouse

解決策

arch wikiのgdmのセクションに解決策がありました。
https://wiki.archlinux.jp/index.php/GDM#Xorg_.E3.83.90.E3.83.83.E3.82.AF.E3.82.A8.E3.83.B3.E3.83.89.E3.82.92.E4.BD.BF.E3.81.86
/etc/gdm/custom.confのWaylandEnable=falseの部分をアンコメントすることで直るようです。

DirtyCowはどのように動くか

github.com
DirtyCowについて調べたので理解するのに必要となる前提知識と一緒にPoCについての説明をまとめておきます。rootを取ったり、REHLで動くものがありますが今回は一番基本的な、権限のないファイルに書き込むPoC(dirtycow.github.io/dirtyc0w.c at master · dirtycow/dirtycow.github.io · GitHub)についてまとめます。

DirtyCowについて

DirtyCowはCopy on Writeの取り扱いにおいて競合状態が発生し、プライベートなメモリマッピングが破壊されるというものです。
CVEではCVE-2016-5195として管理されています。
JVNDB-2016-005596 - JVN iPedia - 脆弱性対策情報データベース


前提知識など

mmap

mmapはファイルやデバイスをメモリにマッピングするためのものです。
readやwriteを使うとカーネル空間からユーザープロセスへのコピーが発生しますが、mmapマッピングしたアドレスを返すだけでユーザー空間へのコピーが発生しません。
そのためreadやwriteを使うよりも効率期にファイルの読み書きが可能になるようです。
"なるようです"と書いたのはどの場合においてもmmapのほうが早いわけではないからです。
日本語で詳しく解説している記事があったのでどうぞ。
read(2) vs mmap(2) の迷信

man mmapしてでてくる情報を簡単にまとめます。
プロトタイプ宣言は次のようになっています。

void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);

addrにはマッピングするアドレスを指定します。NULLを指定するとマッピングするアドレスをカーネルが決定してくれます。
lenにはマッピングするサイズを指定します。
protにはPROT_EXEC, PROT_READ, PROT_WRITE, PROT_NONEを指定します。それぞれ、実行可能、読み書き可能、書き込み可能、アクセス不可能を表します。
flagsには、MAP_SHARED, MAP_PRIVATE, MAP_FIXEDが指定できます。MAP_PRIVATEが指定されるとCopy on Writeでファイルがマッピングされます。
fildesにはファイルディスクリプタを指定します。
offにはファイルのオフセットを指定します。

mmapを利用した簡単なファイルを読み込むをするサンプルを書いてみました。

#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[]){
  if (argc <= 1) {
    printf("please specify filename\n");
    return 1;
  }

  int fd = open(argv[1],O_RDONLY);
  if (fd < 0) {
    printf("file not found\n");
    return 1;
  }
  printf("file discriptor: %d\n", fd);

  struct stat st;
  fstat(fd, &st);
  printf("file size: %ld\n", st.st_size);

  char* map = mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,fd,0);
  printf("%s\n", map);

  return 0;
}

更に詳しい情報はman mmapで得られます。

Copy on Write

Copy on Writeとは書き込みが行われるまでは元から存在するマッピングを利用し、書き込みが起こったと同時にメモリ上に新たに複製が作られるという動作のことをいいます。
これによってforkするたびに、メモリ上に複製を作るより効率よくメモリを利用することができます。
書き込みによって作られた新たな複製は、他のプロセスからは見ることができません。
調べているときに日本語で詳しく説明されているサイトを見つけたのでもっと詳しく知りたい方はどうぞ。
コピーオンライト - Wikipedia
コピーオンライト - Linuxの備忘録とか・・・(目次へ)

ここで実験のために少しコードを書いてみようと思います。
先ほど説明したように、書き込みが行われるまでは同じマッピングを利用し、書き込みが行われるときに新たな領域が確保され、複製されます。
プロセスのファイルマッピング/proc/{pid}/smapsで確認することができます。
出力はだいたいこんなのが得られます。

7f0561870000-7f0561871000 r--p 00000000 08:23 6291502 /home/mute/dirtycow.github.io/dirtyc0w.c
Size: 4 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 0 kB
Pss: 0 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 0 kB
Anonymous: 0 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
VmFlags: rd mr mw me

これを利用してみます。
先程の説明通りなら、書き込みを行うまでは親プロセスと子プロセスのメモリマッピングは同じ、親プロセスか子プロセスどちらかで書き込むとメモリマッピングが変わるはずです。
次のようなコード書いて実験してみます。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>

#define IS_WRITE 1

int fd;
struct stat st;
void *map;

int main(int argc, char *argv[]){
  if (argc <= 1) {
    printf("please specify filename\n");
    return 1;
  }

  fd = open(argv[1],O_RDWR);

  if (fd < 0) {
    fprintf(stderr, "%s\n", strerror(errno));
    return 1;
  }

  fstat(fd, &st);

  // MAP_PRIVATE(copy on write)でマッピング
  map = (char *)mmap(NULL,st.st_size,PROT_WRITE,MAP_PRIVATE,fd,0);

  // 読み込み
  FILE *f = fopen("/dev/null", "w");
  fprintf(f, "%s\n", map);

  if (map == MAP_FAILED) {
    fprintf(stderr, "%s\n", strerror(errno));
    return -1;
  }

  int pid = getpid();
  printf("(main) pid: %d\n", pid);

  // -------------------
  // fork
  // -------------------
  int status;
  pid = fork();

  if (pid == 0) {
    pid = getpid();
    printf("(fork) pid: %d\n", pid);

    // 読み込み
    FILE *f = fopen("/dev/null", "w");
    fprintf(f, "%s\n", map);

    if (!IS_WRITE) sleep(100);

    // 書き込み
    strcat(map, "A");
    if (IS_WRITE) sleep(100);
  }

  wait(&status);
  // -------------------

  if (munmap(map, st.st_size) == -1){
    fprintf(stderr, "%s\n", strerror(errno));
    return -1;
  }
  return 0;
}

マッピングした領域に書き込む前にsleepを挟んでからコンパイルして実行します。
sleepに入ったらcat /proc/{pid}/smaps | grep -A 20 filenameして子プロセスのマッピングをみます。

7f89159e0000-7f89159e1000 -w-p 00000000 08:23 54 /home/mute/testfile
Size: 4 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 4 kB
Pss: 2 kB
Shared_Clean: 4 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 4 kB
Anonymous: 0 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 2 kB
VmFlags: wr mr mw me ac

Shared_Cleanが4kBになってます。
親プロセスと子プロセスで同じファイル触っているので共有されていますね。

書き込みをしてから親プロセスのマッピングを見てみます。

7f9063208000-7f9063209000 -w-p 00000000 08:23 54 /home/mute/testfile
Size: 4 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 4 kB
Pss: 4 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 4 kB
Referenced: 4 kB
Anonymous: 4 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 4 kB
VmFlags: wr mr mw me ac

Private_Dirtyが4kBになりましたね。
マッピングしていたファイルが新しいプロセスのためにメモリ上に新たに複製されました。

madvise

madviseはカーネルに、メモリのページング処理についてアドバイスを出すために使用します。

int madvise(void *addr, size_t length, int advice);

addrにはアドレスを、lengthにはサイズ、adviceにはカーネルに対するアドバイスを渡します。詳細はman madviseで。

DirtyCowではadviceにMADV_DONTNEEDが渡されています。
これは、しばらくは指定されたアドレスへのアクセスはなさそうなのでmmapしたメモリは一旦開放して良いということをカーネルに伝えるものです。
これによってmmapしたファイルは開放され、次に読み込むときは再びIOが発生し、メモリに読み込まれます。
元ファイルが削除されていた場合は0fillされます。

PoCコードの解説

github.com

PoCの大まかな動作としては、mainで読み込みたいファイルをmmap, それからmadviseThread, procselfmemThreadを各スレッドで動かしています。
madviseThreadではmadviseをループして何度も実行、procselfmemThreadでは自分のプロセスのメモリである/proc/self/memを開き、mmapされた場所に文字列を書き込み続けています。
この2つの動作が連続することにより競合状態が発生し、プライベートなメモリマップであるはずのものがディスクに書き込まれてしまいます。

これを理解する上で重要なのはページキャッシュの動作です。
ページキャッシュの動作として、読み込みが行われると、読み込んだものはキャッシュに置かれ、書き込みが行われると、即座に書き込みは行われず、キャッシュに書き込んだ上で適当なタイミングで書き込みが行われます。Linuxカーネルではキャッシュに書き込みを行うと書き込んだページにDirty bitが立てられます。
先ほど説明したmadviseとwriteを繰り返すと、writeを行ってからDirty bitを立てる前にmadviseによってページがディスクに書き込まれることによって、権限の無いファイルに書き込みができてしまうというわけです。

draw.ioで2秒くらいで書いた図によるとこんな感じです。

f:id:mute1997:20180310212543p:plain

最後に

本当はパッチを読んだりしてたんですがこれ以上長くなるとしんどいのでやめました。
DirtyCowのパッチには不十分で、THPを使用している場合にはCoWを用いることなくdirty bitを立てることができるらしいですね。
気が向いたらパッチを読みつつ不十分だった部分など解説します。

scikit-learnを使用した仮想通貨の相場学習

scikit-learnを使って雰囲気で機械学習をしたので覚書ということでまとめておきます。
github.com


使用したデータ

cryptowatchのapiを使用してbitflyerのJPY/BTCのデータを引っ張ってきました。
Public Market REST API - Cryptowatch

import requests
r = requests.get('https://api.cryptowatch.ch/markets/bitflyer/btcjpy/ohlc')
with open('bitflyer.json') as f:
  write(r.text)

 

前処理

このデータは '[終値, 始値, 高値, 安値, 終値, 量]'というふうにjsonでデータが入っているので学習させるために前処理をします。
前処理では[終値0, 終値1, 終値2... 終値9]というデータ、結果ラベルとして上がったら1, 下がってたら0というようなラベルを生成していきます。

def get_train_data():
    train_X = []
    train_y = []

    order_book_data = json.load(open(filename, 'r'))

    # 終値のみを取り出す
    prices = []
    for value in order_book_data['result']['60']:
        prices.append(value[1])
    print('data size:', len(prices))

    # データの生成
    input_amount = 10
    for index in range(0, len(prices) - input_amount, input_amount):
        x = prices[index:index+input_amount]
        y = 1 if x[-1] < prices[index+input_amount+1] else 0 # 上がってたら1, 下がってる or 同じだったら0
        train_X.append(x)
        train_y.append(y)

    return np.array(train_X), np.array(train_y)

評価

まずは価格データをそのまま学習させても良い結果はでないのでscale_standard関数を使って標準化します。標準化について簡単に説明するとデータを一定の規則について加工したもののことです。
今回の価格データを例に取ると100円, 200円, 150円という価格の値動きを学習するよりも、前のデータよりどれくらい利益がでたかとか何%上がったかなどの相対的な値を学習させたほうが分類においていい結果がでます。

標準化したときとしてないときの分類精度を実験してまとめてるブログがあったので参考までに。
ailaby.com

標準化したあとはcross_val_score関数を用いてスコアを検証します。
これは交差検証というもので、データを分割してモデルの汎化性能を測ります。
なぜこうするかと言うと、テストデータと学習データを分けてしまうとテストデータに対する性能しか測れないからです。
交差検証については以下で詳しく説明されているのでどうぞ。
交差検証 - Wikipedia

train_features, train_labels = get_train_data()

# train_Xとtest_Xの標準化
train_X = scale_standard(train_features)

clf = MLPClassifier()

scores = cross_val_score(clf, train_X, train_labels, cv=5)
print("Scores: ", scores)
print("Accuracy: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))

結果

以下が結果になります。

[~/bitcoin-prediction-py]$ python main.py
data size: 6000
Scores: [ 0.47933884 0.36666667 0.55833333 0.5210084 0.51260504]
Accuracy: 0.49 (+/- 0.13)

うーん、微妙。
ほとんど50%になってますね。ランダムに売買しても大差ない...。

まとめ

結構しょっぱい結果が出てしまいました。こうした方が精度上がるよというのがあれば是非おしえていただきたいです。
今後の改善方法としては海外の取引所のデータを使ってみるというのを考えています。どこかのチャットで、日本の取引所は海外の取引を基本的に追随して動いているという話を聞いたことがあります。本当かどうかはわからないですが、暇なときに試してみようと思います。

Nyquist keyboard build log [rev 1.5]

はじめに 

Nyquistキーボードを作ったので僕の失敗談を交えつつBuild logを書いていこうと思います。これを読んだ皆さんはしょうもないことでミスって基板を書い直さなくて良いようにBuild logを穴が開くほど読んでください。

それと僕は入ってないんですが自作キーボードDiscordなるものがあるらしいのでわからなくなったらそういうところで質問するのもいいと思います。

それではどうぞ。

 

f:id:mute1997:20180304003300j:plain

 

 

 

準備編

必要な部品

  • キーキャップ (aliexpress)

特に説明いらずのキーキャップです。今使ってるキーボードから剥ぐなりaliexpressで輸入するなりお好きにどうぞ。

f:id:mute1997:20180304004318j:plain

  • キースイッチ (aliexpress)

キーの下に入ってるやつですね。cherry MX軸以外にも互換軸が結構色々あるので探してみると面白いです。これに関しては5ピンと3ピンと2つ種類がありますがNyquistはどっちも刺さります。変わるのは安定感のみなのでお好みでどうぞ。

f:id:mute1997:20180304004317j:plain

  • 基板 (keeb.io)
  • ケース (keeb.io)

Nyquistの基板をくっつけるところです。僕はkeeb.ioで買いましたが3dプリンターで出力してる人もいるようです。

  • ProMicro x 2 (keeb.io)

Nyquistの基板にくっつけるマイコンです。ぶっ壊してしまって買い直している人もいるようなので余分に買っておいても良いかもしれません。

  • TRRSケーブル x 1 (amazon)

分割jしたキーボードをつなぐケーブルです。keeb.ioにも売ってはいるんですが短いと思ったのでamazonで1mのTRRSケーブルを買いました。

 

  •  Micro USBケーブル x 1 (keeb.io) 

あったら良いものとか

  • クッションゴム

これはケースの裏につけるためのものです。なくてもいいですがkeeb.ioで売ってるケースの底はネジなので安定感はあんまりないです。なくてもいいですがあると滑り止めにもなるのでおすすめです。 

 

3M しっかりつくクッションゴム 12.7x3.6mm 台形 16粒 CS-05

3M しっかりつくクッションゴム 12.7x3.6mm 台形 16粒 CS-05

 
  • セメダイン

これはProMicroの端子周りを固定するものです。僕はまだもげたりしてないんですがProMicroの端子がもげやすいらしく、調べてるときにも結構もげてる人を見かけました。ProMicroの交換は結構無理な感じ(後述)なので買っておいたほうがいいです。

 

  • 4.7kΩの抵抗

これはキーボードのI2C通信に使うものです。僕もよくわかってないんですがI2Cで通信するとLEDが使えなくなる代わりに通信速度が早くなるそうです。ただ僕はシリアルで通信していて全く遅延を感じてないのでなくてもいいとは思います。

(ちなみに僕が買った基板には抵抗が2個ついてきたんですが、4個必要だと思って部品なくした!って騒いでました。実際は2個を片面に実装するだけで大丈夫らしい。)

 

作るに当たって必要になる道具

  • 半田ごて一式
  • ハンダ吸い取り線

ハンダ吸い取り線は必須ではないですがミスするとめんどうなので一応持っておくのがいいです。

 

ハマりポイント

ここからが重要なハマりポイントの説明になります。僕がハマったポイント、調べてるときによく見かけたハマりポイントを列挙して説明するので8億回くらい読んでください。

電気電子の人からしたら何言ってるんだという感じかもしれませんがダイオードは向きがあります。ダイオードのカソード(黒い帯がついてる方)はNyquistの基板のパターンが四角になってる方に実装しないといけないです。ダイオードを取り外すのはそこまで難しくないですが、なにぶん数が多いので間違って実装すると面倒です。

  • ProMicroをつける順番

これは僕がハマったやつです。ちゃんとBuild Log読んでたら絶対起こり得ないことだとは思うんですが抵抗なくしてクソーっていいながら作業してたら間違えました。これはProMicroの方を先につけてしまうとキースイッチをつけられなくなってしまうという簡単なことです。ただこれを間違えるとProMicroの取り外しとかいう地獄みたいな作業が待ってるので絶対にミスしないことをおすすめします。ちなみに僕はProMicroのピンヘッダ取るの諦めて基板を書い直しました。

下はProMicroを先に取り付けてしまってキースイッチを取り付けられないの図。

f:id:mute1997:20180304005229j:plain

 

  • ProMicroの端子

調べてるときに結構もげてる画像を見かけました。先ほど説明したようにProMicroのピンヘッダの取り外しは本当に地獄なので端子がとれてProMicroの交換をしなくていいように接着剤で固めるのをおすすめします。

  • ProMicroのJ1

僕のProMicroはそうではなかったのですがたまにProMicroのJ1がブリッジされててうまく動作しないことがあるようです。ブリッジされてるようならハンダ吸い取り線などで取ってください。

  • ProMicroの裏表

両方同じ向きで実装しちゃダメらしいです。(自分はミスしてないので両方同じ向きで実装するとどうなるのかわかってない)

  • ハンダの盛りすぎ

これは他の人が書いているのは見たことはないけど自分が少し困ったこと。ハンダを盛りすぎていてキースイッチが奥まで押し込めなかった。(隙間からハンダ入れて少し吸った)キースイッチの実装をする前に基板全体のハンダ盛りすぎているところをチェックすると良いと思います。

 

How to build

簡単な作業ログです。

作ってる途中にあんまり写真取ってなくて画像が少ないのですがご了承ください。

ダイオードの取り付け

まずはダイオードの取り付けです。ここで注意すべきは先ほど説明したダイオードの向きです。必ず黒い方をパターンが四角になっているところに実装しましょう。

f:id:mute1997:20180304003509j:plain

TRRS, ピンヘッダ、リセットスイッチの取り付け

画像がなくて申し訳ないんですがパパっと実装するだけという感じです。気をつける点としては同じ向きに実装してしまわないということくらいですかね。

キースイッチの取り付け 

 !!! ProMicroの取り付け前に必ず行ってください。

この作業自体で特に注意することはないです。四隅から実装して浮かないように実装していくだけです。

ProMicroの取り付け

ここまでくればもうほとんど終わったようなものです。ここで注意するのはProMicroの裏表です。同じ向きに実装してしまわないように気をつけて画像のように実装しましょう。

f:id:mute1997:20180304003449j:plain

ProMicroにqmkを書き込む

今回はarchlinuxについてのみ説明します。Mac, Ubuntuとかは公式のドキュメントに従うだけなのでドキュメントよんでください。

$ git clone https://github.com/qmk/qmk_firmware.git

$ pacman -S --noconfirm base-devel avr-gcc avr-binutils arv-libc dfu-util arm-none-eabi-gcc arm-none-eabi-binutils arm-none-eabi-newlib git diffutils avrdude

$ cd qmk_firmware

$ sudo make nyquist:default:avrdude

 だいたいこんな感じです。最後のコマンドの実行時にリセットを求められるので取り付けたボタンを押してください。ここまでの作業で問題がなければ無事書き込まれるはずです。

あとはqmk_firmware/keyboards/nyquistにあるプロファイルをいじってお好きなキー配置を楽しんでください。

最後に

初の自作キーボード、失敗したりもしましたが総じて楽しかったです。他にも色々種類があるようなので作ってみたさがある。

そういえば調べているときにHoltite Socketなるものを見つけて気になっているので次回作る機会があればこういうのにも挑戦してみたい。

riv-mk.hateblo.jp

MacOSのシステムコール呼び出しでの"Bus Error: 10"

MacOSで64bitのシステムコールを呼ぼうとした時に気になったことがあったのでメモ。

 

MacOSシステムコール

https://opensource.apple.com/source/xnu/xnu-1504.3.12/bsd/kern/syscalls.master

これを見るとexitシステムコールは1なのでアセンブリで書くとこんな感じになる。

これをコンパイルしてリンクすると 

$ nasm -f macho64 -o nasm.o nasm.asm

$ ld nasm.o -e _start -o nasm

$ ./nasm

Bus error: 10 

なんでだろうと思って調べてみたら以下の記事が見つかった。

TheXploit | Mac OS X 64 bit Assembly System Calls

Mac OS X 64bitでシステムコール - 七誌の開発日記

書いてある通りなのだけどMacOSではシステムコール呼び出すにはクラス番号を足す必要があるらしい。

正しくは以下。

 

ちょっとだけ解説

先ほどのMac OS X 64 bit Assembly System Callsに書いてあるのを適当に訳してみる。

https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h

BSDではシステムコールはクラスごとに分かれていて、上位ビットはクラスを表すらしい。書き込みと終了はSYSCALL_CLASS_UNIXに分類され、0x2000000 + unix_syscallとなる。なので0x2000001が終了のためのシステムコール

技術書典2に行ってきました

f:id:mute1997:20170409170908j:plain

技術書典2に行ってきました。おわり。