diff options
author | Med Ismail Bennani <medismail.bennani@gmail.com> | 2021-01-08 21:23:34 +0100 |
---|---|---|
committer | Med Ismail Bennani <medismail.bennani@gmail.com> | 2021-01-08 21:23:34 +0100 |
commit | 68e63210aeb74d2ef6ef0110da80d1aa78ba4148 (patch) | |
tree | f16c4018b1de4df591a2abf66ec44788222166aa | |
parent | 0386f3d4f4183a93d7e029abef8110ae4f148335 (diff) | |
download | llvm-scripted-process.tar.gz |
[lldb/Target] Add Scripted Processes (WIP)scripted-process
Signed-off-by: Med Ismail Bennani <medismail.bennani@gmail.com>
22 files changed, 1082 insertions, 3 deletions
diff --git a/lldb/bindings/python/python-scripted-process.swig b/lldb/bindings/python/python-scripted-process.swig new file mode 100644 index 000000000000..4b6888f86b77 --- /dev/null +++ b/lldb/bindings/python/python-scripted-process.swig @@ -0,0 +1,53 @@ +%pythoncode %{ +from abc import ABC, abstractmethod +from typing import List + +import lldb + +class ScriptedProcess(ABC): + @abstractmethod + def __init__(self): + pass + + # Optional initializer + def __init__(self, dictionary: lldb.SBStructuredData): + pass + + ### Main funcitonnalities + @abstractmethod + def get_num_memory_regions(self) -> int: + pass + + @abstractmethod + def get_memory_region_at_index(self, idx: int) -> lldb.SBMemoryRegionInfo: + pass + + @abstractmethod + def get_num_threads(self): + pass + + @abstractmethod + def get_thread_at_index(self, idx: int) -> lldb.SBThread: + pass + + @abstractmethod + def get_register_for_thread(self, tid:int): + pass + + @abstractmethod + def read_memory_at_address(self, addr:int) -> lldb.SBData: + pass + + @abstractmethod + def get_loaded_images(self) -> List[str]: # -> List[lldb.SBModule]: + pass + + ### Process state + @abstractmethod + def can_debug(self) -> bool: + pass + + @abstractmethod + def is_alive(self) -> bool: + pass +%} diff --git a/lldb/bindings/python/python.swig b/lldb/bindings/python/python.swig index 66a75328d1e7..9459c8c7be2e 100644 --- a/lldb/bindings/python/python.swig +++ b/lldb/bindings/python/python.swig @@ -126,6 +126,7 @@ using namespace lldb; %include "interfaces.swig" %include "python-extensions.swig" %include "python-wrapper.swig" +%include "python-scripted-process.swig" %pythoncode%{ _initialize = True diff --git a/lldb/examples/python/scripted_process.py b/lldb/examples/python/scripted_process.py new file mode 100644 index 000000000000..b7c43b63de32 --- /dev/null +++ b/lldb/examples/python/scripted_process.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + +from lldb import ScriptedProcess + +class ScriptedMachCoreProcess(ScriptedProcess): + def __init__(self, target: lldb.SBTarget): + self.memory_regions = [] + self.threads = [] + self.loaded_images = [] + self.stops = [] + self.target = target + + def __init__(self, dictionary: lldb.SBStructuredData): + # User-defined + pass + + ### Main functionalities + def get_num_memory_regions(self) -> int: + return len(self.memory_region) + def get_memory_region_at_index(self, idx: int) -> lldb.SBMemoryRegionInfos: + return self.memory_region[idx] + def get_num_threads(self) -> int: + return len(self.threads) + def get_thread_at_index(self, idx: int) -> lldb.SBThread: + return self.threads[idx] + def get_register_for_thread(self, tid: int): + # Follow register data structure used in OS Plugins. + return reg + def read_memory_at_address(self, addr: int) -> lldb.SBData: + # dict[addr:data] ? + return addr + def get_loaded_images(self) -> list[str]: + return self.loaded_images + + ### Process state + def can_debug(self) -> bool: + return True + def is_alive(self) -> bool: + return True + +example = ScriptedMachCoreProcess("lol") +print(example.sb_target) +print(example.get_num_threads()) diff --git a/lldb/include/lldb/Host/ProcessLaunchInfo.h b/lldb/include/lldb/Host/ProcessLaunchInfo.h index ee9755580825..f12afbbe412a 100644 --- a/lldb/include/lldb/Host/ProcessLaunchInfo.h +++ b/lldb/include/lldb/Host/ProcessLaunchInfo.h @@ -20,6 +20,7 @@ #include "lldb/Host/PseudoTerminal.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/ProcessInfo.h" +#include "lldb/Utility/StructuredData.h" namespace lldb_private { @@ -162,6 +163,34 @@ protected: lldb::ListenerSP m_listener_sp; lldb::ListenerSP m_hijack_listener_sp; }; + +// ScriptedProcessLaunchInfo +// +// Describes any information that is required to launch a scripted process +// - Script file + Class name +// - Key-Value dictionary + +class ScriptedProcessLaunchInfo { +public: + ScriptedProcessLaunchInfo(const FileSpec &script, llvm::StringRef class_name) + : m_script_file(script), m_class_name(class_name), m_is_class(true), + m_dictionary_sp(nullptr) {} + + ScriptedProcessLaunchInfo(const StructuredData::DictionarySP dictionary) + : m_script_file(), m_class_name(), m_is_class(false), + m_dictionary_sp(dictionary) {} + + FileSpec GetScriptFileSpec() const { return m_script_file; } + llvm::StringRef GetClassName() const { return m_class_name; } + bool IsClass() const { return m_is_class; } + StructuredData::DictionarySP GetDictionary() const { return m_dictionary_sp; } + +private: + FileSpec m_script_file; + llvm::StringRef m_class_name; + bool m_is_class; + StructuredData::DictionarySP m_dictionary_sp; +}; } #endif // LLDB_HOST_PROCESSLAUNCHINFO_H diff --git a/lldb/include/lldb/Interpreter/OptionGroupPythonClassWithDict.h b/lldb/include/lldb/Interpreter/OptionGroupPythonClassWithDict.h index d4c924a44157..f1370179ace9 100644 --- a/lldb/include/lldb/Interpreter/OptionGroupPythonClassWithDict.h +++ b/lldb/include/lldb/Interpreter/OptionGroupPythonClassWithDict.h @@ -49,6 +49,8 @@ public: return m_name; } + bool IsClass() const { return m_is_class; } + protected: std::string m_name; std::string m_current_key; diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h index 4abd1ca68167..6746d4f6a109 100644 --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -528,6 +528,60 @@ public: lldb::ScriptLanguage GetLanguage() { return m_script_lang; } +#pragma mark ScriptedProcessInterface + + virtual StructuredData::DictionarySP + ScriptedProcess_Create(StructuredData::ObjectSP scripted_process_object_sp, + lldb::pid_t pid, lldb::addr_t context) { + return nullptr; + } + + virtual StructuredData::DictionarySP ScriptedProcess_GetNumMemoryRegions( + StructuredData::ObjectSP scripted_process_object_sp) { + return nullptr; + } + + virtual lldb::MemoryRegionInfoSP ScriptedProcess_GetMemoryRegionAtIndex( + StructuredData::ObjectSP scripted_process_object_sp, size_t index) { + return nullptr; + } + + virtual StructuredData::DictionarySP ScriptedProcess_GetNumThreads( + StructuredData::ObjectSP scripted_process_object_sp) { + return nullptr; + } + + virtual StructuredData::DictionarySP ScriptedProcess_GetThreadAtIndex( + StructuredData::ObjectSP scripted_process_object_sp, size_t index) { + return nullptr; + } + + virtual StructuredData::DictionarySP ScriptedProcess_GetRegisterForThread( + StructuredData::ObjectSP scripted_process_object_sp) { + return nullptr; + } + + virtual StructuredData::DictionarySP ScriptedProcess_ReadMemoryAtAddress( + StructuredData::ObjectSP scripted_process_object_sp, lldb::addr_t address, + size_t size) { + return nullptr; + } + + virtual StructuredData::DictionarySP ScriptedProcess_GetLoadedImages( + StructuredData::ObjectSP scripted_process_object_sp) { + return nullptr; + } + + virtual StructuredData::DictionarySP ScriptedProcess_CanDebug( + StructuredData::ObjectSP scripted_process_object_sp) { + return nullptr; + } + + virtual StructuredData::DictionarySP + ScriptedProcess_IsAlive(StructuredData::ObjectSP scripted_process_object_sp) { + return nullptr; + } + protected: Debugger &m_debugger; lldb::ScriptLanguage m_script_lang; diff --git a/lldb/include/lldb/Target/ScriptedProcess.h b/lldb/include/lldb/Target/ScriptedProcess.h new file mode 100644 index 000000000000..2e2ac4fab70f --- /dev/null +++ b/lldb/include/lldb/Target/ScriptedProcess.h @@ -0,0 +1,105 @@ +//===-- ScriptableProcess.cpp -------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_SCRIPTABLE_PROCESS_H +#define LLDB_SOURCE_PLUGINS_SCRIPTABLE_PROCESS_H + +#include "lldb/Target/Process.h" +//#include "lldb/Target/StopInfo.h" +//#include "lldb/Target/Target.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/Status.h" +// +//#include "llvm/Support/Format.h" +//#include "llvm/Support/raw_ostream.h" + +namespace lldb_private { + +class ScriptedProcess : public Process { +public: + static lldb::ProcessSP CreateInstance(lldb::TargetSP target_sp, + lldb::ListenerSP listener_sp, + const FileSpec *crash_file_path, + bool can_connect); + + static void Initialize(); + + static void Terminate(); + + static ConstString GetPluginNameStatic(); + + static const char *GetPluginDescriptionStatic(); + + ScriptedProcess(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp, + const ScriptedProcessLaunchInfo &launch_info); + + ~ScriptedProcess() override; + + bool CanDebug(lldb::TargetSP target_sp, + bool plugin_specified_by_name) override; + + DynamicLoader *GetDynamicLoader() override { return nullptr; } + + ConstString GetPluginName() override; + + uint32_t GetPluginVersion() override; + + SystemRuntime *GetSystemRuntime() override { return nullptr; } + + Status DoDestroy() override; + + void RefreshStateAfterStop() override; + + bool IsAlive() override; + + bool WarnBeforeDetach() const override; + + size_t ReadMemory(lldb::addr_t addr, void *buf, size_t size, + Status &error) override; + + size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size, + Status &error) override; + + ArchSpec GetArchitecture(); + + Status GetMemoryRegionInfo(lldb::addr_t load_addr, + MemoryRegionInfo &range_info) override; + + Status + GetMemoryRegions(lldb_private::MemoryRegionInfos ®ion_list) override; + + bool GetProcessInfo(ProcessInstanceInfo &info) override; + + Status WillResume() override { + Status error; + error.SetErrorStringWithFormat( + "error: %s does not support resuming processes", + GetPluginName().GetCString()); + return error; + } + +protected: + void Clear(); + + bool UpdateThreadList(ThreadList &old_thread_list, + ThreadList &new_thread_list) override; + +private: + const ScriptedProcessLaunchInfo &m_launch_info; + lldb::ScriptInterpreterSP m_interpreter_sp; + lldb_private::StructuredData::ObjectSP m_python_object_sp; + // lldb::DataBufferSP m_core_data; + // llvm::ArrayRef<minidump::Thread> m_thread_list; + // const minidump::ExceptionStream *m_active_exception; + // bool m_is_wow64; + // llvm::Optional<MemoryRegionInfos> m_memory_regions; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_SCRIPTABLE_PROCESS_H diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h index 69baefb964b0..af0ab6b1be83 100644 --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -519,6 +519,20 @@ public: bool IsDummyTarget() const { return m_is_dummy_target; } + bool IsScriptable() const { + return m_scripted_process_launch_info.hasValue(); + } + + llvm::Optional<lldb_private::ScriptedProcessLaunchInfo> + GetScriptedProcessLaunchInfo() const { + return m_scripted_process_launch_info; + } + + void + SetScriptedProcessLaunchInfo(const ScriptedProcessLaunchInfo &launch_info) { + m_scripted_process_launch_info = launch_info; + } + /// Find a binary on the system and return its Module, /// or return an existing Module that is already in the Target. /// @@ -1420,6 +1434,9 @@ protected: bool m_valid; bool m_suppress_stop_hooks; bool m_is_dummy_target; + bool m_is_scriptable; + llvm::Optional<lldb_private::ScriptedProcessLaunchInfo> + m_scripted_process_launch_info; unsigned m_next_persistent_variable_index = 0; /// An optional \a lldb_private::Trace object containing processor trace /// information of this target. diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h index a297a928a3f4..01eea3d46035 100644 --- a/lldb/include/lldb/lldb-forward.h +++ b/lldb/include/lldb/lldb-forward.h @@ -340,6 +340,7 @@ typedef std::shared_ptr<lldb_private::LineTable> LineTableSP; typedef std::shared_ptr<lldb_private::Listener> ListenerSP; typedef std::weak_ptr<lldb_private::Listener> ListenerWP; typedef std::shared_ptr<lldb_private::MemoryHistory> MemoryHistorySP; +typedef std::shared_ptr<lldb_private::MemoryRegionInfo> MemoryRegionInfoSP; typedef std::unique_ptr<lldb_private::MemoryRegionInfo> MemoryRegionInfoUP; typedef std::shared_ptr<lldb_private::Module> ModuleSP; typedef std::weak_ptr<lldb_private::Module> ModuleWP; diff --git a/lldb/source/Commands/CMakeLists.txt b/lldb/source/Commands/CMakeLists.txt index 4f10516c2f69..135afcb43dfd 100644 --- a/lldb/source/Commands/CMakeLists.txt +++ b/lldb/source/Commands/CMakeLists.txt @@ -20,6 +20,7 @@ add_lldb_library(lldbCommands CommandObjectPlatform.cpp CommandObjectPlugin.cpp CommandObjectProcess.cpp + CommandObjectProcessScripted.cpp CommandObjectQuit.cpp CommandObjectRegexCommand.cpp CommandObjectRegister.cpp diff --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp index 1eef2800ce16..7edf2dcfc3b9 100644 --- a/lldb/source/Commands/CommandObjectProcess.cpp +++ b/lldb/source/Commands/CommandObjectProcess.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "CommandObjectProcess.h" +#include "CommandObjectProcessScripted.h" #include "lldb/Breakpoint/Breakpoint.h" #include "lldb/Breakpoint/BreakpointLocation.h" #include "lldb/Breakpoint/BreakpointSite.h" @@ -1584,6 +1585,8 @@ CommandObjectMultiwordProcess::CommandObjectMultiwordProcess( CommandObjectSP(new CommandObjectProcessSignal(interpreter))); LoadSubCommand("handle", CommandObjectSP(new CommandObjectProcessHandle(interpreter))); + LoadSubCommand("scripted", CommandObjectSP(new CommandObjectProcessScripted( + interpreter))); LoadSubCommand("status", CommandObjectSP(new CommandObjectProcessStatus(interpreter))); LoadSubCommand("interrupt", CommandObjectSP(new CommandObjectProcessInterrupt( diff --git a/lldb/source/Commands/CommandObjectProcessScripted.cpp b/lldb/source/Commands/CommandObjectProcessScripted.cpp new file mode 100644 index 000000000000..f2d3250027e9 --- /dev/null +++ b/lldb/source/Commands/CommandObjectProcessScripted.cpp @@ -0,0 +1,195 @@ +//===-- CommandObjectProcessScripted.cpp ----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "CommandObjectProcessScripted.h" + +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandObject.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionGroupPythonClassWithDict.h" + +using namespace lldb; +using namespace lldb_private; + +// CommandObjectProcessScriptedLoad +#pragma mark CommandObjectProcessScriptedLoad +#define LLDB_OPTIONS_process_scripted_load +#include "CommandOptions.inc" + +class CommandObjectProcessScriptedLoad : public CommandObjectParsed { +private: + class CommandOptions : public OptionGroup { + public: + CommandOptions() : OptionGroup() {} + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = + g_process_scripted_load_options[option_idx].short_option; + + switch (short_option) { + case 'S': + m_module = std::string(option_arg); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_module = ""; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::makeArrayRef(g_process_scripted_load_options); + } + + std::string m_module; + }; + + CommandOptions m_options; + OptionGroupPythonClassWithDict m_class_options; + OptionGroupOptions m_all_options; + + Options *GetOptions() override { return &m_all_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { +#if LLDB_ENABLE_PYTHON + if (m_class_options.GetName().empty()) { + result.AppendErrorWithFormat( + "%s needs a Python class name (-l argument).\n", m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (m_options.m_module.empty()) { + result.AppendErrorWithFormat("%s needs a module name (-s argument).\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter(); + + if (interpreter && + !interpreter->CheckObjectExists(m_class_options.GetName().c_str())) { + result.AppendWarning( + "The provided class does not exist - please define it " + "before attempting to use this frame recognizer"); + } + + Target &target = GetSelectedOrDummyTarget(); + if (target.IsValid()) { + FileSpec script_spec(m_options.m_module); + ScriptedProcessLaunchInfo launch_info = + (m_class_options.IsClass()) + ? ScriptedProcessLaunchInfo(script_spec, + m_class_options.GetName()) + : ScriptedProcessLaunchInfo(m_class_options.GetStructuredData()); + target.SetScriptedProcessLaunchInfo(launch_info); + } +#endif + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); + } + +public: + CommandObjectProcessScriptedLoad(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "process scripted load", + "Load a scripted process.\n" + "You can either specify a script file and the implementation class " + "or you can specify a dictionary of key (-k) and value (-v) pairs " + "that will be used to populate an SBStructuredData Dictionary, " + "which " + "will be passed to the constructor of the class implementing the " + "scripted step. See the Python Reference for more details.", + nullptr), + m_options(), m_class_options("process scripted load") { + m_all_options.Append(&m_class_options, LLDB_OPT_SET_1 | LLDB_OPT_SET_2, + LLDB_OPT_SET_ALL); + m_all_options.Append(&m_options, LLDB_OPT_SET_3 | LLDB_OPT_SET_4, + LLDB_OPT_SET_ALL); + m_all_options.Finalize(); + /* FIXME: Update Long Help + // SetHelpLong(R"( + // Frame recognizers allow for retrieving information about + // special frames based on ABI, arguments or other special + // properties of that frame, even without source code or + // debug info. Currently, one use case is to extract function + // arguments that would otherwise be unaccesible, or augment + // existing arguments. + // + // Adding a custom frame recognizer is possible by + // implementing a Python class and using the 'frame + // recognizer add' command. The Python class should have a + // 'get_recognized_arguments' method and it will receive an + // argument of type lldb.SBFrame representing the current + // frame that we are trying to recognize. The method should + // return a (possibly empty) list of lldb.SBValue objects + // that represent the recognized arguments. + // + // An example of a recognizer that retrieves the file + // descriptor values from libc functions 'read', 'write' and + // 'close' follows: + // + // class LibcFdRecognizer(object): + // def get_recognized_arguments(self, frame): + // if frame.name in ["read", "write", "close"]: + // fd = frame.EvaluateExpression("$arg1").unsigned + // value = lldb.target.CreateValueFromExpression("fd", + // "(int)%d" % fd) return [value] return [] + // + // The file containing this implementation can be imported + // via 'command script import' and then we can register this + // recognizer with 'frame recognizer add'. It's important to + // restrict the recognizer to the libc library (which is + // libsystem_kernel.dylib on macOS) to avoid matching + // functions with the same name in other modules: + // + // (lldb) command script import .../fd_recognizer.py + // (lldb) frame recognizer add -l + // fd_recognizer.LibcFdRecognizer -n read -s + // libsystem_kernel.dylib + // + // When the program is stopped at the beginning of the 'read' + // function in libc, we can view the recognizer arguments in + // 'frame variable': + // + // (lldb) b read + // (lldb) r + // Process 1234 stopped + // * thread #1, queue = 'com.apple.main-thread', stop reason + // = breakpoint 1.3 frame #0: 0x00007fff06013ca0 + // libsystem_kernel.dylib`read (lldb) frame variable (int) fd + // = 3 + // + // )"); + */ + } + ~CommandObjectProcessScriptedLoad() override = default; +}; + +CommandObjectProcessScripted::CommandObjectProcessScripted( + CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "process scripted", + "Commands for operating on scripted processes.", + "process plugin <subcommand> [<subcommand-options>]") { + LoadSubCommand("load", CommandObjectSP(new CommandObjectProcessScriptedLoad( + interpreter))); + // TODO: Implement CommandObjectProcessPluginScriptedGenerate + // LoadSubCommand( + // "generate", + // CommandObjectSP(new CommandObjectProcessScriptedLoad(interpreter))); +} diff --git a/lldb/source/Commands/CommandObjectProcessScripted.h b/lldb/source/Commands/CommandObjectProcessScripted.h new file mode 100644 index 000000000000..1472a68ce7b9 --- /dev/null +++ b/lldb/source/Commands/CommandObjectProcessScripted.h @@ -0,0 +1,24 @@ +//===-- CommandObjectProcessScripted.h ------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_COMMAND_OBJECT_PROCESS_SCRIPTED_H +#define LLDB_SOURCE_COMMAND_OBJECT_PROCESS_SCRIPTED_H + +#include "lldb/Interpreter/CommandObjectMultiword.h" + +namespace lldb_private { + +class CommandObjectProcessScripted : public CommandObjectMultiword { +public: + CommandObjectProcessScripted(CommandInterpreter &interpreter); + ~CommandObjectProcessScripted() override {} +}; + +}; // namespace lldb_private + +#endif // LLDB_SOURCE_COMMAND_OBJECT_PROCESS_SCRIPTED_H diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td index 7522f47ca57d..96c5bd6271f9 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -694,6 +694,12 @@ let Command = "process handle" in { Desc<"Whether or not the signal should be passed to the process.">; } +let Command = "process scripted load" in { + def scripted_process_shlib : Option<"shlib", "S">, Arg<"ShlibName">, + Completion<"Module">, + Desc<"Name of the Python module that holds the scripted porcess.">; +} + let Command = "process status" in { def process_status_verbose : Option<"verbose", "v">, Group<1>, Desc<"Show verbose process status including extended crash information.">; diff --git a/lldb/source/Plugins/Process/Scripted/CMakeLists.txt b/lldb/source/Plugins/Process/Scripted/CMakeLists.txt new file mode 100644 index 000000000000..0a3b3bc538a3 --- /dev/null +++ b/lldb/source/Plugins/Process/Scripted/CMakeLists.txt @@ -0,0 +1,21 @@ +lldb_tablegen(ScriptedProcessOptions.inc -gen-lldb-option-defs + SOURCE ScriptedProcessOptions.td + TARGET LLDBPluginScriptedProcessOptionGen) + +add_lldb_library(lldbPluginScriptedProcess PLUGIN + ScriptedProcess.cpp + + LINK_LIBS + lldbCore + lldbTarget + lldbUtility + lldbPluginProcessUtility + LINK_COMPONENTS + BinaryFormat + Object + Support + ) + +add_dependencies(lldbPluginScriptedProcess + LLDBPluginScriptedProcessOptionGen +) diff --git a/lldb/source/Plugins/Process/Scripted/ScriptedProcessOptions.td b/lldb/source/Plugins/Process/Scripted/ScriptedProcessOptions.td new file mode 100644 index 000000000000..55ea06e6de65 --- /dev/null +++ b/lldb/source/Plugins/Process/Scripted/ScriptedProcessOptions.td @@ -0,0 +1,7 @@ +include "../../../Commands/OptionsBase.td" + +let Command = "process scripted load" in { + def scripted_process_shlib : Option<"shlib", "S">, Arg<"ShlibName">, + Completion<"Module">, + Desc<"Name of the Python module that holds the scripted porcess.">; +} diff --git a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp index 6f03825cd6cd..ae57051a60b0 100644 --- a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp +++ b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp @@ -27,8 +27,8 @@ #include "lldb/Utility/Log.h" #include "lldb/Utility/State.h" -#include "ProcessMachCore.h" #include "Plugins/Process/Utility/StopInfoMachException.h" +#include "ProcessMachCore.h" #include "ThreadMachCore.h" // Needed for the plug-in names for the dynamic loaders. diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp index 6b53bd3a2edc..cc645edd4220 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -2148,6 +2148,198 @@ ScriptInterpreterPythonImpl::CreateScriptCommandObject(const char *class_name) { return StructuredData::GenericSP(new StructuredPythonObject(ret_val)); } +#pragma mark ScriptedProcessInterface + +// StructuredData::GenericSP +// ScriptInterpreterPythonImpl::ScriptedProcess_CreatePluginObject( +// const char +// *class_name, +// lldb::ProcessSP +// process_sp) { +// if (class_name == nullptr || class_name[0] == '\0') +// return StructuredData::GenericSP(); +// +// if (!process_sp) +// return StructuredData::GenericSP(); +// +// void *ret_val; +// +// { +// Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, +// Locker::FreeLock); +// ret_val = LLDBSWIGPythonCreateOSPlugin( +// class_name, +// m_dictionary_name.c_str(), +// process_sp); +// } +// +// return StructuredData::GenericSP(new StructuredPythonObject(ret_val)); +//} + +StructuredData::DictionarySP +ScriptInterpreterPythonImpl::ScriptedProcess_Create( + StructuredData::ObjectSP scripted_process_object_sp, lldb::pid_t pid, + lldb::addr_t context) { + Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock); + + static char callee_name[] = "create_process"; + std::string param_format; + param_format += GetPythonValueFormatString(pid); + param_format += GetPythonValueFormatString(context); + + if (!scripted_process_object_sp) + return StructuredData::DictionarySP(); + + StructuredData::Generic *generic = scripted_process_object_sp->GetAsGeneric(); + if (!generic) + return nullptr; + + PythonObject implementor(PyRefType::Borrowed, + (PyObject *)generic->GetValue()); + + if (!implementor.IsAllocated()) + return StructuredData::DictionarySP(); + + PythonObject pmeth(PyRefType::Owned, + PyObject_GetAttrString(implementor.get(), callee_name)); + + if (PyErr_Occurred()) + PyErr_Clear(); + + if (!pmeth.IsAllocated()) + return StructuredData::DictionarySP(); + + if (PyCallable_Check(pmeth.get()) == 0) { + if (PyErr_Occurred()) + PyErr_Clear(); + return StructuredData::DictionarySP(); + } + + if (PyErr_Occurred()) + PyErr_Clear(); + + // right now we know this function exists and is callable.. + PythonObject py_return(PyRefType::Owned, + PyObject_CallMethod(implementor.get(), callee_name, + ¶m_format[0], pid, context)); + + // if it fails, print the error but otherwise go on + if (PyErr_Occurred()) { + PyErr_Print(); + PyErr_Clear(); + } + + if (py_return.get()) { + PythonDictionary result_dict(PyRefType::Borrowed, py_return.get()); + return result_dict.CreateStructuredDictionary(); + } + return StructuredData::DictionarySP(); +} + +StructuredData::DictionarySP +ScriptInterpreterPythonImpl::ScriptedProcess_GetNumMemoryRegions( + StructuredData::ObjectSP scripted_process_object_sp) { + Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock); + + static char callee_name[] = "get_num_memory_regions"; + + if (!scripted_process_object_sp) + return StructuredData::DictionarySP(); + + StructuredData::Generic *generic = scripted_process_object_sp->GetAsGeneric(); + if (!generic) + return nullptr; + PythonObject implementor(PyRefType::Borrowed, + (PyObject *)generic->GetValue()); + + if (!implementor.IsAllocated()) + return StructuredData::DictionarySP(); + + PythonObject pmeth(PyRefType::Owned, + PyObject_GetAttrString(implementor.get(), callee_name)); + + if (PyErr_Occurred()) + PyErr_Clear(); + + if (!pmeth.IsAllocated()) + return StructuredData::DictionarySP(); + + if (PyCallable_Check(pmeth.get()) == 0) { + if (PyErr_Occurred()) + PyErr_Clear(); + return StructuredData::DictionarySP(); + } + + if (PyErr_Occurred()) + PyErr_Clear(); + + // right now we know this function exists and is callable.. + PythonObject py_return( + PyRefType::Owned, + PyObject_CallMethod(implementor.get(), callee_name, nullptr)); + + // if it fails, print the error but otherwise go on + if (PyErr_Occurred()) { + PyErr_Print(); + PyErr_Clear(); + } + + if (py_return.get()) { + PythonDictionary result(PyRefType::Borrowed, py_return.get()); + return result.CreateStructuredDictionary(); + } + return StructuredData::DictionarySP(); +} + +lldb::MemoryRegionInfoSP +ScriptInterpreterPythonImpl::ScriptedProcess_GetMemoryRegionAtIndex( + StructuredData::ObjectSP scripted_process_object_sp, size_t index) { + return nullptr; +} + +StructuredData::DictionarySP +ScriptInterpreterPythonImpl::ScriptedProcess_GetNumThreads( + StructuredData::ObjectSP scripted_process_object_sp) { + return nullptr; +} + +StructuredData::DictionarySP +ScriptInterpreterPythonImpl::ScriptedProcess_GetThreadAtIndex( + StructuredData::ObjectSP scripted_process_object_sp, size_t index) { + return nullptr; +} + +StructuredData::DictionarySP +ScriptInterpreterPythonImpl::ScriptedProcess_GetRegisterForThread( + StructuredData::ObjectSP scripted_process_object_sp) { + return nullptr; +} + +StructuredData::DictionarySP +ScriptInterpreterPythonImpl::ScriptedProcess_ReadMemoryAtAddress( + StructuredData::ObjectSP scripted_process_object_sp, lldb::addr_t address, + size_t size) { + return nullptr; +} + +StructuredData::DictionarySP +ScriptInterpreterPythonImpl::ScriptedProcess_GetLoadedImages( + StructuredData::ObjectSP scripted_process_object_sp) { + return nullptr; +} + +StructuredData::DictionarySP +ScriptInterpreterPythonImpl::ScriptedProcess_CanDebug( + StructuredData::ObjectSP scripted_process_object_sp) { + return nullptr; +} + +StructuredData::DictionarySP +ScriptInterpreterPythonImpl::ScriptedProcess_IsAlive( + StructuredData::ObjectSP scripted_process_object_sp) { + return nullptr; +} + bool ScriptInterpreterPythonImpl::GenerateTypeScriptFunction( const char *oneliner, std::string &output, const void *name_token) { StringList input; diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h index 45dad4217005..17a8fc5ca6d3 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -74,6 +74,44 @@ public: StructuredData::GenericSP CreateScriptCommandObject(const char *class_name) override; + StructuredData::DictionarySP ScriptedProcess_CreatePluginObject( + StructuredData::ObjectSP scripted_process_object_sp, lldb::pid_t pid, + lldb::addr_t context); + + StructuredData::DictionarySP + ScriptedProcess_Create(StructuredData::ObjectSP scripted_process_object_sp, + lldb::pid_t pid, lldb::addr_t context) override; + + StructuredData::DictionarySP ScriptedProcess_GetNumMemoryRegions( + StructuredData::ObjectSP scripted_process_object_sp) override; + + lldb::MemoryRegionInfoSP ScriptedProcess_GetMemoryRegionAtIndex( + StructuredData::ObjectSP scripted_process_object_sp, + size_t index) override; + + StructuredData::DictionarySP ScriptedProcess_GetNumThreads( + StructuredData::ObjectSP scripted_process_object_sp) override; + + StructuredData::DictionarySP ScriptedProcess_GetThreadAtIndex( + StructuredData::ObjectSP scripted_process_object_sp, + size_t index) override; + + StructuredData::DictionarySP ScriptedProcess_GetRegisterForThread( + StructuredData::ObjectSP scripted_process_object_sp) override; + + StructuredData::DictionarySP ScriptedProcess_ReadMemoryAtAddress( + StructuredData::ObjectSP scripted_process_object_sp, lldb::addr_t address, + size_t size) override; + + StructuredData::DictionarySP ScriptedProcess_GetLoadedImages( + StructuredData::ObjectSP scripted_process_object_sp) override; + + StructuredData::DictionarySP ScriptedProcess_CanDebug( + StructuredData::ObjectSP scripted_process_object_sp) override; + + StructuredData::DictionarySP ScriptedProcess_IsAlive( + StructuredData::ObjectSP scripted_process_object_sp) override; + StructuredData::ObjectSP CreateScriptedThreadPlan(const char *class_name, StructuredDataImpl *args_data, diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt index 383ac5a6c53c..cf3275436be0 100644 --- a/lldb/source/Target/CMakeLists.txt +++ b/lldb/source/Target/CMakeLists.txt @@ -32,6 +32,7 @@ add_lldb_library(lldbTarget RegisterContextUnwind.cpp RegisterNumber.cpp RemoteAwarePlatform.cpp + ScriptedProcess.cpp SectionLoadHistory.cpp SectionLoadList.cpp StackFrame.cpp @@ -55,6 +56,7 @@ add_lldb_library(lldbTarget ThreadPlanPython.cpp ThreadPlanRunToAddress.cpp ThreadPlanShouldStopHere.cpp + ThreadPlanStack.cpp ThreadPlanStepInRange.cpp ThreadPlanStepInstruction.cpp ThreadPlanStepOut.cpp @@ -64,7 +66,6 @@ add_lldb_library(lldbTarget ThreadPlanStepThrough.cpp ThreadPlanStepUntil.cpp ThreadPlanTracer.cpp - ThreadPlanStack.cpp ThreadSpec.cpp ThreadTrace.cpp Trace.cpp diff --git a/lldb/source/Target/ScriptedProcess.cpp b/lldb/source/Target/ScriptedProcess.cpp new file mode 100644 index 000000000000..d02acee3eea5 --- /dev/null +++ b/lldb/source/Target/ScriptedProcess.cpp @@ -0,0 +1,286 @@ +//===-- ScriptedProcess.cpp -------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===--------------------------------------------------------------------===// + +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" + +//#include "lldb/Core/ModuleSpec.h" + +#include "lldb/Core/PluginManager.h" + +//#include "lldb/Core/Section.h" + +#include "lldb/Host/OptionParser.h" + +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/OptionGroupBoolean.h" +#include "lldb/Interpreter/ScriptInterpreter.h" + +#include "lldb/Target/MemoryRegionInfo.h" +#include "lldb/Target/ScriptedProcess.h" +//#include "lldb/Target/SectionLoadList.h" +//#include "lldb/Target/Target.h" +//#include "lldb/Target/UnixSignals.h" +//#include "lldb/Utility/LLDBAssert.h" +//#include "lldb/Utility/Log.h" +//#include "lldb/Utility/State.h" +//#include "llvm/BinaryFormat/Magic.h" +//#include "llvm/Support/MemoryBuffer.h" +//#include "llvm/Support/Threading.h" + +//#include <memory> + +using namespace lldb; +using namespace lldb_private; + +lldb::ProcessSP ScriptedProcess::CreateInstance(lldb::TargetSP target_sp, + lldb::ListenerSP listener_sp, + const FileSpec *file, + bool can_connect) { + + // TODO: Check FileSpec content ? + if (!target_sp->IsScriptable()) { + return nullptr; + } + + const ScriptedProcessLaunchInfo &launch_info = + *target_sp->GetScriptedProcessLaunchInfo(); + + return std::make_shared<ScriptedProcess>(target_sp, listener_sp, launch_info); +} + +bool ScriptedProcess::CanDebug(lldb::TargetSP target_sp, + bool plugin_specified_by_name) { + return true; +} + +ScriptedProcess::ScriptedProcess(lldb::TargetSP target_sp, + lldb::ListenerSP listener_sp, + const ScriptedProcessLaunchInfo &launch_info) + : Process(target_sp, listener_sp), m_launch_info(launch_info), + m_interpreter_sp(nullptr), m_python_object_sp(nullptr) { + if (!target_sp) + return; + + m_interpreter_sp.reset(target_sp->GetDebugger().GetScriptInterpreter()); + + if (!m_interpreter_sp) + return; + + const FileSpec &script_file = m_launch_info.GetScriptFileSpec(); + + std::string scripted_process_plugin_class_name( + script_file.GetFilename().AsCString("")); + if (!scripted_process_plugin_class_name.empty()) { + const std::string scripted_process_plugin_path = script_file.GetPath(); + const bool init_session = false; + Status error; + if (m_interpreter_sp->LoadScriptingModule( + scripted_process_plugin_path.c_str(), init_session, error)) { + // Strip the ".py" extension if there is one + size_t py_extension_pos = scripted_process_plugin_class_name.rfind(".py"); + if (py_extension_pos != std::string::npos) + scripted_process_plugin_class_name.erase(py_extension_pos); + // Add ".OperatingSystemPlugIn" to the module name to get a string like + // "modulename.OperatingSystemPlugIn" + scripted_process_plugin_class_name += ".OperatingSystemPlugIn"; + + StructuredData::ObjectSP object_sp = + m_interpreter_sp->ScriptedProcess_Create(NULL, 0, + LLDB_INVALID_ADDRESS); + // m_interpreter_sp->OSPlugin_CreatePluginObject(scripted_process_plugin_class_name.c_str(), + // process->CalculateProcess()); + if (object_sp && object_sp->IsValid()) + m_python_object_sp = object_sp; + } + } +} + +ScriptedProcess::~ScriptedProcess() { + Clear(); + // We need to call finalize on the process before destroying ourselves to + // make sure all of the broadcaster cleanup goes as planned. If we destruct + // this class, then Process::~Process() might have problems trying to fully + // destroy the broadcaster. + Finalize(); +} + +void ScriptedProcess::Initialize() { + static llvm::once_flag g_once_flag; + + llvm::call_once(g_once_flag, []() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + GetPluginDescriptionStatic(), CreateInstance); + }); +} + +void ScriptedProcess::Terminate() { + PluginManager::UnregisterPlugin(ScriptedProcess::CreateInstance); +} + +ConstString ScriptedProcess::GetPluginName() { return GetPluginNameStatic(); } + +uint32_t ScriptedProcess::GetPluginVersion() { return 1; } + +Status ScriptedProcess::DoDestroy() { return Status(); } + +void ScriptedProcess::RefreshStateAfterStop() { + // + // if (!m_active_exception) + // return; + // + // constexpr uint32_t BreakpadDumpRequested = 0xFFFFFFFF; + // if (m_active_exception->ExceptionRecord.ExceptionCode == + // BreakpadDumpRequested) { + // // This "ExceptionCode" value is a sentinel that is sometimes used + // // when generating a dump for a process that hasn't crashed. + // + // // TODO: The definition and use of this "dump requested" constant + // // in Breakpad are actually Linux-specific, and for similar use + // // cases on Mac/Windows it defines different constants, referring + // // to them as "simulated" exceptions; consider moving this check + // // down to the OS-specific paths and checking each OS for its own + // // constant. + // return; + // } + // + // lldb::StopInfoSP stop_info; + // lldb::ThreadSP stop_thread; + // + // Process::m_thread_list.SetSelectedThreadByID(m_active_exception->ThreadId); + // stop_thread = Process::m_thread_list.GetSelectedThread(); + // ArchSpec arch = GetArchitecture(); + // + // if (arch.GetTriple().getOS() == llvm::Triple::Linux) { + // uint32_t signo = m_active_exception->ExceptionRecord.ExceptionCode; + // + // if (signo == 0) { + // // No stop. + // return; + // } + // + // stop_info = StopInfo::CreateStopReasonWithSignal( + // *stop_thread, signo); + // } else if (arch.GetTriple().getVendor() == llvm::Triple::Apple) { + // stop_info = StopInfoMachException::CreateStopReasonWithMachException( + // *stop_thread, + // m_active_exception->ExceptionRecord.ExceptionCode, + // 2, + // m_active_exception->ExceptionRecord.ExceptionFlags, + // m_active_exception->ExceptionRecord.ExceptionAddress, + // 0); + // } else { + // std::string desc; + // llvm::raw_string_ostream desc_stream(desc); + // desc_stream << "Exception " + // << llvm::format_hex( + // m_active_exception->ExceptionRecord.ExceptionCode, + // 8) + // << " encountered at address " + // << llvm::format_hex( + // m_active_exception->ExceptionRecord.ExceptionAddress, + // 8); + // stop_info = StopInfo::CreateStopReasonWithException( + // *stop_thread, + // desc_stream.str().c_str()); + // } + // + // stop_thread->SetStopInfo(stop_info); +} + +bool ScriptedProcess::IsAlive() { return true; } + +bool ScriptedProcess::WarnBeforeDetach() const { return false; } + +size_t ScriptedProcess::ReadMemory(lldb::addr_t addr, void *buf, size_t size, + Status &error) { + // Don't allow the caching that lldb_private::Process::ReadMemory does since + // we have it all cached in our dump file anyway. + + return DoReadMemory(addr, buf, size, error); +} + +size_t ScriptedProcess::DoReadMemory(lldb::addr_t addr, void *buf, size_t size, + Status &error) { + + m_interpreter_sp->ScriptedProcess_ReadMemoryAtAddress(m_python_object_sp, + addr, size); + return size; +} + +ArchSpec ScriptedProcess::GetArchitecture() { + llvm::Triple triple; + // triple.setVendor(llvm::Triple::VendorType::UnknownVendor); + // triple.setArch(llvm::Triple::ArchType::x86); + // triple.setOS(llvm::Triple::OSType::Win32); + return ArchSpec(triple); +} + +Status ScriptedProcess::GetMemoryRegionInfo(lldb::addr_t load_addr, + MemoryRegionInfo ®ion) { + // BuildMemoryRegions(); + // region = MinidumpParser::GetMemoryRegionInfo(*m_memory_regions, + // load_addr); + return Status(); +} + +Status ScriptedProcess::GetMemoryRegions(MemoryRegionInfos ®ion_list) { + Status error; + auto res = + m_interpreter_sp->ScriptedProcess_GetNumMemoryRegions(m_python_object_sp); + + if (!res) { + error.SetErrorString( + "ScriptedProcess: Couldn't get number of memory regions!"); + return error; + } + + uint64_t size = res->GetAsInteger()->GetValue(); + + if (size == UINT64_MAX) { + error.SetErrorString("ScriptedProcess: Invalid number of memory region!"); + return error; + } + + for (uint64_t i = 0; i < size; i++) { + // FIXME: Update interface method to handle extra arg (index) + MemoryRegionInfoSP mem_region_sp = + m_interpreter_sp->ScriptedProcess_GetMemoryRegionAtIndex( + m_python_object_sp, i); + + if (!mem_region_sp) { + // FIXME: Interpolate index in error string + error.SetErrorString( + "ScriptedProcess: Couldn't fetch memory region at index BLA"); + return error; + } + region_list.push_back(*mem_region_sp.get()); + } + + return error; +} + +void ScriptedProcess::Clear() { Process::m_thread_list.Clear(); } + +bool ScriptedProcess::UpdateThreadList(ThreadList &old_thread_list, + ThreadList &new_thread_list) { + return new_thread_list.GetSize(false) > 0; +} + +bool ScriptedProcess::GetProcessInfo(ProcessInstanceInfo &info) { + info.Clear(); + info.SetProcessID(GetID()); + info.SetArchitecture(GetArchitecture()); + lldb::ModuleSP module_sp = GetTarget().GetExecutableModule(); + if (module_sp) { + const bool add_exe_file_as_first_arg = false; + info.SetExecutableFile(GetTarget().GetExecutableModule()->GetFileSpec(), + add_exe_file_as_first_arg); + } + return true; +} diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index 736864e021bb..46aee0409083 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -96,7 +96,7 @@ Target::Target(Debugger &debugger, const ArchSpec &target_arch, m_image_search_paths(ImageSearchPathsChanged, this), m_source_manager_up(), m_stop_hooks(), m_stop_hook_next_id(0), m_valid(true), m_suppress_stop_hooks(false), - m_is_dummy_target(is_dummy_target), + m_is_dummy_target(is_dummy_target), m_is_scriptable(false), m_frame_recognizer_manager_up( std::make_unique<StackFrameRecognizerManager>()), m_stats_storage(static_cast<int>(StatisticKind::StatisticMax)) |