8 ms·
Main is usually a function, so when is it not? (2015)
- jpegqs 5y agoCan be both, a string and a function: 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!*/
- failwhaleshark 5y agoNow make it a quine too. ;-)
- ltbarcly3 5y agoWell, I guess technically they could expel him because he had someone do part of the assignment for him.
- FranchuFranchu 5y agoWhy does he add semicolons at the end of the assembly lines?
- wtetzner 5y agoNot sure. I guess it shouldn't hurt anything though, since semicolon is the comment character.
- Taniwha 5y agodepends on the assembler, in some assemblers ';' allows you to put multiple instructions on the same line
- actually_a_dog 5y agoThey're not necessary, so I suspect some combination of reflex, consistency for consistency's sake, or, possibly that they were added automatically by his editor/IDE at some point.
- Taniwha 5y agoThey allow you to put multiple instructions on the same line - in this case you must either have ';'s or '\n's in the string - having both doesn't break stuff, I guess it's more belt and braces
- im3w1l 5y agoFloats being more mysterious and intimidating than ints I prefer 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};
- jalbertoni 5y agoGenuine question, can you be sure the conversion wouldn't introduce a wrong bit here or there? Maybe in a different architecture or something? I'm not that good with CPUs past 16 bits, this is really out of my comfort zone heh
- bombcar 5y agoI think the format for single precision and double is defined by the standard. Beyond that may be implementation dependent.
- _kst_ 5y agoThe format for floating-point is specified by the IEEE floating-point standard (or whatever it's officially called these days). C permits but does not require IEEE format. Most implementations these days use it.
- banana_giraffe 5y agoUnless I'm missing something, this code is already architecture dependent .. adding more architecture dependencies won't really hurt.
- grishka 5y agoYou only depend on the compiler to interpret these floats correctly and generate their binary representation that decodes into valid instructions. As far as the CPU executing this code is concerned, it's machine code either way. > 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.
- shadowgovt 5y agoIs there any particular utility to this trick, or is it just a neat side-effect of the linker and compiler being very permissive and treating something that most languages would call a compilation error as merely a warning?
- Jorengarenar 5y agoNeat side effect
- rm445 5y agoKen Thompson wrote a regex engine which compiled (at runtime) regexes into data structures containing executable machine code, and invoked them (from C source) by jumping into the data i.e. treating its location as a function pointer. That's what's happening here except it's the start code inserted by the linker which is jumping into main. 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?
- dhosek 5y agoI don't know that that would work since if the code is generated at runtime it would live in .data and not .text. At least for the architecture being targeted, you aren't allowed to create executable code at runtime like that (note that the original poster had to declare his main array as const to be able to have it in the .text segment.
- Sharlin 5y agoCoercing a data pointer into a function pointer is undefined behavior in standard C (they don't even need to be the same size), but at least on POSIX platforms the compiler must do the right thing because `dlsym` depends on it working. Generating and executing native code at runtime is not that special, mind; after all JIT compilers are ubiquitous these days!
- twoodfin 5y ago
- monocasa 5y agoI interestingly did this just the other day. I had a testing reason for main to consist of a single specific illegal instruction that I know the hex of anyway. It was less work for the system's Makefile to compile a .c rather than a .s file, and I knew everything I needed to make this trick work, but didn't know how to for sure disable function prologues for this arch. It'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?
- dang 5y agoSome past threads: Main is usually a function, so when is it not? (2015) - https://news.ycombinator.com/item?id=15206198 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 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 https://news.ycombinator.com/item?id=8951283 - Jan 2015 (60 comments)
- pcstl 5y agoIn Haskell, main is a value, not a function. This is sometimes overlooked because in Haskell "values" can be thought of as 0-parameter functions and functions can be used as values, so everything sort of blends together, but I find it gives some fascinating insight into how lazy programming changes things up.
- chowells 5y agoI like Haskell as much as anyone, but this is pretty non-responsive. "main is usually a function" is a gcc warning, and the whole joke is that "usually" leaves some pretty broad questions. This post answers some of them.
- hawski 5y agoTime passed. How was the assignment graded?
- labster 5y agoIf I was the TA in this class, I’d give it -5 (95 of 100) for “doesn’t compile on my 486, please write more portable code” just to screw with the student.
- Sebb767 5y agoIn my university, it would've probably received full points; reason being that people who pull such shenanigans usually don't see "hello world" as a challenge - assuming, of course, the author could explain it.
- ipython 5y agoIt's not hard to get the address of data in 32-bit addressing. You just interleave the data inside your assembly, something like the following (pseudo code I haven't done this in a while): ... 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)
- zh3 5y agoMore or less how Fortran works on PDP-11's.
- ipython 5y agoCurious to hear more!
- fasquoika 5y agoI'm guessing this is referring to the fact that early versions of Fortran stored return addresses in specific memory locations (at the end of the function definition IIRC) instead of on a call stack. This is why those versions of Fortran couldn't do recursion, because the new return address would overwrite the old one.
- zh3 5y agoIt seems weird now, but natural at time. Point a register at the data following the call, rely on the caller advancing over the argument and jumping back to the (presumed) code after the data. The stack isn't involved at all... Edit: found this on the 'net:- https://retrocomputing.stackexchange.com/questions/9328/does-the-in-code-argument-passing-conventions-used-on-pdp-11s-have-a-name https://retrocomputing.stackexchange.com/questions/9328/does...
- _kst_ 5y agoOne of the winners of the 1st International Obfuscated C Code Contest (1984) used this technique. https://www.ioccc.org/1984/mullender/mullender.c https://www.ioccc.org/1984/mullender/mullender.c https://www.ioccc.org/1984/mullender/hint.text 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' };
- alin23 5y agoThat’s already mentioned in the third paragraph but it’s nice of you to also include the bytecode in the comment. > 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!
- ianhanschen 5y agoBarely mentioned, and the author makes us all die inside when they say "Too bad it was written for a whole different architecture and compiler so there is really no easy way for me to find out what it did."
- _kst_ 5y agoAs the hint explains, it's a combination of PDP-11 and VAX machine code, set up so that either system will run its own code and ignore the foreign code. 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 " :-)".
- cptnapalm 5y ago
- dhosek 5y agoI took one CS class as an undergrad. On those occasions I showed up for class, I sat in the back row and wrote poetry. I think I only turned in one assignment as well. It was supposed to be a very simple infix calculator with 26 statically allocated variables. I thought that was boring so I ended up creating an algebraic solver that could use dynamically allocated variables of any length as long as they began with a letter and were all alphanumeric. The whole thing was done using cweb. The TA gave me partial credit saying that he couldn't understand any of what the code did (in his defense, the cweave output was 20+ pages long and I think my classmates' programs were all a couple pages of C) but that it appeared to work.
- anyfoo 5y agoDepends on the assignment, and can easily backfire. 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.
- samatman 5y agoWell I can relate to that kind of story, although I got it mostly out of my system by college. 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. http://www.marktarver.com/bipolar.html http://www.marktarver.com/bipolar.html
- deleted 5y ago[deleted]
- bregma 5y agoIn C (and C++) main is always a function with zero parameters, two parameters as described in detail, or some implementation-specific set of parameters. ANything else is undefined behaviour. 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.
- scintill76 5y agoYou could make main() a standard C function that merely calls this machine code hack function.
- bregma 5y agoYou could. But `main()` would still then be a function, no?
- samatman 5y ago> 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.
- bregma 5y agoIt's hacking, sure. Just like using a hex editor to enter machine code directly. It's not C code, though, despite what the author claims, and it's not showing some clever way to hack C code that makes the author look Klever. SO go ahead an be impressed some something that isn't what it claims to be and doesn't do what it claims to do.
- samatman 5y agoOf course it's C code. It parses, and compiles, giving undefined behavior. 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.
- turtletontine 5y agoReally cool post! Can anyone explain why const makes the array executable? That was the most surprising but for me
- dj_mc_merlin 5y agoThe real effect he's trying to cause is to move the bytes for main from a R/W memory segment (like .data) to an eXecutable one (like .text). 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.
- _kst_ 5y agoThe C standard requires main to be defined as a function, but failure to do so is not a constraint violation, so no diagnostic is required. If you define it as something else, the behavior is undefined. 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".
- deleted 5y ago[deleted]
- mclightning 5y agoTreating main as a function, failed me at an exam back in 2013. Then I asked the question on Stackoverflow, had received pretty interesting answers; https://stackoverflow.com/questions/10513633/why-can-a-java-static-method-call-a-constructor-but-not-refer-to-this https://stackoverflow.com/questions/10513633/why-can-a-java-...