From d8a9cc9e27fdb7f72ecacf609efba87273b06cf5 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Sun, 28 Jun 2026 18:31:48 -0700 Subject: [PATCH 01/19] Implement a new CodeView debugging symbol emitter to cover additions and changes since CV8 implementation was written. --- compiler/src/build.d | 3 +- compiler/src/dmd/backend/backconfig.d | 2 + compiler/src/dmd/backend/cdef.d | 1 + compiler/src/dmd/backend/cgcv.d | 6 +- compiler/src/dmd/backend/cv8.d | 6 + compiler/src/dmd/backend/mscoffobj.d | 15 +- compiler/src/dmd/backend/pdb.d | 920 ++++++++++++++++++++++++++ compiler/src/dmd/cli.d | 2 + compiler/src/dmd/dmsc.d | 1 + compiler/src/dmd/globals.d | 1 + 10 files changed, 948 insertions(+), 9 deletions(-) create mode 100644 compiler/src/dmd/backend/pdb.d diff --git a/compiler/src/build.d b/compiler/src/build.d index 10be696c1c7a..1e4a915e58c8 100755 --- a/compiler/src/build.d +++ b/compiler/src/build.d @@ -1581,7 +1581,7 @@ auto sourceFiles() debugprint.d fp.d symbol.d dcode.d cgsched.d pdata.d util2.d backconfig.d rtlsym.d ptrntab.d dvarstats.d cgen.d barray.d cgcse.d elpicpie.d - dwarfeh.d dwarfdbginf.d cv8.d + dwarfeh.d dwarfdbginf.d cv8.d pdb.d machobj.d elfobj.d mscoffobj.d x86/nteh.d x86/cgreg.d x86/cg87.d x86/cgxmm.d x86/disasm86.d x86/cgcod.d x86/cod1.d x86/cod2.d x86/cod3.d x86/cod4.d x86/cod5.d @@ -2341,3 +2341,4 @@ void copyAndTouch(const string from, const string to) // Wrap standard library functions alias writeText = std.file.write; + diff --git a/compiler/src/dmd/backend/backconfig.d b/compiler/src/dmd/backend/backconfig.d index 97fc547483a8..030fd41ce951 100644 --- a/compiler/src/dmd/backend/backconfig.d +++ b/compiler/src/dmd/backend/backconfig.d @@ -86,6 +86,7 @@ void out_config_init( exefmt_t exefmt, bool generatedMain, // a main entrypoint is generated bool dataimports, + bool newpdb, // use the modern PDB/CodeView debug info emitter ref GlobalOptimizer go, ErrorCallbackBackend errorCallback) { @@ -103,6 +104,7 @@ void out_config_init( } cfg.fulltypes = CVNONE; cfg.fpxmmregs = false; + cfg.newpdb = newpdb; if (!arm) cfg.inline8087 = 1; cfg.memmodel = 0; diff --git a/compiler/src/dmd/backend/cdef.d b/compiler/src/dmd/backend/cdef.d index 7019b70d0975..31bc59dd4bd3 100644 --- a/compiler/src/dmd/backend/cdef.d +++ b/compiler/src/dmd/backend/cdef.d @@ -529,6 +529,7 @@ struct Config bool useTypeInfo; // implement TypeInfo bool useExceptions; // implement exception handling ubyte dwarf; // DWARF version + bool newpdb; // use the modern PDB/CodeView debug info emitter // Configuration that is not saved in precompiled header diff --git a/compiler/src/dmd/backend/cgcv.d b/compiler/src/dmd/backend/cgcv.d index 164b6318252f..4740210b1ced 100644 --- a/compiler/src/dmd/backend/cgcv.d +++ b/compiler/src/dmd/backend/cgcv.d @@ -21,6 +21,7 @@ import dmd.backend.cc : Classsym, Symbol; import dmd.backend.type; public import dmd.backend.cv8; +import dmd.backend.pdb : pdb_outsym; public import dmd.backend.dwarfdbginf : dwarf_outsym; import core.stdc.stdio; @@ -2179,7 +2180,10 @@ void cv_outsym(Symbol* s) break; case CV8: - cv8_outsym(s); + if (config.newpdb) + pdb_outsym(s); + else + cv8_outsym(s); break; default: diff --git a/compiler/src/dmd/backend/cv8.d b/compiler/src/dmd/backend/cv8.d index d56aaf635f22..b9744ccb1fc0 100644 --- a/compiler/src/dmd/backend/cv8.d +++ b/compiler/src/dmd/backend/cv8.d @@ -33,6 +33,7 @@ import dmd.backend.el; import dmd.backend.mscoffobj; import dmd.backend.obj; import dmd.backend.oper; +import dmd.backend.pdb : pdb_udt; import dmd.common.outbuffer; import dmd.backend.rtlsym; import dmd.backend.symbol : globsym; @@ -821,6 +822,11 @@ else void cv8_udt(const(char)* id, idx_t typidx) { //printf("cv8_udt('%s', %x)\n", id, typidx); + if (config.newpdb) + { + pdb_udt(id, typidx); + return; + } auto buf = currentfuncdata.f1buf; size_t len = strlen(id); diff --git a/compiler/src/dmd/backend/mscoffobj.d b/compiler/src/dmd/backend/mscoffobj.d index 3d986169ec7b..e9fd0bb8559f 100644 --- a/compiler/src/dmd/backend/mscoffobj.d +++ b/compiler/src/dmd/backend/mscoffobj.d @@ -32,6 +32,7 @@ import dmd.backend.symbol : symbol_generate, symbol_name, symbol_print, symbol_r import dmd.backend.obj; import dmd.backend.ty; import dmd.backend.type; +import dmd.backend.pdb; import dmd.backend.mscoff; @@ -355,7 +356,7 @@ Obj MsCoffObj_init(OutBuffer* objbuf, const(char)* filename, const(char)* csegna assert(SegData[UDATA].SDseg == UDATA); if (config.fulltypes) - cv8_initfile(filename); + config.newpdb ? pdb_initfile(filename) : cv8_initfile(filename); assert(objbuf.length() == 0); return obj; } @@ -374,7 +375,7 @@ void MsCoffObj_initfile(const(char)* filename, const(char)* csegname, const(char { //dbg_printf("MsCoffObj_initfile(filename = %s, modname = %s)\n",filename,modname); if (config.fulltypes) - cv8_initmodule(filename, modname); + config.newpdb ? pdb_initmodule(filename, modname) : cv8_initmodule(filename, modname); } /************************************ @@ -615,7 +616,7 @@ void MsCoffObj_termfile() //dbg_printf("MsCoffObj_termfile\n"); if (config.addlinenumbers) { - cv8_termmodule(); + config.newpdb ? pdb_termmodule() : cv8_termmodule(); } } @@ -635,7 +636,7 @@ void MsCoffObj_term(const(char)[] objfilename) if (config.addlinenumbers) { - cv8_termfile(objfilename); + config.newpdb ? pdb_termfile(objfilename) : cv8_termfile(objfilename); } // To allow tooling support for most output files @@ -1007,7 +1008,7 @@ void MsCoffObj_linnum(Srcpos srcpos, int seg, targ_size_t offset) if (srcpos.Slinnum == 0 || !srcpos.Sfilename) return; - cv8_linnum(srcpos, cast(uint)offset); + config.newpdb ? pdb_linnum(srcpos, cast(uint)offset) : cv8_linnum(srcpos, cast(uint)offset); } @@ -1845,7 +1846,7 @@ void MsCoffObj_func_start(Symbol* sfunc) sfunc.Soffset = Offset(cseg); if (config.fulltypes) - cv8_func_start(sfunc); + config.newpdb ? pdb_func_start(sfunc) : cv8_func_start(sfunc); } /******************************* @@ -1859,7 +1860,7 @@ void MsCoffObj_func_term(Symbol* sfunc) // sfunc.Sident.ptr, sfunc.Soffset,Offset(cseg),sfunc.Sxtrnnum); if (config.fulltypes) - cv8_func_term(sfunc); + config.newpdb ? pdb_func_term(sfunc) : cv8_func_term(sfunc); } /******************************** diff --git a/compiler/src/dmd/backend/pdb.d b/compiler/src/dmd/backend/pdb.d new file mode 100644 index 000000000000..ffac0e5d7e51 --- /dev/null +++ b/compiler/src/dmd/backend/pdb.d @@ -0,0 +1,920 @@ +/** + * Modern CodeView / PDB symbolic debug info generation. + * + * This module is a self-contained emitter for the `.debug$S` and `.debug$T` + * sections for Win64 MS-COFF objects. It is an alternative to cv8.d intended to + * eventually retire cv4.d/cv8.d. It targets the record set documented by LLVM + * (https://llvm.org/docs/PDB/) and Microsoft + * (https://github.com/microsoft/microsoft-pdb) and is enabled by the + * `-preview=newpdb` compiler flag. + * + * It does not modify the existing CV4/CV8 paths; the type-record pool helpers in + * cgcv.d are reused read-only. + * + * Compiler implementation of the + * $(LINK2 https://www.dlang.org, D programming language). + * + * Copyright: Copyright (C) 2026 by The D Language Foundation, All Rights Reserved + * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/backend/pdb.d, backend/pdb.d) + */ + +module dmd.backend.pdb; + +import core.stdc.stdio; +import core.stdc.stdlib; +import core.stdc.string; +extern (C) nothrow char* getcwd(char*, size_t); + +import dmd.backend.cc; +import dmd.backend.cdef; +import dmd.backend.cgcv; +import dmd.backend.code; +import dmd.backend.x86.code_x86; +import dmd.backend.cv4; +import dmd.backend.mem; +import dmd.backend.el; +import dmd.backend.mscoffobj; +import dmd.backend.obj; +import dmd.backend.oper; +import dmd.common.outbuffer; +import dmd.backend.rtlsym; +import dmd.backend.symbol : globsym; +import dmd.backend.ty; +import dmd.backend.type; +import dmd.backend.dvarstats; + +nothrow: +@safe: + +/* ======================================================================== */ +/* CodeView constants not present in cv4.d, kept local to avoid touching */ +/* the legacy CV4/CV8 implementations. */ +/* ======================================================================== */ + +// Symbol records (DEBUG_S_SYMBOLS subsection content) +enum +{ + S_END_PDB = 0x0006, + S_OBJNAME_V3 = 0x1101, + S_COMPILE3 = 0x113C, + S_ENVBLOCK = 0x113D, + S_BUILDINFO = 0x114C, + S_FRAMEPROC = 0x1012, + S_GPROC32_ID = 0x1147, + S_LPROC32_ID = 0x1146, + S_PROC_ID_END = 0x114F, + S_GPROC32 = 0x1110, + S_LPROC32 = 0x110F, + S_REGREL32 = 0x1111, + S_BPREL32_V3 = 0x110B, + S_REGISTER_V3 = 0x1106, + S_LDATA32 = 0x110C, + S_GDATA32 = 0x110D, + S_LTHREAD32 = 0x1112, + S_GTHREAD32 = 0x1113, + S_PUB32 = 0x110E, + S_CONSTANT_V3 = 0x1107, + S_UDT_V3_ = 0x1108, + S_LABEL32_V3 = 0x1105, + S_THUNK32 = 0x1102, + S_LOCAL = 0x113E, + S_BLOCK32 = 0x1103, + S_RETURN = 0x000D, +} + +// Type leaf records used by the ID stream / source lines +enum +{ + LF_FUNC_ID = 0x1601, + LF_STRING_ID = 0x1605, + LF_BUILDINFO = 0x1603, + LF_UDT_SRC_LINE = 0x1606, + LF_SUBSTR_LIST = 0x1604, + LF_ALIAS = 0x150A, +} + +// .debug$S subsection kinds +enum +{ + DEBUG_S_SYMBOLS = 0xF1, + DEBUG_S_LINES = 0xF2, + DEBUG_S_STRINGTABLE = 0xF3, + DEBUG_S_FILECHKSMS = 0xF4, +} + +// File checksum kinds +enum +{ + CHKSUM_NONE = 0, + CHKSUM_MD5 = 1, + CHKSUM_SHA1 = 2, + CHKSUM_SHA256 = 3, +} + +// keep symbol records well under linker limits +enum PDB_MAX_SYMBOL_LENGTH = 0xffd8; + +// CV_PUBSYMFLAGS +enum PUB_FUNCTION = 0x00000002; + +/* ======================================================================== */ +/* State */ +/* ======================================================================== */ + +private __gshared OutBuffer* F1_buf; // symbols +private __gshared OutBuffer* F2_buf; // line numbers +private __gshared OutBuffer* F3_buf; // string table of source file names +private __gshared OutBuffer* F4_buf; // file checksums + +struct F1_Fixups +{ + Symbol* s; + uint offset; + uint value; +} + +private __gshared OutBuffer* F1fixup; + +struct FuncData +{ + Symbol* sfunc; + uint section_length; + const(char)* srcfilename; + uint srcfileoff; + uint linepairstart; + uint linepairbytes; + uint linepairsegment; + OutBuffer* f1buf; + OutBuffer* f1fixup; +} + +__gshared FuncData currentfuncdata; + +private __gshared OutBuffer* funcdata; // array of FuncData +private __gshared OutBuffer* linepair; // offset/line pairs + +// Determine if this Symbol lives in a COMDAT +@trusted +private bool symbol_iscomdat_pdb(Symbol* s) +{ + return s.Sclass == SC.comdat || + config.flags2 & CFG2comdat && s.Sclass == SC.inline || + config.flags4 & CFG4allcomdat && s.Sclass == SC.global; +} + +/* ======================================================================== */ +/* Name encoding */ +/* ======================================================================== */ + +private @trusted +void pdb_writename(OutBuffer* buf, const(char)* name, size_t len) +{ + if (config.flags2 & CFG2gms) + { + const(char)* start = name; + const(char)* cur = strchr(start, '.'); + const(char)* end = start + len; + while (cur != null) + { + if (cur >= end) + { + buf.writen(start, end - start); + return; + } + buf.writen(start, cur - start); + buf.writeByte('@'); + start = cur + 1; + if (start >= end) + return; + cur = strchr(start, '.'); + } + buf.writen(start, end - start); + } + else + buf.writen(name, len); +} + +/* ======================================================================== */ +/* File lifecycle */ +/* ======================================================================== */ + +@trusted +void pdb_initfile(const(char)* filename) +{ + if (!F1_buf) + { + __gshared OutBuffer f1buf; + f1buf.reserve(1024); + F1_buf = &f1buf; + } + F1_buf.reset(); + + if (!F1fixup) + { + __gshared OutBuffer f1fixupbuf; + f1fixupbuf.reserve(1024); + F1fixup = &f1fixupbuf; + } + F1fixup.reset(); + + if (!F2_buf) + { + __gshared OutBuffer f2buf; + f2buf.reserve(1024); + F2_buf = &f2buf; + } + F2_buf.reset(); + + if (!F3_buf) + { + __gshared OutBuffer f3buf; + f3buf.reserve(1024); + F3_buf = &f3buf; + } + F3_buf.reset(); + F3_buf.writeByte(0); // first "filename" + + if (!F4_buf) + { + __gshared OutBuffer f4buf; + f4buf.reserve(1024); + F4_buf = &f4buf; + } + F4_buf.reset(); + + if (!funcdata) + { + __gshared OutBuffer funcdatabuf; + funcdatabuf.reserve(1024); + funcdata = &funcdatabuf; + } + funcdata.reset(); + + if (!linepair) + { + __gshared OutBuffer linepairbuf; + linepairbuf.reserve(1024); + linepair = &linepairbuf; + } + linepair.reset(); + + memset(¤tfuncdata, 0, currentfuncdata.sizeof); + currentfuncdata.f1buf = F1_buf; + currentfuncdata.f1fixup = F1fixup; + + cv_init(); +} + +void pdb_initmodule(const(char)* filename, const(char)* modulename) +{ +} + +@trusted +void pdb_termmodule() +{ + assert(config.objfmt == OBJ_MSCOFF); +} + +@trusted +void pdb_termfile(const(char)[] objfilename) +{ + int seg = MsCoffObj_seg_debugS(); + + uint value = 4; // CV signature C13 + objmod.bytes(seg, 0, (cast(void*)&value)[0 .. 4]); + + auto buf = OutBuffer(1024); + + // S_OBJNAME + { + size_t len = objfilename.length; + buf.write16(cast(int)(2 + 4 + len + 1)); + buf.write16(S_OBJNAME_V3); + buf.write32(0); // signature + buf.write(objfilename.ptr, cast(uint)(len + 1)); + } + + // S_COMPILE3 + { + const ver = "DMD "; + size_t vlen = VERSION.length; + buf.write16(cast(int)(2 + 4 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + ver.length + vlen + 1)); + buf.write16(S_COMPILE3); + buf.write32(0x00010044); // flags: language=D(0x44)<<8? language byte=0x44, rest 0 + buf.write16(I64 ? 0xD0 : 0x07); // machine: AMD64 / x86 + buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // FE ver + buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // BE ver + buf.write(ver.ptr, cast(uint)ver.length); + buf.write(VERSION.ptr, cast(uint)vlen); + buf.writeByte(0); + } + + pdb_writesection(seg, DEBUG_S_SYMBOLS, &buf); + + // S_ENVBLOCK: build environment (cwd, tool), double-null terminated + { + auto ebuf = OutBuffer(128); + char[260] cwd = 0; + if (!getcwd(cwd.ptr, cwd.sizeof)) + cwd[0] = 0; + size_t cwdlen = strlen(cwd.ptr); + uint rec = cast(uint)(2 + 1 + 4 + cwdlen + 1 + 4 + 1 + 1); + ebuf.write16(cast(int)rec); + ebuf.write16(S_ENVBLOCK); + ebuf.writeByte(0); // flags + ebuf.write("cwd\0".ptr, 4); + ebuf.write(cwd.ptr, cast(uint)(cwdlen + 1)); + ebuf.write("cmd\0".ptr, 4); + ebuf.writeByte(0); // empty cmd value + ebuf.writeByte(0); // terminating double-null + pdb_writesection(seg, DEBUG_S_SYMBOLS, &ebuf); + } + + // S_BUILDINFO referencing an LF_BUILDINFO id record + { + auto bbuf = OutBuffer(64); + idx_t biidx = pdb_buildinfo(); + bbuf.write16(2 + 4); + bbuf.write16(S_BUILDINFO); + bbuf.write32(biidx); + pdb_writesection(seg, DEBUG_S_SYMBOLS, &bbuf); + } + + uint length = cast(uint)funcdata.length(); + ubyte* p = funcdata.buf; + for (uint u = 0; u < length; u += FuncData.sizeof) + { + FuncData* fd = cast(FuncData*)(p + u); + + F2_buf.reset(); + F2_buf.write32(cast(uint)fd.sfunc.Soffset); + F2_buf.write32(0); + F2_buf.write32(fd.section_length); + F2_buf.write(linepair.buf + fd.linepairstart, fd.linepairbytes); + + int f2seg = seg; + if (symbol_iscomdat_pdb(fd.sfunc)) + { + f2seg = MsCoffObj_seg_debugS_comdat(fd.sfunc); + objmod.bytes(f2seg, 0, (cast(void*)&value)[0 .. 4]); + } + + uint offset = cast(uint)SegData[f2seg].SDoffset + 8; + pdb_writesection(f2seg, DEBUG_S_LINES, F2_buf); + objmod.reftoident(f2seg, offset, fd.sfunc, 0, CF.seg | CF.off); + + if (f2seg != seg && fd.f1buf.length()) + { + const uint f1offset = cast(uint)SegData[f2seg].SDoffset; + pdb_writesection(f2seg, DEBUG_S_SYMBOLS, fd.f1buf); + + const uint fixupLength = cast(uint)fd.f1fixup.length(); + ubyte* pfixup = fd.f1fixup.buf; + for (uint v = 0; v < fixupLength; v += F1_Fixups.sizeof) + { + F1_Fixups* f = cast(F1_Fixups*)(pfixup + v); + objmod.reftoident(f2seg, f1offset + 8 + f.offset, f.s, f.value, CF.seg | CF.off); + } + } + } + + if (F3_buf.length() > 1) + pdb_writesection(seg, DEBUG_S_STRINGTABLE, F3_buf); + + if (F4_buf.length() > 0) + pdb_writesection(seg, DEBUG_S_FILECHKSMS, F4_buf); + + if (F1_buf.length()) + { + uint f1offset = cast(uint)SegData[seg].SDoffset; + pdb_writesection(seg, DEBUG_S_SYMBOLS, F1_buf); + + length = cast(uint)F1fixup.length(); + p = F1fixup.buf; + for (uint u = 0; u < length; u += F1_Fixups.sizeof) + { + F1_Fixups* f = cast(F1_Fixups*)(p + u); + objmod.reftoident(seg, f1offset + 8 + f.offset, f.s, f.value, CF.seg | CF.off); + } + } + + cv_term(); // .debug$T +} + +/* ======================================================================== */ +/* Functions */ +/* ======================================================================== */ + +@trusted +void pdb_func_start(Symbol* sfunc) +{ + currentfuncdata.sfunc = sfunc; + currentfuncdata.section_length = 0; + currentfuncdata.srcfilename = null; + currentfuncdata.linepairstart += currentfuncdata.linepairbytes; + currentfuncdata.linepairbytes = 0; + currentfuncdata.f1buf = F1_buf; + currentfuncdata.f1fixup = F1fixup; + if (symbol_iscomdat_pdb(sfunc)) + { + currentfuncdata.f1buf = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); + currentfuncdata.f1buf.reserve(128); + currentfuncdata.f1fixup = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); + currentfuncdata.f1fixup.reserve(128); + } + varStats_startFunction(); +} + +@trusted +void pdb_func_term(Symbol* sfunc) +{ + assert(currentfuncdata.sfunc == sfunc); + currentfuncdata.section_length = cast(uint)sfunc.Ssize; + funcdata.write(¤tfuncdata, currentfuncdata.sizeof); + + assert(tyfunc(sfunc.ty())); + idx_t typidx = cv_typidx(sfunc.Stype); + + const(char)* id = sfunc.prettyIdent ? sfunc.prettyIdent : prettyident(sfunc); + size_t len = strlen(id); + if (len > PDB_MAX_SYMBOL_LENGTH) + len = PDB_MAX_SYMBOL_LENGTH; + + auto buf = currentfuncdata.f1buf; + buf.reserve(cast(uint)(2 + 2 + 4 * 7 + 6 + 1 + len + 1)); + buf.write16n(cast(int)(2 + 4 * 7 + 6 + 1 + len + 1)); + buf.write16n(sfunc.Sclass == SC.static_ ? S_LPROC32 : S_GPROC32); + buf.write32(0); // parent + buf.write32(0); // pend + buf.write32(0); // pnext + buf.write32(cast(uint)currentfuncdata.section_length); + buf.write32(cast(uint)cgstate.startoffset); // prolog size + buf.write32(cast(uint)cgstate.retoffset); // epilog offset + buf.write32(typidx); + + F1_Fixups f1f; + f1f.s = sfunc; + f1f.offset = cast(uint)buf.length(); + f1f.value = 0; + currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); + buf.write32(0); + buf.write16n(0); + buf.writeByte(0); // flags + buf.writen(id, len); + buf.writeByte(0); + + // S_FRAMEPROC + { + buf.write16n(2 + 4 + 4 + 4 + 4 + 4 + 4); + buf.write16n(S_FRAMEPROC); + buf.write32(cast(uint)cgstate.Auto.size); // frame bytes + buf.write32(0); // pad + buf.write32(0); // pad offset + buf.write32(0); // SE callee saved + buf.write32(0); // exception handler off + // flags: encode RBP (2) as local & param base pointer for x64 + buf.write32(I64 ? (2 << 14) | (2 << 16) : 0); + } + + struct pdbblk + { + nothrow: + struct block_v3_data + { + ushort len; + ushort id; + uint pParent; + uint pEnd; + uint length; + uint offset; + ushort seg; + ubyte[1] name; + } + static void endArgs() + { + auto b = currentfuncdata.f1buf; + b.write16(2); + b.write16(0x000A); // S_ENDARG + } + static void beginBlock(int offset, int length) + { + auto b = currentfuncdata.f1buf; + uint soffset = cast(uint)b.length(); + block_v3_data block32 = { block_v3_data.sizeof - 2, S_BLOCK32, 0, 0, length, offset, 0, [ 0 ] }; + b.write(&block32, block32.sizeof); + size_t offOffset = cast(char*)&block32.offset - cast(char*)&block32; + F1_Fixups f; + f.s = currentfuncdata.sfunc; + f.offset = cast(uint)(soffset + offOffset); + f.value = offset; + currentfuncdata.f1fixup.write(&f, f.sizeof); + } + static void endBlock() + { + auto b = currentfuncdata.f1buf; + b.write16(2); + b.write16(S_END_PDB); + } + } + varStats_writeSymbolTable(sfunc, globsym, &pdb_outsym, &pdbblk.endArgs, &pdbblk.beginBlock, &pdbblk.endBlock); + + // S_RETURN to mark the epilogue for "step out" + buf.write16(5); + buf.write16(S_RETURN); + buf.write16(0); // CV_GENERIC_FLAG + buf.writeByte(0); // CV_GENERIC_STYLE: void + + buf.write16(2); + buf.write16(S_END_PDB); + + currentfuncdata.f1buf = F1_buf; + currentfuncdata.f1fixup = F1fixup; +} + +/* ======================================================================== */ +/* Line numbers */ +/* ======================================================================== */ + +@trusted +void pdb_linnum(Srcpos srcpos, uint offset) +{ + const sfilename = srcpos.Sfilename; + if (!sfilename) + return; + + varStats_recordLineOffset(srcpos, offset); + + __gshared uint lastoffset; + __gshared uint lastlinnum; + + if (!currentfuncdata.srcfilename || + (currentfuncdata.srcfilename != sfilename && strcmp(currentfuncdata.srcfilename, sfilename))) + { + currentfuncdata.srcfilename = sfilename; + uint srcfileoff = pdb_addfile(sfilename); + currentfuncdata.linepairsegment = currentfuncdata.linepairstart + currentfuncdata.linepairbytes; + linepair.write32(srcfileoff); + linepair.write32(0); + linepair.write32(12); + currentfuncdata.linepairbytes += 12; + } + else if (offset <= lastoffset || srcpos.Slinnum == lastlinnum) + return; + + lastoffset = offset; + lastlinnum = srcpos.Slinnum; + linepair.write32(offset); + linepair.write32(srcpos.Slinnum | 0x80000000); + currentfuncdata.linepairbytes += 8; + + auto segmentbytes = currentfuncdata.linepairstart + currentfuncdata.linepairbytes - currentfuncdata.linepairsegment; + auto segmentheader = cast(uint*)(linepair.buf + currentfuncdata.linepairsegment); + segmentheader[1] = (segmentbytes - 12) / 8; + segmentheader[2] = segmentbytes; +} + +/* ======================================================================== */ +/* Source files (F3 names + F4 checksums via blake3) */ +/* ======================================================================== */ + +@trusted +uint pdb_addfile(const(char)* filename) +{ + uint length = cast(uint)F3_buf.length(); + ubyte* p = F3_buf.buf; + size_t len = strlen(filename); + + __gshared char[260] cwd = 0; + __gshared uint cwdlen; + bool abs = (*filename == '\\') || (*filename == '/') || + (*filename && filename[1] == ':'); + + if (!abs && cwd[0] == 0) + { + if (getcwd(cwd.ptr, cwd.sizeof)) + { + cwdlen = cast(uint)strlen(cwd.ptr); + if (cwd[cwdlen - 1] != '\\' && cwd[cwdlen - 1] != '/') + cwd[cwdlen++] = '\\'; + } + } + uint off = 1; + while (off + len < length) + { + if (!abs) + { + if (memcmp(p + off, cwd.ptr, cwdlen) == 0 && + memcmp(p + off + cwdlen, filename, len + 1) == 0) + goto L1; + } + else if (memcmp(p + off, filename, len + 1) == 0) + goto L1; + off += strlen(cast(const(char)*)(p + off)) + 1; + } + off = length; + if (!abs) + F3_buf.write(cwd.ptr, cwdlen); + F3_buf.write(filename, cast(uint)(len + 1)); + +L1: + length = cast(uint)F4_buf.length(); + p = F4_buf.buf; + uint u = 0; + while (u + 8 <= length) + { + if (off == *cast(uint*)(p + u)) + return u; + u += 4; + ubyte cklen = *(p + u); + u += 4; + u += cklen; + u = (u + 3) & ~3; + } + + // not present; append a full SHA256-slot checksum from blake3 (32 bytes) + F4_buf.write32(off); + import dmd.common.blake3; + const hash = blake3((cast(ubyte*)filename)[0 .. len]); + F4_buf.writeByte(32); // checksum size + F4_buf.writeByte(CHKSUM_SHA256); + F4_buf.write16(0); + F4_buf.write(hash.ptr, 32); + while (F4_buf.length() & 3) + F4_buf.writeByte(0); + return length; +} + +private @trusted +void pdb_writesection(int seg, uint type, OutBuffer* buf) +{ + uint off = cast(uint)SegData[seg].SDoffset; + objmod.bytes(seg, off, (cast(void*)&type)[0 .. 4]); + uint length = cast(uint)buf.length(); + objmod.bytes(seg, off + 4, (cast(void*)&length)[0 .. 4]); + objmod.bytes(seg, off + 8, buf.buf[0 .. length]); + uint pad = ((length + 3) & ~3) - length; + objmod.lidata(seg, off + 8 + length, pad); +} + +/* ======================================================================== */ +/* Symbols */ +/* ======================================================================== */ + +@trusted +void pdb_outsym(Symbol* s) +{ + if (s.Sflags & SFLnodebug) + return; + + idx_t typidx = cv_typidx(s.Stype); + const(char)* id = s.prettyIdent ? s.prettyIdent : prettyident(s); + size_t len = strlen(id); + if (len > PDB_MAX_SYMBOL_LENGTH) + len = PDB_MAX_SYMBOL_LENGTH; + + F1_Fixups f1f; + f1f.value = 0; + auto buf = currentfuncdata.f1buf; + + uint sr; + uint base; + switch (s.Sclass) + { + case SC.parameter: + case SC.regpar: + case SC.shadowreg: + if (s.Sfl == FL.reg) + { + s.Sfl = FL.para; + pdb_outsym(s); + s.Sfl = FL.reg; + goto case_register; + } + base = cast(uint)(cgstate.Para.size - cgstate.BPoff); + goto L1; + + case SC.auto_: + if (s.Sfl == FL.reg) + goto case_register; + case_auto: + base = cast(uint)cgstate.Auto.size; + L1: + if (s.Sscope) + break; + buf.reserve(cast(uint)(2 + 2 + 4 + 4 + 2 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + 4 + 2 + len + 1)); + buf.write16n(S_REGREL32); + buf.write32(cast(uint)(s.Soffset + base + cgstate.BPoff)); + buf.write32(typidx); + buf.write16n(I64 ? 334 : 22); + pdb_writename(buf, id, len); + buf.writeByte(0); + break; + + case SC.bprel: + base = -cgstate.BPoff; + goto L1; + + case SC.fastpar: + if (s.Sfl != FL.reg) + { + base = cast(uint)cgstate.Fast.size; + goto L1; + } + goto L2; + + case SC.register: + if (s.Sfl != FL.reg) + goto case_auto; + goto case; + + case SC.pseudo: + case_register: + L2: + buf.reserve(cast(uint)(2 + 2 + 4 + 2 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + 2 + len + 1)); + buf.write16n(S_REGISTER_V3); + buf.write32(typidx); + buf.write16n(pdb_regnum(s)); + pdb_writename(buf, id, len); + buf.writeByte(0); + break; + + case SC.extern_: + break; + + case SC.typedef_: + // LF_ALIAS: a named typedef of an existing type + pdb_alias(id, typidx); + break; + + case SC.const_: + // enum members and manifest constants + { + if (!s.Svalue) + break; + uint val = cast(uint)el_tolong(s.Svalue); + buf.reserve(cast(uint)(2 + 2 + 4 + 2 + 4 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + 2 + 4 + len + 1)); + buf.write16n(S_CONSTANT_V3); + buf.write32(typidx); + buf.write16n(0x8004); // LF_ULONG numeric leaf + buf.write32(val); + pdb_writename(buf, id, len); + buf.writeByte(0); + } + break; + + case SC.static_: + case SC.locstat: + sr = S_LDATA32; + goto Ldata; + + case SC.global: + case SC.comdat: + case SC.comdef: + sr = S_GDATA32; + Ldata: + if (s.ty() & mTYthread) // full TLS support + sr = (sr == S_GDATA32) ? S_GTHREAD32 : S_LTHREAD32; + + buf.reserve(cast(uint)(2 + 2 + 4 + 6 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + 6 + len + 1)); + buf.write16n(sr); + buf.write32(typidx); + f1f.s = s; + f1f.offset = cast(uint)buf.length(); + currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); + buf.write32(0); + buf.write16n(0); + pdb_writename(buf, id, len); + buf.writeByte(0); + break; + + default: + break; + } +} + +/* Put out a name for a user defined type. */ +@trusted +void pdb_udt(const(char)* id, idx_t typidx) +{ + auto buf = currentfuncdata.f1buf; + size_t len = strlen(id); + if (len > PDB_MAX_SYMBOL_LENGTH) + len = PDB_MAX_SYMBOL_LENGTH; + buf.reserve(cast(uint)(2 + 2 + 4 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + len + 1)); + buf.write16n(S_UDT_V3_); + buf.write32(typidx); + pdb_writename(buf, id, len); + buf.writeByte(0); +} + +/* ======================================================================== */ +/* ID-stream type records (reuse the shared cgcv type pool, read-only) */ +/* ======================================================================== */ + +/* LF_STRING_ID: an interned string returning a type index. */ +@trusted +idx_t pdb_string_id(const(char)* s) +{ + if (!s) s = ""; + debtyp_t* d = debtyp_alloc(2 + 4 + cv_stringbytes(s)); + TOWORD(d.data.ptr, LF_STRING_ID); + TOLONG(d.data.ptr + 2, 0); // substring list id + cv_namestring(d.data.ptr + 6, s); + return cv_debtyp(d); +} + +/* LF_BUILDINFO: cwd, build tool, source, pdb, args. */ +@trusted +idx_t pdb_buildinfo() +{ + char[260] cwd = 0; + if (!getcwd(cwd.ptr, cwd.sizeof)) + cwd[0] = 0; + idx_t cwdId = pdb_string_id(cwd.ptr); + idx_t toolId = pdb_string_id("dmd"); + idx_t srcId = pdb_string_id(""); + idx_t pdbId = pdb_string_id(""); + idx_t argId = pdb_string_id(""); + debtyp_t* d = debtyp_alloc(2 + 2 + 5 * 4); + TOWORD(d.data.ptr, LF_BUILDINFO); + TOWORD(d.data.ptr + 2, 5); // count + TOLONG(d.data.ptr + 4, cwdId); + TOLONG(d.data.ptr + 8, toolId); + TOLONG(d.data.ptr + 12, srcId); + TOLONG(d.data.ptr + 16, pdbId); + TOLONG(d.data.ptr + 20, argId); + return cv_debtyp(d); +} + +/* LF_FUNC_ID: ties a function type to its name for the ID stream. */ +@trusted +idx_t pdb_func_id(const(char)* id, idx_t functype) +{ + debtyp_t* d = debtyp_alloc(2 + 4 + 4 + cv_stringbytes(id)); + TOWORD(d.data.ptr, LF_FUNC_ID); + TOLONG(d.data.ptr + 2, 0); // parent scope id + TOLONG(d.data.ptr + 6, functype); + cv_namestring(d.data.ptr + 10, id); + return cv_debtyp(d); +} + +/* LF_ALIAS: a named typedef of an underlying type. */ +@trusted +idx_t pdb_alias(const(char)* id, idx_t utype) +{ + debtyp_t* d = debtyp_alloc(2 + 4 + cv_stringbytes(id)); + TOWORD(d.data.ptr, LF_ALIAS); + TOLONG(d.data.ptr + 2, utype); + cv_namestring(d.data.ptr + 6, id); + return cv_debtyp(d); +} + +/* LF_UDT_SRC_LINE: source location of a user-defined type. */ +@trusted +idx_t pdb_udt_src_line(idx_t typidx, idx_t srcId, uint line) +{ + debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4); + TOWORD(d.data.ptr, LF_UDT_SRC_LINE); + TOLONG(d.data.ptr + 2, typidx); + TOLONG(d.data.ptr + 6, srcId); + TOLONG(d.data.ptr + 10, line); + return cv_debtyp(d); +} + +/* Codeview register number for symbol s. */ +int pdb_regnum(Symbol* s) +{ + int reg = s.Sreglsw; + assert(s.Sfl == FL.reg); + if ((1 << reg) & XMMREGS) + return reg - XMM0 + 154; + switch (type_size(s.Stype)) + { + case 1: + if (reg < 4) reg += 1; + else if (reg < 8) reg += 324 - 4; + else reg += 344 - 4; + break; + case 2: + if (reg < 8) reg += 9; + else reg += 352 - 8; + break; + case 4: + if (reg < 8) reg += 17; + else reg += 360 - 8; + break; + case 8: + reg += 328; + break; + default: + reg = 0; + break; + } + return reg; +} diff --git a/compiler/src/dmd/cli.d b/compiler/src/dmd/cli.d index f19b3d227776..c0882b599b7c 100644 --- a/compiler/src/dmd/cli.d +++ b/compiler/src/dmd/cli.d @@ -1120,6 +1120,8 @@ dmd -cov -unittest myprog.d "https://dlang.org/spec/attribute.html#system-variables"), Feature("fastdfa", "useFastDFA", "Fast dataflow analysis engine, experimental"), + Feature("newpdb", "newpdb", + "use the modern PDB/CodeView debug info emitter (Windows MSCOFF only)"), ]; } diff --git a/compiler/src/dmd/dmsc.d b/compiler/src/dmd/dmsc.d index 3f8b4b0a9913..143ce608f474 100644 --- a/compiler/src/dmd/dmsc.d +++ b/compiler/src/dmd/dmsc.d @@ -95,6 +95,7 @@ void backend_init(const ref Param params, const ref DMDparams driverParams, cons exfmt, params.addMain, driverParams.symImport != SymImport.none, + params.newpdb, go, // FIXME: casting to @nogc because errors.d is not marked @nogc yet cast(ErrorCallbackBackend) &errorBackend, diff --git a/compiler/src/dmd/globals.d b/compiler/src/dmd/globals.d index 0c4f9eb5417c..fba780d4c4d4 100644 --- a/compiler/src/dmd/globals.d +++ b/compiler/src/dmd/globals.d @@ -216,6 +216,7 @@ extern (C++) struct Param // https://issues.dlang.org/show_bug.cgi?id=14246 FeatureState systemVariables; // limit access to variables marked @system from @safe code bool useFastDFA; // Use fast data flow analysis engine + bool newpdb; // use the modern PDB/CodeView debug info emitter (Windows MSCOFF) CHECKENABLE useInvariants = CHECKENABLE._default; // generate class invariant checks CHECKENABLE useIn = CHECKENABLE._default; // generate precondition checks From 77d6b8d4869cb9df81a210e926d73bdb5ae8b3ed Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Mon, 29 Jun 2026 04:01:51 -0700 Subject: [PATCH 02/19] Fix incorrect record generations. --- compiler/src/dmd/backend/pdb.d | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dmd/backend/pdb.d b/compiler/src/dmd/backend/pdb.d index ffac0e5d7e51..27dd98032544 100644 --- a/compiler/src/dmd/backend/pdb.d +++ b/compiler/src/dmd/backend/pdb.d @@ -299,7 +299,7 @@ void pdb_termfile(const(char)[] objfilename) { const ver = "DMD "; size_t vlen = VERSION.length; - buf.write16(cast(int)(2 + 4 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + ver.length + vlen + 1)); + buf.write16(cast(int)(2 + 4 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + ver.length + vlen + 1)); buf.write16(S_COMPILE3); buf.write32(0x00010044); // flags: language=D(0x44)<<8? language byte=0x44, rest 0 buf.write16(I64 ? 0xD0 : 0x07); // machine: AMD64 / x86 @@ -466,8 +466,8 @@ void pdb_func_term(Symbol* sfunc) // S_FRAMEPROC { - buf.write16n(2 + 4 + 4 + 4 + 4 + 4 + 4); - buf.write16n(S_FRAMEPROC); + buf.write16(2 + 4 + 4 + 4 + 4 + 4 + 4); + buf.write16(S_FRAMEPROC); buf.write32(cast(uint)cgstate.Auto.size); // frame bytes buf.write32(0); // pad buf.write32(0); // pad offset From bc6952a2821e6e6c682d0f447ebc0cb92e164ae7 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Mon, 29 Jun 2026 16:54:52 -0700 Subject: [PATCH 03/19] Add changelog entry. --- changelog/dmd.newpdb.dd | 9 +++++++++ compiler/src/build.d | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 changelog/dmd.newpdb.dd diff --git a/changelog/dmd.newpdb.dd b/changelog/dmd.newpdb.dd new file mode 100644 index 000000000000..1e8d54568bdb --- /dev/null +++ b/changelog/dmd.newpdb.dd @@ -0,0 +1,9 @@ +Added a new Modern CodeView / PDB symbolic debug info emitter + +A new PDB (Program Database) emitter has been added to improve debug information generation on Windows. Creating modern CodeView/PDB symbolic debug info enables more robust and feature-rich debugging experiences with Microsoft development tools. + +You can activate the new PDB emitter by passing the `-preview=newpdb` flag to the compiler. + +```console +dmd -g -preview=newpdb myapp.d +``` diff --git a/compiler/src/build.d b/compiler/src/build.d index 1e4a915e58c8..fee1fdb717b8 100755 --- a/compiler/src/build.d +++ b/compiler/src/build.d @@ -2341,4 +2341,3 @@ void copyAndTouch(const string from, const string to) // Wrap standard library functions alias writeText = std.file.write; - From 6bd451ea65be9f09a6b0a75637cbb15cec45163a Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Mon, 29 Jun 2026 17:28:12 -0700 Subject: [PATCH 04/19] Remove reference to MSCOFF. --- compiler/src/dmd/cli.d | 2 +- compiler/src/dmd/globals.d | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dmd/cli.d b/compiler/src/dmd/cli.d index c0882b599b7c..52e9b64ba2a9 100644 --- a/compiler/src/dmd/cli.d +++ b/compiler/src/dmd/cli.d @@ -1121,7 +1121,7 @@ dmd -cov -unittest myprog.d Feature("fastdfa", "useFastDFA", "Fast dataflow analysis engine, experimental"), Feature("newpdb", "newpdb", - "use the modern PDB/CodeView debug info emitter (Windows MSCOFF only)"), + "use the modern PDB/CodeView debug info emitter"), ]; } diff --git a/compiler/src/dmd/globals.d b/compiler/src/dmd/globals.d index fba780d4c4d4..b94f1a35cea9 100644 --- a/compiler/src/dmd/globals.d +++ b/compiler/src/dmd/globals.d @@ -216,7 +216,7 @@ extern (C++) struct Param // https://issues.dlang.org/show_bug.cgi?id=14246 FeatureState systemVariables; // limit access to variables marked @system from @safe code bool useFastDFA; // Use fast data flow analysis engine - bool newpdb; // use the modern PDB/CodeView debug info emitter (Windows MSCOFF) + bool newpdb; // use the modern PDB/CodeView debug info emitter CHECKENABLE useInvariants = CHECKENABLE._default; // generate class invariant checks CHECKENABLE useIn = CHECKENABLE._default; // generate precondition checks From 28b1bb3064e7090b9e56292fe48fe7b1349d04c6 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Mon, 29 Jun 2026 17:47:34 -0700 Subject: [PATCH 05/19] Added initBuf local method to simplify code. Invert if to reduce nesting. --- compiler/src/dmd/backend/pdb.d | 119 ++++++++++++--------------------- 1 file changed, 42 insertions(+), 77 deletions(-) diff --git a/compiler/src/dmd/backend/pdb.d b/compiler/src/dmd/backend/pdb.d index 27dd98032544..f97ab74217ce 100644 --- a/compiler/src/dmd/backend/pdb.d +++ b/compiler/src/dmd/backend/pdb.d @@ -119,7 +119,7 @@ enum PDB_MAX_SYMBOL_LENGTH = 0xffd8; enum PUB_FUNCTION = 0x00000002; /* ======================================================================== */ -/* State */ +/* State */ /* ======================================================================== */ private __gshared OutBuffer* F1_buf; // symbols @@ -164,100 +164,65 @@ private bool symbol_iscomdat_pdb(Symbol* s) } /* ======================================================================== */ -/* Name encoding */ +/* Name encoding */ /* ======================================================================== */ private @trusted void pdb_writename(OutBuffer* buf, const(char)* name, size_t len) { - if (config.flags2 & CFG2gms) + if (!(config.flags2 & CFG2gms)) { - const(char)* start = name; - const(char)* cur = strchr(start, '.'); - const(char)* end = start + len; - while (cur != null) + buf.writen(name, len); + return; + } + + const(char)* start = name; + const(char)* cur = strchr(start, '.'); + const(char)* end = start + len; + while (cur != null) + { + if (cur >= end) { - if (cur >= end) - { - buf.writen(start, end - start); - return; - } - buf.writen(start, cur - start); - buf.writeByte('@'); - start = cur + 1; - if (start >= end) - return; - cur = strchr(start, '.'); + buf.writen(start, end - start); + return; } - buf.writen(start, end - start); + buf.writen(start, cur - start); + buf.writeByte('@'); + start = cur + 1; + if (start >= end) + return; + cur = strchr(start, '.'); } - else - buf.writen(name, len); + buf.writen(start, end - start); } /* ======================================================================== */ -/* File lifecycle */ +/* File lifecycle */ /* ======================================================================== */ @trusted void pdb_initfile(const(char)* filename) { - if (!F1_buf) + void initBuf(ref OutBuffer* ptr) { - __gshared OutBuffer f1buf; - f1buf.reserve(1024); - F1_buf = &f1buf; + if (!ptr) + { + ptr = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); + ptr.reserve(1024); + } + ptr.reset(); } - F1_buf.reset(); - if (!F1fixup) - { - __gshared OutBuffer f1fixupbuf; - f1fixupbuf.reserve(1024); - F1fixup = &f1fixupbuf; - } - F1fixup.reset(); + initBuf(F1_buf); + initBuf(F1fixup); + initBuf(F2_buf); - if (!F2_buf) - { - __gshared OutBuffer f2buf; - f2buf.reserve(1024); - F2_buf = &f2buf; - } - F2_buf.reset(); - - if (!F3_buf) - { - __gshared OutBuffer f3buf; - f3buf.reserve(1024); - F3_buf = &f3buf; - } - F3_buf.reset(); + initBuf(F3_buf); F3_buf.writeByte(0); // first "filename" - if (!F4_buf) - { - __gshared OutBuffer f4buf; - f4buf.reserve(1024); - F4_buf = &f4buf; - } - F4_buf.reset(); - - if (!funcdata) - { - __gshared OutBuffer funcdatabuf; - funcdatabuf.reserve(1024); - funcdata = &funcdatabuf; - } - funcdata.reset(); - - if (!linepair) - { - __gshared OutBuffer linepairbuf; - linepairbuf.reserve(1024); - linepair = &linepairbuf; - } - linepair.reset(); + initBuf(F4_buf); + initBuf(funcdata); + initBuf(linepair); memset(¤tfuncdata, 0, currentfuncdata.sizeof); currentfuncdata.f1buf = F1_buf; @@ -403,7 +368,7 @@ void pdb_termfile(const(char)[] objfilename) } /* ======================================================================== */ -/* Functions */ +/* Functions */ /* ======================================================================== */ @trusted @@ -533,7 +498,7 @@ void pdb_func_term(Symbol* sfunc) } /* ======================================================================== */ -/* Line numbers */ +/* Line numbers */ /* ======================================================================== */ @trusted @@ -575,7 +540,7 @@ void pdb_linnum(Srcpos srcpos, uint offset) } /* ======================================================================== */ -/* Source files (F3 names + F4 checksums via blake3) */ +/* Source files (F3 names + F4 checksums via blake3) */ /* ======================================================================== */ @trusted @@ -658,7 +623,7 @@ void pdb_writesection(int seg, uint type, OutBuffer* buf) } /* ======================================================================== */ -/* Symbols */ +/* Symbols */ /* ======================================================================== */ @trusted @@ -814,7 +779,7 @@ void pdb_udt(const(char)* id, idx_t typidx) } /* ======================================================================== */ -/* ID-stream type records (reuse the shared cgcv type pool, read-only) */ +/* ID-stream type records (reuse the shared cgcv type pool, read-only) */ /* ======================================================================== */ /* LF_STRING_ID: an interned string returning a type index. */ From b31e9919ae93b0b06ce548a60f12c86a17a7b59b Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Mon, 29 Jun 2026 17:51:54 -0700 Subject: [PATCH 06/19] Fix trailing whitespace. --- changelog/dmd.newpdb.dd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/dmd.newpdb.dd b/changelog/dmd.newpdb.dd index 1e8d54568bdb..d1f75308fa2c 100644 --- a/changelog/dmd.newpdb.dd +++ b/changelog/dmd.newpdb.dd @@ -1,6 +1,6 @@ Added a new Modern CodeView / PDB symbolic debug info emitter -A new PDB (Program Database) emitter has been added to improve debug information generation on Windows. Creating modern CodeView/PDB symbolic debug info enables more robust and feature-rich debugging experiences with Microsoft development tools. +A new PDB (Program Database) emitter has been added to improve debug information generation on Windows. Creating modern CodeView/PDB symbolic debug info enables more robust and feature-rich debugging experiences with Microsoft development tools. You can activate the new PDB emitter by passing the `-preview=newpdb` flag to the compiler. From 6017d3ef22132c2ee94676617d15d7b0a825a8d6 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Tue, 30 Jun 2026 05:10:52 -0700 Subject: [PATCH 07/19] Update help test. --- compiler/test/compilable/previewhelp.d | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/test/compilable/previewhelp.d b/compiler/test/compilable/previewhelp.d index 94a01ced14bf..117049a56a73 100644 --- a/compiler/test/compilable/previewhelp.d +++ b/compiler/test/compilable/previewhelp.d @@ -20,5 +20,6 @@ Upcoming language changes listed by -preview=name: =fixImmutableConv disallow `void[]` data from holding immutable data (https://dlang.org/changelog/2.101.0.html#dmd.fix-immutable-conv, https://issues.dlang.org/show_bug.cgi?id=17148) =systemVariables disable access to variables marked '@system' from @safe code (https://dlang.org/spec/attribute.html#system-variables) =fastdfa Fast dataflow analysis engine, experimental + =newpdb use the modern PDB/CodeView debug info emitter ---- */ From 191523a0ba378ccf34e65329c5afc766bfbf00d4 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Tue, 30 Jun 2026 20:18:46 -0700 Subject: [PATCH 08/19] Update globals.h --- compiler/include/dmd/globals.h | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/include/dmd/globals.h b/compiler/include/dmd/globals.h index 84142e5d6774..bec8bbb7ced3 100644 --- a/compiler/include/dmd/globals.h +++ b/compiler/include/dmd/globals.h @@ -237,6 +237,7 @@ struct Param // https://issues.dlang.org/show_bug.cgi?id=14246 FeatureState systemVariables; // limit access to variables marked @system from @safe code d_bool useFastDFA; // Use fast data flow analysis engine + d_bool newpdb; // use the modern PDB/CodeView debug info emitter CHECKENABLE useInvariants; // generate class invariant checks CHECKENABLE useIn; // generate precondition checks From 6e93d2a1b15042878fb7c6b67273e37a69548777 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Tue, 30 Jun 2026 20:53:34 -0700 Subject: [PATCH 09/19] Enable -preview=newpdb on the testpdb.d test. --- compiler/test/runnable/testpdb.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/test/runnable/testpdb.d b/compiler/test/runnable/testpdb.d index a8c9706be508..7a2354855029 100644 --- a/compiler/test/runnable/testpdb.d +++ b/compiler/test/runnable/testpdb.d @@ -1,4 +1,4 @@ -// REQUIRED_ARGS: -gf -mixin=${RESULTS_DIR}/runnable/testpdb.mixin -preview=bitfields +// REQUIRED_ARGS: -gf -mixin=${RESULTS_DIR}/runnable/testpdb.mixin -preview=bitfields -preview=newpdb // PERMUTE_ARGS: import core.time; From 4f6acb23b61d4070eebed371649c9a127c4664fb Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Wed, 1 Jul 2026 00:20:15 -0700 Subject: [PATCH 10/19] Fix testpdb.d --- compiler/test/runnable/testpdb.d | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/test/runnable/testpdb.d b/compiler/test/runnable/testpdb.d index 7a2354855029..0715ab17f67c 100644 --- a/compiler/test/runnable/testpdb.d +++ b/compiler/test/runnable/testpdb.d @@ -1,4 +1,5 @@ -// REQUIRED_ARGS: -gf -mixin=${RESULTS_DIR}/runnable/testpdb.mixin -preview=bitfields -preview=newpdb +// REQUIRED_ARGS: -gf -mixin=${RESULTS_DIR}/runnable/testpdb.mixin -preview=bitfields +// REQUIRED_ARGS(windows): -preview=newpdb // PERMUTE_ARGS: import core.time; From 52fefed3627d194999972a14233a9e80e73e5b76 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Wed, 1 Jul 2026 04:23:48 -0700 Subject: [PATCH 11/19] Fixes for Rainer's feedback. --- compiler/src/dmd/backend/pdb.d | 178 ++++++++++++++++++++------------- 1 file changed, 111 insertions(+), 67 deletions(-) diff --git a/compiler/src/dmd/backend/pdb.d b/compiler/src/dmd/backend/pdb.d index f97ab74217ce..9b971a6ba1fa 100644 --- a/compiler/src/dmd/backend/pdb.d +++ b/compiler/src/dmd/backend/pdb.d @@ -33,7 +33,6 @@ import dmd.backend.code; import dmd.backend.x86.code_x86; import dmd.backend.cv4; import dmd.backend.mem; -import dmd.backend.el; import dmd.backend.mscoffobj; import dmd.backend.obj; import dmd.backend.oper; @@ -262,16 +261,19 @@ void pdb_termfile(const(char)[] objfilename) // S_COMPILE3 { - const ver = "DMD "; - size_t vlen = VERSION.length; - buf.write16(cast(int)(2 + 4 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + ver.length + vlen + 1)); + // Honor the configured language/compiler instead of hardcoding it: + // emit the D language index unless the debug info was requested for + // non-DMD (Microsoft) debuggers (-gc sets CFG2gms), and report the + // actual compiler version string (dmd/ldc/gdc) via config._version. + const uint flags = config.flags2 & CFG2gms ? 0 : 'D'; // iLanguage in low byte + const(char)[] ver = config._version; + buf.write16(cast(int)(2 + 4 + 2 + 8 + 8 + ver.length + 1)); buf.write16(S_COMPILE3); - buf.write32(0x00010044); // flags: language=D(0x44)<<8? language byte=0x44, rest 0 + buf.write32(flags); buf.write16(I64 ? 0xD0 : 0x07); // machine: AMD64 / x86 buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // FE ver buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // BE ver buf.write(ver.ptr, cast(uint)ver.length); - buf.write(VERSION.ptr, cast(uint)vlen); buf.writeByte(0); } @@ -399,7 +401,35 @@ void pdb_func_term(Symbol* sfunc) funcdata.write(¤tfuncdata, currentfuncdata.sizeof); assert(tyfunc(sfunc.ty())); - idx_t typidx = cv_typidx(sfunc.Stype); + idx_t typidx; + func_t* fn = sfunc.Sfunc; + if (fn.Fclass) + { + // Member functions get a dedicated LF_MFUNCTION_V2 type record that + // records the enclosing class and the `this` pointer type. This info + // isn't available inside cv4_typidx, so replicate cv8_func_term here. + uint nparam; + const ubyte call = cv4_callconv(sfunc.Stype); + const idx_t paramidx = cv4_arglist(sfunc.Stype, &nparam); + const uint next = cv4_typidx(sfunc.Stype.Tnext); + type* classtype = cast(type*)fn.Fclass; + const uint classidx = cv4_typidx(classtype); + type* tp = type_allocn(TYnptr, classtype); + const uint thisidx = cv4_typidx(tp); + debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4 + 1 + 1 + 2 + 4 + 4); + TOWORD(d.data.ptr, LF_MFUNCTION_V2); + TOLONG(d.data.ptr + 2, next); // return type + TOLONG(d.data.ptr + 6, classidx); // class type + TOLONG(d.data.ptr + 10, thisidx); // this type + d.data.ptr[14] = call; + d.data.ptr[15] = 0; // reserved + TOWORD(d.data.ptr + 16, nparam); + TOLONG(d.data.ptr + 18, paramidx); + TOLONG(d.data.ptr + 22, 0); // this adjust + typidx = cv_debtyp(d); + } + else + typidx = cv_typidx(sfunc.Stype); const(char)* id = sfunc.prettyIdent ? sfunc.prettyIdent : prettyident(sfunc); size_t len = strlen(id); @@ -429,17 +459,30 @@ void pdb_func_term(Symbol* sfunc) buf.writen(id, len); buf.writeByte(0); - // S_FRAMEPROC + // S_FRAMEPROC: describe this function's stack frame { - buf.write16(2 + 4 + 4 + 4 + 4 + 4 + 4); + // Locals and parameters are addressed relative to the frame pointer + // (RBP/EBP), matching the S_REGREL32 records emitted for them; the + // CodeView "FramePtr" register encoding for that is 2. + uint frameflags = (2 << 14) | (2 << 16); // encoded local/param base ptr + if (cgstate.anyiasm) + frameflags |= 1 << 3; // fHasInlAsm + if (cgstate.usednteh) + frameflags |= 1 << 4; // fHasEH + if (config.flags2 & CFG2stomp) + frameflags |= 1 << 8; // fSecurityChecks + if (config.flags4 & CFG4speed) + frameflags |= 1 << 20; // fOptSpeed + + buf.write16(2 + 4 + 4 + 4 + 4 + 4 + 2 + 4); buf.write16(S_FRAMEPROC); - buf.write32(cast(uint)cgstate.Auto.size); // frame bytes - buf.write32(0); // pad - buf.write32(0); // pad offset - buf.write32(0); // SE callee saved - buf.write32(0); // exception handler off - // flags: encode RBP (2) as local & param base pointer for x64 - buf.write32(I64 ? (2 << 14) | (2 << 16) : 0); + buf.write32(cast(uint)localsize); // cbFrame: total frame size + buf.write32(0); // cbPad + buf.write32(0); // offPad + buf.write32(0); // cbSaveRegs + buf.write32(0); // offExHdlr + buf.write16(0); // sectExHdlr + buf.write32(frameflags); // flags } struct pdbblk @@ -484,11 +527,9 @@ void pdb_func_term(Symbol* sfunc) } varStats_writeSymbolTable(sfunc, globsym, &pdb_outsym, &pdbblk.endArgs, &pdbblk.beginBlock, &pdbblk.endBlock); - // S_RETURN to mark the epilogue for "step out" - buf.write16(5); - buf.write16(S_RETURN); - buf.write16(0); // CV_GENERIC_FLAG - buf.writeByte(0); // CV_GENERIC_STYLE: void + // No S_RETURN record: its register list uses single-byte CodeView register + // codes, which cannot encode x64 return registers (e.g. CV_AMD64_RAX = 328). + // Both MSVC and cv8.d omit it, so a real record can't be produced here. buf.write16(2); buf.write16(S_END_PDB); @@ -586,30 +627,66 @@ L1: length = cast(uint)F4_buf.length(); p = F4_buf.buf; uint u = 0; - while (u + 8 <= length) + while (u + 6 <= length) { if (off == *cast(uint*)(p + u)) return u; - u += 4; - ubyte cklen = *(p + u); - u += 4; - u += cklen; - u = (u + 3) & ~3; + u += 4; // file name offset + ubyte cklen = *(p + u); // checksum size + u += 2; // skip size + kind bytes + u += cklen; // skip checksum bytes + u = (u + 3) & ~3; // realign to 4 } - // not present; append a full SHA256-slot checksum from blake3 (32 bytes) + // Not present; append a checksum computed over the source file's *content* + // (not over its name) so debuggers can verify the source matches. F4_buf.write32(off); - import dmd.common.blake3; - const hash = blake3((cast(ubyte*)filename)[0 .. len]); - F4_buf.writeByte(32); // checksum size - F4_buf.writeByte(CHKSUM_SHA256); - F4_buf.write16(0); - F4_buf.write(hash.ptr, 32); + ubyte[32] hash = void; + if (pdb_filehash(filename, hash)) + { + F4_buf.writeByte(32); // checksum size + F4_buf.writeByte(CHKSUM_SHA256); // 32-byte checksum slot + F4_buf.write(hash.ptr, 32); + } + else + { + F4_buf.writeByte(0); // no checksum available + F4_buf.writeByte(CHKSUM_NONE); + } while (F4_buf.length() & 3) F4_buf.writeByte(0); return length; } +/* Compute a blake3 hash over the *content* of the named source file. + * Returns: true on success (hash filled in), false if the file can't be read. + */ +private @trusted +bool pdb_filehash(const(char)* filename, ref ubyte[32] hash) +{ + FILE* fp = fopen(filename, "rb"); + if (!fp) + return false; + scope(exit) fclose(fp); + + if (fseek(fp, 0, SEEK_END) != 0) + return false; + const long size = ftell(fp); + if (size < 0 || fseek(fp, 0, SEEK_SET) != 0) + return false; + + ubyte* data = cast(ubyte*)malloc(size ? cast(size_t)size : 1); + if (!data) + return false; + scope(exit) free(data); + + const size_t nread = fread(data, 1, cast(size_t)size, fp); + + import dmd.common.blake3; + hash = blake3(data[0 .. nread]); + return true; +} + private @trusted void pdb_writesection(int seg, uint type, OutBuffer* buf) { @@ -709,28 +786,6 @@ void pdb_outsym(Symbol* s) case SC.extern_: break; - case SC.typedef_: - // LF_ALIAS: a named typedef of an existing type - pdb_alias(id, typidx); - break; - - case SC.const_: - // enum members and manifest constants - { - if (!s.Svalue) - break; - uint val = cast(uint)el_tolong(s.Svalue); - buf.reserve(cast(uint)(2 + 2 + 4 + 2 + 4 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + 2 + 4 + len + 1)); - buf.write16n(S_CONSTANT_V3); - buf.write32(typidx); - buf.write16n(0x8004); // LF_ULONG numeric leaf - buf.write32(val); - pdb_writename(buf, id, len); - buf.writeByte(0); - } - break; - case SC.static_: case SC.locstat: sr = S_LDATA32; @@ -829,17 +884,6 @@ idx_t pdb_func_id(const(char)* id, idx_t functype) return cv_debtyp(d); } -/* LF_ALIAS: a named typedef of an underlying type. */ -@trusted -idx_t pdb_alias(const(char)* id, idx_t utype) -{ - debtyp_t* d = debtyp_alloc(2 + 4 + cv_stringbytes(id)); - TOWORD(d.data.ptr, LF_ALIAS); - TOLONG(d.data.ptr + 2, utype); - cv_namestring(d.data.ptr + 6, id); - return cv_debtyp(d); -} - /* LF_UDT_SRC_LINE: source location of a user-defined type. */ @trusted idx_t pdb_udt_src_line(idx_t typidx, idx_t srcId, uint line) From 494c9f3b32d89802c731f0acef50beaa523e5f91 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Thu, 2 Jul 2026 19:20:56 -0700 Subject: [PATCH 12/19] Combine cv4.d/cv8.d/pdb.d into a single file named codeview.d and remove the preview switch. --- changelog/dmd.newpdb.dd | 20 +- compiler/include/dmd/globals.h | 1 - compiler/src/build.d | 4 +- compiler/src/dmd/backend/backconfig.d | 9 +- compiler/src/dmd/backend/cdef.d | 1 - compiler/src/dmd/backend/cgcv.d | 17 +- compiler/src/dmd/backend/codeview.d | 1496 ++++++++++++++++++++++++ compiler/src/dmd/backend/cv4.d | 245 ---- compiler/src/dmd/backend/cv8.d | 1207 ------------------- compiler/src/dmd/backend/global.d | 7 + compiler/src/dmd/backend/mscoffobj.d | 21 +- compiler/src/dmd/backend/pdb.d | 929 --------------- compiler/src/dmd/cli.d | 2 - compiler/src/dmd/dmsc.d | 21 +- compiler/src/dmd/globals.d | 1 - compiler/src/dmd/glue/e2ir.d | 2 +- compiler/src/dmd/glue/s2ir.d | 2 +- compiler/src/dmd/glue/tocvdebug.d | 4 +- compiler/src/dmd/glue/toobj.d | 2 +- compiler/test/compilable/previewhelp.d | 1 - compiler/test/runnable/testpdb.d | 1 - 21 files changed, 1566 insertions(+), 2427 deletions(-) create mode 100644 compiler/src/dmd/backend/codeview.d delete mode 100644 compiler/src/dmd/backend/cv4.d delete mode 100644 compiler/src/dmd/backend/cv8.d delete mode 100644 compiler/src/dmd/backend/pdb.d diff --git a/changelog/dmd.newpdb.dd b/changelog/dmd.newpdb.dd index d1f75308fa2c..667119c02008 100644 --- a/changelog/dmd.newpdb.dd +++ b/changelog/dmd.newpdb.dd @@ -1,9 +1,21 @@ -Added a new Modern CodeView / PDB symbolic debug info emitter +Modern CodeView / PDB symbolic debug info is now emitted by default on Windows -A new PDB (Program Database) emitter has been added to improve debug information generation on Windows. Creating modern CodeView/PDB symbolic debug info enables more robust and feature-rich debugging experiences with Microsoft development tools. +The Windows CodeView / PDB debug information emitter (used with `-g` when producing MS-COFF object files) has been modernized to emit the record set expected by Microsoft and LLVM debugging tools. These improvements are now always enabled, so debugging with Visual Studio, WinDbg and LLVM-based tools is more robust out of the box. -You can activate the new PDB emitter by passing the `-preview=newpdb` flag to the compiler. +Because the emitter is now the default, the experimental `-preview=newpdb` switch has been removed. + +The following are now generated: + +$(LIST + * `S_COMPILE3` compiland records reporting the configured language and the real compiler version string, + * `S_ENVBLOCK` and `S_BUILDINFO` (`LF_BUILDINFO`) records describing the build environment, + * `S_FRAMEPROC` records describing each function's stack frame, + * real source-file checksums computed over the source contents so debuggers can verify the source matches, + * first-class thread-local storage records, and documented named CodeView constants throughout. +) + +No action is required; simply compile with `-g` as before: ```console -dmd -g -preview=newpdb myapp.d +dmd -g myapp.d ``` diff --git a/compiler/include/dmd/globals.h b/compiler/include/dmd/globals.h index bec8bbb7ced3..84142e5d6774 100644 --- a/compiler/include/dmd/globals.h +++ b/compiler/include/dmd/globals.h @@ -237,7 +237,6 @@ struct Param // https://issues.dlang.org/show_bug.cgi?id=14246 FeatureState systemVariables; // limit access to variables marked @system from @safe code d_bool useFastDFA; // Use fast data flow analysis engine - d_bool newpdb; // use the modern PDB/CodeView debug info emitter CHECKENABLE useInvariants; // generate class invariant checks CHECKENABLE useIn; // generate precondition checks diff --git a/compiler/src/build.d b/compiler/src/build.d index fee1fdb717b8..b48c3b5d54b7 100755 --- a/compiler/src/build.d +++ b/compiler/src/build.d @@ -1538,7 +1538,7 @@ auto sourceFiles() cc.d cdef.d cgcv.d code.d dt.d el.d global.d obj.d oper.d iasm.d codebuilder.d ty.d type.d - dwarf.d dwarf2.d cv4.d + dwarf.d dwarf2.d melf.d mscoff.d mach.d x86/code_x86.d x86/xmm.d "), @@ -1581,7 +1581,7 @@ auto sourceFiles() debugprint.d fp.d symbol.d dcode.d cgsched.d pdata.d util2.d backconfig.d rtlsym.d ptrntab.d dvarstats.d cgen.d barray.d cgcse.d elpicpie.d - dwarfeh.d dwarfdbginf.d cv8.d pdb.d + dwarfeh.d dwarfdbginf.d codeview.d machobj.d elfobj.d mscoffobj.d x86/nteh.d x86/cgreg.d x86/cg87.d x86/cgxmm.d x86/disasm86.d x86/cgcod.d x86/cod1.d x86/cod2.d x86/cod3.d x86/cod4.d x86/cod5.d diff --git a/compiler/src/dmd/backend/backconfig.d b/compiler/src/dmd/backend/backconfig.d index 030fd41ce951..aaa23d4b4173 100644 --- a/compiler/src/dmd/backend/backconfig.d +++ b/compiler/src/dmd/backend/backconfig.d @@ -17,7 +17,8 @@ import core.stdc.stdio; import dmd.backend.cdef; import dmd.backend.cc; import dmd.backend.code; -import dmd.backend.global : ErrorCallbackBackend, error, errorCallbackBackend; +import dmd.backend.global : ErrorCallbackBackend, error, errorCallbackBackend, + GetFileContentsCallback, getFileContentsCallback; import dmd.backend.go : go_flag, GlobalOptimizer; import dmd.backend.rtlsym : rtlsym_init; import dmd.backend.ty; @@ -86,13 +87,14 @@ void out_config_init( exefmt_t exefmt, bool generatedMain, // a main entrypoint is generated bool dataimports, - bool newpdb, // use the modern PDB/CodeView debug info emitter ref GlobalOptimizer go, - ErrorCallbackBackend errorCallback) + ErrorCallbackBackend errorCallback, + GetFileContentsCallback getFileContents) { //printf("out_config_init()\n"); errorCallbackBackend = errorCallback; + getFileContentsCallback = getFileContents; auto cfg = &config; cfg._version = _version; @@ -104,7 +106,6 @@ void out_config_init( } cfg.fulltypes = CVNONE; cfg.fpxmmregs = false; - cfg.newpdb = newpdb; if (!arm) cfg.inline8087 = 1; cfg.memmodel = 0; diff --git a/compiler/src/dmd/backend/cdef.d b/compiler/src/dmd/backend/cdef.d index 31bc59dd4bd3..7019b70d0975 100644 --- a/compiler/src/dmd/backend/cdef.d +++ b/compiler/src/dmd/backend/cdef.d @@ -529,7 +529,6 @@ struct Config bool useTypeInfo; // implement TypeInfo bool useExceptions; // implement exception handling ubyte dwarf; // DWARF version - bool newpdb; // use the modern PDB/CodeView debug info emitter // Configuration that is not saved in precompiled header diff --git a/compiler/src/dmd/backend/cgcv.d b/compiler/src/dmd/backend/cgcv.d index 4740210b1ced..fd14b3d967a1 100644 --- a/compiler/src/dmd/backend/cgcv.d +++ b/compiler/src/dmd/backend/cgcv.d @@ -20,8 +20,7 @@ module dmd.backend.cgcv; import dmd.backend.cc : Classsym, Symbol; import dmd.backend.type; -public import dmd.backend.cv8; -import dmd.backend.pdb : pdb_outsym; +public import dmd.backend.codeview; public import dmd.backend.dwarfdbginf : dwarf_outsym; import core.stdc.stdio; @@ -33,7 +32,6 @@ import dmd.backend.cdef; import dmd.backend.cgcv; import dmd.backend.code; import dmd.backend.x86.code_x86; -import dmd.backend.cv4; import dmd.backend.dvec; import dmd.backend.el; import dmd.backend.global : err_nomem; @@ -1231,7 +1229,7 @@ L1: { case CV8: { - typidx = cv8_darray(t, next); + typidx = cv_darray(t, next); break; } case CV4: @@ -1266,7 +1264,7 @@ else switch (config.fulltypes) { case CV8: - typidx = cv8_daarray(t, key, next); + typidx = cv_daarray(t, key, next); break; case CV4: @@ -1299,7 +1297,7 @@ else switch (config.fulltypes) { case CV8: - typidx = cv8_ddelegate(t, next); + typidx = cv_ddelegate(t, next); break; case CV4: @@ -1453,7 +1451,7 @@ else { if (config.fulltypes == CV8) { - typidx = cv8_fwdref(t.Ttag); + typidx = cv_fwdref(t.Ttag); } else { @@ -2180,10 +2178,7 @@ void cv_outsym(Symbol* s) break; case CV8: - if (config.newpdb) - pdb_outsym(s); - else - cv8_outsym(s); + cv_symbol(s); break; default: diff --git a/compiler/src/dmd/backend/codeview.d b/compiler/src/dmd/backend/codeview.d new file mode 100644 index 000000000000..a2c5f977781f --- /dev/null +++ b/compiler/src/dmd/backend/codeview.d @@ -0,0 +1,1496 @@ +/** + * CodeView / PDB symbolic debug info generation. + * + * This module generates the `.debug$S` (symbols) and `.debug$T` (types) + * sections for Win64 MS-COFF objects. It merges the CodeView 4 record + * declarations, the legacy CodeView 8 emitter, and the modern CodeView/PDB + * record set documented by LLVM (https://llvm.org/docs/PDB/) and Microsoft + * (https://github.com/microsoft/microsoft-pdb) into a single emitter. + * + * The shared type-record pool in cgcv.d/cv4-era helpers is reused via + * `cv_typidx`, so D-specific type records (dynamic arrays, delegates, + * associative arrays) stay shared. + * + * Compiler implementation of the + * $(LINK2 https://www.dlang.org, D programming language). + * + * Copyright: Copyright (C) 2012-2026 by The D Language Foundation, All Rights Reserved + * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) + * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/backend/codeview.d, backend/codeview.d) + * Documentation: https://dlang.org/phobos/dmd_backend_codeview.html + * Coverage: https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/backend/codeview.d + */ + +module dmd.backend.codeview; + +import core.stdc.stdio; +import core.stdc.stdlib; +import core.stdc.string; +extern (C) nothrow char* getcwd(char*, size_t); + +import dmd.backend.cc; +import dmd.backend.cdef; +import dmd.backend.cgcv; +import dmd.backend.code; +import dmd.backend.x86.code_x86; +import dmd.backend.mem; +import dmd.backend.global : getFileContentsCallback; +import dmd.backend.mscoffobj; +import dmd.backend.obj; +import dmd.backend.oper; +import dmd.common.outbuffer; +import dmd.backend.rtlsym; +import dmd.backend.symbol : globsym; +import dmd.backend.ty; +import dmd.backend.type; +import dmd.backend.dvarstats; + + +nothrow: +@safe: + +/* ======================================================================== */ +/* CodeView record constants (formerly backend/cv4.d). */ +/* ======================================================================== */ + +enum OEM = 0x42; // Digital Mars OEM number (picked at random) + +// Symbol Indices +enum +{ + S_COMPILE = 1, + S_REGISTER = 2, + S_CONST = 3, + S_UDT = 4, + S_SSEARCH = 5, + S_END = 6, + S_SKIP = 7, + S_CVRESERVE = 8, + S_OBJNAME = 9, + S_ENDARG = 0x0A, + S_COBOLUDT = 0x0B, + S_MANYREG = 0x0C, + S_RETURN = 0x0D, + S_ENTRYTHIS = 0x0E, + S_TDBNAME = 0x0F, + + S_BPREL16 = 0x100, + S_LDATA16 = 0x101, + S_GDATA16 = 0x102, + S_PUB16 = 0x103, + S_LPROC16 = 0x104, + S_GPROC16 = 0x105, + S_THUNK16 = 0x106, + S_BLOCK16 = 0x107, + S_WITH16 = 0x108, + S_LABEL16 = 0x109, + S_CEXMODEL16 = 0x10A, + S_VFTPATH16 = 0x10B, + + S_BPREL32 = 0x200, + S_LDATA32 = 0x201, + S_GDATA32 = 0x202, + S_PUB32 = 0x203, + S_LPROC32 = 0x204, + S_GPROC32 = 0x205, + S_THUNK32 = 0x206, + S_BLOCK32 = 0x207, + S_WITH32 = 0x208, + S_LABEL32 = 0x209, + S_CEXMODEL32 = 0x20A, + S_VFTPATH32 = 0x20B, + + /************** Added Since CV4 *********************/ + + S_REGISTER_V2 = 0x1001, + S_CONSTANT_V2 = 0x1002, + S_UDT_V2 = 0x1003, + S_COBOLUDT_V2 = 0x1004, + S_MANYREG_V2 = 0x1005, + S_BPREL_V2 = 0x1006, + S_LDATA_V2 = 0x1007, + S_GDATA_V2 = 0x1008, + S_PUB_V2 = 0x1009, + S_LPROC_V2 = 0x100A, + S_GPROC_V2 = 0x100B, + S_VFTTABLE_V2 = 0x100C, + S_REGREL_V2 = 0x100D, + S_LTHREAD_V2 = 0x100E, + S_GTHREAD_V2 = 0x100F, + S_FUNCINFO_V2 = 0x1012, + S_COMPILAND_V2 = 0x1013, + + S_COMPILAND_V3 = 0x1101, + S_THUNK_V3 = 0x1102, + S_BLOCK_V3 = 0x1103, + S_LABEL_V3 = 0x1105, + S_REGISTER_V3 = 0x1106, + S_CONSTANT_V3 = 0x1107, + S_UDT_V3 = 0x1108, + S_BPREL_V3 = 0x110B, + S_LDATA_V3 = 0x110C, + S_GDATA_V3 = 0x110D, + S_PUB_V3 = 0x110E, + S_LPROC_V3 = 0x110F, + S_GPROC_V3 = 0x1110, + S_BPREL_XXXX_V3 = 0x1111, + S_MSTOOL_V3 = 0x1116, + S_PUB_FUNC1_V3 = 0x1125, + S_PUB_FUNC2_V3 = 0x1127, + S_SECTINFO_V3 = 0x1136, + S_SUBSECTINFO_V3 = 0x1137, + S_ENTRYPOINT_V3 = 0x1138, + S_SECUCOOKIE_V3 = 0x113A, + S_MSTOOLINFO_V3 = 0x113C, + S_MSTOOLENV_V3 = 0x113D, +} + +// Leaf Indices +enum +{ + LF_MODIFIER = 1, + LF_POINTER = 2, + LF_ARRAY = 3, + LF_CLASS = 4, + LF_STRUCTURE = 5, + LF_UNION = 6, + LF_ENUM = 7, + LF_PROCEDURE = 8, + LF_MFUNCTION = 9, + LF_VTSHAPE = 0x0A, + LF_COBOL0 = 0x0B, + LF_COBOL1 = 0x0C, + LF_BARRAY = 0x0D, + LF_LABEL = 0x0E, + LF_NULL = 0x0F, + LF_NOTTRAN = 0x10, + LF_DIMARRAY = 0x11, + LF_VFTPATH = 0x12, + LF_PRECOMP = 0x13, + LF_ENDPRECOMP = 0x14, + LF_OEM = 0x15, + LF_TYPESERVER = 0x16, + + // D extensions (not used, causes linker to fail) + LF_DYN_ARRAY = 0x17, + LF_ASSOC_ARRAY = 0x18, + LF_DELEGATE = 0x19, + + LF_SKIP = 0x200, + LF_ARGLIST = 0x201, + LF_DEFARG = 0x202, + LF_LIST = 0x203, + LF_FIELDLIST = 0x204, + LF_DERIVED = 0x205, + LF_BITFIELD = 0x206, + LF_METHODLIST = 0x207, + LF_DIMCONU = 0x208, + LF_DIMCONLU = 0x209, + LF_DIMVARU = 0x20A, + LF_DIMVARLU = 0x20B, + LF_REFSYM = 0x20C, + + LF_BCLASS = 0x400, + LF_VBCLASS = 0x401, + LF_IVBCLASS = 0x402, + LF_ENUMERATE = 0x403, + LF_FRIENDFCN = 0x404, + LF_INDEX = 0x405, + LF_MEMBER = 0x406, + LF_STMEMBER = 0x407, + LF_METHOD = 0x408, + LF_NESTTYPE = 0x409, + LF_VFUNCTAB = 0x40A, + LF_FRIENDCLS = 0x40B, + + LF_NUMERIC = 0x8000, + LF_CHAR = 0x8000, + LF_SHORT = 0x8001, + LF_USHORT = 0x8002, + LF_LONG = 0x8003, + LF_ULONG = 0x8004, + LF_REAL32 = 0x8005, + LF_REAL64 = 0x8006, + LF_REAL80 = 0x8007, + LF_REAL128 = 0x8008, + LF_QUADWORD = 0x8009, + LF_UQUADWORD = 0x800A, + LF_REAL48 = 0x800B, + + LF_COMPLEX32 = 0x800C, + LF_COMPLEX64 = 0x800D, + LF_COMPLEX80 = 0x800E, + LF_COMPLEX128 = 0x800F, + + LF_VARSTRING = 0x8010, + + /************** Added Since CV4 *********************/ + + LF_MODIFIER_V2 = 0x1001, + LF_POINTER_V2 = 0x1002, + LF_ARRAY_V2 = 0x1003, + LF_CLASS_V2 = 0x1004, + LF_STRUCTURE_V2 = 0x1005, + LF_UNION_V2 = 0x1006, + LF_ENUM_V2 = 0x1007, + LF_PROCEDURE_V2 = 0x1008, + LF_MFUNCTION_V2 = 0x1009, + LF_COBOL0_V2 = 0x100A, + LF_BARRAY_V2 = 0x100B, + LF_DIMARRAY_V2 = 0x100C, + LF_VFTPATH_V2 = 0x100D, + LF_PRECOMP_V2 = 0x100E, + LF_OEM_V2 = 0x100F, + + LF_SKIP_V2 = 0x1200, + LF_ARGLIST_V2 = 0x1201, + LF_DEFARG_V2 = 0x1202, + LF_FIELDLIST_V2 = 0x1203, + LF_DERIVED_V2 = 0x1204, + LF_BITFIELD_V2 = 0x1205, + LF_METHODLIST_V2 = 0x1206, + LF_DIMCONU_V2 = 0x1207, + LF_DIMCONLU_V2 = 0x1208, + LF_DIMVARU_V2 = 0x1209, + LF_DIMVARLU_V2 = 0x120A, + + LF_BCLASS_V2 = 0x1400, + LF_VBCLASS_V2 = 0x1401, + LF_IVBCLASS_V2 = 0x1402, + LF_FRIENDFCN_V2 = 0x1403, + LF_INDEX_V2 = 0x1404, + LF_MEMBER_V2 = 0x1405, + LF_STMEMBER_V2 = 0x1406, + LF_METHOD_V2 = 0x1407, + LF_NESTTYPE_V2 = 0x1408, + LF_VFUNCTAB_V2 = 0x1409, + LF_FRIENDCLS_V2 = 0x140A, + LF_ONEMETHOD_V2 = 0x140B, + LF_VFUNCOFF_V2 = 0x140C, + LF_NESTTYPEEX_V2 = 0x140D, + + LF_ENUMERATE_V3 = 0x1502, + LF_ARRAY_V3 = 0x1503, + LF_CLASS_V3 = 0x1504, + LF_STRUCTURE_V3 = 0x1505, + LF_UNION_V3 = 0x1506, + LF_ENUM_V3 = 0x1507, + LF_MEMBER_V3 = 0x150D, + LF_STMEMBER_V3 = 0x150E, + LF_METHOD_V3 = 0x150F, + LF_NESTTYPE_V3 = 0x1510, + LF_ONEMETHOD_V3 = 0x1511, +} + +/* ======================================================================== */ +/* Modern CodeView (CV8 / PDB) records not present in the CV4 record set. */ +/* ======================================================================== */ + +// Symbol records +enum +{ + S_COMPILE3 = 0x113C, // modern compiland flags/version (aka S_MSTOOLINFO_V3) + S_ENVBLOCK = 0x113D, // build environment (aka S_MSTOOLENV_V3) + S_BUILDINFO = 0x114C, // references an LF_BUILDINFO id record + S_FRAMEPROC = 0x1012, // per-function frame info (aka S_FUNCINFO_V2) + S_REGREL_V3 = 0x1111, // register-relative addressing (aka S_BPREL_XXXX_V3) + S_LTHREAD_V3 = 0x1112, // local thread-local storage + S_GTHREAD_V3 = 0x1113, // global thread-local storage +} + +// ID-stream type leaves +enum +{ + LF_FUNC_ID = 0x1601, // ties a function type to its name + LF_STRING_ID = 0x1605, // interned string -> type index + LF_BUILDINFO = 0x1603, // cwd, tool, source, pdb, args + LF_UDT_SRC_LINE = 0x1606, // source file/line where a UDT is defined +} + +// .debug$S subsection kinds +enum +{ + DEBUG_S_SYMBOLS = 0xF1, + DEBUG_S_LINES = 0xF2, + DEBUG_S_STRINGTABLE = 0xF3, + DEBUG_S_FILECHKSMS = 0xF4, +} + +// File checksum kinds +enum +{ + CHKSUM_NONE = 0, + CHKSUM_MD5 = 1, + CHKSUM_SHA1 = 2, + CHKSUM_SHA256 = 3, +} + +// S_FRAMEPROC flags +enum +{ + CV_FRAME_HASINLASM = 1 << 3, // function has inline asm + CV_FRAME_HASEH = 1 << 4, // function has exception handling + CV_FRAME_SECURITY = 1 << 8, // stack security cookie checks + CV_FRAME_OPTSPEED = 1 << 20, // optimized for speed + CV_FRAME_LOCALBP_RBP = 2 << 14, // locals addressed relative to RBP/EBP + CV_FRAME_PARAMBP_RBP = 2 << 16, // params addressed relative to RBP/EBP +} + +// Mark a line-number entry as a statement (not an expression) +enum CV_LINE_STATEMENT = 0x80000000; + +// CodeView register encodings for base-pointer relative records +enum +{ + CV_REG_EBP = 22, // x86 EBP + CV_AMD64_RBP = 334, // x64 RBP +} + +// if symbols get longer than 65500 bytes, the linker reports corrupt debug info or exits with +// 'fatal error LNK1318: Unexpected PDB error; RPC (23) '(0x000006BA)' +enum CV_MAX_SYMBOL_LENGTH = 0xffd8; + +// CV_PUBSYMFLAGS +enum PUB_FUNCTION = 0x00000002; + +/* ======================================================================== */ +/* State */ +/* ======================================================================== */ + +// Determine if this Symbol is stored in a COMDAT +@trusted +private bool symbol_iscomdat4(Symbol* s) +{ + return s.Sclass == SC.comdat || + config.flags2 & CFG2comdat && s.Sclass == SC.inline || + config.flags4 & CFG4allcomdat && s.Sclass == SC.global; +} + +// The "F1" section, which is the symbols +private __gshared OutBuffer* F1_buf; + +// The "F2" section, which is the line numbers +private __gshared OutBuffer* F2_buf; + +// The "F3" section, which is global and a string table of source file names. +private __gshared OutBuffer* F3_buf; + +// The "F4" section, which is global and a list of info about source files. +private __gshared OutBuffer* F4_buf; + +/* Fixups that go into F1 section + */ +struct F1_Fixups +{ + Symbol* s; + uint offset; + uint value; +} + +private __gshared OutBuffer* F1fixup; // array of F1_Fixups + +/* Struct in which to collect per-function data, for later emission + * into .debug$S. + */ +struct FuncData +{ + Symbol* sfunc; + uint section_length; + const(char)* srcfilename; + uint srcfileoff; + uint linepairstart; // starting byte index of offset/line pairs in linebuf[] + uint linepairbytes; // number of bytes for offset/line pairs + uint linepairsegment; // starting byte index of filename segment for offset/line pairs + OutBuffer* f1buf; + OutBuffer* f1fixup; +} + +__gshared FuncData currentfuncdata; + +private __gshared OutBuffer* funcdata; // array of FuncData's + +private __gshared OutBuffer* linepair; // array of offset/line pairs + +/* ======================================================================== */ +/* Name encoding */ +/* ======================================================================== */ + +private @trusted +void cv_writename(OutBuffer* buf, const(char)* name, size_t len) +{ + if (!(config.flags2 & CFG2gms)) + { + buf.writen(name, len); + return; + } + + const(char)* start = name; + const(char)* cur = strchr(start, '.'); + const(char)* end = start + len; + while (cur != null) + { + if (cur >= end) + { + buf.writen(start, end - start); + return; + } + buf.writen(start, cur - start); + buf.writeByte('@'); + start = cur + 1; + if (start >= end) + return; + cur = strchr(start, '.'); + } + buf.writen(start, end - start); +} + +/* ======================================================================== */ +/* File lifecycle */ +/* ======================================================================== */ + +/************************************************ + * Called at the start of an object file generation. + * One source file can generate multiple object files; this starts an object file. + * Input: + * filename source file name + */ +@trusted +void cv_initfile(const(char)* filename) +{ + void initBuf(ref OutBuffer* ptr) + { + if (!ptr) + { + ptr = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); + ptr.reserve(1024); + } + ptr.reset(); + } + + initBuf(F1_buf); + initBuf(F1fixup); + initBuf(F2_buf); + + initBuf(F3_buf); + F3_buf.writeByte(0); // first "filename" + + initBuf(F4_buf); + initBuf(funcdata); + initBuf(linepair); + + memset(¤tfuncdata, 0, currentfuncdata.sizeof); + currentfuncdata.f1buf = F1_buf; + currentfuncdata.f1fixup = F1fixup; + + cv_init(); +} + +/************************************************ + * Called at the start of a module. + * Note that there can be multiple modules in one object file. + * cv_initfile() must be called first. + */ +void cv_initmodule(const(char)* filename, const(char)* modulename) +{ +} + +@trusted +void cv_termmodule() +{ + assert(config.objfmt == OBJ_MSCOFF); +} + +@trusted +void cv_termfile(const(char)[] objfilename) +{ + int seg = MsCoffObj_seg_debugS(); + + uint value = 4; // CV signature C13 + objmod.bytes(seg, 0, (cast(void*)&value)[0 .. 4]); + + auto buf = OutBuffer(1024); + + // S_OBJNAME + { + size_t len = objfilename.length; + buf.write16(cast(int)(2 + 4 + len + 1)); + buf.write16(S_COMPILAND_V3); + buf.write32(0); // signature + buf.write(objfilename.ptr, cast(uint)(len + 1)); + } + + // S_COMPILE3 + { + // Honor the configured language/compiler instead of hardcoding it: + // emit the D language index unless the debug info was requested for + // non-DMD (Microsoft) debuggers (-gc sets CFG2gms), and report the + // actual compiler version string (dmd/ldc/gdc) via config._version. + const uint flags = config.flags2 & CFG2gms ? 0 : 'D'; // iLanguage in low byte + const(char)[] ver = config._version; + buf.write16(cast(int)(2 + 4 + 2 + 8 + 8 + ver.length + 1)); + buf.write16(S_COMPILE3); + buf.write32(flags); + buf.write16(I64 ? 0xD0 : 0x07); // machine: AMD64 / x86 + buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // FE ver + buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // BE ver + buf.write(ver.ptr, cast(uint)ver.length); + buf.writeByte(0); + } + + cv_writesection(seg, DEBUG_S_SYMBOLS, &buf); + + // S_ENVBLOCK: build environment (cwd, tool), double-null terminated + { + auto ebuf = OutBuffer(128); + char[260] cwd = 0; + if (!getcwd(cwd.ptr, cwd.sizeof)) + cwd[0] = 0; + size_t cwdlen = strlen(cwd.ptr); + uint rec = cast(uint)(2 + 1 + 4 + cwdlen + 1 + 4 + 1 + 1); + ebuf.write16(cast(int)rec); + ebuf.write16(S_ENVBLOCK); + ebuf.writeByte(0); // flags + ebuf.write("cwd\0".ptr, 4); + ebuf.write(cwd.ptr, cast(uint)(cwdlen + 1)); + ebuf.write("cmd\0".ptr, 4); + ebuf.writeByte(0); // empty cmd value + ebuf.writeByte(0); // terminating double-null + cv_writesection(seg, DEBUG_S_SYMBOLS, &ebuf); + } + + // S_BUILDINFO referencing an LF_BUILDINFO id record + { + auto bbuf = OutBuffer(64); + idx_t biidx = cv_buildinfo(); + bbuf.write16(2 + 4); + bbuf.write16(S_BUILDINFO); + bbuf.write32(biidx); + cv_writesection(seg, DEBUG_S_SYMBOLS, &bbuf); + } + + uint length = cast(uint)funcdata.length(); + ubyte* p = funcdata.buf; + for (uint u = 0; u < length; u += FuncData.sizeof) + { + FuncData* fd = cast(FuncData*)(p + u); + + F2_buf.reset(); + F2_buf.write32(cast(uint)fd.sfunc.Soffset); + F2_buf.write32(0); + F2_buf.write32(fd.section_length); + F2_buf.write(linepair.buf + fd.linepairstart, fd.linepairbytes); + + int f2seg = seg; + if (symbol_iscomdat4(fd.sfunc)) + { + f2seg = MsCoffObj_seg_debugS_comdat(fd.sfunc); + objmod.bytes(f2seg, 0, (cast(void*)&value)[0 .. 4]); + } + + uint offset = cast(uint)SegData[f2seg].SDoffset + 8; + cv_writesection(f2seg, DEBUG_S_LINES, F2_buf); + objmod.reftoident(f2seg, offset, fd.sfunc, 0, CF.seg | CF.off); + + if (f2seg != seg && fd.f1buf.length()) + { + const uint f1offset = cast(uint)SegData[f2seg].SDoffset; + cv_writesection(f2seg, DEBUG_S_SYMBOLS, fd.f1buf); + + const uint fixupLength = cast(uint)fd.f1fixup.length(); + ubyte* pfixup = fd.f1fixup.buf; + for (uint v = 0; v < fixupLength; v += F1_Fixups.sizeof) + { + F1_Fixups* f = cast(F1_Fixups*)(pfixup + v); + objmod.reftoident(f2seg, f1offset + 8 + f.offset, f.s, f.value, CF.seg | CF.off); + } + } + } + + if (F3_buf.length() > 1) + cv_writesection(seg, DEBUG_S_STRINGTABLE, F3_buf); + + if (F4_buf.length() > 0) + cv_writesection(seg, DEBUG_S_FILECHKSMS, F4_buf); + + if (F1_buf.length()) + { + uint f1offset = cast(uint)SegData[seg].SDoffset; + cv_writesection(seg, DEBUG_S_SYMBOLS, F1_buf); + + length = cast(uint)F1fixup.length(); + p = F1fixup.buf; + for (uint u = 0; u < length; u += F1_Fixups.sizeof) + { + F1_Fixups* f = cast(F1_Fixups*)(p + u); + objmod.reftoident(seg, f1offset + 8 + f.offset, f.s, f.value, CF.seg | CF.off); + } + } + + cv_term(); // .debug$T +} + +/* ======================================================================== */ +/* Functions */ +/* ======================================================================== */ + +/****************************************** + * Called at the start of a function. + */ +@trusted +void cv_func_start(Symbol* sfunc) +{ + currentfuncdata.sfunc = sfunc; + currentfuncdata.section_length = 0; + currentfuncdata.srcfilename = null; + currentfuncdata.linepairstart += currentfuncdata.linepairbytes; + currentfuncdata.linepairbytes = 0; + currentfuncdata.f1buf = F1_buf; + currentfuncdata.f1fixup = F1fixup; + if (symbol_iscomdat4(sfunc)) + { + // This leaks memory + currentfuncdata.f1buf = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); + currentfuncdata.f1buf.reserve(128); + currentfuncdata.f1fixup = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); + currentfuncdata.f1fixup.reserve(128); + } + varStats_startFunction(); +} + +@trusted +void cv_func_term(Symbol* sfunc) +{ + assert(currentfuncdata.sfunc == sfunc); + currentfuncdata.section_length = cast(uint)sfunc.Ssize; + funcdata.write(¤tfuncdata, currentfuncdata.sizeof); + + assert(tyfunc(sfunc.ty())); + idx_t typidx; + func_t* fn = sfunc.Sfunc; + if (fn.Fclass) + { + // Member functions get a dedicated LF_MFUNCTION_V2 type record that + // records the enclosing class and the `this` pointer type. This info + // isn't available inside cv_typidx, so generate it here. + uint nparam; + const ubyte call = cv4_callconv(sfunc.Stype); + const idx_t paramidx = cv4_arglist(sfunc.Stype, &nparam); + const uint next = cv4_typidx(sfunc.Stype.Tnext); + type* classtype = cast(type*)fn.Fclass; + const uint classidx = cv4_typidx(classtype); + type* tp = type_allocn(TYnptr, classtype); + const uint thisidx = cv4_typidx(tp); + debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4 + 1 + 1 + 2 + 4 + 4); + TOWORD(d.data.ptr, LF_MFUNCTION_V2); + TOLONG(d.data.ptr + 2, next); // return type + TOLONG(d.data.ptr + 6, classidx); // class type + TOLONG(d.data.ptr + 10, thisidx); // this type + d.data.ptr[14] = call; + d.data.ptr[15] = 0; // reserved + TOWORD(d.data.ptr + 16, nparam); + TOLONG(d.data.ptr + 18, paramidx); + TOLONG(d.data.ptr + 22, 0); // this adjust + typidx = cv_debtyp(d); + } + else + typidx = cv_typidx(sfunc.Stype); + + const(char)* id = sfunc.prettyIdent ? sfunc.prettyIdent : prettyident(sfunc); + size_t len = strlen(id); + if (len > CV_MAX_SYMBOL_LENGTH) + len = CV_MAX_SYMBOL_LENGTH; + + auto buf = currentfuncdata.f1buf; + buf.reserve(cast(uint)(2 + 2 + 4 * 7 + 6 + 1 + len + 1)); + buf.write16n(cast(int)(2 + 4 * 7 + 6 + 1 + len + 1)); + buf.write16n(sfunc.Sclass == SC.static_ ? S_LPROC_V3 : S_GPROC_V3); + buf.write32(0); // parent + buf.write32(0); // pend + buf.write32(0); // pnext + buf.write32(cast(uint)currentfuncdata.section_length); + buf.write32(cast(uint)cgstate.startoffset); // prolog size + buf.write32(cast(uint)cgstate.retoffset); // epilog offset + buf.write32(typidx); + + F1_Fixups f1f; + f1f.s = sfunc; + f1f.offset = cast(uint)buf.length(); + f1f.value = 0; + currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); + buf.write32(0); + buf.write16n(0); + buf.writeByte(0); // flags + buf.writen(id, len); + buf.writeByte(0); + + // S_FRAMEPROC: describe this function's stack frame + { + // Locals and parameters are addressed relative to the frame pointer + // (RBP/EBP), matching the S_REGREL_V3 records emitted for them. + uint frameflags = CV_FRAME_LOCALBP_RBP | CV_FRAME_PARAMBP_RBP; + if (cgstate.anyiasm) + frameflags |= CV_FRAME_HASINLASM; + if (cgstate.usednteh) + frameflags |= CV_FRAME_HASEH; + if (config.flags2 & CFG2stomp) + frameflags |= CV_FRAME_SECURITY; + if (config.flags4 & CFG4speed) + frameflags |= CV_FRAME_OPTSPEED; + + buf.write16(2 + 4 + 4 + 4 + 4 + 4 + 2 + 4); + buf.write16(S_FRAMEPROC); + buf.write32(cast(uint)localsize); // cbFrame: total frame size + buf.write32(0); // cbPad + buf.write32(0); // offPad + buf.write32(0); // cbSaveRegs + buf.write32(0); // offExHdlr + buf.write16(0); // sectExHdlr + buf.write32(frameflags); // flags + } + + struct cvblk + { + nothrow: + // record for CV record S_BLOCK_V3 + struct block_v3_data + { + ushort len; + ushort id; + uint pParent; + uint pEnd; + uint length; + uint offset; + ushort seg; + ubyte[1] name; + } + static void endArgs() + { + auto b = currentfuncdata.f1buf; + b.write16(2); + b.write16(S_ENDARG); + } + static void beginBlock(int offset, int length) + { + auto b = currentfuncdata.f1buf; + uint soffset = cast(uint)b.length(); + // parent and end to be filled by linker + block_v3_data block32 = { block_v3_data.sizeof - 2, S_BLOCK_V3, 0, 0, length, offset, 0, [ 0 ] }; + b.write(&block32, block32.sizeof); + size_t offOffset = cast(char*)&block32.offset - cast(char*)&block32; + F1_Fixups f; + f.s = currentfuncdata.sfunc; + f.offset = cast(uint)(soffset + offOffset); + f.value = offset; + currentfuncdata.f1fixup.write(&f, f.sizeof); + } + static void endBlock() + { + auto b = currentfuncdata.f1buf; + b.write16(2); + b.write16(S_END); + } + } + varStats_writeSymbolTable(sfunc, globsym, &cv_symbol, &cvblk.endArgs, &cvblk.beginBlock, &cvblk.endBlock); + + // No S_RETURN record: its register list uses single-byte CodeView register + // codes, which cannot encode x64 return registers (e.g. CV_AMD64_RAX = 328). + // Both MSVC and the legacy CV8 path omit it, so a real record can't be produced here. + + buf.write16(2); + buf.write16(S_END); + + currentfuncdata.f1buf = F1_buf; + currentfuncdata.f1fixup = F1fixup; +} + +/* ======================================================================== */ +/* Line numbers */ +/* ======================================================================== */ + +@trusted +void cv_linnum(Srcpos srcpos, uint offset) +{ + const sfilename = srcpos.Sfilename; + if (!sfilename) + return; + + varStats_recordLineOffset(srcpos, offset); + + __gshared uint lastoffset; + __gshared uint lastlinnum; + + if (!currentfuncdata.srcfilename || + (currentfuncdata.srcfilename != sfilename && strcmp(currentfuncdata.srcfilename, sfilename))) + { + currentfuncdata.srcfilename = sfilename; + uint srcfileoff = cv_addfile(sfilename); + currentfuncdata.linepairsegment = currentfuncdata.linepairstart + currentfuncdata.linepairbytes; + linepair.write32(srcfileoff); + linepair.write32(0); + linepair.write32(12); + currentfuncdata.linepairbytes += 12; + } + else if (offset <= lastoffset || srcpos.Slinnum == lastlinnum) + return; + + lastoffset = offset; + lastlinnum = srcpos.Slinnum; + linepair.write32(offset); + linepair.write32(srcpos.Slinnum | CV_LINE_STATEMENT); // mark as statement, not expression + currentfuncdata.linepairbytes += 8; + + auto segmentbytes = currentfuncdata.linepairstart + currentfuncdata.linepairbytes - currentfuncdata.linepairsegment; + auto segmentheader = cast(uint*)(linepair.buf + currentfuncdata.linepairsegment); + segmentheader[1] = (segmentbytes - 12) / 8; + segmentheader[2] = segmentbytes; +} + +/* ======================================================================== */ +/* Source files (F3 names + F4 checksums via blake3) */ +/* ======================================================================== */ + +/********************************************** + * Add source file, if it isn't already there. + * Return offset into F4. + */ +@trusted +uint cv_addfile(const(char)* filename) +{ + uint length = cast(uint)F3_buf.length(); + ubyte* p = F3_buf.buf; + size_t len = strlen(filename); + + // ensure the filename is absolute to help the debugger to find the source + // without having to know the working directory during compilation + __gshared char[260] cwd = 0; + __gshared uint cwdlen; + bool abs = (*filename == '\\') || (*filename == '/') || + (*filename && filename[1] == ':'); + + if (!abs && cwd[0] == 0) + { + if (getcwd(cwd.ptr, cwd.sizeof)) + { + cwdlen = cast(uint)strlen(cwd.ptr); + if (cwd[cwdlen - 1] != '\\' && cwd[cwdlen - 1] != '/') + cwd[cwdlen++] = '\\'; + } + } + uint off = 1; + while (off + len < length) + { + if (!abs) + { + if (memcmp(p + off, cwd.ptr, cwdlen) == 0 && + memcmp(p + off + cwdlen, filename, len + 1) == 0) + goto L1; + } + else if (memcmp(p + off, filename, len + 1) == 0) + goto L1; + off += strlen(cast(const(char)*)(p + off)) + 1; + } + off = length; + if (!abs) + F3_buf.write(cwd.ptr, cwdlen); + F3_buf.write(filename, cast(uint)(len + 1)); + +L1: + length = cast(uint)F4_buf.length(); + p = F4_buf.buf; + uint u = 0; + while (u + 6 <= length) + { + if (off == *cast(uint*)(p + u)) + return u; + u += 4; // file name offset + ubyte cklen = *(p + u); // checksum size + u += 2; // skip size + kind bytes + u += cklen; // skip checksum bytes + u = (u + 3) & ~3; // realign to 4 + } + + // Not present; append a checksum computed over the source file's *content* + // (not over its name) so debuggers can verify the source matches. + F4_buf.write32(off); + ubyte[32] hash = void; + if (cv_filehash(filename, hash)) + { + F4_buf.writeByte(32); // checksum size + F4_buf.writeByte(CHKSUM_SHA256); // 32-byte checksum slot + F4_buf.write(hash.ptr, 32); + } + else + { + F4_buf.writeByte(0); // no checksum available + F4_buf.writeByte(CHKSUM_NONE); + } + while (F4_buf.length() & 3) + F4_buf.writeByte(0); + return length; +} + +/* Compute a blake3 hash over the *content* of the named source file. + * The bytes are fetched from the front-end's FileManager cache (populated when + * the module was read) via a callback, avoiding a second read from disk. + * Returns: true on success (hash filled in), false if the content is unavailable. + */ +private @trusted +bool cv_filehash(const(char)* filename, ref ubyte[32] hash) +{ + if (!getFileContentsCallback) + return false; + + size_t length; + const(ubyte)* data = getFileContentsCallback(filename, length); + if (!data) + return false; + + import dmd.common.blake3; + hash = blake3(data[0 .. length]); + return true; +} + +private @trusted +void cv_writesection(int seg, uint type, OutBuffer* buf) +{ + /* Write out as: + * bytes desc + * -------+---- + * 4 type + * 4 length + * length data + * pad pad to 4 byte boundary + */ + uint off = cast(uint)SegData[seg].SDoffset; + objmod.bytes(seg, off, (cast(void*)&type)[0 .. 4]); + uint length = cast(uint)buf.length(); + objmod.bytes(seg, off + 4, (cast(void*)&length)[0 .. 4]); + objmod.bytes(seg, off + 8, buf.buf[0 .. length]); + uint pad = ((length + 3) & ~3) - length; + objmod.lidata(seg, off + 8 + length, pad); +} + +/* ======================================================================== */ +/* Symbols */ +/* ======================================================================== */ + +@trusted +void cv_symbol(Symbol* s) +{ + if (s.Sflags & SFLnodebug) + return; + + idx_t typidx = cv_typidx(s.Stype); + const(char)* id = s.prettyIdent ? s.prettyIdent : prettyident(s); + size_t len = strlen(id); + if (len > CV_MAX_SYMBOL_LENGTH) + len = CV_MAX_SYMBOL_LENGTH; + + F1_Fixups f1f; + f1f.value = 0; + auto buf = currentfuncdata.f1buf; + + uint sr; + uint base; + switch (s.Sclass) + { + case SC.parameter: + case SC.regpar: + case SC.shadowreg: + if (s.Sfl == FL.reg) + { + s.Sfl = FL.para; + cv_symbol(s); + s.Sfl = FL.reg; + goto case_register; + } + base = cast(uint)(cgstate.Para.size - cgstate.BPoff); + goto L1; + + case SC.auto_: + if (s.Sfl == FL.reg) + goto case_register; + case_auto: + base = cast(uint)cgstate.Auto.size; + L1: + if (s.Sscope) + break; + buf.reserve(cast(uint)(2 + 2 + 4 + 4 + 2 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + 4 + 2 + len + 1)); + buf.write16n(S_REGREL_V3); + buf.write32(cast(uint)(s.Soffset + base + cgstate.BPoff)); + buf.write32(typidx); + buf.write16n(I64 ? CV_AMD64_RBP : CV_REG_EBP); // relative to RBP/EBP + cv_writename(buf, id, len); + buf.writeByte(0); + break; + + case SC.bprel: + base = -cgstate.BPoff; + goto L1; + + case SC.fastpar: + if (s.Sfl != FL.reg) + { + base = cast(uint)cgstate.Fast.size; + goto L1; + } + goto L2; + + case SC.register: + if (s.Sfl != FL.reg) + goto case_auto; + goto case; + + case SC.pseudo: + case_register: + L2: + buf.reserve(cast(uint)(2 + 2 + 4 + 2 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + 2 + len + 1)); + buf.write16n(S_REGISTER_V3); + buf.write32(typidx); + buf.write16n(cv_regnum(s)); + cv_writename(buf, id, len); + buf.writeByte(0); + break; + + case SC.extern_: + break; + + case SC.static_: + case SC.locstat: + sr = S_LDATA_V3; + goto Ldata; + + case SC.global: + case SC.comdat: + case SC.comdef: + sr = S_GDATA_V3; + Ldata: + if (s.ty() & mTYthread) // full TLS support + sr = (sr == S_GDATA_V3) ? S_GTHREAD_V3 : S_LTHREAD_V3; + + buf.reserve(cast(uint)(2 + 2 + 4 + 6 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + 6 + len + 1)); + buf.write16n(sr); + buf.write32(typidx); + f1f.s = s; + f1f.offset = cast(uint)buf.length(); + currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); + buf.write32(0); + buf.write16n(0); + cv_writename(buf, id, len); + buf.writeByte(0); + break; + + default: + break; + } +} + +/******************************************* + * Put out a name for a user defined type. + * Input: + * id the name + * typidx and its type + */ +@trusted +void cv_udt_symbol(const(char)* id, idx_t typidx) +{ + auto buf = currentfuncdata.f1buf; + size_t len = strlen(id); + if (len > CV_MAX_SYMBOL_LENGTH) + len = CV_MAX_SYMBOL_LENGTH; + buf.reserve(cast(uint)(2 + 2 + 4 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + len + 1)); + buf.write16n(S_UDT_V3); + buf.write32(typidx); + cv_writename(buf, id, len); + buf.writeByte(0); +} + +/********************************************* + * Get Codeview register number for symbol s. + */ +int cv_regnum(Symbol* s) +{ + int reg = s.Sreglsw; + assert(s.Sfl == FL.reg); + if ((1 << reg) & XMMREGS) + return reg - XMM0 + 154; + switch (type_size(s.Stype)) + { + case 1: + if (reg < 4) + reg += 1; + else if (reg >= 4 && reg < 8) + reg += 324 - 4; + else + reg += 344 - 4; + break; + + case 2: + if (reg < 8) + reg += 9; + else + reg += 352 - 8; + break; + + case 4: + if (reg < 8) + reg += 17; + else + reg += 360 - 8; + break; + + case 8: + reg += 328; + break; + + default: + reg = 0; + break; + } + return reg; +} + +/*************************************** + * Put out a forward ref for structs, unions, and classes. + * Only put out the real definitions with toDebug(). + */ +@trusted +idx_t cv_fwdref(Symbol* s) +{ + assert(config.fulltypes == CV8); + struct_t* st = s.Sstruct; + uint leaf; + uint numidx; + if (st.Sflags & STRunion) + { + leaf = LF_UNION_V3; + numidx = 10; + } + else if (st.Sflags & STRclass) + { + leaf = LF_CLASS_V3; + numidx = 18; + } + else + { + leaf = LF_STRUCTURE_V3; + numidx = 18; + } + uint len = numidx + cv4_numericbytes(0); + int idlen = cast(int)strlen(s.Sident.ptr); + + if (idlen > CV_MAX_SYMBOL_LENGTH) + idlen = CV_MAX_SYMBOL_LENGTH; + + debtyp_t* d = debtyp_alloc(len + idlen + 1); + TOWORD(d.data.ptr, leaf); + TOWORD(d.data.ptr + 2, 0); // number of fields + TOWORD(d.data.ptr + 4, 0x80); // property + TOLONG(d.data.ptr + 6, 0); // field list + if (leaf == LF_CLASS_V3 || leaf == LF_STRUCTURE_V3) + { + TOLONG(d.data.ptr + 10, 0); // dList + TOLONG(d.data.ptr + 14, 0); // vshape + } + cv4_storenumeric(d.data.ptr + numidx, 0); + cv_namestring(d.data.ptr + len, s.Sident.ptr, idlen); + d.data.ptr[len + idlen] = 0; + idx_t typidx = cv_debtyp(d); + s.Stypidx = typidx; + + return typidx; +} + +/**************************************** + * Return type index for a darray of type E[] + * Input: + * t darray type + * etypidx type index for E + */ +@trusted +idx_t cv_darray(type* t, idx_t etypidx) +{ + /* Put out a struct: + * struct dArray { + * size_t length; + * E* ptr; + * } + */ + + type* tp = type_pointer(t.Tnext); + idx_t ptridx = cv4_typidx(tp); + type_free(tp); + + __gshared const ubyte[38] fl = + [ + 0x03, 0x12, // LF_FIELDLIST_V2 + 0x0d, 0x15, // LF_MEMBER_V3 + 0x03, 0x00, // attribute + 0x23, 0x00, 0x00, 0x00, // size_t + 0x00, 0x00, // offset + 'l', 'e', 'n', 'g', 't', 'h', 0x00, + 0xf3, 0xf2, 0xf1, // align to 4-byte including length word before data + 0x0d, 0x15, + 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, // etypidx + 0x08, 0x00, + 'p', 't', 'r', 0x00, + 0xf2, 0xf1, + ]; + + debtyp_t* f = debtyp_alloc(fl.sizeof); + memcpy(f.data.ptr,fl.ptr,fl.sizeof); + TOLONG(f.data.ptr + 6, I64 ? 0x23 : 0x22); // size_t + TOLONG(f.data.ptr + 26, ptridx); + TOWORD(f.data.ptr + 30, _tysize[TYnptr]); + idx_t fieldlist = cv_debtyp(f); + + const(char)* id; + switch (t.Tnext.Tty) + { + case mTYimmutable | TYchar: + id = "string"; + break; + + case mTYimmutable | TYwchar_t: + id = "wstring"; + break; + + case mTYimmutable | TYdchar: + id = "dstring"; + break; + + default: + id = t.Tident ? t.Tident : "dArray"; + break; + } + + int idlen = cast(int)strlen(id); + + if (idlen > CV_MAX_SYMBOL_LENGTH) + idlen = CV_MAX_SYMBOL_LENGTH; + + debtyp_t* d = debtyp_alloc(20 + idlen + 1); + TOWORD(d.data.ptr, LF_STRUCTURE_V3); + TOWORD(d.data.ptr + 2, 2); // count + TOWORD(d.data.ptr + 4, 0); // property + TOLONG(d.data.ptr + 6, fieldlist); + TOLONG(d.data.ptr + 10, 0); // dList + TOLONG(d.data.ptr + 14, 0); // vtshape + TOWORD(d.data.ptr + 18, 2 * _tysize[TYnptr]); // size + cv_namestring(d.data.ptr + 20, id, idlen); + d.data.ptr[20 + idlen] = 0; + + idx_t top = cv_numdebtypes(); + idx_t debidx = cv_debtyp(d); + if(top != cv_numdebtypes()) + cv_udt_symbol(id, debidx); + + return debidx; +} + +/**************************************** + * Return type index for a delegate + * Input: + * t delegate type + * functypidx type index for pointer to function + */ +@trusted +idx_t cv_ddelegate(type* t, idx_t functypidx) +{ + /* Put out a struct: + * struct dDelegate { + * void* ptr; + * function* funcptr; + * } + */ + + type* tv = type_fake(TYnptr); + tv.Tcount++; + idx_t pvidx = cv4_typidx(tv); + type_free(tv); + + type* tp = type_pointer(t.Tnext); + idx_t ptridx = cv4_typidx(tp); + type_free(tp); + + __gshared const ubyte[38] fl = + [ + 0x03, 0x12, // LF_FIELDLIST_V2 + 0x0d, 0x15, // LF_MEMBER_V3 + 0x03, 0x00, // attribute + 0x00, 0x00, 0x00, 0x00, // void* + 0x00, 0x00, // offset + 'p','t','r',0, // "ptr" + 0xf2, 0xf1, // align to 4-byte including length word before data + 0x0d, 0x15, + 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, // ptrtypidx + 0x08, 0x00, + 'f', 'u','n','c','p','t','r', 0, // "funcptr" + 0xf2, 0xf1, + ]; + + debtyp_t* f = debtyp_alloc(fl.sizeof); + memcpy(f.data.ptr,fl.ptr,fl.sizeof); + TOLONG(f.data.ptr + 6, pvidx); + TOLONG(f.data.ptr + 22, ptridx); + TOWORD(f.data.ptr + 26, _tysize[TYnptr]); + idx_t fieldlist = cv_debtyp(f); + + const(char)* id = "dDelegate"; + int idlen = cast(int)strlen(id); + if (idlen > CV_MAX_SYMBOL_LENGTH) + idlen = CV_MAX_SYMBOL_LENGTH; + + debtyp_t* d = debtyp_alloc(20 + idlen + 1); + TOWORD(d.data.ptr, LF_STRUCTURE_V3); + TOWORD(d.data.ptr + 2, 2); // count + TOWORD(d.data.ptr + 4, 0); // property + TOLONG(d.data.ptr + 6, fieldlist); + TOLONG(d.data.ptr + 10, 0); // dList + TOLONG(d.data.ptr + 14, 0); // vtshape + TOWORD(d.data.ptr + 18, 2 * _tysize[TYnptr]); // size + memcpy(d.data.ptr + 20, id, idlen); + d.data.ptr[20 + idlen] = 0; + + return cv_debtyp(d); +} + +/**************************************** + * Return type index for a aarray of type Value[Key] + * Input: + * t associative array type + * keyidx key type + * validx value type + */ +@trusted +idx_t cv_daarray(type* t, idx_t keyidx, idx_t validx) +{ + /* Put out a struct: + * struct dAssocArray { + * void* ptr; + * typedef key-type __key_t; + * typedef val-type __val_t; + * } + */ + + type* tv = type_fake(TYnptr); + tv.Tcount++; + idx_t pvidx = cv4_typidx(tv); + type_free(tv); + + __gshared const ubyte[50] fl = + [ + 0x03, 0x12, // LF_FIELDLIST_V2 + 0x0d, 0x15, // LF_MEMBER_V3 + 0x03, 0x00, // attribute + 0x00, 0x00, 0x00, 0x00, // void* + 0x00, 0x00, // offset + 'p','t','r',0, // "ptr" + 0xf2, 0xf1, // align to 4-byte including field id + // offset 18 + 0x10, 0x15, // LF_NESTTYPE_V3 + 0x00, 0x00, // padding + 0x00, 0x00, 0x00, 0x00, // key type + '_','_','k','e','y','_','t',0, // "__key_t" + // offset 34 + 0x10, 0x15, // LF_NESTTYPE_V3 + 0x00, 0x00, // padding + 0x00, 0x00, 0x00, 0x00, // value type + '_','_','v','a','l','_','t',0, // "__val_t" + ]; + + debtyp_t* f = debtyp_alloc(fl.sizeof); + memcpy(f.data.ptr,fl.ptr,fl.sizeof); + TOLONG(f.data.ptr + 6, pvidx); + TOLONG(f.data.ptr + 22, keyidx); + TOLONG(f.data.ptr + 38, validx); + idx_t fieldlist = cv_debtyp(f); + + const(char)* id = t.Tident ? t.Tident : "dAssocArray"; + int idlen = cast(int)strlen(id); + if (idlen > CV_MAX_SYMBOL_LENGTH) + idlen = CV_MAX_SYMBOL_LENGTH; + + debtyp_t* d = debtyp_alloc(20 + idlen + 1); + TOWORD(d.data.ptr, LF_STRUCTURE_V3); + TOWORD(d.data.ptr + 2, 1); // count + TOWORD(d.data.ptr + 4, 0); // property + TOLONG(d.data.ptr + 6, fieldlist); + TOLONG(d.data.ptr + 10, 0); // dList + TOLONG(d.data.ptr + 14, 0); // vtshape + TOWORD(d.data.ptr + 18, _tysize[TYnptr]); // size + memcpy(d.data.ptr + 20, id, idlen); + d.data.ptr[20 + idlen] = 0; + + return cv_debtyp(d); +} + +/* ======================================================================== */ +/* ID-stream type records (reuse the shared cgcv type pool, read-only) */ +/* ======================================================================== */ + +/* LF_STRING_ID: an interned string returning a type index. */ +@trusted +idx_t cv_string_id(const(char)* s) +{ + if (!s) s = ""; + debtyp_t* d = debtyp_alloc(2 + 4 + cv_stringbytes(s)); + TOWORD(d.data.ptr, LF_STRING_ID); + TOLONG(d.data.ptr + 2, 0); // substring list id + cv_namestring(d.data.ptr + 6, s); + return cv_debtyp(d); +} + +/* LF_BUILDINFO: cwd, build tool, source, pdb, args. */ +@trusted +idx_t cv_buildinfo() +{ + char[260] cwd = 0; + if (!getcwd(cwd.ptr, cwd.sizeof)) + cwd[0] = 0; + idx_t cwdId = cv_string_id(cwd.ptr); + idx_t toolId = cv_string_id("dmd"); + idx_t srcId = cv_string_id(""); + idx_t pdbId = cv_string_id(""); + idx_t argId = cv_string_id(""); + debtyp_t* d = debtyp_alloc(2 + 2 + 5 * 4); + TOWORD(d.data.ptr, LF_BUILDINFO); + TOWORD(d.data.ptr + 2, 5); // count + TOLONG(d.data.ptr + 4, cwdId); + TOLONG(d.data.ptr + 8, toolId); + TOLONG(d.data.ptr + 12, srcId); + TOLONG(d.data.ptr + 16, pdbId); + TOLONG(d.data.ptr + 20, argId); + return cv_debtyp(d); +} + +/* LF_FUNC_ID: ties a function type to its name for the ID stream. */ +@trusted +idx_t cv_func_id(const(char)* id, idx_t functype) +{ + debtyp_t* d = debtyp_alloc(2 + 4 + 4 + cv_stringbytes(id)); + TOWORD(d.data.ptr, LF_FUNC_ID); + TOLONG(d.data.ptr + 2, 0); // parent scope id + TOLONG(d.data.ptr + 6, functype); + cv_namestring(d.data.ptr + 10, id); + return cv_debtyp(d); +} + +/* LF_UDT_SRC_LINE: source location of a user-defined type. */ +@trusted +idx_t cv_udt_src_line(idx_t typidx, idx_t srcId, uint line) +{ + debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4); + TOWORD(d.data.ptr, LF_UDT_SRC_LINE); + TOLONG(d.data.ptr + 2, typidx); + TOLONG(d.data.ptr + 6, srcId); + TOLONG(d.data.ptr + 10, line); + return cv_debtyp(d); +} diff --git a/compiler/src/dmd/backend/cv4.d b/compiler/src/dmd/backend/cv4.d deleted file mode 100644 index 9e571753d8cc..000000000000 --- a/compiler/src/dmd/backend/cv4.d +++ /dev/null @@ -1,245 +0,0 @@ -/** - * CodeView 4 symbolic debug info declarations - * - * See "Microsoft Symbol and Type OMF" document - * - * Compiler implementation of the - * $(LINK2 https://www.dlang.org, D programming language). - * - * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/backend/cv4.d, backend/_cv4.d) - */ - -module dmd.backend.cv4; - -@safe: - -// Online documentation: https://dlang.org/phobos/dmd_backend_cv4.html - -enum OEM = 0x42; // Digital Mars OEM number (picked at random) - -// Symbol Indices -enum -{ - S_COMPILE = 1, - S_REGISTER = 2, - S_CONST = 3, - S_UDT = 4, - S_SSEARCH = 5, - S_END = 6, - S_SKIP = 7, - S_CVRESERVE = 8, - S_OBJNAME = 9, - S_ENDARG = 0x0A, - S_COBOLUDT = 0x0B, - S_MANYREG = 0x0C, - S_RETURN = 0x0D, - S_ENTRYTHIS = 0x0E, - S_TDBNAME = 0x0F, - - S_BPREL16 = 0x100, - S_LDATA16 = 0x101, - S_GDATA16 = 0x102, - S_PUB16 = 0x103, - S_LPROC16 = 0x104, - S_GPROC16 = 0x105, - S_THUNK16 = 0x106, - S_BLOCK16 = 0x107, - S_WITH16 = 0x108, - S_LABEL16 = 0x109, - S_CEXMODEL16 = 0x10A, - S_VFTPATH16 = 0x10B, - - S_BPREL32 = 0x200, - S_LDATA32 = 0x201, - S_GDATA32 = 0x202, - S_PUB32 = 0x203, - S_LPROC32 = 0x204, - S_GPROC32 = 0x205, - S_THUNK32 = 0x206, - S_BLOCK32 = 0x207, - S_WITH32 = 0x208, - S_LABEL32 = 0x209, - S_CEXMODEL32 = 0x20A, - S_VFTPATH32 = 0x20B, - - /************** Added Since CV4 *********************/ - - S_REGISTER_V2 = 0x1001, - S_CONSTANT_V2 = 0x1002, - S_UDT_V2 = 0x1003, - S_COBOLUDT_V2 = 0x1004, - S_MANYREG_V2 = 0x1005, - S_BPREL_V2 = 0x1006, - S_LDATA_V2 = 0x1007, - S_GDATA_V2 = 0x1008, - S_PUB_V2 = 0x1009, - S_LPROC_V2 = 0x100A, - S_GPROC_V2 = 0x100B, - S_VFTTABLE_V2 = 0x100C, - S_REGREL_V2 = 0x100D, - S_LTHREAD_V2 = 0x100E, - S_GTHREAD_V2 = 0x100F, - S_FUNCINFO_V2 = 0x1012, - S_COMPILAND_V2 = 0x1013, - - S_COMPILAND_V3 = 0x1101, - S_THUNK_V3 = 0x1102, - S_BLOCK_V3 = 0x1103, - S_LABEL_V3 = 0x1105, - S_REGISTER_V3 = 0x1106, - S_CONSTANT_V3 = 0x1107, - S_UDT_V3 = 0x1108, - S_BPREL_V3 = 0x110B, - S_LDATA_V3 = 0x110C, - S_GDATA_V3 = 0x110D, - S_PUB_V3 = 0x110E, - S_LPROC_V3 = 0x110F, - S_GPROC_V3 = 0x1110, - S_BPREL_XXXX_V3 = 0x1111, - S_MSTOOL_V3 = 0x1116, - S_PUB_FUNC1_V3 = 0x1125, - S_PUB_FUNC2_V3 = 0x1127, - S_SECTINFO_V3 = 0x1136, - S_SUBSECTINFO_V3 = 0x1137, - S_ENTRYPOINT_V3 = 0x1138, - S_SECUCOOKIE_V3 = 0x113A, - S_MSTOOLINFO_V3 = 0x113C, - S_MSTOOLENV_V3 = 0x113D, -} - -// Leaf Indices -enum -{ - LF_MODIFIER = 1, - LF_POINTER = 2, - LF_ARRAY = 3, - LF_CLASS = 4, - LF_STRUCTURE = 5, - LF_UNION = 6, - LF_ENUM = 7, - LF_PROCEDURE = 8, - LF_MFUNCTION = 9, - LF_VTSHAPE = 0x0A, - LF_COBOL0 = 0x0B, - LF_COBOL1 = 0x0C, - LF_BARRAY = 0x0D, - LF_LABEL = 0x0E, - LF_NULL = 0x0F, - LF_NOTTRAN = 0x10, - LF_DIMARRAY = 0x11, - LF_VFTPATH = 0x12, - LF_PRECOMP = 0x13, - LF_ENDPRECOMP = 0x14, - LF_OEM = 0x15, - LF_TYPESERVER = 0x16, - - // D extensions (not used, causes linker to fail) - LF_DYN_ARRAY = 0x17, - LF_ASSOC_ARRAY = 0x18, - LF_DELEGATE = 0x19, - - LF_SKIP = 0x200, - LF_ARGLIST = 0x201, - LF_DEFARG = 0x202, - LF_LIST = 0x203, - LF_FIELDLIST = 0x204, - LF_DERIVED = 0x205, - LF_BITFIELD = 0x206, - LF_METHODLIST = 0x207, - LF_DIMCONU = 0x208, - LF_DIMCONLU = 0x209, - LF_DIMVARU = 0x20A, - LF_DIMVARLU = 0x20B, - LF_REFSYM = 0x20C, - - LF_BCLASS = 0x400, - LF_VBCLASS = 0x401, - LF_IVBCLASS = 0x402, - LF_ENUMERATE = 0x403, - LF_FRIENDFCN = 0x404, - LF_INDEX = 0x405, - LF_MEMBER = 0x406, - LF_STMEMBER = 0x407, - LF_METHOD = 0x408, - LF_NESTTYPE = 0x409, - LF_VFUNCTAB = 0x40A, - LF_FRIENDCLS = 0x40B, - - LF_NUMERIC = 0x8000, - LF_CHAR = 0x8000, - LF_SHORT = 0x8001, - LF_USHORT = 0x8002, - LF_LONG = 0x8003, - LF_ULONG = 0x8004, - LF_REAL32 = 0x8005, - LF_REAL64 = 0x8006, - LF_REAL80 = 0x8007, - LF_REAL128 = 0x8008, - LF_QUADWORD = 0x8009, - LF_UQUADWORD = 0x800A, - LF_REAL48 = 0x800B, - - LF_COMPLEX32 = 0x800C, - LF_COMPLEX64 = 0x800D, - LF_COMPLEX80 = 0x800E, - LF_COMPLEX128 = 0x800F, - - LF_VARSTRING = 0x8010, - - /************** Added Since CV4 *********************/ - - LF_MODIFIER_V2 = 0x1001, - LF_POINTER_V2 = 0x1002, - LF_ARRAY_V2 = 0x1003, - LF_CLASS_V2 = 0x1004, - LF_STRUCTURE_V2 = 0x1005, - LF_UNION_V2 = 0x1006, - LF_ENUM_V2 = 0x1007, - LF_PROCEDURE_V2 = 0x1008, - LF_MFUNCTION_V2 = 0x1009, - LF_COBOL0_V2 = 0x100A, - LF_BARRAY_V2 = 0x100B, - LF_DIMARRAY_V2 = 0x100C, - LF_VFTPATH_V2 = 0x100D, - LF_PRECOMP_V2 = 0x100E, - LF_OEM_V2 = 0x100F, - - LF_SKIP_V2 = 0x1200, - LF_ARGLIST_V2 = 0x1201, - LF_DEFARG_V2 = 0x1202, - LF_FIELDLIST_V2 = 0x1203, - LF_DERIVED_V2 = 0x1204, - LF_BITFIELD_V2 = 0x1205, - LF_METHODLIST_V2 = 0x1206, - LF_DIMCONU_V2 = 0x1207, - LF_DIMCONLU_V2 = 0x1208, - LF_DIMVARU_V2 = 0x1209, - LF_DIMVARLU_V2 = 0x120A, - - LF_BCLASS_V2 = 0x1400, - LF_VBCLASS_V2 = 0x1401, - LF_IVBCLASS_V2 = 0x1402, - LF_FRIENDFCN_V2 = 0x1403, - LF_INDEX_V2 = 0x1404, - LF_MEMBER_V2 = 0x1405, - LF_STMEMBER_V2 = 0x1406, - LF_METHOD_V2 = 0x1407, - LF_NESTTYPE_V2 = 0x1408, - LF_VFUNCTAB_V2 = 0x1409, - LF_FRIENDCLS_V2 = 0x140A, - LF_ONEMETHOD_V2 = 0x140B, - LF_VFUNCOFF_V2 = 0x140C, - LF_NESTTYPEEX_V2 = 0x140D, - - LF_ENUMERATE_V3 = 0x1502, - LF_ARRAY_V3 = 0x1503, - LF_CLASS_V3 = 0x1504, - LF_STRUCTURE_V3 = 0x1505, - LF_UNION_V3 = 0x1506, - LF_ENUM_V3 = 0x1507, - LF_MEMBER_V3 = 0x150D, - LF_STMEMBER_V3 = 0x150E, - LF_METHOD_V3 = 0x150F, - LF_NESTTYPE_V3 = 0x1510, - LF_ONEMETHOD_V3 = 0x1511, -} diff --git a/compiler/src/dmd/backend/cv8.d b/compiler/src/dmd/backend/cv8.d deleted file mode 100644 index b9744ccb1fc0..000000000000 --- a/compiler/src/dmd/backend/cv8.d +++ /dev/null @@ -1,1207 +0,0 @@ -/** - * CodeView 8 symbolic debug info generation - * - * This module generates the `.debug$S` and `.debug$T` sections for Win64, - * which are the MS-Coff symbolic debug info and type debug info sections. - * - * Compiler implementation of the - * $(LINK2 https://www.dlang.org, D programming language). - * - * Copyright: Copyright (C) 2012-2026 by The D Language Foundation, All Rights Reserved - * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) - * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) - * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/backend/cv8.d, backend/cv8.d) - * Documentation: https://dlang.org/phobos/dmd_backend_cv8.html - * Coverage: https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/backend/cv8.d - */ - -module dmd.backend.cv8; - -import core.stdc.stdio; -import core.stdc.stdlib; -import core.stdc.string; -extern (C) nothrow char* getcwd(char*, size_t); - -import dmd.backend.cc; -import dmd.backend.cdef; -import dmd.backend.cgcv; -import dmd.backend.code; -import dmd.backend.x86.code_x86; -import dmd.backend.cv4; -import dmd.backend.mem; -import dmd.backend.el; -import dmd.backend.mscoffobj; -import dmd.backend.obj; -import dmd.backend.oper; -import dmd.backend.pdb : pdb_udt; -import dmd.common.outbuffer; -import dmd.backend.rtlsym; -import dmd.backend.symbol : globsym; -import dmd.backend.ty; -import dmd.backend.type; -import dmd.backend.dvarstats; - - -nothrow: -@safe: - -static if (1) -{ - - -// Determine if this Symbol is stored in a COMDAT -@trusted -private bool symbol_iscomdat4(Symbol* s) -{ - return s.Sclass == SC.comdat || - config.flags2 & CFG2comdat && s.Sclass == SC.inline || - config.flags4 & CFG4allcomdat && s.Sclass == SC.global; -} - - -// if symbols get longer than 65500 bytes, the linker reports corrupt debug info or exits with -// 'fatal error LNK1318: Unexpected PDB error; RPC (23) '(0x000006BA)' -enum CV8_MAX_SYMBOL_LENGTH = 0xffd8; - -// The "F1" section, which is the symbols -private __gshared OutBuffer* F1_buf; - -// The "F2" section, which is the line numbers -private __gshared OutBuffer* F2_buf; - -// The "F3" section, which is global and a string table of source file names. -private __gshared OutBuffer* F3_buf; - -// The "F4" section, which is global and a list of info about source files. -private __gshared OutBuffer* F4_buf; - -/* Fixups that go into F1 section - */ -struct F1_Fixups -{ - Symbol* s; - uint offset; - uint value; -} - -private __gshared OutBuffer* F1fixup; // array of F1_Fixups - -/* Struct in which to collect per-function data, for later emission - * into .debug$S. - */ -struct FuncData -{ - Symbol* sfunc; - uint section_length; - const(char)* srcfilename; - uint srcfileoff; - uint linepairstart; // starting byte index of offset/line pairs in linebuf[] - uint linepairbytes; // number of bytes for offset/line pairs - uint linepairsegment; // starting byte index of filename segment for offset/line pairs - OutBuffer* f1buf; - OutBuffer* f1fixup; -} - -__gshared FuncData currentfuncdata; - -private __gshared OutBuffer* funcdata; // array of FuncData's - -private __gshared OutBuffer* linepair; // array of offset/line pairs - -private @trusted -void cv8_writename(OutBuffer* buf, const(char)* name, size_t len) -{ - if(config.flags2 & CFG2gms) - { - const(char)* start = name; - const(char)* cur = strchr(start, '.'); - const(char)* end = start + len; - while(cur != null) - { - if(cur >= end) - { - buf.writen(start, end - start); - return; - } - buf.writen(start, cur - start); - buf.writeByte('@'); - start = cur + 1; - if(start >= end) - return; - cur = strchr(start, '.'); - } - buf.writen(start, end - start); - } - else - buf.writen(name, len); -} - -/************************************************ - * Called at the start of an object file generation. - * One source file can generate multiple object files; this starts an object file. - * Input: - * filename source file name - */ -@trusted -void cv8_initfile(const(char)* filename) -{ - //printf("cv8_initfile()\n"); - - // Recycle buffers; much faster than delete/renew - - if (!F1_buf) - { - __gshared OutBuffer f1buf; - f1buf.reserve(1024); - F1_buf = &f1buf; - } - F1_buf.reset(); - - if (!F1fixup) - { - __gshared OutBuffer f1fixupbuf; - f1fixupbuf.reserve(1024); - F1fixup = &f1fixupbuf; - } - F1fixup.reset(); - - if (!F2_buf) - { - __gshared OutBuffer f2buf; - f2buf.reserve(1024); - F2_buf = &f2buf; - } - F2_buf.reset(); - - if (!F3_buf) - { - __gshared OutBuffer f3buf; - f3buf.reserve(1024); - F3_buf = &f3buf; - } - F3_buf.reset(); - F3_buf.writeByte(0); // first "filename" - - if (!F4_buf) - { - __gshared OutBuffer f4buf; - f4buf.reserve(1024); - F4_buf = &f4buf; - } - F4_buf.reset(); - - if (!funcdata) - { - __gshared OutBuffer funcdatabuf; - funcdatabuf.reserve(1024); - funcdata = &funcdatabuf; - } - funcdata.reset(); - - if (!linepair) - { - __gshared OutBuffer linepairbuf; - linepairbuf.reserve(1024); - linepair = &linepairbuf; - } - linepair.reset(); - - memset(¤tfuncdata, 0, currentfuncdata.sizeof); - currentfuncdata.f1buf = F1_buf; - currentfuncdata.f1fixup = F1fixup; - - cv_init(); -} - -@trusted -void cv8_termfile(const(char)[] objfilename) -{ - //printf("cv8_termfile()\n"); - - /* Write out the debug info sections. - */ - - int seg = MsCoffObj_seg_debugS(); - - uint value = 4; - objmod.bytes(seg,0,(cast(void*)&value)[0 .. 4]); - - /* Start with starting symbol in separate "F1" section - */ - auto buf = OutBuffer(1024); - size_t len = objfilename.length; - buf.write16(cast(int)(2 + 4 + len + 1)); - buf.write16(S_COMPILAND_V3); - buf.write32(0); - buf.write(objfilename.ptr, cast(uint)(len + 1)); - - // write S_COMPILE record - buf.write16(2 + 1 + 1 + 2 + 1 + VERSION.length + 1); - buf.write16(S_COMPILE); - buf.writeByte(I64 ? 0xD0 : 6); // target machine AMD64 or x86 (Pentium II) - buf.writeByte(config.flags2 & CFG2gms ? 0 : 'D'); // language index (C/C++/D) - buf.write16(0x800 | (config.inline8087 ? 0 : (1<<3))); // 32-bit, float package - buf.writeByte(VERSION.length + 1); - buf.writeByte('Z'); - buf.write(VERSION.ptr, VERSION.length); - - cv8_writesection(seg, 0xF1, &buf); - - // Write out "F2" sections - uint length = cast(uint)funcdata.length(); - ubyte* p = funcdata.buf; - for (uint u = 0; u < length; u += FuncData.sizeof) - { FuncData* fd = cast(FuncData*)(p + u); - - F2_buf.reset(); - - F2_buf.write32(cast(uint)fd.sfunc.Soffset); - F2_buf.write32(0); - F2_buf.write32(fd.section_length); - F2_buf.write(linepair.buf + fd.linepairstart, fd.linepairbytes); - - int f2seg = seg; - if (symbol_iscomdat4(fd.sfunc)) - { - f2seg = MsCoffObj_seg_debugS_comdat(fd.sfunc); - objmod.bytes(f2seg, 0, (cast(void*)&value)[0..4]); - } - - uint offset = cast(uint)SegData[f2seg].SDoffset + 8; - cv8_writesection(f2seg, 0xF2, F2_buf); - objmod.reftoident(f2seg, offset, fd.sfunc, 0, CF.seg | CF.off); - - if (f2seg != seg && fd.f1buf.length()) - { - // Write out "F1" section - const uint f1offset = cast(uint)SegData[f2seg].SDoffset; - cv8_writesection(f2seg, 0xF1, fd.f1buf); - - // Fixups for "F1" section - const uint fixupLength = cast(uint)fd.f1fixup.length(); - ubyte* pfixup = fd.f1fixup.buf; - for (uint v = 0; v < fixupLength; v += F1_Fixups.sizeof) - { F1_Fixups* f = cast(F1_Fixups*)(pfixup + v); - - objmod.reftoident(f2seg, f1offset + 8 + f.offset, f.s, f.value, CF.seg | CF.off); - } - } - } - - // Write out "F3" section - if (F3_buf.length() > 1) - cv8_writesection(seg, 0xF3, F3_buf); - - // Write out "F4" section - if (F4_buf.length() > 0) - cv8_writesection(seg, 0xF4, F4_buf); - - if (F1_buf.length()) - { - // Write out "F1" section - uint f1offset = cast(uint)SegData[seg].SDoffset; - cv8_writesection(seg, 0xF1, F1_buf); - - // Fixups for "F1" section - length = cast(uint)F1fixup.length(); - p = F1fixup.buf; - for (uint u = 0; u < length; u += F1_Fixups.sizeof) - { F1_Fixups* f = cast(F1_Fixups*)(p + u); - - objmod.reftoident(seg, f1offset + 8 + f.offset, f.s, f.value, CF.seg | CF.off); - } - } - - // Write out .debug$T section - cv_term(); -} - -/************************************************ - * Called at the start of a module. - * Note that there can be multiple modules in one object file. - * cv8_initfile() must be called first. - */ -void cv8_initmodule(const(char)* filename, const(char)* modulename) -{ - //printf("cv8_initmodule(filename = %s, modulename = %s)\n", filename, modulename); -} - -@trusted -void cv8_termmodule() -{ - //printf("cv8_termmodule()\n"); - assert(config.objfmt == OBJ_MSCOFF); -} - -/****************************************** - * Called at the start of a function. - */ -@trusted -void cv8_func_start(Symbol* sfunc) -{ - //printf("cv8_func_start(%s)\n", sfunc.Sident); - currentfuncdata.sfunc = sfunc; - currentfuncdata.section_length = 0; - currentfuncdata.srcfilename = null; - currentfuncdata.linepairstart += currentfuncdata.linepairbytes; - currentfuncdata.linepairbytes = 0; - currentfuncdata.f1buf = F1_buf; - currentfuncdata.f1fixup = F1fixup; - if (symbol_iscomdat4(sfunc)) - { - // This leaks memory - currentfuncdata.f1buf = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); - currentfuncdata.f1buf.reserve(128); - currentfuncdata.f1fixup = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); - currentfuncdata.f1fixup.reserve(128); - } - - varStats_startFunction(); -} - -@trusted -void cv8_func_term(Symbol* sfunc) -{ - //printf("cv8_func_term(%s)\n", sfunc.Sident); - - assert(currentfuncdata.sfunc == sfunc); - currentfuncdata.section_length = cast(uint)sfunc.Ssize; - - funcdata.write(¤tfuncdata, currentfuncdata.sizeof); - - // Write function symbol - assert(tyfunc(sfunc.ty())); - idx_t typidx; - func_t* fn = sfunc.Sfunc; - if(fn.Fclass) - { - // generate member function type info - // it would be nicer if this could be in cv4_typidx, but the function info is not available there - uint nparam; - ubyte call = cv4_callconv(sfunc.Stype); - idx_t paramidx = cv4_arglist(sfunc.Stype,&nparam); - uint next = cv4_typidx(sfunc.Stype.Tnext); - - type* classtype = cast(type*)fn.Fclass; - uint classidx = cv4_typidx(classtype); - type* tp = type_allocn(TYnptr, classtype); - uint thisidx = cv4_typidx(tp); // TODO - debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4 + 1 + 1 + 2 + 4 + 4); - TOWORD(d.data.ptr,LF_MFUNCTION_V2); - TOLONG(d.data.ptr + 2,next); // return type - TOLONG(d.data.ptr + 6,classidx); // class type - TOLONG(d.data.ptr + 10,thisidx); // this type - d.data.ptr[14] = call; - d.data.ptr[15] = 0; // reserved - TOWORD(d.data.ptr + 16,nparam); - TOLONG(d.data.ptr + 18,paramidx); - TOLONG(d.data.ptr + 22,0); // this adjust - typidx = cv_debtyp(d); - } - else - typidx = cv_typidx(sfunc.Stype); - - const(char)* id = sfunc.prettyIdent ? sfunc.prettyIdent : prettyident(sfunc); - - size_t len = strlen(id); - if(len > CV8_MAX_SYMBOL_LENGTH) - len = CV8_MAX_SYMBOL_LENGTH; - /* - * 2 length (not including these 2 bytes) - * 2 S_GPROC_V3 - * 4 parent - * 4 pend - * 4 pnext - * 4 size of function - * 4 size of function prolog - * 4 offset to function epilog - * 4 type index - * 6 seg:offset of function start - * 1 flags - * n 0 terminated name string - */ - auto buf = currentfuncdata.f1buf; - buf.reserve(cast(uint)(2 + 2 + 4 * 7 + 6 + 1 + len + 1)); - buf.write16n(cast(int)(2 + 4 * 7 + 6 + 1 + len + 1)); - buf.write16n(sfunc.Sclass == SC.static_ ? S_LPROC_V3 : S_GPROC_V3); - buf.write32(0); // parent - buf.write32(0); // pend - buf.write32(0); // pnext - buf.write32(cast(uint)currentfuncdata.section_length); // size of function - buf.write32(cast(uint)cgstate.startoffset); // size of prolog - buf.write32(cast(uint)cgstate.retoffset); // offset to epilog - buf.write32(typidx); - - F1_Fixups f1f; - f1f.s = sfunc; - f1f.offset = cast(uint)buf.length(); - f1f.value = 0; - currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); - buf.write32(0); - buf.write16n(0); - - buf.writeByte(0); - buf.writen(id, len); - buf.writeByte(0); - - struct cv8 - { - nothrow: - // record for CV record S_BLOCK_V3 - struct block_v3_data - { - ushort len; - ushort id; - uint pParent; - uint pEnd; - uint length; - uint offset; - ushort seg; - ubyte[1] name; - } - - static void endArgs() - { - auto buf = currentfuncdata.f1buf; - buf.write16(2); - buf.write16(S_ENDARG); - } - static void beginBlock(int offset, int length) - { - auto buf = currentfuncdata.f1buf; - uint soffset = cast(uint)buf.length(); - // parent and end to be filled by linker - block_v3_data block32 = { block_v3_data.sizeof - 2, S_BLOCK_V3, 0, 0, length, offset, 0, [ 0 ] }; - buf.write(&block32, block32.sizeof); - size_t offOffset = cast(char*)&block32.offset - cast(char*)&block32; - - F1_Fixups f1f; - f1f.s = currentfuncdata.sfunc; - f1f.offset = cast(uint)(soffset + offOffset); - f1f.value = offset; - currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); - } - static void endBlock() - { - auto buf = currentfuncdata.f1buf; - buf.write16(2); - buf.write16(S_END); - } - } - varStats_writeSymbolTable(sfunc, globsym, &cv8_outsym, &cv8.endArgs, &cv8.beginBlock, &cv8.endBlock); - - /* Put out function return record S_RETURN - * (VC doesn't, so we won't bother, either.) - */ - - // Write function end symbol - buf.write16(2); - buf.write16(S_END); - - currentfuncdata.f1buf = F1_buf; - currentfuncdata.f1fixup = F1fixup; -} - -/********************************************** - */ - -@trusted -void cv8_linnum(Srcpos srcpos, uint offset) -{ - const sfilename = srcpos.Sfilename; - //printf("cv8_linnum(file = %s, line = %d, offset = x%x)\n", sfilename, cast(int)srcpos.Slinnum, cast(uint)offset); - - if (!sfilename) - return; - - varStats_recordLineOffset(srcpos, offset); - - __gshared uint lastoffset; - __gshared uint lastlinnum; - - if (!currentfuncdata.srcfilename || - (currentfuncdata.srcfilename != sfilename && strcmp(currentfuncdata.srcfilename, sfilename))) - { - currentfuncdata.srcfilename = sfilename; - uint srcfileoff = cv8_addfile(sfilename); - - // new file segment - currentfuncdata.linepairsegment = currentfuncdata.linepairstart + currentfuncdata.linepairbytes; - - linepair.write32(srcfileoff); - linepair.write32(0); // reserve space for length information - linepair.write32(12); - currentfuncdata.linepairbytes += 12; - } - else if (offset <= lastoffset || srcpos.Slinnum == lastlinnum) - return; // avoid multiple entries for the same offset - - lastoffset = offset; - lastlinnum = srcpos.Slinnum; - linepair.write32(offset); - linepair.write32(srcpos.Slinnum | 0x80000000); // mark as statement, not expression - - currentfuncdata.linepairbytes += 8; - - // update segment length - auto segmentbytes = currentfuncdata.linepairstart + currentfuncdata.linepairbytes - currentfuncdata.linepairsegment; - auto segmentheader = cast(uint*)(linepair.buf + currentfuncdata.linepairsegment); - segmentheader[1] = (segmentbytes - 12) / 8; - segmentheader[2] = segmentbytes; -} - -/********************************************** - * Add source file, if it isn't already there. - * Return offset into F4. - */ - -@trusted -uint cv8_addfile(const(char)* filename) -{ - //printf("cv8_addfile('%s')\n", filename); - - /* The algorithms here use a linear search. This is acceptable only - * because we expect only 1 or 2 files to appear. - * Unlike C, there won't be lots of .h source files to be accounted for. - */ - - uint length = cast(uint)F3_buf.length(); - ubyte* p = F3_buf.buf; - size_t len = strlen(filename); - - // ensure the filename is absolute to help the debugger to find the source - // without having to know the working directory during compilation - __gshared char[260] cwd = 0; - __gshared uint cwdlen; - bool abs = (*filename == '\\') || - (*filename == '/') || - (*filename && filename[1] == ':'); - - if (!abs && cwd[0] == 0) - { - if (getcwd(cwd.ptr, cwd.sizeof)) - { - cwdlen = cast(uint)strlen(cwd.ptr); - if(cwd[cwdlen - 1] != '\\' && cwd[cwdlen - 1] != '/') - cwd[cwdlen++] = '\\'; - } - } - uint off = 1; - while (off + len < length) - { - if (!abs) - { - if (memcmp(p + off, cwd.ptr, cwdlen) == 0 && - memcmp(p + off + cwdlen, filename, len + 1) == 0) - goto L1; - } - else if (memcmp(p + off, filename, len + 1) == 0) - { // Already there - //printf("\talready there at %x\n", off); - goto L1; - } - off += strlen(cast(const(char)* )(p + off)) + 1; - } - off = length; - // Add it - if(!abs) - F3_buf.write(cwd.ptr, cwdlen); - F3_buf.write(filename, cast(uint)(len + 1)); - -L1: - // off is the offset of the filename in F3. - // Find it in F4. - - length = cast(uint)F4_buf.length(); - p = F4_buf.buf; - - uint u = 0; - while (u + 8 <= length) - { - //printf("\t%x\n", *cast(uint*)(p + u)); - if (off == *cast(uint*)(p + u)) - { - //printf("\tfound %x\n", u); - return u; - } - u += 4; - ushort type = *cast(ushort*)(p + u); - u += 2; - if (type == 0x0110) - u += 16; // truncated blake3 hash - u += 2; - } - - // Not there. Add it. - F4_buf.write32(off); - - /* Write 10 01 [blake3 hash] - * or - * 00 00 - */ - F4_buf.write16(0); - - // 2 bytes of pad - F4_buf.write16(0); - - //printf("\tadded %x\n", length); - return length; -} - -private @trusted -void cv8_writesection(int seg, uint type, OutBuffer* buf) -{ - /* Write out as: - * bytes desc - * -------+---- - * 4 type - * 4 length - * length data - * pad pad to 4 byte boundary - */ - uint off = cast(uint)SegData[seg].SDoffset; - objmod.bytes(seg,off,(cast(void*)&type)[0 .. 4]); - uint length = cast(uint)buf.length(); - objmod.bytes(seg,off+4,(cast(void*)&length)[0 .. 4]); - objmod.bytes(seg,off+8,buf.buf[0 .. length]); - // Align to 4 - uint pad = ((length + 3) & ~3) - length; - objmod.lidata(seg,off+8+length,pad); -} - -@trusted -void cv8_outsym(Symbol* s) -{ - //printf("cv8_outsym(s = '%s')\n", s.Sident); - //type_print(s.Stype); - //symbol_print(s); - if (s.Sflags & SFLnodebug) - return; - - idx_t typidx = cv_typidx(s.Stype); - //printf("typidx = %x\n", typidx); - const(char)* id = s.prettyIdent ? s.prettyIdent : prettyident(s); - size_t len = strlen(id); - - if(len > CV8_MAX_SYMBOL_LENGTH) - len = CV8_MAX_SYMBOL_LENGTH; - - F1_Fixups f1f; - f1f.value = 0; - auto buf = currentfuncdata.f1buf; - - uint sr; - uint base; - switch (s.Sclass) - { - case SC.parameter: - case SC.regpar: - case SC.shadowreg: - if (s.Sfl == FL.reg) - { - s.Sfl = FL.para; - cv8_outsym(s); - s.Sfl = FL.reg; - goto case_register; - } - base = cast(uint)(cgstate.Para.size - cgstate.BPoff); // cancel out add of BPoff - goto L1; - - case SC.auto_: - if (s.Sfl == FL.reg) - goto case_register; - case_auto: - base = cast(uint)cgstate.Auto.size; - L1: - if (s.Sscope) // local variables moved into the closure cannot be emitted directly - break; -static if (1) -{ - // Register relative addressing - buf.reserve(cast(uint)(2 + 2 + 4 + 4 + 2 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + 4 + 2 + len + 1)); - buf.write16n(0x1111); - buf.write32(cast(uint)(s.Soffset + base + cgstate.BPoff)); - buf.write32(typidx); - buf.write16n(I64 ? 334 : 22); // relative to RBP/EBP - cv8_writename(buf, id, len); - buf.writeByte(0); -} -else -{ - // This is supposed to work, implicit BP relative addressing, but it does not - buf.reserve(2 + 2 + 4 + 4 + len + 1); - buf.write16n( 2 + 4 + 4 + len + 1); - buf.write16n(S_BPREL_V3); - buf.write32(s.Soffset + base + cgstate.BPoff); - buf.write32(typidx); - cv8_writename(buf, id, len); - buf.writeByte(0); -} - break; - - case SC.bprel: - base = -cgstate.BPoff; - goto L1; - - case SC.fastpar: - if (s.Sfl != FL.reg) - { base = cast(uint)cgstate.Fast.size; - goto L1; - } - goto L2; - - case SC.register: - if (s.Sfl != FL.reg) - goto case_auto; - goto case; - - case SC.pseudo: - case_register: - L2: - buf.reserve(cast(uint)(2 + 2 + 4 + 2 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + 2 + len + 1)); - buf.write16n(S_REGISTER_V3); - buf.write32(typidx); - buf.write16n(cv8_regnum(s)); - cv8_writename(buf, id, len); - buf.writeByte(0); - break; - - case SC.extern_: - break; - - case SC.static_: - case SC.locstat: - sr = S_LDATA_V3; - goto Ldata; - - case SC.global: - case SC.comdat: - case SC.comdef: - sr = S_GDATA_V3; - Ldata: - /* - * 2 length (not including these 2 bytes) - * 2 S_GDATA_V2 - * 4 typidx - * 6 ref to symbol - * n 0 terminated name string - */ - if (s.ty() & mTYthread) // thread local storage - sr = (sr == S_GDATA_V3) ? 0x1113 : 0x1112; - - buf.reserve(cast(uint)(2 + 2 + 4 + 6 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + 6 + len + 1)); - buf.write16n(sr); - buf.write32(typidx); - - f1f.s = s; - f1f.offset = cast(uint)buf.length(); - F1fixup.write(&f1f, f1f.sizeof); - buf.write32(0); - buf.write16n(0); - - cv8_writename(buf, id, len); - buf.writeByte(0); - break; - - default: - break; - } -} - - -/******************************************* - * Put out a name for a user defined type. - * Input: - * id the name - * typidx and its type - */ -@trusted -void cv8_udt(const(char)* id, idx_t typidx) -{ - //printf("cv8_udt('%s', %x)\n", id, typidx); - if (config.newpdb) - { - pdb_udt(id, typidx); - return; - } - auto buf = currentfuncdata.f1buf; - size_t len = strlen(id); - - if (len > CV8_MAX_SYMBOL_LENGTH) - len = CV8_MAX_SYMBOL_LENGTH; - buf.reserve(cast(uint)(2 + 2 + 4 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + len + 1)); - buf.write16n(S_UDT_V3); - buf.write32(typidx); - cv8_writename(buf, id, len); - buf.writeByte(0); -} - -/********************************************* - * Get Codeview register number for symbol s. - */ -int cv8_regnum(Symbol* s) -{ - int reg = s.Sreglsw; - assert(s.Sfl == FL.reg); - if ((1 << reg) & XMMREGS) - return reg - XMM0 + 154; - switch (type_size(s.Stype)) - { - case 1: - if (reg < 4) - reg += 1; - else if (reg >= 4 && reg < 8) - reg += 324 - 4; - else - reg += 344 - 4; - break; - - case 2: - if (reg < 8) - reg += 9; - else - reg += 352 - 8; - break; - - case 4: - if (reg < 8) - reg += 17; - else - reg += 360 - 8; - break; - - case 8: - reg += 328; - break; - - default: - reg = 0; - break; - } - return reg; -} - -/*************************************** - * Put out a forward ref for structs, unions, and classes. - * Only put out the real definitions with toDebug(). - */ -@trusted -idx_t cv8_fwdref(Symbol* s) -{ - assert(config.fulltypes == CV8); -// if (s.Stypidx && !global.params.multiobj) -// return s.Stypidx; - struct_t* st = s.Sstruct; - uint leaf; - uint numidx; - if (st.Sflags & STRunion) - { - leaf = LF_UNION_V3; - numidx = 10; - } - else if (st.Sflags & STRclass) - { - leaf = LF_CLASS_V3; - numidx = 18; - } - else - { - leaf = LF_STRUCTURE_V3; - numidx = 18; - } - uint len = numidx + cv4_numericbytes(0); - int idlen = cast(int)strlen(s.Sident.ptr); - - if (idlen > CV8_MAX_SYMBOL_LENGTH) - idlen = CV8_MAX_SYMBOL_LENGTH; - - debtyp_t* d = debtyp_alloc(len + idlen + 1); - TOWORD(d.data.ptr, leaf); - TOWORD(d.data.ptr + 2, 0); // number of fields - TOWORD(d.data.ptr + 4, 0x80); // property - TOLONG(d.data.ptr + 6, 0); // field list - if (leaf == LF_CLASS_V3 || leaf == LF_STRUCTURE_V3) - { - TOLONG(d.data.ptr + 10, 0); // dList - TOLONG(d.data.ptr + 14, 0); // vshape - } - cv4_storenumeric(d.data.ptr + numidx, 0); - cv_namestring(d.data.ptr + len, s.Sident.ptr, idlen); - d.data.ptr[len + idlen] = 0; - idx_t typidx = cv_debtyp(d); - s.Stypidx = typidx; - - return typidx; -} - -/**************************************** - * Return type index for a darray of type E[] - * Input: - * t darray type - * etypidx type index for E - */ -@trusted -idx_t cv8_darray(type* t, idx_t etypidx) -{ - //printf("cv8_darray(etypidx = %x)\n", etypidx); - /* Put out a struct: - * struct dArray { - * size_t length; - * E* ptr; - * } - */ - -static if (0) -{ - d = debtyp_alloc(18); - TOWORD(d.data.ptr, 0x100F); - TOWORD(d.data.ptr + 2, OEM); - TOWORD(d.data.ptr + 4, 1); // 1 = dynamic array - TOLONG(d.data.ptr + 6, 2); // count of type indices to follow - TOLONG(d.data.ptr + 10, 0x23); // index type, T_UQUAD - TOLONG(d.data.ptr + 14, next); // element type - return cv_debtyp(d); -} - - type* tp = type_pointer(t.Tnext); - idx_t ptridx = cv4_typidx(tp); - type_free(tp); - - __gshared const ubyte[38] fl = - [ - 0x03, 0x12, // LF_FIELDLIST_V2 - 0x0d, 0x15, // LF_MEMBER_V3 - 0x03, 0x00, // attribute - 0x23, 0x00, 0x00, 0x00, // size_t - 0x00, 0x00, // offset - 'l', 'e', 'n', 'g', 't', 'h', 0x00, - 0xf3, 0xf2, 0xf1, // align to 4-byte including length word before data - 0x0d, 0x15, - 0x03, 0x00, - 0x00, 0x00, 0x00, 0x00, // etypidx - 0x08, 0x00, - 'p', 't', 'r', 0x00, - 0xf2, 0xf1, - ]; - - debtyp_t* f = debtyp_alloc(fl.sizeof); - memcpy(f.data.ptr,fl.ptr,fl.sizeof); - TOLONG(f.data.ptr + 6, I64 ? 0x23 : 0x22); // size_t - TOLONG(f.data.ptr + 26, ptridx); - TOWORD(f.data.ptr + 30, _tysize[TYnptr]); - idx_t fieldlist = cv_debtyp(f); - - const(char)* id; - switch (t.Tnext.Tty) - { - case mTYimmutable | TYchar: - id = "string"; - break; - - case mTYimmutable | TYwchar_t: - id = "wstring"; - break; - - case mTYimmutable | TYdchar: - id = "dstring"; - break; - - default: - id = t.Tident ? t.Tident : "dArray"; - break; - } - - int idlen = cast(int)strlen(id); - - if (idlen > CV8_MAX_SYMBOL_LENGTH) - idlen = CV8_MAX_SYMBOL_LENGTH; - - debtyp_t* d = debtyp_alloc(20 + idlen + 1); - TOWORD(d.data.ptr, LF_STRUCTURE_V3); - TOWORD(d.data.ptr + 2, 2); // count - TOWORD(d.data.ptr + 4, 0); // property - TOLONG(d.data.ptr + 6, fieldlist); - TOLONG(d.data.ptr + 10, 0); // dList - TOLONG(d.data.ptr + 14, 0); // vtshape - TOWORD(d.data.ptr + 18, 2 * _tysize[TYnptr]); // size - cv_namestring(d.data.ptr + 20, id, idlen); - d.data.ptr[20 + idlen] = 0; - - idx_t top = cv_numdebtypes(); - idx_t debidx = cv_debtyp(d); - if(top != cv_numdebtypes()) - cv8_udt(id, debidx); - - return debidx; -} - -/**************************************** - * Return type index for a delegate - * Input: - * t delegate type - * functypidx type index for pointer to function - */ -@trusted -idx_t cv8_ddelegate(type* t, idx_t functypidx) -{ - //printf("cv8_ddelegate(functypidx = %x)\n", functypidx); - /* Put out a struct: - * struct dDelegate { - * void* ptr; - * function* funcptr; - * } - */ - - type* tv = type_fake(TYnptr); - tv.Tcount++; - idx_t pvidx = cv4_typidx(tv); - type_free(tv); - - type* tp = type_pointer(t.Tnext); - idx_t ptridx = cv4_typidx(tp); - type_free(tp); - -static if (0) -{ - debtyp_t* d = debtyp_alloc(18); - TOWORD(d.data.ptr, 0x100F); - TOWORD(d.data.ptr + 2, OEM); - TOWORD(d.data.ptr + 4, 3); // 3 = delegate - TOLONG(d.data.ptr + 6, 2); // count of type indices to follow - TOLONG(d.data.ptr + 10, key); // void* type - TOLONG(d.data.ptr + 14, functypidx); // function type -} -else -{ - __gshared const ubyte[38] fl = - [ - 0x03, 0x12, // LF_FIELDLIST_V2 - 0x0d, 0x15, // LF_MEMBER_V3 - 0x03, 0x00, // attribute - 0x00, 0x00, 0x00, 0x00, // void* - 0x00, 0x00, // offset - 'p','t','r',0, // "ptr" - 0xf2, 0xf1, // align to 4-byte including length word before data - 0x0d, 0x15, - 0x03, 0x00, - 0x00, 0x00, 0x00, 0x00, // ptrtypidx - 0x08, 0x00, - 'f', 'u','n','c','p','t','r', 0, // "funcptr" - 0xf2, 0xf1, - ]; - - debtyp_t* f = debtyp_alloc(fl.sizeof); - memcpy(f.data.ptr,fl.ptr,fl.sizeof); - TOLONG(f.data.ptr + 6, pvidx); - TOLONG(f.data.ptr + 22, ptridx); - TOWORD(f.data.ptr + 26, _tysize[TYnptr]); - idx_t fieldlist = cv_debtyp(f); - - const(char)* id = "dDelegate"; - int idlen = cast(int)strlen(id); - if (idlen > CV8_MAX_SYMBOL_LENGTH) - idlen = CV8_MAX_SYMBOL_LENGTH; - - debtyp_t* d = debtyp_alloc(20 + idlen + 1); - TOWORD(d.data.ptr, LF_STRUCTURE_V3); - TOWORD(d.data.ptr + 2, 2); // count - TOWORD(d.data.ptr + 4, 0); // property - TOLONG(d.data.ptr + 6, fieldlist); - TOLONG(d.data.ptr + 10, 0); // dList - TOLONG(d.data.ptr + 14, 0); // vtshape - TOWORD(d.data.ptr + 18, 2 * _tysize[TYnptr]); // size - memcpy(d.data.ptr + 20, id, idlen); - d.data.ptr[20 + idlen] = 0; -} - return cv_debtyp(d); -} - -/**************************************** - * Return type index for a aarray of type Value[Key] - * Input: - * t associative array type - * keyidx key type - * validx value type - */ -@trusted -idx_t cv8_daarray(type* t, idx_t keyidx, idx_t validx) -{ - //printf("cv8_daarray(keyidx = %x, validx = %x)\n", keyidx, validx); - /* Put out a struct: - * struct dAssocArray { - * void* ptr; - * typedef key-type __key_t; - * typedef val-type __val_t; - * } - */ - -static if (0) -{ - debtyp_t* d = debtyp_alloc(18); - TOWORD(d.data.ptr, 0x100F); - TOWORD(d.data.ptr + 2, OEM); - TOWORD(d.data.ptr + 4, 2); // 2 = associative array - TOLONG(d.data.ptr + 6, 2); // count of type indices to follow - TOLONG(d.data.ptr + 10, keyidx); // key type - TOLONG(d.data.ptr + 14, validx); // element type -} -else -{ - type* tv = type_fake(TYnptr); - tv.Tcount++; - idx_t pvidx = cv4_typidx(tv); - type_free(tv); - - __gshared const ubyte[50] fl = - [ - 0x03, 0x12, // LF_FIELDLIST_V2 - 0x0d, 0x15, // LF_MEMBER_V3 - 0x03, 0x00, // attribute - 0x00, 0x00, 0x00, 0x00, // void* - 0x00, 0x00, // offset - 'p','t','r',0, // "ptr" - 0xf2, 0xf1, // align to 4-byte including field id - // offset 18 - 0x10, 0x15, // LF_NESTTYPE_V3 - 0x00, 0x00, // padding - 0x00, 0x00, 0x00, 0x00, // key type - '_','_','k','e','y','_','t',0, // "__key_t" - // offset 34 - 0x10, 0x15, // LF_NESTTYPE_V3 - 0x00, 0x00, // padding - 0x00, 0x00, 0x00, 0x00, // value type - '_','_','v','a','l','_','t',0, // "__val_t" - ]; - - debtyp_t* f = debtyp_alloc(fl.sizeof); - memcpy(f.data.ptr,fl.ptr,fl.sizeof); - TOLONG(f.data.ptr + 6, pvidx); - TOLONG(f.data.ptr + 22, keyidx); - TOLONG(f.data.ptr + 38, validx); - idx_t fieldlist = cv_debtyp(f); - - const(char)* id = t.Tident ? t.Tident : "dAssocArray"; - int idlen = cast(int)strlen(id); - if (idlen > CV8_MAX_SYMBOL_LENGTH) - idlen = CV8_MAX_SYMBOL_LENGTH; - - debtyp_t* d = debtyp_alloc(20 + idlen + 1); - TOWORD(d.data.ptr, LF_STRUCTURE_V3); - TOWORD(d.data.ptr + 2, 1); // count - TOWORD(d.data.ptr + 4, 0); // property - TOLONG(d.data.ptr + 6, fieldlist); - TOLONG(d.data.ptr + 10, 0); // dList - TOLONG(d.data.ptr + 14, 0); // vtshape - TOWORD(d.data.ptr + 18, _tysize[TYnptr]); // size - memcpy(d.data.ptr + 20, id, idlen); - d.data.ptr[20 + idlen] = 0; - -} - return cv_debtyp(d); -} - -} diff --git a/compiler/src/dmd/backend/global.d b/compiler/src/dmd/backend/global.d index 9743eea960b6..ed0abd77bffe 100644 --- a/compiler/src/dmd/backend/global.d +++ b/compiler/src/dmd/backend/global.d @@ -35,6 +35,13 @@ alias ErrorCallbackBackend = extern(C++) void function(const(char)* filename, ui package(dmd.backend) __gshared ErrorCallbackBackend errorCallbackBackend; +/// Callback for the backend to fetch cached source-file contents from the +/// front-end FileManager (populated when the module was read). Returns a +/// pointer to the bytes and sets `length`; returns null if unavailable. +alias GetFileContentsCallback = extern(C++) const(ubyte)* function(const(char)* filename, ref size_t length); + +package(dmd.backend) __gshared GetFileContentsCallback getFileContentsCallback; + /** * Backend error report function * Params: diff --git a/compiler/src/dmd/backend/mscoffobj.d b/compiler/src/dmd/backend/mscoffobj.d index e9fd0bb8559f..f11f6e479983 100644 --- a/compiler/src/dmd/backend/mscoffobj.d +++ b/compiler/src/dmd/backend/mscoffobj.d @@ -22,7 +22,7 @@ import dmd.backend.cc; import dmd.backend.cdef; import dmd.backend.code; import dmd.backend.x86.code_x86; -import dmd.backend.cv8; +import dmd.backend.codeview; import dmd.backend.dvec; import dmd.backend.el; import dmd.backend.mem; @@ -32,7 +32,6 @@ import dmd.backend.symbol : symbol_generate, symbol_name, symbol_print, symbol_r import dmd.backend.obj; import dmd.backend.ty; import dmd.backend.type; -import dmd.backend.pdb; import dmd.backend.mscoff; @@ -356,7 +355,7 @@ Obj MsCoffObj_init(OutBuffer* objbuf, const(char)* filename, const(char)* csegna assert(SegData[UDATA].SDseg == UDATA); if (config.fulltypes) - config.newpdb ? pdb_initfile(filename) : cv8_initfile(filename); + cv_initfile(filename); assert(objbuf.length() == 0); return obj; } @@ -375,7 +374,7 @@ void MsCoffObj_initfile(const(char)* filename, const(char)* csegname, const(char { //dbg_printf("MsCoffObj_initfile(filename = %s, modname = %s)\n",filename,modname); if (config.fulltypes) - config.newpdb ? pdb_initmodule(filename, modname) : cv8_initmodule(filename, modname); + cv_initmodule(filename, modname); } /************************************ @@ -450,12 +449,12 @@ private void syment_set_name(SymbolTable32* sym, const(char)* name) { // Use offset into string table // symbols larger than 64kB crash link.exe 14.44 or later with /DEBUG OutBuffer buf; - if (len > CV8_MAX_SYMBOL_LENGTH) + if (len > CV_MAX_SYMBOL_LENGTH) { import dmd.common.blake3; const hash = blake3((cast(ubyte*)name)[0 .. len]); //truncate and append the first 16 bytes of the hash - buf.put(name, CV8_MAX_SYMBOL_LENGTH - 33); + buf.put(name, CV_MAX_SYMBOL_LENGTH - 33); buf.put('_'); buf.writeHexString(hash[0 .. 16], true); len = buf.length; @@ -616,7 +615,7 @@ void MsCoffObj_termfile() //dbg_printf("MsCoffObj_termfile\n"); if (config.addlinenumbers) { - config.newpdb ? pdb_termmodule() : cv8_termmodule(); + cv_termmodule(); } } @@ -636,7 +635,7 @@ void MsCoffObj_term(const(char)[] objfilename) if (config.addlinenumbers) { - config.newpdb ? pdb_termfile(objfilename) : cv8_termfile(objfilename); + cv_termfile(objfilename); } // To allow tooling support for most output files @@ -1008,7 +1007,7 @@ void MsCoffObj_linnum(Srcpos srcpos, int seg, targ_size_t offset) if (srcpos.Slinnum == 0 || !srcpos.Sfilename) return; - config.newpdb ? pdb_linnum(srcpos, cast(uint)offset) : cv8_linnum(srcpos, cast(uint)offset); + cv_linnum(srcpos, cast(uint)offset); } @@ -1846,7 +1845,7 @@ void MsCoffObj_func_start(Symbol* sfunc) sfunc.Soffset = Offset(cseg); if (config.fulltypes) - config.newpdb ? pdb_func_start(sfunc) : cv8_func_start(sfunc); + cv_func_start(sfunc); } /******************************* @@ -1860,7 +1859,7 @@ void MsCoffObj_func_term(Symbol* sfunc) // sfunc.Sident.ptr, sfunc.Soffset,Offset(cseg),sfunc.Sxtrnnum); if (config.fulltypes) - config.newpdb ? pdb_func_term(sfunc) : cv8_func_term(sfunc); + cv_func_term(sfunc); } /******************************** diff --git a/compiler/src/dmd/backend/pdb.d b/compiler/src/dmd/backend/pdb.d deleted file mode 100644 index 9b971a6ba1fa..000000000000 --- a/compiler/src/dmd/backend/pdb.d +++ /dev/null @@ -1,929 +0,0 @@ -/** - * Modern CodeView / PDB symbolic debug info generation. - * - * This module is a self-contained emitter for the `.debug$S` and `.debug$T` - * sections for Win64 MS-COFF objects. It is an alternative to cv8.d intended to - * eventually retire cv4.d/cv8.d. It targets the record set documented by LLVM - * (https://llvm.org/docs/PDB/) and Microsoft - * (https://github.com/microsoft/microsoft-pdb) and is enabled by the - * `-preview=newpdb` compiler flag. - * - * It does not modify the existing CV4/CV8 paths; the type-record pool helpers in - * cgcv.d are reused read-only. - * - * Compiler implementation of the - * $(LINK2 https://www.dlang.org, D programming language). - * - * Copyright: Copyright (C) 2026 by The D Language Foundation, All Rights Reserved - * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) - * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/backend/pdb.d, backend/pdb.d) - */ - -module dmd.backend.pdb; - -import core.stdc.stdio; -import core.stdc.stdlib; -import core.stdc.string; -extern (C) nothrow char* getcwd(char*, size_t); - -import dmd.backend.cc; -import dmd.backend.cdef; -import dmd.backend.cgcv; -import dmd.backend.code; -import dmd.backend.x86.code_x86; -import dmd.backend.cv4; -import dmd.backend.mem; -import dmd.backend.mscoffobj; -import dmd.backend.obj; -import dmd.backend.oper; -import dmd.common.outbuffer; -import dmd.backend.rtlsym; -import dmd.backend.symbol : globsym; -import dmd.backend.ty; -import dmd.backend.type; -import dmd.backend.dvarstats; - -nothrow: -@safe: - -/* ======================================================================== */ -/* CodeView constants not present in cv4.d, kept local to avoid touching */ -/* the legacy CV4/CV8 implementations. */ -/* ======================================================================== */ - -// Symbol records (DEBUG_S_SYMBOLS subsection content) -enum -{ - S_END_PDB = 0x0006, - S_OBJNAME_V3 = 0x1101, - S_COMPILE3 = 0x113C, - S_ENVBLOCK = 0x113D, - S_BUILDINFO = 0x114C, - S_FRAMEPROC = 0x1012, - S_GPROC32_ID = 0x1147, - S_LPROC32_ID = 0x1146, - S_PROC_ID_END = 0x114F, - S_GPROC32 = 0x1110, - S_LPROC32 = 0x110F, - S_REGREL32 = 0x1111, - S_BPREL32_V3 = 0x110B, - S_REGISTER_V3 = 0x1106, - S_LDATA32 = 0x110C, - S_GDATA32 = 0x110D, - S_LTHREAD32 = 0x1112, - S_GTHREAD32 = 0x1113, - S_PUB32 = 0x110E, - S_CONSTANT_V3 = 0x1107, - S_UDT_V3_ = 0x1108, - S_LABEL32_V3 = 0x1105, - S_THUNK32 = 0x1102, - S_LOCAL = 0x113E, - S_BLOCK32 = 0x1103, - S_RETURN = 0x000D, -} - -// Type leaf records used by the ID stream / source lines -enum -{ - LF_FUNC_ID = 0x1601, - LF_STRING_ID = 0x1605, - LF_BUILDINFO = 0x1603, - LF_UDT_SRC_LINE = 0x1606, - LF_SUBSTR_LIST = 0x1604, - LF_ALIAS = 0x150A, -} - -// .debug$S subsection kinds -enum -{ - DEBUG_S_SYMBOLS = 0xF1, - DEBUG_S_LINES = 0xF2, - DEBUG_S_STRINGTABLE = 0xF3, - DEBUG_S_FILECHKSMS = 0xF4, -} - -// File checksum kinds -enum -{ - CHKSUM_NONE = 0, - CHKSUM_MD5 = 1, - CHKSUM_SHA1 = 2, - CHKSUM_SHA256 = 3, -} - -// keep symbol records well under linker limits -enum PDB_MAX_SYMBOL_LENGTH = 0xffd8; - -// CV_PUBSYMFLAGS -enum PUB_FUNCTION = 0x00000002; - -/* ======================================================================== */ -/* State */ -/* ======================================================================== */ - -private __gshared OutBuffer* F1_buf; // symbols -private __gshared OutBuffer* F2_buf; // line numbers -private __gshared OutBuffer* F3_buf; // string table of source file names -private __gshared OutBuffer* F4_buf; // file checksums - -struct F1_Fixups -{ - Symbol* s; - uint offset; - uint value; -} - -private __gshared OutBuffer* F1fixup; - -struct FuncData -{ - Symbol* sfunc; - uint section_length; - const(char)* srcfilename; - uint srcfileoff; - uint linepairstart; - uint linepairbytes; - uint linepairsegment; - OutBuffer* f1buf; - OutBuffer* f1fixup; -} - -__gshared FuncData currentfuncdata; - -private __gshared OutBuffer* funcdata; // array of FuncData -private __gshared OutBuffer* linepair; // offset/line pairs - -// Determine if this Symbol lives in a COMDAT -@trusted -private bool symbol_iscomdat_pdb(Symbol* s) -{ - return s.Sclass == SC.comdat || - config.flags2 & CFG2comdat && s.Sclass == SC.inline || - config.flags4 & CFG4allcomdat && s.Sclass == SC.global; -} - -/* ======================================================================== */ -/* Name encoding */ -/* ======================================================================== */ - -private @trusted -void pdb_writename(OutBuffer* buf, const(char)* name, size_t len) -{ - if (!(config.flags2 & CFG2gms)) - { - buf.writen(name, len); - return; - } - - const(char)* start = name; - const(char)* cur = strchr(start, '.'); - const(char)* end = start + len; - while (cur != null) - { - if (cur >= end) - { - buf.writen(start, end - start); - return; - } - buf.writen(start, cur - start); - buf.writeByte('@'); - start = cur + 1; - if (start >= end) - return; - cur = strchr(start, '.'); - } - buf.writen(start, end - start); -} - -/* ======================================================================== */ -/* File lifecycle */ -/* ======================================================================== */ - -@trusted -void pdb_initfile(const(char)* filename) -{ - void initBuf(ref OutBuffer* ptr) - { - if (!ptr) - { - ptr = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); - ptr.reserve(1024); - } - ptr.reset(); - } - - initBuf(F1_buf); - initBuf(F1fixup); - initBuf(F2_buf); - - initBuf(F3_buf); - F3_buf.writeByte(0); // first "filename" - - initBuf(F4_buf); - initBuf(funcdata); - initBuf(linepair); - - memset(¤tfuncdata, 0, currentfuncdata.sizeof); - currentfuncdata.f1buf = F1_buf; - currentfuncdata.f1fixup = F1fixup; - - cv_init(); -} - -void pdb_initmodule(const(char)* filename, const(char)* modulename) -{ -} - -@trusted -void pdb_termmodule() -{ - assert(config.objfmt == OBJ_MSCOFF); -} - -@trusted -void pdb_termfile(const(char)[] objfilename) -{ - int seg = MsCoffObj_seg_debugS(); - - uint value = 4; // CV signature C13 - objmod.bytes(seg, 0, (cast(void*)&value)[0 .. 4]); - - auto buf = OutBuffer(1024); - - // S_OBJNAME - { - size_t len = objfilename.length; - buf.write16(cast(int)(2 + 4 + len + 1)); - buf.write16(S_OBJNAME_V3); - buf.write32(0); // signature - buf.write(objfilename.ptr, cast(uint)(len + 1)); - } - - // S_COMPILE3 - { - // Honor the configured language/compiler instead of hardcoding it: - // emit the D language index unless the debug info was requested for - // non-DMD (Microsoft) debuggers (-gc sets CFG2gms), and report the - // actual compiler version string (dmd/ldc/gdc) via config._version. - const uint flags = config.flags2 & CFG2gms ? 0 : 'D'; // iLanguage in low byte - const(char)[] ver = config._version; - buf.write16(cast(int)(2 + 4 + 2 + 8 + 8 + ver.length + 1)); - buf.write16(S_COMPILE3); - buf.write32(flags); - buf.write16(I64 ? 0xD0 : 0x07); // machine: AMD64 / x86 - buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // FE ver - buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // BE ver - buf.write(ver.ptr, cast(uint)ver.length); - buf.writeByte(0); - } - - pdb_writesection(seg, DEBUG_S_SYMBOLS, &buf); - - // S_ENVBLOCK: build environment (cwd, tool), double-null terminated - { - auto ebuf = OutBuffer(128); - char[260] cwd = 0; - if (!getcwd(cwd.ptr, cwd.sizeof)) - cwd[0] = 0; - size_t cwdlen = strlen(cwd.ptr); - uint rec = cast(uint)(2 + 1 + 4 + cwdlen + 1 + 4 + 1 + 1); - ebuf.write16(cast(int)rec); - ebuf.write16(S_ENVBLOCK); - ebuf.writeByte(0); // flags - ebuf.write("cwd\0".ptr, 4); - ebuf.write(cwd.ptr, cast(uint)(cwdlen + 1)); - ebuf.write("cmd\0".ptr, 4); - ebuf.writeByte(0); // empty cmd value - ebuf.writeByte(0); // terminating double-null - pdb_writesection(seg, DEBUG_S_SYMBOLS, &ebuf); - } - - // S_BUILDINFO referencing an LF_BUILDINFO id record - { - auto bbuf = OutBuffer(64); - idx_t biidx = pdb_buildinfo(); - bbuf.write16(2 + 4); - bbuf.write16(S_BUILDINFO); - bbuf.write32(biidx); - pdb_writesection(seg, DEBUG_S_SYMBOLS, &bbuf); - } - - uint length = cast(uint)funcdata.length(); - ubyte* p = funcdata.buf; - for (uint u = 0; u < length; u += FuncData.sizeof) - { - FuncData* fd = cast(FuncData*)(p + u); - - F2_buf.reset(); - F2_buf.write32(cast(uint)fd.sfunc.Soffset); - F2_buf.write32(0); - F2_buf.write32(fd.section_length); - F2_buf.write(linepair.buf + fd.linepairstart, fd.linepairbytes); - - int f2seg = seg; - if (symbol_iscomdat_pdb(fd.sfunc)) - { - f2seg = MsCoffObj_seg_debugS_comdat(fd.sfunc); - objmod.bytes(f2seg, 0, (cast(void*)&value)[0 .. 4]); - } - - uint offset = cast(uint)SegData[f2seg].SDoffset + 8; - pdb_writesection(f2seg, DEBUG_S_LINES, F2_buf); - objmod.reftoident(f2seg, offset, fd.sfunc, 0, CF.seg | CF.off); - - if (f2seg != seg && fd.f1buf.length()) - { - const uint f1offset = cast(uint)SegData[f2seg].SDoffset; - pdb_writesection(f2seg, DEBUG_S_SYMBOLS, fd.f1buf); - - const uint fixupLength = cast(uint)fd.f1fixup.length(); - ubyte* pfixup = fd.f1fixup.buf; - for (uint v = 0; v < fixupLength; v += F1_Fixups.sizeof) - { - F1_Fixups* f = cast(F1_Fixups*)(pfixup + v); - objmod.reftoident(f2seg, f1offset + 8 + f.offset, f.s, f.value, CF.seg | CF.off); - } - } - } - - if (F3_buf.length() > 1) - pdb_writesection(seg, DEBUG_S_STRINGTABLE, F3_buf); - - if (F4_buf.length() > 0) - pdb_writesection(seg, DEBUG_S_FILECHKSMS, F4_buf); - - if (F1_buf.length()) - { - uint f1offset = cast(uint)SegData[seg].SDoffset; - pdb_writesection(seg, DEBUG_S_SYMBOLS, F1_buf); - - length = cast(uint)F1fixup.length(); - p = F1fixup.buf; - for (uint u = 0; u < length; u += F1_Fixups.sizeof) - { - F1_Fixups* f = cast(F1_Fixups*)(p + u); - objmod.reftoident(seg, f1offset + 8 + f.offset, f.s, f.value, CF.seg | CF.off); - } - } - - cv_term(); // .debug$T -} - -/* ======================================================================== */ -/* Functions */ -/* ======================================================================== */ - -@trusted -void pdb_func_start(Symbol* sfunc) -{ - currentfuncdata.sfunc = sfunc; - currentfuncdata.section_length = 0; - currentfuncdata.srcfilename = null; - currentfuncdata.linepairstart += currentfuncdata.linepairbytes; - currentfuncdata.linepairbytes = 0; - currentfuncdata.f1buf = F1_buf; - currentfuncdata.f1fixup = F1fixup; - if (symbol_iscomdat_pdb(sfunc)) - { - currentfuncdata.f1buf = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); - currentfuncdata.f1buf.reserve(128); - currentfuncdata.f1fixup = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); - currentfuncdata.f1fixup.reserve(128); - } - varStats_startFunction(); -} - -@trusted -void pdb_func_term(Symbol* sfunc) -{ - assert(currentfuncdata.sfunc == sfunc); - currentfuncdata.section_length = cast(uint)sfunc.Ssize; - funcdata.write(¤tfuncdata, currentfuncdata.sizeof); - - assert(tyfunc(sfunc.ty())); - idx_t typidx; - func_t* fn = sfunc.Sfunc; - if (fn.Fclass) - { - // Member functions get a dedicated LF_MFUNCTION_V2 type record that - // records the enclosing class and the `this` pointer type. This info - // isn't available inside cv4_typidx, so replicate cv8_func_term here. - uint nparam; - const ubyte call = cv4_callconv(sfunc.Stype); - const idx_t paramidx = cv4_arglist(sfunc.Stype, &nparam); - const uint next = cv4_typidx(sfunc.Stype.Tnext); - type* classtype = cast(type*)fn.Fclass; - const uint classidx = cv4_typidx(classtype); - type* tp = type_allocn(TYnptr, classtype); - const uint thisidx = cv4_typidx(tp); - debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4 + 1 + 1 + 2 + 4 + 4); - TOWORD(d.data.ptr, LF_MFUNCTION_V2); - TOLONG(d.data.ptr + 2, next); // return type - TOLONG(d.data.ptr + 6, classidx); // class type - TOLONG(d.data.ptr + 10, thisidx); // this type - d.data.ptr[14] = call; - d.data.ptr[15] = 0; // reserved - TOWORD(d.data.ptr + 16, nparam); - TOLONG(d.data.ptr + 18, paramidx); - TOLONG(d.data.ptr + 22, 0); // this adjust - typidx = cv_debtyp(d); - } - else - typidx = cv_typidx(sfunc.Stype); - - const(char)* id = sfunc.prettyIdent ? sfunc.prettyIdent : prettyident(sfunc); - size_t len = strlen(id); - if (len > PDB_MAX_SYMBOL_LENGTH) - len = PDB_MAX_SYMBOL_LENGTH; - - auto buf = currentfuncdata.f1buf; - buf.reserve(cast(uint)(2 + 2 + 4 * 7 + 6 + 1 + len + 1)); - buf.write16n(cast(int)(2 + 4 * 7 + 6 + 1 + len + 1)); - buf.write16n(sfunc.Sclass == SC.static_ ? S_LPROC32 : S_GPROC32); - buf.write32(0); // parent - buf.write32(0); // pend - buf.write32(0); // pnext - buf.write32(cast(uint)currentfuncdata.section_length); - buf.write32(cast(uint)cgstate.startoffset); // prolog size - buf.write32(cast(uint)cgstate.retoffset); // epilog offset - buf.write32(typidx); - - F1_Fixups f1f; - f1f.s = sfunc; - f1f.offset = cast(uint)buf.length(); - f1f.value = 0; - currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); - buf.write32(0); - buf.write16n(0); - buf.writeByte(0); // flags - buf.writen(id, len); - buf.writeByte(0); - - // S_FRAMEPROC: describe this function's stack frame - { - // Locals and parameters are addressed relative to the frame pointer - // (RBP/EBP), matching the S_REGREL32 records emitted for them; the - // CodeView "FramePtr" register encoding for that is 2. - uint frameflags = (2 << 14) | (2 << 16); // encoded local/param base ptr - if (cgstate.anyiasm) - frameflags |= 1 << 3; // fHasInlAsm - if (cgstate.usednteh) - frameflags |= 1 << 4; // fHasEH - if (config.flags2 & CFG2stomp) - frameflags |= 1 << 8; // fSecurityChecks - if (config.flags4 & CFG4speed) - frameflags |= 1 << 20; // fOptSpeed - - buf.write16(2 + 4 + 4 + 4 + 4 + 4 + 2 + 4); - buf.write16(S_FRAMEPROC); - buf.write32(cast(uint)localsize); // cbFrame: total frame size - buf.write32(0); // cbPad - buf.write32(0); // offPad - buf.write32(0); // cbSaveRegs - buf.write32(0); // offExHdlr - buf.write16(0); // sectExHdlr - buf.write32(frameflags); // flags - } - - struct pdbblk - { - nothrow: - struct block_v3_data - { - ushort len; - ushort id; - uint pParent; - uint pEnd; - uint length; - uint offset; - ushort seg; - ubyte[1] name; - } - static void endArgs() - { - auto b = currentfuncdata.f1buf; - b.write16(2); - b.write16(0x000A); // S_ENDARG - } - static void beginBlock(int offset, int length) - { - auto b = currentfuncdata.f1buf; - uint soffset = cast(uint)b.length(); - block_v3_data block32 = { block_v3_data.sizeof - 2, S_BLOCK32, 0, 0, length, offset, 0, [ 0 ] }; - b.write(&block32, block32.sizeof); - size_t offOffset = cast(char*)&block32.offset - cast(char*)&block32; - F1_Fixups f; - f.s = currentfuncdata.sfunc; - f.offset = cast(uint)(soffset + offOffset); - f.value = offset; - currentfuncdata.f1fixup.write(&f, f.sizeof); - } - static void endBlock() - { - auto b = currentfuncdata.f1buf; - b.write16(2); - b.write16(S_END_PDB); - } - } - varStats_writeSymbolTable(sfunc, globsym, &pdb_outsym, &pdbblk.endArgs, &pdbblk.beginBlock, &pdbblk.endBlock); - - // No S_RETURN record: its register list uses single-byte CodeView register - // codes, which cannot encode x64 return registers (e.g. CV_AMD64_RAX = 328). - // Both MSVC and cv8.d omit it, so a real record can't be produced here. - - buf.write16(2); - buf.write16(S_END_PDB); - - currentfuncdata.f1buf = F1_buf; - currentfuncdata.f1fixup = F1fixup; -} - -/* ======================================================================== */ -/* Line numbers */ -/* ======================================================================== */ - -@trusted -void pdb_linnum(Srcpos srcpos, uint offset) -{ - const sfilename = srcpos.Sfilename; - if (!sfilename) - return; - - varStats_recordLineOffset(srcpos, offset); - - __gshared uint lastoffset; - __gshared uint lastlinnum; - - if (!currentfuncdata.srcfilename || - (currentfuncdata.srcfilename != sfilename && strcmp(currentfuncdata.srcfilename, sfilename))) - { - currentfuncdata.srcfilename = sfilename; - uint srcfileoff = pdb_addfile(sfilename); - currentfuncdata.linepairsegment = currentfuncdata.linepairstart + currentfuncdata.linepairbytes; - linepair.write32(srcfileoff); - linepair.write32(0); - linepair.write32(12); - currentfuncdata.linepairbytes += 12; - } - else if (offset <= lastoffset || srcpos.Slinnum == lastlinnum) - return; - - lastoffset = offset; - lastlinnum = srcpos.Slinnum; - linepair.write32(offset); - linepair.write32(srcpos.Slinnum | 0x80000000); - currentfuncdata.linepairbytes += 8; - - auto segmentbytes = currentfuncdata.linepairstart + currentfuncdata.linepairbytes - currentfuncdata.linepairsegment; - auto segmentheader = cast(uint*)(linepair.buf + currentfuncdata.linepairsegment); - segmentheader[1] = (segmentbytes - 12) / 8; - segmentheader[2] = segmentbytes; -} - -/* ======================================================================== */ -/* Source files (F3 names + F4 checksums via blake3) */ -/* ======================================================================== */ - -@trusted -uint pdb_addfile(const(char)* filename) -{ - uint length = cast(uint)F3_buf.length(); - ubyte* p = F3_buf.buf; - size_t len = strlen(filename); - - __gshared char[260] cwd = 0; - __gshared uint cwdlen; - bool abs = (*filename == '\\') || (*filename == '/') || - (*filename && filename[1] == ':'); - - if (!abs && cwd[0] == 0) - { - if (getcwd(cwd.ptr, cwd.sizeof)) - { - cwdlen = cast(uint)strlen(cwd.ptr); - if (cwd[cwdlen - 1] != '\\' && cwd[cwdlen - 1] != '/') - cwd[cwdlen++] = '\\'; - } - } - uint off = 1; - while (off + len < length) - { - if (!abs) - { - if (memcmp(p + off, cwd.ptr, cwdlen) == 0 && - memcmp(p + off + cwdlen, filename, len + 1) == 0) - goto L1; - } - else if (memcmp(p + off, filename, len + 1) == 0) - goto L1; - off += strlen(cast(const(char)*)(p + off)) + 1; - } - off = length; - if (!abs) - F3_buf.write(cwd.ptr, cwdlen); - F3_buf.write(filename, cast(uint)(len + 1)); - -L1: - length = cast(uint)F4_buf.length(); - p = F4_buf.buf; - uint u = 0; - while (u + 6 <= length) - { - if (off == *cast(uint*)(p + u)) - return u; - u += 4; // file name offset - ubyte cklen = *(p + u); // checksum size - u += 2; // skip size + kind bytes - u += cklen; // skip checksum bytes - u = (u + 3) & ~3; // realign to 4 - } - - // Not present; append a checksum computed over the source file's *content* - // (not over its name) so debuggers can verify the source matches. - F4_buf.write32(off); - ubyte[32] hash = void; - if (pdb_filehash(filename, hash)) - { - F4_buf.writeByte(32); // checksum size - F4_buf.writeByte(CHKSUM_SHA256); // 32-byte checksum slot - F4_buf.write(hash.ptr, 32); - } - else - { - F4_buf.writeByte(0); // no checksum available - F4_buf.writeByte(CHKSUM_NONE); - } - while (F4_buf.length() & 3) - F4_buf.writeByte(0); - return length; -} - -/* Compute a blake3 hash over the *content* of the named source file. - * Returns: true on success (hash filled in), false if the file can't be read. - */ -private @trusted -bool pdb_filehash(const(char)* filename, ref ubyte[32] hash) -{ - FILE* fp = fopen(filename, "rb"); - if (!fp) - return false; - scope(exit) fclose(fp); - - if (fseek(fp, 0, SEEK_END) != 0) - return false; - const long size = ftell(fp); - if (size < 0 || fseek(fp, 0, SEEK_SET) != 0) - return false; - - ubyte* data = cast(ubyte*)malloc(size ? cast(size_t)size : 1); - if (!data) - return false; - scope(exit) free(data); - - const size_t nread = fread(data, 1, cast(size_t)size, fp); - - import dmd.common.blake3; - hash = blake3(data[0 .. nread]); - return true; -} - -private @trusted -void pdb_writesection(int seg, uint type, OutBuffer* buf) -{ - uint off = cast(uint)SegData[seg].SDoffset; - objmod.bytes(seg, off, (cast(void*)&type)[0 .. 4]); - uint length = cast(uint)buf.length(); - objmod.bytes(seg, off + 4, (cast(void*)&length)[0 .. 4]); - objmod.bytes(seg, off + 8, buf.buf[0 .. length]); - uint pad = ((length + 3) & ~3) - length; - objmod.lidata(seg, off + 8 + length, pad); -} - -/* ======================================================================== */ -/* Symbols */ -/* ======================================================================== */ - -@trusted -void pdb_outsym(Symbol* s) -{ - if (s.Sflags & SFLnodebug) - return; - - idx_t typidx = cv_typidx(s.Stype); - const(char)* id = s.prettyIdent ? s.prettyIdent : prettyident(s); - size_t len = strlen(id); - if (len > PDB_MAX_SYMBOL_LENGTH) - len = PDB_MAX_SYMBOL_LENGTH; - - F1_Fixups f1f; - f1f.value = 0; - auto buf = currentfuncdata.f1buf; - - uint sr; - uint base; - switch (s.Sclass) - { - case SC.parameter: - case SC.regpar: - case SC.shadowreg: - if (s.Sfl == FL.reg) - { - s.Sfl = FL.para; - pdb_outsym(s); - s.Sfl = FL.reg; - goto case_register; - } - base = cast(uint)(cgstate.Para.size - cgstate.BPoff); - goto L1; - - case SC.auto_: - if (s.Sfl == FL.reg) - goto case_register; - case_auto: - base = cast(uint)cgstate.Auto.size; - L1: - if (s.Sscope) - break; - buf.reserve(cast(uint)(2 + 2 + 4 + 4 + 2 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + 4 + 2 + len + 1)); - buf.write16n(S_REGREL32); - buf.write32(cast(uint)(s.Soffset + base + cgstate.BPoff)); - buf.write32(typidx); - buf.write16n(I64 ? 334 : 22); - pdb_writename(buf, id, len); - buf.writeByte(0); - break; - - case SC.bprel: - base = -cgstate.BPoff; - goto L1; - - case SC.fastpar: - if (s.Sfl != FL.reg) - { - base = cast(uint)cgstate.Fast.size; - goto L1; - } - goto L2; - - case SC.register: - if (s.Sfl != FL.reg) - goto case_auto; - goto case; - - case SC.pseudo: - case_register: - L2: - buf.reserve(cast(uint)(2 + 2 + 4 + 2 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + 2 + len + 1)); - buf.write16n(S_REGISTER_V3); - buf.write32(typidx); - buf.write16n(pdb_regnum(s)); - pdb_writename(buf, id, len); - buf.writeByte(0); - break; - - case SC.extern_: - break; - - case SC.static_: - case SC.locstat: - sr = S_LDATA32; - goto Ldata; - - case SC.global: - case SC.comdat: - case SC.comdef: - sr = S_GDATA32; - Ldata: - if (s.ty() & mTYthread) // full TLS support - sr = (sr == S_GDATA32) ? S_GTHREAD32 : S_LTHREAD32; - - buf.reserve(cast(uint)(2 + 2 + 4 + 6 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + 6 + len + 1)); - buf.write16n(sr); - buf.write32(typidx); - f1f.s = s; - f1f.offset = cast(uint)buf.length(); - currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); - buf.write32(0); - buf.write16n(0); - pdb_writename(buf, id, len); - buf.writeByte(0); - break; - - default: - break; - } -} - -/* Put out a name for a user defined type. */ -@trusted -void pdb_udt(const(char)* id, idx_t typidx) -{ - auto buf = currentfuncdata.f1buf; - size_t len = strlen(id); - if (len > PDB_MAX_SYMBOL_LENGTH) - len = PDB_MAX_SYMBOL_LENGTH; - buf.reserve(cast(uint)(2 + 2 + 4 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + len + 1)); - buf.write16n(S_UDT_V3_); - buf.write32(typidx); - pdb_writename(buf, id, len); - buf.writeByte(0); -} - -/* ======================================================================== */ -/* ID-stream type records (reuse the shared cgcv type pool, read-only) */ -/* ======================================================================== */ - -/* LF_STRING_ID: an interned string returning a type index. */ -@trusted -idx_t pdb_string_id(const(char)* s) -{ - if (!s) s = ""; - debtyp_t* d = debtyp_alloc(2 + 4 + cv_stringbytes(s)); - TOWORD(d.data.ptr, LF_STRING_ID); - TOLONG(d.data.ptr + 2, 0); // substring list id - cv_namestring(d.data.ptr + 6, s); - return cv_debtyp(d); -} - -/* LF_BUILDINFO: cwd, build tool, source, pdb, args. */ -@trusted -idx_t pdb_buildinfo() -{ - char[260] cwd = 0; - if (!getcwd(cwd.ptr, cwd.sizeof)) - cwd[0] = 0; - idx_t cwdId = pdb_string_id(cwd.ptr); - idx_t toolId = pdb_string_id("dmd"); - idx_t srcId = pdb_string_id(""); - idx_t pdbId = pdb_string_id(""); - idx_t argId = pdb_string_id(""); - debtyp_t* d = debtyp_alloc(2 + 2 + 5 * 4); - TOWORD(d.data.ptr, LF_BUILDINFO); - TOWORD(d.data.ptr + 2, 5); // count - TOLONG(d.data.ptr + 4, cwdId); - TOLONG(d.data.ptr + 8, toolId); - TOLONG(d.data.ptr + 12, srcId); - TOLONG(d.data.ptr + 16, pdbId); - TOLONG(d.data.ptr + 20, argId); - return cv_debtyp(d); -} - -/* LF_FUNC_ID: ties a function type to its name for the ID stream. */ -@trusted -idx_t pdb_func_id(const(char)* id, idx_t functype) -{ - debtyp_t* d = debtyp_alloc(2 + 4 + 4 + cv_stringbytes(id)); - TOWORD(d.data.ptr, LF_FUNC_ID); - TOLONG(d.data.ptr + 2, 0); // parent scope id - TOLONG(d.data.ptr + 6, functype); - cv_namestring(d.data.ptr + 10, id); - return cv_debtyp(d); -} - -/* LF_UDT_SRC_LINE: source location of a user-defined type. */ -@trusted -idx_t pdb_udt_src_line(idx_t typidx, idx_t srcId, uint line) -{ - debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4); - TOWORD(d.data.ptr, LF_UDT_SRC_LINE); - TOLONG(d.data.ptr + 2, typidx); - TOLONG(d.data.ptr + 6, srcId); - TOLONG(d.data.ptr + 10, line); - return cv_debtyp(d); -} - -/* Codeview register number for symbol s. */ -int pdb_regnum(Symbol* s) -{ - int reg = s.Sreglsw; - assert(s.Sfl == FL.reg); - if ((1 << reg) & XMMREGS) - return reg - XMM0 + 154; - switch (type_size(s.Stype)) - { - case 1: - if (reg < 4) reg += 1; - else if (reg < 8) reg += 324 - 4; - else reg += 344 - 4; - break; - case 2: - if (reg < 8) reg += 9; - else reg += 352 - 8; - break; - case 4: - if (reg < 8) reg += 17; - else reg += 360 - 8; - break; - case 8: - reg += 328; - break; - default: - reg = 0; - break; - } - return reg; -} diff --git a/compiler/src/dmd/cli.d b/compiler/src/dmd/cli.d index 52e9b64ba2a9..f19b3d227776 100644 --- a/compiler/src/dmd/cli.d +++ b/compiler/src/dmd/cli.d @@ -1120,8 +1120,6 @@ dmd -cov -unittest myprog.d "https://dlang.org/spec/attribute.html#system-variables"), Feature("fastdfa", "useFastDFA", "Fast dataflow analysis engine, experimental"), - Feature("newpdb", "newpdb", - "use the modern PDB/CodeView debug info emitter"), ]; } diff --git a/compiler/src/dmd/dmsc.d b/compiler/src/dmd/dmsc.d index 143ce608f474..70072e5361e4 100644 --- a/compiler/src/dmd/dmsc.d +++ b/compiler/src/dmd/dmsc.d @@ -29,10 +29,27 @@ import dmd.backend.backconfig; import dmd.backend.go; import dmd.backend.cc; import dmd.backend.cdef; -import dmd.backend.global : ErrorCallbackBackend; +import dmd.backend.global : ErrorCallbackBackend, GetFileContentsCallback; import dmd.backend.ty; import dmd.backend.type; +import dmd.file_manager : FileManager; + +/// Callback for the backend to fetch cached source-file contents from the +/// front-end FileManager (populated when the module was read), so that hashing +/// source files for debug info reuses the cache instead of re-reading disk. +extern(C++) const(ubyte)* getFileContentsBackend(const(char)* filename, ref size_t length) +{ + length = 0; + if (!global.fileManager) + return null; + const(ubyte)[] data = global.fileManager.getFileContents(FileName(filename[0 .. strlen(filename)])); + if (!data) + return null; + length = data.length; + return data.ptr; +} + /************************************** * Initialize backend config variables. * Params: @@ -95,10 +112,10 @@ void backend_init(const ref Param params, const ref DMDparams driverParams, cons exfmt, params.addMain, driverParams.symImport != SymImport.none, - params.newpdb, go, // FIXME: casting to @nogc because errors.d is not marked @nogc yet cast(ErrorCallbackBackend) &errorBackend, + cast(GetFileContentsCallback) &getFileContentsBackend, ); out_config_debug( diff --git a/compiler/src/dmd/globals.d b/compiler/src/dmd/globals.d index b94f1a35cea9..0c4f9eb5417c 100644 --- a/compiler/src/dmd/globals.d +++ b/compiler/src/dmd/globals.d @@ -216,7 +216,6 @@ extern (C++) struct Param // https://issues.dlang.org/show_bug.cgi?id=14246 FeatureState systemVariables; // limit access to variables marked @system from @safe code bool useFastDFA; // Use fast data flow analysis engine - bool newpdb; // use the modern PDB/CodeView debug info emitter CHECKENABLE useInvariants = CHECKENABLE._default; // generate class invariant checks CHECKENABLE useIn = CHECKENABLE._default; // generate precondition checks diff --git a/compiler/src/dmd/glue/e2ir.d b/compiler/src/dmd/glue/e2ir.d index 2939c47e4644..4c7c2b660b9b 100644 --- a/compiler/src/dmd/glue/e2ir.d +++ b/compiler/src/dmd/glue/e2ir.d @@ -70,7 +70,7 @@ import dmd.backend.cc; import dmd.backend.cdef; import dmd.backend.cgcv; import dmd.backend.code; -import dmd.backend.cv4; +import dmd.backend.codeview; import dmd.backend.dt; import dmd.backend.el; import dmd.backend.dout : out_readonly_comdat, out_string_literal; diff --git a/compiler/src/dmd/glue/s2ir.d b/compiler/src/dmd/glue/s2ir.d index b17c19108ba9..619597319ca2 100644 --- a/compiler/src/dmd/glue/s2ir.d +++ b/compiler/src/dmd/glue/s2ir.d @@ -59,7 +59,7 @@ import dmd.backend.cdef; import dmd.backend.cgcv; import dmd.backend.code; import dmd.backend.x86.code_x86; -import dmd.backend.cv4; +import dmd.backend.codeview; import dmd.backend.dt; import dmd.backend.el; import dmd.backend.blockopt : block_appendexp, block_calloc, block_goto, blockopt; diff --git a/compiler/src/dmd/glue/tocvdebug.d b/compiler/src/dmd/glue/tocvdebug.d index 12c1f350db1e..0bc23a883418 100644 --- a/compiler/src/dmd/glue/tocvdebug.d +++ b/compiler/src/dmd/glue/tocvdebug.d @@ -46,7 +46,7 @@ import dmd.backend.cc; import dmd.backend.cdef; import dmd.backend.cgcv; import dmd.backend.code; -import dmd.backend.cv4; +import dmd.backend.codeview; import dmd.backend.dt; import dmd.backend.obj; import dmd.backend.oper; @@ -284,7 +284,7 @@ uint cv_align(ubyte* p, uint n) void cv_udt(const char* id, uint typidx) { if (config.fulltypes == CV8) - return cv8_udt(id, typidx); + return cv_udt_symbol(id, typidx); const len = strlen(id); version (AArch64) // TODO AArch64 diff --git a/compiler/src/dmd/glue/toobj.d b/compiler/src/dmd/glue/toobj.d index 76a2f3cbb12d..f7d881ede426 100644 --- a/compiler/src/dmd/glue/toobj.d +++ b/compiler/src/dmd/glue/toobj.d @@ -74,7 +74,7 @@ import dmd.backend.cdef; import dmd.backend.cgcv; import dmd.backend.code; import dmd.backend.x86.code_x86; -import dmd.backend.cv4; +import dmd.backend.codeview; import dmd.backend.dt; import dmd.backend.el; import dmd.backend.dout : out_readonly, outdata; diff --git a/compiler/test/compilable/previewhelp.d b/compiler/test/compilable/previewhelp.d index 117049a56a73..94a01ced14bf 100644 --- a/compiler/test/compilable/previewhelp.d +++ b/compiler/test/compilable/previewhelp.d @@ -20,6 +20,5 @@ Upcoming language changes listed by -preview=name: =fixImmutableConv disallow `void[]` data from holding immutable data (https://dlang.org/changelog/2.101.0.html#dmd.fix-immutable-conv, https://issues.dlang.org/show_bug.cgi?id=17148) =systemVariables disable access to variables marked '@system' from @safe code (https://dlang.org/spec/attribute.html#system-variables) =fastdfa Fast dataflow analysis engine, experimental - =newpdb use the modern PDB/CodeView debug info emitter ---- */ diff --git a/compiler/test/runnable/testpdb.d b/compiler/test/runnable/testpdb.d index 0715ab17f67c..a8c9706be508 100644 --- a/compiler/test/runnable/testpdb.d +++ b/compiler/test/runnable/testpdb.d @@ -1,5 +1,4 @@ // REQUIRED_ARGS: -gf -mixin=${RESULTS_DIR}/runnable/testpdb.mixin -preview=bitfields -// REQUIRED_ARGS(windows): -preview=newpdb // PERMUTE_ARGS: import core.time; From 6ff75f8e40480842de6dfbcf4213673d586fd9e2 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Sun, 5 Jul 2026 17:18:10 -0700 Subject: [PATCH 13/19] Revert "Combine cv4.d/cv8.d/pdb.d into a single file named codeview.d and remove the preview switch." This reverts commit 494c9f3b32d89802c731f0acef50beaa523e5f91. --- changelog/dmd.newpdb.dd | 20 +- compiler/include/dmd/globals.h | 1 + compiler/src/build.d | 4 +- compiler/src/dmd/backend/backconfig.d | 9 +- compiler/src/dmd/backend/cdef.d | 1 + compiler/src/dmd/backend/cgcv.d | 17 +- compiler/src/dmd/backend/codeview.d | 1496 ------------------------ compiler/src/dmd/backend/cv4.d | 245 ++++ compiler/src/dmd/backend/cv8.d | 1207 +++++++++++++++++++ compiler/src/dmd/backend/global.d | 7 - compiler/src/dmd/backend/mscoffobj.d | 21 +- compiler/src/dmd/backend/pdb.d | 929 +++++++++++++++ compiler/src/dmd/cli.d | 2 + compiler/src/dmd/dmsc.d | 21 +- compiler/src/dmd/globals.d | 1 + compiler/src/dmd/glue/e2ir.d | 2 +- compiler/src/dmd/glue/s2ir.d | 2 +- compiler/src/dmd/glue/tocvdebug.d | 4 +- compiler/src/dmd/glue/toobj.d | 2 +- compiler/test/compilable/previewhelp.d | 1 + compiler/test/runnable/testpdb.d | 1 + 21 files changed, 2427 insertions(+), 1566 deletions(-) delete mode 100644 compiler/src/dmd/backend/codeview.d create mode 100644 compiler/src/dmd/backend/cv4.d create mode 100644 compiler/src/dmd/backend/cv8.d create mode 100644 compiler/src/dmd/backend/pdb.d diff --git a/changelog/dmd.newpdb.dd b/changelog/dmd.newpdb.dd index 667119c02008..d1f75308fa2c 100644 --- a/changelog/dmd.newpdb.dd +++ b/changelog/dmd.newpdb.dd @@ -1,21 +1,9 @@ -Modern CodeView / PDB symbolic debug info is now emitted by default on Windows +Added a new Modern CodeView / PDB symbolic debug info emitter -The Windows CodeView / PDB debug information emitter (used with `-g` when producing MS-COFF object files) has been modernized to emit the record set expected by Microsoft and LLVM debugging tools. These improvements are now always enabled, so debugging with Visual Studio, WinDbg and LLVM-based tools is more robust out of the box. +A new PDB (Program Database) emitter has been added to improve debug information generation on Windows. Creating modern CodeView/PDB symbolic debug info enables more robust and feature-rich debugging experiences with Microsoft development tools. -Because the emitter is now the default, the experimental `-preview=newpdb` switch has been removed. - -The following are now generated: - -$(LIST - * `S_COMPILE3` compiland records reporting the configured language and the real compiler version string, - * `S_ENVBLOCK` and `S_BUILDINFO` (`LF_BUILDINFO`) records describing the build environment, - * `S_FRAMEPROC` records describing each function's stack frame, - * real source-file checksums computed over the source contents so debuggers can verify the source matches, - * first-class thread-local storage records, and documented named CodeView constants throughout. -) - -No action is required; simply compile with `-g` as before: +You can activate the new PDB emitter by passing the `-preview=newpdb` flag to the compiler. ```console -dmd -g myapp.d +dmd -g -preview=newpdb myapp.d ``` diff --git a/compiler/include/dmd/globals.h b/compiler/include/dmd/globals.h index 84142e5d6774..bec8bbb7ced3 100644 --- a/compiler/include/dmd/globals.h +++ b/compiler/include/dmd/globals.h @@ -237,6 +237,7 @@ struct Param // https://issues.dlang.org/show_bug.cgi?id=14246 FeatureState systemVariables; // limit access to variables marked @system from @safe code d_bool useFastDFA; // Use fast data flow analysis engine + d_bool newpdb; // use the modern PDB/CodeView debug info emitter CHECKENABLE useInvariants; // generate class invariant checks CHECKENABLE useIn; // generate precondition checks diff --git a/compiler/src/build.d b/compiler/src/build.d index b48c3b5d54b7..fee1fdb717b8 100755 --- a/compiler/src/build.d +++ b/compiler/src/build.d @@ -1538,7 +1538,7 @@ auto sourceFiles() cc.d cdef.d cgcv.d code.d dt.d el.d global.d obj.d oper.d iasm.d codebuilder.d ty.d type.d - dwarf.d dwarf2.d + dwarf.d dwarf2.d cv4.d melf.d mscoff.d mach.d x86/code_x86.d x86/xmm.d "), @@ -1581,7 +1581,7 @@ auto sourceFiles() debugprint.d fp.d symbol.d dcode.d cgsched.d pdata.d util2.d backconfig.d rtlsym.d ptrntab.d dvarstats.d cgen.d barray.d cgcse.d elpicpie.d - dwarfeh.d dwarfdbginf.d codeview.d + dwarfeh.d dwarfdbginf.d cv8.d pdb.d machobj.d elfobj.d mscoffobj.d x86/nteh.d x86/cgreg.d x86/cg87.d x86/cgxmm.d x86/disasm86.d x86/cgcod.d x86/cod1.d x86/cod2.d x86/cod3.d x86/cod4.d x86/cod5.d diff --git a/compiler/src/dmd/backend/backconfig.d b/compiler/src/dmd/backend/backconfig.d index aaa23d4b4173..030fd41ce951 100644 --- a/compiler/src/dmd/backend/backconfig.d +++ b/compiler/src/dmd/backend/backconfig.d @@ -17,8 +17,7 @@ import core.stdc.stdio; import dmd.backend.cdef; import dmd.backend.cc; import dmd.backend.code; -import dmd.backend.global : ErrorCallbackBackend, error, errorCallbackBackend, - GetFileContentsCallback, getFileContentsCallback; +import dmd.backend.global : ErrorCallbackBackend, error, errorCallbackBackend; import dmd.backend.go : go_flag, GlobalOptimizer; import dmd.backend.rtlsym : rtlsym_init; import dmd.backend.ty; @@ -87,14 +86,13 @@ void out_config_init( exefmt_t exefmt, bool generatedMain, // a main entrypoint is generated bool dataimports, + bool newpdb, // use the modern PDB/CodeView debug info emitter ref GlobalOptimizer go, - ErrorCallbackBackend errorCallback, - GetFileContentsCallback getFileContents) + ErrorCallbackBackend errorCallback) { //printf("out_config_init()\n"); errorCallbackBackend = errorCallback; - getFileContentsCallback = getFileContents; auto cfg = &config; cfg._version = _version; @@ -106,6 +104,7 @@ void out_config_init( } cfg.fulltypes = CVNONE; cfg.fpxmmregs = false; + cfg.newpdb = newpdb; if (!arm) cfg.inline8087 = 1; cfg.memmodel = 0; diff --git a/compiler/src/dmd/backend/cdef.d b/compiler/src/dmd/backend/cdef.d index 7019b70d0975..31bc59dd4bd3 100644 --- a/compiler/src/dmd/backend/cdef.d +++ b/compiler/src/dmd/backend/cdef.d @@ -529,6 +529,7 @@ struct Config bool useTypeInfo; // implement TypeInfo bool useExceptions; // implement exception handling ubyte dwarf; // DWARF version + bool newpdb; // use the modern PDB/CodeView debug info emitter // Configuration that is not saved in precompiled header diff --git a/compiler/src/dmd/backend/cgcv.d b/compiler/src/dmd/backend/cgcv.d index fd14b3d967a1..4740210b1ced 100644 --- a/compiler/src/dmd/backend/cgcv.d +++ b/compiler/src/dmd/backend/cgcv.d @@ -20,7 +20,8 @@ module dmd.backend.cgcv; import dmd.backend.cc : Classsym, Symbol; import dmd.backend.type; -public import dmd.backend.codeview; +public import dmd.backend.cv8; +import dmd.backend.pdb : pdb_outsym; public import dmd.backend.dwarfdbginf : dwarf_outsym; import core.stdc.stdio; @@ -32,6 +33,7 @@ import dmd.backend.cdef; import dmd.backend.cgcv; import dmd.backend.code; import dmd.backend.x86.code_x86; +import dmd.backend.cv4; import dmd.backend.dvec; import dmd.backend.el; import dmd.backend.global : err_nomem; @@ -1229,7 +1231,7 @@ L1: { case CV8: { - typidx = cv_darray(t, next); + typidx = cv8_darray(t, next); break; } case CV4: @@ -1264,7 +1266,7 @@ else switch (config.fulltypes) { case CV8: - typidx = cv_daarray(t, key, next); + typidx = cv8_daarray(t, key, next); break; case CV4: @@ -1297,7 +1299,7 @@ else switch (config.fulltypes) { case CV8: - typidx = cv_ddelegate(t, next); + typidx = cv8_ddelegate(t, next); break; case CV4: @@ -1451,7 +1453,7 @@ else { if (config.fulltypes == CV8) { - typidx = cv_fwdref(t.Ttag); + typidx = cv8_fwdref(t.Ttag); } else { @@ -2178,7 +2180,10 @@ void cv_outsym(Symbol* s) break; case CV8: - cv_symbol(s); + if (config.newpdb) + pdb_outsym(s); + else + cv8_outsym(s); break; default: diff --git a/compiler/src/dmd/backend/codeview.d b/compiler/src/dmd/backend/codeview.d deleted file mode 100644 index a2c5f977781f..000000000000 --- a/compiler/src/dmd/backend/codeview.d +++ /dev/null @@ -1,1496 +0,0 @@ -/** - * CodeView / PDB symbolic debug info generation. - * - * This module generates the `.debug$S` (symbols) and `.debug$T` (types) - * sections for Win64 MS-COFF objects. It merges the CodeView 4 record - * declarations, the legacy CodeView 8 emitter, and the modern CodeView/PDB - * record set documented by LLVM (https://llvm.org/docs/PDB/) and Microsoft - * (https://github.com/microsoft/microsoft-pdb) into a single emitter. - * - * The shared type-record pool in cgcv.d/cv4-era helpers is reused via - * `cv_typidx`, so D-specific type records (dynamic arrays, delegates, - * associative arrays) stay shared. - * - * Compiler implementation of the - * $(LINK2 https://www.dlang.org, D programming language). - * - * Copyright: Copyright (C) 2012-2026 by The D Language Foundation, All Rights Reserved - * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) - * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) - * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/backend/codeview.d, backend/codeview.d) - * Documentation: https://dlang.org/phobos/dmd_backend_codeview.html - * Coverage: https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/backend/codeview.d - */ - -module dmd.backend.codeview; - -import core.stdc.stdio; -import core.stdc.stdlib; -import core.stdc.string; -extern (C) nothrow char* getcwd(char*, size_t); - -import dmd.backend.cc; -import dmd.backend.cdef; -import dmd.backend.cgcv; -import dmd.backend.code; -import dmd.backend.x86.code_x86; -import dmd.backend.mem; -import dmd.backend.global : getFileContentsCallback; -import dmd.backend.mscoffobj; -import dmd.backend.obj; -import dmd.backend.oper; -import dmd.common.outbuffer; -import dmd.backend.rtlsym; -import dmd.backend.symbol : globsym; -import dmd.backend.ty; -import dmd.backend.type; -import dmd.backend.dvarstats; - - -nothrow: -@safe: - -/* ======================================================================== */ -/* CodeView record constants (formerly backend/cv4.d). */ -/* ======================================================================== */ - -enum OEM = 0x42; // Digital Mars OEM number (picked at random) - -// Symbol Indices -enum -{ - S_COMPILE = 1, - S_REGISTER = 2, - S_CONST = 3, - S_UDT = 4, - S_SSEARCH = 5, - S_END = 6, - S_SKIP = 7, - S_CVRESERVE = 8, - S_OBJNAME = 9, - S_ENDARG = 0x0A, - S_COBOLUDT = 0x0B, - S_MANYREG = 0x0C, - S_RETURN = 0x0D, - S_ENTRYTHIS = 0x0E, - S_TDBNAME = 0x0F, - - S_BPREL16 = 0x100, - S_LDATA16 = 0x101, - S_GDATA16 = 0x102, - S_PUB16 = 0x103, - S_LPROC16 = 0x104, - S_GPROC16 = 0x105, - S_THUNK16 = 0x106, - S_BLOCK16 = 0x107, - S_WITH16 = 0x108, - S_LABEL16 = 0x109, - S_CEXMODEL16 = 0x10A, - S_VFTPATH16 = 0x10B, - - S_BPREL32 = 0x200, - S_LDATA32 = 0x201, - S_GDATA32 = 0x202, - S_PUB32 = 0x203, - S_LPROC32 = 0x204, - S_GPROC32 = 0x205, - S_THUNK32 = 0x206, - S_BLOCK32 = 0x207, - S_WITH32 = 0x208, - S_LABEL32 = 0x209, - S_CEXMODEL32 = 0x20A, - S_VFTPATH32 = 0x20B, - - /************** Added Since CV4 *********************/ - - S_REGISTER_V2 = 0x1001, - S_CONSTANT_V2 = 0x1002, - S_UDT_V2 = 0x1003, - S_COBOLUDT_V2 = 0x1004, - S_MANYREG_V2 = 0x1005, - S_BPREL_V2 = 0x1006, - S_LDATA_V2 = 0x1007, - S_GDATA_V2 = 0x1008, - S_PUB_V2 = 0x1009, - S_LPROC_V2 = 0x100A, - S_GPROC_V2 = 0x100B, - S_VFTTABLE_V2 = 0x100C, - S_REGREL_V2 = 0x100D, - S_LTHREAD_V2 = 0x100E, - S_GTHREAD_V2 = 0x100F, - S_FUNCINFO_V2 = 0x1012, - S_COMPILAND_V2 = 0x1013, - - S_COMPILAND_V3 = 0x1101, - S_THUNK_V3 = 0x1102, - S_BLOCK_V3 = 0x1103, - S_LABEL_V3 = 0x1105, - S_REGISTER_V3 = 0x1106, - S_CONSTANT_V3 = 0x1107, - S_UDT_V3 = 0x1108, - S_BPREL_V3 = 0x110B, - S_LDATA_V3 = 0x110C, - S_GDATA_V3 = 0x110D, - S_PUB_V3 = 0x110E, - S_LPROC_V3 = 0x110F, - S_GPROC_V3 = 0x1110, - S_BPREL_XXXX_V3 = 0x1111, - S_MSTOOL_V3 = 0x1116, - S_PUB_FUNC1_V3 = 0x1125, - S_PUB_FUNC2_V3 = 0x1127, - S_SECTINFO_V3 = 0x1136, - S_SUBSECTINFO_V3 = 0x1137, - S_ENTRYPOINT_V3 = 0x1138, - S_SECUCOOKIE_V3 = 0x113A, - S_MSTOOLINFO_V3 = 0x113C, - S_MSTOOLENV_V3 = 0x113D, -} - -// Leaf Indices -enum -{ - LF_MODIFIER = 1, - LF_POINTER = 2, - LF_ARRAY = 3, - LF_CLASS = 4, - LF_STRUCTURE = 5, - LF_UNION = 6, - LF_ENUM = 7, - LF_PROCEDURE = 8, - LF_MFUNCTION = 9, - LF_VTSHAPE = 0x0A, - LF_COBOL0 = 0x0B, - LF_COBOL1 = 0x0C, - LF_BARRAY = 0x0D, - LF_LABEL = 0x0E, - LF_NULL = 0x0F, - LF_NOTTRAN = 0x10, - LF_DIMARRAY = 0x11, - LF_VFTPATH = 0x12, - LF_PRECOMP = 0x13, - LF_ENDPRECOMP = 0x14, - LF_OEM = 0x15, - LF_TYPESERVER = 0x16, - - // D extensions (not used, causes linker to fail) - LF_DYN_ARRAY = 0x17, - LF_ASSOC_ARRAY = 0x18, - LF_DELEGATE = 0x19, - - LF_SKIP = 0x200, - LF_ARGLIST = 0x201, - LF_DEFARG = 0x202, - LF_LIST = 0x203, - LF_FIELDLIST = 0x204, - LF_DERIVED = 0x205, - LF_BITFIELD = 0x206, - LF_METHODLIST = 0x207, - LF_DIMCONU = 0x208, - LF_DIMCONLU = 0x209, - LF_DIMVARU = 0x20A, - LF_DIMVARLU = 0x20B, - LF_REFSYM = 0x20C, - - LF_BCLASS = 0x400, - LF_VBCLASS = 0x401, - LF_IVBCLASS = 0x402, - LF_ENUMERATE = 0x403, - LF_FRIENDFCN = 0x404, - LF_INDEX = 0x405, - LF_MEMBER = 0x406, - LF_STMEMBER = 0x407, - LF_METHOD = 0x408, - LF_NESTTYPE = 0x409, - LF_VFUNCTAB = 0x40A, - LF_FRIENDCLS = 0x40B, - - LF_NUMERIC = 0x8000, - LF_CHAR = 0x8000, - LF_SHORT = 0x8001, - LF_USHORT = 0x8002, - LF_LONG = 0x8003, - LF_ULONG = 0x8004, - LF_REAL32 = 0x8005, - LF_REAL64 = 0x8006, - LF_REAL80 = 0x8007, - LF_REAL128 = 0x8008, - LF_QUADWORD = 0x8009, - LF_UQUADWORD = 0x800A, - LF_REAL48 = 0x800B, - - LF_COMPLEX32 = 0x800C, - LF_COMPLEX64 = 0x800D, - LF_COMPLEX80 = 0x800E, - LF_COMPLEX128 = 0x800F, - - LF_VARSTRING = 0x8010, - - /************** Added Since CV4 *********************/ - - LF_MODIFIER_V2 = 0x1001, - LF_POINTER_V2 = 0x1002, - LF_ARRAY_V2 = 0x1003, - LF_CLASS_V2 = 0x1004, - LF_STRUCTURE_V2 = 0x1005, - LF_UNION_V2 = 0x1006, - LF_ENUM_V2 = 0x1007, - LF_PROCEDURE_V2 = 0x1008, - LF_MFUNCTION_V2 = 0x1009, - LF_COBOL0_V2 = 0x100A, - LF_BARRAY_V2 = 0x100B, - LF_DIMARRAY_V2 = 0x100C, - LF_VFTPATH_V2 = 0x100D, - LF_PRECOMP_V2 = 0x100E, - LF_OEM_V2 = 0x100F, - - LF_SKIP_V2 = 0x1200, - LF_ARGLIST_V2 = 0x1201, - LF_DEFARG_V2 = 0x1202, - LF_FIELDLIST_V2 = 0x1203, - LF_DERIVED_V2 = 0x1204, - LF_BITFIELD_V2 = 0x1205, - LF_METHODLIST_V2 = 0x1206, - LF_DIMCONU_V2 = 0x1207, - LF_DIMCONLU_V2 = 0x1208, - LF_DIMVARU_V2 = 0x1209, - LF_DIMVARLU_V2 = 0x120A, - - LF_BCLASS_V2 = 0x1400, - LF_VBCLASS_V2 = 0x1401, - LF_IVBCLASS_V2 = 0x1402, - LF_FRIENDFCN_V2 = 0x1403, - LF_INDEX_V2 = 0x1404, - LF_MEMBER_V2 = 0x1405, - LF_STMEMBER_V2 = 0x1406, - LF_METHOD_V2 = 0x1407, - LF_NESTTYPE_V2 = 0x1408, - LF_VFUNCTAB_V2 = 0x1409, - LF_FRIENDCLS_V2 = 0x140A, - LF_ONEMETHOD_V2 = 0x140B, - LF_VFUNCOFF_V2 = 0x140C, - LF_NESTTYPEEX_V2 = 0x140D, - - LF_ENUMERATE_V3 = 0x1502, - LF_ARRAY_V3 = 0x1503, - LF_CLASS_V3 = 0x1504, - LF_STRUCTURE_V3 = 0x1505, - LF_UNION_V3 = 0x1506, - LF_ENUM_V3 = 0x1507, - LF_MEMBER_V3 = 0x150D, - LF_STMEMBER_V3 = 0x150E, - LF_METHOD_V3 = 0x150F, - LF_NESTTYPE_V3 = 0x1510, - LF_ONEMETHOD_V3 = 0x1511, -} - -/* ======================================================================== */ -/* Modern CodeView (CV8 / PDB) records not present in the CV4 record set. */ -/* ======================================================================== */ - -// Symbol records -enum -{ - S_COMPILE3 = 0x113C, // modern compiland flags/version (aka S_MSTOOLINFO_V3) - S_ENVBLOCK = 0x113D, // build environment (aka S_MSTOOLENV_V3) - S_BUILDINFO = 0x114C, // references an LF_BUILDINFO id record - S_FRAMEPROC = 0x1012, // per-function frame info (aka S_FUNCINFO_V2) - S_REGREL_V3 = 0x1111, // register-relative addressing (aka S_BPREL_XXXX_V3) - S_LTHREAD_V3 = 0x1112, // local thread-local storage - S_GTHREAD_V3 = 0x1113, // global thread-local storage -} - -// ID-stream type leaves -enum -{ - LF_FUNC_ID = 0x1601, // ties a function type to its name - LF_STRING_ID = 0x1605, // interned string -> type index - LF_BUILDINFO = 0x1603, // cwd, tool, source, pdb, args - LF_UDT_SRC_LINE = 0x1606, // source file/line where a UDT is defined -} - -// .debug$S subsection kinds -enum -{ - DEBUG_S_SYMBOLS = 0xF1, - DEBUG_S_LINES = 0xF2, - DEBUG_S_STRINGTABLE = 0xF3, - DEBUG_S_FILECHKSMS = 0xF4, -} - -// File checksum kinds -enum -{ - CHKSUM_NONE = 0, - CHKSUM_MD5 = 1, - CHKSUM_SHA1 = 2, - CHKSUM_SHA256 = 3, -} - -// S_FRAMEPROC flags -enum -{ - CV_FRAME_HASINLASM = 1 << 3, // function has inline asm - CV_FRAME_HASEH = 1 << 4, // function has exception handling - CV_FRAME_SECURITY = 1 << 8, // stack security cookie checks - CV_FRAME_OPTSPEED = 1 << 20, // optimized for speed - CV_FRAME_LOCALBP_RBP = 2 << 14, // locals addressed relative to RBP/EBP - CV_FRAME_PARAMBP_RBP = 2 << 16, // params addressed relative to RBP/EBP -} - -// Mark a line-number entry as a statement (not an expression) -enum CV_LINE_STATEMENT = 0x80000000; - -// CodeView register encodings for base-pointer relative records -enum -{ - CV_REG_EBP = 22, // x86 EBP - CV_AMD64_RBP = 334, // x64 RBP -} - -// if symbols get longer than 65500 bytes, the linker reports corrupt debug info or exits with -// 'fatal error LNK1318: Unexpected PDB error; RPC (23) '(0x000006BA)' -enum CV_MAX_SYMBOL_LENGTH = 0xffd8; - -// CV_PUBSYMFLAGS -enum PUB_FUNCTION = 0x00000002; - -/* ======================================================================== */ -/* State */ -/* ======================================================================== */ - -// Determine if this Symbol is stored in a COMDAT -@trusted -private bool symbol_iscomdat4(Symbol* s) -{ - return s.Sclass == SC.comdat || - config.flags2 & CFG2comdat && s.Sclass == SC.inline || - config.flags4 & CFG4allcomdat && s.Sclass == SC.global; -} - -// The "F1" section, which is the symbols -private __gshared OutBuffer* F1_buf; - -// The "F2" section, which is the line numbers -private __gshared OutBuffer* F2_buf; - -// The "F3" section, which is global and a string table of source file names. -private __gshared OutBuffer* F3_buf; - -// The "F4" section, which is global and a list of info about source files. -private __gshared OutBuffer* F4_buf; - -/* Fixups that go into F1 section - */ -struct F1_Fixups -{ - Symbol* s; - uint offset; - uint value; -} - -private __gshared OutBuffer* F1fixup; // array of F1_Fixups - -/* Struct in which to collect per-function data, for later emission - * into .debug$S. - */ -struct FuncData -{ - Symbol* sfunc; - uint section_length; - const(char)* srcfilename; - uint srcfileoff; - uint linepairstart; // starting byte index of offset/line pairs in linebuf[] - uint linepairbytes; // number of bytes for offset/line pairs - uint linepairsegment; // starting byte index of filename segment for offset/line pairs - OutBuffer* f1buf; - OutBuffer* f1fixup; -} - -__gshared FuncData currentfuncdata; - -private __gshared OutBuffer* funcdata; // array of FuncData's - -private __gshared OutBuffer* linepair; // array of offset/line pairs - -/* ======================================================================== */ -/* Name encoding */ -/* ======================================================================== */ - -private @trusted -void cv_writename(OutBuffer* buf, const(char)* name, size_t len) -{ - if (!(config.flags2 & CFG2gms)) - { - buf.writen(name, len); - return; - } - - const(char)* start = name; - const(char)* cur = strchr(start, '.'); - const(char)* end = start + len; - while (cur != null) - { - if (cur >= end) - { - buf.writen(start, end - start); - return; - } - buf.writen(start, cur - start); - buf.writeByte('@'); - start = cur + 1; - if (start >= end) - return; - cur = strchr(start, '.'); - } - buf.writen(start, end - start); -} - -/* ======================================================================== */ -/* File lifecycle */ -/* ======================================================================== */ - -/************************************************ - * Called at the start of an object file generation. - * One source file can generate multiple object files; this starts an object file. - * Input: - * filename source file name - */ -@trusted -void cv_initfile(const(char)* filename) -{ - void initBuf(ref OutBuffer* ptr) - { - if (!ptr) - { - ptr = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); - ptr.reserve(1024); - } - ptr.reset(); - } - - initBuf(F1_buf); - initBuf(F1fixup); - initBuf(F2_buf); - - initBuf(F3_buf); - F3_buf.writeByte(0); // first "filename" - - initBuf(F4_buf); - initBuf(funcdata); - initBuf(linepair); - - memset(¤tfuncdata, 0, currentfuncdata.sizeof); - currentfuncdata.f1buf = F1_buf; - currentfuncdata.f1fixup = F1fixup; - - cv_init(); -} - -/************************************************ - * Called at the start of a module. - * Note that there can be multiple modules in one object file. - * cv_initfile() must be called first. - */ -void cv_initmodule(const(char)* filename, const(char)* modulename) -{ -} - -@trusted -void cv_termmodule() -{ - assert(config.objfmt == OBJ_MSCOFF); -} - -@trusted -void cv_termfile(const(char)[] objfilename) -{ - int seg = MsCoffObj_seg_debugS(); - - uint value = 4; // CV signature C13 - objmod.bytes(seg, 0, (cast(void*)&value)[0 .. 4]); - - auto buf = OutBuffer(1024); - - // S_OBJNAME - { - size_t len = objfilename.length; - buf.write16(cast(int)(2 + 4 + len + 1)); - buf.write16(S_COMPILAND_V3); - buf.write32(0); // signature - buf.write(objfilename.ptr, cast(uint)(len + 1)); - } - - // S_COMPILE3 - { - // Honor the configured language/compiler instead of hardcoding it: - // emit the D language index unless the debug info was requested for - // non-DMD (Microsoft) debuggers (-gc sets CFG2gms), and report the - // actual compiler version string (dmd/ldc/gdc) via config._version. - const uint flags = config.flags2 & CFG2gms ? 0 : 'D'; // iLanguage in low byte - const(char)[] ver = config._version; - buf.write16(cast(int)(2 + 4 + 2 + 8 + 8 + ver.length + 1)); - buf.write16(S_COMPILE3); - buf.write32(flags); - buf.write16(I64 ? 0xD0 : 0x07); // machine: AMD64 / x86 - buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // FE ver - buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // BE ver - buf.write(ver.ptr, cast(uint)ver.length); - buf.writeByte(0); - } - - cv_writesection(seg, DEBUG_S_SYMBOLS, &buf); - - // S_ENVBLOCK: build environment (cwd, tool), double-null terminated - { - auto ebuf = OutBuffer(128); - char[260] cwd = 0; - if (!getcwd(cwd.ptr, cwd.sizeof)) - cwd[0] = 0; - size_t cwdlen = strlen(cwd.ptr); - uint rec = cast(uint)(2 + 1 + 4 + cwdlen + 1 + 4 + 1 + 1); - ebuf.write16(cast(int)rec); - ebuf.write16(S_ENVBLOCK); - ebuf.writeByte(0); // flags - ebuf.write("cwd\0".ptr, 4); - ebuf.write(cwd.ptr, cast(uint)(cwdlen + 1)); - ebuf.write("cmd\0".ptr, 4); - ebuf.writeByte(0); // empty cmd value - ebuf.writeByte(0); // terminating double-null - cv_writesection(seg, DEBUG_S_SYMBOLS, &ebuf); - } - - // S_BUILDINFO referencing an LF_BUILDINFO id record - { - auto bbuf = OutBuffer(64); - idx_t biidx = cv_buildinfo(); - bbuf.write16(2 + 4); - bbuf.write16(S_BUILDINFO); - bbuf.write32(biidx); - cv_writesection(seg, DEBUG_S_SYMBOLS, &bbuf); - } - - uint length = cast(uint)funcdata.length(); - ubyte* p = funcdata.buf; - for (uint u = 0; u < length; u += FuncData.sizeof) - { - FuncData* fd = cast(FuncData*)(p + u); - - F2_buf.reset(); - F2_buf.write32(cast(uint)fd.sfunc.Soffset); - F2_buf.write32(0); - F2_buf.write32(fd.section_length); - F2_buf.write(linepair.buf + fd.linepairstart, fd.linepairbytes); - - int f2seg = seg; - if (symbol_iscomdat4(fd.sfunc)) - { - f2seg = MsCoffObj_seg_debugS_comdat(fd.sfunc); - objmod.bytes(f2seg, 0, (cast(void*)&value)[0 .. 4]); - } - - uint offset = cast(uint)SegData[f2seg].SDoffset + 8; - cv_writesection(f2seg, DEBUG_S_LINES, F2_buf); - objmod.reftoident(f2seg, offset, fd.sfunc, 0, CF.seg | CF.off); - - if (f2seg != seg && fd.f1buf.length()) - { - const uint f1offset = cast(uint)SegData[f2seg].SDoffset; - cv_writesection(f2seg, DEBUG_S_SYMBOLS, fd.f1buf); - - const uint fixupLength = cast(uint)fd.f1fixup.length(); - ubyte* pfixup = fd.f1fixup.buf; - for (uint v = 0; v < fixupLength; v += F1_Fixups.sizeof) - { - F1_Fixups* f = cast(F1_Fixups*)(pfixup + v); - objmod.reftoident(f2seg, f1offset + 8 + f.offset, f.s, f.value, CF.seg | CF.off); - } - } - } - - if (F3_buf.length() > 1) - cv_writesection(seg, DEBUG_S_STRINGTABLE, F3_buf); - - if (F4_buf.length() > 0) - cv_writesection(seg, DEBUG_S_FILECHKSMS, F4_buf); - - if (F1_buf.length()) - { - uint f1offset = cast(uint)SegData[seg].SDoffset; - cv_writesection(seg, DEBUG_S_SYMBOLS, F1_buf); - - length = cast(uint)F1fixup.length(); - p = F1fixup.buf; - for (uint u = 0; u < length; u += F1_Fixups.sizeof) - { - F1_Fixups* f = cast(F1_Fixups*)(p + u); - objmod.reftoident(seg, f1offset + 8 + f.offset, f.s, f.value, CF.seg | CF.off); - } - } - - cv_term(); // .debug$T -} - -/* ======================================================================== */ -/* Functions */ -/* ======================================================================== */ - -/****************************************** - * Called at the start of a function. - */ -@trusted -void cv_func_start(Symbol* sfunc) -{ - currentfuncdata.sfunc = sfunc; - currentfuncdata.section_length = 0; - currentfuncdata.srcfilename = null; - currentfuncdata.linepairstart += currentfuncdata.linepairbytes; - currentfuncdata.linepairbytes = 0; - currentfuncdata.f1buf = F1_buf; - currentfuncdata.f1fixup = F1fixup; - if (symbol_iscomdat4(sfunc)) - { - // This leaks memory - currentfuncdata.f1buf = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); - currentfuncdata.f1buf.reserve(128); - currentfuncdata.f1fixup = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); - currentfuncdata.f1fixup.reserve(128); - } - varStats_startFunction(); -} - -@trusted -void cv_func_term(Symbol* sfunc) -{ - assert(currentfuncdata.sfunc == sfunc); - currentfuncdata.section_length = cast(uint)sfunc.Ssize; - funcdata.write(¤tfuncdata, currentfuncdata.sizeof); - - assert(tyfunc(sfunc.ty())); - idx_t typidx; - func_t* fn = sfunc.Sfunc; - if (fn.Fclass) - { - // Member functions get a dedicated LF_MFUNCTION_V2 type record that - // records the enclosing class and the `this` pointer type. This info - // isn't available inside cv_typidx, so generate it here. - uint nparam; - const ubyte call = cv4_callconv(sfunc.Stype); - const idx_t paramidx = cv4_arglist(sfunc.Stype, &nparam); - const uint next = cv4_typidx(sfunc.Stype.Tnext); - type* classtype = cast(type*)fn.Fclass; - const uint classidx = cv4_typidx(classtype); - type* tp = type_allocn(TYnptr, classtype); - const uint thisidx = cv4_typidx(tp); - debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4 + 1 + 1 + 2 + 4 + 4); - TOWORD(d.data.ptr, LF_MFUNCTION_V2); - TOLONG(d.data.ptr + 2, next); // return type - TOLONG(d.data.ptr + 6, classidx); // class type - TOLONG(d.data.ptr + 10, thisidx); // this type - d.data.ptr[14] = call; - d.data.ptr[15] = 0; // reserved - TOWORD(d.data.ptr + 16, nparam); - TOLONG(d.data.ptr + 18, paramidx); - TOLONG(d.data.ptr + 22, 0); // this adjust - typidx = cv_debtyp(d); - } - else - typidx = cv_typidx(sfunc.Stype); - - const(char)* id = sfunc.prettyIdent ? sfunc.prettyIdent : prettyident(sfunc); - size_t len = strlen(id); - if (len > CV_MAX_SYMBOL_LENGTH) - len = CV_MAX_SYMBOL_LENGTH; - - auto buf = currentfuncdata.f1buf; - buf.reserve(cast(uint)(2 + 2 + 4 * 7 + 6 + 1 + len + 1)); - buf.write16n(cast(int)(2 + 4 * 7 + 6 + 1 + len + 1)); - buf.write16n(sfunc.Sclass == SC.static_ ? S_LPROC_V3 : S_GPROC_V3); - buf.write32(0); // parent - buf.write32(0); // pend - buf.write32(0); // pnext - buf.write32(cast(uint)currentfuncdata.section_length); - buf.write32(cast(uint)cgstate.startoffset); // prolog size - buf.write32(cast(uint)cgstate.retoffset); // epilog offset - buf.write32(typidx); - - F1_Fixups f1f; - f1f.s = sfunc; - f1f.offset = cast(uint)buf.length(); - f1f.value = 0; - currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); - buf.write32(0); - buf.write16n(0); - buf.writeByte(0); // flags - buf.writen(id, len); - buf.writeByte(0); - - // S_FRAMEPROC: describe this function's stack frame - { - // Locals and parameters are addressed relative to the frame pointer - // (RBP/EBP), matching the S_REGREL_V3 records emitted for them. - uint frameflags = CV_FRAME_LOCALBP_RBP | CV_FRAME_PARAMBP_RBP; - if (cgstate.anyiasm) - frameflags |= CV_FRAME_HASINLASM; - if (cgstate.usednteh) - frameflags |= CV_FRAME_HASEH; - if (config.flags2 & CFG2stomp) - frameflags |= CV_FRAME_SECURITY; - if (config.flags4 & CFG4speed) - frameflags |= CV_FRAME_OPTSPEED; - - buf.write16(2 + 4 + 4 + 4 + 4 + 4 + 2 + 4); - buf.write16(S_FRAMEPROC); - buf.write32(cast(uint)localsize); // cbFrame: total frame size - buf.write32(0); // cbPad - buf.write32(0); // offPad - buf.write32(0); // cbSaveRegs - buf.write32(0); // offExHdlr - buf.write16(0); // sectExHdlr - buf.write32(frameflags); // flags - } - - struct cvblk - { - nothrow: - // record for CV record S_BLOCK_V3 - struct block_v3_data - { - ushort len; - ushort id; - uint pParent; - uint pEnd; - uint length; - uint offset; - ushort seg; - ubyte[1] name; - } - static void endArgs() - { - auto b = currentfuncdata.f1buf; - b.write16(2); - b.write16(S_ENDARG); - } - static void beginBlock(int offset, int length) - { - auto b = currentfuncdata.f1buf; - uint soffset = cast(uint)b.length(); - // parent and end to be filled by linker - block_v3_data block32 = { block_v3_data.sizeof - 2, S_BLOCK_V3, 0, 0, length, offset, 0, [ 0 ] }; - b.write(&block32, block32.sizeof); - size_t offOffset = cast(char*)&block32.offset - cast(char*)&block32; - F1_Fixups f; - f.s = currentfuncdata.sfunc; - f.offset = cast(uint)(soffset + offOffset); - f.value = offset; - currentfuncdata.f1fixup.write(&f, f.sizeof); - } - static void endBlock() - { - auto b = currentfuncdata.f1buf; - b.write16(2); - b.write16(S_END); - } - } - varStats_writeSymbolTable(sfunc, globsym, &cv_symbol, &cvblk.endArgs, &cvblk.beginBlock, &cvblk.endBlock); - - // No S_RETURN record: its register list uses single-byte CodeView register - // codes, which cannot encode x64 return registers (e.g. CV_AMD64_RAX = 328). - // Both MSVC and the legacy CV8 path omit it, so a real record can't be produced here. - - buf.write16(2); - buf.write16(S_END); - - currentfuncdata.f1buf = F1_buf; - currentfuncdata.f1fixup = F1fixup; -} - -/* ======================================================================== */ -/* Line numbers */ -/* ======================================================================== */ - -@trusted -void cv_linnum(Srcpos srcpos, uint offset) -{ - const sfilename = srcpos.Sfilename; - if (!sfilename) - return; - - varStats_recordLineOffset(srcpos, offset); - - __gshared uint lastoffset; - __gshared uint lastlinnum; - - if (!currentfuncdata.srcfilename || - (currentfuncdata.srcfilename != sfilename && strcmp(currentfuncdata.srcfilename, sfilename))) - { - currentfuncdata.srcfilename = sfilename; - uint srcfileoff = cv_addfile(sfilename); - currentfuncdata.linepairsegment = currentfuncdata.linepairstart + currentfuncdata.linepairbytes; - linepair.write32(srcfileoff); - linepair.write32(0); - linepair.write32(12); - currentfuncdata.linepairbytes += 12; - } - else if (offset <= lastoffset || srcpos.Slinnum == lastlinnum) - return; - - lastoffset = offset; - lastlinnum = srcpos.Slinnum; - linepair.write32(offset); - linepair.write32(srcpos.Slinnum | CV_LINE_STATEMENT); // mark as statement, not expression - currentfuncdata.linepairbytes += 8; - - auto segmentbytes = currentfuncdata.linepairstart + currentfuncdata.linepairbytes - currentfuncdata.linepairsegment; - auto segmentheader = cast(uint*)(linepair.buf + currentfuncdata.linepairsegment); - segmentheader[1] = (segmentbytes - 12) / 8; - segmentheader[2] = segmentbytes; -} - -/* ======================================================================== */ -/* Source files (F3 names + F4 checksums via blake3) */ -/* ======================================================================== */ - -/********************************************** - * Add source file, if it isn't already there. - * Return offset into F4. - */ -@trusted -uint cv_addfile(const(char)* filename) -{ - uint length = cast(uint)F3_buf.length(); - ubyte* p = F3_buf.buf; - size_t len = strlen(filename); - - // ensure the filename is absolute to help the debugger to find the source - // without having to know the working directory during compilation - __gshared char[260] cwd = 0; - __gshared uint cwdlen; - bool abs = (*filename == '\\') || (*filename == '/') || - (*filename && filename[1] == ':'); - - if (!abs && cwd[0] == 0) - { - if (getcwd(cwd.ptr, cwd.sizeof)) - { - cwdlen = cast(uint)strlen(cwd.ptr); - if (cwd[cwdlen - 1] != '\\' && cwd[cwdlen - 1] != '/') - cwd[cwdlen++] = '\\'; - } - } - uint off = 1; - while (off + len < length) - { - if (!abs) - { - if (memcmp(p + off, cwd.ptr, cwdlen) == 0 && - memcmp(p + off + cwdlen, filename, len + 1) == 0) - goto L1; - } - else if (memcmp(p + off, filename, len + 1) == 0) - goto L1; - off += strlen(cast(const(char)*)(p + off)) + 1; - } - off = length; - if (!abs) - F3_buf.write(cwd.ptr, cwdlen); - F3_buf.write(filename, cast(uint)(len + 1)); - -L1: - length = cast(uint)F4_buf.length(); - p = F4_buf.buf; - uint u = 0; - while (u + 6 <= length) - { - if (off == *cast(uint*)(p + u)) - return u; - u += 4; // file name offset - ubyte cklen = *(p + u); // checksum size - u += 2; // skip size + kind bytes - u += cklen; // skip checksum bytes - u = (u + 3) & ~3; // realign to 4 - } - - // Not present; append a checksum computed over the source file's *content* - // (not over its name) so debuggers can verify the source matches. - F4_buf.write32(off); - ubyte[32] hash = void; - if (cv_filehash(filename, hash)) - { - F4_buf.writeByte(32); // checksum size - F4_buf.writeByte(CHKSUM_SHA256); // 32-byte checksum slot - F4_buf.write(hash.ptr, 32); - } - else - { - F4_buf.writeByte(0); // no checksum available - F4_buf.writeByte(CHKSUM_NONE); - } - while (F4_buf.length() & 3) - F4_buf.writeByte(0); - return length; -} - -/* Compute a blake3 hash over the *content* of the named source file. - * The bytes are fetched from the front-end's FileManager cache (populated when - * the module was read) via a callback, avoiding a second read from disk. - * Returns: true on success (hash filled in), false if the content is unavailable. - */ -private @trusted -bool cv_filehash(const(char)* filename, ref ubyte[32] hash) -{ - if (!getFileContentsCallback) - return false; - - size_t length; - const(ubyte)* data = getFileContentsCallback(filename, length); - if (!data) - return false; - - import dmd.common.blake3; - hash = blake3(data[0 .. length]); - return true; -} - -private @trusted -void cv_writesection(int seg, uint type, OutBuffer* buf) -{ - /* Write out as: - * bytes desc - * -------+---- - * 4 type - * 4 length - * length data - * pad pad to 4 byte boundary - */ - uint off = cast(uint)SegData[seg].SDoffset; - objmod.bytes(seg, off, (cast(void*)&type)[0 .. 4]); - uint length = cast(uint)buf.length(); - objmod.bytes(seg, off + 4, (cast(void*)&length)[0 .. 4]); - objmod.bytes(seg, off + 8, buf.buf[0 .. length]); - uint pad = ((length + 3) & ~3) - length; - objmod.lidata(seg, off + 8 + length, pad); -} - -/* ======================================================================== */ -/* Symbols */ -/* ======================================================================== */ - -@trusted -void cv_symbol(Symbol* s) -{ - if (s.Sflags & SFLnodebug) - return; - - idx_t typidx = cv_typidx(s.Stype); - const(char)* id = s.prettyIdent ? s.prettyIdent : prettyident(s); - size_t len = strlen(id); - if (len > CV_MAX_SYMBOL_LENGTH) - len = CV_MAX_SYMBOL_LENGTH; - - F1_Fixups f1f; - f1f.value = 0; - auto buf = currentfuncdata.f1buf; - - uint sr; - uint base; - switch (s.Sclass) - { - case SC.parameter: - case SC.regpar: - case SC.shadowreg: - if (s.Sfl == FL.reg) - { - s.Sfl = FL.para; - cv_symbol(s); - s.Sfl = FL.reg; - goto case_register; - } - base = cast(uint)(cgstate.Para.size - cgstate.BPoff); - goto L1; - - case SC.auto_: - if (s.Sfl == FL.reg) - goto case_register; - case_auto: - base = cast(uint)cgstate.Auto.size; - L1: - if (s.Sscope) - break; - buf.reserve(cast(uint)(2 + 2 + 4 + 4 + 2 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + 4 + 2 + len + 1)); - buf.write16n(S_REGREL_V3); - buf.write32(cast(uint)(s.Soffset + base + cgstate.BPoff)); - buf.write32(typidx); - buf.write16n(I64 ? CV_AMD64_RBP : CV_REG_EBP); // relative to RBP/EBP - cv_writename(buf, id, len); - buf.writeByte(0); - break; - - case SC.bprel: - base = -cgstate.BPoff; - goto L1; - - case SC.fastpar: - if (s.Sfl != FL.reg) - { - base = cast(uint)cgstate.Fast.size; - goto L1; - } - goto L2; - - case SC.register: - if (s.Sfl != FL.reg) - goto case_auto; - goto case; - - case SC.pseudo: - case_register: - L2: - buf.reserve(cast(uint)(2 + 2 + 4 + 2 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + 2 + len + 1)); - buf.write16n(S_REGISTER_V3); - buf.write32(typidx); - buf.write16n(cv_regnum(s)); - cv_writename(buf, id, len); - buf.writeByte(0); - break; - - case SC.extern_: - break; - - case SC.static_: - case SC.locstat: - sr = S_LDATA_V3; - goto Ldata; - - case SC.global: - case SC.comdat: - case SC.comdef: - sr = S_GDATA_V3; - Ldata: - if (s.ty() & mTYthread) // full TLS support - sr = (sr == S_GDATA_V3) ? S_GTHREAD_V3 : S_LTHREAD_V3; - - buf.reserve(cast(uint)(2 + 2 + 4 + 6 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + 6 + len + 1)); - buf.write16n(sr); - buf.write32(typidx); - f1f.s = s; - f1f.offset = cast(uint)buf.length(); - currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); - buf.write32(0); - buf.write16n(0); - cv_writename(buf, id, len); - buf.writeByte(0); - break; - - default: - break; - } -} - -/******************************************* - * Put out a name for a user defined type. - * Input: - * id the name - * typidx and its type - */ -@trusted -void cv_udt_symbol(const(char)* id, idx_t typidx) -{ - auto buf = currentfuncdata.f1buf; - size_t len = strlen(id); - if (len > CV_MAX_SYMBOL_LENGTH) - len = CV_MAX_SYMBOL_LENGTH; - buf.reserve(cast(uint)(2 + 2 + 4 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + len + 1)); - buf.write16n(S_UDT_V3); - buf.write32(typidx); - cv_writename(buf, id, len); - buf.writeByte(0); -} - -/********************************************* - * Get Codeview register number for symbol s. - */ -int cv_regnum(Symbol* s) -{ - int reg = s.Sreglsw; - assert(s.Sfl == FL.reg); - if ((1 << reg) & XMMREGS) - return reg - XMM0 + 154; - switch (type_size(s.Stype)) - { - case 1: - if (reg < 4) - reg += 1; - else if (reg >= 4 && reg < 8) - reg += 324 - 4; - else - reg += 344 - 4; - break; - - case 2: - if (reg < 8) - reg += 9; - else - reg += 352 - 8; - break; - - case 4: - if (reg < 8) - reg += 17; - else - reg += 360 - 8; - break; - - case 8: - reg += 328; - break; - - default: - reg = 0; - break; - } - return reg; -} - -/*************************************** - * Put out a forward ref for structs, unions, and classes. - * Only put out the real definitions with toDebug(). - */ -@trusted -idx_t cv_fwdref(Symbol* s) -{ - assert(config.fulltypes == CV8); - struct_t* st = s.Sstruct; - uint leaf; - uint numidx; - if (st.Sflags & STRunion) - { - leaf = LF_UNION_V3; - numidx = 10; - } - else if (st.Sflags & STRclass) - { - leaf = LF_CLASS_V3; - numidx = 18; - } - else - { - leaf = LF_STRUCTURE_V3; - numidx = 18; - } - uint len = numidx + cv4_numericbytes(0); - int idlen = cast(int)strlen(s.Sident.ptr); - - if (idlen > CV_MAX_SYMBOL_LENGTH) - idlen = CV_MAX_SYMBOL_LENGTH; - - debtyp_t* d = debtyp_alloc(len + idlen + 1); - TOWORD(d.data.ptr, leaf); - TOWORD(d.data.ptr + 2, 0); // number of fields - TOWORD(d.data.ptr + 4, 0x80); // property - TOLONG(d.data.ptr + 6, 0); // field list - if (leaf == LF_CLASS_V3 || leaf == LF_STRUCTURE_V3) - { - TOLONG(d.data.ptr + 10, 0); // dList - TOLONG(d.data.ptr + 14, 0); // vshape - } - cv4_storenumeric(d.data.ptr + numidx, 0); - cv_namestring(d.data.ptr + len, s.Sident.ptr, idlen); - d.data.ptr[len + idlen] = 0; - idx_t typidx = cv_debtyp(d); - s.Stypidx = typidx; - - return typidx; -} - -/**************************************** - * Return type index for a darray of type E[] - * Input: - * t darray type - * etypidx type index for E - */ -@trusted -idx_t cv_darray(type* t, idx_t etypidx) -{ - /* Put out a struct: - * struct dArray { - * size_t length; - * E* ptr; - * } - */ - - type* tp = type_pointer(t.Tnext); - idx_t ptridx = cv4_typidx(tp); - type_free(tp); - - __gshared const ubyte[38] fl = - [ - 0x03, 0x12, // LF_FIELDLIST_V2 - 0x0d, 0x15, // LF_MEMBER_V3 - 0x03, 0x00, // attribute - 0x23, 0x00, 0x00, 0x00, // size_t - 0x00, 0x00, // offset - 'l', 'e', 'n', 'g', 't', 'h', 0x00, - 0xf3, 0xf2, 0xf1, // align to 4-byte including length word before data - 0x0d, 0x15, - 0x03, 0x00, - 0x00, 0x00, 0x00, 0x00, // etypidx - 0x08, 0x00, - 'p', 't', 'r', 0x00, - 0xf2, 0xf1, - ]; - - debtyp_t* f = debtyp_alloc(fl.sizeof); - memcpy(f.data.ptr,fl.ptr,fl.sizeof); - TOLONG(f.data.ptr + 6, I64 ? 0x23 : 0x22); // size_t - TOLONG(f.data.ptr + 26, ptridx); - TOWORD(f.data.ptr + 30, _tysize[TYnptr]); - idx_t fieldlist = cv_debtyp(f); - - const(char)* id; - switch (t.Tnext.Tty) - { - case mTYimmutable | TYchar: - id = "string"; - break; - - case mTYimmutable | TYwchar_t: - id = "wstring"; - break; - - case mTYimmutable | TYdchar: - id = "dstring"; - break; - - default: - id = t.Tident ? t.Tident : "dArray"; - break; - } - - int idlen = cast(int)strlen(id); - - if (idlen > CV_MAX_SYMBOL_LENGTH) - idlen = CV_MAX_SYMBOL_LENGTH; - - debtyp_t* d = debtyp_alloc(20 + idlen + 1); - TOWORD(d.data.ptr, LF_STRUCTURE_V3); - TOWORD(d.data.ptr + 2, 2); // count - TOWORD(d.data.ptr + 4, 0); // property - TOLONG(d.data.ptr + 6, fieldlist); - TOLONG(d.data.ptr + 10, 0); // dList - TOLONG(d.data.ptr + 14, 0); // vtshape - TOWORD(d.data.ptr + 18, 2 * _tysize[TYnptr]); // size - cv_namestring(d.data.ptr + 20, id, idlen); - d.data.ptr[20 + idlen] = 0; - - idx_t top = cv_numdebtypes(); - idx_t debidx = cv_debtyp(d); - if(top != cv_numdebtypes()) - cv_udt_symbol(id, debidx); - - return debidx; -} - -/**************************************** - * Return type index for a delegate - * Input: - * t delegate type - * functypidx type index for pointer to function - */ -@trusted -idx_t cv_ddelegate(type* t, idx_t functypidx) -{ - /* Put out a struct: - * struct dDelegate { - * void* ptr; - * function* funcptr; - * } - */ - - type* tv = type_fake(TYnptr); - tv.Tcount++; - idx_t pvidx = cv4_typidx(tv); - type_free(tv); - - type* tp = type_pointer(t.Tnext); - idx_t ptridx = cv4_typidx(tp); - type_free(tp); - - __gshared const ubyte[38] fl = - [ - 0x03, 0x12, // LF_FIELDLIST_V2 - 0x0d, 0x15, // LF_MEMBER_V3 - 0x03, 0x00, // attribute - 0x00, 0x00, 0x00, 0x00, // void* - 0x00, 0x00, // offset - 'p','t','r',0, // "ptr" - 0xf2, 0xf1, // align to 4-byte including length word before data - 0x0d, 0x15, - 0x03, 0x00, - 0x00, 0x00, 0x00, 0x00, // ptrtypidx - 0x08, 0x00, - 'f', 'u','n','c','p','t','r', 0, // "funcptr" - 0xf2, 0xf1, - ]; - - debtyp_t* f = debtyp_alloc(fl.sizeof); - memcpy(f.data.ptr,fl.ptr,fl.sizeof); - TOLONG(f.data.ptr + 6, pvidx); - TOLONG(f.data.ptr + 22, ptridx); - TOWORD(f.data.ptr + 26, _tysize[TYnptr]); - idx_t fieldlist = cv_debtyp(f); - - const(char)* id = "dDelegate"; - int idlen = cast(int)strlen(id); - if (idlen > CV_MAX_SYMBOL_LENGTH) - idlen = CV_MAX_SYMBOL_LENGTH; - - debtyp_t* d = debtyp_alloc(20 + idlen + 1); - TOWORD(d.data.ptr, LF_STRUCTURE_V3); - TOWORD(d.data.ptr + 2, 2); // count - TOWORD(d.data.ptr + 4, 0); // property - TOLONG(d.data.ptr + 6, fieldlist); - TOLONG(d.data.ptr + 10, 0); // dList - TOLONG(d.data.ptr + 14, 0); // vtshape - TOWORD(d.data.ptr + 18, 2 * _tysize[TYnptr]); // size - memcpy(d.data.ptr + 20, id, idlen); - d.data.ptr[20 + idlen] = 0; - - return cv_debtyp(d); -} - -/**************************************** - * Return type index for a aarray of type Value[Key] - * Input: - * t associative array type - * keyidx key type - * validx value type - */ -@trusted -idx_t cv_daarray(type* t, idx_t keyidx, idx_t validx) -{ - /* Put out a struct: - * struct dAssocArray { - * void* ptr; - * typedef key-type __key_t; - * typedef val-type __val_t; - * } - */ - - type* tv = type_fake(TYnptr); - tv.Tcount++; - idx_t pvidx = cv4_typidx(tv); - type_free(tv); - - __gshared const ubyte[50] fl = - [ - 0x03, 0x12, // LF_FIELDLIST_V2 - 0x0d, 0x15, // LF_MEMBER_V3 - 0x03, 0x00, // attribute - 0x00, 0x00, 0x00, 0x00, // void* - 0x00, 0x00, // offset - 'p','t','r',0, // "ptr" - 0xf2, 0xf1, // align to 4-byte including field id - // offset 18 - 0x10, 0x15, // LF_NESTTYPE_V3 - 0x00, 0x00, // padding - 0x00, 0x00, 0x00, 0x00, // key type - '_','_','k','e','y','_','t',0, // "__key_t" - // offset 34 - 0x10, 0x15, // LF_NESTTYPE_V3 - 0x00, 0x00, // padding - 0x00, 0x00, 0x00, 0x00, // value type - '_','_','v','a','l','_','t',0, // "__val_t" - ]; - - debtyp_t* f = debtyp_alloc(fl.sizeof); - memcpy(f.data.ptr,fl.ptr,fl.sizeof); - TOLONG(f.data.ptr + 6, pvidx); - TOLONG(f.data.ptr + 22, keyidx); - TOLONG(f.data.ptr + 38, validx); - idx_t fieldlist = cv_debtyp(f); - - const(char)* id = t.Tident ? t.Tident : "dAssocArray"; - int idlen = cast(int)strlen(id); - if (idlen > CV_MAX_SYMBOL_LENGTH) - idlen = CV_MAX_SYMBOL_LENGTH; - - debtyp_t* d = debtyp_alloc(20 + idlen + 1); - TOWORD(d.data.ptr, LF_STRUCTURE_V3); - TOWORD(d.data.ptr + 2, 1); // count - TOWORD(d.data.ptr + 4, 0); // property - TOLONG(d.data.ptr + 6, fieldlist); - TOLONG(d.data.ptr + 10, 0); // dList - TOLONG(d.data.ptr + 14, 0); // vtshape - TOWORD(d.data.ptr + 18, _tysize[TYnptr]); // size - memcpy(d.data.ptr + 20, id, idlen); - d.data.ptr[20 + idlen] = 0; - - return cv_debtyp(d); -} - -/* ======================================================================== */ -/* ID-stream type records (reuse the shared cgcv type pool, read-only) */ -/* ======================================================================== */ - -/* LF_STRING_ID: an interned string returning a type index. */ -@trusted -idx_t cv_string_id(const(char)* s) -{ - if (!s) s = ""; - debtyp_t* d = debtyp_alloc(2 + 4 + cv_stringbytes(s)); - TOWORD(d.data.ptr, LF_STRING_ID); - TOLONG(d.data.ptr + 2, 0); // substring list id - cv_namestring(d.data.ptr + 6, s); - return cv_debtyp(d); -} - -/* LF_BUILDINFO: cwd, build tool, source, pdb, args. */ -@trusted -idx_t cv_buildinfo() -{ - char[260] cwd = 0; - if (!getcwd(cwd.ptr, cwd.sizeof)) - cwd[0] = 0; - idx_t cwdId = cv_string_id(cwd.ptr); - idx_t toolId = cv_string_id("dmd"); - idx_t srcId = cv_string_id(""); - idx_t pdbId = cv_string_id(""); - idx_t argId = cv_string_id(""); - debtyp_t* d = debtyp_alloc(2 + 2 + 5 * 4); - TOWORD(d.data.ptr, LF_BUILDINFO); - TOWORD(d.data.ptr + 2, 5); // count - TOLONG(d.data.ptr + 4, cwdId); - TOLONG(d.data.ptr + 8, toolId); - TOLONG(d.data.ptr + 12, srcId); - TOLONG(d.data.ptr + 16, pdbId); - TOLONG(d.data.ptr + 20, argId); - return cv_debtyp(d); -} - -/* LF_FUNC_ID: ties a function type to its name for the ID stream. */ -@trusted -idx_t cv_func_id(const(char)* id, idx_t functype) -{ - debtyp_t* d = debtyp_alloc(2 + 4 + 4 + cv_stringbytes(id)); - TOWORD(d.data.ptr, LF_FUNC_ID); - TOLONG(d.data.ptr + 2, 0); // parent scope id - TOLONG(d.data.ptr + 6, functype); - cv_namestring(d.data.ptr + 10, id); - return cv_debtyp(d); -} - -/* LF_UDT_SRC_LINE: source location of a user-defined type. */ -@trusted -idx_t cv_udt_src_line(idx_t typidx, idx_t srcId, uint line) -{ - debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4); - TOWORD(d.data.ptr, LF_UDT_SRC_LINE); - TOLONG(d.data.ptr + 2, typidx); - TOLONG(d.data.ptr + 6, srcId); - TOLONG(d.data.ptr + 10, line); - return cv_debtyp(d); -} diff --git a/compiler/src/dmd/backend/cv4.d b/compiler/src/dmd/backend/cv4.d new file mode 100644 index 000000000000..9e571753d8cc --- /dev/null +++ b/compiler/src/dmd/backend/cv4.d @@ -0,0 +1,245 @@ +/** + * CodeView 4 symbolic debug info declarations + * + * See "Microsoft Symbol and Type OMF" document + * + * Compiler implementation of the + * $(LINK2 https://www.dlang.org, D programming language). + * + * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/backend/cv4.d, backend/_cv4.d) + */ + +module dmd.backend.cv4; + +@safe: + +// Online documentation: https://dlang.org/phobos/dmd_backend_cv4.html + +enum OEM = 0x42; // Digital Mars OEM number (picked at random) + +// Symbol Indices +enum +{ + S_COMPILE = 1, + S_REGISTER = 2, + S_CONST = 3, + S_UDT = 4, + S_SSEARCH = 5, + S_END = 6, + S_SKIP = 7, + S_CVRESERVE = 8, + S_OBJNAME = 9, + S_ENDARG = 0x0A, + S_COBOLUDT = 0x0B, + S_MANYREG = 0x0C, + S_RETURN = 0x0D, + S_ENTRYTHIS = 0x0E, + S_TDBNAME = 0x0F, + + S_BPREL16 = 0x100, + S_LDATA16 = 0x101, + S_GDATA16 = 0x102, + S_PUB16 = 0x103, + S_LPROC16 = 0x104, + S_GPROC16 = 0x105, + S_THUNK16 = 0x106, + S_BLOCK16 = 0x107, + S_WITH16 = 0x108, + S_LABEL16 = 0x109, + S_CEXMODEL16 = 0x10A, + S_VFTPATH16 = 0x10B, + + S_BPREL32 = 0x200, + S_LDATA32 = 0x201, + S_GDATA32 = 0x202, + S_PUB32 = 0x203, + S_LPROC32 = 0x204, + S_GPROC32 = 0x205, + S_THUNK32 = 0x206, + S_BLOCK32 = 0x207, + S_WITH32 = 0x208, + S_LABEL32 = 0x209, + S_CEXMODEL32 = 0x20A, + S_VFTPATH32 = 0x20B, + + /************** Added Since CV4 *********************/ + + S_REGISTER_V2 = 0x1001, + S_CONSTANT_V2 = 0x1002, + S_UDT_V2 = 0x1003, + S_COBOLUDT_V2 = 0x1004, + S_MANYREG_V2 = 0x1005, + S_BPREL_V2 = 0x1006, + S_LDATA_V2 = 0x1007, + S_GDATA_V2 = 0x1008, + S_PUB_V2 = 0x1009, + S_LPROC_V2 = 0x100A, + S_GPROC_V2 = 0x100B, + S_VFTTABLE_V2 = 0x100C, + S_REGREL_V2 = 0x100D, + S_LTHREAD_V2 = 0x100E, + S_GTHREAD_V2 = 0x100F, + S_FUNCINFO_V2 = 0x1012, + S_COMPILAND_V2 = 0x1013, + + S_COMPILAND_V3 = 0x1101, + S_THUNK_V3 = 0x1102, + S_BLOCK_V3 = 0x1103, + S_LABEL_V3 = 0x1105, + S_REGISTER_V3 = 0x1106, + S_CONSTANT_V3 = 0x1107, + S_UDT_V3 = 0x1108, + S_BPREL_V3 = 0x110B, + S_LDATA_V3 = 0x110C, + S_GDATA_V3 = 0x110D, + S_PUB_V3 = 0x110E, + S_LPROC_V3 = 0x110F, + S_GPROC_V3 = 0x1110, + S_BPREL_XXXX_V3 = 0x1111, + S_MSTOOL_V3 = 0x1116, + S_PUB_FUNC1_V3 = 0x1125, + S_PUB_FUNC2_V3 = 0x1127, + S_SECTINFO_V3 = 0x1136, + S_SUBSECTINFO_V3 = 0x1137, + S_ENTRYPOINT_V3 = 0x1138, + S_SECUCOOKIE_V3 = 0x113A, + S_MSTOOLINFO_V3 = 0x113C, + S_MSTOOLENV_V3 = 0x113D, +} + +// Leaf Indices +enum +{ + LF_MODIFIER = 1, + LF_POINTER = 2, + LF_ARRAY = 3, + LF_CLASS = 4, + LF_STRUCTURE = 5, + LF_UNION = 6, + LF_ENUM = 7, + LF_PROCEDURE = 8, + LF_MFUNCTION = 9, + LF_VTSHAPE = 0x0A, + LF_COBOL0 = 0x0B, + LF_COBOL1 = 0x0C, + LF_BARRAY = 0x0D, + LF_LABEL = 0x0E, + LF_NULL = 0x0F, + LF_NOTTRAN = 0x10, + LF_DIMARRAY = 0x11, + LF_VFTPATH = 0x12, + LF_PRECOMP = 0x13, + LF_ENDPRECOMP = 0x14, + LF_OEM = 0x15, + LF_TYPESERVER = 0x16, + + // D extensions (not used, causes linker to fail) + LF_DYN_ARRAY = 0x17, + LF_ASSOC_ARRAY = 0x18, + LF_DELEGATE = 0x19, + + LF_SKIP = 0x200, + LF_ARGLIST = 0x201, + LF_DEFARG = 0x202, + LF_LIST = 0x203, + LF_FIELDLIST = 0x204, + LF_DERIVED = 0x205, + LF_BITFIELD = 0x206, + LF_METHODLIST = 0x207, + LF_DIMCONU = 0x208, + LF_DIMCONLU = 0x209, + LF_DIMVARU = 0x20A, + LF_DIMVARLU = 0x20B, + LF_REFSYM = 0x20C, + + LF_BCLASS = 0x400, + LF_VBCLASS = 0x401, + LF_IVBCLASS = 0x402, + LF_ENUMERATE = 0x403, + LF_FRIENDFCN = 0x404, + LF_INDEX = 0x405, + LF_MEMBER = 0x406, + LF_STMEMBER = 0x407, + LF_METHOD = 0x408, + LF_NESTTYPE = 0x409, + LF_VFUNCTAB = 0x40A, + LF_FRIENDCLS = 0x40B, + + LF_NUMERIC = 0x8000, + LF_CHAR = 0x8000, + LF_SHORT = 0x8001, + LF_USHORT = 0x8002, + LF_LONG = 0x8003, + LF_ULONG = 0x8004, + LF_REAL32 = 0x8005, + LF_REAL64 = 0x8006, + LF_REAL80 = 0x8007, + LF_REAL128 = 0x8008, + LF_QUADWORD = 0x8009, + LF_UQUADWORD = 0x800A, + LF_REAL48 = 0x800B, + + LF_COMPLEX32 = 0x800C, + LF_COMPLEX64 = 0x800D, + LF_COMPLEX80 = 0x800E, + LF_COMPLEX128 = 0x800F, + + LF_VARSTRING = 0x8010, + + /************** Added Since CV4 *********************/ + + LF_MODIFIER_V2 = 0x1001, + LF_POINTER_V2 = 0x1002, + LF_ARRAY_V2 = 0x1003, + LF_CLASS_V2 = 0x1004, + LF_STRUCTURE_V2 = 0x1005, + LF_UNION_V2 = 0x1006, + LF_ENUM_V2 = 0x1007, + LF_PROCEDURE_V2 = 0x1008, + LF_MFUNCTION_V2 = 0x1009, + LF_COBOL0_V2 = 0x100A, + LF_BARRAY_V2 = 0x100B, + LF_DIMARRAY_V2 = 0x100C, + LF_VFTPATH_V2 = 0x100D, + LF_PRECOMP_V2 = 0x100E, + LF_OEM_V2 = 0x100F, + + LF_SKIP_V2 = 0x1200, + LF_ARGLIST_V2 = 0x1201, + LF_DEFARG_V2 = 0x1202, + LF_FIELDLIST_V2 = 0x1203, + LF_DERIVED_V2 = 0x1204, + LF_BITFIELD_V2 = 0x1205, + LF_METHODLIST_V2 = 0x1206, + LF_DIMCONU_V2 = 0x1207, + LF_DIMCONLU_V2 = 0x1208, + LF_DIMVARU_V2 = 0x1209, + LF_DIMVARLU_V2 = 0x120A, + + LF_BCLASS_V2 = 0x1400, + LF_VBCLASS_V2 = 0x1401, + LF_IVBCLASS_V2 = 0x1402, + LF_FRIENDFCN_V2 = 0x1403, + LF_INDEX_V2 = 0x1404, + LF_MEMBER_V2 = 0x1405, + LF_STMEMBER_V2 = 0x1406, + LF_METHOD_V2 = 0x1407, + LF_NESTTYPE_V2 = 0x1408, + LF_VFUNCTAB_V2 = 0x1409, + LF_FRIENDCLS_V2 = 0x140A, + LF_ONEMETHOD_V2 = 0x140B, + LF_VFUNCOFF_V2 = 0x140C, + LF_NESTTYPEEX_V2 = 0x140D, + + LF_ENUMERATE_V3 = 0x1502, + LF_ARRAY_V3 = 0x1503, + LF_CLASS_V3 = 0x1504, + LF_STRUCTURE_V3 = 0x1505, + LF_UNION_V3 = 0x1506, + LF_ENUM_V3 = 0x1507, + LF_MEMBER_V3 = 0x150D, + LF_STMEMBER_V3 = 0x150E, + LF_METHOD_V3 = 0x150F, + LF_NESTTYPE_V3 = 0x1510, + LF_ONEMETHOD_V3 = 0x1511, +} diff --git a/compiler/src/dmd/backend/cv8.d b/compiler/src/dmd/backend/cv8.d new file mode 100644 index 000000000000..b9744ccb1fc0 --- /dev/null +++ b/compiler/src/dmd/backend/cv8.d @@ -0,0 +1,1207 @@ +/** + * CodeView 8 symbolic debug info generation + * + * This module generates the `.debug$S` and `.debug$T` sections for Win64, + * which are the MS-Coff symbolic debug info and type debug info sections. + * + * Compiler implementation of the + * $(LINK2 https://www.dlang.org, D programming language). + * + * Copyright: Copyright (C) 2012-2026 by The D Language Foundation, All Rights Reserved + * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) + * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/backend/cv8.d, backend/cv8.d) + * Documentation: https://dlang.org/phobos/dmd_backend_cv8.html + * Coverage: https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/backend/cv8.d + */ + +module dmd.backend.cv8; + +import core.stdc.stdio; +import core.stdc.stdlib; +import core.stdc.string; +extern (C) nothrow char* getcwd(char*, size_t); + +import dmd.backend.cc; +import dmd.backend.cdef; +import dmd.backend.cgcv; +import dmd.backend.code; +import dmd.backend.x86.code_x86; +import dmd.backend.cv4; +import dmd.backend.mem; +import dmd.backend.el; +import dmd.backend.mscoffobj; +import dmd.backend.obj; +import dmd.backend.oper; +import dmd.backend.pdb : pdb_udt; +import dmd.common.outbuffer; +import dmd.backend.rtlsym; +import dmd.backend.symbol : globsym; +import dmd.backend.ty; +import dmd.backend.type; +import dmd.backend.dvarstats; + + +nothrow: +@safe: + +static if (1) +{ + + +// Determine if this Symbol is stored in a COMDAT +@trusted +private bool symbol_iscomdat4(Symbol* s) +{ + return s.Sclass == SC.comdat || + config.flags2 & CFG2comdat && s.Sclass == SC.inline || + config.flags4 & CFG4allcomdat && s.Sclass == SC.global; +} + + +// if symbols get longer than 65500 bytes, the linker reports corrupt debug info or exits with +// 'fatal error LNK1318: Unexpected PDB error; RPC (23) '(0x000006BA)' +enum CV8_MAX_SYMBOL_LENGTH = 0xffd8; + +// The "F1" section, which is the symbols +private __gshared OutBuffer* F1_buf; + +// The "F2" section, which is the line numbers +private __gshared OutBuffer* F2_buf; + +// The "F3" section, which is global and a string table of source file names. +private __gshared OutBuffer* F3_buf; + +// The "F4" section, which is global and a list of info about source files. +private __gshared OutBuffer* F4_buf; + +/* Fixups that go into F1 section + */ +struct F1_Fixups +{ + Symbol* s; + uint offset; + uint value; +} + +private __gshared OutBuffer* F1fixup; // array of F1_Fixups + +/* Struct in which to collect per-function data, for later emission + * into .debug$S. + */ +struct FuncData +{ + Symbol* sfunc; + uint section_length; + const(char)* srcfilename; + uint srcfileoff; + uint linepairstart; // starting byte index of offset/line pairs in linebuf[] + uint linepairbytes; // number of bytes for offset/line pairs + uint linepairsegment; // starting byte index of filename segment for offset/line pairs + OutBuffer* f1buf; + OutBuffer* f1fixup; +} + +__gshared FuncData currentfuncdata; + +private __gshared OutBuffer* funcdata; // array of FuncData's + +private __gshared OutBuffer* linepair; // array of offset/line pairs + +private @trusted +void cv8_writename(OutBuffer* buf, const(char)* name, size_t len) +{ + if(config.flags2 & CFG2gms) + { + const(char)* start = name; + const(char)* cur = strchr(start, '.'); + const(char)* end = start + len; + while(cur != null) + { + if(cur >= end) + { + buf.writen(start, end - start); + return; + } + buf.writen(start, cur - start); + buf.writeByte('@'); + start = cur + 1; + if(start >= end) + return; + cur = strchr(start, '.'); + } + buf.writen(start, end - start); + } + else + buf.writen(name, len); +} + +/************************************************ + * Called at the start of an object file generation. + * One source file can generate multiple object files; this starts an object file. + * Input: + * filename source file name + */ +@trusted +void cv8_initfile(const(char)* filename) +{ + //printf("cv8_initfile()\n"); + + // Recycle buffers; much faster than delete/renew + + if (!F1_buf) + { + __gshared OutBuffer f1buf; + f1buf.reserve(1024); + F1_buf = &f1buf; + } + F1_buf.reset(); + + if (!F1fixup) + { + __gshared OutBuffer f1fixupbuf; + f1fixupbuf.reserve(1024); + F1fixup = &f1fixupbuf; + } + F1fixup.reset(); + + if (!F2_buf) + { + __gshared OutBuffer f2buf; + f2buf.reserve(1024); + F2_buf = &f2buf; + } + F2_buf.reset(); + + if (!F3_buf) + { + __gshared OutBuffer f3buf; + f3buf.reserve(1024); + F3_buf = &f3buf; + } + F3_buf.reset(); + F3_buf.writeByte(0); // first "filename" + + if (!F4_buf) + { + __gshared OutBuffer f4buf; + f4buf.reserve(1024); + F4_buf = &f4buf; + } + F4_buf.reset(); + + if (!funcdata) + { + __gshared OutBuffer funcdatabuf; + funcdatabuf.reserve(1024); + funcdata = &funcdatabuf; + } + funcdata.reset(); + + if (!linepair) + { + __gshared OutBuffer linepairbuf; + linepairbuf.reserve(1024); + linepair = &linepairbuf; + } + linepair.reset(); + + memset(¤tfuncdata, 0, currentfuncdata.sizeof); + currentfuncdata.f1buf = F1_buf; + currentfuncdata.f1fixup = F1fixup; + + cv_init(); +} + +@trusted +void cv8_termfile(const(char)[] objfilename) +{ + //printf("cv8_termfile()\n"); + + /* Write out the debug info sections. + */ + + int seg = MsCoffObj_seg_debugS(); + + uint value = 4; + objmod.bytes(seg,0,(cast(void*)&value)[0 .. 4]); + + /* Start with starting symbol in separate "F1" section + */ + auto buf = OutBuffer(1024); + size_t len = objfilename.length; + buf.write16(cast(int)(2 + 4 + len + 1)); + buf.write16(S_COMPILAND_V3); + buf.write32(0); + buf.write(objfilename.ptr, cast(uint)(len + 1)); + + // write S_COMPILE record + buf.write16(2 + 1 + 1 + 2 + 1 + VERSION.length + 1); + buf.write16(S_COMPILE); + buf.writeByte(I64 ? 0xD0 : 6); // target machine AMD64 or x86 (Pentium II) + buf.writeByte(config.flags2 & CFG2gms ? 0 : 'D'); // language index (C/C++/D) + buf.write16(0x800 | (config.inline8087 ? 0 : (1<<3))); // 32-bit, float package + buf.writeByte(VERSION.length + 1); + buf.writeByte('Z'); + buf.write(VERSION.ptr, VERSION.length); + + cv8_writesection(seg, 0xF1, &buf); + + // Write out "F2" sections + uint length = cast(uint)funcdata.length(); + ubyte* p = funcdata.buf; + for (uint u = 0; u < length; u += FuncData.sizeof) + { FuncData* fd = cast(FuncData*)(p + u); + + F2_buf.reset(); + + F2_buf.write32(cast(uint)fd.sfunc.Soffset); + F2_buf.write32(0); + F2_buf.write32(fd.section_length); + F2_buf.write(linepair.buf + fd.linepairstart, fd.linepairbytes); + + int f2seg = seg; + if (symbol_iscomdat4(fd.sfunc)) + { + f2seg = MsCoffObj_seg_debugS_comdat(fd.sfunc); + objmod.bytes(f2seg, 0, (cast(void*)&value)[0..4]); + } + + uint offset = cast(uint)SegData[f2seg].SDoffset + 8; + cv8_writesection(f2seg, 0xF2, F2_buf); + objmod.reftoident(f2seg, offset, fd.sfunc, 0, CF.seg | CF.off); + + if (f2seg != seg && fd.f1buf.length()) + { + // Write out "F1" section + const uint f1offset = cast(uint)SegData[f2seg].SDoffset; + cv8_writesection(f2seg, 0xF1, fd.f1buf); + + // Fixups for "F1" section + const uint fixupLength = cast(uint)fd.f1fixup.length(); + ubyte* pfixup = fd.f1fixup.buf; + for (uint v = 0; v < fixupLength; v += F1_Fixups.sizeof) + { F1_Fixups* f = cast(F1_Fixups*)(pfixup + v); + + objmod.reftoident(f2seg, f1offset + 8 + f.offset, f.s, f.value, CF.seg | CF.off); + } + } + } + + // Write out "F3" section + if (F3_buf.length() > 1) + cv8_writesection(seg, 0xF3, F3_buf); + + // Write out "F4" section + if (F4_buf.length() > 0) + cv8_writesection(seg, 0xF4, F4_buf); + + if (F1_buf.length()) + { + // Write out "F1" section + uint f1offset = cast(uint)SegData[seg].SDoffset; + cv8_writesection(seg, 0xF1, F1_buf); + + // Fixups for "F1" section + length = cast(uint)F1fixup.length(); + p = F1fixup.buf; + for (uint u = 0; u < length; u += F1_Fixups.sizeof) + { F1_Fixups* f = cast(F1_Fixups*)(p + u); + + objmod.reftoident(seg, f1offset + 8 + f.offset, f.s, f.value, CF.seg | CF.off); + } + } + + // Write out .debug$T section + cv_term(); +} + +/************************************************ + * Called at the start of a module. + * Note that there can be multiple modules in one object file. + * cv8_initfile() must be called first. + */ +void cv8_initmodule(const(char)* filename, const(char)* modulename) +{ + //printf("cv8_initmodule(filename = %s, modulename = %s)\n", filename, modulename); +} + +@trusted +void cv8_termmodule() +{ + //printf("cv8_termmodule()\n"); + assert(config.objfmt == OBJ_MSCOFF); +} + +/****************************************** + * Called at the start of a function. + */ +@trusted +void cv8_func_start(Symbol* sfunc) +{ + //printf("cv8_func_start(%s)\n", sfunc.Sident); + currentfuncdata.sfunc = sfunc; + currentfuncdata.section_length = 0; + currentfuncdata.srcfilename = null; + currentfuncdata.linepairstart += currentfuncdata.linepairbytes; + currentfuncdata.linepairbytes = 0; + currentfuncdata.f1buf = F1_buf; + currentfuncdata.f1fixup = F1fixup; + if (symbol_iscomdat4(sfunc)) + { + // This leaks memory + currentfuncdata.f1buf = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); + currentfuncdata.f1buf.reserve(128); + currentfuncdata.f1fixup = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); + currentfuncdata.f1fixup.reserve(128); + } + + varStats_startFunction(); +} + +@trusted +void cv8_func_term(Symbol* sfunc) +{ + //printf("cv8_func_term(%s)\n", sfunc.Sident); + + assert(currentfuncdata.sfunc == sfunc); + currentfuncdata.section_length = cast(uint)sfunc.Ssize; + + funcdata.write(¤tfuncdata, currentfuncdata.sizeof); + + // Write function symbol + assert(tyfunc(sfunc.ty())); + idx_t typidx; + func_t* fn = sfunc.Sfunc; + if(fn.Fclass) + { + // generate member function type info + // it would be nicer if this could be in cv4_typidx, but the function info is not available there + uint nparam; + ubyte call = cv4_callconv(sfunc.Stype); + idx_t paramidx = cv4_arglist(sfunc.Stype,&nparam); + uint next = cv4_typidx(sfunc.Stype.Tnext); + + type* classtype = cast(type*)fn.Fclass; + uint classidx = cv4_typidx(classtype); + type* tp = type_allocn(TYnptr, classtype); + uint thisidx = cv4_typidx(tp); // TODO + debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4 + 1 + 1 + 2 + 4 + 4); + TOWORD(d.data.ptr,LF_MFUNCTION_V2); + TOLONG(d.data.ptr + 2,next); // return type + TOLONG(d.data.ptr + 6,classidx); // class type + TOLONG(d.data.ptr + 10,thisidx); // this type + d.data.ptr[14] = call; + d.data.ptr[15] = 0; // reserved + TOWORD(d.data.ptr + 16,nparam); + TOLONG(d.data.ptr + 18,paramidx); + TOLONG(d.data.ptr + 22,0); // this adjust + typidx = cv_debtyp(d); + } + else + typidx = cv_typidx(sfunc.Stype); + + const(char)* id = sfunc.prettyIdent ? sfunc.prettyIdent : prettyident(sfunc); + + size_t len = strlen(id); + if(len > CV8_MAX_SYMBOL_LENGTH) + len = CV8_MAX_SYMBOL_LENGTH; + /* + * 2 length (not including these 2 bytes) + * 2 S_GPROC_V3 + * 4 parent + * 4 pend + * 4 pnext + * 4 size of function + * 4 size of function prolog + * 4 offset to function epilog + * 4 type index + * 6 seg:offset of function start + * 1 flags + * n 0 terminated name string + */ + auto buf = currentfuncdata.f1buf; + buf.reserve(cast(uint)(2 + 2 + 4 * 7 + 6 + 1 + len + 1)); + buf.write16n(cast(int)(2 + 4 * 7 + 6 + 1 + len + 1)); + buf.write16n(sfunc.Sclass == SC.static_ ? S_LPROC_V3 : S_GPROC_V3); + buf.write32(0); // parent + buf.write32(0); // pend + buf.write32(0); // pnext + buf.write32(cast(uint)currentfuncdata.section_length); // size of function + buf.write32(cast(uint)cgstate.startoffset); // size of prolog + buf.write32(cast(uint)cgstate.retoffset); // offset to epilog + buf.write32(typidx); + + F1_Fixups f1f; + f1f.s = sfunc; + f1f.offset = cast(uint)buf.length(); + f1f.value = 0; + currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); + buf.write32(0); + buf.write16n(0); + + buf.writeByte(0); + buf.writen(id, len); + buf.writeByte(0); + + struct cv8 + { + nothrow: + // record for CV record S_BLOCK_V3 + struct block_v3_data + { + ushort len; + ushort id; + uint pParent; + uint pEnd; + uint length; + uint offset; + ushort seg; + ubyte[1] name; + } + + static void endArgs() + { + auto buf = currentfuncdata.f1buf; + buf.write16(2); + buf.write16(S_ENDARG); + } + static void beginBlock(int offset, int length) + { + auto buf = currentfuncdata.f1buf; + uint soffset = cast(uint)buf.length(); + // parent and end to be filled by linker + block_v3_data block32 = { block_v3_data.sizeof - 2, S_BLOCK_V3, 0, 0, length, offset, 0, [ 0 ] }; + buf.write(&block32, block32.sizeof); + size_t offOffset = cast(char*)&block32.offset - cast(char*)&block32; + + F1_Fixups f1f; + f1f.s = currentfuncdata.sfunc; + f1f.offset = cast(uint)(soffset + offOffset); + f1f.value = offset; + currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); + } + static void endBlock() + { + auto buf = currentfuncdata.f1buf; + buf.write16(2); + buf.write16(S_END); + } + } + varStats_writeSymbolTable(sfunc, globsym, &cv8_outsym, &cv8.endArgs, &cv8.beginBlock, &cv8.endBlock); + + /* Put out function return record S_RETURN + * (VC doesn't, so we won't bother, either.) + */ + + // Write function end symbol + buf.write16(2); + buf.write16(S_END); + + currentfuncdata.f1buf = F1_buf; + currentfuncdata.f1fixup = F1fixup; +} + +/********************************************** + */ + +@trusted +void cv8_linnum(Srcpos srcpos, uint offset) +{ + const sfilename = srcpos.Sfilename; + //printf("cv8_linnum(file = %s, line = %d, offset = x%x)\n", sfilename, cast(int)srcpos.Slinnum, cast(uint)offset); + + if (!sfilename) + return; + + varStats_recordLineOffset(srcpos, offset); + + __gshared uint lastoffset; + __gshared uint lastlinnum; + + if (!currentfuncdata.srcfilename || + (currentfuncdata.srcfilename != sfilename && strcmp(currentfuncdata.srcfilename, sfilename))) + { + currentfuncdata.srcfilename = sfilename; + uint srcfileoff = cv8_addfile(sfilename); + + // new file segment + currentfuncdata.linepairsegment = currentfuncdata.linepairstart + currentfuncdata.linepairbytes; + + linepair.write32(srcfileoff); + linepair.write32(0); // reserve space for length information + linepair.write32(12); + currentfuncdata.linepairbytes += 12; + } + else if (offset <= lastoffset || srcpos.Slinnum == lastlinnum) + return; // avoid multiple entries for the same offset + + lastoffset = offset; + lastlinnum = srcpos.Slinnum; + linepair.write32(offset); + linepair.write32(srcpos.Slinnum | 0x80000000); // mark as statement, not expression + + currentfuncdata.linepairbytes += 8; + + // update segment length + auto segmentbytes = currentfuncdata.linepairstart + currentfuncdata.linepairbytes - currentfuncdata.linepairsegment; + auto segmentheader = cast(uint*)(linepair.buf + currentfuncdata.linepairsegment); + segmentheader[1] = (segmentbytes - 12) / 8; + segmentheader[2] = segmentbytes; +} + +/********************************************** + * Add source file, if it isn't already there. + * Return offset into F4. + */ + +@trusted +uint cv8_addfile(const(char)* filename) +{ + //printf("cv8_addfile('%s')\n", filename); + + /* The algorithms here use a linear search. This is acceptable only + * because we expect only 1 or 2 files to appear. + * Unlike C, there won't be lots of .h source files to be accounted for. + */ + + uint length = cast(uint)F3_buf.length(); + ubyte* p = F3_buf.buf; + size_t len = strlen(filename); + + // ensure the filename is absolute to help the debugger to find the source + // without having to know the working directory during compilation + __gshared char[260] cwd = 0; + __gshared uint cwdlen; + bool abs = (*filename == '\\') || + (*filename == '/') || + (*filename && filename[1] == ':'); + + if (!abs && cwd[0] == 0) + { + if (getcwd(cwd.ptr, cwd.sizeof)) + { + cwdlen = cast(uint)strlen(cwd.ptr); + if(cwd[cwdlen - 1] != '\\' && cwd[cwdlen - 1] != '/') + cwd[cwdlen++] = '\\'; + } + } + uint off = 1; + while (off + len < length) + { + if (!abs) + { + if (memcmp(p + off, cwd.ptr, cwdlen) == 0 && + memcmp(p + off + cwdlen, filename, len + 1) == 0) + goto L1; + } + else if (memcmp(p + off, filename, len + 1) == 0) + { // Already there + //printf("\talready there at %x\n", off); + goto L1; + } + off += strlen(cast(const(char)* )(p + off)) + 1; + } + off = length; + // Add it + if(!abs) + F3_buf.write(cwd.ptr, cwdlen); + F3_buf.write(filename, cast(uint)(len + 1)); + +L1: + // off is the offset of the filename in F3. + // Find it in F4. + + length = cast(uint)F4_buf.length(); + p = F4_buf.buf; + + uint u = 0; + while (u + 8 <= length) + { + //printf("\t%x\n", *cast(uint*)(p + u)); + if (off == *cast(uint*)(p + u)) + { + //printf("\tfound %x\n", u); + return u; + } + u += 4; + ushort type = *cast(ushort*)(p + u); + u += 2; + if (type == 0x0110) + u += 16; // truncated blake3 hash + u += 2; + } + + // Not there. Add it. + F4_buf.write32(off); + + /* Write 10 01 [blake3 hash] + * or + * 00 00 + */ + F4_buf.write16(0); + + // 2 bytes of pad + F4_buf.write16(0); + + //printf("\tadded %x\n", length); + return length; +} + +private @trusted +void cv8_writesection(int seg, uint type, OutBuffer* buf) +{ + /* Write out as: + * bytes desc + * -------+---- + * 4 type + * 4 length + * length data + * pad pad to 4 byte boundary + */ + uint off = cast(uint)SegData[seg].SDoffset; + objmod.bytes(seg,off,(cast(void*)&type)[0 .. 4]); + uint length = cast(uint)buf.length(); + objmod.bytes(seg,off+4,(cast(void*)&length)[0 .. 4]); + objmod.bytes(seg,off+8,buf.buf[0 .. length]); + // Align to 4 + uint pad = ((length + 3) & ~3) - length; + objmod.lidata(seg,off+8+length,pad); +} + +@trusted +void cv8_outsym(Symbol* s) +{ + //printf("cv8_outsym(s = '%s')\n", s.Sident); + //type_print(s.Stype); + //symbol_print(s); + if (s.Sflags & SFLnodebug) + return; + + idx_t typidx = cv_typidx(s.Stype); + //printf("typidx = %x\n", typidx); + const(char)* id = s.prettyIdent ? s.prettyIdent : prettyident(s); + size_t len = strlen(id); + + if(len > CV8_MAX_SYMBOL_LENGTH) + len = CV8_MAX_SYMBOL_LENGTH; + + F1_Fixups f1f; + f1f.value = 0; + auto buf = currentfuncdata.f1buf; + + uint sr; + uint base; + switch (s.Sclass) + { + case SC.parameter: + case SC.regpar: + case SC.shadowreg: + if (s.Sfl == FL.reg) + { + s.Sfl = FL.para; + cv8_outsym(s); + s.Sfl = FL.reg; + goto case_register; + } + base = cast(uint)(cgstate.Para.size - cgstate.BPoff); // cancel out add of BPoff + goto L1; + + case SC.auto_: + if (s.Sfl == FL.reg) + goto case_register; + case_auto: + base = cast(uint)cgstate.Auto.size; + L1: + if (s.Sscope) // local variables moved into the closure cannot be emitted directly + break; +static if (1) +{ + // Register relative addressing + buf.reserve(cast(uint)(2 + 2 + 4 + 4 + 2 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + 4 + 2 + len + 1)); + buf.write16n(0x1111); + buf.write32(cast(uint)(s.Soffset + base + cgstate.BPoff)); + buf.write32(typidx); + buf.write16n(I64 ? 334 : 22); // relative to RBP/EBP + cv8_writename(buf, id, len); + buf.writeByte(0); +} +else +{ + // This is supposed to work, implicit BP relative addressing, but it does not + buf.reserve(2 + 2 + 4 + 4 + len + 1); + buf.write16n( 2 + 4 + 4 + len + 1); + buf.write16n(S_BPREL_V3); + buf.write32(s.Soffset + base + cgstate.BPoff); + buf.write32(typidx); + cv8_writename(buf, id, len); + buf.writeByte(0); +} + break; + + case SC.bprel: + base = -cgstate.BPoff; + goto L1; + + case SC.fastpar: + if (s.Sfl != FL.reg) + { base = cast(uint)cgstate.Fast.size; + goto L1; + } + goto L2; + + case SC.register: + if (s.Sfl != FL.reg) + goto case_auto; + goto case; + + case SC.pseudo: + case_register: + L2: + buf.reserve(cast(uint)(2 + 2 + 4 + 2 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + 2 + len + 1)); + buf.write16n(S_REGISTER_V3); + buf.write32(typidx); + buf.write16n(cv8_regnum(s)); + cv8_writename(buf, id, len); + buf.writeByte(0); + break; + + case SC.extern_: + break; + + case SC.static_: + case SC.locstat: + sr = S_LDATA_V3; + goto Ldata; + + case SC.global: + case SC.comdat: + case SC.comdef: + sr = S_GDATA_V3; + Ldata: + /* + * 2 length (not including these 2 bytes) + * 2 S_GDATA_V2 + * 4 typidx + * 6 ref to symbol + * n 0 terminated name string + */ + if (s.ty() & mTYthread) // thread local storage + sr = (sr == S_GDATA_V3) ? 0x1113 : 0x1112; + + buf.reserve(cast(uint)(2 + 2 + 4 + 6 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + 6 + len + 1)); + buf.write16n(sr); + buf.write32(typidx); + + f1f.s = s; + f1f.offset = cast(uint)buf.length(); + F1fixup.write(&f1f, f1f.sizeof); + buf.write32(0); + buf.write16n(0); + + cv8_writename(buf, id, len); + buf.writeByte(0); + break; + + default: + break; + } +} + + +/******************************************* + * Put out a name for a user defined type. + * Input: + * id the name + * typidx and its type + */ +@trusted +void cv8_udt(const(char)* id, idx_t typidx) +{ + //printf("cv8_udt('%s', %x)\n", id, typidx); + if (config.newpdb) + { + pdb_udt(id, typidx); + return; + } + auto buf = currentfuncdata.f1buf; + size_t len = strlen(id); + + if (len > CV8_MAX_SYMBOL_LENGTH) + len = CV8_MAX_SYMBOL_LENGTH; + buf.reserve(cast(uint)(2 + 2 + 4 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + len + 1)); + buf.write16n(S_UDT_V3); + buf.write32(typidx); + cv8_writename(buf, id, len); + buf.writeByte(0); +} + +/********************************************* + * Get Codeview register number for symbol s. + */ +int cv8_regnum(Symbol* s) +{ + int reg = s.Sreglsw; + assert(s.Sfl == FL.reg); + if ((1 << reg) & XMMREGS) + return reg - XMM0 + 154; + switch (type_size(s.Stype)) + { + case 1: + if (reg < 4) + reg += 1; + else if (reg >= 4 && reg < 8) + reg += 324 - 4; + else + reg += 344 - 4; + break; + + case 2: + if (reg < 8) + reg += 9; + else + reg += 352 - 8; + break; + + case 4: + if (reg < 8) + reg += 17; + else + reg += 360 - 8; + break; + + case 8: + reg += 328; + break; + + default: + reg = 0; + break; + } + return reg; +} + +/*************************************** + * Put out a forward ref for structs, unions, and classes. + * Only put out the real definitions with toDebug(). + */ +@trusted +idx_t cv8_fwdref(Symbol* s) +{ + assert(config.fulltypes == CV8); +// if (s.Stypidx && !global.params.multiobj) +// return s.Stypidx; + struct_t* st = s.Sstruct; + uint leaf; + uint numidx; + if (st.Sflags & STRunion) + { + leaf = LF_UNION_V3; + numidx = 10; + } + else if (st.Sflags & STRclass) + { + leaf = LF_CLASS_V3; + numidx = 18; + } + else + { + leaf = LF_STRUCTURE_V3; + numidx = 18; + } + uint len = numidx + cv4_numericbytes(0); + int idlen = cast(int)strlen(s.Sident.ptr); + + if (idlen > CV8_MAX_SYMBOL_LENGTH) + idlen = CV8_MAX_SYMBOL_LENGTH; + + debtyp_t* d = debtyp_alloc(len + idlen + 1); + TOWORD(d.data.ptr, leaf); + TOWORD(d.data.ptr + 2, 0); // number of fields + TOWORD(d.data.ptr + 4, 0x80); // property + TOLONG(d.data.ptr + 6, 0); // field list + if (leaf == LF_CLASS_V3 || leaf == LF_STRUCTURE_V3) + { + TOLONG(d.data.ptr + 10, 0); // dList + TOLONG(d.data.ptr + 14, 0); // vshape + } + cv4_storenumeric(d.data.ptr + numidx, 0); + cv_namestring(d.data.ptr + len, s.Sident.ptr, idlen); + d.data.ptr[len + idlen] = 0; + idx_t typidx = cv_debtyp(d); + s.Stypidx = typidx; + + return typidx; +} + +/**************************************** + * Return type index for a darray of type E[] + * Input: + * t darray type + * etypidx type index for E + */ +@trusted +idx_t cv8_darray(type* t, idx_t etypidx) +{ + //printf("cv8_darray(etypidx = %x)\n", etypidx); + /* Put out a struct: + * struct dArray { + * size_t length; + * E* ptr; + * } + */ + +static if (0) +{ + d = debtyp_alloc(18); + TOWORD(d.data.ptr, 0x100F); + TOWORD(d.data.ptr + 2, OEM); + TOWORD(d.data.ptr + 4, 1); // 1 = dynamic array + TOLONG(d.data.ptr + 6, 2); // count of type indices to follow + TOLONG(d.data.ptr + 10, 0x23); // index type, T_UQUAD + TOLONG(d.data.ptr + 14, next); // element type + return cv_debtyp(d); +} + + type* tp = type_pointer(t.Tnext); + idx_t ptridx = cv4_typidx(tp); + type_free(tp); + + __gshared const ubyte[38] fl = + [ + 0x03, 0x12, // LF_FIELDLIST_V2 + 0x0d, 0x15, // LF_MEMBER_V3 + 0x03, 0x00, // attribute + 0x23, 0x00, 0x00, 0x00, // size_t + 0x00, 0x00, // offset + 'l', 'e', 'n', 'g', 't', 'h', 0x00, + 0xf3, 0xf2, 0xf1, // align to 4-byte including length word before data + 0x0d, 0x15, + 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, // etypidx + 0x08, 0x00, + 'p', 't', 'r', 0x00, + 0xf2, 0xf1, + ]; + + debtyp_t* f = debtyp_alloc(fl.sizeof); + memcpy(f.data.ptr,fl.ptr,fl.sizeof); + TOLONG(f.data.ptr + 6, I64 ? 0x23 : 0x22); // size_t + TOLONG(f.data.ptr + 26, ptridx); + TOWORD(f.data.ptr + 30, _tysize[TYnptr]); + idx_t fieldlist = cv_debtyp(f); + + const(char)* id; + switch (t.Tnext.Tty) + { + case mTYimmutable | TYchar: + id = "string"; + break; + + case mTYimmutable | TYwchar_t: + id = "wstring"; + break; + + case mTYimmutable | TYdchar: + id = "dstring"; + break; + + default: + id = t.Tident ? t.Tident : "dArray"; + break; + } + + int idlen = cast(int)strlen(id); + + if (idlen > CV8_MAX_SYMBOL_LENGTH) + idlen = CV8_MAX_SYMBOL_LENGTH; + + debtyp_t* d = debtyp_alloc(20 + idlen + 1); + TOWORD(d.data.ptr, LF_STRUCTURE_V3); + TOWORD(d.data.ptr + 2, 2); // count + TOWORD(d.data.ptr + 4, 0); // property + TOLONG(d.data.ptr + 6, fieldlist); + TOLONG(d.data.ptr + 10, 0); // dList + TOLONG(d.data.ptr + 14, 0); // vtshape + TOWORD(d.data.ptr + 18, 2 * _tysize[TYnptr]); // size + cv_namestring(d.data.ptr + 20, id, idlen); + d.data.ptr[20 + idlen] = 0; + + idx_t top = cv_numdebtypes(); + idx_t debidx = cv_debtyp(d); + if(top != cv_numdebtypes()) + cv8_udt(id, debidx); + + return debidx; +} + +/**************************************** + * Return type index for a delegate + * Input: + * t delegate type + * functypidx type index for pointer to function + */ +@trusted +idx_t cv8_ddelegate(type* t, idx_t functypidx) +{ + //printf("cv8_ddelegate(functypidx = %x)\n", functypidx); + /* Put out a struct: + * struct dDelegate { + * void* ptr; + * function* funcptr; + * } + */ + + type* tv = type_fake(TYnptr); + tv.Tcount++; + idx_t pvidx = cv4_typidx(tv); + type_free(tv); + + type* tp = type_pointer(t.Tnext); + idx_t ptridx = cv4_typidx(tp); + type_free(tp); + +static if (0) +{ + debtyp_t* d = debtyp_alloc(18); + TOWORD(d.data.ptr, 0x100F); + TOWORD(d.data.ptr + 2, OEM); + TOWORD(d.data.ptr + 4, 3); // 3 = delegate + TOLONG(d.data.ptr + 6, 2); // count of type indices to follow + TOLONG(d.data.ptr + 10, key); // void* type + TOLONG(d.data.ptr + 14, functypidx); // function type +} +else +{ + __gshared const ubyte[38] fl = + [ + 0x03, 0x12, // LF_FIELDLIST_V2 + 0x0d, 0x15, // LF_MEMBER_V3 + 0x03, 0x00, // attribute + 0x00, 0x00, 0x00, 0x00, // void* + 0x00, 0x00, // offset + 'p','t','r',0, // "ptr" + 0xf2, 0xf1, // align to 4-byte including length word before data + 0x0d, 0x15, + 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, // ptrtypidx + 0x08, 0x00, + 'f', 'u','n','c','p','t','r', 0, // "funcptr" + 0xf2, 0xf1, + ]; + + debtyp_t* f = debtyp_alloc(fl.sizeof); + memcpy(f.data.ptr,fl.ptr,fl.sizeof); + TOLONG(f.data.ptr + 6, pvidx); + TOLONG(f.data.ptr + 22, ptridx); + TOWORD(f.data.ptr + 26, _tysize[TYnptr]); + idx_t fieldlist = cv_debtyp(f); + + const(char)* id = "dDelegate"; + int idlen = cast(int)strlen(id); + if (idlen > CV8_MAX_SYMBOL_LENGTH) + idlen = CV8_MAX_SYMBOL_LENGTH; + + debtyp_t* d = debtyp_alloc(20 + idlen + 1); + TOWORD(d.data.ptr, LF_STRUCTURE_V3); + TOWORD(d.data.ptr + 2, 2); // count + TOWORD(d.data.ptr + 4, 0); // property + TOLONG(d.data.ptr + 6, fieldlist); + TOLONG(d.data.ptr + 10, 0); // dList + TOLONG(d.data.ptr + 14, 0); // vtshape + TOWORD(d.data.ptr + 18, 2 * _tysize[TYnptr]); // size + memcpy(d.data.ptr + 20, id, idlen); + d.data.ptr[20 + idlen] = 0; +} + return cv_debtyp(d); +} + +/**************************************** + * Return type index for a aarray of type Value[Key] + * Input: + * t associative array type + * keyidx key type + * validx value type + */ +@trusted +idx_t cv8_daarray(type* t, idx_t keyidx, idx_t validx) +{ + //printf("cv8_daarray(keyidx = %x, validx = %x)\n", keyidx, validx); + /* Put out a struct: + * struct dAssocArray { + * void* ptr; + * typedef key-type __key_t; + * typedef val-type __val_t; + * } + */ + +static if (0) +{ + debtyp_t* d = debtyp_alloc(18); + TOWORD(d.data.ptr, 0x100F); + TOWORD(d.data.ptr + 2, OEM); + TOWORD(d.data.ptr + 4, 2); // 2 = associative array + TOLONG(d.data.ptr + 6, 2); // count of type indices to follow + TOLONG(d.data.ptr + 10, keyidx); // key type + TOLONG(d.data.ptr + 14, validx); // element type +} +else +{ + type* tv = type_fake(TYnptr); + tv.Tcount++; + idx_t pvidx = cv4_typidx(tv); + type_free(tv); + + __gshared const ubyte[50] fl = + [ + 0x03, 0x12, // LF_FIELDLIST_V2 + 0x0d, 0x15, // LF_MEMBER_V3 + 0x03, 0x00, // attribute + 0x00, 0x00, 0x00, 0x00, // void* + 0x00, 0x00, // offset + 'p','t','r',0, // "ptr" + 0xf2, 0xf1, // align to 4-byte including field id + // offset 18 + 0x10, 0x15, // LF_NESTTYPE_V3 + 0x00, 0x00, // padding + 0x00, 0x00, 0x00, 0x00, // key type + '_','_','k','e','y','_','t',0, // "__key_t" + // offset 34 + 0x10, 0x15, // LF_NESTTYPE_V3 + 0x00, 0x00, // padding + 0x00, 0x00, 0x00, 0x00, // value type + '_','_','v','a','l','_','t',0, // "__val_t" + ]; + + debtyp_t* f = debtyp_alloc(fl.sizeof); + memcpy(f.data.ptr,fl.ptr,fl.sizeof); + TOLONG(f.data.ptr + 6, pvidx); + TOLONG(f.data.ptr + 22, keyidx); + TOLONG(f.data.ptr + 38, validx); + idx_t fieldlist = cv_debtyp(f); + + const(char)* id = t.Tident ? t.Tident : "dAssocArray"; + int idlen = cast(int)strlen(id); + if (idlen > CV8_MAX_SYMBOL_LENGTH) + idlen = CV8_MAX_SYMBOL_LENGTH; + + debtyp_t* d = debtyp_alloc(20 + idlen + 1); + TOWORD(d.data.ptr, LF_STRUCTURE_V3); + TOWORD(d.data.ptr + 2, 1); // count + TOWORD(d.data.ptr + 4, 0); // property + TOLONG(d.data.ptr + 6, fieldlist); + TOLONG(d.data.ptr + 10, 0); // dList + TOLONG(d.data.ptr + 14, 0); // vtshape + TOWORD(d.data.ptr + 18, _tysize[TYnptr]); // size + memcpy(d.data.ptr + 20, id, idlen); + d.data.ptr[20 + idlen] = 0; + +} + return cv_debtyp(d); +} + +} diff --git a/compiler/src/dmd/backend/global.d b/compiler/src/dmd/backend/global.d index ed0abd77bffe..9743eea960b6 100644 --- a/compiler/src/dmd/backend/global.d +++ b/compiler/src/dmd/backend/global.d @@ -35,13 +35,6 @@ alias ErrorCallbackBackend = extern(C++) void function(const(char)* filename, ui package(dmd.backend) __gshared ErrorCallbackBackend errorCallbackBackend; -/// Callback for the backend to fetch cached source-file contents from the -/// front-end FileManager (populated when the module was read). Returns a -/// pointer to the bytes and sets `length`; returns null if unavailable. -alias GetFileContentsCallback = extern(C++) const(ubyte)* function(const(char)* filename, ref size_t length); - -package(dmd.backend) __gshared GetFileContentsCallback getFileContentsCallback; - /** * Backend error report function * Params: diff --git a/compiler/src/dmd/backend/mscoffobj.d b/compiler/src/dmd/backend/mscoffobj.d index f11f6e479983..e9fd0bb8559f 100644 --- a/compiler/src/dmd/backend/mscoffobj.d +++ b/compiler/src/dmd/backend/mscoffobj.d @@ -22,7 +22,7 @@ import dmd.backend.cc; import dmd.backend.cdef; import dmd.backend.code; import dmd.backend.x86.code_x86; -import dmd.backend.codeview; +import dmd.backend.cv8; import dmd.backend.dvec; import dmd.backend.el; import dmd.backend.mem; @@ -32,6 +32,7 @@ import dmd.backend.symbol : symbol_generate, symbol_name, symbol_print, symbol_r import dmd.backend.obj; import dmd.backend.ty; import dmd.backend.type; +import dmd.backend.pdb; import dmd.backend.mscoff; @@ -355,7 +356,7 @@ Obj MsCoffObj_init(OutBuffer* objbuf, const(char)* filename, const(char)* csegna assert(SegData[UDATA].SDseg == UDATA); if (config.fulltypes) - cv_initfile(filename); + config.newpdb ? pdb_initfile(filename) : cv8_initfile(filename); assert(objbuf.length() == 0); return obj; } @@ -374,7 +375,7 @@ void MsCoffObj_initfile(const(char)* filename, const(char)* csegname, const(char { //dbg_printf("MsCoffObj_initfile(filename = %s, modname = %s)\n",filename,modname); if (config.fulltypes) - cv_initmodule(filename, modname); + config.newpdb ? pdb_initmodule(filename, modname) : cv8_initmodule(filename, modname); } /************************************ @@ -449,12 +450,12 @@ private void syment_set_name(SymbolTable32* sym, const(char)* name) { // Use offset into string table // symbols larger than 64kB crash link.exe 14.44 or later with /DEBUG OutBuffer buf; - if (len > CV_MAX_SYMBOL_LENGTH) + if (len > CV8_MAX_SYMBOL_LENGTH) { import dmd.common.blake3; const hash = blake3((cast(ubyte*)name)[0 .. len]); //truncate and append the first 16 bytes of the hash - buf.put(name, CV_MAX_SYMBOL_LENGTH - 33); + buf.put(name, CV8_MAX_SYMBOL_LENGTH - 33); buf.put('_'); buf.writeHexString(hash[0 .. 16], true); len = buf.length; @@ -615,7 +616,7 @@ void MsCoffObj_termfile() //dbg_printf("MsCoffObj_termfile\n"); if (config.addlinenumbers) { - cv_termmodule(); + config.newpdb ? pdb_termmodule() : cv8_termmodule(); } } @@ -635,7 +636,7 @@ void MsCoffObj_term(const(char)[] objfilename) if (config.addlinenumbers) { - cv_termfile(objfilename); + config.newpdb ? pdb_termfile(objfilename) : cv8_termfile(objfilename); } // To allow tooling support for most output files @@ -1007,7 +1008,7 @@ void MsCoffObj_linnum(Srcpos srcpos, int seg, targ_size_t offset) if (srcpos.Slinnum == 0 || !srcpos.Sfilename) return; - cv_linnum(srcpos, cast(uint)offset); + config.newpdb ? pdb_linnum(srcpos, cast(uint)offset) : cv8_linnum(srcpos, cast(uint)offset); } @@ -1845,7 +1846,7 @@ void MsCoffObj_func_start(Symbol* sfunc) sfunc.Soffset = Offset(cseg); if (config.fulltypes) - cv_func_start(sfunc); + config.newpdb ? pdb_func_start(sfunc) : cv8_func_start(sfunc); } /******************************* @@ -1859,7 +1860,7 @@ void MsCoffObj_func_term(Symbol* sfunc) // sfunc.Sident.ptr, sfunc.Soffset,Offset(cseg),sfunc.Sxtrnnum); if (config.fulltypes) - cv_func_term(sfunc); + config.newpdb ? pdb_func_term(sfunc) : cv8_func_term(sfunc); } /******************************** diff --git a/compiler/src/dmd/backend/pdb.d b/compiler/src/dmd/backend/pdb.d new file mode 100644 index 000000000000..9b971a6ba1fa --- /dev/null +++ b/compiler/src/dmd/backend/pdb.d @@ -0,0 +1,929 @@ +/** + * Modern CodeView / PDB symbolic debug info generation. + * + * This module is a self-contained emitter for the `.debug$S` and `.debug$T` + * sections for Win64 MS-COFF objects. It is an alternative to cv8.d intended to + * eventually retire cv4.d/cv8.d. It targets the record set documented by LLVM + * (https://llvm.org/docs/PDB/) and Microsoft + * (https://github.com/microsoft/microsoft-pdb) and is enabled by the + * `-preview=newpdb` compiler flag. + * + * It does not modify the existing CV4/CV8 paths; the type-record pool helpers in + * cgcv.d are reused read-only. + * + * Compiler implementation of the + * $(LINK2 https://www.dlang.org, D programming language). + * + * Copyright: Copyright (C) 2026 by The D Language Foundation, All Rights Reserved + * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/backend/pdb.d, backend/pdb.d) + */ + +module dmd.backend.pdb; + +import core.stdc.stdio; +import core.stdc.stdlib; +import core.stdc.string; +extern (C) nothrow char* getcwd(char*, size_t); + +import dmd.backend.cc; +import dmd.backend.cdef; +import dmd.backend.cgcv; +import dmd.backend.code; +import dmd.backend.x86.code_x86; +import dmd.backend.cv4; +import dmd.backend.mem; +import dmd.backend.mscoffobj; +import dmd.backend.obj; +import dmd.backend.oper; +import dmd.common.outbuffer; +import dmd.backend.rtlsym; +import dmd.backend.symbol : globsym; +import dmd.backend.ty; +import dmd.backend.type; +import dmd.backend.dvarstats; + +nothrow: +@safe: + +/* ======================================================================== */ +/* CodeView constants not present in cv4.d, kept local to avoid touching */ +/* the legacy CV4/CV8 implementations. */ +/* ======================================================================== */ + +// Symbol records (DEBUG_S_SYMBOLS subsection content) +enum +{ + S_END_PDB = 0x0006, + S_OBJNAME_V3 = 0x1101, + S_COMPILE3 = 0x113C, + S_ENVBLOCK = 0x113D, + S_BUILDINFO = 0x114C, + S_FRAMEPROC = 0x1012, + S_GPROC32_ID = 0x1147, + S_LPROC32_ID = 0x1146, + S_PROC_ID_END = 0x114F, + S_GPROC32 = 0x1110, + S_LPROC32 = 0x110F, + S_REGREL32 = 0x1111, + S_BPREL32_V3 = 0x110B, + S_REGISTER_V3 = 0x1106, + S_LDATA32 = 0x110C, + S_GDATA32 = 0x110D, + S_LTHREAD32 = 0x1112, + S_GTHREAD32 = 0x1113, + S_PUB32 = 0x110E, + S_CONSTANT_V3 = 0x1107, + S_UDT_V3_ = 0x1108, + S_LABEL32_V3 = 0x1105, + S_THUNK32 = 0x1102, + S_LOCAL = 0x113E, + S_BLOCK32 = 0x1103, + S_RETURN = 0x000D, +} + +// Type leaf records used by the ID stream / source lines +enum +{ + LF_FUNC_ID = 0x1601, + LF_STRING_ID = 0x1605, + LF_BUILDINFO = 0x1603, + LF_UDT_SRC_LINE = 0x1606, + LF_SUBSTR_LIST = 0x1604, + LF_ALIAS = 0x150A, +} + +// .debug$S subsection kinds +enum +{ + DEBUG_S_SYMBOLS = 0xF1, + DEBUG_S_LINES = 0xF2, + DEBUG_S_STRINGTABLE = 0xF3, + DEBUG_S_FILECHKSMS = 0xF4, +} + +// File checksum kinds +enum +{ + CHKSUM_NONE = 0, + CHKSUM_MD5 = 1, + CHKSUM_SHA1 = 2, + CHKSUM_SHA256 = 3, +} + +// keep symbol records well under linker limits +enum PDB_MAX_SYMBOL_LENGTH = 0xffd8; + +// CV_PUBSYMFLAGS +enum PUB_FUNCTION = 0x00000002; + +/* ======================================================================== */ +/* State */ +/* ======================================================================== */ + +private __gshared OutBuffer* F1_buf; // symbols +private __gshared OutBuffer* F2_buf; // line numbers +private __gshared OutBuffer* F3_buf; // string table of source file names +private __gshared OutBuffer* F4_buf; // file checksums + +struct F1_Fixups +{ + Symbol* s; + uint offset; + uint value; +} + +private __gshared OutBuffer* F1fixup; + +struct FuncData +{ + Symbol* sfunc; + uint section_length; + const(char)* srcfilename; + uint srcfileoff; + uint linepairstart; + uint linepairbytes; + uint linepairsegment; + OutBuffer* f1buf; + OutBuffer* f1fixup; +} + +__gshared FuncData currentfuncdata; + +private __gshared OutBuffer* funcdata; // array of FuncData +private __gshared OutBuffer* linepair; // offset/line pairs + +// Determine if this Symbol lives in a COMDAT +@trusted +private bool symbol_iscomdat_pdb(Symbol* s) +{ + return s.Sclass == SC.comdat || + config.flags2 & CFG2comdat && s.Sclass == SC.inline || + config.flags4 & CFG4allcomdat && s.Sclass == SC.global; +} + +/* ======================================================================== */ +/* Name encoding */ +/* ======================================================================== */ + +private @trusted +void pdb_writename(OutBuffer* buf, const(char)* name, size_t len) +{ + if (!(config.flags2 & CFG2gms)) + { + buf.writen(name, len); + return; + } + + const(char)* start = name; + const(char)* cur = strchr(start, '.'); + const(char)* end = start + len; + while (cur != null) + { + if (cur >= end) + { + buf.writen(start, end - start); + return; + } + buf.writen(start, cur - start); + buf.writeByte('@'); + start = cur + 1; + if (start >= end) + return; + cur = strchr(start, '.'); + } + buf.writen(start, end - start); +} + +/* ======================================================================== */ +/* File lifecycle */ +/* ======================================================================== */ + +@trusted +void pdb_initfile(const(char)* filename) +{ + void initBuf(ref OutBuffer* ptr) + { + if (!ptr) + { + ptr = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); + ptr.reserve(1024); + } + ptr.reset(); + } + + initBuf(F1_buf); + initBuf(F1fixup); + initBuf(F2_buf); + + initBuf(F3_buf); + F3_buf.writeByte(0); // first "filename" + + initBuf(F4_buf); + initBuf(funcdata); + initBuf(linepair); + + memset(¤tfuncdata, 0, currentfuncdata.sizeof); + currentfuncdata.f1buf = F1_buf; + currentfuncdata.f1fixup = F1fixup; + + cv_init(); +} + +void pdb_initmodule(const(char)* filename, const(char)* modulename) +{ +} + +@trusted +void pdb_termmodule() +{ + assert(config.objfmt == OBJ_MSCOFF); +} + +@trusted +void pdb_termfile(const(char)[] objfilename) +{ + int seg = MsCoffObj_seg_debugS(); + + uint value = 4; // CV signature C13 + objmod.bytes(seg, 0, (cast(void*)&value)[0 .. 4]); + + auto buf = OutBuffer(1024); + + // S_OBJNAME + { + size_t len = objfilename.length; + buf.write16(cast(int)(2 + 4 + len + 1)); + buf.write16(S_OBJNAME_V3); + buf.write32(0); // signature + buf.write(objfilename.ptr, cast(uint)(len + 1)); + } + + // S_COMPILE3 + { + // Honor the configured language/compiler instead of hardcoding it: + // emit the D language index unless the debug info was requested for + // non-DMD (Microsoft) debuggers (-gc sets CFG2gms), and report the + // actual compiler version string (dmd/ldc/gdc) via config._version. + const uint flags = config.flags2 & CFG2gms ? 0 : 'D'; // iLanguage in low byte + const(char)[] ver = config._version; + buf.write16(cast(int)(2 + 4 + 2 + 8 + 8 + ver.length + 1)); + buf.write16(S_COMPILE3); + buf.write32(flags); + buf.write16(I64 ? 0xD0 : 0x07); // machine: AMD64 / x86 + buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // FE ver + buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // BE ver + buf.write(ver.ptr, cast(uint)ver.length); + buf.writeByte(0); + } + + pdb_writesection(seg, DEBUG_S_SYMBOLS, &buf); + + // S_ENVBLOCK: build environment (cwd, tool), double-null terminated + { + auto ebuf = OutBuffer(128); + char[260] cwd = 0; + if (!getcwd(cwd.ptr, cwd.sizeof)) + cwd[0] = 0; + size_t cwdlen = strlen(cwd.ptr); + uint rec = cast(uint)(2 + 1 + 4 + cwdlen + 1 + 4 + 1 + 1); + ebuf.write16(cast(int)rec); + ebuf.write16(S_ENVBLOCK); + ebuf.writeByte(0); // flags + ebuf.write("cwd\0".ptr, 4); + ebuf.write(cwd.ptr, cast(uint)(cwdlen + 1)); + ebuf.write("cmd\0".ptr, 4); + ebuf.writeByte(0); // empty cmd value + ebuf.writeByte(0); // terminating double-null + pdb_writesection(seg, DEBUG_S_SYMBOLS, &ebuf); + } + + // S_BUILDINFO referencing an LF_BUILDINFO id record + { + auto bbuf = OutBuffer(64); + idx_t biidx = pdb_buildinfo(); + bbuf.write16(2 + 4); + bbuf.write16(S_BUILDINFO); + bbuf.write32(biidx); + pdb_writesection(seg, DEBUG_S_SYMBOLS, &bbuf); + } + + uint length = cast(uint)funcdata.length(); + ubyte* p = funcdata.buf; + for (uint u = 0; u < length; u += FuncData.sizeof) + { + FuncData* fd = cast(FuncData*)(p + u); + + F2_buf.reset(); + F2_buf.write32(cast(uint)fd.sfunc.Soffset); + F2_buf.write32(0); + F2_buf.write32(fd.section_length); + F2_buf.write(linepair.buf + fd.linepairstart, fd.linepairbytes); + + int f2seg = seg; + if (symbol_iscomdat_pdb(fd.sfunc)) + { + f2seg = MsCoffObj_seg_debugS_comdat(fd.sfunc); + objmod.bytes(f2seg, 0, (cast(void*)&value)[0 .. 4]); + } + + uint offset = cast(uint)SegData[f2seg].SDoffset + 8; + pdb_writesection(f2seg, DEBUG_S_LINES, F2_buf); + objmod.reftoident(f2seg, offset, fd.sfunc, 0, CF.seg | CF.off); + + if (f2seg != seg && fd.f1buf.length()) + { + const uint f1offset = cast(uint)SegData[f2seg].SDoffset; + pdb_writesection(f2seg, DEBUG_S_SYMBOLS, fd.f1buf); + + const uint fixupLength = cast(uint)fd.f1fixup.length(); + ubyte* pfixup = fd.f1fixup.buf; + for (uint v = 0; v < fixupLength; v += F1_Fixups.sizeof) + { + F1_Fixups* f = cast(F1_Fixups*)(pfixup + v); + objmod.reftoident(f2seg, f1offset + 8 + f.offset, f.s, f.value, CF.seg | CF.off); + } + } + } + + if (F3_buf.length() > 1) + pdb_writesection(seg, DEBUG_S_STRINGTABLE, F3_buf); + + if (F4_buf.length() > 0) + pdb_writesection(seg, DEBUG_S_FILECHKSMS, F4_buf); + + if (F1_buf.length()) + { + uint f1offset = cast(uint)SegData[seg].SDoffset; + pdb_writesection(seg, DEBUG_S_SYMBOLS, F1_buf); + + length = cast(uint)F1fixup.length(); + p = F1fixup.buf; + for (uint u = 0; u < length; u += F1_Fixups.sizeof) + { + F1_Fixups* f = cast(F1_Fixups*)(p + u); + objmod.reftoident(seg, f1offset + 8 + f.offset, f.s, f.value, CF.seg | CF.off); + } + } + + cv_term(); // .debug$T +} + +/* ======================================================================== */ +/* Functions */ +/* ======================================================================== */ + +@trusted +void pdb_func_start(Symbol* sfunc) +{ + currentfuncdata.sfunc = sfunc; + currentfuncdata.section_length = 0; + currentfuncdata.srcfilename = null; + currentfuncdata.linepairstart += currentfuncdata.linepairbytes; + currentfuncdata.linepairbytes = 0; + currentfuncdata.f1buf = F1_buf; + currentfuncdata.f1fixup = F1fixup; + if (symbol_iscomdat_pdb(sfunc)) + { + currentfuncdata.f1buf = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); + currentfuncdata.f1buf.reserve(128); + currentfuncdata.f1fixup = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); + currentfuncdata.f1fixup.reserve(128); + } + varStats_startFunction(); +} + +@trusted +void pdb_func_term(Symbol* sfunc) +{ + assert(currentfuncdata.sfunc == sfunc); + currentfuncdata.section_length = cast(uint)sfunc.Ssize; + funcdata.write(¤tfuncdata, currentfuncdata.sizeof); + + assert(tyfunc(sfunc.ty())); + idx_t typidx; + func_t* fn = sfunc.Sfunc; + if (fn.Fclass) + { + // Member functions get a dedicated LF_MFUNCTION_V2 type record that + // records the enclosing class and the `this` pointer type. This info + // isn't available inside cv4_typidx, so replicate cv8_func_term here. + uint nparam; + const ubyte call = cv4_callconv(sfunc.Stype); + const idx_t paramidx = cv4_arglist(sfunc.Stype, &nparam); + const uint next = cv4_typidx(sfunc.Stype.Tnext); + type* classtype = cast(type*)fn.Fclass; + const uint classidx = cv4_typidx(classtype); + type* tp = type_allocn(TYnptr, classtype); + const uint thisidx = cv4_typidx(tp); + debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4 + 1 + 1 + 2 + 4 + 4); + TOWORD(d.data.ptr, LF_MFUNCTION_V2); + TOLONG(d.data.ptr + 2, next); // return type + TOLONG(d.data.ptr + 6, classidx); // class type + TOLONG(d.data.ptr + 10, thisidx); // this type + d.data.ptr[14] = call; + d.data.ptr[15] = 0; // reserved + TOWORD(d.data.ptr + 16, nparam); + TOLONG(d.data.ptr + 18, paramidx); + TOLONG(d.data.ptr + 22, 0); // this adjust + typidx = cv_debtyp(d); + } + else + typidx = cv_typidx(sfunc.Stype); + + const(char)* id = sfunc.prettyIdent ? sfunc.prettyIdent : prettyident(sfunc); + size_t len = strlen(id); + if (len > PDB_MAX_SYMBOL_LENGTH) + len = PDB_MAX_SYMBOL_LENGTH; + + auto buf = currentfuncdata.f1buf; + buf.reserve(cast(uint)(2 + 2 + 4 * 7 + 6 + 1 + len + 1)); + buf.write16n(cast(int)(2 + 4 * 7 + 6 + 1 + len + 1)); + buf.write16n(sfunc.Sclass == SC.static_ ? S_LPROC32 : S_GPROC32); + buf.write32(0); // parent + buf.write32(0); // pend + buf.write32(0); // pnext + buf.write32(cast(uint)currentfuncdata.section_length); + buf.write32(cast(uint)cgstate.startoffset); // prolog size + buf.write32(cast(uint)cgstate.retoffset); // epilog offset + buf.write32(typidx); + + F1_Fixups f1f; + f1f.s = sfunc; + f1f.offset = cast(uint)buf.length(); + f1f.value = 0; + currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); + buf.write32(0); + buf.write16n(0); + buf.writeByte(0); // flags + buf.writen(id, len); + buf.writeByte(0); + + // S_FRAMEPROC: describe this function's stack frame + { + // Locals and parameters are addressed relative to the frame pointer + // (RBP/EBP), matching the S_REGREL32 records emitted for them; the + // CodeView "FramePtr" register encoding for that is 2. + uint frameflags = (2 << 14) | (2 << 16); // encoded local/param base ptr + if (cgstate.anyiasm) + frameflags |= 1 << 3; // fHasInlAsm + if (cgstate.usednteh) + frameflags |= 1 << 4; // fHasEH + if (config.flags2 & CFG2stomp) + frameflags |= 1 << 8; // fSecurityChecks + if (config.flags4 & CFG4speed) + frameflags |= 1 << 20; // fOptSpeed + + buf.write16(2 + 4 + 4 + 4 + 4 + 4 + 2 + 4); + buf.write16(S_FRAMEPROC); + buf.write32(cast(uint)localsize); // cbFrame: total frame size + buf.write32(0); // cbPad + buf.write32(0); // offPad + buf.write32(0); // cbSaveRegs + buf.write32(0); // offExHdlr + buf.write16(0); // sectExHdlr + buf.write32(frameflags); // flags + } + + struct pdbblk + { + nothrow: + struct block_v3_data + { + ushort len; + ushort id; + uint pParent; + uint pEnd; + uint length; + uint offset; + ushort seg; + ubyte[1] name; + } + static void endArgs() + { + auto b = currentfuncdata.f1buf; + b.write16(2); + b.write16(0x000A); // S_ENDARG + } + static void beginBlock(int offset, int length) + { + auto b = currentfuncdata.f1buf; + uint soffset = cast(uint)b.length(); + block_v3_data block32 = { block_v3_data.sizeof - 2, S_BLOCK32, 0, 0, length, offset, 0, [ 0 ] }; + b.write(&block32, block32.sizeof); + size_t offOffset = cast(char*)&block32.offset - cast(char*)&block32; + F1_Fixups f; + f.s = currentfuncdata.sfunc; + f.offset = cast(uint)(soffset + offOffset); + f.value = offset; + currentfuncdata.f1fixup.write(&f, f.sizeof); + } + static void endBlock() + { + auto b = currentfuncdata.f1buf; + b.write16(2); + b.write16(S_END_PDB); + } + } + varStats_writeSymbolTable(sfunc, globsym, &pdb_outsym, &pdbblk.endArgs, &pdbblk.beginBlock, &pdbblk.endBlock); + + // No S_RETURN record: its register list uses single-byte CodeView register + // codes, which cannot encode x64 return registers (e.g. CV_AMD64_RAX = 328). + // Both MSVC and cv8.d omit it, so a real record can't be produced here. + + buf.write16(2); + buf.write16(S_END_PDB); + + currentfuncdata.f1buf = F1_buf; + currentfuncdata.f1fixup = F1fixup; +} + +/* ======================================================================== */ +/* Line numbers */ +/* ======================================================================== */ + +@trusted +void pdb_linnum(Srcpos srcpos, uint offset) +{ + const sfilename = srcpos.Sfilename; + if (!sfilename) + return; + + varStats_recordLineOffset(srcpos, offset); + + __gshared uint lastoffset; + __gshared uint lastlinnum; + + if (!currentfuncdata.srcfilename || + (currentfuncdata.srcfilename != sfilename && strcmp(currentfuncdata.srcfilename, sfilename))) + { + currentfuncdata.srcfilename = sfilename; + uint srcfileoff = pdb_addfile(sfilename); + currentfuncdata.linepairsegment = currentfuncdata.linepairstart + currentfuncdata.linepairbytes; + linepair.write32(srcfileoff); + linepair.write32(0); + linepair.write32(12); + currentfuncdata.linepairbytes += 12; + } + else if (offset <= lastoffset || srcpos.Slinnum == lastlinnum) + return; + + lastoffset = offset; + lastlinnum = srcpos.Slinnum; + linepair.write32(offset); + linepair.write32(srcpos.Slinnum | 0x80000000); + currentfuncdata.linepairbytes += 8; + + auto segmentbytes = currentfuncdata.linepairstart + currentfuncdata.linepairbytes - currentfuncdata.linepairsegment; + auto segmentheader = cast(uint*)(linepair.buf + currentfuncdata.linepairsegment); + segmentheader[1] = (segmentbytes - 12) / 8; + segmentheader[2] = segmentbytes; +} + +/* ======================================================================== */ +/* Source files (F3 names + F4 checksums via blake3) */ +/* ======================================================================== */ + +@trusted +uint pdb_addfile(const(char)* filename) +{ + uint length = cast(uint)F3_buf.length(); + ubyte* p = F3_buf.buf; + size_t len = strlen(filename); + + __gshared char[260] cwd = 0; + __gshared uint cwdlen; + bool abs = (*filename == '\\') || (*filename == '/') || + (*filename && filename[1] == ':'); + + if (!abs && cwd[0] == 0) + { + if (getcwd(cwd.ptr, cwd.sizeof)) + { + cwdlen = cast(uint)strlen(cwd.ptr); + if (cwd[cwdlen - 1] != '\\' && cwd[cwdlen - 1] != '/') + cwd[cwdlen++] = '\\'; + } + } + uint off = 1; + while (off + len < length) + { + if (!abs) + { + if (memcmp(p + off, cwd.ptr, cwdlen) == 0 && + memcmp(p + off + cwdlen, filename, len + 1) == 0) + goto L1; + } + else if (memcmp(p + off, filename, len + 1) == 0) + goto L1; + off += strlen(cast(const(char)*)(p + off)) + 1; + } + off = length; + if (!abs) + F3_buf.write(cwd.ptr, cwdlen); + F3_buf.write(filename, cast(uint)(len + 1)); + +L1: + length = cast(uint)F4_buf.length(); + p = F4_buf.buf; + uint u = 0; + while (u + 6 <= length) + { + if (off == *cast(uint*)(p + u)) + return u; + u += 4; // file name offset + ubyte cklen = *(p + u); // checksum size + u += 2; // skip size + kind bytes + u += cklen; // skip checksum bytes + u = (u + 3) & ~3; // realign to 4 + } + + // Not present; append a checksum computed over the source file's *content* + // (not over its name) so debuggers can verify the source matches. + F4_buf.write32(off); + ubyte[32] hash = void; + if (pdb_filehash(filename, hash)) + { + F4_buf.writeByte(32); // checksum size + F4_buf.writeByte(CHKSUM_SHA256); // 32-byte checksum slot + F4_buf.write(hash.ptr, 32); + } + else + { + F4_buf.writeByte(0); // no checksum available + F4_buf.writeByte(CHKSUM_NONE); + } + while (F4_buf.length() & 3) + F4_buf.writeByte(0); + return length; +} + +/* Compute a blake3 hash over the *content* of the named source file. + * Returns: true on success (hash filled in), false if the file can't be read. + */ +private @trusted +bool pdb_filehash(const(char)* filename, ref ubyte[32] hash) +{ + FILE* fp = fopen(filename, "rb"); + if (!fp) + return false; + scope(exit) fclose(fp); + + if (fseek(fp, 0, SEEK_END) != 0) + return false; + const long size = ftell(fp); + if (size < 0 || fseek(fp, 0, SEEK_SET) != 0) + return false; + + ubyte* data = cast(ubyte*)malloc(size ? cast(size_t)size : 1); + if (!data) + return false; + scope(exit) free(data); + + const size_t nread = fread(data, 1, cast(size_t)size, fp); + + import dmd.common.blake3; + hash = blake3(data[0 .. nread]); + return true; +} + +private @trusted +void pdb_writesection(int seg, uint type, OutBuffer* buf) +{ + uint off = cast(uint)SegData[seg].SDoffset; + objmod.bytes(seg, off, (cast(void*)&type)[0 .. 4]); + uint length = cast(uint)buf.length(); + objmod.bytes(seg, off + 4, (cast(void*)&length)[0 .. 4]); + objmod.bytes(seg, off + 8, buf.buf[0 .. length]); + uint pad = ((length + 3) & ~3) - length; + objmod.lidata(seg, off + 8 + length, pad); +} + +/* ======================================================================== */ +/* Symbols */ +/* ======================================================================== */ + +@trusted +void pdb_outsym(Symbol* s) +{ + if (s.Sflags & SFLnodebug) + return; + + idx_t typidx = cv_typidx(s.Stype); + const(char)* id = s.prettyIdent ? s.prettyIdent : prettyident(s); + size_t len = strlen(id); + if (len > PDB_MAX_SYMBOL_LENGTH) + len = PDB_MAX_SYMBOL_LENGTH; + + F1_Fixups f1f; + f1f.value = 0; + auto buf = currentfuncdata.f1buf; + + uint sr; + uint base; + switch (s.Sclass) + { + case SC.parameter: + case SC.regpar: + case SC.shadowreg: + if (s.Sfl == FL.reg) + { + s.Sfl = FL.para; + pdb_outsym(s); + s.Sfl = FL.reg; + goto case_register; + } + base = cast(uint)(cgstate.Para.size - cgstate.BPoff); + goto L1; + + case SC.auto_: + if (s.Sfl == FL.reg) + goto case_register; + case_auto: + base = cast(uint)cgstate.Auto.size; + L1: + if (s.Sscope) + break; + buf.reserve(cast(uint)(2 + 2 + 4 + 4 + 2 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + 4 + 2 + len + 1)); + buf.write16n(S_REGREL32); + buf.write32(cast(uint)(s.Soffset + base + cgstate.BPoff)); + buf.write32(typidx); + buf.write16n(I64 ? 334 : 22); + pdb_writename(buf, id, len); + buf.writeByte(0); + break; + + case SC.bprel: + base = -cgstate.BPoff; + goto L1; + + case SC.fastpar: + if (s.Sfl != FL.reg) + { + base = cast(uint)cgstate.Fast.size; + goto L1; + } + goto L2; + + case SC.register: + if (s.Sfl != FL.reg) + goto case_auto; + goto case; + + case SC.pseudo: + case_register: + L2: + buf.reserve(cast(uint)(2 + 2 + 4 + 2 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + 2 + len + 1)); + buf.write16n(S_REGISTER_V3); + buf.write32(typidx); + buf.write16n(pdb_regnum(s)); + pdb_writename(buf, id, len); + buf.writeByte(0); + break; + + case SC.extern_: + break; + + case SC.static_: + case SC.locstat: + sr = S_LDATA32; + goto Ldata; + + case SC.global: + case SC.comdat: + case SC.comdef: + sr = S_GDATA32; + Ldata: + if (s.ty() & mTYthread) // full TLS support + sr = (sr == S_GDATA32) ? S_GTHREAD32 : S_LTHREAD32; + + buf.reserve(cast(uint)(2 + 2 + 4 + 6 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + 6 + len + 1)); + buf.write16n(sr); + buf.write32(typidx); + f1f.s = s; + f1f.offset = cast(uint)buf.length(); + currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); + buf.write32(0); + buf.write16n(0); + pdb_writename(buf, id, len); + buf.writeByte(0); + break; + + default: + break; + } +} + +/* Put out a name for a user defined type. */ +@trusted +void pdb_udt(const(char)* id, idx_t typidx) +{ + auto buf = currentfuncdata.f1buf; + size_t len = strlen(id); + if (len > PDB_MAX_SYMBOL_LENGTH) + len = PDB_MAX_SYMBOL_LENGTH; + buf.reserve(cast(uint)(2 + 2 + 4 + len + 1)); + buf.write16n(cast(uint)(2 + 4 + len + 1)); + buf.write16n(S_UDT_V3_); + buf.write32(typidx); + pdb_writename(buf, id, len); + buf.writeByte(0); +} + +/* ======================================================================== */ +/* ID-stream type records (reuse the shared cgcv type pool, read-only) */ +/* ======================================================================== */ + +/* LF_STRING_ID: an interned string returning a type index. */ +@trusted +idx_t pdb_string_id(const(char)* s) +{ + if (!s) s = ""; + debtyp_t* d = debtyp_alloc(2 + 4 + cv_stringbytes(s)); + TOWORD(d.data.ptr, LF_STRING_ID); + TOLONG(d.data.ptr + 2, 0); // substring list id + cv_namestring(d.data.ptr + 6, s); + return cv_debtyp(d); +} + +/* LF_BUILDINFO: cwd, build tool, source, pdb, args. */ +@trusted +idx_t pdb_buildinfo() +{ + char[260] cwd = 0; + if (!getcwd(cwd.ptr, cwd.sizeof)) + cwd[0] = 0; + idx_t cwdId = pdb_string_id(cwd.ptr); + idx_t toolId = pdb_string_id("dmd"); + idx_t srcId = pdb_string_id(""); + idx_t pdbId = pdb_string_id(""); + idx_t argId = pdb_string_id(""); + debtyp_t* d = debtyp_alloc(2 + 2 + 5 * 4); + TOWORD(d.data.ptr, LF_BUILDINFO); + TOWORD(d.data.ptr + 2, 5); // count + TOLONG(d.data.ptr + 4, cwdId); + TOLONG(d.data.ptr + 8, toolId); + TOLONG(d.data.ptr + 12, srcId); + TOLONG(d.data.ptr + 16, pdbId); + TOLONG(d.data.ptr + 20, argId); + return cv_debtyp(d); +} + +/* LF_FUNC_ID: ties a function type to its name for the ID stream. */ +@trusted +idx_t pdb_func_id(const(char)* id, idx_t functype) +{ + debtyp_t* d = debtyp_alloc(2 + 4 + 4 + cv_stringbytes(id)); + TOWORD(d.data.ptr, LF_FUNC_ID); + TOLONG(d.data.ptr + 2, 0); // parent scope id + TOLONG(d.data.ptr + 6, functype); + cv_namestring(d.data.ptr + 10, id); + return cv_debtyp(d); +} + +/* LF_UDT_SRC_LINE: source location of a user-defined type. */ +@trusted +idx_t pdb_udt_src_line(idx_t typidx, idx_t srcId, uint line) +{ + debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4); + TOWORD(d.data.ptr, LF_UDT_SRC_LINE); + TOLONG(d.data.ptr + 2, typidx); + TOLONG(d.data.ptr + 6, srcId); + TOLONG(d.data.ptr + 10, line); + return cv_debtyp(d); +} + +/* Codeview register number for symbol s. */ +int pdb_regnum(Symbol* s) +{ + int reg = s.Sreglsw; + assert(s.Sfl == FL.reg); + if ((1 << reg) & XMMREGS) + return reg - XMM0 + 154; + switch (type_size(s.Stype)) + { + case 1: + if (reg < 4) reg += 1; + else if (reg < 8) reg += 324 - 4; + else reg += 344 - 4; + break; + case 2: + if (reg < 8) reg += 9; + else reg += 352 - 8; + break; + case 4: + if (reg < 8) reg += 17; + else reg += 360 - 8; + break; + case 8: + reg += 328; + break; + default: + reg = 0; + break; + } + return reg; +} diff --git a/compiler/src/dmd/cli.d b/compiler/src/dmd/cli.d index f19b3d227776..52e9b64ba2a9 100644 --- a/compiler/src/dmd/cli.d +++ b/compiler/src/dmd/cli.d @@ -1120,6 +1120,8 @@ dmd -cov -unittest myprog.d "https://dlang.org/spec/attribute.html#system-variables"), Feature("fastdfa", "useFastDFA", "Fast dataflow analysis engine, experimental"), + Feature("newpdb", "newpdb", + "use the modern PDB/CodeView debug info emitter"), ]; } diff --git a/compiler/src/dmd/dmsc.d b/compiler/src/dmd/dmsc.d index 70072e5361e4..143ce608f474 100644 --- a/compiler/src/dmd/dmsc.d +++ b/compiler/src/dmd/dmsc.d @@ -29,27 +29,10 @@ import dmd.backend.backconfig; import dmd.backend.go; import dmd.backend.cc; import dmd.backend.cdef; -import dmd.backend.global : ErrorCallbackBackend, GetFileContentsCallback; +import dmd.backend.global : ErrorCallbackBackend; import dmd.backend.ty; import dmd.backend.type; -import dmd.file_manager : FileManager; - -/// Callback for the backend to fetch cached source-file contents from the -/// front-end FileManager (populated when the module was read), so that hashing -/// source files for debug info reuses the cache instead of re-reading disk. -extern(C++) const(ubyte)* getFileContentsBackend(const(char)* filename, ref size_t length) -{ - length = 0; - if (!global.fileManager) - return null; - const(ubyte)[] data = global.fileManager.getFileContents(FileName(filename[0 .. strlen(filename)])); - if (!data) - return null; - length = data.length; - return data.ptr; -} - /************************************** * Initialize backend config variables. * Params: @@ -112,10 +95,10 @@ void backend_init(const ref Param params, const ref DMDparams driverParams, cons exfmt, params.addMain, driverParams.symImport != SymImport.none, + params.newpdb, go, // FIXME: casting to @nogc because errors.d is not marked @nogc yet cast(ErrorCallbackBackend) &errorBackend, - cast(GetFileContentsCallback) &getFileContentsBackend, ); out_config_debug( diff --git a/compiler/src/dmd/globals.d b/compiler/src/dmd/globals.d index 0c4f9eb5417c..b94f1a35cea9 100644 --- a/compiler/src/dmd/globals.d +++ b/compiler/src/dmd/globals.d @@ -216,6 +216,7 @@ extern (C++) struct Param // https://issues.dlang.org/show_bug.cgi?id=14246 FeatureState systemVariables; // limit access to variables marked @system from @safe code bool useFastDFA; // Use fast data flow analysis engine + bool newpdb; // use the modern PDB/CodeView debug info emitter CHECKENABLE useInvariants = CHECKENABLE._default; // generate class invariant checks CHECKENABLE useIn = CHECKENABLE._default; // generate precondition checks diff --git a/compiler/src/dmd/glue/e2ir.d b/compiler/src/dmd/glue/e2ir.d index 4c7c2b660b9b..2939c47e4644 100644 --- a/compiler/src/dmd/glue/e2ir.d +++ b/compiler/src/dmd/glue/e2ir.d @@ -70,7 +70,7 @@ import dmd.backend.cc; import dmd.backend.cdef; import dmd.backend.cgcv; import dmd.backend.code; -import dmd.backend.codeview; +import dmd.backend.cv4; import dmd.backend.dt; import dmd.backend.el; import dmd.backend.dout : out_readonly_comdat, out_string_literal; diff --git a/compiler/src/dmd/glue/s2ir.d b/compiler/src/dmd/glue/s2ir.d index 619597319ca2..b17c19108ba9 100644 --- a/compiler/src/dmd/glue/s2ir.d +++ b/compiler/src/dmd/glue/s2ir.d @@ -59,7 +59,7 @@ import dmd.backend.cdef; import dmd.backend.cgcv; import dmd.backend.code; import dmd.backend.x86.code_x86; -import dmd.backend.codeview; +import dmd.backend.cv4; import dmd.backend.dt; import dmd.backend.el; import dmd.backend.blockopt : block_appendexp, block_calloc, block_goto, blockopt; diff --git a/compiler/src/dmd/glue/tocvdebug.d b/compiler/src/dmd/glue/tocvdebug.d index 0bc23a883418..12c1f350db1e 100644 --- a/compiler/src/dmd/glue/tocvdebug.d +++ b/compiler/src/dmd/glue/tocvdebug.d @@ -46,7 +46,7 @@ import dmd.backend.cc; import dmd.backend.cdef; import dmd.backend.cgcv; import dmd.backend.code; -import dmd.backend.codeview; +import dmd.backend.cv4; import dmd.backend.dt; import dmd.backend.obj; import dmd.backend.oper; @@ -284,7 +284,7 @@ uint cv_align(ubyte* p, uint n) void cv_udt(const char* id, uint typidx) { if (config.fulltypes == CV8) - return cv_udt_symbol(id, typidx); + return cv8_udt(id, typidx); const len = strlen(id); version (AArch64) // TODO AArch64 diff --git a/compiler/src/dmd/glue/toobj.d b/compiler/src/dmd/glue/toobj.d index f7d881ede426..76a2f3cbb12d 100644 --- a/compiler/src/dmd/glue/toobj.d +++ b/compiler/src/dmd/glue/toobj.d @@ -74,7 +74,7 @@ import dmd.backend.cdef; import dmd.backend.cgcv; import dmd.backend.code; import dmd.backend.x86.code_x86; -import dmd.backend.codeview; +import dmd.backend.cv4; import dmd.backend.dt; import dmd.backend.el; import dmd.backend.dout : out_readonly, outdata; diff --git a/compiler/test/compilable/previewhelp.d b/compiler/test/compilable/previewhelp.d index 94a01ced14bf..117049a56a73 100644 --- a/compiler/test/compilable/previewhelp.d +++ b/compiler/test/compilable/previewhelp.d @@ -20,5 +20,6 @@ Upcoming language changes listed by -preview=name: =fixImmutableConv disallow `void[]` data from holding immutable data (https://dlang.org/changelog/2.101.0.html#dmd.fix-immutable-conv, https://issues.dlang.org/show_bug.cgi?id=17148) =systemVariables disable access to variables marked '@system' from @safe code (https://dlang.org/spec/attribute.html#system-variables) =fastdfa Fast dataflow analysis engine, experimental + =newpdb use the modern PDB/CodeView debug info emitter ---- */ diff --git a/compiler/test/runnable/testpdb.d b/compiler/test/runnable/testpdb.d index a8c9706be508..0715ab17f67c 100644 --- a/compiler/test/runnable/testpdb.d +++ b/compiler/test/runnable/testpdb.d @@ -1,4 +1,5 @@ // REQUIRED_ARGS: -gf -mixin=${RESULTS_DIR}/runnable/testpdb.mixin -preview=bitfields +// REQUIRED_ARGS(windows): -preview=newpdb // PERMUTE_ARGS: import core.time; From da1264e485832338443ac59c20b60210d571447e Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Sun, 5 Jul 2026 18:30:19 -0700 Subject: [PATCH 14/19] Moved all new additions and improvements to cv8.d and removed pdb.d along with the preview switch. --- changelog/dmd.newpdb.dd | 17 +- compiler/include/dmd/globals.h | 1 - compiler/src/build.d | 2 +- compiler/src/dmd/backend/backconfig.d | 9 +- compiler/src/dmd/backend/cdef.d | 1 - compiler/src/dmd/backend/cgcv.d | 6 +- compiler/src/dmd/backend/cv8.d | 322 +++++++-- compiler/src/dmd/backend/global.d | 7 + compiler/src/dmd/backend/mscoffobj.d | 15 +- compiler/src/dmd/backend/pdb.d | 929 ------------------------- compiler/src/dmd/cli.d | 2 - compiler/src/dmd/dmsc.d | 21 +- compiler/src/dmd/globals.d | 1 - compiler/test/compilable/previewhelp.d | 1 - compiler/test/runnable/testpdb.d | 117 +++- 15 files changed, 434 insertions(+), 1017 deletions(-) delete mode 100644 compiler/src/dmd/backend/pdb.d diff --git a/changelog/dmd.newpdb.dd b/changelog/dmd.newpdb.dd index d1f75308fa2c..accdd0e380f8 100644 --- a/changelog/dmd.newpdb.dd +++ b/changelog/dmd.newpdb.dd @@ -1,9 +1,18 @@ -Added a new Modern CodeView / PDB symbolic debug info emitter +Improved CodeView / PDB symbolic debug info on Windows -A new PDB (Program Database) emitter has been added to improve debug information generation on Windows. Creating modern CodeView/PDB symbolic debug info enables more robust and feature-rich debugging experiences with Microsoft development tools. +The Windows CodeView / PDB debug information emitter (used with `-g` when producing MS-COFF object files) now emits additional modern records expected by Microsoft and LLVM debugging tools. These improvements are enabled by default, so debugging with Visual Studio, WinDbg and LLVM-based tools works better out of the box. -You can activate the new PDB emitter by passing the `-preview=newpdb` flag to the compiler. +The following are now generated: + +$(LIST + * `S_OBJNAME` and `S_COMPILE3` compiland records reporting the configured language and the real compiler version string, + * `S_ENVBLOCK` and `S_BUILDINFO` (`LF_BUILDINFO`) records describing the build environment, + * `S_FRAMEPROC` records describing each function's stack frame, + * blake3 source-file checksums in the file-checksums subsection so debuggers can verify the source matches. +) + +No action is required; simply compile with `-g` as before: ```console -dmd -g -preview=newpdb myapp.d +dmd -g myapp.d ``` diff --git a/compiler/include/dmd/globals.h b/compiler/include/dmd/globals.h index bec8bbb7ced3..84142e5d6774 100644 --- a/compiler/include/dmd/globals.h +++ b/compiler/include/dmd/globals.h @@ -237,7 +237,6 @@ struct Param // https://issues.dlang.org/show_bug.cgi?id=14246 FeatureState systemVariables; // limit access to variables marked @system from @safe code d_bool useFastDFA; // Use fast data flow analysis engine - d_bool newpdb; // use the modern PDB/CodeView debug info emitter CHECKENABLE useInvariants; // generate class invariant checks CHECKENABLE useIn; // generate precondition checks diff --git a/compiler/src/build.d b/compiler/src/build.d index fee1fdb717b8..10be696c1c7a 100755 --- a/compiler/src/build.d +++ b/compiler/src/build.d @@ -1581,7 +1581,7 @@ auto sourceFiles() debugprint.d fp.d symbol.d dcode.d cgsched.d pdata.d util2.d backconfig.d rtlsym.d ptrntab.d dvarstats.d cgen.d barray.d cgcse.d elpicpie.d - dwarfeh.d dwarfdbginf.d cv8.d pdb.d + dwarfeh.d dwarfdbginf.d cv8.d machobj.d elfobj.d mscoffobj.d x86/nteh.d x86/cgreg.d x86/cg87.d x86/cgxmm.d x86/disasm86.d x86/cgcod.d x86/cod1.d x86/cod2.d x86/cod3.d x86/cod4.d x86/cod5.d diff --git a/compiler/src/dmd/backend/backconfig.d b/compiler/src/dmd/backend/backconfig.d index 030fd41ce951..aaa23d4b4173 100644 --- a/compiler/src/dmd/backend/backconfig.d +++ b/compiler/src/dmd/backend/backconfig.d @@ -17,7 +17,8 @@ import core.stdc.stdio; import dmd.backend.cdef; import dmd.backend.cc; import dmd.backend.code; -import dmd.backend.global : ErrorCallbackBackend, error, errorCallbackBackend; +import dmd.backend.global : ErrorCallbackBackend, error, errorCallbackBackend, + GetFileContentsCallback, getFileContentsCallback; import dmd.backend.go : go_flag, GlobalOptimizer; import dmd.backend.rtlsym : rtlsym_init; import dmd.backend.ty; @@ -86,13 +87,14 @@ void out_config_init( exefmt_t exefmt, bool generatedMain, // a main entrypoint is generated bool dataimports, - bool newpdb, // use the modern PDB/CodeView debug info emitter ref GlobalOptimizer go, - ErrorCallbackBackend errorCallback) + ErrorCallbackBackend errorCallback, + GetFileContentsCallback getFileContents) { //printf("out_config_init()\n"); errorCallbackBackend = errorCallback; + getFileContentsCallback = getFileContents; auto cfg = &config; cfg._version = _version; @@ -104,7 +106,6 @@ void out_config_init( } cfg.fulltypes = CVNONE; cfg.fpxmmregs = false; - cfg.newpdb = newpdb; if (!arm) cfg.inline8087 = 1; cfg.memmodel = 0; diff --git a/compiler/src/dmd/backend/cdef.d b/compiler/src/dmd/backend/cdef.d index 31bc59dd4bd3..7019b70d0975 100644 --- a/compiler/src/dmd/backend/cdef.d +++ b/compiler/src/dmd/backend/cdef.d @@ -529,7 +529,6 @@ struct Config bool useTypeInfo; // implement TypeInfo bool useExceptions; // implement exception handling ubyte dwarf; // DWARF version - bool newpdb; // use the modern PDB/CodeView debug info emitter // Configuration that is not saved in precompiled header diff --git a/compiler/src/dmd/backend/cgcv.d b/compiler/src/dmd/backend/cgcv.d index 4740210b1ced..164b6318252f 100644 --- a/compiler/src/dmd/backend/cgcv.d +++ b/compiler/src/dmd/backend/cgcv.d @@ -21,7 +21,6 @@ import dmd.backend.cc : Classsym, Symbol; import dmd.backend.type; public import dmd.backend.cv8; -import dmd.backend.pdb : pdb_outsym; public import dmd.backend.dwarfdbginf : dwarf_outsym; import core.stdc.stdio; @@ -2180,10 +2179,7 @@ void cv_outsym(Symbol* s) break; case CV8: - if (config.newpdb) - pdb_outsym(s); - else - cv8_outsym(s); + cv8_outsym(s); break; default: diff --git a/compiler/src/dmd/backend/cv8.d b/compiler/src/dmd/backend/cv8.d index b9744ccb1fc0..c5fb008d0e6a 100644 --- a/compiler/src/dmd/backend/cv8.d +++ b/compiler/src/dmd/backend/cv8.d @@ -33,7 +33,7 @@ import dmd.backend.el; import dmd.backend.mscoffobj; import dmd.backend.obj; import dmd.backend.oper; -import dmd.backend.pdb : pdb_udt; +import dmd.backend.global : getFileContentsCallback; import dmd.common.outbuffer; import dmd.backend.rtlsym; import dmd.backend.symbol : globsym; @@ -63,6 +63,69 @@ private bool symbol_iscomdat4(Symbol* s) // 'fatal error LNK1318: Unexpected PDB error; RPC (23) '(0x000006BA)' enum CV8_MAX_SYMBOL_LENGTH = 0xffd8; +// Modern CodeView symbol records not declared in cv4.d +enum +{ + S_OBJNAME_V3 = 0x1101, // object file name (same value as S_COMPILAND_V3) + S_COMPILE3 = 0x113C, // compile flags, language and compiler version + S_ENVBLOCK = 0x113D, // build environment block + S_BUILDINFO = 0x114C, // reference to an LF_BUILDINFO record + S_FRAMEPROC = 0x1012, // extra frame and procedure information + S_REGREL32 = 0x1111, // register-relative address + S_LTHREAD32 = 0x1112, // thread-local static data (local) + S_GTHREAD32 = 0x1113, // thread-local static data (global) + S_LPROC32_ID = 0x1146, // local procedure (type field is an LF_FUNC_ID) + S_GPROC32_ID = 0x1147, // global procedure (type field is an LF_FUNC_ID) +} + +// Type leaf records used by the ID stream +enum +{ + LF_FUNC_ID = 0x1601, // function id: function type + name + LF_STRING_ID = 0x1605, // interned string -> type index + LF_BUILDINFO = 0x1603, // cwd, tool, source, pdb, args +} + +// .debug$S subsection kinds +enum +{ + DEBUG_S_SYMBOLS = 0xF1, + DEBUG_S_LINES = 0xF2, + DEBUG_S_STRINGTABLE = 0xF3, + DEBUG_S_FILECHKSMS = 0xF4, +} + +// Source file checksum kinds +enum +{ + CHKSUM_NONE = 0, + CHKSUM_MD5 = 1, + CHKSUM_SHA1 = 2, + CHKSUM_SHA256 = 3, +} + +// S_FRAMEPROC flags +enum +{ + CV_FRAME_HASALLOCA = 1 << 0, // function uses alloca() + CV_FRAME_HASINLASM = 1 << 3, // function has inline asm + CV_FRAME_HASEH = 1 << 4, // function has exception handling + CV_FRAME_SECURITY = 1 << 8, // function has a stack security cookie + CV_FRAME_OPTSPEED = 1 << 20, // function was optimized for speed + CV_FRAME_LOCALBP_RBP = 2 << 14, // locals addressed relative to RBP/EBP + CV_FRAME_PARAMBP_RBP = 2 << 16, // parameters addressed relative to RBP/EBP +} + +// Mark a line-number entry as a statement (not an expression) +enum CV_LINE_STATEMENT = 0x80000000; + +// CodeView register encodings for base-pointer-relative records +enum +{ + CV_REG_EBP = 22, // x86 EBP + CV_AMD64_RBP = 334, // x64 RBP +} + // The "F1" section, which is the symbols private __gshared OutBuffer* F1_buf; @@ -226,26 +289,64 @@ void cv8_termfile(const(char)[] objfilename) uint value = 4; objmod.bytes(seg,0,(cast(void*)&value)[0 .. 4]); - /* Start with starting symbol in separate "F1" section + /* Start with starting symbols in a separate "F1" section */ auto buf = OutBuffer(1024); - size_t len = objfilename.length; - buf.write16(cast(int)(2 + 4 + len + 1)); - buf.write16(S_COMPILAND_V3); - buf.write32(0); - buf.write(objfilename.ptr, cast(uint)(len + 1)); - // write S_COMPILE record - buf.write16(2 + 1 + 1 + 2 + 1 + VERSION.length + 1); - buf.write16(S_COMPILE); - buf.writeByte(I64 ? 0xD0 : 6); // target machine AMD64 or x86 (Pentium II) - buf.writeByte(config.flags2 & CFG2gms ? 0 : 'D'); // language index (C/C++/D) - buf.write16(0x800 | (config.inline8087 ? 0 : (1<<3))); // 32-bit, float package - buf.writeByte(VERSION.length + 1); - buf.writeByte('Z'); - buf.write(VERSION.ptr, VERSION.length); + // S_OBJNAME record + { + size_t len = objfilename.length; + buf.write16(cast(int)(2 + 4 + len + 1)); + buf.write16(S_OBJNAME_V3); + buf.write32(0); // signature + buf.write(objfilename.ptr, cast(uint)(len + 1)); + } + + // S_COMPILE3 record: honor the configured language and report the real + // compiler version string (works for dmd/ldc/gdc) + { + const uint flags = config.flags2 & CFG2gms ? 0 : 'D'; // iLanguage in low byte + const(char)[] ver = config._version; + buf.write16(cast(int)(2 + 4 + 2 + 8 + 8 + ver.length + 1)); + buf.write16(S_COMPILE3); + buf.write32(flags); + buf.write16(I64 ? 0xD0 : 0x07); // machine: AMD64 / x86 + buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // front end version + buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // back end version + buf.write(ver.ptr, cast(uint)ver.length); + buf.writeByte(0); + } + + cv8_writesection(seg, DEBUG_S_SYMBOLS, &buf); + + // S_ENVBLOCK record: build environment (cwd), double-null terminated + { + auto ebuf = OutBuffer(128); + char[260] cwd = 0; + if (!getcwd(cwd.ptr, cwd.sizeof)) + cwd[0] = 0; + size_t cwdlen = strlen(cwd.ptr); + uint rec = cast(uint)(2 + 1 + 4 + cwdlen + 1 + 4 + 1 + 1); + ebuf.write16(cast(int)rec); + ebuf.write16(S_ENVBLOCK); + ebuf.writeByte(0); // flags + ebuf.write("cwd\0".ptr, 4); + ebuf.write(cwd.ptr, cast(uint)(cwdlen + 1)); + ebuf.write("cmd\0".ptr, 4); + ebuf.writeByte(0); // empty cmd value + ebuf.writeByte(0); // terminating double-null + cv8_writesection(seg, DEBUG_S_SYMBOLS, &ebuf); + } - cv8_writesection(seg, 0xF1, &buf); + // S_BUILDINFO record referencing an LF_BUILDINFO id record + { + auto bbuf = OutBuffer(64); + idx_t biidx = cv8_buildinfo(); + bbuf.write16(2 + 4); + bbuf.write16(S_BUILDINFO); + bbuf.write32(biidx); + cv8_writesection(seg, DEBUG_S_SYMBOLS, &bbuf); + } // Write out "F2" sections uint length = cast(uint)funcdata.length(); @@ -268,14 +369,14 @@ void cv8_termfile(const(char)[] objfilename) } uint offset = cast(uint)SegData[f2seg].SDoffset + 8; - cv8_writesection(f2seg, 0xF2, F2_buf); + cv8_writesection(f2seg, DEBUG_S_LINES, F2_buf); objmod.reftoident(f2seg, offset, fd.sfunc, 0, CF.seg | CF.off); if (f2seg != seg && fd.f1buf.length()) { // Write out "F1" section const uint f1offset = cast(uint)SegData[f2seg].SDoffset; - cv8_writesection(f2seg, 0xF1, fd.f1buf); + cv8_writesection(f2seg, DEBUG_S_SYMBOLS, fd.f1buf); // Fixups for "F1" section const uint fixupLength = cast(uint)fd.f1fixup.length(); @@ -290,17 +391,17 @@ void cv8_termfile(const(char)[] objfilename) // Write out "F3" section if (F3_buf.length() > 1) - cv8_writesection(seg, 0xF3, F3_buf); + cv8_writesection(seg, DEBUG_S_STRINGTABLE, F3_buf); // Write out "F4" section if (F4_buf.length() > 0) - cv8_writesection(seg, 0xF4, F4_buf); + cv8_writesection(seg, DEBUG_S_FILECHKSMS, F4_buf); if (F1_buf.length()) { // Write out "F1" section uint f1offset = cast(uint)SegData[seg].SDoffset; - cv8_writesection(seg, 0xF1, F1_buf); + cv8_writesection(seg, DEBUG_S_SYMBOLS, F1_buf); // Fixups for "F1" section length = cast(uint)F1fixup.length(); @@ -403,19 +504,22 @@ void cv8_func_term(Symbol* sfunc) const(char)* id = sfunc.prettyIdent ? sfunc.prettyIdent : prettyident(sfunc); + // ID-stream record tying the function's type to its name (S_GPROC32_ID references this) + idx_t funcid = cv8_func_id(id, typidx); + size_t len = strlen(id); if(len > CV8_MAX_SYMBOL_LENGTH) len = CV8_MAX_SYMBOL_LENGTH; /* * 2 length (not including these 2 bytes) - * 2 S_GPROC_V3 + * 2 S_GPROC32_ID / S_LPROC32_ID * 4 parent * 4 pend * 4 pnext * 4 size of function * 4 size of function prolog * 4 offset to function epilog - * 4 type index + * 4 LF_FUNC_ID (ID stream) * 6 seg:offset of function start * 1 flags * n 0 terminated name string @@ -423,14 +527,14 @@ void cv8_func_term(Symbol* sfunc) auto buf = currentfuncdata.f1buf; buf.reserve(cast(uint)(2 + 2 + 4 * 7 + 6 + 1 + len + 1)); buf.write16n(cast(int)(2 + 4 * 7 + 6 + 1 + len + 1)); - buf.write16n(sfunc.Sclass == SC.static_ ? S_LPROC_V3 : S_GPROC_V3); + buf.write16n(sfunc.Sclass == SC.static_ ? S_LPROC32_ID : S_GPROC32_ID); buf.write32(0); // parent buf.write32(0); // pend buf.write32(0); // pnext buf.write32(cast(uint)currentfuncdata.section_length); // size of function buf.write32(cast(uint)cgstate.startoffset); // size of prolog buf.write32(cast(uint)cgstate.retoffset); // offset to epilog - buf.write32(typidx); + buf.write32(funcid); // LF_FUNC_ID (ID stream), resolved to the function type F1_Fixups f1f; f1f.s = sfunc; @@ -444,6 +548,33 @@ void cv8_func_term(Symbol* sfunc) buf.writen(id, len); buf.writeByte(0); + // S_FRAMEPROC: describe this function's stack frame + { + // Locals and parameters are addressed relative to the frame pointer + // (RBP/EBP), matching the S_REGREL32 records emitted for them. + uint frameflags = CV_FRAME_LOCALBP_RBP | CV_FRAME_PARAMBP_RBP; + if (cgstate.Alloca.size) + frameflags |= CV_FRAME_HASALLOCA; + if (cgstate.anyiasm) + frameflags |= CV_FRAME_HASINLASM; + if (cgstate.usednteh) + frameflags |= CV_FRAME_HASEH; + if (config.flags2 & CFG2stomp) + frameflags |= CV_FRAME_SECURITY; + if (config.flags4 & CFG4speed) + frameflags |= CV_FRAME_OPTSPEED; + + buf.write16(2 + 4 + 4 + 4 + 4 + 4 + 2 + 4); + buf.write16(S_FRAMEPROC); + buf.write32(cast(uint)localsize); // cbFrame: total frame size + buf.write32(0); // cbPad + buf.write32(0); // offPad + buf.write32(0); // cbSaveRegs + buf.write32(0); // offExHdlr + buf.write16(0); // sectExHdlr + buf.write32(frameflags); // flags + } + struct cv8 { nothrow: @@ -539,7 +670,7 @@ void cv8_linnum(Srcpos srcpos, uint offset) lastoffset = offset; lastlinnum = srcpos.Slinnum; linepair.write32(offset); - linepair.write32(srcpos.Slinnum | 0x80000000); // mark as statement, not expression + linepair.write32(srcpos.Slinnum | CV_LINE_STATEMENT); // mark as statement, not expression currentfuncdata.linepairbytes += 8; @@ -616,33 +747,34 @@ L1: p = F4_buf.buf; uint u = 0; - while (u + 8 <= length) + while (u + 6 <= length) { - //printf("\t%x\n", *cast(uint*)(p + u)); if (off == *cast(uint*)(p + u)) - { - //printf("\tfound %x\n", u); return u; - } - u += 4; - ushort type = *cast(ushort*)(p + u); - u += 2; - if (type == 0x0110) - u += 16; // truncated blake3 hash - u += 2; + u += 4; // file name offset + ubyte cklen = *(p + u); // checksum size + u += 2; // skip size + kind bytes + u += cklen; // skip checksum bytes + u = (u + 3) & ~3; // realign to 4 } - // Not there. Add it. + // Not present; append a checksum computed over the source file's *content* + // (not over its name) so debuggers can verify the source matches. F4_buf.write32(off); - - /* Write 10 01 [blake3 hash] - * or - * 00 00 - */ - F4_buf.write16(0); - - // 2 bytes of pad - F4_buf.write16(0); + ubyte[32] hash = void; + if (cv8_filehash(filename, hash)) + { + F4_buf.writeByte(32); // checksum size + F4_buf.writeByte(CHKSUM_SHA256); // 32-byte checksum slot + F4_buf.write(hash.ptr, 32); + } + else + { + F4_buf.writeByte(0); // no checksum available + F4_buf.writeByte(CHKSUM_NONE); + } + while (F4_buf.length() & 3) + F4_buf.writeByte(0); //printf("\tadded %x\n", length); return length; @@ -720,10 +852,10 @@ static if (1) // Register relative addressing buf.reserve(cast(uint)(2 + 2 + 4 + 4 + 2 + len + 1)); buf.write16n(cast(uint)(2 + 4 + 4 + 2 + len + 1)); - buf.write16n(0x1111); + buf.write16n(S_REGREL32); buf.write32(cast(uint)(s.Soffset + base + cgstate.BPoff)); buf.write32(typidx); - buf.write16n(I64 ? 334 : 22); // relative to RBP/EBP + buf.write16n(I64 ? CV_AMD64_RBP : CV_REG_EBP); // relative to RBP/EBP cv8_writename(buf, id, len); buf.writeByte(0); } @@ -789,7 +921,7 @@ else * n 0 terminated name string */ if (s.ty() & mTYthread) // thread local storage - sr = (sr == S_GDATA_V3) ? 0x1113 : 0x1112; + sr = (sr == S_GDATA_V3) ? S_GTHREAD32 : S_LTHREAD32; buf.reserve(cast(uint)(2 + 2 + 4 + 6 + len + 1)); buf.write16n(cast(uint)(2 + 4 + 6 + len + 1)); @@ -798,7 +930,7 @@ else f1f.s = s; f1f.offset = cast(uint)buf.length(); - F1fixup.write(&f1f, f1f.sizeof); + currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); buf.write32(0); buf.write16n(0); @@ -822,11 +954,6 @@ else void cv8_udt(const(char)* id, idx_t typidx) { //printf("cv8_udt('%s', %x)\n", id, typidx); - if (config.newpdb) - { - pdb_udt(id, typidx); - return; - } auto buf = currentfuncdata.f1buf; size_t len = strlen(id); @@ -1204,4 +1331,85 @@ else return cv_debtyp(d); } +/* Compute a blake3 hash over the *content* of the named source file, so that + * debuggers can verify the source matches. The hash goes into the 32-byte + * SHA256 checksum slot of the file-checksums (F4) subsection. + * The content is fetched from the front-end FileManager cache (Module.src) + * rather than re-read from disk, which would add significant I/O to every build. + * Returns: true on success (hash filled in), false if the content is unavailable. + */ +private @trusted +bool cv8_filehash(const(char)* filename, ref ubyte[32] hash) +{ + if (!getFileContentsCallback) + return false; + + size_t length; + const(ubyte)* data = getFileContentsCallback(filename, length); + if (!data) + return false; + + import dmd.common.blake3; + hash = blake3(data[0 .. length]); + return true; +} + +/* LF_STRING_ID: an interned string returning a type index. */ +@trusted +idx_t cv8_string_id(const(char)* s) +{ + if (!s) s = ""; + debtyp_t* d = debtyp_alloc(2 + 4 + cv_stringbytes(s)); + TOWORD(d.data.ptr, LF_STRING_ID); + TOLONG(d.data.ptr + 2, 0); // substring list id + cv_namestring(d.data.ptr + 6, s); + return cv_debtyp(d); +} + +/* LF_BUILDINFO: cwd, build tool, source, pdb, args. */ +@trusted +idx_t cv8_buildinfo() +{ + char[260] cwd = 0; + if (!getcwd(cwd.ptr, cwd.sizeof)) + cwd[0] = 0; + + // build tool: identify the compiler and its version + char[64] tool = 0; + strcpy(tool.ptr, "dmd "); + const(char)[] ver = config._version; + size_t vn = ver.length; + if (vn > tool.sizeof - 5) // leave room for the "dmd " prefix and the null terminator + vn = tool.sizeof - 5; + memcpy(tool.ptr + 4, ver.ptr, vn); + tool[4 + vn] = 0; + + idx_t cwdId = cv8_string_id(cwd.ptr); + idx_t toolId = cv8_string_id(tool.ptr); + idx_t srcId = cv8_string_id(""); + idx_t pdbId = cv8_string_id(""); + idx_t argId = cv8_string_id(""); + debtyp_t* d = debtyp_alloc(2 + 2 + 5 * 4); + TOWORD(d.data.ptr, LF_BUILDINFO); + TOWORD(d.data.ptr + 2, 5); // count + TOLONG(d.data.ptr + 4, cwdId); + TOLONG(d.data.ptr + 8, toolId); + TOLONG(d.data.ptr + 12, srcId); + TOLONG(d.data.ptr + 16, pdbId); + TOLONG(d.data.ptr + 20, argId); + return cv_debtyp(d); +} + +/* LF_FUNC_ID: ties a function type to its name in the ID stream. */ +@trusted +idx_t cv8_func_id(const(char)* id, idx_t functype) +{ + debtyp_t* d = debtyp_alloc(2 + 4 + 4 + cv_stringbytes(id)); + TOWORD(d.data.ptr, LF_FUNC_ID); + TOLONG(d.data.ptr + 2, 0); // parent scope id + TOLONG(d.data.ptr + 6, functype); + cv_namestring(d.data.ptr + 10, id); + return cv_debtyp(d); +} + } diff --git a/compiler/src/dmd/backend/global.d b/compiler/src/dmd/backend/global.d index 9743eea960b6..4cdf0d337333 100644 --- a/compiler/src/dmd/backend/global.d +++ b/compiler/src/dmd/backend/global.d @@ -35,6 +35,13 @@ alias ErrorCallbackBackend = extern(C++) void function(const(char)* filename, ui package(dmd.backend) __gshared ErrorCallbackBackend errorCallbackBackend; +/// Callback to fetch cached source-file contents from the front-end FileManager +/// (Module.src), avoiding a re-read from disk. Returns a pointer to the bytes +/// and sets `length`; returns null if the content is unavailable. +alias GetFileContentsCallback = extern(C++) const(ubyte)* function(const(char)* filename, ref size_t length); + +package(dmd.backend) __gshared GetFileContentsCallback getFileContentsCallback; + /** * Backend error report function * Params: diff --git a/compiler/src/dmd/backend/mscoffobj.d b/compiler/src/dmd/backend/mscoffobj.d index e9fd0bb8559f..3d986169ec7b 100644 --- a/compiler/src/dmd/backend/mscoffobj.d +++ b/compiler/src/dmd/backend/mscoffobj.d @@ -32,7 +32,6 @@ import dmd.backend.symbol : symbol_generate, symbol_name, symbol_print, symbol_r import dmd.backend.obj; import dmd.backend.ty; import dmd.backend.type; -import dmd.backend.pdb; import dmd.backend.mscoff; @@ -356,7 +355,7 @@ Obj MsCoffObj_init(OutBuffer* objbuf, const(char)* filename, const(char)* csegna assert(SegData[UDATA].SDseg == UDATA); if (config.fulltypes) - config.newpdb ? pdb_initfile(filename) : cv8_initfile(filename); + cv8_initfile(filename); assert(objbuf.length() == 0); return obj; } @@ -375,7 +374,7 @@ void MsCoffObj_initfile(const(char)* filename, const(char)* csegname, const(char { //dbg_printf("MsCoffObj_initfile(filename = %s, modname = %s)\n",filename,modname); if (config.fulltypes) - config.newpdb ? pdb_initmodule(filename, modname) : cv8_initmodule(filename, modname); + cv8_initmodule(filename, modname); } /************************************ @@ -616,7 +615,7 @@ void MsCoffObj_termfile() //dbg_printf("MsCoffObj_termfile\n"); if (config.addlinenumbers) { - config.newpdb ? pdb_termmodule() : cv8_termmodule(); + cv8_termmodule(); } } @@ -636,7 +635,7 @@ void MsCoffObj_term(const(char)[] objfilename) if (config.addlinenumbers) { - config.newpdb ? pdb_termfile(objfilename) : cv8_termfile(objfilename); + cv8_termfile(objfilename); } // To allow tooling support for most output files @@ -1008,7 +1007,7 @@ void MsCoffObj_linnum(Srcpos srcpos, int seg, targ_size_t offset) if (srcpos.Slinnum == 0 || !srcpos.Sfilename) return; - config.newpdb ? pdb_linnum(srcpos, cast(uint)offset) : cv8_linnum(srcpos, cast(uint)offset); + cv8_linnum(srcpos, cast(uint)offset); } @@ -1846,7 +1845,7 @@ void MsCoffObj_func_start(Symbol* sfunc) sfunc.Soffset = Offset(cseg); if (config.fulltypes) - config.newpdb ? pdb_func_start(sfunc) : cv8_func_start(sfunc); + cv8_func_start(sfunc); } /******************************* @@ -1860,7 +1859,7 @@ void MsCoffObj_func_term(Symbol* sfunc) // sfunc.Sident.ptr, sfunc.Soffset,Offset(cseg),sfunc.Sxtrnnum); if (config.fulltypes) - config.newpdb ? pdb_func_term(sfunc) : cv8_func_term(sfunc); + cv8_func_term(sfunc); } /******************************** diff --git a/compiler/src/dmd/backend/pdb.d b/compiler/src/dmd/backend/pdb.d deleted file mode 100644 index 9b971a6ba1fa..000000000000 --- a/compiler/src/dmd/backend/pdb.d +++ /dev/null @@ -1,929 +0,0 @@ -/** - * Modern CodeView / PDB symbolic debug info generation. - * - * This module is a self-contained emitter for the `.debug$S` and `.debug$T` - * sections for Win64 MS-COFF objects. It is an alternative to cv8.d intended to - * eventually retire cv4.d/cv8.d. It targets the record set documented by LLVM - * (https://llvm.org/docs/PDB/) and Microsoft - * (https://github.com/microsoft/microsoft-pdb) and is enabled by the - * `-preview=newpdb` compiler flag. - * - * It does not modify the existing CV4/CV8 paths; the type-record pool helpers in - * cgcv.d are reused read-only. - * - * Compiler implementation of the - * $(LINK2 https://www.dlang.org, D programming language). - * - * Copyright: Copyright (C) 2026 by The D Language Foundation, All Rights Reserved - * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) - * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/backend/pdb.d, backend/pdb.d) - */ - -module dmd.backend.pdb; - -import core.stdc.stdio; -import core.stdc.stdlib; -import core.stdc.string; -extern (C) nothrow char* getcwd(char*, size_t); - -import dmd.backend.cc; -import dmd.backend.cdef; -import dmd.backend.cgcv; -import dmd.backend.code; -import dmd.backend.x86.code_x86; -import dmd.backend.cv4; -import dmd.backend.mem; -import dmd.backend.mscoffobj; -import dmd.backend.obj; -import dmd.backend.oper; -import dmd.common.outbuffer; -import dmd.backend.rtlsym; -import dmd.backend.symbol : globsym; -import dmd.backend.ty; -import dmd.backend.type; -import dmd.backend.dvarstats; - -nothrow: -@safe: - -/* ======================================================================== */ -/* CodeView constants not present in cv4.d, kept local to avoid touching */ -/* the legacy CV4/CV8 implementations. */ -/* ======================================================================== */ - -// Symbol records (DEBUG_S_SYMBOLS subsection content) -enum -{ - S_END_PDB = 0x0006, - S_OBJNAME_V3 = 0x1101, - S_COMPILE3 = 0x113C, - S_ENVBLOCK = 0x113D, - S_BUILDINFO = 0x114C, - S_FRAMEPROC = 0x1012, - S_GPROC32_ID = 0x1147, - S_LPROC32_ID = 0x1146, - S_PROC_ID_END = 0x114F, - S_GPROC32 = 0x1110, - S_LPROC32 = 0x110F, - S_REGREL32 = 0x1111, - S_BPREL32_V3 = 0x110B, - S_REGISTER_V3 = 0x1106, - S_LDATA32 = 0x110C, - S_GDATA32 = 0x110D, - S_LTHREAD32 = 0x1112, - S_GTHREAD32 = 0x1113, - S_PUB32 = 0x110E, - S_CONSTANT_V3 = 0x1107, - S_UDT_V3_ = 0x1108, - S_LABEL32_V3 = 0x1105, - S_THUNK32 = 0x1102, - S_LOCAL = 0x113E, - S_BLOCK32 = 0x1103, - S_RETURN = 0x000D, -} - -// Type leaf records used by the ID stream / source lines -enum -{ - LF_FUNC_ID = 0x1601, - LF_STRING_ID = 0x1605, - LF_BUILDINFO = 0x1603, - LF_UDT_SRC_LINE = 0x1606, - LF_SUBSTR_LIST = 0x1604, - LF_ALIAS = 0x150A, -} - -// .debug$S subsection kinds -enum -{ - DEBUG_S_SYMBOLS = 0xF1, - DEBUG_S_LINES = 0xF2, - DEBUG_S_STRINGTABLE = 0xF3, - DEBUG_S_FILECHKSMS = 0xF4, -} - -// File checksum kinds -enum -{ - CHKSUM_NONE = 0, - CHKSUM_MD5 = 1, - CHKSUM_SHA1 = 2, - CHKSUM_SHA256 = 3, -} - -// keep symbol records well under linker limits -enum PDB_MAX_SYMBOL_LENGTH = 0xffd8; - -// CV_PUBSYMFLAGS -enum PUB_FUNCTION = 0x00000002; - -/* ======================================================================== */ -/* State */ -/* ======================================================================== */ - -private __gshared OutBuffer* F1_buf; // symbols -private __gshared OutBuffer* F2_buf; // line numbers -private __gshared OutBuffer* F3_buf; // string table of source file names -private __gshared OutBuffer* F4_buf; // file checksums - -struct F1_Fixups -{ - Symbol* s; - uint offset; - uint value; -} - -private __gshared OutBuffer* F1fixup; - -struct FuncData -{ - Symbol* sfunc; - uint section_length; - const(char)* srcfilename; - uint srcfileoff; - uint linepairstart; - uint linepairbytes; - uint linepairsegment; - OutBuffer* f1buf; - OutBuffer* f1fixup; -} - -__gshared FuncData currentfuncdata; - -private __gshared OutBuffer* funcdata; // array of FuncData -private __gshared OutBuffer* linepair; // offset/line pairs - -// Determine if this Symbol lives in a COMDAT -@trusted -private bool symbol_iscomdat_pdb(Symbol* s) -{ - return s.Sclass == SC.comdat || - config.flags2 & CFG2comdat && s.Sclass == SC.inline || - config.flags4 & CFG4allcomdat && s.Sclass == SC.global; -} - -/* ======================================================================== */ -/* Name encoding */ -/* ======================================================================== */ - -private @trusted -void pdb_writename(OutBuffer* buf, const(char)* name, size_t len) -{ - if (!(config.flags2 & CFG2gms)) - { - buf.writen(name, len); - return; - } - - const(char)* start = name; - const(char)* cur = strchr(start, '.'); - const(char)* end = start + len; - while (cur != null) - { - if (cur >= end) - { - buf.writen(start, end - start); - return; - } - buf.writen(start, cur - start); - buf.writeByte('@'); - start = cur + 1; - if (start >= end) - return; - cur = strchr(start, '.'); - } - buf.writen(start, end - start); -} - -/* ======================================================================== */ -/* File lifecycle */ -/* ======================================================================== */ - -@trusted -void pdb_initfile(const(char)* filename) -{ - void initBuf(ref OutBuffer* ptr) - { - if (!ptr) - { - ptr = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); - ptr.reserve(1024); - } - ptr.reset(); - } - - initBuf(F1_buf); - initBuf(F1fixup); - initBuf(F2_buf); - - initBuf(F3_buf); - F3_buf.writeByte(0); // first "filename" - - initBuf(F4_buf); - initBuf(funcdata); - initBuf(linepair); - - memset(¤tfuncdata, 0, currentfuncdata.sizeof); - currentfuncdata.f1buf = F1_buf; - currentfuncdata.f1fixup = F1fixup; - - cv_init(); -} - -void pdb_initmodule(const(char)* filename, const(char)* modulename) -{ -} - -@trusted -void pdb_termmodule() -{ - assert(config.objfmt == OBJ_MSCOFF); -} - -@trusted -void pdb_termfile(const(char)[] objfilename) -{ - int seg = MsCoffObj_seg_debugS(); - - uint value = 4; // CV signature C13 - objmod.bytes(seg, 0, (cast(void*)&value)[0 .. 4]); - - auto buf = OutBuffer(1024); - - // S_OBJNAME - { - size_t len = objfilename.length; - buf.write16(cast(int)(2 + 4 + len + 1)); - buf.write16(S_OBJNAME_V3); - buf.write32(0); // signature - buf.write(objfilename.ptr, cast(uint)(len + 1)); - } - - // S_COMPILE3 - { - // Honor the configured language/compiler instead of hardcoding it: - // emit the D language index unless the debug info was requested for - // non-DMD (Microsoft) debuggers (-gc sets CFG2gms), and report the - // actual compiler version string (dmd/ldc/gdc) via config._version. - const uint flags = config.flags2 & CFG2gms ? 0 : 'D'; // iLanguage in low byte - const(char)[] ver = config._version; - buf.write16(cast(int)(2 + 4 + 2 + 8 + 8 + ver.length + 1)); - buf.write16(S_COMPILE3); - buf.write32(flags); - buf.write16(I64 ? 0xD0 : 0x07); // machine: AMD64 / x86 - buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // FE ver - buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // BE ver - buf.write(ver.ptr, cast(uint)ver.length); - buf.writeByte(0); - } - - pdb_writesection(seg, DEBUG_S_SYMBOLS, &buf); - - // S_ENVBLOCK: build environment (cwd, tool), double-null terminated - { - auto ebuf = OutBuffer(128); - char[260] cwd = 0; - if (!getcwd(cwd.ptr, cwd.sizeof)) - cwd[0] = 0; - size_t cwdlen = strlen(cwd.ptr); - uint rec = cast(uint)(2 + 1 + 4 + cwdlen + 1 + 4 + 1 + 1); - ebuf.write16(cast(int)rec); - ebuf.write16(S_ENVBLOCK); - ebuf.writeByte(0); // flags - ebuf.write("cwd\0".ptr, 4); - ebuf.write(cwd.ptr, cast(uint)(cwdlen + 1)); - ebuf.write("cmd\0".ptr, 4); - ebuf.writeByte(0); // empty cmd value - ebuf.writeByte(0); // terminating double-null - pdb_writesection(seg, DEBUG_S_SYMBOLS, &ebuf); - } - - // S_BUILDINFO referencing an LF_BUILDINFO id record - { - auto bbuf = OutBuffer(64); - idx_t biidx = pdb_buildinfo(); - bbuf.write16(2 + 4); - bbuf.write16(S_BUILDINFO); - bbuf.write32(biidx); - pdb_writesection(seg, DEBUG_S_SYMBOLS, &bbuf); - } - - uint length = cast(uint)funcdata.length(); - ubyte* p = funcdata.buf; - for (uint u = 0; u < length; u += FuncData.sizeof) - { - FuncData* fd = cast(FuncData*)(p + u); - - F2_buf.reset(); - F2_buf.write32(cast(uint)fd.sfunc.Soffset); - F2_buf.write32(0); - F2_buf.write32(fd.section_length); - F2_buf.write(linepair.buf + fd.linepairstart, fd.linepairbytes); - - int f2seg = seg; - if (symbol_iscomdat_pdb(fd.sfunc)) - { - f2seg = MsCoffObj_seg_debugS_comdat(fd.sfunc); - objmod.bytes(f2seg, 0, (cast(void*)&value)[0 .. 4]); - } - - uint offset = cast(uint)SegData[f2seg].SDoffset + 8; - pdb_writesection(f2seg, DEBUG_S_LINES, F2_buf); - objmod.reftoident(f2seg, offset, fd.sfunc, 0, CF.seg | CF.off); - - if (f2seg != seg && fd.f1buf.length()) - { - const uint f1offset = cast(uint)SegData[f2seg].SDoffset; - pdb_writesection(f2seg, DEBUG_S_SYMBOLS, fd.f1buf); - - const uint fixupLength = cast(uint)fd.f1fixup.length(); - ubyte* pfixup = fd.f1fixup.buf; - for (uint v = 0; v < fixupLength; v += F1_Fixups.sizeof) - { - F1_Fixups* f = cast(F1_Fixups*)(pfixup + v); - objmod.reftoident(f2seg, f1offset + 8 + f.offset, f.s, f.value, CF.seg | CF.off); - } - } - } - - if (F3_buf.length() > 1) - pdb_writesection(seg, DEBUG_S_STRINGTABLE, F3_buf); - - if (F4_buf.length() > 0) - pdb_writesection(seg, DEBUG_S_FILECHKSMS, F4_buf); - - if (F1_buf.length()) - { - uint f1offset = cast(uint)SegData[seg].SDoffset; - pdb_writesection(seg, DEBUG_S_SYMBOLS, F1_buf); - - length = cast(uint)F1fixup.length(); - p = F1fixup.buf; - for (uint u = 0; u < length; u += F1_Fixups.sizeof) - { - F1_Fixups* f = cast(F1_Fixups*)(p + u); - objmod.reftoident(seg, f1offset + 8 + f.offset, f.s, f.value, CF.seg | CF.off); - } - } - - cv_term(); // .debug$T -} - -/* ======================================================================== */ -/* Functions */ -/* ======================================================================== */ - -@trusted -void pdb_func_start(Symbol* sfunc) -{ - currentfuncdata.sfunc = sfunc; - currentfuncdata.section_length = 0; - currentfuncdata.srcfilename = null; - currentfuncdata.linepairstart += currentfuncdata.linepairbytes; - currentfuncdata.linepairbytes = 0; - currentfuncdata.f1buf = F1_buf; - currentfuncdata.f1fixup = F1fixup; - if (symbol_iscomdat_pdb(sfunc)) - { - currentfuncdata.f1buf = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); - currentfuncdata.f1buf.reserve(128); - currentfuncdata.f1fixup = cast(OutBuffer*)mem_calloc(OutBuffer.sizeof); - currentfuncdata.f1fixup.reserve(128); - } - varStats_startFunction(); -} - -@trusted -void pdb_func_term(Symbol* sfunc) -{ - assert(currentfuncdata.sfunc == sfunc); - currentfuncdata.section_length = cast(uint)sfunc.Ssize; - funcdata.write(¤tfuncdata, currentfuncdata.sizeof); - - assert(tyfunc(sfunc.ty())); - idx_t typidx; - func_t* fn = sfunc.Sfunc; - if (fn.Fclass) - { - // Member functions get a dedicated LF_MFUNCTION_V2 type record that - // records the enclosing class and the `this` pointer type. This info - // isn't available inside cv4_typidx, so replicate cv8_func_term here. - uint nparam; - const ubyte call = cv4_callconv(sfunc.Stype); - const idx_t paramidx = cv4_arglist(sfunc.Stype, &nparam); - const uint next = cv4_typidx(sfunc.Stype.Tnext); - type* classtype = cast(type*)fn.Fclass; - const uint classidx = cv4_typidx(classtype); - type* tp = type_allocn(TYnptr, classtype); - const uint thisidx = cv4_typidx(tp); - debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4 + 1 + 1 + 2 + 4 + 4); - TOWORD(d.data.ptr, LF_MFUNCTION_V2); - TOLONG(d.data.ptr + 2, next); // return type - TOLONG(d.data.ptr + 6, classidx); // class type - TOLONG(d.data.ptr + 10, thisidx); // this type - d.data.ptr[14] = call; - d.data.ptr[15] = 0; // reserved - TOWORD(d.data.ptr + 16, nparam); - TOLONG(d.data.ptr + 18, paramidx); - TOLONG(d.data.ptr + 22, 0); // this adjust - typidx = cv_debtyp(d); - } - else - typidx = cv_typidx(sfunc.Stype); - - const(char)* id = sfunc.prettyIdent ? sfunc.prettyIdent : prettyident(sfunc); - size_t len = strlen(id); - if (len > PDB_MAX_SYMBOL_LENGTH) - len = PDB_MAX_SYMBOL_LENGTH; - - auto buf = currentfuncdata.f1buf; - buf.reserve(cast(uint)(2 + 2 + 4 * 7 + 6 + 1 + len + 1)); - buf.write16n(cast(int)(2 + 4 * 7 + 6 + 1 + len + 1)); - buf.write16n(sfunc.Sclass == SC.static_ ? S_LPROC32 : S_GPROC32); - buf.write32(0); // parent - buf.write32(0); // pend - buf.write32(0); // pnext - buf.write32(cast(uint)currentfuncdata.section_length); - buf.write32(cast(uint)cgstate.startoffset); // prolog size - buf.write32(cast(uint)cgstate.retoffset); // epilog offset - buf.write32(typidx); - - F1_Fixups f1f; - f1f.s = sfunc; - f1f.offset = cast(uint)buf.length(); - f1f.value = 0; - currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); - buf.write32(0); - buf.write16n(0); - buf.writeByte(0); // flags - buf.writen(id, len); - buf.writeByte(0); - - // S_FRAMEPROC: describe this function's stack frame - { - // Locals and parameters are addressed relative to the frame pointer - // (RBP/EBP), matching the S_REGREL32 records emitted for them; the - // CodeView "FramePtr" register encoding for that is 2. - uint frameflags = (2 << 14) | (2 << 16); // encoded local/param base ptr - if (cgstate.anyiasm) - frameflags |= 1 << 3; // fHasInlAsm - if (cgstate.usednteh) - frameflags |= 1 << 4; // fHasEH - if (config.flags2 & CFG2stomp) - frameflags |= 1 << 8; // fSecurityChecks - if (config.flags4 & CFG4speed) - frameflags |= 1 << 20; // fOptSpeed - - buf.write16(2 + 4 + 4 + 4 + 4 + 4 + 2 + 4); - buf.write16(S_FRAMEPROC); - buf.write32(cast(uint)localsize); // cbFrame: total frame size - buf.write32(0); // cbPad - buf.write32(0); // offPad - buf.write32(0); // cbSaveRegs - buf.write32(0); // offExHdlr - buf.write16(0); // sectExHdlr - buf.write32(frameflags); // flags - } - - struct pdbblk - { - nothrow: - struct block_v3_data - { - ushort len; - ushort id; - uint pParent; - uint pEnd; - uint length; - uint offset; - ushort seg; - ubyte[1] name; - } - static void endArgs() - { - auto b = currentfuncdata.f1buf; - b.write16(2); - b.write16(0x000A); // S_ENDARG - } - static void beginBlock(int offset, int length) - { - auto b = currentfuncdata.f1buf; - uint soffset = cast(uint)b.length(); - block_v3_data block32 = { block_v3_data.sizeof - 2, S_BLOCK32, 0, 0, length, offset, 0, [ 0 ] }; - b.write(&block32, block32.sizeof); - size_t offOffset = cast(char*)&block32.offset - cast(char*)&block32; - F1_Fixups f; - f.s = currentfuncdata.sfunc; - f.offset = cast(uint)(soffset + offOffset); - f.value = offset; - currentfuncdata.f1fixup.write(&f, f.sizeof); - } - static void endBlock() - { - auto b = currentfuncdata.f1buf; - b.write16(2); - b.write16(S_END_PDB); - } - } - varStats_writeSymbolTable(sfunc, globsym, &pdb_outsym, &pdbblk.endArgs, &pdbblk.beginBlock, &pdbblk.endBlock); - - // No S_RETURN record: its register list uses single-byte CodeView register - // codes, which cannot encode x64 return registers (e.g. CV_AMD64_RAX = 328). - // Both MSVC and cv8.d omit it, so a real record can't be produced here. - - buf.write16(2); - buf.write16(S_END_PDB); - - currentfuncdata.f1buf = F1_buf; - currentfuncdata.f1fixup = F1fixup; -} - -/* ======================================================================== */ -/* Line numbers */ -/* ======================================================================== */ - -@trusted -void pdb_linnum(Srcpos srcpos, uint offset) -{ - const sfilename = srcpos.Sfilename; - if (!sfilename) - return; - - varStats_recordLineOffset(srcpos, offset); - - __gshared uint lastoffset; - __gshared uint lastlinnum; - - if (!currentfuncdata.srcfilename || - (currentfuncdata.srcfilename != sfilename && strcmp(currentfuncdata.srcfilename, sfilename))) - { - currentfuncdata.srcfilename = sfilename; - uint srcfileoff = pdb_addfile(sfilename); - currentfuncdata.linepairsegment = currentfuncdata.linepairstart + currentfuncdata.linepairbytes; - linepair.write32(srcfileoff); - linepair.write32(0); - linepair.write32(12); - currentfuncdata.linepairbytes += 12; - } - else if (offset <= lastoffset || srcpos.Slinnum == lastlinnum) - return; - - lastoffset = offset; - lastlinnum = srcpos.Slinnum; - linepair.write32(offset); - linepair.write32(srcpos.Slinnum | 0x80000000); - currentfuncdata.linepairbytes += 8; - - auto segmentbytes = currentfuncdata.linepairstart + currentfuncdata.linepairbytes - currentfuncdata.linepairsegment; - auto segmentheader = cast(uint*)(linepair.buf + currentfuncdata.linepairsegment); - segmentheader[1] = (segmentbytes - 12) / 8; - segmentheader[2] = segmentbytes; -} - -/* ======================================================================== */ -/* Source files (F3 names + F4 checksums via blake3) */ -/* ======================================================================== */ - -@trusted -uint pdb_addfile(const(char)* filename) -{ - uint length = cast(uint)F3_buf.length(); - ubyte* p = F3_buf.buf; - size_t len = strlen(filename); - - __gshared char[260] cwd = 0; - __gshared uint cwdlen; - bool abs = (*filename == '\\') || (*filename == '/') || - (*filename && filename[1] == ':'); - - if (!abs && cwd[0] == 0) - { - if (getcwd(cwd.ptr, cwd.sizeof)) - { - cwdlen = cast(uint)strlen(cwd.ptr); - if (cwd[cwdlen - 1] != '\\' && cwd[cwdlen - 1] != '/') - cwd[cwdlen++] = '\\'; - } - } - uint off = 1; - while (off + len < length) - { - if (!abs) - { - if (memcmp(p + off, cwd.ptr, cwdlen) == 0 && - memcmp(p + off + cwdlen, filename, len + 1) == 0) - goto L1; - } - else if (memcmp(p + off, filename, len + 1) == 0) - goto L1; - off += strlen(cast(const(char)*)(p + off)) + 1; - } - off = length; - if (!abs) - F3_buf.write(cwd.ptr, cwdlen); - F3_buf.write(filename, cast(uint)(len + 1)); - -L1: - length = cast(uint)F4_buf.length(); - p = F4_buf.buf; - uint u = 0; - while (u + 6 <= length) - { - if (off == *cast(uint*)(p + u)) - return u; - u += 4; // file name offset - ubyte cklen = *(p + u); // checksum size - u += 2; // skip size + kind bytes - u += cklen; // skip checksum bytes - u = (u + 3) & ~3; // realign to 4 - } - - // Not present; append a checksum computed over the source file's *content* - // (not over its name) so debuggers can verify the source matches. - F4_buf.write32(off); - ubyte[32] hash = void; - if (pdb_filehash(filename, hash)) - { - F4_buf.writeByte(32); // checksum size - F4_buf.writeByte(CHKSUM_SHA256); // 32-byte checksum slot - F4_buf.write(hash.ptr, 32); - } - else - { - F4_buf.writeByte(0); // no checksum available - F4_buf.writeByte(CHKSUM_NONE); - } - while (F4_buf.length() & 3) - F4_buf.writeByte(0); - return length; -} - -/* Compute a blake3 hash over the *content* of the named source file. - * Returns: true on success (hash filled in), false if the file can't be read. - */ -private @trusted -bool pdb_filehash(const(char)* filename, ref ubyte[32] hash) -{ - FILE* fp = fopen(filename, "rb"); - if (!fp) - return false; - scope(exit) fclose(fp); - - if (fseek(fp, 0, SEEK_END) != 0) - return false; - const long size = ftell(fp); - if (size < 0 || fseek(fp, 0, SEEK_SET) != 0) - return false; - - ubyte* data = cast(ubyte*)malloc(size ? cast(size_t)size : 1); - if (!data) - return false; - scope(exit) free(data); - - const size_t nread = fread(data, 1, cast(size_t)size, fp); - - import dmd.common.blake3; - hash = blake3(data[0 .. nread]); - return true; -} - -private @trusted -void pdb_writesection(int seg, uint type, OutBuffer* buf) -{ - uint off = cast(uint)SegData[seg].SDoffset; - objmod.bytes(seg, off, (cast(void*)&type)[0 .. 4]); - uint length = cast(uint)buf.length(); - objmod.bytes(seg, off + 4, (cast(void*)&length)[0 .. 4]); - objmod.bytes(seg, off + 8, buf.buf[0 .. length]); - uint pad = ((length + 3) & ~3) - length; - objmod.lidata(seg, off + 8 + length, pad); -} - -/* ======================================================================== */ -/* Symbols */ -/* ======================================================================== */ - -@trusted -void pdb_outsym(Symbol* s) -{ - if (s.Sflags & SFLnodebug) - return; - - idx_t typidx = cv_typidx(s.Stype); - const(char)* id = s.prettyIdent ? s.prettyIdent : prettyident(s); - size_t len = strlen(id); - if (len > PDB_MAX_SYMBOL_LENGTH) - len = PDB_MAX_SYMBOL_LENGTH; - - F1_Fixups f1f; - f1f.value = 0; - auto buf = currentfuncdata.f1buf; - - uint sr; - uint base; - switch (s.Sclass) - { - case SC.parameter: - case SC.regpar: - case SC.shadowreg: - if (s.Sfl == FL.reg) - { - s.Sfl = FL.para; - pdb_outsym(s); - s.Sfl = FL.reg; - goto case_register; - } - base = cast(uint)(cgstate.Para.size - cgstate.BPoff); - goto L1; - - case SC.auto_: - if (s.Sfl == FL.reg) - goto case_register; - case_auto: - base = cast(uint)cgstate.Auto.size; - L1: - if (s.Sscope) - break; - buf.reserve(cast(uint)(2 + 2 + 4 + 4 + 2 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + 4 + 2 + len + 1)); - buf.write16n(S_REGREL32); - buf.write32(cast(uint)(s.Soffset + base + cgstate.BPoff)); - buf.write32(typidx); - buf.write16n(I64 ? 334 : 22); - pdb_writename(buf, id, len); - buf.writeByte(0); - break; - - case SC.bprel: - base = -cgstate.BPoff; - goto L1; - - case SC.fastpar: - if (s.Sfl != FL.reg) - { - base = cast(uint)cgstate.Fast.size; - goto L1; - } - goto L2; - - case SC.register: - if (s.Sfl != FL.reg) - goto case_auto; - goto case; - - case SC.pseudo: - case_register: - L2: - buf.reserve(cast(uint)(2 + 2 + 4 + 2 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + 2 + len + 1)); - buf.write16n(S_REGISTER_V3); - buf.write32(typidx); - buf.write16n(pdb_regnum(s)); - pdb_writename(buf, id, len); - buf.writeByte(0); - break; - - case SC.extern_: - break; - - case SC.static_: - case SC.locstat: - sr = S_LDATA32; - goto Ldata; - - case SC.global: - case SC.comdat: - case SC.comdef: - sr = S_GDATA32; - Ldata: - if (s.ty() & mTYthread) // full TLS support - sr = (sr == S_GDATA32) ? S_GTHREAD32 : S_LTHREAD32; - - buf.reserve(cast(uint)(2 + 2 + 4 + 6 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + 6 + len + 1)); - buf.write16n(sr); - buf.write32(typidx); - f1f.s = s; - f1f.offset = cast(uint)buf.length(); - currentfuncdata.f1fixup.write(&f1f, f1f.sizeof); - buf.write32(0); - buf.write16n(0); - pdb_writename(buf, id, len); - buf.writeByte(0); - break; - - default: - break; - } -} - -/* Put out a name for a user defined type. */ -@trusted -void pdb_udt(const(char)* id, idx_t typidx) -{ - auto buf = currentfuncdata.f1buf; - size_t len = strlen(id); - if (len > PDB_MAX_SYMBOL_LENGTH) - len = PDB_MAX_SYMBOL_LENGTH; - buf.reserve(cast(uint)(2 + 2 + 4 + len + 1)); - buf.write16n(cast(uint)(2 + 4 + len + 1)); - buf.write16n(S_UDT_V3_); - buf.write32(typidx); - pdb_writename(buf, id, len); - buf.writeByte(0); -} - -/* ======================================================================== */ -/* ID-stream type records (reuse the shared cgcv type pool, read-only) */ -/* ======================================================================== */ - -/* LF_STRING_ID: an interned string returning a type index. */ -@trusted -idx_t pdb_string_id(const(char)* s) -{ - if (!s) s = ""; - debtyp_t* d = debtyp_alloc(2 + 4 + cv_stringbytes(s)); - TOWORD(d.data.ptr, LF_STRING_ID); - TOLONG(d.data.ptr + 2, 0); // substring list id - cv_namestring(d.data.ptr + 6, s); - return cv_debtyp(d); -} - -/* LF_BUILDINFO: cwd, build tool, source, pdb, args. */ -@trusted -idx_t pdb_buildinfo() -{ - char[260] cwd = 0; - if (!getcwd(cwd.ptr, cwd.sizeof)) - cwd[0] = 0; - idx_t cwdId = pdb_string_id(cwd.ptr); - idx_t toolId = pdb_string_id("dmd"); - idx_t srcId = pdb_string_id(""); - idx_t pdbId = pdb_string_id(""); - idx_t argId = pdb_string_id(""); - debtyp_t* d = debtyp_alloc(2 + 2 + 5 * 4); - TOWORD(d.data.ptr, LF_BUILDINFO); - TOWORD(d.data.ptr + 2, 5); // count - TOLONG(d.data.ptr + 4, cwdId); - TOLONG(d.data.ptr + 8, toolId); - TOLONG(d.data.ptr + 12, srcId); - TOLONG(d.data.ptr + 16, pdbId); - TOLONG(d.data.ptr + 20, argId); - return cv_debtyp(d); -} - -/* LF_FUNC_ID: ties a function type to its name for the ID stream. */ -@trusted -idx_t pdb_func_id(const(char)* id, idx_t functype) -{ - debtyp_t* d = debtyp_alloc(2 + 4 + 4 + cv_stringbytes(id)); - TOWORD(d.data.ptr, LF_FUNC_ID); - TOLONG(d.data.ptr + 2, 0); // parent scope id - TOLONG(d.data.ptr + 6, functype); - cv_namestring(d.data.ptr + 10, id); - return cv_debtyp(d); -} - -/* LF_UDT_SRC_LINE: source location of a user-defined type. */ -@trusted -idx_t pdb_udt_src_line(idx_t typidx, idx_t srcId, uint line) -{ - debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4); - TOWORD(d.data.ptr, LF_UDT_SRC_LINE); - TOLONG(d.data.ptr + 2, typidx); - TOLONG(d.data.ptr + 6, srcId); - TOLONG(d.data.ptr + 10, line); - return cv_debtyp(d); -} - -/* Codeview register number for symbol s. */ -int pdb_regnum(Symbol* s) -{ - int reg = s.Sreglsw; - assert(s.Sfl == FL.reg); - if ((1 << reg) & XMMREGS) - return reg - XMM0 + 154; - switch (type_size(s.Stype)) - { - case 1: - if (reg < 4) reg += 1; - else if (reg < 8) reg += 324 - 4; - else reg += 344 - 4; - break; - case 2: - if (reg < 8) reg += 9; - else reg += 352 - 8; - break; - case 4: - if (reg < 8) reg += 17; - else reg += 360 - 8; - break; - case 8: - reg += 328; - break; - default: - reg = 0; - break; - } - return reg; -} diff --git a/compiler/src/dmd/cli.d b/compiler/src/dmd/cli.d index 52e9b64ba2a9..f19b3d227776 100644 --- a/compiler/src/dmd/cli.d +++ b/compiler/src/dmd/cli.d @@ -1120,8 +1120,6 @@ dmd -cov -unittest myprog.d "https://dlang.org/spec/attribute.html#system-variables"), Feature("fastdfa", "useFastDFA", "Fast dataflow analysis engine, experimental"), - Feature("newpdb", "newpdb", - "use the modern PDB/CodeView debug info emitter"), ]; } diff --git a/compiler/src/dmd/dmsc.d b/compiler/src/dmd/dmsc.d index 143ce608f474..ffa558006eaa 100644 --- a/compiler/src/dmd/dmsc.d +++ b/compiler/src/dmd/dmsc.d @@ -29,10 +29,27 @@ import dmd.backend.backconfig; import dmd.backend.go; import dmd.backend.cc; import dmd.backend.cdef; -import dmd.backend.global : ErrorCallbackBackend; +import dmd.backend.global : ErrorCallbackBackend, GetFileContentsCallback; import dmd.backend.ty; import dmd.backend.type; +import dmd.file_manager : FileManager; + +/// Callback for the backend to fetch cached source-file contents from the +/// front-end FileManager (Module.src), so hashing source files for debug info +/// reuses the in-memory cache instead of re-reading them from disk. +extern(C++) const(ubyte)* getFileContentsBackend(const(char)* filename, ref size_t length) +{ + length = 0; + if (!global.fileManager) + return null; + const(ubyte)[] data = global.fileManager.getFileContents(FileName(filename[0 .. strlen(filename)])); + if (!data) + return null; + length = data.length; + return data.ptr; +} + /************************************** * Initialize backend config variables. * Params: @@ -95,10 +112,10 @@ void backend_init(const ref Param params, const ref DMDparams driverParams, cons exfmt, params.addMain, driverParams.symImport != SymImport.none, - params.newpdb, go, // FIXME: casting to @nogc because errors.d is not marked @nogc yet cast(ErrorCallbackBackend) &errorBackend, + cast(GetFileContentsCallback) &getFileContentsBackend, ); out_config_debug( diff --git a/compiler/src/dmd/globals.d b/compiler/src/dmd/globals.d index b94f1a35cea9..0c4f9eb5417c 100644 --- a/compiler/src/dmd/globals.d +++ b/compiler/src/dmd/globals.d @@ -216,7 +216,6 @@ extern (C++) struct Param // https://issues.dlang.org/show_bug.cgi?id=14246 FeatureState systemVariables; // limit access to variables marked @system from @safe code bool useFastDFA; // Use fast data flow analysis engine - bool newpdb; // use the modern PDB/CodeView debug info emitter CHECKENABLE useInvariants = CHECKENABLE._default; // generate class invariant checks CHECKENABLE useIn = CHECKENABLE._default; // generate precondition checks diff --git a/compiler/test/compilable/previewhelp.d b/compiler/test/compilable/previewhelp.d index 117049a56a73..94a01ced14bf 100644 --- a/compiler/test/compilable/previewhelp.d +++ b/compiler/test/compilable/previewhelp.d @@ -20,6 +20,5 @@ Upcoming language changes listed by -preview=name: =fixImmutableConv disallow `void[]` data from holding immutable data (https://dlang.org/changelog/2.101.0.html#dmd.fix-immutable-conv, https://issues.dlang.org/show_bug.cgi?id=17148) =systemVariables disable access to variables marked '@system' from @safe code (https://dlang.org/spec/attribute.html#system-variables) =fastdfa Fast dataflow analysis engine, experimental - =newpdb use the modern PDB/CodeView debug info emitter ---- */ diff --git a/compiler/test/runnable/testpdb.d b/compiler/test/runnable/testpdb.d index 0715ab17f67c..59e4cc32af19 100644 --- a/compiler/test/runnable/testpdb.d +++ b/compiler/test/runnable/testpdb.d @@ -1,5 +1,4 @@ // REQUIRED_ARGS: -gf -mixin=${RESULTS_DIR}/runnable/testpdb.mixin -preview=bitfields -// REQUIRED_ARGS(windows): -preview=newpdb // PERMUTE_ARGS: import core.time; @@ -65,6 +64,11 @@ void main(string[] args) test21665(session, globals); + testSourceChecksums(session, globals); + testCompile3(globals); + testFrameProc(globals); + testFuncId(globals); + source.Release(); session.Release(); globals.Release(); @@ -707,6 +711,117 @@ void test21665(IDiaSession session, IDiaSymbol globals) checkBitField!("b", 3, 6); } +/////////////////////////////////////////////// +// Tests for the modern CodeView records emitted by the CV8 debug info +// generator: blake3 source-file checksums (DEBUG_S_FILECHKSMS), S_COMPILE3 +// (compiland language/version) and S_FRAMEPROC (per-function frame info). + +// Each source file now carries a checksum; the legacy emitter wrote none. +void testSourceChecksums(IDiaSession session, IDiaSymbol globals) +{ + IDiaSymbol funcsym = searchSymbol(globals, "testpdb.test15432"); + funcsym || assert(false, "testpdb.test15432 not found"); + + DWORD rva; + funcsym.get_relativeVirtualAddress(&rva) == S_OK || assert(false, "test15432: no rva"); + ULONGLONG length; + funcsym.get_length(&length) == S_OK || assert(false, "test15432: no length"); + + IDiaEnumLineNumbers dialines; + (session.findLinesByRVA(rva, cast(DWORD)length, &dialines) == S_OK && dialines) + || assert(false, "test15432: no line numbers"); + scope(exit) dialines.Release(); + + IDiaLineNumber line; + ULONG fetched; + bool checkedAny = false; + while (dialines.Next(1, &line, &fetched) == S_OK && fetched == 1) + { + IDiaSourceFile src; + if (line.get_sourceFile(&src) == S_OK && src) + { + DWORD cktype; + src.get_checksumType(&cktype) == S_OK || assert(false, "source file has no checksum type"); + // legacy emitter wrote CV_CHKSUM_NONE (0); modern emitter writes a + // 32-byte checksum (CV_CHKSUM_SHA_256 slot) + cktype != 0 || assert(false, "source file has no checksum"); + checkedAny = true; + src.Release(); + } + line.Release(); + } + checkedAny || assert(false, "no source file checksum found"); +} + +// S_COMPILE3 exposes the compiland language and compiler version through +// SymTagCompilandDetails; the legacy S_COMPILE record did not. +void testCompile3(IDiaSymbol globals) +{ + IDiaEnumSymbols enumComps; + (globals.findChildren(SymTagEnum.SymTagCompiland, null, NameSearchOptions.nsNone, &enumComps) == S_OK && enumComps) + || assert(false, "no compilands"); + scope(exit) enumComps.Release(); + + IDiaSymbol compiland; + ULONG fetched; + bool foundD = false; + while (!foundD && enumComps.Next(1, &compiland, &fetched) == S_OK && fetched == 1) + { + IDiaEnumSymbols enumDetails; + if (compiland.findChildren(SymTagEnum.SymTagCompilandDetails, null, NameSearchOptions.nsNone, &enumDetails) == S_OK && enumDetails) + { + IDiaSymbol details; + ULONG f2; + if (enumDetails.Next(1, &details, &f2) == S_OK && f2 == 1 && details) + { + DWORD lang; + if (details.get_language(&lang) == S_OK && lang == 'D') + { + BSTR compilerName; + (details.get_compilerName(&compilerName) == S_OK && compilerName) + || assert(false, "compiland has no compiler name"); + wcslen(compilerName) > 0 || assert(false, "empty compiler name"); + SysFreeString(compilerName); + foundD = true; + } + details.Release(); + } + enumDetails.Release(); + } + compiland.Release(); + } + foundD || assert(false, "no D compiland details (S_COMPILE3) found"); +} + +// S_FRAMEPROC provides the stack frame size; without it get_frameSize fails. +void testFrameProc(IDiaSymbol globals) +{ + IDiaSymbol funcsym = searchSymbol(globals, "testpdb.sum21384"); + funcsym || assert(false, "testpdb.sum21384 not found"); + + DWORD frameSize; + funcsym.get_frameSize(&frameSize) == S_OK + || assert(false, "sum21384: no frame size (S_FRAMEPROC missing)"); +} + +// Functions are emitted as S_GPROC32_ID referencing an LF_FUNC_ID; DIA resolves +// the function's type through that id record. +void testFuncId(IDiaSymbol globals) +{ + IDiaSymbol funcsym = searchSymbol(globals, "testpdb.sum21384"); + funcsym || assert(false, "testpdb.sum21384 not found"); + + DWORD tag; + (funcsym.get_symTag(&tag) == S_OK && tag == SymTagEnum.SymTagFunction) + || assert(false, "sum21384: not a function symbol"); + + IDiaSymbol funcType; + (funcsym.get_type(&funcType) == S_OK && funcType) + || assert(false, "sum21384: no function type (LF_FUNC_ID chain broken)"); + (funcType.get_symTag(&tag) == S_OK && tag == SymTagEnum.SymTagFunctionType) + || assert(false, "sum21384: type is not a function type"); +} + /////////////////////////////////////////////// import core.stdc.stdio; import core.stdc.wchar_; From 6fd9426b26cfbf48283aafb23bdcd7f5ef3abe36 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Sun, 5 Jul 2026 19:46:14 -0700 Subject: [PATCH 15/19] Add support for LF_UDT_SRC_LINE. --- changelog/dmd.newpdb.dd | 1 + compiler/src/dmd/backend/cv8.d | 40 +++++++++++++++++++++++++++++ compiler/src/dmd/glue/tocvdebug.d | 13 ++++++++++ compiler/test/runnable/testpdb.d | 42 +++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+) diff --git a/changelog/dmd.newpdb.dd b/changelog/dmd.newpdb.dd index accdd0e380f8..e2dd031d93df 100644 --- a/changelog/dmd.newpdb.dd +++ b/changelog/dmd.newpdb.dd @@ -8,6 +8,7 @@ $(LIST * `S_OBJNAME` and `S_COMPILE3` compiland records reporting the configured language and the real compiler version string, * `S_ENVBLOCK` and `S_BUILDINFO` (`LF_BUILDINFO`) records describing the build environment, * `S_FRAMEPROC` records describing each function's stack frame, + * `LF_UDT_SRC_LINE` records recording the source file and line where each user-defined type (struct, class, enum) is defined, so debuggers can jump to a type's definition, * blake3 source-file checksums in the file-checksums subsection so debuggers can verify the source matches. ) diff --git a/compiler/src/dmd/backend/cv8.d b/compiler/src/dmd/backend/cv8.d index c5fb008d0e6a..2555f368633a 100644 --- a/compiler/src/dmd/backend/cv8.d +++ b/compiler/src/dmd/backend/cv8.d @@ -84,6 +84,7 @@ enum LF_FUNC_ID = 0x1601, // function id: function type + name LF_STRING_ID = 0x1605, // interned string -> type index LF_BUILDINFO = 0x1603, // cwd, tool, source, pdb, args + LF_UDT_SRC_LINE = 0x1606, // source file/line where a UDT is defined } // .debug$S subsection kinds @@ -1412,4 +1413,43 @@ idx_t cv8_func_id(const(char)* id, idx_t functype) return cv_debtyp(d); } +/* LF_UDT_SRC_LINE: source file and line where a user-defined type is defined. */ +@trusted +idx_t cv8_udt_src_line(idx_t typidx, const(char)* filename, uint line) +{ + /* The source file must be spelled the same way as in the module's source + * file list (see cv8_addfile), i.e. as an absolute path, otherwise the + * debugger cannot associate this record with a known source file. + */ + char[2 * 260] pathbuf = void; + const(char)* srcpath = filename; + const bool abs = (*filename == '\\') || (*filename == '/') || + (*filename && filename[1] == ':'); + if (!abs) + { + char[260] cwd = 0; + if (getcwd(cwd.ptr, cwd.sizeof)) + { + size_t cwdlen = strlen(cwd.ptr); + if (cwdlen && cwd[cwdlen - 1] != '\\' && cwd[cwdlen - 1] != '/') + cwd[cwdlen++] = '\\'; + const flen = strlen(filename); + if (cwdlen + flen + 1 <= pathbuf.length) + { + memcpy(pathbuf.ptr, cwd.ptr, cwdlen); + memcpy(pathbuf.ptr + cwdlen, filename, flen + 1); + srcpath = pathbuf.ptr; + } + } + } + + idx_t srcId = cv8_string_id(srcpath); + debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4); + TOWORD(d.data.ptr, LF_UDT_SRC_LINE); + TOLONG(d.data.ptr + 2, typidx); // the user-defined type + TOLONG(d.data.ptr + 6, srcId); // LF_STRING_ID of the source file + TOLONG(d.data.ptr + 10, line); // line number of the definition + return cv_debtyp(d); +} + } diff --git a/compiler/src/dmd/glue/tocvdebug.d b/compiler/src/dmd/glue/tocvdebug.d index 12c1f350db1e..54a056332dd8 100644 --- a/compiler/src/dmd/glue/tocvdebug.d +++ b/compiler/src/dmd/glue/tocvdebug.d @@ -306,6 +306,16 @@ void cv_udt(const char* id, uint typidx) objmod.write_bytes(SegData[DEBSYM],debsym[0 .. length]); } +/* Record the source file and line where a user-defined type is defined + * (emits an LF_UDT_SRC_LINE record). Only the modern CodeView (CV8) path + * supports this; the CV4 path has no equivalent record. + */ +private void cv_udt_srcline(Dsymbol s, idx_t typidx) +{ + if (config.fulltypes == CV8) + cast(void)cv8_udt_src_line(typidx, s.loc.filename, s.loc.linnum); +} + /* ==================================================================== */ /**************************** @@ -327,6 +337,7 @@ void toDebug(EnumDeclaration ed) const id = ed.toPrettyChars(true); const idx_t typidx = cv4_Denum(ed); cv_udt(id, typidx); + cv_udt_srcline(ed, typidx); } } @@ -605,6 +616,7 @@ void toDebug(StructDeclaration sd) // cv4_outsym(s); cv_udt(id, typidx); + cv_udt_srcline(sd, typidx); // return typidx; } @@ -806,6 +818,7 @@ void toDebug(ClassDeclaration cd) // cv4_outsym(s); cv_udt(id, typidx); + cv_udt_srcline(cd, typidx); // return typidx; } diff --git a/compiler/test/runnable/testpdb.d b/compiler/test/runnable/testpdb.d index 59e4cc32af19..3a3ea7d3b315 100644 --- a/compiler/test/runnable/testpdb.d +++ b/compiler/test/runnable/testpdb.d @@ -68,6 +68,7 @@ void main(string[] args) testCompile3(globals); testFrameProc(globals); testFuncId(globals); + testUdtSrcLine(globals); source.Release(); session.Release(); @@ -822,6 +823,47 @@ void testFuncId(IDiaSymbol globals) || assert(false, "sum21384: type is not a function type"); } +// LF_UDT_SRC_LINE records the source file/line where a user-defined type is defined. +enum lineUdtStruct = __LINE__ + 1; +struct UdtSrcLineStruct { int x; } + +enum lineUdtClass = __LINE__ + 1; +class UdtSrcLineClass { int y; } + +enum lineUdtEnum = __LINE__ + 1; +enum UdtSrcLineEnum { valueA, valueB } + +void testUdtSrcLine(IDiaSymbol globals) +{ + // Reference each type so that full debug info is emitted for it. + UdtSrcLineStruct s; + s.x = 0; + scope c = new UdtSrcLineClass; + c.y = 0; + UdtSrcLineEnum e = UdtSrcLineEnum.valueA; + assert(e == UdtSrcLineEnum.valueA); + + checkUdtSrcLine(globals, "testpdb.UdtSrcLineStruct"w, SymTagEnum.SymTagUDT, lineUdtStruct); + checkUdtSrcLine(globals, "testpdb.UdtSrcLineClass"w, SymTagEnum.SymTagUDT, lineUdtClass); + checkUdtSrcLine(globals, "testpdb.UdtSrcLineEnum"w, SymTagEnum.SymTagEnum, lineUdtEnum); +} + +void checkUdtSrcLine(IDiaSymbol globals, wstring name, SymTagEnum tag, uint expectedLine) +{ + IDiaSymbol udt = searchSymbol(globals, name.ptr, tag); + udt || assert(false, "testUdtSrcLine: type not found"); + scope(exit) udt.Release(); + + IDiaLineNumber lineNo; + (udt.getSrcLineOnTypeDefn(&lineNo) == S_OK && lineNo) + || assert(false, "testUdtSrcLine: no source line (LF_UDT_SRC_LINE missing)"); + scope(exit) lineNo.Release(); + + DWORD line; + lineNo.get_lineNumber(&line) == S_OK || assert(false, "testUdtSrcLine: no line number"); + line == expectedLine || assert(false, "testUdtSrcLine: wrong definition line"); +} + /////////////////////////////////////////////// import core.stdc.stdio; import core.stdc.wchar_; From 10011713828d44ca2ad2695084f50cf1e43cf1f4 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Sun, 5 Jul 2026 20:44:41 -0700 Subject: [PATCH 16/19] Fix OOM by capping function IDs at CV8_MAX_SYMBOL_LENGTH --- compiler/src/dmd/backend/cv8.d | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/compiler/src/dmd/backend/cv8.d b/compiler/src/dmd/backend/cv8.d index 2555f368633a..ae5641f5a6b0 100644 --- a/compiler/src/dmd/backend/cv8.d +++ b/compiler/src/dmd/backend/cv8.d @@ -1360,10 +1360,14 @@ bool cv8_filehash(const(char)* filename, ref ubyte[32] hash) idx_t cv8_string_id(const(char)* s) { if (!s) s = ""; - debtyp_t* d = debtyp_alloc(2 + 4 + cv_stringbytes(s)); + int idlen = cast(int)strlen(s); + if (idlen > CV8_MAX_SYMBOL_LENGTH) // CodeView records have a 16-bit length field + idlen = CV8_MAX_SYMBOL_LENGTH; + debtyp_t* d = debtyp_alloc(2 + 4 + idlen + 1); TOWORD(d.data.ptr, LF_STRING_ID); TOLONG(d.data.ptr + 2, 0); // substring list id - cv_namestring(d.data.ptr + 6, s); + cv_namestring(d.data.ptr + 6, s, idlen); + d.data.ptr[6 + idlen] = 0; return cv_debtyp(d); } @@ -1405,11 +1409,15 @@ idx_t cv8_buildinfo() @trusted idx_t cv8_func_id(const(char)* id, idx_t functype) { - debtyp_t* d = debtyp_alloc(2 + 4 + 4 + cv_stringbytes(id)); + int idlen = cast(int)strlen(id); + if (idlen > CV8_MAX_SYMBOL_LENGTH) // CodeView records have a 16-bit length field + idlen = CV8_MAX_SYMBOL_LENGTH; + debtyp_t* d = debtyp_alloc(2 + 4 + 4 + idlen + 1); TOWORD(d.data.ptr, LF_FUNC_ID); TOLONG(d.data.ptr + 2, 0); // parent scope id TOLONG(d.data.ptr + 6, functype); - cv_namestring(d.data.ptr + 10, id); + cv_namestring(d.data.ptr + 10, id, idlen); + d.data.ptr[10 + idlen] = 0; return cv_debtyp(d); } From afa9a1be7de3481762dc6e8bde76d2c7488248c6 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Mon, 6 Jul 2026 02:32:16 -0700 Subject: [PATCH 17/19] Nit cleanup. --- compiler/src/dmd/backend/cv8.d | 109 +++++++++++++++--------------- compiler/src/dmd/glue/tocvdebug.d | 32 ++++----- compiler/test/runnable/testpdb.d | 9 ++- 3 files changed, 72 insertions(+), 78 deletions(-) diff --git a/compiler/src/dmd/backend/cv8.d b/compiler/src/dmd/backend/cv8.d index ae5641f5a6b0..d5b6cf108409 100644 --- a/compiler/src/dmd/backend/cv8.d +++ b/compiler/src/dmd/backend/cv8.d @@ -311,7 +311,7 @@ void cv8_termfile(const(char)[] objfilename) buf.write16(cast(int)(2 + 4 + 2 + 8 + 8 + ver.length + 1)); buf.write16(S_COMPILE3); buf.write32(flags); - buf.write16(I64 ? 0xD0 : 0x07); // machine: AMD64 / x86 + buf.write16(I64 ? 0xD0 : 0x06); // machine: AMD64 / x86 buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // front end version buf.write16(0); buf.write16(0); buf.write16(0); buf.write16(0); // back end version buf.write(ver.ptr, cast(uint)ver.length); @@ -682,6 +682,46 @@ void cv8_linnum(Srcpos srcpos, uint offset) segmentheader[2] = segmentbytes; } +/********************************************** + * Make `filename` absolute by prefixing the current working directory, so the + * debugger can find the source without knowing the compilation directory. + * Params: + * filename = source file name (relative or absolute) + * buf = scratch buffer to hold the result + * Returns: + * pointer to the absolute path (stored in `buf`), or `filename` unchanged + * if it is already absolute or does not fit in `buf`. + * Note: + * cv8_addfile() and cv8_udt_src_line() must spell the path identically so + * the debugger can match an LF_UDT_SRC_LINE record to a source file. + */ +@trusted +private const(char)* cv8_absfilename(const(char)* filename, char[] buf) +{ + const bool abs = (*filename == '\\') || (*filename == '/') || + (*filename && filename[1] == ':'); + if (abs) + return filename; + + __gshared char[260] cwd = 0; + __gshared size_t cwdlen = 0; + if (cwd[0] == 0) + { + if (!getcwd(cwd.ptr, cwd.sizeof)) + return filename; + cwdlen = strlen(cwd.ptr); + if (cwdlen && cwd[cwdlen - 1] != '\\' && cwd[cwdlen - 1] != '/') + cwd[cwdlen++] = '\\'; + } + + const flen = strlen(filename); + if (cwdlen + flen + 1 > buf.length) + return filename; + memcpy(buf.ptr, cwd.ptr, cwdlen); + memcpy(buf.ptr + cwdlen, filename, flen + 1); + return buf.ptr; +} + /********************************************** * Add source file, if it isn't already there. * Return offset into F4. @@ -697,48 +737,24 @@ uint cv8_addfile(const(char)* filename) * Unlike C, there won't be lots of .h source files to be accounted for. */ + // The file names are stored as absolute paths (see cv8_absfilename) so the + // debugger can find the source without knowing the compilation directory. + char[2 * 260] pathbuf = void; + const(char)* absname = cv8_absfilename(filename, pathbuf[]); + size_t len = strlen(absname); + uint length = cast(uint)F3_buf.length(); ubyte* p = F3_buf.buf; - size_t len = strlen(filename); - - // ensure the filename is absolute to help the debugger to find the source - // without having to know the working directory during compilation - __gshared char[260] cwd = 0; - __gshared uint cwdlen; - bool abs = (*filename == '\\') || - (*filename == '/') || - (*filename && filename[1] == ':'); - if (!abs && cwd[0] == 0) - { - if (getcwd(cwd.ptr, cwd.sizeof)) - { - cwdlen = cast(uint)strlen(cwd.ptr); - if(cwd[cwdlen - 1] != '\\' && cwd[cwdlen - 1] != '/') - cwd[cwdlen++] = '\\'; - } - } uint off = 1; while (off + len < length) { - if (!abs) - { - if (memcmp(p + off, cwd.ptr, cwdlen) == 0 && - memcmp(p + off + cwdlen, filename, len + 1) == 0) - goto L1; - } - else if (memcmp(p + off, filename, len + 1) == 0) - { // Already there - //printf("\talready there at %x\n", off); - goto L1; - } + if (memcmp(p + off, absname, len + 1) == 0) + goto L1; // already there off += strlen(cast(const(char)* )(p + off)) + 1; } off = length; - // Add it - if(!abs) - F3_buf.write(cwd.ptr, cwdlen); - F3_buf.write(filename, cast(uint)(len + 1)); + F3_buf.write(absname, cast(uint)(len + 1)); // add it L1: // off is the offset of the filename in F3. @@ -759,8 +775,8 @@ L1: u = (u + 3) & ~3; // realign to 4 } - // Not present; append a checksum computed over the source file's *content* - // (not over its name) so debuggers can verify the source matches. + // Not present; append a checksum computed over the source file's content + // so debuggers can verify the source matches. F4_buf.write32(off); ubyte[32] hash = void; if (cv8_filehash(filename, hash)) @@ -1430,26 +1446,7 @@ idx_t cv8_udt_src_line(idx_t typidx, const(char)* filename, uint line) * debugger cannot associate this record with a known source file. */ char[2 * 260] pathbuf = void; - const(char)* srcpath = filename; - const bool abs = (*filename == '\\') || (*filename == '/') || - (*filename && filename[1] == ':'); - if (!abs) - { - char[260] cwd = 0; - if (getcwd(cwd.ptr, cwd.sizeof)) - { - size_t cwdlen = strlen(cwd.ptr); - if (cwdlen && cwd[cwdlen - 1] != '\\' && cwd[cwdlen - 1] != '/') - cwd[cwdlen++] = '\\'; - const flen = strlen(filename); - if (cwdlen + flen + 1 <= pathbuf.length) - { - memcpy(pathbuf.ptr, cwd.ptr, cwdlen); - memcpy(pathbuf.ptr + cwdlen, filename, flen + 1); - srcpath = pathbuf.ptr; - } - } - } + const(char)* srcpath = cv8_absfilename(filename, pathbuf[]); idx_t srcId = cv8_string_id(srcpath); debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4); diff --git a/compiler/src/dmd/glue/tocvdebug.d b/compiler/src/dmd/glue/tocvdebug.d index 54a056332dd8..45bd7c5a1543 100644 --- a/compiler/src/dmd/glue/tocvdebug.d +++ b/compiler/src/dmd/glue/tocvdebug.d @@ -281,10 +281,17 @@ uint cv_align(ubyte* p, uint n) * id = name of user defined type * typidx = type index */ -void cv_udt(const char* id, uint typidx) +void cv_udt(Dsymbol s, const char* id, uint typidx) { if (config.fulltypes == CV8) - return cv8_udt(id, typidx); + { + cv8_udt(id, typidx); + // Record the source file and line where the type is defined + // (LF_UDT_SRC_LINE); the CV4 path has no equivalent record. + if (s) + cast(void)cv8_udt_src_line(typidx, s.loc.filename, s.loc.linnum); + return; + } const len = strlen(id); version (AArch64) // TODO AArch64 @@ -306,16 +313,6 @@ void cv_udt(const char* id, uint typidx) objmod.write_bytes(SegData[DEBSYM],debsym[0 .. length]); } -/* Record the source file and line where a user-defined type is defined - * (emits an LF_UDT_SRC_LINE record). Only the modern CodeView (CV8) path - * supports this; the CV4 path has no equivalent record. - */ -private void cv_udt_srcline(Dsymbol s, idx_t typidx) -{ - if (config.fulltypes == CV8) - cast(void)cv8_udt_src_line(typidx, s.loc.filename, s.loc.linnum); -} - /* ==================================================================== */ /**************************** @@ -336,8 +333,7 @@ void toDebug(EnumDeclaration ed) { const id = ed.toPrettyChars(true); const idx_t typidx = cv4_Denum(ed); - cv_udt(id, typidx); - cv_udt_srcline(ed, typidx); + cv_udt(ed, id, typidx); } } @@ -615,8 +611,7 @@ void toDebug(StructDeclaration sd) // cv4_outsym(s); - cv_udt(id, typidx); - cv_udt_srcline(sd, typidx); + cv_udt(sd, id, typidx); // return typidx; } @@ -817,8 +812,7 @@ void toDebug(ClassDeclaration cd) // cv4_outsym(s); - cv_udt(id, typidx); - cv_udt_srcline(cd, typidx); + cv_udt(cd, id, typidx); // return typidx; } @@ -931,7 +925,7 @@ void toDebugClosure(Symbol* closstru) TOWORD(d.data.ptr + 6,property); } - cv_udt(closname, typidx); + cv_udt(null, closname, typidx); } /* ===================================================================== */ diff --git a/compiler/test/runnable/testpdb.d b/compiler/test/runnable/testpdb.d index 3a3ea7d3b315..a6d4734c8c4b 100644 --- a/compiler/test/runnable/testpdb.d +++ b/compiler/test/runnable/testpdb.d @@ -720,6 +720,9 @@ void test21665(IDiaSession session, IDiaSymbol globals) // Each source file now carries a checksum; the legacy emitter wrote none. void testSourceChecksums(IDiaSession session, IDiaSymbol globals) { + // DIA checksum type; the emitter stores a blake3 hash in this 32-byte slot. + enum CV_CHKSUM_SHA_256 = 3; + IDiaSymbol funcsym = searchSymbol(globals, "testpdb.test15432"); funcsym || assert(false, "testpdb.test15432 not found"); @@ -743,9 +746,9 @@ void testSourceChecksums(IDiaSession session, IDiaSymbol globals) { DWORD cktype; src.get_checksumType(&cktype) == S_OK || assert(false, "source file has no checksum type"); - // legacy emitter wrote CV_CHKSUM_NONE (0); modern emitter writes a - // 32-byte checksum (CV_CHKSUM_SHA_256 slot) - cktype != 0 || assert(false, "source file has no checksum"); + // The emitter stores a blake3 hash in the 32-byte SHA-256 checksum + // slot; the legacy emitter wrote CV_CHKSUM_NONE (0). + cktype == CV_CHKSUM_SHA_256 || assert(false, "source file checksum is not the SHA-256 (blake3) slot"); checkedAny = true; src.Release(); } From 9285dc7527432ada2c330074b6c33d80b6e38a7f Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Mon, 6 Jul 2026 02:49:41 -0700 Subject: [PATCH 18/19] use toAbsolute() --- compiler/src/dmd/backend/cv8.d | 51 ++++------------------------------ 1 file changed, 5 insertions(+), 46 deletions(-) diff --git a/compiler/src/dmd/backend/cv8.d b/compiler/src/dmd/backend/cv8.d index d5b6cf108409..78b9b9769481 100644 --- a/compiler/src/dmd/backend/cv8.d +++ b/compiler/src/dmd/backend/cv8.d @@ -40,6 +40,7 @@ import dmd.backend.symbol : globsym; import dmd.backend.ty; import dmd.backend.type; import dmd.backend.dvarstats; +import dmd.root.filename : FileName; nothrow: @@ -682,46 +683,6 @@ void cv8_linnum(Srcpos srcpos, uint offset) segmentheader[2] = segmentbytes; } -/********************************************** - * Make `filename` absolute by prefixing the current working directory, so the - * debugger can find the source without knowing the compilation directory. - * Params: - * filename = source file name (relative or absolute) - * buf = scratch buffer to hold the result - * Returns: - * pointer to the absolute path (stored in `buf`), or `filename` unchanged - * if it is already absolute or does not fit in `buf`. - * Note: - * cv8_addfile() and cv8_udt_src_line() must spell the path identically so - * the debugger can match an LF_UDT_SRC_LINE record to a source file. - */ -@trusted -private const(char)* cv8_absfilename(const(char)* filename, char[] buf) -{ - const bool abs = (*filename == '\\') || (*filename == '/') || - (*filename && filename[1] == ':'); - if (abs) - return filename; - - __gshared char[260] cwd = 0; - __gshared size_t cwdlen = 0; - if (cwd[0] == 0) - { - if (!getcwd(cwd.ptr, cwd.sizeof)) - return filename; - cwdlen = strlen(cwd.ptr); - if (cwdlen && cwd[cwdlen - 1] != '\\' && cwd[cwdlen - 1] != '/') - cwd[cwdlen++] = '\\'; - } - - const flen = strlen(filename); - if (cwdlen + flen + 1 > buf.length) - return filename; - memcpy(buf.ptr, cwd.ptr, cwdlen); - memcpy(buf.ptr + cwdlen, filename, flen + 1); - return buf.ptr; -} - /********************************************** * Add source file, if it isn't already there. * Return offset into F4. @@ -737,10 +698,9 @@ uint cv8_addfile(const(char)* filename) * Unlike C, there won't be lots of .h source files to be accounted for. */ - // The file names are stored as absolute paths (see cv8_absfilename) so the - // debugger can find the source without knowing the compilation directory. - char[2 * 260] pathbuf = void; - const(char)* absname = cv8_absfilename(filename, pathbuf[]); + // The file names are stored as absolute paths so the debugger can find the + // source without knowing the compilation directory. + const(char)* absname = FileName.toAbsolute(filename); size_t len = strlen(absname); uint length = cast(uint)F3_buf.length(); @@ -1445,8 +1405,7 @@ idx_t cv8_udt_src_line(idx_t typidx, const(char)* filename, uint line) * file list (see cv8_addfile), i.e. as an absolute path, otherwise the * debugger cannot associate this record with a known source file. */ - char[2 * 260] pathbuf = void; - const(char)* srcpath = cv8_absfilename(filename, pathbuf[]); + const(char)* srcpath = FileName.toAbsolute(filename); idx_t srcId = cv8_string_id(srcpath); debtyp_t* d = debtyp_alloc(2 + 4 + 4 + 4); From ff0eb7d9b12527229f74b7c561495f83ea7cb2e8 Mon Sep 17 00:00:00 2001 From: Adam Wilson Date: Mon, 6 Jul 2026 03:01:42 -0700 Subject: [PATCH 19/19] Fix cv_udt Docs. --- compiler/src/dmd/glue/tocvdebug.d | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dmd/glue/tocvdebug.d b/compiler/src/dmd/glue/tocvdebug.d index 45bd7c5a1543..21c7f6e4bb1a 100644 --- a/compiler/src/dmd/glue/tocvdebug.d +++ b/compiler/src/dmd/glue/tocvdebug.d @@ -278,6 +278,7 @@ uint cv_align(ubyte* p, uint n) /************************************* * write a UDT record to the object file * Params: + * s = user defined type symbol * id = name of user defined type * typidx = type index */