diff options
| author | Daniel Gavin <danielgavin5@hotmail.com> | 2022-08-17 00:31:55 +0200 |
|---|---|---|
| committer | Daniel Gavin <danielgavin5@hotmail.com> | 2022-08-17 00:31:55 +0200 |
| commit | a9f3f61ef7e0995a2f8ab30c461a502fa2176753 (patch) | |
| tree | 0fa49881f39929df0969743b47aceb2fb4b5c843 | |
| parent | 81e17b2aa1cf54a5a9226f1b3b766810008372d4 (diff) | |
Fix rare crash with semantic token
| -rw-r--r-- | src/dump.odin | 78 | ||||
| -rw-r--r-- | src/main.odin | 6 | ||||
| -rw-r--r-- | src/pdb/cvsRecord.odin | 773 | ||||
| -rw-r--r-- | src/pdb/cvtRecord.odin | 627 | ||||
| -rw-r--r-- | src/pdb/dbiStream.odin | 351 | ||||
| -rw-r--r-- | src/pdb/hashTable.odin | 64 | ||||
| -rw-r--r-- | src/pdb/modStream.odin | 353 | ||||
| -rw-r--r-- | src/pdb/msf.odin | 376 | ||||
| -rw-r--r-- | src/pdb/namesStream.odin | 25 | ||||
| -rw-r--r-- | src/pdb/pdbStream.odin | 82 | ||||
| -rw-r--r-- | src/pdb/peFile.odin | 312 | ||||
| -rw-r--r-- | src/pdb/stackTrace.odin | 454 | ||||
| -rw-r--r-- | src/pdb/tpiStream.odin | 95 | ||||
| -rw-r--r-- | src/server/analysis.odin | 8 | ||||
| -rw-r--r-- | src/server/documents.odin | 8 |
15 files changed, 3603 insertions, 9 deletions
diff --git a/src/dump.odin b/src/dump.odin new file mode 100644 index 0000000..96908fd --- /dev/null +++ b/src/dump.odin @@ -0,0 +1,78 @@ +package main + +import "core:os" +import "core:slice" +import "core:strings" +import "core:intrinsics" +import "core:runtime" +import "core:io" +import "core:sync" +import "core:path/filepath" +import "core:log" +import "core:fmt" + +import "pdb" + +import windows "core:sys/windows" + +set_stacktrace :: proc() { + pdb.SetUnhandledExceptionFilter(dump_stack_trace_on_exception_logger) +} + +print_source_code_location_builder :: proc (using scl: runtime.Source_Code_Location) -> string { + using runtime + + builder := strings.builder_make() + + strings.write_string(&builder, file_path) + when ODIN_ERROR_POS_STYLE == .Unix { + strings.write_string(&builder, ':') + strings.write_i64(&builder, cast(i64)line) + strings.write_string(&builder, ':') + strings.write_i64(&builder, cast(i64)column) + strings.write_string(&builder, ':') + } else { + strings.write_string(&builder, "(") + strings.write_i64(&builder, cast(i64)line) + strings.write_string(&builder,":") + strings.write_i64(&builder, cast(i64)column) + strings.write_string(&builder, ")") + } + strings.write_string(&builder,procedure) + strings.write_string(&builder,"()\n") + + return strings.to_string(builder) +} + + +dump_stack_trace_on_exception_logger :: proc "stdcall" (ExceptionInfo: ^windows.EXCEPTION_POINTERS) -> windows.LONG { + using pdb + context = runtime.default_context() // TODO: use a more efficient one-off allocators + context.logger = logger + + builder := strings.builder_make() + + sync.guard(&_dumpStackTrackMutex) + + if ExceptionInfo.ExceptionRecord != nil { + strings.write_string(&builder, fmt.tprintf("%v, Flags: 0x %v", ExceptionInfo.ExceptionRecord.ExceptionCode, ExceptionInfo.ExceptionRecord.ExceptionFlags)) + } + + ctxt := cast(^CONTEXT)ExceptionInfo.ContextRecord + traceBuf : [64]StackFrame + traceCount := capture_stack_trace_from_context(ctxt, traceBuf[:]) + strings.write_string(&builder, " Stacktrace:") + strings.write_uint(&builder, traceCount) + strings.write_string(&builder, "\n") + srcCodeLocs : RingBuffer(runtime.Source_Code_Location) + init_rb(&srcCodeLocs, 64) + parse_stack_trace(traceBuf[:traceCount], true, &srcCodeLocs) + for i in 0..<srcCodeLocs.len { + scl := get_rb(&srcCodeLocs, i) + strings.write_string(&builder, print_source_code_location_builder(scl)) + } + + log.error(strings.to_string(builder)) + + return windows.EXCEPTION_CONTINUE_SEARCH +} diff --git a/src/main.odin b/src/main.odin index 37b167d..7df5a3a 100644 --- a/src/main.odin +++ b/src/main.odin @@ -33,6 +33,8 @@ os_write :: proc(handle: rawptr, data: []byte) -> (int, int) { request_thread: ^thread.Thread +logger: log.Logger + run :: proc(reader: ^server.Reader, writer: ^server.Writer) { common.config.collections = make(map[string]string) @@ -59,8 +61,6 @@ run :: proc(reader: ^server.Reader, writer: ^server.Writer) { server.setup_index(); for common.config.running { - logger: log.Logger - if common.config.verbose { logger = server.create_lsp_logger(writer, log.Level.Info) } else { @@ -112,7 +112,7 @@ main :: proc() { context.logger = log.create_file_logger(fh, log.Level.Info) */ - //pdb.SetUnhandledExceptionFilter(pdb.dump_stack_trace_on_exception) + set_stacktrace() when ODIN_OS == .Darwin { init_global_temporary_allocator(mem.Megabyte*100) diff --git a/src/pdb/cvsRecord.odin b/src/pdb/cvsRecord.odin new file mode 100644 index 0000000..b340d56 --- /dev/null +++ b/src/pdb/cvsRecord.odin @@ -0,0 +1,773 @@ +//! CodeView Symbol Records, reference: https://llvm.org/docs/PDB/CodeViewSymbols.html +package pdb +import "core:log" +import "core:intrinsics" + +CvsOffset :: distinct u32le + +CvsRecordHeader :: struct #packed { + length : u16le, // record length excluding this 2 byte field + kind : CvsRecordKind, +} + +CvsRecordKind :: enum u16le { + //Public Symbols + S_PUB32 = 0x110e, // CvsPub32 + //Global Symbols + S_GDATA32 = 0x110d, // CvsData32 + S_GTHREAD32 = 0x1113, // CvsData32 + S_PROCREF = 0x1125, // CvsRef2 + S_DATAREF = 0x1126, // CvsRef2 + S_LPROCREF = 0x1127, // CvsRef2 + S_GMANDATA = 0x111d, // CvsData32 + //Module Symbols + S_END = 0x0006, // nothing follows + S_FRAMEPROC = 0x1012, // CvsFrameProc + S_OBJNAME = 0x1101, // CvsObjName + S_THUNK32 = 0x1102, // CvsThunk32 + S_BLOCK32 = 0x1103, // CvsBlocks32 + S_LABEL32 = 0x1105, // CvsLabel32 + S_REGISTER = 0x1106, // CvsRegister + S_BPREL32 = 0x110b, // CvsBPRel32 + S_LPROC32 = 0x110f, // CvsProc32 + S_GPROC32 = 0x1110, // CvsProc32 + S_REGREL32 = 0x1111, // CvsRegRel32 + S_COMPILE2 = 0x1116, // CvsCompile2 + S_UNAMESPACE = 0x1124, // CsvNamespace + S_TRAMPOLINE = 0x112c, // CvsTrampoline + S_SECTION = 0x1136, // CvsSection + S_COFFGROUP = 0x1137, // CvsCoffGroup + S_EXPORT = 0x1138, // CvsExport + S_CALLSITEINFO = 0x1139, // S_CALLSITEINFO + S_FRAMECOOKIE = 0x113a, // CvsFrameCookie + S_COMPILE3 = 0x113c, // CvsCompile3 + S_ENVBLOCK = 0x113d, // CvsEnvBlock + S_LOCAL = 0x113e, // CvsLocal + S_DEFRANGE = 0x113f, + S_DEFRANGE_SUBFIELD = 0x1140, // CvsDefRangeSubfield + S_DEFRANGE_REGISTER = 0x1141, // CvsDefRangeRegister + S_DEFRANGE_FRAMEPOINTER_REL = 0x1142, // CvsDefRangeFramePointerRel + S_DEFRANGE_SUBFIELD_REGISTER = 0x1143, // CvsDefRangeSubfieldRegister + S_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE = 0x1144, // CvsDefRangeFramePointerRelFullScope + S_DEFRANGE_REGISTER_REL = 0x1145, // CvsDefRangeRegisterRel + S_LPROC32_ID = 0x1146, // CvsProc32 + S_GPROC32_ID = 0x1147, // CvsProc32 + S_BUILDINFO = 0x114c, // CvsBuildInfo + S_INLINESITE = 0x114d, // CvsInlineSite + S_INLINESITE_END = 0x114e, // nothing + S_PROC_ID_END = 0x114f, // nothing + S_FILESTATIC = 0x1153, // CvsFileStatic + S_LPROC32_DPC = 0x1155, // CvsProc32 + S_LPROC32_DPC_ID = 0x1156, // CvsProc32 + S_CALLEES = 0x115a, // CvsFunctionList + S_CALLERS = 0x115b, // CvsFunctionList + S_HEAPALLOCSITE = 0x115e, // CvsHeapAllocSite + //? S_FASTLINK = 0x1167, + //? S_INLINEES = 0x1168, + // either/both of the module info stream & global stream + S_CONSTANT = 0x1107, // CvsConstant + S_UDT = 0x1108, // CvsUDT + S_LDATA32 = 0x110c, // CvsData32 + S_LTHREAD32 = 0x1112, // CvsData32 + S_LMANDATA = 0x111c, // CvsData32 + S_MANCONSTANT = 0x112d, // CvsConstant +} + +CvsEnd :: struct #packed {} +// S_PUB32 +CvsPub32 :: struct { + using _base : struct #packed { + flags : CvsPub32_Flags, + using _pso : PESectionOffset, + }, + name : string, +} +CvsPub32_Flags :: enum u32le { + None = 0, + Code = 1 << 0, // code address + Function = 1 << 1, + Managed = 1 << 2, + MSIL = 1 << 3, // managed IL code +} + +// S_LDATA32, S_GDATA32, S_LMANDATA, S_GMANDATA, S_LTHREAD32, S_GTHREAD32 +CvsData32 :: struct { + using _base : struct #packed { + typind : TypeIndex, + using _pso : PESectionOffset, + }, + name : string, +} + +// S_PROCREF, S_DATAREF, S_LPROCREF +CvsRef2 :: struct { + using _base : struct #packed { + sucName : u32le, + ibSym : u32le, // offset of actual symbol in $$Symbols + imod : u16le, // module containing the actual symbol + }, + name : string, +} + +// S_FRAMEPROC +CvsFrameProc :: struct #packed { + frameCount : u32le, // bytes of total frame of proc + padCount : u32le, // bytes of padding in frame + padOffset : u32le, // pad offset (relative to frame pointer) + saveRegs : u32le, // byteCount of callee save registers + exHdlrOffset: u32le, // exception handler offset + exHdlrSect : u16le, // section id of exception handler + flags : CvsFrameProc_Flags, +} +CvsFrameProc_Flags :: enum u32le { + none = 0, + hasAlloca = 1<<0, // function uses _alloca() + hasSetJmp = 1<<1, // function uses setjmp() + hasLongJmp = 1<<2, // function uses longjmp() + hasInlAsm = 1<<3, // function uses inline asm + hasEH = 1<<4, // function has EH states + inlSpec = 1<<5, // function was speced as inline + hasSEH = 1<<6, // function has SEH + naked = 1<<7, // function is __declspec(naked) + securityChecks = 1<<8, // function has buffer security check introduced by /GS. + asyncEH = 1<<9, // function compiled with /EHa + gSNoStackOrdering = 1<<10, // function has /GS buffer checks, but stack ordering couldn't be done + wasInlined = 1<<11, // function was inlined within another function + gSCheck = 1<<12, // function is __declspec(strict_gs_check) + safeBuffers = 1<<13, // function is __declspec(safebuffers) + encodedLocalBasePointer0 = 1<<14, // record function's local pointer explicitly. + encodedLocalBasePointer1 = 1<<15, // record function's local pointer explicitly. + encodedParamBasePointer0 = 1<<16, // record function's parameter pointer explicitly. + encodedParamBasePointer1 = 1<<17, // record function's parameter pointer explicitly. + pogoOn = 1<<18, // function was compiled with PGO/PGU + validCounts = 1<<19, // Do we have valid Pogo counts? + optSpeed = 1<<20, // Did we optimize for speed? + guardCF = 1<<21, // function contains CFG checks (and no write checks) + guardCFW = 1<<22, // function contains CFW checks and/or instrumentation +} + +// S_OBJNAME +CvsObjName :: struct { + using _base : struct #packed { + signature : u32le, + }, + name : string, +} + +// S_THUNK32 +CvsThunk32 :: struct { + using _base : struct #packed { + pParent : CvsOffset, + pEnd : CvsOffset, + pNext : CvsOffset, + using _pso : PESectionOffset, + length : u16le, + ordinal : CvsThunkOrdinal, + }, + name : string, + // TODO: ??variant following name +} +CvsThunkOrdinal :: enum u8 { + NoType, + Adjustor, + VirtCall, + PCode, + Load, + TrampIncremental, + TrampBranchIsland, +} + +// S_BLOCK32 +CvsBlocks32 :: struct { + using _base : struct #packed { + pParent : CvsOffset, + pEnd : CvsOffset, + blockLen: u32le, + using _pso : PESectionOffset, + }, + name : string, +} + +// S_LABEL32 +CvsLabel32 :: struct { + using _base : struct #packed { + using _pso : PESectionOffset, + flags : CvsProcFlags, + }, + name : string, +} + +// S_REGISTER +CvsRegister :: struct { + using _base : struct #packed { + regType : TypeIndex, + reg : u16le, // register enumerate + }, + name : string, +} + +// S_BPREL32 +CvsBPRel32 :: struct { + using _base : struct #packed { + offset : u32le, // BP-relative offset + typind : TypeIndex, + }, + name : string, +} + +// S_GPROC32, S_LPROC32, S_GPROC32_ID, S_LPROC32_ID, S_LPROC32_DPC, S_LPROC32_DPC_ID +CvsProc32 :: struct { + using _base : struct #packed { + pParent : CvsOffset, + pEnd : CvsOffset, // pointer to this blocks end + pNext : CvsOffset, + length : u32le, + dbgStart: u32le, + dbgEnd : u32le, + typind : TypeIndex, + using _pso : PESectionOffset, + flags : CvsProcFlags, + }, + name : string, +} +// TODO: CV_PROCFLAGS +CvsProcFlags :: distinct u8 + +// S_REGREL32 +CvsRegRel32 :: struct { +using _base : struct #packed { + offset : u32le, + typind : TypeIndex, + reg : u16le, + }, + name : string, +} + +// S_COMPILE2 +CvsCompile2 :: struct { + using _base : struct #packed { + flags : CvsCompile2_Flags, + machine : u16le, // target processor + verFEMajor : u16le, // front end major version # + verFEMinor : u16le, // front end minor version # + verFEBuild : u16le, // front end build version # + verMajor : u16le, // back end major version # + verMinor : u16le, // back end minor version # + verBuild : u16le, // back end build version # + }, + name : string, // TODO: verSt should be an array of strings double terminated by 00 +} +// TODO: COMPILESYM.flags +CvsCompile2_Flags :: distinct u32le + +// S_UNAMESPACE +CvsUnamespace :: struct { + using _base : struct #packed {}, + name : string, +} + +// S_TRAMPOLINE +CvsTrampoline :: struct #packed { + trampType : u16le, // trampoline sym subtype + thunkSize : u16le, + thunkOffset : u32le, // offset of the thunk + targetOffset: u32le, // offset of the target of the thunk + thunkSect : u16le, // section index of the thunk + targetSect : u16le, // section index of the target of the thunk +} + +// S_SECTION +CvsSection :: struct { + using _base : struct #packed { + isec : u16le, // Section number + align : u8, // Alignment of this section (power of 2) + bReserved : u8, // Must be zero. + rva : u32le, + cb : u32le, + characteristics : u32le, + }, + name : string, +} + +// S_COFFGROUP +CvsCoffGroup :: struct { + using _base : struct #packed { + cb : u32le, + characteristics : u32le, + offset : u32le, + seg : u16le, + }, + name : string, +} + +// S_EXPORT +CvsExport :: struct { + using _base : struct #packed { + ordinal : u16le, + flags : CvsExportFlags, + }, + name : string, +} +CvsExportFlags :: enum u16le { + none = 0, + constant = 1 << 0, + data = 1 << 1, + private = 1 << 2, + noName = 1 << 3, + ordinal = 1 << 4, + forwarder= 1 << 5, +} + +// S_CALLSITEINFO +CvsCallsiteInfo :: struct #packed { + offset : u32le, + sect : u16le, + pad0 : u16le, + typind : TypeIndex, // for funtion signature +} + +// S_FRAMECOOKIE +CvsFrameCookie :: struct #packed { + offset : u32le, + reg : u16le, + cookieType : CvsCookieType, + flags : u8, //? +} +CvsCookieType :: enum u32le { + Copy, XorSP, XorBP, XorR13, +} + +// S_COMPILE3 +CvsCompile3 :: struct { +using _base : struct #packed { + flags : CvsCompile3_Flags, + machine : u16le, // target processor + verFEMajor : u16le, // front end major version # + verFEMinor : u16le, // front end minor version # + verFEBuild : u16le, // front end build version # + verFEQFE : u16le, // front end QFE version # + verMajor : u16le, // back end major version # + verMinor : u16le, // back end minor version # + verBuild : u16le, // back end build version # + verQFE : u16le, // back end QFE version # + }, + name : string, +} +// TODO: COMPILESYM3.flags +CvsCompile3_Flags :: distinct u32le + +// S_ENVBLOCK +CvsEnvBlock :: struct { + using _base : struct #packed { + reserved : u8, + }, + name : string, // TODO: should be an array of cstrings, double terminated by \0\0 +} + +// S_LOCAL +CvsLocal :: struct { + using _base : struct #packed { + typind : TypeIndex, + flags : CvsLvarFlags, + }, + name : string, +} +CvsLvarFlags :: enum u16le { + none =0, + isParam =1<<0, + addrTaken =1<<1, + compGenx =1<<2, // compiler generated + isAggregate =1<<3, // the symbol is splitted in temporaries, + isAggregated =1<<4, // Counterpart of fAggregate + isAliased =1<<5, // has multiple simultaneous lifetimes + isAlias =1<<6, // Counterparts of Aliased + isRetValue =1<<7, // represents a function return value + isOptimizedOut =1<<8, // has no lifetimes + isEnregGlob =1<<9, // an enregistered global + isEnregStat =1<<10, // an enregistered static +} + +// S_BUILDINFO +CvsBuildInfo :: struct #packed { + id : CvItemId, // build info id +} + +// S_INLINESITE +CvsInlineSite :: struct { + using _npm : MsfNotPackedMarker, + using _base : struct #packed { + pParent : CvsOffset, // might points to a proc or another inline site + pEnd : CvsOffset, + inlinee : CvItemId, + }, + //binaryAnnotations : []byte; // an array of compressed binary annotations. + lines : []CvsInlineUncompressedLine, // blocks that should be checked individually +} +CvsInlineUncompressedLine :: struct { + offset : u32le, // +rva base. + end : u32le, // offset+len + lineStart : u32le, // discard lineEnd info, they usually don't make sense anyway + colStart : u16le, + fileIdx : u16le, +} + +read_cvsInlineSite :: proc(using this: ^BlocksReader, blockEnd: uint, $T: typeid) -> (ret: T) + where intrinsics.type_is_subtype_of(T, CvsInlineSite) { + ret._base = readv(this, type_of(ret._base)) + ret.lines = parse_binary_annotation(this, blockEnd) + return +} +// rerefence: https://github.com/willglynn/pdb/blob/5f07022b0188a4c9c39ff9d23270b1631c223631/src/symbol/annotations.rs +BinaryAnnotationOpcode :: enum u8 { + Eof = 0, + CodeOffset = 1, // set code offset + ChangeCodeOffsetBase = 2, // set code offset base, subsequent offset is relative + ChangeCodeOffset = 3, // delta offset, emitting + ChangeCodeLength = 4, // change current line record length. if not set, default to dif from next emitted recored + ChangeFile = 5, // change file index + ChangeLineOffset = 6, // offset line (i32) + ChangeLineEndDelta = 7, // set how many lines, default is 1 + ChangeRangeKind = 8, // isStatement(1). default is 1 + ChangeColumnStart = 9, // start column number, 0 means no column info. default 0 + ChangeColumnEndDelta = 10, // offset line end (i32) + ChangeCodeOffsetAndLineOffset = 11, // ((sourceDelta << 4) | CodeDelta). emitting + ChangeCodeLengthAndCodeOffset = 12, // (codeLength, codeOffset). emitting + ChangeColumnEnd = 13, // set end column number +} +parse_binary_annotation :: proc(using this: ^BlocksReader, blockEnd: uint) -> (ret: []CvsInlineUncompressedLine) { + baseOffset := this.offset + // pase 1: count emiiting + lineCount := 0 + for this.offset < blockEnd { + #partial switch BinaryAnnotationOpcode(uncompress_binary_annonation(this)) { + case .ChangeCodeOffset:fallthrough + case .ChangeCodeOffsetAndLineOffset:fallthrough + case .ChangeCodeLengthAndCodeOffset: + lineCount+=1 + case .Eof: break + } + } + //log.debug(lineCount) + this.offset = baseOffset + ret = make([]CvsInlineUncompressedLine, lineCount) + // pass 2: emit + lineCount = 0 + cbo :u32le=0 // code base offset + co :i32le=0 // code offset from base + fi :u32le=0 // file index + l :u32le=0 //? + c :u32le=1 + cl :u32le=0 + for this.offset < blockEnd { + opCode := cast(BinaryAnnotationOpcode)uncompress_binary_annonation(this) + //log.debug(opCode) + switch opCode { + case .Eof: break + case .CodeOffset: co = i32le(uncompress_binary_annonation(this)) + case .ChangeCodeOffsetBase: cbo = uncompress_binary_annonation(this) + case .ChangeCodeOffset: + co = (i32le(co) + i32le(uncompress_binary_annonation(this))) + case .ChangeFile: fi = uncompress_binary_annonation(this) + case .ChangeLineOffset: l = u32le(i32le(l)+uncompress_binary_annonation_signed(this)) + case .ChangeCodeLength: + ret[lineCount-1].end = uncompress_binary_annonation(this) + case .ChangeLineEndDelta:fallthrough // ignored + case .ChangeRangeKind:fallthrough // ignored + case .ChangeColumnEnd:fallthrough // ignored + case .ChangeColumnEndDelta: uncompress_binary_annonation(this) + case .ChangeColumnStart: c = uncompress_binary_annonation(this) + case .ChangeCodeOffsetAndLineOffset: + op := uncompress_binary_annonation(this) + co += i32le(op & 0xf) + l += op >> 4 + case .ChangeCodeLengthAndCodeOffset: + cl = uncompress_binary_annonation(this) // code len + co += i32le(uncompress_binary_annonation(this)) + case: assert(false) + } + #partial switch opCode { + case .ChangeCodeOffset:fallthrough + case .ChangeCodeOffsetAndLineOffset:fallthrough + case .ChangeCodeLengthAndCodeOffset: + ret[lineCount] = CvsInlineUncompressedLine{u32le(i32le(cbo)+co), cl, l,u16le(c), u16le(fi),} + c = 1 + cl = 0 + lineCount+=1 + } + } + // fix line lengths to line ends + for i in 0..<len(ret) { + if ret[i].end != 0 { + ret[i].end = ret[i].offset + ret[i].end + } else if i+1 < len(ret) { + ret[i].end = ret[i+1].offset + } else { + ret[i].end = ret[i].offset+1 //? + } + } + return +} +uncompress_binary_annonation :: proc(using this: ^BlocksReader) -> u32le { + b1 := u32le(readv(this, u8)) + if (b1 & 0x80) == 0 do return b1 + b2 := u32le(readv(this, u8)) + if (b1 & 0xc0) == 0x80 do return ((b1 & 0x3f) << 8) | b2 + b3 := u32le(readv(this, u8)) + b4 := u32le(readv(this, u8)) + return ((b1 & 0x1f) << 24) | (b2 << 16) | (b3 << 8) | b4 +} +uncompress_binary_annonation_signed :: proc(using this: ^BlocksReader) -> i32le { + u := uncompress_binary_annonation(this) + if (u&1) != 0 do return -i32le(u>>1) + return i32le(u>>1) +} + +// S_FILESTATIC +CvsFileStatic :: struct { + using _base : struct #packed { + typeind : TypeIndex, + modOffset : u32le, // index of mod filename in stringtable + flags : CvsLvarFlags, // local var flags + }, + name : string, +} + +// S_CALLEES, S_CALLERS +CvsFunctionList :: struct { + using _npm : MsfNotPackedMarker, + // count : u32le + funcs : []TypeIndex, +} +read_cvsFunctionList :: proc(this: ^BlocksReader, $T: typeid) -> (ret: T) + where intrinsics.type_is_subtype_of(T, CvsFunctionList) { + count := readv(this, u32le) + ret.funcs = read_packed_array(this, uint(count), TypeIndex) + return +} + +// S_HEAPALLOCSITE +CvsHeapAllocSite :: struct #packed { + offset : u32le, + sect : u16le, + instrLen : u16le, + typind : TypeIndex, +} + +// S_CONSTANT, S_MANCONSTANT +CvsConstant :: struct { + using _base : struct #packed { + typind : TypeIndex, + value : u16le, // numeric leaf containing value + }, + name : string, +} + +// S_UDT +CvsUDT :: struct { + using _base : struct #packed { + typind : TypeIndex, + }, + name : string, +} + +// S_DEFRANGE +CvsDefRange :: struct { + using _base : struct #packed { + program : u32le, // DIA program to evaluate the value of the symbol + }, + using _rag : CvsLvarAddrRangeAndGap, +} + +// S_DEFRANGE_SUBFIELD +CvsDefRangeSubfield :: struct { + using _base : struct #packed { + program : u32le, // DIA program to evaluate the value of the symbol + oParent : u32le, // offset in parent variable + }, + using _rag : CvsLvarAddrRangeAndGap, +} + +// S_DEFRANGE_REGISTER +CvsDefRangeRegister :: struct { + using _base : struct #packed { + reg : u16le, + attr : CvsRangeAttr, + }, + using _rag : CvsLvarAddrRangeAndGap, +} + +// S_DEFRANGE_SUBFIELD_REGISTER +CvsDefRangeSubfieldRegister :: struct { + using _base : struct #packed { + reg : u16le, + attr : CvsRangeAttr, + pOffset : u32le, // offset in parent variable, only lower 12 bits used + }, + using _rag : CvsLvarAddrRangeAndGap, +} + +// S_DEFRANGE_FRAMEPOINTER_REL +CvsDefRangeFramePointerRel :: struct { + using _base : CvsDefRangeFramePointerRelFullScope, + using _rag : CvsLvarAddrRangeAndGap, +} +// S_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE +CvsDefRangeFramePointerRelFullScope :: struct #packed { + oFramePtr : u32le, // offset to frame pointer +} + +// S_DEFRANGE_REGISTER_REL +CvsDefRangeRegisterRel :: struct { + using _base : struct #packed { + baseReg : u16le, // register to hold the base pointer + flags : CvsDefRangeRegisterRelFlags, + baseOffset : u32le, // offset to base pointer register + }, + using _rag : CvsLvarAddrRangeAndGap, +} +// TODO: methods to extract parent offset from this +// ------------|---|- +// offsetParent|pad|spilledUdtMember +CvsDefRangeRegisterRelFlags :: distinct u16le + +read_with_trailing_rag :: #force_inline proc(this: ^BlocksReader, recLen: u16le, $T : typeid) -> (ret: T) + where intrinsics.type_has_field(T, "_base"), + intrinsics.type_has_field(T, "_rag"), + intrinsics.type_field_index_of(T, "_rag") == 1, + intrinsics.type_struct_field_count(T) == 2 { + ret._base = readv(this, type_of(ret._base)) + ret._rag = read_cvsLvarAddrRangeAndGap(this, recLen, size_of(ret._base)) + return +} +CvsRangeAttr :: enum u16le { none = 0, maybe = 1, } +CvsLvarAddrRangeAndGap :: struct { + range : struct #packed { + offsetStart : u32le, + iSectStart : u16le, + length : u16le, + }, + gaps : []CvsLvarAddrGap, +} +CvsLvarAddrGap :: struct #packed { + relOffset : u16le, + length : u16le, +} +read_cvsLvarAddrRangeAndGap :: proc (this: ^BlocksReader, recLen : u16le, headLen: int) -> (ret : CvsLvarAddrRangeAndGap) { + ret.range = readv(this, type_of(ret.range)) + gapsSize := int(recLen) - size_of(CvsRecordKind) - headLen - size_of(ret.range) + gapsCount := gapsSize / size_of(CvsLvarAddrGap) + if gapsCount <= 0 do return + ret.gaps = read_packed_array(this, uint(gapsCount), CvsLvarAddrGap) + return +} + +CodeViewSymbol :: struct { + kind : CvsRecordKind, + value : union {CvsPub32, CvsRef2, CvsObjName, CvsThunk32,CvsLabel32, CvsBPRel32, CvsRegister, CvsCompile3, CvsBuildInfo, CvsData32, CvsLocal, CvsBlocks32, CvsFrameProc, CvsCompile2, CvsUnamespace, CvsTrampoline, CvsSection, CvsCoffGroup, CvsExport, CvsCallsiteInfo, CvsFrameCookie, CvsRegRel32, CvsEnvBlock, CvsInlineSite, CvsFileStatic,CvsFunctionList, CvsHeapAllocSite, CvsConstant, CvsUDT, CvsProc32, CvsEnd, CvsDefRange, CvsDefRangeSubfield, CvsDefRangeRegister, CvsDefRangeRegisterRel, CvsDefRangeSubfieldRegister, CvsDefRangeFramePointerRel, CvsDefRangeFramePointerRelFullScope, }, +} + +parse_cvs :: proc(this: ^BlocksReader, cvsHeader : CvsRecordHeader) -> (v: CodeViewSymbol) { + v.kind = cvsHeader.kind + switch cvsHeader.kind { + case .S_PUB32: + v.value = readv(this, CvsPub32) + case .S_PROCREF:fallthrough + case .S_DATAREF:fallthrough + case .S_LPROCREF: + v.value = readv(this, CvsRef2) + case .S_OBJNAME: + v.value = readv(this, CvsObjName) + case .S_THUNK32: + v.value = readv(this, CvsThunk32) + case .S_LABEL32: + v.value = readv(this, CvsLabel32) + case .S_BPREL32: + v.value = readv(this, CvsBPRel32) + case .S_REGISTER: + v.value = readv(this, CvsRegister) + case .S_COMPILE3: + v.value = readv(this, CvsCompile3) + case .S_BUILDINFO: + v.value = readv(this, CvsBuildInfo) + case .S_LDATA32:fallthrough + case .S_GDATA32:fallthrough + case .S_LMANDATA:fallthrough + case .S_GMANDATA:fallthrough + case .S_LTHREAD32:fallthrough + case .S_GTHREAD32: + v.value = readv(this, CvsData32) + case .S_LOCAL: + v.value = readv(this, CvsLocal) + case .S_BLOCK32: + v.value = readv(this, CvsBlocks32) + case .S_FRAMEPROC: + v.value = readv(this, CvsFrameProc) + case .S_COMPILE2: + v.value = readv(this, CvsCompile2) + case .S_UNAMESPACE: + v.value = readv(this, CvsUnamespace) + case .S_TRAMPOLINE: + v.value = readv(this, CvsTrampoline) + case .S_SECTION: + v.value = readv(this, CvsSection) + case .S_COFFGROUP: + v.value = readv(this, CvsCoffGroup) + case .S_EXPORT: + v.value = readv(this, CvsExport) + case .S_CALLSITEINFO: + v.value = readv(this, CvsCallsiteInfo) + case .S_FRAMECOOKIE: + v.value = readv(this, CvsFrameCookie) + case .S_REGREL32: + v.value = readv(this, CvsRegRel32) + case .S_ENVBLOCK: + v.value = readv(this, CvsEnvBlock) + case .S_INLINESITE: + v.value = readv(this, this.offset + uint(cvsHeader.length)-size_of(CvsRecordKind), CvsInlineSite) + case .S_FILESTATIC: + v.value = readv(this, CvsFileStatic) + case .S_CALLEES:fallthrough + case .S_CALLERS: + v.value = readv(this, CvsFunctionList) + case .S_HEAPALLOCSITE: + v.value = readv(this, CvsHeapAllocSite) + case .S_CONSTANT:fallthrough + case .S_MANCONSTANT: + v.value = readv(this, CvsConstant) + case .S_UDT: + v.value = readv(this, CvsUDT) + case .S_LPROC32:fallthrough + case .S_LPROC32_ID:fallthrough + case .S_LPROC32_DPC:fallthrough + case .S_LPROC32_DPC_ID:fallthrough + case .S_GPROC32:fallthrough + case .S_GPROC32_ID: + v.value = readv(this, CvsProc32) + case .S_INLINESITE_END:fallthrough + case .S_PROC_ID_END:fallthrough + case .S_END: + v.value = CvsEnd{} + case .S_DEFRANGE: + v.value = readv(this, cvsHeader.length, CvsDefRange) + case .S_DEFRANGE_SUBFIELD: + v.value = readv(this, cvsHeader.length, CvsDefRangeSubfield) + case .S_DEFRANGE_REGISTER: + v.value = readv(this, cvsHeader.length, CvsDefRangeRegister) + case .S_DEFRANGE_REGISTER_REL: + v.value = readv(this, cvsHeader.length, CvsDefRangeRegisterRel) + case .S_DEFRANGE_SUBFIELD_REGISTER: + v.value = readv(this, cvsHeader.length, CvsDefRangeSubfieldRegister) + case .S_DEFRANGE_FRAMEPOINTER_REL: + v.value = readv(this, cvsHeader.length, CvsDefRangeFramePointerRel) + case .S_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE: + v.value = readv(this, CvsDefRangeFramePointerRelFullScope) + case: + v.value = nil + } + return +} + +inspect_cvs :: proc(this: ^BlocksReader, cvsHeader : CvsRecordHeader) { + cvs := parse_cvs(this, cvsHeader) + log.debugf(".%v@[%v]: %v", cvsHeader.kind, this.offset, cvs.value) +} diff --git a/src/pdb/cvtRecord.odin b/src/pdb/cvtRecord.odin new file mode 100644 index 0000000..77777df --- /dev/null +++ b/src/pdb/cvtRecord.odin @@ -0,0 +1,627 @@ +//! CodeView Type records, reference: https://llvm.org/docs/PDB/CodeViewTypes.html +package pdb +import "core:log" +import "core:intrinsics" + +// | Unused | Mode | Kind | +// |+32 |+12 |+8 |+0 +TypeIndex :: distinct u32le +CvItemId :: TypeIndex //? Item Id is a stricter typeindex which may referenced from symbol stream. +get_type_kind :: #force_inline proc(this: TypeIndex) -> TypeIndex_Kind { + bits := u32le(this) + bits = bits & ((1 << 9) - 1) + return TypeIndex_Kind(bits) +} + +get_type_mode :: #force_inline proc(this: TypeIndex) -> TypeIndex_Mode { + bits := u32le(this) + bits = (bits>>8) & ((1 << 5) - 1) + return TypeIndex_Mode(bits) +} + +TypeIndex_Kind :: enum u32le { + None = 0x0000, // uncharacterized type (no type) + Void = 0x0003, // void + NotTranslated = 0x0007, // type not translated by cvpack + HResult = 0x0008, // OLE/COM HRESULT + + SignedCharacter = 0x0010, // 8 bit signed + UnsignedCharacter = 0x0020, // 8 bit unsigned + NarrowCharacter = 0x0070, // really a char + WideCharacter = 0x0071, // wide char + Character16 = 0x007a, // char16_t + Character32 = 0x007b, // char32_t + Character8 = 0x007c, // char8_t + + SByte = 0x0068, // 8 bit signed int + Byte = 0x0069, // 8 bit unsigned int + Int16Short = 0x0011, // 16 bit signed + UInt16Short = 0x0021, // 16 bit unsigned + Int16 = 0x0072, // 16 bit signed int + UInt16 = 0x0073, // 16 bit unsigned int + Int32Long = 0x0012, // 32 bit signed + UInt32Long = 0x0022, // 32 bit unsigned + Int32 = 0x0074, // 32 bit signed int + UInt32 = 0x0075, // 32 bit unsigned int + Int64Quad = 0x0013, // 64 bit signed + UInt64Quad = 0x0023, // 64 bit unsigned + Int64 = 0x0076, // 64 bit signed int + UInt64 = 0x0077, // 64 bit unsigned int + Int128Oct = 0x0014, // 128 bit signed int + UInt128Oct = 0x0024, // 128 bit unsigned int + Int128 = 0x0078, // 128 bit signed int + UInt128 = 0x0079, // 128 bit unsigned int + + Float16 = 0x0046, // 16 bit real + Float32 = 0x0040, // 32 bit real + Float32PartialPrecision = 0x0045, // 32 bit PP real + Float48 = 0x0044, // 48 bit real + Float64 = 0x0041, // 64 bit real + Float80 = 0x0042, // 80 bit real + Float128 = 0x0043, // 128 bit real + + Complex16 = 0x0056, // 16 bit complex + Complex32 = 0x0050, // 32 bit complex + Complex32PartialPrecision = 0x0055, // 32 bit PP complex + Complex48 = 0x0054, // 48 bit complex + Complex64 = 0x0051, // 64 bit complex + Complex80 = 0x0052, // 80 bit complex + Complex128 = 0x0053, // 128 bit complex + + Boolean8 = 0x0030, // 8 bit boolean + Boolean16 = 0x0031, // 16 bit boolean + Boolean32 = 0x0032, // 32 bit boolean + Boolean64 = 0x0033, // 64 bit boolean + Boolean128 = 0x0034, // 128 bit boolean +}; + +TypeIndex_Mode :: enum u32le { + Direct = 0, // Not a pointer + NearPointer = 1, // Near pointer + FarPointer = 2, // Far pointer + HugePointer = 3, // Huge pointer + NearPointer32 = 4, // 32 bit near pointer + FarPointer32 = 5, // 32 bit far pointer + NearPointer64 = 6, // 64 bit near pointer + NearPointer128 = 7,// 128 bit near pointer +}; + +CvtRecordHeader :: struct #packed { + length : u16le, // record length excluding this 2 byte field + kind : CvtRecordKind, +} + +CvtRecordKind :: enum u16le { + LF_POINTER = 0x1002, // CvtPointer + LF_MODIFIER = 0x1001, // CvtModifier + LF_PROCEDURE = 0x1008, // CvtProc + LF_MFUNCTION = 0x1009, + LF_LABEL = 0x000e, + LF_ARGLIST = 0x1201, // CvtArgs + LF_FIELDLIST = 0x1203, // TODO: proper child enumeration + LF_ARRAY = 0x1503, // CvtArray + LF_CLASS = 0x1504, // CvtStruct + LF_STRUCTURE = 0x1505, // CvtStruct + LF_INTERFACE = 0x1519, // CvtStruct + LF_UNION = 0x1506, + LF_ENUM = 0x1507, // CvtEnum + LF_TYPESERVER2 = 0x1515, + LF_VFTABLE = 0x151d, + LF_VTSHAPE = 0x000a, + LF_BITFIELD = 0x1205, + LF_FUNC_ID = 0x1601, + LF_MFUNC_ID = 0x1602, + LF_BUILDINFO = 0x1603, + LF_SUBSTR_LIST = 0x1604, + LF_STRING_ID = 0x1605, + LF_UDT_SRC_LINE = 0x1606, + LF_UDT_MOD_SRC_LINE = 0x1607, + LF_METHODLIST = 0x1206, + LF_PRECOMP = 0x1509, + LF_ENDPRECOMP = 0x0014, + //==== member records, dont describe length + LF_BCLASS = 0x1400, + LF_BINTERFACE = 0x151a, + LF_VBCLASS = 0x1401, + LF_IVBCLASS = 0x1402, + LF_VFUNCTAB = 0x1409, + LF_STMEMBER = 0x150e, + LF_METHOD = 0x150f, + LF_MEMBER = 0x150d, + LF_NESTTYPE = 0x1510, + LF_ONEMETHOD = 0x1511, + LF_ENUMERATE = 0x1502, + LF_INDEX = 0x1404, + //==== numeric records + 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, + LF_OCTWORD = 0x8017, + LF_UOCTWORD = 0x8018, + LF_DECIMAL = 0x8019, + LF_DATE = 0x801a, + LF_UTF8STRING = 0x801b, + LF_REAL16 = 0x801c, + //==== padding records + LF_PAD0 = 0xf0, + LF_PAD1 = 0xf1, + LF_PAD2 = 0xf2, + LF_PAD3 = 0xf3, + LF_PAD4 = 0xf4, + LF_PAD5 = 0xf5, + LF_PAD6 = 0xf6, + LF_PAD7 = 0xf7, + LF_PAD8 = 0xf8, + LF_PAD9 = 0xf9, + LF_PAD10 = 0xfa, + LF_PAD11 = 0xfb, + LF_PAD12 = 0xfc, + LF_PAD13 = 0xfd, + LF_PAD14 = 0xfe, + LF_PAD15 = 0xff, +} + +read_int_record :: proc(this: ^BlocksReader) -> i128le { + numKind := readv(this, CvtRecordKind) + #partial switch numKind { + case .LF_CHAR: return cast(i128le)readv(this, i8) + case .LF_SHORT: return cast(i128le)readv(this, i16le) + case .LF_USHORT: return cast(i128le)readv(this, u16le) + case .LF_LONG: return cast(i128le)readv(this, i32le) + case .LF_ULONG: return cast(i128le)readv(this, u32le) + case .LF_QUADWORD: return cast(i128le)readv(this, i64le) + case .LF_UQUADWORD: return cast(i128le)readv(this, u64le) + case .LF_OCTWORD: return readv(this, i128le) + case: { + if uint(numKind) < uint(CvtRecordKind.LF_NUMERIC) { + return i128le(numKind) + } + log.errorf("unsupported record type:%v", numKind) + assert(false, "unsupported record type. should be a non-overflow integer") + } + } + return -1 +} + +//====type record for LF_MODIFIER +CvtModifier :: struct #packed { + modifiedType : TypeIndex, + attrs : CvtModifier_Type, +} +CvtModifier_Type :: enum u16le { + None = 0, + Const = 1, + Volatile = 2, + Unaligned = 4, +} + +//====type record for LF_POINTER +// Note that “plain” pointers to primitive types are not represented by LF_POINTER records, they are indicated by special reserved TypeIndex values. +CvtPointer :: struct #packed { + referentType: TypeIndex, + attributes : CvtPointer_Attrs, +} + +// bit field +// | Flags | Size | Modifiers | Mode | Kind | +// +0x16 +0x13 +0xD +0x8 +0x5 +0x0 +CvtPointer_Attrs :: distinct u32le + +CvtPointer_Kind :: enum u8 { + Near16 = 0x00, // 16 bit pointer + Far16 = 0x01, // 16:16 far pointer + Huge16 = 0x02, // 16:16 huge pointer + BasedOnSegment = 0x03, // based on segment + BasedOnValue = 0x04, // based on value of base + BasedOnSegmentValue = 0x05, // based on segment value of base + BasedOnAddress = 0x06, // based on address of base + BasedOnSegmentAddress = 0x07, // based on segment address of base + BasedOnType = 0x08, // based on type + BasedOnSelf = 0x09, // based on self + Near32 = 0x0a, // 32 bit pointer + Far32 = 0x0b, // 16:32 pointer + Near64 = 0x0c, // 64 bit pointer +}; + +CvtPointer_Mode :: enum u8 { + Pointer = 0x00, // "normal" pointer + LValueReference = 0x01, // "old" reference + PointerToDataMember = 0x02, // pointer to data member + PointerToMemberFunction = 0x03, // pointer to member function + RValueReference = 0x04, // r-value reference +}; + +CvtPointer_Modifiers :: enum u8 { + None = 0x00, // "normal" pointer + Flat32 = 0x01, // "flat" pointer + Volatile = 0x02, // marked volatile + Const = 0x04, // marked const + Unaligned = 0x08, // marked unaligned + Restrict = 0x10, // marked restrict +}; + +CvtPointer_Flags :: enum u8 { + WinRTSmartPointer = 0x01, // a WinRT smart pointer + LValueRefThisPointer = 0x02, // 'this' pointer of a member function with ref qualifier (e.g. void X::foo() &) + RValueRefThisPointer = 0x04, // 'this' pointer of a member function with ref qualifier (e.g. void X::foo() &&) +}; + +// TODO: member point info +//====type record for LF_BITFIELD +CvtBitfield :: struct #packed { + utype : TypeIndex, + length: u8, + pos : u8, +} +//====type record for LF_STRING_ID +CvtStringId :: struct { + using _base : struct #packed { + id : CvItemId, // ID to list of sub string IDs + }, + name : string, +} + +//====type record for LF_FUNC_ID +CvtFuncId :: struct { + using _base : struct #packed { + scopeId : CvItemId, // parrent scope of the ID, 0 if global + funcType : TypeIndex, + }, + name : string, +} + +//====type record for LF_MFUNC_ID +CvtMfuncId :: struct { + using _base : struct #packed { + parent : TypeIndex, + funcType : TypeIndex, + }, + name : string, +} + +//====type record for LF_UDT_MOD_SRC_LINE +CvtUdtModSrcLine :: struct #packed { + udtType : TypeIndex, + src : CvItemId, // index into string table of src filename + line : u32le, + imod : u16le, // module that contributes this udt def +} +//====type record for LF_BUILDINFO +CvtBuildInfo :: struct { + using _npm : MsfNotPackedMarker, + //count : u16le, + args : []CvItemId, +} +read_cvtBuildInfo :: proc(this: ^BlocksReader, $T: typeid) -> (ret: T) where intrinsics.type_is_subtype_of(T, CvtBuildInfo) { + argCount := readv(this, u16le) + ret.args = read_packed_array(this, uint(argCount), CvItemId) + return +} + +//====type record for LF_PROCEDURE +CvtProc :: struct #packed { + retType : TypeIndex, + callType : u8, //? calling convention + attrs : CvtProc_Attribute, + paramCount : u16le, + argList : TypeIndex, +} +CvtProc_Attribute :: enum u8 { + None = 0, + CxxReturnUdt = 1 << 0, + Ctor = 1 << 1, + Ctorvbase = 1 << 2, +} + +//====type record for LF_MFUNCTION +CvtMFunction :: struct #packed { + retType : TypeIndex, + classType : TypeIndex, // containing class + thisType : TypeIndex, // this pointer type (model specific) + callType : u8, //? calling convention + attrs : CvtProc_Attribute, + paramCount: u16le, + argList : TypeIndex, + thisAdjust: u32le, +} + +//====type record for LF_ARGLIST, LF_SUBSTR_LIST +CvtProc_ArgList :: struct { + using _npm : MsfNotPackedMarker, + //count : u32le, + args : []TypeIndex, +} +read_cvtfArgList :: proc(this: ^BlocksReader, $T: typeid) -> (ret: T) where intrinsics.type_is_subtype_of(T, CvtProc_ArgList) { + argCount := readv(this, u32le) + ret.args = read_packed_array(this, uint(argCount), TypeIndex) + return +} + +//====type record for LF_FIELDLIST, a collection of sub fields. +CvtFieldList :: struct { + using _npm : MsfNotPackedMarker, + fields : []CvtField, +} +CvtField :: struct { + kind : CvtRecordKind, + value : union { CvtField_BClass, CvtField_Vbclass, CvtField_Member, CvtField_Enumerate, }, +} +read_cvtFieldList :: proc(this: ^BlocksReader, endOffset: uint, $T: typeid) -> (ret: T) + where intrinsics.type_is_subtype_of(T, CvtFieldList) { + defer this.offset = endOffset + stack := make_stack(CvtField, int(endOffset-this.offset)*2/size_of(CvtField), context.temp_allocator); defer delete_stack(&stack) + for this.offset < endOffset { + for ;this.offset<endOffset;this.offset+=1 { + if this.data[this.offset] < u8(CvtRecordKind.LF_PAD0) { + break + } + } + f : CvtField + f.kind = readv(this, CvtRecordKind) + #partial switch f.kind { + case .LF_BCLASS: { + f.value = readv(this, CvtField_BClass) + } + case .LF_VBCLASS:fallthrough + case .LF_IVBCLASS: { + f.value = readv(this, CvtField_Vbclass) + } + case .LF_MEMBER: { + f.value = readv(this, CvtField_Member) + } + case .LF_ENUMERATE: { + f.value = readv(this, CvtField_Enumerate) + } + case: { //? + log.debugf("unrecognized: %v", f.kind) + } + } + push(&stack, f) + } + ret.fields = make_slice_clone_from_stack(&stack) + return +} +//====sub LF_BCLASS +CvtField_BClass :: struct { + using _npm : MsfNotPackedMarker, + attr : CvtField_Attribute, + baseType: TypeIndex, // type index of the base class + offset : uint, // offset of base within class, stored as a LF_NUMERIC +} +read_cvtfBclass :: proc(this: ^BlocksReader, $T: typeid) -> (ret: T) + where intrinsics.type_is_subtype_of(T, CvtField_BClass) { + ret.attr = readv(this, CvtField_Attribute) + ret.baseType = readv(this, TypeIndex) + ret.offset = cast(uint)read_int_record(this) + return +} +//====sub LF_VBCLASS|LF_IVBCLASS +CvtField_Vbclass :: struct { + using _npm : MsfNotPackedMarker, + attr : CvtField_Attribute, + baseType: TypeIndex, + vbptr : TypeIndex, + vbpo : uint, // virtual base pointer offset from address pointer + vbo : uint, // virutal base offset from vbtable +} +read_cvtfVbclass :: proc(this: ^BlocksReader, $T: typeid) -> (ret: T) where intrinsics.type_is_subtype_of(T, CvtField_Vbclass) { + ret.attr = readv(this, CvtField_Attribute) + ret.baseType = readv(this, TypeIndex) + ret.vbptr = readv(this, TypeIndex) + ret.vbpo = cast(uint)read_int_record(this) + ret.vbo = cast(uint)read_int_record(this) + return +} +//====sub LF_MEMBER +CvtField_Member :: struct { + using _npm : MsfNotPackedMarker, + attr : CvtField_Attribute, + memType : TypeIndex, + offset : uint, + name : string, +} +read_cvtfMember :: proc(this: ^BlocksReader, $T: typeid) -> (ret: T) where intrinsics.type_is_subtype_of(T, CvtField_Member) { + ret.attr = readv(this, CvtField_Attribute) + ret.memType = readv(this, TypeIndex) + ret.offset = cast(uint)read_int_record(this) + ret.name = read_length_prefixed_name(this) + return +} +//====sub LF_ENUMERATE +CvtField_Enumerate :: struct { + using _npm : MsfNotPackedMarker, + attr : CvtField_Attribute, + value: uint, //? overflow? + name : string, +} +read_cvtfEnumerate :: proc(this: ^BlocksReader, $T: typeid) -> (ret: T) where intrinsics.type_is_subtype_of(T, CvtField_Enumerate) { + ret.attr = readv(this, CvtField_Attribute) + ret.value = cast(uint)read_int_record(this) + ret.name = read_length_prefixed_name(this) + return +} +//LF_PADs + +//====type record for LF_ARRAY +CvtArray :: struct { + using _base : struct #packed { + elemType : TypeIndex, + idxType : TypeIndex, + }, + size : uint, + name : string, +} + +//====type record for LF_UNION +CvtUnion :: struct { + using _npm : MsfNotPackedMarker, + elemCount : u16le, + props : CvtStruct_Prop, + field : TypeIndex, // LF_FIELD descriptor list + size : uint, + name : string, +} +read_cvtUnion :: proc(this: ^BlocksReader, $T: typeid) -> (ret: T) where intrinsics.type_is_subtype_of(T, CvtUnion) { + ret.elemCount = readv(this, u16le) + ret.props = readv(this, CvtStruct_Prop) + ret.field = readv(this, TypeIndex) + ret.size = cast(uint)read_int_record(this) + ret.name = read_length_prefixed_name(this) + return +} + +//====type record for LF_CLASS, LF_STRUCTURE, LF_INTERFACE +// followed by data describing length of structure in bytes and name +CvtStruct :: struct { + using _base : struct #packed { + elemCount : u16le, + props : CvtStruct_Prop, + field : TypeIndex, // LF_FIELD descriptor list + derivedFrom : TypeIndex, // derived from list if not zero + vshape : TypeIndex, // vshape table + }, + size : uint, + name : string, +} + +CvtStruct_Prop :: enum u16le { + None = 0, + Packed = 1 << 0, + Ctor = 1 << 1, + Ovlops = 1 << 2, + IsNested = 1 << 3, + Cnested = 1 << 4, + OpAssign = 1 << 5, + OpCast = 1 << 6, + FwdRef = 1 << 7, + Scoped = 1 << 8, + HasUniqueueName = 1 << 9, + Sealed = 1 << 10, + HFA_Float = u16le(CvtStruct_HFA.Float) << 11, + HFA_Double = u16le(CvtStruct_HFA.Double) << 11, + HFA_Other = u16le(CvtStruct_HFA.Other) << 11, + Intrinsics = 1 << 13, + Mocom_Ref = u16le(CvtStruct_MoCOM_UDT.Ref) << 14, + Mocom_Value = u16le(CvtStruct_MoCOM_UDT.Value) << 14, + Mocom_Interface = u16le(CvtStruct_MoCOM_UDT.Interface) << 14, +} +CvtStruct_HFA :: enum u16le { + None=0, Float=1, Double=2, Other=3, +} +CvtStruct_MoCOM_UDT :: enum u16le { + None, Ref, Value, Interface, +} + +//====type record for LF_ENUM +CvtEnum :: struct { + using _base : struct #packed { + elemCount : u16le, + props : CvtStruct_Prop, + underlyType : TypeIndex, + fieldList : TypeIndex, // type index into the LF_FIELD descriptor list + }, + name : string, +} + +CvtField_Attribute :: enum u16le { + None = 0, + Access_Private = 1, + Access_Protected = 2, + Access_Public = 3, + Mprop_Virtual = u16le(CvtMethodProp.Virtual) << 2, + Mprop_Static = u16le(CvtMethodProp.Static) << 2, + Mprop_Friend = u16le(CvtMethodProp.Friend) << 2, + Mprop_Intro = u16le(CvtMethodProp.Intro) << 2, + Mprop_PureVirt = u16le(CvtMethodProp.PureVirt) << 2, + Mprop_PureIntro = u16le(CvtMethodProp.PureIntro) << 2, + Pseudo = 1<<5, // compiler generated function, doesn't exist + NoInherit = 1<<6, // class cannot be inherited + NoConstruct = 1<<7, // class cannot be constructed + CompGenx = 1<<8, // compiler genertaed function, do exist + Sealed = 1<<9, // function cannot be overriden +} +CvtAccess :: enum u8 { + Private = 1, + Protected = 2, + Public = 3, +} +CvtMethodProp :: enum u8 { + Vanilla = 0x00, + Virtual = 0x01, + Static = 0x02, + Friend = 0x03, + Intro = 0x04, + PureVirt = 0x05, + PureIntro = 0x06, +} + +CodeViewType :: struct { + kind : CvtRecordKind, + value : union {CvtPointer, CvtProc, CvtProc_ArgList, CvtStruct, CvtEnum, CvtArray, CvtUnion, CvtModifier, CvtMFunction, CvtBitfield, CvtStringId, CvtFuncId, CvtMfuncId, CvtUdtModSrcLine, CvtBuildInfo, CvtFieldList, }, +} + +parse_cvt :: proc(this: ^BlocksReader, cvtHeader : CvtRecordHeader) -> (v: CodeViewType) { + v.kind = cvtHeader.kind + #partial switch cvtHeader.kind { + case .LF_POINTER: + v.value = readv(this, CvtPointer) + case .LF_PROCEDURE: + v.value = readv(this, CvtProc) + case .LF_ARGLIST:fallthrough + case .LF_SUBSTR_LIST: + v.value = readv(this, CvtProc_ArgList) + case .LF_CLASS:fallthrough + case .LF_STRUCTURE:fallthrough + case .LF_INTERFACE: + v.value = readv(this, CvtStruct) + case .LF_ENUM: + v.value = readv(this, CvtEnum) + case .LF_ARRAY: + v.value = readv(this, CvtArray) + case .LF_UNION: + v.value = readv(this, CvtUnion) + case .LF_MODIFIER: + v.value = readv(this, CvtModifier) + case .LF_MFUNCTION: + v.value = readv(this, CvtMFunction) + case .LF_BITFIELD: + v.value = readv(this, CvtBitfield) + case .LF_STRING_ID: + v.value = readv(this, CvtStringId) + case .LF_FUNC_ID: + v.value = readv(this, CvtFuncId) + case .LF_MFUNC_ID: + v.value = readv(this, CvtMfuncId) + case .LF_UDT_MOD_SRC_LINE: + v.value = readv(this, CvtUdtModSrcLine) + case .LF_BUILDINFO: + v.value = readv(this, CvtBuildInfo) + case .LF_FIELDLIST: + endOffset := this.offset + uint(cvtHeader.length) - size_of(CvtRecordKind) + v.value = readv(this, endOffset, CvtFieldList) + // case .LF_VTSHAPE: // TODO: + // case .LF_METHODLIST: //? + case: log.debugf("Unhandled %v", cvtHeader.kind) + } + return +} + +inspect_cvt :: proc(this: ^BlocksReader, cvtHeader : CvtRecordHeader) { + v := parse_cvt(this, cvtHeader) + log.debug(v) +} diff --git a/src/pdb/dbiStream.odin b/src/pdb/dbiStream.odin new file mode 100644 index 0000000..e9bc44b --- /dev/null +++ b/src/pdb/dbiStream.odin @@ -0,0 +1,351 @@ +//! DBI Debug Info Stream reference: https://llvm.org/docs/PDB/DbiStream.html +package pdb +import "core:log" +import "core:intrinsics" + +DbiStream_Index :MsfStreamIdx: 3 + +DbiStreamHeader :: struct #packed { + versionSignature : i32le, + versionHeader : DbiStreamVersion, + age : u32le, + globalStreamIndex : MsfStreamIdx, + buildNumber : DbiBuildNumber, + publicStreamIndex : MsfStreamIdx, + pdbDllVersion : u16le, + symRecordStream : u16le, + pdbDllRbld : u16le, + modInfoSize : i32le, + secContributionSize : i32le, + secMapSize : i32le, + srcInfoSize : i32le, + typeServerMapSize : i32le, + mfcTypeServerIndex : u32le, + optDbgHeaderSize : i32le, + ecSubstreamSize : i32le, + flags : DbiFlags, + machine : u16le, // Image File Machine Constants from https://docs.microsoft.com/en-us/windows/win32/sysinfo/image-file-machine-constants + padding : u32le, +} +DbiStreamVersion :: enum u32le { + VC41 = 930803, + V50 = 19960307, + V60 = 19970606, + V70 = 19990903, + V110 = 20091201, +} +//-|-------|--------| +//+15 +8 +0 +//isNew majorVer minorVer +DbiBuildNumber :: distinct u16le +@private +_is_new_version_format :: #force_inline proc (b: DbiBuildNumber) -> bool { + return (b & (1<<15)) != 0 +} + +DbiFlags :: enum u16le { + None = 0x0, + WasIncrenetallyLinked = 1 << 0, + ArePrivateSymbolsStripped = 1 << 1, + HasConflictingTypes = 1 << 2, +} + +//==== Module Info Substream, array of DbiModInfos. +DbiModInfo :: struct { + // using _npm : MsfNotPackedMarker, + using _base : struct #packed { + unused1 : u32le, + sectionContribution : DbiSectionContribution, + flags : DbiModInfo_Flags, + moduleSymStream : MsfStreamIdx, // index of the stream.. + symByteSize : u32le, + c11ByteSize : u32le, + c13ByteSize : u32le, + sourceFileCount : u16le, + padding : u16le, + unused2 : u32le, + sourceFileNameIndex : u32le, + pdbFilePathNameIndex: u32le, + }, + moduleName : string, + objFileName : string, +} +DbiModInfo_Flags :: enum u16le { + None = 0, + Dirty = 1 << 0, + EC = 1 << 1, // edit and continue + // TODO: TypeServerIndex stuff in high8 +} +read_dbiModInfo :: proc(this: ^BlocksReader, $T: typeid) -> (ret: T) where intrinsics.type_is_subtype_of(T, DbiModInfo) { + ret._base = readv(this, type_of(ret._base)) + ret.moduleName = read_length_prefixed_name(this) + ret.objFileName = read_length_prefixed_name(this) + // align to 4-byte boundary + this.offset = (this.offset + 3) & ~uint(3) + return ret +} + +// section contribution sub stream seems to be nicely sorted by section index and offset +// based on function rva, we can bisearch this array to locate the module. +DbiSecContrVersion :: enum u32le { + Ver60 = 0xeffe0000 + 19970605, + V2 = 0xeffe0000 + 20140516, +} +DbiSectionContribution :: struct #packed { + section : u16le, + padding1 : u16le, + offset : u32le, + size : u32le, + characteristics : u32le, + moduleIndex : u16le, // index into the module substream + padding2 : u16le, + dataCrc : u32le, + relocCrc : u32le, +} +DbiSectionContribution2 :: struct #packed { + using sc : DbiSectionContribution, + iSectCoff : u32le, +} + +DbiSecMapHeader :: struct #packed { + count : u16le, // segment descriptors + logCount: u16le, // logival segment descriptors +} +DbiSecMapEntry :: struct #packed { + flags : DbiSecMapEntryFlags, + ovl : u16le, + group : u16le, + frame : u16le, + sectionName : u16le, + className : u16le, + offset : u32le, + sectionLength : u32le, +} +DbiSecMapEntryFlags :: enum u16le { + None = 0, + Read = 1 << 0, + Write = 1 << 1, + Execute = 1 << 2, + AddressIs32Bit = 1 << 3, + IsSelector = 1 << 8, + IsAbsoluteAddress = 1 << 9, + IsGroup = 1 << 10, +} + +DbiFileInfos :: struct { + using _npm : MsfNotPackedMarker, + // numModules :: u16le + // numSourceFiles :: u16le + // modIndices : []u16le, // len==numModules + modFileCounts : []u16le, // len==numModules + srcFileNames : []string, // fileNameOffsets : []u32le, + // namesBuffer : []string, +} +read_dbiFileInfos :: proc(this: ^BlocksReader, $T: typeid) -> (ret: T) where intrinsics.type_is_subtype_of(T, DbiFileInfos) { + moduleCount := readv(this, u16le) + readv(this, u16le) // ignored invalid src count + //log.debugf("Module count: %v", moduleCount) + this.offset += size_of(u16le) * uint(moduleCount) // skip unused mod indices + ret.modFileCounts = make([]u16le, cast(int)moduleCount) + srcFileSum :uint=0 + for i in 0..<len(ret.modFileCounts) { + ret.modFileCounts[i] = readv(this, u16le) + srcFileSum += uint(ret.modFileCounts[i]) + } + //log.debugf("Src File count: %v", srcFileSum) + nameMap := make(map[u32le]string, srcFileSum, context.temp_allocator) + defer delete(nameMap) + ret.srcFileNames = make([]string, srcFileSum) + nameBufOffset := this.offset + size_of(u32le) * srcFileSum + for i in 0..<srcFileSum { + baseOffset := this.offset + defer this.offset = baseOffset + size_of(u32le) + nameOffset := readv(this, u32le) + existingName, nameExist := nameMap[nameOffset] + if nameExist { + ret.srcFileNames[i] = existingName + } else { + this.offset = nameBufOffset + uint(nameOffset) + ret.srcFileNames[i] = read_length_prefixed_name(this) + nameMap[nameOffset] = ret.srcFileNames[i] + } + + } + return +} + +// TODO: Type Server Map Substream, EC Substream + +// Optional Debug Header Streams +DbiOptDbgHeaders :: struct #packed { + framePointerOmission: MsfStreamIdx, + exception : MsfStreamIdx, + fixup : MsfStreamIdx, + omapToSrc : MsfStreamIdx, // []PEOMapRecord + omapFromSrc : MsfStreamIdx, // []PEOMapRecord + sectionHeader : MsfStreamIdx, // []PESectionHeader in pdb (or original if identical) + tokenToRID : MsfStreamIdx, + xdata : MsfStreamIdx, + pdata : MsfStreamIdx, + newFPO : MsfStreamIdx, + oSectionHeader : MsfStreamIdx, // []PESectionHeader in original file (if not identical) +} + +// data that's essential for sourcecode lookup +SlimDbiData :: struct { + modules : []SlimDbiMod, + contributions : []SlimDbiSecContr, // sorted by section/offset, deduplicated on module + sections : []SlimDbiSecInfo, + // TODO: omap lookup for rva transition +} +@private +_cmp_sc :: #force_inline proc(l, r: SlimDbiSecContr) -> int { + if l.secIdx < r.secIdx do return -1 + else if l.secIdx > r.secIdx do return 1 + else if l.offset < r.offset do return -1 + else if l.offset > r.offset do return 1 + return 0 +} +search_for_section_contribution :: proc(using this: ^SlimDbiData, imgRva : u32le) -> (sci : int) { + sectionIdx := -1 + offsetInSection :u32le= 0 + for section, i in sections { + if imgRva >= section.vAddr && imgRva < section.vAddr + section.vSize { + sectionIdx = i + //log.debug(section) + offsetInSection = imgRva - section.vAddr + break + } + } + if sectionIdx == -1 do return -1 + // bisearch for module + ti := SlimDbiSecContr{PESectionOffset{offsetInSection, u16le(sectionIdx+1),}, 0} + lo, hi := 0, (len(contributions)-1) + if hi < 0 || _cmp_sc(ti, contributions[lo]) < 0 do return -1 + if _cmp_sc(contributions[hi], ti) < 0 do return int(hi) + // ti in range, do a bisearch + for lo <= hi { + mid := lo + ((hi-lo)>>1) + mv := _cmp_sc(ti, contributions[mid]) + if mv == 0 { + lo = mid + 1 + break + } + else if mv < 0 do hi = mid - 1 + else do lo = mid + 1 + } + if lo > 0 do lo -= 1 + return int(lo) +} + +SlimDbiMod :: struct { + moduleName : string, + objFileName : string, + symByteSize : u32le, + c11ByteSize : u32le, + c13ByteSize : u32le, + secContrOffset : PESectionOffset, // pdb section offset of its first section contribution. this is useful for module address hashing + moduleSymStream : MsfStreamIdx, +} +SlimDbiSecContr :: struct { + using _secOffset : PESectionOffset, + module : u16le, // 0-indexed module id into []modules +} +SlimDbiSecInfo :: struct { + name : PESectionName, + vSize : u32le, + vAddr : u32le, +} + +parse_dbi_stream :: proc(streamDir: ^StreamDirectory) -> (ret : SlimDbiData) { + dbiSr := get_stream_reader(streamDir, DbiStream_Index) + this := &dbiSr + header := readv(this, DbiStreamHeader) + if header.versionSignature != -1 { + log.warnf("unrecognized dbiVersionSignature: %v", header.versionSignature) + } + if header.versionHeader != .V70 { + log.warnf("unrecognized dbiVersionHeader: %v", header.versionHeader) + } + if !_is_new_version_format(header.buildNumber) { + log.warnf("unrecognized old dbiBuildNumber: %v", header.buildNumber) + } + { // Module Info substream + stack := make_stack(SlimDbiMod, int(header.modInfoSize / size_of(DbiModInfo)), context.temp_allocator) + defer delete_stack(&stack) + substreamEnd := uint(header.modInfoSize) + this.offset + defer assert(this.offset == substreamEnd) + for this.offset < substreamEnd { + modi := readv(this, DbiModInfo) + push(&stack, SlimDbiMod{modi.moduleName, modi.objFileName, modi.symByteSize, modi.c11ByteSize, modi.c13ByteSize, PESectionOffset{offset = modi.sectionContribution.offset, secIdx = modi.sectionContribution.section,}, modi.moduleSymStream}) + } + if stack.count > 0 { + ret.modules = make_slice_clone_from_stack(&stack) + } + } + { // section contribution substream + substreamEnd := uint(header.secContributionSize) + this.offset + defer assert(this.offset == substreamEnd) + secContrSubstreamVersion := readv(this, DbiSecContrVersion) + //log.debug(secContrSubstreamVersion) + secContrEntrySize := size_of(DbiSectionContribution) + switch secContrSubstreamVersion { + case .Ver60: + case .V2: secContrEntrySize = size_of(DbiSectionContribution2) + case: assert(false, "Invalid DbiSecContrVersion") + } + itemCount := (substreamEnd - this.offset) / uint(secContrEntrySize) + assert(itemCount > 0) + lastItem : SlimDbiSecContr + { + baseOffset := this.offset + defer this.offset = baseOffset + cast(uint)secContrEntrySize + secContr := readv(this, DbiSectionContribution) + lastItem = SlimDbiSecContr{ + PESectionOffset{secContr.offset, secContr.section,}, secContr.moduleIndex, + } + } + stack := make_stack(SlimDbiSecContr, int(itemCount), context.temp_allocator) + defer delete_stack(&stack) + for this.offset < substreamEnd { + baseOffset := this.offset + defer this.offset = baseOffset + cast(uint)secContrEntrySize + secContr := readv(this, DbiSectionContribution) + if secContr.size == 0 do continue + //log.debug(secContr) + assert(secContr.section > lastItem.secIdx || secContr.offset > lastItem.offset) + if secContr.section != lastItem.secIdx || secContr.moduleIndex != lastItem.module { + // new + push(&stack, lastItem) + lastItem = SlimDbiSecContr{ + {secContr.offset, secContr.section,}, secContr.moduleIndex, + } + } else { + // cumulate + lastItem.offset = secContr.offset + } + } + push(&stack, lastItem) + ret.contributions = make_slice_clone_from_stack(&stack) + } + // skip irrelevant streams + this.offset += uint(header.secMapSize) + this.offset += uint(header.srcInfoSize) + this.offset += uint(header.typeServerMapSize) + this.offset += uint(header.ecSubstreamSize) + optDbgHeaders := readv(this, DbiOptDbgHeaders) + if stream_idx_valid(optDbgHeaders.sectionHeader) { + sectionRdr := get_stream_reader(streamDir, optDbgHeaders.sectionHeader) + sectionLen := sectionRdr.size / size_of(PESectionHeader) + ret.sections = make([]SlimDbiSecInfo, sectionLen) + for i in 0..<sectionLen { + secHdr := readv(§ionRdr, PESectionHeader) + ret.sections[i] = SlimDbiSecInfo{secHdr.name, secHdr.vSize, secHdr.vAddr, } + } + // TODO: omaps and stuff + if stream_idx_valid(optDbgHeaders.oSectionHeader) { + log.warn("oSection presented presented, need RVA remap!") + } + } + return +} diff --git a/src/pdb/hashTable.odin b/src/pdb/hashTable.odin new file mode 100644 index 0000000..199e153 --- /dev/null +++ b/src/pdb/hashTable.odin @@ -0,0 +1,64 @@ +//! https://llvm.org/docs/PDB/HashTable.html +package pdb + +PdbHashTable :: struct($Value: typeid) { + size: u32le, + capacity: u32le, + presentBits: BitVector, + deletedBits: BitVector, + kvPairs: []PdbHashTable_KVPair(Value), +} + +PdbHashTable_KVPair :: struct($Value: typeid) #packed { + key: u32le, value: Value, +} + +BitVector :: struct { + //wordCount : u32le, + words: []u32le, +} + +get_kv_at :: proc(using this: ^PdbHashTable($T), at: u32le) -> (ret:PdbHashTable_KVPair(T), ok:bool) { + ok = get_bit(&presentBits, at) + if !ok { + ret = {} + return + } + ret = kvPairs[at] + return +} + +get_bit :: proc(using this: ^BitVector, at: u32le) -> bool { + ELEMENT_PER_WORD :: 32 + wi := at / ELEMENT_PER_WORD + if int(wi) >= len(words) do return false + word := words[uint(wi)] + iiw := at - (wi * ELEMENT_PER_WORD) + return (word & (1 << iiw)) != 0 +} + +read_hash_table :: proc(using this: ^BlocksReader, $Value: typeid) -> (ret:PdbHashTable(Value)) { + ret.size = readv(this, u32le) + ret.capacity = readv(this, u32le) + //log.debugf("hash_table size%v capacity%v", ret.size, ret.capacity) + ret.presentBits = read_bit_vector(this) + //log.debugf("presentBits: %v words read: 0x%x", len(ret.presentBits.words), ret.presentBits.words) + ret.deletedBits = read_bit_vector(this) + //log.debugf("deletedBit: %v words read: 0x%x", len(ret.deletedBits.words), ret.deletedBits.words) + ret.kvPairs = make([]PdbHashTable_KVPair(Value), ret.capacity) + for i in 0..<ret.capacity { + //NOTE(lux): documentation given at https://llvm.org/docs/PDB/HashTable.html doesn't seems to match actual pdbs tested, so we're guessing here that only valid and tombstone blocks get written into the file + if get_bit(&ret.presentBits, i) || get_bit(&ret.deletedBits, i) { + //log.debugf("read kvPair#%v of type %v with size %v... ", i, typeid_of(Value), size_of(PdbHashTable_KVPair(Value))) + ret.kvPairs[i] = readv(this, PdbHashTable_KVPair(Value)) + //log.debugf("key: %v, value: %v", ret.kvPairs[i].key, ret.kvPairs[i].value) + } + } + return +} + +read_bit_vector :: proc(using this: ^BlocksReader) -> (ret: BitVector) { + wordCount := readv(this, u32le) + ret.words = read_packed_array(this, uint(wordCount), u32le) + return +} diff --git a/src/pdb/modStream.odin b/src/pdb/modStream.odin new file mode 100644 index 0000000..2af714a --- /dev/null +++ b/src/pdb/modStream.odin @@ -0,0 +1,353 @@ +//! Module Information Stream, ref: https://llvm.org/docs/PDB/ModiStream.html +package pdb +import "core:log" +import "core:slice" +import "core:intrinsics" + +ModStreamHeader :: struct #packed { + signature : ModStreamSignature, // expected to be .C13 +} +// symbolsSubStream : [modi.symByteSize-4]byte +// c11LineInfoSubStream : [modi.c11ByteSize]byte +// c13LineInfoSubStream : [modi.c13ByteSize]byte +// globalRefsSize : u32le +// globalRefs: [modi.globalRefsSize]byte + +ModStreamSignature :: enum u32le {C7 = 1, C11 = 2, C13 = 4,} + +parse_mod_stream :: proc(streamDir: ^StreamDirectory, modi: ^SlimDbiMod) -> (ret: SlimModData) { + modSr := get_stream_reader(streamDir, modi.moduleSymStream) + readv(&modSr, ModStreamHeader) + ret.modStream = modSr.data[modSr.offset:] + + { // symbol substream + symSubStreamSize := modi.symByteSize - 4 + symSubStreamEnd := modSr.offset + uint(symSubStreamSize) + defer modSr.offset = symSubStreamEnd + pStack := make_stack(SlimCvsProc, cast(int)symSubStreamSize / ((size_of(CvsProc32)+size_of(CvsRecordKind))*4), context.temp_allocator); defer delete_stack(&pStack) + iStack := make_stack(SlimCvsInlineSite, cast(int)symSubStreamSize / ((size_of(CvsInlineSite)+size_of(CvsRecordKind))*4), context.temp_allocator); defer delete_stack(&iStack) + for modSr.offset < symSubStreamEnd { + pSelf := CvsOffset(modSr.offset) + cvsHeader := readv(&modSr, CvsRecordHeader) + blockEnd := modSr.offset + uint(cvsHeader.length) - size_of(CvsRecordKind) + defer modSr.offset = blockEnd + #partial switch cvsHeader.kind { + case .S_LPROC32:fallthrough + case .S_LPROC32_ID:fallthrough + case .S_LPROC32_DPC:fallthrough + case .S_LPROC32_DPC_ID:fallthrough + case .S_GPROC32:fallthrough + case .S_GPROC32_ID: + push(&pStack, SlimCvsProc{readv(&modSr, CvsProc32), pSelf}) + case .S_INLINESITE: + push(&iStack, SlimCvsInlineSite{readv(&modSr, blockEnd, CvsInlineSite), pSelf}) + } + } + if pStack.count > 0 { + ret.procs = make_slice_clone_from_stack(&pStack) + } + if iStack.count > 0 { + ret.inlineSites = make_slice_clone_from_stack(&iStack) + // sort inline sites by pParent, pEnd + // this allows us to do binary search on pParent to quickly narrow the range of sites needed for checking + slice.sort_by(ret.inlineSites, proc(a, b: SlimCvsInlineSite)->bool { + if a.pParent < b.pParent do return true + else if a.pParent > b.pParent do return false + else if a.pEnd < b.pEnd do return true + else do return false + }) + } + } + + { + // skip c11 lines + modSr.offset += uint(modi.c11ByteSize) + c13StreamStart := modSr.offset + c13StreamEnd := modSr.offset + uint(modi.c13ByteSize) + fileChecksumOffset : Maybe(uint) + lineBlockCount := 0 + inlineSrcCount := 0 + // first pass + for modSr.offset < c13StreamEnd { + ssh := readv(&modSr, CvDbgSubsectionHeader) + endOffset := modSr.offset + uint(ssh.length) + //log.debugf("[%v:%v] %v", modSr.offset, endOffset, ssh) + defer modSr.offset = endOffset + #partial switch ssh.subsectionType { + case .FileChecksums: fileChecksumOffset = modSr.offset + case .Lines: lineBlockCount+=1 + case .InlineeLines: + islHdr := readv(&modSr, CvDbgssInlineeLinesHeader) + islSize := uint(size_of(CvDbgInlineeSrcLine)) + if islHdr.signature == .ex { + islSize = uint(size_of(CvDbgInlineeSrcLineEx)) + } + inlineSrcCount += int((endOffset - modSr.offset) / islSize) + } + } + ret.blocks = make([]SlimModLineBlock, lineBlockCount) + ret.inlineSrcs = make([]SlimModInlineSrc, inlineSrcCount) + // second pass + modSr.offset = c13StreamStart + lineBlockCount = 0 + inlineSrcCount = 0 + for modSr.offset < c13StreamEnd { + ssh := readv(&modSr, CvDbgSubsectionHeader) + endOffset := modSr.offset + uint(ssh.length) + //log.debugf("[%v:%v] %v", modSr.offset, endOffset, ssh) + defer modSr.offset = endOffset + #partial switch ssh.subsectionType { + case .Lines: + cBlock := &ret.blocks[lineBlockCount] + lineBlockCount+=1 + ssLines := readv(&modSr, CvDbgssLinesHeader) + lineBlock := readv(&modSr, CvDbgLinesFileBlockHeader) + cBlock^ = SlimModLineBlock{ + _secOffset = PESectionOffset{offset = ssLines.offset, secIdx = ssLines.seg,}, + nameOffset = lineBlock.offFile, // unresolved for now + lines = make([]SlimModLinePos, lineBlock.nLines), + } + for li in 0..<lineBlock.nLines { + line := readv(&modSr, CvDbgLinePacked) + lns, lne, _ := unpack_lineFlag(line.flags) + cBlock.lines[li].offset = line.offset + cBlock.lines[li].lineStart = cast(u32le)lns + } + if ssLines.flags != .hasColumns && endOffset - modSr.offset == size_of(CvDbgColumn) * uint(lineBlock.nLines) { + log.debug("Flag indicates no column info, but infered from block length we assume column info anyway") + ssLines.flags = .hasColumns + } + if ssLines.flags == .hasColumns { + for li in 0..<lineBlock.nLines { + column := readv(&modSr, CvDbgColumn) + cBlock.lines[li].colStart = u32le(column.start) + } + } + case .InlineeLines: + islHdr := readv(&modSr, CvDbgssInlineeLinesHeader) + islSize := uint(size_of(CvDbgInlineeSrcLine)) + if islHdr.signature == .ex { + islSize = uint(size_of(CvDbgInlineeSrcLineEx)) + } + for modSr.offset < endOffset { + itemEndOffset := modSr.offset + islSize + isl := readv(&modSr, CvDbgInlineeSrcLine) + //log.debugf("%v", isl) + ret.inlineSrcs[inlineSrcCount] = SlimModInlineSrc{isl.inlinee, isl. fileId, isl.srcLineNum, } + inlineSrcCount+=1 + } + } + } + // resolve fileIds into nameOffsets + if fco, ok := fileChecksumOffset.?; ok { + for i in 0..<len(ret.blocks) { + modSr.offset = fco + uint(ret.blocks[i].nameOffset) + checksumHdr := readv(&modSr, CvDbgFileChecksumHeader) + ret.blocks[i].nameOffset = NamesStream_StartOffset + checksumHdr.nameOffset + //log.debug(ret.blocks[i].nameOffset) + } + for i in 0..<len(ret.inlineSrcs) { + modSr.offset = fco + uint(ret.inlineSrcs[i].nameOffset) + checksumHdr := readv(&modSr, CvDbgFileChecksumHeader) + ret.inlineSrcs[i].nameOffset = NamesStream_StartOffset + checksumHdr.nameOffset + } + } else { + for i in 0..<len(ret.blocks) { + ret.blocks[i].nameOffset = 0 + } + for i in 0..<len(ret.inlineSrcs) { + ret.inlineSrcs[i].nameOffset = 0 + } + } + // finally, sort inlineSrcs by inlinee + slice.sort_by(ret.inlineSrcs, proc(a, b: SlimModInlineSrc)->bool { + return a.inlinee < b.inlinee + }) + } + + return +} + +SlimCvsProc :: struct { + using _p : CvsProc32, + pSelf : CvsOffset, +} +SlimCvsInlineSite :: struct { + using _p : CvsInlineSite, + pSelf : CvsOffset, +} + +SlimModData :: struct { + modStream : []byte, // module stream without the stream header, can be directly indexed by Cvs section pointers to read certain stuff + procs : []SlimCvsProc, + blocks : []SlimModLineBlock, + inlineSites : []SlimCvsInlineSite, + inlineSrcs : []SlimModInlineSrc, +} +SlimModLineBlock :: struct { + using _secOffset : PESectionOffset, + nameOffset: u32le, // into the namesStream, with NamesStream_StartOffset already applied. + lines : []SlimModLinePos, +} +SlimModLinePos :: struct {lineStart, colStart, offset : u32le,} + +SlimModInlineSrc :: struct { + inlinee : CvItemId, // TODO: sort by this for quick look up + nameOffset : u32le, // into the namesStreams + srcLineNum : u32le, +} + +locate_pc :: proc(using data: ^SlimModData, func: PESectionOffset, pcFromFunc: u32le) -> (csvProc:^SlimCvsProc, lineBlock: ^SlimModLineBlock, line: ^SlimModLinePos){ + for i in 0..<len(procs) { + //log.debugf("%d/%d:", i, ) + p := &procs[i] + if p.secIdx == func.secIdx && p.offset == func.offset { + csvProc = p + break + } + } + for lbi in 0..<len(blocks) { + lb := &blocks[lbi] + if lb.secIdx != func.secIdx || lb.offset != func.offset { + continue + } + lineBlock = lb + line = &lb.lines[0] + // ?bisearch? + for i in 1..<len(lb.lines) { + cl := &lb.lines[i] + if cl.offset > pcFromFunc do break + line = cl + } + return + } + return +} + +locate_inline_site:: proc(using data: ^SlimModData, pParent: CvsOffset, pcFromFunc: u32le) -> (site: ^SlimCvsInlineSite, src: ^SlimModInlineSrc){ + // bisearch on a sorted acceleration structure + iStart := binary_search_min_ge(inlineSites, pParent, proc(v: CvsOffset, t: ^SlimCvsInlineSite) -> int { + //log.debugf("Comparing %v with %v", v, t.pParent) + if v < t.pParent do return -1 + else if v > t.pParent do return 1 + else do return 0 + }) + for i in iStart..<len(inlineSites) { + if inlineSites[i].pParent != pParent do break + found := false + for iline in inlineSites[i].lines { + if iline.end > pcFromFunc && iline.offset <= pcFromFunc { + site = &inlineSites[i] + found = true + break + } + } + if found do break + } + if site == nil do return + + iSite := binary_search_min_ge(inlineSrcs, site.inlinee, proc(v: CvItemId, t: ^SlimModInlineSrc) -> int { + if v < t.inlinee do return -1 + else if v > t.inlinee do return 1 + else do return 0 + }) + if iSite < len(inlineSrcs) && inlineSrcs[iSite].inlinee == site.inlinee { + src = &inlineSrcs[iSite] + } + return +} + +// the C13 CodeView line information format +// reference: https://github.com/willglynn/pdb/blob/master/src/modi/c13.rs +// a variable-size array of SubsectionHeader/Subsection contents +CvDbgSubsectionHeader :: struct #packed { + subsectionType : CvDbgSubsectionType, + length : u32le, +} +CvDbgSubsectionType :: enum u32 { // DEBUG_S_SUBSECTION_TYPE + // Native + Symbols = 0xf1, + Lines, + StringTable, + FileChecksums, + FrameData, + InlineeLines, + CrossScopeImports, + CrossScopeExports, + // .NET + IlLines, // seems that this can be parsed by SsLinesHeader as well, need further investigation + FuncMdtokenMap, + TypeMdtokenMap, + MergedAssemblyInput, + CoffSymbolRVA, + + ShouldIgnore = 0x8000_0000, // if set, the subsection content should be ignored +} + +// lines subsection starts with this header, then follows the CvDbgLinesFileBlockHeader +CvDbgssLinesHeader :: struct #packed { + offset : u32le, // section offset + seg : u16le, // seg index in the PDB's section header list, incremented by 1 + flags : CvDbgssLinesFlags, + codeSize: u32le, +} +CvDbgssLinesFlags :: enum u16le { none, hasColumns= 0x0001, } + +// follows by []CvDbgLinePacked and []CvDbgColumn, if hasColumns +CvDbgLinesFileBlockHeader :: struct #packed { + offFile : u32le, // offset of the file checksum in the file checksums debug subsection (after reading header) + nLines : u32le, // number of lines. if hasColumns, then same number of column entries with follow the line entries buffer + size : u32le, // total block size +} + +CvDbgLinePacked :: struct #packed { + offset : u32le, // ?to start of code bytes for line number + flags : CvDbgLinePackedFlags, +} +//1 +31|(7) +24|(24) +0 +//isStatement, deltaToLineEnd(optional), lineNumStart, +CvDbgLinePackedFlags :: distinct u32le +unpack_lineFlag :: proc(this: CvDbgLinePackedFlags) -> (lineNumStart:u32, lineNumEnd: u32, isStatement: bool) { + lineNumStart = u32(this & 0xff_ffff) + dLineEnd := (u32(this) & (0x7f00_0000)) >> 24 // this has been a truncation instead of delta + lineNumEnd = (lineNumStart & 0x7f) | dLineEnd + if lineNumEnd < lineNumStart do lineNumEnd += 1 << 7 + isStatement = (this & 0x8000_0000) != 0 + return +} + +CvDbgColumn :: struct #packed { + // byte offsets in a src line + start : u16le, + end : u16le, +} + +// InlineeLines subsection, starts with this header, follows with []CvDbgInlineeSrcLine(Ex) depending on the flag +CvDbgssInlineeLinesHeader :: struct #packed { + signature : CvDbgssInlineeLinesSignature, +} +CvDbgssInlineeLinesSignature :: enum u32le { none = 0x0, ex = 0x1, } + +CvDbgInlineeSrcLine :: struct #packed { + inlinee : CvItemId, // inlinee function id + fileId : u32le, // offset into file table DEBUG_S_FILECHKSMS + srcLineNum : u32le, // definition start line number +} +CvDbgInlineeSrcLineEx :: struct {// TODO: read this + using _base : CvDbgInlineeSrcLine, + // extraFileCount: u32le, + extraFileIds: []u32le, +} + +// File checksum subsection: []CvDbgFileChecksumHeader(variable length depending on checksumSize and 4byte alignment) +CvDbgFileChecksumHeader :: struct #packed { + nameOffset : u32le, // name ref into the global name table(after reading header) + checksumSize: u8, + checksumKind: CvDbgFileChecksumKind, + // then follows the checksum value buffer of len checksumSize + // then align to 4byte boundary to the next header +} +CvDbgFileChecksumKind :: enum u8 {none, md5, sha1, sha256, } + +// TODO: StringTable, FrameData, CrossScopeImports, CrossScopeExports etc + diff --git a/src/pdb/msf.odin b/src/pdb/msf.odin new file mode 100644 index 0000000..9c61c41 --- /dev/null +++ b/src/pdb/msf.odin @@ -0,0 +1,376 @@ +//! reference: https://llvm.org/docs/PDB/MsfFile.html https://llvm.org/docs/PDB/index.html +package pdb +import "core:strings" +import "core:mem" +import "core:log" +import "core:intrinsics" +import "core:runtime" +import "core:io" + +// SuperBlock|FPM1|FPM2|DataBlocks[BlockSize-3]|FPM1|FPM2|DataBlocks[BlockSize-3])+ + +FileMagic :string: "Microsoft C/C++ MSF 7.00\r\n\x1a\x44\x53\x00\x00\x00" +SuperBlock :: struct #packed { + //fileMagic : [len(SuperBlock_FileMagic)]byte, // == SuperBlock_FileMagic + blockSize : u32le, // block size of the internal file system == 4096 + freeBlockMapBlock : u32le, // index of a block which contains a bitfield indicating free blocks in the file. This index can only be 1/2. ????A file has two FPM to support incremental and atomic updates of the underlying MSF file: while writing, if active FPM is 1, you can write to free blocks indicated by FPM2, and vice-versa.?????? + numBlocks : u32le, // total number of blocks in file, NumBlocks * BlockSize should == size of the file on disk + numDirectoryBytes : u32le, // size of the StreamDirectory + unknown : u32le, + blockMapAddr: u32le, // index of a block within the MSF file, which stores an array of u32le, listing the blocks that the stream directory resides on, because the stream directory might occupy more than one block. array length given by ceil(NumDirectoryBytes/blockSize) +} + +// root to other streams in an MSF file, total bytes occupied by this struct in file is stored in superBlock.numDirectoryBytes +StreamDirectory :: struct { + numStreams : u32le, + streamSizes : []u32le, // len == numStreams. size of each stream in bytes + streamBlocks : [][]u32le, // blockIndices = StreamBlocks[streamIdx]. len(blockIndices) == ceil(streamSizes[streamIdx]/superBlock.blockSize) + r : io.Reader, + blockSize: u32le, +} +MsfStreamIdx :: distinct u16le +MsfStreamIdx_Invalid : MsfStreamIdx : 0xffff +stream_idx_valid ::#force_inline proc(idx: MsfStreamIdx) -> bool {return idx < MsfStreamIdx_Invalid} + +get_stream_reader :: #force_inline proc(using this: ^StreamDirectory, streamIdx : MsfStreamIdx) -> BlocksReader { + assert(stream_idx_valid(streamIdx)) + return make_reader_from_indiced_buf(r, streamBlocks[streamIdx], int(blockSize), streamSizes[streamIdx]) +} + +read_superblock :: proc(r: io.Reader) -> (this : SuperBlock, success: bool) { + SuperBlock_ReadSize :: len(FileMagic) + size_of(SuperBlock) + success = false + if r->impl_size() < SuperBlock_ReadSize { + log.debug("stream len too small to be a valid pdb file") + return + } + data: [SuperBlock_ReadSize]byte + if nRead, err := io.read(r, data[:]); err != nil || nRead != len(data) { + log.debugf("stream read failed with %v, nRead:%d", err, nRead) + return + } + if strings.compare(strings.string_from_ptr(&data[0], len(FileMagic)), FileMagic) != 0 { + log.debug("FileMagic mismatch") + return + } + + this = (cast(^SuperBlock)&data[len(FileMagic)])^ + success = true + return +} + +read_stream_dir :: proc(using this: ^SuperBlock, r: io.Reader) -> (sd: StreamDirectory, success: bool) { + success = false + if seekN, seekErr := r->impl_seek(i64(blockMapAddr * blockSize), .Start); seekErr != nil { + log.debugf("seek failed with %v", seekErr) + return + } + sdBlockCount := ceil_div(numDirectoryBytes, blockSize) + iData := make([]byte, sdBlockCount*size_of(u32le), context.temp_allocator) + defer delete(iData, context.temp_allocator) + if nRead, readErr := r->impl_read(iData); readErr != nil || nRead != len(iData) { + log.debugf("read block map failed with %v, nRead: %d, should be %d", readErr, nRead, numDirectoryBytes) + return + } + breader := make_reader_from_indiced_buf(r, transmute([]u32le)mem.Raw_Slice{&iData[0], int(sdBlockCount)}, int(blockSize), numDirectoryBytes) + + sd.numStreams = readv(&breader, u32le) + //fmt.printf("number of streams %v\n", sd.numStreams) + sd.streamSizes = make([]u32le, sd.numStreams) + sd.streamBlocks = make([][]u32le, sd.numStreams) + sd.r = r + sd.blockSize = blockSize + for i in 0..<sd.numStreams { + sd.streamSizes[i] = readv(&breader, u32le) + if sd.streamSizes[i] == 0xffff_ffff { + sd.streamSizes[i] = 0 //? clear invalid streamSizes? + } + //fmt.printf("reading stream#%v size %v\n", i, sd.streamSizes[i]) + sd.streamBlocks[i] = make([]u32le, ceil_div(sd.streamSizes[i], blockSize)) + } + + for i in 0..<sd.numStreams { + streamBlock := sd.streamBlocks[i] + //fmt.printf("reading stream#%v indices...\n", i) + for j in 0..< len(streamBlock) { + streamBlock[j] = readv(&breader, u32le) + } + } + success = true + return +} + +find_stream_dir :: proc(r: io.Reader) -> (sd: StreamDirectory, success: bool) { + success = false + pdbContent := read_superblock(r) or_return + sd = read_stream_dir(&pdbContent, r) or_return + success = true + return +} + +//@private +BlocksReader :: struct { + data: []byte, + offset : uint, + size : uint, +} +_dummy_indices :[]u32le = {0,} +make_dummy_reader :: proc(data: []byte) -> BlocksReader { + return BlocksReader{ + data = data, + offset = 0, + size = len(data), + } +} +make_reader_from_indiced_buf :: proc(r: io.Reader, indices: []u32le, blockSize: int, totalSize: u32le) -> BlocksReader { + buf := make([]byte, cast(uint)totalSize) + for i in 0..<len(indices)-1 { + isrc := int(indices[i])*blockSize + r->impl_seek(i64(isrc), .Start) + r->impl_read(buf[i*blockSize:(i+1)*blockSize]) + } + if len(indices) > 0 { // last buf + i := len(indices) - 1 + isrc := int(indices[i])*blockSize + r->impl_seek(i64(isrc), .Start) + r->impl_read(buf[i*blockSize:]) + } + return make_dummy_reader(buf) +} + +@private +_can_read_packed :: proc ($T: typeid) -> bool { + if intrinsics.type_is_struct(T) { + bti := runtime.type_info_base(type_info_of(T)) + ti, ok := bti.variant.(runtime.Type_Info_Struct) + if !ok do return false + return ti.is_packed + } + return true +} + +MsfNotPackedMarker :: struct {} // we use this marker to bypass the lacking of `intrinsics.is_struct_packed()` in certain cases + +read_packed_from_stream :: #force_inline proc(r: io.Stream, $T: typeid) -> (ret:T, err:io.Error) { + when ODIN_DEBUG==true { + if (!_can_read_packed(T)) { + log.errorf("Invalid type: %v", type_info_of(T).variant) + assert(false) + } + } + buf := transmute([]byte)mem.Raw_Slice{&ret, size_of(T),} + r->impl_read(buf) or_return + return ret, err +} + +read_packed_array :: proc(using this: ^BlocksReader, count: uint, $T: typeid) -> (ret: []T) { + when ODIN_DEBUG==true { + if (!_can_read_packed(T)) { + log.errorf("Invalid type: %v", type_info_of(T).variant) + assert(false) + } + } + endOffset := offset + count * size_of(T) + assert(endOffset <= this.size) + defer offset = endOffset + return mem.slice_ptr(cast(^T)&data[offset], int(count)) +} + +read_packed :: #force_inline proc(using this: ^BlocksReader, $T: typeid) -> (ret: T) + where !intrinsics.type_has_field(T, "_base"), + !intrinsics.type_is_subtype_of(T, MsfNotPackedMarker) { + when ODIN_DEBUG==true { + if (!_can_read_packed(T)) { + log.errorf("Invalid type: %v", type_info_of(T).variant) + assert(false) + } + } + tsize := size_of(T) + assert(size == 0 || offset + uint(tsize) <= size, "block overflow") + pret := cast(^byte)&ret + psrc := &data[offset] + mem.copy_non_overlapping(pret, psrc, tsize) + offset += uint(tsize) + return +} + +read_with_trailing_name :: #force_inline proc(this: ^BlocksReader, $T: typeid) -> (ret: T) + where intrinsics.type_has_field(T, "_base"), + intrinsics.type_has_field(T, "name"), + intrinsics.type_field_index_of(T, "name") == 1, + intrinsics.type_struct_field_count(T) == 2 { + ret._base = read_packed(this, type_of(ret._base)) + ret.name = read_length_prefixed_name(this) + return ret +} + +read_with_size_and_trailing_name :: #force_inline proc(this: ^BlocksReader, $T: typeid) -> (ret: T) + where intrinsics.type_has_field(T, "_base"), + intrinsics.type_has_field(T, "name"), + intrinsics.type_has_field(T, "size"), + intrinsics.type_field_index_of(T, "size") == 1, + intrinsics.type_field_index_of(T, "name") == 2, + intrinsics.type_struct_field_count(T) == 3 { + ret._base = read_packed(this, type_of(ret._base)) + ret.size = cast(uint)read_int_record(this) + ret.name = read_length_prefixed_name(this) + return +} + +readv :: proc { read_packed, read_with_trailing_name, read_with_size_and_trailing_name, read_with_trailing_rag, read_cvtBuildInfo, read_cvtUnion, read_cvtfArgList, read_cvtfBclass, read_cvtfVbclass, read_cvtfMember, read_cvtfEnumerate, read_cvtFieldList, read_dbiModInfo, read_dbiFileInfos, read_cvsFunctionList, read_cvsInlineSite, } + +read_length_prefixed_name :: proc(this: ^BlocksReader) -> (ret: string) { + //nameLen := cast(int)readv(this, u8) //? this is a fucking lie? + nameLen :int = 0 + for i in this.offset..<this.size { + if this.data[i] == 0 do break + nameLen+=1 + } + defer this.offset+=uint(nameLen+1) // eat trailing \0 as well + if nameLen == 0 do return "" + return strings.string_from_ptr(&this.data[this.offset], nameLen) +} + +@private +ceil_div :: #force_inline proc(a: u32le, b: u32le) -> u32le { + ret := a / b + if b * ret != a do ret += 1 + return ret +} + +@private +Stack :: struct($T: typeid) { + buf: []T, + count: int, + allocator: mem.Allocator, +} +make_slice_clone_from_stack :: #force_inline proc(stack: ^Stack($T)) -> (ret: []T) { + ret = make([]T, stack.count) + intrinsics.mem_copy_non_overlapping(&ret[0], &stack.buf[0], stack.count * size_of(T)) + return +} + +make_stack :: proc($T: typeid, cap: int, allocator : mem.Allocator) -> (ret:Stack(T)) { + ret.allocator = allocator + ensure_cap(&ret, cap) + return +} + +delete_stack :: proc(using stack: ^Stack($T)) { + c := context + if allocator.procedure != nil { + c.allocator = allocator + } + context = c + delete(buf) + count = 0 + buf = nil +} + +get_stack :: #force_inline proc(using stack: ^Stack($T), index : int) -> T { + return buf[index] +} + +clear_stack :: #force_inline proc(using stack: ^Stack($T)) { + count = 0 +} + +ensure_cap :: proc(using stack: ^Stack($T), newCap: int) { + if newCap > len(buf) { + c := context + if allocator.procedure != nil { + c.allocator = allocator + } + context = c + + oldBuffer := buf + defer delete(oldBuffer) + + buf = make(type_of(buf), newCap) + if count > 0 { + mem.copy(&buf[0], &oldBuffer[0], count * size_of(T)) + } + } +} + +push :: #force_inline proc(using stack: ^Stack($T), value: T) { + if len(buf) == count { + newCap := len(buf)*2 + if newCap < 8 { newCap = 8 } + ensure_cap(stack, newCap) + } + buf[count]=value + count+=1 +} + +pop :: #force_inline proc(using stack: ^Stack($T)) -> (T, bool) { + if count == 0 { + return T{}, false + } + + count-=1 + return buf[count], true +} + +pop_n :: #force_inline proc(using stack: ^Stack($T), toPop: int) -> (popped: int) { + popped = toPop + if popped > count { popped = count } + count -= popped + return +} + +// no resize +//@private +RingBuffer :: struct($T: typeid) { + data: []T, + len: uint, + offset: uint, + allocator : mem.Allocator, +} +init_rb :: proc(q: ^$Q/RingBuffer($T), capacity : int, allocator := context.allocator) { + q.data = make([]T, capacity, allocator) + q.len = 0 + q.offset = 0 + q.allocator = allocator + return +} +get_rb :: #force_inline proc "contextless"(q: ^$Q/RingBuffer($T), #any_int i: int, loc := #caller_location) -> T { + return get_ptr_rb(q, i, loc)^ +} +set_rb :: proc "contextless"(q: ^$Q/RingBuffer($T), #any_int i: int, val: T, loc := #caller_location) { + runtime.bounds_check_error_loc(loc, i, len(q.data)) + + idx := (uint(i)+q.offset)%len(q.data) + q.data[idx] = val +} +get_ptr_rb :: proc "contextless"(q: ^$Q/RingBuffer($T), #any_int i: int, loc := #caller_location) -> ^T { + runtime.bounds_check_error_loc(loc, i, len(q.data)) + + idx := (uint(i)+q.offset)%len(q.data) + return &q.data[idx] +} +push_back_rb :: proc "contextless"(q: ^$Q/RingBuffer($T), elem: T) -> bool { + idx := (q.offset+uint(q.len))%len(q.data) + q.data[idx] = elem + if q.len < len(q.data) do q.len += 1 + return true +} +push_front_rb :: proc "contextless"(q: ^$Q/RingBuffer($T), elem: T) -> bool { + q.offset = uint(q.offset - 1 + len(q.data)) % len(q.data) + if q.len < len(q.data) do q.len += 1 + q.data[q.offset] = elem + return true +} + +binary_search_min_ge :: proc(buf: []$T, value: $V, cmp: proc(v: V, t: ^T) -> int) -> int { + lb := 0 + hb := len(buf)-1 + if hb < lb do return lb + if cmp(value, &buf[hb]) > 0 do return hb+1 + for lb < hb { + mid := lb + ((hb-lb)>>1) + if cmp(value, &buf[mid]) > 0 do lb = mid + 1 + else do hb = mid + } + return lb +} diff --git a/src/pdb/namesStream.odin b/src/pdb/namesStream.odin new file mode 100644 index 0000000..9ff61f4 --- /dev/null +++ b/src/pdb/namesStream.odin @@ -0,0 +1,25 @@ +//! the pdb string name table. reference: https://github.com/willglynn/pdb/blob/master/src/strings.rs +package pdb +import "core:log" + +NamesStreamHeader :: struct #packed { + magic : u32le, // == NamesStream_HeaderMagic + hashVersion : NamesStreamHashVersion, + size : u32le, // size of all names in bytes +} +NamesStreamHashVersion :: enum u32le {LongHash = 1, LongHashV2 = 2,} +NamesStream_HeaderMagic :u32le: 0xeffe_effe +NamesStream_StartOffset :: size_of(NamesStreamHeader) + +parse_names_stream :: proc(this: ^BlocksReader) -> (header: NamesStreamHeader) { + header = readv(this, NamesStreamHeader) + if header.magic != NamesStream_HeaderMagic { + log.warnf("Unrecognized magic 0x%x for pdbNaming table..", header.magic) + } + if NamesStream_StartOffset + uint(header.size) > this.size { + log.warnf("data buffer not big enough, should be %v but was %v", NamesStream_StartOffset + uint(header.size), this.size) + } + return +} + +// TODO: reverse has stuff diff --git a/src/pdb/pdbStream.odin b/src/pdb/pdbStream.odin new file mode 100644 index 0000000..4f45e29 --- /dev/null +++ b/src/pdb/pdbStream.odin @@ -0,0 +1,82 @@ +//! the pdb info stream, reference: https://llvm.org/docs/PDB/PdbStream.html +package pdb +import "core:log" +import "core:strings" + +PdbStream_Index :MsfStreamIdx: 1 +NamesStream_Name :: "/names" + +PdbStreamHeader :: struct #packed { + version : PdbStreamVersion, + signature : u32le, + age : u32le, + guid : u128le, +} + +PdbStreamVersion :: enum u32le { + VC2 = 19941610, + VC4 = 19950623, + VC41 = 19950814, + VC50 = 19960307, + VC98 = 19970604, + VC70Dep = 19990604, + VC70 = 20000404, + VC80 = 20030901, + VC110 = 20091201, + VC140 = 20140508, +} + +PdbNamedStreamMap :: struct { + strBuf : []byte, names : []PdbNamedStream, +} +find_named_stream :: proc(using this: PdbNamedStreamMap, name: string) -> (streamIdx: MsfStreamIdx) { + for ns in names { + if strings.compare(ns.name, name) == 0 { + return MsfStreamIdx(ns.streamIdx) + } + } + return MsfStreamIdx_Invalid +} + +PdbNamedStream :: struct { + name : string, streamIdx : u32le, //? +} + +PdbRaw_FeatureSig :: enum u32le { + None = 0, //? + VC110 = 20091201, + VC140 = 20140508, + NoTypeMerge = 0x4D544F4E, + MinimalDebugInfo = 0x494E494D, +} + +parse_pdb_stream :: proc(this: ^BlocksReader) -> (header: PdbStreamHeader, nameMap: PdbNamedStreamMap, features: []PdbRaw_FeatureSig) { + header = readv(this, PdbStreamHeader) + if header.version != .VC70 { + log.warnf("unrecoginized pdbStreamVersion: %v", header.version) + } + + nameStringLen := readv(this, u32le) + //log.debugf("nameStringLen: %v", nameStringLen) + nameMap.strBuf = read_packed_array(this, uint(nameStringLen), byte) + namesTable := read_hash_table(this, u32le) + nameMap.names = make([]PdbNamedStream, namesTable.size) + nameIdx := 0 + for i in 0..< namesTable.capacity { + kv, ok := get_kv_at(&namesTable, i) + if ok { + //fmt.printf("k: %v, v: %v, vstr: %v\n", kv.key, kv.value, )) + nameStr : string + assert(kv.key < nameStringLen, "invalid name key") + nameStr = strings.string_from_nul_terminated_ptr(&nameMap.strBuf[kv.key], len(nameMap.strBuf)-int(kv.key)) + //fmt.printf("bucket#%v [%v:%v], name: %v\n", i, kv.key, kv.value, nameStr) + nameMap.names[nameIdx] = {nameStr, kv.value } + nameIdx+=1 + } + } + + featuresLen := (this.size - this.offset)/size_of(PdbRaw_FeatureSig) + features = read_packed_array(this, uint(featuresLen), PdbRaw_FeatureSig) + + return +} diff --git a/src/pdb/peFile.odin b/src/pdb/peFile.odin new file mode 100644 index 0000000..1692c1b --- /dev/null +++ b/src/pdb/peFile.odin @@ -0,0 +1,312 @@ +//! reference: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format +package pdb +import "core:log" +import "core:fmt" +import "core:slice" +import "core:io" + +PE_Signature_OffsetIdxPos :: 0x3c // ?u32le? +PE_Signature :u32le: 0x0000_4550 //PE\0\0 + +// At the beginning of an object file, or immediately after the signature of an image file +CoffFileHeader :: struct #packed { + machine : PEMachineType, + numSecs : u16le, // number of sections following the header + dateStamp : u32le, + pSymTable : u32le, // offset to COFF symbol table 0 if not presented + numSyms : u32le, + optHdrSize : u16le, // size of optional header following this + charas : PECharacteristics, +} +PEMachineType :: enum u16le { // IMAGE_FILE_MACHINE_xx + Unknown = 0x0, // assumed to be applicable to any machine type + AM33 = 0x1d3, // Matsushita AM33 + Amd64 = 0x8664, // x64 + Arm = 0x1c0, // ARM little endian + Arm64 = 0xaa64, //ARM64 little endian + ArmNT = 0x1c4, //ARM Thumb-2 little endian + EBC = 0xebc, //EFI byte code + I386 = 0x14c, //Intel 386 or later processors and compatible processors + IA64 = 0x200, //Intel Itanium processor family + LoongArch32 = 0x6232, + LoongArch64 = 0x6264, + M32R = 0x9041, //Mitsubishi M32R little endian + Mips16 = 0x266, + MipsFpu = 0x366, + MipsFpu16 = 0x466, + PowerPC = 0x1f0, //Power PC little endian + PowerPCFP = 0x1f1, //Power PC with floating point support + R4000 = 0x166, //MIPS little endian + RiscV32 = 0x5032, //RISC-V + RiscV64 = 0x5064, //RISC-V + RiscV128 = 0x5128, //RISC-V + SH3 = 0x1a2, //Hitachi SH3 + SH3DSP = 0x1a3, //Hitachi SH3 DSP + SH4 = 0x1a6, //Hitachi SH4 + SH5 = 0x1a8, //Hitachi SH5 + Thumb = 0x1c2, + WCEMipsV2 = 0x169, //MIPS little-endian WCE v2 +} +PECharacteristics :: enum u16le {// IMAGE_FILE_xxxx + None = 0, + RelocsStripped = 0x0001, //Image only, file does not contain base relocations + ExeImage = 0x0002, //Image only. This indicates that image is valid and can be run + _LineNumsStripped = 0x0004, //deprecated COFF stuff, should be zero. + _LocalSymsStripped = 0x0008, //deprecated COFF stuff, should be zero. + _AggressiveWorkingSetTrim = 0x0010, //Obsolete, must be zero. + LargeAddressAware = 0x0020, //Application can handle > 2-GB addresses. + _Reserved0 = 0x0040, //This flag is reserved for future use. + _BytesReservedLo = 0x0080, //deprecated and should be zero. + Machine32bit = 0x0100, //Machine is based on a 32-bit-word architecture. + DebugStripped = 0x0200, //Debugging information is removed from image. + RemovableRunFromSwap = 0x0400, //If on removable media, fully load to the swap file. + NetRunFromSwap = 0x0800, //If on network media, fully load to the swap file. + System = 0x1000, //image is a system file + Dll = 0x2000, //image is a dynamic-link library (DLL) + SystemOnly = 0x4000, //The file should be run only on a uniprocessor machine. + _BytesReservedHi = 0x8000, //deprecated and should be zero. +} +PEOptHdrMagic :: enum u16le { PE32 = 0x10b, PE32Plus = 0x20b, } +PEOptHdr :: struct #packed { + standard : PEOptHdr_StandardOld, + windows : PEOptHdr_Win, +} +PEOptHdrPlus :: struct #packed { + standard : PEOptHdr_Standard, + windows : PEOptHdr_WinPlus, +} +PEOptHdr_Standard :: struct #packed { + // magic : PEOptHdrMagic + majorLinkerVer : u8, + minorLinkerVer : u8, + codeSize : u32le, // (sum)size of code(text) section(s) + initDataSize : u32le, // (sum)size of initialized data section(s) + uninitDataSize : u32le, // (sum)size of BSS data section(s) + entryPointAddr : u32le, // address of entry point relative to image base + codeBaseAddr : u32le, // address of beginning of code section relative to image base +} +PEOptHdr_StandardOld :: struct #packed { + using _base : PEOptHdr_Standard, + dataBaseAddr : u32le, // address of beginning of data section relative to image base +} +PEOptHdr_Win :: _PEOptHdr_WinImpl(u32le) +PEOptHdr_WinPlus :: _PEOptHdr_WinImpl(u64le) +_PEOptHdr_WinImpl :: struct($T: typeid) #packed { + imageBase : T, // preferred address of image when loaded in memory + sectionAlignment: u32le, // section alignment when loaded in mem + fileAlignment : u32le, // alignment used to align raw section data in image file + majorOSVer : u16le, + minorOSVer : u16le, + majorImageVer : u16le, + minorImageVer : u16le, + majorSubsysVer : u16le, + minorSubsysVer : u16le, + _win32Ver : u32le, // reserved, must be 0 + imageSize : u32le, // size of image when loaded in mem + headersSize : u32le, // size of MS-DOS stub, PE and section headers, aligned to fileAlignment + checksum : u32le, // image file checksum + subsystem : PEWinSubsystem, + dllCharas : PEDllCharacteristics, + stackSizeReserve: T, + stackSizeCommit : T, + heapSizeReserve : T, + heapSizeCommit : T, + _loaderFlags : u32le, // reserved, must be 0 + dataDirCount : u32le, // count of data-directory entries in the remainder of OptHdr +} +PEWinSubsystem :: enum u16le { + Unknown = 0, Native = 1, WinGUI = 2, WinCUI = 3, OS2CUI = 5, PosixCUI = 7, + NativeWin9x = 8, WinCEGUI = 9, EFIApp = 10, EFIServiceDriver = 11, + EFIRuntimeDriver = 12, EFIRom = 13, Xbox = 14, WinBootApp = 16, +} +PEDllCharacteristics :: enum u16le { + HighEntropyVA = 0x0020, // can handle a high entropy 64-bit virtual address space + DynamicBase = 0x0040, // can be relocated at runtime + ForceIntegrity = 0x0080, + NXCompat = 0x0100, + NoIsolation = 0x0200, + NoSEH = 0x0400, + NoBind = 0x0800, + AppContainer = 0x1000, + WDMDriver = 0x2000, + GuardCF = 0x4000, + TerminalServerAware = 0x8000, +} + +// might not be fully valid: only optHdrWin.dataDirCount available +PEOptHdr_DataDirectories :: struct #packed { + edata, // export table + idata, // import table + rsrc, // resource table + pdata, // exception table + attrCert, // attribute certificate table + reloc, // base relocation table + debug, // debug data table + _architecture, // reserved + globalPtr, // global pointer register. size==0 + tls, // thread local storage table + loadConfig, // load configuration table + boundImport, // bound import table + iat, // import address table + delayLoadImport, // delay-load import tables + clrRuntime, // clr runtime headers + _reserved: PEOptHdr_DataDirectory, +} +// RVA: address of the table relative to image base address after loaded +PEOptHdr_DataDirectory :: struct #packed { rva, size: u32le, } + +// section table directly follows all the headers above, numbers given by coffHdr.numSecs +PESectionHeader :: struct #packed { + name : PESectionName, // formatting string using fmt.register_user_formatter, or slash+decimal ascii number as offset into string table + vSize : u32le, // size of section when loaded into memory + vAddr : u32le, // address of section relative to image base when loaded (for exe) + rawSize : u32le, // size of raw data on disk + pRaw : u32le, // file pointer to the first page of the section within COFF file + pRelocs : u32le, // file pointer to relocation entries for the section + pLines : u32le, // file pointer to COFF line-number entries, 0 if none + relocNum: u16le, // numbre of relocation entries + lineNum : u16le, // number of line-number entries + flags : PESectionFlags, +} +PESectionName :: struct #raw_union {buf: [8]byte, num: u64le,} +peSectionName_formatter :: proc(fi: ^fmt.Info, arg: any, verb: rune) -> bool { + n := arg.(PESectionName) + io.write_rune(fi.writer, '"', &fi.n) + for i in 0..<len(n.buf) { + if n.buf[i] == 0 do break + io.write_rune(fi.writer, rune(n.buf[i]), &fi.n) + } + io.write_string(fi.writer, "\"(0x", &fi.n) + io.write_u64(fi.writer, u64(n.num), 16, &fi.n) + io.write_rune(fi.writer, ')', &fi.n) + return true +} +PESectionOffset :: struct #packed {offset: u32le, secIdx: u16le,} +PESectionFlags :: enum u32le { + None = 0, + NoPad = 0x0000_0008, // obsolete, replaced by Align1 + CntCode = 0x0000_0020, + CntInitData = 0x0000_0040, + CntUninitData = 0x0000_0080, + LnkInfo = 0x0000_0200, // comments or ther info. .drectve section. valid for object files + LnkRemove = 0x0000_0800, // section will not become part of image. valid for object files + LnkComdat = 0x0000_1000, // COMDAT data + GlobalPointerRel = 0x0000_8000, // contains data referenced through global pointer(GP) + // alignments, valid for object files + Align1 = 0x0010_0000, + Align2 = 0x0020_0000, + Align4 = 0x0030_0000, + Align8 = 0x0040_0000, + Align16 = 0x0050_0000, + Align32 = 0x0060_0000, + Align64 = 0x0070_0000, + Align128 = 0x0080_0000, + Align256 = 0x0090_0000, + Align512 = 0x00a0_0000, + Align1024 = 0x00b0_0000, + Align2048 = 0x00c0_0000, + Align4096 = 0x00d0_0000, + Align8192 = 0x00e0_0000, + LnkNRelocOvfl = 0x0100_0000, // indicates that relocNum overflows 0xffff. If set and relocNum==0xffff, then actual count is stored in the rva field of the first relocation. + MemDiscardable = 0x0200_0000, + MemNotCached = 0x0400_0000, + MemNotPaged = 0x0800_0000, + MemShared = 0x1000_0000, + MemExecute = 0x2000_0000, + MemRead = 0x4000_0000, + MemWrite = 0x8000_0000, +} + +// TODO: COFF Relocations + +//.debug secion: Debug Directory +PEDebugDirEntry :: struct #packed { + flags : u32le, // reserved, must be 0 + dateStamp : u32le, + majorVer : u16le, + minorVer : u16le, + debugType : PEDebugType, + dataSize : u32le, // excluding the directory itself + rawDataAddr : u32le, // address of debug data when loaded + pRawData : u32le, // file pointer to raw data on disk +} +PEDebugType :: enum u32le { + Unknown = 0, + COFF = 1, // The COFF debug information + CodeView = 2, // The Visual C++ debug information. + FPO = 3, // The frame pointer omission (FPO) information. This information tells the debugger how to interpret nonstandard stack frames, which use the EBP register for a purpose other than as a frame pointer. + Misc = 4, // The location of DBG file. + Exception = 5, // A copy of .pdata section. + Fixup = 6, // Reserved. + OmapToSrc = 7, // RVA mapping to src + OmapFromSrc = 8, // RVA mapping from src + BORLAND = 9, // Reserved for Borland. + Reserved10 = 10, // Reserved. + CLSID = 11, // Reserved. + Repro = 16, // PE determinism or reproducibility. + ExtendedDllCharacteristics = 20, // Extended DLL characteristics bits. +} + +// record applies to the half-open intervals in the array +PEOMapRecord :: struct #packed { sourceAddr, targetAddr: u32le, } + +PECodeViewInfoPdb70 :: struct { + using _base : PECodeViewInfoPdb70Base, + name : string, +} +PECodeViewInfoPdb70Base :: struct #packed { + cvSignature : u32le, // should be PECodeView_Signature_RSDS + guid : u128le, + age : u32le, +} +PECodeView_Signature_RSDS :u32le: 0x5344_5352 + +parse_pe_data_dirs :: proc(r: io.Stream) -> (ret : PEOptHdr_DataDirectories, err: io.Error) { + r->impl_seek(PE_Signature_OffsetIdxPos, .Start) or_return + peSigOffset := read_packed_from_stream(r, u32le) or_return + r->impl_seek(i64(peSigOffset), .Start) or_return + PEPlusDataDirEnd :: size_of(u32le) + size_of(CoffFileHeader) + size_of(PEOptHdrMagic) + size_of(PEOptHdrPlus) + size_of(PEOptHdr_DataDirectories) + + buf :[PEPlusDataDirEnd]byte + r->impl_read(buf[:]) or_return + br := make_dummy_reader(buf[:]) + _, _, dataDirs := read_pe_headers(&br) + return dataDirs, nil +} + +seek_to_pe_headers :: proc(this: ^BlocksReader) { + this.offset = PE_Signature_OffsetIdxPos + peSigOffset := readv(this, u32le) + this.offset = uint(peSigOffset) +} + +read_pe_headers :: proc(this: ^BlocksReader) -> (coffHdr : CoffFileHeader, optHdr: union{PEOptHdr, PEOptHdrPlus}, dataDirs : PEOptHdr_DataDirectories) { + // start from PE_Signature + signature := readv(this, u32le) + if signature != PE_Signature { + log.warnf("Invalid signature 0x%x", signature) + return + } + coffHdr = readv(this, CoffFileHeader) + optHdrMagic := readv(this, PEOptHdrMagic) + switch optHdrMagic { + case .PE32: optHdr = readv(this, PEOptHdr) + case .PE32Plus: optHdr = readv(this, PEOptHdrPlus) + case: assert(false, "Invalid PEOptional Header Magic number") + } + { // dataDir, limited read + dataDirCount : u32le + switch h in optHdr { + case PEOptHdr: dataDirCount = h.windows.dataDirCount + case PEOptHdrPlus: dataDirCount = h.windows.dataDirCount + case: assert(false) + } + assert(dataDirCount * size_of(PEOptHdr_DataDirectory) <= size_of(PEOptHdr_DataDirectories), "dataDir overflow") + ddSlice := slice.from_ptr(cast(^PEOptHdr_DataDirectory)&dataDirs, int(dataDirCount)) + for i in 0..<len(ddSlice) { + ddSlice[i] = readv(this, PEOptHdr_DataDirectory) + } + } + return +} + diff --git a/src/pdb/stackTrace.odin b/src/pdb/stackTrace.odin new file mode 100644 index 0000000..aea205a --- /dev/null +++ b/src/pdb/stackTrace.odin @@ -0,0 +1,454 @@ +package pdb +import "core:os" +import "core:slice" +import "core:strings" +import "core:intrinsics" +import "core:runtime" +import "core:io" +import "core:sync" +import "core:path/filepath" +import windows "core:sys/windows" +foreign import ntdll_lib "system:ntdll.lib" +foreign import kernel32 "system:Kernel32.lib" + +@(default_calling_convention="std") +foreign ntdll_lib { + RtlCaptureStackBackTrace :: proc(FramesToSkip : DWORD, FramesToCapture: DWORD, BackTrace: ^rawptr, BackTraceHash: ^DWORD) -> WORD --- + RtlCaptureContext :: proc(ContextRecord : ^CONTEXT) --- + RtlLookupFunctionEntry :: proc(ControlPc : DWORD64, ImageBase: ^DWORD64, HistoryTable: ^UNWIND_HISTORY_TABLE) -> ^RUNTIME_FUNCTION --- + RtlVirtualUnwind :: proc( + HandlerType : DWORD, + ImageBase: DWORD64, + ControlPC: DWORD64, + FunctionEntry: ^RUNTIME_FUNCTION, + ContextRecord: ^CONTEXT, + HandlerData: ^rawptr, + EstablisherFrame: ^DWORD64, + ContextPointers: ^KNONVOLATILE_CONTEXT_POINTERS, + ) -> EXCEPTION_ROUTINE --- +} + +@(default_calling_convention="stdcall") +foreign kernel32 { + SetUnhandledExceptionFilter :: proc(lpTopLevelExceptionFilter: PTOP_LEVEL_EXCEPTION_FILTER) -> PTOP_LEVEL_EXCEPTION_FILTER --- +} + +RUNTIME_FUNCTION :: struct { + BeginAddress : DWORD, + EndAddress : DWORD, + using _dummyU: struct #raw_union { + UnwindData : DWORD, + UnwindInfoAddress : DWORD, + }, +} +BYTE :: windows.BYTE +WORD :: windows.WORD +DWORD :: windows.DWORD +PTOP_LEVEL_EXCEPTION_FILTER :: windows.PVECTORED_EXCEPTION_HANDLER +DWORD64 :: u64 +NEON128 :: struct {Low: u64, High: i64,} +ARM64_MAX_BREAKPOINTS :: 8 +ARM64_MAX_WATCHPOINTS :: 1 +M128A :: struct #align 16 #raw_union { + using _base : struct { Low: u64, High: i64, }, + _v128 : i128, +} +EXCEPTION_ROUTINE :: #type proc "stdcall" (ExceptionRecord: ^windows.EXCEPTION_RECORD, EstablisherFrame: rawptr, ContextRecord: ^CONTEXT, DispatcherContext: rawptr) -> windows.EXCEPTION_DISPOSITION +UNWIND_HISTORY_TABLE_SIZE :: 12 +CONTEXT :: _AMD64_CONTEXT +KNONVOLATILE_CONTEXT_POINTERS :: _AMD64_KNONVOLATILE_CONTEXT_POINTERS +UNWIND_HISTORY_TABLE :: _AMD64_UNWIND_HISTORY_TABLE +UNWIND_HISTORY_TABLE_ENTRY :: _AMD64_UNWIND_HISTORY_TABLE_ENTRY + +_AMD64_CONTEXT :: struct #align 16 { + // register parameter home addresses + P1Home,P2Home,P3Home,P4Home,P5Home,P6Home: DWORD64, + // control flags + ContextFlags,MxCsr: DWORD, + // segment registers and processor flags + SegCs,SegDs,SegEs,SegFs,SegGs,SegSs: WORD, EFlags: DWORD, + // debug registers + Dr0,Dr1,Dr2,Dr3,Dr6,Dr7: DWORD64, + // integer registers + Rax,Rcx,Rdx,Rbx,Rsp,Rbp,Rsi,Rdi,R8,R9,R10,R11,R12,R13,R14,R15: DWORD64, + // program counter + Rip: DWORD64, + // Floating point state + using _dummyU : struct #raw_union { + FltSave: XMM_SAVE_AREA32, + using _dummyS : struct { + Header: [2]M128A, + Legacy: [8]M128A, + Xmm0,Xmm1,Xmm2,Xmm3,Xmm4,Xmm5,Xmm6,Xmm7,Xmm8,Xmm9,Xmm10,Xmm11,Xmm12,Xmm13,Xmm14,Xmm15: M128A, + }, + }, + // Vector registers + VectorRegister: [26]M128A, VectorControl: DWORD64, + // special debug control registers + DebugControl,LastBranchToRip,LastBranchFromRip,LastExceptionToRip,LastExceptionFromRip: DWORD64, +} +XMM_SAVE_AREA32 :: struct #align 16 { // _XSAVE_FORMAT + ControlWord : WORD, + StatusWord : WORD, + TagWord : BYTE, + Reserved1 : BYTE, + ErrorOpcode : WORD, + ErrorOffset : DWORD, + ErrorSelector : WORD, + Reserved2 : WORD, + DataOffset : DWORD, + DataSelector : WORD, + Reserved3 : WORD, + MxCsr : DWORD, + MxCsr_Mask : DWORD, + FloatRegisters : [8]M128A, + + XmmRegisters : [16]M128A, + Reserved4 : [96]BYTE, +} +_AMD64_KNONVOLATILE_CONTEXT_POINTERS :: struct { + using _dummyU : struct #raw_union { + FloatingContext : [16]^M128A, + using _dummyS : struct { + Xmm0, Xmm1, Xmm2, Xmm3, + Xmm4, Xmm5, Xmm6, Xmm7, + Xmm8, Xmm9, Xmm10,Xmm11, + Xmm12,Xmm13,Xmm14,Xmm15: ^M128A, + }, + }, + using _dummyU2 : struct #raw_union { + IntegerContext : [16]^u64, + using _dummyS2 : struct { + Rax, Rcx, Rdx, Rbx, + Rsp, Rbp, Rsi, Rdi, + R8, R9, R10, R11, + R12, R13, R14, R15 : ^u64, + }, + }, +} +_AMD64_UNWIND_HISTORY_TABLE :: struct { + Count : DWORD, + LocalHint : BYTE, + GlobalHint : BYTE, + Search : BYTE, + Once : BYTE, + LowAddress : DWORD64, + HighAddress : DWORD64, + Entry : [UNWIND_HISTORY_TABLE_SIZE]_AMD64_UNWIND_HISTORY_TABLE_ENTRY, +} +_AMD64_UNWIND_HISTORY_TABLE_ENTRY :: struct { + ImageBase : DWORD64, + FunctionEntry : ^RUNTIME_FUNCTION, +} + +// TODO: arm bindings +// _ARM64_NT_CONTEXT :: struct { +// ContextFlags : DWORD, +// Cpsr : DWORD, +// using _dummyU : struct #raw_union { +// X : [31]DWORD64, +// using _dummyS : struct { +// X0, X1, X2, X3, X4, X5, X6, X7, +// X8, X9, X10,X11,X12,X13,X14,X15, +// X16,X17,X18,X19,X20,X21,X22,X23, +// X24,X25,X26,X27,X28,Fp, Lr : DWORD64, +// }, +// }, +// Sp : DWORD64, +// Pc : DWORD64, +// V : [32]NEON128, +// Fpcr : DWORD, +// Fpsr : DWORD, +// Bcr : [ARM64_MAX_BREAKPOINTS]DWORD, +// Bvr : [ARM64_MAX_BREAKPOINTS]DWORD64, +// Wcr : [ARM64_MAX_WATCHPOINTS]DWORD, +// Wvr : [ARM64_MAX_WATCHPOINTS]DWORD64, +// } + +StackFrame :: struct { + progCounter : uintptr, // instruction pointer + imgBaseAddr : uintptr, + funcBegin : u32, // rva marking the beginning of the function + funcEnd : u32, // rva marking the end of the function +} + +capture_stack_trace :: #force_no_inline proc "contextless" (traceBuf: []StackFrame) -> (count : uint) { + fImgBase : DWORD64 + ctx : CONTEXT + RtlCaptureContext(&ctx) + handlerData : rawptr + establisherFrame : DWORD64 + // skip current frame + rtFunc := RtlLookupFunctionEntry(ctx.Rip, &fImgBase, nil) + RtlVirtualUnwind(0, fImgBase, ctx.Rip, rtFunc, &ctx, &handlerData, &establisherFrame, nil) + + for count = 0; count < len(traceBuf); count+=1 { + rtFunc = RtlLookupFunctionEntry(ctx.Rip, &fImgBase, nil) + if rtFunc == nil do break + pst := &traceBuf[count] + pst.progCounter = cast(uintptr)ctx.Rip + pst.imgBaseAddr = cast(uintptr)fImgBase + pst.funcBegin = rtFunc.BeginAddress + pst.funcEnd = rtFunc.EndAddress + RtlVirtualUnwind(0, fImgBase, ctx.Rip, rtFunc, &ctx, &handlerData, &establisherFrame, nil) + } + return +} + +capture_stack_trace_from_context :: proc "contextless" (ctx: ^CONTEXT, traceBuf: []StackFrame) -> (count : uint) { + fImgBase : DWORD64 + handlerData : rawptr + establisherFrame : DWORD64 + for count = 0; count < len(traceBuf); count+=1 { + rtFunc := RtlLookupFunctionEntry(ctx.Rip, &fImgBase, nil) + if rtFunc == nil do break + pst := &traceBuf[count] + pst.progCounter = cast(uintptr)ctx.Rip + pst.imgBaseAddr = cast(uintptr)fImgBase + pst.funcBegin = rtFunc.BeginAddress + pst.funcEnd = rtFunc.EndAddress + RtlVirtualUnwind(0, fImgBase, ctx.Rip, rtFunc, ctx, &handlerData, &establisherFrame, nil) + } + return +} + +_PEModuleInfo :: struct { + filePath : string, + pdbPath : string, + pdbHandle : os.Handle, + streamDir : StreamDirectory, + namesStream : BlocksReader, + dbiData : SlimDbiData, + ipiStream : BlocksReader, + ipiOffsetBuf : TpiIndexOffsetBuffer, +} + +parse_stack_trace :: proc(stackTrace: []StackFrame, sameProcess: bool, srcCodeLocs: ^RingBuffer(runtime.Source_Code_Location)) { + miMap := make(map[uintptr]_PEModuleInfo, 8, context.temp_allocator) + defer { + for _, pemi in miMap { + os.close(pemi.pdbHandle) // close file + } + delete(miMap) + } + mdMap := make(map[uintptr]SlimModData, 8, context.temp_allocator) + defer delete(mdMap) + //for stackFrame, i in stackTrace { + for i := len(stackTrace)-1; i>=0; i-=1 { + stackFrame := stackTrace[i] + mi, ok := miMap[stackFrame.imgBaseAddr] + if !ok { + // PEModuleInfo not found for this module, load them in + defer miMap[stackFrame.imgBaseAddr] = mi + nameBuf : [windows.MAX_PATH]u16 + pBuf := &nameBuf[0] + nameLen := windows.GetModuleFileNameW(windows.HMODULE(stackFrame.imgBaseAddr), pBuf, len(nameBuf)) + path, err := windows.wstring_to_utf8(pBuf, cast(int)nameLen) + if err != nil { + continue + } + mi.filePath = path + peFile, peFileErr := os.open(mi.filePath) + if peFileErr != 0 { + // skip images that we cannot open + continue + } + defer os.close(peFile) + dataDirs, dde := parse_pe_data_dirs(os.stream_from_handle(peFile)) + if dataDirs.debug.size > 0 && dde == nil { + ddEntrys := slice.from_ptr( + (^PEDebugDirEntry)((stackFrame.imgBaseAddr) + uintptr(dataDirs.debug.rva)), + int(dataDirs.debug.size / size_of(PEDebugDirEntry)), + ) + for dde in ddEntrys { + if dde.debugType != .CodeView do continue + if sameProcess { + // image is supposed to beloaded, we can just look at the struct in memory + pPdbBase := (^PECodeViewInfoPdb70Base)((stackFrame.imgBaseAddr) + uintptr(dde.rawDataAddr)) + if pPdbBase.cvSignature != PECodeView_Signature_RSDS do continue + pPdbPath := (^byte)(uintptr(pPdbBase) + cast(uintptr)size_of(PECodeViewInfoPdb70Base)) + mi.pdbPath = strings.string_from_nul_terminated_ptr(pPdbPath, int(dde.dataSize-size_of(PECodeViewInfoPdb70Base))) + } else { + // otherwise we need to seek to it on disk + peStream := os.stream_from_handle(peFile) + if n, err := peStream->impl_seek(i64(dde.pRawData), .Start); err != nil || n != i64(dde.pRawData) do continue + buf := make([]byte, int(dde.dataSize)) + if n, err := peStream->impl_read(buf[:]); err != nil || n != len(buf) { + delete(buf) + continue + } + pdbr := make_dummy_reader(buf) + pdbInfo := readv(&pdbr, PECodeViewInfoPdb70) + if pdbInfo.cvSignature != PECodeView_Signature_RSDS { + delete(buf) + continue + } + mi.pdbPath = pdbInfo.name + } + + break + } + } + // TODO: if pdbPath is still not found by now, we should look into other possible directories for them + pdbFile, pdbErr := os.open(mi.pdbPath) + if pdbErr != 0 { // try load pdb at the same path as src exe + toConcatenate := []string {filepath.stem(mi.filePath), ".pdb"} + mi.pdbPath = strings.concatenate(toConcatenate) + pdbFile, pdbErr = os.open(mi.pdbPath) + } + if pdbErr == 0 { + mi.pdbHandle = pdbFile + pdbr := io.Reader{os.stream_from_handle(pdbFile)} + if streamDir, sdOk := find_stream_dir(pdbr); sdOk { + mi.streamDir = streamDir + pdbSr := get_stream_reader(&mi.streamDir, PdbStream_Index) + pdbHeader, nameMap, pdbFeatures := parse_pdb_stream(&pdbSr) + mi.namesStream = get_stream_reader(&mi.streamDir, find_named_stream(nameMap, NamesStream_Name)) + mi.dbiData = parse_dbi_stream(&mi.streamDir) + mi.ipiStream = get_stream_reader(&mi.streamDir, IpiStream_Index) + _, ipiOffsetBuf := parse_tpi_stream(&mi.ipiStream, &streamDir) + mi.ipiOffsetBuf = ipiOffsetBuf + } + } + } + pcRva := u32le(stackFrame.progCounter - stackFrame.imgBaseAddr) + funcRva := u32le(stackFrame.funcBegin) + if sci := search_for_section_contribution(&mi.dbiData, funcRva); sci >= 0 { + sc := mi.dbiData.contributions[sci] + modi := mi.dbiData.modules[sc.module] + funcOffset := PESectionOffset { + offset = funcRva - mi.dbiData.sections[sc.secIdx-1].vAddr, + secIdx = sc.secIdx, + } + // address of module's first section contribution in memory. This should + // be unique per module across the whole process, making it usable as + // hash keys for the mod data map + mdAddress := stackFrame.imgBaseAddr + uintptr(mi.dbiData.sections[modi.secContrOffset.secIdx-1].vAddr) + uintptr(modi.secContrOffset.offset) + modData, modDataOk := mdMap[mdAddress] + if !modDataOk { + modData = parse_mod_stream(&mi.streamDir, &modi) + mdMap[mdAddress] = modData + } + p, lb, l := locate_pc(&modData, funcOffset, pcRva-funcRva) + scl : runtime.Source_Code_Location + if lb != nil && lb.nameOffset > 0 { + mi.namesStream.offset = uint(lb.nameOffset) + scl.file_path = read_length_prefixed_name(&mi.namesStream) + } else { + scl.file_path = modi.moduleName + } + if p != nil { + scl.procedure = p.name + scl.line = i32(l.lineStart) + scl.column = i32(l.colStart) + push_front_rb(srcCodeLocs, scl) + + // locate inline sites + pParent := p.pSelf + pcFromFunc := pcRva-funcRva + inlineSite, src := locate_inline_site(&modData, pParent, pcFromFunc) + srcLineNum :u32le = 0 + for inlineSite != nil { + if src != nil && src.nameOffset > 0 { + mi.namesStream.offset = uint(src.nameOffset) + scl.file_path = read_length_prefixed_name(&mi.namesStream) + srcLineNum = src.srcLineNum + } else { + scl.file_path = modi.moduleName + } + lastLine := inlineSite.lines[0] + for iline in inlineSite.lines { + if iline.offset > pcFromFunc do break + lastLine = iline + } + + scl.procedure = "_inlinedFunc" // TODO: parse proc name by cvt info + if seek_for_tpi(mi.ipiOffsetBuf, inlineSite.inlinee, &mi.ipiStream) { + cvtHeader := readv(&mi.ipiStream, CvtRecordHeader) + #partial switch cvtHeader.kind { + case .LF_MFUNC_ID:fallthrough // legit because binary structure identical + case .LF_FUNC_ID: + cvtFuncId := readv(&mi.ipiStream, CvtFuncId) + scl.procedure = cvtFuncId.name + } + } + + scl.line = i32(lastLine.lineStart + srcLineNum) + scl.column = i32(lastLine.colStart) + push_front_rb(srcCodeLocs, scl) + //fmt.printf("#%v[%v:%v] site found: %v, src: %v\n", srcCodeLocs.len, pParent, pcFromFunc, inlineSite, src) + pParent = inlineSite.pSelf + inlineSite, src = locate_inline_site(&modData, pParent, pcFromFunc) + } + } + } else { + scl : runtime.Source_Code_Location + scl.file_path = mi.filePath // pdb failed to provide us with a valid source file name, fallback to filePath provided by the image + push_front_rb(srcCodeLocs, scl) + } + } + return +} + +_dumpStackTrackMutex : sync.Atomic_Mutex + +dump_stack_trace_on_exception :: proc "stdcall" (ExceptionInfo: ^windows.EXCEPTION_POINTERS) -> windows.LONG { + context = runtime.default_context() // TODO: use a more efficient one-off allocators + sync.guard(&_dumpStackTrackMutex) + if ExceptionInfo.ExceptionRecord != nil { + runtime.print_string("ExceptionType: 0x") + print_u64_x(u64(ExceptionInfo.ExceptionRecord.ExceptionCode)) + runtime.print_string(", Flags: 0x") + print_u64_x(u64(ExceptionInfo.ExceptionRecord.ExceptionFlags)) + } + ctxt := cast(^CONTEXT)ExceptionInfo.ContextRecord + traceBuf : [64]StackFrame + traceCount := capture_stack_trace_from_context(ctxt, traceBuf[:]) + runtime.print_string(" Stacktrace:") + runtime.print_uint(traceCount) + runtime.print_string("\n") + srcCodeLocs : RingBuffer(runtime.Source_Code_Location) + init_rb(&srcCodeLocs, 64) + parse_stack_trace(traceBuf[:traceCount], true, &srcCodeLocs) + for i in 0..<srcCodeLocs.len { + scl := get_rb(&srcCodeLocs, i) + print_source_code_location(scl) + } + return windows.EXCEPTION_CONTINUE_SEARCH +} + +print_u64_x :: proc "contextless" (x: u64) #no_bounds_check { + using runtime + digits := "0123456789abcdefghijklmnopqrstuvwxyz" + a: [129]byte + i := len(a) + b := u64(16) + u := x + for u >= b { + i -= 1; a[i] = digits[u % b] + u /= b + } + i -= 1; a[i] = digits[u % b] + + os_write(a[i:]) +} + +print_source_code_location :: proc (using scl: runtime.Source_Code_Location) { + using runtime + print_string(file_path) + when ODIN_ERROR_POS_STYLE == .Unix { + print_byte(':') + print_u64(u64(line)) + print_byte(':') + print_u64(u64(column)) + print_byte(':') + } else { + print_byte('(') + print_u64(u64(line)) + print_byte(':') + print_u64(u64(column)) + print_byte(')') + } + print_string(procedure) + print_string("()\n") +} diff --git a/src/pdb/tpiStream.odin b/src/pdb/tpiStream.odin new file mode 100644 index 0000000..be53e4f --- /dev/null +++ b/src/pdb/tpiStream.odin @@ -0,0 +1,95 @@ +//! tpi or ipi stream, reference: https://llvm.org/docs/PDB/TpiStream.html +package pdb +import "core:log" + +TpiStream_Index :MsfStreamIdx: 2 +IpiStream_Index :MsfStreamIdx: 4 + +TpiStreamHeader :: struct #packed { + version : TpiStreamVersion, // appears to always be v80 + headerSize : u32le, // == sizeof(TpiStreamHeader) + typeIndexBegin : TypeIndex, // usually 0x1000(4096), type indices lower are usually reserved + typeIndexEnd : TypeIndex, // total number of type records = typeIndexEnd-typeIndexBegin + typeRecordBytes : u32le, // type record data size following the header + + hashStreamIndex : MsfStreamIdx, // -1 means not present, usually always observed to be present + hashAuxStreamIndex : MsfStreamIdx, // unclear what for + hashKeySize : u32le, // usually 4 bytes + numHashBuckets : u32le, // number of buckets used to generate these hash values + + // ?offset and size within the hash stream. HashBufferLength should == numTypeRecords * hashKeySize + hashValueBufferOffset, hashValueBufferLength: i32le, + // type index offset buffer pos within the hash stream, which is + // a list of u32le (typeIndex, offset in type record data)pairs that can be biSearched + indexOffsetBufferOffset, indexOffsetBufferLength: i32le, + // a pdb hashTable, with u32le (hash values, type indices) kv pair + hashAdjBufferOffset, hashAdjBufferLength: i32le, +} + +TpiStreamVersion :: enum u32le { + V40 = 19950410, + V41 = 19951122, + V50 = 19961031, + V70 = 19990903, + V80 = 20040203, +} + +TpiIndexOffsetBuffer :: struct { + buf : []TpiIndexOffsetPair, +} +TpiIndexOffsetPair :: struct #packed {ti: TypeIndex, offset: u32le,} + +seek_for_tpi :: proc(using this: TpiIndexOffsetBuffer, ti : TypeIndex, tpiStream: ^BlocksReader) -> (ok: bool) { + // bisearch then linear seaarch proc + lo, hi := 0, (len(buf)-1) + if hi < 0 || buf[lo].ti > ti || buf[hi].ti < ti do return false + // ti in range, do a bisearch + for lo <= hi { + //log.debugf("Find block [%v, %v) for ti%v", lo,hi, ti) + mid := lo + ((hi-lo)>>1) + mv := buf[mid].ti + if mv == ti { + lo = mid + 1 + break + } + else if mv > ti do hi = mid - 1 + else do lo = mid + 1 + } + if lo > 0 do lo -= 1 + //log.debugf("Find block [%v, %v) for ti%v", lo,lo+1, ti) + // now a linear search from lo to high + tIdx := buf[lo].ti + tpiStream.offset = cast(uint)buf[lo].offset + endOffset := tpiStream.size + if lo+1 < len(buf) do endOffset = cast(uint)buf[lo+1].offset + for ;tpiStream.offset < endOffset && tIdx != ti; tIdx+=1 { + cvtHeader := readv(tpiStream, CvtRecordHeader) + tpiStream.offset += cast(uint)cvtHeader.length - size_of(CvtRecordKind) + } + //log.debugf("Block offset: %v", tpiStream.offset) + return true +} + +parse_tpi_stream :: proc(this: ^BlocksReader, dir: ^StreamDirectory) -> (header: TpiStreamHeader, tiob: TpiIndexOffsetBuffer) { + header = readv(this, TpiStreamHeader) + assert(header.headerSize == size_of(TpiStreamHeader), "Incorrect header size, mulfunctional stream") + if header.version != .V80 { + log.warnf("unrecoginized tpiVersion: %v", header.version) + } + + if header.hashStreamIndex >= 0 { + hashStream := get_stream_reader(dir, header.hashStreamIndex) + iobLen := header.indexOffsetBufferLength / size_of(TpiIndexOffsetPair) + tiob.buf = make([]TpiIndexOffsetPair, iobLen) + hashStream.offset = uint(header.indexOffsetBufferOffset) //? + for i in 0..<iobLen { + tiob.buf[i] = readv(&hashStream, TpiIndexOffsetPair) + tiob.buf[i].offset += u32le(this.offset) // apply header offset here as well. + } + } else { + // fallback + tiob.buf = make([]TpiIndexOffsetPair, 1) + tiob.buf[0] = TpiIndexOffsetPair{ti = header.typeIndexBegin} + } + return +} diff --git a/src/server/analysis.odin b/src/server/analysis.odin index 0873be4..3491557 100644 --- a/src/server/analysis.odin +++ b/src/server/analysis.odin @@ -920,7 +920,7 @@ resolve_type_expression :: proc(ast_context: ^AstContext, node: ^ast.Expr) -> (S case ^Ellipsis: return resolve_type_expression(ast_context, v.expr) case ^Implicit: - ident := new_type(Ident, v.node.pos, v.node.end, context.temp_allocator) + ident := new_type(Ident, v.node.pos, v.node.end, ast_context.allocator) ident.name = v.tok.text return resolve_type_identifier(ast_context, ident^) case ^Type_Assertion: @@ -1018,7 +1018,7 @@ resolve_type_expression :: proc(ast_context: ^AstContext, node: ^ast.Expr) -> (S } case SymbolProcedureValue: if len(s.return_types) == 1 { - selector_expr := new_type(ast.Selector_Expr, s.return_types[0].node.pos, s.return_types[0].node.end, context.temp_allocator) + selector_expr := new_type(ast.Selector_Expr, s.return_types[0].node.pos, s.return_types[0].node.end, ast_context.allocator) selector_expr.expr = s.return_types[0].type selector_expr.field = v.field return resolve_type_expression(ast_context, selector_expr) @@ -2281,9 +2281,9 @@ get_locals_using_stmt :: proc(stmt: ast.Using_Stmt, ast_context: ^AstContext) { } case SymbolStructValue: for name, i in v.names { - selector := new_type(ast.Selector_Expr, v.types[i].pos, v.types[i].end, context.temp_allocator) + selector := new_type(ast.Selector_Expr, v.types[i].pos, v.types[i].end, ast_context.allocator) selector.expr = u - selector.field = new_type(ast.Ident, v.types[i].pos, v.types[i].end, context.temp_allocator) + selector.field = new_type(ast.Ident, v.types[i].pos, v.types[i].end, ast_context.allocator) selector.field.name = name store_local(ast_context, u, selector, 0, name, ast_context.local_id) ast_context.variables[name] = true diff --git a/src/server/documents.odin b/src/server/documents.odin index d320cf0..d5b0f09 100644 --- a/src/server/documents.odin +++ b/src/server/documents.odin @@ -377,9 +377,13 @@ parse_document :: proc(document: ^Document, config: ^common.Config) -> ([]Parser current_errors = make([dynamic]ParserError, context.temp_allocator) - free_all(common.scratch_allocator(document.allocator)) + if document.uri.uri in file_resolve_cache.files { + key := file_resolve_cache.files[document.uri.uri] + delete_key(&file_resolve_cache.files, document.uri.uri) + delete(key) + } - delete_key(&file_resolve_cache.files, document.uri.uri) + free_all(common.scratch_allocator(document.allocator)) context.allocator = common.scratch_allocator(document.allocator) |