6

On macOS 10.12.6, I have a shared library and an executable (both built myself, SIP should not apply AFAIK) and I'd like to preload the library for the executable. It fails, with a return code of 1, and silently otherwise:

$ ./bar/exec
<does stuff>
$ DYLD_FORCE_FLAT_NAMESPACE=1 DYLD_INSERT_LIBRARIES=./foo/my.dylib ./bar/exec
<returns immediately>
$ echo $?
1
# 1 is not an explicitly returned value from ./bar/exec

If I add either DYLD_PRINT_LIBRARIES or DYLD_PRINT_LIBRARIES_POST_LAUNCH options as documented in man dyld, they both show that both the library and the executable have been loaded successfully:

...
dyld: loaded: /full/path/to/exec
dyld: loaded: ./foo/my.dylib
...

If I try to run the executable with preload in either dtruss or lldb, it runs, without the library preloaded. This might make sense because these tools are SIP-protected.

How do I go about debugging this?

UPDATE

Thanks to @macmoonshine, I am able to reproduce the return code 1 exit under lldb by setting the preload environment from LLDB itself as opposed for LLDB:

(lldb) env DYLD_FORCE_FLAT_NAMESPACE=1 DYLD_INSERT_LIBRARIES=./foo/my.dylib
(lldb) run
...
Process 24130 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
    frame #0: 0x00007fffc8aac310 libsystem_kernel.dylib`__exit
...
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
  * frame #0: 0x00007fffc8aac310 libsystem_kernel.dylib`__exit
    frame #1: 0x000000010008ebac my.dylib`my_init at my.c:86 [opt]
    frame #2: 0x0000000100018a1b dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 385
    frame #3: 0x0000000100018c1e dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
    frame #4: 0x00000001000144aa dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 338
    frame #5: 0x0000000100013524 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 138
    frame #6: 0x00000001000135b9 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 75
    frame #7: 0x0000000100005434 dyld`dyld::initializeMainExecutable() + 125
    frame #8: 0x00000001000098c6 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 3966
    frame #9: 0x0000000100004249 dyld`dyldbootstrap::start(macho_header const*, int, char const**, long, macho_header const*, unsigned long*) + 470
    frame #10: 0x0000000100004036 dyld`_dyld_start + 54

This is a start for debugging.

Laurynas Biveinis
  • 10,547
  • 4
  • 53
  • 66
  • Have you tried to run it in `lldb` to get a stack trace? – clemens Aug 08 '17 at 05:02
  • I have, as I wrote in the question - the library is silently not preloaded, thus I'm not sure what stacktrace I should be looking for in lldb? – Laurynas Biveinis Aug 08 '17 at 07:26
  • If you run your executable in `lldb`, it should crash, too. `lldb`will show you the crash position, if type `bt` after crash. – clemens Aug 08 '17 at 07:41
  • I have no evidence of any crash: the app run w/o preload does not crash, the app run w/ (which does not result in actual preload) and w/o preload in lldb does not crash. The only thing that could suggest a crash is that exit code 1 when run outside w/ preload outside of lldb. But since lldb ignores the preloads, I cannot reproduce it under lldb. – Laurynas Biveinis Aug 08 '17 at 11:18
  • 1
    You can set the environment for the executable in lldb with `env`, e. g.: `DYLD_FORCE_FLAT_NAMESPACE=1` and `DYLD_INSERT_LIBRARIES=./foo/my.dylib` should work. – clemens Aug 08 '17 at 11:21
  • Thanks, I only tried setting them in the shell of LLDB itself - I will test and update – Laurynas Biveinis Aug 08 '17 at 11:32
  • "(lldb) process launch -v DYLD_FORCE_FLAT_NAMESPACE=1 DYLD_INSERT_LIBRARIES=./foo/my.dylib" results in a running app, but adding an "DYLD_PRINT_LIBRARIES_POST_LAUNCH=ON" does not result in any output as it does when run from shell. Trying with env – Laurynas Biveinis Aug 08 '17 at 11:37
  • Aha, with env it reproduces the exit code of 1 but alas this is not a crash: `(lldb) run Process 24081 launched: '/full/path/to/bar/exec' (x86_64) Process 24081 exited with status = 1 (0x00000001) ` I wonder if I need a breakpoint on exit() or what would be the right function name on mac? – Laurynas Biveinis Aug 08 '17 at 11:39
  • `exit` is standard c and setting a symbolic breakpoint on it should work. – clemens Aug 08 '17 at 11:44
  • exit did not hit, _exit did - updated the question. This indeed gives a stacktrace which useful for debugging the library. Thanks - do you want to convert your comments to an answer? – Laurynas Biveinis Aug 08 '17 at 11:49
  • What happens on line 86 in `my.c`? – clemens Aug 08 '17 at 11:55
  • Well, _exit(); :) – Laurynas Biveinis Aug 08 '17 at 11:56

1 Answers1

4

You can run your code in lldb, where you can set the environment with the env command:

env DYLD_FORCE_FLAT_NAMESPACE=1
env DYLD_INSERT_LIBRARIES=./foo/my.dylib
run

With lldb you can run your program until it crashes. If it exists abnormally with exit() (or _exit()), you should set symbolic breakpoints with b exitand b _exit. So you have the chance to get a backtrace where it fails.

clemens
  • 16,716
  • 11
  • 50
  • 65