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

    0

    1. 1
    #define BSWAP16(x) ( (uint16_t)(((uint32_t)x)*(1 | 1 << 16) >> 8) )

    Запостил: j123123, 15 Марта 2019

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

    • #define BSWAP16(x) (uint16_t)( (uint16_t)(x) * (1 | 1 << 16) >> 8)
      Ответить
      • На платформе с 16-битным интом не сработает, в отличие от оригинала.
        Ответить
        • А если в оригинал сунут число > 65536?
          Ответить
        • Разве на платформе с 16-битным интом оно не сломается еще на стадии (1 | 1 << 16) в том числе и на оригинале?

          Надо наверно так, чтоб уж наверняка:
          #define BSWAP16(x) (uint16_t)( (uint16_t)(x) * (1ULL | 1ULL << 16ULL) >> 8ULL)
          Ответить
          • По-моему 0x00010001 нагляднее этих сдвигов будет.
            Ответить
        • показать все, что скрытоvanished
          Ответить
          • Почему? На платформах с 32-битным интом sizeof(long long) * CHAR_BIT может быть (и чаще всего так и есть) равным 64. Аналогично, на платформах с 16-битным интом uint32_t скорее всего будет синонимом unsigned long long.
            Ответить
      • #define BSWAP16(x) (x ^ (uint16_t)(x << 8 | x >> 8) ^ x)
        Ответить
    • : BSWAP16  ( u -- u )
          BASE @ >R  HEX
          S" FFFF AND 00010001 * 8 RSHIFT FFFF AND" EVALUATE
          R> BASE !
      ; IMMEDIATE
      Работает и в режиме компиляции (как макрос) и в режиме интерпретации.

      https://ideone.com/1KiivR
      Ответить
    • Байтоёбство без побитовых операций:
      #define BSWAP16(x) ({union{uint16_t u16; uint8_t u8[2];} t = {.u16 = x}; (union{uint16_t u16; uint8_t u8[2];}){.u8 = {t.u8[1], t.u8[0]}}.u16;})
      Ответить
    • ror ax, 8
      Именно поэтому я за "assembler".
      Ответить
      • rev16 r0, r0
        Именно поэтому я за "ARM".
        Ответить
      • xchg ah, al
        Ответить
        • Это в x86 валидная инструкция? О_о
          Ответить
          • Вполне
            Ответить
          • А что не так? ah, al, bh, bl, ch, cl, dh, dl в некоторых инструкциях могут использоваться как самостоятельные регистры.

            Есть инструкция xchg, ожидающая восьмибитные данные. Аргументами могут выступать два восьмибитных регистра либо восьмибитный регистр и указатель на память.
            Ответить
          • Ещё как, у XCHG есть несколько версий: однобайтовая с регисторм AX/EAX (КОП 90-9F), и с батом MOD/RM (КОП 86 -- размер операнда байт, 87 -- WORD/DWORD), что рождает кучу 2-хбайтовых NOP'ов типа XCHG AH, AH XCHG BX, BX и т.п. К сожалению XCHG REG, REG не попадает в множество ASCII :(
            Ответить
            • Зато XOR и SUB попадают. Можно эмулировать XCHG пачкой ксоров или вычитаний.
              Ответить
              • Во множество ASCII-кодов попадают только значения «регистр-память» байта mod R/M. Кобенации «регистр-регистр» за пределами ASCII-кодов.
                Ответить
                • Придумал, как обменять значения ah и al в ascii-кодах.
                  temp equ [bx+40h] ; Произвольный адрес в памяти.
                  ; Значение этой ячейки потом восстановится.
                  xor ah, temp; [email protected] ; в ah теперь ah XOR temp
                  xor temp, al; [email protected] ; в temp теперь temp XOR ah XOR al
                  xor al, temp; [email protected] ; в al теперь temp xor ah
                  xor al, temp; [email protected] ; в al теперь старое значение ah
                  xor ah, temp; [email protected] ; в ah теперь temp XOR al
                  xor temp, ah; [email protected] ; в ah теперь старое значение al
                  xor temp, al; [email protected] ; в temp теперь старое значение temp

                  Итого: [email protected]@[email protected]@[email protected]@[email protected]
                  21 байт. Длинновато вышло, зато в ASCII-кодах.
                  Ответить
                  • Ты же не будешь отрицать, что ты поехавший?
                    Ответить
                    • Братишка, давай я насру в уголочке в ASCII-кодах, программисты слетятся, тут мы их и прихлопнем?
                      Ответить
                  • показать все, что скрытоvanished
                    Ответить
                    • У меня шпаргалка есть:
                      http://govnokod.ru/15764#comment419684
                      Ответить
                    • Тут самое сложное –— не запутаться, изобретая схему обмена значений. Ещё важно было не забыть восстановить значение ни в чём не повинной переменной, которое мы временно портили.
                      Ответить
                  • 5 байт:
                    PPDXD
                    push ax
                    push ax
                    inc sp
                    pop ax
                    inc sp
                    Ответить
                    • Ты реально аццкий сотона!
                      Ответить
                    • Твоим методом также можно вращать регистры BP, SI, DI, для которых у x86 не было половинок (bpl, sil, dil появились только у x86-64 в длинном режиме, а bph, sih, dih до сих пор не придумали).
                      Ответить
                      • Так можно было бы ещё и числа по регисрам сдвигать:
                        PUSHA
                        PUSH (какой там? AX?)
                        ;INC SP если надо сдвинуть на байт
                        POPA
                        но SP тоже сдвигается, а в старом стеке остаётся число :(
                        Ответить
                        • Да, жаль, что PUSHA и POPA захватывают SP, а то бы легко было эмулировать SIMD.

                          А так придётся SP где-то ещё сохранять, а потом восстанавливать.
                          Ответить
                    • Циклический сдвиг 32-битного числа на 8:
                      #include <stdio.h>
                      #include <stdint.h>
                      char * __attribute__((section(".text"))) vrot8 = "QXPPDXDDD\xc3";
                      int main() {
                          uint32_t __attribute__((fastcall)) (* rotator)(uint32_t) = (void *)vrot8;
                          uint32_t x = 0x12345678;
                          printf("rot(%x) = %x\n", x, rotator(x));
                          return 0;
                      }


                      64-битный вариант (почему-то не работает):
                      #include <stdio.h>
                      #include <stdint.h>
                      char *__attribute__((section(".text"))) vrot8 = "WXPPDXDDDDDDD\xc3";
                      int main() {
                      	uint64_t __attribute__((sysv_abi)) (* rotator)(uint64_t) = (void *)vrot8;
                      	uint64_t x = 0x123456789abcdef;
                      	printf("rot(%lx) = %lx\n", x, rotator(x));
                      	return 0;
                      }
                      Ответить
                      • В 64-битном режиме почему-то после INC RSP неправильно работает POP. Похоже, что POP в длинном режиме выравнивает значение RSP перед тем, как взять данные.
                        Ответить
                        • Например, сдвиг на 16 бит без применения INC/DEC работает:
                          #include <stdio.h>
                          #include <stdint.h>
                          const char __attribute__((section(".text"))) vrot8[] = "WXfPPfXX\xc3";
                          int main() {
                          	uint64_t __attribute__((sysv_abi)) (*rotator)(uint64_t) = vrot8;
                          	uint64_t x = 0x123456789abcdef;
                          	printf("rot(%lx) = %lx\n", x, rotator(x));
                          	return 0;
                          }


                          https://ideone.com/jycvRV
                          Ответить
                        • > почему-то
                          А ты декомпильни этот код. 'D' - не инкремент. Это префикс REX.R
                          Ответить
                          • Вот я идиот! Даже не глянул сетку опкодов. Я думал, что все новые инструкции где-то в пространстве 0F, а опкоды для старых инструкций совпадают, как было при переходе с 16-битного кода на 32-битный. А они, оказывается, часть старых опкодов затёрли новыми инструкциями.

                            Какой багор )))

                            Надо запомнить, что у x86-64 однобайтовых инкрементов/декрементов нет, на их месте ненужные префиксы Рэкс-фас; PUSHA, POPA, BOUND отменили; на месте ARPL теперь MOVSXD; AAA, AAS, DAA, DAS, AAM, AAD отменили; префиксы сегментов отменили, за исключением FS, GS; пуш и поп для сегментных регистров тоже отменили; LES, LDS, CALL FAR, JMP FAR тоже отменили.

                            И зачем-то отменили опкод 82H, который никому не мешал.

                            Как вообще можно что-то делать без однобайтовых инкрементов/декрементов???
                            Ответить
                            • Отредактировал комментарий, потому что ошибся.

                              83H –— это операции с 16/32-битным регистром, у которых в качестве второго аргумента выступало 8-битное непосредственное, которое на лету дополнялось нулями до 16/32-битного. Было очень удобно, потому что не нужно было хранить в программе лишние нули.

                              А 82H –— это просто копия 80H.
                              Ответить
                            • Идея: вместо инкремента вычитать минус единицу. Минус единицу можно представить как 0x5555555555555555 + 0x5555555555555555 + 0x5555555555555555.
                              Ответить
                              • Всё, высрал инкремент:
                                #include <stdio.h>
                                #include <stdint.h>
                                char *__attribute__((section(".text"))) incr = "fhUUfhUUfhUUfhUUTZH+:H+:H+:ZWX\xc3";
                                int main() {
                                    uint64_t __attribute__((sysv_abi)) (* increment)(uint64_t) = (void *)incr;
                                    uint64_t x = 0x123456789abcdef;
                                    printf("inc(%lx) = %lx\n", x, increment(x));
                                    return 0;
                                }


                                https://ideone.com/2Z5MCE

                                Узнал две особенности x86-64:
                                1. У кучи инструкций непосредственный аргумент может быть максимум 32-битным. Старшая половина добивается нулями. Мне пришлось делать четыре 16-битных пуша, чтобы запушить 64-битное число.
                                2. У инструкций с mod-R/M нужно указывать префикс REX.W (H), иначе результат обрежется до 32-битного.

                                Как всё сложно...
                                Ответить
                          • Кстати, почему в длинном режиме непосредственный аргумент не может быть 64-битным? Они ввели это ограничение, чтобы инструкция не получалась слишком длинной и чтобы с лёгкостью укладывалась в кэш/конвейер/предиктор?
                            Ответить
                            • > The x86 instruction set (16, 32 or 64 bit, all variants/modes) guarantees / requires that instructions are at most 15 bytes.
                              https://stackoverflow.com/questions/14698350/x86-64-asm-maximum-bytes-for-an-instruction

                              «mov qword [rax + rcx + 0x11223344], 0x55667788», например, занимает 12 байт, а с 64-битным аргументом получится 16 (и ещё всякие префиксы могут быть же). Ну и как пишут на SO — 64-битный аргумент можно напрямую загружать в регистр.
                              Ответить
                              • Поглядел, нашёл единственную инструкцию с 64-битным непосредственным: MOV reg, imm (КОП B8H+номер регистра).
                                Ответить
                              • Получается, что если я искусственно соберу длинную инструкцию (нафигачив префиксов сегмента и REP), то программа выполнится на 8086/8088, но упадёт на более современных процессорах (начиная с 80286) даже в реальном режиме?
                                Ответить
                      • Замечание про атрибуты.

                        В 64-битном режиме у «gcc» есть два соглашения о вызове: «sysv_abi» и «ms_abi». При «sysv_abi» первые 6 аргументов передаются через регистры (RDI, RSI, RDX, RCX, R8, R9), при «ms_abi» только четыре первых аргумента передаются через регистры (RCX, RDX, R8, R9). Плавающий питух передаётся через питушиный стек (до четырёх аргументов в случае «ms_abi» и до восьми аргументов в случае «sysv_abi»). Стек в обоих случаях чистит вызываемая функция. Нельзя портить RBX и RBP (а для «ms_abi» ещё и RSI и RDI) и четыре последних нумерных.

                        В 32-битном режиме вариантов способа вызова гораздо больше. «Fastcall» у разных компиляторов реализован по-разному («gcc» первые два аргумента передаёт через ecx и edx).

                        Мне больше всего понравилось, как сделали в «Watcom C»: там с помощью #pragma можно создать своё собственное соглашение, указав, через какие именно регистры нужно передавать аргументы и кто будет чистить стек.
                        Ответить
                        • Нашёл ещё одно отличие ms_abi и sysv_abi в 64-битном режиме: перед вызовом функции большого количества аргументов в ms_abi указатель на вершину стека уменьшается, как будто первые четыре аргумента тоже запушили, хотя реально в этом участке стека лежит мусор.
                          Ответить
                          • https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2017
                            The x64 Application Binary Interface (ABI) uses a four-register fast-call calling convention by default.
                            Space is allocated on the call stack as a shadow store for callees to save those registers.
                            Ответить
                          • В sysv_abi есть ещё одно страшное отличие - red zone. Кусок стека, который можно невозбранно юзать без декремента rsp.

                            Эта хуйня порождает очень весёлые баги в ring 0 (если пишешь своё ядро, например)... Её, конечно, можно отключить. Но для этого про неё надо знать.
                            Ответить
                        • > вызываемая
                          Вызывающая.
                          Ответить
                          • Да, посмотрел дизасм, теперь уже понял, что вызывающая, как в cdecl.

                            То есть можно создавать функции с переменным количеством аргументов, а вот сделать принудительный пуш/поп посредством каламбура типов не получится.
                            Ответить
                            • Напиши себе ascii'шные push и pop. Они то гарантированно будут работать в отличие от игры с конвеншенами. Вот только оптимизатор потом обидится и достанет локальные переменные не оттуда...
                              Ответить
                            • При очистке вызываемым тоже можно замутить переменное количество аргументов. Передаёшь ему количество в регистре или крайнем аргументе да и всё.

                              В общем-то это даже надёжней т.к. вызываемый видит обе границы аргументов. А в cdecl - только нижнюю.
                              Ответить
                              • Как я тебе стек из вызываемой функции чистить буду? RET ожидает кококоличество очищаемых байтиков в непосредственном аргументе.

                                Варианты реализации:
                                1. Патчить код (изменять байтик, следующий за опкодом RET). Для этого нужно снимать защиту.

                                2. Свитч-кейс, в каждой ветке которого будет RET N с соответствующим значением N.

                                3. Извлекать из стека адрес возврата, чистить стек, после этого делать джамп (ну или push+ret) на сохранённый в регистре адрес возврата.
                                Ответить
                                • 3.

                                  А можно даже количество не передавать. Не дочитал все vararg'и - получай UB.
                                  Ответить
                                  • З.Ы. В общем-то вызов printf() с кривой строкой формата - это уже UB. Так что такая реализация имеет право на жизнь.
                                    Ответить
                                • > RET N
                                  Кстати, а ведь ret и ret N спекулятивно возвращаются к вызывающему независимо от N и состояния стека? Т.е. вариант с push + ret будет более правильным, нежели jmp...
                                  Ответить
                                • В жопу стек.
                                  Ответить
    • показать все, что скрытоvanished
      Ответить
      • Задачки, которые нахуй не пригодятся в работе, задавали?
        Ответить
      • Один парень устроился в «Яндекс» и умер. Курьер сервиса «Яндекс.Еда» умер прямо на рабочем месте, отработав 10 часов без перерыва. Молодому человеку исполнился 21 год. Компания «Яндекс» уже принесла извинения родственникам погибшего.

        Пишут, что сервис славится нереальными задачами типа доставить еду на расстояние 4 километра за 14 минут. Напоминаю, что курьер пеший. За опоздание штраф.
        Ответить
        • Ублюдки, пользуются тем, что в сране безработица, выжимают из людей последние силы.
          Ответить
          • На Х-ре уже появилась заметка с интересными комментариями:
            https://habr.com/ru/news/t/448722/

            Один комментатор уже вспомнил песню «16 тонн». Если кто-то не знает, это песня про США эпохи Великой депрессии:
            https://ru.wikipedia.org/wiki/Sixteen_Tons
            Ответить
          • показать все, что скрытоvanished
            Ответить
        • > 10 часов без перерыва
          Имхо, страшнее когда так работают таксисты или газелисты. Зомбаки за рулём.

          > нереально
          Если верить гуглу, то КМС должен пробежать за это время 5км. Так что вполне реально.
          Ответить
        • Какой анскилл )))
          Ответить
    • показать все, что скрытоvanished
      Ответить

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