Repository: openthread/openthread (language: python)

Issue:
[api][spinel] add support for logging crash dumps
This PR adds a new feature that allows platforms to log crash logs.

### Additions
- `void otPlatLogCrashDump(void)` - API that logs a crash dump using OpenThread Logging APIs
- `SPINEL_PROP_RCP_LOG_CRASH_DUMP` - spinel prop that calls `otPlatLogCrashDump()` when `Set`
- `SPINEL_CAP_RCP_LOG_CRASH_DUMP` - spinel capability denoting that a RCP supports logging crash dumps

### Usage
- For `ot-cli-ftd|mtd`, `otPlatLogCrashDump()` is called at the end of app initialization. See [main.c]
- For Host/RCP setups, during initialization, the Host gets the RCP capabilities. If the RCP has the `SPINEL_CAP_RCP_LOG_CRASH_DUMP` capability, the Host will `Set` the `SPINEL_PROP_RCP_LOG_CRASH_DUMP` property, triggering the RCP to call `otPlatLogCrashDump()`.
Request for a new hook that occurs after Host gets RCP capabilities
**Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is.

RCPs don't have a hook that's executed after the Host finishes initializing RCP capabilities.

In my use case, I'm trying to print some crash logs from the RCP using `LOG_STREAM` spinel messages to the Host after recovering from a crash.


**Describe the solution you'd like** A clear and concise description of what you want to happen.

I think there are many ways to implement something that would address the feature request. Here are a couple independent solutions I have in mind:

- Add a new [Notifier event](https://github.com/openthread/openthread/blob/fa6477197c61a7e69e47e9e8dd9a032cd38a709b/src/core/common/notifier.hpp#L67-L101) `kRcpCapabilitiesSentToHost`.
  - On the RCP, this event would be emitted at the end of [`NcpBase::HandlePropertyGet<SPINEL_PROP_CAPS>()`](https://github.com/openthread/openthread/blob/fa6477197c61a7e69e47e9e8dd9a032cd38a709b/src/ncp/ncp_base.cpp#L2068-L2069) if no error has occurred.
  - This would allow a vendor to use the event to send crash logs or any other logs upon a Host establishing a connection to the RCP using [`ExtensionBase::HandleNotifierEvents()`](https://github.com/openthread/openthread/blob/fa6477197c61a7e69e47e9e8dd9a032cd38a709b/src/core/common/notifier.cpp#L175)
  - Nothing would need to be added to the Host code.

- Add a new spinel capability and property `SPINEL_PROP/CAP_RCP_INIT_COMPLETE`.
  - After the Host gets the RCP capabilities, if a RCP has this capability, the Host sends `SPINEL_PROP_RCP_INIT_COMPLETE` to RCP.
  - The handler for this in `ncp_base_radio.cpp` would call a new `otPlatRcpInitComplete()` platform API.
    - I recognize that @abtink [expressed some concern in my PR](https://github.com/openthread/openthread/pull/9829#discussion_r1478940471) about how `otPlat...()` APIs are intended for OT core modules use. 
    - It looks like [`otPlatMultipanGet/SetActiveInstance()`](https://github.com/search?q=repo%3Aopenthread%2Fopenthread%20otPlatMultipanGetActiveInstance&type=code) is being used in a similar way to how I invision `otPlatRcpInitComplete()` being used. `otPlatMultipanGet/SetActiveInstance()` are only being called in [`NcpBase::HandlePropertyGet<SPINEL_PROP_MULTIPAN_ACTIVE_INTERFACE>()`](https://github.com/openthread/openthread/blob/4737231d64d262eb850e0da729d4b2df130033e1/src/ncp/ncp_base_radio.cpp#L589)
    - Alternatively, a callback could be added to the `NcpBase` class instead of adding a new `otPlat...()` API


**Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered.

- Adding a new spinel capability and property specifically for logging crash dumps
  - I proposed this in https://github.com/openthread/openthread/pull/9829
  - This approach would have allowed for the Host to request the latest crash dump from the RCP
- @zhanglongxia suggested in (https://github.com/openthread/openthread/pull/9829#issuecomment-1925563823) that using [`ExtensionBase::SignalNcpInit()`](https://github.com/openthread/openthread/blob/main/src/core/instance/extension_example.cpp#L79) might be a better approach.
  - I implemented this and upon initial testing using a `diag` command to trigger a fault, this solution works since the RCP crashes and resets while the Host has an existing/established connection to the RCP. See 
    - See log [ot-cli-syslog_ot-rcp-using-vendor-hook-to-print-logs.txt](https://github.com/openthread/openthread/files/15045096/ot-cli-syslog_ot-rcp-using-vendor-hook-to-print-logs.txt)
 
  - After more thorough testing, I discovered that there are still some scenarios where the RCP can send `LOG_STREAM` spinel messages to the Host before the Host is aware of the RCP capabilities, causing the logs to be missed on the Host.
  - Specifically, this occurs when I configure the RCP to crash every 2 seconds. Since the [`ExtensionBase::SignalNcpInit()`](https://github.com/openthread/openthread/blob/main/src/core/instance/extension_example.cpp#L79) hook is called at the end of the [NcpBase::NcpBase() constructor](https://github.com/openthread/openthread/blob/fa6477197c61a7e69e47e9e8dd9a032cd38a709b/src/ncp/ncp_base.cpp#L361), it's possible for the hook to be called before `ot-cli` has been started on the Host.

**Additional context** Add any other context or screenshots about the feature request here.



Discussion / hints:

Fix patch:
diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake
index eb41ec875bc..f1b1eb0cd2d 100644
--- a/etc/cmake/options.cmake
+++ b/etc/cmake/options.cmake
@@ -233,6 +233,7 @@ ot_option(OT_OTNS OPENTHREAD_CONFIG_OTNS_ENABLE "OTNS")
 ot_option(OT_PING_SENDER OPENTHREAD_CONFIG_PING_SENDER_ENABLE "ping sender" ${OT_APP_CLI})
 ot_option(OT_PLATFORM_BOOTLOADER_MODE OPENTHREAD_CONFIG_PLATFORM_BOOTLOADER_MODE_ENABLE "platform bootloader mode")
 ot_option(OT_PLATFORM_KEY_REF OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE "platform key reference secure storage")
+ot_option(OT_PLATFORM_LOG_CRASH_DUMP OPENTHREAD_CONFIG_PLATFORM_LOG_CRASH_DUMP_ENABLE "platform log crash dump")
 ot_option(OT_PLATFORM_NETIF OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE "platform netif")
 ot_option(OT_PLATFORM_POWER_CALIBRATION OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE "power calibration")
 ot_option(OT_PLATFORM_UDP OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE "platform UDP")
diff --git a/examples/apps/cli/main.c b/examples/apps/cli/main.c
index 62f7e3f1b07..d0c6be2ddc6 100644
--- a/examples/apps/cli/main.c
+++ b/examples/apps/cli/main.c
@@ -39,6 +39,7 @@
 #include <openthread/diag.h>
 #include <openthread/tasklet.h>
 #include <openthread/platform/logging.h>
+#include <openthread/platform/misc.h>
 
 #include "openthread-system.h"
 #include "cli/cli_config.h"
@@ -140,6 +141,10 @@ int main(int argc, char *argv[])
     IgnoreError(otCliSetUserCommands(kCommands, OT_ARRAY_LENGTH(kCommands), instance));
 #endif
 
+#if OPENTHREAD_CONFIG_PLATFORM_LOG_CRASH_DUMP_ENABLE
+    otPlatLogCrashDump();
+#endif
+
     while (!otSysPseudoResetWasRequested())
     {
         otTaskletsProcess(instance);
diff --git a/examples/platforms/simulation/misc.c b/examples/platforms/simulation/misc.c
index 07739a2fef8..a6d73370ca2 100644
--- a/examples/platforms/simulation/misc.c
+++ b/examples/platforms/simulation/misc.c
@@ -31,6 +31,7 @@
 #include <setjmp.h>
 #include <unistd.h>
 
+#include <openthread/logging.h>
 #include <openthread/platform/misc.h>
 
 #include "openthread-system.h"
@@ -108,3 +109,13 @@ otPlatMcuPowerState otPlatGetMcuPowerState(otInstance *aInstance)
 
     return gPlatMcuPowerState;
 }
+
+#if OPENTHREAD_CONFIG_PLATFORM_LOG_CRASH_DUMP_ENABLE
+otError otPlatLogCrashDump(void)
+{
+    otLogCritPlat("LOGGING SIMULATED CRASH DUMP");
+    otLogCritPlat("Reset Reason: %d", sPlatResetReason);
+
+    return OT_ERROR_NONE;
+}
+#endif
diff --git a/include/openthread/instance.h b/include/openthread/instance.h
index ae50bbf8d2b..ab0d5f63be4 100644
--- a/include/openthread/instance.h
+++ b/include/openthread/instance.h
@@ -53,7 +53,7 @@ extern "C" {
  * @note This number versions both OpenThread platform and user APIs.
  *
  */
-#define OPENTHREAD_API_VERSION (409)
+#define OPENTHREAD_API_VERSION (410)
 
 /**
  * @addtogroup api-instance
diff --git a/include/openthread/platform/misc.h b/include/openthread/platform/misc.h
index b1b20211112..ecde9bae66e 100644
--- a/include/openthread/platform/misc.h
+++ b/include/openthread/platform/misc.h
@@ -207,6 +207,16 @@ otError otPlatSetMcuPowerState(otInstance *aInstance, otPlatMcuPowerState aState
  */
 otPlatMcuPowerState otPlatGetMcuPowerState(otInstance *aInstance);
 
+/**
+ * Logs a crash dump using OpenThread logging APIs
+ *
+ * @note This API is an optional logging platform API. It's up to the platform layer to implement it.
+ *
+ * @retval OT_ERROR_NONE            Crash dump was logged successfully
+ * @retval OT_ERROR_NOT_CAPABLE     Platform is not capable of logging a crash dump
+ */
+otError otPlatLogCrashDump(void);
+
 /**
  * @}
  *
diff --git a/src/core/config/misc.h b/src/core/config/misc.h
index e764b887279..56dd92bed7f 100644
--- a/src/core/config/misc.h
+++ b/src/core/config/misc.h
@@ -609,6 +609,20 @@
 #define OPENTHREAD_CONFIG_BLE_TCAT_ENABLE 0
 #endif
 
+/**
+ * @def OPENTHREAD_CONFIG_PLATFORM_LOG_CRASH_DUMP_ENABLE
+ *
+ * Define to 1 to enable crash dump logging.
+ *
+ * On platforms that support crash dump logging, this feature will log a crash dump using the OT Debug Log service.
+ *
+ * Logging a crash dump requires the platform to implement the `otPlatLogCrashDump()` function.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_PLATFORM_LOG_CRASH_DUMP_ENABLE
+#define OPENTHREAD_CONFIG_PLATFORM_LOG_CRASH_DUMP_ENABLE 0
+#endif
+
 /**
  * @}
  *
diff --git a/src/lib/spinel/logger.cpp b/src/lib/spinel/logger.cpp
index 46636c7f10b..c017613433e 100644
--- a/src/lib/spinel/logger.cpp
+++ b/src/lib/spinel/logger.cpp
@@ -340,6 +340,15 @@ void Logger::LogSpinelFrame(const uint8_t *aFrame, uint16_t aLength, bool aTx)
     }
     break;
 
+    case SPINEL_PROP_RCP_LOG_CRASH_DUMP:
+    {
+        const char *name;
+        name = "log-crash-dump";
+
+        start += Snprintf(start, static_cast<uint32_t>(end - start), ", %s", name);
+    }
+    break;
+
     case SPINEL_PROP_MAC_ENERGY_SCAN_RESULT:
     case SPINEL_PROP_PHY_CHAN_MAX_POWER:
     {
diff --git a/src/lib/spinel/radio_spinel.cpp b/src/lib/spinel/radio_spinel.cpp
index 87abaef93e4..65d2123f7f1 100644
--- a/src/lib/spinel/radio_spinel.cpp
+++ b/src/lib/spinel/radio_spinel.cpp
@@ -64,6 +64,8 @@ bool RadioSpinel::sSupportsLogStream =
 
 bool RadioSpinel::sSupportsResetToBootloader = false; ///< RCP supports resetting into bootloader mode.
 
+bool RadioSpinel::sSupportsLogCrashDump = false; ///< RCP supports logging a crash dump.
+
 otRadioCaps RadioSpinel::sRadioCaps = OT_RADIO_CAPS_NONE;
 
 RadioSpinel::RadioSpinel(void)
@@ -147,6 +149,12 @@ void RadioSpinel::Init(SpinelInterface    &aSpinelInterface,
     SuccessOrExit(error = Get(SPINEL_PROP_HWADDR, SPINEL_DATATYPE_EUI64_S, sIeeeEui64.m8));
     InitializeCaps(supportsRcpApiVersion, supportsRcpMinHostApiVersion);
 
+    if (sSupportsLogCrashDump)
+    {
+        LogDebg("RCP supports crash dump logging. Requesting crash dump.");
+        SuccessOrExit(error = Set(SPINEL_PROP_RCP_LOG_CRASH_DUMP, nullptr));
+    }
+
     if (!aSkipRcpCompatibilityCheck)
     {
         SuccessOrDie(CheckRcpApiVersion(supportsRcpApiVersion, supportsRcpMinHostApiVersion));
@@ -212,8 +220,9 @@ void RadioSpinel::InitializeCaps(bool &aSupportsRcpApiVersion, bool &aSupportsRc
 
     sSupportsLogStream            = mSpinelDriver.CoprocessorHasCap(SPINEL_CAP_OPENTHREAD_LOG_METADATA);
     aSupportsRcpApiVersion        = mSpinelDriver.CoprocessorHasCap(SPINEL_CAP_RCP_API_VERSION);
-    sSupportsResetToBootloader    = mSpinelDriver.CoprocessorHasCap(SPINEL_CAP_RCP_RESET_TO_BOOTLOADER);
     aSupportsRcpMinHostApiVersion = mSpinelDriver.CoprocessorHasCap(SPINEL_CAP_RCP_MIN_HOST_API_VERSION);
+    sSupportsResetToBootloader    = mSpinelDriver.CoprocessorHasCap(SPINEL_CAP_RCP_RESET_TO_BOOTLOADER);
+    sSupportsLogCrashDump         = mSpinelDriver.CoprocessorHasCap(SPINEL_CAP_RCP_LOG_CRASH_DUMP);
 }
 
 otError RadioSpinel::CheckRadioCapabilities(void)
@@ -2001,6 +2010,9 @@ void RadioSpinel::RecoverFromRcpFailure(void)
     }
 
     --mRcpFailureCount;
+
+    SuccessOrDie(Set(SPINEL_PROP_RCP_LOG_CRASH_DUMP, nullptr));
+
     LogNote("RCP recovery is done");
 
 exit:
diff --git a/src/lib/spinel/radio_spinel.hpp b/src/lib/spinel/radio_spinel.hpp
index 1117cfb581d..6eb5e4e4c19 100644
--- a/src/lib/spinel/radio_spinel.hpp
+++ b/src/lib/spinel/radio_spinel.hpp
@@ -1247,6 +1247,7 @@ class RadioSpinel : private Logger
     static bool sIsReady;           ///< NCP ready.
     static bool sSupportsLogStream; ///< RCP supports `LOG_STREAM` property with OpenThread log meta-data format.
     static bool sSupportsResetToBootloader; ///< RCP supports resetting into bootloader mode.
+    static bool sSupportsLogCrashDump;      ///< RCP supports logging a crash dump.
 
 #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0
 
diff --git a/src/lib/spinel/spinel.c b/src/lib/spinel/spinel.c
index c85d2444cf4..683e58fdff5 100644
--- a/src/lib/spinel/spinel.c
+++ b/src/lib/spinel/spinel.c
@@ -1427,6 +1427,7 @@ const char *spinel_prop_key_to_cstr(spinel_prop_key_t prop_key)
         {SPINEL_PROP_SERVER_LEADER_SERVICES, "SERVER_LEADER_SERVICES"},
         {SPINEL_PROP_RCP_API_VERSION, "RCP_API_VERSION"},
         {SPINEL_PROP_RCP_MIN_HOST_API_VERSION, "RCP_MIN_HOST_API_VERSION"},
+        {SPINEL_PROP_RCP_LOG_CRASH_DUMP, "RCP_LOG_CRASH_DUMP"},
         {SPINEL_PROP_UART_BITRATE, "UART_BITRATE"},
         {SPINEL_PROP_UART_XON_XOFF, "UART_XON_XOFF"},
         {SPINEL_PROP_15_4_PIB_PHY_CHANNELS_SUPPORTED, "15_4_PIB_PHY_CHANNELS_SUPPORTED"},
@@ -1608,6 +1609,7 @@ const char *spinel_capability_to_cstr(spinel_capability_t capability)
         {SPINEL_CAP_RCP_API_VERSION, "RCP_API_VERSION"},
         {SPINEL_CAP_RCP_MIN_HOST_API_VERSION, "RCP_MIN_HOST_API_VERSION"},
         {SPINEL_CAP_RCP_RESET_TO_BOOTLOADER, "RCP_RESET_TO_BOOTLOADER"},
+        {SPINEL_CAP_RCP_LOG_CRASH_DUMP, "RCP_LOG_CRASH_DUMP"},
         {SPINEL_CAP_MAC_ALLOWLIST, "MAC_ALLOWLIST"},
         {SPINEL_CAP_MAC_RAW, "MAC_RAW"},
         {SPINEL_CAP_OOB_STEERING_DATA, "OOB_STEERING_DATA"},
diff --git a/src/lib/spinel/spinel.h b/src/lib/spinel/spinel.h
index c1865f37360..4d5fc28d9c2 100644
--- a/src/lib/spinel/spinel.h
+++ b/src/lib/spinel/spinel.h
@@ -1296,6 +1296,7 @@ enum
     SPINEL_CAP_RCP_API_VERSION          = (SPINEL_CAP_RCP__BEGIN + 0),
     SPINEL_CAP_RCP_MIN_HOST_API_VERSION = (SPINEL_CAP_RCP__BEGIN + 1),
     SPINEL_CAP_RCP_RESET_TO_BOOTLOADER  = (SPINEL_CAP_RCP__BEGIN + 2),
+    SPINEL_CAP_RCP_LOG_CRASH_DUMP       = (SPINEL_CAP_RCP__BEGIN + 3),
     SPINEL_CAP_RCP__END                 = 80,
 
     SPINEL_CAP_OPENTHREAD__BEGIN       = 512,
@@ -4399,6 +4400,16 @@ enum
      */
     SPINEL_PROP_RCP_MIN_HOST_API_VERSION = SPINEL_PROP_RCP__BEGIN + 1,
 
+    /// Crash Dump
+    /** Format: Empty : Write only
+     *
+     * Required capability: SPINEL_CAP_RADIO and SPINEL_CAP_RCP_LOG_CRASH_DUMP.
+     *
+     * Writing to this property instructs the RCP to log a crash dump if available.
+     *
+     */
+    SPINEL_PROP_RCP_LOG_CRASH_DUMP = SPINEL_PROP_RCP__BEGIN + 2,
+
     SPINEL_PROP_RCP__END = 0xFF,
 
     SPINEL_PROP_INTERFACE__BEGIN = 0x100,
diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp
index 96e362575b4..6b99b4d4a71 100644
--- a/src/ncp/ncp_base.cpp
+++ b/src/ncp/ncp_base.cpp
@@ -1947,6 +1947,10 @@ template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_CAPS>(void)
     SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_RCP_RESET_TO_BOOTLOADER));
 #endif
 
+#if OPENTHREAD_CONFIG_PLATFORM_LOG_CRASH_DUMP_ENABLE
+    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_RCP_LOG_CRASH_DUMP));
+#endif
+
 #if OPENTHREAD_PLATFORM_POSIX
     SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_POSIX));
 #endif
diff --git a/src/ncp/ncp_base_dispatcher.cpp b/src/ncp/ncp_base_dispatcher.cpp
index 48bd97e82a7..112f5022f99 100644
--- a/src/ncp/ncp_base_dispatcher.cpp
+++ b/src/ncp/ncp_base_dispatcher.cpp
@@ -479,6 +479,13 @@ NcpBase::PropertyHandler NcpBase::FindSetPropertyHandler(spinel_prop_key_t aKey)
 #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_SERVER_ALLOW_LOCAL_DATA_CHANGE),
 #endif
+#endif // OPENTHREAD_MTD || OPENTHREAD_FTD
+
+#if OPENTHREAD_CONFIG_PLATFORM_LOG_CRASH_DUMP_ENABLE && OPENTHREAD_RADIO
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_RCP_LOG_CRASH_DUMP),
+#endif
+
+#if OPENTHREAD_MTD || OPENTHREAD_FTD
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_CNTR_RESET),
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_CNTR_ALL_MAC_COUNTERS),
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_CNTR_MLE_COUNTERS),
@@ -487,6 +494,7 @@ NcpBase::PropertyHandler NcpBase::FindSetPropertyHandler(spinel_prop_key_t aKey)
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_CNTR_MAC_RETRY_HISTOGRAM),
 #endif
 #endif // OPENTHREAD_MTD || OPENTHREAD_FTD
+
 #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_RCP_MAC_KEY),
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_RCP_MAC_FRAME_COUNTER),
diff --git a/src/ncp/ncp_base_radio.cpp b/src/ncp/ncp_base_radio.cpp
index cad3ef52ddc..299f34100a5 100644
--- a/src/ncp/ncp_base_radio.cpp
+++ b/src/ncp/ncp_base_radio.cpp
@@ -35,6 +35,7 @@
 #include <openthread/link.h>
 #include <openthread/link_raw.h>
 #include <openthread/ncp.h>
+#include <openthread/platform/misc.h>
 #include <openthread/platform/multipan.h>
 #include <openthread/platform/radio.h>
 #include <openthread/platform/time.h>
@@ -614,6 +615,10 @@ template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_RCP_ENH_ACK_PROBING>(
 }
 #endif
 
+#if OPENTHREAD_CONFIG_PLATFORM_LOG_CRASH_DUMP_ENABLE
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_RCP_LOG_CRASH_DUMP>(void) { return otPlatLogCrashDump(); }
+#endif
+
 } // namespace Ncp
 } // namespace ot
 


Test patch:
diff --git a/script/test b/script/test
index a9a71b3b55c..ceab700ef2d 100755
--- a/script/test
+++ b/script/test
@@ -104,6 +104,7 @@ build_simulation()
         "-DOT_MESSAGE_USE_HEAP=OFF"
         "-DOT_NETDATA_PUBLISHER=ON"
         "-DOT_PING_SENDER=ON"
+        "-DOT_PLATFORM_LOG_CRASH_DUMP=ON"
         "-DOT_REFERENCE_DEVICE=ON"
         "-DOT_SERVICE=ON"
         "-DOT_SRP_CLIENT=ON"
@@ -173,6 +174,7 @@ build_posix()
         "-DBUILD_TESTING=ON"
         "-DOT_MESSAGE_USE_HEAP=ON"
         "-DOT_PLATFORM_BOOTLOADER_MODE=ON"
+        "-DOT_PLATFORM_LOG_CRASH_DUMP=ON"
         "-DOT_THREAD_VERSION=${version}"
     )
 
diff --git a/tests/unit/test_platform.cpp b/tests/unit/test_platform.cpp
index 496dc497954..c30c68ebc2c 100644
--- a/tests/unit/test_platform.cpp
+++ b/tests/unit/test_platform.cpp
@@ -844,4 +844,8 @@ OT_TOOL_WEAK void otPlatDnssdUnregisterKey(otInstance                 *aInstance
 
 #endif // OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE
 
+#if OPENTHREAD_CONFIG_PLATFORM_LOG_CRASH_DUMP_ENABLE
+OT_TOOL_WEAK otError otPlatLogCrashDump(void) { return OT_ERROR_NONE; }
+#endif
+
 } // extern "C"