diff options
Diffstat (limited to 'gdb/python')
35 files changed, 630 insertions, 331 deletions
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py index 4ea5d06..cedd897 100644 --- a/gdb/python/lib/gdb/__init__.py +++ b/gdb/python/lib/gdb/__init__.py @@ -19,17 +19,22 @@ import sys import threading import traceback from contextlib import contextmanager +from importlib import reload -# Python 3 moved "reload" -if sys.version_info >= (3, 4): - from importlib import reload -else: - from imp import reload - -import _gdb - +# The star import imports _gdb names. When the names are used locally, they +# trigger F405 warnings unless added to the explicit import list. # Note that two indicators are needed here to silence flake8. from _gdb import * # noqa: F401,F403 +from _gdb import ( + STDERR, + STDOUT, + Command, + execute, + flush, + parameter, + selected_inferior, + write, +) # isort: split @@ -60,14 +65,14 @@ class _GdbFile(object): self.write(line) def flush(self): - _gdb.flush(stream=self.stream) + flush(stream=self.stream) def write(self, s): - _gdb.write(s, stream=self.stream) + write(s, stream=self.stream) -sys.stdout = _GdbFile(_gdb.STDOUT) -sys.stderr = _GdbFile(_gdb.STDERR) +sys.stdout = _GdbFile(STDOUT) +sys.stderr = _GdbFile(STDERR) # Default prompt hook does nothing. prompt_hook = None @@ -189,7 +194,7 @@ def GdbSetPythonDirectory(dir): def current_progspace(): "Return the current Progspace." - return _gdb.selected_inferior().progspace + return selected_inferior().progspace def objfiles(): @@ -226,14 +231,14 @@ def set_parameter(name, value): value = "on" else: value = "off" - _gdb.execute("set " + name + " " + str(value), to_string=True) + execute("set " + name + " " + str(value), to_string=True) @contextmanager def with_parameter(name, value): """Temporarily set the GDB parameter NAME to VALUE. Note that this is a context manager.""" - old_value = _gdb.parameter(name) + old_value = parameter(name) set_parameter(name, value) try: # Nothing that useful to return. @@ -392,3 +397,121 @@ def _handle_missing_objfile(pspace, buildid, filename): return _handle_missing_files( pspace, "objfile", lambda h: h(pspace, buildid, filename) ) + + +class ParameterPrefix: + # A wrapper around gdb.Command for creating set/show prefixes. + # + # When creating a gdb.Parameter sub-classes, it is sometimes necessary + # to first create a gdb.Command object in order to create the needed + # command prefix. However, for parameters, we actually need two + # prefixes, a 'set' prefix, and a 'show' prefix. With this helper + # class, a single instance of this class will create both prefixes at + # once. + # + # It is important that this class-level documentation not be a __doc__ + # string. Users are expected to sub-class this ParameterPrefix class + # and add their own documentation. If they don't, then GDB will + # generate a suitable doc string. But, if this (parent) class has a + # __doc__ string of its own, then sub-classes will inherit that __doc__ + # string, and GDB will not understand that it needs to generate one. + + class _PrefixCommand(Command): + """A gdb.Command used to implement both the set and show prefixes. + + This documentation string is not used as the prefix command + documentation as it is overridden in the __init__ method below.""" + + # This private method is connected to the 'invoke' attribute within + # this _PrefixCommand object if the containing ParameterPrefix + # object has an invoke_set or invoke_show method. + # + # This method records within self.__delegate which _PrefixCommand + # object is currently active, and then calls the correct invoke + # method on the delegat object (the ParameterPrefix sub-class + # object). + # + # Recording the currently active _PrefixCommand object is important; + # if from the invoke method the user calls dont_repeat, then this is + # forwarded to the currently active _PrefixCommand object. + def __invoke(self, args, from_tty): + + # A helper class for use as part of a Python 'with' block. + # Records which gdb.Command object is currently running its + # invoke method. + class MarkActiveCallback: + # The CMD is a _PrefixCommand object, and the DELEGATE is + # the ParameterPrefix class, or sub-class object. At this + # point we simple record both of these within the + # MarkActiveCallback object. + def __init__(self, cmd, delegate): + self.__cmd = cmd + self.__delegate = delegate + + # Record the currently active _PrefixCommand object within + # the outer ParameterPrefix sub-class object. + def __enter__(self): + self.__delegate.active_prefix = self.__cmd + + # Once the invoke method has completed, then clear the + # _PrefixCommand object that was stored into the outer + # ParameterPrefix sub-class object. + def __exit__(self, exception_type, exception_value, traceback): + self.__delegate.active_prefix = None + + # The self.__cb attribute is set when the _PrefixCommand object + # is created, and is either invoke_set or invoke_show within the + # ParameterPrefix sub-class object. + assert callable(self.__cb) + + # Record the currently active _PrefixCommand object within the + # ParameterPrefix sub-class object, then call the relevant + # invoke method within the ParameterPrefix sub-class object. + with MarkActiveCallback(self, self.__delegate): + self.__cb(args, from_tty) + + @staticmethod + def __find_callback(delegate, mode): + """The MODE is either 'set' or 'show'. Look for an invoke_MODE method + on DELEGATE, if a suitable method is found, then return it, otherwise, + return None. + """ + cb = getattr(delegate, "invoke_" + mode, None) + if callable(cb): + return cb + return None + + def __init__(self, mode, name, cmd_class, delegate, doc=None): + """Setup this gdb.Command. Mode is a string, either 'set' or 'show'. + NAME is the name for this prefix command, that is, the + words that appear after both 'set' and 'show' in the + command name. CMD_CLASS is the usual enum. And DELEGATE + is the gdb.ParameterPrefix object this prefix is part of. + """ + assert mode == "set" or mode == "show" + if doc is None: + self.__doc__ = delegate.__doc__ + else: + self.__doc__ = doc + self.__cb = self.__find_callback(delegate, mode) + self.__delegate = delegate + if self.__cb is not None: + self.invoke = self.__invoke + super().__init__(mode + " " + name, cmd_class, prefix=True) + + def __init__(self, name, cmd_class, doc=None): + """Create a _PrefixCommand for both the set and show prefix commands. + NAME is the command name without either the leading 'set ' or + 'show ' strings, and CMD_CLASS is the usual enum value. + """ + self.active_prefix = None + self._set_prefix_cmd = self._PrefixCommand("set", name, cmd_class, self, doc) + self._show_prefix_cmd = self._PrefixCommand("show", name, cmd_class, self, doc) + + # When called from within an invoke method the self.active_prefix + # attribute should be set to a gdb.Command sub-class (a _PrefixCommand + # object, see above). Forward the dont_repeat call to this object to + # register the actual command as none repeating. + def dont_repeat(self): + if self.active_prefix is not None: + self.active_prefix.dont_repeat() diff --git a/gdb/python/lib/gdb/dap/breakpoint.py b/gdb/python/lib/gdb/dap/breakpoint.py index 59da120..3162911 100644 --- a/gdb/python/lib/gdb/dap/breakpoint.py +++ b/gdb/python/lib/gdb/dap/breakpoint.py @@ -326,7 +326,7 @@ def _rewrite_fn_breakpoint( } -@request("setFunctionBreakpoints") +@request("setFunctionBreakpoints", expect_stopped=False) @capability("supportsFunctionBreakpoints") def set_fn_breakpoint(*, breakpoints: Sequence, **args): specs = [_rewrite_fn_breakpoint(**bp) for bp in breakpoints] @@ -359,7 +359,7 @@ def _rewrite_insn_breakpoint( } -@request("setInstructionBreakpoints") +@request("setInstructionBreakpoints", expect_stopped=False) @capability("supportsInstructionBreakpoints") def set_insn_breakpoints( *, breakpoints: Sequence, offset: Optional[int] = None, **args @@ -373,10 +373,12 @@ def set_insn_breakpoints( @in_gdb_thread def _catch_exception(filterId, **args): if filterId in ("assert", "exception", "throw", "rethrow", "catch"): - cmd = "-catch-" + filterId + cmd = ["-catch-" + filterId] else: raise DAPException("Invalid exception filterID: " + str(filterId)) - result = exec_mi_and_log(cmd) + if "exception" in args and args["exception"] is not None: + cmd += ["-e", args["exception"]] + result = exec_mi_and_log(*cmd) # While the Ada catchpoints emit a "bkptno" field here, the C++ # ones do not. So, instead we look at the "number" field. num = result["bkpt"]["number"] @@ -404,13 +406,20 @@ def _rewrite_exception_breakpoint( # Note that exception breakpoints do not support a hit count. **args, ): + if filterId == "exception": + # Treat Ada exceptions specially -- in particular the + # condition is just an exception name, not an expression. + return { + "filterId": filterId, + "exception": condition, + } return { "filterId": filterId, "condition": condition, } -@request("setExceptionBreakpoints") +@request("setExceptionBreakpoints", expect_stopped=False) @capability("supportsExceptionFilterOptions") @capability( "exceptionBreakpointFilters", diff --git a/gdb/python/lib/gdb/dap/completions.py b/gdb/python/lib/gdb/dap/completions.py index 85acc43..e5003ff 100644 --- a/gdb/python/lib/gdb/dap/completions.py +++ b/gdb/python/lib/gdb/dap/completions.py @@ -39,8 +39,11 @@ def completions( line = 1 else: line = import_line(line) - text = text.splitlines()[line - 1] - text = text[: column - 1] + if text: + text = text.splitlines()[line - 1] + text = text[: column - 1] + else: + text = "" mi_result = exec_mi_and_log("-complete", text) result = [] completion = None diff --git a/gdb/python/lib/gdb/dap/evaluate.py b/gdb/python/lib/gdb/dap/evaluate.py index 55538e6..fcbcc99 100644 --- a/gdb/python/lib/gdb/dap/evaluate.py +++ b/gdb/python/lib/gdb/dap/evaluate.py @@ -69,7 +69,7 @@ def _repl(command, frame_id): } -@request("evaluate") +@request("evaluate", defer_events=False) @capability("supportsEvaluateForHovers") @capability("supportsValueFormattingOptions") def eval_request( @@ -110,7 +110,7 @@ def variables( @capability("supportsSetExpression") -@request("setExpression") +@request("setExpression", defer_events=False) def set_expression( *, expression: str, value: str, frameId: Optional[int] = None, format=None, **args ): @@ -126,7 +126,7 @@ def set_expression( @capability("supportsSetVariable") -@request("setVariable") +@request("setVariable", defer_events=False) def set_variable( *, variablesReference: int, name: str, value: str, format=None, **args ): diff --git a/gdb/python/lib/gdb/dap/events.py b/gdb/python/lib/gdb/dap/events.py index 4dc9d65..778acc5 100644 --- a/gdb/python/lib/gdb/dap/events.py +++ b/gdb/python/lib/gdb/dap/events.py @@ -17,12 +17,12 @@ import gdb from .modules import is_module, make_module from .scopes import set_finish_value -from .server import send_event, send_event_maybe_later +from .server import send_event from .startup import exec_and_log, in_gdb_thread, log # True when the inferior is thought to be running, False otherwise. # This may be accessed from any thread, which can be racy. However, -# this unimportant because this global is only used for the +# this is unimportant because this global is only used for the # 'notStopped' response, which itself is inherently racy. inferior_running = False @@ -161,7 +161,7 @@ _expected_pause = False @in_gdb_thread -def exec_and_expect_stop(cmd, expected_pause=False, propagate_exception=False): +def exec_and_expect_stop(cmd, expected_pause=False): """A wrapper for exec_and_log that sets the continue-suppression flag. When EXPECTED_PAUSE is True, a stop that looks like a pause (e.g., @@ -174,7 +174,7 @@ def exec_and_expect_stop(cmd, expected_pause=False, propagate_exception=False): # continuing. _suppress_cont = not expected_pause # FIXME if the call fails should we clear _suppress_cont? - exec_and_log(cmd, propagate_exception) + exec_and_log(cmd) # Map from gdb stop reasons to DAP stop reasons. Some of these can't @@ -240,7 +240,7 @@ def _on_stop(event): else: obj["reason"] = stop_reason_map[event.details["reason"]] _expected_pause = False - send_event_maybe_later("stopped", obj) + send_event("stopped", obj) # This keeps a bit of state between the start of an inferior call and diff --git a/gdb/python/lib/gdb/dap/next.py b/gdb/python/lib/gdb/dap/next.py index ddf4e05..993e9d2 100644 --- a/gdb/python/lib/gdb/dap/next.py +++ b/gdb/python/lib/gdb/dap/next.py @@ -16,8 +16,8 @@ import gdb from .events import exec_and_expect_stop -from .server import capability, request, send_gdb, send_gdb_with_response -from .startup import in_gdb_thread +from .server import capability, request +from .startup import DAPException, exec_and_log, in_gdb_thread from .state import set_thread @@ -36,11 +36,9 @@ def _handle_thread_step(thread_id, single_thread, select=False): result = False arg = "off" try: - # This can fail, depending on the target, so catch the error - # and report to our caller. We can't use exec_and_log because - # that does not propagate exceptions. - gdb.execute("set scheduler-locking " + arg, from_tty=True, to_string=True) - except gdb.error: + # This can fail, depending on the target, so catch any error. + exec_and_log("set scheduler-locking " + arg) + except DAPException: result = False # Other DAP code may select a frame, and the "finish" command uses # the selected frame. @@ -73,19 +71,14 @@ def step_in( exec_and_expect_stop(cmd) -@request("stepOut", defer_stop_events=True) +@request("stepOut") def step_out(*, threadId: int, singleThread: bool = False, **args): _handle_thread_step(threadId, singleThread, True) - exec_and_expect_stop("finish &", propagate_exception=True) + exec_and_expect_stop("finish &") -# This is a server-side request because it is funny: it wants to -# 'continue' but also return a result, which precludes using -# response=False. Using 'continue &' would mostly work ok, but this -# yields races when a stop occurs before the response is sent back to -# the client. -@request("continue", on_dap_thread=True) +@request("continue") def continue_request(*, threadId: int, singleThread: bool = False, **args): - locked = send_gdb_with_response(lambda: _handle_thread_step(threadId, singleThread)) - send_gdb(lambda: exec_and_expect_stop("continue")) + locked = _handle_thread_step(threadId, singleThread) + exec_and_expect_stop("continue &") return {"allThreadsContinued": not locked} diff --git a/gdb/python/lib/gdb/dap/server.py b/gdb/python/lib/gdb/dap/server.py index c4fa781..98a8084 100644 --- a/gdb/python/lib/gdb/dap/server.py +++ b/gdb/python/lib/gdb/dap/server.py @@ -74,6 +74,13 @@ class DeferredRequest: self._result = result @in_dap_thread + def defer_events(self): + """Return True if events should be deferred during execution. + + This may be overridden by subclasses.""" + return True + + @in_dap_thread def invoke(self): """Implement the deferred request. @@ -95,7 +102,10 @@ class DeferredRequest: """ with _server.canceller.current_request(self._req): + if self.defer_events(): + _server.set_defer_events() _server.invoke_request(self._req, self._result, self.invoke) + _server.emit_pending_events() # A subclass of Exception that is used solely for reporting that a @@ -230,7 +240,7 @@ class Server: self._out_stream = out_stream self._child_stream = child_stream self._delayed_fns_lock = threading.Lock() - self.defer_stop_events = False + self._defer_events = False self._delayed_fns = [] # This queue accepts JSON objects that are then sent to the # DAP client. Writing is done in a separate thread to avoid @@ -316,7 +326,7 @@ class Server: def _read_inferior_output(self): while True: line = self._child_stream.readline() - self.send_event( + self.send_event_maybe_later( "output", { "category": "stdout", @@ -356,6 +366,17 @@ class Server: self._read_queue.put(None) @in_dap_thread + def emit_pending_events(self): + """Emit any pending events.""" + fns = None + with self._delayed_fns_lock: + fns = self._delayed_fns + self._delayed_fns = [] + self._defer_events = False + for fn in fns: + fn() + + @in_dap_thread def main_loop(self): """The main loop of the DAP server.""" # Before looping, start the thread that writes JSON to the @@ -371,28 +392,22 @@ class Server: req = cmd["seq"] with self.canceller.current_request(req): self._handle_command(cmd) - fns = None - with self._delayed_fns_lock: - fns = self._delayed_fns - self._delayed_fns = [] - self.defer_stop_events = False - for fn in fns: - fn() + self.emit_pending_events() # Got the terminate request. This is handled by the # JSON-writing thread, so that we can ensure that all # responses are flushed to the client before exiting. self._write_queue.put(None) json_writer.join() - send_gdb("quit") + send_gdb(lambda: exec_and_log("quit")) @in_dap_thread - def send_event_later(self, event, body=None): - """Send a DAP event back to the client, but only after the - current request has completed.""" + def set_defer_events(self): + """Defer any events until the current request has completed.""" with self._delayed_fns_lock: - self._delayed_fns.append(lambda: self.send_event(event, body)) + self._defer_events = True - @in_gdb_thread + # Note that this does not need to be run in any particular thread, + # because it uses locks for thread-safety. def send_event_maybe_later(self, event, body=None): """Send a DAP event back to the client, but if a request is in-flight within the dap thread and that request is configured to delay the event, @@ -401,10 +416,10 @@ class Server: with self.canceller.lock: if self.canceller.in_flight_dap_thread: with self._delayed_fns_lock: - if self.defer_stop_events: - self._delayed_fns.append(lambda: self.send_event(event, body)) + if self._defer_events: + self._delayed_fns.append(lambda: self._send_event(event, body)) return - self.send_event(event, body) + self._send_event(event, body) @in_dap_thread def call_function_later(self, fn): @@ -415,7 +430,7 @@ class Server: # Note that this does not need to be run in any particular thread, # because it just creates an object and writes it to a thread-safe # queue. - def send_event(self, event, body=None): + def _send_event(self, event, body=None): """Send an event to the DAP client. EVENT is the name of the event, a string. BODY is the body of the event, an arbitrary object.""" @@ -439,14 +454,6 @@ def send_event(event, body=None): """Send an event to the DAP client. EVENT is the name of the event, a string. BODY is the body of the event, an arbitrary object.""" - _server.send_event(event, body) - - -def send_event_maybe_later(event, body=None): - """Send a DAP event back to the client, but if a request is in-flight - within the dap thread and that request is configured to delay the event, - wait until the response has been sent until the event is sent back to - the client.""" _server.send_event_maybe_later(event, body) @@ -476,7 +483,7 @@ def request( response: bool = True, on_dap_thread: bool = False, expect_stopped: bool = True, - defer_stop_events: bool = False + defer_events: bool = True ): """A decorator for DAP requests. @@ -498,9 +505,9 @@ def request( inferior is running. When EXPECT_STOPPED is False, the request will proceed regardless of the inferior's state. - If DEFER_STOP_EVENTS is True, then make sure any stop events sent - during the request processing are not sent to the client until the - response has been sent. + If DEFER_EVENTS is True, then make sure any events sent during the + request processing are not sent to the client until the response + has been sent. """ # Validate the parameters. @@ -523,26 +530,33 @@ def request( # Verify that the function is run on the correct thread. if on_dap_thread: - cmd = in_dap_thread(func) + check_cmd = in_dap_thread(func) else: func = in_gdb_thread(func) if response: - if defer_stop_events: - if _server is not None: - with _server.delayed_events_lock: - _server.defer_stop_events = True def sync_call(**args): return send_gdb_with_response(lambda: func(**args)) - cmd = sync_call + check_cmd = sync_call else: def non_sync_call(**args): return send_gdb(lambda: func(**args)) - cmd = non_sync_call + check_cmd = non_sync_call + + if defer_events: + + def deferring(**args): + _server.set_defer_events() + return check_cmd(**args) + + cmd = deferring + + else: + cmd = check_cmd # If needed, check that the inferior is not running. This # wrapping is done last, so the check is done first, before @@ -582,7 +596,7 @@ def client_bool_capability(name, default=False): @request("initialize", on_dap_thread=True) def initialize(**args): _server.config = args - _server.send_event_later("initialized") + _server.send_event_maybe_later("initialized") global _lines_start_at_1 _lines_start_at_1 = client_bool_capability("linesStartAt1", True) global _columns_start_at_1 @@ -600,7 +614,7 @@ def terminate(**args): @capability("supportTerminateDebuggee") def disconnect(*, terminateDebuggee: bool = False, **args): if terminateDebuggee: - send_gdb_with_response("kill") + send_gdb_with_response(lambda: exec_and_log("kill")) _server.shutdown() @@ -619,18 +633,6 @@ def cancel(**args): return None -class Invoker(object): - """A simple class that can invoke a gdb command.""" - - def __init__(self, cmd): - self._cmd = cmd - - # This is invoked in the gdb thread to run the command. - @in_gdb_thread - def __call__(self): - exec_and_log(self._cmd) - - class Cancellable(object): def __init__(self, fn, result_q=None): @@ -663,25 +665,16 @@ class Cancellable(object): def send_gdb(cmd): """Send CMD to the gdb thread. - CMD can be either a function or a string. - If it is a string, it is passed to gdb.execute.""" - if isinstance(cmd, str): - cmd = Invoker(cmd) - + CMD is a function.""" # Post the event and don't wait for the result. gdb.post_event(Cancellable(cmd)) def send_gdb_with_response(fn): """Send FN to the gdb thread and return its result. - If FN is a string, it is passed to gdb.execute and None is - returned as the result. If FN throws an exception, this function will throw the same exception in the calling thread. """ - if isinstance(fn, str): - fn = Invoker(fn) - # Post the event and wait for the result in result_q. result_q = DAPQueue() gdb.post_event(Cancellable(fn, result_q)) diff --git a/gdb/python/lib/gdb/dap/sources.py b/gdb/python/lib/gdb/dap/sources.py index 625c01f..efcd799 100644 --- a/gdb/python/lib/gdb/dap/sources.py +++ b/gdb/python/lib/gdb/dap/sources.py @@ -64,9 +64,9 @@ def decode_source(source): """Decode a Source object. Finds and returns the filename of a given Source object.""" - if "path" in source: - return source["path"] - if "sourceReference" not in source: + if "sourceReference" not in source or source["sourceReference"] <= 0: + if "path" in source: + return source["path"] raise DAPException("either 'path' or 'sourceReference' must appear in Source") ref = source["sourceReference"] if ref not in _id_map: diff --git a/gdb/python/lib/gdb/dap/startup.py b/gdb/python/lib/gdb/dap/startup.py index ab3e8fd..0c95ada 100644 --- a/gdb/python/lib/gdb/dap/startup.py +++ b/gdb/python/lib/gdb/dap/startup.py @@ -204,7 +204,7 @@ def log_stack(level=LogLevel.DEFAULT): @in_gdb_thread -def exec_and_log(cmd, propagate_exception=False): +def exec_and_log(cmd): """Execute the gdb command CMD. If logging is enabled, log the command and its output.""" log("+++ " + cmd) @@ -213,10 +213,10 @@ def exec_and_log(cmd, propagate_exception=False): if output != "": log(">>> " + output) except gdb.error as e: - if propagate_exception: - raise DAPException(str(e)) from e - else: - log_stack() + # Don't normally want to see this, as it interferes with the + # test suite. + log_stack(LogLevel.FULL) + raise DAPException(str(e)) from e @in_gdb_thread diff --git a/gdb/python/lib/gdb/dap/threads.py b/gdb/python/lib/gdb/dap/threads.py index c271961..89046a8 100644 --- a/gdb/python/lib/gdb/dap/threads.py +++ b/gdb/python/lib/gdb/dap/threads.py @@ -16,27 +16,32 @@ import gdb from .server import request +from .startup import in_gdb_thread +@in_gdb_thread def _thread_name(thr): if thr.name is not None: return thr.name if thr.details is not None: return thr.details - return None + # Always return a name, as the protocol doesn't allow for nameless + # threads. Use the local thread number here... it doesn't matter + # without multi-inferior but in that case it might make more + # sense. + return f"Thread #{thr.num}" -@request("threads") +@request("threads", expect_stopped=False) def threads(**args): result = [] for thr in gdb.selected_inferior().threads(): - one_result = { - "id": thr.global_num, - } - name = _thread_name(thr) - if name is not None: - one_result["name"] = name - result.append(one_result) + result.append( + { + "id": thr.global_num, + "name": _thread_name(thr), + } + ) return { "threads": result, } diff --git a/gdb/python/lib/gdb/dap/varref.py b/gdb/python/lib/gdb/dap/varref.py index 8a13c51..d18197b 100644 --- a/gdb/python/lib/gdb/dap/varref.py +++ b/gdb/python/lib/gdb/dap/varref.py @@ -146,6 +146,10 @@ class BaseReference(ABC): if self._children is None: self._children = [None] * self.child_count() for idx in range(start, start + count): + if idx >= len(self._children): + raise DAPException( + f"requested child {idx} outside range of variable {self._ref}" + ) if self._children[idx] is None: (name, value) = self.fetch_one_child(idx) name = self._compute_name(name) @@ -242,7 +246,11 @@ class VariableReference(BaseReference): # changed DAP to allow memory references for any of the # variable response requests, and to lift the restriction # to pointer-to-function from Variable. - if self._value.type.strip_typedefs().code == gdb.TYPE_CODE_PTR: + if ( + self._value.type.strip_typedefs().code == gdb.TYPE_CODE_PTR + and not self._value.is_optimized_out + and not self._value.is_unavailable + ): result["memoryReference"] = hex(int(self._value)) if client_bool_capability("supportsVariableType"): result["type"] = str(self._value.type) diff --git a/gdb/python/lib/gdb/printing.py b/gdb/python/lib/gdb/printing.py index cba27d2..f1ac19d 100644 --- a/gdb/python/lib/gdb/printing.py +++ b/gdb/python/lib/gdb/printing.py @@ -415,10 +415,21 @@ def make_visualizer(value): result = NoOpArrayPrinter(ty, value) elif ty.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION): result = NoOpStructPrinter(ty, value) - elif ty.code in ( - gdb.TYPE_CODE_PTR, - gdb.TYPE_CODE_REF, - gdb.TYPE_CODE_RVALUE_REF, + elif ( + ty.code + in ( + gdb.TYPE_CODE_PTR, + gdb.TYPE_CODE_REF, + gdb.TYPE_CODE_RVALUE_REF, + ) + # Avoid "void *" here because those pointers can't be + # dereferenced without a cast. + and ty.target().code != gdb.TYPE_CODE_VOID + # An optimized-out or unavailable pointer should just be + # treated as a scalar, since there's no way to dereference + # it. + and not value.is_optimized_out + and not value.is_unavailable ): result = NoOpPointerReferencePrinter(value) else: diff --git a/gdb/python/py-block.c b/gdb/python/py-block.c index fa7dd19..66ccad7 100644 --- a/gdb/python/py-block.c +++ b/gdb/python/py-block.c @@ -356,6 +356,9 @@ block_to_block_object (const struct block *block, struct objfile *objfile) } result = PyObject_New (block_object, &block_object_type); + if (result == nullptr) + return nullptr; + result->block = block; result->objfile = objfile; diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c index 58998f5..9ce8671 100644 --- a/gdb/python/py-breakpoint.c +++ b/gdb/python/py-breakpoint.c @@ -1537,9 +1537,7 @@ PyTypeObject breakpoint_object_type = 0, /* tp_alloc */ }; -void _initialize_py_breakpoint (); -void -_initialize_py_breakpoint () +INIT_GDB_FILE (py_breakpoint) { add_setshow_boolean_cmd ("py-breakpoint", class_maintenance, &pybp_debug, diff --git a/gdb/python/py-cmd.c b/gdb/python/py-cmd.c index 5d98d03..dc5e270 100644 --- a/gdb/python/py-cmd.c +++ b/gdb/python/py-cmd.c @@ -105,19 +105,17 @@ cmdpy_function (const char *args, int from_tty, cmd_list_element *command) gdbpy_enter enter_py; - if (! obj) + if (obj == nullptr) error (_("Invalid invocation of Python command object.")); - if (! PyObject_HasAttr ((PyObject *) obj, invoke_cst)) - { - if (obj->command->is_prefix ()) - { - /* A prefix command does not need an invoke method. */ - return; - } - error (_("Python command object missing 'invoke' method.")); - } - if (! args) + /* If we get here for a prefix command then the prefix command had an + 'invoke' method when it was created. If the 'invoke' method is now + missing, then the user has done something weird (like deleting the + invoke method, yuck!). */ + if (!PyObject_HasAttr ((PyObject *) obj, invoke_cst)) + error (_("Python command object missing 'invoke' method.")); + + if (args == nullptr) args = ""; gdbpy_ref<> argobj (PyUnicode_Decode (args, strlen (args), host_charset (), NULL)); @@ -336,24 +334,30 @@ cmdpy_completer (struct cmd_list_element *command, name of the new command. All earlier words must be existing prefix commands. - *BASE_LIST is set to the final prefix command's list of - *sub-commands. + *BASE_LIST is set to the final prefix command's list of sub-commands. START_LIST is the list in which the search starts. + When PREFIX_CMD is not NULL then *PREFIX_CMD is set to the prefix + command itself, or NULL, if there is no prefix command. + This function returns the name of the new command. On error sets the Python error and returns NULL. */ gdb::unique_xmalloc_ptr<char> gdbpy_parse_command_name (const char *name, struct cmd_list_element ***base_list, - struct cmd_list_element **start_list) + struct cmd_list_element **start_list, + struct cmd_list_element **prefix_cmd) { struct cmd_list_element *elt; int len = strlen (name); int i, lastchar; const char *prefix_text2; + if (prefix_cmd != nullptr) + *prefix_cmd = nullptr; + /* Skip trailing whitespace. */ for (i = len - 1; i >= 0 && (name[i] == ' ' || name[i] == '\t'); --i) ; @@ -368,9 +372,8 @@ gdbpy_parse_command_name (const char *name, for (; i > 0 && valid_cmd_char_p (name[i - 1]); --i) ; - gdb::unique_xmalloc_ptr<char> result ((char *) xmalloc (lastchar - i + 2)); - memcpy (result.get (), &name[i], lastchar - i + 1); - result.get ()[lastchar - i + 1] = '\0'; + gdb::unique_xmalloc_ptr<char> result + = make_unique_xstrndup (&name[i], lastchar - i + 1); /* Skip whitespace again. */ for (--i; i >= 0 && (name[i] == ' ' || name[i] == '\t'); --i) @@ -385,7 +388,7 @@ gdbpy_parse_command_name (const char *name, prefix_text2 = prefix_text.c_str (); elt = lookup_cmd_1 (&prefix_text2, *start_list, NULL, NULL, 1); - if (elt == NULL || elt == CMD_LIST_AMBIGUOUS) + if (elt == nullptr || elt == CMD_LIST_AMBIGUOUS || *prefix_text2 != '\0') { PyErr_Format (PyExc_RuntimeError, _("Could not find command prefix %s."), prefix_text.c_str ()); @@ -395,6 +398,8 @@ gdbpy_parse_command_name (const char *name, if (elt->is_prefix ()) { *base_list = elt->subcommands; + if (prefix_cmd != nullptr) + *prefix_cmd = elt; return result; } @@ -469,8 +474,9 @@ cmdpy_init (PyObject *self, PyObject *args, PyObject *kw) return -1; } + cmd_list_element *prefix_cmd = nullptr; gdb::unique_xmalloc_ptr<char> cmd_name - = gdbpy_parse_command_name (name, &cmd_list, &cmdlist); + = gdbpy_parse_command_name (name, &cmd_list, &cmdlist, &prefix_cmd); if (cmd_name == nullptr) return -1; @@ -507,26 +513,64 @@ cmdpy_init (PyObject *self, PyObject *args, PyObject *kw) if (is_prefix) { - int allow_unknown; - - /* If we have our own "invoke" method, then allow unknown - sub-commands. */ - allow_unknown = PyObject_HasAttr (self, invoke_cst); - cmd = add_prefix_cmd (cmd_name.get (), - (enum command_class) cmdtype, - NULL, docstring.release (), &obj->sub_list, - allow_unknown, cmd_list); + bool has_invoke = PyObject_HasAttr (self, invoke_cst) == 1; + if (has_invoke) + { + /* If there's an 'invoke' method, then create the prefix + command, but call cmdpy_function to dispatch to the invoke + method when the user runs the prefix with no sub-command. */ + cmd = add_prefix_cmd (cmd_name.get (), + (enum command_class) cmdtype, + nullptr, + docstring.release (), &obj->sub_list, + 1 /* allow_unknown */, cmd_list); + cmd->func = cmdpy_function; + } + else + { + /* If there is no 'invoke' method, then create the prefix + using the standard prefix callbacks. This means that for + 'set prefix' the user will get the help text listing all + of the sub-commands, and for 'show prefix', the user will + see all of the sub-command values. */ + if (prefix_cmd != nullptr) + { + while (prefix_cmd->prefix != nullptr) + prefix_cmd = prefix_cmd->prefix; + } + + bool is_show = (prefix_cmd != nullptr + && prefix_cmd->subcommands == &showlist); + + if (is_show) + cmd = add_show_prefix_cmd (cmd_name.get (), + (enum command_class) cmdtype, + docstring.release (), + &obj->sub_list, + 0 /* allow_unknown */, cmd_list); + else + cmd = add_basic_prefix_cmd (cmd_name.get (), + (enum command_class) cmdtype, + docstring.release (), + &obj->sub_list, + 0 /* allow_unknown */, cmd_list); + } } else - cmd = add_cmd (cmd_name.get (), (enum command_class) cmdtype, - docstring.release (), cmd_list); + { + /* For non-prefix commands, arrange to call cmdpy_function, which + invokes the Python 'invoke' method, or raises an exception if + the 'invoke' method is missing. */ + cmd = add_cmd (cmd_name.get (), (enum command_class) cmdtype, + docstring.release (), cmd_list); + cmd->func = cmdpy_function; + } /* If successful, the above takes ownership of the name, since we set name_allocated, so release it. */ cmd_name.release (); - /* There appears to be no API to set this. */ - cmd->func = cmdpy_function; + /* There appears to be no API to set these member variables. */ cmd->destroyer = cmdpy_destroyer; cmd->doc_allocated = 1; cmd->name_allocated = 1; diff --git a/gdb/python/py-connection.c b/gdb/python/py-connection.c index 93757ac..a6d9ad0 100644 --- a/gdb/python/py-connection.c +++ b/gdb/python/py-connection.c @@ -428,9 +428,7 @@ connpy_send_packet (PyObject *self, PyObject *args, PyObject *kw) /* Global initialization for this file. */ -void _initialize_py_connection (); -void -_initialize_py_connection () +INIT_GDB_FILE (py_connection) { gdb::observers::connection_removed.attach (connpy_connection_removed, "py-connection"); diff --git a/gdb/python/py-dap.c b/gdb/python/py-dap.c index 7196d6c..9a9875a 100644 --- a/gdb/python/py-dap.c +++ b/gdb/python/py-dap.c @@ -110,9 +110,7 @@ dap_interp::pre_command_loop () call_dap_fn ("pre_command_loop"); } -void _initialize_py_interp (); -void -_initialize_py_interp () +INIT_GDB_FILE (py_interp) { /* The dap code uses module typing, available starting python 3.5. */ #if PY_VERSION_HEX >= 0x03050000 diff --git a/gdb/python/py-disasm.c b/gdb/python/py-disasm.c index 17064dc..47ae99c 100644 --- a/gdb/python/py-disasm.c +++ b/gdb/python/py-disasm.c @@ -254,15 +254,15 @@ disasm_info_object_is_valid (disasm_info_object *obj) /* Fill in OBJ with all the other arguments. */ static void -disasm_info_fill (disasm_info_object *obj, struct gdbarch *gdbarch, +disasm_info_fill (disasm_info_object &obj, struct gdbarch *gdbarch, program_space *progspace, bfd_vma address, disassemble_info *di, disasm_info_object *next) { - obj->gdbarch = gdbarch; - obj->program_space = progspace; - obj->address = address; - obj->gdb_info = di; - obj->next = next; + obj.gdbarch = gdbarch; + obj.program_space = progspace; + obj.address = address; + obj.gdb_info = di; + obj.next = next; } /* Implement DisassembleInfo.__init__. Takes a single argument that must @@ -281,7 +281,7 @@ disasm_info_init (PyObject *self, PyObject *args, PyObject *kwargs) disasm_info_object *other = (disasm_info_object *) info_obj; disasm_info_object *info = (disasm_info_object *) self; - disasm_info_fill (info, other->gdbarch, other->program_space, + disasm_info_fill (*info, other->gdbarch, other->program_space, other->address, other->gdb_info, other->next); other->next = info; @@ -1156,19 +1156,18 @@ gdbpy_disassembler::gdbpy_disassembler (disasm_info_object *obj) happens when gdbpy_print_insn returns. This class is responsible for marking the DisassembleInfo as invalid in its destructor. */ -struct scoped_disasm_info_object +struct scoped_invalidate_disasm_info { - /* Constructor. */ - scoped_disasm_info_object (struct gdbarch *gdbarch, CORE_ADDR memaddr, - disassemble_info *info) - : m_disasm_info (allocate_disasm_info_object ()) + /* Constructor. Just cache DISASM_INFO for use in the destructor. */ + scoped_invalidate_disasm_info + (gdbpy_ref<disasm_info_object> disasm_info) + : m_disasm_info (std::move (disasm_info)) { - disasm_info_fill (m_disasm_info.get (), gdbarch, current_program_space, - memaddr, info, nullptr); + /* Nothing. */ } /* Upon destruction mark m_disasm_info as invalid. */ - ~scoped_disasm_info_object () + ~scoped_invalidate_disasm_info () { /* Invalidate the original DisassembleInfo object as well as any copies that the user might have made. */ @@ -1178,30 +1177,15 @@ struct scoped_disasm_info_object obj->gdb_info = nullptr; } - /* Return a pointer to the underlying disasm_info_object instance. */ - disasm_info_object * - get () const - { - return m_disasm_info.get (); - } - private: - /* Wrapper around the call to PyObject_New, this wrapper function can be - called from the constructor initialization list, while PyObject_New, a - macro, can't. */ - static disasm_info_object * - allocate_disasm_info_object () - { - return (disasm_info_object *) PyObject_New (disasm_info_object, - &disasm_info_object_type); - } - /* A reference to a gdb.disassembler.DisassembleInfo object. When this - containing instance goes out of scope this reference is released, - however, the user might be holding other references to the - DisassembleInfo object in Python code, so the underlying object might - not be deleted. */ + object goes out of scope this reference is released, however, the user + might be holding other references to the DisassembleInfo (either + directly, or via copies of this object), in which case the underlying + object will not be deleted. The destructor of this class ensures + that this DisassembleInfo object, and any copies, are all marked + invalid. */ gdbpy_ref<disasm_info_object> m_disasm_info; }; @@ -1242,17 +1226,30 @@ gdbpy_print_insn (struct gdbarch *gdbarch, CORE_ADDR memaddr, return {}; } - /* Create the new DisassembleInfo object we will pass into Python. This - object will be marked as invalid when we leave this scope. */ - scoped_disasm_info_object scoped_disasm_info (gdbarch, memaddr, info); - disasm_info_object *disasm_info = scoped_disasm_info.get (); + /* Create the new DisassembleInfo object we will pass into Python. */ + gdbpy_ref<disasm_info_object> disasm_info + ((disasm_info_object *) PyObject_New (disasm_info_object, + &disasm_info_object_type)); + if (disasm_info == nullptr) + { + gdbpy_print_stack (); + return {}; + } + + /* Initialise the DisassembleInfo object. */ + disasm_info_fill (*disasm_info.get (), gdbarch, current_program_space, + memaddr, info, nullptr); + + /* Ensure the DisassembleInfo, along with any copies the user makes, are + marked as invalid when we leave this scope. */ + scoped_invalidate_disasm_info invalidate_disasm (disasm_info); /* Call into the registered disassembler to (possibly) perform the disassembly. */ - PyObject *insn_disas_obj = (PyObject *) disasm_info; - gdbpy_ref<> result (PyObject_CallFunctionObjArgs (hook.get (), - insn_disas_obj, - nullptr)); + gdbpy_ref<> result + (PyObject_CallFunctionObjArgs (hook.get (), + (PyObject *) disasm_info.get (), + nullptr)); if (result == nullptr) { diff --git a/gdb/python/py-event-types.def b/gdb/python/py-event-types.def index 15cd9fa..83167f3 100644 --- a/gdb/python/py-event-types.def +++ b/gdb/python/py-event-types.def @@ -54,7 +54,7 @@ GDB_PY_DEFINE_EVENT_TYPE (new_thread, GDB_PY_DEFINE_EVENT_TYPE (thread_exited, "ThreadExitedEvent", "GDB thread exited event object", - event_object_type); + thread_event_object_type); GDB_PY_DEFINE_EVENT_TYPE (new_inferior, "NewInferiorEvent", diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c index 0ea629f..70e1684 100644 --- a/gdb/python/py-finishbreakpoint.c +++ b/gdb/python/py-finishbreakpoint.c @@ -175,7 +175,7 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs) struct frame_id frame_id; PyObject *internal = NULL; int internal_bp = 0; - CORE_ADDR pc; + std::optional<CORE_ADDR> pc; if (!gdb_PyArg_ParseTupleAndKeywords (args, kwargs, "|OO", keywords, &frame_obj, &internal)) @@ -249,9 +249,9 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs) try { - if (get_frame_pc_if_available (frame, &pc)) + if ((pc = get_frame_pc_if_available (frame))) { - struct symbol *function = find_pc_function (pc); + struct symbol *function = find_pc_function (*pc); if (function != nullptr) { struct type *ret_type = diff --git a/gdb/python/py-framefilter.c b/gdb/python/py-framefilter.c index adf4233..db8c274 100644 --- a/gdb/python/py-framefilter.c +++ b/gdb/python/py-framefilter.c @@ -168,7 +168,7 @@ mi_should_print (struct symbol *sym, enum mi_print_types type) { int print_me = 0; - switch (sym->aclass ()) + switch (sym->loc_class ()) { default: case LOC_UNDEF: /* catches errors */ diff --git a/gdb/python/py-gdb-readline.c b/gdb/python/py-gdb-readline.c index ea3a385..70ceebb 100644 --- a/gdb/python/py-gdb-readline.c +++ b/gdb/python/py-gdb-readline.c @@ -29,11 +29,7 @@ static char * gdbpy_readline_wrapper (FILE *sys_stdin, FILE *sys_stdout, -#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 4 const char *prompt) -#else - char *prompt) -#endif { int n; const char *p = NULL; diff --git a/gdb/python/py-infevents.c b/gdb/python/py-infevents.c index e63ba52..f74fb01 100644 --- a/gdb/python/py-infevents.c +++ b/gdb/python/py-infevents.c @@ -40,7 +40,7 @@ create_inferior_call_event_object (inferior_call_kind flag, ptid_t ptid, gdb_assert_not_reached ("invalid inferior_call_kind"); } - gdbpy_ref<> ptid_obj (gdbpy_create_ptid_object (ptid)); + gdbpy_ref<> ptid_obj = gdbpy_create_ptid_object (ptid); if (ptid_obj == NULL) return NULL; diff --git a/gdb/python/py-infthread.c b/gdb/python/py-infthread.c index 4f1f8d4..08533fe 100644 --- a/gdb/python/py-infthread.c +++ b/gdb/python/py-infthread.c @@ -190,7 +190,7 @@ thpy_get_ptid (PyObject *self, void *closure) THPY_REQUIRE_VALID (thread_obj); - return gdbpy_create_ptid_object (thread_obj->thread->ptid); + return gdbpy_create_ptid_object (thread_obj->thread->ptid).release (); } /* Implement gdb.InferiorThread.ptid_string attribute. */ @@ -361,23 +361,14 @@ thpy_repr (PyObject *self) target_pid_to_str (thr->ptid).c_str ()); } -/* Return a reference to a new Python object representing a ptid_t. - The object is a tuple containing (pid, lwp, tid). */ -PyObject * +/* See python-internal.h. */ + +gdbpy_ref<> gdbpy_create_ptid_object (ptid_t ptid) { - int pid; - long lwp; - ULONGEST tid; - PyObject *ret; - - ret = PyTuple_New (3); - if (!ret) - return NULL; - - pid = ptid.pid (); - lwp = ptid.lwp (); - tid = ptid.tid (); + int pid = ptid.pid (); + long lwp = ptid.lwp (); + ULONGEST tid = ptid.tid (); gdbpy_ref<> pid_obj = gdb_py_object_from_longest (pid); if (pid_obj == nullptr) @@ -389,10 +380,14 @@ gdbpy_create_ptid_object (ptid_t ptid) if (tid_obj == nullptr) return nullptr; + gdbpy_ref<> ret (PyTuple_New (3)); + if (ret == nullptr) + return nullptr; + /* Note that these steal references, hence the use of 'release'. */ - PyTuple_SET_ITEM (ret, 0, pid_obj.release ()); - PyTuple_SET_ITEM (ret, 1, lwp_obj.release ()); - PyTuple_SET_ITEM (ret, 2, tid_obj.release ()); + PyTuple_SET_ITEM (ret.get (), 0, pid_obj.release ()); + PyTuple_SET_ITEM (ret.get (), 1, lwp_obj.release ()); + PyTuple_SET_ITEM (ret.get (), 2, tid_obj.release ()); return ret; } diff --git a/gdb/python/py-mi.c b/gdb/python/py-mi.c index 9b871d4..b2ab4e2 100644 --- a/gdb/python/py-mi.c +++ b/gdb/python/py-mi.c @@ -218,11 +218,11 @@ py_object_to_mi_key (PyObject *key_obj) { gdb_assert (name != nullptr); - if (*name == '\0' || !isalpha (*name)) + if (*name == '\0' || !c_isalpha (*name)) return false; for (; *name != '\0'; ++name) - if (!isalnum (*name) && *name != '_' && *name != '-') + if (!c_isalnum (*name) && *name != '_' && *name != '-') return false; return true; @@ -363,7 +363,7 @@ gdbpy_notify_mi (PyObject *self, PyObject *args, PyObject *kwargs) } for (int i = 0; i < name_len; i++) { - if (!isalnum (name[i]) && name[i] != '-') + if (!c_isalnum (name[i]) && name[i] != '-') { PyErr_Format (PyExc_ValueError, diff --git a/gdb/python/py-micmd.c b/gdb/python/py-micmd.c index e4f07c9..07db0cc 100644 --- a/gdb/python/py-micmd.c +++ b/gdb/python/py-micmd.c @@ -350,7 +350,7 @@ micmdpy_init (PyObject *self, PyObject *args, PyObject *kwargs) PyErr_SetString (PyExc_ValueError, _("MI command name is empty.")); return -1; } - else if ((name_len < 2) || (name[0] != '-') || !isalnum (name[1])) + else if ((name_len < 2) || (name[0] != '-') || !c_isalnum (name[1])) { PyErr_SetString (PyExc_ValueError, _("MI command name does not start with '-'" @@ -361,7 +361,7 @@ micmdpy_init (PyObject *self, PyObject *args, PyObject *kwargs) { for (int i = 2; i < name_len; i++) { - if (!isalnum (name[i]) && name[i] != '-') + if (!c_isalnum (name[i]) && name[i] != '-') { PyErr_Format (PyExc_ValueError, @@ -578,9 +578,7 @@ PyTypeObject micmdpy_object_type = { 0, /* tp_alloc */ }; -void _initialize_py_micmd (); -void -_initialize_py_micmd () +INIT_GDB_FILE (py_micmd) { add_setshow_boolean_cmd ("py-micmd", class_maintenance, &pymicmd_debug, diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c index 1c6f569..a9f5754 100644 --- a/gdb/python/py-objfile.c +++ b/gdb/python/py-objfile.c @@ -556,7 +556,7 @@ objfpy_build_id_ok (const char *string) return 0; for (i = 0; i < n; ++i) { - if (!isxdigit (string[i])) + if (!c_isxdigit (string[i])) return 0; } return 1; @@ -619,9 +619,8 @@ gdbpy_lookup_objfile (PyObject *self, PyObject *args, PyObject *kw) struct objfile *objfile = nullptr; if (by_build_id) - gdbarch_iterate_over_objfiles_in_search_order - (current_inferior ()->arch (), - [&objfile, name] (struct objfile *obj) + current_program_space->iterate_over_objfiles_in_search_order + ([&objfile, name] (struct objfile *obj) { /* Don't return separate debug files. */ if (obj->separate_debug_objfile_backlink != nullptr) @@ -642,9 +641,8 @@ gdbpy_lookup_objfile (PyObject *self, PyObject *args, PyObject *kw) return 1; }, gdbpy_current_objfile); else - gdbarch_iterate_over_objfiles_in_search_order - (current_inferior ()->arch (), - [&objfile, name] (struct objfile *obj) + current_program_space->iterate_over_objfiles_in_search_order + ([&objfile, name] (struct objfile *obj) { /* Don't return separate debug files. */ if (obj->separate_debug_objfile_backlink != nullptr) diff --git a/gdb/python/py-param.c b/gdb/python/py-param.c index 763680e..06237b6 100644 --- a/gdb/python/py-param.c +++ b/gdb/python/py-param.c @@ -495,7 +495,11 @@ get_doc_string (PyObject *object, enum doc_string_type doc_type, } } - if (result == nullptr) + /* For the set/show docs, if these strings are empty then we set then to + a non-empty string. This ensures that the command has some sane + documentation for its 'help' text. */ + if (result == nullptr + || (doc_type != doc_string_description && *result == '\0')) { if (doc_type == doc_string_description) result.reset (xstrdup (_("This command is not documented."))); @@ -904,6 +908,18 @@ parmpy_init (PyObject *self, PyObject *args, PyObject *kwds) show_doc = get_doc_string (self, doc_string_show, name); doc = get_doc_string (self, doc_string_description, cmd_name.get ()); + /* The set/show docs should always be a non-empty string. */ + gdb_assert (set_doc != nullptr && *set_doc != '\0'); + gdb_assert (show_doc != nullptr && *show_doc != '\0'); + + /* For the DOC string only, if it is the empty string, then we convert it + to NULL. This means GDB will not even display a blank line for this + part of the help text, instead the set/show line is all the user will + get. */ + gdb_assert (doc != nullptr); + if (*doc == '\0') + doc = nullptr; + Py_INCREF (self); try diff --git a/gdb/python/py-record.c b/gdb/python/py-record.c index 7e7904b..89c2e77 100644 --- a/gdb/python/py-record.c +++ b/gdb/python/py-record.c @@ -696,6 +696,9 @@ gdbpy_current_recording (PyObject *self, PyObject *args) Py_RETURN_NONE; ret = PyObject_New (recpy_record_object, &recpy_record_type); + if (ret == nullptr) + return nullptr; + ret->thread = inferior_thread (); ret->method = target_record_method (ret->thread->ptid); diff --git a/gdb/python/py-symbol.c b/gdb/python/py-symbol.c index 3028a30..284e385 100644 --- a/gdb/python/py-symbol.c +++ b/gdb/python/py-symbol.c @@ -126,7 +126,7 @@ sympy_get_addr_class (PyObject *self, void *closure) SYMPY_REQUIRE_VALID (self, symbol); - return gdb_py_object_from_longest (symbol->aclass ()).release (); + return gdb_py_object_from_longest (symbol->loc_class ()).release (); } /* Implement gdb.Symbol.domain attribute. Return the domain as an @@ -156,42 +156,39 @@ static PyObject * sympy_is_constant (PyObject *self, void *closure) { struct symbol *symbol = NULL; - enum address_class theclass; SYMPY_REQUIRE_VALID (self, symbol); - theclass = symbol->aclass (); + location_class loc_class = symbol->loc_class (); - return PyBool_FromLong (theclass == LOC_CONST || theclass == LOC_CONST_BYTES); + return PyBool_FromLong (loc_class == LOC_CONST || loc_class == LOC_CONST_BYTES); } static PyObject * sympy_is_function (PyObject *self, void *closure) { struct symbol *symbol = NULL; - enum address_class theclass; SYMPY_REQUIRE_VALID (self, symbol); - theclass = symbol->aclass (); + location_class loc_class = symbol->loc_class (); - return PyBool_FromLong (theclass == LOC_BLOCK); + return PyBool_FromLong (loc_class == LOC_BLOCK); } static PyObject * sympy_is_variable (PyObject *self, void *closure) { struct symbol *symbol = NULL; - enum address_class theclass; SYMPY_REQUIRE_VALID (self, symbol); - theclass = symbol->aclass (); + location_class loc_class = symbol->loc_class (); return PyBool_FromLong (!symbol->is_argument () - && (theclass == LOC_LOCAL || theclass == LOC_REGISTER - || theclass == LOC_STATIC || theclass == LOC_COMPUTED - || theclass == LOC_OPTIMIZED_OUT)); + && (loc_class == LOC_LOCAL || loc_class == LOC_REGISTER + || loc_class == LOC_STATIC || loc_class == LOC_COMPUTED + || loc_class == LOC_OPTIMIZED_OUT)); } /* Implementation of Symbol.is_artificial. */ @@ -279,7 +276,7 @@ sympy_value (PyObject *self, PyObject *args) } SYMPY_REQUIRE_VALID (self, symbol); - if (symbol->aclass () == LOC_TYPEDEF) + if (symbol->loc_class () == LOC_TYPEDEF) { PyErr_SetString (PyExc_TypeError, "cannot get the value of a typedef"); return NULL; @@ -605,17 +602,15 @@ gdbpy_lookup_static_symbols (PyObject *self, PyObject *args, PyObject *kw) /* Expand any symtabs that contain potentially matching symbols. */ lookup_name_info lookup_name (name, symbol_name_match_type::FULL); - expand_symtabs_matching (NULL, lookup_name, NULL, NULL, - SEARCH_STATIC_BLOCK, flags); for (objfile *objfile : current_program_space->objfiles ()) { - for (compunit_symtab *cust : objfile->compunits ()) + auto callback = [&] (compunit_symtab *cust) { /* Skip included compunits to prevent including compunits from being searched twice. */ if (cust->user != nullptr) - continue; + return true; const struct blockvector *bv = cust->blockvector (); const struct block *block = bv->static_block (); @@ -628,13 +623,18 @@ gdbpy_lookup_static_symbols (PyObject *self, PyObject *args, PyObject *kw) if (symbol != nullptr) { PyObject *sym_obj = symbol_to_symbol_object (symbol); - if (sym_obj == nullptr) - return nullptr; - if (PyList_Append (return_list.get (), sym_obj) == -1) - return nullptr; + if (sym_obj == nullptr + || PyList_Append (return_list.get (), sym_obj) == -1) + return false; } } - } + + return true; + }; + + if (!objfile->search (nullptr, &lookup_name, nullptr, callback, + SEARCH_STATIC_BLOCK, flags)) + return nullptr; } } catch (const gdb_exception &except) diff --git a/gdb/python/py-type.c b/gdb/python/py-type.c index f37afde..a2c5939 100644 --- a/gdb/python/py-type.c +++ b/gdb/python/py-type.c @@ -158,7 +158,7 @@ convert_field (struct type *type, int field) } else { - if (type->field (field).loc_kind () == FIELD_LOC_KIND_DWARF_BLOCK) + if (type->field (field).loc_is_dwarf_block ()) arg = gdbpy_ref<>::new_reference (Py_None); else arg = gdb_py_object_from_longest (type->field (field).loc_bitpos ()); @@ -1045,9 +1045,9 @@ typy_template_argument (PyObject *self, PyObject *args) } sym = TYPE_TEMPLATE_ARGUMENT (type, argno); - if (sym->aclass () == LOC_TYPEDEF) + if (sym->loc_class () == LOC_TYPEDEF) return type_to_type_object (sym->type ()); - else if (sym->aclass () == LOC_OPTIMIZED_OUT) + else if (sym->loc_class () == LOC_OPTIMIZED_OUT) { PyErr_Format (PyExc_RuntimeError, _("Template argument is optimized out")); @@ -1312,10 +1312,9 @@ static PyObject * typy_has_key (PyObject *self, PyObject *args) { struct type *type = ((type_object *) self)->type; - const char *field; - int i; + const char *field_name; - if (!PyArg_ParseTuple (args, "s", &field)) + if (!PyArg_ParseTuple (args, "s", &field_name)) return NULL; /* We want just fields of this type, not of base types, so instead of @@ -1326,11 +1325,11 @@ typy_has_key (PyObject *self, PyObject *args) if (type == NULL) return NULL; - for (i = 0; i < type->num_fields (); i++) + for (const auto &field : type->fields ()) { - const char *t_field_name = type->field (i).name (); + const char *t_field_name = field.name (); - if (t_field_name && (strcmp_iw (t_field_name, field) == 0)) + if (t_field_name && (strcmp_iw (t_field_name, field_name) == 0)) Py_RETURN_TRUE; } Py_RETURN_FALSE; diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c index d43d7e9..43125bb 100644 --- a/gdb/python/py-unwind.c +++ b/gdb/python/py-unwind.c @@ -287,6 +287,8 @@ pyuw_create_unwind_info (PyObject *pyo_pending_frame, unwind_info_object *unwind_info = PyObject_New (unwind_info_object, &unwind_info_object_type); + if (unwind_info == nullptr) + return nullptr; unwind_info->frame_id = frame_id; Py_INCREF (pyo_pending_frame); @@ -1029,9 +1031,7 @@ gdbpy_initialize_unwind (void) return 0; } -void _initialize_py_unwind (); -void -_initialize_py_unwind () +INIT_GDB_FILE (py_unwind) { add_setshow_boolean_cmd ("py-unwind", class_maintenance, &pyuw_debug, diff --git a/gdb/python/py-value.c b/gdb/python/py-value.c index 8a2e263..5d8fab9 100644 --- a/gdb/python/py-value.c +++ b/gdb/python/py-value.c @@ -478,6 +478,9 @@ valpy_get_dynamic_type (PyObject *self, void *closure) type = value_rtti_type (val, NULL, NULL, NULL); else type = val->type (); + + if (type == nullptr) + type = val->type (); } catch (const gdb_exception &except) { @@ -1288,6 +1291,30 @@ valpy_get_is_optimized_out (PyObject *self, void *closure) Py_RETURN_FALSE; } +/* Implements gdb.Value.is_unavailable. Return true if any part of the + value is unavailable. */ + +static PyObject * +valpy_get_is_unavailable (PyObject *self, void *closure) +{ + struct value *value = ((value_object *) self)->value; + bool entirely_available = false; + + try + { + entirely_available = value->entirely_available (); + } + catch (const gdb_exception &except) + { + return gdbpy_handle_gdb_exception (nullptr, except); + } + + if (!entirely_available) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; +} + /* Implements gdb.Value.is_lazy. */ static PyObject * valpy_get_is_lazy (PyObject *self, void *closure) @@ -1842,7 +1869,7 @@ valpy_long (PyObject *self) { struct value *value = ((value_object *) self)->value; struct type *type = value->type (); - LONGEST l = 0; + PyObject *result; try { @@ -1858,17 +1885,57 @@ valpy_long (PyObject *self) && type->code () != TYPE_CODE_PTR) error (_("Cannot convert value to long.")); - l = value_as_long (value); + gdb::array_view<const gdb_byte> contents = value->contents (); +#if PY_VERSION_HEX >= 0x030d0000 + int flags = (type_byte_order (type) == BFD_ENDIAN_BIG + ? Py_ASNATIVEBYTES_BIG_ENDIAN + : Py_ASNATIVEBYTES_LITTLE_ENDIAN); + if (type->is_unsigned ()) + flags |= Py_ASNATIVEBYTES_UNSIGNED_BUFFER; + result = PyLong_FromNativeBytes (contents.data (), contents.size (), + flags); +#else + /* Here we construct a call to "int.from_bytes", passing in the + appropriate arguments. We need a somewhat roundabout + approach because int.from_bytes requires "signed" to be a + keyword arg. */ + + /* PyObject_Call requires a tuple argument. */ + gdbpy_ref<> empty_tuple (PyTuple_New (0)); + if (empty_tuple == nullptr) + return nullptr; + + /* Since we need a dictionary anyway, we pass all arguments as + keywords, building the dictionary here. */ + gdbpy_ref<> args + (Py_BuildValue ("{sy#sssO}", + "bytes", contents.data (), + (Py_ssize_t) contents.size (), + "byteorder", + (type_byte_order (type) == BFD_ENDIAN_BIG + ? "big" : "little"), + "signed", + type->is_unsigned () + ? Py_False : Py_True)); + if (args == nullptr) + return nullptr; + + /* Find the "int.from_bytes" callable. */ + gdbpy_ref<> callable (PyObject_GetAttrString ((PyObject *) &PyLong_Type, + "from_bytes")); + if (callable == nullptr) + return nullptr; + + result = PyObject_Call (callable.get (), empty_tuple.get (), + args.get ()); +#endif } catch (const gdb_exception &except) { return gdbpy_handle_gdb_exception (nullptr, except); } - if (type->is_unsigned ()) - return gdb_py_object_from_ulongest (l).release (); - else - return gdb_py_object_from_longest (l).release (); + return result; } /* Implements conversion to float. */ @@ -2194,6 +2261,9 @@ static gdb_PyGetSetDef value_object_getset[] = { "Boolean telling whether the value is optimized " "out (i.e., not available).", NULL }, + { "is_unavailable", valpy_get_is_unavailable, nullptr, + "Boolean telling whether the value is unavailable.", + nullptr }, { "type", valpy_get_type, NULL, "Type of the value.", NULL }, { "dynamic_type", valpy_get_dynamic_type, NULL, "Dynamic type of the value.", NULL }, diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index 5262d76..f61a175 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -88,6 +88,8 @@ #include <frameobject.h> #include "py-ref.h" +static_assert (PY_VERSION_HEX >= 0x03040000); + #define Py_TPFLAGS_CHECKTYPES 0 /* If Python.h does not define WITH_THREAD, then the various @@ -135,17 +137,6 @@ typedef unsigned long gdb_py_ulongest; #endif /* HAVE_LONG_LONG */ -#if PY_VERSION_HEX < 0x03020000 -typedef long Py_hash_t; -#endif - -/* PyMem_RawMalloc appeared in Python 3.4. For earlier versions, we can just - fall back to PyMem_Malloc. */ - -#if PY_VERSION_HEX < 0x03040000 -#define PyMem_RawMalloc PyMem_Malloc -#endif - /* A template variable holding the format character (as for Py_BuildValue) for a given type. */ template<typename T> @@ -512,14 +503,20 @@ PyObject *gdbpy_create_lazy_string_object (CORE_ADDR address, long length, const char *encoding, struct type *type); PyObject *gdbpy_inferiors (PyObject *unused, PyObject *unused2); -PyObject *gdbpy_create_ptid_object (ptid_t ptid); + +/* Return a reference to a new Python Tuple object representing a ptid_t. + The object is a tuple containing (pid, lwp, tid). */ + +extern gdbpy_ref<> gdbpy_create_ptid_object (ptid_t ptid); + PyObject *gdbpy_selected_thread (PyObject *self, PyObject *args); PyObject *gdbpy_selected_inferior (PyObject *self, PyObject *args); PyObject *gdbpy_string_to_argv (PyObject *self, PyObject *args); PyObject *gdbpy_parameter_value (const setting &var); gdb::unique_xmalloc_ptr<char> gdbpy_parse_command_name (const char *name, struct cmd_list_element ***base_list, - struct cmd_list_element **start_list); + struct cmd_list_element **start_list, + struct cmd_list_element **prefix_cmd = nullptr); PyObject *gdbpy_register_tui_window (PyObject *self, PyObject *args, PyObject *kw); diff --git a/gdb/python/python.c b/gdb/python/python.c index 24cb511..740b196 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -31,7 +31,6 @@ #include "python.h" #include "extension-priv.h" #include "cli/cli-utils.h" -#include <ctype.h> #include "location.h" #include "run-on-main-thread.h" #include "observable.h" @@ -1202,15 +1201,22 @@ gdbpy_post_event (PyObject *self, PyObject *args) static PyObject * gdbpy_interrupt (PyObject *self, PyObject *args) { +#ifdef __MINGW32__ { - /* Make sure the interrupt isn't delivered immediately somehow. - This probably is not truly needed, but at the same time it - seems more clear to be explicit about the intent. */ gdbpy_allow_threads temporarily_exit_python; scoped_disable_cooperative_sigint_handling no_python_sigint; set_quit_flag (); } +#else + { + /* For targets with support kill() just send SIGINT. This will be + handled as if the user hit Ctrl+C. This isn't exactly the same as + the above, which directly sets the quit flag. Consider, for + example, every place that install_sigint_handler is called. */ + kill (getpid (), SIGINT); + } +#endif Py_RETURN_NONE; } @@ -1570,21 +1576,21 @@ gdbpy_write (PyObject *self, PyObject *args, PyObject *kw) try { + ui_file *stream; switch (stream_type) { case 1: - { - gdb_printf (gdb_stderr, "%s", arg); - break; - } + stream = gdb_stderr; + break; case 2: - { - gdb_printf (gdb_stdlog, "%s", arg); - break; - } + stream = gdb_stdlog; + break; default: - gdb_printf (gdb_stdout, "%s", arg); + stream = gdb_stdout; + break; } + + gdb_puts (arg, stream); } catch (const gdb_exception &except) { @@ -1630,6 +1636,40 @@ gdbpy_flush (PyObject *self, PyObject *args, PyObject *kw) Py_RETURN_NONE; } +/* Implement gdb.warning(). Takes a single text string argument and emit a + warning using GDB's 'warning' function. The input text string must not + be empty. */ + +static PyObject * +gdbpy_warning (PyObject *self, PyObject *args, PyObject *kw) +{ + const char *text; + static const char *keywords[] = { "text", nullptr }; + + if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s", keywords, &text)) + return nullptr; + + if (strlen (text) == 0) + { + PyErr_SetString (PyExc_ValueError, + _("Empty text string passed to gdb.warning")); + return nullptr; + } + + try + { + warning ("%s", text); + } + catch (const gdb_exception &ex) + { + /* The warning() call probably cannot throw an exception. But just + in case it ever does. */ + return gdbpy_handle_gdb_exception (nullptr, ex); + } + + Py_RETURN_NONE; +} + /* Return non-zero if print-stack is not "none". */ int @@ -2446,7 +2486,7 @@ py_initialize () /foo/lib/pythonX.Y/... This must be done before calling Py_Initialize. */ gdb::unique_xmalloc_ptr<char> progname - (concat (ldirname (python_libdir.c_str ()).c_str (), SLASH_STRING, "bin", + (concat (gdb_ldirname (python_libdir.c_str ()).c_str (), SLASH_STRING, "bin", SLASH_STRING, "python", (char *) NULL)); { @@ -2683,9 +2723,7 @@ test_python () /* See python.h. */ cmd_list_element *python_cmd_element = nullptr; -void _initialize_python (); -void -_initialize_python () +INIT_GDB_FILE (python) { cmd_list_element *python_interactive_cmd = add_com ("python-interactive", class_obscure, @@ -3124,6 +3162,12 @@ Return the current print options." }, METH_VARARGS | METH_KEYWORDS, "notify_mi (name, data) -> None\n\ Output async record to MI channels if any." }, + + { "warning", (PyCFunction) gdbpy_warning, + METH_VARARGS | METH_KEYWORDS, + "warning (text) -> None\n\ +Print a warning." }, + {NULL, NULL, 0, NULL} }; |