2011年10月1日土曜日

CLangでクラスの中身を調べる(4)

前回の続き。アサーションではなくエラーメッセージにする方法の検討。

どうもclang::DiagnosticのメンバにClientというのがあり、それがnullptrであるときにアサーションされるようだ。

ってことで、改めてマジメにclang::Diagnosticsについて調べてみる。.hのドキュメントを見ると、clang::DiagnosticClientを出力先に指定しなければならないようだ。もし自分が診断結果の出力先のクラスを作るなら、きっと出力先を切り替えられるよう抽象化するだろうと思ってpublic DiagnosticClientで検索すると、以下のようなものがヒットした:
  • ChainedDiagnosticClient
  • TextDiagnosticBuffer
  • TextDiagnosticPrinter
  • VerifyDiagnosticsClient
  • StoredDiagnosticClient
  • FixItRewriter
  • IgnoringDiagClient
  • PathDiagnosticClient
最もそれっぽいclang::TextDiagnosticPrinterを使ってみる。

clanng::TextDiagnosticPrinterのコンストラクタはllvm::raw_ostreamとclang::DiagnosticOptionsを要求する。
前者は出力先ストリームであるからさらに抽象化されているのではないかと思って検索すると、やはり派生クラスがいくつか出てくる。ひとまずstd::ostreamに出力すると書いてあるllvm::raw_os_ostreamを使ってみる。
後者は色々設定できるようだが、まずはデフォルト設定で使ってみる。
インクルードに
#include <iostream>
#include "llvm/Support/raw_os_ostream.h"
#include "clang/Frontend/DiagnosticOptions.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
を追加して、以下のコードを記述。
	llvm::raw_os_ostream diagOStream(std::cout);
	clang::DiagnosticOptions diagOpts;
	clang::TextDiagnosticPrinter diagPrinter(diagOStream, diagOpts);

	llvm::IntrusiveRefCntPtr<clang::diagnosticids>
		diagIds(new clang::DiagnosticIDs());
	clang::Diagnostic diag(diagIds, &diagPrinter);

ビルドするとclang::TextDiagnosticPrinterがリンクエラーになるので、clangFrontendプロジェクトを依存関係に追加。

で、実行すると・・・今度はTextDiagnosticPrinterでアサーションされるorz
メンバにLangOptionsがいて、そいつがnullptrであるとのこと。そのメンバ変数を設定するAPIはTextDiagnosticPrinter::BeginSourceFile()のみであるようだ。親クラスであるclang::DiagnosticClientの.hに記載のドキュメントによると、ソースファイルの処理が開始されたこと通知するためのコールバックであると書かれている。

そこでBeginSourceFile()の呼び出しルートを調べる。

■clang::DiagnosticClient::BeginSourceFile()の呼び出し元
  • clang::ASTMergeAction::ExecuteAction()
    ※ASTMergeActionはclang::FrontendActionの派生クラス。
  • clang::FrontendAction::BeginSourceFile()
■clang::FrontendAction::ExecuteAction()の呼び出し元
  • clang::FrontendAction::Execute()
■clang::FrontendAction::BeginSourceFile()の呼び出し元
  • clang::ASTUnit::Parse()
  • clang::ASTUnit::getMainBufferWithPrecompiledPreamble()
  • clang::ASTUnit::CodeComplete()
  • clang::CompilerInstance::ExecuteAction()
■clang::FrontendAction::Execute()の呼び出し元
  • clang::ASTUnit::Parse()
  • clang::ASTUnit::getMainBufferWithPrecompiledPreamble()
  • clang::ASTUnit::CodeComplete()
  • clang::CompilerInstance::ExecuteAction()
■clang::CompilerInstance::ExecuteAction()の呼び出し元
  • clang::ExecuteCompilerInvocation()

うーん、CompilerInstanceやASTUnitはプリプロセッサより後段であると思う。では自前でコールするのがいいのか?
正しいやり方かどうか怪しくなってきたが、試してみると・・・アサーションされなくなり、プリプロセッサのエラーが確認できるようになった。
#include <iostream>
#include "llvm/Support/raw_os_ostream.h"
#include "llvm/Support/Host.h"
#include "clang/Frontend/DiagnosticOptions.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/TargetOptions.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Lex/Preprocessor.h"

int main(int, char*[])
{
	// Preprocessor をセットアップする。

	llvm::raw_os_ostream diagOStream(std::cout);
	clang::DiagnosticOptions diagOpts;
	clang::TextDiagnosticPrinter diagPrinter(diagOStream, diagOpts);

	llvm::IntrusiveRefCntPtr<clang::diagnosticids>
		diagIds(new clang::DiagnosticIDs());
	clang::Diagnostic diag(diagIds, &diagPrinter, false);

	clang::LangOptions langOpts;
	diagPrinter.BeginSourceFile(langOpts, NULL);

	clang::TargetOptions targetOpts;
	targetOpts.Triple = llvm::sys::getHostTriple();

	clang::TargetInfo* targetInfo
		= clang::TargetInfo::CreateTargetInfo(diag, targetOpts);

	clang::FileSystemOptions fileSysOpts;
	clang::FileManager fileMgr(fileSysOpts);
	clang::SourceManager srcMgr(diag, fileMgr);

	clang::HeaderSearch headerSearch(fileMgr);

	clang::Preprocessor pp(diag, langOpts, *targetInfo, srcMgr, headerSearch);


	// 標準インクルードパスを設定する。

	llvm::StringRef stdHeaderPath1("C:\\Program Files (x86)\\Microsoft Visual Studio 8\\VC\\INCLUDE");
	llvm::StringRef stdHeaderPath2("C:\\Program Files (x86)\\Microsoft Visual Studio 8\\VC\\PlatformSDK\\include");
	std::vector<clang::directorylookup> stdHeaderPaths;
	{
		const clang::DirectoryEntry* p = fileMgr.getDirectory(stdHeaderPath1);
		clang::DirectoryLookup d(p, clang::SrcMgr::C_System, false, false);
		stdHeaderPaths.push_back(d);
	}
	{
		const clang::DirectoryEntry* p = fileMgr.getDirectory(stdHeaderPath2);
		clang::DirectoryLookup d(p, clang::SrcMgr::C_System, false, false);
		stdHeaderPaths.push_back(d);
	}
	headerSearch.SetSearchPaths(stdHeaderPaths, 0, false);


	// コンパイル対象のファイルを指定する。

	llvm::StringRef cppPath("D:\\develop\\Clang\\temp.cpp");
	const clang::FileEntry* cppEntry = fileMgr.getFile(cppPath);
	clang::FileID fileId = srcMgr.createMainFileID(cppEntry);
	pp.EnterMainSourceFile();

	clang::Token token;
	pp.Lex(token);

	diagPrinter.EndSourceFile();

	getchar();
	return 0;
}

しばらくこれで進めてみよう・・・。

2011年9月29日木曜日

CLangでクラスの中身を調べる(3)

前回の続き。標準インクルードパスを設定する方法を探さねばならない。


クラス名的に最もクサイのはclang::HeaderSearchである。ということでHeaderSearchの.hから見てみると、SetSearchPaths()というAPIがある。
このAPIはclang::DirectoryLookupというクラスの配列を受け取る。これは単純にHeaderSearchのメンバ変数を書き換えるようだ。そちらのメンバ変数の説明によると

  • 最初にカレントフォルダを検索する。
  • 次に(当該メンバ変数)を順に検索する。
  • オプションで(当該メンバ変数)の検索開始インデックスを指定したり、カレントフォルダを検索しないようにできる。

という感じらしい。

DirectoryLookupはえーと・・・検索エントリで、単なるフォルダではなくフレームワーク?やヘッダマップ?も指定できるオブジェクトと説明があるがさっぱりわからない。DirectoryLookupは2種類のコンストラクタがあるが、そのうち片方ではclang::DirectoryEntryとclang::SrcMgr::CharacteristicKindを受け取る。DirectoryEntryは単にフォルダ名を格納するだけであるが、フォルダ名のセットAPIはない。代わりにFileManagerがfriend指定されているので、FileManagerから受け取るのだろう。CharacteristicKindは列挙でC_User, C_System, C_ExternCSystemが選べる。

FileManagerからDirectoryEntryを受け取る方法だが、単発のファイルパスの解決のときと同様、getDirectory()APIにファイルパス文字列を渡した結果として受け取れるようだ。

ってことで、以下のように実装してみる。
	llvm::StringRef stdHeaderPath1("C:\\Program Files (x86)\\Microsoft Visual Studio 8\\VC\\INCLUDE");
	llvm::StringRef stdHeaderPath2("C:\\Program Files (x86)\\Microsoft Visual Studio 8\\VC\\PlatformSDK\\include");
	std::vector<clang::directorylookup> stdHeaderPaths;
	{
		const clang::DirectoryEntry* p = fileMgr.getDirectory(stdHeaderPath1);
		clang::DirectoryLookup d(p, clang::SrcMgr::C_System, false, true);
		stdHeaderPaths.push_back(d);
	}
	{
		const clang::DirectoryEntry* p = fileMgr.getDirectory(stdHeaderPath2);
		clang::DirectoryLookup d(p, clang::SrcMgr::C_System, false, true);
		stdHeaderPaths.push_back(d);
	}
	headerSearch.SetSearchPaths(stdHeaderPaths, 0, false);

実行すると・・・まだダメ。headerSearchに追加したDirectoryLookupのLookupFile()は呼び出されているが、「フレームワーク」として登録されている場合、'/'文字が検索するファイルのパスに入ってないとダメらしい。フレームワークってboostとかclangとか、そういうのを指しているのか?

ためしにフーレムワーク指定をfalseにすると・・・今度は別の箇所でアサーションした。なるほど、標準ライブラリはフレームワークではないらしい。

ところで、ヘッダファイルが見つからないごときでアサーションが発生するのは勘弁してほしい気がする。次は先に進む前に、アサーションではなくエラーメッセージを表示させるようなことを考える。

CLangでクラスの中身を調べる(2)

CLangのプリプロセッサ実行の続き・・・の前に。CLangが提供する各クラスのドキュメントについて。
ググって出てくるドキュメントには要注意。バージョンアップでI/Fが変更されている箇所が非常に多いのだ。だから正確な情報を得るには、CLangのソースコードの.hを見たり、.hや.cppに記述されているdoxygenスタイルコメントを読む必要がある。


では続き。
ドキュメントによるとプリプロセッサの実行はclang::Preprocessor::Lex関数によるようだ。そしてLex関数の引数はclang::Tokenで戻り値はvoidなので、引数がoutなんだろうと推測される。

しかし、今まで対象のファイルやフォルダを指定していない。何も指定せずにLex関数をコールするとどうなるだろうか?
	clang::Token token;
	pp.Lex(token);
実行すると見事になにもエラーが発生しない。CLangは例外をthrowしないのか?

ということで対象ファイルを指定する方法を探す。前回ちょっと触ったクラスのうち、たぶんFileManagerあたりでファイルやフォルダを指定し、SourceManagerで読み込む、という感じではないかと推測して各クラスを調べる。
そしてFileManagerのヘッダを見ると、FileEntryとかDirectoryEntiryというのを返却するAPIがある。そしてSourceManagerのドキュメントを見ると、FileEntryを受け取るAPIとしてcreateFileID()やcreateMainFileID()がある。
createFileID()などはclang::FileIDを返す。そこでPreprocessorのヘッダを見ると、FileIDを引数に取るEnterSourceFile()や、引数はないけどFileIDとmain source fileに言及しているEnterMainSourceFile()がある。だいぶ見えてきた。

ということで、main source fileとmainじゃないsource fileの違いがよくわからないが、とりあえずmain source fileを処理しそうなコードを書いて実行してみる。
	llvm::StringRef cppPath("D:\\develop\\Clang\\temp.cpp");
	const clang::FileEntry* cppEntry = fileMgr.getFile(cppPath);
	clang::FileID fileId = srcMgr.createMainFileID(cppEntry);
	pp.EnterMainSourceFile();

	clang::Token token;
	pp.Lex(token);
ちなみにtemp.cppの中身はこんな感じ:
#include <cstdio>
int main(int, char**) { std::printf("Hello,world\n"); std::getchar(); return 0; }

そうすると、clang::Preprocessor::Lex()の実行でアサーションされてしまった。コールスタックを見るとclang::Preprocessor::HandleIncludeDirective()という関数にて「Search include directories.」とコメントされている箇所で、処理結果の診断がNGになっているようだ。なるほど。

CLangでクラスの中身を調べる(1)

C++において、ある特定の型で宣言されたメンバ変数を持つクラスとその変数名を列挙したい。
CLangを使ってそれを実現する(か挫折する)まで頑張る。

現在の環境:
・Visual Studio 2005 with SP1
・llvm 2.9
・CLang 2.9

まずCLangのドキュメントを読んでみたがよくわからない。さかんにASTという単語が出てくるが、これはたぶんAbstract Syntax Treeの略(=構文解析結果)だと思うので、そこまでたどり着ければ何か見えてくるだろう。

まずはプリプロセッサ。ドキュメントには「The Lexer and Preprocessor Library」という節があるので、ここを見ればいいはず。ちなみにLexer = Lexical Analyzerであるらしい。これによるとプリプロセッサのクラスはclang::Preprocessorというのがある。ドキュメントを読み進める前にこいつのオブジェクトを作ってみよう。
clang::Preprocessorはコンストラクタで多数のインスタンスが要求される。
  • clang::Diagnostic
  • clang::LangOptions
  • clang::TargetInfo
  • clang::SourceManager
  • clang::HeaderSearch
以下、1つずつ潰す。

■clang::Diagnostic
コンパイルエラーなどを通知する?
こいつはllvm::IntrusiveRefCntPtrを要求する。clang::DiagnosticIDsはデフォルト生成可能。


■clang::LangOptions
言語オプション?RTTIを有効にしたりする様子。
コンストラクタにて全オプションがデフォルト指定されているので、まずはデフォルトのまま利用する。

■clang::TargetInfo
ターゲットとは、たぶんバイナリの動作環境の事だろう。
こいつはコンストラクタは利用できず、staticなファクトリ関数を使う。clang::Diagnosticとclang::TargetOptionを要求する。
clang::TargetOptionは単なる構造体である。そのメンバにTripleというのがあって最初躓いたが、どうも以下の4つをまとめたものであるらしい:
  • CPUアーキテクチャ(alpha, ppc, x86, x86_64等)
  • ベンダ(UnknownとAppleとPCしかねぇー)
  • OS(Cygwin, Linux, Win32等)
  • 環境(オプション? GNU, MachO等)
そしてllvm::sysにgetHostTriple()というAPIが用意されている。今回は別にバイナリを作るわけではないで、ホスト環境をそのまま使うことにする。それ以外のオプションは設定しなくても大丈夫っぽい。Featuresがちょっと気になるが・・・。

■clang::SourceManager
ソースコードをファイルから読み込んでメモリ上に保持管理する役割を持つようだ。
こいつはclang::Diagnosticとclang::FileManagerを要求する。
clang::FileManagerはファイルやフォルダを検索する機能、またファイルをキャッシングする機能を提供する。clang::FileSystemOptionsを要求する。
clang::FileSystemOptionsは単なる構造体で、作業フォルダの指定しか属性がない。しかもデフォルトでは指定する必要がないようだ。

■clang::HeaderSearch
その名の通りヘッダ検索をサポートするようだ。
こいつもclang::FileManagerを要求する。

■clang::Preprocessor
こんな感じになった。
#include "llvm/Support/Host.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/TargetOptions.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Lex/Preprocessor.h"

int main(int, char*[])
{
	llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> diagIds(new clang::DiagnosticIDs());
	clang::Diagnostic diag(diagIds);

	clang::LangOptions langOpts;

	clang::TargetOptions targetOpts;
	targetOpts.Triple = llvm::sys::getHostTriple();

	clang::TargetInfo* targetInfo
		= clang::TargetInfo::CreateTargetInfo(diag, targetOpts);

	clang::FileSystemOptions fileSysOpts;
	clang::FileManager fileMgr(fileSysOpts);
	clang::SourceManager srcMgr(diag, fileMgr);

	clang::HeaderSearch headerSearch(fileMgr);

	clang::Preprocessor pp(diag, langOpts, *targetInfo, srcMgr, headerSearch);

	getchar();
	return 0;
}

ビルドすると当然リンクエラーが出る。今はclang/Basicとclang/Lexに依存しているので、それぞれclangBasicプロジェクトとclangLexプロジェクトに依存するよう、プロジェクト依存関係を追加すればOK。
※LLVM.slnに自分のテストプロジェクトを追加している。
実行してエラーも何もでないことを確認した。

2011年9月27日火曜日

CLangをビルド

CLangをビルドしたので、ちょっとメモ。

基本は
に従って進める。

trunkだとビルドが通らなかったので、LLVMとCLang共にRELEASE 2.9を取得。
チェックアウト時にCLangのチェックアウトフォルダ名をうっかりcfeにしたらビルド対象に含まれなかった。clangに修正してやり直したらOK。

Cygwinでビルドしたときは、改行にCRが入っているとCygwinのシェルに怒られた。Unix系使ったことないから何故怒られるのかわからないので全部LFのみに置換。
llvm/config
llvm/autoconf/config.guess
llvm/autoconf/config.sub
llvm/autoconf/mkinstalldirs
llvm/autoconf/install-sh
llvm/projects/sample/configure

VS2010でビルドしたバイナリで.cppをコンパイルしたら
「clang: error: unable to execute command: program not executable」
「clang: error: linker command failed due to signal 1 (use -v to see invocation)」
と出力され、コンパイルできなかった。何故だろう・・・。

2010年9月5日日曜日

Xperiaでプリインストールソフトを削除する

普段からeclipseなりAndroid開発やってる人、せめてlinuxいじったことある人にはたいしたことないのだろうが、Win、VxWorks、INtimeでC++な自分はかなり手間取ったのでメモしておく。

※実施時のシステムバージョンはビルドR1EA029。
※セキュリティやばいところはちゃんと戻す。/systemをro、unroot化、提供元不明アプリのインストール防止。

(1) Android SDKをPCにインストール

1.JDK5以上をインストール
から。実施したときはJDK6Update21だった。

2.Android SDKをダウンロードして解凍
からローカルに落とす。実施したときはr6だった。
c:\の直下にそのまま解凍した。

3.Android SDKをインストール
解凍したフォルダの直下にある「SDK Setup.exe」を実行。
※リリースによっては「tools」フォルダの中にあるらしい。
画面左側「Avairable Packges」を選んで「https://dl-ssl.google.com/android/repository/repository.xml」にチェックし、「Install Selected」をクリック。
「Choose Packages to Install」画面で「Accept All」をクリックし、インストール。

4.Android SDKツールのパスを通す
解凍したフォルダ下の「tools」のパスを通す(その中のadb.exeを使いたいので)。


(2) root化ツールUniversal AndrootをXperiaにインストール

1.root化ツールをダウンロードする
検索かけて探す。実施したときは1.6.1だった。
ダウンロードしたファイルを解凍して「UniversalAndroot.apk」を適当なフォルダに置く。

2.Xperia側でインストール準備する。
Xperiaの「設定」→「アプリケーション」にて、
・「提供元不明のアプリ」をチェック
・「開発」→「USBデバッグ」をチェック
しておく。
その後、Xperiaに付属のUSBケーブルでPCと接続する。USBデバッグモードで接続されるはず。
※USBデバッグのデバドラ要求されたら・・・どうするんだろ。Media Go入れてたせいかデバドラ要求されたことない。

3.デバイス認識を確認
コマンドプロンプトを起動して「adb devices」と入力し、「ほげほげ device」とか出てくればOK。

4.Xperiaにインストール
コマンドプロンプトを起動してapkファイルの置き場所に移動し、「adb install UniversalAndroot.apk」とやればインストールできる。
「Universal Androot」のアイコンがアプリ一覧に出てくるはず。


(3) Xperiaにて削除のお膳立て

1.削除したいアプリのプロセスを落とす
必要あるかどうかわからないけど、削除できたときは殺していたので。自分はアストロファイルマネージャを使って落とした。

2.Root化
「Universal Androot」を起動し、Root化する。

3.デバッグシェル起動し、スーパーユーザー権限を取得
コマンドプロンプトで「adb shell」と入れる。「$」が出てきたら「su」と入れてenterを押すと、Xperia側にて権限を渡してもいいのか聞いてくるのでOKすると、「$」が「#」になるはず。

4./systemに書き込み権限を付与
/systemが読み取り専用になっているので、「$ mount -o remount,rw -t yaffs2 /dev/block/mtdblock2 /system」と入力すると書き込み権限が与えられる。


(4) デバッグシェルから削除
コマンドを入れて削除する。たとえば「rm /system/app/mox*」とか入れるとmoxierメールを殲滅できる。
削除しおわったら「$ roboot」でリブート。
※アストロファイルマネージャで削除できると記述されているブログもあったが、自分のXperiaでは「ファイル削除中エラー」などと表示されて削除できなかった。


(x) 参考文献
・逆引きAndroid入門

・コムギドットネット

・Digital Surviver

・YT-1300

2010年2月12日金曜日

INtimeで例外がバグる

INtimeをいじりはじめた。
PC等にかなり制限が多い(IRQがWinと重複できない等)ので電気屋さんは大変だが、ソフト屋としてはVC++が使えるので他の組み込み環境よりはコーディングが楽だ。
# Eclipse+CDTは重いしソースコード管理が変だと思う。

で、使っているINtimeはver.3.13なのだが、リンクされるC++ランタイムにバグがあり、例外がまともに使えない。参照でcatchすると参照先アドレスがおかしいのである。実体catchならバグらないが、例外オブジェクトにエラー詳細情報を詰めているのでコピーコストが大きくなってしまう。

残念ながらver.3.13でのバグで、パッチリリースの予定はないらしい。4.0まで待たないといけないのか・・・メジャーバージョンアップ直後のOSは製品に使うのが怖いのだが。

他、ver.3.13にはqsort()にバグがあったりして、品質面で不安が残る。全体的にはVxWorksなんかより遥かに良くてコストが下がるので、移行すること自体は大賛成なのだが。