Main is usually a function, so when is it not? (2015)
jroweboy.github.io
jroweboy.github.io
char main
[/*x86*/]
__attribute__
((section(".text"))
)="WTYH)9Zj8_j7H)9]R"
"H)9^\350\0\0\0\0H)1^R"
"H))Z8<2u\366j<)9Xj9"
")9j9VY)<$[S_H\xbd^["
"H$@\xcd\200-XP\xf"
"\5XP_j<W\xeb\xe2]"
"Hello World!\n";
/*Linux_Only!*/ ...
call continue
.db "Hello World!\n\0"
continue:
pop eax
...
Since 'call' just turns into a 'push eip; jmp target' (simplified, sorry), the address of the string is now pushed onto the stack. Popping off the top, now eax contains the address of the string "Hello World!\n\0". Since in 32-bit ABI most parameters are passed on the stack, many times you don't even need to 'pop' the address off the stack, it'll just be part of your arguments to the function.Old school malware used this a lot to 1) run regardless of the memory base address it was loaded at and 2) confuse some disassemblers (you can use silly conditionals that are always true or false to control whether you execute the 'call' instruction or not, forcing the disassembler to try and 'disassemble' the string into valid x86 opcodes)
Edit: found this on the 'net:-
https://retrocomputing.stackexchange.com/questions/9328/does...
https://www.ioccc.org/1984/mullender/mullender.c
https://www.ioccc.org/1984/mullender/hint.text
short main[] = {
277, 04735, -4129, 25, 0, 477, 1019, 0xbef, 0, 12800,
-113, 21119, 0x52d7, -1006, -7151, 0, 0x4bc, 020004,
14880, 10541, 2056, 04010, 4548, 3044, -6716, 0x9,
4407, 6, 5568, 1, -30460, 0, 0x9, 5570, 512, -30419,
0x7e82, 0760, 6, 0, 4, 02400, 15, 0, 4, 1280, 4, 0,
4, 0, 0, 0, 0x8, 0, 4, 0, ',', 0, 12, 0, 4, 0, '#',
0, 020, 0, 4, 0, 30, 0, 026, 0, 0x6176, 120, 25712,
'p', 072163, 'r', 29303, 29801, 'e'
};> Apparently in 1984, a strange program won the IOCCC where main was declared as a short main[] = {...} and somehow this did stuff and printed to the screen!
You can extract a few ASCII strings from the data. As the hint says: "Can you guess what is printed? We knew you couldn't! :-)"
The ASCII strings I found were "vax", "pdp", "str", "write", and " :-)".
Main is usually a function, so when is it not? (2015) - https://news.ycombinator.com/item?id=15206198 - Sept 2017 (65 comments)
Main is usually a function. So then when is it not? - https://news.ycombinator.com/item?id=12799637 - Oct 2016 (1 comment)
Main is usually a function – when is it not? - https://news.ycombinator.com/item?id=8951283 - Jan 2015 (60 comments)
const float main[] = {-8.10373123e+22, 6.16571324e-43, 1.58918456e-40, -7.11823707e-31, 5.81398733e-42, 1.26058568e-39, 6.72382769e-36, 2.17817833e-41, 2.16139414e-29, 1.10873646e+27, 1.76400414e+14, 1.74467096e+22, -221.039566};
I'm not that good with CPUs past 16 bits, this is really out of my comfort zone heh
> Maybe in a different architecture or something?
Of course this isn't portable across CPU architectures, neither is it portable across operating systems due to at least ABI differences.
I meant like, how can you be sure a compiler would interpret them correctly and give you the exact binary value you wanted.
And by different architectures, I meant more like, if you compiled it on Intel and AMD, could the results be different? Though I guess this part of the question makes no sense now that I think more on it.
Bonus points for finding them all in the same header file, or with like names, so as to give appearance of them actually meaning something in the context of the prank.
cd /usr/include; egrep -r \#define.*[0-9]+$ . | sed 's/#define[\t ]//' | awk '{print $NF, $1}' | sort -nIt's the first time I had a legtimate excuse to whip out this technique since seeing it in an ancient obfuscated C contest entry for the PDP-11 probably a decade ago. mullender.c I think?
A conforming C compiler could reject a program that defines main as an array, but is not required to do so.
gcc doesn't complain by default, but with warning enabled it says "warning: 'main' is usually a function".
So there's the utility, if you're hardcore enough to build machine code at runtime.
If you wanted to abuse main() particularly, I guess you've got argc and argv in registers, and your hand-compiled main 'function' could maybe have some self-modifying code?
I once argued on the C standard forum that a C compiler should not know about "main". "#include <unix.h>" should contain the usual Unix declaration for "main", and "#include <windows.h>" should contain the Windows declaration, which at the time was, roughly:
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow);
It's then up to the user to define their startup function to match, with normal type checking.This gets the compiler out of handling "main" as a special case.
This was generally considered to be the right answer, but would break too much existing code.
but I agree that the fact that the standard allow for 2 different declarations of main in a language without poliformism doesn't help.
Not to start with the whole implementations are free to define extra entry points part.
The entry point is automatically generated by the compiler, it calls a few functions depending on what the program does then calls the main, I think it had to do with initializing the standard library. You can see the stub using a debugger or a disassembler.
It's possible to set the entry point point to any function name. See advanced project settings.
Now about the arguments and return type. With main the caller is responsible for pushing arguments onto the stack before the call, then popping the stack after the call. the return code is in the EAX register if I remember well.
Because of that, it doesn't matter what's the signature of the main, the invocation will work irrelevant of the arguments.
People may ask what's the point of knowing any of this? One major use case is to write executable compressors like UPX. Another use case is to make a custom entry point written in assembler.
Nevertheless, main is treated specially by the compiler for the aforementioned historical reasons, for example to not warn/error out if it does not return a value despite the type clearly telling so.
Observe:
% echo 'int main(void) { }' > foo.c; clang -c foo.c
<no output>
% echo 'int foo(void) { }' > foo.c; clang -c foo.c
foo.c:1:17: warning: non-void function does not return a value [-Wreturn-type]
int foo(void) { } ^ 1 warning generated.
As you can see, clang is happy to ignore the missing return value for main(), but not for foo().
You can pass -nostdlib to gcc to disable linking the CRT startup object (or use ld directly) and you can pass --default-script /dev/null to ld to disable the linker script.
There is no need to declare main or check arguments or return types since in C arguments are both pushed and popped by the caller and the language provides no typing guarantees and thus there is no problem in calling functions with mismatched argument or return type declarations.
$ gcc -m32 -O2 -fno-pie -fno-asynchronous-unwind-tables -fomit-frame-pointer -S -masm=intel -xc -o - -
int foo(void); int main(void) { return foo(); }
^D
.file "<stdin>"
.intel_syntax noprefix
.text
.section .text.startup,"ax",@progbits
.p2align 4
.globl main
.type main, @function
main:
push ebp
mov ebp, esp
and esp, -16
call foo
leave
ret
.size main, .-main
.ident "GCC: (GNU) 11.1.0"
.section .note.GNU-stack,"",@progbits
It doesn’t do that if you set the historical stack alignment, though (-mpreferred-stack-boundary=2), or if you name the function anything else but main (it even does a tail call). Presumably it’s trying to (somewhat) recover from the time when the GCC authors accidentally the SysV i386 ABI[1,2].[1]: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=40838 [2]: https://stackoverflow.com/a/49397524
Can anyone explain why const makes the array executable? That was the most surprising but for me
The const keyword tells C that a certain variable should never be modified by code. Doing so would be undefined behaviour. GCC is free to implement whatever guarantees for memory meant for const variables. On some versions and under certain criteria, it would place const variables into .text so any access would cause a SIGSEGV. It can also achieve the same by putting it into other write protected memory, like .rodata (which is what the newest version of gcc prefers for the code in this article, making it no longer work). Why GCC chooses one over the other and why it would change over time are hard questions.
A more effective way would be to use __attribute__s (on gcc) or #pragma directives to specify that the bytes need to be in the .text segment. However, that ruins the magic a bit.
In other words, you can write `main` as anything you want, and it might do something when presented to a C compiler, and that might do something when linked through the system linker, but it's not C code.
Doing something that's by definition incorrect and having it maybe do something somewhere sometimes, or maybe not, is not really all that impressive when you think about it.
See I would have called this hacking, which here on Hacker News is its own special kind of impressive.
If C wanted to insist that main is a function, it could. That could be in the standard, and then these clever hacks wouldn't compile.
So no, you're dead wrong about that claim. So I'll go ahead and not be impressed with your commentary.
Of course a standards compliant C compiler could give you nasal demons, or delete your root directory, so it's not the sort of thing one should push to prod on a Friday.
It's just a hack.
When I was TA'ing the CS intro class, which in my University was actually using a functional programming language (ML), I received a bunch of "working" programs from students who already knew how to program, but not with functional programming languages. They would force an imperative style into ML, which was not what the assignment was, and kind of showed that they must not have paid attention at all.
I took one CS class as well. I didn't read the syllabus carefully, and thus didn't realize that the lab was 10% of my grade, so I got a final score of 89%. I tried to argue that getting 99% of the rest of the course right should make up for the missing lab work, to no avail. This is how I ended up a chemistry major.
Anyway, I think you would get a lot out of this essay. I did at least.