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

    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
    void trampoline(void (*func)(void), bool flag)
    {
        if (flag)
            func();
        else
            trampoline(func, true);
    }
    
    int 
    main(int argc, char *argv[])
    {
        /*... */
        trampoline(set_aes_keys, false);
    }

    Зачем так? ЯННП.

    Запостил: codemonkey, 14 Июня 2018

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

    • Переведи на "PHP".
      Ответить
      • function trampoline($func, $flag)
        {
            if ($flag)
                $func();
            else
                trampoline($func, true);
        }
        
            /*... */
            trampoline("set_aes_keys", false);
        Ответить
        • <?php
          
          function trampoline($func, $flag)
          {
              if ($flag)
                  $func();
              else
                  trampoline($func, true);
          }
          
          /*... */
          trampoline("set_aes_keys", false);
          trampoline("set_aes_keys", true);
          
          function set_aes_keys()
          {
              var_dump(sizeof(debug_backtrace()));
          }
          Ответить
    • Защита от инлайна какая-нибудь?

      З.Ы. А у этого трамплина только одна реализация? Или есть какая-то другая под #ifdef'ом?
      Ответить
      • Да, очень похоже, что условие вставили, чтобы оптимизатор сразу не сократил вызовы функций. А вызываем с аргументом false, чтобы ещё усложнить работу оптимизатора (в надежде, что так много шагов он не делает, он ведь не будет трассировать всю программу).

        Остаётся открытым вопрос, для чего нам нужен этот трамплин, почему напрямую нельзя. Вызываемая функция использует какие-нибудь "грязные" методы типа вычисления адреса стека?
        Ответить
        • Дождёмся перевода на "PHP", а там глядишь и прояснится
          Ответить
        • >Вызываемая функция использует какие-нибудь "грязные" методы типа вычисления адреса стека?

          Там хуйня типа:
          char buf[SOME_BYTES] = { 0 };
          int fd = open("/dev/urandom", O_RDONLY);
          read(fd, buf, SOME_BYTES);
          close(fd);
          
          for (i = 0; i < SOME_BYTES; i++)
              key[i] ^= buf[i];
          
          fwrite(some_faggot_file, 1, SOME_BYTES, buf);


          Смысл трамплина остаётся неясным.
          Ответить
        • А я подумал, что это что-то типа Meltdown или защиты от него.
          Ответить
          • как это может помочь защишщиться от Meltdown?

            Meltdown это когда ты тренишь предиктор чтобы он выполнил левый код, который считает данные в кеш, и потом замеряешь скорость чтения с кеша
            Ответить
      • ХЗ. Кресты-аналог с г++ -О2 выдают:

        func_trampoline(poo_in_loo);
         33b:   be 00 00 00 00          mov    $0x0,%esi
         340:   bf 00 00 00 00          mov    $0x0,%edi
                                341: R_X86_64_32        _Z10poo_in_loo2v
         345:   e8 00 00 00 00          callq  34a <_Z13some_other_crapv+0x123>
                                346: R_X86_64_PC32      _Z15func_trampolinePFvvEb-0x4
        }
         34a:   c9                      leaveq


        Типа кококо-защита от реверсинга, но похоже что хуита.

        >З.Ы. А у этого трамплина только одна реализация? Или есть какая-то другая под #ifdef'ом?
        Только одна.

        Тащемта, похоже что ЭТО надо выжечь калёным железом.
        Ответить
        • Тут коконпелятор убрал ветвление, но сохранил вызов. А если бы ветвления вообще не было, он мог бы заменить вызов инлайном.
          Ответить
          • Собрал с О3:
            0000000000000010 <function_trampoline>:
              10:   ff e7                   jmpq   *%rdi

            Ололо
            Ответить
            • Так ведь по глубине стека можно вычислить, с каким ключом оптимизации был вызван компилятор.
              Ответить
          • чтобы компилятор не инлайнил функцию есть noinline и аналоги
            Ответить
            • Зачем может понадобиться запрет инлайна функции?
              Ответить
              • Бинарник сильно пухнет в размере.

                ps что с гк творится? почему 500е постоянно?
                неужели задрочили самопальными приблудами и бесконечными стоками?
                Ответить
                • > что с гк творится
                  Сам токошто гневный комент пейсал, так ГКшный эксепшен его скушал.
                  Ответить
                • Пиздец, это сколько нужно написать вызовов функции, чтобы бинарник заметно распух?
                  Ответить
                • Пиздец, это сколько нужно написать вызовов функции, чтобы бинарник заметно распух?
                  Ответить
                  • Нужно иметь две версии программы: без инлайна (оптимизирована по размеру текста) и с и инлайном (оптимизирована по скорости).

                    Так учил Фредерик Брукс в 1963 г.
                    Ответить
                    • Тем не менее, это не ответ на мой вопрос
                      Ответить
        • Простите мне мою необразованность, но если там есть leave то это не значит-ли что где-то сверху должен быть enter?
          Ответить
          • Вместо enter может лежать пара инструкций:
            push ebp
            mov ebp, esp


            leave в свою очередь является аналогом такого кода:
            mov esp, ebp
            pop ebp


            Есть даже срачи на тему, что работает быстрее. Это примерно как inc eax и add eax, 1 или lea eax, [eax+1].
            Ответить
            • Так enter сохранял frame pointer и всё?

              Вангую что в темные века между 386 (когда стало можно адресоваться по sp) и, примерно, 2006 годом, им никто не пользовался ибо frame pointer omition
              Ответить
              • Порядка не было. В одной и той же библиотеки у половины функций могло быть frame pointer omition, у другой половины всё же копия sp сохранялась в bp (ну чтобы внутри функции можно было невозбранно использовать push/pop, не теряя указатель на локальные переменные и на аргументы функции). Frame pointer omition было популярно у функций без локальных переменных, принимающих аргументы через регистры, а не через стек, и у совсем коротеньких функций (которые свою работу делегировали другим функциям, типа как трамплин из этого ГК).

                Причём компилятор мог генерировать непарные enter/leave. Легко могло быть push bp; mov bp, sp + leave или enter + pop bp (ага, mov sp, bp компилятор мог пропустить, если был "уверен", что никто не портит стек).
                Ответить
                • > не теряя указатель на локальные переменные
                  Ой да ладно, конпелятору не так уж и сложно трекать сколько байт он наклал на стек... Единственный случай, где FPO реально не заюзать -- функция с alloca.

                  З.Ы. А в 16-битном коде об SP нельзя было адресоваться, так что там BP без вариантов и никакого FPO.
                  Ответить
                  • Я упустил из виду, что в 16-битном режиме доступна только адресация вида [BX+SI], [BX+DI], [BP+SI], [BP+DI], [SI], [DI], [BP], [ВХ], а в 32-битном и в 64-битном [EAX], [ECX], [EDX], [EBX], [EBP], [ESI], [EDI] плюс s-i-b (в котором можно составлять пары из перечисленных регистров).

                    А где-то можно адресоваться через SP?
                    Ответить
                    • Через esp/rsp -- можно. Через sp -- нет.

                      З.Ы. Разве что въебать 67h, нагло заюзать [esp] прямо из 16-битного кода и молиться, чтобы никто не насрал в старшую половину esp. Всё равно чистых 8086 в природе уже нет.
                      Ответить
                      • Всё, нашёл. В "s-i-b" можно использовать esp/rsp, но только в качестве базы (в качестве индекса с множителем -- нельзя). В "mod R/M" esp/rsp нет.

                        А старшую половину esp можно обнулить. Безо всякой зелени. Видел 16-битные библиотеки, оптимизированные под 386. В них использовались префиксы 66h и 67h, чтобы воспользоваться новыми плюшками, находясь в 16-битном сегменте. И там как раз приходилось инициализировать старшие половинки регистров.
                        Ответить
                        • > Безо всякой зелени.
                          На некоторых прошивках видно, что после вызова "реалмодного" прерывания меняется IDTR. Т.е. CSM иногда зачем-то бегает в защищёнку и есть риск, что он и старшие части регистров хуёво сохранит и помнёт.

                          Нахуй и в пизду, короче. Разве что под CLI и с обнулением прямо перед использованием...
                          Ответить
                  • >> конпелятору не так уж
                    именно тем и руководствовались питухи, когда выпиливали frame pointer.
                    А потом без символов стек не прочитать

                    >>А в 16-битном коде об SP нельзя было адресоваться,
                    даже на совр. процах?
                    Ответить
                    • Гугли mod R/M.

                      Косвенная адресация использует либо байт mod R/M, либо s-i-b. В mod R/M нет варианта [SP]. Даже в 32-битном и в 64-битном режиме нет варианта [ESP]/[RSP]. А s-i-b, в котором есть [ESP]/[RSP] в 16-битном варианте недоступен.

                      Отдельной инструкции, использующей [SP], в сетке команд нет.

                      Единственный способ адресоваться в 16-битном коде об SP, это использовать префикс размера адреса. Т. е. писать mov ax, word ptr [esp] вместо mov ax, word ptr [sp], предварительно обнулив старшую половинку регистра esp, чтобы в ней не было мусора. А об этом уже написал bormand: придётся оборачивать код в CLI/STI, чтобы какой-нибудь обработчик прерывания не засрал старшую половину регистра.
                      Ответить
              • Офтопик. Нашёл на просторах интернетов задний конец для "gcc", генерирующий 16-битный код для 8086. А кто-нибудь пытался написать задний конец для 8008 или для 4040, чтобы был полный хардкор?
                Ответить
                • под 4004

                  Ответить
                  • Под однобитный процессор.

                    З.Ы. Лол, оказывается даже суперкомпьютер с 65536 однобитных процессоров был...
                    Ответить
                    • Какая прелесть, всего 16 опкодов:
                      http://www.brouhaha.com/~eric/retrocomputing/motorola/mc14500b/
                      Ответить
                      • БЛя, у него даже счетчика команд не было. Что это за хуйня?
                        Ответить
                        • Промышленный контроллер, чтобы всякие моторы и лампочки включать по сигналам от кнопок и датчиков.

                          Там для большинства задач даже переходы нинужны были. Команды тупо исполнялись по кругу. Зато инженерам не надо было учиться программировать, они брали схему на реле и дословно переносили её на асм этого контроллера.
                          Ответить
                          • Для чего-нибудь типа светофора?
                            Ответить
                            • Скорее для какого-нибудь лифта. Светофор то и на обычной логике легко запиливается.
                              Ответить
                    • Следующим шагом предлагаю писать под защелку, а еще лучше под транзистор, а еще лучше сразу под лампу или даже под реле
                      Ответить
                      • Писать под реле не советую, особенно если оно коммутирует цепь высокого напряжения.
                        Ответить
                • Полный хардкор -- это malbolge.
                  Ответить
                  • передний конец под malbolge
                    Ответить
                    • Ну, это уже для пользователя конпеляторва хардкрор, а вот если задний конец под malbolge -- хардкор для разработчика.
                      Ответить
        • GCC 8.1 c -O3 прошарился и выпилил trampoline: https://godbolt.org/g/AmmB7f
          Ответить
          • ... только правда недоконца выпилил. Кто-нибудь знает что такое этот их
            [clone .constprop.0]

            ?
            Ответить
            • Попытаемся предположить: это версия функции с уже применёнными аргументами. Т. е. компилятор упростил вызов trampoline(set_aes_keys, false), потому что его можно упростить ещё на этапе компиляции, и обозвал это новой функцией.

              Что-то вроде частичного применения.
              Ответить
              • Похожее гуглится по фразе "constprop clones".

                Ну почему нет частичного применения на уровне языка?
                Ответить
            • Simple constant propagation.

              foo 2, 2
              ->
              foo 4
              Ответить
              • Обидно, что в режиме оптимизации компилятор это умеет, а на уровне языка средств для этого нет. Хотелось бы явно получать что-то типа такого:
                trampoline_1(func) = trampoline(func, false);
                trampoline_2(flag) = trampoline(set_aes_keys, flag);
                trampoline_3() = trampoline(set_aes_keys, false);
                Чтобы генерацией функцией-клонов можно было управлять и чтобы клоны можно было использовать самостоятельно.
                Ответить
                • Хотя в принципе уже можно: тело функции-клона составить из единственного вызова:
                  void trampoline_1(void (*func)(void)) {
                      trampoline(func, false);
                  }
                  void trampoline_2(bool flag) {
                      trampoline(set_aes_keys, flag);
                  }
                  void trampoline_3(void) {
                      trampoline(set_aes_keys, false);
                  }
                  Ответить
      • >>Защита от инлайна какая-нибудь?
        неужто нет ключ слова у копелятора?

        >>З.Ы. А у этого трамплина только одна реализация?
        конечно, есть еще и такая

        void trampoline(void (*func)(void), bool flag)
        {
        if (flag)
        func();
        else
        trampoline(func, false);
        }

        ахахахахах
        Ответить
    • > trampoline
      Сперва прочитал как traponline
      Ответить
    • typedef void (*callback_t)(void (*func)(void), void *callback);
      
      void inner_trampoline(void (*func)(void), callback_t callback)
      {
          if (callback == NULL)
              func();
          else
              callback(func, NULL);
      }
      
      void outer_trampoline(void (*func)(void), bool flag)
      {
          if (flag)
              inner_trampoline(func, &inner_trampoline);
          else
              outer_trampoline(func, true);
      }
      
      int 
      main(int argc, char *argv[])
      {
          /*... */
          outer_trampoline(set_aes_keys, false);
      }
      Ответить
      • Щас придёт 1024-- и перепишет на trampoline<N>(func, bool)
        Ответить
        • Щас придёт 1024-- и перепишет на JavaScript
          Ответить
          • const trampoline = (N, func, ...a) => N ? trampoline(N-1, func, ...a) : func(...a);
            
            trampoline(5, set_aes_keys);
            Ответить
            • Кстати, о "=>".
              Ты пишешь под браузер или только под ноду?
              Ответить
              • В нормальных браузерах это работает
                Ответить
                • ну так
                  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions#Browser_compatibility


                  Но у нас всё равно Бабель, может быть для let или import
                  Ответить
              • В моём браузере => работает. Выходит, и под браузер, и под ноду.
                Ответить
        • template <int N, typename F, typename... A>
          struct trampoline_
          {
              trampoline_(F func, A...a)
              {
                  trampoline_<N-1, F, A...>(func, a...);
              }
          };
          
          template <typename F, typename... A>
          struct trampoline_<0, F, A...>
          {
              trampoline_(F func, A...a)
              {
                  func(a...);
              }
          };
          
          template <int N, typename F, typename... A>
          void trampoline(F func, A...a)
          {
            trampoline_<N-1, F, A...>(func, a...);
          }
          
          int 
          main(int argc, char *argv[])
          {
              /*... */
              trampoline<5>(set_aes_keys);
          }
          Ответить
    • А неиспользуемые argc, argv никого значит не смутили??!
      Ответить
      • некоторые всегда так main пишут
        Ответить
      • Вспомнился говнокод, где чувак перепутал "argc" и "argv", но чёт не смог найти
        Ответить
        • Но работает ведь: https://wandbox.org/permlink/xKIINH4HwD2jzBPs
          int main(int argv, char *argc[]) {
              std::cout << "last arg: " << argv[argc - 1] << '\n'; 
          }
          Ответить
          • puts(-1[argc + argv]);
            Ответить
            • Страшная сишная арифметика указателей. И ведь работает, хотя мозг отказывается это понимать.
              Ответить
              • нормальная арифметика

                foo + bar это обычно foo[bar]
                Ответить
              • Главное - зачем такая питушня понадобилась?
                Идеи кастов в JS ещё можно понять: удобно, когда пишешь "form.y.value = form.x.value * 10" и не отвлекаешься на преобразования.
                Но это перемешивание индексов - какая-то питушня. "Вдруг программист случайно перепутает массив и индекс - с кем не бывает - а наш язык ему поможет".
                Ответить
                • > Но это перемешивание индексов - какая-то питушня.
                  Эта питушня наверное походит еще со времен, когда С считался непозволительной роскошью из-за оверхеда над ASM.

                  > "Вдруг программист случайно перепутает массив и индекс - с кем не бывает - а наш язык ему поможет".
                  Скорее мысль была такая "в случае x[y] заменяем и интерпретируем как *(x + y) ибо надо сэкономить пару байт кода компилятора"
                  Ответить

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