//===- X86LDBackend.cpp ---------------------------------------------------===// // // The MCLinker Project // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "X86.h" #include "X86ELFDynamic.h" #include "X86LDBackend.h" #include "X86Relocator.h" #include "X86GNUInfo.h" #include <llvm/ADT/StringRef.h> #include <llvm/ADT/Triple.h> #include <llvm/Support/Casting.h> #include <mcld/LinkerConfig.h> #include <mcld/IRBuilder.h> #include <mcld/LD/ELFFileFormat.h> #include <mcld/Fragment/FillFragment.h> #include <mcld/Fragment/RegionFragment.h> #include <mcld/Support/MsgHandling.h> #include <mcld/Support/TargetRegistry.h> #include <mcld/Object/ObjectBuilder.h> #include <llvm/Support/Dwarf.h> #include <cstring> using namespace mcld; //===----------------------------------------------------------------------===// // X86GNULDBackend //===----------------------------------------------------------------------===// X86GNULDBackend::X86GNULDBackend(const LinkerConfig& pConfig, GNUInfo* pInfo, Relocation::Type pCopyRel) : GNULDBackend(pConfig, pInfo), m_pRelocator(NULL), m_pPLT(NULL), m_pRelDyn(NULL), m_pRelPLT(NULL), m_pDynamic(NULL), m_pGOTSymbol(NULL), m_CopyRel(pCopyRel) { llvm::Triple::ArchType arch = pConfig.targets().triple().getArch(); assert (arch == llvm::Triple::x86 || arch == llvm::Triple::x86_64); if (arch == llvm::Triple::x86 || pConfig.targets().triple().getEnvironment() == llvm::Triple::GNUX32) { m_RelEntrySize = 8; m_RelaEntrySize = 12; if (arch == llvm::Triple::x86) m_PointerRel = llvm::ELF::R_386_32; else m_PointerRel = llvm::ELF::R_X86_64_32; } else { m_RelEntrySize = 16; m_RelaEntrySize = 24; m_PointerRel = llvm::ELF::R_X86_64_64; } } X86GNULDBackend::~X86GNULDBackend() { delete m_pRelocator; delete m_pPLT; delete m_pRelDyn; delete m_pRelPLT; delete m_pDynamic; } const Relocator* X86GNULDBackend::getRelocator() const { assert(NULL != m_pRelocator); return m_pRelocator; } Relocator* X86GNULDBackend::getRelocator() { assert(NULL != m_pRelocator); return m_pRelocator; } void X86GNULDBackend::doPreLayout(IRBuilder& pBuilder) { // initialize .dynamic data if (!config().isCodeStatic() && NULL == m_pDynamic) m_pDynamic = new X86ELFDynamic(*this, config()); // set .got.plt and .got sizes // when building shared object, the .got section is must if (LinkerConfig::Object != config().codeGenType()) { setGOTSectionSize(pBuilder); // set .plt size if (m_pPLT->hasPLT1()) m_pPLT->finalizeSectionSize(); // set .rel.dyn/.rela.dyn size if (!m_pRelDyn->empty()) { assert(!config().isCodeStatic() && "static linkage should not result in a dynamic relocation section"); setRelDynSize(); } // set .rel.plt/.rela.plt size if (!m_pRelPLT->empty()) { assert(!config().isCodeStatic() && "static linkage should not result in a dynamic relocation section"); setRelPLTSize(); } } if (config().options().genUnwindInfo()) addEhFrameForPLT(pBuilder.getModule()); } void X86GNULDBackend::doPostLayout(Module& pModule, IRBuilder& pBuilder) { } /// dynamic - the dynamic section of the target machine. /// Use co-variant return type to return its own dynamic section. X86ELFDynamic& X86GNULDBackend::dynamic() { assert(NULL != m_pDynamic); return *m_pDynamic; } /// dynamic - the dynamic section of the target machine. /// Use co-variant return type to return its own dynamic section. const X86ELFDynamic& X86GNULDBackend::dynamic() const { assert(NULL != m_pDynamic); return *m_pDynamic; } void X86GNULDBackend::defineGOTSymbol(IRBuilder& pBuilder, Fragment& pFrag) { // define symbol _GLOBAL_OFFSET_TABLE_ if (m_pGOTSymbol != NULL) { pBuilder.AddSymbol<IRBuilder::Force, IRBuilder::Unresolve>( "_GLOBAL_OFFSET_TABLE_", ResolveInfo::Object, ResolveInfo::Define, ResolveInfo::Local, 0x0, // size 0x0, // value FragmentRef::Create(pFrag, 0x0), ResolveInfo::Hidden); } else { m_pGOTSymbol = pBuilder.AddSymbol<IRBuilder::Force, IRBuilder::Resolve>( "_GLOBAL_OFFSET_TABLE_", ResolveInfo::Object, ResolveInfo::Define, ResolveInfo::Local, 0x0, // size 0x0, // value FragmentRef::Create(pFrag, 0x0), ResolveInfo::Hidden); } } uint64_t X86GNULDBackend::emitSectionData(const LDSection& pSection, MemoryRegion& pRegion) const { assert(pRegion.size() && "Size of MemoryRegion is zero!"); const ELFFileFormat* FileFormat = getOutputFormat(); assert(FileFormat && "ELFFileFormat is NULL in X86GNULDBackend::emitSectionData!"); unsigned int EntrySize = 0; uint64_t RegionSize = 0; if (FileFormat->hasPLT() && (&pSection == &(FileFormat->getPLT()))) { unsigned char* buffer = pRegion.begin(); m_pPLT->applyPLT0(); m_pPLT->applyPLT1(); X86PLT::iterator it = m_pPLT->begin(); unsigned int plt0_size = llvm::cast<PLTEntryBase>((*it)).size(); memcpy(buffer, llvm::cast<PLTEntryBase>((*it)).getValue(), plt0_size); RegionSize += plt0_size; ++it; PLTEntryBase* plt1 = 0; X86PLT::iterator ie = m_pPLT->end(); while (it != ie) { plt1 = &(llvm::cast<PLTEntryBase>(*it)); EntrySize = plt1->size(); memcpy(buffer + RegionSize, plt1->getValue(), EntrySize); RegionSize += EntrySize; ++it; } } else if (FileFormat->hasGOT() && (&pSection == &(FileFormat->getGOT()))) { RegionSize += emitGOTSectionData(pRegion); } else if (FileFormat->hasGOTPLT() && (&pSection == &(FileFormat->getGOTPLT()))) { RegionSize += emitGOTPLTSectionData(pRegion, FileFormat); } else { fatal(diag::unrecognized_output_sectoin) << pSection.name() << "mclinker@googlegroups.com"; } return RegionSize; } X86PLT& X86GNULDBackend::getPLT() { assert(NULL != m_pPLT && "PLT section not exist"); return *m_pPLT; } const X86PLT& X86GNULDBackend::getPLT() const { assert(NULL != m_pPLT && "PLT section not exist"); return *m_pPLT; } OutputRelocSection& X86GNULDBackend::getRelDyn() { assert(NULL != m_pRelDyn && ".rel.dyn/.rela.dyn section not exist"); return *m_pRelDyn; } const OutputRelocSection& X86GNULDBackend::getRelDyn() const { assert(NULL != m_pRelDyn && ".rel.dyn/.rela.dyn section not exist"); return *m_pRelDyn; } OutputRelocSection& X86GNULDBackend::getRelPLT() { assert(NULL != m_pRelPLT && ".rel.plt/.rela.plt section not exist"); return *m_pRelPLT; } const OutputRelocSection& X86GNULDBackend::getRelPLT() const { assert(NULL != m_pRelPLT && ".rel.plt/.rela.plt section not exist"); return *m_pRelPLT; } unsigned int X86GNULDBackend::getTargetSectionOrder(const LDSection& pSectHdr) const { const ELFFileFormat* file_format = getOutputFormat(); if (file_format->hasGOT() && (&pSectHdr == &file_format->getGOT())) { if (config().options().hasNow()) return SHO_RELRO; return SHO_RELRO_LAST; } if (file_format->hasGOTPLT() && (&pSectHdr == &file_format->getGOTPLT())) { if (config().options().hasNow()) return SHO_RELRO; return SHO_NON_RELRO_FIRST; } if (file_format->hasPLT() && (&pSectHdr == &file_format->getPLT())) return SHO_PLT; return SHO_UNDEFINED; } void X86GNULDBackend::initTargetSymbols(IRBuilder& pBuilder, Module& pModule) { if (LinkerConfig::Object != config().codeGenType()) { // Define the symbol _GLOBAL_OFFSET_TABLE_ if there is a symbol with the // same name in input m_pGOTSymbol = pBuilder.AddSymbol<IRBuilder::AsReferred, IRBuilder::Resolve>( "_GLOBAL_OFFSET_TABLE_", ResolveInfo::Object, ResolveInfo::Define, ResolveInfo::Local, 0x0, // size 0x0, // value FragmentRef::Null(), // FragRef ResolveInfo::Hidden); } } void X86GNULDBackend::addEhFrameForPLT(Module& pModule) { LDSection* plt_sect = pModule.getSection(".plt"); if (!plt_sect || plt_sect->size() == 0u) return; LDSection* eh_sect = pModule.getSection(".eh_frame"); if (!eh_sect || !eh_sect->hasEhFrame()) return; EhFrame* eh_frame = eh_sect->getEhFrame(); SectionData::FragmentListType& frag_list = eh_frame->getSectionData()->getFragmentList(); llvm::StringRef cie_region = createCIERegionForPLT(); llvm::StringRef fde_region = createFDERegionForPLT(); EhFrame::CIE* cie = new EhFrame::GeneratedCIE(cie_region); EhFrame::FDE* fde = new EhFrame::GeneratedFDE(fde_region, *cie); // Augmentation data only contains FDE encoding. uint8_t aug_data = (uint8_t)(llvm::dwarf::DW_EH_PE_pcrel | llvm::dwarf::DW_EH_PE_sdata4); cie->setFDEEncode(aug_data); cie->setAugmentationData(std::string(1, aug_data)); EhFrame::cie_iterator i = eh_frame->cie_begin(); for (EhFrame::cie_iterator e = eh_frame->cie_end(); i != e; ++i) { EhFrame::CIE& exist_cie = **i; if (exist_cie == *cie) { // Insert the FDE fragment SectionData::iterator cur_iter(exist_cie); frag_list.insertAfter(cur_iter, fde); fde->setCIE(exist_cie); // Cleanup the CIE we created cie->clearFDEs(); delete cie; break; } } if (i == eh_frame->cie_end()) { // Newly insert eh_frame->addCIE(*cie); eh_frame->addFDE(*fde); } } /// finalizeSymbol - finalize the symbol value bool X86GNULDBackend::finalizeTargetSymbols() { return true; } /// doCreateProgramHdrs - backend can implement this function to create the /// target-dependent segments void X86GNULDBackend::doCreateProgramHdrs(Module& pModule) { // TODO } X86_32GNULDBackend::X86_32GNULDBackend(const LinkerConfig& pConfig, GNUInfo* pInfo) : X86GNULDBackend(pConfig, pInfo, llvm::ELF::R_386_COPY), m_pGOT (NULL), m_pGOTPLT (NULL) { } X86_32GNULDBackend::~X86_32GNULDBackend() { delete m_pGOT; delete m_pGOTPLT; } bool X86_32GNULDBackend::initRelocator() { if (NULL == m_pRelocator) { m_pRelocator = new X86_32Relocator(*this, config()); } return true; } void X86_32GNULDBackend::initTargetSections(Module& pModule, ObjectBuilder& pBuilder) { if (LinkerConfig::Object != config().codeGenType()) { ELFFileFormat* file_format = getOutputFormat(); // initialize .got LDSection& got = file_format->getGOT(); m_pGOT = new X86_32GOT(got); // initialize .got.plt LDSection& gotplt = file_format->getGOTPLT(); m_pGOTPLT = new X86_32GOTPLT(gotplt); // initialize .plt LDSection& plt = file_format->getPLT(); plt.setAlign(16u); m_pPLT = new X86_32PLT(plt, *m_pGOTPLT, config()); // initialize .rel.plt LDSection& relplt = file_format->getRelPlt(); relplt.setLink(&plt); m_pRelPLT = new OutputRelocSection(pModule, relplt); // initialize .rel.dyn LDSection& reldyn = file_format->getRelDyn(); m_pRelDyn = new OutputRelocSection(pModule, reldyn); } } X86_32GOT& X86_32GNULDBackend::getGOT() { assert(NULL != m_pGOT); return *m_pGOT; } const X86_32GOT& X86_32GNULDBackend::getGOT() const { assert(NULL != m_pGOT); return *m_pGOT; } X86_32GOTPLT& X86_32GNULDBackend::getGOTPLT() { assert(NULL != m_pGOTPLT); return *m_pGOTPLT; } const X86_32GOTPLT& X86_32GNULDBackend::getGOTPLT() const { assert(NULL != m_pGOTPLT); return *m_pGOTPLT; } llvm::StringRef X86_32GNULDBackend::createCIERegionForPLT() { using namespace llvm::dwarf; static const uint8_t data[4+4+16] = { 0x14, 0, 0, 0, // length 0, 0, 0, 0, // ID 1, // version 'z', 'R', '\0', // augmentation string 1, // code alignment factor 0x7c, // data alignment factor 8, // return address column 1, // augmentation data size DW_EH_PE_pcrel | DW_EH_PE_sdata4, // FDE encoding DW_CFA_def_cfa, 4, 4, DW_CFA_offset + 8, 1, DW_CFA_nop, DW_CFA_nop }; return llvm::StringRef((const char*)data, 4+4+16); } llvm::StringRef X86_32GNULDBackend::createFDERegionForPLT() { using namespace llvm::dwarf; static const uint8_t data[4+4+32] = { 0x24, 0, 0, 0, // length 0, 0, 0, 0, // offset to CIE 0, 0, 0, 0, // offset to PLT 0, 0, 0, 0, // size of PLT 0, // augmentation data size DW_CFA_def_cfa_offset, 8, DW_CFA_advance_loc + 6, DW_CFA_def_cfa_offset, 12, DW_CFA_advance_loc + 10, DW_CFA_def_cfa_expression, 11, DW_OP_breg4, 4, DW_OP_breg8, 0, DW_OP_lit15, DW_OP_and, DW_OP_lit11, DW_OP_ge, DW_OP_lit2, DW_OP_shl, DW_OP_plus, DW_CFA_nop, DW_CFA_nop, DW_CFA_nop, DW_CFA_nop }; return llvm::StringRef((const char*)data, 4+4+32); } void X86_32GNULDBackend::setRelDynSize() { ELFFileFormat* file_format = getOutputFormat(); file_format->getRelDyn().setSize (m_pRelDyn->numOfRelocs() * getRelEntrySize()); } void X86_32GNULDBackend::setRelPLTSize() { ELFFileFormat* file_format = getOutputFormat(); file_format->getRelPlt().setSize (m_pRelPLT->numOfRelocs() * getRelEntrySize()); } void X86_32GNULDBackend::setGOTSectionSize(IRBuilder& pBuilder) { // set .got.plt size if (LinkerConfig::DynObj == config().codeGenType() || m_pGOTPLT->hasGOT1() || NULL != m_pGOTSymbol) { m_pGOTPLT->finalizeSectionSize(); defineGOTSymbol(pBuilder, *(m_pGOTPLT->begin())); } // set .got size if (!m_pGOT->empty()) m_pGOT->finalizeSectionSize(); } uint64_t X86_32GNULDBackend::emitGOTSectionData(MemoryRegion& pRegion) const { assert(m_pGOT && "emitGOTSectionData failed, m_pGOT is NULL!"); uint32_t* buffer = reinterpret_cast<uint32_t*>(pRegion.begin()); X86_32GOTEntry* got = 0; unsigned int EntrySize = X86_32GOTEntry::EntrySize; uint64_t RegionSize = 0; for (X86_32GOT::iterator it = m_pGOT->begin(), ie = m_pGOT->end(); it != ie; ++it, ++buffer) { got = &(llvm::cast<X86_32GOTEntry>((*it))); *buffer = static_cast<uint32_t>(got->getValue()); RegionSize += EntrySize; } return RegionSize; } uint64_t X86_32GNULDBackend::emitGOTPLTSectionData(MemoryRegion& pRegion, const ELFFileFormat* FileFormat) const { assert(m_pGOTPLT && "emitGOTPLTSectionData failed, m_pGOTPLT is NULL!"); m_pGOTPLT->applyGOT0(FileFormat->getDynamic().addr()); m_pGOTPLT->applyAllGOTPLT(*m_pPLT); uint32_t* buffer = reinterpret_cast<uint32_t*>(pRegion.begin()); X86_32GOTEntry* got = 0; unsigned int EntrySize = X86_32GOTEntry::EntrySize; uint64_t RegionSize = 0; for (X86_32GOTPLT::iterator it = m_pGOTPLT->begin(), ie = m_pGOTPLT->end(); it != ie; ++it, ++buffer) { got = &(llvm::cast<X86_32GOTEntry>((*it))); *buffer = static_cast<uint32_t>(got->getValue()); RegionSize += EntrySize; } return RegionSize; } X86_64GNULDBackend::X86_64GNULDBackend(const LinkerConfig& pConfig, GNUInfo* pInfo) : X86GNULDBackend(pConfig, pInfo, llvm::ELF::R_X86_64_COPY), m_pGOT (NULL), m_pGOTPLT (NULL) { } X86_64GNULDBackend::~X86_64GNULDBackend() { delete m_pGOT; delete m_pGOTPLT; } bool X86_64GNULDBackend::initRelocator() { if (NULL == m_pRelocator) { m_pRelocator = new X86_64Relocator(*this, config()); } return true; } X86_64GOT& X86_64GNULDBackend::getGOT() { assert(NULL != m_pGOT); return *m_pGOT; } const X86_64GOT& X86_64GNULDBackend::getGOT() const { assert(NULL != m_pGOT); return *m_pGOT; } X86_64GOTPLT& X86_64GNULDBackend::getGOTPLT() { assert(NULL != m_pGOTPLT); return *m_pGOTPLT; } const X86_64GOTPLT& X86_64GNULDBackend::getGOTPLT() const { assert(NULL != m_pGOTPLT); return *m_pGOTPLT; } llvm::StringRef X86_64GNULDBackend::createCIERegionForPLT() { using namespace llvm::dwarf; static const uint8_t data[4+4+16] = { 0x14, 0, 0, 0, // length 0, 0, 0, 0, // ID 1, // CIE version 'z', 'R', '\0', // augmentation string 1, // code alignment factor 0x78, // data alignment factor 16, // return address column 1, // augmentation data size DW_EH_PE_pcrel | DW_EH_PE_sdata4, // FDE encoding DW_CFA_def_cfa, 7, 8, DW_CFA_offset + 16, 1, DW_CFA_nop, DW_CFA_nop }; return llvm::StringRef((const char*)data, 4+4+16); } llvm::StringRef X86_64GNULDBackend::createFDERegionForPLT() { using namespace llvm::dwarf; static const uint8_t data[4+4+32] = { 0x24, 0, 0, 0, // length 0, 0, 0, 0, // ID 0, 0, 0, 0, // offset to PLT 0, 0, 0, 0, // size of PLT 0, // augmentation data size DW_CFA_def_cfa_offset, 16, DW_CFA_advance_loc + 6, DW_CFA_def_cfa_offset, 24, DW_CFA_advance_loc + 10, DW_CFA_def_cfa_expression, 11, DW_OP_breg7, 8, DW_OP_breg16, 0, DW_OP_lit15, DW_OP_and, DW_OP_lit11, DW_OP_ge, DW_OP_lit3, DW_OP_shl, DW_OP_plus, DW_CFA_nop, DW_CFA_nop, DW_CFA_nop, DW_CFA_nop }; return llvm::StringRef((const char*)data, 4+4+32); } void X86_64GNULDBackend::setRelDynSize() { ELFFileFormat* file_format = getOutputFormat(); file_format->getRelaDyn().setSize (m_pRelDyn->numOfRelocs() * getRelaEntrySize()); } void X86_64GNULDBackend::setRelPLTSize() { ELFFileFormat* file_format = getOutputFormat(); file_format->getRelaPlt().setSize (m_pRelPLT->numOfRelocs() * getRelaEntrySize()); } void X86_64GNULDBackend::initTargetSections(Module& pModule, ObjectBuilder& pBuilder) { if (LinkerConfig::Object != config().codeGenType()) { ELFFileFormat* file_format = getOutputFormat(); // initialize .got LDSection& got = file_format->getGOT(); m_pGOT = new X86_64GOT(got); // initialize .got.plt LDSection& gotplt = file_format->getGOTPLT(); m_pGOTPLT = new X86_64GOTPLT(gotplt); // initialize .plt LDSection& plt = file_format->getPLT(); plt.setAlign(16u); m_pPLT = new X86_64PLT(plt, *m_pGOTPLT, config()); // initialize .rela.plt LDSection& relplt = file_format->getRelaPlt(); relplt.setLink(&plt); m_pRelPLT = new OutputRelocSection(pModule, relplt); // initialize .rela.dyn LDSection& reldyn = file_format->getRelaDyn(); m_pRelDyn = new OutputRelocSection(pModule, reldyn); } } void X86_64GNULDBackend::setGOTSectionSize(IRBuilder& pBuilder) { // set .got.plt size if (LinkerConfig::DynObj == config().codeGenType() || m_pGOTPLT->hasGOT1() || NULL != m_pGOTSymbol) { m_pGOTPLT->finalizeSectionSize(); defineGOTSymbol(pBuilder, *(m_pGOTPLT->begin())); } // set .got size if (!m_pGOT->empty()) m_pGOT->finalizeSectionSize(); } uint64_t X86_64GNULDBackend::emitGOTSectionData(MemoryRegion& pRegion) const { assert(m_pGOT && "emitGOTSectionData failed, m_pGOT is NULL!"); uint64_t* buffer = reinterpret_cast<uint64_t*>(pRegion.begin()); X86_64GOTEntry* got = 0; unsigned int EntrySize = X86_64GOTEntry::EntrySize; uint64_t RegionSize = 0; for (X86_64GOT::iterator it = m_pGOT->begin(), ie = m_pGOT->end(); it != ie; ++it, ++buffer) { got = &(llvm::cast<X86_64GOTEntry>((*it))); *buffer = static_cast<uint64_t>(got->getValue()); RegionSize += EntrySize; } return RegionSize; } uint64_t X86_64GNULDBackend::emitGOTPLTSectionData(MemoryRegion& pRegion, const ELFFileFormat* FileFormat) const { assert(m_pGOTPLT && "emitGOTPLTSectionData failed, m_pGOTPLT is NULL!"); m_pGOTPLT->applyGOT0(FileFormat->getDynamic().addr()); m_pGOTPLT->applyAllGOTPLT(*m_pPLT); uint64_t* buffer = reinterpret_cast<uint64_t*>(pRegion.begin()); X86_64GOTEntry* got = 0; unsigned int EntrySize = X86_64GOTEntry::EntrySize; uint64_t RegionSize = 0; for (X86_64GOTPLT::iterator it = m_pGOTPLT->begin(), ie = m_pGOTPLT->end(); it != ie; ++it, ++buffer) { got = &(llvm::cast<X86_64GOTEntry>((*it))); *buffer = static_cast<uint64_t>(got->getValue()); RegionSize += EntrySize; } return RegionSize; } namespace mcld { //===----------------------------------------------------------------------===// /// createX86LDBackend - the help funtion to create corresponding X86LDBackend /// TargetLDBackend* createX86LDBackend(const LinkerConfig& pConfig) { if (pConfig.targets().triple().isOSDarwin()) { assert(0 && "MachO linker is not supported yet"); /** return new X86MachOLDBackend(createX86MachOArchiveReader, createX86MachOObjectReader, createX86MachOObjectWriter); **/ } if (pConfig.targets().triple().isOSWindows()) { assert(0 && "COFF linker is not supported yet"); /** return new X86COFFLDBackend(createX86COFFArchiveReader, createX86COFFObjectReader, createX86COFFObjectWriter); **/ } llvm::Triple::ArchType arch = pConfig.targets().triple().getArch(); if (arch == llvm::Triple::x86) return new X86_32GNULDBackend(pConfig, new X86_32GNUInfo(pConfig.targets().triple())); assert (arch == llvm::Triple::x86_64); return new X86_64GNULDBackend(pConfig, new X86_64GNUInfo(pConfig.targets().triple())); } } // namespace of mcld //===----------------------------------------------------------------------===// // Force static initialization. //===----------------------------------------------------------------------===// extern "C" void MCLDInitializeX86LDBackend() { // Register the linker backend mcld::TargetRegistry::RegisterTargetLDBackend(TheX86_32Target, createX86LDBackend); mcld::TargetRegistry::RegisterTargetLDBackend(TheX86_64Target, createX86LDBackend); }