ラベル プログラム テクニック の投稿を表示しています。 すべての投稿を表示
ラベル プログラム テクニック の投稿を表示しています。 すべての投稿を表示

2014年7月17日木曜日

コンパイルや、ビルド時に付与したいオプション


コンパイルや、ビルド時にセキュアなオブジェクトコードを生成するために
利用すべきオプションを記録しておく。

g++ ビルド時のオプション
それぞれのオプションの意味はmanを参照してほしい。

$ g++ \
-g \
-fmudflap \
-lmudflap \
-fstack-protector \
-ftrapv \
-O2 \
-fno-strict-aliasing \
-Wall \
-Wextra \
-Wformat=2 \
-Wstrict-aliasing=2 \
-Wcast-qual \
-Wcast-align \
-Wwrite-strings \
-Wconversion \
-Wfloat-equal \
-Wpointer-arith \
-Wswitch-enum \
-Woverloaded-virtual \
-Weffc++ \
-o hoge.o \
hoge.cc


さらに、メモリの取得と解放のチェックを実施する環境変数を指定する。
$ export MALLOC_CHECK_=1

fmudflapオプションを付与して実行ファイルを作った場合、
mudflapライブラリを指定して実行する必要がある。
$ ./hoge.o -lmudflap

※gccとの差異
下はg++のみで有効。gccでは使えない。
-Woverloaded-virtual
-Weffc++



デバッグ時のみに使うべきオプション
デバッグ用のオプションなので、リリースビルド時には外すこと。

● -g
いわずもがな、デバッグオプション。
コンパイル、リンク時にDEBUG情報を付加して、
gdbなどのデバッガを使用するときに必要となる。


● -fmudflap
-gオプションと一緒に使う
また、リンク時にライブラリの指定が必要である。
-lmudflap

mudflapはコンパイル時に不正なポインタアクセスを検出できる。
仕組みとしてはアドレス周辺にチェックコードを埋め込むことで、
heap、stack、data、bss領域の変数の検査を行う。

mudflapが使えない場合はパッケージのインストールが必要である。
$ sudo yum install libmudflap libmudflap-devl

ポインタ周りのチェックにvalgrindを使うことも多いだろう。
mudflapとvalgrindの大きな違いは、
valgrindは解析対象の再コンパイルとリンクが不要で使える、
しかし、stack、data、bss変数の検査は不可(heapは可)、
また解析速度がmudflapに比べて遅い、
あたりだろう。
ついでなので、valgrindの使い方も後半に少しだけまとめておく。

同様の機能として-D_FORTIFY_SOURCEオプションを使ってもよい。
mudflapはデバッグ用の機能であったが、これはランタイムのオーバーヘッドが少ないため、
リソースビルドにも利用できる。
$ g++ -O2 --D_FORTIFY_SOURCE=1 ~
-O1以上の最適化が必須である。
コンパイル時にも、実行時にもオーバーフローチェックが走る。


● -fstack-protector
スタック上に確保されたchar変数あふれによって生じる、
リターンアドレスの書き変えを防止、検出する。

関数ポインタの指し先を上書けないように、
スタックレイアウトの調整、またcnary(カナリア)というガード値を
別途スタック上に挿入しそこへの改ざんの有無をチェックすることで実現する。

スタック破壊検出コードの生成はすべての関数に対しては行われないことに注意が必要である。
8バイト以上のchar配列が確保される関数だけが保護対象となる。
なお、この8バイトという閾値は、次のオプションを用いて変更できる。
--param ssp-buffer-size=N

どの関数についてもスタック破壊検出コードを生成させるには次のオプションを使う。
-fstack-protector-all


● -ftrapv
符号付き整数同士の加算・減算・乗算で整数のオーバーフローをランタイムに検出する。
検出した場合はabortする。
除算はNビット同士の除算結果がNビットを超えないためチェック対象外である。

オーバーフローの有無をチェックする関数とabort関数が
ハードコーディングされる。



valgrindの使い方
valgrindを使うにはビルド時に以下3点を気にかけておかなければならない。

・デバッグオプションを付与する
・最適化は最大O1まで
・スタティックリンクは避ける

スタティックリンクは避ける理由は
valgrindは仮想的なCPU上でプログラムを実行するのだが、
mallocなどの関数は置き換えが不可能であるためである。

% g++ -g -O0 -o hoge.o hoge.c

一般的によく使うオプションはこのあたりだろう。
$ valgrind \
--leak-check=full \
--leak-resolution=high \
--show-reachable=yes \
--trace-children=yes
./hoge.o 

先に一度書いているが、stack、data、bss変数の検査は不可なので注意すること。



2013年1月14日月曜日

ビット(bit)演算操作の覚え書き


プログラミングをする上でビットの操作の理解は必須である。

これだけは覚えておきたい基礎となるポイントをまとめておく。

これより先、0101などの0、1の羅列はすべて2進数とする。

見やすさ優先で頭に0bつけない。


◆ 手動でのビット操作

(1)
Q.
0110 + 0110 + 0110 + 0110

A.

11000
10進数と同じように考えればよい。
0110が4つあるので、×4で2ビットシフトさせた、と考えてもいい。


(2)

Q.
0010 * 0011

A.

0110
10進数と同じように筆算でも使ってやればいい。
0010は2なので1ビットシフトさせた、と考えてもいい。


(3)

Q.
1000 - 0110

A.

0010

手動でするならコンピュータと同じように補数計算しなくとも10進数の時と同じようにやればいい。

これで悩むようなら繰り下がりのある引き算が分かっていない。算数から復習しよう。


(4)

Q.
1101 ^ 0101
^はXORである。

A.

1000


(5)

Q.
1011 & (~0 << 2)
~はnotである。

A.

1000
~0 は 1111 であり、
それを2ビットシフトするので、1100。
下位2ビットをすべて0にしたい場合などに利用できる。


◆ バイト数の設定

256K、256M, 256Gなどのサイズ表現をビットで分かりやすく定義する。
例えばC言語ならこのようなプリプロセッサをよく見かける。
#define SIZE_K (256 << 10) /* 256Kバイト */
#define SIZE_M (256 << 20) /* 256Mバイト */
#define SIZE_G (256 << 30) /* 256Gバイト */

* << 10 とは、*を10個左シフトすることである。
これは2倍を10回(2^10 = 1024)することに等しい。



◆ アラインメント(alignment)
あるアドレスの先頭を4バイトの倍数にアラインメントしたい。

例えば、あるアドレスが6の場合、4にする

|    |    |    |
0    4    8    12
       ↑
       6

どういったコードで表現できるか示す。

PAGE_SIZE = 4
MASK = (1 << 2)-1  # 4 - 1 == 3

address = 6 # アラインメントされていない

excess = address & MASK  # 6 % 4 = 2 として余りを求めたと考えてもよい


address -= excess  # 4



分かりやすく4バイト単位としたが、OSがページ単位でメモリを管理する場合は

4Kバイトで区切られることが多い。その場合、MASKは下にすればよい。
4 < < 10 - 1
(4 × 1024 - 1)


アラインメントのために、Cのコードで切り下げ、切り上げするマクロをよく見かけることがある。

#define ROUND_DOWN_TO_PAGE_SIZE(p) \
    ((size_t)(p) & ~(PAGE_SIZE - 1))

#define ROUND_UP_TO_PAGE_SIZE(p) \
    (((size_t)(p) + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1))



◆ 特定領域のビットのクリア操作
あるビットで表されたnumをiからjまでをクリアする。
例えば、
num:10101101
i:2
j:5

結果:10000001



コード例

// jより上位を1に、下位を0にする
// jが5なら~0(=11111111)が11000000になる
int left = ~0 << (j + 1);

// iより下位を1にする

// iが2なら1<<iは100なのでそこから1減算してで011
int right = (1 << i) -1 ;

int mask = left | right;


return num & mask;




◆ ビットを使った整数の管理

ビットマップ(bit map)を使い整数を管理する。ビットベクトル(bit vector)とも呼ばれる。

例えば40億個のint型の整数(4byte)を扱うとする。メモリは1GByteしか利用できない。

int型(4byteとする) × 40億 > 1GByte
byteで比較すると、
4 × 4 * 2^30 > 2^30
2^34(byte) > 2^30(byte)

整数1つ1つをint型で扱うと利用可能なメモリをオーバする。しかし各bitに整数を対応させられれば、1GByteは80億bitであるため管理できる。

1(GByte)
= 1 × 1024 × 1024 × 1024 × 8(bit)
= 1 × 2^10 × 2^10 × 2^10 × 8(bit)
= 8 × 2^30
= 80億(bit)

さっそくコード例を見てもらおうと思うが、先にコード内で利用したテクニックを説明しておく。
以下のように読み替えてもいい。
num >> 3  ⇒ num / 8
num & 0x7 ⇒ num % 8

例えば10進数の10は

10 / 8 = 1, 10 % 8 = 2 なので
2バイト目(bitset[1])の2ビット目(00000010)に対応する。

(コード例)

char bitset [size >> 3];

int getBit(int num) {

  return bitset[ num >> 3 ] & (1 << (num & 0x7));
  // 直接整数値を返したい場合は以下でもいいか
  // return (num >> 3) * 8 + (num & 0x7)
}

void setBit(int num) {

  bitset[ num >> 3 ] |= (1 << (num & 0x7));
}

void clearBit(int num) {

  bitset[ num >> 3 ] &= ~(1 << (num & 0x7));
}

このビットマップはbitsetはchar型(1byte)単位で扱っていたが、例えばbitsetをint型にしてもよい。int型が4byteであれば32個ずつ管理できるため下記の変更をする。

num >> 5  ⇒ num / 32
num & 0x1F ⇒ num % 32

また、仮にbit単位で扱える型があればこのような書き方もできただろう。
int getBit(int num) {
  return bitset & (1 << num);
}

void setBit(int num) {
  bitset | (1 << num);
}

void clearBit(int num) {
  bitset & ~(1 << num);
}


(補足)

ビットベクトルを使ってすべてを解決できるわけではない。
例えば、同じく40億(2^32)個の整数を管理する場合であっても、10MByte(2^23バイト程度 ※2^3×2^20)のメモリしか使用できない場合どうなるだろうか。ビットベクトルを使っても足りない。2^23×8=2^26である。

物理的な制約はどうしようもないので、整数をある範囲ごとに区切るしかない。


1つの配列サイズ = 40億(2^32)個 / 整数範囲 <= 10MByte(2^23) / 4 = 2^21

※整数値を4byte(2^2)のint型とすると利用できる配列サイズは1/4になる。

上記から整数範囲は、

2^11 <= 整数範囲
さらに
2^11 <= 整数範囲 <= 10MByte(2^23バイト = 2^26ビット)

間をとって整数範囲を2^20とすると、1つの配列では2^20の整数を扱える。

ビットベクトルを使うと、2^20 × 8 = 2^23個である。