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回りの技術については、下記がお勧めとの事。

MDN web docs

確かに、よく整理されてて、ここだけで全てが賄えそう。

それにしてもブラウザー付属の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

スタックフレーム

コールスタック(スタックフレーム)の仕組みを復習する

Schemeの実装におけるスタックフレーム

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

sedで各種コマンドを作ってみよう