blob: 05ad24208341055b7a4e559091454174765a6213 [file] [log] [blame]
// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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 <algorithm>
#include "starboard/memory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace starboard {
namespace nplb {
namespace {
#if SB_HAS(MMAP)
const size_t kSize = SB_MEMORY_PAGE_SIZE * 8;
const void* kFailed = SB_MEMORY_MAP_FAILED;
TEST(SbMemoryMapTest, AllocatesNormally) {
void* memory = SbMemoryMap(kSize, kSbMemoryMapProtectRead, "test");
ASSERT_NE(kFailed, memory);
EXPECT_TRUE(SbMemoryUnmap(memory, kSize));
}
TEST(SbMemoryMapTest, AllocatesZero) {
void* memory = SbMemoryMap(0, kSbMemoryMapProtectRead, "test");
ASSERT_EQ(kFailed, memory);
EXPECT_FALSE(SbMemoryUnmap(memory, 0));
}
TEST(SbMemoryMapTest, AllocatesOne) {
void* memory = SbMemoryMap(1, kSbMemoryMapProtectRead, "test");
ASSERT_NE(kFailed, memory);
EXPECT_TRUE(SbMemoryUnmap(memory, 1));
}
TEST(SbMemoryMapTest, AllocatesOnePage) {
void* memory =
SbMemoryMap(SB_MEMORY_PAGE_SIZE, kSbMemoryMapProtectRead, "test");
ASSERT_NE(kFailed, memory);
EXPECT_TRUE(SbMemoryUnmap(memory, SB_MEMORY_PAGE_SIZE));
}
TEST(SbMemoryMapTest, DoesNotLeak) {
// Map 4x the amount of system memory (sequentially, not at once).
int64_t bytes_mapped = SbSystemGetTotalCPUMemory() / 4;
for (int64_t total_bytes_mapped = 0;
total_bytes_mapped < SbSystemGetTotalCPUMemory() * 3;
total_bytes_mapped += bytes_mapped) {
void* memory = SbMemoryMap(bytes_mapped, kSbMemoryMapProtectWrite, "test");
ASSERT_NE(kFailed, memory);
// If this is the last iteration of the loop, then force a page commit for
// every single page. For any other iteration, force a page commit for
// roughly 1000 of the pages.
bool last_iteration =
!(total_bytes_mapped + bytes_mapped < SbSystemGetTotalCPUMemory() * 4);
uint8_t* first_page = static_cast<uint8_t*>(memory);
const size_t page_increment_factor =
(last_iteration)
? size_t(1u)
: std::max(static_cast<size_t>(bytes_mapped /
(1000 * SB_MEMORY_PAGE_SIZE)),
size_t(1u));
for (uint8_t* page = first_page; page < first_page + bytes_mapped;
page += SB_MEMORY_PAGE_SIZE * page_increment_factor) {
*page = 0x55;
}
EXPECT_TRUE(SbMemoryUnmap(memory, bytes_mapped));
}
}
TEST(SbMemoryMapTest, CanReadWriteToResult) {
void* memory = SbMemoryMap(kSize, kSbMemoryMapProtectReadWrite, "test");
ASSERT_NE(kFailed, memory);
char* data = static_cast<char*>(memory);
for (int i = 0; i < kSize; ++i) {
data[i] = static_cast<char>(i);
}
for (int i = 0; i < kSize; ++i) {
EXPECT_EQ(data[i], static_cast<char>(i));
}
EXPECT_TRUE(SbMemoryUnmap(memory, kSize));
}
#if SB_CAN(MAP_EXECUTABLE_MEMORY)
typedef int (*SumFunction)(int, int);
static int sum(int x, int y) {
return x + y;
}
static int sum2(int x, int y) {
return x + y + x;
}
// This test is known to run on x64, ARM, and MIPS32 with MIPS32 and MIPS16
// instructions.
TEST(SbMemoryMapTest, CanExecuteMappedMemoryWithExecFlag) {
void* memory = SbMemoryMap(
kSize, kSbMemoryMapProtectReadWrite | kSbMemoryMapProtectExec, "test");
ASSERT_NE(kFailed, memory);
// There's no reliable way to determine the size of the 'sum' function. If we
// assume the function is at most a certain size, then we might try to read
// beyond mapped memory when copying it to the destination address. We can
// get a reasonable upper bound by assuming that the function's implementation
// does not cross a page boundary, and copy the memory from the beginning of
// the function to the end of the page that it is mapped to.
//
// However, since it's possible the function may cross the page boundary, we
// define two functions and use the one closest to the start of a page. There
// is no guarantee that the linker will place these definitions sequentially
// (although it likely will), so we can't use the address of 'sum2' as the
// end of 'sum'.
//
// To avoid the possibility that COMDAT folding will combine these two
// definitions into one, make sure they are different.
// A function pointer can't be cast to void*, but uint8* seems to be okay. So
// cast to a uint* which will be implicitly casted to a void* below.
SumFunction original_function = &sum;
if (reinterpret_cast<uintptr_t>(&sum2) % SB_MEMORY_PAGE_SIZE <
reinterpret_cast<uintptr_t>(&sum) % SB_MEMORY_PAGE_SIZE) {
original_function = &sum2;
}
uint8_t* sum_function_start = reinterpret_cast<uint8_t*>(original_function);
// MIPS16 instruction are kept odd addresses to differentiate between that and
// MIPS32 instructions. Most other instructions are aligned to at least even
// addresses, so this code should do nothing for those architectures.
// https://www.linux-mips.org/wiki/MIPS16
ptrdiff_t sum_function_offset =
sum_function_start -
reinterpret_cast<uint8_t*>(
reinterpret_cast<uintptr_t>(sum_function_start) & ~0x1);
sum_function_start -= sum_function_offset;
// Get the last address of the page that |sum_function_start| is on.
uint8_t* sum_function_page_end = reinterpret_cast<uint8_t*>(
(reinterpret_cast<uintptr_t>(sum_function_start) / SB_MEMORY_PAGE_SIZE) *
SB_MEMORY_PAGE_SIZE +
SB_MEMORY_PAGE_SIZE);
ASSERT_TRUE(SbMemoryIsAligned(sum_function_page_end, SB_MEMORY_PAGE_SIZE));
ASSERT_LT(sum_function_start, sum_function_page_end);
size_t bytes_to_copy = sum_function_page_end - sum_function_start;
ASSERT_LE(bytes_to_copy, SB_MEMORY_PAGE_SIZE);
SbMemoryCopy(memory, sum_function_start, bytes_to_copy);
SbMemoryFlush(memory, bytes_to_copy);
SumFunction mapped_function = reinterpret_cast<SumFunction>(
reinterpret_cast<uint8_t*>(memory) + sum_function_offset);
EXPECT_EQ((*original_function)(1, 3), (*mapped_function)(1, 3));
EXPECT_EQ((*original_function)(10, -5), (*mapped_function)(10, -5));
EXPECT_TRUE(SbMemoryUnmap(memory, kSize));
}
#endif // SB_CAN(MAP_EXECUTABLE_MEMORY)
#endif // SB_HAS(MMAP)
} // namespace
} // namespace nplb
} // namespace starboard