「木蘭コンパイラ」を逆コンパイルし、ソースコードを分析

41684 ワード

関連するコードは以下の通りです.https://github.com/loopyme/ulan-uncompile
みんなはすべて木蘭のコンパイラが水のプロジェクトにあると言っていますが、私は多くの人が何も知らないと感じています.あなたはランダムに何人かのネットユーザーをサンプリングして、Parserとlexerを説明できない可能性が高いです.だから私は時間を見つけて、木蘭のコンパイラを分解してソースコードを見て、良いか悪いかを分解して見ます.

文書ディレクトリ

  • 1. 逆コンパイル
  • 1.1 exeコンテンツの抽出
  • 1.2 pycファイルのパッチ
  • 1.3逆コンパイルpycファイル
  • 2. ソース分析
  • 2.1プロジェクト構造
  • 2.2 ulang.parser
  • 2.2.1 ulang.parser.lexer
  • 2.2.2 ulang.parser.core
  • 2.2.3 ulang.parser.lrparserとulang.parser.parsergenerator
  • 2.2.4 ulang.parser.error
  • 2.3 ulang.CodeGen
  • 2.3.1 ulang.CodeGen.ulgen
  • 2.3.2 ulang.CodeGen.blockly
  • 2.4 ulang.runtime
  • 3.まとめ
  • 4.免責声明
  • 木蘭コンパイラはpythonの先端を変えたと思いますが、少なくとも私が思っていたシール(evalで実現したもの)を貼ったわけではないので、面白い小さなプロジェクトだと思います.ただ、「国産コンパイラ」の名前が大きすぎて、世論が加わったので、車をひっくり返しました.しかし、私がこのようなプロジェクトを書いたら(しかもプロジェクトに文字数の書類を追加しなかったら)、私は誇りに思っています.少なくともズボンを脱いでおならをしたpymipsよりどこに行ったのか分かりません.
    木蘭のコンパイラは、車輪を見つけて、タイヤの説明書をよく読んで、タイヤを作って車輪に交換した.玄人はこのタイヤを交換しても意味がないと思っています.まだ車輪が使えないかもしれません.多くの素人が騒いでいます.木蘭は元のタイヤに膜を貼っただけだと思っています.

    1.逆コンパイル


    木蘭コンパイラはPyInstallerでパッケージ化されたpythonプロジェクトであることがわかり、このexeを逆コンパイルする考え方がはっきりしています

    1.1 exeコンテンツの抽出


    pyinstxtractorでPyInstallerで生成されたWindows実行可能ファイルの内容を簡単に抽出できます
    python ./tools/pyinstxtractor.py ./ulang-0.2.2.exe
    

    1.2 pycファイルの修正


    PyInstallerはpycファイルのmagicとタイムスタンプを食べてしまうのでstructファイルから上位8バイトを取ってpycファイルの前に戻す必要があります.
    # ./tools/add_header.py
    import os
    
    
    with open("./ulang-0.2.2.exe_extracted/struct", "rb") as f:
        header = f.read()[:4]
    
    for filename in os.listdir("./ulang-0.2.2.exe_extracted/PYZ-00.pyz_extracted"):
        if 'ulang' not in filename:
            continue
        
        with open("./ulang-0.2.2.exe_extracted/PYZ-00.pyz_extracted/" + filename, "rb") as f:
            data = f.read()
    
        with open("./pyc/" + filename, "wb") as f:
            f.write(header + data)
    
    mkdir ./pyc/
    python ./tools/add_header.py
    

    1.3 pycファイルの逆コンパイル

    uncompyle6で直接pycファイルを反コンパイルすることができて、下のshについては、私はまずlsに行って、それから熟練した列の操作でvs codeの中で整然とした指令を貼り付けます.
    mkdir ./ulang/
    mkdir ./ulang/codegen/
    mkdir ./ulang/parser/
    mkdir ./ulang/runtime/
    
    pip install uncompyle6
    
    uncompyle6 ./pyc/ulang.codegen.blockly.pyc        > ./ulang/codegen/blockly.py
    uncompyle6 ./pyc/ulang.codegen.pyc                > ./ulang/codegen/__init__.py
    uncompyle6 ./pyc/ulang.codegen.python.pyc         > ./ulang/codegen/python.py
    uncompyle6 ./pyc/ulang.codegen.ulgen.pyc          > ./ulang/codegen/ulgen.py
    uncompyle6 ./pyc/ulang.parser.core.pyc            > ./ulang/parser/core.py
    uncompyle6 ./pyc/ulang.parser.error.pyc           > ./ulang/parser/error.py
    uncompyle6 ./pyc/ulang.parser.lexer.pyc           > ./ulang/parser/lexer.py
    uncompyle6 ./pyc/ulang.parser.lrparser.pyc        > ./ulang/parser/lrparser.py
    uncompyle6 ./pyc/ulang.parser.parsergenerator.pyc > ./ulang/parser/parsergenerator.py
    uncompyle6 ./pyc/ulang.parser.pyc                 > ./ulang/parser/__init__.py
    uncompyle6 ./pyc/ulang.pyc                        > ./ulang/__init__.py
    uncompyle6 ./pyc/ulang.runtime.env.pyc            > ./ulang/runtime/env.py
    uncompyle6 ./pyc/ulang.runtime.main.pyc           > ./ulang/runtime/main.py
    uncompyle6 ./pyc/ulang.runtime.pyc                > ./ulang/runtime/__init__.py
    uncompyle6 ./pyc/ulang.runtime.repl.pyc           > ./ulang/runtime/repl.py
    

    そして手動で調整すると大成功!
    このように逆コンパイルされたコードは実際には走れないが、debugはいくつかの場所で小さな問題が発生していることを発見し、これは私がソースコードを読む大まかな考え方に影響を与えない.

    2.ソース分析


    2.1プロジェクト構造

    .
    ├── __init__.py
    ├── main.py
    ├── CodeGen
    │   ├── __init__.py
    │   ├── blockly.py
    │   ├── python.py*
    │   └── ulgen.py
    ├── parser
    │   ├── __init__.py
    │   ├── core.py
    │   ├── error.py
    │   ├── lexer.py
    │   ├── lrparser.py*
    │   ├── parsergenerator.py*
    └── runtime
        ├── __init__.py
        ├── env.py
        ├── main.py
        └── repl.py
    
    *: 
    

    この項目の主な外部依存はast,rply,codegenである.

    2.2 ulang.parser

    ulang.parser.core.Parser注記:A simple LR(1)parser to parse the source code of mu and yield the python ast for later using…(muのソースコードを解析しpython astを生成して後で使用するための簡単なLR(1)解析器.)
    資料を調べてみると、著者はrplyの文書を熟読したはずだと推測し、文書指導に基づいてParserとLexerを実現した.以下は具体的な分析である.

    2.2.1 ulang.parser.lexer

    ulang.parser.lexerセグメント:
    lg.add('IDENTIFIER', '\\$?[_a-zA-Z][_a-zA-Z0-9]*')
    lg.add('DOTDOTDOT', '\\.\\.\\.')
    lg.add('DOTDOTLT', '\\.\\.)
    lg.add('DOTDOT', '\\.\\.')
    lg.add('DOT', '\\.')
    lg.add('DOLLAR', '\\$')
    lg.add('[', '\\[')
    lg.add(']', '\\]')
    lg.add('(', '\\(')
    

    Lexerはrply.LexerGenerator教程のサンプルコードを複数回コピーして貼り付ける方式を用いて、すべての文法規則を加えて、文法分析を実現したと推測する.

    2.2.2 ulang.parser.core

    ulang.parser.coreは比較的硬い核(結局core)のように見え、多くの関数、装飾器などがあふれており、rply.parsergeneratorに基づいてParserを生成している.
    rplyドキュメントをよく読むと、このファイルのコードは基本的に理解でき、ファイル全体の考え方ははっきりしているが、作業量は比較的大きい(ulang.parser.lexerの場合と少し似ている).例えば複雑で冗長な装飾器のように見えますが、実はすべてrply.parsergenerator.productionでterminals(tokens)&non-terminalsシーケンスを指定しています.
    総じて言えば、作者はこの辺りで大きな精力を払うべきだと思います.

    2.2.3 ulang.parser.lrparserとulang.parser.parsergenerator


    この二人は微妙だ.私が読んだときはちょっと変わった感じで、わざわざ調べてみるとrply.parserrply.parsergeneratorのコピーだった
    私はコンパクト数と推測するしかありません(作者の環境が合わないかもしれませんがimportはできません)

    2.2.4 ulang.parser.error

    SyntaxErrorを実現しました.中規中拠だと思います.

    2.3 ulang.CodeGen

    ulang.CodeGen.blockly.CodeGen注記:A simple python ast to blockly xml converter.(簡単なpython astからblockly xmlへの変換器)ulang.parserコードのスタイルとは感じが違います.ulang.CodeGenのファイルはそれぞれ実現されました.
  • ulang.CodeGen.ulgen : python ast -> ulang
  • ulang.CodeGen.blockly : python ast -> blockly xml
  • ulang.CodeGen.pythoncodegen.codegenのコピーです
    総じて言えば、ulang.CodeGenulang.parserより仕事量が悪いとは思いませんが、もっと面白いです.

    2.3.1 ulang.CodeGen.ulgen

    codegen.codegenに従って瓢箪を描き、さらにulangの構想に基づいて調整するとulang.CodeGen.ulgenが得られ、その中でも作業量は大きい.

    2.3.2 ulang.CodeGen.blockly


    このファイルはpython astをblockly xmlに変換します.たぶんvisitツリーノードで、ノードのタイプに応じて変換します.面白くて、仕事量も大きいです.

    2.4 ulang.runtime


    このモジュールの中でコードは特に悪くて、プロジェクト全体の力を十分に発揚して奇跡の風格を出して、あまり表現しなくて、直接コードを節選しました:ulang.runtime.repl節:
    #  ( ,[(]) )
    unclosed = []
    unmatched = [0, 0, 0]
    last = 2 * ['']
    for tok in tokens:
        c = tok.gettokentype()
        last[0], last[1] = last[1], c
        if c in keywords:
            unclosed.append(c)
        if c == 'LBRACE':
            unmatched[0] += 1
        elif c == 'RBRACE':
            unmatched[0] -= 1
            if len(unclosed):
                unclosed.pop(-1)
        elif c == '(':
            unmatched[1] += 1
        elif c == ')':
            unmatched[1] -= 1
        elif c == '[':
            unmatched[2] += 1
        elif c == ']':
            unmatched[2] -= 1
    unmatched_sum = sum(unmatched)
    unclosed_sum = len(unclosed)
    if unclosed_sum > 0:
        if unmatched_sum == 0:
            if last[1] == 'NEWLINE':
                if (last[0] == 'NEWLINE' or last[0]) == ';':
                    pass
                return True
    return unclosed_sum == 0 and unmatched_sum == 0
    

    私はこのように書く(私にとって)少し楽しくて、バグを修理したような気がします.
    SYMBOLS = {"}": "{", "]": "[", ")": "(", "RBRACE": "LBRACE"}
    
    last = [""] * 2
    unmatched = []
    unclosed = []
    
    for tok in tokens:
        c = tok.gettokentype()
        last[0], last[1] = last[1], c
    
        if c in keywords:
            unclosed.append(c)
        elif c == "RBRACE" and unclosed:
            unclosed.pop()
        
        if c in SYMBOLS.values(): # left/right symbol
            unmatched.append(c)
        elif c in SYMBOLS.keys() and unmatched.pop() != SYMBOLS[c]:
            return False
    
    return (
        # NEWLINE
        unclosed
        and not unmatched
        and last[1] == "NEWLINE"
        and last[0] not in ["NEWLINE", ";"]
    ) or (
        # closed and matched
        not unclosed
        and not unmatched
    )
    
    ulang.runtime.env節:
    #  ( )
    return {
        "print"             : local_print,
        "println"           : lambda *objs: local_print(*objs, **{"end":"
    "
    }), "assert" : local_assert, "len" : len, "enumerate" : enumerate, "all" : all, "any" : any, "range" : range, "round" : round, "input" : input, "reverse" : reversed, "super" : super, "locals" : lambda: locals(), "bool" : bool, "float" : float, "int" : int, "str" : str, "list" : list, "dict" : dict, "set" : set, "tuple" : lambda *args: args, "char" : chr, "ord" : ord, "bytes" : lambda s, encoding="ascii":bytes(s, encoding), "typeof" : lambda x: x.__class__.__name__, "isa" : lambda x, t: isinstance(x, t), "max" : max, "min" : min, "map" : map, "filter" : filter, "zip" : zip, "staticmethod" : staticmethod, "property" : property, "ceil" : math.ceil, "floor" : math.floor, "fabs" : math.fabs, "sqrt" : math.sqrt, "log" : math.log, "log10" : math.log10, "exp" : math.exp, "pow" : math.pow, "sin" : math.sin, "cos" : math.cos, "tan" : math.tan, "asin" : math.asin, "acos" : math.acos, "atan" : math.atan, "spawn" : builtin_spawn, "kill" : builtin_kill, "self" : builtin_self, "quit" : sys.exit, "open" : open, "install" : pip_install, "time" : time.time, "year" : lambda: datetime.now().year, "month" : lambda: datetime.now().month, "day" : lambda: datetime.now().day, "hour" : lambda: datetime.now().hour, "minute" : lambda: datetime.now().minute, "second" : lambda: datetime.now().second, "microsecond" : lambda: datetime.now().microsecond, "sleep" : time.sleep, "delay" : lambda ms: time.sleep(ms / 1000), "delayMicroseconds" : lambda us: time.sleep(us / 1000000), "PI" : math.pi, "ARGV" : argv, "__builtins__" : fix_builtins( { "__import__" : local_import, "__build_class__" : __build_class__, "__name__" : "__main__", "__file__" : fname, "__print__" : eval_print, "___" : None, "__div__" : __builtin_div, "__rem__" : __builtin_rem, } ), }

    3.まとめ


    次のように思います.
    木蘭コンパイラは仕事量によって1つの“大型の小さいプロジェクト”と言えることができて、一定の技術の含有量もあって、何人かの同級生(3つを超えない)が一緒に書いたのかもしれなくて、しかも濃厚な奇跡の風格を持って、このような風格は高校の実験室のコードの中で比較的によく見られます.コードの品質は私の小さなプロジェクトの第1回の書き方とあまり悪くなくて、まだ整理して再構築したことがないはずで、すべてドキュメントを調べて資料を調べて走って通せばいいのです.著者(チーム)はrplycodegenのドキュメントとチュートリアルを熟読し、修理し、補完し、このプロジェクトを書いたはずだ.
    大きな問題は、他の公開庫の書類を3つ梱包して入り、名前を強引に変えて内容を変えなかったことだ.
    総じて言えば、pythonに先端を変えたのですが、少なくとも私が思っていたシール(evalで実現したもの)を付けたわけではないので、面白い小さなプロジェクトですが、「国産コンパイラ」の名前が大きすぎて世論が加わったので、車をひっくり返しました.しかし、私がこのようなプロジェクトを書いたら(しかもプロジェクトに文字数の書類を追加しなかったら)、私は誇りに思っています.少なくともズボンを脱いでおならをしたpymipsより高いです.どこへ行ったのか分かりません.

    4.免責声明


    私はいかなる会社、集団にも属していません.リバースエンジニアリングの時に使用するすべての参考情報はネット公開資料から合法的に獲得した.逆工程後の一部のコードと論理は完全に推測によって完成し、具体的なコードは原木蘭言語の実現と何の関係もなく、原木蘭プロジェクトの機能とコード品質を代表しない.本文のすべての観点と分析は個人の推測であり、実際の状況と大きな違いがある可能性がある.