Serenity Operating System
at master 472 lines 18 kB view raw
1/* 2 * Copyright (c) 2020-2021, Itamar S. <itamar8910@gmail.com> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include "DebugInfo.h" 8#include <AK/Debug.h> 9#include <AK/LexicalPath.h> 10#include <AK/QuickSort.h> 11#include <LibDebug/Dwarf/CompilationUnit.h> 12#include <LibDebug/Dwarf/DwarfInfo.h> 13#include <LibDebug/Dwarf/Expression.h> 14 15namespace Debug { 16 17DebugInfo::DebugInfo(ELF::Image const& elf, DeprecatedString source_root, FlatPtr base_address) 18 : m_elf(elf) 19 , m_source_root(move(source_root)) 20 , m_base_address(base_address) 21 , m_dwarf_info(m_elf) 22{ 23 prepare_variable_scopes().release_value_but_fixme_should_propagate_errors(); 24 prepare_lines().release_value_but_fixme_should_propagate_errors(); 25} 26 27ErrorOr<void> DebugInfo::prepare_variable_scopes() 28{ 29 TRY(m_dwarf_info.for_each_compilation_unit([&](Dwarf::CompilationUnit const& unit) -> ErrorOr<void> { 30 auto root = unit.root_die(); 31 TRY(parse_scopes_impl(root)); 32 return {}; 33 })); 34 return {}; 35} 36 37ErrorOr<void> DebugInfo::parse_scopes_impl(Dwarf::DIE const& die) 38{ 39 TRY(die.for_each_child([&](Dwarf::DIE const& child) -> ErrorOr<void> { 40 if (child.is_null()) 41 return {}; 42 if (!(child.tag() == Dwarf::EntryTag::SubProgram || child.tag() == Dwarf::EntryTag::LexicalBlock)) 43 return {}; 44 45 if (TRY(child.get_attribute(Dwarf::Attribute::Inline)).has_value()) { 46 dbgln_if(SPAM_DEBUG, "DWARF inlined functions are not supported"); 47 return {}; 48 } 49 if (TRY(child.get_attribute(Dwarf::Attribute::Ranges)).has_value()) { 50 dbgln_if(SPAM_DEBUG, "DWARF ranges are not supported"); 51 return {}; 52 } 53 auto name = TRY(child.get_attribute(Dwarf::Attribute::Name)); 54 55 VariablesScope scope {}; 56 scope.is_function = (child.tag() == Dwarf::EntryTag::SubProgram); 57 if (name.has_value()) 58 scope.name = TRY(name.value().as_string()); 59 60 if (!TRY(child.get_attribute(Dwarf::Attribute::LowPc)).has_value()) { 61 dbgln_if(SPAM_DEBUG, "DWARF: Couldn't find attribute LowPc for scope"); 62 return {}; 63 } 64 scope.address_low = TRY(TRY(child.get_attribute(Dwarf::Attribute::LowPc)).value().as_addr()); 65 auto high_pc = TRY(child.get_attribute(Dwarf::Attribute::HighPc)); 66 if (high_pc->type() == Dwarf::AttributeValue::Type::Address) 67 scope.address_high = TRY(high_pc->as_addr()); 68 else 69 scope.address_high = scope.address_low + high_pc->as_unsigned(); 70 71 TRY(child.for_each_child([&](Dwarf::DIE const& variable_entry) -> ErrorOr<void> { 72 if (!(variable_entry.tag() == Dwarf::EntryTag::Variable 73 || variable_entry.tag() == Dwarf::EntryTag::FormalParameter)) 74 return {}; 75 scope.dies_of_variables.append(variable_entry); 76 return {}; 77 })); 78 m_scopes.append(scope); 79 80 TRY(parse_scopes_impl(child)); 81 82 return {}; 83 })); 84 return {}; 85} 86 87ErrorOr<void> DebugInfo::prepare_lines() 88{ 89 Vector<Dwarf::LineProgram::LineInfo> all_lines; 90 TRY(m_dwarf_info.for_each_compilation_unit([&all_lines](Dwarf::CompilationUnit const& unit) -> ErrorOr<void> { 91 all_lines.extend(unit.line_program().lines()); 92 return {}; 93 })); 94 95 HashMap<DeprecatedFlyString, Optional<DeprecatedString>> memoized_full_paths; 96 auto compute_full_path = [&](DeprecatedFlyString const& file_path) -> Optional<DeprecatedString> { 97 if (file_path.view().contains("Toolchain/"sv) || file_path.view().contains("libgcc"sv)) 98 return {}; 99 if (file_path.view().starts_with("./"sv) && !m_source_root.is_null()) 100 return LexicalPath::join(m_source_root, file_path).string(); 101 if (auto index_of_serenity_slash = file_path.view().find("serenity/"sv); index_of_serenity_slash.has_value()) { 102 auto start_index = index_of_serenity_slash.value() + "serenity/"sv.length(); 103 return file_path.view().substring_view(start_index, file_path.length() - start_index); 104 } 105 return file_path; 106 }; 107 108 m_sorted_lines.ensure_capacity(all_lines.size()); 109 110 for (auto const& line_info : all_lines) { 111 auto maybe_full_path = memoized_full_paths.ensure(line_info.file, [&] { return compute_full_path(line_info.file); }); 112 if (!maybe_full_path.has_value()) 113 continue; 114 m_sorted_lines.unchecked_append({ line_info.address, maybe_full_path.release_value(), line_info.line }); 115 } 116 117 quick_sort(m_sorted_lines, [](auto& a, auto& b) { 118 return a.address < b.address; 119 }); 120 return {}; 121} 122 123Optional<DebugInfo::SourcePosition> DebugInfo::get_source_position(FlatPtr target_address) const 124{ 125 if (m_sorted_lines.is_empty()) 126 return {}; 127 if (target_address < m_sorted_lines[0].address) 128 return {}; 129 130 // TODO: We can do a binary search here 131 for (size_t i = 0; i < m_sorted_lines.size() - 1; ++i) { 132 if (m_sorted_lines[i + 1].address > target_address) { 133 return SourcePosition::from_line_info(m_sorted_lines[i]); 134 } 135 } 136 return {}; 137} 138 139Optional<DebugInfo::SourcePositionAndAddress> DebugInfo::get_address_from_source_position(DeprecatedString const& file, size_t line) const 140{ 141 DeprecatedString file_path = file; 142 if (!file_path.starts_with('/')) 143 file_path = DeprecatedString::formatted("/{}", file_path); 144 145 Optional<SourcePositionAndAddress> result; 146 for (auto const& line_entry : m_sorted_lines) { 147 if (!line_entry.file.ends_with(file_path)) 148 continue; 149 150 if (line_entry.line > line) 151 continue; 152 153 // We look for the source position that is closest to the desired position, and is not after it. 154 // For example, get_address_of_source_position("main.cpp", 73) could return the address for an instruction whose location is ("main.cpp", 72) 155 // as there might not be an instruction mapped for "main.cpp", 73. 156 if (!result.has_value() || (line_entry.line > result.value().line)) { 157 result = SourcePositionAndAddress { line_entry.file, line_entry.line, line_entry.address }; 158 } 159 } 160 return result; 161} 162 163ErrorOr<Vector<NonnullOwnPtr<DebugInfo::VariableInfo>>> DebugInfo::get_variables_in_current_scope(PtraceRegisters const& regs) const 164{ 165 Vector<NonnullOwnPtr<DebugInfo::VariableInfo>> variables; 166 167 // TODO: We can store the scopes in a better data structure 168 for (auto const& scope : m_scopes) { 169 FlatPtr ip; 170#if ARCH(X86_64) 171 ip = regs.rip; 172#elif ARCH(AARCH64) 173 TODO_AARCH64(); 174#else 175# error Unknown architecture 176#endif 177 if (ip - m_base_address < scope.address_low || ip - m_base_address >= scope.address_high) 178 continue; 179 180 for (auto const& die_entry : scope.dies_of_variables) { 181 auto variable_info = TRY(create_variable_info(die_entry, regs)); 182 if (!variable_info) 183 continue; 184 variables.append(variable_info.release_nonnull()); 185 } 186 } 187 return variables; 188} 189 190static ErrorOr<Optional<Dwarf::DIE>> parse_variable_type_die(Dwarf::DIE const& variable_die, DebugInfo::VariableInfo& variable_info) 191{ 192 auto type_die_offset = TRY(variable_die.get_attribute(Dwarf::Attribute::Type)); 193 if (!type_die_offset.has_value()) 194 return Optional<Dwarf::DIE> {}; 195 196 VERIFY(type_die_offset.value().type() == Dwarf::AttributeValue::Type::DieReference); 197 198 auto type_die = variable_die.compilation_unit().get_die_at_offset(type_die_offset.value().as_unsigned()); 199 auto type_name = TRY(type_die.get_attribute(Dwarf::Attribute::Name)); 200 if (type_name.has_value()) { 201 variable_info.type_name = TRY(type_name.value().as_string()); 202 } else { 203 dbgln("Unnamed DWARF type at offset: {}", type_die.offset()); 204 variable_info.type_name = "[Unnamed Type]"; 205 } 206 207 return type_die; 208} 209 210static ErrorOr<void> parse_variable_location(Dwarf::DIE const& variable_die, DebugInfo::VariableInfo& variable_info, PtraceRegisters const& regs) 211{ 212 auto location_info = TRY(variable_die.get_attribute(Dwarf::Attribute::Location)); 213 if (!location_info.has_value()) { 214 location_info = TRY(variable_die.get_attribute(Dwarf::Attribute::MemberLocation)); 215 } 216 217 if (!location_info.has_value()) 218 return {}; 219 220 switch (location_info.value().type()) { 221 case Dwarf::AttributeValue::Type::UnsignedNumber: 222 variable_info.location_type = DebugInfo::VariableInfo::LocationType::Address; 223 variable_info.location_data.address = location_info.value().as_unsigned(); 224 break; 225 case Dwarf::AttributeValue::Type::DwarfExpression: { 226 auto expression_bytes = location_info.value().as_raw_bytes(); 227 auto value = TRY(Dwarf::Expression::evaluate(expression_bytes, regs)); 228 229 if (value.type != Dwarf::Expression::Type::None) { 230 VERIFY(value.type == Dwarf::Expression::Type::UnsignedInteger); 231 variable_info.location_type = DebugInfo::VariableInfo::LocationType::Address; 232 variable_info.location_data.address = value.data.as_addr; 233 } 234 break; 235 } 236 default: 237 dbgln("Warning: unhandled Dwarf location type: {}", to_underlying(location_info.value().type())); 238 } 239 240 return {}; 241} 242 243ErrorOr<OwnPtr<DebugInfo::VariableInfo>> DebugInfo::create_variable_info(Dwarf::DIE const& variable_die, PtraceRegisters const& regs, u32 address_offset) const 244{ 245 VERIFY(is_variable_tag_supported(variable_die.tag())); 246 247 if (variable_die.tag() == Dwarf::EntryTag::FormalParameter 248 && !TRY(variable_die.get_attribute(Dwarf::Attribute::Name)).has_value()) { 249 // We don't want to display info for unused parameters 250 return OwnPtr<DebugInfo::VariableInfo> {}; 251 } 252 253 NonnullOwnPtr<VariableInfo> variable_info = make<VariableInfo>(); 254 auto name_attribute = TRY(variable_die.get_attribute(Dwarf::Attribute::Name)); 255 if (name_attribute.has_value()) 256 variable_info->name = TRY(name_attribute.value().as_string()); 257 258 auto type_die = TRY(parse_variable_type_die(variable_die, *variable_info)); 259 260 if (variable_die.tag() == Dwarf::EntryTag::Enumerator) { 261 auto constant = TRY(variable_die.get_attribute(Dwarf::Attribute::ConstValue)); 262 VERIFY(constant.has_value()); 263 switch (constant.value().type()) { 264 case Dwarf::AttributeValue::Type::UnsignedNumber: 265 variable_info->constant_data.as_u32 = constant.value().as_unsigned(); 266 break; 267 case Dwarf::AttributeValue::Type::SignedNumber: 268 variable_info->constant_data.as_i32 = constant.value().as_signed(); 269 break; 270 case Dwarf::AttributeValue::Type::String: 271 variable_info->constant_data.as_string = TRY(constant.value().as_string()); 272 break; 273 default: 274 VERIFY_NOT_REACHED(); 275 } 276 } else { 277 TRY(parse_variable_location(variable_die, *variable_info, regs)); 278 variable_info->location_data.address += address_offset; 279 } 280 281 if (type_die.has_value()) 282 TRY(add_type_info_to_variable(type_die.value(), regs, variable_info)); 283 284 return variable_info; 285} 286 287ErrorOr<void> DebugInfo::add_type_info_to_variable(Dwarf::DIE const& type_die, PtraceRegisters const& regs, DebugInfo::VariableInfo* parent_variable) const 288{ 289 OwnPtr<VariableInfo> type_info; 290 auto is_array_type = type_die.tag() == Dwarf::EntryTag::ArrayType; 291 292 if (type_die.tag() == Dwarf::EntryTag::EnumerationType 293 || type_die.tag() == Dwarf::EntryTag::StructureType 294 || is_array_type) { 295 type_info = TRY(create_variable_info(type_die, regs)); 296 } 297 298 TRY(type_die.for_each_child([&](Dwarf::DIE const& member) -> ErrorOr<void> { 299 if (member.is_null()) 300 return {}; 301 302 if (is_array_type && member.tag() == Dwarf::EntryTag::SubRangeType) { 303 auto upper_bound = TRY(member.get_attribute(Dwarf::Attribute::UpperBound)); 304 VERIFY(upper_bound.has_value()); 305 auto size = upper_bound.value().as_unsigned() + 1; 306 type_info->dimension_sizes.append(size); 307 return {}; 308 } 309 310 if (!is_variable_tag_supported(member.tag())) 311 return {}; 312 313 auto member_variable = TRY(create_variable_info(member, regs, parent_variable->location_data.address)); 314 VERIFY(member_variable); 315 316 if (type_die.tag() == Dwarf::EntryTag::EnumerationType) { 317 member_variable->parent = type_info.ptr(); 318 type_info->members.append(member_variable.release_nonnull()); 319 } else { 320 if (parent_variable->location_type != DebugInfo::VariableInfo::LocationType::Address) 321 return {}; 322 323 member_variable->parent = parent_variable; 324 parent_variable->members.append(member_variable.release_nonnull()); 325 } 326 327 return {}; 328 })); 329 330 if (type_info) { 331 if (is_array_type) { 332 StringBuilder array_type_name; 333 array_type_name.append(type_info->type_name); 334 for (auto array_size : type_info->dimension_sizes) { 335 array_type_name.append('['); 336 array_type_name.append(DeprecatedString::formatted("{:d}", array_size)); 337 array_type_name.append(']'); 338 } 339 parent_variable->type_name = array_type_name.to_deprecated_string(); 340 } 341 parent_variable->type = move(type_info); 342 parent_variable->type->type_tag = type_die.tag(); 343 } 344 345 return {}; 346} 347 348bool DebugInfo::is_variable_tag_supported(Dwarf::EntryTag const& tag) 349{ 350 return tag == Dwarf::EntryTag::Variable 351 || tag == Dwarf::EntryTag::Member 352 || tag == Dwarf::EntryTag::FormalParameter 353 || tag == Dwarf::EntryTag::EnumerationType 354 || tag == Dwarf::EntryTag::Enumerator 355 || tag == Dwarf::EntryTag::StructureType 356 || tag == Dwarf::EntryTag::ArrayType; 357} 358 359DeprecatedString DebugInfo::name_of_containing_function(FlatPtr address) const 360{ 361 auto function = get_containing_function(address); 362 if (!function.has_value()) 363 return {}; 364 return function.value().name; 365} 366 367Optional<DebugInfo::VariablesScope> DebugInfo::get_containing_function(FlatPtr address) const 368{ 369 for (auto const& scope : m_scopes) { 370 if (!scope.is_function || address < scope.address_low || address >= scope.address_high) 371 continue; 372 return scope; 373 } 374 return {}; 375} 376 377Vector<DebugInfo::SourcePosition> DebugInfo::source_lines_in_scope(VariablesScope const& scope) const 378{ 379 Vector<DebugInfo::SourcePosition> source_lines; 380 for (auto const& line : m_sorted_lines) { 381 if (line.address < scope.address_low) 382 continue; 383 384 if (line.address >= scope.address_high) 385 break; 386 source_lines.append(SourcePosition::from_line_info(line)); 387 } 388 return source_lines; 389} 390 391DebugInfo::SourcePosition DebugInfo::SourcePosition::from_line_info(Dwarf::LineProgram::LineInfo const& line) 392{ 393 return { line.file, line.line, line.address }; 394} 395 396ErrorOr<DebugInfo::SourcePositionWithInlines> DebugInfo::get_source_position_with_inlines(FlatPtr address) const 397{ 398 // If the address is in an "inline chain", this is the inner-most inlined position. 399 auto inner_source_position = get_source_position(address); 400 401 auto die = TRY(m_dwarf_info.get_die_at_address(address)); 402 if (!die.has_value() || die->tag() == Dwarf::EntryTag::SubroutineType) { 403 // Inline chain is empty 404 return SourcePositionWithInlines { inner_source_position, {} }; 405 } 406 407 Vector<SourcePosition> inline_chain; 408 409 auto insert_to_chain = [&](Dwarf::DIE const& die) -> ErrorOr<void> { 410 auto caller_source_path = TRY(get_source_path_of_inline(die)); 411 auto caller_line = TRY(get_line_of_inline(die)); 412 413 if (!caller_source_path.has_value() || !caller_line.has_value()) { 414 return {}; 415 } 416 417 inline_chain.append({ DeprecatedString::formatted("{}/{}", caller_source_path->directory, caller_source_path->filename), caller_line.value() }); 418 return {}; 419 }; 420 421 while (die->tag() == Dwarf::EntryTag::InlinedSubroutine) { 422 TRY(insert_to_chain(*die)); 423 424 if (!die->parent_offset().has_value()) { 425 break; 426 } 427 428 auto parent = TRY(die->compilation_unit().dwarf_info().get_cached_die_at_offset(die->parent_offset().value())); 429 if (!parent.has_value()) { 430 break; 431 } 432 die = *parent; 433 } 434 435 return SourcePositionWithInlines { inner_source_position, inline_chain }; 436} 437 438ErrorOr<Optional<Dwarf::LineProgram::DirectoryAndFile>> DebugInfo::get_source_path_of_inline(Dwarf::DIE const& die) const 439{ 440 auto caller_file = TRY(die.get_attribute(Dwarf::Attribute::CallFile)); 441 if (caller_file.has_value()) { 442 size_t file_index = 0; 443 444 if (caller_file->type() == Dwarf::AttributeValue::Type::UnsignedNumber) { 445 file_index = caller_file->as_unsigned(); 446 } else if (caller_file->type() == Dwarf::AttributeValue::Type::SignedNumber) { 447 // For some reason, the file_index is sometimes stored as a signed number. 448 auto signed_file_index = caller_file->as_signed(); 449 VERIFY(signed_file_index >= 0); 450 VERIFY(static_cast<u64>(signed_file_index) <= NumericLimits<size_t>::max()); 451 file_index = static_cast<size_t>(caller_file->as_signed()); 452 } else { 453 return Optional<Dwarf::LineProgram::DirectoryAndFile> {}; 454 } 455 456 return die.compilation_unit().line_program().get_directory_and_file(file_index); 457 } 458 return Optional<Dwarf::LineProgram::DirectoryAndFile> {}; 459} 460 461ErrorOr<Optional<uint32_t>> DebugInfo::get_line_of_inline(Dwarf::DIE const& die) const 462{ 463 auto caller_line = TRY(die.get_attribute(Dwarf::Attribute::CallLine)); 464 if (!caller_line.has_value()) 465 return Optional<uint32_t> {}; 466 467 if (caller_line->type() != Dwarf::AttributeValue::Type::UnsignedNumber) 468 return Optional<uint32_t> {}; 469 return Optional<uint32_t> { caller_line.value().as_unsigned() }; 470} 471 472}