Files
cpu-check/malign_buffer.cc
Eric Liu b9f6ef50af
Some checks failed
Build Alpine Package / build-alpine (push) Failing after 1m50s
Build Debian Package / build-debian (push) Successful in 1m58s
Add fallback for cache line size detection
- Provide default cache line size for systems without _SC_LEVEL1_DCACHE_LINESIZE
- Set default to 64 bytes, which is common on x86_64 architectures
- Improve portability of cache line size detection
2025-02-01 06:52:15 +00:00

388 lines
12 KiB
C++

// Copyright 2020 Google LLC
//
// 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 "malign_buffer.h"
#include <cstddef>
#if defined(__i386__) || defined(__x86_64__)
#include <immintrin.h>
#endif
#include <sys/mman.h>
#include <unistd.h>
#include <cstdlib>
#include <iomanip>
#include <sstream>
#include "log.h"
#include "utils.h"
#undef HAS_FEATURE_MEMORY_SANITIZER
#if defined(__has_feature)
#if __has_feature(memory_sanitizer)
#define HAS_FEATURE_MEMORY_SANITIZER
#endif
#endif
#if defined(__i386__) || defined(__x86_64__)
#define X86_TARGET_ATTRIBUTE(s) __attribute__((target(s)))
#else
#define X86_TARGET_ATTRIBUTE(s)
#endif
namespace cpu_check {
namespace {
inline void __movsb(char *dst, const char *src, size_t size) {
#if defined(__i386__) || defined(__x86_64__)
__asm__ __volatile__("rep movsb"
: "+D"(dst), "+S"(src), "+c"(size)
:
: "memory");
#else
LOG(FATAL) << "Cannot rep;movsb";
#endif
}
inline void __stosb(void *dst, unsigned char c, size_t size) {
#if defined(__i386__) || defined(__x86_64__)
__asm__ __volatile__("rep stosb" : "+D"(dst), "+c"(size) : "a"(c) : "memory");
#else
LOG(FATAL) << "Cannot rep;stosb";
#endif
}
inline void __sse_128_memcpy(char *dst, const char *src, size_t size) {
#if (defined(__i386__) || defined(__x86_64__))
size_t blks = size / 16;
for (int i = 0; i < blks; i++) {
_mm_storeu_si128(
reinterpret_cast<__m128i *>(dst) + i,
_mm_loadu_si128(reinterpret_cast<const __m128i *>(src) + i));
}
memcpy(dst + blks * 16, src + blks * 16, size - blks * 16);
#else
LOG(FATAL) << "SSE not available";
#endif
}
X86_TARGET_ATTRIBUTE("avx")
inline void __avx_256_memcpy(char *dst, const char *src, size_t size) {
#if (defined(__i386__) || defined(__x86_64__))
size_t blks = size / 32;
for (int i = 0; i < blks; i++) {
_mm256_storeu_si256(
reinterpret_cast<__m256i *>(dst) + i,
_mm256_loadu_si256(reinterpret_cast<const __m256i *>(src) + i));
}
memcpy(dst + blks * 32, src + blks * 32, size - blks * 32);
#else
LOG(FATAL) << "x86 only";
#endif
}
X86_TARGET_ATTRIBUTE("avx512f")
inline void __avx_512_memcpy(char *dst, const char *src, size_t size) {
#if (defined(__i386__) || defined(__x86_64__))
size_t blks = size / 64;
for (int i = 0; i < blks; i++) {
_mm512_storeu_si512(
reinterpret_cast<__m512i *>(dst) + i,
_mm512_loadu_si512(reinterpret_cast<const __m512i *>(src) + i));
}
memcpy(dst + blks * 64, src + blks * 64, size - blks * 64);
#else
LOG(FATAL) << "x86 only";
#endif
}
} // namespace
size_t MalignBuffer::RoundUpToPageSize(size_t k) {
return ((k + kPageSize - 1) / kPageSize) * kPageSize;
}
// Helper to make MSAN happy. NOP if memory sanitizer is not enabled.
void MalignBuffer::InitializeMemoryForSanitizer(char *addr, size_t size) {
#ifdef HAS_FEATURE_MEMORY_SANITIZER
std::default_random_engine rnd;
std::uniform_int_distribution<int> dist(std::numeric_limits<char>::min(),
std::numeric_limits<char>::max());
for (size_t i = 0; i < size; i++) {
addr[i] = dist(rnd);
}
#endif
}
const size_t MalignBuffer::kPageSize = sysconf(_SC_PAGESIZE);
#ifdef _SC_LEVEL1_DCACHE_LINESIZE
const size_t MalignBuffer::kCacheLineSize = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
#else
// Default to 64 bytes which is common on x86_64
const size_t MalignBuffer::kCacheLineSize = 64;
#endif
std::string MalignBuffer::ToString(CopyMethod m) {
switch (m) {
case kMemcpy:
return "memcpy";
case kRepMov:
return "rep;mov";
case kSseBy128:
return "sse:128";
case kAvxBy256:
return "avx:256";
case kAvxBy512:
return "avx:512";
}
}
size_t MalignBuffer::RandomAlignment(uint64_t seed) {
std::knuth_b rng(seed);
return std::uniform_int_distribution<size_t>(0, kPageSize - 1)(rng);
}
MalignBuffer::MalignBuffer(size_t capacity)
: capacity_(capacity),
base_address_(
aligned_alloc(kPageSize, RoundUpToPageSize(capacity) + kPageSize)) {
if (base_address_ == nullptr) {
LOG(FATAL) << "Failed allocate for capacity: " << capacity;
}
// There are lots of places that use unitialized MalignBuffer. So just
// fill some pseudo-random bytes if cpu_check is compiled with msan.
InitializeMemoryForSanitizer(static_cast<char *>(base_address_), capacity_);
}
MalignBuffer::MalignBuffer(size_t alignment_offset, absl::string_view s)
: MalignBuffer(s.size() + alignment_offset) {
Initialize(alignment_offset, s.size());
CopyFrom(s, kMemcpy);
}
MalignBuffer::~MalignBuffer() { free(base_address_); }
void MalignBuffer::Initialize(size_t alignment_offset, size_t length) {
if (length > capacity_) {
LOG(FATAL) << "Length: " << length << " Capacity: " << capacity_;
}
if (alignment_offset >= kPageSize) {
LOG(FATAL) << "Alignment: " << alignment_offset
<< " PageSize: " << kPageSize;
}
alignment_offset_ = alignment_offset;
length_ = length;
buffer_address_ = static_cast<char *>(base_address_) + alignment_offset_;
}
void MalignBuffer::resize(size_t length) {
Initialize(alignment_offset_, length);
}
std::string MalignBuffer::CopyFrom(const MalignBuffer &that, CopyMethod m) {
CopyFrom(absl::string_view(that.data(), that.size()), m);
return Syndrome(that);
}
void MalignBuffer::CopyFrom(absl::string_view src, CopyMethod m) {
if (size() != src.size()) {
LOG(FATAL) << "this.size: " << size() << " src.size:" << src.size();
}
CopyFrom(0, src, m);
}
void MalignBuffer::CopyFrom(size_t pos, absl::string_view src, CopyMethod m) {
if (pos + src.size() > size()) {
LOG(FATAL) << "this.size: " << size() << " src.size:" << src.size()
<< " pos: " << pos;
}
switch (m) {
case kMemcpy:
// Assumes memcpy doesn't use rep;movsb; false in lots of environments.
memcpy(data() + pos, src.data(), src.size());
break;
case kRepMov:
__movsb(data() + pos, src.data(), src.size());
break;
case kSseBy128:
__sse_128_memcpy(data() + pos, src.data(), src.size());
break;
case kAvxBy256:
__avx_256_memcpy(data() + pos, src.data(), src.size());
break;
case kAvxBy512:
__avx_512_memcpy(data() + pos, src.data(), src.size());
break;
}
}
std::string MalignBuffer::Syndrome(const MalignBuffer &that) const {
std::stringstream s;
std::string syndrome = CorruptionSyndrome(that);
if (syndrome.empty()) return "";
s << syndrome << ", \"this\": \"" << static_cast<const void *>(data())
<< "\", "
<< "\"that\": \"" << static_cast<const void *>(that.data()) << "\"";
return s.str();
}
std::string MalignBuffer::CorruptionSyndrome(const MalignBuffer &that) const {
std::stringstream s;
if (size() != that.size()) {
s << Json("unequalSizeThis", size()) << ", "
<< Json("unequalSizeThat", that.size());
return s.str();
}
bool failed_memcmp = memcmp(data(), that.data(), that.size());
int wrong_bytes = 0;
int wrong_bits = 0;
int byte_faults = 0;
int first_wrong = INT_MAX;
int last_wrong = INT_MIN;
std::vector<int> lane_errors(8, 0);
for (size_t i = 0; i < size(); i++) {
unsigned char a = *(data() + i);
unsigned char b = *(that.data() + i);
unsigned char d = a ^ b;
if (d) {
first_wrong = std::min<int>(first_wrong, i);
last_wrong = std::max<int>(last_wrong, i);
byte_faults |= d;
wrong_bytes++;
wrong_bits += __builtin_popcount(d);
for (size_t i = 0; i < 8; i++) {
if ((d >> i) & 1) {
lane_errors[i]++;
}
}
}
}
if (wrong_bits || wrong_bytes) {
const int range_width = (last_wrong - first_wrong) + 1;
s << Json("cmpResult",
(failed_memcmp ? "Failed_Memcmp" : "**Passed_Memcmp**"))
<< ", " << Json("wrongByteCount", wrong_bytes) << ", "
<< Json("wrongBitCount", wrong_bits) << ", "
<< Json("corruptionWidth", range_width) << ", "
<< Json("corruptStart", first_wrong) << ", "
<< Json("corruptByteBitMask", byte_faults) << ", "
<< "\"byBitLane\": [";
for (size_t i = 0; i < 8; i++) {
if (i) s << ", ";
s << lane_errors[i];
}
s << " ] ";
// Dump up to 64 corrupted locations.
std::stringstream dump;
dump << " \"byteErrors\": [ " << std::hex;
uint64_t buf_a = 0;
uint64_t buf_b = 0;
for (size_t k = 0; k < std::min(64, range_width); k++) {
uint8_t a = *(data() + first_wrong + k);
uint8_t b = *(that.data() + first_wrong + k);
if (k) dump << ", ";
dump << "[ " << std::setw(2) << "\"0x" << static_cast<int>(a) << "\", "
<< std::setw(2) << "\"0x" << static_cast<int>(b) << "\" ";
buf_a = (buf_a >> 8) | static_cast<uint64_t>(a) << 56;
buf_b = (buf_b >> 8) | static_cast<uint64_t>(b) << 56;
if ((k >= 7) && (7 == ((first_wrong + k) % 8))) {
dump << ", " << CrackId(buf_a) << ", " << CrackId(buf_b);
buf_a = 0;
buf_b = 0;
}
dump << " ]";
}
dump << " ] ";
return s.str() + ", " + dump.str();
} else {
if (!failed_memcmp) return "";
return Json("cmpResult", "**Failed_Memcmp**");
}
}
std::string MalignBuffer::CrackId(uint64_t v) const {
std::stringstream s;
s << std::hex << " [\"0x" << std::setw(4) << (v >> 48) << "\", \"0x"
<< std::setw(6) << ((v >> 24) & 0xffffff) << "\", \"0x" << std::setw(6)
<< (v & 0xffffff) << "\"]";
return s.str();
}
void MalignBuffer::RandomFlush(std::knuth_b *rng) const {
#if defined(__i386__) || defined(__x86_64__)
// Note: no barriers used.
const char *p = buffer_address_ + alignment_offset_;
while (p < buffer_address_ + length_) {
if (std::uniform_int_distribution<int>(0, 1)(*rng)) {
__builtin_ia32_clflush(p);
}
p += kCacheLineSize;
}
#endif
}
std::string MalignBuffer::PunchedHole::ToString() const {
if (length) {
return JsonRecord("hole", Json("start", start) + ", " +
Json("length", length) + ", " +
Json("v", static_cast<int>(v)));
} else {
return JsonNull("hole");
}
}
void MalignBuffer::Memset(size_t offset, unsigned char v, size_t length,
bool use_rep_stos) {
if (use_rep_stos) {
__stosb(data() + offset, v, length);
} else {
memset(data() + offset, v, length);
}
}
void MalignBuffer::PunchHole(const PunchedHole &hole, bool use_rep_stos) {
if (hole.length) {
Memset(hole.start, hole.v, hole.length, use_rep_stos);
}
}
// Hints to the OS to release the buffer's memory.
void MalignBuffer::MadviseDontNeed() const {
// Round up the buffer start address to a page boundary.
intptr_t start = ((intptr_t)data() + kPageSize - 1) & ~(kPageSize - 1);
// Round down the buffer end address to a page boundary.
intptr_t end = ((intptr_t)(data() + size() - 1)) & ~(kPageSize - 1);
if (end > start) {
const size_t length = end - start;
if (madvise((char *)start, length, MADV_DONTNEED) == -1) {
LOG(WARN) << "tid "
<< " madvise(MADV_DONTNEED) failed: " << strerror(errno)
<< " length: " << length;
}
}
}
MalignBuffer::PunchedHole MalignBuffer::RandomPunchedHole(uint64_t seed) const {
std::knuth_b rng(seed);
MalignBuffer::PunchedHole hole;
hole.length = std::uniform_int_distribution<size_t>(
1, std::min<size_t>(length_, 8192))(rng);
hole.start =
std::uniform_int_distribution<size_t>(0, length_ - hole.length)(rng);
return hole;
}
} // namespace cpu_check