mirror of
https://github.com/apache/nuttx.git
synced 2026-03-23 22:43:57 +08:00
I0680e48d8ff8847c8712e1a54efe32d320e7c84d changes the scope of the symbol definition. So nxgdb needs to synchronize this modification. Signed-off-by: guoshengyuan1 <guoshengyuan1@xiaomi.com>
683 lines
23 KiB
Python
683 lines
23 KiB
Python
############################################################################
|
|
# tools/pynuttx/nxgdb/thread.py
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# Licensed to the Apache Software Foundation (ASF) under one or more
|
|
# contributor license agreements. See the NOTICE file distributed with
|
|
# this work for additional information regarding copyright ownership. The
|
|
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance with the
|
|
# License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
#
|
|
############################################################################
|
|
|
|
import argparse
|
|
import re
|
|
from enum import Enum, auto
|
|
|
|
import gdb
|
|
|
|
from . import utils
|
|
from .stack import Stack
|
|
|
|
UINT16_MAX = 0xFFFF
|
|
SEM_TYPE_MUTEX = 4
|
|
TSTATE_TASK_RUNNING = utils.get_symbol_value("TSTATE_TASK_RUNNING")
|
|
CONFIG_SMP_NCPUS = utils.get_symbol_value("CONFIG_SMP_NCPUS") or 1
|
|
|
|
|
|
def is_thread_command_supported():
|
|
# Check if the native thread command is available by compare the number of threads.
|
|
# It should have at least CONFIG_SMP_NCPUS of idle threads.
|
|
return len(gdb.selected_inferior().threads()) > CONFIG_SMP_NCPUS
|
|
|
|
|
|
class Registers:
|
|
saved_regs = None
|
|
reginfo = None
|
|
|
|
def __init__(self):
|
|
if not Registers.reginfo:
|
|
reginfo = {}
|
|
|
|
# Switch to second inferior to get the original remote-register layout
|
|
state = utils.suppress_cli_notifications(True)
|
|
utils.switch_inferior(2)
|
|
|
|
natural_size = gdb.lookup_type("long").sizeof
|
|
tcb_info = gdb.parse_and_eval("g_tcbinfo")
|
|
reg_off = tcb_info["reg_off"]["p"] # Register offsets in tcbinfo
|
|
packet_size = tcb_info["regs_num"] * natural_size
|
|
|
|
lines = gdb.execute("maint print remote-registers", to_string=True)
|
|
for line in lines.splitlines()[1:]:
|
|
if not line:
|
|
continue
|
|
|
|
# Name Nr Rel Offset Size Type Rmt Nr g/G Offset
|
|
match = re.match(
|
|
r"\s*(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)(?:\s+(\d+)\s+(\d+))?",
|
|
line,
|
|
)
|
|
if not match:
|
|
continue
|
|
|
|
name, _, _, _, size, _, rmt_nr, offset = match.groups()
|
|
|
|
# We only need those registers that have a remote register
|
|
if rmt_nr is None:
|
|
continue
|
|
|
|
rmt_nr = int(rmt_nr)
|
|
offset = int(offset)
|
|
size = int(size)
|
|
|
|
# We only have limited number of registers in packet
|
|
if offset + size > packet_size:
|
|
continue
|
|
|
|
index = offset // natural_size
|
|
tcb_reg_off = int(reg_off[index])
|
|
if tcb_reg_off == UINT16_MAX:
|
|
# This register is not saved in tcb context
|
|
continue
|
|
|
|
reginfo[name] = {
|
|
"rmt_nr": rmt_nr, # The register number in remote-registers, Aka the one we saved in g_tcbinfo.
|
|
"tcb_reg_off": tcb_reg_off,
|
|
}
|
|
|
|
Registers.reginfo = reginfo
|
|
utils.switch_inferior(1) # Switch back
|
|
utils.suppress_cli_notifications(state)
|
|
|
|
def load(self, regs):
|
|
"""Load registers from context register address"""
|
|
regs = int(regs)
|
|
for name, info in Registers.reginfo.items():
|
|
addr = regs + info["tcb_reg_off"]
|
|
# value = *(uintptr_t *)addr
|
|
value = (
|
|
gdb.Value(addr)
|
|
.cast(utils.lookup_type("uintptr_t").pointer())
|
|
.dereference()
|
|
)
|
|
gdb.execute(f"set ${name}={int(value)}")
|
|
|
|
def switch(self, pid):
|
|
"""Switch to the specified thread"""
|
|
tcb = utils.get_tcb(pid)
|
|
if not tcb:
|
|
gdb.write(f"Thread {pid} not found\n")
|
|
return
|
|
|
|
if tcb["task_state"] == TSTATE_TASK_RUNNING:
|
|
# If the thread is running, then register is not in context but saved temporarily
|
|
self.restore()
|
|
return
|
|
|
|
# Save current if this is the running thread, which is the case we never saved it before
|
|
if not self.saved_regs:
|
|
self.save()
|
|
|
|
self.load(tcb["xcp"]["regs"])
|
|
|
|
def save(self):
|
|
"""Save current registers"""
|
|
if Registers.saved_regs:
|
|
# Already saved
|
|
return
|
|
|
|
registers = {}
|
|
frame = gdb.newest_frame()
|
|
for name, _ in Registers.reginfo.items():
|
|
value = frame.read_register(name)
|
|
registers[name] = value
|
|
|
|
Registers.saved_regs = registers
|
|
|
|
def restore(self):
|
|
if not Registers.saved_regs:
|
|
return
|
|
|
|
for name, value in Registers.saved_regs.items():
|
|
gdb.execute(f"set ${name}={int(value)}")
|
|
|
|
Registers.saved_regs = None
|
|
|
|
|
|
g_registers = Registers()
|
|
|
|
|
|
class SetRegs(gdb.Command):
|
|
"""Set registers to the specified values.
|
|
Usage: setregs [regs]
|
|
|
|
Etc: setregs
|
|
setregs tcb->xcp.regs
|
|
setregs g_pidhash[0]->xcp.regs
|
|
|
|
Default regs is tcbinfo_current_regs(),if regs is NULL, it will not set registers.
|
|
"""
|
|
|
|
def __init__(self):
|
|
super().__init__("setregs", gdb.COMMAND_USER)
|
|
|
|
def invoke(self, arg, from_tty):
|
|
parser = argparse.ArgumentParser(
|
|
description="Set registers to the specified values"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"regs",
|
|
nargs="?",
|
|
default="",
|
|
help="The registers to set, use tcbinfo_current_regs() if not specified",
|
|
)
|
|
|
|
try:
|
|
args = parser.parse_args(gdb.string_to_argv(arg))
|
|
except SystemExit:
|
|
return
|
|
|
|
if args and args.regs:
|
|
regs = gdb.parse_and_eval(f"{args.regs}").cast(
|
|
utils.lookup_type("char").pointer()
|
|
)
|
|
else:
|
|
current_regs = gdb.parse_and_eval("tcbinfo_current_regs()")
|
|
regs = current_regs.cast(utils.lookup_type("char").pointer())
|
|
|
|
if regs == 0:
|
|
gdb.write("regs is NULL\n")
|
|
return
|
|
|
|
g_registers.save()
|
|
g_registers.load(regs)
|
|
|
|
|
|
class Nxinfothreads(gdb.Command):
|
|
"""Display information of all threads"""
|
|
|
|
def __init__(self):
|
|
super().__init__("info nxthreads", gdb.COMMAND_USER)
|
|
if not is_thread_command_supported():
|
|
gdb.execute("define info threads\n info nxthreads \n end\n")
|
|
|
|
def invoke(self, args, from_tty):
|
|
npidhash = gdb.parse_and_eval("g_npidhash")
|
|
pidhash = gdb.parse_and_eval("g_pidhash")
|
|
statenames = gdb.parse_and_eval("nxsched_get_stateinfo::g_statenames")
|
|
|
|
if utils.is_target_smp():
|
|
gdb.write(
|
|
"%-5s %-4s %-4s %-4s %-21s %-80s %-30s\n"
|
|
% ("Index", "Tid", "Pid", "Cpu", "Thread", "Info", "Frame")
|
|
)
|
|
else:
|
|
gdb.write(
|
|
"%-5s %-4s %-4s %-21s %-80s %-30s\n"
|
|
% ("Index", "Tid", "Pid", "Thread", "Info", "Frame")
|
|
)
|
|
|
|
for i, tcb in enumerate(utils.ArrayIterator(pidhash, npidhash)):
|
|
if not tcb:
|
|
continue
|
|
|
|
pid = tcb["group"]["tg_pid"]
|
|
tid = tcb["pid"]
|
|
|
|
if tcb["task_state"] == gdb.parse_and_eval("TSTATE_TASK_RUNNING"):
|
|
index = f"*{i}"
|
|
pc = utils.get_pc()
|
|
else:
|
|
index = f" {i}"
|
|
pc = utils.get_pc(tcb=tcb)
|
|
|
|
thread = f"Thread {hex(tcb)}"
|
|
|
|
statename = statenames[tcb["task_state"]].string()
|
|
statename = f'\x1b{"[32;1m" if statename == "Running" else "[33;1m"}{statename}\x1b[m'
|
|
|
|
if tcb["task_state"] == gdb.parse_and_eval("TSTATE_WAIT_SEM"):
|
|
mutex = tcb["waitobj"].cast(utils.lookup_type("sem_t").pointer())
|
|
if mutex["flags"] & SEM_TYPE_MUTEX:
|
|
mutex = tcb["waitobj"].cast(utils.lookup_type("mutex_t").pointer())
|
|
statename = f"Waiting,Mutex:{mutex['holder']}"
|
|
|
|
try:
|
|
"""Maybe tcb not have name member, or name is not utf-8"""
|
|
info = (
|
|
"(Name: \x1b[31;1m%s\x1b[m, State: %s, Priority: %d, Stack: %d)"
|
|
% (
|
|
utils.get_task_name(tcb),
|
|
statename,
|
|
tcb["sched_priority"],
|
|
tcb["adj_stack_size"],
|
|
)
|
|
)
|
|
except gdb.error and UnicodeDecodeError:
|
|
info = "(Name: Not utf-8, State: %s, Priority: %d, Stack: %d)" % (
|
|
statename,
|
|
tcb["sched_priority"],
|
|
tcb["adj_stack_size"],
|
|
)
|
|
|
|
line = gdb.find_pc_line(pc)
|
|
if line.symtab:
|
|
func = gdb.execute(f"info symbol {pc} ", to_string=True)
|
|
frame = "\x1b[34;1m0x%x\x1b[\t\x1b[33;1m%s\x1b[m at %s:%d" % (
|
|
pc,
|
|
func.split()[0] + "()",
|
|
line.symtab,
|
|
line.line,
|
|
)
|
|
else:
|
|
frame = "No symbol with pc"
|
|
|
|
if utils.is_target_smp():
|
|
cpu = f"{tcb['cpu']}"
|
|
gdb.write(
|
|
"%-5s %-4s %-4s %-4s %-21s %-80s %-30s\n"
|
|
% (index, tid, pid, cpu, thread, info, frame)
|
|
)
|
|
else:
|
|
gdb.write(
|
|
"%-5s %-4s %-4s %-21s %-80s %-30s\n"
|
|
% (index, tid, pid, thread, info, frame)
|
|
)
|
|
|
|
|
|
class Nxthread(gdb.Command):
|
|
"""Switch to a specified thread"""
|
|
|
|
def __init__(self):
|
|
if not is_thread_command_supported():
|
|
super().__init__("thread", gdb.COMMAND_USER)
|
|
else:
|
|
super().__init__("nxthread", gdb.COMMAND_USER)
|
|
|
|
def invoke(self, args, from_tty):
|
|
npidhash = gdb.parse_and_eval("g_npidhash")
|
|
pidhash = gdb.parse_and_eval("g_pidhash")
|
|
arg = args.split(" ")
|
|
arglen = len(arg)
|
|
|
|
if arg[0] == "":
|
|
pass
|
|
elif arg[0] == "apply":
|
|
if arglen <= 1:
|
|
gdb.write("Please specify a thread ID list\n")
|
|
elif arglen <= 2:
|
|
gdb.write("Please specify a command following the thread ID list\n")
|
|
|
|
elif arg[1] == "all":
|
|
for i, tcb in enumerate(utils.ArrayIterator(pidhash, npidhash)):
|
|
if tcb == 0:
|
|
continue
|
|
try:
|
|
gdb.write(f"Thread {i} {tcb['name'].string()}\n")
|
|
except gdb.error and UnicodeDecodeError:
|
|
gdb.write(f"Thread {i}\n")
|
|
|
|
gdb.execute(f"setregs g_pidhash[{i}]->xcp.regs")
|
|
cmd_arg = ""
|
|
for cmd in arg[2:]:
|
|
cmd_arg += cmd + " "
|
|
|
|
gdb.execute(f"{cmd_arg}\n")
|
|
g_registers.restore()
|
|
else:
|
|
threadlist = []
|
|
i = 0
|
|
cmd = ""
|
|
for i in range(1, arglen):
|
|
if arg[i].isnumeric():
|
|
threadlist.append(int(arg[i]))
|
|
else:
|
|
cmd += arg[i] + " "
|
|
|
|
if len(threadlist) == 0 or cmd == "":
|
|
gdb.write("Please specify a thread ID list and command\n")
|
|
else:
|
|
for i in threadlist:
|
|
if i >= npidhash:
|
|
break
|
|
|
|
if pidhash[i] == 0:
|
|
continue
|
|
|
|
try:
|
|
gdb.write(f"Thread {i} {pidhash[i]['name'].string()}\n")
|
|
except gdb.error and UnicodeDecodeError:
|
|
gdb.write(f"Thread {i}\n")
|
|
|
|
gdb.execute(f"setregs g_pidhash[{i}]->xcp.regs")
|
|
gdb.execute(f"{cmd}\n")
|
|
g_registers.restore()
|
|
|
|
else:
|
|
if (
|
|
arg[0].isnumeric()
|
|
and int(arg[0]) < npidhash
|
|
and pidhash[int(arg[0])] != 0
|
|
):
|
|
if pidhash[int(arg[0])]["task_state"] == gdb.parse_and_eval(
|
|
"TSTATE_TASK_RUNNING"
|
|
):
|
|
g_registers.restore()
|
|
else:
|
|
gdb.execute("setregs g_pidhash[%s]->xcp.regs" % arg[0])
|
|
else:
|
|
gdb.write(f"Invalid thread id {arg[0]}\n")
|
|
|
|
|
|
class Nxcontinue(gdb.Command):
|
|
"""Restore the registers and continue the execution"""
|
|
|
|
def __init__(self):
|
|
super().__init__("nxcontinue", gdb.COMMAND_USER)
|
|
if not is_thread_command_supported():
|
|
gdb.execute("define c\n nxcontinue \n end\n")
|
|
gdb.write(
|
|
"\n\x1b[31;1m if use thread command, please don't use 'continue', use 'c' instead !!!\x1b[m\n"
|
|
)
|
|
|
|
def invoke(self, args, from_tty):
|
|
g_registers.restore()
|
|
gdb.execute("continue")
|
|
|
|
|
|
class Nxstep(gdb.Command):
|
|
"""Restore the registers and step the execution"""
|
|
|
|
def __init__(self):
|
|
super().__init__("nxstep", gdb.COMMAND_USER)
|
|
if not is_thread_command_supported():
|
|
gdb.execute("define s\n nxstep \n end\n")
|
|
gdb.write(
|
|
"\x1b[31;1m if use thread command, please don't use 'step', use 's' instead !!!\x1b[m\n"
|
|
)
|
|
|
|
def invoke(self, args, from_tty):
|
|
g_registers.restore()
|
|
gdb.execute("step")
|
|
|
|
|
|
class TaskType(Enum):
|
|
TASK = 0
|
|
PTHREAD = 1
|
|
KTHREAD = 2
|
|
|
|
|
|
class TaskSchedPolicy(Enum):
|
|
FIFO = 0
|
|
RR = 1
|
|
SPORADIC = 2
|
|
|
|
|
|
class TaskState(Enum):
|
|
Invalid = 0
|
|
Waiting_Unlock = auto()
|
|
Ready = auto()
|
|
if utils.get_symbol_value("CONFIG_SMP"):
|
|
Assigned = auto()
|
|
Running = auto()
|
|
Inactive = auto()
|
|
Waiting_Semaphore = auto()
|
|
Waiting_Signal = auto()
|
|
if utils.get_symbol_value("CONFIG_SCHED_EVENTS"):
|
|
Waiting_Event = auto()
|
|
if not utils.get_symbol_value(
|
|
"CONFIG_DISABLE_MQUEUE"
|
|
) or not utils.get_symbol_value("CONFIG_DISABLE_MQUEUE_SYSV"):
|
|
Waiting_MQEmpty = auto()
|
|
Waiting_MQFull = auto()
|
|
if utils.get_symbol_value("CONFIG_PAGING"):
|
|
Waiting_PagingFill = auto()
|
|
if utils.get_symbol_value("CONFIG_SIG_SIGSTOP_ACTION"):
|
|
Stopped = auto()
|
|
|
|
|
|
class Ps(gdb.Command):
|
|
def __init__(self):
|
|
super().__init__("ps", gdb.COMMAND_USER)
|
|
self._fmt_wxl = "{0: <{width}}"
|
|
# By default we align to the right, which respects the nuttx format
|
|
self._fmt_wx = "{0: >{width}}"
|
|
|
|
def parse_and_show_info(self, tcb):
|
|
def get_macro(x):
|
|
return utils.get_symbol_value(x)
|
|
|
|
def eval2str(cls, x):
|
|
return cls(int(x)).name
|
|
|
|
def cast2ptr(x, t):
|
|
return x.cast(utils.lookup_type(t).pointer())
|
|
|
|
pid = int(tcb["pid"])
|
|
group = int(tcb["group"]["tg_pid"])
|
|
priority = int(tcb["sched_priority"])
|
|
|
|
policy = eval2str(
|
|
TaskSchedPolicy,
|
|
(tcb["flags"] & get_macro("TCB_FLAG_POLICY_MASK"))
|
|
>> get_macro("TCB_FLAG_POLICY_SHIFT"),
|
|
)
|
|
|
|
task_type = eval2str(
|
|
TaskType,
|
|
(tcb["flags"] & get_macro("TCB_FLAG_TTYPE_MASK"))
|
|
>> get_macro("TCB_FLAG_TTYPE_SHIFT"),
|
|
)
|
|
|
|
npx = "P" if (tcb["flags"] & get_macro("TCB_FLAG_EXIT_PROCESSING")) else "-"
|
|
|
|
waiter = (
|
|
str(int(cast2ptr(tcb["waitobj"], "mutex_t")["holder"]))
|
|
if tcb["waitobj"]
|
|
and cast2ptr(tcb["waitobj"], "sem_t")["flags"] & get_macro("SEM_TYPE_MUTEX")
|
|
else ""
|
|
)
|
|
state_and_event = eval2str(TaskState, (tcb["task_state"])) + (
|
|
"@Mutex_Holder: " + waiter if waiter else ""
|
|
)
|
|
state_and_event = state_and_event.split("_")
|
|
|
|
# Append a null str here so we don't need to worry
|
|
# about the number of elements as we only want the first two
|
|
state, event = (
|
|
state_and_event if len(state_and_event) > 1 else state_and_event + [""]
|
|
)
|
|
|
|
sigmask = "{0:#0{1}x}".format(
|
|
sum(
|
|
int(tcb["sigprocmask"]["_elem"][i] << i)
|
|
for i in range(get_macro("_SIGSET_NELEM"))
|
|
),
|
|
get_macro("_SIGSET_NELEM") * 8 + 2,
|
|
)[
|
|
2:
|
|
] # exclude "0x"
|
|
|
|
st = Stack(
|
|
utils.get_task_name(tcb),
|
|
hex(tcb["entry"]["pthread"]), # should use main?
|
|
int(tcb["stack_base_ptr"]),
|
|
int(tcb["stack_alloc_ptr"]),
|
|
int(tcb["adj_stack_size"]),
|
|
utils.get_sp(tcb if tcb["task_state"] != TSTATE_TASK_RUNNING else None),
|
|
4,
|
|
)
|
|
|
|
stacksz = st._stack_size
|
|
used = st.max_usage()
|
|
filled = "{0:.2%}".format(st.max_usage() / st._stack_size)
|
|
|
|
cpu = int(tcb["cpu"]) if get_macro("CONFIG_SMP") else 0
|
|
|
|
# For a task we need to display its cmdline arguments, while for a thread we display
|
|
# pointers to its entry and argument
|
|
cmd = ""
|
|
name = utils.get_task_name(tcb)
|
|
|
|
if int(tcb["flags"] & get_macro("TCB_FLAG_TTYPE_MASK")) == int(
|
|
get_macro("TCB_FLAG_TTYPE_PTHREAD")
|
|
):
|
|
entry = tcb["entry"]["main"]
|
|
ptcb = cast2ptr(tcb, "struct pthread_tcb_s")
|
|
arg = ptcb["arg"]
|
|
cmd = " ".join((name, hex(entry), hex(arg)))
|
|
elif tcb["pid"] < get_macro("CONFIG_SMP_NCPUS"):
|
|
# This must be the Idle Tasks, hence we just get its name
|
|
cmd = name
|
|
else:
|
|
# For tasks other than pthreads, hence need to get its command line
|
|
# arguments from
|
|
argv = (
|
|
tcb["stack_alloc_ptr"]
|
|
+ cast2ptr(tcb["stack_alloc_ptr"], "struct tls_info_s")["tl_size"]
|
|
)
|
|
args = []
|
|
parg = argv.cast(gdb.lookup_type("char").pointer().pointer()) + 1
|
|
while parg.dereference():
|
|
args.append(parg.dereference().string())
|
|
parg += 1
|
|
|
|
cmd = " ".join([name] + args)
|
|
|
|
if not utils.get_symbol_value("CONFIG_SCHED_CPULOAD_NONE"):
|
|
load = "{0:.1%}".format(
|
|
int(tcb["ticks"]) / int(gdb.parse_and_eval("g_cpuload_total"))
|
|
)
|
|
else:
|
|
load = "Dis."
|
|
|
|
gdb.write(
|
|
" ".join(
|
|
(
|
|
self._fmt_wx.format(pid, width=5),
|
|
self._fmt_wx.format(group, width=5),
|
|
self._fmt_wx.format(cpu, width=3),
|
|
self._fmt_wx.format(priority, width=3),
|
|
self._fmt_wxl.format(policy, width=8),
|
|
self._fmt_wxl.format(task_type, width=7),
|
|
self._fmt_wx.format(npx, width=3),
|
|
self._fmt_wxl.format(state, width=8),
|
|
self._fmt_wxl.format(event, width=9),
|
|
self._fmt_wxl.format(sigmask, width=8),
|
|
self._fmt_wx.format(stacksz, width=7),
|
|
self._fmt_wx.format(used, width=7),
|
|
self._fmt_wx.format(filled, width=6),
|
|
self._fmt_wx.format(load, width=6),
|
|
cmd,
|
|
)
|
|
)
|
|
)
|
|
gdb.write("\n")
|
|
|
|
def invoke(self, args, from_tty):
|
|
gdb.write(
|
|
" ".join(
|
|
(
|
|
self._fmt_wx.format("PID", width=5),
|
|
self._fmt_wx.format("GROUP", width=5),
|
|
self._fmt_wx.format("CPU", width=3),
|
|
self._fmt_wx.format("PRI", width=3),
|
|
self._fmt_wxl.format("POLICY", width=8),
|
|
self._fmt_wxl.format("TYPE", width=7),
|
|
self._fmt_wx.format("NPX", width=3),
|
|
self._fmt_wxl.format("STATE", width=8),
|
|
self._fmt_wxl.format("EVENT", width=9),
|
|
self._fmt_wxl.format(
|
|
"SIGMASK", width=utils.get_symbol_value("_SIGSET_NELEM") * 8
|
|
),
|
|
self._fmt_wx.format("STACK", width=7),
|
|
self._fmt_wx.format("USED", width=7),
|
|
self._fmt_wx.format("FILLED", width=3),
|
|
self._fmt_wx.format("LOAD", width=6),
|
|
"COMMAND",
|
|
)
|
|
)
|
|
)
|
|
gdb.write("\n")
|
|
|
|
for tcb in utils.get_tcbs():
|
|
self.parse_and_show_info(tcb)
|
|
|
|
|
|
class DeadLock(gdb.Command):
|
|
"""Detect and report if threads have deadlock."""
|
|
|
|
def __init__(self):
|
|
super().__init__("deadlock", gdb.COMMAND_USER)
|
|
|
|
def has_deadlock(self, pid):
|
|
"""Check if the thread has a deadlock"""
|
|
tcb = utils.get_tcb(pid)
|
|
if not tcb or not tcb["waitobj"]:
|
|
return False
|
|
|
|
sem = tcb["waitobj"].cast(utils.lookup_type("sem_t").pointer())
|
|
if not sem["flags"] & SEM_TYPE_MUTEX:
|
|
return False
|
|
|
|
# It's waiting on a mutex
|
|
mutex = tcb["waitobj"].cast(utils.lookup_type("mutex_t").pointer())
|
|
holder = mutex["holder"]
|
|
if holder in self.holders:
|
|
return True
|
|
|
|
self.holders.append(holder)
|
|
return self.has_deadlock(holder)
|
|
|
|
def collect(self, tcbs):
|
|
"""Collect the deadlock information"""
|
|
|
|
detected = []
|
|
collected = []
|
|
for tcb in tcbs:
|
|
self.holders = [] # Holders for this tcb
|
|
pid = tcb["pid"]
|
|
if pid in detected or not self.has_deadlock(tcb["pid"]):
|
|
continue
|
|
|
|
# Deadlock detected
|
|
detected.append(pid)
|
|
detected.extend(self.holders)
|
|
collected.append((pid, self.holders))
|
|
|
|
return collected
|
|
|
|
def diagnose(self, *args, **kwargs):
|
|
collected = self.collect(utils.get_tcbs())
|
|
|
|
return {
|
|
"title": "Deadlock Report",
|
|
"summary": f"{'No' if not collected else len(collected)} deadlocks",
|
|
"command": "deadlock",
|
|
"deadlocks": {int(pid): [i for i in h] for pid, h in collected},
|
|
}
|
|
|
|
def invoke(self, args, from_tty):
|
|
collected = self.collect(utils.get_tcbs())
|
|
if not collected:
|
|
gdb.write("No deadlock detected.")
|
|
return
|
|
|
|
for pid, holders in collected:
|
|
gdb.write(f'Thread {pid} "{utils.get_task_name(pid)}" has deadlocked!\n')
|
|
gdb.write(f" holders: {pid}->")
|
|
gdb.write("->".join(str(pid) for pid in holders))
|
|
gdb.write("\n")
|