C/C++の関数の呼び出しをトレースする (GCC限定)

GCCの拡張機能である-finstrument-functionsオプションLD_PRELOAD環境変数を利用して実行時に関数とメソッドの呼び出しをトレースする方法のメモです。

この方法の良いところは、他のツールと違って、自分の興味がある関数やメソッドがあるソースコードだけを対象にトレースできる所だと思います。dtraceやcallgrindだとシステムコールやライブラリの関数の呼び出しまでトレースしてしまったりします。関数呼び出しをトレースするお手軽な方法です。

手法

-finstrument-functionsオプションをつけてコンパイルすると関数やメソッドの入口と出口に自動的に関数を呼び出す命令を挿入します。入口で呼ばれる関数は__cyg_profile_func_enter、出口は__cyg_profile_func_exitです。そんな感じで関数を定義してコンパイルオプションを追加するだけで関数の呼び出しをトレース出来るんですけど、面倒なんでLD_PRELOAD環境変数を使ったテクニックとよく一緒に使われるみたいです
LD_PRELOAD環境変数に共有ライブラリを指定すると、プログラムを実行する前に指定された共有ライブラリが読み込まれるようになります。その時に共有ライブラリにある関数やメッソドのシンボルと実行ファイルのシンボルが被ってた場合は、先に読み込まれた共有ライブラリの関数やメソッドが利用される。
これを使ってフック関数を共有ライブラリ化してしまえばコンパイルオプションと環境変数を変えるだけで関数の呼び出しをトレースできるようになります。

サンプル

自分は以下のような感じで使ってます。
OSはUbuntu10.04で、gccのバージョンは4.4.3です。

test.cpp

// test.cpp

class B{
public:
  B(){}
  virtual ~B(){}

  virtual void f(){}
};

class D : public B{
public:
  D(){}

  virtual void f(){}
};

void f1(int a){}
void f2(int b){}

int main(){
  f1(0);
  f2(0);

  B *o = new D();
  o->f();
  delete o;

  return 0;
}

trace.cpp

#include <dlfcn.h>
#include <cxxabi.h>

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

extern "C" {
  void __cyg_profile_func_enter(void* func_address, void* call_site);
  void __cyg_profile_func_exit(void* func_address, void* call_site);
}

namespace ftracer {
  const char* addr2name(void* address) {
    Dl_info dli;
    if (0 != dladdr(address, &dli)) {
      return dli.dli_sname;
    }
    return NULL;
  }

  char* demangle(const char *symbol_name){
    int status;
    return abi::__cxa_demangle(symbol_name, 0, 0, &status);
  }

  void output(const char* action, void* func_address){
    const char* symbol_name = addr2name(func_address);
    if (symbol_name) {
      char* func_name = demangle(symbol_name);
      printf("%lu, %s, %s, %s\n", (unsigned long)clock(),
          action, symbol_name, func_name ? func_name : "(null)");
      free(func_name);
    }
  }
}

using ftracer::output;

void __cyg_profile_func_enter(void* func_address, void* call_site) {
  output("enter", func_address);
}

void __cyg_profile_func_exit(void* func_address, void* call_site) {
  output("exit", func_address);
}

コンパイルと実行

g++ -fPIC -shared -ldl trace.cpp -o trace.so
g++ -finstrument-functions -rdynamic test.cpp
LD_PRELOAD=./trace.so ./a.out

出力結果:
0, enter, main, (null)
0, enter, _Z2f1i, f1(int)
0, exit, _Z2f1i, f1(int)
0, enter, _Z2f2i, f2(int)
0, exit, _Z2f2i, f2(int)
0, enter, _ZN1DC1Ev, D::D()
0, enter, _ZN1BC2Ev, B::B()
0, exit, _ZN1BC2Ev, B::B()
0, exit, _ZN1DC1Ev, D::D()
0, enter, _ZN1D1fEv, D::f()
0, exit, _ZN1D1fEv, D::f()
0, enter, _ZN1DD0Ev, D::~D()
0, enter, _ZN1BD2Ev, B::~B()
0, exit, _ZN1BD2Ev, B::~B()
0, exit, _ZN1DD0Ev, D::~D()
0, exit, main, (null)

フック関数の引数で渡されるのは関数のアドレスだけです。アドレスを表示してnmコマンドとかで関数名を調べられますが、面倒なんでdladdrでシンボル名を取得して、abi::__cxa_demangleでシンボル名をdemangleしています。clock関数で実行時間も測っていますが一瞬で終わってるので全て0になってしまっています。
ちなみに、trace.cppでわざわざ新しい名前空間を作っているのはシンボル名が実行ファイルのシンボル名と被らないようにするためです。

参考

Posted in C++, Linux at 2011/11/13. No Comments.

picojsonを使ってみた

C++でJSONパーサが欲しくなりpicojsonというライブラリを使って見ました。
特徴はヘッダをインクルードするだけで使える、STLを利用しているのでobjectとarrayの操作を覚えなくて良いところ。

Parsing

#include <iostream>
#include <cstring>
using namespace std;

#include "picojson.h"
using namespace picojson;

int main(){
  const char *str = "{\"a\" : 1, \"b\" : \"test\"}";

  value v;
  parse(v, str, str + strlen(str));

  if(v.is<object>()){
    value a = v.get("a");
    if(a.is<double>()){
      cout << "a: " << a.get<double>() << endl;
    }

    value b = v.get("b");
    if(b.is<string>()){
      cout << "b: " << b.get<string>() << endl;
    }
  }

  return 0;
}

Serialization

シリアライズも出来ます。objectstd::maparraystd::vectorです。

#include <iostream>
#include <string>
using namespace std;

#include "picojson.h"
using namespace picojson;

int main(){
  array a;
  a.push_back((value)1.0);
  a.push_back((value)string("2"));

  value v1(a);
  cout << "array: " << v1.serialize() << endl;

  object o;
  o["a"] = (value)a;
  o["b"] = (value)string("test");

  value v2(o);
  cout << "object: " << v2.serialize() << endl;

  return 0;
}

問題点

1つのcppファイルでpicojson.h (Revision: 38824)をインクルードしていると問題ないのですが、複数のcppファイルからヘッダをインクルードするとリンカにシンボル名が衝突してると怒られます。
以下のように609行目bool operator==(const value& x, const value& y)inlineを指定すると関数がインライン展開されるかWeakシンボルになりリンカが怒らなくなります。

  inline bool operator==(const value& x, const value& y) {
    if (x.is<null>())
      return y.is<null>();
Posted in C++ at 2011/10/21. No Comments.

Rails3を使った話

相変わらずRails初心者の私ですが、割りとしっかりとRails3を使ってみたので感想とか書きたいと思います。

Haml

初めてHaml使って見ました。これはめっちゃ便利です。
インデントでタグを囲えるので閉じタグ忘れるって事もないしend書かなくてもいいです。
Hamlに対応するためVimにはvim-hamlを入れました。

SCSSとCoffeeScript

前から興味あったんですけどRails3.1でデフォルトで使えるようになってたので喜んで使いました。

SCSSではセレクタのネストを沢山使用する。 パラメータ化できるMixin面白い。
「こんな感じかな?」って感じで見よう見まねで書いてました。

ちょっとしかコード書かなかったけどCoffeeScriptも使いました。
Javascriptに比べてかなり便利。==演算子がデフォルトで厳密な比較になっているし、?演算子Function binding ( => )が超便利です。
いろいろとRubyっぽく書けたりします。
オフサイドルールには頑張って慣れます。

CoffeeScript書くためにVimにvim-coffee-scriptを入れました。SCSSはvim-hamlでシンタックスハイライトできてた。

フォームのバリデーション

モデルのバリデーション機能はとても便利なんだけど、DBに関係なくバリデーションをしたい時もあります。例えば、フォームのバリデーションとか。
そんなときRails3だといくつかのモジュールをMixinすれば簡単にバリデーション機能付きのDBに結びついてないモデルが作れます。これには感動しました。

class ActiveForm
  include ActiveModel::Conversion
  extend ActiveModel::Naming
  include ActiveModel::Validations

  def persisted?; false; end

  def initialize(attributes = {})
    attributes.each do |name, value|
      send("#{name}=", value) rescue nil
    end
  end
end
class SearchForm < ActiveForm
  attr_accessor :keyword
  validates_presence_of :keyword
end

これでform_forで使えて、valid?もできるようになりました。エラーを表示したい場合は、普通に@model.errorsから情報を取得できます。

ちなみにGitHubを探せばもっと高機能なライブラリがあると思います。

Bundler

Railsアプリ以外ににもbundlerを使えることに気づく。
速攻で非RailsなRubyプログラムにもbundlerを導入しました。

開発の参考になった書籍

Ruby on Rails 3 アプリケーションプログラミング
山田 祥寛
4774146633

Rails3レシピブック 190の技
高橋 征義 松田 明 諸橋 恭介
4797363827

Posted in Ruby at 2011/10/07. No Comments.

mod_proxy_balancerについてのメモ

mod_proxy_balancerを使ってみたときのメモです。

環境構築

環境は、Ubuntu10.04Apache2.2.14です。

まずApacheの設定を書き換える

<VirtualHost *:80>
  ServerAdmin webmaster@localhost

  ProxyPass / balancer://cluster/
  ProxyPassReverse / balancer://cluster/

  <Proxy balancer://cluster>
    BalancerMember http://192.168.0.1/
    BalancerMember http://192.168.0.2/
  </Proxy>
</VirtualHost>

次にモジュールを有効にします。

# モジュールを有効にする
sudo a2enmod proxy
sudo a2enmod proxy_balancer
sudo a2enmod proxy_http

# Apacheを再起動する
sudo apache2ctl restart

これで設定完了と思ってサーバにアクセスするとForbiddenになってしまう。
ぐぬぬぬぬぬぬって感じに原因を探していたらmod_proxyがデフォルトで全部のアクセスを弾くようになっていた。 なのでちょっと/etc/apache2/mods-available/proxy.confを書き換える。

〜 略 〜

<Proxy *>
  AddDefaultCharset off
  Order allow,deny
  Allow from all
</Proxy>

〜 略 〜

これでちゃんとリクエストを分けてくれるようになりました。
片方のHTTPサーバを落とすと生きているサーバだけにリクエストが行くようになり、死んでいるHTTPサーバを復活させてしばらく待つとまた両方にリクエストがふられるようになります。

Posted in Linux at 2011/08/10. No Comments.

Androidの電話帳アプリを作ってて悲しかったこと

ContactsContract.Dataに入っている情報の中には、TYPEカラムを持っているものが多いです。 TYPEっていうのは、そのデータの種類を表してます。 例えば、電話番号のTYPEにはTYPE_HOME, TYPE_MOBILE, TYPE_WORKといった定数が割り当てられます。
そうゆう定数は基本的に、ContactsContract.CommonDataKindsに載っています。 電話番号の場合は、ContactsContract.CommonDataKinds.Phoneです。

しかし、TYPEの値は整数なのでアプリケーションから利用するときには文字列に変換しないといけません。 国際化を考えなくても結構面倒です。 あと、TYPEの値がTYPE_CUSTOMの時はLABELカラムの文字列を参照するといったTYPEを拡張する仕組みもあります。

そんな面倒な事を肩代わりしてくれるメソッドがContactsContract.CommonDataKindsの中のクラスにはあります。 getTypeLabelResourcegetTypeLabelです。

getTypeLabelResourceはTYPEの値に対するリソースIDを返してくれます。 あとはResources#getTextを使えば文字列を手に入れられます。

getTypeLabelはTYPEとLABELを引数で受け取り、TYPEの値に対応する文字列を返してくれて、もしTYPEの値がTYPE_CUSTOMの時はLABELの値を返してくれます。

これは便利で嬉しい。

ちょっと悲しかったこと

ここから本題のちょっと悲しかったことについて書きたいと思います。

CommonDataKinds.RelationにもgetTypeLabelResourcegetTypeLabelがあるんですが使えるのがAPI Level 11からみたい。 CommonDataKinds.Relation自体はAPI Level 5から使えるのに。。。翻訳する作業がリリースに間に合わなかったんですかね。
これはちょっと悲しい。

CommonDataKinds.EventにはgetTypeLabelResourcegetTypeLabelは無くて代わりにgetTypeResourceがあります。
これはどう見てもgetTypeLabelResourceと同じ働きをするメソッドっぽいのに名前が異なっている。あとなぜかgetTypeLabelと同じ働きをするメソッドが存在しない。
これは結構悲しい。

ちなみにEventのgetTypeLabelを自作してみました。こんな感じになりました。

	public static final CharSequence getTypeLabel(Resources res,
			int type, CharSequence label) {
		if(type == Event.TYPE_CUSTOM && !TextUtils.isEmpty(label)){
			return label;
		}else{
			final int labelRes = Event.getTypeResource(type);
			return res.getText(labelRes);
		}
	}

他にもCommonDataKinds.WebsiteみたいにTYPEとLABELカラムがあるのにgetTypeLabelResourcegetTypeLabelのような働きをするメソッドが一切無いクラスもあります。
これも結構悲しい。

Posted in Android, Java at 2011/07/23. No Comments.