//===-- cli-wrapper-mpxtable.cpp --------------------------------*- C++ -*-===// // // 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 // //===----------------------------------------------------------------------===// // C++ includes #include #include #include "cli-wrapper-mpxtable.h" #include "lldb/API/SBCommandInterpreter.h" #include "lldb/API/SBCommandReturnObject.h" #include "lldb/API/SBMemoryRegionInfo.h" #include "lldb/API/SBProcess.h" #include "lldb/API/SBTarget.h" #include "lldb/API/SBThread.h" #include "llvm/ADT/Triple.h" #include "llvm/ADT/Twine.h" static bool GetPtr(char *cptr, uint64_t &ptr, lldb::SBFrame &frame, lldb::SBCommandReturnObject &result) { if (!cptr) { result.SetError("Bad argument."); result.SetStatus(lldb::eReturnStatusFailed); return false; } lldb::SBValue ptr_addr = frame.GetValueForVariablePath(cptr); if (!ptr_addr.IsValid()) { result.SetError("Invalid pointer."); result.SetStatus(lldb::eReturnStatusFailed); return false; } ptr = ptr_addr.GetLoadAddress(); return true; } enum { mpx_base_mask_64 = ~(uint64_t)0xFFFULL, mpx_bd_mask_64 = 0xFFFFFFF00000ULL, bd_r_shift_64 = 20, bd_l_shift_64 = 3, bt_r_shift_64 = 3, bt_l_shift_64 = 5, bt_mask_64 = 0x0000000FFFF8ULL, mpx_base_mask_32 = 0xFFFFFFFFFFFFF000ULL, mpx_bd_mask_32 = 0xFFFFF000ULL, bd_r_shift_32 = 12, bd_l_shift_32 = 2, bt_r_shift_32 = 2, bt_l_shift_32 = 4, bt_mask_32 = 0x00000FFCULL, }; static void PrintBTEntry(lldb::addr_t lbound, lldb::addr_t ubound, uint64_t value, uint64_t meta, lldb::SBCommandReturnObject &result) { const lldb::addr_t one_cmpl64 = ~((lldb::addr_t)0); const lldb::addr_t one_cmpl32 = ~((uint32_t)0); if ((lbound == one_cmpl64 || one_cmpl32) && ubound == 0) { result.Printf("Null bounds on map: pointer value = 0x%lx\n", value); } else { result.Printf(" lbound = 0x%lx,", lbound); result.Printf(" ubound = 0x%lx", ubound); result.Printf(" (pointer value = 0x%lx,", value); result.Printf(" metadata = 0x%lx)\n", meta); } } static bool GetBTEntryAddr(uint64_t bndcfgu, uint64_t ptr, lldb::SBTarget &target, llvm::Triple::ArchType arch, size_t &size, lldb::addr_t &bt_entry_addr, lldb::SBCommandReturnObject &result, lldb::SBError &error) { lldb::addr_t mpx_base_mask; lldb::addr_t mpx_bd_mask; lldb::addr_t bd_r_shift; lldb::addr_t bd_l_shift; lldb::addr_t bt_r_shift; lldb::addr_t bt_l_shift; lldb::addr_t bt_mask; if (arch == llvm::Triple::ArchType::x86_64) { mpx_base_mask = mpx_base_mask_64; mpx_bd_mask = mpx_bd_mask_64; bd_r_shift = bd_r_shift_64; bd_l_shift = bd_l_shift_64; bt_r_shift = bt_r_shift_64; bt_l_shift = bt_l_shift_64; bt_mask = bt_mask_64; } else if (arch == llvm::Triple::ArchType::x86) { mpx_base_mask = mpx_base_mask_32; mpx_bd_mask = mpx_bd_mask_32; bd_r_shift = bd_r_shift_32; bd_l_shift = bd_l_shift_32; bt_r_shift = bt_r_shift_32; bt_l_shift = bt_l_shift_32; bt_mask = bt_mask_32; } else { result.SetError("Invalid arch."); result.SetStatus(lldb::eReturnStatusFailed); return false; } size = target.GetAddressByteSize(); lldb::addr_t mpx_bd_base = bndcfgu & mpx_base_mask; lldb::addr_t bd_entry_offset = ((ptr & mpx_bd_mask) >> bd_r_shift) << bd_l_shift; lldb::addr_t bd_entry_addr = mpx_bd_base + bd_entry_offset; std::vector bd_entry_v(size); size_t ret = target.GetProcess().ReadMemory( bd_entry_addr, static_cast(bd_entry_v.data()), size, error); if (ret != size || !error.Success()) { result.SetError("Failed access to BD entry."); return false; } lldb::SBData data; data.SetData(error, bd_entry_v.data(), bd_entry_v.size(), target.GetByteOrder(), size); lldb::addr_t bd_entry = data.GetAddress(error, 0); if (!error.Success()) { result.SetError("Failed access to BD entry."); return false; } if ((bd_entry & 0x01) == 0) { result.SetError("Invalid bound directory."); result.SetStatus(lldb::eReturnStatusFailed); return false; } // Clear status bit. // bd_entry--; lldb::addr_t bt_addr = bd_entry & ~bt_r_shift; lldb::addr_t bt_entry_offset = ((ptr & bt_mask) >> bt_r_shift) << bt_l_shift; bt_entry_addr = bt_addr + bt_entry_offset; return true; } static bool GetBTEntry(uint64_t bndcfgu, uint64_t ptr, lldb::SBTarget &target, llvm::Triple::ArchType arch, lldb::SBCommandReturnObject &result, lldb::SBError &error) { lldb::addr_t bt_entry_addr; size_t size; if (!GetBTEntryAddr(bndcfgu, ptr, target, arch, size, bt_entry_addr, result, error)) return false; // bt_entry_v must have space to store the 4 elements of the BT entry (lower // boundary, // upper boundary, pointer value and meta data), which all have the same size // 'size'. // std::vector bt_entry_v(size * 4); size_t ret = target.GetProcess().ReadMemory( bt_entry_addr, static_cast(bt_entry_v.data()), size * 4, error); if ((ret != (size * 4)) || !error.Success()) { result.SetError("Unsuccessful. Failed access to BT entry."); result.SetStatus(lldb::eReturnStatusFailed); return false; } lldb::addr_t lbound; lldb::addr_t ubound; uint64_t value; uint64_t meta; lldb::SBData data; data.SetData(error, bt_entry_v.data(), bt_entry_v.size(), target.GetByteOrder(), size); lbound = data.GetAddress(error, size * 0); ubound = data.GetAddress(error, size * 1); value = data.GetAddress(error, size * 2); meta = data.GetAddress(error, size * 3); // ubound is stored as one's complement. if (arch == llvm::Triple::ArchType::x86) { ubound = (~ubound) & 0x00000000FFFFFFFF; } else { ubound = ~ubound; } if (!error.Success()) { result.SetError("Failed access to BT entry."); return false; } PrintBTEntry(lbound, ubound, value, meta, result); result.SetStatus(lldb::eReturnStatusSuccessFinishResult); return true; } static std::vector uIntToU8(uint64_t input, size_t size) { std::vector output; for (size_t i = 0; i < size; i++) output.push_back( static_cast((input & (0xFFULL << (i * 8))) >> (i * 8))); return output; } static bool SetBTEntry(uint64_t bndcfgu, uint64_t ptr, lldb::addr_t lbound, lldb::addr_t ubound, lldb::SBTarget &target, llvm::Triple::ArchType arch, lldb::SBCommandReturnObject &result, lldb::SBError &error) { lldb::addr_t bt_entry_addr; size_t size; if (!GetBTEntryAddr(bndcfgu, ptr, target, arch, size, bt_entry_addr, result, error)) return false; // bt_entry_v must have space to store only 2 elements of the BT Entry, the // lower boundary and the upper boundary, which both have size 'size'. // std::vector bt_entry_v(size * 2); std::vector lbound_v = uIntToU8(lbound, size); bt_entry_v.insert(bt_entry_v.begin(), lbound_v.begin(), lbound_v.end()); std::vector ubound_v = uIntToU8(~ubound, size); bt_entry_v.insert(bt_entry_v.begin() + size, ubound_v.begin(), ubound_v.end()); size_t ret = target.GetProcess().WriteMemory( bt_entry_addr, (void *)(bt_entry_v.data()), size * 2, error); if ((ret != (size * 2)) || !error.Success()) { result.SetError("Failed access to BT entry."); result.SetStatus(lldb::eReturnStatusFailed); return false; } result.SetStatus(lldb::eReturnStatusSuccessFinishResult); return true; } static bool GetInitInfo(lldb::SBDebugger debugger, lldb::SBTarget &target, llvm::Triple::ArchType &arch, uint64_t &bndcfgu, char *arg, uint64_t &ptr, lldb::SBCommandReturnObject &result, lldb::SBError &error) { target = debugger.GetSelectedTarget(); if (!target.IsValid()) { result.SetError("Invalid target."); result.SetStatus(lldb::eReturnStatusFailed); return false; } const std::string triple_s(target.GetTriple()); const llvm::Triple triple(triple_s); arch = triple.getArch(); if ((arch != llvm::Triple::ArchType::x86) && (arch != llvm::Triple::ArchType::x86_64)) { result.SetError("Platform not supported."); result.SetStatus(lldb::eReturnStatusFailed); return false; } lldb::SBFrame frame = target.GetProcess().GetSelectedThread().GetSelectedFrame(); if (!frame.IsValid()) { result.SetError("No valid process, thread or frame."); result.SetStatus(lldb::eReturnStatusFailed); return false; } lldb::SBValue bndcfgu_val = frame.FindRegister("bndcfgu"); if (!bndcfgu_val.IsValid()) { result.SetError("Cannot access register BNDCFGU. Does the target support " "Intel(R) Memory Protection Extensions (Intel(R) MPX)?"); result.SetStatus(lldb::eReturnStatusFailed); return false; } lldb::SBData bndcfgu_data = bndcfgu_val.GetData(); bndcfgu = bndcfgu_data.GetUnsignedInt64(error, 0); if (!error.Success()) { result.SetError(error, "Invalid read of register BNDCFGU."); return false; } if (!GetPtr(arg, ptr, frame, result)) return false; return true; } class MPXTableShow : public lldb::SBCommandPluginInterface { public: bool DoExecute(lldb::SBDebugger debugger, char **command, lldb::SBCommandReturnObject &result) override { if (command) { int arg_c = 0; char *arg; while (*command) { if (arg_c >= 1) { result.SetError("Too many arguments. See help."); result.SetStatus(lldb::eReturnStatusFailed); return false; } arg_c++; arg = *command; command++; } if (!debugger.IsValid()) { result.SetError("Invalid debugger."); result.SetStatus(lldb::eReturnStatusFailed); return false; } lldb::SBTarget target; llvm::Triple::ArchType arch; lldb::SBError error; uint64_t bndcfgu; uint64_t ptr; if (!GetInitInfo(debugger, target, arch, bndcfgu, arg, ptr, result, error)) return false; return GetBTEntry(bndcfgu, ptr, target, arch, result, error); } result.SetError("Too few arguments. See help."); result.SetStatus(lldb::eReturnStatusFailed); return false; } }; class MPXTableSet : public lldb::SBCommandPluginInterface { public: bool DoExecute(lldb::SBDebugger debugger, char **command, lldb::SBCommandReturnObject &result) override { if (command) { int arg_c = 0; char *arg[3]; while (*command) { arg[arg_c] = *command; command++; arg_c++; } if (arg_c != 3) { result.SetError("Wrong arguments. See help."); return false; } if (!debugger.IsValid()) { result.SetError("Invalid debugger."); return false; } lldb::SBTarget target; llvm::Triple::ArchType arch; lldb::SBError error; uint64_t bndcfgu; uint64_t ptr; if (!GetInitInfo(debugger, target, arch, bndcfgu, arg[0], ptr, result, error)) return false; char *endptr; errno = 0; uint64_t lbound = std::strtoul(arg[1], &endptr, 16); if (endptr == arg[1] || errno == ERANGE) { result.SetError("Lower Bound: bad argument format."); errno = 0; return false; } uint64_t ubound = std::strtoul(arg[2], &endptr, 16); if (endptr == arg[1] || errno == ERANGE) { result.SetError("Upper Bound: bad argument format."); errno = 0; return false; } return SetBTEntry(bndcfgu, ptr, lbound, ubound, target, arch, result, error); } result.SetError("Too few arguments. See help."); return false; } }; bool MPXPluginInitialize(lldb::SBDebugger &debugger) { lldb::SBCommandInterpreter interpreter = debugger.GetCommandInterpreter(); lldb::SBCommand mpxTable = interpreter.AddMultiwordCommand( "mpx-table", "A utility to access the Intel(R) MPX table entries."); const char *mpx_show_help = "Show the Intel(R) MPX table entry of a pointer." "\nmpx-table show "; mpxTable.AddCommand("show", new MPXTableShow(), mpx_show_help); const char *mpx_set_help = "Set the Intel(R) MPX table entry of a pointer.\n" "mpx-table set "; mpxTable.AddCommand("set", new MPXTableSet(), mpx_set_help); return true; }