mcommit's message

大阪でソフトウェア開発の仕事をしている simotinといいます。記事の内容でご質問やご意見がありましたらお気軽にコメントしてください\^o^/

CMakeでクロスコンパイルする

最近、オープンソースなどで配布されているソフトのビルドシステムがCmakeになっているのをよく見かけるようになりました。

CMakeはこれまでも何回か使ったことがありましたが、クロスコンパイルをしたいときの使い方を忘れてしまっていて、思い出すのに時間がかかったので備忘録として書いておきたいと思います。

CMakeでHello World

ロスコンパイルの前にC言語Hello WorldプログラムをCMakeを使って(セルフコンパイル)ビルドしてみます。

ビルドするコード

#include <stdio.h>

int main(int argc, char **argv)
{
	printf("hello world\n");
	return 0;
}

CMakeLists.txt を書く

CMakeLists.txtを最小限の構成で手っ取り早く書くとこんな感じです。

cmake_minimum_required(VERSION 2.8)
add_executable(helloworld main.c)

cmake_minimum_required は文字通りcmakeのバージョン
add_executable でターゲット名とソースファイルを指定する。

ビルド

cmakeの便利な点として、ソースファイルのディレクトリとオブジェクトの出力ディレクトリを分けてビルドできる点があります。
CMakeLists.txt があるディレクトリで、

$ mkdir build
$ cd build
$ cmake ..

とすると、作成したbuildディレクトリ内にMakefileなどビルドに必要なファイルが生成されます。

実行例はこんな感じです。

-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done

$ ls
CMakeCache.txt  CMakeFiles  Makefile  cmake_install.cmake

あとはこのbuildディレクトリでmakeコマンドを実行すればビルドできます。
私の環境だとコンパイラGCCが使われたようです。

$ make
Scanning dependencies of target helloworld
[ 50%] Building C object CMakeFiles/helloworld.dir/main.c.o
[100%] Linking C executable helloworld
[100%] Built target helloworld

$ ./helloworld 
hello world

ロスコンパイルを試す

これでCMakeによるセルフコンパイルの準備はできました。
ロスコンパイルを行う場合は、更にツールチェインファイルというファイルを作成する必要があります。
といっても大げさなものではなくて、文字通りクロスコンパイル時に使用するコンパイラの指定をするだけです。

今回は、 Raspberrypi でも実行できるので、 arm-linux-gnueabihf-gcc を使ってみました。
今回は私が試した環境は64bit版の Linux mint です。

$ uname -a
Linux mint 4.4.0-21-generic #37-Ubuntu SMP Mon Apr 18 18:33:37 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

Ubuntu系のOSであればインストールは、

$sudo apt install g++-arm-linux-gnueabihf

でインストールできると思います。

ツールチェインファイルの書き方

さて、本題のツールチェインファイルは以下のようになります。

ファイル名を arm-toolchain.cmake として、以下のような記述をします。

SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc)

ツールチェインファイルを書いたら、後はセルフコンパイルの時と同様にビルド用のディレクトリを作ってcmakeコマンドを実行します。

$ mkdir cross-build
$ cd cross-build
$ cmake .. -DCMAKE_TOOLCHAIN_FILE=../arm-toolchain.cmake

セルフコンパイルの時との違いとして、

  • DCMAKE_TOOLCHAIN_FILE=../arm-toolchain.cmake

のようにツールチェインファイルのパスを指定してあげる必要があります。

-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/arm-linux-gnueabihf-gcc
-- Check for working C compiler: /usr/bin/arm-linux-gnueabihf-gcc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done

$ make
Scanning dependencies of target helloworld
[ 50%] Building C object CMakeFiles/helloworld.dir/main.c.o
[100%] Linking C executable helloworld
[100%] Built target helloworld

$ file helloworld
helloworld: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=9bd916148cf9880f2494f47199e8c001244a450d, not stripped

ちゃんとARM用にクロスコンパイルできました。
かなりミニマムな構成でのビルドの例なので、実際にオープンソースのプロジェクトなどをビルドする際はもう少しオプションやパスの指定などが必要になってくるかと思います。

自分の雛形用として、Githubにおいています。もし参考にされる方がいらっしゃいましたらはどうぞ。

github.com

感想

CmakeはMakefileを書くよりは楽といえば楽ですね。

昔はMakeの本を読んで一生懸命Makefileを書き方を勉強したものですが、Makefileは書かないとすぐに忘れてしまいます。

GNU Make 第3版

GNU Make 第3版

Makefileを書きたくないものぐさな私はよく

$ echo "gcc main.c hoge.c -o target" > build.sh
$ chmod +x build.sh
$ ./build.sh

みたいなことをよくしています。

小規模な実験的なコードを書く場合はこのやり方でもよいのですが、プロジェクトが育ってくるとやはりビルド管理したくなります。

コマンドラインにこだわらないのであればEclipse等の環境を導入し、プロジェクトを作ってソースファイルを指定するのが楽かもしれません。

Makefileさえ知らない新人のころのあるプロジェクトでは、

ソースコード書いたらmfmkってコマンドを実行すればいいから」

と親切な先輩が教えくれた現場がありましたがmkmfはLinux系のOSには標準では入っていないんですね。
依存関係とかを気にせずに特定のディレクトリのソースコード調べてMakefile生成するだけであれば簡単に作れそうに思います。

気が向いたら作ってみようかな・・・

ウイルス性のいぼを自宅で治療した

最近、子供の足にウイルス性のイボができていたのですが、病院に行かずなんとか自力で治すことができました。

写真は治りかけの子供の患部の状態です。

削った後なのでかなりきれいになった状態ですが、(写真ではわかりづらいですが)赤いぶつぶつがまだ少し残っています。
※記事を書こうとは思っていなかったこともあり治療前から写真はとっていませんでした。

f:id:simotin13:20170904215656j:plain

私自身も数年前にウイルス性のイボが右足の裏にできて苦しんだことがあったのですが、病院(皮膚科)への通院を途中で辞めてから自力で治せたことがあります。

ウイルス性のイボは一度なってしまうとなかなか治らず、結構つらいです。

今回試してみた方法でかなり短期間(1か月半ほど)でイボが治ったので、試みた方法について書いておきたいと思います。

治療の戦略

まずは、今回子供のイボ治療に試してみた方法の概要です。

  1. ヨクイニンやハトムギを飲む→体内から正常な皮膚の再生能力を高める
  2. ある程度(2~3週間)継続して飲んだら、イボを針や爪切りで削る
  3. 削った患部を消毒する
  4. 1~3を繰り返す

準備する物

針・爪切り

上記の2にある、イボを削る際に使います。

イソジン軟膏

ウイルス性のイボは文字通り体内にウイルスが侵入し細胞に感染するため、皮膚が硬くなったりするそうです。なので患部にウイルスを近づけないことが大切です。

イソジン軟膏は消毒・殺菌成分が含まれており、イボの患部に塗っておくと効果があるそうです。

【第3類医薬品】明治きず軟膏 5g

【第3類医薬品】明治きず軟膏 5g

そういえば、イボとは関係ありませんが、中学生の時にニキビにうがい薬の方のイソジンをつけていたら数日でニキビが治っていました。イソジンは殺菌効果が高いのかもしれません。

イソジンの欠点は、肌に塗ると黄色くなってしまうことと、塗った薬が布団等についてしまうことでしょうか。

今回子供のイボを治療する際には患部にイソジン軟膏を塗って、そこに絆創膏を張るようにしました。絆創膏を張っていれば布団や床を汚さなくて済みます。

ヨクイニン

ハトムギに含まれるヨクイニン成分を抽出したタブレットです。

ドラッグストアの漢方やサプリメントのコーナーで売っています。
商品によっては、年齢制限がある場合もありますのでお子さんに飲ませる場合は注意が必要です。

今回は子供でも飲めるクラシエの商品を飲ませてみました。

【第3類医薬品】クラシエヨクイニンタブレット 126錠

【第3類医薬品】クラシエヨクイニンタブレット 126錠

ハトムギ

上記のヨクイニン成分が含まれるハトムギ茶ははイボや皮膚のターンオーバーに効果があるといわれています。
こちらもドラッグストアの漢方のコーナーとかで売られています。

今回、子供にはヨクイニンと合わせてハトムギ茶も飲ませていました。

ハトムギ茶は麦茶の仲間なので普通に飲み物として飲む分にもおいしく飲めます。

ヨクイニンやハトムギ茶は飲み続ける必要がある

ヨクイニンやハトムギ茶は飲み始めてすぐに効果は出ないようです。私も2~3か月飲み続けて気が付いたらイボが小さくなっていたという感じでした。

ヨクイニンやハトムギ自体はお肌にいいそうなのでイボ治療に関係なく飲み続けてもいいかもしれません。私も子供のために買ったハトムギ茶をかなり飲むようにになりました。

具体的な手順

ヨクイニン・ハトムギ茶を飲む

買ってきたヨクイニン、ハトムギ茶を継続して飲むだけです。
ハトムギ茶は普通に麦茶として飲めるので子供でも問題ありませんでした。
今回買ったヨクイニンタブレットは毎食毎に飲むものでしたが、うちの子供は時々飲むのを忘れているようでした。

イボを削る

ヨクイニン・ハトムギ茶を飲み始めてから2週間ほどしてからイボを削りました。
削るまでに2週間待つ理由は、ヨクイニンやハトムギの効果はすぐ出ないのである程度期間をあけた方がよいという私の考えからです。
※これは、医学的な根拠があるわけではありません。

削るといっても針でほじくって出血して赤くなったイボをえぐり出すという感じです。
注意点として、針をよく消毒しておく必要があります。私はコンロで炙って傷薬で消毒してからイボをほじるようにしました。

消毒する

赤いぶつぶつ部分を削った後は患部にイソジン軟膏を塗って消毒し、絆創膏で保護します。
イソジン軟膏は黄色のクリーム状の薬ですのでそのままだと布団や床が汚れてしまうということもあります。

消毒を怠るとイボがかえって広がる可能性があると思いますので注意してください。


削るのは3~4日ほど間隔を空けました。削るたびにウイルス性いぼの赤いぶつぶつが減っていることが確認できました。

病院での治療の場合

ウイルス性のイボができた場合、病院での治療は、恐らく液体窒素でイボを焼いての治療になると思います。

私も数年前に病院に通院した経験があります。

「焼く」というだけあって結構痛いです。しかし大人であればまぁなんとか我慢できない痛みではなかったと思います。
焼いている時よりはむしろ、治療後から1~3日間が痛く、それから痛みが少しずつかゆみに変わっていきます。

焼くときより痛い「削る」治療

私の場合、焼いてから1週間~2週間程度経ってからまた通院していました。
たしか歯医者さんのように1週間ペースで予約を入れるよう病院から求められたと思います。

この時、再度イボを焼く前に、前回焼いた部分の皮膚をナイフのようなもので削られます。

削る理由は、焼いた部分は固くなって他の部分より盛り上がってくるので削らないと再度液体窒素で焼くときに効果が出ないためです。

この「削る」治療が無茶苦茶痛かったです(涙)

イボというのは皮膚の奥の方に芯があるのですが、芯はかなり堅くなっており押すと神経に触れて痛みを感じます。

削るときは皮膚に圧力がかかるのでかなり痛みを伴いました。
また、液体窒素で焼いた皮膚は固くなっているので簡単には削れないようでした。

削ってくれる看護師さんにも上手下手があるようで、丁寧に削ってくれない看護師さんだととても痛かったです。

あまりの痛さに途中で通院を辞めた

毎週のようにこの痛みに耐えないといけないというのはかなりつらい経験でした。
また上記の通り焼いてから数日は患部の痛みで歩くのも変な感じになってしまいます。

2か月ほど通院していたのですが、なかなか治る気配がなく、看護師さんの「削り」が少し乱暴なときがあったので、皮膚科に通うのはやめてしまいました。

実は、通院をやめる少し前からヨクイニンを飲んでいて、通院をやめてから自分で針でいじくったりしていたのですが、2週間程して少しずつイボが小さくなり始め、さらに数週間で完全に治りました。

子供の病院に連れて行かなかった理由

私自身にこのような経験もあったので、今回子供の足にイボが見つかった時、病院に行かずに治せないだろうかと考えました。
ネットで調べてみると痛くない治療法もあるそうですが、小さい子供を頻繁に子供を病院に連れていくのも大変です。また評判のいい皮膚科は人気で、診察のためにものすごく行列ができていたりもします。

自分で治療する場合、下手をするとかえってイボが広がってしまう可能性はあったのですが、キチンと消毒すれば大丈夫だろうという考えて自力での治療を試みてみました。

足にウイルス性のいぼができる原因

子供も私もそうでしたが、足の裏のイボは気が付くとできていることが多いです。

そもそもイボはどうしてできるのでしょうか。

皮膚科のサイトなどではウイルスが細胞に感染してなることが解説されています。

私の場合、後から振り返って分かったことですが、足に合わないきつい靴を履いたことが原因だったと思います。

その当時、買った靴が少しきつかったのですが、我慢して数か月履いていると、ある日右足の指の下、横幅でいうと真ん中くらいの位置に魚の目ができていました。恐らくきつい靴によって前後左右から締め付けられたしわ寄せがきていたんだと思います。

魚の目になったのは初めてだったのでどうすればいいか調べてみて、魚の目用の絆創膏で治療を試みていたのですがなかなか治りませんでした。

それどころか、絆創膏のサリチル酸で柔らかくなった皮膚がウイルスに感染して、魚の目がそのままウイルス性のイボになってしまいました。

なので個人的にはあの魚の目を治す絆創膏には不信感を持っています。足の裏はどうしても不衛生な部位だと思うので使う場合は消毒もきちんとしないといけなかったんだと思います。

今回の子供の場合も履いていたサンダルが少し足に合っていなかった可能性があります。

足に負担をかけるという意味では、(いぼや魚の目は)男性よりもハイヒールなどを履いたりする女性がなりやすいのかもしれません。

お風呂あがりに、ときどき自分で足裏マッサージをするなどして異変がないかチェックしておくといいかもしれません。

まとめ

イボやタコ、魚の目といった皮膚のトラブルは頻繁になるものではありませんが、一度なると完治するまでに時間がかかります。
きちんと皮膚科に通院して治療するのが当然ベストですが、治療法によっては痛みを伴いますし通院したからと言って必ずしも早く治るというわけでもありません。また治療が長期戦になるため通院費もトータルで見ると意外と安くないです。

今回の記事で紹介したようなやり方以外でも自力でこういった皮膚のトラブルを治している人もいらっしゃるようですので、同じように悩まれている方のご参考になれば幸いです。

ただし、ヨクイニンやハトムギ茶の効果は個人差があるそうですし、今回子供にできたイボは比較的小さかったということもあるとは思います。私自身はもちろん皮膚科医でもなんでもありませんので、あくまで参考情報として読んで頂ければ幸いです。

書いておいて言うのは変ですが、イボになられた方は、やはりまずは信頼できる皮膚科のお医者さんに見て頂くのが正しいかと思います。

WiFiのパケットをWireSharkでキャプチャする方法

スマホの通信(WiFi)をキャプチャする必要があり、少しネットを調べてみたのですが、あまりいい方法が見つからなかったので思いついた方法を試してみました。

試してみた結果、いい感じにスマホが送受信するパケットをWireSharkでキャプチャすることができたので記事として挙げておきたいと思います。

目次

  • 目次
  • 注意
  • 作戦
    • パソコンをWiFiのAPにする
  • 準備するもの
    • 注意点
  • 手順
  • その他のやり方は?

注意

紹介する方法はWindows7でしかできない方法になります。他のOSについては別途試したときに記事を書きたいと思います。

作戦

パソコンをWiFiのAPにする

WiFiのパケットをキャプチャする場合、WiFiルータのところでキャプチャする必要がありますが、ルーターの通信をキャプチャするとなるとできなくはないのですが、環境構築とかが少し面倒です。

他に手軽にできる方法が無いか考えて、パソコンをアクセスポイントにして流れてくるパケットをキャプチャするという方法を思いつきました。

今回試した環境は、Windows7のPCです。インターネットには有線LAN経由で接続した状態にします。

準備するもの

パソコンをWiFiのAPにする場合、親機になれるUSBの無線LANドングルがあれば実現可能です。

USB無線LANドングルは1つの独立したネットワークアダプタとして認識されるのでWireSharkでキャプチャするときも便利です。

いろいろ調べてみると、バッファローのWLI-UC-GNM2SであればWindows7であればWiFiの親機のになれるとのことで購入して試してみました。

続きを読む

cppcheckをビルドして使ってみる

目次

cppcheckという静的解析ツールを試してみました。
cppcheckはC/C++言語向け静的解析ツールですが、今回メモリリーク検出を目的として試してみました。

ビルド~インストールや試してみた結果について書いておきたいと思います。

背景

C言語C++でコードを書いていると否が応でもメモリリークの危険性と戦う必要があります。

リークをなくすためには、

  1. 動的メモリ確保を行わない
  2. メモリリークがないかしっかり試験する

の2つのアプローチがあります。

私が普段仕事をしている組み込みソフトの開発では1の「動的メモリ確保を行わない」というアプローチは一般的です。

そもそも組み込み開発の場合、OSがない=メモリ管理の機構(malloc,free)が存在しないというのは一般的です。
リアルタイムOSitronを使う場合、固定長メモリプールや可変長メモリプールのAPIは利用できますが、メモリリークを嫌って使わないことも多いです。

最近では組み込みソフトの開発にLinuxを使われることが多く、さすがにその場合は「動的メモリ確保を行わない」というアプローチを取らなかったりします。
メモリリークというリスクを考慮すると、使わないで済むのであれば使わないに越したことはないと思いますが、Linuxを使うくらいですから扱うデータの規模もそれなりに大きかったりするので、スタックで確保するには大きすぎたり、静的変数で確保には使用効率が悪かったりします。

動的メモリ確保(malloc~free, new, delete)を使うとなると2のメモリリークがないかしっかり試験する」が重要になってくるのですが、人手で試験をするのは手間も時間をかかりますので現実的とは言えません。

できる限り自動化したいところです。

メモリリークの検出にはいろいろなツールがあり、私も過去にvalgrindというツールを試してみたことがあります。

mcommit.hatenadiary.com

valgrindは非常に便利なツールなのですが、ビルド済みのバイナリ(デバッグビルド)を使ってメモリリークのチェックが行われるため、実は組み込み開発の実機では気軽に使えない(valgrind自体のクロスコンパイルが必要)という事情があります。
※組み込みLinuxの場合は開発中はセルフコンパイルで動作を確認したりするので、valgrindを使うメリットは十分享受できるのですが。

cppcheckを試してみる

いろいろ調べているとcppcheckという静的解析ツールでメモリリークも検知できるということを知り、今回ビルド・インストールして試してみました。

ビルド

こちらのサイトからソース一式をダウンロードします。

cppcheck.sourceforge.net


$ wget https://github.com/danmar/cppcheck/archive/1.80.zip
$ unzip unzip 1.80.zip
$ cd cppcheck-1.80

ビルド方法については readme.txt に簡単に説明があります。
※cppcheckはいろいろな環境に対応しており、Windows環境であればVisualStudioでビルドできるほか、cmakeを使ったビルドもできるそうです。

私の場合、手元のLinux(Mint)で試しました。恐らくUbuntuでも同じようにビルドできると思います。


$ uname -a
Linux mint 4.4.0-21-generic #37-Ubuntu SMP Mon Apr 18 18:33:37 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

ビルド時の注意点

ビルド時の注意点が1点あります。
cppcheckは静的解析ツールなので、チェックするルールを.cfgというファイルで指定できるのですが、このファイルの配置場所をビルド時に指定しておく必要があります。
指定方法は

ちなみに指定しないでmake;make install してcppcheckを動かすと、


cppcheck: Failed to load library configuration file 'std.cfg'. File not found
(information) Failed to load std.cfg. Your Cppcheck installation is broken, please re-install. The Cppcheck binary was compiled without CFGDIR set. Either the std.cfg should be available in cfg or the CFGDIR should be configured.

というメッセージが表示されてソースコードのチェックが正しくできません。

正しく解析するためには、makeする際に、 CFGDIR=cfgファイルのパス としてcfgの配置ディレクトリを指定する必要があります。

なので、makeの実行は以下のようなコマンドを実行しました。


$ make CFGDIR=/usr/local/cppcheck/cfg HAVE_RULES=yes

※readme.txt を読んでもHAVE_RULESの説明は詳しく書かれていませんでしたがとりあえずつけてみました。
ちなみにHAVE_RULES=yesを指定する場合は、依存するライブラリとしてpcreが必要になるため、インストールが必要です。


$ sudo apt install libpcre3-dev

cfgファイルは解凍したフォルダ内のcfgディレクトリ以下にいろいろなcfgファイルが入っています。
デフォルトでは、std.cfgが解析に使用されるようです。

インストール時に指定した/usr/local/cppcheck/cfg というディレクトリを作成し、これらのファイルをコピーします。

ビルドから、上記手順までのコマンドは、


$ make CFGDIR=/usr/local/cppcheck/cfg HAVE_RULES=yes
$ sudo make install;
$ sudo mkdir -p /usr/local/cppcheck/cfg
$ sudo cp ./cfg/* /usr/local/cppcheck/cfg

のような感じになります。

これでcppcheckを使う準備が整いました。
この時点で、cppcheck自体は /usr/bin にインストールされています。

cppcheckを動かしてみる

早速メモリリークの検出を試してみます。

今回、以下のようなコードでどんな感じにメモリリークが検出されるか試してみました。

#include <stdio.h>
#include <stdlib.h>

// =============================================================================
// define 定義
// =============================================================================
#define TEST_BUF_SIZE_MAX	(16)

// =============================================================================
// プロトタイプ宣言
// =============================================================================
void test_stack_overflow(void);													// スタックオーバーフロー(直値・ループ)
void test_stack_overflow_with_idx(int idx);										// スタックオーバーフロー(変数アクセス)
void test_heap_access(void);													// メモリリーク(free漏れ)
void test_mem_double_free(void);												// メモリ2重解放
void test_buf_over_run(unsigned char *buf, int len);							// バッファオーバーラン


// =============================================================================
// static 変数
// =============================================================================
unsigned char s_test_buf[TEST_BUF_SIZE_MAX] = { 0 };

int main(int argc, char **argv) {

	// =====================================================
	// スタックオーバーフロー
	// =====================================================
	test_stack_overflow();
	test_stack_overflow_with_idx(0);
	test_stack_overflow_with_idx(16);

	// =====================================================
	// バッファオーバーラン
	// =====================================================
	test_buf_over_run(s_test_buf, 4);
	test_buf_over_run(s_test_buf, 256);

	// =====================================================
	// メモリリーク
	// =====================================================
	test_heap_access();

	// =====================================================
	// 2重解放
	// =====================================================
	test_mem_double_free();

	return 0;
}

// 直接・ローカル変数によるループ中での配列アクセス
void test_stack_overflow(void)
{
	unsigned char buf[TEST_BUF_SIZE_MAX];
	int i;

	// インデックスアクセス:直値
	buf[0] = 0x00;
	buf[15] = 0x00;
	buf[16] = 0x00;
	buf[-1] = 0x00;

	// ループ変数アクセス
	for (i = 0; i < 17; i++) {
		buf[i] = 0x00;
	}

	return;
}

// 変数による配列へのアクセス
// 検出するとしたら呼び出し元で検出されるはず
void test_stack_overflow_with_idx(int idx) {
	unsigned char buf[TEST_BUF_SIZE_MAX];

	buf[idx] = 0x00;
	return;
}

// free漏れテスト
void test_heap_access(void) {
	char *p;

	// 確保して解放しない
	p = malloc(1024);

	return;
}

// freeによる2重解放テスト
void test_mem_double_free(void) {
	char *p;

	p = malloc(1024);

	// 2重解放
	free(p);
	free(p);

	return;
}

// バッファオーバーラン
void test_buf_over_run(unsigned char *buf, int len)
{
	int i;

	// 引数で指定されたサイズ分だけループする
	// →呼び出し側でlenを間違えた場合ここではなく呼び出し側でエラーがでる?
	for (i = 0; i < len; i++) {
		buf[i] = i;
	}

	// 引数で指定されたサイズ分だけループする
	// →呼び出し側でlenを間違えた場合ここではなく呼び出し側でエラーがでる?
	for (i = 0; i < len; i++) {
		buf[i] = i;
	}
	return;
}


cppcheckのシンプルな使い方として、引数にフォルダパスをしてするとそのパス内のファイルの解析をしてくれるようです。
今回は上記のコードを main.c として保存して試してみました。


$cppcheck .

出力は以下のような感じになります。


Checking main.c ...
[main.c:61]: (error) Array 'buf[16]' accessed at index 16, which is out of bounds.
[main.c:66]: (error) Array 'buf[16]' accessed at index 16, which is out of bounds.
[main.c:77]: (error) Array 'buf[16]' accessed at index 16, which is out of bounds.
[main.c:62]: (error) Array index -1 is out of bounds.
[main.c:88]: (error) Memory leak: p
[main.c:99]: (error) Memory pointed to by 'p' is freed twice.
[main.c:99]: (error) Deallocating a deallocated pointer: p

検出できたリークパターン

上記のコードはぱっと思いつくメモリリークのパターンをいくつか起してみたコードになりますがいい感じに検出してくれています。

検出内容として、

  1. 配列の添え字がおかしい場合(直値の場合)
  2. 配列の添え字がおかしい場合(マイナスの直値の場合)
  3. 配列の添え字がおかしい場合(ループ変数の場合)
  4. 配列の添え字がおかしい場合(引数の場合)
  5. mallocのfree忘れ
  6. 2重解放(freeしすぎ)

の6パターン検出しています。
なかなか優秀ですね。

検出できなかったリークパターン

上記のコードで検出できていないのは、 test_buf_over_run 関数の呼び出し、

    test_buf_over_run(s_test_buf, 256);

のケースです。
16byteで確保しているバッファに対し、256byte分の書き込みを行うというリークですが、さすがにこのパターンは検出できていません。

manualを読んでみた。

cppcheckには簡単なマニュアルがついています。

http://cppcheck.sourceforge.net/manual.pdf

一通り読んでみましたが思っていたより高機能のようで驚きました。

面白いと思ったのは、ソースコードではなくライブラリとして提供される関数のチェックをしたい場合、チェックしたい関数について設定ファイルに情報を書いて置くことでその関数も設定に従ってチェック対象になるそうです。そういった面では、拡張性を意識して作ってあるようですね。

感想

マクロの解析を含む構文解析、簡単な意味解析は行っていそうですが、関数をまたいだリークの検出までは行っていなさそうです。
なので、cppcheckによって100%メモリリークがなくなるとは言えない気がしますがソフトの品質を高めるのには有効なツールのような気がします。

今回はLinuxコマンドライン環境で試してみましたが、VisualStduioやEclipseプラグインもあるそうなのでGUIの開発環境からも連携して使えるようです。

普段の仕事でもうまく活用できないかもうすこしいろいろ試してみたいと思います。

Let's encript Failed authorization procedure ではまった

Let's encryptを使ったSSL証明書取得に挑戦してみたのですが、ハマりました。

3日ほど、色々ためした挙句、無事証明書を取得できたので、注意点など書いておきたいと思います。

参考にさせて頂いたサイト・記事

こちらのサイトの参考にさせて頂きました。ありがとうございます。

tsuchikazu.net

qiita.com

やりたかったこと

CentOS6.7上のNginxでWebサービスを立てているのですが、これまで使っていたStartSSLの証明書の期限が切れるので
Let's encryptに移行しようと思い上記サイトを参考にコマンドをたたいてみましたが正常に証明書が取得できませんでした。

実行コマンド


sudo ./letsencrypt-auto certonly --webroot --webroot-path ドキュメントルートのパス -d ドメイン

何度コマンドを実行しても、

Failed authorization procedure

のエラーメッセージが出てきます。

認証の流れ

証明書取得のコマンドである、
./letsencrypt-auto

Pythonスクリプトですが、指定されたドキュメントルートに認証用のページデータを作成し、そのページに外部からアクセスできれば認証OKと判断しているようです。

どうもうまく動かないので、エラーメッセージでググったりした後、Nginxのアクセスログを見ながらコマンドを実行してみると、そもそもそれらしいアクセスが見当たりませんでした。

...



...



...



...



...

はい。


数秒考えて、原因分かりました。


そうです。火の壁が原因でした。

Let's encrypt ははInternet Security Research Group (ISRG)という海外の団体が運営しているそうです。

letsencrypt.org

不正アクセス対策のため、アメリカからのアクセスを含む海外からのアクセスはシャットアウトしていたのをすっかり忘れていました。

mcommit.hatenadiary.com


というわけで、一時的にファイアウォールを無効化し、再度コマンドを実行してみるとあっさりと証明書が取得できました。
証明書取得後はWebサーバの設定ファイル内で証明書・秘密鍵のパスを記載してあげるだけで更新作業は完了です。

はまったといっても、自分で作った落とし穴に自分で落ちたような感じでした。

失敗しすぎ注意!

ちなみに短時間の間に何度もコマンドの実行に失敗すると、認証やりすぎってことで注意されます。


There were too many requests of a given type :: Error creating new authz :: Too many invalid authorizations recently.
Please see the logfiles in /var/log/letsencrypt for more details.

感想

Let's encrptは証明書が楽に取得・更新できてとても便利ですが、海外からのアクセスに対しファイアウォールの設定をされている環境では注意する必要がありますね。

サーバの管理って、ちょっとしたことでもやったことが無かったり、久しぶりに触ったりすると結構な確率ではまる気がします。


nginx実践入門 (WEB+DB PRESS plus)

nginx実践入門 (WEB+DB PRESS plus)

Pocoを使ってHTTPS通信するコードを書いてみる

少し前に知ったのですが、C++にPocoというとても便利なライブラリがあります。
HTTPS通信を実装する際に使ったのですが、便利だったので使い方等を書いておきたいと思います。

幅広い機能

PocoにはJSONXMLのファイルフォーマット処理の機能があります。
また、ほかのライブラリに依存はしますが、SQLite,MySQL,ODBCのようなデータベースに関する機能、
FTPやHTTPといったネットワークに関わる機能など幅広い機能も提供されています。

https://pocoproject.org/features.html

ライセンスは?

Boost Software Licenseで公開されています。
基本的にはライブラリを利用するにあたって、ソースコード著作権の表示があれば(公開を含めて)無償で利用できるそうです。

https://pocoproject.org/license.html

使い方

ここからソース一式をダウンロードします。
https://pocoproject.org/download/index.html

私が利用したのはUnix/Linux系向けの方ですが、Windows用のソースも提供されているようです。(VisualStdioでビルドできるようですね)
タイトルにも書きました、「HTTPS通信」には「Complete Edition」の方をダウンロードする必要があります。
HTTPS通信の機能はOpenSSLに依存しています。ここでは、OpenSSLはインストール済みのものとして説明します。

ビルドについて

Pocoの実体は各機能を提供する共有ライブラリ(.so)として生成されます。
./configure時に --static をつけることで静的ライブラリとしてビルドすることも可能です。
※試していませんがCMakeにも対応しているみたいです。

オプションがたくさんありますが、


$ ./configure --help

でコンフィグレーションに関するオプション情報を確認できます。

ビルド対象とするコンポーネントについても--omit オプションで取捨選択できます。
どのようなコンポーネントがあるかは、componentsというファイルに一覧が記載されています。


$cat components
CppUnit
CppUnit/WinTestRunner
Foundation
XML
JSON
Util
Net
Crypto
NetSSL_OpenSSL
Data
Data/SQLite
Data/ODBC
Data/MySQL
MongoDB
Zip
PageCompiler
PageCompiler/File2Page

ビルドまでのコマンド実行例


$ wget https://pocoproject.org/releases/poco-1.7.8/poco-1.7.8p3-all.tar.gz
$ tar xzfv poco-1.7.8p3-all.tar.gz
$ cd poco-1.7.8p3-all
$ ./configure --no-tests --no-wstring --omit=Data/SQLite,Data/MySQL,Data/ODBC,Zip,MongoDB,PageCompiler,PageCompiler/File2Page
$ make
$ sudo make install
$ sudo ldconfig

./configure は一瞬で終わるのですが、ビルドにはそれなりに時間がかかると思うので不要な手順は省略するようにオプションを指定した方がよさそうです。


デフォルトでは、ビルドされたライブラリ(.so)は /usr/local/lib にインストールされます。
ヘッダファイルは、/usr/local/include/Poco 以下にインストールされます。

ライブラリを使う前に ldconfig でライブラリ情報は更新しておく必要があります。

HTTPS通信のコードを書いてみる。

早速HTTPS通信をするコードを書いてみます。

#include <string>
#include <iostream>
#include <sstream>

#include <Poco/URI.h>
#include <Poco/Net/HTTPSClientSession.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/StreamCopier.h>
#include "Poco/Net/Context.h"
#include "Poco/Net/SSLManager.h"
#include "Poco/Net/AcceptCertificateHandler.h"
using namespace std;

int main(int argc, char **argv)
{

	// SSL接続情報 初期化
	Poco::Net::initializeSSL();
      Poco::Net::SSLManager::InvalidCertificateHandlerPtr ptrHandler ( new Poco::Net::AcceptCertificateHandler(false) );
      Poco::Net::Context::Ptr ptrContext ( new Poco::Net::Context(Poco::Net::Context::CLIENT_USE, "") );
      Poco::Net::SSLManager::instance().initializeClient(0, ptrHandler, ptrContext);


	try {
		Poco::URI uri("https://www.yahoo.co.jp/");
		Poco::Net::HTTPSClientSession session(uri.getHost(), uri.getPort());
		Poco::Net::HTTPRequest req("GET", uri.getPath(), Poco::Net::HTTPMessage::HTTP_1_1);

		// リクエスト送信
		ostream& ss = session.sendRequest(req);

		// レスポンス受信
		Poco::Net::HTTPResponse res;
		istream& rs = session.receiveResponse(res);

		stringstream sstr;
		Poco::StreamCopier::copyStream(rs, sstr);
		string response = sstr.str();
		cout << string("response:") + response << endl;
	}
	catch ( Poco::Exception& ex )
	{
		string msg = string("Poco Exception : ") + ex.what() + ", message: " + ex.message();
		cout << msg << endl;
	}

	return 0;
}

コンパイルと実行

HTTPS通信でリンクする必要があるライブラリはPocoNetSSLになります。
※StreamCopierとか使っているのでたぶんPocoUtilとかもリンクする必要があるように思いますが、PocoNetSSLでリンク指定しているのか、手元の環境では、ビルド時に指定しなくても動きました。



$ g++ main.cpp -lPocoNetSSL #上記のコードをmain.cppで保存した場合
$ ./a.out # 正常に実行されると、yahooのトップページが取得・表示されます

pocoのクラスについて

サーバーとのHTTPS通信は HTTPSClientSession クラスを使って行います。
通信を始める前にSSLManagerの接続情報を初期化しておく必要があります。
OpenSSL をインストールしていれば上記のようなコードで、OpenSSL のデフォルトの証明書を使って通信されます。

認証に使用する証明書を指定したい場合や、パスフレーズを使う場合は、Context クラスに証明書のファイルパスやパスフレーズ等を細かく指定する必要があります。
パスフレーズを使う場合は、PrivateKeyPassphraseHandler クラスを継承して、ライブラリ側にパスフレーズを受け渡しするコードを書く必要があります。

Poco のクラスのリファレンスは、

https://pocoproject.org/docs/

にあります。

HTTP通信してみるコード

ちなみに、SSLを使わない通常のHTTP通信をする場合は、 HTTPSession クラスを使うことで可能です。

#include <string>
#include <iostream>
#include <sstream>

#include <Poco/URI.h>
#include <Poco/Net/HTTPClientSession.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/StreamCopier.h>
using namespace std;

int main(int argc, char **argv)
{
	try {
		Poco::URI uri("http://www.hatena.ne.jp/");
		Poco::Net::HTTPClientSession session(uri.getHost(), uri.getPort());
		Poco::Net::HTTPRequest req("GET", uri.getPath(), Poco::Net::HTTPMessage::HTTP_1_1);

		// リクエスト送信
		ostream& ss = session.sendRequest(req);

		// レスポンス受信
		Poco::Net::HTTPResponse res;
		istream& rs = session.receiveResponse(res);

		stringstream sstr;
		Poco::StreamCopier::copyStream(rs, sstr);
		string response = sstr.str();
		cout << string("response:") + response << endl;
	}
	catch ( Poco::Exception& ex )
	{
		string msg = string("Poco Exception : ") + ex.what() + ", message: " + ex.message();
		cout << msg << endl;
	}

	return 0;
}


依存するクラスが減るのでコードも少しシンプルになりますね。
HTTP通信の場合はビルドにリンクするライブラリはPocoNetの方になります。

参考

Pocoの各コンポーネントにはサンプルプログラムが付属しています。
HTTPS通信の場合は、NetSSL_OpenSSL/samplesに

  • HTTPSでのダウンロード(HTTPSクライアント)
  • 時刻サーバ
  • Twitterクライアント

といったサンプルコードがあります。

感想

C++のライブラリというとBoostくらいしか知りませんでしたが、Pocoは非常に便利・高機能だなという印象を受けました。
あと、リファレンスもきちんと提供されていて、クラスの階層が奇麗なのでわからないことがあった時もソースを追っかけやすかったです。

Linuxでシリアル通信のプログラム(C言語)を書く

Linux上でC言語でシリアル通信をするプログラムを書く際に、いろいろ調べたので書いておきたいと思います。

シリアル通信プログラムの流れ

Linuxでシリアル通信プログラムを書く際の大まかな流れですが、

  1. シリアルポートに対応するデバイスファイルをオープンする
  2. termios構造体を使って通信設定する
  3. termios構造体の設定値をポートに反映させる
  4. read/write関数を使って通信する

というような流れになります。

シリアルポートのデバイスファイル

一般的にUnix/Linux系OSではシリアルポートは/dev/ttyS* にマッピングされています。tty はテレタイプ(teletype)を意味するそうです。

termios構造体

termios構造体を使ってシリアル通信のための設定をします。

■参考にするmanページ

https://linuxjm.osdn.jp/html/LDP_man-pages/man3/termios.3.html

設定処理は結構面倒臭い感じになりますが、おおむねビット毎にパリティやらデータビットやらの役割が決められているような感じですね。

制御コードに気をつけろ!

シリアル通信のプログラミングで気を付けないといけないことがいくつかあります。
termios構造体は名前の通り端末との通信を扱うことを意識して設定されています。

そもそもUNIXは現代のパソコンとは使い勝手の異なるコンピュータでした。
Unixシステムには、端末と呼ばれるキャラクタ画面とキーボードがついた装置が複数つながれていて、何か計算をしたい人たちはこの「端末」からログインし、コンピュータを使っていました。
そして、termios構造体はこの「端末」との入出力を制御するために使われていたのです。
今となっては懐かしい時代ですね・・・産まれてなかったけど。

前置きが長くなりましたが、センサや各種デバイスと通信する際は生データ(8bit)で通信することが多いと思いますが、
termios構造体を使う場合そのままでは生データとして受信できません上位ビットがマスクされてしまうようです。

生データを扱う場合は、cfmakeraw関数により設定値を生データの送受信用の値にしてあげる必要があるそうです。

ちなみに、cfmakerawの中の人は、

 termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
    termios_p->c_oflag &= ~OPOST;
    termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    termios_p->c_cflag &= ~(CSIZE | PARENB);
    termios_p->c_cflag |= CS8;

をしてくれているそうです。

raspberry piで動かしてみる。

というわけでtermios構造体を使うC言語のコードを書いてみました。
以前Raspberrypiで、Rubyを使ってシリアル通信をする記事を書きましたが、

mcommit.hatenadiary.com


今回はtermios構造体を使うC言語のコードを書いて、Raspberry piで動かしてみました。
ソースコードはこんな感じです。

Raspberry pi で動かしてみたコード(C言語)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>

#define SERIAL_PORT "/dev/serial0"

int main(int argc, char *argv[])
{
    unsigned char msg[] = "serial port open...\n";
    unsigned char buf[255];             // バッファ
    int fd;                             // ファイルディスクリプタ
    struct termios tio;                 // シリアル通信設定
    int baudRate = B9600;
    int i;
    int len;
    int ret;
    int size;

    fd = open(SERIAL_PORT, O_RDWR);     // デバイスをオープンする
    if (fd < 0) {
        printf("open error\n");
        return -1;
    }

    tio.c_cflag += CREAD;               // 受信有効
    tio.c_cflag += CLOCAL;              // ローカルライン(モデム制御なし)
    tio.c_cflag += CS8;                 // データビット:8bit
    tio.c_cflag += 0;                   // ストップビット:1bit
    tio.c_cflag += 0;                   // パリティ:None

    cfsetispeed( &tio, baudRate );
    cfsetospeed( &tio, baudRate );

    cfmakeraw(&tio);                    // RAWモード

    tcsetattr( fd, TCSANOW, &tio );     // デバイスに設定を行う

    ioctl(fd, TCSETS, &tio);            // ポートの設定を有効にする

    write(fd, msg, sizeof(msg));    // デバイスへ255バイト書き込み
    printf("%s", msg);

    // 送受信処理ループ
    while(1) {
        len = read(fd, buf, sizeof(buf));
        if (0 < len) {
            for(i = 0; i < len; i++) {
                printf("%02X", buf[i]);
            }
            printf("\n");
        }

        // エコーバック
        write(fd, buf, len);
    }

    close(fd);                              // デバイスのクローズ
    return 0;
}

Raspberrypi のデバイスファイルについて

前回の記事でも書きましたが、Raspberrypiのシリアルポートのデバイスファイルは "/dev/serial0"としてマッピングされています。
手元のRaspberrypiだと/dev/ttyS0だとopenは成功しましたが、うまく通信できませんでした。ご注意ください。

結線は以前の記事でも書きましたが、
6 :GND
8 :TXD
10:RXD
になります。

感想

組み込みソフトのプログラミングだとシリアル通信のプログラミングは比較的簡単な部類に入ります。
UARTのデバイスドライバは割り込みハンドラを入れてもC言語で200~300行程度にしかなりません。
UARTは各種デバイスやセンサなどとのインターフェースとして使われますが、「端末」との通信のように制御文字処理する必要はありません。
今回termiousを使ってみてその辺の作法がよくわからず少し苦労しました。

現代ではサーバへのログインアクセスはもっぱらSSHになりました。
RS-232Cを使ったシリアルコンソールログインなどは組み込みLinuxを使う時くらいしかありません。
(SSHと違ってシリアルコンソールだとviがうまく動かなかったりするんですよね...)

そのためか「termios/端末プログラミング」について触れられている書籍はあまりありません。

いろいろと本棚の書籍やKindleをあさっていると、何と詳解Unixプログラミングには「端末入出力」の章があるではありませんか!サンクス、Mr.スティーブンス.

詳解UNIXプログラミング 第3版

詳解UNIXプログラミング 第3版

詳解Unixプログラムは分厚くて机に置いておくと邪魔になるので普段めったに読まない本ですが、こういうときに役に立つんですね。Unix/Linux系のコードを書くときはやはりありがたい一冊です。

ちなみに、Linuxプログラミングインターフェースという本でも端末プログラミングについて解説されている章があるようです。こちらの本は読んでみたいのですが、ちょっと高いな・・・

Linuxプログラミングインタフェース

Linuxプログラミングインタフェース

termiousを使うプログラムはUnixとしての「端末プログラミング」の作法を押さえておく必要がありそうです。

あと、よく考えてみればRubyのserialportがRaspberry pi上で動いているので、素直に拡張ライブラリのコードを読んでみるというのも1つの手段だと思いました。時間があるとき読んでみたいと思います。