プロジェクトごと(特定ディレクトリ以下ごと)に .vimrc を切り替えて使う方法

複数のプロジェクトに参加している場合に、プロジェクトごとに vim の設定(.vimrc など)を自動で切り替える方法を direnvvim 用の環境変数 VIMINIT で実現できたのでメモしておく

前提

  • 通常とは異なる vim 設定を読み込みたいプロジェクトのファイルは '~/workspace/project_aaa/' 以下に存在すると仮定する
  • 通常とは異なる vim 設定の設定ファイル配置場所は '~/workspace/project_aaa/__vimrc' と仮定する

設定手順

  • vim 関連の設定ファイルをコピー
$ mkdir ~/workspace/project_aaa/__vimrc/
$ cp -p ~/.vimrc ~/workspace/project_aaa/__vimrc/
$ cp -rp ~/.vim/ ~/workspace/project_aaa/__vimrc/
  • vim の runtimepath を ~/workspace/project_aaa/__vimrc/.vimrc へ設定(先頭に追加)
" vim default runtimepath
set runtimepath=~/workspace/project_aaa/__vimrc/.vim,/usr/share/vim/vimfiles,/usr/share/vim/vim82,/usr/share/vim/vimfiles/after,~/workspace/project_aaa/__vimrc/.vim/after

既存の runtimepath が分からなければ、vim 内で「:echo &rtp」によって現在の runtimepath を確認可能なので、参考にしつつ必要な箇所を __vimrc のディレクトリへ変更する

  • direnv のインストール

以下手順に従ってインストール

https://direnv.net/docs/installation.html

  • direnv の初期設定

以下手順に従って設定

https://direnv.net/docs/hook.html

  • vim の設定を変更したい特定ディレクトリ直下(今回は ~/workspace/project_aaa/)に以下 .envrc を作成

~/workspace/project_aaa/.envrc に以下内容のファイルを作成

export VIMINIT='source $MYVIMRC'
export MYVIMRC='${HOME}/workspace/project_aaa/__vimrc/.vimrc'
  • 以下コマンドでこのディレクトリにおける direnv を有効化する
$ cd ~/workspace/project_aaa/
$ direnv allow .

以上の設定で、'~/workspace/project_aaa/' 以下に移動した場合に自動で '~/workspace/project_aaa/__vimrc/.vimrc' を .vimrc として読み込むようになる

$ cd ~/workspace/project_aaa/path/to/somedir

上記のように cd すると、下記のように設定が有効になる

direnv: loading ~/workspace/project_aaa/.envrc
direnv: export +MYVIMRC +VIMINIT

これで '~/workspace/project_aaa/__vimrc/.vimrc' を編集すれば、'~/workspace/project_aaa/' 以下でのみ設定が有効になる

この設定の欠点

  • 外部ディレクトリで起動した vim から '~/workspace/project_aaa/' 内のファイルを開いた場合には設定が有効にならない

別の良い方法があれば知りたい

追記

localrc.vim というのを教えてもらった
良さそう

thinca.hatenablog.com

eu-unstripでdebuginfoからデバッグ情報付きバイナリを再作成

eu-unstrip とは

rpm系では解析時によく使う debuginfo パッケージですが、時々不便なことがあります。
例えば objdump -S をしようとしてもデバッグ情報が入っているバイナリとstripされたバイナリに情報が別れてしまっているため、objdump -S で元のコード付きで逆アセンブリ表示をすることができません。
以下の例だと /usr/sbin/zabbix_server_mysql と /usr/lib/debug/usr/sbin/zabbix_server_mysql.debug に分かれてしまっています。

$ rpm -ql zabbix-server-mysql|grep zabbix_server_mysql
/usr/sbin/zabbix_server_mysql
$ rpm -ql zabbix-debuginfo|grep zabbix_server_mysql.debug
/usr/lib/debug/usr/sbin/zabbix_server_mysql.debug
$ objdump -S /usr/sbin/zabbix_server_mysql
・・・逆アセンブルされるが、元のコードは表示されない
$ objdump -S /usr/lib/debug/usr/sbin/zabbix_server_mysql.debug
・・・逆アセンブルされない

そのため、stripとは逆に2つのバイナリを1つにしてくれるコマンドは無いものか、と人に聞いてみたところ elfutils に eu-unstrip というコマンドが存在することを教えてもらいました。

ビルド

どうやらRHEL5系やその互換OSの elfutils のバージョンでは eu-unstrip は存在しないようですが、 https://fedorahosted.org/releases/e/l/elfutils/ から elfutils-0.152.tar.bz2 を持ってきてビルドしたら使用できました。

$ wget https://fedorahosted.org/releases/e/l/elfutils/0.152/elfutils-0.152.tar.bz2
$ wget https://fedorahosted.org/releases/e/l/elfutils/0.152/elfutils-robustify.patch
$ wget https://fedorahosted.org/releases/e/l/elfutils/0.152/elfutils-portability.patch
$ tar xjf elfutils-0.152.tar.bz2
$ cd elfutils-0.152
$ patch -p1 < ../elfutils-robustify.patch
$ patch -p1 < ../elfutils-portability.patch
$ ./configure
$ make

これで ./src/unstrip(パッケージだと eu-unstrip という名前になっているもの)というバイナリが作成されます。
実行時にいくつかライブラリを参照する必要があるので、elfutils-0.152 の直下に以下のような LD_LIBRARY_PATH を指定して実行してくれる unstrip.sh を作っておきましょう。

$ cat unstrip.sh
#!/bin/bash
THIS_DIR=$(dirname $0)
LD_LIBRARY_PATH=$THIS_DIR/libdw:$THIS_DIR/libelf: $THIS_DIR/src/unstrip $@

これで unstrip が実行できるようになりました。

実行してみる

$ ./elfutils-0.152/unstrip.sh --help
Usage: unstrip [OPTION...] STRIPPED-FILE DEBUG-FILE
or: unstrip [OPTION...] [MODULE...]
Combine stripped files with separate symbols and debug information.

Input selection options:
--core=COREFILE Find addresses from signatures found in COREFILE
--debuginfo-path=PATH Search path for separate debuginfo files
-e, --executable=FILE Find addresses in FILE
-k, --kernel Find addresses in the running kernel
-K, --offline-kernel[=RELEASE] Kernel with all modules
-M, --linux-process-map=FILE Find addresses in files mapped as read from
FILE in Linux /proc/PID/maps format
-p, --pid=PID Find addresses in files mapped into process PID

-f, --match-file-names Match MODULE against file names, not module names
-i, --ignore-missing Silently skip unfindable files

Output options:
-a, --all Create output for modules that have no separate
debug information
-d, --output-directory=DIRECTORY
Create multiple output files under DIRECTORY
-m, --module-names Use module rather than file names
-n, --list-only Only list module and file names, build IDs
-o, --output=FILE Place output into FILE
-R, --relocate Apply relocations to section contents in ET_REL
files

-?, --help Give this help list
--usage Give a short usage message
-V, --version Print program version

Mandatory or optional arguments to long options are also mandatory or optional
for any corresponding short options.

The first form puts the result in DEBUG-FILE if -o was not given.

MODULE arguments give file name patterns matching modules to process.
With -f these match the file name of the main (stripped) file (slashes are
never special), otherwise they match the simple module names. With no
arguments, process all modules found.

Multiple modules are written to files under OUTPUT-DIRECTORY, creating
subdirectories as needed. With -m these files have simple module names,
otherwise they have the name of the main file complete with directory
underneath OUTPUT-DIRECTORY.

With -n no files are written, but one line to standard output for each module:
START+SIZE BUILDID FILE DEBUGFILE MODULENAME
START and SIZE are hexadecimal giving the address bounds of the module.
BUILDID is hexadecimal for the build ID bits, or - if no ID is known; the
hexadecimal may be followed by @0xADDR giving the address where the ID resides
if that is known. FILE is the file name found for the module, or - if none was
found, or . if an ELF image is available but not from any named file.
DEBUGFILE is the separate debuginfo file name, or - if no debuginfo was found,
or . if FILE contains the debug information.

Report bugs to http://bugzilla.redhat.com/bugzilla/.

使い方は以下の通り。
/usr/sbin/zabbix_server_mysql と /usr/lib/debug/usr/sbin/zabbix_server_mysql.debug を元に、zabbix_server_mysql__unstrip が作成されます。
これで zabbix_server_mysql__unstrip にデバッグ情報と実行バイナリの情報がマージされたバイナリが作成されます。

$ ./elfutils-0.152/unstrip.sh /usr/sbin/zabbix_server_mysql /usr/lib/debug/usr/sbin/zabbix_server_mysql.debug -o zabbix_server_mysql__unstrip

objdump -S を実行する際にはソースコードが存在するパスを「-I」で指定して実行すれば良さそうです。(ちょっと一部がずれてるような気も。)

$ objdump -S zabbix_server_mysql__unstrip $(find /usr/src/debug/zabbix-1.8.3/ -type d|sed s/'^'/'-I'/g|tr '\n' ' ')|less

Debianでは・・・

Debian では stable(squeeze) でも elfutils パッケージに eu-unstrip が入っていたので気軽に使えました。

Zabbix設定ファイルの注意点


Zabbix構築時に、zabbix_server.conf や zabbix_proxy.conf や zabbix_agentd.conf の設定は重要なのですが、意外と日本語での情報が少ないようなのでこれらの設定の中で重要なものにしぼって解説をしたいと思います。

zabbix_server.conf

参考: http://www.zabbix.com/documentation/jp/1.8/manual/processes/zabbix_server

重要度 高 (重大な問題が発生する可能性がある)
  • CacheSize
    • 設定キャッシュサイズ(バイト単位)。ホストとアイテムデータを記憶させる共有メモリサイズ。
    • 足りないとZabbixサーバプロセスがクラッシュします。
    • デフォルト設定値は8MBですが、hostsテーブルとitemsテーブルの中身を保持するための領域なので、これらのテーブルのサイズがどの程度か注意しておくこと。
    • ただ、大きく設定しすぎるとメモリを食いすぎて、起動に時間がかかりすぎます。
  • MaxHousekeeperDelete
    • Housekeeper(ヒストリなどの保持期限が過ぎたデータを削除するプロセス)が、1回削除動作をする際に実行する、1アイテムあたりに削除する最大行数
    • デフォルトでは500行になっているため、1時間に1回Housekeeperが動作する場合、1時間に1アイテムについてヒストリが500行以上溜まるような環境ではDBサイズが単調増加していく。最終的にはディスクが枯渇する可能性があります。
    • 削除すべきものを全て削除したいならば、0 に設定することで無制限に削除してくれます。ただ、DBへの負荷は向上するかもしれません。
  • ProxyConfigFrequency
    • Zabbix Proxyをパッシブモードで使用時に、監視設定情報を更新する間隔
    • デフォルトは3600秒(1時間)。これはつまり、あるホストをProxy経由での監視に切り替えた場合、最悪1時間Proxy経由での監視がされない、ということ。しかもWebUIでProxy経由に切り替えたタイミングでサーバからも監視されなくなるため、監視情報がProxyに伝えられる(最悪1時間)まで、一切の監視がされない状態になる。
    • せめて600秒(10分)間隔くらいが適切だと思います。
    • なお、Zabbix Proxyをアクティブモードで使用時には、zabbix_proxy.confにこれに相当する設定項目があるので、同様に重要。
重要度 中 (何か問題がある場合に設定すると改善する可能性がある)
  • LogSlowQueries
    • 処理に時間が掛かっているクエリを表示してくれる
    • パフォーマンス解析時に調査が楽になるかも
  • StartDiscoverers、StartHTTPPollers、StartIPMIPollers、StartPingers、StartPollersUnreachable、StartPollers、StartTrappers
    • 各監視タイプ別の、監視用のプロセスの数を設定
    • 監視の遅延が発生している場合、これらの監視プロセスが不足している可能性があるので、これらを調整すると効果があるかも
    • ディスカバリ遅延ならばStartDiscoverers、Web監視遅延ならばStartHTTPPollers、IPMI監視遅延ならばStartIPMIPollers、ping監視遅延ならばStartPingers、どんどん監視不能ホストが増加して監視再開が追い付いていない場合にはStartPollersUnreachable、その他のほとんどの監視ならばStartPollers、アクティブチェック監視ならばStartTrappers
  • Timeout
    • 監視のタイムアウト設定
    • よくあるのがユーザパラメータでコマンド実行結果を取得する監視で、ローカルやzabbix_getで値が取得できているのに、Zabbixサーバで監視不能状態になってしまう場合。デフォルトは3秒なので、実行時に3秒以上かかるコマンドだと監視失敗になる。
    • 本来ならば3秒以内に実行できるように改善するべきだけど、他に方法がなければ30秒に変更することで監視可能になることがある。

zabbix_proxy.conf

参考: http://www.zabbix.com/documentation/jp/1.8/manual/processes/zabbix_proxy

重要度 高 (重大な問題が発生する可能性がある)
  • CacheSize
    • 設定キャッシュサイズ(バイト単位)。ホストとアイテムデータを記憶させる共有メモリサイズ。
    • 足りないとZabbix Proxyプロセスがクラッシュします。
    • デフォルト設定値は8MBですが、hostsテーブルとitemsテーブルの中で、そのプロキシに割り当てられている監視情報を保持するための領域なので、これらのテーブルのサイズがどの程度か注意しておくこと。
    • ただ、大きく設定しすぎるとメモリを食いすぎて、起動に時間がかかりすぎます。
  • ConfigFrequency
    • Zabbix Proxyをアクティブモードで使用時に、監視設定情報を更新する間隔
    • デフォルトは3600秒(1時間)。これはつまり、あるホストをProxy経由での監視に切り替えた場合、最悪1時間Proxy経由での監視がされない、ということ。しかもWebUIでProxy経由に切り替えたタイミングでサーバからも監視されなくなるため、監視情報がProxyに伝えられる(最悪1時間)まで、一切の監視がされない状態になる。
    • せめて600秒(10分)間隔くらいが適切だと思います。
    • なお、Zabbix Proxyをパッシブモードで使用時には、zabbix_server.confにこれに相当する設定項目があるので、同様に重要。
  • ProxyOfflineBuffer
    • Zabbixサーバへの接続ができない場合に、プロキシ内のDBにデータを保持する期限。
    • デフォルトだと1時間ですが、この場合1時間以上プロキシ・サーバ間のコネクションがロストすると、データが失われます。
    • 24時間貯めてもそれほど大きくはならないので、mysqlとかそこそこ良いDBを使って、容量があるならもっと増やしても良いよ思います。
重要度 中 (何か問題がある場合に設定すると改善する可能性がある)
  • LogSlowQueries
    • 処理に時間が掛かっているクエリを表示してくれる
    • パフォーマンス解析時に調査が楽になるかも
  • StartDiscoverers、StartHTTPPollers、StartIPMIPollers、StartPingers、StartPollersUnreachable、StartPollers、StartTrappers
    • 各監視タイプ別の、監視用のプロセスの数を設定
    • 監視の遅延が発生している場合、これらの監視プロセスが不足している可能性があるので、これらを調整すると効果があるかも
    • ディスカバリ遅延ならばStartDiscoverers、Web監視遅延ならばStartHTTPPollers、IPMI監視遅延ならばStartIPMIPollers、ping監視遅延ならばStartPingers、どんどん監視不能ホストが増加して監視再開が追い付いていない場合にはStartPollersUnreachable、その他のほとんどの監視ならばStartPollers、アクティブチェック監視ならばStartTrappers
  • Timeout
    • 監視のタイムアウト設定
    • よくあるのがユーザパラメータでコマンド実行結果を取得する監視で、ローカルやzabbix_getで値が取得できているのに、Zabbixサーバで監視不能状態になってしまう場合。デフォルトは3秒なので、実行時に3秒以上かかるコマンドだと監視失敗になる。
    • 本来ならば3秒以内に実行できるように改善するべきだけど、他に方法がなければ30秒に変更することで監視可能になることがある。

zabbix_agentd.conf

参考(Un*x): http://www.zabbix.com/documentation/jp/1.8/manual/processes/zabbix_agentd
参考(Windows): http://www.zabbix.com/documentation/jp/1.8/manual/processes/zabbix_agentd_win

重要度 高 (重大な問題が発生する可能性がある)
  • MaxLinesPerSecond
    • 監視可能な1秒あたりの最大ログ行数を設定する。それ以上のログが出力された場合はログが送信されない。(訂正: コメントで指摘もらいましたが、次回監視時にログ送信が分割されるので、最終的にすべてのログは送信されます。失礼しました)
    • さらに、送信すべきログ行がMaxLinesPerSecondを越えるだけでなく、送信しない行もふくめてMaxLinesPerSecond x 4を越えた場合もこの制限に掛かる。
    • 内部で実際に使用されている計算式は次の通り
      • 「今回監視時の送信対象のログ行数 / アイテムの監視間隔(秒)」がMaxLinesPerSecondを越えると条件に掛かります
      • 「今回監視時の増加ログ行数 / アイテムの監視間隔(秒)」がMaxLinesPerSecond * 4を越えると条件に掛かります。「今回監視時の増加ログ行数」は送信対象のログ行だけでなく、送信対象外のログ行も加算したものになります。
    • デフォルトは100だけど、大丈夫ですか?
重要度 中 (何か問題がある場合に設定すると改善する可能性がある)
  • Hostname
    • ホスト名を入力する
    • ZabbixのWebUIに登録しておく名前と一致させておかないと、アクティブチェックが正常に動作しません。特にログ監視
    • 大文字・小文字を区別するので、気をつけること
  • EnableRemoteCommands
    • リモートコマンドを利用する場合にチェックしておくこと
    • system.run[]やユーザパラメータを使用するならチェックしておくこと
  • RefreshActiveChecks
    • アクティブチェックの監視情報を取得する間隔
    • ログ監視などのアクティブチェックの項目は、設定後、有効になるまで最低限この間隔だけ待つ必要がある
  • StartAgents
    • 監視エージェントのプロセス数
    • ある特定のエージェントで多数の項目を監視していて、そのホストでのみ監視遅延が発生している場合、この数を増加することで改善する場合がある
  • UnsafeUserParameters
    • あまり安全でない文字をユーザパラメータで使用することを許可するオプション。
    • ユーザパラメータで「\」などを使用する場合にこれをチェックしておかないと実行できない。Windowsの場合は比較的、これに引っかかりやすい。
  • Timeout
    • 監視のタイムアウト設定
    • よくあるのがユーザパラメータでコマンド実行結果を取得する監視で、zabbix_getでも値が取得できない場合はこれに引っかかっている可能性がある。デフォルトは3秒。
    • 本来ならば3秒以内に実行できるように改善するべきだけど、他に方法がなければ30秒に変更することで監視可能になることがある。
    • アクティブチェック監視の値をサーバへ送信する際のタイムアウト値にもなっている
    • DBが重い場合、実際にデータ送信できていてもタイムアウトになって結果的に2重にデータ送信してしまうかもしれない。根本的な解決はDBを軽くすべき。

bash を使って zabbix_get.sh を作ってみる


zabbix_get と同等のものを bash とその他一般的なコマンドラインツールだけを使って作ってみました。
きっかけは、@ さんからの RT で、 bash では /dev/tcp を使うとネットワーク通信ができるということを知ったので、試しに作ってみました。

zabbix_get のプロトコル解説

まず、zabbix_get を自作するにあたり、zabbix_get のプロトコルを解説します。
Zabbix agent の動作しているマシンの 10050 ポートに監視キーをそのまま文字列で送りつける。
以上。
ヘッダも何もありません。
楽です。(追記: その後よく見たところ、これは旧版のプロトコルのようです。ただ、最新のZabbix1.8.3でも互換性のために、server側はこの形式でデータを送信していました。zabbix_getではまた別の新版のプロトコルでした。)


返ってくるデータにはヘッダがあります。ヘッダ部分は、Zabbix Sender のプロトコル解説の P.7 と同じ構造になっています。
データ部分は、Zabbix Sender とは異なり、JSONではなく、じかに監視結果が入っているだけになります。

例えば、'system.cpu.load[all,avg1]'を送ると、以下のようなデータが返ってきます。
16進数で表記していますが、ASCIIで表示できるところは()で表記しています。
以下のデータでは、監視結果として「1.700000」が返ってきていることがわかります。

送信データ
data (key)
73(s) 79(y) 73(s) 74(t) 65(e) 6D(m) 2E(.) 63(c) 70(p) 75(u) 2E(.) 6C(l) 6F(o) 61(a) 64(d) 5B([) 61(a) 6C(l) 6C(l) 2C(,) 61(a) 76(v) 67(g) 31(1) 5D(])
受信データ
header protocol
version
data length (uint64 LE) data (result)
5a(Z) 42(B) 58(X) 44(D) 01 08 00 00 00 00 00 00 00 31(1) 2e(.) 37(7) 30(0) 30(0) 30(0) 30(0) 30(0)

コード

制限がほとんど無い、DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE にしておきました。
どうせネタだしね。。。
https://github.com/BlueSkyDetector/code-snippet/tree/master/bash-zabbix-tools


ポイントは以下の部分になります。
ファイルディスクリプタ3にZabbixエージェントのポートを割り当てて、監視キーを流し込んで、それで返ってきた結果をRETに入れてます。
$RETに代入するところで od とかを使っているのは、バイト列をテキストの16進数表示に変換しているためです。

...snip
exec 3<>/dev/tcp/${server}/${port}
echo -n "${key}" >&3
RET=$(cat <&3 | od -v -An -tx1 | while read line; do echo -n "$line "; done)
...snip

それ以降は、get_in_hex_text_le や get_in_text といった関数を使って、$RETから任意の箇所のデータをバイト列やテキスト変換してから処理しています。
今回 bash でバイナリを扱うにあたり、バイナリをテキストの16進数表示に変換して保存しておくことがコツなのではないか、と少しわかった気がします

使用方法

zabbix_get.sh をそのまま実行すると使用法が表示されます。

$ zabbix_get.sh
usage: zabbix_get.sh -s <host name or IP> [-p <port>] -k <key>

Options:
  -s <host name or IP>          Specify host name or IP address of a host.
  -p <port number>              Specify port number of agent running on the host. Default is 10050.
  -k <key of metric>            Specify metric name (key) we want to retrieve.
  -v                            Verbose mode.

Example: zabbix_get.sh -s 127.0.0.1 -p 10050 -k "system.cpu.load[all,avg1]"


実際に実行してみます。

$ zabbix_get.sh -s 192.168.0.16 -p 10050 -k "system.cpu.load[all,avg1]"
1.490000


vorboseモードも作ってみました。
一応ヘッダの文字列やデータの長さも取得できるようにしています。

$ zabbix_get.sh -s 192.168.0.16 -p 10050 -k "system.cpu.load[all,avg1]" -v
raw data: 5a 42 58 44 01 08 00 00 00 00 00 00 00 31 2e 37 30 30 30 30 30 
header: "ZBXD" ...ok
version: "0x01" ...ok
length: 8
data: 1.700000
1.700000

zabbix_top.sh

zabbix_get.sh と同じ内容のコードを使って zabbix_top.sh も作ってみました。
zabbix_top.sh の表示内容は @ さんの zabtop を参考にしてみました。
が、やっているうちにやる気が衰えたために、zabtop のような%の計算とかはできてません。

$ zabbix_top.sh 
usage: zabbix_top.sh -s <host name or IP list> [-r <sec>] [-d]

Options:
  -s <host name or IP>          Specify host name or IP address list of a hosts. Such as "host1 host2 host3"
  -r <sec>                      Refresh rate.
  -d                            Debug mode.

Example: zabbix_top.sh -s "host1 host2 host3" -r 1 -d
$ zabbix_top.sh -s '192.168.0.16 192.168.0.166'
server name:    load_avg1       load_avg5       load_avg15      mem_avail       mem_total       swap_used       cpu_user        cpu_system      cpu_idle        cpu_wait        cpu_nice
192.168.0.16 :  1.870000        1.660000        1.680000        399609856       1046474752      11.331122       9.697584        4.223686        55.735766       29.278594       0.625106
192.168.0.166 : 3.540000        3.350000        3.500000        277200896       2078892032      41.327874       7.055448        6.616592        85.981939       0.151912        0.118153

assert(0)をフックしてcore dumpを出力する


assert(0)をフックしてcore dumpを出力するコードを試しに書いてみました。
http://github.com/BlueSkyDetector/code-snippet/tree/master/assert_coredump

ビルド方法

先にGoogle CoreDumperをインストールしておくこと。
http://code.google.com/p/google-coredumper/

その後、githubから取得したコードから、以下コマンドで assert_coredump.so を作成する。

$ gcc -fPIC -O0 -shared -g -o assert_coredump.so assert_coredump.c -ldl

以上。

使用方法

例えば、以下のようなC言語のコード hello_main.c があるとします。

#include <stdio.h>
#include <assert.h>

int main(){
        puts("hello world");
        assert(0);
}


これをコンパイルして実行すると通常は以下のような出力になります。

$ ./hello_main
hello world
hello_main: hello_main.c:6: main: Assertion `0' failed.
Aborted


ここで、上記でビルドした assert_coredump.so を LD_PRELOAD でプリロードしてやると、この hello_main のバイナリを変更せずに、assert(0) が発行されている時点で core dump を取得できます。

$ LD_PRELOAD=./assert_coredump.so ./hello_main
hello world
#########################################
     Generating CoreDump at assert()!!
#########################################
hello_main: hello_main.c:6: main: Assertion `0' failed.
Aborted

$ ls assert_*.core
assert_9699.core


注意点として、core dumpする際に Google CoreDumper を利用しているので、事前にインストールしておく必要があります。
gcore と同等のことができる関数があれば、そちらの方がよかったのですが、方法が見つからなかったのでこれに落ち着きました。

RedHat系Linuxでvimを使用したphpデバッグ環境構築

Xdebug使うとvimEclipse経由でステップ実行できるので、便利です。
今回はvimでその環境を構築する手順について書いていきます。


Xdebug のインストール

1. 事前準備
vim plugin のダウンロード
http://www.vim.org/scripts/script.php?script_id=1152


※ インストールしたいマシンがインターネットに接続できないときのみ、あらかじめダウンロードしておく
xdebug のダウンロード
# pecl download xdebug
# scp debugger.tgz xdebug-2.0.4.tgz root@xxx:


2. インストールしたいマシンにログイン
php-config phpize がインストールされていなければ、php-devel をインストールする
# rpm -ivh php-devel


3. vim plugin のインストール
# mkdir .vim
# tar zxvf debugger.tgz -C .vim/


4. xdebug のインストール
# pecl install xdebug
※ インターネットに接続できないときは手動コンパイル
# tar zxvf xdebug-2.0.4.tgz
# cd xdebug-2.0.4
# phpize
# ./configure --enable-xdebug
# make
# make install

x86_64 でコンパイルに失敗したら
/usr/share/pear/pearcmd.php に以下を追加する
@ini_set('memory_limit', '16M');


5. php.ini の設定
# vi /etc/php.ini
zend_extension = /usr/lib/php/modules/xdebug.so
xdebug.remote_autostart=On
xdebug.remote_enable = On
xdebug.remote_handler = dbgp
xdebug.remote_host = 127.0.0.1
# service httpd restart
# php -m
[Zend Modules]
Xdebug

vimXdebug を利用する手順

※ 先に xdebug のインストールを実施しておく
webサーバ側でvimを開く。
vim 実行中に、 を押し、5 秒以内に任意の php ファイルをweb経由で開くとデバッグ状態になる。
デバッグ終了は


その他キーは以下参照。


: resizing windows
: step into
: step over
: step out

: stop debugging

: shows all variables
: shows variable on current cursor

,e : evalute expression and display result. cursor is automatically move to watch window. type line and just press enter.

command line command)

Bp
toggle breakpoint on current line
Up
goto upper level of stack
Dn
goto lower level of stack


もっとも、phpのdebugなんてほとんど(やりません|やりたくないです)が。

gcore_with_breakpoint 作りました

gcore_with_breakpointって何?

  • プログラムを止めずに core dump が取れる!!
  • break point を設定して、そのポイントでの core dump が取れる!!
  • break point のアドレスを控えておけば、debuginfo の無い環境でも core dump が取れる!!



  • なので、ユーザ環境への変更を最低限に抑えつつ、core dump を取得可能に!!

usage: gcore_with_breakpoint [-o filename] [-b breakpoint] [-n prog_name | pid]

GitHub - BlueSkyDetector/gcore_with_breakpoint: Extend gcore script to enable to set breakpoint in command line.


もともと gdb に備わっている機能を簡単に呼び出せるようにしているだけですが、結構便利です。
デバッグ対象のプロセスを極力停止させずに、任意のブレークポイントでの core dump を取得するために、gdb パッケージに付いてきた gcore スクリプトをベースに作成しました。
他ノードと通信しながら動作するような、止めると再度通常の状態まで戻るのに時間がかかるようなサーバプロセスをデバッグしたい時に、結構役立つのではないでしょうか。
デバッグの度に動作が止まるのも嫌ですし。

なお、そもそも gcore って何?ってことについてはこちらを参照してください。

簡単に書くと、デバッグ対象のプロセスを極力停止させずに core dump を取得することができるコマンドです。


元々は手元でオリジナルのgcoreブレークポイントだけを追加してその場しのぎで使っていたのですが、使っているうちにだんだんと書き換わっていって、そこそこ使えるようになったので公開することにしました。


gcore_with_breakpoint は gcore に以下3機能を追加しました。

  • core を取得するためのブレークポイントを「-b」で設定できる(ブレークしそうにない場合にはCtrl+Cで止められます)
  • プロセスidの代わりに「-n」でプログラム名を指定すると、裏でプロセスidを引いてくれる
  • 複数のプロセスに対して同時にgcoreを実行できる


ただ、メインの目的は

です。
「-b」で使用できる書式は、以下のように gdb で使用できるものと同じです。

-b "functionname"
-b "filename:functionname"
-b "filename:linenumber"
-b "*address"

ところで、複数プロセスを作ったりしているので、本当は Bash ではなく Python とかで書いたほうが良いのかなーとも思っているんですが、用が足りているのでとりあえずそのままにしています。

gcore_with_breakpoint 実行サンプル1

デバッグ対象プログラムが "zabbix_agentd"。
"common.c:140" でブレークポイントをセットした core dump として core.7995 を取得できています。

# gcore_with_breakpoint -b "common.c:140" -n zabbix_agentd

Using host libthread_db library "/lib64/libthread_db.so.1".
Using host libthread_db library "/lib64/libthread_db.so.1".
Using host libthread_db library "/lib64/libthread_db.so.1".
Using host libthread_db library "/lib64/libthread_db.so.1".
Using host libthread_db library "/lib64/libthread_db.so.1".
Using host libthread_db library "/lib64/libthread_db.so.1".
0x00000036642954f0 in __nanosleep_nocancel () from /lib64/libc.so.6
0x00000036642c7d63 in __select_nocancel () from /lib64/libc.so.6
0x00000036642c7d63 in __select_nocancel () from /lib64/libc.so.6
0x00000036642c7d63 in __select_nocancel () from /lib64/libc.so.6
0x00000036642954f0 in __nanosleep_nocancel () from /lib64/libc.so.6
0x00000036642950f5 in waitpid () from /lib64/libc.so.6
Breakpoint 1 at 0x40e143: file common.c, line 140.
Breakpoint 1 at 0x40e143: file common.c, line 140.
Breakpoint 1 at 0x40e143: file common.c, line 140.
Breakpoint 1 at 0x40e143: file common.c, line 140.
Breakpoint 1 at 0x40e143: file common.c, line 140.
Breakpoint 1 at 0x40e143: file common.c, line 140.

Breakpoint 1, AGENT_VERSION (cmd=0x7fff4a8ff780 "agent.version", param=0x7fff4a8fef80 "", flags=0,
result=0x7fff4a902890) at common.c:141
141 { ←ここでブレークポイントに掛かってます。
Saved corefile core.7995 ←目的のcoreファイル。

Program received signal SIGINT, Interrupt.
0x00000036642c7d63 in __select_nocancel () from /lib64/libc.so.6

Program received signal SIGINT, Interrupt.
0x00000036642950f5 in waitpid () from /lib64/libc.so.6

Program received signal SIGINT, Interrupt.
0x00000036642954f0 in __nanosleep_nocancel () from /lib64/libc.so.6

Program received signal SIGINT, Interrupt.
0x00000036642c7d63 in __select_nocancel () from /lib64/libc.so.6

Program received signal SIGINT, Interrupt.
0x00000036642954f0 in __nanosleep_nocancel () from /lib64/libc.so.6
Saved corefile core.7994
Saved corefile core.7997
Saved corefile core.7990
Saved corefile core.7993
Saved corefile core.7996

gcore_with_breakpoint 実行サンプル2

デバッグ対象プログラムが "zabbix_agentd"。
以下の例では "*0x40e143" でブレークポイントをセットした core dump として core.7995 を取得できています。
"*0x40e143" は "common.c:140" のアドレスなので、"common.c:140" でブレークしたのと同じ結果になります。

これは例えば顧客が debug symbol を持っておらず、かつ顧客の環境でしか現象が再現しない場合に役立ちます。
あらかじめ "common.c:140" のアドレスを調べておけば、顧客のサービスを(ほぼ)停止せずにcore dump取得ができるからです。
もちろん解析時には debug symbol を用意した環境が必要ですが、顧客のサービスを止めずに顧客のプロセスをデバッグできるのは便利です。

# gcore_with_breakpoint -o zabbix_agentd_core -b "*0x40e143" -n zabbix_agentd
Using host libthread_db library "/lib64/libthread_db.so.1".
Using host libthread_db library "/lib64/libthread_db.so.1".
Using host libthread_db library "/lib64/libthread_db.so.1".
Using host libthread_db library "/lib64/libthread_db.so.1".
Using host libthread_db library "/lib64/libthread_db.so.1".
Using host libthread_db library "/lib64/libthread_db.so.1".
0x00000036642950f5 in waitpid () from /lib64/libc.so.6
Breakpoint 1 at 0x40e143: file common.c, line 141.
0x00000036642954f0 in __nanosleep_nocancel () from /lib64/libc.so.6
Breakpoint 1 at 0x40e143: file common.c, line 141.
0x00000036642c7d63 in __select_nocancel () from /lib64/libc.so.6
Breakpoint 1 at 0x40e143: file common.c, line 141.
0x00000036642c7d63 in __select_nocancel () from /lib64/libc.so.6
Breakpoint 1 at 0x40e143: file common.c, line 141.
0x00000036642954f0 in __nanosleep_nocancel () from /lib64/libc.so.6
0x00000036642c7d63 in __select_nocancel () from /lib64/libc.so.6
Breakpoint 1 at 0x40e143: file common.c, line 141.
Breakpoint 1 at 0x40e143: file common.c, line 141.

Breakpoint 1, AGENT_VERSION (cmd=0x7fff4a8ff780 "agent.version", param=0x7fff4a8fef80 "", flags=0,
result=0x7fff4a902890) at common.c:141
141 { ←ここでブレークポイントに掛かってます。
Saved corefile zabbix_agentd_core.7995 ←目的のcoreファイル。

Program received signal SIGINT, Interrupt.
0x00000036642950f5 in waitpid () from /lib64/libc.so.6

Program received signal SIGINT, Interrupt.
0x00000036642954f0 in __nanosleep_nocancel () from /lib64/libc.so.6

Program received signal SIGINT, Interrupt.
0x00000036642c7d63 in __select_nocancel () from /lib64/libc.so.6

Program received signal SIGINT, Interrupt.
0x00000036642c7d63 in __select_nocancel () from /lib64/libc.so.6

Program received signal SIGINT, Interrupt.
0x00000036642954f0 in __nanosleep_nocancel () from /lib64/libc.so.6
Saved corefile zabbix_agentd_core.7996
Saved corefile zabbix_agentd_core.7994
Saved corefile zabbix_agentd_core.7993
Saved corefile zabbix_agentd_core.7997
Saved corefile zabbix_agentd_core.7990