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