Use of -dead_strip_dylibs by default makes the -framework flag appear broken.

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Use of -dead_strip_dylibs by default makes the -framework flag appear broken.

Travis Whitaker
Hello Haskell Friends,

GHC always passes -dead_strip_dylibs to the linker on macOS. This means that Haskell programs that use Objective-C-style dynamic binding (via objc_getClass or similar) won't actually be able to find the Objective-C methods they need at runtime. Here's an example illustrating the problem: Consider this small example program:

#include <stdio.h>

extern void *objc_getClass(char *n);

void test_get_class(char *n)
{
        void *cp = objc_getClass(n);
        if(cp == NULL)
        {
                printf("Didn't find class %s\n", n);
        }
        else
        {
                printf("Found class %s\n", n);
        }
}

int main(int argc, char *argv[])
{
        test_get_class(argv[1]);
        return 0;
}

Building like this:

clang -o hasclass main.c -lobjc -L/usr/lib -framework Foundation -F /System/Library/Frameworks/

Yields an executable that works like this:

$ ./hasclass NSObject
Found class NSObject
$ ./hasclass NSString
Found class NSString
$ ./hasclass NSDate
Found class NSDate

otool shows that we're linked against Foundation properly:

hasclass:
        /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
        /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 1452.23.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.50.4)


Now consider this equivalent Haskell example:

module Main where

import Foreign.C.String
import Foreign.Ptr

import System.Environment

foreign import ccall objc_getClass :: CString -> IO (Ptr a)

testGetClass :: String -> IO ()
testGetClass n = withCString n $ \cn -> do
    cp <- objc_getClass cn
    let m | cp == nullPtr = "Didn't find class " ++ n
          | otherwise     = "Found class " ++ n
    putStrLn m

main :: IO ()
main = getArgs >>= (testGetClass . head)

Building like this:

ghc -o hasclass Main.hs -lobjc -L/usr/lib -framework foundation -framework-path /System/Library/Frameworks/

Yields an executable that works like this:

$ ./hasclass NSObject
Found class NSObject
$ ./hasclass NSString
Didn't find class NSString
$ ./hasclass NSDate
Didn't find class NSDate

otool shows that our load commands for Foundation are missing:

hasclass:
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.50.4)
        /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
        /nix/store/7jdxjpy1p5ynl9qrr3ymx01973a1abf6-gmp-6.1.2/lib/libgmp.10.dylib (compatibility version 14.0.0, current version 14.2.0)
        /usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)


Interestingly, the testGetClass function will work just fine in GHCi, since it always loads all of the shared objects and frameworks it's asked to. As far as I can tell the only way to get a hasclass executable with the correct behavior is to do the final linking manually with Clang.

My understanding is that this behavior was introduced to work around symbol count limitations introduced in macOS Sierra. It would be nice to wrap the frameworks passed to the linker in some flags that spares them from -dead_strip_dylibs. I haven't found such a feature in my limited digging around, but perhaps someone who knows more about systems programming on macOS will have an idea. Statically linking against the system frameworks would be a workable stopgap solution, but I have yet to find an easy way to do that.

I'm curious what others' thoughts are on this issue; it's very difficult to call Objective-C methods from Haskell (without generating Objective-C) without a fix for this.

Regards,

Travis Whitaker

_______________________________________________
ghc-devs mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs
Reply | Threaded
Open this post in threaded view
|

Re: Use of -dead_strip_dylibs by default makes the -framework flag appear broken.

Ben Gamari-3
Travis Whitaker <[hidden email]> writes:

> Hello Haskell Friends,
>
Hi Travis,

This behavior was introduced in
https://phabricator.haskell.org/rGHCb592bd98ff25730bbe3c13d6f62a427df8c78e28
to mitigate macOS's very restrictive linker command limit (which limits
the number of direct dependencies that an object may have; see #14444).

Perhaps it would help if we omitted -dead_strip_dylibs when doing the
final link of an executable? This would allow the user to specify the
-framework flags during the final link, (presumably) ensuring that they
are linked in untouched.

I'm sure Moritz will have insightful to add here.

Cheers,

- Ben


_______________________________________________
ghc-devs mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs

signature.asc (497 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Use of -dead_strip_dylibs by default makes the -framework flag appear broken.

Moritz Angermann-2
Hi,

I haven't found a quick fix, which is a bit annoying.  A few observations:
As we never reference anything from Foundation, we strip it away, and a runtime
test of Foundation classes returns false.  That does make sense in the dead_strip
setting.  But as Travis pointed out is contrary to what the explicit providing of
the framework flag would suggest.  This is however not only limited to frameworks
we will strip *any* dynamic library that is not referenced.  As such it would also
mean that linking -lxyz, without referencing any symbol from xyz, would drop it.
And any runtime symbol lookup for symbols from -xyz *will* also fail.

A rather low-impact fix might be to just add a ghc flag to disable the
-dead_strip_dylibs passing.  That would result in either linking any library
provided, used or not, and eventually run into the LOAD COMMAND SIZE LIMIT
issues if the flag is enabled.  I would still suggest to do the
dead_strip_dylibs by default.

When using static libraries you might need to pass -load_all to prevent
dead_stripping.

In general though, when trying to lookup symbols that are not within the set of
referenced libraries at compile time, one might want to dynamically load them
at runtime anyway (which is what ghci is doing, I believe).

A rather radical alternative would be to try the direct dependencies only path
and try to make the indirect libraries work.  I don't recall why I couldn't get
them to work the last time I tried.  I might have just been missing a single
linker flag.

Cheers,
 Moritz

> On Jun 19, 2018, at 6:49 AM, Ben Gamari <[hidden email]> wrote:
>
> Travis Whitaker <[hidden email]> writes:
>
>> Hello Haskell Friends,
>>
> Hi Travis,
>
> This behavior was introduced in
> https://phabricator.haskell.org/rGHCb592bd98ff25730bbe3c13d6f62a427df8c78e28
> to mitigate macOS's very restrictive linker command limit (which limits
> the number of direct dependencies that an object may have; see #14444).
>
> Perhaps it would help if we omitted -dead_strip_dylibs when doing the
> final link of an executable? This would allow the user to specify the
> -framework flags during the final link, (presumably) ensuring that they
> are linked in untouched.
>
> I'm sure Moritz will have insightful to add here.
>
> Cheers,
>
> - Ben
>
> _______________________________________________
> ghc-devs mailing list
> [hidden email]
> http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs

_______________________________________________
ghc-devs mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs

signature.asc (499 bytes) Download Attachment