Skip to content

Commit

Permalink
[lldb][AArch64] Linux corefile support for SME
Browse files Browse the repository at this point in the history
This adds the ability to read streaming SVE registers,
ZA, SVCR and SVG from core files.

Streaming SVE is in a new note NT_ARM_SSVE but otherwise
has the same format as SVE. So I've done the same as I
did for live processes and reused the existing SVE state
with an extra state for the mode variable.

ZA is in a note NT_ARM_ZA and again the handling matches
live processes. Except that it gets setup only once. A
disabled ZA reads as 0s as usual.

SVCR and SVG are pseudo registers, generated from the notes.

An important detail is that the notes represent what
you would have got if you read from ptrace at the time of
the crash.

This means that for a corefile in non-streaming mode,
there is still an NT_ARM_SSVE note and we check the header
flags to tell if it is active. We cannot just say if you
have the note you're in streaming mode.

The kernel does not provide register values for the inactive
mode and even if it did, they would be undefined, so if we find
streaming state, we ignore the non-streaming state.

Same for ZA, a disabled ZA still has the header in the note.

The tests do not cover all combinations but enough different
vector lengths, modes and ZA states to be confident.

Reviewed By: omjavaid

Differential Revision: https://reviews.llvm.org/D158500
  • Loading branch information
DavidSpickett committed Sep 21, 2023
1 parent 7078993 commit 43812c8
Show file tree
Hide file tree
Showing 10 changed files with 383 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,18 @@ class RegisterContextPOSIX_arm64 : public lldb_private::RegisterContext {
size_t GetFPUSize() { return sizeof(RegisterInfoPOSIX_arm64::FPU); }

bool IsSVE(unsigned reg) const;
bool IsSME(unsigned reg) const;
bool IsPAuth(unsigned reg) const;
bool IsTLS(unsigned reg) const;
bool IsSME(unsigned reg) const;

bool IsSVEZ(unsigned reg) const { return m_register_info_up->IsSVEZReg(reg); }
bool IsSVEP(unsigned reg) const { return m_register_info_up->IsSVEPReg(reg); }
bool IsSVEVG(unsigned reg) const {
return m_register_info_up->IsSVERegVG(reg);
}
bool IsSMEZA(unsigned reg) const {
return m_register_info_up->IsSMERegZA(reg);
}

uint32_t GetRegNumSVEZ0() const {
return m_register_info_up->GetRegNumSVEZ0();
Expand Down
127 changes: 110 additions & 17 deletions lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ RegisterContextCorePOSIX_arm64::Create(Thread &thread, const ArchSpec &arch,
llvm::ArrayRef<CoreNote> notes) {
Flags opt_regsets = RegisterInfoPOSIX_arm64::eRegsetMaskDefault;

DataExtractor ssve_data =
getRegset(notes, arch.GetTriple(), AARCH64_SSVE_Desc);
if (ssve_data.GetByteSize() >= sizeof(sve::user_sve_header))
opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskSSVE);

DataExtractor sve_data = getRegset(notes, arch.GetTriple(), AARCH64_SVE_Desc);
if (sve_data.GetByteSize() > sizeof(sve::user_sve_header))
if (sve_data.GetByteSize() >= sizeof(sve::user_sve_header))
opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskSVE);

// Pointer Authentication register set data is based on struct
Expand All @@ -40,6 +45,11 @@ RegisterContextCorePOSIX_arm64::Create(Thread &thread, const ArchSpec &arch,
if (tls_data.GetByteSize() >= sizeof(uint64_t))
opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskTLS);

DataExtractor za_data = getRegset(notes, arch.GetTriple(), AARCH64_ZA_Desc);
// Nothing if ZA is not present, just the header if it is disabled.
if (za_data.GetByteSize() >= sizeof(sve::user_za_header))
opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskZA);

auto register_info_up =
std::make_unique<RegisterInfoPOSIX_arm64>(arch, opt_regsets);
return std::unique_ptr<RegisterContextCorePOSIX_arm64>(
Expand All @@ -51,6 +61,8 @@ RegisterContextCorePOSIX_arm64::RegisterContextCorePOSIX_arm64(
Thread &thread, std::unique_ptr<RegisterInfoPOSIX_arm64> register_info,
const DataExtractor &gpregset, llvm::ArrayRef<CoreNote> notes)
: RegisterContextPOSIX_arm64(thread, std::move(register_info)) {
::memset(&m_sme_pseudo_regs, 0, sizeof(m_sme_pseudo_regs));

m_gpr_data.SetData(std::make_shared<DataBufferHeap>(gpregset.GetDataStart(),
gpregset.GetByteSize()));
m_gpr_data.SetByteOrder(gpregset.GetByteOrder());
Expand All @@ -59,7 +71,15 @@ RegisterContextCorePOSIX_arm64::RegisterContextCorePOSIX_arm64(
m_register_info_up->GetTargetArchitecture().GetTriple();
m_fpr_data = getRegset(notes, target_triple, FPR_Desc);

if (m_register_info_up->IsSVEEnabled())
if (m_register_info_up->IsSSVEEnabled()) {
m_sve_data = getRegset(notes, target_triple, AARCH64_SSVE_Desc);
lldb::offset_t flags_offset = 12;
uint16_t flags = m_sve_data.GetU32(&flags_offset);
if ((flags & sve::ptrace_regs_mask) == sve::ptrace_regs_sve)
m_sve_state = SVEState::Streaming;
}

if (m_sve_state != SVEState::Streaming && m_register_info_up->IsSVEEnabled())
m_sve_data = getRegset(notes, target_triple, AARCH64_SVE_Desc);

if (m_register_info_up->IsPAuthEnabled())
Expand All @@ -68,6 +88,9 @@ RegisterContextCorePOSIX_arm64::RegisterContextCorePOSIX_arm64(
if (m_register_info_up->IsTLSEnabled())
m_tls_data = getRegset(notes, target_triple, AARCH64_TLS_Desc);

if (m_register_info_up->IsZAEnabled())
m_za_data = getRegset(notes, target_triple, AARCH64_ZA_Desc);

ConfigureRegisterContext();
}

Expand Down Expand Up @@ -95,15 +118,18 @@ void RegisterContextCorePOSIX_arm64::ConfigureRegisterContext() {
if (m_sve_data.GetByteSize() > sizeof(sve::user_sve_header)) {
uint64_t sve_header_field_offset = 8;
m_sve_vector_length = m_sve_data.GetU16(&sve_header_field_offset);
sve_header_field_offset = 12;
uint16_t sve_header_flags_field =
m_sve_data.GetU16(&sve_header_field_offset);
if ((sve_header_flags_field & sve::ptrace_regs_mask) ==
sve::ptrace_regs_fpsimd)
m_sve_state = SVEState::FPSIMD;
else if ((sve_header_flags_field & sve::ptrace_regs_mask) ==
sve::ptrace_regs_sve)
m_sve_state = SVEState::Full;

if (m_sve_state != SVEState::Streaming) {
sve_header_field_offset = 12;
uint16_t sve_header_flags_field =
m_sve_data.GetU16(&sve_header_field_offset);
if ((sve_header_flags_field & sve::ptrace_regs_mask) ==
sve::ptrace_regs_fpsimd)
m_sve_state = SVEState::FPSIMD;
else if ((sve_header_flags_field & sve::ptrace_regs_mask) ==
sve::ptrace_regs_sve)
m_sve_state = SVEState::Full;
}

if (!sve::vl_valid(m_sve_vector_length)) {
m_sve_state = SVEState::Disabled;
Expand All @@ -115,6 +141,23 @@ void RegisterContextCorePOSIX_arm64::ConfigureRegisterContext() {
if (m_sve_state != SVEState::Disabled)
m_register_info_up->ConfigureVectorLengthSVE(
sve::vq_from_vl(m_sve_vector_length));

if (m_sve_state == SVEState::Streaming)
m_sme_pseudo_regs.ctrl_reg |= 1;

if (m_za_data.GetByteSize() >= sizeof(sve::user_za_header)) {
lldb::offset_t vlen_offset = 8;
uint16_t svl = m_za_data.GetU16(&vlen_offset);
m_sme_pseudo_regs.svg_reg = svl / 8;
m_register_info_up->ConfigureVectorLengthZA(svl / 16);

// If there is register data then ZA is active. The size of the note may be
// misleading here so we use the size field of the embedded header.
lldb::offset_t size_offset = 0;
uint32_t size = m_za_data.GetU32(&size_offset);
if (size > sizeof(sve::user_za_header))
m_sme_pseudo_regs.ctrl_reg |= 1 << 1;
}
}

uint32_t RegisterContextCorePOSIX_arm64::CalculateSVEOffset(
Expand All @@ -124,7 +167,8 @@ uint32_t RegisterContextCorePOSIX_arm64::CalculateSVEOffset(
if (m_sve_state == SVEState::FPSIMD) {
const uint32_t reg = reg_info->kinds[lldb::eRegisterKindLLDB];
sve_reg_offset = sve::ptrace_fpsimd_offset + (reg - GetRegNumSVEZ0()) * 16;
} else if (m_sve_state == SVEState::Full) {
} else if (m_sve_state == SVEState::Full ||
m_sve_state == SVEState::Streaming) {
uint32_t sve_z0_offset = GetGPRSize() + 16;
sve_reg_offset =
sve::SigRegsOffset() + reg_info->byte_offset - sve_z0_offset;
Expand Down Expand Up @@ -163,19 +207,19 @@ bool RegisterContextCorePOSIX_arm64::ReadRegister(const RegisterInfo *reg_info,
}
} else {
// FPSR and FPCR will be located right after Z registers in
// SVEState::FPSIMD while in SVEState::Full they will be located at the
// end of register data after an alignment correction based on currently
// selected vector length.
// SVEState::FPSIMD while in SVEState::Full/SVEState::Streaming they will
// be located at the end of register data after an alignment correction
// based on currently selected vector length.
uint32_t sve_reg_num = LLDB_INVALID_REGNUM;
if (reg == GetRegNumFPSR()) {
sve_reg_num = reg;
if (m_sve_state == SVEState::Full)
if (m_sve_state == SVEState::Full || m_sve_state == SVEState::Streaming)
offset = sve::PTraceFPSROffset(sve::vq_from_vl(m_sve_vector_length));
else if (m_sve_state == SVEState::FPSIMD)
offset = sve::ptrace_fpsimd_offset + (32 * 16);
} else if (reg == GetRegNumFPCR()) {
sve_reg_num = reg;
if (m_sve_state == SVEState::Full)
if (m_sve_state == SVEState::Full || m_sve_state == SVEState::Streaming)
offset = sve::PTraceFPCROffset(sve::vq_from_vl(m_sve_vector_length));
else if (m_sve_state == SVEState::FPSIMD)
offset = sve::ptrace_fpsimd_offset + (32 * 16) + 4;
Expand Down Expand Up @@ -217,6 +261,7 @@ bool RegisterContextCorePOSIX_arm64::ReadRegister(const RegisterInfo *reg_info,
error);
} break;
case SVEState::Full:
case SVEState::Streaming:
offset = CalculateSVEOffset(reg_info);
assert(offset < m_sve_data.GetByteSize());
value.SetFromMemoryData(*reg_info, GetSVEBuffer(offset),
Expand All @@ -237,6 +282,54 @@ bool RegisterContextCorePOSIX_arm64::ReadRegister(const RegisterInfo *reg_info,
assert(offset < m_tls_data.GetByteSize());
value.SetFromMemoryData(*reg_info, m_tls_data.GetDataStart() + offset,
reg_info->byte_size, lldb::eByteOrderLittle, error);
} else if (IsSME(reg)) {
// If you had SME in the process, active or otherwise, there will at least
// be a ZA header. No header, no SME at all.
if (m_za_data.GetByteSize() < sizeof(sve::user_za_header))
return false;

if (!IsSMEZA(reg)) {
offset = reg_info->byte_offset - m_register_info_up->GetSMEOffset();
assert(offset < sizeof(m_sme_pseudo_regs));
// Host endian since these values are derived instead of being read from a
// core file note.
value.SetFromMemoryData(
*reg_info, reinterpret_cast<uint8_t *>(&m_sme_pseudo_regs) + offset,
reg_info->byte_size, lldb_private::endian::InlHostByteOrder(), error);
} else {
// If the process did not have the SME extension.
if (m_za_data.GetByteSize() < sizeof(sve::user_za_header))
return false;

// Don't use the size of the note to tell whether ZA is enabled. There may
// be non-register padding data after the header. Use the embedded
// header's size field instead.
lldb::offset_t size_offset = 0;
uint32_t size = m_za_data.GetU32(&size_offset);
bool za_enabled = size > sizeof(sve::user_za_header);

size_t za_note_size = m_za_data.GetByteSize();
// For a disabled ZA we fake a value of all 0s.
if (!za_enabled) {
uint64_t svl = m_sme_pseudo_regs.svg_reg * 8;
za_note_size = sizeof(sve::user_za_header) + (svl * svl);
}

const uint8_t *src = nullptr;
std::vector<uint8_t> disabled_za_data;

if (za_enabled)
src = m_za_data.GetDataStart();
else {
disabled_za_data.resize(za_note_size);
std::fill(disabled_za_data.begin(), disabled_za_data.end(), 0);
src = disabled_za_data.data();
}

value.SetFromMemoryData(*reg_info, src + sizeof(sve::user_za_header),
reg_info->byte_size, lldb::eByteOrderLittle,
error);
}
} else
return false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,20 @@ class RegisterContextCorePOSIX_arm64 : public RegisterContextPOSIX_arm64 {
lldb_private::DataExtractor m_sve_data;
lldb_private::DataExtractor m_pac_data;
lldb_private::DataExtractor m_tls_data;
lldb_private::DataExtractor m_za_data;

SVEState m_sve_state;
uint16_t m_sve_vector_length = 0;

// These are pseudo registers derived from the values in SSVE and ZA data.
struct __attribute__((packed)) sme_pseudo_regs {
uint64_t ctrl_reg;
uint64_t svg_reg;
};
static_assert(sizeof(sme_pseudo_regs) == 16);

struct sme_pseudo_regs m_sme_pseudo_regs;

const uint8_t *GetSVEBuffer(uint64_t offset = 0);

void ConfigureRegisterContext();
Expand Down
4 changes: 4 additions & 0 deletions lldb/source/Plugins/Process/elf-core/RegisterUtilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ constexpr RegsetDesc AARCH64_SVE_Desc[] = {
{llvm::Triple::Linux, llvm::Triple::aarch64, llvm::ELF::NT_ARM_SVE},
};

constexpr RegsetDesc AARCH64_SSVE_Desc[] = {
{llvm::Triple::Linux, llvm::Triple::aarch64, llvm::ELF::NT_ARM_SSVE},
};

constexpr RegsetDesc AARCH64_ZA_Desc[] = {
{llvm::Triple::Linux, llvm::Triple::aarch64, llvm::ELF::NT_ARM_ZA},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""
Check that LLDB can read Scalable Matrix Extension (SME) data from core files.
"""


import lldb
import itertools
from enum import Enum
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *


class Mode(Enum):
SVE = 0
SSVE = 1


class ZA(Enum):
Disabled = 0
Enabled = 1


class AArch64LinuxSMECoreFileTestCase(TestBase):
NO_DEBUG_INFO_TESTCASE = True

# SME introduces an extra SVE mode "streaming mode" and an array storage
# register "ZA". ZA can be enabled or disabled independent of streaming mode.
# Vector length can also be different between the streaming and non-streaming
# mode. Therefore this test checks a few combinations, but not all.
#
# The numbers in the core file names are options to the crashing program,
# see main.c for their meaning. The test case names will also explain them.

def check_corefile(self, corefile):
self.runCmd("target create --core " + corefile)

_, sve_mode, vl, svl, za = corefile.split("_")

sve_mode = Mode(int(sve_mode))
vl = int(vl)
svl = int(svl)
za = ZA(int(za))

self.expect("register read tpidr2", substrs=["0x1122334455667788"])

# In streaming mode, vg is the same as svg. 'g' is for granule which is
# 8 bytes.
if sve_mode == Mode.SSVE:
self.expect("register read vg", substrs=["0x{:016x}".format(svl // 8)])
else:
self.expect("register read vg", substrs=["0x{:016x}".format(vl // 8)])

# svg is always the streaming mode vector length.
self.expect("register read svg", substrs=["0x{:016x}".format(svl // 8)])

svcr = 1 if sve_mode == Mode.SSVE else 0
if za == ZA.Enabled:
svcr |= 2
self.expect("register read svcr", substrs=["0x{:016x}".format(svcr)])

repeat_bytes = lambda v, n: " ".join(["0x{:02x}".format(v)] * n)

sve_vl = svl if sve_mode == Mode.SSVE else vl
for i in range(0, 32):
# Each element is set to the register number + 1, for example:
# z0 = {0x01 0x01 0x01 ... }
expected = "{{{}}}".format(repeat_bytes(i + 1, sve_vl))
self.expect("register read z{}".format(i), substrs=[expected])

# The P registers cycle between a few values.
# p0 = {0xff 0xff ... }
# p1 = {0x55 0x55 ... }
# ...
# P registers and FFR have 1 bit per byte element in a vector.
p_value = lambda v: "{{{}}}".format(repeat_bytes(v, sve_vl // 8))
expected_p_values = [p_value(v) for v in [0xFF, 0x55, 0x11, 0x01, 0x00]]
expected_p_values = itertools.cycle(expected_p_values)

for i in range(0, 15):
expected = next(expected_p_values)
self.expect("register read p{}".format(i), substrs=[expected])

self.expect(
"register read ffr",
substrs=["{{{}}}".format(repeat_bytes(0xFF, sve_vl // 8))],
)

if za == ZA.Enabled:
# Each row of ZA is set to the row number plus 1. For example:
# za = {0x01 0x01 0x01 0x01 <repeat until end of row> 0x02 0x02 ...
make_row = repeat_bytes
else:
# When ZA is disabled lldb shows it as 0s.
make_row = lambda _, n: repeat_bytes(0, n)

expected_za = "{{{}}}".format(
" ".join([make_row(i + 1, svl) for i in range(svl)])
)
self.expect("register read za", substrs=[expected_za])

@skipIfLLVMTargetMissing("AArch64")
def test_sme_core_file_ssve_vl32_svl16_za_enabled(self):
self.check_corefile("core_1_32_16_1")

@skipIfLLVMTargetMissing("AArch64")
def test_sme_core_file_ssve_vl16_svl32_za_disabled(self):
self.check_corefile("core_1_16_32_0")

@skipIfLLVMTargetMissing("AArch64")
def test_sme_core_file_sve_vl16_svl32_za_enabled(self):
self.check_corefile("core_0_16_32_1")

@skipIfLLVMTargetMissing("AArch64")
def test_sme_core_file_sve_vl32_svl16_za_disabled(self):
self.check_corefile("core_0_32_16_0")
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading

0 comments on commit 43812c8

Please sign in to comment.