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