58 changes: 58 additions & 0 deletions lldb/source/Plugins/Process/Utility/RegisterContext_x86.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//===-- RegisterContext_x86.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
//
//===----------------------------------------------------------------------===//

#include "RegisterContext_x86.h"

using namespace lldb_private;

// Convert the 8-bit abridged FPU Tag Word (as found in FXSAVE) to the full
// 16-bit FPU Tag Word (as found in FSAVE, and used by gdb protocol). This
// requires knowing the values of the ST(i) registers and the FPU Status Word.
uint16_t lldb_private::AbridgedToFullTagWord(uint8_t abridged_tw, uint16_t sw,
llvm::ArrayRef<MMSReg> st_regs) {
// Tag word is using internal FPU register numbering rather than ST(i).
// Mapping to ST(i): i = FPU regno - TOP (Status Word, bits 11:13).
// Here we start with FPU reg 7 and go down.
int st = 7 - ((sw >> 11) & 7);
uint16_t tw = 0;
for (uint8_t mask = 0x80; mask != 0; mask >>= 1) {
tw <<= 2;
if (abridged_tw & mask) {
// The register is non-empty, so we need to check the value of ST(i).
uint16_t exp =
st_regs[st].comp.sign_exp & 0x7fff; // Discard the sign bit.
if (exp == 0) {
if (st_regs[st].comp.mantissa == 0)
tw |= 1; // Zero
else
tw |= 2; // Denormal
} else if (exp == 0x7fff)
tw |= 2; // Infinity or NaN
// 0 if normal number
} else
tw |= 3; // Empty register

// Rotate ST down.
st = (st - 1) & 7;
}

return tw;
}

// Convert the 16-bit FPU Tag Word to the abridged 8-bit value, to be written
// into FXSAVE.
uint8_t lldb_private::FullToAbridgedTagWord(uint16_t tw) {
uint8_t abridged_tw = 0;
for (uint16_t mask = 0xc000; mask != 0; mask >>= 2) {
abridged_tw <<= 1;
// full TW uses 11 for empty registers, aTW uses 0
if ((tw & mask) != mask)
abridged_tw |= 1;
}
return abridged_tw;
}
20 changes: 19 additions & 1 deletion lldb/source/Plugins/Process/Utility/RegisterContext_x86.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <cstddef>
#include <cstdint>

#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/BitmaskEnum.h"
#include "llvm/Support/Compiler.h"

Expand Down Expand Up @@ -239,10 +240,23 @@ enum {

// Generic floating-point registers

LLVM_PACKED_START
struct MMSRegComp {
uint64_t mantissa;
uint16_t sign_exp;
};

struct MMSReg {
uint8_t bytes[10];
union {
uint8_t bytes[10];
MMSRegComp comp;
};
uint8_t pad[6];
};
LLVM_PACKED_END

static_assert(sizeof(MMSRegComp) == 10, "MMSRegComp is not 10 bytes of size");
static_assert(sizeof(MMSReg) == 16, "MMSReg is not 16 bytes of size");

struct XMMReg {
uint8_t bytes[16]; // 128-bits for each XMM register
Expand Down Expand Up @@ -369,6 +383,10 @@ inline void YMMToXState(const YMMReg& input, void* xmm_bytes, void* ymmh_bytes)
::memcpy(ymmh_bytes, input.bytes + sizeof(XMMReg), sizeof(YMMHReg));
}

uint16_t AbridgedToFullTagWord(uint8_t abridged_tw, uint16_t sw,
llvm::ArrayRef<MMSReg> st_regs);
uint8_t FullToAbridgedTagWord(uint16_t tw);

} // namespace lldb_private

#endif
22 changes: 16 additions & 6 deletions lldb/source/Plugins/Process/Utility/RegisterInfos_x86_64.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,14 @@
RegisterContextPOSIX_x86::g_invalidate_##reg64, nullptr, 0 \
}

#define DEFINE_FPR_32(name, reg, kind1, kind2, kind3, kind4, reg64) \
{ \
#name, nullptr, FPR_SIZE(reg), FPR_OFFSET(reg), eEncodingUint, eFormatHex, \
{kind1, kind2, kind3, kind4, lldb_##name##_x86_64 }, \
RegisterContextPOSIX_x86::g_contained_##reg64, \
RegisterContextPOSIX_x86::g_invalidate_##reg64, nullptr, 0 \
}

// clang-format off
static RegisterInfo g_register_infos_x86_64[] = {
// General purpose registers EH_Frame DWARF Generic Process Plugin
Expand Down Expand Up @@ -251,16 +259,18 @@ static RegisterInfo g_register_infos_x86_64[] = {
DEFINE_GPR_PSEUDO_8L(r12l, r12), DEFINE_GPR_PSEUDO_8L(r13l, r13),
DEFINE_GPR_PSEUDO_8L(r14l, r14), DEFINE_GPR_PSEUDO_8L(r15l, r15),

// i387 Floating point registers. EH_frame DWARF Generic Process Plugin
// ====================================== =============== ================== =================== ====================
// i387 Floating point registers. EH_frame DWARF Generic Process Plugin reg64
// ====================================== =============== ================== =================== ==================== =====
DEFINE_FPR(fctrl, fctrl, dwarf_fctrl_x86_64, dwarf_fctrl_x86_64, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM),
DEFINE_FPR(fstat, fstat, dwarf_fstat_x86_64, dwarf_fstat_x86_64, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM),
DEFINE_FPR(ftag, ftag, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM),
DEFINE_FPR(fop, fop, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM),
DEFINE_FPR(fiseg, ptr.i386_.fiseg, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM),
DEFINE_FPR(fioff, ptr.i386_.fioff, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM),
DEFINE_FPR(foseg, ptr.i386_.foseg, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM),
DEFINE_FPR(fooff, ptr.i386_.fooff, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM),
DEFINE_FPR_32(fiseg, ptr.i386_.fiseg, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, fip),
DEFINE_FPR_32(fioff, ptr.i386_.fioff, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, fip),
DEFINE_FPR(fip, ptr.x86_64.fip, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM),
DEFINE_FPR_32(foseg, ptr.i386_.foseg, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, fdp),
DEFINE_FPR_32(fooff, ptr.i386_.fooff, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, fdp),
DEFINE_FPR(fdp, ptr.x86_64.fdp, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM),
DEFINE_FPR(mxcsr, mxcsr, dwarf_mxcsr_x86_64, dwarf_mxcsr_x86_64, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM),
DEFINE_FPR(mxcsrmask, mxcsrmask, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM),

Expand Down
2 changes: 2 additions & 0 deletions lldb/source/Plugins/Process/Utility/lldb-x86-register-enums.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,10 @@ enum {
lldb_fop_x86_64,
lldb_fiseg_x86_64,
lldb_fioff_x86_64,
lldb_fip_x86_64,
lldb_foseg_x86_64,
lldb_fooff_x86_64,
lldb_fdp_x86_64,
lldb_mxcsr_x86_64,
lldb_mxcsrmask_x86_64,
lldb_st0_x86_64,
Expand Down
39 changes: 39 additions & 0 deletions lldb/test/Shell/Register/x86-64-fp-read.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# XFAIL: system-windows
# XFAIL: system-darwin
# REQUIRES: native && target-x86_64
# RUN: %clangxx_host -g %p/Inputs/x86-fp-read.cpp -o %t
# RUN: %lldb -b -s %s %t | FileCheck %s
process launch
# CHECK: Process {{.*}} stopped

# fdiv (%rbx) gets encoded into 2 bytes, int3 into 1 byte
print (void*)($pc-3)
# CHECK: (void *) $0 = [[FDIV:0x[0-9a-f]*]]
print &zero
# CHECK: (uint32_t *) $1 = [[ZERO:0x[0-9a-f]*]]

register read --all
# CHECK-DAG: fctrl = 0x037b
# CHECK-DAG: fstat = 0x8084
# CHECK-DAG: ftag = 0xea58
# CHECK-DAG: fop = 0x0033
# CHECK-DAG: fip = [[FDIV]]
# CHECK-DAG: fdp = [[ZERO]]

# CHECK-DAG: st{{(mm)?}}0 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x80 0x00 0x40}
# CHECK-DAG: st{{(mm)?}}1 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x3f 0x00 0x00}
# CHECK-DAG: st{{(mm)?}}2 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
# CHECK-DAG: st{{(mm)?}}3 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x80}
# CHECK-DAG: st{{(mm)?}}4 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x80 0xff 0x7f}
# CHECK-DAG: st{{(mm)?}}5 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x80 0xff 0xff}
# CHECK-DAG: st{{(mm)?}}6 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xc0 0xff 0xff}
# CHECK-DAG: st{{(mm)?}}7 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}

# legacy approach, superseded by fip/fdp registers
print (void*)($fiseg*0x100000000 + $fioff)
# CHECK: (void *) $2 = [[FDIV]]
print (uint32_t*)($foseg * 0x100000000 + $fooff)
# CHECK: (uint32_t *) $3 = [[ZERO]]

process continue
# CHECK: Process {{[0-9]+}} exited with status = 0
9 changes: 3 additions & 6 deletions lldb/test/Shell/Register/x86-64-fp-write.test
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@ process launch
register write fctrl 0x037b
register write fstat 0x8884
# note: this needs to enable all registers for writes to be effective
# TODO: fix it to use proper ftag values instead of 'abridged'
register write ftag 0x00ff
register write ftag 0x2a58
register write fop 0x0033
# the exact addresses do not matter, we want just to verify FXSAVE
# note: fxrstor64 apparently truncates this to 48 bits, and sign extends
# the highest bits, so let's keep the value safely below
register write fiseg 0x00000567
register write fioff 0x89abcdef
register write foseg 0x00000a98
register write fooff 0x76543210
register write fip 0x0000056789abcdef
register write fdp 0x00000a9876543210

register write st0 "{0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x80 0x00 0x40}"
register write st1 "{0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x3f 0x00 0x00}"
Expand Down
25 changes: 10 additions & 15 deletions lldb/test/Shell/Register/x86-fp-read.test
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
# XFAIL: system-windows
# REQUIRES: native && (target-x86 || target-x86_64)
# REQUIRES: native && target-x86
# RUN: %clangxx_host -g %p/Inputs/x86-fp-read.cpp -o %t
# RUN: %lldb -b -s %s %t | FileCheck %s
process launch
# CHECK: Process {{.*}} stopped

# fdiv (%rbx) gets encoded into 2 bytes, int3 into 1 byte
print (void*)($pc-3)
# CHECK: (void *) $0 = [[FDIV:0x[0-9a-f]*]]
print &zero
# CHECK: (uint32_t *) $1 = [[ZERO:0x[0-9a-f]*]]

register read --all
# CHECK-DAG: fctrl = 0x037b
# CHECK-DAG: fstat = 0x8084
# TODO: the following value is incorrect, it's a bug in the way
# FXSAVE/XSAVE is interpreted
# CHECK-DAG: ftag = 0x007f
# CHECK-DAG: ftag = 0xea58
# CHECK-DAG: fop = 0x0033
# CHECK-DAG: fioff = [[FDIV]]
# CHECK-DAG: fooff = [[ZERO]]

# CHECK-DAG: st{{(mm)?}}0 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x80 0x00 0x40}
# CHECK-DAG: st{{(mm)?}}1 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x3f 0x00 0x00}
Expand All @@ -22,16 +28,5 @@ register read --all
# CHECK-DAG: st{{(mm)?}}6 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xc0 0xff 0xff}
# CHECK-DAG: st{{(mm)?}}7 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}

# fdiv (%rbx) gets encoded into 2 bytes, int3 into 1 byte
print (void*)($pc-3)
# CHECK: (void *) $0 = [[FDIV:0x[0-9a-f]*]]
# TODO: we probably should not split it like this
print (void*)($fiseg*0x100000000 + $fioff)
# CHECK: (void *) $1 = [[FDIV]]
print &zero
# CHECK: (uint32_t *) $2 = [[ZERO:0x[0-9a-f]*]]
print (uint32_t*)($foseg * 0x100000000 + $fooff)
# CHECK: (uint32_t *) $3 = [[ZERO]]

process continue
# CHECK: Process {{[0-9]+}} exited with status = 0
12 changes: 7 additions & 5 deletions lldb/test/Shell/Register/x86-fp-write.test
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
# XFAIL: system-windows
# REQUIRES: native && target-x86
# REQUIRES: native && (target-x86 || target-x86_64)
# RUN: %clangxx_host %p/Inputs/x86-fp-write.cpp -o %t
# RUN: %lldb -b -s %s %t | FileCheck %s
process launch

register write fctrl 0x037b
register write fstat 0x8884
# note: this needs to enable all registers for writes to be effective
# TODO: fix it to use proper ftag values instead of 'abridged'
register write ftag 0x00ff
register write ftag 0x2a58
register write fop 0x0033
# the exact addresses do not matter, we want just to verify FXSAVE
# note: segment registers are not supported on all CPUs
Expand All @@ -31,8 +30,11 @@ process continue
# CHECK-DAG: fstat = 0x8884
# CHECK-DAG: ftag = 0xa961
# CHECK-DAG: fop = 0x0033
# CHECK-DAG: fip = 0x89abcdef
# CHECK-DAG: fdp = 0x76543210

# This test is run both on 32-bit and 64-bit systems, in order to test
# that fioff/fooff setting works as well as fip/fdp.
# CHECK-DAG: fip = 0x{{(00000000)?}}89abcdef
# CHECK-DAG: fdp = 0x{{(00000000)?}}76543210

# CHECK-DAG: st0 = { 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x80 0x00 0x40 }
# CHECK-DAG: st1 = { 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x3f 0x00 0x00 }
Expand Down
1 change: 1 addition & 0 deletions lldb/unittests/Process/Utility/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
add_lldb_unittest(ProcessUtilityTests
RegisterContextTest.cpp
RegisterContextFreeBSDTest.cpp

LINK_LIBS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,15 @@ TEST(RegisterContextFreeBSDTest, x86_64) {
EXPECT_OFF(ftag_x86_64, 0x04, 2);
EXPECT_OFF(fop_x86_64, 0x06, 2);
// NB: Technically fiseg/foseg are 16-bit long and the higher 16 bits
// are reserved. However, we use them to access/recombine 64-bit FIP/FDP.
// are reserved. However, LLDB defines them to be 32-bit long for backwards
// compatibility, as they were used to reconstruct FIP/FDP before explicit
// register entries for them were added. Also, this is still how GDB does it.
EXPECT_OFF(fioff_x86_64, 0x08, 4);
EXPECT_OFF(fiseg_x86_64, 0x0C, 4);
EXPECT_OFF(fip_x86_64, 0x08, 8);
EXPECT_OFF(fooff_x86_64, 0x10, 4);
EXPECT_OFF(foseg_x86_64, 0x14, 4);
EXPECT_OFF(fdp_x86_64, 0x10, 8);
EXPECT_OFF(mxcsr_x86_64, 0x18, 4);
EXPECT_OFF(mxcsrmask_x86_64, 0x1C, 4);
EXPECT_OFF(st0_x86_64, 0x20, 10);
Expand Down
73 changes: 73 additions & 0 deletions lldb/unittests/Process/Utility/RegisterContextTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//===-- RegisterContextTest.cpp -------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "gtest/gtest.h"

#include "Plugins/Process/Utility/RegisterContext_x86.h"

#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/FormatVariadic.h"

#include <array>

using namespace lldb_private;

struct TagWordTestVector {
uint16_t sw;
uint16_t tw;
uint8_t tw_abridged;
int st_reg_num;
};

constexpr MMSReg st_from_comp(uint64_t mantissa, uint16_t sign_exp) {
MMSReg ret = {};
ret.comp.mantissa = mantissa;
ret.comp.sign_exp = sign_exp;
return ret;
}

const std::array<MMSReg, 8> st_regs = {
st_from_comp(0x8000000000000000, 0x4000), // +2.0
st_from_comp(0x3f00000000000000, 0x0000), // 1.654785e-4932
st_from_comp(0x0000000000000000, 0x0000), // +0
st_from_comp(0x0000000000000000, 0x8000), // -0
st_from_comp(0x8000000000000000, 0x7fff), // +inf
st_from_comp(0x8000000000000000, 0xffff), // -inf
st_from_comp(0xc000000000000000, 0xffff), // nan
st_from_comp(0x8000000000000000, 0xc000), // -2.0
};

const std::array<TagWordTestVector, 8> tag_word_test_vectors{
TagWordTestVector{0x3800, 0x3fff, 0x80, 1},
TagWordTestVector{0x3000, 0x2fff, 0xc0, 2},
TagWordTestVector{0x2800, 0x27ff, 0xe0, 3},
TagWordTestVector{0x2000, 0x25ff, 0xf0, 4},
TagWordTestVector{0x1800, 0x25bf, 0xf8, 5},
TagWordTestVector{0x1000, 0x25af, 0xfc, 6},
TagWordTestVector{0x0800, 0x25ab, 0xfe, 7},
TagWordTestVector{0x0000, 0x25a8, 0xff, 8},
};

TEST(RegisterContext_x86Test, AbridgedToFullTagWord) {
for (const auto &x : llvm::enumerate(tag_word_test_vectors)) {
SCOPED_TRACE(llvm::formatv("tag_word_test_vectors[{0}]", x.index()));
std::array<MMSReg, 8> test_regs;
for (int i = 0; i < x.value().st_reg_num; ++i)
test_regs[i] = st_regs[x.value().st_reg_num - i - 1];
EXPECT_EQ(
AbridgedToFullTagWord(x.value().tw_abridged, x.value().sw, test_regs),
x.value().tw);
}
}

TEST(RegisterContext_x86Test, FullToAbridgedTagWord) {
for (const auto &x : llvm::enumerate(tag_word_test_vectors)) {
SCOPED_TRACE(llvm::formatv("tag_word_test_vectors[{0}]", x.index()));
EXPECT_EQ(FullToAbridgedTagWord(x.value().tw), x.value().tw_abridged);
}
}