Комментарии 63
"Мы не знаем как должно быть но вы делаете неправильно"?
В х86 процессорах есть stack engine, который позволяет эффективно выполнять последовательности push/pop.
Насколько это эффективно по сравнению с регистрами? Как я понял, stack engine генерирует адреса, а load/store никуда не исчезают?
Что делать если параметров больше, чем регистров выделенных в ABI для передачи параметров?
Да что там, фон неймановская архитектура — это анахронизм 40х.
Допустим параметры идут через регистры и так, а куда локальные переменные девать? Регистров же не напасешся? Компайлер и так по возможности старается регистры использовать и под локальные переменные, но на все не хватит.
Плюс как трейсить вызовы без стека? Рекурсия под запретом? Хвостовую не все умеют.
Кучу использовать вместо стека тоже не получиться, там другие проблемы.
Чего сразу ущербен то?
Я попытался такое запустить, и оно ничерта не работает. Это точно портабельный исполняемый файл? aarch64? Вы уверены?
Тема совершенно не раскрыта. Осталось куча вопросов после прочтения статьи. Насколько мне известно, в общем случае задача создания кроссплатформенного бинарника не имеет решения. Хотелось бы увидеть теоретические рассуждения, каким образом они решили эту задачу или с какими оговроками.
Нет, всё ещё непонятно.
Спасибо за привлечение внимания к проекту, но тема заслуживает нормальной статьи! Основные вопросы:
- Там кросс-компиляция в неявном виде (для каждой платформы на самом деле запускается свой код) или идет трансляция всех системных вызовов? Если второе, то какие ограничения.
- Что с подключением внешних библиотек (статических, понятно, что магии не бывает)?
- Как возможна заявленная работа на разных архитектурных с разной системой команд (x86/64, ARM)?
- Работа на bare-metal какие ограничения накладывает? Работает ли оно как IncludeOS или ее (или аналог) можно/нужно подключить? Что в этом случаи с драйверами (ФС, сеть)? Можно ли без MMU?
- На оригинальной странице написано, что не предназначено для программ с GUI (что оправданно), но есть же кроссплатформенные GUI библиотеки, реально ли что-то подружить?
Думается мне, что это — просто способ эмулировать PE формат файла как sh скрипт. То есть будет одинаково исполняемым на Windows и Linux. Но вот расставлять набор ифов даже начальных для разных архитектур — не предлагается.
Просто отличная задача. Чем-то напоминает задачу о квайнах. Сложно, интересно, но на практике — довольно бесполезно.
Я пока серьезно не вгрызался в проект и вообще ненастоящий сварщик, но вот что я откопал за 30 минут (плюс инфа из треда на HN):
"Не миллион а тысячу, не в казино а в карты, не выиграл а проиграл". Ни то, ни другое.
Её изобретение — способ эмулировать 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).
Как я понял, особых ограничений нет, статическая линковка она и Африке статическая линковка, но все библиотеки (если делают системные вызовы) должны быть специальным образом перекомпилированы чтобы быть совместимыми с этим чудо-форматом. И да, либо x86-64 Linux ABI, либо специальная прослойка типа той что для WinAPI.
Ученый изнасиловал журналиста. x86-64 only, плюс есть способ внедрить qemu в бинарник и эмулировать, эмулировать, эмулировать! Написано что выходит вроде как шустро, но черт его знает, сомнения гложут меня.
Смотрите пункты 1 и 3. Ваш камень x86-64 и система System V Compatible? Поздравляю, вы
знаете толк в baremetalв деле! Нет? Для вас есть вариант с qemu, выводы о драйверах и MMU делайте сами.
Вроде как пишут что умеет в BIOS, то есть можно использовать бинарник в качестве загрузчика на IBM PC.
Смотрите пункты 1 и 2. Не думаю что кто-то будет тратить на это время.
В воскресенье покопаюсь в этом wunderwaffe основательнее, но то, что я пока увидел, мягко говоря обламывает ожидания. С одной стороны, поделка вроде интересная и даже ограниченно полезная, но с другой — уровень хайпа и дифирамб вокруг этой штуки вот вообще никак не оправдан ИМХО. И ведь в ридми у них значится гордое "like Java", сеошники чертовы (ушел возмущаться несовершенством мира в целом и программирования в частности)...
Вообще, статья больше вводит в заблуждение, чем объясняет. Сначала в ней говорится, что 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 байт в зависимости от конкретной архитектуры).
Согласен, зависит от архитектуры и надо смотреть, но что-то мне кажется, что все равно преимущества не будет — читать/писать блоками больше байта можно и в однопроходном варианте.
По байту искать конец строки это прошлый век — на самом деле блоками по 8 байт.
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 байт из памяти в регистр быстрее чем читать побайтно.
w = ((uint64_t*)(p))[0];
Но почему разработчики не написали именно так?
А алгоритм сводится к битовой магии add, not, add, add.
Но почему разработчики не написали именно так?
Потому что оно должно работать при любом порядке байтов.
#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.
Найдет применение разве что в кросплатформенной малвари.
И то врядли надолго. Антивирусы добавят в свои БД сигнатуры.
Другое дело, что потребуется очень хитрая процедура, выясняющая текущую платформу.
Малое количество ошибок можно обработать через техподдержку или страницу ручного выбора, что скачать.
Вот когда эта софтина заглючит и крешнется из-за хитросплетений работы загрузчика бинарей, антивируса, и фаз луны…
То бизнес быстренько выкинет все хакерство и сделает стандартными железобетонными методами.
Тут любое изменение надо тестировать на огромном числе платформ.
Еще одна причина этим APE не заниматься :)
Ведь так всё и будет. Точно кто-нибудь, да попадёт на такое.
Если не секрет, то как у вас алгоритм действий, если вы узнали о какой-то программе и хотите её установить? И на какой ОС?
Но это же не бинарник под всё кроме винды и bare metal получается. Это скрипт, сродни .run файлу, со всеми приколами (зависимость от шелла, ограничения на suid, ...). Похожего эффекта можно добиться, сделав .cmd файл с shebang'ом, который извлекает из себя платформо-зависимую версию java и .jar файл. Да, получится сильно большой размер, зато гораздо меньше костылей. Ну и да, на bare metal не запустится, но давайте быть честными: кому нужен бинарник, который работает на bare metal и в оси? Вернее, зачем внутри оси нужен бинарник, который написан под bare metal?
Я не отрицаю, что это прикольно и познавательно, но практический смысл немного ускользает. Кажется, самая интересная часть была бы в переходниках между системными вызовами и библиотеками, но её, ятп, нет. А если это делать, есть шанс написать очередную джаву/.нет/...
Почему статья не помечена как перевод?
А адвокаты Oracle не засудят камрадку за стибренный слоган? ;-)
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();
}
}
Один бинарник, любое окружение. Магия чистого C