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な石のエミュレーションが著しく簡単になり、実行スピードが上がるとな。