4

I am trying to write a c++ program without main. Is it possible to change the entry point of a mach-o executable to a custom function (other than main())?

If not, then, is it possible to wrap main to call my version of main before the actual C main is called?

Edit:

I want to my custom function to call the C main. If I gave it a constructor attribute or added it to ctor list then main will be called twice. I do not want that to happen.

P.S I'm building executables in Mac OS X High Sierra with clang version 9.1.0

Rohit
  • 175
  • 11

3 Answers3

6

You can use the -e <symbol> option of ld, which you can invoke as -Wl,-e,_<symbol> from clang. Historically, the entry point of a program would be _start from crt0.o, however that has not been a thing on Darwin since Mac OS X 10.8 and iOS 6.0, where the LC_MAIN load command was introduced (replacing LC_UNIXTHREAD). The "old" way can still be used, but would have to be explicitly enabled with the -no_new_main linker flag (which has a counterpart -new_main, should you ever need it). The duty once carried by crt0.o has been shifted to the dynamic linker, /usr/lib/dyld, which can handle both LC_MAIN and LC_UNIXTHREAD as needed.

So given a C program with a main:

// t.c
#include <stdio.h>

int main(int argc, const char **argv)
{
    printf("test %i\n", argc);
    return 0;
}

You can easily create a C++ file like this:

// t.cpp

extern int main(int, const char**);

extern "C" int derp(int argc, const char **argv)
{
    return main(0, (const char*[]){ (const char*)0 });
}

And compile them with clang++ -o t t.cpp -xc t.c -Wl,-e,_derp.
Just be sure to either declare derp as extern "C", or specify the mangled symbol on the command line.

You can also inspect the resulting executable with otool to make sure it uses LC_MAIN rather than LC_UNIXTHREAD:

bash$ otool -l ./t | fgrep -B1 -A3 LC_MAIN
Load command 11
       cmd LC_MAIN
   cmdsize 24
  entryoff 3808
 stacksize 0
Siguza
  • 21,155
  • 6
  • 52
  • 89
  • This looks exactly what I want. Are you sure that I do not lose out on any functionality which I would otherwise get without invoking the `-Wl,-e` command? – Rohit Jun 29 '18 at 23:46
  • If there are functions in .init section, would they be called? Otherwise, for c++ code author is going to be surprised – Severin Pappadeux Jun 29 '18 at 23:55
4

You can pass the -e <symbol name> option to the linker (ld) to specify a different entry point. The default entry point is not main; it's start which is provided by crt1.o and which, in turn, calls main.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • Adding a `-e ` would mean I would have to provide `nostartfiles` as well. But that would prevent linking to mac os's startup files (please correct me if I'm wrong). I only intend to create a wrapper for the main function (or any function that calls main). – Rohit Jun 29 '18 at 21:34
  • @Rohit Actually no, `-e` works as you'd want it to. You can create a small hello world binary with `derp` instead of `main`, compile with `-Wl,-e,_derp`, and examine the output with `otool -l`. Notice the presence of `LC_MAIN`, yet the absence of `LC_UNIXTHREAD`. If I'm reading ld64 source right, that should hold true for macOS since 10.8 and iOS since 6.0. If it doesn't hold true for you, you should still be able to force such behaviour with `-Wl,-new_main`. (You could also force `start` behaviour with `-Wl,-no_new_main`, if you wanted to.) – Siguza Jun 29 '18 at 23:12
  • Also @KenThomases, `start` and crt0.o have not been a thing on Darwin for quite a few years now. The advent of the `LC_MAIN` load command killed them off and moved that duty to `dyld`, the dynamic linker. – Siguza Jun 29 '18 at 23:14
  • @KenThomases Man pages are quite a sad story, because Apple seems to neglect many of them entirely. The one for `ld` seems to have last been updated in 2011 - which was indeed before the introduction of `LC_MAIN`. :/ – Siguza Jun 30 '18 at 00:34
2

You may use _start()

It sets up some stuff, populates the argument array argv, counts how many arguments are there, and then calls main. After main returns, exit is called.

Here's are a couple of references:
https://stackoverflow.com/a/29694977/2302572
http://learningpearls.blogspot.com/2011/02/start-function-inside-c.html

shriroop_
  • 775
  • 6
  • 15