diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d3d226133a..15e25b62d23 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,13 +67,13 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) option(WITH_GUI "Setting this option will enable the Qt-based UI client." ON) -if(NOT WIN32) - option(WITH_VULKAN "Setting this option will enable Vulkan" ON) +#if(NOT WIN32) +# option(WITH_VULKAN "Setting this option will enable Vulkan" ON) - if(WITH_VULKAN) - add_definitions(-DVULKAN_ENABLED) - endif() -endif() +# if(WITH_VULKAN) +# add_definitions(-DVULKAN_ENABLED) +# endif() +#endif() # This is only for designated initializers if(WIN32) @@ -82,6 +82,8 @@ else() set(CMAKE_CXX_STANDARD 17) endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_BINARY_DIR}" "${CMAKE_SOURCE_DIR}/cmake") @@ -104,7 +106,7 @@ find_package(Threads REQUIRED) find_package(xxHash REQUIRED) find_package(concurrentqueue REQUIRED) find_package(gte REQUIRED) -find_package(libprotobuf-mutator REQUIRED) +#find_package(libprotobuf-mutator REQUIRED) find_package(LZMA REQUIRED) find_package(absl REQUIRED) find_package(LLVM REQUIRED) @@ -144,7 +146,7 @@ endif() include("cmake/protobuf.cmake") include("cmake/grpc_helper.cmake") -include("cmake/fuzzing.cmake") +#include("cmake/fuzzing.cmake") include("cmake/tests.cmake") include("cmake/iwyu.cmake") enable_testing() @@ -211,7 +213,7 @@ add_subdirectory(src/Containers) add_subdirectory(src/CrashService) add_subdirectory(src/DisplayFormats) add_subdirectory(src/FakeProducerSideService) -add_subdirectory(src/FuzzingUtils) +#add_subdirectory(src/FuzzingUtils) add_subdirectory(src/GrpcProtos) add_subdirectory(src/Introspection) add_subdirectory(src/ModuleUtils) diff --git a/conanfile.py b/conanfile.py index 2e9235b1504..31dcf1081c3 100644 --- a/conanfile.py +++ b/conanfile.py @@ -21,9 +21,9 @@ def requirements(self): self.requires("outcome/2.2.9") self.requires("llvm-core/13.0.0") if self.settings.os != "Windows": - self.requires("volk/1.3.268.0") - self.requires("vulkan-headers/1.3.290.0") - self.requires("vulkan-validationlayers/1.3.290.0") + self.requires("volk/1.3.239.0") + self.requires("vulkan-headers/1.3.239") + #self.requires("vulkan-validationlayers/1.3.239.0") self.requires("zlib/1.3.1", override=True) self.requires("openssl/3.3.2", override=True) self.requires("libssh2/1.11.0") diff --git a/src/CaptureClient/CMakeLists.txt b/src/CaptureClient/CMakeLists.txt index 7997f20eb22..10b14549a80 100644 --- a/src/CaptureClient/CMakeLists.txt +++ b/src/CaptureClient/CMakeLists.txt @@ -38,10 +38,6 @@ target_link_libraries(CaptureClient PUBLIC GrpcProtos Introspection) -add_fuzzer(CaptureEventProcessorProcessEventsFuzzer CaptureEventProcessorProcessEventsFuzzer.cpp) -target_link_libraries(CaptureEventProcessorProcessEventsFuzzer - PRIVATE CaptureClient FuzzingUtils) - add_executable(CaptureClientTests) target_sources(CaptureClientTests PRIVATE diff --git a/src/ClientData/CMakeLists.txt b/src/ClientData/CMakeLists.txt index 69025534808..f9c9f44a93a 100644 --- a/src/ClientData/CMakeLists.txt +++ b/src/ClientData/CMakeLists.txt @@ -126,8 +126,3 @@ target_link_libraries(ClientDataTests PRIVATE GTest_Main) register_test(ClientDataTests) - -add_fuzzer(ModuleLoadSymbolsFuzzer ModuleLoadSymbolsFuzzer.cpp) -target_link_libraries( - ModuleLoadSymbolsFuzzer PRIVATE ClientData - FuzzingUtils) \ No newline at end of file diff --git a/src/ObjectUtils/CMakeLists.txt b/src/ObjectUtils/CMakeLists.txt index 8c16abf65c3..5e734781e13 100644 --- a/src/ObjectUtils/CMakeLists.txt +++ b/src/ObjectUtils/CMakeLists.txt @@ -98,5 +98,3 @@ target_link_libraries( register_test(ObjectUtilsTests) -add_fuzzer(ElfFileLoadSymbolsFuzzer ElfFileLoadSymbolsFuzzer.cpp) -target_link_libraries(ElfFileLoadSymbolsFuzzer FuzzingUtils ObjectUtils) diff --git a/src/OrbitBase/AnyErrorOfTest.cpp b/src/OrbitBase/AnyErrorOfTest.cpp deleted file mode 100644 index 047baf92690..00000000000 --- a/src/OrbitBase/AnyErrorOfTest.cpp +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) 2023 The Orbit Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include - -#include -#include -#include -#include - -#include "OrbitBase/AnyErrorOf.h" -#include "OrbitBase/Result.h" -#include "TestUtils/TestUtils.h" - -namespace orbit_base { -using orbit_test_utils::HasError; -using testing::Eq; - -namespace { -// We are defining 6 arbitrary error types here. E1..E3 are copyable, while U1...U3 are move-only. -// All error types need to have `.message()` member function that returns something convertible to -// `std::string`. - -template -struct ErrorBase { - [[nodiscard]] static std::string_view message() { return {}; } - - // The error types don't hold any state, so all instances are equal to each other. - [[nodiscard]] friend bool operator==(const T& /*unused*/, const T& /*unused*/) { return true; } - [[nodiscard]] friend bool operator!=(const T& /*unused*/, const T& /*unused*/) { return false; } -}; - -struct E1 : ErrorBase {}; -struct E2 : ErrorBase {}; -struct E3 : ErrorBase {}; - -template -struct MoveOnlyErrorBase : ErrorBase { - MoveOnlyErrorBase() = default; - MoveOnlyErrorBase(const MoveOnlyErrorBase&) = delete; - MoveOnlyErrorBase(MoveOnlyErrorBase&&) = default; - MoveOnlyErrorBase& operator=(const MoveOnlyErrorBase&) = delete; - MoveOnlyErrorBase& operator=(MoveOnlyErrorBase&&) = default; -}; - -struct U1 : MoveOnlyErrorBase {}; -struct U2 : MoveOnlyErrorBase {}; -struct U3 : MoveOnlyErrorBase {}; -} // namespace - -TEST(AnyErrorOf, CopyConstructionFromErrorType) { - E1 error_value{}; - - // Copy construction - AnyErrorOf error{error_value}; - - EXPECT_TRUE(std::holds_alternative(error)); - EXPECT_EQ(error, E1{}); - EXPECT_NE(error, E2{}); -} - -TEST(AnyErrorOf, MoveConstructionFromErrorType) { - // Move construction - AnyErrorOf error{U1{}}; - - EXPECT_TRUE(std::holds_alternative(error)); - EXPECT_EQ(error, U1{}); - EXPECT_NE(error, E2{}); -} - -TEST(AnyErrorOf, CopyAssignmentFromErrorType) { - E2 error_value{}; - AnyErrorOf error{E1{}}; - - // Copy assignment - error = error_value; - - EXPECT_TRUE(std::holds_alternative(error)); - EXPECT_NE(error, E1{}); - EXPECT_EQ(error, E2{}); -} - -TEST(AnyErrorOf, MoveAssignmentFromErrorType) { - AnyErrorOf error{E1{}}; - - // Move assignment - error = U2{}; - - EXPECT_TRUE(std::holds_alternative(error)); - EXPECT_NE(error, E1{}); - EXPECT_EQ(error, U2{}); -} - -TEST(AnyErrorOf, CopyConstructionFromCompatibleAnyErrorOf) { - AnyErrorOf source{E2{}}; - - // Copy construction - AnyErrorOf destination{source}; - - EXPECT_TRUE(std::holds_alternative(destination)); - EXPECT_NE(destination, E1{}); - EXPECT_EQ(destination, E2{}); - EXPECT_NE(destination, E3{}); -} - -TEST(AnyErrorOf, MoveConstructionFromCompatibleAnyErrorOf) { - AnyErrorOf source{U2{}}; - - // Move construction - AnyErrorOf destination{std::move(source)}; - - EXPECT_TRUE(std::holds_alternative(destination)); - EXPECT_NE(destination, E1{}); - EXPECT_EQ(destination, U2{}); - EXPECT_NE(destination, E3{}); -} - -TEST(AnyErrorOf, CopyAssignmentFromCompatibleAnyErrorOf) { - AnyErrorOf source{E2{}}; - AnyErrorOf destination{}; - - // Copy assignment - destination = source; - - EXPECT_TRUE(std::holds_alternative(destination)); - EXPECT_NE(destination, E1{}); - EXPECT_EQ(destination, E2{}); - EXPECT_NE(destination, E3{}); -} - -TEST(AnyErrorOf, MoveAssignmentFromCompatibleAnyErrorOf) { - AnyErrorOf source{E2{}}; - AnyErrorOf destination{}; - - // Move assignment - destination = std::move(source); - - EXPECT_TRUE(std::holds_alternative(destination)); - EXPECT_NE(destination, U1{}); - EXPECT_EQ(destination, E2{}); - EXPECT_NE(destination, E3{}); -} - -TEST(AnyErrorOf, OutcomeTryConstructsAnyErrorOfFromErrorType) { - const auto converts_result = [&]() -> Result> { - // Imagine we call a function that returns `ErrorMessageOr`, but we have to return - // `Result>`. The needed conversion should be - // seamless. - OUTCOME_TRY((Result{E1{}})); - return outcome::success(); - }; - - EXPECT_THAT(converts_result(), HasError(Eq(E1{}))); -} - -TEST(AnyErrorOf, OutcomeTryConstructsAnyErrorOfFromCompatibleAnyErrorOf) { - const auto converts_result = [&]() -> Result> { - // Imagine we call a function that returns `Result>`, - // but we have to return `Result>`. - // The needed conversion should be seamless. - OUTCOME_TRY((Result>{E1{}})); - return outcome::success(); - }; - - EXPECT_THAT(converts_result(), HasError(Eq(E1{}))); -} -} // namespace orbit_base diff --git a/src/OrbitBase/CMakeLists.txt b/src/OrbitBase/CMakeLists.txt index cfb94478007..c485e2bdf33 100644 --- a/src/OrbitBase/CMakeLists.txt +++ b/src/OrbitBase/CMakeLists.txt @@ -17,7 +17,6 @@ target_include_directories(OrbitBase PRIVATE target_sources(OrbitBase PRIVATE include/OrbitBase/Action.h include/OrbitBase/Align.h - include/OrbitBase/AnyErrorOf.h include/OrbitBase/AnyInvocable.h include/OrbitBase/AnyMovable.h include/OrbitBase/Append.h @@ -109,7 +108,6 @@ add_executable(OrbitBaseTests) target_sources(OrbitBaseTests PRIVATE AlignTest.cpp AnyInvocableTest.cpp - AnyErrorOfTest.cpp AnyMovableTest.cpp AppendTest.cpp CanceledOrTest.cpp diff --git a/src/OrbitBase/include/OrbitBase/AnyErrorOf.h b/src/OrbitBase/include/OrbitBase/AnyErrorOf.h deleted file mode 100644 index f8092eae9d9..00000000000 --- a/src/OrbitBase/include/OrbitBase/AnyErrorOf.h +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2023 The Orbit Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef ORBIT_BASE_ANY_ERROR_OF_H_ -#define ORBIT_BASE_ANY_ERROR_OF_H_ - -#include -#include -#include - -#include "OrbitBase/ParameterPackTrait.h" - -namespace orbit_base { - -// A wrapper around `std::variant` holding one instance of multiple possible error types. It's -// mainly meant to be used with `Result` (`Result`) in cases where a function -// may return one of multiple possible error types. `AnyErrorOf` has a `.message()` member -// function that returns the error message of the holding error by forwarding the call to the -// `.message()` function of the holding error. -// -// `AnyErrorOf` behaves like `std::variant` except for the following properties: -// 1. `ErrorTypes...` may not have duplicate types - all types must be unique. -// 2. An empty list of `ErrorTypes...` is not allowed. -// 3. All error types must have a `.message()` function that's either marked `const` or `static` and -// that returns something that's convertible to `std::string`. -// 4. `AnyErrorOf` can be converted into `AnyErrorOf` if `T2s` contains at least all -// the types present in `T1s`. The order of the types has no meaning. -// 5. `AnyErrorOf` can be directly compared (equality and inequality) to a value of one of its error -// types if the given error type defines an equality comparison operator. The compared values are -// considered equal if `AnyErrorOf` holds a value of the comparing error type and if the equality -// comparison operator returns true. -template -class AnyErrorOf : public std::variant { - using Base = std::variant; - - public: - using Base::Base; - using Base::operator=; - - static_assert(ParameterPackTrait::kSize >= 1, - "AnyError<> (AnyErrorOf with no error types) is not allowed."); - - static_assert(!ParameterPackTrait::kHasDuplicates, - "AnyError must not have duplicate error types."); - - template - constexpr static bool kCanBeConstructedFromTypesAndIsNotCopy = [] { - constexpr ParameterPackTrait kThisTrait{}; - constexpr ParameterPackTrait kOtherTrait{}; - - // We return false if `AnyErrorOf` is the same type as `AnyErrorOf`. - // This avoids collisions with the copy and move constructors/assignment operators. - if (kThisTrait == kOtherTrait) return false; - - // We only allow conversion from an instance of `AnyErrorOf` with `Types` being a - // subset of `ErrorTypes`. - return kThisTrait.IsSubset(kOtherTrait); - }(); - - template - using EnableIfCanBeConstructedFromTypesAndIsNotCopy = - std::enable_if_t, int>; - - // Is true iff `Type` is in the `ErrorTypes...` parameter pack. - template - constexpr static bool kIsAnErrorType = - ParameterPackTrait::template kContains; - - template - auto ToBase(Variant&& other) { - return std::visit( - [](auto&& alternative) -> Base { return std::forward(alternative); }, - std::forward(other)); - } - - // The following converting constructors/assignment operators allow conversion of any AnyErrorOf - // type into a compatible AnyErrorOf type. An AnyErrorOf type is considered compatible - // if its error type list is a super set of the other's error type list. The order of the types - // doesn't matter though. - // - // Examples: - // - AnyErrorOf can be converted into AnyErrorOf but not into AnyErrorOf. - // - AnyErrorOf can be converted into AnyErrorOf. - // - AnyErrorOf can be converted into AnyErrorOf. - template = 0> - // NOLINTNEXTLINE(google-explicit-constructor) - AnyErrorOf(const AnyErrorOf& other) : Base{ToBase(other)} {} - - template = 0> - // NOLINTNEXTLINE(google-explicit-constructor) - AnyErrorOf(AnyErrorOf&& other) : Base{ToBase(std::move(other))} {} - - template = 0> - AnyErrorOf& operator=(const AnyErrorOf& other) { - *this = ToBase(other); - return *this; - } - - template = 0> - AnyErrorOf& operator=(AnyErrorOf&& other) { - *this = ToBase(std::move(other)); - return *this; - } - - [[nodiscard]] std::string message() const { - return std::visit([](const auto& error) { return std::string{error.message()}; }, *this); - } - - // We allow transparent comparison with any of the error types. - template , int> = 0> - [[nodiscard]] friend bool operator==(const AnyErrorOf& lhs, const T& rhs) { - return std::holds_alternative(lhs) && std::get(lhs) == rhs; - } - - template , int> = 0> - [[nodiscard]] friend bool operator==(const T& lhs, const AnyErrorOf& rhs) { - return std::holds_alternative(lhs) && std::get(lhs) == rhs; - } - - template , int> = 0> - [[nodiscard]] friend bool operator!=(const AnyErrorOf& lhs, const T& rhs) { - return !(lhs == rhs); - } - - template , int> = 0> - [[nodiscard]] friend bool operator!=(const T& lhs, const AnyErrorOf& rhs) { - return !(lhs == rhs); - } -}; -} // namespace orbit_base - -#endif // ORBIT_BASE_ANY_ERROR_OF_H_ diff --git a/src/ProcessService/CMakeLists.txt b/src/ProcessService/CMakeLists.txt index 8bcc7112a6e..b2835f76986 100644 --- a/src/ProcessService/CMakeLists.txt +++ b/src/ProcessService/CMakeLists.txt @@ -45,9 +45,3 @@ target_link_libraries(ProcessServiceTests PRIVATE register_test(ProcessServiceTests PROPERTIES TIMEOUT 10) -add_fuzzer(ProcessServiceUtilsFindSymbolsFilePathFuzzer - ProcessServiceUtilsFindSymbolsFilePathFuzzer.cpp) - -target_link_libraries(ProcessServiceUtilsFindSymbolsFilePathFuzzer PRIVATE - FuzzingUtils - ProcessService) diff --git a/src/UserSpaceInstrumentation/TrampolineTest.cpp b/src/UserSpaceInstrumentation/TrampolineTest.cpp index eec24dd7986..d41fb6935ca 100644 --- a/src/UserSpaceInstrumentation/TrampolineTest.cpp +++ b/src/UserSpaceInstrumentation/TrampolineTest.cpp @@ -1,1362 +1,1362 @@ -// Copyright (c) 2021 The Orbit Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "AccessTraceesMemory.h" -#include "AllocateInTracee.h" -#include "GetTestLibLibraryPath.h" -#include "GrpcProtos/module.pb.h" -#include "MachineCode.h" -#include "ModuleUtils/ReadLinuxModules.h" -#include "OrbitBase/ExecutablePath.h" -#include "OrbitBase/Logging.h" -#include "OrbitBase/Result.h" -#include "TestUtils.h" -#include "TestUtils/TestUtils.h" -#include "Trampoline.h" -#include "UserSpaceInstrumentation/AddressRange.h" -#include "UserSpaceInstrumentation/Attach.h" -#include "UserSpaceInstrumentation/InjectLibraryInTracee.h" - -namespace orbit_user_space_instrumentation { -using orbit_test_utils::HasErrorWithMessage; - -namespace { - -using orbit_test_utils::HasErrorWithMessage; -using orbit_test_utils::HasNoError; -using orbit_test_utils::HasValue; -using testing::ElementsAreArray; - -constexpr const char* kEntryPayloadFunctionName = "EntryPayload"; -constexpr const char* kExitPayloadFunctionName = "ExitPayload"; - -extern "C" __attribute__((noinline)) int DoubleAndIncrement(int i) { - i = 2 * i; - return i + 1; -} - -} // namespace - -TEST(TrampolineTest, DoAddressRangesOverlap) { - AddressRange a = {3, 7}; - AddressRange b1 = {1, 2}; - EXPECT_FALSE(DoAddressRangesOverlap(a, b1)); - AddressRange b2 = {1, 3}; - EXPECT_FALSE(DoAddressRangesOverlap(a, b2)); - AddressRange b3 = {1, 4}; - EXPECT_TRUE(DoAddressRangesOverlap(a, b3)); - AddressRange b4 = {1, 9}; - EXPECT_TRUE(DoAddressRangesOverlap(a, b4)); - AddressRange b5 = {4, 5}; - EXPECT_TRUE(DoAddressRangesOverlap(a, b5)); - AddressRange b6 = {4, 9}; - EXPECT_TRUE(DoAddressRangesOverlap(a, b6)); - AddressRange b7 = {7, 9}; - EXPECT_FALSE(DoAddressRangesOverlap(a, b7)); - AddressRange b8 = {8, 9}; - EXPECT_FALSE(DoAddressRangesOverlap(a, b8)); -} - -TEST(TrampolineTest, LowestIntersectingAddressRange) { - const std::vector all_ranges = {{0, 5}, {20, 30}, {40, 60}}; - - EXPECT_FALSE(LowestIntersectingAddressRange({}, {0, 60}).has_value()); - - EXPECT_EQ(0, LowestIntersectingAddressRange(all_ranges, {1, 2})); - EXPECT_EQ(1, LowestIntersectingAddressRange(all_ranges, {21, 22})); - EXPECT_EQ(2, LowestIntersectingAddressRange(all_ranges, {51, 52})); - - EXPECT_EQ(0, LowestIntersectingAddressRange(all_ranges, {3, 6})); - EXPECT_EQ(1, LowestIntersectingAddressRange(all_ranges, {19, 22})); - EXPECT_EQ(2, LowestIntersectingAddressRange(all_ranges, {30, 52})); - - EXPECT_EQ(0, LowestIntersectingAddressRange(all_ranges, {4, 72})); - EXPECT_EQ(1, LowestIntersectingAddressRange(all_ranges, {29, 52})); - EXPECT_EQ(2, LowestIntersectingAddressRange(all_ranges, {59, 72})); - - EXPECT_FALSE(LowestIntersectingAddressRange(all_ranges, {5, 20}).has_value()); - EXPECT_FALSE(LowestIntersectingAddressRange(all_ranges, {30, 40}).has_value()); - EXPECT_FALSE(LowestIntersectingAddressRange(all_ranges, {60, 80}).has_value()); -} - -TEST(TrampolineTest, HighestIntersectingAddressRange) { - const std::vector all_ranges = {{0, 5}, {20, 30}, {40, 60}}; - - EXPECT_FALSE(HighestIntersectingAddressRange({}, {0, 60}).has_value()); - - EXPECT_EQ(0, HighestIntersectingAddressRange(all_ranges, {1, 2})); - EXPECT_EQ(1, HighestIntersectingAddressRange(all_ranges, {21, 22})); - EXPECT_EQ(2, HighestIntersectingAddressRange(all_ranges, {51, 52})); - - EXPECT_EQ(0, HighestIntersectingAddressRange(all_ranges, {3, 6})); - EXPECT_EQ(1, HighestIntersectingAddressRange(all_ranges, {19, 22})); - EXPECT_EQ(2, HighestIntersectingAddressRange(all_ranges, {30, 52})); - - EXPECT_EQ(2, HighestIntersectingAddressRange(all_ranges, {4, 72})); - EXPECT_EQ(2, HighestIntersectingAddressRange(all_ranges, {29, 52})); - EXPECT_EQ(2, HighestIntersectingAddressRange(all_ranges, {59, 72})); - - EXPECT_FALSE(HighestIntersectingAddressRange(all_ranges, {5, 20}).has_value()); - EXPECT_FALSE(HighestIntersectingAddressRange(all_ranges, {30, 40}).has_value()); - EXPECT_FALSE(HighestIntersectingAddressRange(all_ranges, {60, 80}).has_value()); -} - -TEST(TrampolineTest, FindAddressRangeForTrampoline) { - constexpr uint64_t k1Kb = 0x400; - constexpr uint64_t k64Kb = 0x10000; - constexpr uint64_t kOneMb = 0x100000; - constexpr uint64_t k256Mb = 0x10000000; - constexpr uint64_t kOneGb = 0x40000000; - - // Trivial placement to the left. - const std::vector unavailable_ranges1 = { - {0, k64Kb}, {kOneGb, 2 * kOneGb}, {3 * kOneGb, 4 * kOneGb}}; - auto address_range_or_error = - FindAddressRangeForTrampoline(unavailable_ranges1, {kOneGb, 2 * kOneGb}, k256Mb); - ASSERT_FALSE(address_range_or_error.has_error()); - EXPECT_EQ(kOneGb - k256Mb, address_range_or_error.value().start); - - // Placement to the left just fits. - const std::vector unavailable_ranges2 = { - {0, k64Kb}, {k256Mb, kOneGb}, {3 * kOneGb, 4 * kOneGb}}; - address_range_or_error = - FindAddressRangeForTrampoline(unavailable_ranges2, {k256Mb, kOneGb}, k256Mb - k64Kb); - ASSERT_FALSE(address_range_or_error.has_error()); - EXPECT_EQ(k64Kb, address_range_or_error.value().start); - - // Placement to the left fails due to page alignment. So we place to the right which fits - // trivially. - const std::vector unavailable_ranges3 = { - {0, k64Kb + 1}, {k256Mb, kOneGb}, {3 * kOneGb, 4 * kOneGb}}; - address_range_or_error = - FindAddressRangeForTrampoline(unavailable_ranges3, {k256Mb, kOneGb}, k256Mb - k64Kb - 5); - ASSERT_FALSE(address_range_or_error.has_error()); - EXPECT_EQ(kOneGb, address_range_or_error.value().start); - - // Placement to the left just fits but only after a few hops. - const std::vector unavailable_ranges4 = { - {0, k64Kb}, // this is the gap that just fits - {k64Kb + kOneMb, 6 * kOneMb}, - {6 * kOneMb + kOneMb - 1, 7 * kOneMb}, - {7 * kOneMb + kOneMb - 1, 8 * kOneMb}, - {8 * kOneMb + kOneMb - 1, 9 * kOneMb}}; - address_range_or_error = FindAddressRangeForTrampoline( - unavailable_ranges4, {8 * kOneMb + kOneMb - 1, 9 * kOneMb}, kOneMb); - ASSERT_FALSE(address_range_or_error.has_error()); - EXPECT_EQ(k64Kb, address_range_or_error.value().start); - - // No space to the left but trivial placement to the right. - const std::vector unavailable_ranges5 = { - {0, k64Kb}, {kOneMb, kOneGb}, {5 * kOneGb, 6 * kOneGb}}; - address_range_or_error = - FindAddressRangeForTrampoline(unavailable_ranges5, {kOneMb, kOneGb}, kOneMb); - ASSERT_FALSE(address_range_or_error.has_error()) << address_range_or_error.error().message(); - EXPECT_EQ(kOneGb, address_range_or_error.value().start); - - // No space to the left but placement to the right works after a few hops. - const std::vector unavailable_ranges6 = { - {0, k64Kb}, - {kOneMb, kOneGb}, - {kOneGb + 0x01 * kOneMb - 1, kOneGb + 0x10 * kOneMb}, - {kOneGb + 0x11 * kOneMb - 1, kOneGb + 0x20 * kOneMb}, - {kOneGb + 0x21 * kOneMb - 1, kOneGb + 0x30 * kOneMb}, - {kOneGb + 0x31 * kOneMb - 1, kOneGb + 0x40 * kOneMb}}; - address_range_or_error = - FindAddressRangeForTrampoline(unavailable_ranges6, {kOneMb, kOneGb}, kOneMb); - ASSERT_FALSE(address_range_or_error.has_error()) << address_range_or_error.error().message(); - EXPECT_EQ(kOneGb + 0x40 * kOneMb, address_range_or_error.value().start); - - // No space to the left and the last segment nearly fills up the 64 bit address space. So no - // placement is possible. - const std::vector unavailable_ranges7 = { - {0, k64Kb}, - {kOneMb, k256Mb}, - {1 * k256Mb + kOneMb - 1, 2 * k256Mb}, - {2 * k256Mb + kOneMb - 1, 3 * k256Mb}, - {3 * k256Mb + kOneMb - 1, 4 * k256Mb + 1}, // this gap is large but alignment doesn't fit - {4 * k256Mb + kOneMb + 2, 5 * k256Mb}, - {5 * k256Mb + kOneMb - 1, 0xffffffffffffffff - kOneMb / 2}}; - address_range_or_error = - FindAddressRangeForTrampoline(unavailable_ranges7, {kOneMb, k256Mb}, kOneMb); - ASSERT_TRUE(address_range_or_error.has_error()); - - // There is no sufficiently large gap in the mappings in the 2GB below the code segment. So the - // trampoline is placed above the code segment. Also we test that the trampoline starts at the - // next memory page above last taken segment. - const std::vector unavailable_ranges8 = { - {0, k64Kb}, // huge gap here, but it's too far away - {0x10 * kOneGb, 0x11 * kOneGb}, - {0x11 * kOneGb + kOneMb - 1, 0x12 * kOneGb}, - {0x12 * kOneGb + kOneMb - 1, 0x12 * kOneGb + 2 * kOneMb + 42}}; - address_range_or_error = FindAddressRangeForTrampoline( - unavailable_ranges8, {0x12 * kOneGb + kOneMb - 1, 0x12 * kOneGb + 2 * kOneMb}, kOneMb); - ASSERT_FALSE(address_range_or_error.has_error()) << address_range_or_error.error().message(); - constexpr uint64_t kPageSize = 4096; - constexpr uint64_t kNextPage = - (((0x12 * kOneGb + 2 * kOneMb + 42) + (kPageSize - 1)) / kPageSize) * kPageSize; - EXPECT_EQ(kNextPage, address_range_or_error.value().start); - - // There is no sufficiently large gap in the mappings in the 2GB below the code segment. And there - // also is no gap large enough in the 2GB above the code segment. So no placement is possible. - const std::vector unavailable_ranges9 = { - {0, k64Kb}, // huge gap here, but it's too far away - {0x10 * kOneGb + kOneMb - 1, 0x11 * kOneGb}, - {0x11 * kOneGb + kOneMb - 1, 0x12 * kOneGb}, - {0x12 * kOneGb + kOneMb - 1, 0x12 * kOneGb + 2 * kOneMb}, - {0x12 * kOneGb + 3 * kOneMb - 1, 0x13 * kOneGb + 1}, - {0x13 * kOneGb + kOneMb + 42, 0x14 * kOneGb}}; - address_range_or_error = FindAddressRangeForTrampoline( - unavailable_ranges9, {0x12 * kOneGb + kOneMb - 1, 0x12 * kOneGb + 2 * kOneMb}, kOneMb); - ASSERT_TRUE(address_range_or_error.has_error()); - - // Fail on malformed input: first address range does not start at zero. - const std::vector unavailable_ranges10 = {{k64Kb, kOneGb}}; - EXPECT_DEATH( - auto result = FindAddressRangeForTrampoline(unavailable_ranges10, {k64Kb, kOneGb}, kOneMb), - "needs to start at zero"); - - // Placement to the left fails since the requested memory chunk is too big. So we place to the - // right which fits trivially. - // The special case here is that the requested memory size (k256Mb + k64Kb) is larger than the - // left interval border of the second interval (k256Mb). This produced an artithmetic overflow in - // a previous version of the algorithm. - const std::vector unavailable_ranges11 = {{0, k64Kb}, {k256Mb, kOneGb}}; - address_range_or_error = - FindAddressRangeForTrampoline(unavailable_ranges11, {k256Mb, kOneGb}, k256Mb + k64Kb); - ASSERT_FALSE(address_range_or_error.has_error()); - EXPECT_EQ(kOneGb, address_range_or_error.value().start); - - // Placement to the left fails, placement to the right fails also because we are close to the end - // of the address space. This produced an artithmetic overflow in a previous version of the - // algorithm. - const std::vector unavailable_ranges12 = { - {0, k64Kb}, - {UINT64_MAX - 10 * kOneGb, UINT64_MAX - k64Kb - 1}, - {UINT64_MAX - k64Kb, UINT64_MAX - k1Kb}}; - address_range_or_error = FindAddressRangeForTrampoline( - unavailable_ranges12, {UINT64_MAX - k64Kb, UINT64_MAX - k1Kb}, k64Kb); - ASSERT_THAT(address_range_or_error, HasErrorWithMessage("No place to fit")); - - // We can not fit anything close to a range larger than 2GB. - const std::vector unavailable_ranges13 = {{0, k64Kb}, {kOneGb, 4 * kOneGb}}; - address_range_or_error = - FindAddressRangeForTrampoline(unavailable_ranges13, {kOneGb, 4 * kOneGb}, k64Kb); - ASSERT_THAT(address_range_or_error, HasErrorWithMessage("No place to fit")); -} - -TEST(TrampolineTest, AllocateMemoryForTrampolines) { - pid_t pid = fork(); - ORBIT_CHECK(pid != -1); - if (pid == 0) { - prctl(PR_SET_PDEATHSIG, SIGTERM); - - [[maybe_unused]] volatile uint64_t sum = 0; - // Endless loops without side effects are UB and recent versions of clang optimize - // it away. Making `i` volatile avoids that problem. - volatile int i = 0; - while (true) { - i = (i + 1) & 3; - sum += DoubleAndIncrement(i); - } - } - - // Stop the process using our tooling. - ORBIT_CHECK(AttachAndStopProcess(pid).has_value()); - - // Find the address range of the code for `DoubleAndIncrement`. For the purpose of this test we - // just take the entire address space taken up by `UserSpaceInstrumentationTests`. - auto modules_or_error = orbit_module_utils::ReadModules(pid); - ORBIT_CHECK(!modules_or_error.has_error()); - - auto& modules = modules_or_error.value(); - const auto module = std::find_if(modules.begin(), modules.end(), [&](const auto& module) { - return module.file_path() == orbit_base::GetExecutablePath(); - }); - - ASSERT_NE(module, modules.end()); - const AddressRange code_range{module->address_start(), module->address_end()}; - - // Allocate one megabyte in the tracee. The memory will be close to `code_range`. - constexpr uint64_t kTrampolineSize = 1024 * 1024; - auto memory_or_error = AllocateMemoryForTrampolines(pid, code_range, kTrampolineSize); - ASSERT_FALSE(memory_or_error.has_error()); - - // Check that the tracee is functional: Continue, stop again, free the allocated memory, then run - // briefly again. - ORBIT_CHECK(DetachAndContinueProcess(pid).has_value()); - ORBIT_CHECK(AttachAndStopProcess(pid).has_value()); - ASSERT_THAT(memory_or_error.value()->Free(), HasNoError()); - ORBIT_CHECK(DetachAndContinueProcess(pid).has_value()); - ORBIT_CHECK(AttachAndStopProcess(pid).has_value()); - - // Detach and end child. - ORBIT_CHECK(DetachAndContinueProcess(pid).has_value()); - kill(pid, SIGKILL); - waitpid(pid, nullptr, 0); -} - -TEST(TrampolineTest, AddressDifferenceAsInt32) { - // Result of the difference is negative; in the first case it just fits, the second case - // overflows. - constexpr uint64_t kAddr1 = 0x6012345612345678; - constexpr uint64_t kAddr2Larger = kAddr1 - std::numeric_limits::min(); - auto result = AddressDifferenceAsInt32(kAddr1, kAddr2Larger); - ASSERT_THAT(result, HasNoError()); - EXPECT_EQ(std::numeric_limits::min(), result.value()); - result = AddressDifferenceAsInt32(kAddr1, kAddr2Larger + 1); - EXPECT_THAT(result, HasErrorWithMessage("Difference is larger than -2GB")); - - // Result of the difference is positive; in the first case it just fits, the second case - // overflows. - constexpr uint64_t kAddr2Smaller = kAddr1 - std::numeric_limits::max(); - result = AddressDifferenceAsInt32(kAddr1, kAddr2Smaller); - ASSERT_THAT(result, HasNoError()); - EXPECT_EQ(std::numeric_limits::max(), result.value()); - result = AddressDifferenceAsInt32(kAddr1, kAddr2Smaller - 1); - EXPECT_THAT(result, HasErrorWithMessage("Difference is larger than +2GB")); - - // Result of the difference does not even fit into a int64. We handle that gracefully as well. - constexpr uint64_t kAddrHigh = 0xf234567812345678; - constexpr uint64_t kAddrLow = kAddrHigh - 0xe234567812345678; - result = AddressDifferenceAsInt32(kAddrHigh, kAddrLow); - EXPECT_THAT(result, HasErrorWithMessage("Difference is larger than +2GB")); - result = AddressDifferenceAsInt32(kAddrLow, kAddrHigh); - EXPECT_THAT(result, HasErrorWithMessage("Difference is larger than -2GB")); -} - -class RelocateInstructionTest : public testing::Test { - protected: - void SetUp() override { - ORBIT_CHECK(cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_) == CS_ERR_OK); - ORBIT_CHECK(cs_option(capstone_handle_, CS_OPT_DETAIL, CS_OPT_ON) == CS_ERR_OK); - instruction_ = cs_malloc(capstone_handle_); - ORBIT_CHECK(instruction_ != nullptr); - } - - void Disassemble(const MachineCode& code) { - const uint8_t* code_pointer = code.GetResultAsVector().data(); - size_t code_size = code.GetResultAsVector().size(); - uint64_t disassemble_address = 0; - ORBIT_CHECK(cs_disasm_iter(capstone_handle_, &code_pointer, &code_size, &disassemble_address, - instruction_)); - } - - void TearDown() override { - cs_free(instruction_, 1); - cs_close(&capstone_handle_); - } - - cs_insn* instruction_ = nullptr; - - private: - csh capstone_handle_ = 0; -}; - -TEST_F(RelocateInstructionTest, RipRelativeAddressing) { - MachineCode code; - constexpr int32_t kOffset = 0x969433; - // add qword ptr [rip + kOffset], 1 - // Handled by "((instruction->detail->x86.modrm & 0xC7) == 0x05)" branch in 'RelocateInstruction'. - code.AppendBytes({0x48, 0x83, 0x05}).AppendImmediate32(kOffset).AppendBytes({0x01}); - Disassemble(code); - - constexpr uint64_t kOriginalAddress = 0x0100000000; - ErrorMessageOr result = - RelocateInstruction(instruction_, kOriginalAddress, kOriginalAddress + kOffset - 0x123456); - ASSERT_THAT(result, HasValue()); - // add qword ptr [rip + new_offset], 1 48 83 05 56 34 12 00 01 - // new_offset is computed as - // old_absolute_address - new_address - // == (old_address + old_displacement) - (old_address + old_displacement - 0x123456) - // == 0x123456 - EXPECT_THAT(result.value().code, - ElementsAreArray({0x48, 0x83, 0x05, 0x56, 0x34, 0x12, 0x00, 0x01})); - EXPECT_FALSE(result.value().position_of_absolute_address.has_value()); - - result = - RelocateInstruction(instruction_, kOriginalAddress, kOriginalAddress + kOffset + 0x123456); - ASSERT_THAT(result, HasValue()); - // add qword ptr [rip + new_offset], 1 48 83 05 aa cb ed ff 01 - // new_offset is computed as - // old_absolute_address - new_address - // == (old_address + old_displacement) - (old_address + old_displacement + 0x123456) - // == -0x123456 == 0xffedcbaa - EXPECT_THAT(result.value().code, - ElementsAreArray({0x48, 0x83, 0x05, 0xaa, 0xcb, 0xed, 0xff, 0x01})); - EXPECT_FALSE(result.value().position_of_absolute_address.has_value()); - - result = RelocateInstruction(instruction_, kOriginalAddress, kOriginalAddress - 0x7fff0000); - EXPECT_THAT(result, - HasErrorWithMessage( - "While trying to relocate an instruction with rip relative addressing the " - "target was out of range from the trampoline.")); -} - -TEST_F(RelocateInstructionTest, UnconditionalJumpTo8BitImmediate) { - MachineCode code; - constexpr int8_t kOffset = 0x08; - // jmp [rip + kOffset] - // Handled by "(instruction->detail->x86.opcode[0] == 0xeb)" branch in 'RelocateInstruction'. - code.AppendBytes({0xeb}).AppendImmediate8(kOffset); - Disassemble(code); - - ErrorMessageOr result = - RelocateInstruction(instruction_, 0x0100000000, 0x0200000000); - ASSERT_THAT(result, HasValue()); - // jmp [rip + 0] ff 25 00 00 00 00 - // absolute_address 0a 00 00 00 01 00 00 00 - // original jump instruction ends on 0x0100000000 + 0x02. Adding kOffset (=8) yields 0x010000000a. - EXPECT_THAT(result.value().code, ElementsAreArray({0xff, 0x25, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00})); - ASSERT_TRUE(result.value().position_of_absolute_address.has_value()); - EXPECT_EQ(6, result.value().position_of_absolute_address.value()); -} - -TEST_F(RelocateInstructionTest, UnconditionalJumpTo32BitImmediate) { - MachineCode code; - constexpr int32_t kOffset = 0x01020304; - // jmp [rip + kOffset] - // Handled by "(instruction->detail->x86.opcode[0] == 0xe9)" branch in 'RelocateInstruction'. - code.AppendBytes({0xe9}).AppendImmediate32(kOffset); - Disassemble(code); - - ErrorMessageOr result = - RelocateInstruction(instruction_, 0x0100000000, 0x0200000000); - ASSERT_THAT(result, HasValue()); - // jmp [rip + 0] ff 25 00 00 00 00 - // absolute_address 09 03 02 01 01 00 00 00 - // original jump instruction ends on 0x0100000000 + 0x05. Adding kOffset yields 0x0101020309. - EXPECT_THAT(result.value().code, ElementsAreArray({0xff, 0x25, 0x00, 0x00, 0x00, 0x00, 0x09, 0x03, - 0x02, 0x01, 0x01, 0x00, 0x00, 0x00})); - ASSERT_TRUE(result.value().position_of_absolute_address.has_value()); - EXPECT_EQ(6, result.value().position_of_absolute_address.value()); -} - -TEST_F(RelocateInstructionTest, CallInstructionIsNotSupported) { - MachineCode code; - constexpr int32_t kOffset = 0x01020304; - // call [rip + kOffset] - // Handled by "(instruction->detail->x86.opcode[0] == 0xe8)" branch in 'RelocateInstruction'. - code.AppendBytes({0xe8}).AppendImmediate32(kOffset); - Disassemble(code); - - ErrorMessageOr result = - RelocateInstruction(instruction_, 0x0100000000, 0x0200000000); - EXPECT_THAT(result, HasErrorWithMessage("Relocating a call instruction is not supported.")); -} - -TEST_F(RelocateInstructionTest, ConditionalJumpTo8BitImmediate) { - MachineCode code; - constexpr int8_t kOffset = 0x40; - // jno rip + kOffset - // Handled by "((instruction->detail->x86.opcode[0] & 0xf0) == 0x70)" branch in - // 'RelocateInstruction'. - code.AppendBytes({0x71}).AppendImmediate8(kOffset); - Disassemble(code); - - ErrorMessageOr result = - RelocateInstruction(instruction_, 0x0100000000, 0x0200000000); - ASSERT_THAT(result, HasValue()); - // jo rip + 16 70 0e - // jmp [rip + 6] ff 25 00 00 00 00 - // absolute_address 42 00 00 00 01 00 00 00 - // original jump instruction ends on 0x0100000002 + 0x40 (kOffset) == 0x0100000042. - EXPECT_THAT(result.value().code, - ElementsAreArray({0x70, 0x0e, 0xff, 0x25, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x00, 0x00})); - ASSERT_TRUE(result.value().position_of_absolute_address.has_value()); - EXPECT_EQ(8, result.value().position_of_absolute_address.value()); -} - -TEST_F(RelocateInstructionTest, ConditionalJumpTo32BitImmediate) { - MachineCode code; - constexpr int32_t kOffset = 0x12345678; - // jno rip + kOffset 0f 80 78 56 34 12 - // Handled by "(instruction->detail->x86.opcode[0] == 0x0f && - // (instruction->detail->x86.opcode[1] & 0xf0) == 0x80)" - // branch in 'RelocateInstruction'. - code.AppendBytes({0x0f, 0x80}).AppendImmediate32(kOffset); - Disassemble(code); - - ErrorMessageOr result = - RelocateInstruction(instruction_, 0x0100000000, 0x0200000000); - ASSERT_TRUE(result.has_value()); - // jo rip + 16 71 0e - // jmp [rip +6] ff 25 00 00 00 00 - // absolute_address 7a 56 34 12 01 00 00 00 - // original jump instruction ends on 0x0100000006 + 0x12345678 (kOffset) == 0x011234567e. - EXPECT_THAT(result.value().code, - ElementsAreArray({0x71, 0x0e, 0xff, 0x25, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x56, 0x34, - 0x12, 0x01, 0x00, 0x00, 0x00})); - ASSERT_TRUE(result.value().position_of_absolute_address.has_value()); - EXPECT_EQ(8, result.value().position_of_absolute_address.value()); -} - -TEST_F(RelocateInstructionTest, LoopIsUnsupported) { - MachineCode code; - constexpr int8_t kOffset = 0x40; - // loopz rip + kOffset - // Handled by "((instruction->detail->x86.opcode[0] & 0xfc) == 0xe0)" branch in - // 'RelocateInstruction'. - code.AppendBytes({0xe1}).AppendImmediate8(kOffset); - Disassemble(code); - - ErrorMessageOr result = - RelocateInstruction(instruction_, 0x0100000000, 0x0200000000); - EXPECT_THAT(result, HasErrorWithMessage("Relocating a loop instruction is not supported.")); -} - -TEST_F(RelocateInstructionTest, TrivialTranslation) { - MachineCode code; - // nop - // Handled by "else" branch in 'RelocateInstruction' - instruction is just copied. - code.AppendBytes({0x90}); - Disassemble(code); - - ErrorMessageOr result = - RelocateInstruction(instruction_, 0x0100000000, 0x0200000000); - ASSERT_THAT(result, HasValue()); - EXPECT_THAT(result.value().code, ElementsAreArray({0x90})); - EXPECT_FALSE(result.value().position_of_absolute_address.has_value()); -} - -class InstrumentFunctionTest : public testing::Test { - protected: - void SetUp() override { - /* copybara:insert(b/237251106 injecting the library into the target process triggers some - initilization code that check fails.) - GTEST_SKIP(); - */ - // Init Capstone disassembler. - cs_err error_code = cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_); - ORBIT_CHECK(error_code == CS_ERR_OK); - error_code = cs_option(capstone_handle_, CS_OPT_DETAIL, CS_OPT_ON); - ORBIT_CHECK(error_code == CS_ERR_OK); - - max_trampoline_size_ = GetMaxTrampolineSize(); - } - - void RunChild(int (*function_pointer)(), std::string_view function_name) { - function_name_ = function_name; - - pid_ = fork(); - ORBIT_CHECK(pid_ != -1); - if (pid_ == 0) { - prctl(PR_SET_PDEATHSIG, SIGTERM); - - // Endless loops without side effects are UB and recent versions of clang optimize - // it away. Making `sum` volatile avoids that problem. - [[maybe_unused]] volatile uint64_t sum = 0; - while (true) { - sum += (*function_pointer)(); - } - } - } - - AddressRange GetFunctionAddressRangeOrDie() { - return GetFunctionAbsoluteAddressRangeOrDie(function_name_); - } - - void PrepareInstrumentation(std::string_view entry_payload_function_name, - std::string_view exit_payload_function_name) { - // Stop the child process using our tooling. - ORBIT_CHECK(AttachAndStopProcess(pid_).has_value()); - - auto library_path_or_error = GetTestLibLibraryPath(); - ORBIT_CHECK(library_path_or_error.has_value()); - std::filesystem::path library_path = std::move(library_path_or_error.value()); - - auto modules_or_error = orbit_module_utils::ReadModules(pid_); - ORBIT_CHECK(modules_or_error.has_value()); - const std::vector& modules = modules_or_error.value(); - - // Inject the payload for the instrumentation. - auto library_handle_or_error = DlmopenInTracee(pid_, modules, library_path, RTLD_NOW, - LinkerNamespace::kCreateNewNamespace); - ORBIT_CHECK(library_handle_or_error.has_value()); - void* library_handle = library_handle_or_error.value(); - - auto entry_payload_function_address_or_error = - DlsymInTracee(pid_, modules, library_handle, entry_payload_function_name); - ORBIT_CHECK(entry_payload_function_address_or_error.has_value()); - entry_payload_function_address_ = - absl::bit_cast(entry_payload_function_address_or_error.value()); - - auto exit_payload_function_address_or_error = - DlsymInTracee(pid_, modules, library_handle, exit_payload_function_name); - ORBIT_CHECK(exit_payload_function_address_or_error.has_value()); - exit_payload_function_address_ = - absl::bit_cast(exit_payload_function_address_or_error.value()); - - // Get address of the function to instrument. - const AddressRange address_range_code = GetFunctionAddressRangeOrDie(); - function_address_ = address_range_code.start; - const uint64_t size_of_function = address_range_code.end - address_range_code.start; - - // Get memory for the trampoline. - auto trampoline_or_error = - AllocateMemoryForTrampolines(pid_, address_range_code, max_trampoline_size_); - ORBIT_CHECK(!trampoline_or_error.has_error()); - trampoline_memory_ = std::move(trampoline_or_error.value()); - trampoline_address_ = trampoline_memory_->GetAddress(); - - // Get memory for return trampoline and create the return trampoline. - auto return_trampoline_or_error = MemoryInTracee::Create(pid_, 0, GetReturnTrampolineSize()); - ORBIT_CHECK(!return_trampoline_or_error.has_error()); - return_trampoline_address_ = return_trampoline_or_error.value()->GetAddress(); - auto result = - CreateReturnTrampoline(pid_, exit_payload_function_address_, return_trampoline_address_); - ORBIT_CHECK(!result.has_error()); - ORBIT_CHECK(!return_trampoline_or_error.value()->EnsureMemoryExecutable().has_error()); - - // Copy the beginning of the function over into this process. - constexpr uint64_t kMaxFunctionBackupSize = 200; - const uint64_t bytes_to_copy = std::min(size_of_function, kMaxFunctionBackupSize); - ErrorMessageOr> function_backup = - ReadTraceesMemory(pid_, function_address_, bytes_to_copy); - ORBIT_CHECK(function_backup.has_value()); - function_code_ = function_backup.value(); - } - - // Runs the child for a millisecond to assert it is still working fine, stops it, removes the - // instrumentation, restarts and stops it again. - void RestartAndRemoveInstrumentation() { - ORBIT_CHECK(!trampoline_memory_->EnsureMemoryExecutable().has_error()); - - MoveInstructionPointersOutOfOverwrittenCode(pid_, relocation_map_); - - ORBIT_CHECK(!DetachAndContinueProcess(pid_).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - ORBIT_CHECK(AttachAndStopProcess(pid_).has_value()); - - auto write_result_or_error = WriteTraceesMemory(pid_, function_address_, function_code_); - ORBIT_CHECK(!write_result_or_error.has_error()); - - ORBIT_CHECK(!DetachAndContinueProcess(pid_).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - ORBIT_CHECK(AttachAndStopProcess(pid_).has_value()); - } - - void TearDown() override { - cs_close(&capstone_handle_); - - // Detach and end child. - if (pid_ != -1) { - ORBIT_CHECK(!DetachAndContinueProcess(pid_).has_error()); - kill(pid_, SIGKILL); - waitpid(pid_, nullptr, 0); - } - } - - pid_t pid_ = -1; - csh capstone_handle_ = 0; - uint64_t max_trampoline_size_ = 0; - std::unique_ptr trampoline_memory_; - uint64_t trampoline_address_ = 0; - uint64_t return_trampoline_address_ = 0; - uint64_t entry_payload_function_address_ = 0; - uint64_t exit_payload_function_address_ = 0; - - absl::flat_hash_map relocation_map_; - - std::string function_name_; - uint64_t function_address_ = 0; - std::vector function_code_; -}; - -// Function with an ordinary compiler-synthesised prologue; performs some arithmetics. Most real -// world functions will look like this (starting with pushing the stack frame...). Most functions -// below are declared "naked", i.e. without the prologue and implemented entirely in assembly. This -// is done to also cover edge cases. -extern "C" __attribute__((noinline)) int DoSomething() { - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution dis(1, 6); - std::vector v(10); - std::generate(v.begin(), v.end(), [&]() { return dis(gen); }); - int sum = std::accumulate(v.begin(), v.end(), 0); - return sum; -} - -TEST_F(InstrumentFunctionTest, DoSomething) { - RunChild(&DoSomething, "DoSomething"); - PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); - ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(address_after_prologue_or_error, HasNoError()); - ErrorMessageOr result = - InstrumentFunction(pid_, function_address_, /*function_id=*/42, - address_after_prologue_or_error.value(), trampoline_address_); - EXPECT_THAT(result, HasNoError()); - RestartAndRemoveInstrumentation(); -} - -TEST_F(InstrumentFunctionTest, CheckStackAlignedTo16Bytes) { - RunChild(&DoSomething, "DoSomething"); - PrepareInstrumentation("EntryPayloadAlignedCopy", kExitPayloadFunctionName); - ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(address_after_prologue_or_error, HasNoError()); - ErrorMessageOr result = - InstrumentFunction(pid_, function_address_, /*function_id=*/42, - address_after_prologue_or_error.value(), trampoline_address_); - EXPECT_THAT(result, HasNoError()); - RestartAndRemoveInstrumentation(); -} - -// We will not be able to instrument this - the function is just four bytes long and we need five -// bytes to write a jump. -extern "C" __attribute__((noinline, naked)) int TooShort() { - __asm__ __volatile__( - "nop \n\t" - "nop \n\t" - "nop \n\t" - "ret \n\t" - : - : - :); -} - -TEST_F(InstrumentFunctionTest, TooShort) { -#if defined(ORBIT_COVERAGE_BUILD) || !defined(__clang__) || !defined(NDEBUG) - GTEST_SKIP(); -#endif - RunChild(&TooShort, "TooShort"); - PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); - ErrorMessageOr result = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(result, - HasErrorWithMessage("Unable to disassemble enough of the function to instrument it")); - RestartAndRemoveInstrumentation(); -} - -// This function is just long enough to be instrumented (five bytes). It is also interesting in that -// the return statement is copied into the trampoline and executed from there. -extern "C" __attribute__((noinline, naked)) int LongEnough() { - __asm__ __volatile__( - "nop \n\t" - "nop \n\t" - "nop \n\t" - "nop \n\t" - "ret \n\t" - : - : - :); -} - -TEST_F(InstrumentFunctionTest, LongEnough) { - RunChild(&LongEnough, "LongEnough"); - PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); - ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(address_after_prologue_or_error, HasNoError()); - ErrorMessageOr result = - InstrumentFunction(pid_, function_address_, /*function_id=*/42, - address_after_prologue_or_error.value(), trampoline_address_); - EXPECT_THAT(result, HasNoError()); - RestartAndRemoveInstrumentation(); -} - -// The rip relative address is translated to the new code position. -extern "C" __attribute__((noinline, naked)) int RipRelativeAddressing() { - __asm__ __volatile__( - "movq 0x03(%%rip), %%rax\n\t" - "nop \n\t" - "nop \n\t" - "ret \n\t" - ".quad 0x0102034200000000 \n\t" - : - : - :); -} - -TEST_F(InstrumentFunctionTest, RipRelativeAddressing) { - RunChild(&RipRelativeAddressing, "RipRelativeAddressing"); - PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); - ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(address_after_prologue_or_error, HasNoError()); - ErrorMessageOr result = - InstrumentFunction(pid_, function_address_, /*function_id=*/42, - address_after_prologue_or_error.value(), trampoline_address_); - EXPECT_THAT(result, HasNoError()); - RestartAndRemoveInstrumentation(); -} - -// Unconditional jump to an 8-bit offset. -extern "C" __attribute__((noinline, naked)) int UnconditionalJump8BitOffset() { - __asm__ __volatile__( - "jmp label_unconditional_jmp_8_bit \n\t" - "nop \n\t" - "nop \n\t" - "nop \n\t" - "label_unconditional_jmp_8_bit: \n\t" - "ret \n\t" - : - : - :); -} - -TEST_F(InstrumentFunctionTest, UnconditionalJump8BitOffset) { - RunChild(&UnconditionalJump8BitOffset, "UnconditionalJump8BitOffset"); - PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); - ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(address_after_prologue_or_error, HasNoError()); - ErrorMessageOr result = - InstrumentFunction(pid_, function_address_, /*function_id=*/42, - address_after_prologue_or_error.value(), trampoline_address_); - EXPECT_THAT(result, HasNoError()); - RestartAndRemoveInstrumentation(); -} - -// Unconditional jump to a 32 bit offset. -extern "C" __attribute__((noinline, naked)) int UnconditionalJump32BitOffset() { - __asm__ __volatile__( - "jmp label_unconditional_jmp_32_bit \n\t" - ".octa 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \n\t" // 256 bytes of zeros - "label_unconditional_jmp_32_bit: \n\t" - "ret \n\t" - : - : - :); -} - -TEST_F(InstrumentFunctionTest, UnconditionalJump32BitOffset) { - RunChild(&UnconditionalJump32BitOffset, "UnconditionalJump32BitOffset"); - PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); - ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(address_after_prologue_or_error, HasNoError()); - ErrorMessageOr result = - InstrumentFunction(pid_, function_address_, /*function_id=*/42, - address_after_prologue_or_error.value(), trampoline_address_); - EXPECT_THAT(result, HasNoError()); - RestartAndRemoveInstrumentation(); -} - -// The rip relative address is translated to the new code position. -extern "C" __attribute__((noinline, naked)) int ConditionalJump8BitOffset() { - __asm__ __volatile__( - "jnz loop_label_jcc \n\t" - "nop \n\t" - "nop \n\t" - "nop \n\t" - "loop_label_jcc: \n\t" - "xor %%eax, %%eax \n\t" - "nop \n\t" - "nop \n\t" - "ret \n\t" - : - : - :); -} - -TEST_F(InstrumentFunctionTest, ConditionalJump8BitOffset) { - RunChild(&ConditionalJump8BitOffset, "ConditionalJump8BitOffset"); - PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); - ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(address_after_prologue_or_error, HasNoError()); - ErrorMessageOr result = - InstrumentFunction(pid_, function_address_, /*function_id=*/42, - address_after_prologue_or_error.value(), trampoline_address_); - EXPECT_THAT(result, HasNoError()); - RestartAndRemoveInstrumentation(); -} - -// The rip relative address is translated to the new code position. -extern "C" __attribute__((noinline, naked)) int ConditionalJump32BitOffset() { - __asm__ __volatile__( - "xor %%eax, %%eax \n\t" - "jnz label_jcc_32_bit \n\t" - "nop \n\t" - "ret \n\t" - ".octa 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \n\t" // 256 bytes of zeros - "label_jcc_32_bit: \n\t" - "ret \n\t" - : - : - :); -} - -TEST_F(InstrumentFunctionTest, ConditionalJump32BitOffset) { - RunChild(&ConditionalJump32BitOffset, "ConditionalJump32BitOffset"); - PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); - ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(address_after_prologue_or_error, HasNoError()); - ErrorMessageOr result = - InstrumentFunction(pid_, function_address_, /*function_id=*/42, - address_after_prologue_or_error.value(), trampoline_address_); - EXPECT_THAT(result, HasNoError()); - RestartAndRemoveInstrumentation(); -} - -// Function can not be instrumented since it uses the unsupported loop instruction. -extern "C" __attribute__((noinline, naked)) int Loop() { - __asm__ __volatile__( - "mov $42, %%cx\n\t" - "loop_label:\n\t" - "loopnz loop_label\n\t" - "ret \n\t" - : - : - :); -} - -TEST_F(InstrumentFunctionTest, Loop) { -#if defined(ORBIT_COVERAGE_BUILD) || !defined(__clang__) - GTEST_SKIP(); -#endif - RunChild(&Loop, "Loop"); - PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); - ErrorMessageOr result = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(result, HasErrorWithMessage("Relocating a loop instruction is not supported.")); - RestartAndRemoveInstrumentation(); -} - -// Check-fails if any parameter is not zero. -extern "C" __attribute__((noinline)) int CheckIntParameters(uint64_t p0, uint64_t p1, uint64_t p2, - uint64_t p3, uint64_t p4, uint64_t p5, - uint64_t p6, uint64_t p7) { - ORBIT_CHECK(p0 == 0 && p1 == 0 && p2 == 0 && p3 == 0 && p4 == 0 && p5 == 0 && p6 == 0 && p7 == 0); - return 0; -} - -// This test and the tests below check for proper handling of parameters handed to the instrumented -// function. The payload that is called before the instrumented function is executed clobbers the -// respective set of registers. So the Check*Parameter methods can check if the backup worked -// correctly. -TEST_F(InstrumentFunctionTest, CheckIntParameters) { - function_name_ = "CheckIntParameters"; - pid_ = fork(); - ORBIT_CHECK(pid_ != -1); - if (pid_ == 0) { - prctl(PR_SET_PDEATHSIG, SIGTERM); - - // Endless loops without side effects are UB and recent versions of clang optimize it away. - // Making `sum` volatile avoids that problem. - [[maybe_unused]] volatile uint64_t sum = 0; - while (true) { - sum += CheckIntParameters(0, 0, 0, 0, 0, 0, 0, 0); - } - } - PrepareInstrumentation("EntryPayloadClobberParameterRegisters", kExitPayloadFunctionName); - ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(address_after_prologue_or_error, HasNoError()); - ErrorMessageOr result = - InstrumentFunction(pid_, function_address_, /*function_id=*/42, - address_after_prologue_or_error.value(), trampoline_address_); - EXPECT_THAT(result, HasNoError()); - RestartAndRemoveInstrumentation(); -} - -// Check-fails if any parameter is not zero. -extern "C" __attribute__((noinline)) int CheckFloatParameters(float p0, float p1, float p2, - float p3, float p4, float p5, - float p6, float p7) { - ORBIT_CHECK(p0 == 0.f && p1 == 0.f && p2 == 0.f && p3 == 0.f && p4 == 0.f && p5 == 0.f && - p6 == 0.f && p7 == 0.f); - return 0; -} - -TEST_F(InstrumentFunctionTest, CheckFloatParameters) { - function_name_ = "CheckFloatParameters"; - pid_ = fork(); - ORBIT_CHECK(pid_ != -1); - if (pid_ == 0) { - prctl(PR_SET_PDEATHSIG, SIGTERM); - - // Endless loops without side effects are UB and recent versions of clang optimize it away. - // Making `sum` volatile avoids that problem. - [[maybe_unused]] volatile uint64_t sum = 0; - while (true) { - sum += CheckFloatParameters(0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f); - } - } - PrepareInstrumentation("EntryPayloadClobberXmmRegisters", kExitPayloadFunctionName); - ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(address_after_prologue_or_error, HasNoError()); - ErrorMessageOr result = - InstrumentFunction(pid_, function_address_, /*function_id=*/42, - address_after_prologue_or_error.value(), trampoline_address_); - EXPECT_THAT(result, HasNoError()); - RestartAndRemoveInstrumentation(); -} - -// Check-fails if any parameter is not zero. -extern "C" __attribute__((noinline)) int CheckM256iParameters(__m256i p0, __m256i p1, __m256i p2, - __m256i p3, __m256i p4, __m256i p5, - __m256i p6, __m256i p7) { - ORBIT_CHECK(_mm256_extract_epi64(p0, 0) == 0 && _mm256_extract_epi64(p1, 0) == 0 && - _mm256_extract_epi64(p2, 0) == 0 && _mm256_extract_epi64(p3, 0) == 0 && - _mm256_extract_epi64(p4, 0) == 0 && _mm256_extract_epi64(p5, 0) == 0 && - _mm256_extract_epi64(p6, 0) == 0 && _mm256_extract_epi64(p7, 0) == 0); - return 0; -} - -TEST_F(InstrumentFunctionTest, CheckM256iParameters) { - function_name_ = "CheckM256iParameters"; - pid_ = fork(); - ORBIT_CHECK(pid_ != -1); - if (pid_ == 0) { - prctl(PR_SET_PDEATHSIG, SIGTERM); - - // Endless loops without side effects are UB and recent versions of clang optimize it away. - // Making `sum` volatile avoids that problem. - [[maybe_unused]] volatile uint64_t sum = 0; - while (true) { - sum += - CheckM256iParameters(_mm256_set1_epi64x(0), _mm256_set1_epi64x(0), _mm256_set1_epi64x(0), - _mm256_set1_epi64x(0), _mm256_set1_epi64x(0), _mm256_set1_epi64x(0), - _mm256_set1_epi64x(0), _mm256_set1_epi64x(0)); - } - } - PrepareInstrumentation("EntryPayloadClobberYmmRegisters", kExitPayloadFunctionName); - ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(address_after_prologue_or_error, HasNoError()); - ErrorMessageOr result = - InstrumentFunction(pid_, function_address_, /*function_id=*/42, - address_after_prologue_or_error.value(), trampoline_address_); - EXPECT_THAT(result, HasNoError()); - RestartAndRemoveInstrumentation(); -} - -// Check-fails if any parameter is not zero. -extern "C" __attribute__((noinline, ms_abi)) int CheckIntParametersMsAbi(uint64_t p0, uint64_t p1, - uint64_t p2, uint64_t p3) { - ORBIT_CHECK(p0 == 0 && p1 == 0 && p2 == 0 && p3 == 0); - return 0; -} - -TEST_F(InstrumentFunctionTest, CheckIntParametersMsAbi) { - function_name_ = "CheckIntParametersMsAbi"; - pid_ = fork(); - ORBIT_CHECK(pid_ != -1); - if (pid_ == 0) { - prctl(PR_SET_PDEATHSIG, SIGTERM); - - // Endless loops without side effects are UB and recent versions of clang optimize it away. - // Making `sum` volatile avoids that problem. - [[maybe_unused]] volatile uint64_t sum = 0; - while (true) { - sum += CheckIntParametersMsAbi(0, 0, 0, 0); - } - } - PrepareInstrumentation("EntryPayloadClobberParameterRegisters", kExitPayloadFunctionName); - ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(address_after_prologue_or_error, HasNoError()); - ErrorMessageOr result = - InstrumentFunction(pid_, function_address_, /*function_id=*/42, - address_after_prologue_or_error.value(), trampoline_address_); - EXPECT_THAT(result, HasNoError()); - RestartAndRemoveInstrumentation(); -} - -// Check-fails if any parameter is not zero. -extern "C" __attribute__((noinline, ms_abi)) int CheckFloatParametersMsAbi(float p0, float p1, - float p2, float p3) { - ORBIT_CHECK(p0 == 0.f && p1 == 0.f && p2 == 0.f && p3 == 0.f); - return 0; -} - -TEST_F(InstrumentFunctionTest, CheckFloatParametersMsAbi) { - function_name_ = "CheckFloatParametersMsAbi"; - pid_ = fork(); - ORBIT_CHECK(pid_ != -1); - if (pid_ == 0) { - prctl(PR_SET_PDEATHSIG, SIGTERM); - - // Endless loops without side effects are UB and recent versions of clang optimize it away. - // Making `sum` volatile avoids that problem. - [[maybe_unused]] volatile uint64_t sum = 0; - while (true) { - sum += CheckFloatParametersMsAbi(0.f, 0.f, 0.f, 0.f); - } - } - PrepareInstrumentation("EntryPayloadClobberXmmRegisters", kExitPayloadFunctionName); - ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(address_after_prologue_or_error, HasNoError()); - ErrorMessageOr result = - InstrumentFunction(pid_, function_address_, /*function_id=*/42, - address_after_prologue_or_error.value(), trampoline_address_); - EXPECT_THAT(result, HasNoError()); - RestartAndRemoveInstrumentation(); -} - -// This test guards against naively backing up x87 registers in the return trampoline when the -// instrumented function doesn't use them to return values. -TEST_F(InstrumentFunctionTest, CheckNoX87UnderflowInReturnTrampoline) { - function_name_ = "DoSomething"; - pid_ = fork(); - ORBIT_CHECK(pid_ != -1); - if (pid_ == 0) { - prctl(PR_SET_PDEATHSIG, SIGTERM); - - // Reset bit 0 of the 16-bit x87 FPU Control Word, in order to unmask invalid-operation - // exception. If the return trampoline causes the underflow of the x87 register stack before - // masking the exception, the process will crash. - uint16_t control = 0; - __asm__ __volatile__("fnstcw %0\n\t" : "=m"(control) : :); - control &= 0xFE; - __asm__ __volatile__("fldcw %0\n\t" : : "m"(control) :); - - // Endless loops without side effects are UB and recent versions of clang optimize it away. - // Making `sum` volatile avoids that problem. - [[maybe_unused]] volatile uint64_t sum = 0; - while (true) { - sum += DoSomething(); - } - } - PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); - ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(address_after_prologue_or_error, HasNoError()); - ErrorMessageOr result = - InstrumentFunction(pid_, function_address_, /*function_id=*/42, - address_after_prologue_or_error.value(), trampoline_address_); - EXPECT_THAT(result, HasNoError()); - RestartAndRemoveInstrumentation(); -} - -extern "C" __attribute__((noinline, naked)) int UnconditionalJump8BitOffsetBackToBeginning() { - __asm__ __volatile__( - "nop \n\t" - "nop \n\t" - "nop \n\t" - "nop \n\t" - "nop \n\t" - ".byte 0xeb \n\t" // jmp -7 (which is the first nop) - ".byte 0xf9 \n\t" - "xor %%eax, %%eax \n\t" - "ret \n\t" - : - : - :); -} - -// This will fail to create a trampoline since the function contains an unconditional jump to an -// eight bit offset which points back into the first five bytes of the function. -TEST_F(InstrumentFunctionTest, UnconditionalJump8BitOffsetBackToBeginning) { -// Exclude gcc builds: the inline assembly above gets messed up by the compiler. -#if defined(ORBIT_COVERAGE_BUILD) || !defined(__clang__) || !defined(NDEBUG) - GTEST_SKIP(); -#endif - RunChild(&UnconditionalJump8BitOffsetBackToBeginning, - "UnconditionalJump8BitOffsetBackToBeginning"); - PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); - ErrorMessageOr result = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(result, - HasErrorWithMessage( - "Failed to create trampoline since the function contains a jump back into")); -} - -extern "C" __attribute__((noinline, naked)) int UnconditionalJump32BitOffsetBackToBeginning() { - __asm__ __volatile__( - "nop \n\t" - "nop \n\t" - "nop \n\t" - "nop \n\t" - "nop \n\t" - ".byte 0xe9 \n\t" // jmp -10 (which is the first nop) - ".long 0xfffffff6 \n\t" - "xor %%eax, %%eax \n\t" - "ret \n\t" - : - : - :); -} - -// This will fail to create a trampoline since the function contains an unconditional jump to a -// 32 bit offset which points back into the first five bytes of the function. -TEST_F(InstrumentFunctionTest, UnconditionalJump32BitOffsetBackToBeginning) { -// Exclude gcc builds: the inline assembly above gets messed up by the compiler. -#if defined(ORBIT_COVERAGE_BUILD) || !defined(__clang__) || !defined(NDEBUG) - GTEST_SKIP(); -#endif - RunChild(&UnconditionalJump32BitOffsetBackToBeginning, - "UnconditionalJump32BitOffsetBackToBeginning"); - PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); - ErrorMessageOr result = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(result, - HasErrorWithMessage( - "Failed to create trampoline since the function contains a jump back into")); -} - -extern "C" __attribute__((noinline, naked)) int ConditionalJump8BitOffsetBackToBeginning() { - __asm__ __volatile__( - "nop \n\t" - "nop \n\t" - "nop \n\t" - "nop \n\t" - "nop \n\t" - ".byte 0x70 \n\t" // jo -7 (which is the first nop) - ".byte 0xf9 \n\t" - "xor %%eax, %%eax \n\t" - "ret \n\t" - : - : - :); -} - -// This will fail to create a trampoline since the function contains a conditional jump to an -// eight bit offset which points back into the first five bytes of the function. -TEST_F(InstrumentFunctionTest, ConditionalJump8BitOffsetBackToBeginning) { -// Exclude gcc builds: the inline assembly above gets messed up by the compiler. -#if defined(ORBIT_COVERAGE_BUILD) || !defined(__clang__) || !defined(NDEBUG) - GTEST_SKIP(); -#endif - RunChild(&ConditionalJump8BitOffsetBackToBeginning, "ConditionalJump8BitOffsetBackToBeginning"); - PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); - ErrorMessageOr result = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(result, - HasErrorWithMessage( - "Failed to create trampoline since the function contains a jump back into")); -} - -extern "C" __attribute__((noinline, naked)) int ConditionalJump32BitOffsetBackToBeginning() { - __asm__ __volatile__( - "nop \n\t" - "nop \n\t" - "nop \n\t" - "nop \n\t" - "nop \n\t" - ".byte 0x0f \n\t" // jo -7 (which is the last nop) - ".byte 0x80 \n\t" - ".long 0xfffffff9 \n\t" - "xor %%eax, %%eax \n\t" - "ret \n\t" - : - : - :); -} - -// This will fail to create a trampoline since the function contains a conditional jump to a -// 32 bit offset which points back into the first five bytes of the function. -TEST_F(InstrumentFunctionTest, ConditionalJump32BitOffsetBackToBeginning) { -// Exclude gcc builds: the inline assembly above gets messed up by the compiler. -#if defined(ORBIT_COVERAGE_BUILD) || !defined(__clang__) || !defined(NDEBUG) - GTEST_SKIP(); -#endif - RunChild(&ConditionalJump32BitOffsetBackToBeginning, "ConditionalJump32BitOffsetBackToBeginning"); - PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); - ErrorMessageOr result = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(result, - HasErrorWithMessage( - "Failed to create trampoline since the function contains a jump back into")); -} - -extern "C" __attribute__((noinline, naked)) int LongConditionalJump32BitOffsetBackToBeginning() { - __asm__ __volatile__( - "xor %%eax, %%eax \n\t" - "ret \n\t" - ".octa 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \n\t" // 256 bytes of zeros - ".byte 0x0f \n\t" // jo -263 (which is the ret) - ".byte 0x80 \n\t" - ".long 0xfffffef9 \n\t" - : - : - :); -} - -// This will create a trampoline. The function contains a conditional jump to a -// 32 bit offset which points back into the first five bytes of the function. However the jump is -// occurring after the 200 byte limit and therefore it stays undetected. -TEST_F(InstrumentFunctionTest, LongConditionalJump32BitOffsetBackToBeginning) { - RunChild(&LongConditionalJump32BitOffsetBackToBeginning, - "LongConditionalJump32BitOffsetBackToBeginning"); - PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); - ErrorMessageOr result = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(result, HasNoError()); -} - -extern "C" __attribute__((noinline, naked)) int UnableToDisassembleBadInstruction() { - __asm__ __volatile__( - "nop \n\t" - "nop \n\t" - "nop \n\t" - "nop \n\t" - "ret \n\t" - ".byte 0x06 \n\t" // bad instruction - ".byte 0x0f \n\t" // jo -12 (which is the first nop) - ".byte 0x80 \n\t" - ".long 0xfffffff4 \n\t" - : - : - :); -} - -// This will create a trampoline. There is a conditional jump back to the start but the disassembler -// gets confused before it reaches this and so we don't detect it. -TEST_F(InstrumentFunctionTest, UnableToDisassembleBadInstruction) { - RunChild(&UnableToDisassembleBadInstruction, "UnableToDisassembleBadInstruction"); - PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); - ErrorMessageOr result = CreateTrampoline( - pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, - return_trampoline_address_, capstone_handle_, relocation_map_); - EXPECT_THAT(result, HasNoError()); -} - -} // namespace orbit_user_space_instrumentation +// // Copyright (c) 2021 The Orbit Authors. All rights reserved. +// // Use of this source code is governed by a BSD-style license that can be +// // found in the LICENSE file. + +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include + +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include + +// #include "AccessTraceesMemory.h" +// #include "AllocateInTracee.h" +// #include "GetTestLibLibraryPath.h" +// #include "GrpcProtos/module.pb.h" +// #include "MachineCode.h" +// #include "ModuleUtils/ReadLinuxModules.h" +// #include "OrbitBase/ExecutablePath.h" +// #include "OrbitBase/Logging.h" +// #include "OrbitBase/Result.h" +// #include "TestUtils.h" +// #include "TestUtils/TestUtils.h" +// #include "Trampoline.h" +// #include "UserSpaceInstrumentation/AddressRange.h" +// #include "UserSpaceInstrumentation/Attach.h" +// #include "UserSpaceInstrumentation/InjectLibraryInTracee.h" + +// namespace orbit_user_space_instrumentation { +// using orbit_test_utils::HasErrorWithMessage; + +// namespace { + +// using orbit_test_utils::HasErrorWithMessage; +// using orbit_test_utils::HasNoError; +// using orbit_test_utils::HasValue; +// using testing::ElementsAreArray; + +// constexpr const char* kEntryPayloadFunctionName = "EntryPayload"; +// constexpr const char* kExitPayloadFunctionName = "ExitPayload"; + +// extern "C" __attribute__((noinline)) int DoubleAndIncrement(int i) { +// i = 2 * i; +// return i + 1; +// } + +// } // namespace + +// TEST(TrampolineTest, DoAddressRangesOverlap) { +// AddressRange a = {3, 7}; +// AddressRange b1 = {1, 2}; +// EXPECT_FALSE(DoAddressRangesOverlap(a, b1)); +// AddressRange b2 = {1, 3}; +// EXPECT_FALSE(DoAddressRangesOverlap(a, b2)); +// AddressRange b3 = {1, 4}; +// EXPECT_TRUE(DoAddressRangesOverlap(a, b3)); +// AddressRange b4 = {1, 9}; +// EXPECT_TRUE(DoAddressRangesOverlap(a, b4)); +// AddressRange b5 = {4, 5}; +// EXPECT_TRUE(DoAddressRangesOverlap(a, b5)); +// AddressRange b6 = {4, 9}; +// EXPECT_TRUE(DoAddressRangesOverlap(a, b6)); +// AddressRange b7 = {7, 9}; +// EXPECT_FALSE(DoAddressRangesOverlap(a, b7)); +// AddressRange b8 = {8, 9}; +// EXPECT_FALSE(DoAddressRangesOverlap(a, b8)); +// } + +// TEST(TrampolineTest, LowestIntersectingAddressRange) { +// const std::vector all_ranges = {{0, 5}, {20, 30}, {40, 60}}; + +// EXPECT_FALSE(LowestIntersectingAddressRange({}, {0, 60}).has_value()); + +// EXPECT_EQ(0, LowestIntersectingAddressRange(all_ranges, {1, 2})); +// EXPECT_EQ(1, LowestIntersectingAddressRange(all_ranges, {21, 22})); +// EXPECT_EQ(2, LowestIntersectingAddressRange(all_ranges, {51, 52})); + +// EXPECT_EQ(0, LowestIntersectingAddressRange(all_ranges, {3, 6})); +// EXPECT_EQ(1, LowestIntersectingAddressRange(all_ranges, {19, 22})); +// EXPECT_EQ(2, LowestIntersectingAddressRange(all_ranges, {30, 52})); + +// EXPECT_EQ(0, LowestIntersectingAddressRange(all_ranges, {4, 72})); +// EXPECT_EQ(1, LowestIntersectingAddressRange(all_ranges, {29, 52})); +// EXPECT_EQ(2, LowestIntersectingAddressRange(all_ranges, {59, 72})); + +// EXPECT_FALSE(LowestIntersectingAddressRange(all_ranges, {5, 20}).has_value()); +// EXPECT_FALSE(LowestIntersectingAddressRange(all_ranges, {30, 40}).has_value()); +// EXPECT_FALSE(LowestIntersectingAddressRange(all_ranges, {60, 80}).has_value()); +// } + +// TEST(TrampolineTest, HighestIntersectingAddressRange) { +// const std::vector all_ranges = {{0, 5}, {20, 30}, {40, 60}}; + +// EXPECT_FALSE(HighestIntersectingAddressRange({}, {0, 60}).has_value()); + +// EXPECT_EQ(0, HighestIntersectingAddressRange(all_ranges, {1, 2})); +// EXPECT_EQ(1, HighestIntersectingAddressRange(all_ranges, {21, 22})); +// EXPECT_EQ(2, HighestIntersectingAddressRange(all_ranges, {51, 52})); + +// EXPECT_EQ(0, HighestIntersectingAddressRange(all_ranges, {3, 6})); +// EXPECT_EQ(1, HighestIntersectingAddressRange(all_ranges, {19, 22})); +// EXPECT_EQ(2, HighestIntersectingAddressRange(all_ranges, {30, 52})); + +// EXPECT_EQ(2, HighestIntersectingAddressRange(all_ranges, {4, 72})); +// EXPECT_EQ(2, HighestIntersectingAddressRange(all_ranges, {29, 52})); +// EXPECT_EQ(2, HighestIntersectingAddressRange(all_ranges, {59, 72})); + +// EXPECT_FALSE(HighestIntersectingAddressRange(all_ranges, {5, 20}).has_value()); +// EXPECT_FALSE(HighestIntersectingAddressRange(all_ranges, {30, 40}).has_value()); +// EXPECT_FALSE(HighestIntersectingAddressRange(all_ranges, {60, 80}).has_value()); +// } + +// TEST(TrampolineTest, FindAddressRangeForTrampoline) { +// constexpr uint64_t k1Kb = 0x400; +// constexpr uint64_t k64Kb = 0x10000; +// constexpr uint64_t kOneMb = 0x100000; +// constexpr uint64_t k256Mb = 0x10000000; +// constexpr uint64_t kOneGb = 0x40000000; + +// // Trivial placement to the left. +// const std::vector unavailable_ranges1 = { +// {0, k64Kb}, {kOneGb, 2 * kOneGb}, {3 * kOneGb, 4 * kOneGb}}; +// auto address_range_or_error = +// FindAddressRangeForTrampoline(unavailable_ranges1, {kOneGb, 2 * kOneGb}, k256Mb); +// ASSERT_FALSE(address_range_or_error.has_error()); +// EXPECT_EQ(kOneGb - k256Mb, address_range_or_error.value().start); + +// // Placement to the left just fits. +// const std::vector unavailable_ranges2 = { +// {0, k64Kb}, {k256Mb, kOneGb}, {3 * kOneGb, 4 * kOneGb}}; +// address_range_or_error = +// FindAddressRangeForTrampoline(unavailable_ranges2, {k256Mb, kOneGb}, k256Mb - k64Kb); +// ASSERT_FALSE(address_range_or_error.has_error()); +// EXPECT_EQ(k64Kb, address_range_or_error.value().start); + +// // Placement to the left fails due to page alignment. So we place to the right which fits +// // trivially. +// const std::vector unavailable_ranges3 = { +// {0, k64Kb + 1}, {k256Mb, kOneGb}, {3 * kOneGb, 4 * kOneGb}}; +// address_range_or_error = +// FindAddressRangeForTrampoline(unavailable_ranges3, {k256Mb, kOneGb}, k256Mb - k64Kb - 5); +// ASSERT_FALSE(address_range_or_error.has_error()); +// EXPECT_EQ(kOneGb, address_range_or_error.value().start); + +// // Placement to the left just fits but only after a few hops. +// const std::vector unavailable_ranges4 = { +// {0, k64Kb}, // this is the gap that just fits +// {k64Kb + kOneMb, 6 * kOneMb}, +// {6 * kOneMb + kOneMb - 1, 7 * kOneMb}, +// {7 * kOneMb + kOneMb - 1, 8 * kOneMb}, +// {8 * kOneMb + kOneMb - 1, 9 * kOneMb}}; +// address_range_or_error = FindAddressRangeForTrampoline( +// unavailable_ranges4, {8 * kOneMb + kOneMb - 1, 9 * kOneMb}, kOneMb); +// ASSERT_FALSE(address_range_or_error.has_error()); +// EXPECT_EQ(k64Kb, address_range_or_error.value().start); + +// // No space to the left but trivial placement to the right. +// const std::vector unavailable_ranges5 = { +// {0, k64Kb}, {kOneMb, kOneGb}, {5 * kOneGb, 6 * kOneGb}}; +// address_range_or_error = +// FindAddressRangeForTrampoline(unavailable_ranges5, {kOneMb, kOneGb}, kOneMb); +// ASSERT_FALSE(address_range_or_error.has_error()) << address_range_or_error.error().message(); +// EXPECT_EQ(kOneGb, address_range_or_error.value().start); + +// // No space to the left but placement to the right works after a few hops. +// const std::vector unavailable_ranges6 = { +// {0, k64Kb}, +// {kOneMb, kOneGb}, +// {kOneGb + 0x01 * kOneMb - 1, kOneGb + 0x10 * kOneMb}, +// {kOneGb + 0x11 * kOneMb - 1, kOneGb + 0x20 * kOneMb}, +// {kOneGb + 0x21 * kOneMb - 1, kOneGb + 0x30 * kOneMb}, +// {kOneGb + 0x31 * kOneMb - 1, kOneGb + 0x40 * kOneMb}}; +// address_range_or_error = +// FindAddressRangeForTrampoline(unavailable_ranges6, {kOneMb, kOneGb}, kOneMb); +// ASSERT_FALSE(address_range_or_error.has_error()) << address_range_or_error.error().message(); +// EXPECT_EQ(kOneGb + 0x40 * kOneMb, address_range_or_error.value().start); + +// // No space to the left and the last segment nearly fills up the 64 bit address space. So no +// // placement is possible. +// const std::vector unavailable_ranges7 = { +// {0, k64Kb}, +// {kOneMb, k256Mb}, +// {1 * k256Mb + kOneMb - 1, 2 * k256Mb}, +// {2 * k256Mb + kOneMb - 1, 3 * k256Mb}, +// {3 * k256Mb + kOneMb - 1, 4 * k256Mb + 1}, // this gap is large but alignment doesn't fit +// {4 * k256Mb + kOneMb + 2, 5 * k256Mb}, +// {5 * k256Mb + kOneMb - 1, 0xffffffffffffffff - kOneMb / 2}}; +// address_range_or_error = +// FindAddressRangeForTrampoline(unavailable_ranges7, {kOneMb, k256Mb}, kOneMb); +// ASSERT_TRUE(address_range_or_error.has_error()); + +// // There is no sufficiently large gap in the mappings in the 2GB below the code segment. So the +// // trampoline is placed above the code segment. Also we test that the trampoline starts at the +// // next memory page above last taken segment. +// const std::vector unavailable_ranges8 = { +// {0, k64Kb}, // huge gap here, but it's too far away +// {0x10 * kOneGb, 0x11 * kOneGb}, +// {0x11 * kOneGb + kOneMb - 1, 0x12 * kOneGb}, +// {0x12 * kOneGb + kOneMb - 1, 0x12 * kOneGb + 2 * kOneMb + 42}}; +// address_range_or_error = FindAddressRangeForTrampoline( +// unavailable_ranges8, {0x12 * kOneGb + kOneMb - 1, 0x12 * kOneGb + 2 * kOneMb}, kOneMb); +// ASSERT_FALSE(address_range_or_error.has_error()) << address_range_or_error.error().message(); +// constexpr uint64_t kPageSize = 4096; +// constexpr uint64_t kNextPage = +// (((0x12 * kOneGb + 2 * kOneMb + 42) + (kPageSize - 1)) / kPageSize) * kPageSize; +// EXPECT_EQ(kNextPage, address_range_or_error.value().start); + +// // There is no sufficiently large gap in the mappings in the 2GB below the code segment. And there +// // also is no gap large enough in the 2GB above the code segment. So no placement is possible. +// const std::vector unavailable_ranges9 = { +// {0, k64Kb}, // huge gap here, but it's too far away +// {0x10 * kOneGb + kOneMb - 1, 0x11 * kOneGb}, +// {0x11 * kOneGb + kOneMb - 1, 0x12 * kOneGb}, +// {0x12 * kOneGb + kOneMb - 1, 0x12 * kOneGb + 2 * kOneMb}, +// {0x12 * kOneGb + 3 * kOneMb - 1, 0x13 * kOneGb + 1}, +// {0x13 * kOneGb + kOneMb + 42, 0x14 * kOneGb}}; +// address_range_or_error = FindAddressRangeForTrampoline( +// unavailable_ranges9, {0x12 * kOneGb + kOneMb - 1, 0x12 * kOneGb + 2 * kOneMb}, kOneMb); +// ASSERT_TRUE(address_range_or_error.has_error()); + +// // Fail on malformed input: first address range does not start at zero. +// const std::vector unavailable_ranges10 = {{k64Kb, kOneGb}}; +// EXPECT_DEATH( +// auto result = FindAddressRangeForTrampoline(unavailable_ranges10, {k64Kb, kOneGb}, kOneMb), +// "needs to start at zero"); + +// // Placement to the left fails since the requested memory chunk is too big. So we place to the +// // right which fits trivially. +// // The special case here is that the requested memory size (k256Mb + k64Kb) is larger than the +// // left interval border of the second interval (k256Mb). This produced an artithmetic overflow in +// // a previous version of the algorithm. +// const std::vector unavailable_ranges11 = {{0, k64Kb}, {k256Mb, kOneGb}}; +// address_range_or_error = +// FindAddressRangeForTrampoline(unavailable_ranges11, {k256Mb, kOneGb}, k256Mb + k64Kb); +// ASSERT_FALSE(address_range_or_error.has_error()); +// EXPECT_EQ(kOneGb, address_range_or_error.value().start); + +// // Placement to the left fails, placement to the right fails also because we are close to the end +// // of the address space. This produced an artithmetic overflow in a previous version of the +// // algorithm. +// const std::vector unavailable_ranges12 = { +// {0, k64Kb}, +// {UINT64_MAX - 10 * kOneGb, UINT64_MAX - k64Kb - 1}, +// {UINT64_MAX - k64Kb, UINT64_MAX - k1Kb}}; +// address_range_or_error = FindAddressRangeForTrampoline( +// unavailable_ranges12, {UINT64_MAX - k64Kb, UINT64_MAX - k1Kb}, k64Kb); +// ASSERT_THAT(address_range_or_error, HasErrorWithMessage("No place to fit")); + +// // We can not fit anything close to a range larger than 2GB. +// const std::vector unavailable_ranges13 = {{0, k64Kb}, {kOneGb, 4 * kOneGb}}; +// address_range_or_error = +// FindAddressRangeForTrampoline(unavailable_ranges13, {kOneGb, 4 * kOneGb}, k64Kb); +// ASSERT_THAT(address_range_or_error, HasErrorWithMessage("No place to fit")); +// } + +// TEST(TrampolineTest, AllocateMemoryForTrampolines) { +// pid_t pid = fork(); +// ORBIT_CHECK(pid != -1); +// if (pid == 0) { +// prctl(PR_SET_PDEATHSIG, SIGTERM); + +// [[maybe_unused]] volatile uint64_t sum = 0; +// // Endless loops without side effects are UB and recent versions of clang optimize +// // it away. Making `i` volatile avoids that problem. +// volatile int i = 0; +// while (true) { +// i = (i + 1) & 3; +// sum += DoubleAndIncrement(i); +// } +// } + +// // Stop the process using our tooling. +// ORBIT_CHECK(AttachAndStopProcess(pid).has_value()); + +// // Find the address range of the code for `DoubleAndIncrement`. For the purpose of this test we +// // just take the entire address space taken up by `UserSpaceInstrumentationTests`. +// auto modules_or_error = orbit_module_utils::ReadModules(pid); +// ORBIT_CHECK(!modules_or_error.has_error()); + +// auto& modules = modules_or_error.value(); +// const auto module = std::find_if(modules.begin(), modules.end(), [&](const auto& module) { +// return module.file_path() == orbit_base::GetExecutablePath(); +// }); + +// ASSERT_NE(module, modules.end()); +// const AddressRange code_range{module->address_start(), module->address_end()}; + +// // Allocate one megabyte in the tracee. The memory will be close to `code_range`. +// constexpr uint64_t kTrampolineSize = 1024 * 1024; +// auto memory_or_error = AllocateMemoryForTrampolines(pid, code_range, kTrampolineSize); +// ASSERT_FALSE(memory_or_error.has_error()); + +// // Check that the tracee is functional: Continue, stop again, free the allocated memory, then run +// // briefly again. +// ORBIT_CHECK(DetachAndContinueProcess(pid).has_value()); +// ORBIT_CHECK(AttachAndStopProcess(pid).has_value()); +// ASSERT_THAT(memory_or_error.value()->Free(), HasNoError()); +// ORBIT_CHECK(DetachAndContinueProcess(pid).has_value()); +// ORBIT_CHECK(AttachAndStopProcess(pid).has_value()); + +// // Detach and end child. +// ORBIT_CHECK(DetachAndContinueProcess(pid).has_value()); +// kill(pid, SIGKILL); +// waitpid(pid, nullptr, 0); +// } + +// TEST(TrampolineTest, AddressDifferenceAsInt32) { +// // Result of the difference is negative; in the first case it just fits, the second case +// // overflows. +// constexpr uint64_t kAddr1 = 0x6012345612345678; +// constexpr uint64_t kAddr2Larger = kAddr1 - std::numeric_limits::min(); +// auto result = AddressDifferenceAsInt32(kAddr1, kAddr2Larger); +// ASSERT_THAT(result, HasNoError()); +// EXPECT_EQ(std::numeric_limits::min(), result.value()); +// result = AddressDifferenceAsInt32(kAddr1, kAddr2Larger + 1); +// EXPECT_THAT(result, HasErrorWithMessage("Difference is larger than -2GB")); + +// // Result of the difference is positive; in the first case it just fits, the second case +// // overflows. +// constexpr uint64_t kAddr2Smaller = kAddr1 - std::numeric_limits::max(); +// result = AddressDifferenceAsInt32(kAddr1, kAddr2Smaller); +// ASSERT_THAT(result, HasNoError()); +// EXPECT_EQ(std::numeric_limits::max(), result.value()); +// result = AddressDifferenceAsInt32(kAddr1, kAddr2Smaller - 1); +// EXPECT_THAT(result, HasErrorWithMessage("Difference is larger than +2GB")); + +// // Result of the difference does not even fit into a int64. We handle that gracefully as well. +// constexpr uint64_t kAddrHigh = 0xf234567812345678; +// constexpr uint64_t kAddrLow = kAddrHigh - 0xe234567812345678; +// result = AddressDifferenceAsInt32(kAddrHigh, kAddrLow); +// EXPECT_THAT(result, HasErrorWithMessage("Difference is larger than +2GB")); +// result = AddressDifferenceAsInt32(kAddrLow, kAddrHigh); +// EXPECT_THAT(result, HasErrorWithMessage("Difference is larger than -2GB")); +// } + +// class RelocateInstructionTest : public testing::Test { +// protected: +// void SetUp() override { +// ORBIT_CHECK(cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_) == CS_ERR_OK); +// ORBIT_CHECK(cs_option(capstone_handle_, CS_OPT_DETAIL, CS_OPT_ON) == CS_ERR_OK); +// instruction_ = cs_malloc(capstone_handle_); +// ORBIT_CHECK(instruction_ != nullptr); +// } + +// void Disassemble(const MachineCode& code) { +// const uint8_t* code_pointer = code.GetResultAsVector().data(); +// size_t code_size = code.GetResultAsVector().size(); +// uint64_t disassemble_address = 0; +// ORBIT_CHECK(cs_disasm_iter(capstone_handle_, &code_pointer, &code_size, &disassemble_address, +// instruction_)); +// } + +// void TearDown() override { +// cs_free(instruction_, 1); +// cs_close(&capstone_handle_); +// } + +// cs_insn* instruction_ = nullptr; + +// private: +// csh capstone_handle_ = 0; +// }; + +// TEST_F(RelocateInstructionTest, RipRelativeAddressing) { +// MachineCode code; +// constexpr int32_t kOffset = 0x969433; +// // add qword ptr [rip + kOffset], 1 +// // Handled by "((instruction->detail->x86.modrm & 0xC7) == 0x05)" branch in 'RelocateInstruction'. +// code.AppendBytes({0x48, 0x83, 0x05}).AppendImmediate32(kOffset).AppendBytes({0x01}); +// Disassemble(code); + +// constexpr uint64_t kOriginalAddress = 0x0100000000; +// ErrorMessageOr result = +// RelocateInstruction(instruction_, kOriginalAddress, kOriginalAddress + kOffset - 0x123456); +// ASSERT_THAT(result, HasValue()); +// // add qword ptr [rip + new_offset], 1 48 83 05 56 34 12 00 01 +// // new_offset is computed as +// // old_absolute_address - new_address +// // == (old_address + old_displacement) - (old_address + old_displacement - 0x123456) +// // == 0x123456 +// EXPECT_THAT(result.value().code, +// ElementsAreArray({0x48, 0x83, 0x05, 0x56, 0x34, 0x12, 0x00, 0x01})); +// EXPECT_FALSE(result.value().position_of_absolute_address.has_value()); + +// result = +// RelocateInstruction(instruction_, kOriginalAddress, kOriginalAddress + kOffset + 0x123456); +// ASSERT_THAT(result, HasValue()); +// // add qword ptr [rip + new_offset], 1 48 83 05 aa cb ed ff 01 +// // new_offset is computed as +// // old_absolute_address - new_address +// // == (old_address + old_displacement) - (old_address + old_displacement + 0x123456) +// // == -0x123456 == 0xffedcbaa +// EXPECT_THAT(result.value().code, +// ElementsAreArray({0x48, 0x83, 0x05, 0xaa, 0xcb, 0xed, 0xff, 0x01})); +// EXPECT_FALSE(result.value().position_of_absolute_address.has_value()); + +// result = RelocateInstruction(instruction_, kOriginalAddress, kOriginalAddress - 0x7fff0000); +// EXPECT_THAT(result, +// HasErrorWithMessage( +// "While trying to relocate an instruction with rip relative addressing the " +// "target was out of range from the trampoline.")); +// } + +// TEST_F(RelocateInstructionTest, UnconditionalJumpTo8BitImmediate) { +// MachineCode code; +// constexpr int8_t kOffset = 0x08; +// // jmp [rip + kOffset] +// // Handled by "(instruction->detail->x86.opcode[0] == 0xeb)" branch in 'RelocateInstruction'. +// code.AppendBytes({0xeb}).AppendImmediate8(kOffset); +// Disassemble(code); + +// ErrorMessageOr result = +// RelocateInstruction(instruction_, 0x0100000000, 0x0200000000); +// ASSERT_THAT(result, HasValue()); +// // jmp [rip + 0] ff 25 00 00 00 00 +// // absolute_address 0a 00 00 00 01 00 00 00 +// // original jump instruction ends on 0x0100000000 + 0x02. Adding kOffset (=8) yields 0x010000000a. +// EXPECT_THAT(result.value().code, ElementsAreArray({0xff, 0x25, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, +// 0x00, 0x00, 0x01, 0x00, 0x00, 0x00})); +// ASSERT_TRUE(result.value().position_of_absolute_address.has_value()); +// EXPECT_EQ(6, result.value().position_of_absolute_address.value()); +// } + +// TEST_F(RelocateInstructionTest, UnconditionalJumpTo32BitImmediate) { +// MachineCode code; +// constexpr int32_t kOffset = 0x01020304; +// // jmp [rip + kOffset] +// // Handled by "(instruction->detail->x86.opcode[0] == 0xe9)" branch in 'RelocateInstruction'. +// code.AppendBytes({0xe9}).AppendImmediate32(kOffset); +// Disassemble(code); + +// ErrorMessageOr result = +// RelocateInstruction(instruction_, 0x0100000000, 0x0200000000); +// ASSERT_THAT(result, HasValue()); +// // jmp [rip + 0] ff 25 00 00 00 00 +// // absolute_address 09 03 02 01 01 00 00 00 +// // original jump instruction ends on 0x0100000000 + 0x05. Adding kOffset yields 0x0101020309. +// EXPECT_THAT(result.value().code, ElementsAreArray({0xff, 0x25, 0x00, 0x00, 0x00, 0x00, 0x09, 0x03, +// 0x02, 0x01, 0x01, 0x00, 0x00, 0x00})); +// ASSERT_TRUE(result.value().position_of_absolute_address.has_value()); +// EXPECT_EQ(6, result.value().position_of_absolute_address.value()); +// } + +// TEST_F(RelocateInstructionTest, CallInstructionIsNotSupported) { +// MachineCode code; +// constexpr int32_t kOffset = 0x01020304; +// // call [rip + kOffset] +// // Handled by "(instruction->detail->x86.opcode[0] == 0xe8)" branch in 'RelocateInstruction'. +// code.AppendBytes({0xe8}).AppendImmediate32(kOffset); +// Disassemble(code); + +// ErrorMessageOr result = +// RelocateInstruction(instruction_, 0x0100000000, 0x0200000000); +// EXPECT_THAT(result, HasErrorWithMessage("Relocating a call instruction is not supported.")); +// } + +// TEST_F(RelocateInstructionTest, ConditionalJumpTo8BitImmediate) { +// MachineCode code; +// constexpr int8_t kOffset = 0x40; +// // jno rip + kOffset +// // Handled by "((instruction->detail->x86.opcode[0] & 0xf0) == 0x70)" branch in +// // 'RelocateInstruction'. +// code.AppendBytes({0x71}).AppendImmediate8(kOffset); +// Disassemble(code); + +// ErrorMessageOr result = +// RelocateInstruction(instruction_, 0x0100000000, 0x0200000000); +// ASSERT_THAT(result, HasValue()); +// // jo rip + 16 70 0e +// // jmp [rip + 6] ff 25 00 00 00 00 +// // absolute_address 42 00 00 00 01 00 00 00 +// // original jump instruction ends on 0x0100000002 + 0x40 (kOffset) == 0x0100000042. +// EXPECT_THAT(result.value().code, +// ElementsAreArray({0x70, 0x0e, 0xff, 0x25, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, +// 0x00, 0x01, 0x00, 0x00, 0x00})); +// ASSERT_TRUE(result.value().position_of_absolute_address.has_value()); +// EXPECT_EQ(8, result.value().position_of_absolute_address.value()); +// } + +// TEST_F(RelocateInstructionTest, ConditionalJumpTo32BitImmediate) { +// MachineCode code; +// constexpr int32_t kOffset = 0x12345678; +// // jno rip + kOffset 0f 80 78 56 34 12 +// // Handled by "(instruction->detail->x86.opcode[0] == 0x0f && +// // (instruction->detail->x86.opcode[1] & 0xf0) == 0x80)" +// // branch in 'RelocateInstruction'. +// code.AppendBytes({0x0f, 0x80}).AppendImmediate32(kOffset); +// Disassemble(code); + +// ErrorMessageOr result = +// RelocateInstruction(instruction_, 0x0100000000, 0x0200000000); +// ASSERT_TRUE(result.has_value()); +// // jo rip + 16 71 0e +// // jmp [rip +6] ff 25 00 00 00 00 +// // absolute_address 7a 56 34 12 01 00 00 00 +// // original jump instruction ends on 0x0100000006 + 0x12345678 (kOffset) == 0x011234567e. +// EXPECT_THAT(result.value().code, +// ElementsAreArray({0x71, 0x0e, 0xff, 0x25, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x56, 0x34, +// 0x12, 0x01, 0x00, 0x00, 0x00})); +// ASSERT_TRUE(result.value().position_of_absolute_address.has_value()); +// EXPECT_EQ(8, result.value().position_of_absolute_address.value()); +// } + +// TEST_F(RelocateInstructionTest, LoopIsUnsupported) { +// MachineCode code; +// constexpr int8_t kOffset = 0x40; +// // loopz rip + kOffset +// // Handled by "((instruction->detail->x86.opcode[0] & 0xfc) == 0xe0)" branch in +// // 'RelocateInstruction'. +// code.AppendBytes({0xe1}).AppendImmediate8(kOffset); +// Disassemble(code); + +// ErrorMessageOr result = +// RelocateInstruction(instruction_, 0x0100000000, 0x0200000000); +// EXPECT_THAT(result, HasErrorWithMessage("Relocating a loop instruction is not supported.")); +// } + +// TEST_F(RelocateInstructionTest, TrivialTranslation) { +// MachineCode code; +// // nop +// // Handled by "else" branch in 'RelocateInstruction' - instruction is just copied. +// code.AppendBytes({0x90}); +// Disassemble(code); + +// ErrorMessageOr result = +// RelocateInstruction(instruction_, 0x0100000000, 0x0200000000); +// ASSERT_THAT(result, HasValue()); +// EXPECT_THAT(result.value().code, ElementsAreArray({0x90})); +// EXPECT_FALSE(result.value().position_of_absolute_address.has_value()); +// } + +// class InstrumentFunctionTest : public testing::Test { +// protected: +// void SetUp() override { +// /* copybara:insert(b/237251106 injecting the library into the target process triggers some +// initilization code that check fails.) +// GTEST_SKIP(); +// */ +// // Init Capstone disassembler. +// cs_err error_code = cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_); +// ORBIT_CHECK(error_code == CS_ERR_OK); +// error_code = cs_option(capstone_handle_, CS_OPT_DETAIL, CS_OPT_ON); +// ORBIT_CHECK(error_code == CS_ERR_OK); + +// max_trampoline_size_ = GetMaxTrampolineSize(); +// } + +// void RunChild(int (*function_pointer)(), std::string_view function_name) { +// function_name_ = function_name; + +// pid_ = fork(); +// ORBIT_CHECK(pid_ != -1); +// if (pid_ == 0) { +// prctl(PR_SET_PDEATHSIG, SIGTERM); + +// // Endless loops without side effects are UB and recent versions of clang optimize +// // it away. Making `sum` volatile avoids that problem. +// [[maybe_unused]] volatile uint64_t sum = 0; +// while (true) { +// sum += (*function_pointer)(); +// } +// } +// } + +// AddressRange GetFunctionAddressRangeOrDie() { +// return GetFunctionAbsoluteAddressRangeOrDie(function_name_); +// } + +// void PrepareInstrumentation(std::string_view entry_payload_function_name, +// std::string_view exit_payload_function_name) { +// // Stop the child process using our tooling. +// ORBIT_CHECK(AttachAndStopProcess(pid_).has_value()); + +// auto library_path_or_error = GetTestLibLibraryPath(); +// ORBIT_CHECK(library_path_or_error.has_value()); +// std::filesystem::path library_path = std::move(library_path_or_error.value()); + +// auto modules_or_error = orbit_module_utils::ReadModules(pid_); +// ORBIT_CHECK(modules_or_error.has_value()); +// const std::vector& modules = modules_or_error.value(); + +// // Inject the payload for the instrumentation. +// auto library_handle_or_error = DlmopenInTracee(pid_, modules, library_path, RTLD_NOW, +// LinkerNamespace::kCreateNewNamespace); +// ORBIT_CHECK(library_handle_or_error.has_value()); +// void* library_handle = library_handle_or_error.value(); + +// auto entry_payload_function_address_or_error = +// DlsymInTracee(pid_, modules, library_handle, entry_payload_function_name); +// ORBIT_CHECK(entry_payload_function_address_or_error.has_value()); +// entry_payload_function_address_ = +// absl::bit_cast(entry_payload_function_address_or_error.value()); + +// auto exit_payload_function_address_or_error = +// DlsymInTracee(pid_, modules, library_handle, exit_payload_function_name); +// ORBIT_CHECK(exit_payload_function_address_or_error.has_value()); +// exit_payload_function_address_ = +// absl::bit_cast(exit_payload_function_address_or_error.value()); + +// // Get address of the function to instrument. +// const AddressRange address_range_code = GetFunctionAddressRangeOrDie(); +// function_address_ = address_range_code.start; +// const uint64_t size_of_function = address_range_code.end - address_range_code.start; + +// // Get memory for the trampoline. +// auto trampoline_or_error = +// AllocateMemoryForTrampolines(pid_, address_range_code, max_trampoline_size_); +// ORBIT_CHECK(!trampoline_or_error.has_error()); +// trampoline_memory_ = std::move(trampoline_or_error.value()); +// trampoline_address_ = trampoline_memory_->GetAddress(); + +// // Get memory for return trampoline and create the return trampoline. +// auto return_trampoline_or_error = MemoryInTracee::Create(pid_, 0, GetReturnTrampolineSize()); +// ORBIT_CHECK(!return_trampoline_or_error.has_error()); +// return_trampoline_address_ = return_trampoline_or_error.value()->GetAddress(); +// auto result = +// CreateReturnTrampoline(pid_, exit_payload_function_address_, return_trampoline_address_); +// ORBIT_CHECK(!result.has_error()); +// ORBIT_CHECK(!return_trampoline_or_error.value()->EnsureMemoryExecutable().has_error()); + +// // Copy the beginning of the function over into this process. +// constexpr uint64_t kMaxFunctionBackupSize = 200; +// const uint64_t bytes_to_copy = std::min(size_of_function, kMaxFunctionBackupSize); +// ErrorMessageOr> function_backup = +// ReadTraceesMemory(pid_, function_address_, bytes_to_copy); +// ORBIT_CHECK(function_backup.has_value()); +// function_code_ = function_backup.value(); +// } + +// // Runs the child for a millisecond to assert it is still working fine, stops it, removes the +// // instrumentation, restarts and stops it again. +// void RestartAndRemoveInstrumentation() { +// ORBIT_CHECK(!trampoline_memory_->EnsureMemoryExecutable().has_error()); + +// MoveInstructionPointersOutOfOverwrittenCode(pid_, relocation_map_); + +// ORBIT_CHECK(!DetachAndContinueProcess(pid_).has_error()); +// std::this_thread::sleep_for(std::chrono::milliseconds(1)); +// ORBIT_CHECK(AttachAndStopProcess(pid_).has_value()); + +// auto write_result_or_error = WriteTraceesMemory(pid_, function_address_, function_code_); +// ORBIT_CHECK(!write_result_or_error.has_error()); + +// ORBIT_CHECK(!DetachAndContinueProcess(pid_).has_error()); +// std::this_thread::sleep_for(std::chrono::milliseconds(1)); +// ORBIT_CHECK(AttachAndStopProcess(pid_).has_value()); +// } + +// void TearDown() override { +// cs_close(&capstone_handle_); + +// // Detach and end child. +// if (pid_ != -1) { +// ORBIT_CHECK(!DetachAndContinueProcess(pid_).has_error()); +// kill(pid_, SIGKILL); +// waitpid(pid_, nullptr, 0); +// } +// } + +// pid_t pid_ = -1; +// csh capstone_handle_ = 0; +// uint64_t max_trampoline_size_ = 0; +// std::unique_ptr trampoline_memory_; +// uint64_t trampoline_address_ = 0; +// uint64_t return_trampoline_address_ = 0; +// uint64_t entry_payload_function_address_ = 0; +// uint64_t exit_payload_function_address_ = 0; + +// absl::flat_hash_map relocation_map_; + +// std::string function_name_; +// uint64_t function_address_ = 0; +// std::vector function_code_; +// }; + +// // Function with an ordinary compiler-synthesised prologue; performs some arithmetics. Most real +// // world functions will look like this (starting with pushing the stack frame...). Most functions +// // below are declared "naked", i.e. without the prologue and implemented entirely in assembly. This +// // is done to also cover edge cases. +// extern "C" __attribute__((noinline)) int DoSomething() { +// std::random_device rd; +// std::mt19937 gen(rd()); +// std::uniform_int_distribution dis(1, 6); +// std::vector v(10); +// std::generate(v.begin(), v.end(), [&]() { return dis(gen); }); +// int sum = std::accumulate(v.begin(), v.end(), 0); +// return sum; +// } + +// TEST_F(InstrumentFunctionTest, DoSomething) { +// RunChild(&DoSomething, "DoSomething"); +// PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); +// ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(address_after_prologue_or_error, HasNoError()); +// ErrorMessageOr result = +// InstrumentFunction(pid_, function_address_, /*function_id=*/42, +// address_after_prologue_or_error.value(), trampoline_address_); +// EXPECT_THAT(result, HasNoError()); +// RestartAndRemoveInstrumentation(); +// } + +// TEST_F(InstrumentFunctionTest, CheckStackAlignedTo16Bytes) { +// RunChild(&DoSomething, "DoSomething"); +// PrepareInstrumentation("EntryPayloadAlignedCopy", kExitPayloadFunctionName); +// ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(address_after_prologue_or_error, HasNoError()); +// ErrorMessageOr result = +// InstrumentFunction(pid_, function_address_, /*function_id=*/42, +// address_after_prologue_or_error.value(), trampoline_address_); +// EXPECT_THAT(result, HasNoError()); +// RestartAndRemoveInstrumentation(); +// } + +// // We will not be able to instrument this - the function is just four bytes long and we need five +// // bytes to write a jump. +// extern "C" __attribute__((noinline, naked)) int TooShort() { +// __asm__ __volatile__( +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// "ret \n\t" +// : +// : +// :); +// } + +// TEST_F(InstrumentFunctionTest, TooShort) { +// #if defined(ORBIT_COVERAGE_BUILD) || !defined(__clang__) || !defined(NDEBUG) +// GTEST_SKIP(); +// #endif +// RunChild(&TooShort, "TooShort"); +// PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); +// ErrorMessageOr result = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(result, +// HasErrorWithMessage("Unable to disassemble enough of the function to instrument it")); +// RestartAndRemoveInstrumentation(); +// } + +// // This function is just long enough to be instrumented (five bytes). It is also interesting in that +// // the return statement is copied into the trampoline and executed from there. +// extern "C" __attribute__((noinline, naked)) int LongEnough() { +// __asm__ __volatile__( +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// "ret \n\t" +// : +// : +// :); +// } + +// TEST_F(InstrumentFunctionTest, LongEnough) { +// RunChild(&LongEnough, "LongEnough"); +// PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); +// ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(address_after_prologue_or_error, HasNoError()); +// ErrorMessageOr result = +// InstrumentFunction(pid_, function_address_, /*function_id=*/42, +// address_after_prologue_or_error.value(), trampoline_address_); +// EXPECT_THAT(result, HasNoError()); +// RestartAndRemoveInstrumentation(); +// } + +// // The rip relative address is translated to the new code position. +// extern "C" __attribute__((noinline, naked)) int RipRelativeAddressing() { +// __asm__ __volatile__( +// "movq 0x03(%%rip), %%rax\n\t" +// "nop \n\t" +// "nop \n\t" +// "ret \n\t" +// ".quad 0x0102034200000000 \n\t" +// : +// : +// :); +// } + +// TEST_F(InstrumentFunctionTest, RipRelativeAddressing) { +// RunChild(&RipRelativeAddressing, "RipRelativeAddressing"); +// PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); +// ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(address_after_prologue_or_error, HasNoError()); +// ErrorMessageOr result = +// InstrumentFunction(pid_, function_address_, /*function_id=*/42, +// address_after_prologue_or_error.value(), trampoline_address_); +// EXPECT_THAT(result, HasNoError()); +// RestartAndRemoveInstrumentation(); +// } + +// // Unconditional jump to an 8-bit offset. +// extern "C" __attribute__((noinline, naked)) int UnconditionalJump8BitOffset() { +// __asm__ __volatile__( +// "jmp label_unconditional_jmp_8_bit \n\t" +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// "label_unconditional_jmp_8_bit: \n\t" +// "ret \n\t" +// : +// : +// :); +// } + +// TEST_F(InstrumentFunctionTest, UnconditionalJump8BitOffset) { +// RunChild(&UnconditionalJump8BitOffset, "UnconditionalJump8BitOffset"); +// PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); +// ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(address_after_prologue_or_error, HasNoError()); +// ErrorMessageOr result = +// InstrumentFunction(pid_, function_address_, /*function_id=*/42, +// address_after_prologue_or_error.value(), trampoline_address_); +// EXPECT_THAT(result, HasNoError()); +// RestartAndRemoveInstrumentation(); +// } + +// // Unconditional jump to a 32 bit offset. +// extern "C" __attribute__((noinline, naked)) int UnconditionalJump32BitOffset() { +// __asm__ __volatile__( +// "jmp label_unconditional_jmp_32_bit \n\t" +// ".octa 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \n\t" // 256 bytes of zeros +// "label_unconditional_jmp_32_bit: \n\t" +// "ret \n\t" +// : +// : +// :); +// } + +// TEST_F(InstrumentFunctionTest, UnconditionalJump32BitOffset) { +// RunChild(&UnconditionalJump32BitOffset, "UnconditionalJump32BitOffset"); +// PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); +// ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(address_after_prologue_or_error, HasNoError()); +// ErrorMessageOr result = +// InstrumentFunction(pid_, function_address_, /*function_id=*/42, +// address_after_prologue_or_error.value(), trampoline_address_); +// EXPECT_THAT(result, HasNoError()); +// RestartAndRemoveInstrumentation(); +// } + +// // The rip relative address is translated to the new code position. +// extern "C" __attribute__((noinline, naked)) int ConditionalJump8BitOffset() { +// __asm__ __volatile__( +// "jnz loop_label_jcc \n\t" +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// "loop_label_jcc: \n\t" +// "xor %%eax, %%eax \n\t" +// "nop \n\t" +// "nop \n\t" +// "ret \n\t" +// : +// : +// :); +// } + +// TEST_F(InstrumentFunctionTest, ConditionalJump8BitOffset) { +// RunChild(&ConditionalJump8BitOffset, "ConditionalJump8BitOffset"); +// PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); +// ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(address_after_prologue_or_error, HasNoError()); +// ErrorMessageOr result = +// InstrumentFunction(pid_, function_address_, /*function_id=*/42, +// address_after_prologue_or_error.value(), trampoline_address_); +// EXPECT_THAT(result, HasNoError()); +// RestartAndRemoveInstrumentation(); +// } + +// // The rip relative address is translated to the new code position. +// extern "C" __attribute__((noinline, naked)) int ConditionalJump32BitOffset() { +// __asm__ __volatile__( +// "xor %%eax, %%eax \n\t" +// "jnz label_jcc_32_bit \n\t" +// "nop \n\t" +// "ret \n\t" +// ".octa 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \n\t" // 256 bytes of zeros +// "label_jcc_32_bit: \n\t" +// "ret \n\t" +// : +// : +// :); +// } + +// TEST_F(InstrumentFunctionTest, ConditionalJump32BitOffset) { +// RunChild(&ConditionalJump32BitOffset, "ConditionalJump32BitOffset"); +// PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); +// ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(address_after_prologue_or_error, HasNoError()); +// ErrorMessageOr result = +// InstrumentFunction(pid_, function_address_, /*function_id=*/42, +// address_after_prologue_or_error.value(), trampoline_address_); +// EXPECT_THAT(result, HasNoError()); +// RestartAndRemoveInstrumentation(); +// } + +// // Function can not be instrumented since it uses the unsupported loop instruction. +// extern "C" __attribute__((noinline, naked)) int Loop() { +// __asm__ __volatile__( +// "mov $42, %%cx\n\t" +// "loop_label:\n\t" +// "loopnz loop_label\n\t" +// "ret \n\t" +// : +// : +// :); +// } + +// TEST_F(InstrumentFunctionTest, Loop) { +// #if defined(ORBIT_COVERAGE_BUILD) || !defined(__clang__) +// GTEST_SKIP(); +// #endif +// RunChild(&Loop, "Loop"); +// PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); +// ErrorMessageOr result = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(result, HasErrorWithMessage("Relocating a loop instruction is not supported.")); +// RestartAndRemoveInstrumentation(); +// } + +// // Check-fails if any parameter is not zero. +// extern "C" __attribute__((noinline)) int CheckIntParameters(uint64_t p0, uint64_t p1, uint64_t p2, +// uint64_t p3, uint64_t p4, uint64_t p5, +// uint64_t p6, uint64_t p7) { +// ORBIT_CHECK(p0 == 0 && p1 == 0 && p2 == 0 && p3 == 0 && p4 == 0 && p5 == 0 && p6 == 0 && p7 == 0); +// return 0; +// } + +// // This test and the tests below check for proper handling of parameters handed to the instrumented +// // function. The payload that is called before the instrumented function is executed clobbers the +// // respective set of registers. So the Check*Parameter methods can check if the backup worked +// // correctly. +// TEST_F(InstrumentFunctionTest, CheckIntParameters) { +// function_name_ = "CheckIntParameters"; +// pid_ = fork(); +// ORBIT_CHECK(pid_ != -1); +// if (pid_ == 0) { +// prctl(PR_SET_PDEATHSIG, SIGTERM); + +// // Endless loops without side effects are UB and recent versions of clang optimize it away. +// // Making `sum` volatile avoids that problem. +// [[maybe_unused]] volatile uint64_t sum = 0; +// while (true) { +// sum += CheckIntParameters(0, 0, 0, 0, 0, 0, 0, 0); +// } +// } +// PrepareInstrumentation("EntryPayloadClobberParameterRegisters", kExitPayloadFunctionName); +// ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(address_after_prologue_or_error, HasNoError()); +// ErrorMessageOr result = +// InstrumentFunction(pid_, function_address_, /*function_id=*/42, +// address_after_prologue_or_error.value(), trampoline_address_); +// EXPECT_THAT(result, HasNoError()); +// RestartAndRemoveInstrumentation(); +// } + +// // Check-fails if any parameter is not zero. +// extern "C" __attribute__((noinline)) int CheckFloatParameters(float p0, float p1, float p2, +// float p3, float p4, float p5, +// float p6, float p7) { +// ORBIT_CHECK(p0 == 0.f && p1 == 0.f && p2 == 0.f && p3 == 0.f && p4 == 0.f && p5 == 0.f && +// p6 == 0.f && p7 == 0.f); +// return 0; +// } + +// TEST_F(InstrumentFunctionTest, CheckFloatParameters) { +// function_name_ = "CheckFloatParameters"; +// pid_ = fork(); +// ORBIT_CHECK(pid_ != -1); +// if (pid_ == 0) { +// prctl(PR_SET_PDEATHSIG, SIGTERM); + +// // Endless loops without side effects are UB and recent versions of clang optimize it away. +// // Making `sum` volatile avoids that problem. +// [[maybe_unused]] volatile uint64_t sum = 0; +// while (true) { +// sum += CheckFloatParameters(0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f); +// } +// } +// PrepareInstrumentation("EntryPayloadClobberXmmRegisters", kExitPayloadFunctionName); +// ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(address_after_prologue_or_error, HasNoError()); +// ErrorMessageOr result = +// InstrumentFunction(pid_, function_address_, /*function_id=*/42, +// address_after_prologue_or_error.value(), trampoline_address_); +// EXPECT_THAT(result, HasNoError()); +// RestartAndRemoveInstrumentation(); +// } + +// // Check-fails if any parameter is not zero. +// extern "C" __attribute__((noinline)) int CheckM256iParameters(__m256i p0, __m256i p1, __m256i p2, +// __m256i p3, __m256i p4, __m256i p5, +// __m256i p6, __m256i p7) { +// // ORBIT_CHECK(_mm256_extract_epi64(p0, 0) == 0 && _mm256_extract_epi64(p1, 0) == 0 && +// // _mm256_extract_epi64(p2, 0) == 0 && _mm256_extract_epi64(p3, 0) == 0 && +// // _mm256_extract_epi64(p4, 0) == 0 && _mm256_extract_epi64(p5, 0) == 0 && +// // _mm256_extract_epi64(p6, 0) == 0 && _mm256_extract_epi64(p7, 0) == 0); +// // return 0; +// } + +// TEST_F(InstrumentFunctionTest, CheckM256iParameters) { +// // function_name_ = "CheckM256iParameters"; +// // pid_ = fork(); +// // ORBIT_CHECK(pid_ != -1); +// // if (pid_ == 0) { +// // prctl(PR_SET_PDEATHSIG, SIGTERM); + +// // // Endless loops without side effects are UB and recent versions of clang optimize it away. +// // // Making `sum` volatile avoids that problem. +// // [[maybe_unused]] volatile uint64_t sum = 0; +// // while (true) { +// // sum += +// // CheckM256iParameters(_mm256_set1_epi64x(0), _mm256_set1_epi64x(0), _mm256_set1_epi64x(0), +// // _mm256_set1_epi64x(0), _mm256_set1_epi64x(0), _mm256_set1_epi64x(0), +// // _mm256_set1_epi64x(0), _mm256_set1_epi64x(0)); +// // } +// // } +// // PrepareInstrumentation("EntryPayloadClobberYmmRegisters", kExitPayloadFunctionName); +// // ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( +// // pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// // return_trampoline_address_, capstone_handle_, relocation_map_); +// // EXPECT_THAT(address_after_prologue_or_error, HasNoError()); +// // ErrorMessageOr result = +// // InstrumentFunction(pid_, function_address_, /*function_id=*/42, +// // address_after_prologue_or_error.value(), trampoline_address_); +// // EXPECT_THAT(result, HasNoError()); +// // RestartAndRemoveInstrumentation(); +// } + +// // Check-fails if any parameter is not zero. +// extern "C" __attribute__((noinline, ms_abi)) int CheckIntParametersMsAbi(uint64_t p0, uint64_t p1, +// uint64_t p2, uint64_t p3) { +// ORBIT_CHECK(p0 == 0 && p1 == 0 && p2 == 0 && p3 == 0); +// return 0; +// } + +// TEST_F(InstrumentFunctionTest, CheckIntParametersMsAbi) { +// function_name_ = "CheckIntParametersMsAbi"; +// pid_ = fork(); +// ORBIT_CHECK(pid_ != -1); +// if (pid_ == 0) { +// prctl(PR_SET_PDEATHSIG, SIGTERM); + +// // Endless loops without side effects are UB and recent versions of clang optimize it away. +// // Making `sum` volatile avoids that problem. +// [[maybe_unused]] volatile uint64_t sum = 0; +// while (true) { +// sum += CheckIntParametersMsAbi(0, 0, 0, 0); +// } +// } +// PrepareInstrumentation("EntryPayloadClobberParameterRegisters", kExitPayloadFunctionName); +// ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(address_after_prologue_or_error, HasNoError()); +// ErrorMessageOr result = +// InstrumentFunction(pid_, function_address_, /*function_id=*/42, +// address_after_prologue_or_error.value(), trampoline_address_); +// EXPECT_THAT(result, HasNoError()); +// RestartAndRemoveInstrumentation(); +// } + +// // Check-fails if any parameter is not zero. +// extern "C" __attribute__((noinline, ms_abi)) int CheckFloatParametersMsAbi(float p0, float p1, +// float p2, float p3) { +// ORBIT_CHECK(p0 == 0.f && p1 == 0.f && p2 == 0.f && p3 == 0.f); +// return 0; +// } + +// TEST_F(InstrumentFunctionTest, CheckFloatParametersMsAbi) { +// function_name_ = "CheckFloatParametersMsAbi"; +// pid_ = fork(); +// ORBIT_CHECK(pid_ != -1); +// if (pid_ == 0) { +// prctl(PR_SET_PDEATHSIG, SIGTERM); + +// // Endless loops without side effects are UB and recent versions of clang optimize it away. +// // Making `sum` volatile avoids that problem. +// [[maybe_unused]] volatile uint64_t sum = 0; +// while (true) { +// sum += CheckFloatParametersMsAbi(0.f, 0.f, 0.f, 0.f); +// } +// } +// PrepareInstrumentation("EntryPayloadClobberXmmRegisters", kExitPayloadFunctionName); +// ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(address_after_prologue_or_error, HasNoError()); +// ErrorMessageOr result = +// InstrumentFunction(pid_, function_address_, /*function_id=*/42, +// address_after_prologue_or_error.value(), trampoline_address_); +// EXPECT_THAT(result, HasNoError()); +// RestartAndRemoveInstrumentation(); +// } + +// // This test guards against naively backing up x87 registers in the return trampoline when the +// // instrumented function doesn't use them to return values. +// TEST_F(InstrumentFunctionTest, CheckNoX87UnderflowInReturnTrampoline) { +// function_name_ = "DoSomething"; +// pid_ = fork(); +// ORBIT_CHECK(pid_ != -1); +// if (pid_ == 0) { +// prctl(PR_SET_PDEATHSIG, SIGTERM); + +// // Reset bit 0 of the 16-bit x87 FPU Control Word, in order to unmask invalid-operation +// // exception. If the return trampoline causes the underflow of the x87 register stack before +// // masking the exception, the process will crash. +// uint16_t control = 0; +// __asm__ __volatile__("fnstcw %0\n\t" : "=m"(control) : :); +// control &= 0xFE; +// __asm__ __volatile__("fldcw %0\n\t" : : "m"(control) :); + +// // Endless loops without side effects are UB and recent versions of clang optimize it away. +// // Making `sum` volatile avoids that problem. +// [[maybe_unused]] volatile uint64_t sum = 0; +// while (true) { +// sum += DoSomething(); +// } +// } +// PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); +// ErrorMessageOr address_after_prologue_or_error = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(address_after_prologue_or_error, HasNoError()); +// ErrorMessageOr result = +// InstrumentFunction(pid_, function_address_, /*function_id=*/42, +// address_after_prologue_or_error.value(), trampoline_address_); +// EXPECT_THAT(result, HasNoError()); +// RestartAndRemoveInstrumentation(); +// } + +// extern "C" __attribute__((noinline, naked)) int UnconditionalJump8BitOffsetBackToBeginning() { +// __asm__ __volatile__( +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// ".byte 0xeb \n\t" // jmp -7 (which is the first nop) +// ".byte 0xf9 \n\t" +// "xor %%eax, %%eax \n\t" +// "ret \n\t" +// : +// : +// :); +// } + +// // This will fail to create a trampoline since the function contains an unconditional jump to an +// // eight bit offset which points back into the first five bytes of the function. +// TEST_F(InstrumentFunctionTest, UnconditionalJump8BitOffsetBackToBeginning) { +// // Exclude gcc builds: the inline assembly above gets messed up by the compiler. +// #if defined(ORBIT_COVERAGE_BUILD) || !defined(__clang__) || !defined(NDEBUG) +// GTEST_SKIP(); +// #endif +// RunChild(&UnconditionalJump8BitOffsetBackToBeginning, +// "UnconditionalJump8BitOffsetBackToBeginning"); +// PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); +// ErrorMessageOr result = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(result, +// HasErrorWithMessage( +// "Failed to create trampoline since the function contains a jump back into")); +// } + +// extern "C" __attribute__((noinline, naked)) int UnconditionalJump32BitOffsetBackToBeginning() { +// __asm__ __volatile__( +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// ".byte 0xe9 \n\t" // jmp -10 (which is the first nop) +// ".long 0xfffffff6 \n\t" +// "xor %%eax, %%eax \n\t" +// "ret \n\t" +// : +// : +// :); +// } + +// // This will fail to create a trampoline since the function contains an unconditional jump to a +// // 32 bit offset which points back into the first five bytes of the function. +// TEST_F(InstrumentFunctionTest, UnconditionalJump32BitOffsetBackToBeginning) { +// // Exclude gcc builds: the inline assembly above gets messed up by the compiler. +// #if defined(ORBIT_COVERAGE_BUILD) || !defined(__clang__) || !defined(NDEBUG) +// GTEST_SKIP(); +// #endif +// RunChild(&UnconditionalJump32BitOffsetBackToBeginning, +// "UnconditionalJump32BitOffsetBackToBeginning"); +// PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); +// ErrorMessageOr result = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(result, +// HasErrorWithMessage( +// "Failed to create trampoline since the function contains a jump back into")); +// } + +// extern "C" __attribute__((noinline, naked)) int ConditionalJump8BitOffsetBackToBeginning() { +// __asm__ __volatile__( +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// ".byte 0x70 \n\t" // jo -7 (which is the first nop) +// ".byte 0xf9 \n\t" +// "xor %%eax, %%eax \n\t" +// "ret \n\t" +// : +// : +// :); +// } + +// // This will fail to create a trampoline since the function contains a conditional jump to an +// // eight bit offset which points back into the first five bytes of the function. +// TEST_F(InstrumentFunctionTest, ConditionalJump8BitOffsetBackToBeginning) { +// // Exclude gcc builds: the inline assembly above gets messed up by the compiler. +// #if defined(ORBIT_COVERAGE_BUILD) || !defined(__clang__) || !defined(NDEBUG) +// GTEST_SKIP(); +// #endif +// RunChild(&ConditionalJump8BitOffsetBackToBeginning, "ConditionalJump8BitOffsetBackToBeginning"); +// PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); +// ErrorMessageOr result = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(result, +// HasErrorWithMessage( +// "Failed to create trampoline since the function contains a jump back into")); +// } + +// extern "C" __attribute__((noinline, naked)) int ConditionalJump32BitOffsetBackToBeginning() { +// __asm__ __volatile__( +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// ".byte 0x0f \n\t" // jo -7 (which is the last nop) +// ".byte 0x80 \n\t" +// ".long 0xfffffff9 \n\t" +// "xor %%eax, %%eax \n\t" +// "ret \n\t" +// : +// : +// :); +// } + +// // This will fail to create a trampoline since the function contains a conditional jump to a +// // 32 bit offset which points back into the first five bytes of the function. +// TEST_F(InstrumentFunctionTest, ConditionalJump32BitOffsetBackToBeginning) { +// // Exclude gcc builds: the inline assembly above gets messed up by the compiler. +// #if defined(ORBIT_COVERAGE_BUILD) || !defined(__clang__) || !defined(NDEBUG) +// GTEST_SKIP(); +// #endif +// RunChild(&ConditionalJump32BitOffsetBackToBeginning, "ConditionalJump32BitOffsetBackToBeginning"); +// PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); +// ErrorMessageOr result = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(result, +// HasErrorWithMessage( +// "Failed to create trampoline since the function contains a jump back into")); +// } + +// extern "C" __attribute__((noinline, naked)) int LongConditionalJump32BitOffsetBackToBeginning() { +// __asm__ __volatile__( +// "xor %%eax, %%eax \n\t" +// "ret \n\t" +// ".octa 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \n\t" // 256 bytes of zeros +// ".byte 0x0f \n\t" // jo -263 (which is the ret) +// ".byte 0x80 \n\t" +// ".long 0xfffffef9 \n\t" +// : +// : +// :); +// } + +// // This will create a trampoline. The function contains a conditional jump to a +// // 32 bit offset which points back into the first five bytes of the function. However the jump is +// // occurring after the 200 byte limit and therefore it stays undetected. +// TEST_F(InstrumentFunctionTest, LongConditionalJump32BitOffsetBackToBeginning) { +// RunChild(&LongConditionalJump32BitOffsetBackToBeginning, +// "LongConditionalJump32BitOffsetBackToBeginning"); +// PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); +// ErrorMessageOr result = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(result, HasNoError()); +// } + +// extern "C" __attribute__((noinline, naked)) int UnableToDisassembleBadInstruction() { +// __asm__ __volatile__( +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// "nop \n\t" +// "ret \n\t" +// ".byte 0x06 \n\t" // bad instruction +// ".byte 0x0f \n\t" // jo -12 (which is the first nop) +// ".byte 0x80 \n\t" +// ".long 0xfffffff4 \n\t" +// : +// : +// :); +// } + +// // This will create a trampoline. There is a conditional jump back to the start but the disassembler +// // gets confused before it reaches this and so we don't detect it. +// TEST_F(InstrumentFunctionTest, UnableToDisassembleBadInstruction) { +// RunChild(&UnableToDisassembleBadInstruction, "UnableToDisassembleBadInstruction"); +// PrepareInstrumentation(kEntryPayloadFunctionName, kExitPayloadFunctionName); +// ErrorMessageOr result = CreateTrampoline( +// pid_, function_address_, function_code_, trampoline_address_, entry_payload_function_address_, +// return_trampoline_address_, capstone_handle_, relocation_map_); +// EXPECT_THAT(result, HasNoError()); +// } + +// } // namespace orbit_user_space_instrumentation diff --git a/third_party/libunwindstack/CMakeLists.txt b/third_party/libunwindstack/CMakeLists.txt index e956de18da9..9d3fe78e270 100644 --- a/third_party/libunwindstack/CMakeLists.txt +++ b/third_party/libunwindstack/CMakeLists.txt @@ -196,206 +196,193 @@ endif() target_compile_options(libunwindstack PRIVATE -Wno-error=format-nonliteral) -add_executable(libunwindstack_tests) -target_sources(libunwindstack_tests PRIVATE - $ - LogAndroid.cpp - - # AndroidUnwinderTest are broken and need to be investigated, - # but we currently don't use this part of the library. - # tests/AndroidUnwinderTest.cpp - - tests/ArmExidxDecodeTest.cpp - tests/ArmExidxExtractTest.cpp - - # We compile without DEX file support - # tests/DexFileTest.cpp - # tests/DexFilesTest.cpp - - tests/DwarfCfaLogTest.cpp - tests/DwarfCfaTest.cpp - tests/DwarfDebugFrameTest.cpp - tests/DwarfEhFrameTest.cpp - tests/DwarfEhFrameWithHdrTest.cpp - tests/DwarfMemoryTest.cpp - tests/DwarfOpLogTest.cpp - tests/DwarfOpTest.cpp - tests/DwarfSectionImplTest.cpp - tests/DwarfSectionTest.cpp - tests/ElfFake.cpp - tests/ElfInterfaceArmTest.cpp - tests/ElfInterfaceTest.cpp - tests/ElfTest.cpp - tests/ElfTestUtils.cpp - - # Not a test file - # tests/GenGnuDebugdata.cpp - - tests/GlobalDebugImplTest.cpp - tests/GlobalTest.cpp - tests/IsolatedSettings.cpp - tests/JitDebugTest.cpp - tests/LocalUpdatableMapsTest.cpp - tests/LogFake.cpp - tests/MapInfoCreateMemoryTest.cpp - tests/MapInfoGetBuildIDTest.cpp - tests/MapInfoGetLoadBiasTest.cpp - tests/MapInfoGetObjectTest.cpp - tests/MapInfoTest.cpp - tests/MapsTest.cpp - tests/MemoryBufferTest.cpp - tests/MemoryCacheTest.cpp - tests/MemoryFileTest.cpp - tests/MemoryLocalTest.cpp - - # This is Android-specific - # tests/MemoryMteTest.cpp - - tests/MemoryOfflineBufferTest.cpp - tests/MemoryOfflineTest.cpp - tests/MemoryRangeTest.cpp - tests/MemoryRangesTest.cpp - tests/MemoryRemoteTest.cpp - tests/MemoryTest.cpp - tests/MemoryThreadCacheTest.cpp - tests/MemoryXzTest.cpp - tests/ObjectBuildIdTest.cpp - tests/ObjectCacheTest.cpp - tests/PeCoffInterfaceTest.cpp - tests/PeCoffFake.cpp - tests/PeCoffRuntimeFunctionsTest.cpp - tests/PeCoffTest.cpp - tests/PeCoffUnwindInfoEvaluatorTest.cpp - tests/PeCoffUnwindInfosTest.cpp - tests/PeCoffUnwindInfoUnwinderX86_64Test.cpp - tests/PeCoffEpilogTest.cpp - tests/RegsInfoTest.cpp - tests/RegsIterateTest.cpp - tests/RegsStepIfSignalHandlerTest.cpp - tests/RegsRemoteTest.cpp - tests/RegsTest.cpp - tests/SymbolsTest.cpp - - # TestLocal is not a test. It belongs to a testing library. - # tests/TestLocal.cpp - - tests/TestUtils.cpp - tests/UnwindOfflineTest.cpp - tests/UnwindTest.cpp - tests/UnwinderTest.cpp - tests/VerifyBionicTerminationTest.cpp - utils/MemoryFake.cpp - utils/OfflineUnwindUtils.cpp - utils/PidUtils.cpp - utils/ProcessTracer.cpp) - -# Some tests call dlopen/dlsym/dlclose which requires the -# dynamic linker. -target_link_libraries(libunwindstack_tests PRIVATE ${CMAKE_DL_LIBS}) - -target_link_libraries(libunwindstack_tests PRIVATE - liblog_shared - libbase - libprocinfo - capstone::capstone - LZMA::LZMA - GTest::Main - ZLIB::ZLIB) - -target_include_directories(libunwindstack_tests PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR} - utils/ - include/) - -if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - target_compile_options(libunwindstack_tests PRIVATE - -Wno-error=format-nonliteral - -Wno-error=inconsistent-missing-override - -Wno-error=unused-result - -Wno-error=defaulted-function-deleted) -else() - target_compile_options(libunwindstack_tests PRIVATE - -Wno-error=format-nonliteral - -Wno-error=unused-result) -endif() - -# Some tests unwind its own callstacks so we have to disable inlining to make -# sure all the expected functions show up in the examined callstacks. -# They also invoke a lot of different kinds of UB which would all be optimized away, -# if we didn't disable optimization. -target_compile_options(libunwindstack_tests PRIVATE -O0) - -register_test(libunwindstack_tests) - -# The tests expect the testdata next to the test binary. -add_custom_command(TARGET libunwindstack_tests POST_BUILD - COMMAND ${CMAKE_COMMAND} -E remove_directory - $/offline_files - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_CURRENT_LIST_DIR}/offline_files - $/offline_files) - -add_custom_command(TARGET libunwindstack_tests POST_BUILD - COMMAND ${CMAKE_COMMAND} -E remove_directory - $/tests - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_CURRENT_LIST_DIR}/tests - $/tests) - - - -# This is a testing library that will be consumed by the tests. -add_library(libunwindstack_local SHARED - $ - LogStdout.cpp - tests/TestLocal.cpp) - -target_link_libraries(libunwindstack_local PRIVATE - libbase - - # liblog_static needs to be listed AFTER libbase due to cyclic depdendencies. - liblog_static - LZMA::LZMA) - -target_include_directories(libunwindstack_local PRIVATE include/) - -# Clang >= 8.0 -if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND - CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 8) - target_compile_options(libunwindstack_local PRIVATE - -Wno-error=ctad-maybe-unsupported) -endif() - -target_compile_options(libunwindstack_local PRIVATE - -Wno-error=format-nonliteral) - -# This library unwinds its own callstacks so we have to disable inlining -# to make sure all the expected functions show up in the examined callstacks. -# It also invokes a lot of different kinds of UB which would all be optimized away, -# if we didn't disable optimization. -target_compile_options(libunwindstack_local PRIVATE -O0) - -set_target_properties(libunwindstack_local PROPERTIES OUTPUT_NAME "unwindstack_local") - -# The tests expect this library next to the executable. -set_target_properties(libunwindstack_local PROPERTIES - LIBRARY_OUTPUT_DIRECTORY $) -set_target_properties(libunwindstack_local PROPERTIES - LIBRARY_OUTPUT_DIRECTORY_RELEASE $) -set_target_properties(libunwindstack_local PROPERTIES - LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO $) -set_target_properties(libunwindstack_local PROPERTIES - LIBRARY_OUTPUT_DIRECTORY_DEBUG $) - -# Fuzz testing for parsers -add_fuzzer(PeCoffInterfaceFuzzer tests/fuzz/PeCoffInterfaceFuzzer.cpp) -target_include_directories(PeCoffInterfaceFuzzer PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} include/) -target_link_libraries(PeCoffInterfaceFuzzer PRIVATE libunwindstack) - -add_fuzzer(PeCoffUnwindInfosFuzzer tests/fuzz/PeCoffUnwindInfosFuzzer.cpp) -target_include_directories(PeCoffUnwindInfosFuzzer PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} include/) -target_link_libraries(PeCoffUnwindInfosFuzzer PRIVATE libunwindstack) - -add_fuzzer(PeCoffRuntimeFunctionsFuzzer tests/fuzz/PeCoffRuntimeFunctionsFuzzer.cpp) -target_include_directories(PeCoffRuntimeFunctionsFuzzer PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} include/) -target_link_libraries(PeCoffRuntimeFunctionsFuzzer PRIVATE libunwindstack) \ No newline at end of file +# add_executable(libunwindstack_tests) +# target_sources(libunwindstack_tests PRIVATE +# $ +# LogAndroid.cpp + +# # AndroidUnwinderTest are broken and need to be investigated, +# # but we currently don't use this part of the library. +# # tests/AndroidUnwinderTest.cpp + +# tests/ArmExidxDecodeTest.cpp +# tests/ArmExidxExtractTest.cpp + +# # We compile without DEX file support +# # tests/DexFileTest.cpp +# # tests/DexFilesTest.cpp + +# tests/DwarfCfaLogTest.cpp +# tests/DwarfCfaTest.cpp +# tests/DwarfDebugFrameTest.cpp +# tests/DwarfEhFrameTest.cpp +# tests/DwarfEhFrameWithHdrTest.cpp +# tests/DwarfMemoryTest.cpp +# tests/DwarfOpLogTest.cpp +# tests/DwarfOpTest.cpp +# tests/DwarfSectionImplTest.cpp +# tests/DwarfSectionTest.cpp +# tests/ElfFake.cpp +# tests/ElfInterfaceArmTest.cpp +# tests/ElfInterfaceTest.cpp +# tests/ElfTest.cpp +# tests/ElfTestUtils.cpp + +# # Not a test file +# # tests/GenGnuDebugdata.cpp + +# tests/GlobalDebugImplTest.cpp +# tests/GlobalTest.cpp +# tests/IsolatedSettings.cpp +# tests/JitDebugTest.cpp +# tests/LocalUpdatableMapsTest.cpp +# tests/LogFake.cpp +# tests/MapInfoCreateMemoryTest.cpp +# tests/MapInfoGetBuildIDTest.cpp +# tests/MapInfoGetLoadBiasTest.cpp +# tests/MapInfoGetObjectTest.cpp +# tests/MapInfoTest.cpp +# tests/MapsTest.cpp +# tests/MemoryBufferTest.cpp +# tests/MemoryCacheTest.cpp +# tests/MemoryFileTest.cpp +# tests/MemoryLocalTest.cpp + +# # This is Android-specific +# # tests/MemoryMteTest.cpp + +# tests/MemoryOfflineBufferTest.cpp +# tests/MemoryOfflineTest.cpp +# tests/MemoryRangeTest.cpp +# tests/MemoryRangesTest.cpp +# tests/MemoryRemoteTest.cpp +# tests/MemoryTest.cpp +# tests/MemoryThreadCacheTest.cpp +# tests/MemoryXzTest.cpp +# tests/ObjectBuildIdTest.cpp +# tests/ObjectCacheTest.cpp +# tests/PeCoffInterfaceTest.cpp +# tests/PeCoffFake.cpp +# tests/PeCoffRuntimeFunctionsTest.cpp +# tests/PeCoffTest.cpp +# tests/PeCoffUnwindInfoEvaluatorTest.cpp +# tests/PeCoffUnwindInfosTest.cpp +# tests/PeCoffUnwindInfoUnwinderX86_64Test.cpp +# tests/PeCoffEpilogTest.cpp +# tests/RegsInfoTest.cpp +# tests/RegsIterateTest.cpp +# tests/RegsStepIfSignalHandlerTest.cpp +# tests/RegsRemoteTest.cpp +# tests/RegsTest.cpp +# tests/SymbolsTest.cpp + +# # TestLocal is not a test. It belongs to a testing library. +# # tests/TestLocal.cpp + +# tests/TestUtils.cpp +# tests/UnwindOfflineTest.cpp +# tests/UnwindTest.cpp +# tests/UnwinderTest.cpp +# tests/VerifyBionicTerminationTest.cpp +# utils/MemoryFake.cpp +# utils/OfflineUnwindUtils.cpp +# utils/PidUtils.cpp +# utils/ProcessTracer.cpp) + +# # Some tests call dlopen/dlsym/dlclose which requires the +# # dynamic linker. +# target_link_libraries(libunwindstack_tests PRIVATE ${CMAKE_DL_LIBS}) + +# target_link_libraries(libunwindstack_tests PRIVATE +# liblog_shared +# libbase +# libprocinfo +# capstone::capstone +# LZMA::LZMA +# GTest::Main +# ZLIB::ZLIB) + +# target_include_directories(libunwindstack_tests PRIVATE +# ${CMAKE_CURRENT_SOURCE_DIR} +# utils/ +# include/) + +# if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") +# target_compile_options(libunwindstack_tests PRIVATE +# -Wno-error=format-nonliteral +# -Wno-error=inconsistent-missing-override +# -Wno-error=unused-result +# -Wno-error=defaulted-function-deleted) +# else() +# target_compile_options(libunwindstack_tests PRIVATE +# -Wno-error=format-nonliteral +# -Wno-error=unused-result) +# endif() + +# # Some tests unwind its own callstacks so we have to disable inlining to make +# # sure all the expected functions show up in the examined callstacks. +# # They also invoke a lot of different kinds of UB which would all be optimized away, +# # if we didn't disable optimization. +# target_compile_options(libunwindstack_tests PRIVATE -O0) + +# register_test(libunwindstack_tests) + +# # The tests expect the testdata next to the test binary. +# add_custom_command(TARGET libunwindstack_tests POST_BUILD +# COMMAND ${CMAKE_COMMAND} -E remove_directory +# $/offline_files +# COMMAND ${CMAKE_COMMAND} -E copy_directory +# ${CMAKE_CURRENT_LIST_DIR}/offline_files +# $/offline_files) + +# add_custom_command(TARGET libunwindstack_tests POST_BUILD +# COMMAND ${CMAKE_COMMAND} -E remove_directory +# $/tests +# COMMAND ${CMAKE_COMMAND} -E copy_directory +# ${CMAKE_CURRENT_LIST_DIR}/tests +# $/tests) + + + +# # This is a testing library that will be consumed by the tests. +# add_library(libunwindstack_local SHARED +# $ +# LogStdout.cpp +# tests/TestLocal.cpp) + +# target_link_libraries(libunwindstack_local PRIVATE +# libbase + +# # liblog_static needs to be listed AFTER libbase due to cyclic depdendencies. +# liblog_static +# LZMA::LZMA) + +# target_include_directories(libunwindstack_local PRIVATE include/) + +# # Clang >= 8.0 +# if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND +# CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 8) +# target_compile_options(libunwindstack_local PRIVATE +# -Wno-error=ctad-maybe-unsupported) +# endif() + +# target_compile_options(libunwindstack_local PRIVATE +# -Wno-error=format-nonliteral) + +# # This library unwinds its own callstacks so we have to disable inlining +# # to make sure all the expected functions show up in the examined callstacks. +# # It also invokes a lot of different kinds of UB which would all be optimized away, +# # if we didn't disable optimization. +# target_compile_options(libunwindstack_local PRIVATE -O0) + +# set_target_properties(libunwindstack_local PROPERTIES OUTPUT_NAME "unwindstack_local") + +# # The tests expect this library next to the executable. +# set_target_properties(libunwindstack_local PROPERTIES +# LIBRARY_OUTPUT_DIRECTORY $) +# set_target_properties(libunwindstack_local PROPERTIES +# LIBRARY_OUTPUT_DIRECTORY_RELEASE $) +# set_target_properties(libunwindstack_local PROPERTIES +# LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO $) +# set_target_properties(libunwindstack_local PROPERTIES +# LIBRARY_OUTPUT_DIRECTORY_DEBUG $)