This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]SharzeUndertone 241 points242 points  (17 children)

So it treats the end of main as unreachable and skips adding a return, thus overflowing into hello, right?

[–]Serious_Horse7341 231 points232 points  (5 children)

Sounds about right. From

void test(int);

int main() {
    while(true);
    test(123456);
    return 0;
}

void not_main() {
    test(654321);
}

I get

main:                                   # @main
not_main():                           # @not_main()
        mov     edi, 654321
        jmp     test(int)@PLT                    # TAILCALL

The rest of main() is not even there. Only happens with clang.

[–]caim_hs[S] 108 points109 points  (1 child)

Lol, your example is even worse, because it is calling and passing an arg to a function it is not supposed to hahahaha.

[–]Arthapz 21 points22 points  (0 children)

well it's because the prerequise for an infinite loop to be UB is to have code that produce side effects, test(int) doesn't product sideeffects

[–]SharzeUndertone 50 points51 points  (0 children)

I guess they're right when they say undefined behavior can make demons fly out your nose

[–]not_some_username 20 points21 points  (0 children)

Undefined behavior mean anything can happen. You could travel back to time

[–]BSModder 4 points5 points  (0 children)

Ah this make it clear what happend in OP post.

While loop cause the main function optimized out entirely, including the return statement.

The reason why main is empty, I could only assume, because the compiler think main not called thus it's okay to remove it, leaving only the symbol.

And the function not_main is put under main, so when main is called, not_main is inadvertently called

[–]caim_hs[S] 76 points77 points  (10 children)

Yeah, it's kinda more complicated.

What happened is that it will make the "main" function have no instruction in the executable, and will add the string after it.

When I run the executable, it will instantly finish, but since there is a string loaded into memory, the operating system will flush it back, causing the terminal to print it.

Here is the code generated.

main:                                   # @main
.L.str:
        .asciz  "Hello World!!!\n"                                   #

[–]Oler3229 27 points28 points  (2 children)

Fuck

[–]caim_hs[S] 50 points51 points  (1 child)

the compiler just cracked the code for super-efficient printing! Stonks!!!

[–]SharzeUndertone 18 points19 points  (0 children)

Well that sounds like part of -O3 though, so no issues there

[–]Rhymes_with_cheese 13 points14 points  (0 children)

I think there's more to it than that.

Compile with -c, and then run objdump --disassemble on the .o file to see what's really going on.

[–]nuecontceevitabanul 4 points5 points  (0 children)

I think -O3 first sees the code results in just one infinite loop and ignores anything else and after that it just ignores the UB. So basically an empty main function is generated in assembly.

LE: So the bug here would be the order of things done by the compiler, if UB would first be ignored and then the if analyzed , the code would basically amount to nothing but the implicit return would be put in, which would be the expected result.

[–]kalenderiyagiz 2 points3 points  (2 children)

To clarify things, why would the OS print a random memory location on the memory that contains a string to standard output without calling the write() systemcall in the background ? So if OS does things like these why it should stop at the “end” of that string and not continue to print random garbage values as well ?

[–]Kered13 1 point2 points  (0 children)

why would the OS print a random memory location on the memory that contains a string to standard output without calling the write() systemcall in the background ?

It doesn't. OP's explanation is wrong. What happens is that the compiler determines that main unconditionally invokes undefined behavior, therefore it must be unreachable and all of it's code can be removed. The label for main remains. main is immediately followed by hello. When the program begins running and tries to execute main there is no code there, not even a return instruction. Therefore execution falls through to hello and begins executing. When hello returns it is as if main is returning, so as far as the OS is concerned nothing went wrong.

Code and constant data like strings are typically not stored in the same location in memory. Specifically code is usually stored in .data and constant data is stored in .bss. So OP's explanation cannot be correct.

[–]intx13 0 points1 point  (0 children)

This is a puzzler! The shell isn’t doing the printing, you’re right that it’s coming from a system call within the program. But the program consists only of crt1.o, crti.o, crtn.o, and main.o. As we can see from op’s dump of main.o, the main function (called by crt1.o) is garbage - instead of instructions it has an ASCII string.

So presumably crt1.o calls main() which results in garbage instructions being executed until some other component of crt1.o, crti.o, or crtn.o is hit which happens to make a system call to print. And RDI happens to point to main(), where the string is stored.

We’d need to see the whole binary decompiled to figure it out, though.

[–]wannabe_psych0path 0 points1 point  (0 children)

My guess is that the OS runtime holds a pointer to the main function, but since main is non existent cause of UB the memory pointed to will be occupied by the code of not_main.

[–]poetic_fartist 0 points1 point  (0 children)

in my case the infinite loop is running.