Linuxでkernelを再構築せず自分のシステムコールを追加(偽)する

最近、仮想記憶システムを勉強するためCR3レジストとページテーブルをアクセス必要がある。
Ring3でアクセスできないので、OSのカーネルにする。しかしどうするとカーネルに入れるのかなって。
ネットで検索して、Linuxで一番簡単な方法はカーネルモジュールを書く。しかしコード全部はカーネルモジュールで実行するのはいろいろな不便(デバッグとか)です。
CR3とページテーブルをアクセスするシステムコールがあれば便利だと思う。しかしこんなシステムコールがないようだ。ネットで自作システムコールの資料を参考して、カーネルの再構築は必要らしい。それは面倒くさい。
じゃあカーネルモジュールに自分のシステムコールを追加できるのかなって思って、よく見るとシステムコールの数はカーネルのソースコードで#defineで定義されちゃった。それは追加不可能になった。
ほかの方法はいろいろ探した。既存のシステムコールを入れ替えるのはいい方法に見える。
確かにネットでいろいろ関する資料があるが、中古なので今のカーネルに対してもう使えなくなった。だから私はこの記事を書いた。

まず、ネットでの資料によるとシステムコール関数のアドレスははsys_call_tableに保存される。このテーブルを編集すればシステムコールを入れ替える。でもそんな簡単ではない。1、今のカーネルは、sys_call_tableというシンボルは公開しない。だからカーネルモジュールのコードにextern void** sys_call_table;を書けば構築したカーネルモジュールはinsmodできない。sys_call_tableというシンボルは見付からないから。
実はこのシンボルのアドレスは、/boot/System.mapに探せる・・・
私のシステムに、
grep sys_call_table /boot/System.map-3.6.6-1.fc16.i686.PAE
を実行すれば
c097b2c0 R sys_call_table
って表れる。
これで、sys_call_tableを見付からない問題は解いた。

あと、sys_call_tableの編集の問題だ。sys_call_tableは読み出し専用なので、普通で編集できない。しかし今コードはカーネルで実行してるから、この「読み出し専用」は無視できる。
「読み出し専用」は、「書き込み不可」だ。この「不可」はCR0レジストの十六番目のビットに制御される。カーネルでCR0レジストを編集できる。だから「CR0の十六番目のビットを0になせる→sys_call_tableを編集する→CR0の十六番目のビットを戻らせる」って方法でsys_call_tableを編集できる。

って、勝手にsys_call_tableを編集すればシステムは正しく動かなくなった。だから自分の編集は元のシステムコールの機能に邪魔するのはいけない。私はgetpidを入れ替える。getpidは元々パラメータがない。自分の書いた偽者関数はパラメータを調べて、特定な値なら自分のコードを実行する。でないと元の関数を実行する。

最後、カーネルモジュールの構築。ネットで「GCCを直接に実行してカーネルモジュールを構築する」の記事が多い。私はその方法で失敗した。Makefileを使用するって説明する記事に従って成功した。詳しいのは知らないままで。

これはソースコードです。
http://www1.axfc.net/uploader/so/2682144.bz2

注意:CR3を取得のコードは絶対に間違った。ただいまカーネル仮想メモリアドレスとユーザー仮想メモリアドレスについて勉強している。

mygetpid.cは自作システムコールを試すコードです。Makefileに入らない。
直接コンパイルしていいです。

Boost.Spirit.Qiで自作int型パーサーの使用

Boost.Spirit.Qiはパーサー作成のライブラリです。
このライブラリの説明書に、主に文字列の解析で説明した。
ほかの型も解析できると思うが、今試した。

int型の解析器で練習してた。いろいろの問題に会ったが最後にできた。

まず、基本のパーサーを作る。Qiの説明書のレファレンスにコンセプトのチャプターがあります。よく見ると、パーサーはいろいろの種別がある。私は今ParserとPrimitiveParserしか試してなかった。パーサーについて、四つの条件がある。

  1. parse関数がある。パラメータは五つ:1と2、解析されるもののIterator範囲(〜から〜まで、パラメータ二つ)3、作用しらない(おい!)コンテキスト4、読み飛ばすパーサーと5、読み取る値。戻り値はbool型の成功(true)と失敗(false)。
  2. what関数。戻り値は boost::spirit::info の型の知らないもの。私はとりあえずreturn boost::spirit::info();にした(←
  3. attribute::type 。読み取る値の型。こっちはintにする。
  4. traits::is_parser

    ::type ←なにこれわからない。しかしこのパーサーはboost::spirit::qi::parser を流用すればよさそうだ。この条件は放置しも大丈夫と思う

使わないパラメータはunused_typeにする。

すると、何もしないint型のパーサー(後は読み飛ばすパーサーとして使う)は作った:


template<class Iter>
struct MySkipper : boost::spirit::qi::parser<MySkipper<Iter> > {
bool parse(Iter& f, const Iter& l, boost::spirit::unused_type, boost::spirit::unused_type, boost::spirit::unused_type) const
{
return false;
}

boost::spirit::info what(boost::spirit::unused_type) const {
boost::spirit::info i;
return i;
}

template<class Context, class Iter>
struct attribute { typedef void type; };
};

parseに初めてのパラメータのIteratorはconstじゃない、それにレファレンスを使う。Qiの説明書について、成功するときfは「次はどこから続く」を示すべき。詳しいのはQiの説明書に参考してください。

後は何かをするパーサー。自分は「範囲パーサー」作った。もし解析される値は特定の範囲に入れば成功になる。こうなる:


template<class Iter, class Skipper>
struct MyIntParser : boost::spirit::qi::parser<MyIntParser<Iter, Skipper> > {

int _min, _max;
MyIntParser(int min, int max) : _min(min), _max(max) { }

template<class SK, class Cont>
bool parse(Iter& f, const Iter& l, Cont& c, SK& sk, int& attr) const
{
boost::spirit::qi::skip_over(f, l, sk);
if ( *f >= _min && *f < _max) {
attr = *f;
++f;
return true;
} else {
return false;
}
}

boost::spirit::info what(boost::spirit::unused_type) const {
boost::spirit::info i;
return i;
}

template<class Context, class Iter>
struct attribute { typedef int type; };
};

今度はSkipperを使い。例えば解析されるint配列に0を無視すべきにしたら、0のパーサーを作ってSkipperとして使えばいい。
parse関数はQiの説明書により:

  1. 成功するときtrueの値を戻る、そして初めてのパラメータ(f)は「次はどこから続く」を示す、最後のパラメータは読み取る値を示す。
  2. そうじゃなくとfを変更じゃだめ
  3. 読み取る値はint型からattribute::typeの型はintだ。

そして、私はその自作パーサーを使いたい。説明書のtutorialにより、大体こんな感じ:


template<class Iter, class Skipper>
struct MyParser : boost::spirit::qi::grammar<Iter, Skipper> {
boost::spirit::qi::rule<Iter, Skipper> _start;
typedef MyIntParser<Iter, Skipper> MIP;

MyParser() : base_type(_start)
{
_start = MIP(1,5) >> MIP(5,10) >> MIP(3,7);
}
};

解説は、「初めての数字は1〜5の範囲に入って、次は5〜10、最後の数字は3〜7」。
でもこうするとエラーになる。
MyIntParserに「>>」演算子は使えない。自分で>>演算子を書くより、もっと簡単な方法がある:ruleに入る。
そしてこうなる:

template<class Iter, class Skipper>
struct MyParser : boost::spirit::qi::grammar<Iter, Skipper> {
boost::spirit::qi::rule<Iter, Skipper> _start;
boost::spirit::qi::rule<Iter, int(), Skipper> _m1;
boost::spirit::qi::rule<Iter, int(), Skipper> _m2;
boost::spirit::qi::rule<Iter, int(), Skipper> _m3;
typedef MyIntParser<Iter, Skipper> MIP;

MyParser() : base_type(_start)
{
_m1 = MIP(1,5);
_m2 = MIP(5,10);
_m3 = MIP(3,7);
_start = _m1 >> _m2 >> _m3;
}
};

_m1、_m2、_m3のruleテンプレートに、int()は省略しちゃだめだ。このテンプレートパラメータは「このパーサーの戻り値はintだ」と示す。デフォルトでunused_typeになっちゃう。省略したら何も読み取れなかった

Skipperは省略しちゃだめだ。grammarのテンプレートパラメータにSkipperを省略したら、デフォルトでunused_typeになっちゃう。後はphrase関数に何のSkipperを指定しても読み飛ばせない

さあ、使ってみよう:


int main()
{
int x[] = {3, 6, 6};
MyParser<int*, MySkipper<int*> > mp;
MySkipper<int*> sk;
int* xf = x;
boost::fusion::vector3<int, int, int> xval;
if (boost::spirit::qi::phrase_parse(xf, xf + 3, mp, sk))
std::cout << "success" << std::endl;
else
std::cout << "failed" << std::endl;
return 0;
}

さっきIterは直接int*と書かなかった理由は、std::vector::iteratorの可能性もあるとかなんとか・・・ね

success ←w
このプログラムのソースコードを取得する ←CPPです


これだけで足りない。読み取る値を知りたいです。方法は二つある。

1、Parser Semantic Actionsを使う。方法は簡単です。tutorialの言うとおり:


template<class Iter, class Skipper>
struct MyParser : boost::spirit::qi::grammar<Iter, Skipper> {
boost::spirit::qi::rule<Iter, Skipper> _start;
boost::spirit::qi::rule<Iter, int(), Skipper> _m1;
boost::spirit::qi::rule<Iter, int(), Skipper> _m2;
boost::spirit::qi::rule<Iter, int(), Skipper> _m3;
typedef MyIntParser<Iter, Skipper> MIP;

int x[3];

MyParser() : base_type(_start)
{
using boost::phoenix::ref;
using boost::spirit::qi::_1;

_m1 = MIP(1,5);
_m2 = MIP(5,10);
_m3 = MIP(3,7);
_start = _m1[ref(x[0])=_1] >> _m2[ref(x[1])=_1] >> _m3[ref(x[2])=_1];
}
};

ソースコードをこっち ←CPPです

もう一つの方法は、MyParserを三つのintの値を返せる。

Qiの説明書の「Employee - Parsing into structs」チャプターによると

Well, typically, the attribute of:
a >> b >> c

is:
fusion::vector<A, B, C>

三つのint()型のruleは>>演算子で接続すると、読み取る値はfusion::vector型になる。
では_startとMyParserの型は
rule<..., boost::fusion::vector(), ...>
にすれば、fusion::vector型の値を読み取れる。

ではMyParserは、こうなる


template<class Iter, class Skipper>
struct MyParser : boost::spirit::qi::grammar<Iter, boost::fusion::vector3<int, int, int>(), Skipper> {
boost::spirit::qi::rule<Iter, boost::fusion::vector3<int, int, int>(), Skipper> _start;
boost::spirit::qi::rule<Iter, int(), Skipper> _m1;
boost::spirit::qi::rule<Iter, int(), Skipper> _m2;
boost::spirit::qi::rule<Iter, int(), Skipper> _m3;
typedef MyIntParser<Iter, Skipper> MIP;

int a,b,c;

MyParser() : base_type(_start)
{
_m1 = MIP(1,5);
_m2 = MIP(5,10);
_m3 = MIP(3,7);
_start = _m1 >> _m2 >> _m3;
}
};

読み取る値はphrase_parse関数のパラメータで返す。

boost::fusion::vector3<int, int, int> xval;
boost::spirit::qi::phrase_parse(xf, xf + 3, mp, sk, xval);
ソースコードを取得する ←CPPです


実はね、そのint()とかboost::fusion::vector()とか、書き間違えたらコンパイルできる可能性もある。しかし当たり前で何も取れなかった。


Skipperは、元々boost::spirit::qi::no_skip使いたいですが、なんだか使えないようだ。仕方ない、自作する。→MySkipper


私はSpirit.Qiに初心者です。興味ある方は交流してくれるとうれしいです。


追記:
私の書いたコードは正しいかどうかわからない。ここでまた何かを発見した。公式の例で、しかも使い方は私と違ってる。

C++でスタックでメモリ確保

動的配列を作るとき、普段でnewとかmallocとか使う。
それはヒープからメモリブロックを取得するのです。
小さいメモリブロックを取得する時、性能的にスタックから取得より低いと思う。

スタックからメモリブロックの取得は、allocaと言う関数がありますが、標準にその関数がありません。
じゃあその「標準」から離脱せずスタックからメモリブロックの取得する方法あるのかなって思って。ローカル変数を利用する方法を思いついた。

たとえ、この文字列のコードページを変更する関数


int EncodingConv(
const char* inpStr, int nInpLen,
char* outStr, int nOutLen,
const std::locale& srcLoc, const std::locale& dstLoc
) {
typedef std::codecvt<wchar_t, char, std::mbstate_t> MyCodeCvt;

std::vector<wchar_t> vbuf(nInpLen + 1);
wchar_t* buf = &vbuf[0];
int nBufLen = vbuf.size();

std::mbstate_t state = 0;
const char* inpMid;
const char* inpEnd = inpStr + nInpLen;
wchar_t* bufMid;
wchar_t* bufEnd = buf + nBufLen;
std::use_facet<MyCodeCvt>(srcLoc).in(state,
inpStr, inpEnd, inpMid,
buf, bufEnd, bufMid);

state = 0;
char* outMid;
char* outEnd = outStr + nOutLen - 1;
bufEnd = bufMid;
const wchar_t* cBufMid;
std::use_facet<MyCodeCvt>(dstLoc).out(state,
buf, bufEnd, cBufMid,
outStr, outEnd, outMid);
*outMid = 0;
return outMid - outStr;
}

その中にvectorを使う。vectorは動的メモリ確保の関数を使う。性能はこれで低くなるかも。この関数を改造して、スタックをその「buf」として使用すれば…

メモリ確保の部分は、こんな関数になった


template<int LEN>
inline int EncodingConvImpl(wchar_t* buf, int nBufLen,
const char* inpStr, int nInpLen,
char* outStr, int nOutLen,
const std::locale& srcLoc, const std::locale& dstLoc
) {
wchar_t localbuf[LEN];
return EncodingConvImpl<0>(localbuf, LEN, inpStr, nInpLen, outStr, nOutLen, srcLoc, dstLoc);
}
localbufはローカル変数ので、スタックで存在する。

そして元の関数は


template<>
inline int EncodingConvImpl<0>(wchar_t* buf, int nBufLen,
const char* inpStr, int nInpLen,
char* outStr, int nOutLen,
const std::locale& srcLoc, const std::locale& dstLoc
)
こうなる。これでスタックのメモリを使うようになった。

メモリブロックのサイズよって、テンプレートのパラメータは変わる


typedef std::codecvt<wchar_t, char, std::mbstate_t> MyCodeCvt;

if (buf == 0) {
if (nBufLen > 8192) {
std::vector<wchar_t> buf(nInpLen + 1);
return EncodingConvImpl<0>(&buf[0],nInpLen, inpStr,nInpLen, outStr,nOutLen, srcLoc,dstLoc);
}
#define FORWARDIMPLCALL(x,y) if(nBufLen > x) return EncodingConvImpl<y>(0,y, inpStr,nInpLen, outStr,nOutLen, srcLoc,dstLoc)
FORWARDIMPLCALL(7168, 8192);
FORWARDIMPLCALL(6411, 7168);
FORWARDIMPLCALL(5120, 6144);
FORWARDIMPLCALL(4096, 5120);
FORWARDIMPLCALL(3072, 4096);
FORWARDIMPLCALL(2048, 3072);
FORWARDIMPLCALL(1024, 2048);
FORWARDIMPLCALL(0, 1024);
#undef FORWARDIMPLCALL
}
そして元のコード

else if (buf != 0) {
std::mbstate_t state = 0;
const char* inpMid;
const char* inpEnd = inpStr + nInpLen;
wchar_t* bufMid;
wchar_t* bufEnd = buf + nBufLen;
std::use_facet<MyCodeCvt>(srcLoc).in(state,
inpStr, inpEnd, inpMid,
buf, bufEnd, bufMid);

state = 0;
char* outMid;
char* outEnd = outStr + nOutLen;
bufEnd = bufMid;
const wchar_t* cBufMid;
std::use_facet<MyCodeCvt>(dstLoc).out(state,
buf, bufEnd, cBufMid,
outStr, outEnd, outMid);
*outMid = 0;
return outMid - outStr;
}
}
最後

int EncodingConv(const char* inpStr, int nInpLen, char* outStr, int nOutLen,
const std::locale& srcLoc, const std::locale& dstLoc)
{
return EncodingConvImpl<0>(0, nInpLen + 1, inpStr, nInpLen, outStr, nOutLen, srcLoc, dstLoc);
}

これでできた。

mciSendStringでMP3再生するときID3v2のせいで起こる遅延の計算(C++コード付き)

昨日言ったこと。
自分で書いた計算のC++コードは大体この感じ


double CheckMp3_mciSendStringWorkAround(std::istream& mp3file)
{
char id3[3];

mp3file.read(id3, 3);
if(mp3file.gcount() != 3) throw MyException("Fail to read id3v2 tag");

if(std::equal(id3, id3 + 3, "ID3")) {
//start workaround
//skip 3 byte, advanced to id3v2 frame size
mp3file.ignore(3);
unsigned char id3v2size[4];

mp3file.read(reinterpret_cast<char*>(id3v2size), 4);
if (mp3file.gcount() != 4) throw MyException("Fail to read id3v2 size");

int nId3v2size = id3v2size[0] * 0x80 * 0x80 * 0x80
+ id3v2size[1] * 0x80 * 0x80
+ id3v2size[2] * 0x80
+ id3v2size[3];

int id3end = nId3v2size + 10;
mp3file.seekg(0, std::ios::beg);
char buf[8]; //frame header + crc
mp3file.read(buf, 8);
if (mp3file.gcount() != 8) throw MyException("Fail to read mp3 frames");
if (buf[1] & 1) {//check if there are crc
nId3v2size -= 4;
} else {
if ( (buf[4] | buf[5] | buf[6] | buf[7]) == 0 )
nId3v2size -= 8;
else
return 0;
}

//get duration
return double(nId3v2size) * 8 / 128000;
} else
//no need to work around
return 0;
}

試したことはまた少ない。
少なくとも試したMP3だけ一応計算できる

その128000にまた知らないことがあります。
ほかの計算方法あるのかなって

・・・クソ、MCIでMP3を再生するの仕様

Jubeat Analyzerとmp3ファイル

一応Jubeatプレイヤーです。最近Air Raid From Tha Undaground通れなくて困ってる。
JubeatクラスタにJubeat Analyzerはすごく有名なツールです。TXT形式の譜面スクリプトを再生できる。まるでゲーセンのユビートと同じです。譜面確認とかすごい便利なツールです。
前の日記に言ったよね、私は出力したフレームから譜面動画を作成するツールを作っているってこと。今度はまた何かの問題にあった。
Jubeat Analyzerと同梱してるTwilight Gooseの譜面に、o=1747と書いてあります。Readme.txtによって、仕様は「初めての拍は1.747秒で出る」って意味。でも自分の動画作成ツールでTwilight Gooseの譜面動画を作成するとき、音声は早すぎます。
最初、私は「これもしかしてo=の仕様だった?」って思って。Readme.txtで

※注意点 2:「それでは譜面の冒頭に "r=840" と書くのと "o=840" と書くのでは効果が全く同じなのではないか??」
と思われる方がいらっしゃるかもしれません。そう思いますよね。
その通りならよかったのですが………実は、前者は後者より100ミリ秒譜面が遅れて再生されます。(これ以上の詳細は、ホームページをご覧ください)

と書いてあります。じゃo=1747ってr=1647と同じく?・・・
そして、私はTwilight GooseをAudacityで開いた。結局こうなった

待てよ!これ、1.747でも1.647でもなく1.53秒だよ。おかしい。Air Raid from the undagroundの譜面動画を作成する時、こんな問題がありません。
そして、私はo=xxxとr=xxxの仕様とほかの何か聞きたいことを作者に電子メールを送信した。昨日返信は届きました。「o=1747」は確かに「r=1647」と同じです。私自分で試しました。確かに同じです。じゃあ、問題はそのMP3ファイルに・・・
私はAudacityでそのTwilight GooseがWAVを出力して、再度lameでMP3に変換した。Jubeat Analyzerで再生して、音声と譜面は合わなくなった。

じゃあ問題はそのMP3では間違えないよね。何か妙なMetadata込んでるのかな。MediaInfoでAir raid from tha undagroundのMP3とTwilight GooseのMP3に確認して、なんだか同じのようだ。おかしい、MediaInfoを読み込めない情報があるのかな。
そしてFrhed(Hex編集ツール)でそのMP3を開いて、何かを発見した

ちょ?!ID3v2?まさか。
id3v2.3.0 - ID3.org←ここでID3v2に関する情報を確認して、そのID3v2Frameのサイズは 0*0x80^3 + 0*0x80^2 + 0x20*0x80 + 0x25 = 4133バイト。じゃ、この情報をデータとして取り扱えば「妙な遅延」になっちゃったね!
ビットレートはMP3のフレームで書いてある。普通のMP3フレームのヘーダは「0xff」バイトで始める。この「I」でどう見ても0xffではありません。じゃどんなデータとして取り扱われたのでしょうか、別にヘーダでもありません。むかし、一番よく見えるのは128KbpsのMP3って間違えない。では128Kbpsで試す。 (4133バイト + 10バイト(ID3v2ヘーダの長さ)- 4バイト(MP3フレームの長さ)) * 8ビット/バイト / 128000ビット/秒 = 0.2586875秒。これは「妙な遅延」の長さでしょう。試したらやっぱり。

Jubeat Analyzer内部はmciSendString関数でMP3の再生をするのです。じゃこれはmciSendStringはID3v2に対応してなかった問題ですね。

.netでGDI+の使用とガベージコレクション機能

Jubeat Analyzerは譜面動画のフレーム出力機能があります。
そして私はニコニコとかの動画サイトで数字付くの譜面動画を見たことがある。

その数字はどう追加するのだろう。キーワードとかわからなくてネットで検索して役に立てる情報出ていない。

譜面情報を取得したら自分でプログラム書いてフレームに数字を追加する。

譜面情報の取得は予想以外にめちゃくちゃ難しい。デバッグとかと共に何日かかった。

それはこのポストのポイントじゃないですが(

重点は、私は譜面情報をメモリに読み込んだ、そして毎フレームに数字を書くのループで、OutOfMemoryException起こった。

あれ?プログラムは フレームをメモリに読み込み→数字を描く→画像ファイルに保存 しかなにもやってないはず。.netはガベージコレクション機能があるのだろう。じゃ私は別にImageオブジェクトとGraphicsオブジェクトはほかの所で使用じゃないので、メモリ使用は多くなった時ガベージをコレクションすべき。なんでこうなったんだ。

そして私はループにSystem::GC::Collect();を使用して、問題は解決しました。

いや、まって。手動でSystem::GC::Collect関数を呼ぶことは推奨ではない。じゃどうすればいいのだろう。

ネットで検索して、何かが出てる

.netでGDI+を使用しても中はnativeのオブジェクトです。そのnativeのオブジェクトはガベージコレクションの規制外です。多くのメモリを使用したことはガベージコレクションシステムに知らない。メモリ不足してもガベージコレクションシステムは「もう仕事の時間だ」とか意識してなかった。

結論:
こんなオブジェクトを使用する時、C#でusingを使って/C++でdeleteを使ってDispose関数を呼ぶ必要があります。ガベージコレクション機能は頼ら過ぎると悲劇は起こった(おい

正しいかどうかわからないだけどね。
でもdelete使ったらOutOfMemoryExceptionは起こらなくなった。

・・・(日本語力は不足だ。こんなブログの書きとかすごく難しい・・・しかも正しく表れなかった気がする

昨日書いた文字列コード変換のこと・・・

mbstate_t state;
と書いたよね。それじゃだめです。
よくみると、このmbstate_tはただのintです。
構造関数がありません。
ローカル変数なので初期化しなければならない。
おかしい変換結果になるよ。
だから
mbstate_t state = 0;
と書けばいい。デフォルトの値はどこで定義するのはまだわからないなので0に決まる・・・