8 ms·
Main is usually a function, so when is it not? (2015)
- dang 9y agoDiscussed at the time: https://news.ycombinator.com/item?id=8951283 https://news.ycombinator.com/item?id=8951283.
- deleted 9y ago[deleted]
- panic 9y agoOn the subject of machine code as data, there was an interesting text file released at this year's SIGBOVIK: https://www.cs.cmu.edu/~tom7/abc/paper.txt https://www.cs.cmu.edu/~tom7/abc/paper.txt (you'll need to resize your browser window to read it properly -- for an easier reading experience you can also check out the PDF and video at https://www.cs.cmu.edu/~tom7/abc/ https://www.cs.cmu.edu/~tom7/abc/).
- userbinator 9y agoThat is an amazing work of art. I only skimmed it briefly but it would make for great spare-time reading.
- poizan42 9y agoThe thing I'm wondering about with this is why ld by default creates only two LOAD segments, one r-x and one rw-. .text and .rodata are put into the former while .data is put into the latter. This is the only reason this works. So my question is why there isn't a third LOAD segment with access r-- for the read only data? If anything it would reduce the surface of where to find ROP gadgets slightly so couldn't hurt, no?
- clarry 9y agoI can't tell you why others don't do it, but OpenBSD has used a proper read-only rodata segment since long ago http://www.openbsd.org/papers/pacsec03/e/mgp00013.html http://www.openbsd.org/papers/pacsec03/e/mgp00013.html
- ajross 9y agoThat's architecture-specific. On some machines there is indeed a separate "execute-only" segment in the ELF file. But on many architectures (x86) there has historically been no hardware support for that, and for compatibility reasons the standard linker output still maintains the same scheme.
- poizan42 9y agoIt's not a lack of execute-only segment but of a read-only, no-execute segment I'm talking about. x86 has had support for that for exactly as long as it has supported no-execute, which the second LOAD segment is marked with (i.e. the one that .data goes in). 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.
- pm215 9y agoThe compatibility requirement is the result of the historic lack of x86 platform support, though: x86 used not to support r-- permissions, so it put rodata in an r-x segment, so there are likely programs in the wild which accidentally rely on that, so the linker can't now tighten the rodata permissions without breaking some existing set of programs of unknown size. Whether you think that's a good tradeoff depends on your opinion on the size of that set of programs and how heavily you weight 'avoid breaking code that used to work' against 'tighten permissions for security reasons'. 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.
- poizan42 9y ago> x86 used not to support r-- permissions, 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.html http://www.cl.cam.ac.uk/~srk31/blog/devel/custom-elf-phdrs.h...
- gumby 9y agoThe old a.out format used by BSD had a third, virtual section for uninitialized data. It was allocated at runtime but of course took no space in the object file.
- twoodfin 9y agoDo you mean something different from ELF's bss segment?
- dfox 9y agoIt is different only in the sense that in ELF it is real segment (that even has sane ELF segment header when loaded), but in MZ, most flavours of a.out and some flavours of COFF it is just a single word quantity somewhere in the image header.
- poizan42 9y agoIt is not really there in the ELF. It exists as a section. But sections are not used at runtime, they only exists for tools like debuggers etc. You can strip them from the binary with e.g. the sstrip[0] tool and it still works fine. 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 http://www.muppetlabs.com/~breadbox/software/elfkickers.html
- smeenai 9y agoLLD (the new linker from the LLVM project) actually does exactly this; by default, it creates a separate read-only segment, and you have to pass --no-rosegment explicitly if you don't want this. 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.)
- glandium 9y agoOne part of the reason it's still this way is that read-only data is actually not read-only. 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.
- louithethrid 9y agoah, this reminded me of some embeded oses where driver came as precompiled arrayblobs, that are included with ifdef guards set in some central config tool
- protomyth 9y ago> Since I knew the target system is going to be 64bit Linux 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.
- TazeTSchnitzel 9y agoIt's weird how compilers complain if main() is void, despite it not having to return anything if it's int.
- user5994461 9y agoYou have to return something. If you don't, the exit code is the value that happened to be in memory at this point.
- TazeTSchnitzel 9y agoNormally, yes. But main() is special and implicitly returns 0, for whatever reason.
- user5994461 9y agoAbsolutely not. You can look at a crashing programs and see the return code is a random value. You can also make a program that doesn't return anything and see that it's also a random value, though some compilers on some platforms might initialize that to zero.
- TazeTSchnitzel 9y agoNo, it's literally special-cased in the C standard.
- monochromatic 9y agoIs it undefined behavior to have main be anything other than a function that returns int?
- delinka 9y agoUB comes with high-level languages. "main" is not specified in the high-level language, but by the OS. You write some machine code, give it the name "main" and the OS jumps to that location and starts executing. One should adhere to the "C calling convention" (managing the stack correctly) if that code is expected to behave within the system. But "main is not a function" does not produce undefined behavior.
- clarry 9y ago> "main" is not specified in the high-level language, but by the OS. You write some machine code, give it the name "main" and the OS jumps to that location 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.
- monochromatic 9y agoIf you're feeding source code to a C compiler, it makes sense to ask whether or not it contains UB.
- clarry 9y agoYes, in hosted environment it is UB, unless the implementation specifies otherwise. Relevant quotes from N1256: 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).
- deleted 9y ago[deleted]
- gumby 9y agoThe program entry point is actually _start (which does some setup and later calls main()) so for even more extreme TA befuddlement, write a program that doesn't even call main()!
- mmjaa 9y agoint noMain() __attribute__ (constructor); int noMain() { printf("Goodbye, main()"); } // &etc.
- dfox 9y agoThis actually would not link, because _start() or something it calls into (depending on implementation of CRT on given platform) would contain unresolved reference to main. (and goven the fact that all this CRT startup code is usually one .o, you cannot just patch out the part that calls main(), you have to replace it completely) 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)
- ChuckMcM 9y agoHere is a link to the several times this has appeared here: https://news.ycombinator.com/from?site=jroweboy.github.io https://news.ycombinator.com/from?site=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 :-)
- microcolonel 9y agoI like to start with the assembler, then just tell people that high level languages are fancy assemblers. Some of them restrict you to using the designer's favourite macros, some of them allow you to build on highly general tools at approximately the lowest level, but ultimately all computer programming languages are fancy macro assembler syntaxes.
- tomjakubowski 9y agoThis is an OK mental model until a wholly compliant C or C++ compiler blows it all to smithereens with an optimization exploiting UB. 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.
- microtherion 9y ago> "What if you cast a string pointer to a function and called it! Huh?" and I explain that is perfectly legal C 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.
- kaushiks 9y agoThe CLR uses a similar technique to take over control of execution of a managed EXE - http://srevas.net/notes/2007/12/25/mscoree/ http://srevas.net/notes/2007/12/25/mscoree/
- Deestan 9y agoUnless the assignment in question specifies architecture, the TA could (should? I know I would) run it on a 32 bit Windows environment and mark it down for not working.
- userbinator 9y agoNot surprisingly, there are tricks one can use to figure out what the environment is, using the same binary; start here: https://stackoverflow.com/questions/38063529/x86-32-x86-64-polyglot-machine-code-fragment-that-detects-64bit-mode-at-run-ti 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-multiple-architectures 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.
- userbinator 9y agoWe joked with him about how he needs to make a program that works, but the grading TAs wouldn’t be able to figure out how it works. 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. ;-)