blob: c965d07504441d5834727e241539c410c019f26d [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#if defined(__linux__) && !defined(THREAD_SANITIZER) && !defined(USE_MUSL)
#include "common/phdr_cache.h"
#include <dlfcn.h>
#include <link.h>
#include <cstdlib>
#include <filesystem>
#include <string>
#include <string_view>
namespace {
constexpr std::string_view TEST_DSO_NAME = "libphdr_cache_test_dso";
int find_test_dso(dl_phdr_info* info, size_t /*size*/, void* data) {
auto* found = reinterpret_cast<bool*>(data);
if (info->dlpi_name != nullptr &&
std::string_view(info->dlpi_name).find(TEST_DSO_NAME) != std::string_view::npos) {
*found = true;
return 1;
}
return 0;
}
bool phdr_contains_test_dso() {
bool found = false;
dl_iterate_phdr(find_test_dso, &found);
return found;
}
bool unwind_phdr_cache_contains_test_dso(uintptr_t ip) {
bool found = false;
doris_unwind_iterate_phdr(find_test_dso, &found, ip);
return found;
}
std::string test_dso_path() {
const char* doris_home = std::getenv("DORIS_HOME");
std::filesystem::path path = doris_home == nullptr ? "." : doris_home;
path /= "libphdr_cache_test_dso.so";
return path.string();
}
} // namespace
// Covers the exact late-dlopen risk of PHDR caching. Normal callers of dl_iterate_phdr must keep
// seeing the live loader list, while the stack-trace signal handler can explicitly opt in to the
// cached snapshot to avoid re-entering glibc's loader lock from an interrupted thread.
TEST(PhdrCacheTest, DefaultLoaderViewIsLiveWhileScopedViewUsesSnapshot) {
updatePHDRCache();
ASSERT_TRUE(hasPHDRCache());
ASSERT_FALSE(phdr_contains_test_dso()) << "test DSO was loaded before the snapshot";
void* handle = dlopen(test_dso_path().c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_NE(nullptr, handle) << dlerror();
using MarkerFunction = int (*)();
auto* marker = reinterpret_cast<MarkerFunction>(dlsym(handle, "phdr_cache_test_dso_marker"));
ASSERT_NE(nullptr, marker) << dlerror();
// Keep the handle open. Several sanitizer configurations are known to be fragile around
// dlclose(), and this test only needs a late-loaded object in the process loader list.
(void)handle;
EXPECT_TRUE(phdr_contains_test_dso())
<< "default dl_iterate_phdr must use glibc's live loader list";
{
ScopedPHDRCacheRead cache_scope;
EXPECT_FALSE(phdr_contains_test_dso())
<< "scoped PHDR cache should read the pre-dlopen snapshot";
}
EXPECT_FALSE(unwind_phdr_cache_contains_test_dso(reinterpret_cast<uintptr_t>(marker)))
<< "libunwind PHDR hook should also read the pre-dlopen snapshot";
updatePHDRCache();
EXPECT_TRUE(unwind_phdr_cache_contains_test_dso(reinterpret_cast<uintptr_t>(marker)))
<< "libunwind PHDR hook should use the refreshed snapshot without scoped opt-in";
{
ScopedPHDRCacheRead cache_scope;
EXPECT_TRUE(phdr_contains_test_dso())
<< "refreshed scoped PHDR cache should include the late-loaded DSO";
}
}
#endif