1. Си / Говнокод #25399

    0

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    25. 25
    26. 26
    27. 27
    28. 28
    29. 29
    30. 30
    31. 31
    32. 32
    33. 33
    34. 34
    35. 35
    36. 36
    37. 37
    38. 38
    39. 39
    40. 40
    41. 41
    42. 42
    43. 43
    // https://github.com/ghc/ghc/blob/e204431e5a5e2fd16da52b04bda2798f16c51344/rts/Interpreter.c#L1184
    
            case bci_PUSH8: {
                int off = BCO_NEXT;
                Sp_subB(1);
                *(StgWord8*)Sp = *(StgWord8*)(Sp_plusB(off+1));
                goto nextInsn;
            }
    
            case bci_PUSH16: {
                int off = BCO_NEXT;
                Sp_subB(2);
                *(StgWord16*)Sp = *(StgWord16*)(Sp_plusB(off+2));
                goto nextInsn;
            }
    
            case bci_PUSH32: {
                int off = BCO_NEXT;
                Sp_subB(4);
                *(StgWord32*)Sp = *(StgWord32*)(Sp_plusB(off+4));
                goto nextInsn;
            }
    
            case bci_PUSH8_W: {
                int off = BCO_NEXT;
                *(StgWord*)(Sp_minusW(1)) = *(StgWord8*)(Sp_plusB(off));
                Sp_subW(1);
                goto nextInsn;
            }
    
            case bci_PUSH16_W: {
                int off = BCO_NEXT;
                *(StgWord*)(Sp_minusW(1)) = *(StgWord16*)(Sp_plusB(off));
                Sp_subW(1);
                goto nextInsn;
            }
    
            case bci_PUSH32_W: {
                int off = BCO_NEXT;
                *(StgWord*)(Sp_minusW(1)) = *(StgWord32*)(Sp_plusB(off));
                Sp_subW(1);
                goto nextInsn;
            }

    https://ghc.haskell.org/trac/ghc/wiki/Commentary/Rts/Interpreter

    > The linker lives in rts/Linker.c and is responsible for handling runtime loading of code into a Haskell process. This is something of a big blob of unpleasant code, and see DynamicGhcPrograms for information about efforts to reduce our dependence on this linker.

    Итак, тут у нас стековая машина из хачкеля, которая вродекак отвечает за динамическую загрузку какого-то говна.

    Запостил: j123123, 22 Февраля 2019

    Комментарии (101) RSS

    • Так блядь, хуйня типа Sp_subB(1); Sp_subB(2); и проч - она значит стекпоинтер декрементирует

      Что это за хуйня "*(StgWord8*)Sp" по-вашему? Типы StgWord8 StgWord16 StgWord32 определены в
      https://github.com/ghc/ghc/blob/1c2c2d3dfd4c36884b22163872feb87122b4528d/includes/stg/Types.h#L64


      Что мы видим? Обычные синонимы к херне из uint8_t int8_t uint16_t int16_t и так далее.

      А у нас тут есть херня такая, что для bci_PUSH8 мы делаем Sp_subB(1) т.е. уменьшаем стек на 1, потом кастуем: *(StgWord8*)Sp = *(StgWord8*)(Sp_plusB(off+1));

      Ну в общем какую-то херь в стек записываем из какого-то говна, но при этом стекпоинтер уменьшаем на 1. Т.е. стек у нас нихера не выровнен. И если потом будет хуйня bci_PUSH16: *(StgWord16*)Sp = *(StgWord16*)(Sp_plusB(off+2));

      Которая кастанет невыровненную хуйню в *uint16_t и потом разыменует даже. А это UB
      Ответить
      • https://github.com/ghc/ghc/blob/e204431e5a5e2fd16da52b04bda2798f16c51344/rts/Interpreter.c#L134


        #define Sp_plusB(n)  ((void *)(((StgWord8*)Sp) + (n)))
        #define Sp_minusB(n) ((void *)(((StgWord8*)Sp) - (n)))
        
        #define Sp_plusW(n)  (Sp_plusB((n) * sizeof(W_)))
        #define Sp_minusW(n) (Sp_minusB((n) * sizeof(W_)))
        
        #define Sp_addB(n)   (Sp = Sp_plusB(n))
        #define Sp_subB(n)   (Sp = Sp_minusB(n))
        #define Sp_addW(n)   (Sp = Sp_plusW(n))
        #define Sp_subW(n)   (Sp = Sp_minusW(n))
        
        #define SpW(n)       (*(StgWord*)(Sp_plusW(n)))
        #define SpB(n) (*(StgWord*)(Sp_plusB(n)))


        И кто вас блядь учил писать так макросы? Макросы принято писать КАПСОМ!
        Ответить
      • В S" Forth" всё в стеке занимает как минимум одну ячейку, даже символ. Именно поэтому я за S" Forth"
        Ответить
    • У нас было два пакетика GHC Core, семьдесят пять ампул C--, 5 пакетиков Spineless Tagless G-Machine или STG, солонка, наполовину наполненная LLVM, и целое море разноцветных бекендов, интерпретаторов, а так же пинта стекового языка, и 12 пузырьков ghci. Не то, чтобы всё это было категорически необходимо в языке программирования, но если уж начал обеспечивать студентов темами для masters theses, то к делу надо подходить серьёзно. Единственное, что вызывало у меня опасение — это стековые языки. Ничто в мире не бывает более беспомощным, безответственным и порочным, чем шитый код. Я знал, что рано или поздно мы перейдем и на эту дрянь.
      Ответить
      • показать все, что скрытоvanished
        Ответить
        • А я за «Ассемблер», потому что в нём нет ни кастов, ни UB. Можно просто изменять указатель на вершину стека и течь.
          Ответить
          • показать все, что скрытоvanished
            Ответить
            • Регистры иногда заканчиваются, надо где-то сохранять их содержимое. Да и в подпрограммах содержимое используемых регистров тоже надобно схоронить, а в конце восстановить. Так что без стека только совсем примитивные программы обходились.
              Ответить
              • > подпрограммах
                На фортране и бейсике отлично обходились без этих ваших автоматических переменных.
                Ответить
              • Недавно смотрел документацию по «Итаниуму» (помним, скорбим). У него была фишка, именуемая ротацией регистров. При упоминании в программе нумерных регистров PR16...PR63, GR32...GR127 и FR32...FR127 реальное обращение происходит не к регистру с указанным номером (N), а к регистру с номером N+RRB (по модулю количества регистров), где RRB –— специальный регистр, значение которого увеличивается на единицу после инструкции, передающей управление на начало цикла. Т. е. если на первой итерации цикла RRB=0, то при упоминании PR16 данные читаются из PR16, на второй итерации цикла RRB=1 и при упоминании PR16 данные читаются из PR17, на третьей итерации RRB=2 и при упоминании PR16 данные читаются из PR18 и так далее. Аппаратный кольцевой буфер!

                В «x86» вращаемых банков регистров нет, именно поэтому я за «x86».
                Ответить
                • > В «x86» вращаемых банков регистров нет, именно поэтому я за «x86».

                  Зато в x86 можно циклически сдвигать какой-нибудь регистр, и брать от него кусочек поменьше. В x86-64 например 64-битный регистр rax и его 8-битные кусочки ah и al.
                  Ответить
              • показать все, что скрытоvanished
                Ответить
                • И это будет ёбаный пихдец из сотни глобалок, даже для временных переменных.

                  > в асембреле у тебя не было никакой кучи
                  В виндах и линупсах есть. И ничто не машало использовать билиботеку/написать свой для ДОЧ'а.
                  Ответить
          • > А я за «Ассемблер», потому что в нём нет ни кастов, ни UB.

            В ассемблере есть (точнее, могут быть) недокументированные инструкции, которые хуй знает что делают. Вполне можно считать это UB-ом. А еще, если взять процессор версии X, в котором инструкция A (т.е. некоторая некорректная последовательность байт, которая не может быть декодирована как инструкция) не поддерживается (при попытке ее исполнения будет брошено исключение), то в каком-то более новом процессоре версии X+1 могут додуматься в такой способ кодировать некоторые инструкции, и тогда это уже не будет приводить к броску исключения.
            Ответить
            • Но ведь это не ассемблер, а машинные коды. Разве ассемблер (программа) может скомпилировать какой-то исходный код в бинарник с невалидными инструкциями (если погромист не додумался напрямую байтики писать через db, конечно)?
              Ответить
              • Да, например если писать под Intel 80386, но использовать более свежие инструкции, доступные только начиная с 80486.
                Например
                https://ru.wikipedia.org/wiki/80486#Математическая_модель_и_набор_инструкций
                Ответить
                • показать все, что скрытоvanished
                  Ответить
                • Ну нет. Это implementation-defined - я могу предсказать результат прочитав даташит на конкретный проц.

                  З.Ы. Хотя... вызов недокументированной инструкции на старом проце всё-таки UB. Хуй знает что она там делала. Вроде не гарантируется, что все неизвестные инструкции должны кидать исключение?
                  Ответить
                  • > Ну нет. Это implementation-defined - я могу предсказать результат прочитав даташит на конкретный проц.

                    В даташите может быть ничего не написано по поводу этой инструкции. Может даже так оказаться, что при попытке выполнения этой инструкции, процессор сгорает нахрен.
                    Ответить
                    • Да, согласен.
                      Ответить
                    • показать все, что скрытоvanished
                      Ответить
                      • Можно пруф из спеки, что каждая не описанная в ней инструкция кидает это исключение?

                        Вроде на интеле находили недокументированные опкоды которые что-то делают и не кидают.
                        Ответить
                        • Вот тут например http://www.z80.info/zip/z80-documented.pdf описаны недокументированные опкоды к z80, которые что-то там делают, не кидая при этом исключений.
                          Ответить
                        • показать все, что скрытоvanished
                          Ответить
                        • salc
                          Ответить
                          • Он документирован, оказывается.

                            The opcodes D6 and F1 are undefined opcodes reserved by the Intel 64 and IA-32 architectures.
                            Ответить
                            • показать все, что скрытоvanished
                              Ответить
                              • Я думаю, что они больше так не делают.

                                Но надо ещё поискать как обстоят дела с недокументированными сочетаниями префиксов.
                                Ответить
                                • Other use of branch hint prefixes and/or other undefined opcodes with Intel 64 or IA-32 instructions is reserved; such use may cause unpredictable behavior.

                                  Other use of the 66H prefix is reserved; such use may cause unpredictable behavior.

                                  The address-size override prefix (67H) allows programs to switch between 16- and 32-bit addressing. <...> Using this prefix and/or other undefined opcodes when operands for the instruction do not reside in memory is reserved; such use may cause unpredictable behavior.
                                  Ответить
                                  • показать все, что скрытоvanished
                                    Ответить
                                    • На «x86» можно выполнить JMP в середину инструкции или вообще в секцию данных (если нет защиты от выполнения). Именно поэтому я за «x86».
                                      Ответить
                                      • Когда-то я тоже кодил на ассёмблере, но потом стал рекитиром.
                                        Ответить
                                        • Как ты мог кодить на ассемблере? Ведь они вымерли 65 млн. лет назад!
                                          Ответить
                                          • Ххех. У меня ещё и писюк был, с пузатым таким, голубым экраном и жужжалка для плёнки.
                                            Ответить
                                    • > изобрел процессор который при использовании недокументированного префикса подымает напряжение на VRM до 12 вольт и самоуничтожается

                                      Интел кажется что-то такое тоже изобрел
                                      https://www.opennet.ru/opennews/art.shtml?num=52011

                                      > Суть метода заключается в создании условий для возникновения непредвиденных искажений данных при вычислениях в SGX, от которых не защищает применение шифрования и аутентификации памяти в анклаве. Для внесения искажений, оказалось, можно использовать штатные программные интерфейсы для управления частотой и напряжением, обычно применяемые для снижения энергопотребления во время простоя системы и активации максимальной производительности при выполнении интенсивных работ. Характеристики частоты и напряжения охватывают весь чип, в том числе влияют на выполнение вычислений в изолированном анклаве.

                                      > Изменяя напряжение, можно добиться возникновения условий, при которых заряда оказывается недостаточно для регенерации ячейки памяти внутри CPU, и её значение меняется. Ключевым отличием от атаки RowHammer является то, что RowHammer позволяет изменить содержимое отдельных битов в памяти DRAM, путём цикличного чтения данных из соседних ячеек, в то время как Plundervolt позволяет добиться изменения битов внутри CPU, когда данные уже загружены из памяти для выполнения вычислений. Подобная особенность позволяет обойти применяемые в SGX механизмы контроля целостности и шифрования данных в памяти, так как значения в памяти остаются корректными, но могут исказиться при операциях с ними до того, как результат будет записан в память.
                                      Ответить
                                      • Интересно, можно ли там что-то так зарегулировать, чтоб спалить процессор нахрен?
                                        Ответить
                                    • >изобрел процессор который при использовании недокументированного префикса подымает напряжение на VRM до 12 вольт и самоуничтожается

                                      Новая уязвимость у Интел процессоров позволяет управлять напряжением на камне +/- 0.5В, что легко может привести к его выходу из строя
                                      https://www.linux.org.ru/forum/talks/15845897
                                      Компания «Intel» пиздит идеи с говнокода.
                                      Ответить
                          • salsa
                            Ответить
                    • Программы с «классическим» — сишко-крестовым — UB использовать нельзя: их поведение не определено где угодно (оно может «правильно» работать на каких-то платформах, но рассчитывать на это категорически нельзя).
                      Чтобы поведение какой-то конструкции было implementation-defined, оно должно быть задокументировано для всех платформ:
                      implementation-defined behavior
                      behavior, for a well-formed program construct and correct data, that depends on the implementation and
                      that each implementation documents

                      §3.12, [defns.impl.defined]

                      В результате имеем, что неподдерживаемые инструкции ассемблера — это и не UB, и не ID: с одной стороны, на платформах без поддержки данной инструкции поведение в принципе не определено (т. е. уже не ID), а с другой — на «хороших» платформах — точно задокументировано (не UB).
                      Ответить
                      • 22.15 Undefined opcodes
                        All new instructions defined for IA-32 processors use binary encodings that were reserved on earlier-generation processors. Attempting to execute a reserved opcode always results in an invalid-opcode (#UD) exception being generated.

                        6.15 Exception and interrupt reference
                        Interrupt 6 - Invalid Opcode Exception (#UD)
                        <...> The opcodes D6 and F1 are undefined opcodes reserved by the Intel 64 and IA-32 architectures. These opcodes, even through undefined, do not generate an invalid opcode exception.

                        Интел сам себе противоречит... Все опкоды равны, но некоторые равнее других.
                        Ответить
                      • https://habr.com/ru/post/471020/ а еще в процессорах бывают баги
                        Ответить
          • В асмемблере есть касты: byte, word, dworf, qword.
            Ответить
      • Стековые языки не обязательно реализуются в шитом коде, например S" SP-FORTH" компилицца в нативный код, а 'RETRO_FORTH в байткод, просто шитый код легче декомпилить.
        Ответить
        • Ещё доебись что и регистровые ВМ могут быть на шитом коде. Я писатель, я так вижу.
          Ответить
        • показать все, что скрытоvanished
          Ответить
          • Давай. Чур я зашитого кода.

            Шитый код компактный, быстрый, а его интерпретатор реализуется парой инструкций. Вот минимальный адресный интерпретатор прямого шитого кода с примерами подпрограмм:
            ; esi -- указатель инструкций
            ; ebp -- стек развраьов
            ; esp -- стек банных
            
            macro next { ; сам адресный интерпретатор, этим макросом должны заканчиватся низкоуровневые определения
                lodsd
                jmp eax
            }
            
            macro rpush x { ; запушить x в стек развратов
                sub ebp, 4
                mov dword [ebp], x
            }
            macro rpop x { ; попнуть из стека развратов в x
                mov dword x, [ebp]
                add ebp, 4
            }
            
            ; примеры низкоуровневых определений:
            false:
                push 0
                next
            
            state:
                push var_state
                next
            
            var_state dd ?
            
            store_: ; x addr !
                pop ebx
                pop dword [ebx]
                next
            
            forth_call: ; помещается в начале "высокоуровневых" определений
                rpush esi
                pop esi
                next
            
            exit: ; возврат в вызывающую подпрограмму
                rpop esi
                next
            
            left_bracket: ; пример "высокоуровневого" определения
                call forth_call
                dd false, state, store_, exit

            А в косвенном шитом коде в высокоуровневых определениях только адреса, и они могут хранится в секции с данными, а само ядро маленькое и может целиком поместиться в кешу. А вот пример адресного интерпретатора косвенного шитого кода, он не сложнее и не больше чем у прямого:
            macro next {
                lodsd
                jmp [eax]
            }
            
            forth_call: ; адресс помещается в начале "высокоуровневых" определений
                rpush esi
                mov esi, eax
                add esi, 4
                next
            
            ; пример низкоуровневого определения
            exit dd @f
            @@:
                rpop esi
                next
            
            ; пример "высокоуровневого" определения:
            left_bracket dd forth_call, false, state_, store, exit
            А в подпрограмном шитом коде адресный интерпретатор не нужен, программа состоит из цепочки call'ов, и не сложно перейти от него к обычному нативному коду.

            Теперь ваш ход.
            Ответить
            • показать все, что скрытоvanished
              Ответить
              • Хуёвый ты оппонент, тогда я пока временно за баткод:

                В байкоде только КОП занимает один байт, но у него ещё могут быть аргументы, как в твоём примере у push (и в шитом коде тоже могут быть оргументы). Сам код получается компактный, но для его декодирования нужна как минимум одна таблица как минимум на 256 адресов (можно и меньше, но придётся добавлять условия в интерпретаторе байткола), вот примитивный интерпретатор бацкода:
                ; esi -- укахатель инструкций
                ; ebx -- адрес таблицы
                ; esp -- стек банных
                ; ebp -- стек развратов
                
                next equ jmp next_
                
                next_:
                    xor eax, eax
                    lodsb
                    jmp dword [ebx + eax * 4] ; у джумпа же так можно?
                
                ; пример реализации самих команд:
                call_:
                    rpush esi
                    pop esi
                    next
                
                exit:
                    rpop esi
                    next
                
                push_:
                    lodsd
                    push eax
                    next
                
                add_:
                    pop eax
                    add [esp], eax
                    next
                
                ; пример пожпрограссы в бакоде (PUSH_, ADD_, EXIT это сами байткоды
                my_sub:
                    db PUSH_
                    dd 2
                    db PUSH_
                    dd 2
                    db ADD_, EXIT
                Как видно интерпретатор байткода сложнее чем у прямого или косвенного шитого кода, ему требуется дополнительная память для таблицы. Но его главное преимущество в том, что сам сконпелированный ьайткод лучше переносим чем шитый код.

                У меня усё.
                Ответить
                • Интересно, а что быстрее movzx + inc или xor + lodsb?
                  Ответить
                • показать все, что скрытоvanished
                  Ответить
                  • Он проще компиляции. И не требует RWX памяти для генерации на лету. Ебашишь адреса функций в массив да и всё.
                    Ответить
                    • А ещё шитый код легко декомпилировать, почти один в один получается ( так, кстати обычно работает ' SEE в S" Forth" )
                      Ответить
                    • Кстати, только косвенный не требует RWX памяти, в остальных видах присутствуют машкоды.
                      Ответить
                  • Косвеный шитый код можно переносить, но это сложнее чем переносить баткод, надо генерить объектный файл, а потом с ним линковатся. А форт язык простой и компилица быстро, в нём обычно всё в исходниках хранится/переносица и объектные файлы не нужны.
                    Ответить
                    • показать все, что скрытоvanished
                      Ответить
                      • Там обычно всё в исходниках, даже части самой системы могут в исходниках хранится.
                        Если сильно хочеца генерь образ для gforth или просто бинарник. Вот только деконпелируется он просто, надо брать систему которая умеет в нативный код и оптимизации (типа SP-FORTH).
                        Ответить
                  • А ещё на декодирование одной инструкции шитого кода нужно меньше операций чем на один байткод.
                    Ответить
                  • Вот кстати годная статья по шитому коду:
                    http://www.complang.tuwien.ac.at/forth/threaded-code.html
                    там есть примеры переносимых реализаций интерпретаторов, по ним кстати видно какая сишка убогая при своей-то низкоуровневости.

                    А вот бенчмарк разных техник интерпретации шитого кода:
                    http://www.complang.tuwien.ac.at/forth/threading/Во многих случаях обычно быстрее прямой. Баткода там, к сожалению нет, но наверное за него можно считать switch threading.
                    Ответить
                • показать все, что скрытоvanished
                  Ответить
                  • > как шитокод может быть переносимым.
                    Токены можно легко переносить, а чтоб перенести косвенный шиткод надо генерить объектные файлы, и при загрузке просто подправить нужные адреса. Из форт-систем я знаю, что только GForth умеет делать переносимые образы:
                    http://www.complang.tuwien.ac.at/forth/gforth/Docs-html/Fully-Relocatable-Image-Files.html#Fully-Relocatable-Image-Files
                    На счёт других языков конпилирующихся в шиткоды я не знаю.

                    Кстати, прямой шиткод тоже можно переносить как косвенный, надо просто в начале всех определений ещё вставить платформо-специфичный кусочек.

                    > генерится чисто для скорости, а переносица все равно сырцы?
                    Тип того, в стандарте нет ничего про перенос сконпелированых программ, в каждой системе может быть по своему, а миожет и вообще не быть. А ещё шиткод может выигрывать по размеру у нативного, в какои-нибудь зачуханом контроллере это может оч важно.

                    > compile once, run everywhere не полу?
                    Token threading
                    Ответить
                • показать все, что скрытоvanished
                  Ответить
                  • разговор действительно интересный, сразу видно, что нидлес умный
                    Ответить
            • lodsd это, конечно, быстро и коротко, но есть и минус: пропадает возможность юзать eax для вершины стека.
              Ответить

    Добавить комментарий