Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to hide objects in an objective-c library

I am trying to create a static library written in Objective-C. I would like to hide all implementation details from consumers of this library. In this example, the "OneThing" object uses other features internal to the library, including the "SecretThing", which may be used by many things inside the Library (and can't be hidden inside of OneThing). However I don't want users of the library to see that OneThing uses SecretThing, or that SecretThing even exists, even if they poke around in the .a file.

@interface OneThing
+ (void) do;
@end
@interface SecretThing
+ (void) undo;
@end
@implementation OneThing
+ (void) do
{
    [SecretThing undo];
}
@end
@implementation SecretThing
+ (void) undo { }
@end

If we compile this, and inspect the symbol table:

% cc -c onething.m
% nm onething.o | grep Thing
0000000000000000 t +[OneThing do]
00000000000002f8 s +[OneThing do].eh
0000000000000040 t +[SecretThing undo]
0000000000000320 s +[SecretThing undo].eh
0000000000000078 S _OBJC_CLASS_$_OneThing
0000000000000050 S _OBJC_CLASS_$_SecretThing
00000000000000a0 S _OBJC_METACLASS_$_OneThing
00000000000000c8 S _OBJC_METACLASS_$_SecretThing
0000000000000128 s l_OBJC_$_CLASS_METHODS_OneThing
00000000000001d8 s l_OBJC_$_CLASS_METHODS_SecretThing
0000000000000190 s l_OBJC_CLASS_RO_$_OneThing
0000000000000240 s l_OBJC_CLASS_RO_$_SecretThing
0000000000000148 s l_OBJC_METACLASS_RO_$_OneThing
00000000000001f8 s l_OBJC_METACLASS_RO_$_SecretThing
% 

We see OneThing and SecretThing equally exposed, as we would expect. Now it would be great to somehow resolve internal-to-the-library use of SecretThing, and only expose OneThing to the outside world. What I wanted to work was this (picking just one method to try and manage):

% ld -r onething.o -exported_symbol "+[OneThing do]" -o onlyonething.o

I was hoping this would mark "+[OneThing do]" as type " T ", (Text global), which would then survive a "strip". It does not. This bugs me because I thought this worked at one point in time, and maybe when I updated toolchains I got a new version of ld ("ld -v"==ld64-133.3) that works differently.

I am out of ideas except getting the source to the linker and writing my own new flag to do what I want. I hope I am just being stupid and there is something I do not understand which makes this easier.


In the answers it was suggested that this would be fundamentally impossible, because you need all the linkage information available at runtime to do method dispatch. I believe this experiment proves that isn't true.

# Move OneThing and SecertThing into their own files, with their own .h
% cat c_api.m
#include "OneThing.h"

void one_thing_do()
{
    [OneThing do];
}
% cc -c c_api.c
% cat main.m
int main(int argc, char**argv)
{
    extern void one_thing_do();

    one_thing_do();
}
% cc main.m c_api.o onething.o secretthing.o -framework Foundation
% ./a.out
% ( runs to completion, no errors )

Now we try and hide the parts we don't want the world to see:

% ld -r c_api.o onething.o secretthing.o -o strip_c_api.o -exported_symbol "_one_thing_do"
% strip -x c_strip_c_api.o
% nm strip_c_api.o
0000000000000118 s EH_Frame1
0000000000000098 s EH_Frame1
00000000000000d8 s EH_Frame1
                 U __objc_empty_cache
                 U __objc_empty_vtable
                 U _objc_msgSend
0000000000000000 T _one_thing_do
0000000000000130 s func.eh
00000000000000f0 s func.eh
00000000000000b0 s func.eh
0000000000000020 t l001
0000000000000060 t l002
0000000000000170 s l003
0000000000000190 s l004
00000000000001d8 s l005
0000000000000220 s l006
0000000000000240 s l007
0000000000000288 s l008
00000000000002f0 s l009
0000000000000318 s l010
0000000000000340 s l011
0000000000000368 s l012
% clang main.m strip_c_api.o -framework Foundation
% ./a.out
% nm a.out
0000000100001280 S _NXArgc
0000000100001288 S _NXArgv
0000000100001298 S ___progname
0000000100000000 T __mh_execute_header
                 U __objc_empty_cache
                 U __objc_empty_vtable
0000000100001290 S _environ
                 U _exit
0000000100000db0 T _main
                 U _objc_msgSend
0000000100000de0 T _one_thing_do
0000000100001000 s _pvars
                 U dyld_stub_binder
0000000100000d70 T start
like image 384
mtoy Avatar asked Jul 26 '12 18:07

mtoy


2 Answers

This is not possible due to the dynamic way the Objective-C runtime works. The runtime needs these pieces of information to instantiate the class and send messages to it, so there really is no way to hide this information without breaking your library.

like image 77
JustSid Avatar answered Oct 19 '22 23:10

JustSid


From Apple's Documentaion, some more details: C++ Runtime Environment Programming Guide

Symbol Visibility and Objective-C

Objective-C is a strict superset of C, and Objective-C++ is a strict superset of C++. This means that all of the discussion regarding symbol visibility in C and C++ applies to Objective-C and Objective-C++ too. You can use the compiler flags, visibility attributes, and the visibility pragma to hide C and C++ code in your Objective-C code files.

In a 32-bit OS X project, these visibility controls apply only to the C or C++ subset of your code. They do not apply to Objective-C classes and methods. Objective-C class and message names are bound by the Objective-C runtime, not by the linker, so the notion of visibility does not apply to them. There is no mechanism for hiding an Objective-C class or method defined in a dynamic library from the clients of that library.

When building for x86_64 OS X or for iOS, symbol visibility does affect objective-C classes. Hiding a class is not a security panacea—enterprising developers can access any class with objective-C runtime calls—but if you directly reference a class whose visibility is hidden in a library you link to, you will get a linker error. This means that if a given class is intended to be usable outside the library or executable it's defined in, you need to ensure proper symbol visibility.

like image 37
mtoy Avatar answered Oct 20 '22 00:10

mtoy