├── .gitignore ├── COPYING ├── README.md ├── assets └── firmware.elf.map ├── map_parser.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | venv/ 3 | .idea/ 4 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Lars-Dominik Braun 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Map file parser 2 | 3 | Is a python script to parse a map file generated from GCC. 4 | 5 | --- 6 | # How to Use 7 | 8 | ```bash 9 | python map_parser 10 | ``` 11 | -------------------------------------------------------------------------------- /map_parser.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import re 3 | import os 4 | from typing import TextIO 5 | 6 | from cxxfilt import demangle 7 | 8 | 9 | class Objectfile: 10 | 11 | def __init__(self, section: str, offset: int, size: int, comment: str): 12 | self.section = section.strip() 13 | self.offset = offset 14 | self.size = size 15 | self.path = (None, None) 16 | self.basepath = None 17 | 18 | if comment: 19 | self.path = re.match(r'^(.+?)(?:\(([^\)]+)\))?$', comment).groups() 20 | self.basepath = os.path.basename(self.path[0]) 21 | 22 | self.children = [] 23 | 24 | def __repr__(self) -> str: 25 | return f'' 26 | 27 | 28 | def update_children_size(children: list[list], subsection_size: int) -> list: 29 | # set subsection size to an only child 30 | if len(children) == 1: 31 | children[0][1] = subsection_size 32 | return children 33 | 34 | rest_size = subsection_size 35 | 36 | for index in range(1, len(children)): 37 | if rest_size > 0: 38 | # current size = current address - previous child address 39 | child_size = children[index][0] - children[index - 1][0] 40 | rest_size -= child_size 41 | children[index - 1][1] = child_size 42 | 43 | # if there is rest size, set it to the last child element 44 | if rest_size > 0: 45 | children[-1][1] = rest_size 46 | 47 | return children 48 | 49 | 50 | def parse_sections(file_name: str) -> list: 51 | """ 52 | Quick&Dirty parsing for GNU ld’s linker map output, needs LANG=C, because 53 | some messages are localized. 54 | """ 55 | 56 | sections = [] 57 | with open(file_name, 'r') as file: 58 | # skip until memory map is found 59 | found = False 60 | 61 | while True: 62 | line = file.readline() 63 | if not line: 64 | break 65 | if line.strip() == 'Memory Configuration': 66 | found = True 67 | break 68 | 69 | if not found: 70 | raise Exception(f'Memory configuration is not found in the{input_file}') 71 | 72 | # long section names result in a linebreak afterwards 73 | sectionre = re.compile( 74 | '(?P
.+?|.{14,}\n)[ ]+0x(?P[0-9a-f]+)[ ]+0x(?P[0-9a-f]+)(?:[ ]+(?P.+))?\n+', 75 | re.I) 76 | subsectionre = re.compile('[ ]{16}0x(?P[0-9a-f]+)[ ]+(?P.+)\n+', re.I) 77 | s = file.read() 78 | pos = 0 79 | 80 | while True: 81 | m = sectionre.match(s, pos) 82 | if not m: 83 | # skip that line 84 | try: 85 | nextpos = s.index('\n', pos) + 1 86 | pos = nextpos 87 | continue 88 | except ValueError: 89 | break 90 | 91 | pos = m.end() 92 | section = m.group('section') 93 | v = m.group('offset') 94 | offset = int(v, 16) if v is not None else None 95 | v = m.group('size') 96 | size = int(v, 16) if v is not None else None 97 | comment = m.group('comment') 98 | 99 | if section != '*default*' and size > 0: 100 | of = Objectfile(section, offset, size, comment) 101 | 102 | if section.startswith(' '): 103 | children = [] 104 | sections[-1].children.append(of) 105 | 106 | while True: 107 | m = subsectionre.match(s, pos) 108 | if not m: 109 | break 110 | pos = m.end() 111 | offset, function = m.groups() 112 | offset = int(offset, 16) 113 | if sections and sections[-1].children: 114 | children.append([offset, 0, function]) 115 | 116 | if children: 117 | children = update_children_size(children=children, subsection_size=of.size) 118 | 119 | sections[-1].children[-1].children.extend(children) 120 | 121 | else: 122 | sections.append(of) 123 | 124 | return sections 125 | 126 | 127 | def get_subsection_name(section_name: str, subsection: Objectfile) -> str: 128 | subsection_split_names = subsection.section.split('.') 129 | if subsection.section.startswith('.'): 130 | subsection_split_names = subsection_split_names[1:] 131 | 132 | return f'.{subsection_split_names[1]}' if len(subsection_split_names) > 2 else section_name 133 | 134 | 135 | def write_subsection( 136 | section_name: str, 137 | subsection_name: str, 138 | address: str, 139 | size: int, 140 | demangled_name: str, 141 | module_name: str, 142 | file_name: str, 143 | mangled_name: str, 144 | write_file_object: TextIO 145 | ) -> None: 146 | write_file_object.write( 147 | f'{section_name}\t' 148 | f'{subsection_name}\t' 149 | f'{address}\t' 150 | f'{size}\t' 151 | f'{demangled_name}\t' 152 | f'{module_name}\t' 153 | f'{file_name}\t' 154 | f'{mangled_name}\n' 155 | ) 156 | 157 | 158 | def save_subsection(section_name: str, subsection: Objectfile, write_file_object: TextIO) -> None: 159 | subsection_name = get_subsection_name(section_name, subsection) 160 | module_name = subsection.path[0] 161 | file_name = subsection.path[1] 162 | 163 | if not file_name: 164 | file_name, module_name = module_name, '' 165 | 166 | if not subsection.children: 167 | address = f'{subsection.offset:x}' 168 | size = subsection.size 169 | mangled_name = '' if subsection.section == section_name else subsection.section.split(".")[-1] 170 | demangled_name = demangle(mangled_name) if mangled_name else mangled_name 171 | 172 | write_subsection( 173 | section_name=section_name, 174 | subsection_name=subsection_name, 175 | address=address, 176 | size=size, 177 | demangled_name=demangled_name, 178 | module_name=module_name, 179 | file_name=file_name, 180 | mangled_name=mangled_name, 181 | write_file_object=write_file_object 182 | ) 183 | return 184 | 185 | for subsection_child in subsection.children: 186 | address = f'{subsection_child[0]:x}' 187 | size = subsection_child[1] 188 | mangled_name = subsection_child[2] 189 | demangled_name = demangle(mangled_name) 190 | 191 | write_subsection( 192 | section_name=section_name, 193 | subsection_name=subsection_name, 194 | address=address, 195 | size=size, 196 | demangled_name=demangled_name, 197 | module_name=module_name, 198 | file_name=file_name, 199 | mangled_name=mangled_name, 200 | write_file_object=write_file_object 201 | ) 202 | 203 | 204 | def save_section(section: Objectfile, write_file_object: TextIO) -> None: 205 | section_name = section.section 206 | for subsection in section.children: 207 | save_subsection(section_name=section_name, subsection=subsection, write_file_object=write_file_object) 208 | 209 | 210 | def save_parsed_data(parsed_data: list[Objectfile], output_file_name: str) -> None: 211 | with open(output_file_name, 'w') as write_file_object: 212 | for section in parsed_data: 213 | if section.children: 214 | save_section(section=section, write_file_object=write_file_object) 215 | 216 | 217 | if __name__ == '__main__': 218 | if len(sys.argv) < 3: 219 | raise Exception(f'Usage: {sys.argv[0]} ') 220 | 221 | input_file = sys.argv[1] 222 | output_file = sys.argv[2] 223 | 224 | parsed_sections = parse_sections(input_file) 225 | 226 | if parsed_sections is None: 227 | raise Exception(f'Memory configuration is not {input_file}') 228 | 229 | save_parsed_data(parsed_sections, output_file) 230 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cxxfilt==0.3.0 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name="map-parser", 5 | version="0.1.0", 6 | author="Flipper & Community", 7 | author_email="lars+linkermapviz@6xq.net, hello@flipperzero.one", 8 | license="MIT", 9 | description="Visualize GNU ld’s linker map with a tree map.", 10 | url="https://github.com/flipperdevices/map-gcc-parser-python", 11 | classifiers=[ 12 | "Development Status :: 3 - Alpha", 13 | "Intended Audience :: Developers", 14 | "License :: OSI Approved :: MIT License", 15 | "Programming Language :: Python :: 3", 16 | ], 17 | python_requires=">=3.10", 18 | install_requires=[ 19 | "cxxfilt", 20 | ], 21 | entry_points={ 22 | "console_scripts": [ 23 | "map-parser = map-parser.__main__:main", 24 | ], 25 | }, 26 | ) 27 | --------------------------------------------------------------------------------