void DwarfImpl::findInlinedSubroutineDieForAddress()

in folly/experimental/symbolizer/DwarfImpl.cpp [567:722]


void DwarfImpl::findInlinedSubroutineDieForAddress(
    const Die& die,
    const DwarfLineNumberVM& lineVM,
    uint64_t address,
    folly::Optional<uint64_t> baseAddrCU,
    folly::Range<CallLocation*> locations,
    size_t& numFound) const {
  if (numFound >= locations.size()) {
    return;
  }

  forEachChild(die, [&](const Die& childDie) {
    // Between a DW_TAG_subprogram and and DW_TAG_inlined_subroutine we might
    // have arbitrary intermediary "nodes", including DW_TAG_common_block,
    // DW_TAG_lexical_block, DW_TAG_try_block, DW_TAG_catch_block and
    // DW_TAG_with_stmt, etc.
    // We can't filter with locationhere since its range may be not specified.
    // See section 2.6.2: A location list containing only an end of list entry
    // describes an object that exists in the source code but not in the
    // executable program.
    if (childDie.abbr.tag == DW_TAG_try_block ||
        childDie.abbr.tag == DW_TAG_catch_block ||
        childDie.abbr.tag == DW_TAG_entry_point ||
        childDie.abbr.tag == DW_TAG_common_block ||
        childDie.abbr.tag == DW_TAG_lexical_block) {
      findInlinedSubroutineDieForAddress(
          childDie, lineVM, address, baseAddrCU, locations, numFound);
      return true;
    }

    folly::Optional<uint64_t> lowPc;
    folly::Optional<uint64_t> highPc;
    folly::Optional<bool> isHighPcAddr;
    folly::Optional<uint64_t> abstractOrigin;
    folly::Optional<uint64_t> abstractOriginRefType;
    folly::Optional<uint64_t> callFile;
    folly::Optional<uint64_t> callLine;
    folly::Optional<uint64_t> rangeOffset;
    forEachAttribute(cu_, childDie, [&](const Attribute& attr) {
      switch (attr.spec.name) {
        case DW_AT_ranges:
          rangeOffset = boost::get<uint64_t>(attr.attrValue);
          break;
        case DW_AT_low_pc:
          lowPc = boost::get<uint64_t>(attr.attrValue);
          break;
        case DW_AT_high_pc:
          // The value of the DW_AT_high_pc attribute can be
          // an address (DW_FORM_addr*) or an offset (DW_FORM_data*).
          isHighPcAddr = attr.spec.form == DW_FORM_addr || //
              attr.spec.form == DW_FORM_addrx || //
              attr.spec.form == DW_FORM_addrx1 || //
              attr.spec.form == DW_FORM_addrx2 || //
              attr.spec.form == DW_FORM_addrx3 || //
              attr.spec.form == DW_FORM_addrx4;
          highPc = boost::get<uint64_t>(attr.attrValue);
          break;
        case DW_AT_abstract_origin:
          abstractOriginRefType = attr.spec.form;
          abstractOrigin = boost::get<uint64_t>(attr.attrValue);
          break;
        case DW_AT_call_line:
          callLine = boost::get<uint64_t>(attr.attrValue);
          break;
        case DW_AT_call_file:
          callFile = boost::get<uint64_t>(attr.attrValue);
          break;
      }
      return true; // continue forEachAttribute
    });

    // 2.17 Code Addresses and Ranges
    // Any debugging information entry describing an entity that has a
    // machine code address or range of machine code addresses,
    // which includes compilation units, module initialization, subroutines,
    // ordinary blocks, try/catch blocks, labels and the like, may have
    //  - A DW_AT_low_pc attribute for a single address,
    //  - A DW_AT_low_pc and DW_AT_high_pc pair of attributes for a
    //    single contiguous range of addresses, or
    //  - A DW_AT_ranges attribute for a non-contiguous range of addresses.
    // TODO: Support DW_TAG_entry_point and DW_TAG_common_block that don't
    // have DW_AT_low_pc/DW_AT_high_pc pairs and DW_AT_ranges.
    // TODO: Support relocated address which requires lookup in relocation map.
    bool pcMatch = lowPc && highPc && isHighPcAddr && address >= *lowPc &&
        (address < (*isHighPcAddr ? *highPc : (*lowPc + *highPc)));
    bool rangeMatch =
        rangeOffset &&
        isAddrInRangeList(
            address, baseAddrCU, rangeOffset.value(), cu_.addrSize);
    if (!pcMatch && !rangeMatch) {
      // Address doesn't match. Keep searching other children.
      return true;
    }

    if (!abstractOrigin || !abstractOriginRefType || !callLine || !callFile) {
      // We expect a single sibling DIE to match on addr, but it's missing
      // required fields. Stop searching for other DIEs.
      return false;
    }

    locations[numFound].file = lineVM.getFullFileName(*callFile);
    locations[numFound].line = *callLine;

    auto getFunctionName = [&](const CompilationUnit& srcu,
                               uint64_t dieOffset) {
      auto declDie = getDieAtOffset(srcu, dieOffset);
      // Jump to the actual function definition instead of declaration for name
      // and line info.
      auto defDie = findDefinitionDie(srcu, declDie);

      folly::StringPiece name;
      // The file and line will be set in the next inline subroutine based on
      // its DW_AT_call_file and DW_AT_call_line.
      forEachAttribute(srcu, defDie, [&](const Attribute& attr) {
        switch (attr.spec.name) {
          case DW_AT_linkage_name:
            name = boost::get<folly::StringPiece>(attr.attrValue);
            break;
          case DW_AT_name:
            // NOTE: when DW_AT_linkage_name and DW_AT_name match, dwarf
            // emitters omit DW_AT_linkage_name (to save space). If present
            // DW_AT_linkage_name should always be preferred (mangled C++ name
            // vs just the function name).
            if (name.empty()) {
              name = boost::get<folly::StringPiece>(attr.attrValue);
            }
            break;
        }
        return true; // continue forEachAttribute
      });
      return name;
    };

    // DW_AT_abstract_origin is a reference. There a 3 types of references:
    // - the reference can identify any debugging information entry within the
    //   compilation unit (DW_FORM_ref1, DW_FORM_ref2, DW_FORM_ref4,
    //   DW_FORM_ref8, DW_FORM_ref_udata). This type of reference is an offset
    //   from the first byte of the compilation header for the compilation unit
    //   containing the reference.
    // - the reference can identify any debugging information entry within a
    //   .debug_info section; in particular, it may refer to an entry in a
    //   different compilation unit (DW_FORM_ref_addr)
    // - the reference can identify any debugging information type entry that
    //   has been placed in its own type unit.
    //   Not applicable for DW_AT_abstract_origin.
    locations[numFound].name = (*abstractOriginRefType != DW_FORM_ref_addr)
        ? getFunctionName(cu_, cu_.offset + *abstractOrigin)
        : getFunctionName(
              findCompilationUnit(*abstractOrigin), *abstractOrigin);

    findInlinedSubroutineDieForAddress(
        childDie, lineVM, address, baseAddrCU, locations, ++numFound);

    return false;
  });
}