diff --git a/chb/app/CHVersion.py b/chb/app/CHVersion.py index 76b145e3..3eeb4889 100644 --- a/chb/app/CHVersion.py +++ b/chb/app/CHVersion.py @@ -1,3 +1,3 @@ -chbversion: str = "0.3.0-20260324" +chbversion: str = "0.3.0-20260329" -minimum_required_chb_version = "0.6.0_20260324" +minimum_required_chb_version = "0.6.0_20260329" diff --git a/chb/arm/ARMCallOpcode.py b/chb/arm/ARMCallOpcode.py index 413fe1cb..d1ccde0f 100644 --- a/chb/arm/ARMCallOpcode.py +++ b/chb/arm/ARMCallOpcode.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2022-2025 Aarno Labs LLC +# Copyright (c) 2022-2026 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 @@ -173,8 +173,8 @@ def calltarget(self) -> "CallTarget": def annotation(self) -> str: tgt = str(self.calltarget) args = ", ".join(str(x) for x in self.arguments) - cargs = " (C: (" + ", ".join(str(x) for x in self.c_arguments) + "))" - call = "call " + str(tgt) + "(" + args + ")" + cargs + # cargs = " (C: (" + ", ".join(str(x) for x in self.c_arguments) + "))" + call = "call " + str(tgt) + "(" + args + ")" # + cargs return self.add_instruction_condition(call) diff --git a/chb/bctypes/BCAttrParam.py b/chb/bctypes/BCAttrParam.py index 803364c5..afc12a8d 100644 --- a/chb/bctypes/BCAttrParam.py +++ b/chb/bctypes/BCAttrParam.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2022 Aarno Labs LLC +# Copyright (c) 2022-2026 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 @@ -60,6 +60,7 @@ from chb.bctypes.BCDictionary import BCDictionary from chb.bctypes.BCTyp import BCTyp from chb.bctypes.BCTypSig import BCTypSig + from chb.bctypes.BCVisitor import BCVisitor class BCAttrParam(BCDictionaryRecord): @@ -104,6 +105,9 @@ def is_int(self) -> bool: def to_c_string(self) -> str: return str(self.intvalue) + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_attr_param_int(self) + def __str__(self) -> str: return "aint(" + str(self.intvalue) + ")" @@ -126,6 +130,9 @@ def strvalue(self) -> str: def to_c_string(self) -> str: return self.strvalue + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_attr_param_str(self) + def __str__(self) -> str: return "astr(" + self.strvalue + ")" @@ -158,6 +165,9 @@ def to_c_string(self) -> str: + ", ".join(p.to_c_string for p in self.params) + ")") + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_attr_param_cons(self) + def __str__(self) -> str: return ( "acons(" diff --git a/chb/bctypes/BCAttribute.py b/chb/bctypes/BCAttribute.py index 950b4af8..92c05fa7 100644 --- a/chb/bctypes/BCAttribute.py +++ b/chb/bctypes/BCAttribute.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2022-2023 Aarno Labs LLC +# Copyright (c) 2022-2026 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 @@ -35,6 +35,7 @@ if TYPE_CHECKING: from chb.bctypes.BCDictionary import BCDictionary from chb.bctypes.BCAttrParam import BCAttrParam + from chb.bctypes.BCVisitor import BCVisitor class BCAttribute(BCDictionaryRecord): @@ -61,6 +62,9 @@ def is_volatile(self) -> bool: def to_c_string(self) -> str: return self.name + "(" + ", ".join(p.to_c_string for p in self.params) + ")" + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_attribute(self) + def __str__(self) -> str: return self.name + "(" + ", ".join(str(p) for p in self.params) + ")" @@ -81,6 +85,9 @@ def attrs(self) -> List[BCAttribute]: def is_empty(self) -> bool: return len(self.attrs) == 0 + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_attributes(self) + @property def to_c_string(self) -> str: return ( diff --git a/chb/bctypes/BCCompInfo.py b/chb/bctypes/BCCompInfo.py index 48c6e3df..9b611531 100644 --- a/chb/bctypes/BCCompInfo.py +++ b/chb/bctypes/BCCompInfo.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2023 Aarno Labs LLC +# Copyright (c) 2021-2026 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 @@ -29,16 +29,17 @@ import chb.ast.ASTNode as AST -from chb.bctypes.BCConverter import BCConverter from chb.bctypes.BCDictionaryRecord import BCDictionaryRecord import chb.util.fileutil as UF import chb.util.IndexedTable as IT if TYPE_CHECKING: + from chb.bctypes.BCConverter import BCConverter from chb.bctypes.BCDictionary import BCDictionary from chb.bctypes.BCFieldInfo import BCFieldInfo from chb.bctypes.BCTyp import BCTyp + from chb.bctypes.BCVisitor import BCVisitor class OffsetAccumulator: @@ -209,7 +210,10 @@ def field_at_offset(self, offset: int) -> Tuple["BCFieldInfo", int]: + str(self.alignment()) + ")") - def convert(self, converter: BCConverter) -> AST.ASTCompInfo: + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_compinfo(self) + + def convert(self, converter: "BCConverter") -> AST.ASTCompInfo: return converter.convert_compinfo(self) def __str__(self) -> str: diff --git a/chb/bctypes/BCDictionary.py b/chb/bctypes/BCDictionary.py index 4532c942..ecbf4b4c 100644 --- a/chb/bctypes/BCDictionary.py +++ b/chb/bctypes/BCDictionary.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2024 Aarno Labs LLC +# Copyright (c) 2021-2026 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 @@ -263,6 +263,17 @@ def initialize(self, xnode: Optional[ET.Element]) -> None: cinfo = self.compinfo(ix) self.compinfo_keys[cinfo.ckey] = cinfo + # ------------------ Values ------------------------------------------------ + + def typeinfos(self) -> List["BCTypeInfo"]: + return [self.typeinfo(ix) for ix in self.typeinfo_table.keys()] + + def varinfos(self) -> List["BCVarInfo"]: + return [self.varinfo(ix) for ix in self.varinfo_table.keys()] + + def compinfos(self) -> List["BCCompInfo"]: + return [self.compinfo(ix) for ix in self.compinfo_table.keys()] + # ------------------ Printing ---------------------------------------------- def typ_table_to_string(self) -> str: diff --git a/chb/bctypes/BCEnumInfo.py b/chb/bctypes/BCEnumInfo.py index f7a52cbe..08abd711 100644 --- a/chb/bctypes/BCEnumInfo.py +++ b/chb/bctypes/BCEnumInfo.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2022 Aarno Labs LLC +# Copyright (c) 2022-2026 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 @@ -29,16 +29,17 @@ import chb.ast.ASTNode as AST -from chb.bctypes.BCConverter import BCConverter from chb.bctypes.BCDictionaryRecord import BCDictionaryRecord import chb.util.fileutil as UF import chb.util.IndexedTable as IT if TYPE_CHECKING: + from chb.bctypes.BCConverter import BCConverter from chb.bctypes.BCDictionary import BCDictionary from chb.bctypes.BCEnumItem import BCEnumItem from chb.bctypes.BCTyp import BCTyp + from chb.bctypes.BCVisitor import BCVisitor class BCEnumInfo(BCDictionaryRecord): @@ -61,7 +62,10 @@ def ekind(self) -> str: def enumitems(self) -> List["BCEnumItem"]: return [self.bcd.enumitem(i) for i in self.args[1:]] - def convert(self, converter: BCConverter) -> AST.ASTEnumInfo: + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_enuminfo(self) + + def convert(self, converter: "BCConverter") -> AST.ASTEnumInfo: return converter.convert_enuminfo(self) def __str__(self) -> str: diff --git a/chb/bctypes/BCEnumItem.py b/chb/bctypes/BCEnumItem.py index bd398da2..5635c212 100644 --- a/chb/bctypes/BCEnumItem.py +++ b/chb/bctypes/BCEnumItem.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2022 Aarno Labs LLC +# Copyright (c) 2022-2026 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 @@ -29,16 +29,17 @@ import chb.ast.ASTNode as AST -from chb.bctypes.BCConverter import BCConverter from chb.bctypes.BCDictionaryRecord import BCDictionaryRecord import chb.util.fileutil as UF import chb.util.IndexedTable as IT if TYPE_CHECKING: + from chb.bctypes.BCConverter import BCConverter from chb.bctypes.BCDictionary import BCDictionary - from chb.bctypes.BCTyp import BCTyp, BCTypArray, BCTypComp from chb.bctypes.BCExp import BCExp + from chb.bctypes.BCTyp import BCTyp, BCTypArray, BCTypComp + from chb.bctypes.BCVisitor import BCVisitor class BCEnumItem(BCDictionaryRecord): @@ -57,7 +58,10 @@ def itemname(self) -> str: def itemexpr(self) -> "BCExp": return self.bcd.exp(self.args[0]) - def convert(self, converter: BCConverter) -> AST.ASTEnumItem: + def accept(self, visitor: "BCVisitor") -> None: + return visitor.visit_enumitem(self) + + def convert(self, converter: "BCConverter") -> AST.ASTEnumItem: return converter.convert_enumitem(self) def __str__(self) -> str: diff --git a/chb/bctypes/BCFieldInfo.py b/chb/bctypes/BCFieldInfo.py index cc0f76ee..14ecb145 100644 --- a/chb/bctypes/BCFieldInfo.py +++ b/chb/bctypes/BCFieldInfo.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2023 Aarno Labs LLC +# Copyright (c) 2021-2026 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 @@ -29,7 +29,6 @@ import chb.ast.ASTNode as AST -from chb.bctypes.BCConverter import BCConverter from chb.bctypes.BCDictionaryRecord import BCDictionaryRecord import chb.util.fileutil as UF @@ -38,8 +37,10 @@ if TYPE_CHECKING: from chb.bctypes.BCAttribute import BCAttribute from chb.bctypes.BCAttrParam import BCAttrParam, BCAttrParamInt + from chb.bctypes.BCConverter import BCConverter from chb.bctypes.BCDictionary import BCDictionary from chb.bctypes.BCTyp import BCTyp, BCTypArray, BCTypComp + from chb.bctypes.BCVisitor import BCVisitor class BCFieldInfo(BCDictionaryRecord): @@ -90,7 +91,10 @@ def alignment(self) -> int: return self.fieldtype.alignment() - def convert(self, converter: BCConverter) -> AST.ASTFieldInfo: + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_fieldinfo(self) + + def convert(self, converter: "BCConverter") -> AST.ASTFieldInfo: return converter.convert_fieldinfo(self) def __str__(self) -> str: diff --git a/chb/bctypes/BCFiles.py b/chb/bctypes/BCFiles.py index 90d8866c..98589102 100644 --- a/chb/bctypes/BCFiles.py +++ b/chb/bctypes/BCFiles.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2024 Aarno Labs LLC +# Copyright (c) 2021-2026 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 @@ -49,6 +49,7 @@ class BCFiles: def __init__(self, app: "AppAccess", xnode: ET.Element) -> None: self._app = app + self._typeinfos: List[BCTypeInfo] = [] self._gtypes: List[BCTyp] = [] self._gcomptags: List[BCCompInfo] = [] self._genumtags: List[BCEnumInfo] = [] @@ -66,6 +67,10 @@ def app(self) -> "AppAccess": def bcd(self) -> BCDictionary: return self.app.bcdictionary + @property + def typeinfos(self) -> List[BCTypeInfo]: + return self._typeinfos + @property def gtypes(self) -> List[BCTyp]: return self._gtypes diff --git a/chb/bctypes/BCFunArgs.py b/chb/bctypes/BCFunArgs.py index b8ed06a2..c264c6fd 100644 --- a/chb/bctypes/BCFunArgs.py +++ b/chb/bctypes/BCFunArgs.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2022 Aarno Labs LLC +# Copyright (c) 2021-2026 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 @@ -60,6 +60,9 @@ def typ(self) -> "BCTyp": def is_leq(self, other: "BCFunArg") -> bool: return self.typ.is_leq(other.typ) + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_funarg(self) + def convert(self, converter: "BCConverter") -> AST.ASTFunArg: return converter.convert_funarg(self) @@ -115,6 +118,9 @@ def is_scalar_argtypes(self) -> bool: def is_leq(self, other: "BCFunArgs") -> bool: return all(a.is_leq(o) for (a, o) in zip(self.funargs, other.funargs)) + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_funargs(self) + def convert(self, converter: "BCConverter") -> AST.ASTFunArgs: return converter.convert_funargs(self) diff --git a/chb/bctypes/BCHeaderFile.py b/chb/bctypes/BCHeaderFile.py new file mode 100644 index 00000000..5ac9d50a --- /dev/null +++ b/chb/bctypes/BCHeaderFile.py @@ -0,0 +1,447 @@ +# ------------------------------------------------------------------------------ +# CodeHawk Binary Analyzer +# Author: Henny Sipma +# ------------------------------------------------------------------------------ +# The MIT License (MIT) +# +# Copyright (c) 2026 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. +# ------------------------------------------------------------------------------ + +from datetime import datetime +from typing import cast, Dict, List, Tuple, TYPE_CHECKING + +from chb.bctypes.BCVisitor import BCVisitor + +if TYPE_CHECKING: + from chb.app.Instruction import Instruction + from chb.bctypes.BCAttribute import BCAttribute, BCAttributes + import chb.bctypes.BCAttrParam as AP + from chb.bctypes.BCCompInfo import BCCompInfo + import chb.bctypes.BCConstant as BCC + from chb.bctypes.BCEnumInfo import BCEnumInfo + from chb.bctypes.BCEnumItem import BCEnumItem + import chb.bctypes.BCExp as BCE + from chb.bctypes.BCFieldInfo import BCFieldInfo + from chb.bctypes.BCFunArgs import BCFunArgs, BCFunArg + from chb.bctypes.BCLHost import BCHostVar, BCHostMem + from chb.bctypes.BCLval import BCLval + from chb.bctypes.BCOffset import BCNoOffset, BCFieldOffset, BCIndexOffset + import chb.bctypes.BCTyp as BCT + from chb.bctypes.BCTypeInfo import BCTypeInfo + from chb.bctypes.BCVarInfo import BCVarInfo + + +def get_varinfo_functions( + varinfos: List["BCVarInfo"] +) -> Tuple[List["BCVarInfo"], List["BCVarInfo"]]: + + fns: List["BCVarInfo"] = [] + newfns: List["BCVarInfo"] = [] + for vinfo in varinfos: + if vinfo.vtype.is_function: + if vinfo.vid >= 10000: + ftype = cast("BCT.BCTypFun", vinfo.vtype) + if ftype.has_arguments(): + newfns.append(vinfo) + else: + pass + else: + fns.append(vinfo) + return (fns, newfns) + + +class BCHeaderCode: + + def __init__(self) -> None: + self._outputlines: List[str] = [] + self._pos: int = 0 # position in the line + + @property + def outputlines(self) -> List[str]: + return self._outputlines + + @property + def pos(self) -> int: + return self._pos + + def newline(self, indent: int = 0) -> None: + self._outputlines.append(" " * indent) + self._pos = indent + + def write(self, s: str) -> None: + self._outputlines[-1] += s + self._pos += len(s) + + def __str__(self) -> str: + return "\n".join(self.outputlines) + + +class BCHeaderPrettyPrinter(BCVisitor): + + def __init__( + self, + varinfos: List["BCVarInfo"], + typeinfos: List["BCTypeInfo"], + compinfos: List["BCCompInfo"], + indentation: int = 2) -> None: + self._varinfos = varinfos + self._typeinfos = typeinfos + self._compinfos = compinfos + self._indentation = indentation + self._indent = 0 + self._ccode = BCHeaderCode() + self._newfunctionsmode: bool = False + self._returntype_replacements = 0 + self._parameter_type_replacements = 0 + + @property + def indentation(self) -> int: + return self._indentation + + @property + def indent(self) -> int: + return self._indent + + def increase_indent(self) -> None: + self._indent += self.indentation + + def decrease_indent(self) -> None: + self._indent -= self.indentation + + @property + def ccode(self) -> BCHeaderCode: + return self._ccode + + @property + def varinfos(self) -> List["BCVarInfo"]: + return self._varinfos + + @property + def typeinfos(self) -> List["BCTypeInfo"]: + return self._typeinfos + + @property + def compinfos(self) -> List["BCCompInfo"]: + return self._compinfos + + @property + def returntype_replacements(self) -> int: + return self._returntype_replacements + + @property + def parameter_type_replacements(self) -> int: + return self._parameter_type_replacements + + def set_newfunctions_mode(self) -> None: + self._newfunctionsmode = True + + def increment_returntype_replacements(self) -> None: + self._returntype_replacements += 1 + + def increment_parameter_type_replacements(self) -> None: + self._parameter_type_replacements += 1 + + @property + def is_newfunctionsmode(self) -> bool: + return self._newfunctionsmode + + def to_header_file(self, callers: Dict[str, List["Instruction"]] = {}) -> str: + self.ccode.newline() + self.ccode.write("// Header file from generated signatures") + self.ccode.newline() + self.ccode.write("// Date: " + datetime.today().strftime("%Y-%m-%d")) + self.ccode.newline() + self.ccode.newline() + if len(self.typeinfos) > 0: + self.ccode.newline() + self.ccode.write("// Type definitions") + self.ccode.newline() + for typeinfo in self.typeinfos: + self.ccode.newline() + typeinfo.accept(self) + self.ccode.newline() + self.ccode.newline() + for cinfo in self.compinfos: + self.ccode.write( + "// compinfo " + cinfo.cname + " (" + str(cinfo.ckey) + ")") + self.ccode.newline() + cinfo.accept(self) + self.ccode.newline() + self.ccode.newline() + self.ccode.newline() + (fns, newfns) = get_varinfo_functions(self.varinfos) + for vinfo in fns: + if vinfo.vname in callers: + self.write_callers(callers[vinfo.vname]) + vinfo.accept(self) + self.ccode.newline() + self.ccode.newline() + self.ccode.newline() + self.ccode.newline() + self.set_newfunctions_mode() + self.ccode.write("// Newly generated function signatures (") + self.ccode.write(str(len(newfns)) + ")") + self.ccode.newline() + self.ccode.newline() + for vinfo in newfns: + if self.check_vinfo_for_inclusion(vinfo): + vinfo.accept(self) + self.ccode.newline() + self.ccode.newline() + self.ccode.newline() + self.ccode.newline() + if self.returntype_replacements > 0: + self.ccode.write("// " + str(self.returntype_replacements) + + " return types were replaced by int") + self.ccode.newline() + if self.parameter_type_replacements > 0: + self.ccode.write("// " + str(self.parameter_type_replacements) + + " parameter types were replaced by void*") + return str(self.ccode) + + def write_callers(self, instrs: List["Instruction"]) -> None: + for instr in instrs: + self.ccode.newline() + self.ccode.write("// ") + self.ccode.write(instr.annotation) + self.ccode.newline() + + def check_vinfo_for_inclusion(self, vinfo: "BCVarInfo") -> bool: + ftype = cast("BCT.BCTypFun", vinfo.vtype) + argtypes = ftype.argtypes + if argtypes is None: + return False + + funargs = argtypes.funargs + if len(funargs) == 0: + return False + + if ftype.returntype.is_unknown: + self.increment_returntype_replacements() + self.ccode.write("// Replacing unknown returntype by int") + self.ccode.newline() + for (i, arg) in enumerate(funargs): + if arg.typ.is_unknown: + self.increment_parameter_type_replacements() + self.ccode.write("// Replacing unknown type of argument ") + self.ccode.write(str(i + 1)) + self.ccode.write(" with void *") + self.ccode.newline() + return True + + def visit_lval(self, lval: "BCLval") -> None: + pass + + def visit_varinfo(self, vinfo: "BCVarInfo") -> None: + if vinfo.vtype.is_function: + ftype = cast("BCT.BCTypFun", vinfo.vtype) + if ftype.returntype.is_unknown: + self.ccode.write("int") + else: + ftype.returntype.accept(self) + self.ccode.write(" ") + self.ccode.write(vinfo.vname) + self.ccode.write("(") + if ftype.argtypes is not None: + ftype.argtypes.accept(self) + if ftype.is_vararg: + self.ccode.write(", ...") + self.ccode.write(")") + if vinfo.attributes is None or vinfo.attributes.is_empty: + self.ccode.write(";") + else: + self.ccode.newline() + vinfo.attributes.accept(self) + self.ccode.write(";") + else: + pass + + def visit_variable(self, var: "BCHostVar") -> None: + pass + + def visit_memref(self, memref: "BCHostMem") -> None: + pass + + def visit_no_offset(self, offset: "BCNoOffset") -> None: + pass + + def visit_field_offset(self, offset: "BCFieldOffset") -> None: + pass + + def visit_index_offset(self, offset: "BCIndexOffset") -> None: + pass + + def visit_integer_constant(self, c: "BCC.BCCInt64") -> None: + pass + + def visit_string_constant(self, c: "BCC.BCStr") -> None: + pass + + def visit_lval_expression(self, expr: "BCE.BCExpLval") -> None: + pass + + def visit_cast_expression(self, expr: "BCE.BCExpCastE") -> None: + pass + + def visit_unary_expression(self, expr: "BCE.BCExpUnOp") -> None: + pass + + def visit_binary_expression(self, expr: "BCE.BCExpBinOp") -> None: + pass + + def visit_question_expression(self, expr: "BCE.BCExpQuestion") -> None: + pass + + def visit_address_of_expression(self, expr: "BCE.BCExpAddressOf") -> None: + pass + + def visit_void_typ(self, typ: "BCT.BCTypVoid") -> None: + self.ccode.write("void") + + def visit_integer_typ(self, typ: "BCT.BCTypInt") -> None: + self.ccode.write(str(typ)) + + def visit_float_typ(self, typ: "BCT.BCTypFloat") -> None: + self.ccode.write(str(typ)) + + def visit_pointer_typ(self, typ: "BCT.BCTypPtr") -> None: + typ.tgttyp.accept(self) + self.ccode.write(" *") + + def visit_array_typ(self, typ: "BCT.BCTypArray") -> None: + pass + + def visit_fun_typ(self, typ: "BCT.BCTypFun") -> None: + typ.returntype.accept(self) + self.ccode.write(" (") + if typ.argtypes is not None: + typ.argtypes.accept(self) + self.ccode.write(")") + + def visit_funargs(self, funargs: "BCFunArgs") -> None: + args = funargs.funargs + if len(args) == 0: + self.ccode.write("void") + else: + for arg in args[:-1]: + arg.accept(self) + self.ccode.write(", ") + args[-1].accept(self) + + def visit_funarg(self, funarg: "BCFunArg") -> None: + if funarg.typ.is_unknown: + self.ccode.write("void *") + else: + funarg.typ.accept(self) + self.ccode.write(" ") + self.ccode.write(funarg.name) + + def visit_named_typ(self, typ: "BCT.BCTypNamed") -> None: + self.ccode.write(typ.tname) + + def visit_comp_typ(self, typ: "BCT.BCTypComp") -> None: + self.ccode.write("struct " + typ.compname) + + def visit_compinfo(self, cinfo: "BCCompInfo") -> None: + self.ccode.write("struct " + cinfo.cname + " {") + if len(cinfo.fieldinfos) > 0: + self.increase_indent() + self.ccode.newline(indent=self.indent) + for (i, finfo) in enumerate(cinfo.fieldinfos): + finfo.fieldtype.accept(self) + self.ccode.write(" ") + self.ccode.write(finfo.fieldname) + self.ccode.write(";") + if i < len(cinfo.fieldinfos) - 1: + self.ccode.newline(indent=self.indent) + self.decrease_indent() + self.ccode.newline() + self.ccode.write("};") + self.ccode.newline() + else: + self.ccode.write(" };") + self.ccode.newline() + + def visit_fieldinfo(self, finfo: "BCFieldInfo") -> None: + pass + + def visit_enum_typ(self, typ: "BCT.BCTypEnum") -> None: + self.ccode.write("enum " + typ.ename) + + def visit_enuminfo(self, einfo: "BCEnumInfo") -> None: + self.ccode.write("enuminfo: Not yet implemented") + + def visit_enumitem(self, eitem: "BCEnumItem") -> None: + pass + + def visit_typeinfo(self, tinfo: "BCTypeInfo") -> None: + self.ccode.write("typedef ") + tinfo.ttype.accept(self) + self.ccode.write(" ") + self.ccode.write(tinfo.tname) + self.ccode.write(";") + + def visit_attributes(self, attrs: "BCAttributes") -> None: + if len(attrs.attrs) == 0: + return + + self.ccode.write(" __attribute__((") + attrs.attrs[0].accept(self) + if len(attrs.attrs) == 1: + self.ccode.write("))") + return + + for attr in attrs.attrs[1:]: + self.ccode.write(",") + self.ccode.newline(indent=17) + attr.accept(self) + self.ccode.newline() + self.ccode.write(" ))") + + def visit_attribute(self, attr: "BCAttribute") -> None: + self.ccode.write(attr.name) + self.ccode.write("(") + if len(attr.params) == 0: + pass + else: + for param in attr.params[:-1]: + param.accept(self) + self.ccode.write(", ") + attr.params[-1].accept(self) + self.ccode.write(")") + + def visit_attr_param_int(self, param: "AP.BCAttrParamInt") -> None: + self.ccode.write(str(param.intvalue)) + + def visit_attr_param_str(self, param: "AP.BCAttrParamStr") -> None: + self.ccode.write('"' + param.strvalue + '"') + + def visit_attr_param_cons(self, param: "AP.BCAttrParamCons") -> None: + self.ccode.write(param.name) + if len(param.params) > 0: + self.ccode.write("(") + for p in param.params[:-1]: + p.accept(self) + self.ccode.write(", ") + param.params[-1].accept(self) + self.ccode.write(")") + else: + self.ccode.write("()") diff --git a/chb/bctypes/BCTyp.py b/chb/bctypes/BCTyp.py index 77ad23b2..2588760b 100644 --- a/chb/bctypes/BCTyp.py +++ b/chb/bctypes/BCTyp.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2025 Aarno Labs LLC +# Copyright (c) 2021-2026 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 @@ -244,6 +244,9 @@ def returntype(self) -> "BCTyp": def argtypes(self) -> Optional["BCFunArgs"]: raise UF.CHBError("Type is not a function: " + str(self)) + def accept(self, visitor: "BCVisitor") -> None: + raise NotImplementedError("BCTyp.visit: " + self.tags[0]) + def convert(self, converter: "BCConverter") -> AST.ASTTyp: raise NotImplementedError("BCTyp.convert: " + self.tags[0]) @@ -267,6 +270,9 @@ def is_void(self) -> bool: def is_leq(self, other: "BCTyp") -> bool: return other.is_void + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_void_typ(self) + def convert(self, converter: "BCConverter") -> AST.ASTTypVoid: return converter.convert_void_typ(self) @@ -305,6 +311,9 @@ def is_leq(self, other: "BCTyp") -> bool: def byte_size(self) -> int: return size_of_integer_type(self.ikind) + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_integer_typ(self) + def convert(self, converter: "BCConverter") -> AST.ASTTypInt: return converter.convert_integer_typ(self) @@ -343,6 +352,9 @@ def is_leq(self, other: "BCTyp") -> bool: def byte_size(self) -> int: return size_of_float_type(self.fkind) + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_float_typ(self) + def convert(self, converter: "BCConverter") -> AST.ASTTypFloat: return converter.convert_float_typ(self) @@ -384,6 +396,9 @@ def byte_size(self) -> int: return 4 + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_pointer_typ(self) + def convert(self, converter: "BCConverter") -> AST.ASTTypPtr: return converter.convert_pointer_typ(self) @@ -516,6 +531,13 @@ def argtypes(self) -> Optional["BCFunArgs"]: def is_vararg(self) -> bool: return self.args[2] == 1 + def has_arguments(self) -> bool: + argtypes = self.argtypes + if argtypes is not None: + return len(argtypes.funargs) > 0 + else: + return False + @property def is_function(self) -> bool: return True @@ -535,6 +557,9 @@ def is_leq(self, other: "BCTyp") -> bool: else: return False + def accept(self, visitor: "BCVisitor") -> None: + return visitor.visit_fun_typ(self) + def convert(self, converter: "BCConverter") -> AST.ASTTypFun: return converter.convert_fun_typ(self) @@ -695,6 +720,9 @@ def alignment(self) -> int: else: raise UF.CHBError("No type definition for " + self.tname) + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_named_typ(self) + def convert(self, converter: "BCConverter") -> AST.ASTTyp: return converter.convert_named_typ(self) @@ -747,6 +775,9 @@ def byte_size(self) -> int: def alignment(self) -> int: return self.compinfo.alignment() + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_comp_typ(self) + def convert(self, converter: "BCConverter") -> AST.ASTTypComp: return converter.convert_comp_typ(self) @@ -778,6 +809,9 @@ def is_scalar(self) -> bool: def byte_size(self) -> int: return 4 + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_enum_typ(self) + def convert(self, converter: "BCConverter") -> AST.ASTTypEnum: return converter.convert_enum_typ(self) diff --git a/chb/bctypes/BCTypeInfo.py b/chb/bctypes/BCTypeInfo.py index a28b36d9..4e9b728e 100644 --- a/chb/bctypes/BCTypeInfo.py +++ b/chb/bctypes/BCTypeInfo.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2023 Aarno Labs LLC +# Copyright (c) 2021-2026 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 @@ -35,6 +35,7 @@ if TYPE_CHECKING: from chb.bctypes.BCDictionary import BCDictionary from chb.bctypes.BCTyp import BCTyp + from chb.bctypes.BCVisitor import BCVisitor class BCTypeInfo(BCDictionaryRecord): @@ -53,5 +54,8 @@ def tname(self) -> str: def ttype(self) -> "BCTyp": return self.bcd.typ(self.args[0]) + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_typeinfo(self) + def __str__(self) -> str: return "typeinfo: " + self.tname + ": " + str(self.ttype) diff --git a/chb/bctypes/BCVarInfo.py b/chb/bctypes/BCVarInfo.py index 4a31c900..19fa8e07 100644 --- a/chb/bctypes/BCVarInfo.py +++ b/chb/bctypes/BCVarInfo.py @@ -40,6 +40,7 @@ if TYPE_CHECKING: from chb.bctypes.BCDictionary import BCDictionary from chb.bctypes.BCTyp import BCTyp, BCTypArray, BCTypFun + from chb.bctypes.BCVisitor import BCVisitor class BCVarInfo(BCDictionaryRecord): @@ -70,6 +71,9 @@ def vparam(self) -> int: def attributes(self) -> Optional["BCAttributes"]: return self.bcd.attributes(self.args[2]) + def accept(self, visitor: "BCVisitor") -> None: + visitor.visit_varinfo(self) + def convert(self, converter: "BCConverter") -> AST.ASTVarInfo: return converter.convert_varinfo(self) @@ -99,7 +103,13 @@ def to_c_string(self) -> str: pfattrs = "\n" + fattrs.to_c_string else: pfattrs = "" - return (str(ftype.returntype) + " " + self.vname + argtypes + pfattrs) + ";" + return ( + str(ftype.returntype) + + " " + + self.vname + + argtypes + + pfattrs + + ";") else: return str(self.vtype) + " " + self.vname + ";" diff --git a/chb/bctypes/BCVisitor.py b/chb/bctypes/BCVisitor.py index 85f140a2..3eff9a54 100644 --- a/chb/bctypes/BCVisitor.py +++ b/chb/bctypes/BCVisitor.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2022 Aarno Labs LLC +# Copyright (c) 2022-2026 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 @@ -30,8 +30,12 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: + from chb.bctypes.BCAttribute import BCAttribute, BCAttributes + import chb.bctypes.BCAttrParam as AP from chb.bctypes.BCCompInfo import BCCompInfo import chb.bctypes.BCConstant as BCC + from chb.bctypes.BCEnumInfo import BCEnumInfo + from chb.bctypes.BCEnumItem import BCEnumItem import chb.bctypes.BCExp as BCE from chb.bctypes.BCFieldInfo import BCFieldInfo from chb.bctypes.BCFunArgs import BCFunArgs, BCFunArg @@ -39,6 +43,7 @@ from chb.bctypes.BCLval import BCLval from chb.bctypes.BCOffset import BCNoOffset, BCFieldOffset, BCIndexOffset import chb.bctypes.BCTyp as BCT + from chb.bctypes.BCTypeInfo import BCTypeInfo from chb.bctypes.BCVarInfo import BCVarInfo @@ -142,3 +147,51 @@ def visit_funarg(self, funarg: "BCFunArg") -> None: @abstractmethod def visit_named_typ(self, typ: "BCT.BCTypNamed") -> None: ... + + @abstractmethod + def visit_comp_typ(self, typ: "BCT.BCTypComp") -> None: + ... + + @abstractmethod + def visit_compinfo(self, cinfo: "BCCompInfo") -> None: + ... + + @abstractmethod + def visit_fieldinfo(self, finfo: "BCFieldInfo") -> None: + ... + + @abstractmethod + def visit_enum_typ(self, typ: "BCT.BCTypEnum") -> None: + ... + + @abstractmethod + def visit_enuminfo(self, einfo: "BCEnumInfo") -> None: + ... + + @abstractmethod + def visit_enumitem(self, eitem: "BCEnumItem") -> None: + ... + + @abstractmethod + def visit_typeinfo(self, tinfo: "BCTypeInfo") -> None: + ... + + @abstractmethod + def visit_attributes(self, attrs: "BCAttributes") -> None: + ... + + @abstractmethod + def visit_attribute(self, attr: "BCAttribute") -> None: + ... + + @abstractmethod + def visit_attr_param_int(self, param: "AP.BCAttrParamInt") -> None: + ... + + @abstractmethod + def visit_attr_param_str(self, param: "AP.BCAttrParamStr") -> None: + ... + + @abstractmethod + def visit_attr_param_cons(self, param: "AP.BCAttrParamCons") -> None: + ... diff --git a/chb/cmdline/chkx b/chb/cmdline/chkx index c1f7fbb1..d273e47a 100755 --- a/chb/cmdline/chkx +++ b/chb/cmdline/chkx @@ -1006,6 +1006,16 @@ def parse() -> argparse.Namespace: resultsfunction.set_defaults(func=UCC.results_function) + # --- results cheader --- + resultscheader = resultsparsers.add_parser("c_header") + resultscheader.add_argument("xname", help="name of executable") + resultscheader.add_argument("--output", "-o", help="name of output file") + resultscheader.add_argument( + "--include_callers", + action="store_true", + help="print the instructions calling a declared function") + resultscheader.set_defaults(func = UCC.results_cheader) + # --- results callback tables --- resultscallbacktables = resultsparsers.add_parser("callbacktables") resultscallbacktables.add_argument("xname", help="name of executable") diff --git a/chb/cmdline/commandutil.py b/chb/cmdline/commandutil.py index e7108e79..7d474ee9 100644 --- a/chb/cmdline/commandutil.py +++ b/chb/cmdline/commandutil.py @@ -5,7 +5,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2025 Aarno Labs, LLC +# Copyright (c) 2021-2026 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 @@ -68,6 +68,7 @@ from chb.arm.ARMAssembly import ARMAssembly from chb.bctypes.BCFiles import BCFiles +from chb.bctypes.BCHeaderFile import BCHeaderPrettyPrinter from chb.cmdline.AnalysisManager import AnalysisManager @@ -1299,6 +1300,50 @@ def results_function(args: argparse.Namespace) -> NoReturn: exit(1) +def results_cheader(args: argparse.Namespace) -> NoReturn: + + # arguments + xname: str = str(args.xname) + outputfilename: Optional[str] = args.output + include_callers: bool = args.include_callers + + try: + (path, xfile) = get_path_filename(xname) + UF.check_analysis_results(path, xfile) + except UF.CHBError as e: + print_error(str(e.wrap())) + exit(1) + + xinfo = XI.XInfo() + xinfo.load(path, xfile) + + app = get_app(path, xfile, xinfo) + + callers: Dict[str, List["Instruction"]] = {} + if include_callers: + callinstrs = app.call_instructions() + for (faddr, fcalls) in callinstrs.items(): + for (baddr, bcalls) in fcalls.items(): + for c in bcalls: + tgt = c.call_target.name + callers.setdefault(tgt, []) + callers[tgt].append(c) + + cheader = BCHeaderPrettyPrinter( + app.bcdictionary.varinfos(), + app.bcdictionary.typeinfos(), + app.bcfiles.gcomptags) + cheader_str = cheader.to_header_file(callers) + + if outputfilename is not None: + with open(outputfilename, "w") as fp: + fp.write(cheader_str) + else: + print(cheader_str) + + exit(0) + + def results_callbacktables(args: argparse.Namespace) -> NoReturn: """Prints or saves information regarding callback tables."""