Skip to content

Commit 3975e55

Browse files
committed
USER: add support for function annotations
1 parent 9797b9f commit 3975e55

File tree

1 file changed

+271
-1
lines changed

1 file changed

+271
-1
lines changed

chb/userdata/UserHints.py

Lines changed: 271 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# ------------------------------------------------------------------------------
55
# The MIT License (MIT)
66
#
7-
# Copyright (c) 2021-2024 Aarno Labs, LLC
7+
# Copyright (c) 2021-2025 Aarno Labs, LLC
88
#
99
# Permission is hereby granted, free of charge, to any person obtaining a copy
1010
# of this software and associated documentation files (the "Software"), to deal
@@ -51,6 +51,9 @@
5151
- DataBlocks
5252
pairs of addresses (start inclusive, end exclusive) that indicate data
5353
54+
- FunctionAnnotations
55+
register and stack variable introductions
56+
5457
- FunctionEntryPoints
5558
addresses of function entry points
5659
@@ -102,6 +105,7 @@
102105
from chb.userdata.UserBType import UserStructBType
103106

104107
import chb.util.fileutil as UF
108+
from chb.util.loggingutil import chklogger
105109
import chb.util.xmlutil as UX
106110

107111

@@ -451,6 +455,245 @@ def __str__(self) -> str:
451455
return "\n".join(lines)
452456

453457

458+
class RegisterVarIntro:
459+
460+
def __init__(self, d: Dict[str, Union[str, List[str]]]) -> None:
461+
self._d = d
462+
463+
@property
464+
def varintro(self) -> Dict[str, Union[str, List[str]]]:
465+
return self._d
466+
467+
@property
468+
def iaddr(self) -> str:
469+
if not "iaddr" in self.varintro:
470+
chklogger.logger.warning(
471+
"Register variable intro without address; returning 0x0")
472+
return cast(str, self.varintro.get("iaddr", "0x0"))
473+
474+
@property
475+
def name(self) -> str:
476+
if not "name" in self.varintro:
477+
chklogger.logger.warning(
478+
"Register variable intro without name; returning noname")
479+
return cast(str, self.varintro.get("name", "noname"))
480+
481+
@property
482+
def typename(self) -> Optional[str]:
483+
return cast(str, self.varintro.get("typename", None))
484+
485+
@property
486+
def ispointerto(self) -> bool:
487+
return "ptrto" in self.mods
488+
489+
@property
490+
def mods(self) -> List[str]:
491+
return cast(List[str], self.varintro.get("mods", []))
492+
493+
def has_cast(self) -> bool:
494+
return "cast" in self.mods
495+
496+
def to_xml(self, node: ET.Element) -> None:
497+
xvintro = ET.Element("vintro")
498+
node.append(xvintro)
499+
xvintro.set("name", self.name)
500+
xvintro.set("iaddr", self.iaddr)
501+
if self.typename is not None:
502+
xvintro.set("typename", self.typename)
503+
if self.ispointerto:
504+
xvintro.set("ptrto", "yes")
505+
if self.has_cast():
506+
xvintro.set("cast", "yes")
507+
508+
def __str__(self) -> str:
509+
return (
510+
"iaddr: " + self.iaddr
511+
+ "; name: " + self.name
512+
+ (("; typename: " + self.typename) if self.typename else ""))
513+
514+
515+
class StackVarIntro:
516+
517+
def __init__(self, d: Dict[str, str]) -> None:
518+
self._d = d
519+
520+
@property
521+
def varintro(self) -> Dict[str, str]:
522+
return self._d
523+
524+
@property
525+
def offset(self) -> int:
526+
if not "offset" in self.varintro:
527+
chklogger.logger.warning(
528+
"Stack variable intro without offset; returning 0")
529+
return int(self.varintro.get("offset", "0"))
530+
531+
@property
532+
def name(self) -> str:
533+
if not "name" in self.varintro:
534+
chklogger.logger.warning(
535+
"Stack variable intro without name; returning noname")
536+
return self.varintro.get("name", "noname")
537+
538+
@property
539+
def typename(self) -> Optional[str]:
540+
return self.varintro.get("typename", None)
541+
542+
@property
543+
def arraysize(self) -> Optional[int]:
544+
if "array" in self.varintro:
545+
return int(self.varintro.get("array", "0"))
546+
else:
547+
return None
548+
549+
@property
550+
def ispointer(self) -> bool:
551+
if "ptrto" in self.varintro:
552+
return self.varintro.get("ptrto", "no") == "yes"
553+
return False
554+
555+
def to_xml(self, node: ET.Element) -> None:
556+
xvintro = ET.Element("vintro")
557+
node.append(xvintro)
558+
xvintro.set("name", self.name)
559+
xvintro.set("offset", str(self.offset))
560+
if self.typename is not None:
561+
xvintro.set("typename", self.typename)
562+
if self.arraysize is not None:
563+
xvintro.set("arraysize", str(self.arraysize))
564+
elif self.ispointer:
565+
xvintro.set("ptrto", "yes")
566+
567+
def __str__(self) -> str:
568+
return (
569+
"offset: " + str(self.offset)
570+
+ "; name: " + self.name
571+
+ (("; typename: " + self.typename) if self.typename else ""))
572+
573+
574+
class FunctionAnnotation:
575+
576+
def __init__(self, fnannotation: Dict[str, Any]) -> None:
577+
self._fnannotation = fnannotation
578+
579+
@property
580+
def fnannotation(self) -> Dict[str, Any]:
581+
return self._fnannotation
582+
583+
@property
584+
def faddr(self) -> str:
585+
if not "faddr" in self.fnannotation:
586+
chklogger.logger.warning(
587+
"Function annotation without faddr, returning 0x0")
588+
return self.fnannotation.get("faddr", "0x0")
589+
590+
@property
591+
def stack_variable_introductions(self) -> Dict[int, StackVarIntro]:
592+
result: Dict[int, StackVarIntro] = {}
593+
for d in self.fnannotation.get("stack-variable-introductions", []):
594+
svi = StackVarIntro(d)
595+
result[svi.offset] = svi
596+
return result
597+
598+
@property
599+
def register_variable_introductions(self) -> Dict[str, RegisterVarIntro]:
600+
result: Dict[str, RegisterVarIntro] = {}
601+
for d in self.fnannotation.get("register-variable-introductions", []):
602+
rvi = RegisterVarIntro(d)
603+
result[rvi.iaddr] = rvi
604+
return result
605+
606+
def has_register_variable_introduction(self, iaddr: str) -> bool:
607+
return iaddr in self.register_variable_introductions
608+
609+
def get_register_variable_introduction(self, iaddr: str) -> RegisterVarIntro:
610+
rvintros = self.register_variable_introductions
611+
if iaddr in rvintros:
612+
return rvintros[iaddr]
613+
raise UF.CHBError("No register variable introductions for " + iaddr)
614+
615+
def to_xml(self, node: ET.Element) -> None:
616+
node.set("faddr", self.faddr)
617+
if len(self.stack_variable_introductions) > 0:
618+
xstackintros = ET.Element("stackvar-intros")
619+
node.append(xstackintros)
620+
for svintro in self.stack_variable_introductions.values():
621+
svintro.to_xml(xstackintros)
622+
if len(self.register_variable_introductions) > 0:
623+
xregintros = ET.Element("regvar-intros")
624+
node.append(xregintros)
625+
for rvintro in self.register_variable_introductions.values():
626+
rvintro.to_xml(xregintros)
627+
628+
def __str__(self) -> str:
629+
lines: List[str] = []
630+
lines.append("Function: " + self.faddr)
631+
if len(self.stack_variable_introductions) > 0:
632+
lines.append(" Stack variable introductions:")
633+
for svintro in self.stack_variable_introductions:
634+
lines.append(" " + str(svintro))
635+
if len(self.register_variable_introductions) > 0:
636+
for rvintro in self.register_variable_introductions:
637+
lines.append(" " + str(rvintro))
638+
lines.append("")
639+
return "\n".join(lines)
640+
641+
642+
class FunctionAnnotations(HintsEntry):
643+
"""List of functions with variable introductions.
644+
645+
Supersedes variable-introductions and stack-variable-introductions.
646+
"""
647+
648+
def __init__(self, fnannotations: List[Dict[str, Any]]) -> None:
649+
HintsEntry.__init__(self, "function-annotations")
650+
self._fnannotations = fnannotations
651+
652+
@property
653+
def fnannotations(self) -> List[Dict[str, Any]]:
654+
return self._fnannotations
655+
656+
def has_function(self, faddr: str) -> bool:
657+
for a in self.fnannotations:
658+
if faddr == a.get("faddr", "0x0"):
659+
return True
660+
else:
661+
return False
662+
663+
def get_function(self, faddr: str) -> FunctionAnnotation:
664+
for a in self.fnannotations:
665+
if faddr == a.get("faddr", "0x0"):
666+
return FunctionAnnotation(a)
667+
raise UF.CHBError("No annotations found for function address " + faddr)
668+
669+
def update(self, l: List[Dict[str, Any]]) -> None:
670+
for d in l:
671+
faddr = d.get("faddr", "0x0")
672+
if self.has_function(faddr):
673+
chklogger.logger.warning(
674+
"Duplicate record for function annotations: %s. "
675+
+ "New entry is ignored",
676+
faddr)
677+
else:
678+
self._fnannotations.append(d)
679+
680+
def to_xml(self, node: ET.Element) -> None:
681+
xfnannotations = ET.Element(self.name)
682+
node.append(xfnannotations)
683+
for a in self.fnannotations:
684+
xfa = ET.Element("function-annotation")
685+
FunctionAnnotation(a).to_xml(xfa)
686+
xfnannotations.append(xfa)
687+
688+
def __str__(self) -> str:
689+
lines: List[str] = []
690+
lines.append("Function annotations")
691+
lines.append("--------------------")
692+
for a in self.fnannotations:
693+
lines.append(str(FunctionAnnotation(a)))
694+
return "\n".join(lines)
695+
696+
454697
class FunctionEntryPointsHints(HintsEntry):
455698
"""List of function entry points in hex."""
456699

@@ -1131,6 +1374,19 @@ def variable_introductions(self) -> Dict[str, str]:
11311374
else:
11321375
return {}
11331376

1377+
def function_annotations(self) -> Optional[FunctionAnnotations]:
1378+
if "function-annotations" in self.astdata:
1379+
return cast(
1380+
FunctionAnnotations, self.astdata["function-annotations"])
1381+
return None
1382+
1383+
def function_annotation(self, faddr: str) -> Optional[FunctionAnnotation]:
1384+
fnannotations = self.function_annotations()
1385+
if fnannotations is not None:
1386+
if fnannotations.has_function(faddr):
1387+
return fnannotations.get_function(faddr)
1388+
return None
1389+
11341390
def stack_variable_introductions(self) -> Dict[str, Dict[int, str]]:
11351391
"""Return map from function address to stack offset to variable name.
11361392
@@ -1242,6 +1498,20 @@ def add_hints(self, hints: Dict[str, Any]) -> None:
12421498
else:
12431499
self.userdata[tag] = FunctionEntryPointsHints(fepoints)
12441500

1501+
if "function-annotations" in hints:
1502+
tag = "function-annotations"
1503+
fnannotations: List[Dict[str, Any]] = hints[tag]
1504+
if self._toxml:
1505+
if tag in self.userdata:
1506+
self.userdata[tag].update(fnannotations)
1507+
else:
1508+
self.userdata[tag] = FunctionAnnotations(fnannotations)
1509+
else:
1510+
if tag in self.astdata:
1511+
self.astdata[tag].update(fnannotations)
1512+
else:
1513+
self.astdata[tag] = FunctionAnnotations(fnannotations)
1514+
12451515
if "function-names" in hints:
12461516
tag = "function-names"
12471517
fnames: Dict[str, str] = hints[tag]

0 commit comments

Comments
 (0)