How can I combine many C++ main() functions into a single binary?

Viewed 91

I'm working with a large C++ project that presently produces 66 different binaries. Each entrypoint contains its own global variables and main() function, though there's a lot of common code that's provided through a shared library.

For ease of distribution, I would like to distribute a single statically-linked binary, like you'd get from a Go or Rust project. Instead of invoking:

./ProgramFoo
./ProgramBar

I'd like to have a single combined binary that "execs" itself into one of those tools behind the scenes based on argv parameters, sort of like how busybox works:

./CombinedProgram ProgramFoo
./CombinedProgram ProgramBar

Look, I get that there's a "right" way to do this — refactor all 66 source files. Replace all global state with class singletons. Replace all the main() functions with entrypoint functions that could be dispatched from a single, unifying main() function. That sounds like a lot of work and a fair amount of disruption to all the other developers on the project. Is there truly no alternative on the compiler/linker level?

(I also don't want to just archive the binaries inside the CombinedProgram, write them to disk, and exec them. Boo. If I wanted a tarball, I'd just use a tarball.)

My understanding of C/C++ binary production is that the compiler will insert a crt0 startup routine that will initialize all my global state and then invoke main() with the appropriate parameters. Could I... perhaps... sneak some code in before that crt0 that peeks at argv and then proceeds down the correct code path?

1 Answers

Is there truly no alternative on the compiler/linker level?

Not really. Refactoring is the best / least work way to accomplish your task.

% cc -o one busy.c ; ln one two ; ./one ; ./two one two

The main() needs to look at ARGV[0] to determine how it was called. Then act on that information.

Simplest example:

#include <stdio.h>
#include <string.h>

int one(int argc, char **argv) {
    printf("one\n");
    return 1;
}

int two(int argc, char **argv) {
    printf("two\n");
    return 2;
}

int main(int argc, char **argv) {
    char *end = argv[0];
    int len = strlen(end);
    end += len; // jump to the end of the command.
    if (argc >= 1) {
        if (!strcmp(end-3, "one")) {
            return one(argc, argv);
        } else if (!strcmp(end-3, "two")) {
            return two(argc, argv);
        }
    }
}
Related