-xs を用いずにデバッグシンボルを組み込むのを抑制する2013年08月04日 11時20分32秒

動作するプログラムを書くことを出来る人は大勢いるが、正しくコンパイルする事、効率的にコンパイル、リンクをする事が出来る人はほとんどいない。大部分の人達が、自分達が毎日、自分で何を行なっているかを把握していないのだ。知らず知らずのうちに、無駄を毎日の様に繰り返していることもしばしば。少しの変更で、大きな無駄を省けることも多々ある。一人で一割、二割の時間を有効に使えるようになるとしよう。それが、百人に及んだら事は重大だ。

Solaris には -xs というコンパイルオプションがある。これによりオブジェクトファイルなしに dbx でデバッグできるようになる。これを用いると、各々のオブジェクトファイルに入っているデバッグ情報が、実行ファイルへ全てコピーされるのだ。これが、リンクオプションで無いのは若干の不思議。

さて、このオプションを有効にするには -xs を指定する必要がある。これは、コードや中間生成物を共に移さない、本番系に有効なオプションだ。

つまり、このオプションを利用しないと、プログラムのサイズが大幅に小さくなる。元の一割やそれ以下の大きさにまでなってしまった等というのは日常茶飯事だ。

これを省くと、プログラムのリンクの時間が大幅に減る。デバッガの起動と再読み込みの時間も大きく減る。そして、ディスクの圧迫も回避となる。

もちろん、これを使わない不利益は、オブジェクトファイルが必要なこと。何かあった時に心配との事で、常に指定している組織も多い。

逆にいえば、一般的な開発環境では必要無い。そして、本番環境にオブジェクトファイルが無くても、core ファイルを持ってくれば、しっかりとデバッグが出来る。つまり、コードとオブジェクトファイルとしっかりと保持する運用体勢を確立していれば、用いなくても問題はない。または、本番用にのみ指定する運用でもいい。

成果物からは、弘法筆を選ばずの世界なのだが、その過程には、過ぎたるは猶及ばざるが如し、そして、労多くして功少なしの世界でもあるのだ。

なお、-xs は -xdebugformat=stabs を指定し、stabs 形式のデバッグ情報を用いる時に有効になる。-xdebugformat=dwarf で古い型の dwarf 形式を用いている場合には無意味だ。

AIX はある筋の情報によると stabs で、実行ファイル外にデバッグ情報を置くことは出来るが、dbx がオブジェクトファイルからその情報を読むことがまだ出来ない為、似たようなオプションは無いそうだ。

テンプレートの定義の位置を抑制2013年04月16日 11時41分25秒

-template=no%extdef は Solaris C++ のコンパイルオプション。

テンプレートは C++ の型を指定しない一般型のコードを書く時に用いる。データの大きさなどは、コンパイル時に自動で生成され、各々に適した形になるわけだ。

そのため、多くのコンパイラでは、ヘッダファイルに実装も書く必要がある。各々のファイルをコンパイルするときに、その実装も既に見えていないと、型ごとのコードに変換できないからだ。

Solaris の C++ コンパイラは、テンプレートの宣言があるヘッダーファイルと同じ実装ファイルを探す仕様になっている。例えば、myfunc.h にテンプレート関数の宣言があると、myfunc.cpp や myfunc.cxx、myfunc.CC などのファイルを探し、そこから実装を引っ張ってくる。

多くの場合この機能は便利なのだが、問題もある。例えば、複数の cpp ファイルを同時に開いてしまうので、同じ名前の静的変数が違うファイルにあった時に、コンパイラがエラーを起こす。旧来の C++ の静的変数のスコープは各々の cpp ファイル内だったが、テンプレートの所為で他の cpp ファイルも同時にコンパイルされてしまうからだ。

そんな時に、この動作を抑制するのが -template=no%extdef。これを指定するとヘッダーファイルと各々の cpp ファイルからテンプレートの実装を探すので、その様な問題を回避できる。

違うアーキテクチャへの移植性を考えると常に指定しておいた方が、後々楽になる。

sh -c へ引数を渡す2012年08月28日 15時14分44秒

sh -c を用いてファイルに書かずに、シェルスクリプトを実行出来る。Makefile などで、わざわざ別にファイルを作る程でもない時、csh 系のシェルを使っていてループを使いたい時など、幾つかの場面がある。

そのスクリプトに更に引数を渡すことも出来る。

ただ、厄介なのがその引数の扱われ方。sh -c でコマンドを渡した後に残ったものが、引数として扱われる。そして、面白いことに、一つ目の引数は $0 になり、それに順次続くといった動作になっている。


Solaris % sh -c 'echo $1' one two three
two
AIX % sh -c 'echo $1' one two three
two
FreeBSD % sh -c 'echo $1' one two three
two
linux% sh -c 'echo $1' one two three
two

通常のシェルスクリプト内では、$0 はプログラム自体で、$1 は一つ目の引数になる。sh -c の $1 は二つ目の引数だ。

あちこちのホストで試してみた。長いこと知らなかった。

mkfifo のあまり書かれていない特徴2012年08月17日 15時32分21秒

mkfifo は UNIX のシステムコールであり、UNIX コマンドの一つでもある。FIFO は First-In First-Out で先入れ先出し。データ構造などでは Queue、キューと呼ばれる。UNIX 上では名前付き pipe などとも言われる。

mkfifo でファイルを作成。tail で読み出す。


term1 % mkfifo /tmp/fifo
term1 % tail -f /tmp/fifo

もう一つのターミナルで入力する。

term2 % echo 1 > /tmp/fifo
term2 % echo 2 > /tmp/fifo

そうすると先程の tail が出力をだす。

term1 % mkfifo /tmp/fifo
term1 % tail -f /tmp/fifo
1
2
^C

fifo には幾つかの特徴がある。

まず、open が書き込み側と読み出し側が open を呼ぶまでブロックされる。echo と tail ではなく、両方で cat を使うとその動作が良く解る。読み出し cat を先に起動しても、書き込み cat が始まるまで、待ち続ける。逆もまた然り。

そして、fifo は何度でも使い直す事ができる。cat の実験は同じfifo を用いて何度でもできる。

また、出力側が close をすると、read は 0 を返すことになる。そのため、cat はすぐに終了。select 等や再 open 等で、読み出しの状態を調べる tail -f は、読み出し続ける事になる。

最後に、書き込み側も読み出し側も複数が行なえる。複数のプロセスでの読み出しは、各入力は一つのプロセスにしか渡らない。しかし、close は全てに伝達されてしまうので、一気に全てのプロセスの read が 0 を返す。ほとんど、使い様の無い形だろう。複数の書き込みプロセスが各々書き込んで、一つのプロセスで処理は利用できる。書き込んだ順番と内容は保護されるが、内容は行単位で独立していないと処理しづらい。

この性質を利用して、大量の圧縮ファイルを復元せずに、同時に伸長しつつ、全てを一気にに grep 等といった使い方ができる。


% sh -c 'for i in /var/log/messages*bz2; do bzip2 -d -c $i > /tmp/fifo & done'
% grep da /tmp/fifo
Aug 17 01:09:25 kernel: da0 at umass-sim0 bus 0 scbus0 target 0 lun 0
Aug 17 01:09:25 kernel: da0: <IC25N040 ATCS05-0 0811> Fixed Direct Access SCSI-0 device 
Aug 17 01:09:25 kernel: da0: 40.000MB/s transfers
Aug 17 01:09:25 kernel: da0: 38154MB (78140160 512 byte sectors: 255H 63S/T 4864C)

まあ、伸長したので正しく改行が行なわれているとは限らないが、あくまで使い方の一例として。

open でモードを設定し忘れたら、読み書きできなかった2012年05月14日 11時50分52秒

ちょっとした失敗。open システムコールを使って、ファイルを作成したが、パーミッションを指定するのを忘れていた。そうしたら、--S になっていて、読み書きができない。

open システムコールは、引数が可変で、二つ又は三つの引数を取る。三つ目の引数は、ファイルを作成したときのパーミッションで、二つ目の引数に O_CREAT が入っている時に有効になる。


% cat open.c 
#include <fcntl.h>

int main(int argc, char* argv[])
{
    open(argv[1], O_CREAT | O_WRONLY | O_TRUNC);
}

ファイルが存在しない時の open は、ファイルを作成して、ファイルディスクリプタを返す。書き込み様に開いているので、最初に実験したときは、問題なく書き出せるのだ。

しかし、落とし穴は二回目と読み込み時にある。--S のパーミッションになってしまい、自分で読み書きできない。最初に動かした時は、書いた内容。内容どころか書かれているのをファイルの大きさを見て点検しただけだったので、アクセス権限は全く見逃していた。

そんなこんなで、ファイルの読み書きが失敗しているのに気が付いてみたら、open の仕業。


% cat open.c 
#include <fcntl.h>

int main(int argc, char* argv[])
{
    mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    open(argv[1], O_CREAT | O_WRONLY | O_TRUNC, mode);
}

これで、自分は読み書き。そして、それ以外は読める。

大量のファイルを同時に圧縮する2012年03月25日 11時43分36秒

時折、大量のファイルを圧縮することがある。一つずつのファイルの大きさは、数 MB から数 GB まで、多種多様。そんなファイルが数百にも上る。Solaris など、特にコア数が多いホスト上の時は、手っ取り早く並列処理したい。CPU リソースは余っている事が多いので、100 以上あるコアのうち、20、30 ぐらい使ったところで気がつく人はほとんどおらず、苦情も来ない。Solaris ほどコアの数があると、CPU を全てしっかり使い切るのは結構難しい。とは言ったものの nice 20 で処理しても、流石に一人で全ての CPU を 100% 使うのは気が引ける。

実は、大きさの異なる仕事を並列的に処理するスクリプトを書くのはとても難しい。シェルスクリプトを筆頭に、特定の数だけ並列化するライブラリがない。そうなると、自分で子プロセスを生成し、管理することになる。それに、各々の仕事量が異なっているので、仕事の割り振りも工夫が必要になる。一つだけ、大きい仕事ばかり抱えて、他のプロセスが直ぐに終わってしまったのでは、効果が下がる。そう考えていくと、ささっと書いて使い捨てられるほど、簡単にはいかない。

いくつかそのようなスクリプトの尻拭いをしなければいけないことがあったが、どれも醜いものだった。コード数が不必要に長い。余計な処理ばかり増えて、本題の作業効率が余り上がっていない。負荷が偏っていて、無駄が多い。どれも一生懸命、書いたのは見て取れるのだが、直す人の気苦労などまったくわかっちゃいない。

実は、その様に百行から数百行にわたる、たった一行のプログラムで並列処理の仕事配分をしてくれるプログラムがある。その名も make。make だけだとちょっと扱いづらいので awk も当てる。

残念ながら、make にコマンドラインから直接 make ルールを打ち込む人たち等は見たことがない。make をシェルスクリプトの様に、手順を追って書く人たちや、make に依存関係の処理を任せれば簡単なのに、スクリプトで難解な発明をし、四苦八苦している人たちはよく見かけるが。

さて、愚痴と能書きが多くなったがさて本題。make での古いやつだと並列処理が出来ないものもあるので、GNU make や BSD make など若干新しいものが必要になる。


% cat bzip20
#!/bin/sh

exec $* | \
  nawk 'BEGIN{ print "ALL:" }
    { print $1 ".bz2 : " $1;
    print "\tbzip2 " $1;
    print "ALL : " $1 ".bz2" }' | \
  gmake -j 20 -f -
% bzip20 "ls *.txt"
% bzip20 "find . -name '*.log'"

exec を通して、ファイルを探し、それらのファイルを圧縮するターゲットを作る。ALL を一番最初にしているので、全てが圧縮される。exec を通しているので、圧縮順を気にせず適当に渡したり、逆に大きさ順に並べて、極力処理が偏らないように出来る。

動的ライブラリのパスを指定する環境変数2012年02月18日 14時35分50秒

UNIX の動的ライブラリがある場所を指定する環境変数の名前はシステムによって色々と違う。中には同じものを用いるシステムもある。さて、わざわざシステムに依って変数の条件分けをしなくていいと喜ぶべきなのか、システム固有値のみを設定すべきなのか。中途半端な違いが悩みの種だ。

LD_LIBRARY_PATH 派

  • Solaris
  • FreeBSD
  • NetBSD
  • OpenBSD
  • Linux

LIBPATH 派

  • AIX

SHLIB_PATH 派

  • HP-UX

DYLD_LIBRARY_PATH 派

  • MacOS X

コンパイラオプションの対比表2012年02月15日 20時35分35秒

C/C++ コンパイラの各種オプションのアーキテクチャ毎の対比が C/C++ Comparison tables に良くまとめられている。サポートをするアーキテクチャが増えてくると、個々のコンパイラのオプション時折こんがらがるので、便利に使っている。

Solaris の elf_find_sym で動的ロードで2012年01月07日 12時44分07秒

Solaris で elf_find_sym が SIGSEGV を起こして、プログラムが止まってしまう。何と古来からある標準 C ライブラリ関数を呼び出そうとしている時に、プログラムが落ちる。全ての環境で起きるわけでもなく、特定のバイナリのみで起きる。取り敢えず、見つけたことをまとめてみた。今回の場合は、strtok を呼び出す瞬間に落ちるので、かなり厄介だ。

Solaris ではバイナリの実行中に呼び出す関数がまだ組み込まれていなければ、 elf_find_sym を通して関数をロードして行く。libc 等は、共有ライブラリとして、リンクされているので典型的な例だ。nm で実行ファイルを調べると、strtok を参照しているのが分る。ldd では、libc を必要としているのが判る。

elf_find_sym 関数内で落ちるので、デバッガでブレイクポイントを仕掛けて、幾つも観察しているとやはり、プログラムの実行を初めてから最初の関数呼び出しのときに、呼び出され共有ライブラリから関数を読みだしているのが確認できた。つまり、プログラム起動時に、全てのシンボルを読み込んでおくのではなく、必要になるたびに、随時読み込んでいく遅延方式になる。

コンパイル時には、何のエラーもない。また、リンク時にも問題はない。実行時に関数を呼び出そうとしている時に問題があるので、ldd などの動的ロード関連かとは思うのだが、調べても有効なものが見付からず困っていた。Solaris のバグだとは思うのだが、確証が掴めず取り敢えずのところは別の実装を静的に組み込むことで回避した。三ヵ月ぐらい放置したところで、再度試したところ再現しなくなっていたのだが…。

Solaris dbx の説明書2012年01月06日 13時08分22秒

dbx の説明書のdbx コマンドによるデバッグが入手可能だ。

コマンドを忘れるのが滅法に早いのと、データの表示には優れた他のデバッガがあるので、最近は dbx の利用が減っていた。もう一つのデバッガは、データを見るのは楽なのだが、それ以外の操作が出来なかったり、厄介な部分がある。そんな時に dbx の出番なのだ。

忘れたコマンドで適当に検索をしていたら、偶然見つけた。