# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import enum
import gdb
from .server import send_event
)
-class StopKinds(enum.Enum):
- # The values here are chosen to follow the DAP spec.
- STEP = "step"
- BREAKPOINT = "breakpoint"
- PAUSE = "pause"
- EXCEPTION = "exception"
-
-
-_expected_stop = None
+_expected_pause = False
@in_gdb_thread
-def exec_and_expect_stop(cmd, reason):
- """Indicate that a stop is expected, then execute CMD"""
- global _expected_stop
- _expected_stop = reason
- if reason != StopKinds.PAUSE:
- global _suppress_cont
- _suppress_cont = True
+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.,
+ a SIGINT) will be reported as "pause" instead.
+ """
+ global _expected_pause
+ _expected_pause = expected_pause
+ global _suppress_cont
+ # If we're expecting a pause, then we're definitely not
+ # continuing.
+ _suppress_cont = not expected_pause
# FIXME if the call fails should we clear _suppress_cont?
exec_and_log(cmd)
+# Map from gdb stop reasons to DAP stop reasons. Some of these can't
+# be seen ordinarily in DAP -- only if the client lets the user toggle
+# some settings (e.g. stop-on-solib-events) or enter commands (e.g.,
+# 'until').
+stop_reason_map = {
+ "breakpoint-hit": "breakpoint",
+ "watchpoint-trigger": "data breakpoint",
+ "read-watchpoint-trigger": "data breakpoint",
+ "access-watchpoint-trigger": "data breakpoint",
+ "function-finished": "step",
+ "location-reached": "step",
+ "watchpoint-scope": "data breakpoint",
+ "end-stepping-range": "step",
+ "exited-signalled": "exited",
+ "exited": "exited",
+ "exited-normally": "exited",
+ "signal-received": "signal",
+ "solib-event": "solib",
+ "fork": "fork",
+ "vfork": "vfork",
+ "syscall-entry": "syscall-entry",
+ "syscall-return": "syscall-return",
+ "exec": "exec",
+ "no-history": "no-history",
+}
+
+
@in_gdb_thread
def _on_stop(event):
global inferior_running
inferior_running = False
log("entering _on_stop: " + repr(event))
- global _expected_stop
+ log(" details: " + repr(event.details))
obj = {
"threadId": gdb.selected_thread().global_num,
"allThreadsStopped": True,
}
if isinstance(event, gdb.BreakpointEvent):
- # Ignore the expected stop, we hit a breakpoint instead.
- _expected_stop = StopKinds.BREAKPOINT
obj["hitBreakpointIds"] = [x.number for x in event.breakpoints]
- elif _expected_stop is None:
- # FIXME what is even correct here
- _expected_stop = StopKinds.EXCEPTION
- obj["reason"] = _expected_stop.value
- _expected_stop = None
+ global stop_reason_map
+ reason = event.details["reason"]
+ global _expected_pause
+ if (
+ _expected_pause
+ and reason == "signal-received"
+ and event.details["signal-name"] in ("SIGINT", "SIGSTOP")
+ ):
+ obj["reason"] = "pause"
+ else:
+ obj["reason"] = stop_reason_map[reason]
+ _expected_pause = False
send_event("stopped", obj)
@in_gdb_thread
def _on_inferior_call(event):
global _infcall_was_running
+ global inferior_running
if isinstance(event, gdb.InferiorCallPreEvent):
_infcall_was_running = inferior_running
if not _infcall_was_running:
_cont(None)
else:
- if not _infcall_was_running:
- _on_stop(None)
+ # If the inferior is already marked as stopped here, then that
+ # means that the call caused some other stop, and we don't
+ # want to double-report it.
+ if not _infcall_was_running and inferior_running:
+ inferior_running = False
+ obj = {
+ "threadId": gdb.selected_thread().global_num,
+ "allThreadsStopped": True,
+ # DAP says any string is ok.
+ "reason": "function call",
+ }
+ global _expected_pause
+ _expected_pause = False
+ send_event("stopped", obj)
gdb.events.stop.connect(_on_stop)
import gdb
-from .events import StopKinds, exec_and_expect_stop
+from .events import exec_and_expect_stop
from .server import capability, request
from .startup import in_gdb_thread, send_gdb, send_gdb_with_response
from .state import set_thread
cmd = "next"
if granularity == "instruction":
cmd += "i"
- exec_and_expect_stop(cmd, StopKinds.STEP)
+ exec_and_expect_stop(cmd)
@capability("supportsSteppingGranularity")
cmd = "step"
if granularity == "instruction":
cmd += "i"
- exec_and_expect_stop(cmd, StopKinds.STEP)
+ exec_and_expect_stop(cmd)
@request("stepOut", response=False)
def step_out(*, threadId: int, singleThread: bool = False, **args):
_handle_thread_step(threadId, singleThread, True)
- exec_and_expect_stop("finish", StopKinds.STEP)
+ exec_and_expect_stop("finish")
# This is a server-side request because it is funny: it wants to
@request("continue", on_dap_thread=True)
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", None))
+ send_gdb(lambda: exec_and_expect_stop("continue"))
return {"allThreadsContinued": not locked}