C language without the main function of the helloworld example

  • 2020-04-02 03:01:25
  • OfStack

Almost every programmer's first class is helloworld, so let's take a look at the classic C language, helloworl


 
#include <stdio.h> 
 
int main() 

    printf("hello world!n"); 
    return 0; 


This too is a simple program, but it contains the most important part of a program, that is what we can be seen in almost all of the code of the main function, we compiled into executable file and view the symbol table, filter out the inside of the function is as follows (in order to facilitate check my manually adjust the grep output format, and so your output format is not the same)

$ gcc hello.c -o hello 
$ readelf -s hello | grep FUNC 
Num:    Value          Size Type    Bind   Vis      Ndx Name 
27: 000000000040040c     0 FUNC    LOCAL  DEFAULT   13 call_gmon_start 
32: 0000000000400430     0 FUNC    LOCAL  DEFAULT   13 __do_global_dtors_aux 
35: 00000000004004a0     0 FUNC    LOCAL  DEFAULT   13 frame_dummy 
40: 0000000000400580     0 FUNC    LOCAL  DEFAULT   13 __do_global_ctors_aux 
47: 00000000004004e0     2 FUNC    GLOBAL DEFAULT   13 __libc_csu_fini 
48: 00000000004003e0     0 FUNC    GLOBAL DEFAULT   13 _start 
51: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.5 
52: 00000000004005b8     0 FUNC    GLOBAL DEFAULT   14 _fini 
53: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_ 
58: 00000000004004f0   137 FUNC    GLOBAL DEFAULT   13 __libc_csu_init 
62: 00000000004004c4    21 FUNC    GLOBAL DEFAULT   13 main 
63: 0000000000400390     0 FUNC    GLOBAL DEFAULT   11 _init 

We all know that the user's code starts with the main function. Although we only wrote a main function, we can see from the above table that there are many other functions, such as _start. In fact, the real entry of the program is not the main function, we compile the hello.c code with the following command


$ gcc hello.c -nostdlib 
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000400144 

The -nostdlib command does not link to the standard library and fails to find the entry symbol _start. In this case, the entry symbol _start cannot be found

In fact, the main function is the entry of the user code, it will go to call the system library, before the main function, system libraries to do some initialization, such as the allocation of global variables to memory, initialization stack, thread, etc., when the main function after the execution, through the exit () function to do some cleanup, users can realize _start function


 
#include <stdio.h> 
#include <stdlib.h> 
 
_start(void) 

    printf("hello world!n"); 
    exit(0); 


Execute the following compile command and run

$ gcc hello_start.c -nostartfiles -o hello_start 
$ ./hello_start 
hello world! 

The function of -nostartfiles here is Do not use the standard system startup files when linking, that is, Do not use the standard startup files, but still link to the system library, so the program can still be executed. Again, we look at the symbol table


$ readelf -s hello_start | grep FUNC 
Num:    Value          Size Type    Bind   Vis      Ndx Name 
20: 0000000000400350    24 FUNC    GLOBAL DEFAULT   10 _start 
21: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.5 
22: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@@GLIBC_2.2.5 

Now there are only three functions left, all of them implemented by ourselves, in which printf is turned off at compile time by adding the -fno-builtin option because only one argument is optimized by the compiler to the puts function

If we remove the exit(0) statement from the _start function, the program will get out of the core, because the _start function will finish the program, and our implementation of _start does not call exit() to clean up memory

Finally removed the main function, then found that there must be a _start function, is not very annoying, in fact, _start function is just a default entry, we can specify the entry


 
#include <stdio.h> 
#include <stdlib.h> 
 
int nomain() 

    printf("hello world!n"); 
    exit(0); 

Compile with the following command


$ gcc hello_nomain.c -nostartfiles -e nomain -o hello_nomain 

Where the -e option specifies the program entry symbol, see the symbol table below

$ readelf -s hello_nomain | grep FUNC 
Num:    Value          Size Type    Bind   Vis      Ndx Name 
20: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.5 
21: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@@GLIBC_2.2.5 
22: 0000000000400350    24 FUNC    GLOBAL DEFAULT   10 nomain 

Comparing the symbol table of hello_start, we found that we just changed _start to nomain

So far it's clear that the default entry is the _start function in the standard library, which does some initialization, calls the user's main function, and finally does some cleanup. We can write the _start function to override the _start function in the standard library, or even specify the entry of the program


Related articles: