summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatt Morehouse <mascasa@google.com>2018-08-30 15:54:44 +0000
committerMatt Morehouse <mascasa@google.com>2018-08-30 15:54:44 +0000
commit7e042bb1d18a06fc4b67143b5615acb15b4b8c57 (patch)
treeeffd0132d7b8811860d71c417fda7a072c36d482
parenta733d08db251c5c12103a30dd9c3a28f2eb82509 (diff)
downloadbcm5719-llvm-7e042bb1d18a06fc4b67143b5615acb15b4b8c57.tar.gz
bcm5719-llvm-7e042bb1d18a06fc4b67143b5615acb15b4b8c57.zip
[libFuzzer] Port to Windows
Summary: Port libFuzzer to windows-msvc. This patch allows libFuzzer targets to be built and run on Windows, using -fsanitize=fuzzer and/or fsanitize=fuzzer-no-link. It allows these forms of coverage instrumentation to work on Windows as well. It does not fix all issues, such as those with -fsanitize-coverage=stack-depth, which is not usable on Windows as of this patch. It also does not fix any libFuzzer integration tests. Nearly all of them fail to compile, fixing them will come in a later patch, so libFuzzer tests are disabled on Windows until them. Patch By: metzman Reviewers: morehouse, rnk Reviewed By: morehouse, rnk Subscribers: #sanitizers, delcypher, morehouse, kcc, eraman Differential Revision: https://reviews.llvm.org/D51022 llvm-svn: 341082
-rw-r--r--clang/lib/Driver/ToolChains/MSVC.cpp13
-rw-r--r--compiler-rt/cmake/config-ix.cmake5
-rw-r--r--compiler-rt/lib/fuzzer/FuzzerDefs.h7
-rw-r--r--compiler-rt/lib/fuzzer/FuzzerIO.cpp8
-rw-r--r--compiler-rt/lib/fuzzer/FuzzerIO.h2
-rw-r--r--compiler-rt/lib/fuzzer/FuzzerIOPosix.cpp7
-rw-r--r--compiler-rt/lib/fuzzer/FuzzerIOWindows.cpp20
-rw-r--r--compiler-rt/lib/fuzzer/FuzzerTracePC.cpp3
-rw-r--r--compiler-rt/lib/fuzzer/FuzzerUtilWindows.cpp4
-rw-r--r--compiler-rt/lib/fuzzer/tests/CMakeLists.txt2
-rw-r--r--compiler-rt/lib/fuzzer/tests/FuzzerUnittest.cpp7
-rw-r--r--compiler-rt/lib/sanitizer_common/sanitizer_coverage_win_sections.cc53
-rw-r--r--compiler-rt/test/CMakeLists.txt5
-rw-r--r--llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp39
-rw-r--r--llvm/test/Instrumentation/SanitizerCoverage/coff-pc-table-inline-8bit-counters.ll12
15 files changed, 161 insertions, 26 deletions
diff --git a/clang/lib/Driver/ToolChains/MSVC.cpp b/clang/lib/Driver/ToolChains/MSVC.cpp
index d062c6abc95..4df43ac8ce9 100644
--- a/clang/lib/Driver/ToolChains/MSVC.cpp
+++ b/clang/lib/Driver/ToolChains/MSVC.cpp
@@ -365,6 +365,17 @@ void visualstudio::Linker::ConstructJob(Compilation &C, const JobAction &JA,
CmdArgs.push_back(Args.MakeArgString(std::string("-implib:") + ImplibName));
}
+ if (TC.getSanitizerArgs().needsFuzzer()) {
+ if (!Args.hasArg(options::OPT_shared))
+ CmdArgs.push_back(
+ Args.MakeArgString(std::string("-wholearchive:") +
+ TC.getCompilerRTArgString(Args, "fuzzer", false)));
+ CmdArgs.push_back(Args.MakeArgString("-debug"));
+ // Prevent the linker from padding sections we use for instrumentation
+ // arrays.
+ CmdArgs.push_back(Args.MakeArgString("-incremental:no"));
+ }
+
if (TC.getSanitizerArgs().needsAsanRt()) {
CmdArgs.push_back(Args.MakeArgString("-debug"));
CmdArgs.push_back(Args.MakeArgString("-incremental:no"));
@@ -1298,6 +1309,8 @@ MSVCToolChain::ComputeEffectiveClangTriple(const ArgList &Args,
SanitizerMask MSVCToolChain::getSupportedSanitizers() const {
SanitizerMask Res = ToolChain::getSupportedSanitizers();
Res |= SanitizerKind::Address;
+ Res |= SanitizerKind::Fuzzer;
+ Res |= SanitizerKind::FuzzerNoLink;
Res &= ~SanitizerKind::CFIMFCall;
return Res;
}
diff --git a/compiler-rt/cmake/config-ix.cmake b/compiler-rt/cmake/config-ix.cmake
index f3935ffd6fd..1c60c3d0314 100644
--- a/compiler-rt/cmake/config-ix.cmake
+++ b/compiler-rt/cmake/config-ix.cmake
@@ -619,7 +619,10 @@ else()
endif()
if (COMPILER_RT_HAS_SANITIZER_COMMON AND FUZZER_SUPPORTED_ARCH AND
- OS_NAME MATCHES "Android|Darwin|Linux|NetBSD|FreeBSD|OpenBSD|Fuchsia")
+ OS_NAME MATCHES "Android|Darwin|Linux|NetBSD|FreeBSD|OpenBSD|Fuchsia|Windows" AND
+ # TODO: Support builds with MSVC.
+ NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC" AND
+ NOT "${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC")
set(COMPILER_RT_HAS_FUZZER TRUE)
else()
set(COMPILER_RT_HAS_FUZZER FALSE)
diff --git a/compiler-rt/lib/fuzzer/FuzzerDefs.h b/compiler-rt/lib/fuzzer/FuzzerDefs.h
index a35c7a181b7..31655d562cd 100644
--- a/compiler-rt/lib/fuzzer/FuzzerDefs.h
+++ b/compiler-rt/lib/fuzzer/FuzzerDefs.h
@@ -129,8 +129,15 @@
#if LIBFUZZER_WINDOWS
#define ATTRIBUTE_INTERFACE __declspec(dllexport)
+// This is used for __sancov_lowest_stack which is needed for
+// -fsanitize-coverage=stack-depth. That feature is not yet available on
+// Windows, so make the symbol static to avoid linking errors.
+#define ATTRIBUTES_INTERFACE_TLS_INITIAL_EXEC \
+ __attribute__((tls_model("initial-exec"))) thread_local static
#else
#define ATTRIBUTE_INTERFACE __attribute__((visibility("default")))
+#define ATTRIBUTES_INTERFACE_TLS_INITIAL_EXEC \
+ ATTRIBUTE_INTERFACE __attribute__((tls_model("initial-exec"))) thread_local
#endif
namespace fuzzer {
diff --git a/compiler-rt/lib/fuzzer/FuzzerIO.cpp b/compiler-rt/lib/fuzzer/FuzzerIO.cpp
index f3ead0ec535..dac5ec658f1 100644
--- a/compiler-rt/lib/fuzzer/FuzzerIO.cpp
+++ b/compiler-rt/lib/fuzzer/FuzzerIO.cpp
@@ -100,14 +100,6 @@ std::string DirPlusFile(const std::string &DirPath,
return DirPath + GetSeparator() + FileName;
}
-std::string Basename(const std::string &Path, char Separator) {
- size_t Pos = Path.rfind(Separator);
- if (Pos == std::string::npos)
- return Path;
- assert(Pos < Path.size());
- return Path.substr(Pos + 1);
-}
-
void DupAndCloseStderr() {
int OutputFd = DuplicateFile(2);
if (OutputFd > 0) {
diff --git a/compiler-rt/lib/fuzzer/FuzzerIO.h b/compiler-rt/lib/fuzzer/FuzzerIO.h
index 6d7757435b7..b4a68190e78 100644
--- a/compiler-rt/lib/fuzzer/FuzzerIO.h
+++ b/compiler-rt/lib/fuzzer/FuzzerIO.h
@@ -68,7 +68,7 @@ void GetSizedFilesFromDir(const std::string &Dir, Vector<SizedFile> *V);
char GetSeparator();
// Similar to the basename utility: returns the file name w/o the dir prefix.
-std::string Basename(const std::string &Path, char Separator = GetSeparator());
+std::string Basename(const std::string &Path);
FILE* OpenFile(int Fd, const char *Mode);
diff --git a/compiler-rt/lib/fuzzer/FuzzerIOPosix.cpp b/compiler-rt/lib/fuzzer/FuzzerIOPosix.cpp
index 17e884d3c4c..401b4cbbf74 100644
--- a/compiler-rt/lib/fuzzer/FuzzerIOPosix.cpp
+++ b/compiler-rt/lib/fuzzer/FuzzerIOPosix.cpp
@@ -46,6 +46,13 @@ size_t FileSize(const std::string &Path) {
return St.st_size;
}
+std::string Basename(const std::string &Path) {
+ size_t Pos = Path.rfind(GetSeparator());
+ if (Pos == std::string::npos) return Path;
+ assert(Pos < Path.size());
+ return Path.substr(Pos + 1);
+}
+
void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
Vector<std::string> *V, bool TopDir) {
auto E = GetEpoch(Dir);
diff --git a/compiler-rt/lib/fuzzer/FuzzerIOWindows.cpp b/compiler-rt/lib/fuzzer/FuzzerIOWindows.cpp
index 74853646b21..314b79d0f81 100644
--- a/compiler-rt/lib/fuzzer/FuzzerIOWindows.cpp
+++ b/compiler-rt/lib/fuzzer/FuzzerIOWindows.cpp
@@ -72,6 +72,26 @@ bool IsFile(const std::string &Path) {
return IsFile(Path, Att);
}
+std::string Basename(const std::string &Path) {
+ size_t Pos = Path.find_last_of("/\\");
+ if (Pos == std::string::npos) return Path;
+ assert(Pos < Path.size());
+ return Path.substr(Pos + 1);
+}
+
+size_t FileSize(const std::string &Path) {
+ WIN32_FILE_ATTRIBUTE_DATA attr;
+ if (!GetFileAttributesExA(Path.c_str(), GetFileExInfoStandard, &attr)) {
+ Printf("GetFileAttributesExA() failed for \"%s\" (Error code: %lu).\n",
+ Path.c_str(), GetLastError());
+ return 0;
+ }
+ ULARGE_INTEGER size;
+ size.HighPart = attr.nFileSizeHigh;
+ size.LowPart = attr.nFileSizeLow;
+ return size.QuadPart;
+}
+
void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
Vector<std::string> *V, bool TopDir) {
auto E = GetEpoch(Dir);
diff --git a/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp b/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp
index 1aba816e832..75130840ce0 100644
--- a/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp
+++ b/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp
@@ -32,8 +32,7 @@ ATTRIBUTE_INTERFACE
uintptr_t __sancov_trace_pc_pcs[fuzzer::TracePC::kNumPCs];
// Used by -fsanitize-coverage=stack-depth to track stack depth
-ATTRIBUTE_INTERFACE __attribute__((tls_model("initial-exec")))
-thread_local uintptr_t __sancov_lowest_stack;
+ATTRIBUTES_INTERFACE_TLS_INITIAL_EXEC uintptr_t __sancov_lowest_stack;
namespace fuzzer {
diff --git a/compiler-rt/lib/fuzzer/FuzzerUtilWindows.cpp b/compiler-rt/lib/fuzzer/FuzzerUtilWindows.cpp
index 8227e778ea0..257723b6050 100644
--- a/compiler-rt/lib/fuzzer/FuzzerUtilWindows.cpp
+++ b/compiler-rt/lib/fuzzer/FuzzerUtilWindows.cpp
@@ -179,7 +179,9 @@ const void *SearchMemory(const void *Data, size_t DataLen, const void *Patt,
}
std::string DisassembleCmd(const std::string &FileName) {
- if (ExecuteCommand("dumpbin /summary > nul") == 0)
+ Vector<std::string> command_vector;
+ command_vector.push_back("dumpbin /summary > nul");
+ if (ExecuteCommand(Command(command_vector)) == 0)
return "dumpbin /disasm " + FileName;
Printf("libFuzzer: couldn't find tool to disassemble (dumpbin)\n");
exit(1);
diff --git a/compiler-rt/lib/fuzzer/tests/CMakeLists.txt b/compiler-rt/lib/fuzzer/tests/CMakeLists.txt
index ed580716830..0b561c170ec 100644
--- a/compiler-rt/lib/fuzzer/tests/CMakeLists.txt
+++ b/compiler-rt/lib/fuzzer/tests/CMakeLists.txt
@@ -17,6 +17,8 @@ list(APPEND LIBFUZZER_UNITTEST_LINK_FLAGS --driver-mode=g++)
if(APPLE OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
list(APPEND LIBFUZZER_UNITTEST_LINK_FLAGS -lc++ -lpthread)
+elseif(WIN32)
+ list(APPEND LIBFUZZER_UNITTEST_LINK_FLAGS -lstdc++)
else()
list(APPEND LIBFUZZER_UNITTEST_LINK_FLAGS -lstdc++ -lpthread)
endif()
diff --git a/compiler-rt/lib/fuzzer/tests/FuzzerUnittest.cpp b/compiler-rt/lib/fuzzer/tests/FuzzerUnittest.cpp
index e3b06702603..7cdd4458232 100644
--- a/compiler-rt/lib/fuzzer/tests/FuzzerUnittest.cpp
+++ b/compiler-rt/lib/fuzzer/tests/FuzzerUnittest.cpp
@@ -34,6 +34,13 @@ TEST(Fuzzer, Basename) {
EXPECT_EQ(Basename("/bar"), "bar");
EXPECT_EQ(Basename("foo/x"), "x");
EXPECT_EQ(Basename("foo/"), "");
+#if LIBFUZZER_WINDOWS
+ EXPECT_EQ(Basename("foo\\bar"), "bar");
+ EXPECT_EQ(Basename("foo\\bar/baz"), "baz");
+ EXPECT_EQ(Basename("\\bar"), "bar");
+ EXPECT_EQ(Basename("foo\\x"), "x");
+ EXPECT_EQ(Basename("foo\\"), "");
+#endif
}
TEST(Fuzzer, CrossOver) {
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_coverage_win_sections.cc b/compiler-rt/lib/sanitizer_common/sanitizer_coverage_win_sections.cc
index 4b0bbf1ed1e..108f76effee 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_coverage_win_sections.cc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_coverage_win_sections.cc
@@ -7,16 +7,57 @@
//
//===----------------------------------------------------------------------===//
//
-// This file defines delimiters for Sanitizer Coverage's section.
+// This file defines delimiters for Sanitizer Coverage's section. It contains
+// Windows specific tricks to coax the linker into giving us the start and stop
+// addresses of a section, as ELF linkers can do, to get the size of certain
+// arrays. According to https://msdn.microsoft.com/en-us/library/7977wcck.aspx
+// sections with the same name before "$" are sorted alphabetically by the
+// string that comes after "$" and merged into one section. We take advantage
+// of this by putting data we want the size of into the middle (M) of a section,
+// by using the letter "M" after "$". We get the start of this data (ie:
+// __start_section_name) by making the start variable come at the start of the
+// section (using the letter A after "$"). We do the same to get the end of the
+// data by using the letter "Z" after "$" to make the end variable come after
+// the data. Note that because of our technique the address of the start
+// variable is actually the address of data that comes before our middle
+// section. We also need to prevent the linker from adding any padding. Each
+// technique we use for this is explained in the comments below.
//===----------------------------------------------------------------------===//
#include "sanitizer_platform.h"
#if SANITIZER_WINDOWS
#include <stdint.h>
-#pragma section(".SCOV$A", read, write) // NOLINT
-#pragma section(".SCOV$Z", read, write) // NOLINT
extern "C" {
-__declspec(allocate(".SCOV$A")) uint32_t __start___sancov_guards = 0;
-__declspec(allocate(".SCOV$Z")) uint32_t __stop___sancov_guards = 0;
+// The Guard array and counter array should both be merged into the .data
+// section to reduce the number of PE sections However, because PCTable is
+// constant it should be merged with the .rdata section.
+#pragma section(".SCOV$GA", read, write) // NOLINT
+// Use align(1) to avoid adding any padding that will mess up clients trying to
+// determine the start and end of the array.
+__declspec(allocate(".SCOV$GA")) __declspec(align(1)) uint64_t
+ __start___sancov_guards = 0;
+#pragma section(".SCOV$GZ", read, write) // NOLINT
+__declspec(allocate(".SCOV$GZ")) __declspec(align(1)) uint64_t
+ __stop___sancov_guards = 0;
+
+#pragma section(".SCOV$CA", read, write) // NOLINT
+__declspec(allocate(".SCOV$CA")) __declspec(align(1)) uint64_t
+ __start___sancov_cntrs = 0;
+#pragma section(".SCOV$CZ", read, write) // NOLINT
+__declspec(allocate(".SCOV$CZ")) __declspec(align(1)) uint64_t
+ __stop___sancov_cntrs = 0;
+
+#pragma comment(linker, "/MERGE:.SCOV=.data")
+
+// Use uint64_t so there won't be any issues if the linker tries to word align
+// the pc array.
+#pragma section(".SCOVP$A", read) // NOLINT
+__declspec(allocate(".SCOVP$A")) __declspec(align(1)) uint64_t
+ __start___sancov_pcs = 0;
+#pragma section(".SCOVP$Z", read) // NOLINT
+__declspec(allocate(".SCOVP$Z")) __declspec(align(1)) uint64_t
+ __stop___sancov_pcs = 0;
+
+#pragma comment(linker, "/MERGE:.SCOVP=.rdata")
}
-#endif // SANITIZER_WINDOWS
+#endif // SANITIZER_WINDOWS
diff --git a/compiler-rt/test/CMakeLists.txt b/compiler-rt/test/CMakeLists.txt
index 554ba5fa0f0..0a3fc025419 100644
--- a/compiler-rt/test/CMakeLists.txt
+++ b/compiler-rt/test/CMakeLists.txt
@@ -62,7 +62,10 @@ if(COMPILER_RT_CAN_EXECUTE_TESTS)
compiler_rt_test_runtime(sanitizer_common)
# OpenBSD not supporting asan, cannot run the tests
- if(COMPILER_RT_BUILD_LIBFUZZER AND NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "OpenBSD" AND NOT ANDROID)
+ # Tests are broken for now on Windows
+ if(COMPILER_RT_BUILD_LIBFUZZER
+ AND NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "OpenBSD"
+ AND NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "Windows" AND NOT ANDROID)
compiler_rt_test_runtime(fuzzer)
endif()
diff --git a/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp b/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp
index c0b219d1d1b..1f97e909851 100644
--- a/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp
+++ b/llvm/lib/Transforms/Instrumentation/SanitizerCoverage.cpp
@@ -273,9 +273,20 @@ Function *SanitizerCoverageModule::CreateInitCallsForSections(
auto SecStart = SecStartEnd.first;
auto SecEnd = SecStartEnd.second;
Function *CtorFunc;
+ Value *SecStartPtr = nullptr;
+ // Account for the fact that on windows-msvc __start_* symbols actually
+ // point to a uint64_t before the start of the array.
+ if (TargetTriple.getObjectFormat() == Triple::COFF) {
+ auto SecStartI8Ptr = IRB.CreatePointerCast(SecStart, Int8PtrTy);
+ auto GEP = IRB.CreateGEP(SecStartI8Ptr,
+ ConstantInt::get(IntptrTy, sizeof(uint64_t)));
+ SecStartPtr = IRB.CreatePointerCast(GEP, Ty);
+ } else {
+ SecStartPtr = IRB.CreatePointerCast(SecStart, Ty);
+ }
std::tie(CtorFunc, std::ignore) = createSanitizerCtorAndInitFunctions(
M, SanCovModuleCtorName, InitFunctionName, {Ty, Ty},
- {IRB.CreatePointerCast(SecStart, Ty), IRB.CreatePointerCast(SecEnd, Ty)});
+ {SecStartPtr, IRB.CreatePointerCast(SecEnd, Ty)});
if (TargetTriple.supportsCOMDAT()) {
// Use comdat to dedup CtorFunc.
@@ -397,9 +408,20 @@ bool SanitizerCoverageModule::runOnModule(Module &M) {
Function *InitFunction = declareSanitizerInitFunction(
M, SanCovPCsInitName, {IntptrPtrTy, IntptrPtrTy});
IRBuilder<> IRBCtor(Ctor->getEntryBlock().getTerminator());
- IRBCtor.CreateCall(InitFunction,
- {IRB.CreatePointerCast(SecStartEnd.first, IntptrPtrTy),
- IRB.CreatePointerCast(SecStartEnd.second, IntptrPtrTy)});
+ Value *SecStartPtr = nullptr;
+ // Account for the fact that on windows-msvc __start_pc_table actually
+ // points to a uint64_t before the start of the PC table.
+ if (TargetTriple.getObjectFormat() == Triple::COFF) {
+ auto SecStartI8Ptr = IRB.CreatePointerCast(SecStartEnd.first, Int8PtrTy);
+ auto GEP = IRB.CreateGEP(SecStartI8Ptr,
+ ConstantInt::get(IntptrTy, sizeof(uint64_t)));
+ SecStartPtr = IRB.CreatePointerCast(GEP, IntptrPtrTy);
+ } else {
+ SecStartPtr = IRB.CreatePointerCast(SecStartEnd.first, IntptrPtrTy);
+ }
+ IRBCtor.CreateCall(
+ InitFunction,
+ {SecStartPtr, IRB.CreatePointerCast(SecStartEnd.second, IntptrPtrTy)});
}
// We don't reference these arrays directly in any of our runtime functions,
// so we need to prevent them from being dead stripped.
@@ -809,8 +831,13 @@ void SanitizerCoverageModule::InjectCoverageAtBlock(Function &F, BasicBlock &BB,
std::string
SanitizerCoverageModule::getSectionName(const std::string &Section) const {
- if (TargetTriple.getObjectFormat() == Triple::COFF)
- return ".SCOV$M";
+ if (TargetTriple.getObjectFormat() == Triple::COFF) {
+ if (Section == SanCovCountersSectionName)
+ return ".SCOV$CM";
+ if (Section == SanCovPCsSectionName)
+ return ".SCOVP$M";
+ return ".SCOV$GM"; // For SanCovGuardsSectionName.
+ }
if (TargetTriple.isOSBinFormatMachO())
return "__DATA,__" + Section;
return "__" + Section;
diff --git a/llvm/test/Instrumentation/SanitizerCoverage/coff-pc-table-inline-8bit-counters.ll b/llvm/test/Instrumentation/SanitizerCoverage/coff-pc-table-inline-8bit-counters.ll
new file mode 100644
index 00000000000..d81d480009b
--- /dev/null
+++ b/llvm/test/Instrumentation/SanitizerCoverage/coff-pc-table-inline-8bit-counters.ll
@@ -0,0 +1,12 @@
+; Checks that the PC and 8-bit Counter Arrays are placed in their own sections in COFF binaries.
+; RUN: opt < %s -sancov -sanitizer-coverage-level=1 -sanitizer-coverage-inline-8bit-counters=1 -sanitizer-coverage-pc-table=1 -S | FileCheck %s
+target datalayout = "e-m:w-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-pc-windows-msvc19.14.26433"
+
+define void @foo() {
+entry:
+ ret void
+}
+
+; CHECK-DAG: section ".SCOV{{\$}}CM",
+; CHECK-DAG: section ".SCOVP{{\$}}M",
OpenPOWER on IntegriCloud