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:
xuxingliang
2024-09-09 14:04:18 +08:00
committed by Xiang Xiao
parent a2da6b94f0
commit 5d86bee5c7
5 changed files with 155 additions and 15 deletions
+5 -7
View File
@@ -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()
+78
View File
@@ -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))
+8
View File
@@ -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
+28 -8
View File
@@ -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")
+36
View File
@@ -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>"""