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
ocgraph/data/encoding.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Encoding Class."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Encoding:
|
|
7
|
+
"""
|
|
8
|
+
Encoding represents a sequence of bytes for instruction encoding.
|
|
9
|
+
|
|
10
|
+
E.g. the '31 c0' in:
|
|
11
|
+
|
|
12
|
+
'16bd3: 31 c0 xor %eax,%eax'
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, bites: list[int]) -> None:
|
|
16
|
+
self.bites = bites
|
|
17
|
+
|
|
18
|
+
def __len__(self) -> int:
|
|
19
|
+
"""
|
|
20
|
+
Return size of the bytes.
|
|
21
|
+
|
|
22
|
+
>>> len(Encoding([0xa,0xb,0xc]))
|
|
23
|
+
3
|
|
24
|
+
"""
|
|
25
|
+
return len(self.bites)
|
|
26
|
+
|
|
27
|
+
def __repr__(self) -> str:
|
|
28
|
+
"""
|
|
29
|
+
Convert an Encoding to a readable string.
|
|
30
|
+
|
|
31
|
+
>>> Encoding([0xa,0xb,0xc])
|
|
32
|
+
0xa 0xb 0xc
|
|
33
|
+
"""
|
|
34
|
+
return " ".join(f"{b:#x}" for b in self.bites)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Represents a function with an address."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FunctionHeader:
|
|
5
|
+
"""FunctionHeader Class."""
|
|
6
|
+
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
name: str,
|
|
10
|
+
addr: int,
|
|
11
|
+
) -> None:
|
|
12
|
+
"""Create a new FunctionHeader."""
|
|
13
|
+
self.name = name
|
|
14
|
+
self.addr = addr
|
|
15
|
+
|
|
16
|
+
def __repr__(self) -> str:
|
|
17
|
+
"""
|
|
18
|
+
Transform a FunctionHeader into a readable string.
|
|
19
|
+
|
|
20
|
+
>>> FunctionHeader("fn", 0x1234)
|
|
21
|
+
FunctionHeader(fn at 0x1234)
|
|
22
|
+
"""
|
|
23
|
+
return f"FunctionHeader({self.name} at 0x{self.addr:x})"
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Represents a single assembly instruction with its operands, location and optional branch
|
|
3
|
+
target.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
12
|
+
from .address import Address
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Coverage(Enum):
|
|
16
|
+
"""Enumeration for coverage records."""
|
|
17
|
+
|
|
18
|
+
MISS = "missed"
|
|
19
|
+
"""Indicates instruction is not executed"""
|
|
20
|
+
LINE_TAKEN = "taken"
|
|
21
|
+
"""Indicates instruction is executed"""
|
|
22
|
+
JUMP_PASS = "skipped"
|
|
23
|
+
"""Indicates, if branch is just passed without jump"""
|
|
24
|
+
JUMP_TAKEN = "jumped"
|
|
25
|
+
"""Indicates, if branch is just jumped without passed"""
|
|
26
|
+
JUMP_BOTH = "both taken"
|
|
27
|
+
"""Indicates, if branch is just jumped without passed"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Instruction:
|
|
31
|
+
"""Instruction Class."""
|
|
32
|
+
|
|
33
|
+
lineno: int | None = None
|
|
34
|
+
"""Line number in the file"""
|
|
35
|
+
|
|
36
|
+
address: Address | None = None
|
|
37
|
+
"""Computed address of the instruction"""
|
|
38
|
+
|
|
39
|
+
opcode: str | None = None
|
|
40
|
+
"""Disassembled opcode"""
|
|
41
|
+
|
|
42
|
+
ops: list[str] = []
|
|
43
|
+
"""Disassembled operands of the instruction"""
|
|
44
|
+
|
|
45
|
+
target: Address | None = None
|
|
46
|
+
"""Optional target of the instruction (branch)"""
|
|
47
|
+
|
|
48
|
+
returns: set[int] = set()
|
|
49
|
+
"""Stores addresses of sink instructions like return"""
|
|
50
|
+
|
|
51
|
+
coverage: Coverage = Coverage.MISS
|
|
52
|
+
"""If line is executed on test"""
|
|
53
|
+
|
|
54
|
+
branch_taken: bool | None = None
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
lineno: int,
|
|
59
|
+
address: Address,
|
|
60
|
+
opcode: str,
|
|
61
|
+
ops: list[str],
|
|
62
|
+
target: Address | None,
|
|
63
|
+
) -> None:
|
|
64
|
+
self.lineno = lineno
|
|
65
|
+
self.address = address
|
|
66
|
+
self.opcode = opcode
|
|
67
|
+
self.ops = ops
|
|
68
|
+
self.target = target
|
|
69
|
+
self.returns = set()
|
|
70
|
+
self.coverage = Coverage.MISS
|
|
71
|
+
|
|
72
|
+
def update_coverage(self, addresses: set[int], is_branch: bool = False) -> None:
|
|
73
|
+
"""
|
|
74
|
+
Update the coverage information of the instruction.
|
|
75
|
+
|
|
76
|
+
:param addresses: Set of addresses executed after this instruction,
|
|
77
|
+
used to determine whether a branch was taken or not.
|
|
78
|
+
:param is_branch: Flag whether the instruction should be
|
|
79
|
+
interpreted as a branch
|
|
80
|
+
"""
|
|
81
|
+
if not is_branch:
|
|
82
|
+
# exception for ret (target is None) or call (target.abs is None)
|
|
83
|
+
self.coverage = Coverage.LINE_TAKEN
|
|
84
|
+
self.returns = addresses
|
|
85
|
+
elif len(addresses) == 2 and self.target and self.target.abs_addr in addresses:
|
|
86
|
+
self.coverage = Coverage.JUMP_BOTH
|
|
87
|
+
elif len(addresses) == 1 and self.target and self.target.abs_addr in addresses:
|
|
88
|
+
self.coverage = Coverage.JUMP_TAKEN
|
|
89
|
+
elif len(addresses) == 1:
|
|
90
|
+
self.coverage = Coverage.JUMP_PASS
|
|
91
|
+
else:
|
|
92
|
+
msg = f"Invalid Coverage Information at {self.address}: {addresses}"
|
|
93
|
+
raise AssertionError(msg)
|
|
94
|
+
|
|
95
|
+
def __repr__(self) -> str:
|
|
96
|
+
"""Convert an Instruction into a representative string."""
|
|
97
|
+
result = f"{self.address}: {self.opcode}"
|
|
98
|
+
if self.ops:
|
|
99
|
+
result += f" {self.ops}"
|
|
100
|
+
if self.target is not None:
|
|
101
|
+
result += " -> " + str(self.target)
|
|
102
|
+
return f"Instruction({result})"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Holds info about branch sources and destinations in asm function."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
8
|
+
from ocgraph.configuration.configuration import OcGraphConfiguration
|
|
9
|
+
|
|
10
|
+
from .address import Address
|
|
11
|
+
from .instruction import Instruction
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class JumpTable:
|
|
15
|
+
"""JumpTable Class."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
instructions: list[Instruction],
|
|
20
|
+
configuration: OcGraphConfiguration,
|
|
21
|
+
) -> None:
|
|
22
|
+
self.config: OcGraphConfiguration = configuration
|
|
23
|
+
|
|
24
|
+
# Address where the jump begins and value which address
|
|
25
|
+
# to jump to. This also includes calls.
|
|
26
|
+
self.abs_sources: dict[int, Address] = {}
|
|
27
|
+
self.rel_sources: dict[int, Address] = {}
|
|
28
|
+
|
|
29
|
+
# Addresses where jumps end inside the current function.
|
|
30
|
+
self.abs_destinations: set[int] = set()
|
|
31
|
+
self.rel_destinations: set[int] = set()
|
|
32
|
+
|
|
33
|
+
# Iterate over the instructions and collect jump targets and branching points.
|
|
34
|
+
for instr in instructions:
|
|
35
|
+
if instr is None or instr.target is None or instr.address is None:
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
if instr.target.abs_addr is not None and instr.address.abs_addr is not None:
|
|
39
|
+
self.abs_sources[instr.address.abs_addr] = instr.target
|
|
40
|
+
self.abs_destinations.add(instr.target.abs_addr)
|
|
41
|
+
|
|
42
|
+
if instr.target.offset is not None and instr.address.offset is not None:
|
|
43
|
+
self.rel_sources[instr.address.offset] = instr.target
|
|
44
|
+
self.rel_destinations.add(instr.target.offset)
|
|
45
|
+
|
|
46
|
+
def is_jump_target(self, addr: Address) -> bool:
|
|
47
|
+
"""Return if address is a destination."""
|
|
48
|
+
if addr.abs_addr is not None:
|
|
49
|
+
return addr.abs_addr in self.abs_destinations
|
|
50
|
+
if addr.offset is not None:
|
|
51
|
+
return addr.offset in self.rel_destinations
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
def get_target(self, addr: Address) -> Address | None:
|
|
55
|
+
"""Return the target of a branch."""
|
|
56
|
+
if addr.abs_addr is not None:
|
|
57
|
+
return self.abs_sources.get(addr.abs_addr)
|
|
58
|
+
if addr.offset is not None:
|
|
59
|
+
return self.rel_sources.get(addr.offset)
|
|
60
|
+
return None
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""Class for read and analyze the input string."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from ocgraph.data import Address, BasicBlock, Instruction, JumpTable
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
10
|
+
from ocgraph.configuration import OcGraphConfiguration
|
|
11
|
+
from ocgraph.configuration.disassembler import Disassembler
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Analyzer:
|
|
15
|
+
"""Analyzer Class."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, config: OcGraphConfiguration) -> None:
|
|
18
|
+
self.configuration = config
|
|
19
|
+
self.logger = self.configuration.logger
|
|
20
|
+
self.parser: Disassembler = self.configuration.disassembler
|
|
21
|
+
|
|
22
|
+
self.lines: list[str] = []
|
|
23
|
+
self.function_name: str = ""
|
|
24
|
+
self.instructions: list[Instruction] = []
|
|
25
|
+
self.jump_table: JumpTable
|
|
26
|
+
self.basic_blocks: dict[int, BasicBlock] = {}
|
|
27
|
+
|
|
28
|
+
def parse_file(self, file_path: str) -> None:
|
|
29
|
+
"""Parse a assembler file."""
|
|
30
|
+
with open(file_path, encoding="utf8") as asm_file:
|
|
31
|
+
lines = asm_file.readlines()
|
|
32
|
+
self.parse_lines(lines)
|
|
33
|
+
|
|
34
|
+
def parse_lines(self, lines: list[str]) -> None:
|
|
35
|
+
"""Parse a list of assembly lines."""
|
|
36
|
+
self.lines = lines
|
|
37
|
+
self._parse_instructions()
|
|
38
|
+
# Compute already known jump targets
|
|
39
|
+
# This is only a first pass as indirect jumps can't be
|
|
40
|
+
# discovered yet
|
|
41
|
+
self._compute_jump_targets()
|
|
42
|
+
|
|
43
|
+
def analyze(self) -> None:
|
|
44
|
+
# Re-compute jump targets this time with indirect branches
|
|
45
|
+
self._compute_jump_targets()
|
|
46
|
+
self._infer_relative_addresses()
|
|
47
|
+
self._create_jump_table()
|
|
48
|
+
self._create_basic_blocks()
|
|
49
|
+
|
|
50
|
+
def _parse_instructions(self) -> None:
|
|
51
|
+
self.instructions = []
|
|
52
|
+
|
|
53
|
+
for info in self.parser.extract_information(self.lines):
|
|
54
|
+
if isinstance(info, Instruction):
|
|
55
|
+
self.instructions.append(info)
|
|
56
|
+
else:
|
|
57
|
+
if self.function_name:
|
|
58
|
+
msg = "we handle only one function for now"
|
|
59
|
+
raise RuntimeError(msg)
|
|
60
|
+
self.logger.info(f"New function {info.name}")
|
|
61
|
+
self.function_name = info.name
|
|
62
|
+
|
|
63
|
+
def _compute_jump_targets(self) -> None:
|
|
64
|
+
# Infer target address for jump instructions
|
|
65
|
+
for instr in self.instructions:
|
|
66
|
+
if (
|
|
67
|
+
instr.target is None or instr.target.abs_addr is None
|
|
68
|
+
) and self.configuration.architecture.is_direct_branch(instr):
|
|
69
|
+
if instr.target is None:
|
|
70
|
+
instr.target = Address(None)
|
|
71
|
+
# parse the absolute target out of the operands
|
|
72
|
+
# (first hex address is assumed to be the target address)
|
|
73
|
+
try:
|
|
74
|
+
instr.target.abs_addr = self.parser.parse_jump_target(instr.ops)
|
|
75
|
+
except ValueError:
|
|
76
|
+
# Mark the address as not (yet) known
|
|
77
|
+
instr.target.abs_addr = None
|
|
78
|
+
|
|
79
|
+
def _infer_relative_addresses(self) -> None:
|
|
80
|
+
# Infer relative addresses (for objdump or stripped gdb)
|
|
81
|
+
if (
|
|
82
|
+
len(self.instructions) > 0
|
|
83
|
+
and self.instructions[0].address
|
|
84
|
+
and self.instructions[0].address.abs_addr is not None
|
|
85
|
+
):
|
|
86
|
+
start_address = self.instructions[0].address.abs_addr
|
|
87
|
+
else:
|
|
88
|
+
start_address = 0
|
|
89
|
+
if (
|
|
90
|
+
len(self.instructions) > 0
|
|
91
|
+
and self.instructions[-1].address
|
|
92
|
+
and self.instructions[-1].address.abs_addr is not None
|
|
93
|
+
):
|
|
94
|
+
end_address = self.instructions[-1].address.abs_addr
|
|
95
|
+
else:
|
|
96
|
+
end_address = 0xFFFFFFFF
|
|
97
|
+
|
|
98
|
+
for instr in self.instructions:
|
|
99
|
+
for addr in (instr.address, instr.target):
|
|
100
|
+
if (
|
|
101
|
+
addr is not None
|
|
102
|
+
and addr.abs_addr is not None
|
|
103
|
+
and addr.offset is None
|
|
104
|
+
and start_address <= addr.abs_addr <= end_address
|
|
105
|
+
):
|
|
106
|
+
addr.offset = addr.abs_addr - start_address
|
|
107
|
+
|
|
108
|
+
self.logger.debug("Instructions:")
|
|
109
|
+
for instruction in self.instructions:
|
|
110
|
+
if instruction is not None:
|
|
111
|
+
self.logger.debug(f" {instruction}")
|
|
112
|
+
|
|
113
|
+
def _create_jump_table(self) -> None:
|
|
114
|
+
self.jump_table = JumpTable(self.instructions, self.configuration)
|
|
115
|
+
|
|
116
|
+
self.logger.debug("Absolute destinations:")
|
|
117
|
+
for dst in self.jump_table.abs_destinations:
|
|
118
|
+
if dst:
|
|
119
|
+
self.logger.debug(f" {dst:#x}")
|
|
120
|
+
else:
|
|
121
|
+
self.logger.debug(" None")
|
|
122
|
+
self.logger.debug("Relative destinations:")
|
|
123
|
+
for dst in self.jump_table.rel_destinations:
|
|
124
|
+
if dst:
|
|
125
|
+
self.logger.debug(f" {dst:#x}")
|
|
126
|
+
else:
|
|
127
|
+
self.logger.debug(" None")
|
|
128
|
+
self.logger.debug("Absolute branches:")
|
|
129
|
+
for key, addr in self.jump_table.abs_sources.items():
|
|
130
|
+
self.logger.debug(f" {key:#x} -> {addr}")
|
|
131
|
+
self.logger.debug("Relative branches:")
|
|
132
|
+
for key, addr in self.jump_table.rel_sources.items():
|
|
133
|
+
self.logger.debug(f" {key:#x} -> {addr}")
|
|
134
|
+
|
|
135
|
+
def _create_basic_blocks(self) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Now iterate over the assembly again and split it to basic blocks using the branching
|
|
138
|
+
information from earlier.
|
|
139
|
+
"""
|
|
140
|
+
self.basic_blocks = {}
|
|
141
|
+
|
|
142
|
+
curr_basic_block: BasicBlock | None = None
|
|
143
|
+
# Store last block if ending with branch opcode
|
|
144
|
+
prev_branch_block: BasicBlock | None = None
|
|
145
|
+
|
|
146
|
+
# block completion flag (introduced for SPARC pipeline)
|
|
147
|
+
block_completion: int = 0
|
|
148
|
+
|
|
149
|
+
for instruction in self.instructions:
|
|
150
|
+
# if block completion is in progress
|
|
151
|
+
if block_completion > 0:
|
|
152
|
+
block_completion -= 1
|
|
153
|
+
if block_completion > 0 and curr_basic_block:
|
|
154
|
+
self.basic_blocks[curr_basic_block.key].add_instruction(instruction)
|
|
155
|
+
continue
|
|
156
|
+
curr_basic_block = None
|
|
157
|
+
|
|
158
|
+
# Current program counter
|
|
159
|
+
pc_addr = instruction.address
|
|
160
|
+
# Get optional jump target
|
|
161
|
+
jump_target = self.jump_table.get_target(pc_addr or Address(None))
|
|
162
|
+
is_unconditional = self.configuration.architecture.is_unconditional_branch(
|
|
163
|
+
instruction,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Start new blocks if last ended
|
|
167
|
+
if curr_basic_block is None and pc_addr is not None and pc_addr.abs_addr is not None:
|
|
168
|
+
# Create new basic block
|
|
169
|
+
self.basic_blocks[pc_addr.abs_addr] = curr_basic_block = BasicBlock(
|
|
170
|
+
key=pc_addr.abs_addr,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# if previous basic block ended in branch instruction. Add the basic
|
|
174
|
+
# block what follows if the jump was not taken.
|
|
175
|
+
if prev_branch_block is not None:
|
|
176
|
+
prev_branch_block.add_no_jump_edge(curr_basic_block)
|
|
177
|
+
prev_branch_block = None
|
|
178
|
+
# or if current address is a jump target
|
|
179
|
+
elif (
|
|
180
|
+
curr_basic_block is not None
|
|
181
|
+
and pc_addr is not None
|
|
182
|
+
and pc_addr.abs_addr is not None
|
|
183
|
+
and self.jump_table.is_jump_target(pc_addr)
|
|
184
|
+
):
|
|
185
|
+
closing_block = curr_basic_block
|
|
186
|
+
self.basic_blocks[pc_addr.abs_addr] = curr_basic_block = BasicBlock(
|
|
187
|
+
key=pc_addr.abs_addr,
|
|
188
|
+
)
|
|
189
|
+
closing_block.add_no_jump_edge(pc_addr.abs_addr)
|
|
190
|
+
|
|
191
|
+
if curr_basic_block is not None:
|
|
192
|
+
curr_basic_block.add_instruction(instruction)
|
|
193
|
+
|
|
194
|
+
# End current block if current opcode is a jump/branch/sink
|
|
195
|
+
if jump_target and curr_basic_block:
|
|
196
|
+
curr_basic_block.add_jump_edge(jump_target.abs_addr)
|
|
197
|
+
prev_branch_block = None if is_unconditional else curr_basic_block
|
|
198
|
+
block_completion = self.configuration.architecture.get_branch_delay(
|
|
199
|
+
instruction,
|
|
200
|
+
)
|
|
201
|
+
elif self.configuration.architecture.is_sink(instruction):
|
|
202
|
+
block_completion = self.configuration.architecture.get_branch_delay(
|
|
203
|
+
instruction,
|
|
204
|
+
)
|
|
205
|
+
prev_branch_block = None
|
|
206
|
+
|
|
207
|
+
if prev_branch_block:
|
|
208
|
+
# If last instruction of the function is jump/call, then add dummy
|
|
209
|
+
# block to designate end of the function.
|
|
210
|
+
end_instruction = Instruction(
|
|
211
|
+
0,
|
|
212
|
+
Address(None),
|
|
213
|
+
"",
|
|
214
|
+
[],
|
|
215
|
+
Address(None),
|
|
216
|
+
)
|
|
217
|
+
end_block = BasicBlock("end_of_function")
|
|
218
|
+
end_block.add_instruction(end_instruction)
|
|
219
|
+
prev_branch_block.add_no_jump_edge(end_block.key)
|
|
220
|
+
self.basic_blocks[end_block.key] = end_block
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# SPDX-License-Identifier: GTDGmbH
|
|
2
|
+
"""Class for read coverage input and update the instruction."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import ast
|
|
7
|
+
import csv
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from ocgraph.data import Address, Instruction
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
13
|
+
from ocgraph.configuration import OcGraphConfiguration
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CoverageReader: # pylint: disable=too-few-public-methods
|
|
17
|
+
"""CoverageReader Class."""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
instructions: list[Instruction],
|
|
22
|
+
config: OcGraphConfiguration,
|
|
23
|
+
) -> None:
|
|
24
|
+
self.instructions = instructions
|
|
25
|
+
self.config = config
|
|
26
|
+
|
|
27
|
+
def update_by_csv(self, file_path: str) -> None:
|
|
28
|
+
"""Read coverage csv file and update."""
|
|
29
|
+
# Store for coverage information
|
|
30
|
+
coverage_info: dict[int, set[int]] = {}
|
|
31
|
+
|
|
32
|
+
# read the csv file. expected values in address and branch_jumps
|
|
33
|
+
with open(file_path, newline="", encoding="utf-8") as csvfile:
|
|
34
|
+
reader = csv.DictReader(csvfile)
|
|
35
|
+
for row in reader:
|
|
36
|
+
_temp = ast.literal_eval(row["branch_jumps"])
|
|
37
|
+
coverage_info[int(row["address"], 0)] = {int(x, 0) for x in _temp}
|
|
38
|
+
|
|
39
|
+
# update instructions
|
|
40
|
+
for instr in self.instructions:
|
|
41
|
+
if (
|
|
42
|
+
instr.address
|
|
43
|
+
and instr.address.abs_addr
|
|
44
|
+
and (covered_branch_targets := coverage_info.get(instr.address.abs_addr, None))
|
|
45
|
+
is not None
|
|
46
|
+
):
|
|
47
|
+
is_branch = self.config.architecture.is_branch(instr)
|
|
48
|
+
|
|
49
|
+
# Check if this is a branch which misses its branch target.
|
|
50
|
+
# This happens when the branch is indirect and
|
|
51
|
+
# objdump does not know the address of the branch target.
|
|
52
|
+
if is_branch:
|
|
53
|
+
if instr.target is None:
|
|
54
|
+
instr.target = Address(None)
|
|
55
|
+
|
|
56
|
+
if instr.target.abs_addr is None:
|
|
57
|
+
max_distance = 0
|
|
58
|
+
likely_branch_target = None
|
|
59
|
+
|
|
60
|
+
# Determine the most interesting branch target,
|
|
61
|
+
# which is the one furthest away from the branch location
|
|
62
|
+
for branch_target in covered_branch_targets:
|
|
63
|
+
distance = abs(instr.address.abs_addr - branch_target)
|
|
64
|
+
if distance > max_distance:
|
|
65
|
+
max_distance = distance
|
|
66
|
+
likely_branch_target = branch_target
|
|
67
|
+
|
|
68
|
+
instr.target.abs_addr = likely_branch_target
|
|
69
|
+
|
|
70
|
+
instr.update_coverage(covered_branch_targets, is_branch=is_branch)
|