1 // Copyright 2016 The Fuchsia Authors
2 //
3 // Use of this source code is governed by a MIT-style
4 // license that can be found in the LICENSE file or at
5 // https://opensource.org/licenses/MIT
6
7 #include <arch.h>
8 #include <arch/exception.h>
9 #include <assert.h>
10 #include <err.h>
11 #include <inttypes.h>
12 #include <stdio.h>
13 #include <trace.h>
14
15 #include <object/excp_port.h>
16 #include <object/job_dispatcher.h>
17 #include <object/process_dispatcher.h>
18 #include <object/thread_dispatcher.h>
19
20 #define LOCAL_TRACE 0
21 #define TRACE_EXCEPTIONS 1
22
excp_type_to_string(uint type)23 static const char* excp_type_to_string(uint type) {
24 switch (type) {
25 case ZX_EXCP_FATAL_PAGE_FAULT:
26 return "fatal page fault";
27 case ZX_EXCP_UNDEFINED_INSTRUCTION:
28 return "undefined instruction";
29 case ZX_EXCP_GENERAL:
30 return "general fault";
31 case ZX_EXCP_SW_BREAKPOINT:
32 return "software breakpoint";
33 case ZX_EXCP_HW_BREAKPOINT:
34 return "hardware breakpoint";
35 case ZX_EXCP_UNALIGNED_ACCESS:
36 return "alignment fault";
37 case ZX_EXCP_POLICY_ERROR:
38 return "policy error";
39 default:
40 return "unknown fault";
41 }
42 }
43
44 // This isn't an "iterator" in the pure c++ sense. We don't need all that
45 // complexity. I just couldn't think of a better term.
46 //
47 // Exception ports are tried in the following order:
48 // - debugger
49 // - thread
50 // - process
51 // - job (first owning job, then its parent job, and so on up to root job)
52 // - system
53 class ExceptionPortIterator final {
54 public:
ExceptionPortIterator(ThreadDispatcher * thread)55 explicit ExceptionPortIterator(ThreadDispatcher* thread)
56 : thread_(thread),
57 previous_type_(ExceptionPort::Type::NONE) {
58 }
59
60 // Returns true with |out_eport| filled in for the next one to try.
61 // Returns false if there are no more to try.
Next(fbl::RefPtr<ExceptionPort> * out_eport)62 bool Next(fbl::RefPtr<ExceptionPort>* out_eport) {
63 fbl::RefPtr<ExceptionPort> eport;
64 ExceptionPort::Type expected_type = ExceptionPort::Type::NONE;
65
66 while (true) {
67 switch (previous_type_) {
68 case ExceptionPort::Type::NONE:
69 eport = thread_->process()->debugger_exception_port();
70 expected_type = ExceptionPort::Type::DEBUGGER;
71 break;
72 case ExceptionPort::Type::DEBUGGER:
73 eport = thread_->exception_port();
74 expected_type = ExceptionPort::Type::THREAD;
75 break;
76 case ExceptionPort::Type::THREAD:
77 eport = thread_->process()->exception_port();
78 expected_type = ExceptionPort::Type::PROCESS;
79 break;
80 case ExceptionPort::Type::PROCESS:
81 previous_job_ = thread_->process()->job();
82 eport = previous_job_->exception_port();
83 expected_type = ExceptionPort::Type::JOB;
84 break;
85 case ExceptionPort::Type::JOB:
86 previous_job_ = previous_job_->parent();
87 if (previous_job_) {
88 eport = previous_job_->exception_port();
89 expected_type = ExceptionPort::Type::JOB;
90 } else {
91 // Reached the root job and there was no handler.
92 return false;
93 }
94 break;
95 default:
96 ASSERT_MSG(0, "unexpected exception type %d",
97 static_cast<int>(previous_type_));
98 __UNREACHABLE;
99 }
100 previous_type_ = expected_type;
101 if (eport) {
102 DEBUG_ASSERT(eport->type() == expected_type);
103 *out_eport = ktl::move(eport);
104 return true;
105 }
106 }
107 __UNREACHABLE;
108 }
109
110 private:
111 ThreadDispatcher* thread_;
112 ExceptionPort::Type previous_type_;
113 // Jobs are traversed up their hierarchy. This is the previous one.
114 fbl::RefPtr<JobDispatcher> previous_job_;
115
116 DISALLOW_COPY_ASSIGN_AND_MOVE(ExceptionPortIterator);
117 };
118
try_exception_handler(fbl::RefPtr<ExceptionPort> eport,ThreadDispatcher * thread,const zx_exception_report_t * report,const arch_exception_context_t * arch_context,ThreadState::Exception * estatus)119 static zx_status_t try_exception_handler(fbl::RefPtr<ExceptionPort> eport,
120 ThreadDispatcher* thread,
121 const zx_exception_report_t* report,
122 const arch_exception_context_t* arch_context,
123 ThreadState::Exception* estatus) {
124 LTRACEF("Trying exception port type %d\n", static_cast<int>(eport->type()));
125 auto status = thread->ExceptionHandlerExchange(eport, report, arch_context, estatus);
126 LTRACEF("ExceptionHandlerExchange returned status %d, estatus %d\n", status, static_cast<int>(*estatus));
127
128 return status;
129 }
130
131 enum handler_status_t {
132 // thread is to be resumed
133 HS_RESUME,
134 // thread was killed
135 HS_KILLED,
136 // exception not handled (process will be killed)
137 HS_NOT_HANDLED
138 };
139
140 // Subroutine of dispatch_user_exception to simplify the code.
141 // One useful thing this does is guarantee ExceptionPortIterator is properly
142 // destructed.
143 // |*out_processed| is set to a boolean indicating if at least one
144 // handler processed the exception.
145
exception_handler_worker(uint exception_type,arch_exception_context_t * context,ThreadDispatcher * thread,bool * out_processed)146 static handler_status_t exception_handler_worker(uint exception_type,
147 arch_exception_context_t* context,
148 ThreadDispatcher* thread,
149 bool* out_processed) {
150 *out_processed = false;
151
152 zx_exception_report_t report;
153 ExceptionPort::BuildArchReport(&report, exception_type, context);
154
155 ExceptionPortIterator iter(thread);
156 fbl::RefPtr<ExceptionPort> eport;
157
158 while (iter.Next(&eport)) {
159 // Initialize for paranoia's sake.
160 ThreadState::Exception estatus = ThreadState::Exception::UNPROCESSED;
161 auto status = try_exception_handler(eport, thread, &report, context, &estatus);
162 LTRACEF("handler returned %d/%d\n",
163 static_cast<int>(status), static_cast<int>(estatus));
164 switch (status) {
165 case ZX_ERR_INTERNAL_INTR_KILLED:
166 // thread was killed, probably with zx_task_kill
167 return HS_KILLED;
168 case ZX_OK:
169 switch (estatus) {
170 case ThreadState::Exception::TRY_NEXT:
171 *out_processed = true;
172 break;
173 case ThreadState::Exception::RESUME:
174 return HS_RESUME;
175 default:
176 ASSERT_MSG(0, "invalid exception status %d",
177 static_cast<int>(estatus));
178 __UNREACHABLE;
179 }
180 break;
181 default:
182 // Instead of requiring exception processing to only return
183 // specific kinds of errors (and thus requiring us to be updated
184 // every time a change causes a new error to be returned), treat
185 // all other errors as fatal. It's debatable whether to give the
186 // next handler a try or immediately kill the task. By immediately
187 // killing the task we bypass the root job exception handler,
188 // but it feels safer.
189 // TODO(ZX-2853): Are there times when we should try harder to
190 // process the exception?
191 // Print something to give the user a clue.
192 printf("KERN: Error %d processing exception in user thread %lu.%lu\n",
193 status, thread->process()->get_koid(), thread->get_koid());
194 // Still mark the exception as processed so that we don't trigger
195 // later bare-bones crash reporting (TRACE_EXCEPTIONS).
196 *out_processed = true;
197 return HS_NOT_HANDLED;
198 }
199 }
200
201 return HS_NOT_HANDLED;
202 }
203
204 // Dispatches an exception to the appropriate handler. Called by arch code
205 // when it cannot handle an exception.
206 //
207 // If we return ZX_OK, the caller is expected to resume the thread "as if"
208 // nothing happened, the handler is expected to have modified state such that
209 // resumption is possible.
210 //
211 // If we return ZX_ERR_BAD_STATE, the current thread is not a user thread
212 // (i.e., not associated with a ThreadDispatcher).
213 //
214 // Otherwise, we cause the current thread to exit and do not return at all.
215 //
216 // TODO(dje): Support unwinding from this exception and introducing a different
217 // exception?
dispatch_user_exception(uint exception_type,arch_exception_context_t * context)218 zx_status_t dispatch_user_exception(uint exception_type,
219 arch_exception_context_t* context) {
220 LTRACEF("type %u, context %p\n", exception_type, context);
221
222 ThreadDispatcher* thread = ThreadDispatcher::GetCurrent();
223 if (unlikely(!thread)) {
224 // The current thread is not a user thread; bail.
225 return ZX_ERR_BAD_STATE;
226 }
227
228 // From now until the exception is resolved the thread is in an exception.
229 ThreadDispatcher::AutoBlocked by(ThreadDispatcher::Blocked::EXCEPTION);
230
231 bool processed;
232 handler_status_t hstatus = exception_handler_worker(exception_type, context,
233 thread, &processed);
234 switch (hstatus) {
235 case HS_RESUME:
236 return ZX_OK;
237 case HS_KILLED:
238 thread->Exit();
239 __UNREACHABLE;
240 case HS_NOT_HANDLED:
241 break;
242 default:
243 ASSERT_MSG(0, "unexpected exception worker result %d", static_cast<int>(hstatus));
244 __UNREACHABLE;
245 }
246
247 auto process = thread->process();
248
249 #if TRACE_EXCEPTIONS
250 if (!processed) {
251 // only print this if an exception handler wasn't involved
252 // in handling the exception
253 char pname[ZX_MAX_NAME_LEN];
254 process->get_name(pname);
255 char tname[ZX_MAX_NAME_LEN];
256 thread->get_name(tname);
257 printf("KERN: %s in user thread '%s' in process '%s'\n",
258 excp_type_to_string(exception_type), tname, pname);
259
260 arch_dump_exception_context(context);
261 }
262 #endif
263
264 // kill our process
265 process->Kill();
266
267 // exit
268 thread->Exit();
269
270 // should not get here
271 panic("fell out of thread exit somehow!\n");
272 __UNREACHABLE;
273 }
274