366 lines
11 KiB
C++
366 lines
11 KiB
C++
// Copyright 2018 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include <signal.h>
|
|
|
|
#include "gtest/gtest.h"
|
|
#include "test/util/logging.h"
|
|
#include "test/util/signal_util.h"
|
|
#include "test/util/test_util.h"
|
|
|
|
namespace gvisor {
|
|
namespace testing {
|
|
|
|
// Default value for the x87 FPU control word. See Intel SDM Vol 1, Ch 8.1.5
|
|
// "x87 FPU Control Word".
|
|
constexpr uint16_t kX87ControlWordDefault = 0x37f;
|
|
|
|
// Mask for the divide-by-zero exception.
|
|
constexpr uint16_t kX87ControlWordDiv0Mask = 1 << 2;
|
|
|
|
// Default value for the SSE control register (MXCSR). See Intel SDM Vol 1, Ch
|
|
// 11.6.4 "Initialization of SSE/SSE3 Extensions".
|
|
constexpr uint32_t kMXCSRDefault = 0x1f80;
|
|
|
|
// Mask for the divide-by-zero exception.
|
|
constexpr uint32_t kMXCSRDiv0Mask = 1 << 9;
|
|
|
|
// Flag for a pending divide-by-zero exception.
|
|
constexpr uint32_t kMXCSRDiv0Flag = 1 << 2;
|
|
|
|
void inline Halt() { asm("hlt\r\n"); }
|
|
|
|
void inline SetAlignmentCheck() {
|
|
asm("subq $128, %%rsp\r\n" // Avoid potential red zone clobber
|
|
"pushf\r\n"
|
|
"pop %%rax\r\n"
|
|
"or $0x40000, %%rax\r\n"
|
|
"push %%rax\r\n"
|
|
"popf\r\n"
|
|
"addq $128, %%rsp\r\n"
|
|
:
|
|
:
|
|
: "ax");
|
|
}
|
|
|
|
void inline ClearAlignmentCheck() {
|
|
asm("subq $128, %%rsp\r\n" // Avoid potential red zone clobber
|
|
"pushf\r\n"
|
|
"pop %%rax\r\n"
|
|
"mov $0x40000, %%rbx\r\n"
|
|
"not %%rbx\r\n"
|
|
"and %%rbx, %%rax\r\n"
|
|
"push %%rax\r\n"
|
|
"popf\r\n"
|
|
"addq $128, %%rsp\r\n"
|
|
:
|
|
:
|
|
: "ax", "bx");
|
|
}
|
|
|
|
void inline Int3Normal() { asm(".byte 0xcd, 0x03\r\n"); }
|
|
|
|
void inline Int3Compact() { asm(".byte 0xcc\r\n"); }
|
|
|
|
void InIOHelper(int width, int value) {
|
|
EXPECT_EXIT(
|
|
{
|
|
switch (width) {
|
|
case 1:
|
|
asm volatile("inb %%dx, %%al" ::"d"(value) : "%eax");
|
|
break;
|
|
case 2:
|
|
asm volatile("inw %%dx, %%ax" ::"d"(value) : "%eax");
|
|
break;
|
|
case 4:
|
|
asm volatile("inl %%dx, %%eax" ::"d"(value) : "%eax");
|
|
break;
|
|
default:
|
|
FAIL() << "invalid input width, only 1, 2 or 4 is allowed";
|
|
}
|
|
},
|
|
::testing::KilledBySignal(SIGSEGV), "");
|
|
}
|
|
|
|
TEST(ExceptionTest, Halt) {
|
|
// In order to prevent the regular handler from messing with things (and
|
|
// perhaps refaulting until some other signal occurs), we reset the handler to
|
|
// the default action here and ensure that it dies correctly.
|
|
struct sigaction sa = {};
|
|
sa.sa_handler = SIG_DFL;
|
|
auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa));
|
|
|
|
EXPECT_EXIT(Halt(), ::testing::KilledBySignal(SIGSEGV), "");
|
|
}
|
|
|
|
TEST(ExceptionTest, DivideByZero) {
|
|
// See above.
|
|
struct sigaction sa = {};
|
|
sa.sa_handler = SIG_DFL;
|
|
auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGFPE, sa));
|
|
|
|
EXPECT_EXIT(
|
|
{
|
|
uint32_t remainder;
|
|
uint32_t quotient;
|
|
uint32_t divisor = 0;
|
|
uint64_t value = 1;
|
|
asm("divl 0(%2)\r\n"
|
|
: "=d"(remainder), "=a"(quotient)
|
|
: "r"(&divisor), "d"(value >> 32), "a"(value));
|
|
TEST_CHECK(quotient > 0); // Force dependency.
|
|
},
|
|
::testing::KilledBySignal(SIGFPE), "");
|
|
}
|
|
|
|
// By default, x87 exceptions are masked and simply return a default value.
|
|
TEST(ExceptionTest, X87DivideByZeroMasked) {
|
|
int32_t quotient;
|
|
int32_t value = 1;
|
|
int32_t divisor = 0;
|
|
asm("fildl %[value]\r\n"
|
|
"fidivl %[divisor]\r\n"
|
|
"fistpl %[quotient]\r\n"
|
|
: [ quotient ] "=m"(quotient)
|
|
: [ value ] "m"(value), [ divisor ] "m"(divisor));
|
|
|
|
EXPECT_EQ(quotient, INT32_MIN);
|
|
}
|
|
|
|
// When unmasked, division by zero raises SIGFPE.
|
|
TEST(ExceptionTest, X87DivideByZeroUnmasked) {
|
|
// See above.
|
|
struct sigaction sa = {};
|
|
sa.sa_handler = SIG_DFL;
|
|
auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGFPE, sa));
|
|
|
|
EXPECT_EXIT(
|
|
{
|
|
// Clear the divide by zero exception mask.
|
|
constexpr uint16_t kControlWord =
|
|
kX87ControlWordDefault & ~kX87ControlWordDiv0Mask;
|
|
|
|
int32_t quotient;
|
|
int32_t value = 1;
|
|
int32_t divisor = 0;
|
|
asm volatile(
|
|
"fldcw %[cw]\r\n"
|
|
"fildl %[value]\r\n"
|
|
"fidivl %[divisor]\r\n"
|
|
"fistpl %[quotient]\r\n"
|
|
: [ quotient ] "=m"(quotient)
|
|
: [ cw ] "m"(kControlWord), [ value ] "m"(value),
|
|
[ divisor ] "m"(divisor));
|
|
},
|
|
::testing::KilledBySignal(SIGFPE), "");
|
|
}
|
|
|
|
// Pending exceptions in the x87 status register are not clobbered by syscalls.
|
|
TEST(ExceptionTest, X87StatusClobber) {
|
|
// See above.
|
|
struct sigaction sa = {};
|
|
sa.sa_handler = SIG_DFL;
|
|
auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGFPE, sa));
|
|
|
|
EXPECT_EXIT(
|
|
{
|
|
// Clear the divide by zero exception mask.
|
|
constexpr uint16_t kControlWord =
|
|
kX87ControlWordDefault & ~kX87ControlWordDiv0Mask;
|
|
|
|
int32_t quotient;
|
|
int32_t value = 1;
|
|
int32_t divisor = 0;
|
|
asm volatile(
|
|
"fildl %[value]\r\n"
|
|
"fidivl %[divisor]\r\n"
|
|
// Exception is masked, so it does not occur here.
|
|
"fistpl %[quotient]\r\n"
|
|
|
|
// SYS_getpid placed in rax by constraint.
|
|
"syscall\r\n"
|
|
|
|
// Unmask exception. The syscall didn't clobber the pending
|
|
// exception, so now it can be raised.
|
|
//
|
|
// N.B. "a floating-point exception will be generated upon execution
|
|
// of the *next* floating-point instruction".
|
|
"fldcw %[cw]\r\n"
|
|
"fwait\r\n"
|
|
: [ quotient ] "=m"(quotient)
|
|
: [ value ] "m"(value), [ divisor ] "m"(divisor), "a"(SYS_getpid),
|
|
[ cw ] "m"(kControlWord)
|
|
: "rcx", "r11");
|
|
},
|
|
::testing::KilledBySignal(SIGFPE), "");
|
|
}
|
|
|
|
// By default, SSE exceptions are masked and simply return a default value.
|
|
TEST(ExceptionTest, SSEDivideByZeroMasked) {
|
|
uint32_t status;
|
|
int32_t quotient;
|
|
int32_t value = 1;
|
|
int32_t divisor = 0;
|
|
asm("cvtsi2ssl %[value], %%xmm0\r\n"
|
|
"cvtsi2ssl %[divisor], %%xmm1\r\n"
|
|
"divss %%xmm1, %%xmm0\r\n"
|
|
"cvtss2sil %%xmm0, %[quotient]\r\n"
|
|
: [ quotient ] "=r"(quotient), [ status ] "=r"(status)
|
|
: [ value ] "r"(value), [ divisor ] "r"(divisor)
|
|
: "xmm0", "xmm1");
|
|
|
|
EXPECT_EQ(quotient, INT32_MIN);
|
|
}
|
|
|
|
// When unmasked, division by zero raises SIGFPE.
|
|
TEST(ExceptionTest, SSEDivideByZeroUnmasked) {
|
|
// See above.
|
|
struct sigaction sa = {};
|
|
sa.sa_handler = SIG_DFL;
|
|
auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGFPE, sa));
|
|
|
|
EXPECT_EXIT(
|
|
{
|
|
// Clear the divide by zero exception mask.
|
|
constexpr uint32_t kMXCSR = kMXCSRDefault & ~kMXCSRDiv0Mask;
|
|
|
|
int32_t quotient;
|
|
int32_t value = 1;
|
|
int32_t divisor = 0;
|
|
asm volatile(
|
|
"ldmxcsr %[mxcsr]\r\n"
|
|
"cvtsi2ssl %[value], %%xmm0\r\n"
|
|
"cvtsi2ssl %[divisor], %%xmm1\r\n"
|
|
"divss %%xmm1, %%xmm0\r\n"
|
|
"cvtss2sil %%xmm0, %[quotient]\r\n"
|
|
: [ quotient ] "=r"(quotient)
|
|
: [ mxcsr ] "m"(kMXCSR), [ value ] "r"(value),
|
|
[ divisor ] "r"(divisor)
|
|
: "xmm0", "xmm1");
|
|
},
|
|
::testing::KilledBySignal(SIGFPE), "");
|
|
}
|
|
|
|
// Pending exceptions in the SSE status register are not clobbered by syscalls.
|
|
TEST(ExceptionTest, SSEStatusClobber) {
|
|
uint32_t mxcsr;
|
|
int32_t quotient;
|
|
int32_t value = 1;
|
|
int32_t divisor = 0;
|
|
asm("cvtsi2ssl %[value], %%xmm0\r\n"
|
|
"cvtsi2ssl %[divisor], %%xmm1\r\n"
|
|
"divss %%xmm1, %%xmm0\r\n"
|
|
// Exception is masked, so it does not occur here.
|
|
"cvtss2sil %%xmm0, %[quotient]\r\n"
|
|
|
|
// SYS_getpid placed in rax by constraint.
|
|
"syscall\r\n"
|
|
|
|
// Intel SDM Vol 1, Ch 10.2.3.1 "SIMD Floating-Point Mask and Flag Bits":
|
|
// "If LDMXCSR or FXRSTOR clears a mask bit and sets the corresponding
|
|
// exception flag bit, a SIMD floating-point exception will not be
|
|
// generated as a result of this change. The unmasked exception will be
|
|
// generated only upon the execution of the next SSE/SSE2/SSE3 instruction
|
|
// that detects the unmasked exception condition."
|
|
//
|
|
// Though ambiguous, empirical evidence indicates that this means that
|
|
// exception flags set in the status register will never cause an
|
|
// exception to be raised; only a new exception condition will do so.
|
|
//
|
|
// Thus here we just check for the flag itself rather than trying to raise
|
|
// the exception.
|
|
"stmxcsr %[mxcsr]\r\n"
|
|
: [ quotient ] "=r"(quotient), [ mxcsr ] "+m"(mxcsr)
|
|
: [ value ] "r"(value), [ divisor ] "r"(divisor), "a"(SYS_getpid)
|
|
: "xmm0", "xmm1", "rcx", "r11");
|
|
|
|
EXPECT_TRUE(mxcsr & kMXCSRDiv0Flag);
|
|
}
|
|
|
|
TEST(ExceptionTest, IOAccessFault) {
|
|
// See above.
|
|
struct sigaction sa = {};
|
|
sa.sa_handler = SIG_DFL;
|
|
auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa));
|
|
|
|
InIOHelper(1, 0x0);
|
|
InIOHelper(2, 0x7);
|
|
InIOHelper(4, 0x6);
|
|
InIOHelper(1, 0xffff);
|
|
InIOHelper(2, 0xffff);
|
|
InIOHelper(4, 0xfffd);
|
|
}
|
|
|
|
TEST(ExceptionTest, Alignment) {
|
|
SetAlignmentCheck();
|
|
ClearAlignmentCheck();
|
|
}
|
|
|
|
TEST(ExceptionTest, AlignmentHalt) {
|
|
// See above.
|
|
struct sigaction sa = {};
|
|
sa.sa_handler = SIG_DFL;
|
|
auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa));
|
|
|
|
// Reported upstream. We need to ensure that bad flags are cleared even in
|
|
// fault paths. Set the alignment flag and then generate an exception.
|
|
EXPECT_EXIT(
|
|
{
|
|
SetAlignmentCheck();
|
|
Halt();
|
|
},
|
|
::testing::KilledBySignal(SIGSEGV), "");
|
|
}
|
|
|
|
TEST(ExceptionTest, AlignmentCheck) {
|
|
|
|
// See above.
|
|
struct sigaction sa = {};
|
|
sa.sa_handler = SIG_DFL;
|
|
auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGBUS, sa));
|
|
|
|
EXPECT_EXIT(
|
|
{
|
|
char array[16];
|
|
SetAlignmentCheck();
|
|
for (int i = 0; i < 8; i++) {
|
|
// At least 7/8 offsets will be unaligned here.
|
|
uint64_t* ptr = reinterpret_cast<uint64_t*>(&array[i]);
|
|
asm("mov %0, 0(%0)\r\n" : : "r"(ptr) : "ax");
|
|
}
|
|
},
|
|
::testing::KilledBySignal(SIGBUS), "");
|
|
}
|
|
|
|
TEST(ExceptionTest, Int3Normal) {
|
|
// See above.
|
|
struct sigaction sa = {};
|
|
sa.sa_handler = SIG_DFL;
|
|
auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGTRAP, sa));
|
|
|
|
EXPECT_EXIT(Int3Normal(), ::testing::KilledBySignal(SIGTRAP), "");
|
|
}
|
|
|
|
TEST(ExceptionTest, Int3Compact) {
|
|
// See above.
|
|
struct sigaction sa = {};
|
|
sa.sa_handler = SIG_DFL;
|
|
auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGTRAP, sa));
|
|
|
|
EXPECT_EXIT(Int3Compact(), ::testing::KilledBySignal(SIGTRAP), "");
|
|
}
|
|
|
|
} // namespace testing
|
|
} // namespace gvisor
|