paolosev updated this revision to Diff 239784. paolosev added a comment. I have verified the logic of the dynamic loader quite carefully, but there are a couple of things to clarify.
A Wasm module is loaded at a 64 bit address, where the upper 32 bits are used as module identifier. Let’s say that we have a module with Id==4, so it will be loaded at address 0x00000004`00000000. Each section is loaded at its relative file offset. Therefore if the code section starts at file offset 0x4d in the Wasm module, we call: Target::SetSectionLoadAddress(section_sp, 0x40000004d). The module can also contain embedded DWARF sections, which will also be loaded at their relative file offset in the same way. And since there cannot be duplicated sections in a module, there is no overlapping, we can always convert a load address back into a section. However, there are two complications. The first is that we need to call `Target::SetSectionLoadAddress()` twice, from two different places. First we need to call `Target::SetSectionLoadAddress()` in `ObjectFileWasm::SetLoadAddress()`, and then again in `DynamicLoaderWasmDYLD::DidAttach()`. The reason for this seems to originate in the sequence of function calls: In `DynamicLoaderWasmDYLD::DidAttach()` we call `ProcessGDBRemote::LoadModules()` to get list of loaded modules from the remote (Wasm engine). `ProcessGDBRemote::LoadModules()` calls, first: - `DynamicLoaderWasmDYLD::LoadModuleAtAddress()` and from there: 1. `DynamicLoader::UpdateLoadedSections() -> ObjectFileWasm::SetLoadAddress()` 2. `Target::GetImages()::AppendIfNeeded(module) -> ProcessGDBRemote::ModulesDidLoad() -> JITLoaderList::ModulesDidLoad() -> Module::GetSymbolFile() -> SymbolFileDWARF::CalculateAbilities()`. Here we initialize the symbols for the module, and set `m_did_load_symfile`, but for this to work we need to have already set the load address for each section, in the previous `ObjectFileWasm::SetLoadAddress()`. then: - `Target::SetExecutableModule() -> Target::ClearModules() -> SectionLoadList::Clear()` So, at the end of `LoadModules()` in `DynamicLoaderWasmDYLD::DidAttach()` the SectionLoadList is empty, and we need to set it again by calling `Target::.SetSectionLoadAddress()` again. This works but the duplication is ugly; is there a way to improve this? _ The second problem is that the Code Section needs to be initialized (in `ObjectFileWasm::CreateSections()`) with `m_file_addr = m_file_offset = 0`, and not with the actual file offset of the Code section in the Wasm file. If we set `Section::m_file_addr` and `Section::m_file_offset` to the actual code offset, the DWARF info does not work correctly. I have some doubts regarding the DWARF data generated by Clang for a Wasm target. Looking at an example, for a Wasm module that has the Code section at offset 0x57, I see this DWARF data: 0x0000000b: DW_TAG_compile_unit […] DW_AT_low_pc (0x0000000000000000) DW_AT_ranges (0x00000000 [0x00000002, 0x0000000e) [0x0000000f, 0x0000001a) [0x0000001b, 0x00000099) [0x0000009b, 0x0000011c)) The documentation <https://yurydelendik.github.io/webassembly-dwarf/#pc> says that //“Wherever a code address is used in DWARF for WebAssembly, it must be the offset of an instruction relative within the Code section of the WebAssembly file.”// But is this correct? Shouldn't maybe code addresses be offset-ed by the file address of the Code section? Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D72751/new/ https://reviews.llvm.org/D72751 Files: lldb/packages/Python/lldbsuite/test/functionalities/gdb_remote_client/TestWasm.py lldb/packages/Python/lldbsuite/test/functionalities/gdb_remote_client/test_wasm.yaml lldb/source/API/SystemInitializerFull.cpp lldb/source/Plugins/DynamicLoader/CMakeLists.txt lldb/source/Plugins/DynamicLoader/wasm-DYLD/CMakeLists.txt lldb/source/Plugins/DynamicLoader/wasm-DYLD/DynamicLoaderWasmDYLD.cpp lldb/source/Plugins/DynamicLoader/wasm-DYLD/DynamicLoaderWasmDYLD.h lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.h lldb/tools/lldb-test/SystemInitializerTest.cpp
Index: lldb/tools/lldb-test/SystemInitializerTest.cpp =================================================================== --- lldb/tools/lldb-test/SystemInitializerTest.cpp +++ lldb/tools/lldb-test/SystemInitializerTest.cpp @@ -37,6 +37,7 @@ #include "Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.h" #include "Plugins/DynamicLoader/Static/DynamicLoaderStatic.h" #include "Plugins/DynamicLoader/Windows-DYLD/DynamicLoaderWindowsDYLD.h" +#include "Plugins/DynamicLoader/wasm-DYLD/DynamicLoaderWasmDYLD.h" #include "Plugins/Instruction/ARM64/EmulateInstructionARM64.h" #include "Plugins/Instruction/PPC64/EmulateInstructionPPC64.h" #include "Plugins/InstrumentationRuntime/ASan/ASanRuntime.h" @@ -246,6 +247,7 @@ DynamicLoaderMacOSXDYLD::Initialize(); DynamicLoaderMacOS::Initialize(); DynamicLoaderPOSIXDYLD::Initialize(); + wasm::DynamicLoaderWasmDYLD::Initialize(); DynamicLoaderStatic::Initialize(); DynamicLoaderWindowsDYLD::Initialize(); @@ -329,6 +331,7 @@ DynamicLoaderMacOSXDYLD::Terminate(); DynamicLoaderMacOS::Terminate(); DynamicLoaderPOSIXDYLD::Terminate(); + wasm::DynamicLoaderWasmDYLD::Terminate(); DynamicLoaderStatic::Terminate(); DynamicLoaderWindowsDYLD::Terminate(); Index: lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.h =================================================================== --- lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.h +++ lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.h @@ -101,8 +101,7 @@ bool value_is_offset) override; lldb_private::Address GetBaseAddress() override { - return IsInMemory() ? Address(m_memory_addr + m_code_section_offset) - : Address(m_code_section_offset); + return IsInMemory() ? Address(m_memory_addr) : Address(0); } /// \} @@ -112,6 +111,8 @@ /// information for this module. llvm::Optional<FileSpec> GetExternalDebugInfoFileSpec(); + uint32_t GetCodeSectionOffset() const { return m_code_section_offset; } + private: ObjectFileWasm(const lldb::ModuleSP &module_sp, lldb::DataBufferSP &data_sp, lldb::offset_t data_offset, const FileSpec *file, @@ -127,7 +128,7 @@ /// \} /// Read a range of bytes from the Wasm module. - DataExtractor ReadImageData(uint64_t offset, size_t size); + DataExtractor ReadImageData(lldb::offset_t offset, uint32_t size); typedef struct section_info { lldb::offset_t offset; Index: lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp =================================================================== --- lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp +++ lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp @@ -355,12 +355,12 @@ return false; const size_t num_sections = section_list->GetSize(); - size_t sect_idx = 0; - - for (sect_idx = 0; sect_idx < num_sections; ++sect_idx) { + for (size_t sect_idx = 0; sect_idx < num_sections; ++sect_idx) { SectionSP section_sp(section_list->GetSectionAtIndex(sect_idx)); - if (target.GetSectionLoadList().SetSectionLoadAddress( - section_sp, load_address | section_sp->GetFileAddress())) { + lldb::addr_t file_address = (section_sp->GetName() == "code") + ? GetCodeSectionOffset() + : section_sp->GetFileAddress(); + if (target.SetSectionLoadAddress(section_sp, load_address | file_address)) { ++num_loaded_sections; } } @@ -368,11 +368,11 @@ return num_loaded_sections > 0; } -DataExtractor ObjectFileWasm::ReadImageData(uint64_t offset, size_t size) { +DataExtractor ObjectFileWasm::ReadImageData(offset_t offset, uint32_t size) { DataExtractor data; if (m_file) { if (offset < GetByteSize()) { - size = std::min(size, GetByteSize() - offset); + size = std::min(static_cast<uint64_t>(size), GetByteSize() - offset); auto buffer_sp = MapFileData(m_file, size, offset); return DataExtractor(buffer_sp, GetByteOrder(), GetAddressByteSize()); } Index: lldb/source/Plugins/DynamicLoader/wasm-DYLD/DynamicLoaderWasmDYLD.h =================================================================== --- /dev/null +++ lldb/source/Plugins/DynamicLoader/wasm-DYLD/DynamicLoaderWasmDYLD.h @@ -0,0 +1,52 @@ +//===-- DynamicLoaderWasmDYLD.h ---------------------------------*- C++ -*-===// +// +// 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 liblldb_Plugins_DynamicLoaderWasmDYLD_h_ +#define liblldb_Plugins_DynamicLoaderWasmDYLD_h_ + +#include "lldb/Target/DynamicLoader.h" + +namespace lldb_private { +namespace wasm { + +class DynamicLoaderWasmDYLD : public DynamicLoader { +public: + DynamicLoaderWasmDYLD(Process *process); + + static void Initialize(); + static void Terminate() {} + + static ConstString GetPluginNameStatic(); + static const char *GetPluginDescriptionStatic(); + + static DynamicLoader *CreateInstance(Process *process, bool force); + + /// DynamicLoader + /// \{ + lldb::ModuleSP LoadModuleAtAddress(const lldb_private::FileSpec &file, + lldb::addr_t link_map_addr, + lldb::addr_t base_addr, + bool base_addr_is_offset) override; + void DidAttach() override; + void DidLaunch() override {} + Status CanLoadImage() override { return Status(); } + lldb::ThreadPlanSP GetStepThroughTrampolinePlan(Thread &thread, + bool stop) override; + /// \} + + /// PluginInterface protocol. + /// \{ + ConstString GetPluginName() override { return GetPluginNameStatic(); } + uint32_t GetPluginVersion() override { return 1; } + /// \} +}; + +} // namespace wasm +} // namespace lldb_private + +#endif // liblldb_Plugins_DynamicLoaderWasmDYLD_h_ Index: lldb/source/Plugins/DynamicLoader/wasm-DYLD/DynamicLoaderWasmDYLD.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/DynamicLoader/wasm-DYLD/DynamicLoaderWasmDYLD.cpp @@ -0,0 +1,133 @@ +//===-- DynamicLoaderWasmDYLD.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 "DynamicLoaderWasmDYLD.h" + +#include "Plugins/ObjectFile/wasm/ObjectFileWasm.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/Section.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/Log.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::wasm; + +DynamicLoaderWasmDYLD::DynamicLoaderWasmDYLD(Process *process) + : DynamicLoader(process) {} + +void DynamicLoaderWasmDYLD::Initialize() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + GetPluginDescriptionStatic(), CreateInstance); +} + +ConstString DynamicLoaderWasmDYLD::GetPluginNameStatic() { + static ConstString g_plugin_name("wasm-dyld"); + return g_plugin_name; +} + +const char *DynamicLoaderWasmDYLD::GetPluginDescriptionStatic() { + return "Dynamic loader plug-in that watches for shared library " + "loads/unloads in WebAssembly engines."; +} + +DynamicLoader *DynamicLoaderWasmDYLD::CreateInstance(Process *process, + bool force) { + bool should_create = force; + if (!should_create) { + should_create = + (process->GetTarget().GetArchitecture().GetTriple().getArch() == + llvm::Triple::wasm32); + } + + if (should_create) + return new DynamicLoaderWasmDYLD(process); + + return nullptr; +} + +/// In WebAssembly, linear memory is disjointed from code space. The VM can load +/// multiple instances of a module, which logically share the same code. +/// Currently we only support wasm32, which uses a 32-bit address space for the +/// code. +/// We represent a code address in LLDB as a 64-bit address with the format: +/// 63 32 31 0 +/// +---------------+---------------+ +/// + module_id | offset | +/// +---------------+---------------+ +/// where the lower 32 bits represent a module offset (relative to the module +/// start not to the beginning of the code section) and the higher 32 bits +/// uniquely identify the module in the WebAssembly VM. +/// This method should be called for each Wasm module loaded in the debuggee, +/// with base_addr = 0x{module_id}|00000000 (for example 0x0000000100000000). +ModuleSP DynamicLoaderWasmDYLD::LoadModuleAtAddress(const FileSpec &file, + addr_t link_map_addr, + addr_t base_addr, + bool base_addr_is_offset) { + if (ModuleSP module_sp = m_process->ReadModuleFromMemory(file, base_addr)) { + UpdateLoadedSections(module_sp, link_map_addr, base_addr, false); + m_process->GetTarget().GetImages().AppendIfNeeded(module_sp); + return module_sp; + } + + return ModuleSP(); +} + +void DynamicLoaderWasmDYLD::DidAttach() { + Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_DYNAMIC_LOADER)); + LLDB_LOGF(log, "DynamicLoaderWasmDYLD::%s()", __FUNCTION__); + + // Ask the process for the list of loaded WebAssembly modules. + auto error = m_process->LoadModules(); + LLDB_LOG_ERROR(log, std::move(error), "Couldn't load modules: {0}"); + + ModuleList loaded_module_list; + const ModuleList &module_list = m_process->GetTarget().GetImages(); + const size_t num_modules = module_list.GetSize(); + for (uint32_t idx = 0; idx < num_modules; ++idx) { + ModuleSP module_sp(module_list.GetModuleAtIndexUnlocked(idx)); + if (!module_sp) + continue; + + ObjectFileWasm *image_object_file = + llvm::dyn_cast_or_null<ObjectFileWasm>(module_sp->GetObjectFile()); + if (!image_object_file) + continue; + + lldb::addr_t image_load_address = + image_object_file->GetBaseAddress().GetOffset(); + + SectionList *section_list = image_object_file->GetSectionList(); + if (!section_list) + continue; + + // Fixes the section load address for each section. + const size_t num_sections = section_list->GetSize(); + for (size_t sect_idx = 0; sect_idx < num_sections; ++sect_idx) { + SectionSP section_sp(section_list->GetSectionAtIndex(sect_idx)); + + // Code section load address is offsetted by the code section + // offset in the Wasm module. + lldb::addr_t file_addr = (section_sp->GetName() == "code") + ? image_object_file->GetCodeSectionOffset() + : section_sp->GetFileAddress(); + lldb::addr_t load_addr = image_load_address | file_addr; + if (m_process->GetTarget().SetSectionLoadAddress(section_sp, load_addr)) + loaded_module_list.AppendIfNeeded(module_sp); + } + } + + m_process->GetTarget().ModulesDidLoad(loaded_module_list); +} + +ThreadPlanSP DynamicLoaderWasmDYLD::GetStepThroughTrampolinePlan(Thread &thread, + bool stop) { + return ThreadPlanSP(); +} Index: lldb/source/Plugins/DynamicLoader/wasm-DYLD/CMakeLists.txt =================================================================== --- /dev/null +++ lldb/source/Plugins/DynamicLoader/wasm-DYLD/CMakeLists.txt @@ -0,0 +1,9 @@ +add_lldb_library(lldbPluginDynamicLoaderWasmDYLD PLUGIN + DynamicLoaderWasmDYLD.cpp + + LINK_LIBS + lldbCore + lldbTarget + LINK_COMPONENTS + Support + ) Index: lldb/source/Plugins/DynamicLoader/CMakeLists.txt =================================================================== --- lldb/source/Plugins/DynamicLoader/CMakeLists.txt +++ lldb/source/Plugins/DynamicLoader/CMakeLists.txt @@ -4,3 +4,4 @@ add_subdirectory(Static) add_subdirectory(Hexagon-DYLD) add_subdirectory(Windows-DYLD) +add_subdirectory(wasm-DYLD) Index: lldb/source/API/SystemInitializerFull.cpp =================================================================== --- lldb/source/API/SystemInitializerFull.cpp +++ lldb/source/API/SystemInitializerFull.cpp @@ -45,6 +45,7 @@ #include "Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.h" #include "Plugins/DynamicLoader/Static/DynamicLoaderStatic.h" #include "Plugins/DynamicLoader/Windows-DYLD/DynamicLoaderWindowsDYLD.h" +#include "Plugins/DynamicLoader/wasm-DYLD/DynamicLoaderWasmDYLD.h" #include "Plugins/Instruction/ARM/EmulateInstructionARM.h" #include "Plugins/Instruction/ARM64/EmulateInstructionARM64.h" #include "Plugins/Instruction/MIPS/EmulateInstructionMIPS.h" @@ -284,6 +285,7 @@ DynamicLoaderMacOSXDYLD::Initialize(); DynamicLoaderMacOS::Initialize(); DynamicLoaderPOSIXDYLD::Initialize(); + wasm::DynamicLoaderWasmDYLD::Initialize(); DynamicLoaderStatic::Initialize(); DynamicLoaderWindowsDYLD::Initialize(); @@ -377,6 +379,7 @@ DynamicLoaderMacOSXDYLD::Terminate(); DynamicLoaderMacOS::Terminate(); DynamicLoaderPOSIXDYLD::Terminate(); + wasm::DynamicLoaderWasmDYLD::Terminate(); DynamicLoaderStatic::Terminate(); DynamicLoaderWindowsDYLD::Terminate(); Index: lldb/packages/Python/lldbsuite/test/functionalities/gdb_remote_client/test_wasm.yaml =================================================================== --- /dev/null +++ lldb/packages/Python/lldbsuite/test/functionalities/gdb_remote_client/test_wasm.yaml @@ -0,0 +1,25 @@ +--- !WASM +FileHeader: + Version: 0x00000001 +Sections: + + - Type: CODE + Functions: + - Index: 0 + Locals: + - Type: I32 + Count: 6 + Body: 238080808000210141102102200120026B21032003200036020C200328020C2104200328020C2105200420056C210620060F0B + - Type: CUSTOM + Name: .debug_info + Payload: 4C00 + - Type: CUSTOM + Name: .debug_abbrev + Payload: 0111 + - Type: CUSTOM + Name: .debug_line + Payload: 5100 + - Type: CUSTOM + Name: .debug_str + Payload: 636CFF +... Index: lldb/packages/Python/lldbsuite/test/functionalities/gdb_remote_client/TestWasm.py =================================================================== --- /dev/null +++ lldb/packages/Python/lldbsuite/test/functionalities/gdb_remote_client/TestWasm.py @@ -0,0 +1,124 @@ +import lldb +import binascii +import struct +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from gdbclientutils import * + +def format_register_value(val): + """ + Encode each byte by two hex digits in little-endian order. + """ + return ''.join(x.encode('hex') for x in struct.pack('<Q', val)) + +class TestWasm(GDBRemoteTestBase): + + def setUp(self): + super(TestWasm, self).setUp() + self._initial_platform = lldb.DBG.GetSelectedPlatform() + + def tearDown(self): + lldb.DBG.SetSelectedPlatform(self._initial_platform) + super(TestWasm, self).tearDown() + + def test_connect(self): + + class MyResponder(MockGDBServerResponder): + load_address = 0x400000000 + current_pc = 0x40000000a + + def __init__(self, obj_path): + self._obj_path = obj_path + MockGDBServerResponder.__init__(self) + + def respond(self, packet): + if packet == "qProcessInfo": + return self.qProcessInfo() + if packet[0:13] == "qRegisterInfo": + return self.qRegisterInfo(packet[13:]) + return MockGDBServerResponder.respond(self, packet) + + def qSupported(self, client_supported): + return "qXfer:libraries:read+;PacketSize=1000;vContSupported-" + + def qHostInfo(self): + return "" + + def QEnableErrorStrings(self): + return "" + + def qfThreadInfo(self): + return "OK" + + def qRegisterInfo(self, index): + if (index == 0): + return "name:pc;alt-name:pc;bitsize:64;offset:0;encoding:uint;format:hex;set:General Purpose Registers;gcc:16;dwarf:16;generic:pc;" + return "E45" + + def qProcessInfo(self): + return "pid:1;ppid:1;uid:1;gid:1;euid:1;egid:1;name:%s;triple:%s;ptrsize:4" % (hex_encode_bytes("lldb"), hex_encode_bytes("wasm32-unknown-unknown-wasm")) + + def haltReason(self): + return "T05thread-pcs:" + format(self.current_pc, 'x') + ";thread:1;" + + def readRegister(self, register): + return format_register_value(self.current_pc) + + def qXferRead(self, obj, annex, offset, length): + if obj == "libraries": + xml = '<library-list><library name=\"%s\"><section address=\"%d\"/></library></library-list>' % ("test_wasm", self.load_address) + return xml, False + else: + return None, False + + def readMemory(self, addr, length): + result = "" + with open(self._obj_path, mode='rb') as file: + file_content = bytearray(file.read()) + addr_from = addr - self.load_address + addr_to = addr_from + min(length, len(file_content) - addr_from) + for i in range(addr_from, addr_to): + result += format(file_content[i], '02x') + file.close() + return result + + """Test connecting to a WebAssembly engine via GDB-remote""" + + yaml_path = "test_wasm.yaml" + yaml_base, ext = os.path.splitext(yaml_path) + obj_path = self.getBuildArtifact(yaml_base) + self.yaml2obj(yaml_path, obj_path) + + self.server.responder = MyResponder(obj_path) + + target = self.dbg.CreateTarget("") + process = self.connect(target) + lldbutil.expect_state_changes(self, self.dbg.GetListener(), process, [lldb.eStateStopped]) + + num_modules = target.GetNumModules() + self.assertEquals(1, num_modules) + + module = target.GetModuleAtIndex(0) + num_sections = module.GetNumSections() + self.assertEquals(5, num_sections) + + code_section = module.GetSectionAtIndex(0) + self.assertEquals("code", code_section.GetName()) + self.assertEquals(0x40000000a, code_section.GetLoadAddress(target)) + + debug_info_section = module.GetSectionAtIndex(1) + self.assertEquals(".debug_info", debug_info_section.GetName()) + self.assertEquals(0x400000050, debug_info_section.GetLoadAddress(target)) + + debug_abbrev_section = module.GetSectionAtIndex(2) + self.assertEquals(".debug_abbrev", debug_abbrev_section.GetName()) + self.assertEquals(0x400000062, debug_abbrev_section.GetLoadAddress(target)) + + debug_line_section = module.GetSectionAtIndex(3) + self.assertEquals(".debug_line", debug_line_section.GetName()) + self.assertEquals(0x400000072, debug_line_section.GetLoadAddress(target)) + + debug_str_section = module.GetSectionAtIndex(4) + self.assertEquals(".debug_str", debug_str_section.GetName()) + self.assertEquals(0x400000081, debug_str_section.GetLoadAddress(target)) +
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits