[lld-macho][re-land] Warn on method name collisions from category definitions

This implements ld64's checks for duplicate method names in categories &
classes.

In addition, this sets us up for implementing Obj-C category merging.
This diff handles the most of the parsing work; what's left is rewriting
those category / class structures.

Numbers for chromium_framework:

             base           diff           difference (95% CI)
  sys_time   2.182 ± 0.027  2.200 ± 0.047  [  -0.2% ..   +1.8%]
  user_time  6.451 ± 0.034  6.479 ± 0.062  [  -0.0% ..   +0.9%]
  wall_time  6.841 ± 0.048  6.885 ± 0.105  [  -0.1% ..   +1.4%]
  samples    33             22

Fixes https://github.com/llvm/llvm-project/issues/54912.

Issues seen with the previous land will be fixed in the next commit.

Reviewed By: #lld-macho, thevinster, oontvoo

Differential Revision: https://reviews.llvm.org/D142916
This commit is contained in:
Jez Ng 2023-02-16 16:18:46 -05:00
parent 3fbc6fd493
commit 453102a028
13 changed files with 630 additions and 55 deletions

View File

@ -1921,6 +1921,8 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
if (config->deadStrip)
markLive();
objc::checkCategories();
// ICF assumes that all literals have been folded already, so we must run
// foldIdenticalLiterals before foldIdenticalSections.
foldIdenticalLiterals();

View File

@ -1254,13 +1254,10 @@ static CIE parseCIE(const InputSection *isec, const EhReader &reader,
}
}
if (personalityAddrOff != 0) {
auto personalityRelocIt =
llvm::find_if(isec->relocs, [=](const macho::Reloc &r) {
return r.offset == personalityAddrOff;
});
if (personalityRelocIt == isec->relocs.end())
const auto *personalityReloc = isec->getRelocAt(personalityAddrOff);
if (!personalityReloc)
reader.failOn(off, "Failed to locate relocation for personality symbol");
cie.personalitySymbol = personalityRelocIt->referent.get<macho::Symbol *>();
cie.personalitySymbol = personalityReloc->referent.get<macho::Symbol *>();
}
return cie;
}

View File

@ -135,6 +135,14 @@ std::string InputSection::getSourceLocation(uint64_t off) const {
return {};
}
const Reloc *InputSection::getRelocAt(uint32_t off) const {
auto it = llvm::find_if(
relocs, [=](const macho::Reloc &r) { return r.offset == off; });
if (it == relocs.end())
return nullptr;
return &*it;
}
void ConcatInputSection::foldIdentical(ConcatInputSection *copy) {
align = std::max(align, copy->align);
copy->live = false;
@ -259,6 +267,15 @@ const StringPiece &CStringInputSection::getStringPiece(uint64_t off) const {
return const_cast<CStringInputSection *>(this)->getStringPiece(off);
}
size_t CStringInputSection::getStringPieceIndex(uint64_t off) const {
if (off >= data.size())
fatal(toString(this) + ": offset is outside the section");
auto it =
partition_point(pieces, [=](StringPiece p) { return p.inSecOff <= off; });
return std::distance(pieces.begin(), it) - 1;
}
uint64_t CStringInputSection::getOffset(uint64_t off) const {
const StringPiece &piece = getStringPiece(off);
uint64_t addend = off - piece.inSecOff;

View File

@ -55,6 +55,8 @@ public:
// Return the source line corresponding to an address, or the empty string.
// Format: Source.cpp:123 (/path/to/Source.cpp:123)
std::string getSourceLocation(uint64_t off) const;
// Return the relocation at \p off, if it exists. This does a linear search.
const Reloc *getRelocAt(uint32_t off) const;
// Whether the data at \p off in this InputSection is live.
virtual bool isLive(uint64_t off) const = 0;
virtual void markLive(uint64_t off) = 0;
@ -218,6 +220,10 @@ public:
return toStringRef(data.slice(begin, end - begin));
}
StringRef getStringRefAtOffset(uint64_t off) const {
return getStringRef(getStringPieceIndex(off));
}
// Returns i'th piece as a CachedHashStringRef. This function is very hot when
// string merging is enabled, so we want to inline.
LLVM_ATTRIBUTE_ALWAYS_INLINE
@ -232,6 +238,9 @@ public:
bool deduplicateLiterals = false;
std::vector<StringPiece> pieces;
private:
size_t getStringPieceIndex(uint64_t off) const;
};
class WordLiteralInputSection final : public InputSection {

74
lld/MachO/Layout.h Normal file
View File

@ -0,0 +1,74 @@
//===- Layout.h -----------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// Convenience macros for obtaining offsets of members in structs.
//
// Usage:
//
// #define FOR_EACH_FOO_FIELD(DO) \
// DO(Ptr, bar) \
// DO(uint32_t, baz) \
// CREATE_LAYOUT_CLASS(Foo, FOR_EACH_FOO_FIELD)
// #undef FOR_EACH_FOO_FIELD
//
// This will generate
//
// struct FooLayout {
// uint32_t barOffset;
// uint32_t bazOffset;
// uint32_t totalSize;
//
// FooLayout(size_t wordSize) {
// if (wordSize == 8)
// init<uint64_t>();
// else {
// assert(wordSize == 4);
// init<uint32_t>();
// }
// }
//
// private:
// template <class Ptr> void init() {
// FOR_EACH_FIELD(_INIT_OFFSET);
// barOffset = offsetof(Layout<Ptr>, bar);
// bazOffset = offsetof(Layout<Ptr>, baz);
// totalSize = sizeof(Layout<Ptr>);
// }
// template <class Ptr> struct Layout {
// Ptr bar;
// uint32_t baz;
// };
// };
#define _OFFSET_FOR_FIELD(_, name) uint32_t name##Offset;
#define _INIT_OFFSET(type, name) name##Offset = offsetof(Layout<Ptr>, name);
#define _LAYOUT_ENTRY(type, name) type name;
#define CREATE_LAYOUT_CLASS(className, FOR_EACH_FIELD) \
struct className##Layout { \
FOR_EACH_FIELD(_OFFSET_FOR_FIELD) \
uint32_t totalSize; \
\
className##Layout(size_t wordSize) { \
if (wordSize == 8) \
init<uint64_t>(); \
else { \
assert(wordSize == 4); \
init<uint32_t>(); \
} \
} \
\
private: \
template <class Ptr> void init() { \
FOR_EACH_FIELD(_INIT_OFFSET); \
totalSize = sizeof(Layout<Ptr>); \
} \
template <class Ptr> struct Layout { \
FOR_EACH_FIELD(_LAYOUT_ENTRY) \
}; \
}

View File

@ -9,10 +9,12 @@
#include "ObjC.h"
#include "InputFiles.h"
#include "InputSection.h"
#include "Layout.h"
#include "OutputSegment.h"
#include "Target.h"
#include "lld/Common/ErrorHandler.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/BinaryFormat/MachO.h"
#include "llvm/Bitcode/BitcodeReader.h"
@ -66,3 +68,229 @@ bool macho::hasObjCSection(MemoryBufferRef mb) {
return false;
}
}
namespace {
#define FOR_EACH_CATEGORY_FIELD(DO) \
DO(Ptr, name) \
DO(Ptr, klass) \
DO(Ptr, instanceMethods) \
DO(Ptr, classMethods) \
DO(Ptr, protocols) \
DO(Ptr, instanceProps) \
DO(Ptr, classProps)
CREATE_LAYOUT_CLASS(Category, FOR_EACH_CATEGORY_FIELD);
#undef FOR_EACH_CATEGORY_FIELD
#define FOR_EACH_CLASS_FIELD(DO) \
DO(Ptr, metaClass) \
DO(Ptr, superClass) \
DO(Ptr, methodCache) \
DO(Ptr, vtable) \
DO(Ptr, roData)
CREATE_LAYOUT_CLASS(Class, FOR_EACH_CLASS_FIELD);
#undef FOR_EACH_CLASS_FIELD
#define FOR_EACH_RO_CLASS_FIELD(DO) \
DO(uint32_t, flags) \
DO(uint32_t, instanceStart) \
DO(Ptr, instanceSize) \
DO(Ptr, ivarLayout) \
DO(Ptr, name) \
DO(Ptr, baseMethods) \
DO(Ptr, baseProtocols) \
DO(Ptr, ivars) \
DO(Ptr, weakIvarLayout) \
DO(Ptr, baseProperties)
CREATE_LAYOUT_CLASS(ROClass, FOR_EACH_RO_CLASS_FIELD);
#undef FOR_EACH_RO_CLASS_FIELD
#define FOR_EACH_LIST_HEADER(DO) \
DO(uint32_t, size) \
DO(uint32_t, count)
CREATE_LAYOUT_CLASS(ListHeader, FOR_EACH_LIST_HEADER);
#undef FOR_EACH_LIST_HEADER
#define FOR_EACH_METHOD(DO) \
DO(Ptr, name) \
DO(Ptr, type) \
DO(Ptr, impl)
CREATE_LAYOUT_CLASS(Method, FOR_EACH_METHOD);
#undef FOR_EACH_METHOD
enum MethodContainerKind {
MCK_Class,
MCK_Category,
};
struct MethodContainer {
MethodContainerKind kind;
const ConcatInputSection *isec;
};
enum MethodKind {
MK_Instance,
MK_Static,
};
struct ObjcClass {
DenseMap<CachedHashStringRef, MethodContainer> instanceMethods;
DenseMap<CachedHashStringRef, MethodContainer> classMethods;
};
} // namespace
class ObjcCategoryChecker {
public:
ObjcCategoryChecker();
void parseCategory(const ConcatInputSection *catListIsec);
private:
void parseClass(const Defined *classSym);
void parseMethods(const ConcatInputSection *methodsIsec,
const Symbol *methodContainer,
const ConcatInputSection *containerIsec,
MethodContainerKind, MethodKind);
CategoryLayout catLayout;
ClassLayout classLayout;
ROClassLayout roClassLayout;
ListHeaderLayout listHeaderLayout;
MethodLayout methodLayout;
DenseMap<const Symbol *, ObjcClass> classMap;
};
ObjcCategoryChecker::ObjcCategoryChecker()
: catLayout(target->wordSize), classLayout(target->wordSize),
roClassLayout(target->wordSize), listHeaderLayout(target->wordSize),
methodLayout(target->wordSize) {}
// \p r must point to an offset within a cstring section.
static StringRef getReferentString(const Reloc &r) {
if (auto *isec = r.referent.dyn_cast<InputSection *>())
return cast<CStringInputSection>(isec)->getStringRefAtOffset(r.addend);
auto *sym = cast<Defined>(r.referent.get<Symbol *>());
return cast<CStringInputSection>(sym->isec)->getStringRefAtOffset(sym->value +
r.addend);
}
void ObjcCategoryChecker::parseMethods(const ConcatInputSection *methodsIsec,
const Symbol *methodContainerSym,
const ConcatInputSection *containerIsec,
MethodContainerKind mcKind,
MethodKind mKind) {
ObjcClass &klass = classMap[methodContainerSym];
for (const Reloc &r : methodsIsec->relocs) {
if ((r.offset - listHeaderLayout.totalSize) % methodLayout.totalSize !=
methodLayout.nameOffset)
continue;
CachedHashStringRef methodName(getReferentString(r));
auto &methodMap =
mKind == MK_Instance ? klass.instanceMethods : klass.classMethods;
if (methodMap
.try_emplace(methodName, MethodContainer{mcKind, containerIsec})
.second)
continue;
// We have a duplicate; generate a warning message.
const auto &mc = methodMap.lookup(methodName);
const Reloc *nameReloc = nullptr;
if (mc.kind == MCK_Category) {
nameReloc = mc.isec->getRelocAt(catLayout.nameOffset);
} else {
assert(mc.kind == MCK_Class);
const auto *roIsec = mc.isec->getRelocAt(classLayout.roDataOffset)
->getReferentInputSection();
nameReloc = roIsec->getRelocAt(roClassLayout.nameOffset);
}
StringRef containerName = getReferentString(*nameReloc);
StringRef methPrefix = mKind == MK_Instance ? "-" : "+";
// We should only ever encounter collisions when parsing category methods
// (since the Class struct is parsed before any of its categories).
assert(mcKind == MCK_Category);
StringRef newCatName =
getReferentString(*containerIsec->getRelocAt(catLayout.nameOffset));
StringRef containerType = mc.kind == MCK_Category ? "category" : "class";
warn("method '" + methPrefix + methodName.val() +
"' has conflicting definitions:\n>>> defined in category " +
newCatName + " from " + toString(containerIsec->getFile()) +
"\n>>> defined in " + containerType + " " + containerName + " from " +
toString(mc.isec->getFile()));
}
}
void ObjcCategoryChecker::parseCategory(const ConcatInputSection *catIsec) {
auto *classReloc = catIsec->getRelocAt(catLayout.klassOffset);
if (!classReloc)
return;
auto *classSym = classReloc->referent.get<Symbol *>();
if (auto *d = dyn_cast<Defined>(classSym))
if (!classMap.count(d))
parseClass(d);
if (const auto *r = catIsec->getRelocAt(catLayout.classMethodsOffset)) {
parseMethods(cast<ConcatInputSection>(r->getReferentInputSection()),
classSym, catIsec, MCK_Category, MK_Static);
}
if (const auto *r = catIsec->getRelocAt(catLayout.instanceMethodsOffset)) {
parseMethods(cast<ConcatInputSection>(r->getReferentInputSection()),
classSym, catIsec, MCK_Category, MK_Instance);
}
}
void ObjcCategoryChecker::parseClass(const Defined *classSym) {
// Given a Class struct, get its corresponding Methods struct
auto getMethodsIsec =
[&](const InputSection *classIsec) -> ConcatInputSection * {
if (const auto *r = classIsec->getRelocAt(classLayout.roDataOffset)) {
const auto *roIsec =
cast<ConcatInputSection>(r->getReferentInputSection());
if (const auto *r = roIsec->getRelocAt(roClassLayout.baseMethodsOffset)) {
if (auto *methodsIsec =
cast_or_null<ConcatInputSection>(r->getReferentInputSection()))
return methodsIsec;
}
}
return nullptr;
};
const auto *classIsec = cast<ConcatInputSection>(classSym->isec);
// Parse instance methods.
if (const auto *instanceMethodsIsec = getMethodsIsec(classIsec))
parseMethods(instanceMethodsIsec, classSym, classIsec, MCK_Class,
MK_Instance);
// Class methods are contained in the metaclass.
if (const auto *r = classSym->isec->getRelocAt(classLayout.metaClassOffset))
if (const auto *classMethodsIsec = getMethodsIsec(
cast<ConcatInputSection>(r->getReferentInputSection())))
parseMethods(classMethodsIsec, classSym, classIsec, MCK_Class, MK_Static);
}
void objc::checkCategories() {
ObjcCategoryChecker checker;
for (const InputSection *isec : inputSections) {
if (isec->getName() == section_names::objcCatList)
for (const Reloc &r : isec->relocs) {
auto *catIsec = cast<ConcatInputSection>(r.getReferentInputSection());
checker.parseCategory(catIsec);
}
}
}

View File

@ -20,6 +20,9 @@ constexpr const char metaclass[] = "_OBJC_METACLASS_$_";
constexpr const char ehtype[] = "_OBJC_EHTYPE_$_";
constexpr const char ivar[] = "_OBJC_IVAR_$_";
// Check for duplicate method names within related categories / classes.
void checkCategories();
} // namespace objc
bool hasObjCSection(llvm::MemoryBufferRef);

View File

@ -21,6 +21,16 @@ using namespace lld::macho;
static_assert(sizeof(void *) != 8 || sizeof(Reloc) == 24,
"Try to minimize Reloc's size; we create many instances");
InputSection *Reloc::getReferentInputSection() const {
if (const auto *sym = referent.dyn_cast<Symbol *>()) {
if (const auto *d = dyn_cast<Defined>(sym))
return d->isec;
return nullptr;
} else {
return referent.get<InputSection *>();
}
}
bool macho::validateSymbolRelocation(const Symbol *sym,
const InputSection *isec, const Reloc &r) {
const RelocAttrs &relocAttrs = target->getRelocAttrs(r.type);

View File

@ -67,6 +67,8 @@ struct Reloc {
int64_t addend, llvm::PointerUnion<Symbol *, InputSection *> referent)
: type(type), pcrel(pcrel), length(length), offset(offset),
addend(addend), referent(referent) {}
InputSection *getReferentInputSection() const;
};
bool validateSymbolRelocation(const Symbol *, const InputSection *,

View File

@ -8,6 +8,7 @@
#include "UnwindInfoSection.h"
#include "InputSection.h"
#include "Layout.h"
#include "OutputSection.h"
#include "OutputSegment.h"
#include "SymbolTable.h"
@ -88,41 +89,18 @@ using namespace lld::macho;
// TODO(gkm): how do we align the 2nd-level pages?
// The offsets of various fields in the on-disk representation of each compact
// unwind entry.
struct CompactUnwindOffsets {
uint32_t functionAddress;
uint32_t functionLength;
uint32_t encoding;
uint32_t personality;
uint32_t lsda;
// The various fields in the on-disk representation of each compact unwind
// entry.
#define FOR_EACH_CU_FIELD(DO) \
DO(Ptr, functionAddress) \
DO(uint32_t, functionLength) \
DO(compact_unwind_encoding_t, encoding) \
DO(Ptr, personality) \
DO(Ptr, lsda)
CompactUnwindOffsets(size_t wordSize) {
if (wordSize == 8)
init<uint64_t>();
else {
assert(wordSize == 4);
init<uint32_t>();
}
}
CREATE_LAYOUT_CLASS(CompactUnwind, FOR_EACH_CU_FIELD);
private:
template <class Ptr> void init() {
functionAddress = offsetof(Layout<Ptr>, functionAddress);
functionLength = offsetof(Layout<Ptr>, functionLength);
encoding = offsetof(Layout<Ptr>, encoding);
personality = offsetof(Layout<Ptr>, personality);
lsda = offsetof(Layout<Ptr>, lsda);
}
template <class Ptr> struct Layout {
Ptr functionAddress;
uint32_t functionLength;
compact_unwind_encoding_t encoding;
Ptr personality;
Ptr lsda;
};
};
#undef FOR_EACH_CU_FIELD
// LLD's internal representation of a compact unwind entry.
struct CompactUnwindEntry {
@ -148,7 +126,7 @@ struct SecondLevelPage {
// lengthy definition of UnwindInfoSection.
class UnwindInfoSectionImpl final : public UnwindInfoSection {
public:
UnwindInfoSectionImpl() : cuOffsets(target->wordSize) {}
UnwindInfoSectionImpl() : cuLayout(target->wordSize) {}
uint64_t getSize() const override { return unwindInfoSize; }
void prepare() override;
void finalize() override;
@ -162,7 +140,7 @@ private:
uint64_t unwindInfoSize = 0;
std::vector<decltype(symbols)::value_type> symbolsVec;
CompactUnwindOffsets cuOffsets;
CompactUnwindLayout cuLayout;
std::vector<std::pair<compact_unwind_encoding_t, size_t>> commonEncodings;
EncodingMap commonEncodingIndexes;
// The entries here will be in the same order as their originating symbols
@ -261,7 +239,7 @@ void UnwindInfoSectionImpl::prepareRelocations(ConcatInputSection *isec) {
// compact unwind entries that references them, and thus appear as section
// relocs. There is no need to prepare them. We only prepare relocs for
// personality functions.
if (r.offset != cuOffsets.personality)
if (r.offset != cuLayout.personalityOffset)
continue;
if (auto *s = r.referent.dyn_cast<Symbol *>()) {
@ -373,17 +351,13 @@ void UnwindInfoSectionImpl::relocateCompactUnwind(
auto buf = reinterpret_cast<const uint8_t *>(d->unwindEntry->data.data()) -
target->wordSize;
cu.functionLength =
support::endian::read32le(buf + cuOffsets.functionLength);
cu.encoding = support::endian::read32le(buf + cuOffsets.encoding);
support::endian::read32le(buf + cuLayout.functionLengthOffset);
cu.encoding = support::endian::read32le(buf + cuLayout.encodingOffset);
for (const Reloc &r : d->unwindEntry->relocs) {
if (r.offset == cuOffsets.personality) {
if (r.offset == cuLayout.personalityOffset)
cu.personality = r.referent.get<Symbol *>();
} else if (r.offset == cuOffsets.lsda) {
if (auto *referentSym = r.referent.dyn_cast<Symbol *>())
cu.lsda = cast<Defined>(referentSym)->isec;
else
cu.lsda = r.referent.get<InputSection *>();
}
else if (r.offset == cuLayout.lsdaOffset)
cu.lsda = r.getReferentInputSection();
}
});
}

View File

@ -0,0 +1,10 @@
--- !tapi-tbd-v3
archs: [ i386, x86_64, arm64 ]
uuids: [ 'i386: 00000000-0000-0000-0000-000000000000', 'x86_64: 00000000-0000-0000-0000-000000000001', 'arm64: 00000000-0000-0000-0000-000000000002' ]
platform: macosx
install-name: '/usr/lib/libobjc.dylib'
current-version: 1281
exports:
- archs: [ i386, x86_64, arm64 ]
symbols: [ __objc_empty_cache ]
...

View File

@ -0,0 +1,252 @@
# REQUIRES: x86
# RUN: rm -rf %t; split-file %s %t
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos11.0 -I %t %t/cat1.s -o %t/cat1.o
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos11.0 -I %t %t/cat2.s -o %t/cat2.o
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos11.0 -I %t %t/klass.s -o %t/klass.o
# RUN: %lld -dylib -lobjc %t/klass.o -o %t/libklass.dylib
# RUN: %no-fatal-warnings-lld -dylib -lobjc %t/klass.o %t/cat1.o %t/cat2.o -o \
# RUN: /dev/null 2>&1 | FileCheck %s --check-prefixes=CATCLS,CATCAT
# RUN: %no-fatal-warnings-lld -dylib -lobjc %t/libklass.dylib %t/cat1.o \
# RUN: %t/cat2.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=CATCAT
# CATCLS: warning: method '+s1' has conflicting definitions:
# CATCLS-NEXT: >>> defined in category Cat1 from {{.*}}cat1.o
# CATCLS-NEXT: >>> defined in class Foo from {{.*}}klass.o
# CATCLS: warning: method '-m1' has conflicting definitions:
# CATCLS-NEXT: >>> defined in category Cat1 from {{.*}}cat1.o
# CATCLS-NEXT: >>> defined in class Foo from {{.*}}klass.o
# CATCAT: warning: method '+s2' has conflicting definitions:
# CATCAT-NEXT: >>> defined in category Cat2 from {{.*}}cat2.o
# CATCAT-NEXT: >>> defined in category Cat1 from {{.*}}cat1.o
# CATCAT: warning: method '-m2' has conflicting definitions:
# CATCAT-NEXT: >>> defined in category Cat2 from {{.*}}cat2.o
# CATCAT-NEXT: >>> defined in category Cat1 from {{.*}}cat1.o
#--- cat1.s
.include "objc-macros.s"
## @interface Foo(Cat1)
## -(void) m1;
## -(void) m2;
## +(void) s1;
## +(void) s2;
## @end
##
## @implementation Foo(Cat1)
## -(void) m1 {}
## -(void) m2 {}
## +(void) s1 {}
## +(void) s2 {}
## @end
.section __DATA,__objc_catlist,regular,no_dead_strip
.quad __OBJC_$_CATEGORY_Foo_$_Cat1
.section __DATA,__objc_const
__OBJC_$_CATEGORY_Foo_$_Cat1:
.objc_classname "Cat1"
.quad _OBJC_CLASS_$_Foo
.quad __OBJC_$_CATEGORY_INSTANCE_METHODS_Foo_$_Cat1
.quad __OBJC_$_CATEGORY_CLASS_METHODS_Foo_$_Cat1
.quad 0
.quad 0
.quad 0
.long 64
.space 4
__OBJC_$_CATEGORY_INSTANCE_METHODS_Foo_$_Cat1:
.long 24 # size of method entry
.long 2 # number of methods
.empty_objc_method "m1", "v16@0:8", "-[Foo(Cat1) m1]"
.empty_objc_method "m2", "v16@0:8", "-[Foo(Cat2) m2]"
__OBJC_$_CATEGORY_CLASS_METHODS_Foo_$_Cat1:
.long 24
.long 2
.empty_objc_method "s1", "v16@0:8", "+[Foo(Cat1) s1]"
.empty_objc_method "s2", "v16@0:8", "+[Foo(Cat1) s2]"
.section __DATA,__objc_imageinfo,regular,no_dead_strip
.long 0
.long 64
.subsections_via_symbols
#--- cat2.s
.include "objc-macros.s"
## @interface Foo(Cat2)
## -(void) m2;
## +(void) s2;
## @end
##
## @implementation Foo(Cat2)
## -(void) m2 {}
## +(void) s2 {}
## @end
.section __DATA,__objc_catlist,regular,no_dead_strip
.quad __OBJC_$_CATEGORY_Foo_$_Cat2
.section __DATA,__objc_const
__OBJC_$_CATEGORY_Foo_$_Cat2:
.objc_classname "Cat2"
.quad _OBJC_CLASS_$_Foo
.quad __OBJC_$_CATEGORY_INSTANCE_METHODS_Foo_$_Cat2
.quad __OBJC_$_CATEGORY_CLASS_METHODS_Foo_$_Cat2
.quad 0
.quad 0
.quad 0
.long 64
.space 4
__OBJC_$_CATEGORY_INSTANCE_METHODS_Foo_$_Cat2:
.long 24
.long 1
.empty_objc_method "m2", "v16@0:8", "-[Foo(Cat2) m2]"
__OBJC_$_CATEGORY_CLASS_METHODS_Foo_$_Cat2:
.long 24
.long 1
.empty_objc_method "s2", "v16@0:8", "+[Foo(Cat2) m2]"
.section __DATA,__objc_imageinfo,regular,no_dead_strip
.long 0
.long 64
.subsections_via_symbols
#--- klass.s
.include "objc-macros.s"
## @interface Foo
## -(void) m1;
## +(void) s1;
## @end
##
## @implementation Foo
## -(void) m1 {}
## +(void) s1 {}
## @end
.globl _OBJC_CLASS_$_Foo, _OBJC_METACLASS_$_Foo
.section __DATA,__objc_data
_OBJC_CLASS_$_Foo:
.quad _OBJC_METACLASS_$_Foo
.quad 0
.quad __objc_empty_cache
.quad 0
.quad __OBJC_CLASS_RO_$_Foo
_OBJC_METACLASS_$_Foo:
.quad _OBJC_METACLASS_$_Foo
.quad _OBJC_CLASS_$_Foo
.quad __objc_empty_cache
.quad 0
.quad __OBJC_METACLASS_RO_$_Foo
.section __DATA,__objc_const
__OBJC_METACLASS_RO_$_Foo:
.long 3
.long 40
.long 40
.space 4
.quad 0
.objc_classname "Foo"
.quad __OBJC_$_CLASS_METHODS_Foo
.quad 0
.quad 0
.quad 0
.quad 0
__OBJC_CLASS_RO_$_Foo:
.long 2
.long 0
.long 0
.space 4
.quad 0
.objc_classname "Foo"
.quad __OBJC_$_INSTANCE_METHODS_Foo
.quad 0
.quad 0
.quad 0
.quad 0
__OBJC_$_CLASS_METHODS_Foo:
.long 24
.long 1
.empty_objc_method "s1", "v16@0:8", "+[Foo s1]"
__OBJC_$_INSTANCE_METHODS_Foo:
.long 24
.long 1
.empty_objc_method "m1", "v16@0:8", "-[Foo m1]"
.section __DATA,__objc_classlist,regular,no_dead_strip
.quad _OBJC_CLASS_$_Foo
.section __DATA,__objc_imageinfo,regular,no_dead_strip
.long 0
.long 64
.subsections_via_symbols
#--- objc-macros.s
# Macros for taking some of the boilerplate out of defining objc structs.
# NOTE: \@ below is a variable that gets auto-incremented by the assembler on
# each macro invocation. It serves as a mechanism for generating unique symbol
# names for each macro call.
.macro .objc_classname name
.section __TEXT,__objc_classname,cstring_literals
L_OBJC_CLASS_NAME_.\@:
.asciz "\name"
.section __DATA,__objc_const
.quad L_OBJC_CLASS_NAME_.\@
.endm
# struct method_t {
# const char *name;
# const char *type;
# void *impl;
# }
.macro .objc_method name, type, impl
.section __TEXT,__objc_methname,cstring_literals
L_OBJC_METH_VAR_NAME_.\@:
.asciz "\name"
.section __TEXT,__objc_methtype,cstring_literals
L_OBJC_METH_VAR_TYPE_.\@:
.asciz "\type"
.section __DATA,__objc_const
.quad L_OBJC_METH_VAR_NAME_.\@
.quad L_OBJC_METH_VAR_TYPE_.\@
.quad "\impl"
.endm
# Generate a method_t with a basic impl that just contains `ret`.
.macro .empty_objc_method name, type, impl
.text
"\impl":
ret
.objc_method "\name", "\type", "\impl"
.endm

View File

@ -152,14 +152,11 @@ _OBJC_CLASS_$_FooClass:
.space 40
.section __DATA,__objc_const
.p2align 3
__OBJC_$_CATEGORY_INSTANCE_METHODS_FooClass_$_barcat:
.p2align 3
__OBJC_$_CATEGORY_FooClass_$_barcat:
.quad L_CAT_NAME
.quad _OBJC_CLASS_$_FooClass
.quad __OBJC_$_CATEGORY_INSTANCE_METHODS_FooClass_$_barcat
.quad 0
.quad 0
.quad 0
.quad 0