核心转储
=========

:link_to_translation:`en:[English]`

概述
--------

核心转储是软件发生致命错误时，由紧急处理程序自动保存的一组软件状态信息。核心转储有助于对故障进行事后分析，了解软件状态。ESP-IDF 支持生成核心转储。

核心转储包含了系统中所有任务在发生故障时的快照，每个快照都包括任务的控制块 (TCB) 和栈信息。通过分析任务快照，可以确定是哪个任务、在哪个指令（代码行），以及该任务的哪个调用栈导致了系统崩溃。如果将某些变量赋予特殊的核心转储属性，还可以转储这些变量的内容。

核心转储数据会按照特定格式保存在核心转储文件中，详情请参阅 :doc:`core_dump_internals`。然而，ESP-IDF 的 ``idf.py`` 命令提供了专门的子命令，用于解码和分析核心转储文件。


配置
--------------

.. note::

    只有在构建中包含 ``espcoredump`` 组件时，``Core dump`` 配置选项才可用。要在项目中包含 ``Core dump`` 功能，在使用 ``idf_component_register`` 注册组件时，将 ``espcoredump`` 组件添加为 ``REQUIRES`` 或 ``PRIV_REQUIRES`` 的依赖项。

目标
^^^^^^^^^^^

选项 :ref:`CONFIG_ESP_COREDUMP_TO_FLASH_OR_UART` 可以启用或禁用核心转储，并在启用时选择核心转储的目标。发生崩溃时，生成的核心转储文件可以保存到 flash 中，也可以通过 UART 输出到连接的主机上。


格式和大小
^^^^^^^^^^^^^

核心转储文件以 ELF 格式生成，该格式具有可扩展性，能够在系统崩溃时完整保存错误任务和故障软件的详细信息。ELF 格式具备良好的扩展灵活性，便于未来版本升级时存储更多诊断信息。

选项 :ref:`CONFIG_ESP_COREDUMP_MAX_TASKS_NUM` 可用于配置核心转储保存的任务快照数量。崩溃任务的寄存器和栈总是会被保存，不受此配置选项影响。其他任务的快照将根据优先级顺序（从就绪状态下优先级最高的任务开始）依次保存。

数据完整性检查
^^^^^^^^^^^^^^^^^^^^

核心转储文件包含 SHA256 校验和，用于验证核心转储文件的完整性，确保文件未被损坏。SHA256 哈希算法在检测损坏方面具有很高的准确率，包括多位错误。

保留栈大小
^^^^^^^^^^^^^^^^^^^

核心转储例程需要解析并保存所有其他任务的栈，因此会从单独的栈中运行。选项 :ref:`CONFIG_ESP_COREDUMP_STACK_SIZE` 控制核心转储栈大小，以字节数表示。

将此选项设置为 0 字节将使核心转储例程从 ISR 栈中运行，从而节省内存。将选项设置为大于零的值将创建一个独立的栈。

.. note::

   如果使用了独立的栈，建议栈大小应大于 1300 字节，确保核心转储例程本身不会导致栈溢出。


.. only:: not esp32c5

    核心转储内存区域
    ^^^^^^^^^^^^^^^^

    核心转储默认保存 CPU 寄存器、任务数据和崩溃原因。选择 :ref:`CONFIG_ESP_COREDUMP_CAPTURE_DRAM` 选项后，``.bss`` 段和 ``.data`` 段以及 ``heap`` 数据也将保存到转储中。

    推荐将上面提到的几个数据段都保存到核心转储中，以方便调试。但这会导致核心转储文件变大，具体所需的额外存储空间取决于应用程序使用的 DRAM 大小。

    .. only:: SOC_SPIRAM_SUPPORTED

        .. note::

            除了崩溃任务的 TCB 和栈外，位于外部 RAM 中的数据不会保存到核心转储文件中，包括使用 ``EXT_RAM_BSS_ATTR`` 或 ``EXT_RAM_NOINIT_ATTR`` 属性定义的变量，以及存储在 ``extram_bss`` 段中的任何数据。

将核心转储保存到 flash
-----------------------

将核心转储文件保存至 flash 时，这些文件会保存到 flash 上的特殊分区。指定核心转储分区可以在 flash 芯片上预留空间来存储核心转储文件。

使用 ESP-IDF 提供的默认分区表时，核心转储分区会自动声明。但使用自定义分区表时，请按如下示例进行核心转储分区声明：

.. code-block:: none

    # 名称，   类型，子类型，   偏移量，   大小
    # 注意：如果增加了引导加载程序大小，请及时更新偏移量，避免产生重叠
    nvs,      data, nvs,     0x9000,  0x6000
    phy_init, data, phy,     0xf000,  0x1000
    factory,  app,  factory, 0x10000, 1M
    coredump, data, coredump,,        64K

.. important::

    如果设备启用了 :doc:`../security/flash-encryption`，请在核心转储分区中添加 ``encrypted`` 标志。请注意，使用 ``idf.py coredump-info`` 或 ``idf.py coredump-debug`` 命令无法从加密分区读取核心转储。
    建议使用 ``idf.py coredump-info -c <path-to-core-dump>`` 命令从 ESP 设备侧读取核心转储，ESP 设备会自动解密分区并发送到相应位置用于分析。

    .. code-block:: none

        coredump, data, coredump,,       64K, encrypted

分区命名没有特殊要求，可以根据应用程序的需要选择。但分区类型应为 ``data``，子类型应为 ``coredump``。此外，在选择分区大小时需注意，核心转储的数据结构会产生 20 字节的固定开销和 12 字节的单任务开销，此开销不包括每个任务的 TCB 和栈的大小。因此，分区大小应至少为 ``20 + 最大任务数 x（12 + TCB 大小 + 最大任务栈大小）`` 字节。

用于分析 flash 中核心转储的常用命令，可参考以下示例：

.. code-block:: bash

    idf.py coredump-info

或

.. code-block:: bash

    idf.py coredump-debug


.. note::

    ``idf.py coredump-info`` 命令和 ``idf.py coredump-debug`` 命令对 `esp-coredump` 工具进行了封装，可以在 ESP-IDF 环境中轻松使用。更多信息，请参考 :ref:`core_dump_commands`。


将核心转储保存到 UART
-----------------------

当核心转储文件输出到 UART 时，输出文件会以 Base64 编码方式呈现。通过 :ref:`CONFIG_ESP_COREDUMP_DECODE` 选项，可以选择 ESP-IDF 监视器对输出文件自动解码，或保持编码状态等待手动解码。


自动解码
^^^^^^^^^^^^^^^^^^

如果设置 :ref:`CONFIG_ESP_COREDUMP_DECODE`，使其自动解码 UART 核心转储文件，ESP-IDF 监视器会自动解码数据，将所有函数地址转换为源代码行，并在监视器中显示相应信息。ESP-IDF 监视器会输出类似以下内容：

.. code-block:: none

    ===============================================================
    ==================== ESP32 CORE DUMP START ====================

    Crashed task handle: 0x3ffafba0, name: 'main', GDB name: 'process 1073413024'
    Crashed task is not in the interrupt context
    Panic reason: abort() was called at PC 0x400d66b9 on core 0

    ================== CURRENT THREAD REGISTERS ===================
    exccause       0x1d (StoreProhibitedCause)
    excvaddr       0x0
    epc1           0x40084013
    epc2           0x0
    ...
    ==================== CURRENT THREAD STACK =====================
    #0  0x4008110d in panic_abort (details=0x3ffb4f0b "abort() was called at PC 0x400d66b9 on core 0") at /builds/espressif/esp-idf/components/esp_system/panic.c:472
    #1  0x4008510c in esp_system_abort (details=0x3ffb4f0b "abort() was called at PC 0x400d66b9 on core 0") at /builds/espressif/esp-idf/components/esp_system/port/esp_system_chip.c:93
    ...
    ======================== THREADS INFO =========================
      Id   Target Id          Frame
    * 1    process 1073413024 0x4008110d in panic_abort (details=0x3ffb4f0b "abort() was called at PC 0x400d66b9 on core 0") at /builds/espressif/esp-idf/components/esp_system/panic.c:472
      2    process 1073413368 vPortTaskWrapper (pxCode=0x0, pvParameters=0x0) at /builds/espressif/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c:133
    ...
           TCB             NAME PRIO C/B  STACK USED/FREE
    ---------- ---------------- -------- ----------------
    0x3ffafba0             main      1/1         368/3724
    0x3ffafcf8            IDLE0      0/0         288/1240
    0x3ffafe50            IDLE1      0/0         416/1108
    ...
    ==================== THREAD 1 (TCB: 0x3ffafba0, name: 'main') =====================
    #0  0x4008110d in panic_abort (details=0x3ffb4f0b "abort() was called at PC 0x400d66b9 on core 0") at /builds/espressif/esp-idf/components/esp_system/panic.c:472
    #1  0x4008510c in esp_system_abort (details=0x3ffb4f0b "abort() was called at PC 0x400d66b9 on core 0") at /builds/espressif/esp-idf/components/esp_system/port/esp_system_chip.c:93
    ...
    ==================== THREAD 2 (TCB: 0x3ffafcf8, name: 'IDLE0') =====================
    #0  vPortTaskWrapper (pxCode=0x0, pvParameters=0x0) at /builds/espressif/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c:133
    #1  0x40000000 in ?? ()
    ...
    ======================= ALL MEMORY REGIONS ========================
    Name   Address   Size   Attrs
    ...
    .iram0.vectors 0x40080000 0x403 R XA
    .iram0.text 0x40080404 0xb8ab R XA
    .dram0.data 0x3ffb0000 0x2114 RW A
    ...
    ===================== ESP32 CORE DUMP END =====================
    ===============================================================

此外，选项 :ref:`CONFIG_ESP_COREDUMP_UART_DELAY` 支持在将核心转储文件输出到 UART 前添加延迟。

手动解码
^^^^^^^^^^^^^^^

如果设置 :ref:`CONFIG_ESP_COREDUMP_DECODE` 为不解码，则在以下 UART 输出的页眉和页脚之间，将输出核心转储的原始 Base64 编码正文：

.. code-block:: none

    ================= CORE DUMP START =================
    <将 Base64 编码的核心转储内容解码，并将其保存到磁盘文件中>
    ================= CORE DUMP END ===================

建议将核心转储文本主体手动保存到文件， ``CORE DUMP START`` 和 ``CORE DUMP END`` 行不应包含在核心转储文本文件中。随后，可以使用以下命令解码保存的文本：

.. code-block:: bash

    idf.py coredump-info -c </path/to/saved/base64/text>

或

.. code-block:: bash

    idf.py coredump-debug -c </path/to/saved/base64/text>


.. _core_dump_commands:

核心转储命令
------------------

ESP-IDF 提供了一些特殊命令，用于检索和分析核心转储：

* ``idf.py coredump-info`` - 从 flash 中读取核心转储，打印崩溃任务的寄存器、调用栈、系统可用任务列表、内存区域以及核心转储中存储的内存内容（包括 TCB 和栈）。
* ``idf.py coredump-debug`` - 从 flash 中读取核心转储，将其保存为 ELF 文件，并使用该文件运行 GDB 调试会话。你可以手动检查内存、变量和任务状态。请注意，由于并未将所有内存保存在核心转储中，因此只有在栈上分配的变量的值才有意义。

运行 ``idf.py coredump-info --help`` 和 ``idf.py coredump-debug --help`` 命令可以查看更详细的使用说明。例如，它们可以将核心转储保存到文件中，避免每次执行命令时都需要从 flash 中读取。

高阶用户如果需要传递额外参数或使用自定义 ELF 文件，可直接使用 `esp-coredump <https://github.com/espressif/esp-coredump>`_ 工具。如果在 ESP-IDF 环境中使用该工具，可运行如下命令查询更多信息：

.. code-block:: bash

    esp-coredump --help


回溯中的 ROM 函数
---------------------------

程序崩溃时，某些任务和/或崩溃任务本身的调用栈中可能包含一或多个 ROM 函数。由于 ROM 不是程序 ELF 的一部分，而 GDB 需要分析函数序言来解码回溯，因此 GDB 无法解析这些调用栈。因此，在遇到第一个 ROM 函数时，调用栈解析将中断并报错。

为解决这一问题，ESP-IDF 监视器会根据目标芯片及其修订版本自动加载乐鑫提供的 `ROM ELF <https://github.com/espressif/esp-rom-elfs/releases>`_。有关 ROM ELF 的详细信息，请参阅 `esp-rom-elfs <https://github.com/espressif/esp-rom-elfs/blob/master/README.md>`_。


按需转储变量
---------------------------

通过读取变量的最后一个值，可以了解崩溃发生的根本原因。核心转储支持通过为已声明的变量添加特殊标记，在 GDB 上检索变量数据。


支持的标记和 RAM 区域
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. list::

   * ``COREDUMP_DRAM_ATTR`` 将变量放置在 DRAM 区域，该区域包含在转储中。
   :SOC_RTC_FAST_MEM_SUPPORTED or SOC_RTC_SLOW_MEM_SUPPORTED: * ``COREDUMP_RTC_ATTR`` 将变量放置在 RTC 区域，该区域包含在转储中。
   :SOC_RTC_FAST_MEM_SUPPORTED: * ``COREDUMP_RTC_FAST_ATTR`` 将变量放置在 RTC_FAST 区域，该区域包含在转储中。


示例
^^^^^^^

1. 在 :ref:`project-configuration-menu` 中启用 :ref:`COREDUMP TO FLASH <CONFIG_ESP_COREDUMP_TO_FLASH_OR_UART>`，随后保存并退出。

2. 在项目中，创建如下全局变量，放置在 DRAM 区域：

.. code-block:: bash

   // uint8_t global_var;
   COREDUMP_DRAM_ATTR uint8_t global_var;

3. 在主应用程序中，将该变量设置为任意值，并以 ``assert(0)`` 引发崩溃。

.. code-block:: bash

   global_var = 25;
   assert(0);

4. 在目标设备上构建、烧写并运行应用程序，等待转储信息。

5. 运行以下命令，在 GDB 中开始核心转储，其中 ``PORT`` 是设备的 USB 端口：

.. code-block:: bash

   idf.py coredump-debug

6. 在 GDB shell 中，输入 ``p global_var`` 获取变量内容：

.. code-block:: bash

   (gdb) p global_var
   $1 = 25 '\031'

相关文档
^^^^^^^^^^^^^^^^^

.. toctree::
    :maxdepth: 1

    core_dump_internals
