diff --git a/common/common.h b/common/common.h index a9fa8f03..15eb1195 100644 --- a/common/common.h +++ b/common/common.h @@ -795,6 +795,14 @@ private: int perm; }; +template +class LockingOutputFile : public OutputFile { +public: + LockingOutputFile(Context &ctx, std::string path, int perm); + void resize(Context &ctx, i64 filesize); + void close(Context &ctx) override; +}; + // // hyperloglog.cc // diff --git a/common/output-file-unix.h b/common/output-file-unix.h index 916ae05a..dcbebfb3 100644 --- a/common/output-file-unix.h +++ b/common/output-file-unix.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -143,4 +144,52 @@ OutputFile::open(Context &ctx, std::string path, i64 filesize, int perm return std::unique_ptr(file); } +// LockingOutputFile is similar to MemoryMappedOutputFile, but it doesn't +// rename output files and instead acquires file lock using flock(). +template +LockingOutputFile::LockingOutputFile(Context &ctx, std::string path, + int perm) + : OutputFile(path, 0, true) { + this->fd = ::open(path.c_str(), O_RDWR | O_CREAT, perm); + if (this->fd == -1) + Fatal(ctx) << "cannot open " << path << ": " << errno_string(); + flock(this->fd, LOCK_EX); + + // We may be overwriting to an existing debug info file. We want to + // make the file unusable so that gdb won't use it by accident until + // it's ready. + u8 buf[256] = {}; + (void)!!write(this->fd, buf, sizeof(buf)); +} + +template +void LockingOutputFile::resize(Context &ctx, i64 filesize) { + if (ftruncate(this->fd, filesize) == -1) + Fatal(ctx) << "ftruncate failed: " << errno_string(); + + this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE, + MAP_SHARED, this->fd, 0); + if (this->buf == MAP_FAILED) + Fatal(ctx) << this->path << ": mmap failed: " << errno_string(); + + this->filesize = filesize; + mold::output_buffer_start = this->buf; + mold::output_buffer_end = this->buf + filesize; +} + +template +void LockingOutputFile::close(Context &ctx) { + if (!this->is_unmapped) + munmap(this->buf, this->filesize); + + if (!this->buf2.empty()) { + FILE *out = fdopen(this->fd, "w"); + fseek(out, 0, SEEK_END); + fwrite(&this->buf2[0], this->buf2.size(), 1, out); + fclose(out); + } + + ::close(this->fd); +} + } // namespace mold diff --git a/common/output-file-win32.h b/common/output-file-win32.h index 7a9606ef..5e37fb47 100644 --- a/common/output-file-win32.h +++ b/common/output-file-win32.h @@ -97,4 +97,17 @@ OutputFile::open(Context &ctx, std::string path, i64 filesize, int perm return std::unique_ptr>(file); } +template +LockingOutputFile::LockingOutputFile(Context &ctx, std::string path, + int perm) + : OutputFile(path, 0, true) { + Fatal(ctx) << "LockingOutputFile is not supported on Windows"; +} + +template +void LockingOutputFile::resize(Context &ctx, i64 filesize) {} + +template +void LockingOutputFile::close(Context &ctx) {} + } // namespace mold diff --git a/elf/passes.cc b/elf/passes.cc index d4b8a3af..a970e9a7 100644 --- a/elf/passes.cc +++ b/elf/passes.cc @@ -1,5 +1,6 @@ #include "mold.h" #include "blake3.h" +#include "../common/output-file.h" #include #include @@ -3056,6 +3057,10 @@ template void write_separate_debug_file(Context &ctx) { Timer t(ctx, "write_separate_debug_file"); + // Open an output file early + LockingOutputFile> *file = + new LockingOutputFile>(ctx, ctx.arg.separate_debug_file, 0666); + // 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) @@ -3087,11 +3092,9 @@ void write_separate_debug_file(Context &ctx) { // Write to the debug info file as if it were a regular output file. compute_section_headers(ctx); - i64 filesize = set_osec_offsets(ctx); + file->resize(ctx, set_osec_offsets(ctx)); - ctx.output_file = - OutputFile>::open(ctx, ctx.arg.separate_debug_file, - filesize, 0666); + ctx.output_file.reset(file); ctx.buf = ctx.output_file->buf; copy_chunks(ctx); @@ -3102,7 +3105,7 @@ void write_separate_debug_file(Context &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); + u32 crc = compute_crc32(0, ctx.buf, ctx.output_file->filesize); std::vector &buf2 = ctx.output_file->buf2; if (!buf2.empty()) diff --git a/test/elf/separate-debug-file.sh b/test/elf/separate-debug-file.sh index 33bf6c75..115ee4ba 100755 --- a/test/elf/separate-debug-file.sh +++ b/test/elf/separate-debug-file.sh @@ -16,11 +16,12 @@ $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 +flock $t/exe1 true +gdb $t/exe1 -ex 'list main' -ex 'quit' | grep -Fq printf + $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 +flock $t/exe2 true gdb $t/exe2 -ex 'list main' -ex 'quit' | grep -Fq printf