Linux (Ubuntu 18.04) からUTokyo WiFiに接続する (GUI編+CLI編)
UTokyo WiFiの公式ページの記載だけでは、Ubuntuからうまく接続できなかったので、 またOSをふっ飛ばして再インストールするであろう将来の自分に向けて接続方法のメモ
GUI編
以下のように設定すれば接続できるはず。
設定項目 | 設定値 |
---|---|
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(追記)
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のインストール
公式サイトのインストール方法(Get Docker CE for Debian)に従ったらコケたので、下記スクリプトで導入
sh curl -sSL https://get.docker.com | sh
cf. 最近のRaspberry Piイメージ(Raspbian)をインストールするメモ
今回初めて知ったけれど、mDNSという仕組みがあり、それを使えばIPを固定しなくても同じLANでraspberrypi.localという名前でラズパイを参照できるらしい、これまで何度か /etc以下設定ファイルを書き換えたりしてたけど、そんな必要なかったのか〰(便利なので良いけれど)。
2. Prometheusを動かしてみる
まずはPrometheusの概要を学んでみようということで、Qiitaで以下の記事を発見
Prometheusは雑に解釈すると、以下の2種類のプログラムからなるらしい
exporter:
- 監視対象のサーバーで動作するプログラム。
- 監視するリソース毎に準備
- WebAPIの感覚で、情報をテキストベースで公開する
prometheus:
監視サーバーのプログラム
定期的に巡回してexporterから情報を収集する
というわけで、早速試してみる。
を参考にしつつ、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を開いてみる。
良さそうだ
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
をクエリとしてグラフを表示させてみると以下のようになった。
うまく表示できた。
しばらく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から変更が入っていそうです。
おわり
カーネルモジュールのロードに失敗した話(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)がデフォルトになったと聞いた気がするな...
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コンパイラが対応するPowerPCやSPARCを参考にする班*2や、コピュータアーキテクチャの名著であるパタヘネ本で引用されているMIPSアーキテクチャをベースとする班が多いようですが 、 僕の所属するIS17er第1班では主に僕の独断と偏見でRISC-VというアーキテクチャをベースとしたISAを使用することにしました。
なぜRISC-Vなのか
RISC-VはRISCという言葉の生みの親でもありパタヘネ本の著者でもあるパターソン博士の属するUCバークレー発ののRISCアーキテクチャです。
- オープンソースのアーキテクチャであること 。*3
- グーグルやオラクル等も参加し、比較的勢いのあるアーキテクチャであること。
- 参考として買ったFPGAマガジンNo.18に資料が多かったこと。
- 学科同期の@00_から勧められたり、FPGA開発日記というブログで取り上げられていたこと.
RISC-Vの基本構成
今回の実験では32bitの基本命令及び単精度FPU命令からなるRV32IFをベースにしました。
RV32Iの各命令について
RV32Iの命令エンコーディングは下記表に示す5つのタイプに分けられます。
上記の様に即値が複雑になっているのは、できるだけ同じビット位置に同じデータを入れることでハードウェアが簡単になるからだそうです。*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++
最後に
今回も適当に書いてしまって反省...
時間ができたらまた詳しく書き直そうかと思います。
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に悩まされたので、本記事を書くことにしました。
各種バージョン
今回やること
LEDをチカチカさせよう!!
- Vivadoプロジェクトの作成
- IPインテグレーター(ブロックデザイン)及びIPコアの使用*1
- クロックの生成
- LEDの使用*2
- 合成(シンセサイズ)、配置配線(インプリメンテーション)、及びビットストリームの生成*3
- FPGAボードのコンフィギュレーション*4
Vivadoプロジェクトの作成
Vivadoを起動すると以下のようなウィンドウが表示されるので、
まずはプロジェクトを作成します。
プロジェクト名とプロジェクトの保存場所を指定します.
この時 Create project subdirectoryのチェックを外すと指定したフォルダにVivadoが生成する大量のファイルが広がることになるので、外さないことを推奨します。
プロジェクトの種類を指定します。
ここでは基本的なプロジェクトであるRTL Projectを選択してください。
Do not specify sources at this timeのチェックを外すと使用するソースファイルを指定できますが、後から指定できるのでチェックをつけたままにしてください。
Boardsタブから使用するFPGAボードを指定します。 *5
これで設定は終わりです。最後に確認ウィンドウが表示されるのでfinishを選択するとプロジェクトが生成されます。
プロジェクトができました。
IPインテグレーター(ブロックデザイン)の使用
Vivadoには様々な機能がありますが、ウィンドウ左側に表示されているFlow Navigatorに表示されているコマンドを上から下に実行して行くことがプロジェクトの基本的な流れになります。
まずはじめに、Create Block Design*6を実行してください。
プロジェクト名は基本的にデフォルトで大丈夫なのですが、今回はtest_block_designと名付けました。
こんな感じでブロックデザインが生成されます。
クロックの生成
画面中央にあるboard タブからsystem differential clockのいづれかをドラック&ドロップでブロックデザインに追加します。*7
これでクロックを生成するIPコア(Clocking Wizard)が生成されました
クロック周波数はデフォルトで100MHzに設定されています。
IPコアの使用
さて、今回の目標であるLチカのためにはクロックの周波数は速すぎて人の目に追えません。
そこでバイナリカウンターを用いていい感じに遅い周波数を取り出したいと思います。
verilog等で直接書いてもいいですが、今回はXilinxが提供しているモジュール(IPコア)を使用したいと思います。
IPコアは右クリックかブロックデザインサイドバーから追加できます。 検索ウィンドウにbinaryと入力すればバイナリカウンターが見つかると思うので追加してください。
追加できたら上記のようにドラッグしてバイナリカウンターにクロックをつなげてください。
デフォルトでは0~216-1までカウントできますが、これでもまだ速すぎるのでバイナリカウンターをクリックして設定ウィンドウ開いてIPコアに変更を加えます。
とりあえず28bit分くらい使えば228/100MHz > 1Hzとなりいい感じでチカチカが見えると思います。
LEDの使用(物理制約及びポートの使用)
それではLEDに繋がるポートを追加します。
ブロックデザイン上で右クリックしてポートの追加を選択してください.
今回使用するKCU105のボードにはユーザー用に8個のLEDがあるので上記のように設定してください。
led用のポートが生成されました。 ただ、このままではバイナリカウンターの出力が28bitに対しledの入力が8bitなのでsliceと呼ばれるIPコアを追加して出力の一部を取り出します。
上記のように設定してください。
こんな感じに各モジュールを繋いでブロックデザイン上部にあるrun connection automationを実行してブロックデザインでの基本的な作業は終了です 。
ソースファイル(制約)の追加
先ほどブロックデザインに追加したledポートですがこのままでは実際のLEDに紐付けされていないので使用できません。 それを解決するために制約ファイルと呼ばれるファイルを追加してポートを指定します。 Flow Navigator(左サイドバー)からadd sources を実行してください。
add or crate constraints をチェックしてください。
create fileを選択し制約ファイルを作ります。 ここでの制約ファイル名は特に重要ではないですが今回はled.xdc*8とします。
制約ファイルを追加したらウィンドウ中央から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の生成
最後に、ウィンドウ中央のSources - Hierarchyタブを開きブロックデザインを右クリックしてcreat HDL wrapper を実行してください。
合成、配置配線及びビットストリームの生成
FPGAに書き込むために合成、配置配線及びビットストリームの生成*10を行います。
Flow Navigatorから run synthesizeを実行
しばらく待つとこの様なポップアップが現れるのでrun implementation を実行します。
generate bitstreamを実行
これでFPGAに書き込むファイルが生成できました。
ボードに電源をつなぎ、JTAGポートとPCをマイクロusbケーブルでつなぎFlownavigatorからハードウェアマネージャーを開き auto connectを行うとPCとFPGAが繋がります。 右クリックでprogram deviceを実行するとこんな感じでLEDがチカチカ光り始めるはずです。
最後に
もしやる気があれば次はBlock memoryやverilogなどの使い方を書きます。
*1:今回はclock wizard,binary counter, sliceを用いた
*2:ポート及び物理制約の使用
*5:おそらく基本設定でボードを指定するとこの選択ができるようになると思うのですが、その設定方法を知らないので割愛します。
*7:clocking wizardもIPコアの一つなのでadd IPから追加することも可能ですが、この方法で追加することで制約ファイルを自分で書くことなくクロックを簡単に生成することができます。
*8:xdcは制約ファイルの拡張子
*9:制約ファイルの物理制約はボード毎に異なりますKCU105の場合このリンクのPDFの末尾の方に設定例が乗っています。