| ===================================== | |
| CodeView Symbol Records | |
| ===================================== | |
| .. contents:: | |
| :local: | |
| .. _symbols_intro: | |
| Introduction | |
| ============ | |
| This document describes the usage and serialization format of the various | |
| CodeView symbol records that LLVM understands. Like | |
| :doc:`CodeView Type Records <CodeViewTypes>`, we describe only the important | |
| types which are generated by modern C++ toolchains. | |
| Record Categories | |
| ================= | |
| Symbol records share one major similarity with :doc:`type records <CodeViewTypes>`: | |
| They start with the same :ref:`record prefix <leaf_types>`, which we will not describe | |
| again (refer to the previous link for a description). As a result of this, a sequence | |
| of symbol records can be processed with largely the same code as that which processes | |
| type records. There are several important differences between symbol and type records: | |
| * Symbol records only appear in the :doc:`PublicStream`, :doc:`GlobalStream`, and | |
| :doc:`Module Info Streams <ModiStream>`. | |
| * Type records only appear in the :doc:`TPI & IPI streams <TpiStream>`. | |
| * While types are referenced from other CodeView records via :ref:`type indices <type_indices>`, | |
| symbol records are referenced by the byte offset of the record in the stream that it appears | |
| in. | |
| * Types can reference types (via type indices), and symbols can reference both types (via type | |
| indices) and symbols (via offsets), but types can never reference symbols. | |
| * There is no notion of :ref:`Leaf Records <leaf_types>` and :ref:`Member Records <member_types>` | |
| as there are with types. Every symbol record describes is own length. | |
| * Certain special symbol records begin a "scope". For these records, all following records | |
| up until the next ``S_END`` record are "children" of this symbol record. For example, | |
| given a symbol record which describes a certain function, all local variables of this | |
| function would appear following the function up until the corresponding ``S_END`` record. | |
| Finally, there are three general categories of symbol record, grouped by where they are legal | |
| to appear in a PDB file. Public Symbols (which appear only in the | |
| :doc:`publics stream <PublicStream>`), Global Symbols (which appear only in the | |
| :doc:`globals stream <GlobalStream>`) and module symbols (which appear in the | |
| :doc:`module info stream <ModiStream>`). | |
| .. _public_symbols: | |
| Public Symbols | |
| -------------- | |
| Public symbols are the CodeView equivalent of DWARF ``.debug_pubnames``. There | |
| is one public symbol record for every function or variable in the program that | |
| has a mangled name. The :doc:`Publics Stream <PublicStream>`, which contains these | |
| records, additionally contains a hash table that allows one to quickly locate a | |
| record by mangled name. | |
| S_PUB32 (0x110e) | |
| ^^^^^^^^^^^^^^^^ | |
| There is only type of public symbol, an ``S_PUB32`` which describes a mangled | |
| name, a flag indicating what kind of symbol it is (e.g. function, variable), and | |
| the symbol's address. The :ref:`dbi_section_map_substream` of the | |
| :doc:`DBI Stream <DbiStream>` can be consulted to determine what module this address | |
| corresponds to, and from there that module's :doc:`module debug stream <ModiStream>` | |
| can be consulted to locate full information for the symbol with the given address. | |
| .. _global_symbols: | |
| Global Symbols | |
| -------------- | |
| While there is one :ref:`public symbol <public_symbols>` for every symbol in the | |
| program with `external` linkage, there is one global symbol for every symbol in the | |
| program with linkage (including internal linkage). As a result, global symbols do | |
| not describe a mangled name *or* an address, since symbols with internal linkage | |
| need not have any mangling at all, and also may not have an address. Thus, all | |
| global symbols simply refer directly to the full symbol record via a module/offset | |
| combination. | |
| Similarly to :ref:`public symbols <public_symbols>`, all global symbols are contained | |
| in a single :doc:`Globals Stream <GlobalStream>`, which contains a hash table mapping | |
| fully qualified name to the corresponding record in the globals stream (which as | |
| mentioned, then contains information allowing one to locate the full record in the | |
| corresponding module symbol stream). | |
| Note that a consequence and limitation of this design is that program-wide lookup | |
| by anything other than an exact textually matching fully-qualified name of whatever | |
| the compiler decided to emit is impractical. This differs from DWARF, where even | |
| though we don't necessarily have O(1) lookup by basename within a given scope (including | |
| O(1) scope, we at least have O(n) access within a given scope). | |
| .. important:: | |
| Program-wide lookup of names by anything other than an exact textually matching fully | |
| qualified name is not possible. | |
| S_GDATA32 | |
| ^^^^^^^^^^ | |
| S_GTHREAD32 (0x1113) | |
| ^^^^^^^^^^^^^^^^^^^^ | |
| S_PROCREF (0x1125) | |
| ^^^^^^^^^^^^^^^^^^ | |
| S_LPROCREF (0x1127) | |
| ^^^^^^^^^^^^^^^^^^^ | |
| S_GMANDATA (0x111d) | |
| ^^^^^^^^^^^^^^^^^^^ | |
| .. _module_symbols: | |
| Module Symbols | |
| -------------- | |
| S_END (0x0006) | |
| ^^^^^^^^^^^^^^ | |
| S_FRAMEPROC (0x1012) | |
| ^^^^^^^^^^^^^^^^^^^^ | |
| S_OBJNAME (0x1101) | |
| ^^^^^^^^^^^^^^^^^^ | |
| S_THUNK32 (0x1102) | |
| ^^^^^^^^^^^^^^^^^^ | |
| S_BLOCK32 (0x1103) | |
| ^^^^^^^^^^^^^^^^^^ | |
| S_LABEL32 (0x1105) | |
| ^^^^^^^^^^^^^^^^^^ | |
| S_REGISTER (0x1106) | |
| ^^^^^^^^^^^^^^^^^^^ | |
| S_BPREL32 (0x110b) | |
| ^^^^^^^^^^^^^^^^^^ | |
| S_LPROC32 (0x110f) | |
| ^^^^^^^^^^^^^^^^^^ | |
| S_GPROC32 (0x1110) | |
| ^^^^^^^^^^^^^^^^^^ | |
| S_REGREL32 (0x1111) | |
| ^^^^^^^^^^^^^^^^^^^ | |
| S_COMPILE2 (0x1116) | |
| ^^^^^^^^^^^^^^^^^^^ | |
| S_UNAMESPACE (0x1124) | |
| ^^^^^^^^^^^^^^^^^^^^^ | |
| S_TRAMPOLINE (0x112c) | |
| ^^^^^^^^^^^^^^^^^^^^^ | |
| S_SECTION (0x1136) | |
| ^^^^^^^^^^^^^^^^^^ | |
| S_COFFGROUP (0x1137) | |
| ^^^^^^^^^^^^^^^^^^^^ | |
| S_EXPORT (0x1138) | |
| ^^^^^^^^^^^^^^^^^ | |
| S_CALLSITEINFO (0x1139) | |
| ^^^^^^^^^^^^^^^^^^^^^^^ | |
| S_FRAMECOOKIE (0x113a) | |
| ^^^^^^^^^^^^^^^^^^^^^^ | |
| S_COMPILE3 (0x113c) | |
| ^^^^^^^^^^^^^^^^^^^ | |
| S_ENVBLOCK (0x113d) | |
| ^^^^^^^^^^^^^^^^^^^ | |
| .. _s_local: | |
| S_LOCAL (0x113e) | |
| ^^^^^^^^^^^^^^^^ | |
| Defines a local variable. | |
| This record is followed by a series of ``S_DEFRANGE*`` records that define the | |
| live range of this variable. | |
| .. code:: cpp | |
| struct LocalSym { | |
| /// The type of this variable (TPI type index). | |
| uint32_t Type; | |
| /// See enum below. | |
| uint16_t Flags; | |
| /// Name of the variable. A zero terminated string. | |
| char Name[]; | |
| }; | |
| enum class LocalSymFlags : uint16_t { | |
| None = 0, | |
| IsParameter = 1 << 0, | |
| IsAddressTaken = 1 << 1, | |
| IsCompilerGenerated = 1 << 2, | |
| IsAggregate = 1 << 3, | |
| IsAggregated = 1 << 4, | |
| IsAliased = 1 << 5, | |
| IsAlias = 1 << 6, | |
| IsReturnValue = 1 << 7, | |
| IsOptimizedOut = 1 << 8, | |
| IsEnregisteredGlobal = 1 << 9, | |
| IsEnregisteredStatic = 1 << 10, | |
| }; | |
| All ``S_DEFRANGE*`` records consist of a header followed by | |
| ``LocalVariableAddrRange`` and a list of ``LocalVariableAddrGap`` (until the | |
| record length is reached) except for the | |
| `S_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE <_defrange_framepointer_rel_full_scope>`_ | |
| record. | |
| .. code:: cpp | |
| /// A live range of a variable. | |
| struct LocalVariableAddrRange { | |
| /// Starting offset in the section | |
| uint32_t OffsetStart; | |
| /// Index of the section | |
| uint16_t ISectStart; | |
| /// Size of the range | |
| uint16_t Range; | |
| }; | |
| /// A subrange of a `LocalVariableAddrRange` where a variable is _not_ live. | |
| struct LocalVariableAddrGap { | |
| /// Start of the range, relative to `LocalVariableAddrRange::OffsetStart` | |
| uint16_t GapStartOffset; | |
| /// Size of the range | |
| uint16_t Range; | |
| }; | |
| The following records only describe the header. | |
| S_DEFRANGE (0x113f) | |
| ^^^^^^^^^^^^^^^^^^^ | |
| .. FIXME: Document the DIA programs. Are these the same as FPO programs? | |
| A live range expressed as a DIA program. | |
| .. code:: cpp | |
| struct DefrangeSymHeader { | |
| /// DIA program to evaluate the value of the symbol | |
| uint32_t Program; | |
| }; | |
| S_DEFRANGE_SUBFIELD (0x1140) | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| A live range of sub field of variable (e.g. ``local.i``). | |
| .. code:: cpp | |
| struct DefrangeSubfieldSymHeader { | |
| /// DIA program to evaluate the value of the symbol | |
| uint32_t Program; | |
| /// Offset in parent variable. | |
| uint32_t OffsetInParent; | |
| }; | |
| S_DEFRANGE_REGISTER (0x1141) | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| A live range of a variable living in a register. | |
| .. code:: cpp | |
| struct DefrangeRegisterSymHeader { | |
| /// Register to hold the value of the symbol | |
| uint16_t Register; | |
| /// May have no user name on one control flow path | |
| uint16_t MayHaveNoName : 1; | |
| uint16_t Padding : 15; | |
| }; | |
| S_DEFRANGE_FRAMEPOINTER_REL (0x1142) | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| A live range of frame variable. The register for the frame pointer is specified in ``S_FRAMEPROC``. | |
| .. code:: cpp | |
| struct DefrangeFramepointerSymHeader { | |
| /// Offset from the frame pointer | |
| int32_t Offset; | |
| }; | |
| S_DEFRANGE_SUBFIELD_REGISTER (0x1143) | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| A live range of sub field of variable (e.g. ``local.i``). | |
| .. code:: cpp | |
| struct DefrangeSubfieldRegisterSymHeader { | |
| /// Register to hold the value of the symbol | |
| uint16_t Register; | |
| /// May have no user name on one of control flow path | |
| uint16_t MayHaveNoName : 1; | |
| uint16_t Padding1 : 15; | |
| /// Offset in parent variable | |
| uint32_t OffsetInParent : 12; | |
| uint32_t Padding2 : 20; | |
| }; | |
| .. _defrange_framepointer_rel_full_scope: | |
| S_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE (0x1144) | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| A frame variable valid in all function scope. | |
| .. code:: cpp | |
| struct DefrangeFramepointerRelFullScopeSymHeader { | |
| /// Offset from the frame pointer | |
| int32_t Offset; | |
| }; | |
| S_DEFRANGE_REGISTER_REL (0x1145) | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| A live range of variable relative to a register (range version of ``S_REGREL32``). | |
| .. code:: cpp | |
| struct DefrangeRegisterRelSymHeader { | |
| /// Register to hold the base pointer of the symbol | |
| uint16_t Register; | |
| /// Spilled member for s.i | |
| uint16_t SpilledUdtMember : 1; | |
| uint16_t Padding : 3; | |
| /// Offset in parent variable. | |
| uint16_t OffsetInParent : 12; | |
| /// Offset to register | |
| int32_t BasePointerOffset; | |
| }; | |
| S_DEFRANGE_REGISTER_REL_INDIR (0x1177) | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| A live range of variable indirectly relative to a register (range version of ``S_REGREL32_INDIR``). | |
| .. code:: cpp | |
| struct DefrangeRegisterRelIndirSymHeader { | |
| /// Register to hold the base pointer of the symbol | |
| uint16_t Register; | |
| /// Spilled member for s.i | |
| uint16_t SpilledUdtMember : 1; | |
| uint16_t Padding : 3; | |
| /// Offset in parent variable. | |
| uint16_t OffsetInParent : 12; | |
| /// Offset to register | |
| int32_t BasePointerOffset; | |
| /// Offset to add after dereferencing `Register + BasePointerOffset` | |
| int32_t OffsetInUdt; | |
| }; | |
| S_LPROC32_ID (0x1146) | |
| ^^^^^^^^^^^^^^^^^^^^^ | |
| S_GPROC32_ID (0x1147) | |
| ^^^^^^^^^^^^^^^^^^^^^ | |
| S_BUILDINFO (0x114c) | |
| ^^^^^^^^^^^^^^^^^^^^ | |
| S_INLINESITE (0x114d) | |
| ^^^^^^^^^^^^^^^^^^^^^ | |
| S_INLINESITE_END (0x114e) | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| S_PROC_ID_END (0x114f) | |
| ^^^^^^^^^^^^^^^^^^^^^^ | |
| S_FILESTATIC (0x1153) | |
| ^^^^^^^^^^^^^^^^^^^^^ | |
| S_LPROC32_DPC (0x1155) | |
| ^^^^^^^^^^^^^^^^^^^^^^ | |
| S_LPROC32_DPC_ID (0x1156) | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| S_CALLEES (0x115a) | |
| ^^^^^^^^^^^^^^^^^^ | |
| S_CALLERS (0x115b) | |
| ^^^^^^^^^^^^^^^^^^ | |
| S_HEAPALLOCSITE (0x115e) | |
| ^^^^^^^^^^^^^^^^^^^^^^^^ | |
| S_FASTLINK (0x1167) | |
| ^^^^^^^^^^^^^^^^^^^ | |
| S_INLINEES (0x1168) | |
| ^^^^^^^^^^^^^^^^^^^ | |
| S_REGREL32_INDIR (0x1171) | |
| ^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| This encodes a variable at the location ``*($Register + Offset) + OffsetInUdt``. | |
| It's equivalent to the following DWARF location expression: | |
| .. code:: | |
| DW_OP_breg{corresponding DWARF register} {Offset} | |
| DW_OP_deref | |
| DW_OP_plus_uconst {OffsetInUdt} | |
| It's used in C++ 17 structured bindings for example: | |
| .. code:: cpp | |
| struct Foo { int a, b; }; | |
| void fn() { | |
| Foo f = {1, 2}; | |
| // ╰─ S_REGREL32{ reg = rsp, offset = 0 } | |
| auto &[x, y] = f; | |
| // │ ╰─ S_REGREL32_INDIR{ reg = rsp, offset = 8, offset-in-udt = 4, type = int } | |
| // ╰─ S_REGREL32_INDIR{ reg = rsp, offset = 8, offset-in-udt = 0, type = int } | |
| } | |
| The ``S_REGREL32_INDIR`` symbol for ``y`` from above looks like this: | |
| ============ ============ ============ ======== ======== | |
| Offset Type OffsetInUdt Register Name | |
| ============ ============ ============ ======== ======== | |
| ``08000000`` ``74000000`` ``04000000`` ``4F01`` ``7900`` | |
| 8 int 4 RSP "a" | |
| ============ ============ ============ ======== ======== | |
| .. _module_and_global_symbols: | |
| Symbols which can go in either/both of the module info stream & global stream | |
| ----------------------------------------------------------------------------- | |
| S_CONSTANT (0x1107) | |
| ^^^^^^^^^^^^^^^^^^^ | |
| S_UDT (0x1108) | |
| ^^^^^^^^^^^^^^ | |
| S_LDATA32 (0x110c) | |
| ^^^^^^^^^^^^^^^^^^ | |
| S_LTHREAD32 (0x1112) | |
| ^^^^^^^^^^^^^^^^^^^^ | |
| S_LMANDATA (0x111c) | |
| ^^^^^^^^^^^^^^^^^^^ | |
| S_MANCONSTANT (0x112d) | |
| ^^^^^^^^^^^^^^^^^^^^^^ | |