Avoiding the main (entry point) in a C program

2020-01-28 06:01发布

问题:

Is it possible to avoid the entry point (main) in a C program. In the below code, is it possible to invoke the func() call without calling via main() in the below program ? If Yes, how to do it and when would it be required and why is such a provision given ?

int func(void)
{
     printf("This is func \n");
     return 0;
}

int main(void)
{
     printf("This is main \n");
     return 0;
}

回答1:

If you're using gcc, I found a thread that said you can use the -e command-line parameter to specify a different entry point; so you could use func as your entry point, which would leave main unused.

Note that this doesn't actually let you call another routine instead of main. Instead, it lets you call another routine instead of _start, which is the libc startup routine -- it does some setup and then it calls main. So if you do this, you'll lose some of the initialization code that's built into your runtime library, which might include things like parsing command-line arguments. Read up on this parameter before using it.

If you're using another compiler, there may or may not be a parameter for this.



回答2:

When building embedded systems firmware to run directly from ROM, I often will avoid naming the entry point main() to emphasize to a code reviewer the special nature of the code. In these cases, I am supplying a customized version of the C runtime startup module, so it is easy to replace its call to main() with another name such as BootLoader().

I (or my vendor) almost always have to customize the C runtime startup in these systems because it isn't unusual for the RAM to require initialization code for it to begin operating correctly. For instance, typical DRAM chips require a surprising amount of configuration of their controlling hardware, and often require a substantial (thousands of bus clock cycles) delay before they are useful. Until that is complete, there may not even be a place to put the call stack so the startup code may not be able to call any functions. Even if the RAM devices are operational at power on, there is almost always some amount of chip select hardware or an FPGA or two that requires initialization before it is safe to let the C runtime start its initialization.

When a program written in C loads and starts, some component is responsible for making the environment in which main() is called exist. In Unix, linux, Windows, and other interactive environments, much of that effort is a natural consequence of the OS component that loads the program. However, even in these environments there is some amount of initialization work to do before main() can be called. If the code is really C++, then there can be a substantial amount of work that includes calling the constructors for all global object instances.

The details of all of this are handled by the linker and its configuration and control files. The linker ld(1) has a very elaborate control file that tells it exactly what segments to include in the output, at what addresses, and in what order. Finding the linker control file you are implicitly using for your toolchain and reading it can be instructive, as can the reference manual for the linker itself and the ABI standard your executables must follow in order to run.

Edit: To more directly answer the question as asked in a more common context: "Can you call foo instead of main?" The answer is "Maybe, but but only by being tricky".

On Windows, an executable and a DLL are very nearly the same format of file. It is possible to write a program that loads an arbitrary DLL named at runtime, and locates an arbitrary function within it, and calls it. One such program actually ships as part of a standard Windows distribution: rundll32.exe.

Since a .EXE file can be loaded and inspected by the same APIs that handle .DLL files, in principle if the .EXE has an EXPORTS section that names the function foo, then a similar utility could be written to load and invoke it. You don't need to do anything special with main, of course, since that will be the natural entry point. Of course, the C runtime that was initialized in your utility might not be the same C runtime that was linked with your executable. (Google for "DLL Hell" for hint.) In that case, your utility might need to be smarter. For instance, it could act as a debugger, load the EXE with a break point at main, run to that break point, then change the PC to point at or into foo and continue from there.

Some kind of similar trickery might be possible on Linux since .so files are also similar in some respects to true executables. Certainly, the approach of acting like a debugger could be made to work.



回答3:

A rule of thumb would be that the loader supplied by the system would always run main. With sufficient authority and competence you could theoretically write a different loader that did something else.



回答4:

Rename main to be func and func to be main and call func from name.

If you have access to the source, you can do this and it's easy.



回答5:

If you are using an open source compiler such as GCC or a compiler targeted at embedded systems you can modify the C runtime startup (CRT) to start at any entry point you need. In GCC this code is in crt0.s. Generally this code is partially or wholly in assembler, for most embedded systems compilers example or default start-up code will be provided.

However a simpler approach is to simply 'hide' main() in a static library that you link to your code. If that implementation of main() looks like:

int main(void)
{
    func() ;
}

Then it will look to all intents and purposes as if the user entry point is func(). This is how many application frameworks with entry points other than main() work. Note that because it is in a static library, any user definition of main() will override that static library version.



回答6:

The solution depends on the compiler and linker which you use. Always is that not main is the real entry point of the application. The real entry point makes some initializations and call for example main. If you write programs for Windows using Visual Studio, you can use /ENTRY switch of the linker to overwrite the default entry point mainCRTStartup and call func() instead of main():

#ifdef NDEBUG
void mainCRTStartup()
{
    ExitProcess (func());
}
#endif

If is a standard practice if you write the most small application. In the case you will receive restrictions in the usage of C-Runtime functions. You should use Windows API function instead of C-Runtime function. For example instead of printf("This is func \n") you should use OutputString(TEXT("This is func \n")) where OutputString are implemented only with respect of WriteFile or WriteConsole:

static HANDLE g_hStdOutput = INVALID_HANDLE_VALUE;
static BOOL g_bConsoleOutput = TRUE;

BOOL InitializeStdOutput()
{
    g_hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
    if (g_hStdOutput == INVALID_HANDLE_VALUE)
        return FALSE;

    g_bConsoleOutput = (GetFileType (g_hStdOutput) & ~FILE_TYPE_REMOTE) != FILE_TYPE_DISK;
#ifdef UNICODE
    if (!g_bConsoleOutput && GetFileSize (g_hStdOutput, NULL) == 0) {
        DWORD n;

        WriteFile (g_hStdOutput, "\xFF\xFE", 2, &n, NULL);
    }
#endif

    return TRUE;
}

void Output (LPCTSTR pszString, UINT uStringLength)
{
    DWORD n;

    if (g_bConsoleOutput) {
#ifdef UNICODE
        WriteConsole (g_hStdOutput, pszString, uStringLength, &n, NULL);
#else
        CHAR szOemString[MAX_PATH];
        CharToOem (pszString, szOemString);
        WriteConsole (g_hStdOutput, szOemString, uStringLength, &n, NULL);
#endif
    }
    else
#ifdef UNICODE
        WriteFile (g_hStdOutput, pszString, uStringLength * sizeof (TCHAR), &n, NULL);
#else
    {
        //PSTR pszOemString = _alloca ((uStringLength + sizeof(DWORD)));
        CHAR szOemString[MAX_PATH];
        CharToOem (pszString, szOemString);
        WriteFile (g_hStdOutput, szOemString, uStringLength, &n, NULL);
    }
#endif
}

void OutputString (LPCTSTR pszString)
{
    Output (pszString, lstrlen (pszString));
}


回答7:

This really depends how you are invoking the binary, and is going to be reasonably platform and environment specific. The most obvious answer is to simply rename the "main" symbol to something else and call "func" "main", but I suspect that's not what you are trying to do.