mirror of
https://github.com/apache/nuttx.git
synced 2026-05-28 11:56:10 +08:00
tools: rename tools/gdb/nuttxgdb to tools/pynuttx/nxgdb
Signed-off-by: yinshengkai <yinshengkai@xiaomi.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
dist/
|
||||
nxgdb.egg-info/
|
||||
@@ -0,0 +1,37 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/gdbinit.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 sys
|
||||
from os import path
|
||||
|
||||
here = path.dirname(path.abspath(__file__))
|
||||
|
||||
if __name__ == "__main__":
|
||||
if here not in sys.path:
|
||||
sys.path.insert(0, here)
|
||||
|
||||
if "nxgdb" in sys.modules:
|
||||
for key in list(sys.modules.keys()):
|
||||
if key.startswith("nxgdb"):
|
||||
del sys.modules[key]
|
||||
|
||||
import nxgdb # noqa: F401
|
||||
@@ -0,0 +1,87 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/__init__.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 importlib
|
||||
import traceback
|
||||
from os import path
|
||||
|
||||
import gdb
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
gdb.write("Use nuttx/tools/pynuttx/gdbinit.py instead")
|
||||
sys.exit(1)
|
||||
|
||||
here = path.dirname(path.abspath(__file__))
|
||||
|
||||
|
||||
def register_commands(event):
|
||||
if getattr(register_commands, "registered", False):
|
||||
return
|
||||
|
||||
register_commands.registered = True
|
||||
|
||||
gdb.write(f"Registering NuttX GDB commands from {here}\n")
|
||||
gdb.execute("set pagination off")
|
||||
gdb.write("set pagination off\n")
|
||||
gdb.execute("set python print-stack full")
|
||||
gdb.write("set python print-stack full\n")
|
||||
gdb.execute('handle SIGUSR1 "nostop" "pass" "noprint"')
|
||||
gdb.write('"handle SIGUSR1 "nostop" "pass" "noprint"\n')
|
||||
|
||||
def init_gdb_commands(m: str):
|
||||
try:
|
||||
module = importlib.import_module(f"{__package__}.{m}")
|
||||
except Exception as e:
|
||||
gdb.write(f"\x1b[31;1mIgnore module: {m}, error: {e}\n\x1b[m")
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
for c in module.__dict__.values():
|
||||
if isinstance(c, type) and issubclass(c, gdb.Command):
|
||||
try:
|
||||
c()
|
||||
except Exception as e:
|
||||
gdb.write(f"\x1b[31;1mIgnore command: {c}, e: {e}\n\x1b[m")
|
||||
|
||||
# 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")
|
||||
modules.remove("__init__")
|
||||
|
||||
# Register all other modules
|
||||
for m in modules:
|
||||
init_gdb_commands(m)
|
||||
|
||||
utils.check_version()
|
||||
|
||||
|
||||
if len(gdb.objfiles()) == 0:
|
||||
gdb.write("No objectfile, defer to register NuttX commands.\n")
|
||||
gdb.events.new_objfile.connect(register_commands)
|
||||
else:
|
||||
register_commands(None)
|
||||
@@ -0,0 +1,64 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/debug.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 DebugPy(gdb.Command):
|
||||
"""Start debugpy server, so we can debug python code from IDE like VSCode"""
|
||||
|
||||
def __init__(self):
|
||||
debugpy = utils.import_check("debugpy", errmsg="Please pip install debugpy")
|
||||
if not debugpy:
|
||||
return
|
||||
|
||||
self.debugpy = debugpy
|
||||
super().__init__("debugpy", gdb.COMMAND_USER)
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
debugpy = self.debugpy
|
||||
if debugpy.is_client_connected():
|
||||
gdb.write("debugpy is already running.\n")
|
||||
return
|
||||
|
||||
parser = argparse.ArgumentParser(description=DebugPy.__doc__)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--port",
|
||||
default=5678,
|
||||
type=int,
|
||||
help="Server listening port",
|
||||
)
|
||||
|
||||
try:
|
||||
args = parser.parse_args(gdb.string_to_argv(args))
|
||||
except SystemExit:
|
||||
return
|
||||
|
||||
debugpy.listen(args.port)
|
||||
gdb.write(f"Waiting for connection at localhost:{args.port}\n")
|
||||
debugpy.wait_for_client()
|
||||
gdb.write("Debugger connected.\n")
|
||||
@@ -0,0 +1,93 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/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()
|
||||
name = clz.__name__.lower()
|
||||
gdb.write(f"Run command: {name}\n")
|
||||
try:
|
||||
result = command.diagnose()
|
||||
except gdb.error as e:
|
||||
result = {
|
||||
"title": f"Command {name} failed",
|
||||
"summary": "Command execution failed",
|
||||
"result": "info",
|
||||
"command": name,
|
||||
"message": str(e),
|
||||
}
|
||||
|
||||
gdb.write(f"Failed: {e}\n")
|
||||
|
||||
result.setdefault("command", name)
|
||||
results.append(result)
|
||||
|
||||
gdb.write(f"Write report to {reportfile}\n")
|
||||
with open(reportfile, "w") as f:
|
||||
f.write(utils.jsonify(results, indent=4))
|
||||
@@ -0,0 +1,71 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/dmesg.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 gdb
|
||||
|
||||
from . import utils
|
||||
|
||||
CONFIG_RAMLOG_SYSLOG = utils.get_symbol_value("CONFIG_RAMLOG_SYSLOG")
|
||||
|
||||
|
||||
class Dmesg(gdb.Command):
|
||||
def __init__(self):
|
||||
if CONFIG_RAMLOG_SYSLOG:
|
||||
super().__init__("dmesg", gdb.COMMAND_USER)
|
||||
|
||||
def _get_buf(self):
|
||||
sysdev = utils.gdb_eval_or_none("g_sysdev")
|
||||
if not sysdev:
|
||||
gdb.write("RAM log not available.\n")
|
||||
return None
|
||||
|
||||
rl_header = sysdev["rl_header"]
|
||||
rl_bufsize = sysdev["rl_bufsize"]
|
||||
|
||||
offset = rl_header["rl_head"] % rl_bufsize # Currently writing to this offset
|
||||
tail = rl_bufsize - offset # Total size till buffer end.
|
||||
rl_buffer = int(rl_header["rl_buffer"].address) # rl_buffer is a char array
|
||||
|
||||
inf = gdb.selected_inferior()
|
||||
buf = bytes(inf.read_memory(offset + rl_buffer, tail))
|
||||
buf = buf.lstrip(b"\0") # Remove leading NULLs
|
||||
buf += bytes(inf.read_memory(rl_buffer, offset))
|
||||
buf = buf.replace(
|
||||
b"\0", "␀".encode("utf-8")
|
||||
) # NULL is valid utf-8 but not printable
|
||||
|
||||
return buf.decode("utf-8", errors="replace")
|
||||
|
||||
def diagnose(self, *args, **kwargs):
|
||||
buf = self._get_buf()
|
||||
return {
|
||||
"title": "RAM log",
|
||||
"summary": (
|
||||
f"Buffer length:{len(buf)} bytes" if buf else "Buffer not available"
|
||||
),
|
||||
"result": "info",
|
||||
"command": "dmesg",
|
||||
"message": buf,
|
||||
}
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
gdb.write(f"{self._get_buf()}\n")
|
||||
@@ -0,0 +1,461 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/fs.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 enum
|
||||
from typing import Generator, Tuple
|
||||
|
||||
import gdb
|
||||
|
||||
from . import utils
|
||||
from .protocols.fs import File, Inode
|
||||
from .protocols.thread import Tcb
|
||||
|
||||
FSNODEFLAG_TYPE_MASK = utils.get_symbol_value("FSNODEFLAG_TYPE_MASK")
|
||||
|
||||
CONFIG_PSEUDOFS_FILE = utils.get_symbol_value("CONFIG_PSEUDOFS_FILE")
|
||||
CONFIG_PSEUDOFS_ATTRIBUTES = utils.get_symbol_value("CONFIG_PSEUDOFS_ATTRIBUTES")
|
||||
|
||||
CONFIG_FS_BACKTRACE = utils.get_symbol_value("CONFIG_FS_BACKTRACE")
|
||||
CONFIG_NFILE_DESCRIPTORS_PER_BLOCK = int(
|
||||
utils.get_symbol_value("CONFIG_NFILE_DESCRIPTORS_PER_BLOCK")
|
||||
)
|
||||
CONFIG_FS_SHMFS = utils.get_symbol_value("CONFIG_FS_SHMFS")
|
||||
|
||||
g_special_inodes = {} # Map of the special inodes including epoll, inotify, etc.
|
||||
|
||||
|
||||
class InodeType(enum.Enum):
|
||||
# define FSNODEFLAG_TYPE_PSEUDODIR 0x00000000 /* Pseudo dir (default) */
|
||||
# define FSNODEFLAG_TYPE_DRIVER 0x00000001 /* Character driver */
|
||||
# define FSNODEFLAG_TYPE_BLOCK 0x00000002 /* Block driver */
|
||||
# define FSNODEFLAG_TYPE_MOUNTPT 0x00000003 /* Mount point */
|
||||
# define FSNODEFLAG_TYPE_NAMEDSEM 0x00000004 /* Named semaphore */
|
||||
# define FSNODEFLAG_TYPE_MQUEUE 0x00000005 /* Message Queue */
|
||||
# define FSNODEFLAG_TYPE_SHM 0x00000006 /* Shared memory region */
|
||||
# define FSNODEFLAG_TYPE_MTD 0x00000007 /* Named MTD driver */
|
||||
# define FSNODEFLAG_TYPE_SOFTLINK 0x00000008 /* Soft link */
|
||||
# define FSNODEFLAG_TYPE_SOCKET 0x00000009 /* Socket */
|
||||
# define FSNODEFLAG_TYPE_PIPE 0x0000000a /* Pipe */
|
||||
# define FSNODEFLAG_TYPE_NAMEDEVENT 0x0000000b /* Named event group */
|
||||
PSEUDODIR = 0
|
||||
DRIVER = 1
|
||||
BLOCK = 2
|
||||
MOUNTPT = 3
|
||||
NAMEDSEM = 4
|
||||
MQUEUE = 5
|
||||
SHM = 6
|
||||
MTD = 7
|
||||
SOFTLINK = 8
|
||||
SOCKET = 9
|
||||
PIPE = 10
|
||||
NAMEDEVENT = 11
|
||||
UNKNOWN = 12
|
||||
|
||||
|
||||
def get_inode_name(inode: Inode):
|
||||
if not inode:
|
||||
return ""
|
||||
|
||||
def special_inode_name(inode):
|
||||
global g_special_inodes
|
||||
if not g_special_inodes:
|
||||
g_special_inodes = {}
|
||||
for special in (
|
||||
"perf",
|
||||
"timerfd",
|
||||
"signalfd",
|
||||
"dir",
|
||||
"inotify",
|
||||
"epoll",
|
||||
"eventfd",
|
||||
"sock",
|
||||
):
|
||||
value = utils.gdb_eval_or_none(f"g_{special}_inode")
|
||||
if value:
|
||||
g_special_inodes[special] = value.address
|
||||
|
||||
for name, value in g_special_inodes.items():
|
||||
if value == inode:
|
||||
return name
|
||||
|
||||
return None
|
||||
|
||||
if name := special_inode_name(inode):
|
||||
return name
|
||||
|
||||
ptr = inode.i_name.cast(gdb.lookup_type("char").pointer())
|
||||
return ptr.string()
|
||||
|
||||
|
||||
def inode_getpath(inode: Inode):
|
||||
"""get path fron inode"""
|
||||
if not inode:
|
||||
return ""
|
||||
|
||||
name = get_inode_name(inode)
|
||||
|
||||
if inode.i_parent:
|
||||
return inode_getpath(inode.i_parent) + "/" + name
|
||||
|
||||
return name
|
||||
|
||||
|
||||
def inode_gettype(inode: Inode) -> InodeType:
|
||||
if not inode:
|
||||
return InodeType.UNKNOWN
|
||||
|
||||
type = int(inode.i_flags & FSNODEFLAG_TYPE_MASK)
|
||||
|
||||
# check if it's a valid type in InodeType
|
||||
if type in [e.value for e in InodeType]:
|
||||
return InodeType(type)
|
||||
|
||||
return InodeType.UNKNOWN
|
||||
|
||||
|
||||
def get_file(tcb: Tcb, fd):
|
||||
group = tcb.group
|
||||
filelist = group.tg_filelist
|
||||
fl_files = filelist.fl_files
|
||||
fl_rows = filelist.fl_rows
|
||||
|
||||
row = fd // CONFIG_NFILE_DESCRIPTORS_PER_BLOCK
|
||||
col = fd % CONFIG_NFILE_DESCRIPTORS_PER_BLOCK
|
||||
|
||||
if row >= fl_rows:
|
||||
return None
|
||||
|
||||
return fl_files[row][col]
|
||||
|
||||
|
||||
def foreach_inode(root=None, path="") -> Generator[Tuple[Inode, str], None, None]:
|
||||
node: Inode = root or utils.parse_and_eval("g_root_inode").i_child
|
||||
while node:
|
||||
newpath = path + "/" + get_inode_name(node)
|
||||
yield node, newpath
|
||||
if node.i_child:
|
||||
yield from foreach_inode(node.i_child, newpath)
|
||||
node = node.i_peer
|
||||
|
||||
|
||||
def foreach_file(tcb: Tcb):
|
||||
"""Iterate over all file descriptors in a tcb"""
|
||||
group = tcb.group
|
||||
filelist = group.tg_filelist
|
||||
fl_files = filelist.fl_files
|
||||
fl_rows = filelist.fl_rows
|
||||
|
||||
for row in range(fl_rows):
|
||||
for col in range(CONFIG_NFILE_DESCRIPTORS_PER_BLOCK):
|
||||
file = fl_files[row][col]
|
||||
|
||||
if not file or not file.f_inode:
|
||||
continue
|
||||
|
||||
fd = row * CONFIG_NFILE_DESCRIPTORS_PER_BLOCK + col
|
||||
|
||||
yield fd, file
|
||||
|
||||
|
||||
class Fdinfo(gdb.Command):
|
||||
"""Dump fd info information of process"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("fdinfo", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION)
|
||||
self.total_fd_count = 0
|
||||
|
||||
def print_file_info(self, fd, file: File, formatter: str):
|
||||
backtrace_formatter = "{0:<5} {1:<36} {2}"
|
||||
|
||||
oflags = int(file.f_oflags)
|
||||
pos = int(file.f_pos)
|
||||
path = inode_getpath(file.f_inode)
|
||||
|
||||
output = []
|
||||
if CONFIG_FS_BACKTRACE:
|
||||
backtrace = utils.Backtrace(
|
||||
utils.ArrayIterator(file.f_backtrace), formatter=backtrace_formatter
|
||||
)
|
||||
|
||||
output.append(
|
||||
formatter.format(
|
||||
fd, hex(file.address), oflags, pos, path, backtrace.formatted[0]
|
||||
)
|
||||
)
|
||||
output.extend(
|
||||
formatter.format("", "", "", "", "", bt)
|
||||
for bt in backtrace.formatted[1:]
|
||||
)
|
||||
output.append("") # separate each backtrace
|
||||
else:
|
||||
output = [
|
||||
formatter.format(fd, hex(file.address), hex(oflags), pos, path, "")
|
||||
]
|
||||
|
||||
gdb.write("\n".join(output).rstrip()) # strip trailing spaces
|
||||
gdb.write("\n")
|
||||
|
||||
def print_fdinfo_by_tcb(self, tcb):
|
||||
"""print fdlist from tcb"""
|
||||
gdb.write(f"PID: {tcb['pid']}\n")
|
||||
group = tcb.group
|
||||
|
||||
if not group:
|
||||
return
|
||||
|
||||
if group in self.processed_groups:
|
||||
return
|
||||
|
||||
self.processed_groups.add(group)
|
||||
|
||||
headers = ["FD", "FILEP", "OFLAGS", "POS", "PATH", "BACKTRACE"]
|
||||
formatter = "{:<4}{:<12}{:<12}{:<10}{:<48}{:<50}"
|
||||
gdb.write(formatter.format(*headers) + "\n")
|
||||
|
||||
fd_count = 0
|
||||
for fd, file in foreach_file(tcb):
|
||||
self.print_file_info(fd, file, formatter)
|
||||
fd_count += 1
|
||||
self.total_fd_count += fd_count
|
||||
|
||||
gdb.write("\n")
|
||||
|
||||
def diagnose(self, *args, **kwargs):
|
||||
output = gdb.execute("fdinfo", to_string=True)
|
||||
|
||||
return {
|
||||
"title": "fdinfo report",
|
||||
"summary": f"Total files opened:{self.total_fd_count}",
|
||||
"result": "info",
|
||||
"command": "fdinfo",
|
||||
"message": output,
|
||||
}
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Get fdinfo for a process or all processes."
|
||||
)
|
||||
parser.add_argument("-p", "--pid", type=int, help="Optional process ID")
|
||||
|
||||
try:
|
||||
args = parser.parse_args(gdb.string_to_argv(arg))
|
||||
except SystemExit:
|
||||
gdb.write("Invalid arguments.\n")
|
||||
return
|
||||
|
||||
self.processed_groups = set()
|
||||
self.total_fd_count = 0
|
||||
tcbs = [utils.get_tcb(args.pid)] if args.pid else utils.get_tcbs()
|
||||
for tcb in tcbs:
|
||||
self.print_fdinfo_by_tcb(tcb)
|
||||
|
||||
|
||||
class Mount(gdb.Command):
|
||||
def __init__(self):
|
||||
if not utils.get_symbol_value("CONFIG_DISABLE_MOUNTPOINT"):
|
||||
super().__init__("mount", gdb.COMMAND_USER)
|
||||
self.mount_count = 0
|
||||
|
||||
def diagnose(self, *args, **kwargs):
|
||||
output = gdb.execute("mount", to_string=True)
|
||||
|
||||
return {
|
||||
"title": "File system mount information",
|
||||
"summary": f"Total {self.mount_count} mount points",
|
||||
"command": "mount",
|
||||
"result": "info",
|
||||
"message": output or "No mount",
|
||||
}
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
self.mount_count = 0
|
||||
nodes = filter(
|
||||
lambda x: inode_gettype(x[0]) == InodeType.MOUNTPT, foreach_inode()
|
||||
)
|
||||
for node, path in nodes:
|
||||
statfs = node.u.i_mops.statfs
|
||||
funcname = gdb.block_for_pc(int(statfs)).function.print_name
|
||||
fstype = funcname.split("_")[0]
|
||||
gdb.write(" %s type %s\n" % (path, fstype))
|
||||
self.mount_count += 1
|
||||
|
||||
|
||||
class ForeachInode(gdb.Command):
|
||||
"""Dump each inode info"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("foreach inode", gdb.COMMAND_USER)
|
||||
self.level = 4096
|
||||
|
||||
def get_root_inode(self, addr_or_expr):
|
||||
try:
|
||||
return utils.Value(int(addr_or_expr, 0)).cast(
|
||||
gdb.lookup_type("struct inode").pointer()
|
||||
)
|
||||
except ValueError:
|
||||
return utils.gdb_eval_or_none(addr_or_expr)
|
||||
|
||||
def parse_arguments(self, argv):
|
||||
parser = argparse.ArgumentParser(description="foreach inode command")
|
||||
parser.add_argument(
|
||||
"-L",
|
||||
"--level",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Only render the tree to a specific depth",
|
||||
)
|
||||
parser.add_argument(
|
||||
"addr_or_expr",
|
||||
type=str,
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="set the start inode to be tranversed",
|
||||
)
|
||||
try:
|
||||
args = parser.parse_args(argv)
|
||||
except SystemExit:
|
||||
return None
|
||||
return {
|
||||
"level": args.level if args.level else 4096,
|
||||
"root_inode": (
|
||||
self.get_root_inode(args.addr_or_expr)
|
||||
if args.addr_or_expr
|
||||
else utils.gdb_eval_or_none("g_root_inode")
|
||||
),
|
||||
}
|
||||
|
||||
def print_inode_info(self, node: Inode, level, prefix):
|
||||
if level > self.level:
|
||||
return
|
||||
while node:
|
||||
if node.i_peer:
|
||||
initial_indent = prefix + "├── "
|
||||
subsequent_indent = prefix + "│ "
|
||||
newprefix = prefix + "│ "
|
||||
else:
|
||||
initial_indent = prefix + "└── "
|
||||
subsequent_indent = prefix + " "
|
||||
newprefix = prefix + " "
|
||||
gdb.write(
|
||||
"%s [%s], %s, %s\n"
|
||||
% (initial_indent, get_inode_name(node), node.i_ino, node)
|
||||
)
|
||||
gdb.write(
|
||||
"%s i_crefs: %s, i_flags: %s, i_private: %s\n"
|
||||
% (
|
||||
subsequent_indent,
|
||||
node.i_crefs,
|
||||
node.i_flags,
|
||||
node.i_private,
|
||||
)
|
||||
)
|
||||
if CONFIG_PSEUDOFS_FILE:
|
||||
gdb.write("%s i_size: %s\n" % (subsequent_indent, node.i_size))
|
||||
if CONFIG_PSEUDOFS_ATTRIBUTES:
|
||||
gdb.write(
|
||||
"%s i_mode: %s, i_owner: %s, i_group: %s\n"
|
||||
% (
|
||||
subsequent_indent,
|
||||
node.i_mode,
|
||||
node.i_owner,
|
||||
node.i_group,
|
||||
)
|
||||
)
|
||||
gdb.write(
|
||||
"%s i_atime: %s, i_mtime: %s, i_ctime: %s\n"
|
||||
% (
|
||||
subsequent_indent,
|
||||
node.i_atime,
|
||||
node.i_mtime,
|
||||
node.i_ctime,
|
||||
)
|
||||
)
|
||||
if node.i_child:
|
||||
self.print_inode_info(node.i_child, level + 1, newprefix)
|
||||
node = node.i_peer
|
||||
|
||||
def diagnose(self, *args, **kwargs):
|
||||
output = gdb.execute("foreach inode", to_string=True)
|
||||
|
||||
return {
|
||||
"title": "File node information",
|
||||
"summary": "inode formation dump",
|
||||
"command": "foreach inode",
|
||||
"result": "info",
|
||||
"message": output,
|
||||
}
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
arg = self.parse_arguments(args.split(" "))
|
||||
if not arg:
|
||||
return
|
||||
self.level = arg["level"]
|
||||
self.print_inode_info(arg["root_inode"], 1, "")
|
||||
|
||||
|
||||
class InfoShmfs(gdb.Command):
|
||||
"""Show share memory usage"""
|
||||
|
||||
def __init__(self):
|
||||
if CONFIG_FS_SHMFS:
|
||||
super().__init__("info shm", gdb.COMMAND_USER)
|
||||
self.total_size = 0
|
||||
self.block_count = 0
|
||||
|
||||
def shm_filter(self, node: Inode, path):
|
||||
if inode_gettype(node) != InodeType.SHM:
|
||||
return
|
||||
|
||||
obj = node.i_private.cast(gdb.lookup_type("struct shmfs_object_s").pointer())
|
||||
length = obj.length
|
||||
paddr = obj.paddr
|
||||
print(f" {path} memsize: {length}, paddr: {paddr}")
|
||||
|
||||
self.total_size += length / 1024
|
||||
self.block_count += 1
|
||||
|
||||
def diagnose(self, *args, **kwargs):
|
||||
output = gdb.execute("info shm", to_string=True)
|
||||
|
||||
return {
|
||||
"title": "Share memory usage",
|
||||
"summary": f"Total used:{self.total_size}kB, {self.block_count}blocks",
|
||||
"result": "info",
|
||||
"command": "info shm",
|
||||
"message": output or "No InfoShmfs",
|
||||
}
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
self.total_size = 0
|
||||
self.block_count = 0
|
||||
nodes = filter(lambda x: inode_gettype(x[0]) == InodeType.SHM, foreach_inode())
|
||||
for node, path in nodes:
|
||||
obj = node.i_private.cast(
|
||||
gdb.lookup_type("struct shmfs_object_s").pointer()
|
||||
)
|
||||
length = obj.length
|
||||
paddr = obj.paddr
|
||||
print(f" {path} memsize: {length}, paddr: {paddr}")
|
||||
|
||||
self.total_size += length / 1024
|
||||
self.block_count += 1
|
||||
@@ -0,0 +1,160 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/gcore.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 os
|
||||
import shutil
|
||||
|
||||
import gdb
|
||||
|
||||
from . import utils
|
||||
|
||||
|
||||
def create_file_with_size(filename, size):
|
||||
with open(filename, "wb") as f:
|
||||
f.write(b"\0" * size)
|
||||
|
||||
|
||||
def parse_args(args):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-o", "--output", help="Gcore output file")
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--objcopy",
|
||||
help="Select the appropriate architecture for the objcopy tool",
|
||||
type=str,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--trust-readonly",
|
||||
help="Trust readonly section from elf, bypass read from device.",
|
||||
action="store_true",
|
||||
default=True,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--no-trust-readonly",
|
||||
help="Do not trust readonly section from elf, read from device.",
|
||||
action="store_false",
|
||||
dest="trust_readonly",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--memrange",
|
||||
type=str,
|
||||
)
|
||||
return parser.parse_args(args)
|
||||
|
||||
|
||||
class NXGcore(gdb.Command):
|
||||
def __init__(self):
|
||||
self.tempfile = utils.import_check(
|
||||
"tempfile",
|
||||
errmsg="No tempfile module found, please try gdb-multiarch instead.\n",
|
||||
)
|
||||
|
||||
if not self.tempfile:
|
||||
return
|
||||
|
||||
super().__init__("nxgcore", gdb.COMMAND_USER)
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
try:
|
||||
args = parse_args(gdb.string_to_argv(args))
|
||||
except SystemExit:
|
||||
return
|
||||
|
||||
objfile = gdb.current_progspace().objfiles()[0]
|
||||
elffile = objfile.filename
|
||||
tmpfile = self.tempfile.NamedTemporaryFile(suffix=".elf")
|
||||
|
||||
# Create temporary ELF file with sections for each memory region
|
||||
|
||||
shutil.copy(elffile, tmpfile.name)
|
||||
|
||||
# If no parameters are passed in
|
||||
|
||||
if args.output:
|
||||
corefile = args.output
|
||||
else:
|
||||
corefile = elffile + ".core"
|
||||
|
||||
# Obtain memory range from macros or parameters
|
||||
|
||||
if args.memrange:
|
||||
memregion = args.memrange
|
||||
else:
|
||||
memregion = str(utils.get_symbol_value("CONFIG_BOARD_MEMORY_RANGE"))
|
||||
if len(memregion) < 3:
|
||||
print(
|
||||
"Please set CONFIG_BOARD_MEMORY_RANGE.\n"
|
||||
"Memory range of board. format: <start>,<end>,<flags>,....\n"
|
||||
"start: start address of memory range\n"
|
||||
"end: end address of memory range\n"
|
||||
"flags: Readable 0x1, writable 0x2, executable 0x4\n"
|
||||
"example:0x1000,0x2000,0x1,0x2000,0x3000,0x3,0x3000,0x4000,0x7"
|
||||
)
|
||||
return
|
||||
|
||||
# Resolve memory range and shell run commands
|
||||
|
||||
values = memregion.replace('"', "").split(",")
|
||||
|
||||
for i in range(0, len(values), 3):
|
||||
start = int(values[i], 16)
|
||||
end = int(values[i + 1], 16)
|
||||
|
||||
# Create a random section name
|
||||
|
||||
section = tmpfile.name + f"{i // 3}"
|
||||
|
||||
# Add objcopy insertion segment command and modify segment start address command
|
||||
|
||||
create_file_with_size(section, end - start)
|
||||
|
||||
os.system(
|
||||
f"{args.objcopy} --add-section {section}={section} "
|
||||
f"--set-section-flags {section}=noload,alloc {tmpfile.name}"
|
||||
)
|
||||
os.system(
|
||||
f"{args.objcopy} --change-section-address "
|
||||
f"{section}={hex(start)} {tmpfile.name}"
|
||||
)
|
||||
|
||||
os.remove(section)
|
||||
|
||||
gdb.execute(f"file {tmpfile.name}")
|
||||
|
||||
if args.trust_readonly:
|
||||
gdb.execute("set trust-readonly-sections on")
|
||||
|
||||
gdb.execute(f"gcore {corefile}")
|
||||
|
||||
if args.trust_readonly:
|
||||
# Restore trust-readonly-sections to default off state
|
||||
gdb.execute("set trust-readonly-sections off")
|
||||
|
||||
gdb.execute(f'file "{elffile}"')
|
||||
tmpfile.close()
|
||||
|
||||
print(f"Please run gdbserver.py to parse {corefile}")
|
||||
@@ -0,0 +1,421 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/lists.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
|
||||
|
||||
list_node_type = utils.lookup_type("struct list_node")
|
||||
sq_queue_type = utils.lookup_type("sq_queue_t")
|
||||
sq_entry_type = utils.lookup_type("sq_entry_t")
|
||||
dq_queue_type = utils.lookup_type("dq_queue_t")
|
||||
|
||||
|
||||
class NxList:
|
||||
def __init__(self, list, container_type=None, member=None, reverse=False):
|
||||
"""Initialize the list iterator. Optionally specify the container type and member name."""
|
||||
|
||||
if not list:
|
||||
raise ValueError("The head cannot be None.\n")
|
||||
|
||||
if list.type.code != gdb.TYPE_CODE_PTR:
|
||||
list = list.address # Make sure list is a pointer.
|
||||
|
||||
if container_type and not member:
|
||||
raise ValueError("Must specify the member name in container.\n")
|
||||
|
||||
self.list = list
|
||||
self.reverse = reverse
|
||||
self.container_type = container_type
|
||||
self.member = member
|
||||
self.current = self._get_first()
|
||||
|
||||
def _get_first(self):
|
||||
"""Get the initial node based on the direction of traversal."""
|
||||
|
||||
prev = self.list["prev"]
|
||||
next = self.list["next"]
|
||||
|
||||
first = prev if self.reverse else next
|
||||
return first if first and first != self.list else None
|
||||
|
||||
def _get_next(self, node):
|
||||
# for(node = (list)->next; node != (list); node = node->next)
|
||||
return node["next"] if node["next"] != self.list else None
|
||||
|
||||
def _get_prev(self, node):
|
||||
# for(node = (list)->next; node != (list); node = node->prev)
|
||||
return node["prev"] if node["prev"] != self.list else None
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
if self.current is None:
|
||||
raise StopIteration
|
||||
|
||||
node = self.current
|
||||
self.current = self._get_prev(node) if self.reverse else self._get_next(node)
|
||||
return (
|
||||
utils.container_of(node, self.container_type, self.member)
|
||||
if self.container_type
|
||||
else node
|
||||
)
|
||||
|
||||
|
||||
class NxSQueue(NxList):
|
||||
def __init__(self, list, container_type=None, member=None, reverse=False):
|
||||
"""Initialize the singly linked list iterator. Optionally specify the container type and member name."""
|
||||
if reverse:
|
||||
raise ValueError(
|
||||
"Reverse iteration is not supported for singly linked lists.\n"
|
||||
)
|
||||
super().__init__(list, container_type, member, reverse)
|
||||
|
||||
def _get_first(self):
|
||||
# for ((p) = (q)->head; (p) != NULL; (p) = (p)->flink)
|
||||
return self.list["head"] or None
|
||||
|
||||
def _get_next(self, node):
|
||||
# if not node["flink"], then return None, to indicate end of list
|
||||
return node["flink"] or None
|
||||
|
||||
|
||||
class NxDQueue(NxList):
|
||||
def __init__(self, list, container_type=None, member=None, reverse=False):
|
||||
"""Initialize the doubly linked list iterator. Optionally specify the container type and member name."""
|
||||
super().__init__(list, container_type, member, reverse)
|
||||
|
||||
def _get_first(self):
|
||||
head = self.list["head"]
|
||||
tail = self.list["tail"]
|
||||
|
||||
first = head if not self.reverse else tail
|
||||
return first or None
|
||||
|
||||
def _get_next(self, node):
|
||||
# for ((p) = (q)->head; (p) != NULL; (p) = (p)->flink)
|
||||
return node["flink"] or None
|
||||
|
||||
def _get_prev(self, node):
|
||||
# for ((p) = (q)->tail; (p) != NULL; (p) = (p)->blink)
|
||||
return node["blink"] or None
|
||||
|
||||
|
||||
def list_check(head):
|
||||
"""Check the consistency of a list"""
|
||||
nb = 0
|
||||
|
||||
if head.type == list_node_type.pointer():
|
||||
head = head.dereference()
|
||||
elif head.type != list_node_type:
|
||||
raise gdb.GdbError("argument must be of type (struct list_node [*])")
|
||||
c = head
|
||||
try:
|
||||
gdb.write("Starting with: {}\n".format(c))
|
||||
except gdb.MemoryError:
|
||||
gdb.write("head is not accessible\n")
|
||||
return
|
||||
while True:
|
||||
p = c["prev"].dereference()
|
||||
n = c["next"].dereference()
|
||||
try:
|
||||
if p["next"] != c.address:
|
||||
gdb.write(
|
||||
"prev.next != current: "
|
||||
"current@{current_addr}={current} "
|
||||
"prev@{p_addr}={p}\n".format(
|
||||
current_addr=c.address,
|
||||
current=c,
|
||||
p_addr=p.address,
|
||||
p=p,
|
||||
)
|
||||
)
|
||||
return
|
||||
except gdb.MemoryError:
|
||||
gdb.write(
|
||||
"prev is not accessible: "
|
||||
"current@{current_addr}={current}\n".format(
|
||||
current_addr=c.address, current=c
|
||||
)
|
||||
)
|
||||
return
|
||||
try:
|
||||
if n["prev"] != c.address:
|
||||
gdb.write(
|
||||
"next.prev != current: "
|
||||
"current@{current_addr}={current} "
|
||||
"next@{n_addr}={n}\n".format(
|
||||
current_addr=c.address,
|
||||
current=c,
|
||||
n_addr=n.address,
|
||||
n=n,
|
||||
)
|
||||
)
|
||||
return
|
||||
except gdb.MemoryError:
|
||||
gdb.write(
|
||||
"next is not accessible: "
|
||||
"current@{current_addr}={current}\n".format(
|
||||
current_addr=c.address, current=c
|
||||
)
|
||||
)
|
||||
return
|
||||
c = n
|
||||
nb += 1
|
||||
if c == head:
|
||||
gdb.write("list is consistent: {} node(s)\n".format(nb))
|
||||
return
|
||||
|
||||
|
||||
def sq_is_empty(sq):
|
||||
"""Check if a singly linked list is empty"""
|
||||
if sq.type == sq_queue_type.pointer():
|
||||
sq = sq.dereference()
|
||||
elif sq.type != sq_queue_type:
|
||||
return False
|
||||
|
||||
if sq["head"] == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def sq_check(sq, verbose=True) -> int:
|
||||
"""Check the consistency of a singly linked list"""
|
||||
nb = 0
|
||||
if sq.type == sq_queue_type.pointer():
|
||||
sq = sq.dereference()
|
||||
elif sq.type != sq_queue_type:
|
||||
gdb.write("Must be struct sq_queue not {}".format(sq.type))
|
||||
return nb
|
||||
|
||||
if sq["head"] == 0:
|
||||
if verbose:
|
||||
gdb.write("sq_queue head is empty {}\n".format(sq.address))
|
||||
return nb
|
||||
|
||||
nodes = set()
|
||||
entry = sq["head"].dereference()
|
||||
try:
|
||||
while entry.address:
|
||||
nb += 1
|
||||
if int(entry.address) in nodes:
|
||||
gdb.write("sq_queue is circular: {}\n".format(entry.address))
|
||||
return nb
|
||||
nodes.add(int(entry.address))
|
||||
entry = entry["flink"].dereference()
|
||||
except gdb.MemoryError:
|
||||
gdb.write("entry address is unaccessible {}\n".format(entry.address))
|
||||
return nb
|
||||
|
||||
if int(sq["tail"]) not in nodes:
|
||||
gdb.write("sq_queue tail is not in the list {}\n".format(sq["tail"]))
|
||||
return nb
|
||||
if sq["tail"]["flink"] != 0:
|
||||
gdb.write("sq_queue tail->flink is not null {}\n".format(sq["tail"]))
|
||||
return nb
|
||||
|
||||
if verbose:
|
||||
gdb.write("sq_queue is consistent: {} node(s)\n".format(nb))
|
||||
return nb
|
||||
|
||||
|
||||
def sq_count(sq) -> int:
|
||||
"""Count sq elements, abort if check failed"""
|
||||
return sq_check(sq, verbose=False)
|
||||
|
||||
|
||||
def dq_for_every(dq, entry=None):
|
||||
"""Iterate over a doubly linked list"""
|
||||
if dq.type == dq_queue_type.pointer():
|
||||
dq = dq.dereference()
|
||||
elif dq.type != dq_queue_type:
|
||||
gdb.write("Must be struct dq_queue not {}".format(dq.type))
|
||||
return
|
||||
|
||||
if dq["head"] == 0:
|
||||
return
|
||||
|
||||
if not entry:
|
||||
entry = dq["head"].dereference()
|
||||
|
||||
while entry.address:
|
||||
yield entry.address
|
||||
entry = entry["flink"].dereference()
|
||||
|
||||
|
||||
def dq_check(dq):
|
||||
"""Check the consistency of a doubly linked list"""
|
||||
nb = 0
|
||||
if dq.type == dq_queue_type.pointer():
|
||||
dq = dq.dereference()
|
||||
elif dq.type != dq_queue_type:
|
||||
gdb.write("Must be struct dq_queue not {}".format(dq.type))
|
||||
return
|
||||
|
||||
if dq["head"] == 0:
|
||||
gdb.write("dq_queue head is empty {}\n".format(dq.address))
|
||||
return
|
||||
|
||||
nodes = set()
|
||||
entry = dq["head"].dereference()
|
||||
try:
|
||||
while entry.address:
|
||||
nb += 1
|
||||
if int(entry.address) in nodes:
|
||||
gdb.write("dq_queue is circular: {}\n".format(entry.address))
|
||||
return
|
||||
nodes.add(int(entry.address))
|
||||
entry = entry["flink"].dereference()
|
||||
except gdb.MemoryError:
|
||||
gdb.write("entry address is unaccessible {}\n".format(entry.address))
|
||||
return
|
||||
|
||||
if int(dq["tail"]) not in nodes:
|
||||
gdb.write("dq_queue tail is not in the list {}\n".format(dq["tail"]))
|
||||
return
|
||||
if dq["tail"]["flink"] != 0:
|
||||
gdb.write("dq_queue tail->flink is not null {}\n".format(dq["tail"]))
|
||||
return
|
||||
|
||||
gdb.write("dq_queue is consistent: {} node(s)\n".format(nb))
|
||||
|
||||
|
||||
class ListCheck(gdb.Command):
|
||||
"""Verify a list consistency"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("list_check", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION)
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
argv = gdb.string_to_argv(arg)
|
||||
if len(argv) != 1:
|
||||
raise gdb.GdbError("nx-list-check takes one argument")
|
||||
|
||||
obj = gdb.parse_and_eval(argv[0])
|
||||
if obj.type == list_node_type.pointer():
|
||||
list_check(obj)
|
||||
elif obj.type == sq_queue_type.pointer():
|
||||
sq_check(obj)
|
||||
elif obj.type == dq_queue_type.pointer():
|
||||
dq_check(obj)
|
||||
else:
|
||||
raise gdb.GdbError("Invalid argument type: {}".format(obj.type))
|
||||
|
||||
|
||||
class ForeachListEntry(gdb.Command):
|
||||
"""Dump list members for a given list"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("foreach list", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION)
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
argv = gdb.string_to_argv(arg)
|
||||
|
||||
parser = argparse.ArgumentParser(description="Iterate the items in list")
|
||||
parser.add_argument("head", type=str, help="List head")
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--next",
|
||||
type=str,
|
||||
help="The name of the next pointer in the list node",
|
||||
default="next",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--container", type=str, default=None, help="Optional container type"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-m", "--member", type=str, default=None, help="Member name in container"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--element",
|
||||
type=str,
|
||||
help="Only dump this element in array member struct.",
|
||||
default=None,
|
||||
)
|
||||
try:
|
||||
args = parser.parse_args(argv)
|
||||
except SystemExit:
|
||||
gdb.write("Invalid arguments\n")
|
||||
return
|
||||
|
||||
pointer = gdb.parse_and_eval(args.head)
|
||||
node = pointer
|
||||
i = 0
|
||||
while node:
|
||||
entry = (
|
||||
utils.container_of(node, args.container, args.member)
|
||||
if args.container
|
||||
else node
|
||||
)
|
||||
entry = entry.dereference()
|
||||
entry = entry[args.element] if args.element else entry
|
||||
gdb.write(
|
||||
f"{i} *({entry.type} *){hex(entry.address)} {entry.format_string(styling=True)}\n"
|
||||
)
|
||||
i += 1
|
||||
node = node[args.next]
|
||||
if node == pointer:
|
||||
break
|
||||
|
||||
|
||||
class ForeachArray(gdb.Command):
|
||||
"""Dump array members."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("foreach array", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION)
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
argv = gdb.string_to_argv(arg)
|
||||
|
||||
parser = argparse.ArgumentParser(description="Iterate the items in array")
|
||||
parser.add_argument("head", type=str, help="List head")
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--length",
|
||||
type=int,
|
||||
help="The array length",
|
||||
default=None,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--element",
|
||||
type=str,
|
||||
help="Only dump this element in array member struct.",
|
||||
default=None,
|
||||
)
|
||||
try:
|
||||
args = parser.parse_args(argv)
|
||||
except SystemExit:
|
||||
gdb.write("Invalid arguments\n")
|
||||
return
|
||||
|
||||
pointer = gdb.parse_and_eval(args.head)
|
||||
node = pointer
|
||||
len = args.length if args.length else utils.nitems(pointer)
|
||||
for i in range(len):
|
||||
entry = node[i][args.element] if args.element else node[i]
|
||||
gdb.write(f"{i}: {entry.format_string(styling=True)}\n")
|
||||
@@ -0,0 +1,289 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/macros.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.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
# NOTE: GDB stores macro information based on the current stack frame's scope,
|
||||
# including the source file and line number. Therefore, there may be missing
|
||||
# macro definitions when you are at different stack frames.
|
||||
#
|
||||
# To resolve this issue, we need to retrieve all macro information from the ELF file
|
||||
# then parse and evaluate it by ourselves.
|
||||
#
|
||||
# There might be two ways to achieve this, one is to leverage the C preprocessor
|
||||
# to directly preprocess all the macros interpreted into python constants
|
||||
# gcc -E -x c -P <file_with_macros> -I/path/to/nuttx/include
|
||||
#
|
||||
# While the other way is to leverage the dwarf info stored in the ELF file,
|
||||
# with -g3 switch, we have a `.debug_macro` section containing all the information
|
||||
# about the macros.
|
||||
#
|
||||
# Currently, we are using the second method.
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
import time
|
||||
from os import path
|
||||
|
||||
PUNCTUATORS = [
|
||||
r"\[",
|
||||
r"\]",
|
||||
r"\(",
|
||||
r"\)",
|
||||
r"\{",
|
||||
r"\}",
|
||||
r"\?",
|
||||
r";",
|
||||
r",",
|
||||
r"~",
|
||||
r"\.\.\.",
|
||||
r"\.",
|
||||
r"\-\>",
|
||||
r"\-\-",
|
||||
r"\-\=",
|
||||
r"\-",
|
||||
r"\+\+",
|
||||
r"\+\=",
|
||||
r"\+",
|
||||
r"\*\=",
|
||||
r"\*",
|
||||
r"\!\=",
|
||||
r"\!",
|
||||
r"\&\&",
|
||||
r"\&\=",
|
||||
r"\&",
|
||||
r"\/\=",
|
||||
r"\/",
|
||||
r"\%\>",
|
||||
r"%:%:",
|
||||
r"%:",
|
||||
r"%=",
|
||||
r"%",
|
||||
r"\^\=",
|
||||
r"\^",
|
||||
r"\#\#",
|
||||
r"\#",
|
||||
r"\:\>",
|
||||
r"\:",
|
||||
r"\|\|",
|
||||
r"\|\=",
|
||||
r"\|",
|
||||
r"<<=",
|
||||
r"<<",
|
||||
r"<=",
|
||||
r"<:",
|
||||
r"<%",
|
||||
r"<",
|
||||
r">>=",
|
||||
r">>",
|
||||
r">=",
|
||||
r">",
|
||||
r"\=\=",
|
||||
r"\=",
|
||||
]
|
||||
|
||||
|
||||
def parse_macro(line, macros, pattern):
|
||||
# grep name, value
|
||||
# the first group matches the token, the second matches the replacement
|
||||
m = pattern.match(line)
|
||||
if not m:
|
||||
return False
|
||||
|
||||
name, value = m.group(1), m.group(2)
|
||||
|
||||
if name in macros:
|
||||
# FIXME: what should we do if we got a redefinition/duplication here?
|
||||
# for now I think it's ok just overwrite the old value
|
||||
pass
|
||||
|
||||
# emplace, for all undefined macros we evalute it to zero
|
||||
macros[name] = value if value else "0"
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def fetch_macro_info(file):
|
||||
if not path.isfile(file):
|
||||
raise FileNotFoundError("No given ELF target found")
|
||||
|
||||
# FIXME: we don't use subprocess here because
|
||||
# it's broken on some GDB distribution :(, I haven't
|
||||
# found a solution to it.
|
||||
|
||||
with open(file, "rb") as f:
|
||||
hash = hashlib.md5(f.read()).hexdigest()
|
||||
|
||||
macros = {}
|
||||
p = re.compile(r".*macro[ ]*:[ ]*([\S]+\(.*?\)|[\w]+)[ ]*(.*)")
|
||||
cache = path.join(tempfile.gettempdir(), f"{hash}.json")
|
||||
print(f"Load macro: {cache}")
|
||||
if not path.isfile(cache):
|
||||
t = time.time()
|
||||
os.system(f'readelf -wm "{file}" > "{cache}"')
|
||||
print(f"readelf took {time.time() - t:.1f} seconds")
|
||||
|
||||
t = time.time()
|
||||
with open(cache, "r") as f2:
|
||||
for line in f2.readlines():
|
||||
if not line.startswith(" DW_MACRO_define") and not line.startswith(
|
||||
" DW_MACRO_undef"
|
||||
):
|
||||
continue
|
||||
|
||||
if not parse_macro(line, macros, p):
|
||||
print(f"Failed to parse {line}")
|
||||
|
||||
print(f"Parse macro took {time.time() - t:.1f} seconds")
|
||||
|
||||
with open(cache, "w") as f2:
|
||||
dump = json.dumps(macros, indent=4, sort_keys=True)
|
||||
f2.write(dump)
|
||||
|
||||
print(f"Cache macro info to {cache}")
|
||||
else:
|
||||
with open(cache, "r") as f2:
|
||||
macros = json.load(f2)
|
||||
|
||||
return macros
|
||||
|
||||
|
||||
def split_tokens(expr):
|
||||
p = "(" + "|".join(PUNCTUATORS) + ")"
|
||||
res = list(
|
||||
filter(lambda e: e != "", map(lambda e: e.rstrip().lstrip(), re.split(p, expr)))
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
def do_expand(expr, macro_map):
|
||||
if expr in PUNCTUATORS:
|
||||
return expr
|
||||
|
||||
tokens = split_tokens(expr)
|
||||
|
||||
res = []
|
||||
|
||||
for t in tokens:
|
||||
if t not in macro_map:
|
||||
res.append(t)
|
||||
continue
|
||||
res += do_expand(macro_map[t], macro_map)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
# NOTE: Implement a fully functional parser which can
|
||||
# preprocess all the C marcos according to ISO 9899 standard
|
||||
# may be an overkill, what we really care about are those
|
||||
# macros that can be evaluated to a constant value.
|
||||
#
|
||||
# #define A (B + C + D)
|
||||
# #define B 1
|
||||
# #define C 2
|
||||
# #define D 3
|
||||
# invoking try_expand('A', macro_map) will give you "(1 + 2 + 3)"
|
||||
#
|
||||
# However,
|
||||
# #define SUM(B,C,D) (B + C + D)
|
||||
# invoking try_expand('SUM(1,2,3)', macro_map) will give you "SUM(1,2,3)"
|
||||
#
|
||||
# We have not implemented this feature as we have not found a practical
|
||||
# use case for it in our GDB plugin.
|
||||
#
|
||||
# However, you can switch to the correct stack frame that has this macro defined
|
||||
# and let GDB expand and evaluate it for you if you really want to evaluate some very
|
||||
# complex macros.
|
||||
|
||||
|
||||
def try_expand(expr, macro):
|
||||
res = []
|
||||
|
||||
res += do_expand(expr, macro)
|
||||
|
||||
return "".join(res)
|
||||
|
||||
|
||||
class Macro:
|
||||
"""
|
||||
This is a singleton class which only initializes once to
|
||||
cache a context of macro definition which can be queried later
|
||||
TODO: we only deal with single ELF at the moment for simplicity
|
||||
If you load more object files while debugging, only the first one gets loaded
|
||||
will be used to retrieve macro information
|
||||
|
||||
Usage:
|
||||
macro = Macro("nuttx/nuttx")
|
||||
print(macro.CONFIG_MM_BACKTRACE)
|
||||
if macro.CONFIG_MM_BACKTRACE:
|
||||
print("mm backtrace is enabled")
|
||||
else:
|
||||
print("mm backtrace is disabled")
|
||||
"""
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if not hasattr(cls, "instance"):
|
||||
cls.instance = super(Macro, cls).__new__(cls)
|
||||
return cls.instance
|
||||
|
||||
def __init__(self, filename):
|
||||
self._macro_map = {}
|
||||
self._file = filename
|
||||
self._macro_map = fetch_macro_info(filename)
|
||||
|
||||
def is_defined(self, macro_name):
|
||||
"""
|
||||
Check if a macro is defined
|
||||
"""
|
||||
return macro_name in self._macro_map
|
||||
|
||||
def get_value(self, macro_name, default=None):
|
||||
"""
|
||||
Get the value of a macro, return default if macro is not defined
|
||||
"""
|
||||
if not self.is_defined(macro_name):
|
||||
return default
|
||||
|
||||
value = self._macro_map[macro_name]
|
||||
# Try to convert to numeric type
|
||||
try:
|
||||
# Handle hexadecimal
|
||||
if isinstance(value, str) and value.startswith("0x"):
|
||||
return int(value, 16)
|
||||
# Handle integer
|
||||
return int(value)
|
||||
except (ValueError, TypeError):
|
||||
# Return original value if conversion fails
|
||||
return value
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
Allow using dot notation to access macros
|
||||
"""
|
||||
return self.get_value(name)
|
||||
|
||||
def __call__(self, macro_name):
|
||||
"""
|
||||
Allow using function call syntax to get macro
|
||||
"""
|
||||
return self.get_value(macro_name)
|
||||
@@ -0,0 +1,175 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/memcheck.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 traceback
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
import gdb
|
||||
|
||||
from . import memdump, mm, utils
|
||||
|
||||
|
||||
class MMCheck(gdb.Command):
|
||||
"""Check memory manager and pool integrity"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("mm check", gdb.COMMAND_USER)
|
||||
utils.alias("memcheck", "mm check")
|
||||
|
||||
def check_heap(self, heap: mm.MMHeap) -> Dict[int, List[str]]: # noqa: C901
|
||||
"""Check heap integrity and return list of issues in string"""
|
||||
issues = defaultdict(list) # key: address, value: list of issues
|
||||
|
||||
def report(e, heap, node: mm.MMNode) -> None:
|
||||
gdb.write(f"Error happened during heap check: {e}\n")
|
||||
try:
|
||||
gdb.write(f" heap: {heap}\n")
|
||||
gdb.write(f"current node: {node}")
|
||||
if node.prevnode:
|
||||
gdb.write(f" prev node: {node.prevnode}")
|
||||
if node.nextnode:
|
||||
gdb.write(f" next node: {node.nextnode}")
|
||||
|
||||
gdb.write("\n")
|
||||
except gdb.error as e:
|
||||
gdb.write(f"Error happened during report: {e}\n")
|
||||
|
||||
def is_node_corrupted(node: mm.MMNode) -> Tuple[bool, str]:
|
||||
# Must be in this heap
|
||||
if not heap.contains(node.address):
|
||||
return True, f"node@{hex(node.address)} not in heap"
|
||||
|
||||
# Check next node
|
||||
if node.nodesize > node.MM_SIZEOF_ALLOCNODE:
|
||||
nextnode = node.nextnode
|
||||
if not heap.contains(nextnode.address):
|
||||
return True, f"nexnode@{hex(nextnode.address)} not in heap"
|
||||
if node.is_free:
|
||||
if not nextnode.is_prev_free:
|
||||
# This node is free, then next node must have prev free set
|
||||
return (
|
||||
True,
|
||||
f"nextnode@{hex(nextnode.address)} not marked as prev free",
|
||||
)
|
||||
|
||||
if nextnode.prevsize != node.nodesize:
|
||||
return (
|
||||
True,
|
||||
f"nextnode @{hex(nextnode.address)} prevsize not match",
|
||||
)
|
||||
|
||||
if node.is_free:
|
||||
if node.nodesize < node.MM_MIN_CHUNK:
|
||||
return True, f"nodesize {int(node.nodesize)} too small"
|
||||
|
||||
if node.flink and node.flink.blink != node:
|
||||
return (
|
||||
True,
|
||||
f"flink not intact: {hex(node.flink.blink)}, node: {hex(node.address)}",
|
||||
)
|
||||
|
||||
if node.blink.flink != node:
|
||||
return (
|
||||
True,
|
||||
f"blink not intact: {hex(node.blink.flink)}, node: {hex(node.address)}",
|
||||
)
|
||||
|
||||
# Node should be in correctly sorted order
|
||||
if (blinksize := mm.MMNode(node.blink).nodesize) > node.nodesize:
|
||||
return (
|
||||
True,
|
||||
f"blink node not in sorted order: {blinksize} > {node.nodesize}",
|
||||
)
|
||||
|
||||
fnode = mm.MMNode(node.flink) if node.flink else None
|
||||
if fnode and fnode.nodesize and fnode.nodesize < node.nodesize:
|
||||
return (
|
||||
True,
|
||||
f"flink node not in sorted order: {fnode.nodesize} < {node.nodesize}",
|
||||
)
|
||||
else:
|
||||
# Node is allocated.
|
||||
if node.nodesize < node.MM_SIZEOF_ALLOCNODE:
|
||||
return True, f"nodesize {node.nodesize} too small"
|
||||
|
||||
return False, ""
|
||||
|
||||
try:
|
||||
# Check nodes in physical memory order
|
||||
for node in heap.nodes:
|
||||
corrupted, reason = is_node_corrupted(node)
|
||||
if corrupted:
|
||||
issues[node.address].append(reason)
|
||||
|
||||
# Check free list
|
||||
for node in utils.ArrayIterator(heap.mm_nodelist):
|
||||
# node is in type of gdb.Value, struct mm_freenode_s
|
||||
while node:
|
||||
address = int(node.address)
|
||||
if node["flink"] and not heap.contains(node["flink"]):
|
||||
issues[address].append(
|
||||
f"flink {hex(node['flink'])} not in heap"
|
||||
)
|
||||
break
|
||||
|
||||
if address in issues or node["size"] == 0:
|
||||
# This node is already checked or size is 0, which is a node in node table
|
||||
node = node["flink"]
|
||||
continue
|
||||
|
||||
# Check if this node is corrupted
|
||||
corrupted, reason = is_node_corrupted(mm.MMNode(node))
|
||||
if corrupted:
|
||||
issues[address].append(reason)
|
||||
break
|
||||
|
||||
# Continue to it's flink
|
||||
node = node["flink"]
|
||||
|
||||
except Exception as e:
|
||||
report(e, heap, node)
|
||||
traceback.print_exc()
|
||||
|
||||
return issues
|
||||
|
||||
def dump_issues(self, heap, issues: Dict[int, List[str]]) -> None:
|
||||
for address, reasons in issues.items():
|
||||
gdb.write(
|
||||
f"{len(reasons)} issues @{hex(address)}: " f"{','.join(reasons)}\n"
|
||||
)
|
||||
|
||||
def invoke(self, arg: str, from_tty: bool) -> None:
|
||||
try:
|
||||
heaps = memdump.get_heaps()
|
||||
for heap in heaps:
|
||||
issues = self.check_heap(heap)
|
||||
if not issues:
|
||||
continue
|
||||
|
||||
print(f"Found {len(issues)} issues in heap {heap}")
|
||||
self.dump_issues(heap, issues)
|
||||
except Exception as e:
|
||||
print(f"Error happened during check: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
print("Check done.")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,219 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/memleak.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 bisect
|
||||
import json
|
||||
import time
|
||||
from os import path
|
||||
from typing import Dict, Generator, List, Tuple
|
||||
|
||||
import gdb
|
||||
|
||||
from . import memdump, mm, utils
|
||||
|
||||
|
||||
class GlobalNode(memdump.MMNodeDump):
|
||||
def __init__(self, address: int, nodesize: int):
|
||||
self.address = address
|
||||
self.nodesize = nodesize
|
||||
self.pid = None
|
||||
self.seqno = None
|
||||
self.overhead = 0
|
||||
self.backtrace = ()
|
||||
|
||||
def __repr__(self):
|
||||
return f"GlobalVar@{self.address:x}:{self.nodesize}Bytes"
|
||||
|
||||
def contains(self, addr: int) -> bool:
|
||||
pass
|
||||
|
||||
def read_memory(self) -> memoryview:
|
||||
return gdb.selected_inferior().read_memory(self.address, self.nodesize)
|
||||
|
||||
|
||||
class MMLeak(gdb.Command):
|
||||
"""Dump memory manager heap"""
|
||||
|
||||
def __init__(self):
|
||||
self.elf = utils.import_check(
|
||||
"elftools.elf.elffile", "ELFFile", "Please pip install pyelftools\n"
|
||||
)
|
||||
if not self.elf:
|
||||
return
|
||||
|
||||
super().__init__("mm leak", gdb.COMMAND_USER)
|
||||
utils.alias("memleak", "mm leak")
|
||||
|
||||
def global_nodes(self) -> List[GlobalNode]:
|
||||
cache = path.join(
|
||||
path.dirname(path.abspath(gdb.objfiles()[0].filename)),
|
||||
f"{utils.get_elf_md5()}-globals.json",
|
||||
)
|
||||
|
||||
nodes: List[GlobalNode] = []
|
||||
|
||||
if path.isfile(cache):
|
||||
with open(cache, "r") as f:
|
||||
variables = json.load(f)
|
||||
for var in variables:
|
||||
nodes.append(GlobalNode(var["address"], var["size"]))
|
||||
return nodes
|
||||
|
||||
longsize = utils.get_long_type().sizeof
|
||||
for objfile in gdb.objfiles():
|
||||
elf = self.elf.load_from_path(objfile.filename)
|
||||
symtab = elf.get_section_by_name(".symtab")
|
||||
symbols = filter(
|
||||
lambda s: s["st_info"]["type"] == "STT_OBJECT"
|
||||
and s["st_size"] >= longsize,
|
||||
symtab.iter_symbols(),
|
||||
)
|
||||
|
||||
for symbol in symbols:
|
||||
size = symbol["st_size"] // longsize * longsize
|
||||
address = symbol["st_value"]
|
||||
nodes.append(GlobalNode(address, size))
|
||||
|
||||
with open(cache, "w") as f:
|
||||
variables = [
|
||||
{"address": node.address, "size": node.nodesize} for node in nodes
|
||||
]
|
||||
str = utils.jsonify(variables)
|
||||
f.write(str)
|
||||
|
||||
return nodes
|
||||
|
||||
def collect_leaks(
|
||||
self, heaps: List[mm.MMHeap]
|
||||
) -> Dict[memdump.MMNodeDump, List[memdump.MMNodeDump]]:
|
||||
t = time.time()
|
||||
print("Loading globals from elf...", flush=True, end="")
|
||||
good_nodes = self.global_nodes() # Global memory are all good.
|
||||
print(f" {time.time() - t:.2f}s", flush=True, end="\n")
|
||||
|
||||
nodes_dict: Dict[int, memdump.MMNodeDump] = {}
|
||||
sorted_addr = set()
|
||||
t = time.time()
|
||||
print("Gather memory nodes...", flush=True, end="")
|
||||
for node in memdump.dump_nodes(heaps, {"no_pid": mm.PID_MM_MEMPOOL}):
|
||||
nodes_dict[node.address] = node
|
||||
sorted_addr.add(node.address)
|
||||
|
||||
sorted_addr = sorted(sorted_addr)
|
||||
print(f" {time.time() - t:.2f}s", flush=True, end="\n")
|
||||
|
||||
regions = [
|
||||
{"start": start.address, "end": end.address}
|
||||
for heap in heaps
|
||||
for start, end in heap.regions
|
||||
]
|
||||
|
||||
longsize = utils.get_long_type().sizeof
|
||||
|
||||
def pointers(node: memdump.MMNodeDump) -> Generator[int, None, None]:
|
||||
# Return all possible pointers stored in this node
|
||||
size = node.nodesize - node.overhead
|
||||
memory = node.read_memory()
|
||||
while size > 0:
|
||||
size -= longsize
|
||||
ptr = int.from_bytes(memory[size : size + longsize], "little")
|
||||
if any(region["start"] <= ptr < region["end"] for region in regions):
|
||||
yield ptr
|
||||
|
||||
print("Leak analyzing...", flush=True, end="")
|
||||
t = time.time()
|
||||
for good in good_nodes:
|
||||
for ptr in pointers(good):
|
||||
if not (idx := bisect.bisect_right(sorted_addr, ptr)):
|
||||
continue
|
||||
|
||||
node = nodes_dict[sorted_addr[idx - 1]]
|
||||
if node.contains(ptr):
|
||||
del sorted_addr[idx - 1]
|
||||
good_nodes.append(node)
|
||||
|
||||
print(f" {time.time() - t:.2f}s", flush=True, end="\n")
|
||||
|
||||
return memdump.group_nodes((nodes_dict[addr] for addr in sorted_addr))
|
||||
|
||||
def _iterate_leaks(
|
||||
self, nodes: Dict[memdump.MMNodeDump, List[memdump.MMNodeDump]]
|
||||
) -> Generator[Tuple[memdump.MMNodeDump, bool, int], None, None]:
|
||||
pids = [int(tcb["pid"]) for tcb in utils.get_tcbs()]
|
||||
|
||||
def is_pid_alive(pid):
|
||||
return pid in pids
|
||||
|
||||
for node in nodes.keys():
|
||||
count = len(nodes[node])
|
||||
yield node, is_pid_alive(node.pid), count
|
||||
|
||||
def invoke(self, arg: str, from_tty: bool) -> None:
|
||||
heaps = memdump.get_heaps("g_mmheap")
|
||||
|
||||
leak_nodes = self.collect_leaks(heaps)
|
||||
memdump.print_header()
|
||||
total_blk = total_size = 0
|
||||
for node, alive, count in self._iterate_leaks(leak_nodes):
|
||||
total_blk += count
|
||||
total_size += count * node.nodesize
|
||||
memdump.print_node(node, alive, count=count)
|
||||
|
||||
print(f"Leaked {total_blk} blks, {total_size} bytes")
|
||||
|
||||
def diagnose(self, *args, **kwargs):
|
||||
heaps = memdump.get_heaps("g_mmheap")
|
||||
leak_nodes = self.collect_leaks(heaps)
|
||||
total_blk = total_size = 0
|
||||
data = []
|
||||
for node, alive, count in self._iterate_leaks(leak_nodes):
|
||||
total_blk += count
|
||||
total_size += count * node.nodesize
|
||||
info = {
|
||||
"count": count,
|
||||
"pid": node.pid,
|
||||
"size": node.nodesize,
|
||||
"address": node.address,
|
||||
"seqno": node.seqno,
|
||||
"alive": alive,
|
||||
"backtrace": [],
|
||||
}
|
||||
|
||||
if mm.CONFIG_MM_BACKTRACE and node.backtrace and node.backtrace[0]:
|
||||
bt = utils.Backtrace(node.backtrace)
|
||||
info["backtrace"] = [
|
||||
{
|
||||
"address": addr,
|
||||
"function": func,
|
||||
"source": source,
|
||||
}
|
||||
for addr, func, source in bt.backtrace
|
||||
]
|
||||
data.append(info)
|
||||
|
||||
return {
|
||||
"title": "Memory Leak Report",
|
||||
"summary": f"Total {total_blk} blks, {total_size} bytes leaked",
|
||||
"result": "fail" if total_blk else "pass",
|
||||
"command": "mm leak",
|
||||
"data": data,
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,320 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/net.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.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
from enum import Enum
|
||||
|
||||
import gdb
|
||||
|
||||
from . import utils
|
||||
from .lists import NxDQueue, NxSQueue
|
||||
|
||||
socket = utils.import_check(
|
||||
"socket", errmsg="No socket module found, please try gdb-multiarch instead.\n"
|
||||
)
|
||||
|
||||
NET_IPv4 = utils.get_symbol_value("CONFIG_NET_IPv4")
|
||||
NET_IPv6 = utils.get_symbol_value("CONFIG_NET_IPv6")
|
||||
|
||||
AF_INET = utils.get_symbol_value("AF_INET")
|
||||
AF_INET6 = utils.get_symbol_value("AF_INET6")
|
||||
|
||||
|
||||
def ntohs(val):
|
||||
"""Convert a 16-bit value from network byte order to host byte order"""
|
||||
|
||||
if utils.get_target_endianness() == utils.BIG_ENDIAN:
|
||||
return val
|
||||
return utils.swap16(val)
|
||||
|
||||
|
||||
def get_ip_port(conn):
|
||||
"""Get the IP address and port of a network connection"""
|
||||
|
||||
domain = utils.get_field(conn, "domain", AF_INET if NET_IPv4 else AF_INET6)
|
||||
ip_binding = conn["u"]["ipv4" if domain == AF_INET else "ipv6"]
|
||||
lport = ntohs(conn["lport"])
|
||||
rport = ntohs(conn["rport"])
|
||||
laddr = inet_ntop(domain, ip_binding["laddr"])
|
||||
raddr = inet_ntop(domain, ip_binding["raddr"])
|
||||
return laddr, lport, raddr, rport
|
||||
|
||||
|
||||
def inet_ntop(domain, addr):
|
||||
"""Convert a network address to a string"""
|
||||
|
||||
addr_len = 16 if domain == AF_INET6 else 4
|
||||
if socket:
|
||||
return socket.inet_ntop(domain, utils.get_bytes(addr, addr_len))
|
||||
else:
|
||||
separator = "." if domain == AF_INET else ""
|
||||
fmt = "%d" if domain == AF_INET else "%02x"
|
||||
return separator.join([fmt % byte for byte in utils.get_bytes(addr, addr_len)])
|
||||
|
||||
|
||||
def socket_for_each_entry(proto):
|
||||
"""Iterate over a dq of socket structs, usage:
|
||||
for conn in socket_for_each_entry("icmp"):
|
||||
readahead = conn["readahead"]
|
||||
"""
|
||||
|
||||
g_active_connections = gdb.parse_and_eval("g_active_%s_connections" % proto)
|
||||
for node in NxDQueue(g_active_connections, "struct socket_conn_s", "node"):
|
||||
# udp_conn_s::socket_conn_s sconn
|
||||
yield utils.container_of(
|
||||
node,
|
||||
"struct %s_conn_s" % proto,
|
||||
"sconn",
|
||||
)
|
||||
|
||||
|
||||
def wrbuffer_inqueue_size(queue=None, protocol="tcp"):
|
||||
"""Calculate the total size of all iob in the write queue of a udp connection"""
|
||||
|
||||
if not queue:
|
||||
return 0
|
||||
|
||||
type = "struct %s_wrbuffer_s" % protocol
|
||||
node = "wb_node"
|
||||
return sum(entry["wb_iob"]["io_pktlen"] for entry in NxSQueue(queue, type, node))
|
||||
|
||||
|
||||
def tcp_ofoseg_bufsize(conn):
|
||||
"""Calculate the pending size of out-of-order buffer of a tcp connection"""
|
||||
|
||||
total = 0
|
||||
if utils.get_symbol_value("CONFIG_NET_TCP_OUT_OF_ORDER"):
|
||||
total = sum(
|
||||
seg["data"]["io_pktlen"]
|
||||
for seg in utils.ArrayIterator(conn["ofosegs"], conn["nofosegs"])
|
||||
)
|
||||
return total
|
||||
|
||||
|
||||
class NetStats(gdb.Command):
|
||||
"""Network statistics
|
||||
Usage: netstats [iob|pkt|tcp|udp|all]
|
||||
|
||||
Examples: netstats - Show all stats
|
||||
netstats all - Show all stats
|
||||
netstats iob - Show IOB stats
|
||||
netstats tcp udp - Show both TCP and UDP stats
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
if utils.get_symbol_value("CONFIG_NET"):
|
||||
super().__init__("netstats", gdb.COMMAND_USER)
|
||||
|
||||
def iob_stats(self):
|
||||
try:
|
||||
size = utils.get_symbol_value("CONFIG_IOB_BUFSIZE")
|
||||
ntotal = utils.get_symbol_value("CONFIG_IOB_NBUFFERS")
|
||||
|
||||
nfree = gdb.parse_and_eval("g_iob_sem")["semcount"]
|
||||
nwait, nfree = (0, nfree) if nfree >= 0 else (-nfree, 0)
|
||||
|
||||
nthrottle = (
|
||||
gdb.parse_and_eval("g_throttle_sem")["semcount"]
|
||||
if utils.get_symbol_value("CONFIG_IOB_THROTTLE") > 0
|
||||
else 0
|
||||
)
|
||||
|
||||
gdb.write(
|
||||
"IOB: %10s%10s%10s%10s%10s\n"
|
||||
% ("size", "ntotal", "nfree", "nwait", "nthrottle")
|
||||
)
|
||||
gdb.write(
|
||||
" %10d%10d%10d%10d%10d\n" % (size, ntotal, nfree, nwait, nthrottle)
|
||||
)
|
||||
except gdb.error as e:
|
||||
gdb.write("Failed to get IOB stats: %s\n" % e)
|
||||
|
||||
def pkt_stats(self):
|
||||
try:
|
||||
netstats = gdb.parse_and_eval("g_netstats")
|
||||
gdb.write(
|
||||
"Packets:%7s%7s%7s%7s%7s%7s\n"
|
||||
% ("IPv4", "IPv6", "TCP", "UDP", "ICMP", "ICMPv6")
|
||||
)
|
||||
|
||||
def stats_line(title, member):
|
||||
gdb.write("%-8s" % title)
|
||||
for proto in ("ipv4", "ipv6", "tcp", "udp", "icmp", "icmpv6"):
|
||||
gdb.write(
|
||||
"%7s"
|
||||
% utils.get_field(utils.get_field(netstats, proto), member, "-")
|
||||
)
|
||||
gdb.write("\n")
|
||||
|
||||
stats_line("Received", "recv")
|
||||
stats_line("Dropped", "drop")
|
||||
stats_line(" VHL", "vhlerr")
|
||||
stats_line(" Frag", "fragerr")
|
||||
stats_line(" Chksum", "chkerr")
|
||||
stats_line(" Type", "typeerr")
|
||||
stats_line(" Proto", "protoerr")
|
||||
# TODO: Maybe print TCP's ackerr, rst, syndrop, synrst here
|
||||
stats_line("Sent", "sent")
|
||||
stats_line(" Rexmit", "rexmit")
|
||||
|
||||
except gdb.error as e:
|
||||
gdb.write("Failed to get Net Stats: %s\n" % e)
|
||||
|
||||
def tcp_stats(self):
|
||||
try:
|
||||
gdb.write(
|
||||
"TCP Conn: %3s %3s %3s %3s %4s %3s"
|
||||
% ("st", "flg", "ref", "tmr", "uack", "nrt")
|
||||
)
|
||||
gdb.write(
|
||||
" %11s %11s+%-5s %21s %21s\n"
|
||||
% ("txbuf", "rxbuf", "ofo", "local_address", "remote_address")
|
||||
)
|
||||
for idx, conn in enumerate(socket_for_each_entry("tcp")):
|
||||
state = conn["tcpstateflags"]
|
||||
flags = conn["sconn"]["s_flags"]
|
||||
ref = conn["crefs"]
|
||||
timer = conn["timer"]
|
||||
unacked = conn["tx_unacked"]
|
||||
nrtx = conn["nrtx"]
|
||||
|
||||
txbuf = utils.get_field(conn, "snd_bufs", -1)
|
||||
rxbuf = utils.get_field(conn, "rcv_bufs", -1)
|
||||
txsz = wrbuffer_inqueue_size(
|
||||
utils.get_field(conn, "unacked_q"), "tcp"
|
||||
) + wrbuffer_inqueue_size(utils.get_field(conn, "write_q"), "tcp")
|
||||
rxsz = conn["readahead"]["io_pktlen"] if conn["readahead"] else 0
|
||||
ofosz = tcp_ofoseg_bufsize(conn)
|
||||
laddr, lport, raddr, rport = get_ip_port(conn)
|
||||
|
||||
gdb.write(
|
||||
"%-4d %3x %3x %3d %3d %4d %3d"
|
||||
% (idx, state, flags, ref, timer, unacked, nrtx)
|
||||
)
|
||||
gdb.write(
|
||||
" %5d/%-5d %5d/%-5d+%-5d %15s:%-5d %15s:%-5d\n"
|
||||
% (txsz, txbuf, rxsz, rxbuf, ofosz, laddr, lport, raddr, rport)
|
||||
)
|
||||
except gdb.error as e:
|
||||
gdb.write("Failed to get TCP stats: %s\n" % e)
|
||||
|
||||
def udp_stats(self):
|
||||
try:
|
||||
gdb.write(
|
||||
"UDP Conn: %4s %11s %11s %21s %21s\n"
|
||||
% ("flg", "txbuf", "rxbuf", "local_address", "remote_address")
|
||||
)
|
||||
for idx, conn in enumerate(socket_for_each_entry("udp")):
|
||||
flags = conn["sconn"]["s_flags"]
|
||||
txbuf = utils.get_field(conn, "sndbufs", -1)
|
||||
rxbuf = utils.get_field(conn, "rcvbufs", -1)
|
||||
txsz = wrbuffer_inqueue_size(utils.get_field(conn, "write_q"), "udp")
|
||||
rxsz = conn["readahead"]["io_pktlen"] if conn["readahead"] else 0
|
||||
laddr, lport, raddr, rport = get_ip_port(conn)
|
||||
|
||||
gdb.write(
|
||||
"%-4d %4x %5d/%-5d %5d/%-5d %15s:%-5d %15s:%-5d\n"
|
||||
% (idx, flags, txsz, txbuf, rxsz, rxbuf, laddr, lport, raddr, rport)
|
||||
)
|
||||
except gdb.error as e:
|
||||
gdb.write("Failed to get UDP stats: %s\n" % e)
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
# Parse the arguments in a simple way
|
||||
if not args or "all" in args:
|
||||
args = "iob pkt tcp udp"
|
||||
|
||||
# Call the corresponding function
|
||||
if utils.get_symbol_value("CONFIG_MM_IOB") and "iob" in args:
|
||||
self.iob_stats()
|
||||
gdb.write("\n")
|
||||
if utils.get_symbol_value("CONFIG_NET_STATISTICS") and "pkt" in args:
|
||||
self.pkt_stats()
|
||||
gdb.write("\n")
|
||||
if utils.get_symbol_value("CONFIG_NET_TCP") and "tcp" in args:
|
||||
self.tcp_stats()
|
||||
gdb.write("\n")
|
||||
if utils.get_symbol_value("CONFIG_NET_UDP") and "udp" in args:
|
||||
self.udp_stats()
|
||||
gdb.write("\n")
|
||||
|
||||
|
||||
class NetCheckResult(Enum):
|
||||
PASS = 0
|
||||
WARN = 1
|
||||
FAILED = 2
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.value < other.value
|
||||
|
||||
|
||||
class NetCheck(gdb.Command):
|
||||
"""Network check"""
|
||||
|
||||
def __init__(self):
|
||||
if utils.get_symbol_value("CONFIG_NET"):
|
||||
super().__init__("netcheck", gdb.COMMAND_USER)
|
||||
|
||||
def diagnose(self, *args, **kwargs):
|
||||
result, message = NetCheckResult.PASS, []
|
||||
|
||||
if utils.get_symbol_value("CONFIG_MM_IOB"):
|
||||
ret, msg = self.check_iob()
|
||||
result = max(result, ret)
|
||||
message.extend(msg)
|
||||
|
||||
return {
|
||||
"title": "Netcheck Report",
|
||||
"summary": "Net status check",
|
||||
"result": "pass" if result else "fail",
|
||||
"command": "netcheck",
|
||||
"data": message,
|
||||
}
|
||||
|
||||
def check_iob(self):
|
||||
result = NetCheckResult.PASS
|
||||
message = []
|
||||
try:
|
||||
nfree = gdb.parse_and_eval("g_iob_sem")["semcount"]
|
||||
nthrottle = (
|
||||
gdb.parse_and_eval("g_throttle_sem")["semcount"]
|
||||
if utils.get_symbol_value("CONFIG_IOB_THROTTLE") > 0
|
||||
else 0
|
||||
)
|
||||
|
||||
if nfree < 0 or nthrottle < 0:
|
||||
result = max(result, NetCheckResult.WARN)
|
||||
message.append(
|
||||
"[WARNING] IOB used up: free %d throttle %d" % (nfree, nthrottle)
|
||||
)
|
||||
|
||||
except gdb.error as e:
|
||||
result = max(result, NetCheckResult.FAILED)
|
||||
message.append("[FAILED] Failed to check IOB: %s" % e)
|
||||
|
||||
finally:
|
||||
return result, message
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
if utils.get_symbol_value("CONFIG_MM_IOB"):
|
||||
result, message = self.check_iob()
|
||||
gdb.write("IOB check: %s\n" % result.name)
|
||||
gdb.write("\n".join(message))
|
||||
@@ -0,0 +1,37 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/prefix.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 gdb
|
||||
|
||||
|
||||
class ForeachPrefix(gdb.Command):
|
||||
"""foreach commands prefix."""
|
||||
|
||||
def __init__(self):
|
||||
super(ForeachPrefix, self).__init__("foreach", gdb.COMMAND_USER, prefix=True)
|
||||
|
||||
|
||||
class MMPrefixCommand(gdb.Command):
|
||||
"""Memory manager related commands prefix."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("mm", gdb.COMMAND_USER, prefix=True)
|
||||
@@ -0,0 +1,62 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/profile.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 gdb
|
||||
|
||||
from .utils import import_check
|
||||
|
||||
|
||||
class Profile(gdb.Command):
|
||||
"""Profile a gdb command
|
||||
|
||||
Usage: profile <gdb command>
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.cProfile = import_check(
|
||||
"cProfile", errmsg="cProfile module not found, try gdb-multiarch.\n"
|
||||
)
|
||||
if not self.cProfile:
|
||||
return
|
||||
|
||||
super().__init__("profile", gdb.COMMAND_USER)
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
self.cProfile.run(f"gdb.execute('{args}')", sort="cumulative")
|
||||
|
||||
|
||||
class Time(gdb.Command):
|
||||
"""Time a gdb command
|
||||
|
||||
Usage: time <gdb command>
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("time", gdb.COMMAND_USER)
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
import time
|
||||
|
||||
start = time.time()
|
||||
gdb.execute(args)
|
||||
end = time.time()
|
||||
gdb.write(f"Time elapsed: {end - start:.6f}s\n")
|
||||
@@ -0,0 +1,65 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/protocols/fs.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.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
from .value import Value
|
||||
|
||||
|
||||
class File(Value):
|
||||
"""struct file"""
|
||||
|
||||
f_oflags: Value
|
||||
f_refs: Value
|
||||
f_pos: Value
|
||||
f_inode: Value
|
||||
f_priv: Value
|
||||
f_tag_fdsan: Value
|
||||
f_tag_fdcheck: Value
|
||||
f_backtrace: Value
|
||||
locked: Value
|
||||
|
||||
|
||||
class Inode(Value):
|
||||
"""struct inode"""
|
||||
|
||||
i_parent: Value
|
||||
i_peer: Value
|
||||
i_child: Value
|
||||
i_crefs: Value
|
||||
i_flags: Value
|
||||
u: Value
|
||||
i_ino: Value
|
||||
i_size: Value
|
||||
i_mode: Value
|
||||
i_owner: Value
|
||||
i_group: Value
|
||||
i_atime: Value
|
||||
i_mtime: Value
|
||||
i_ctime: Value
|
||||
i_private: Value
|
||||
i_name: Value
|
||||
|
||||
|
||||
class FileList(Value):
|
||||
"""struct filelist_s"""
|
||||
|
||||
fl_rows: Value
|
||||
fl_files: Value
|
||||
@@ -0,0 +1,122 @@
|
||||
############################################################################
|
||||
# tools/gdb/nuttxgdb/protocols/mm.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.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from .value import Value
|
||||
|
||||
|
||||
class ProcfsMeminfoEntry(Value):
|
||||
"""struct procfs_meminfo_entry_s"""
|
||||
|
||||
name: Value
|
||||
heap: Value
|
||||
next: ProcfsMeminfoEntry
|
||||
|
||||
|
||||
class MMAllocNode(Value):
|
||||
"""struct mm_allocnode_s"""
|
||||
|
||||
preceding: Value
|
||||
size: Value
|
||||
pid: Value
|
||||
seqno: Value
|
||||
backtrace: Value
|
||||
|
||||
|
||||
class MMFreeNode(Value):
|
||||
"""struct mm_freenode_s"""
|
||||
|
||||
preceding: Value
|
||||
size: Value
|
||||
pid: Value
|
||||
seqno: Value
|
||||
backtrace: Value
|
||||
flink: MMFreeNode
|
||||
blink: MMFreeNode
|
||||
|
||||
|
||||
class MMHeap(Value):
|
||||
"""struct mm_heap_s"""
|
||||
|
||||
mm_lock: Value
|
||||
mm_heapsize: Value
|
||||
mm_maxused: Value
|
||||
mm_curused: Value
|
||||
mm_heapstart: List[MMAllocNode]
|
||||
mm_heapend: List[MMAllocNode]
|
||||
mm_nregions: Value
|
||||
mm_nodelist: Value
|
||||
|
||||
|
||||
class MemPool(Value):
|
||||
"""struct mempool_s"""
|
||||
|
||||
initialsize: Value
|
||||
interruptsize: Value
|
||||
expandsize: Value
|
||||
wait: Value
|
||||
priv: Value
|
||||
alloc: Value
|
||||
free: Value
|
||||
check: Value
|
||||
ibase: Value
|
||||
queue: Value
|
||||
iqueue: Value
|
||||
equeue: Value
|
||||
nalloc: Value
|
||||
lock: Value
|
||||
waitsem: Value
|
||||
procfs: Value
|
||||
|
||||
|
||||
class MemPoolMultiple(Value):
|
||||
"""struct mempool_multiple_s"""
|
||||
|
||||
pools: List[MemPool]
|
||||
npools: Value
|
||||
expandsize: Value
|
||||
minpoolsize: Value
|
||||
arg: Value
|
||||
alloc: Value
|
||||
alloc_size: Value
|
||||
free: Value
|
||||
alloced: Value
|
||||
delta: Value
|
||||
lock: Value
|
||||
chunk_queue: Value
|
||||
chunk_size: Value
|
||||
dict_used: Value
|
||||
dict_col_num_log2: Value
|
||||
dict_row_num: Value
|
||||
dict: Value
|
||||
|
||||
|
||||
class MemPoolBlock(Value):
|
||||
"""struct mempool_backtrace_s"""
|
||||
|
||||
magic: Value
|
||||
pid: Value
|
||||
seqno: Value
|
||||
backtrace: Value
|
||||
@@ -0,0 +1,125 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/protocols/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.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
from .fs import FileList
|
||||
from .value import Value
|
||||
|
||||
|
||||
class Group(Value):
|
||||
"""struct group_s"""
|
||||
|
||||
tg_pid: Value
|
||||
tg_ppid: Value
|
||||
tg_flags: Value
|
||||
tg_uid: Value
|
||||
tg_gid: Value
|
||||
tg_euid: Value
|
||||
tg_egid: Value
|
||||
tg_members: Value
|
||||
tg_bininfo: Value
|
||||
tg_children: Value
|
||||
tg_nchildren: Value
|
||||
tg_exitcode: Value
|
||||
tg_nwaiters: Value
|
||||
tg_waitflags: Value
|
||||
tg_exitsem: Value
|
||||
tg_statloc: Value
|
||||
tg_joinlock: Value
|
||||
tg_joinqueue: Value
|
||||
tg_info: Value
|
||||
tg_sigactionq: Value
|
||||
tg_sigpendingq: Value
|
||||
tg_sigdefault: Value
|
||||
tg_envp: Value
|
||||
tg_envc: Value
|
||||
itimer: Value
|
||||
tg_filelist: FileList
|
||||
tg_mm_map: Value
|
||||
|
||||
|
||||
class Tcb(Value):
|
||||
"""struct tcb_s"""
|
||||
|
||||
flink: Value
|
||||
blink: Value
|
||||
group: Group
|
||||
member: Value
|
||||
join_queue: Value
|
||||
join_entry: Value
|
||||
join_sem: Value
|
||||
join_val: Value
|
||||
addrenv_own: Value
|
||||
addrenv_curr: Value
|
||||
pid: Value
|
||||
sched_priority: Value
|
||||
init_priority: Value
|
||||
start: Value
|
||||
entry: Value
|
||||
task_state: Value
|
||||
boost_priority: Value
|
||||
base_priority: Value
|
||||
holdsem: Value
|
||||
cpu: Value
|
||||
affinity: Value
|
||||
flags: Value
|
||||
lockcount: Value
|
||||
irqcount: Value
|
||||
errcode: Value
|
||||
timeslice: Value
|
||||
sporadic: Value
|
||||
waitdog: Value
|
||||
adj_stack_size: Value
|
||||
stack_alloc_ptr: Value
|
||||
stack_base_ptr: Value
|
||||
dspace: Value
|
||||
waitobj: Value
|
||||
sigprocmask: Value
|
||||
sigwaitmask: Value
|
||||
sigpendactionq: Value
|
||||
sigpostedq: Value
|
||||
sigunbinfo: Value
|
||||
mhead: Value
|
||||
ticks: Value
|
||||
run_start: Value
|
||||
run_max: Value
|
||||
run_time: Value
|
||||
premp_start: Value
|
||||
premp_max: Value
|
||||
premp_caller: Value
|
||||
premp_max_caller: Value
|
||||
crit_start: Value
|
||||
crit_max: Value
|
||||
crit_caller: Value
|
||||
crit_max_caller: Value
|
||||
perf_event_ctx: Value
|
||||
perf_event_mutex: Value
|
||||
xcp: Value
|
||||
sigdeliver: Value
|
||||
name: Value
|
||||
stackrecord_pc: Value
|
||||
stackrecord_sp: Value
|
||||
stackrecord_pc_deepest: Value
|
||||
stackrecord_sp_deepest: Value
|
||||
sp_deepest: Value
|
||||
caller_deepest: Value
|
||||
level_deepest: Value
|
||||
level: Value
|
||||
@@ -0,0 +1,67 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/protocols/value.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.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Protocol
|
||||
|
||||
import gdb
|
||||
|
||||
|
||||
class Value(Protocol):
|
||||
address: Value
|
||||
is_optimized_out: bool
|
||||
type: gdb.Type
|
||||
dynamic_type: gdb.Type
|
||||
is_lazy: bool
|
||||
bytes: bytes
|
||||
|
||||
def cast(self, type: gdb.Type) -> Value: ...
|
||||
def dereference(self) -> Value: ...
|
||||
def referenced_value(self) -> Value: ...
|
||||
def reference_value(self) -> Value: ...
|
||||
def rvalue_reference_value(self) -> Value: ...
|
||||
def const_value(self) -> Value: ...
|
||||
def dynamic_cast(self, type: gdb.Type) -> Value: ...
|
||||
def reinterpret_cast(self, type: gdb.Type) -> Value: ...
|
||||
|
||||
def format_string(
|
||||
self,
|
||||
raw: bool = ...,
|
||||
pretty_arrays: bool = ...,
|
||||
pretty_structs: bool = ...,
|
||||
array_indexes: bool = ...,
|
||||
symbols: bool = ...,
|
||||
unions: bool = ...,
|
||||
address: bool = ...,
|
||||
deref_refs: bool = ...,
|
||||
actual_objects: bool = ...,
|
||||
static_members: bool = ...,
|
||||
max_elements: int = ...,
|
||||
max_depth: int = ...,
|
||||
repeat_threshold: int = ...,
|
||||
format: str = ...,
|
||||
) -> str: ...
|
||||
|
||||
def string(
|
||||
self, encoding: str = ..., errors: str = ..., length: int = ...
|
||||
) -> str: ...
|
||||
@@ -0,0 +1,35 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/protocols/wdog.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.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .value import Value
|
||||
|
||||
|
||||
class WDog(Value):
|
||||
"""struct wdog_s"""
|
||||
|
||||
node: Value
|
||||
arg: Value
|
||||
func: Value
|
||||
picbase: Value
|
||||
expired: Value
|
||||
@@ -0,0 +1,71 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/protocols/wqueue.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.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from .value import Value
|
||||
|
||||
|
||||
class Work(Value):
|
||||
"""struct work_s"""
|
||||
|
||||
class U(Value):
|
||||
class S(Value):
|
||||
dq: Value
|
||||
qtime: Value
|
||||
|
||||
s: S
|
||||
timer: Value # wdog_s
|
||||
|
||||
u: U
|
||||
worker: Value # void (*worker_t)(FAR void *arg);
|
||||
arg: Value
|
||||
wq: KWorkQueue
|
||||
|
||||
|
||||
class KWorker(Value):
|
||||
"""struct kworker_s"""
|
||||
|
||||
pid: Value
|
||||
work: Value
|
||||
wait: Value
|
||||
|
||||
|
||||
class KWorkQueue(Value):
|
||||
"""struct kwork_wqueue_s"""
|
||||
|
||||
q: Value
|
||||
sem: Value
|
||||
exsem: Value
|
||||
nthreads: int
|
||||
exit: bool
|
||||
worker: List[Value]
|
||||
|
||||
|
||||
class HPWorkQueue(KWorkQueue):
|
||||
"""struct hp_wqueue_s"""
|
||||
|
||||
|
||||
class LPWorkQueue(KWorkQueue):
|
||||
"""struct lp_wqueue_s"""
|
||||
@@ -0,0 +1,107 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/rpmsg.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 gdb
|
||||
|
||||
from . import utils
|
||||
from .lists import NxList
|
||||
|
||||
|
||||
class RPMsgDump(gdb.Command):
|
||||
"""Dump rpmsg service"""
|
||||
|
||||
CALLBACK_HEADER = ["rpmsg_cb_s at", "ns_match", "ns_bind"]
|
||||
ENDPOINT_HEADER = [
|
||||
"endpoint_addr",
|
||||
"name",
|
||||
"addr",
|
||||
"dest_addr",
|
||||
"cb",
|
||||
"ns_bound_cb",
|
||||
"ns_unbind_cb",
|
||||
]
|
||||
|
||||
CALLBACK_FORAMTTER = "{:<20} {:<40} {:<40}"
|
||||
ENDPOINT_FORMATTER = "{:<20} {:<20} {:<12} {:<12} {:<40} {:<40} {:<40}"
|
||||
|
||||
def __init__(self):
|
||||
if utils.get_symbol_value("CONFIG_RPMSG"):
|
||||
super(RPMsgDump, self).__init__("rpmsgdump", gdb.COMMAND_USER)
|
||||
|
||||
def print_headers(self, headers, formatter):
|
||||
gdb.write(formatter.format(*headers) + "\n")
|
||||
gdb.write(formatter.format(*["-" * len(header) for header in headers]) + "\n")
|
||||
|
||||
def dump_rdev_epts(self, endpoints_head):
|
||||
gdb.write(f"dump_rdev_epts:{endpoints_head}\n")
|
||||
self.print_headers(self.ENDPOINT_HEADER, self.ENDPOINT_FORMATTER)
|
||||
|
||||
output = []
|
||||
for endpoint in NxList(endpoints_head, "struct rpmsg_endpoint", "node"):
|
||||
output.append(
|
||||
self.ENDPOINT_FORMATTER.format(
|
||||
f"{endpoint}",
|
||||
f"{endpoint['name'].string()}",
|
||||
f"{endpoint['addr']}",
|
||||
f"{endpoint['dest_addr']}",
|
||||
f"{endpoint['cb']}",
|
||||
f"{endpoint['ns_bound_cb']}",
|
||||
f"{endpoint['ns_unbind_cb']}",
|
||||
)
|
||||
)
|
||||
|
||||
gdb.write("\n".join(output) + "\n")
|
||||
|
||||
def dump_rdev_bitmap(self, rdev):
|
||||
bitmap_values = [hex(bit) for bit in utils.ArrayIterator(rdev["bitmap"])]
|
||||
|
||||
gdb.write(
|
||||
f"bitmap:{' '.join(bitmap_values):<20} bitmaplast: {rdev['bitmap']}\n"
|
||||
)
|
||||
|
||||
def dump_rdev(self, rdev):
|
||||
gdb.write(f"device:{rdev}\n")
|
||||
|
||||
self.dump_rdev_bitmap(rdev)
|
||||
self.dump_rdev_epts(rdev["endpoints"])
|
||||
|
||||
def dump_rpmsg_cb(self):
|
||||
gdb.write("g_rpmsg_cb:\n")
|
||||
self.print_headers(self.CALLBACK_HEADER, self.CALLBACK_FORAMTTER)
|
||||
|
||||
output = []
|
||||
for cb in NxList(gdb.parse_and_eval("g_rpmsg_cb"), "struct rpmsg_cb_s", "node"):
|
||||
output.append(
|
||||
self.CALLBACK_FORAMTTER.format(
|
||||
str(cb), str(cb["ns_match"]), str(cb["ns_bind"])
|
||||
)
|
||||
)
|
||||
gdb.write("\n".join(output) + "\n")
|
||||
|
||||
def dump_rpmsg(self):
|
||||
gdb.write("g_rpmsg:\n")
|
||||
for rpmsg in NxList(gdb.parse_and_eval("g_rpmsg"), "struct rpmsg_s", "node"):
|
||||
self.dump_rdev(rpmsg["rdev"])
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
self.dump_rpmsg_cb()
|
||||
self.dump_rpmsg()
|
||||
@@ -0,0 +1,219 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/stack.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 traceback
|
||||
|
||||
import gdb
|
||||
|
||||
from . import utils
|
||||
|
||||
STACK_COLORATION_PATTERN = utils.get_symbol_value(
|
||||
"STACK_COLOR", locspec="up_create_stack"
|
||||
)
|
||||
|
||||
|
||||
class Stack(object):
|
||||
def __init__(self, name, entry, base, alloc, size, cursp, align):
|
||||
# We don't care about the stack growth here, base always point to the lower address!
|
||||
self._thread_name = name
|
||||
self._thread_entry = entry
|
||||
self._stack_base = base
|
||||
self._stack_alloc = alloc
|
||||
self._stack_top = base + size
|
||||
self._cur_sp = cursp
|
||||
self._stack_size = size
|
||||
self._align = align
|
||||
self._pattern = STACK_COLORATION_PATTERN
|
||||
|
||||
self._sanity_check()
|
||||
|
||||
def _sanity_check(self):
|
||||
# do some basic sanity checking to make sure we have a sane stack object
|
||||
if (
|
||||
self._stack_base < self._stack_alloc
|
||||
or not self._stack_size
|
||||
or self._cur_sp <= self._stack_base
|
||||
or self._cur_sp > self._stack_base + self._stack_size
|
||||
):
|
||||
|
||||
gdb.write(
|
||||
f"base: {self._stack_base}, \
|
||||
size: {self._stack_size}, sp: {self._cur_sp}\n"
|
||||
)
|
||||
|
||||
raise gdb.GdbError("Inconsistant stack size...Maybe memory corruption?")
|
||||
|
||||
# TODO: check if stack ptr is located at a sane address range!
|
||||
|
||||
def cur_usage(self):
|
||||
usage = self._stack_top - self._cur_sp
|
||||
|
||||
if self.is_stackof():
|
||||
gdb.write("An overflow detected, dumping the stack:\n")
|
||||
|
||||
ptr_4bytes = gdb.Value(self._stack_base).cast(
|
||||
utils.lookup_type("unsigned int").pointer()
|
||||
)
|
||||
|
||||
for i in range(0, self._stack_size // 4):
|
||||
if i % 8 == 0:
|
||||
gdb.write(f"{hex(self._stack_base + 4 * i)}: ")
|
||||
|
||||
gdb.write(f"{hex(ptr_4bytes[i]):10} ")
|
||||
|
||||
if i % 8 == 7:
|
||||
gdb.write("\n")
|
||||
|
||||
gdb.write("\n")
|
||||
raise gdb.GdbError(
|
||||
"pls check your stack size! @ {0} sp:{1:x} base:{2:x}".format(
|
||||
self._thread_name, self._cur_sp, self._stack_base
|
||||
)
|
||||
)
|
||||
|
||||
return usage
|
||||
|
||||
def check_max_usage(self):
|
||||
ptr_4bytes = gdb.Value(self._stack_base).cast(
|
||||
utils.lookup_type("unsigned int").pointer()
|
||||
)
|
||||
|
||||
spare = 0
|
||||
|
||||
for i in range(0, self._stack_size // 4):
|
||||
if int(ptr_4bytes[i]) != self._pattern:
|
||||
spare = i * 4
|
||||
break
|
||||
return self._stack_size - spare
|
||||
|
||||
def max_usage(self):
|
||||
if not utils.get_symbol_value("CONFIG_STACK_COLORATION"):
|
||||
return 0
|
||||
|
||||
return self.check_max_usage()
|
||||
|
||||
def avalaible(self):
|
||||
cur_usage = self.cur_usage()
|
||||
return self._stack_size - cur_usage
|
||||
|
||||
def maxdepth_backtrace(self):
|
||||
raise gdb.GdbError("Not implemented yet", traceback.print_stack())
|
||||
|
||||
def cur_sp(self):
|
||||
return self._cur_sp
|
||||
|
||||
def is_stackof(self):
|
||||
# we should notify the user if the stack overflow is about to happen as well!
|
||||
return self._cur_sp <= self._stack_base
|
||||
|
||||
def has_stackof(self):
|
||||
max_usage = self.max_usage()
|
||||
|
||||
return max_usage >= self._stack_size
|
||||
|
||||
|
||||
# Always refetch the stack infos, never cached as we may have threads created/destroyed
|
||||
# dynamically!
|
||||
def fetch_stacks():
|
||||
stacks = dict()
|
||||
|
||||
for tcb in utils.get_tcbs():
|
||||
# We have no way to detect if we are in an interrupt context for now.
|
||||
# Originally we use `and not utils.in_interrupt_context()`
|
||||
if tcb["task_state"] == gdb.parse_and_eval("TSTATE_TASK_RUNNING"):
|
||||
sp = utils.get_sp()
|
||||
else:
|
||||
sp = utils.get_sp(tcb=tcb)
|
||||
|
||||
try:
|
||||
stacks[int(tcb["pid"])] = 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"]),
|
||||
sp,
|
||||
4,
|
||||
)
|
||||
|
||||
except gdb.GdbError as e:
|
||||
pid = tcb["pid"]
|
||||
gdb.write(
|
||||
f"Failed to construction stack object for tcb {pid} due to: {e}\n"
|
||||
)
|
||||
|
||||
return stacks
|
||||
|
||||
|
||||
class StackUsage(gdb.Command):
|
||||
"""Display the stack usage of each thread, similar to cat /proc/<pid>/stack"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("stack-usage", gdb.COMMAND_USER)
|
||||
self._stacks = []
|
||||
# format template
|
||||
self._fmt = (
|
||||
"{0: <4} | {1: <10} | {2: <10} | {3: <20} | {4: <10} | {5: <10} | {6: <10}"
|
||||
)
|
||||
|
||||
def format_print(self, pid, stack):
|
||||
def gen_info_str(x):
|
||||
usage = x / stack._stack_size
|
||||
res = ",".join([str(x), "{0:.2%}".format(usage)])
|
||||
if usage > 0.8:
|
||||
res += "!"
|
||||
return res
|
||||
|
||||
gdb.write(
|
||||
self._fmt.format(
|
||||
pid,
|
||||
stack._thread_name[:10],
|
||||
stack._thread_entry,
|
||||
hex(stack._stack_base),
|
||||
stack._stack_size,
|
||||
gen_info_str(stack.cur_usage()),
|
||||
gen_info_str(stack.max_usage()),
|
||||
)
|
||||
)
|
||||
gdb.write("\n")
|
||||
|
||||
def invoke(self, args, from_tty):
|
||||
stacks = fetch_stacks()
|
||||
|
||||
args = [int(arg) for arg in args.split()]
|
||||
|
||||
pids = stacks.keys() if len(args) == 0 else args
|
||||
|
||||
gdb.write(
|
||||
self._fmt.format(
|
||||
"Pid", "Name", "Entry", "Base", "Size", "CurUsage", "MaxUsage"
|
||||
)
|
||||
)
|
||||
gdb.write("\n")
|
||||
|
||||
for pid in pids:
|
||||
stack = stacks.get(pid)
|
||||
|
||||
if not stack:
|
||||
continue
|
||||
|
||||
self.format_print(pid, stack)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,67 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/wdog.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.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
import gdb
|
||||
|
||||
from . import lists, utils
|
||||
from .protocols import wdog as p
|
||||
from .utils import Value
|
||||
|
||||
|
||||
class WDog(Value, p.WDog):
|
||||
def __init__(self, wdog: p.WDog):
|
||||
if wdog.type.code == gdb.TYPE_CODE_PTR:
|
||||
wdog = wdog.dereference()
|
||||
super().__init__(wdog)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"WDog@{self.address:#x}: tick: {self.expired: <16}"
|
||||
f" {self.func.format_string(styling=True)} arg: {self.arg:#x}"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
def get_wdog_list() -> List[WDog]:
|
||||
wdogs = []
|
||||
active = utils.parse_and_eval("g_wdactivelist")
|
||||
for wdog in lists.NxList(active, "struct wdog_s", "node"):
|
||||
wdogs.append(WDog(wdog))
|
||||
|
||||
return wdogs
|
||||
|
||||
|
||||
class WDogDump(gdb.Command):
|
||||
"""Show wdog timer information"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("wdog", gdb.COMMAND_USER)
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
for wdog in get_wdog_list():
|
||||
print(wdog)
|
||||
@@ -0,0 +1,170 @@
|
||||
############################################################################
|
||||
# tools/pynuttx/nxgdb/wqueue.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.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
import gdb
|
||||
|
||||
from . import lists, utils
|
||||
from .protocols import wqueue as p
|
||||
from .utils import Value
|
||||
|
||||
|
||||
class Work(Value, p.Work):
|
||||
def __init__(self, work: p.Work):
|
||||
if work.type.code == gdb.TYPE_CODE_PTR:
|
||||
work = work.dereference()
|
||||
super().__init__(work)
|
||||
|
||||
@property
|
||||
def wqueue(self) -> WorkQueue:
|
||||
return WorkQueue(self.wq)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"work_s@{self.address:#x}: {self.worker.format_string(styling=True)} arg={self.arg}"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class KWorker(Value, p.KWorker):
|
||||
"""Worker thread information"""
|
||||
|
||||
def __init__(self, worker: p.KWorker, wqueue=None):
|
||||
if worker.type.code == gdb.TYPE_CODE_PTR:
|
||||
worker = worker.dereference()
|
||||
super().__init__(worker)
|
||||
self.wqueue = wqueue
|
||||
|
||||
@property
|
||||
def is_running(self):
|
||||
return self.work
|
||||
|
||||
@property
|
||||
def work(self) -> Work:
|
||||
work = self["work"]
|
||||
return Work(work) if work else None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return utils.get_task_name(utils.get_tcb(self.pid))
|
||||
|
||||
def __repr__(self):
|
||||
return f"kworker_s@{self.address:#x} {self.work or 'idle'}"
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class WorkQueue(Value, p.KWorkQueue):
|
||||
"""Work queue information"""
|
||||
|
||||
def __init__(self, queue: p.KWorkQueue, name: str = None):
|
||||
if queue.type.code == gdb.TYPE_CODE_PTR:
|
||||
queue = queue.dereference()
|
||||
super().__init__(queue)
|
||||
self.name = name or "<noname>"
|
||||
|
||||
@property
|
||||
def workers(self) -> List[Work]:
|
||||
work_s = utils.lookup_type("struct work_s")
|
||||
return [
|
||||
Work(worker.cast(work_s.pointer())) for worker in lists.NxDQueue(self.q)
|
||||
]
|
||||
|
||||
@property
|
||||
def threads(self) -> List[KWorker]:
|
||||
return [
|
||||
KWorker(thread, wqueue=self)
|
||||
for thread in utils.ArrayIterator(self.worker, self.nthreads)
|
||||
]
|
||||
|
||||
@property
|
||||
def nthreads(self):
|
||||
return int(self["nthreads"])
|
||||
|
||||
@property
|
||||
def is_running(self):
|
||||
return any(thread.is_running for thread in self.threads)
|
||||
|
||||
@property
|
||||
def is_exiting(self):
|
||||
return self.exit
|
||||
|
||||
def __repr__(self):
|
||||
state = "running" if self.is_running else "idle"
|
||||
return f"{self.name}@{self.address:#x}, {state}, {self.nthreads} threads, {len(self.workers)} work"
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
def get_work_queues() -> List[WorkQueue]:
|
||||
entry = gdb.parse_and_eval("work_thread")
|
||||
kwork_wqueue_s = utils.lookup_type("struct kwork_wqueue_s")
|
||||
|
||||
tcbs = utils.get_tcbs()
|
||||
# The function address may be or'ed with 0x01
|
||||
tcbs = filter(lambda tcb: int(tcb.entry.main) & ~0x01 == entry, tcbs)
|
||||
queue = []
|
||||
for tcb in tcbs:
|
||||
if not (args := utils.get_task_argvstr(tcb)):
|
||||
continue
|
||||
# wqueue = (FAR struct kwork_wqueue_s *)
|
||||
# ((uintptr_t)strtoul(argv[1], NULL, 16));
|
||||
# kworker = (FAR struct kworker_s *)
|
||||
# ((uintptr_t)strtoul(argv[2], NULL, 16));
|
||||
wqueue = gdb.Value(int(args[1], 16)).cast(kwork_wqueue_s.pointer())
|
||||
wqueue = WorkQueue(wqueue, name=utils.get_task_name(tcb))
|
||||
if wqueue not in queue:
|
||||
queue.append(wqueue)
|
||||
|
||||
return queue
|
||||
|
||||
|
||||
class WorkQueueDump(gdb.Command):
|
||||
"""Show work queue information"""
|
||||
|
||||
def __init__(self):
|
||||
if not utils.get_symbol_value("CONFIG_SCHED_WORKQUEUE"):
|
||||
return
|
||||
|
||||
super().__init__("worker", gdb.COMMAND_USER)
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
queues = get_work_queues()
|
||||
for queue in queues:
|
||||
print(f"{queue}")
|
||||
if not queue.is_running:
|
||||
continue
|
||||
|
||||
print(" Running:") # Dump the work that is running
|
||||
running = [thread for thread in queue.threads if thread.is_running]
|
||||
print("\n".join(f" {thread}" for thread in running))
|
||||
|
||||
if not queue.workers:
|
||||
continue
|
||||
|
||||
print(" Queued:")
|
||||
print("\n".join(f" {work}" for work in queue.workers))
|
||||
@@ -0,0 +1,22 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=61", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
version = "0.0.1"
|
||||
name = 'nxgdb'
|
||||
description = 'NuttX RTOS GDB extensions'
|
||||
readme = "README.md"
|
||||
license = { file = 'LICENSE' }
|
||||
classifiers = [
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
]
|
||||
|
||||
dependencies = ["matplotlib", "numpy", "pyelftools"]
|
||||
requires-python = ">=3.8"
|
||||
|
||||
[tool.setuptools.package-dir]
|
||||
nxgdb = "nxgdb"
|
||||
@@ -0,0 +1,13 @@
|
||||
contourpy==1.3.0
|
||||
cycler==0.12.1
|
||||
debugpy==1.8.7
|
||||
fonttools==4.54.1
|
||||
kiwisolver==1.4.7
|
||||
matplotlib==3.9.2
|
||||
numpy==2.1.2
|
||||
packaging==24.1
|
||||
pillow==11.0.0
|
||||
pyelftools==0.31
|
||||
pyparsing==3.2.0
|
||||
python-dateutil==2.9.0.post0
|
||||
six==1.16.0
|
||||
Reference in New Issue
Block a user