|
| 1 | +//! @file |
| 2 | +//! |
| 3 | +//! Copyright (c) Memfault, Inc. |
| 4 | +//! See License.txt for details |
| 5 | + |
| 6 | +#include <stdarg.h> |
| 7 | +#include <stdio.h> |
| 8 | +#include <string.h> |
| 9 | + |
| 10 | +#include "memfault/config.h" |
| 11 | + |
| 12 | +#if MEMFAULT_COMPACT_LOG_ENABLE |
| 13 | + |
| 14 | +#include "memfault/core/compiler.h" |
| 15 | +#include "memfault/core/debug_log.h" |
| 16 | +#include "memfault/core/compact_log_helpers.h" |
| 17 | +#include "memfault/core/compact_log_serializer.h" |
| 18 | +#include "memfault/util/cbor.h" |
| 19 | + |
| 20 | +//! Compact logs are placed in a linker section named "log_fmt". This is a symbol exposed by the |
| 21 | +//! linker which points to the start of the section |
| 22 | +extern uint32_t __start_log_fmt; |
| 23 | + |
| 24 | +//! Note: We don't read this in the firmware but it is used during the decode |
| 25 | +//! process to sanity check the section is being laid out as we would expect. |
| 26 | +MEMFAULT_PUT_IN_SECTION(".log_fmt_hdr") |
| 27 | +const sMemfaultLogFmtElfSectionHeader g_memfault_log_fmt_elf_section_hdr = { |
| 28 | + .magic = 0x66474f4c, /* LOGf */ |
| 29 | + .version = 1, |
| 30 | +}; |
| 31 | + |
| 32 | +bool memfault_vlog_compact_serialize(sMemfaultCborEncoder *encoder, uint32_t log_id, |
| 33 | + uint32_t compressed_fmt, va_list args) { |
| 34 | + const uint32_t bits_per_arg = 2; |
| 35 | + const uint32_t bits_per_arg_mask = (1 << bits_per_arg) - 1; |
| 36 | + |
| 37 | + const size_t num_args = (31UL - MEMFAULT_CLZ(compressed_fmt)) / bits_per_arg; |
| 38 | + |
| 39 | + if (!memfault_cbor_encode_array_begin(encoder, 1 /* log_id */ + num_args)) { |
| 40 | + return false; |
| 41 | + } |
| 42 | + |
| 43 | + // We use an offset within the section to reduce the space needed when serializing |
| 44 | + // the log. |
| 45 | + uint32_t log_fmt_offset = log_id - (uint32_t)(uintptr_t)&__start_log_fmt; |
| 46 | + |
| 47 | + if (!memfault_cbor_encode_unsigned_integer(encoder, log_fmt_offset)) { |
| 48 | + return false; |
| 49 | + } |
| 50 | + |
| 51 | + for (size_t i = 0; i < num_args; i++) { |
| 52 | + // See memfault/core/compact_log_helpers.h for more details. In essence, |
| 53 | + // the promotion type of a given va_arg is encoded within two bits of the |
| 54 | + // compressed_fmt field. |
| 55 | + const uint32_t type = |
| 56 | + (compressed_fmt >> ((num_args - i - 1) * bits_per_arg)) & bits_per_arg_mask; |
| 57 | + |
| 58 | + bool success = false; |
| 59 | + switch (type) { |
| 60 | + |
| 61 | + case MEMFAULT_LOG_ARG_PROMOTED_TO_INT32: { |
| 62 | + int32_t val = va_arg(args, int32_t); |
| 63 | + success = memfault_cbor_encode_signed_integer(encoder, val); |
| 64 | + break; |
| 65 | + } |
| 66 | + |
| 67 | + case MEMFAULT_LOG_ARG_PROMOTED_TO_INT64: { |
| 68 | + // We differentiate between an 64 bit and 32 bit integer arg |
| 69 | + // by packing the cbor encoded int in an array. |
| 70 | + uint64_t val = va_arg(args, uint64_t); |
| 71 | + success = memfault_cbor_encode_array_begin(encoder, 1) && |
| 72 | + memfault_cbor_encode_long_signed_integer(encoder, (int64_t)val); |
| 73 | + break; |
| 74 | + } |
| 75 | + |
| 76 | + case MEMFAULT_LOG_ARG_PROMOTED_TO_DOUBLE: { |
| 77 | + // Note: Per the ARM ABI, doubles are just serialized out onto the stack |
| 78 | + // and occupy 8 bytes: |
| 79 | + // |
| 80 | + // Chapter 7: THE STANDARD VARIANTS |
| 81 | + // For a variadic function the base standard is always used both for argument passing and |
| 82 | + // result return. |
| 83 | + // |
| 84 | + // 6.5 Parameter Passing |
| 85 | + // In the base standard there are no arguments that are candidates for a co-processor |
| 86 | + // register class. |
| 87 | + // |
| 88 | + // So for ARM we could just do the following: |
| 89 | + // uint64_t val = va_arg(args, uint64_t) |
| 90 | + // |
| 91 | + // But parsing as a double is more portable and get's optimized away by compilers |
| 92 | + // like GCC anyway: https://godbolt.org/z/mVAS7D |
| 93 | + MEMFAULT_STATIC_ASSERT(sizeof(uint64_t) == sizeof(double), "double does not fit in uint64_t"); |
| 94 | + |
| 95 | + // Note: We use memcpy to properly type-pun / avoid strict-aliasing violation: |
| 96 | + // https://mflt.io/strict-aliasing-type-punning |
| 97 | + double val = va_arg(args, double); |
| 98 | + uint64_t double_as_uint64; |
| 99 | + memcpy(&double_as_uint64, &val, sizeof(val)); |
| 100 | + success = memfault_cbor_encode_uint64_as_double(encoder, double_as_uint64); |
| 101 | + break; |
| 102 | + } |
| 103 | + |
| 104 | + case MEMFAULT_LOG_ARG_PROMOTED_TO_STR: { |
| 105 | + const char *str = va_arg(args, const char*); |
| 106 | + success = memfault_cbor_encode_string(encoder, str != NULL ? str : "(null)"); |
| 107 | + break; |
| 108 | + } |
| 109 | + |
| 110 | + default: |
| 111 | + break; |
| 112 | + |
| 113 | + } |
| 114 | + |
| 115 | + if (!success) { |
| 116 | + return false; |
| 117 | + } |
| 118 | + } |
| 119 | + |
| 120 | + return true; |
| 121 | +} |
| 122 | + |
| 123 | +bool memfault_log_compact_serialize(sMemfaultCborEncoder *encoder, uint32_t log_id, |
| 124 | + uint32_t compressed_fmt, ...) { |
| 125 | + va_list args; |
| 126 | + va_start(args, compressed_fmt); |
| 127 | + const bool success = memfault_vlog_compact_serialize(encoder, log_id, compressed_fmt, args); |
| 128 | + va_end(args); |
| 129 | + |
| 130 | + return success; |
| 131 | +} |
| 132 | + |
| 133 | +#endif /* MEMFAULT_COMPACT_LOG_ENABLE */ |
0 commit comments