1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include <elf.h> 18 #include <stdint.h> 19 20 #include <memory> 21 #include <string> 22 #include <utility> 23 24 #include <7zCrc.h> 25 #include <Xz.h> 26 #include <XzCrc64.h> 27 28 #include <unwindstack/DwarfError.h> 29 #include <unwindstack/DwarfSection.h> 30 #include <unwindstack/ElfInterface.h> 31 #include <unwindstack/Log.h> 32 #include <unwindstack/Memory.h> 33 #include <unwindstack/Regs.h> 34 35 #include "DwarfDebugFrame.h" 36 #include "DwarfEhFrame.h" 37 #include "DwarfEhFrameWithHdr.h" 38 #include "Symbols.h" 39 40 namespace unwindstack { 41 42 ElfInterface::~ElfInterface() { 43 for (auto symbol : symbols_) { 44 delete symbol; 45 } 46 } 47 48 bool ElfInterface::IsValidPc(uint64_t pc) { 49 if (!pt_loads_.empty()) { 50 for (auto& entry : pt_loads_) { 51 uint64_t start = entry.second.table_offset; 52 uint64_t end = start + entry.second.table_size; 53 if (pc >= start && pc < end) { 54 return true; 55 } 56 } 57 return false; 58 } 59 60 // No PT_LOAD data, look for a fde for this pc in the section data. 61 if (debug_frame_ != nullptr && debug_frame_->GetFdeFromPc(pc) != nullptr) { 62 return true; 63 } 64 65 if (eh_frame_ != nullptr && eh_frame_->GetFdeFromPc(pc) != nullptr) { 66 return true; 67 } 68 69 return false; 70 } 71 72 Memory* ElfInterface::CreateGnuDebugdataMemory() { 73 if (gnu_debugdata_offset_ == 0 || gnu_debugdata_size_ == 0) { 74 return nullptr; 75 } 76 77 // TODO: Only call these initialization functions once. 78 CrcGenerateTable(); 79 Crc64GenerateTable(); 80 81 std::vector<uint8_t> src(gnu_debugdata_size_); 82 if (!memory_->ReadFully(gnu_debugdata_offset_, src.data(), gnu_debugdata_size_)) { 83 gnu_debugdata_offset_ = 0; 84 gnu_debugdata_size_ = static_cast<uint64_t>(-1); 85 return nullptr; 86 } 87 88 ISzAlloc alloc; 89 CXzUnpacker state; 90 alloc.Alloc = [](void*, size_t size) { return malloc(size); }; 91 alloc.Free = [](void*, void* ptr) { return free(ptr); }; 92 93 XzUnpacker_Construct(&state, &alloc); 94 95 std::unique_ptr<MemoryBuffer> dst(new MemoryBuffer); 96 int return_val; 97 size_t src_offset = 0; 98 size_t dst_offset = 0; 99 ECoderStatus status; 100 dst->Resize(5 * gnu_debugdata_size_); 101 do { 102 size_t src_remaining = src.size() - src_offset; 103 size_t dst_remaining = dst->Size() - dst_offset; 104 if (dst_remaining < 2 * gnu_debugdata_size_) { 105 dst->Resize(dst->Size() + 2 * gnu_debugdata_size_); 106 dst_remaining += 2 * gnu_debugdata_size_; 107 } 108 return_val = XzUnpacker_Code(&state, dst->GetPtr(dst_offset), &dst_remaining, &src[src_offset], 109 &src_remaining, CODER_FINISH_ANY, &status); 110 src_offset += src_remaining; 111 dst_offset += dst_remaining; 112 } while (return_val == SZ_OK && status == CODER_STATUS_NOT_FINISHED); 113 XzUnpacker_Free(&state); 114 if (return_val != SZ_OK || !XzUnpacker_IsStreamWasFinished(&state)) { 115 gnu_debugdata_offset_ = 0; 116 gnu_debugdata_size_ = static_cast<uint64_t>(-1); 117 return nullptr; 118 } 119 120 // Shrink back down to the exact size. 121 dst->Resize(dst_offset); 122 123 return dst.release(); 124 } 125 126 template <typename AddressType> 127 void ElfInterface::InitHeadersWithTemplate() { 128 if (eh_frame_hdr_offset_ != 0) { 129 eh_frame_.reset(new DwarfEhFrameWithHdr<AddressType>(memory_)); 130 if (!eh_frame_->Init(eh_frame_hdr_offset_, eh_frame_hdr_size_)) { 131 eh_frame_.reset(nullptr); 132 } 133 } 134 135 if (eh_frame_.get() == nullptr && eh_frame_offset_ != 0) { 136 // If there is an eh_frame section without an eh_frame_hdr section, 137 // or using the frame hdr object failed to init. 138 eh_frame_.reset(new DwarfEhFrame<AddressType>(memory_)); 139 if (!eh_frame_->Init(eh_frame_offset_, eh_frame_size_)) { 140 eh_frame_.reset(nullptr); 141 } 142 } 143 144 if (eh_frame_.get() == nullptr) { 145 eh_frame_hdr_offset_ = 0; 146 eh_frame_hdr_size_ = static_cast<uint64_t>(-1); 147 eh_frame_offset_ = 0; 148 eh_frame_size_ = static_cast<uint64_t>(-1); 149 } 150 151 if (debug_frame_offset_ != 0) { 152 debug_frame_.reset(new DwarfDebugFrame<AddressType>(memory_)); 153 if (!debug_frame_->Init(debug_frame_offset_, debug_frame_size_)) { 154 debug_frame_.reset(nullptr); 155 debug_frame_offset_ = 0; 156 debug_frame_size_ = static_cast<uint64_t>(-1); 157 } 158 } 159 } 160 161 template <typename EhdrType, typename PhdrType, typename ShdrType> 162 bool ElfInterface::ReadAllHeaders(uint64_t* load_bias) { 163 EhdrType ehdr; 164 if (!memory_->ReadFully(0, &ehdr, sizeof(ehdr))) { 165 last_error_.code = ERROR_MEMORY_INVALID; 166 last_error_.address = 0; 167 return false; 168 } 169 170 if (!ReadProgramHeaders<EhdrType, PhdrType>(ehdr, load_bias)) { 171 return false; 172 } 173 174 // We could still potentially unwind without the section header 175 // information, so ignore any errors. 176 if (!ReadSectionHeaders<EhdrType, ShdrType>(ehdr)) { 177 log(0, "Malformed section header found, ignoring..."); 178 } 179 return true; 180 } 181 182 template <typename EhdrType, typename PhdrType> 183 uint64_t ElfInterface::GetLoadBias(Memory* memory) { 184 EhdrType ehdr; 185 if (!memory->Read(0, &ehdr, sizeof(ehdr))) { 186 return false; 187 } 188 189 uint64_t offset = ehdr.e_phoff; 190 for (size_t i = 0; i < ehdr.e_phnum; i++, offset += ehdr.e_phentsize) { 191 PhdrType phdr; 192 if (!memory->Read(offset, &phdr, sizeof(phdr))) { 193 return 0; 194 } 195 if (phdr.p_type == PT_LOAD && phdr.p_offset == 0) { 196 return phdr.p_vaddr; 197 } 198 } 199 return 0; 200 } 201 202 template <typename EhdrType, typename PhdrType> 203 bool ElfInterface::ReadProgramHeaders(const EhdrType& ehdr, uint64_t* load_bias) { 204 uint64_t offset = ehdr.e_phoff; 205 for (size_t i = 0; i < ehdr.e_phnum; i++, offset += ehdr.e_phentsize) { 206 PhdrType phdr; 207 if (!memory_->ReadField(offset, &phdr, &phdr.p_type, sizeof(phdr.p_type))) { 208 last_error_.code = ERROR_MEMORY_INVALID; 209 last_error_.address = 210 offset + reinterpret_cast<uintptr_t>(&phdr.p_type) - reinterpret_cast<uintptr_t>(&phdr); 211 return false; 212 } 213 214 if (HandleType(offset, phdr.p_type, *load_bias)) { 215 continue; 216 } 217 218 switch (phdr.p_type) { 219 case PT_LOAD: 220 { 221 // Get the flags first, if this isn't an executable header, ignore it. 222 if (!memory_->ReadField(offset, &phdr, &phdr.p_flags, sizeof(phdr.p_flags))) { 223 last_error_.code = ERROR_MEMORY_INVALID; 224 last_error_.address = offset + reinterpret_cast<uintptr_t>(&phdr.p_flags) - 225 reinterpret_cast<uintptr_t>(&phdr); 226 return false; 227 } 228 if ((phdr.p_flags & PF_X) == 0) { 229 continue; 230 } 231 232 if (!memory_->ReadField(offset, &phdr, &phdr.p_vaddr, sizeof(phdr.p_vaddr))) { 233 last_error_.code = ERROR_MEMORY_INVALID; 234 last_error_.address = offset + reinterpret_cast<uintptr_t>(&phdr.p_vaddr) - 235 reinterpret_cast<uintptr_t>(&phdr); 236 return false; 237 } 238 if (!memory_->ReadField(offset, &phdr, &phdr.p_offset, sizeof(phdr.p_offset))) { 239 last_error_.code = ERROR_MEMORY_INVALID; 240 last_error_.address = offset + reinterpret_cast<uintptr_t>(&phdr.p_offset) - 241 reinterpret_cast<uintptr_t>(&phdr); 242 return false; 243 } 244 if (!memory_->ReadField(offset, &phdr, &phdr.p_memsz, sizeof(phdr.p_memsz))) { 245 last_error_.code = ERROR_MEMORY_INVALID; 246 last_error_.address = offset + reinterpret_cast<uintptr_t>(&phdr.p_memsz) - 247 reinterpret_cast<uintptr_t>(&phdr); 248 return false; 249 } 250 pt_loads_[phdr.p_offset] = LoadInfo{phdr.p_offset, phdr.p_vaddr, 251 static_cast<size_t>(phdr.p_memsz)}; 252 if (phdr.p_offset == 0) { 253 *load_bias = phdr.p_vaddr; 254 } 255 break; 256 } 257 258 case PT_GNU_EH_FRAME: 259 if (!memory_->ReadField(offset, &phdr, &phdr.p_offset, sizeof(phdr.p_offset))) { 260 last_error_.code = ERROR_MEMORY_INVALID; 261 last_error_.address = offset + reinterpret_cast<uintptr_t>(&phdr.p_offset) - 262 reinterpret_cast<uintptr_t>(&phdr); 263 return false; 264 } 265 // This is really the pointer to the .eh_frame_hdr section. 266 eh_frame_hdr_offset_ = phdr.p_offset; 267 if (!memory_->ReadField(offset, &phdr, &phdr.p_memsz, sizeof(phdr.p_memsz))) { 268 last_error_.code = ERROR_MEMORY_INVALID; 269 last_error_.address = offset + reinterpret_cast<uintptr_t>(&phdr.p_memsz) - 270 reinterpret_cast<uintptr_t>(&phdr); 271 return false; 272 } 273 eh_frame_hdr_size_ = phdr.p_memsz; 274 break; 275 276 case PT_DYNAMIC: 277 if (!memory_->ReadField(offset, &phdr, &phdr.p_offset, sizeof(phdr.p_offset))) { 278 last_error_.code = ERROR_MEMORY_INVALID; 279 last_error_.address = offset + reinterpret_cast<uintptr_t>(&phdr.p_offset) - 280 reinterpret_cast<uintptr_t>(&phdr); 281 return false; 282 } 283 dynamic_offset_ = phdr.p_offset; 284 if (!memory_->ReadField(offset, &phdr, &phdr.p_vaddr, sizeof(phdr.p_vaddr))) { 285 last_error_.code = ERROR_MEMORY_INVALID; 286 last_error_.address = offset + reinterpret_cast<uintptr_t>(&phdr.p_vaddr) - 287 reinterpret_cast<uintptr_t>(&phdr); 288 return false; 289 } 290 dynamic_vaddr_ = phdr.p_vaddr; 291 if (!memory_->ReadField(offset, &phdr, &phdr.p_memsz, sizeof(phdr.p_memsz))) { 292 last_error_.code = ERROR_MEMORY_INVALID; 293 last_error_.address = offset + reinterpret_cast<uintptr_t>(&phdr.p_memsz) - 294 reinterpret_cast<uintptr_t>(&phdr); 295 return false; 296 } 297 dynamic_size_ = phdr.p_memsz; 298 break; 299 } 300 } 301 return true; 302 } 303 304 template <typename EhdrType, typename ShdrType> 305 bool ElfInterface::ReadSectionHeaders(const EhdrType& ehdr) { 306 uint64_t offset = ehdr.e_shoff; 307 uint64_t sec_offset = 0; 308 uint64_t sec_size = 0; 309 310 // Get the location of the section header names. 311 // If something is malformed in the header table data, we aren't going 312 // to terminate, we'll simply ignore this part. 313 ShdrType shdr; 314 if (ehdr.e_shstrndx < ehdr.e_shnum) { 315 uint64_t sh_offset = offset + ehdr.e_shstrndx * ehdr.e_shentsize; 316 if (memory_->ReadField(sh_offset, &shdr, &shdr.sh_offset, sizeof(shdr.sh_offset)) && 317 memory_->ReadField(sh_offset, &shdr, &shdr.sh_size, sizeof(shdr.sh_size))) { 318 sec_offset = shdr.sh_offset; 319 sec_size = shdr.sh_size; 320 } 321 } 322 323 // Skip the first header, it's always going to be NULL. 324 offset += ehdr.e_shentsize; 325 for (size_t i = 1; i < ehdr.e_shnum; i++, offset += ehdr.e_shentsize) { 326 if (!memory_->Read(offset, &shdr, sizeof(shdr))) { 327 last_error_.code = ERROR_MEMORY_INVALID; 328 last_error_.address = offset; 329 return false; 330 } 331 332 if (shdr.sh_type == SHT_SYMTAB || shdr.sh_type == SHT_DYNSYM) { 333 // Need to go get the information about the section that contains 334 // the string terminated names. 335 ShdrType str_shdr; 336 if (shdr.sh_link >= ehdr.e_shnum) { 337 last_error_.code = ERROR_UNWIND_INFO; 338 return false; 339 } 340 uint64_t str_offset = ehdr.e_shoff + shdr.sh_link * ehdr.e_shentsize; 341 if (!memory_->Read(str_offset, &str_shdr, sizeof(str_shdr))) { 342 last_error_.code = ERROR_MEMORY_INVALID; 343 last_error_.address = str_offset; 344 return false; 345 } 346 if (str_shdr.sh_type != SHT_STRTAB) { 347 last_error_.code = ERROR_UNWIND_INFO; 348 return false; 349 } 350 symbols_.push_back(new Symbols(shdr.sh_offset, shdr.sh_size, shdr.sh_entsize, 351 str_shdr.sh_offset, str_shdr.sh_size)); 352 } else if (shdr.sh_type == SHT_PROGBITS && sec_size != 0) { 353 // Look for the .debug_frame and .gnu_debugdata. 354 if (shdr.sh_name < sec_size) { 355 std::string name; 356 if (memory_->ReadString(sec_offset + shdr.sh_name, &name)) { 357 uint64_t* offset_ptr = nullptr; 358 uint64_t* size_ptr = nullptr; 359 if (name == ".debug_frame") { 360 offset_ptr = &debug_frame_offset_; 361 size_ptr = &debug_frame_size_; 362 } else if (name == ".gnu_debugdata") { 363 offset_ptr = &gnu_debugdata_offset_; 364 size_ptr = &gnu_debugdata_size_; 365 } else if (name == ".eh_frame") { 366 offset_ptr = &eh_frame_offset_; 367 size_ptr = &eh_frame_size_; 368 } else if (eh_frame_hdr_offset_ == 0 && name == ".eh_frame_hdr") { 369 offset_ptr = &eh_frame_hdr_offset_; 370 size_ptr = &eh_frame_hdr_size_; 371 } 372 if (offset_ptr != nullptr) { 373 *offset_ptr = shdr.sh_offset; 374 *size_ptr = shdr.sh_size; 375 } 376 } 377 } 378 } else if (shdr.sh_type == SHT_STRTAB) { 379 // In order to read soname, keep track of address to offset mapping. 380 strtabs_.push_back(std::make_pair<uint64_t, uint64_t>(static_cast<uint64_t>(shdr.sh_addr), 381 static_cast<uint64_t>(shdr.sh_offset))); 382 } 383 } 384 return true; 385 } 386 387 template <typename DynType> 388 bool ElfInterface::GetSonameWithTemplate(std::string* soname) { 389 if (soname_type_ == SONAME_INVALID) { 390 return false; 391 } 392 if (soname_type_ == SONAME_VALID) { 393 *soname = soname_; 394 return true; 395 } 396 397 soname_type_ = SONAME_INVALID; 398 399 uint64_t soname_offset = 0; 400 uint64_t strtab_addr = 0; 401 uint64_t strtab_size = 0; 402 403 // Find the soname location from the dynamic headers section. 404 DynType dyn; 405 uint64_t offset = dynamic_offset_; 406 uint64_t max_offset = offset + dynamic_size_; 407 for (uint64_t offset = dynamic_offset_; offset < max_offset; offset += sizeof(DynType)) { 408 if (!memory_->ReadFully(offset, &dyn, sizeof(dyn))) { 409 last_error_.code = ERROR_MEMORY_INVALID; 410 last_error_.address = offset; 411 return false; 412 } 413 414 if (dyn.d_tag == DT_STRTAB) { 415 strtab_addr = dyn.d_un.d_ptr; 416 } else if (dyn.d_tag == DT_STRSZ) { 417 strtab_size = dyn.d_un.d_val; 418 } else if (dyn.d_tag == DT_SONAME) { 419 soname_offset = dyn.d_un.d_val; 420 } else if (dyn.d_tag == DT_NULL) { 421 break; 422 } 423 } 424 425 // Need to map the strtab address to the real offset. 426 for (const auto& entry : strtabs_) { 427 if (entry.first == strtab_addr) { 428 soname_offset = entry.second + soname_offset; 429 if (soname_offset >= entry.second + strtab_size) { 430 return false; 431 } 432 if (!memory_->ReadString(soname_offset, &soname_)) { 433 return false; 434 } 435 soname_type_ = SONAME_VALID; 436 *soname = soname_; 437 return true; 438 } 439 } 440 return false; 441 } 442 443 template <typename SymType> 444 bool ElfInterface::GetFunctionNameWithTemplate(uint64_t addr, uint64_t load_bias, std::string* name, 445 uint64_t* func_offset) { 446 if (symbols_.empty()) { 447 return false; 448 } 449 450 for (const auto symbol : symbols_) { 451 if (symbol->GetName<SymType>(addr, load_bias, memory_, name, func_offset)) { 452 return true; 453 } 454 } 455 return false; 456 } 457 458 template <typename SymType> 459 bool ElfInterface::GetGlobalVariableWithTemplate(const std::string& name, uint64_t* memory_address) { 460 if (symbols_.empty()) { 461 return false; 462 } 463 464 for (const auto symbol : symbols_) { 465 if (symbol->GetGlobal<SymType>(memory_, name, memory_address)) { 466 return true; 467 } 468 } 469 return false; 470 } 471 472 bool ElfInterface::Step(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory, 473 bool* finished) { 474 last_error_.code = ERROR_NONE; 475 last_error_.address = 0; 476 477 // Adjust the load bias to get the real relative pc. 478 if (pc < load_bias) { 479 last_error_.code = ERROR_UNWIND_INFO; 480 return false; 481 } 482 uint64_t adjusted_pc = pc - load_bias; 483 484 // Try the debug_frame first since it contains the most specific unwind 485 // information. 486 DwarfSection* debug_frame = debug_frame_.get(); 487 if (debug_frame != nullptr && debug_frame->Step(adjusted_pc, regs, process_memory, finished)) { 488 return true; 489 } 490 491 // Try the eh_frame next. 492 DwarfSection* eh_frame = eh_frame_.get(); 493 if (eh_frame != nullptr && eh_frame->Step(adjusted_pc, regs, process_memory, finished)) { 494 return true; 495 } 496 497 // Finally try the gnu_debugdata interface, but always use a zero load bias. 498 if (gnu_debugdata_interface_ != nullptr && 499 gnu_debugdata_interface_->Step(pc, 0, regs, process_memory, finished)) { 500 return true; 501 } 502 503 // Set the error code based on the first error encountered. 504 DwarfSection* section = nullptr; 505 if (debug_frame_ != nullptr) { 506 section = debug_frame_.get(); 507 } else if (eh_frame_ != nullptr) { 508 section = eh_frame_.get(); 509 } else if (gnu_debugdata_interface_ != nullptr) { 510 last_error_ = gnu_debugdata_interface_->last_error(); 511 return false; 512 } else { 513 return false; 514 } 515 516 // Convert the DWARF ERROR to an external error. 517 DwarfErrorCode code = section->LastErrorCode(); 518 switch (code) { 519 case DWARF_ERROR_NONE: 520 last_error_.code = ERROR_NONE; 521 break; 522 523 case DWARF_ERROR_MEMORY_INVALID: 524 last_error_.code = ERROR_MEMORY_INVALID; 525 last_error_.address = section->LastErrorAddress(); 526 break; 527 528 case DWARF_ERROR_ILLEGAL_VALUE: 529 case DWARF_ERROR_ILLEGAL_STATE: 530 case DWARF_ERROR_STACK_INDEX_NOT_VALID: 531 case DWARF_ERROR_TOO_MANY_ITERATIONS: 532 case DWARF_ERROR_CFA_NOT_DEFINED: 533 case DWARF_ERROR_NO_FDES: 534 last_error_.code = ERROR_UNWIND_INFO; 535 break; 536 537 case DWARF_ERROR_NOT_IMPLEMENTED: 538 case DWARF_ERROR_UNSUPPORTED_VERSION: 539 last_error_.code = ERROR_UNSUPPORTED; 540 break; 541 } 542 return false; 543 } 544 545 // This is an estimation of the size of the elf file using the location 546 // of the section headers and size. This assumes that the section headers 547 // are at the end of the elf file. If the elf has a load bias, the size 548 // will be too large, but this is acceptable. 549 template <typename EhdrType> 550 void ElfInterface::GetMaxSizeWithTemplate(Memory* memory, uint64_t* size) { 551 EhdrType ehdr; 552 if (!memory->ReadFully(0, &ehdr, sizeof(ehdr))) { 553 return; 554 } 555 if (ehdr.e_shnum == 0) { 556 return; 557 } 558 *size = ehdr.e_shoff + ehdr.e_shentsize * ehdr.e_shnum; 559 } 560 561 // Instantiate all of the needed template functions. 562 template void ElfInterface::InitHeadersWithTemplate<uint32_t>(); 563 template void ElfInterface::InitHeadersWithTemplate<uint64_t>(); 564 565 template bool ElfInterface::ReadAllHeaders<Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr>(uint64_t*); 566 template bool ElfInterface::ReadAllHeaders<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr>(uint64_t*); 567 568 template bool ElfInterface::ReadProgramHeaders<Elf32_Ehdr, Elf32_Phdr>(const Elf32_Ehdr&, uint64_t*); 569 template bool ElfInterface::ReadProgramHeaders<Elf64_Ehdr, Elf64_Phdr>(const Elf64_Ehdr&, uint64_t*); 570 571 template bool ElfInterface::ReadSectionHeaders<Elf32_Ehdr, Elf32_Shdr>(const Elf32_Ehdr&); 572 template bool ElfInterface::ReadSectionHeaders<Elf64_Ehdr, Elf64_Shdr>(const Elf64_Ehdr&); 573 574 template bool ElfInterface::GetSonameWithTemplate<Elf32_Dyn>(std::string*); 575 template bool ElfInterface::GetSonameWithTemplate<Elf64_Dyn>(std::string*); 576 577 template bool ElfInterface::GetFunctionNameWithTemplate<Elf32_Sym>(uint64_t, uint64_t, std::string*, 578 uint64_t*); 579 template bool ElfInterface::GetFunctionNameWithTemplate<Elf64_Sym>(uint64_t, uint64_t, std::string*, 580 uint64_t*); 581 582 template bool ElfInterface::GetGlobalVariableWithTemplate<Elf32_Sym>(const std::string&, uint64_t*); 583 template bool ElfInterface::GetGlobalVariableWithTemplate<Elf64_Sym>(const std::string&, uint64_t*); 584 585 template void ElfInterface::GetMaxSizeWithTemplate<Elf32_Ehdr>(Memory*, uint64_t*); 586 template void ElfInterface::GetMaxSizeWithTemplate<Elf64_Ehdr>(Memory*, uint64_t*); 587 588 template uint64_t ElfInterface::GetLoadBias<Elf32_Ehdr, Elf32_Phdr>(Memory*); 589 template uint64_t ElfInterface::GetLoadBias<Elf64_Ehdr, Elf64_Phdr>(Memory*); 590 591 } // namespace unwindstack 592