Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ include_directories(inc ${Boost_INCLUDE_DIRS} ${LEATHERMAN_INCLUDE_DIRS})
set(PROJECT_SOURCES
"src/${PROJECT_NAME}.cc"
"src/detectors/virtualbox_detector.cc"
"src/sources/cpuid_source.cc"
"src/sources/dmi_source.cc")

## An object target is generated that can be used by both the library and test executable targets.
Expand Down
5 changes: 4 additions & 1 deletion lib/inc/internal/detectors/virtualbox_detector.hpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
#pragma once

#include <internal/sources/dmi_source.hpp>
#include <internal/sources/cpuid_source.hpp>

namespace whereami { namespace detectors {

/**
* VirtualBox detector function
* @param cpuid_source An instance of a CPUID data source
* @param dmi_source An instance of a DMI data source
* @return Whether this machine is a VirtualBox guest
*/
bool virtualbox(const sources::dmi_base& dmi_source);
bool virtualbox(const sources::cpuid_base& cpuid_source,
const sources::dmi_base& dmi_source);

}} // namespace whereami::detectors
68 changes: 68 additions & 0 deletions lib/inc/internal/sources/cpuid_source.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#pragma once

#include <string>

namespace whereami { namespace sources {

/**
* Register values returned by CPUID
*/
struct cpuid_registers
{
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say these comments are redundant, but I think travis will fail if we don't have 100% docs coverage :/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is correct! 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they have to be here, maybe we could make them a little more descriptive? Something like "Value from eax register", etc. Still redundant, but slightly less general (I'd argue any field could accurately be annotated with " value" :P).

* Value from eax register
*/
unsigned int eax;
/**
* Value from ebx register
*/
unsigned int ebx;
/**
* Value from ecx register
*/
unsigned int ecx;
/**
* Value from edx register
*/
unsigned int edx;
};

/**
* Base cpuid data source
*/
class cpuid_base
{
public:
/**
* Call the cpuid instruction
* @param leaf The value to pass into CPUID via eax; Determines the type of information returned
* @return Register object with results of the CPUID function
*/
virtual cpuid_registers read_cpuid(unsigned int leaf) const;
/**
* Determine whether CPUID reports that this system is running on a hypervisor (Calls CPUID with eax = 1)
* @return whether CPUID reports a hypervisor
*/
bool has_hypervisor() const;
/**
* Retrieve the vendor ID (Calls CPUID with eax = VENDOR_LEAF)
* @return the vendor ID string
*/
std::string vendor() const;
/**
* Most hypervisors store vendor information in this leaf
* When this value is passed to CPUID, ebx, ecx, and edx report a vendor ID
*/
static const unsigned int VENDOR_LEAF = 0x40000000;
/**
* When CPUID is passed a 1, bit 31 of ecx reports whether the machine is running on a hypervisor
*/
static const unsigned int HYPERVISOR_PRESENT = 1;
};

/**
* Default CPUID data source; Requires no extra functionality beyond the base.
*/
using cpuid = cpuid_base;

}} // namespace whereami::sources
11 changes: 4 additions & 7 deletions lib/src/detectors/virtualbox_detector.cc
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
#include <internal/detectors/virtualbox_detector.hpp>
#include <leatherman/logging/logging.hpp>
#include <leatherman/execution/execution.hpp>
#include <leatherman/util/regex.hpp>

using namespace std;
using namespace whereami;
using namespace leatherman::execution;
using namespace leatherman::util;

namespace whereami { namespace detectors {

bool virtualbox(const sources::dmi_base& dmi_source) {
bool virtualbox(const sources::cpuid_base& cpuid_source,
const sources::dmi_base& dmi_source) {
static const boost::regex re_virtualbox{"[Vv]irtual[Bb]ox"};

return re_search(
dmi_source.product_name(),
re_virtualbox);
return cpuid_source.vendor() == "VBoxVBoxVBox" ||
re_search(dmi_source.product_name(), re_virtualbox);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

longer-term we're going to want to be able to track which data sources were successful, but this is good until we have a metadata object to work with 👍

}

}} // namespace whereami::detectors
36 changes: 36 additions & 0 deletions lib/src/sources/cpuid_source.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <internal/sources/cpuid_source.hpp>

using namespace std;
using namespace whereami::sources;

namespace whereami { namespace sources {

#if defined(__x86_64__) || defined(__i386__)
cpuid_registers cpuid_base::read_cpuid(unsigned int leaf) const {
cpuid_registers result;
asm volatile(
"xchgl %%ebx,%1; xor %%ebx,%%ebx; cpuid; xchgl %%ebx,%1"
: "=a" (result.eax), "=b" (result.ebx), "=c" (result.ecx), "=d" (result.edx)
: "a" (leaf), "c" (0));
return result;
}
#else
cpuid_registers cpuid_base::read_cpuid(unsigned int leaf) const {
return {};
}
#endif

bool cpuid_base::has_hypervisor() const
{
auto regs = read_cpuid(HYPERVISOR_PRESENT);
return static_cast<bool>(regs.ecx & (1 << 31));
}

string cpuid_base::vendor() const
{
auto regs = read_cpuid(VENDOR_LEAF);
unsigned int result[4] = {regs.ebx, regs.ecx, regs.edx, 0};
return string{reinterpret_cast<char*>(result)};
}

}}
4 changes: 3 additions & 1 deletion lib/src/whereami.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <whereami/version.h>
#include <internal/vm.hpp>
#include <internal/sources/dmi_source.hpp>
#include <internal/sources/cpuid_source.hpp>
#include <internal/detectors/virtualbox_detector.hpp>
#include <leatherman/logging/logging.hpp>

Expand All @@ -20,8 +21,9 @@ namespace whereami {
{
vector<string> result;
sources::dmi dmi_source;
sources::cpuid cpuid_source;

auto virtualbox_result = detectors::virtualbox(dmi_source);
auto virtualbox_result = detectors::virtualbox(cpuid_source, dmi_source);

if (virtualbox_result) {
result.emplace_back(vm::virtualbox);
Expand Down
2 changes: 2 additions & 0 deletions lib/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ set(TEST_CASES
"${PROJECT_NAME}.cc"
"detectors/virtualbox_detector.cc"
"fixtures.cc"
"fixtures/cpuid_fixtures.cc"
"fixtures/dmi_fixtures.cc"
"sources/cpuid_source.cc"
"sources/dmi_source.cc")

add_executable(lib${PROJECT_NAME}_test $<TARGET_OBJECTS:libprojectsrc> ${TEST_CASES} main.cc)
Expand Down
40 changes: 33 additions & 7 deletions lib/tests/detectors/virtualbox_detector.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,55 @@
#include <internal/sources/dmi_source.hpp>
#include <internal/detectors/virtualbox_detector.hpp>
#include "../fixtures/dmi_fixtures.hpp"
#include "../fixtures/cpuid_fixtures.hpp"

using namespace std;
using namespace whereami::detectors;
using namespace whereami::sources;
using namespace whereami::testing::dmi;
using namespace whereami::testing::cpuid;

SCENARIO("Using the VirtualBox detector") {
WHEN("running in a VirtualBox VM") {
dmi_fixture_values dmi_virtualbox({
WHEN("running on a Linux VirtualBox guest") {
cpuid_fixture_values cpuid_source({
{VENDOR_LEAF, register_fixtures::VENDOR_KVMKVMKVM},
{HYPERVISOR_PRESENT, register_fixtures::HYPERVISOR_PRESENT},
});
dmi_fixture_values dmi_source({
"VirtualBox",
"VirtualBox",
"Oracle Corporation",
"innotek GmbH",
"VirtualBox" });
"VirtualBox", });
THEN("it should return true") {
REQUIRE(virtualbox(dmi_virtualbox));
REQUIRE(virtualbox(cpuid_source, dmi_source));
}
}

WHEN("running elsewhere") {
dmi_fixture_empty dmi_empty;
WHEN("running on a Windows VirtualBox guest") {
cpuid_fixture_values cpuid_source({
{VENDOR_LEAF, register_fixtures::VENDOR_VBoxVBoxVBox},
{HYPERVISOR_PRESENT, register_fixtures::HYPERVISOR_PRESENT}
});
dmi_fixture_empty dmi_source;
THEN("it should return true") {
REQUIRE(virtualbox(cpuid_source, dmi_source));
}
}

WHEN("running outside of VirtualBox") {
cpuid_fixture_values cpuid_source({
{VENDOR_LEAF, register_fixtures::VENDOR_KVMKVMKVM},
{HYPERVISOR_PRESENT, register_fixtures::HYPERVISOR_PRESENT}
});
dmi_fixture_values dmi_source({
"Other",
"Other",
"Other",
"Other",
"Other", });
THEN("it should return false") {
REQUIRE_FALSE(virtualbox(dmi_empty));
REQUIRE_FALSE(virtualbox(cpuid_source, dmi_source));
}
}
}
28 changes: 28 additions & 0 deletions lib/tests/fixtures/cpuid_fixtures.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include "./cpuid_fixtures.hpp"

using namespace whereami::testing;
using namespace whereami::sources;
using namespace std;

namespace whereami { namespace testing { namespace cpuid {

cpuid_registers cpuid_fixture_empty::read_cpuid(unsigned int leaf) const
{
return {};
}

cpuid_fixture_values::cpuid_fixture_values(std::unordered_map<unsigned int, sources::cpuid_registers> values)
: register_values_(std::move(values))
{
}

cpuid_registers cpuid_fixture_values::read_cpuid(unsigned int leaf) const
{
auto it = register_values_.find(leaf);
if (it == register_values_.end()) {
return {};
}
return it->second;
}

}}} // namespace whereami::testing::cpuid
44 changes: 44 additions & 0 deletions lib/tests/fixtures/cpuid_fixtures.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "../fixtures.hpp"
#include <internal/sources/cpuid_source.hpp>
#include <unordered_map>

namespace whereami { namespace testing { namespace cpuid {

/**
* CPUID data source returning no usable information
*/
class cpuid_fixture_empty : public sources::cpuid_base {
public:
sources::cpuid_registers read_cpuid(unsigned int leaf) const override;
};

/**
* CPUID data source with predefined register results
*/
class cpuid_fixture_values : public sources::cpuid_base {
public:
cpuid_fixture_values(std::unordered_map<unsigned int, sources::cpuid_registers> values);
sources::cpuid_registers read_cpuid(unsigned int leaf) const override;

protected:
std::unordered_map<unsigned int, sources::cpuid_registers> register_values_;
};

/**
* Expected raw register values
*/
namespace register_fixtures {
static const sources::cpuid_registers HYPERVISOR_ABSENT{0, 0, 0, 0};
static const sources::cpuid_registers HYPERVISOR_PRESENT{0, 0, 3736609283, 0};
static const sources::cpuid_registers VENDOR_NONE{0, 0, 0, 0};
static const sources::cpuid_registers VENDOR_KVMKVMKVM{0, 1263359563, 1447775574, 77};
static const sources::cpuid_registers VENDOR_VBoxVBoxVBox{0, 2020557398, 2020557398, 2020557398};
}

/**
* Aliased here to allow use in cpuid fixture initialization lists
*/
static const unsigned int VENDOR_LEAF = sources::cpuid_base::VENDOR_LEAF;
static const unsigned int HYPERVISOR_PRESENT = sources::cpuid_base::HYPERVISOR_PRESENT;

}}}; // namespace whereami::testing::cpuid
47 changes: 47 additions & 0 deletions lib/tests/sources/cpuid_source.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include <catch.hpp>
#include <iostream>
#include "../fixtures/cpuid_fixtures.hpp"

using namespace std;
using namespace whereami::sources;
using namespace whereami::testing::cpuid;

SCENARIO("No cpuid information is available") {
cpuid_fixture_empty cpuid_source;
WHEN("attempting to call cpuid") {
THEN("no information is found") {
REQUIRE(cpuid_source.vendor().empty());
REQUIRE_FALSE(cpuid_source.has_hypervisor());
}
}
}

SCENARIO("Using the cpuid data source on a virtual machine") {
WHEN("looking for a hypervisor") {
cpuid_fixture_values cpuid_source({
{VENDOR_LEAF, register_fixtures::VENDOR_VBoxVBoxVBox},
{HYPERVISOR_PRESENT, register_fixtures::HYPERVISOR_PRESENT},
});
THEN("a hypervisor is detected") {
REQUIRE(cpuid_source.has_hypervisor());
}
THEN("a vendor ID is found") {
REQUIRE(cpuid_source.vendor() == "VBoxVBoxVBox");
}
}
}

SCENARIO("Using the cpuid data source on a physical machine") {
WHEN("looking for a hypervisor") {
cpuid_fixture_values cpuid_source({
{VENDOR_LEAF, register_fixtures::VENDOR_NONE},
{HYPERVISOR_PRESENT, register_fixtures::HYPERVISOR_ABSENT},
});
THEN("no hypervisor is detected") {
REQUIRE_FALSE(cpuid_source.has_hypervisor());
}
THEN("no vendor ID is found") {
REQUIRE(cpuid_source.vendor() == "");
}
}
}