Electric Fenceを試す。

はじめに

 メモリ系のバグには、問題点を発見するまでに苦労する。早期発見にツールを活用できればと思い、Electric Fenceを試してみた。
今回のサンプルはこちら

問題のあるソース

 下記ソースは、サイズが2である配列を動的に確保し、存在しない3つ目の配列にアクセスするソースである。

andre@andre-VirtualBox:~/work/efence$ cat outOfBound.c
#include<stdio.h>
#include<stdlib.h>

int
main(void)
{
    int *a = ( int * )malloc( 2 * sizeof( int ) );

    for( int i = 0; i <= 2; ++i )     ★ a[2]にもアクセスしてしまうため、他のデータを壊す恐れがある。
    {
        a[ i ] = i;
        printf( "%d\n", a[ i ] );
    }

    free( a );
    return 0;
}

Electric Fenceを使ったものとそうでないものを動作させ、比較してみる。

Electric Fenceを使ったもの

 Electric Fenceを使ったときの見え方を下記に示す。

andre@andre-VirtualBox:~/work/efence$ ./outOfBound_with_efence 

  Electric Fence 2.1 Copyright (C) 1987-1998 Bruce Perens.
0
1
Segmentation fault (コアダンプ)                      ★ Segvする
andre@andre-VirtualBox:~/work/efence$ gdb outOfBound_with_efence core
GNU gdb (Ubuntu/Linaro 7.3-0ubuntu2) 7.3-2011.08
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/andre/work/efence/outOfBound_with_efence...done.

warning: core file may not match specified executable file.
[New LWP 3034]

warning: Can't read pathname for load map: 入力/出力エラーです.
[Thread debugging using libthread_db enabled]
Core was generated by `./outOfBound_with_efence'.
Program terminated with signal 11, Segmentation fault.
#0  0x08048546 in main () at outOfBound.c:11
11	        a[ i ] = i;                           ★ バグの場所でSegvしている

Electric Fenceを使っていないもの

andre@andre-VirtualBox:~/work/efence$ ./outOfBound_without_efence 
0
1
2             ★ Segvしない
andre@andre-VirtualBox:~/work/efence$ 

感想

 当たり前のことを書くが、Electric Fenceを使わないと、メモリアクセス違反(上記ソースのように)していたとき、問題が発生した箇所と問題点が離れすぎていてわからない。なので、正常ルートくらいは使うべきではないか。

pthread_cancelを試す。

はじめに

 メインスレッドから生成したスレッドを途中で終了させるにはどうするのかを調査中、pthread_killが使えることがわかったが、
【POS44-C. シグナルを使用してスレッドを終了しない】
では、pthread_cancelを使用せよとあった。使ったことがないので、サンプルを作りながら、挙動を確認してみた。
それをまとめた記事である。

今回のサンプルソースこちら

pthread_cancelの挙動まとめ

 pthread_cancelの挙動は、大きく分けて4つある。

  • キャンセルを許可/不許可 ・・・A
  • 遅延取り消し/即時取り消し(非協調的) ・・・B

A×B(=2×2)の4つである。

【A】は、キャンセルされる側のスレッド上で、pthread_setcancelstate()で設定可能。
【B】は、キャンセルされる側のスレッド上で、pthread_setcanceltype()で設定可能。

キャンセル許可 キャンセル不許可
遅延取り消し
即時取り消し

デフォルトの動作

 上記表の【1】にあたる。

サンプル

 デフォルトの動作がわかるサンプルを下記に示す。

andre@andre-VirtualBox:~/work/c/pthread_cancel$ cat main.c
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>

#define FIB_N   40

unsigned long
fib( int n )                   ★フィボナッチ数を計算
{
    if( n < 0 )     return 0;
    if( n == 0 )    return 1;
    if( n == 1 )    return 1;
    if( n > 1 )     return fib( n-1 ) + fib( n-2 );
}

int
thread( void* arg )            ★ cancelされる側のスレッド
{
    int i = 0;
    unsigned long    ret = 0;

    printf( "sub thread fib calc start...\n" );
    for( i = 0; i < FIB_N; i++ )
    {
//        printf( "for:%d\n", i );
        ret = fib( i );
//        printf( "fib(%d)=%lu\n", i, ret );
    }
    printf( "sub thread fib calc finish!\n" );   ★ 遅延取り消しのキャンセルポイント
    printf( "finish thread!\n" );                ★ このメッセージは出力「されない」

    return 0;
}

int
main( void )                  ★ cancelする側のスレッド
{
    int         ret = 0;
    pthread_t   th;

    ret = pthread_create(   &th,
                            NULL,
                            ( void* )thread,
                            NULL );

    printf( "main thread sleep...\n" );
    sleep( 1 );
    printf( "main thread wake up!\n" );

    printf( "do thread cancel.\n" );
    pthread_cancel( th );     ★スレッドをキャンセルする

    pthread_join( th, NULL ); ★ joinしないとメモリリークする。

    return 0;
}

実行結果

andre@andre-VirtualBox:~/work/c/pthread_cancel$ ./test_pthread_cancel 
main thread sleep...
sub thread fib calc start...
main thread wake up!
do thread cancel.            ★ キャンセル実行
sub thread fib calc finish!  ★ キャンセルポイント

【sub thread fib calc finish!】のメッセージで、フィボナッチ数をすべて計算してから、スレッドが終了しているのがわかる。

安全ではないマクロ関数

はじめに

 安全なコーディング(セキュアコーディング)の勉強中である。
セキュアコーディングのサイトを見つけたので、勉強中に試したものを記事として残す。

記事概要

 マクロ関数は、安全ではない1例が記載されていた(PRE31-C)ので、試してみた。

安全ではないマクロ関数

 下記ソースは、引数に渡された値の絶対値を返却するマクロが含まれており、そのマクロを使って計算した結果を出力するものである。
しかし、想定どおりに動作しない。
(想定)引数の絶対値
(実際)?

安全ではないマクロを使ったソース
andre@andre-VirtualBox:~/work/PRE/PRE31-C$ cat main.c
#include

#define ABS(x)  ( ((x) < 0) ? -(x) : (x) )
// 展開後=> ( ((++n) < 0) ? -(++n) : (++n) ) ★ ? の左右で n が1回ずつインクリメントされる。
int
main(void)
{
    int m = 0;
    int n1 = 1;
    int n2 = -4;
    int n3 = 0;

    m = ABS( ++(n1) );
    printf("m = %d\n", m);
    m = ABS( ++(n2) );
    printf("m = %d\n", m);
    m = ABS( ++(n3) );
    printf("m = %d\n", m);

    return 0;
}

ビルドして、動作させてみた。

andre@andre-VirtualBox:~/work/PRE/PRE31-C$ gcc -o test_main main.c
andre@andre-VirtualBox:~/work/PRE/PRE31-C$ ./test_main
m = 3   (想定)m = 2
m = 2   (想定)m = 3
m = 2   (想定)m = 1

安全に(想定どおりに)動作させるには

 inline もしくは static 関数として定義する。
下記に示す。

andre@andre-VirtualBox:~/work/PRE/PRE31-C$ cat main_legal.c
#include

inline int
abs( int x )
{
    return ( ((x) < 0) ? -(x) : (x) );
}

int
main(void)
{
    int m = 0;
    int n1 = 1;
    int n2 = -4;
    int n3 = 0;

    m = abs( ++(n1) );
    printf("m = %d\n", m);
    m = abs( ++(n2) );
    printf("m = %d\n", m);
    m = abs( ++(n3) );
    printf("m = %d\n", m);

    return 0;
}

ビルドして、想定どおりに動作するか試した。

andre@andre-VirtualBox:~/work/PRE/PRE31-C$ gcc -o main_legal main_legal.c
andre@andre-VirtualBox:~/work/PRE/PRE31-C$ ./main_legal 
m = 2   (想定)m = 2
m = 3   (想定)m = 3
m = 1   (想定)m = 1

結論

 マクロは想定したとおりに動作しないことがあるので、基本的に使わないようにする。

マクロ展開の注意点を試す。

はじめに

 本記事は、マクロについての注意点を実際に試したことをまとめたものである。

概要

 下記のページに書かれているマクロについての違反コードと適合コードを試してみた。
PRE05-C

違反コード

 マクロで展開された結果が"4"(数字の4の文字列)の想定が、そうならないソースである。
確認コマンド:gcc -E illegal.c

illegal.c
andre@andre-VirtualBox:~/work/PRE/PRE05-C$ cat illegal.c 
#define str(s) #s
#define foo 4

    str(foo);

確認コマンドでマクロが展開された後のソースを下記に示す。

確認コマンド実行結果
andre@andre-VirtualBox:~/work/PRE/PRE05-C$ gcc -E illegal.c
# 1 "illegal.c"
# 1 "<組み込み>"
# 1 "<コマンドライン>"
# 1 "illegal.c"



    "foo";

適合コード

 こちらは、想定どおり"4"(数値の4の文字列)に展開される。
確認コマンド:gcc -E legal.c

legal.c
andre@andre-VirtualBox:~/work/PRE/PRE05-C$ cat legal.c
#define xstr(s) str(s)
#define str(s) #s
#define foo 4

    xstr(foo);

 確認コマンド実行後のマクロ展開されたものを下記に示す。

andre@andre-VirtualBox:~/work/PRE/PRE05-C$ gcc -E legal.c
# 1 "legal.c"
# 1 "<組み込み>"
# 1 "<コマンドライン>"
# 1 "legal.c"




    "4";

気をつけなければいけないこと

 ここで注意しなければならないのは、# 演算子による引数の文字列化である。

#define str(s) #s    // str(foo) => "foo" => "foo"  ★ 文字列は、マクロで展開されない。
#define foo 4        
#define xstr(s) str(s)   // xstr(foo) => str(foo) => str(4) ★ マクロを2段階にすることで、【foo】 が 【4】 に展開された
#define str(s) #s        // str(4) => "4"
#define foo 4

LD_PRELOAD を試したら、Segv した。

はじめに

 本記事は、Binary Hackの第3章#22に出てくる【LD_PRELOAD】を簡単に試した結果、Segvしたので、その原因追求について、記述したものである。
作成したソースはGithubに置いておく。
https://github.com/tomohikoseven/LD_PRELOAD

LD_PRELOADとは

 バイナリを実行したときに、他のライブラリの読み込み前に、優先的に指定したライブラリを読み込むことができる環境変数である。

Segv 事象とソース

 下記のコマンドを実行したら、Segvした。

andre@andre-VirtualBox:~/work/LD_PRELOAD$ LD_PRELOAD=./libhack.so ./test_main
Segmentation fault

 test_main( test_main.c ) と libhack.so( hack.c )を下記に示す。

andre@andre-VirtualBox:~/work/LD_PRELOAD$ cat main.c
#include

int
main()
{
    puts("hello world!");
    return 0;
}
andre@andre-VirtualBox:~/work/LD_PRELOAD$ cat hack.c
#include
int
puts( const char *str )
{
    printf("hack!!\n");
}

原因:GCC の最適化

 30分くらい原因を探っていたが、答えは、Binary Hack 第3章 #22(LD_PRELOADの前の段落!)に書いてあった。
すなわち、printfは、putsに置き換えることがあると。

これにより、hack.c内のprintfがputsに置き換わって、libhack.so内でputsが再帰的に呼ばれ、スタックオーバーフローでSegvしていた。

スタックオーバーフローの確認

 本当にスタックオーバーフローしているのか、検証した。
検証ソースは以下。

andre@andre-VirtualBox:~/work/LD_PRELOAD$ cat main.c_altstack 
#include
#include
#include
#include

#define ALT_STACK_SIZE (64*1024)

static sigjmp_buf return_point;

static void
signal_handler( int sig )
{
    if( sig == SIGSEGV )
    {
        siglongjmp( return_point, 1 );
    }
}

static void
register_sigaltstack()
{
    stack_t newSS;
    stack_t oldSS;

    newSS.ss_sp     = malloc( ALT_STACK_SIZE );
    newSS.ss_size   = ALT_STACK_SIZE;
    newSS.ss_flags  = 0;

    sigaltstack( &newSS, &oldSS );
}

int
main()
{
    struct sigaction newAct;
    struct sigaction oldAct;

    // 代替スタック作成
    register_sigaltstack();

    // シグナル集合の初期化(空)
    sigemptyset( &newAct.sa_mask );
    // シグナル集合の設定
    sigaddset( &newAct.sa_mask, SIGSEGV );
    // ハンドラ設定
    newAct.sa_handler = signal_handler;
    // シグナル発生時のスタックを代替スタックに設定
    newAct.sa_flags     = SA_ONSTACK;

    // SEGV 時の動作登録
    sigaction( SIGSEGV, &newAct, &oldAct );

    if( sigsetjmp( return_point, 1 ) == 0 )
    {
        puts("hello world!");
    }
    else
    {
        fprintf( stderr, "stack overflow error\n" );
    }

    return 0;
}

このソースをmain.cにリネームし、make そして実行すると、[ stack overflow error ]とメッセージが表示され、確認ができた。

andre@andre-VirtualBox:~/work/LD_PRELOAD$ LD_PRELOAD=./libhack.so ./test_main
stack overflow error

今回使用したGCCのバージョンを下記に示す。

andre@andre-VirtualBox:~/work/LD_PRELOAD$ gcc --version
gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

CUnit を試す。

はじめに

 本記事は、CUnitの入門してみた結果をまとめてみたものである。
使用したソース等は、https://github.com/tomohikoseven/cunit_sample

テスト環境概要

 CUnitのテスト環境は、下記のような階層的な構造になっている。

テスト・レジストリ
   |- テスト・スイート1
   |      | - テストケース1
   |       :
   |      ` - テストケース N
   |- テスト・スイート2
   :
   `- テスト・スイート M

テスト環境とテストソースの対応

 上記テスト環境とテストソースの対応を下記に示す(★の部分)。

/*
 * test
 */
#include
#include

// test target
#include                                           ※ 今回のテスト対象。ただのフィボナッチ数出力関数

/*
 * prototype
 */
void test_fib_001(void);
void test_fib_002(void);

/*
 * main
 */
int
main()
{
    CU_pSuite fib_suite;

    // テストレジストリ作成
    CU_initialize_registry();                             ★ テスト・レジストリ

    // テストスイート
    fib_suite = CU_add_suite( "Fib", NULL, NULL );        ★ テスト・スイート
    // テストケース
    CU_add_test( fib_suite, "test_001", test_fib_001 );   ★ テストケース
    CU_add_test( fib_suite, "test_002", test_fib_002 );

    // テスト実行
    CU_console_run_tests();

    // テストレジストリ削除
    CU_cleanup_registry();

    return 0;
}
void
test_fib_001(void)
{
    int n   = 0;
    int ans = 1;

    CU_ASSERT_EQUAL( fib( n ), ans );
}
void
test_fib_002(void)
{
    int n   = 1;
    int ans = 1;

    CU_ASSERT_EQUAL( fib( n ), ans );
}

Makefile

 テストプログラムのMakefileを示す。libcunit.so が/usr/local/lib に、CUnit/CUnit.h と CUnit/Console.h が /usr/local/includeにないといけない。

SRCS=test_main.c fib.c
OBJS=$(SRCS:.c=.o)
INCLUDES=-I.                                       ※ fib.hのインクルードパス。CUnit.h や Console.hのインクルードパスではない。

all:test_main
test_main:$(OBJS)
	$(CC) -o $@ $^ -L/usr/local/lib -lcunit    ★ libcunit.so のリンク
.c.o:
	$(CC) -c $< $(INCLUDES)
clean:
	\rm -f $(OBJS) test_main core.*

テスト実行結果

andre@andre-VirtualBox:~/work/cunit/test$ ./test_main


     CUnit - A Unit testing framework for C - Version 2.1-2
             http://cunit.sourceforge.net/


 ***************** CUNIT CONSOLE - MAIN MENU ******************************
(R)un  (S)elect  (L)ist  (A)ctivate  (F)ailures  (O)ptions  (H)elp  (Q)uit
Enter command: r

Running Suite : Fib
     Running Test : test_001
     Running Test : test_002

Run Summary:    Type  Total    Ran Passed Failed Inactive
              suites      1      1    n/a      0        0
               tests      2      2      2      0        0
             asserts      2      2      2      0      n/a

Elapsed time =    0.000 seconds


 ***************** CUNIT CONSOLE - MAIN MENU ******************************
(R)un  (S)elect  (L)ist  (A)ctivate  (F)ailures  (O)ptions  (H)elp  (Q)uit
Enter command: 

実行可能形式そのものに共有ライブラリパスを指定させる

はじめに

 共有ライブラリの場所は、LD_LIBLARY_PATHだけだと思っていたが、実行可能形式そのものに指定することもできると知った。それを試した。
今回試すために使ったファイル群を下記に置く。
https://github.com/tomohikoseven/rpath

実行可能形式に指定する方法

 gcc でのリンク時に rpath を指定して行う。具体的には以下。

all:main.o fibso
	$(CC) -Xlinker -rpath -Xlinker ./ -o fib main.o libfib.so
#	$(CC) -Xlinker -rpath -Xlinker /home/andre/work/liba/fib-1.0.0 -o fib main.o libfib.so

下のほうはコメントアウトになっているが、気にしない。要は、絶対パスでも相対パスでもOKであるということ。

記述形式は、[ -Xlinker -rpath -Xlinker xxx(パス) ]である。

ここで注意したいのは、共有ライブラリの名称をきちんと書くこと。
つまり、[ -fib ]ではなく[ libfib.so ]であること。

試した結果

 実際に実行可能形式を作成し、ldd コマンドで共有ライブラリへのパスが通っているのか見てみた。

 andre@andre-VirtualBox:~/work/liba/fib-1.0.0$ ll
 合計 40
 drwxrwxr-x 2 andre andre 4096 2012-09-09 16:17 ./
 drwxrwxr-x 3 andre andre 4096 2012-09-09 16:06 ../
 -rw-rw-r-- 1 andre andre  322 2012-09-09 16:04 Makefile
 -rwxrwxr-x 1 andre andre 8108 2012-09-09 16:05 fib*
 -rw-rw-r-- 1 andre andre  260 2012-09-09 15:36 fib.c
 -rwxrwxr-x 1 andre andre 7450 2012-09-09 16:05 libfib.so*
 -rw-rw-r-- 1 andre andre  130 2012-09-09 16:05 main.c
 -rw-rw-r-- 1 andre andre 2400 2012-09-09 16:05 main.o

andre@andre-VirtualBox:~/work/liba/fib-1.0.0$ ldd fib
	linux-gate.so.1 =>  (0x0034b000)
	libfib.so => ./libfib.so (0x00e03000)                          ★ 共有ライブラリが通っている
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0x00110000)
	/lib/ld-linux.so.2 (0x00610000)
andre@andre-VirtualBox:~/work/liba/fib-1.0.0$ ./fib
fib(6):13                                                              ★ 実行できる
andre@andre-VirtualBox:~/work/liba/fib-1.0.0$ echo $LD_LIBRARY_PATH
                                                                       ★ LD_LIBRARY_PATH は空
andre@andre-VirtualBox:~/work/liba/fib-1.0.0$ mv libfib.so ../         ★ libfib.so の場所を変える
andre@andre-VirtualBox:~/work/liba/fib-1.0.0$ ldd fib
	linux-gate.so.1 =>  (0x003f5000)
	libfib.so => not found                                         ★ 共有ライブラリが通っていない
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0x007a6000)
	/lib/ld-linux.so.2 (0x0038a000)
andre@andre-VirtualBox:~/work/liba/fib-1.0.0$ ./fib
./fib: error while loading shared libraries: libfib.so: cannot open shared object file: No such file or directory
                                                                       ★ 共有ライブラリのロードに失敗している

Makefile

Makefile の中身だけ、重要なので、下記に示す。後のファイルはGithubからダウンロードしてください。

andre@andre-VirtualBox:~/work/liba/fib-1.0.0$ cat Makefile 
CC = gcc
CFLAGS=-g

all:main.o fibso
	$(CC) -Xlinker -rpath -Xlinker ./ -o fib main.o libfib.so
#	$(CC) -Xlinker -rpath -Xlinker /home/andre/work/liba/fib-1.0.0 -o fib main.o libfib.so

main.o:main.c
	$(CC) $(CFLAGS) -c main.c

fibso:fib.c
	$(CC) $(CFLAGS) -fPIC -shared -o libfib.so fib.c

clean:
	rm -f fib.o fib main.o