Question

The following program compiles perfectly with no errors or warnings (even with -Wall) in g++, but crashes immediately.

#include <cstdio>

int stuff(void)
{
    puts("hello there.");
    return 0;
}


int (*main)(void) = stuff;

This is an (obviously horribly misguided) attempt at running a C++ program without explicitly declaring main as a function. It was my intention for the program to execute stuff by binding it to the symbol main. I was very surprised that this compiled, but why exactly does it fail, having compiled? I've looked at the generated assembly but I don't know enough to understand it at all.

I'm fully aware that there are plenty of restrictions on how main can be defined/used, but I'm unclear on how my program breaks any of them. I haven't overloaded main or called it within my program... so exactly what rule am I breaking by defining main this way?

Note: this was not something I was trying to do in actual code. It was actually the beginnings of an attempt to write Haskell in C++.

Was it helpful?

Solution

In the code that runs before main, there is something like:

extern "C" int main(int argc, char **argv);

The problem with your code is that if you have a function pointer called main, it is not a the same as a function (as opposed to Haskell where a function and a funciton pointer is pretty much interchangable - at least with my 0.1% knowledge of Haskell).

Whilst the compiler will happily accept:

int (*func)()  = ...;

int x = func();

as a valid call to the function pointer func. However, when the compiler generates code to call func, it actually does this in a different way [although the standard doesn't say how this should be done, and it varies on different processor architectures, in practice it loads the value in the pointer variable, and then calls this content].

When you have:

int func() { ... }

int x = func();

the call to func just refers to the address of func itself, and calls that.

So, assuming your code actually does compile, the startup code before main will call the address of your variable main rather than indirectly reading the value in main and then calling that. In modern systems, this will cause a segfault because main lives in the data segment which is not executable, but in older OS's it would most likely crash due to main does not contain real code (but it may execute a few instructions before it falls over in this case - in the dim and distant past, I've accidentally run all sorts of "rubbish" with rather difficult to discover causes...)

But since main is a "special" function, it's also possible that the compiler says "No, you can't do this".

It used to work, many years ago to do this:

char main[] = { 0xXX, 0xYY, 0xZZ ... }; 

but again, this doesn't work in a modern OS, because main ends up in the data section, and it's not executable in that section.

Edit: After actually testing the posted code, at least on my 64-bit Linux, the code actually compiles, but crashes, unsurprisingly, when it tries to execute main.

Running in GDB gives this:

Program received signal SIGSEGV, Segmentation fault.
0x0000000000600950 in main ()
(gdb) bt
#0  0x0000000000600950 in main ()
(gdb) disass
Dump of assembler code for function main:
=> 0x0000000000600950 <+0>: and    %al,0x40(%rip)        # 0x600996
   0x0000000000600956 <+6>: add    %al,(%rax)
End of assembler dump.
(gdb) disass stuff
Dump of assembler code for function stuff():
   0x0000000000400520 <+0>: push   %rbp
   0x0000000000400521 <+1>: mov    %rsp,%rbp
   0x0000000000400524 <+4>: sub    $0x10,%rsp
   0x0000000000400528 <+8>: lea    0x400648,%rdi
   0x0000000000400530 <+16>:    callq  0x400410 <puts@plt>
   0x0000000000400535 <+21>:    mov    $0x0,%ecx
   0x000000000040053a <+26>:    mov    %eax,-0x4(%rbp)
   0x000000000040053d <+29>:    mov    %ecx,%eax
   0x000000000040053f <+31>:    add    $0x10,%rsp
   0x0000000000400543 <+35>:    pop    %rbp
   0x0000000000400544 <+36>:    retq   
End of assembler dump.
(gdb) x main
0x400520 <stuff()>: 0xe5894855
(gdb) p main
$1 = (int (*)(void)) 0x400520 <stuff()>
(gdb) 

So, we can see that main is not really a function, it's a variable which contains a pointer to stuff. The startup code calls main as if it was a function, but it fails to execute the instructions there (because it's data, and data has the "no execute" bit set - not that you can see that here, but I know it works that way).

Edit2:

Inspecting dmesg shows:

a.out[7035]: segfault at 600950 ip 0000000000600950 sp 00007fff4e7cb928 error 15 in a.out[600000+1000]

In other words, the segmentation fault happens immediately with the execution of main - because it's not executable.

Edit3:

Ok, so it's slightly more convoluted than that (at least in my C runtime library), as the code that calls main is a function that takes the pointer to main as an argument, and calls it through a pointer. This however doesn't change the fact that when the compiler builds the code, it produces a level of indirection less than it needs, and tries to execute the variable called main rather than the function that the variable is pointing at.

Listing __libc_start_main in GDB:

87  STATIC int
88  LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
89           int argc, char *__unbounded *__unbounded ubp_av,
90  #ifdef LIBC_START_MAIN_AUXVEC_ARG
91           ElfW(auxv_t) *__unbounded auxvec,
92  #endif

At this point, printing main gives us a function pointer that points at 0x600950, which is the variable called main (same as what I dissassembled above)

(gdb) p main
$1 = (int (*)(int, char **, char **)) 0x600950 <main>

Note that this is a different variable main than the one called main in the source posted in the question.

OTHER TIPS

There's nothing special here about it being main(). The same will happen if you do this for any function. Consider this example:

file1.cpp:

#include <cstdio>

void stuff(void)
{
     puts("hello there.");
}

void (*func)(void) = stuff;

file2.cpp:

extern "C" {void func(void);}

int main(int argc, char**argv)
{
    func();
}

This will also compile, and then segfault. It is essentially doing the same thing for the function func, but because the coding is explicit it now more apparently looks wrong. main() is a plain C type function with no name mangling, and just appears as a name in the symbol table. If you make it something other than a function, you get a segfault when it executes a pointer.

I guess the interesting part is that the compiler will allow you to define a symbol called main when it is already implicitly declared with a different type.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top