mapuche 0.0.1__tar.gz
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.
- mapuche-0.0.1/.github/workflows/python-publish.yml +39 -0
- mapuche-0.0.1/.gitignore +4 -0
- mapuche-0.0.1/LICENSE +20 -0
- mapuche-0.0.1/PKG-INFO +53 -0
- mapuche-0.0.1/README.md +40 -0
- mapuche-0.0.1/imgs/mapuche_diff_demo.png +0 -0
- mapuche-0.0.1/mapuche/__init__.py +0 -0
- mapuche-0.0.1/mapuche/map_parse.py +196 -0
- mapuche-0.0.1/mapuche/mapuche.py +36 -0
- mapuche-0.0.1/mapuche/tree_table.py +2742 -0
- mapuche-0.0.1/mapuche.egg-info/PKG-INFO +53 -0
- mapuche-0.0.1/mapuche.egg-info/SOURCES.txt +21 -0
- mapuche-0.0.1/mapuche.egg-info/dependency_links.txt +1 -0
- mapuche-0.0.1/mapuche.egg-info/entry_points.txt +2 -0
- mapuche-0.0.1/mapuche.egg-info/requires.txt +1 -0
- mapuche-0.0.1/mapuche.egg-info/top_level.txt +1 -0
- mapuche-0.0.1/pyproject.toml +27 -0
- mapuche-0.0.1/requirements.txt +1 -0
- mapuche-0.0.1/setup.cfg +4 -0
- mapuche-0.0.1/test/build_new/test_cxx_exception.elf +0 -0
- mapuche-0.0.1/test/build_new/test_cxx_exception.map +24808 -0
- mapuche-0.0.1/test/build_old/test_cxx_exception.elf +0 -0
- mapuche-0.0.1/test/build_old/test_cxx_exception.map +24222 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# This workflow will upload a Python Package using Twine when a release is created
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
|
|
3
|
+
|
|
4
|
+
# This workflow uses actions that are not certified by GitHub.
|
|
5
|
+
# They are provided by a third-party and are governed by
|
|
6
|
+
# separate terms of service, privacy policy, and support
|
|
7
|
+
# documentation.
|
|
8
|
+
|
|
9
|
+
name: Upload Python Package
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
release:
|
|
13
|
+
types: [published]
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
deploy:
|
|
20
|
+
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
- name: Set up Python
|
|
26
|
+
uses: actions/setup-python@v3
|
|
27
|
+
with:
|
|
28
|
+
python-version: '3.x'
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: |
|
|
31
|
+
python -m pip install --upgrade pip
|
|
32
|
+
pip install build
|
|
33
|
+
- name: Build package
|
|
34
|
+
run: python -m build
|
|
35
|
+
- name: Publish package
|
|
36
|
+
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
|
|
37
|
+
with:
|
|
38
|
+
user: __token__
|
|
39
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
mapuche-0.0.1/.gitignore
ADDED
mapuche-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2024 Alexey Lapshin and others
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
mapuche-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: mapuche
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Linker's map file browser
|
|
5
|
+
Author-email: Alexey Lapshin <alexeyfonlapshin@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: map,elf,linker
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Requires-Python: >=3.6
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: textual==0.40.0
|
|
13
|
+
|
|
14
|
+
# mapuche
|
|
15
|
+
|
|
16
|
+
Mapuche is a linker's map file browser
|
|
17
|
+
|
|
18
|
+
If you are reading this, please don't forget to give this project a star on GitHub!
|
|
19
|
+
|
|
20
|
+
> [!CAUTION]
|
|
21
|
+
> Mapuche is in pre-alfa development stage. Crashes or unexpected output may occur! Please create an issue if any.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
pip install mapuche
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
> [!IMPORTANT]
|
|
33
|
+
> For now mapuche supports only map files of ELFs generated with `-ffunction-sections` and `-fdata-sections` compile options.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
mapuche <elf.map> [elf_for_diff.map]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Screenshot
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
## TODO list
|
|
44
|
+
|
|
45
|
+
- [ ] implement `--help`/`--version`
|
|
46
|
+
- [ ] regex filters
|
|
47
|
+
- [ ] columns sort
|
|
48
|
+
- [ ] support expand/collapse by space button
|
|
49
|
+
- [ ] move input section name from "name" to separate column
|
|
50
|
+
- [ ] cute alignment for `size`/`diff`/`delta` columns
|
|
51
|
+
- [ ] assembler diff viewer in popup widget
|
|
52
|
+
- [ ] support map files for ELFs without `-ffunction-sections`/`-fdata-sections`
|
|
53
|
+
- [ ] tests
|
mapuche-0.0.1/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# mapuche
|
|
2
|
+
|
|
3
|
+
Mapuche is a linker's map file browser
|
|
4
|
+
|
|
5
|
+
If you are reading this, please don't forget to give this project a star on GitHub!
|
|
6
|
+
|
|
7
|
+
> [!CAUTION]
|
|
8
|
+
> Mapuche is in pre-alfa development stage. Crashes or unexpected output may occur! Please create an issue if any.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
pip install mapuche
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
> [!IMPORTANT]
|
|
20
|
+
> For now mapuche supports only map files of ELFs generated with `-ffunction-sections` and `-fdata-sections` compile options.
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
mapuche <elf.map> [elf_for_diff.map]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Screenshot
|
|
27
|
+
|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
## TODO list
|
|
31
|
+
|
|
32
|
+
- [ ] implement `--help`/`--version`
|
|
33
|
+
- [ ] regex filters
|
|
34
|
+
- [ ] columns sort
|
|
35
|
+
- [ ] support expand/collapse by space button
|
|
36
|
+
- [ ] move input section name from "name" to separate column
|
|
37
|
+
- [ ] cute alignment for `size`/`diff`/`delta` columns
|
|
38
|
+
- [ ] assembler diff viewer in popup widget
|
|
39
|
+
- [ ] support map files for ELFs without `-ffunction-sections`/`-fdata-sections`
|
|
40
|
+
- [ ] tests
|
|
Binary file
|
|
File without changes
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
INFO = "*info*" # TODO remove
|
|
6
|
+
|
|
7
|
+
sections = {}
|
|
8
|
+
sections_diff = {}
|
|
9
|
+
|
|
10
|
+
def pretty_print_dict(d):
|
|
11
|
+
# print pretty json
|
|
12
|
+
import json
|
|
13
|
+
print(json.dumps(d, indent=4))
|
|
14
|
+
|
|
15
|
+
def parse_lib_and_objfile(source):
|
|
16
|
+
if source == None:
|
|
17
|
+
return (None, None)
|
|
18
|
+
objfile = None
|
|
19
|
+
|
|
20
|
+
lib_end = source.rfind('(')
|
|
21
|
+
if lib_end > 0:
|
|
22
|
+
objfile = source[lib_end + 1:-1]
|
|
23
|
+
else:
|
|
24
|
+
lib_end = len(source)
|
|
25
|
+
lib_start = source.rfind('/') + 1
|
|
26
|
+
lib = source[lib_start:lib_end]
|
|
27
|
+
return (lib, objfile)
|
|
28
|
+
|
|
29
|
+
def parse_mapfile_entry(f, line):
|
|
30
|
+
split = line.split()
|
|
31
|
+
name = split[0]
|
|
32
|
+
# remove the first element from split
|
|
33
|
+
split.pop(0)
|
|
34
|
+
# check if one word in line
|
|
35
|
+
if len(split) == 0:
|
|
36
|
+
# read new line and print
|
|
37
|
+
line = f.readline()
|
|
38
|
+
split = line.split()
|
|
39
|
+
|
|
40
|
+
if len(split) == 1:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
source = split[2] if len(split) == 3 else None
|
|
44
|
+
|
|
45
|
+
return {'name': name, 'address': split[0], 'size': int(split[1], 16), 'source': source}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def fill_all_fields_with_info(data):
|
|
49
|
+
size = 0
|
|
50
|
+
for k in data['entries']:
|
|
51
|
+
size += fill_all_fields_with_info(data['entries'][k])
|
|
52
|
+
|
|
53
|
+
if data['size'] == None:
|
|
54
|
+
data['size'] = size
|
|
55
|
+
return data['size']
|
|
56
|
+
|
|
57
|
+
def convert_to_textual_rows(json_data, is_diff=False, add_header=True):
|
|
58
|
+
rows = []
|
|
59
|
+
if add_header:
|
|
60
|
+
if is_diff:
|
|
61
|
+
rows += [tuple(['name', 'address', 'size', 'diff', 'delta'])]
|
|
62
|
+
else:
|
|
63
|
+
rows += [tuple(['name', 'address', 'size'])]
|
|
64
|
+
for k, v in json_data['entries'].items():
|
|
65
|
+
if is_diff:
|
|
66
|
+
rows.append(tuple([v['name'], v['address'], v['size'], v['diff'], v['delta']]))
|
|
67
|
+
else:
|
|
68
|
+
rows.append(tuple([v['name'], v['address'], v['size']]))
|
|
69
|
+
if len(v['entries']) == 0:
|
|
70
|
+
continue
|
|
71
|
+
rows += tuple([convert_to_textual_rows(v, is_diff, False)])
|
|
72
|
+
return rows
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def add_entry_to_entries(entries, name, entry=None):
|
|
76
|
+
if name in entries:
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
if entry != None:
|
|
80
|
+
entries[name] = entry
|
|
81
|
+
else:
|
|
82
|
+
entries[name] = {'name': name, 'address': None, 'size': None, 'source': None}
|
|
83
|
+
entries[name]['entries'] = {}
|
|
84
|
+
|
|
85
|
+
def _parse_map_file(map_file):
|
|
86
|
+
# open file in argv[0] and read until string "Linker script and memory map"
|
|
87
|
+
# array of sections. Contains section name, address and size
|
|
88
|
+
sections = {}
|
|
89
|
+
parse = False
|
|
90
|
+
root_entry_name = ''
|
|
91
|
+
with open(map_file, 'r') as f:
|
|
92
|
+
while True:
|
|
93
|
+
line = f.readline()
|
|
94
|
+
if not line:
|
|
95
|
+
break
|
|
96
|
+
if not parse:
|
|
97
|
+
if "Linker script and memory map" in line:
|
|
98
|
+
parse = True
|
|
99
|
+
continue
|
|
100
|
+
if "Cross Reference Table" in line:
|
|
101
|
+
break
|
|
102
|
+
|
|
103
|
+
if line.startswith('.'):
|
|
104
|
+
is_root_entry = True
|
|
105
|
+
elif line.startswith(' .'):
|
|
106
|
+
is_root_entry = False
|
|
107
|
+
else:
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
map_entry = parse_mapfile_entry(f, line)
|
|
111
|
+
|
|
112
|
+
if map_entry is None:
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
if is_root_entry:
|
|
116
|
+
root_entry_name = map_entry['name']
|
|
117
|
+
add_entry_to_entries(sections, root_entry_name, map_entry)
|
|
118
|
+
else:
|
|
119
|
+
# append last sections element with entry
|
|
120
|
+
lib, obj_file = parse_lib_and_objfile(map_entry['source'])
|
|
121
|
+
add_entry_to_entries(sections[root_entry_name]['entries'], lib)
|
|
122
|
+
add_entry_to_entries(sections[root_entry_name]['entries'][lib]['entries'], obj_file)
|
|
123
|
+
add_entry_to_entries(sections[root_entry_name]['entries'][lib]['entries'][obj_file]['entries'], map_entry['name'], map_entry)
|
|
124
|
+
sections = {'name': 'total', 'size': 0, 'entries': sections}
|
|
125
|
+
fill_all_fields_with_info(sections)
|
|
126
|
+
return sections
|
|
127
|
+
|
|
128
|
+
def parse_map_file(map_file):
|
|
129
|
+
p = _parse_map_file(map_file)
|
|
130
|
+
return convert_to_textual_rows(p, False)
|
|
131
|
+
|
|
132
|
+
def get_all_entries_keys(d1, d2):
|
|
133
|
+
result = []
|
|
134
|
+
if not d1 is None:
|
|
135
|
+
result += d1['entries'].keys()
|
|
136
|
+
if not d2 is None:
|
|
137
|
+
result += d2['entries'].keys()
|
|
138
|
+
return list(dict.fromkeys(result))
|
|
139
|
+
|
|
140
|
+
def _collect_diff(diff_entries, leaf, m1, m2):
|
|
141
|
+
has_diff = False
|
|
142
|
+
|
|
143
|
+
diff_entries[leaf] = {'name': leaf, 'address': None, 'size': 0, 'diff': 0, 'delta': 0, 'source': None, 'entries': {}}
|
|
144
|
+
|
|
145
|
+
for k in get_all_entries_keys(m1, m2):
|
|
146
|
+
if m2 == None or not k in m2['entries']:
|
|
147
|
+
has_diff |= _collect_diff(diff_entries[leaf]['entries'], k, m1['entries'][k], None)
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
if m1 == None or not k in m1['entries']:
|
|
151
|
+
has_diff |= _collect_diff(diff_entries[leaf]['entries'], k, None, m2['entries'][k])
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
has_diff |= _collect_diff(diff_entries[leaf]['entries'], k, m1['entries'][k], m2['entries'][k])
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
s1 = 0 if m1 == None else m1['size']
|
|
158
|
+
s2 = 0 if m2 == None else m2['size']
|
|
159
|
+
has_diff |= s1 != s2
|
|
160
|
+
|
|
161
|
+
if not has_diff:
|
|
162
|
+
del diff_entries[leaf]
|
|
163
|
+
return has_diff
|
|
164
|
+
|
|
165
|
+
reference = m1
|
|
166
|
+
if m1 is None:
|
|
167
|
+
reference = m2
|
|
168
|
+
|
|
169
|
+
diff_entries[leaf]['name'] = reference['name']
|
|
170
|
+
diff_entries[leaf]['size'] = s1
|
|
171
|
+
diff_entries[leaf]['address'] = reference['address'] if 'address' in reference else None
|
|
172
|
+
diff_entries[leaf]['source'] = reference['source'] if 'source' in reference else None
|
|
173
|
+
diff_entries[leaf]['diff'] = s1 - s2
|
|
174
|
+
diff_entries[leaf]['delta'] = 100 if s2 == 0 else round((s1 - s2 ) / s2 * 100, 1)
|
|
175
|
+
|
|
176
|
+
return has_diff
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def generate_diff_table(map_1, map_2):
|
|
180
|
+
m1 = _parse_map_file(map_1)
|
|
181
|
+
m2 = _parse_map_file(map_2)
|
|
182
|
+
|
|
183
|
+
_collect_diff(sections_diff, 'total', m1, m2)
|
|
184
|
+
|
|
185
|
+
return convert_to_textual_rows(sections_diff['total'], True)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
if __name__ == "__main__":
|
|
189
|
+
if len(sys.argv) == 2:
|
|
190
|
+
rows_file = parse_map_file(sys.argv[1])
|
|
191
|
+
print(rows_file)
|
|
192
|
+
elif len(sys.argv) == 3:
|
|
193
|
+
rows_diff = generate_diff_table(sys.argv[1], sys.argv[2])
|
|
194
|
+
else:
|
|
195
|
+
print("Wrong number of arguments. Please provide one or two files.")
|
|
196
|
+
exit()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from itertools import cycle
|
|
4
|
+
from os import sys
|
|
5
|
+
from textual.app import App, ComposeResult
|
|
6
|
+
from .tree_table import DataTable
|
|
7
|
+
from .map_parse import parse_map_file, generate_diff_table
|
|
8
|
+
|
|
9
|
+
class TableApp(App):
|
|
10
|
+
def compose(self) -> ComposeResult:
|
|
11
|
+
yield DataTable()
|
|
12
|
+
|
|
13
|
+
def on_mount(self) -> None:
|
|
14
|
+
table = self.query_one(DataTable)
|
|
15
|
+
table.cursor_type = next(cycle(["row"]))
|
|
16
|
+
table.zebra_stripes = True
|
|
17
|
+
if len(sys.argv) == 2:
|
|
18
|
+
rows = parse_map_file(sys.argv[1])
|
|
19
|
+
elif len(sys.argv) == 3:
|
|
20
|
+
rows = generate_diff_table(sys.argv[1], sys.argv[2])
|
|
21
|
+
table.add_columns(*rows[0])
|
|
22
|
+
table.add_rows(rows[1:])
|
|
23
|
+
|
|
24
|
+
def key_c(self):
|
|
25
|
+
table = self.query_one(DataTable)
|
|
26
|
+
table.cursor_type = next(cursors)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def main():
|
|
30
|
+
if len(sys.argv) != 2 and len(sys.argv) != 3:
|
|
31
|
+
print("Wrong parameters count!")
|
|
32
|
+
app = TableApp()
|
|
33
|
+
app.run()
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
main()
|