Possible drawbacks of overriding the entry point of a main program - c

So I was trying to set my own custom name for main in my C program, and I found this answer.
You can specify an entry point to your program using the -e flag to ld.
That means you can override the entry point if you like, but you may not want to do that for a C program you intend to run normally on your machine, since start might do all kinds of OS specific stuff that's required before your program runs.
What would be the (possible) drawbacks of not calling _start from crt0.o and writing my own that simply does whatever I want it to?

The entry point usually does stuff like
Prepare arguments and call main and handles its exit
Call global constructors before main and destructors after
Populate global variables like environ and the like
Initialize the C runtime, e.g. timezone, stdio streams and such
Maybe configure x87 to use 80-bit floating point
Inflate and zero .bss if your loader doesn't
Whatever else is necessary for hosted C programs to run on your platform
These things are tightly coupled to your C implementation, so usually you provide your own _start only when you are targeting a freestanding environment.

Related

C startup code is only written in assembly confusion

I understand that the C startup code is for initializing the C runtime environment, initializes static variables, sets up the stack pointer etc. and finally branches to main().
They say that this can only be written in assembly language as it's platform-specific. However, can't this still be written in C and compiled for the specific platform?
Function calls of course would be not possible because we "more than likely" don't have the stack pointer set up at that stage. I still can't see other main reasons. Thanks in advance.
Startup code can be written in C language only if:
Implementation provides all necessary intrinsic functions to set hardware features that cannot be set using standard C
Provides mechanism of placing fragments of code and data in the specific place and in specific order (gcc support for ld linker scripts for example).
If both conditions are met you can write the startup code in C language.
I use my own startup code written in C (instead of one provided by the chip vendors) for Cortex-M microcontrollers as ARM provides CMSIS header files with all needed inline assembly functions and gcc based toolchain gives me full memory layout control.
Most of the problem with writing early startup code in C is, in fact, the absence of a properly structured stack. It's worse than just not being able to make function calls. All of a C compiler's generated machine code assumes the existence of a stack, pointed to by the ABI-specified register, that can be used for scratch storage at any time. Changing this assumption would be so much work as to amount to a complete second "back end" for the compiler—way more work than continuing to write early startup code by hand in assembly.
Early bootstrap code, bringing up the machine from power-on, also has to do a bunch of special operations that can't usually be accessed from C, like configuring interrupts and virtual memory. And it may have to deal with the code not having been loaded at the address it was linked for, or the relocation table not having been processed, or other similar problems; these also break pervasive assumptions made by the C compiler (e.g. that it can inject a call to memcpy whenever it wants).
Despite all that, most of a user mode C library's startup code will, in fact, be written in C, for exactly the reason you are thinking. Nobody wants to write more code in assembly, over and over for each supported ISA, than absolutely necessary.
A minimal C runtime environment requires a stack, and a jump to a start address. Setting the stack pointer on most architectures requires assembly code. Once a stack is available it is possible to run code generated from C source.
ARM Cortex-M devices load the stack pointer and start address from the vector table on reset, so can in fact boot directly into code generated from C source.
On other architectures, the minimal assembly requires is to set a stack pointer, and jump to the start address. Thereafter it is possible to write other start-up tasks in C ( or C++ even). Such startup code is responsible for establishing the full C runtime, so must not assume static initialisation or library initialisation (no heap or filesystem for example), which are things that must be done by the startup code.
In that sense you can run code generated from C source, but the environment is not strictly conforming until main() has been called, so there are some constraints.
Even where assembly code is used, it need not be the whole start-up code that is in assembly.

What is __libc_start_main and _start?

From the past few days I have been trying to understand what happens behind the curtain when we execute a C program. However even after reading numerous posts I cannot find a detailed and accurate explanation for the same. Can someone please help me out ?
You would usually find special names like this for specific uses when compiling and linking programs.
Keeping in mind that this answer is of a general nature rather than a specific implementation of starting up a C environment, you would typically have something like a _start label, which would be the actual entry point for an executable (from the hosting environment's point of view).
This would be located in some object file or library (like crt0.o for the C runtime start-up code) and would normally be added automagically to your executable file by the linker, similar to the way the C runtime library is added(a).
The operating system code for starting a program would then be akin to (pseudo-code, obviously, and with much less error checking than it should have):
def spawnProg(progName):
id = newProcess() # make process space
loadProgram(pid = id, file = progName) # load program into it
newThread(pid, initialPc = '_start') # make thread to run it
Even though you yourself create a main when coding in C, that's not really where things start happening. There's a whole slew of things that need to be done even before your main program starts. Hence the content of the C start-up code would be along the lines of (at its most simplistic):
_start: ;; Weave magic here to set up C and libc.
;; Note this is example code for a mythical implementation,
;; intended to show how it could work. It is not specific
;; bound to any given implementation.
call __setup_for_c ; Set up C environment.
call __libc_start_main ; Set up standard library.
call _main ; Call your main.
call __libc_stop_main ; Tear down standard library.
call __teardown_for_c ; Tear down C environment.
jmp __exit ; Return to OS.
The "weaving of magic" is whatever it takes to make the environment ready for a C program. This may include things like:
setting up static data (this is supposed to be initialised to zeros so it's probably just an allocation of a chunk of of memory, which is then zeroed by the start-up code - otherwise you would need to store a chunk of that size, already zeroed, in the executable file);
preparing argc and argv on the stack, and even preparing the stack itself (there are specific calling conventions that may be used for C, and it's likely the operating system doesn't necessarily set up the stack at all when calling _start since the needs of the process are not known);
setting up thread-specific data structures (things like random number generators, or error variables, per thread);
initialising the C library in other ways; and so on.
Only once all that is complete will it be okay to call your main function. There's also the likelihood that work needs to be done after your main exits, such as:
invoking atexit handlers (things you want run automatically on exit, no matter where the exit occurs);
detaching from shared resources (for example, shared memory if the OS doesn't do this automatically when it shuts down a process); and
freeing up any other resources not automatically cleaned when the process exits, that would otherwise hang around.
(a) Many linkers can be told to not do that if, for example, you're writing something that doesn't use the standard C library, or if you want to provide your own _start routine for low-level work.

Which mechanism knows the entry point of a program is main()

How does an application program know its entry point is the main() function?
I know an application doesn't know its entry point is main() -- it is directed to main() function by means of the language specification whatever it is.
At that point, where is the specification actually declared? For example in C, entry point shall be main() function. Who provides this mechanism to the program? An operating system or compiler?
I came to the question after disassembling a canonical simple "Hello World" example in Visual Studio.
In this code there are only a few lines and a function main().
But after disassembling it, there are lots of definitions and macro in the memory space and main() is not the only declaration and definiton.
Here below disassembling part's screenshot. I also know there is a strict rule in language definition which is only one main() function must be defined and exist.
To summarize my question: I wonder which mechanism directs or sets main() function as an entry point of an application program.
The application does not know that main() is the entry point. Firstly, we assume C not C++ here despite your picture.
For C the "C" entry point is main(). But you cant just start execution there as we have assumptions, more than that, rules, in C that for example .data needs to be initialized and .bss zeroed.
unsigned x = 1;
unsigned int y;
We expect that when main() is hit that x=1. and most folks assume and perhaps it is specified that y = 0 at that time, I wouldn't make that assumption, but anyway.
We also need a stack pointer and need to deal with argc/argv. If C++ then other stuff has to be done. Even for C depending.
The APPLICATION does not generally know any of this. You are likely working with a C library and that library is/should be responsible for bootstrap code that preceeds main() as well as a linker script for the linker as bootstrap and linker script are intimately related. And one could argue based on some implementations that the C library is separable from the toolchain as we know with gnu you can choose from different ones and those have different bootstraps and linker scripts. But I am sure there are many that are intimately related, there is also a relationship of the library and the operating system as so many C library calls end up in one or countless system calls.
You design an operating system, part of the design of the operating system assuming it supports runtime loadable applications is a file format that the operating systems loader supports, features that the operating system loader wants to support and how they overlap with the file format, not uncommon for the OS to define the file format, but with elf and others (not accidentally/independently created no doubt) you have opportunities for a new OS to use an existing container like elf. The OS design and its loader determines a lot of things, and the C library that mates up with all of that has to follow all of those rules, if integrated into the compiler then the compiler has to play along as well.
It is not the application that knows it is part of the system design and the application is simply a slave to all of that, when you compile on that platform for that platform all of these rules and relationships are in play, you are putting in a very small part of the puzzle, the rest is already in place, what file formats are supported, per format what information is required, what rules are required that the compiler/library solution must provide. The system design dictates if .data and .bss are zeroed by the loader or by the application and what I mean by that is by the bootstrap not the user's portion of the program, you cant bootstrap C in C because that C would need a bootstrap and if that bootstrap were in C that C would need a bootstrap and so on.
int main ( void )
{
return 0;
}
there are a lot of things going on in the background when you compile that program not just the few instructions that might be needed to implement that code.
compile that program on windows and Linux and mac and different versions of each with different compilers for each or C libraries, and different versions of each, etc. And what you should expect to see is perhaps even if the same target ISA, same computer even, some percentage of the combinations MIGHT choose the same few instructions for the function, what is wrapped around it is expected to be maybe similar but not the same. Would be no reason to be surprised if some of the implementations are very different from each other.
And this is all for full blown operating systems that load programs into ram and run them, for embedded things don't be surprised if the differences are even bigger. Within a full blown os you would expect to see an mmu and the application gets a perhaps zero based address space for .text, .data, .bss at a minimum so all the solutions might have a favorite place or favorite number of sections in the same order in the binary but the size of each may be specific to the implementation. The order/size might vary by C library version or compiler version, etc.
The magic is in the system design. and that is not magic, that is design. main() cannot be entered directly and still have various parts of the language still work like .data and .bss init, stack pointer can be solved before the entry but how and where .data and .bss are is application specific so cant be handled by a simple branch to main from the OS.
The linker for your toolchain can be told in various ways where the entry point is it could be assumed/dictated for that tool/target or a command line option or a linker script option, or some special symbol you put on a label or whatever the designers choose. main is assumed to be the C entry point, although that doesn't actually mean it is there might be some C code that precedes it but in general there is some amount of asm (cant bootstrap C with C) and then one or more steps to main().

Win32, WinMain vs custom Entry Point (huge size difference), why?

As topic says.
I noticed that if i use WinMain or any other default Entry Point, a C application can be like 70kb.
But if i just specify a custom Entry Point, say "RawMain", int RawMain().
Then the file will be like 6kb.
So i am wondering, why is this, what does it add/reference to the file?
I could understand there being some small difference in size, but the difference is huge for an empty application.
Thanks!
When building for windows in most environments, the actual program entry point will be provided by a function in a small runtime library. That will do some environment preparation and then call a function you provide, such as main, wmain, WinMain, etc.
The code that runs before your user-provided main function includes running global C++ constructors, enabling TLS variables, initializing global mutexes so that standard-library calls work properly in a multithreaded environment, setting up the standard locale, and other stuff.
One thing that setting the entry point does is starts the linker with an undefined symbol with the name you give the entry point, so for example, if you're using mingw32, the linker will start assuming that it needs to link libmingw32.a and with the undefined symbol __tmainCRTStartup.
The linker will find (hopefully) __tmainCRTStartup in libmingw32.a, and include the object file crtexe.o which contains it, along with anything else needed to satisfy undefined symbols emanating from crtexe.o, which is where the extra size comes from.
When you set your own entry point, you override this, and just set the linker to look for whatever function you specify. You get a smaller executable, but you have to be careful that features you're using don't rely on any of the global initialization that would be done by the runtime's startup function.

What is the need for C startup routine?

Quoting from one of the unix programming books,
When a C program is executed by the
kernelby, one of the exec functions
calls special start-up routine. This
function is called before the main
function is called. The executable
program file specifies this routine as
the starting address for the program;
this is set up by the link editor when
it is invoked by the C compiler. This
start-up routine takes values from the
kernel the command-line arguments and
the environment and sets things up so
that the main function is called as
shown earlier.
Why do we a need a middle man start-up routine. The exec function could have straightway called the main function and the kernel could have directly passed the command line arguments and environment to the main function. Why do we need the start-up routine in between?
Because C has no concept of "plug in". So if you want to use, say, malloc() someone has to initialize the necessary data structures. The C programmers were lazy and didn't want to have to write code like this all the time:
main() {
initialize_malloc();
initialize_stdio();
initialize_...();
initialize_...();
initialize_...();
initialize_...();
initialize_...();
... oh wow, can we start already? ...
}
So the C compiler figures out what needs to be done, generates the necessary code and sets up everything so you can start with your code right away.
The start-up routine initializes the CRT (i.e. creates the CRT heap so that malloc/free work, initializes standard I/O streams, etc.); in case of C++ it also calls the globals' constructors. There may be other system-specific setup, you should check the sources of your run-time library for more details.
Calling main() is a C thing, while calling _start() is a kernel thing, indicated by the entry point in the binary format header. (for clarity: the kernel doesn't want or need to know that we call it _start)
If you would have a non-C binary, you might not have a main() function, you might not even have the concept of a "function" at all.
So the actual question would be: why doesn't a compiler give the address of main() as a starting point? That's because typical libc implementations want to do some initializations before really starting the program, see the other answers for that.
edit as an example, you can change the entry point like this:
$ cat entrypoint.c
int blabla() { printf("Yes it works!\n"); exit(0); }
int main() { printf("not called\n"); }
$ gcc entrypoint.c -e blabla
$ ./a.out
Yes it works!
Important to know also is that an application program is executed in user mode, and any system calls out, set the privileged bit and go into kernel mode. This helps increase OS security by preventing the user from accessing kernel level system calls and a myriad of other complications. So a call to printf will trap, set kernel mode bit, execute code, then reset to user mode and return to your application.
The CRT is required to help you and allow you to use the languages you want in Windows and Linux. it provides some very fundamental bootstrapping into the OS to provide you with feature sets for development.

Resources