プログラミングなどなど

プログラミングに関係しそうな内容を

C言語:GDBによるデバッグ

0からnまでの整数の和を求める例を題材にデバッガ(gdb)の使用例を紹介したいと思います。バグがある状態から始めます。

main.c(バグ有り)

int sum(int n)
{
  int acc = 0;

  int i;
  for (i = 0; i < n; i++)
  {
    acc += i;
  }
  return acc;
}

int main(void)
{
  int n = 10;
  int wa = sum(n);

  return wa;
}

コンパイル

デバッグ用にオプションをつけておきます。

$ gcc -Wall -g3 main.c -o main

GDB配下でのプログラム実行

gdbは起動中のプログラムにアタッチす方法も有りますが、今回はgdbの引数にプログラムを指定して起動します。

$ gdb -q main
Reading symbols from main...done.
(gdb) run 
Startping program: /home/makoto/blog/22_Cdebug/main 
[Inferior 1 (process 4924) exited with code 055]
(gdb) p $_exitcode
$1 = 45

1から10までの和なので55になってほしいのですが、45になっています。

ステップ実行していく

ひとまず関数のトップであるmainから調べていくことにします。
mainで止まるようにブレークポイントを設定します。

(gdb) break main
Breakpoint 1 at 0x555555554630: file main.c, line 15.
(gdb) info breakpoints 
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000555555554630 in main at main.c:15
	breakpoint already hit 1 time

main.cの15行目にブレークポイントが設定されています。再度プログラム実行します。

(gdb) run 
Starting program: /home/makoto/blog/22_Cdebug/main 

main関数でプログラムが止まります。

Breakpoint 1, main () at main.c:15
15	  int n = 10;

まだ、「int n = 10」は実行されていません。試しに変数nの値を調べてみます。

(gdb) print n
$2 = 0

1文進めます。($Xの部分は気にしなくて大丈夫です。)

(gdb) next
16	  int wa = sum(n);

今度はnが10で初期化されているはずです。

(gdb) print n
$3 = 10

もう1文進めます。

(gdb) next
18	  return wa;
(gdb) print wa
$4 = 45

結果が45になっています。sumが悪そうです。mainのブレークポイントを削除して、今度はsumに設定してみます。

(gdb) delete 1
(gdb) break sum
Breakpoint 2 at 0x555555554601: file main.c, line 3.
(gdb) info breakpoints 
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x0000555555554601 in sum at main.c:3

再度最初から実行します。

(gdb) run 
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/makoto/blog/22_Cdebug/main 

Breakpoint 2, sum (n=10) at main.c:3
3		int acc = 0;

今度はsum(main.cの3行目)で止まります。1文実行してみます。

(gdb) next
6		for (i = 0; i < n; i++)
(gdb) print acc
$8 = 0

accが0で初期化されています。for文のなかまで進めてみます。

(gdb) next
8	    acc += i;

最初のループを終え、行が戻ります。

(gdb) next
6	  for (i = 0; i < n; i++)

ここで各種値を確認してみます。

(gdb) print acc
$5 = 0
(gdb) print i
$6 = 0

このタイミングではまだiはインクリメントされていません。継続条件も確認してみます。

(gdb) print i < n
$3 = 1

成立しています。もう一文進めてみます。

(gdb) next
8	    acc += i;

i++が実行されているはずです。

(gdb) print i
$5 = 1

iが1なので今度はaccに1が加えられるはずです。

(gdb) next
6	  for (i = 0; i < n; i++)
(gdb) print acc
$6 = 1

加えられています。ループを一気に進めてみます。

(gdb) until 
10	  return acc;
(gdb) print acc
$7 = 45

accが45となっていて10足りません。この時のiと各種値を見てみます。

(gdb) print i
$10 = 10
(gdb) print n
$11 = 10
(gdb) print i < n
$12 = 0

iは10になっていますが、i < n が成立せずにループを終了していそうです。もう少し詳しく見てみます。再度実行します。

(gdb) run 
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/makoto/blog/22_Cdebug/main 

Breakpoint 2, sum (n=10) at main.c:3
3	  int acc = 0;

直前のiが9の部分で止まるようにして、そこから詳しく見てみたいと思います。

(gdb) watch i == 9
Hardware watchpoint 3: i == 9

実行を進めます。

(gdb) continue 
Continuing.

Hardware watchpoint 3: i == 9

Old value = 0
New value = 1
0x000055555555461b in sum (n=10) at main.c:6
6	  for (i = 0; i < n; i++)

値を確認します。

(gdb) print i
$10 = 9
(gdb) print acc
$11 = 36

i++が実行された直後ですね。

(gdb) next
8	    acc += i;
(gdb) next
6	  for (i = 0; i < n; i++)
(gdb) print acc
$13 = 45

36に9が加えられて45になっています。更に実行を継続してみます。

(gdb) continue 
Continuing.

Hardware watchpoint 7: i == 9

Old value = 1
New value = 0
0x000055555555461b in sum (n=10) at main.c:6
6	  for (i = 0; i < n; i++)
(gdb) print i
$29 = 10
(gdb) print n
$30 = 10
(gdb) print i < n
$31 = 0

i == 9の条件が破れたところで再度止まってくれました。各種値を確認してみると、i,nが10となりi < nの継続条件が成り立っていません。C言語からはインクリメントと継続条件のどちらが先か見えませんが、このような場合は逆アセンブルしてみると見えてきます。(私はアセンブラの理解が怪しいので誤っていたらすみません。)

(gdb) disassemble 
Dump of assembler code for function sum:
   0x00005555555545fa <+0>:	push   %rbp
   0x00005555555545fb <+1>:	mov    %rsp,%rbp
   0x00005555555545fe <+4>:	mov    %edi,-0x14(%rbp)
   0x0000555555554601 <+7>:	movl   $0x0,-0x8(%rbp)
   0x0000555555554608 <+14>:	movl   $0x0,-0x4(%rbp)
   0x000055555555460f <+21>:	jmp    0x55555555461b <sum+33>
   0x0000555555554611 <+23>:	mov    -0x4(%rbp),%eax
   0x0000555555554614 <+26>:	add    %eax,-0x8(%rbp)
   0x0000555555554617 <+29>:	addl   $0x1,-0x4(%rbp)
=> 0x000055555555461b <+33>:	mov    -0x4(%rbp),%eax
   0x000055555555461e <+36>:	cmp    -0x14(%rbp),%eax
   0x0000555555554621 <+39>:	jl     0x555555554611 <sum+23>
   0x0000555555554623 <+41>:	mov    -0x8(%rbp),%eax
   0x0000555555554626 <+44>:	pop    %rbp
   0x0000555555554627 <+45>:	retq   
End of assembler dump.

+29でインクリメントされていて+36の部分で継続条件を判定しているようです。
rbp-0x4の場所が変数iの場所ですね。

(gdb) print *(int *)($rbp-0x4)
$32 = 10

nが10の場合はaccに加えたいので、継続条件をi <= nに修正することにします。

int sum(int n)
{
  int acc = 0;

  int i;
  for (i = 0; i <= n; i++)
  {
    acc += i;
  }
  return acc;
}

コンパイルして再度実行してみます。(コンパイルば別のターミナルから実行してgdbは別ターミナルで実行したままにしています。)

(gdb) run 
Starting program: /home/makoto/blog/22_Cdebug/main 
warning: Probes-based dynamic linker interface failed.
Reverting to original interface.


Breakpoint 2, sum (n=10) at main.c:3
3	  int acc = 0;
(gdb) until 
6	  for (i = 0; i <= n; i++)
(gdb) 
8	    acc += i;
(gdb) 
6	  for (i = 0; i <= n; i++)
(gdb) 
10	  return acc;
(gdb) print acc
$33 = 55

うまく行っていそうです。

C言語:型に性質をもたせる?(コンパイル時の警告まで)

[増補改訂]関数プログラミング実践入門 ──簡潔で、正しいコードを書くために (WEB+DB PRESS plus)の「1.5 型に性質をもたせる HTMLの文字列エスケープ」を読んで、C言語の場合はどんな風にできるのかと思い実装してみました。おそらくコンパイル時のチェックで警告されるところまではいくのかな、と思いつつ。



ソース以下となります。

サンプルソースコンパイル

HTMLEscapedString.h

ヘッダファイルでは構造体HTMLEscapedStringのメンバを利用者に公開しないようにしています。

#ifndef __HTML_ESC_STRING__
#define __HTML_ESC_STRING__

typedef struct HTMLEscapedString HTMLEscapedString;

const HTMLEscapedString * const escape(const char *);
void putHTMLEscapedStrLn(const struct HTMLEscapedString * const);

#endif
HTMLEscapedString.c

構造体HTMLEscapedStringのメンバはヘッダファイルで利用者に公開しないようにしていますが、今回のケースでは欲しいメンバがなかったので、実際のメンバもありません。メンバを持たない構造体って今まで考えたことはなかったのですが定義できるのですね。本のサンプルではescape定義時に使用する関数escapeAmpとescapeOtherを補助として定義していますが、以下ではそれらを使用せずに処理を真似て実装しています。

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

#include "HTMLEscapedString.h"

/* 型 */
struct HTMLEscapedString
{
};

/* メモリを確保して返すことにする。*/
static HTMLEscapedString * const createHTMLEscapedString(int size)
{
  return malloc(size);
}

const HTMLEscapedString * const escape(const char *src)
{
  /* 簡易的にサイズは固定とする。*/
  HTMLEscapedString *const dst = createHTMLEscapedString(128);
  char *dstp = (char *)dst;

  while(*src)
  {
    if('&' == *src)
    {
       *(dstp++) = '&';
       *(dstp++) = 'a';
       *(dstp++) = 'm';
       *(dstp++) = 'p';
    }
    else if('<' == *src)
    {
       *(dstp++) = '&';
       *(dstp++) = 'l';
       *(dstp++) = 't';
    }
    else if('>' == *src)
    {
       *(dstp++) = '&';
       *(dstp++) = 'g';
       *(dstp++) = 't';
    }
    else if('"' == *src)
    {
       *(dstp++) = '&';
       *(dstp++) = 'q';
       *(dstp++) = 'u';
       *(dstp++) = 'o';
       *(dstp++) = 't';
    }
    else
    {
      *(dstp++) = *src;
    }

    src++;
  }

  return dst;
}

void putHTMLEscapedStrLn(const struct HTMLEscapedString *str)
{
  printf("%s\n", (char *)str);
}

簡単にするためエスケープ後の文字列は適当にサイズ128で確保しています。実際にこのような作りにする場合は、変換後の文字列の領域がどれぐらい必要になるかを知るためにサイズを求める関数を別途用意するなど必要になると思います。

main.c
#include <stdio.h>
#include "HTMLEscapedString.h"

int main(void)
{
  const char *src = "Amp:&,<include>,Quote:\"";
  const HTMLEscapedString * const dst = escape(src);

  printf("%s\n", src);
  putHTMLEscapedStrLn(dst);

  /* 警告が出る */
  printf("%s\n", dst);

  return 0;
}
Makefile
all : runAll

runAll : main01 main02
	./main01
	@echo ""
	LD_LIBRARY_PATH=. ./main02

# 共有ライブラリ使用版
main02 : main.o libHTMLEscapedString.so
	gcc -Wall main.o -L . -lHTMLEscapedString -o main02

# オブジェクトファイルリンク版
main01 : main.o HTMLEscapedString.o
	gcc -Wall main.o HTMLEscapedString.o -o main01

main.o : main.c HTMLEscapedString.h
	gcc -Wall -c main.c

libHTMLEscapedString.so : HTMLEscapedString.c HTMLEscapedString.h
	gcc -Wall -shared -fPIC HTMLEscapedString.o -o libHTMLEscapedString.so

HTMLEscapedString.o : HTMLEscapedString.c HTMLEscapedString.h
	gcc -Wall -c HTMLEscapedString.c

clean :
	rm -f *.o *.so main01 main02

実行

オプジェクトファイルをリンク時に指定する方法(main01)と共有ライブラリを使用する方法で実行してみました。実行すると型が合わない旨の警告が出ています。

$ make
gcc -Wall -c main.c
main.c: In function ‘main’:
main.c:13:12: warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘const HTMLEscapedString * const {aka const struct HTMLEscapedString * const}’ [-Wformat=]
   printf("%s\n", dst);
           ~^
gcc -Wall -c HTMLEscapedString.c
gcc -Wall main.o HTMLEscapedString.o -o main01
gcc -Wall -shared -fPIC HTMLEscapedString.o -o libHTMLEscapedString.so
gcc -Wall main.o -L . -lHTMLEscapedString -o main02
./main01
Amp:&,<include>,Quote:"
Amp:&amp,&ltinclude&gt,Quote:&quot
Amp:&amp,&ltinclude&gt,Quote:&quot

LD_LIBRARY_PATH=. ./main02
Amp:&,<include>,Quote:"
Amp:&amp,&ltinclude&gt,Quote:&quot
Amp:&amp,&ltinclude&gt,Quote:&quot

GCC:LD_PRELOADを使用したmallocの動作切替

今回もC言語の関数単体テスト系の話題です。mallocを利用する関数を試験する際にmallocのスタブを作成して単体試験を実行しようとすると単体試験のツール自体がmallocを使用していたりしてうまく動かないケースがあります。

このような場合、GDBを使用してmallocの戻り値を操作したりしていたのですが、LD_PRELOADを使用してmallocの動作を切り替えることができましたので紹介します。ネタもとは「Binary Hacks ―ハッカー秘伝のテクニック100選」のHACK#61「LD_PRELOADで既存の関数をラップする」です。

使用したのは以下のソースになります。

サンプルソースコンパイル

malloc_stub.c
#define _GNU_SOURCE

#include <dlfcn.h>
#include <stdlib.h>

static void *(*malloc0)(size_t size);

void __attribute__((constructor))
init_malloc0(void)
{
  malloc0 = dlsym(RTLD_NEXT, "malloc");
}

void *malloc(size_t size)
{
  if( !getenv("MALLOC_STUB") )
  {
    /* 環境変数未定義時 */
    return malloc0(size);
  }
  else
  {
    /* 環境変数定義時 */
    return NULL;
  }
}

環境変数MALLOC_STUBが定義されている場合はNULLを返すことにします。
このモジュールは共有ライブラリとしてコンパイルします。

$ gcc -shared -fPIC -o malloc_stub.so malloc_stub.c -ldl
use_malloc.c

mallocを使用するモジュール

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

int main(void)
{
  void *vp = malloc(1024);

  if( vp )
  {
    printf("通常\n");
  }
  else
  {
    printf("NULL\n");
  }

  return 0;
}

こちらは普通にコンパイルします。

$ gcc -Wall use_malloc.c -o use_malloc

実行

通常の実行
$ ./use_malloc 
通常
LD_PRELOAD指定かつ環境変数未定義
$ LD_PRELOAD=./malloc_stub.so ./use_malloc 
通常
LD_PRELOAD指定かつ環境変数定義
$ LD_PRELOAD=./malloc_stub.so MALLOC_STUB=1 ./use_malloc 
NULL

GCC:-includeを使用したマクロ展開制御

C言語の関数単体テストを作成している際に関数形式マクロの展開を調整したいと思う部分が出てきました。使用していたコンパイラGCCでマニュアルを見るとマクロの読み込みを制御するオプション(-include)がありましたので、使ってみました。

使用したのは以下のソースになります。

サンプルソース

macro.h

関数形式マクロが定義されているヘッダファイル

#ifndef __MACRO_H__
#define __MACRO_H__

#include <stdio.h>

#define LOG(fmt, ...) printf("%s, %s, %d : " fmt,  __func__, __FILE__, __LINE__, ##__VA_ARGS__)

#endif
func.c

関数形式マクロを利用するモジュール

#include "macro.h"
#include "func.h"

int square(int x)
{
  int ret = x * x;
  LOG("x = %d, x**2 = %d\n", x, ret);
  
  return ret;
}
func.h
#ifndef __FUNC_H__
#define __FUNC_H__

int square(int);

#endif

普通にプリプロセスを通した場合

$ gcc -E func.c
:
int square(int x)
{
  int ret = x * x;
  printf("%s, %s, %d : " "x = %d, x**2 = %d\n", __func__, "func.c", 7, x, ret);

  return ret;
}

普通にプリプロセスを通すとLOGが展開されてprintfになっていますが、
このprintf展開をfunc.c, func.hを変更せずにどうにかLOGのままにしたい。

GCCのオプションを調べると-includeが使えそうでした。
以下のようなテスト向けのヘッダファイルを用意してプリプロセスを行ってみます。

test.h
#ifdef LOG
#undef LOG
#endif

GCC -includeオプション使用

$ gcc -Wall -E -include macro.h -include test.h func.c
:
int square(int x)
{
  int ret = x * x;
  LOG("x = %d, x**2 = %d\n", x, ret);

  return ret;
}

LOGのまま残っています。

SPIN:状態の探索(2プロセス)(探索)

前回「Pr: 0 Tr: 6」で探索が切れているように見える部分が気になったので、デバッグしてみました。調べられた感じでは、プロセスの削除順序が関係しているようです。一応、「プロセスは生成順序と逆順で消滅する」と記載のある資料を見つけたのでこの記述の動作に該当してそうです。ただ、1次情報までは見つけられませんでした。

使用したソースから検証器の実行まで

ソース 2p.pmlを使用しています。検証器の生成とコンパイルも前回と同様となります。

デバッグ

トライ&エラーは省きますが、調べていくとdo_transitとdelprocに行き着きました。

(gdb) break do_transit 
Breakpoint 1 at 0x404d: file pan.c, line 5887.
(gdb) break delproc 
Breakpoint 2 at 0x93c6: file pan.c, line 10440.
(gdb) run 
Starting program: /home/makoto/blog/16_delproc/pan 
0: Down - program non-accepting [pids 1-0]
	New state 0
	 State: 2,0,0,0,0,0,28,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,1,9,0,0,
	Vector: 2,0,0,0,0,0,28,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,1,9,0,0,
:
(gdb) c
Continuing.
Pr: 0 Tr: 6 <-- 気になる部分

Breakpoint 2, delproc (sav=1, h=0) at pan.c:10440 <-- h=0はプロセスp
10440	{	int d, i=0;
(gdb) n
10444		if (h+1 != (int) now._nr_pr)
(gdb) 
10445		{	return 0;
(gdb) list 
10440	{	int d, i=0;
10441	#ifndef NOCOMP
10442		int o_vsize = vsize;
10443	#endif
10444		if (h+1 != (int) now._nr_pr)  <-- この条件が成立し
10445		{	return 0;             <-- delprocを抜けている。(プロセスqはまだ生きている。)
10446		}
10447	#ifdef TRIX
10448		#ifdef V_TRIX
10449			printf("%4d: delproc %d -- parent %d\n", depth, h, processes[h]->parent_pid);
(gdb) n
10530	}
(gdb) 
do_transit (t=0x55555576c350, II=0) at pan.c:5910
5910	}
:
(gdb) n
new_state () at pan.c:7126
7126				{	continue;
(gdb) list 
7121	#ifdef INLINE
7122	#include FORWARD_MOVES
7123	P999:			/* jumps here when move succeeds */
7124	#else
7125				if (!(_m = do_transit(t, II)))
7126				{	continue; <-- このルートに入り次の探索に進んでいる。
7127				}
7128	#endif
7129	#ifdef BCS
7130				if (depth > BASE
デバッグ補助

上記をみるとソースにプリプロセスディレクディブが多く含まれてきます。これがあるとかなりソースが読みにくいです。実は、この結果に行き着く際にはpan.cに含まれているコメント「gcc -E -DNOREDUCE -DBITSTATE pan.c > ppan.c」を参考に以下のようにコンパイルしてデバッグしました。

$ gcc -E -DVERBOSE -DSDUMP -DNOCOMP -DNOREDUCE pan.c | indent -sob -st -bli0 | expand | sed '/^#/d' > ppan.c
$ gcc -w -g3 ppan.c -o pan

SPIN:状態の探索(2プロセス)

今回は2プロセスが動作するサンプルモデルを作成して状態空間の探索を追ってみました。前回と同じように読み解けるかと思いきや、よくわからない動きを見ることとなりました。

使用したソースは以下となります。

使用したソースから検証器の実行まで

単に2プロセスが動作するできるだけ単純になるような以下のソースを用意しました。

ソース 2p.pml
byte a;
active proctype p()
{
  a = 170; /* 0xaa = -86 */
}

byte b;
active proctype q()
{
  b = 187; /* 0xbb = -69 */
}
検証器の生成
$ spin -a -o2 2p.pml
検証器のコンパイル
$ gcc -w -g3 -DVERBOSE -DSDUMP -DNOCOMP -DNOREDUCE pan.c -o pan
実行結果
$ ./pan | cat -n
     1	0: Down - program non-accepting [pids 1-0]
     2		New state 0
     3		 State: 2,0,0,0,0,0,28,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,1,9,0,0,
     4		Vector: 2,0,0,0,0,0,28,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,1,9,0,0,
     5	Pr: 1 Tr: 3
     6	  0: proc 1 exec 3, 1 to 2, b = 187   non-accepting [tau=0]
     7	1: Down - program non-accepting [pids 1-0]
     8		New state 1
     9		 State: 2,0,0,0,0,0,28,0,0,-69,0,0,0,0,0,0,0,8,0,0,0,0,0,0,1,17,0,0,
    10		Vector: 2,0,0,0,0,0,28,0,0,-69,0,0,0,0,0,0,0,8,0,0,0,0,0,0,1,17,0,0,
    11	Pr: 1 Tr: 4
    12	  1: proc 1 exec 4, 2 to 0, -end-   non-accepting [tau=0]
    13	2: Down - program non-accepting [pids 0-0]
    14		New state 2
    15		 State: 1,0,0,0,0,0,20,0,0,-69,0,0,0,0,0,0,0,8,0,0,
    16		Vector: 1,0,0,0,0,0,20,0,0,-69,0,0,0,0,0,0,0,8,0,0,
    17	Pr: 0 Tr: 5
    18	  2: proc 0 exec 5, 1 to 2, a = 170   non-accepting [tau=0]
    19	3: Down - program non-accepting [pids 0-0]
    20		New state 3
    21		 State: 1,0,0,0,0,0,20,0,-86,-69,0,0,0,0,0,0,0,16,0,0,
    22		Vector: 1,0,0,0,0,0,20,0,-86,-69,0,0,0,0,0,0,0,16,0,0,
    23	Pr: 0 Tr: 6
    24	  3: proc 0 exec 6, 2 to 0, -end-   non-accepting [tau=0]
    25	4: Down - program non-accepting [pids -1-0]
    26		New state 4
    27		 State: 0,0,0,0,0,0,10,0,-86,-69,
    28		Vector: 0,0,0,0,0,0,10,0,-86,-69,
    29	  4: no move [II=-1, tau=0, boq=-1]
    30	4: Up - program
    31	  4: proc 0 reverses 6, 2 to 0
    32		-end- [abit=0,adepth=0,tau=0,0]
    33	3: Up - program
    34	  3: proc 0 reverses 5, 1 to 2
    35		a = 170 [abit=0,adepth=0,tau=0,0]
    36	2: Up - program
    37	  2: proc 1 reverses 4, 2 to 0
    38		-end- [abit=0,adepth=0,tau=0,0]
    39	Pr: 0 Tr: 5
    40	  1: proc 0 exec 5, 1 to 2, a = 170   non-accepting [tau=0]
    41	2: Down - program non-accepting [pids 1-0]
    42		New state 5
    43		 State: 2,0,0,0,0,0,28,0,-86,-69,0,0,0,0,0,0,0,16,0,0,0,0,0,0,1,17,0,0,
    44		Vector: 2,0,0,0,0,0,28,0,-86,-69,0,0,0,0,0,0,0,16,0,0,0,0,0,0,1,17,0,0,
    45	Pr: 1 Tr: 4
    46	  2: proc 1 exec 4, 2 to 0, -end-   non-accepting [tau=0]
    47	3: Down - program non-accepting [pids 0-0]
    48		Old state 3
    49		 State: 1,0,0,0,0,0,20,0,-86,-69,0,0,0,0,0,0,0,16,0,0,
    50		Vector: 1,0,0,0,0,0,20,0,-86,-69,0,0,0,0,0,0,0,16,0,0,
    51	3: Up - program
    52	  3: proc 1 reverses 4, 2 to 0
    53		-end- [abit=0,adepth=0,tau=0,0]
    54	Pr: 0 Tr: 6
    55	2: Up - program
    56	  2: proc 0 reverses 5, 1 to 2
    57		a = 170 [abit=0,adepth=0,tau=0,0]
    58	1: Up - program
    59	  1: proc 1 reverses 3, 1 to 2
    60		b = 187 [abit=0,adepth=0,tau=0,0]
    61	Pr: 0 Tr: 5
    62	  0: proc 0 exec 5, 1 to 2, a = 170   non-accepting [tau=0]
    63	1: Down - program non-accepting [pids 1-0]
    64		New state 6
    65		 State: 2,0,0,0,0,0,28,0,-86,0,0,0,0,0,0,0,0,16,0,0,0,0,0,0,1,9,0,0,
    66		Vector: 2,0,0,0,0,0,28,0,-86,0,0,0,0,0,0,0,0,16,0,0,0,0,0,0,1,9,0,0,
    67	Pr: 1 Tr: 3
    68	  1: proc 1 exec 3, 1 to 2, b = 187   non-accepting [tau=0]
    69	2: Down - program non-accepting [pids 1-0]
    70		Old state 5
    71		 State: 2,0,0,0,0,0,28,0,-86,-69,0,0,0,0,0,0,0,16,0,0,0,0,0,0,1,17,0,0,
    72		Vector: 2,0,0,0,0,0,28,0,-86,-69,0,0,0,0,0,0,0,16,0,0,0,0,0,0,1,17,0,0,
    73	2: Up - program
    74	  2: proc 1 reverses 3, 1 to 2
    75		b = 187 [abit=0,adepth=0,tau=0,0]
    76	Pr: 0 Tr: 6
    77	1: Up - program
    78	  1: proc 0 reverses 5, 1 to 2
    79		a = 170 [abit=0,adepth=0,tau=0,0]
    80	
    81	(Spin Version 6.4.9 -- 17 December 2018)
    82		+ FullStack Matching
    83	
    84	Full statespace search for:
    85		never claim         	- (none specified)
    86		assertion violations	+
    87		acceptance   cycles 	- (not selected)
    88		invalid end states	+
    89	
    90	State-vector 28 byte, depth reached 4, errors: 0
    91	        7 states, stored
    92	        2 states, matched
    93	        0 matches within stack
    94	        9 transitions (= stored+matched)
    95	        0 atomic steps
    96	hash conflicts:         0 (resolved)
    97	stackframes: 2/0
    98	
    99	stats: fa 0, fh 0, zh 0, zn 0 - check 0 holds 0
   100	stack stats: puts 0, probes 0, zaps 0
   101	  128.730	memory usage (Mbyte)
   102	
   103	unreached in proctype p
   104		(0 of 2 states)
   105	unreached in proctype q
   106		(0 of 2 states)
   107	
   108	pan: elapsed time 0 seconds

結果のグラフ化

上記結果を読み解いていくと前回は出てこなかった以下のパターンが54, 76行目に出てきます。「Pr: 0 Tr: 6」の部分でいきなりスタックを巻き戻し、ぱっと見ると探索を中断しているように見えます。

    51	3: Up - program
    52	  3: proc 1 reverses 4, 2 to 0
    53		-end- [abit=0,adepth=0,tau=0,0]
    54	Pr: 0 Tr: 6
    55	2: Up - program
    56	  2: proc 0 reverses 5, 1 to 2
    57		a = 170 [abit=0,adepth=0,tau=0,0]
    73	2: Up - program
    74	  2: proc 1 reverses 3, 1 to 2
    75		b = 187 [abit=0,adepth=0,tau=0,0]
    76	Pr: 0 Tr: 6
    77	1: Up - program
    78	  1: proc 0 reverses 5, 1 to 2
    79		a = 170 [abit=0,adepth=0,tau=0,0]

グラフ化したものが以下になります。「Pr: 0 Tr: 6」以降の部分は赤で表示させています。
ともに新規状態としても良さそうですが、なぜなんでしょうかね。

f:id:hihdrum:20190813231848j:plain
グラフ

SPIN:状態探索(1プロセス,分岐あり)とグラフ化

SPINの検証器が状態空間をどのように探索しているのかが気になったので、状態ベクトルを出力させながら追ってみました。

使用したソースは以下となります。

使用したソースから検証器の実行まで

分岐を2箇所に用意した計4経路を通るような以下のソースを用意しました。

ソース branch.pml
byte a = 0;
active proctype p()
{
  if
  :: a = 170; /* 0xaa = -86 */
  :: a = 187; /* 0xbb = -69 */
  fi

  if
  :: a = 204; /* 0xcc = -52 */
  :: a = 221; /* 0xdd = -35 */
  fi
}
検証器の生成
$ spin -a -o2 2p.pml
検証器のコンパイル
$ gcc -w -g3 -DVERBOSE -DSDUMP -DNOCOMP pan.c -o pan
実行結果
$ ./pan 
0: Down - program non-accepting [pids 0-0]
	New state 0
	 State: 1,0,0,0,0,0,20,0,0,0,0,0,0,0,0,0,0,12,0,0,
	Vector: 1,0,0,0,0,0,20,0,0,0,0,0,0,0,0,0,0,12,0,0,
Pr: 0 Tr: 3
  0: proc 0 exec 3, 3 to 7, a = 170   non-accepting [tau=0]
1: Down - program non-accepting [pids 0-0]
	New state 1
	 State: 1,0,0,0,0,0,20,0,-86,0,0,0,0,0,0,0,0,28,0,0,
	Vector: 1,0,0,0,0,0,20,0,-86,0,0,0,0,0,0,0,0,28,0,0,
Pr: 0 Tr: 5
  1: proc 0 exec 5, 7 to 9, a = 204   non-accepting [tau=0]
2: Down - program non-accepting [pids 0-0]
	New state 2
	 State: 1,0,0,0,0,0,20,0,-52,0,0,0,0,0,0,0,0,36,0,0,
	Vector: 1,0,0,0,0,0,20,0,-52,0,0,0,0,0,0,0,0,36,0,0,
Pr: 0 Tr: 7
  2: proc 0 exec 7, 9 to 0, -end-   non-accepting [tau=0]
3: Down - program non-accepting [pids -1-0]
	New state 3
	 State: 0,0,0,0,0,0,10,0,-52,0,
	Vector: 0,0,0,0,0,0,10,0,-52,0,
  3: no move [II=-1, tau=0, boq=-1]
3: Up - program
  3: proc 0 reverses 7, 9 to 0
	-end- [abit=0,adepth=0,tau=0,0]
2: Up - program
  2: proc 0 reverses 5, 7 to 9
	a = 204 [abit=0,adepth=0,tau=0,0]
Pr: 0 Tr: 6
  1: proc 0 exec 6, 7 to 9, a = 221   non-accepting [tau=0]
2: Down - program non-accepting [pids 0-0]
	New state 4
	 State: 1,0,0,0,0,0,20,0,-35,0,0,0,0,0,0,0,0,36,0,0,
	Vector: 1,0,0,0,0,0,20,0,-35,0,0,0,0,0,0,0,0,36,0,0,
Pr: 0 Tr: 7
  2: proc 0 exec 7, 9 to 0, -end-   non-accepting [tau=0]
3: Down - program non-accepting [pids -1-0]
	New state 5
	 State: 0,0,0,0,0,0,10,0,-35,0,
	Vector: 0,0,0,0,0,0,10,0,-35,0,
  3: no move [II=-1, tau=0, boq=-1]
3: Up - program
  3: proc 0 reverses 7, 9 to 0
	-end- [abit=0,adepth=0,tau=0,0]
2: Up - program
  2: proc 0 reverses 6, 7 to 9
	a = 221 [abit=0,adepth=0,tau=0,0]
1: Up - program
  1: proc 0 reverses 3, 3 to 7
	a = 170 [abit=0,adepth=0,tau=0,0]
Pr: 0 Tr: 4
  0: proc 0 exec 4, 3 to 7, a = 187   non-accepting [tau=0]
1: Down - program non-accepting [pids 0-0]
	New state 6
	 State: 1,0,0,0,0,0,20,0,-69,0,0,0,0,0,0,0,0,28,0,0,
	Vector: 1,0,0,0,0,0,20,0,-69,0,0,0,0,0,0,0,0,28,0,0,
Pr: 0 Tr: 5
  1: proc 0 exec 5, 7 to 9, a = 204   non-accepting [tau=0]
2: Down - program non-accepting [pids 0-0]
	Old state 2
	 State: 1,0,0,0,0,0,20,0,-52,0,0,0,0,0,0,0,0,36,0,0,
	Vector: 1,0,0,0,0,0,20,0,-52,0,0,0,0,0,0,0,0,36,0,0,
2: Up - program
  2: proc 0 reverses 5, 7 to 9
	a = 204 [abit=0,adepth=0,tau=0,0]
Pr: 0 Tr: 6
  1: proc 0 exec 6, 7 to 9, a = 221   non-accepting [tau=0]
2: Down - program non-accepting [pids 0-0]
	Old state 4
	 State: 1,0,0,0,0,0,20,0,-35,0,0,0,0,0,0,0,0,36,0,0,
	Vector: 1,0,0,0,0,0,20,0,-35,0,0,0,0,0,0,0,0,36,0,0,
2: Up - program
  2: proc 0 reverses 6, 7 to 9
	a = 221 [abit=0,adepth=0,tau=0,0]
1: Up - program
  1: proc 0 reverses 4, 3 to 7
	a = 187 [abit=0,adepth=0,tau=0,0]

(Spin Version 6.4.9 -- 17 December 2018)
	+ Partial Order Reduction
	+ FullStack Matching

Full statespace search for:
	never claim         	- (none specified)
	assertion violations	+
	acceptance   cycles 	- (not selected)
	invalid end states	+

State-vector 20 byte, depth reached 3, errors: 0
        7 states, stored
        2 states, matched
        0 matches within stack
        9 transitions (= stored+matched)
        0 atomic steps
hash conflicts:         0 (resolved)
stackframes: 1/0

stats: fa 0, fh 0, zh 0, zn 0 - check 0 holds 0
stack stats: puts 0, probes 0, zaps 0
  128.730	memory usage (Mbyte)

unreached in proctype p
	(0 of 9 states)

pan: elapsed time 0 seconds

結果のグラフ化

上記の結果を見ると深さ優先探索で以下の流れて探索しています。

  • 1経路目 : s1からs3まで探索して終了状態まで進み、s1まで戻っています。
  • 2経路目 : s1からs5のまで探索し、s0まで戻っています。
  • 3経路目 : s1からs6まで進み、遷移先s2が探索済みだったのでs6に戻っています。
  • 4経路目 : s6からs4と進みますが、こちらも探索済みだったので、s0まで戻って探索終了となっています。

グラフ化したものが以下になります。
円形は状態で「State X」に対応するものを「sX」で、各状態でのaの値とプロセスの状態p(1:実行中,0:終了)を「a,p」で記載しています。四角は遷移で「Pr: A Tr: B」に対応するものを「trA,B」で記載しています。


f:id:hihdrum:20190811184922j:plain
グラフ