pw_assert_basic: add a handler facade layer Splits up the pw_assert_basic module to add a facade layer to allow a user to provide a custom pw_assert_basic_HandleFailure() implementation. The existing handler, previously named pw_Crash(), was moved to a basic_handler facade which mirrors the pw_cpu_exception structure and naming. Unlike pw_cpu_exception, pw_assert_basic defaults to the basic_handler backend. This refactor should allow users, especially during initial bringup, to trivially bring up assert support with target specific handlers with less code duplication and complications. Note that pw_assert_basic is still not recommended for production use as it is rather heavy weight, we strongly recommend using tokenized asserts. Change-Id: I3a0c0aacc9a88d44b35b24ad0a13dd7e2badc209 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/28520 Commit-Queue: Ewout van Bekkum <ewout@google.com> Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/pw_assert_basic/BUILD b/pw_assert_basic/BUILD index 22d063c..a22a266 100644 --- a/pw_assert_basic/BUILD +++ b/pw_assert_basic/BUILD
@@ -45,7 +45,30 @@ ":headers", "//pw_assert:facade", "//pw_preprocessor", + ], +) + +pw_cc_library( + name = "handler_facade", + hdrs = [ + "public/pw_assert_basic/handler.h", + ], + deps = [ + "//pw_preprocessor", + ], +) + +pw_cc_library( + name = "pw_assert_basic_handler", + srcs = [ + "basic_handler.cc", + ], + deps = [ + ":headers", + "//pw_assert:facade", + "//pw_preprocessor", "//pw_string", "//pw_sys_io", + ":handler_facade", ], )
diff --git a/pw_assert_basic/BUILD.gn b/pw_assert_basic/BUILD.gn index a43c95c..a8682f1 100644 --- a/pw_assert_basic/BUILD.gn +++ b/pw_assert_basic/BUILD.gn
@@ -14,8 +14,10 @@ import("//build_overrides/pigweed.gni") +import("$dir_pw_build/facade.gni") import("$dir_pw_build/target_types.gni") import("$dir_pw_docgen/docs.gni") +import("backend.gni") config("default_config") { include_dirs = [ "public" ] @@ -25,25 +27,41 @@ include_dirs = [ "public_overrides" ] } +pw_facade("handler") { + backend = pw_assert_basic_HANDLER_BACKEND + public_configs = [ ":default_config" ] + public_deps = [ "$dir_pw_preprocessor" ] + public = [ "public/pw_assert_basic/handler.h" ] +} + pw_source_set("pw_assert_basic") { public_configs = [ ":backend_config", ":default_config", ] - deps = [ ":core" ] - public = [ "public_overrides/pw_assert_backend/assert_backend.h" ] + deps = [ + "$dir_pw_assert:facade", + "$dir_pw_preprocessor", + pw_assert_basic_HANDLER_BACKEND, + ] + public = [ + "public/pw_assert_basic/assert_basic.h", + "public/pw_assert_basic/handler.h", + "public_overrides/pw_assert_backend/assert_backend.h", + ] + sources = [ "assert_basic.cc" ] } -pw_source_set("core") { - public_configs = [ ":default_config" ] +# A basic handler backend using sysio. +pw_source_set("basic_handler") { deps = [ + ":handler.facade", "$dir_pw_assert:facade", "$dir_pw_preprocessor", "$dir_pw_string", "$dir_pw_sys_io", ] - public = [ "public/pw_assert_basic/assert_basic.h" ] - sources = [ "assert_basic.cc" ] + sources = [ "basic_handler.cc" ] } pw_doc_group("docs") {
diff --git a/pw_assert_basic/assert_basic.cc b/pw_assert_basic/assert_basic.cc index e6a64f5..72f2e8c 100644 --- a/pw_assert_basic/assert_basic.cc +++ b/pw_assert_basic/assert_basic.cc
@@ -12,155 +12,18 @@ // License for the specific language governing permissions and limitations under // the License. -// This is a very basic direct output log implementation with no buffering. - -//#define PW_LOG_MODULE_NAME "ASRT" -//#include "pw_log/log.h" - -#include "pw_assert_basic/assert_basic.h" - -#include <cstring> - #include "pw_assert/options.h" -#include "pw_preprocessor/util.h" -#include "pw_string/string_builder.h" -#include "pw_sys_io/sys_io.h" - -// If 1, call C's standard abort() function on assert failure. -#ifndef PW_ASSERT_BASIC_ABORT -#define PW_ASSERT_BASIC_ABORT 1 -#endif // PW_ASSERT_BASIC_ABORT - -// TODO(pwbug/17): Expose these through the config system. -#define PW_ASSERT_BASIC_SHOW_BANNER 1 -#define PW_ASSERT_BASIC_USE_COLORS 1 - -// ANSI color constants to control the terminal. Not Windows compatible. -// clang-format off -#if PW_ASSERT_BASIC_USE_COLORS -#define MAGENTA "\033[35m" -#define YELLOW "\033[33m" -#define RED "\033[31m" -#define GREEN "\033[32m" -#define BLUE "\033[96m" -#define BLACK "\033[30m" -#define YELLOW_BG "\033[43m" -#define WHITE_BG "\033[47m" -#define RED_BG "\033[41m" -#define BOLD "\033[1m" -#define RESET "\033[0m" -#else -#define MAGENTA "" -#define YELLOW "" -#define RED "" -#define GREEN "" -#define BLUE "" -#define BLACK "" -#define YELLOW_BG "" -#define WHITE_BG "" -#define RED_BG "" -#define BOLD "" -#define RESET "" -#endif // PW_ASSERT_BASIC_USE_COLORS -// clang-format on - -static const char* kCrashBanner[] = { - " ", - " ▄████▄ ██▀███ ▄▄▄ ██████ ██░ ██ ", - " ▒██▀ ▀█ ▓██ ▒ ██▒ ▒████▄ ▒██ ▒ ▓██░ ██▒ ", - " ▒▓█ 💥 ▄ ▓██ ░▄█ ▒ ▒██ ▀█▄ ░ ▓██▄ ▒██▀▀██░ ", - " ▒▓▓▄ ▄██▒ ▒██▀▀█▄ ░██▄▄▄▄██ ▒ ██▒ ░▓█ ░██ ", - " ▒ ▓███▀ ░ ░██▓ ▒██▒ ▓█ ▓██▒ ▒██████▒▒ ░▓█▒░██▓ ", - " ░ ░▒ ▒ ░ ░ ▒▓ ░▒▓░ ▒▒ ▓▒█░ ▒ ▒▓▒ ▒ ░ ▒ ░░▒░▒ ", - " ░ ▒ ░▒ ░ ▒░ ▒ ▒▒ ░ ░ ░▒ ░ ░ ▒ ░▒░ ░ ", - " ░ ░░ ░ ░ ▒ ░ ░ ░ ░ ░░ ░ ", - " ░ ░ ░ ░ ░ ░ ░ ░ ░ ", - " ░", - " ", -}; - -using pw::sys_io::WriteLine; - -typedef pw::StringBuffer<150> Buffer; - -extern "C" void pw_Crash(const char* file_name, - int line_number, - const char* function_name, - const char* message, - ...) { - // As a matter of usability, crashes should be visible; make it so. -#if PW_ASSERT_BASIC_SHOW_BANNER - WriteLine(RED); - for (const char* line : kCrashBanner) { - WriteLine(line); - } - WriteLine(RESET); -#endif // PW_ASSERT_BASIC_SHOW_BANNER - - WriteLine( - " Welp, that didn't go as planned. " - "It seems we crashed. Terribly sorry!"); - WriteLine(""); - WriteLine(YELLOW " CRASH MESSAGE" RESET); - WriteLine(""); - { - Buffer buffer; - buffer << " "; - va_list args; - va_start(args, message); - buffer.FormatVaList(message, args); - va_end(args); - WriteLine(buffer.view()); - } - - WriteLine(""); - WriteLine(YELLOW " CRASH FILE & LINE" RESET); - WriteLine(""); - { - Buffer buffer; - buffer.Format(" %s:%d", file_name, line_number); - WriteLine(buffer.view()); - } - WriteLine(""); - WriteLine(YELLOW " CRASH FUNCTION" RESET); - WriteLine(""); - { - Buffer buffer; - buffer.Format(" %s", function_name); - WriteLine(buffer.view()); - } - WriteLine(""); - - // TODO(pwbug/95): Perhaps surprisingly, this doesn't actually crash the - // device. At some point we'll have a reboot BSP function or similar, but for - // now this is acceptable since no one is using this basic backend. - if (!PW_ASSERT_BASIC_DISABLE_NORETURN) { - if (PW_ASSERT_BASIC_ABORT) { - abort(); - } else { - WriteLine(MAGENTA " HANG TIME" RESET); - WriteLine(""); - WriteLine( - " ... until a debugger joins. System is waiting in a while(1)"); - while (1) { - } - } - PW_UNREACHABLE; - } else { - WriteLine(MAGENTA " NOTE: YOU ARE IN ASSERT BASIC TEST MODE" RESET); - WriteLine(""); - WriteLine(" This build returns from the crash handler for testing."); - WriteLine(" If you see this message in production, your build is "); - WriteLine(" incorrectly configured. Search for"); - WriteLine(" PW_ASSERT_BASIC_DISABLE_NORETURN to fix it."); - WriteLine(""); - } -} +#include "pw_assert_basic/handler.h" extern "C" void pw_assert_HandleFailure(void) { #if PW_ASSERT_ENABLE_DEBUG - pw_Crash("", 0, "", "Crash: PW_ASSERT() or PW_DASSERT() failure"); + pw_assert_basic_HandleFailure( + nullptr, -1, nullptr, "Crash: PW_ASSERT() or PW_DASSERT() failure"); #else - pw_Crash("", 0, "", "Crash: PW_ASSERT() failure. Note: PW_DASSERT disabled"); + pw_assert_basic_HandleFailure( + nullptr, + -1, + nullptr, + "Crash: PW_ASSERT() failure. Note: PW_DASSERT disabled"); #endif // PW_ASSERT_ENABLE_DEBUG }
diff --git a/pw_assert_basic/backend.gni b/pw_assert_basic/backend.gni new file mode 100644 index 0000000..49e9d33 --- /dev/null +++ b/pw_assert_basic/backend.gni
@@ -0,0 +1,29 @@ +# Copyright 2020 The Pigweed Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +import("//build_overrides/pigweed.gni") + +declare_args() { + # Handler backend for the pw_assert_basic module which implements + # pw_assert_basic_HandleFailure, this defaults to the basic_handler. + # + # Note: Don't confuse pw_assert_BACKEND and pw_assert_basic_HANDLER_BACKEND: + # 1) pw_assert_BACKEND must be set to dir_pw_assert_basic in order to + # use this module which ensures that asserts always invoke + # pw_assert_basic_HandleFailure. + # 2) pw_assert_basic_HANDLER_BACKEND allows you to switch out the + # implementation of the handler which is invoked (i.e. + # pw_assert_basic_HandleFailure). + pw_assert_basic_HANDLER_BACKEND = "$dir_pw_assert_basic:basic_handler" +}
diff --git a/pw_assert_basic/basic_handler.cc b/pw_assert_basic/basic_handler.cc new file mode 100644 index 0000000..db2ed8c --- /dev/null +++ b/pw_assert_basic/basic_handler.cc
@@ -0,0 +1,162 @@ +// Copyright 2020 The Pigweed Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// This is a very basic direct output log implementation with no buffering. + +//#define PW_LOG_MODULE_NAME "ASRT" +//#include "pw_log/log.h" + +#include <cstring> + +#include "pw_assert/options.h" +#include "pw_assert_basic/handler.h" +#include "pw_preprocessor/util.h" +#include "pw_string/string_builder.h" +#include "pw_sys_io/sys_io.h" + +// If 1, call C's standard abort() function on assert failure. +#ifndef PW_ASSERT_BASIC_ABORT +#define PW_ASSERT_BASIC_ABORT 1 +#endif // PW_ASSERT_BASIC_ABORT + +// TODO(pwbug/17): Expose these through the config system. +#define PW_ASSERT_BASIC_SHOW_BANNER 1 +#define PW_ASSERT_BASIC_USE_COLORS 1 + +// ANSI color constants to control the terminal. Not Windows compatible. +// clang-format off +#if PW_ASSERT_BASIC_USE_COLORS +#define MAGENTA "\033[35m" +#define YELLOW "\033[33m" +#define RED "\033[31m" +#define GREEN "\033[32m" +#define BLUE "\033[96m" +#define BLACK "\033[30m" +#define YELLOW_BG "\033[43m" +#define WHITE_BG "\033[47m" +#define RED_BG "\033[41m" +#define BOLD "\033[1m" +#define RESET "\033[0m" +#else +#define MAGENTA "" +#define YELLOW "" +#define RED "" +#define GREEN "" +#define BLUE "" +#define BLACK "" +#define YELLOW_BG "" +#define WHITE_BG "" +#define RED_BG "" +#define BOLD "" +#define RESET "" +#endif // PW_ASSERT_BASIC_USE_COLORS +// clang-format on + +static const char* kCrashBanner[] = { + " ", + " ▄████▄ ██▀███ ▄▄▄ ██████ ██░ ██ ", + " ▒██▀ ▀█ ▓██ ▒ ██▒ ▒████▄ ▒██ ▒ ▓██░ ██▒ ", + " ▒▓█ 💥 ▄ ▓██ ░▄█ ▒ ▒██ ▀█▄ ░ ▓██▄ ▒██▀▀██░ ", + " ▒▓▓▄ ▄██▒ ▒██▀▀█▄ ░██▄▄▄▄██ ▒ ██▒ ░▓█ ░██ ", + " ▒ ▓███▀ ░ ░██▓ ▒██▒ ▓█ ▓██▒ ▒██████▒▒ ░▓█▒░██▓ ", + " ░ ░▒ ▒ ░ ░ ▒▓ ░▒▓░ ▒▒ ▓▒█░ ▒ ▒▓▒ ▒ ░ ▒ ░░▒░▒ ", + " ░ ▒ ░▒ ░ ▒░ ▒ ▒▒ ░ ░ ░▒ ░ ░ ▒ ░▒░ ░ ", + " ░ ░░ ░ ░ ▒ ░ ░ ░ ░ ░░ ░ ", + " ░ ░ ░ ░ ░ ░ ░ ░ ░ ", + " ░", + " ", +}; + +using pw::sys_io::WriteLine; + +typedef pw::StringBuffer<150> Buffer; + +extern "C" void pw_assert_basic_HandleFailure(const char* file_name, + int line_number, + const char* function_name, + const char* format, + ...) { + // As a matter of usability, crashes should be visible; make it so. +#if PW_ASSERT_BASIC_SHOW_BANNER + WriteLine(RED); + for (const char* line : kCrashBanner) { + WriteLine(line); + } + WriteLine(RESET); +#endif // PW_ASSERT_BASIC_SHOW_BANNER + + WriteLine( + " Welp, that didn't go as planned. " + "It seems we crashed. Terribly sorry!"); + WriteLine(""); + WriteLine(YELLOW " CRASH MESSAGE" RESET); + WriteLine(""); + { + Buffer buffer; + buffer << " "; + va_list args; + va_start(args, format); + buffer.FormatVaList(format, args); + va_end(args); + WriteLine(buffer.view()); + } + + if (file_name != nullptr && line_number != -1) { + WriteLine(""); + WriteLine(YELLOW " CRASH FILE & LINE" RESET); + WriteLine(""); + { + Buffer buffer; + buffer.Format(" %s:%d", file_name, line_number); + WriteLine(buffer.view()); + } + } + if (function_name != nullptr) { + WriteLine(""); + WriteLine(YELLOW " CRASH FUNCTION" RESET); + WriteLine(""); + { + Buffer buffer; + buffer.Format(" %s", function_name); + WriteLine(buffer.view()); + } + } + + // TODO(pwbug/95): Perhaps surprisingly, this doesn't actually crash the + // device. At some point we'll have a reboot BSP function or similar, but for + // now this is acceptable since no one is using this basic backend. + if (!PW_ASSERT_BASIC_DISABLE_NORETURN) { + if (PW_ASSERT_BASIC_ABORT) { + abort(); + } else { + WriteLine(""); + WriteLine(MAGENTA " HANG TIME" RESET); + WriteLine(""); + WriteLine( + " ... until a debugger joins. System is waiting in a while(1)"); + while (1) { + } + } + PW_UNREACHABLE; + } else { + WriteLine(""); + WriteLine(MAGENTA " NOTE: YOU ARE IN ASSERT BASIC TEST MODE" RESET); + WriteLine(""); + WriteLine(" This build returns from the crash handler for testing."); + WriteLine(" If you see this message in production, your build is "); + WriteLine(" incorrectly configured. Search for"); + WriteLine(" PW_ASSERT_BASIC_DISABLE_NORETURN to fix it."); + WriteLine(""); + } +}
diff --git a/pw_assert_basic/docs.rst b/pw_assert_basic/docs.rst index 45ff8f7..2ddc367 100644 --- a/pw_assert_basic/docs.rst +++ b/pw_assert_basic/docs.rst
@@ -7,5 +7,60 @@ -------- Overview -------- -This is a simple assert backend to implement the ``pw_assert`` facade. +This is a simple assert backend to implement the ``pw_assert`` facade which +relies on a single function ``pw_assert_basic_HandleFailure`` handler facade +which defaults to the ``basic_handler`` backend. Users may be interested in +overriding this default in case they need to do things like transition to +crash time logging or implementing application specific reset and/or hang +behavior. +.. attention:: + + This log backend comes with a very large ROM and potentially RAM cost. It is + intended mostly for ease of initial bringup. We encourage teams to use + tokenized asserts since they are much smaller both in terms of ROM and RAM. + +Custom handler backend example +------------------------------ +Here is a typical usage example implementing a simple handler backend which uses +a UART backed sys_io implementation to print during crash time and then reboots. +Note that this example uses CMSIS and a psuedo STM HAL, as a backend implementer +you are responsible for using whatever APIs make sense for your use case(s). + +.. code-block:: cpp + + #include "cmsis.h" + #include "hal.h" + #include "pw_string/string_builder.h" + + using pw::sys_io::WriteLine; + + extern "C" void pw_assert_basic_HandleFailure( + [[maybe_unused]] const char* file_name, + [[maybe_unused]] int line_number, + [[maybe_unused]] const char* function_name, + const char* message, + ...) { + // Global interrupt disable for a single core microcontroller. + __disable_irq(); + + // Re-initialize the UART to ensure it's safe to use at crash time. + HAL_UART_DeInit(sys_io_uart); + HAL_UART_Init(sys_io_uart); + + WriteLine( + " Welp, that didn't go as planned. " + "It seems we crashed. Terribly sorry! Assert reason:"); + { + pw::StringBuffer<150> buffer; + buffer << " "; + va_list args; + va_start(args, format); + buffer.FormatVaList(format, args); + va_end(args); + WriteLine(buffer.view()); + } + + // Reboot the microcontroller. + HAL_NVIC_SystemReset(); + }
diff --git a/pw_assert_basic/public/pw_assert_basic/assert_basic.h b/pw_assert_basic/public/pw_assert_basic/assert_basic.h index 269c0a6..cc933d0 100644 --- a/pw_assert_basic/public/pw_assert_basic/assert_basic.h +++ b/pw_assert_basic/public/pw_assert_basic/assert_basic.h
@@ -13,42 +13,26 @@ // the License. #pragma once +#include "pw_assert_basic/handler.h" #include "pw_preprocessor/compiler.h" #include "pw_preprocessor/util.h" -// This is needed for testing the basic crash handler. -// TODO(pwbug/17): Replace when Pigweed config system is added. -#define PW_ASSERT_BASIC_DISABLE_NORETURN 0 -#if PW_ASSERT_BASIC_DISABLE_NORETURN -#define PW_ASSERT_NORETURN -#else -#define PW_ASSERT_NORETURN PW_NO_RETURN -#endif - -PW_EXTERN_C_START - -// Crash, including a message with the listed attributes. -void pw_Crash(const char* file_name, - int line_number, - const char* function_name, - const char* message, - ...) PW_PRINTF_FORMAT(4, 5) PW_ASSERT_NORETURN; - -PW_EXTERN_C_END +// Die with a message with many attributes included. This is the crash macro +// frontend that funnels everything into the C handler provided by the user, +// pw_assert_basic_HandleFailure(). +#define PW_HANDLE_CRASH(...) \ + pw_assert_basic_HandleFailure( \ + __FILE__, __LINE__, __func__ PW_COMMA_ARGS(__VA_ARGS__)) // Die with a message with many attributes included. This is the crash macro -// frontend that funnels everything into the C handler above, pw_Crash(). -#define PW_HANDLE_CRASH(...) \ - pw_Crash(__FILE__, __LINE__, __func__ PW_COMMA_ARGS(__VA_ARGS__)) - -// Die with a message with many attributes included. This is the crash macro -// frontend that funnels everything into the C handler above, pw_Crash(). -#define PW_HANDLE_ASSERT_FAILURE(condition_string, message, ...) \ - pw_Crash(__FILE__, \ - __LINE__, \ - __func__, \ - "Check failed: " condition_string \ - ". " message PW_COMMA_ARGS(__VA_ARGS__)) +// frontend that funnels everything into the C handler provided by the user, +// pw_assert_basic_HandleFailure(). +#define PW_HANDLE_ASSERT_FAILURE(condition_string, message, ...) \ + pw_assert_basic_HandleFailure(__FILE__, \ + __LINE__, \ + __func__, \ + "Check failed: " condition_string \ + ". " message PW_COMMA_ARGS(__VA_ARGS__)) // Sample assert failure message produced by the below implementation: // @@ -65,7 +49,8 @@ arg_b_val, \ type_fmt, \ message, ...) \ - pw_Crash(__FILE__, \ + pw_assert_basic_HandleFailure( \ + __FILE__, \ __LINE__, \ __func__, \ "Check failed: " \
diff --git a/pw_assert_basic/public/pw_assert_basic/handler.h b/pw_assert_basic/public/pw_assert_basic/handler.h new file mode 100644 index 0000000..33dd354 --- /dev/null +++ b/pw_assert_basic/public/pw_assert_basic/handler.h
@@ -0,0 +1,45 @@ +// Copyright 2020 The Pigweed Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +#pragma once + +#include "pw_preprocessor/compiler.h" +#include "pw_preprocessor/util.h" + +// This is needed for testing the basic crash handler. +// TODO(pwbug/17): Replace when Pigweed config system is added. +#define PW_ASSERT_BASIC_DISABLE_NORETURN 0 +#if PW_ASSERT_BASIC_DISABLE_NORETURN +#define PW_ASSERT_NORETURN +#else +#define PW_ASSERT_NORETURN PW_NO_RETURN +#endif + +PW_EXTERN_C_START + +// Application-defined assert failure handler for pw_assert_basic. +// file_name - may be nullptr if not available +// line_number - may be -1 if not available +// function_name - may be nullptr if not available +// format & varags - The assert reason can be built using the format string and +// the varargs. +// +// Applications must define this function; it is not defined by pw_assert_basic. +void pw_assert_basic_HandleFailure(const char* file_name, + int line_number, + const char* function_name, + const char* format, + ...) + PW_PRINTF_FORMAT(4, 5) PW_ASSERT_NORETURN; + +PW_EXTERN_C_END