| //===-- 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/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "llvm/ADT/ArrayRef.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include "llvm/ADT/StringSwitch.h" | |
| #include "llvm/BinaryFormat/Magic.h" | |
| #include "llvm/BinaryFormat/Wasm.h" | |
| #include "llvm/Support/CheckedArithmetic.h" | |
| #include "llvm/Support/Endian.h" | |
| #include "llvm/Support/Format.h" | |
| #include <cstring> | |
| #include <optional> | |
| 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); | |
| /// File address the synthetic global section is based at. Code and linear | |
| /// memory are both addressed from zero, so the globals need a range of their | |
| /// own to keep a global index from naming a code or data address as well. | |
| static constexpr lldb::addr_t kWasmGlobalFileAddress = | |
| uint64_t(WasmAddressType::Global) << kWasmAddressTypeShift; | |
| /// Helper to read a 32-bit ULEB using LLDB's DataExtractor. | |
| static inline llvm::Expected<uint32_t> GetULEB32(DataExtractor &data, | |
| lldb::offset_t &offset) { | |
| const uint64_t value = data.GetULEB128(&offset); | |
| if (value > std::numeric_limits<uint32_t>::max()) | |
| return llvm::createStringError("ULEB exceeds 32 bits"); | |
| return value; | |
| } | |
| /// Helper to read a 32-bit ULEB using LLVM's DataExtractor. | |
| static inline llvm::Expected<uint32_t> | |
| GetULEB32(llvm::DataExtractor &data, llvm::DataExtractor::Cursor &c) { | |
| const uint64_t value = data.getULEB128(c); | |
| if (!c) | |
| return c.takeError(); | |
| if (value > std::numeric_limits<uint32_t>::max()) | |
| return llvm::createStringError("ULEB exceeds 32 bits"); | |
| return value; | |
| } | |
| /// Helper to read a Wasm string, whcih is encoded as a vector of UTF-8 codes. | |
| static inline llvm::Expected<std::string> | |
| GetWasmString(llvm::DataExtractor &data, llvm::DataExtractor::Cursor &c) { | |
| llvm::Expected<uint32_t> len = GetULEB32(data, c); | |
| if (!len) | |
| return len.takeError(); | |
| llvm::SmallVector<uint8_t, 32> str_storage; | |
| data.getU8(c, str_storage, *len); | |
| if (!c) | |
| return c.takeError(); | |
| return std::string(toStringRef(llvm::ArrayRef(str_storage))); | |
| } | |
| /// An "init expr" refers to a constant expression used to determine the initial | |
| /// value of certain elements within a module during instantiation. These | |
| /// expressions are restricted to operations that can be evaluated at module | |
| /// instantiation time. Currently we only support simple constant opcodes. | |
| static lldb::offset_t GetWasmOffsetFromInitExpr(DataExtractor &data, | |
| lldb::offset_t &offset) { | |
| lldb::offset_t init_expr_offset = LLDB_INVALID_OFFSET; | |
| uint8_t opcode = data.GetU8(&offset); | |
| switch (opcode) { | |
| case llvm::wasm::WASM_OPCODE_I32_CONST: | |
| case llvm::wasm::WASM_OPCODE_I64_CONST: | |
| init_expr_offset = data.GetSLEB128(&offset); | |
| break; | |
| case llvm::wasm::WASM_OPCODE_GLOBAL_GET: | |
| init_expr_offset = data.GetULEB128(&offset); | |
| break; | |
| case llvm::wasm::WASM_OPCODE_F32_CONST: | |
| case llvm::wasm::WASM_OPCODE_F64_CONST: | |
| // Not a meaningful offset. | |
| data.GetFloat(&offset); | |
| break; | |
| case llvm::wasm::WASM_OPCODE_REF_NULL: | |
| // Not a meaningful offset. | |
| data.GetULEB128(&offset); | |
| break; | |
| } | |
| // Make sure the opcodes we read aren't part of an extended init expr. | |
| opcode = data.GetU8(&offset); | |
| if (opcode == llvm::wasm::WASM_OPCODE_END) | |
| return init_expr_offset; | |
| // Extended init expressions are not supported, but we still have to parse | |
| // them to skip over them and read the next segment. A truncated expression | |
| // never reaches the end opcode, so the scan is bounded by the data. | |
| while (opcode != llvm::wasm::WASM_OPCODE_END && data.ValidOffset(offset)) | |
| opcode = data.GetU8(&offset); | |
| return LLDB_INVALID_OFFSET; | |
| } | |
| /// Checks whether the data buffer starts with a valid Wasm module header. | |
| static bool ValidateModuleHeader(llvm::ArrayRef<uint8_t> data) { | |
| if (data.size() < kWasmHeaderSize) | |
| return false; | |
| if (llvm::identify_magic(toStringRef(data)) != llvm::file_magic::wasm_object) | |
| return false; | |
| const uint8_t *Ptr = data.data() + sizeof(llvm::wasm::WasmMagic); | |
| uint32_t version = llvm::support::endian::read32le(Ptr); | |
| return version == llvm::wasm::WasmVersion; | |
| } | |
| 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, | |
| DataExtractorSP extractor_sp, | |
| offset_t data_offset, | |
| const FileSpec *file, | |
| offset_t file_offset, | |
| offset_t length) { | |
| Log *log = GetLog(LLDBLog::Object); | |
| if (!extractor_sp || !extractor_sp->HasData()) { | |
| DataBufferSP 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; | |
| } | |
| extractor_sp = std::make_shared<DataExtractor>(data_sp); | |
| data_offset = 0; | |
| } | |
| assert(extractor_sp); | |
| if (!ValidateModuleHeader(extractor_sp->GetData())) { | |
| 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 (extractor_sp->GetByteSize() < length) { | |
| DataBufferSP 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; | |
| } | |
| extractor_sp = std::make_shared<DataExtractor>(data_sp); | |
| data_offset = 0; | |
| } | |
| std::unique_ptr<ObjectFileWasm> objfile_up(new ObjectFileWasm( | |
| module_sp, extractor_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, | |
| WritableDataBufferSP data_sp, | |
| const ProcessSP &process_sp, | |
| addr_t header_addr) { | |
| if (!ValidateModuleHeader(data_sp->GetData())) | |
| 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 > std::numeric_limits<uint32_t>::max()) | |
| 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::Expected<std::string> sect_name = GetWasmString(data, c); | |
| if (!sect_name) { | |
| LLDB_LOG_ERROR(GetLog(LLDBLog::Object), sect_name.takeError(), | |
| "failed to parse section name: {0}"); | |
| 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, std::move(*sect_name)}); | |
| *offset_ptr += (c.tell() + section_length); | |
| } else if (section_id <= llvm::wasm::WASM_SEC_LAST_KNOWN) { | |
| m_sect_infos.push_back(section_info{*offset_ptr + c.tell(), | |
| static_cast<uint32_t>(payload_len), | |
| section_id, | |
| {}}); | |
| *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; | |
| } | |
| ModuleSpecList | |
| ObjectFileWasm::GetModuleSpecifications(const FileSpec &file, | |
| DataExtractorSP &extractor_sp, | |
| offset_t file_offset, offset_t length) { | |
| if (!ValidateModuleHeader(extractor_sp->GetData())) | |
| return {}; | |
| ModuleSpecList specs; | |
| specs.Append(ModuleSpec(file, ArchSpec("wasm32"))); | |
| return specs; | |
| } | |
| ObjectFileWasm::ObjectFileWasm(const ModuleSP &module_sp, | |
| DataExtractorSP extractor_sp, | |
| offset_t data_offset, const FileSpec *file, | |
| offset_t offset, offset_t length) | |
| : ObjectFile(module_sp, file, offset, length, extractor_sp, data_offset), | |
| m_arch("wasm32") { | |
| m_data_nsp->SetAddressByteSize(4); | |
| } | |
| ObjectFileWasm::ObjectFileWasm(const lldb::ModuleSP &module_sp, | |
| lldb::WritableDataBufferSP header_data_sp, | |
| const lldb::ProcessSP &process_sp, | |
| lldb::addr_t header_addr) | |
| : ObjectFile(module_sp, process_sp, header_addr, | |
| std::make_shared<DataExtractor>(header_data_sp)), | |
| m_arch("wasm32") {} | |
| bool ObjectFileWasm::ParseHeader() { | |
| // We already parsed the header during initialization. | |
| return true; | |
| } | |
| struct WasmFunction { | |
| /// Offset from the section to the start of the function. This points past the | |
| /// function size, which some other tools consider part of the function. | |
| lldb::offset_t section_offset = LLDB_INVALID_OFFSET; | |
| /// Function size, which includes the function header, but not the size ULEB | |
| /// that proceeds it. | |
| uint32_t size = 0; | |
| /// Offset from section_offset to the first instruction in the function, past | |
| /// the local variable declarations. | |
| uint32_t code_offset = 0; | |
| }; | |
| /// The number of imports of each kind. Imports occupy the low indices of the | |
| /// index space of their kind. | |
| struct WasmImports { | |
| uint32_t functions = 0; | |
| uint32_t globals = 0; | |
| }; | |
| static llvm::Expected<WasmImports> ParseImports(DataExtractor &import_data) { | |
| llvm::DataExtractor data = import_data.GetAsLLVM(); | |
| llvm::DataExtractor::Cursor c(0); | |
| llvm::Expected<uint32_t> count = GetULEB32(data, c); | |
| if (!count) | |
| return count.takeError(); | |
| WasmImports imports; | |
| for (uint32_t i = 0; c && i < *count; ++i) { | |
| // We don't need module and field names, so we can just get them as raw | |
| // strings and discard. | |
| llvm::Expected<std::string> module_name = GetWasmString(data, c); | |
| if (!module_name) | |
| return llvm::joinErrors( | |
| llvm::createStringError("failed to parse module name"), | |
| module_name.takeError()); | |
| llvm::Expected<std::string> field_name = GetWasmString(data, c); | |
| if (!field_name) | |
| return llvm::joinErrors( | |
| llvm::createStringError("failed to parse field name"), | |
| field_name.takeError()); | |
| // The descriptor differs per kind, so each has to be parsed to find where | |
| // the next import starts. | |
| const uint8_t kind = data.getU8(c); | |
| switch (kind) { | |
| case llvm::wasm::WASM_EXTERNAL_FUNCTION: | |
| imports.functions++; | |
| data.getULEB128(c); // type index | |
| break; | |
| case llvm::wasm::WASM_EXTERNAL_GLOBAL: | |
| imports.globals++; | |
| data.getU8(c); // value type | |
| data.getU8(c); // mutability | |
| break; | |
| case llvm::wasm::WASM_EXTERNAL_TAG: | |
| data.getU8(c); // attribute | |
| data.getULEB128(c); // type index | |
| break; | |
| case llvm::wasm::WASM_EXTERNAL_TABLE: | |
| data.getU8(c); // element type | |
| [[fallthrough]]; | |
| case llvm::wasm::WASM_EXTERNAL_MEMORY: { | |
| // Tables and memories are both described by limits. | |
| const uint8_t flags = data.getU8(c); | |
| data.getULEB128(c); // minimum | |
| if (flags & llvm::wasm::WASM_LIMITS_FLAG_HAS_MAX) | |
| data.getULEB128(c); | |
| break; | |
| } | |
| default: | |
| // The cursor's error has to be consumed before it goes out of scope. | |
| return llvm::joinErrors( | |
| c.takeError(), | |
| llvm::createStringError("unknown import kind %u", kind)); | |
| } | |
| } | |
| if (!c) | |
| return c.takeError(); | |
| return imports; | |
| } | |
| /// Get the offset in the function to the first instruction. | |
| static llvm::Expected<uint32_t> GetFunctionCodeOffset(DataExtractor &data, | |
| lldb::offset_t offset) { | |
| // Wasm function bodies start with: | |
| // [local_count: ULEB128] | |
| // [local_decl: {count: ULEB128, type: byte}] × local_count | |
| // [instructions...] | |
| const lldb::offset_t locals_start = offset; | |
| const uint32_t local_count = data.GetULEB128(&offset); | |
| for (uint32_t i = 0; i < local_count; ++i) { | |
| data.GetULEB128(&offset); // count | |
| data.GetU8(&offset); // valtype | |
| } | |
| return offset - locals_start; | |
| } | |
| static llvm::Expected<std::vector<WasmFunction>> | |
| ParseFunctions(DataExtractor &data) { | |
| lldb::offset_t offset = 0; | |
| llvm::Expected<uint32_t> function_count = GetULEB32(data, offset); | |
| if (!function_count) | |
| return function_count.takeError(); | |
| std::vector<WasmFunction> functions; | |
| functions.reserve(*function_count); | |
| for (uint32_t i = 0; i < *function_count; ++i) { | |
| // llvm-objdump considers the ULEB with the function size to be part of the | |
| // function. We can't do that here because that would not match the DWARF, | |
| // which considers the function to start with the local variable | |
| // declarations (the header). | |
| llvm::Expected<uint32_t> function_size = GetULEB32(data, offset); | |
| if (!function_size) | |
| return function_size.takeError(); | |
| // Functions start with with a number of local variable declarations. | |
| // They're part of the function but they're not instructions. | |
| llvm::Expected<uint32_t> code_offset = GetFunctionCodeOffset(data, offset); | |
| if (!code_offset) | |
| return code_offset.takeError(); | |
| functions.push_back({offset, *function_size, *code_offset}); | |
| std::optional<lldb::offset_t> next_offset = | |
| llvm::checkedAddUnsigned<lldb::offset_t>(offset, *function_size); | |
| if (!next_offset) | |
| return llvm::createStringError("function offset overflows 64 bits"); | |
| offset = *next_offset; | |
| } | |
| return functions; | |
| } | |
| struct WasmSegment { | |
| enum SegmentType { | |
| Active, | |
| Passive, | |
| }; | |
| std::string name; | |
| SegmentType type = Passive; | |
| lldb::offset_t section_offset = LLDB_INVALID_OFFSET; | |
| uint32_t size = 0; | |
| uint32_t memory_index = 0; | |
| lldb::offset_t init_expr_offset = 0; | |
| lldb::offset_t GetFileOffset() const { return section_offset & 0xffffffff; } | |
| }; | |
| static llvm::Expected<std::vector<WasmSegment>> ParseData(DataExtractor &data) { | |
| lldb::offset_t offset = 0; | |
| llvm::Expected<uint32_t> segment_count = GetULEB32(data, offset); | |
| if (!segment_count) | |
| return segment_count.takeError(); | |
| std::vector<WasmSegment> segments; | |
| segments.reserve(*segment_count); | |
| for (uint32_t i = 0; i < *segment_count; ++i) { | |
| llvm::Expected<uint32_t> flags = GetULEB32(data, offset); | |
| if (!flags) | |
| return flags.takeError(); | |
| WasmSegment segment; | |
| // Data segments have a mode that identifies them as either passive or | |
| // active. An active data segment copies its contents into a memory during | |
| // instantiation, as specified by a memory index and a constant expression | |
| // defining an offset into that memory. | |
| segment.type = (*flags & llvm::wasm::WASM_DATA_SEGMENT_IS_PASSIVE) | |
| ? WasmSegment::Passive | |
| : WasmSegment::Active; | |
| if (*flags & llvm::wasm::WASM_DATA_SEGMENT_HAS_MEMINDEX) { | |
| assert(segment.type == WasmSegment::Active); | |
| llvm::Expected<uint32_t> memidx = GetULEB32(data, offset); | |
| if (!memidx) | |
| return memidx.takeError(); | |
| segment.memory_index = *memidx; | |
| } | |
| if (segment.type == WasmSegment::Active) | |
| segment.init_expr_offset = GetWasmOffsetFromInitExpr(data, offset); | |
| llvm::Expected<uint32_t> segment_size = GetULEB32(data, offset); | |
| if (!segment_size) | |
| return segment_size.takeError(); | |
| segment.section_offset = offset; | |
| segment.size = *segment_size; | |
| segments.push_back(segment); | |
| std::optional<lldb::offset_t> next_offset = | |
| llvm::checkedAddUnsigned<lldb::offset_t>(offset, *segment_size); | |
| if (!next_offset) | |
| return llvm::createStringError("segment offset overflows 64 bits"); | |
| offset = *next_offset; | |
| } | |
| return segments; | |
| } | |
| /// Parse the minimum size in bytes of the module's first linear memory. This is | |
| /// the memory guaranteed to exist at instantiation, and so the upper bound of | |
| /// the static data region. | |
| static llvm::Expected<uint64_t> ParseMemoryMinSize(DataExtractor &data) { | |
| lldb::offset_t offset = 0; | |
| llvm::Expected<uint32_t> memory_count = GetULEB32(data, offset); | |
| if (!memory_count) | |
| return memory_count.takeError(); | |
| if (*memory_count == 0) | |
| return llvm::createStringError("module declares no linear memory"); | |
| // The limits of a memory are a flags byte followed by the minimum, and | |
| // optionally the maximum, page count. | |
| data.GetU8(&offset); | |
| llvm::Expected<uint32_t> min_pages = GetULEB32(data, offset); | |
| if (!min_pages) | |
| return min_pages.takeError(); | |
| return static_cast<uint64_t>(*min_pages) * llvm::wasm::WasmDefaultPageSize; | |
| } | |
| /// Size in bytes of a WebAssembly value type, or nothing for the types whose | |
| /// values cannot be read. | |
| static std::optional<uint32_t> GetWasmValueTypeSize(uint8_t type) { | |
| switch (type) { | |
| case llvm::wasm::WASM_TYPE_I32: | |
| case llvm::wasm::WASM_TYPE_F32: | |
| return 4; | |
| case llvm::wasm::WASM_TYPE_I64: | |
| case llvm::wasm::WASM_TYPE_F64: | |
| return 8; | |
| default: | |
| return std::nullopt; | |
| } | |
| } | |
| /// Parse the init expr that gives a global its initial value. The result is the | |
| /// bit pattern of the value, so an operand that has to be evaluated against | |
| /// module state yields nothing. | |
| static std::optional<uint64_t> ParseGlobalInitValue(DataExtractor &data, | |
| lldb::offset_t &offset) { | |
| std::optional<uint64_t> value; | |
| switch (data.GetU8(&offset)) { | |
| case llvm::wasm::WASM_OPCODE_I32_CONST: | |
| value = static_cast<uint32_t>(data.GetSLEB128(&offset)); | |
| break; | |
| case llvm::wasm::WASM_OPCODE_I64_CONST: | |
| value = static_cast<uint64_t>(data.GetSLEB128(&offset)); | |
| break; | |
| case llvm::wasm::WASM_OPCODE_F32_CONST: | |
| value = data.GetU32(&offset); | |
| break; | |
| case llvm::wasm::WASM_OPCODE_F64_CONST: | |
| value = data.GetU64(&offset); | |
| break; | |
| case llvm::wasm::WASM_OPCODE_GLOBAL_GET: | |
| case llvm::wasm::WASM_OPCODE_REF_NULL: | |
| // The operand still has to be consumed to find the end of the expression. | |
| data.GetULEB128(&offset); | |
| break; | |
| } | |
| // An expression this parser does not understand can only be skipped to its | |
| // end opcode. If that end never comes the parse is out of step, and there is | |
| // no value to report. | |
| uint8_t opcode = data.GetU8(&offset); | |
| while (opcode != llvm::wasm::WASM_OPCODE_END && data.ValidOffset(offset)) | |
| opcode = data.GetU8(&offset); | |
| if (opcode != llvm::wasm::WASM_OPCODE_END) | |
| return std::nullopt; | |
| return value; | |
| } | |
| /// Parse the module's own globals, which start at the number of imported ones. | |
| static llvm::Expected<std::vector<WasmGlobal>> | |
| ParseGlobals(DataExtractor &data) { | |
| lldb::offset_t offset = 0; | |
| llvm::Expected<uint32_t> count = GetULEB32(data, offset); | |
| if (!count) | |
| return count.takeError(); | |
| // The count comes from the file, so it is not a size to allocate up front. | |
| std::vector<WasmGlobal> globals; | |
| for (uint32_t i = 0; i < *count; ++i) { | |
| if (!data.ValidOffset(offset)) | |
| return llvm::createStringError( | |
| "global section holds %zu of its %u globals", globals.size(), *count); | |
| WasmGlobal global; | |
| global.size = GetWasmValueTypeSize(data.GetU8(&offset)); | |
| data.GetU8(&offset); // mutability | |
| global.init_expr_value = ParseGlobalInitValue(data, offset); | |
| globals.push_back(global); | |
| } | |
| return globals; | |
| } | |
| static llvm::Expected<std::vector<Symbol>> | |
| ParseNames(SectionSP code_section_sp, SectionSP global_section_sp, | |
| DataExtractor &name_data, const std::vector<WasmFunction> &functions, | |
| std::vector<WasmSegment> &segments, | |
| const std::vector<WasmGlobal> &globals, | |
| uint32_t num_imported_functions, uint32_t num_imported_globals) { | |
| llvm::DataExtractor data = name_data.GetAsLLVM(); | |
| llvm::DataExtractor::Cursor c(0); | |
| std::vector<Symbol> symbols; | |
| while (c && c.tell() < data.size()) { | |
| const uint8_t type = data.getU8(c); | |
| llvm::Expected<uint32_t> size = GetULEB32(data, c); | |
| if (!size) | |
| return size.takeError(); | |
| switch (type) { | |
| case llvm::wasm::WASM_NAMES_FUNCTION: { | |
| const uint64_t count = data.getULEB128(c); | |
| if (count > std::numeric_limits<uint32_t>::max()) | |
| return llvm::joinErrors( | |
| c.takeError(), | |
| llvm::createStringError("function count overflows uint32_t")); | |
| for (uint64_t i = 0; c && i < count; ++i) { | |
| llvm::Expected<uint32_t> idx = GetULEB32(data, c); | |
| if (!idx) | |
| return idx.takeError(); | |
| llvm::Expected<std::string> name = GetWasmString(data, c); | |
| if (!name) | |
| return name.takeError(); | |
| if (*idx >= num_imported_functions + functions.size()) | |
| continue; | |
| if (*idx < num_imported_functions) { | |
| symbols.emplace_back(symbols.size(), *name, lldb::eSymbolTypeCode, | |
| /*external=*/true, /*is_debug=*/false, | |
| /*is_trampoline=*/false, | |
| /*is_artificial=*/false, | |
| /*section_sp=*/lldb::SectionSP(), | |
| /*value=*/0, /*size=*/0, | |
| /*size_is_valid=*/false, | |
| /*contains_linker_annotations=*/false, | |
| /*flags=*/0); | |
| } else { | |
| const WasmFunction &func = functions[*idx - num_imported_functions]; | |
| symbols.emplace_back(symbols.size(), *name, lldb::eSymbolTypeCode, | |
| /*external=*/false, /*is_debug=*/false, | |
| /*is_trampoline=*/false, /*is_artificial=*/false, | |
| code_section_sp, func.section_offset, func.size, | |
| /*size_is_valid=*/true, | |
| /*contains_linker_annotations=*/false, | |
| /*flags=*/0); | |
| if (func.code_offset) | |
| symbols.back().SetPrologueByteSize(func.code_offset); | |
| } | |
| } | |
| } break; | |
| case llvm::wasm::WASM_NAMES_DATA_SEGMENT: { | |
| llvm::Expected<uint32_t> count = GetULEB32(data, c); | |
| if (!count) | |
| return count.takeError(); | |
| for (uint32_t i = 0; c && i < *count; ++i) { | |
| llvm::Expected<uint32_t> idx = GetULEB32(data, c); | |
| if (!idx) | |
| return idx.takeError(); | |
| llvm::Expected<std::string> name = GetWasmString(data, c); | |
| if (!name) | |
| return name.takeError(); | |
| if (*idx >= segments.size()) | |
| continue; | |
| // Update the segment name. | |
| segments[*idx].name = *name; | |
| } | |
| } break; | |
| case llvm::wasm::WASM_NAMES_GLOBAL: { | |
| llvm::Expected<uint32_t> count = GetULEB32(data, c); | |
| if (!count) | |
| return count.takeError(); | |
| for (uint32_t i = 0; c && i < *count; ++i) { | |
| llvm::Expected<uint32_t> idx = GetULEB32(data, c); | |
| if (!idx) | |
| return idx.takeError(); | |
| llvm::Expected<std::string> name = GetWasmString(data, c); | |
| if (!name) | |
| return name.takeError(); | |
| // An imported global has no entry in the global section, and so no | |
| // value to bound a read with. | |
| if (*idx < num_imported_globals) | |
| continue; | |
| const uint32_t global_idx = *idx - num_imported_globals; | |
| if (global_idx >= globals.size()) | |
| continue; | |
| // The section is indexed rather than byte addressed, so a global spans | |
| // the one index it occupies. Bounding a read by the size of the value | |
| // is the section's business. | |
| symbols.emplace_back(symbols.size(), *name, lldb::eSymbolTypeData, | |
| /*external=*/true, /*is_debug=*/false, | |
| /*is_trampoline=*/false, /*is_artificial=*/false, | |
| global_section_sp, /*offset=*/*idx, | |
| /*size=*/1, | |
| /*size_is_valid=*/true, | |
| /*contains_linker_annotations=*/false, | |
| /*flags=*/0); | |
| } | |
| } break; | |
| case llvm::wasm::WASM_NAMES_LOCAL: | |
| default: | |
| std::optional<lldb::offset_t> offset = | |
| llvm::checkedAddUnsigned<lldb::offset_t>(c.tell(), *size); | |
| if (!offset) | |
| return llvm::joinErrors( | |
| c.takeError(), llvm::createStringError("offset overflows 64 bits")); | |
| c.seek(*offset); | |
| } | |
| } | |
| if (!c) | |
| return c.takeError(); | |
| return symbols; | |
| } | |
| void ObjectFileWasm::ParseSymtab(Symtab &symtab) { | |
| for (const Symbol &symbol : m_symbols) | |
| symtab.AddSymbol(symbol); | |
| symtab.Finalize(); | |
| m_symbols.clear(); | |
| } | |
| static SectionType GetSectionTypeFromName(llvm::StringRef Name) { | |
| if (Name == "name") | |
| return lldb::eSectionTypeWasmName; | |
| if (Name.consume_front(".debug_") || Name.consume_front(".zdebug_")) | |
| return ObjectFile::GetDWARFSectionTypeFromName(Name); | |
| return eSectionTypeOther; | |
| } | |
| /// A `section` attribute on a data variable lands in a named data segment on | |
| /// wasm, not a top-level custom section, so formatter sections appear as | |
| /// segment names rather than section names. | |
| static SectionType GetSegmentTypeFromName(llvm::StringRef Name) { | |
| return llvm::StringSwitch<SectionType>(Name) | |
| .Case(".lldbsummaries", eSectionTypeLLDBTypeSummaries) | |
| .Case(".lldbformatters", eSectionTypeLLDBFormatters) | |
| .Default(eSectionTypeData); | |
| } | |
| std::optional<ObjectFileWasm::section_info> | |
| ObjectFileWasm::GetSectionInfo(uint32_t section_id) { | |
| for (const section_info §_info : m_sect_infos) { | |
| if (sect_info.id == section_id) | |
| return sect_info; | |
| } | |
| return std::nullopt; | |
| } | |
| std::optional<ObjectFileWasm::section_info> | |
| ObjectFileWasm::GetSectionInfo(llvm::StringRef section_name) { | |
| for (const section_info §_info : m_sect_infos) { | |
| if (sect_info.name == section_name) | |
| return sect_info; | |
| } | |
| return std::nullopt; | |
| } | |
| void ObjectFileWasm::CreateSections(SectionList &unified_section_list) { | |
| Log *log = GetLog(LLDBLog::Object); | |
| 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; | |
| std::string section_name; | |
| offset_t file_offset = sect_info.offset & 0xffffffff; | |
| addr_t vm_addr = sect_info.offset; | |
| size_t vm_size = sect_info.size; | |
| if (llvm::wasm::WASM_SEC_CODE == sect_info.id) { | |
| section_type = eSectionTypeCode; | |
| section_name = "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); | |
| if (section_type == eSectionTypeOther) | |
| continue; | |
| section_name = sect_info.name; | |
| if (!IsInMemory()) { | |
| vm_size = 0; | |
| vm_addr = 0; | |
| } | |
| } | |
| SectionSP section_sp = std::make_shared<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. | |
| m_sections_up->AddSection(section_sp); | |
| unified_section_list.AddSection(section_sp); | |
| } | |
| // The name section contains names and indexes. First parse the data from the | |
| // relevant sections so we can access it by its index. | |
| std::vector<WasmFunction> functions; | |
| std::vector<WasmSegment> segments; | |
| // Parse the code section. | |
| if (std::optional<section_info> info = | |
| GetSectionInfo(llvm::wasm::WASM_SEC_CODE)) { | |
| DataExtractor code_data = ReadImageData(info->offset, info->size); | |
| llvm::Expected<std::vector<WasmFunction>> maybe_functions = | |
| ParseFunctions(code_data); | |
| if (!maybe_functions) { | |
| LLDB_LOG_ERROR(log, maybe_functions.takeError(), | |
| "Failed to parse Wasm code section: {0}"); | |
| } else { | |
| functions = *maybe_functions; | |
| } | |
| } | |
| // Parse the import section. The counts are needed because the function and | |
| // global index spaces used in the name section include imports. | |
| if (std::optional<section_info> info = | |
| GetSectionInfo(llvm::wasm::WASM_SEC_IMPORT)) { | |
| DataExtractor import_data = ReadImageData(info->offset, info->size); | |
| llvm::Expected<WasmImports> imports = ParseImports(import_data); | |
| if (!imports) { | |
| LLDB_LOG_ERROR(log, imports.takeError(), | |
| "Failed to parse Wasm import section: {0}"); | |
| } else { | |
| m_num_imported_functions = imports->functions; | |
| m_num_imported_globals = imports->globals; | |
| } | |
| } | |
| // Parse the global section. | |
| if (std::optional<section_info> info = | |
| GetSectionInfo(llvm::wasm::WASM_SEC_GLOBAL)) { | |
| DataExtractor global_data = ReadImageData(info->offset, info->size); | |
| llvm::Expected<std::vector<WasmGlobal>> globals = ParseGlobals(global_data); | |
| if (!globals) { | |
| LLDB_LOG_ERROR(log, globals.takeError(), | |
| "Failed to parse Wasm global section: {0}"); | |
| } else { | |
| m_globals = *globals; | |
| } | |
| } | |
| // Parse the data section. | |
| std::optional<section_info> data_info = | |
| GetSectionInfo(llvm::wasm::WASM_SEC_DATA); | |
| if (data_info) { | |
| DataExtractor data_data = ReadImageData(data_info->offset, data_info->size); | |
| llvm::Expected<std::vector<WasmSegment>> maybe_segments = | |
| ParseData(data_data); | |
| if (!maybe_segments) { | |
| LLDB_LOG_ERROR(log, maybe_segments.takeError(), | |
| "Failed to parse Wasm data section: {0}"); | |
| } else { | |
| segments = *maybe_segments; | |
| } | |
| } | |
| // The section maps nothing: it exists to give globals an address, which is | |
| // what lets one be named and read. Its size counts globals rather than bytes, | |
| // imported ones included, because they share the index space. | |
| SectionSP global_section_sp; | |
| if (!m_globals.empty()) { | |
| global_section_sp = std::make_shared<Section>( | |
| GetModule(), | |
| /*obj_file=*/this, eSectionTypeWasmGlobal, "global", | |
| eSectionTypeWasmGlobal, | |
| /*file_vm_addr=*/kWasmGlobalFileAddress, | |
| /*vm_size=*/m_num_imported_globals + m_globals.size(), | |
| /*file_offset=*/0, /*file_size=*/0, | |
| /*log2align=*/0, /*flags=*/0); | |
| m_sections_up->AddSection(global_section_sp); | |
| unified_section_list.AddSection(global_section_sp); | |
| } | |
| if (std::optional<section_info> info = GetSectionInfo("name")) { | |
| DataExtractor names_data = ReadImageData(info->offset, info->size); | |
| llvm::Expected<std::vector<Symbol>> symbols = ParseNames( | |
| m_sections_up->FindSectionByType(lldb::eSectionTypeCode, false), | |
| global_section_sp, names_data, functions, segments, m_globals, | |
| m_num_imported_functions, m_num_imported_globals); | |
| if (!symbols) { | |
| LLDB_LOG_ERROR(log, symbols.takeError(), | |
| "Failed to parse Wasm names: {0}"); | |
| } else { | |
| m_symbols = *symbols; | |
| } | |
| } | |
| lldb::user_id_t segment_id = 0; | |
| lldb::addr_t static_data_end = 0; | |
| for (const WasmSegment &segment : segments) { | |
| if (segment.type == WasmSegment::Active) { | |
| // FIXME: Support segments with a memory index. | |
| if (segment.memory_index != 0) { | |
| LLDB_LOG(log, | |
| "Skipping segment {}: non-zero memory index is " | |
| "currently unsupported", | |
| segment.name); | |
| continue; | |
| } | |
| if (segment.init_expr_offset == LLDB_INVALID_OFFSET) { | |
| LLDB_LOG(log, "Skipping segment {}: unsupported init expression", | |
| segment.name); | |
| continue; | |
| } | |
| } | |
| const lldb::addr_t file_vm_addr = | |
| segment.type == WasmSegment::Active | |
| ? segment.init_expr_offset | |
| : data_info->offset + segment.section_offset; | |
| const lldb::offset_t file_offset = | |
| data_info->GetFileOffset() + segment.GetFileOffset(); | |
| SectionSP segment_sp = std::make_shared<Section>( | |
| GetModule(), | |
| /*obj_file=*/this, | |
| ++segment_id << 8, // 1-based segment index, shifted by 8 bits to avoid | |
| // collision with section IDs. | |
| segment.name, GetSegmentTypeFromName(segment.name), | |
| /*file_vm_addr=*/file_vm_addr, | |
| /*vm_size=*/segment.size, | |
| /*file_offset=*/file_offset, | |
| /*file_size=*/segment.size, | |
| /*log2align=*/0, /*flags=*/0); | |
| m_sections_up->AddSection(segment_sp); | |
| GetModule()->GetSectionList()->AddSection(segment_sp); | |
| if (segment.type == WasmSegment::Active) | |
| static_data_end = std::max(static_data_end, file_vm_addr + segment.size); | |
| } | |
| // Zero-initialized globals (BSS) have no data segment, so the loop above | |
| // leaves their linear-memory addresses uncovered by any section, and a static | |
| // read of one can't be resolved. Cover the rest of linear memory with a | |
| // zero-fill section. SetLoadAddress maps it like a data segment so live reads | |
| // still go through process memory. | |
| if (std::optional<section_info> mem_info = | |
| GetSectionInfo(llvm::wasm::WASM_SEC_MEMORY)) { | |
| DataExtractor mem_data = ReadImageData(mem_info->offset, mem_info->size); | |
| llvm::Expected<uint64_t> memory_size = ParseMemoryMinSize(mem_data); | |
| if (!memory_size) { | |
| LLDB_LOG_ERROR(log, memory_size.takeError(), | |
| "Failed to parse Wasm memory section: {0}"); | |
| } else if (*memory_size > static_data_end) { | |
| SectionSP bss_sp = std::make_shared<Section>( | |
| GetModule(), | |
| /*obj_file=*/this, ++segment_id << 8, ".bss", eSectionTypeZeroFill, | |
| /*file_vm_addr=*/static_data_end, | |
| /*vm_size=*/*memory_size - static_data_end, | |
| /*file_offset=*/0, | |
| /*file_size=*/0, | |
| /*log2align=*/0, /*flags=*/0); | |
| m_sections_up->AddSection(bss_sp); | |
| GetModule()->GetSectionList()->AddSection(bss_sp); | |
| } | |
| } | |
| } | |
| size_t ObjectFileWasm::ReadSectionData(Section *section, | |
| lldb::offset_t section_offset, void *dst, | |
| size_t dst_len) { | |
| if (!section || section->GetType() != eSectionTypeWasmGlobal) | |
| return ObjectFile::ReadSectionData(section, section_offset, dst, dst_len); | |
| // The low indices belong to imported globals, which the module does not | |
| // declare and so has no initializer for. | |
| if (section_offset < m_num_imported_globals) | |
| return 0; | |
| const lldb::offset_t index = section_offset - m_num_imported_globals; | |
| if (index >= m_globals.size()) | |
| return 0; | |
| const WasmGlobal &global = m_globals[index]; | |
| if (!global.init_expr_value || !global.size || dst_len > *global.size) | |
| return 0; | |
| // A global holds a value rather than bytes, and WebAssembly is little-endian. | |
| uint8_t bytes[sizeof(uint64_t)]; | |
| llvm::support::endian::write64le(bytes, *global.init_expr_value); | |
| std::memcpy(dst, bytes, dst_len); | |
| return dst_len; | |
| } | |
| 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)); | |
| lldb::addr_t section_load_addr; | |
| switch (section_sp->GetType()) { | |
| case eSectionTypeData: | |
| case eSectionTypeZeroFill: | |
| case eSectionTypeLLDBTypeSummaries: | |
| case eSectionTypeLLDBFormatters: | |
| case eSectionTypeWasmGlobal: | |
| // These live in linear memory, and the globals in an index space of their | |
| // own, both separate from code. A section's file address already carries | |
| // the space it belongs to, so only the module id comes from the load | |
| // address. | |
| section_load_addr = | |
| (load_address & ~kWasmAddressTypeMask) | section_sp->GetFileAddress(); | |
| break; | |
| default: | |
| // Code (and other) sections are addressed by their offset within the | |
| // module in the Object address space. | |
| section_load_addr = load_address | section_sp->GetFileOffset(); | |
| break; | |
| } | |
| if (target.SetSectionLoadAddress(section_sp, section_load_addr)) | |
| ++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); | |
| } | |
| } else if (offset < m_data_nsp->GetByteSize()) { | |
| size = std::min(static_cast<uint64_t>(size), | |
| m_data_nsp->GetByteSize() - offset); | |
| return DataExtractor(m_data_nsp->GetDataStart() + offset, size, | |
| GetByteOrder(), GetAddressByteSize()); | |
| } | |
| } | |
| data.SetByteOrder(GetByteOrder()); | |
| return data; | |
| } | |
| UUID ObjectFileWasm::GetUUID() { | |
| if (m_uuid) | |
| return m_uuid; | |
| // A Wasm module carries the identifier a linker gave it in a custom section, | |
| // as a vector of bytes. It is the only thing that tells one build of a module | |
| // from another, so a module linked without one cannot be identified at all. | |
| static constexpr llvm::StringLiteral g_sect_name_build_id("build_id"); | |
| for (const section_info §_info : m_sect_infos) { | |
| if (g_sect_name_build_id != sect_info.name) | |
| continue; | |
| DataExtractor section_data = | |
| ReadImageData(sect_info.offset, sect_info.size); | |
| llvm::DataExtractor data = section_data.GetAsLLVM(); | |
| llvm::DataExtractor::Cursor c(0); | |
| llvm::Expected<uint32_t> length = GetULEB32(data, c); | |
| if (!length) { | |
| LLDB_LOG_ERROR(GetLog(LLDBLog::Object), length.takeError(), | |
| "failed to parse the build id length: {0}"); | |
| return m_uuid; | |
| } | |
| llvm::SmallVector<uint8_t, 32> id(*length, 0); | |
| data.getU8(c, id.data(), id.size()); | |
| if (!c) { | |
| LLDB_LOG_ERROR(GetLog(LLDBLog::Object), c.takeError(), | |
| "failed to parse the build id: {0}"); | |
| return m_uuid; | |
| } | |
| m_uuid = UUID(id); | |
| break; | |
| } | |
| return m_uuid; | |
| } | |
| std::optional<FileSpec> ObjectFileWasm::GetExternalDebugInfoFileSpec() { | |
| static constexpr llvm::StringLiteral 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::Expected<std::string> symbols_url = GetWasmString(data, c); | |
| if (!symbols_url) { | |
| llvm::consumeError(symbols_url.takeError()); | |
| return std::nullopt; | |
| } | |
| return FileSpec(*symbols_url); | |
| } | |
| } | |
| return std::nullopt; | |
| } | |
| 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 &sh) { | |
| ostream << llvm::left_justify(sh.name, 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); | |
| } | |
| } |