mirror of
https://github.com/apache/nuttx.git
synced 2026-05-27 19:36:35 +08:00
tools/gdb: add diagnose commands
Run diagnostic related commands anytime to generate report. E.g `diag report -o systemreport.json` Signed-off-by: xuxingliang <xuxingliang@xiaomi.com>
This commit is contained in:
@@ -21,17 +21,12 @@
|
|||||||
############################################################################
|
############################################################################
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
from os import listdir, path
|
from os import path
|
||||||
|
|
||||||
import gdb
|
import gdb
|
||||||
|
|
||||||
here = path.dirname(path.abspath(__file__))
|
here = path.dirname(path.abspath(__file__))
|
||||||
|
|
||||||
# Scan dir to get all modules available
|
|
||||||
modules = [
|
|
||||||
path.splitext(path.basename(f))[0] for f in listdir(here) if f.endswith(".py")
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def register_commands(event):
|
def register_commands(event):
|
||||||
if getattr(register_commands, "registered", False):
|
if getattr(register_commands, "registered", False):
|
||||||
@@ -53,6 +48,10 @@ def register_commands(event):
|
|||||||
if isinstance(c, type) and issubclass(c, gdb.Command):
|
if isinstance(c, type) and issubclass(c, gdb.Command):
|
||||||
c()
|
c()
|
||||||
|
|
||||||
|
# import utils module
|
||||||
|
utils = importlib.import_module(f"{__package__}.utils")
|
||||||
|
modules = utils.gather_modules(here)
|
||||||
|
|
||||||
# Register prefix commands firstly
|
# Register prefix commands firstly
|
||||||
init_gdb_commands("prefix")
|
init_gdb_commands("prefix")
|
||||||
modules.remove("prefix")
|
modules.remove("prefix")
|
||||||
@@ -62,7 +61,6 @@ def register_commands(event):
|
|||||||
for m in modules:
|
for m in modules:
|
||||||
init_gdb_commands(m)
|
init_gdb_commands(m)
|
||||||
|
|
||||||
utils = importlib.import_module(f"{__package__}.utils")
|
|
||||||
utils.check_version()
|
utils.check_version()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
############################################################################
|
||||||
|
# tools/gdb/nuttx_gdb/diagnose.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 gdb
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
|
class DiagnosePrefix(gdb.Command):
|
||||||
|
"""Diagnostic related commands."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("diagnose", gdb.COMMAND_USER, prefix=True)
|
||||||
|
|
||||||
|
|
||||||
|
class DiagnoseReport(gdb.Command):
|
||||||
|
"""Run diagnostics to generate reports."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("diagnose report", gdb.COMMAND_USER)
|
||||||
|
|
||||||
|
def invoke(self, args, from_tty):
|
||||||
|
parser = argparse.ArgumentParser(description=self.__doc__)
|
||||||
|
parser.add_argument(
|
||||||
|
"-o",
|
||||||
|
"--output",
|
||||||
|
type=str,
|
||||||
|
help="report output file name",
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
args = parser.parse_args(gdb.string_to_argv(args))
|
||||||
|
except SystemExit:
|
||||||
|
return
|
||||||
|
|
||||||
|
reportfile = (
|
||||||
|
args.output
|
||||||
|
if args.output
|
||||||
|
else gdb.objfiles()[0].filename + ".diagnostics.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
modules = utils.gather_modules()
|
||||||
|
modules.remove("prefix")
|
||||||
|
modules.remove("__init__")
|
||||||
|
|
||||||
|
commands = utils.gather_gdbcommands(modules=modules)
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for clz in commands:
|
||||||
|
if hasattr(clz, "diagnose"):
|
||||||
|
command = clz()
|
||||||
|
gdb.write(f"Run command: {clz.__name__}\n")
|
||||||
|
results.append(command.diagnose())
|
||||||
|
|
||||||
|
gdb.write(f"Write report to {reportfile}\n")
|
||||||
|
with open(reportfile, "w") as f:
|
||||||
|
f.write(utils.jsonify(results, indent=4))
|
||||||
@@ -762,6 +762,14 @@ class Memleak(gdb.Command):
|
|||||||
|
|
||||||
return {"simple": args.simple, "detail": args.detail}
|
return {"simple": args.simple, "detail": args.detail}
|
||||||
|
|
||||||
|
def diagnose(self, *args, **kwargs):
|
||||||
|
output = gdb.execute("memleak", to_string=True)
|
||||||
|
return {
|
||||||
|
"title": "Memory Leak Report",
|
||||||
|
"command": "memleak",
|
||||||
|
"details": output,
|
||||||
|
}
|
||||||
|
|
||||||
def invoke(self, args, from_tty):
|
def invoke(self, args, from_tty):
|
||||||
if sizeof_size_t == 4:
|
if sizeof_size_t == 4:
|
||||||
align = 11
|
align = 11
|
||||||
|
|||||||
@@ -616,6 +616,8 @@ class Ps(gdb.Command):
|
|||||||
|
|
||||||
|
|
||||||
class DeadLock(gdb.Command):
|
class DeadLock(gdb.Command):
|
||||||
|
"""Detect and report if threads have deadlock."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("deadlock", gdb.COMMAND_USER)
|
super().__init__("deadlock", gdb.COMMAND_USER)
|
||||||
|
|
||||||
@@ -638,9 +640,12 @@ class DeadLock(gdb.Command):
|
|||||||
self.holders.append(holder)
|
self.holders.append(holder)
|
||||||
return self.has_deadlock(holder)
|
return self.has_deadlock(holder)
|
||||||
|
|
||||||
def invoke(self, args, from_tty):
|
def collect(self, tcbs):
|
||||||
|
"""Collect the deadlock information"""
|
||||||
|
|
||||||
detected = []
|
detected = []
|
||||||
for tcb in utils.get_tcbs():
|
collected = []
|
||||||
|
for tcb in tcbs:
|
||||||
self.holders = [] # Holders for this tcb
|
self.holders = [] # Holders for this tcb
|
||||||
pid = tcb["pid"]
|
pid = tcb["pid"]
|
||||||
if pid in detected or not self.has_deadlock(tcb["pid"]):
|
if pid in detected or not self.has_deadlock(tcb["pid"]):
|
||||||
@@ -649,13 +654,28 @@ class DeadLock(gdb.Command):
|
|||||||
# Deadlock detected
|
# Deadlock detected
|
||||||
detected.append(pid)
|
detected.append(pid)
|
||||||
detected.extend(self.holders)
|
detected.extend(self.holders)
|
||||||
gdb.write(f'Thread {pid} "{utils.get_task_name(tcb)}" has deadlocked!\n')
|
collected.append((pid, self.holders))
|
||||||
|
|
||||||
gdb.write(f" holders: {pid}->")
|
return collected
|
||||||
gdb.write("->".join(str(pid) for pid in self.holders))
|
|
||||||
gdb.write("\n")
|
|
||||||
|
|
||||||
if not detected:
|
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.")
|
gdb.write("No deadlock detected.")
|
||||||
|
return
|
||||||
|
|
||||||
gdb.write("\n")
|
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")
|
||||||
|
|||||||
@@ -20,6 +20,9 @@
|
|||||||
#
|
#
|
||||||
############################################################################
|
############################################################################
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
from typing import List, Tuple, Union
|
from typing import List, Tuple, Union
|
||||||
@@ -600,6 +603,39 @@ def check_version():
|
|||||||
suppress_cli_notifications(state)
|
suppress_cli_notifications(state)
|
||||||
|
|
||||||
|
|
||||||
|
def gather_modules(dir=None) -> List[str]:
|
||||||
|
dir = os.path.normpath(dir) if dir else os.path.dirname(__file__)
|
||||||
|
return [
|
||||||
|
os.path.splitext(os.path.basename(f))[0]
|
||||||
|
for f in os.listdir(dir)
|
||||||
|
if f.endswith(".py")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def gather_gdbcommands(modules=None, path=None) -> List[gdb.Command]:
|
||||||
|
modules = modules or gather_modules(path)
|
||||||
|
commands = []
|
||||||
|
for m in modules:
|
||||||
|
module = importlib.import_module(f"{__package__}.{m}")
|
||||||
|
for c in module.__dict__.values():
|
||||||
|
if isinstance(c, type) and issubclass(c, gdb.Command):
|
||||||
|
commands.append(c)
|
||||||
|
return commands
|
||||||
|
|
||||||
|
|
||||||
|
def jsonify(obj, indent=None):
|
||||||
|
if not obj:
|
||||||
|
return "{}"
|
||||||
|
|
||||||
|
def dumper(obj):
|
||||||
|
try:
|
||||||
|
return str(obj) if isinstance(obj, gdb.Value) else obj.toJSON()
|
||||||
|
except Exception:
|
||||||
|
return obj.__dict__
|
||||||
|
|
||||||
|
return json.dumps(obj, default=dumper, indent=indent)
|
||||||
|
|
||||||
|
|
||||||
class Hexdump(gdb.Command):
|
class Hexdump(gdb.Command):
|
||||||
"""hexdump address/symbol <size>"""
|
"""hexdump address/symbol <size>"""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user