mirror of
https://github.com/apache/nuttx.git
synced 2026-05-24 07:46:16 +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
|
||||
from os import listdir, path
|
||||
from os import path
|
||||
|
||||
import gdb
|
||||
|
||||
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):
|
||||
if getattr(register_commands, "registered", False):
|
||||
@@ -53,6 +48,10 @@ def register_commands(event):
|
||||
if isinstance(c, type) and issubclass(c, gdb.Command):
|
||||
c()
|
||||
|
||||
# import utils module
|
||||
utils = importlib.import_module(f"{__package__}.utils")
|
||||
modules = utils.gather_modules(here)
|
||||
|
||||
# Register prefix commands firstly
|
||||
init_gdb_commands("prefix")
|
||||
modules.remove("prefix")
|
||||
@@ -62,7 +61,6 @@ def register_commands(event):
|
||||
for m in modules:
|
||||
init_gdb_commands(m)
|
||||
|
||||
utils = importlib.import_module(f"{__package__}.utils")
|
||||
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}
|
||||
|
||||
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):
|
||||
if sizeof_size_t == 4:
|
||||
align = 11
|
||||
|
||||
@@ -616,6 +616,8 @@ class Ps(gdb.Command):
|
||||
|
||||
|
||||
class DeadLock(gdb.Command):
|
||||
"""Detect and report if threads have deadlock."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("deadlock", gdb.COMMAND_USER)
|
||||
|
||||
@@ -638,9 +640,12 @@ class DeadLock(gdb.Command):
|
||||
self.holders.append(holder)
|
||||
return self.has_deadlock(holder)
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
def collect(self, tcbs):
|
||||
"""Collect the deadlock information"""
|
||||
|
||||
detected = []
|
||||
for tcb in utils.get_tcbs():
|
||||
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"]):
|
||||
@@ -649,13 +654,28 @@ class DeadLock(gdb.Command):
|
||||
# Deadlock detected
|
||||
detected.append(pid)
|
||||
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}->")
|
||||
gdb.write("->".join(str(pid) for pid in self.holders))
|
||||
gdb.write("\n")
|
||||
return collected
|
||||
|
||||
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.")
|
||||
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 shlex
|
||||
from typing import List, Tuple, Union
|
||||
@@ -600,6 +603,39 @@ def check_version():
|
||||
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):
|
||||
"""hexdump address/symbol <size>"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user