Skip to content

Commit ff997eb

Browse files
authored
Merge pull request #102 from VelocityRa/velocity/import-sym-file
scripts: Add ImportSymFile.py
2 parents 1f97fe9 + 729efad commit ff997eb

File tree

1 file changed

+183
-0
lines changed

1 file changed

+183
-0
lines changed

ghidra_scripts/ImportSymFile.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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

Comments
 (0)