simotin13's message

simotin13といいます。記事の内容でご質問やご意見がありましたらお気軽にコメントしてください\^o^/

C言語の関数の戻り値を実行時に変更する

「試験をしているんだけど、実行中に関数の戻り値を変える方法はないかな?」

と相談を受けました。

C言語単体テストをする場合、システムコールやライブラリ関数はダミーの関数を用意しておいて自作部分の実装をテストすることはよくありますが、実行中にこのダミー関数の振舞いを変えたいという趣旨です。*1

単純にダミー関数の戻り値をグローバル変数にしてしまえば問題はありません。
ただしテストコードが多いと呼び出し元のテストコードが少し複雑になりそうです。

少し考えてみましたが、

DLL,共有ライブラリならできるのでは?

というのが私の思いついた答えでした。

linuxだとdlopen,dlsym,dlcloseを使って、ダミー関数を共有ライブラリにしておけば実際に呼び出される関数を変更することができます。

ということでそれっぽいコードを書いてみました。

試してみたコード

呼び出し元のソースコード(main.c)

#include <stdio.h>
#include <dlfcn.h>
#include <unistd.h>

int add(int a, int b)
{
    int result;
    void *handle_a = dlopen("./liba.so", RTLD_LAZY);
    int(* add_handler)(int, int) = dlsym( handle_a, __FUNCTION__ );

    result = add_handler(a, b);

    dlclose(handle_a);
    return result;
}

int main(int argc, char **argv)
{
    int result = 0;

    while(1)
    {
        result = add(1,2);
        sleep(1);
        printf("%d\n", result);
    }

    return 0;
}

ダミー関数の実装(add.c)

int add(int a, int b)
{
	return a + b;
}

ビルドと実行


$ gcc -shared add.c -o libadd.so
$ gcc main.c -ldl
$ ./a.out
$ 3
$ 3
$ 3
$ 3
....(1秒間隔で出力される)

共有ライブラリのaddが呼び出されてadd(1,2)の結果が正しく(1+2=3)出力されていることは分かります。

さてここからが本番です。
add.cの中身を

int add(int a, int b)
{
	return a * b;
}

のように書き換えてコンパイルしてみましょう。
この時、先ほど実行した a.out は停止せず動かしっぱなしにします。

別ターミナルで、


$ gcc -shared add.c -o libadd.so

すると、実行している a.outの出力はどうなるでしょうか。


3
3
3
3
2
2
2
2

2に変わっています!
ということで実行中に関数の戻り値を変える事ができました。

わざわざ共有ライブラリを動的にロードするのはまどろっこしい気もしますが規模が大きいテストではこちらの手法の方が保守性が高い気もします。

補足

呼び出し側で sleep(1)を呼び出している箇所がありますが、最初sleep無しで試したところセグメンテーションフォルトで a.out は止まってしまいました。
add.cをコンパイルすることによってロードしているライブラリのファイルに対して競合してしまったんだ思います。
sleep(1)を入れて試した限りでは問題なく動作がしましたが、きちんと排他制御を行うのは正しい姿だと思います。

JITコンパイルについて

rubyでも 2.6 からJITコンパイルが取り入れられたそうですが、今回試した実行時にライブラリを差し替えるのはアプローチとしては同じようなイメージになるかと思います。今回のadd関数は1行で終わるシンプルな関数なので面白くありませんが、これが処理時間のかかる関数だったとして実行中に処理時間のかからない関数に差し替える事ができれば嬉しいですよね。実行中のデータなどで関数の処理にフィードバックをかけて最適な処理にしていくといったこともできます。
*2

感想

同じような事は以前にも試してみていたのですが、単体テストなんかで使えるテクニックだとは気づいていませんでした。

mcommit.hatenadiary.com

この手のテクニックはどの言語でも何かしら手段が提供されていると思います。
私の知っている範囲ではC言語ですと、BINARY HACKS、RubyですとメタプログラミングRubyでそういったテクニックが解説されています。

Binary Hacks ―ハッカー秘伝のテクニック100選

Binary Hacks ―ハッカー秘伝のテクニック100選

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

メタプログラミング.Netという本もあるんですね。面白そうなので読んでみたいと思います。

メタプログラミング.NET (アスキー書籍)

メタプログラミング.NET (アスキー書籍)

動的に何かを変更するのってなんかかっこよくて面白いですね。

*1:正常系のケースを実行した後そのまま異常系を試験したいというような状況ですね

*2:人工知能系のプログラミングもこういった形で実装できそうですね。

社長失格を読んだ

最近社長失格という本を読みました。だいぶ前にも読んだことがある本だったのですが、久しぶりに読んだら面白かったので読書録を書いておきたいと思います。

読みたくなった理由

実は私が、この本を読むのは2回目になります。最初にこの本を読んだのは2013年頃だったと思います。当時、会社を辞めて個人事業主になったばかりで、起業や経営に関する本を読み漁っていました。この「社長失格」のほかに「不格好経営」、「今から君が社長をしなさい」など読んでました。

久しぶりに読みたくなった理由ですが、twitterで「平成ネット史」というハッシュタグのついたツイートがTLに沢山流れてきていて、
その中にハイパーネット社と夏野剛さんとに関するツイートを見つけて、この本のことを思い出し、懐かしさのせいか急に本書を読みたくなりました。
初めて読んだときはブックオフの中古で買ったような気がしますが、Kindle版が出版されており、すぐに読みたいという気持ちも強くてポチってしまいました。

面白かったところ

この本は板倉さんの体験談ですので基本的にノンフィクションの自伝ということになると思うのですが、物語として非常にわかりやすい筋書きになっていて、
前半はダイアルQ2やインターネットを使った画期的なアイデアで順風満帆に会社に板倉さんの会社が成長していく様子がかかれています。
そしてビルゲイツにあってビジネスを賞賛されるところをピークとして、後半はビジネスモデルの弱点に気付いた銀行団に見切りをつけられ融資の返済に追われ転落していくというストーリーです。後半で、銀行の人が貸したお金を取り立てに来た時の様子などは読んでいて怖くなってくるものがありました。

多少の脚色などはあるのかも知れませんが、恐らく記憶を頼りに当時の様子を臨場感あふれる文体で書かれていて引き込まれるものがありページをめくる手が止まりませんでした。

感想

日本人は社会的弱者に厳しいというイメージがありますが、著者の板倉さんのように何かで失敗した人に対しても接し方が厳しいのではないかと思いました。読んだ後で思いました。
「敗軍の兵はよく学ぶ」と言いますが失敗した人や組織に属していた人がその後に成功するというのはよくあるのではないでしょうか。本書で登場する夏野さんもハイパーネットを退職された後はドコモでi-modeの開発で活躍されたわけですし。

関係者で多少なり不快に思われる人はいるのかも知れませんが、過去を振り返って、失敗した原因などを分析し語るというのは失敗した人にしかできないことですし、ある意味ではその失敗というのは人々に語ることによってその人が世の中に提供できる資産であるように思います。ビジネスに限らずこの書籍のような失敗談を語るというスタイルのコンテンツは(失礼ですが)面白いし、得られるものも多いのではないのかと思いました。

社長失格

社長失格

Ethernet2(DIX)のフレームについて

来年から仕事でお世話になるであろう方からネットワーク力についていろいろ質問されたけどネットワーク良く分かっていないので、勉強がてらCCNAの資格でも取ろうと思って真面目にネットワークについて勉強を始めることにしました。

そもそもTCPUDPを扱うプログラミングは経験がありますし、FTPとかHTTPとかのサーバは実装したことがあって、あと他にもFA系のプロトコルは沢山実装したことがあるので少しはネットワークについては分かっているつもりだったのですがいざ勉強してみるとよくわかっていないことが多いことに気づかされました。

せっかく勉強しているので、調べたこととか備忘の為に書いてきたいと思います。

今回はレイヤー2。Ethernetフレームについてです。

Ethernetフレームの規格

Ethernetのフレームには、

の2つの規格が存在する。現在の主流はEthernet2の方。
ちょっと気になってRX63Nのハードウェアマニュアルを見てみたのですがイーサネットコントローラはIEEE802.3規格のフロー制御準拠とありました。PHYのチップは802.3準拠のチップが多いからでしょうか...

Ethernet2のフレームフォーマット。

フィールド サイズ(byte) 内容
プリアンプル 8 7byteの間 0xAA(10101010)を送信 8byte目で 0xAB(10101011)を送信
送信先MACアドレス 6 フレームの送信先
送信元MACアドレス 6 フレームの送信元
タイプ 2 上位のプロトコルタイプ
データ 46-1500 送信データ
FCS 4 送信先MACアドレスからデータ部までのCRC
IFG 12 フレームの区切りを表す96bit分の無通信区間

受信データサイズについて

Ethernetは可変長のフレームであるにも関わらずデータ長をフィールドはありません。

じゃあフレームの区切りはどうやって識別するんだ?

と疑問が湧いたのですが、調べてみるとFCSの後に96bit分の無通信時間(IFG:Inter Frame Gap)が入ることでフレームの区切りを識別するようです。

ちなみにwiresharkでパケットを表示すると送信先MACアドレス~データまでは表示されますがプリアンプルとFCSは表示されません(もちろん無通信時間もですが)

どちらもレイヤ1で処理されているからだと思いますが、Ethernetのフレームに問題がある場合*2トラブルシューティングができないですね。

タイプについて

2byteのタイプフィールドで、上位レイヤ(レイヤ3)で使用するタイプを指定します。
よく使われるIPv4は0x0800です。
PPPoEの仕様とかよく知らないですけどISPとの接続はPPPoEであることが多いと思うので実はお世話になっているプロトコルですね。

タイプの例

タイプ プロトコル
0x0800 IPv4
0x0806 ARP
0x8100 IEEE802.1Q
0x86DD IPv6
0x8863 PPPoE Discovery Stage
0x8864 PPPoE Session Stage

参考書籍

パラパラと読んでみましたが、Ethernetのレイヤ1、レイヤ2に関しては詳説 Ethernetでかなり詳しく書かれています。
IFGの話も付録に書かれていました。

詳説 イーサネット 第2版

詳説 イーサネット 第2版

次は802.1QとかL3スイッチについて勉強する予定。

*1:DIXはDEC,Intel,Xeroxの略

*2:例えばFCSが一致しない

Cコンパイラ作成入門をarmで試してみる

久しぶりに記事を書きます。

このところ数学とか電気回路の勉強ばかりで、仕事以外でコードを書いていないので気分転換にコードを書きたい欲が強くなっていたのですが、@rui314さんがCコンパイラ作成入門という面白そうな本を書かれているそうなので自分もC言語でのコンパイラ開発試してみたいと思います。

低レイヤを知りたい人のための Cコンパイラ作成入門

raspberrypiに移植してみる

このコンパイラ作成入門の本は、任意のexitコードを返すアセンブラを出力するところから始めるようですが、環境としてx86_64/linuxもしくはx86/linuxを想定されています。
私は普段windowsを使っていて、*nix系のコードはcygwin上で書いているのでそのままではコンパイルが出来ません。
どうしたものかと思ったのですが、手元でいつでも気軽に動かせるlinuxというとraspberrypiになるのでarmの勉強も兼ねてこの本のコードをraspberrypiに移植するような形で遊んでみたいと思います。

任意のコードでのreturn

コンパイラ本体の作成は、コマンドライン引数で受け取った数値でreturn(exit)するアセンブラを出力するコードから始まるのですが、とりあえずそれっぽいコードを書いてみました。

main.c

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

int main(int argc, char **argv)
{
	FILE *fp = stdout;
	
	if (argc < 2) {
		fprintf(stderr, "input code\n");
		return -1;
	}

	fprintf(fp, ".text\n");
	fprintf(fp, ".global main\n");
	fprintf(fp, "main:\n");
	fprintf(fp, "  mov r0, #%d\n", atoi(argv[1]));
	fprintf(fp, "  bx lr\n");
	return 0;
}


$ ./a.out 39 > tmp.s
$ gcc -o tmp tmp.s
$ ./tmp
$ echo $?
39

うむ。受け取った引数でexitできてそう。

armのアセンブラについて

戻り値に使うレジスタ

armでは戻り値に使うレジスタは r0 レジスタを使っている。

return命令

armでは関数の呼び出し元に戻る命令はないようで分岐命令で戻るみたいだ。なのでスタックの操作は自分でしておく必要がある。bx は命令セットの切替(thumb/arm)を伴う無条件分岐命令らしい。lrはr14レジスタエイリアスでリンクレジスタのこと。関数呼び出しの際に戻り先のアドレスをのlrレジスタに退避しておく。

移植してみたコードでは、コマンドライン引数の値をr0レジスタに入れて呼び出し元に分岐している。armのABIはよく分かっていないけどとりあえず動いたのでよしとしよう。

感想

とりあえず触りのところを遊んでみましたが、まずはmain関数のアセンブラを吐いてみるっていうのはなかなか斬新だと思いました。linux/gccだとリンカがよきに計らってくれるのでこれはいい!

armのアセンブラよく知らないけどx86_64/x86と比べるとどうなんだろう。RISCということで出力するアセンブラの量は多そうですが、なんとなくエンコーディングとかは直感的な気がします。
ということで、バックエンドまでいくと実はarmのほうが楽チン!という落ちが待っていることを期待つつぼちぼち遊んでみたいです。

Concolic Testってなんだ?

自分が常日頃考えている課題として「組込ソフトウェアのユニットテストを自動化できないか」という課題があるのですが、ここ数日テストの自動化に関する論文やオープンソースの情報を漁ったりして少しですがインプットがたまったので整理の為にアウトプットしておきたいと思います。だらだらと書いてしまった感があるのでまとまりは微妙です。

Concolic Testとは

そもそもConcolicとはなんでしょうか。調べてみるとSymbolicとconcreteを書けた造語のようです。Symbolicというのはシンボリックテスト・シンボリック実行のことで、動的テスト手法の1つです。
そもそも私はシンボリック実行を知りませんでしたので以下の3サイトの情報を参考にさせて頂きました。


はじめてのコンコリックテスト
http://jasst.jp/symposium/jasst15tokai/pdf/S4-1.pdf

ntddk.github.io

www.kzsuzuki.com


参考にさせて頂きまして、ありがとうございます!

さて、シンボリック実行についてですが、シンボリック実行では実際に実効対象のコードが実行されるわけではありません。SATソルバが制約条件を満たす値を求める計算が行われるだけです。*1

かいつまんでの私の理解ですが、シンボリック実行が抱える課題として、シンボルだけでは経路を求める計算が発散してしまい実用的でないという課題があるようです。
そこで登場したのが入力値に具体的な値を入れてみるという手法でシンボリック実行と組み合わせるといい感じになるということで

Symbolic + concrete = concolic

ということでConcolic Testという手法が出てきたそうです。

テストは金がかかる

そもそも自分がなんでconcolinic testに関心を持ったかというと、テストは面倒で時間がかかるからでした。時間がかかる=金がかかるということでもあります。
組み込みの製品開発におけるテスト工程にはとても時間がかかります。組み込みソフトウェア開発での単体テストの場合、実機で動かすことが求められ、オシロ・ロジアナといった測定機器を使用してハードウェアレベルでの確認が必要な場合もあります。通信の場合はWireShark等を使ってパケットの確認なんかも必要です。

実機で動かす都合上、テストの自動化がとても難しく、他のソフト開発と違って回帰テストは容易に実施できません。

またカバレッジは基本的にC2カバレッジを満たすことが求められるので呼び出し側のコードを書くのにも多少頭を使う必要があります。
実行はともかくとしてC2カバレッジを満たすテストコードを書くというのは関数内に存在する条件分岐などの制約(constraint)に従って書くわけですが、ちょっと面倒なパズルみたいなものでこれはソルバーでも解ける問題です。ということで自動化ツールを探していていました。なかなか情報が見つからないので自分で作ってしまいたいと思っていました。

論文・参考記事等

情報収集にあたって読んだ論文などの一覧をここに残しておきたいと思います。

CUTEというConcolic Test Toolに関する論文

http://mir.cs.illinois.edu/marinov/publications/SenETAL05CUTE.pdf

CUTEというC言語向けConcolic Testツールに関する論文です。Concolic Testで検索すると上位に出てきました。
論文の概要ですが、

CUTEというツールを使ってSGLIBという汎用的なデータ構造を提供してくれるライブラリをテストしてみた結果...

という感じでしょうか。もちろんconcolic testのアプローチの手法についてきちんと述べられています。ただしCUTEのソースコードはどうも非公開のようです。

詳細は詳しく読んでいないところもありますが、

  • 有限の深さ優先探索でシンボリック実行を行い、必要に応じて具体値を代入するアプローチをとった。
  • この検証作業によってSGLIBのバグを2件検出した(うち1件は割と深刻なバグだった)
  • テストデータの動的な作成・削除の有効な方法をしめすことができた
  • Java版もつくって並行して試してる
  • 暗号プロトコル脆弱性に対して代数的なアプローチで攻撃を行うソフトについても調べてる

論文中で面白かったのはC言語だとポインタ型を引数に取ることがしばしばありますが*2ポインタ引数をテストする手法は既に先行事例が存在していてCUTEではそれらの事例を参考に実装したそうです。

A Framework for Generating Object-Oriented Unit Tests using Symbolic Execution

http://mir.cs.illinois.edu/marinov/publications/XieETAL05Symstra.pdf

私もRubyC言語のテストコードの生成ツールを書いていた時期があったのですがポインタ型はどうすればいいのかで躓きました。*3

はじめてのコンクリックテスト

http://jasst.jp/symposium/jasst15tokai/pdf/S4-1.pdf

CRESTの活用事例

http://sea.jp/ss2015/paper/ss2015_C1-4(2).pdf

この論文は株式会社デンソーの方が発表された論文です。実際に社内でCRESTを活用された事例を紹介されています。2015年に発表されたようですのでもう3年も前です...
「7.おわりに」でテストケースの作成作業の効率化にCRESTが活かせた旨が記載されています。変数のサイズによってテストケースの生成時間が長くなることが課題として挙げられています。*4

http://debugeng.com/concolic%20testing.pdf

C言語で使えるConclonic Test Tool

CREST

ユニットテストのコードを自動生成してくれる(Conclonic Test)
http://www.burn.im/crest/

CUTEと違ってこちらは公開されています。まだ試していません...

CS453 Automated Software Testing

KAISTの先生のテスト自動化に関する講義資料です。
http://swtv.kaist.ac.kr/courses/cs453-fall14/

LLVM/Clangを使ってカバレッジツールを作ったりする宿題があるみたいで面白そうです。
ちょっと前ですが、私もこちらで紹介されている内容を参考に、Clangのライブラリを使ってC言語のASTを拾うサンプルを書いてみました。
Clangは直交性の高いクラス設計を意識しているのか、ASTだけでたくさんクラスが切られていて出力内容を理解しきれずに挫折しています・・・*5

github.com

感想

ソフトウェア工学の強さと現場への浸透

少しですが論文とかGithubとか見た感じではシステム工学・ソフトウェア工学の分野でもやはりアメリカの大学が強そう。日本の大学でも研究している研究室はあるのかも知れないけど、開発の現場までそれがリーチしているという感覚は少なくとも自分の知っている範囲ではあまりない。

[追記]
ICSE勉強会なる勉強会が開催されていることを知りました。面白難しそう。concolic testも取り上げられているようなので日本でも研究している方はそれなりにいらっしゃるようです。

アメリカ人はなんでもシステマチックに問題を解決しがちで、日本人は職人魂に頼りがちというかそういうイメージがある。計算機が普及した時代にどちらが有利なのかはいうまでもない気がする。

計算機でも意外と簡単に解けない問題があって、現実な解決策としてどう落とし込んでいくかを考えるというのは好きな分野かもしれない。

日本はモノ作りの国とか言われるけど、膨大な時間がかかる作業を人手に頼るというのは女工哀史とか蟹工船的なイメージがあるのでどうかと思う...

計算機にも人権があるみたいな考え方をした場合、自動化は道徳的に悪になると思うけど何を大切にしたいかという問いへの答えがアプローチの分かれ目になっていると思う。

自動化や工学的アプローチをどれくらいリスペクトするかというのは「どれくらい、人間もしくは人間の時間をリスペクトするか?」と相関しているのではないだろうか...

何をassertするか

先日のkernelvm関西のときに

テストコードの生成というのは現実的なレベルで実現できるけど、じゃあ自動でコードをテストするときにどこにassertを入れるかというのは計算機では解決できない

という話を聞いた。

それはその通りで「仕様」というのは計算機が決めるものではないので仕方がない気がする。「仕様」まで計算機が想定してくれるというのはパターンマッチのようなことをすればできるようになるかもしれないけど精度は期待できない気がする。

組み込みソフト開発現場ではデンソーの事例にもあったようにテストコードの生成が自動化されるだけでもめちゃめちゃ効率があがる。というのもC2カバレッジを狙ってテストコードを書くというのはコードを書いた本人でも頭を使って考える必要がある。テストケースを考えはじめると異常系とかの入力なんかは基本的にはなんでもいいのについつい現実的なケースを考えてしまったりする。要するに頭が勝手に動いて疲れる。テストコード考えて書くのはやはり面倒だ。何をassertするかは計算機が判断できなくても構わない。むしろ自動で出来上がったカバレッジの結果を解釈して意味を見出していくというのが人間の仕事なんだと思う。

開発プロセスに関して現在では、コードレビュー→単体テストという流れが一般的だが、テストコードの生成が自動化され、テストの実行コストが下がれば、単体テスト→コードレビューという順番での開発も可能になる気がする。そうなるとレビューする観点も変わって面白いんじゃないだろうか。*6

動的なテストの研究の歴史は浅い

論文や論文中の参考文献の発表時期を見てもテストの自動化に関する研究はまだまだ浅いように感じた。2000年代の論文はテスト手法に関する論文が多いようなので、静的解析によるテストが研究されていた時期かもしれない。動的テスト手法に関する論文が出てきたのは2000年半ば~2010年代にかけてっぽい。違ってたらすいません。

[追記]
書き終わった後でさらにネット漁ってたらこちらの記事に「シンボリック実行は1970年代から広く知られている」と書かれていました。そうなのか・・・ごめんなさい、まぁ産まれてなかったということで許してください(涙)

d.hatena.ne.jp


話はそれるけど、仕事でcoverityのテストツールを使わして貰ったことがあって、非常に便利で出来の良いテストツールだった。*7

あのcoverity社は設立されたのが2009年11月とのことなのでまだ10年にも満たないのですね。coverityはスタンフォード大学のテストに関する研究からスタートしたそう。
スタンフォードは特にそういう印象があるけど、アメリカの大学のこういった研究をビジネスにして成功していく実学的なスタイルは好き。

日本人もアメリカ人のこういった気質はもう少し見習ってもよいようにも思うけどあまりそういう事例を聞かないのはなぜでしょうか。

まぁ答えはある程度分かってはいるのですが、せめてもう少し一般の人や企業の経営者がテクノロジーに対するリスペクトと好奇心を持つようになってもいいのではないかと思う。

追記

[2019/4/12]
この記事でカバレッジの指標をC0と書いておりましたが、C2の誤りです。本文のC0をC2に訂正致しました。
組込のテストでは一般的にC2(条件網羅 コンディション・カバレージ)の網羅が求められます。
*8

*1:将棋で実際に駒を動かさずに「こうすればああなる」を先読みするのと同じようなイメージかと思います。

*2:特に構造体や文字列はポインタで渡しますね

*3:変数名から想像して解決すればいいのではとか思ってました...

*4:これは実際に自分でもCRESTを使って確認してみたい

*5:ClangでASTを出してゴニョゴニョしたい人には少しは参考にして頂けるのではないかと思います

*6:テストが自動化されても実装漏れは検出できないのでレビューが必要なのは変わりない

*7:年間100万円くらいのライセンス費が必要なので、「使えない窓際社員を首にしてうちの会社でもこれ買いましょうや!」と言って上司に白い目で見られた

*8:会話とかでもよくC0とC2を逆の状態で話してしまいます...