Skip to content

Commit 81eb144

Browse files
authored
⚡ (minor) Add nearpoint search support (#92)
Nearpoint search reduces the time required to perform an exception entry lookup by grouping transparent functions by their unwind information and sorting all groups of functions by size from largest to smallest. This ordering allows for a lookup table to be built that can provide information of where to find an exception entry. The goal for valid nearpoint tables is to ensure that for all inputs, every output is within 8 entries of the desired entry. The current format of the nearpoint table and descriptors is experimental and thus may change between new updates.
1 parent 22b72ca commit 81eb144

File tree

11 files changed

+2153
-20
lines changed

11 files changed

+2153
-20
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"reg": [
3+
{
4+
"bits": 10,
5+
"name": "inter-block offset",
6+
"type": 6
7+
},
8+
{
9+
"bits": 22,
10+
"name": "block index",
11+
"type": 5
12+
}
13+
],
14+
"config": {
15+
"fontsize": 14,
16+
"bits": 32,
17+
"hflip": true,
18+
"lanes": 1,
19+
"compact": true
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"reg": [
3+
{
4+
"bits": 24,
5+
"name": "entry number offset",
6+
"type": 5
7+
},
8+
{
9+
"bits": 8,
10+
"name": "average function size",
11+
"type": 6
12+
}
13+
],
14+
"config": {
15+
"fontsize": 14,
16+
"bits": 32,
17+
"hflip": true,
18+
"lanes": 1,
19+
"compact": true
20+
}
21+
}

scripts/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Scripts
2+
3+
The scripts for testing and playing around with libhal exceptions.
4+
5+
## Near Point Script?
6+
7+
```plaintext
8+
usage: nearpoint.py [-h] [-b BLOCK_POWER] [-s SMALL_BLOCK_POWER] [-n] file
9+
10+
positional arguments:
11+
file text file with a list of function addresses in your code starting from 0 to N where N is the last function
12+
address, or output from the command: nm app.elf --size-sort --radix=d | grep " [Tt] " | awk '{print $1}'
13+
14+
options:
15+
-h, --help show this help message and exit
16+
-b BLOCK_POWER, --block_power BLOCK_POWER
17+
Set the block size based on a power of 2.
18+
-s SMALL_BLOCK_POWER, --small_block_power SMALL_BLOCK_POWER
19+
Set the small block size based on a power of 2.
20+
-n, --nm The input file is actually output from an NM command: nm app.elf --size-sort --radix=d | grep " [Tt] " | awk
21+
'{print $1}'
22+
```
23+
24+
The script works with output from nm following this:
25+
26+
```bash
27+
python3 nearpoint.py --tool-prefix="/path/to/arm-none-eabi/bin/" --map="app.elf.map" app.elf
28+
```
29+
30+
After
31+
32+
What this will do is create a size sorted list of each function in the program
33+
and output its starting address. This easy to execute script enable 3rd parties
34+
to send us this information in a mostly erased fashion to compare against our
35+
application. Even if the application does not use exceptions.
36+
37+
```bash
38+
python3 scripts/nearpoint.py demos/multi_levels.elf.nm -n -b 10
39+
```
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#!/usr/bin/env python3
2+
import subprocess
3+
import re
4+
import argparse
5+
import csv
6+
from typing import NamedTuple, List
7+
8+
9+
class Function(NamedTuple):
10+
name: str
11+
size: int
12+
orig_addr: int
13+
new_addr: int
14+
index: int
15+
16+
17+
def get_sorted_functions(binary_path: str) -> List[Function]:
18+
"""Get size-sorted functions using nm and calculate new addresses."""
19+
try:
20+
cmd = ['nm', '--size-sort', '--print-size', binary_path]
21+
nm_output = subprocess.check_output(
22+
cmd, universal_newlines=True, stderr=subprocess.PIPE)
23+
24+
# First pass to get functions and find lowest address
25+
temp_functions = []
26+
lowest_addr = float('inf')
27+
for line in nm_output.splitlines():
28+
if ' t ' in line or ' T ' in line:
29+
parts = line.strip().split()
30+
if len(parts) >= 4:
31+
addr = int(parts[0], 16)
32+
size = int(parts[1], 16)
33+
name = parts[3]
34+
lowest_addr = min(lowest_addr, addr)
35+
temp_functions.append((name, size, addr))
36+
37+
# Sort by size in descending order
38+
temp_functions.sort(key=lambda x: x[1], reverse=True)
39+
40+
# Calculate new sequential addresses
41+
current_addr = lowest_addr
42+
functions = []
43+
for idx, (name, size, orig_addr) in enumerate(temp_functions):
44+
functions.append(
45+
Function(name, size, orig_addr, current_addr, idx))
46+
# Align next address to 4-byte boundary
47+
current_addr = (current_addr + size + 3) & ~3
48+
49+
return functions
50+
51+
except subprocess.CalledProcessError as e:
52+
print(f"Error running nm: {e}")
53+
print(f"stderr: {e.stderr}")
54+
return []
55+
56+
57+
def generate_linker_section(functions: List[Function]) -> str:
58+
"""Generate linker script section with sorted functions."""
59+
script = []
60+
script.append("SECTIONS")
61+
script.append("{")
62+
script.append(" .text :")
63+
script.append(" {")
64+
65+
# Add functions in size order
66+
for func in functions:
67+
script.append(f" KEEP(*(.text.{func.name}))")
68+
69+
# Add any remaining text sections
70+
script.append(" *(.text*)")
71+
script.append(" *(.rodata*)")
72+
script.append(" } > FLASH")
73+
script.append("}")
74+
75+
return "\n".join(script)
76+
77+
78+
def write_csv(functions: List[Function], filepath: str):
79+
"""Write function information to CSV file."""
80+
with open(filepath, 'w', newline='') as csvfile:
81+
writer = csv.writer(csvfile)
82+
writer.writerow(['Index Entry', 'Function Name', 'Address'])
83+
for func in functions:
84+
writer.writerow([func.index, func.name, hex(func.new_addr)])
85+
86+
87+
def format_size(size: int) -> str:
88+
"""Format size in bytes to human-readable format."""
89+
if size < 1024:
90+
return f"{size:>8} B"
91+
elif size < 1024 * 1024:
92+
return f"{size/1024:>7.1f} KB"
93+
else:
94+
return f"{size/1024/1024:>7.1f} MB"
95+
96+
97+
def main():
98+
parser = argparse.ArgumentParser(
99+
description='Generate size-sorted linker script section and CSV from ELF file'
100+
)
101+
parser.add_argument('binary', help='Path to the ELF binary')
102+
parser.add_argument('-o', '--output', help='Output linker script file')
103+
parser.add_argument('-c', '--csv', help='Output CSV file')
104+
parser.add_argument('--summary', action='store_true',
105+
help='Show size summary of functions')
106+
args = parser.parse_args()
107+
108+
# Get sorted functions
109+
functions = get_sorted_functions(args.binary)
110+
111+
if not functions:
112+
print("No functions found in binary")
113+
return
114+
115+
# Generate linker script
116+
linker_script = generate_linker_section(functions)
117+
118+
# Output handling for linker script
119+
if args.output:
120+
with open(args.output, 'w') as f:
121+
f.write(linker_script)
122+
print(f"Linker script written to: {args.output}")
123+
else:
124+
print("\nLinker Script Section:")
125+
print("=" * 80)
126+
print(linker_script)
127+
print("=" * 80)
128+
129+
# Output CSV if requested
130+
if args.csv:
131+
write_csv(functions, args.csv)
132+
print(f"CSV file written to: {args.csv}")
133+
134+
# Print summary if requested
135+
if args.summary:
136+
total_size = sum(f.size for f in functions)
137+
print("\nFunction Size Summary:")
138+
print("-" * 90)
139+
print(f"{'Index':>5} {'Size':>10} {'New Addr':>12} {'Function Name':<50}")
140+
print("-" * 90)
141+
142+
for func in functions:
143+
print(
144+
f"{func.index:>5} {format_size(func.size)} {func.new_addr:>#12x} {func.name:<50}")
145+
146+
print("-" * 90)
147+
print(f"Total Functions: {len(functions)}")
148+
print(f"Total Size: {format_size(total_size)}")
149+
150+
151+
if __name__ == '__main__':
152+
main()

0 commit comments

Comments
 (0)