blob: 5d1355747228d4399f3829db4b71c1db753b3bcd [file] [log] [blame]
//===-- NativeRegisterContextLinux_s390x.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
//
//===----------------------------------------------------------------------===//
#if defined(__s390x__) && defined(__linux__)
#include "NativeRegisterContextLinux_s390x.h"
#include "Plugins/Process/Linux/NativeProcessLinux.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Utility/DataBufferHeap.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/RegisterValue.h"
#include "lldb/Utility/Status.h"
#include "Plugins/Process/Utility/RegisterContextLinux_s390x.h"
#include <asm/ptrace.h>
#include <linux/uio.h>
#include <sys/ptrace.h>
using namespace lldb_private;
using namespace lldb_private::process_linux;
// Private namespace.
namespace {
// s390x 64-bit general purpose registers.
static const uint32_t g_gpr_regnums_s390x[] = {
lldb_r0_s390x, lldb_r1_s390x, lldb_r2_s390x, lldb_r3_s390x,
lldb_r4_s390x, lldb_r5_s390x, lldb_r6_s390x, lldb_r7_s390x,
lldb_r8_s390x, lldb_r9_s390x, lldb_r10_s390x, lldb_r11_s390x,
lldb_r12_s390x, lldb_r13_s390x, lldb_r14_s390x, lldb_r15_s390x,
lldb_acr0_s390x, lldb_acr1_s390x, lldb_acr2_s390x, lldb_acr3_s390x,
lldb_acr4_s390x, lldb_acr5_s390x, lldb_acr6_s390x, lldb_acr7_s390x,
lldb_acr8_s390x, lldb_acr9_s390x, lldb_acr10_s390x, lldb_acr11_s390x,
lldb_acr12_s390x, lldb_acr13_s390x, lldb_acr14_s390x, lldb_acr15_s390x,
lldb_pswm_s390x, lldb_pswa_s390x,
LLDB_INVALID_REGNUM // register sets need to end with this flag
};
static_assert((sizeof(g_gpr_regnums_s390x) / sizeof(g_gpr_regnums_s390x[0])) -
1 ==
k_num_gpr_registers_s390x,
"g_gpr_regnums_s390x has wrong number of register infos");
// s390x 64-bit floating point registers.
static const uint32_t g_fpu_regnums_s390x[] = {
lldb_f0_s390x, lldb_f1_s390x, lldb_f2_s390x, lldb_f3_s390x,
lldb_f4_s390x, lldb_f5_s390x, lldb_f6_s390x, lldb_f7_s390x,
lldb_f8_s390x, lldb_f9_s390x, lldb_f10_s390x, lldb_f11_s390x,
lldb_f12_s390x, lldb_f13_s390x, lldb_f14_s390x, lldb_f15_s390x,
lldb_fpc_s390x,
LLDB_INVALID_REGNUM // register sets need to end with this flag
};
static_assert((sizeof(g_fpu_regnums_s390x) / sizeof(g_fpu_regnums_s390x[0])) -
1 ==
k_num_fpr_registers_s390x,
"g_fpu_regnums_s390x has wrong number of register infos");
// s390x Linux operating-system information.
static const uint32_t g_linux_regnums_s390x[] = {
lldb_orig_r2_s390x, lldb_last_break_s390x, lldb_system_call_s390x,
LLDB_INVALID_REGNUM // register sets need to end with this flag
};
static_assert((sizeof(g_linux_regnums_s390x) /
sizeof(g_linux_regnums_s390x[0])) -
1 ==
k_num_linux_registers_s390x,
"g_linux_regnums_s390x has wrong number of register infos");
// Number of register sets provided by this context.
enum { k_num_register_sets = 3 };
// Register sets for s390x 64-bit.
static const RegisterSet g_reg_sets_s390x[k_num_register_sets] = {
{"General Purpose Registers", "gpr", k_num_gpr_registers_s390x,
g_gpr_regnums_s390x},
{"Floating Point Registers", "fpr", k_num_fpr_registers_s390x,
g_fpu_regnums_s390x},
{"Linux Operating System Data", "linux", k_num_linux_registers_s390x,
g_linux_regnums_s390x},
};
}
#define REG_CONTEXT_SIZE (sizeof(s390_regs) + sizeof(s390_fp_regs) + 4)
// Required ptrace defines.
#define NT_S390_LAST_BREAK 0x306 /* s390 breaking event address */
#define NT_S390_SYSTEM_CALL 0x307 /* s390 system call restart data */
std::unique_ptr<NativeRegisterContextLinux>
NativeRegisterContextLinux::CreateHostNativeRegisterContextLinux(
const ArchSpec &target_arch, NativeThreadProtocol &native_thread) {
return llvm::make_unique<NativeRegisterContextLinux_s390x>(target_arch,
native_thread);
}
// NativeRegisterContextLinux_s390x members.
static RegisterInfoInterface *
CreateRegisterInfoInterface(const ArchSpec &target_arch) {
assert((HostInfo::GetArchitecture().GetAddressByteSize() == 8) &&
"Register setting path assumes this is a 64-bit host");
return new RegisterContextLinux_s390x(target_arch);
}
NativeRegisterContextLinux_s390x::NativeRegisterContextLinux_s390x(
const ArchSpec &target_arch, NativeThreadProtocol &native_thread)
: NativeRegisterContextLinux(native_thread,
CreateRegisterInfoInterface(target_arch)) {
// Set up data about ranges of valid registers.
switch (target_arch.GetMachine()) {
case llvm::Triple::systemz:
m_reg_info.num_registers = k_num_registers_s390x;
m_reg_info.num_gpr_registers = k_num_gpr_registers_s390x;
m_reg_info.num_fpr_registers = k_num_fpr_registers_s390x;
m_reg_info.last_gpr = k_last_gpr_s390x;
m_reg_info.first_fpr = k_first_fpr_s390x;
m_reg_info.last_fpr = k_last_fpr_s390x;
break;
default:
assert(false && "Unhandled target architecture.");
break;
}
// Clear out the watchpoint state.
m_watchpoint_addr = LLDB_INVALID_ADDRESS;
}
uint32_t NativeRegisterContextLinux_s390x::GetRegisterSetCount() const {
uint32_t sets = 0;
for (uint32_t set_index = 0; set_index < k_num_register_sets; ++set_index) {
if (IsRegisterSetAvailable(set_index))
++sets;
}
return sets;
}
uint32_t NativeRegisterContextLinux_s390x::GetUserRegisterCount() const {
uint32_t count = 0;
for (uint32_t set_index = 0; set_index < k_num_register_sets; ++set_index) {
const RegisterSet *set = GetRegisterSet(set_index);
if (set)
count += set->num_registers;
}
return count;
}
const RegisterSet *
NativeRegisterContextLinux_s390x::GetRegisterSet(uint32_t set_index) const {
if (!IsRegisterSetAvailable(set_index))
return nullptr;
switch (GetRegisterInfoInterface().GetTargetArchitecture().GetMachine()) {
case llvm::Triple::systemz:
return &g_reg_sets_s390x[set_index];
default:
assert(false && "Unhandled target architecture.");
return nullptr;
}
return nullptr;
}
bool NativeRegisterContextLinux_s390x::IsRegisterSetAvailable(
uint32_t set_index) const {
return set_index < k_num_register_sets;
}
bool NativeRegisterContextLinux_s390x::IsGPR(uint32_t reg_index) const {
// GPRs come first. "orig_r2" counts as GPR since it is part of the GPR
// register area.
return reg_index <= m_reg_info.last_gpr || reg_index == lldb_orig_r2_s390x;
}
bool NativeRegisterContextLinux_s390x::IsFPR(uint32_t reg_index) const {
return (m_reg_info.first_fpr <= reg_index &&
reg_index <= m_reg_info.last_fpr);
}
Status
NativeRegisterContextLinux_s390x::ReadRegister(const RegisterInfo *reg_info,
RegisterValue &reg_value) {
if (!reg_info)
return Status("reg_info NULL");
const uint32_t reg = reg_info->kinds[lldb::eRegisterKindLLDB];
if (reg == LLDB_INVALID_REGNUM)
return Status("register \"%s\" is an internal-only lldb register, cannot "
"read directly",
reg_info->name);
if (IsGPR(reg)) {
s390_regs regs;
Status error = DoReadGPR(&regs, sizeof(regs));
if (error.Fail())
return error;
uint8_t *src = (uint8_t *)&regs + reg_info->byte_offset;
assert(reg_info->byte_offset + reg_info->byte_size <= sizeof(regs));
switch (reg_info->byte_size) {
case 4:
reg_value.SetUInt32(*(uint32_t *)src);
break;
case 8:
reg_value.SetUInt64(*(uint64_t *)src);
break;
default:
assert(false && "Unhandled data size.");
return Status("unhandled byte size: %" PRIu32, reg_info->byte_size);
}
return Status();
}
if (IsFPR(reg)) {
s390_fp_regs fp_regs;
Status error = DoReadFPR(&fp_regs, sizeof(fp_regs));
if (error.Fail())
return error;
// byte_offset is just the offset within FPR, not the whole user area.
uint8_t *src = (uint8_t *)&fp_regs + reg_info->byte_offset;
assert(reg_info->byte_offset + reg_info->byte_size <= sizeof(fp_regs));
switch (reg_info->byte_size) {
case 4:
reg_value.SetUInt32(*(uint32_t *)src);
break;
case 8:
reg_value.SetUInt64(*(uint64_t *)src);
break;
default:
assert(false && "Unhandled data size.");
return Status("unhandled byte size: %" PRIu32, reg_info->byte_size);
}
return Status();
}
if (reg == lldb_last_break_s390x) {
uint64_t last_break;
Status error = DoReadRegisterSet(NT_S390_LAST_BREAK, &last_break, 8);
if (error.Fail())
return error;
reg_value.SetUInt64(last_break);
return Status();
}
if (reg == lldb_system_call_s390x) {
uint32_t system_call;
Status error = DoReadRegisterSet(NT_S390_SYSTEM_CALL, &system_call, 4);
if (error.Fail())
return error;
reg_value.SetUInt32(system_call);
return Status();
}
return Status("failed - register wasn't recognized");
}
Status NativeRegisterContextLinux_s390x::WriteRegister(
const RegisterInfo *reg_info, const RegisterValue &reg_value) {
if (!reg_info)
return Status("reg_info NULL");
const uint32_t reg = reg_info->kinds[lldb::eRegisterKindLLDB];
if (reg == LLDB_INVALID_REGNUM)
return Status("register \"%s\" is an internal-only lldb register, cannot "
"write directly",
reg_info->name);
if (IsGPR(reg)) {
s390_regs regs;
Status error = DoReadGPR(&regs, sizeof(regs));
if (error.Fail())
return error;
uint8_t *dst = (uint8_t *)&regs + reg_info->byte_offset;
assert(reg_info->byte_offset + reg_info->byte_size <= sizeof(regs));
switch (reg_info->byte_size) {
case 4:
*(uint32_t *)dst = reg_value.GetAsUInt32();
break;
case 8:
*(uint64_t *)dst = reg_value.GetAsUInt64();
break;
default:
assert(false && "Unhandled data size.");
return Status("unhandled byte size: %" PRIu32, reg_info->byte_size);
}
return DoWriteGPR(&regs, sizeof(regs));
}
if (IsFPR(reg)) {
s390_fp_regs fp_regs;
Status error = DoReadFPR(&fp_regs, sizeof(fp_regs));
if (error.Fail())
return error;
// byte_offset is just the offset within fp_regs, not the whole user area.
uint8_t *dst = (uint8_t *)&fp_regs + reg_info->byte_offset;
assert(reg_info->byte_offset + reg_info->byte_size <= sizeof(fp_regs));
switch (reg_info->byte_size) {
case 4:
*(uint32_t *)dst = reg_value.GetAsUInt32();
break;
case 8:
*(uint64_t *)dst = reg_value.GetAsUInt64();
break;
default:
assert(false && "Unhandled data size.");
return Status("unhandled byte size: %" PRIu32, reg_info->byte_size);
}
return DoWriteFPR(&fp_regs, sizeof(fp_regs));
}
if (reg == lldb_last_break_s390x) {
return Status("The last break address is read-only");
}
if (reg == lldb_system_call_s390x) {
uint32_t system_call = reg_value.GetAsUInt32();
return DoWriteRegisterSet(NT_S390_SYSTEM_CALL, &system_call, 4);
}
return Status("failed - register wasn't recognized");
}
Status NativeRegisterContextLinux_s390x::ReadAllRegisterValues(
lldb::DataBufferSP &data_sp) {
Status error;
data_sp.reset(new DataBufferHeap(REG_CONTEXT_SIZE, 0));
if (!data_sp) {
error.SetErrorStringWithFormat(
"failed to allocate DataBufferHeap instance of size %" PRIu64,
REG_CONTEXT_SIZE);
return error;
}
uint8_t *dst = data_sp->GetBytes();
if (dst == nullptr) {
error.SetErrorStringWithFormat("DataBufferHeap instance of size %" PRIu64
" returned a null pointer",
REG_CONTEXT_SIZE);
return error;
}
error = DoReadGPR(dst, sizeof(s390_regs));
dst += sizeof(s390_regs);
if (error.Fail())
return error;
error = DoReadFPR(dst, sizeof(s390_fp_regs));
dst += sizeof(s390_fp_regs);
if (error.Fail())
return error;
// Ignore errors if the regset is unsupported (happens on older kernels).
DoReadRegisterSet(NT_S390_SYSTEM_CALL, dst, 4);
dst += 4;
// To enable inferior function calls while the process is stopped in an
// interrupted system call, we need to clear the system call flag. It will be
// restored to its original value by WriteAllRegisterValues. Again we ignore
// error if the regset is unsupported.
uint32_t system_call = 0;
DoWriteRegisterSet(NT_S390_SYSTEM_CALL, &system_call, 4);
return error;
}
Status NativeRegisterContextLinux_s390x::WriteAllRegisterValues(
const lldb::DataBufferSP &data_sp) {
Status error;
if (!data_sp) {
error.SetErrorStringWithFormat(
"NativeRegisterContextLinux_s390x::%s invalid data_sp provided",
__FUNCTION__);
return error;
}
if (data_sp->GetByteSize() != REG_CONTEXT_SIZE) {
error.SetErrorStringWithFormat(
"NativeRegisterContextLinux_s390x::%s data_sp contained mismatched "
"data size, expected %" PRIu64 ", actual %" PRIu64,
__FUNCTION__, REG_CONTEXT_SIZE, data_sp->GetByteSize());
return error;
}
uint8_t *src = data_sp->GetBytes();
if (src == nullptr) {
error.SetErrorStringWithFormat("NativeRegisterContextLinux_s390x::%s "
"DataBuffer::GetBytes() returned a null "
"pointer",
__FUNCTION__);
return error;
}
error = DoWriteGPR(src, sizeof(s390_regs));
src += sizeof(s390_regs);
if (error.Fail())
return error;
error = DoWriteFPR(src, sizeof(s390_fp_regs));
src += sizeof(s390_fp_regs);
if (error.Fail())
return error;
// Ignore errors if the regset is unsupported (happens on older kernels).
DoWriteRegisterSet(NT_S390_SYSTEM_CALL, src, 4);
src += 4;
return error;
}
Status NativeRegisterContextLinux_s390x::DoReadRegisterValue(
uint32_t offset, const char *reg_name, uint32_t size,
RegisterValue &value) {
return Status("DoReadRegisterValue unsupported");
}
Status NativeRegisterContextLinux_s390x::DoWriteRegisterValue(
uint32_t offset, const char *reg_name, const RegisterValue &value) {
return Status("DoWriteRegisterValue unsupported");
}
Status NativeRegisterContextLinux_s390x::PeekUserArea(uint32_t offset,
void *buf,
size_t buf_size) {
ptrace_area parea;
parea.len = buf_size;
parea.process_addr = (addr_t)buf;
parea.kernel_addr = offset;
return NativeProcessLinux::PtraceWrapper(PTRACE_PEEKUSR_AREA,
m_thread.GetID(), &parea);
}
Status NativeRegisterContextLinux_s390x::PokeUserArea(uint32_t offset,
const void *buf,
size_t buf_size) {
ptrace_area parea;
parea.len = buf_size;
parea.process_addr = (addr_t)buf;
parea.kernel_addr = offset;
return NativeProcessLinux::PtraceWrapper(PTRACE_POKEUSR_AREA,
m_thread.GetID(), &parea);
}
Status NativeRegisterContextLinux_s390x::DoReadGPR(void *buf, size_t buf_size) {
assert(buf_size == sizeof(s390_regs));
return PeekUserArea(offsetof(user_regs_struct, psw), buf, buf_size);
}
Status NativeRegisterContextLinux_s390x::DoWriteGPR(void *buf,
size_t buf_size) {
assert(buf_size == sizeof(s390_regs));
return PokeUserArea(offsetof(user_regs_struct, psw), buf, buf_size);
}
Status NativeRegisterContextLinux_s390x::DoReadFPR(void *buf, size_t buf_size) {
assert(buf_size == sizeof(s390_fp_regs));
return PeekUserArea(offsetof(user_regs_struct, fp_regs), buf, buf_size);
}
Status NativeRegisterContextLinux_s390x::DoWriteFPR(void *buf,
size_t buf_size) {
assert(buf_size == sizeof(s390_fp_regs));
return PokeUserArea(offsetof(user_regs_struct, fp_regs), buf, buf_size);
}
Status NativeRegisterContextLinux_s390x::DoReadRegisterSet(uint32_t regset,
void *buf,
size_t buf_size) {
struct iovec iov;
iov.iov_base = buf;
iov.iov_len = buf_size;
return ReadRegisterSet(&iov, buf_size, regset);
}
Status NativeRegisterContextLinux_s390x::DoWriteRegisterSet(uint32_t regset,
const void *buf,
size_t buf_size) {
struct iovec iov;
iov.iov_base = const_cast<void *>(buf);
iov.iov_len = buf_size;
return WriteRegisterSet(&iov, buf_size, regset);
}
Status NativeRegisterContextLinux_s390x::IsWatchpointHit(uint32_t wp_index,
bool &is_hit) {
per_lowcore_bits per_lowcore;
if (wp_index >= NumSupportedHardwareWatchpoints())
return Status("Watchpoint index out of range");
if (m_watchpoint_addr == LLDB_INVALID_ADDRESS) {
is_hit = false;
return Status();
}
Status error = PeekUserArea(offsetof(user_regs_struct, per_info.lowcore),
&per_lowcore, sizeof(per_lowcore));
if (error.Fail()) {
is_hit = false;
return error;
}
is_hit = (per_lowcore.perc_storage_alteration == 1 &&
per_lowcore.perc_store_real_address == 0);
if (is_hit) {
// Do not report this watchpoint again.
memset(&per_lowcore, 0, sizeof(per_lowcore));
PokeUserArea(offsetof(user_regs_struct, per_info.lowcore), &per_lowcore,
sizeof(per_lowcore));
}
return Status();
}
Status NativeRegisterContextLinux_s390x::GetWatchpointHitIndex(
uint32_t &wp_index, lldb::addr_t trap_addr) {
uint32_t num_hw_wps = NumSupportedHardwareWatchpoints();
for (wp_index = 0; wp_index < num_hw_wps; ++wp_index) {
bool is_hit;
Status error = IsWatchpointHit(wp_index, is_hit);
if (error.Fail()) {
wp_index = LLDB_INVALID_INDEX32;
return error;
} else if (is_hit) {
return error;
}
}
wp_index = LLDB_INVALID_INDEX32;
return Status();
}
Status NativeRegisterContextLinux_s390x::IsWatchpointVacant(uint32_t wp_index,
bool &is_vacant) {
if (wp_index >= NumSupportedHardwareWatchpoints())
return Status("Watchpoint index out of range");
is_vacant = m_watchpoint_addr == LLDB_INVALID_ADDRESS;
return Status();
}
bool NativeRegisterContextLinux_s390x::ClearHardwareWatchpoint(
uint32_t wp_index) {
per_struct per_info;
if (wp_index >= NumSupportedHardwareWatchpoints())
return false;
Status error = PeekUserArea(offsetof(user_regs_struct, per_info), &per_info,
sizeof(per_info));
if (error.Fail())
return false;
per_info.control_regs.bits.em_storage_alteration = 0;
per_info.control_regs.bits.storage_alt_space_ctl = 0;
per_info.starting_addr = 0;
per_info.ending_addr = 0;
error = PokeUserArea(offsetof(user_regs_struct, per_info), &per_info,
sizeof(per_info));
if (error.Fail())
return false;
m_watchpoint_addr = LLDB_INVALID_ADDRESS;
return true;
}
Status NativeRegisterContextLinux_s390x::ClearAllHardwareWatchpoints() {
if (ClearHardwareWatchpoint(0))
return Status();
return Status("Clearing all hardware watchpoints failed.");
}
uint32_t NativeRegisterContextLinux_s390x::SetHardwareWatchpoint(
lldb::addr_t addr, size_t size, uint32_t watch_flags) {
per_struct per_info;
if (watch_flags != 0x1)
return LLDB_INVALID_INDEX32;
if (m_watchpoint_addr != LLDB_INVALID_ADDRESS)
return LLDB_INVALID_INDEX32;
Status error = PeekUserArea(offsetof(user_regs_struct, per_info), &per_info,
sizeof(per_info));
if (error.Fail())
return LLDB_INVALID_INDEX32;
per_info.control_regs.bits.em_storage_alteration = 1;
per_info.control_regs.bits.storage_alt_space_ctl = 1;
per_info.starting_addr = addr;
per_info.ending_addr = addr + size - 1;
error = PokeUserArea(offsetof(user_regs_struct, per_info), &per_info,
sizeof(per_info));
if (error.Fail())
return LLDB_INVALID_INDEX32;
m_watchpoint_addr = addr;
return 0;
}
lldb::addr_t
NativeRegisterContextLinux_s390x::GetWatchpointAddress(uint32_t wp_index) {
if (wp_index >= NumSupportedHardwareWatchpoints())
return LLDB_INVALID_ADDRESS;
return m_watchpoint_addr;
}
uint32_t NativeRegisterContextLinux_s390x::NumSupportedHardwareWatchpoints() {
return 1;
}
#endif // defined(__s390x__) && defined(__linux__)