1 //===- DWARFVerifier.cpp --------------------------------------------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "llvm/DebugInfo/DWARF/DWARFVerifier.h" 11 #include "llvm/DebugInfo/DWARF/DWARFCompileUnit.h" 12 #include "llvm/DebugInfo/DWARF/DWARFContext.h" 13 #include "llvm/DebugInfo/DWARF/DWARFDebugLine.h" 14 #include "llvm/DebugInfo/DWARF/DWARFDie.h" 15 #include "llvm/DebugInfo/DWARF/DWARFFormValue.h" 16 #include "llvm/DebugInfo/DWARF/DWARFSection.h" 17 #include "llvm/DebugInfo/DWARF/DWARFAcceleratorTable.h" 18 #include "llvm/Support/raw_ostream.h" 19 #include <map> 20 #include <set> 21 #include <vector> 22 23 using namespace llvm; 24 using namespace dwarf; 25 using namespace object; 26 27 bool DWARFVerifier::verifyUnitHeader(const DWARFDataExtractor DebugInfoData, 28 uint32_t *Offset, unsigned UnitIndex, 29 uint8_t &UnitType, bool &isUnitDWARF64) { 30 uint32_t AbbrOffset, Length; 31 uint8_t AddrSize = 0; 32 uint16_t Version; 33 bool Success = true; 34 35 bool ValidLength = false; 36 bool ValidVersion = false; 37 bool ValidAddrSize = false; 38 bool ValidType = true; 39 bool ValidAbbrevOffset = true; 40 41 uint32_t OffsetStart = *Offset; 42 Length = DebugInfoData.getU32(Offset); 43 if (Length == UINT32_MAX) { 44 isUnitDWARF64 = true; 45 OS << format( 46 "Unit[%d] is in 64-bit DWARF format; cannot verify from this point.\n", 47 UnitIndex); 48 return false; 49 } 50 Version = DebugInfoData.getU16(Offset); 51 52 if (Version >= 5) { 53 UnitType = DebugInfoData.getU8(Offset); 54 AddrSize = DebugInfoData.getU8(Offset); 55 AbbrOffset = DebugInfoData.getU32(Offset); 56 ValidType = DWARFUnit::isValidUnitType(UnitType); 57 } else { 58 UnitType = 0; 59 AbbrOffset = DebugInfoData.getU32(Offset); 60 AddrSize = DebugInfoData.getU8(Offset); 61 } 62 63 if (!DCtx.getDebugAbbrev()->getAbbreviationDeclarationSet(AbbrOffset)) 64 ValidAbbrevOffset = false; 65 66 ValidLength = DebugInfoData.isValidOffset(OffsetStart + Length + 3); 67 ValidVersion = DWARFContext::isSupportedVersion(Version); 68 ValidAddrSize = AddrSize == 4 || AddrSize == 8; 69 if (!ValidLength || !ValidVersion || !ValidAddrSize || !ValidAbbrevOffset || 70 !ValidType) { 71 Success = false; 72 OS << format("Units[%d] - start offset: 0x%08x \n", UnitIndex, OffsetStart); 73 if (!ValidLength) 74 OS << "\tError: The length for this unit is too " 75 "large for the .debug_info provided.\n"; 76 if (!ValidVersion) 77 OS << "\tError: The 16 bit unit header version is not valid.\n"; 78 if (!ValidType) 79 OS << "\tError: The unit type encoding is not valid.\n"; 80 if (!ValidAbbrevOffset) 81 OS << "\tError: The offset into the .debug_abbrev section is " 82 "not valid.\n"; 83 if (!ValidAddrSize) 84 OS << "\tError: The address size is unsupported.\n"; 85 } 86 *Offset = OffsetStart + Length + 4; 87 return Success; 88 } 89 90 bool DWARFVerifier::verifyUnitContents(DWARFUnit Unit) { 91 uint32_t NumUnitErrors = 0; 92 unsigned NumDies = Unit.getNumDIEs(); 93 for (unsigned I = 0; I < NumDies; ++I) { 94 auto Die = Unit.getDIEAtIndex(I); 95 if (Die.getTag() == DW_TAG_null) 96 continue; 97 NumUnitErrors += verifyDieRanges(Die); 98 for (auto AttrValue : Die.attributes()) { 99 NumUnitErrors += verifyDebugInfoAttribute(Die, AttrValue); 100 NumUnitErrors += verifyDebugInfoForm(Die, AttrValue); 101 } 102 } 103 return NumUnitErrors == 0; 104 } 105 106 unsigned DWARFVerifier::verifyAbbrevSection(const DWARFDebugAbbrev *Abbrev) { 107 unsigned NumErrors = 0; 108 if (Abbrev) { 109 const DWARFAbbreviationDeclarationSet *AbbrDecls = 110 Abbrev->getAbbreviationDeclarationSet(0); 111 for (auto AbbrDecl : *AbbrDecls) { 112 SmallDenseSet<uint16_t> AttributeSet; 113 for (auto Attribute : AbbrDecl.attributes()) { 114 auto Result = AttributeSet.insert(Attribute.Attr); 115 if (!Result.second) { 116 OS << "Error: Abbreviation declaration contains multiple " 117 << AttributeString(Attribute.Attr) << " attributes.\n"; 118 AbbrDecl.dump(OS); 119 ++NumErrors; 120 } 121 } 122 } 123 } 124 return NumErrors; 125 } 126 127 bool DWARFVerifier::handleDebugAbbrev() { 128 OS << "Verifying .debug_abbrev...\n"; 129 130 const DWARFObject &DObj = DCtx.getDWARFObj(); 131 bool noDebugAbbrev = DObj.getAbbrevSection().empty(); 132 bool noDebugAbbrevDWO = DObj.getAbbrevDWOSection().empty(); 133 134 if (noDebugAbbrev && noDebugAbbrevDWO) { 135 return true; 136 } 137 138 unsigned NumErrors = 0; 139 if (!noDebugAbbrev) 140 NumErrors += verifyAbbrevSection(DCtx.getDebugAbbrev()); 141 142 if (!noDebugAbbrevDWO) 143 NumErrors += verifyAbbrevSection(DCtx.getDebugAbbrevDWO()); 144 return NumErrors == 0; 145 } 146 147 bool DWARFVerifier::handleDebugInfo() { 148 OS << "Verifying .debug_info Unit Header Chain...\n"; 149 150 const DWARFObject &DObj = DCtx.getDWARFObj(); 151 DWARFDataExtractor DebugInfoData(DObj, DObj.getInfoSection(), 152 DCtx.isLittleEndian(), 0); 153 uint32_t NumDebugInfoErrors = 0; 154 uint32_t OffsetStart = 0, Offset = 0, UnitIdx = 0; 155 uint8_t UnitType = 0; 156 bool isUnitDWARF64 = false; 157 bool isHeaderChainValid = true; 158 bool hasDIE = DebugInfoData.isValidOffset(Offset); 159 while (hasDIE) { 160 OffsetStart = Offset; 161 if (!verifyUnitHeader(DebugInfoData, &Offset, UnitIdx, UnitType, 162 isUnitDWARF64)) { 163 isHeaderChainValid = false; 164 if (isUnitDWARF64) 165 break; 166 } else { 167 std::unique_ptr<DWARFUnit> Unit; 168 switch (UnitType) { 169 case dwarf::DW_UT_type: 170 case dwarf::DW_UT_split_type: { 171 DWARFUnitSection<DWARFTypeUnit> TUSection{}; 172 Unit.reset(new DWARFTypeUnit( 173 DCtx, DObj.getInfoSection(), DCtx.getDebugAbbrev(), 174 &DObj.getRangeSection(), DObj.getStringSection(), 175 DObj.getStringOffsetSection(), &DObj.getAppleObjCSection(), 176 DObj.getLineSection(), DCtx.isLittleEndian(), false, TUSection, 177 nullptr)); 178 break; 179 } 180 case dwarf::DW_UT_skeleton: 181 case dwarf::DW_UT_split_compile: 182 case dwarf::DW_UT_compile: 183 case dwarf::DW_UT_partial: 184 // UnitType = 0 means that we are 185 // verifying a compile unit in DWARF v4. 186 case 0: { 187 DWARFUnitSection<DWARFCompileUnit> CUSection{}; 188 Unit.reset(new DWARFCompileUnit( 189 DCtx, DObj.getInfoSection(), DCtx.getDebugAbbrev(), 190 &DObj.getRangeSection(), DObj.getStringSection(), 191 DObj.getStringOffsetSection(), &DObj.getAppleObjCSection(), 192 DObj.getLineSection(), DCtx.isLittleEndian(), false, CUSection, 193 nullptr)); 194 break; 195 } 196 default: { llvm_unreachable("Invalid UnitType."); } 197 } 198 Unit->extract(DebugInfoData, &OffsetStart); 199 if (!verifyUnitContents(*Unit)) 200 ++NumDebugInfoErrors; 201 } 202 hasDIE = DebugInfoData.isValidOffset(Offset); 203 ++UnitIdx; 204 } 205 if (UnitIdx == 0 && !hasDIE) { 206 OS << "Warning: .debug_info is empty.\n"; 207 isHeaderChainValid = true; 208 } 209 NumDebugInfoErrors += verifyDebugInfoReferences(); 210 return (isHeaderChainValid && NumDebugInfoErrors == 0); 211 } 212 213 unsigned DWARFVerifier::verifyDieRanges(const DWARFDie &Die) { 214 unsigned NumErrors = 0; 215 for (auto Range : Die.getAddressRanges()) { 216 if (Range.LowPC >= Range.HighPC) { 217 ++NumErrors; 218 OS << format("error: Invalid address range [0x%08" PRIx64 219 " - 0x%08" PRIx64 "].\n", 220 Range.LowPC, Range.HighPC); 221 } 222 } 223 return NumErrors; 224 } 225 226 unsigned DWARFVerifier::verifyDebugInfoAttribute(const DWARFDie &Die, 227 DWARFAttribute &AttrValue) { 228 const DWARFObject &DObj = DCtx.getDWARFObj(); 229 unsigned NumErrors = 0; 230 const auto Attr = AttrValue.Attr; 231 switch (Attr) { 232 case DW_AT_ranges: 233 // Make sure the offset in the DW_AT_ranges attribute is valid. 234 if (auto SectionOffset = AttrValue.Value.getAsSectionOffset()) { 235 if (*SectionOffset >= DObj.getRangeSection().Data.size()) { 236 ++NumErrors; 237 OS << "error: DW_AT_ranges offset is beyond .debug_ranges " 238 "bounds:\n"; 239 Die.dump(OS, 0); 240 OS << "\n"; 241 } 242 } else { 243 ++NumErrors; 244 OS << "error: DIE has invalid DW_AT_ranges encoding:\n"; 245 Die.dump(OS, 0); 246 OS << "\n"; 247 } 248 break; 249 case DW_AT_stmt_list: 250 // Make sure the offset in the DW_AT_stmt_list attribute is valid. 251 if (auto SectionOffset = AttrValue.Value.getAsSectionOffset()) { 252 if (*SectionOffset >= DObj.getLineSection().Data.size()) { 253 ++NumErrors; 254 OS << "error: DW_AT_stmt_list offset is beyond .debug_line " 255 "bounds: " 256 << format("0x%08" PRIx32, *SectionOffset) << "\n"; 257 Die.dump(OS, 0); 258 OS << "\n"; 259 } 260 } else { 261 ++NumErrors; 262 OS << "error: DIE has invalid DW_AT_stmt_list encoding:\n"; 263 Die.dump(OS, 0); 264 OS << "\n"; 265 } 266 break; 267 268 default: 269 break; 270 } 271 return NumErrors; 272 } 273 274 unsigned DWARFVerifier::verifyDebugInfoForm(const DWARFDie &Die, 275 DWARFAttribute &AttrValue) { 276 const DWARFObject &DObj = DCtx.getDWARFObj(); 277 unsigned NumErrors = 0; 278 const auto Form = AttrValue.Value.getForm(); 279 switch (Form) { 280 case DW_FORM_ref1: 281 case DW_FORM_ref2: 282 case DW_FORM_ref4: 283 case DW_FORM_ref8: 284 case DW_FORM_ref_udata: { 285 // Verify all CU relative references are valid CU offsets. 286 Optional<uint64_t> RefVal = AttrValue.Value.getAsReference(); 287 assert(RefVal); 288 if (RefVal) { 289 auto DieCU = Die.getDwarfUnit(); 290 auto CUSize = DieCU->getNextUnitOffset() - DieCU->getOffset(); 291 auto CUOffset = AttrValue.Value.getRawUValue(); 292 if (CUOffset >= CUSize) { 293 ++NumErrors; 294 OS << "error: " << FormEncodingString(Form) << " CU offset " 295 << format("0x%08" PRIx32, CUOffset) 296 << " is invalid (must be less than CU size of " 297 << format("0x%08" PRIx32, CUSize) << "):\n"; 298 Die.dump(OS, 0); 299 OS << "\n"; 300 } else { 301 // Valid reference, but we will verify it points to an actual 302 // DIE later. 303 ReferenceToDIEOffsets[*RefVal].insert(Die.getOffset()); 304 } 305 } 306 break; 307 } 308 case DW_FORM_ref_addr: { 309 // Verify all absolute DIE references have valid offsets in the 310 // .debug_info section. 311 Optional<uint64_t> RefVal = AttrValue.Value.getAsReference(); 312 assert(RefVal); 313 if (RefVal) { 314 if (*RefVal >= DObj.getInfoSection().Data.size()) { 315 ++NumErrors; 316 OS << "error: DW_FORM_ref_addr offset beyond .debug_info " 317 "bounds:\n"; 318 Die.dump(OS, 0); 319 OS << "\n"; 320 } else { 321 // Valid reference, but we will verify it points to an actual 322 // DIE later. 323 ReferenceToDIEOffsets[*RefVal].insert(Die.getOffset()); 324 } 325 } 326 break; 327 } 328 case DW_FORM_strp: { 329 auto SecOffset = AttrValue.Value.getAsSectionOffset(); 330 assert(SecOffset); // DW_FORM_strp is a section offset. 331 if (SecOffset && *SecOffset >= DObj.getStringSection().size()) { 332 ++NumErrors; 333 OS << "error: DW_FORM_strp offset beyond .debug_str bounds:\n"; 334 Die.dump(OS, 0); 335 OS << "\n"; 336 } 337 break; 338 } 339 default: 340 break; 341 } 342 return NumErrors; 343 } 344 345 unsigned DWARFVerifier::verifyDebugInfoReferences() { 346 // Take all references and make sure they point to an actual DIE by 347 // getting the DIE by offset and emitting an error 348 OS << "Verifying .debug_info references...\n"; 349 unsigned NumErrors = 0; 350 for (auto Pair : ReferenceToDIEOffsets) { 351 auto Die = DCtx.getDIEForOffset(Pair.first); 352 if (Die) 353 continue; 354 ++NumErrors; 355 OS << "error: invalid DIE reference " << format("0x%08" PRIx64, Pair.first) 356 << ". Offset is in between DIEs:\n"; 357 for (auto Offset : Pair.second) { 358 auto ReferencingDie = DCtx.getDIEForOffset(Offset); 359 ReferencingDie.dump(OS, 0); 360 OS << "\n"; 361 } 362 OS << "\n"; 363 } 364 return NumErrors; 365 } 366 367 void DWARFVerifier::verifyDebugLineStmtOffsets() { 368 std::map<uint64_t, DWARFDie> StmtListToDie; 369 for (const auto &CU : DCtx.compile_units()) { 370 auto Die = CU->getUnitDIE(); 371 // Get the attribute value as a section offset. No need to produce an 372 // error here if the encoding isn't correct because we validate this in 373 // the .debug_info verifier. 374 auto StmtSectionOffset = toSectionOffset(Die.find(DW_AT_stmt_list)); 375 if (!StmtSectionOffset) 376 continue; 377 const uint32_t LineTableOffset = *StmtSectionOffset; 378 auto LineTable = DCtx.getLineTableForUnit(CU.get()); 379 if (LineTableOffset < DCtx.getDWARFObj().getLineSection().Data.size()) { 380 if (!LineTable) { 381 ++NumDebugLineErrors; 382 OS << "error: .debug_line[" << format("0x%08" PRIx32, LineTableOffset) 383 << "] was not able to be parsed for CU:\n"; 384 Die.dump(OS, 0); 385 OS << '\n'; 386 continue; 387 } 388 } else { 389 // Make sure we don't get a valid line table back if the offset is wrong. 390 assert(LineTable == nullptr); 391 // Skip this line table as it isn't valid. No need to create an error 392 // here because we validate this in the .debug_info verifier. 393 continue; 394 } 395 auto Iter = StmtListToDie.find(LineTableOffset); 396 if (Iter != StmtListToDie.end()) { 397 ++NumDebugLineErrors; 398 OS << "error: two compile unit DIEs, " 399 << format("0x%08" PRIx32, Iter->second.getOffset()) << " and " 400 << format("0x%08" PRIx32, Die.getOffset()) 401 << ", have the same DW_AT_stmt_list section offset:\n"; 402 Iter->second.dump(OS, 0); 403 Die.dump(OS, 0); 404 OS << '\n'; 405 // Already verified this line table before, no need to do it again. 406 continue; 407 } 408 StmtListToDie[LineTableOffset] = Die; 409 } 410 } 411 412 void DWARFVerifier::verifyDebugLineRows() { 413 for (const auto &CU : DCtx.compile_units()) { 414 auto Die = CU->getUnitDIE(); 415 auto LineTable = DCtx.getLineTableForUnit(CU.get()); 416 // If there is no line table we will have created an error in the 417 // .debug_info verifier or in verifyDebugLineStmtOffsets(). 418 if (!LineTable) 419 continue; 420 uint32_t MaxFileIndex = LineTable->Prologue.FileNames.size(); 421 uint64_t PrevAddress = 0; 422 uint32_t RowIndex = 0; 423 for (const auto &Row : LineTable->Rows) { 424 if (Row.Address < PrevAddress) { 425 ++NumDebugLineErrors; 426 OS << "error: .debug_line[" 427 << format("0x%08" PRIx32, 428 *toSectionOffset(Die.find(DW_AT_stmt_list))) 429 << "] row[" << RowIndex 430 << "] decreases in address from previous row:\n"; 431 432 DWARFDebugLine::Row::dumpTableHeader(OS); 433 if (RowIndex > 0) 434 LineTable->Rows[RowIndex - 1].dump(OS); 435 Row.dump(OS); 436 OS << '\n'; 437 } 438 439 if (Row.File > MaxFileIndex) { 440 ++NumDebugLineErrors; 441 OS << "error: .debug_line[" 442 << format("0x%08" PRIx32, 443 *toSectionOffset(Die.find(DW_AT_stmt_list))) 444 << "][" << RowIndex << "] has invalid file index " << Row.File 445 << " (valid values are [1," << MaxFileIndex << "]):\n"; 446 DWARFDebugLine::Row::dumpTableHeader(OS); 447 Row.dump(OS); 448 OS << '\n'; 449 } 450 if (Row.EndSequence) 451 PrevAddress = 0; 452 else 453 PrevAddress = Row.Address; 454 ++RowIndex; 455 } 456 } 457 } 458 459 bool DWARFVerifier::handleDebugLine() { 460 NumDebugLineErrors = 0; 461 OS << "Verifying .debug_line...\n"; 462 verifyDebugLineStmtOffsets(); 463 verifyDebugLineRows(); 464 return NumDebugLineErrors == 0; 465 } 466 467 unsigned DWARFVerifier::verifyAccelTable(const DWARFSection *AccelSection, 468 DataExtractor *StrData, 469 const char *SectionName) { 470 unsigned NumErrors = 0; 471 DWARFDataExtractor AccelSectionData(DCtx.getDWARFObj(), *AccelSection, 472 DCtx.isLittleEndian(), 0); 473 DWARFAcceleratorTable AccelTable(AccelSectionData, *StrData); 474 475 OS << "Verifying " << SectionName << "...\n"; 476 // Verify that the fixed part of the header is not too short. 477 478 if (!AccelSectionData.isValidOffset(AccelTable.getSizeHdr())) { 479 OS << "\terror: Section is too small to fit a section header.\n"; 480 return 1; 481 } 482 // Verify that the section is not too short. 483 if (!AccelTable.extract()) { 484 OS << "\terror: Section is smaller than size described in section header.\n"; 485 return 1; 486 } 487 // Verify that all buckets have a valid hash index or are empty. 488 uint32_t NumBuckets = AccelTable.getNumBuckets(); 489 uint32_t NumHashes = AccelTable.getNumHashes(); 490 491 uint32_t BucketsOffset = 492 AccelTable.getSizeHdr() + AccelTable.getHeaderDataLength(); 493 uint32_t HashesBase = BucketsOffset + NumBuckets * 4; 494 uint32_t OffsetsBase = HashesBase + NumHashes * 4; 495 for (uint32_t BucketIdx = 0; BucketIdx < NumBuckets; ++BucketIdx) { 496 uint32_t HashIdx = AccelSectionData.getU32(&BucketsOffset); 497 if (HashIdx >= NumHashes && HashIdx != UINT32_MAX) { 498 OS << format("\terror: Bucket[%d] has invalid hash index: %u.\n", BucketIdx, 499 HashIdx); 500 ++NumErrors; 501 } 502 } 503 uint32_t NumAtoms = AccelTable.getAtomsDesc().size(); 504 if (NumAtoms == 0) { 505 OS << "\terror: no atoms; failed to read HashData.\n"; 506 return 1; 507 } 508 if (!AccelTable.validateForms()) { 509 OS << "\terror: unsupported form; failed to read HashData.\n"; 510 return 1; 511 } 512 513 for (uint32_t HashIdx = 0; HashIdx < NumHashes; ++HashIdx) { 514 uint32_t HashOffset = HashesBase + 4 * HashIdx; 515 uint32_t DataOffset = OffsetsBase + 4 * HashIdx; 516 uint32_t Hash = AccelSectionData.getU32(&HashOffset); 517 uint32_t HashDataOffset = AccelSectionData.getU32(&DataOffset); 518 if (!AccelSectionData.isValidOffsetForDataOfSize(HashDataOffset, 519 sizeof(uint64_t))) { 520 OS << format("\terror: Hash[%d] has invalid HashData offset: 0x%08x.\n", 521 HashIdx, HashDataOffset); 522 ++NumErrors; 523 } 524 525 uint32_t StrpOffset; 526 uint32_t StringOffset; 527 uint32_t StringCount = 0; 528 uint32_t DieOffset = dwarf::DW_INVALID_OFFSET; 529 530 while ((StrpOffset = AccelSectionData.getU32(&HashDataOffset)) != 0) { 531 const uint32_t NumHashDataObjects = 532 AccelSectionData.getU32(&HashDataOffset); 533 for (uint32_t HashDataIdx = 0; HashDataIdx < NumHashDataObjects; 534 ++HashDataIdx) { 535 DieOffset = AccelTable.readAtoms(HashDataOffset); 536 if (!DCtx.getDIEForOffset(DieOffset)) { 537 const uint32_t BucketIdx = 538 NumBuckets ? (Hash % NumBuckets) : UINT32_MAX; 539 StringOffset = StrpOffset; 540 const char *Name = StrData->getCStr(&StringOffset); 541 if (!Name) 542 Name = "<NULL>"; 543 544 OS << format( 545 "\terror: %s Bucket[%d] Hash[%d] = 0x%08x " 546 "Str[%u] = 0x%08x " 547 "DIE[%d] = 0x%08x is not a valid DIE offset for \"%s\".\n", 548 SectionName, BucketIdx, HashIdx, Hash, StringCount, StrpOffset, 549 HashDataIdx, DieOffset, Name); 550 551 ++NumErrors; 552 } 553 } 554 ++StringCount; 555 } 556 } 557 return NumErrors; 558 } 559 560 bool DWARFVerifier::handleAccelTables() { 561 const DWARFObject &D = DCtx.getDWARFObj(); 562 DataExtractor StrData(D.getStringSection(), DCtx.isLittleEndian(), 0); 563 unsigned NumErrors = 0; 564 if (!D.getAppleNamesSection().Data.empty()) 565 NumErrors += 566 verifyAccelTable(&D.getAppleNamesSection(), &StrData, ".apple_names"); 567 if (!D.getAppleTypesSection().Data.empty()) 568 NumErrors += 569 verifyAccelTable(&D.getAppleTypesSection(), &StrData, ".apple_types"); 570 if (!D.getAppleNamespacesSection().Data.empty()) 571 NumErrors += verifyAccelTable(&D.getAppleNamespacesSection(), &StrData, 572 ".apple_namespaces"); 573 if (!D.getAppleObjCSection().Data.empty()) 574 NumErrors += 575 verifyAccelTable(&D.getAppleObjCSection(), &StrData, ".apple_objc"); 576 return NumErrors == 0; 577 } 578