diff --git a/tpde-llvm/src/LLVMCompilerBase.hpp b/tpde-llvm/src/LLVMCompilerBase.hpp index be145ba8..d0bdf4ca 100644 --- a/tpde-llvm/src/LLVMCompilerBase.hpp +++ b/tpde-llvm/src/LLVMCompilerBase.hpp @@ -25,6 +25,7 @@ #include "tpde/Assembler.hpp" #include "tpde/CompilerBase.hpp" +#include "tpde/DwarfLineWriter.hpp" #include "tpde/ValLocalIdx.hpp" #include "tpde/ValueAssignment.hpp" #include "tpde/base.hpp" @@ -169,6 +170,8 @@ struct LLVMCompilerBase : public LLVMCompiler, tpde::util::SmallVector, 16> type_info_syms; + std::unique_ptr dw_line_writer; + enum class LibFunc { divti3, udivti3, @@ -335,6 +338,66 @@ struct LLVMCompilerBase : public LLVMCompiler, } public: + /// Record a debug location at the current PC + void record_debug_location(llvm::DebugLoc debug_loc) { + if (!dw_line_writer || !debug_loc) { + return; + } + + const u32 current_pc = this->text_writer.offset(); + // Avoid emitting duplicate locations for the same PC, which can happen + // when multiple instructions share the same debug location. + if (dw_line_writer->offset_equals_last_offset(current_pc)) { + return; + } + + const llvm::DILocation *di_loc = debug_loc.get(); + const u32 line = di_loc->getLine(); + // Line number 0 is used to indicate an unknown location, so skip it. + if (line == 0) { + return; + } + + dw_line_writer->push_location( + current_pc, + tpde::DebugLocation{ + .file_name = di_loc->getFilename(), + .directory = di_loc->getDirectory(), + .line = di_loc->getLine(), + .column = static_cast(di_loc->getColumn()), + }); + } + + void set_current_debug_location_function(const IRFuncRef func) { + if (!dw_line_writer) { + return; + } + + const u32 current_pc = this->text_writer.offset(); + if (const llvm::DISubprogram *sp = func->getSubprogram()) { + dw_line_writer->push_location( + current_pc, + tpde::DebugLocation{ + .file_name = sp->getFilename(), + .directory = sp->getDirectory(), + .line = sp->getLine(), + .column = 0u, + }); + dw_line_writer->set_subprogram_written(); + } + } + + void finish_debug_location_function(const IRFuncRef, const u32 func_idx) { + if (!dw_line_writer) { + return; + } + + const u32 sym_id = this->func_syms[func_idx].id(); + const auto it = this->func_sym_id_to_skew.find(sym_id); + const u32 skew = (it != this->func_sym_id_to_skew.end()) ? it->second : 0u; + dw_line_writer->end_function(skew); + } + /// Whether to use a DSO-local access instead of going through the GOT. static bool use_local_access(const llvm::GlobalValue *gv) { // If the symbol is preemptible, don't generate a local access. @@ -1250,6 +1313,11 @@ bool LLVMCompilerBase::compile(llvm::Module &mod) { group_secs.clear(); libfunc_syms.fill({}); + if (mod.getNamedMetadata("llvm.dbg.cu") != nullptr) { + const tpde::DwarfConfig cfg(Config::MIN_INST_WIDTH); + dw_line_writer = std::make_unique(cfg); + } + if (!Base::compile()) { return false; } @@ -1270,6 +1338,12 @@ bool LLVMCompilerBase::compile(llvm::Module &mod) { this->assembler.sym_copy(dst_sym, from_sym); } + // Write DWARF 5 line information if enabled and the module has debug info. + if (dw_line_writer) { + llvm::TimeTraceScope time_scope("TPDE_DebugLine_Write"); + dw_line_writer->finalize(this->assembler); + } + return true; } @@ -1366,6 +1440,9 @@ bool LLVMCompilerBase::compile_inst( return res; }(); + // Record debug location before instruction + record_debug_location(i->getDebugLoc()); + const ValInfo &val_info = this->adaptor->val_info(i); assert(i->getOpcode() < fns.size()); const auto [compile_fn, arg] = fns[i->getOpcode()]; diff --git a/tpde/CMakeLists.txt b/tpde/CMakeLists.txt index 7613be53..ffe6adb9 100644 --- a/tpde/CMakeLists.txt +++ b/tpde/CMakeLists.txt @@ -33,6 +33,7 @@ target_sources(tpde PRIVATE src/Assembler.cpp src/AssemblerElf.cpp src/base.cpp + src/DwarfLineWriter.cpp src/ElfMapper.cpp src/FunctionWriter.cpp src/StringTable.cpp @@ -52,6 +53,7 @@ target_sources(tpde PRIVATE include/tpde/CompilerBase.hpp include/tpde/CompilerConfig.hpp include/tpde/DWARF.hpp + include/tpde/DwarfLineWriter.hpp include/tpde/ELF.hpp include/tpde/ElfMapper.hpp include/tpde/FunctionWriter.hpp diff --git a/tpde/include/tpde/Assembler.hpp b/tpde/include/tpde/Assembler.hpp index 28a775e8..cd395609 100644 --- a/tpde/include/tpde/Assembler.hpp +++ b/tpde/include/tpde/Assembler.hpp @@ -53,15 +53,18 @@ struct Relocation { /// Section kinds, lowered to file-format specific flags. enum class SectionKind : u8 { - Text, ///< Text section, executable code (ELF .text) - ReadOnly, ///< Read-only data section (ELF .rodata) - EHFrame, ///< EH Frame section (ELF .eh_frame) - LSDA, ///< LSDA section (ELF .gcc_except_table) - Data, ///< Writable data section (ELF .data) - DataRelRO, ///< Read-only data section with relocations (ELF .data.rel.ro) - BSS, ///< Zero-initialized data section (ELF .bss) - ThreadData, ///< Initialized thread-local data section (ELF .tdata) - ThreadBSS, ///< Zero-initialized thread-local data section (ELF .tbss) + Text, ///< Text section, executable code (ELF .text) + ReadOnly, ///< Read-only data section (ELF .rodata) + EHFrame, ///< EH Frame section (ELF .eh_frame) + LSDA, ///< LSDA section (ELF .gcc_except_table) + Data, ///< Writable data section (ELF .data) + DataRelRO, ///< Read-only data section with relocations (ELF .data.rel.ro) + BSS, ///< Zero-initialized data section (ELF .bss) + ThreadData, ///< Initialized thread-local data section (ELF .tdata) + ThreadBSS, ///< Zero-initialized thread-local data section (ELF .tbss) + DebugInfo, ///< DWARF debug info (ELF .debug_info) + DebugLine, ///< DWARF line information (ELF .debug_line) + DebugAbbrev, ///< DWARF abbreviations (ELF .debug_abbrev) Max }; diff --git a/tpde/include/tpde/CompilerBase.hpp b/tpde/include/tpde/CompilerBase.hpp index 26e4a13e..d0850bbb 100644 --- a/tpde/include/tpde/CompilerBase.hpp +++ b/tpde/include/tpde/CompilerBase.hpp @@ -194,6 +194,8 @@ struct CompilerBase { // allocations util::SmallVector