mmap

社会的圧力

最近、義姉さんの所に孫が誕生したそうだ。それにつられて、今まで使っていたガラケーをスマホに変えたとか。言わずと知れた、おばあちゃんをしたいのだな。孫の成長をビデオ撮りしたい。その一点ですよ。

スマホ初心者ゆえ、買った所で主催してる無料のスマホ教室に通っているそうだ。どんな事を教えてくれるの?って聞いたら、電話の扱い、LINE、メール、ビデオ系の扱い、QRコードで支払いとかのてんこ盛り。初PayPayをやってみたって言ってたけど、そんなハイカラな店、ここら辺に有ったかなあ?

孫の動画が息子から送られて来たってんで、見せてもらった。これってメール添付なの? 容量大きいだろうに、制限に引っかからなかったのかな? それとも、息子が何処かのサーバーにUPして、そのURLだけが送られて来た? これだと確かに容量制限は無いな。でも、何度も見ると、ギガ足りない、お金足りない状態になるぞ。スマホ屋の思う壺。

オイラーは、ガラパゴス。全くスマホを使う必要性を感じない。だから、上記のギガ足りない?のも、想像の上での話だ。第三者の正確な知識を教えてくださいな。

大体、あんなチンケな画面に目を凝らすなんて無理。行動範囲が決まっているので、外でスマホを使う必要性が全く無い。それに、聞けばほぼ毎日充電が必要らしい。それだけ電池がヘタるのが早いんだな。電池交換、自分で出来ない。交換修理に出すと、

信じて預けたスマホちゃんがぶっ壊れて帰って来た話

こんな事が起こるらしい。よって、ガラケーで十分と言う結論なんだけど。。。

何でも、マイナンバーの普及率が低いので、QRコード付きの推進案内を全家庭に配布するとか。 政府も、スマホを持ってる事をデフォと認識してるんだな。

更に、最近車検の予約案内が届いたんだけど、QRコードしか表示されていない。もう、よってたかっての社会的圧力がかかっていますよ。

スマホの通話代が高いって、総務省が3大メーカーに圧力をかけたら、AUの社長だから、政府が口出す事項では無いと啖呵を切った。そんなに抵抗してると、意地悪されるぞ。お前の所は、新しい電波帯の免許おろさないからねってね。権限巨大。

実験計画法

前回SEGVの最後の所で、疑問を上げておいた。sbclでネイティブコードになったのがどうやって実行出来るの? nprologでも、オブジェクトファイルを作っておいて、後で動的に読み込んで実行出来るけど、その仕組みは?

もっと一般化すれば、みんな大好きなブラウザー、人によっては山ほど拡張機能を入れてる人がいる。その拡張だけど、javascriptの場合はいいとして、中にはバイナリーになってる奴もあるはず。どういう仕組みで実行される? 夜も眠れないので、謎解きに挑戦してみたい。

まずは作戦の立案だな。難しく言うと実験計画法。統計学の父フィッシャー先生が編み出した。 彼の本業は、農業関係。色々な作物の交配やら、生産性の向上を研究してた。色々な条件で試験する訳だ。肥料を変えたり、水やりの方法を変えたり。。。基本的に一年に一度しか実験出来ないから、失敗の無いように計画しなければならない。そして、結果をまとめる時、誰もが納得する方法にしなければならない。そんな事情で、統計をやらざるを得ずって訳だ。

nprologで採用されてる、dlopenとかの挙動を観察すればいいんでないかい。観察方法は? リナだとstraceあたりだろうけど、オイラーはOpenBSDな小僧。ktraceを使ってみようか。 そうすれば、余す所なくシステムコールをキャッチ出来るはずだ。

まて、nprologじゃ、とてつもないログが収集されちゃって大変だろう。苦労をしょい込むのは御免だ。

現役時代に新人の指導をした事が有る。新人と言っても院生みたいなりっぱな学力の連中。 回路を設計させて、それを組み立て、実機でデバッグ。デバッグのお供はgdbじゃなくて、電気の波形を観測出来る、オシロスコープ。

漫然と回路にプローブを当てて波形を見ている。波形を見易いようにつまみを調整するとか、ここはこういう波形になるはずだって言う目的を持って、観測出来ていない。

なるべく、見たくない不要な部分を減らし、観測したい部分だけを浮かび上がるのも大事な技術のはずだ。そんなのが身に染み込んでいるので、nprolog観測は敬遠したのさ。

何か、dlopenを使う簡単な例は無いかな?

dlopenで共有ライブラリを使ってみる

これが、真っ当な例だろう。他に無いかな?

Program Library HOWTO

こちらは、dlopenのmanに載ってた例らしい。

実験 1 at OpenBSD

上記のmanに出てたのを、OpenBSD用に少し改変。

#include <stdlib.h>
#include <unistd.h>   // for sleep
#include <stdio.h>
#include <dlfcn.h>

int main(int argc, char **argv) {
        void           *handle;
        double          (*func) (double);
        char           *error;

        sleep(1);

        handle = dlopen("/usr/lib/libm.so.10.1", RTLD_LAZY);
        if (!handle) {
                fputs(dlerror(), stderr);
                exit(1);
        }
        func = dlsym(handle, "sqrt");
        if ((error = dlerror()) != NULL) {
                fputs(error, stderr);
                exit(1);
        }
        //      printf("%f\n", (*func) (2.0));
        dlclose(handle);
}

sleepを入れてるのは、起動時のごちゃごちゃした部分と実験部分を分離する為。更に結果を出さないようにしたのは、余計なログが残らないようにする為(勿論、結果が正しい事を確認の上です)。

vbox$ cc z.c
vbox$ ktrace -tc ./a.out

mathライブラリーを使っているのにリンクしてません。勿論 -ldl もOpenBSDゆえ不要。そしてトレースを取得。

vbox$ kdump
   :
 99544 a.out    CALL  nanosleep(0xcf7eaa48,0xcf7eaa38)
   :
 99544 a.out    CALL  open(0x79817b51,0x10000<O_RDONLY|O_CLOEXEC>)
 99544 a.out    RET   open 3
   :
 99544 a.out    CALL  read(3,0xcf7e99c8,0x1000)
 99544 a.out    RET   read 4096/0x1000
 99544 a.out    CALL  mquery(0,0x9000,0x5<PROT_READ|PROT_EXEC>,0x2<MAP_PRIVATE>,3,0)
 99544 a.out    RET   mquery 251351040/0xefb5000
 99544 a.out    CALL  mmap(0xefb5000,0x9000,0x1<PROT_READ>,0x812<MAP_PRIVATE|MAP_FIXED|__MAP_NOREPLACE>,3,0)
 99544 a.out    RET   mmap 251351040/0xefb5000
 99544 a.out    CALL  mmap(0xefbe000,0x20000,0x5<PROT_READ|PROT_EXEC>,0x812<MAP_PRIVATE|MAP_FIXED|__MAP_NOREPLACE>,3,0x8000)
 99544 a.out    RET   mmap 251387904/0xefbe000
 99544 a.out    CALL  mmap(0x2efb5000,0x1000,0x3<PROT_READ|PROT_WRITE>,0x812<MAP_PRIVATE|MAP_FIXED|__MAP_NOREPLACE>,3,0x28000)
 99544 a.out    RET   mmap 788221952/0x2efb5000
 99544 a.out    CALL  mmap(0x2efb6000,0x1000,0x3<PROT_READ|PROT_WRITE>,0x812<MAP_PRIVATE|MAP_FIXED|__MAP_NOREPLACE>,3,0x28000)
 99544 a.out    RET   mmap 788226048/0x2efb6000
 99544 a.out    CALL  close(3)
    :

まだまだ続くんだけど、どれがどのCALLに対応してるのか分からない。そこでマーカーとして、各所にsleepを埋め込む事にした。更に目障りなエラーチェックを削除。代わりに演算結果をreturnで返すと言う無茶をする。結果の確認は、実行が終了したら即、echo $? で、行う事。

昔ISO 9001の取得の時に、教え込まれたPDCA(Plan Do Check Action)を思い出しての実践だ。

#include <stdlib.h>
#include <unistd.h>   // for sleep
#include <stdio.h>
#include <dlfcn.h>

int main(int argc, char **argv) {
        void           *handle;
        double          (*func)(double), fo;

        sleep(0);
        handle = dlopen("/usr/lib/libm.so.10.1", RTLD_LAZY);
        sleep(0);
        func = dlsym(handle, "sqrt");
        sleep(0);
        fo = (*func)(64.0);
        sleep(0);
        dlclose(handle);
	sleep(0);
        return ((int)fo);
}

これが、改善済のコードだ。余裕で24行しか表示出来ない、昔のCRT画面に収まった。コードを小さくするのも、技術のうちだ。

実験 2 at FreeBSD

暫くFreeBSDをほったらかしにしてたんで、火を入れてみた(ああ、ついついハム語が出た。無線機を動かすと真空管が光るので、こう呼ぶ)。

結果だけを見易く編集して掲載。

CALL  openat(AT_FDCWD,0x80022c100,0x300000<O_RDONLY|O_CLOEXEC|O_VERIFY>)
CALL  fstat(0x3,0x7fffffffd710)
CALL  mmap(0,0x1000,0x1<PROT_READ>,0x40002<MAP_PRIVATE|MAP_PREFAULT_READ>,0x3,0)
CALL  mmap(0,0x32000,0<PROT_NONE>,0x2000<MAP_GUARD>,0xffffffff,0)
CALL  mmap(0x80064b000,0x11000,0x1<PROT_READ>,0x60012<MAP_PRIVATE|MAP_FIXED|MAP_NOCORE|MAP_PREFAULT_READ>,0x3,0)
CALL  mmap(0x80065c000,0x1f000,0x5<PROT_READ|PROT_EXEC>,0x60012<MAP_PRIVATE|MAP_FIXED|MAP_NOCORE|MAP_PREFAULT_READ>,0x3,0x10000)
CALL  mmap(0x80067b000,0x1000,0x3<PROT_READ|PROT_WRITE>,0x40012<MAP_PRIVATE|MAP_FIXED|MAP_PREFAULT_READ>,0x3,0x2e000)
CALL  mmap(0x80067c000,0x1000,0x3<PROT_READ|PROT_WRITE>,0x40012<MAP_PRIVATE|MAP_FIXED|MAP_PREFAULT_READ>,0x3,0x2e000)
CALL  munmap(0x80064a000,0x1000)
CALL  close(0x3)

ほとんどが、dlopenに集中してた。mmapが主役なんですねぇ。

折角火が入っているので、nprologでコンパイルした、queens.oを、起動時にロードする所を、ktraceでキャプチャーしてみた。openが多数出て来るけど多分最後のopenがdlopen対応だろう。

CALL  openat(AT_FDCWD,0x800286000,0x300000<O_RDONLY|O_CLOEXEC|O_VERIFY>)
CALL  fstat(0x4,0x7fffffffd160)
CALL  mmap(0,0x1000,0x1<PROT_READ>,0x40002<MAP_PRIVATE|MAP_PREFAULT_READ>,0x4,0)
CALL  mmap(0,0x6000,0<PROT_NONE>,0x2000<MAP_GUARD>,0xffffffff,0)
CALL  mmap(0x8006d0000,0x1000,0x1<PROT_READ>,0x60012<MAP_PRIVATE|MAP_FIXED|MAP_NOCORE|MAP_PREFAULT_READ>,0x4,0)
CALL  mmap(0x8006d1000,0x3000,0x5<PROT_READ|PROT_EXEC>,0x60012<MAP_PRIVATE|MAP_FIXED|MAP_NOCORE|MAP_PREFAULT_READ>,0x4,0)
CALL  mmap(0x8006d4000,0x1000,0x3<PROT_READ|PROT_WRITE>,0x40012<MAP_PRIVATE|MAP_FIXED|MAP_PREFAULT_READ>,0x4,0x2000)
CALL  mmap(0x8006d5000,0x1000,0x3<PROT_READ|PROT_WRITE>,0x40012<MAP_PRIVATE|MAP_FIXED|MAP_PREFAULT_READ>,0x4,0x2000)
CALL  munmap(0x8006cf000,0x1000)
CALL  close(0x4)

このログで、ちょいと引っかかる所が有るな。要再検証。

手探りってのは、実験の信頼性を低下させる。そんな結果を使って書いた論文は却下されるぞ。

実験 3 at debian

あるscheme好きな先生のHPを見てたら、schemeが読めない人のためにと銘打って、流行りのpython語も書いてあった。オイラーも見習って、世間にはこびっているlinuxで一応確認してみる。

sakae@pen:/tmp$ strace  -o LOG ./a.out

リナの場合、コンパイル時に、-ldl を面倒くさいけど付ける事(あんたは、遅れてるーーー)。

ログに落としておいて、後からじっくり見るのが良いだろう。実験コードは上で挙げた、ひたすらsleepを入れて区分けしたやつ。straceは、-tt オプションを付けると、実行時の時刻をマイクロ秒まで表示するなんて言うサービス精神が有るから、覚えておくと役にたつかも。多数なオプションは良い事なのか、悪い事なのかは、人それぞれで判断が違うから、何も言うまい。

下記は実行結果の主要部分

nanosleep({tv_sec=0, tv_nsec=0}, 0x7ffc342e2550) = 0
brk(NULL)                               = 0x556d143be000
brk(0x556d143df000)                     = 0x556d143df000
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\322"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=1579448, ...}) = 0
mmap(NULL, 1581384, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f23685a9000
mmap(0x7f23685b6000, 651264, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xd000) = 0x7f23685b6000
mmap(0x7f2368655000, 872448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3,0xac000) = 0x7f2368655000
mmap(0x7f236872a000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x180000) = 0x7f236872a000
close(3)                                = 0
mprotect(0x7f236872a000, 4096, PROT_READ) = 0
nanosleep({tv_sec=0, tv_nsec=0}, 0x7ffc342e2550) = 0
nanosleep({tv_sec=0, tv_nsec=0}, 0x7ffc342e2550) = 0
nanosleep({tv_sec=0, tv_nsec=0}, 0x7ffc342e2550) = 0
munmap(0x7f23685a9000, 1581384)         = 0
nanosleep({tv_sec=0, tv_nsec=0}, 0x7ffc342e2550) = 0
exit_group(8)                           = ?
+++ exited with 8 +++

後は、 MMAPなんて言う日本語man が有るから、首ったけになればいいんだな。

要約するとファイルの任意の部分をメモリーに張り付けてしまいましょうという事だな。気になるのは、ファイルのパーミション。

たまたまnprologで作ったqueens.oが有るので、確認してみた。

sakae@pen:~/nprolog$ ls -l queens.o
-rwxr-xr-x 1 sakae sakae 20608 Nov 28 15:57 queens.o
sakae@pen:~/nprolog$ chmod 444 queens.o
sakae@pen:~/nprolog$ ls -l queens.o
-r--r--r-- 1 sakae sakae 20608 Nov 28 15:57 queens.o

コンパイル直後は、実行パーミションがONになってる。これを、強制的に読み込みだけに変更。 これで、機能するか?

ちゃんと動いたよ。そんなものなのか。

セカンドオピニオンでOpenBSDのmanにも当たっておく。

SEE ALSO
     madvise(2), mlock(2), mprotect(2), mquery(2), msync(2), munmap(2),
     getpagesize(3), core(5)

HISTORY
     A fully functional mmap() system call first appeared in SunOS 4.0 and has
     been available since 4.3BSD-Net/2.

CAVEATS
     IEEE Std 1003.1-2008 (“POSIX.1”) specifies that references to pages
     beyond the end of a mapped object shall generate a SIGBUS signal;
     however, on some architectures OpenBSD generates a SIGSEGV signal in this
     case instead.

ふーん、SunOSか、懐かしいな。標準ではある種の使い方間違いをするとSIGBUSになるけど、OpenBSDは、SIGSEGVになるとな。

そうそう、リナとかFreeBSDでは、カーネルの機構にモジュールが積極的に取り入れられている。言わばdlopenのカーネル版だ。新しいNICを増設したんだけど、カーネルが対応していない。そんな時は、対応するドライバーをひょいとmodprobeとかを使って追加出来る。便利である。

OpenBSDは、こういう機構を一切用意していない。頑固一徹なセキュリティ原理主義が根本にある訳だ。大体、どこの馬の骨とも知れない物を、カーネルに追加するなんて、もっての他って事だ。追加しようとしたモジュールがウィルスに汚染されてたら、それを防ぐ手段は無い。トロイの木馬状態は原理的に出来ないようにしておこう。それが正しい道だ。潔い。

再検証

FreeBSDでのログを見て、疑問が有ったんだ。それは、ファイルディスクリプタに4が使われている事。

ファイルディスクリプタは、0,1,2が標準入出力とエラーに割り当てられている。だから、次にファイルをオープンすると、3番が割り当てられる。使い終わったら、普通は即ファイルを閉じる。だから、3番が空いてて、queens.oも、3番を使って欲しい所。それが、何故か4番を使ってる。すなわち、何かのファイルが開かれて、それがそのまま3番を占有してたって事になる。

細かい事だけど、神は細部に宿るって言葉もあるからね。ひょっとしたらノーベル賞級の大発見につながるかも。そう言えば、今年のノーベル賞受賞者は晩餐会にありつけないようですね。ご愁傷様です。

sakae@pen:~/nprolog$ strace -o LOG npl -c queens.o
N-Prolog Ver 1.52
?- halt.

まずは、ログの収集。次はopenとcloseがセットになってるか確認。こういのは、眼grep禁止です。冷徹なgrepにやらせましょう。

sakae@pen:~/nprolog$ egrep '(open|close)' LOG
 :
openat(AT_FDCWD, "queens.o", O_RDONLY)  = 3
close(3)                                = 0
openat(AT_FDCWD, "queens.o", O_RDONLY)  = 3
openat(AT_FDCWD, "./queens.o", O_RDONLY|O_CLOEXEC) = 4
close(4)                                = 0

コマンドラインからの指示通りに、queens.oを開いてみた。何故か一度閉じてから、再オープン。それから、自dirにあるやつだよと再指定。多分それでちゃんと読めて処理出来たんでしょうね。

謝罪

ここまで、拙い文章を読んでくれてありがとう。

実は、この調査に取り掛かる時、ggして、次のような答えを得ていたんだ。

Linuxでライブラリをロードするために使用されるシステムコールは何ですか?

でも、これで納得してしまっては、ぐぐるの弊害をもろに受けてるなと思ったのさ。

偶然にも、『中高生の悩みを理系センスで解決する』なんて本を読んでいたんだ。 その中に、お歳を召した数学の教授が余暇の楽しみで、1000個を超える公式集が 載った本から、自分流の証明の仕方にチャレンジしてるって話が出てた。

数学の楽しみ方って、そういう方向も有りかと、甚く感心した。ならば、オイラーも それに似た事をやってみようと思った次第。楽しいのが何よりだ。


This year's Index

Home