| =========================== |
| Static Stack Usage Analysis |
| =========================== |
| |
| Overview |
| ======== |
| |
| ``tools/stackusage.py`` performs static stack usage analysis by reading |
| DWARF ``.debug_frame`` data from an ELF file. It extracts per-function |
| stack sizes from CFA (Canonical Frame Address) offsets and optionally |
| builds a call graph via disassembly to compute worst-case total stack |
| depth. |
| |
| - **Self** – stack bytes used by the function itself (max CFA offset). |
| - **Total** – worst-case stack depth through the deepest call chain |
| (self + callees). A marker prefix flags uncertain values. |
| |
| Dependencies |
| ============ |
| |
| The tool invokes standard toolchain binaries: |
| |
| - **readelf** – symbol table and DWARF frame info |
| - **objdump** – disassembly for call graph analysis |
| - **addr2line** – source file and line resolution |
| |
| Both GNU and LLVM toolchains are supported. Use ``-p`` to set the |
| toolchain prefix (e.g. ``-p arm-none-eabi-`` for GCC, |
| ``-p llvm-`` for LLVM). |
| |
| The ELF must contain DWARF debug info (``-g`` or ``-gdwarf``). |
| No special Kconfig option is needed. |
| |
| Usage |
| ===== |
| |
| Analyze a native ELF (no prefix needed):: |
| |
| python3 tools/stackusage.py nuttx |
| |
| Cross-compiled ELF with GCC toolchain:: |
| |
| python3 tools/stackusage.py -p arm-none-eabi- nuttx |
| |
| Cross-compiled ELF with LLVM toolchain:: |
| |
| python3 tools/stackusage.py -p llvm- nuttx |
| |
| Show top 20 functions:: |
| |
| python3 tools/stackusage.py -p arm-none-eabi- -n 20 nuttx |
| |
| Estimate recursion depth of 10:: |
| |
| python3 tools/stackusage.py -p arm-none-eabi- -r 10 nuttx |
| |
| Command Line Options |
| ==================== |
| |
| .. code-block:: text |
| |
| positional arguments: |
| elf path to ELF file with DWARF debug info |
| |
| options: |
| -p, --prefix PREFIX toolchain prefix (e.g. arm-none-eabi- or llvm-) |
| -n, --rank N show top N functions (default: 0 = all) |
| -r, --recursion-depth N |
| assumed recursion depth (default: 0) |
| |
| Text Output |
| =========== |
| |
| The default output is an aligned table. Each function's deepest |
| backtrace is shown with one frame per row. The ``Self`` column shows |
| each frame's own stack cost. The ``Backtrace`` column shows the |
| function name followed by its code size in parentheses (when available |
| from the symbol table), e.g. ``main(128)``. The entry point of each |
| call chain is suffixed with ``~``. |
| |
| Example (``nucleo-f429zi:trace``, ``-n 3``):: |
| |
| Total Self Backtrace File:Line |
| ----- ---- --------------------------- ------------------------------------------- |
| @2344 56 telnetd_main(236)~ apps/system/telnetd/telnetd.c:42 |
| ^24 nsh_telnetmain(128) apps/nshlib/nsh_telnetd.c:48 |
| ^48 nsh_session(400) apps/nshlib/nsh_session.c:73 |
| ... |
| @224 nsh_parse_cmdparm(1024) apps/nshlib/nsh_parse.c:2362 |
| @96 nsh_execute(512) apps/nshlib/nsh_parse.c:510 |
| ^56 nsh_builtin(320) apps/nshlib/nsh_builtin.c:76 |
| 88 exec_builtin(256) apps/builtin/exec_builtin.c:61 |
| ... |
| ^64 file_vopen(192) nuttx/fs/vfs/fs_open.c:124 |
| ... |
| @2328 16 sh_main(64)~ apps/system/nsh/sh_main.c:40 |
| 16 nsh_system_ctty(96) apps/nshlib/nsh_system.c:105 |
| ^32 nsh_system_(160) apps/nshlib/nsh_system.c:41 |
| ^48 nsh_session(400) apps/nshlib/nsh_session.c:73 |
| ... |
| @2312 24 nsh_main(80)~ apps/system/nsh/nsh_main.c:54 |
| ^24 nsh_consolemain(48) apps/nshlib/nsh_consolemain.c:65 |
| ^48 nsh_session(400) apps/nshlib/nsh_session.c:73 |
| ... |
| |
| Uncertainty markers on both Total and Self columns indicate the most |
| significant reason: |
| |
| ======= ========================================== |
| Marker Meaning |
| ======= ========================================== |
| ``~`` entry point of the call chain (suffix) |
| ``?`` no DWARF data (self counted as zero) |
| ``*`` dynamic stack (alloca or VLA) |
| ``@`` recursion detected |
| ``^`` indirect call (function pointer) |
| ======= ========================================== |
| |
| Uncertainty Reasons |
| =================== |
| |
| ====================================== ========================================= |
| Reason Description |
| ====================================== ========================================= |
| recursion: A->B->...->A Recursive cycle detected. Use ``-r N`` |
| to estimate. |
| indirect call (function pointer) Callee unknown at compile time. |
| no DWARF data No ``.debug_frame`` entry; self counted |
| as zero. |
| dynamic stack (alloca/VLA) Function uses ``alloca()`` or |
| variable-length arrays; self is a |
| minimum. |
| ====================================== ========================================= |
| |
| Uncertainty propagates upward: if any callee in the deepest path is |
| uncertain the caller is also marked uncertain. |
| |
| Recursion Depth Estimation |
| ========================== |
| |
| By default (``-r 0``) recursive back-edges contribute zero stack. |
| With ``-r N`` (N > 0) the tool estimates:: |
| |
| cycle_body_cost × N |
| |
| For example ``A(64) -> B(32) -> A``:: |
| |
| cycle_body_cost = 64 + 32 = 96 |
| -r 10 → 96 × 10 = 960 bytes |
| |
| The result is still marked uncertain. |
| |
| Supported Architectures |
| ======================= |
| |
| Any architecture supported by the toolchain's ``readelf``, |
| ``objdump``, and ``addr2line`` is supported. This includes |
| ARM, AArch64, x86, x86_64, MIPS, RISC-V, Xtensa, PowerPC, SPARC, |
| TriCore, SuperH, and others. |