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.
- ocgraph/__init__.py +0 -0
- ocgraph/__main__.py +95 -0
- ocgraph/configuration/__init__.py +4 -0
- ocgraph/configuration/architecture/__init__.py +8 -0
- ocgraph/configuration/architecture/architecture.py +66 -0
- ocgraph/configuration/architecture/arm.py +52 -0
- ocgraph/configuration/architecture/msp430.py +48 -0
- ocgraph/configuration/architecture/ppc.py +698 -0
- ocgraph/configuration/architecture/sparc.py +100 -0
- ocgraph/configuration/architecture/x86.py +34 -0
- ocgraph/configuration/configuration.py +122 -0
- ocgraph/configuration/disassembler/__init__.py +10 -0
- ocgraph/configuration/disassembler/disassembler.py +148 -0
- ocgraph/configuration/disassembler/gdb_default.py +11 -0
- ocgraph/configuration/disassembler/objdump_arm.py +11 -0
- ocgraph/configuration/disassembler/objdump_msp430.py +27 -0
- ocgraph/configuration/disassembler/objdump_ppc.py +11 -0
- ocgraph/configuration/disassembler/objdump_sparc.py +11 -0
- ocgraph/configuration/disassembler/objdump_x86.py +11 -0
- ocgraph/configuration/logger.py +50 -0
- ocgraph/data/__init__.py +9 -0
- ocgraph/data/address.py +58 -0
- ocgraph/data/basic_block.py +37 -0
- ocgraph/data/encoding.py +34 -0
- ocgraph/data/function_header.py +23 -0
- ocgraph/data/instruction.py +102 -0
- ocgraph/data/jump_table.py +60 -0
- ocgraph/interface/__init__.py +5 -0
- ocgraph/interface/analyzer.py +220 -0
- ocgraph/interface/coverage_reader.py +70 -0
- ocgraph/interface/drawer.py +181 -0
- ocgraph-0.3.0.dist-info/LICENSES/GTDGmbH.md +24 -0
- ocgraph-0.3.0.dist-info/LICENSES/MauriMustonen.md +24 -0
- ocgraph-0.3.0.dist-info/METADATA +55 -0
- ocgraph-0.3.0.dist-info/RECORD +37 -0
- ocgraph-0.3.0.dist-info/WHEEL +4 -0
- 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)
|
ocgraph/data/__init__.py
ADDED
|
@@ -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
|
ocgraph/data/address.py
ADDED
|
@@ -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])})"
|