| //===-- ObjectFileWasm.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 "ObjectFileWasm.h" |
| #include "lldb/Core/Module.h" |
| #include "lldb/Core/ModuleSpec.h" |
| #include "lldb/Core/PluginManager.h" |
| #include "lldb/Core/Section.h" |
| #include "lldb/Target/Process.h" |
| #include "lldb/Target/SectionLoadList.h" |
| #include "lldb/Target/Target.h" |
| #include "lldb/Utility/DataBufferHeap.h" |
| #include "lldb/Utility/Log.h" |
| #include "llvm/ADT/ArrayRef.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/BinaryFormat/Magic.h" |
| #include "llvm/BinaryFormat/Wasm.h" |
| #include "llvm/Support/Endian.h" |
| #include "llvm/Support/Format.h" |
| |
| using namespace lldb; |
| using namespace lldb_private; |
| using namespace lldb_private::wasm; |
| |
| LLDB_PLUGIN_DEFINE(ObjectFileWasm) |
| |
| static const uint32_t kWasmHeaderSize = |
| sizeof(llvm::wasm::WasmMagic) + sizeof(llvm::wasm::WasmVersion); |
| |
| /// Checks whether the data buffer starts with a valid Wasm module header. |
| static bool ValidateModuleHeader(const DataBufferSP &data_sp) { |
| if (!data_sp || data_sp->GetByteSize() < kWasmHeaderSize) |
| return false; |
| |
| if (llvm::identify_magic(toStringRef(data_sp->GetData())) != |
| llvm::file_magic::wasm_object) |
| return false; |
| |
| uint8_t *Ptr = data_sp->GetBytes() + sizeof(llvm::wasm::WasmMagic); |
| |
| uint32_t version = llvm::support::endian::read32le(Ptr); |
| return version == llvm::wasm::WasmVersion; |
| } |
| |
| static llvm::Optional<ConstString> |
| GetWasmString(llvm::DataExtractor &data, llvm::DataExtractor::Cursor &c) { |
| // A Wasm string is encoded as a vector of UTF-8 codes. |
| // Vectors are encoded with their u32 length followed by the element |
| // sequence. |
| uint64_t len = data.getULEB128(c); |
| if (!c) { |
| consumeError(c.takeError()); |
| return llvm::None; |
| } |
| |
| if (len >= (uint64_t(1) << 32)) { |
| return llvm::None; |
| } |
| |
| llvm::SmallVector<uint8_t, 32> str_storage; |
| data.getU8(c, str_storage, len); |
| if (!c) { |
| consumeError(c.takeError()); |
| return llvm::None; |
| } |
| |
| llvm::StringRef str = toStringRef(makeArrayRef(str_storage)); |
| return ConstString(str); |
| } |
| |
| char ObjectFileWasm::ID; |
| |
| void ObjectFileWasm::Initialize() { |
| PluginManager::RegisterPlugin(GetPluginNameStatic(), |
| GetPluginDescriptionStatic(), CreateInstance, |
| CreateMemoryInstance, GetModuleSpecifications); |
| } |
| |
| void ObjectFileWasm::Terminate() { |
| PluginManager::UnregisterPlugin(CreateInstance); |
| } |
| |
| ObjectFile * |
| ObjectFileWasm::CreateInstance(const ModuleSP &module_sp, DataBufferSP &data_sp, |
| offset_t data_offset, const FileSpec *file, |
| offset_t file_offset, offset_t length) { |
| Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_OBJECT)); |
| |
| if (!data_sp) { |
| data_sp = MapFileData(*file, length, file_offset); |
| if (!data_sp) { |
| LLDB_LOGF(log, "Failed to create ObjectFileWasm instance for file %s", |
| file->GetPath().c_str()); |
| return nullptr; |
| } |
| data_offset = 0; |
| } |
| |
| assert(data_sp); |
| if (!ValidateModuleHeader(data_sp)) { |
| LLDB_LOGF(log, |
| "Failed to create ObjectFileWasm instance: invalid Wasm header"); |
| return nullptr; |
| } |
| |
| // Update the data to contain the entire file if it doesn't contain it |
| // already. |
| if (data_sp->GetByteSize() < length) { |
| data_sp = MapFileData(*file, length, file_offset); |
| if (!data_sp) { |
| LLDB_LOGF(log, |
| "Failed to create ObjectFileWasm instance: cannot read file %s", |
| file->GetPath().c_str()); |
| return nullptr; |
| } |
| data_offset = 0; |
| } |
| |
| std::unique_ptr<ObjectFileWasm> objfile_up(new ObjectFileWasm( |
| module_sp, data_sp, data_offset, file, file_offset, length)); |
| ArchSpec spec = objfile_up->GetArchitecture(); |
| if (spec && objfile_up->SetModulesArchitecture(spec)) { |
| LLDB_LOGF(log, |
| "%p ObjectFileWasm::CreateInstance() module = %p (%s), file = %s", |
| static_cast<void *>(objfile_up.get()), |
| static_cast<void *>(objfile_up->GetModule().get()), |
| objfile_up->GetModule()->GetSpecificationDescription().c_str(), |
| file ? file->GetPath().c_str() : "<NULL>"); |
| return objfile_up.release(); |
| } |
| |
| LLDB_LOGF(log, "Failed to create ObjectFileWasm instance"); |
| return nullptr; |
| } |
| |
| ObjectFile *ObjectFileWasm::CreateMemoryInstance(const ModuleSP &module_sp, |
| DataBufferSP &data_sp, |
| const ProcessSP &process_sp, |
| addr_t header_addr) { |
| if (!ValidateModuleHeader(data_sp)) |
| return nullptr; |
| |
| std::unique_ptr<ObjectFileWasm> objfile_up( |
| new ObjectFileWasm(module_sp, data_sp, process_sp, header_addr)); |
| ArchSpec spec = objfile_up->GetArchitecture(); |
| if (spec && objfile_up->SetModulesArchitecture(spec)) |
| return objfile_up.release(); |
| return nullptr; |
| } |
| |
| bool ObjectFileWasm::DecodeNextSection(lldb::offset_t *offset_ptr) { |
| // Buffer sufficient to read a section header and find the pointer to the next |
| // section. |
| const uint32_t kBufferSize = 1024; |
| DataExtractor section_header_data = ReadImageData(*offset_ptr, kBufferSize); |
| |
| llvm::DataExtractor data = section_header_data.GetAsLLVM(); |
| llvm::DataExtractor::Cursor c(0); |
| |
| // Each section consists of: |
| // - a one-byte section id, |
| // - the u32 size of the contents, in bytes, |
| // - the actual contents. |
| uint8_t section_id = data.getU8(c); |
| uint64_t payload_len = data.getULEB128(c); |
| if (!c) |
| return !llvm::errorToBool(c.takeError()); |
| |
| if (payload_len >= (uint64_t(1) << 32)) |
| return false; |
| |
| if (section_id == llvm::wasm::WASM_SEC_CUSTOM) { |
| // Custom sections have the id 0. Their contents consist of a name |
| // identifying the custom section, followed by an uninterpreted sequence |
| // of bytes. |
| lldb::offset_t prev_offset = c.tell(); |
| llvm::Optional<ConstString> sect_name = GetWasmString(data, c); |
| if (!sect_name) |
| return false; |
| |
| if (payload_len < c.tell() - prev_offset) |
| return false; |
| |
| uint32_t section_length = payload_len - (c.tell() - prev_offset); |
| m_sect_infos.push_back(section_info{*offset_ptr + c.tell(), section_length, |
| section_id, *sect_name}); |
| *offset_ptr += (c.tell() + section_length); |
| } else if (section_id <= llvm::wasm::WASM_SEC_TAG) { |
| m_sect_infos.push_back(section_info{*offset_ptr + c.tell(), |
| static_cast<uint32_t>(payload_len), |
| section_id, ConstString()}); |
| *offset_ptr += (c.tell() + payload_len); |
| } else { |
| // Invalid section id. |
| return false; |
| } |
| return true; |
| } |
| |
| bool ObjectFileWasm::DecodeSections() { |
| lldb::offset_t offset = kWasmHeaderSize; |
| if (IsInMemory()) { |
| offset += m_memory_addr; |
| } |
| |
| while (DecodeNextSection(&offset)) |
| ; |
| return true; |
| } |
| |
| size_t ObjectFileWasm::GetModuleSpecifications( |
| const FileSpec &file, DataBufferSP &data_sp, offset_t data_offset, |
| offset_t file_offset, offset_t length, ModuleSpecList &specs) { |
| if (!ValidateModuleHeader(data_sp)) { |
| return 0; |
| } |
| |
| ModuleSpec spec(file, ArchSpec("wasm32-unknown-unknown-wasm")); |
| specs.Append(spec); |
| return 1; |
| } |
| |
| ObjectFileWasm::ObjectFileWasm(const ModuleSP &module_sp, DataBufferSP &data_sp, |
| offset_t data_offset, const FileSpec *file, |
| offset_t offset, offset_t length) |
| : ObjectFile(module_sp, file, offset, length, data_sp, data_offset), |
| m_arch("wasm32-unknown-unknown-wasm") { |
| m_data.SetAddressByteSize(4); |
| } |
| |
| ObjectFileWasm::ObjectFileWasm(const lldb::ModuleSP &module_sp, |
| lldb::DataBufferSP &header_data_sp, |
| const lldb::ProcessSP &process_sp, |
| lldb::addr_t header_addr) |
| : ObjectFile(module_sp, process_sp, header_addr, header_data_sp), |
| m_arch("wasm32-unknown-unknown-wasm") {} |
| |
| bool ObjectFileWasm::ParseHeader() { |
| // We already parsed the header during initialization. |
| return true; |
| } |
| |
| Symtab *ObjectFileWasm::GetSymtab() { return nullptr; } |
| |
| static SectionType GetSectionTypeFromName(llvm::StringRef Name) { |
| if (Name.consume_front(".debug_") || Name.consume_front(".zdebug_")) { |
| return llvm::StringSwitch<SectionType>(Name) |
| .Case("abbrev", eSectionTypeDWARFDebugAbbrev) |
| .Case("abbrev.dwo", eSectionTypeDWARFDebugAbbrevDwo) |
| .Case("addr", eSectionTypeDWARFDebugAddr) |
| .Case("aranges", eSectionTypeDWARFDebugAranges) |
| .Case("cu_index", eSectionTypeDWARFDebugCuIndex) |
| .Case("frame", eSectionTypeDWARFDebugFrame) |
| .Case("info", eSectionTypeDWARFDebugInfo) |
| .Case("info.dwo", eSectionTypeDWARFDebugInfoDwo) |
| .Cases("line", "line.dwo", eSectionTypeDWARFDebugLine) |
| .Cases("line_str", "line_str.dwo", eSectionTypeDWARFDebugLineStr) |
| .Case("loc", eSectionTypeDWARFDebugLoc) |
| .Case("loc.dwo", eSectionTypeDWARFDebugLocDwo) |
| .Case("loclists", eSectionTypeDWARFDebugLocLists) |
| .Case("loclists.dwo", eSectionTypeDWARFDebugLocListsDwo) |
| .Case("macinfo", eSectionTypeDWARFDebugMacInfo) |
| .Cases("macro", "macro.dwo", eSectionTypeDWARFDebugMacro) |
| .Case("names", eSectionTypeDWARFDebugNames) |
| .Case("pubnames", eSectionTypeDWARFDebugPubNames) |
| .Case("pubtypes", eSectionTypeDWARFDebugPubTypes) |
| .Case("ranges", eSectionTypeDWARFDebugRanges) |
| .Case("rnglists", eSectionTypeDWARFDebugRngLists) |
| .Case("rnglists.dwo", eSectionTypeDWARFDebugRngListsDwo) |
| .Case("str", eSectionTypeDWARFDebugStr) |
| .Case("str.dwo", eSectionTypeDWARFDebugStrDwo) |
| .Case("str_offsets", eSectionTypeDWARFDebugStrOffsets) |
| .Case("str_offsets.dwo", eSectionTypeDWARFDebugStrOffsetsDwo) |
| .Case("tu_index", eSectionTypeDWARFDebugTuIndex) |
| .Case("types", eSectionTypeDWARFDebugTypes) |
| .Case("types.dwo", eSectionTypeDWARFDebugTypesDwo) |
| .Default(eSectionTypeOther); |
| } |
| return eSectionTypeOther; |
| } |
| |
| void ObjectFileWasm::CreateSections(SectionList &unified_section_list) { |
| if (m_sections_up) |
| return; |
| |
| m_sections_up = std::make_unique<SectionList>(); |
| |
| if (m_sect_infos.empty()) { |
| DecodeSections(); |
| } |
| |
| for (const section_info §_info : m_sect_infos) { |
| SectionType section_type = eSectionTypeOther; |
| ConstString section_name; |
| offset_t file_offset = sect_info.offset & 0xffffffff; |
| addr_t vm_addr = file_offset; |
| size_t vm_size = sect_info.size; |
| |
| if (llvm::wasm::WASM_SEC_CODE == sect_info.id) { |
| section_type = eSectionTypeCode; |
| section_name = ConstString("code"); |
| |
| // A code address in DWARF for WebAssembly is the offset of an |
| // instruction relative within the Code section of the WebAssembly file. |
| // For this reason Section::GetFileAddress() must return zero for the |
| // Code section. |
| vm_addr = 0; |
| } else { |
| section_type = GetSectionTypeFromName(sect_info.name.GetStringRef()); |
| if (section_type == eSectionTypeOther) |
| continue; |
| section_name = sect_info.name; |
| if (!IsInMemory()) { |
| vm_size = 0; |
| vm_addr = 0; |
| } |
| } |
| |
| SectionSP section_sp( |
| new Section(GetModule(), // Module to which this section belongs. |
| this, // ObjectFile to which this section belongs and |
| // should read section data from. |
| section_type, // Section ID. |
| section_name, // Section name. |
| section_type, // Section type. |
| vm_addr, // VM address. |
| vm_size, // VM size in bytes of this section. |
| file_offset, // Offset of this section in the file. |
| sect_info.size, // Size of the section as found in the file. |
| 0, // Alignment of the section |
| 0, // Flags for this section. |
| 1)); // Number of host bytes per target byte |
| m_sections_up->AddSection(section_sp); |
| unified_section_list.AddSection(section_sp); |
| } |
| } |
| |
| bool ObjectFileWasm::SetLoadAddress(Target &target, lldb::addr_t load_address, |
| bool value_is_offset) { |
| /// In WebAssembly, linear memory is disjointed from code space. The VM can |
| /// load multiple instances of a module, which logically share the same code. |
| /// We represent a wasm32 code address with 64-bits, like: |
| /// 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. |
| /// In other words, we assume that each WebAssembly module is loaded by the |
| /// engine at a 64-bit address that starts at the boundary of 4GB pages, like |
| /// 0x0000000400000000 for module_id == 4. |
| /// These 64-bit addresses will be used to request code ranges for a specific |
| /// module from the WebAssembly engine. |
| |
| assert(m_memory_addr == LLDB_INVALID_ADDRESS || |
| m_memory_addr == load_address); |
| |
| ModuleSP module_sp = GetModule(); |
| if (!module_sp) |
| return false; |
| |
| DecodeSections(); |
| |
| size_t num_loaded_sections = 0; |
| SectionList *section_list = GetSectionList(); |
| if (!section_list) |
| return false; |
| |
| 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)); |
| if (target.SetSectionLoadAddress( |
| section_sp, load_address | section_sp->GetFileOffset())) { |
| ++num_loaded_sections; |
| } |
| } |
| |
| return num_loaded_sections > 0; |
| } |
| |
| DataExtractor ObjectFileWasm::ReadImageData(offset_t offset, uint32_t size) { |
| DataExtractor data; |
| if (m_file) { |
| if (offset < GetByteSize()) { |
| size = std::min(static_cast<uint64_t>(size), GetByteSize() - offset); |
| auto buffer_sp = MapFileData(m_file, size, offset); |
| return DataExtractor(buffer_sp, GetByteOrder(), GetAddressByteSize()); |
| } |
| } else { |
| ProcessSP process_sp(m_process_wp.lock()); |
| if (process_sp) { |
| auto data_up = std::make_unique<DataBufferHeap>(size, 0); |
| Status readmem_error; |
| size_t bytes_read = process_sp->ReadMemory( |
| offset, data_up->GetBytes(), data_up->GetByteSize(), readmem_error); |
| if (bytes_read > 0) { |
| DataBufferSP buffer_sp(data_up.release()); |
| data.SetData(buffer_sp, 0, buffer_sp->GetByteSize()); |
| } |
| } |
| } |
| |
| data.SetByteOrder(GetByteOrder()); |
| return data; |
| } |
| |
| llvm::Optional<FileSpec> ObjectFileWasm::GetExternalDebugInfoFileSpec() { |
| static ConstString g_sect_name_external_debug_info("external_debug_info"); |
| |
| for (const section_info §_info : m_sect_infos) { |
| if (g_sect_name_external_debug_info == sect_info.name) { |
| const uint32_t kBufferSize = 1024; |
| DataExtractor section_header_data = |
| ReadImageData(sect_info.offset, kBufferSize); |
| llvm::DataExtractor data = section_header_data.GetAsLLVM(); |
| llvm::DataExtractor::Cursor c(0); |
| llvm::Optional<ConstString> symbols_url = GetWasmString(data, c); |
| if (symbols_url) |
| return FileSpec(symbols_url->GetStringRef()); |
| } |
| } |
| return llvm::None; |
| } |
| |
| void ObjectFileWasm::Dump(Stream *s) { |
| ModuleSP module_sp(GetModule()); |
| if (!module_sp) |
| return; |
| |
| std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); |
| |
| llvm::raw_ostream &ostream = s->AsRawOstream(); |
| ostream << static_cast<void *>(this) << ": "; |
| s->Indent(); |
| ostream << "ObjectFileWasm, file = '"; |
| m_file.Dump(ostream); |
| ostream << "', arch = "; |
| ostream << GetArchitecture().GetArchitectureName() << "\n"; |
| |
| SectionList *sections = GetSectionList(); |
| if (sections) { |
| sections->Dump(s->AsRawOstream(), s->GetIndentLevel(), nullptr, true, |
| UINT32_MAX); |
| } |
| ostream << "\n"; |
| DumpSectionHeaders(ostream); |
| ostream << "\n"; |
| } |
| |
| void ObjectFileWasm::DumpSectionHeader(llvm::raw_ostream &ostream, |
| const section_info_t &sh) { |
| ostream << llvm::left_justify(sh.name.GetStringRef(), 16) << " " |
| << llvm::format_hex(sh.offset, 10) << " " |
| << llvm::format_hex(sh.size, 10) << " " << llvm::format_hex(sh.id, 6) |
| << "\n"; |
| } |
| |
| void ObjectFileWasm::DumpSectionHeaders(llvm::raw_ostream &ostream) { |
| ostream << "Section Headers\n"; |
| ostream << "IDX name addr size id\n"; |
| ostream << "==== ---------------- ---------- ---------- ------\n"; |
| |
| uint32_t idx = 0; |
| for (auto pos = m_sect_infos.begin(); pos != m_sect_infos.end(); |
| ++pos, ++idx) { |
| ostream << "[" << llvm::format_decimal(idx, 2) << "] "; |
| ObjectFileWasm::DumpSectionHeader(ostream, *pos); |
| } |
| } |