1 // Copyright 2017 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 <object/excp_port.h>
8 
9 #include <err.h>
10 #include <inttypes.h>
11 #include <string.h>
12 
13 #include <arch/exception.h>
14 
15 #include <object/job_dispatcher.h>
16 #include <object/port_dispatcher.h>
17 #include <object/process_dispatcher.h>
18 #include <object/thread_dispatcher.h>
19 
20 #include <fbl/alloc_checker.h>
21 
22 #include <trace.h>
23 
24 #define LOCAL_TRACE 0
25 
MakePacket(uint64_t key,uint32_t type,zx_koid_t pid,zx_koid_t tid)26 static PortPacket* MakePacket(uint64_t key, uint32_t type, zx_koid_t pid, zx_koid_t tid) {
27     if (!ZX_PKT_IS_EXCEPTION(type))
28         return nullptr;
29 
30     auto port_packet = PortDispatcher::DefaultPortAllocator()->Alloc();
31     if (!port_packet)
32         return nullptr;
33 
34     port_packet->packet.key = key;
35     port_packet->packet.type = type;
36     port_packet->packet.status = ZX_OK;
37     port_packet->packet.exception.pid = pid;
38     port_packet->packet.exception.tid = tid;
39     port_packet->packet.exception.reserved0 = 0;
40     port_packet->packet.exception.reserved1 = 0;
41 
42     return port_packet;
43 }
44 
45 // static
Create(Type type,fbl::RefPtr<PortDispatcher> port,uint64_t port_key,fbl::RefPtr<ExceptionPort> * out_eport)46 zx_status_t ExceptionPort::Create(Type type, fbl::RefPtr<PortDispatcher> port, uint64_t port_key,
47                                   fbl::RefPtr<ExceptionPort>* out_eport) {
48     fbl::AllocChecker ac;
49     auto eport = new (&ac) ExceptionPort(type, ktl::move(port), port_key);
50     if (!ac.check())
51         return ZX_ERR_NO_MEMORY;
52 
53     // ExceptionPort's ctor causes the first ref to be adopted,
54     // so we should only wrap.
55     *out_eport = fbl::WrapRefPtr<ExceptionPort>(eport);
56     return ZX_OK;
57 }
58 
ExceptionPort(Type type,fbl::RefPtr<PortDispatcher> port,uint64_t port_key)59 ExceptionPort::ExceptionPort(Type type, fbl::RefPtr<PortDispatcher> port, uint64_t port_key)
60     : type_(type), port_key_(port_key), port_(port) {
61     LTRACE_ENTRY_OBJ;
62     DEBUG_ASSERT(port_ != nullptr);
63     port_->LinkExceptionPort(this);
64 }
65 
~ExceptionPort()66 ExceptionPort::~ExceptionPort() {
67     LTRACE_ENTRY_OBJ;
68     DEBUG_ASSERT(port_ == nullptr);
69     DEBUG_ASSERT(!InContainer());
70     DEBUG_ASSERT(!IsBoundLocked());
71 }
72 
SetTarget(const fbl::RefPtr<JobDispatcher> & target)73 void ExceptionPort::SetTarget(const fbl::RefPtr<JobDispatcher>& target) {
74     canary_.Assert();
75 
76     LTRACE_ENTRY_OBJ;
77     Guard<fbl::Mutex> guard{&lock_};
78     DEBUG_ASSERT_MSG(type_ == Type::JOB || type_ == Type::JOB_DEBUGGER,
79                      "unexpected type %d", static_cast<int>(type_));
80     DEBUG_ASSERT(!IsBoundLocked());
81     DEBUG_ASSERT(target != nullptr);
82     DEBUG_ASSERT(port_ != nullptr);
83     target_ = target;
84 }
85 
SetTarget(const fbl::RefPtr<ProcessDispatcher> & target)86 void ExceptionPort::SetTarget(const fbl::RefPtr<ProcessDispatcher>& target) {
87     canary_.Assert();
88 
89     LTRACE_ENTRY_OBJ;
90     Guard<fbl::Mutex> guard{&lock_};
91     DEBUG_ASSERT_MSG(type_ == Type::DEBUGGER || type_ == Type::PROCESS,
92                      "unexpected type %d", static_cast<int>(type_));
93     DEBUG_ASSERT(!IsBoundLocked());
94     DEBUG_ASSERT(target != nullptr);
95     DEBUG_ASSERT(port_ != nullptr);
96     target_ = target;
97 }
98 
SetTarget(const fbl::RefPtr<ThreadDispatcher> & target)99 void ExceptionPort::SetTarget(const fbl::RefPtr<ThreadDispatcher>& target) {
100     canary_.Assert();
101 
102     LTRACE_ENTRY_OBJ;
103     Guard<fbl::Mutex> guard{&lock_};
104     DEBUG_ASSERT_MSG(type_ == Type::THREAD,
105                      "unexpected type %d", static_cast<int>(type_));
106     DEBUG_ASSERT(!IsBoundLocked());
107     DEBUG_ASSERT(target != nullptr);
108     DEBUG_ASSERT(port_ != nullptr);
109     target_ = target;
110 }
111 
112 // Called by PortDispatcher after unlinking us from its eport list.
OnPortZeroHandles()113 void ExceptionPort::OnPortZeroHandles() {
114     canary_.Assert();
115 
116     LTRACE_ENTRY_OBJ;
117     Guard<fbl::Mutex> guard{&lock_};
118     if (port_ == nullptr) {
119         // Already unbound. This can happen when
120         // PortDispatcher::on_zero_handles and a manual unbind (via
121         // zx_task_bind_exception_port) race with each other.
122         LTRACEF("already unbound\n");
123         DEBUG_ASSERT(!IsBoundLocked());
124         return;
125     }
126 
127     // Unbind ourselves from our target if necessary. At the end of this
128     // block, some thread (ours or another) will have called back into our
129     // ::OnTargetUnbind method, cleaning up our target/port references. The
130     // "other thread" case can happen if another thread manually unbinds after
131     // we release the lock.
132     if (!IsBoundLocked()) {
133         // Created but never bound.
134         guard.Release();
135         // Simulate an unbinding to finish cleaning up.
136         OnTargetUnbind();
137     } else {
138         switch (type_) {
139             case Type::JOB:
140             case Type::JOB_DEBUGGER: {
141                 DEBUG_ASSERT(target_ != nullptr);
142                 auto job = DownCastDispatcher<JobDispatcher>(&target_);
143                 DEBUG_ASSERT(job != nullptr);
144                 guard.Release();  // The target may call our ::OnTargetUnbind
145                 job->ResetExceptionPort(type_ == Type::JOB_DEBUGGER);
146                 break;
147             }
148             case Type::PROCESS:
149             case Type::DEBUGGER: {
150                 DEBUG_ASSERT(target_ != nullptr);
151                 auto process = DownCastDispatcher<ProcessDispatcher>(&target_);
152                 DEBUG_ASSERT(process != nullptr);
153                 guard.Release();  // The target may call our ::OnTargetUnbind
154                 process->ResetExceptionPort(type_ == Type::DEBUGGER);
155                 break;
156             }
157             case Type::THREAD: {
158                 DEBUG_ASSERT(target_ != nullptr);
159                 auto thread = DownCastDispatcher<ThreadDispatcher>(&target_);
160                 DEBUG_ASSERT(thread != nullptr);
161                 guard.Release();  // The target may call our ::OnTargetUnbind
162                 thread->ResetExceptionPort();
163                 break;
164             }
165             default:
166                 PANIC("unexpected type %d", static_cast<int>(type_));
167         }
168     }
169     // All cases must release the lock.
170     DEBUG_ASSERT(!lock_.lock().IsHeld());
171 
172 #if (LK_DEBUGLEVEL > 1)
173     // The target should have called back into ::OnTargetUnbind by this point,
174     // cleaning up our references.
175     {
176         Guard<fbl::Mutex> guard2{&lock_};
177         DEBUG_ASSERT(port_ == nullptr);
178         DEBUG_ASSERT(!IsBoundLocked());
179     }
180 #endif  // if (LK_DEBUGLEVEL > 1)
181 
182     LTRACE_EXIT_OBJ;
183 }
184 
OnTargetUnbind()185 void ExceptionPort::OnTargetUnbind() {
186     canary_.Assert();
187 
188     LTRACE_ENTRY_OBJ;
189     fbl::RefPtr<PortDispatcher> port;
190     {
191         Guard<fbl::Mutex> guard{&lock_};
192         if (port_ == nullptr) {
193             // Already unbound.
194             // This could happen if ::OnPortZeroHandles releases the
195             // lock and another thread immediately does a manual unbind
196             // via zx_task_bind_exception_port.
197             DEBUG_ASSERT(!IsBoundLocked());
198             return;
199         }
200         // Clear port_, indicating that this ExceptionPort has been unbound.
201         port_.swap(port);
202 
203         // Drop references to the target.
204         // We may not have a target if the binding (Set*Target) never happened,
205         // so don't require that we're bound.
206         target_.reset();
207     }
208     // It should actually be safe to hold our lock while calling into
209     // the PortDispatcher, but there's no reason to.
210 
211     // Unlink ourselves from the PortDispatcher's list.
212     // No-op if this method was ultimately called from
213     // PortDispatcher:on_zero_handles (via ::OnPortZeroHandles).
214     port->UnlinkExceptionPort(this);
215 
216     LTRACE_EXIT_OBJ;
217 }
218 
PortMatches(const PortDispatcher * port,bool allow_null)219 bool ExceptionPort::PortMatches(const PortDispatcher *port, bool allow_null) {
220     Guard<fbl::Mutex> guard{&lock_};
221     return (allow_null && port_ == nullptr) || port_.get() == port;
222 }
223 
SendPacketWorker(uint32_t type,zx_koid_t pid,zx_koid_t tid)224 zx_status_t ExceptionPort::SendPacketWorker(uint32_t type, zx_koid_t pid, zx_koid_t tid) {
225     Guard<fbl::Mutex> guard{&lock_};
226     LTRACEF("%s, type %u, pid %" PRIu64 ", tid %" PRIu64 "\n",
227             port_ == nullptr ? "Not sending exception report on unbound port"
228                 : "Sending exception report",
229             type, pid, tid);
230     if (port_ == nullptr) {
231         // The port has been unbound.
232         return ZX_ERR_PEER_CLOSED;
233     }
234 
235     auto iopk = MakePacket(port_key_, type, pid, tid);
236     if (!iopk)
237         return ZX_ERR_NO_MEMORY;
238 
239     zx_status_t status = port_->Queue(iopk, 0, 0);
240     if (status != ZX_OK) {
241         iopk->Free();
242     }
243     return status;
244 }
245 
SendPacket(ThreadDispatcher * thread,uint32_t type)246 zx_status_t ExceptionPort::SendPacket(ThreadDispatcher* thread, uint32_t type) {
247     canary_.Assert();
248 
249     zx_koid_t pid = thread->process()->get_koid();
250     zx_koid_t tid = thread->get_koid();
251     return SendPacketWorker(type, pid, tid);
252 }
253 
BuildReport(zx_exception_report_t * report,uint32_t type)254 void ExceptionPort::BuildReport(zx_exception_report_t* report, uint32_t type) {
255     memset(report, 0, sizeof(*report));
256     report->header.size = sizeof(*report);
257     report->header.type = type;
258 }
259 
BuildArchReport(zx_exception_report_t * report,uint32_t type,const arch_exception_context_t * arch_context)260 void ExceptionPort::BuildArchReport(zx_exception_report_t* report, uint32_t type,
261                                     const arch_exception_context_t* arch_context) {
262     BuildReport(report, type);
263     arch_fill_in_exception_context(arch_context, report);
264 }
265 
OnThreadStartForDebugger(ThreadDispatcher * thread)266 void ExceptionPort::OnThreadStartForDebugger(ThreadDispatcher* thread) {
267     canary_.Assert();
268 
269     DEBUG_ASSERT(type_ == Type::DEBUGGER);
270 
271     zx_koid_t pid = thread->process()->get_koid();
272     zx_koid_t tid = thread->get_koid();
273     LTRACEF("thread %" PRIu64 ".%" PRIu64 " started\n", pid, tid);
274 
275     zx_exception_report_t report;
276     BuildReport(&report, ZX_EXCP_THREAD_STARTING);
277     arch_exception_context_t context;
278     // There is no iframe at the moment. We'll need one (or equivalent) if/when
279     // we want to make $pc, $sp available.
280     memset(&context, 0, sizeof(context));
281     ThreadState::Exception estatus;
282     auto status = thread->ExceptionHandlerExchange(fbl::RefPtr<ExceptionPort>(this), &report, &context, &estatus);
283     if (status != ZX_OK) {
284         // Ignore any errors. There's nothing we can do here, and
285         // we still want the thread to run. It's possible the thread was
286         // killed (status == ZX_ERR_INTERNAL_INTR_KILLED), the kernel will kill the
287         // thread shortly.
288     }
289 }
290 
OnProcessStartForDebugger(ThreadDispatcher * thread)291 void ExceptionPort::OnProcessStartForDebugger(ThreadDispatcher* thread) {
292     canary_.Assert();
293 
294     DEBUG_ASSERT(type_ == Type::JOB_DEBUGGER);
295 
296     zx_koid_t pid = thread->process()->get_koid();
297     zx_koid_t tid = thread->get_koid();
298     LTRACEF("process %" PRIu64 ".%" PRIu64 " started\n", pid, tid);
299 
300     zx_exception_report_t report;
301     BuildReport(&report, ZX_EXCP_PROCESS_STARTING);
302     arch_exception_context_t context;
303     // There is no iframe at the moment. We'll need one (or equivalent) if/when
304     // we want to make $pc, $sp available.
305     memset(&context, 0, sizeof(context));
306     ThreadState::Exception estatus;
307     auto status = thread->ExceptionHandlerExchange(fbl::RefPtr<ExceptionPort>(this), &report, &context, &estatus);
308     if (status != ZX_OK) {
309         // Ignore any errors. There's nothing we can do here, and
310         // we still want the thread to run. It's possible the thread was
311         // killed (status == ZX_ERR_INTERNAL_INTR_KILLED), the kernel will kill the
312         // thread shortly.
313     }
314 }
315 
316 // This isn't called for every thread's destruction, only when a debugger
317 // is attached.
318 
OnThreadExitForDebugger(ThreadDispatcher * thread)319 void ExceptionPort::OnThreadExitForDebugger(ThreadDispatcher* thread) {
320     canary_.Assert();
321 
322     DEBUG_ASSERT(type_ == Type::DEBUGGER);
323 
324     zx_koid_t pid = thread->process()->get_koid();
325     zx_koid_t tid = thread->get_koid();
326     LTRACEF("thread %" PRIu64 ".%" PRIu64 " exited\n", pid, tid);
327 
328     zx_exception_report_t report;
329     BuildReport(&report, ZX_EXCP_THREAD_EXITING);
330     arch_exception_context_t context;
331     // There is no iframe at the moment. We'll need one (or equivalent) if/when
332     // we want to make $pc, $sp available.
333     memset(&context, 0, sizeof(context));
334     ThreadState::Exception estatus;
335     // N.B. If the process is exiting it will have killed all threads. That
336     // means all threads get marked with THREAD_SIGNAL_KILL which means this
337     // exchange will return immediately with ZX_ERR_INTERNAL_INTR_KILLED.
338     auto status = thread->ExceptionHandlerExchange(fbl::RefPtr<ExceptionPort>(this), &report, &context, &estatus);
339     if (status != ZX_OK) {
340         // Ignore any errors, we still want the thread to continue exiting.
341     }
342 }
343