| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/developer/debug/zxdb/console/format_register.cc" |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/developer/debug/shared/arch_arm64.h" |
| #include "src/developer/debug/shared/arch_x86.h" |
| #include "src/lib/fxl/logging.h" |
| |
| namespace zxdb { |
| |
| using namespace debug_ipc; |
| |
| namespace { |
| |
| // Creates fake data for a register. |
| // |length| is how long the register data (and thus the register) is. |
| // |val_loop| is to determine a loop that will fill the register with a |
| // particular pattern (010203....). |
| std::vector<uint8_t> CreateData(size_t length, size_t val_loop) { |
| FXL_DCHECK(length >= val_loop); |
| |
| std::vector<uint8_t> data(length); |
| // So that we get the number backwards (0x0102...). |
| uint8_t base = val_loop; |
| for (size_t i = 0; i < val_loop; i++) { |
| data[i] = base - i; |
| } |
| return data; |
| } |
| |
| debug_ipc::Register CreateRegister(RegisterID id, size_t length, size_t val_loop) { |
| debug_ipc::Register reg; |
| reg.id = id; |
| reg.data = CreateData(length, val_loop); |
| return reg; |
| } |
| |
| void SetRegisterValue(Register* reg, uint64_t value) { |
| std::vector<uint8_t> data; |
| data.reserve(8); |
| uint8_t* ptr = reinterpret_cast<uint8_t*>(&value); |
| for (size_t i = 0; i < 8; i++, ptr++) |
| data.emplace_back(*ptr); |
| reg->data() = data; |
| } |
| |
| Register CreateRegisterWithValue(RegisterID id, uint64_t value) { |
| Register reg(CreateRegister(id, 8, 8)); |
| SetRegisterValue(®, value); |
| return reg; |
| } |
| |
| void GetCategories(RegisterSet* registers) { |
| std::vector<debug_ipc::RegisterCategory> categories; |
| |
| RegisterCategory cat1; |
| cat1.type = RegisterCategory::Type::kGeneral; |
| cat1.registers.push_back(CreateRegister(RegisterID::kX64_rax, 8, 1)); |
| cat1.registers.push_back(CreateRegister(RegisterID::kX64_rbx, 8, 2)); |
| cat1.registers.push_back(CreateRegister(RegisterID::kX64_rcx, 8, 4)); |
| cat1.registers.push_back(CreateRegister(RegisterID::kX64_rdx, 8, 8)); |
| categories.push_back(cat1); |
| |
| // Sanity check |
| ASSERT_EQ(*(uint8_t*)&(cat1.registers[0].data[0]), 0x01u); |
| ASSERT_EQ(*(uint16_t*)&(cat1.registers[1].data[0]), 0x0102u); |
| ASSERT_EQ(*(uint32_t*)&(cat1.registers[2].data[0]), 0x01020304u); |
| ASSERT_EQ(*(uint64_t*)&(cat1.registers[3].data[0]), 0x0102030405060708u); |
| |
| RegisterCategory cat2; |
| cat2.type = RegisterCategory::Type::kVector; |
| cat2.registers.push_back(CreateRegister(RegisterID::kX64_xmm0, 16, 1)); |
| cat2.registers.push_back(CreateRegister(RegisterID::kX64_xmm1, 16, 2)); |
| cat2.registers.push_back(CreateRegister(RegisterID::kX64_xmm2, 16, 4)); |
| cat2.registers.push_back(CreateRegister(RegisterID::kX64_xmm3, 16, 8)); |
| cat2.registers.push_back(CreateRegister(RegisterID::kX64_xmm4, 16, 16)); |
| categories.push_back(cat2); |
| |
| RegisterCategory cat3; |
| cat3.type = RegisterCategory::Type::kFP; |
| cat3.registers.push_back(CreateRegister(RegisterID::kX64_st0, 16, 4)); |
| cat3.registers.push_back(CreateRegister(RegisterID::kX64_st1, 16, 4)); |
| // Invalid |
| cat3.registers.push_back(CreateRegister(RegisterID::kX64_st2, 16, 16)); |
| // Push a valid 16-byte long double value. |
| auto& reg = cat3.registers.back(); |
| // Clear all (create a 0 value). |
| for (size_t i = 0; i < reg.data.size(); i++) |
| reg.data[i] = 0; |
| |
| categories.push_back(cat3); |
| |
| RegisterSet regs(debug_ipc::Arch::kArm64, std::move(categories)); |
| *registers = std::move(regs); |
| } |
| |
| } // namespace |
| |
| TEST(FormatRegisters, GeneralRegisters) { |
| RegisterSet registers; |
| GetCategories(®isters); |
| |
| FormatRegisterOptions options; |
| options.arch = debug_ipc::Arch::kX64; |
| options.categories = {RegisterCategory::Type::kGeneral}; |
| |
| FilteredRegisterSet filtered_set; |
| Err err = FilterRegisters(options, registers, &filtered_set); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| // Force rcx to -2 to test negative integer formatting. |
| auto& reg = filtered_set[RegisterCategory::Type::kGeneral]; |
| Register& rcx = reg[2]; |
| EXPECT_EQ(RegisterID::kX64_rcx, rcx.id()); |
| SetRegisterValue(&rcx, static_cast<uint64_t>(-2)); |
| |
| OutputBuffer out; |
| err = FormatRegisters(options, filtered_set, &out); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| EXPECT_EQ( |
| "General Purpose Registers\n" |
| " rax 0x1 = 1\n" |
| " rbx 0x102 = 258\n" |
| " rcx 0xfffffffffffffffe = -2\n" |
| " rdx 0x102030405060708 \n" |
| "\n", |
| out.AsString()); |
| } |
| |
| TEST(FormatRegisters, VectorRegisters) { |
| RegisterSet registers; |
| GetCategories(®isters); |
| |
| FormatRegisterOptions options; |
| options.arch = debug_ipc::Arch::kX64; |
| options.categories = {RegisterCategory::Type::kVector}; |
| |
| FilteredRegisterSet filtered_set; |
| Err err = FilterRegisters(options, registers, &filtered_set); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| OutputBuffer out; |
| err = FormatRegisters(options, filtered_set, &out); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| EXPECT_EQ( |
| "Vector Registers\n" |
| " xmm0 00000000 00000000 00000000 00000001\n" |
| " xmm1 00000000 00000000 00000000 00000102\n" |
| " xmm2 00000000 00000000 00000000 01020304\n" |
| " xmm3 00000000 00000000 01020304 05060708\n" |
| " xmm4 01020304 05060708 090a0b0c 0d0e0f10\n" |
| "\n", |
| out.AsString()); |
| } |
| |
| TEST(FormatRegisters, AllRegisters) { |
| RegisterSet registers; |
| GetCategories(®isters); |
| |
| FormatRegisterOptions options; |
| options.arch = debug_ipc::Arch::kX64; |
| options.categories = {RegisterCategory::Type::kGeneral, RegisterCategory::Type::kFP, |
| RegisterCategory::Type::kVector}; |
| FilteredRegisterSet filtered_set; |
| Err err = FilterRegisters(options, registers, &filtered_set); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| OutputBuffer out; |
| err = FormatRegisters(options, filtered_set, &out); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| // TODO(donosoc): Detect the maximum length and make the tables coincide. |
| EXPECT_EQ( |
| "General Purpose Registers\n" |
| " rax 0x1 = 1\n" |
| " rbx 0x102 = 258\n" |
| " rcx 0x1020304 \n" |
| " rdx 0x102030405060708 \n" |
| "\n" |
| "Floating Point Registers\n" |
| " st0 6.163689759657267600e-4944 00000000 00000000 00000000 01020304\n" |
| " st1 6.163689759657267600e-4944 00000000 00000000 00000000 01020304\n" |
| " st2 0.000000000000000000e+00 00000000 00000000 00000000 00000000\n" |
| "\n" |
| "Vector Registers\n" |
| " xmm0 00000000 00000000 00000000 00000001\n" |
| " xmm1 00000000 00000000 00000000 00000102\n" |
| " xmm2 00000000 00000000 00000000 01020304\n" |
| " xmm3 00000000 00000000 01020304 05060708\n" |
| " xmm4 01020304 05060708 090a0b0c 0d0e0f10\n" |
| "\n", |
| out.AsString()); |
| } |
| |
| TEST(FormatRegisters, OneRegister) { |
| RegisterSet registers; |
| GetCategories(®isters); |
| |
| FormatRegisterOptions options; |
| options.arch = debug_ipc::Arch::kX64; |
| options.filter_regexp = "xmm3"; |
| options.categories = {RegisterCategory::Type::kGeneral, RegisterCategory::Type::kFP, |
| RegisterCategory::Type::kVector}; |
| |
| FilteredRegisterSet filtered_set; |
| Err err = FilterRegisters(options, registers, &filtered_set); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| OutputBuffer out; |
| err = FormatRegisters(options, filtered_set, &out); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| EXPECT_EQ( |
| "Vector Registers\n" |
| " xmm3 00000000 00000000 01020304 05060708\n" |
| "\n", |
| out.AsString()); |
| } |
| |
| TEST(FormatRegister, RegexSearch) { |
| RegisterSet registers; |
| GetCategories(®isters); |
| |
| FormatRegisterOptions options; |
| options.arch = debug_ipc::Arch::kX64; |
| options.filter_regexp = "XMm[2-4]$"; |
| options.categories = {RegisterCategory::Type::kVector}; |
| |
| FilteredRegisterSet filtered_set; |
| Err err = FilterRegisters(options, registers, &filtered_set); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| OutputBuffer out; |
| err = FormatRegisters(options, filtered_set, &out); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| EXPECT_EQ( |
| "Vector Registers\n" |
| " xmm2 00000000 00000000 00000000 01020304\n" |
| " xmm3 00000000 00000000 01020304 05060708\n" |
| " xmm4 01020304 05060708 090a0b0c 0d0e0f10\n" |
| "\n", |
| out.AsString()); |
| } |
| |
| TEST(FormatRegisters, CannotFindRegister) { |
| RegisterSet registers; |
| GetCategories(®isters); |
| |
| FormatRegisterOptions options; |
| options.arch = debug_ipc::Arch::kX64; |
| options.filter_regexp = "W0"; |
| options.categories = {RegisterCategory::Type::kGeneral, RegisterCategory::Type::kFP, |
| RegisterCategory::Type::kVector}; |
| FilteredRegisterSet filtered_set; |
| Err err = FilterRegisters(options, registers, &filtered_set); |
| EXPECT_TRUE(err.has_error()); |
| } |
| |
| TEST(FormatRegisters, WithRflags) { |
| RegisterSet register_set; |
| GetCategories(®ister_set); |
| auto& cat = register_set.category_map()[RegisterCategory::Type::kGeneral]; |
| cat.push_back(CreateRegisterWithValue(RegisterID::kX64_rflags, 0)); |
| |
| FormatRegisterOptions options; |
| options.arch = debug_ipc::Arch::kX64; |
| options.categories = {RegisterCategory::Type::kGeneral}; |
| |
| FilteredRegisterSet filtered_set; |
| Err err = FilterRegisters(options, register_set, &filtered_set); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| OutputBuffer out; |
| err = FormatRegisters(options, filtered_set, &out); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| EXPECT_EQ( |
| "General Purpose Registers\n" |
| " rax 0x1 = 1\n" |
| " rbx 0x102 = 258\n" |
| " rcx 0x1020304 \n" |
| " rdx 0x102030405060708 \n" |
| " rflags 0x00000000 CF=0, PF=0, AF=0, ZF=0, SF=0, TF=0, IF=0, " |
| "DF=0, OF=0\n" |
| "\n", |
| out.AsString()); |
| } |
| |
| TEST(FormatRegisters, RFlagsValues) { |
| RegisterSet register_set; |
| auto& cat = register_set.category_map()[RegisterCategory::Type::kGeneral]; |
| cat.push_back(CreateRegisterWithValue(RegisterID::kX64_rflags, 0)); |
| |
| FormatRegisterOptions options; |
| options.arch = debug_ipc::Arch::kX64; |
| options.filter_regexp = "rflags"; |
| options.categories = {RegisterCategory::Type::kGeneral}; |
| |
| FilteredRegisterSet filtered_set; |
| Err err = FilterRegisters(options, register_set, &filtered_set); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| // filtered_set now holds a pointer to rflags that we can change. |
| auto& reg = filtered_set[RegisterCategory::Type::kGeneral].front(); |
| SetRegisterValue(®, X86_FLAG_MASK(RflagsCF) | X86_FLAG_MASK(RflagsPF) | |
| X86_FLAG_MASK(RflagsAF) | X86_FLAG_MASK(RflagsZF) | |
| X86_FLAG_MASK(RflagsTF) | X86_FLAG_MASK(RflagsDF)); |
| |
| OutputBuffer out; |
| err = FormatRegisters(options, filtered_set, &out); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| EXPECT_EQ( |
| "General Purpose Registers\n" |
| " rflags 0x00000555 CF=1, PF=1, AF=1, ZF=1, SF=0, TF=1, IF=0, DF=1, " |
| "OF=0\n" |
| "\n", |
| out.AsString()); |
| } |
| |
| TEST(FormatRegisters, RFlagsValuesExtended) { |
| RegisterSet register_set; |
| auto& cat = register_set.category_map()[RegisterCategory::Type::kGeneral]; |
| cat.push_back(CreateRegisterWithValue(RegisterID::kX64_rflags, 0)); |
| |
| FormatRegisterOptions options; |
| options.arch = debug_ipc::Arch::kX64; |
| options.filter_regexp = "rflags"; |
| options.extended = true; |
| options.categories = {RegisterCategory::Type::kGeneral}; |
| |
| FilteredRegisterSet filtered_set; |
| Err err = FilterRegisters(options, register_set, &filtered_set); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| // filtered_set now holds a pointer to rflags that we can change. |
| auto& reg = filtered_set[RegisterCategory::Type::kGeneral].front(); |
| |
| SetRegisterValue( |
| ®, X86_FLAG_MASK(RflagsCF) | X86_FLAG_MASK(RflagsPF) | X86_FLAG_MASK(RflagsAF) | |
| X86_FLAG_MASK(RflagsZF) | X86_FLAG_MASK(RflagsTF) | X86_FLAG_MASK(RflagsDF) | |
| // Extended flags |
| (0b10 << kRflagsIOPLShift) | X86_FLAG_MASK(RflagsNT) | X86_FLAG_MASK(RflagsVM) | |
| X86_FLAG_MASK(RflagsVIF) | X86_FLAG_MASK(RflagsID)); |
| |
| OutputBuffer out; |
| err = FormatRegisters(options, filtered_set, &out); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| EXPECT_EQ( |
| "General Purpose Registers\n" |
| " rflags 0x002a6555 CF=1, PF=1, AF=1, ZF=1, SF=0, TF=1, IF=0, DF=1, " |
| "OF=0\n" |
| " IOPL=2, NT=1, RF=0, VM=1, AC=0, VIF=1, VIP=0, " |
| "ID=1\n" |
| "\n", |
| out.AsString()); |
| } |
| |
| TEST(FormatRegisters, CPSRValues) { |
| RegisterSet register_set; |
| auto& cat = register_set.category_map()[RegisterCategory::Type::kGeneral]; |
| cat.push_back(CreateRegisterWithValue(RegisterID::kARMv8_cpsr, 0)); |
| |
| FormatRegisterOptions options; |
| options.arch = debug_ipc::Arch::kArm64; |
| options.filter_regexp = "cpsr"; |
| options.categories = {RegisterCategory::Type::kGeneral}; |
| |
| FilteredRegisterSet filtered_set; |
| Err err = FilterRegisters(options, register_set, &filtered_set); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| // filtered_set now holds a pointer to rflags that we can change. |
| auto& reg = filtered_set[RegisterCategory::Type::kGeneral].front(); |
| SetRegisterValue(®, ARM64_FLAG_MASK(Cpsr, C) | ARM64_FLAG_MASK(Cpsr, N)); |
| |
| OutputBuffer out; |
| err = FormatRegisters(options, filtered_set, &out); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| EXPECT_EQ( |
| "General Purpose Registers\n" |
| " cpsr 0xa0000000 V=0, C=1, Z=0, N=1\n" |
| "\n", |
| out.AsString()); |
| |
| // Check out extended |
| SetRegisterValue(®, ARM64_FLAG_MASK(Cpsr, C) | ARM64_FLAG_MASK(Cpsr, N) | |
| // Extended flags. |
| ARM64_FLAG_MASK(Cpsr, EL) | ARM64_FLAG_MASK(Cpsr, I) | |
| ARM64_FLAG_MASK(Cpsr, A) | ARM64_FLAG_MASK(Cpsr, IL) | |
| ARM64_FLAG_MASK(Cpsr, PAN) | ARM64_FLAG_MASK(Cpsr, UAO)); |
| |
| options.extended = true; |
| out = OutputBuffer(); |
| err = FormatRegisters(options, filtered_set, &out); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| EXPECT_EQ( |
| "General Purpose Registers\n" |
| " cpsr 0xa0d00181 V=0, C=1, Z=0, N=1\n" |
| " EL=1, F=0, I=1, A=1, D=0, IL=1, SS=0, PAN=1, UAO=1\n" |
| "\n", |
| out.AsString()); |
| } |
| |
| TEST(FormatRegisters, DebugRegisters_x86) { |
| RegisterSet register_set; |
| auto& cat = register_set.category_map()[RegisterCategory::Type::kDebug]; |
| cat.push_back(CreateRegisterWithValue(RegisterID::kX64_dr0, 0x1234)); |
| cat.push_back(CreateRegisterWithValue(RegisterID::kX64_dr1, 0x1234567)); |
| cat.push_back(CreateRegisterWithValue(RegisterID::kX64_dr2, 0x123456789ab)); |
| cat.push_back(CreateRegisterWithValue(RegisterID::kX64_dr3, 0x123456789abcdef)); |
| |
| cat.push_back(CreateRegisterWithValue(RegisterID::kX64_dr6, 0xaffa)); |
| cat.push_back(CreateRegisterWithValue(RegisterID::kX64_dr7, 0xaaaa26aa)); |
| |
| FormatRegisterOptions options; |
| options.arch = debug_ipc::Arch::kX64; |
| options.categories = {RegisterCategory::Type::kDebug}; |
| |
| FilteredRegisterSet filtered_set; |
| Err err = FilterRegisters(options, register_set, &filtered_set); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| OutputBuffer out; |
| err = FormatRegisters(options, filtered_set, &out); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| EXPECT_EQ( |
| "Debug Registers\n" |
| " dr0 0x1234 = 4660\n" |
| " dr1 0x1234567 \n" |
| " dr2 0x123456789ab \n" |
| " dr3 0x123456789abcdef \n" |
| " dr6 0x0000affa B0=0, B1=1, B2=0, B3=1, BD=1, BS=0, BT=1\n" |
| " dr7 0xaaaa26aa L0=0, G0=1, L1=0, G1=1, L2=0, G2=1, L3=0, " |
| "G4=1, " |
| "LE=0, GE=1, GD=1\n" |
| " R/W0=2, LEN0=2, R/W1=2, LEN1=2, R/W2=2, " |
| "LEN2=2, " |
| "R/W3=2, LEN3=2\n" |
| "\n", |
| out.AsString()); |
| } |
| |
| TEST(FormatRegisters, DebugRegisters_arm64) { |
| RegisterSet register_set; |
| auto& cat = register_set.category_map()[RegisterCategory::Type::kDebug]; |
| cat.push_back(CreateRegisterWithValue( |
| RegisterID::kARMv8_dbgbcr0_el1, |
| ARM64_FLAG_MASK(DBGBCR, PMC) | ARM64_FLAG_MASK(DBGBCR, HMC) | ARM64_FLAG_MASK(DBGBCR, LBN))); |
| cat.push_back(CreateRegisterWithValue(RegisterID::kARMv8_dbgbvr0_el1, 0xdeadbeefaabbccdd)); |
| cat.push_back(CreateRegisterWithValue(RegisterID::kARMv8_dbgbcr15_el1, |
| ARM64_FLAG_MASK(DBGBCR, E) | ARM64_FLAG_MASK(DBGBCR, BAS) | |
| ARM64_FLAG_MASK(DBGBCR, SSC) | |
| ARM64_FLAG_MASK(DBGBCR, BT))); |
| cat.push_back(CreateRegisterWithValue(RegisterID::kARMv8_dbgbvr0_el1, 0xaabbccdd11223344)); |
| cat.push_back(CreateRegisterWithValue( |
| RegisterID::kARMv8_id_aa64dfr0_el1, |
| ARM64_FLAG_MASK(ID_AA64DFR0_EL1, DV) | ARM64_FLAG_MASK(ID_AA64DFR0_EL1, PMUV) | |
| ARM64_FLAG_MASK(ID_AA64DFR0_EL1, BRP) | ARM64_FLAG_MASK(ID_AA64DFR0_EL1, WRP) | |
| ARM64_FLAG_MASK(ID_AA64DFR0_EL1, PMSV))); |
| cat.push_back(CreateRegisterWithValue( |
| RegisterID::kARMv8_mdscr_el1, |
| ARM64_FLAG_MASK(MDSCR_EL1, SS) | ARM64_FLAG_MASK(MDSCR_EL1, TDCC) | |
| ARM64_FLAG_MASK(MDSCR_EL1, MDE) | ARM64_FLAG_MASK(MDSCR_EL1, TXU) | |
| ARM64_FLAG_MASK(MDSCR_EL1, RXfull))); |
| |
| FormatRegisterOptions options; |
| options.arch = debug_ipc::Arch::kArm64; |
| options.categories = {RegisterCategory::Type::kDebug}; |
| |
| FilteredRegisterSet filtered_set; |
| Err err = FilterRegisters(options, register_set, &filtered_set); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| OutputBuffer out; |
| err = FormatRegisters(options, filtered_set, &out); |
| ASSERT_FALSE(err.has_error()) << err.msg(); |
| |
| EXPECT_EQ( |
| "Debug Registers\n" |
| " kARMv8_dbgbcr0_el1 0x000f2006 E=0, PMC=3, BAS=0, HMC=1, " |
| "SSC=0, LBN=15, BT=0\n" |
| " kARMv8_dbgbvr0_el1 0xdeadbeefaabbccdd \n" |
| " kARMv8_dbgbcr15_el1 0x00f0c1e1 E=1, PMC=0, BAS=15, HMC=0, " |
| "SSC=3, LBN=0, BT=15\n" |
| " kARMv8_dbgbvr0_el1 0xaabbccdd11223344 \n" |
| " id_aa64dfr0_el1 0xf00f0ff0f DV=15, TV=0, PMUV=15, BRP=16, " |
| "WRP=16, CTX_CMP=1, PMSV=15\n" |
| " mdscr_el1 0x44009001 SS=1, TDCC=1, KDE=0, HDE=0, " |
| "MDE=1, RAZ/WI=0, TDA=0, INTdis=0, TXU=1, RXO=0, TXfull=0, RXfull=1\n" |
| "\n", |
| out.AsString()); |
| } |
| |
| } // namespace zxdb |