summaryrefslogtreecommitdiff
path: root/tools/libdol2asm/linker_map.py
blob: 925d3324003ae0c6cf932cc45a07fe6dc5dff772 (plain)
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