66Author: Grazfather
77"""
88
9+ from typing import Optional
10+
11+ import gdb
12+
13+
914class ARMOpenOCD (ARM ):
1015 arch = "ARMOpenOCD"
1116 aliases = ("ARMOpenOCD" ,)
@@ -18,3 +23,147 @@ def supports_gdb_arch(arch: str) -> Optional[bool]:
1823 if "arm" in arch and arch .endswith ("-m" ):
1924 return True
2025 return None
26+
27+ @staticmethod
28+ def maps ():
29+ yield from GefMemoryManager .parse_info_mem ()
30+
31+
32+ @register
33+ class OpenOCDRemoteCommand (GenericCommand ):
34+ """This command is intended to replace `gef-remote` to connect to an
35+ OpenOCD-hosted gdbserver. It uses a special session manager that knows how
36+ to connect and manage the server."""
37+
38+ _cmdline_ = "gef-openocd-remote"
39+ _syntax_ = f"{ _cmdline_ } [OPTIONS] HOST PORT"
40+ _example_ = [f"{ _cmdline_ } --file /path/to/binary.elf localhost 3333" ,
41+ f"{ _cmdline_ } localhost 3333" ]
42+
43+ def __init__ (self ) -> None :
44+ super ().__init__ (prefix = False )
45+ return
46+
47+ @parse_arguments ({"host" : "" , "port" : "" }, {"--file" : "" })
48+ def do_invoke (self , _ : List [str ], ** kwargs : Any ) -> None :
49+ if gef .session .remote is not None :
50+ err ("You're already in a remote session. Close it first before opening a new one..." )
51+ return
52+
53+ # argument check
54+ args : argparse .Namespace = kwargs ["arguments" ]
55+ if not args .host or not args .port :
56+ err ("Missing parameters" )
57+ return
58+
59+ # Try to establish the remote session, throw on error
60+ # Set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which
61+ # calls `is_remote_debug` which checks if `remote_initializing` is True or `.remote` is None
62+ # This prevents some spurious errors being thrown during startup
63+ gef .session .remote_initializing = True
64+ session = GefOpenOCDRemoteSessionManager (args .host , args .port , args .file )
65+
66+ dbg (f"[remote] initializing remote session with { session .target } under { session .root } " )
67+
68+ # Connect can return false if it wants us to disconnect
69+ if not session .connect ():
70+ gef .session .remote = None
71+ gef .session .remote_initializing = False
72+ return
73+ if not session .setup ():
74+ gef .session .remote = None
75+ gef .session .remote_initializing = False
76+ raise EnvironmentError ("Failed to setup remote target" )
77+
78+ gef .session .remote_initializing = False
79+ gef .session .remote = session
80+ reset_all_caches ()
81+ gdb .execute ("context" )
82+ return
83+
84+
85+ # We CANNOT use the normal session manager because it assumes we have a PID
86+ class GefOpenOCDRemoteSessionManager (GefRemoteSessionManager ):
87+ """This subclass of GefRemoteSessionManager specially handles the
88+ intricacies involved with connecting to an OpenOCD-hosted GDB server.
89+ Specifically, it does not have the concept of PIDs which we need to work
90+ around."""
91+ def __init__ (self , host , port , file : str = "" ) -> None :
92+ self .__host = host
93+ self .__port = port
94+ self .__file = file
95+ self .__local_root_fd = tempfile .TemporaryDirectory ()
96+ self .__local_root_path = pathlib .Path (self .__local_root_fd .name )
97+
98+ def __str__ (self ) -> str :
99+ return f"OpenOCDRemoteSessionManager(='{ self .__tty } ', file='{ self .__file } ', attach={ self .__attach } )"
100+
101+ def close (self ) -> None :
102+ self .__local_root_fd .cleanup ()
103+ try :
104+ gef_on_new_unhook (self .remote_objfile_event_handler )
105+ gef_on_new_hook (new_objfile_handler )
106+ except Exception as e :
107+ warn (f"Exception while restoring local context: { str (e )} " )
108+ return
109+
110+ @property
111+ def target (self ) -> str :
112+ return f"{ self .__host } :{ self .__port } "
113+
114+ @property
115+ def root (self ) -> pathlib .Path :
116+ return self .__local_root_path .absolute ()
117+
118+ def sync (self , src : str , dst : Optional [str ] = None ) -> bool :
119+ # We cannot sync from this target
120+ return None
121+
122+ @property
123+ def file (self ) -> Optional [pathlib .Path ]:
124+ if self .__file :
125+ return pathlib .Path (self .__file ).expanduser ()
126+ return None
127+
128+ def connect (self ) -> bool :
129+ """Connect to remote target. If in extended mode, also attach to the given PID."""
130+ # before anything, register our new hook to download files from the remote target
131+ dbg (f"[remote] Installing new objfile handlers" )
132+ try :
133+ gef_on_new_unhook (new_objfile_handler )
134+ except SystemError :
135+ # the default objfile handler might already have been removed, ignore failure
136+ pass
137+
138+ gef_on_new_hook (self .remote_objfile_event_handler )
139+
140+ # Connect
141+ with DisableContextOutputContext ():
142+ self ._gdb_execute (f"target extended-remote { self .target } " )
143+
144+ try :
145+ with DisableContextOutputContext ():
146+ if self .file :
147+ self ._gdb_execute (f"file { self .file } " )
148+ except Exception as e :
149+ err (f"Failed to connect to { self .target } : { e } " )
150+ # a failure will trigger the cleanup, deleting our hook
151+ return False
152+
153+ return True
154+
155+ def setup (self ) -> bool :
156+ dbg (f"Setting up as remote session" )
157+
158+ # refresh gef to consider the binary
159+ reset_all_caches ()
160+ if self .file :
161+ gef .binary = Elf (self .file )
162+ # We'd like to set this earlier, but we can't because of this bug
163+ # https://sourceware.org/bugzilla/show_bug.cgi?id=31303
164+ reset_architecture ("ARMOpenOCD" )
165+ return True
166+
167+ def _gdb_execute (self , cmd ):
168+ dbg (f"[remote] Executing '{ cmd } '" )
169+ gdb .execute (cmd )
0 commit comments