1
1
mirror of https://github.com/rui314/mold.git synced 2024-09-11 13:06:59 +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. # Add other non-template source files.
target_sources(mold PRIVATE target_sources(mold PRIVATE
common/compress.cc common/compress.cc
common/crc32.cc
common/demangle.cc common/demangle.cc
common/filepath.cc common/filepath.cc
common/glob.cc common/glob.cc

View File

@ -2,6 +2,7 @@
#include "integers.h" #include "integers.h"
#include <array>
#include <atomic> #include <atomic>
#include <bit> #include <bit>
#include <bitset> #include <bitset>
@ -899,6 +900,13 @@ std::optional<std::string_view> demangle_rust(std::string_view name);
void acquire_global_lock(); void acquire_global_lock();
void release_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 // 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 A .so file. Short for Dynamic Shared Object. Often called as a
shared library, a dynamic libray or a shared object as well. 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 executables and/or other DSOs. At runtime, a DSO is loaded to a
contiguous region in the virtual address. 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`, if you compile a C source file containing a call of `printf`,
the actual function code of `printf` is not included in the resulting the actual function code of `printf` is not included in the resulting
object file. You include `stdio.h`, but that teaches the compiler 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`. `printf` actually does. Therefore, it cannot emit code for `printf`.
You need to link an object file with other object file or a shared 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 ## Virtual address space
A pointer has a value like 0x803020 which is an address of the 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 pointee. But it doesn't mean that the pointee resides at the
physical memory address 0x803020 on the computer. Modern CPUs 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 memory are first translated by MMU to the physical address.
The address before translation is called the "virtual address". The address before translation is called the "virtual address".
Unless you are doing the kernel programming, all addresses you Unless you are doing the kernel programming, all addresses you
handle are virtual addresses. handle are virtual addresses.
The OS kernel controls the MMU so that each process owns the entire 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 address, they don't conflict. They are mapped to different physical
addresses. 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 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 instruction begins with `0xe8`. The following four bytes are
displacement; that is, the address of the branch target relative to displacement; that is, the address of the branch target relative to
the end of this `callq` instruction. Notice that the displacement is the end of this `callq` instruction. Notice that the displacement is
0. The compiler couldn't fill the displacement because it has no 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 idea as to where `foo` will be at runtime. So, the compiler writes 0
as a placeholder and instead write a relocation `R_X86_64_PLT32` as a placeholder and instead writes a relocation `R_X86_64_PLT32`
with `foo` as its associated symbol. The linker reads this with `foo` as its associated symbol. The linker reads this
relocation, computes the offsets between this call instruction and 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. displacement.
There are many different types of relocations. For example, if you 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 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` is an overloaded function, we need to distinguish different
`foo`s by its type. Therefore, C++ compiler mangles an identifier by `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. different things get different names.
For example, a function `int foo(int)` in a namespace `bar` is 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 --defsym=SYMBOL=VALUE Define a symbol alias
--demangle Demangle C++ symbols in log messages (default) --demangle Demangle C++ symbols in log messages (default)
--no-demangle --no-demangle
--detach Create separate debug info file in the background (default)
--no-detach
--enable-new-dtags Emit DT_RUNPATH for --rpath (default) --enable-new-dtags Emit DT_RUNPATH for --rpath (default)
--disable-new-dtags Emit DT_RPATH for --rpath --disable-new-dtags Emit DT_RPATH for --rpath
--execute-only Make executable segments unreadable --execute-only Make executable segments unreadable
@ -143,6 +145,8 @@ Options:
--rpath-link DIR Ignored --rpath-link DIR Ignored
--run COMMAND ARG... Run COMMAND with mold as /usr/bin/ld --run COMMAND ARG... Run COMMAND with mold as /usr/bin/ld
--section-start=SECTION=ADDR Set address for section --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 --shared, --Bshareable Create a shared library
--shuffle-sections[=SEED] Randomize the output by shuffling input sections --shuffle-sections[=SEED] Randomize the output by shuffling input sections
--sort-common Ignored --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<SeparateCodeKind> z_separate_code;
std::optional<bool> report_undefined; std::optional<bool> report_undefined;
std::optional<bool> z_relro; std::optional<bool> z_relro;
std::optional<std::string> separate_debug_file;
std::optional<u64> shuffle_sections_seed; std::optional<u64> shuffle_sections_seed;
std::unordered_set<std::string_view> rpaths; 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; ctx.arg.demangle = true;
} else if (read_flag("no-demangle")) { } else if (read_flag("no-demangle")) {
ctx.arg.demangle = false; 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")) { } else if (read_flag("default-symver")) {
ctx.arg.default_symver = true; ctx.arg.default_symver = true;
} else if (read_flag("noinhibit-exec")) { } 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; ctx.arg.z_origin = true;
} else if (read_z_flag("nodefaultlib")) { } else if (read_z_flag("nodefaultlib")) {
ctx.arg.z_nodefaultlib = true; 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")) { } else if (read_z_flag("separate-loadable-segments")) {
z_separate_code = SEPARATE_LOADABLE_SEGMENTS; z_separate_code = SEPARATE_LOADABLE_SEGMENTS;
} else if (read_z_flag("separate-code")) { } 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; 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) if (ctx.arg.shared && warn_shared_textrel)
ctx.arg.warn_textrel = true; 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); ctx.arg.undefined.push_back(ctx.arg.entry);
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++) { 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 the is_weak bit for each imported symbol.
compute_imported_symbol_weakness(ctx); 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 // Sort sections by section attributes so that we'll have to
// create as few segments as possible. // create as few segments as possible.
sort_output_sections(ctx); 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 // If --packed_dyn_relocs=relr was given, base relocations are stored
// to a .relr.dyn section in a compressed form. Construct a compressed // to a .relr.dyn section in a compressed form. Construct a compressed
// relocations now so that we can fix section sizes and file layout. // 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 // .gdb_index's contents cannot be constructed before applying
// relocations to other debug sections. We have relocated debug // relocations to other debug sections. We have relocated debug
// sections now, so write the .gdb_index section. // 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); write_gdb_index(ctx);
if (!ctx.arg.separate_debug_file.empty())
write_gnu_debuglink(ctx);
t_copy.stop(); t_copy.stop();
ctx.checkpoint(); ctx.checkpoint();
@ -680,6 +686,9 @@ int elf_main(int argc, char **argv) {
if (ctx.arg.print_map) if (ctx.arg.print_map)
print_map(ctx); print_map(ctx);
if (!ctx.arg.separate_debug_file.empty())
write_separate_debug_file(ctx);
// Show stats numbers // Show stats numbers
if (ctx.arg.stats) if (ctx.arg.stats)
show_stats(ctx); show_stats(ctx);
@ -690,9 +699,7 @@ int elf_main(int argc, char **argv) {
std::cout << std::flush; std::cout << std::flush;
std::cerr << std::flush; std::cerr << std::flush;
if (ctx.arg.fork) notify_parent();
notify_parent();
release_global_lock(); release_global_lock();
if (ctx.arg.quick_exit) if (ctx.arg.quick_exit)

View File

@ -993,6 +993,22 @@ private:
std::map<u32, u32> properties; 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> template <typename E>
class GdbIndexSection : public Chunk<E> { class GdbIndexSection : public Chunk<E> {
public: 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 parse_symbol_version(Context<E> &);
template <typename E> void compute_import_export(Context<E> &); template <typename E> void compute_import_export(Context<E> &);
template <typename E> void compute_address_significance(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> void compute_section_headers(Context<E> &);
template <typename E> i64 set_osec_offsets(Context<E> &); template <typename E> i64 set_osec_offsets(Context<E> &);
template <typename E> void fix_synthetic_symbols(Context<E> &); template <typename E> void fix_synthetic_symbols(Context<E> &);
template <typename E> i64 compress_debug_sections(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_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 write_dependency_file(Context<E> &);
template <typename E> void show_stats(Context<E> &); template <typename E> void show_stats(Context<E> &);
@ -1721,6 +1740,7 @@ struct Context {
bool color_diagnostics = false; bool color_diagnostics = false;
bool default_symver = false; bool default_symver = false;
bool demangle = true; bool demangle = true;
bool detach = true;
bool discard_all = false; bool discard_all = false;
bool discard_locals = false; bool discard_locals = false;
bool eh_frame_hdr = true; bool eh_frame_hdr = true;
@ -1807,6 +1827,7 @@ struct Context {
std::string package_metadata; std::string package_metadata;
std::string plugin; std::string plugin;
std::string rpaths; std::string rpaths;
std::string separate_debug_file;
std::string soname; std::string soname;
std::string sysroot; std::string sysroot;
std::unique_ptr<std::unordered_set<std::string_view>> retain_symbols_file; 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; tbb::concurrent_hash_map<Symbol<E> *, std::vector<std::string>> undef_errors;
// For --separate-debug-file
std::vector<Chunk<E> *> debug_chunks;
// Output chunks // Output chunks
OutputEhdr<E> *ehdr = nullptr; OutputEhdr<E> *ehdr = nullptr;
OutputShdr<E> *shdr = nullptr; OutputShdr<E> *shdr = nullptr;
@ -1900,6 +1924,7 @@ struct Context {
DynstrSection<E> *dynstr = nullptr; DynstrSection<E> *dynstr = nullptr;
HashSection<E> *hash = nullptr; HashSection<E> *hash = nullptr;
GnuHashSection<E> *gnu_hash = nullptr; GnuHashSection<E> *gnu_hash = nullptr;
GnuDebuglinkSection<E> *gnu_debuglink = nullptr;
ShstrtabSection<E> *shstrtab = nullptr; ShstrtabSection<E> *shstrtab = nullptr;
PltSection<E> *plt = nullptr; PltSection<E> *plt = nullptr;
PltGotSection<E> *pltgot = nullptr; PltGotSection<E> *pltgot = nullptr;

View File

@ -2948,6 +2948,20 @@ void ComdatGroupSection<E>::copy_buf(Context<E> &ctx) {
*buf++ = chunk->shndx; *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; using E = MOLD_TARGET;
template class Chunk<E>; template class Chunk<E>;
@ -2986,6 +3000,7 @@ template class GdbIndexSection<E>;
template class CompressedSection<E>; template class CompressedSection<E>;
template class RelocSection<E>; template class RelocSection<E>;
template class ComdatGroupSection<E>; template class ComdatGroupSection<E>;
template class GnuDebuglinkSection<E>;
template OutputSection<E> *find_section(Context<E> &, u32); template OutputSection<E> *find_section(Context<E> &, u32);
template OutputSection<E> *find_section(Context<E> &, std::string_view); 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>); ctx.verdef = push(new VerdefSection<E>);
if (ctx.arg.emit_relocs) if (ctx.arg.emit_relocs)
ctx.eh_frame_reloc = push(new EhFrameRelocSection<E>); 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) { if (ctx.arg.shared || !ctx.dsos.empty() || ctx.arg.pie) {
ctx.dynamic = push(new DynamicSection<E>(ctx)); ctx.dynamic = push(new DynamicSection<E>(ctx));
@ -2602,6 +2604,24 @@ static i64 set_file_offsets(Context<E> &ctx) {
return fileoff; 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> template <typename E>
void compute_section_headers(Context<E> &ctx) { void compute_section_headers(Context<E> &ctx) {
// Update sh_size for each chunk. // Update sh_size for each chunk.
@ -2993,6 +3013,106 @@ void write_build_id(Context<E> &ctx) {
ctx.buildid->copy_buf(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 // Write Makefile-style dependency rules to a file specified by
// --dependency-file. This is analogous to the compiler's -M flag. // --dependency-file. This is analogous to the compiler's -M flag.
template <typename E> template <typename E>
@ -3126,11 +3246,14 @@ template void apply_version_script(Context<E> &);
template void parse_symbol_version(Context<E> &); template void parse_symbol_version(Context<E> &);
template void compute_import_export(Context<E> &); template void compute_import_export(Context<E> &);
template void compute_address_significance(Context<E> &); template void compute_address_significance(Context<E> &);
template void separate_debug_sections(Context<E> &);
template void compute_section_headers(Context<E> &); template void compute_section_headers(Context<E> &);
template i64 set_osec_offsets(Context<E> &); template i64 set_osec_offsets(Context<E> &);
template void fix_synthetic_symbols(Context<E> &); template void fix_synthetic_symbols(Context<E> &);
template i64 compress_debug_sections(Context<E> &); template i64 compress_debug_sections(Context<E> &);
template void write_build_id(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 write_dependency_file(Context<E> &);
template void show_stats(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 grep -Eq '.note.nonalloc\s+NOTE.+000008 00 0 0 1' $t/log
readelf --segments $t/exe > $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 ! 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 ./mold -o $t/exe $t/a.o $t/b.o $t/c.o $t/d.o
readelf --segments $t/exe > $t/log 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