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
26static 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
46zx_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, fbl::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
59ExceptionPort::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
66ExceptionPort::~ExceptionPort() {
67    LTRACE_ENTRY_OBJ;
68    DEBUG_ASSERT(port_ == nullptr);
69    DEBUG_ASSERT(!InContainer());
70    DEBUG_ASSERT(!IsBoundLocked());
71}
72
73void 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
86void 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
99void 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.
113void ExceptionPort::OnPortZeroHandles() {
114    canary_.Assert();
115
116    // TODO(ZX-988): Add a way to mark specific ports as unbinding quietly
117    // when auto-unbinding.
118    static const bool default_quietness = false;
119
120    LTRACE_ENTRY_OBJ;
121    Guard<fbl::Mutex> guard{&lock_};
122    if (port_ == nullptr) {
123        // Already unbound. This can happen when
124        // PortDispatcher::on_zero_handles and a manual unbind (via
125        // zx_task_bind_exception_port) race with each other.
126        LTRACEF("already unbound\n");
127        DEBUG_ASSERT(!IsBoundLocked());
128        return;
129    }
130
131    // Unbind ourselves from our target if necessary. At the end of this
132    // block, some thread (ours or another) will have called back into our
133    // ::OnTargetUnbind method, cleaning up our target/port references. The
134    // "other thread" case can happen if another thread manually unbinds after
135    // we release the lock.
136    if (!IsBoundLocked()) {
137        // Created but never bound.
138        guard.Release();
139        // Simulate an unbinding to finish cleaning up.
140        OnTargetUnbind();
141    } else {
142        switch (type_) {
143            case Type::JOB:
144            case Type::JOB_DEBUGGER: {
145                DEBUG_ASSERT(target_ != nullptr);
146                auto job = DownCastDispatcher<JobDispatcher>(&target_);
147                DEBUG_ASSERT(job != nullptr);
148                guard.Release();  // The target may call our ::OnTargetUnbind
149                job->ResetExceptionPort(type_ == Type::JOB_DEBUGGER, default_quietness);
150                break;
151            }
152            case Type::PROCESS:
153            case Type::DEBUGGER: {
154                DEBUG_ASSERT(target_ != nullptr);
155                auto process = DownCastDispatcher<ProcessDispatcher>(&target_);
156                DEBUG_ASSERT(process != nullptr);
157                guard.Release();  // The target may call our ::OnTargetUnbind
158                process->ResetExceptionPort(type_ == Type::DEBUGGER, default_quietness);
159                break;
160            }
161            case Type::THREAD: {
162                DEBUG_ASSERT(target_ != nullptr);
163                auto thread = DownCastDispatcher<ThreadDispatcher>(&target_);
164                DEBUG_ASSERT(thread != nullptr);
165                guard.Release();  // The target may call our ::OnTargetUnbind
166                thread->ResetExceptionPort(default_quietness);
167                break;
168            }
169            default:
170                PANIC("unexpected type %d", static_cast<int>(type_));
171        }
172    }
173    // All cases must release the lock.
174    DEBUG_ASSERT(!lock_.lock().IsHeld());
175
176#if (LK_DEBUGLEVEL > 1)
177    // The target should have called back into ::OnTargetUnbind by this point,
178    // cleaning up our references.
179    {
180        Guard<fbl::Mutex> guard2{&lock_};
181        DEBUG_ASSERT(port_ == nullptr);
182        DEBUG_ASSERT(!IsBoundLocked());
183    }
184#endif  // if (LK_DEBUGLEVEL > 1)
185
186    LTRACE_EXIT_OBJ;
187}
188
189void ExceptionPort::OnTargetUnbind() {
190    canary_.Assert();
191
192    LTRACE_ENTRY_OBJ;
193    fbl::RefPtr<PortDispatcher> port;
194    {
195        Guard<fbl::Mutex> guard{&lock_};
196        if (port_ == nullptr) {
197            // Already unbound.
198            // This could happen if ::OnPortZeroHandles releases the
199            // lock and another thread immediately does a manual unbind
200            // via zx_task_bind_exception_port.
201            DEBUG_ASSERT(!IsBoundLocked());
202            return;
203        }
204        // Clear port_, indicating that this ExceptionPort has been unbound.
205        port_.swap(port);
206
207        // Drop references to the target.
208        // We may not have a target if the binding (Set*Target) never happened,
209        // so don't require that we're bound.
210        target_.reset();
211    }
212    // It should actually be safe to hold our lock while calling into
213    // the PortDispatcher, but there's no reason to.
214
215    // Unlink ourselves from the PortDispatcher's list.
216    // No-op if this method was ultimately called from
217    // PortDispatcher:on_zero_handles (via ::OnPortZeroHandles).
218    port->UnlinkExceptionPort(this);
219
220    LTRACE_EXIT_OBJ;
221}
222
223bool ExceptionPort::PortMatches(const PortDispatcher *port, bool allow_null) {
224    Guard<fbl::Mutex> guard{&lock_};
225    return (allow_null && port_ == nullptr) || port_.get() == port;
226}
227
228zx_status_t ExceptionPort::SendPacketWorker(uint32_t type, zx_koid_t pid, zx_koid_t tid) {
229    Guard<fbl::Mutex> guard{&lock_};
230    LTRACEF("%s, type %u, pid %" PRIu64 ", tid %" PRIu64 "\n",
231            port_ == nullptr ? "Not sending exception report on unbound port"
232                : "Sending exception report",
233            type, pid, tid);
234    if (port_ == nullptr) {
235        // The port has been unbound.
236        return ZX_ERR_PEER_CLOSED;
237    }
238
239    auto iopk = MakePacket(port_key_, type, pid, tid);
240    if (!iopk)
241        return ZX_ERR_NO_MEMORY;
242
243    zx_status_t status = port_->Queue(iopk, 0, 0);
244    if (status != ZX_OK) {
245        iopk->Free();
246    }
247    return status;
248}
249
250zx_status_t ExceptionPort::SendPacket(ThreadDispatcher* thread, uint32_t type) {
251    canary_.Assert();
252
253    zx_koid_t pid = thread->process()->get_koid();
254    zx_koid_t tid = thread->get_koid();
255    return SendPacketWorker(type, pid, tid);
256}
257
258void ExceptionPort::BuildReport(zx_exception_report_t* report, uint32_t type) {
259    memset(report, 0, sizeof(*report));
260    report->header.size = sizeof(*report);
261    report->header.type = type;
262}
263
264void ExceptionPort::BuildArchReport(zx_exception_report_t* report, uint32_t type,
265                                    const arch_exception_context_t* arch_context) {
266    BuildReport(report, type);
267    arch_fill_in_exception_context(arch_context, report);
268}
269
270void ExceptionPort::OnThreadStartForDebugger(ThreadDispatcher* thread) {
271    canary_.Assert();
272
273    DEBUG_ASSERT(type_ == Type::DEBUGGER);
274
275    zx_koid_t pid = thread->process()->get_koid();
276    zx_koid_t tid = thread->get_koid();
277    LTRACEF("thread %" PRIu64 ".%" PRIu64 " started\n", pid, tid);
278
279    zx_exception_report_t report;
280    BuildReport(&report, ZX_EXCP_THREAD_STARTING);
281    arch_exception_context_t context;
282    // There is no iframe at the moment. We'll need one (or equivalent) if/when
283    // we want to make $pc, $sp available.
284    memset(&context, 0, sizeof(context));
285    ThreadDispatcher::ExceptionStatus estatus;
286    auto status = thread->ExceptionHandlerExchange(fbl::RefPtr<ExceptionPort>(this), &report, &context, &estatus);
287    if (status != ZX_OK) {
288        // Ignore any errors. There's nothing we can do here, and
289        // we still want the thread to run. It's possible the thread was
290        // killed (status == ZX_ERR_INTERNAL_INTR_KILLED), the kernel will kill the
291        // thread shortly.
292    }
293}
294
295void ExceptionPort::OnProcessStartForDebugger(ThreadDispatcher* thread) {
296    canary_.Assert();
297
298    DEBUG_ASSERT(type_ == Type::JOB_DEBUGGER);
299
300    zx_koid_t pid = thread->process()->get_koid();
301    zx_koid_t tid = thread->get_koid();
302    LTRACEF("process %" PRIu64 ".%" PRIu64 " started\n", pid, tid);
303
304    zx_exception_report_t report;
305    BuildReport(&report, ZX_EXCP_PROCESS_STARTING);
306    arch_exception_context_t context;
307    // There is no iframe at the moment. We'll need one (or equivalent) if/when
308    // we want to make $pc, $sp available.
309    memset(&context, 0, sizeof(context));
310    ThreadDispatcher::ExceptionStatus estatus;
311    auto status = thread->ExceptionHandlerExchange(fbl::RefPtr<ExceptionPort>(this), &report, &context, &estatus);
312    if (status != ZX_OK) {
313        // Ignore any errors. There's nothing we can do here, and
314        // we still want the thread to run. It's possible the thread was
315        // killed (status == ZX_ERR_INTERNAL_INTR_KILLED), the kernel will kill the
316        // thread shortly.
317    }
318}
319
320// This isn't called for every thread's destruction, only when a debugger
321// is attached.
322
323void ExceptionPort::OnThreadExitForDebugger(ThreadDispatcher* thread) {
324    canary_.Assert();
325
326    DEBUG_ASSERT(type_ == Type::DEBUGGER);
327
328    zx_koid_t pid = thread->process()->get_koid();
329    zx_koid_t tid = thread->get_koid();
330    LTRACEF("thread %" PRIu64 ".%" PRIu64 " exited\n", pid, tid);
331
332    zx_exception_report_t report;
333    BuildReport(&report, ZX_EXCP_THREAD_EXITING);
334    arch_exception_context_t context;
335    // There is no iframe at the moment. We'll need one (or equivalent) if/when
336    // we want to make $pc, $sp available.
337    memset(&context, 0, sizeof(context));
338    ThreadDispatcher::ExceptionStatus estatus;
339    // N.B. If the process is exiting it will have killed all threads. That
340    // means all threads get marked with THREAD_SIGNAL_KILL which means this
341    // exchange will return immediately with ZX_ERR_INTERNAL_INTR_KILLED.
342    auto status = thread->ExceptionHandlerExchange(fbl::RefPtr<ExceptionPort>(this), &report, &context, &estatus);
343    if (status != ZX_OK) {
344        // Ignore any errors, we still want the thread to continue exiting.
345    }
346}
347