Skip to content

Commit a5736c6

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

File tree

1 file changed

+97
-40
lines changed

1 file changed

+97
-40
lines changed
Lines changed: 97 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
22
# SPDX-License-Identifier: Apache-2.0
33

4-
from typing import List, Optional, Union
4+
from typing import List, Optional, Union, Dict, Tuple
55
import re
66
import subprocess
77

@@ -23,50 +23,107 @@ def __init__(
2323
self.toolchain_prefix = toolchain_prefix
2424
self.elf_files = elf_file if isinstance(elf_file, list) else [elf_file]
2525
self.rom_elf_file = rom_elf_file
26-
self.pc_address_buffer = b''
2726
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]
27+
if self.rom_elf_file:
28+
self.pc_address_matcher.append(PcAddressMatcher(self.rom_elf_file))
29+
30+
31+
def decode_addresses(self, line: bytes) -> List[Tuple[str, List[dict]]]:
32+
"""Decode possible addresses in a line, batching addr2line calls per ELF."""
33+
34+
# Find all hex addresses
35+
addresses = re.findall(ADDRESS_RE, line.decode(errors='ignore'))
36+
37+
# Mapped addresses
38+
mapped: Dict[str, List[dict]] = {}
39+
40+
# Addresses left to find
41+
remaining = addresses.copy()
42+
43+
# Check each elf file for matches
44+
for matcher in self.pc_address_matcher:
45+
elf_path = matcher.elf_path
46+
is_rom = elf_path == self.rom_elf_file
47+
elf_addresses = [addr for addr in addresses if matcher.is_executable_address(int(addr, 16))]
48+
if not elf_addresses:
49+
continue
50+
51+
# Lookup addresses using addr2line
52+
mapped_addresses = self.lookup_pc_address(elf_addresses, is_rom=is_rom, elf_file=elf_path)
53+
54+
# Store mapped addresses
55+
mapped.update(mapped_addresses)
56+
57+
# Stop searching for addresses that have been found
58+
remaining = [addr for addr in remaining if addr not in mapped_addresses]
59+
60+
# Return all mapped addresses that were found, in the original order
61+
return [(addr, mapped[addr]) for addr in addresses if addr in mapped]
62+
63+
64+
def lookup_pc_address(
65+
self,
66+
pc_addr: List[str],
67+
is_rom: bool = False,
68+
elf_file: str = ''
69+
) -> Dict[str, List[dict]]:
70+
"""
71+
Decode a list of addresses using addr2line, returning a map from each address string
72+
to a tuple (function_name, path:line).
73+
"""
74+
elf_file = elf_file if elf_file else (self.rom_elf_file if is_rom else self.elf_files[0]) # type: ignore
75+
cmd = [f'{self.toolchain_prefix}addr2line', '-fiaC', '-e', elf_file, *pc_addr]
6176

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

0 commit comments

Comments
 (0)