env
以前に目を付けていた『JavaScriptふりがなプログラミング』なんてのを読んだ。
どういう環境でやるんかと思ったら、Chrome + Atom(editor) だった。Atomってnode.jsのアプリらしいんで、全部Javascript関連だな。Chromeだと、F12キーでdebugツールが立ち上がり、その中でconsoleを選べばreplが使える。
けど、打ち間違いが有ると、再度入力しないといけないので、Atomで書いておいて、コピペしろとな。F12で起動してみたら、補完も効くし便利じゃん。
変数ans 入れろ 数値10 ans = 10; 数値10を変数ansに入れろ。(読み下し文)
こういう塩梅で解説が進んでいく。それぞれの章の終わりに、エラーメッセージを読み解くっている説明が有り、色々なエラーメッセージの解説が出てた。これ、なかなか良いね。
関数定義の方法が、最近はアロー方式になってるとか。
let mustPay = (kakaku) => { return kakaku * 1.08; };
こういうアローって、最近のrubyでも採用してたかな。縁遠くなっちゃたんでたまにはフォローしておくか。
Web回りの技術については、下記がお勧めとの事。
確かに、よく整理されてて、ここだけで全てが賄えそう。
それにしてもブラウザー付属のrepl環境って、揃いもそろって、文字が細かいな。年寄りには見えないぞ。見せるのが商売のアプリにしては、フォントサイズが変更出来ないみたいだ。やっぱり、ハズキルーペを買えって事? それとも、年寄りはJavaScriptなんてのに手を出すなって事? どうやら、オイラーの結論は後者であります。
行の連結方法
突然だけど、行を連結するには、どんな方法が有るだろう。行を連結するとは、単純に言えば、改行を削除すれば良い。色々あるので、改行を1個のスペースに置き換える事にしよう。 調べてみたら(そんなの調べるんか? 今までの経験はどうした)
sakae@fb:/tmp % seq 10 >NUM sakae@fb:/tmp % cat NUM | tr "\n" " " 1 2 3 4 5 6 7 8 9 10 sakae@fb:/tmp %
さすがに、これは思いついた。
sakae@fb:/tmp % cat NUM | awk '{ORS=" ";print}' 1 2 3 4 5 6 7 8 9 10 sakae@fb:/tmp %
OSRって、アウトプットセパレータの事なのね。通常は、\n になってるんで、改変しとくんだな。
sakae@fb:/tmp % cat NUM | xargs 1 2 3 4 5 6 7 8 9 10
この間、やったばかりだと言うのに、思いつかなかったぞ。短期記憶力が低下して、昔取った杵柄だけで、生きているって証拠だ。悲しい脳。
ひつこくARGMAX
前回やった数字の段で、最後にcons領域(argv,env)とatom領域(文字列エリア)が、どの辺にあるかの確認プログラムを作った。考察がまだだった。
両領域を足したものが、ARGMAX内に収まっていると期待。今回はその裏取り調査。 んなわけで、長大な引数列が必要。それで、行の連結に思いが行ったのさ。
ob6$ find /usr/src/sys -name '*.[ch]' | tr '\n' ' ' >NUM ob6$ wc NUM 0 5522 202162 NUM
カーネルソースに置いてあるソースファイル名を抽出。そいつを1行に連結。そうしておけば、 オイラー的実験プログラムの引数として、与えられるな。
ob6$ gdb -q a.out Reading symbols from a.out...done. (gdb) r `cat NUM` Starting program: /tmp/a.out `cat NUM` env: 0x7f7ffffbd758 argv: 0x7f7ffffb2ab8 argc: 0x7f7ffffb2a64 stk: 0x7f7ffffb2a63 bss: 0x5da2730105c data: 0x5da27301000 Program received signal SIGTRAP, Trace/breakpoint trap. 0x000005da271005e3 in main (argc=5523, argv=0x7f7ffffb2ab8, env=0x7f7ffffbd758) at test.c:16 16 __asm__ __volatile__("int3"); // drop gdb
gdbの中でも、任意のファイルの展開・置き換えが出来るのね。バッククォートに注目だな。
(gdb) p argc $1 = 5523 (gdb) x/a argv 0x7f7ffffb2ab8: 0x7f7ffffbd868 (gdb) x/5s 0x7f7ffffbd868 0x7f7ffffbd868: "/tmp/a.out" 0x7f7ffffbd873: "/usr/src/sys/arch/alpha/alpha/api_up1000.c" 0x7f7ffffbd89e: "/usr/src/sys/arch/alpha/alpha/autoconf.c" 0x7f7ffffbd8c7: "/usr/src/sys/arch/alpha/alpha/clock.c" 0x7f7ffffbd8ed: "/usr/src/sys/arch/alpha/alpha/clockvar.h" (gdb) x/a env 0x7f7ffffbd758: 0x7f7ffffeee25 (gdb) x/2s 0x7f7ffffeee25 0x7f7ffffeee25: "_=/tmp/a.out" 0x7f7ffffeee32: "LOGNAME=sakae"
それなりのデータが格納されてるな。
(gdb) p 0x7f7ffffeee25 - 0x7f7ffffbd868 $2 = 202173
argvとenvの文字列が格納されてる先の隔たりを表示させた。 argvのサイズは、8Byte x 5000 = 40kByteって事になるから、それに、 202173を足してみると、かつかつ範囲(262144)に収まるな。
ob6$ wc NUM 0 5300 270643 NUM ob6$ gdb -q ./a.out Reading symbols from ./a.out...done. (gdb) r `cat NUM` Starting program: /tmp/a.out `cat NUM` /bin/ksh: /tmp/a.out: Argument list too long During startup program exited with code 1. (gdb) bt No stack.
こんな風に、範囲を超える引数を与えると、プログラムの起動は拒否された。
ob6$ wc NUM 0 5150 261721 NUM ob6$ bc 5150*8 + 261721 302921
これだと、ぎりぎり起動した。文字領域は制限を超えていないけど、配列領域を足すと制限を超える。と言う事は、ARGMAXの制限ってのは、文字領域の制限って事だな。 大騒ぎした割には、得られた結果ってこれだけですか。
stack frame
int hoge(int x, int y) { return x * y; } int main(){ int aa=257, bb=1024; hoge(aa, bb); return 1; }
hoge: @ args = 0, pretend = 0, frame = 8 @ frame_needed = 1, uses_anonymous_args = 0 @ link register save eliminated. str fp, [sp, #-4]! add fp, sp, #0 sub sp, sp, #12 str r0, [fp, #-8] str r1, [fp, #-12] ldr r3, [fp, #-8] ldr r2, [fp, #-12] mul r3, r2, r3 mov r0, r3 sub sp, fp, #0 @ sp needed ldr fp, [sp], #4 bx lr main: @ args = 0, pretend = 0, frame = 8 @ frame_needed = 1, uses_anonymous_args = 0 stmfd sp!, {fp, lr} add fp, sp, #4 sub sp, sp, #8 ldr r3, .L5 str r3, [fp, #-8] mov r3, #1024 str r3, [fp, #-12] ldr r0, [fp, #-8] ldr r1, [fp, #-12] bl hoge mov r3, #1 mov r0, r3 sub sp, fp, #4 @ sp needed ldmfd sp!, {fp, pc} .L6: .align 2 .L5: .word 257
久しぶりにarmな石を引っ張り出してみた。綺麗だな。それに比べて糞石と言ったら。。。 gdbで追う時、eipとかebpとか、慣れないインテル語を使うのはいやなんで、 display/i $pc とか、 x/8x $fp を使っちゃうんだよな。
environ
環境変数って事で、printenvを調べていたんだ。そのコードの中に environってのが出て来る。ソース上は外部宣言されたものってなってる。どこのヘッダーで宣言されてる?
そんなの簡単、ヘッダーの宣言を無効にしていって、environが無効だよと言われたやつが、その正体のはず。それより先に、 /usr/includeの中をさらってみろってのがあるけどね。 やってみたけど、思わしくない。そこで、ヘッダーの無効化及びソースの縮小化をしたんだ。
ob6$ cat printenv.c //#include <stdio.h> //#include <string.h> //#include <stdlib.h> //#include <unistd.h> //#include <err.h> /* * printenv * * Bill Joy, UCB * February, 1979 */ int main(int argc, char *argv[]) { extern char **environ; char *cp, **ep; int cnt=0; for (ep = environ; *ep; ep++) cnt++; }
恐れ多くも、あのBill Joyさんのコードを傷付けちゃったけど、これでもちゃんとコンパイル出来る。さあ、どうする?
ob6$ cc printenv.c ob6$ nm a.out 00200e50 a _DYNAMIC 00200fb0 a _GLOBAL_OFFSET_TABLE_ W _Jv_RegisterClasses 00201000 A __bss_start 00201000 A __data_start 00201000 B __dso_handle 00000580 T __fini 00200e40 R __guard_local 00000350 T __init 00000410 W __register_frame_info 000003b0 T __start U _csu_finish 00201000 A _edata 00201058 A _end 000003b0 T _start U atexit U environ U exit 00000520 T main 00000000 F printenv.c
ステータスがUってなってる所は、libcに有るって事だな。
ob6$ find /usr/src/lib/libc -name '*.[ch]' | xargs grep -l environ /usr/src/lib/libc/asr/asr.c /usr/src/lib/libc/dlfcn/init.c /usr/src/lib/libc/gen/auth_subr.c /usr/src/lib/libc/gen/exec.c /usr/src/lib/libc/gen/login_cap.c /usr/src/lib/libc/gen/posix_spawn.c /usr/src/lib/libc/hidden/stdlib.h /usr/src/lib/libc/locale/_get_locname.c /usr/src/lib/libc/stdlib/getenv.c /usr/src/lib/libc/stdlib/setenv.c /usr/src/lib/libc/stdlib/system.c /usr/src/lib/libc/thread/rthread.c /usr/src/lib/libc/time/localtime.c /usr/src/lib/libc/time/tzfile.h
この中から探せととな。
ob6$ grep environ /usr/src/lib/libc/dlfcn/init.c * In dynamicly linked binaries environ and __progname are overriden by * the definitions in ld.so. char **environ __attribute__((weak)) = NULL; * b) init __progname, environ, and the TIB in static links. return &environ; * __progname and environ environ = envp;
どうも、これっぽい。atexit なんてのもうろうろしてる。ld.soも一枚噛んでいるみたいだし、これ以上の深入りは徒労に終わりそうだから、撤退します。
env
次はenvだ。manしたら、環境を全部クリアーした状態にも出来るのね。知らんかった。どうやってるか見ておく。
case '-': /* obsolete */ case 'i': if ((environ = calloc(1, sizeof(char *))) == NULL) err(126, "calloc"); break;
environって上で悩んだやつ。ここにも顔を出している。ポインターをちょいと書き換えて、無きものにしてる。世界の汚れた環境(温暖化とか放射能まみれ等)を、簡単に浄化出来ればいいのにね。そうじゃないと、早晩人類は滅びるぞ。まあ、諸行無常だわさ。
gdbに喰わせたのは、HOGE=fugaって設定。
_libc_setenv (name=0x7f7ffffd52a6 "HOGE", value=0x7f7ffffd52ab "fuga", rewrite=1 ) at /usr/src/lib/libc/stdlib/setenv.c:95
=を目印にして、引数をstrchr(*argv, '=')で分割して、飛んできた。 そして、その中で、ポインターの場所を一つ確保し、
143│ if (!(environ[offset] = /* name + `=' + value */ 144├───────────> malloc((int)(np - name) + l_value + 2))) 145│ return (-1); 146│ for (C = environ[offset]; (*C = *name++) && *C != '='; ++C) 147│ ; 148│ for (*C++ = '='; (*C++ = *value++); ) 149│ ; 150│ return (0);
ポインターを書き込んでから、文字列領域を埋めている。既存の変数の値を置き替える時、場所の調整に苦労してる。
それはそうと、それぞれの関数(putenv,setenv,unsetenv)の後ろに、例えば、DEF_WEAK(setenv); が置いてあるんだけど、これは何?
cpp ?
大文字になってるんで、マクロの類なんだなと当たりを付ける。きっとライブラリィー特有の事情でも有るかも。
ob6$ pwd /usr/src/lib ob6$ find . -type f | xargs grep DEF_WEAK| grep define /usr/src/lib/libc/include/namespace.h:#define DEF_WEAK(x) __weak_alias(x, HIDDEN(x))
DEF_WEAKだけだと、候補が多すぎたので、defineで絞ってみたら、それらしいのが引っかかってきた。
* DEF_WEAK(x) Symbols used internally and not in ISO C * This defines x as a weak alias for _libc_x * Matches with PROTO_NORMAL() * ex: DEF_WEAK(lseek) #define DEF_WEAK(x) __weak_alias(x, HIDDEN(x))
このnamespace.hは、libcをコンパイルするMalefileで、ひっそりとCFLAGSに宣言されてた。 リンカ・ローダ実践開発テクニックよると、この宣言があると、同名の関数と喧嘩した時に、負ける事を運命付けられて いるとの事。逆にSTRONGなんてのも定義されてたんで、設計者の趣くままにだな。
ちょっと、実験してみる。
qob$ cat lib.c #include <stdio.h> __attribute__((weak)) void mine(void) { printf("lib side!\n"); }
libに見立てたコード。特徴的なアトリビュートでweakを設定してる。
qob$ cat main.c #include <stdio.h> void mine(void); /* same as lib.h */ // void mine(void) { // printf("main side!\n"); // } int main(void) { mine(); return 0; }
こちらは、ユーザーアプリを想定。初期値として、mineは殺してある。
qob$ cc main.c lib.c qob$ ./a.out lib side!
これで、実行すると、ライブラリィー側のmineが使われる。
qob$ vi main.c : qob$ cc main.c lib.c qob$ ./a.out main side!
今度は、main側のコメントを削除。同名のmineがmain側とライブラリィー側に存在する事に なるけど、ライブラリィー側が弱いので、main側が使われた。
qob$ vi lib.c : qob$ cc main.c lib.c /tmp/lib-39139b.o: In function `mine': lib.c:(.text+0x0): multiple definition of `mine' /tmp/main-5ad3de.o:main.c:(.text+0x0): first defined here cc: error: linker command failed with exit code 1 (use -v to see invocation) qob$ cc lib.c main.c /tmp/main-375fd6.o: In function `mine': main.c:(.text+0x0): multiple definition of `mine' /tmp/lib-8c4079.o:lib.c:(.text+0x0): first defined here cc: error: linker command failed with exit code 1 (use -v to see invocation)
ライブラリィー側のアトリビュートをコメントにする。そうすると、mineの強さは同一になるんで、喧嘩になる。喧嘩両成敗で、コンパイルは失敗した。
これで分かるように、ライブラリィー側にweakが付いている関数は、メイン側から上書き出来る。ライブラリィー側に何か問題が有った時とか、動作を変えたい時にライブラリィー側を変更する事なく対処出来るので便利。
OpenBSDでは、このweakアトリビュートと、絶対変更するなっていうstrongが付いたものと、使い分けてる。これらを把握しておくと便利。
実は、setenvの動作をgdbで追う時、ライブラリィー側のそれを使うと、オプチマイズされてて、所々の変数が確認出来なかった。で、手元のenv.cにコピペしてコンパイルしたら、オプチマイズが外れて、変数がちゃんと見えた。正にオイラーの為の機能だなあ。(なんか、使い方が邪道っぽいけど、イイノダー)
NetBSDでも確認
リンカーローダー本を見ていると、OpenBSDは出てこないでNetBSDが出て来る。記念にそれで確認してみるぞ。
nb8$ cc -c main.c nb8$ cc -c lib.c nb8$ nm main.o 0000000000000000 T main U mine nb8$ nm lib.o 0000000000000000 W mine U puts
nb8$ nm libc.so.12.207 | fgrep ' W ' | grep env 0000000000100902 W getenv_r 0000000000073a02 W putenv 00000000000824d9 W setenv 0000000000073997 W unsetenv
伝統なのかしら?
調子こいて、FreeBSDやらうぶはどうなってるか確認したら、哀れストリップされてた。OS開発のポリシーを垣間見た思いですよ。
etc
Raspberry Pi用バイナリをQEMUエミュレータで動かす方法(ユーザーモードエミュレーション)
Raspberry Pi のセルフビルド環境を QEMU で作る
WSLの機構と一緒だな。arm用のカーネルが無くても、それの代わりをするqemu-arm-staticが働いてる。具体的には、armのアプリが発行するシステムコールをx86サイドのlinuxが肩代わり。そうする事により、armな石のエミュレーションが著しく簡単になり、実行スピードが上がるとな。