mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 17:52:02 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49066ea329 | ||
|
|
25ceab07f7 | ||
|
|
6e97393740 | ||
|
|
ff3d54d26f | ||
|
|
25c9cf3976 | ||
|
|
5f2fb08cfd | ||
|
|
60bc56848f | ||
|
|
600ca381fd | ||
|
|
da3f5dd4ec | ||
|
|
b59911b0c1 | ||
|
|
8131528305 | ||
|
|
6a71e7dd4b | ||
|
|
a63b2f70b2 | ||
|
|
3f88964527 | ||
|
|
e18dc70af0 | ||
|
|
0c6daad215 | ||
|
|
8c35c80bb7 | ||
|
|
d42ba3bae4 | ||
|
|
8c337b25df | ||
|
|
8d1c08bd03 | ||
|
|
5599a6f24f | ||
|
|
ef9a610dd1 | ||
|
|
31c3b5397b | ||
|
|
e11d64b5c7 | ||
|
|
9d91a2bc23 | ||
|
|
8c41f21d3c | ||
|
|
5c7a75b3e4 | ||
|
|
0804fbd095 | ||
|
|
0f78fa9c43 | ||
|
|
d30e970eb8 | ||
|
|
edc2f852a1 | ||
|
|
348e44a9c4 | ||
|
|
42f663cac0 | ||
|
|
4dd5d2f8cb | ||
|
|
4cdfc7e72c | ||
|
|
9516da1751 | ||
|
|
f3de82b2a2 | ||
|
|
c4c506c1cb | ||
|
|
0c62a91ded | ||
|
|
2fd0a4c60a | ||
|
|
d3188e39df | ||
|
|
3229b5b7e3 | ||
|
|
d9ede96aca | ||
|
|
4b4219fc33 | ||
|
|
02ead29989 | ||
|
|
37919009ab | ||
|
|
782ff4f582 | ||
|
|
3c468c71a4 | ||
|
|
a31679cf04 | ||
|
|
9cd65ed152 | ||
|
|
79ad405319 | ||
|
|
b60a164fcc | ||
|
|
2d4ba3ac74 | ||
|
|
42eabf9f71 | ||
|
|
aa4b3f7d01 | ||
|
|
2d121b8d6d | ||
|
|
c86a59a511 | ||
|
|
f7dea3a21b | ||
|
|
651ae8a2a8 | ||
|
|
5f7aca6276 | ||
|
|
39cdf61695 | ||
|
|
5c4955763b | ||
|
|
dae070283b | ||
|
|
403655904b | ||
|
|
44942b638b | ||
|
|
ceffd4679b | ||
|
|
6fa742be67 | ||
|
|
484e357971 | ||
|
|
53a5a37370 | ||
|
|
41579888b6 | ||
|
|
1e2d84038e | ||
|
|
6c7700b051 | ||
|
|
34c53777fb | ||
|
|
7be1b66791 | ||
|
|
4bd80ec21c | ||
|
|
a9ab0ff452 | ||
|
|
206bf65c50 | ||
|
|
2b3e8eec05 | ||
|
|
9eac818254 | ||
|
|
6fe0cadfa1 | ||
|
|
1a2baee096 | ||
|
|
728b265cbd | ||
|
|
9fa61b9298 | ||
|
|
6ad942cb27 | ||
|
|
eed4496af2 | ||
|
|
17c292c2b4 | ||
|
|
ab1ab78f8a | ||
|
|
8506065fd2 | ||
|
|
a291d0d448 | ||
|
|
690fefb97d | ||
|
|
c7c1779ad7 | ||
|
|
ec095e7513 | ||
|
|
69a91e8aa0 | ||
|
|
af46000645 | ||
|
|
b1658c4702 | ||
|
|
9f85267b44 | ||
|
|
f8d20cd3ce | ||
|
|
a396fa400f | ||
|
|
d8a4f14fd0 | ||
|
|
c8b5fae2d0 | ||
|
|
0ce32d36d5 | ||
|
|
01cd5d1a32 | ||
|
|
39a25e2394 | ||
|
|
bfdd75ed38 | ||
|
|
5411f4216e | ||
|
|
3cf00e30d5 | ||
|
|
0f714710f0 | ||
|
|
83ec409426 | ||
|
|
e6ae83fbf9 | ||
|
|
00702773f4 | ||
|
|
1739cfbfb4 | ||
|
|
ff689a1253 | ||
|
|
ae427d133e | ||
|
|
d92af2456d | ||
|
|
b3e1092879 | ||
|
|
46ef71eb75 | ||
|
|
871ef8cf14 | ||
|
|
c1b1caa854 | ||
|
|
dcab76697e | ||
|
|
cf11efb147 | ||
|
|
a688b73f8e | ||
|
|
9bb50ae7a5 | ||
|
|
ead6235470 | ||
|
|
967006479f | ||
|
|
f843daee9b | ||
|
|
c7129916ee | ||
|
|
e234ce25b6 | ||
|
|
02e1b9de10 | ||
|
|
effde5ffa9 | ||
|
|
1d3706285e | ||
|
|
085a1148fd | ||
|
|
5cf3e53ab9 | ||
|
|
a5aa387cb4 | ||
|
|
f99cb33c2d | ||
|
|
e55703405b | ||
|
|
4c71174d5d | ||
|
|
2ea08ac4b3 | ||
|
|
82ace062db | ||
|
|
17f0d85efb | ||
|
|
a994d71fa1 | ||
|
|
0eea8f4015 | ||
|
|
2e3b56ee3f | ||
|
|
b8bcb8a2a3 | ||
|
|
ac3ee91cbd | ||
|
|
18df821625 |
No files matched your search
@@ -98,7 +98,7 @@ jobs:
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DMOLD_ENABLE_QEMU_TESTS=On -DMOLD_ENABLE_QEMU_TESTS_RV32=On -DMOLD_ENABLE_QEMU_TESTS_LOONGARCH=On ..
|
||||
cmake ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest . -j$(nproc)
|
||||
- name: archive test results
|
||||
|
||||
@@ -3,15 +3,7 @@
|
||||
*~
|
||||
**/.DS_Store
|
||||
.cache
|
||||
compile_commands.json
|
||||
/mold
|
||||
/test/tmp
|
||||
options.inc
|
||||
core
|
||||
ld
|
||||
ld64
|
||||
ld64.mold
|
||||
gentoo
|
||||
out
|
||||
/mold-*.tar.gz
|
||||
/build*
|
||||
+9
-8
@@ -44,7 +44,7 @@
|
||||
# features and behave exactly the same.
|
||||
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(mold VERSION 2.2.0)
|
||||
project(mold VERSION 2.3.3)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
include(GNUInstallDirs)
|
||||
@@ -64,9 +64,12 @@ if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
|
||||
-fno-exceptions
|
||||
-fno-unwind-tables
|
||||
-fno-asynchronous-unwind-tables
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wno-sign-compare
|
||||
-Wno-unused-function
|
||||
-Wunused-variable
|
||||
-Wno-unused-parameter
|
||||
-Wno-missing-field-initializers
|
||||
-ggnu-pubnames)
|
||||
endif()
|
||||
|
||||
@@ -157,12 +160,10 @@ endif()
|
||||
#
|
||||
# We enable mimalloc by default for 64-bit targets. It doesn't seem to
|
||||
# be stable on 32-bit targets.
|
||||
include(CheckCXXSourceCompiles)
|
||||
check_cxx_source_compiles(
|
||||
"static_assert(sizeof(void *) == 4); int main() {}" P32)
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_MIMALLOC "Use mimalloc" ON
|
||||
"CMAKE_SIZEOF_VOID_P EQUAL 8; NOT APPLE; NOT ANDROID; NOT OPENBSD" OFF)
|
||||
|
||||
cmake_dependent_option(MOLD_USE_MIMALLOC "Use mimalloc" ON
|
||||
"NOT APPLE; NOT ANDROID; NOT OPENBSD; NOT P32" OFF)
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
|
||||
MOLD_USE_MIMALLOC OFF)
|
||||
@@ -301,8 +302,8 @@ list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
elf/arch-loongarch.cc
|
||||
elf/arch-riscv.cc
|
||||
elf/cmdline.cc
|
||||
elf/dwarf.cc
|
||||
elf/gc-sections.cc
|
||||
elf/gdb-index.cc
|
||||
elf/icf.cc
|
||||
elf/input-files.cc
|
||||
elf/input-sections.cc
|
||||
|
||||
@@ -71,7 +71,7 @@ necessary packages. You may need to run it as root.
|
||||
git clone https://github.com/rui314/mold.git
|
||||
mkdir mold/build
|
||||
cd mold/build
|
||||
git checkout v2.2.0
|
||||
git checkout v2.3.3
|
||||
../install-build-deps.sh
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ ..
|
||||
cmake --build . -j $(nproc)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# This dockerfile creates a reproducible build environment for mold.
|
||||
#
|
||||
# $ docker buildx create --use
|
||||
# $ docker buildx build --platform x86_64,aarch64,arm,ppc64le,s390x -t rui314/mold-builder:latest --push .
|
||||
|
||||
FROM ubuntu:18.04
|
||||
ENV TZ=Europe/London
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends software-properties-common && \
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential wget libstdc++-11-dev zlib1g-dev gcc-10 g++-10 python3 && \
|
||||
\
|
||||
mkdir /cmake && cd /cmake && \
|
||||
wget -O- -q https://github.com/Kitware/CMake/releases/download/v3.24.2/cmake-3.24.2.tar.gz | tar --strip-components=1 -xzf - && \
|
||||
./bootstrap --parallel=$(nproc) && make -j$(nproc) && make -j$(nproc) install && \
|
||||
rm -rf /var/lib/apt/lists/* /cmake
|
||||
+57
-46
@@ -6,58 +6,69 @@ namespace mold {
|
||||
|
||||
template <typename Context>
|
||||
std::vector<std::string_view>
|
||||
read_response_file(Context &ctx, std::string_view path) {
|
||||
read_response_file(Context &ctx, std::string_view path, i64 depth) {
|
||||
if (depth > 10)
|
||||
Fatal(ctx) << path << ": response file nesting too deep";
|
||||
|
||||
std::vector<std::string_view> vec;
|
||||
MappedFile<Context> *mf = MappedFile<Context>::must_open(ctx, std::string(path));
|
||||
u8 *data = mf->data;
|
||||
std::string_view data((char *)mf->data, mf->size);
|
||||
|
||||
auto read_quoted = [&](i64 i, char quote) {
|
||||
std::string buf;
|
||||
while (i < mf->size && data[i] != quote) {
|
||||
if (data[i] == '\\') {
|
||||
buf.append(1, data[i + 1]);
|
||||
i += 2;
|
||||
} else {
|
||||
buf.append(1, data[i++]);
|
||||
}
|
||||
}
|
||||
if (i >= mf->size)
|
||||
Fatal(ctx) << path << ": premature end of input";
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i + 1;
|
||||
};
|
||||
|
||||
auto read_unquoted = [&](i64 i) {
|
||||
std::string buf;
|
||||
|
||||
while (i < mf->size) {
|
||||
if (data[i] == '\\' && i + 1 < mf->size) {
|
||||
buf.append(1, data[i + 1]);
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isspace(data[i])) {
|
||||
buf.append(1, data[i++]);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
while (!data.empty()) {
|
||||
if (isspace(data[0])) {
|
||||
data = data.substr(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i;
|
||||
};
|
||||
auto read_quoted = [&]() {
|
||||
char quote = data[0];
|
||||
data = data.substr(1);
|
||||
|
||||
for (i64 i = 0; i < mf->size;) {
|
||||
if (isspace(data[i]))
|
||||
i++;
|
||||
else if (data[i] == '\'')
|
||||
i = read_quoted(i + 1, '\'');
|
||||
else if (data[i] == '\"')
|
||||
i = read_quoted(i + 1, '\"');
|
||||
std::string buf;
|
||||
while (!data.empty() && data[0] != quote) {
|
||||
if (data[0] == '\\' && data.size() >= 1) {
|
||||
buf.append(1, data[1]);
|
||||
data = data.substr(2);
|
||||
} else {
|
||||
buf.append(1, data[0]);
|
||||
data = data.substr(1);
|
||||
}
|
||||
}
|
||||
if (data.empty())
|
||||
Fatal(ctx) << path << ": premature end of input";
|
||||
data = data.substr(1);
|
||||
return save_string(ctx, buf);
|
||||
};
|
||||
|
||||
auto read_unquoted = [&] {
|
||||
std::string buf;
|
||||
while (!data.empty()) {
|
||||
if (data[0] == '\\' && data.size() >= 1) {
|
||||
buf.append(1, data[1]);
|
||||
data = data.substr(2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isspace(data[0])) {
|
||||
buf.append(1, data[0]);
|
||||
data = data.substr(1);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return save_string(ctx, buf);
|
||||
};
|
||||
|
||||
std::string_view tok;
|
||||
if (data[0] == '\'' || data[0] == '\"')
|
||||
tok = read_quoted();
|
||||
else
|
||||
i = read_unquoted(i);
|
||||
tok = read_unquoted();
|
||||
|
||||
if (tok.starts_with('@'))
|
||||
append(vec, read_response_file(ctx, tok.substr(1), depth + 1));
|
||||
else
|
||||
vec.push_back(tok);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
@@ -68,7 +79,7 @@ std::vector<std::string_view> expand_response_files(Context &ctx, char **argv) {
|
||||
std::vector<std::string_view> vec;
|
||||
for (i64 i = 0; argv[i]; i++) {
|
||||
if (argv[i][0] == '@')
|
||||
append(vec, read_response_file(ctx, argv[i] + 1));
|
||||
append(vec, read_response_file(ctx, argv[i] + 1, 1));
|
||||
else
|
||||
vec.push_back(argv[i]);
|
||||
}
|
||||
|
||||
+183
-91
@@ -23,6 +23,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/enumerable_thread_specific.h>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -225,8 +226,14 @@ struct Atomic : std::atomic<T> {
|
||||
void operator=(T val) { store(val); }
|
||||
operator T() const { return load(); }
|
||||
|
||||
void store(T val) { std::atomic<T>::store(val, relaxed); }
|
||||
T load() const { return std::atomic<T>::load(relaxed); }
|
||||
void store(T val, std::memory_order order = relaxed) {
|
||||
std::atomic<T>::store(val, order);
|
||||
}
|
||||
|
||||
T load(std::memory_order order = relaxed) const {
|
||||
return std::atomic<T>::load(order);
|
||||
}
|
||||
|
||||
T exchange(T val) { return std::atomic<T>::exchange(val, relaxed); }
|
||||
T operator|=(T val) { return std::atomic<T>::fetch_or(val, relaxed); }
|
||||
T operator++() { return std::atomic<T>::fetch_add(1, relaxed) + 1; }
|
||||
@@ -243,6 +250,85 @@ struct Atomic : std::atomic<T> {
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// perf.cc
|
||||
//
|
||||
|
||||
// Counter is used to collect statistics numbers.
|
||||
class Counter {
|
||||
public:
|
||||
Counter(std::string_view name, i64 value = 0) : name(name), values(value) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
instances.push_back(this);
|
||||
}
|
||||
|
||||
Counter &operator++(int) {
|
||||
if (enabled) [[unlikely]]
|
||||
values.local()++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Counter &operator+=(int delta) {
|
||||
if (enabled) [[unlikely]]
|
||||
values.local() += delta;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static void print();
|
||||
|
||||
static inline bool enabled = false;
|
||||
|
||||
private:
|
||||
i64 get_value();
|
||||
|
||||
std::string_view name;
|
||||
tbb::enumerable_thread_specific<i64> values;
|
||||
|
||||
static inline std::vector<Counter *> instances;
|
||||
};
|
||||
|
||||
// Timer and TimeRecord records elapsed time (wall clock time)
|
||||
// used by each pass of the linker.
|
||||
struct TimerRecord {
|
||||
TimerRecord(std::string name, TimerRecord *parent = nullptr);
|
||||
void stop();
|
||||
|
||||
std::string name;
|
||||
TimerRecord *parent;
|
||||
tbb::concurrent_vector<TimerRecord *> children;
|
||||
i64 start;
|
||||
i64 end;
|
||||
i64 user;
|
||||
i64 sys;
|
||||
bool stopped = false;
|
||||
};
|
||||
|
||||
void
|
||||
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
|
||||
|
||||
template <typename Context>
|
||||
class Timer {
|
||||
public:
|
||||
Timer(Context &ctx, std::string name, Timer *parent = nullptr) {
|
||||
record = new TimerRecord(name, parent ? parent->record : nullptr);
|
||||
ctx.timer_records.push_back(std::unique_ptr<TimerRecord>(record));
|
||||
}
|
||||
|
||||
Timer(const Timer &) = delete;
|
||||
|
||||
~Timer() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
private:
|
||||
TimerRecord *record;
|
||||
};
|
||||
|
||||
//
|
||||
// Bit vector
|
||||
//
|
||||
@@ -486,7 +572,7 @@ public:
|
||||
// to make this object to be aligned to a reasonably large
|
||||
// power-of-two address.
|
||||
struct alignas(32) Entry {
|
||||
std::atomic<const char *> key;
|
||||
Atomic<const char *> key;
|
||||
T value;
|
||||
u32 keylen;
|
||||
};
|
||||
@@ -554,15 +640,61 @@ public:
|
||||
return {nullptr, false};
|
||||
}
|
||||
|
||||
const char *get_key(i64 idx) {
|
||||
return entries[idx].key.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
i64 get_idx(T *value) const {
|
||||
uintptr_t addr = (uintptr_t)value - (uintptr_t)value % sizeof(Entry);
|
||||
return (Entry *)addr - entries;
|
||||
}
|
||||
|
||||
// Return a list of map entries sorted in a deterministic order.
|
||||
std::vector<Entry *> get_sorted_entries(i64 shard_idx) {
|
||||
if (nbuckets == 0)
|
||||
return {};
|
||||
|
||||
i64 shard_size = nbuckets / NUM_SHARDS;
|
||||
i64 begin = shard_idx * shard_size;
|
||||
i64 end = begin + shard_size;
|
||||
|
||||
i64 sz = 0;
|
||||
for (i64 i = begin; i < end; i++)
|
||||
if (entries[i].key)
|
||||
sz++;
|
||||
|
||||
std::vector<Entry *> vec;
|
||||
vec.reserve(sz);
|
||||
|
||||
// Since the shard is circular, we need to handle the last entries
|
||||
// as if they were next to the first entries.
|
||||
while (entries[end - 1].key)
|
||||
vec.push_back(entries + --end);
|
||||
|
||||
// Find entries contiguous in the buckets and sort them.
|
||||
i64 last = 0;
|
||||
for (i64 i = begin; i < end;) {
|
||||
while (i < end && entries[i].key)
|
||||
vec.push_back(entries + i++);
|
||||
|
||||
std::sort(vec.begin() + last, vec.end(), [](Entry *a, Entry *b) {
|
||||
if (a->keylen != b->keylen)
|
||||
return a->keylen < b->keylen;
|
||||
return memcmp(a->key, b->key, a->keylen) < 0;
|
||||
});
|
||||
|
||||
last = vec.size();
|
||||
|
||||
while (i < end && !entries[i].key)
|
||||
i++;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<Entry *> get_sorted_entries_all() {
|
||||
std::vector<std::vector<Entry *>> vec(NUM_SHARDS);
|
||||
tbb::parallel_for((i64)0, NUM_SHARDS, [&](i64 i) {
|
||||
vec[i] = get_sorted_entries(i);
|
||||
});
|
||||
return flatten(vec);
|
||||
}
|
||||
|
||||
static constexpr i64 MIN_NBUCKETS = 2048;
|
||||
static constexpr i64 NUM_SHARDS = 16;
|
||||
static constexpr i64 MAX_RETRY = 128;
|
||||
@@ -594,9 +726,11 @@ public:
|
||||
virtual ~OutputFile() = default;
|
||||
|
||||
u8 *buf = nullptr;
|
||||
std::vector<u8> buf2;
|
||||
std::string path;
|
||||
i64 filesize;
|
||||
bool is_mmapped;
|
||||
i64 fd = -1;
|
||||
i64 filesize = 0;
|
||||
bool is_mmapped = false;
|
||||
bool is_unmapped = false;
|
||||
|
||||
protected:
|
||||
@@ -604,6 +738,43 @@ protected:
|
||||
: path(path), filesize(filesize), is_mmapped(is_mmapped) {}
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
class MallocOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)malloc(filesize);
|
||||
if (!this->buf)
|
||||
Fatal(ctx) << "malloc failed";
|
||||
}
|
||||
|
||||
~MallocOutputFile() {
|
||||
free(this->buf);
|
||||
}
|
||||
|
||||
void close(Context &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
FILE *fp;
|
||||
|
||||
if (this->path == "-") {
|
||||
fp = stdout;
|
||||
} else {
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
fp = fdopen(fd, "w");
|
||||
}
|
||||
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
if (!this->buf2.empty())
|
||||
fwrite(this->buf2.data(), this->buf2.size(), 1, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
//
|
||||
// hyperloglog.cc
|
||||
//
|
||||
@@ -739,85 +910,6 @@ private:
|
||||
std::vector<std::vector<u8>> shards;
|
||||
};
|
||||
|
||||
//
|
||||
// perf.cc
|
||||
//
|
||||
|
||||
// Counter is used to collect statistics numbers.
|
||||
class Counter {
|
||||
public:
|
||||
Counter(std::string_view name, i64 value = 0) : name(name), values(value) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
instances.push_back(this);
|
||||
}
|
||||
|
||||
Counter &operator++(int) {
|
||||
if (enabled) [[unlikely]]
|
||||
values.local()++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Counter &operator+=(int delta) {
|
||||
if (enabled) [[unlikely]]
|
||||
values.local() += delta;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static void print();
|
||||
|
||||
static inline bool enabled = false;
|
||||
|
||||
private:
|
||||
i64 get_value();
|
||||
|
||||
std::string_view name;
|
||||
tbb::enumerable_thread_specific<i64> values;
|
||||
|
||||
static inline std::vector<Counter *> instances;
|
||||
};
|
||||
|
||||
// Timer and TimeRecord records elapsed time (wall clock time)
|
||||
// used by each pass of the linker.
|
||||
struct TimerRecord {
|
||||
TimerRecord(std::string name, TimerRecord *parent = nullptr);
|
||||
void stop();
|
||||
|
||||
std::string name;
|
||||
TimerRecord *parent;
|
||||
tbb::concurrent_vector<TimerRecord *> children;
|
||||
i64 start;
|
||||
i64 end;
|
||||
i64 user;
|
||||
i64 sys;
|
||||
bool stopped = false;
|
||||
};
|
||||
|
||||
void
|
||||
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
|
||||
|
||||
template <typename Context>
|
||||
class Timer {
|
||||
public:
|
||||
Timer(Context &ctx, std::string name, Timer *parent = nullptr) {
|
||||
record = new TimerRecord(name, parent ? parent->record : nullptr);
|
||||
ctx.timer_records.push_back(std::unique_ptr<TimerRecord>(record));
|
||||
}
|
||||
|
||||
Timer(const Timer &) = delete;
|
||||
|
||||
~Timer() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
private:
|
||||
TimerRecord *record;
|
||||
};
|
||||
|
||||
//
|
||||
// tar.cc
|
||||
//
|
||||
@@ -905,9 +997,9 @@ MappedFile<Context> *MappedFile<Context>::open(Context &ctx, std::string path) {
|
||||
|
||||
i64 fd;
|
||||
#ifdef _WIN32
|
||||
fd = ::_open(path.c_str(), O_RDONLY);
|
||||
fd = ::_open(path.c_str(), O_RDONLY);
|
||||
#else
|
||||
fd = ::open(path.c_str(), O_RDONLY);
|
||||
fd = ::open(path.c_str(), O_RDONLY);
|
||||
#endif
|
||||
|
||||
if (fd == -1) {
|
||||
@@ -943,7 +1035,7 @@ MappedFile<Context> *MappedFile<Context>::open(Context &ctx, std::string path) {
|
||||
if (mf->data == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return mf;
|
||||
|
||||
+12
-37
@@ -52,14 +52,13 @@ class MemoryMappedOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MemoryMappedOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, true) {
|
||||
i64 fd;
|
||||
std::tie(fd, output_tmpfile) = open_or_create_file(ctx, path, filesize, perm);
|
||||
std::tie(this->fd, output_tmpfile) =
|
||||
open_or_create_file(ctx, path, filesize, perm);
|
||||
|
||||
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, fd, 0);
|
||||
MAP_SHARED, this->fd, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
::close(fd);
|
||||
|
||||
mold::output_buffer_start = this->buf;
|
||||
mold::output_buffer_end = this->buf + filesize;
|
||||
@@ -76,6 +75,15 @@ public:
|
||||
if (!this->is_unmapped)
|
||||
munmap(this->buf, this->filesize);
|
||||
|
||||
if (this->buf2.empty()) {
|
||||
::close(this->fd);
|
||||
} else {
|
||||
FILE *out = fdopen(this->fd, "w");
|
||||
fseek(out, 0, SEEK_END);
|
||||
fwrite(&this->buf2[0], this->buf2.size(), 1, out);
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
// If an output file already exists, open a file and then remove it.
|
||||
// This is the fastest way to unlink a file, as it does not make the
|
||||
// system to immediately release disk blocks occupied by the file.
|
||||
@@ -92,39 +100,6 @@ private:
|
||||
int fd2 = -1;
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
class MallocOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)mmap(NULL, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << "mmap failed: " << errno_string();
|
||||
}
|
||||
|
||||
void close(Context &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (this->path == "-") {
|
||||
fwrite(this->buf, this->filesize, 1, stdout);
|
||||
fclose(stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
|
||||
FILE *fp = fdopen(fd, "w");
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
std::unique_ptr<OutputFile<Context>>
|
||||
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
|
||||
@@ -6,39 +6,6 @@
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename Context>
|
||||
class MallocOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)malloc(filesize);
|
||||
if (!this->buf)
|
||||
Fatal(ctx) << "malloc failed";
|
||||
}
|
||||
|
||||
void close(Context &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (this->path == "-") {
|
||||
fwrite(this->buf, this->filesize, 1, stdout);
|
||||
fclose(stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
|
||||
FILE *fp = fdopen(fd, "w");
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
fclose(fp);
|
||||
free(this->buf);
|
||||
}
|
||||
|
||||
private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
std::unique_ptr<OutputFile<Context>>
|
||||
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
|
||||
@@ -1,11 +1,43 @@
|
||||
#!/bin/bash
|
||||
# This script creates a mold binary distribution. The output is
|
||||
# written in this directory as `mold-$version-$arch-linux.tar.gz`
|
||||
# (e.g. `mold-1.0.3-x86_64-linux.tar.gz`).
|
||||
#
|
||||
# The mold executable created by this script is statically-linked to
|
||||
# libstdc++ and libcrypto but dynamically-linked to libc, libm, libz
|
||||
# and librt, as they almost always exist on any Linux systems.
|
||||
# This script creates a mold binary distribution. The output is written in
|
||||
# this directory as `mold-$version-$arch-linux.tar.gz` (e.g.,
|
||||
# `mold-1.0.3-x86_64-linux.tar.gz`).
|
||||
#
|
||||
# The mold executable created by this script is statically linked to
|
||||
# libstdc++ but dynamically linked to libc, libm, libz, and librt, as
|
||||
# these libraries almost always exist on any Linux system. We can't
|
||||
# statically link libc because doing so would disable dlopen(), which is
|
||||
# necessary to open the LTO linker plugin.
|
||||
#
|
||||
# This script aims to produce reproducible outputs. That means if you run
|
||||
# the script twice on the same git commit, it should produce bit-by-bit
|
||||
# identical binary files. This property is crucial as a countermeasure
|
||||
# against supply chain attacks. With this, you can verify that the binary
|
||||
# files distributed on the GitHub release pages are created from the
|
||||
# commit with release tags by rebuilding the binaries yourself.
|
||||
#
|
||||
# Debian provides snapshot.debian.org to host all historical binary
|
||||
# packages. We use it to construct Docker images pinned to a
|
||||
# particular timestamp.
|
||||
#
|
||||
# We aim to use a reasonably old Debian version because we'll dynamically
|
||||
# link glibc to mold, and a binary linked against a newer version of glibc
|
||||
# won't work on a system with an older version of glibc.
|
||||
#
|
||||
# We need GCC 10 or newer to build mold. If GCC 10 is not available on an
|
||||
# old Debian version, we'll build it ourselves.
|
||||
#
|
||||
# You may need to run the following command to use Docker with Qemu:
|
||||
#
|
||||
# $ docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
|
||||
set -e -x
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x ]"
|
||||
exit 1
|
||||
}
|
||||
|
||||
case $# in
|
||||
0)
|
||||
@@ -16,25 +48,93 @@ case $# in
|
||||
arch="$1"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | ppc64le | s390x ]"
|
||||
exit 1
|
||||
usage
|
||||
esac
|
||||
|
||||
echo "$arch" | grep -Eq '^(x86_64|aarch64|arm|ppc64le|s390x)$' || \
|
||||
{ echo "Error: no docker image for $arch"; exit 1; }
|
||||
echo "$arch" | grep -Eq '^(x86_64|aarch64|arm|riscv64|ppc64le|s390x)$' || usage
|
||||
|
||||
image=mold-builder-$arch
|
||||
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' $(dirname $0)/CMakeLists.txt)
|
||||
dest=mold-$version-$arch-linux
|
||||
set -e -x
|
||||
|
||||
docker run --platform linux/$arch -i --rm -v "$(pwd):/mold" \
|
||||
-e "OWNER=$(id -u):$(id -g)" rui314/mold-builder:latest \
|
||||
bash -c "mkdir /tmp/build &&
|
||||
cd /tmp/build &&
|
||||
cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DMOLD_MOSTLY_STATIC=On -DCMAKE_BUILD_TYPE=Release /mold &&
|
||||
cmake --build . -j\$(nproc) &&
|
||||
[ $arch = arm ] || ctest -j\$(nproc) &&
|
||||
# Create a Docker image.
|
||||
case $arch in
|
||||
x86_64)
|
||||
# Debian 8 (Jessie) released in April 2015
|
||||
cat <<EOF | docker build --platform $arch -t $image -
|
||||
FROM debian:jessie-20210326
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^deb/d' -e 's/^# //g' /etc/apt/sources.list && \
|
||||
echo 'Acquire { Retries "10"; http::timeout "10"; Check-Valid-Until "false"; };' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --force-yes --no-install-recommends wget bzip2 file make autoconf gcc g++ libssl-dev && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
|
||||
# Build CMake 3.27
|
||||
RUN mkdir -p /build/cmake && \
|
||||
cd /build/cmake && \
|
||||
wget -O- --no-check-certificate https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz | tar xzf - --strip-components=1 && \
|
||||
./bootstrap --parallel=$(nproc) && \
|
||||
make -j$(nproc) && \
|
||||
make install && \
|
||||
rm -rf /build
|
||||
|
||||
# Build GCC 10
|
||||
RUN mkdir -p /build/gcc && \
|
||||
cd /build/gcc && \
|
||||
wget -O- http://ftp.gnu.org/gnu/gcc/gcc-10.5.0/gcc-10.5.0.tar.gz | tar xzf - --strip-components=1 && \
|
||||
mkdir isl gmp mpc mpfr && \
|
||||
wget -O- http://gcc.gnu.org/pub/gcc/infrastructure/isl-0.18.tar.bz2 | tar xjf - --strip-components=1 -C isl && \
|
||||
wget -O- http://ftp.gnu.org/gnu/gmp/gmp-6.1.2.tar.bz2 | tar xjf - --strip-components=1 -C gmp && \
|
||||
wget -O- http://ftp.gnu.org/gnu/mpc/mpc-1.2.1.tar.gz | tar xzf - --strip-components=1 -C mpc && \
|
||||
wget -O- http://ftp.gnu.org/gnu/mpfr/mpfr-4.1.0.tar.gz | tar xzf - --strip-components=1 -C mpfr && \
|
||||
./configure --prefix=/usr --enable-languages=c,c++ --disable-bootstrap --disable-multilib && \
|
||||
make -j$(nproc) && \
|
||||
make install && \
|
||||
ln -sf /usr/lib64/libstdc++.so.6 /usr/lib/x86_64-linux-gnu/libstdc++.so.6 && \
|
||||
rm -rf /build
|
||||
EOF
|
||||
;;
|
||||
aarch64 | arm | ppc64le | s390x)
|
||||
# Debian 10 (Bullseye) released in July 2019
|
||||
cat <<EOF | docker build --platform $arch -t $image -
|
||||
FROM debian:bullseye-20231030
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^deb/d' -e 's/^# //g' /etc/apt/sources.list && \
|
||||
echo 'Acquire { Retries "10"; http::timeout "10"; Check-Valid-Until "false"; };' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 cmake && \
|
||||
ln -sf /usr/bin/gcc-10 /usr/bin/cc && \
|
||||
ln -sf /usr/bin/g++-10 /usr/bin/c++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
;;
|
||||
riscv64)
|
||||
# snapshot.debian.org is not available for RISC-V binaries
|
||||
cat <<EOF | docker build --platform $arch -t $image -
|
||||
FROM riscv64/debian:unstable-20231030
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc-12 g++-12 cmake && \
|
||||
ln -sf /usr/bin/gcc-12 /usr/bin/cc && \
|
||||
ln -sf /usr/bin/g++-12 /usr/bin/c++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
|
||||
# We use the timestamp of the last Git commit as the file timestamp
|
||||
# for build artifacts.
|
||||
timestamp="$(git log -1 --format=%ci)"
|
||||
|
||||
# Build mold in a container.
|
||||
docker run --platform linux/$arch -i --rm -v "$(realpath $(dirname $0)):/mold" $image \
|
||||
bash -c "mkdir -p /build/mold &&
|
||||
cd /build/mold &&
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=On /mold &&
|
||||
cmake --build . -j$(nproc) &&
|
||||
ctest -j$(nproc) &&
|
||||
cmake --install . --prefix $dest --strip &&
|
||||
tar czf /mold/$dest.tar.gz $dest &&
|
||||
cp mold mold-wrapper.so /mold &&
|
||||
chown \$OWNER /mold/mold /mold/mold-wrapper.so /mold/$dest.tar.gz"
|
||||
find $dest -print | xargs touch --no-dereference --date='$timestamp' &&
|
||||
find $dest -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /mold/$dest.tar.gz &&
|
||||
chown $(id -u):$(id -g) /mold/$dest.tar.gz"
|
||||
+15
-13
@@ -764,22 +764,24 @@ arguments.
|
||||
## ENVIRONMENT VARIABLES
|
||||
|
||||
* `MOLD_JOBS`:
|
||||
If this variable is set to `1`, only one process of `mold` runs actively. A
|
||||
mold process invoked while another active mold process is running will wait
|
||||
before doing anything until the active process exits.
|
||||
If this variable is set to `1`, only one `mold` process will run at a time.
|
||||
If a new mold process is initiated while another is already active, the new
|
||||
process will wait until the active one completes before starting.
|
||||
|
||||
The purpose of this environment variable is to reduce peak memory usage.
|
||||
Since mold is highly parallelized, there's no point in running it
|
||||
simultaneously. If you run N instances of mold in parallel, it would take N
|
||||
times more time and N times more memory. If you run them serially, it would
|
||||
still take N times more to finish, but their peak memory usage is reduced to
|
||||
normal.
|
||||
The primary reason for this environment variable is to minimize peak memory
|
||||
usage. Since mold is designed to operate with high parallelism, running
|
||||
multiple mold instances simultaneously may not be beneficial. If you execute
|
||||
N instances of mold concurrently, it could require N times the time and N
|
||||
times the memory. On the other hand, running them one after the other might
|
||||
still take N times longer, but the peak memory usage would be the same as
|
||||
running just a single instance.
|
||||
|
||||
If your build system tends to invoke multiple linker processes
|
||||
simultaneously, you may want to try to set this environment variable to
|
||||
`1` to see if it could improve overall performance.
|
||||
If your build system invokes multiple linker processes simultaneously and
|
||||
some of them often get killed due to out-of-memory errors, you might
|
||||
consider setting this environment variable to `1` to see if it addresses the
|
||||
OOM issue.
|
||||
|
||||
Currently, any value other than 1 is silently ignored.
|
||||
Currently, any value other than `1` is silently ignored.
|
||||
|
||||
* `MOLD_DEBUG`:
|
||||
If this variable is set to a non-empty string, `mold` embeds its
|
||||
|
||||
+1
-1
@@ -318,7 +318,7 @@ void AlphaGotSection::copy_buf(Context<E> &ctx) {
|
||||
|
||||
if (e.sym->is_imported) {
|
||||
*buf = ctx.arg.apply_dynamic_relocs ? e.addend : 0;
|
||||
*dynrel++ = ElfRel<E>(P, E::R_DYNAMIC, e.sym->get_dynsym_idx(ctx), e.addend);
|
||||
*dynrel++ = ElfRel<E>(P, E::R_ABS, e.sym->get_dynsym_idx(ctx), e.addend);
|
||||
} else {
|
||||
*buf = e.sym->get_addr(ctx) + e.addend;
|
||||
if (ctx.arg.pic && !e.sym->is_absolute())
|
||||
|
||||
+4
-4
@@ -200,9 +200,9 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
0xe08f'e00e, // 1: add lr, pc, lr
|
||||
0xe5be'f008, // ldr pc, [lr, #8]!
|
||||
0x0000'0000, // 2: .word .got.plt - 1b - 8
|
||||
0xe320'f000, // nop
|
||||
0xe320'f000, // nop
|
||||
0xe320'f000, // nop
|
||||
0x0000'0000, // (padding)
|
||||
0x0000'0000, // (padding)
|
||||
0x0000'0000, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
@@ -225,7 +225,7 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(ul32 *)(buf + 12) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
*(ul32 *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
// ARM does not use .eh_frame for exception handling. Instead, it uses
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
0xd503'201f, // nop
|
||||
};
|
||||
|
||||
u64 got = sym.get_got_addr(ctx);
|
||||
u64 got = sym.get_got_pltgot_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
|
||||
+2
-2
@@ -164,7 +164,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
*(ul32 *)(buf + 6) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
@@ -172,7 +172,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx);
|
||||
*(ul32 *)(buf + 6) = sym.get_got_pltgot_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-24
@@ -65,14 +65,12 @@ static u64 hi64(u64 val, u64 pc) {
|
||||
// LU52I.D simply set bits to [51:31] and to [63:53], respectively.
|
||||
//
|
||||
// Compensating all the sign-extensions is a bit complicated.
|
||||
bool x = val & 0x800;
|
||||
bool y = (page(val + 0x800) - page(pc)) & 0x8000'0000;
|
||||
|
||||
if (x && !y)
|
||||
return val - 0x1'0000'0000;
|
||||
if (!x && y)
|
||||
return val + 0x1'0000'0000;
|
||||
return val;
|
||||
u64 x = page(val) - page(pc);
|
||||
if (val & 0x800)
|
||||
x += 0x1000 - 0x1'0000'0000;
|
||||
if (x & 0x8000'0000)
|
||||
x += 0x1'0000'0000;
|
||||
return x;
|
||||
}
|
||||
|
||||
static u64 higher20(u64 val, u64 pc) {
|
||||
@@ -174,7 +172,7 @@ void write_plt_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u64 got = sym.get_got_addr(ctx);
|
||||
u64 got = sym.get_got_pltgot_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
|
||||
@@ -325,10 +323,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_k12(loc, (S + A) >> 52);
|
||||
break;
|
||||
case R_LARCH_PCALA_LO12:
|
||||
write_k12(loc, S + A);
|
||||
// It looks like R_LARCH_PCALA_LO12 is sometimes used for JIRL even
|
||||
// though the instruction takes a 16 bit immediate rather than 12 bits.
|
||||
// It is contrary to the psABI document, but GNU ld has special
|
||||
// code to handle it, so we accept it too.
|
||||
if ((*(ul32 *)loc & 0xfc00'0000) == 0x4c00'0000)
|
||||
write_k16(loc, sign_extend(S + A, 11) >> 2);
|
||||
else
|
||||
write_k12(loc, S + A);
|
||||
break;
|
||||
case R_LARCH_PCALA_HI20:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
write_j20(loc, hi20(S + A, P));
|
||||
break;
|
||||
case R_LARCH_PCALA64_LO20:
|
||||
@@ -341,7 +345,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_k12(loc, GOT + G + A);
|
||||
break;
|
||||
case R_LARCH_GOT_PC_HI20:
|
||||
check(GOT + G + A - P, -(1LL << 31), 1LL << 31);
|
||||
write_j20(loc, hi20(GOT + G + A, P));
|
||||
break;
|
||||
case R_LARCH_GOT64_PC_LO20:
|
||||
@@ -378,7 +381,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_k12(loc, sym.get_gottp_addr(ctx) + A);
|
||||
break;
|
||||
case R_LARCH_TLS_IE_PC_HI20:
|
||||
check(sym.get_gottp_addr(ctx) + A - P, -(1LL << 31), 1LL << 31);
|
||||
write_j20(loc, hi20(sym.get_gottp_addr(ctx) + A, P));
|
||||
break;
|
||||
case R_LARCH_TLS_IE64_PC_LO20:
|
||||
@@ -567,11 +569,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
@@ -587,6 +584,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_LARCH_B26:
|
||||
case R_LARCH_PCALA_HI20:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
@@ -620,7 +618,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_LARCH_ABS_LO12:
|
||||
case R_LARCH_ABS64_LO20:
|
||||
case R_LARCH_ABS64_HI12:
|
||||
case R_LARCH_PCALA_HI20:
|
||||
case R_LARCH_PCALA_LO12:
|
||||
case R_LARCH_PCALA64_LO20:
|
||||
case R_LARCH_PCALA64_HI12:
|
||||
@@ -658,10 +655,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static const ul32 insn[] = {
|
||||
0x1a00'000c, // pcalau12i $t0, 0
|
||||
0x02c0'018c, // addi.d $t0, $t0, 0
|
||||
0x1e00'000c, // pcaddu18i $t0, 0
|
||||
0x4c00'0180, // jirl $zero, $t0, 0
|
||||
0x0340'0000, // nop
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(insn));
|
||||
@@ -673,8 +668,8 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u64 S = sym->get_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_j20(buf, hi20(S, P));
|
||||
write_k12(buf + 4, S);
|
||||
write_j20(buf, (S - P + 0x20000) >> 18);
|
||||
write_k16(buf + 4, (S - P) >> 2);
|
||||
|
||||
buf += sizeof(insn);
|
||||
P += sizeof(insn);
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 4) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 2;
|
||||
*(ub32 *)(buf + 4) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 2;
|
||||
}
|
||||
|
||||
template <>
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
|
||||
ub32 *loc = (ub32 *)buf;
|
||||
i64 offset = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 8;
|
||||
i64 offset = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 8;
|
||||
loc[4] |= higha(offset);
|
||||
loc[5] |= lo(offset);
|
||||
}
|
||||
|
||||
+2
-4
@@ -465,7 +465,6 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + offset;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
if (sym->has_got(ctx)) {
|
||||
@@ -486,7 +485,6 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
buf += E::thunk_size;
|
||||
P += E::thunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,7 +519,7 @@ struct OpdSymbol {
|
||||
};
|
||||
|
||||
static Symbol<E> *
|
||||
get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
|
||||
get_opd_sym_at(std::span<OpdSymbol> syms, u64 offset) {
|
||||
auto it = std::lower_bound(syms.begin(), syms.end(), OpdSymbol{offset});
|
||||
if (it == syms.end())
|
||||
return nullptr;
|
||||
@@ -616,7 +614,7 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
|
||||
if (sym.get_input_section() != opd)
|
||||
continue;
|
||||
|
||||
Symbol<E> *real_sym = get_opd_sym_at(ctx, opd_syms, r.r_addend);
|
||||
Symbol<E> *real_sym = get_opd_sym_at(opd_syms, r.r_addend);
|
||||
if (!real_sym)
|
||||
Fatal(ctx) << *isec << ": cannot find a symbol in .opd for " << r
|
||||
<< " at offset 0x" << std::hex << (u64)r.r_addend;
|
||||
|
||||
+27
-17
@@ -92,8 +92,10 @@ static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
|
||||
static u64 prefix34(u64 x) {
|
||||
return bits(x, 33, 16) | (bits(x, 15, 0) << 32);
|
||||
static void write34(u8 *loc, u64 x) {
|
||||
ul32 *buf = (ul32 *)loc;
|
||||
buf[0] = (buf[0] & 0xfffc'0000) | bits(x, 33, 16);
|
||||
buf[1] = (buf[1] & 0xffff'0000) | bits(x, 15, 0);
|
||||
}
|
||||
|
||||
// .plt is used only for lazy symbol resolution on PPC64. All PLT
|
||||
@@ -205,7 +207,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
auto r2save_thunk_addr = [&] { return get_thunk_addr(i); };
|
||||
auto no_r2save_thunk_addr = [&] { return get_thunk_addr(i) + 4; };
|
||||
auto no_r2save_thunk_addr = [&] { return get_thunk_addr(i) + 8; };
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
@@ -279,10 +281,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_PLT_PCREL34:
|
||||
case R_PPC64_PLT_PCREL34_NOTOC:
|
||||
case R_PPC64_GOT_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(G + GOT - P);
|
||||
write34(loc, G + GOT - P);
|
||||
break;
|
||||
case R_PPC64_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(S + A - P);
|
||||
write34(loc, S + A - P);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
|
||||
@@ -291,7 +293,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(sym.get_gottp_addr(ctx) - P);
|
||||
write34(loc, sym.get_gottp_addr(ctx) - P);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
*(ul16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
@@ -300,7 +302,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)loc = lo(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(sym.get_tlsgd_addr(ctx) - P);
|
||||
write34(loc, sym.get_tlsgd_addr(ctx) - P);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
*(ul16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
@@ -309,7 +311,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)loc = lo(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD_PCREL34:
|
||||
*(ul64 *)loc |= prefix34(ctx.got->get_tlsld_addr(ctx) - P);
|
||||
write34(loc, ctx.got->get_tlsld_addr(ctx) - P);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.dtp_addr);
|
||||
@@ -318,7 +320,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)loc = lo(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL34:
|
||||
*(ul64 *)loc |= prefix34(S + A - ctx.dtp_addr);
|
||||
write34(loc, S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
@@ -326,6 +328,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_TPREL16_LO:
|
||||
*(ul16 *)loc = lo(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL34:
|
||||
write34(loc, S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTSEQ_NOTOC:
|
||||
case R_PPC64_PLTCALL:
|
||||
@@ -443,6 +448,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_TPREL34:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_REL32:
|
||||
@@ -483,6 +489,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// and jump there.
|
||||
static const ul32 plt_thunk[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x3d82'0000, // addis r12, r2, foo@gotplt@toc@ha
|
||||
0xe98c'0000, // ld r12, foo@gotplt@toc@lo(r12)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
@@ -491,6 +498,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
static const ul32 plt_thunk_power10[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x0410'0000, // pld r12, foo@gotplt@pcrel
|
||||
0xe580'0000,
|
||||
0x7d89'03a6, // mtctr r12
|
||||
@@ -501,6 +509,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// to its local entry point.
|
||||
static const ul32 local_thunk[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x3d82'0000, // addis r12, r2, foo@toc@ha
|
||||
0x398c'0000, // addi r12, r12, foo@toc@lo
|
||||
0x7d89'03a6, // mtctr r12
|
||||
@@ -509,6 +518,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
static const ul32 local_thunk_power10[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x0610'0000, // pla r12, foo@pcrel
|
||||
0x3980'0000,
|
||||
0x7d89'03a6, // mtctr r12
|
||||
@@ -522,6 +532,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + offset;
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
if (sym->has_plt(ctx)) {
|
||||
@@ -530,22 +541,21 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
if (ctx.extra.is_power10) {
|
||||
memcpy(buf, plt_thunk_power10, E::thunk_size);
|
||||
*(ul64 *)(buf + 1) |= prefix34(got - P - 4);
|
||||
write34(buf + 8, got - P - 8);
|
||||
} else {
|
||||
i64 val = got - ctx.extra.TOC->value;
|
||||
memcpy(buf, plt_thunk, E::thunk_size);
|
||||
*(ul32 *)(buf + 4) |= higha(val);
|
||||
*(ul32 *)(buf + 8) |= lo(val);
|
||||
*(ul32 *)(buf + 8) |= higha(got - TOC);
|
||||
*(ul32 *)(buf + 12) |= lo(got - TOC);
|
||||
}
|
||||
} else {
|
||||
u64 S = sym->get_addr(ctx);
|
||||
if (ctx.extra.is_power10) {
|
||||
memcpy(buf, local_thunk_power10, E::thunk_size);
|
||||
*(ul64 *)(buf + 1) |= prefix34(sym->get_addr(ctx) - P - 4);
|
||||
write34(buf + 8, S - P - 8);
|
||||
} else {
|
||||
i64 val = sym->get_addr(ctx) - ctx.extra.TOC->value;
|
||||
memcpy(buf, local_thunk, E::thunk_size);
|
||||
*(ul32 *)(buf + 4) |= higha(val);
|
||||
*(ul32 *)(buf + 8) |= lo(val);
|
||||
*(ul32 *)(buf + 8) |= higha(S - TOC);
|
||||
*(ul32 *)(buf + 12) |= lo(S - TOC);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
-5
@@ -198,7 +198,7 @@ void write_plt_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u64 got = sym.get_got_addr(ctx);
|
||||
u64 got = sym.get_got_pltgot_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
|
||||
@@ -259,6 +259,7 @@ static inline bool is_hi20(const ElfRel<E> &rel) {
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
u64 GP = ctx.__global_pointer ? ctx.__global_pointer->get_addr(ctx) : 0;
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
@@ -375,7 +376,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_utype(loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
case R_RISCV_PCREL_LO12_S: {
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
case R_RISCV_GPREL_LO12_I:
|
||||
case R_RISCV_GPREL_LO12_S: {
|
||||
i64 idx2 = find_paired_reloc();
|
||||
const ElfRel<E> &rel2 = rels[idx2];
|
||||
Symbol<E> &sym2 = *file.symbols[rel2.r_sym];
|
||||
@@ -399,11 +402,15 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_RISCV_PCREL_HI20:
|
||||
val = S + A - P;
|
||||
break;
|
||||
case R_RISCV_GPREL_HI20:
|
||||
val = S + A - GP;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
if (rel.r_type == R_RISCV_PCREL_LO12_I)
|
||||
if (rel.r_type == R_RISCV_PCREL_LO12_I ||
|
||||
rel.r_type == R_RISCV_GPREL_LO12_I)
|
||||
write_itype(loc, val);
|
||||
else
|
||||
write_stype(loc, val);
|
||||
@@ -560,6 +567,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
check(S + A - P, -(1 << 11), 1 << 11);
|
||||
write_cjtype(loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_GPREL_HI20:
|
||||
check(S + A - GP, -(1LL << 31), 1LL << 31);
|
||||
write_utype(loc, S + A - GP);
|
||||
break;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - S - A) & 0b0011'1111);
|
||||
break;
|
||||
@@ -761,6 +772,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_RISCV_TPREL_ADD:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_RISCV_GPREL_HI20:
|
||||
if (ctx.arg.shared)
|
||||
Fatal(ctx) << *this << ": R_RISCV_GPREL_HI20 may not be used with -shared";
|
||||
break;
|
||||
case R_RISCV_BRANCH:
|
||||
case R_RISCV_JAL:
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
@@ -781,6 +796,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_RISCV_ALIGN:
|
||||
case R_RISCV_RVC_BRANCH:
|
||||
case R_RISCV_RVC_JUMP:
|
||||
case R_RISCV_GPREL_LO12_I:
|
||||
case R_RISCV_GPREL_LO12_S:
|
||||
case R_RISCV_RELAX:
|
||||
case R_RISCV_SUB6:
|
||||
case R_RISCV_SET6:
|
||||
@@ -821,7 +838,7 @@ u64 get_eflags(Context<E> &ctx) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool is_resizable(Context<E> &ctx, InputSection<E> *isec) {
|
||||
static bool is_resizable(InputSection<E> *isec) {
|
||||
return isec && isec->is_alive && (isec->shdr().sh_flags & SHF_ALLOC) &&
|
||||
(isec->shdr().sh_flags & SHF_EXECINSTR);
|
||||
}
|
||||
@@ -1021,7 +1038,7 @@ i64 riscv_resize_sections<E>(Context<E> &ctx) {
|
||||
// This step should only shrink sections.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (is_resizable(ctx, isec.get()))
|
||||
if (is_resizable(isec.get()))
|
||||
shrink_section(ctx, *isec, use_rvc);
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 2) = (sym.get_got_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
|
||||
*(ub32 *)(buf + 2) = (sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
|
||||
}
|
||||
|
||||
template <>
|
||||
|
||||
+2
-2
@@ -169,7 +169,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
|
||||
static_assert(sizeof(insn) == E::pltgot_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 8) = sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
*(ul32 *)(buf + 8) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x01, 0xd0, // mov.l 1f, r0
|
||||
@@ -181,7 +181,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
|
||||
static_assert(sizeof(insn) == E::pltgot_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 8) = sym.get_got_addr(ctx);
|
||||
*(ul32 *)(buf + 8) = sym.get_got_pltgot_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -115,7 +115,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
};
|
||||
|
||||
memcpy(buf, entry, sizeof(entry));
|
||||
*(ub64 *)(buf + 24) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 4;
|
||||
*(ub64 *)(buf + 24) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 4;
|
||||
}
|
||||
|
||||
template <>
|
||||
|
||||
+32
-19
@@ -64,28 +64,41 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
};
|
||||
// Only a canonical PLT can be address-taken; there's no way to take
|
||||
// an address of a non-canonical PLT. Therefore, a non-canonical PLT
|
||||
// doesn't have to start with an endbr64.
|
||||
if (sym.is_canonical) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_plt_idx(ctx);
|
||||
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 16;
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_plt_idx(ctx);
|
||||
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 16;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 2) = sym.get_plt_idx(ctx);
|
||||
*(ul32 *)(buf + 8) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
0xcc, 0xcc, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 10;
|
||||
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 6;
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -345,7 +358,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr;
|
||||
u64 GOTPLT = ctx.gotplt->shdr.sh_addr;
|
||||
u64 GOT = ctx.gotplt->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
@@ -388,19 +401,19 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
case R_X86_64_GOTOFF64:
|
||||
case R_X86_64_PLTOFF64:
|
||||
*(ul64 *)loc = S + A - GOTPLT;
|
||||
*(ul64 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_X86_64_GOTPC32:
|
||||
write32s(GOTPLT + A - P);
|
||||
write32s(GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_GOTPC64:
|
||||
*(ul64 *)loc = GOTPLT + A - P;
|
||||
*(ul64 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_X86_64_GOTPCREL:
|
||||
write32s(G + GOTPLT + A - P);
|
||||
write32s(G + GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_GOTPCREL64:
|
||||
*(ul64 *)loc = G + GOTPLT + A - P;
|
||||
*(ul64 *)loc = G + GOT + A - P;
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX:
|
||||
// We always want to relax GOTPCRELX relocs even if --no-relax
|
||||
@@ -416,7 +429,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
write32s(G + GOTPLT + A - P);
|
||||
write32s(G + GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
@@ -430,7 +443,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
write32s(G + GOTPLT + A - P);
|
||||
write32s(G + GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_TLSGD:
|
||||
if (sym.has_tlsgd(ctx))
|
||||
|
||||
+29
-18
@@ -660,9 +660,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
else
|
||||
Fatal(ctx) << "unknown --unresolved-symbols argument: " << arg;
|
||||
} else if (read_arg("undefined") || read_arg("u")) {
|
||||
ctx.arg.undefined.push_back(arg);
|
||||
ctx.arg.undefined.push_back(get_symbol(ctx, arg));
|
||||
} else if (read_arg("require-defined")) {
|
||||
ctx.arg.require_defined.push_back(arg);
|
||||
ctx.arg.require_defined.push_back(get_symbol(ctx, arg));
|
||||
} else if (read_arg("init")) {
|
||||
ctx.arg.init = arg;
|
||||
} else if (read_arg("fini")) {
|
||||
@@ -883,6 +883,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.z_sectionheader = true;
|
||||
} else if (read_z_flag("nosectionheader")) {
|
||||
ctx.arg.z_sectionheader = false;
|
||||
} else if (read_z_flag("rewrite-endbr")) {
|
||||
ctx.arg.z_rewrite_endbr = true;
|
||||
} else if (read_flag("no-undefined")) {
|
||||
ctx.arg.z_defs = true;
|
||||
} else if (read_flag("fatal-warnings")) {
|
||||
@@ -1102,21 +1104,21 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_flag("no-keep-memory")) {
|
||||
} else if (read_arg("max-cache-size")) {
|
||||
} else if (read_arg("version-script")) {
|
||||
// --version-script, --dynamic-list and --export-dynamic-symbol[-list]
|
||||
// are treated as positional arguments even though they are actually not
|
||||
// positional. This is because linker scripts (a positional argument)
|
||||
// can also specify a version script, and it's better to consolidate
|
||||
// parsing in read_input_files. In particular, version scripts can
|
||||
// modify ctx.default_version which we initialize *after* parsing
|
||||
// non-positional args, so the parsing cannot be done right here.
|
||||
// --version-script is treated as positional arguments even though
|
||||
// they are actually not positional. This is because linker scripts
|
||||
// (a positional argument) can also specify a version script, and
|
||||
// it's better to consolidate parsing in read_input_files. In
|
||||
// particular, version scripts can modify ctx.default_version which
|
||||
// we initialize *after* parsing non-positional args, so the parsing
|
||||
// cannot be done right here.
|
||||
remaining.push_back("--version-script=" + std::string(arg));
|
||||
} else if (read_arg("dynamic-list")) {
|
||||
ctx.arg.Bsymbolic = true;
|
||||
remaining.push_back("--dynamic-list=" + std::string(arg));
|
||||
append(ctx.dynamic_list_patterns, parse_dynamic_list(ctx, arg));
|
||||
} else if (read_arg("export-dynamic-symbol")) {
|
||||
remaining.push_back("--export-dynamic-symbol=" + std::string(arg));
|
||||
ctx.dynamic_list_patterns.push_back({arg, "<command line>"});
|
||||
} else if (read_arg("export-dynamic-symbol-list")) {
|
||||
remaining.push_back("--export-dynamic-symbol-list=" + std::string(arg));
|
||||
append(ctx.dynamic_list_patterns, parse_dynamic_list(ctx, arg));
|
||||
} else if (read_flag("as-needed")) {
|
||||
remaining.push_back("--as-needed");
|
||||
} else if (read_flag("no-as-needed")) {
|
||||
@@ -1226,11 +1228,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (char *env = getenv("MOLD_REPRO"); env && env[0])
|
||||
ctx.arg.repro = true;
|
||||
|
||||
if (ctx.arg.shared || ctx.arg.export_dynamic)
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.default_version = VER_NDX_LOCAL;
|
||||
|
||||
if (ctx.arg.default_symver) {
|
||||
std::string ver = ctx.arg.soname.empty() ?
|
||||
filepath(ctx.arg.output).filename().string() : std::string(ctx.arg.soname);
|
||||
@@ -1241,7 +1238,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (ctx.arg.shared && warn_shared_textrel)
|
||||
ctx.arg.warn_textrel = true;
|
||||
|
||||
ctx.arg.undefined.push_back(ctx.arg.entry);
|
||||
ctx.arg.undefined.push_back(get_symbol(ctx, ctx.arg.entry));
|
||||
|
||||
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++) {
|
||||
std::variant<Symbol<E> *, u64> &val = ctx.arg.defsyms[i].second;
|
||||
if (Symbol<E> **sym = std::get_if<Symbol<E> *>(&val))
|
||||
ctx.arg.undefined.push_back(*sym);
|
||||
}
|
||||
|
||||
// --oformat=binary implies --strip-all because without a section
|
||||
// header, there's no way to identify the locations of a symbol
|
||||
@@ -1262,6 +1265,14 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (ctx.arg.shared)
|
||||
ctx.overwrite_output_file = false;
|
||||
|
||||
if (!ctx.arg.chroot.empty()) {
|
||||
if (!ctx.arg.Map.empty())
|
||||
ctx.arg.Map = ctx.arg.chroot + "/" + ctx.arg.Map;
|
||||
|
||||
if (!ctx.arg.dependency_file.empty())
|
||||
ctx.arg.dependency_file = ctx.arg.chroot + "/" + ctx.arg.dependency_file;
|
||||
}
|
||||
|
||||
if (version_shown && remaining.empty())
|
||||
exit(0);
|
||||
return remaining;
|
||||
|
||||
-554
@@ -1,554 +0,0 @@
|
||||
// This file contains code to read DWARF debug info to create .gdb_index.
|
||||
//
|
||||
// .gdb_index is an optional section to speed up GNU debugger. It contains
|
||||
// two maps: 1) a map from function/variable/type names to compunits, and
|
||||
// 2) a map from function address ranges to compunits. gdb uses these
|
||||
// maps to quickly find a compunit given a name or an instruction pointer.
|
||||
//
|
||||
// (Terminology: a compilation unit, which often abbreviated as compunit
|
||||
// or cu, is a unit of debug info. An input .debug_info section usually
|
||||
// contains one compunit, and thus an output .debug_info contains as
|
||||
// many compunits as the number of input files.)
|
||||
//
|
||||
// .gdb_index is not mandatory. All the information in .gdb_index is
|
||||
// also in other debug info sections. You can actually create an
|
||||
// executable without .gdb_index and later add it using `gdb-add-index`
|
||||
// post-processing tool that comes with gdb.
|
||||
//
|
||||
// The mapping from names to compunits is 1:n while the mapping from
|
||||
// address ranges to compunits is 1:1. That is, two object files may
|
||||
// define the same type name (with the same definition), while there
|
||||
// should be no two functions that overlap with each other in memory.
|
||||
//
|
||||
// .gdb_index contains an on-disk hash table for names, so gdb can
|
||||
// lookup names without loading all strings into memory and construct an
|
||||
// in-memory hash table.
|
||||
//
|
||||
// Names are in .debug_gnu_pubnames and .debug_gnu_pubtypes input
|
||||
// sections. These sections are created if `-ggnu-pubnames` is given.
|
||||
// Besides names, these sections contains attributes for each name so
|
||||
// that gdb can distinguish type names from function names, for example.
|
||||
//
|
||||
// A compunit contains one or more function address ranges. If an
|
||||
// object file is compiled without -ffunction-sections, it contains
|
||||
// only one .text section and therefore contains a single address range.
|
||||
// Such range is typically stored directly to the compunit.
|
||||
//
|
||||
// If an object file is compiled with -fucntion-sections, it contains
|
||||
// more than one .text section, and it has as many address ranges as
|
||||
// the number of .text sections. Such discontiguous address ranges are
|
||||
// stored to .debug_ranges in DWARF 2/3/4/5 and
|
||||
// .debug_rnglists/.debug_addr in DWARF 5.
|
||||
//
|
||||
// .debug_info section contains DWARF debug info. Although we don't need
|
||||
// to parse the whole .debug_info section to read address ranges, we
|
||||
// have to do a little bit. DWARF is complicated and often handled using
|
||||
// a library such as libdwarf. But we don't use any library because we
|
||||
// don't want to add an extra run-time dependency just for --gdb-index.
|
||||
//
|
||||
// This page explains the format of .gdb_index:
|
||||
// https://sourceware.org/gdb/onlinedocs/gdb/Index-Section-Format.html
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// The hash function for .gdb_index.
|
||||
static u32 gdb_hash(std::string_view name) {
|
||||
u32 h = 0;
|
||||
for (u8 c : name) {
|
||||
if ('A' <= c && c <= 'Z')
|
||||
c = 'a' + c - 'A';
|
||||
h = h * 67 + c - 113;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
// Split .debug_info into so-called "compilation units". A .debug_info
|
||||
// section usually contains one compunit unless it was created by `ld -r`.
|
||||
// This is for --gdb-index.
|
||||
template <typename E>
|
||||
std::vector<std::string_view>
|
||||
read_compunits(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
file.debug_info->uncompress(ctx);
|
||||
std::string_view data = file.debug_info->contents;
|
||||
std::vector<std::string_view> vec;
|
||||
|
||||
while (!data.empty()) {
|
||||
if (data.size() < 4)
|
||||
Fatal(ctx) << *file.debug_info << ": corrupted .debug_info";
|
||||
if (*(U32<E> *)data.data() == 0xffff'ffff)
|
||||
Fatal(ctx) << *file.debug_info << ": --gdb-index: DWARF64 not supported";
|
||||
i64 len = *(U32<E> *)data.data() + 4;
|
||||
vec.push_back(data.substr(0, len));
|
||||
data = data.substr(len);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Parses .debug_gnu_pubnames and .debug_gnu_pubtypes. These sections
|
||||
// start with a 14 bytes header followed by (4-byte offset, 1-byte type,
|
||||
// null-terminated string) tuples.
|
||||
//
|
||||
// The 4-byte offset is an offset into .debug_info that contains details
|
||||
// about the name. The 1-byte type is a type of the corresponding name
|
||||
// (e.g. function, variable or datatype). The string is a name of a
|
||||
// function, a variable or a type.
|
||||
template <typename E>
|
||||
std::vector<GdbIndexName> read_pubnames(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
std::vector<GdbIndexName> vec;
|
||||
|
||||
auto get_cu_idx = [&](InputSection<E> &isec, i64 offset) {
|
||||
i64 off = 0;
|
||||
for (i64 i = 0; i < file.compunits.size(); i++) {
|
||||
if (offset == off)
|
||||
return file.compunits_idx + i;
|
||||
off += file.compunits[i].size();
|
||||
}
|
||||
Fatal(ctx) << isec << ": corrupted debug_info_offset";
|
||||
};
|
||||
|
||||
auto read = [&](InputSection<E> &isec) {
|
||||
isec.uncompress(ctx);
|
||||
std::string_view contents = isec.contents;
|
||||
|
||||
while (!contents.empty()) {
|
||||
if (contents.size() < 14)
|
||||
Fatal(ctx) << isec << ": corrupted header";
|
||||
|
||||
u32 len = *(U32<E> *)contents.data() + 4;
|
||||
u32 debug_info_offset = *(U32<E> *)(contents.data() + 6);
|
||||
u32 cu_idx = get_cu_idx(isec, debug_info_offset);
|
||||
|
||||
std::string_view data = contents.substr(14, len - 14);
|
||||
contents = contents.substr(len);
|
||||
|
||||
while (!data.empty()) {
|
||||
u32 offset = *(U32<E> *)data.data();
|
||||
data = data.substr(4);
|
||||
if (offset == 0)
|
||||
break;
|
||||
|
||||
u8 type = data[0];
|
||||
data = data.substr(1);
|
||||
|
||||
std::string_view name = data.data();
|
||||
data = data.substr(name.size() + 1);
|
||||
|
||||
vec.push_back({name, gdb_hash(name), (type << 24) | cu_idx});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (file.debug_pubnames)
|
||||
read(*file.debug_pubnames);
|
||||
if (file.debug_pubtypes)
|
||||
read(*file.debug_pubtypes);
|
||||
|
||||
// Uniquify elements because GCC 11 seems to emit one record for each
|
||||
// comdat group which results in having a lot of duplicate records.
|
||||
auto less = [](const GdbIndexName &a, const GdbIndexName &b) {
|
||||
return std::tuple{a.hash, a.attr, a.name} <
|
||||
std::tuple{b.hash, b.attr, b.name};
|
||||
};
|
||||
|
||||
auto equal = [](const GdbIndexName &a, const GdbIndexName &b) {
|
||||
return std::tuple{a.hash, a.attr, a.name} ==
|
||||
std::tuple{b.hash, b.attr, b.name};
|
||||
};
|
||||
|
||||
std::sort(vec.begin(), vec.end(), less);
|
||||
vec.erase(std::unique(vec.begin(), vec.end(), equal), vec.end());
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static u8 *get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
if (u8 *buf = chunk->get_uncompressed_data())
|
||||
return buf;
|
||||
return ctx.buf + chunk->shdr.sh_offset;
|
||||
}
|
||||
|
||||
// Try to find a compilation unit from .debug_info and its
|
||||
// corresponding record from .debug_abbrev and returns them.
|
||||
template <typename E>
|
||||
static std::tuple<u8 *, u8 *, u32>
|
||||
find_compunit(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
// Read .debug_info to find the record at a given offset.
|
||||
u8 *cu = get_buffer(ctx, ctx.debug_info) + offset;
|
||||
u32 dwarf_version = *(U16<E> *)(cu + 4);
|
||||
u32 abbrev_offset;
|
||||
|
||||
// Skip a header.
|
||||
switch (dwarf_version) {
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
abbrev_offset = *(U32<E> *)(cu + 6);
|
||||
if (u32 address_size = cu[10]; address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
|
||||
<< address_size;
|
||||
cu += 11;
|
||||
break;
|
||||
case 5: {
|
||||
abbrev_offset = *(U32<E> *)(cu + 8);
|
||||
if (u32 address_size = cu[7]; address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
|
||||
<< address_size;
|
||||
|
||||
switch (u32 unit_type = cu[6]; unit_type) {
|
||||
case DW_UT_compile:
|
||||
case DW_UT_partial:
|
||||
cu += 12;
|
||||
break;
|
||||
case DW_UT_skeleton:
|
||||
case DW_UT_split_compile:
|
||||
cu += 20;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unknown DW_UT_* value: 0x"
|
||||
<< std::hex << unit_type;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unknown DWARF version: "
|
||||
<< dwarf_version;
|
||||
}
|
||||
|
||||
u32 abbrev_code = read_uleb(&cu);
|
||||
|
||||
// Find a .debug_abbrev record corresponding to the .debug_info record.
|
||||
// We assume the .debug_info record at a given offset is of
|
||||
// DW_TAG_compile_unit which describes a compunit.
|
||||
u8 *abbrev = get_buffer(ctx, ctx.debug_abbrev) + abbrev_offset;
|
||||
|
||||
for (;;) {
|
||||
u32 code = read_uleb(&abbrev);
|
||||
if (code == 0)
|
||||
Fatal(ctx) << file << ": --gdb-index: .debug_abbrev does not contain"
|
||||
<< " a record for the first .debug_info record";
|
||||
|
||||
if (code == abbrev_code) {
|
||||
// Found a record
|
||||
u64 abbrev_tag = read_uleb(&abbrev);
|
||||
if (abbrev_tag != DW_TAG_compile_unit && abbrev_tag != DW_TAG_skeleton_unit)
|
||||
Fatal(ctx) << file << ": --gdb-index: the first entry's tag is not"
|
||||
<< " DW_TAG_compile_unit/DW_TAG_skeleton_unit but 0x"
|
||||
<< std::hex << abbrev_tag;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip an uninteresting record
|
||||
read_uleb(&abbrev); // tag
|
||||
abbrev++; // has_children byte
|
||||
for (;;) {
|
||||
u64 name = read_uleb(&abbrev);
|
||||
u64 form = read_uleb(&abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
if (form == DW_FORM_implicit_const)
|
||||
read_uleb(&abbrev);
|
||||
}
|
||||
}
|
||||
|
||||
abbrev++; // skip has_children byte
|
||||
return {cu, abbrev, dwarf_version};
|
||||
}
|
||||
|
||||
// Estimate the number of address ranges contained in a given file.
|
||||
// It may over-estimate but never under-estimate.
|
||||
template <typename E>
|
||||
i64 estimate_address_areas(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
// Each CU contains zero or one address area.
|
||||
i64 ret = file.compunits.size();
|
||||
|
||||
// In DWARF 4, a CU can refer address ranges in .debug_ranges.
|
||||
// .debug_ranges contains a vector of [begin, end) address pairs.
|
||||
// The last entry must be a null terminator, so we do -1.
|
||||
if (file.debug_ranges)
|
||||
ret += file.debug_ranges->sh_size / sizeof(Word<E>) / 2 - 1;
|
||||
|
||||
// In DWARF 5, a CU can refer address ranges in .debug_rnglists, which
|
||||
// contains variable-length entries. The smallest possible range entry
|
||||
// is one byte for the code and two ULEB128 values (each can be as
|
||||
// small as one byte), so 3 bytes.
|
||||
if (file.debug_rnglists)
|
||||
ret += file.debug_rnglists->sh_size / 3;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// .debug_info contains variable-length fields. This class reads them.
|
||||
template <typename E>
|
||||
class DebugInfoReader {
|
||||
public:
|
||||
DebugInfoReader(Context<E> &ctx, ObjectFile<E> &file, u8 *cu)
|
||||
: ctx(ctx), file(file), cu(cu) {}
|
||||
|
||||
u64 read(u64 form);
|
||||
|
||||
Context<E> &ctx;
|
||||
ObjectFile<E> &file;
|
||||
u8 *cu;
|
||||
};
|
||||
|
||||
// Read value of the given DW_FORM_* form. If a value is not scalar,
|
||||
// returns a dummy value 0.
|
||||
template <typename E>
|
||||
u64 DebugInfoReader<E>::read(u64 form) {
|
||||
switch (form) {
|
||||
case DW_FORM_flag_present:
|
||||
return 0;
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_flag:
|
||||
case DW_FORM_strx1:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_ref1:
|
||||
return *cu++;
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_strx2:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_ref2: {
|
||||
u64 val = *(U16<E> *)cu;
|
||||
cu += 2;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx3:
|
||||
case DW_FORM_addrx3: {
|
||||
u64 val = *(U24<E> *)cu;
|
||||
cu += 3;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_strp:
|
||||
case DW_FORM_sec_offset:
|
||||
case DW_FORM_line_strp:
|
||||
case DW_FORM_strx4:
|
||||
case DW_FORM_addrx4:
|
||||
case DW_FORM_ref4: {
|
||||
u64 val = *(U32<E> *)cu;
|
||||
cu += 4;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data8:
|
||||
case DW_FORM_ref8: {
|
||||
u64 val = *(U64<E> *)cu;
|
||||
cu += 8;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_addr:
|
||||
case DW_FORM_ref_addr: {
|
||||
u64 val = *(Word<E> *)cu;
|
||||
cu += sizeof(Word<E>);
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx:
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_ref_udata:
|
||||
case DW_FORM_loclistx:
|
||||
case DW_FORM_rnglistx:
|
||||
return read_uleb(&cu);
|
||||
case DW_FORM_string:
|
||||
cu += strlen((char *)cu) + 1;
|
||||
return 0;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled debug info form: 0x"
|
||||
<< std::hex << form;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Read a range list from .debug_ranges starting at the given offset.
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_debug_range(Context<E> &ctx, ObjectFile<E> &file, Word<E> *range) {
|
||||
std::vector<u64> vec;
|
||||
u64 base = 0;
|
||||
|
||||
for (i64 i = 0; range[i] || range[i + 1]; i += 2) {
|
||||
if (range[i] + 1 == 0) {
|
||||
// base address selection entry
|
||||
base = range[i + 1];
|
||||
} else {
|
||||
vec.push_back(range[i] + base);
|
||||
vec.push_back(range[i + 1] + base);
|
||||
}
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Read a range list from .debug_rnglists starting at the given offset.
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_rnglist_range(Context<E> &ctx, ObjectFile<E> &file, u8 *rnglist,
|
||||
Word<E> *addrx) {
|
||||
std::vector<u64> vec;
|
||||
u64 base = 0;
|
||||
|
||||
for (;;) {
|
||||
switch (*rnglist++) {
|
||||
case DW_RLE_end_of_list:
|
||||
return vec;
|
||||
case DW_RLE_base_addressx:
|
||||
base = addrx[read_uleb(&rnglist)];
|
||||
break;
|
||||
case DW_RLE_startx_endx:
|
||||
vec.push_back(addrx[read_uleb(&rnglist)]);
|
||||
vec.push_back(addrx[read_uleb(&rnglist)]);
|
||||
break;
|
||||
case DW_RLE_startx_length:
|
||||
vec.push_back(addrx[read_uleb(&rnglist)]);
|
||||
vec.push_back(vec.back() + read_uleb(&rnglist));
|
||||
break;
|
||||
case DW_RLE_offset_pair:
|
||||
vec.push_back(base + read_uleb(&rnglist));
|
||||
vec.push_back(base + read_uleb(&rnglist));
|
||||
break;
|
||||
case DW_RLE_base_address:
|
||||
base = *(Word<E> *)rnglist;
|
||||
rnglist += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_end:
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_length:
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
vec.push_back(vec.back() + read_uleb(&rnglist));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a list of address ranges explained by a compunit at the
|
||||
// `offset` in an output .debug_info section.
|
||||
//
|
||||
// .debug_info contains DWARF debug info records, so this function
|
||||
// parses DWARF. If a designated compunit contains multiple ranges, the
|
||||
// ranges are read from .debug_ranges (or .debug_rnglists for DWARF5).
|
||||
// Otherwise, a range is read directly from .debug_info (or possibly
|
||||
// from .debug_addr for DWARF5).
|
||||
template <typename E>
|
||||
std::vector<u64>
|
||||
read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
u8 *cu;
|
||||
u8 *abbrev;
|
||||
u32 dwarf_version;
|
||||
std::tie(cu, abbrev, dwarf_version) = find_compunit(ctx, file, offset);
|
||||
|
||||
DebugInfoReader<E> reader{ctx, file, cu};
|
||||
|
||||
struct Record {
|
||||
u64 form = 0;
|
||||
u64 value = 0;
|
||||
};
|
||||
|
||||
Record low_pc;
|
||||
Record high_pc;
|
||||
Record ranges;
|
||||
std::optional<u64> rnglists_base;
|
||||
Word<E> *addrx = nullptr;
|
||||
|
||||
// Read all interesting debug records.
|
||||
for (;;) {
|
||||
u64 name = read_uleb(&abbrev);
|
||||
u64 form = read_uleb(&abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
|
||||
u64 val = reader.read(form);
|
||||
|
||||
switch (name) {
|
||||
case DW_AT_low_pc:
|
||||
low_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_high_pc:
|
||||
high_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_rnglists_base:
|
||||
rnglists_base = val;
|
||||
break;
|
||||
case DW_AT_addr_base:
|
||||
addrx = (Word<E> *)(get_buffer(ctx, ctx.debug_addr) + val);
|
||||
break;
|
||||
case DW_AT_ranges:
|
||||
ranges = {form, val};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-contiguous address ranges.
|
||||
if (ranges.form) {
|
||||
if (dwarf_version <= 4) {
|
||||
Word<E> *range_begin =
|
||||
(Word<E> *)(get_buffer(ctx, ctx.debug_ranges) + ranges.value);
|
||||
return read_debug_range(ctx, file, range_begin);
|
||||
}
|
||||
|
||||
assert(dwarf_version == 5);
|
||||
|
||||
u8 *buf = get_buffer(ctx, ctx.debug_rnglists);
|
||||
if (ranges.form == DW_FORM_sec_offset)
|
||||
return read_rnglist_range(ctx, file, buf + ranges.value, addrx);
|
||||
|
||||
if (!rnglists_base)
|
||||
Fatal(ctx) << file << ": --gdb-index: missing DW_AT_rnglists_base";
|
||||
|
||||
u8 *base = buf + *rnglists_base;
|
||||
return read_rnglist_range(ctx, file, base + *(U32<E> *)base, addrx);
|
||||
}
|
||||
|
||||
// Handle a contiguous address range.
|
||||
if (low_pc.form && high_pc.form) {
|
||||
u64 lo;
|
||||
|
||||
switch (low_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
lo = low_pc.value;
|
||||
break;
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
lo = addrx[low_pc.value];
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled form for DW_AT_low_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
|
||||
switch (high_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
return {lo, high_pc.value};
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
return {lo, addrx[high_pc.value]};
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_data8:
|
||||
return {lo, lo + high_pc.value};
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled form for DW_AT_high_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template std::vector<std::string_view> read_compunits(Context<E> &, ObjectFile<E> &);
|
||||
template std::vector<GdbIndexName> read_pubnames(Context<E> &, ObjectFile<E> &);
|
||||
template i64 estimate_address_areas(Context<E> &, ObjectFile<E> &);
|
||||
template std::vector<u64> read_address_areas(Context<E> &, ObjectFile<E> &, i64);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -161,6 +161,7 @@ enum : u32 {
|
||||
VER_NDX_LOCAL = 0,
|
||||
VER_NDX_GLOBAL = 1,
|
||||
VER_NDX_LAST_RESERVED = 1,
|
||||
VER_NDX_UNSPECIFIED = 0xffff,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -291,6 +292,7 @@ enum : u32 {
|
||||
DT_VERNEEDNUM = 0x6fffffff,
|
||||
DT_PPC_GOT = 0x70000000,
|
||||
DT_PPC64_GLINK = 0x70000000,
|
||||
DT_AARCH64_VARIANT_PCS = 0x70000005,
|
||||
DT_AUXILIARY = 0x7ffffffd,
|
||||
DT_FILTER = 0x7fffffff,
|
||||
};
|
||||
@@ -774,6 +776,9 @@ enum : u32 {
|
||||
R_RISCV_RVC_BRANCH = 44,
|
||||
R_RISCV_RVC_JUMP = 45,
|
||||
R_RISCV_RVC_LUI = 46,
|
||||
R_RISCV_GPREL_LO12_I = 47,
|
||||
R_RISCV_GPREL_LO12_S = 48,
|
||||
R_RISCV_GPREL_HI20 = 49,
|
||||
R_RISCV_RELAX = 51,
|
||||
R_RISCV_SUB6 = 52,
|
||||
R_RISCV_SET6 = 53,
|
||||
@@ -1638,6 +1643,16 @@ struct ElfRel<E> {
|
||||
std::conditional_t<E::is_64, U32<E>, u8> r_type;
|
||||
};
|
||||
|
||||
// Returns true if a given relocation is of type used for direct
|
||||
// function call.
|
||||
template <typename E>
|
||||
inline bool is_func_call_rel(const ElfRel<E> &r) {
|
||||
for (u32 r_type : E::R_FUNCALL)
|
||||
if (r.r_type == r_type)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
struct ElfDyn {
|
||||
Word<E> d_tag;
|
||||
@@ -1705,6 +1720,35 @@ struct ElfNhdr {
|
||||
// Target-specific ELF data types
|
||||
//
|
||||
|
||||
template <>
|
||||
struct ElfSym<ARM64> {
|
||||
bool is_undef() const { return st_shndx == SHN_UNDEF; }
|
||||
bool is_abs() const { return st_shndx == SHN_ABS; }
|
||||
bool is_common() const { return st_shndx == SHN_COMMON; }
|
||||
bool is_weak() const { return st_bind == STB_WEAK; }
|
||||
bool is_undef_weak() const { return is_undef() && is_weak(); }
|
||||
|
||||
ul32 st_name;
|
||||
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u8 : 5;
|
||||
u8 arm64_variant_pcs : 1; // ARM64-specific
|
||||
#else
|
||||
u8 st_bind : 4;
|
||||
u8 st_type : 4;
|
||||
u8 arm64_variant_pcs : 1;
|
||||
u8 : 6;
|
||||
u8 st_visibility : 2;
|
||||
#endif
|
||||
|
||||
ul16 st_shndx;
|
||||
ul64 st_value;
|
||||
ul64 st_size;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ElfSym<PPC64V2> {
|
||||
bool is_undef() const { return st_shndx == SHN_UNDEF; }
|
||||
@@ -1824,19 +1868,20 @@ struct X86_64 {
|
||||
static constexpr u32 e_machine = EM_X86_64;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr u8 filler[] = { 0xcc };
|
||||
|
||||
static constexpr u32 R_COPY = R_X86_64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_X86_64_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_X86_64_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_X86_64_64;
|
||||
static constexpr u32 R_DYNAMIC = R_X86_64_64;
|
||||
static constexpr u32 R_RELATIVE = R_X86_64_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_X86_64_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_X86_64_DTPOFF64;
|
||||
static constexpr u32 R_TPOFF = R_X86_64_TPOFF64;
|
||||
static constexpr u32 R_DTPMOD = R_X86_64_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_X86_64_TLSDESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_X86_64_PLT32, R_X86_64_PLTOFF64 };
|
||||
};
|
||||
|
||||
struct I386 {
|
||||
@@ -1849,18 +1894,19 @@ struct I386 {
|
||||
static constexpr u32 plt_hdr_size = 16;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0xcc };
|
||||
|
||||
static constexpr u32 R_COPY = R_386_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_386_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_386_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_386_32;
|
||||
static constexpr u32 R_DYNAMIC = R_386_32;
|
||||
static constexpr u32 R_RELATIVE = R_386_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_386_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_386_TLS_DTPOFF32;
|
||||
static constexpr u32 R_TPOFF = R_386_TLS_TPOFF;
|
||||
static constexpr u32 R_DTPMOD = R_386_TLS_DTPMOD32;
|
||||
static constexpr u32 R_TLSDESC = R_386_TLS_DESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_386_PLT32 };
|
||||
};
|
||||
|
||||
struct ARM64 {
|
||||
@@ -1875,18 +1921,19 @@ struct ARM64 {
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
static constexpr u8 filler[] = { 0x00, 0x7d, 0x20, 0xd4 };
|
||||
|
||||
static constexpr u32 R_COPY = R_AARCH64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_AARCH64_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_AARCH64_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_AARCH64_ABS64;
|
||||
static constexpr u32 R_DYNAMIC = R_AARCH64_ABS64;
|
||||
static constexpr u32 R_RELATIVE = R_AARCH64_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_AARCH64_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_AARCH64_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_AARCH64_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_AARCH64_TLS_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_AARCH64_TLSDESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_AARCH64_JUMP26, R_AARCH64_CALL26 };
|
||||
};
|
||||
|
||||
struct ARM32 {
|
||||
@@ -1901,18 +1948,22 @@ struct ARM32 {
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 16;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
static constexpr u8 filler[] = { 0xff, 0xde };
|
||||
|
||||
static constexpr u32 R_COPY = R_ARM_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_ARM_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_ARM_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_ARM_ABS32;
|
||||
static constexpr u32 R_DYNAMIC = R_ARM_ABS32;
|
||||
static constexpr u32 R_RELATIVE = R_ARM_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_ARM_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_ARM_TLS_DTPOFF32;
|
||||
static constexpr u32 R_TPOFF = R_ARM_TLS_TPOFF32;
|
||||
static constexpr u32 R_DTPMOD = R_ARM_TLS_DTPMOD32;
|
||||
static constexpr u32 R_TLSDESC = R_ARM_TLS_DESC;
|
||||
|
||||
static constexpr u32 R_FUNCALL[] = {
|
||||
R_ARM_JUMP24, R_ARM_THM_JUMP24, R_ARM_CALL, R_ARM_THM_CALL, R_ARM_PLT32,
|
||||
};
|
||||
};
|
||||
|
||||
struct RV64 {
|
||||
@@ -1923,18 +1974,19 @@ struct RV64 {
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0x02, 0x90 };
|
||||
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
|
||||
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_RISCV_64;
|
||||
static constexpr u32 R_DYNAMIC = R_RISCV_64;
|
||||
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_RISCV_TLSDESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_RISCV_CALL, R_RISCV_CALL_PLT };
|
||||
};
|
||||
|
||||
struct RV64LE : RV64 {
|
||||
@@ -1955,18 +2007,19 @@ struct RV32 {
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0x02, 0x90 };
|
||||
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
|
||||
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_RISCV_32;
|
||||
static constexpr u32 R_DYNAMIC = R_RISCV_32;
|
||||
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD32;
|
||||
static constexpr u32 R_TLSDESC = R_RISCV_TLSDESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_RISCV_CALL, R_RISCV_CALL_PLT };
|
||||
};
|
||||
|
||||
struct RV32LE : RV32 {
|
||||
@@ -1991,17 +2044,21 @@ struct PPC32 {
|
||||
static constexpr u32 pltgot_size = 36;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 36;
|
||||
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 };
|
||||
|
||||
static constexpr u32 R_COPY = R_PPC_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_PPC_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_PPC_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_PPC_ADDR32;
|
||||
static constexpr u32 R_DYNAMIC = R_PPC_ADDR32;
|
||||
static constexpr u32 R_RELATIVE = R_PPC_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_PPC_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_PPC_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_PPC_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_PPC_DTPMOD32;
|
||||
|
||||
static constexpr u32 R_FUNCALL[] = {
|
||||
R_PPC_REL24, R_PPC_PLTREL24, R_PPC_LOCAL24PC,
|
||||
};
|
||||
};
|
||||
|
||||
struct PPC64 {
|
||||
@@ -2014,12 +2071,12 @@ struct PPC64 {
|
||||
static constexpr u32 R_GLOB_DAT = R_PPC64_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_PPC64_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_PPC64_ADDR64;
|
||||
static constexpr u32 R_DYNAMIC = R_PPC64_ADDR64;
|
||||
static constexpr u32 R_RELATIVE = R_PPC64_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_PPC64_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_PPC64_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_PPC64_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_PPC64_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_PPC64_REL24, R_PPC64_REL24_NOTOC };
|
||||
};
|
||||
|
||||
struct PPC64V1 : PPC64 {
|
||||
@@ -2029,6 +2086,7 @@ struct PPC64V1 : PPC64 {
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 28;
|
||||
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 };
|
||||
};
|
||||
|
||||
struct PPC64V2 : PPC64 {
|
||||
@@ -2038,7 +2096,8 @@ struct PPC64V2 : PPC64 {
|
||||
static constexpr u32 plt_size = 4;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 20;
|
||||
static constexpr u32 thunk_size = 24;
|
||||
static constexpr u8 filler[] = { 0x08, 0x00, 0xe0, 0x7f };
|
||||
};
|
||||
|
||||
struct S390X {
|
||||
@@ -2051,17 +2110,18 @@ struct S390X {
|
||||
static constexpr u32 plt_hdr_size = 48;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0x07, 0x00 };
|
||||
|
||||
static constexpr u32 R_COPY = R_390_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_390_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_390_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_390_64;
|
||||
static constexpr u32 R_DYNAMIC = R_390_64;
|
||||
static constexpr u32 R_RELATIVE = R_390_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_390_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_390_TLS_DTPOFF;
|
||||
static constexpr u32 R_TPOFF = R_390_TLS_TPOFF;
|
||||
static constexpr u32 R_DTPMOD = R_390_TLS_DTPMOD;
|
||||
static constexpr u32 R_FUNCALL[] = { R_390_PLT32DBL };
|
||||
};
|
||||
|
||||
struct SPARC64 {
|
||||
@@ -2074,17 +2134,18 @@ struct SPARC64 {
|
||||
static constexpr u32 plt_hdr_size = 128;
|
||||
static constexpr u32 plt_size = 32;
|
||||
static constexpr u32 pltgot_size = 32;
|
||||
static constexpr u8 filler[] = { 0x91, 0xd0, 0x20, 0x05 };
|
||||
|
||||
static constexpr u32 R_COPY = R_SPARC_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_SPARC_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_SPARC_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_SPARC_64;
|
||||
static constexpr u32 R_DYNAMIC = R_SPARC_64;
|
||||
static constexpr u32 R_RELATIVE = R_SPARC_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_SPARC_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_SPARC_TLS_DTPOFF64;
|
||||
static constexpr u32 R_TPOFF = R_SPARC_TLS_TPOFF64;
|
||||
static constexpr u32 R_DTPMOD = R_SPARC_TLS_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_SPARC_WPLT30, R_SPARC_WDISP30 };
|
||||
};
|
||||
|
||||
struct M68K {
|
||||
@@ -2097,16 +2158,17 @@ struct M68K {
|
||||
static constexpr u32 plt_hdr_size = 18;
|
||||
static constexpr u32 plt_size = 14;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr u8 filler[] = { 0x4a, 0xfc };
|
||||
|
||||
static constexpr u32 R_COPY = R_68K_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_68K_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_68K_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_68K_32;
|
||||
static constexpr u32 R_DYNAMIC = R_68K_32;
|
||||
static constexpr u32 R_RELATIVE = R_68K_RELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_68K_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_68K_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_68K_TLS_DTPMOD32;
|
||||
static constexpr u32 R_FUNCALL[] = { R_68K_PLT32 };
|
||||
};
|
||||
|
||||
struct SH4 {
|
||||
@@ -2119,16 +2181,17 @@ struct SH4 {
|
||||
static constexpr u32 plt_hdr_size = 16;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 12;
|
||||
static constexpr u8 filler[] = { 0x00, 0x00 };
|
||||
|
||||
static constexpr u32 R_COPY = R_SH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_SH_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_SH_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_SH_DIR32;
|
||||
static constexpr u32 R_DYNAMIC = R_SH_DIR32;
|
||||
static constexpr u32 R_RELATIVE = R_SH_RELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_SH_TLS_DTPOFF32;
|
||||
static constexpr u32 R_TPOFF = R_SH_TLS_TPOFF32;
|
||||
static constexpr u32 R_DTPMOD = R_SH_TLS_DTPMOD32;
|
||||
static constexpr u32 R_FUNCALL[] = { R_SH_PLT32 };
|
||||
};
|
||||
|
||||
struct ALPHA {
|
||||
@@ -2141,16 +2204,17 @@ struct ALPHA {
|
||||
static constexpr u32 plt_hdr_size = 0;
|
||||
static constexpr u32 plt_size = 0;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u8 filler[] = { 0x81, 0x00, 0x00, 0x00 };
|
||||
|
||||
static constexpr u32 R_COPY = R_ALPHA_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_ALPHA_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_ALPHA_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_ALPHA_REFQUAD;
|
||||
static constexpr u32 R_DYNAMIC = R_ALPHA_REFQUAD;
|
||||
static constexpr u32 R_RELATIVE = R_ALPHA_RELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_ALPHA_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_ALPHA_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_ALPHA_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = {};
|
||||
};
|
||||
|
||||
struct LOONGARCH64 {
|
||||
@@ -2164,18 +2228,19 @@ struct LOONGARCH64 {
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
static constexpr u32 thunk_size = 8;
|
||||
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 };
|
||||
|
||||
static constexpr u32 R_COPY = R_LARCH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_LARCH_64;
|
||||
static constexpr u32 R_JUMP_SLOT = R_LARCH_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_LARCH_64;
|
||||
static constexpr u32 R_DYNAMIC = R_LARCH_64;
|
||||
static constexpr u32 R_RELATIVE = R_LARCH_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_LARCH_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_LARCH_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_LARCH_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_LARCH_TLS_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26 };
|
||||
};
|
||||
|
||||
struct LOONGARCH32 {
|
||||
@@ -2189,18 +2254,19 @@ struct LOONGARCH32 {
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
static constexpr u32 thunk_size = 8;
|
||||
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 };
|
||||
|
||||
static constexpr u32 R_COPY = R_LARCH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_LARCH_32;
|
||||
static constexpr u32 R_JUMP_SLOT = R_LARCH_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_LARCH_32;
|
||||
static constexpr u32 R_DYNAMIC = R_LARCH_32;
|
||||
static constexpr u32 R_RELATIVE = R_LARCH_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_LARCH_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_LARCH_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_LARCH_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_LARCH_TLS_DTPMOD32;
|
||||
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26 };
|
||||
};
|
||||
|
||||
} // namespace mold::elf
|
||||
+4
-4
@@ -116,11 +116,11 @@ static void collect_root_set(Context<E> &ctx,
|
||||
// Add sections referenced by root symbols.
|
||||
enqueue_symbol(get_symbol(ctx, ctx.arg.entry));
|
||||
|
||||
for (std::string_view name : ctx.arg.undefined)
|
||||
enqueue_symbol(get_symbol(ctx, name));
|
||||
for (Symbol<E> *sym : ctx.arg.undefined)
|
||||
enqueue_symbol(sym);
|
||||
|
||||
for (std::string_view name : ctx.arg.require_defined)
|
||||
enqueue_symbol(get_symbol(ctx, name));
|
||||
for (Symbol<E> *sym : ctx.arg.require_defined)
|
||||
enqueue_symbol(sym);
|
||||
|
||||
// .eh_frame consists of variable-length records called CIE and FDE
|
||||
// records, and they are a unit of inclusion or exclusion.
|
||||
|
||||
@@ -0,0 +1,793 @@
|
||||
// This file contains code to read DWARF debug info to create .gdb_index.
|
||||
//
|
||||
// .gdb_index is an optional section to speed up GNU debugger. It contains
|
||||
// two maps: 1) a map from function/variable/type names to compunits, and
|
||||
// 2) a map from function address ranges to compunits. gdb uses these
|
||||
// maps to quickly find a compunit given a name or an instruction pointer.
|
||||
//
|
||||
// (Terminology: a compilation unit, often abbreviated as compunit or
|
||||
// CU, is a unit of debug info. An input .debug_info section usually
|
||||
// contains one compunit, and thus an output .debug_info contains as
|
||||
// many compunits as the number of input files.)
|
||||
//
|
||||
// .gdb_index is not mandatory. All the information in .gdb_index is
|
||||
// also in other debug info sections. You can actually create an
|
||||
// executable without .gdb_index and later add it using the
|
||||
// `gdb-add-index` post-processing tool that comes with gdb.
|
||||
//
|
||||
// Post-relocated debug section contents are needed to create a
|
||||
// .gdb_index. Therefore, we create it after relocating all the other
|
||||
// sections. The size of the section is also hard to estimate before
|
||||
// applying relocations to debug info sections, so a .gdb_index is
|
||||
// placed at the very end of the output file, even after the section
|
||||
// header.
|
||||
//
|
||||
// The mapping from names to compunits is 1:n while the mapping from
|
||||
// address ranges to compunits is 1:1. That is, two object files may
|
||||
// define the same type name, while there should be no two functions
|
||||
// that overlap with each other in memory.
|
||||
//
|
||||
// .gdb_index contains an on-disk hash table for names, so gdb can
|
||||
// lookup names without loading all strings into memory and construct an
|
||||
// in-memory hash table.
|
||||
//
|
||||
// Names are in .debug_gnu_pubnames and .debug_gnu_pubtypes input
|
||||
// sections. These sections are created if `-ggnu-pubnames` is given.
|
||||
// Besides names, these sections contain attributes for each name so
|
||||
// that gdb can distinguish type names from function names, for example.
|
||||
//
|
||||
// A compunit contains one or more function address ranges. If an
|
||||
// object file is compiled without -ffunction-sections, it contains
|
||||
// only one .text section and therefore contains a single address range.
|
||||
// Such range is typically stored directly to the compunit.
|
||||
//
|
||||
// If an object file is compiled with -ffunction-sections, it contains
|
||||
// more than one .text section, and it has as many address ranges as
|
||||
// the number of .text sections. Such discontiguous address ranges are
|
||||
// stored to .debug_ranges in DWARF 2/3/4 and .debug_rnglists/.debug_addr
|
||||
// in DWARF 5.
|
||||
//
|
||||
// .debug_info section contains DWARF debug info. Although we don't need
|
||||
// to parse the whole .debug_info section to read address ranges, we
|
||||
// have to do a little bit. DWARF is complicated and often handled using
|
||||
// a library such as libdwarf. But we don't use any library because we
|
||||
// don't want to add an extra run-time dependency just for --gdb-index.
|
||||
//
|
||||
// This page explains the format of .gdb_index:
|
||||
// https://sourceware.org/gdb/onlinedocs/gdb/Index-Section-Format.html
|
||||
|
||||
#include "mold.h"
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <tbb/parallel_sort.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
enum DwarfKind { DWARF2_32, DWARF5_32, DWARF2_64, DWARF5_64 };
|
||||
|
||||
template <typename E>
|
||||
struct CuHdrDwarf2_32 {
|
||||
U32<E> size;
|
||||
U16<E> version;
|
||||
U32<E> abbrev_offset;
|
||||
u8 address_size;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct CuHdrDwarf5_32 {
|
||||
U32<E> size;
|
||||
U16<E> version;
|
||||
u8 unit_type;
|
||||
u8 address_size;
|
||||
U32<E> abbrev_offset;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct CuHdrDwarf2_64 {
|
||||
U32<E> magic;
|
||||
U64<E> size;
|
||||
U16<E> version;
|
||||
U64<E> abbrev_offset;
|
||||
u8 address_size;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct CuHdrDwarf5_64 {
|
||||
U32<E> magic;
|
||||
U64<E> size;
|
||||
U16<E> version;
|
||||
u8 unit_type;
|
||||
u8 address_size;
|
||||
U64<E> abbrev_offset;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct PubnamesHdr32 {
|
||||
U32<E> size;
|
||||
U16<E> version;
|
||||
U32<E> debug_info_offset;
|
||||
U32<E> debug_info_size;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct PubnamesHdr64 {
|
||||
U32<E> magic;
|
||||
U64<E> size;
|
||||
U16<E> version;
|
||||
U64<E> debug_info_offset;
|
||||
U64<E> debug_info_size;
|
||||
};
|
||||
|
||||
struct SectionHeader {
|
||||
ul32 version = 7;
|
||||
ul32 cu_list_offset = 0;
|
||||
ul32 cu_types_offset = 0;
|
||||
ul32 ranges_offset = 0;
|
||||
ul32 symtab_offset = 0;
|
||||
ul32 const_pool_offset = 0;
|
||||
};
|
||||
|
||||
struct NameType {
|
||||
bool operator==(const NameType &) const = default;
|
||||
|
||||
bool operator<(const NameType &other) const {
|
||||
return std::tuple(hash, type, name) <
|
||||
std::tuple(other.hash, other.type, other.name);
|
||||
}
|
||||
|
||||
std::string_view name;
|
||||
u32 hash;
|
||||
u8 type;
|
||||
};
|
||||
|
||||
struct MapValue {
|
||||
u32 hash = 0;
|
||||
Atomic<u32> count = 0;
|
||||
u32 name_offset = 0;
|
||||
u32 type_offset = 0;
|
||||
};
|
||||
|
||||
struct Compunit {
|
||||
DwarfKind kind;
|
||||
i64 offset;
|
||||
i64 size;
|
||||
std::vector<std::pair<u64, u64>> ranges;
|
||||
std::vector<NameType> nametypes;
|
||||
std::vector<MapValue *> entries;
|
||||
};
|
||||
|
||||
// The hash function for .gdb_index.
|
||||
static u32 gdb_hash(std::string_view name) {
|
||||
u32 h = 0;
|
||||
for (u8 c : name) {
|
||||
if ('A' <= c && c <= 'Z')
|
||||
c = 'a' + c - 'A';
|
||||
h = h * 67 + c - 113;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static DwarfKind get_dwarf_kind(Context<E> &ctx, u8 *p) {
|
||||
if (*(U32<E> *)p == 0xffff'ffff) {
|
||||
CuHdrDwarf2_64<E> &hdr = *(CuHdrDwarf2_64<E> *)p;
|
||||
if (hdr.version > 5)
|
||||
Fatal(ctx) << "--gdb-index: DWARF version " << hdr.version
|
||||
<< " is not supported";
|
||||
return (hdr.version == 5) ? DWARF5_64 : DWARF2_64;
|
||||
}
|
||||
|
||||
CuHdrDwarf2_32<E> &hdr = *(CuHdrDwarf2_32<E> *)p;
|
||||
if (hdr.version > 5)
|
||||
Fatal(ctx) << "--gdb-index: DWARF version " << hdr.version
|
||||
<< " is not supported";
|
||||
return (hdr.version == 5) ? DWARF5_32 : DWARF2_32;
|
||||
}
|
||||
|
||||
template <typename E, typename CuHdr>
|
||||
u8 *find_cu_abbrev(Context<E> &ctx, u8 **p, const CuHdr &hdr) {
|
||||
if (hdr.address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << "--gdb-index: unsupported address size " << hdr.address_size;
|
||||
|
||||
if constexpr (requires { hdr.unit_type; }) {
|
||||
switch (hdr.unit_type) {
|
||||
case DW_UT_compile:
|
||||
case DW_UT_partial:
|
||||
break;
|
||||
case DW_UT_skeleton:
|
||||
case DW_UT_split_compile:
|
||||
*p += 8;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "--gdb-index: unknown unit type: 0x"
|
||||
<< std::hex << hdr.unit_type;
|
||||
}
|
||||
}
|
||||
|
||||
i64 abbrev_code = read_uleb(p);
|
||||
|
||||
// Find a .debug_abbrev record corresponding to the .debug_info record.
|
||||
// We assume the .debug_info record at a given offset is of
|
||||
// DW_TAG_compile_unit which describes a compunit.
|
||||
u8 *abbrev = &ctx.debug_abbrev[0] + hdr.abbrev_offset;
|
||||
|
||||
for (;;) {
|
||||
u32 code = read_uleb(&abbrev);
|
||||
if (code == 0)
|
||||
Fatal(ctx) << "--gdb-index: .debug_abbrev does not contain"
|
||||
<< " a record for the first .debug_info record";
|
||||
|
||||
if (code == abbrev_code) {
|
||||
// Found a record
|
||||
u64 abbrev_tag = read_uleb(&abbrev);
|
||||
if (abbrev_tag != DW_TAG_compile_unit && abbrev_tag != DW_TAG_skeleton_unit)
|
||||
Fatal(ctx) << "--gdb-index: the first entry's tag is not"
|
||||
<< " DW_TAG_compile_unit/DW_TAG_skeleton_unit but 0x"
|
||||
<< std::hex << abbrev_tag;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip an uninteresting record
|
||||
read_uleb(&abbrev); // tag
|
||||
abbrev++; // has_children byte
|
||||
for (;;) {
|
||||
u64 name = read_uleb(&abbrev);
|
||||
u64 form = read_uleb(&abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
if (form == DW_FORM_implicit_const)
|
||||
read_uleb(&abbrev);
|
||||
}
|
||||
}
|
||||
|
||||
abbrev++; // skip has_children byte
|
||||
return abbrev;
|
||||
}
|
||||
|
||||
// .debug_info contains variable-length fields.
|
||||
// This function reads one scalar value from a given location.
|
||||
template <typename E, typename Offset>
|
||||
u64 read_scalar(Context<E> &ctx, u8 **p, u64 form) {
|
||||
switch (form) {
|
||||
case DW_FORM_flag_present:
|
||||
return 0;
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_flag:
|
||||
case DW_FORM_strx1:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_ref1:
|
||||
return *(*p)++;
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_strx2:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_ref2: {
|
||||
u64 val = *(U16<E> *)*p;
|
||||
*p += 2;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx3:
|
||||
case DW_FORM_addrx3: {
|
||||
u64 val = *(U24<E> *)*p;
|
||||
*p += 3;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_strx4:
|
||||
case DW_FORM_addrx4:
|
||||
case DW_FORM_ref4: {
|
||||
u64 val = *(U32<E> *)*p;
|
||||
*p += 4;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data8:
|
||||
case DW_FORM_ref8: {
|
||||
u64 val = *(U64<E> *)*p;
|
||||
*p += 8;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strp:
|
||||
case DW_FORM_sec_offset:
|
||||
case DW_FORM_line_strp: {
|
||||
u64 val = *(Offset *)*p;
|
||||
*p += sizeof(Offset);
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_addr:
|
||||
case DW_FORM_ref_addr: {
|
||||
u64 val = *(Word<E> *)*p;
|
||||
*p += sizeof(Word<E>);
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx:
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_ref_udata:
|
||||
case DW_FORM_loclistx:
|
||||
case DW_FORM_rnglistx:
|
||||
return read_uleb(p);
|
||||
case DW_FORM_string:
|
||||
*p += strlen((char *)*p) + 1;
|
||||
return 0;
|
||||
default:
|
||||
Fatal(ctx) << "--gdb-index: unhandled debug info form: 0x"
|
||||
<< std::hex << form;
|
||||
}
|
||||
}
|
||||
|
||||
// Read a range list from .debug_ranges starting at the given offset.
|
||||
template <typename E>
|
||||
static std::vector<std::pair<u64, u64>>
|
||||
read_debug_range(Word<E> *range, u64 base) {
|
||||
std::vector<std::pair<u64, u64>> vec;
|
||||
|
||||
for (i64 i = 0; range[i] || range[i + 1]; i += 2) {
|
||||
if (range[i] + 1 == 0)
|
||||
base = range[i + 1];
|
||||
else
|
||||
vec.emplace_back(range[i] + base, range[i + 1] + base);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Read a range list from .debug_rnglists starting at the given offset.
|
||||
template <typename E>
|
||||
static void
|
||||
read_rnglist_range(std::vector<std::pair<u64, u64>> &vec, u8 *p,
|
||||
Word<E> *addrx, u64 base) {
|
||||
for (;;) {
|
||||
switch (*p++) {
|
||||
case DW_RLE_end_of_list:
|
||||
return;
|
||||
case DW_RLE_base_addressx:
|
||||
base = addrx[read_uleb(&p)];
|
||||
break;
|
||||
case DW_RLE_startx_endx: {
|
||||
u64 val1 = read_uleb(&p);
|
||||
u64 val2 = read_uleb(&p);
|
||||
vec.emplace_back(addrx[val1], addrx[val2]);
|
||||
break;
|
||||
}
|
||||
case DW_RLE_startx_length: {
|
||||
u64 val1 = read_uleb(&p);
|
||||
u64 val2 = read_uleb(&p);
|
||||
vec.emplace_back(addrx[val1], addrx[val1] + val2);
|
||||
break;
|
||||
}
|
||||
case DW_RLE_offset_pair: {
|
||||
u64 val1 = read_uleb(&p);
|
||||
u64 val2 = read_uleb(&p);
|
||||
|
||||
// If the base is 0, this address range is for an eliminated
|
||||
// section. We only emit it if it's alive.
|
||||
if (base)
|
||||
vec.emplace_back(base + val1, base + val2);
|
||||
break;
|
||||
}
|
||||
case DW_RLE_base_address:
|
||||
base = *(Word<E> *)p;
|
||||
p += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_end: {
|
||||
u64 val1 = ((Word<E> *)p)[0];
|
||||
u64 val2 = ((Word<E> *)p)[1];
|
||||
p += sizeof(Word<E>) * 2;
|
||||
vec.emplace_back(val1, val2);
|
||||
break;
|
||||
}
|
||||
case DW_RLE_start_length: {
|
||||
u64 val1 = *(Word<E> *)p;
|
||||
p += sizeof(Word<E>);
|
||||
u64 val2 = read_uleb(&p);
|
||||
vec.emplace_back(val1, val1 + val2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a list of address ranges explained by a compunit at the
|
||||
// `offset` in an output .debug_info section.
|
||||
//
|
||||
// .debug_info contains DWARF debug info records, so this function
|
||||
// parses DWARF. If a designated compunit contains multiple ranges, the
|
||||
// ranges are read from .debug_ranges (or .debug_rnglists for DWARF5).
|
||||
// Otherwise, a range is read directly from .debug_info (or possibly
|
||||
// from .debug_addr for DWARF5).
|
||||
template <typename E, typename CuHdr>
|
||||
static std::vector<std::pair<u64, u64>>
|
||||
read_address_ranges(Context<E> &ctx, const Compunit &cu) {
|
||||
// Read .debug_info to find the record at a given offset.
|
||||
u8 *p = &ctx.debug_info[0] + cu.offset;
|
||||
CuHdr &hdr = *(CuHdr *)p;
|
||||
p += sizeof(hdr);
|
||||
|
||||
u8 *abbrev = find_cu_abbrev(ctx, &p, hdr);
|
||||
|
||||
// Now, read debug info records.
|
||||
struct Record {
|
||||
u64 form = 0;
|
||||
u64 value = 0;
|
||||
};
|
||||
|
||||
using Offset = decltype(hdr.size);
|
||||
|
||||
Record low_pc;
|
||||
Record high_pc;
|
||||
Record ranges;
|
||||
u64 rnglists_base = -1;
|
||||
Word<E> *addrx = nullptr;
|
||||
|
||||
// Read all interesting debug records.
|
||||
for (;;) {
|
||||
u64 name = read_uleb(&abbrev);
|
||||
u64 form = read_uleb(&abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
|
||||
u64 val = read_scalar<E, Offset>(ctx, &p, form);
|
||||
|
||||
switch (name) {
|
||||
case DW_AT_low_pc:
|
||||
low_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_high_pc:
|
||||
high_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_rnglists_base:
|
||||
rnglists_base = val;
|
||||
break;
|
||||
case DW_AT_addr_base:
|
||||
addrx = (Word<E> *)(&ctx.debug_addr[0] + val);
|
||||
break;
|
||||
case DW_AT_ranges:
|
||||
ranges = {form, val};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-contiguous address ranges.
|
||||
if (ranges.form) {
|
||||
if (hdr.version <= 4) {
|
||||
Word<E> *p = (Word<E> *)(&ctx.debug_ranges[0] + ranges.value);
|
||||
return read_debug_range<E>(p, low_pc.value);
|
||||
}
|
||||
|
||||
assert(hdr.version == 5);
|
||||
|
||||
std::vector<std::pair<u64, u64>> vec;
|
||||
u8 *buf = &ctx.debug_rnglists[0];
|
||||
|
||||
if (ranges.form == DW_FORM_sec_offset) {
|
||||
read_rnglist_range<E>(vec, buf + ranges.value, addrx, low_pc.value);
|
||||
} else {
|
||||
if (rnglists_base == -1)
|
||||
Fatal(ctx) << "--gdb-index: missing DW_AT_rnglists_base";
|
||||
|
||||
u8 *base = buf + rnglists_base;
|
||||
i64 num_offsets = *(U32<E> *)(base - 4);
|
||||
Offset *offsets = (Offset *)base;
|
||||
|
||||
for (i64 i = 0; i < num_offsets; i++)
|
||||
read_rnglist_range<E>(vec, base + offsets[i], addrx, low_pc.value);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Handle a contiguous address range.
|
||||
if (low_pc.form && high_pc.form) {
|
||||
u64 lo;
|
||||
|
||||
switch (low_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
lo = low_pc.value;
|
||||
break;
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
lo = addrx[low_pc.value];
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "--gdb-index: unhandled form for DW_AT_low_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
|
||||
switch (high_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
return {{lo, high_pc.value}};
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
return {{lo, addrx[high_pc.value]}};
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_data8:
|
||||
return {{lo, lo + high_pc.value}};
|
||||
default:
|
||||
Fatal(ctx) << "--gdb-index: unhandled form for DW_AT_high_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename E, typename PubnamesHdr>
|
||||
static i64 read_pubnames_cu(Context<E> &ctx, const PubnamesHdr &hdr,
|
||||
std::vector<Compunit> &cus, ObjectFile<E> &file) {
|
||||
using Offset = decltype(hdr.size);
|
||||
|
||||
auto get_cu = [&](i64 offset) {
|
||||
for (i64 i = 0; i < cus.size(); i++)
|
||||
if (cus[i].offset == offset)
|
||||
return &cus[i];
|
||||
Fatal(ctx) << file << ": corrupted debug_info_offset";
|
||||
};
|
||||
|
||||
Compunit *cu = get_cu(file.debug_info->offset + hdr.debug_info_offset);
|
||||
i64 size = hdr.size + offsetof(PubnamesHdr, size) + sizeof(hdr.size);
|
||||
u8 *p = (u8 *)&hdr + sizeof(hdr);
|
||||
u8 *end = (u8 *)&hdr + size;
|
||||
|
||||
while (p < end) {
|
||||
if (*(Offset *)p == 0)
|
||||
break;
|
||||
p += sizeof(Offset);
|
||||
|
||||
u8 type = *p++;
|
||||
std::string_view name = (char *)p;
|
||||
p += name.size() + 1;
|
||||
cu->nametypes.push_back({name, gdb_hash(name), type});
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
// Parses .debug_gnu_pubnames and .debug_gnu_pubtypes. These sections
|
||||
// start with a 14 bytes header followed by (4-byte offset, 1-byte type,
|
||||
// null-terminated string) tuples.
|
||||
//
|
||||
// The 4-byte offset is an offset into .debug_info that contains details
|
||||
// about the name. The 1-byte type is a type of the corresponding name
|
||||
// (e.g. function, variable or datatype). The string is a name of a
|
||||
// function, a variable or a type.
|
||||
template <typename E>
|
||||
static void read_pubnames(Context<E> &ctx, std::vector<Compunit> &cus,
|
||||
ObjectFile<E> &file) {
|
||||
for (InputSection<E> *isec : { file.debug_pubnames, file.debug_pubtypes }) {
|
||||
if (!isec)
|
||||
continue;
|
||||
|
||||
isec->uncompress(ctx);
|
||||
if (isec->contents.empty())
|
||||
continue;
|
||||
|
||||
u8 *p = (u8*)&isec->contents[0];
|
||||
u8 *end = p + isec->contents.size();
|
||||
|
||||
while (p < end) {
|
||||
if (*(U32<E> *)p == 0xffff'ffff)
|
||||
p += read_pubnames_cu(ctx, *(PubnamesHdr64<E> *)p, cus, file);
|
||||
else
|
||||
p += read_pubnames_cu(ctx, *(PubnamesHdr32<E> *)p, cus, file);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<Compunit> read_compunits(Context<E> &ctx) {
|
||||
std::vector<Compunit> cus;
|
||||
|
||||
// Read compunits from the output .debug_info section.
|
||||
u8 *begin = &ctx.debug_info[0];
|
||||
u8 *end = begin + ctx.debug_info.size();
|
||||
|
||||
for (u8 *p = begin; p < end;) {
|
||||
DwarfKind kind = get_dwarf_kind(ctx, p);
|
||||
i64 size;
|
||||
if (kind == DWARF2_32 || kind == DWARF5_32)
|
||||
size = ((CuHdrDwarf2_32<E> *)p)->size + 4;
|
||||
else
|
||||
size = ((CuHdrDwarf2_64<E> *)p)->size + 12;
|
||||
|
||||
cus.push_back(Compunit{kind, p - begin, size});
|
||||
p += size;
|
||||
}
|
||||
|
||||
// Read address ranges for each compunit.
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
switch (cu.kind) {
|
||||
case DWARF2_32:
|
||||
cu.ranges = read_address_ranges<E, CuHdrDwarf2_32<E>>(ctx, cu);
|
||||
break;
|
||||
case DWARF5_32:
|
||||
cu.ranges = read_address_ranges<E, CuHdrDwarf5_32<E>>(ctx, cu);
|
||||
break;
|
||||
case DWARF2_64:
|
||||
cu.ranges = read_address_ranges<E, CuHdrDwarf2_64<E>>(ctx, cu);
|
||||
break;
|
||||
case DWARF5_64:
|
||||
cu.ranges = read_address_ranges<E, CuHdrDwarf5_64<E>>(ctx, cu);
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove empty ranges
|
||||
std::erase_if(cu.ranges, [](std::pair<u64, u64> p) {
|
||||
return p.first == 0 || p.first == p.second;
|
||||
});
|
||||
});
|
||||
|
||||
// Read symbols from .debug_gnu_pubnames and .debug_gnu_pubtypes.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
read_pubnames(ctx, cus, *file);
|
||||
});
|
||||
|
||||
// Uniquify elements because GCC 11 seems to emit one record for each
|
||||
// comdat group which results in having a lot of duplicate records.
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
sort(cu.nametypes);
|
||||
remove_duplicates(cu.nametypes);
|
||||
});
|
||||
|
||||
return cus;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::span<u8> get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
if (chunk->is_compressed)
|
||||
return chunk->uncompressed_data;
|
||||
return {ctx.buf + chunk->shdr.sh_offset, (size_t)chunk->shdr.sh_size};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void write_gdb_index(Context<E> &ctx) {
|
||||
Timer t(ctx, "write_gdb_index");
|
||||
|
||||
// Find debug info sections
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
std::string_view name = chunk->name;
|
||||
if (name == ".debug_info")
|
||||
ctx.debug_info = get_buffer(ctx, chunk);
|
||||
if (name == ".debug_abbrev")
|
||||
ctx.debug_abbrev = get_buffer(ctx, chunk);
|
||||
if (name == ".debug_ranges")
|
||||
ctx.debug_ranges = get_buffer(ctx, chunk);
|
||||
if (name == ".debug_addr")
|
||||
ctx.debug_addr = get_buffer(ctx, chunk);
|
||||
if (name == ".debug_rnglists")
|
||||
ctx.debug_rnglists = get_buffer(ctx, chunk);
|
||||
}
|
||||
|
||||
if (ctx.debug_info.empty())
|
||||
return;
|
||||
|
||||
// Read debug info
|
||||
std::vector<Compunit> cus = read_compunits(ctx);
|
||||
|
||||
// Uniquify symbols
|
||||
HyperLogLog estimator;
|
||||
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
HyperLogLog e;
|
||||
for (NameType &nt : cu.nametypes)
|
||||
e.insert(nt.hash);
|
||||
estimator.merge(e);
|
||||
});
|
||||
|
||||
ConcurrentMap<MapValue> map(estimator.get_cardinality() * 3 / 2);
|
||||
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
cu.entries.reserve(cu.nametypes.size());
|
||||
for (NameType &nt : cu.nametypes) {
|
||||
MapValue *ent;
|
||||
bool inserted;
|
||||
std::tie(ent, inserted) = map.insert(nt.name, nt.hash, MapValue{nt.hash});
|
||||
ent->count++;
|
||||
cu.entries.push_back(ent);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort symbols for build reproducibility
|
||||
using Entry = typename decltype(map)::Entry;
|
||||
std::vector<Entry *> entries = map.get_sorted_entries_all();
|
||||
|
||||
// Compute sizes of each components
|
||||
SectionHeader hdr;
|
||||
hdr.cu_list_offset = sizeof(hdr);
|
||||
hdr.cu_types_offset = hdr.cu_list_offset + cus.size() * 16;
|
||||
hdr.ranges_offset = hdr.cu_types_offset;
|
||||
|
||||
hdr.symtab_offset = hdr.ranges_offset;
|
||||
for (Compunit &cu : cus)
|
||||
hdr.symtab_offset += cu.ranges.size() * 20;
|
||||
|
||||
i64 ht_size = bit_ceil(entries.size() * 5 / 4 + 1);
|
||||
hdr.const_pool_offset = hdr.symtab_offset + ht_size * 8;
|
||||
|
||||
i64 offset = 0;
|
||||
for (Entry *ent : entries) {
|
||||
ent->value.type_offset = offset;
|
||||
offset += ent->value.count * 4 + 4;
|
||||
}
|
||||
|
||||
for (Entry *ent : entries) {
|
||||
ent->value.name_offset = offset;
|
||||
offset += ent->keylen + 1;
|
||||
}
|
||||
|
||||
i64 bufsize = hdr.const_pool_offset + offset;
|
||||
|
||||
// Allocate an output buffer
|
||||
ctx.output_file->buf2.resize(bufsize);
|
||||
u8 *buf = ctx.output_file->buf2.data();
|
||||
|
||||
// Write a section header
|
||||
memcpy(buf, &hdr, sizeof(hdr));
|
||||
|
||||
// Write a CU list
|
||||
u8 *p = buf + sizeof(hdr);
|
||||
|
||||
for (Compunit &cu : cus) {
|
||||
*(ul64 *)p = cu.offset;
|
||||
*(ul64 *)(p + 8) = cu.size;
|
||||
p += 16;
|
||||
}
|
||||
|
||||
// Write address areas
|
||||
for (i64 i = 0; i < cus.size(); i++) {
|
||||
for (std::pair<u64, u64> range : cus[i].ranges) {
|
||||
*(ul64 *)p = range.first;
|
||||
*(ul64 *)(p + 8) = range.second;
|
||||
*(ul32 *)(p + 16) = i;
|
||||
p += 20;
|
||||
}
|
||||
}
|
||||
|
||||
// Write a symbol table
|
||||
u32 mask = ht_size - 1;
|
||||
ul32 *ht = (ul32 *)(buf + hdr.symtab_offset);
|
||||
|
||||
for (Entry *ent : entries) {
|
||||
u32 hash = ent->value.hash;
|
||||
u32 step = ((hash * 17) & mask) | 1;
|
||||
u32 j = hash & mask;
|
||||
|
||||
while (ht[j * 2] || ht[j * 2 + 1])
|
||||
j = (j + step) & mask;
|
||||
|
||||
ht[j * 2] = ent->value.name_offset;
|
||||
ht[j * 2 + 1] = ent->value.type_offset;
|
||||
}
|
||||
|
||||
// Write types
|
||||
for (i64 i = 0; i < cus.size(); i++) {
|
||||
Compunit &cu = cus[i];
|
||||
u8 *base = buf + hdr.const_pool_offset;
|
||||
|
||||
for (i64 j = 0; j < cu.nametypes.size(); j++) {
|
||||
ul32 *p = (ul32 *)(base + cu.entries[j]->type_offset);
|
||||
i64 idx = ++p[0];
|
||||
p[idx] = (cu.nametypes[j].type << 24) | i;
|
||||
}
|
||||
}
|
||||
|
||||
// Write names
|
||||
tbb::parallel_for_each(entries, [&](Entry *ent) {
|
||||
memcpy(buf + hdr.const_pool_offset + ent->value.name_offset,
|
||||
ent->key, ent->keylen);
|
||||
});
|
||||
|
||||
// Update the section size and rewrite the section header
|
||||
if (ctx.shdr) {
|
||||
ctx.gdb_index->shdr.sh_size = bufsize;
|
||||
ctx.shdr->copy_buf(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void write_gdb_index(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
+12
-14
@@ -115,21 +115,19 @@ static bool is_eligible(Context<E> &ctx, InputSection<E> &isec) {
|
||||
const ElfShdr<E> &shdr = isec.shdr();
|
||||
std::string_view name = isec.name();
|
||||
|
||||
bool is_alloc = (shdr.sh_flags & SHF_ALLOC);
|
||||
bool is_exec = (shdr.sh_flags & SHF_EXECINSTR) ||
|
||||
ctx.arg.ignore_data_address_equality;
|
||||
bool is_relro = (name == ".data.rel.ro" ||
|
||||
name.starts_with(".data.rel.ro."));
|
||||
bool is_readonly = !(shdr.sh_flags & SHF_WRITE) || is_relro;
|
||||
bool is_bss = (shdr.sh_type == SHT_NOBITS);
|
||||
bool is_empty = (shdr.sh_size == 0);
|
||||
bool is_init = (shdr.sh_type == SHT_INIT_ARRAY || name == ".init");
|
||||
bool is_fini = (shdr.sh_type == SHT_FINI_ARRAY || name == ".fini");
|
||||
bool is_enumerable = is_c_identifier(name);
|
||||
bool is_addr_taken = !ctx.arg.icf_all && isec.address_significant;
|
||||
if (shdr.sh_size == 0 || !(shdr.sh_flags & SHF_ALLOC) ||
|
||||
shdr.sh_type == SHT_NOBITS || is_c_identifier(name))
|
||||
return false;
|
||||
|
||||
return is_alloc && is_exec && is_readonly && !is_bss && !is_empty &&
|
||||
!is_init && !is_fini && !is_enumerable && !is_addr_taken;
|
||||
if (shdr.sh_flags & SHF_EXECINSTR) {
|
||||
return (ctx.arg.icf_all || !isec.address_taken) &&
|
||||
name != ".init" && name != ".fini";
|
||||
} else {
|
||||
bool is_readonly = !(shdr.sh_flags & SHF_WRITE);
|
||||
bool is_relro = isec.output_section && isec.output_section->is_relro;
|
||||
return (ctx.arg.ignore_data_address_equality || !isec.address_taken) &&
|
||||
(is_readonly || is_relro);
|
||||
}
|
||||
}
|
||||
|
||||
static Digest digest_final(blake3_hasher *hasher) {
|
||||
|
||||
+31
-64
@@ -63,7 +63,7 @@ void InputFile<E>::clear_symbols() {
|
||||
sym->origin = 0;
|
||||
sym->value = -1;
|
||||
sym->sym_idx = -1;
|
||||
sym->ver_idx = 0;
|
||||
sym->ver_idx = VER_NDX_UNSPECIFIED;
|
||||
sym->is_weak = false;
|
||||
sym->is_imported = false;
|
||||
sym->is_exported = false;
|
||||
@@ -249,15 +249,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
break;
|
||||
}
|
||||
case SHT_REL:
|
||||
if constexpr (E::is_rela)
|
||||
Fatal(ctx) << *this << ": REL-type relocation table is not supported"
|
||||
<< " for this target";
|
||||
break;
|
||||
case SHT_RELA:
|
||||
if constexpr (!E::is_rela)
|
||||
Fatal(ctx) << *this <<": RELA-type relocation table is not supported"
|
||||
<< " for this target";
|
||||
break;
|
||||
case SHT_SYMTAB:
|
||||
case SHT_SYMTAB_SHNDX:
|
||||
case SHT_STRTAB:
|
||||
@@ -314,7 +306,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
if (ctx.arg.oformat_binary && !(shdr.sh_flags & SHF_ALLOC))
|
||||
continue;
|
||||
|
||||
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, name, i);
|
||||
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, i);
|
||||
|
||||
// Save .llvm_addrsig for --icf=safe.
|
||||
if (shdr.sh_type == SHT_LLVM_ADDRSIG && !ctx.arg.relocatable) {
|
||||
@@ -322,6 +314,15 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shdr.sh_type == SHT_INIT_ARRAY ||
|
||||
shdr.sh_type == SHT_FINI_ARRAY ||
|
||||
shdr.sh_type == SHT_PREINIT_ARRAY)
|
||||
ctx.has_init_array = true;
|
||||
|
||||
if (name == ".ctors" || name.starts_with(".ctors.") ||
|
||||
name == ".dtors" || name.starts_with(".dtors."))
|
||||
ctx.has_ctors = true;
|
||||
|
||||
if (name == ".eh_frame")
|
||||
eh_frame_section = this->sections[i].get();
|
||||
|
||||
@@ -335,10 +336,6 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
|
||||
if (name == ".debug_info")
|
||||
debug_info = isec;
|
||||
if (name == ".debug_ranges")
|
||||
debug_ranges = isec;
|
||||
if (name == ".debug_rnglists")
|
||||
debug_rnglists = isec;
|
||||
|
||||
// If --gdb-index is given, contents of .debug_gnu_pubnames and
|
||||
// .debug_gnu_pubtypes are copied to .gdb_index, so keeping them
|
||||
@@ -649,9 +646,20 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
if (!(shdr.sh_flags & SHF_MERGE))
|
||||
return nullptr;
|
||||
|
||||
i64 entsize = shdr.sh_entsize;
|
||||
if (entsize == 0)
|
||||
entsize = (shdr.sh_flags & SHF_STRINGS) ? 1 : (int)shdr.sh_addralign;
|
||||
|
||||
if (entsize == 0)
|
||||
return nullptr;
|
||||
|
||||
i64 addralign = shdr.sh_addralign;
|
||||
if (addralign == 0)
|
||||
addralign = 1;
|
||||
|
||||
std::unique_ptr<MergeableSection<E>> rec(new MergeableSection<E>);
|
||||
rec->parent = MergedSection<E>::get_instance(ctx, sec.name(), shdr.sh_type,
|
||||
shdr.sh_flags);
|
||||
shdr.sh_flags, entsize, addralign);
|
||||
rec->p2align = sec.p2align;
|
||||
|
||||
if (sec.sh_size == 0)
|
||||
@@ -662,18 +670,10 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
|
||||
std::string_view data = sec.contents;
|
||||
const char *begin = data.data();
|
||||
u64 entsize = shdr.sh_entsize;
|
||||
HyperLogLog estimator;
|
||||
|
||||
// Split sections
|
||||
if (shdr.sh_flags & SHF_STRINGS) {
|
||||
if (entsize == 0) {
|
||||
// GHC (Glasgow Haskell Compiler) sometimes creates a mergeable
|
||||
// string section with entsize of 0 instead of 1, though such
|
||||
// entsize is technically wrong. This is a workaround for the issue.
|
||||
entsize = 1;
|
||||
}
|
||||
|
||||
while (!data.empty()) {
|
||||
size_t end = find_null(data, entsize);
|
||||
if (end == data.npos)
|
||||
@@ -690,11 +690,6 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
estimator.insert(hash);
|
||||
}
|
||||
} else {
|
||||
// OCaml compiler seems to create a mergeable non-string section with
|
||||
// entisze of 0. Such section is malformed. We do not split such section.
|
||||
if (entsize == 0)
|
||||
return nullptr;
|
||||
|
||||
if (data.size() % entsize)
|
||||
Fatal(ctx) << sec << ": section size is not multiple of sh_entsize";
|
||||
|
||||
@@ -866,33 +861,6 @@ void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
|
||||
this->symbols.push_back(&sym);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::mark_addrsig(Context<E> &ctx) {
|
||||
// Parse a .llvm_addrsig section.
|
||||
if (llvm_addrsig) {
|
||||
u8 *cur = (u8 *)llvm_addrsig->contents.data();
|
||||
u8 *end = cur + llvm_addrsig->contents.size();
|
||||
|
||||
while (cur != end) {
|
||||
Symbol<E> &sym = *this->symbols[read_uleb(&cur)];
|
||||
if (sym.file == this)
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
isec->address_significant = true;
|
||||
}
|
||||
}
|
||||
|
||||
// We treat a symbol's address as significant if
|
||||
//
|
||||
// 1. we have no address significance information for the symbol, or
|
||||
// 2. the symbol can be referenced from the outside in an address-
|
||||
// significant manner.
|
||||
for (Symbol<E> *sym : this->symbols)
|
||||
if (sym->file == this)
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
if (!llvm_addrsig || sym->is_exported)
|
||||
isec->address_significant = true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
sections.resize(this->elf_sections.size());
|
||||
@@ -1124,13 +1092,9 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
ElfShdr<E> &shdr = elf_sections2.back();
|
||||
memset(&shdr, 0, sizeof(shdr));
|
||||
|
||||
std::string_view name;
|
||||
|
||||
if (sym.get_type() == STT_TLS) {
|
||||
name = ".tls_common";
|
||||
shdr.sh_flags = SHF_ALLOC | SHF_WRITE | SHF_TLS;
|
||||
} else {
|
||||
name = ".common";
|
||||
shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
|
||||
}
|
||||
|
||||
@@ -1140,7 +1104,7 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
|
||||
i64 idx = this->elf_sections.size() + elf_sections2.size() - 1;
|
||||
std::unique_ptr<InputSection<E>> isec =
|
||||
std::make_unique<InputSection<E>>(ctx, *this, name, idx);
|
||||
std::make_unique<InputSection<E>>(ctx, *this, idx);
|
||||
|
||||
sym.file = this;
|
||||
sym.set_input_section(isec.get());
|
||||
@@ -1326,17 +1290,17 @@ void SharedFile<E>::parse(Context<E> &ctx) {
|
||||
continue;
|
||||
|
||||
std::string_view name = this->symbol_strtab.data() + esyms[i].st_name;
|
||||
bool is_hidden = (!vers.empty() && (vers[i] & VERSYM_HIDDEN));
|
||||
bool is_default = vers.empty() || !(vers[i] & VERSYM_HIDDEN);
|
||||
|
||||
this->elf_syms2.push_back(esyms[i]);
|
||||
this->versyms.push_back(ver);
|
||||
|
||||
if (is_hidden) {
|
||||
if (is_default) {
|
||||
this->symbols.push_back(get_symbol(ctx, name));
|
||||
} else {
|
||||
std::string_view mangled_name = save_string(
|
||||
ctx, std::string(name) + "@" + std::string(version_strings[ver]));
|
||||
this->symbols.push_back(get_symbol(ctx, mangled_name, name));
|
||||
} else {
|
||||
this->symbols.push_back(get_symbol(ctx, name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1391,6 +1355,9 @@ std::vector<std::string_view> SharedFile<E>::read_verdef(Context<E> &ctx) {
|
||||
ElfVerdef<E> *ver = (ElfVerdef<E> *)verdef.data();
|
||||
|
||||
for (;;) {
|
||||
if (ver->vd_ndx == VER_NDX_UNSPECIFIED)
|
||||
Fatal(ctx) << *this << ": symbol version too large";
|
||||
|
||||
if (ret.size() <= ver->vd_ndx)
|
||||
ret.resize(ver->vd_ndx + 1);
|
||||
|
||||
|
||||
+20
-8
@@ -7,7 +7,8 @@
|
||||
namespace mold::elf {
|
||||
|
||||
typedef enum {
|
||||
NONE, ERROR, COPYREL, DYN_COPYREL, PLT, CPLT, DYN_CPLT, DYNREL, BASEREL, IFUNC,
|
||||
NONE, ERROR, COPYREL, DYN_COPYREL, PLT, CPLT, DYN_CPLT, DYNREL,
|
||||
BASEREL, IFUNC_DYNREL,
|
||||
} Action;
|
||||
|
||||
template <typename E>
|
||||
@@ -37,8 +38,7 @@ static i64 to_p2align(u64 alignment) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
std::string_view name, i64 shndx)
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx)
|
||||
: file(file), shndx(shndx) {
|
||||
if (shndx < file.elf_sections.size())
|
||||
contents = {(char *)file.mf->data + shdr().sh_offset, (size_t)shdr().sh_size};
|
||||
@@ -204,7 +204,7 @@ static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
|
||||
if (!isec.is_relr_reloc(ctx, rel))
|
||||
isec.file.num_dynrel++;
|
||||
break;
|
||||
case IFUNC:
|
||||
case IFUNC_DYNREL:
|
||||
// Create an IRELATIVE relocation for a GNU ifunc symbol.
|
||||
//
|
||||
// We usually create an IRELATIVE relocation in .got for each ifunc.
|
||||
@@ -274,7 +274,7 @@ static Action get_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
template <typename E>
|
||||
static Action get_dyn_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (sym.is_ifunc())
|
||||
return IFUNC;
|
||||
return sym.is_pde_ifunc(ctx) ? NONE : IFUNC_DYNREL;
|
||||
|
||||
// This is a decision table for absolute relocations for the pointer
|
||||
// size data (e.g. R_X86_64_64). Unlike the absrel_table, we can emit
|
||||
@@ -292,7 +292,7 @@ static Action get_dyn_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
template <typename E>
|
||||
static Action get_ppc64_toc_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (sym.is_ifunc())
|
||||
return IFUNC;
|
||||
return IFUNC_DYNREL;
|
||||
|
||||
// As a special case, we do not create copy relocations nor canonical
|
||||
// PLTs for .toc sections. PPC64's .toc is a compiler-generated
|
||||
@@ -372,7 +372,7 @@ static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
|
||||
bool writable = (isec.shdr().sh_flags & SHF_WRITE);
|
||||
|
||||
auto emit_abs_dynrel = [&] {
|
||||
*dynrel++ = ElfRel<E>(P, E::R_DYNAMIC, sym.get_dynsym_idx(ctx), A);
|
||||
*dynrel++ = ElfRel<E>(P, E::R_ABS, sym.get_dynsym_idx(ctx), A);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = A;
|
||||
};
|
||||
@@ -407,7 +407,7 @@ static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
|
||||
case DYNREL:
|
||||
emit_abs_dynrel();
|
||||
break;
|
||||
case IFUNC:
|
||||
case IFUNC_DYNREL:
|
||||
if constexpr (supports_ifunc<E>) {
|
||||
u64 addr = sym.get_addr(ctx, NO_PLT) + A;
|
||||
*dynrel++ = ElfRel<E>(P, E::R_IRELATIVE, 0, addr);
|
||||
@@ -457,6 +457,18 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
apply_reloc_alloc(ctx, buf);
|
||||
else
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
|
||||
if constexpr (is_x86_64<E>) {
|
||||
u8 endbr[] = {0xf3, 0x0f, 0x1e, 0xfa};
|
||||
u8 nop[] = {0x0f, 0x1f, 0x40, 0x00};
|
||||
|
||||
// Rewrite the leading endbr instruction with a nop if the section
|
||||
// is not address-taken.
|
||||
if (ctx.arg.z_rewrite_endbr && (shdr().sh_flags & SHF_EXECINSTR) &&
|
||||
!address_taken && sh_size >= 4 && memcmp(buf, endbr, 4) == 0) {
|
||||
memcpy(buf, nop, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-5
@@ -1,3 +1,14 @@
|
||||
// Many build systems attempt to invoke as many linker processes as there
|
||||
// are cores, based on the assumption that the linker is single-threaded.
|
||||
// However, since mold is multi-threaded, such build systems' behavior is
|
||||
// not beneficial and just increases the overall peak memory usage.
|
||||
// On machines with limited memory, this could lead to an out-of-memory
|
||||
// error.
|
||||
//
|
||||
// This file implements a feature that limits the number of concurrent
|
||||
// mold processes to just 1 for each user. It is intended to be used as
|
||||
// `MOLD_JOBS=1 ninja` or `MOLD_JOBS=1 make -j$(nproc)`.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
@@ -14,14 +25,15 @@ template <typename E>
|
||||
void acquire_global_lock(Context<E> &ctx) {
|
||||
#ifndef _WIN32
|
||||
char *jobs = getenv("MOLD_JOBS");
|
||||
if (!jobs || std::string(jobs) != "1")
|
||||
if (!jobs || jobs != "1"s)
|
||||
return;
|
||||
|
||||
char *home = getenv("HOME");
|
||||
if (!home)
|
||||
home = getpwuid(getuid())->pw_dir;
|
||||
std::string path;
|
||||
if (char *dir = getenv("XDG_RUNTIME_DIR"))
|
||||
path = dir + "/mold-lock"s;
|
||||
else
|
||||
path = "/tmp/mold-lock-"s + getpwuid(getuid())->pw_name;
|
||||
|
||||
std::string path = std::string(home) + "/.mold-lock";
|
||||
int fd = open(path.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0600);
|
||||
if (fd == -1)
|
||||
return;
|
||||
|
||||
+20
-15
@@ -312,7 +312,6 @@ read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
|
||||
if (tok[0] == "*") {
|
||||
ctx.default_version = (is_global ? ver_idx : (u32)VER_NDX_LOCAL);
|
||||
ctx.default_version_from_version_script = true;
|
||||
} else if (is_global) {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
ver_str, ver_idx, is_cpp});
|
||||
@@ -367,7 +366,9 @@ void parse_version_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
void read_dynamic_list_commands(Context<E> &ctx,
|
||||
std::vector<DynamicPattern> &result,
|
||||
std::span<std::string_view> &tok,
|
||||
bool is_cpp) {
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
if (tok[0] == "extern") {
|
||||
@@ -376,11 +377,11 @@ void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &to
|
||||
if (!tok.empty() && tok[0] == "\"C\"") {
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, false);
|
||||
read_dynamic_list_commands(ctx, result, tok, false);
|
||||
} else {
|
||||
tok = skip(ctx, tok, "\"C++\"");
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, true);
|
||||
read_dynamic_list_commands(ctx, result, tok, true);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "}");
|
||||
@@ -388,29 +389,32 @@ void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &to
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tok[0] == "*")
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
"global", VER_NDX_GLOBAL, is_cpp});
|
||||
|
||||
result.push_back({unquote(tok[0]), "", is_cpp});
|
||||
tok = skip(ctx, tok.subspan(1), ";");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_dynamic_list(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::vector<DynamicPattern>
|
||||
parse_dynamic_list(Context<E> &ctx, std::string_view path) {
|
||||
std::string_view contents =
|
||||
MappedFile<Context<E>>::must_open(ctx, std::string(path))->get_contents();
|
||||
std::vector<std::string_view> vec = tokenize(ctx, contents);
|
||||
std::span<std::string_view> tok = vec;
|
||||
std::vector<DynamicPattern> result;
|
||||
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, false);
|
||||
read_dynamic_list_commands(ctx, result, tok, false);
|
||||
tok = skip(ctx, tok, "}");
|
||||
tok = skip(ctx, tok, ";");
|
||||
|
||||
if (!tok.empty())
|
||||
SyntaxError(ctx, tok[0]) << "trailing garbage token";
|
||||
|
||||
for (DynamicPattern &p : result)
|
||||
p.source = path;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
@@ -418,6 +422,7 @@ using E = MOLD_TARGET;
|
||||
template void parse_linker_script(Context<E> &, MappedFile<Context<E>> *);
|
||||
template std::string_view get_script_output_type(Context<E> &, MappedFile<Context<E>> *);
|
||||
template void parse_version_script(Context<E> &, MappedFile<Context<E>> *);
|
||||
template void parse_dynamic_list(Context<E> &, MappedFile<Context<E>> *);
|
||||
template std::vector<DynamicPattern> parse_dynamic_list(Context<E> &, std::string_view);
|
||||
|
||||
|
||||
} // namespace mold::elf
|
||||
+3
-2
@@ -709,8 +709,9 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
get_symbol(ctx, y)->referenced_by_regular_obj = true;
|
||||
}
|
||||
|
||||
// Handle --entry
|
||||
get_symbol(ctx, ctx.arg.entry)->referenced_by_regular_obj = true;
|
||||
// Keep some symbols
|
||||
for (Symbol<E> *sym : ctx.arg.undefined)
|
||||
sym->referenced_by_regular_obj = true;
|
||||
|
||||
// all_symbols_read_hook() calls add_input_file() and add_input_library()
|
||||
LOG << "all symbols read\n";
|
||||
|
||||
+21
-98
@@ -191,7 +191,8 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ctx.visited.insert(mf->name);
|
||||
if (!ctx.whole_archive)
|
||||
ctx.visited.insert(mf->name);
|
||||
return;
|
||||
case FileType::TEXT:
|
||||
parse_linker_script(ctx, mf);
|
||||
@@ -298,22 +299,6 @@ static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--version-script: file not found: " << arg;
|
||||
parse_version_script(ctx, mf);
|
||||
} else if (remove_prefix(arg, "--dynamic-list=")) {
|
||||
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--dynamic-list: file not found: " << arg;
|
||||
parse_dynamic_list(ctx, mf);
|
||||
} else if (remove_prefix(arg, "--export-dynamic-symbol=")) {
|
||||
if (arg == "*")
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.version_patterns.push_back({arg, "--export-dynamic-symbol",
|
||||
"global", VER_NDX_GLOBAL, false});
|
||||
} else if (remove_prefix(arg, "--export-dynamic-symbol-list=")) {
|
||||
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--export-dynamic-symbol-list: file not found: " << arg;
|
||||
parse_dynamic_list(ctx, mf);
|
||||
} else if (arg == "--push-state") {
|
||||
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static,
|
||||
ctx.in_lib});
|
||||
@@ -338,47 +323,6 @@ static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
ctx.tg.wait();
|
||||
}
|
||||
|
||||
// Since elf_main is a template, we can't run it without a type parameter.
|
||||
// We speculatively run elf_main with X86_64, and if the speculation was
|
||||
// wrong, re-run it with an actual machine type.
|
||||
static int redo_main(int argc, char **argv, std::string_view target) {
|
||||
if (target == I386::target_name)
|
||||
return elf_main<I386>(argc, argv);
|
||||
if (target == ARM64::target_name)
|
||||
return elf_main<ARM64>(argc, argv);
|
||||
if (target == ARM32::target_name)
|
||||
return elf_main<ARM32>(argc, argv);
|
||||
if (target == RV64LE::target_name)
|
||||
return elf_main<RV64LE>(argc, argv);
|
||||
if (target == RV64BE::target_name)
|
||||
return elf_main<RV64BE>(argc, argv);
|
||||
if (target == RV32LE::target_name)
|
||||
return elf_main<RV32LE>(argc, argv);
|
||||
if (target == RV32BE::target_name)
|
||||
return elf_main<RV32BE>(argc, argv);
|
||||
if (target == PPC32::target_name)
|
||||
return elf_main<PPC32>(argc, argv);
|
||||
if (target == PPC64V1::target_name)
|
||||
return elf_main<PPC64V1>(argc, argv);
|
||||
if (target == PPC64V2::target_name)
|
||||
return elf_main<PPC64V2>(argc, argv);
|
||||
if (target == S390X::target_name)
|
||||
return elf_main<S390X>(argc, argv);
|
||||
if (target == SPARC64::target_name)
|
||||
return elf_main<SPARC64>(argc, argv);
|
||||
if (target == M68K::target_name)
|
||||
return elf_main<M68K>(argc, argv);
|
||||
if (target == SH4::target_name)
|
||||
return elf_main<SH4>(argc, argv);
|
||||
if (target == ALPHA::target_name)
|
||||
return elf_main<ALPHA>(argc, argv);
|
||||
if (target == LOONGARCH32::target_name)
|
||||
return elf_main<LOONGARCH32>(argc, argv);
|
||||
if (target == LOONGARCH64::target_name)
|
||||
return elf_main<LOONGARCH64>(argc, argv);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
int elf_main(int argc, char **argv) {
|
||||
Context<E> ctx;
|
||||
@@ -402,7 +346,7 @@ int elf_main(int argc, char **argv) {
|
||||
// Redo if -m is not x86-64.
|
||||
if constexpr (is_x86_64<E>)
|
||||
if (ctx.arg.emulation != X86_64::target_name)
|
||||
return redo_main(argc, argv, ctx.arg.emulation);
|
||||
return redo_main(ctx, argc, argv);
|
||||
|
||||
Timer t_all(ctx, "all");
|
||||
|
||||
@@ -496,9 +440,9 @@ int elf_main(int argc, char **argv) {
|
||||
// Set is_imported and is_exported bits for each symbol.
|
||||
compute_import_export(ctx);
|
||||
|
||||
// Read address-significant section information.
|
||||
if (ctx.arg.icf && !ctx.arg.icf_all)
|
||||
mark_addrsig(ctx);
|
||||
// Set "address-taken" bits for input sections.
|
||||
if (ctx.arg.icf || ctx.arg.z_rewrite_endbr)
|
||||
compute_address_significance(ctx);
|
||||
|
||||
// Garbage-collect unreachable sections.
|
||||
if (ctx.arg.gc_sections)
|
||||
@@ -563,9 +507,9 @@ int elf_main(int argc, char **argv) {
|
||||
write_repro_file(ctx);
|
||||
|
||||
// Handle --require-defined
|
||||
for (std::string_view name : ctx.arg.require_defined)
|
||||
if (!get_symbol(ctx, name)->file)
|
||||
Error(ctx) << "--require-defined: undefined symbol: " << name;
|
||||
for (Symbol<E> *sym : ctx.arg.require_defined)
|
||||
if (!sym->file)
|
||||
Error(ctx) << "--require-defined: undefined symbol: " << *sym;
|
||||
|
||||
// .init_array and .fini_array contents have to be sorted by
|
||||
// a special rule. Sort them.
|
||||
@@ -575,6 +519,11 @@ int elf_main(int argc, char **argv) {
|
||||
// because they are superceded by .init_array/.fini_array, though.
|
||||
sort_ctor_dtor(ctx);
|
||||
|
||||
// If .ctors/.dtors are to be placed to .init_array/.fini_array,
|
||||
// we need to reverse their contents.
|
||||
if (ctx.has_init_array && ctx.has_ctors)
|
||||
fixup_ctors_in_init_array(ctx);
|
||||
|
||||
// Handle --shuffle-sections
|
||||
if (ctx.arg.shuffle_sections != SHUFFLE_SECTIONS_NONE)
|
||||
shuffle_sections(ctx);
|
||||
@@ -637,10 +586,6 @@ int elf_main(int argc, char **argv) {
|
||||
// Here, we construct output .eh_frame contents.
|
||||
ctx.eh_frame->construct(ctx);
|
||||
|
||||
// Handle --gdb-index.
|
||||
if (ctx.arg.gdb_index)
|
||||
ctx.gdb_index->construct(ctx);
|
||||
|
||||
// If --emit-relocs is given, we'll copy relocation sections from input
|
||||
// files to an output file.
|
||||
if (ctx.arg.emit_relocs)
|
||||
@@ -686,12 +631,6 @@ int elf_main(int argc, char **argv) {
|
||||
// Copy input sections to the output file and apply relocations.
|
||||
copy_chunks(ctx);
|
||||
|
||||
// Some part of .gdb_index couldn't be computed until other debug
|
||||
// sections are complete. We have complete debug sections now, so
|
||||
// write the rest of .gdb_index.
|
||||
if (ctx.gdb_index)
|
||||
ctx.gdb_index->write_address_areas(ctx);
|
||||
|
||||
// Dynamic linker works better with sorted .rela.dyn section,
|
||||
// so we sort them.
|
||||
ctx.reldyn->sort(ctx);
|
||||
@@ -699,6 +638,12 @@ int elf_main(int argc, char **argv) {
|
||||
// Zero-clear paddings between sections
|
||||
clear_padding(ctx);
|
||||
|
||||
// .gdb_index's contents cannot be constructed before applying
|
||||
// relocations to other debug sections. We have relocated debug
|
||||
// sections now, so write the .gdb_index section.
|
||||
if (ctx.gdb_index)
|
||||
write_gdb_index(ctx);
|
||||
|
||||
// .note.gnu.build-id section contains a cryptographic hash of the
|
||||
// entire output file. Now that we wrote everything except build-id,
|
||||
// we can compute it.
|
||||
@@ -747,35 +692,13 @@ int elf_main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
#ifdef MOLD_X86_64
|
||||
|
||||
extern template int elf_main<I386>(int, char **);
|
||||
extern template int elf_main<ARM32>(int, char **);
|
||||
extern template int elf_main<ARM64>(int, char **);
|
||||
extern template int elf_main<RV32BE>(int, char **);
|
||||
extern template int elf_main<RV32LE>(int, char **);
|
||||
extern template int elf_main<RV64LE>(int, char **);
|
||||
extern template int elf_main<RV64BE>(int, char **);
|
||||
extern template int elf_main<PPC32>(int, char **);
|
||||
extern template int elf_main<PPC64V1>(int, char **);
|
||||
extern template int elf_main<PPC64V2>(int, char **);
|
||||
extern template int elf_main<S390X>(int, char **);
|
||||
extern template int elf_main<SPARC64>(int, char **);
|
||||
extern template int elf_main<M68K>(int, char **);
|
||||
extern template int elf_main<SH4>(int, char **);
|
||||
extern template int elf_main<ALPHA>(int, char **);
|
||||
extern template int elf_main<LOONGARCH32>(int, char **);
|
||||
extern template int elf_main<LOONGARCH64>(int, char **);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
return elf_main<X86_64>(argc, argv);
|
||||
}
|
||||
|
||||
#else
|
||||
#endif
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template int elf_main<E>(int, char **);
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace mold::elf
|
||||
+72
-91
@@ -224,8 +224,7 @@ struct InputSectionExtras<E> {
|
||||
template <typename E>
|
||||
class InputSection {
|
||||
public:
|
||||
InputSection(Context<E> &ctx, ObjectFile<E> &file, std::string_view name,
|
||||
i64 shndx);
|
||||
InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx);
|
||||
|
||||
void uncompress(Context<E> &ctx);
|
||||
void uncompress_to(Context<E> &ctx, u8 *buf);
|
||||
@@ -258,17 +257,19 @@ public:
|
||||
i32 fde_begin = -1;
|
||||
i32 fde_end = -1;
|
||||
|
||||
u32 offset = -1;
|
||||
u64 offset = -1;
|
||||
u32 shndx = -1;
|
||||
u32 relsec_idx = -1;
|
||||
u32 reldyn_offset = 0;
|
||||
|
||||
bool uncompressed = false;
|
||||
|
||||
// For COMDAT de-duplication and garbage collection
|
||||
std::atomic_bool is_alive = true;
|
||||
u8 p2align = 0;
|
||||
|
||||
bool address_significant : 1 = false;
|
||||
bool uncompressed : 1 = false;
|
||||
// For ICF
|
||||
Atomic<bool> address_taken = false;
|
||||
|
||||
// For garbage collection
|
||||
Atomic<bool> is_visited = false;
|
||||
@@ -354,14 +355,15 @@ public:
|
||||
virtual void write_to(Context<E> &ctx, u8 *buf) { unreachable(); }
|
||||
virtual void update_shdr(Context<E> &ctx) {}
|
||||
|
||||
// For --gdb-index
|
||||
virtual u8 *get_uncompressed_data() { return nullptr; }
|
||||
|
||||
std::string_view name;
|
||||
ElfShdr<E> shdr = { .sh_addralign = 1 };
|
||||
i64 shndx = 0;
|
||||
bool is_relro = false;
|
||||
|
||||
// For --gdb-index
|
||||
bool is_compressed = false;
|
||||
std::vector<u8> uncompressed_data;
|
||||
|
||||
// Some synethetic sections add local symbols to the output.
|
||||
// For example, range extension thunks adds function_name@thunk
|
||||
// symbol for each thunk entry. The following members are used
|
||||
@@ -620,6 +622,12 @@ public:
|
||||
}
|
||||
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
// Offsets in .strtab for ARM32 mapping symbols
|
||||
static constexpr i64 ARM = 1;
|
||||
static constexpr i64 THUMB = 4;
|
||||
static constexpr i64 DATA = 7;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -759,7 +767,8 @@ template <typename E>
|
||||
class MergedSection : public Chunk<E> {
|
||||
public:
|
||||
static MergedSection<E> *
|
||||
get_instance(Context<E> &ctx, std::string_view name, u64 type, u64 flags);
|
||||
get_instance(Context<E> &ctx, std::string_view name, i64 type, i64 flags,
|
||||
i64 entsize, i64 addralign);
|
||||
|
||||
SectionFragment<E> *insert(Context<E> &ctx, std::string_view data,
|
||||
u64 hash, i64 p2align);
|
||||
@@ -772,7 +781,7 @@ public:
|
||||
HyperLogLog estimator;
|
||||
|
||||
private:
|
||||
MergedSection(std::string_view name, u64 flags, u32 type);
|
||||
MergedSection(std::string_view name, i64 flags, i64 type, i64 entsize);
|
||||
|
||||
ConcurrentMap<SectionFragment<E>> map;
|
||||
std::vector<i64> shard_offsets;
|
||||
@@ -948,13 +957,6 @@ private:
|
||||
std::map<u32, u32> properties;
|
||||
};
|
||||
|
||||
struct GdbIndexName {
|
||||
std::string_view name;
|
||||
u32 hash = 0;
|
||||
u32 attr = 0;
|
||||
u32 entry_idx = 0;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class GdbIndexSection : public Chunk<E> {
|
||||
public:
|
||||
@@ -963,38 +965,6 @@ public:
|
||||
this->shdr.sh_type = SHT_PROGBITS;
|
||||
this->shdr.sh_addralign = 4;
|
||||
}
|
||||
|
||||
void construct(Context<E> &ctx);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
void write_address_areas(Context<E> &ctx);
|
||||
|
||||
private:
|
||||
struct SectionHeader {
|
||||
ul32 version = 7;
|
||||
ul32 cu_list_offset = 0;
|
||||
ul32 cu_types_offset = 0;
|
||||
ul32 areas_offset = 0;
|
||||
ul32 symtab_offset = 0;
|
||||
ul32 const_pool_offset = 0;
|
||||
};
|
||||
|
||||
struct MapEntry {
|
||||
MapEntry(ObjectFile<E> *owner, u32 hash) : owner(owner), hash(hash) {}
|
||||
|
||||
MapEntry(const MapEntry &other)
|
||||
: owner(other.owner.load()), num_attrs(other.num_attrs.load()),
|
||||
hash(other.hash), name_offset(other.name_offset),
|
||||
attr_offset(other.attr_offset) {}
|
||||
|
||||
std::atomic<ObjectFile<E> *> owner;
|
||||
std::atomic_uint32_t num_attrs = 0;
|
||||
u32 hash = 0;
|
||||
u32 name_offset = -1;
|
||||
u32 attr_offset = -1;
|
||||
};
|
||||
|
||||
SectionHeader header;
|
||||
ConcurrentMap<MapEntry> map;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -1002,12 +972,10 @@ class CompressedSection : public Chunk<E> {
|
||||
public:
|
||||
CompressedSection(Context<E> &ctx, Chunk<E> &chunk);
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
u8 *get_uncompressed_data() override { return uncompressed.get(); }
|
||||
|
||||
private:
|
||||
ElfChdr<E> chdr = {};
|
||||
std::unique_ptr<Compressor> compressed;
|
||||
std::unique_ptr<u8[]> uncompressed;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -1059,22 +1027,10 @@ private:
|
||||
};
|
||||
|
||||
//
|
||||
// dwarf.cc
|
||||
// gdb-index.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
std::vector<std::string_view>
|
||||
read_compunits(Context<E> &ctx, ObjectFile<E> &file);
|
||||
|
||||
template <typename E>
|
||||
std::vector<GdbIndexName> read_pubnames(Context<E> &ctx, ObjectFile<E> &file);
|
||||
|
||||
template <typename E>
|
||||
i64 estimate_address_areas(Context<E> &ctx, ObjectFile<E> &file);
|
||||
|
||||
template <typename E>
|
||||
std::vector<u64>
|
||||
read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset);
|
||||
template <typename E> void write_gdb_index(Context<E> &ctx);
|
||||
|
||||
//
|
||||
// input-files.cc
|
||||
@@ -1210,7 +1166,6 @@ public:
|
||||
void mark_live_objects(Context<E> &ctx,
|
||||
std::function<void(InputFile<E> *)> feeder) override;
|
||||
void convert_undefined_weak_symbols(Context<E> &ctx);
|
||||
void mark_addrsig(Context<E> &ctx);
|
||||
void scan_relocations(Context<E> &ctx);
|
||||
void convert_common_symbols(Context<E> &ctx);
|
||||
void compute_symtab_size(Context<E> &ctx);
|
||||
@@ -1246,19 +1201,8 @@ public:
|
||||
|
||||
// For .gdb_index
|
||||
InputSection<E> *debug_info = nullptr;
|
||||
InputSection<E> *debug_ranges = nullptr;
|
||||
InputSection<E> *debug_rnglists = nullptr;
|
||||
InputSection<E> *debug_pubnames = nullptr;
|
||||
InputSection<E> *debug_pubtypes = nullptr;
|
||||
std::vector<std::string_view> compunits;
|
||||
std::vector<GdbIndexName> gdb_names;
|
||||
i64 compunits_idx = 0;
|
||||
i64 attrs_size = 0;
|
||||
i64 attrs_offset = 0;
|
||||
i64 names_size = 0;
|
||||
i64 names_offset = 0;
|
||||
i64 num_areas = 0;
|
||||
i64 area_offset = 0;
|
||||
|
||||
// For LTO
|
||||
std::vector<std::string_view> lto_symbol_versions;
|
||||
@@ -1337,8 +1281,15 @@ get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf);
|
||||
template <typename E>
|
||||
void parse_version_script(Context<E> &ctx, MappedFile<Context<E>> *mf);
|
||||
|
||||
struct DynamicPattern {
|
||||
std::string_view pattern;
|
||||
std::string_view source;
|
||||
bool is_cpp = false;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void parse_dynamic_list(Context<E> &ctx, MappedFile<Context<E>> *mf);
|
||||
std::vector<DynamicPattern>
|
||||
parse_dynamic_list(Context<E> &ctx, std::string_view path);
|
||||
|
||||
//
|
||||
// lto.cc
|
||||
@@ -1409,6 +1360,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx);
|
||||
// passes.cc
|
||||
//
|
||||
|
||||
template <typename E> int redo_main(Context<E> &, int argc, char **argv);
|
||||
template <typename E> void create_internal_file(Context<E> &);
|
||||
template <typename E> void apply_exclude_libs(Context<E> &);
|
||||
template <typename E> void create_synthetic_sections(Context<E> &);
|
||||
@@ -1427,6 +1379,7 @@ template <typename E> void check_duplicate_symbols(Context<E> &);
|
||||
template <typename E> void check_symbol_types(Context<E> &);
|
||||
template <typename E> void sort_init_fini(Context<E> &);
|
||||
template <typename E> void sort_ctor_dtor(Context<E> &);
|
||||
template <typename E> void fixup_ctors_in_init_array(Context<E> &);
|
||||
template <typename E> void shuffle_sections(Context<E> &);
|
||||
template <typename E> void compute_section_sizes(Context<E> &);
|
||||
template <typename E> void sort_output_sections(Context<E> &);
|
||||
@@ -1440,7 +1393,7 @@ template <typename E> void copy_chunks(Context<E> &);
|
||||
template <typename E> void apply_version_script(Context<E> &);
|
||||
template <typename E> void parse_symbol_version(Context<E> &);
|
||||
template <typename E> void compute_import_export(Context<E> &);
|
||||
template <typename E> void mark_addrsig(Context<E> &);
|
||||
template <typename E> void compute_address_significance(Context<E> &);
|
||||
template <typename E> void clear_padding(Context<E> &);
|
||||
template <typename E> void compute_section_headers(Context<E> &);
|
||||
template <typename E> i64 set_osec_offsets(Context<E> &);
|
||||
@@ -1741,6 +1694,7 @@ struct Context {
|
||||
bool z_now = false;
|
||||
bool z_origin = false;
|
||||
bool z_relro = true;
|
||||
bool z_rewrite_endbr = false;
|
||||
bool z_sectionheader = true;
|
||||
bool z_shstk = false;
|
||||
bool z_text = false;
|
||||
@@ -1772,6 +1726,8 @@ struct Context {
|
||||
std::unordered_set<std::string_view> ignore_ir_file;
|
||||
std::unordered_set<std::string_view> wrap;
|
||||
std::vector<SectionOrder> section_order;
|
||||
std::vector<Symbol<E> *> require_defined;
|
||||
std::vector<Symbol<E> *> undefined;
|
||||
std::vector<std::pair<Symbol<E> *, std::variant<Symbol<E> *, u64>>> defsyms;
|
||||
std::vector<std::string> library_paths;
|
||||
std::vector<std::string> plugin_opt;
|
||||
@@ -1779,20 +1735,16 @@ struct Context {
|
||||
std::vector<std::string_view> auxiliary;
|
||||
std::vector<std::string_view> exclude_libs;
|
||||
std::vector<std::string_view> filter;
|
||||
std::vector<std::string_view> require_defined;
|
||||
std::vector<std::string_view> trace_symbol;
|
||||
std::vector<std::string_view> undefined;
|
||||
u64 image_base = 0x200000;
|
||||
} arg;
|
||||
|
||||
std::vector<VersionPattern> version_patterns;
|
||||
u16 default_version = VER_NDX_GLOBAL;
|
||||
std::vector<DynamicPattern> dynamic_list_patterns;
|
||||
i64 default_version = VER_NDX_UNSPECIFIED;
|
||||
i64 page_size = E::page_size;
|
||||
std::optional<int> global_lock_fd;
|
||||
|
||||
// true if default_version is set by a wildcard in version script.
|
||||
bool default_version_from_version_script = false;
|
||||
|
||||
// Reader context
|
||||
bool as_needed = false;
|
||||
bool whole_archive = false;
|
||||
@@ -1804,6 +1756,8 @@ struct Context {
|
||||
|
||||
bool has_error = false;
|
||||
bool has_lto_object = false;
|
||||
Atomic<bool> has_init_array = false;
|
||||
Atomic<bool> has_ctors = false;
|
||||
|
||||
// Symbol table
|
||||
tbb::concurrent_hash_map<std::string_view, Symbol<E>, HashCmp> symbol_map;
|
||||
@@ -1883,11 +1837,11 @@ struct Context {
|
||||
[[no_unique_address]] ContextExtras<E> extra;
|
||||
|
||||
// For --gdb-index
|
||||
Chunk<E> *debug_info = nullptr;
|
||||
Chunk<E> *debug_abbrev = nullptr;
|
||||
Chunk<E> *debug_ranges = nullptr;
|
||||
Chunk<E> *debug_addr = nullptr;
|
||||
Chunk<E> *debug_rnglists = nullptr;
|
||||
std::span<u8> debug_info;
|
||||
std::span<u8> debug_abbrev;
|
||||
std::span<u8> debug_ranges;
|
||||
std::span<u8> debug_addr;
|
||||
std::span<u8> debug_rnglists;
|
||||
|
||||
// For thread-local variables
|
||||
u64 tls_begin = 0;
|
||||
@@ -1995,6 +1949,7 @@ public:
|
||||
u64 get_tlsdesc_addr(Context<E> &ctx) const;
|
||||
u64 get_plt_addr(Context<E> &ctx) const;
|
||||
u64 get_opd_addr(Context<E> &ctx) const;
|
||||
u64 get_got_pltgot_addr(Context<E> &ctx) const;
|
||||
|
||||
void set_got_idx(Context<E> &ctx, i32 idx);
|
||||
void set_gottp_idx(Context<E> &ctx, i32 idx);
|
||||
@@ -2028,6 +1983,7 @@ public:
|
||||
bool is_relative() const { return !is_absolute(); }
|
||||
bool is_local(Context<E> &ctx) const;
|
||||
bool is_ifunc() const { return get_type() == STT_GNU_IFUNC; }
|
||||
bool is_pde_ifunc(Context<E> &ctx) const;
|
||||
bool is_remaining_undef_weak() const;
|
||||
|
||||
bool is_pcrel_linktime_const(Context<E> &ctx) const;
|
||||
@@ -2083,7 +2039,7 @@ public:
|
||||
i32 sym_idx = -1;
|
||||
|
||||
i32 aux_idx = -1;
|
||||
u16 ver_idx = 0;
|
||||
u16 ver_idx = VER_NDX_UNSPECIFIED;
|
||||
|
||||
// `flags` has NEEDS_ flags.
|
||||
Atomic<u8> flags = 0;
|
||||
@@ -2629,6 +2585,25 @@ inline u64 Symbol<E>::get_opd_addr(Context<E> &ctx) const {
|
||||
get_opd_idx(ctx) * PPC64OpdSection::ENTRY_SIZE;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline u64 Symbol<E>::get_got_pltgot_addr(Context<E> &ctx) const {
|
||||
// An ifunc symbol occupies two consecutive GOT slots in a
|
||||
// position-dependent executable (PDE). The first slot contains the
|
||||
// symbol's PLT address, and the second slot holds the resolved
|
||||
// address. A PDE uses the ifunc symbol's PLT entry as the address
|
||||
// for the symbol, akin to a canonical PLT.
|
||||
//
|
||||
// This function returns the address that the PLT entry should use
|
||||
// to jump to the resolved address.
|
||||
//
|
||||
// Note that we don't use this function for PPC64. In PPC64, symbols
|
||||
// are always accessed through the TOC table regardless of the
|
||||
// -fno-PIE setting. We don't need canonical PLTs on the psABIs too.
|
||||
if (is_pde_ifunc(ctx))
|
||||
return get_got_addr(ctx) + sizeof(Word<E>);
|
||||
return get_got_addr(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline void Symbol<E>::set_got_idx(Context<E> &ctx, i32 idx) {
|
||||
assert(aux_idx != -1);
|
||||
@@ -2757,6 +2732,12 @@ inline bool Symbol<E>::is_local(Context<E> &ctx) const {
|
||||
return !is_imported && !is_exported;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline bool Symbol<E>::is_pde_ifunc(Context<E> &ctx) const {
|
||||
// Returns true if this is an ifunc tha uses two GOT slots
|
||||
return is_ifunc() && !ctx.arg.pic && !is_ppc64<E>;
|
||||
}
|
||||
|
||||
// A remaining weak undefined symbol is promoted to a dynamic symbol
|
||||
// in DSO and resolved to 0 in an executable. This function returns
|
||||
// true if it's latter.
|
||||
|
||||
+212
-429
@@ -462,6 +462,14 @@ template <typename E>
|
||||
void StrtabSection<E>::update_shdr(Context<E> &ctx) {
|
||||
i64 offset = 1;
|
||||
|
||||
// ARM32 uses $a, $t and $t mapping symbols to mark the beginning of
|
||||
// ARM, Thumb and data in text, respectively. These symbols don't
|
||||
// affect correctness of the program but helps disassembler to
|
||||
// disassemble machine code appropriately.
|
||||
if constexpr (is_arm32<E>)
|
||||
if (!ctx.arg.strip_all && !ctx.arg.retain_symbols_file)
|
||||
offset += sizeof("$a\0$t\0$d");
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
chunk->strtab_offset = offset;
|
||||
offset += chunk->strtab_size;
|
||||
@@ -480,6 +488,16 @@ void StrtabSection<E>::update_shdr(Context<E> &ctx) {
|
||||
this->shdr.sh_size = (offset == 1) ? 0 : offset;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void StrtabSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
buf[0] = '\0';
|
||||
|
||||
if constexpr (is_arm32<E>)
|
||||
if (!ctx.arg.strip_all && !ctx.arg.retain_symbols_file)
|
||||
memcpy(buf + 1, "$a\0$t\0$d", 9);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ShstrtabSection<E>::update_shdr(Context<E> &ctx) {
|
||||
std::unordered_map<std::string_view, i64> map;
|
||||
@@ -591,9 +609,6 @@ void SymtabSection<E>::copy_buf(Context<E> &ctx) {
|
||||
ElfSym<E> *buf = (ElfSym<E> *)(ctx.buf + this->shdr.sh_offset);
|
||||
memset(buf, 0, sizeof(ElfSym<E>));
|
||||
|
||||
// Write the initial NUL byte to .strtab.
|
||||
ctx.buf[ctx.strtab->shdr.sh_offset] = '\0';
|
||||
|
||||
if (ctx.symtab_shndx) {
|
||||
ElfShdr<E> &shdr = ctx.symtab_shndx->shdr;
|
||||
memset(ctx.buf + shdr.sh_offset, 0, shdr.sh_size);
|
||||
@@ -632,6 +647,27 @@ void SymtabSection<E>::copy_buf(Context<E> &ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
// An ARM64 function with a non-standard calling convention is marked with
|
||||
// STO_AARCH64_VARIANT_PCS bit in the symbol table.
|
||||
//
|
||||
// A function with that bit is not safe to be called through a lazy PLT
|
||||
// stub because the PLT resolver may clobber registers that should be
|
||||
// preserved in a non-standard calling convention.
|
||||
//
|
||||
// To solve the problem, the dynamic linker scans the dynamic symbol table
|
||||
// at process startup time and resolve symbols with STO_AARCH64_VARIANT_PCS
|
||||
// bit eagerly, so that the PLT resolver won't be called for that symbol
|
||||
// lazily. As an optimization, it does so only when DT_AARCH64_VARIANT_PCS
|
||||
// is set in the dynamic section.
|
||||
//
|
||||
// This function returns true if DT_AARCH64_VARIANT_PCS needs to be set.
|
||||
static bool contains_variant_pcs(Context<ARM64> &ctx) {
|
||||
for (Symbol<ARM64> *sym : ctx.plt->symbols)
|
||||
if (sym->esym().arm64_variant_pcs)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<Word<E>> create_dynamic_section(Context<E> &ctx) {
|
||||
std::vector<Word<E>> vec;
|
||||
@@ -781,6 +817,10 @@ static std::vector<Word<E>> create_dynamic_section(Context<E> &ctx) {
|
||||
if (flags1)
|
||||
define(DT_FLAGS_1, flags1);
|
||||
|
||||
if constexpr (is_arm64<E>)
|
||||
if (contains_variant_pcs(ctx))
|
||||
define(DT_AARCH64_VARIANT_PCS, 1);
|
||||
|
||||
if constexpr (is_ppc32<E>)
|
||||
define(DT_PPC_GOT, ctx.gotplt->shdr.sh_addr);
|
||||
|
||||
@@ -825,7 +865,7 @@ OutputSection<E>::OutputSection(Context<E> &ctx, std::string_view name,
|
||||
u32 type, u64 flags) {
|
||||
this->name = name;
|
||||
this->shdr.sh_type = type;
|
||||
this->shdr.sh_flags = flags;
|
||||
this->shdr.sh_flags = flags & ~SHF_MERGE & ~SHF_STRINGS;
|
||||
|
||||
if (auto it = ctx.arg.section_align.find(name);
|
||||
it != ctx.arg.section_align.end())
|
||||
@@ -858,31 +898,30 @@ void OutputSection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void OutputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
auto clear = [&](u8 *loc, i64 size) {
|
||||
// As a special case, .init and .fini are filled with NOPs for s390x
|
||||
// because the runtime executes the sections as if they were a single
|
||||
// function. .init and .fini are superceded by .init_array and
|
||||
// .fini_array but being actively used only on s390x.
|
||||
if constexpr (is_s390x<E>) {
|
||||
if (this->name == ".init" || this->name == ".fini") {
|
||||
for (i64 i = 0; i < size; i += 2)
|
||||
*(ub16 *)(loc + i) = 0x0700; // nop
|
||||
return;
|
||||
}
|
||||
}
|
||||
memset(loc, 0, size);
|
||||
};
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)members.size(), [&](i64 i) {
|
||||
// Copy section contents to an output file
|
||||
// Copy section contents to an output file.
|
||||
InputSection<E> &isec = *members[i];
|
||||
isec.write_to(ctx, buf + isec.offset);
|
||||
|
||||
// Clear trailing padding
|
||||
// Clear trailing padding. We write trap or nop instructions for
|
||||
// an executable segment so that a disassembler wouldn't try to
|
||||
// disassemble garbage as instructions.
|
||||
u64 this_end = isec.offset + isec.sh_size;
|
||||
u64 next_start = (i == members.size() - 1) ?
|
||||
(u64)this->shdr.sh_size : members[i + 1]->offset;
|
||||
clear(buf + this_end, next_start - this_end);
|
||||
u64 next_start;
|
||||
if (i + 1 < members.size())
|
||||
next_start = members[i + 1]->offset;
|
||||
else
|
||||
next_start = this->shdr.sh_size;
|
||||
|
||||
u8 *loc = buf + this_end;
|
||||
i64 size = next_start - this_end;
|
||||
|
||||
if (this->shdr.sh_flags & SHF_EXECINSTR) {
|
||||
for (i64 i = 0; i + sizeof(E::filler) <= size; i += sizeof(E::filler))
|
||||
memcpy(loc + i, E::filler, sizeof(E::filler));
|
||||
} else {
|
||||
memset(loc, 0, size);
|
||||
}
|
||||
});
|
||||
|
||||
if constexpr (needs_thunk<E>) {
|
||||
@@ -976,16 +1015,10 @@ void OutputSection<E>::construct_relr(Context<E> &ctx) {
|
||||
// Compute spaces needed for thunk symbols
|
||||
template <typename E>
|
||||
void OutputSection<E>::compute_symtab_size(Context<E> &ctx) {
|
||||
if (ctx.arg.strip_all || ctx.arg.retain_symbols_file || ctx.arg.relocatable)
|
||||
return;
|
||||
|
||||
if constexpr (needs_thunk<E>) {
|
||||
this->strtab_size = 0;
|
||||
this->num_local_symtab = 0;
|
||||
|
||||
if constexpr (is_arm32<E>)
|
||||
this->strtab_size = 9; // for "$t", "$a" and "$d" symbols
|
||||
|
||||
for (std::unique_ptr<RangeExtensionThunk<E>> &thunk : thunks) {
|
||||
// For ARM32, we emit additional symbol "$t", "$a" and "$d" for
|
||||
// each thunk to mark the beginning of Thumb code, ARM code and
|
||||
@@ -1006,7 +1039,7 @@ void OutputSection<E>::compute_symtab_size(Context<E> &ctx) {
|
||||
// disassembling and/or debugging our output.
|
||||
template <typename E>
|
||||
void OutputSection<E>::populate_symtab(Context<E> &ctx) {
|
||||
if (this->strtab_size == 0)
|
||||
if (this->num_local_symtab == 0)
|
||||
return;
|
||||
|
||||
if constexpr (needs_thunk<E>) {
|
||||
@@ -1016,37 +1049,30 @@ void OutputSection<E>::populate_symtab(Context<E> &ctx) {
|
||||
u8 *strtab_base = ctx.buf + ctx.strtab->shdr.sh_offset;
|
||||
u8 *strtab = strtab_base + this->strtab_offset;
|
||||
|
||||
if constexpr (is_arm32<E>) {
|
||||
// ARM uses these symbols to mark the begining of Thumb code, ARM
|
||||
// code and data, respectively. Our thunk contains all of them.
|
||||
strtab += write_string(strtab, "$t");
|
||||
strtab += write_string(strtab, "$a");
|
||||
strtab += write_string(strtab, "$d");
|
||||
}
|
||||
auto write_esym = [&](u64 addr, i64 st_name) {
|
||||
memset(esym, 0, sizeof(*esym));
|
||||
esym->st_name = st_name;
|
||||
esym->st_type = STT_FUNC;
|
||||
esym->st_shndx = this->shndx;
|
||||
esym->st_value = addr;
|
||||
esym++;
|
||||
};
|
||||
|
||||
for (std::unique_ptr<RangeExtensionThunk<E>> &thunk : thunks) {
|
||||
for (i64 i = 0; i < thunk->symbols.size(); i++) {
|
||||
Symbol<E> &sym = *thunk->symbols[i];
|
||||
u64 addr = thunk->get_addr(i);
|
||||
|
||||
auto write_esym = [&](i64 st_name, i64 off) {
|
||||
memset(esym, 0, sizeof(*esym));
|
||||
esym->st_name = st_name;
|
||||
esym->st_type = STT_FUNC;
|
||||
esym->st_shndx = this->shndx;
|
||||
esym->st_value = thunk->get_addr(i) + off;
|
||||
esym++;
|
||||
};
|
||||
|
||||
write_esym(strtab - strtab_base, 0);
|
||||
write_esym(addr, strtab - strtab_base);
|
||||
|
||||
strtab += write_string(strtab, sym.name()) - 1;
|
||||
strtab += write_string(strtab, "$thunk");
|
||||
|
||||
// Emit "$t", "$a" and "$d" if ARM32.
|
||||
if constexpr (is_arm32<E>) {
|
||||
write_esym(this->strtab_offset, 0);
|
||||
write_esym(this->strtab_offset + 3, 4);
|
||||
write_esym(this->strtab_offset + 6, 12);
|
||||
write_esym(addr, ctx.strtab->THUMB);
|
||||
write_esym(addr + 4, ctx.strtab->ARM);
|
||||
write_esym(addr + 12, ctx.strtab->DATA);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1056,7 +1082,14 @@ void OutputSection<E>::populate_symtab(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
void GotSection<E>::add_got_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
sym->set_got_idx(ctx, this->shdr.sh_size / sizeof(Word<E>));
|
||||
this->shdr.sh_size += sizeof(Word<E>);
|
||||
|
||||
// An IFUNC symbol uses two GOT slots in a position-dependent
|
||||
// executable.
|
||||
if (sym->is_pde_ifunc(ctx))
|
||||
this->shdr.sh_size += sizeof(Word<E>) * 2;
|
||||
else
|
||||
this->shdr.sh_size += sizeof(Word<E>);
|
||||
|
||||
got_syms.push_back(sym);
|
||||
}
|
||||
|
||||
@@ -1080,10 +1113,10 @@ void GotSection<E>::add_tlsdesc_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
// always emit a dynamic relocation for each TLSDESC entry.
|
||||
//
|
||||
// If dynamic relocation is not available (i.e. if we are creating a
|
||||
// non-PIC executable), we always relax TLSDESC relocations so that
|
||||
// no TLSDESC relocation exist at runtime.
|
||||
// statically-linked executable), we always relax TLSDESC relocations
|
||||
// so that no TLSDESC relocation exist at runtime.
|
||||
assert(supports_tlsdesc<E>);
|
||||
assert(ctx.arg.pic);
|
||||
assert(!ctx.arg.is_static);
|
||||
|
||||
sym->set_tlsdesc_idx(ctx, this->shdr.sh_size / sizeof(Word<E>));
|
||||
this->shdr.sh_size += sizeof(Word<E>) * 2;
|
||||
@@ -1150,7 +1183,12 @@ static std::vector<GotEntry<E>> get_got_entries(Context<E> &ctx) {
|
||||
// IFUNC always needs to be fixed up by the dynamic linker.
|
||||
if constexpr (supports_ifunc<E>) {
|
||||
if (sym->is_ifunc()) {
|
||||
add({idx, sym->get_addr(ctx, NO_PLT), E::R_IRELATIVE});
|
||||
if (sym->is_pde_ifunc(ctx)) {
|
||||
add({idx, sym->get_plt_addr(ctx)});
|
||||
add({idx + 1, sym->get_addr(ctx, NO_PLT), E::R_IRELATIVE});
|
||||
} else {
|
||||
add({idx, sym->get_addr(ctx, NO_PLT), E::R_IRELATIVE});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -1305,9 +1343,6 @@ void GotSection<E>::construct_relr(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void GotSection<E>::compute_symtab_size(Context<E> &ctx) {
|
||||
if (ctx.arg.strip_all || ctx.arg.retain_symbols_file)
|
||||
return;
|
||||
|
||||
this->strtab_size = 0;
|
||||
this->num_local_symtab = 0;
|
||||
|
||||
@@ -1339,7 +1374,7 @@ void GotSection<E>::compute_symtab_size(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void GotSection<E>::populate_symtab(Context<E> &ctx) {
|
||||
if (this->strtab_size == 0)
|
||||
if (this->num_local_symtab == 0)
|
||||
return;
|
||||
|
||||
ElfSym<E> *esym =
|
||||
@@ -1430,19 +1465,19 @@ void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void PltSection<E>::compute_symtab_size(Context<E> &ctx) {
|
||||
if (ctx.arg.strip_all || ctx.arg.retain_symbols_file)
|
||||
return;
|
||||
|
||||
this->num_local_symtab = symbols.size();
|
||||
this->strtab_size = 0;
|
||||
|
||||
for (Symbol<E> *sym : symbols)
|
||||
this->strtab_size += sym->name().size() + sizeof("$plt");
|
||||
|
||||
if constexpr (is_arm32<E>)
|
||||
this->num_local_symtab += symbols.size() * 2 + 2;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void PltSection<E>::populate_symtab(Context<E> &ctx) {
|
||||
if (this->strtab_size == 0)
|
||||
if (this->num_local_symtab == 0)
|
||||
return;
|
||||
|
||||
ElfSym<E> *esym =
|
||||
@@ -1451,16 +1486,30 @@ void PltSection<E>::populate_symtab(Context<E> &ctx) {
|
||||
u8 *strtab_base = ctx.buf + ctx.strtab->shdr.sh_offset;
|
||||
u8 *strtab = strtab_base + this->strtab_offset;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
auto write_esym = [&](u64 addr, i64 st_name) {
|
||||
memset(esym, 0, sizeof(*esym));
|
||||
esym->st_name = strtab - strtab_base;
|
||||
esym->st_name = st_name;
|
||||
esym->st_type = STT_FUNC;
|
||||
esym->st_shndx = this->shndx;
|
||||
esym->st_value = sym->get_plt_addr(ctx);
|
||||
esym->st_value = addr;
|
||||
esym++;
|
||||
};
|
||||
|
||||
if constexpr (is_arm32<E>) {
|
||||
write_esym(this->shdr.sh_addr, ctx.strtab->ARM);
|
||||
write_esym(this->shdr.sh_addr + 16, ctx.strtab->DATA);
|
||||
}
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u64 addr = sym->get_plt_addr(ctx);
|
||||
write_esym(addr, strtab - strtab_base);
|
||||
strtab += write_string(strtab, sym->name()) - 1;
|
||||
strtab += write_string(strtab, "$plt");
|
||||
|
||||
if constexpr (is_arm32<E>) {
|
||||
write_esym(addr, ctx.strtab->ARM);
|
||||
write_esym(addr + 12, ctx.strtab->DATA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1483,19 +1532,19 @@ void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void PltGotSection<E>::compute_symtab_size(Context<E> &ctx) {
|
||||
if (ctx.arg.strip_all || ctx.arg.retain_symbols_file)
|
||||
return;
|
||||
|
||||
this->num_local_symtab = symbols.size();
|
||||
this->strtab_size = 0;
|
||||
|
||||
for (Symbol<E> *sym : symbols)
|
||||
this->strtab_size += sym->name().size() + sizeof("$pltgot");
|
||||
|
||||
if constexpr (is_arm32<E>)
|
||||
this->num_local_symtab += symbols.size() * 2;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void PltGotSection<E>::populate_symtab(Context<E> &ctx) {
|
||||
if (this->strtab_size == 0)
|
||||
if (this->num_local_symtab == 0)
|
||||
return;
|
||||
|
||||
ElfSym<E> *esym =
|
||||
@@ -1504,16 +1553,25 @@ void PltGotSection<E>::populate_symtab(Context<E> &ctx) {
|
||||
u8 *strtab_base = ctx.buf + ctx.strtab->shdr.sh_offset;
|
||||
u8 *strtab = strtab_base + this->strtab_offset;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
auto write_esym = [&](u64 addr, i64 st_name) {
|
||||
memset(esym, 0, sizeof(*esym));
|
||||
esym->st_name = strtab - strtab_base;
|
||||
esym->st_name = st_name;
|
||||
esym->st_type = STT_FUNC;
|
||||
esym->st_shndx = this->shndx;
|
||||
esym->st_value = sym->get_plt_addr(ctx);
|
||||
esym->st_value = addr;
|
||||
esym++;
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u64 addr = sym->get_plt_addr(ctx);
|
||||
write_esym(addr, strtab - strtab_base);
|
||||
strtab += write_string(strtab, sym->name()) - 1;
|
||||
strtab += write_string(strtab, "$pltgot");
|
||||
|
||||
if constexpr (is_arm32<E>) {
|
||||
write_esym(addr, ctx.strtab->ARM);
|
||||
write_esym(addr + 12, ctx.strtab->DATA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1561,6 +1619,9 @@ ElfSym<E> to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name,
|
||||
else
|
||||
esym.st_bind = sym.esym().st_bind;
|
||||
|
||||
if constexpr (is_arm64<E>)
|
||||
esym.arm64_variant_pcs = sym.esym().arm64_variant_pcs;
|
||||
|
||||
if constexpr (is_ppc64v2<E>)
|
||||
esym.ppc_local_entry = sym.esym().ppc_local_entry;
|
||||
|
||||
@@ -1607,8 +1668,15 @@ ElfSym<E> to_output_esym(Context<E> &ctx, Symbol<E> &sym, u32 st_name,
|
||||
esym.st_shndx = SHN_ABS;
|
||||
esym.st_value = sym.get_addr(ctx);
|
||||
} else if (sym.get_type() == STT_TLS) {
|
||||
// TLS symbol
|
||||
shndx = get_st_shndx(sym);
|
||||
esym.st_value = sym.get_addr(ctx) - ctx.tls_begin;
|
||||
} else if (sym.is_pde_ifunc(ctx)) {
|
||||
// IFUNC symbol in PDE that uses two GOT slots
|
||||
shndx = get_st_shndx(sym);
|
||||
esym.st_type = STT_FUNC;
|
||||
esym.st_visibility = sym.visibility;
|
||||
esym.st_value = sym.get_addr(ctx);
|
||||
} else {
|
||||
shndx = get_st_shndx(sym);
|
||||
esym.st_visibility = sym.visibility;
|
||||
@@ -1844,34 +1912,51 @@ void GnuHashSection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
std::string_view
|
||||
get_merged_output_name(Context<E> &ctx, std::string_view name, u64 flags) {
|
||||
get_merged_output_name(Context<E> &ctx, std::string_view name, u64 flags,
|
||||
i64 entsize, i64 addralign) {
|
||||
if (ctx.arg.relocatable && !ctx.arg.relocatable_merge_sections)
|
||||
return name;
|
||||
if (ctx.arg.unique && ctx.arg.unique->match(name))
|
||||
return name;
|
||||
if (name == ".rodata" || name.starts_with(".rodata."))
|
||||
return (flags & SHF_STRINGS) ? ".rodata.str" : ".rodata.cst";
|
||||
|
||||
// GCC seems to create sections named ".rodata.strN.<mangled-symbol-name>.M".
|
||||
// We want to eliminate the symbol name part from the section name.
|
||||
if ((flags & SHF_STRINGS) && name.starts_with(".rodata.")) {
|
||||
if (entsize == 1 && addralign == 1)
|
||||
return ".rodata.str1.1";
|
||||
if (entsize == 2 && addralign == 2)
|
||||
return ".rodata.str2.2";
|
||||
if (entsize == 4 && addralign == 4)
|
||||
return ".rodata.str4.4";
|
||||
return save_string(ctx,".rodata.str"s + std::to_string(entsize) + "." +
|
||||
std::to_string(addralign));
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MergedSection<E>::MergedSection(std::string_view name, u64 flags, u32 type) {
|
||||
MergedSection<E>::MergedSection(std::string_view name, i64 flags, i64 type,
|
||||
i64 entsize) {
|
||||
this->name = name;
|
||||
this->shdr.sh_flags = flags;
|
||||
this->shdr.sh_type = type;
|
||||
this->shdr.sh_entsize = entsize;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MergedSection<E> *
|
||||
MergedSection<E>::get_instance(Context<E> &ctx, std::string_view name,
|
||||
u64 type, u64 flags) {
|
||||
name = get_merged_output_name(ctx, name, flags);
|
||||
i64 type, i64 flags,
|
||||
i64 entsize, i64 addralign) {
|
||||
name = get_merged_output_name(ctx, name, flags, entsize, addralign);
|
||||
flags = flags & ~(u64)SHF_GROUP & ~(u64)SHF_COMPRESSED;
|
||||
|
||||
auto find = [&]() -> MergedSection * {
|
||||
for (std::unique_ptr<MergedSection<E>> &osec : ctx.merged_sections)
|
||||
if (std::tuple(name, flags, type) ==
|
||||
std::tuple(osec->name, osec->shdr.sh_flags, osec->shdr.sh_type))
|
||||
if (std::tuple(name, flags, type, entsize) ==
|
||||
std::tuple(osec->name, osec->shdr.sh_flags, osec->shdr.sh_type,
|
||||
osec->shdr.sh_entsize))
|
||||
return osec.get();
|
||||
return nullptr;
|
||||
};
|
||||
@@ -1889,7 +1974,7 @@ MergedSection<E>::get_instance(Context<E> &ctx, std::string_view name,
|
||||
if (MergedSection *osec = find())
|
||||
return osec;
|
||||
|
||||
MergedSection *osec = new MergedSection(name, flags, type);
|
||||
MergedSection *osec = new MergedSection(name, flags, type, entsize);
|
||||
ctx.merged_sections.emplace_back(osec);
|
||||
return osec;
|
||||
}
|
||||
@@ -1919,67 +2004,49 @@ MergedSection<E>::insert(Context<E> &ctx, std::string_view data, u64 hash,
|
||||
template <typename E>
|
||||
void MergedSection<E>::assign_offsets(Context<E> &ctx) {
|
||||
std::vector<i64> sizes(map.NUM_SHARDS);
|
||||
std::vector<i64> max_p2aligns(map.NUM_SHARDS);
|
||||
shard_offsets.resize(map.NUM_SHARDS + 1);
|
||||
|
||||
i64 shard_size = map.nbuckets / map.NUM_SHARDS;
|
||||
Atomic<i64> alignment = 1;
|
||||
|
||||
tbb::parallel_for((i64)0, map.NUM_SHARDS, [&](i64 i) {
|
||||
struct KeyVal {
|
||||
std::string_view key;
|
||||
SectionFragment<E> *val;
|
||||
};
|
||||
using Entry = typename decltype(map)::Entry;
|
||||
std::vector<Entry *> entries = map.get_sorted_entries(i);
|
||||
|
||||
std::vector<KeyVal> fragments;
|
||||
fragments.reserve(shard_size);
|
||||
|
||||
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++)
|
||||
if (const char *key = map.get_key(j))
|
||||
if (SectionFragment<E> &frag = map.entries[j].value; frag.is_alive)
|
||||
fragments.push_back({{key, map.entries[j].keylen}, &frag});
|
||||
|
||||
// Sort fragments to make output deterministic.
|
||||
tbb::parallel_sort(fragments.begin(), fragments.end(),
|
||||
[](const KeyVal &a, const KeyVal &b) {
|
||||
return std::tuple{(u32)a.val->p2align, a.key.size(), a.key} <
|
||||
std::tuple{(u32)b.val->p2align, b.key.size(), b.key};
|
||||
});
|
||||
|
||||
// Assign offsets.
|
||||
i64 offset = 0;
|
||||
i64 p2align = 0;
|
||||
|
||||
for (KeyVal &kv : fragments) {
|
||||
SectionFragment<E> &frag = *kv.val;
|
||||
offset = align_to(offset, 1 << frag.p2align);
|
||||
frag.offset = offset;
|
||||
offset += kv.key.size();
|
||||
p2align = std::max<i64>(p2align, frag.p2align);
|
||||
for (Entry *ent : entries) {
|
||||
SectionFragment<E> &frag = ent->value;
|
||||
if (frag.is_alive) {
|
||||
offset = align_to(offset, 1 << frag.p2align);
|
||||
frag.offset = offset;
|
||||
offset += ent->keylen;
|
||||
p2align = std::max<i64>(p2align, frag.p2align);
|
||||
}
|
||||
}
|
||||
|
||||
sizes[i] = offset;
|
||||
max_p2aligns[i] = p2align;
|
||||
update_maximum(alignment, 1 << p2align);
|
||||
|
||||
static Counter merged_strings("merged_strings");
|
||||
merged_strings += fragments.size();
|
||||
merged_strings += entries.size();
|
||||
});
|
||||
|
||||
i64 p2align = 0;
|
||||
for (i64 x : max_p2aligns)
|
||||
p2align = std::max(p2align, x);
|
||||
i64 shard_size = map.nbuckets / map.NUM_SHARDS;
|
||||
shard_offsets.resize(map.NUM_SHARDS + 1);
|
||||
|
||||
for (i64 i = 1; i < map.NUM_SHARDS + 1; i++)
|
||||
shard_offsets[i] =
|
||||
align_to(shard_offsets[i - 1] + sizes[i - 1], 1 << p2align);
|
||||
align_to(shard_offsets[i - 1] + sizes[i - 1], alignment);
|
||||
|
||||
tbb::parallel_for((i64)1, map.NUM_SHARDS, [&](i64 i) {
|
||||
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++)
|
||||
if (SectionFragment<E> &frag = map.entries[j].value; frag.is_alive)
|
||||
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++) {
|
||||
SectionFragment<E> &frag = map.entries[j].value;
|
||||
if (frag.is_alive)
|
||||
frag.offset += shard_offsets[i];
|
||||
}
|
||||
});
|
||||
|
||||
this->shdr.sh_size = shard_offsets[map.NUM_SHARDS];
|
||||
this->shdr.sh_addralign = 1 << p2align;
|
||||
this->shdr.sh_addralign = alignment;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -1995,9 +2062,11 @@ void MergedSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
memset(buf + shard_offsets[i], 0, shard_offsets[i + 1] - shard_offsets[i]);
|
||||
|
||||
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++)
|
||||
if (const char *key = map.get_key(j))
|
||||
if (SectionFragment<E> &frag = map.entries[j].value; frag.is_alive)
|
||||
if (const char *key = map.entries[j].key) {
|
||||
SectionFragment<E> &frag = map.entries[j].value;
|
||||
if (frag.is_alive)
|
||||
memcpy(buf + frag.offset, key, map.entries[j].keylen);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2005,7 +2074,7 @@ template <typename E>
|
||||
void MergedSection<E>::print_stats(Context<E> &ctx) {
|
||||
i64 used = 0;
|
||||
for (i64 i = 0; i < map.nbuckets; i++)
|
||||
if (map.get_key(i))
|
||||
if (map.entries[i].key)
|
||||
used++;
|
||||
|
||||
SyncOut(ctx) << this->name
|
||||
@@ -2287,8 +2356,7 @@ void CopyrelSection<E>::update_shdr(Context<E> &ctx) {
|
||||
// a segment, so a .copyrel.rel.ro usually requires one extra
|
||||
// segment for it. We turn a .copyrel.rel.ro into a regular section
|
||||
// if it is very small to avoid the cost of the extra segment.
|
||||
constexpr i64 threshold = 4096;
|
||||
if (this->is_relro && ctx.arg.z_relro && this->shdr.sh_size < threshold)
|
||||
if (this->is_relro && ctx.arg.z_relro && this->shdr.sh_size < E::page_size)
|
||||
this->shdr.sh_type = SHT_PROGBITS;
|
||||
}
|
||||
|
||||
@@ -2482,8 +2550,12 @@ void VerdefSection<E>::construct(Context<E> &ctx) {
|
||||
for (std::string_view verstr : ctx.arg.version_definitions)
|
||||
write(verstr, idx++, 0);
|
||||
|
||||
for (Symbol<E> *sym : std::span<Symbol<E> *>(ctx.dynsym->symbols).subspan(1))
|
||||
ctx.versym->contents[sym->get_dynsym_idx(ctx)] = sym->ver_idx;
|
||||
for (Symbol<E> *sym : std::span<Symbol<E> *>(ctx.dynsym->symbols).subspan(1)) {
|
||||
i64 ver = sym->ver_idx;
|
||||
if (ver == VER_NDX_UNSPECIFIED)
|
||||
ver = VER_NDX_GLOBAL;
|
||||
ctx.versym->contents[sym->get_dynsym_idx(ctx)] = ver;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -2704,316 +2776,25 @@ void NotePropertySection<E>::copy_buf(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// This page explains the format of .gdb_index:
|
||||
// https://sourceware.org/gdb/onlinedocs/gdb/Index-Section-Format.html
|
||||
template <typename E>
|
||||
void GdbIndexSection<E>::construct(Context<E> &ctx) {
|
||||
Timer t(ctx, "GdbIndexSection::construct");
|
||||
|
||||
std::atomic_bool has_debug_info = false;
|
||||
|
||||
// Read debug sections
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
if (file->debug_info) {
|
||||
// Read compilation units from .debug_info.
|
||||
file->compunits = read_compunits(ctx, *file);
|
||||
|
||||
// Count the number of address areas contained in this file.
|
||||
file->num_areas = estimate_address_areas(ctx, *file);
|
||||
has_debug_info = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!has_debug_info)
|
||||
return;
|
||||
|
||||
// Initialize `area_offset` and `compunits_idx`.
|
||||
for (i64 i = 0; i < ctx.objs.size() - 1; i++) {
|
||||
ctx.objs[i + 1]->area_offset =
|
||||
ctx.objs[i]->area_offset + ctx.objs[i]->num_areas * 20;
|
||||
ctx.objs[i + 1]->compunits_idx =
|
||||
ctx.objs[i]->compunits_idx + ctx.objs[i]->compunits.size();
|
||||
}
|
||||
|
||||
// Read .debug_gnu_pubnames and .debug_gnu_pubtypes.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->gdb_names = read_pubnames(ctx, *file);
|
||||
});
|
||||
|
||||
// Estimate the unique number of pubnames.
|
||||
HyperLogLog estimator;
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
HyperLogLog e;
|
||||
for (GdbIndexName &name : file->gdb_names)
|
||||
e.insert(name.hash);
|
||||
estimator.merge(e);
|
||||
});
|
||||
|
||||
// Uniquify pubnames by inserting all name strings into a concurrent
|
||||
// hashmap.
|
||||
map.resize(estimator.get_cardinality() * 2);
|
||||
tbb::enumerable_thread_specific<i64> num_names;
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (GdbIndexName &name : file->gdb_names) {
|
||||
MapEntry *ent;
|
||||
bool inserted;
|
||||
std::tie(ent, inserted) = map.insert(name.name, name.hash, {file, name.hash});
|
||||
if (inserted)
|
||||
num_names.local()++;
|
||||
|
||||
ObjectFile<E> *old_val = ent->owner;
|
||||
while (file->priority < old_val->priority &&
|
||||
!ent->owner.compare_exchange_weak(old_val, file));
|
||||
|
||||
ent->num_attrs++;
|
||||
name.entry_idx = map.get_idx(ent);
|
||||
}
|
||||
});
|
||||
|
||||
// Assign offsets for names and attributes within each file.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (GdbIndexName &name : file->gdb_names) {
|
||||
MapEntry &ent = map.entries[name.entry_idx].value;
|
||||
if (ent.owner == file) {
|
||||
ent.attr_offset = file->attrs_size;
|
||||
file->attrs_size += (ent.num_attrs + 1) * 4;
|
||||
ent.name_offset = file->names_size;
|
||||
file->names_size += name.name.size() + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Compute per-file name and attributes offsets.
|
||||
for (i64 i = 0; i < ctx.objs.size() - 1; i++)
|
||||
ctx.objs[i + 1]->attrs_offset =
|
||||
ctx.objs[i]->attrs_offset + ctx.objs[i]->attrs_size;
|
||||
|
||||
ctx.objs[0]->names_offset =
|
||||
ctx.objs.back()->attrs_offset + ctx.objs.back()->attrs_size;
|
||||
|
||||
for (i64 i = 0; i < ctx.objs.size() - 1; i++)
|
||||
ctx.objs[i + 1]->names_offset =
|
||||
ctx.objs[i]->names_offset + ctx.objs[i]->names_size;
|
||||
|
||||
// .gdb_index contains an on-disk hash table for pubnames and
|
||||
// pubtypes. We aim 75% utilization. As per the format specification,
|
||||
// It must be a power of two.
|
||||
i64 num_symtab_entries =
|
||||
std::max<i64>(bit_ceil(num_names.combine(std::plus()) * 4 / 3), 16);
|
||||
|
||||
// Now that we can compute the size of this section.
|
||||
ObjectFile<E> &last = *ctx.objs.back();
|
||||
i64 compunits_size = (last.compunits_idx + last.compunits.size()) * 16;
|
||||
i64 areas_size = last.area_offset + last.num_areas * 20;
|
||||
i64 offset = sizeof(header);
|
||||
|
||||
header.cu_list_offset = offset;
|
||||
offset += compunits_size;
|
||||
|
||||
header.cu_types_offset = offset;
|
||||
header.areas_offset = offset;
|
||||
offset += areas_size;
|
||||
|
||||
header.symtab_offset = offset;
|
||||
offset += num_symtab_entries * 8;
|
||||
|
||||
header.const_pool_offset = offset;
|
||||
offset += last.names_offset + last.names_size;
|
||||
|
||||
this->shdr.sh_size = offset;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void GdbIndexSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
// Write section header.
|
||||
memcpy(buf, &header, sizeof(header));
|
||||
buf += sizeof(header);
|
||||
|
||||
// Write compilation unit list.
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file->debug_info) {
|
||||
u64 offset = file->debug_info->offset;
|
||||
for (std::string_view cu : file->compunits) {
|
||||
*(ul64 *)buf = offset;
|
||||
*(ul64 *)(buf + 8) = cu.size();
|
||||
buf += 16;
|
||||
offset += cu.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip address areas. It'll be filled by write_address_areas.
|
||||
buf += header.symtab_offset - header.areas_offset;
|
||||
|
||||
// Write an on-disk hash table for names.
|
||||
u32 symtab_size = header.const_pool_offset - header.symtab_offset;
|
||||
memset(buf, 0, symtab_size);
|
||||
|
||||
assert(has_single_bit(symtab_size / 8));
|
||||
u32 mask = symtab_size / 8 - 1;
|
||||
|
||||
for (i64 i = 0; i < map.nbuckets; i++) {
|
||||
if (map.get_key(i)) {
|
||||
MapEntry &ent = map.entries[i].value;
|
||||
u32 hash = ent.hash;
|
||||
u32 step = (hash & mask) | 1;
|
||||
u32 j = hash & mask;
|
||||
|
||||
while (*(U32<E> *)(buf + j * 8))
|
||||
j = (j + step) & mask;
|
||||
|
||||
ObjectFile<E> &file = *ent.owner;
|
||||
*(ul32 *)(buf + j * 8) = file.names_offset + ent.name_offset;
|
||||
*(ul32 *)(buf + j * 8 + 4) = file.attrs_offset + ent.attr_offset;
|
||||
}
|
||||
}
|
||||
|
||||
buf += symtab_size;
|
||||
|
||||
// Write CU vector
|
||||
memset(buf, 0, ctx.objs[0]->names_offset);
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
std::atomic_uint32_t *attrs = (std::atomic_uint32_t *)buf;
|
||||
|
||||
for (GdbIndexName &name : file->gdb_names) {
|
||||
MapEntry &ent = map.entries[name.entry_idx].value;
|
||||
u32 idx = (ent.owner.load()->attrs_offset + ent.attr_offset) / 4;
|
||||
attrs[idx + ++attrs[idx]] = name.attr;
|
||||
}
|
||||
});
|
||||
|
||||
// Sort CU vector for build reproducibility
|
||||
const i64 shard_size = map.nbuckets / map.NUM_SHARDS;
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)map.NUM_SHARDS, [&](i64 i) {
|
||||
u32 *attrs = (u32 *)buf;
|
||||
|
||||
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++) {
|
||||
if (map.get_key(j)) {
|
||||
MapEntry &ent = map.entries[j].value;
|
||||
u32 idx = (ent.owner.load()->attrs_offset + ent.attr_offset) / 4;
|
||||
u32 *start = attrs + idx + 1;
|
||||
std::sort(start, start + attrs[idx]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// .gdb_index contents are little-endian, so swap bytes if big-endian.
|
||||
if constexpr (std::endian::native == std::endian::big)
|
||||
for (i64 i = 0; i < ctx.objs[0]->names_offset; i += 4)
|
||||
*(u32 *)(buf + i) = bswap(*(u32 *)(buf + i));
|
||||
|
||||
// Write pubnames and pubtypes.
|
||||
tbb::parallel_for((i64)0, (i64)map.NUM_SHARDS, [&](i64 i) {
|
||||
for (i64 j = shard_size * i; j < shard_size * (i + 1); j++) {
|
||||
if (const char *key = map.get_key(j)) {
|
||||
MapEntry &ent = map.entries[j].value;
|
||||
ObjectFile<E> &file = *ent.owner;
|
||||
std::string_view name{key, map.entries[j].keylen};
|
||||
write_string(buf + file.names_offset + ent.name_offset, name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void GdbIndexSection<E>::write_address_areas(Context<E> &ctx) {
|
||||
Timer t(ctx, "GdbIndexSection::write_address_areas");
|
||||
|
||||
if (this->shdr.sh_size == 0)
|
||||
return;
|
||||
|
||||
u8 *base = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
std::string_view name = chunk->name;
|
||||
if (name == ".debug_info")
|
||||
ctx.debug_info = chunk;
|
||||
if (name == ".debug_abbrev")
|
||||
ctx.debug_abbrev = chunk;
|
||||
if (name == ".debug_ranges")
|
||||
ctx.debug_ranges = chunk;
|
||||
if (name == ".debug_addr")
|
||||
ctx.debug_addr = chunk;
|
||||
if (name == ".debug_rnglists")
|
||||
ctx.debug_rnglists = chunk;
|
||||
}
|
||||
|
||||
assert(ctx.debug_info);
|
||||
assert(ctx.debug_abbrev);
|
||||
|
||||
struct Entry {
|
||||
ul64 start;
|
||||
ul64 end;
|
||||
ul32 attr;
|
||||
};
|
||||
|
||||
// Read address ranges from debug sections and copy them to .gdb_index.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
if (!file->debug_info)
|
||||
return;
|
||||
|
||||
Entry *begin = (Entry *)(base + header.areas_offset + file->area_offset);
|
||||
Entry *e = begin;
|
||||
u64 offset = file->debug_info->offset;
|
||||
|
||||
for (i64 i = 0; i < file->compunits.size(); i++) {
|
||||
std::vector<u64> addrs = read_address_areas(ctx, *file, offset);
|
||||
|
||||
for (i64 j = 0; j < addrs.size(); j += 2) {
|
||||
// Skip an empty range
|
||||
if (addrs[j] == addrs[j + 1])
|
||||
continue;
|
||||
|
||||
// Gdb crashes if there are entries with address 0.
|
||||
if (addrs[j] == 0)
|
||||
continue;
|
||||
|
||||
assert(e < begin + file->num_areas);
|
||||
e->start = addrs[j];
|
||||
e->end = addrs[j + 1];
|
||||
e->attr = file->compunits_idx + i;
|
||||
e++;
|
||||
}
|
||||
offset += file->compunits[i].size();
|
||||
}
|
||||
|
||||
// Fill trailing null entries with dummy values because gdb
|
||||
// crashes if there are entries with address 0.
|
||||
u64 filler;
|
||||
if (e == begin)
|
||||
filler = ctx.etext->get_addr(ctx) - 1;
|
||||
else
|
||||
filler = e[-1].start;
|
||||
|
||||
for (; e < begin + file->num_areas; e++) {
|
||||
e->start = filler;
|
||||
e->end = filler;
|
||||
e->attr = file->compunits_idx;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
CompressedSection<E>::CompressedSection(Context<E> &ctx, Chunk<E> &chunk) {
|
||||
assert(chunk.name.starts_with(".debug"));
|
||||
this->name = chunk.name;
|
||||
this->is_compressed = true;
|
||||
|
||||
uncompressed.reset(new u8[chunk.shdr.sh_size]);
|
||||
chunk.write_to(ctx, uncompressed.get());
|
||||
this->uncompressed_data.resize(chunk.shdr.sh_size);
|
||||
u8 *buf = this->uncompressed_data.data();
|
||||
|
||||
chunk.write_to(ctx, buf);
|
||||
|
||||
switch (ctx.arg.compress_debug_sections) {
|
||||
case COMPRESS_ZLIB:
|
||||
chdr.ch_type = ELFCOMPRESS_ZLIB;
|
||||
compressed.reset(new ZlibCompressor(uncompressed.get(), chunk.shdr.sh_size));
|
||||
compressed.reset(new ZlibCompressor(buf, chunk.shdr.sh_size));
|
||||
break;
|
||||
case COMPRESS_ZSTD:
|
||||
chdr.ch_type = ELFCOMPRESS_ZSTD;
|
||||
compressed.reset(new ZstdCompressor(uncompressed.get(), chunk.shdr.sh_size));
|
||||
compressed.reset(new ZstdCompressor(buf, chunk.shdr.sh_size));
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
@@ -3029,8 +2810,10 @@ CompressedSection<E>::CompressedSection(Context<E> &ctx, Chunk<E> &chunk) {
|
||||
this->shndx = chunk.shndx;
|
||||
|
||||
// We don't need to keep the original data unless --gdb-index is given.
|
||||
if (!ctx.arg.gdb_index)
|
||||
uncompressed.reset(nullptr);
|
||||
if (!ctx.arg.gdb_index) {
|
||||
this->uncompressed_data.clear();
|
||||
this->uncompressed_data.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
|
||||
+402
-136
@@ -13,6 +13,50 @@
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Since elf_main is a template, we can't run it without a type parameter.
|
||||
// We speculatively run elf_main with X86_64, and if the speculation was
|
||||
// wrong, re-run it with an actual machine type.
|
||||
template <typename E>
|
||||
int redo_main(Context<E> &ctx, int argc, char **argv) {
|
||||
std::string_view target = ctx.arg.emulation;
|
||||
|
||||
if (target == I386::target_name)
|
||||
return elf_main<I386>(argc, argv);
|
||||
if (target == ARM64::target_name)
|
||||
return elf_main<ARM64>(argc, argv);
|
||||
if (target == ARM32::target_name)
|
||||
return elf_main<ARM32>(argc, argv);
|
||||
if (target == RV64LE::target_name)
|
||||
return elf_main<RV64LE>(argc, argv);
|
||||
if (target == RV64BE::target_name)
|
||||
return elf_main<RV64BE>(argc, argv);
|
||||
if (target == RV32LE::target_name)
|
||||
return elf_main<RV32LE>(argc, argv);
|
||||
if (target == RV32BE::target_name)
|
||||
return elf_main<RV32BE>(argc, argv);
|
||||
if (target == PPC32::target_name)
|
||||
return elf_main<PPC32>(argc, argv);
|
||||
if (target == PPC64V1::target_name)
|
||||
return elf_main<PPC64V1>(argc, argv);
|
||||
if (target == PPC64V2::target_name)
|
||||
return elf_main<PPC64V2>(argc, argv);
|
||||
if (target == S390X::target_name)
|
||||
return elf_main<S390X>(argc, argv);
|
||||
if (target == SPARC64::target_name)
|
||||
return elf_main<SPARC64>(argc, argv);
|
||||
if (target == M68K::target_name)
|
||||
return elf_main<M68K>(argc, argv);
|
||||
if (target == SH4::target_name)
|
||||
return elf_main<SH4>(argc, argv);
|
||||
if (target == ALPHA::target_name)
|
||||
return elf_main<ALPHA>(argc, argv);
|
||||
if (target == LOONGARCH32::target_name)
|
||||
return elf_main<LOONGARCH32>(argc, argv);
|
||||
if (target == LOONGARCH64::target_name)
|
||||
return elf_main<LOONGARCH64>(argc, argv);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void apply_exclude_libs(Context<E> &ctx) {
|
||||
Timer t(ctx, "apply_exclude_libs");
|
||||
@@ -31,6 +75,14 @@ void apply_exclude_libs(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool has_debug_info_section(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->debug_info)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void create_synthetic_sections(Context<E> &ctx) {
|
||||
auto push = [&](auto *x) {
|
||||
@@ -91,7 +143,7 @@ void create_synthetic_sections(Context<E> &ctx) {
|
||||
ctx.buildid = push(new BuildIdSection<E>);
|
||||
if (ctx.arg.eh_frame_hdr)
|
||||
ctx.eh_frame_hdr = push(new EhFrameHdrSection<E>);
|
||||
if (ctx.arg.gdb_index)
|
||||
if (ctx.arg.gdb_index && has_debug_info_section(ctx))
|
||||
ctx.gdb_index = push(new GdbIndexSection<E>);
|
||||
if (ctx.arg.z_relro && ctx.arg.section_order.empty() &&
|
||||
ctx.arg.z_separate_code != SEPARATE_LOADABLE_SEGMENTS)
|
||||
@@ -137,15 +189,13 @@ void create_synthetic_sections(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
static void mark_live_objects(Context<E> &ctx) {
|
||||
auto mark_symbol = [&](std::string_view name) {
|
||||
if (InputFile<E> *file = get_symbol(ctx, name)->file)
|
||||
file->is_alive = true;
|
||||
};
|
||||
for (Symbol<E> *sym : ctx.arg.undefined)
|
||||
if (sym->file)
|
||||
sym->file->is_alive = true;
|
||||
|
||||
for (std::string_view name : ctx.arg.undefined)
|
||||
mark_symbol(name);
|
||||
for (std::string_view name : ctx.arg.require_defined)
|
||||
mark_symbol(name);
|
||||
for (Symbol<E> *sym : ctx.arg.require_defined)
|
||||
if (sym->file)
|
||||
sym->file->is_alive = true;
|
||||
|
||||
std::vector<InputFile<E> *> roots;
|
||||
|
||||
@@ -345,7 +395,7 @@ template <typename E>
|
||||
void add_comment_string(Context<E> &ctx, std::string str) {
|
||||
MergedSection<E> *sec =
|
||||
MergedSection<E>::get_instance(ctx, ".comment", SHT_PROGBITS,
|
||||
SHF_MERGE | SHF_STRINGS);
|
||||
SHF_MERGE | SHF_STRINGS, 1, 1);
|
||||
|
||||
std::string_view buf = save_string(ctx, str);
|
||||
std::string_view data(buf.data(), buf.size() + 1);
|
||||
@@ -458,9 +508,31 @@ get_output_name(Context<E> &ctx, std::string_view name, u64 flags) {
|
||||
return name;
|
||||
}
|
||||
|
||||
// Returns true if a given input section is a .ctors/.dtors that
|
||||
// should be put into .init_array/.fini_array.
|
||||
//
|
||||
// CRT object files contain .ctors/.dtors sections without any
|
||||
// relocations. They contain sentinel values, 0 and -1, to mark the
|
||||
// beginning and the end of the initializer/finalizer pointer arrays. We
|
||||
// do not place them into .init_array/.fini_array because such invalid
|
||||
// pointer values would simply make the program to crash.
|
||||
template <typename E>
|
||||
static bool is_ctors_in_init_array(Context<E> &ctx, InputSection<E> &isec) {
|
||||
std::string_view name = isec.name();
|
||||
return ctx.has_init_array && !isec.get_rels(ctx).empty() &&
|
||||
(name == ".ctors" || name.starts_with(".ctors.") ||
|
||||
name == ".dtors" || name.starts_with(".dtors."));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static OutputSectionKey
|
||||
get_output_section_key(Context<E> &ctx, InputSection<E> &isec) {
|
||||
if (is_ctors_in_init_array(ctx, isec)) {
|
||||
if (isec.name().starts_with(".ctors"))
|
||||
return {".init_array", SHT_INIT_ARRAY, SHF_ALLOC | SHF_WRITE};
|
||||
return {".fini_array", SHT_FINI_ARRAY, SHF_ALLOC | SHF_WRITE};
|
||||
}
|
||||
|
||||
const ElfShdr<E> &shdr = isec.shdr();
|
||||
std::string_view name = get_output_name(ctx, isec.name(), shdr.sh_flags);
|
||||
u64 type = canonicalize_type<E>(name, shdr.sh_type);
|
||||
@@ -471,6 +543,7 @@ get_output_section_key(Context<E> &ctx, InputSection<E> &isec) {
|
||||
// So is .fini_array.
|
||||
if (type == SHT_INIT_ARRAY || type == SHT_FINI_ARRAY)
|
||||
flags |= SHF_WRITE;
|
||||
|
||||
return {name, type, flags};
|
||||
}
|
||||
|
||||
@@ -609,34 +682,9 @@ void create_internal_file(Context<E> &ctx) {
|
||||
ctx.internal_esyms.push_back(esym);
|
||||
};
|
||||
|
||||
auto add_undef = [&](Symbol<E> *sym) {
|
||||
obj->symbols.push_back(sym);
|
||||
sym->value = 0xdeadbeef;
|
||||
|
||||
ElfSym<E> esym;
|
||||
memset(&esym, 0, sizeof(esym));
|
||||
esym.st_type = STT_NOTYPE;
|
||||
esym.st_shndx = SHN_UNDEF;
|
||||
esym.st_bind = STB_GLOBAL;
|
||||
esym.st_visibility = STV_DEFAULT;
|
||||
ctx.internal_esyms.push_back(esym);
|
||||
};
|
||||
|
||||
// Add --defsym symbols
|
||||
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++) {
|
||||
Symbol<E> *sym = ctx.arg.defsyms[i].first;
|
||||
std::variant<Symbol<E> *, u64> val = ctx.arg.defsyms[i].second;
|
||||
add(sym);
|
||||
|
||||
if (Symbol<E> **ref = std::get_if<Symbol<E> *>(&val)) {
|
||||
// Add an undefined symbol to keep a reference to the defsym target.
|
||||
// This prevents elimination by e.g. LTO or gc-sections.
|
||||
// The undefined symbol will never make to the final object file; we
|
||||
// double-check that the defsym target is not undefined in
|
||||
// fix_synthetic_symbols.
|
||||
add_undef(*ref);
|
||||
}
|
||||
}
|
||||
// Add --defsym'd symbols
|
||||
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++)
|
||||
add(ctx.arg.defsyms[i].first);
|
||||
|
||||
// Add --section-order symbols
|
||||
for (SectionOrder &ord : ctx.arg.section_order)
|
||||
@@ -769,26 +817,27 @@ void add_synthetic_symbols(Context<E> &ctx) {
|
||||
|
||||
// Handle --defsym symbols.
|
||||
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++) {
|
||||
Symbol<E> *sym = ctx.arg.defsyms[i].first;
|
||||
Symbol<E> *sym1 = ctx.arg.defsyms[i].first;
|
||||
std::variant<Symbol<E> *, u64> val = ctx.arg.defsyms[i].second;
|
||||
|
||||
Symbol<E> *target = nullptr;
|
||||
if (Symbol<E> **ref = std::get_if<Symbol<E> *>(&val))
|
||||
target = *ref;
|
||||
if (Symbol<E> **ref = std::get_if<Symbol<E> *>(&val)) {
|
||||
Symbol<E> *sym2 = *ref;
|
||||
if (!sym2->file) {
|
||||
Error(ctx) << "--defsym: undefined symbol: " << *sym2;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the alias refers another symobl, copy ELF symbol attributes.
|
||||
if (target) {
|
||||
ElfSym<E> &esym = obj.elf_syms[i + 1];
|
||||
esym.st_type = target->esym().st_type;
|
||||
esym.st_type = sym2->esym().st_type;
|
||||
if constexpr (is_ppc64v2<E>)
|
||||
esym.ppc_local_entry = target->esym().ppc_local_entry;
|
||||
esym.ppc_local_entry = sym2->esym().ppc_local_entry;
|
||||
|
||||
if (sym2->is_absolute())
|
||||
sym1->origin = 0;
|
||||
} else {
|
||||
sym1->origin = 0;
|
||||
}
|
||||
|
||||
// Make the target absolute if necessary.
|
||||
if (!target || target->is_absolute())
|
||||
sym->origin = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -982,8 +1031,8 @@ void check_symbol_types(Context<E> &ctx) {
|
||||
const ElfSym<E> &esym1 = sym.esym();
|
||||
const ElfSym<E> &esym2 = file->elf_syms[i];
|
||||
|
||||
u32 ty1 = (esym1.st_type == STT_GNU_IFUNC) ? STT_FUNC : esym1.st_type;
|
||||
u32 ty2 = (esym2.st_type == STT_GNU_IFUNC) ? STT_FUNC : esym2.st_type;
|
||||
u32 ty1 = (esym1.st_type == STT_GNU_IFUNC) ? (u32)STT_FUNC : esym1.st_type;
|
||||
u32 ty2 = (esym2.st_type == STT_GNU_IFUNC) ? (u32)STT_FUNC : esym2.st_type;
|
||||
|
||||
if (ty1 != STT_NOTYPE && ty2 != STT_NOTYPE && ty1 != ty2)
|
||||
Warn(ctx) << "symbol type mismatch: " << sym << '\n'
|
||||
@@ -995,19 +1044,42 @@ void check_symbol_types(Context<E> &ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 get_init_fini_priority(InputSection<E> *isec) {
|
||||
static std::regex re(R"(\.(\d+)$)", std::regex_constants::optimize);
|
||||
std::string_view name = isec->name();
|
||||
std::cmatch m;
|
||||
if (std::regex_search(name.data(), name.data() + name.size(), m, re))
|
||||
return std::stoi(m[1]);
|
||||
return 65536;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 get_ctor_dtor_priority(InputSection<E> *isec) {
|
||||
auto opts = std::regex_constants::optimize | std::regex_constants::ECMAScript;
|
||||
static std::regex re1(R"((?:clang_rt\.)?crtbegin)", opts);
|
||||
static std::regex re2(R"((?:clang_rt\.)?crtend)", opts);
|
||||
static std::regex re3(R"(\.(\d+)$)", opts);
|
||||
|
||||
// crtbegin.o and crtend.o contain marker symbols such as
|
||||
// __CTOR_LIST__ or __DTOR_LIST__. So they have to be at the
|
||||
// beginning or end of the section.
|
||||
std::smatch m;
|
||||
if (std::regex_search(isec->file.filename, m, re1))
|
||||
return -2;
|
||||
if (std::regex_search(isec->file.filename, m, re2))
|
||||
return 65536;
|
||||
|
||||
std::string name(isec->name());
|
||||
if (std::regex_search(name, m, re3))
|
||||
return std::stoi(m[1]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void sort_init_fini(Context<E> &ctx) {
|
||||
Timer t(ctx, "sort_init_fini");
|
||||
|
||||
auto get_priority = [](InputSection<E> *isec) {
|
||||
static std::regex re(R"(\.(\d+)$)", std::regex_constants::optimize);
|
||||
std::string_view name = isec->name();
|
||||
std::cmatch m;
|
||||
if (std::regex_search(name.data(), name.data() + name.size(), m, re))
|
||||
return std::stoi(m[1]);
|
||||
return 65536;
|
||||
};
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (OutputSection<E> *osec = chunk->to_osec()) {
|
||||
if (osec->name == ".init_array" || osec->name == ".preinit_array" ||
|
||||
@@ -1016,8 +1088,14 @@ void sort_init_fini(Context<E> &ctx) {
|
||||
std::reverse(osec->members.begin(), osec->members.end());
|
||||
|
||||
std::unordered_map<InputSection<E> *, i64> map;
|
||||
for (InputSection<E> *isec : osec->members)
|
||||
map.insert({isec, get_priority(isec)});
|
||||
|
||||
for (InputSection<E> *isec : osec->members) {
|
||||
std::string_view name = isec->name();
|
||||
if (name.starts_with(".ctors") || name.starts_with(".dtors"))
|
||||
map.insert({isec, 65535 - get_ctor_dtor_priority(isec)});
|
||||
else
|
||||
map.insert({isec, get_init_fini_priority(isec)});
|
||||
}
|
||||
|
||||
sort(osec->members, [&](InputSection<E> *a, InputSection<E> *b) {
|
||||
return map[a] < map[b];
|
||||
@@ -1031,27 +1109,6 @@ template <typename E>
|
||||
void sort_ctor_dtor(Context<E> &ctx) {
|
||||
Timer t(ctx, "sort_ctor_dtor");
|
||||
|
||||
auto get_priority = [](InputSection<E> *isec) {
|
||||
auto opts = std::regex_constants::optimize | std::regex_constants::ECMAScript;
|
||||
static std::regex re1(R"((?:clang_rt\.)?crtbegin)", opts);
|
||||
static std::regex re2(R"((?:clang_rt\.)?crtend)", opts);
|
||||
static std::regex re3(R"(\.(\d+)$)", opts);
|
||||
|
||||
// crtbegin.o and crtend.o contain marker symbols such as
|
||||
// __CTOR_LIST__ or __DTOR_LIST__. So they have to be at the
|
||||
// beginning or end of the section.
|
||||
std::smatch m;
|
||||
if (std::regex_search(isec->file.filename, m, re1))
|
||||
return -2;
|
||||
if (std::regex_search(isec->file.filename, m, re2))
|
||||
return 65536;
|
||||
|
||||
std::string name(isec->name());
|
||||
if (std::regex_search(name, m, re3))
|
||||
return std::stoi(m[1]);
|
||||
return -1;
|
||||
};
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (OutputSection<E> *osec = chunk->to_osec()) {
|
||||
if (osec->name == ".ctors" || osec->name == ".dtors") {
|
||||
@@ -1060,7 +1117,7 @@ void sort_ctor_dtor(Context<E> &ctx) {
|
||||
|
||||
std::unordered_map<InputSection<E> *, i64> map;
|
||||
for (InputSection<E> *isec : osec->members)
|
||||
map.insert({isec, get_priority(isec)});
|
||||
map.insert({isec, get_ctor_dtor_priority(isec)});
|
||||
|
||||
sort(osec->members, [&](InputSection<E> *a, InputSection<E> *b) {
|
||||
return map[a] < map[b];
|
||||
@@ -1070,6 +1127,45 @@ void sort_ctor_dtor(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// .ctors/.dtors serves the same purpose as .init_array/.fini_array,
|
||||
// albeit with very subtly differences. Both contain pointers to
|
||||
// initializer/finalizer functions. The runtime executes them one by one
|
||||
// but in the exact opposite order to one another. Therefore, if we are to
|
||||
// place the contents of .ctors/.dtors into .init_array/.fini_array, we
|
||||
// need to reverse them.
|
||||
//
|
||||
// It's unfortunate that we have both .ctors/.dtors and
|
||||
// .init_array/.fini_array in ELF for historical reasons, but that's
|
||||
// the reality we need to deal with.
|
||||
template <typename E>
|
||||
void fixup_ctors_in_init_array(Context<E> &ctx) {
|
||||
Timer t(ctx, "fixup_ctors_in_init_array");
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (OutputSection<E> *osec = chunk->to_osec()) {
|
||||
if (osec->name == ".init_array" || osec->name == ".fini_array") {
|
||||
for (InputSection<E> *isec : osec->members) {
|
||||
if (isec->name().starts_with(".ctors") ||
|
||||
isec->name().starts_with(".dtors")) {
|
||||
if (isec->sh_size % sizeof(Word<E>)) {
|
||||
Error(ctx) << *isec << ": section corrupted";
|
||||
continue;
|
||||
}
|
||||
|
||||
u8 *buf = (u8 *)isec->contents.data();
|
||||
std::reverse((Word<E> *)buf, (Word<E> *)(buf + isec->sh_size));
|
||||
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
for (ElfRel<E> &r : rels)
|
||||
r.r_offset = isec->sh_size - r.r_offset - sizeof(Word<E>);
|
||||
std::reverse(rels.begin(), rels.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void shuffle(std::vector<T> &vec, u64 seed) {
|
||||
if (vec.empty())
|
||||
@@ -1198,12 +1294,15 @@ void compute_section_sizes(Context<E> &ctx) {
|
||||
// inserting thunks. This pass cannot be parallelized. That is,
|
||||
// create_range_extension_thunks is parallelized internally, but the
|
||||
// function itself is not thread-safe.
|
||||
if constexpr (needs_thunk<E>)
|
||||
if constexpr (needs_thunk<E>) {
|
||||
Timer t2(ctx, "create_range_extension_thunks");
|
||||
|
||||
if (!ctx.arg.relocatable)
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
if (osec->shdr.sh_flags & SHF_EXECINSTR)
|
||||
osec->create_range_extension_thunks(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// Find all unresolved symbols and attach them to the most appropriate files.
|
||||
@@ -1480,9 +1579,11 @@ template <typename E>
|
||||
void create_output_symtab(Context<E> &ctx) {
|
||||
Timer t(ctx, "compute_symtab_size");
|
||||
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
chunk->compute_symtab_size(ctx);
|
||||
});
|
||||
if (!ctx.arg.strip_all && !ctx.arg.retain_symbols_file) {
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
chunk->compute_symtab_size(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->compute_symtab_size(ctx);
|
||||
@@ -1497,9 +1598,6 @@ template <typename E>
|
||||
void apply_version_script(Context<E> &ctx) {
|
||||
Timer t(ctx, "apply_version_script");
|
||||
|
||||
// If all patterns are simple (i.e. not containing any meta-
|
||||
// characters and is not a C++ name), we can simply look up
|
||||
// symbols.
|
||||
auto is_simple = [&] {
|
||||
for (VersionPattern &v : ctx.version_patterns)
|
||||
if (v.is_cpp || v.pattern.find_first_of("*?[") != v.pattern.npos)
|
||||
@@ -1507,6 +1605,9 @@ void apply_version_script(Context<E> &ctx) {
|
||||
return true;
|
||||
};
|
||||
|
||||
// If all patterns are simple (i.e. not containing any meta-
|
||||
// characters and is not a C++ name), we can simply look up
|
||||
// symbols.
|
||||
if (is_simple()) {
|
||||
for (VersionPattern &v : ctx.version_patterns) {
|
||||
Symbol<E> *sym = get_symbol(ctx, v.pattern);
|
||||
@@ -1632,52 +1733,205 @@ void compute_import_export(Context<E> &ctx) {
|
||||
if (!ctx.arg.shared) {
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file && !sym->file->is_dso && sym->visibility != STV_HIDDEN) {
|
||||
if (sym->ver_idx != VER_NDX_LOCAL ||
|
||||
!ctx.default_version_from_version_script) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->is_exported = true;
|
||||
}
|
||||
if (sym->file && !sym->file->is_dso && sym->visibility != STV_HIDDEN &&
|
||||
sym->ver_idx != VER_NDX_LOCAL) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->is_exported = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
auto should_export = [&](Symbol<E> &sym) {
|
||||
if (sym.visibility == STV_HIDDEN)
|
||||
return false;
|
||||
|
||||
switch (sym.ver_idx) {
|
||||
case VER_NDX_UNSPECIFIED:
|
||||
if (ctx.arg.shared)
|
||||
return !((ObjectFile<E> *)sym.file)->exclude_libs;
|
||||
return ctx.arg.export_dynamic;
|
||||
case VER_NDX_LOCAL:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Export symbols that are not hidden or marked as local.
|
||||
// We also want to mark imported symbols as such.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->get_global_syms()) {
|
||||
if (!sym->file || sym->visibility == STV_HIDDEN ||
|
||||
sym->ver_idx == VER_NDX_LOCAL)
|
||||
continue;
|
||||
|
||||
// If we are using a symbol in a DSO, we need to import it at runtime.
|
||||
if (sym->file != file && sym->file->is_dso && !sym->is_absolute()) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->is_imported = true;
|
||||
// If we are using a symbol in a DSO, we need to import it.
|
||||
if (sym->file && sym->file->is_dso) {
|
||||
if (!sym->is_absolute()) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->is_imported = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we are creating a DSO, all global symbols are exported by default.
|
||||
if (sym->file == file) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
// If we have a definition of a symbol, we may want to export it.
|
||||
if (sym->file == file && should_export(*sym)) {
|
||||
sym->is_exported = true;
|
||||
|
||||
if (ctx.arg.shared && sym->visibility != STV_PROTECTED &&
|
||||
// Exported symbols are marked as imported as well by default
|
||||
// for DSOs.
|
||||
if (ctx.arg.shared &&
|
||||
sym->visibility != STV_PROTECTED &&
|
||||
!ctx.arg.Bsymbolic &&
|
||||
!(ctx.arg.Bsymbolic_functions && sym->get_type() == STT_FUNC))
|
||||
sym->is_imported = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Apply --dynamic-list, --export-dynamic-symbol and
|
||||
// --export-dynamic-symbol-list options.
|
||||
//
|
||||
// The semantics of these options vary depending on whether we are
|
||||
// creating an executalbe or a shared object.
|
||||
//
|
||||
// For executable, matched symbols are exported.
|
||||
//
|
||||
// For shared objects, matched symbols are imported if it is already
|
||||
// exported so that they are interposable. In other words, symbols
|
||||
// that did not match will be bound locally within the output file,
|
||||
// effectively turning them into protected symbols.
|
||||
MultiGlob matcher;
|
||||
MultiGlob cpp_matcher;
|
||||
|
||||
auto handle_match = [&](Symbol<E> *sym) {
|
||||
if (ctx.arg.shared) {
|
||||
if (sym->is_exported)
|
||||
sym->is_imported = true;
|
||||
} else {
|
||||
if (sym->file && !sym->file->is_dso && sym->visibility != STV_HIDDEN)
|
||||
sym->is_exported = true;
|
||||
}
|
||||
};
|
||||
|
||||
for (DynamicPattern &p : ctx.dynamic_list_patterns) {
|
||||
if (p.is_cpp) {
|
||||
if (!cpp_matcher.add(p.pattern, 1))
|
||||
Fatal(ctx) << p.source << ": invalid dynamic list entry: "
|
||||
<< p.pattern;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p.pattern.find_first_of("*?[") != p.pattern.npos) {
|
||||
if (!matcher.add(p.pattern, 1))
|
||||
Fatal(ctx) << p.source << ": invalid dynamic list entry: "
|
||||
<< p.pattern;
|
||||
continue;
|
||||
}
|
||||
|
||||
handle_match(get_symbol(ctx, p.pattern));
|
||||
}
|
||||
|
||||
if (!matcher.empty() || !cpp_matcher.empty()) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->get_global_syms()) {
|
||||
if (sym->file != file)
|
||||
continue;
|
||||
if (ctx.arg.shared && !sym->is_exported)
|
||||
continue;
|
||||
|
||||
std::string_view name = sym->name();
|
||||
|
||||
if (matcher.find(name)) {
|
||||
handle_match(sym);
|
||||
} else if (!cpp_matcher.empty()) {
|
||||
if (std::optional<std::string_view> s = cpp_demangle(name))
|
||||
name = *s;
|
||||
if (cpp_matcher.find(name))
|
||||
handle_match(sym);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the "address-taken" bit for each input section.
|
||||
//
|
||||
// As a space-saving optimization, we want to merge two read-only objects
|
||||
// into a single object if their contents are equivalent. That
|
||||
// optimization is called the Identical Code Folding or ICF.
|
||||
//
|
||||
// A catch is that comparing object contents is not enough to determine if
|
||||
// two objects can be merged safely; we need to take care of pointer
|
||||
// equivalence.
|
||||
//
|
||||
// In C/C++, two pointers are equivalent if and only if they are taken for
|
||||
// the same object. Merging two objects into a single object can break
|
||||
// this assumption because two distinctive pointers would become
|
||||
// equivalent as a result of merging. We can still merge one object with
|
||||
// another if no pointer to the object was taken in code, because without
|
||||
// a pointer, comparing its address becomes moot.
|
||||
//
|
||||
// In mold, each input section has an "address-taken" bit. If there is a
|
||||
// pointer-taking reference to the object, it's set to true. At the ICF
|
||||
// stage, we merge only objects whose addresses were not taken.
|
||||
//
|
||||
// For functions, address-taking relocations are separated from
|
||||
// non-address-taking ones. For example, x86-64 uses R_X86_64_PLT32 for
|
||||
// direct function calls (e.g., "call foo" to call the function foo) while
|
||||
// R_X86_64_PC32 or R_X86_64_GOT32 are used for pointer-taking operations.
|
||||
//
|
||||
// Unfortunately, for data, we can't distinguish between address-taking
|
||||
// relocations and non-address-taking ones. LLVM generates an "address
|
||||
// significance" table in the ".llvm_addrsig" section to mark symbols
|
||||
// whose addresses are taken in code. If that table is available, we use
|
||||
// that information in this function. Otherwise, we conservatively assume
|
||||
// that all data items are address-taken.
|
||||
template <typename E>
|
||||
void mark_addrsig(Context<E> &ctx) {
|
||||
Timer t(ctx, "mark_addrsig");
|
||||
void compute_address_significance(Context<E> &ctx) {
|
||||
Timer t(ctx, "compute_address_significance");
|
||||
|
||||
// Flip address-taken bit for executable sections first.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->mark_addrsig(ctx);
|
||||
for (std::unique_ptr<InputSection<E>> &src : file->sections)
|
||||
if (src && src->is_alive && (src->shdr().sh_flags & SHF_ALLOC))
|
||||
for (const ElfRel<E> &r : src->get_rels(ctx))
|
||||
if (!is_func_call_rel(r))
|
||||
if (InputSection<E> *dst = file->symbols[r.r_sym]->get_input_section())
|
||||
if (dst->shdr().sh_flags & SHF_EXECINSTR)
|
||||
dst->address_taken = true;
|
||||
});
|
||||
|
||||
auto mark = [](Symbol<E> *sym) {
|
||||
if (sym)
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
isec->address_taken = true;
|
||||
};
|
||||
|
||||
// Some symbols' pointer values are leaked to the dynamic section.
|
||||
mark(get_symbol(ctx, ctx.arg.entry));
|
||||
mark(get_symbol(ctx, ctx.arg.init));
|
||||
mark(get_symbol(ctx, ctx.arg.fini));
|
||||
|
||||
// Exported symbols are conservatively considered address-taken.
|
||||
if (ctx.dynsym)
|
||||
for (Symbol<E> *sym : ctx.dynsym->symbols)
|
||||
if (sym->is_exported)
|
||||
mark(sym);
|
||||
|
||||
// Handle data objects.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
if (InputSection<E> *sec = file->llvm_addrsig.get()) {
|
||||
u8 *p = (u8 *)sec->contents.data();
|
||||
u8 *end = p + sec->contents.size();
|
||||
while (p != end) {
|
||||
Symbol<E> *sym = file->symbols[read_uleb(&p)];
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
isec->address_taken = true;
|
||||
}
|
||||
} else {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (isec && !(isec->shdr().sh_flags & SHF_EXECINSTR))
|
||||
isec->address_taken = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1729,6 +1983,7 @@ void clear_padding(Context<E> &ctx) {
|
||||
// <writable non-RELRO bss>
|
||||
// <non-memory-allocated sections>
|
||||
// <section header>
|
||||
// .gdb_index
|
||||
//
|
||||
// .interp and some other linker-synthesized sections are placed at the
|
||||
// beginning of a file because they are needed by loader. Especially on
|
||||
@@ -1740,6 +1995,10 @@ void clear_padding(Context<E> &ctx) {
|
||||
// particular, if .note.gnu.build-id is in a truncated core file, you
|
||||
// can at least identify which executable has crashed.
|
||||
//
|
||||
// .gdb_index cannot be constructed before applying relocations to
|
||||
// other debug sections, so we create it after completing other part
|
||||
// of the output file and append it to the very end of the file.
|
||||
//
|
||||
// A PT_NOTE segment will contain multiple .note sections if exists,
|
||||
// but there's no way to represent a gap between .note sections.
|
||||
// Therefore, we sort .note sections by decreasing alignment
|
||||
@@ -1785,6 +2044,8 @@ void sort_output_sections_regular(Context<E> &ctx) {
|
||||
if (chunk == ctx.relplt)
|
||||
return 11;
|
||||
if (chunk == ctx.shdr)
|
||||
return INT32_MAX - 1;
|
||||
if (chunk == ctx.gdb_index)
|
||||
return INT32_MAX;
|
||||
|
||||
bool alloc = (flags & SHF_ALLOC);
|
||||
@@ -1800,23 +2061,31 @@ void sort_output_sections_regular(Context<E> &ctx) {
|
||||
|
||||
// Ties are broken by additional rules
|
||||
auto get_rank2 = [&](Chunk<E> *chunk) -> i64 {
|
||||
if (chunk->shdr.sh_type == SHT_NOTE)
|
||||
return -chunk->shdr.sh_addralign;
|
||||
ElfShdr<E> &shdr = chunk->shdr;
|
||||
if (shdr.sh_type == SHT_NOTE)
|
||||
return -shdr.sh_addralign;
|
||||
|
||||
if (chunk == ctx.got)
|
||||
return 1;
|
||||
if (chunk->name == ".toc")
|
||||
return 2;
|
||||
if (chunk->name == ".alpha_got")
|
||||
if (chunk->name == ".toc")
|
||||
return 3;
|
||||
if (chunk->name == ".alpha_got")
|
||||
return 4;
|
||||
|
||||
if (shdr.sh_flags & SHF_MERGE) {
|
||||
if (shdr.sh_flags & SHF_STRINGS)
|
||||
return (5LL << 32) | shdr.sh_entsize;
|
||||
return (6LL << 32) | shdr.sh_entsize;
|
||||
}
|
||||
|
||||
if (chunk == ctx.relro_padding)
|
||||
return INT_MAX;
|
||||
return INT64_MAX;
|
||||
return 0;
|
||||
};
|
||||
|
||||
sort(ctx.chunks, [&](Chunk<E> *a, Chunk<E> *b) {
|
||||
return std::tuple{get_rank1(a), get_rank2(a)} <
|
||||
std::tuple{get_rank1(b), get_rank2(b)};
|
||||
return std::tuple{get_rank1(a), get_rank2(a), a->name} <
|
||||
std::tuple{get_rank1(b), get_rank2(b), b->name};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2188,8 +2457,9 @@ void compute_section_headers(Context<E> &ctx) {
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
// Remove empty chunks.
|
||||
std::erase_if(ctx.chunks, [](Chunk<E> *chunk) {
|
||||
return chunk->kind() != OUTPUT_SECTION && chunk->shdr.sh_size == 0;
|
||||
std::erase_if(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
return chunk->kind() != OUTPUT_SECTION && chunk != ctx.gdb_index &&
|
||||
chunk->shdr.sh_size == 0;
|
||||
});
|
||||
|
||||
// Set section indices.
|
||||
@@ -2446,18 +2716,12 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
if (u64 *addr = std::get_if<u64>(&val)) {
|
||||
sym->origin = 0;
|
||||
sym->value = *addr;
|
||||
continue;
|
||||
} else {
|
||||
Symbol<E> *sym2 = std::get<Symbol<E> *>(val);
|
||||
sym->value = sym2->value;
|
||||
sym->origin = sym2->origin;
|
||||
sym->visibility = sym2->visibility.load();
|
||||
}
|
||||
|
||||
Symbol<E> *sym2 = std::get<Symbol<E> *>(val);
|
||||
if (!sym2->file) {
|
||||
Error(ctx) << "--defsym: undefined symbol: " << *sym2;
|
||||
continue;
|
||||
}
|
||||
|
||||
sym->value = sym2->value;
|
||||
sym->origin = sym2->origin;
|
||||
sym->visibility = sym2->visibility.load();
|
||||
}
|
||||
|
||||
|
||||
@@ -2592,6 +2856,7 @@ void show_stats(Context<E> &ctx) {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template int redo_main(Context<E> &, int, char **);
|
||||
template void create_internal_file(Context<E> &);
|
||||
template void apply_exclude_libs(Context<E> &);
|
||||
template void create_synthetic_sections(Context<E> &);
|
||||
@@ -2609,6 +2874,7 @@ template void check_duplicate_symbols(Context<E> &);
|
||||
template void check_symbol_types(Context<E> &);
|
||||
template void sort_init_fini(Context<E> &);
|
||||
template void sort_ctor_dtor(Context<E> &);
|
||||
template void fixup_ctors_in_init_array(Context<E> &);
|
||||
template void shuffle_sections(Context<E> &);
|
||||
template void compute_section_sizes(Context<E> &);
|
||||
template void sort_output_sections(Context<E> &);
|
||||
@@ -2622,7 +2888,7 @@ template void create_output_symtab(Context<E> &);
|
||||
template void apply_version_script(Context<E> &);
|
||||
template void parse_symbol_version(Context<E> &);
|
||||
template void compute_import_export(Context<E> &);
|
||||
template void mark_addrsig(Context<E> &);
|
||||
template void compute_address_significance(Context<E> &);
|
||||
template void clear_padding(Context<E> &);
|
||||
template void compute_section_headers(Context<E> &);
|
||||
template i64 set_osec_offsets(Context<E> &);
|
||||
|
||||
+20
-38
@@ -68,27 +68,6 @@ static constexpr i64 max_thunk_size = 102400;
|
||||
|
||||
static_assert(max_thunk_size / E::thunk_size < INT16_MAX);
|
||||
|
||||
// Returns true if a given relocation is of type used for function calls.
|
||||
template <typename E>
|
||||
static bool needs_thunk_rel(const ElfRel<E> &r) {
|
||||
u32 ty = r.r_type;
|
||||
|
||||
if constexpr (is_arm64<E>) {
|
||||
return ty == R_AARCH64_JUMP26 || ty == R_AARCH64_CALL26;
|
||||
} else if constexpr (is_arm32<E>) {
|
||||
return ty == R_ARM_JUMP24 || ty == R_ARM_THM_JUMP24 ||
|
||||
ty == R_ARM_CALL || ty == R_ARM_THM_CALL ||
|
||||
ty == R_ARM_PLT32;
|
||||
} else if constexpr (is_ppc32<E>) {
|
||||
return ty == R_PPC_REL24 || ty == R_PPC_PLTREL24 || ty == R_PPC_LOCAL24PC;
|
||||
} else if constexpr (is_ppc64<E>) {
|
||||
return ty == R_PPC64_REL24 || ty == R_PPC64_REL24_NOTOC;
|
||||
} else {
|
||||
static_assert(is_loongarch<E>);
|
||||
return ty == R_LARCH_B26;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
|
||||
Symbol<E> &sym, const ElfRel<E> &rel) {
|
||||
@@ -157,7 +136,7 @@ static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (!needs_thunk_rel(rel))
|
||||
if (!is_func_call_rel(rel))
|
||||
continue;
|
||||
|
||||
// Skip if the symbol is undefined. apply_reloc() will report an error.
|
||||
@@ -191,17 +170,17 @@ static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
|
||||
|
||||
template <>
|
||||
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
using Thunk = RangeExtensionThunk<E>;
|
||||
|
||||
std::span<InputSection<E> *> m = members;
|
||||
if (m.empty())
|
||||
return;
|
||||
|
||||
m[0]->offset = 0;
|
||||
|
||||
// Initialize input sections with a dummy offset so that we can
|
||||
// distinguish sections that have got an address with the one who
|
||||
// haven't.
|
||||
tbb::parallel_for((i64)1, (i64)m.size(), [&](i64 i) {
|
||||
m[i]->offset = -1;
|
||||
tbb::parallel_for_each(m, [](InputSection<E> *isec) {
|
||||
isec->offset = -1;
|
||||
});
|
||||
|
||||
// We create thunks from the beginning of the section to the end.
|
||||
@@ -211,7 +190,7 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
// Input sections between B and C are in the current batch.
|
||||
//
|
||||
// A is the input section with the smallest address than can reach
|
||||
// anywhere from the current batch.
|
||||
// from the current batch.
|
||||
//
|
||||
// D is the input section with the largest address such that the thunk
|
||||
// is reachable from the current batch if it's inserted right before D.
|
||||
@@ -219,9 +198,9 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
// ................................ <input sections> ............
|
||||
// A B C D
|
||||
// ^ We insert a thunk for the current batch just before D
|
||||
// <---> The current batch, which is smaller than batch_size
|
||||
// <--------> Smaller than max_distance
|
||||
// <--------> Smaller than max_distance
|
||||
// <---> The current batch, which is smaller than BATCH_SIZE
|
||||
// <--------> Smaller than MAX_DISTANCE
|
||||
// <--------> Smaller than MAX_DISTANCE
|
||||
// <-------------> Reachable from the current batch
|
||||
i64 a = 0;
|
||||
i64 b = 0;
|
||||
@@ -233,10 +212,14 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
i64 t = 0;
|
||||
|
||||
while (b < m.size()) {
|
||||
// Move D foward as far as we can jump from B to anywhere in a thunk at D.
|
||||
// Move D foward as far as we can jump from B to a thunk at D.
|
||||
auto d_thunk_end = [&] {
|
||||
u64 d_end = align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size;
|
||||
return align_to(d_end, Thunk::alignment) + max_thunk_size;
|
||||
};
|
||||
|
||||
while (d < m.size() &&
|
||||
align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size + max_thunk_size <
|
||||
m[b]->offset + max_distance()) {
|
||||
(b == d || d_thunk_end() <= m[b]->offset + max_distance())) {
|
||||
offset = align_to(offset, 1 << m[d]->p2align);
|
||||
m[d]->offset = offset;
|
||||
offset += m[d]->sh_size;
|
||||
@@ -247,13 +230,12 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
// to make sure that there's at least one section between B and C
|
||||
// to ensure progress.
|
||||
c = b + 1;
|
||||
while (c < m.size() &&
|
||||
m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size)
|
||||
while (c < d && m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size)
|
||||
c++;
|
||||
|
||||
// Move A forward so that A is reachable from C.
|
||||
i64 c_offset = (c == m.size()) ? offset : m[c]->offset;
|
||||
while (a < m.size() && m[a]->offset + max_distance() < c_offset)
|
||||
while (a < b && m[a]->offset + max_distance() < c_offset)
|
||||
a++;
|
||||
|
||||
// Erase references to out-of-range thunks.
|
||||
@@ -261,9 +243,9 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
reset_thunk(*thunks[t++]);
|
||||
|
||||
// Create a new thunk and place it at D.
|
||||
offset = align_to(offset, RangeExtensionThunk<E>::alignment);
|
||||
offset = align_to(offset, Thunk::alignment);
|
||||
i64 thunk_idx = thunks.size();
|
||||
RangeExtensionThunk<E> *thunk = new RangeExtensionThunk<E>(*this, offset);
|
||||
Thunk *thunk = new Thunk(*this, offset);
|
||||
thunks.emplace_back(thunk);
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need
|
||||
|
||||
@@ -36,7 +36,7 @@ gentoo-*)
|
||||
emerge-webrsync
|
||||
emerge dev-util/cmake
|
||||
;;
|
||||
arch-* | archarm-*)
|
||||
arch-* | archarm-* | artix-*)
|
||||
pacman -Sy
|
||||
pacman -S --needed --noconfirm base-devel cmake util-linux
|
||||
;;
|
||||
|
||||
+159
-62
@@ -1,84 +1,181 @@
|
||||
option(MOLD_ENABLE_QEMU_TESTS "Enable tests on non-native targets" OFF)
|
||||
option(MOLD_ENABLE_QEMU_TESTS "Enable tests on non-native targets" ON)
|
||||
set(HOST ${CMAKE_HOST_SYSTEM_PROCESSOR})
|
||||
|
||||
function(add_target TRIPLE)
|
||||
set(HOST ${CMAKE_HOST_SYSTEM_PROCESSOR})
|
||||
if(EXISTS "/proc/cpuinfo")
|
||||
file(READ "/proc/cpuinfo" CPUINFO)
|
||||
endif()
|
||||
|
||||
if(${HOST} MATCHES "amd64")
|
||||
set(HOST x86_64)
|
||||
elseif(${HOST} MATCHES "arm.*")
|
||||
set(HOST arm)
|
||||
elseif(${HOST} STREQUAL "ppc64")
|
||||
set(HOST powerpc64)
|
||||
elseif(${HOST} STREQUAL "ppc64le")
|
||||
set(HOST powerpc64le)
|
||||
endif()
|
||||
# CMAKE_HOST_SYSTEM_PROCESSOR returns "aarch64" on ARM32 userland
|
||||
# with ARM64 kernel. Here, we run `cc -dumpmachine` to determine the
|
||||
# compiler's default target.
|
||||
execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpmachine
|
||||
RESULT_VARIABLE EXIT_CODE
|
||||
OUTPUT_VARIABLE ARCH
|
||||
ERROR_QUIET)
|
||||
if(NOT EXIT_CODE AND ARCH MATCHES "([^-]+).*")
|
||||
set(HOST ${CMAKE_MATCH_1})
|
||||
endif()
|
||||
|
||||
if(${TRIPLE} MATCHES "${HOST}-.*")
|
||||
if(${HOST} MATCHES "amd64")
|
||||
set(HOST x86_64)
|
||||
elseif(${HOST} MATCHES "i386")
|
||||
set(HOST i686)
|
||||
elseif(${HOST} MATCHES "arm.*")
|
||||
set(HOST arm)
|
||||
elseif(${HOST} STREQUAL "powerpc64")
|
||||
set(HOST ppc64)
|
||||
elseif(${HOST} STREQUAL "powerpc64le")
|
||||
set(HOST ppc64le)
|
||||
endif()
|
||||
|
||||
if(MOLD_ENABLE_QEMU_TESTS)
|
||||
list(APPEND QEMU_ARCHS
|
||||
x86_64 i386 arm aarch64 ppc ppc64 ppc64le sparc64 sh4 s390x
|
||||
alpha riscv64 riscv32 m68k loongarch64)
|
||||
|
||||
LIST(APPEND TRIPLES
|
||||
x86_64-linux-gnu
|
||||
i686-linux-gnu
|
||||
aarch64-linux-gnu
|
||||
arm-linux-gnueabihf
|
||||
riscv64-linux-gnu
|
||||
powerpc-linux-gnu
|
||||
powerpc64-linux-gnu
|
||||
powerpc64le-linux-gnu
|
||||
sparc64-linux-gnu
|
||||
s390x-linux-gnu
|
||||
sh4-linux-gnu
|
||||
alpha-linux-gnu
|
||||
riscv32-linux-gnu
|
||||
m68k-linux-gnu
|
||||
loongarch64-linux-gnu)
|
||||
|
||||
foreach(ARCH IN LISTS QEMU_ARCHS)
|
||||
find_program(HAS_qemu-${ARCH} qemu-${ARCH})
|
||||
endforeach()
|
||||
|
||||
foreach(TRIPLE IN LISTS TRIPLES)
|
||||
find_program(HAS_${TRIPLE}-gcc ${TRIPLE}-gcc)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
function(add_target ARCH TRIPLE)
|
||||
set(CPU ${ARGV2})
|
||||
|
||||
if(${ARCH} STREQUAL ${HOST})
|
||||
set(IS_NATIVE 1)
|
||||
endif()
|
||||
|
||||
if(${TRIPLE} MATCHES "([^-]+)-.")
|
||||
set(MACHINE ${CMAKE_MATCH_1})
|
||||
endif()
|
||||
file(GLOB ALL_TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
|
||||
"*.sh")
|
||||
|
||||
if(IS_NATIVE OR MOLD_ENABLE_QEMU_TESTS)
|
||||
file(GLOB ALL_TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
|
||||
"*.sh")
|
||||
list(FILTER ALL_TESTS EXCLUDE REGEX "_")
|
||||
|
||||
list(FILTER ALL_TESTS EXCLUDE REGEX "_")
|
||||
file(GLOB TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
|
||||
"${ARCH}_*.sh")
|
||||
|
||||
file(GLOB TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
|
||||
"${MACHINE}_*.sh")
|
||||
list(APPEND TESTS ${ALL_TESTS})
|
||||
|
||||
list(APPEND TESTS ${ALL_TESTS})
|
||||
foreach(TEST IN LISTS TESTS)
|
||||
if(CPU)
|
||||
string(REGEX REPLACE "\\.sh$" "" TESTNAME "${ARCH}_${CPU}-${TEST}")
|
||||
else()
|
||||
string(REGEX REPLACE "\\.sh$" "" TESTNAME "${ARCH}-${TEST}")
|
||||
endif()
|
||||
|
||||
foreach(TEST IN LISTS TESTS)
|
||||
string(REGEX REPLACE "\\.sh$" "" TESTNAME "${MACHINE}-${TEST}")
|
||||
add_test(NAME ${TESTNAME}
|
||||
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
|
||||
WORKING_DIRECTORY ${mold_BINARY_DIR})
|
||||
|
||||
add_test(NAME ${TESTNAME}
|
||||
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
|
||||
WORKING_DIRECTORY ${mold_BINARY_DIR})
|
||||
set_property(TEST ${TESTNAME} APPEND PROPERTY ENVIRONMENT
|
||||
"HOST=${HOST};CPU=${CPU}")
|
||||
|
||||
if(IS_NATIVE)
|
||||
set_tests_properties(${TESTNAME} PROPERTIES
|
||||
SKIP_REGULAR_EXPRESSION "skipped")
|
||||
else()
|
||||
set_tests_properties(${TESTNAME} PROPERTIES
|
||||
ENVIRONMENT "TRIPLE=${TRIPLE}")
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
if(IS_NATIVE)
|
||||
set_property(TEST ${TESTNAME} APPEND PROPERTY SKIP_REGULAR_EXPRESSION
|
||||
"skipped")
|
||||
else()
|
||||
set_property(TEST ${TESTNAME} APPEND PROPERTY ENVIRONMENT
|
||||
"TRIPLE=${TRIPLE}")
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
add_target(x86_64-linux-gnu)
|
||||
add_target(i686-linux-gnu)
|
||||
add_target(aarch64-linux-gnu)
|
||||
add_target(arm-linux-gnueabihf)
|
||||
add_target(riscv64-linux-gnu)
|
||||
add_target(powerpc-linux-gnu)
|
||||
add_target(powerpc64-linux-gnu)
|
||||
add_target(powerpc64le-linux-gnu)
|
||||
add_target(sparc64-linux-gnu)
|
||||
add_target(s390x-linux-gnu)
|
||||
add_target(sh4-linux-gnu)
|
||||
add_target(alpha-linux-gnu)
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_RV32 "Enable tests for RV32" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_RV32)
|
||||
add_target(riscv32-linux-gnu)
|
||||
if(${HOST} STREQUAL "x86_64" OR (HAS_qemu-x86_64 AND HAS_x86_64-linux-gnu-gcc))
|
||||
add_target(x86_64 x86_64-linux-gnu)
|
||||
endif()
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_POWER10 "Enable tests for Power10" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_POWER10)
|
||||
add_target(powerpc64le_power10-linux-gnu)
|
||||
if(${HOST} STREQUAL "i686" OR (HAS_qemu-i386 AND HAS_i686-linux-gnu-gcc))
|
||||
add_target(i686 i686-linux-gnu)
|
||||
endif()
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_M68K "Enable tests for m68k" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_M68K)
|
||||
add_target(m68k-linux-gnu)
|
||||
if(${HOST} STREQUAL "aarch64" OR (HAS_qemu-aarch64 AND HAS_aarch64-linux-gnu-gcc))
|
||||
add_target(aarch64 aarch64-linux-gnu)
|
||||
endif()
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_LOONGARCH "Enable tests for LoongArch" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_LOONGARCH)
|
||||
add_target(loongarch64-linux-gnu)
|
||||
if(${HOST} STREQUAL "arm" OR (HAS_qemu-arm AND HAS_arm-linux-gnueabihf-gcc))
|
||||
add_target(arm arm-linux-gnueabihf)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "riscv64" OR (HAS_qemu-riscv64 AND HAS_riscv64-linux-gnu-gcc))
|
||||
add_target(riscv64 riscv64-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "riscv32" OR (HAS_qemu-riscv32 AND HAS_riscv32-linux-gnu-gcc))
|
||||
add_target(riscv32 riscv32-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "ppc" OR (HAS_qemu-ppc AND HAS_powerpc-linux-gnu-gcc))
|
||||
add_target(ppc powerpc-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "ppc64" OR (HAS_qemu-ppc64 AND HAS_powerpc64-linux-gnu-gcc))
|
||||
add_target(ppc64 powerpc64-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "ppc64le" OR (HAS_qemu-ppc64le AND HAS_powerpc64le-linux-gnu-gcc))
|
||||
add_target(ppc64le powerpc64le-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "ppc64le" AND "${CPUINFO}" MATCHES "POWER10")
|
||||
add_target(ppc64le powerpc64le-linux-gnu power10)
|
||||
elseif(HAS_qemu-ppc64le AND HAS_powerpc64le-linux-gnu-gcc)
|
||||
file(WRITE "${CMAKE_BINARY_DIR}/empty.c" "")
|
||||
|
||||
execute_process(
|
||||
COMMAND powerpc64le-linux-gnu-gcc -mcpu=power10 -E
|
||||
"${CMAKE_BINARY_DIR}/empty.c"
|
||||
RESULT_VARIABLE GCC_EXIT_CODE
|
||||
OUTPUT_QUIET ERROR_QUIET)
|
||||
|
||||
execute_process(
|
||||
COMMAND qemu-ppc64le -cpu help
|
||||
OUTPUT_VARIABLE QEMU_CPUS)
|
||||
|
||||
if("${GCC_EXIT_CODE}" EQUAL "0" AND "${QEMU_CPUS}" MATCHES power10_v2.0)
|
||||
add_target(ppc64le powerpc64le-linux-gnu power10)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "sparc64" OR (HAS_qemu-sparc64 AND HAS_sparc64-linux-gnu-gcc))
|
||||
add_target(sparc64 sparc64-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "s390x" OR (HAS_qemu-s390x AND HAS_s390x-linux-gnu-gcc))
|
||||
add_target(s390x s390x-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "sh4" OR (HAS_qemu-sh4 AND HAS_sh4-linux-gnu-gcc))
|
||||
add_target(sh4 sh4-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "alpha" OR (HAS_qemu-alpha AND HAS_alpha-linux-gnu-gcc))
|
||||
add_target(alpha alpha-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "m68k" OR (HAS_qemu-m68k AND HAS_m68k-linux-gnu-gcc))
|
||||
add_target(m68k m68k-linux-gnu)
|
||||
endif()
|
||||
|
||||
if(${HOST} STREQUAL "loongarch64" OR (HAS_qemu-loongarch64 AND HAS_loongarch64-linux-gnu-gcc))
|
||||
add_target(loongarch64 loongarch64-linux-gnu)
|
||||
endif()
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = aarch64 ] || skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -fPIC -xassembler - 2> /dev/null || skip
|
||||
.global foo
|
||||
.type foo, %function
|
||||
.variant_pcs foo
|
||||
foo:
|
||||
ret
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o
|
||||
readelf -W --dyn-syms $t/b.so | grep foo | grep -q '[VARIANT_PCS]'
|
||||
|
||||
cat <<EOF | $CC -c -o $t/c.o -xc -
|
||||
void foo();
|
||||
int main() { foo(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/c.o $t/b.so
|
||||
readelf -W --dynamic $t/exe | grep -q AARCH64_VARIANT_PCS
|
||||
@@ -2,6 +2,7 @@
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = arm ] || skip
|
||||
is_musl && skip
|
||||
|
||||
echo 'int main() {}' | $GCC -c -o /dev/null -xc - -O0 -mthumb >& /dev/null \
|
||||
|| skip
|
||||
|
||||
@@ -7,8 +7,7 @@ $CC -B. -o $t/exe $t/a.c -Wl,-build-id
|
||||
readelf -n $t/exe | grep -qv 'GNU.*0x00000010.*NT_GNU_BUILD_ID'
|
||||
|
||||
$CC -B. -o $t/exe $t/a.c -Wl,-build-id=uuid
|
||||
readelf -nW $t/exe |
|
||||
grep -Eq 'GNU.*0x00000010.*NT_GNU_BUILD_ID.*Build ID: ............4...[89abcdef]'
|
||||
readelf -nW $t/exe | grep -Eq 'Build ID: ............4...[89abcdef]'
|
||||
|
||||
$CC -B. -o $t/exe $t/a.c -Wl,-build-id=md5
|
||||
readelf -n $t/exe | grep -q 'GNU.*0x00000010.*NT_GNU_BUILD_ID'
|
||||
|
||||
+20
-7
@@ -5,7 +5,7 @@ export LC_ALL=C
|
||||
|
||||
canonical_name() {
|
||||
case $1 in
|
||||
i?86) echo i386 ;;
|
||||
i?86) echo i686 ;;
|
||||
arm*) echo arm ;;
|
||||
powerpc) echo ppc ;;
|
||||
powerpc64) echo ppc64 ;;
|
||||
@@ -14,10 +14,12 @@ canonical_name() {
|
||||
esac
|
||||
}
|
||||
|
||||
HOST=$(canonical_name $(uname -m))
|
||||
if [ -z "$HOST" ]; then
|
||||
HOST=$(canonical_name $(uname -m))
|
||||
fi
|
||||
|
||||
# Set tool names
|
||||
if [ "$TRIPLE" = "" ]; then
|
||||
if [ -z "$TRIPLE" ]; then
|
||||
MACHINE=$HOST
|
||||
TESTDIR=out/test/elf/$HOST
|
||||
CC="${TEST_CC:-cc}"
|
||||
@@ -28,8 +30,7 @@ if [ "$TRIPLE" = "" ]; then
|
||||
OBJCOPY=objcopy
|
||||
STRIP=strip
|
||||
QEMU=
|
||||
elif [ "$TRIPLE" = powerpc64le_power10-linux-gnu ]; then
|
||||
TRIPLE=powerpc64le-linux-gnu
|
||||
elif [ "$TRIPLE" = powerpc64le-linux-gnu -a "$CPU" = power10 ]; then
|
||||
MACHINE=ppc64le
|
||||
TESTDIR=out/test/elf/ppc64le-power10
|
||||
CC="${TEST_CC:-$TRIPLE-gcc} -mcpu=power10"
|
||||
@@ -50,10 +51,15 @@ else
|
||||
OBJDUMP="$TRIPLE-objdump"
|
||||
OBJCOPY="$TRIPLE-objcopy"
|
||||
STRIP="$TRIPLE-strip"
|
||||
QEMU="qemu-$MACHINE -L /usr/$TRIPLE"
|
||||
|
||||
if [ $MACHINE = i686 ]; then
|
||||
QEMU="qemu-i386 -L /usr/$TRIPLE"
|
||||
else
|
||||
QEMU="qemu-$MACHINE -L /usr/$TRIPLE"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $MACHINE = x86_64 -o $MACHINE = i386 -o $MACHINE = arm ]; then
|
||||
if [ $MACHINE = x86_64 -o $MACHINE = i686 -o $MACHINE = arm ]; then
|
||||
tlsdesc_opt=-mtls-dialect=gnu2
|
||||
elif [ $MACHINE = aarch64 ]; then
|
||||
tlsdesc_opt=-mtls-dialect=desc
|
||||
@@ -69,7 +75,14 @@ supports_ifunc() {
|
||||
$CC -c -o /dev/null -xc - >& /dev/null
|
||||
}
|
||||
|
||||
is_musl() {
|
||||
ldd --help 2>&1 | grep -q musl
|
||||
}
|
||||
|
||||
supports_tlsdesc() {
|
||||
# musl's tlsdesc on arm32 seems to be broken
|
||||
[ $MACHINE = arm ] && is_musl && return 1
|
||||
|
||||
[ -n "$tlsdesc_opt" ]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIE -
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
@@ -32,7 +31,7 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -fPIC -shared -o $t/b.so -xc -
|
||||
cat <<EOF | $CC -B. -fPIC -shared -o $t/b.so -xc -
|
||||
__attribute__((section (".data.rel.ro"))) char readonly[100] = "abc";
|
||||
char readwrite[100] = "abc";
|
||||
EOF
|
||||
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
static void ctor1() { printf("ctor1 "); }
|
||||
static void ctor2() { printf("ctor2 "); }
|
||||
static void ctor3() { printf("ctor3 "); }
|
||||
static void ctor4() { printf("ctor4 "); }
|
||||
|
||||
static void dtor1() { printf("dtor1 "); }
|
||||
static void dtor2() { printf("dtor2 "); }
|
||||
static void dtor3() { printf("dtor3 "); }
|
||||
static void dtor4() { printf("dtor4 "); }
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".ctors.65435")))
|
||||
void (*ctors65435[])() = { ctor1 };
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".ctors.65433")))
|
||||
void (*ctors65433[])() = { ctor2 };
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".ctors")))
|
||||
void (*ctors[])() = { ctor4, ctor3 };
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".dtors")))
|
||||
void (*dtors[])() = { dtor1, dtor2 };
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".dtors.65433")))
|
||||
void (*dtors65433[])() = { dtor3 };
|
||||
|
||||
__attribute__((aligned(sizeof(void *)), section(".dtors.65435")))
|
||||
void (*dtors65435[])() = { dtor4 };
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
__attribute__((constructor(101)))
|
||||
static void init1() { printf("init1 "); }
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init2() { printf("init2 "); }
|
||||
|
||||
__attribute__((destructor(101)))
|
||||
static void fini1() { printf("fini1 "); }
|
||||
|
||||
__attribute__((destructor))
|
||||
static void fini2() { printf("fini2 "); }
|
||||
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o
|
||||
$QEMU $t/exe | grep -q 'ctor1 init1 ctor2 ctor3 ctor4 init2 fini2 dtor1 dtor2 dtor3 fini1 dtor4'
|
||||
@@ -6,8 +6,8 @@ void foo() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/b.so -shared $t/a.o -Wl,-default-symver
|
||||
readelf --dyn-syms $t/b.so | grep -q ' foo@@b\.so$'
|
||||
readelf --dyn-syms $t/b.so | grep -q ' foo@@b\.so'
|
||||
|
||||
$CC -B. -o $t/b.so -shared $t/a.o \
|
||||
-Wl,--soname=bar -Wl,-default-symver
|
||||
readelf --dyn-syms $t/b.so | grep -q ' foo@@bar$'
|
||||
readelf --dyn-syms $t/b.so | grep -q ' foo@@bar'
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
! $CC -B. -o $t/exe $t/a.o -Wl,-defsym=foo=bar 2> $t/log
|
||||
grep -q 'undefined symbol: bar' $t/log
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
|
||||
# musl libc does not support init/fini on ARM
|
||||
# https://github.com/rui314/mold/issues/951
|
||||
[ $MACHINE = arm -o $MACHINE = aarch64 ] && ldd --help 2>&1 | grep -q musl && skip
|
||||
[ $MACHINE = arm -o $MACHINE = aarch64 ] && is_musl && skip
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
|
||||
void keep();
|
||||
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
rm -f $t/b.a
|
||||
ar rcs $t/b.a $t/a.o
|
||||
|
||||
cat <<EOF | $CC -o $t/c.o -c -xc -
|
||||
void foo();
|
||||
int main() { foo(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/c.o $t/b.a $t/b.a
|
||||
|
||||
! $CC -B. -o $t/exe $t/c.o -Wl,--push-state,--whole-archive \
|
||||
$t/b.a $t/b.a -Wl,--pop-state 2> $t/log || false
|
||||
|
||||
grep -q 'duplicate symbol:.* foo' $t/log
|
||||
@@ -10,8 +10,8 @@ EOF
|
||||
$CC -B. -o $t/exe $t/a.o
|
||||
|
||||
readelf --dyn-syms $t/exe > $t/log
|
||||
! grep -q ' foo$' $t/log || false
|
||||
! grep -q ' bar$' $t/log || false
|
||||
! grep -q ' foo' $t/log || false
|
||||
! grep -q ' bar' $t/log || false
|
||||
|
||||
cat <<EOF > $t/dyn
|
||||
{ foo; bar; };
|
||||
@@ -20,17 +20,17 @@ EOF
|
||||
$CC -B. -o $t/exe1 $t/a.o -Wl,-dynamic-list=$t/dyn
|
||||
|
||||
readelf --dyn-syms $t/exe1 > $t/log1
|
||||
grep -q ' foo$' $t/log1
|
||||
grep -q ' bar$' $t/log1
|
||||
grep -q ' foo' $t/log1
|
||||
grep -q ' bar' $t/log1
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o -Wl,--export-dynamic-symbol-list=$t/dyn
|
||||
|
||||
readelf --dyn-syms $t/exe2 > $t/log2
|
||||
grep -q ' foo$' $t/log2
|
||||
grep -q ' bar$' $t/log2
|
||||
grep -q ' foo' $t/log2
|
||||
grep -q ' bar' $t/log2
|
||||
|
||||
$CC -B. -o $t/exe3 $t/a.o -Wl,--export-dynamic-symbol=foo,--export-dynamic-symbol=bar
|
||||
|
||||
readelf --dyn-syms $t/exe3 > $t/log3
|
||||
grep -q ' foo$' $t/log3
|
||||
grep -q ' bar$' $t/log3
|
||||
grep -q ' foo' $t/log3
|
||||
grep -q ' bar' $t/log3
|
||||
@@ -14,8 +14,8 @@ EOF
|
||||
$CXX -B. -o $t/exe $t/a.o $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/exe > $t/log
|
||||
! grep -q ' foo$' $t/log || false
|
||||
! grep -q ' bar$' $t/log || false
|
||||
! grep -q ' foo' $t/log || false
|
||||
! grep -q ' bar' $t/log || false
|
||||
|
||||
cat <<EOF > $t/dyn
|
||||
{ foo; extern "C++" { "baz(int)"; }; };
|
||||
@@ -24,13 +24,13 @@ EOF
|
||||
$CC -B. -o $t/exe1 $t/a.o $t/b.o -Wl,-dynamic-list=$t/dyn
|
||||
|
||||
readelf --dyn-syms $t/exe1 > $t/log1
|
||||
grep -q ' foo$' $t/log1
|
||||
! grep -q ' bar$' $t/log1 || false
|
||||
grep -q ' _Z3bazi$' $t/log1
|
||||
grep -q ' foo' $t/log1
|
||||
! grep -q ' bar' $t/log1 || false
|
||||
grep -q ' _Z3bazi' $t/log1
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o $t/b.o -Wl,--export-dynamic-symbol-list=$t/dyn
|
||||
|
||||
readelf --dyn-syms $t/exe2 > $t/log2
|
||||
grep -q ' foo$' $t/log2
|
||||
! grep -q ' bar$' $t/log2 || false
|
||||
grep -q ' _Z3bazi$' $t/log2
|
||||
grep -q ' foo' $t/log2
|
||||
! grep -q ' bar' $t/log2 || false
|
||||
grep -q ' _Z3bazi' $t/log2
|
||||
+12
-12
@@ -21,19 +21,19 @@ EOF
|
||||
$CC -B. -Wl,--dynamic-list=$t/dyn -o $t/exe1 $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/exe1 > $t/log1
|
||||
grep -q ' xyz$' $t/log1
|
||||
! grep -q ' foobarzx$' $t/log1 || false
|
||||
grep -q ' foobarcx$' $t/log1
|
||||
grep -q ' foo123bar456bx$' $t/log1
|
||||
! grep -q ' foo123bar456c$' $t/log1 || false
|
||||
! grep -q ' foo123bar456x$' $t/log1 || false
|
||||
grep -q ' xyz' $t/log1
|
||||
! grep -q ' foobarzx' $t/log1 || false
|
||||
grep -q ' foobarcx' $t/log1
|
||||
grep -q ' foo123bar456bx' $t/log1
|
||||
! grep -q ' foo123bar456c' $t/log1 || false
|
||||
! grep -q ' foo123bar456x' $t/log1 || false
|
||||
|
||||
$CC -B. -Wl,--export-dynamic-symbol-list=$t/dyn -o $t/exe2 $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/exe2 > $t/log2
|
||||
grep -q ' xyz$' $t/log2
|
||||
! grep -q ' foobarzx$' $t/log2 || false
|
||||
grep -q ' foobarcx$' $t/log2
|
||||
grep -q ' foo123bar456bx$' $t/log2
|
||||
! grep -q ' foo123bar456c$' $t/log2 || false
|
||||
! grep -q ' foo123bar456x$' $t/log2 || false
|
||||
grep -q ' xyz' $t/log2
|
||||
! grep -q ' foobarzx' $t/log2 || false
|
||||
grep -q ' foobarcx' $t/log2
|
||||
grep -q ' foo123bar456bx' $t/log2
|
||||
! grep -q ' foo123bar456c' $t/log2 || false
|
||||
! grep -q ' foo123bar456x' $t/log2 || false
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -fPIC
|
||||
#include <stdio.h>
|
||||
|
||||
void foo() { printf("foo1 "); }
|
||||
void bar() { printf("bar1 "); }
|
||||
void baz() { printf("baz1 "); }
|
||||
|
||||
void print() {
|
||||
foo();
|
||||
bar();
|
||||
baz();
|
||||
printf("\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/dyn
|
||||
{ foo; bar; };
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o -Wl,--dynamic-list=$t/dyn
|
||||
|
||||
cat <<EOF | $CC -o $t/c.o -c -xc - -fPIC
|
||||
#include <stdio.h>
|
||||
void foo() { printf("foo2 "); }
|
||||
void bar() { printf("bar2 "); }
|
||||
void baz() { printf("baz2 "); }
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/d.so $t/c.o
|
||||
|
||||
cat <<EOF | $CC -o $t/e.o -c -xc -
|
||||
#include <stdio.h>
|
||||
void print();
|
||||
int main() { print(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/e.o -Wl,-push-state,-no-as-needed $t/b.so -Wl,-pop-state
|
||||
$QEMU $t/exe1 | grep -q 'foo1 bar1 baz1'
|
||||
|
||||
$CC -B. -o $t/exe2 $t/e.o -Wl,-push-state,-no-as-needed $t/d.so $t/b.so -Wl,-pop-state
|
||||
$QEMU $t/exe2 | grep -q 'foo2 bar2 baz1'
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
cat <<EOF | $CXX -o $t/a.o -c -fPIC -xc++ -g -ffunction-sections -
|
||||
|
||||
@@ -11,5 +11,5 @@ EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o
|
||||
|
||||
readelf --dyn-syms $t/b.so | grep -q 'bar1$'
|
||||
readelf --dyn-syms $t/b.so | grep -q 'bar2$'
|
||||
readelf --dyn-syms $t/b.so | grep -q ' bar1'
|
||||
readelf --dyn-syms $t/b.so | grep -q ' bar2'
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
static=
|
||||
|
||||
Executable
+101
@@ -0,0 +1,101 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = $HOST ] || skip
|
||||
[ $MACHINE = riscv64 -o $MACHINE = riscv32 -o $MACHINE = sparc64 ] && skip
|
||||
|
||||
command -v gdb >& /dev/null || skip
|
||||
|
||||
test_cflags -gdwarf-5 -g -gdwarf64 || skip
|
||||
|
||||
cat <<EOF > $t/a.c
|
||||
void fn3();
|
||||
|
||||
static void fn2() {
|
||||
fn3();
|
||||
}
|
||||
|
||||
void fn1() {
|
||||
fn2();
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/b.c
|
||||
void fn5();
|
||||
|
||||
static void fn4() {
|
||||
fn5();
|
||||
}
|
||||
|
||||
void fn3() {
|
||||
fn4();
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/c.c
|
||||
void fn7();
|
||||
|
||||
static void fn6() {
|
||||
fn7();
|
||||
}
|
||||
|
||||
void fn5() {
|
||||
fn6();
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/d.c
|
||||
#include <stdio.h>
|
||||
void trap() {}
|
||||
|
||||
static void fn8() {
|
||||
printf("Hello world\n");
|
||||
trap();
|
||||
}
|
||||
|
||||
void fn7() {
|
||||
fn8();
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -c -o $t/a.o $t/a.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gdwarf64 -ffunction-sections
|
||||
$CC -c -o $t/b.o $t/b.c -fPIC -g -ggnu-pubnames -gdwarf-4 -gdwarf64 -ffunction-sections
|
||||
$CC -c -o $t/c.o $t/c.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gdwarf64
|
||||
$CC -c -o $t/d.o $t/d.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gdwarf64 -ffunction-sections
|
||||
|
||||
$CC -B. -shared -o $t/e.so $t/a.o $t/b.o $t/c.o $t/d.o
|
||||
|
||||
$CC -B. -shared -o $t/f.so $t/a.o $t/b.o $t/c.o $t/d.o -Wl,--gdb-index
|
||||
readelf -WS $t/f.so 2> /dev/null | grep -Fq .gdb_index
|
||||
|
||||
cat <<EOF | $CC -c -o $t/g.o -fPIC -g -ggnu-pubnames -gdwarf-5 -xc - -gz
|
||||
void fn1();
|
||||
|
||||
int main() {
|
||||
fn1();
|
||||
}
|
||||
EOF
|
||||
|
||||
# Older versions of gdb are buggy that they complain DWARF64 debug sections
|
||||
# even without .gdb_index. Skip if such version.
|
||||
$CC -B. -o $t/exe1 $t/e.so $t/g.o
|
||||
DEBUGINFOD_URLS= gdb $t/exe1 -nx -batch -ex 'b main' -ex r -ex quit >& $t/log1
|
||||
grep -q 'DW_FORM_line_strp pointing outside of .debug_line_str' $t/log1 && skip
|
||||
|
||||
# We are using a recent version of gdb.
|
||||
$CC -B. -o $t/exe2 $t/f.so $t/g.o -Wl,--gdb-index
|
||||
readelf -WS $t/exe2 2> /dev/null | grep -Fq .gdb_index
|
||||
|
||||
$QEMU $t/exe2 | grep -q 'Hello world'
|
||||
|
||||
DEBUGINFOD_URLS= gdb $t/exe2 -nx -batch -ex 'b main' -ex r -ex 'b trap' \
|
||||
-ex c -ex bt -ex quit >& $t/log2
|
||||
|
||||
grep -q 'fn8 () at .*/d.c:6' $t/log2
|
||||
grep -q 'fn7 () at .*/d.c:10' $t/log2
|
||||
grep -q 'fn6 () at .*/c.c:4' $t/log2
|
||||
grep -q 'fn5 () at .*/c.c:8' $t/log2
|
||||
grep -q 'fn4 () at .*/b.c:4' $t/log2
|
||||
grep -q 'fn3 () at .*/b.c:8' $t/log2
|
||||
grep -q 'fn2 () at .*/a.c:4' $t/log2
|
||||
grep -q 'fn1 () at .*/a.c:8' $t/log2
|
||||
@@ -20,7 +20,7 @@ GOT_ADDR=$($QEMU $t/exe)
|
||||
|
||||
# _GLOBAL_OFFSET_TABLE_ refers the end of .got only on x86.
|
||||
# We assume .got is followed by .gotplt.
|
||||
if [ $MACHINE = x86_64 -o $MACHINE = i386 ]; then
|
||||
if [ $MACHINE = x86_64 -o $MACHINE = i686 ]; then
|
||||
readelf -WS $t/exe | grep -q "\.got\.plt .*$GOT_ADDR "
|
||||
else
|
||||
readelf -WS $t/exe | grep -q "\.got .*$GOT_ADDR "
|
||||
|
||||
@@ -18,5 +18,5 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o -no-pie
|
||||
$QEMU $t/exe | grep -q 'foo=5'
|
||||
@@ -16,5 +16,5 @@ int main() {
|
||||
EOF
|
||||
|
||||
# Make sure that we do not copy .gnu.warning.* sections.
|
||||
$CC -B. -o $t/exe $t/a.o
|
||||
$CC -B. -o $t/exe $t/a.o -no-pie
|
||||
$QEMU $t/exe | grep -q 'Hello world'
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = i386 ] || skip
|
||||
[ $MACHINE = i686 ] || skip
|
||||
|
||||
cat <<'EOF' | $CC -fPIC -o $t/a.o -c -xassembler -
|
||||
.globl get_foo
|
||||
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# GCC 11 or older uses R_390_PLT32DBL to take an address of a function
|
||||
# instead of R_390_PC32DBL
|
||||
if [ $MACHINE = s390x ]; then
|
||||
echo 'void *foo() { return foo; }' | $CC -c -o $t/a.o -xc -
|
||||
readelf -r $t/a.o | grep -q R_390_PLT32DBL && skip
|
||||
fi
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -ffunction-sections -fdata-sections -xc -
|
||||
int bar() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
int foo1(int x) {
|
||||
return bar() + x;
|
||||
}
|
||||
|
||||
int foo2(int x) {
|
||||
return bar() + x;
|
||||
}
|
||||
|
||||
int foo3(int x) {
|
||||
return bar() + x;
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -o $t/b.o -ffunction-sections -fdata-sections -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
int foo1();
|
||||
int foo2();
|
||||
int foo3();
|
||||
|
||||
int main() {
|
||||
printf("%d %d\n", foo1 == foo2, foo2 == foo3);
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 -Wl,-icf=safe $t/a.o $t/b.o
|
||||
$QEMU $t/exe1 | grep -q '^0 0$'
|
||||
|
||||
cat <<EOF | $CC -c -o $t/c.o -ffunction-sections -fdata-sections -xc -
|
||||
int foo1();
|
||||
int foo2();
|
||||
int foo3();
|
||||
|
||||
int main() {
|
||||
foo1();
|
||||
foo2();
|
||||
foo3();
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe2 -Wl,-icf=safe $t/a.o $t/c.o
|
||||
$QEMU $t/exe2 > $t/log2
|
||||
! grep foo2 $t/log2 || false
|
||||
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
supports_ifunc || skip
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
|
||||
typedef void Func();
|
||||
void foo();
|
||||
Func *get_foo() { return foo; }
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o
|
||||
|
||||
cat <<EOF | $CC -c -fno-PIE -o $t/c.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
typedef void Func();
|
||||
|
||||
__attribute__((ifunc("resolve_foo"))) void foo(void);
|
||||
void real_foo(void) { printf("foo "); }
|
||||
Func *resolve_foo() { return real_foo; }
|
||||
|
||||
Func *get_foo();
|
||||
|
||||
int main() {
|
||||
printf("%p %p\n", foo, get_foo());
|
||||
foo();
|
||||
printf("\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/c.o $t/b.so -no-pie
|
||||
$QEMU $t/exe | grep -Eq '^(\S+) \1'
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
supports_ifunc || skip
|
||||
|
||||
cat <<EOF | $CC -c -fno-PIE -o $t/a.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
typedef void Func();
|
||||
|
||||
__attribute__((ifunc("resolve_foo"))) void foo(void);
|
||||
void real_foo(void) { printf("foo "); }
|
||||
Func *resolve_foo() { return real_foo; }
|
||||
|
||||
__attribute__((ifunc("resolve_bar"))) void bar(void);
|
||||
void real_bar(void) { printf("bar "); }
|
||||
Func *resolve_bar() { return real_bar; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/b.o -xc -
|
||||
typedef void Func();
|
||||
|
||||
void foo();
|
||||
void bar();
|
||||
|
||||
Func *get_foo() { return foo; }
|
||||
Func *get_bar() { return bar; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -fno-PIE -o $t/c.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
typedef void Func();
|
||||
|
||||
void foo();
|
||||
void bar();
|
||||
Func *get_foo();
|
||||
Func *get_bar();
|
||||
|
||||
int main() {
|
||||
printf("%p %p %p %p\n", foo, get_foo(), bar, get_bar());
|
||||
foo();
|
||||
bar();
|
||||
printf("\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o $t/b.o $t/c.o -no-pie
|
||||
$QEMU $t/exe1 | grep -Eq '^(\S+) \1 (\S+) \2'
|
||||
|
||||
readelf --dynamic $t/exe1 > $t/log1
|
||||
! grep -q TEXTREL $t/log1 || false
|
||||
@@ -16,5 +16,8 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o
|
||||
$QEMU $t/exe | grep -Eq '^(\S+) \1$'
|
||||
$CC -B. -o $t/exe1 $t/a.o -pie
|
||||
$QEMU $t/exe1 | grep -Eq '^(\S+) \1$'
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o -no-pie
|
||||
$QEMU $t/exe2 | grep -Eq '^(\S+) \1$'
|
||||
@@ -33,5 +33,8 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o $t/c.o
|
||||
$QEMU $t/exe | grep -q '^3$'
|
||||
$CC -B. -o $t/exe1 $t/a.o $t/b.o $t/c.o -pie
|
||||
$QEMU $t/exe1 | grep -q '^3$'
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o $t/b.o $t/c.o -no-pie
|
||||
$QEMU $t/exe2 | grep -q '^3$'
|
||||
@@ -24,5 +24,8 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o
|
||||
$QEMU $t/exe | grep -q 'Hello world'
|
||||
$CC -B. -o $t/exe1 $t/a.o -pie
|
||||
$QEMU $t/exe1 | grep -q 'Hello world'
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o -no-pie
|
||||
$QEMU $t/exe2 | grep -q 'Hello world'
|
||||
@@ -2,7 +2,7 @@
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# musl does not support GNU-style init/fini priorities
|
||||
ldd --help 2>&1 | grep -q musl && skip
|
||||
is_musl && skip
|
||||
|
||||
cat <<'EOF' | $CC -c -o $t/a.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
cat <<EOF | $CXX -o $t/a.o -c -xc++ -
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = i386 ] && skip
|
||||
[ $MACHINE = i686 ] && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -ffunction-sections -fPIC
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = i386 ] && skip
|
||||
[ $MACHINE = i686 ] && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -ffunction-sections
|
||||
#include <stdio.h>
|
||||
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -mcmodel=extreme
|
||||
#include <stdio.h>
|
||||
char msg[] = "Hello world\n";
|
||||
int main() { printf(msg); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o
|
||||
$QEMU $t/exe1 | grep -q 'Hello world'
|
||||
Regular → Executable
+1
-1
@@ -6,4 +6,4 @@ void _start() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe -flto $t/a.o -nostdlib
|
||||
readelf -Ws $t/exe | grep -Eq ' _start$'
|
||||
readelf -Ws $t/exe | grep -Eq ' _start'
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = i386 ] && skip
|
||||
[ $MACHINE = i686 ] && skip
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = arm ] && skip
|
||||
[ $MACHINE = ppc64 ] && skip
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# Skip if 32 bits as we use very large addresses in this test.
|
||||
[ $MACHINE = i386 ] && skip
|
||||
[ $MACHINE = i686 ] && skip
|
||||
[ $MACHINE = riscv32 ] && skip
|
||||
|
||||
# It looks like SPARC's runtime can't handle PLT if it's too far from GOT.
|
||||
[ $MACHINE = sparc64 ] && skip
|
||||
|
||||
# qemu aborts with the "Unknown exception 0x5" error, although this
|
||||
# test passes on a real POWER10 machine.
|
||||
[ -n "$QEMU" -a "$CPU" = power10 ] && skip
|
||||
|
||||
cat <<EOF > $t/a.c
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -ffunction-sections
|
||||
#include <stdio.h>
|
||||
|
||||
void f0();
|
||||
void f1();
|
||||
void f2();
|
||||
void f3();
|
||||
void f4();
|
||||
void f5();
|
||||
void f6();
|
||||
void f7();
|
||||
void f8();
|
||||
void f9();
|
||||
void f10();
|
||||
void f11();
|
||||
void f12();
|
||||
void f13();
|
||||
void f14();
|
||||
void f15();
|
||||
void f16();
|
||||
void f17();
|
||||
void f18();
|
||||
void f19();
|
||||
|
||||
void f0(int x) { printf("0 "); if (!x) f9(); }
|
||||
void space0() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f1(int x) { printf("1 "); f8(x); }
|
||||
void space1() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f2(int x) { printf("2 "); f7(x); }
|
||||
void space2() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f3(int x) { printf("3 "); f6(x); }
|
||||
void space3() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f4(int x) { printf("4 "); f5(x); }
|
||||
void space4() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f5(int x) { printf("5 "); f10(x); }
|
||||
void space5() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f6(int x) { printf("6 "); f4(x); }
|
||||
void space6() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f7(int x) { printf("7 "); f3(x); }
|
||||
void space7() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f8(int x) { printf("8 "); f2(x); }
|
||||
void space8() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f9(int x) { printf("9 "); f1(x); }
|
||||
void space9() { __asm__(".space 1024*1024"); }
|
||||
|
||||
void f10(int x) { printf("10 "); f19(x); }
|
||||
void space10() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f11(int x) { printf("11 "); f18(x); }
|
||||
void space11() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f12(int x) { printf("12 "); f17(x); }
|
||||
void space12() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f13(int x) { printf("13 "); f16(x); }
|
||||
void space13() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f14(int x) { printf("14 "); f15(x); }
|
||||
void space14() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f15(int x) { printf("15 "); f0(x + 1); }
|
||||
void space15() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f16(int x) { printf("16 "); f14(x); }
|
||||
void space16() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f17(int x) { printf("17 "); f13(x); }
|
||||
void space17() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f18(int x) { printf("18 "); f12(x); }
|
||||
void space18() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
void f19(int x) { printf("19 "); f11(x); }
|
||||
void space19() { __asm__(".space 8*1024*1024"); }
|
||||
|
||||
int main() {
|
||||
f0(0);
|
||||
printf("\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o
|
||||
$QEMU $t/exe | grep -Eq '^0 9 1 8 2 7 3 6 4 5 10 19 11 18 12 17 13 16 14 15 0 $'
|
||||
@@ -4,7 +4,8 @@
|
||||
# OneTBB isn't tsan-clean
|
||||
nm mold | grep -q '__tsan_init' && skip
|
||||
|
||||
# Ubuntu 22.04 SH4 GCC is broken
|
||||
# Ubuntu 22.04 GCC is broken
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
cat <<EOF | $CXX -c -o $t/a.o -xc++ -
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = m68k ] && skip
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
# OneTBB isn't tsan-clean
|
||||
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# OneTBB isn't tsan-clean
|
||||
nm mold | grep -q '__tsan_init' && skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -xassembler -
|
||||
.section .rodata.str1.1,"aMS",@progbits,1
|
||||
val1:
|
||||
.ascii "Hello \0"
|
||||
|
||||
.section .rodata.str1.1,"aMS",@progbits,1
|
||||
val5:
|
||||
.ascii "World \0"
|
||||
EOF
|
||||
|
||||
./mold --relocatable -o $t/b.o $t/a.o
|
||||
|
||||
readelf -W -p .rodata.str1.1 $t/b.o | grep -Eq '\b0\b.*Hello'
|
||||
readelf -W -p .rodata.str1.1 $t/b.o | grep -Eq '\b7\b.*World'
|
||||
@@ -17,4 +17,4 @@ EOF
|
||||
|
||||
echo "'$t/b.o' '$t/c.o'" > $t/rsp
|
||||
|
||||
$CC -o $t/exe $t/a.o -Wl,@$t/rsp
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,@$t/rsp
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -xc -
|
||||
void foo();
|
||||
int main() { foo(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -o $t/b.o -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
echo "'$t/b.o'" > $t/rsp1
|
||||
echo "@$t/rsp1" > $t/rsp2
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,@$t/rsp2
|
||||
+11
-11
@@ -12,23 +12,23 @@ cat <<'EOF' | $CC -c -o $t/a.o -x assembler -
|
||||
val1:
|
||||
.ascii "Hello \0"
|
||||
|
||||
.section .rodata.str4.4,"aMS",@progbits,4
|
||||
.align 4
|
||||
.section .rodata.foo,"aMS",@progbits,4
|
||||
.p2align 2
|
||||
val2:
|
||||
.ascii "world \0\0\0\0"
|
||||
|
||||
.section .rodata.foo,"aMS",@progbits,1
|
||||
.section .rodata.x,"aMS",@progbits,1
|
||||
val3:
|
||||
.ascii "foobar\0"
|
||||
|
||||
.section .rodata.cst8,"aM",@progbits,8
|
||||
.align 8
|
||||
.p2align 3
|
||||
val4:
|
||||
.ascii "abcdefgh"
|
||||
|
||||
.section .rodatabar,"aMS",@progbits,1
|
||||
.section .rodatabaz,"aMS",@progbits,1
|
||||
val5:
|
||||
.ascii "bar\0"
|
||||
.ascii "baz\0"
|
||||
EOF
|
||||
|
||||
cat <<'EOF' | $CC -c -o $t/b.o -xc -
|
||||
@@ -43,8 +43,8 @@ EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o
|
||||
|
||||
readelf -p .rodata.str $t/exe | grep -q Hello
|
||||
readelf -p .rodata.str $t/exe | grep -q world
|
||||
readelf -p .rodata.str $t/exe | grep -q foobar
|
||||
readelf -p .rodata.cst $t/exe | grep -q abcdefgh
|
||||
readelf -p .rodatabar $t/exe | grep -q bar
|
||||
readelf -p .rodata.str1.1 $t/exe | grep -q Hello
|
||||
readelf -p .rodata.str4.4 $t/exe | grep -q world
|
||||
readelf -p .rodata.str1.1 $t/exe | grep -q foobar
|
||||
readelf -p .rodata.cst8 $t/exe | grep -q abcdefgh
|
||||
readelf -p .rodatabaz $t/exe | grep -q baz
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
[ "$CC" = cc ] || skip
|
||||
|
||||
# ASAN doesn't work with LD_PRELOAD
|
||||
nm mold-wrapper.so | grep -q '__[at]san_init' && skip
|
||||
nm mold | grep -q '__[at]san_init' && skip
|
||||
|
||||
cat <<'EOF' | $CC -xc -c -o $t/a.o -
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -12,4 +12,4 @@ $CC -B. -o $t/exe $t/a.o -Wl,--strip-debug
|
||||
readelf -W --sections $t/exe > $t/log
|
||||
! grep -Fq .debug_info $t/log || false
|
||||
|
||||
readelf -W --symbols $t/exe | grep -q ' bar$'
|
||||
readelf -W --symbols $t/exe | grep -q ' bar'
|
||||
@@ -16,7 +16,7 @@ EOF
|
||||
$CC -B. -o $t/c.so -shared $t/a.o -Wl,--version-script=$t/b.version
|
||||
readelf -W --dyn-syms $t/c.so > $t/log
|
||||
|
||||
grep -q ' foo@TEST$' $t/log
|
||||
grep -q ' bar@TEST$' $t/log
|
||||
grep -q ' bar1$' $t/log
|
||||
! grep -q ' foo@@TEST$' $t/log || false
|
||||
grep -q ' foo@TEST' $t/log
|
||||
grep -q ' bar@TEST' $t/log
|
||||
grep -q ' bar1' $t/log
|
||||
! grep -q ' foo@@TEST' $t/log || false
|
||||
@@ -19,7 +19,7 @@ EOF
|
||||
$CC -B. -o $t/c.so -shared $t/a.o -Wl,--version-script=$t/b.version
|
||||
readelf -W --dyn-syms $t/c.so > $t/log
|
||||
|
||||
grep -q ' foo@@TEST1$' $t/log
|
||||
grep -q ' foo@TEST2$' $t/log
|
||||
grep -q ' foo@TEST3$' $t/log
|
||||
grep -q ' foo@@TEST1' $t/log
|
||||
grep -q ' foo@TEST2' $t/log
|
||||
grep -q ' foo@TEST3' $t/log
|
||||
! grep -q ' foo$' $t/log || false
|
||||
+1
-1
@@ -19,7 +19,7 @@ EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o
|
||||
|
||||
cat <<EOF | $GCC -c -o $t/c.o -xc -
|
||||
cat <<EOF | $GCC -c -o $t/c.o -xc - -fPIC
|
||||
#include <stdio.h>
|
||||
|
||||
_Thread_local int baz;
|
||||
|
||||
@@ -23,5 +23,5 @@ cat <<EOF | $CC -xc -c -o $t/b.o -
|
||||
__attribute__((tls_model("global-dynamic"))) _Thread_local int foo;
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o -no-pie
|
||||
$QEMU $t/exe | grep -q '3 5 3 5'
|
||||
@@ -14,6 +14,6 @@ EOF
|
||||
$CC -B. -shared -Wl,--version-script=$t/a.ver -o $t/c.so $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/c.so > $t/log
|
||||
grep -q 'foo@@VER_X1$' $t/log
|
||||
! grep -q ' bar$' $t/log || false
|
||||
! grep -q ' baz$' $t/log || false
|
||||
grep -q 'foo@@VER_X1' $t/log
|
||||
! grep -q ' bar' $t/log || false
|
||||
! grep -q ' baz' $t/log || false
|
||||
@@ -20,6 +20,6 @@ EOF
|
||||
$CC -B. -shared -Wl,--version-script=$t/a.ver -o $t/c.so $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/c.so > $t/log
|
||||
grep -q ' xyz$' $t/log
|
||||
grep -q ' foo_bar$' $t/log
|
||||
grep -q ' xyz' $t/log
|
||||
grep -q ' foo_bar' $t/log
|
||||
! grep -q ' foo$' $t/log || false
|
||||
@@ -16,5 +16,5 @@ EOF
|
||||
$CC -B. -shared -Wl,--version-script=$t/a.ver -o $t/c.so $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/c.so > $t/log
|
||||
grep -q ' foobar$' $t/log
|
||||
grep -q ' foobar' $t/log
|
||||
! grep -q ' foo$' $t/log || false
|
||||
@@ -23,9 +23,9 @@ EOF
|
||||
$CC -B. -shared -Wl,--version-script=$t/a.ver -o $t/c.so $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/c.so > $t/log
|
||||
grep -q ' xyz$' $t/log
|
||||
! grep -q ' foobarzx$' $t/log || false
|
||||
grep -q ' foobarcx$' $t/log
|
||||
grep -q ' foo123bar456bx$' $t/log
|
||||
! grep -q ' foo123bar456c$' $t/log || false
|
||||
! grep -q ' foo123bar456x$' $t/log || false
|
||||
grep -q ' xyz' $t/log
|
||||
! grep -q ' foobarzx' $t/log || false
|
||||
grep -q ' foobarcx' $t/log
|
||||
grep -q ' foo123bar456bx' $t/log
|
||||
! grep -q ' foo123bar456c' $t/log || false
|
||||
! grep -q ' foo123bar456x' $t/log || false
|
||||
@@ -20,7 +20,7 @@ EOF
|
||||
$CC -B. -shared -Wl,--version-script=$t/a.ver -o $t/c.so $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/c.so > $t/log
|
||||
grep -q ' azZ$' $t/log
|
||||
grep -q ' czZ$' $t/log
|
||||
! grep -q ' azC$' $t/log || false
|
||||
! grep -q ' aaZ$' $t/log || false
|
||||
grep -q ' azZ' $t/log
|
||||
grep -q ' czZ' $t/log
|
||||
! grep -q ' azC' $t/log || false
|
||||
! grep -q ' aaZ' $t/log || false
|
||||
@@ -9,10 +9,10 @@ EOF
|
||||
cat <<EOF | $CXX -fPIC -c -o $t/b.o -xc -
|
||||
int foo = 5;
|
||||
int bar = 6;
|
||||
int quux = 100;
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -Wl,--version-script=$t/a.ver \
|
||||
-o $t/c.so $t/b.o
|
||||
$CC -B. -shared -Wl,--version-script=$t/a.ver -o $t/c.so $t/b.o
|
||||
|
||||
cat <<'EOF' > $t/d.ver
|
||||
VER_Y1 { local; *; };
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
# Skip if libc is musl because musl does not fully support GNU-style
|
||||
# symbol versioning.
|
||||
ldd --help 2>&1 | grep -q musl && skip
|
||||
is_musl && skip
|
||||
|
||||
cat <<EOF | $CC -fPIC -c -o $t/a.o -xc -
|
||||
int foo1() { return 1; }
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xassembler -
|
||||
.section .rodata.str1.1, "aMS", @progbits, 1
|
||||
.section .rodata.foo, "aMS", @progbits, 1
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xassembler -
|
||||
@@ -19,4 +20,5 @@ $CC -B. -o $t/exe $t/a.o $t/b.o $t/c.o
|
||||
$t/exe
|
||||
|
||||
readelf -SW $t/exe > $t/log
|
||||
! grep -Fq .rodata.str1.1 $t/log || false
|
||||
grep -q '.rodata.str1.1 .* AMS ' $t/log
|
||||
! grep -q '.rodata.str1.1 .* AM ' $t/log || false
|
||||
Loaded 100 of 121 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user