[LLDB] Add DynamicLoaderWasmDYLD plugin for WebAssembly debugging

Add a dynamic loader plug-in class for WebAssembly modules.

Differential Revision: https://reviews.llvm.org/D72751
This commit is contained in:
Paolo Severini
2020-02-05 10:33:13 -08:00
committed by Derek Schuff
parent 89b7091c28
commit 3ec28da6d6
13 changed files with 381 additions and 39 deletions

View File

@@ -0,0 +1,273 @@
import lldb
import binascii
import struct
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
from gdbclientutils import *
LLDB_INVALID_ADDRESS = 0xffffffffffffffff
load_address = 0x400000000
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))
def uleb128_encode(val):
"""
encode a number to uleb128
"""
result = bytearray()
while True:
byte = val & 0x7f
val >>= 7
if val != 0:
byte |= 0x80 # mark this byte to show that more bytes will follow
result.append(byte)
if val == 0:
break
return result
def encode_wasm_string(s):
"""
Encode a string as an array of UTF-8 bytes preceded by its ULEB128 length.
"""
char_array = bytearray(x.encode("utf8") for x in s)
return uleb128_encode(len(char_array)) + char_array
def format_bytearray_as_hexstring(byte_array):
"""
Encode a n array of bytes as a string of hexadecimal digits.
"""
return ''.join(format(x, '02x') for x in byte_array)
class MyResponder(MockGDBServerResponder):
current_pc = load_address + 0x0a
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 int(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", load_address)
return xml, False
else:
return None, False
def readMemory(self, addr, length):
if addr < load_address:
return "E02"
result = ""
with open(self._obj_path, mode='rb') as file:
file_content = bytearray(file.read())
addr_from = addr - 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
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_load_module_with_embedded_symbols_from_remote(self):
"""Test connecting to a WebAssembly engine via GDB-remote and loading a Wasm module with embedded DWARF symbols"""
yaml_path = "test_wasm_embedded_debug_sections.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(load_address | code_section.GetFileOffset(), code_section.GetLoadAddress(target))
debug_info_section = module.GetSectionAtIndex(1)
self.assertEquals(".debug_info", debug_info_section.GetName())
self.assertEquals(load_address | debug_info_section.GetFileOffset(), debug_info_section.GetLoadAddress(target))
debug_abbrev_section = module.GetSectionAtIndex(2)
self.assertEquals(".debug_abbrev", debug_abbrev_section.GetName())
self.assertEquals(load_address | debug_abbrev_section.GetFileOffset(), debug_abbrev_section.GetLoadAddress(target))
debug_line_section = module.GetSectionAtIndex(3)
self.assertEquals(".debug_line", debug_line_section.GetName())
self.assertEquals(load_address | debug_line_section.GetFileOffset(), debug_line_section.GetLoadAddress(target))
debug_str_section = module.GetSectionAtIndex(4)
self.assertEquals(".debug_str", debug_str_section.GetName())
self.assertEquals(load_address | debug_line_section.GetFileOffset(), debug_line_section.GetLoadAddress(target))
def test_load_module_with_stripped_symbols_from_remote(self):
"""Test connecting to a WebAssembly engine via GDB-remote and loading a Wasm module with symbols stripped into a separate Wasm file"""
sym_yaml_path = "test_sym.yaml"
sym_yaml_base, ext = os.path.splitext(sym_yaml_path)
sym_obj_path = self.getBuildArtifact(sym_yaml_base) + ".wasm"
self.yaml2obj(sym_yaml_path, sym_obj_path)
yaml_template_path = "test_wasm_external_debug_sections.yaml"
yaml_base = "test_wasm_external_debug_sections_modified"
yaml_path = self.getBuildArtifact(yaml_base) + ".yaml"
obj_path = self.getBuildArtifact(yaml_base) + ".wasm"
with open(yaml_template_path, mode='r') as file:
yaml = file.read()
file.close()
yaml = yaml.replace("###_EXTERNAL_DEBUG_INFO_###", format_bytearray_as_hexstring(encode_wasm_string(sym_obj_path)))
with open(yaml_path, mode='w') as file:
file.write(yaml)
file.close()
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(load_address | code_section.GetFileOffset(), code_section.GetLoadAddress(target))
debug_info_section = module.GetSectionAtIndex(1)
self.assertEquals(".debug_info", debug_info_section.GetName())
self.assertEquals(LLDB_INVALID_ADDRESS, debug_info_section.GetLoadAddress(target))
debug_abbrev_section = module.GetSectionAtIndex(2)
self.assertEquals(".debug_abbrev", debug_abbrev_section.GetName())
self.assertEquals(LLDB_INVALID_ADDRESS, debug_abbrev_section.GetLoadAddress(target))
debug_line_section = module.GetSectionAtIndex(3)
self.assertEquals(".debug_line", debug_line_section.GetName())
self.assertEquals(LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target))
debug_str_section = module.GetSectionAtIndex(4)
self.assertEquals(".debug_str", debug_str_section.GetName())
self.assertEquals(LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target))
def test_load_module_from_file(self):
"""Test connecting to a WebAssembly engine via GDB-remote and loading a Wasm module from a file"""
class Responder(MyResponder):
def __init__(self, obj_path):
MyResponder.__init__(self, obj_path)
def qXferRead(self, obj, annex, offset, length):
if obj == "libraries":
xml = '<library-list><library name=\"%s\"><section address=\"%d\"/></library></library-list>' % (self._obj_path, load_address)
print xml
return xml, False
else:
return None, False
def readMemory(self, addr, length):
assert False # Should not be called
yaml_path = "test_wasm_embedded_debug_sections.yaml"
yaml_base, ext = os.path.splitext(yaml_path)
obj_path = self.getBuildArtifact(yaml_base)
self.yaml2obj(yaml_path, obj_path)
self.server.responder = Responder(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(load_address | code_section.GetFileOffset(), code_section.GetLoadAddress(target))
debug_info_section = module.GetSectionAtIndex(1)
self.assertEquals(".debug_info", debug_info_section.GetName())
self.assertEquals(LLDB_INVALID_ADDRESS, debug_info_section.GetLoadAddress(target))
debug_abbrev_section = module.GetSectionAtIndex(2)
self.assertEquals(".debug_abbrev", debug_abbrev_section.GetName())
self.assertEquals(LLDB_INVALID_ADDRESS, debug_abbrev_section.GetLoadAddress(target))
debug_line_section = module.GetSectionAtIndex(3)
self.assertEquals(".debug_line", debug_line_section.GetName())
self.assertEquals(LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target))
debug_str_section = module.GetSectionAtIndex(4)
self.assertEquals(".debug_str", debug_str_section.GetName())
self.assertEquals(LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target))

View File

@@ -0,0 +1,18 @@
--- !WASM
FileHeader:
Version: 0x00000001
Sections:
- 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
...

View File

@@ -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
...

View File

@@ -0,0 +1,16 @@
--- !WASM
FileHeader:
Version: 0x00000001
Sections:
- Type: CODE
Functions:
- Index: 0
Locals:
- Type: I32
Count: 6
Body: 238080808000210141102102200120026B21032003200036020C200328020C2104200328020C2105200420056C210620060F0B
- Type: CUSTOM
Name: external_debug_info
Payload: ###_EXTERNAL_DEBUG_INFO_###
...

View File

@@ -48,6 +48,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"
@@ -274,6 +275,7 @@ llvm::Error SystemInitializerFull::Initialize() {
DynamicLoaderMacOSXDYLD::Initialize();
DynamicLoaderMacOS::Initialize();
DynamicLoaderPOSIXDYLD::Initialize();
wasm::DynamicLoaderWasmDYLD::Initialize(); // before DynamicLoaderStatic.
DynamicLoaderStatic::Initialize();
DynamicLoaderWindowsDYLD::Initialize();
@@ -362,6 +364,7 @@ void SystemInitializerFull::Terminate() {
DynamicLoaderMacOSXDYLD::Terminate();
DynamicLoaderMacOS::Terminate();
DynamicLoaderPOSIXDYLD::Terminate();
wasm::DynamicLoaderWasmDYLD::Terminate();
DynamicLoaderStatic::Terminate();
DynamicLoaderWindowsDYLD::Terminate();

View File

@@ -4,3 +4,4 @@ add_subdirectory(POSIX-DYLD)
add_subdirectory(Static)
add_subdirectory(Hexagon-DYLD)
add_subdirectory(Windows-DYLD)
add_subdirectory(wasm-DYLD)

View File

@@ -233,7 +233,7 @@ 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_code_section_offset(0) {
m_arch("wasm32-unknown-unknown-wasm") {
m_data.SetAddressByteSize(4);
}
@@ -242,7 +242,7 @@ ObjectFileWasm::ObjectFileWasm(const lldb::ModuleSP &module_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"), m_code_section_offset(0) {}
m_arch("wasm32-unknown-unknown-wasm") {}
bool ObjectFileWasm::ParseHeader() {
// We already parsed the header during initialization.
@@ -264,15 +264,19 @@ void ObjectFileWasm::CreateSections(SectionList &unified_section_list) {
for (const section_info &sect_info : m_sect_infos) {
SectionType section_type = eSectionTypeOther;
ConstString section_name;
offset_t file_offset = 0;
addr_t vm_addr = 0;
size_t vm_size = 0;
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");
m_code_section_offset = sect_info.offset & 0xffffffff;
vm_size = sect_info.size;
// 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 =
llvm::StringSwitch<SectionType>(sect_info.name.GetStringRef())
@@ -300,10 +304,9 @@ void ObjectFileWasm::CreateSections(SectionList &unified_section_list) {
if (section_type == eSectionTypeOther)
continue;
section_name = sect_info.name;
file_offset = sect_info.offset & 0xffffffff;
if (IsInMemory()) {
vm_addr = sect_info.offset & 0xffffffff;
vm_size = sect_info.size;
if (!IsInMemory()) {
vm_size = 0;
vm_addr = 0;
}
}
@@ -343,6 +346,10 @@ bool ObjectFileWasm::SetLoadAddress(Target &target, lldb::addr_t load_address,
/// 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;
@@ -355,12 +362,10 @@ bool ObjectFileWasm::SetLoadAddress(Target &target, lldb::addr_t load_address,
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())) {
if (target.SetSectionLoadAddress(
section_sp, load_address | section_sp->GetFileOffset())) {
++num_loaded_sections;
}
}
@@ -368,11 +373,11 @@ bool ObjectFileWasm::SetLoadAddress(Target &target, lldb::addr_t load_address,
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, (size_t) (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());
}

View File

@@ -69,7 +69,7 @@ public:
return m_arch.GetByteOrder();
}
bool IsExecutable() const override { return true; }
bool IsExecutable() const override { return false; }
uint32_t GetAddressByteSize() const override {
return m_arch.GetAddressByteSize();
@@ -81,7 +81,7 @@ public:
Symtab *GetSymtab() override;
bool IsStripped() override { return true; }
bool IsStripped() override { return !!GetExternalDebugInfoFileSpec(); }
void CreateSections(SectionList &unified_section_list) override;
@@ -93,7 +93,7 @@ public:
uint32_t GetDependentModules(FileSpecList &files) override { return 0; }
Type CalculateType() override { return eTypeExecutable; }
Type CalculateType() override { return eTypeSharedLibrary; }
Strata CalculateStrata() override { return eStrataUser; }
@@ -101,8 +101,7 @@ public:
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);
}
/// \}
@@ -127,7 +126,7 @@ private:
/// \}
/// 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;
@@ -145,7 +144,6 @@ private:
std::vector<section_info_t> m_sect_infos;
ArchSpec m_arch;
UUID m_uuid;
uint32_t m_code_section_offset;
};
} // namespace wasm

View File

@@ -4,11 +4,11 @@
# CHECK: Plugin name: wasm
# CHECK: Architecture: wasm32-unknown-unknown-wasm
# CHECK: UUID:
# CHECK: Executable: true
# CHECK: Stripped: true
# CHECK: Type: executable
# CHECK: Executable: false
# CHECK: Stripped: false
# CHECK: Type: shared library
# CHECK: Strata: user
# CHECK: Base VM address: 0xa
# CHECK: Base VM address: 0x0
# CHECK: Name: code
# CHECK: Type: code

View File

@@ -4,11 +4,11 @@
# CHECK: Plugin name: wasm
# CHECK: Architecture: wasm32-unknown-unknown-wasm
# CHECK: UUID:
# CHECK: Executable: true
# CHECK: Stripped: true
# CHECK: Type: executable
# CHECK: Executable: false
# CHECK: Stripped: false
# CHECK: Type: shared library
# CHECK: Strata: user
# CHECK: Base VM address: 0xa
# CHECK: Base VM address: 0x0
# CHECK: Name: code
# CHECK: Type: code

View File

@@ -4,9 +4,9 @@
# CHECK: Plugin name: wasm
# CHECK: Architecture: wasm32-unknown-unknown-wasm
# CHECK: UUID:
# CHECK: Executable: true
# CHECK: Stripped: true
# CHECK: Type: executable
# CHECK: Executable: false
# CHECK: Stripped: false
# CHECK: Type: shared library
# CHECK: Strata: user
# CHECK: Base VM address: 0x0

View File

@@ -13,11 +13,11 @@
# CHECK: Plugin name: wasm
# CHECK: Architecture: wasm32-unknown-unknown-wasm
# CHECK: UUID:
# CHECK: Executable: true
# CHECK: Executable: false
# CHECK: Stripped: true
# CHECK: Type: executable
# CHECK: Type: shared library
# CHECK: Strata: user
# CHECK: Base VM address: 0xa
# CHECK: Base VM address: 0x0
# CHECK: Name: code
# CHECK: Type: code

View File

@@ -38,6 +38,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"
@@ -244,6 +245,7 @@ llvm::Error SystemInitializerTest::Initialize() {
DynamicLoaderMacOSXDYLD::Initialize();
DynamicLoaderMacOS::Initialize();
DynamicLoaderPOSIXDYLD::Initialize();
wasm::DynamicLoaderWasmDYLD::Initialize(); // before DynamicLoaderStatic.
DynamicLoaderStatic::Initialize();
DynamicLoaderWindowsDYLD::Initialize();
@@ -332,6 +334,7 @@ void SystemInitializerTest::Terminate() {
DynamicLoaderMacOSXDYLD::Terminate();
DynamicLoaderMacOS::Terminate();
DynamicLoaderPOSIXDYLD::Terminate();
wasm::DynamicLoaderWasmDYLD::Terminate();
DynamicLoaderStatic::Terminate();
DynamicLoaderWindowsDYLD::Terminate();