diff --git a/projects/rocprofiler-sdk/source/lib/rocprofiler-sdk-rocattach/symbol_lookup.cpp b/projects/rocprofiler-sdk/source/lib/rocprofiler-sdk-rocattach/symbol_lookup.cpp index fcaabfb7dbbc..16edcd47d8c2 100644 --- a/projects/rocprofiler-sdk/source/lib/rocprofiler-sdk-rocattach/symbol_lookup.cpp +++ b/projects/rocprofiler-sdk/source/lib/rocprofiler-sdk-rocattach/symbol_lookup.cpp @@ -581,54 +581,66 @@ calculate_load_bias(const target_elf& elf, const mapped_object& object) auto page_size = (page_size_value > 0) ? static_cast(page_size_value) : uint64_t{4096}; - auto first_load_segment = std::min_element( - elf.load_segments.begin(), elf.load_segments.end(), [](const auto& lhs, const auto& rhs) { - return lhs.p_vaddr < rhs.p_vaddr; - }); - if(first_load_segment == elf.load_segments.end()) + // Accept a bias only if every file-backed PT_LOAD segment has a maps entry + // at bias + page-aligned p_vaddr with the matching file page. Segments merged + // into one maps entry are not handled. More than one surviving bias (e.g. the + // same inode mapped twice) is rejected as ambiguous. + auto segment_is_mapped_at_bias = [&](const Elf64_Phdr& segment, uint64_t bias) { + if(segment.p_filesz == 0) return true; + + auto segment_file_page = align_down(segment.p_offset, page_size); + auto segment_virtual_page = align_down(segment.p_vaddr, page_size); + if(!segment_file_page || !segment_virtual_page) return false; + + auto expected_start = checked_add(bias, *segment_virtual_page); + if(!expected_start) return false; + + return std::any_of( + object.mappings.begin(), object.mappings.end(), [&](const auto& mapping) { + return mapping.start == *expected_start && + mapping.file_offset == *segment_file_page; + }); + }; + + auto candidates = std::vector{}; + for(const auto& mapping : object.mappings) { - ROCP_ERROR << "[rocprofiler-sdk-rocattach] Target ELF has no PT_LOAD segments: " - << object.path; - return std::nullopt; - } + for(const auto& segment : elf.load_segments) + { + if(segment.p_filesz == 0) continue; - // Mappings are sorted by start address, so front() is the object's lowest - // mapping. That is the loader's first PT_LOAD as long as the object is - // mapped once; a second independent mapping of the same inode placed below - // it would produce a bias for the wrong instance. - auto first_mapping = object.mappings.front(); - auto segment_file_page = align_down(first_load_segment->p_offset, page_size); - auto segment_virtual_page = align_down(first_load_segment->p_vaddr, page_size); - if(!segment_file_page || !segment_virtual_page) - { - ROCP_ERROR << "[rocprofiler-sdk-rocattach] Invalid page size " << page_size - << " while calculating load bias for " << object.path; - return std::nullopt; - } - // Match the maps entry to the PT_LOAD segment by file page before using it - // to derive the ET_DYN load bias. - if(first_mapping.file_offset != *segment_file_page) - { - ROCP_ERROR << "[rocprofiler-sdk-rocattach] First target mapping for " << object.path - << " has file offset 0x" << std::hex << first_mapping.file_offset - << ", but the first PT_LOAD segment starts at file page 0x" << *segment_file_page - << std::dec; - return std::nullopt; + auto segment_file_page = align_down(segment.p_offset, page_size); + if(!segment_file_page || mapping.file_offset != *segment_file_page) continue; + + auto segment_virtual_page = align_down(segment.p_vaddr, page_size); + if(!segment_virtual_page) continue; + + auto candidate = + checked_sub(static_cast(mapping.start), *segment_virtual_page); + if(!candidate) continue; + + auto all_segments_mapped = std::all_of( + elf.load_segments.begin(), elf.load_segments.end(), [&](const auto& other) { + return segment_is_mapped_at_bias(other, *candidate); + }); + if(all_segments_mapped && + std::find(candidates.begin(), candidates.end(), *candidate) == candidates.end()) + { + candidates.emplace_back(*candidate); + } + } } - // For ET_DYN shared objects, load bias is runtime start minus page-aligned - // segment virtual address. - auto bias = checked_sub(static_cast(first_mapping.start), *segment_virtual_page); - if(!bias) + if(candidates.size() != 1) { - ROCP_ERROR << "[rocprofiler-sdk-rocattach] Invalid target mapping/segment pair for " - << object.path << ": mapping start is below segment virtual page"; + ROCP_ERROR << "[rocprofiler-sdk-rocattach] Could not calculate a unique load bias for " + << object.path << " from its PT_LOAD segments and mappings"; return std::nullopt; } ROCP_TRACE << "[rocprofiler-sdk-rocattach] Calculated target load bias for " << object.path - << " as 0x" << std::hex << *bias << std::dec; - return bias; + << " as 0x" << std::hex << candidates.front() << std::dec; + return candidates.front(); } std::optional>