Nim (2)

技評の所で [入門]関数プログラミング―質の高いコードをすばやく直感的に書ける! なんてのをやってた。何これ、TV屋風に言うと再放送じゃん。でも、見ごたえ、もとえ、読み応えがあるな。

んな訳で、オイラーも、プログラミングHaskell本をパラパラしてみたんだ。 ふと、巻末の参考文献に目が行った。興味深いのが載ってた。

暗号解読 -- ロゼッタストーンから量子暗号まで、サイモン・シン著 ってやつ。 2001年に新潮社から出たとな。面白そうだな。果たして内容は?

ぐぐってみたぞ。いろいろ出てきた。

亡くなった叔父の暗号日記をTwitterで公開→約4時間で見事解読される こういうのもね。釣られて読んでみた。寄り道だな。

生々しい、暗号解読の実例。平文は果たして日本語? アイヌ語かも知れない。ソーシャル ハッキングとして、叔父さんの年齢は? なんて質問が有ったりして。。。

そのうちに、ある人が、和文モールスの ト(短点)とツー(長点)をひっくり返しているんじゃ ないって提案。それによって解読を心みると、見事に文章になった。 見事なひらめきだな。

これって一種の換字式暗号になるんかな。和文モールスは知らないので、欧文モールスで やってみる。文字Aのモールスは、ト・ツー。ひっくり返すと、ツー・ト。これを文字に デコードするとNになる。(逆にNはAになる)

叔父さんをプロファイリング。和文モールスって、今じゃアマチュア無線ぐらいにしか、 残っていない世界遺産。年の頃は60代以上か。昔、通信兵をやってた? 船に乗って通信士を やってた? 自衛隊に居た?

暗号の歴史に触れたページがあった。 暗号と暗号史の連載が面白かったよ。

いかん、いかん。道草を喰った。

サイモン・シン「暗号解読 ロゼッタストーンから量子暗号まで」新潮社 青木薫訳

サイモン・シン『暗号解読』の暗号解読!

C言語実験室

暗号解読をリクエストしに図書館へ行った。そしたら、 コンピュータのしくみがよくわかる! C言語プログラミングなるほど実験室 なんて本が置いてあったので、借りてきた。Windows上のVisual Studio 2013とかの環境を 使って、いろいろ実験してみましょって趣旨。

オイラーはVS2013なんて、持ってないぞ。で、fedoraです。tccです。ソースは丁寧なコメントが、 Shift_JIS語で書かれているので、nkf -w とかしてあげればよい。

ちょいと実験

[sakae@fedora chap05]$ tcc z.c -lm
[sakae@fedora chap05]$ ./a.out
素数の一覧
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,

素数p = 29
素数q = 47

鍵n = 1363
鍵e = 3
鍵d = 859
[sakae@fedora chap05]$ tcc -run z.c -lm
Segmentation fault (コアダンプ)

何の実験してるやら? 合わせて読みたい、

C言語分かってなかった

コンパイラの最適化についてすべてのプログラマが知っておくべきこと

この本のコラムに載ってた、OS種類によって異なる改行の表現って所で、オイラーの古い 知識が是正されたよ。いわく、最近のMAC OS X では、改行の扱いがLFなんですって。

これが何処に効いてくるかと言うと、以前に調べた、rustのtomlの仕様。改行コードは、 Windows用のCRLFと、Unix用のLFです。古い知識のおいらは、MACはCRだから、MAC無視 されてるよって思った訳。よく考えれば、MACのUnix族だから、改行コードはLFだわな。

Nim言語実験室

ちょいと古い、 nimrodが、記録として残っていたぞ。それはさておき、 目標を、今までやってきた、血圧表示アプリに置き、使えそうな要素技術(って言う表現で良かったかな)の 実験をやっておく。

proc idiv(x,y : int) : int = return x div y

echo( idiv(8,2) )
echo( 15.idiv(3) )

自分で書いた整数の割り算。第一変数を外に出せるよ。これを利用すると、簡単に、チェーン化 出来るので、使い道がありそう。

qiitaに載ってた実例

from strutils import split, parseInt
from math import sum

let
  inputStr = readLine(stdin)  # 一行入力(string)
  inputStrSeq = readLine(stdin).split(':')  # ':'でsplit(seq[string])
  inputIntSeq = readLine(stdin).split().map(parseInt)  # 空白でsplitしてintに変 換(seq[int])

echo("inputStr : ", inputStr)  # 引数を連結して出力
echo("inputStrSeq : ", inputStrSeq)
echo("sum of inputIntSeq : ", sum(inputIntSeq))

discard """

sample.txt

hoge fuga
foo:bar:baz
1 1 2 3 5 8

cat sample.txt | nim c -r stdio.nim
inputStr : hoge fuga
inputStrSeq : @[foo, bar, baz]
sum of inputIntSeq : 20
"""

discardってのは無視するって命令。これを使って、複数行のコメントを書ける。他にも、 値を返す関数は、その値をちゃんと受け取らないと怒られるんだけど、このdiscardを使うと、 取り合えず、怒りを静められるよ。debug中に便利かな。

parseとtcc

上の例を見ると、parseIntってのは、文字列を数値に直してくれるんだな。そして、その 関数がstrutilsに有ると読み取れる。群落の法則で、多分浮動小数点への変換も有るだろうと 予想が付く。もっとほかに無いの?

import strutils

var x : BiggestInt = 23.BiggestInt

x =  parseBiggestInt("1000000000000000000000000000000000000000000000000001")
echo(x)

期待を持って実行すると、

Traceback (most recent call last)
parse.nim(6)             parse
strutils.nim(430)        parseBiggestInt
parseutils.nim(218)      parseBiggestInt
parseutils.nim(203)      rawParseInt
Error: unhandled exception: over- or underflow [OverflowError]
Error: execution of an external program failed

Process nim-run exited abnormally with code 1

見事に予想を裏切られた。BiggestIntは、int64の事だったのね。 そうそう、23.BiggestIntってのがあるけど、これは、BiggestInt(23)の事。この型変換関数の 第一引数を前に出し、更にカッコを省略するっていう技を使ったもの。括弧の省略は、Rubyや Pythonでお馴染みなんで、取り入れたんかな。

で、これらを実験してた時、

import strutils
echo( parseInt("123") )

こんな、一番簡単そうなやつを、tccをバックエンドにしたやつでコンパイル実行すると

[sakae@fedora t]$ nimhs parse.nim
Error: unhandled exception: overflow [OverflowError]
Error: execution of an external program failed

見事に落ちた。これは何? tccを緩やかに使ってみると、エラーの詳細ももう少し分かるだろう。

[sakae@fedora t]$ nim --cc:tcc c parse.nim
[sakae@fedora t]$ ./parse
Traceback (most recent call last)
parse.nim(2)             parse
strutils.nim(421)        parseInt
parseutils.nim(230)      parseInt
Error: unhandled exception: overflow [OverflowError]

もう、lib/pure/parseutils.nimを見ろと良いヒントが表示されましたよ。

参考に見てく。

proc parseInt*(s: string, number: var int, start = 0): int {.
  rtl, extern: "npuParseInt", noSideEffect.} =
  ## parses an integer starting at `start` and stores the value into `number`.
  ## Result is the number of processed chars or 0 if there is no integer.
  ## `EOverflow` is raised if an overflow occurs.
  var res: BiggestInt
  result = parseBiggestInt(s, res, start)
  if (sizeof(int) <= 4) and
      ((res < low(int)) or (res > high(int))):
    raise newException(OverflowError, "overflow")
  else:
    number = int(res)

230行目って、raise文の所。parseIntと言いながら、中では、parseBiggestIntを使っているのね。 なお、関数名の後ろに米印が付いているのは、モジュールから公開されてるよって事を意味してる。 goでの公開関数は、先頭文字が大文字だったな。こういうルールをいちいち覚えるのは、惚け 防止にうってつけ。

if文の前に、

  echo("Debug " & $res)
  echo(sizeof(int), " ", low(int), " ", high(int))

こんなdebug印字を入れて実行してみる。モジュールもソースになってるってのは利点だなあ。 ああ、アンパサントは、文字列の結合ね。文字列の結合なんで、resも文字列が期待される。 数値を文字列にするには、変数名の前に、ダラー文字を置けばよい。それを忘れると、

lib/pure/parseutils.nim(228, 16) Error: type mismatch: got (string, BiggestInt)
but expected one of:
system.&(x: string, y: string): string
system.&(x: char, y: string): string
system.&(x: seq[T], y: T): seq[T]
system.&(x: string, y: char): string
system.&(x: char, y: char): string
system.&(x: T, y: seq[T]): seq[T]
system.&(x: seq[T], y: seq[T]): seq[T]

こういう小言を言われるけど、分かり易いやつだなあ。

[sakae@fedora t]$ nimhs parse.nim
Debug 123
4 -2147483648 2147483647
Error: unhandled exception: overflow [OverflowError]
Error: execution of an external program failed

はて、これからどうする? そりゃ、実験コードを組み立てるに決まってる。

let res = 123.BiggestInt
if (sizeof(int) <= 4) and
      ((res < low(int)) or (res > high(int))):
    echo("overflow")
else:
    echo("OK")

そして、走らせる。

[sakae@fedora t]$ nim --cc:tcc c -r tc.nim
overflow

分離成功。で、BiggestIntを外すと、ちゃんとOKになる。コンパイルしてtc.cを眺めると

NIM_EXTERNC N_NOINLINE(void, tcInit)(void) {
        res_88004 = IL64(123);
           :
                LOC5 = (res_88004 < IL64(-2147483648));
                if (LOC5) goto LA6;
                LOC5 = (IL64(2147483647) < res_88004);
                LA6: ;

このIL64は、nimbase.hに宣言されてるマクロだ。

/* --------------- how int64 constants should be declared: ----------- */
#if defined(__GNUC__) || defined(__LCC__) || \
    defined(__POCC__) || defined(__DMC__) || defined(_MSC_VER)
#  define IL64(x) x##LL
#else /* works only without LL */
#  define IL64(x) ((NI64)x)
#endif

tccの場合は(NI64)xに展開されるんだな。そしてそのNI64は、long long intとな。tccには、 そんなの無いよって事で、調査終了って事にしましょ。まてまてと囁く声がある。

どんなコードを吐くかアセンブラを眺めればいいんだろうけど、tccではアセンブラ出力 出来ない。gdbにかけて逆アセンブラって手を思い付いた。

[sakae@fedora nimcache]$ tcc -g  -I/home/sakae/.nim/lib parse.c  parseutils.c  strutils.c  system.c
tcc: error: undefined symbol 'dlclose'
tcc: error: undefined symbol 'dlopen'
tcc: error: undefined symbol 'dlsym'

これってリナちゃん特製なやつをリンクしないと駄目? 最後に、-ldl を追加。

[sakae@fedora nimcache]$ gdb -q a.out
BFD: /home/sakae/t/nimcache/a.out: セクション .text.__x86.get_pc_thunk.bx にグループ情報がありません
Reading symbols from a.out...done.
(gdb) b tcInit
Breakpoint 1 at 0x8048424: file tc.c, line 71.
(gdb) run
Starting program: /home/sakae/t/nimcache/a.out
Missing separate debuginfos, use: debuginfo-install glibc-2.20-8.fc21.i686

Breakpoint 1, tcInit () at tc.c:71
71              res_88003 = IL64(31);
(gdb) n
75                      LOC3 = 0;

tccでコンパイルしたやつは、debug情報が十分に付いていないのだな。

(gdb) disassemble tcInit
Dump of assembler code for function tcInit:
   0x0804841b <+0>:     push   %ebp
   0x0804841c <+1>:     mov    %esp,%ebp
   0x0804841e <+3>:     sub    $0x10,%esp
   0x08048424 <+9>:     mov    $0x1f,%eax
   0x08048429 <+14>:    mov    $0x0,%ecx
   0x0804842e <+19>:    mov    %eax,0x804e740
   0x08048434 <+25>:    mov    %ecx,0x804e744
=> 0x0804843a <+31>:    mov    $0x0,%eax

0x1fってのは、31の事で、それを、0x804e740に保存。上位は0でそれをecx経由で0x804e744 に保存してる。これだけ見ると、64ビットのオペレーションをやってるぽいな。

後は、暇にまけせて、解析してみるか。ifの条件を簡略化したのを巻末に挙げておく。

Nimとprocess

nimから他のプログラムを起動して、それとやり取りするにはどうする? nim付属の例に載ってるかと思ったら、無し。しょうがないので、ロゼッタ・ストーンに 刻まれていないか調べてみたよ。

import osproc

let (output, exitCode2) = execCmdEx "ls -l"
echo(output)

ふむ、こんな風にすると、外部の力を借りて、結果を取り込めるんだな。それはそれで、 いいんだけど、外部のアプリに情報を伝えるにはどうする?

これはもう、ヒントになるosprocを読むしかありません。で、execCmdExを見ます。

proc execCmdEx*(command: string,
                options: set[ProcessOption] = {poStdErrToStdOut, poUsePath}) : 
                tuple[output: TaintedString,exitCode: int] 
                {.tags: [ExecIOEffect, ReadIOEffect], gcsafe.} =

難解な入出力を整理してみた。入力は、コマンド文字列とオプション。このオプションには、 省略時の設定(poStdErrToStdOut, poUsePath)が集合型で定義されてる。

出力は、タプル型で、プロセスの結果と終了コードになる。最後の.tagsは、コードの追跡 とかにコンパイラーが使う補助情報だ。ユーザーは気にする必要は無い。

これだけの情報を理解した上で、定義の本体を読み取り、自前のコードに落としてみた。 車輪の再発明じゃなくて、プロセスに指示を与えるようにした。

import osproc, streams

let text = "plot '-' w l\n1 3\n2 4\n3 2\nend\n"
var pid = startCmd("tr 'a-z' 'n-za-m'", {poStdErrToStdOut, poUsePath})
var outp = outputStream(pid)
var inp  = inputStream(pid)

inp.write(text)
inp.close()
echo(text)

var result = (TaintedString"", -1)
var line = newStringOfCap(120).TaintedString
while true:
    if outp.readLine(line):
        result[0].string.add(line.string)
        result[0].string.add("\n")
    else:
        result[1] = peekExitCode(pid)
        if result[1] != -1: break
close(pid)
echo(result[0])

上の例は、unix側のtrコマンドを使って、textに指定してる文字列を暗号化する 積もりのコード例だ。世間ではROT13なんて呼ばれる、シーザー暗号の一種。

unixでは、どう暗号化、復号化されるか、一応、見とく。

[sakae@fedora t]$ echo abc | tr 'a-z' 'n-za-m'
nop
[sakae@fedora t]$ echo nop | tr 'a-z' 'n-za-m'
abc
[sakae@fedora t]$ ./exec
plot '-' w l
1 3
2 4
3 2
end

cybg '-' j y
1 3
2 4
3 2
raq

今度は、起動するプロセスを gnuplot -p に変更して、グラフが書けるか確認。 結果はちゃんと表示されましたよ。やれやれ。と、すんなりグラフが 書けたような涼しい顔をしてるけど、実はすんなりとは行かなかったのさ。

(たぶん)パイプを伝わってgnuplotに行くはずの命令には、グラフ書き終了のendが入って いるので、ちゃんと動くと思ってたんだ。

でも違った。ここでパイプからの入力が終了しましたって言う目印(EOF)を送ってあげないと、 gnuplot側は行動を起さないんだ。

お前、考えが甘いぞ。gnuplotの気持ちになれば、入力終了まで、ループして文字列を 取り込むだろうに。endが来たから、グラフを書きましょ、なんて面倒な事はしないよな。

で、思い出したのが、上記のコード。goだと、パイプをクローズする事により、その目印が 送られる。多分nimも一緒でしょ。喋る言語は違っても、やる事は一緒って事だもの。

文字化けしたファイル、ディレクトリを消す

先の実験室のファイルをDLし、展開した所、文字化けしちゃった。いつもは、そんな事も あろうかと、tmpを作ってその中で展開するんだけど、魔が差した。

そういう時は、inodeを調べて消すんだよって事は覚えていたんだけど、、詳細を 思い出せなかったので、おばあちゃんの知恵袋に聞いてみた。

[sakae@fedora ~]$ ls -i
663524 ?+????\?[???t?@?C??  658948 godev  656775 rust  658714 t
663625 D                    665441 nim    664585 src   131440 z
[sakae@fedora ~]$ find . -inum 663524  -exec rm -rf {} \;
find: ‘./\304+\356\246?\342\\\374[\342\357\342t\342@\342C\342\357’: そのよう なファイルやディレクトリはありません
[sakae@fedora ~]$ ls
D  godev  nim  rust  src  t  z

いきなり消すんじゃなくて、mv するのも一つの手

find . -inum 99999 -ok mv '{}' OBAKE \;

変更してよければyで応答。これで、OBAKEに変更出来る。後は、煮るなり焼くなり、 お好きなように。何せ、化ですから。

My Homework

NIM_EXTERNC N_NOINLINE(void, tcInit)(void) {
        res_88003 = IL64(31);
        {
                if (!(res_88003 < IL64(-2147483648))) goto LA3;
                printf("%s\012", (((NimStringDesc*) &TMP5))->data);
        }
        goto LA1;
        LA3: ;
        {
                printf("%s\012", (((NimStringDesc*) &TMP6))->data);
        }
        LA1: ;
}
Dump of assembler code for function tcInit:
   0x0804841b <+0>:     push   %ebp
   0x0804841c <+1>:     mov    %esp,%ebp
   0x0804841e <+3>:     sub    $0x4,%esp
   0x08048424 <+9>:     mov    $0x1f,%eax
   0x08048429 <+14>:    mov    $0x0,%ecx
   0x0804842e <+19>:    mov    %eax,0x804e6a0
   0x08048434 <+25>:    mov    %ecx,0x804e6a4
   0x0804843a <+31>:    mov    0x804e6a0,%eax
   0x08048440 <+37>:    mov    0x804e6a4,%ecx
   0x08048446 <+43>:    mov    $0x80000000,%edx
   0x0804844b <+48>:    mov    %eax,-0x4(%ebp)
   0x0804844e <+51>:    mov    $0x0,%eax
   0x08048453 <+56>:    cmp    %eax,%ecx
   0x08048455 <+58>:    jg     0x8048471 <tcInit+86>
   0x0804845b <+64>:    jne    0x804846c <tcInit+81>
   0x08048461 <+70>:    mov    -0x4(%ebp),%eax
   0x08048464 <+73>:    cmp    %edx,%eax
   0x08048466 <+75>:    jae    0x8048471 <tcInit+86>
   0x0804846c <+81>:    jmp    0x8048476 <tcInit+91>
   0x08048471 <+86>:    jmp    0x804848f <tcInit+116>
=> 0x08048476 <+91>:    mov    $0x804e454,%eax
   0x0804847b <+96>:    push   %eax
   0x0804847c <+97>:    mov    $0x804e46c,%eax
   0x08048481 <+102>:   push   %eax
   0x08048482 <+103>:   call   0x804d380 <printf>
   0x08048487 <+108>:   add    $0x8,%esp
   0x0804848a <+111>:   jmp    0x80484a3 <tcInit+136>
   0x0804848f <+116>:   mov    $0x804e468,%eax
   0x08048494 <+121>:   push   %eax
   0x08048495 <+122>:   mov    $0x804e470,%eax
   0x0804849a <+127>:   push   %eax
   0x0804849b <+128>:   call   0x804d380 <printf>
   0x080484a0 <+133>:   add    $0x8,%esp
   0x080484a3 <+136>:   leave
   0x080484a4 <+137>:   ret
End of assembler dump.

上記は、ifの条件を満足して、overflowを表示しようとした所だ。さあ、目が汚れるぞ。 C語実験室にもアセンブラが出てくるけど、理解しなくてもいいよってなってるから、無視 してもいいけど、tccの虫を捕まえた事は確かだな。

参考まで、gccでコンパイルした正解を挙げておく。

tcInit:
.LFB8:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        subl    $8, %esp
        movl    $31, res_88003
        movl    $0, res_88003+4
        movl    res_88003, %eax
        movl    res_88003+4, %edx
        cmpl    $-1, %edx
        jl      .L9
        cmpl    $-1, %edx
        jg      .L13
        cmpl    $-2147483648, %eax
        jb      .L9
.L13:
        nop
.L11:
        subl    $12, %esp
        pushl   $TMP6+8         ;; OK
        call    puts
        addl    $16, %esp
        jmp     .L8
.L9:
        subl    $12, %esp
        pushl   $TMP5+8          ;; overflow
        call    puts
        addl    $16, %esp
.L12:
        nop
.L8:
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
.LFE8:
        .size   tcInit, .-tcInit