ocgraph 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. ocgraph/__init__.py +0 -0
  2. ocgraph/__main__.py +95 -0
  3. ocgraph/configuration/__init__.py +4 -0
  4. ocgraph/configuration/architecture/__init__.py +8 -0
  5. ocgraph/configuration/architecture/architecture.py +66 -0
  6. ocgraph/configuration/architecture/arm.py +52 -0
  7. ocgraph/configuration/architecture/msp430.py +48 -0
  8. ocgraph/configuration/architecture/ppc.py +698 -0
  9. ocgraph/configuration/architecture/sparc.py +100 -0
  10. ocgraph/configuration/architecture/x86.py +34 -0
  11. ocgraph/configuration/configuration.py +122 -0
  12. ocgraph/configuration/disassembler/__init__.py +10 -0
  13. ocgraph/configuration/disassembler/disassembler.py +148 -0
  14. ocgraph/configuration/disassembler/gdb_default.py +11 -0
  15. ocgraph/configuration/disassembler/objdump_arm.py +11 -0
  16. ocgraph/configuration/disassembler/objdump_msp430.py +27 -0
  17. ocgraph/configuration/disassembler/objdump_ppc.py +11 -0
  18. ocgraph/configuration/disassembler/objdump_sparc.py +11 -0
  19. ocgraph/configuration/disassembler/objdump_x86.py +11 -0
  20. ocgraph/configuration/logger.py +50 -0
  21. ocgraph/data/__init__.py +9 -0
  22. ocgraph/data/address.py +58 -0
  23. ocgraph/data/basic_block.py +37 -0
  24. ocgraph/data/encoding.py +34 -0
  25. ocgraph/data/function_header.py +23 -0
  26. ocgraph/data/instruction.py +102 -0
  27. ocgraph/data/jump_table.py +60 -0
  28. ocgraph/interface/__init__.py +5 -0
  29. ocgraph/interface/analyzer.py +220 -0
  30. ocgraph/interface/coverage_reader.py +70 -0
  31. ocgraph/interface/drawer.py +181 -0
  32. ocgraph-0.3.0.dist-info/LICENSES/GTDGmbH.md +24 -0
  33. ocgraph-0.3.0.dist-info/LICENSES/MauriMustonen.md +24 -0
  34. ocgraph-0.3.0.dist-info/METADATA +55 -0
  35. ocgraph-0.3.0.dist-info/RECORD +37 -0
  36. ocgraph-0.3.0.dist-info/WHEEL +4 -0
  37. ocgraph-0.3.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,100 @@
1
+ # SPDX-License-Identifier: GTDGmbH
2
+ """Contains instruction info for Sparc-compatible targets."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import TYPE_CHECKING
7
+
8
+ from .architecture import GenericArchitecture
9
+
10
+ if TYPE_CHECKING: # pragma: no cover
11
+ from ocgraph.data import Instruction
12
+
13
+ # fmt: off
14
+ sparc_v8_call_opcodes = [
15
+ "call",
16
+ ]
17
+
18
+ sparc_v8_sink_opcodes = [
19
+ # ret: return from subroutine
20
+ # retl: return from leaf subroutine
21
+ "ret", "retl",
22
+ ]
23
+
24
+ sparc_v8_Bicc_opcodes = [
25
+ # conditional icc branch opcodes
26
+ "ba", "bn", "bne", "be", "bg", "ble", "bge", "bl", "bgu", "bleu", "bcc",
27
+ "bcs", "bpos", "bneg", "bvc", "bvs",
28
+ ]
29
+
30
+ sparc_v8_FBfcc_opcodes = [
31
+ # conditional fcc branch opcodes
32
+ "fba", "fbn", "fbu", "fbg", "fbug", "fbl", "fbul", "fblg", "fbne", "fbe",
33
+ "fbue", "fbge", "fbuge", "fble", "fbule", "fbo",
34
+ ]
35
+
36
+ sparc_v8_CBfcc_opcodes = [
37
+ # conditional coprocessor opcodes
38
+ "cba", "cbn", "cb3", "cb2", "cb23", "cb1", "cb13", "cb12", "cb123", "cb0",
39
+ "cb03", "cb02", "cb023", "cb01", "cb013", "cb012",
40
+ ]
41
+
42
+ sparc_v8_Ticc_opcodes = [
43
+ # conditional traps on icc
44
+ "ta", "tn", "tne", "te", "tg", "tle", "tge", "tl", "tgu", "tleu", "tcc",
45
+ "tcs", "tpos", "tneg", "tvc", "tvs",
46
+ ]
47
+
48
+ sparc_v8_branch_cond_delay_opcodes = [
49
+ f"{x},a" for x in
50
+ sparc_v8_Bicc_opcodes +
51
+ sparc_v8_FBfcc_opcodes +
52
+ sparc_v8_CBfcc_opcodes
53
+ ]
54
+
55
+ sparc_v8_unconditional_branch_opcodes = [
56
+ "jmpl", "jmp", "b", "b,a",
57
+ ]
58
+
59
+ sparc_v8_delayed_opcodes = sparc_v8_Bicc_opcodes + \
60
+ sparc_v8_FBfcc_opcodes + \
61
+ sparc_v8_CBfcc_opcodes + \
62
+ sparc_v8_branch_cond_delay_opcodes + \
63
+ sparc_v8_unconditional_branch_opcodes
64
+
65
+ sparc_v8_conditional_branch_opcodes = sparc_v8_Bicc_opcodes + \
66
+ sparc_v8_FBfcc_opcodes + \
67
+ sparc_v8_CBfcc_opcodes + \
68
+ sparc_v8_Ticc_opcodes + \
69
+ sparc_v8_branch_cond_delay_opcodes
70
+ # fmt: on
71
+
72
+
73
+ class SparcArchitecture(GenericArchitecture):
74
+ """SparcArchitecture Class."""
75
+
76
+ def is_call(self, instruction: Instruction) -> bool:
77
+ return instruction.opcode in sparc_v8_call_opcodes
78
+
79
+ def is_branch(self, instruction: Instruction) -> bool:
80
+ return instruction.opcode in (
81
+ sparc_v8_conditional_branch_opcodes + sparc_v8_unconditional_branch_opcodes
82
+ )
83
+
84
+ def get_branch_delay(self, instruction: Instruction) -> int:
85
+ if instruction.opcode in sparc_v8_delayed_opcodes or self.is_sink(instruction):
86
+ delay = 2
87
+ else:
88
+ delay = 1
89
+ return delay
90
+
91
+ def is_direct_branch(self, instruction: Instruction) -> bool:
92
+ # every branch is disassembled with the complete offset,
93
+ # i.e. every branch is direct
94
+ return self.is_branch(instruction)
95
+
96
+ def is_unconditional_branch(self, instruction: Instruction) -> bool:
97
+ return instruction.opcode in sparc_v8_unconditional_branch_opcodes
98
+
99
+ def is_sink(self, instruction: Instruction) -> bool:
100
+ return instruction.opcode in sparc_v8_sink_opcodes
@@ -0,0 +1,34 @@
1
+ # SPDX-License-Identifier: GTDGmbH
2
+ """Contains instruction info for X86-compatible targets."""
3
+
4
+ from ocgraph.data import Instruction
5
+
6
+ from .architecture import GenericArchitecture
7
+
8
+
9
+ class X86Architecture(GenericArchitecture):
10
+ """X86Architecture Class."""
11
+
12
+ def is_call(self, instruction: Instruction) -> bool:
13
+ # Various flavors of call:
14
+ # call *0x26a16(%rip)
15
+ # call 0x555555555542
16
+ # addr32 call 0x55555558add0
17
+ if instruction.opcode:
18
+ return "call" in instruction.opcode
19
+ return False
20
+
21
+ def is_branch(self, instruction: Instruction) -> bool:
22
+ if instruction.opcode:
23
+ return instruction.opcode[0] == "j"
24
+ return False
25
+
26
+ def is_unconditional_branch(self, instruction: Instruction) -> bool:
27
+ if instruction.opcode:
28
+ return instruction.opcode.startswith("jmp")
29
+ return False
30
+
31
+ def is_sink(self, instruction: Instruction) -> bool:
32
+ if instruction.opcode:
33
+ return instruction.opcode.startswith("ret")
34
+ return False
@@ -0,0 +1,122 @@
1
+ # SPDX-License-Identifier: GTDGmbH
2
+ """Module for configuration of the ocgraph package."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import logging
7
+ from typing import TYPE_CHECKING
8
+
9
+ from .architecture import (
10
+ ArmArchitecture,
11
+ MSP430Architecture,
12
+ PpcArchitecture,
13
+ SparcArchitecture,
14
+ X86Architecture,
15
+ )
16
+ from .disassembler import (
17
+ GdbDisassembler,
18
+ ObjDumpArmDisassembler,
19
+ ObjDumpMsp430Disassembler,
20
+ ObjDumpPpcDisassembler,
21
+ ObjDumpSparcDisassembler,
22
+ ObjDumpx86Disassembler,
23
+ )
24
+ from .logger import OCGraphLogger, preset_logging
25
+
26
+ if TYPE_CHECKING: # pragma: no cover
27
+ from .architecture import Architecture
28
+ from .disassembler import Disassembler
29
+
30
+ # fmt: off
31
+ disassembler_option: dict[str, dict[str, Disassembler]] = {
32
+ "OBJDUMP": {
33
+ "sparc": ObjDumpSparcDisassembler(),
34
+ "ppc": ObjDumpPpcDisassembler(),
35
+ "x86": ObjDumpx86Disassembler(),
36
+ "arm": ObjDumpArmDisassembler(),
37
+ "msp430": ObjDumpMsp430Disassembler(),
38
+ },
39
+ "GDB": {
40
+ "sparc": GdbDisassembler(),
41
+ "ppc": GdbDisassembler(),
42
+ "x86": GdbDisassembler(),
43
+ "arm": GdbDisassembler(),
44
+ "msp430": GdbDisassembler(),
45
+ },
46
+ }
47
+
48
+ architecture_option: dict[str, dict[str, str | Architecture | Disassembler]] = {
49
+ "x86": {
50
+ "platform": "X86",
51
+ "architecture": X86Architecture(),
52
+ },
53
+ "arm": {
54
+ "platform": "ARM",
55
+ "architecture": ArmArchitecture(),
56
+ },
57
+ "sparc": {
58
+ "platform": "SPARC",
59
+ "architecture": SparcArchitecture(),
60
+ },
61
+ "ppc": {
62
+ "platform": "PPC",
63
+ "architecture": PpcArchitecture(),
64
+ },
65
+ "msp430": {
66
+ "platform": "MSP430",
67
+ "architecture": MSP430Architecture(),
68
+ },
69
+ }
70
+ # fmt: on
71
+
72
+
73
+ class OcGraphConfiguration:
74
+ """Implement configuration presets for the ASM2CFG tool."""
75
+
76
+ def __init__(
77
+ self,
78
+ arch: str = "sparc",
79
+ disassembler: str = "OBJDUMP",
80
+ logging_preset: str = "default",
81
+ ) -> None:
82
+ if architecture_option.get(arch) is None:
83
+ msg = "Architecture option not supported!"
84
+ raise NotImplementedError(msg)
85
+ if disassembler_option.get(disassembler) is None:
86
+ msg = "Disassembler option not supported!"
87
+ raise NotImplementedError(msg)
88
+ if preset_logging.get(logging_preset) is None:
89
+ msg = "Logging preset not supported!"
90
+ raise NotImplementedError(msg)
91
+
92
+ # load module preset
93
+ _preset = architecture_option[arch]
94
+ _preset["disassembler"] = disassembler_option[disassembler][arch]
95
+ self.__dict__ = _preset
96
+
97
+ # configure logging
98
+ self.logger = OCGraphLogger("OcGraph", logging_preset, "ocgraph")
99
+
100
+ @staticmethod
101
+ def architectures() -> list[str]:
102
+ """Return all available architectures options."""
103
+ return list(architecture_option.keys())
104
+
105
+ @staticmethod
106
+ def disassemblers() -> list[str]:
107
+ """Return all available disassemblers options."""
108
+ return list(disassembler_option.keys())
109
+
110
+ @staticmethod
111
+ def loggers() -> list[str]:
112
+ """Return all available disassemblers options."""
113
+ return list(preset_logging.keys())
114
+
115
+ logger: logging.Logger = logging.getLogger("OcGraph")
116
+ """Logging mechanism for module"""
117
+
118
+ architecture: Architecture
119
+ """Target architecture instance"""
120
+
121
+ disassembler: Disassembler
122
+ """Target disassembler tool like OBJDump, GDB, ..."""
@@ -0,0 +1,10 @@
1
+ """The main file of the disassembler module."""
2
+
3
+ from .disassembler import Disassembler as Disassembler
4
+ from .disassembler import ObjdumpDisassembler as ObjdumpDisassembler
5
+ from .gdb_default import GdbDisassembler as GdbDisassembler
6
+ from .objdump_arm import ObjDumpArmDisassembler as ObjDumpArmDisassembler
7
+ from .objdump_msp430 import ObjDumpMsp430Disassembler as ObjDumpMsp430Disassembler
8
+ from .objdump_ppc import ObjDumpPpcDisassembler as ObjDumpPpcDisassembler
9
+ from .objdump_sparc import ObjDumpSparcDisassembler as ObjDumpSparcDisassembler
10
+ from .objdump_x86 import ObjDumpx86Disassembler as ObjDumpx86Disassembler
@@ -0,0 +1,148 @@
1
+ """Class configuring the used disassembler tool."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from abc import ABC, abstractmethod
7
+ from re import Match
8
+ from typing import TYPE_CHECKING
9
+
10
+ from ocgraph.data import Address, FunctionHeader, Instruction
11
+
12
+ if TYPE_CHECKING: # pragma: no cover
13
+ from collections.abc import Generator
14
+
15
+ from ocgraph.configuration.architecture.architecture import Architecture
16
+
17
+ # Common regexes
18
+ HEX_PATTERN = r"[0-9a-fA-F]+"
19
+ HEX_LONG_PATTERN = r"(?:0x0*)?" + HEX_PATTERN
20
+
21
+
22
+ class DisassemblerError(Exception):
23
+ """Raised when the extract_information method was not successful."""
24
+
25
+
26
+ class Disassembler(ABC):
27
+ """Disassembler Class."""
28
+
29
+ architecture: Architecture
30
+ """Architecture configuration used by the disassembler"""
31
+
32
+ name: str = ""
33
+ """Disassembler tool identification like SparcV8Objdump, GDB, ..."""
34
+
35
+ @abstractmethod
36
+ def extract_information(self, lines: list[str]) -> Generator[Instruction | FunctionHeader]:
37
+ """
38
+ Parse the given lines of disassembler output.
39
+
40
+ This extracts all available information from the disassembler
41
+ output and returns a generator providing Instruction or a
42
+ FunctionHeader.
43
+ """
44
+ raise NotImplementedError # pragma: no cover
45
+
46
+ @abstractmethod
47
+ def parse_jump_target(self, ops: list[str]) -> int | None:
48
+ """Return the jump target value from a list of operands."""
49
+ raise NotImplementedError # pragma: no cover
50
+
51
+
52
+ class ObjdumpDisassembler(Disassembler):
53
+ """Objdump Disassembler Class."""
54
+
55
+ name: str = ""
56
+ """ Disassembler tool identification like SparcV8Objdump, GDB, ..."""
57
+
58
+ function_header_regex: str = r"^([0-9a-fA-F]+) <(.*)>:$"
59
+ function_header_groups: dict[str, int] = {
60
+ "address": 1,
61
+ "name": 2,
62
+ }
63
+
64
+ instruction_regex: str = (
65
+ r"^\s*([0-9a-fA-F]+):\t((?:[0-9a-fA-F]+ )+) *\t(\S+)[\t ]*([^\n\r\f\v;]*?) ?[\t ]*(?:|[#!;] *([0-9a-fA-F]+) *<(.*?)(\+.*)?>|\t.*:.*|<(.*)(\+.*)>|[#!;] 0x([0-9a-fA-F]+)|;abs 0x([0-9a-fA-F]+)|;.*)$"
66
+ )
67
+ instruction_groups: dict[str, int] = {
68
+ "address": 1,
69
+ "encoding": 2,
70
+ "mnemonic": 3,
71
+ "operands": 4,
72
+ "decoded_immediate1": 5,
73
+ "symbol": 6,
74
+ "symbol_offset": 7,
75
+ "relative_jump_base": 8,
76
+ "relative_jump_offset": 9,
77
+ "decoded_immediate2": 10,
78
+ "absolute_jump_target": 11,
79
+ }
80
+
81
+ def __init__(self) -> None:
82
+ self.current_function: str | None = None
83
+
84
+ def extract_information(self, lines: list[str]) -> Generator[Instruction | FunctionHeader]:
85
+ """
86
+ Parse the given lines of disassembler output.
87
+
88
+ This extracts all available information from the disassembler
89
+ output and returns a generator providing Instruction or a
90
+ FunctionHeader.
91
+ """
92
+
93
+ def get_match(match_object: Match[str], match_number: int) -> str:
94
+ return match_object.group(match_number)
95
+
96
+ for i, line in enumerate(lines):
97
+ instruction = re.match(self.instruction_regex, line)
98
+ function_header = re.match(self.function_header_regex, line)
99
+
100
+ if function_header:
101
+ address = get_match(function_header, self.function_header_groups["address"])
102
+ name = get_match(function_header, self.function_header_groups["name"])
103
+ self.current_function = name
104
+ yield FunctionHeader(name, int(address, 16))
105
+ elif instruction:
106
+ relative_jump_base = get_match(
107
+ instruction,
108
+ self.instruction_groups["relative_jump_base"],
109
+ )
110
+ relative_jump_offset = get_match(
111
+ instruction,
112
+ self.instruction_groups["relative_jump_offset"],
113
+ )
114
+ address = get_match(instruction, self.instruction_groups["address"])
115
+ absolute_jump_target = get_match(
116
+ instruction,
117
+ self.instruction_groups["absolute_jump_target"],
118
+ )
119
+ ops = [
120
+ x.strip()
121
+ for x in get_match(instruction, self.instruction_groups["operands"]).split(",")
122
+ ]
123
+
124
+ if address and relative_jump_base and relative_jump_offset:
125
+ target = Address(
126
+ base=relative_jump_base,
127
+ offset=int(relative_jump_offset, 16),
128
+ )
129
+ elif address and absolute_jump_target:
130
+ target = Address(
131
+ abs_addr=int(absolute_jump_target, 16),
132
+ base=relative_jump_base,
133
+ )
134
+ else:
135
+ target = Address(abs_addr=None)
136
+
137
+ yield Instruction(
138
+ lineno=i,
139
+ address=Address(abs_addr=int(address, 16), base=self.current_function),
140
+ opcode=get_match(instruction, self.instruction_groups["mnemonic"]),
141
+ ops=ops,
142
+ target=target,
143
+ )
144
+
145
+ def parse_jump_target(self, ops: list[str]) -> int | None:
146
+ """Return the jump target value from a list of operands."""
147
+ # it assumes the last operand of the branch to be the target address
148
+ return int(ops[-1], 16)
@@ -0,0 +1,11 @@
1
+ """Class for parsing the input."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .disassembler import ObjdumpDisassembler
6
+
7
+
8
+ class GdbDisassembler(ObjdumpDisassembler):
9
+ """x86 GDB disassembler."""
10
+
11
+ name: str = "Default GDB Disassembler (x86 Binutils)"
@@ -0,0 +1,11 @@
1
+ """Class for parsing the objdump ARM input."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .disassembler import ObjdumpDisassembler
6
+
7
+
8
+ class ObjDumpArmDisassembler(ObjdumpDisassembler):
9
+ """Objdump ARM disassembler."""
10
+
11
+ name: str = "ARM Objdump Disassembler"
@@ -0,0 +1,27 @@
1
+ """Class for parsing the objdump MSP430 input."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from .disassembler import ObjdumpDisassembler
8
+
9
+
10
+ class ObjDumpMsp430Disassembler(ObjdumpDisassembler):
11
+ """Objdump MSP430 disassembler."""
12
+
13
+ name: str = "MSP430 Objdump Disassembler"
14
+
15
+ def parse_jump_target(self, ops: list[str]) -> int | None:
16
+ pattern_jump = r"abs\s+(0x[0-9a-fA-F]+)"
17
+ pattern_call = r"#0x([0-9a-fA-F]+)"
18
+ for op in ops:
19
+ match_jump = re.search(pattern_jump, op)
20
+ match_call = re.search(pattern_call, op)
21
+ if match_jump:
22
+ abs_adr = match_jump.group(1)
23
+ return int(abs_adr, 16)
24
+ if match_call:
25
+ abs_adr = match_call.group(1)
26
+ return int(abs_adr, 16)
27
+ return None
@@ -0,0 +1,11 @@
1
+ """Class for parsing the objdump PPC input."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .disassembler import ObjdumpDisassembler
6
+
7
+
8
+ class ObjDumpPpcDisassembler(ObjdumpDisassembler):
9
+ """Objdump PPC disassembler."""
10
+
11
+ name: str = "PPC Objdump Disassembler (PPC Binutils)"
@@ -0,0 +1,11 @@
1
+ """Class for parsing the objdump SPARC input."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .disassembler import ObjdumpDisassembler
6
+
7
+
8
+ class ObjDumpSparcDisassembler(ObjdumpDisassembler):
9
+ """Objdump SPARC disassembler."""
10
+
11
+ name: str = "SPARC Objdump Disassembler (SparcV8 Binutils)"
@@ -0,0 +1,11 @@
1
+ """Class for parsing the objdump x86 input."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .disassembler import ObjdumpDisassembler
6
+
7
+
8
+ class ObjDumpx86Disassembler(ObjdumpDisassembler):
9
+ """x86 objdump disassembler."""
10
+
11
+ name: str = "x86 Disassembler (x86 Binutils)"
@@ -0,0 +1,50 @@
1
+ # SPDX-License-Identifier: GTDGmbH
2
+ # Copyright 2024 by GTD GmbH.
3
+ """Class configuring the OCGraph logging."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import logging
8
+
9
+ # fmt: off
10
+ preset_logging: dict[str, dict[str, bool | int]] = {
11
+ "development": {
12
+ "file_log": True,
13
+ "file_level": logging.DEBUG,
14
+ "console_log": True,
15
+ "console_level": logging.DEBUG,
16
+ },
17
+
18
+ "module": {
19
+ "file_log": False,
20
+ "file_level": logging.ERROR,
21
+ "console_log": False,
22
+ "console_level": logging.ERROR,
23
+ },
24
+
25
+ "default": {
26
+ "file_log": True,
27
+ "file_level": logging.INFO,
28
+ "console_log": True,
29
+ "console_level": logging.INFO,
30
+ },
31
+ }
32
+ # fmt: on
33
+
34
+
35
+ class OCGraphLogger(logging.Logger):
36
+ """Logging mechanism for module."""
37
+
38
+ def __init__(self, name: str, preset: str = "default", file: str = "") -> None:
39
+ super().__init__(name)
40
+ log_config = preset_logging.get(preset)
41
+ if log_config:
42
+ if log_config["file_log"]:
43
+ logging_file = file + ".log"
44
+ file_stream = logging.FileHandler(logging_file)
45
+ file_stream.setLevel(log_config["file_level"])
46
+ self.addHandler(file_stream)
47
+ if log_config["console_log"]:
48
+ console_stream = logging.StreamHandler()
49
+ console_stream.setLevel(log_config["console_level"])
50
+ self.addHandler(console_stream)
@@ -0,0 +1,9 @@
1
+ """Main file of the data module."""
2
+
3
+ from .address import Address as Address
4
+ from .basic_block import BasicBlock as BasicBlock
5
+ from .encoding import Encoding as Encoding
6
+ from .function_header import FunctionHeader as FunctionHeader
7
+ from .instruction import Coverage as Coverage
8
+ from .instruction import Instruction as Instruction
9
+ from .jump_table import JumpTable as JumpTable
@@ -0,0 +1,58 @@
1
+ """Represents location in program which may be absolute or relative."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class Address:
7
+ """Address Class."""
8
+
9
+ def __init__(
10
+ self,
11
+ abs_addr: int | None = None,
12
+ base: str | None = None,
13
+ offset: int | None = None,
14
+ ) -> None:
15
+ self.abs_addr = abs_addr
16
+ self.base = base
17
+ self.offset = offset
18
+
19
+ def is_absolute(self) -> bool:
20
+ """
21
+ Return if Address is absolute.
22
+
23
+ Examples
24
+ --------
25
+ >>> Address(abs_addr=0x1234).is_absolute()
26
+ True
27
+ >>> Address(base="fn").is_absolute()
28
+ False
29
+
30
+ """
31
+ return self.base is None
32
+
33
+ def is_relative(self) -> bool:
34
+ """
35
+ Return if Address is relative.
36
+
37
+ >>> Address(abs_addr=0x1234).is_relative()
38
+ False
39
+ >>> Address(base="fn").is_relative()
40
+ True
41
+ """
42
+ return not self.is_absolute()
43
+
44
+ def __repr__(self) -> str:
45
+ """
46
+ Convert an Address into a representative string.
47
+
48
+ >>> Address(abs_addr=0x1234)
49
+ Address(0x1234, None+None)
50
+ >>> Address(base="fn", offset=0x1234)
51
+ Address(None, fn+0x1234)
52
+ >>> Address(abs_addr=0x5678, base="fn", offset=0x1234)
53
+ Address(0x5678, fn+0x1234)
54
+ """
55
+ abs_addr = f"0x{self.abs_addr:x}" if self.abs_addr is not None else None
56
+ base = self.base
57
+ offset = f"0x{self.offset:x}" if self.offset is not None else None
58
+ return f"Address({abs_addr}, {base}+{offset})"
@@ -0,0 +1,37 @@
1
+ """Basic Block Class."""
2
+
3
+ from typing import Any
4
+
5
+ from .instruction import Instruction
6
+
7
+
8
+ class BasicBlock:
9
+ """Class to represent an object code basic block as node in CFG."""
10
+
11
+ def __init__(self, key: Any) -> None:
12
+ self.key = key
13
+ self.instructions: list[Instruction] = []
14
+ self.jump_edge = None
15
+ self.no_jump_edge = None
16
+
17
+ def add_instruction(self, instruction: Instruction) -> None:
18
+ """Add instruction to this block."""
19
+ self.instructions.append(instruction)
20
+
21
+ def add_jump_edge(self, basic_block_key: Any) -> None:
22
+ """Add jump target block to this block."""
23
+ if isinstance(basic_block_key, BasicBlock):
24
+ self.jump_edge = basic_block_key.key
25
+ else:
26
+ self.jump_edge = basic_block_key
27
+
28
+ def add_no_jump_edge(self, basic_block_key: Any) -> None:
29
+ """Add no jump target block to this block."""
30
+ if isinstance(basic_block_key, BasicBlock):
31
+ self.no_jump_edge = basic_block_key.key
32
+ else:
33
+ self.no_jump_edge = basic_block_key
34
+
35
+ def __repr__(self) -> str:
36
+ """Convert a BasicBlock into a representative string."""
37
+ return f"BasicBlock({', '.join([str(i) for i in self.instructions])})"