diff --git a/chb/api/FormatStringSpec.py b/chb/api/FormatStringSpec.py new file mode 100644 index 00000000..dd40f880 --- /dev/null +++ b/chb/api/FormatStringSpec.py @@ -0,0 +1,142 @@ +# ------------------------------------------------------------------------------ +# 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 dataclasses import dataclass +from typing import List, Optional, TYPE_CHECKING + +from chb.api.InterfaceDictionaryRecord import InterfaceDictionaryRecord + +import chb.util.fileutil as UF + +from chb.util.IndexedTable import IndexedTableValue + +if TYPE_CHECKING: + from chb.api.InterfaceDictionary import InterfaceDictionary + + +@dataclass +class FmtArgFieldWidth: + s: str + + def has_fieldwidth(self) -> bool: + return self.s.startswith("fwc:") + + def has_fieldwidth_argument(self) -> bool: + return self.s == "fwa" + + def fieldwidth(self) -> Optional[int]: + if self.has_fieldwidth(): + return int(self.s[4:]) + return None + + def __str__(self) -> str: + if self.has_fieldwidth(): + return self.s[4:] + elif self.has_fieldwidth_argument(): + return "*" + else: + return "" + +@dataclass +class FmtArgPrecision: + s: str + + def has_precision(self) -> bool: + return self.s.startswith("pc:") + + def has_precision_argument(self) -> bool: + return self.s == "pa" + + def precision(self) -> Optional[int]: + if self.has_precision(): + return int(self.s[3:]) + return None + + def __str__(self) -> str: + if self.has_precision(): + return "." + self.s[3:] + elif self.has_precision_argument(): + return ".*" + else: + return "" + + +class FormatArgSpec(InterfaceDictionaryRecord): + + def __init__( + self, ixd: "InterfaceDictionary", ixval: IndexedTableValue) -> None: + InterfaceDictionaryRecord.__init__(self, ixd, ixval) + + @property + def fieldwidth(self) -> FmtArgFieldWidth: + return FmtArgFieldWidth(self.tags[0]) + + @property + def precision(self) -> FmtArgPrecision: + return FmtArgPrecision(self.tags[1]) + + @property + def lengthmodifier(self) -> str: + if self.tags[2] == "none": + return "" + else: + return self.tags[2] + + @property + def conversion(self) -> str: + return self.tags[3] + + @property + def flags(self) -> str: + return "".join(chr(i) for i in self.args) + + def __str__(self): + return ( + "%" + + self.flags + + str(self.fieldwidth) + + str(self.precision) + + str(self.lengthmodifier) + + self.conversion) + + +class FormatStringSpec(InterfaceDictionaryRecord): + + def __init__( + self, ixd: "InterfaceDictionary", ixval: IndexedTableValue) -> None: + InterfaceDictionaryRecord.__init__(self, ixd, ixval) + + @property + def argspecs(self) -> List[FormatArgSpec]: + return [self.id.formatarg_spec(ix) for ix in self.args[1:]] + + @property + def literal_length(self) -> int: + return self.args[0] + + def __str__(self) -> str: + return ", ".join(str(s) for s in self.argspecs) diff --git a/chb/api/InterfaceDictionary.py b/chb/api/InterfaceDictionary.py index f5d1ea8a..1dd50417 100644 --- a/chb/api/InterfaceDictionary.py +++ b/chb/api/InterfaceDictionary.py @@ -6,7 +6,7 @@ # # Copyright (c) 2016-2020 Kestrel Technology LLC # Copyright (c) 2020 Henny Sipma -# 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 @@ -33,6 +33,7 @@ from typing import List, TYPE_CHECKING from chb.api.BTerm import BTerm +from chb.api.FormatStringSpec import FormatStringSpec, FormatArgSpec from chb.api.FormatStringType import FormatStringType from chb.api.FtsParameter import FtsParameter from chb.api.FtsParameterLocation import FtsParameterLocation @@ -62,6 +63,8 @@ def __init__( app: "AppAccess", xnode: ET.Element) -> None: self._app = app + self.formatarg_spec_table = IT.IndexedTable("formatarg-spec-table") + self.formatstring_spec_table = IT.IndexedTable("formatstring-spec-table") self.function_stub_table = IT.IndexedTable("function-stub-table") self.call_target_table = IT.IndexedTable("call-target-table") self.formatstring_type_table = IT.IndexedTable("formatstring-type-table") @@ -79,6 +82,8 @@ def __init__( self.bterm_table = IT.IndexedTable("bterm-table") self.tables: List[IT.IndexedTable] = [ self.formatstring_type_table, + self.formatarg_spec_table, + self.formatstring_spec_table, self.parameter_location_table, self.parameter_location_list_table, self.function_stub_table, @@ -113,6 +118,12 @@ def formatstring_type(self, ix: int) -> FormatStringType: return apiregistry.mk_instance( self, self.formatstring_type_table.retrieve(ix), FormatStringType) + def formatarg_spec(self, ix: int) -> FormatArgSpec: + return FormatArgSpec(self, self.formatarg_spec_table.retrieve(ix)) + + def formatstring_spec(self, ix: int) -> FormatStringSpec: + return FormatStringSpec(self, self.formatstring_spec_table.retrieve(ix)) + def parameter_location(self, ix: int) -> FtsParameterLocation: return apiregistry.mk_instance( self, self.parameter_location_table.retrieve(ix), FtsParameterLocation) diff --git a/chb/app/AppAccess.py b/chb/app/AppAccess.py index dcdf46c8..38d73aa2 100644 --- a/chb/app/AppAccess.py +++ b/chb/app/AppAccess.py @@ -343,7 +343,7 @@ def function_info(self, faddr: str) -> FunctionInfo: if faddr not in self._functioninfos: xnode = UF.get_function_info_xnode(self.path, self.filename, faddr) self._functioninfos[faddr] = FunctionInfo( - self.interfacedictionary, faddr, xnode) + self.bdictionary, self.interfacedictionary, faddr, xnode) return self._functioninfos[faddr] # Instructions ----------------------------------------------------------- diff --git a/chb/app/CHVersion.py b/chb/app/CHVersion.py index 67272060..3861035a 100644 --- a/chb/app/CHVersion.py +++ b/chb/app/CHVersion.py @@ -1,3 +1,3 @@ -chbversion: str = "0.3.0-20260614" +chbversion: str = "0.3.0-20260617" -minimum_required_chb_version = "0.6.0_20260614" +minimum_required_chb_version = "0.6.0_20260617" diff --git a/chb/app/Function.py b/chb/app/Function.py index 6d7c8899..02706b8b 100644 --- a/chb/app/Function.py +++ b/chb/app/Function.py @@ -89,6 +89,7 @@ if TYPE_CHECKING: + from chb.api.FormatStringSpec import FormatStringSpec from chb.app.AppAccess import AppAccess from chb.app.FunctionStackframe import FunctionStackframe from chb.app.GlobalMemoryMap import ( @@ -409,6 +410,10 @@ def register_lhs_type(self, iaddr: str, reg: str) -> Optional["BCTyp"]: return None + @property + def formatstrings(self) -> Mapping[str, Tuple[str, "FormatStringSpec"]]: + return self.finfo.formatstrings + @property def lhs_names(self) -> Dict[str, str]: return self.finfo.lhs_names diff --git a/chb/app/FunctionInfo.py b/chb/app/FunctionInfo.py index 8e4d6664..1555844a 100644 --- a/chb/app/FunctionInfo.py +++ b/chb/app/FunctionInfo.py @@ -6,7 +6,7 @@ # # Copyright (c) 2016-2020 Kestrel Technology LLC # Copyright (c) 2020 Henny Sipma -# 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 @@ -30,7 +30,7 @@ import xml.etree.ElementTree as ET -from typing import Dict, Mapping, Optional, TYPE_CHECKING +from typing import Dict, Mapping, Optional, Tuple, TYPE_CHECKING from chb.api.AppSummary import AppSummary from chb.api.CallTarget import CallTarget @@ -38,27 +38,36 @@ import chb.util.fileutil as UF if TYPE_CHECKING: + from chb.api.FormatStringSpec import FormatStringSpec from chb.api.InterfaceDictionary import InterfaceDictionary + from chb.app.BDictionary import BDictionary class FunctionInfo: def __init__( self, + bd: "BDictionary", ixd: "InterfaceDictionary", faddr: str, xnode: ET.Element) -> None: + self._bd = bd self._ixd = ixd self._faddr = faddr self.xnode = xnode self._calltargets: Dict[str, CallTarget] = {} self._variablenames: Dict[int, str] = {} self._calltargetinfos: Dict[str, CallTargetInfo] = {} + self._formatstringspecs: Optional[Dict[str, Tuple[str,FormatStringSpec]]] = None @property def faddr(self) -> str: return self._faddr + @property + def bd(self) -> "BDictionary": + return self._bd + @property def ixd(self) -> "InterfaceDictionary": return self._ixd @@ -115,6 +124,23 @@ def calltargetinfos(self) -> Mapping[str, CallTargetInfo]: ctgt, fintf, fsem) return self._calltargetinfos + @property + def formatstrings(self) -> Mapping[str, Tuple[str, "FormatStringSpec"]]: + if self._formatstringspecs is None: + self._formatstringspecs = {} + fsnode = self.xnode.find("format-strings") + if fsnode is not None: + for fs in fsnode.findall("fs"): + xaddr = fs.get("a") + ixs = int(fs.get("ixs", "-1")) + ixc = int(fs.get("ixc", "-1")) + kind = fs.get("kind", "printf") + if xaddr is not None and ixs > 0 and ixc > 0: + formatstring = self.bd.string(ixs) + formatspec = self.ixd.formatstring_spec(ixc) + self._formatstringspecs[xaddr] = (formatstring, formatspec) + return self._formatstringspecs + @property def lhs_names(self) -> Dict[str, str]: result: Dict[str, str] = {}