Skip to content

Instantly share code, notes, and snippets.

@mstange
Last active October 26, 2023 19:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mstange/8db7a7a0ec21ca1e93c5f7fc12077559 to your computer and use it in GitHub Desktop.
Save mstange/8db7a7a0ec21ca1e93c5f7fc12077559 to your computer and use it in GitHub Desktop.
Jitdump record format for debug info with inlines

Jitdump extensions for samply

SAMPLY_JIT_CODE_DEBUG_INFO2

SAMPLY_JIT_CODE_DEBUG_INFO2 = 827346259

Must be emitted before the JIT_CODE_LOAD record for the described function, just like JIT_CODE_DEBUG_INFO and JIT_CODE_UNWIND_INFO.

/// Describes the debug info for a function. This includes a mapping between
/// code addresses and file/line/column locations, as well as a tree of inline
/// calls. The format is designed to be easily convertible to DWARF.
///
/// Contains six lists:
///  1. The file list
///  2. The function names list
///  3. The function list, the first entry of which describes this outer function
///  4. The inline call records list
///  5. The range list
///  6. The line records list
///
struct JitCodeSamplyDebugInfo2Record {
  /// The number of filename strings in the file list in this record.
  file_count: u32,
  /// The size in bytes of the file list in this record, rounded up to a multiple of four bytes.
  file_list_size: u32,
  /// The number of function name strings in the inline origin list in this record.
  function_names_count: u32,
  /// The size in bytes of the function names list in this record, rounded up to a multiple of four bytes.
  function_names_list_size: u32,
  /// The number of functions records in the function record list. The first item in this list
  /// describes the outer function, i.e. the function 
  function_count: u32,
  /// The number of inline call records in the inline call record list in this record.
  inline_call_record_count: u32,
  /// The number of ranges in the range list in this record.
  range_count: u32,
  /// The number of line records in the line record list in this record.
  line_record_count: u32,
  /// The file list. This is a series of nul-terminated utf-8
  /// strings, with 0-3 bytes of padding at the end so that the total
  /// length matches `file_list_size`.
  file_list: char[][file_count],
  /// The function names list. This is a series of nul-terminated
  /// utf-8 strings, with 0-3 bytes of padding at the end so that the
  /// total length matches `function_names_list_size`.
  function_names_list: char[][inline_origin_count],
  /// The function list. The first element in this list describes
  /// the outer function, the remaining elements describe functions which
  /// were inlined into this function.
  function_list: FunctionRecord[function_count],
  /// The inline call list. This describes a tree structure, where the parent-child
  /// relationship is established via the parent_call field. The nodes in this list
  /// must be in DFS traversal order.
  inline_call_list: InlineCallRecord[inline_call_record_count],
  /// The range list. The ranges in this list are used by the inline call
  /// records.
  /// The order of the range list must match the order of the inline call list.
  /// Consumers of this format will iterate the inline call list and consume
  /// `inline_call.range_count` ranges for each inline call.
  range_list: RangeRecord[range_count],
  /// The line record list.
  line_record_list: LineRecord[line_record_count],
}

struct FunctionRecord {
  /// The name of this function. This is an index into the function name list.
  function_name_index: u32,
  /// The file which this function is declared in. This is an index into the
  /// file list.
  file_index: u32,
  /// The line number at which this function starts.
  start_line: u32,
  /// The column number at which this function starts.
  start_column: u32,
  /// Various flags. For example, should this function be shown in the JS-only view in the Firefox Profiler?
  flags: u32,
}

/// One entry per inlined call to a function.
struct InlineCallRecord {
  /// The depth of this inlined call. Calls from the outer function have
  /// depth zero.
  /// This must be less than or equal to `previous_inline_call.depth + 1`.
  /// Consumers of this format will find the caller by maintaining a current
  /// call stack while iterating the inline call list.
  depth: u32,
  /// The number of ranges in the range list for this call, 1 or higher.
  /// If this is 1, this call covers a contiguous range of instructions.
  /// Values greater than one allow describing non-contiguous inline calls.
  range_count: u32,
  /// The index of the called function in the function list.
  function_index: u32,
  /// The line number of the call, in the file of the caller.
  call_line_number: u32,
  /// The column number of the call, in the file of the caller.
  call_column_number: u32,
}

struct Range {
  /// The address offset, relative to the function start address,
  /// where the range of instructions which are described by this
  /// range begins.
  start_offset: u32,
  /// The number of bytes starting at `start_offset` which are
  /// described by this range.
  size: u32,
}

/// One entry per contiguous range of instructions with the same
/// file/line/col information. If there are inlines spanning the
/// line record's range, the line record describes a location inside
/// the deepest inline function.
struct LineRecord {
  /// The address offset, relative to the function start address,
  /// where the range of instructions which are described by this
  /// line record begins.
  code_offset: u32,
  /// An index into the file list for the filename.
  file_index: u32,
  /// The line number. The first line of the file is line 1.
  line_number: u32,
  /// The column number. The first column of a line is column 0.
  column_number: u32,
}

This format keeps the line information and the inline information separate. The lines refer to the location inside the deepest inline. The file/line information of the call is on the inline call record. This matches DWARF and not PDB. Also, the ranges are kept separate from the inline records, because each inline record can potentially have multiple ranges (if the instructions for the inline call are disjoint), and I didn't want to put the ranges "inside" the InlineRecord because I wanted InlineRecord to be a fixed size.

SAMPLY_JIT_TIER

SAMPLY_JIT_TIER = 827346260

A record which describes the name and color of a JIT tier, such as Interpreter, Baseline or Ion. There is a fixed list of colors, this record has an index into that list.

SAMPLY_EMBEDDED_SOURCE_FILE

SAMPLY_EMBEDDED_SOURCE_FILE = 827346261

Can be used to embed source code into the jitdump. For example a file containing the byte code (one line per byte code op), or a file containing IR code.

Wishlist

  • A way to have clean function names, and all the meta info in proper fields. For example, currently, the JIT name from V8 might be JS:~TheFunctionName thefilename.js:123:45. This contains the JIT tier, the name, the file, and the function start position.
  • A way to have multiple "source tracks" described by the debug info. For a given code address, I want to know the location in the JS file, and in the fake "byte code" file, and in the fake "IR code" file.
  • A line+inlines description format which makes it harder to de-sync the two. For example, it would be great to enforce that a given function only has lines from a single file.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment