1- import gdb
21from elftools .elf .elffile import ELFFile
32from elftools .elf .constants import SH_FLAGS
43from itertools import pairwise
4+ from pathlib import Path
5+ import argparse
56import struct
7+ import sys
8+ import os
9+ import subprocess
10+ import tempfile
611
712NT_LMA = 0x00414D4C
813
9- class NoMMULoad (gdb .Command ):
10- """Auto-load core file and offset symbols on nommu target."""
11-
12- def __init__ (self ):
13- super (NoMMULoad , self ).__init__ ("nommu-load" , gdb .COMMAND_USER )
14- self .target_arch = None
15-
16-
17- def parse_core_file (self , filename ):
18- with open (filename , 'rb' ) as f :
19- elffile = ELFFile (f )
20- self .target_arch = elffile .get_machine_arch ()
21- for segment in elffile .iter_segments ():
22- if segment ['p_type' ] != 'PT_NOTE' :
23- continue
24- for note in segment .iter_notes ():
25- if note ['n_type' ] != NT_LMA :
26- continue
27- values = struct .unpack (f'<{ len (note ["n_desc" ]) // 4 } I' , note ['n_desc' ])
28- res = {}
29- for lma , vma in pairwise (values ):
30- res [vma ] = lma
31- return res
32- return {}
33-
34- def parse_symbol_file (self , filename ):
35- section_to_segment_mapping = {}
36-
37- with open (filename , 'rb' ) as f :
38- elffile = ELFFile (f )
39- for section in elffile .iter_sections ():
40- if not (section ['sh_flags' ] & SH_FLAGS .SHF_ALLOC ) or section ['sh_size' ] == 0 :
41- continue
42-
43- sec_start_addr = section ['sh_addr' ]
44- sec_end_addr = sec_start_addr + section ['sh_size' ]
45-
46- for segment in elffile .iter_segments ():
47- if segment ['p_type' ] != 'PT_LOAD' :
48- continue
49- seg_start_addr = segment ['p_vaddr' ]
50- seg_end_addr = seg_start_addr + segment ['p_memsz' ]
51-
52- if sec_start_addr >= seg_start_addr and sec_end_addr <= seg_end_addr :
53- section_to_segment_mapping [section .name ] = {
54- 'seg_addr' : seg_start_addr ,
55- 'offset' : section ['sh_addr' ] - seg_start_addr ,
56- }
57- break
58-
59- return section_to_segment_mapping
60-
61- def create_mapping_args (self , core_file , symbol_file ):
62- core_segment_mapping = self .parse_core_file (core_file )
63- elf_section_mapping = self .parse_symbol_file (symbol_file )
64- args = []
65- for section_name , section_info in elf_section_mapping .items ():
66- if section_info ['seg_addr' ] not in core_segment_mapping :
67- print (f"Warning: Segment containing '{ section_name } ' not found in core NT_FILE mapping." )
68- if section_name == '.text' :
69- print ("Warning: No valid segment mapping found for '.text' section!" )
70- return []
14+ COREDUMP_PARSER_PATH = (Path (__file__ ).parent .parent / "prog" / "coredump_parser" ).absolute ()
15+ COREDUMP_PARSER_OUTPUT = Path (tempfile .gettempdir ()) / "phoenix-coredumps"
16+
17+ os .makedirs (COREDUMP_PARSER_OUTPUT , exist_ok = True )
18+
19+
20+ def has_lma_note (elffile ):
21+ for segment in elffile .iter_segments ():
22+ if segment ['p_type' ] != 'PT_NOTE' :
23+ continue
24+ for note in segment .iter_notes ():
25+ if note ['n_type' ] == NT_LMA :
26+ return True
27+ return False
28+
29+ def parse_core_file (elffile ):
30+ for segment in elffile .iter_segments ():
31+ if segment ['p_type' ] != 'PT_NOTE' :
32+ continue
33+ for note in segment .iter_notes ():
34+ if note ['n_type' ] != NT_LMA :
35+ continue
36+ values = struct .unpack (f'<{ len (note ["n_desc" ]) // 4 } I' , note ['n_desc' ])
37+ res = {}
38+ for lma , vma in pairwise (values ):
39+ res [vma ] = lma
40+ return res
41+ return {}
42+
43+ def parse_symbol_file (elffile ):
44+ section_to_segment_mapping = {}
45+
46+ for section in elffile .iter_sections ():
47+ if not (section ['sh_flags' ] & SH_FLAGS .SHF_ALLOC ) or section ['sh_size' ] == 0 :
48+ continue
49+
50+ sec_start_addr = section ['sh_addr' ]
51+ sec_end_addr = sec_start_addr + section ['sh_size' ]
52+
53+ for segment in elffile .iter_segments ():
54+ if segment ['p_type' ] != 'PT_LOAD' :
7155 continue
72- new_addr = core_segment_mapping [section_info ['seg_addr' ]] + section_info ['offset' ]
73- args .append (f"-s { section_name } { new_addr :#x} " )
74- if len (args ) == 0 :
75- print ("Error: No valid segment mappings found." )
76- return args
77-
78- def invoke (self , arg , from_tty ):
79- args = gdb .string_to_argv (arg )
80-
81- if len (args ) < 2 :
82- print ("Usage: nommu-load <symbol-file> <core-file>" )
83- return
84-
85- symbol_file = args [0 ]
86- core_file = args [1 ]
87-
88- mapping_args = self .create_mapping_args (core_file , symbol_file )
89- try :
90- gdb .execute (f"add-symbol-file { symbol_file } { ' ' .join (mapping_args )} " )
91- gdb .execute (f"exec-file { symbol_file } { ' ' .join (mapping_args )} " )
92- except gdb .error as e :
93- print (f"Failed to load symbols: { e } " )
94-
95- try :
96- gdb .execute (f"target core { core_file } " )
97- except gdb .error as e :
98- print (f"Failed to load core file: { e } " )
99- return
100-
101- NoMMULoad ()
56+ seg_start_addr = segment ['p_vaddr' ]
57+ seg_end_addr = seg_start_addr + segment ['p_memsz' ]
58+
59+ if sec_start_addr >= seg_start_addr and sec_end_addr <= seg_end_addr :
60+ section_to_segment_mapping [section .name ] = {
61+ 'seg_addr' : seg_start_addr ,
62+ 'offset' : section ['sh_addr' ] - seg_start_addr ,
63+ }
64+ break
65+
66+ return section_to_segment_mapping
67+
68+ def create_mapping_args (core_file , symbol_file ):
69+ core_segment_mapping = parse_core_file (core_file )
70+ elf_section_mapping = parse_symbol_file (symbol_file )
71+ args = []
72+ for section_name , section_info in elf_section_mapping .items ():
73+ if section_info ['seg_addr' ] not in core_segment_mapping :
74+ print (f"Warning: Segment containing '{ section_name } ' not found in core NT_FILE mapping." , file = sys .stderr )
75+ if section_name == '.text' :
76+ print ("Warning: No valid segment mapping found for '.text' section!" , file = sys .stderr )
77+ return []
78+ continue
79+ new_addr = core_segment_mapping [section_info ['seg_addr' ]] + section_info ['offset' ]
80+ args .append (f"-s { section_name } { new_addr :#x} " )
81+ if len (args ) == 0 :
82+ print ("Error: No valid segment mappings found." , file = sys .stderr )
83+ return args
84+
85+ def run_parser (coredump = None , symbolfile = None ):
86+ arglist = [COREDUMP_PARSER_OUTPUT ]
87+ if symbolfile : arglist .append (Path (symbolfile ).name )
88+ try :
89+ if coredump :
90+ with open (coredump , "rb" ) as f :
91+ result = subprocess .run ([COREDUMP_PARSER_PATH , * arglist ], check = True , stdin = f , stdout = subprocess .PIPE )
92+ return result .stdout .decode ('utf-8' ).strip ()
93+ else :
94+ result = subprocess .run ([COREDUMP_PARSER_PATH , * arglist ], check = True , stdout = subprocess .PIPE )
95+ return result .stdout .decode ('utf-8' ).strip ()
96+ except subprocess .CalledProcessError as e :
97+ print (f"Running coredump_parser failed with exit status: { e .returncode } " , file = sys .stderr )
98+ return []
99+
100+ def generate_gdb_commands (args ):
101+ """
102+ If symbols is None, only corefile will be loaded
103+ If core is None, corefile will be loaded from stdin
104+ """
105+ coreelf = None
106+ symbolelf = None
107+ corefile = None
108+ symbolfile = None
109+
110+ try :
111+ corefile = open (args .core , 'rb' )
112+ coreelf = ELFFile (corefile )
113+ except Exception as e :
114+ args .core = run_parser (args .core , args .symbol )
115+ if args .core :
116+ print (f"Using core file '{ args .core } ' with symbol file '{ args .symbol } '." , file = sys .stderr )
117+ corefile = open (args .core , 'rb' )
118+ coreelf = ELFFile (corefile )
119+
120+ if args .symbol :
121+ symbolfile = open (args .symbol , 'rb' )
122+ symbolelf = ELFFile (symbolfile )
123+
124+ if not coreelf :
125+ print ("No core file found." , file = sys .stderr )
126+ if corefile : corefile .close ()
127+ if symbolfile : symbolfile .close ()
128+ return []
129+
130+ commands = []
131+ if has_lma_note (coreelf ):
132+ if not symbolelf :
133+ print ("Warning: NOMMU coreelf detected, but no symbol file found. Symbols won't be resolved properly when loading symbols separately!" , file = sys .stderr )
134+ else :
135+ mapping_args = create_mapping_args (coreelf , symbolelf )
136+ commands .append (f"symbol-file" ) # clear previous
137+ commands .append (f"exec-file" ) # clear previous
138+ commands .append (f"add-symbol-file { args .symbol } { ' ' .join (mapping_args )} " )
139+ commands .append (f"exec-file { args .symbol } { ' ' .join (mapping_args )} " )
140+ elif symbolelf :
141+ commands .append (f"symbol-file" ) # clear previous
142+ commands .append (f"exec-file" ) # clear previous
143+ commands .append (f"add-symbol-file { args .symbol } " )
144+ commands .append (f"exec-file { args .symbol } " )
145+
146+ commands .append (f"core-file { args .core } " )
147+ if corefile : corefile .close ()
148+ if symbolfile : symbolfile .close ()
149+ return commands
150+
151+ def parse_args (args ):
152+ parser = argparse .ArgumentParser (add_help = True )
153+ parser .add_argument ("-c" , "--core" , help = "Path to the core file or to text containg dump (by default stdin will be used)" )
154+ parser .add_argument ("-s" , "--symbol" , "--sym" , help = "Path to the symbol file (by default in gdb currently loaded symbol file will be used)" )
155+ try :
156+ args = parser .parse_args (args )
157+ except SystemExit :
158+ return {"help" : True }
159+ return args
160+
161+ is_in_gdb = True
162+ try :
163+ import gdb
164+ except ImportError :
165+ is_in_gdb = False
166+
167+ if is_in_gdb :
168+ class PhoenixCoreCommand (gdb .Command ):
169+ """
170+ Auto-load Phoenix core file and offset symbols on NOMMU targets.
171+ Use `phoenix-load --help` for more information.
172+ """
173+
174+ def __init__ (self ):
175+ super (PhoenixCoreCommand , self ).__init__ ("phoenix-core" , gdb .COMMAND_USER )
176+ self .target_arch = None
177+
178+ def invoke (self , arg , from_tty ):
179+ args = parse_args (gdb .string_to_argv (arg ))
180+ if "help" in args :
181+ return
182+ if not args .symbol :
183+ try :
184+ if gdb .objfiles ():
185+ args .symbol = gdb .objfiles ()[0 ].filename
186+ except gdb .error as e :
187+ print (f"Phoenix-core: Note: Could not determine current GDB symbol file:" , e , file = sys .stderr )
188+ commands = generate_gdb_commands (args )
189+ for command in commands :
190+ gdb .execute (command )
191+ def complete (self , text , word ):
192+ options = ["-c" , "--core" , "-s" , "--symbol" , "--sym" , "-h" , "--help" ]
193+ if word and word .startswith ("-" ):
194+ return [opt for opt in options if opt .startswith (word )]
195+ return gdb .COMPLETE_FILENAME
196+
197+
198+ PhoenixCoreCommand ()
199+
200+ elif __name__ == "__main__" :
201+ args = parse_args (sys .argv [1 :])
202+ if "help" in args :
203+ exit (0 )
204+ commands = generate_gdb_commands (args )
205+ if commands :
206+ print ("\n \n # Generated GDB commands:\n " )
207+ for cmd in commands :
208+ print (cmd )
209+ print ()
210+ else :
211+ print ("No commands generated." , file = sys .stderr )
0 commit comments