Skip to content

Commit b36bc60

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

File tree

1 file changed

+96
-41
lines changed

1 file changed

+96
-41
lines changed
Lines changed: 96 additions & 41 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
3-
4-
from typing import List, Optional, Union
3+
from itertools import groupby
4+
from typing import List, Optional, Union, Dict
55
import re
66
import subprocess
77

@@ -23,50 +23,105 @@ 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) -> Dict[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+
# Prepare result (to preserve overall order)
38+
result: Dict[str, Optional[List[dict]]] = {addr: None for addr in addresses}
39+
40+
# Check each elf file for matches
41+
for matcher in self.pc_address_matcher:
42+
elf_path = matcher.elf_path
43+
elf_addresses = [addr for addr in addresses if matcher.is_executable_address(int(addr, 16))]
44+
if not elf_addresses:
45+
continue
46+
47+
# Lookup addresses using addr2line
48+
mapped_addresses = self.lookup_pc_address(elf_addresses, is_rom=(elf_path == self.rom_elf_file), elf_file=elf_path)
49+
50+
# Update result with the mapped addresses
51+
result.update(mapped_addresses)
52+
53+
# Stop searching for addresses that have been found
54+
addresses = [addr for addr in addresses if addr not in mapped_addresses.keys()]
55+
56+
# Filter out addresses that could not be resolved
57+
result = {key: value for key, value in result.items() if value is not None}
58+
59+
return result
60+
61+
62+
def lookup_pc_address(
63+
self,
64+
pc_addr: List[str],
65+
is_rom: bool = False,
66+
elf_file: str = ''
67+
) -> Dict[str, List[dict]]:
68+
"""
69+
Decode a list of addresses using addr2line, returning a map from each address string
70+
to a tuple (function_name, path:line).
71+
"""
72+
elf_file = elf_file if elf_file else (self.rom_elf_file if is_rom else self.elf_files[0]) # type: ignore
73+
cmd = [f'{self.toolchain_prefix}addr2line', '-fiaC', '-e', elf_file, *pc_addr]
6174

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

0 commit comments

Comments
 (0)