Pull to refresh

Comments 63

UFO just landed and posted this here

"Мы не знаем как должно быть но вы делаете неправильно"?

UFO just landed and posted this here
… у вас останется только один способ как сделать и он и будет… тысяча первым способом как не надо :)
UFO just landed and posted this here

Ну так по-идее System V amd64 ABI предполагает передачу целочисленных аргументов через rdi, rsi, rdx, rcx, r8, r9, что должно быть оптимальнее стека.

Почему ущербен?
В х86 процессорах есть stack engine, который позволяет эффективно выполнять последовательности push/pop.

Насколько это эффективно по сравнению с регистрами? Как я понял, stack engine генерирует адреса, а load/store никуда не исчезают?

Просто для справки, эти push/pop предназначены не для передачи аргументов, а для сохранения состояния тех регистров общего назначения, которые функция обязана сохранять согласно calling convention.
3 операнда в приведённой в статье функции strlcpy() и передаются через регистры RDX,RSI,RDI.

UFO just landed and posted this here
Они через регистры передаются. Я выше ответил.
Что делать если параметров больше, чем регистров выделенных в ABI для передачи параметров?
В первую очередь — подумать, нельзя ли уменьшить число параметров. Но если речь о каком-нибудь специфическом ABI или передаваемом типе данных, то без стека не обойтись.

Да что там, фон неймановская архитектура — это анахронизм 40х.

Допустим параметры идут через регистры и так, а куда локальные переменные девать? Регистров же не напасешся? Компайлер и так по возможности старается регистры использовать и под локальные переменные, но на все не хватит.
Плюс как трейсить вызовы без стека? Рекурсия под запретом? Хвостовую не все умеют.
Кучу использовать вместо стека тоже не получиться, там другие проблемы.


Чего сразу ущербен то?

UFO just landed and posted this here

Я попытался такое запустить, и оно ничерта не работает. Это точно портабельный исполняемый файл? aarch64? Вы уверены?

При помощи заголовка вы добились запуска бинарника на разных ОС. При дальнейшем выполнении кода неизбежно возникают следующие проблемы: системные вызовы windows и linux разливаются и по номерам, и по функционалу, как будут осуществляться системные вызовы в этом бинарнике; различные платформы имеют различные ABI, какое соглашение будет использоваться для передачи параметров в подгружаемую внешнюю библиотеку; на некоторых платформах может быть не быть какой-нибудь инструкции или регистра, которые приведены в в листингах, будет ли работать программа на такой платформе; допустим, самая первая же инструкция pop r10 — если тот же машинный код будет соответствовать какой-нибудь инструкции, то программа даже не запустится?

Тема совершенно не раскрыта. Осталось куча вопросов после прочтения статьи. Насколько мне известно, в общем случае задача создания кроссплатформенного бинарника не имеет решения. Хотелось бы увидеть теоретические рассуждения, каким образом они решили эту задачу или с какими оговроками.
Запихиваем в файл реализации ввода/вывода для всех поддерживаемых ОС; указываем разные entry point в заголовках разных форматов, в зависимости от того, который из них был реально вызван, подставляем нужные реализации в рантайме. PROFIT
Пусть не изящное, но рабочее решение. Однако это не поможет с тем, что на разных аппаратных платформах (amd64 и тот же arm) одни и те же двочиные коды будут выполнять разные команды. Можно попытаться сделать конструкцию вида SMTSMT; add rax, CONST; jmp rax. Тогда в зависимости от результата выполнения первой команды мы узнаём, на какой платформе работает, затем при помощи add превращаем результат в адрес и делаем jump… но для этого надо чтобы хотя бы jmp имел одинаковые машинные коды…

Нет, всё ещё непонятно.
Насколько я понял магию, то можно тупо создать бинарь, который на разных платформах будет выглядить родным. При этом под каждую платформу будет свой код. Соответственно, надо будет просто скомпилировать родные исполняемый файлы (PE, ELF) а потом запаковать их этим магическим образом.

Да, но только в пределах одной операционной системы.

Спасибо за привлечение внимания к проекту, но тема заслуживает нормальной статьи! Основные вопросы:


  1. Там кросс-компиляция в неявном виде (для каждой платформы на самом деле запускается свой код) или идет трансляция всех системных вызовов? Если второе, то какие ограничения.
  2. Что с подключением внешних библиотек (статических, понятно, что магии не бывает)?
  3. Как возможна заявленная работа на разных архитектурных с разной системой команд (x86/64, ARM)?
  4. Работа на bare-metal какие ограничения накладывает? Работает ли оно как IncludeOS или ее (или аналог) можно/нужно подключить? Что в этом случаи с драйверами (ФС, сеть)? Можно ли без MMU?
  5. На оригинальной странице написано, что не предназначено для программ с GUI (что оправданно), но есть же кроссплатформенные GUI библиотеки, реально ли что-то подружить?

Думается мне, что это — просто способ эмулировать PE формат файла как sh скрипт. То есть будет одинаково исполняемым на Windows и Linux. Но вот расставлять набор ифов даже начальных для разных архитектур — не предлагается.
Просто отличная задача. Чем-то напоминает задачу о квайнах. Сложно, интересно, но на практике — довольно бесполезно.

Я пока серьезно не вгрызался в проект и вообще ненастоящий сварщик, но вот что я откопал за 30 минут (плюс инфа из треда на HN):


  1. "Не миллион а тысячу, не в казино а в карты, не выиграл а проиграл". Ни то, ни другое.


    Её изобретение — способ эмулировать PE формат файла как sh скрип, как тут рядом написали. Список новинок на этом заканчивается. Этот Cosmopolitan по сути просто libc + POSIX, т.е цель — создать кросплатформенную libc, а также дать людям инструмент который позволит создавать их собственные кроссплатформенные библиотеки без особого геморроя. Как я понял, про явные ioctl в пользовательском коде речи не идет — на "неверной" платформе они тупо вернут ошибку (но программу не уронят, вроде как).


    Как реализовано конвертирование syscall-ов я и сам толком не разобрался, извиняйте.


    Насчет ABI — безальтернативно x86-64 Linux ABI (без обязательного "фи" в сторону Windows calling convention не обошлось). Ваши функции вот прям обязаны использовать другие соглашения? Для вас есть вариант с трамплинами, которые любезно генерирует компилятор — только пропишите определение функции вот в хэтотъ(https://github.com/jart/cosmopolitan/blob/master/libc/nt/master.sh) файл (именно так реализована привязка в WinAPI).


  2. Как я понял, особых ограничений нет, статическая линковка она и Африке статическая линковка, но все библиотеки (если делают системные вызовы) должны быть специальным образом перекомпилированы чтобы быть совместимыми с этим чудо-форматом. И да, либо x86-64 Linux ABI, либо специальная прослойка типа той что для WinAPI.


  3. Ученый изнасиловал журналиста. x86-64 only, плюс есть способ внедрить qemu в бинарник и эмулировать, эмулировать, эмулировать! Написано что выходит вроде как шустро, но черт его знает, сомнения гложут меня.


  4. Смотрите пункты 1 и 3. Ваш камень x86-64 и система System V Compatible? Поздравляю, вы знаете толк в baremetal в деле! Нет? Для вас есть вариант с qemu, выводы о драйверах и MMU делайте сами.


    Вроде как пишут что умеет в BIOS, то есть можно использовать бинарник в качестве загрузчика на IBM PC.


  5. Смотрите пункты 1 и 2. Не думаю что кто-то будет тратить на это время.



В воскресенье покопаюсь в этом wunderwaffe основательнее, но то, что я пока увидел, мягко говоря обламывает ожидания. С одной стороны, поделка вроде интересная и даже ограниченно полезная, но с другой — уровень хайпа и дифирамб вокруг этой штуки вот вообще никак не оправдан ИМХО. И ведь в ридми у них значится гордое "like Java", сеошники чертовы (ушел возмущаться несовершенством мира в целом и программирования в частности)...

Теперь хоть что-то стало понятно. Спасибо. Ваш бы комментарий вместо статьи. А какие у вас были ожидания? То, что нельзя создать бинарник, который будет исполняться на всех платформах, без чего-нибудь специально предустановленного для этого, — это математическая теорема. Вопрос лишь в том, чем именно они пожертвовали, пытаясь создать невозможное.
Вообще создать бинарник, который без предустановленного софта сможет выполняться на разных платформах вполне реально (привет .NET Core), хотя там вся виртуальная машина прячется в этот бинарник, и он получается просто огромным.
Нашёл, в чём здесь подвох. Создать бинарник, который выполняется в бинарном виде — невозможно. Здесь же другая ситуация.
Вообще, статья больше вводит в заблуждение, чем объясняет. Сначала в ней говорится, что win-заголовок может интерпретироваться как скрипт, причём без шебанга, но в следующем абзаце этот код запускается в двоичном виде. На каком основании? Заголовок ELF должен быть расположен строго в начале файла и нигде более, если его там нет, то программа запускается как скрипт, но она уже запущена как скрипт. То есть вот exec 7<> $(command -v $0) приведёт к бесконечной рекурсии. Ладно, допустим, этот код как-то приыёл к запуску программы в двочином виде. В дебаггере мы видим опять MZ, размещённые по адресу 40000, то есть теперь этот же заголовок исполняется как _start. Отлично. Давайте рассмотрим абстрактную платформу, в которой опкод MZ просто не реализован, и натолкнувшись на него процессор сразу же выдаёт Illegal Opcode Exception, а система радостно грохает процесс.

Что касается qemu, то из скрипта не понятно, как он запускается, однако расчитывать, что он окажется в системе, нельзя по условию задачи, а распаковать его код — для этого мы должны знать на какой аппаратной платформе запущены, чтобы нужный код распаковать. Средствами shell, наверное, это можно сделать, но в этой работе не сделано. Подозреваю, формат сильно ограничивает размеры скрипта.

С таким же и даже большим успехом можно было просто написать программу на python. Интерпретатор установлен на системном уровне и в linux, и в последних windows.

Странная реализация strlcpy. Два прохода по исходной строке: один раз в strlen, второй раз в memcpy. Откуда тут хорошая производительность?

Я не смотрел код, но в зависимости от оптимизаций это действительно может быть быстрее — за счёт специализированных инструкций процессора (типа REPNE SCASB в x86 или проверки сразу слов а не байт) и за счёт того что копирование блоками по 4/8/16 байт (если архитектура позволяет) будет ощутимо быстрее (memcpy обычно так и делает).


Реализация "в лоб", т.е. побайтное копирование с одновременной проверкой на нулевой байт явно будет медленней чем оптимизированнй вариант — выигрыш в первом случае (и то небольшой) будет только на очень коротких строках (обычно до 8-16 байт в зависимости от конкретной архитектуры).

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

Если посмотреть на версию из libc, то видны те же два вызова strlen и memcpy. Не с проста это. Дело в том, что эта функция не знает длину исходной строки, в неё передаётся только размер буфера. Поэтому приходится сначала по одному байту искать, где же конец строки, а затем блоками копировать нужную длину (memcpy это умеет).
Блоки по 8 байт, а внутри каждого блока также каждый байт проверяется. Алгоритм опять сводится к тому, что каждый байт проверяется. Получается 28 арифметических операций на 8 байт. Это больше, чем N=3*8=24, не исключено, что такая оптимизация будет даже медленнее.

w = (uint64_t)p[7] << 070 | (uint64_t)p[6] << 060 | (uint64_t)p[5] << 050 |
(uint64_t)p[4] << 040 | (uint64_t)p[3] << 030 | (uint64_t)p[2] << 020 |
(uint64_t)p[1] << 010 | (uint64_t)p[0] << 000;
if ((w = ~w & (w — 0x0101010101010101) & 0x8080808080808080)) {

Если бы каждая операция в C тупо выполнялась процессором — да. Но не всё так просто. К тому же, чтение 8 байт из памяти в регистр быстрее чем читать побайтно.

Ассемблерный код оказался более читаемым, чем исходник. То есть вся эта страшная конструкция эквивалентна одному простому mov:
w = ((uint64_t*)(p))[0];
Но почему разработчики не написали именно так?

А алгоритм сводится к битовой магии add, not, add, add.
Но почему разработчики не написали именно так?

Потому что оно должно работать при любом порядке байтов.

Точно. с BigEndian будут проблемы. Но если присмотреться, цикл проверяет, что в блоке нет ни одного нулевого байта, и при смене порядка порядка цикл будет корректно работать. Меняется только действие, когда мы нашли нулевой байт: надо не с конца считать номер байта, а с начала. Но для этого уже предусмотрена другая функция.

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
i += (unsigned)__builtin_ctzll(w) >> 3;
#elif __BYTE_ORDER__ == __BIG_ENDIAN__
i += (unsigned)__builtin_clzll(w) >> 3;
#else
AUCHTUNG!;
#endif

В таком виде Big Engian машинам не придётся за зря крутить байты в строках.

Хм… троичная логика детектед!

Видел файл polyglot ещё в тыща девятьсот… нацатом году. Это был код одновременно на 6 или 7 языках (помню C, Pascal, Postscript(!), возможно Fortran, Perl) и даже. COM-файл для запуска из DOS.

Журнал pocorgtfo ещё и не таких франкенштейнов в выпусках делает порой)

Это какое-то лютое хакерство ради хакерства.
Найдет применение разве что в кросплатформенной малвари.
И то врядли надолго. Антивирусы добавят в свои БД сигнатуры.
Ну почему. Один бинарник, одна кнопка «скачать» на сайте. И пользователям (в т.ч. далёким от компьютеров, не отличающим x86-64 от ARM) не нужно думать, какой вариант софтины скачивать.

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

Вот когда эта софтина заглючит и крешнется из-за хитросплетений работы загрузчика бинарей, антивируса, и фаз луны…
То бизнес быстренько выкинет все хакерство и сделает стандартными железобетонными методами.
увы, последние лет 10 бизнес в таких ситуациях призывает далёкого от программирования человека и говорит: «ну сделай что-нибудь с этим, короче, через 15 минут эта штуковина должна зработать», и так рождается очередной костыль/патч/скрипт по перезапуску для какого-нибудь нестабильного продукта.
Возможно, но вот именно эту хрень APE так не исправишь.
Тут любое изменение надо тестировать на огромном числе платформ.
Еще одна причина этим APE не заниматься :)
Допустим, проблема наблюдается при запуске такого приложения на линукс. Устанавливаем Wine, запускаем приложение через него. После пару десятков segfault и подбора параметров запуска добиваемся того, что программа выполняет нужную нам задачу и сваливается с какой-нибудь ошибкой после. Объясняем начальству, что программа задачу выполняет, а появление окошка «приложение будет закрыто» — вам надо, чтобы задачу делало или чтоб красиво было?

Ведь так всё и будет. Точно кто-нибудь, да попадёт на такое.
Господи, вы из какого года пишите? :) Какие пользователи, далёкие от компьютера, качают исполняемые файлы для десктопа с сайтов в 2021 году? Какие сайты в 2021 году не могут понять с какой ОС и архитектуры зашёл на них пользователь?

Если не секрет, то как у вас алгоритм действий, если вы узнали о какой-то программе и хотите её установить? И на какой ОС?

Он заходит на сайт программы и нажимает кнопочку download. То, что скачалось — запускает. Если нет сайта, скачалось не то, или нет вообще кнопочки, то программа негодная, надо найти годную.

Но это же не бинарник под всё кроме винды и bare metal получается. Это скрипт, сродни .run файлу, со всеми приколами (зависимость от шелла, ограничения на suid, ...). Похожего эффекта можно добиться, сделав .cmd файл с shebang'ом, который извлекает из себя платформо-зависимую версию java и .jar файл. Да, получится сильно большой размер, зато гораздо меньше костылей. Ну и да, на bare metal не запустится, но давайте быть честными: кому нужен бинарник, который работает на bare metal и в оси? Вернее, зачем внутри оси нужен бинарник, который написан под bare metal?


Я не отрицаю, что это прикольно и познавательно, но практический смысл немного ускользает. Кажется, самая интересная часть была бы в переходниках между системными вызовами и библиотеками, но её, ятп, нет. А если это делать, есть шанс написать очередную джаву/.нет/...

Необходимости извлекать jar-файл нет.
JAR-батник с call java %0 %* отработает и без извлечения. При наличии java в путях, конечно же.

Похоже, это единственная область применения данной технологии.

Почему статья не помечена как перевод?

А адвокаты Oracle не засудят камрадку за стибренный слоган? ;-)

каким образом redbean работает с сокетами?

https://github.com/jart/cosmopolitan/blob/0.3/libc/sock


ssize_t sendto(int fd, const void *buf, size_t size, uint32_t flags,
               const void *opt_addr, uint32_t addrsize) {
  assert(sizeof(struct sockaddr_in) == sizeof(struct sockaddr_in_bsd));
  if (!IsWindows()) {
    if (!IsBsd() || !opt_addr) {
      return sys_sendto(fd, buf, size, flags, opt_addr, addrsize);
    } else {
      struct sockaddr_in_bsd addr2;
      if (addrsize != sizeof(addr2)) return einval();
      memcpy(&addr2, opt_addr, sizeof(struct sockaddr_in));
      sockaddr2bsd(&addr2);
      return sys_sendto(fd, buf, size, flags, &addr2, addrsize);
    }
  } else if (__isfdkind(fd, kFdSocket)) {
    return sys_sendto_nt(&g_fds.p[fd], (struct iovec[]){{buf, size}}, 1, flags,
                         opt_addr, addrsize);
  } else {
    return ebadf();
  }
}
Sign up to leave a comment.