Skip to content

Commit 6f25eba

Browse files
committed
Merge branch 'feature/add_a_script_to_generate_symbols' into 'master'
feat(elf_loader): Add a script to generate symbols Closes AEG-2079 See merge request ae_group/esp-iot-solution!1187
2 parents 99d6826 + dd23493 commit 6f25eba

File tree

6 files changed

+404
-0
lines changed

6 files changed

+404
-0
lines changed

components/elf_loader/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# ChangeLog
22

3+
## Unreleased - 2024-12-27
4+
5+
* Added a script to generate the symbol table for the ELF APP:
6+
* Supports generating symbols table based on ELF file
7+
* Supports generating symbols table based on static libraries
8+
39
## v1.0.0 - 2024-12-09
410

511
* Added support for the following RISC-V chips: ESP32-P4 and ESP32-C6

components/elf_loader/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ if(CONFIG_ELF_LOADER)
44
"src/esp_elf.c"
55
"src/esp_elf_adapter.c")
66

7+
if(CONFIG_ELF_LOADER_CUSTOMER_SYMBOLS)
8+
list(APPEND srcs "src/esp_all_symbol.c")
9+
endif()
10+
711
if(CONFIG_IDF_TARGET_ARCH_XTENSA)
812
list(APPEND srcs "src/arch/esp_elf_xtensa.c")
913

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <stddef.h>
8+
9+
#include "private/elf_symbol.h"
10+
11+
/* Extern declarations from ELF symbol table */
12+
13+
#pragma GCC diagnostic push
14+
#pragma GCC diagnostic ignored "-Wbuiltin-declaration-mismatch"
15+
#pragma GCC diagnostic pop
16+
17+
/* Available ELF symbols table: g_customer_elfsyms */
18+
19+
const struct esp_elfsym g_customer_elfsyms[] = {
20+
ESP_ELFSYM_END
21+
};
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
#!/usr/bin/env python
2+
#
3+
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
import logging
8+
import os
9+
import argparse
10+
import sys
11+
import re
12+
import subprocess
13+
14+
def get_global_symbols(lines, type, undefined=False, symbol_types=None):
15+
"""
16+
Extract global symbols from the given lines of a symbol table.
17+
18+
:param lines: List of lines from the symbol table, each representing a symbol.
19+
:param type: Type of the input file ('e' for ELF files, 'l' for static libraries).
20+
:param undefined: If True, only extract undefined (UND) symbols; otherwise, extract all GLOBAL symbols.
21+
:param symbol_types: A list of symbol types to filter by (e.g., ['FUNC', 'OBJECT']). If None, no filtering by type.
22+
:return: List of symbol names if any match the criteria; otherwise, returns an empty list.
23+
"""
24+
symbols = []
25+
26+
if type == 'e':
27+
# Pattern for ELF files
28+
if not undefined:
29+
pattern = re.compile(
30+
r'^\s*\d+:\s+(\S+)\s+(\d+)\s+(FUNC|OBJECT)\s+GLOBAL\s+DEFAULT\s+(?:\d+|ABS|UND|COM|DEBUG)\s+(\S+)',
31+
re.MULTILINE
32+
)
33+
else:
34+
pattern = re.compile(
35+
r'^\s*\d*:\s*\w*\s*\d*\s*NOTYPE\s*GLOBAL\s*DEFAULT\s*UND\s+(\S*)',
36+
re.MULTILINE
37+
)
38+
39+
for line in lines:
40+
match = pattern.match(line)
41+
if match:
42+
if not undefined:
43+
address, size, symbol_type, symbol_name = match.groups()
44+
45+
# Filter by symbol type if specified
46+
if symbol_types and symbol_type not in symbol_types:
47+
continue
48+
49+
symbols.append(symbol_name)
50+
else:
51+
symbol_name = match.group(1)
52+
symbols.append(symbol_name)
53+
54+
elif type == 'l':
55+
# Patterns for static libraries
56+
func_pattern = re.compile(r'^\s*[0-9a-fA-F]+\s+[TD]\s+(\S+)$')
57+
var_pattern = re.compile(r'^\s*[0-9a-fA-F]+\s+[BD]\s+(\S+)$')
58+
59+
for line in lines:
60+
if not undefined:
61+
func_match = func_pattern.match(line)
62+
var_match = var_pattern.match(line)
63+
64+
if func_match:
65+
symbols.append(func_match.group(1))
66+
elif var_match:
67+
symbols.append(var_match.group(1))
68+
69+
return symbols
70+
71+
def save_c_file(symbols, output, symbol_table, exclude_symbols=None):
72+
"""
73+
Write extern declarations and ESP_ELFSYM structure to a C file, excluding specified symbols.
74+
75+
:param symbols: List of symbol names.
76+
:param output: Path to the output C file.
77+
:param exclude_symbols: List of symbol names to exclude; defaults to ['elf_find_sym'].
78+
"""
79+
if exclude_symbols is None:
80+
exclude_symbols = ['elf_find_sym'] # Set default excluded symbols
81+
82+
# Filter out excluded symbols
83+
filtered_symbols = [name for name in symbols if name not in exclude_symbols]
84+
85+
# Build the content of the C file
86+
buf = '/*\n'
87+
buf += ' * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD\n'
88+
buf += ' *\n'
89+
buf += ' * SPDX-License-Identifier: Apache-2.0\n'
90+
buf += ' */\n\n'
91+
92+
# Add standard library headers
93+
libc_headers = ['stddef'] # Add more headers as needed
94+
buf += '\n'.join([f'#include <{h}.h>' for h in libc_headers]) + '\n\n'
95+
96+
# Add custom header
97+
buf += '#include "private/elf_symbol.h"\n\n'
98+
99+
# Generate extern declarations if there are symbols
100+
if filtered_symbols:
101+
buf += '/* Extern declarations from ELF symbol table */\n\n'
102+
buf += '#pragma GCC diagnostic push\n'
103+
buf += '#pragma GCC diagnostic ignored "-Wbuiltin-declaration-mismatch"\n'
104+
for symbol_name in filtered_symbols:
105+
buf += f'extern int {symbol_name};\n'
106+
buf += '#pragma GCC diagnostic pop\n\n'
107+
108+
# Define the symbol table structure with dynamic variable name
109+
symbol_table_var = f'g_{symbol_table}_elfsyms'
110+
buf += f'/* Available ELF symbols table: {symbol_table_var} */\n'
111+
buf += f'\nconst struct esp_elfsym {symbol_table_var}[] = {{\n'
112+
113+
# Generate ESP_ELFSYM_EXPORT entries
114+
if filtered_symbols:
115+
for symbol_name in filtered_symbols:
116+
buf += f' ESP_ELFSYM_EXPORT({symbol_name}),\n'
117+
118+
# End the symbol table
119+
buf += ' ESP_ELFSYM_END\n'
120+
buf += '};\n'
121+
122+
# Write to the file
123+
with open(output, 'w+') as f:
124+
f.write(buf)
125+
126+
def main():
127+
"""
128+
Main function to parse command-line arguments and process the input file's symbol table.
129+
"""
130+
parser = argparse.ArgumentParser(description='Extract all public functions from an application project', prog='symbols')
131+
132+
parser.add_argument(
133+
'--output-file', '-of',
134+
help='Custom output file path with filename (overrides --output)',
135+
default=None
136+
)
137+
138+
parser.add_argument(
139+
'--input', '-i',
140+
help='Input file name with full path',
141+
required=True
142+
)
143+
144+
parser.add_argument(
145+
'--undefined', '-u',
146+
action='store_true',
147+
help='If set, only extract undefined (UND) symbols; otherwise, extract all GLOBAL symbols.',
148+
default=False
149+
)
150+
151+
parser.add_argument(
152+
'--exclude', '-e',
153+
nargs='+',
154+
help='Symbols to exclude from the generated C file (e.g., memcpy __ltdf2). Default: elf_find_sym',
155+
default=[] # User can extend this list
156+
)
157+
158+
parser.add_argument(
159+
'--type', '-t',
160+
choices=['e', 'l'],
161+
required=True,
162+
help='Type of the input file: "elf" for ELF file, "lib" for static library (.a)'
163+
)
164+
165+
parser.add_argument(
166+
'--debug', '-d',
167+
help='Debug level(option is \'debug\')',
168+
default='no',
169+
type=str)
170+
171+
args = parser.parse_args()
172+
173+
# Configure logging
174+
if args.debug == 'debug':
175+
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
176+
else:
177+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
178+
179+
# Get the absolute path of the current file
180+
cur_dir = os.path.dirname(os.path.abspath(__file__))
181+
182+
if args.type == 'e':
183+
extracted_part = 'customer'
184+
elif args.type == 'l':
185+
# Convert relative path to absolute path
186+
input_abs_path = os.path.abspath(args.input)
187+
188+
# Get the base name of the input file (without extension)
189+
input_basename = os.path.basename(input_abs_path)
190+
191+
# Use a regular expression to extract the middle part
192+
match = re.search(r'^lib(.+)\.a$', input_basename)
193+
if match:
194+
extracted_part = match.group(1)
195+
else:
196+
logging.error('Invalid input file name format. Expected format: lib<name>.a')
197+
sys.exit(1)
198+
199+
# Determine the output file path
200+
if args.output_file:
201+
# Use the custom file path provided by the user
202+
elfsym_file_dir = os.path.abspath(args.output_file)
203+
output_dir = os.path.dirname(elfsym_file_dir)
204+
output_abs_path = os.path.abspath(args.output_file)
205+
output_basename = os.path.basename(output_abs_path)
206+
extracted_part = os.path.splitext(output_basename)[0]
207+
else:
208+
# Use the default behavior: generate the file in the parent directory's 'src' folder
209+
parent_dir = os.path.dirname(cur_dir)
210+
output_dir = os.path.join(parent_dir, 'src') # Default directory is 'src' under the parent directory
211+
output_file_name = f'esp_all_symbol.c'
212+
elfsym_file_dir = os.path.join(output_dir, output_file_name)
213+
214+
# Ensure the output directory exists
215+
os.makedirs(output_dir, exist_ok=True)
216+
217+
# Set default excluded symbols and allow user to extend the list
218+
exclude_symbols = ['elf_find_sym', 'g_customer_elfsyms'] + args.exclude
219+
220+
if args.type == 'e':
221+
cmd = ['readelf', '-s', '-W', args.input]
222+
elif args.type == 'l':
223+
cmd = ['nm', '--defined-only', '-g', args.input]
224+
225+
# Execute the readelf or nm command for static libraries
226+
try:
227+
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True)
228+
lines = result.stdout.splitlines()
229+
except subprocess.CalledProcessError as e:
230+
logging.error(f'Error executing command: {e.stderr}')
231+
sys.exit(1)
232+
except FileNotFoundError:
233+
logging.error('nm command not found. Please ensure it is installed and available in your PATH.')
234+
sys.exit(1)
235+
236+
# Extract global symbols from ELF file or static library
237+
symbols = get_global_symbols(lines, type=args.type, undefined=args.undefined, symbol_types=['FUNC', 'OBJECT'])
238+
239+
if not symbols:
240+
logging.warning('No global symbols found in the input file.')
241+
sys.exit(0)
242+
243+
logging.debug('symbols: %s'%(cmd))
244+
logging.debug('symbols: %s'%(symbols))
245+
logging.debug('elfsym_file_dir: %s'%(elfsym_file_dir))
246+
logging.debug('extracted_part: %s'%(extracted_part))
247+
logging.debug('exclude_symbols: %s'%(exclude_symbols))
248+
249+
# Save the C file
250+
try:
251+
save_c_file(symbols, elfsym_file_dir, extracted_part, exclude_symbols=exclude_symbols)
252+
logging.info(f"C file with extern declarations and symbol table has been saved to '{elfsym_file_dir}'.")
253+
except Exception as e:
254+
logging.error(f'Error writing C file: {e}')
255+
sys.exit(1)
256+
257+
def _main():
258+
"""
259+
Wrapper for the main function to catch and handle runtime errors.
260+
"""
261+
try:
262+
main()
263+
except RuntimeError as e:
264+
logging.error(f'A fatal error occurred: {e}')
265+
sys.exit(2)
266+
267+
if __name__ == '__main__':
268+
_main()

0 commit comments

Comments
 (0)