「車輪の再実装」って言葉が好き(実践はできてない)

Linux (Ubuntu 18.04) からUTokyo WiFiに接続する (GUI編+CLI編)

UTokyo WiFiの公式ページの記載だけでは、Ubuntuからうまく接続できなかったので、 またOSをふっ飛ばして再インストールするであろう将来の自分に向けて接続方法のメモ

GUI

f:id:progrunner17:20191112171005p:plain
設定例

以下のように設定すれば接続できるはず。

設定項目 設定値
Wi-Fi security WPA & WPA2 Enterprise
Authentication 保護付きEAP(PEAP)
Anonymous identity 学生証に書かれた10桁の番号@utac.u-tokyo.ac.jp
Domain 空のまま
CA証明書 Security Communication RootCA2 を選択(詳細後述)
PEAP version 自動
Inner authentication MSCHAPv2
Username 発行されたid
Username 発行されたパスワード

なお、Ubuntu 18.04 (Desktop)の場合

/usr/share/ca-certificates/mozilla/Security_Communication_RootCA2.crt に 必要な証明書がすでに存在するので、それをCA証明書の欄で選択する。

CLI

NetworkManager(nmcliコマンド)を用いる。 以前Lubuntuを使った際はデフォルトのGUIの設定からは設定できなかったが、このコマンドでは設定可能だったので、汎用性も高そう

$ ip a | grep w # wifiのデバイスのインターフェース名の調査
2: wlo1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN group default qlen 1000
# たいていwifiデバイスのifnameは wから始まる気がする。
# ここではwlo1を使用
$ nmcli connection add type wifi ifname wlo1 con-name UTOKYO_WIFI ssid UTokyo-WiFi
$ nmcli con edit id UTOKYO_WIFI # con は connection の短縮。 c1文字でもOK
nmcli> set ipv4.method auto
nmcli> set 802-1x.eap peap
nmcli> set 802-1x.anonymous-identity 10桁の番号@utac.u-tokyo.ac.jp
nmcli> set 802-1x.phase2-auth mschapv2
nmcli> set 802-1x.ca-cert /usr/share/ca-certificates/mozilla/Security_Communication_RootCA2.crt 
nmcli> set 802-1x.identity 発行されたID
nmcli> set 802-1x.password 発行されたパスワード
nmcli> set wifi-sec.key-mgmt wpa-eap
nmcli> save
nmcli> activate
nmcli> quit

なお、nmcliのコマンドについては以下も知っておくと良さそう。

$ # 設定一覧の表示
$ nmcli connection show
$ # 設定の有効化(wifiへの接続)
$ nmcli connection up 設定名 # 上記の設定の場合、設定名は UTOKYO_WIFI
$ # 設定の非有効化(wifiの切断)
$ nmcli connection down 設定名 # 上記の設定の場合、設定名は UTOKYO_WIFI

nmcli con showでみた限り、この設定名(con-name)、たいていはssid名が直接使われていそうな気がするし、そうした方が良かったかも?

この他、nmcliを使用した(wifiの)設定方法は以下を見ると良さそう
access.redhat.com

netplan(追記)

netplan.io

Ubuntu18.04等だとnetplanでも設定できるらしい? なんとなく、yamlに平文でパスワードを書くのは気が引けるけど、nmcliなら良いかというとそれもわからないし、結局は好みなのかな...?

ネットワークへの接続状況を監視してみる (Prometheus on Docker on ラズパイ)

github pagesを畳んだので、そこにあった記事の転載

現在一人暮らし中なので、自宅の回線はモバイルルーターを契約しているのだが、近頃回線がものすごく遅くなるときがある。 google.comとか1.1.1.1とかにpingを飛ばすとロス率が70%を越したり、レイテンシが500msとかになったりする。今どき地球の裏側の方がちゃんと繋がりそうだなぁなんて思いつつ、いい機会なので死活監視をしてみようと思うので記録。

ところでレイテンシといえば、Latency Numbers Every Programmer Should Knowってページを思い出した。ちなみにこのページによると、CA(カリフォルニア?)とオランダ間でのRTTは150ms程らしい。この手のレイテンシとかのオーダーの話は稀によく耳にするけど、いまいち感覚が身についている気がしない。ちゃんと覚えないとなぁ...

使用するツール

  • Prometheus
  • Docker / Docker Compose
  • RaspberryPi(3B+)

Prometheus

監視ツールというとDatadogとかMackerelとかを聞くことが多い気がするけど、そもそも内側から見たいのでこれらを含めたSaaSは候補から除外、OSSが好きなのと、勢いがありそうだったので?Prometheusを使うことにした。

Docker/ Docker Compose

PrometheusはGolang製の単一バイナリでそのまま動くらしいので使う理由もあまりないけど、単純に趣味

強いて理由を上げるとすれば、そのうちデータベースと連携してデータを保存したりする場合にまとめて管理するのが楽そうかな〰という気分。

RaspberryPi(3B+)

24時間監視しようと思うと、普段遣いのノートPCだと辛いし、消費電力とかもヤバそうなので、ラズパイを使うことに決定。

1. ラズパイの設定

しばらく使ってなかったら、調子が悪くなっていたので、OSをクリーンインストール

  • OSをインストール

  • パスワード設定

  • SSHを有効化

  • Dockerのインストール

cf. 最近のRaspberry Piイメージ(Raspbian)をインストールするメモ

今回初めて知ったけれど、mDNSという仕組みがあり、それを使えばIPを固定しなくても同じLANでraspberrypi.localという名前でラズパイを参照できるらしい、これまで何度か /etc以下設定ファイルを書き換えたりしてたけど、そんな必要なかったのか〰(便利なので良いけれど)。

2. Prometheusを動かしてみる

まずはPrometheusの概要を学んでみようということで、Qiitaで以下の記事を発見

10分で理解する Prometheus

Prometheusは雑に解釈すると、以下の2種類のプログラムからなるらしい

  1. exporter:

    • 監視対象のサーバーで動作するプログラム。
    • 監視するリソース毎に準備
    • WebAPIの感覚で、情報をテキストベースで公開する
  2. prometheus:

    • 監視サーバーのプログラム

    • 定期的に巡回してexporterから情報を収集する

というわけで、早速試してみる。

PrometheusのGETTING STARTED

を参考にしつつ、Dockerを使いたいのでPrometheusのDockerイメージを落としてくる。

 $ docker pull prom/prometheus
 $ docker inspect prom/prometheus
 ~~~前略~~~
             "Cmd": [
                "--config.file=/etc/prometheus/prometheus.yml",
                "--storage.tsdb.path=/prometheus",
                "--web.console.libraries=/usr/share/prometheus/console_libraries",
                "--web.console.templates=/usr/share/prometheus/consoles"
            ],
            "ArgsEscaped": true,
            "Image": "sha256:b452ed1aa226459ac89e04859fa746c0f15861534109fc0c9155596f0244ec22",
            "Volumes": {
                "/prometheus": {}
            },
            "WorkingDir": "/prometheus",
            "Entrypoint": [
                "/bin/prometheus"
            ],
~~~後略~~~

なんとなくPrometheusの使い方がわかったところで、

Getting Started に従い、prometheus.ymlを作成

$ mkdir -p ~/workspace/prometheus
$ cd ~/workspace/prometheus
$ cat > prometheus.yml <<EOF
global:
  scrape_interval:     15s
  external_labels:
    monitor: 'codelab-monitor'
scrape_configs:
  - job_name: 'prometheus'
    scrape_interval: 5s
    static_configs:
      - targets: ['localhost:9090']
EOF

というわけで、さて動かすぞ!と思ったらエラー

$ docker run -p 9090:9090 -v prometheus.yml:/etc/prometheus.yml prom/prometheus
standard_init_linux.go:207: exec user process caused "exec format error"

いろいろ試してみたけど、動かない。

多分イメージがx86向けなんだなぁ(動くものしかpullできないと思ってた...)

というわけで、自分でイメージを作って実行してみる

# dockerfile
FROM armhf/alpine AS build-env

RUN apk update && apk add ca-certificates wget &&\
      wget https://github.com/prometheus/prometheus/releases/download/v2.9.2/prometheus-2.9.2.linux-armv7.tar.gz &&\
      tar -xzf prometheus-2.9.2.linux-armv7.tar.gz

FROM scratch
COPY --from=build-env /prometheus-2.9.2.linux-armv7/* /
ENTRYPOINT ["/prometheus"]
CMD ["--config.file=/prometheus.yml",\
     "--storage.tsdb.path=/data",\
     "--web.console.libraries=/usr/share/prometheus/console_libraries",\
     "--web.console.templates=/usr/share/prometheus/consoles"\
     ]
$ mkdir build
$ cd build
$ vim Dockerfile # 上の内容を記述
$ docker build . -t arm-prometheus
$ docker run --rm -p 9090:9090 arm-prometheus

raspberrypi.local:9090を開いてみる。

f:id:progrunner17:20191014134706j:plain

良さそうだ

Exporterを導入する

Prometheusの概要のところでも少し触れたが、Prometheusでは、監視するリソース毎にexporterと呼ばれるプログラムを動かす。

今回はGithubで公開されているczerwonk/ping_exporterを使ってみる。

こちらは本当にconfigなしでも動くので、とりあえず動かしてみる。

$ wget https://github.com/czerwonk/ping_exporter/releases/download/0.44/ping_exporter-0.4.4_linux_arm
$ mv ping_exporter-0.4.4_linux_arm ping_exporter
$ chmod +x ping_exporter
$ ./ping_exporter 1.1.1.1
$ curl localhost:9427/metrics
# HELP ping_loss_percent Packet loss in percent
# TYPE ping_loss_percent gauge
ping_loss_percent{ip="1.1.1.1",ip_version="4",target="1.1.1.1"} 0
# HELP ping_rtt_best_ms Best round trip time in millis
# TYPE ping_rtt_best_ms gauge
ping_rtt_best_ms{ip="1.1.1.1",ip_version="4",target="1.1.1.1"} 88.44157409667969
# HELP ping_rtt_mean_ms Mean round trip time in millis
# TYPE ping_rtt_mean_ms gauge
ping_rtt_mean_ms{ip="1.1.1.1",ip_version="4",target="1.1.1.1"} 188.0380859375
# HELP ping_rtt_ms Round trip time in millis (deprecated)
# TYPE ping_rtt_ms gauge
ping_rtt_ms{ip="1.1.1.1",ip_version="4",target="1.1.1.1",type="best"} 88.44157409667969
ping_rtt_ms{ip="1.1.1.1",ip_version="4",target="1.1.1.1",type="mean"} 188.0380859375
ping_rtt_ms{ip="1.1.1.1",ip_version="4",target="1.1.1.1",type="std_dev"} 84.13036346435547
ping_rtt_ms{ip="1.1.1.1",ip_version="4",target="1.1.1.1",type="worst"} 348.2742004394531
# HELP ping_rtt_std_deviation_ms Standard deviation in millis
# TYPE ping_rtt_std_deviation_ms gauge
ping_rtt_std_deviation_ms{ip="1.1.1.1",ip_version="4",target="1.1.1.1"} 84.13036346435547
# HELP ping_rtt_worst_ms Worst round trip time in millis
# TYPE ping_rtt_worst_ms gauge
ping_rtt_worst_ms{ip="1.1.1.1",ip_version="4",target="1.1.1.1"} 348.2742004394531

良さそうだ。

exporterとprometheusを連携するため、prometheus.ymlを書き換える

global:
  scrape_interval:     15s
  external_labels:
    monitor: 'codelab-monitor'
scrape_configs:
  - job_name: 'prometheus'
    scrape_interval: 5s
    static_configs:
      - targets: ['localhost:9090']
  - job_name: 'ping_exporter'
    scrape_interval: 5s
    static_configs:
      - targets: ['172.17.0.1:9427']

さて、ここの172.17.0.1は実際にはlocalhostを指すのだが、詳しい説明は長くなるので省略。

(dockerのネットワークのホストやブリッジ等を調べるとよい) (なお、実行時に、 --network="host"というオプションをつけると、localhostでも接続可能 )

さて、もう一度prometheusを実行してみる。

$ docker run --rm  -p 9090:9090 -v prometheus.yml:/prometheus.yml arm-prometheus

raspberrypi.local:9090を開き、ping_rtt_msをクエリとしてグラフを表示させてみると以下のようになった。

f:id:progrunner17:20191014134744j:plain

うまく表示できた。

しばらくpingのデータだけだと物足りない気もするが、疲れたので今日はおしまい。

しばらくデータを記録してみようとおもう。

git bisectを用いてsyscallテーブルの表記変更コミットを探す

linuxシステムコールを追加しようと思ったりすると、 arch/x86/entry/syscalls/syscall_64.tbl にsys*と書くと思っていたのですが、最近は__x64_sys*という表記に変わっていることに気づきました。

例えばLinux v4.15ではシステムコールテーブルは以下の様になっています。

# 64-bit system call numbers and entry vectors
#
# The format is:
# <number> <abi> <name> <entry point>
#
# The abi is "common", "64" or "x32" for this file.
#
0   common  read            sys_read
1   common  write           sys_write
2   common  open            sys_open
3   common  close           sys_close
4   common  stat            sys_newstat
5   common  fstat           sys_newfstat
6   common  lstat           sys_newlstat
7   common  poll            sys_poll
8   common  lseek           sys_lseek
9   common  mmap            sys_mmap
10  common  mprotect        sys_mprotect

ただ最近(v5.0)このテーブルを見直したら以下のように変更されていました。

#
# 64-bit system call numbers and entry vectors
#
# The format is:
# <number> <abi> <name> <entry point>
#
# The __x64_sys_*() stubs are created on-the-fly for sys_*() system calls
#
# The abi is "common", "64" or "x32" for this file.
#
0   common  read            __x64_sys_read
1   common  write           __x64_sys_write
2   common  open            __x64_sys_open
3   common  close           __x64_sys_close
4   common  stat            __x64_sys_newstat
5   common  fstat           __x64_sys_newfstat
6   common  lstat           __x64_sys_newlstat
7   common  poll            __x64_sys_poll
8   common  lseek           __x64_sys_lseek
9   common  mmap            __x64_sys_mmap
10  common  mprotect        __x64_sys_mprotect

git bisect というコマンドを思い出し、良い練習問題だと思って調べてみることにしました。

manのドキュメントの言葉を借りると、git bisectは

git-bisect - Use binary search to find the commit that introduced a bug

すなわち、ある段階まで動いていたコードにどの段階でバグが入り込んだかを調べる機能です。

テストスクリプトを走らせて*12分探索で該当コミットを探してくれるので、一つずつさかのぼって確認するのと比べ、高速で探索が可能です。 今回はバグではありませんが、以下のテストスクリプト(test.sh)を使い、__x64という表記が混入する場所を探ることにしました。

#!/bin/sh
set -eu

egrep "\ssys_open" arch/x86/entry/syscalls/syscall_64.tbl

git bisect ではまず、該当コミットがある範囲を決めます。

git bisect run <bad/new-commit> <good/old-commit>

今回、v5.0ではx64_sys*という表記となり、v4.15ではsys*という表記であることがわかっているので、以下のように実行しました。

git bisect start v5.0 v4.15

Bisecting: 44096 revisions left to test after this (roughly 16 steps)
[93b9bcdf9fbcb683d4e8c44ee8cec0989053d4de] btrfs: remove unused parameter from btrfs_parse_subvol_options

「だいたい16ステップで終わるよ」と教えてくれるのは便利。

次に、実際に探索を行いたいと思います。

git bisect run ./test.sh
running ./test.sh
Bisecting: 22106 revisions left to test after this (roughly 15 steps)
[38047d5c269bbdedf900fc86954913f3dffa01f1] Merge tag 'driver-core-4.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 10700 revisions left to test after this (roughly 14 steps)
[135c5504a600ff9b06e321694fbcac78a9530cd4] Merge tag 'drm-next-2018-06-06-1' of git://anongit.freedesktop.org/drm/drm
running ./test.sh
Bisecting: 5696 revisions left to test after this (roughly 13 steps)
[a72db42cee37a43f8a40e1f47358ac86921ad8e4] Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
running ./test.sh
Bisecting: 2942 revisions left to test after this (roughly 12 steps)
[28da7be5ebc096ada5e6bc526c623bdd8c47800a] Merge tag 'mailbox-v4.17' of git://git.linaro.org/landing-teams/working/fujitsu/integration
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 1471 revisions left to test after this (roughly 11 steps)
[5504ed29692faad06ea74c4275e96a8ffc83a1e1] mm/hmm: do not differentiate between empty entry or missing directory
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 733 revisions left to test after this (roughly 10 steps)
[80a17a5f501ea048d86f81d629c94062b76610d4] Merge tag 'apparmor-pr-2018-04-10' of git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 373 revisions left to test after this (roughly 9 steps)
[ba2b137d10bafc3cc514e52172b549e64a5402fb] Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 193 revisions left to test after this (roughly 8 steps)
[f0d98d85831bf1a3b1f56f8c14af60797aaca536] Merge tag 'scsi-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/jejb/scsi
running ./test.sh
Bisecting: 71 revisions left to test after this (roughly 7 steps)
[174e719439b8224d7cedfbdd9529de396cac01ff] Merge branch 'perf-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 30 revisions left to test after this (roughly 5 steps)
[9fb71c2f230df44bdd237e9a4457849a3909017d] Merge branch 'x86-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
running ./test.sh
Bisecting: 18 revisions left to test after this (roughly 4 steps)
[6b0a02e86c293c32a50d49b33a1f04420585d40b] Merge branch 'x86-pti-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 9 revisions left to test after this (roughly 3 steps)
[5ac9efa3c50d7caff9f3933bb8a3ad1139d92d92] syscalls/core, syscalls/x86: Clean up compat syscall stub naming convention
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[d94a155c59c98c19b98ee949eaab6a0312bbd6be] x86/cpu: Prevent cpuinfo_x86::x86_phys_bits adjustment corruption
running ./test.sh
2   common  open            sys_open
257 common  openat          sys_openat
304 common  open_by_handle_at   sys_open_by_handle_at
Bisecting: 2 revisions left to test after this (roughly 1 step)
[c76fc98260751e71c884dc1a18a07e427ef033b5] syscalls/x86: Adapt syscall_wrapper.h to the new syscall stub naming convention
running ./test.sh
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[d5a00528b58cdb2c71206e18bd021e34c4eab878] syscalls/core, syscalls/x86: Rename struct pt_regs-based sys_*() to __x64_sys_*()
running ./test.sh
d5a00528b58cdb2c71206e18bd021e34c4eab878 is the first bad commit
commit d5a00528b58cdb2c71206e18bd021e34c4eab878
Author: Dominik Brodowski <linux@dominikbrodowski.net>
Date:   Mon Apr 9 12:51:44 2018 +0200

    syscalls/core, syscalls/x86: Rename struct pt_regs-based sys_*() to __x64_sys_*()
    
    This rename allows us to have a coherent syscall stub naming convention on
    64-bit x86 (0xffffffff prefix removed):
    
     810f0af0 t            kernel_waitid    # common (32/64) kernel helper
    
     <inline>            __do_sys_waitid    # inlined helper doing actual work
     810f0be0 t          __se_sys_waitid    # C func calling inlined helper
    
     <inline>     __do_compat_sys_waitid    # inlined helper doing actual work
     810f0d80 t   __se_compat_sys_waitid    # compat C func calling inlined helper
    
     810f2080 T         __x64_sys_waitid    # x64 64-bit-ptregs -> C stub
     810f20b0 T        __ia32_sys_waitid    # ia32 32-bit-ptregs -> C stub[*]
     810f2470 T __ia32_compat_sys_waitid    # ia32 32-bit-ptregs -> compat C stub
     810f2490 T  __x32_compat_sys_waitid    # x32 64-bit-ptregs -> compat C stub
    
        [*] This stub is unused, as the syscall table links
            __ia32_compat_sys_waitid instead of __ia32_sys_waitid as we need
            a compat variant here.
    
    Suggested-by: Ingo Molnar <mingo@kernel.org>
    Signed-off-by: Dominik Brodowski <linux@dominikbrodowski.net>
    Cc: Al Viro <viro@zeniv.linux.org.uk>
    Cc: Andrew Morton <akpm@linux-foundation.org>
    Cc: Andy Lutomirski <luto@kernel.org>
    Cc: Borislav Petkov <bp@alien8.de>
    Cc: Brian Gerst <brgerst@gmail.com>
    Cc: Denys Vlasenko <dvlasenk@redhat.com>
    Cc: Josh Poimboeuf <jpoimboe@redhat.com>
    Cc: Linus Torvalds <torvalds@linux-foundation.org>
    Cc: Peter Zijlstra <peterz@infradead.org>
    Cc: Thomas Gleixner <tglx@linutronix.de>
    Link: http://lkml.kernel.org/r/20180409105145.5364-4-linux@dominikbrodowski.net
    Signed-off-by: Ingo Molnar <mingo@kernel.org>

:040000 040000 3f963b85ebd00a03e36f36f6417684a64da0b8dc e83d401c538f34cb271d6c0ae3c8563f43db6a5f M  arch
bisect run success

うまく行きました。

これで、ちょうど”バグ”が入り込んだコミット。 すなわちここでは_x64_sys*という表記に変わったコミットにチェックアウトした状態となっています。 git diff HEAD~とすると

前略
-0      common  read                    sys_read
-1      common  write                   sys_write
-2      common  open                    sys_open
-3      common  close                   sys_close
-4      common  stat                    sys_newstat
-5      common  fstat                   sys_newfstat
中略
+0      common  read                    __x64_sys_read
+1      common  write                   __x64_sys_write
+2      common  open                    __x64_sys_open
+3      common  close                   __x64_sys_close
+4      common  stat                    __x64_sys_newstat
+5      common  fstat                   __x64_sys_newfstat
後略

となっていたので、このコミット(d5a00528b58cdb2c71206e18bd021e34c4eab878)で間違いなさそうです。

さて、このコミットがどのバージョンかですが、 git merge-base --is-ancestor v4.16 HEAD に成功して、git merge-base --is-ancestor v4.17 HEADに失敗したのでv4.17から変更が入っていそうです。

おわり

*1:このコミットは良い、悪いをスクリプトではなく手動で指定することもできそうです

カーネルモジュールのロードに失敗した話(insmod が Operation not permittedでコケる)。

結論

セキュアブート環境下*1では、カーネルモジュールに署名をつけましょう

経緯

卒論の関係でカーネルモジュールを書いていて、「さて実機で動かすぞ!」という段階でコケました。 自分が悪いコードを書いたせいかと思い、よく転がっていそうな単純なモジュールを書いても以下のようにコケる...

$ sudo insmod hello.ko
 insmod: ERROR: could not insert module hello.ko: Operation not permitted

解決策を調べると、insmodでうまくいかないなら、depmodしてmodprobeを使うといいとか書いてあって試しても、うまく行かない...

最終的には How to sign things for Secure Boot に書いてあるとおり、署名をつけたら成功しました。

(結局カーネルモジュールは動いたものの、思うような結果が得られず卒論には使えなかった...)

*1:厳密な条件は違いそう

gccで本当に使われているデフォルトリンカスクリプトを手に入れる。

結論

ld --verboseではなく、gcc -Wl,--verboseを使いましょう。

環境

$ gcc --version
gcc (Ubuntu 8.2.0-7ubuntu1) 8.2.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ ld --version                 
GNU ld (GNU Binutils for Ubuntu) 2.31.1
Copyright (C) 2018 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or (at your option) a later version.
This program has absolutely no warranty.

$ ld --verbose

というようなコマンドを実行するとデフォルトリンカスクリプトが手に入る。というような記述をしばしば見かけます。(リンカ・ローダ実践開発テクニックとか)

このスクリプトを指定して生成した実行バイナリと、リンカスクリプトを指定しないで生成した実行バイナリは等しいものになると思いますよね?

という訳で、得られたリンカスクリプト*1 (default.ldsとします。)を用いて、以下を実行します。

$ gcc -o hoge hoge.c
$ gcc -o hoge_with_lds hoge.c -T default.lds
$ diff hoge hoge_with_lds 
Binary files hoge and hoge_with_lds differ 

実行結果が期待通りにならない...

恐らくld --verboseで得たリンカスクリプトが違うんだろう... *2

ではどうすれば実際に使われているスクリプトを得られるかというと、コンパイル時に-Wlオプションを使って、直接リンカに--verboseオプションを渡すとうまくいきます。

$ gcc  -Wl,--verbose

これで得られたスクリプトを用いて再度コンパイル&差分を確認すると、無事差分がなくなりました。

ちなみに実際のスクリプトの差を確認したら以下のようになりました。

$ diff default.lds default_gcc.lds
1c1
< /* Script for -z combreloc -z separate-code: combine and sort reloc sections with separate code segment */
---
> /* Script for -pie -z combreloc -z now -z relro -z separate-code: position independent executable, combine & sort relocs with separate code segment */
14c14
<   PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
---
>   PROVIDE (__executable_start = SEGMENT_START("text-segment", 0)); . = SEGMENT_START("text-segment", 0) + SIZEOF_HEADERS;
155,157c155,156
<   .got            : { *(.got) *(.igot) }
<   . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .);
<   .got.plt        : { *(.got.plt)  *(.igot.plt) }
---
>   .got            : { *(.got.plt) *(.igot.plt) *(.got) *(.igot) }
>   . = DATA_SEGMENT_RELRO_END (0, .);
240a240
> 

どうやら オプションごとにデフォルトリンカスクリプトは異なるらしい。

gccコマンドを使用して入手した方は -pie -z combreloc ... 向けみたいですね。

そういえば、ある時から(確かUbuntuでは)位置独立実行形式(PIE)がデフォルトになったと聞いた気がするな...

*1:実際の ld --verbose の出力はメタ情報等を含むのでいい感じに整形してください

*2:gccは内部?で色々なコマンドを実行しているので、この情報だけでは ld --verbose で得たリンカスクリプトが間違っていることが原因とは断定できない気はします

RISC-Vについて(CPU実験その2)

この記事はIS17er Advent Calendar 2017 - Adventarの3日目の記事として書かれました。 「日本語で書かれたRISC-V命令一覧が欲しいな」と思ったのがこの記事を書いたモチベーションなので、基本的にはCPU実験の班員向けに書いたRISC-Vの基本命令RV32Iのまとめの流用です。 おもしろみのない記事になってしまいました...

アーキテクチャを選ぶ

CPU実験では自分たちで作るプロセッサ及びコンパイラが対応するISA*1も自分たちで策定します。
課題であるレイトレーシング用プログラムmin-rtがOCamlのサブセットであるmin-camlで書かれているので、既存のmin-camlコンパイラが対応するPowerPCSPARCを参考にする班*2や、コピュータアーキテクチャの名著であるパタヘネ本で引用されているMIPSアーキテクチャをベースとする班が多いようですが 、 僕の所属するIS17er第1班では主に僕の独断と偏見でRISC-VというアーキテクチャをベースとしたISAを使用することにしました。

なぜRISC-Vなのか

RISC-VはRISCという言葉の生みの親でもありパタヘネ本の著者でもあるパターソン博士の属するUCバークレー発ののRISCアーキテクチャです。

などの理由でRISC-Vアーキテクチャを採用しました。

RISC-Vの基本構成

今回の実験では32bitの基本命令及び単精度FPU命令からなるRV32IFをベースにしました。

RV32Iの各命令について

RV32Iの命令エンコーディングは下記表に示す5つのタイプに分けられます。
f:id:progrunner17:20171203211238p:plain

上記の様に即値が複雑になっているのは、できるだけ同じビット位置に同じデータを入れることでハードウェアが簡単になるからだそうです。*5

命令はopcodeによって処理を行う演算器が指定され、さらにfunct3、及びfunct7によって細かい演算が指定されます。
例えばadd命令はオペコード 0110011でALUを用いてrs1とrs2の演算が行われることを指定し、funct3:000で ALUに加減演算を指定し、funct7で減算と区別されるという感じです。

RISC-V 命令一覧(算術論理演算まで)

短時間で書いたので抜けが多くミスもあると思います。 あくまで公式の仕様であるSpecifications - RISC-V Foundationを読む参考にしていただけたらと思います。

lui

  • オペコード 0110111
  • funct3: 即値として使用されるためなし
  • 命令形式: lui rd, imm
  • 命令意味: load upper immediate
  • 疑似コード rd = imm << 12,pc++
  • 即値タイプ: U

レジスタの上位20bitに即値を入れ、下位12bitは0埋め。 oriやaddiと組み合わせて任意の32bitの即値を得るのに使える。

auipc

  • オペコード 0010111
  • funct3: 即値として使用されるためなし
  • 命令形式: auipc rd, imm
  • 命令意味: add upper immediate to pc
  • 疑似コード rd = pc + (imm<<12),pc++
  • 即値タイプ: U

luiの結果をpcに足した値を得る。jalやbranch系命令では即値の範囲が狭いので、遠くへ相対ジャンプをしたい時に使える。

jal

  • オペコード 1101111
  • funct3: 即値として使用されるためなし
  • 命令形式: jal rd, imm
  • 命令意味: jump and link
  • 疑似コード rd = pc + 1 ,pc += imm
  • 即値タイプ: J

専用のリンクレジスタがないので、rdに指定したレジスタに戻りアドレスを入れてPC相対ジャンプをする。 基本的にx1が使われることがRISC-Vのspecの推奨らしい。戻りアドレスが不要な場合はx0を使うとよい

jalr

  • オペコード 1100111
  • funct3: 000
  • 命令形式: jalr rd, rs1, imm
  • 命令意味: jump and link register
  • 疑似コード rd = pc + 1 ,pc = rs1 + imm
  • 即値タイプ: I

rs1で指定したレジスタの示すアドレスへジャンプする。 luiやaddi、で即値を生成して絶対ジャンプや、auipcの結果を用いて相対ジャンプする際に使える。

BRANCH 系命令

  • オペコード 1100011
  • 即値タイプ: B

beq

  • funct3: 000
  • 命令形式: beq rs1, rs2, pc + imm
  • 命令意味: branch equal
  • 疑似コード if(rs1 == rs2)then pc += imm else pc++

bne

  • funct3: 001
  • 命令形式: bne rs1, rs2, pc + imm
  • 命令意味: branch not equal
  • 疑似コード if(rs1 != rs2)then pc += imm else pc++

blt

  • funct3: 100
  • 命令形式: blt rs1, rs2, pc + imm
  • 命令意味: branch less than
  • 疑似コード if(rs1 < rs2) then pc += imm else pc++

bge

  • funct3: 101
  • 命令形式: bge rs1, rs2, pc + imm
  • 命令意味: branch greater equal
  • 疑似コード if(rs1 >= rs2)then pc += imm else pc++

bltu

  • funct3: 110
  • 命令形式: bltu rs1, rs2, pc + imm
  • 命令意味: branch less than unsigned
  • 疑似コード if(rs1 < rs2) then pc += imm else pc++

bgeu

  • funct3: 111
  • 命令形式: bgeu rs1, rs2, pc + imm
  • 命令意味:branch greater equal unsigned
  • 疑似コード if(rs1 >= rs2)then pc += imm else pc++

LOAD系命令

  • オペコード 0000011
  • 即値タイプ: I

lb

  • funct3: 000
  • 命令形式: lb rd, offset(rs1)
  • 命令意味: load byte
  • 疑似コード rd = mem[rs1+offset] ,pc++

メモリのrs1+offset番地にある1バイトを符号拡張した値をrdで指定したレジスタにセットする.

lh

  • funct3: 001
  • 命令形式: lh rd, offset(rs1)
  • 命令意味: load half word
  • 疑似コード rd = mem[rs1+offset] ,pc++

メモリのrs1+offset番地にある2バイトを符号拡張した値をrdで指定したレジスタにセットする.

lw

  • funct3: 010
  • 命令形式: lw rd, offset(rs1)
  • 命令意味: load word
  • 疑似コード rd = mem[rs1+offset] ,pc++

メモリのrs1+offset番地にある1ワードを符号拡張した値をrdで指定したレジスタにセットする.

lbu

  • funct3: 100
  • 命令形式: lbu rd, offset(rs1)
  • 命令意味: load byte unsigned
  • 疑似コード rd = mem[rs1+offset] ,pc++

メモリのrs1+offset番地にある1バイトをゼロ拡張した値をrdで指定したレジスタにセットする.

lhu

  • funct3: 101
  • 命令形式: lhu rd, offset(rs1)
  • 命令意味: load half word unsigned
  • 疑似コード rd = mem[rs1+offset]

メモリのrs1+offset番地にある2バイトをゼロ拡張した値をrdで指定したレジスタにセットする.

STORE系命令

  • オペコード 0100011
  • 即値タイプ: S

sb

  • funct3: 000
  • 命令形式: sb rs2, offset(rs1)
  • 命令意味: store byte
  • 疑似コード mem[rs1+offset] = rs2,pc++

メモリのrs1+offset番地にLSBをセットする。

sh

  • funct3: 001
  • 命令形式: sh rs2, offset(rs1)
  • 命令意味: store half word
  • 疑似コード mem[rs1+offset] = rs2,pc++

メモリのrs1+offset番地に下位2バイトをセットする

sw

  • funct3: 010
  • 命令形式: sw rs2, offset(rs1)
  • 命令意味: store word
  • 疑似コード mem[rs1+offset] = rs2 ,pc++

メモリのrs1+offset番地に1wordをセットする。

OP-IMM系命令(即値算術論理演算)

  • オペコード 0010011
  • 即値タイプ: I

addi

  • funct3: 000
  • 命令形式: addi rd, rs1, imm
  • 命令意味: add immediate
  • 疑似コード rd = rs1 + imm ,pc++

即値を負の値に設定することでsubiも兼ねる。

slti

  • funct3: 010
  • 命令形式: slti rd, rs1, imm
  • 命令意味: set less than imm
  • 疑似コード rd = (rs1 < imm) ? 1 : 0 ,pc++

sltiu

  • funct3: 011
  • 命令形式: sltiu rd, rs1, imm
  • 命令意味: set less than unsigned imm
  • 疑似コード rd = (rs1 < imm) ? 1 : 0 ,pc++

xori

  • funct3: 100
  • 命令形式: xori rd, rs1, imm
  • 命令意味: xor immediate
  • 疑似コード rd = rs1 ^ imm ,pc++

ori

  • funct3: 110
  • 命令形式: ori rd, rs1, imm
  • 命令意味: or immediate
  • 疑似コード rd = rs1 |imm ,pc++

andi

  • funct3: 111
  • 命令形式: andi rd, rs1, imm
  • 命令意味: and immediate
  • 疑似コード rd = rs1 & imm ,pc++

slli

  • funct3: 001
  • 命令形式: slli rd, rs1, imm
  • 命令意味: shift left logical imm
  • 疑似コード rd = rs1 << imm ,pc++
  • 即値タイプ: I(5bit)

srli

  • funct3: 101
  • 命令形式: srli rd, rs1, imm
  • 命令意味: shift rifht logical imm
  • 疑似コード rd = rs1 >> imm ,pc++
  • 即値タイプ: I(5bit)

srai

  • funct3: 同上
    • funct7: 0100000
  • 命令形式: srai rd, rs1, imm
  • 命令意味: shift right arithmetic imm
  • 疑似コード rd = rs1 >>> imm ,pc++
  • 即値タイプ: I(5bit)

OP系命令

  • オペコード 0110011
  • 即値タイプ: R

add

  • funct3: 000
  • 命令形式: add rd, rs1, rs2
  • 命令意味: add
  • 疑似コード rd = rs1 + rs2 ,pc++

sub

  • funct3: 同上
    • funct7: 0100000
  • 命令形式: sub rd, rs1, rs2
  • 命令意味: sub
  • 疑似コード rd = rs1 - rs2 ,pc++

sll

  • funct3: 001
  • 命令形式: sll rd, rs1, rs2
  • 命令意味: shift left logical
  • 疑似コード rd = rs1 << rs2 ,pc++

slt

  • funct3: 010
  • 命令形式: slt rd, rs1, rs2
  • 命令意味: set less than
  • 疑似コード rd = (rs1 < rs2) ? 1:0 ,pc++

sltu

  • funct3: 011
  • 命令形式: sltu rd, rs1, rs2
  • 命令意味: set less than unsigned
  • 疑似コード rd = (rs1 < rs2) ? 1:0 ,pc++

xor

  • funct3: 100
  • 命令形式: xor rd, rs1, rs2
  • 命令意味: xor
  • 疑似コード rd = rs1 ^ rs2 ,pc++

srl

  • funct3: 101
  • 命令形式: srl rd, rs1, rs2
  • 命令意味: shift right logical
  • 疑似コード rd = rs1 >> rs2 ,pc++

sra

  • funct3: 同上
    • funct7: 0100000
  • 命令形式: sra rd, rs1, rs2
  • 命令意味: shift right arithmetic
  • 疑似コード rd = rs1 >>> rs2 ,pc++

or

  • funct3: 110
  • 命令形式: or rd, rs1, rs2
  • 命令意味: or
  • 疑似コード rd = rs1 |rs2 ,pc++

and

  • funct3: 111
  • 命令形式: and rd, rs1, rs2
  • 命令意味: and
  • 疑似コード rd = rs1 & rs2 ,pc++

最後に

今回も適当に書いてしまって反省...
時間ができたらまた詳しく書き直そうかと思います。

*1:Instruction Set Architecture: CPUが扱う命令のニーモニックや、エンコーディング、動作等

*2:min-camlはx86にも対応していますが、x86CISCでありハードウェアが複雑になるので採用する班は少ないようです。中にはx86コンパイラOCamlフルスクラッチしている同期もいますが(笑).

*3:オープンソースが優れているというより、僕がオープンソースが好きなので

*4:x0は0にハードワイヤード

*5:簡単の意味の名言は避けさせてもらいます。

Vivado入門1(CPU実験その1)

この記事は IS17er Advent Calendar 2017 - Adventarの1日目の記事として書かれました。  

CPU実験とは

東大理学部情報科学科では名物講義のひとつにCPU実験というものがあります。

CPU実験の説明は歴代の先輩方が様々説明を書いてくださっているので説明は割愛します。

Redirecting…
CPU実験2016年度D班コア係(CPU実験でマルチコア) - sueki743's blog
adventar.org

なぜVivado入門なのか

CPU実験ではFPGAを使ってCPUを作ります。

その際に用いるボードが昨年度からXilinx社製のものに更新され 、その統合開発環境であるVivadoに悩まされたので、本記事を書くことにしました。

各種バージョン

  • IDE :Vivado v2016.4(64bit) Ubuntu

  • ボード: Xilinx社製Kintex Ultrascale(KCU105)

 

今回やること

LEDをチカチカさせよう!!

  • Vivadoプロジェクトの作成
  • IPインテグレーター(ブロックデザイン)及びIPコアの使用*1
  • クロックの生成
  • LEDの使用*2
  • 合成(シンセサイズ)、配置配線(インプリメンテーション)、及びビットストリームの生成*3
  • FPGAボードのコンフィギュレーション*4

Vivadoプロジェクトの作成

f:id:progrunner17:20171201223735p:plain

 Vivadoを起動すると以下のようなウィンドウが表示されるので、
まずはプロジェクトを作成します。

f:id:progrunner17:20171201155144p:plain プロジェクト名とプロジェクトの保存場所を指定します.
この時 Create project subdirectoryのチェックを外すと指定したフォルダにVivadoが生成する大量のファイルが広がることになるので、外さないことを推奨します。

f:id:progrunner17:20171201223658p:plain

プロジェクトの種類を指定します。
ここでは基本的なプロジェクトであるRTL Projectを選択してください。 Do not specify sources at this timeのチェックを外すと使用するソースファイルを指定できますが、後から指定できるのでチェックをつけたままにしてください。

f:id:progrunner17:20171201223714p:plain
Boardsタブから使用するFPGAボードを指定します。 *5

これで設定は終わりです。最後に確認ウィンドウが表示されるのでfinishを選択するとプロジェクトが生成されます。

f:id:progrunner17:20171201224058p:plain

プロジェクトができました。

IPインテグレーター(ブロックデザイン)の使用

f:id:progrunner17:20171201224118p:plain
Vivadoには様々な機能がありますが、ウィンドウ左側に表示されているFlow Navigatorに表示されているコマンドを上から下に実行して行くことがプロジェクトの基本的な流れになります。

まずはじめに、Create Block Design*6を実行してください。 f:id:progrunner17:20171201225436p:plain
プロジェクト名は基本的にデフォルトで大丈夫なのですが、今回はtest_block_designと名付けました。

f:id:progrunner17:20171201225700p:plain

こんな感じでブロックデザインが生成されます。

クロックの生成

f:id:progrunner17:20171201225910p:plain
画面中央にあるboard タブからsystem differential clockのいづれかをドラック&ドロップでブロックデザインに追加します。*7

f:id:progrunner17:20171201230117p:plain

これでクロックを生成するIPコア(Clocking Wizard)が生成されました
クロック周波数はデフォルトで100MHzに設定されています。

IPコアの使用

さて、今回の目標であるLチカのためにはクロックの周波数は速すぎて人の目に追えません。
そこでバイナリカウンターを用いていい感じに遅い周波数を取り出したいと思います。
verilog等で直接書いてもいいですが、今回はXilinxが提供しているモジュール(IPコア)を使用したいと思います。

f:id:progrunner17:20171201230321p:plain

IPコアは右クリックかブロックデザインサイドバーから追加できます。 検索ウィンドウにbinaryと入力すればバイナリカウンターが見つかると思うので追加してください。

f:id:progrunner17:20171201231045p:plain

追加できたら上記のようにドラッグしてバイナリカウンターにクロックをつなげてください。

f:id:progrunner17:20171201231310p:plain デフォルトでは0~216-1までカウントできますが、これでもまだ速すぎるのでバイナリカウンターをクリックして設定ウィンドウ開いてIPコアに変更を加えます。

f:id:progrunner17:20171201231544p:plain とりあえず28bit分くらい使えば228/100MHz > 1Hzとなりいい感じでチカチカが見えると思います。

LEDの使用(物理制約及びポートの使用)

それではLEDに繋がるポートを追加します。
ブロックデザイン上で右クリックしてポートの追加を選択してください. f:id:progrunner17:20171201231758p:plain

今回使用するKCU105のボードにはユーザー用に8個のLEDがあるので上記のように設定してください。

f:id:progrunner17:20171201231904p:plain

led用のポートが生成されました。 ただ、このままではバイナリカウンターの出力が28bitに対しledの入力が8bitなのでsliceと呼ばれるIPコアを追加して出力の一部を取り出します。

f:id:progrunner17:20171201232057p:plain

上記のように設定してください。

f:id:progrunner17:20171201232243p:plain

こんな感じに各モジュールを繋いでブロックデザイン上部にあるrun connection automationを実行してブロックデザインでの基本的な作業は終了です 。

ソースファイル(制約)の追加

先ほどブロックデザインに追加したledポートですがこのままでは実際のLEDに紐付けされていないので使用できません。 それを解決するために制約ファイルと呼ばれるファイルを追加してポートを指定します。 Flow Navigator(左サイドバー)からadd sources を実行してください。

f:id:progrunner17:20171201232906p:plain

add or crate constraints をチェックしてください。

f:id:progrunner17:20171201233000p:plain

create fileを選択し制約ファイルを作ります。 ここでの制約ファイル名は特に重要ではないですが今回はled.xdc*8とします。

f:id:progrunner17:20171201233024p:plain

制約ファイルを追加したらウィンドウ中央からled.xdcをダブルクリックしてテキストエディタを開き下記の設定項目を入力してください。*9

set_property PACKAGE_PIN AP8 [get_ports "led[0]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[0]"]
set_property PACKAGE_PIN H23 [get_ports "led[1]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[1]"]
set_property PACKAGE_PIN P20 [get_ports "led[2]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[2]"]
set_property PACKAGE_PIN P21 [get_ports "led[3]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[3]"]
set_property PACKAGE_PIN N22 [get_ports "led[4]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[4]"]
set_property PACKAGE_PIN M22 [get_ports "led[5]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[5]"]
set_property PACKAGE_PIN R23 [get_ports "led[6]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[6]"]
set_property PACKAGE_PIN P23 [get_ports "led[7]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[7]"]

これで制約ファイルがの設定が終わりました。

HDLwrapperの生成

f:id:progrunner17:20171201233325p:plain

最後に、ウィンドウ中央のSources - Hierarchyタブを開きブロックデザインを右クリックしてcreat HDL wrapper を実行してください。

合成、配置配線及びビットストリームの生成

FPGAに書き込むために合成、配置配線及びビットストリームの生成*10を行います。

f:id:progrunner17:20171201233705p:plain

Flow Navigatorから run synthesizeを実行

f:id:progrunner17:20171201234009p:plain

しばらく待つとこの様なポップアップが現れるのでrun implementation を実行します。

f:id:progrunner17:20171201234409p:plain

generate bitstreamを実行
これでFPGAに書き込むファイルが生成できました。

f:id:progrunner17:20171201234435p:plain

ボードに電源をつなぎ、JTAGポートとPCをマイクロusbケーブルでつなぎFlownavigatorからハードウェアマネージャーを開き auto connectを行うとPCとFPGAが繋がります。 右クリックでprogram deviceを実行するとこんな感じでLEDがチカチカ光り始めるはずです。

f:id:progrunner17:20171201235140g:plain

最後に

もしやる気があれば次はBlock memoryやverilogなどの使い方を書きます。

*1:今回はclock wizard,binary counter, sliceを用いた

*2:ポート及び物理制約の使用

*3:要はFPGAボードへ書き込む準備

*4:実際にFPGAに書き込みを行うこと

*5:おそらく基本設定でボードを指定するとこの選択ができるようになると思うのですが、その設定方法を知らないので割愛します。

*6:ブロクデザインはGUIを使用した設計ツールです

*7:clocking wizardもIPコアの一つなのでadd IPから追加することも可能ですが、この方法で追加することで制約ファイルを自分で書くことなくクロックを簡単に生成することができます。

*8:xdcは制約ファイルの拡張子

*9:制約ファイルの物理制約はボード毎に異なりますKCU105の場合このリンクのPDFの末尾の方に設定例が乗っています。

*10:かなり適当に例えると推論、リンク、コンパイルみたいな感覚です。