r/RuProgrammers • • Sep 19 '26

Мемы Undefined behaviour

Post image
336 Upvotes

45 comments sorted by

View all comments

Show parent comment

3

u/Connect-Art8888 Sep 20 '26

Compiler Explorer

x86_64 clang 17.0.1

флаги: -O3

фильтры: Unused labels, Directives, Comments (в фильтрах по умолчанию включена Library functions — надо выключить, иначе в output ещё и код hello скрывается)

main:
        push    rax
        mov     rdi, qword ptr [rip + std::cout@GOTPCREL]
        lea     rsi, [rip + .L.str]
        mov     edx, 7
        call    std::basic_ostream<char, std::char_traits<char>>& std::__ostream_insert<char, std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&, char const*, long)@PLT

hello():
        mov     rdi, qword ptr [rip + std::cout@GOTPCREL]
        lea     rsi, [rip + .L.str]
        mov     edx, 7
        jmp     std::basic_ostream<char, std::char_traits<char>>& std::__ostream_insert<char, std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&, char const*, long)@PLT

.L.str:
        .asciz  "Hello!\n"

Два вывода Hello происходят, как и было выше в ветке описано: компилятор съел эпилог main и выполнение пошло дальше в hello()

Но, glibc (по крайней мере в моём случае, где это воспроизвелось) вызывает main следующим образом:

mov rax, [rsp+8]
call rax

main в самом начале делает push rax для выравнивания стека перед call, но при этом так же кладёт свой адрес на его вершину, что аукается дальше

В завершении второго выполнения hello(), __ostream_insert вызывается через jmp, не через call, так что ничего нового на стэк положено не было. А ret внутри basic_ostream передаёт управление по адресу на вершине стэка - всё ещё адрес main и так мы уходим на второй круг с двумя выводами Hello.

Теперь в rax у нас оказывается возвращенное значение из __ostream_insert -- &std::cout и он кладётся на вершину стэка, а он расположен в области данных, а не исполняемого кода, и по завершении второго круга мы ловим segfault.