貧乏人のラズパイ(もどき) (2)

『図書館のプロが教える 調べるコツ』なんて本を読んでみた。日本の図書の 分類法NDCによると、000の総記の次、010が図書館学ってなってる。ここにこの 本が分類されてる訳ね。ラベルを見ると015ってなってたから、確かにそうだ。

図書館には、知識のコンシェルジェ、司書の方がいるそうな。皆様の疑問を 図書館の本を駆使して調べて差し上げますって、有り難い知識の使者。

この方達が、探し方のコツを惜しげもなく、実例を交えて教えてくれている。 なに、そんな司書さんに相談しなくても、ググレば一発だよ。

ネットの情報は、うそや伝聞も混じっていて、得られた情報が本当に正しいか 検証が大変との事。勿論、彼(彼女)らは、ぐぐる事もあるけど、それは あくまで補助手段。検索の枠を広めたり、手がかりを得るために使うのだと言う。

じゃ、司書の方は何を手がかりに探すのか? それは書籍をDBにしたものを 多用するとの事。OPACと言う。思わず、 OPAC - Wikipediaを引いちゃったぞ。

これで当りを付けるとな。後は、図書館に置いてある百科事典で、当りを 付けたり、検索の広がりを得るそうだ。

なんだか、これ、Wikipediaで済みそうな感じがしないでもない。ああ、司書 さんの知恵として、児童向けの本がかかせないとか。

こんな例が挙げられていた。魚のひれは縦になってるのに、いるかさんのひれは 横になってるのは何故ですか?

小供電話相談室によく寄せられそうな質問だな。こういう質問にも丁寧に調べて、 答えが載っている本を示してくれるそうだ。

司書さんは、どうやって調べたか? 読んだけど、忘れたので、自前がぐぐる。

いるか 泳ぎ ひれ の検索ワードで、 イルカのひれとその役割 に到達。そうか、ただのひれより、尾びれって指定して絞った方がいいか。

検索ワードに いるか 尾びれ って入れたら、進化ってのも一緒に検索すると いいよと、ぐぐる司書が言う。で、到達したのが、 イルカはなぜ尾(お)を上下(うえした)に?魚(サメ等も)の尾びれは縦になっているのにイル...。どうやらこれが正解っぽい。やっぱり、小供の 鋭い疑問だな。

最近読んだ、ニュートン誌には、手足の進化って記事が有った。捕食者、被捕食者って 言う立場の違いが、進化の最大要因説。馬が早く走るために、足を軽くする戦略。 その為、指が一本になった。競馬は、進化の進み具合に賭けるのね。

地中に住む生物は、穴を掘るシャベルとしての手もしくは鼻を発達させる。手足なんて 邪魔だから退化させちゃえってのがへび。なかなな面白い。

で、司書の方がどういう風に調べたか? まずは百科事典で調べて答えが見つかれば もうけもの。日本大百科全書にあたる。いるか、泳ぐ、くじらにいるかの説明発見。 回答に繋がる答え無し。でも、いるかとくじら動物学的に差は無い。両者の区別は、 身長が4mより大きいか小さいかだけとの事。これは思わぬトリビア。

次は分類番号480の動物学の棚をブラウジング。くじら・いるか大百科を手にする、 とあるページに、哺乳類の仲間の疾走方法、体を上下にしならせて走る、馬や チーターの走りを継承したんではってのに出会う。何百ページから1ページの 記述を素早く見つけるとは、さすが!

くじらと日本人って本にも当る。尾ひれを水平に伸ばして、早く水面に浮上出来る ようにするって記述を発見。魚との違いには言及無し。

児童書にも当る。いるかと泳ぎたい本を発見。そこにずばりの回答を発見。 こちらには、えさを探して深く潜ったり、空気を吸いに水面に戻ったりするには 尾びれが横向きに付いていて、これを上下に振る方が楽との事。

これらをまとめると、2つの説になった。哺乳類の上下に体をしならせる動きを 受けついだ説、水面に出たり、潜ったりするのが容易説。

そんなのいるかさんに直接聞いてみるしかないんでないかい。いるか兵器に 重用されるぐらい頭が良いそうですから、そのうちに人間とコミュニケーション 出来るかも。

調べ方の基本パターンは、百科事典に当って、全体像を把握。関連する分類 番号の棚で、本をブラウジング。あるいは、OPACって言う本に特化したぐぐる 対抗検索ツールを当る。ぐぐるにあたるってのがセオリーみたいだ。

インターネット上なら、まずはWikiで全体像を把握ってのが良い方法。 オイラーも既に実施中。って、司書になるるか? 豊富な検索ワードを紡ぎ出す 想像力が一番大切な気がするぞ。

gdbを新しいのにする

前回は、『ハロー Hello,World』本の勧めにより、armのシュミレーション環境を 作った。これって、Linux界隈で言うqemu-user-static相当の機能を持った物だな。

で、一つ不満が有る。作られたgdbがちと古いんだ。同本でもgdbは新しいので 実験しましたって言ってたので、OpenBSDのarmで採用してた、7.9.1をソースで 持ってきて、FreeBSDでコンパイルしてみた。無事にgdbとrunが出来上がったのは いいんだけど、gdbtuiが無い。何か特別なオプションが要るのかな?

  $ ../../../toolchain/gdb-7.3.1/configure --target=arm-elf --prefix=/usr/local/cross2 --disable-werror --disable-nls

config.logを見る限りでは、特別なパラメータを指定してるようには見えない。 そうすると、gdb-7.3.1に当てているpatchが効いているのかな。気が向いたら やってみよう。なにせ、CPUがフルスピードで13分も動く世界ですから。老体に 鞭打つ仕打ちになりそう。

新たに作ったrunコマンドが、何処から実行されるか確認。これって、armの シュミレータの動きを調べている事になるな。

$ /usr/local/bin/gdb -q run
Reading symbols from run...done.
(gdb) b main
Breakpoint 1 at 0x804a160: file ./../common/run.c, line 85.
(gdb) r a.out
Starting program: /usr/local/cross2/bin/run a.out

Breakpoint 1, main (ac=2, av=0xbfbfe820) at ./../common/run.c:85
85      {
(gdb) n
101       myname = av[0] + strlen (av[0]);
(gdb)

arm-elf-run で、トレース

ああ、元々あるgdb7.3.1由来のやつは、トレースで面白い事が出来ないので、 7.9.1で作ったrun(と名前を変えた)で、前回のhello.cを走らせてみる。

$ run -d -t a.out > TR.log 2>&1
HOGEFUGA
$ wc TR.log
    3881   22597  109314 TR.log

トレース結果がザーと流れてしまうので、一応ログに落とした。主体はstderrに 出て来るので、それもログに混ぜこぜした。

$ grep -B 5 svc TR.log
pc: 8110,  movs r0, #22
pc: 8114,  add  r1, pc, #272    ; 0x110
pc: 8118,  svc  0x00123456
--
pc: a0ac,  mov  r4, #1
pc: a0b0,  str  sl, [sp, #4]
pc: a0b4,  mov  r6, sp
pc: a0b8,  mov  r0, r4
pc: a0bc,  mov  r1, sp
pc: a0c0,  svc  0x00123456
 :
pc: a160,  mov  r4, #19
pc: a164,  mov  r6, r0
pc: a168,  mov  r5, #0
pc: a16c,  mov  r0, r4
pc: a170,  mov  r1, r5
pc: a174,  svc  0x00123456
--
pc: a568,  beq  0x00000024
pc: a56c,  add  r5, r6, #38     ; 0x26
pc: a570,  mov  r4, #24
pc: a574,  mov  r0, r4
pc: a578,  mov  r1, r5
pc: a57c,  svc  0x00123456

システムコールは、svcで呼び出す約束。r0にコール番号、r1,2に引数を入れる って言う規約になってる。

行儀よく、svcの手前5命令以内の所で、r0の値が確定してるな。例だと、#22,#1,#19,#24 ぐらいのシステムコール番号(シャープ接頭語は10進数)が見える。

objdump -S a.out したやつから、最初のシステムコール場所を特定してみる。 8110: で、アドレスを頼りに探すと、

#ifdef ARM_RDI_MONITOR
        /*  Issue Angel SWI to read stack info.  */
        movs    r0, #AngelSWI_Reason_HeapInfo
    8110:       e3b00016        movs    r0, #22
        adr     r1, .LC0        /*  Point at ptr to 4 words to receive data.  */
    8114:       e28f1e11        add     r1, pc, #272    ; 0x110
#elif defined(__thumb2__)
        /*  We are in thumb mode for startup on armv7 architectures.  */
        AngelSWIAsm     AngelSWI
#else
        /*  We are always in ARM mode for startup on pre armv7 archs.  */
        AngelSWIAsm     AngelSWI_ARM
    8118:       ef123456        svc     0x00123456

こんなのが引っかかってきた。ifdefで囲まれていて、ちょっとごちゃごちゃしてるけど、 ARM_RDI_MONITORって部類で、縮小モード(thumb2)じゃない、ゆったりモードで コンパイルされてる事が分かる。

static inline int
do_AngelSWI (int reason, void * arg)
{
    a0ac:       e3a04001        mov     r4, #1
#ifdef ARM_RDI_MONITOR
  int volatile block[3];

  block[0] = (int) ":tt";
  block[2] = 3;     /* length of filename */
  block[1] = 0;     /* mode "r" */
    a0b0:       e58da004        str     sl, [sp, #4]

#ifdef ARM_RDI_MONITOR

static inline int
do_AngelSWI (int reason, void * arg)
{
    a0b4:       e1a0600d        mov     r6, sp
  int value;
  asm volatile ("mov r0, %1; mov r1, %2; " AngelSWIInsn " %a3; mov %0, r0"
    a0b8:       e1a00004        mov     r0, r4
    a0bc:       e1a0100d        mov     r1, sp
    a0c0:       ef123456        svc     0x00123456

次のシステムコールも調べてみた。どうもstdinの設定ぽい。内部的には、標準入出力用の デバイス名が、:tt って名前なんだろうな。

newlibのコードを読んでみる。

リナとかのgccは普通、glibcを取り込んで、コンパイルされてる。今回作ったものは newlibを取り込んでgccが作られている。

実質、gccだけでは便利に使えないので、こういう気配りがしてある訳だ。 上でobjdumpして出てきた、ソース混じりのものは、newlibの下のソースが 表示されてる事になる。

そして、システムコールがらみの各CPU回りのコードは、newlibの下のlibglossの 中に集団を作っている。今回はその中のarmの部だ。(共通部分は、newlibの下)

mainとかに入る前ったら、アプリが動くための準備段階。そういう観点で探すと crt0.Sが担当者って事になる。

/* Start by setting up a stack */
#ifdef ARM_RDP_MONITOR
        /*  Issue Demon SWI to read stack info */
        swi     SWI_GetEnv      /*  Returns command line in r0 */
        mov     sp,r1           /*  and the highest memory address in r1 */
         :
#else
#ifdef ARM_RDI_MONITOR
        /*  Issue Angel SWI to read stack info */
        movs    r0, #AngelSWI_Reason_HeapInfo
        adr     r1, .LC0        /*  point at ptr to 4 words to receive data */
#ifdef THUMB_V7M_V6M
        bkpt    AngelSWI
#elif defined(__thumb2__)
        /*  We are in thumb mode for startup on armv7 architectures. */
        AngelSWIAsm     AngelSWI
#else
        /*  We are always in ARM mode for startup on pre armv7 archs. */
        AngelSWIAsm     AngelSWI_ARM
         :

大きく分けて、ARM_RDP_MONITOR系とARM_RDI_MONITOR系に分かれる。システムコール へ入る呼び出し方法が違う。今回は後者が使われている。そして、色々な タイプのCPUに対応出来るように、ifdefの嵐になってる訳だ。

続いて、stdinとかと予想した部分、syscalls.cに有った。

initialise_monitor_handles (void)
{
  int i;

  /* Open the standard file descriptors by opening the special
   * teletype device, ":tt", read-only to obtain a descritpor for
   * standard input and write-only to obtain a descriptor for standard
   * output. Finally, open ":tt" in append mode to obtain a descriptor
   * for standard error. Since this is a write mode, most kernels will
   * probably return the same value as for standard output, but the
   * kernel can differentiate the two using the mode flag and return a
   * different descriptor for standard error.
   */

#ifdef ARM_RDI_MONITOR
  int volatile block[3];

  block[0] = (int) ":tt";
  block[2] = 3;     /* length of filename */
  block[1] = 0;     /* mode "r" */
  monitor_stdin = do_AngelSWI (AngelSWI_Reason_Open, (void *) block);

initialise_monitor_handles()は、crt0.Sから呼んでるけど、同じコードがnewlib/libc/sys/arm/syscalls.cにも 置いてあるなあ。この使い道は?

gccは何をやってる?

コンパイル過程を晒してみる。(見易いように、整形しておきます)

$ arm-elf-gcc -g -v hello.c
Reading specs from /usr/local/cross2/lib/gcc/arm-elf/3.4.6/specs
  Configured with: ../../../toolchain/gcc-3.4.6/configure
   --target=arm-elf  --prefix=/usr/local/cross2
   --disable-werror  --disable-nls --disable-threads
   --disable-shared  --enable-languages=c,c++
   --with-newlib
Thread model: single
gcc version 3.4.6
## どんな風にgccが作成されたか報告

/usr/local/cross2/libexec/gcc/arm-elf/3.4.6/cc1 -quiet -v
   -D__ARM_ARCH_4T__  -D__USES_INITFINI__   hello.c -quiet
   -dumpbase hello.c -auxbase hello -g -version -o /tmp//ccz9V2XO.s
ignoring nonexistent directory 
  "/usr/local/cross2/lib/gcc/arm-elf/3.4.6/../../../../arm-elf/sys-include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/local/cross2/lib/gcc/arm-elf/3.4.6/include
 /usr/local/cross2/lib/gcc/arm-elf/3.4.6/../../../../arm-elf/include
End of search list.
GNU C version 3.4.6 (arm-elf)
        compiled by GNU C version 4.8.5.
GGC heuristics: --param ggc-min-expand=63 --param ggc-min-heapsize=62900
## includeの展開してから、C語をアセンブラ語に変換

/usr/local/cross2/lib/gcc/arm-elf/3.4.6/../../../../arm-elf/bin/as
   -o /tmp//ccRRH6nD.o /tmp//ccz9V2XO.s
## 機械語に変換

/usr/local/cross2/libexec/gcc/arm-elf/3.4.6/collect2
   -X /usr/local/cross2/lib/gcc/arm-elf/3.4.6/crti.o 
   /usr/local/cross2/lib/gcc/arm-elf/3.4.6/crtbegin.o
   /usr/local/cross2/lib/gcc/arm-elf/3.4.6/../../../../arm-elf/lib/crt0.o 
   -L/usr/local/cross2/lib/gcc/arm-elf/3.4.6
   -L/usr/local/cross2/lib/gcc/arm-elf/3.4.6/../../../../arm-elf/lib 
   /tmp//ccRRH6nD.o 
   -lgcc -lg -lc -lgcc 
   /usr/local/cross2/lib/gcc/arm-elf/3.4.6/crtend.o 
   /usr/local/cross2/lib/gcc/arm-elf/3.4.6/crtn.o
## リンクして実行形式、a.outを作成

リンク時に、色々な物が追加されてるなあ。

こちらがgcc由来の物だろう。

$ arm-elf-nm crti.o
00000000 T _fini
00000000 T _init
$ arm-elf-nm crtn.o
$ arm-elf-nm crtbegin.o
         w _Jv_RegisterClasses
00000000 d __CTOR_LIST__
00000000 d __DTOR_LIST__
00000000 d __EH_FRAME_BEGIN__
00000000 d __JCR_LIST__
         w __deregister_frame_info
00000000 t __do_global_dtors_aux
00000000 D __dso_handle
         w __register_frame_info
00000078 t call___do_global_dtors_aux
000000e4 t call_frame_dummy
00000000 b completed.1
00000088 t frame_dummy
00000004 b object.2
00000004 d p.0
$ arm-elf-nm crtend.o
00000000 d __CTOR_END__
00000000 d __DTOR_END__
00000000 d __FRAME_END__
00000000 d __JCR_END__
00000000 t __do_global_ctors_aux
00000040 t call___do_global_ctors_aux

そして、こちらがnewlibの中に有ったcrt0.Sのなれの果てだろう。多分。

$ arm-elf-nm crt0.o
00000010 d CommandLine
00000000 d HeapBase
00000004 d HeapLimit
0000000c d StackLimit
         U __bss_end__
         U __bss_start__
         U __end__
         U __libc_fini_array
         U __libc_init_array
00000008 D __stack_base__
00000000 T _mainCRTStartup
00000000 T _start
         U atexit
         U exit
         U initialise_monitor_handles
         U main
         U memset
00000000 T start

ああ、疑問に思ってた、initialise_monitor_handlesが出てきた。 しつこく追うと、分かるな。ソースのストーカーなら、誰にも文句を言われ ないだろう。

最後にcrt0.oに有ったコードもlibc.aに含まれていると予想されるので、 確認してみた。

$ /usr/local/cross2/bin/arm-elf-nm libc.a | grep initialise_monitor_handles
000000a8 T initialise_monitor_handles

やはり有ったね。どちらのコードが使われるのだろう? 坂井さんのリンカー本で 実験されてるかな?

#0  initialise_monitor_handles ()
    at ../../../../../../../../toolchain/gcc-3.4.6/newlib/libc/sys/arm/syscalls.c:141
#1  0x00008174 in start ()
    at ../../../../../../../../toolchain/gcc-3.4.6/newlib/libc/sys/arm/crt0.S:274

ふーん、libc.aの物が使われている。libcをリンクしなくても、mainが動く事を 補償するための予備手段を提供してるんだな。勉強になるなあ。