FreeBSDでも、ふつパイラ

女房がこの間から、掃除機が壊れた々と言うので調べてみた。3年前に買ったやつだ。 そんなに早く壊れるかね? ソニータイマーなんて内蔵してない某PANA製だし。

女房からの訴えによると、掃除機じゃなくて、ごみを撒き散らすようになってしまったとの事。 Bugって、モーターが逆回転するようになった? まさかね!

説明書を引っ張り出してきて、調べてみても、ごみを撒き散らすようになった場合は、、、な んて故障事例は載っている訳がない。ふと、説明書をぱらぱら見てたら、紙フィルターを格納 するバスケットとやらが付いていない事に気が付いた。きっと、集塵したごみが隙間から漏れてしまって いるに違いないと予想。

こんな部品、補修用に取ってあるのかな? 半信半疑ながら、サポートセンターに電話して みたら、「ええ、ありますよ」との事。やっぱり、そそっかしい奥様向けのリクエストに 答えられるようになっているのね。(バスケット毎、紙フィルターを捨てちゃう、慌て者)

近くの電気店からオーダーして欲しいとの事で、チャリを飛ばして頼んできたら、次の日には 部品が届いてた。これにて、一件落着。

コンパイラ二題

最近、chickenとか、ふつパイラに凝っているせいか、コンパイラの記事に敏感になっている。

まずは、FreeBSDが将来、gcc から独立するかも? という話。gcc 4.2.2からは、GPLv3に ライセンスが変更されたとか。で、ベースシステムに取り込むのはいやだと言う話になり。。。 代替の有力候補として、LLVM Clangの試験が始まっているとか。ライセンスは勿論 BSD。 その他のGPLv3ライセンスのソフトも、ベースシステムには取り込まない方針とか。 これって、OpenBSDのあの人の影響かしらん。

もう一つの話題は、gaucheでx86アセンブラコードを出力するコンパイラ を開発中との情報。まだ、作業に取り掛かったばかりで、fibしかコンパイル出来ないそうですが 、開発者の方は、これからどういう方向を目指したらいいか、ちとお悩みの様子。 がんばってください。

ああ、コンパイラと言えば、ユニマガの最終巻が、coinsの特集だったような。手に入るうちに 仕入れておこう。

FreeBSDでも、ふつパイラ

前回は、ふつパイラをdebianでやった訳であるが、ふつパイラ本を読み進めている うちに、ひょっとしてFreeBSDでも動くんでないかいと、思えるようになってきた。 動くか早速やってみんべ。

動くか動かんかは、testが用意されているので調べるのは簡単。testじゅーよーって 事で、角谷宣教師さんに習ってみる。

[sakae@fb ~/cbc-1.0]$ make test
cd test; make test
./run.sh
./run.sh:No such file or directory
*** Error code 1

Stop in /home/sakae/cbc-1.0/test.
*** Error code 1

Stop in /home/sakae/cbc-1.0.

ははは、いきなりのエラーだよ。run.shが無いってさ。run.shを調べてみたら、その 冒頭部分が、#!/bin/bashとなってた。FreeBSDのデフォshは、(t)csh なんだけど、 csh系は嫌いなので、/usr/local/bin/bash なんだよな。今後の事も考えて, リンク張ってごまかしておくか。

[sakae@fb ~/cbc-1.0]$ make test
cd test; make test
./run.sh

01_exec..shunit[../bin/cbc ./zero.cb]: status 0 expected but was: 1
..shunit[../bin/cbc ./one.cb]: status 0 expected but was: 1

02_print..shunit[../bin/cbc ./hello.cb]: status 0 expected but was: 1
..shunit[../bin/cbc ./hello2.cb]: status 0 expected but was: 1
.^C

走り始めたけど、ことごとくエラーを背負っているなあ。どうもコンパイルを 失敗してるっぽいな。ちょっと、手打ちしてみっか。

[sakae@fb ~/cbc-1.0]$ cd test
[sakae@fb ~/cbc-1.0/test]$ ../bin/cbc ./zero.cb
readlink: illegal option -- f
usage: readlink [-n] [file ...]
Exception in thread "main" java.lang.NoClassDefFoundError: net/loveruby/cflat/compiler/Compiler
Caused by: java.lang.ClassNotFoundException: net.loveruby.cflat.compiler.Compiler
        at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:276)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
        at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)

ははは、出たな Java !! でも、その前にさりげなく出てるエラーの方が気になるぞ。 これはもう、../bin/cbc を見る鹿。多分、こやつは shell script だろう。

#!/bin/bash
# cbc -- cflat compiler

JAVA=${JAVA:-java}

# cmd_path="$(readlink -f $0)"
# srcdir_root="$(dirname "$(dirname "$cmd_path")")"

srcdir_root=/home/sakae/cbc-1.0
"$JAVA" -classpath "$srcdir_root/lib/cbc.jar" \
        net.loveruby.cflat.compiler.Compiler \
        -I"$srcdir_root/import" \
        -L"$srcdir_root/lib" \
        "$@"

readlink -f って、FreeBSDには、無いけど、その意図は想像出来るよ。cbcのツリーが /usr/local 等の下あたりに置かれて、/usr/local/bin/cbc から、リンクされる。その リンクを追跡して、絶対PATHを返すんだな。後は、それを元に、dirnameを2回取って cbcのツリーPATHを求めてるのね。だから、先周りしちゃったよ。

[sakae@fb ~/cbc-1.0/test]$  ../bin/cbc ./zero.cb
[sakae@fb ~/cbc-1.0/test]$ 

コンパイルは出来たな。ついでだから、ソースの確認と、実行もしてみっか。

[sakae@fb ~/cbc-1.0/test]$  ../bin/cbc ./zero.cb
[sakae@fb ~/cbc-1.0/test]$  cat zero.cb
int
main(int argc, char **argv)
{
    return 0;
}
[sakae@fb ~/cbc-1.0/test]$ ls -l zero*
-rwxr-xr-x  1 sakae  kuma  3373 10  6 10:43 zero*
-rw-r--r--  1 sakae  kuma    50 12 24  2007 zero.cb
-rw-r--r--  1 sakae  kuma   504 10  6 10:43 zero.o
-rw-r--r--  1 sakae  kuma   175 10  6 10:43 zero.s
[sakae@fb ~/cbc-1.0/test]$ cat zero.s
.file   "./zero.cb"
        .text
.globl main
        .type   main,@function
main:
        pushl   %ebp
        movl    %esp, %ebp
        movl    $0, %eax
        jmp     .L0
.L0:
        movl    %ebp, %esp
        popl    %ebp
        ret
        .size   main,.-main
[sakae@fb ~/cbc-1.0/test]$ ./zero
ELF interpreter /lib/ld-linux.so.2 not found
Abort trap: 6
[sakae@fb ~/cbc-1.0/test]$

何もしないコードなんだけど、実行出来ないよ。ld-linux.so.2 って言われてもねぇ。 そんなの、ある訳ないっしょ。でも、FreeBSDと言えども、Tool chain は、rms系な 事は間違いないので、同等品を探せばいいんだな。

頭に ldが付くやつで、おいらが知ってるのは、ldd,ld.so あたりなので、ここらあたりを 手がかりに、man を手繰ってみた所、ld-elf.so.1 が、引っかかってきた。えぃ、こいつも、リンクしちゃ!

[sakae@fb ~]$ cd /lib
[sakae@fb /lib]$  sudo ln -s /libexec/ld-elf.so.1 ld-linux.so.2

これをしたら、エラー無く、zeroが実行できたよ。うひょ。続けて、run.sh を実行。 時間がかかりそうだったので、LOGに落す事にした。

[sakae@fb ~/cbc-1.0/test]$ ./run.sh > LOG 2>&1

して、その結果と言えば

01_exec........
02_print................................................................
03_integer................
04_funcall....................................
05_string....stdout differ: string "-e ;;a;aa;b;";';\a\b\0033\f\n\r\t\v;ABCabc"
and cmd "./string"
--- tc.out.expected     2009-10-06 11:02:11.000000000 +0900
+++ tc.out.real 2009-10-06 11:02:11.000000000 +0900
@@ -1 +1,2 @@
--e ;;a;aa;b;";';\a\b\0033\f\n\r\t\v;ABCabc
+;;a;aa;b;";';^[^L
+
        ^K;ABCabc
----
....stdout differ: string "-e ;;a;aa;b;";';\a\b\0033\f\n\r\t\v;ABCabc" and cmd "
./string"
--- tc.out.expected     2009-10-06 11:02:13.000000000 +0900
+++ tc.out.real 2009-10-06 11:02:13.000000000 +0900
@@ -1 +1,2 @@
--e ;;a;aa;b;";';\a\b\0033\f\n\r\t\v;ABCabc
+;;a;aa;b;";';^[^L
+
        ^K;ABCabc
----
 :
06_variables....................................................................
....................................................shunit[../bin/cbc ./gvar.cb]
: status 0 expected but was: 1
 :
34_varargs..shunit[../bin/cbc ./varargs.cb]: status 0 expected but was: 1

35_invalidstmt......
36_alloca..................................shunit[../bin/cbc -fPIE -pie alloca2.
cb]: status 0 expected but was: 1

37_setjmp................
FAIL (7/1874 failed) -- fb.kuma.net / FreeBSD 6.4-STABLE i386

Stringのエラーはさておき、他のエラーをちょっとみてみる。

[sakae@fb ~/cbc-1.0/test]$ ../bin/cbc ./gvar.cb
gvar.o(.text+0x7b): In function `main':
: undefined reference to `stdin'
cbc: error: /usr/bin/ld failed. (status 1)
cbc: error: compile error
[sakae@fb ~/cbc-1.0/test]$ ../bin/cbc ./varargs.cb
varargs.o(.text+0x4b): In function `myprintf':
: undefined reference to `stdout'
cbc: error: /usr/bin/ld failed. (status 1)
cbc: error: compile error
[sakae@fb ~/cbc-1.0/test]$ ../bin/cbc -fPIE -pie alloca2.cb
cbc: warning: alloca2.cb:16: unused variable: junk
cbc: warning: alloca2.cb:28: unused variable: junk
cbc: warning: alloca2.cb:41: unused variable: junk
cbc: warning: alloca2.cb:54: unused variable: junk
alloca2.s: Assembler messages:
alloca2.s:198: Warning: setting incorrect section attributes for .text.__i686.get_pc_thunk.bx
/usr/bin/ld: /usr/lib/Scrt1.o: No such file: No such file or directory
cbc: error: /usr/bin/ld failed. (status 1)
cbc: error: compile error

stdin,stdoutって、環境がらみ? Scrt1.oって、FreeBSDには、無いんだよ。これは 諦める鹿。

でも、

[sakae@fb ~/cbc-1.0/test]$ ls -l /usr/lib/*crt*
-r--r--r--  1 root  wheel  2220 Sep  1 06:26 /usr/lib/crtbeginS.o
-r--r--r--  1 root  wheel  1260 Sep  1 06:26 /usr/lib/crtendS.o
-r--r--r--  1 root  wheel  1652 Sep  1 06:25 /usr/lib/gcrt1.o

怪しげなやつがあるんだよな。これのソースを読めば、何か分かるかも?

後、stdin,stdout は、stdio.hb に宣言されてるんだけど、取り込まれていないから エラーになるんだな。--dump-ast 付きでコンパイルしてみたけど、import は、現れ なかった。ちゃんと import を認識してるんかい? と思って、わざと importする ファイル名を変えたら、ちゃんとエラーになる。

[sakae@fb ~/cbc-1.0/test]$ ../bin/cbc varargs.cb
cbc: error: no such library header file: Xstdio

ちゃんと見ててくれているよ。だったら、なぜにastに現れないん? 分かった積もり でも、やっぱり分かっていないや。

残りは、stringのエラーだな。テストコード(test_cbc.sh)を見ると

test_05_string() {
    assert_out "$(/bin/echo -e ';;a;aa;b;";'\'';\a\b\0033\f\n\r\t\v;ABCabc')" ./string

FrrBSD の echo コマンドに、-e なんてオプション無いんよ。ちなみに、-e って、バックスラッシュつきの文字を扱うみたい。って、事で、期待値が正しく扱われていないっぽい。 なお、テストの一方は、

[sakae@fb ~/cbc-1.0/test]$ cat string.cb
import stdio;

int
main(int argc, char **argv)
{
    printf("");
    printf(";");
    printf(";a");
    printf(";aa;b");
    printf(";\"");
    printf(";\'");
    printf(";\a\b\e\f\n\r\t\v");
    printf(";\101\102\103\141\142\143");
    puts("");
    return 0;
}

こんなコードになってたよ。これって、エスケープシーケンスじゃ、ありませんか。 今、debian機も立ち上げたので、一応 stringの出力をdumpして貼っておく。

[sakae@fb ~/cbc-1.0/test]$ ./string >onBSD
[sakae@fb ~/cbc-1.0/test]$ hd onBSD
00000000  3b 3b 61 3b 61 61 3b 62  3b 22 3b 27 3b 07 08 1b  |;;a;aa;b;";';...|
00000010  0c 0a 0d 09 0b 3b 41 42  43 61 62 63 0a           |.....;ABCabc.|
sakae@debian:~/cbc-1.0/test$ hexdump -C onLinux
00000000  3b 3b 61 3b 61 61 3b 62  3b 22 3b 27 3b 07 08 1b  |;;a;aa;b;";';...|
00000010  0c 0a 0d 09 0b 3b 41 42  43 61 62 63 0a           |.....;ABCabc.|

stdout,stdin ?

どうしても、上のstdoutやらが、どこからやってくるか調べておきたいのだ。 まずは、基準となる debian機で

sakae@debian:~/cbc-1.0/test$ ../bin/cbc -v varargs.cb
as -o varargs.o varargs.s
/usr/bin/ld -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o /usr/lib/crti.o -L/home/sakae/cbc-1.0/lib varargs.o -lc -lcbc /usr/lib/crtn.o -o varargs

ふむ、*.cbをcbcでコンパイルすると、アセンブラソースが得られ、それをアセンブルすると *.oのオブジェクトが出てきて、そいつをリンカーを使って、ライブラリー類と結合するのか。

[sakae@fb ~/cbc-1.0/test]$ ../bin/cbc -v varargs.cb
as -o varargs.o varargs.s
/usr/bin/ld -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o /usr/lib/crti.o -L/home/sakae/cbc-1.0/lib varargs.o -lc -lcbc /usr/lib/crtn.o -o varargs
varargs.o(.text+0x4b): In function `myprintf':
: undefined reference to `stdout'
cbc: error: /usr/bin/ld failed. (status 1)
cbc: error: compile error

リンカーのフェーズで失敗してるんだな。一応、varargs.oを確認しとく

sakae@debian:~/cbc-1.0/test$ cat varargs.s
  :
        movl    8(%ebp), %eax
        pushl   %eax
        movl    stdout, %eax
        pushl   %eax
        call    vfprintf
  :
sakae@debian:~/cbc-1.0/test$ objdump -d varargs.o

varargs.o:     file format elf32-i386

Disassembly of section .text:
  :
  46:   8b 45 08                mov    0x8(%ebp),%eax
  49:   50                      push   %eax
  4a:   a1 00 00 00 00          mov    0x0,%eax
  4f:   50                      push   %eax
  50:   e8 fc ff ff ff          call   51 <myprintf+0x2a>
  :

movl stdout, %eax の、stdout が、リロケータブルファイルでは場所を開けてまってると いう事か。ちなみに、FreeBSDの varargs.o は、Debianのそれと全く同一のものが生成 されてたよ。まあ、ここまでは、問題無いわな。

で、リンクが済むと(実行形式になると)

sakae@debian:~/cbc-1.0/test$ objdump -d varargs

varargs:     file format elf32-i386
  :
 804835a:       8b 45 08                mov    0x8(%ebp),%eax
 804835d:       50                      push   %eax
 804835e:       a1 60 95 04 08          mov    0x8049560,%eax
 8048363:       50                      push   %eax
 8048364:       e8 6f ff ff ff          call   80482d8 <vfprintf@plt>
  :

アドレスが確定されるんだな。ここで決まった(上の例では、0x8049560)やつは、何だ? ldが利用出来るようなヘッダーが付加されてるんだな。

sakae@debian:~/cbc-1.0/test$ readelf -s varargs | grep stdout
     4: 08049560     4 OBJECT  GLOBAL DEFAULT   19 stdout@GLIBC_2.0 (2)
    52: 08049560     4 OBJECT  GLOBAL DEFAULT   19 stdout@@GLIBC_2.0

そして、いよいよ実行時に

sakae@debian:~/cbc-1.0/test$ LD_DEBUG=reloc,symbols,bindings ./varargs 2>&1 | grep stdout
      2521:     symbol=_IO_stdout_;  lookup in file=./varargs [0]
      2521:     symbol=_IO_stdout_;  lookup in file=/lib/i686/cmov/libc.so.6 [0]
      2521:     binding file /lib/i686/cmov/libc.so.6 [0] to /lib/i686/cmov/libc.so.6 [0]: normal symbol `_IO_stdout_' [GLIBC_2.0]
      2521:     symbol=stdout;  lookup in file=./varargs [0]
      2521:     binding file /lib/i686/cmov/libc.so.6 [0] to ./varargs [0]: normal symbol `stdout' [GLIBC_2.0]
      2521:     symbol=_IO_2_1_stdout_;  lookup in file=./varargs [0]
      2521:     symbol=_IO_2_1_stdout_;  lookup in file=/lib/i686/cmov/libc.so.6 [0]
      2521:     binding file /lib/i686/cmov/libc.so.6 [0] to /lib/i686/cmov/libc.so.6 [0]: normal symbol `_IO_2_1_stdout_' [GLIBC_2.1]
      2521:     symbol=stdout;  lookup in file=/lib/i686/cmov/libc.so.6 [0]
      2521:     binding file ./varargs [0] to /lib/i686/cmov/libc.so.6 [0]: normal symbol `stdout' [GLIBC_2.0]

メモリー上で最終落ち着き先が決まる。(この解釈でいいんだろうか?)

FreeBSDでは、リンクに失敗してるので、勿論実行形式のファイルは作成されていない。その 原因は、libcに stdout が無い。いくら何でもそんな事は無いだろう。

疑問だ、疑問だ。 後は、どうやって調べよう?

思いついたぞ

sakae@debian:~/tmp$ cat test_stdout.c
#include <stdio.h>

main(){
   fputs("Hello\n", stdout);
}

こんなのを用意して、アセンブルしてみる。まずは、debian様から

sakae@debian:~/tmp$ cat test_stdout.s
        .file   "test_stdout.c"
        .section        .rodata
.LC0:
        .string "Hello\n"
        .text
.globl main
        .type   main, @function
main:
        leal    4(%esp), %ecx
        andl    $-16, %esp
        pushl   -4(%ecx)
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %ecx
        subl    $20, %esp
        movl    stdout, %eax
        movl    %eax, 12(%esp)
        movl    $6, 8(%esp)
        movl    $1, 4(%esp)
        movl    $.LC0, (%esp)
        call    fwrite
        addl    $20, %esp
        popl    %ecx
        popl    %ebp
        leal    -4(%ecx), %esp
        ret
        .size   main, .-main
        .ident  "GCC: (Debian 4.3.2-1.1) 4.3.2"
        .section        .note.GNU-stack,"",@progbits

fputsがfwriteに化けちゃったのは置いといて、Cのソース中に出てきた stdout は、その まま残っている。

一方、FreeBSDの方はどうかと言うと

[sakae@fb ~/tmp]$ cat test_stdout.s
        .file   "test_stdout.c"
        .section        .rodata
.LC0:
        .string "Hello\n"
        .text
        .p2align 2,,3
.globl main
        .type   main, @function
main:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        andl    $-16, %esp
        movl    $0, %eax
        addl    $15, %eax
        addl    $15, %eax
        shrl    $4, %eax
        sall    $4, %eax
        subl    %eax, %esp
        subl    $8, %esp
        pushl   __stdoutp
        pushl   $.LC0
        call    fputs
        addl    $16, %esp
        leave
        ret
        .size   main, .-main
        .ident  "GCC: (GNU) 3.4.6 [FreeBSD] 20060305"

stdoutの名前が変形されちゃっているよ。変形した名前を受け付けるように、libcが調整 されてるから、ちゃんとリンクが出来ると。

今回は、アセンブラソースが、cbc経由でLinux用に出力されるんで、(FreeBSDの)libcとは 一致しない。その結果、エラーになったのね。いやー、勉強になるなあ。

果たして解決策はあるのか?

ちょいと、varargs.s の stdout を、__stdoutp に書き換えて、cbc varargs.s したら エラーなく、コンパイル&GO出来たよ。

FreeBSD の、stdio.h を見たら、#define stdout __stdoutp となってた。と言う事は stdio.hb にも、これを書いておくと良きに計らってくれるんでないかいと、やってみたら 青木さんが出てきて、怒られた。#define なんて仕様にないってば。

次は、cbcの何処かを改造して、sedでも挟んでやるか。まてまて、var名とぶつかったら どうする? うかつに出来ないよ。あれ? stdout って、予約語? 疑問が一杯。