| 1 | //===-- ELFDump.cpp - ELF-specific dumper -----------------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | /// |
| 9 | /// \file |
| 10 | /// This file implements the ELF-specific dumper for llvm-objdump. |
| 11 | /// |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "ELFDump.h" |
| 15 | |
| 16 | #include "llvm-objdump.h" |
| 17 | #include "llvm/Demangle/Demangle.h" |
| 18 | #include "llvm/Object/ELFObjectFile.h" |
| 19 | #include "llvm/Support/Format.h" |
| 20 | #include "llvm/Support/raw_ostream.h" |
| 21 | |
| 22 | using namespace llvm; |
| 23 | using namespace llvm::object; |
| 24 | using namespace llvm::objdump; |
| 25 | |
| 26 | namespace { |
| 27 | template <typename ELFT> class ELFDumper : public Dumper { |
| 28 | public: |
| 29 | ELFDumper(const ELFObjectFile<ELFT> &O) : Dumper(O), Obj(O) {} |
| 30 | void printPrivateHeaders() override; |
| 31 | void printDynamicRelocations() override; |
| 32 | |
| 33 | private: |
| 34 | const ELFObjectFile<ELFT> &Obj; |
| 35 | |
| 36 | const ELFFile<ELFT> &getELFFile() const { return Obj.getELFFile(); } |
| 37 | void printDynamicSection(); |
| 38 | void printProgramHeaders(); |
| 39 | void printSymbolVersion(); |
| 40 | void printSymbolVersionDependency(const typename ELFT::Shdr &Sec); |
| 41 | }; |
| 42 | } // namespace |
| 43 | |
| 44 | template <class ELFT> |
| 45 | static std::unique_ptr<Dumper> createDumper(const ELFObjectFile<ELFT> &Obj) { |
| 46 | return std::make_unique<ELFDumper<ELFT>>(Obj); |
| 47 | } |
| 48 | |
| 49 | std::unique_ptr<Dumper> |
| 50 | objdump::createELFDumper(const object::ELFObjectFileBase &Obj) { |
| 51 | if (const auto *O = dyn_cast<ELF32LEObjectFile>(Val: &Obj)) |
| 52 | return createDumper(Obj: *O); |
| 53 | if (const auto *O = dyn_cast<ELF32BEObjectFile>(Val: &Obj)) |
| 54 | return createDumper(Obj: *O); |
| 55 | if (const auto *O = dyn_cast<ELF64LEObjectFile>(Val: &Obj)) |
| 56 | return createDumper(Obj: *O); |
| 57 | return createDumper(Obj: cast<ELF64BEObjectFile>(Val: Obj)); |
| 58 | } |
| 59 | |
| 60 | template <class ELFT> |
| 61 | static Expected<StringRef> getDynamicStrTab(const ELFFile<ELFT> &Elf) { |
| 62 | auto DynamicEntriesOrError = Elf.dynamicEntries(); |
| 63 | if (!DynamicEntriesOrError) |
| 64 | return DynamicEntriesOrError.takeError(); |
| 65 | |
| 66 | typename ELFT::Xword StringTableSize{0}; |
| 67 | const uint8_t *MappedAddr = nullptr; |
| 68 | for (const typename ELFT::Dyn &Dyn : *DynamicEntriesOrError) { |
| 69 | if (Dyn.d_tag == ELF::DT_STRTAB) { |
| 70 | auto MappedAddrOrError = Elf.toMappedAddr(Dyn.getPtr()); |
| 71 | if (!MappedAddrOrError) |
| 72 | return MappedAddrOrError.takeError(); |
| 73 | MappedAddr = *MappedAddrOrError; |
| 74 | } |
| 75 | if (Dyn.d_tag == ELF::DT_STRSZ) |
| 76 | StringTableSize = Dyn.getVal(); |
| 77 | } |
| 78 | if (MappedAddr && StringTableSize) |
| 79 | return StringRef(reinterpret_cast<const char *>(MappedAddr), |
| 80 | StringTableSize); |
| 81 | |
| 82 | // If the dynamic segment is not present, or is missing the important tags, we |
| 83 | // fall back on the sections. |
| 84 | auto SectionsOrError = Elf.sections(); |
| 85 | if (!SectionsOrError) |
| 86 | return SectionsOrError.takeError(); |
| 87 | |
| 88 | for (const typename ELFT::Shdr &Sec : *SectionsOrError) { |
| 89 | if (Sec.sh_type == ELF::SHT_DYNAMIC) |
| 90 | return Elf.getLinkAsStrtab(Sec); |
| 91 | } |
| 92 | |
| 93 | return createError(Err: "dynamic string table not found" ); |
| 94 | } |
| 95 | |
| 96 | template <class ELFT> |
| 97 | static Error getRelocationValueString(const ELFObjectFile<ELFT> *Obj, |
| 98 | const RelocationRef &RelRef, |
| 99 | SmallVectorImpl<char> &Result) { |
| 100 | const ELFFile<ELFT> &EF = Obj->getELFFile(); |
| 101 | DataRefImpl Rel = RelRef.getRawDataRefImpl(); |
| 102 | auto SecOrErr = EF.getSection(Rel.d.a); |
| 103 | if (!SecOrErr) |
| 104 | return SecOrErr.takeError(); |
| 105 | |
| 106 | int64_t Addend = 0; |
| 107 | // If there is no Symbol associated with the relocation, we set the undef |
| 108 | // boolean value to 'true'. This will prevent us from calling functions that |
| 109 | // requires the relocation to be associated with a symbol. |
| 110 | // |
| 111 | // In SHT_REL case we would need to read the addend from section data. |
| 112 | // GNU objdump does not do that and we just follow for simplicity atm. |
| 113 | bool Undef = false; |
| 114 | if ((*SecOrErr)->sh_type == ELF::SHT_CREL) { |
| 115 | auto ERela = Obj->getCrel(Rel); |
| 116 | Addend = ERela.r_addend; |
| 117 | Undef = ERela.getSymbol(false) == 0; |
| 118 | } else if ((*SecOrErr)->sh_type == ELF::SHT_RELA) { |
| 119 | const typename ELFT::Rela *ERela = Obj->getRela(Rel); |
| 120 | Addend = ERela->r_addend; |
| 121 | Undef = ERela->getSymbol(false) == 0; |
| 122 | } else if ((*SecOrErr)->sh_type == ELF::SHT_REL) { |
| 123 | const typename ELFT::Rel *ERel = Obj->getRel(Rel); |
| 124 | Undef = ERel->getSymbol(false) == 0; |
| 125 | } else { |
| 126 | return make_error<BinaryError>(); |
| 127 | } |
| 128 | |
| 129 | // Default scheme is to print Target, as well as "+ <addend>" for nonzero |
| 130 | // addend. Should be acceptable for all normal purposes. |
| 131 | std::string FmtBuf; |
| 132 | raw_string_ostream Fmt(FmtBuf); |
| 133 | |
| 134 | if (!Undef) { |
| 135 | symbol_iterator SI = RelRef.getSymbol(); |
| 136 | Expected<const typename ELFT::Sym *> SymOrErr = |
| 137 | Obj->getSymbol(SI->getRawDataRefImpl()); |
| 138 | // TODO: test this error. |
| 139 | if (!SymOrErr) |
| 140 | return SymOrErr.takeError(); |
| 141 | |
| 142 | if ((*SymOrErr)->getType() == ELF::STT_SECTION) { |
| 143 | Expected<section_iterator> SymSI = SI->getSection(); |
| 144 | if (!SymSI) |
| 145 | return SymSI.takeError(); |
| 146 | const typename ELFT::Shdr *SymSec = |
| 147 | Obj->getSection((*SymSI)->getRawDataRefImpl()); |
| 148 | auto SecName = EF.getSectionName(*SymSec); |
| 149 | if (!SecName) |
| 150 | return SecName.takeError(); |
| 151 | Fmt << *SecName; |
| 152 | } else { |
| 153 | Expected<StringRef> SymName = SI->getName(); |
| 154 | if (!SymName) |
| 155 | return SymName.takeError(); |
| 156 | Fmt << (Demangle ? demangle(MangledName: *SymName) : *SymName); |
| 157 | } |
| 158 | } else { |
| 159 | Fmt << "*ABS*" ; |
| 160 | } |
| 161 | if (Addend != 0) { |
| 162 | Fmt << (Addend < 0 |
| 163 | ? "-" |
| 164 | : "+" ) << format(Fmt: "0x%" PRIx64, |
| 165 | Vals: (Addend < 0 ? -(uint64_t)Addend : (uint64_t)Addend)); |
| 166 | } |
| 167 | Fmt.flush(); |
| 168 | Result.append(in_start: FmtBuf.begin(), in_end: FmtBuf.end()); |
| 169 | return Error::success(); |
| 170 | } |
| 171 | |
| 172 | Error objdump::getELFRelocationValueString(const ELFObjectFileBase *Obj, |
| 173 | const RelocationRef &Rel, |
| 174 | SmallVectorImpl<char> &Result) { |
| 175 | if (auto *ELF32LE = dyn_cast<ELF32LEObjectFile>(Val: Obj)) |
| 176 | return getRelocationValueString(Obj: ELF32LE, RelRef: Rel, Result); |
| 177 | if (auto *ELF64LE = dyn_cast<ELF64LEObjectFile>(Val: Obj)) |
| 178 | return getRelocationValueString(Obj: ELF64LE, RelRef: Rel, Result); |
| 179 | if (auto *ELF32BE = dyn_cast<ELF32BEObjectFile>(Val: Obj)) |
| 180 | return getRelocationValueString(Obj: ELF32BE, RelRef: Rel, Result); |
| 181 | auto *ELF64BE = cast<ELF64BEObjectFile>(Val: Obj); |
| 182 | return getRelocationValueString(Obj: ELF64BE, RelRef: Rel, Result); |
| 183 | } |
| 184 | |
| 185 | template <class ELFT> |
| 186 | static uint64_t getSectionLMA(const ELFFile<ELFT> &Obj, |
| 187 | const object::ELFSectionRef &Sec) { |
| 188 | auto PhdrRangeOrErr = Obj.program_headers(); |
| 189 | if (!PhdrRangeOrErr) |
| 190 | report_fatal_error(reason: Twine(toString(PhdrRangeOrErr.takeError()))); |
| 191 | |
| 192 | // Search for a PT_LOAD segment containing the requested section. Use this |
| 193 | // segment's p_addr to calculate the section's LMA. |
| 194 | for (const typename ELFT::Phdr &Phdr : *PhdrRangeOrErr) |
| 195 | if ((Phdr.p_type == ELF::PT_LOAD) && |
| 196 | (isSectionInSegment<ELFT>( |
| 197 | Phdr, *cast<const ELFObjectFile<ELFT>>(Sec.getObject()) |
| 198 | ->getSection(Sec.getRawDataRefImpl())))) |
| 199 | return Sec.getAddress() - Phdr.p_vaddr + Phdr.p_paddr; |
| 200 | |
| 201 | // Return section's VMA if it isn't in a PT_LOAD segment. |
| 202 | return Sec.getAddress(); |
| 203 | } |
| 204 | |
| 205 | uint64_t objdump::getELFSectionLMA(const object::ELFSectionRef &Sec) { |
| 206 | if (const auto *ELFObj = dyn_cast<ELF32LEObjectFile>(Val: Sec.getObject())) |
| 207 | return getSectionLMA(Obj: ELFObj->getELFFile(), Sec); |
| 208 | else if (const auto *ELFObj = dyn_cast<ELF32BEObjectFile>(Val: Sec.getObject())) |
| 209 | return getSectionLMA(Obj: ELFObj->getELFFile(), Sec); |
| 210 | else if (const auto *ELFObj = dyn_cast<ELF64LEObjectFile>(Val: Sec.getObject())) |
| 211 | return getSectionLMA(Obj: ELFObj->getELFFile(), Sec); |
| 212 | const auto *ELFObj = cast<ELF64BEObjectFile>(Val: Sec.getObject()); |
| 213 | return getSectionLMA(Obj: ELFObj->getELFFile(), Sec); |
| 214 | } |
| 215 | |
| 216 | template <class ELFT> void ELFDumper<ELFT>::printDynamicSection() { |
| 217 | const ELFFile<ELFT> &Elf = getELFFile(); |
| 218 | auto DynamicEntriesOrErr = Elf.dynamicEntries(); |
| 219 | if (!DynamicEntriesOrErr) { |
| 220 | reportWarning(toString(DynamicEntriesOrErr.takeError()), Obj.getFileName()); |
| 221 | return; |
| 222 | } |
| 223 | ArrayRef<typename ELFT::Dyn> DynamicEntries = *DynamicEntriesOrErr; |
| 224 | |
| 225 | // Find the maximum tag name length to format the value column properly. |
| 226 | size_t MaxLen = 0; |
| 227 | for (const typename ELFT::Dyn &Dyn : DynamicEntries) |
| 228 | MaxLen = std::max(MaxLen, Elf.getDynamicTagAsString(Dyn.d_tag).size()); |
| 229 | std::string TagFmt = " %-" + std::to_string(val: MaxLen) + "s " ; |
| 230 | |
| 231 | outs() << "\nDynamic Section:\n" ; |
| 232 | |
| 233 | for (const typename ELFT::Dyn &Dyn : DynamicEntries) { |
| 234 | if (Dyn.d_tag == ELF::DT_NULL) |
| 235 | continue; |
| 236 | |
| 237 | std::string Str = Elf.getDynamicTagAsString(Dyn.d_tag); |
| 238 | |
| 239 | const char *Fmt = |
| 240 | ELFT::Is64Bits ? "0x%016" PRIx64 "\n" : "0x%08" PRIx64 "\n" ; |
| 241 | if (Dyn.d_tag == ELF::DT_NEEDED || Dyn.d_tag == ELF::DT_RPATH || |
| 242 | Dyn.d_tag == ELF::DT_RUNPATH || Dyn.d_tag == ELF::DT_SONAME || |
| 243 | Dyn.d_tag == ELF::DT_AUXILIARY || Dyn.d_tag == ELF::DT_FILTER) { |
| 244 | Expected<StringRef> StrTabOrErr = getDynamicStrTab(Elf); |
| 245 | if (StrTabOrErr) { |
| 246 | const char *Data = StrTabOrErr->data(); |
| 247 | if (Dyn.getVal() >= StrTabOrErr->size()) { |
| 248 | reportWarning("invalid string table offset, string table size: 0x" + |
| 249 | Twine::utohexstr(Val: StrTabOrErr->size()), |
| 250 | Obj.getFileName()); |
| 251 | outs() << format(Fmt: TagFmt.c_str(), Vals: Str.c_str()) |
| 252 | << format(Fmt, Vals: (uint64_t)Dyn.getVal()); |
| 253 | continue; |
| 254 | } |
| 255 | outs() << format(Fmt: TagFmt.c_str(), Vals: Str.c_str()) << Data + Dyn.getVal() |
| 256 | << "\n" ; |
| 257 | continue; |
| 258 | } |
| 259 | reportWarning(toString(E: StrTabOrErr.takeError()), Obj.getFileName()); |
| 260 | consumeError(Err: StrTabOrErr.takeError()); |
| 261 | } |
| 262 | outs() << format(Fmt: TagFmt.c_str(), Vals: Str.c_str()) |
| 263 | << format(Fmt, Vals: (uint64_t)Dyn.getVal()); |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | template <class ELFT> void ELFDumper<ELFT>::() { |
| 268 | outs() << "\nProgram Header:\n" ; |
| 269 | auto = getELFFile().program_headers(); |
| 270 | if (!ProgramHeaderOrError) { |
| 271 | reportWarning("unable to read program headers: " + |
| 272 | toString(ProgramHeaderOrError.takeError()), |
| 273 | Obj.getFileName()); |
| 274 | return; |
| 275 | } |
| 276 | |
| 277 | for (const typename ELFT::Phdr &Phdr : *ProgramHeaderOrError) { |
| 278 | switch (Phdr.p_type) { |
| 279 | case ELF::PT_DYNAMIC: |
| 280 | outs() << " DYNAMIC " ; |
| 281 | break; |
| 282 | case ELF::PT_GNU_EH_FRAME: |
| 283 | outs() << "EH_FRAME " ; |
| 284 | break; |
| 285 | case ELF::PT_GNU_RELRO: |
| 286 | outs() << " RELRO " ; |
| 287 | break; |
| 288 | case ELF::PT_GNU_PROPERTY: |
| 289 | outs() << "PROPERTY " ; |
| 290 | break; |
| 291 | case ELF::PT_GNU_STACK: |
| 292 | outs() << " STACK " ; |
| 293 | break; |
| 294 | case ELF::PT_INTERP: |
| 295 | outs() << " INTERP " ; |
| 296 | break; |
| 297 | case ELF::PT_LOAD: |
| 298 | outs() << " LOAD " ; |
| 299 | break; |
| 300 | case ELF::PT_NOTE: |
| 301 | outs() << " NOTE " ; |
| 302 | break; |
| 303 | case ELF::PT_OPENBSD_BOOTDATA: |
| 304 | outs() << "OPENBSD_BOOTDATA " ; |
| 305 | break; |
| 306 | case ELF::PT_OPENBSD_MUTABLE: |
| 307 | outs() << "OPENBSD_MUTABLE " ; |
| 308 | break; |
| 309 | case ELF::PT_OPENBSD_NOBTCFI: |
| 310 | outs() << "OPENBSD_NOBTCFI " ; |
| 311 | break; |
| 312 | case ELF::PT_OPENBSD_RANDOMIZE: |
| 313 | outs() << "OPENBSD_RANDOMIZE " ; |
| 314 | break; |
| 315 | case ELF::PT_OPENBSD_SYSCALLS: |
| 316 | outs() << "OPENBSD_SYSCALLS " ; |
| 317 | break; |
| 318 | case ELF::PT_OPENBSD_WXNEEDED: |
| 319 | outs() << "OPENBSD_WXNEEDED " ; |
| 320 | break; |
| 321 | case ELF::PT_PHDR: |
| 322 | outs() << " PHDR " ; |
| 323 | break; |
| 324 | case ELF::PT_TLS: |
| 325 | outs() << " TLS " ; |
| 326 | break; |
| 327 | default: |
| 328 | outs() << " UNKNOWN " ; |
| 329 | } |
| 330 | |
| 331 | const char *Fmt = ELFT::Is64Bits ? "0x%016" PRIx64 " " : "0x%08" PRIx64 " " ; |
| 332 | |
| 333 | outs() << "off " << format(Fmt, Vals: (uint64_t)Phdr.p_offset) << "vaddr " |
| 334 | << format(Fmt, Vals: (uint64_t)Phdr.p_vaddr) << "paddr " |
| 335 | << format(Fmt, Vals: (uint64_t)Phdr.p_paddr) |
| 336 | << format("align 2**%u\n" , llvm::countr_zero<uint64_t>(Phdr.p_align)) |
| 337 | << " filesz " << format(Fmt, Vals: (uint64_t)Phdr.p_filesz) |
| 338 | << "memsz " << format(Fmt, Vals: (uint64_t)Phdr.p_memsz) << "flags " |
| 339 | << ((Phdr.p_flags & ELF::PF_R) ? "r" : "-" ) |
| 340 | << ((Phdr.p_flags & ELF::PF_W) ? "w" : "-" ) |
| 341 | << ((Phdr.p_flags & ELF::PF_X) ? "x" : "-" ) << "\n" ; |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | template <typename ELFT> void ELFDumper<ELFT>::printDynamicRelocations() { |
| 346 | if (!any_of(Obj.sections(), [](const ELFSectionRef Sec) { |
| 347 | return Sec.getType() == ELF::SHT_DYNAMIC; |
| 348 | })) { |
| 349 | reportError(Obj.getFileName(), "not a dynamic object" ); |
| 350 | return; |
| 351 | } |
| 352 | |
| 353 | std::vector<SectionRef> DynRelSec = |
| 354 | cast<ObjectFile>(Obj).dynamic_relocation_sections(); |
| 355 | if (DynRelSec.empty()) |
| 356 | return; |
| 357 | |
| 358 | outs() << "\nDYNAMIC RELOCATION RECORDS\n" ; |
| 359 | const uint32_t OffsetPadding = (Obj.getBytesInAddress() > 4 ? 16 : 8); |
| 360 | const uint32_t TypePadding = 24; |
| 361 | outs() << left_justify(Str: "OFFSET" , Width: OffsetPadding) << ' ' |
| 362 | << left_justify(Str: "TYPE" , Width: TypePadding) << " VALUE\n" ; |
| 363 | |
| 364 | StringRef Fmt = Obj.getBytesInAddress() > 4 ? "%016" PRIx64 : "%08" PRIx64; |
| 365 | for (const SectionRef &Section : DynRelSec) |
| 366 | for (const RelocationRef &Reloc : Section.relocations()) { |
| 367 | uint64_t Address = Reloc.getOffset(); |
| 368 | SmallString<32> RelocName; |
| 369 | SmallString<32> ValueStr; |
| 370 | Reloc.getTypeName(Result&: RelocName); |
| 371 | if (Error E = getELFRelocationValueString(&Obj, Reloc, ValueStr)) |
| 372 | reportError(std::move(E), Obj.getFileName()); |
| 373 | outs() << format(Fmt: Fmt.data(), Vals: Address) << ' ' |
| 374 | << left_justify(Str: RelocName, Width: TypePadding) << ' ' << ValueStr << '\n'; |
| 375 | } |
| 376 | } |
| 377 | |
| 378 | template <class ELFT> |
| 379 | void ELFDumper<ELFT>::printSymbolVersionDependency( |
| 380 | const typename ELFT::Shdr &Sec) { |
| 381 | outs() << "\nVersion References:\n" ; |
| 382 | Expected<std::vector<VerNeed>> V = |
| 383 | getELFFile().getVersionDependencies(Sec, this->WarningHandler); |
| 384 | if (!V) { |
| 385 | reportWarning(toString(E: V.takeError()), Obj.getFileName()); |
| 386 | return; |
| 387 | } |
| 388 | |
| 389 | raw_fd_ostream &OS = outs(); |
| 390 | for (const VerNeed &VN : *V) { |
| 391 | OS << " required from " << VN.File << ":\n" ; |
| 392 | for (const VernAux &Aux : VN.AuxV) |
| 393 | OS << format(Fmt: " 0x%08x 0x%02x %02u %s\n" , Vals: Aux.Hash, Vals: Aux.Flags, |
| 394 | Vals: Aux.Other, Vals: Aux.Name.c_str()); |
| 395 | } |
| 396 | } |
| 397 | |
| 398 | template <class ELFT> void ELFDumper<ELFT>::printSymbolVersion() { |
| 399 | const ELFFile<ELFT> &Elf = getELFFile(); |
| 400 | StringRef FileName = Obj.getFileName(); |
| 401 | ArrayRef<typename ELFT::Shdr> Sections = |
| 402 | unwrapOrError(Elf.sections(), FileName); |
| 403 | for (const typename ELFT::Shdr &Shdr : Sections) { |
| 404 | if (Shdr.sh_type != ELF::SHT_GNU_verneed && |
| 405 | Shdr.sh_type != ELF::SHT_GNU_verdef) |
| 406 | continue; |
| 407 | |
| 408 | if (Shdr.sh_type == ELF::SHT_GNU_verneed) { |
| 409 | printSymbolVersionDependency(Sec: Shdr); |
| 410 | } else { |
| 411 | OS << "\nVersion definitions:\n" ; |
| 412 | Expected<std::vector<VerDef>> V = |
| 413 | getELFFile().getVersionDefinitions(Shdr); |
| 414 | if (!V) { |
| 415 | this->reportUniqueWarning(V.takeError()); |
| 416 | continue; |
| 417 | } |
| 418 | for (const VerDef &Def : *V) { |
| 419 | OS << Def.Ndx << ' ' << format_hex(N: Def.Flags, Width: 4) << ' ' |
| 420 | << format_hex(N: Def.Hash, Width: 10) << ' ' << Def.Name << '\n'; |
| 421 | if (!Def.AuxV.empty()) { |
| 422 | for (auto [I, Aux] : enumerate(First: Def.AuxV)) |
| 423 | OS << (I ? ' ' : '\t') << Aux.Name; |
| 424 | OS << '\n'; |
| 425 | } |
| 426 | } |
| 427 | } |
| 428 | } |
| 429 | } |
| 430 | |
| 431 | template <class ELFT> void ELFDumper<ELFT>::() { |
| 432 | printProgramHeaders(); |
| 433 | printDynamicSection(); |
| 434 | printSymbolVersion(); |
| 435 | } |
| 436 | |