| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/developer/debug/zxdb/console/commands/verb_async_backtrace.h" |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| |
| #include "src/developer/debug/shared/string_util.h" |
| #include "src/developer/debug/zxdb/client/frame.h" |
| #include "src/developer/debug/zxdb/client/target.h" |
| #include "src/developer/debug/zxdb/client/thread.h" |
| #include "src/developer/debug/zxdb/common/file_util.h" |
| #include "src/developer/debug/zxdb/common/join_callbacks.h" |
| #include "src/developer/debug/zxdb/console/async_output_buffer.h" |
| #include "src/developer/debug/zxdb/console/command.h" |
| #include "src/developer/debug/zxdb/console/command_utils.h" |
| #include "src/developer/debug/zxdb/console/format_location.h" |
| #include "src/developer/debug/zxdb/console/format_name.h" |
| #include "src/developer/debug/zxdb/console/format_node_console.h" |
| #include "src/developer/debug/zxdb/console/output_buffer.h" |
| #include "src/developer/debug/zxdb/console/string_util.h" |
| #include "src/developer/debug/zxdb/console/verbs.h" |
| #include "src/developer/debug/zxdb/expr/expr.h" |
| #include "src/developer/debug/zxdb/expr/expr_value.h" |
| #include "src/developer/debug/zxdb/expr/expr_value_source.h" |
| #include "src/developer/debug/zxdb/expr/find_name.h" |
| #include "src/developer/debug/zxdb/expr/found_member.h" |
| #include "src/developer/debug/zxdb/expr/resolve_base.h" |
| #include "src/developer/debug/zxdb/expr/resolve_collection.h" |
| #include "src/developer/debug/zxdb/expr/resolve_ptr_ref.h" |
| #include "src/developer/debug/zxdb/expr/resolve_variant.h" |
| #include "src/developer/debug/zxdb/symbols/collection.h" |
| #include "src/developer/debug/zxdb/symbols/data_member.h" |
| #include "src/developer/debug/zxdb/symbols/identifier.h" |
| #include "src/developer/debug/zxdb/symbols/process_symbols.h" |
| #include "src/developer/debug/zxdb/symbols/symbol.h" |
| #include "src/developer/debug/zxdb/symbols/template_parameter.h" |
| #include "src/lib/fxl/memory/ref_ptr.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| // NOTE: we only support async Rust on Fuchsia for now. Async backtraces in other programming |
| // languages with different designs will require different approaches. |
| // |
| // An asynchronous backtrace is fundamentally different from a synchronous backtrace in that |
| // |
| // * An asynchronous backtrace is a tree of futures rather than a list of frames, as one future |
| // could await multiple other futures. |
| // * To show a synchronous backtrace, the unwinder walks the stack and uncovers register values |
| // of previous frames. Then the debugger symbolizes those frames and could display variables by |
| // reading the stack. To show an asynchronous backtrace, the debugger finds the executor, list |
| // active tasks, and displays futures recursively. Since Rust doesn't keep the stack or register |
| // values for non-running futures, the information is limited to the state stored in the memory. |
| // * Since any data types in Rust can be futures and the implementation of their poll methods can |
| // be arbitrary, it's impossible for us to find all futures and detect every dependency between |
| // them. Instead, we only focus on the async functions or async blocks that are usually the more |
| // interesting part during debugging. |
| |
| constexpr int kVerbose = 1; |
| constexpr int kMoreVerbose = 2; |
| |
| const char kAsyncBacktraceShortHelp[] = "async-backtrace / abt: Display all async tasks."; |
| const char kAsyncBacktraceHelp[] = |
| R"(async-backtrace |
| |
| Alias: "abt" |
| |
| Print a tree of async tasks from the main future in the current thread. |
| |
| Arguments |
| |
| -v |
| --verbose |
| Include extra information |
| |
| --more-verbose |
| Include more extra information |
| |
| Examples |
| |
| abt |
| abt -v |
| process 2 abt |
| )"; |
| |
| const std::string kAwaiteeMarker = "└─ "; |
| |
| struct FormatFutureOptions { |
| bool verbose = false; |
| ConsoleFormatOptions variable; // options to format variables |
| }; |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatFuture(const ExprValue& future, |
| const FormatFutureOptions& options, |
| const fxl::RefPtr<EvalContext>& context, int indent, |
| const std::string& awaiter_file_name = ""); |
| |
| // Strip the template part of a type. This bypasses the complexity in ParsedIdentifier and should |
| // be sufficient. |
| std::string_view StripTemplate(std::string_view type_name) { |
| return type_name.substr(0, type_name.find('<')); |
| } |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatMessage(Syntax syntax, const std::string& str) { |
| auto out = fxl::MakeRefCounted<AsyncOutputBuffer>(); |
| out->Complete(syntax, str + '\n'); |
| return out; |
| } |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatError(std::string msg, const Err& err = Err()) { |
| if (err.has_error()) |
| msg += ": " + err.msg(); |
| return FormatMessage(Syntax::kWarning, msg); |
| } |
| |
| bool IsAsyncFunctionOrBlock(Type* type) { |
| if (type->GetIdentifier().components().empty()) |
| return false; |
| |
| // {async_fn_env#0} or {async_block_env#0} |
| return debug::StringStartsWith(type->GetIdentifier().components().back().name(), "{async_"); |
| } |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatAsyncFunctionOrBlock(const ExprValue& future, |
| const FormatFutureOptions& options, |
| const fxl::RefPtr<EvalContext>& context, |
| int indent) { |
| auto out = fxl::MakeRefCounted<AsyncOutputBuffer>(); |
| |
| // Resolve the current value of the async function. |
| fxl::RefPtr<DataMember> member; |
| ErrOrValue varient_val = ResolveSingleVariantValue(context, future, &member); |
| if (varient_val.has_error()) |
| return FormatError("Cannot resolve async function", varient_val.err()); |
| |
| // This should be a struct, e.g., async_rust::main::func::λ::Suspend0. |
| ExprValue value = varient_val.take_value(); |
| |
| // Print the name of the original function, and put the state in a comment. |
| Identifier ident = value.type()->GetIdentifier(); |
| std::string state = ident.components()[ident.components().size() - 1].name(); |
| ident.components().resize(ident.components().size() - 2); |
| out->Append(FormatIdentifier(ident, {})); |
| |
| if (!debug::StringStartsWith(state, "Suspend")) |
| out->Append(Syntax::kComment, " (" + state + ")"); |
| |
| std::string filename; |
| if (member->decl_line().is_valid()) { |
| filename = ExtractLastFileComponent(member->decl_line().file()); |
| out->Append(" " + GetBullet() + " "); |
| out->Append( |
| FormatFileLine(member->decl_line(), context->GetProcessSymbols()->target_symbols())); |
| } |
| out->Append("\n"); |
| |
| // Iterate data members and find the awaitee. |
| std::optional<ExprValue> awaitee; |
| std::set<std::string> printed; |
| if (const Collection* coll = value.type()->As<Collection>()) { |
| for (const auto& lazy_member : coll->data_members()) { |
| const DataMember* member = lazy_member.Get()->As<DataMember>(); |
| // Skip compiler-generated data and static data. |
| if (!member || member->artificial() || member->is_external()) |
| continue; |
| |
| std::string name = member->GetAssignedName(); |
| ErrOrValue val = ResolveNonstaticMember(context, value, FoundMember(coll, member)); |
| if (val.has_error()) |
| continue; |
| if (name == "__awaitee") { |
| awaitee = val.take_value(); |
| } else if (options.verbose && printed.emplace(name).second) { |
| // For some reason Rust could repeat the same field twice. |
| out->Append(std::string(indent + 2, ' ')); |
| out->Append(FormatValueForConsole(val.take_value(), options.variable, context, name)); |
| out->Append("\n"); |
| } |
| } |
| } |
| if (awaitee) { |
| out->Append(std::string(indent, ' ') + kAwaiteeMarker); |
| out->Append(FormatFuture(*awaitee, options, context, indent + 3, filename)); |
| } |
| out->Complete(); |
| return out; |
| } |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatSelectJoin(const ExprValue& future, |
| const FormatFutureOptions& options, |
| const fxl::RefPtr<EvalContext>& context, int indent, |
| const std::string& select_or_join) { |
| auto out = fxl::MakeRefCounted<AsyncOutputBuffer>(); |
| out->Append(select_or_join + "!\n", TextForegroundColor::kCyan); |
| |
| // f should be a lambda. |
| ErrOrValue err_or_f = ResolveNonstaticMember(context, future, {"f"}); |
| if (err_or_f.has_error()) |
| return FormatError("Cannot read f from PollFn", err_or_f.err()); |
| |
| ExprValue f = err_or_f.take_value(); |
| const Collection* f_coll = f.type()->As<Collection>(); |
| if (!f_coll) |
| return FormatError("Wrong type for f in PollFn"); |
| |
| for (const auto& lazy_member : f_coll->data_members()) { |
| const DataMember* member = lazy_member.Get()->As<DataMember>(); |
| if (!member || member->artificial() || member->is_external()) |
| continue; |
| ErrOrValue member_val = ResolveNonstaticMember(context, f, FoundMember(f_coll, member)); |
| // Each member_val should be a future. |
| if (member_val) { |
| out->Append(std::string(indent, ' ') + kAwaiteeMarker); |
| out->Append(FormatFuture(member_val.take_value(), options, context, indent + 3)); |
| } |
| } |
| |
| out->Complete(); |
| return out; |
| } |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatFuse(const ExprValue& fuse, const FormatFutureOptions& options, |
| const fxl::RefPtr<EvalContext>& context, int indent) { |
| ErrOrValue inner = ResolveNonstaticMember(context, fuse, {"inner"}); |
| if (inner.has_error()) |
| return FormatError("Invalid Fuse (1)", inner.err()); |
| // |inner| should be an option. |
| ErrOrValue some = ResolveSingleVariantValue(context, inner.value()); |
| if (some.has_error()) |
| return FormatError("Invalid Fuse (2)", some.err()); |
| if (some.value().type()->GetAssignedName() == "None") |
| return FormatMessage(Syntax::kComment, "(terminated)"); |
| ErrOrValue future = ResolveNonstaticMember(context, some.value(), {"__0"}); |
| if (future.has_error()) |
| return FormatError("Invalid Fuse (3)", future.err()); |
| return FormatFuture(future.value(), options, context, indent); |
| } |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatThen(const ExprValue& then, const FormatFutureOptions& options, |
| const fxl::RefPtr<EvalContext>& context, int indent) { |
| ErrOrValue val = ResolveNonstaticMember(context, then, {"inner"}); |
| if (val.has_error()) |
| return FormatError("Invalid Then (1)", val.err()); |
| val = ResolveSingleVariantValue(context, val.value()); |
| if (val.has_error()) |
| return FormatError("Invalid Then (2)", val.err()); |
| val = ResolveNonstaticMember(context, val.value(), {"f"}); |
| if (val.has_error()) |
| return FormatError("Invalid Then (3)", val.err()); |
| return FormatFuture(val.value(), options, context, indent); |
| } |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatMap(const ExprValue& map, const FormatFutureOptions& options, |
| const fxl::RefPtr<EvalContext>& context, int indent) { |
| ErrOrValue val = ResolveSingleVariantValue(context, map); |
| if (val.has_error()) |
| return FormatError("Invalid Map (1)", val.err()); |
| val = ResolveNonstaticMember(context, val.value(), {"future"}); |
| if (val.has_error()) |
| return FormatError("Invalid Map (2)", val.err()); |
| return FormatFuture(val.value(), options, context, indent); |
| } |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatMapDebug(const ExprValue& map, |
| const FormatFutureOptions& options, |
| const fxl::RefPtr<EvalContext>& context, int indent) { |
| ErrOrValue val = ResolveNonstaticMember(context, map, {"inner"}); |
| if (val.has_error()) |
| return FormatError("Invalid Map (0)", val.err()); |
| return FormatMap(val.value(), options, context, indent); |
| } |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatMaybeDone(const ExprValue& maybe_done, |
| const FormatFutureOptions& options, |
| const fxl::RefPtr<EvalContext>& context, |
| int indent) { |
| ErrOrValue some = ResolveSingleVariantValue(context, maybe_done); |
| if (some.has_error()) |
| return FormatError("Invalid MaybeDone (1)", some.err()); |
| if (some.value().type()->GetAssignedName() == "Future") { |
| ErrOrValue future = ResolveNonstaticMember(context, some.value(), {"__0"}); |
| if (future.has_error()) |
| return FormatError("Invalid MaybeDone (2)", future.err()); |
| return FormatFuture(future.value(), options, context, indent); |
| } |
| return FormatMessage(Syntax::kComment, "(" + some.value().type()->GetAssignedName() + ")"); |
| } |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatRemote(const ExprValue& remote, |
| const FormatFutureOptions& options, |
| const fxl::RefPtr<EvalContext>& context, int indent) { |
| ErrOrValue future = ResolveNonstaticMember(context, remote, {"future", "future", "__0"}); |
| if (future.has_error()) |
| return FormatError("Invalid Remote", future.err()); |
| return FormatFuture(future.value(), options, context, indent); |
| } |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatPin(const ExprValue& pin, const FormatFutureOptions& options, |
| const fxl::RefPtr<EvalContext>& context, int indent) { |
| // Pin changed the member variable name to "__pointer" in |
| // https://fuchsia.googlesource.com/third_party/rust/+/346397d081d289db20bc5cbdc23c96e00e60825f |
| // So if this fails we should try the old variable. |
| ErrOrValue pointer = ResolveNonstaticMember(context, pin, {"__pointer"}); |
| if (pointer.has_error()) { |
| pointer = ResolveNonstaticMember(context, pin, {"pointer"}); |
| |
| if (pointer.has_error()) |
| return FormatError("Invalid Pin", pointer.err()); |
| } |
| |
| // Let FormatFuture to resolve pointer. |
| return FormatFuture(pointer.value(), options, context, indent); |
| } |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatTaskRunner(const ExprValue& task_runner, |
| const FormatFutureOptions& options, |
| const fxl::RefPtr<EvalContext>& context, |
| int indent) { |
| ErrOrValue future = ResolveNonstaticMember(context, task_runner, {"task"}); |
| if (future.has_error()) |
| return FormatError("Invalid TaskRunner", future.err()); |
| return FormatFuture(future.value(), options, context, indent); |
| } |
| |
| fxl::RefPtr<AsyncOutputBuffer> FormatFuture(const ExprValue& future, |
| const FormatFutureOptions& options, |
| const fxl::RefPtr<EvalContext>& context, int indent, |
| const std::string& awaiter_file_name) { |
| std::string_view type = StripTemplate(future.type()->GetFullName()); |
| |
| // Resolve pointers first. A pointer could be either non-dyn or dyn, raw or boxed. |
| // |
| // A non-dyn pointer (raw or boxed) is a ModifiedType. |
| // A dyn raw pointer is a Collection and has a name "*mut dyn ..." or "*mut (dyn ... + ...)". |
| // A dyn boxed pointer has the same layout but with a name "alloc::boxed::Box<(dyn ... + ...)>". |
| if (future.type()->As<ModifiedType>() || debug::StringStartsWith(type, "*mut ") || |
| type == "alloc::boxed::Box") { |
| auto out = fxl::MakeRefCounted<AsyncOutputBuffer>(); |
| ResolvePointer(context, future, [=](ErrOrValue val) { |
| if (val.has_error()) { |
| out->Complete(FormatError("Fail to resolve pointer", val.err())); |
| } else { |
| out->Complete(FormatFuture(val.value(), options, context, indent)); |
| } |
| }); |
| return out; |
| } |
| |
| if (IsAsyncFunctionOrBlock(future.type())) |
| return FormatAsyncFunctionOrBlock(future, options, context, indent); |
| |
| if (type == "futures_util::future::future::fuse::Fuse") |
| return FormatFuse(future, options, context, indent); |
| if (type == "futures_util::future::maybe_done::MaybeDone") |
| return FormatMaybeDone(future, options, context, indent); |
| if (type == "futures_util::future::future::Then") |
| return FormatThen(future, options, context, indent); |
| if (type == "futures_util::future::future::Map") // only appears in debug mode. |
| return FormatMapDebug(future, options, context, indent); |
| if (type == "futures_util::future::future::map::Map") |
| return FormatMap(future, options, context, indent); |
| if (type == "futures_util::future::future::remote_handle::Remote") |
| return FormatRemote(future, options, context, indent); |
| if (type == "core::pin::Pin") |
| return FormatPin(future, options, context, indent); |
| if (type == "vfs::execution_scope::TaskRunner") |
| return FormatTaskRunner(future, options, context, indent); |
| |
| // NOTE: `select!` and `join!` macro expand to PollFn. It'll be useful if we could describe it. |
| // However, PollFn could encode an arbitrary function so there's a chance we're doing very wrong. |
| // To be more accurate, we also check the filename of the awaiter. `select!` will be expanded |
| // from select_mod.rs, and `join!` will be expanded from `join_mod.rs`. |
| if (type == "futures_util::future::poll_fn::PollFn") { |
| if (awaiter_file_name == "select_mod.rs") |
| return FormatSelectJoin(future, options, context, indent, "select"); |
| if (awaiter_file_name == "join_mod.rs") |
| return FormatSelectJoin(future, options, context, indent, "join"); |
| } |
| |
| // General formatter. |
| auto out = fxl::MakeRefCounted<AsyncOutputBuffer>(); |
| if (options.verbose) { |
| out->Append(FormatValueForConsole(future, options.variable, context)); |
| out->Complete("\n"); |
| } else { |
| out->Complete(std::string(type) + "\n", TextForegroundColor::kGray); |
| } |
| return out; |
| } |
| |
| // Format (usize, alloc::sync::Arc<fuchsia_async::runtime::fuchsia::executor::common::Task>) |
| // Instead of return an AsyncOutputBuffer directly, use a callback to also return task_id. |
| void FormatActiveTasksHashMapTuple( |
| const ExprValue& tuple, const FormatFutureOptions& options, |
| const fxl::RefPtr<EvalContext>& context, |
| fit::callback<void(uint64_t, fxl::RefPtr<AsyncOutputBuffer>)> cb) { |
| ErrOrValue arc_inner_ptr = ResolveNonstaticMember(context, tuple, {"__1", "ptr", "pointer"}); |
| if (arc_inner_ptr.has_error()) |
| return cb(0, FormatError("Invalid HashMap tuple (1)", arc_inner_ptr.err())); |
| ResolvePointer( |
| context, arc_inner_ptr.value(), [=, cb = std::move(cb)](ErrOrValue arc_inner) mutable { |
| if (arc_inner.has_error()) |
| return cb(0, FormatError("Invalid HashMap tuple (2)", arc_inner.err())); |
| ErrOrValue id = ResolveNonstaticMember(context, arc_inner.value(), {"data", "id"}); |
| uint64_t task_id = 0; |
| if (id.has_error() || id.value().PromoteTo64(&task_id).has_error()) |
| return cb(0, FormatError("Invalid HashMap tuple (3)", id.err())); |
| auto out = fxl::MakeRefCounted<AsyncOutputBuffer>(); |
| out->Append("Task(id = " + std::to_string(task_id) + ")\n", TextForegroundColor::kGreen); |
| out->Append(kAwaiteeMarker); |
| // Arc -> Task -> AtomicFuture -> UnsafeCell -> ManuallyDrop -> FutureObj -> LocalFutureObj |
| ErrOrValue future = |
| ResolveNonstaticMember(context, arc_inner.value(), |
| {"data", "future", "future", "value", "value", "__0", "future"}); |
| if (future.has_error()) |
| return cb(task_id, FormatError("Invalid HashMap tuple (6)", future.err())); |
| |
| // |future| is a $(*mut dyn core::future::future::Future<Output=()>). |
| out->Complete(FormatFuture(future.value(), options, context, 3)); |
| cb(task_id, out); |
| }); |
| } |
| |
| // Format HashMap<usize, alloc::sync::Arc<fuchsia_async::runtime::fuchsia::executor::common::Task>> |
| fxl::RefPtr<AsyncOutputBuffer> FormatActiveTasksHashMap(const ErrOrValue& hashmap, |
| const FormatFutureOptions& options, |
| const fxl::RefPtr<EvalContext>& context) { |
| if (hashmap.has_error()) { |
| return FormatError("Cannot locate active_tasks", hashmap.err()); |
| } |
| if (StripTemplate(hashmap.value().type()->GetFullName()) != |
| "std::collections::hash::map::HashMap") { |
| return FormatError("Expect a HashMap, got " + hashmap.value().type()->GetFullName()); |
| } |
| // See |StdHashMapSyntheticProvider| in .../rustlib/etc/lldb_providers.py for the layout. |
| |
| // 1. Obtain the type of the tuple (usize, Arc<Task>) |
| ErrOrValue raw_table = ResolveNonstaticMember(context, hashmap.value(), {"base", "table"}); |
| if (raw_table.has_error()) |
| return FormatError("Invalid HashMap (1)", raw_table.err()); |
| const Collection* raw_table_coll = raw_table.value().type()->As<Collection>(); |
| if (!raw_table_coll || raw_table_coll->template_params().empty()) |
| return FormatError("Invalid HashMap (2)"); |
| fxl::RefPtr<Type> tuple_type; |
| if (auto param = raw_table_coll->template_params()[0].Get()->As<TemplateParameter>()) |
| tuple_type = RefPtrTo(param->type().Get()->As<Type>()); |
| if (!tuple_type) |
| return FormatError("Invalid HashMap (3)"); |
| |
| // 2. Resolve bucket_mask and ctrl pointer. |
| ErrOrValue bucket_mask_res = |
| ResolveNonstaticMember(context, raw_table.value(), {"table", "bucket_mask"}); |
| if (bucket_mask_res.has_error()) |
| return FormatError("Invalid HashMap (4)", bucket_mask_res.err()); |
| uint64_t bucket_mask = 0; |
| Err err = bucket_mask_res.value().PromoteTo64(&bucket_mask); |
| if (err.has_error()) |
| return FormatError("Invalid HashMap (5)", err); |
| if (!bucket_mask) { |
| // Empty hashmap. |
| auto out = fxl::MakeRefCounted<AsyncOutputBuffer>(); |
| out->Complete(); |
| return out; |
| } |
| ErrOrValue ctrl_res = |
| ResolveNonstaticMember(context, raw_table.value(), {"table", "ctrl", "pointer"}); |
| if (ctrl_res.has_error()) |
| return FormatError("Invalid HashMap (6)", ctrl_res.err()); |
| uint64_t ctrl = 0; |
| err = ctrl_res.value().PromoteTo64(&ctrl); |
| if (err.has_error() || !ctrl) |
| return FormatError("Invalid HashMap (7)", err); |
| |
| // 3. Read the memory. To save some operations we try to fetch the whole hashmap once. |
| // The layout of a HashMap looks like |
| // Tn, ..., T2, T1, C1, C2, ..., Cn |
| // ^ |ctrl| points here |
| uint64_t capacity = bucket_mask + 1; |
| uint64_t total_buckets_size = tuple_type->byte_size() * capacity; |
| auto out = fxl::MakeRefCounted<AsyncOutputBuffer>(); |
| context->GetDataProvider()->GetMemoryAsync( |
| ctrl - total_buckets_size, total_buckets_size + capacity, |
| [=](const Err& err, std::vector<uint8_t> data) { |
| if (err.has_error()) { |
| out->Complete(FormatError("Invalid HashMap (8)", err)); |
| return; |
| } |
| // Tasks are collected using a JoinCallbacks first so that they can be sorted by their id |
| // rather than the orders appearing in the hashmap. |
| using CallbackDataType = std::pair<uint64_t, fxl::RefPtr<AsyncOutputBuffer>>; |
| auto joiner = fxl::MakeRefCounted<JoinCallbacks<CallbackDataType>>(); |
| for (size_t idx = 0; idx < capacity; idx++) { |
| if ((data[total_buckets_size + idx] & 0x80)) // not present |
| continue; |
| uint8_t* slot = &data[total_buckets_size - (idx + 1) * tuple_type->byte_size()]; |
| ExprValue tuple(tuple_type, {slot, slot + tuple_type->byte_size()}, |
| ExprValueSource(ctrl - (idx + 1) * tuple_type->byte_size())); |
| FormatActiveTasksHashMapTuple( |
| tuple, options, context, |
| [cb = joiner->AddCallback()](auto task_id, auto output) mutable { |
| cb(std::make_pair(task_id, std::move(output))); |
| }); |
| } |
| joiner->Ready([out](std::vector<CallbackDataType> tasks) { |
| std::sort(tasks.begin(), tasks.end(), |
| [](auto& p1, auto& p2) { return p1.first < p2.first; }); |
| for (auto& p : tasks) { |
| out->Append(std::move(p.second)); |
| } |
| out->Complete(); |
| }); |
| }); |
| |
| return out; |
| } |
| |
| void OnStackReady(Stack& stack, fxl::RefPtr<CommandContext> cmd_context, |
| const FormatFutureOptions& options) { |
| // Step 2: locate main_future and the executor. |
| if (stack.empty()) { |
| cmd_context->ReportError( |
| Err("Cannot sync frames. Please ensure the thread is either suspended " |
| "or blocked in an exception. Use \"pause\" to suspend it.")); |
| return; |
| } |
| for (size_t i = 0; i < stack.size(); i++) { |
| if (!stack[i]->GetLocation().has_symbols()) |
| continue; |
| std::string func_name(StripTemplate(stack[i]->GetLocation().symbol().Get()->GetFullName())); |
| if (func_name == "fuchsia_async::runtime::fuchsia::executor::local::LocalExecutor::run" || |
| func_name == "fuchsia_async::runtime::fuchsia::executor::send::SendExecutor::run") { |
| auto out = fxl::MakeRefCounted<AsyncOutputBuffer>(); |
| auto context = stack[i]->GetEvalContext(); |
| EvalExpression("self.inner->data.active_tasks.data.value", context, false, |
| [out, options, context](const ErrOrValue& value) { |
| out->Complete(FormatActiveTasksHashMap(value, options, context)); |
| }); |
| cmd_context->Output(out); |
| return; |
| } |
| } |
| cmd_context->ReportError(Err("Cannot locate the async executor on the stack.")); |
| } |
| |
| void RunVerbAsyncBacktrace(const Command& cmd, fxl::RefPtr<CommandContext> cmd_context) { |
| if (Err err = cmd.ValidateNouns({Noun::kProcess, Noun::kThread}); err.has_error()) |
| return cmd_context->ReportError(err); |
| |
| if (!cmd.thread()) |
| return cmd_context->ReportError(Err("There is no thread to show backtrace.")); |
| |
| FormatFutureOptions options; |
| if (cmd.HasSwitch(kMoreVerbose)) { |
| options.verbose = true; |
| options.variable.verbosity = ConsoleFormatOptions::Verbosity::kMedium; |
| options.variable.wrapping = ConsoleFormatOptions::Wrapping::kSmart; |
| options.variable.pointer_expand_depth = 3; |
| options.variable.max_depth = 6; |
| } else if (cmd.HasSwitch(kVerbose)) { |
| options.verbose = true; |
| options.variable.verbosity = ConsoleFormatOptions::Verbosity::kMedium; |
| options.variable.pointer_expand_depth = 1; |
| options.variable.max_depth = 3; |
| } |
| |
| // Step 1: obtain the (synchronous) stack. |
| if (cmd.thread()->GetStack().has_all_frames()) { |
| OnStackReady(cmd.thread()->GetStack(), std::move(cmd_context), options); |
| } else { |
| cmd.thread()->GetStack().SyncFrames( |
| false, [thread = cmd.thread(), cmd_context = std::move(cmd_context), |
| options](const Err& err) mutable { |
| if (err.has_error()) { |
| cmd_context->ReportError(err); |
| } else { |
| OnStackReady(thread->GetStack(), std::move(cmd_context), options); |
| } |
| }); |
| } |
| } |
| |
| } // namespace |
| |
| VerbRecord GetAsyncBacktraceVerbRecord() { |
| VerbRecord abt(&RunVerbAsyncBacktrace, {"async-backtrace", "abt"}, kAsyncBacktraceShortHelp, |
| kAsyncBacktraceHelp, CommandGroup::kQuery); |
| abt.switches.emplace_back(kVerbose, false, "verbose", 'v'); |
| abt.switches.emplace_back(kMoreVerbose, false, "more-verbose", 0); |
| return abt; |
| } |
| |
| } // namespace zxdb |