rsyslogはなにかおかしい気がする
おそらく、rsyslogは何かおかしい。
短時間に大量にログを書き込み続けると、CPU 100%になってシステムを巻き込んでクラッシュさせるような気がする。
これまで、3回ほど経験しました。
googleすると似たようなことを書いている人が何人かいる。
https://www.google.co.jp/search?q=rsyslog+bug+cpu
もちろん、少数のログならば全く問題ない。
大量に書き込み続けなければこれも問題ない。
このバグを引き起こすには、大量のログを長期に渡って書き込み続ける必要があるようだ。
大量のログを書き込み続けると、CPU 100%になってシステムをクラッシュさせるloggerとはいったい・・・
これはloggerではなくただの時限爆弾だろう。
大量にログを出力するときは、rsyslogを使わないほうがいい。
rsyslogの設定でオフにしましょう。
そして、rsyslogではなく、自分で出力しましょう。
ゆとりのある優しいログだけを出力させるようにしましょう。
rsyslogに大量のログを書かせるとシステムが死にます。多分。
プログラムで大切なことは読むべきコードを少なくすること。ただそれだけだ。
オブジェクト思考とか手続き型とか、関数型とかの議論はまったく無価値だ。
こういう議論をしているお前らは、どうやら、プログラムで大切なことを理解していないのだろう。
const性も、オブジェクトも、ライブラリや部品の再利用、グローバル変数の禁止も、継承を避けることも、巨大な関数を避けることも、それはたった一つのことをやるためにあるといってもいい。
それは、読むべきコードを少なくすることだ。
コードを読むのはとにかく疲れる。
だから読むべきコードを少なくしないといけない。
コードがたくさんあると、どこにバグがあるのか、探すのが大変だ。
新しい機能を実装するべき場所を探すのが大変だし、
何を変更したときに他を間違えて、他を壊さないかのチェックも大変だ。
お前らは、printfを知っていますか?
画面に文字を表示する関数だ。
では、この関数がどのように実装されているか知っていますか?
まあ知らないだろう。俺様も知らない。
知る必要がないからだ。
仮に、この関数の内部に100万行のソースコードがあったとしても、我々はそれを読む必要はない。
printfの仕様を知っていれば、実装を読む必要がないのだ。
これで、100万行のソースコードはたった1行のコードになった。
100万行のコードがあったとしても、ブラックボックス化して無視することができる。
これがとても重要なことなのだ。
const性があれば、その機能はデータを読み込むだけで破壊しない。
よって、その関数を読んだとしてもデータが壊れない。
だから、仮に、そのデータがおかしな状態になってデバッグするときにも、その関数の先の実装を読む必要がない。
C++のconstメソッドやstaticメソッドの場合は、クラスの状態を変えないことを言語が保証してくれる。
だから、仮にクラスの状態が何か変になったとしても、そのメソッドがクラスの状態を壊さないことわけだから、そのメソッドの実装を見る必要がない。
ライブラリや部品の再利用についても同じだ。
動作実績があり、枯れているライブラリの場合、何か変な事象が起きたとしても、そのライブラリの内部まで追いかける必要性が低い。
たいていは、自分が書いたライブラリを呼び出す手順に問題があるということになるわけだ。
(これが不安定なライブラリだと、内部まで追跡しないといけず、時間と体力を消耗してしまう)
グローバル変数の禁止も同様だ。
いつどこで変更されるかわからない変数を追跡するのはとてつもなく時間がかかる。
だから禁止されるのだ。
また、オブジェクト化して、変更されるスコープをより限定することで、読むべきソースコードの範囲を狭くすることができる。
継承を避けるのも同様だ。
なぜなら、継承はクラスの足し算だからだ。
巨大なクラスになってしまうと、メンバ変数はグローバル変数の性質をより強く持ってしまう。
だから、グローバル変数の禁止と同じ理由で禁止される。
(似たようなものでsingletonをできるだけ避けるというのもあるだろう。)
巨大な関数を避けることも同様だ。
巨大な関数になってしまうと、どこで何をやっているのか追跡するのが大変だからだ。
ソースコードをひたすら読まないといけない。
テストについても、一部については同様のことが言える。
テストされて動作が保証されているコードは、枯れているライブラリと同じく内部を読む必要がないのだ。
契約による設計については語るまでもないだろう。
契約に従っていることが保証されれば、内部を読む必要がないのだ。
よって、すべてのことは、「読むべきコードを少なくする」ことのために存在する。
ここまで理解できれば、どういうコードが最良なのかわかるはずだ。
それは、読むべきコードができるだけ少なくなるように書かれたコードだ。
部品は独立しており、十分にテストされ枯れていて、いちいち内部の実装を確認しなくてもいいコードだ。
逆にどういうコードが最悪なのかも明白だ。
それは、読むべきコードが多いひたすら多いコードだ。
ドリフのコントみたいに、こちらのドアをパーンと閉めたら向こう側のドアが連動して開くみたいに、複雑に依存して全部読まないと意味がさっぱり分からないコードだ。
コードを書くときは、それは読むべき内容を増やさないだろうか?と疑問を持ちながらコードを書けばいい。
そうすれば多少はマシなソースコードが書けるだろう。
関数型でも読むべき内容が多い最悪なコードな書くことができるし、手続き型でも読むべき内容が少ない良いコードを書くことができる。
しょせんは、読むべき内容を少なくするための方法論の一つにすぎない。
どちらがいいかを議論するのは時間の無駄だ。
そんなのを議論している暇があれば、お前らのクソコードから読むべき部分を少なくする努力でもしてください。
プログラムの書きすぎでキーボードが壊れることに定評があるDynabookのキーボードを交換した
キーボード交換完了。
東芝 Dynabook PRZ63VS
プログラムの書きすぎでキーボードが壊れることに定評があるDynabookのキーボードを交換した。
さすがに年に2回もキーボードが壊れるとは思っていなかった。
即日修理の東芝PC工房がなくなるということで、今回は自分でやってみたよ。
とても面倒だった。
キーボードを分解工具で剥がしただけでは、ダメ。
キーボードとママンボードとの接合部が裏にあるので、分解しないといけない。
後ろカバーを開けて、バッテリーを外して、作業スペースを作ってから、
キーボードロックピンを外して、
その後で、キーボードを取り外し、
新しいのに交換して、再度接合する。
問題は、キーボードの接合部は2つあること。
メインの接合部がかなりシビアで、ちゃんとロックしないと一部のキーが入力できないという謎現象が発生すること。
なぜかRキーだけが入力できなくて、3回ぐらい開け締めしたよ。
また、後ろカバーを固定するネジも、足裏の隠しネジがあるし、とても面倒だったよ。
C#の例外メッセージの書き方はクソだ
C#の例外メッセージの書き方はクソだ。
あの例外メッセージの書き方は、例外の悪い書き方でしかない。
例えばこんなのだ。
System.NullReferenceException Object reference not set to an instance of an object.
このどこが悪いのか?
それは、理由の情報が書いてないことだ。
NullReferenceException ぬるぽが起きたらしいね。
なるほど、オブジェクトがnullだったらしい。
Nullだったということはわかる。
そうだね。nullらしいね。それじゃあ修正しよう。
では、どのオブジェクトがNULLだったのかい?
・・・・
・・・・・・・・
わかりません。
まったくわかりません。
スタックトレースに書いてある関数のどこかです。
ふさげるなっ!!
デバッグビルドだとIDEが場所で止まってくれるし、情報も吐き出される。
しかし、このダメな例外メッセージの書き方で問題になるのは、リリースビルドのときだ。
スタックトレースには、pdbがないと行番号は出力されないし、あってもなぜか出力されないこともある。
(世界は広いのでそういう変な環境もあるらしい)
よって、私たちが得られる情報は、直前に実行されていた関数内のどこかのオブジェクトがnullだったということだけだ。
どこだよそれは。
変数名はコンパイルの過程で消えるから出せない?
そうだね。確かに消えて出せない。でも、変数の型ならだせるだろう?
どの型が問題を起こしたのか?これだけでも候補を絞り込むことができる。
なんでこんな簡単なことをやらないの?
他にもダメなのがいっぱいある。
これを作った奴らはおかしい。
System.ArgumentOutOfRangeException Index was out of range. System.Runtime.InteropServices.ExternalException A generic error occurred in GDI .
どれもこれも理由が明記されていない。
範囲外アクセスしたのはわかる。でもそれはどの型のオブジェクトで、何番目にアクセスしたから範囲外なのかといった基本的な情報が欠落している。
これはダメな例外メッセージの典型例だ。
意味が分からない例外メッセージはないと同じだ。 throw new ArgumentOutOfRangeException("fuck you"); と書いているのとほぼ同じだ。
いったいMSの開発者は何を考えていたんだ。
これを書いたMSの開発者は後で私のオフィスに来るように。
もし、このクソ仕様を受け入れるならば、関数をできるだけ小さい単位に細分化する必要がある。
1つの関数が1つのメソッドしか呼ばない程度にな。
そんな面倒なことはしてられないけど。
C#のネイティヴの例外は、起きたことはわかるんだけど、誰がなぜ起こしたのかわからない。
ダメな例外メッセージの書き方そのものだ。
C#の例外メッセージの書き方を真似しなければ、いいプログラムが書けるだろう。
C++だと黒魔術を利用して、例外ハンドラーそのものに割り込んで、もっとましな例外メッセージを追加したり、情報を多く取れるだろう。
しかし、C#の世界ではそれは不可能だ。ランタイムライブラリに割り込むことは不可能だろう。たぶん。
もし、もっとましな方法があるなら教えてほしい。
いったいどうやるのがベストプラクティスなんだ。
死後の世界は存在するのか?[思考実験]
死後の世界は存在するのか?
死後の世界は、いろいろな宗教において、大切なテーマになっている。
今回はこれについて考えてみよう。
死後の世界は存在するのか?
俺様は、存在しないと思う。
理由は以下の通り。
例えば、生き物は死ねばあの世に行くと仮定する。
と、いうことは、ウイルスや細菌はどうなんだ?
ウイルスも死ねばあの世に行くんだろうか?
行くとすれば、インフルエンザウイルスや、エボラウイルス、エイズウイルスなどの強烈なウイルスや、
天然痘などの人類が絶滅させたウイルスもあの世にいるはずだ。
あの世は恐ろしいウイルスまみれということになるだろう。
害虫とかもあの世に行くんだろうか?
殺虫剤やバルサン、農薬などで私達は毎年のように大量の害虫を殺している。
そしたら、あの世は恐ろしい害虫まみれということになる。
ミジンコや単細胞生物はあの世に行くんだろうか?
そしたら、あの世は恐ろしいミジンコまみれということになる。
・・・・
そんなのありえないだろう。
だから、死後の世界は存在しない。
それとも、あの世に行けるのは、人間だけということなんだろうか?
ネコや犬はいけないの?
でも、化け猫などのおばけの話はあるよね。
と、いうことは、哺乳類はいけるのかな?
でも、鳥類の化物の話も世界中にある。
と、いうことは鳥類も行けるのかな?
両生類は?魚類は?
・・・・
こちらの考え方でも理論が破綻する。
よって、死後の世界は存在しないと考えるのが一番合理的だ。
説得力がある。
だから、私は、死後の世界は存在しないと思っている。
C#で var_dump
C#で var_dump するいい例がなかったので作った。
public static String var_dump(object obj) { if (obj == null || obj is uint || obj is int || obj is ushort || obj is short || obj is byte || obj is byte || obj is float || obj is double || obj is bool || obj is UInt16 || obj is Int16 || obj is UInt32 || obj is Int32 || obj is UInt64 || obj is Int64 ) { return obj.ToString(); } if (obj is string) { return "\"" + obj.ToString() + "\""; } string ret ; IEnumerable ienum = obj as IEnumerable; if (ienum != null) { ret = "["; foreach (object o in ienum) { ret += var_dump(o) + ","; } return ret + "]"; } ret = "{"; const BindingFlags FINDS_FLAG = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly; FieldInfo[] infoArray = obj.GetType().GetFields(FINDS_FLAG); foreach (FieldInfo info in infoArray) { object o = info.GetValue(obj); ret += info.Name + ": " + var_dump( o ) + ","; } return ret + "}"; }
ELF形式からシンボルデータを読み取って、アドレスから関数を求めよう。
ELF形式からシンボルデータを読み取って、アドレスから関数を求めよう。
バイナリのあるアドレスから、そのアドレスにある関数名を求めるにはどうすればいいだろうか?
たとえば、クラッシュして落ちたときに、クラッシュしたアドレスのログを残したい場合だ。
アドレスだけではわけがわからないので、関数名も一緒にログに書きたいのだ。
libbfd
libbfdを使えれば、bfd_openr や bfd_read_minisymbols といろいろ使って、アドレスから関数名を求めることができる。
ただ、もとまるのかもしれないが、libbfdはGPLである。
残念ながら LGPLではない。
ライセンスを気にする人は利用できないだろう。
Binary File Descriptor library
https://en.wikipedia.org/wiki/Binary_File_Descriptor_library
License: GNU General Public License
addr2line
では、代わりに addr2lineコマンドを利用して、
addr2line -p -C -e `readlink /proc/self/exe` 0x123 とかを呼び出すか?
それもよい。
でも、addr2lineはOSディフォルトでインストールされるわけではない。
インストールされていない環境もあるだろう。
ないならつくろう
では、どうするか?
自前で、elf形式を解析して、symbol情報を参照し、addrから関数を求めればいいのだ。
行数も出せればいいのだが、計算式がよくわからないので省略する。
とりあえず、アドレスが関数名になるだけでもかなりのヒントだろう。
ELF形式は、他の実行形式に比べて、比較的資料があるが、
日本語でシンボル情報参照まで書いている人があまりいなかったので、ここに解説したいと思う。
ソースコード。
スニペットではなく動くものが見たい人は、ここらへん参照。
https://github.com/rti7743/naichichi2/blob/master/naichichi2/haikuwoyome.h#L123
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <fcntl.h> #include <cxxabi.h> //でまんぐる #include <elf.h> //山田エルフ先生 //ELF形式は、64bitと32bitでわかれるらしいので、typedefして切り分ける。 #ifdef __x86_64__ typedef uint64_t Elf_Addr; typedef Elf64_Ehdr Elf_Ehdr; typedef Elf64_Shdr Elf_Shdr; typedef Elf64_Sym Elf_Sym; #else typedef uint32_t Elf_Addr; typedef Elf32_Ehdr Elf_Ehdr; typedef Elf32_Shdr Elf_Shdr; typedef Elf32_Sym Elf_Sym; #endif //__x86_64__ //C++のデマングル static std::string demangle(const std::string& name) { char buf[256]; int status; size_t length = 256; if ( abi::__cxa_demangle(name.c_str(),buf,&length,&status)) { return buf; } return name; } //elf形式の実行ファイルのシンボル情報からアドレス位置にある関数名の取得を行う. static bool ElfToSymbol(const std::string& filename,Elf_Addr addr,std::string* outSymbol) { int fd = open(filename.c_str(),O_RDONLY); if (fd < 0) {//ファイルを開けない. return false; } Elf_Ehdr ehdr; Elf_Shdr shdr; Elf_Shdr shdr_linksecsion; Elf_Sym sym; int r = read(fd,&ehdr,sizeof(ehdr)); if (r < 0) {//ファイル先頭のELFヘッダを読み込めない。 close(fd); return false; } if ( memcmp(ehdr.e_ident,ELFMAG,SELFMAG) != 0 ) {//ELF文字の確認。 close(fd); return false; } //find SHDRテーブルの探索. for(int i = 0 ; i < ehdr.e_shnum ; i++ ) { lseek(fd,ehdr.e_shoff + (i * sizeof(shdr)),SEEK_SET); r = read(fd,&shdr,sizeof(shdr)); if ( r < sizeof(shdr) ) { continue; } if ( ! (shdr.sh_type == SHT_SYMTAB || shdr.sh_type == SHT_DYNSYM) ) {//シンボルが書かれているテーブルではないっぽい. continue; } //sh_link番目にあるデータに文字列テーブルがあるらしい. lseek(fd,ehdr.e_shoff + (shdr.sh_link * sizeof(shdr)),SEEK_SET); r = read(fd,&shdr_linksecsion,sizeof(shdr_linksecsion)); if ( r < sizeof(shdr_linksecsion) ) { continue; } //現在のSHDRテーブルを読む const unsigned int nloop_count = shdr.sh_size / sizeof(sym); for(int n = 0 ; n < nloop_count; n++ ) { lseek(fd,shdr.sh_offset + (n*sizeof(sym)),SEEK_SET); r = read(fd,&sym,sizeof(sym)); if ( r < sizeof(sym) ) { continue; } if (addr < sym.st_value || addr >= sym.st_value + sym.st_size ) {//探しているアドレスではない continue; } //found. char buf[256]; if (sym.st_name != 0) { //名前がある場合、[sh_link].sh_offset + sym.st_name に名前がある. 0終端 lseek(fd,shdr_linksecsion.sh_offset + sym.st_name,SEEK_SET); r = read(fd,buf,255); if ( r < 0 ) { continue; } buf[r] = 0; //終端 *outSymbol = demangle(buf); } close(fd); return true; } } close(fd); return false; }
解説
基本的にコードに書いてある通りだが、
とりあえず、冗長なエラー処理を除いて、簡単に解説していこう。
ELF形式の構造は64ビット、32ビットで変わるので typedef しておくと吉。
これらは、 elf.h ( /usr/include/elf.h ) に記載されている。
#include <elf.h> //山田エルフ先生 //ELF形式は、64bitと32bitでわかれるらしいので、typedefして切り分ける。 #ifdef __x86_64__ typedef uint64_t Elf_Addr; typedef Elf64_Ehdr Elf_Ehdr; typedef Elf64_Shdr Elf_Shdr; typedef Elf64_Sym Elf_Sym; #else typedef uint32_t Elf_Addr; typedef Elf32_Ehdr Elf_Ehdr; typedef Elf32_Shdr Elf_Shdr; typedef Elf32_Sym Elf_Sym; #endif //__x86_64__
実行ファイルの 先頭から ELFヘッダはスタートする。
int fd = open(filename.c_str(),O_RDONLY); Elf_Ehdr ehdr; int r = read(fd,&ehdr,sizeof(ehdr)); if ( memcmp(ehdr.e_ident,ELFMAG,SELFMAG) != 0 ) {//ELF文字の確認。 close(fd); return false; }
そして、
ファイルのELFヘッダ ehdr.e_shoff バイト目から、 ehdr.e_shnum個の セクションヘッダテーブル struct Elf_Shdrのデータが並んでいる。
struct Elf_Shdrファイルの下の方に配置される。
struct Elf_Shdrは必須ではない。
stripして消されている可能性、つまり、ない可能性もある。
struct Elf_Shdrを順次読んでいき、 シンボル情報が書かれている可能性がある SHT_SYMTAB と SHT_DYNSYM を探り当てる。
for(int i = 0 ; i < ehdr.e_shnum ; i++ ) { lseek(fd,ehdr.e_shoff + (i * sizeof(shdr)),SEEK_SET); r = read(fd,&shdr,sizeof(shdr)); if ( ! (shdr.sh_type == SHT_SYMTAB || shdr.sh_type == SHT_DYNSYM) ) {//シンボルが書かれているテーブルではないっぽい. continue; } ... }
SHT_SYMTAB と SHT_DYNSYMにはアドレスとそのシンボル情報が文字列テーブルの何番目に記録されているか書かている。
文字列テーブルは、shdr.sh_link番目の struct Elf_Shdr に書かれている。
なので、文字列テーブルの struct Elf_Shdr を、別途参照する必要がある。
ここでは、struct Elf_Shdr shdr_linksecsion; として読み込む.
//sh_link番目にあるデータに文字列テーブルがあるらしい. lseek(fd,ehdr.e_shoff + (shdr.sh_link * sizeof(shdr)),SEEK_SET); r = read(fd,&shdr_linksecsion,sizeof(shdr_linksecsion));
さて、文字列テーブルに寄り道をしたが、アドレスの話に戻る。
ファイルの shdr.sh_offset からデータが始まる。
データは、struct Elf_Sym である。
データは、hdr.sh_sizeバイト存在することになる。
//現在のSHDRテーブルを読む
const unsigned int nloop_count = shdr.sh_size / sizeof(sym); for(int n = 0 ; n < nloop_count; n++ ) { lseek(fd,shdr.sh_offset + (n*sizeof(sym)),SEEK_SET); r = read(fd,&sym,sizeof(sym)); if (addr < sym.st_value || addr >= sym.st_value + sym.st_size ) {//探しているアドレスではない continue; } ... }
struct Elf_Sym には、
その関数の開始アドレス sym.st_value
その関数の長さ sym.st_size
その関数の名前 sym.st_name
などがある。
探しているアドレスがどこにマッチするのかを探索し、
探しているアドレスが見つかれば名前を取得する。
関数の名前を取得するには、
sym.sh_link番目の struct Elf_Shdr を参照する。
先ほど、shdr_linksecsion として、ロードしたものだ。
shdr_linksecsion.sh_offset + sym.st_name が、望む文字列である。
//found. char buf[256]; if (sym.st_name != 0) { //名前がある場合、[sh_link].sh_offset + sym.st_name に名前がある. 0終端 lseek(fd,shdr_linksecsion.sh_offset + sym.st_name,SEEK_SET); r = read(fd,buf,255); if ( r < 0 ) { continue; } buf[r] = 0; //終端 *outSymbol = demangle(buf); }
取得したシンボル名は、C++のマングル表記されているので、demangleする。
そのままでも読めはするが、わかりにくいので、demangleしたものを利用する。
demangleには複数の方法があるが、今回は abi::__cxa_demangle を利用した。
//C++のデマングル static std::string demangle(const std::string& name) { char buf[256]; int status; size_t length = 256; if ( abi::__cxa_demangle(name.c_str(),buf,&length,&status)) { return buf; } return name; }
以上で、アドレスからELFバイナリのデバック情報を使って関数名に変換することができる。