platform_bionic/tests/elftls_test.cpp
Ryan Prichard 439639268d Fix StaticTlsLayout for atypical alignment values
arm32/arm64: Previously, the loader miscalculated a negative value for
offset_bionic_tcb_ when the executable's alignment was greater than
(8 * sizeof(void*)). The process then tended to crash.

riscv: Previously, the loader didn't propagate the p_align field of the
PT_TLS segment into StaticTlsLayout::alignment_, so high alignment
values were ignored.

__bionic_check_tls_alignment: Stop capping alignment at page_size().
There is no need to cap it, and the uncapped value is necessary for
correctly positioning the TLS segment relative to the thread pointer
(TP) for ARM and x86. The uncapped value is now used for computing
static TLS layout, but only a page of alignment is actually provided:
 * static TLS: __allocate_thread_mapping uses mmap, which provides only
   a page's worth of alignment
 * dynamic TLS: BionicAllocator::memalign caps align to page_size()
 * There were no callers to StaticTlsLayout::alignment(), so remove it.

Allow PT_TLS.p_align to be 0: quietly convert it to 1.

For static TLS, ensure that the address of a TLS block is congruent to
p_vaddr, modulo p_align. That is, ensure this formula holds:

    (&tls_block % p_align) == (p_vaddr % p_align)

For dynamic TLS, a TLS block is still allocated congruent to 0 modulo
p_align. Fixing dynamic TLS congruence is mostly a separate problem
from fixing static TLS congruence, and requires changing the dynamic
TLS allocator and/or DTV structure, so it should be fixed in a
later follow-up commit.

Typically (p_vaddr % p_align) is zero, but it's currently possible to
get a non-zero value with LLD: when .tbss has greater than page
alignment, but .tdata does not, LLD can produce a TLS segment where
(p_vaddr % p_align) is non-zero. LLD calculates TP offsets assuming
the loader will align the segment using (p_vaddr % p_align).
Previously, Bionic and LLD disagreed on the offsets from the TP to
the executable's TLS variables.

Add unit tests for StaticTlsLayout in bionic-unit-tests-static.

See also:
 * https://github.com/llvm/llvm-project/issues/40872
 * https://sourceware.org/bugzilla/show_bug.cgi?id=24606
 * https://reviews.llvm.org/D61824
 * https://reviews.freebsd.org/D31538

Bug: http://b/133354825
Bug: http://b/328844725
Bug: http://b/328844839
Test: bionic-unit-tests bionic-unit-tests-static
Change-Id: I8850c32ff742a45d3450d8fc39075c10a1e11000
2024-03-20 17:01:35 -07:00

106 lines
3.9 KiB
C++

/*
* Copyright (C) 2019 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <gtest/gtest.h>
#include <thread>
#include "gtest_globals.h"
#include "utils.h"
// Specify the LE access model explicitly. This file is compiled into the
// bionic-unit-tests executable, but the compiler sees an -fpic object file
// output into a static library, so it defaults to dynamic TLS accesses.
// This variable will be zero-initialized (.tbss)
__attribute__((tls_model("local-exec"))) static __thread int tlsvar_le_zero;
// This variable will have an initializer (.tdata)
__attribute__((tls_model("local-exec"))) static __thread int tlsvar_le_init = 10;
// Access libtest_elftls_shared_var's TLS variable using an IE access.
__attribute__((tls_model("initial-exec"))) extern "C" __thread int elftls_shared_var;
TEST(elftls, basic_le) {
// Check the variables on the main thread.
ASSERT_EQ(11, ++tlsvar_le_init);
ASSERT_EQ(1, ++tlsvar_le_zero);
// Check variables on a new thread.
std::thread([] {
ASSERT_EQ(11, ++tlsvar_le_init);
ASSERT_EQ(1, ++tlsvar_le_zero);
}).join();
}
TEST(elftls, shared_ie) {
ASSERT_EQ(21, ++elftls_shared_var);
std::thread([] {
ASSERT_EQ(21, ++elftls_shared_var);
}).join();
}
extern "C" int bump_static_tls_var_1();
extern "C" int bump_static_tls_var_2();
TEST(elftls, tprel_addend) {
ASSERT_EQ(4, bump_static_tls_var_1());
ASSERT_EQ(8, bump_static_tls_var_2());
std::thread([] {
ASSERT_EQ(4, bump_static_tls_var_1());
ASSERT_EQ(8, bump_static_tls_var_2());
}).join();
}
// Because this C++ source file is built with -fpic, the compiler will access
// this variable using a GD model. Typically, the static linker will relax the
// GD to LE, but the arm32 linker doesn't do TLS relaxations, so we can test
// calling __tls_get_addr in a static executable. The static linker knows that
// the main executable's TlsIndex::module_id is 1 and writes that into the GOT.
__thread int tlsvar_general = 30;
TEST(elftls, general) {
ASSERT_EQ(31, ++tlsvar_general);
std::thread([] {
ASSERT_EQ(31, ++tlsvar_general);
}).join();
}
TEST(elftls, align_test) {
std::string helper = GetTestLibRoot() + "/elftls_align_test_helper";
ExecTestHelper eth;
eth.SetArgs({helper.c_str(), nullptr});
eth.Run([&]() { execve(helper.c_str(), eth.GetArgs(), eth.GetEnv()); }, 0, nullptr);
}
TEST(elftls, skew_align_test) {
std::string helper = GetTestLibRoot() + "/elftls_skew_align_test_helper";
ExecTestHelper eth;
eth.SetArgs({helper.c_str(), nullptr});
eth.Run([&]() { execve(helper.c_str(), eth.GetArgs(), eth.GetEnv()); }, 0, nullptr);
}