1+ # Import function names from a .sym file and apply them to the current program.
2+ # @category ghidra-emotionengine
3+ # @runtime PyGhidra
4+
5+ import typing
6+ if typing .TYPE_CHECKING :
7+ from ghidra .ghidra_builtins import *
8+
9+ import re
10+ from ghidra .program .flatapi import FlatProgramAPI
11+ from ghidra .program .model .symbol import SourceType
12+
13+ def parse_sym_line (line ):
14+ """
15+ Parse a line from a .sym file.
16+
17+ Supports two formats:
18+ - Format 1 (with size): "00100008 _start,0210"
19+ - Format 2 (without size): "00100008 _start"
20+
21+ Returns:
22+ tuple: (address_string, function_name) or None if line is invalid
23+ """
24+ line = line .strip ()
25+
26+ # Skip empty lines or comments
27+ if not line or line .startswith ('#' ) or line .startswith ('//' ):
28+ return None
29+
30+ # Split on whitespace
31+ parts = line .split (None , 1 ) # Split on first whitespace only
32+ if len (parts ) != 2 :
33+ return None
34+
35+ address_str , name_part = parts
36+
37+ # Validate address format (hex string)
38+ if not re .match (r'^[0-9A-Fa-f]+$' , address_str ):
39+ return None
40+
41+ # Extract function name (remove size if present)
42+ # Format: "function_name,size" or just "function_name"
43+ if ',' in name_part :
44+ function_name = name_part .split (',' )[0 ]
45+ else :
46+ function_name = name_part
47+
48+ # Clean up function name
49+ function_name = function_name .strip ()
50+
51+ if not function_name :
52+ return None
53+
54+ return (address_str , function_name )
55+
56+ def import_sym_file ():
57+ """Main function to import .sym file and apply function names."""
58+
59+ # Validate that a program is loaded
60+ if currentProgram is None :
61+ printerr ("No program is currently loaded" )
62+ return
63+
64+ # Use askFile to get the .sym file from the user
65+ # This method is provided by GhidraScript and works in both GUI and headless modes
66+ try :
67+ selected_file = askFile ("Select .sym File to Import" , "Import" )
68+ except :
69+ # User cancelled the dialog
70+ println ("No file selected, import cancelled" )
71+ return
72+
73+ if selected_file is None :
74+ println ("No file selected, import cancelled" )
75+ return
76+
77+ println (f"Importing symbols from: { selected_file .absolutePath } " )
78+ println ("-" * 60 )
79+
80+ # Initialize API and counters
81+ flat_api = FlatProgramAPI (currentProgram )
82+ function_manager = currentProgram .functionManager
83+ address_factory = currentProgram .addressFactory
84+
85+ total_lines = 0
86+ functions_created = 0
87+ functions_renamed = 0
88+ errors = 0
89+ skipped = 0
90+
91+ try :
92+ # Read and process the .sym file
93+ with open (selected_file .absolutePath , 'r' , encoding = 'utf-8' ) as f :
94+ for line_num , line in enumerate (f , 1 ):
95+ total_lines += 1
96+
97+ # Parse the line
98+ parsed = parse_sym_line (line )
99+ if parsed is None :
100+ skipped += 1
101+ continue
102+
103+ address_str , function_name = parsed
104+
105+ try :
106+ # Convert address string to Ghidra Address object
107+ # Prepend "0x" if not present for proper parsing
108+ if not address_str .startswith ('0x' ):
109+ address_str = '0x' + address_str
110+
111+ address = address_factory .getAddress (address_str )
112+
113+ if address is None :
114+ printerr (f"Line { line_num } : Invalid address '{ address_str } '" )
115+ errors += 1
116+ continue
117+
118+ # Check if address is valid in program memory
119+ if not currentProgram .memory .contains (address ):
120+ printerr (f"Line { line_num } : Address { address } is not in program memory" )
121+ errors += 1
122+ continue
123+
124+ # Check if function exists at this address
125+ function = function_manager .getFunctionAt (address )
126+
127+ # If no function exists, create one
128+ if function is None :
129+ try :
130+ function = flat_api .createFunction (address , function_name )
131+ if function is not None :
132+ functions_created += 1
133+ println (f"Created function '{ function_name } ' at { address } " )
134+ else :
135+ printerr (f"Line { line_num } : Failed to create function at { address } " )
136+ errors += 1
137+ continue
138+ except Exception as e :
139+ printerr (f"Line { line_num } : Error creating function at { address } : { str (e )} " )
140+ errors += 1
141+ continue
142+
143+ # Apply the function name
144+ try :
145+ old_name = function .name
146+ function .setName (function_name , SourceType .USER_DEFINED )
147+
148+ if old_name != function_name :
149+ functions_renamed += 1
150+ println (f"Renamed function at { address } : '{ old_name } ' -> '{ function_name } '" )
151+
152+ except Exception as e :
153+ printerr (f"Line { line_num } : Error setting function name at { address } : { str (e )} " )
154+ errors += 1
155+ continue
156+
157+ except Exception as e :
158+ printerr (f"Line { line_num } : Unexpected error processing '{ line .strip ()} ': { str (e )} " )
159+ errors += 1
160+ continue
161+
162+ # Print summary
163+ println ("-" * 60 )
164+ println ("Import Summary:" )
165+ println (f" Total lines processed: { total_lines } " )
166+ println (f" Functions created: { functions_created } " )
167+ println (f" Functions renamed: { functions_renamed } " )
168+ println (f" Lines skipped (empty/comments): { skipped } " )
169+ println (f" Errors: { errors } " )
170+ println ("-" * 60 )
171+
172+ if errors == 0 :
173+ println ("Import completed successfully!" )
174+ else :
175+ println (f"Import completed with { errors } error(s). Check output for details." )
176+
177+ except Exception as e :
178+ printerr (f"Fatal error reading file: { str (e )} " )
179+ import traceback
180+ printerr (traceback .format_exc ())
181+
182+ # Run the import
183+ import_sym_file ()
0 commit comments