1
1
mirror of https://github.com/rui314/mold.git synced 2024-08-16 08:20:23 +03:00

Compare commits

...

9 Commits

Author SHA1 Message Date
Dickless
ac6df3933c
Merge d6d0c9178f into 18da5b654e 2024-07-09 05:47:42 +01:00
Rui Ueyama
18da5b654e Add --no-detach to write to a separate debug file in the foreground
--detach is the default.
2024-07-09 12:06:41 +09:00
Rui Ueyama
97a1e218c5 Simplify crc32_solve()
The code was originally written by Pete Cawley
https://gist.github.com/corsix/bdfc8f2f1dc0f28de39f74de9bf4f060
2024-07-09 10:15:26 +09:00
Rui Ueyama
f9e4cb1a7f Add a missing #include 2024-07-08 10:43:40 +09:00
Rui Ueyama
60760a892a Attempt to fix CI 2024-07-08 09:59:07 +09:00
Rui Ueyama
596ffa959a Add --separate-debug-info
This option is to separate debug info to a different file. The debug
info file's filename is stored to the main output file's .gnu_debuglink
section. gdb can read the section contents and followg the link to
find debug info in another file.

Fixes https://github.com/rui314/mold/issues/1294
2024-07-08 09:28:32 +09:00
Dickless
d6d0c9178f
Fixed MMU typo 2024-01-31 14:42:30 +09:00
Dickless
93096442f3
Update docs/glossary.md
Co-authored-by: Alcaro <floating@muncher.se>
2024-01-31 14:40:39 +09:00
Dickless
e63e3a6cf4
Fixed typo 2023-04-17 03:10:42 +09:00
12 changed files with 311 additions and 20 deletions

View File

@ -364,6 +364,7 @@ endforeach()
# Add other non-template source files.
target_sources(mold PRIVATE
common/compress.cc
common/crc32.cc
common/demangle.cc
common/filepath.cc
common/glob.cc

View File

@ -2,6 +2,7 @@
#include "integers.h"
#include <array>
#include <atomic>
#include <bit>
#include <bitset>
@ -899,6 +900,13 @@ std::optional<std::string_view> demangle_rust(std::string_view name);
void acquire_global_lock();
void release_global_lock();
//
// crc32.cc
//
u32 compute_crc32(u32 crc, u8 *buf, i64 len);
std::vector<u8> crc32_solve(u32 current, u32 desired);
//
// compress.cc
//

60
common/crc32.cc Normal file
View File

@ -0,0 +1,60 @@
#include "common.h"
#include <tbb/parallel_for_each.h>
#include <zlib.h>
namespace mold {
// This function "forges" a CRC. That is, given the current and a desired
// CRC32 value, crc32_solve() returns a binary blob to add to the end of
// the original data to yield the desired CRC. Trailing garbage is ignored
// by many bianry file formats, so you can create a file with a desired
// CRC using crc32_solve(). We need it for --separate-debug-file.
std::vector<u8> crc32_solve(u32 current, u32 desired) {
constexpr u32 poly = 0xedb88320;
u32 x = ~desired;
// Each iteration computes x = (x * x^-1) mod poly.
for (i64 i = 0; i < 32; i++) {
x = std::rotl(x, 1);
x ^= (x & 1) * (poly << 1);
}
x ^= ~current;
std::vector<u8> out(4);
out[0] = x;
out[1] = x >> 8;
out[2] = x >> 16;
out[3] = x >> 24;
return out;
}
// Compute a CRC for given data in parallel
u32 compute_crc32(u32 crc, u8 *buf, i64 len) {
struct Shard {
u8 *buf;
i64 len;
u32 crc;
};
constexpr i64 shard_size = 1024 * 1024; // 1 MiB
std::vector<Shard> shards;
while (len > 0) {
i64 sz = std::min(len, shard_size);
shards.push_back({buf, sz, 0});
buf += sz;
len -= sz;
}
tbb::parallel_for_each(shards.begin(), shards.end(), [](Shard &shard) {
shard.crc = crc32_z(0, shard.buf, shard.len);
});
for (Shard &shard : shards)
crc = crc32_combine(crc, shard.crc, shard.len);
return crc;
}
} // namespace mold

View File

@ -13,7 +13,7 @@ you need to understand to read mold code.
A .so file. Short for Dynamic Shared Object. Often called as a
shared library, a dynamic libray or a shared object as well.
An DSO contains common functions and data that are used by multiple
A DSO contains common functions and data that are used by multiple
executables and/or other DSOs. At runtime, a DSO is loaded to a
contiguous region in the virtual address.
@ -24,25 +24,25 @@ cannot be executed because it's not self-contained. For example,
if you compile a C source file containing a call of `printf`,
the actual function code of `printf` is not included in the resulting
object file. You include `stdio.h`, but that teaches the compiler
only about `printf`'s type, and the compiler still don't know what
only about `printf`'s type, and the compiler still doesn't know what
`printf` actually does. Therefore, it cannot emit code for `printf`.
You need to link an object file with other object file or a shared
library to make it exectuable.
library to make it executable.
## Virtual address space
A pointer has a value like 0x803020 which is an address of the
pointee. But it doesn't mean that the pointee resides at the
physical memory address 0x803020 on the computer. Modern CPUs
contains so-called Mmeory Management Unit (MMU), and all access to
contains so-called Memory Management Unit (MMU), and all access to
the memory are first translated by MMU to the physical address.
The address before translation is called the "virtual address".
Unless you are doing the kernel programming, all addresses you
handle are virtual addresses.
The OS kernel controls the MMU so that each process owns the entire
virtual address space. So, even if two process uses the same virtual
virtual address space. So, even if two processes use the same virtual
address, they don't conflict. They are mapped to different physical
addresses.
@ -70,16 +70,16 @@ example, if you compile a function which calls a non-local function
```
The above `callq` is the instruction to call a function at the
machine code level. It's opcode is `0xe8` in x86-64, so the
machine code level. Its opcode is `0xe8` in x86-64, so the
instruction begins with `0xe8`. The following four bytes are
displacement; that is, the address of the branch target relative to
the end of this `callq` instruction. Notice that the displacement is
0. The compiler couldn't fill the displacement because it has no
idea as to where `foo` will be at runtime. So, the compiler write 0
as a placeholder and instead write a relocation `R_X86_64_PLT32`
idea as to where `foo` will be at runtime. So, the compiler writes 0
as a placeholder and instead writes a relocation `R_X86_64_PLT32`
with `foo` as its associated symbol. The linker reads this
relocation, computes the offsets between this call instruction and
function `foo` and overwrite the placeholder value 0 with an actual
function `foo` and overwrites the placeholder value 0 with an actual
displacement.
There are many different types of relocations. For example, if you
@ -139,7 +139,7 @@ identify a function or a data in C++, because for example `foo` may
be in a namespace or defined as a static member in some class. If
`foo` is an overloaded function, we need to distinguish different
`foo`s by its type. Therefore, C++ compiler mangles an identifier by
appending nmaepsace names, type information and such so that
appending namespace names, type information and such so that
different things get different names.
For example, a function `int foo(int)` in a namespace `bar` is

View File

@ -85,6 +85,8 @@ Options:
--defsym=SYMBOL=VALUE Define a symbol alias
--demangle Demangle C++ symbols in log messages (default)
--no-demangle
--detach Create separate debug info file in the background (default)
--no-detach
--enable-new-dtags Emit DT_RUNPATH for --rpath (default)
--disable-new-dtags Emit DT_RPATH for --rpath
--execute-only Make executable segments unreadable
@ -143,6 +145,8 @@ Options:
--rpath-link DIR Ignored
--run COMMAND ARG... Run COMMAND with mold as /usr/bin/ld
--section-start=SECTION=ADDR Set address for section
--separate-debug-file[=FILE] Separate debug info to the specified file
--no-separate-debug-file
--shared, --Bshareable Create a shared library
--shuffle-sections[=SEED] Randomize the output by shuffling input sections
--sort-common Ignored
@ -526,6 +530,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
std::optional<SeparateCodeKind> z_separate_code;
std::optional<bool> report_undefined;
std::optional<bool> z_relro;
std::optional<std::string> separate_debug_file;
std::optional<u64> shuffle_sections_seed;
std::unordered_set<std::string_view> rpaths;
@ -756,6 +761,10 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.demangle = true;
} else if (read_flag("no-demangle")) {
ctx.arg.demangle = false;
} else if (read_flag("detach")) {
ctx.arg.detach = true;
} else if (read_flag("no-detach")) {
ctx.arg.detach = false;
} else if (read_flag("default-symver")) {
ctx.arg.default_symver = true;
} else if (read_flag("noinhibit-exec")) {
@ -1003,6 +1012,12 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.z_origin = true;
} else if (read_z_flag("nodefaultlib")) {
ctx.arg.z_nodefaultlib = true;
} else if (read_eq("separate-debug-file")) {
separate_debug_file = arg;
} else if (read_flag("separate-debug-file")) {
separate_debug_file = "";
} else if (read_flag("no-separate-debug-file")) {
separate_debug_file.reset();
} else if (read_z_flag("separate-loadable-segments")) {
z_separate_code = SEPARATE_LOADABLE_SEGMENTS;
} else if (read_z_flag("separate-code")) {
@ -1394,9 +1409,20 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.default_version = VER_NDX_LAST_RESERVED + 1;
}
if (separate_debug_file) {
if (separate_debug_file->empty())
ctx.arg.separate_debug_file = ctx.arg.output + ".dbg";
else
ctx.arg.separate_debug_file = *separate_debug_file;
}
if (ctx.arg.shared && warn_shared_textrel)
ctx.arg.warn_textrel = true;
// We don't want the background process to write to stdout
if (ctx.arg.stats || ctx.arg.perf)
ctx.arg.detach = false;
ctx.arg.undefined.push_back(ctx.arg.entry);
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++) {

View File

@ -559,14 +559,17 @@ int elf_main(int argc, char **argv) {
// Compute the is_weak bit for each imported symbol.
compute_imported_symbol_weakness(ctx);
// Compute sizes of output sections while assigning offsets
// within an output section to input sections.
compute_section_sizes(ctx);
// Sort sections by section attributes so that we'll have to
// create as few segments as possible.
sort_output_sections(ctx);
if (!ctx.arg.separate_debug_file.empty())
separate_debug_sections(ctx);
// Compute sizes of output sections while assigning offsets
// within an output section to input sections.
compute_section_sizes(ctx);
// If --packed_dyn_relocs=relr was given, base relocations are stored
// to a .relr.dyn section in a compressed form. Construct a compressed
// relocations now so that we can fix section sizes and file layout.
@ -659,9 +662,12 @@ int elf_main(int argc, char **argv) {
// .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)
if (ctx.gdb_index && ctx.arg.separate_debug_file.empty())
write_gdb_index(ctx);
if (!ctx.arg.separate_debug_file.empty())
write_gnu_debuglink(ctx);
t_copy.stop();
ctx.checkpoint();
@ -680,6 +686,9 @@ int elf_main(int argc, char **argv) {
if (ctx.arg.print_map)
print_map(ctx);
if (!ctx.arg.separate_debug_file.empty())
write_separate_debug_file(ctx);
// Show stats numbers
if (ctx.arg.stats)
show_stats(ctx);
@ -690,9 +699,7 @@ int elf_main(int argc, char **argv) {
std::cout << std::flush;
std::cerr << std::flush;
if (ctx.arg.fork)
notify_parent();
notify_parent();
release_global_lock();
if (ctx.arg.quick_exit)

View File

@ -993,6 +993,22 @@ private:
std::map<u32, u32> properties;
};
template <typename E>
class GnuDebuglinkSection : public Chunk<E> {
public:
GnuDebuglinkSection() {
this->name = ".gnu_debuglink";
this->shdr.sh_type = SHT_PROGBITS;
this->shdr.sh_addralign = 4;
}
void update_shdr(Context<E> &ctx) override;
void copy_buf(Context<E> &ctx) override;
std::string filename;
u32 crc32 = 0;
};
template <typename E>
class GdbIndexSection : public Chunk<E> {
public:
@ -1439,11 +1455,14 @@ 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 compute_address_significance(Context<E> &);
template <typename E> void separate_debug_sections(Context<E> &);
template <typename E> void compute_section_headers(Context<E> &);
template <typename E> i64 set_osec_offsets(Context<E> &);
template <typename E> void fix_synthetic_symbols(Context<E> &);
template <typename E> i64 compress_debug_sections(Context<E> &);
template <typename E> void write_build_id(Context<E> &);
template <typename E> void write_gnu_debuglink(Context<E> &);
template <typename E> void write_separate_debug_file(Context<E> &ctx);
template <typename E> void write_dependency_file(Context<E> &);
template <typename E> void show_stats(Context<E> &);
@ -1721,6 +1740,7 @@ struct Context {
bool color_diagnostics = false;
bool default_symver = false;
bool demangle = true;
bool detach = true;
bool discard_all = false;
bool discard_locals = false;
bool eh_frame_hdr = true;
@ -1807,6 +1827,7 @@ struct Context {
std::string package_metadata;
std::string plugin;
std::string rpaths;
std::string separate_debug_file;
std::string soname;
std::string sysroot;
std::unique_ptr<std::unordered_set<std::string_view>> retain_symbols_file;
@ -1885,6 +1906,9 @@ struct Context {
tbb::concurrent_hash_map<Symbol<E> *, std::vector<std::string>> undef_errors;
// For --separate-debug-file
std::vector<Chunk<E> *> debug_chunks;
// Output chunks
OutputEhdr<E> *ehdr = nullptr;
OutputShdr<E> *shdr = nullptr;
@ -1900,6 +1924,7 @@ struct Context {
DynstrSection<E> *dynstr = nullptr;
HashSection<E> *hash = nullptr;
GnuHashSection<E> *gnu_hash = nullptr;
GnuDebuglinkSection<E> *gnu_debuglink = nullptr;
ShstrtabSection<E> *shstrtab = nullptr;
PltSection<E> *plt = nullptr;
PltGotSection<E> *pltgot = nullptr;

View File

@ -2948,6 +2948,20 @@ void ComdatGroupSection<E>::copy_buf(Context<E> &ctx) {
*buf++ = chunk->shndx;
}
template <typename E>
void GnuDebuglinkSection<E>::update_shdr(Context<E> &ctx) {
filename = std::filesystem::path(ctx.arg.separate_debug_file).filename().string();
this->shdr.sh_size = align_to(filename.size() + 1, 4) + 4;
}
template <typename E>
void GnuDebuglinkSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
memset(buf, 0, this->shdr.sh_size);
write_string(buf, filename);
*(U32<E> *)(buf + this->shdr.sh_size - 4) = crc32;
}
using E = MOLD_TARGET;
template class Chunk<E>;
@ -2986,6 +3000,7 @@ template class GdbIndexSection<E>;
template class CompressedSection<E>;
template class RelocSection<E>;
template class ComdatGroupSection<E>;
template class GnuDebuglinkSection<E>;
template OutputSection<E> *find_section(Context<E> &, u32);
template OutputSection<E> *find_section(Context<E> &, std::string_view);

View File

@ -156,6 +156,8 @@ void create_synthetic_sections(Context<E> &ctx) {
ctx.verdef = push(new VerdefSection<E>);
if (ctx.arg.emit_relocs)
ctx.eh_frame_reloc = push(new EhFrameRelocSection<E>);
if (!ctx.arg.separate_debug_file.empty())
ctx.gnu_debuglink = push(new GnuDebuglinkSection<E>);
if (ctx.arg.shared || !ctx.dsos.empty() || ctx.arg.pie) {
ctx.dynamic = push(new DynamicSection<E>(ctx));
@ -2602,6 +2604,24 @@ static i64 set_file_offsets(Context<E> &ctx) {
return fileoff;
}
// Remove debug sections from ctx.chunks and save them to ctx.debug_chunks.
// This is for --separate-debug-file.
template <typename E>
void separate_debug_sections(Context<E> &ctx) {
auto is_debug_section = [&](Chunk<E> *chunk) {
if (chunk->shdr.sh_flags & SHF_ALLOC)
return false;
return chunk == ctx.gdb_index || chunk == ctx.symtab || chunk == ctx.strtab ||
chunk->name.starts_with(".debug_");
};
auto mid = std::stable_partition(ctx.chunks.begin(), ctx.chunks.end(),
is_debug_section);
ctx.debug_chunks = {ctx.chunks.begin(), mid};
ctx.chunks.erase(ctx.chunks.begin(), mid);
}
template <typename E>
void compute_section_headers(Context<E> &ctx) {
// Update sh_size for each chunk.
@ -2993,6 +3013,106 @@ void write_build_id(Context<E> &ctx) {
ctx.buildid->copy_buf(ctx);
}
// A .gnu_debuglink section contains a filename and a CRC32 checksum of a
// debug info file. When we are writing a .gnu_debuglink, we don't know
// its CRC32 checksum because we haven't created a debug info file. So we
// write a dummy value instead.
//
// We can't choose a random value as a dummy value for build
// reproducibility. We also don't want to write a fixed value for all
// files because the CRC checksum is in this section to prevent using
// wrong file on debugging. gdb rejects a debug info file if its CRC
// doesn't match with the one in .gdb_debuglink.
//
// Therefore, we'll try to make our CRC checksum as unique as possible.
// We'll remember that checksum, and after creating a debug info file, add
// a few bytes of garbage at the end of it so that the debug info file's
// CRC checksum becomes the one that we have precomputed.
template <typename E>
void write_gnu_debuglink(Context<E> &ctx) {
Timer t(ctx, "write_gnu_debuglink");
u32 crc32;
if (ctx.buildid) {
crc32 = compute_crc32(0, ctx.buildid->contents.data(),
ctx.buildid->contents.size());
} else {
std::vector<std::span<u8>> shards = get_shards(ctx);
std::vector<U64<E>> hashes(shards.size());
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
hashes[i] = hash_string({(char *)shards[i].data(), shards[i].size()});
});
crc32 = compute_crc32(0, (u8 *)hashes.data(), hashes.size() * 8);
}
ctx.gnu_debuglink->crc32 = crc32;
ctx.gnu_debuglink->copy_buf(ctx);
}
// Write a separate debug file. This function is called after we finish
// writing to the usual output file.
template <typename E>
void write_separate_debug_file(Context<E> &ctx) {
Timer t(ctx, "write_separate_debug_file");
// We want to write to the debug info file in background so that the
// user doesn't have to wait for it to complete.
if (ctx.arg.detach)
notify_parent();
// A debug info file contains all sections as the original file, though
// most of them can be empty as if they were bss sections. We convert
// real sections into dummy sections here.
for (i64 i = 0; i < ctx.chunks.size(); i++) {
Chunk<E> *chunk = ctx.chunks[i];
if (chunk != ctx.ehdr && chunk != ctx.shdr && chunk != ctx.shstrtab &&
chunk->shdr.sh_type != SHT_NOTE) {
Chunk<E> *sec = new OutputSection<E>(chunk->name, SHT_NULL);
sec->shdr = chunk->shdr;
sec->shdr.sh_type = SHT_NOBITS;
ctx.chunks[i] = sec;
ctx.chunk_pool.emplace_back(sec);
}
}
// Restore debug info sections that had been set aside while we were
// creating the main file.
tbb::parallel_for_each(ctx.debug_chunks, [&](Chunk<E> *chunk) {
chunk->compute_section_size(ctx);
});
append(ctx.chunks, ctx.debug_chunks);
// Write to the debug info file as if it were a regular output file.
compute_section_headers(ctx);
i64 filesize = set_osec_offsets(ctx);
ctx.output_file =
OutputFile<Context<E>>::open(ctx, ctx.arg.separate_debug_file,
filesize, 0666);
ctx.buf = ctx.output_file->buf;
copy_chunks(ctx);
if (ctx.gdb_index)
write_gdb_index(ctx);
// Reverse-compute a CRC32 value so that the CRC32 checksum embedded to
// the .gnu_debuglink section in the main executable matches with the
// debug info file's CRC32 checksum.
u32 crc = compute_crc32(0, ctx.buf, filesize);
std::vector<u8> &buf2 = ctx.output_file->buf2;
if (!buf2.empty())
crc = compute_crc32(crc, buf2.data(), buf2.size());
std::vector<u8> trailer = crc32_solve(crc, ctx.gnu_debuglink->crc32);
append(ctx.output_file->buf2, trailer);
ctx.output_file->close(ctx);
}
// Write Makefile-style dependency rules to a file specified by
// --dependency-file. This is analogous to the compiler's -M flag.
template <typename E>
@ -3126,11 +3246,14 @@ template void apply_version_script(Context<E> &);
template void parse_symbol_version(Context<E> &);
template void compute_import_export(Context<E> &);
template void compute_address_significance(Context<E> &);
template void separate_debug_sections(Context<E> &);
template void compute_section_headers(Context<E> &);
template i64 set_osec_offsets(Context<E> &);
template void fix_synthetic_symbols(Context<E> &);
template i64 compress_debug_sections(Context<E> &);
template void write_build_id(Context<E> &);
template void write_gnu_debuglink(Context<E> &);
template void write_separate_debug_file(Context<E> &);
template void write_dependency_file(Context<E> &);
template void show_stats(Context<E> &);

26
test/elf/separate-debug-file.sh Executable file
View File

@ -0,0 +1,26 @@
#!/bin/bash
. $(dirname $0)/common.inc
on_qemu && skip
command -v gdb >& /dev/null || skip
command -v flock >& /dev/null || skip
cat <<EOF > $t/a.c
#include <stdio.h>
int main() {
printf("Hello world\n");
}
EOF
$CC -c -o $t/a.o $t/a.c -g
$CC -B. -o $t/exe1 $t/a.o -Wl,--separate-debug-file
readelf -SW $t/exe1 | grep -Fq .gnu_debuglink
$CC -c -o $t/a.o $t/a.c -g
$CC -B. -o $t/exe2 $t/a.o -Wl,--separate-debug-file -Wl,--no-build-id
readelf -SW $t/exe2 | grep -Fq .gnu_debuglink
sleep 1
gdb $t/exe1 -ex 'list main' -ex 'quit' | grep -Fq printf
gdb $t/exe2 -ex 'list main' -ex 'quit' | grep -Fq printf

View File

@ -37,5 +37,5 @@ grep -Eq '.note.baz\s+NOTE.+000008 00 A 0 0 8' $t/log
grep -Eq '.note.nonalloc\s+NOTE.+000008 00 0 0 1' $t/log
readelf --segments $t/exe > $t/log
grep -Fq '01 .note.baz .note.foo .note.bar' $t/log
grep -Fq '01 .note.bar .note.baz .note.foo' $t/log
! grep -q 'NOTE.*0x0000000000000000 0x0000000000000000' $t/log || false

View File

@ -29,4 +29,4 @@ EOF
./mold -o $t/exe $t/a.o $t/b.o $t/c.o $t/d.o
readelf --segments $t/exe > $t/log
grep -Fq '01 .note.a .note.c .note.b' $t/log
grep -Fq '01 .note.a .note.b .note.c' $t/log