diff --git a/chb/app/CHVersion.py b/chb/app/CHVersion.py index 7ba54b9c..a6853405 100644 --- a/chb/app/CHVersion.py +++ b/chb/app/CHVersion.py @@ -1 +1 @@ -chbversion: str = "0.3.0-20250308" +chbversion: str = "0.3.0-20250309" diff --git a/chb/ast/astutil.py b/chb/ast/astutil.py index e63c8091..5081e774 100644 --- a/chb/ast/astutil.py +++ b/chb/ast/astutil.py @@ -31,17 +31,22 @@ import subprocess import sys +import xml.etree.ElementTree as ET + from typing import Any, Dict, List, NoReturn, Optional from chb.ast.AbstractSyntaxTree import AbstractSyntaxTree +from chb.ast.ASTCPrettyPrinter import ASTCPrettyPrinter from chb.ast.ASTDeserializer import ASTDeserializer import chb.ast.ASTNode as AST from chb.ast.ASTViewer import ASTViewer import chb.ast.astdotutil as DU +from chb.astparser.ASTCParseManager import ASTCParseManager + def print_pirinfo(pirjson: Dict[str, Any]) -> None: - print("PIR-version: " + pirjson["pir-version"]) + print("PIR-version: " + pirjson["pir-version"]) if "created-by" in pirjson: cb = pirjson["created-by"] print( @@ -146,7 +151,7 @@ def get_function_addr(pirjson: Dict[str, Any], function: Optional[str]) -> str: + "\n".join( (" " + va.ljust(8) + name) for (va, name) in functions.items())) exit(1) - + def view_ast_function( faddr: str, @@ -166,7 +171,7 @@ def view_ast_function( else: g = viewer.to_graph(dfn.high_unreduced_ast) return g - + def viewastcmd(args: argparse.Namespace) -> NoReturn: @@ -178,7 +183,7 @@ def viewastcmd(args: argparse.Namespace) -> NoReturn: cutoff: Optional[str] = args.cutoff with open(pirfile, "r") as fp: - pirjson = json.load(fp) + pirjson = json.load(fp) faddr = get_function_addr(pirjson, function) g = view_ast_function(faddr, level, pirjson, cutoff) @@ -196,7 +201,7 @@ def viewinstrcmd(args: argparse.Namespace) -> NoReturn: outputfilename: str = args.output with open(pirfile, "r") as fp: - pirjson = json.load(fp) + pirjson = json.load(fp) faddr = get_function_addr(pirjson, function) deserializer = ASTDeserializer(pirjson) @@ -231,7 +236,7 @@ def viewexprcmd(args: argparse.Namespace) -> NoReturn: outputfilename: str = args.output with open(pirfile, "r") as fp: - pirjson = json.load(fp) + pirjson = json.load(fp) faddr = get_function_addr(pirjson, function) deserializer = ASTDeserializer(pirjson) @@ -294,3 +299,88 @@ def include_loc(loc: str) -> bool: + str(exprec[1]) + ")") exit(0) + + +def printsrccmd(args: argparse.Namespace) -> NoReturn: + + # arguments + pirfile: str = args.pirfile + function: str = args.function + + with open(pirfile, "r") as fp: + pirjson = json.load(fp) + + faddr = get_function_addr(pirjson, function) + deserializer = ASTDeserializer(pirjson) + (symtable, astnode) = deserializer.lifted_functions[faddr] + pp = ASTCPrettyPrinter(symtable, annotations=deserializer.annotations) + print(pp.to_c(astnode, include_globals=True)) + + exit(0) + + +def parsecmd(args: argparse.Namespace) -> NoReturn: + + # arguments + pirfile: str = args.pirfile + cname: str = args.cname + + if not ASTCParseManager().check_cparser(): + print("*" * 80) + print("CodeHawk CIL parser not found.") + print("~" * 80) + print("Copy CHC/cchcil/parseFile from the (compiled) codehawk ") + print("repository to the chb/bin/binaries/linux directory in this ") + print("repository, or ") + print("set up ConfigLocal.py with another location for parseFile") + print("*" * 80) + exit(1) + + cfilename = cname + ".c" + with open(pirfile, "r") as fp: + pirjson = json.load(fp) + + lines: List[str] = [] + functions: Dict[str, AST.ASTStmt] = {} + deserializer = ASTDeserializer(pirjson) + printglobals = True + for (faddr, (symtable, dfn)) in deserializer.lifted_functions.items(): + pp = ASTCPrettyPrinter(symtable, annotations=deserializer.annotations) + lines.append(pp.to_c(dfn, include_globals=printglobals)) + functions[faddr] = dfn + printglobals = False + + with open(cfilename, "w") as fp: + fp.write("\n".join(lines)) + + parsemanager = ASTCParseManager() + ifile = parsemanager.preprocess_file_with_gcc(cfilename) + parsemanager.parse_ifile(ifile) + + for (faddr, dfn) in functions.items(): + fname = faddr.replace("0x", "sub_") + xpath = os.path.join(cname, "functions") + xpath = os.path.join(xpath, fname) + xfile = os.path.join(xpath, cname + "_" + fname + "_cfun.xml") + + if os.path.isfile(xfile): + try: + tree = ET.parse(xfile) + root = tree.getroot() + rootnode = root.find("function") + except ET.ParseError as e: + raise Exception("Error in parsing " + xfile + ": " + + str(e.code) + ", " + str(e.position)) + else: + print("Error: file " + xfile + " not found") + exit(1) + + if rootnode is None: + print("Error: No function node found for " + fname) + exit(1) + + xsbody = rootnode.find("sbody") + + + + exit(0) diff --git a/chb/ast/pirinspector b/chb/ast/pirinspector index cf65e9a9..45d71f46 100755 --- a/chb/ast/pirinspector +++ b/chb/ast/pirinspector @@ -5,7 +5,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2023 Aarno Labs, LLC +# Copyright (c) 2023-2025 Aarno Labs, LLC # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -156,6 +156,26 @@ def parse() -> argparse.Namespace: ) showaexprs.set_defaults(func=AU.showaexprscmd) + # ---------------------------------------------------------------- print --- + printcmd = subparsers.add_parser("print") + printparsers = printcmd.add_subparsers(title="show options") + + # --- print sourcecode + printsrc = printparsers.add_parser("src") + printsrc.add_argument( + "pirfile", help="name of json file with ast information") + printsrc.add_argument( + "function", help="name or address of funnction to print") + printsrc.set_defaults(func=AU.printsrccmd) + + # ---------------------------------------------------------------- parse --- + parsecmd = subparsers.add_parser("parse") + parsecmd.add_argument( + "pirfile", help="name of json file with ast information") + parsecmd.add_argument( + "cname", help="name of c file to be created (without extension)") + parsecmd.set_defaults(func=AU.parsecmd) + # -- parse args = parser.parse_args() return args diff --git a/chb/astparser/ASTCParseManager.py b/chb/astparser/ASTCParseManager.py new file mode 100644 index 00000000..8f5fb4c3 --- /dev/null +++ b/chb/astparser/ASTCParseManager.py @@ -0,0 +1,76 @@ +# ------------------------------------------------------------------------------ +# CodeHawk Binary Analyzer +# Author: Henny Sipma +# ------------------------------------------------------------------------------ +# The MIT License (MIT) +# +# Copyright (c) 2025 Aarno Labs LLC +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# ------------------------------------------------------------------------------ + +import os +import subprocess +import sys + +from typing import List + +from chb.util.Config import Config + + +class ASTCParseManager: + + def __init__(self) -> None: + pass + + def check_cparser(self) -> bool: + return os.path.isfile(Config().cparser) + + def preprocess_file_with_gcc( + self, cfilename: str, moreoptions: List[str] = []) -> str: + + ifilename = cfilename[:-1] + "i" + cmd = [ + "gcc", + "-fno-inline", + "-fno-builtin", + "-E", + "-g", + "-o", + ifilename, + cfilename] + cmd = cmd[:1] + moreoptions + cmd[1:] + + subprocess.call( + cmd, + cwd=os.getcwd(), + stdout=open(os.devnull, "w"), + stderr=subprocess.STDOUT, + ) + return ifilename + + def parse_ifile(self, ifilename: str) -> int: + cwd = os.getcwd() + ifilename = os.path.join(cwd, ifilename) + cmd = [Config().cparser, "-projectpath", cwd, "-targetdirectory", cwd] + cmd.append(ifilename) + p = subprocess.call(cmd, stderr=subprocess.STDOUT) + sys.stdout.flush() + return p + diff --git a/chb/astparser/__init__.py b/chb/astparser/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/chb/cmdline/commandutil.py b/chb/cmdline/commandutil.py index a92fbbfa..7be9a075 100644 --- a/chb/cmdline/commandutil.py +++ b/chb/cmdline/commandutil.py @@ -425,6 +425,16 @@ def analyzecmd(args: argparse.Namespace) -> NoReturn: logfilename: Optional[str] = args.logfilename logfilemode: str = args.logfilemode + if not os.path.isfile(Config().chx86_analyze): + print_error( + "CodeHawk analyzer executable not found.\n" + + ("~" * 80) + "\n" + + "Copy CHB/bchcmdline/chx86_analyze from the (compiled) " + + "codehawk repository to the\nchb/bin/binaries/linux directory " + + "in this directory, or\n" + + "set up ConfigLocal.py with another location for chx86_analyze") + exit(1) + try: (path, xfile) = get_path_filename(xname) except UF.CHBError as e: diff --git a/chb/util/Config.py b/chb/util/Config.py index 5a7594b7..39434bc0 100644 --- a/chb/util/Config.py +++ b/chb/util/Config.py @@ -65,6 +65,7 @@ def __init__(self) -> None: if self.platform == 'linux': self.linuxdir = os.path.join(self.binariesdir, "linux") self.chx86_analyze = os.path.join(self.linuxdir, "chx86_analyze") + self.cparser = os.path.join(self.linuxdir, "parseFile") elif self.platform == "macOS": self.macOSdir = os.path.join(self.binariesdir, "macOS")