| // Copyright 2018 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 "garnet/bin/zxdb/console/verbs.h" |
| |
| #include <stdlib.h> |
| |
| #include <algorithm> |
| |
| #include "garnet/bin/zxdb/client/session.h" |
| #include "garnet/bin/zxdb/client/setting_schema.h" |
| #include "garnet/bin/zxdb/client/thread.h" |
| #include "garnet/bin/zxdb/common/err.h" |
| #include "garnet/bin/zxdb/console/command.h" |
| #include "garnet/bin/zxdb/console/command_utils.h" |
| #include "garnet/bin/zxdb/console/console.h" |
| #include "garnet/bin/zxdb/console/format_settings.h" |
| #include "garnet/bin/zxdb/console/format_table.h" |
| #include "garnet/bin/zxdb/console/output_buffer.h" |
| #include "lib/fxl/strings/split_string.h" |
| #include "lib/fxl/strings/string_printf.h" |
| #include "lib/fxl/strings/trim.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| // help ------------------------------------------------------------------------ |
| |
| const char kHelpShortHelp[] = R"(help / h: Help.)"; |
| const char kHelpHelp[] = |
| R"(help |
| |
| Yo dawg, I heard you like help on your help so I put help on the help in |
| the help.)"; |
| |
| const char kHelpIntro[] = |
| R"(Help! |
| |
| Type "help <topic>" for more information. |
| |
| Command syntax |
| |
| Verbs |
| "step" |
| Applies the "step" verb to the currently selected thread. |
| "mem-read --size=16 0x12345678" |
| Pass a named switch and an argument. |
| |
| Nouns |
| "thread" |
| List available threads |
| "thread 1" |
| Select thread with ID 1 to be the default. |
| |
| Noun-Verb combinations |
| "thread 4 step" |
| Steps thread 4 of the current process regardless of the currently |
| selected thread. |
| "process 1 thread 4 step" |
| Steps thread 4 of process 1 regardless of the currently selected |
| thread or process. |
| )"; |
| |
| std::string FormatGroupHelp(const char* heading, |
| std::vector<std::string>* items) { |
| std::sort(items->begin(), items->end()); |
| |
| std::string help("\n"); |
| help.append(heading); |
| help.append("\n"); |
| for (const auto& line : *items) |
| help += " " + line + "\n"; |
| return help; |
| } |
| |
| std::string GetReference() { |
| std::string help = kHelpIntro; |
| |
| // Group all verbs by their CommandGroup. Add nouns to this since people |
| // will expect, for example, "breakpoint" to be in the breakpoints section. |
| std::map<CommandGroup, std::vector<std::string>> groups; |
| |
| // Get the separate noun reference and add to the groups. |
| help += "\nNouns\n"; |
| std::vector<std::string> noun_lines; |
| for (const auto& pair : GetNouns()) { |
| noun_lines.push_back(pair.second.short_help); |
| groups[pair.second.command_group].push_back(pair.second.short_help); |
| } |
| std::sort(noun_lines.begin(), noun_lines.end()); |
| for (const auto& line : noun_lines) |
| help += " " + line + "\n"; |
| |
| // Add in verbs. |
| for (const auto& pair : GetVerbs()) |
| groups[pair.second.command_group].push_back(pair.second.short_help); |
| |
| help += FormatGroupHelp("General", &groups[CommandGroup::kGeneral]); |
| help += FormatGroupHelp("Process", &groups[CommandGroup::kProcess]); |
| help += FormatGroupHelp("Assembly", &groups[CommandGroup::kAssembly]); |
| help += FormatGroupHelp("Breakpoint", &groups[CommandGroup::kBreakpoint]); |
| help += FormatGroupHelp("Query", &groups[CommandGroup::kQuery]); |
| help += FormatGroupHelp("Step", &groups[CommandGroup::kStep]); |
| |
| return help; |
| } |
| |
| Err DoHelp(ConsoleContext* context, const Command& cmd) { |
| OutputBuffer out; |
| |
| if (cmd.args().empty()) { |
| // Generic help, list topics and quick reference. |
| out.FormatHelp(GetReference()); |
| Console::get()->Output(std::move(out)); |
| return Err(); |
| } |
| const std::string& on_what = cmd.args()[0]; |
| |
| const char* help = nullptr; |
| |
| // Check for a noun. |
| const auto& string_noun = GetStringNounMap(); |
| auto found_string_noun = string_noun.find(on_what); |
| if (found_string_noun != string_noun.end()) { |
| // Find the noun record to get the help. This is guaranteed to exist. |
| const auto& nouns = GetNouns(); |
| help = nouns.find(found_string_noun->second)->second.help; |
| } else { |
| // Check for a verb |
| const auto& string_verb = GetStringVerbMap(); |
| auto found_string_verb = string_verb.find(on_what); |
| if (found_string_verb != string_verb.end()) { |
| // Find the verb record to get the help. This is guaranteed to exist. |
| const auto& verbs = GetVerbs(); |
| help = verbs.find(found_string_verb->second)->second.help; |
| } else { |
| // Not a valid command. |
| out.Append(Err("\"" + on_what + "\" is not a valid command.\n" |
| "Try just \"help\" to get a list.")); |
| Console::get()->Output(std::move(out)); |
| return Err(); |
| } |
| } |
| |
| out.FormatHelp(help); |
| Console::get()->Output(std::move(out)); |
| return Err(); |
| } |
| |
| // quit ------------------------------------------------------------------------ |
| |
| const char kQuitShortHelp[] = R"(quit / q: Quits the debugger.)"; |
| const char kQuitHelp[] = |
| R"(quit |
| |
| Quits the debugger.)"; |
| |
| Err DoQuit(ConsoleContext* context, const Command& cmd) { |
| // This command is special-cased by the main loop so it shouldn't get |
| // executed. |
| return Err(); |
| } |
| |
| // quit-agent ------------------------------------------------------------------ |
| |
| const char kQuitAgentShortHelp[] = R"(quit-agent: Quits the debug agent.)"; |
| const char kQuitAgentHelp[] = |
| R"(quit-agent |
| |
| Quits the connected debug agent running on the target.)"; |
| |
| Err DoQuitAgent(ConsoleContext* context, const Command& cmd) { |
| context->session()->QuitAgent([](const Err& err) { |
| if (err.has_error()) { |
| Console::get()->Output(err); |
| } else { |
| Console::get()->Output("Successfully stopped the debug agent."); |
| } |
| }); |
| |
| return Err(); |
| } |
| |
| // connect --------------------------------------------------------------------- |
| |
| const char kConnectShortHelp[] = |
| R"(connect: Connect to a remote system for debugging.)"; |
| const char kConnectHelp[] = |
| R"(connect <remote_address> |
| |
| Connects to a debug_agent at the given address/port. Both IP address and port |
| are required. |
| |
| See also "disconnect". |
| |
| Addresses |
| |
| Addresses can be of the form "<host> <port>" or "<host>:<port>". When using |
| the latter form, IPv6 addresses must be [bracketed]. Otherwise the brackets |
| are optional. |
| |
| Examples |
| |
| connect mystem.localnetwork 1234 |
| connect mystem.localnetwork:1234 |
| connect 192.168.0.4:1234 |
| connect 192.168.0.4 1234 |
| connect [1234:5678::9abc] 1234 |
| connect 1234:5678::9abc 1234 |
| connect [1234:5678::9abc]:1234 |
| )"; |
| |
| Err DoConnect(ConsoleContext* context, const Command& cmd, |
| CommandCallback callback = nullptr) { |
| // Can accept either one or two arg forms. |
| std::string host; |
| uint16_t port = 0; |
| |
| if (cmd.args().size() == 0) { |
| return Err(ErrType::kInput, "Need host and port to connect to."); |
| } else if (cmd.args().size() == 1) { |
| Err err = ParseHostPort(cmd.args()[0], &host, &port); |
| if (err.has_error()) |
| return err; |
| } else if (cmd.args().size() == 2) { |
| Err err = ParseHostPort(cmd.args()[0], cmd.args()[1], &host, &port); |
| if (err.has_error()) |
| return err; |
| } else { |
| return Err(ErrType::kInput, "Too many arguments."); |
| } |
| |
| context->session()->Connect(host, port, [callback, cmd](const Err& err) { |
| if (err.has_error()) { |
| // Don't display error message if they canceled the connection. |
| if (err.type() != ErrType::kCanceled) |
| Console::get()->Output(err); |
| } else { |
| OutputBuffer msg; |
| msg.Append("Connected successfully.\n"); |
| |
| // Assume if there's a callback this is not being run interactively. |
| // Otherwise, show the usage tip. |
| if (!callback) { |
| msg.Append(Syntax::kWarning, "👉 "); |
| msg.Append(Syntax::kComment, |
| "Normally you will \"run <program path>\" or \"attach " |
| "<process koid>\"."); |
| } |
| Console::get()->Output(std::move(msg)); |
| } |
| |
| if (callback) |
| callback(err); |
| }); |
| Console::get()->Output("Connecting (use \"disconnect\" to cancel)...\n"); |
| |
| return Err(); |
| } |
| |
| // opendump -------------------------------------------------------------------- |
| |
| const char kOpenDumpShortHelp[] = |
| R"(opendump: Open a dump file for debugging.)"; |
| const char kOpenDumpHelp[] = |
| R"(opendump <path> |
| |
| Opens a minidump file. Currently only the 'minidump' format is supported. |
| )"; |
| |
| Err DoOpenDump(ConsoleContext* context, const Command& cmd, |
| CommandCallback callback = nullptr) { |
| std::string path; |
| |
| if (cmd.args().size() == 0) { |
| return Err(ErrType::kInput, "Need path to open."); |
| } else if (cmd.args().size() == 1) { |
| path = cmd.args()[0]; |
| } else { |
| return Err(ErrType::kInput, "Too many arguments."); |
| } |
| |
| context->session()->OpenMinidump(path, [callback](const Err& err) { |
| if (err.has_error()) { |
| Console::get()->Output(err); |
| } else { |
| Console::get()->Output("Dump loaded successfully.\n"); |
| } |
| |
| if (callback) |
| callback(err); |
| }); |
| Console::get()->Output("Opening dump file...\n"); |
| |
| return Err(); |
| } |
| |
| // disconnect ------------------------------------------------------------------ |
| |
| const char kDisconnectShortHelp[] = |
| R"(disconnect: Disconnect from the remote system.)"; |
| const char kDisconnectHelp[] = |
| R"(disconnect |
| |
| Disconnects from the remote system, or cancels an in-progress connection if |
| there is one. |
| |
| There are no arguments. |
| )"; |
| |
| Err DoDisconnect(ConsoleContext* context, const Command& cmd, |
| CommandCallback callback = nullptr) { |
| if (!cmd.args().empty()) |
| return Err(ErrType::kInput, "\"disconnect\" takes no arguments."); |
| |
| context->session()->Disconnect([callback](const Err& err) { |
| if (err.has_error()) |
| Console::get()->Output(err); |
| else |
| Console::get()->Output("Disconnected successfully."); |
| |
| // We call the given callback |
| if (callback) |
| callback(err); |
| }); |
| |
| return Err(); |
| } |
| |
| // cls ------------------------------------------------------------------------- |
| |
| const char kClsShortHelp[] = "cls: clear screen."; |
| const char kClsHelp[] = |
| R"(cls |
| |
| Clears the contents of the console. Similar to "clear" on a shell. |
| |
| There are no arguments. |
| )"; |
| |
| Err DoCls(ConsoleContext* context, const Command& cmd, |
| CommandCallback callback = nullptr) { |
| if (!cmd.args().empty()) |
| return Err(ErrType::kInput, "\"cls\" takes no arguments."); |
| |
| Console::get()->Clear(); |
| |
| if (callback) |
| callback(Err()); |
| return Err(); |
| } |
| |
| // get ------------------------------------------------------------------------- |
| |
| const char kGetShortHelp[] = "get: Get a setting(s) value(s)."; |
| const char kGetHelp[] = |
| R"(get (--system|-s) [setting_name] |
| |
| Gets the value of all the settings or the detailed description of one. |
| |
| Arguments |
| |
| --system|-s |
| Refer to the system context instead of the current one. |
| See below for more details. |
| |
| [setting_name] |
| Filter for one setting. Will show detailed information, such as a |
| description and more easily copyable values. |
| |
| Setting Types |
| |
| Settings have a particular type: bool, int, string or list (of strings). |
| The type is set beforehand and cannot change. Getting the detailed information |
| of a setting will show the type of setting it is, though normally it is easy |
| to tell from the list of values. |
| |
| Contexts |
| |
| Within zxdb, there is the concept of the current context. This means that at |
| any given moment, there is a current process, thread and breakpoint. This also |
| applies when handling settings. By default, get will query the settings for |
| the current thread. If you want to query the settings for the current target |
| or system, you need to qualify at such. |
| |
| There are currently 3 contexts where settings live: |
| |
| - System |
| - Target (roughly equivalent to a Process, but remains even when not running). |
| - Thread |
| |
| In order to query a particular context, you need to qualify it: |
| |
| get foo |
| Unqualified. Queries the current thread settings. |
| p 1 get foo |
| Qualified. Queries the selected process settings. |
| p 3 t 2 get foo |
| Qualified. Queries the selectedthread settings. |
| |
| For system settings, we need to override the context, so we need to explicitly |
| ask for it. Any explicit context will be ignored in this case: |
| |
| get -s foo |
| Retrieves the value of "foo" for the system. |
| |
| Schemas |
| |
| Each setting level (thread, target, etc.) has an associated schema. |
| This defines what settings are available for it and the default values. |
| Initially, all objects default to their schemas, but values can be overridden |
| for individual objects. |
| |
| Instance Overrides |
| |
| Values overriding means that you can modify behaviour for a particular object. |
| If a setting has not been overridden for that object, it will fallback to the |
| settings of parent object. The fallback order is as follows: |
| |
| Thread -> Process -> System -> Schema Default |
| |
| This means that if a thread has not overridden a value, it will check if the |
| owning process has overridden it, then is the system has overridden it. If |
| there are none, it will get the default value of the thread schema. |
| |
| For example, if t1 has overridden "foo" but t2 has not: |
| |
| t 1 foo |
| Gets the value of "foo" for t1. |
| t 2 foo |
| Queries the owning process for foo. If that process doesn't have it (no |
| override), it will query the system. If there is no override, it will |
| fallback to the schema default. |
| |
| NOTE: |
| Not all settings are present in all schemas, as some settings only make sense |
| in a particular context. If the thread schema holds a setting "foo" which the |
| process schema does not define, asking for "foo" on a thread will only default |
| to the schema default, as the concept of "foo" does not makes sense to a |
| process. |
| |
| Examples |
| |
| get |
| List the global settings for the System context. |
| |
| p get foo |
| Get the value of foo for the global Process context. |
| |
| p 2 t1 get |
| List the values of settings for t1 of p2. |
| This will list all the settings within the Thread schema, highlighting |
| which ones are overridden. |
| |
| get -s |
| List the values of settings at the system level. |
| )"; |
| |
| constexpr int kGetSystemSwitch = 0; |
| |
| Err DoGet(ConsoleContext* context, const Command& cmd) { |
| std::string setting_name; |
| if (!cmd.args().empty()) { |
| if (cmd.args().size() > 1) |
| return Err("Expected only one setting name"); |
| setting_name = cmd.args()[0]; |
| } |
| |
| Err err; |
| OutputBuffer out; |
| if (setting_name.empty()) { |
| out.Append({Syntax::kComment, |
| R"(Run "get <option>" to see detailed information. )" |
| R"(eg. "get symbol-paths").)" |
| "\n"}); |
| FormatSettings(cmd, &out); |
| } else { |
| err = FormatSetting(cmd, setting_name, &out); |
| } |
| |
| if (err.has_error()) |
| return err; |
| |
| Console::get()->Output(std::move(out)); |
| return Err(); |
| } |
| |
| // Set ------------------------------------------------------------------------- |
| |
| constexpr int kSetSystemSwitch = 0; |
| |
| const char kSetShortHelp[] = "set: Set a setting value."; |
| const char kSetHelp[] = |
| R"(set <setting_name> <value> |
| |
| Sets the value of a setting. |
| |
| Arguments |
| |
| <setting_name> |
| The setting that will modified. Must match exactly. |
| |
| <value> |
| The value to set. Keep in mind that settings have different types, so the |
| value will be validated. Read more below. |
| |
| Contexts, Schemas and Instance Overrides |
| |
| Settings have a hierarchical system of contexts where settings are defined. |
| When setting a value, if it is not qualified, it will be set the setting at |
| the highest level it can, in order to make it as general as possible. |
| |
| In most cases these higher level will be system-wide, to change behavior to |
| the whole system, that can be overriden per-process or per-thread. Sometimes |
| though, the setting only makes sense on a per-object basis (eg. new process |
| filters for jobs). In this case, the unqualified set will work on the current |
| object in the context. |
| |
| In order to override a setting at a job, target or thread level, the setting |
| command has to be explicitly qualified. This works for both avoiding setting |
| the value at a global context or to set the value for an object other than |
| the current one. See examples below. |
| |
| There is detailed information on contexts and schemas in "help get". |
| |
| Setting Types |
| |
| Settings have a particular type: bool, int, string or list (of strings). |
| The type is set beforehand and cannot change. Getting the detailed information |
| of a setting will show the type of setting it is, though normally it is easy |
| to tell from the list of valued. |
| |
| The valid inputs for each type are: |
| |
| - bool: "0", "false" -> false |
| "1", "true" -> true |
| - int: Any string convertible to integer (think std::atoi). |
| - string: Any one-word string. Working on getting multi-word strings. |
| - list: List uses a representation of colon (:) separated values. While |
| showing the list value uses bullet points, setting it requires the |
| colon-separated representation. Running "get <setting_name>" will give |
| the current "list setting value" for a list setting, which can be |
| copy-pasted for easier editing. See example for a demostration. |
| |
| Examples |
| |
| [zxdb] set boolean_setting true |
| Set boolean_setting system-wide: |
| true |
| |
| [zxdb] pr set int_setting 1024 |
| Set int_setting for process 2: |
| 1024 |
| |
| [zxdb] p 3 t 2 set string_setting somesuperlongstring |
| Set setting for thread 2 of process 3: |
| somesuperlongstring |
| |
| [zxdb] get foo |
| ... |
| • first |
| • second |
| • third |
| ... |
| Set value: first:second:third |
| [zxdb] set foo first:second:third:fourth |
| Set foo for job 3: |
| • first |
| • second |
| • third |
| • fourth |
| |
| NOTE: In the last case, even though the setting was not qualified, it was |
| set at the job level. This is because this is a job-specific setting |
| that doesn't make sense system-wide, but rather only per job. |
| )"; |
| |
| // Struct to represents all the context needed to correctly reason about the |
| // set command. |
| struct SetContext { |
| SettingStore* store = nullptr; |
| JobContext* job_context = nullptr; |
| Target* target = nullptr; |
| Thread* thread = nullptr; |
| |
| SettingSchemaItem schema_item; // What kind of setting this is. |
| std::string setting_name; // The setting that was set. |
| // At what level the setting was applied. |
| SettingSchema::Level level = SettingSchema::Level::kDefault; |
| // What kind of operation this is. |
| AssignType assign_type = AssignType::kAssign; |
| |
| // On append, it is the elements added. |
| // On remove, it is the elements removed. |
| std::vector<std::string> elements_changed; |
| }; |
| |
| Err SetBool(SettingStore* store, const std::string& setting_name, |
| const std::string& value) { |
| if (value == "0" || value == "false") { |
| store->SetBool(setting_name, false); |
| } else if (value == "1" || value == "true") { |
| store->SetBool(setting_name, true); |
| } else { |
| return Err("%s expects a boolean. See \"help set\" for valid values.", |
| setting_name.data()); |
| } |
| return Err(); |
| } |
| |
| Err SetInt(SettingStore* store, const std::string& setting_name, |
| const std::string& value) { |
| int out; |
| Err err = StringToInt(value, &out); |
| if (err.has_error()) { |
| return Err("%s expects a valid int: %s", setting_name.data(), |
| err.msg().data()); |
| } |
| |
| return store->SetInt(setting_name, out); |
| } |
| |
| Err SetList(const SetContext& context, |
| const std::vector<std::string>& elements_to_set, |
| SettingStore* store, std::vector<std::string>* elements_changed) { |
| if (context.assign_type == AssignType::kAssign) |
| return store->SetList(context.setting_name, elements_to_set); |
| |
| if (context.assign_type == AssignType::kAppend) { |
| auto list = store->GetList(context.setting_name); |
| list.insert(list.end(), elements_to_set.begin(), elements_to_set.end()); |
| *elements_changed = elements_to_set; |
| return store->SetList(context.setting_name, list); |
| } |
| |
| if (context.assign_type == AssignType::kRemove) { |
| // We search for the elements to remove. |
| auto list = store->GetList(context.setting_name); |
| |
| std::vector<std::string> list_after_remove; |
| for (auto& elem : list) { |
| // If the element to change is within the list, means that we remove it. |
| auto it = std::find(elements_to_set.begin(), elements_to_set.end(), elem); |
| if (it == elements_to_set.end()) { |
| list_after_remove.push_back(elem); |
| } else { |
| elements_changed->push_back(elem); |
| } |
| } |
| |
| // If none, were removed, we error so that the user can check why. |
| if (list.size() == list_after_remove.size()) |
| return Err("Could not find any elements to remove."); |
| return store->SetList(context.setting_name, list_after_remove); |
| } |
| |
| FXL_NOTREACHED(); |
| return Err(); |
| } |
| |
| // Will run the sets against the correct SettingStore: |
| // |context| represents the required context needed to reason about the command. |
| // |elements_changed| are all the values that changed. This is used afterwards |
| // for user feedback. |
| // |out| is the resultant setting, which is used for user feedback. |
| Err SetSetting(const SetContext& context, |
| const std::vector<std::string>& elements_to_set, |
| SettingStore* store, std::vector<std::string>* elements_changed, |
| StoredSetting* out) { |
| Err err; |
| if (context.assign_type != AssignType::kAssign && |
| !context.schema_item.value().is_list()) |
| return Err("Appending/removing only works for list options."); |
| |
| switch (context.schema_item.type()) { |
| case SettingType::kBoolean: |
| err = SetBool(store, context.setting_name, elements_to_set[0]); |
| break; |
| case SettingType::kInteger: |
| err = SetInt(store, context.setting_name, elements_to_set[0]); |
| break; |
| case SettingType::kString: |
| err = store->SetString(context.setting_name, elements_to_set[0]); |
| break; |
| case SettingType::kList: |
| err = SetList(context, elements_to_set, store, elements_changed); |
| break; |
| case SettingType::kNull: |
| return Err("Unknown type for setting %s. Please file a bug with repro.", |
| context.setting_name.data()); |
| } |
| |
| if (!err.ok()) |
| return err; |
| |
| *out = store->GetSetting(context.setting_name); |
| return Err(); |
| } |
| |
| OutputBuffer FormatSetFeedback(ConsoleContext* context, |
| const SetContext& set_context) { |
| std::string verb; |
| switch (set_context.assign_type) { |
| case AssignType::kAssign: |
| verb = "Set value(s)"; |
| break; |
| case AssignType::kAppend: |
| verb = "Added value(s)"; |
| break; |
| case AssignType::kRemove: |
| verb = "Removed the following value(s)"; |
| break; |
| } |
| FXL_DCHECK(!verb.empty()); |
| |
| std::string message; |
| switch (set_context.level) { |
| case SettingSchema::Level::kSystem: |
| message = fxl::StringPrintf("%s system wide:\n", verb.data()); |
| break; |
| case SettingSchema::Level::kJob: { |
| int job_id = context->IdForJobContext(set_context.job_context); |
| message = fxl::StringPrintf("%s for job %d:\n", verb.data(), job_id); |
| break; |
| } |
| case SettingSchema::Level::kTarget: { |
| int target_id = context->IdForTarget(set_context.target); |
| message = |
| fxl::StringPrintf("%s for process %d:\n", verb.data(), target_id); |
| break; |
| } |
| case SettingSchema::Level::kThread: { |
| int target_id = context->IdForTarget(set_context.target); |
| int thread_id = context->IdForThread(set_context.thread); |
| message = fxl::StringPrintf("%s for thread %d of process %d:\n", |
| verb.data(), thread_id, target_id); |
| break; |
| } |
| default: |
| FXL_NOTREACHED() << "Should not receive a default setting."; |
| } |
| |
| return OutputBuffer(std::move(message)); |
| } |
| |
| Err GetSetContext(const Command& cmd, const std::string& setting_name, |
| SetContext* out) { |
| SettingStore* store = nullptr; |
| JobContext* job_context = cmd.job_context(); |
| Target* target = cmd.target(); |
| Thread* thread = cmd.thread(); |
| |
| if (!target) |
| return Err("No target found. Please file a bug with a repro."); |
| |
| // If the user qualified the query, it means that we want to query *that* |
| // specific SettingStore, so we search for it. We search from more specific |
| // to less specific. |
| if (cmd.HasNoun(Noun::kThread)) { |
| store = thread ? &thread->settings() : nullptr; |
| } else if (cmd.HasNoun(Noun::kProcess)) { |
| store = &target->settings(); |
| } else if (cmd.HasNoun(Noun::kJob)) { |
| store = job_context ? &job_context->settings() : nullptr; |
| } |
| |
| if (!store) { |
| // We didn't found an explicit specified store, so we lookup in the current |
| // context. We look from less specific to more specific in terms of stores, |
| // so that the setting will be set for a biggest setting as it can. |
| // NOTE): Some settings are replicated upwards, even to the system level, |
| // as they make sense (eg. pause on attach). But others only make |
| // sense locally (eg. job filters). |
| System& system = target->session()->system(); |
| |
| if (system.settings().schema()->HasSetting(setting_name)) { |
| store = &system.settings(); |
| } else if (job_context && |
| job_context->settings().schema()->HasSetting(setting_name)) { |
| store = &job_context->settings(); |
| } else if (target->settings().schema()->HasSetting(setting_name)) { |
| store = &target->settings(); |
| } else if (thread && |
| thread->settings().schema()->HasSetting(setting_name)) { |
| store = &thread->settings(); |
| } |
| } |
| |
| if (!store || !store->schema()->HasSetting(setting_name)) |
| return Err("Could not find setting \"%s\".", setting_name.data()); |
| |
| out->job_context = job_context; |
| out->target = target; |
| out->thread = thread; |
| out->store = store; |
| out->schema_item = store->schema()->GetItem(setting_name); |
| |
| return Err(); |
| } |
| |
| Err DoSet(ConsoleContext* context, const Command& cmd) { |
| if (cmd.args().size() < 2) |
| return Err("Wrong amount of Arguments. See \"help set\"."); |
| |
| // Expected format is <option_name> [(=|+=|-=)] <value> [<value> ...] |
| |
| Err err; |
| const std::string& setting_name = cmd.args()[0]; |
| |
| // See where this setting would be stored. |
| SetContext set_context; |
| set_context.setting_name = setting_name; |
| err = GetSetContext(cmd, setting_name, &set_context); |
| if (err.has_error()) |
| return err; |
| |
| // See what kind of assignment this is (whether it has =|+=|-=). |
| AssignType assign_type; |
| std::vector<std::string> elements_to_set; |
| err = SetElementsToAdd(cmd.args(), &assign_type, &elements_to_set); |
| if (err.has_error()) |
| return err; |
| |
| set_context.assign_type = assign_type; |
| |
| // Validate that the operations makes sense. |
| if (assign_type != AssignType::kAssign && |
| !set_context.schema_item.value().is_list()) |
| return Err("List assignment (+=, -=) used on a non-list option."); |
| |
| if (elements_to_set.size() > 1u && !set_context.schema_item.value().is_list()) |
| return Err("Multiple values on a non-list option."); |
| |
| StoredSetting out; // Used for showing the new value. |
| err = SetSetting(set_context, elements_to_set, set_context.store, |
| &set_context.elements_changed, &out); |
| if (!err.ok()) |
| return err; |
| |
| // Should never override default (schema) values. |
| FXL_DCHECK(out.level != SettingSchema::Level::kDefault); |
| set_context.level = out.level; |
| |
| // We output the new value. |
| |
| Console::get()->Output(FormatSetFeedback(context, set_context)); |
| |
| // For removed values, we show which ones were removed. |
| if (set_context.assign_type != AssignType::kRemove) { |
| Console::get()->Output(FormatSettingValue(out)); |
| } else { |
| StoredSetting setting; |
| setting.value = SettingValue(set_context.elements_changed); |
| Console::get()->Output(FormatSettingValue(std::move(setting))); |
| } |
| |
| return Err(); |
| } |
| |
| } // namespace |
| |
| void AppendControlVerbs(std::map<Verb, VerbRecord>* verbs) { |
| (*verbs)[Verb::kHelp] = VerbRecord(&DoHelp, {"help", "h"}, kHelpShortHelp, |
| kHelpHelp, CommandGroup::kGeneral); |
| (*verbs)[Verb::kQuit] = VerbRecord(&DoQuit, {"quit", "q"}, kQuitShortHelp, |
| kQuitHelp, CommandGroup::kGeneral); |
| (*verbs)[Verb::kConnect] = |
| VerbRecord(&DoConnect, {"connect"}, kConnectShortHelp, kConnectHelp, |
| CommandGroup::kGeneral); |
| (*verbs)[Verb::kDisconnect] = |
| VerbRecord(&DoDisconnect, {"disconnect"}, kDisconnectShortHelp, |
| kDisconnectHelp, CommandGroup::kGeneral); |
| (*verbs)[Verb::kQuitAgent] = |
| VerbRecord(&DoQuitAgent, {"quit-agent"}, kQuitAgentShortHelp, |
| kQuitAgentHelp, CommandGroup::kGeneral); |
| (*verbs)[Verb::kOpenDump] = |
| VerbRecord(&DoOpenDump, {"opendump"}, kOpenDumpShortHelp, kOpenDumpHelp, |
| CommandGroup::kGeneral); |
| (*verbs)[Verb::kCls] = VerbRecord(&DoCls, {"cls"}, kClsShortHelp, kClsHelp, |
| CommandGroup::kGeneral); |
| |
| // get. |
| SwitchRecord get_system(kGetSystemSwitch, false, "system", 's'); |
| VerbRecord get(&DoGet, {"get"}, kGetShortHelp, kGetHelp, |
| CommandGroup::kGeneral); |
| get.switches.push_back(std::move(get_system)); |
| (*verbs)[Verb::kGet] = std::move(get); |
| |
| // set. |
| SwitchRecord set_system(kSetSystemSwitch, false, "system", 's'); |
| VerbRecord set(&DoSet, {"set"}, kSetShortHelp, kSetHelp, |
| CommandGroup::kGeneral); |
| set.switches.push_back(std::move(set_system)); |
| (*verbs)[Verb::kSet] = std::move(set); |
| } |
| |
| } // namespace zxdb |