Skip to content

Instantly share code, notes, and snippets.

@OliverLetterer
Created January 26, 2013 17:05
Show Gist options
  • Save OliverLetterer/4643294 to your computer and use it in GitHub Desktop.
Save OliverLetterer/4643294 to your computer and use it in GitHub Desktop.
With this gist, I, as a total assembly noob, am trying to understand the XCDUUID runtime hack (https://github.com/0xced/NSUUID/blob/1.0.1/NSUUID.m#L167-L221) to be able to use NSUUID on iOS < 6.0.
@implementation XCDUUID
+ (void) load
{
// query runtime if NSUUID class already exists, if so => done
if (objc_getClass("NSUUID"))
{
return;
}
/**
* The compiler hardcodes uses of the NSUUID class by referencing the _OBJC_CLASS_$_NSUUID label in objc2
* if this label is Nil or doesnt exist, the class does not exist and cannot be allocated/used
* NSUUIDClassRef is a pointer to this label
* since one cannot access the _OBJC_CLASS_$_NSUUID label in C, one will have to get a pointer to this label via inline assembly and store the XCDUUID class in this label
*/
Class *NSUUIDClassRef = NULL;
// the following assembly stores a pointer to the _OBJC_CLASS_$_NSUUID label in NSUUIDClassRef based on the target platform
#if TARGET_CPU_ARM // arm assembly
__asm(
/**
ios runs on 32 bit arm platform
one cannot move a 32 bit constant into a register, therefore the lower and upper 16 bit will have to be moved into the register for NSUUIDClassRef separately
movw and movt are new in ARMv7 for exactly that purpose (http://blogs.arm.com/software-enablement/251-how-to-load-constants-in-assembly-for-arm-architecture/)
for understanding the inline assembly: http://www.ethernut.de/en/documents/arm-inline-asm.html
short explanation for the following assembly:
there are 3 assembly instructions here:
1) movw %0, :lower16:(L_OBJC_CLASS_NSUUID-(LPC0+4))
2) movt %0, :upper16:(L_OBJC_CLASS_NSUUID-(LPC0+4))
3) LPC0: add %0, pc
%0 will be replaced with the register holding NSUUIDClassRef
L_OBJC_CLASS_NSUUID is a label storing the _OBJC_CLASS_$_NSUUID pointer
LPC0 is just a label for instruction 3
pc is the program counter and is 4 bytes ahead of the current instruction, therefore pc in the third instruction is equal to LPC0+4
*/
"movw %0, :lower16:(L_OBJC_CLASS_NSUUID-(LPC0+4))\n" // move the lower 16 bit of `L_OBJC_CLASS_NSUUID-(LPC0+4)` into NSUUIDClassRef
"movt %0, :upper16:(L_OBJC_CLASS_NSUUID-(LPC0+4))\n" // move the upper 16 bit of `L_OBJC_CLASS_NSUUID-(LPC0+4)` into NSUUIDClassRef
"LPC0: add %0, pc" : "=r"(NSUUIDClassRef) // NSUUIDClassRef = NSUUIDClassRef + pc
/**
the three instructions are therefore equivalent to
NSUUIDClassRef = NSUUIDClassRef + pc
<=>
NSUUIDClassRef = L_OBJC_CLASS_NSUUID-(LPC0+4) + pc
<=>
NSUUIDClassRef = L_OBJC_CLASS_NSUUID-(LPC0+4) + LPC0+4
<=>
NSUUIDClassRef = L_OBJC_CLASS_NSUUID
*/
);
#elif TARGET_CPU_X86_64
__asm("leaq L_OBJC_CLASS_NSUUID(%%rip), %0" : "=r"(NSUUIDClassRef));
#elif TARGET_CPU_X86
void *pc = NULL;
__asm(
"calll L0\n"
"L0: popl %0\n"
"leal L_OBJC_CLASS_NSUUID-L0(%0), %1" : "=r"(pc), "=r"(NSUUIDClassRef)
);
#else
#error Unsupported CPU
#endif
/**
check if we have a pointer to `_OBJC_CLASS_$_NSUUID` and if `_OBJC_CLASS_$_NSUUID` is Nil
*/
if (NSUUIDClassRef && *NSUUIDClassRef == Nil)
{
/**
`objc_duplicateClass(self, "NSUUID", 0)` dublicates `XCDUUID` and registers it with name `NSUUID` in the objc runtime
the newly created class is then stored in `_OBJC_CLASS_$_NSUUID` (`_OBJC_CLASS_$_NSUUID = objc_duplicateClass(self, "NSUUID", 0);`)
*/
*NSUUIDClassRef = objc_duplicateClass(self, "NSUUID", 0);
}
/**
hardcoded references to `_OBJC_CLASS_$_NSUUID` by the compiler will now point to the newly allocated class
*/
}
__asm(
#if defined(__OBJC2__) && __OBJC2__
/**
this is a data section for objc2 class references with the following attributes:
* regular: "A regular section may contain any kind of data and gets no special processing from the link editor. This is the default section type. Examples of regular sections include program instructions or initialized data."
* no_dead_strip: "The no_dead_strip section attribute specifies that a particular section must not be dead-stripped."
Documentation can be found here: https://developer.apple.com/library/mac/#documentation/developertools/Reference/Assembler/040-Assembler_Directives/asm_directives.html
*/
".section __DATA,__objc_classrefs,regular,no_dead_strip\n"
#if TARGET_RT_64_BIT
".align 3\n" // align the next label to 2^3 bytes = 64 bit for 64 bit platforms
"L_OBJC_CLASS_NSUUID:\n" // the L_OBJC_CLASS_NSUUID label will store the _OBJC_CLASS_$_NSUUID label, which is weak referenced (see below)
".quad _OBJC_CLASS_$_NSUUID\n"
#else
".align 2\n"
"L_OBJC_CLASS_NSUUID:\n"
".long _OBJC_CLASS_$_NSUUID\n"
#endif
#else
/**
Data section for NSUUID earlier than objc2
*/
".section __TEXT,__cstring,cstring_literals\n"
"L_OBJC_CLASS_NAME_NSUUID:\n"
".asciz \"NSUUID\"\n"
".section __OBJC,__cls_refs,literal_pointers,no_dead_strip\n"
".align 2\n"
"L_OBJC_CLASS_NSUUID:\n"
".long L_OBJC_CLASS_NAME_NSUUID\n"
#endif
/**
.weak_reference: "The .weak_reference directive causes symbol_name to be a weak undefined symbol present in the output file’s symbol table. This is used by the compiler when referencing a symbol with the weak_import attribute."
*/
".weak_reference _OBJC_CLASS_$_NSUUID\n"
);
@end
@0xced
Copy link

0xced commented Jan 28, 2013

The comments are pretty accurate, well done! To answer your question: you can not simply reference the L_OBJC_CLASS_NSUUID label because of position independent executables. If you don't write it PC-relative (with the LPC0 label) you will get this linker warning and PIE will be disabled:

ld: warning: PIE disabled. Absolute addressing (perhaps -mdynamic-no-pic) not allowed in code signed PIE, but used in +[XCDUUID load] from ~/Library/Developer/Xcode/DerivedData/MyApp-hjrsiejfyzwurlckipecpujunlyh/Build/Intermediates/MyApp.build/Debug-iphoneos/MyApp.build/Objects-normal/armv7/NSUUID.o. To fix this warning, don't compile with -mdynamic-no-pic or link with -Wl,-no_pie

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment