Main is usually a function, so when is it not? (2015)
jroweboy.github.io
jroweboy.github.io
I find these "hey there is this language called C and you can do some really weird stuff in it!" articles humorous because from the beginning of time there have been people who are fascinated by this aspect of the C language and gave rise to the whole obfuscated C contest thing.
The idea that a symbol references an address and its type is only a convenience in the source code always messes people up. When I've taught C to people there is always that one person who says "What if you cast a string pointer to a function and called it! Huh?" and I explain that is perfectly legal C and has been exploited for years and you can practically see their brain change conceptual planes in mid-air :-)
You gotta write C or C++ for the abstract machine defined by the standards. Thinking about C in terms of some concrete assembly language or architecture can be problematic.
I would argue that it's less than "perfectly" legal: The C standard lists it as a "common extension":
J.5.7 Function pointer casts
A pointer to an object or to void may be cast to a pointer to
a function, allowing data to be invoked as a function
And on a practical level, it seems to me that the increasing proliferation of NX enforcement is cramping the acceptance of this extension.Platform support is irrelevant, systems not supporting the access flags simply gives you more access. E.g. running a modern Linux on pre-Athlon 64 CPUs will just cause all readable pages to be executable as well.
It would be interesting to know if you can ask the linux linker to put rodata in its own r-- segment -- I scanned the docs but didn't see an option for it.
Depends on what you mean, the original page table entries have a R (Read/Write) flag, if not set the page is read-only. What you couldn't do was mark a page non-executable. But nevertheless the second LOAD segment is marked rw- and not rwx, so it would seem that it wasn't deemed a problem in the past having segments with unsupported permissions.
At the time when we got the NX bit it did happen that some programs broke because they expected executable data, but the security benefits were more important.
> It would be interesting to know if you can ask the linux linker to put rodata in its own r-- segment -- I scanned the docs but didn't see an option for it.
You have to write your own linker script, see e.g. [0].
[0] http://www.cl.cam.ac.uk/~srk31/blog/devel/custom-elf-phdrs.h...
It's just not the default, for historical reasons.
What is used at runtime is the program headers named LOAD, which specifies the segments. Here is an example:
LOAD off 0x0000000000000000 vaddr 0x0000000000400000 paddr 0x0000000000400000 align 2**21
filesz 0x000000000000069c memsz 0x000000000000069c flags r-x
LOAD off 0x0000000000000e10 vaddr 0x0000000000600e10 paddr 0x0000000000600e10 align 2**21
filesz 0x0000000000000220 memsz 0x0000000000000650 flags rw-
... Idx Name Size VMA LMA File off Algn
25 .bss 00000420 0000000000601040 0000000000601040 00001030 2**5
ALLOC
So note that .bss is placed at the end of the second segment (.bss at 0x601040 + 0x420 = 0x601460,
and the second segments ends in memory at 0x600e10 + 0x650 which is also 0x601460).
But note also that the file size is only 0x220, which places the end of the data mapped from the
file at 0x600e10 + 0x220 = 0x601030 which is slightly before .bss starts. So what happens is
that the information about .bss is actually described by setting the memory size of the LOAD
segment bigger than the file size, the dynamic linker will then fill the rest with zeroes.[0] http://www.muppetlabs.com/~breadbox/software/elfkickers.html
My point was essentially that for ELF this is not some kind of kludgeish special case for .bdd, but general feature that any segment can be zero extended to arbitrary size larger than what is contained in the executable image (although on sane platforms it is not useful for anything but .bss)
Amusingly enough, I ran into some issues because of this; e.g. valgrind's symbolication was failing on LLD-linked binaries unless I used --no-rosegment. I didn't dig into it too much, but it's probably making some bad assumptions about the text section's load address. (LLD places the read-executable segment after the read-only segment, and I think valgrind was assuming that the text section would be part of the first segment.)
For example, say you have this:
const char* const errors[] = { "error 1", "error 2", /* ... */ };
Now, you'd expect all to be read-only, but with Position Independent Code (PIC) and Position Independent Executables (PIE), which are prerequisite for Address Space Layout Randomization (ASLR), it's not, because that errors array contains pointers to those literal strings, which means those pointers need to be "relocated": the executable or library can't contain the actual pointer to those, since their addess may vary between executions. So the executable/library only contains offsets, and the dynamic linker adjusts those offsets to make them real pointers. Thus that errors array is actually not read-only.
There is an additional ELF segment for those read-only-but-really-write-because-relocated data, GNU_RELRO, which tells the dynamic linker to remove the write bit on those parts of the read-write section where relocations happened.
int noMain() __attribute__ (constructor);
int noMain() {
printf("Goodbye, main()");
} // &etc.On the other hand something around the lines of:
void _start(){
write(1, "Hello world\n", 12);
exit(0);
}
should probably work, given that you link it without CRT startup code (ie. gcc -nostdlib)Now for even more confusion, have the main function (which never runs) call the _start function.
Unless you come across a TA like me (I've been one before), who will comment on the fact that using xor+inc or push+pop would be shorter ways to set a register to a small immediate. ;-)
Knowing the target system makes a lot of these things quite a bit easier. I had really good luck in college knowing our graphics teacher was using a 286.
As a TA you pretty much can be a hardass or save time for the rest of the semester and just mark 100.
Another early scenario was declaring main() as void in some embedded systems. I guess there was nothing to return to but it was still odd.
int main()
{
int a = 12;
}
$ gcc maintest.c
$ ./a.out
$ echo $?
148 $ cat test.c
int main(void) {int a = 12;}
$ gcc-6 test.c -o test --std=c89; ./test; echo $?
162
$ gcc-6 test.c -o test --std=c99; ./test; echo $?
0
Must be a C99 thing. int main(void)
{
__asm {
mov eax, 123
}
}
// visual studio command line
// cl testc.c
// cl testcpp.cpp
// testc.exe
// echo C %errorlevel%
// testcpp.exe
// echo CPP %errorlevel%
Indeed. It's a C99 thing.For those not familiar with MSVC. The C compiler is not C99 compliant and the C++ compiler is.
Often this is as easy as coding to an older standard, like C89 with a few selected features from later standards, and adding some compatibility features / problem detection in the build system.
And don't rely on features that are difficult to explain and/or add only questionable value. Just return 0 from main, it's the obvious simple thing to do.
That calling convention on x86 specifies that the return value is red from the EAX register. So, when your main() function exits, the return value is red from EAX, it's that simple.
The compilers may add boilerplate code around the main, in fact main() is rarely the real main() function, but that doesn't change the spec.
And as I've just tested, gcc doesn't return zero termination status if you reach the } at the end of main.
The main() is called like a regular function, by the system thing that executes programs.
Depending on the compiler and the flags, the main() is not the real entry point of the program. It can add another entry point to do some magic, like setting the return code.
https://stackoverflow.com/questions/38063529/x86-32-x86-64-p...
...and if you want to expand it to run on a SPARC or MIPS or something else...
https://hackaday.io/project/18614-polyglot-one-binary-multip...
The general idea is to find a set of bytes which can be interpreted in different (and valid) ways by all the architectures you want to support, and using those differences, jump to architecture-specific code.
But "main is not a function" does not produce undefined behavior.
main() is specified by C. The OS as such doesn't know or care about main. Headers in the binary executable specify where the OS should begin execution, and this is rarely in main.
1. In this International Standard, "shall" is to be interpreted as a requirement on an implementation or on a program;
2. If a "shall" or "shall not" requirement that appears outside of a constraint is violated, the behavior is undefined.
3. (5.1.2.2) A hosted environment need not be provided, but shall conform to the following specifications if present.
4. (5.1.2.2.1) The function called at program startup is named main. The implementation declares no prototype for this function. It shall be defined with a return type of int and with no parameters:
int main(void) { /* ... */ }
or with two parameters (referred to here as argc and argv, though any names may be used, as they are local to the function in which they are declared): int main(int argc, char *argv[]) { /* ... */ }
or equivalent; or in some other implementation-defined manner.5. (J.2 Undefined behavior) - A program in a hosted environment does not define a function named main using one of the specified forms (5.1.2.2.1).