Pull to refresh

Comments 47

Раз уж в конце дошли до сисколлов, надо было с ядра и начать - fork (точнее clone)/exec для создания нового процесса и попытки выполнить наш бинарник, загрузка ядром ни разу не нашего бинарника, а ld-linux.so, который грузит уже и сам бинарник, и нужные динамические библиотеки с настройкой связей, и только после этого переход на стартовый адрес.

Про работу с эльфийским заголовком тоже было бы не плохо подробнее рассказать в контексте минимально необходимой информации для загрузки и выполнения приложения в оперативной памяти.

Про LD была отдельная статья, про ELF наверняка тоже.

нда...

такая анатомия это не привлекательные наружные половые органы

это кишки и кости, разбросанные по операционному столу

Вот-вот. Было ожидание прочитать про crt0, про отличие Position-Independent Executable file от более простого способа (и считающимся небезопасным из-за более простого взлома). Про секции бинарника - тоже ни слова.

Тем не менее, неплохо показано, что за простейшим принтфом стоит просто адова куча работы программистов всех времен и народов. И это автор еще не прошелся по железу.

всё так, аналогия верная

Здорово, спасибо!

Кстати, насколько помню, в 80-е в RSX11 на PDP11 был двухступенчатый компилятор, которой на первом этапе выдавал ассемблерный код.

на PC со времён TurboC есть ключики компилятора, которые будут выдавать ассемблерный код. В случае с gcc это ключик -S (и ещё конечно можно и нужно отладочный -g и -fverbose=asm )

Спасибо, про gcc я подзабыл!

Ну так и сейчас тоже самое, попробуйте спрятать /usr/bin/as:

$ gcc hello.c
gcc: fatal error: cannot execute ‘as’: execvp: No such file or directory
compilation terminated.

Спасибо, про gcc я подзабыл!

Все хорошо. И в целом не новость. Едиственный вопрос, который меня всегда слегка заботит, это почему printf? Разве канонический HelloWorld на C не должен быть таким

#include <unistd.h>

char msg[] = "Hello, world!\n";

int main(void)
{
	write(STDOUT_FILENO, msg, sizeof(msg));
	return 0;
}

P.S.

И не надо мне говорить, что результат, возвращаемый write в целом надо бы проверять. В исходном варианте возвращаемое printf тоже игнорируется.

Я не спорю с метрами. Я спрашиваю мнение общественности. Особенно той ее части, что работает преимущественно с языком С и спускается к ассемблеру по мере необходимости.

Тем более, что это далеко не идеал, если копнуть поглубже. Например const перед char в общем случае был бы весьма уместен.

Что может быть каноничнее K&R? Причем там даже так (во втором издании, которое типа под ANSI стандарт):

#include <stdio.h>

main()

{

printf("hello, world\n");

}

Никаких return и типов у main, какой уж там const...

Так, собственно, отсюда и вопрос... Дело ведь в том, что язык С изначально пытался занять очень много стульев сразу и претендовал на роль панацеи. Мы не будем тыкать в современные языки, которые ведут себя ровно так же - это проходит со временем. Вот и С довольно быстро разделился.

"Кроссплатформенный ассемблер с элементами структурного синтаксиса" (с) - это современный язык С. Все остальные его реинкарнации потихоньку сходят на нет, уступая место более удобным инструментам. И POSIX в таком варианте наше все. А это read/write, а еще atoi/atof и парные им ftoa/ftol.

"Переносимый между платформами язык С" - это безусловно K&R. В таком варианте безусловно printf/scanf, с соответствующими слоями абстракции. Ну и понятно, что в конце-концов именно "Переносимы С" обрел потомка виде С++ и сильно сдал свои позиции.

Вот отсюда и вопрос. Правда ли, что K&R с их "переносимым С" стоит сегодня рассматривать как канонический вариант использования языка. Да, безусловно, если еще комплект system-utils, sed, find, gawk и прочего прикладного софта нижнего уровня, который написан на таком С. Но все же, современный С это сильно больше язык загрузчиков, ядер операционных систем и драйверов. Другими словами тот самый "кроссплатформенный ассемблер". Или это у меня профдеформация и на самом деле все не так?

Я не думаю, что бывает какой-то "канонический вариант использования языка" в принципе, потому что С для прошивок и С для прикладных программ - очень разные языки, и тут в статье показан второй как раз. Первый при этом никуда не девается, и со вторым если и пересекается, то не очень сильно.

Буквально недавно объяснял интернам важность правильного указания выравнивания у структур и опасность добавления новых переменных в середину этих самых структур, являющихся частью XNU ABI, и понял, что первому языку не учат нигде, кроме как на работе, а с привычками, сформированными вторым, потом приходится бороться годами.

Или другой пример: Intel в свое время решили, что вместо стандартных memcpy и memset у них в EFI будут gBS->CopyMem и gBS->SetMem, а стандартной библиотеки не будет совсем. И все бы ничего, только вот компиляторы по стандарту языка С99 имею право заменять присваивания структурных переменных на вызов memcpy, и получается полная херня, когда тебе линкер сообщает, что линковать не будет, потому что у тебя нет memcpy, а у тебя в коде его и нет нигде, иди ищи присваивание, удачи.

первому языку не учат нигде, кроме как на работе, а с привычками, сформированными вторым, потом приходится бороться годами

Я вас категорически приветствую!

Да, черт возьми. Собственно по этой причине я и открыл эту ветку комментариев. С наивной надеждой хоть что-то поменять. Только вот... Конструктивный диалог получается разве что со старыми знакомыми. Понятно, что определение "канонический вариант" оно лукаво и не описывает суть проблемы. Но я не знаю как переформулировать более грамотно.

Радует, что понимание того, что язык С разный для разных задач есть не только у меня. Обидно, что нас крайне мало.

И я вас, Алексей.

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

Если вы хотите что-то реально поменять, пишите не комментарии, а статьи, такого же примерно формата. "Вот так выглядит Hello World для какой-нибудь умной пуговицы на 8051", или "пишем на С прошивку для самодельного паяльника Т12", или "какой-нибудь промышленный 1Ггц\16к осциллограф за миллиард управляется древним Intel Atom или AMD Geode LX, ковыряем установленную на нем Windows XP" и т.п.

Ничего нельзя сделать, задавая вопросы в комментариях, если мы хотим, чтобы молодежь училась и интересовалась, надо её учить и интересовать. Я тоже пойду учить и интересовать, когда уволюсь из корпорации. Мой бывший начальник Xeno Kovah уже так и сделал в 2020 году, и теперь делится знаниями на OpenSecurityTraining2, про который я тут в комментариях тоже без конца повторяю, как попугай.

Если вы хотите что-то реально поменять, пишите не комментарии, а статьи

Да, именно тут и есть замкнутый круг. Я могу рассказывать на работе молодому поколению. Как правило в режиме ответов на вопрос "что это было?", "а почему так нельзя?" или "а что так можно было"? Тогда появляются структурные и функциональные схемы железок, тогда появляются тайминги и прочие радости нашего уровня. Я честно пытался хоть из чего-то похожего сделать статью. Получается или слишком узко и нишево, или очередной пример такого вот варианта.

Ну или да... Ржут надо мной коллеги-схемотехники. Говорят ты за столько лет уже лекцию "радиоэлектроника для программистов" можешь снимать и на youutube выкладывать. Она и начинающим схемотехникам полезной окажется. За три поколения воспитанников отточил... Может правда как-нить заняться... Тогда может быть текстовая версия и здесь появится. Но пока есть интересная работа и тупо не до того... И это, пожалуй, самое главное.

Вообще получается парадоксальная ситуация - есть потребность в специалистах, но никто не знает как именно их готовить. Ибо инструмент один, а навык его использования оттачивается только в процессе работы. Я даже не знаю с чем бы сравнить... Ну, возможно, MIDI-клавиатура (синтезатор) в музыкальной студии против концертного рояля в консерватории. Т.е. инструмент один, а подход (и, безусловно, результат) разный. При чем и тот и другой результат востребованы. И принципиально нет каких-то серьезных ограничений, по которым пианист-виртуоз не смог бы записать на синтезаторе партии всех инструментов, или музыкант-клавишник современной популярной группы не смог бы исполнить условного Моцарта в условной консерватории, но... Эти миру сосуществуют рядом практически не соприкасаясь. Лишь изредка случается миграция в ту или иную сторону.

Похоже что пока так и останется. Меня на работе учили, я учу и мои последователи учить будут. Может оно и правильно. Ну а здесь будем ловить минусы от тех, кто идет параллельным путем.

современный С это сильно больше язык загрузчиков, ядер операционных систем и драйверов

А на этом уровне и POSIX с его write нет, только прямая запись в порты или видеопамять. Согласен, что ниша C сейчас ближе к ядру и чтобы начать писать на нём новый прикладной софт нужны особые причины. Но legacy много, не только масштаба sed/find, но и, скажем, PostgreSQL или Nginx.

На этом уровне как раз и формируется POSIX. Собственно здесь же он и начинает использоваться. В частности и для абстракций высшего уровня. И не только для самого языка С.

Что же до PostgreSQL или Nginx, то.... Может быть Legacy, а может быть разумный выбор инструмента для оптимального решения конкретной задачи.

Все меняется. Кто сейчас помнит самый популярный некогда почтовик sendmail? Уж даже не спрашиваю кто им пользуется... Так что когда становится реально надо, legacy переписывается.

Что же до PostgreSQL или Nginx, то.... Может быть Legacy, а может быть разумный выбор инструмента для оптимального решения конкретной задачи. Все меняется. Кто сейчас помнит самый популярный некогда почтовик sendmail?

postfix тоже написан на C. Не на C++. И не на Rust

Причина скорее всего в отсутствии реальных, т.е. достаточно стабильных альтернатив для именно кроссплатформенной разработки.

Но за рамками кроссплатформы современный C как язык общего назначения даже вместе с POSIX это довольно сомнительное мероприятие, т.к. читабельность типового С кода вида:

namespace_classname_t result = namespace_classname_methodname(&object_nameb, param_A, param_B);

объективно ну очень сильно проигрывает типовому "не С" коду:

var result = object.methodname(paramA, paramB);

В С прям очень сильно не хватает разумной реализации C with Classes and Namespaces.

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

А современный POSIX он уже скорее даже вреден. Т.к. не только замер в развитии, да еще и регрессирует. К примеру пометил ucontext.h устаревшим, и не предложив альтернативы. Продвигает напрочь устаревшие fork() и system() вместо clone() и т.д. И вообще ничего не говоря про events, кроме select(), страшно далеки они там в комитете от народа.

Как там в POSIX будет про epoll(), kqueue(), IOCPs ? Cовсем никак? так уже лет 15 прошло, неужели до сих пор договориться не смогли?

А vectors/ArrayList/resizable arrays, duallinked lists с hashmaps, и прочие нормальные строки (которые size+pointer) в С и POSIX наверное уже никогда не дождемся, так и будем каждый свои самодельные велосипеды (в каждый проект свои, особо эффективные, на макросах) прикручивать до конца столетия?

Давайте так - каждое из утверждений имеет свои "за" и свои "против". В общем случае хочется думать, что если обозначенные проблемы реально являются проблемами они будут решены. Пусть и не в рамках языка С, который однажды окажется в учебниках по истории IT и редко где в реальной жизни.

А современный POSIX он уже скорее даже вреден.

И какую альтернативу предлагаете для общения с ядром в Линуксе и т.п.? Да, заметной части прикладного кода достаточно высокоуровневых фреймворков (но тогда и писать можно на каком нибудь go или Java), но иногда надо чётко знать, как и какие сисколлы вызываются.

И какую альтернативу предлагаете для общения с ядром в Линуксе и т.п.? 

К сожалению альтернативой POSIX и является сам Linux. Обеспечение совместимости только с относительно свежими Linux и только.

И тотальное забвение необходимости совместимости с MacOS, FreeBSD, Solaris, AIX, и кто там еще живой. Про Windows и говорить не стоит.

В противном случае POSIX совместимость является типичным чемоданом без ручки - затраты на поддержание регрессионных тестов это еще ладно (можно поднять ферму из виртуалок и гонять там через Jenkins автотесты), это просто потеря времени, а вот то что функционально приходится весьма серьезно самоограничиваться, к примеру в части clone() - это уже сильно хуже, это потеря функциональности.

И тотальное забвение необходимости совместимости с MacOS, FreeBSD, Solaris, AIX, и кто там еще живой. Про Windows и говорить не стоит.

Смело, модно, молодежно.

Вообще говоря, posix с самого начала был "чемоданом без ручки". Примерно как знаменитый автомат Калашникова. Каждому конкретному бойцу он неудобен - масса, габариты, кучность, убойная сила.... Но для каждого рода войск и прочих неармейских подразделений своя модификация. При сохранении базовых принципов и взаимозаменяемости основных частей.

В этом смысле Linux, как альтернатива Posix ничего кроме улыбки не вызывает. Posix не запрещает clone() и прочие так любые вашему сердцу моменты. А если единственная целевая аудитория это Linux - ну и славненько.

В этом смысле Linux, как альтернатива Posix ничего кроме улыбки не вызывает.

Причина улыбок? POSIX в мире Windows не значит ничего. Абсолютный ноль.

А в мире серверов что-то, кроме Linux - имеет смысл только в режиме доживания, когда перейти на Linux нет технической возможности.

Posix не запрещает clone() и прочие так любые вашему сердцу моменты.

В смысле не запрещает? Если ты используешь clone(), то автоматически становишься POSIX несовместимым. В Posix нет прямого функционального аналога clone(). Его нет во FreeBSD (есть rfork(), но это не совсем то), Solaris и т.д.

Подобные моменты это все равно что купить какой спорткар, и потом на себя самоограничений понавесиать - ездить не быстрее 30км/ч, чтоб телеги на конной тяге не обгонять, им обидно будет, поворачивать только направо, потому что таксистам тоже запретили, и окна не открывать - потому что у Петровича соседа из гаража ручка на двери сломалась.

Если заранее известно про ограничение в 30 км/час зачем покупать спорткар? Если есть задача поставить мировой рекорд скорости, то конейнеровоз не выглядит подходящим решением.

Уникальная задача всегда имеет уникальное решение. А типовое решение всегда неудобный ни для кого компромисс. Главное искусство инженера ровно в том, что бы не путать эти сущности.

Если заранее известно про ограничение в 30 км/час зачем покупать спорткар?

Так кроме спорткара ничего и не продают. И нет там никаких ограничений, на самом деле. Просто когда права выдавали, сказали, что вот тут нужно ездить 30км/ч, потому что все деды тут тоже веками так ездили. Но даже знак ограничительный не поставили.

Говоря проще - кто-то когда-то пришел и сказал, оно всё должно быть все POSIX совместимо, потому что дескать в мире существуют AIX, HP/UX, Solaris и даже OSF/1 (но это не точно). А на самом деле куда не посмотри - везде один Linux, Free/Net/OpenBSD похоже живут только в комментах на LORе, ну и еще какие-то странные люди иногда с макбуками мимо проходят, крутят в них Docker образы... да, опять на Linux-ах внутри.

И возникает вопрос - ну раз везде Linux, то зачем и кому вообще сейчас нужен POSIX? Товарищи из BSD мира (и macOS в т.ч.) вполне могут со временем самостоятельно все нужные linux specific APIs реализовать, гордости только чуть поубавить и дело пойдет. Насколько я помню в последних Solaris 11 был очень сильный дрейф в сторону glibc и не только, т.е. могут ведь, если захотят. Даже Microsoft смогла нужные API реализовать с WSL 1, жаль правда там это сдулось быстрее, чем стало практически применимым.

"Узок круг этих революционеров. Страшно далеки они от народа" (c) В. И. Ленин.

QNX (тоже сильно сложно, но все же POSIX система). Solaris (аналогично - условно POSIX) до сих пор актуальна. Похороненная и живущая только в комментариях на LORе BSD (туда же) основа для Playstion. Ах да, еще macOs корнями упирается в нее же. И это только то, что на слуху и вспоминается на счет раз.

У Linux есть GPL и надзорные органы, которые не очень эффективно, но следят за ее соблюдением. Потому про Linux знают. Можно долго спорить, но "вирусная лицензия" GPL сильно мешает вашему же "могут ... все нужные linux specific APIs реализовать". Тут дело не в гордости. Обратный процесс тоже не очень идет. ZFS портируется с трудом. И да, опять все упирается в "гордости только чуть поубавить". Хотя в ту же BSD давно забрали.

Безусловно, основной прогресс в Linux. Причем прогресс настолько огромный, что уже начинает попахивать цепной реакцией и не очень понятно найдется ли на нее контролирующий стержень. Впрочем, это уже тема отдельного разговора. И я не хочу его начинать.

QNX (тоже сильно сложно, но все же POSIX система).

Трудно сказать, ни разу ее в деле нигде даже на стенде не видел, только в статьях и книжках. Где она вообще применяется? В автомобилях на панелях можно увидеть Android, Windows (иногда, на старых авто особенно). А в микроконтроллеры она не влезет.

Solaris (аналогично - условно POSIX) до сих пор актуальна.

Актуальна в режиме доживания в корпоративе, где она в основном и применялась. Проекты по миграции на RHEL или на SLES уже много лет являются трендом. Даже сам Oracle активно форсирует свой OEL как замену. SPARC это уже больше экзотика, последнее что там было новационное - это нативная поддержка Oracle Database арифметики (вариации BCD NUMBERS), и то, Oracle проще будет с ARM договориться (туда все и идет).

Похороненная и живущая только в комментариях на LORе BSD (туда же) основа для Playstion. Ах да, еще macOs корнями упирается в нее же. 

Playstation это прям сильно нишевое. Игроделы ЕМНИП работают и вовсе на платформо-независимых абстракциях. А MacOS актуальна разве как запускалка AutoCAD и MS Office, которые почему-то до сих пор под Linux не портировали. Ну и Dockerа. Хотя безусловно есть любители разработки и чисто под MacOS, но POSIXом там явно не ограничивается, мягко говоря.

У Linux есть GPL и надзорные органы, которые не очень эффективно, но следят за ее соблюдением. Потому про Linux знают. Можно долго спорить, но "вирусная лицензия" GPL сильно мешает вашему же "могут ... все нужные linux specific APIs реализовать"

Насколько я помню даже Oracle не получилось Java APIs в суде отстоять, хотя денег туда в суды вбухали порядочно. А что, действительно ходят и пальчиком грозят, что использовать Linux APIs никак нельзя? Я как-то краем глаза видел, что во FreeBSD пытались нативно Linux ELFы запускать, кажется вот про это https://docs.freebsd.org/en/articles/linux-emulation/

Никто вроде не приходил трубу шатать, возможно я пропустил что-то.

Безусловно, основной прогресс в Linux. Причем прогресс настолько огромный, что уже начинает попахивать цепной реакцией и не очень понятно найдется ли на нее контролирующий стержень. Впрочем, это уже тема отдельного разговора. И я не хочу его начинать.

А что так? Личная боль в виде растраченных инвестиций в Solaris? Я вот сейчас уже даже и не припомню каких-то особо уникально полезных APIs в Solaris, аналогов которых в Linux не было (про обратное сказать не получится). Но они точно были, миграции проходили не в режиме перекомпилировали, подкинув врапперов и ifdefов и поехали.

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

С++ не заставляет в каждой строке кода, использовать все фишки всех стандартов. Выберите те конструкции которые вам нужны и используйте их. Не вижу проблемы с переразвитостью.

 С++ не заставляет в каждой строке кода, использовать все фишки. Не вижу проблемы с переразвитостью.

Так не прокатит. Даже ограниченный набор приводит к пережевыванию сотен тысяч строк заголовочных файлов с шаблонами, в которых наскладирован этот многовековой legacy, который радостно будет предложен для парсинга в тот-же clangd. И это на старте, с libstdc++. Плюс компиляция - да, pch помогает, но это тоже полумера (ибо все на шаблонах).

И модули? А какие такие модули? :)

А если пытаться еще какие третьесторонние библиотеки на C++ подключать, с Boost и прочими прелестями...

Да даже на простой autocomplete в VScode в C++ проектах смотреть очень грустно, он прям столько тебе "полезного" пытается предложить, задумчиво так.

Но выкинуть это лишнее никак нельзя. Даже initializer lists дефиницию оказывается нельзя взять и выкусить в свой строго отдельный заголовочный файл (чтоб не тянуть весь libstdc++) - там прям гвоздями прибито к libstdc++, что просто удивительно. Только копируешь дефиницию в свой отдельный .hpp файл с другим namespace - и оно сразу перестает работать магическим образом. Фантастика (хотя может и починили уже, но не уверен).

Потому, что системный вызов write не имеет отношения к языку C. А “Hello world” от Кернигана и Ричи — она именно про язык, частью которого является и стандартная библиотека. Потому и printf.

Спасибо. Хороший аргумент.

 она именно про язык, частью которого является и стандартная библиотека. 

Стандартная библиотека не является частью языка.

Хоть стандартная, хоть не стандартная, это всего лишь библиотека, и это очень хорошо, что в мире C и даже в мире C++ вполне можно выкинуть стандартную библиотеку на старте и использовать что-то принципиально иное. Причем это не случайность, это как раз базовые требования, иначе бы не была возможна кросскомпиляция для каких микроконтроллеров.

Всё оч просто: попробуйте скомпилировать ваш код в Windows, после чего поймёте почему printf)

Если опускаться до ассемблера, то я делал Hello World для разных архитектур. Заголовки исполняемых файлов тоже написаны на ассемблере.

Мы придумали такую систему размером с нейтрон ( Three-body problem )

А теперь представьте, тоже самое на js. console.log('Hello World!');
От там будет, марианская впадина:) Электрон...

В ноде можно посмотреть байт код, если добавить флаг "--print-bytecode":
node --print-bytecode --eval "console.log('Hello, World')"

Но причем тут электрон?)

Электрон позволяет делать десктопное приложение. Не просто сайт, а именно приложение.

Я к тому, что в статье же не рассматривается десктопное приложение на С, потому и привел пример с нодой. JS - это ж не только браузер и электрон.

Похожая статья, которую я когда-то переводил, если интересно ещё немного почитать на тему. В частности, немного про обработку в ядре.

https://habr.com/en/articles/438044/

Sign up to leave a comment.