1 // Copyright 2021 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 // This is a very basic direct output log implementation with no buffering.
16
17 //#define PW_LOG_MODULE_NAME "ASRT"
18 //#include "pw_log/log.h"
19
20 #include <cstring>
21
22 #include "pw_assert/options.h"
23 #include "pw_assert_basic/handler.h"
24 #include "pw_preprocessor/util.h"
25 #include "pw_string/string_builder.h"
26 #include "pw_sys_io/sys_io.h"
27
28 // If 1, call C's standard abort() function on assert failure.
29 #ifndef PW_ASSERT_BASIC_ABORT
30 #define PW_ASSERT_BASIC_ABORT 1
31 #endif // PW_ASSERT_BASIC_ABORT
32
33 // TODO(pwbug/17): Expose these through the config system.
34 #define PW_ASSERT_BASIC_SHOW_BANNER 1
35 #define PW_ASSERT_BASIC_USE_COLORS 1
36
37 // ANSI color constants to control the terminal. Not Windows compatible.
38 // clang-format off
39 #if PW_ASSERT_BASIC_USE_COLORS
40 #define MAGENTA "\033[35m"
41 #define YELLOW "\033[33m"
42 #define RED "\033[31m"
43 #define GREEN "\033[32m"
44 #define BLUE "\033[96m"
45 #define BLACK "\033[30m"
46 #define YELLOW_BG "\033[43m"
47 #define WHITE_BG "\033[47m"
48 #define RED_BG "\033[41m"
49 #define BOLD "\033[1m"
50 #define RESET "\033[0m"
51 #else
52 #define MAGENTA ""
53 #define YELLOW ""
54 #define RED ""
55 #define GREEN ""
56 #define BLUE ""
57 #define BLACK ""
58 #define YELLOW_BG ""
59 #define WHITE_BG ""
60 #define RED_BG ""
61 #define BOLD ""
62 #define RESET ""
63 #endif // PW_ASSERT_BASIC_USE_COLORS
64 // clang-format on
65
66 static const char* kCrashBanner[] = {
67 " ",
68 " ▄████▄ ██▀███ ▄▄▄ ██████ ██░ ██ ",
69 " ▒██▀ ▀█ ▓██ ▒ ██▒ ▒████▄ ▒██ ▒ ▓██░ ██▒ ",
70 " ▒▓█ ▄ ▓██ ░▄█ ▒ ▒██ ▀█▄ ░ ▓██▄ ▒██▀▀██░ ",
71 " ▒▓▓▄ ▄██▒ ▒██▀▀█▄ ░██▄▄▄▄██ ▒ ██▒ ░▓█ ░██ ",
72 " ▒ ▓███▀ ░ ░██▓ ▒██▒ ▓█ ▓██▒ ▒██████▒▒ ░▓█▒░██▓ ",
73 " ░ ░▒ ▒ ░ ░ ▒▓ ░▒▓░ ▒▒ ▓▒█░ ▒ ▒▓▒ ▒ ░ ▒ ░░▒░▒ ",
74 " ░ ▒ ░▒ ░ ▒░ ▒ ▒▒ ░ ░ ░▒ ░ ░ ▒ ░▒░ ░ ",
75 " ░ ░░ ░ ░ ▒ ░ ░ ░ ░ ░░ ░ ",
76 " ░ ░ ░ ░ ░ ░ ░ ░ ░ ",
77 " ░",
78 " ",
79 };
80
81 using pw::sys_io::WriteLine;
82
83 typedef pw::StringBuffer<150> Buffer;
84
pw_assert_basic_HandleFailure(const char * file_name,int line_number,const char * function_name,const char * format,...)85 extern "C" void pw_assert_basic_HandleFailure(const char* file_name,
86 int line_number,
87 const char* function_name,
88 const char* format,
89 ...) {
90 // As a matter of usability, crashes should be visible; make it so.
91 #if PW_ASSERT_BASIC_SHOW_BANNER
92 WriteLine(RED);
93 for (const char* line : kCrashBanner) {
94 WriteLine(line);
95 }
96 WriteLine(RESET);
97 #endif // PW_ASSERT_BASIC_SHOW_BANNER
98
99 WriteLine(
100 " Welp, that didn't go as planned. "
101 "It seems we crashed. Terribly sorry!");
102 WriteLine("");
103 WriteLine(YELLOW " CRASH MESSAGE" RESET);
104 WriteLine("");
105 {
106 Buffer buffer;
107 buffer << " ";
108 va_list args;
109 va_start(args, format);
110 buffer.FormatVaList(format, args);
111 va_end(args);
112 WriteLine(buffer.view());
113 }
114
115 if (file_name != nullptr && line_number != -1) {
116 WriteLine("");
117 WriteLine(YELLOW " CRASH FILE & LINE" RESET);
118 WriteLine("");
119 {
120 Buffer buffer;
121 buffer.Format(" %s:%d", file_name, line_number);
122 WriteLine(buffer.view());
123 }
124 }
125 if (function_name != nullptr) {
126 WriteLine("");
127 WriteLine(YELLOW " CRASH FUNCTION" RESET);
128 WriteLine("");
129 {
130 Buffer buffer;
131 buffer.Format(" %s", function_name);
132 WriteLine(buffer.view());
133 }
134 }
135
136 // TODO(pwbug/95): Perhaps surprisingly, this doesn't actually crash the
137 // device. At some point we'll have a reboot BSP function or similar, but for
138 // now this is acceptable since no one is using this basic backend.
139 if (!PW_ASSERT_BASIC_DISABLE_NORETURN) {
140 if (PW_ASSERT_BASIC_ABORT) {
141 // Using exit() instead of abort() here because exit() allows for the
142 // destructors for the stdout buffers to be called. This addresses an
143 // issue that occurs when Bazel's execution wrapper binds stdout. This
144 // results in stdout going from a synchronized to a buffered file
145 // descriptor. In this case when abort() is called in a Bazel test the
146 // program exits before the stdout buffer can be synchronized with Bazel's
147 // execution wrapper, the resulting output from a test is an empty output
148 // buffer. Using exit() here allows the destructors to synchronized the
149 // stdout buffer before exiting.
150 exit(1);
151 } else {
152 WriteLine("");
153 WriteLine(MAGENTA " HANG TIME" RESET);
154 WriteLine("");
155 WriteLine(
156 " ... until a debugger joins. System is waiting in a while(1)");
157 while (1) {
158 }
159 }
160 PW_UNREACHABLE;
161 } else {
162 WriteLine("");
163 WriteLine(MAGENTA " NOTE: YOU ARE IN ASSERT BASIC TEST MODE" RESET);
164 WriteLine("");
165 WriteLine(" This build returns from the crash handler for testing.");
166 WriteLine(" If you see this message in production, your build is ");
167 WriteLine(" incorrectly configured. Search for");
168 WriteLine(" PW_ASSERT_BASIC_DISABLE_NORETURN to fix it.");
169 WriteLine("");
170 }
171 }
172