1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
import os
import re
import rich
from dataclasses import dataclass, field
from typing import Optional, List, Tuple
from collections import defaultdict
from pathlib import Path
from . import settings
from .context import Context
@dataclass
class Symbol:
""" Symbol from the linker map """
addr: int
size: int
padding: int
name: str
lib: Optional[str] = None
obj: Optional[str] = None
is_function: bool = False
source: str = None
access: "disassembler.Access" = None
first_padding: int = 0
@property
def start(self):
return self.addr
@property
def end(self):
return self.addr + self.size
@dataclass
class Section:
""" Section from the linker map """
name: str
addr: int
size: int
cs: List[Tuple[int, int]] = field(default_factory=list)
symbols: List[Symbol] = field(default_factory=list)
objects: List[str] = field(default_factory=list)
index: int = 0
data: bytes = field(default=None, repr=False)
@property
def start(self):
return self.addr
@property
def end(self):
return self.addr + self.size
def is_addr_code(self, addr):
for segment in self.cs:
if addr >= segment[0] and addr < segment[1]:
return True
return False
#
SECTION_NAME_ORDER = [
".text",
".ctors",
".dtors",
".rodata",
".data",
".bss",
]
def parse(context: Context, module_id: int, linker_map_path: Path, executable_sections, base_folder=True):
"""Read the linker map file to get symbols and section names."""
sections = dict()
lines = []
# read linker map line-by-line
with linker_map_path.open('r') as file:
lines = file.readlines()
# group linker map lines by what section they are in
groups = defaultdict(list)
section_name = None
for text_line in lines:
if "section layout" in text_line:
section_name = text_line.split(" ")[0]
groups[section_name] = []
elif section_name:
groups[section_name].append(text_line)
# determine what section names are already known
already_known_names = set()
for section in executable_sections:
if section.name:
already_known_names.add(section.name)
# get all the linker map sections which are not already known
map_names = [x for x in groups.keys() if not x in already_known_names]
# get the name for all sections which has size and are not already known (this will probably be an list of None's)
sects_names = [
x.name for x in executable_sections if not x.name in already_known_names and x.size > 0]
# try to match the section names with those from the linker map, i.e., assign the section a name from the linker map.
skip = 0
for i, section in enumerate([x for x in executable_sections if not x.name in already_known_names and x.size > 0]):
index = i + skip
if index < len(map_names):
if len(groups[map_names[index]]) == 0 and map_names[index] != ".bss":
section.name = map_names[index + 1]
skip += 1
else:
section.name = map_names[index]
# sort and create linker map sections
executable_sections.sort(key=lambda x: x.start)
for i, section in enumerate(executable_sections):
if section.size > 0:
if not section.name:
assert i > 1
last_section = executable_sections[i - 1]
name_index = SECTION_NAME_ORDER.index(last_section.name)
section.name = SECTION_NAME_ORDER[name_index + 1]
context.warning(f"section name could not be determine using the map file, using the next name instead '{section.name}'")
assert False # TODO
sections[section.name] = Section(
section.name, section.start, section.size, section.code_segments)
sections[section.name].first_padding = section.first_padding + section.offset_padding
sections[section.name].data = section.data
sections[section.name].index = i
sections[section.name].alignment = section.alignment
# for each section, go through all the lines and try to find sysmbols
for section_name, lines in groups.items():
for text_line in lines:
line = re.sub(r'( \(entry of [^)]*\))', "", text_line)
data = [x.strip() for x in line.strip().split(" ")]
data = [x for x in data if len(x) > 0]
# not symbols
if len(data) < 6 or len(data) > 7:
continue
lib = None
obj = None
name = None
size = int(data[1], base=16)
addr = int(data[2], base=16)
if len(data) == 6:
name = data[4].split("\\")[-1]
obj = data[5]
elif len(data) == 7:
name = data[4]
lib = data[5].split("\\")[-1]
obj = data[6]
# group libraries togather (e.g. JSystem)
if lib:
for k, v in settings.LIBRARY_LUT:
if lib.startswith(k):
lib = v + lib
break
if obj:
# move translation units into files (e.g. d_a_XXX -> d/a/d_a_XXX)
if base_folder:
if not lib:
for k, v in settings.FOLDERS:
if obj.startswith(k):
obj = v + obj
break
if "\\" in obj:
obj = obj.replace("\\", "/")
if "/" in obj:
# if the object file is a path only use the last 3 parts.
# choice was arbitrary, but it looks OK
obj = "/".join(obj.split("/")[-3:])
# if we're the main.dol, then convert the address to relative address
if module_id == 0:
addr -= sections[section_name].addr
# add the symbol
symbol = Symbol(addr, size, 0, name, lib, obj)
symbol.source = f"linker_map/'{linker_map_path}'/{addr:08X}"
sections[section_name].symbols.append(symbol)
# calculate a dictionary of addresses used by each section, this will later
# be used to remove access labels generated by the analyze.py module
addrs = defaultdict(lambda: dict())
for k, v in sections.items():
for symbol in v.symbols:
addrs[k][symbol.addr] = symbol
return sections, addrs
|