Skip to content

Commit aea4537

Browse files
committed
feat: Batch requests and extract fn/paths from addr2line
1 parent 13a8447 commit aea4537

File tree

1 file changed

+97
-41
lines changed

1 file changed

+97
-41
lines changed
Lines changed: 97 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
22
# SPDX-License-Identifier: Apache-2.0
3-
4-
from typing import List, Optional, Union
3+
from typing import List, Optional, Union, Dict, Tuple
54
import re
65
import subprocess
76

@@ -23,50 +22,107 @@ def __init__(
2322
self.toolchain_prefix = toolchain_prefix
2423
self.elf_files = elf_file if isinstance(elf_file, list) else [elf_file]
2524
self.rom_elf_file = rom_elf_file
26-
self.pc_address_buffer = b''
2725
self.pc_address_matcher = [PcAddressMatcher(file) for file in self.elf_files]
28-
if rom_elf_file is not None:
29-
self.rom_pc_address_matcher = PcAddressMatcher(rom_elf_file)
30-
31-
def decode_address(self, line: bytes) -> str:
32-
"""Decoded possible addresses in line"""
33-
line = self.pc_address_buffer + line
34-
self.pc_address_buffer = b''
35-
out = ''
36-
for match in re.finditer(ADDRESS_RE, line.decode(errors='ignore')):
37-
num = match.group()
38-
address_int = int(num, 16)
39-
translation = None
40-
41-
# Try looking for the address in the app ELF files
42-
for matcher in self.pc_address_matcher:
43-
if matcher.is_executable_address(address_int):
44-
translation = self.lookup_pc_address(num, elf_file=matcher.elf_path)
45-
if translation is not None:
46-
break
47-
# Not found in app ELF file, check ROM ELF file (if it is available)
48-
if translation is None and self.rom_elf_file is not None and \
49-
self.rom_pc_address_matcher.is_executable_address(address_int):
50-
translation = self.lookup_pc_address(num, is_rom=True, elf_file=self.rom_elf_file)
51-
52-
# Translation found either in the app or ROM ELF file
53-
if translation is not None:
54-
out += translation
55-
return out
56-
57-
def lookup_pc_address(self, pc_addr: str, is_rom: bool = False, elf_file: str = '') -> Optional[str]:
58-
"""Decode address using addr2line tool"""
59-
elf_file: str = elf_file if elf_file else self.rom_elf_file if is_rom else self.elf_files[0] # type: ignore
60-
cmd = [f'{self.toolchain_prefix}addr2line', '-pfiaC', '-e', elf_file, pc_addr]
26+
if self.rom_elf_file:
27+
self.pc_address_matcher.append(PcAddressMatcher(self.rom_elf_file))
28+
29+
30+
def decode_addresses(self, line: bytes) -> List[Tuple[str, List[dict]]]:
31+
"""Decode possible addresses in a line, batching addr2line calls per ELF."""
32+
33+
# Find all hex addresses
34+
addresses = re.findall(ADDRESS_RE, line.decode(errors='ignore'))
35+
36+
# Mapped addresses
37+
mapped: Dict[str, List[dict]] = {}
38+
39+
# Addresses left to find
40+
remaining = addresses.copy()
41+
42+
# Check each elf file for matches
43+
for matcher in self.pc_address_matcher:
44+
elf_path = matcher.elf_path
45+
is_rom = elf_path == self.rom_elf_file
46+
elf_addresses = [addr for addr in addresses if matcher.is_executable_address(int(addr, 16))]
47+
if not elf_addresses:
48+
continue
49+
50+
# Lookup addresses using addr2line
51+
mapped_addresses = self.lookup_pc_address(elf_addresses, is_rom=is_rom, elf_file=elf_path)
52+
53+
# Store mapped addresses
54+
mapped.update(mapped_addresses)
55+
56+
# Stop searching for addresses that have been found
57+
remaining = [addr for addr in remaining if addr not in mapped_addresses]
58+
59+
# Return all mapped addresses that were found, in the original order
60+
return [(addr, mapped[addr]) for addr in addresses if addr in mapped]
61+
62+
63+
def lookup_pc_address(
64+
self,
65+
pc_addr: List[str],
66+
is_rom: bool = False,
67+
elf_file: str = ''
68+
) -> Dict[str, List[dict]]:
69+
"""
70+
Decode a list of addresses using addr2line, returning a map from each address string
71+
to a tuple (function_name, path:line).
72+
"""
73+
elf_file = elf_file if elf_file else (self.rom_elf_file if is_rom else self.elf_files[0]) # type: ignore
74+
cmd = [f'{self.toolchain_prefix}addr2line', '-fiaC', '-e', elf_file, *pc_addr]
6175

6276
try:
63-
translation = subprocess.check_output(cmd, cwd='.')
64-
if b'?? ??:0' not in translation:
65-
decoded = translation.decode()
66-
return decoded if not is_rom else decoded.replace('at ??:?', 'in ROM')
77+
batch_output = subprocess.check_output(cmd, cwd='.')
6778
except OSError as err:
6879
red_print(f'{" ".join(cmd)}: {err}')
80+
return {}
6981
except subprocess.CalledProcessError as err:
7082
red_print(f'{" ".join(cmd)}: {err}')
7183
red_print('ELF file is missing or has changed, the build folder was probably modified.')
72-
return None
84+
return {}
85+
86+
decoded_output = batch_output.decode(errors='ignore')
87+
88+
# Step 1: Split into sections where each section starts with an 8-hex-digit address
89+
sections = re.split(r'(?=0x[0-9A-Fa-f]{8}\r?\n)', decoded_output)
90+
91+
result: Dict[str, List[dict]] = {}
92+
for section in sections:
93+
section = section.strip()
94+
if not section:
95+
continue
96+
97+
# Step 2: Split the section by newlines
98+
lines = section.split('\n')
99+
100+
# Step 3: First line is the address
101+
address = lines[0].strip()
102+
103+
# Step 4: Build trace by consuming lines in pairs (function, path:line)
104+
trace: List[dict] = []
105+
for i in range(1, len(lines) - 1, 2):
106+
fn = lines[i].strip()
107+
path_line = lines[i + 1].strip()
108+
109+
# Remove any " (discriminator N)" suffix
110+
path_line = re.sub(r' \(discriminator \d+\)$', '', path_line)
111+
112+
# Split on the last colon before digits to separate path and line number
113+
parts = re.split(r':(?=\d+|\?$)', path_line, maxsplit=1)
114+
if len(parts) == 2:
115+
path, line_str = parts
116+
line_num = int(line_str) if line_str != '?' else '?'
117+
else:
118+
path = parts[0]
119+
line_num = 0
120+
121+
if path == '??' and is_rom:
122+
path = 'ROM'
123+
124+
trace.append({'fn': fn, 'path': path, 'line': line_num})
125+
126+
result[address] = trace
127+
128+
return result

0 commit comments

Comments
 (0)