Skip to content

Commit

Permalink
[ESP32] Read core dump from flash, for diagnostic crash logs (#32192)
Browse files Browse the repository at this point in the history
* [ESP32] Read core dump from flash, for diagnostic crash logs

* Restyled by clang-format

* Restyled by prettier-markdown

* add espcoredump to .wordlist

* Add some null checks and init the struct members

---------

Co-authored-by: Restyled.io <[email protected]>
  • Loading branch information
shubhamdp and restyled-commits authored Feb 20, 2024
1 parent 70e08ec commit 165da46
Show file tree
Hide file tree
Showing 8 changed files with 286 additions and 78 deletions.
3 changes: 2 additions & 1 deletion .github/.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ epochStartTime
eq
errorValue
esd
espcoredump
ESPPORT
Espressif
esptool
Expand Down Expand Up @@ -1621,4 +1622,4 @@ zephyrproject
zhengyaohan
Zigbee
zigbeealliance
zigbeethread
zigbeethread
32 changes: 31 additions & 1 deletion examples/temperature-measurement-app/esp32/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,39 @@ chip-tool pairing ble-wifi 1 SSID PASSPHRASE 20202021 3840
chip-tool diagnosticlogs retrieve-logs-request 0 0 1 0
# Read network diagnostic using BDX protocol
chip-tool diagnosticlogs retrieve-logs-request 1 0 1 0 --TransferFileDesignator network-diag.log
chip-tool interactive start
> diagnosticlogs retrieve-logs-request 1 1 1 0 --TransferFileDesignator network-diag.log
# Retrieve crash over BDX
> diagnosticlogs retrieve-logs-request 1 1 1 0 --TransferFileDesignator crash.bin
```

esp-idf supports storing and retrieving
[core dump in flash](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/core_dump.html#core-dump-to-flash).

To support that, application needs to add core dump partition's entry in
[partitons.csv](partitions.csv#7) and we need to enable few menuconfig options.

```
CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH=y
CONFIG_ESP32_COREDUMP_DATA_FORMAT_ELF=y
```

This example's partition table and sdkconfig.default are already modified

- Retrieve the core dump using diagnostic logs cluster

```
# Read crash logs over BDX
chip-tool interactive start
> diagnosticlogs retrieve-logs-request 1 1 1 0 --TransferFileDesignator crash.bin
```
- Decode the crash logs, using espcoredump.py
```
espcoredump.py --chip (CHIP) info_corefile --core /tmp/crash.bin \
--core-format elf build/chip-temperature-measurement-app.elf
```
## Optimization
Optimization related to WiFi, BLuetooth, Asserts etc are the part of this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ set(SRC_DIRS_LIST
"${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/examples/providers"
)

set(PRIV_REQUIRES_LIST chip QRCode bt nvs_flash)
set(PRIV_REQUIRES_LIST chip QRCode bt nvs_flash bootloader_support espcoredump)

if (CONFIG_ENABLE_PW_RPC)
# Append additional directories for RPC build
Expand Down Expand Up @@ -83,7 +83,7 @@ endif (CONFIG_ENABLE_PW_RPC)
idf_component_register(PRIV_INCLUDE_DIRS ${PRIV_INCLUDE_DIRS_LIST}
SRC_DIRS ${SRC_DIRS_LIST}
PRIV_REQUIRES ${PRIV_REQUIRES_LIST}
EMBED_FILES diagnostic_logs/end_user_support.log diagnostic_logs/network_diag.log diagnostic_logs/crash.log)
EMBED_FILES diagnostic_logs/end_user_support.log diagnostic_logs/network_diag.log)

include("${CHIP_ROOT}/build/chip/esp32/esp32_codegen.cmake")
chip_app_component_codegen("${CHIP_ROOT}/examples/temperature-measurement-app/temperature-measurement-common/temperature-measurement.matter")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,41 @@
#include <diagnostic-logs-provider-delegate-impl.h>
#include <lib/support/SafeInt.h>

#if defined(CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH) && defined(CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF)
#include <esp_core_dump.h>
#include <esp_flash_encrypt.h>
// Its a bit hackish but we need this in order to pull in the sizeof(core_dump_header_t)
// we can even use the static 20 but, what if that gets chagned?
#include "../include_core_dump/esp_core_dump_types.h"
#endif // defined(CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH) && defined(CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF)

using namespace chip;
using namespace chip::app::Clusters::DiagnosticLogs;

LogProvider LogProvider::sInstance;
LogProvider::CrashLogContext LogProvider::sCrashLogContext;

namespace {
bool IsValidIntent(IntentEnum intent)
{
return intent != IntentEnum::kUnknownEnumValue;
}

// end_user_support.log, network_diag.log, and crash.log files are embedded in the firmware
// end_user_support.log and network_diag.log files are embedded in the firmware
extern const uint8_t endUserSupportLogStart[] asm("_binary_end_user_support_log_start");
extern const uint8_t endUserSupportLogEnd[] asm("_binary_end_user_support_log_end");

extern const uint8_t networkDiagnosticLogStart[] asm("_binary_network_diag_log_start");
extern const uint8_t networkDiagnosticLogEnd[] asm("_binary_network_diag_log_end");

extern const uint8_t crashLogStart[] asm("_binary_crash_log_start");
extern const uint8_t crashLogEnd[] asm("_binary_crash_log_end");
} // namespace

LogProvider::~LogProvider()
{
for (auto sessionSpan : mSessionSpanMap)
for (auto sessionSpan : mSessionContextMap)
{
Platform::MemoryFree(sessionSpan.second);
}
mSessionSpanMap.clear();
mSessionContextMap.clear();
}

CHIP_ERROR LogProvider::GetLogForIntent(IntentEnum intent, MutableByteSpan & outBuffer, Optional<uint64_t> & outTimeStamp,
Expand All @@ -69,93 +75,233 @@ CHIP_ERROR LogProvider::GetLogForIntent(IntentEnum intent, MutableByteSpan & out
return CHIP_NO_ERROR;
}

const uint8_t * LogProvider::GetDataStartForIntent(IntentEnum intent)
size_t LogProvider::GetSizeForIntent(IntentEnum intent)
{
switch (intent)
{
case IntentEnum::kEndUserSupport:
return &endUserSupportLogStart[0];
return static_cast<size_t>(endUserSupportLogEnd - endUserSupportLogStart);
case IntentEnum::kNetworkDiag:
return &networkDiagnosticLogStart[0];
return static_cast<size_t>(networkDiagnosticLogEnd - networkDiagnosticLogStart);
case IntentEnum::kCrashLogs:
return &crashLogStart[0];
return GetCrashSize();
default:
return nullptr;
return 0;
}
}

size_t LogProvider::GetSizeForIntent(IntentEnum intent)
size_t LogProvider::GetCrashSize()
{
size_t outSize = 0;

#if defined(CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH) && defined(CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF)
size_t unusedOutAddr;
esp_err_t esp_err = esp_core_dump_image_get(&unusedOutAddr, &outSize);
VerifyOrReturnValue(esp_err == ESP_OK, 0, ChipLogError(DeviceLayer, "Failed to get core dump image, esp_err:%d", esp_err));
#endif // defined(CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH) && defined(CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF)

return outSize;
}

CHIP_ERROR LogProvider::MapCrashPartition(CrashLogContext * context)
{
#if defined(CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH) && defined(CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF)
size_t outAddr, outSize;
esp_err_t esp_err = esp_core_dump_image_get(&outAddr, &outSize);
VerifyOrReturnError(esp_err == ESP_OK, CHIP_ERROR(ChipError::Range::kPlatform, esp_err),
ChipLogError(DeviceLayer, "Failed to get core dump image, esp_err:%d", esp_err));

/* map the full core dump parition, including the checksum. */
esp_err = spi_flash_mmap(outAddr, outSize, SPI_FLASH_MMAP_DATA, &context->mappedAddress, &context->mappedHandle);
VerifyOrReturnError(esp_err == ESP_OK, CHIP_ERROR(ChipError::Range::kPlatform, esp_err),
ChipLogError(DeviceLayer, "Failed to mmap the crash partition, esp_err:%d", esp_err));

context->crashSize = static_cast<uint32_t>(outSize);
return CHIP_NO_ERROR;
#else
return CHIP_ERROR_NOT_FOUND;
#endif // defined(CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH) && defined(CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF)
}

CHIP_ERROR LogProvider::PrepareLogContextForIntent(LogContext * context, IntentEnum intent)
{
context->intent = intent;

switch (intent)
{
case IntentEnum::kEndUserSupport: {
context->EndUserSupport.span =
ByteSpan(&endUserSupportLogStart[0], static_cast<size_t>(endUserSupportLogEnd - endUserSupportLogStart));
}
break;

case IntentEnum::kNetworkDiag: {
context->NetworkDiag.span =
ByteSpan(&networkDiagnosticLogStart[0], static_cast<size_t>(networkDiagnosticLogEnd - networkDiagnosticLogStart));
}
break;

case IntentEnum::kCrashLogs: {
#if defined(CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH) && defined(CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF)
sCrashLogContext.Reset();
context->Crash.logContext = &sCrashLogContext;

CHIP_ERROR err = MapCrashPartition(context->Crash.logContext);
VerifyOrReturnError(err == CHIP_NO_ERROR, err, context->Crash.logContext = nullptr);

context->Crash.logContext->readOffset = sizeof(core_dump_header_t);
context->Crash.logContext->isMapped = true;
#else
return CHIP_ERROR_NOT_FOUND;
#endif // defined(CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH) && defined(CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF)
}
break;

default:
return CHIP_ERROR_INVALID_ARGUMENT;
}

return CHIP_NO_ERROR;
}

void LogProvider::CleanupLogContextForIntent(LogContext * context)
{
switch (context->intent)
{
case IntentEnum::kEndUserSupport:
return static_cast<size_t>(endUserSupportLogEnd - endUserSupportLogStart);
break;

case IntentEnum::kNetworkDiag:
return static_cast<size_t>(networkDiagnosticLogEnd - networkDiagnosticLogStart);
case IntentEnum::kCrashLogs:
return static_cast<size_t>(crashLogEnd - crashLogStart);
break;

case IntentEnum::kCrashLogs: {
#if defined(CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH) && defined(CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF)
CrashLogContext * logContext = context->Crash.logContext;
spi_flash_munmap(logContext->mappedHandle);
logContext->Reset();
#endif // defined(CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH) && defined(CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF)
}
break;

default:
return 0;
break;
}
}

CHIP_ERROR LogProvider::GetDataForIntent(LogContext * context, MutableByteSpan & outBuffer, bool & outIsEndOfLog)
{
switch (context->intent)
{
case IntentEnum::kEndUserSupport: {
auto dataSize = context->EndUserSupport.span.size();
auto count = std::min(dataSize, outBuffer.size());

VerifyOrReturnError(CanCastTo<off_t>(count), CHIP_ERROR_INVALID_ARGUMENT, outBuffer.reduce_size(0));
ReturnErrorOnFailure(CopySpanToMutableSpan(ByteSpan(context->EndUserSupport.span.data(), count), outBuffer));

outIsEndOfLog = dataSize == count;
if (!outIsEndOfLog)
{
// reduce the span after reading count bytes
context->EndUserSupport.span = context->EndUserSupport.span.SubSpan(count);
}
}
break;

case IntentEnum::kNetworkDiag: {
auto dataSize = context->NetworkDiag.span.size();
auto count = std::min(dataSize, outBuffer.size());

VerifyOrReturnError(CanCastTo<off_t>(count), CHIP_ERROR_INVALID_ARGUMENT, outBuffer.reduce_size(0));
ReturnErrorOnFailure(CopySpanToMutableSpan(ByteSpan(context->NetworkDiag.span.data(), count), outBuffer));

outIsEndOfLog = dataSize == count;
if (!outIsEndOfLog)
{
// reduce the span after reading count bytes
context->NetworkDiag.span = context->NetworkDiag.span.SubSpan(count);
}
}
break;

case IntentEnum::kCrashLogs: {
#if defined(CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH) && defined(CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF)
CrashLogContext * logContext = context->Crash.logContext;
size_t dataSize = logContext->crashSize - logContext->readOffset;
auto count = std::min(dataSize, outBuffer.size());

VerifyOrReturnError(CanCastTo<off_t>(count), CHIP_ERROR_INVALID_ARGUMENT, outBuffer.reduce_size(0));

const uint8_t * readAddr = reinterpret_cast<const uint8_t *>(logContext->mappedAddress) + logContext->readOffset;
memcpy(outBuffer.data(), readAddr, count);
outBuffer.reduce_size(count);

logContext->readOffset += count;
outIsEndOfLog = dataSize == count;
#else
outBuffer.reduce_size(0);
return CHIP_ERROR_NOT_FOUND;
#endif // defined(CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH) && defined(CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF)
}
break;

default:
return CHIP_ERROR_INVALID_ARGUMENT;
}

return CHIP_NO_ERROR;
}

CHIP_ERROR LogProvider::StartLogCollection(IntentEnum intent, LogSessionHandle & outHandle, Optional<uint64_t> & outTimeStamp,
Optional<uint64_t> & outTimeSinceBoot)
{
VerifyOrReturnValue(IsValidIntent(intent), CHIP_ERROR_INVALID_ARGUMENT);

const uint8_t * dataStart = GetDataStartForIntent(intent);
VerifyOrReturnError(dataStart, CHIP_ERROR_NOT_FOUND);

size_t dataSize = GetSizeForIntent(intent);
VerifyOrReturnError(dataSize, CHIP_ERROR_NOT_FOUND);
// In case of crash logs we can only mmap at max once, so check before doing anything
if (intent == IntentEnum::kCrashLogs)
{
VerifyOrReturnError(sCrashLogContext.isMapped == false, CHIP_ERROR_INCORRECT_STATE,
ChipLogError(DeviceLayer, "Crash partition already mapped"));
}

ByteSpan * span = reinterpret_cast<ByteSpan *>(Platform::MemoryCalloc(1, sizeof(ByteSpan)));
VerifyOrReturnValue(span, CHIP_ERROR_NO_MEMORY);
LogContext * context = reinterpret_cast<LogContext *>(Platform::MemoryCalloc(1, sizeof(LogContext)));
VerifyOrReturnValue(context != nullptr, CHIP_ERROR_NO_MEMORY);

*span = ByteSpan(dataStart, dataSize);
CHIP_ERROR err = PrepareLogContextForIntent(context, intent);
VerifyOrReturnError(err == CHIP_NO_ERROR, err, Platform::MemoryFree(context));

mLogSessionHandle++;
// If the session handle rolls over to UINT16_MAX which is invalid, reset to 0.
VerifyOrDo(mLogSessionHandle != kInvalidLogSessionHandle, mLogSessionHandle = 0);

outHandle = mLogSessionHandle;
mSessionSpanMap[mLogSessionHandle] = span;
outHandle = mLogSessionHandle;
mSessionContextMap[mLogSessionHandle] = context;

return CHIP_NO_ERROR;
}

CHIP_ERROR LogProvider::EndLogCollection(LogSessionHandle sessionHandle)
{
VerifyOrReturnValue(sessionHandle != kInvalidLogSessionHandle, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnValue(mSessionSpanMap.count(sessionHandle), CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnValue(mSessionContextMap.count(sessionHandle), CHIP_ERROR_INVALID_ARGUMENT);

ByteSpan * span = mSessionSpanMap[sessionHandle];
mSessionSpanMap.erase(sessionHandle);
LogContext * context = mSessionContextMap[sessionHandle];
VerifyOrReturnError(context, CHIP_ERROR_INCORRECT_STATE);

CleanupLogContextForIntent(context);
Platform::MemoryFree(context);
mSessionContextMap.erase(sessionHandle);

Platform::MemoryFree(span);
return CHIP_NO_ERROR;
}

CHIP_ERROR LogProvider::CollectLog(LogSessionHandle sessionHandle, MutableByteSpan & outBuffer, bool & outIsEndOfLog)
{
VerifyOrReturnValue(sessionHandle != kInvalidLogSessionHandle, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnValue(mSessionSpanMap.count(sessionHandle), CHIP_ERROR_INVALID_ARGUMENT);

ByteSpan * span = mSessionSpanMap[sessionHandle];
auto dataSize = span->size();
auto count = std::min(dataSize, outBuffer.size());

VerifyOrReturnError(CanCastTo<off_t>(count), CHIP_ERROR_INVALID_ARGUMENT, outBuffer.reduce_size(0));

ReturnErrorOnFailure(CopySpanToMutableSpan(ByteSpan(span->data(), count), outBuffer));
VerifyOrReturnValue(mSessionContextMap.count(sessionHandle), CHIP_ERROR_INVALID_ARGUMENT);

outIsEndOfLog = dataSize == count;
LogContext * context = mSessionContextMap[sessionHandle];
VerifyOrReturnError(context, CHIP_ERROR_INCORRECT_STATE);

if (!outIsEndOfLog)
{
// reduce the span after reading count bytes
*span = span->SubSpan(count);
}

return CHIP_NO_ERROR;
return GetDataForIntent(context, outBuffer, outIsEndOfLog);
}
Loading

0 comments on commit 165da46

Please sign in to comment.