2010-01-02

pthreadのbacktraceでの不思議な現象

Debian lennyのgdbでbacktraceが正しく表示されないことがある。

うーむ。さっぱり原因が分からない。
どなたか分かる方、教えてください。

以下、再現させるためのプログラム。
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>


static void *print_thread(void *arg)
{
for (;;) {
printf("print thread\n");
}
}

int main(void)
{
pthread_t th;

pthread_create(&th, 0, print_thread, 0);


for (;;) {
printf("main\n");
}

return 0;
}

このプログラムをgdbで実行すると、"main\n"と"print thread\n"が表示される.
途中でCtrl-Cでプログラムを停止させてbacktraceを表示すると、stackが正しく表示されない。
(gdb) thr 1
[Switching to thread 1 (Thread 0xb7e576b0 (LWP 12994))]#0 0xb7fdc424 in __kernel_vsyscall ()
(gdb) bt
#0 0xb7fdc424 in __kernel_vsyscall ()
#1 0xb7f46b53 in ?? () from /lib/i686/cmov/libc.so.6
#2 0xb7eb87b3 in ?? () from /lib/i686/cmov/libc.so.6
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) thr 2
[Switching to thread 2 (Thread 0xb7e56b90 (LWP 12997))]#0 0xb7fdc424 in __kernel_vsyscall ()
(gdb) bt
#0 0xb7fdc424 in __kernel_vsyscall ()
#1 0xb7f2828b in write () from /lib/i686/cmov/libc.so.6
#2 0xb7ec1d6c in _IO_file_write () from /lib/i686/cmov/libc.so.6
#3 0xb7ec2ee7 in _IO_do_write () from /lib/i686/cmov/libc.so.6
#4 0xb7ec2895 in _IO_file_overflow () from /lib/i686/cmov/libc.so.6
#5 0xb7ec5713 in __overflow () from /lib/i686/cmov/libc.so.6
#6 0xb7eb8746 in puts () from /lib/i686/cmov/libc.so.6
#7 0x08048456 in print_thread (arg=0x0) at a.c:12
#8 0xb7fb94c0 in start_thread () from /lib/i686/cmov/libpthread.so.0
#9 0xb7f386de in clone () from /lib/i686/cmov/libc.so.6

thread 1のbacktraceが、corruptと表示される。

2009-12-23

プログラマ性愚説

プログラマ(SEも含む)の性賢説と性愚説というのを考えた。
どちらの立場をとるかによって、テストコードの意味合いが変わってくる。

■性賢説

プログラマは、正しく完璧な設計と実装を行うことが可能である。
そのコードはメンテナンス性も良く、後日の機能追加、仕様変更も容易である。

テストコードは、その完璧な実装に近づけるための手助けとなるためのものである.

■性愚説

プログラマは、正しく完璧な設計と実装を行うことはできない。
そのため、後日の機能追加、仕様変更時にコードを少なからず書き換える必要がでてくる。
最悪の場合はコードを全て書き直す必要がある。

テストコードは、その不完全なコードを少しでも良くするためものである。

----

性愚説をもう少し考えると、以下の結論がでてくる。

テスト対象のコードは大幅に書き換えられる。時には外部公開APIも変わる可能性がある。
→テストコードはテスト対象のコードが変わっても使える様にしておくべきである。
→テストコードは、再利用できる部分とできない部分に分けておくべきである。

例えばAPIのテストを行うときには、以下の二つのコードがある。
1. テストに使用するテストパターン
2. テスト対象の関数の呼び出し
このうち、1.は再利用が可能なので、2.とは別にしておくのが望ましい。

2009-12-10

x86のmisalignアクセス時に例外を起こす方法

x86ではmisalignアクセス時でも、例外を起こさずにアクセスする事ができる。
例えば次のコード

long *p;
char buf[32];
p = (long*)&buf[1];

*p = 0;

は、misalignアクセスであり、多くのプロセッサでは例外が発生する。

x86でも同様に例外を発生させてやるには、EFLAGSレジスタのbit18、AC(Alignment Check)ビットを1にしてやれば良い。

gccのインラインアセンブラの例。

__asm__ __volatile__ (
"pushf\n"
"\tpopl %%eax\n"
"\tor $0x00040000, %%eax\n"
"\tpushl %%eax\n"
"\tpopf\n"
:::"%eax");


これが何の役に立つかと言うと、
・misalignアクセスで例外が発生するプロセッサ上で動作させるコードを作成したい。
・でも、そのテストはx86(Linuxなど)上で行いたい。
という場合。

参考:
X86 Assembly/X86 Architecture
Mis-aligned pointers on x86
GCC Inline Assembler


09/12/11 追記:
この機能、実はあまり使えないような気がしてきた。
なぜなら、glibcはACが0である事を期待したコードになっていて、ACを1にするとglibcの関数で落ちるから。

2009-12-06

pmccabe - プログラムの複雑度を測定する

pmccabe
CやC++のプログラムの循環的複雑度を計測してくれるツール。

改行コードがCRLFのファイルにpmccabeをかけると、以下のようなエラーがでる。

"a.c", line 187: too many }'s

これを防ぐには、pmccabeに通す前にソースコードの改行コードをLFに書き換えるか、pmccabe自体を以下の様に変更すれば良い。

--- pmccabe.h.orig 2009-12-06 23:36:37.000000000 +0900
+++ pmccabe.h 2009-12-06 23:26:15.000000000 +0900
@@ -133,7 +133,7 @@
stats_t *stats_pop(stats_t *sp);
void stats_accumulate(stats_t *sp);

-#define ISSPACE(c) ((c) == T_NCNULINE || (c) == '\n' \
+#define ISSPACE(c) ((c) == T_NCNULINE || (c) == '\n' || (c) == '\r'\
|| (c) == '\t' || (c) == ' ')

#define ISIDENT1(c) (((c) >= 'a' && (c) <= 'z') \

2009-12-04

Cで、constなポインタからconstをはずす方法

constなポインタからconstをはずすキャストをすると、コンパイラのwarningが出る事がある。

const void *p;
void *p2;

p = xxx;
p2 = (void*)p; ←ココ。

明示的にキャストしているから問題ないような気もするのだが、warningは出てしまう。

一度longにキャストするという手もある。

p2 = (void*)(long)p;

しかしこれだと、「ポインタのサイズとlongのサイズは同じなのか?」問題があり、スッキリしない。

以下のようにunionを使うと、コンパイラをだまらせる事ができ、しかもスッキリと問題が解決する。

union {
void *vp;
const void *cvp;
} u;

u.cvp = p;
p2 = u.vp;

2009-11-18

様々な周波数のbeep音を出す2

ioctlによる方法だと特別な権限が必要になってしまう。特別な権限無しにbeep音をだしたかったので、PortAudioを使うことにした。

テスト用のファイルのうち"sin"とつくものを適当に改造すれば、任意の周波数のbeep音を出すことができる。

様々な周波数のbeep音を出す。

ioctl(fd, KIOCSOUND, val);で、様々な周波数のbeep音を出すことができる。要root権限。

#define CLOCK_TICK_RATE 1193180

int main(int argc, char *argv[])
{
int fd;
int ret;
int freq;
int val;

fd = open("/dev/console", O_WRONLY);
if (fd == -1) {
perror("open");
exit(1);
if (argc > 1) {
freq = atoi(argv[1]);
val = (int)(CLOCK_TICK_RATE/freq);
} else {
val = 0;
}

ret = ioctl(fd, KIOCSOUND, val);
if (ret < 0) {
perror("ioctl");
exit(1);
}
return 0;
}

2009-08-13

ITRONのランデブ機能を使って、mallocで確保したメモリを受け渡してはいけない

ITRONには、ランデブという機能がある。
簡単に説明すると、タスクAからタスクBにメッセージを送り、BからAにその返答を返すことができる機能。

ぱっと見便利な機能なのだが、タスクAからタスクBにメッセージを渡すときに、malloc()等で動的に確保したメモリのポインタを渡すことができないというワナがある。
このワナを知らないと、メモリリークを起こす。

以下、詳細。

タスクAからメッセージを送るときには、cal_por()またはtcal_por()というAPIを使う。

tcal_por(portid, 0x1, msg, msgsize, timeout);

これでタスクAは、タスクBにmsgを送ることができる。
タスクB側は、以下のようなコードになる。

acp_por(portid, 0x1, &retno, buf); /* tcal_porされるのを待つ */
...
rpl_rdv(retno, retmsg, sizeof(retmsg)); /* タスクAに返答を返す */

タスクBがrpl_rdv()を呼ぶと、タスクAのtcal_por()がリターンして、タスクAはタスクBから返答を受け取ることができる。(返答は、tcal_por()の第3引数msgの指すアドレスに上書きされる)

ここで問題となるのは、tcal_por()がタイムアウトした場合。
tcal_por()は、タスクBから一定時間内にrpl_rdv()が呼ばれなかった場合にタイムアウトする。タスクBがacp_por()でメッセージを受け取ったかどうかは関係が無い。
すなわちタスクAは、タスクBがacp_por()でAからのメッセージを受け取ったかどうかを判断することができない。

以下のように、malloc()で動的に確保したメモリをメッセージに含めて、tcal_por()に渡したとする。

msg.pointer = malloc(msgsize);
tcal_por(portid, 0x1, msg, msgsize, timeout);

tcal_por()がタイムアウトした場合、free()は一体誰が呼べば良いだろうか?
メッセージはタスクBに伝わっていればBがfree()し、伝わっていなければAがfree()すべきである。
しかし、タスクAは、メッセージが伝わっているのか否かを判断することができないのである。

---

タイトルがちょっと不正確なので、正しく書き直すと、
「ITRON4準拠のランデブ機能を使うときに、mallocで確保したアドレスを含むメッセージをtcal_por()に渡してはいけない。」