Это видео недоступно.
Сожалеем об этом.
Зачем нужен ассемблер на примере. Соглашения о вызовах в разных архитектурах
HTML-код
- Опубликовано: 19 янв 2024
- Зачем нужен ассемблер. Соглашения о вызовах для разных архитектур.
Видео-ответ на комментарии к ролику с решением задачи, решение которой так никто и не предложил (без использования ассемблера, в т.ч., ассемблерной вставки в программу на C, задачу не решить): • Блок управления двигат...
Специфичные задачи, которые можно решить с использованием ассемблера. Соглашения о вызовах для архитектур: x86, x64, arm, mcs-51
В видео решается задача:
Как вызвать из языка более высокого уровня подпрограмму из встроенного начального загрузчика, расположенного во внутреннем ПЗУ микроконтроллера, с передачей аргумента, не прибегая к языку низкого уровня (ассемблеру)? Причем, подпрограмма (функция) в этом встроенном загрузчике не соответствует соглашению о вызовах функций (регистры, стек, возвращаемое значение и т.п., если понимаете, о чем я).
Внимание! Уточнение!
9:45 Обратите внимание, что компилятор сгенерировал инструкцию LJMP (переход по абсолютному 16-битному адресу), а не инструкцию LCALL (вызов подпрограммы по абсолютному 16-битному адресу). Почему он так сделал? А потому что программа состояла всего из двух инструкций, оканчивающихся именно вызовом подпрограммы. Если бы после вызова нашей подпрограммы следовали бы еще инструкции, то компилятор сгенерировал бы для вызова подпрограммы инструкцию LCALL. При этом, генерируя инструкцию LJMP, компилятор подразумевает, что выход из основной программы будет осуществлен по завершению подпрограммы по инструкции RET (возврат из подпрограммы). На 12:43 я немного накосячил, "под впечатлением от этой инструкции", сгенерированной компилятором, поэтому в своей вставке на ассемблере также автоматически указал инструкцию LJMP. Однако, если речь идет именно о вызове подпрограммы по адресу, а не переходе по адресу, то, как вы понимаете, инструкция должна быть LCALL.
Еще один важный и имеющий непосредственное отношение к теме момент. Вызов внешних подпрограмм "по классике" для языка С-51 предлагается делать по такой "схеме":
typedef void my_func(unsigned char);
my_func* send_byte = (my_func*)SEND_BYTE_SP0;
send_byte(65);
Т.е. объявляется тип, затем объявляется переменная-указатель данного типа с инициализацией в ней адреса подпрограммы. И в заключении осуществляется вызов подпрограммы. Прекрасно, но посмотрим, что выдаст на выходе компилятор... Остальное читайте в закрепленном комментарии, т.к. ютуб в описании делает ненужные хэштеги из констант ассемблера.
#ассемблер
#микроконтроллер
#программирование
Внимание! Уточнение!
9:45 Обратите внимание, что компилятор сгенерировал инструкцию LJMP (переход по абсолютному 16-битному адресу), а не инструкцию LCALL (вызов подпрограммы по абсолютному 16-битному адресу). Почему он так сделал? А потому что программа состояла всего из двух инструкций, оканчивающихся именно вызовом подпрограммы. Если бы после вызова нашей подпрограммы следовали бы еще инструкции, то компилятор сгенерировал бы для вызова подпрограммы инструкцию LCALL. При этом, генерируя инструкцию LJMP, компилятор подразумевает, что выход из основной программы будет осуществлен по завершению подпрограммы по инструкции RET (возврат из подпрограммы). На 12:43 я немного накосячил, "под впечатлением от этой инструкции", сгенерированной компилятором, поэтому в своей вставке на ассемблере также автоматически указал инструкцию LJMP. Однако, если речь идет именно о вызове подпрограммы по адресу, а не переходе по адресу, то, как вы понимаете, инструкция должна быть LCALL.
Еще один важный и имеющий непосредственное отношение к теме момент. Вызов внешних подпрограмм "по классике" для языка С-51 предлагается делать по такой "схеме":
typedef void my_func(unsigned char);
my_func* send_byte = (my_func*)SEND_BYTE_SP0;
send_byte(65);
Т.е. объявляется тип, затем объявляется переменная-указатель данного типа с инициализацией в ней адреса подпрограммы. И в заключении осуществляется вызов подпрограммы. Прекрасно, но посмотрим, что выдаст на выходе компилятор:
MOV R7,#0x0E
MOV R6,#0x00
MOV DPL(0x82),R7
MOV DPH(0x83),R6
MOV R7,#0x41
LCALL C?ICALL2
RET
C?ICALL2:
CLR A
JMP @A+DPTR
Вот это простыня... Да, еще и компилятор намутил. Вместо этого "безобразия":
MOV R7,#0x0E
MOV R6,#0x00
MOV DPL(0x82),R7
MOV DPH(0x83),R6
Просится вот это, что короче на 2 байта и на 2 такта быстрее:
MOV DPL(0x82),#0x0E
MOV DPH(0x83),#0x00
А что он вообще делает этот код? В младший и старший разряды регистра-указателя адреса DPTR последовательно пересылаются младший и старший разряды адреса подпрограммы. Затем в регистр R7 переносится аргумент для вызываемой подпрограммы. Осуществляется вызов подпрограммы по адресу C?ICALL2, в которой очищается регистр-аккумулятор A и производится безусловный косвенный относительный переход по адресу, который образуется путем сложения содержимого регистра-аккумулятора A со значением в 16-битном регистре-указателе адреса DPTR, т.е. на ту самую нашу подпрограмму. Получается, что этот код делает в 8 строчках и 15 байтах тоже самое, что в первом варианте в 2 строчках и 5 байтах. Т.е. наш первый вариант круче. Вот поэтому, в т.ч., и стоит быть немного знакомым с ассемблером, чтобы уметь делать полезные "трюки". 🙂
В видео пытаемся решить задачу:
Как вызвать из языка более высокого уровня подпрограмму из встроенного начального загрузчика, расположенного во внутреннем ПЗУ микроконтроллера, с передачей аргумента, не прибегая к языку низкого уровня (ассемблеру)? Причем, подпрограмма (функция) в этом встроенном загрузчике не соответствует соглашению о вызовах функций (регистры, стек, возвращаемое значение и т.п., если понимаете, о чем я).
Ничего не понятно, но очень интересно )
Это прекрасно❤
Спасибо 👍
Требую БОЛЬШЕ подробностей и нюансов !!!!!! Больше БОЛЬШШШЕЕЕ ВОДЫЫЫЫЫ )))))
Про архитектуру и соглашения . Разницу в архитектурах нюансы архитектур .Какие (конкретно) доп материалы по тематике можно посмотреть, с указанием на что стоит обратить внимание и почему.
@@alexshu1609 , это трехтомник, как минимум. 🤣
@@VadRov Есть Краснодарский Мединцев так он вас уже рекламирует маненько в телеге . Трех томный контент - больше подписоты. Можно рекламу - $$$. Мотивирует ? Книжки пишите Курсы открывайте. НУ нет денег или возможностей у пипла на институт, и образование нынче - денег на это дело у государства (на образование
Дружище делитесь своим опытом . То что для вас является само собой - для других может быть открытием вселенной ( соглашение о вызовах ). Нынче образование под гнетом капитализьма, кто то на пенсию (реальные преподы) кто то в бизнес (Мединцев). А у вас хватает смелости пилить контент. Обращать внимание зрителя на очевидные для вас, но не очевидные для не посвященных, стороны программирования. Есть шутка " Даешь 10 часов по программированию на С для микроконтроллеров". Но в каждой шутке лишь доля шутки.
@@VadRov Есть такой историк из Харькова Bushwaker так он стримы по истории ведет следующим образом при описании какого либо момента переключается на страницу с картинкой в браузере описывающей кусок ситуации. Блин было бы здорово если бы вы пояснения подкрепляли поГуглиным материалом в виде картинок или куска из даташита , поскольку это дает доп инфу , улучшает запоминание материала благодаря визуальной составляющей на которую мозг реагирует более живо-активно. И если вдруг.... не стоит упоминать комментарий и(или) комментатора (испанский стыд), тем более агриться на комментатора с его комментом ( но стоит использовать его как мотивацию для создания контента ) .
"Люблю-целую. Зафод." (с) )))))
Код ассемблера намного меньше затрагивает ресурсы железа, надежен как АК и всякие левые прерывания не случаются при исполнении. В противовес: пишем ВСЕ ручками.
роботает в миллион раз быстрее ржавых питонов и весит килобайты а не гигабайты
Спасибо. Полезно.
Бывает ощущение, что на асме программы понятнее и проще, чем на том же С :)
Это отличное ощущение. И должен заметить, оно меня часто посещает. Есть функции, которые рука так и тянется написать на ASM. 🙂 Но это ощущение быстро исчезает когда сталкиваешься со сложными или громоздкими математическими вычислениями. Это, конечно, не для слабонервных. Поэтому в таких случаях только С. 😂
Это только пока лампочкой помигать.
@@user-fd7fj4ii8g , на мой взгляд, с периферией на ассемблере по уровню сложности практически также работать, что и на регистрах (CMSIS). Подход одинаковый. Записываем в регистр процессора адрес нужного регистра периферии читаем/модифицируем/записываем (Upd. так правильнее). А можно вообще макросы написать на манеру CMSIS.
Не кто не спорит асм знать это хорошо, сам когда то писал подпрограммы которые вызывались из основной программы на "С" для AVRок. Сейчас для отладки STM бывает заглядываю, что там компилятор наворотил .Наверное для автомобильного блока управления на асме писать сам бог велел. Там, как я понимаю, низкоуровневый ногодрыг, датчики, форсунки и т.д. А вот допустим написать программу где есть пользовательский интерфейс, сложные структуры данных, разветвленная логика, математика, ну в принципе можно и на асме, на спор наверное.
Все правильно пишите. Я сам часто смотрю, что на выходе компилятора получается. Знать, в принципе, достаточно на уровне знакомства. Неспроста же при отладке нам показывают листинг дизассемблера.
Здравствуйте, а можете пожалуйста дать, для молодых, список литературы по которой можно дорасти до вашего уровня понимания. В частности: где можно подробнее почитать о "соглашении о вызовах" и впринцепе литературу по асемблерам (х86, ARM). И может какую другую литературу, на ваш взгляд. Или, лично Я бы с большим удовольствием посмотрел видео где вы расскажете про литературу и дадите свои комментарии на её счёт.
Заранее спасибо! Ждём новых видео!
Здравствуйте. По x86 ничего посоветовать не могу, потому что малознаком (да, фактически не знаком) с ядрами для встраиваемых решений на базе этой архитектуры. Знакомился, разве что, с Intel Quark, где просто бегло пробежался по информации в сети. Программированием на языке ассемблера под эту архитектуру баловался в "лохматые 90-е", будучи студентом. Тогда это было на уровне встраиваемого ассемблера в программу на С. Ну, типа того, что мы, увлекающиеся немного программированием (в группе было несколько "отъявленных спектрумистов" с опытом программирования на ассемблере z-80), выпендривались друг перед другом и перед преподавателем по вычислительной технике и программированию... Вообще, не важно какой ассемблер изучать. Изучив один, легко перейти на другой. Главное, это базовая подготовка: понимание булевой алгебры, понимание видов операндов и видов их адресации.
Уже несколько раз писал в комментариях, что стремлюсь черпать информацию из первоисточников, т.е.: reference manual, programming manual, ISA and instruction set, Programmer’s Guide и т.п. на конкретное ядро м/к и его архитектуру. Более подробной информации просто не существует, потому что все книги - это "скомпилированные" выдержки из первоисточников. По архитектуре и ассемблеру Cortex-Mx (ARMv6-M, ARMv7-M, ARMv7E-M) для встраиваемых решений можно почитать неплохую книгу с иллюстрациями и альбомами:
Практический курс микропроцессорной техники на базе процессорных ядер ARM-Cortex-M3/M4/M4F [электронный ресурс]: учебное пособие - электрон. текстовые дан. (12 Мб) / В.Ф. Козаченко, А.С. Анучин, Д. И. Алямкин и др.; под общ. ред. В.Ф. Козаченко. - М.: Издательство МЭИ, 2019.
По названию можно найти и скачать книгу. Это бесплатно. Книга подойдет как начинающему (с самых азов), так и профессионалу (в качестве наглядной памятки по системе команд). А вообще, по архитектурам ARM просто море информации с примерами на одноименном сайте (сайте компании).
Про соглашения о вызовах для разных архитектур можно найти информацию, в т.ч., на русском языке, на сайте microsoft. Про соглашения о вызовах для различных архитектур ARM можно найти информацию на сайте компании.
Ну, а вообще ассемблер изучать немодно, непрактично, бесполезно... Дополните список 🙂
В Си для управления соглащениями есть ключевые слова __cdecl, __stdcall и __fastcall
Какой используется в кеил по умолчанию и можно ли его менять надо смотреть в настройках проекта. В иар точно была настройка.
Тут наверное какой-то registered по умолчанию стоит, вот и передал он параметр через регистр.
Да, все правильно, но с одним условием. Было сказано, что в зависимости от архитектуры может быть несколько соглашений о вызовах. Модификаторы, перечисленные Вами, компилятор может просто игнорировать (если вообще пропустит, а не выдаст ошибку), например, в arm архитектуре, где только один предписанный способ передачи аргументов. Т.е. вышеуказанные модификаторы подойдут для архитектуры x86. Для архитектуры MCS-51, что на видео, также предусмотрено безвариантное соглашение о вызовах, и о нем также сказано в видео.
Upd. Забыл уточнить про "Тут наверное какой-то registered по умолчанию стоит, вот и передал он параметр через регистр.". Все правильно, нам и надо передать параметр через регистр. Но проблема в том, что он передается не через тот регистр, который нам необходимо (подпрограмма "ждет" параметр в другом регистре). Параметр требуется передать через регистр A, но он в соответствии с соглашением о вызовах передается компилятором в регистре R7. И этот порядок мы можем изменить только "вручную" путем программы на ассемблере, а равно, ассемблерной вставкой. 🙂
@@VadRov ну откровенно для встраиваемых систем соглашение о вызовах в 99% избыточно. Это ж по факту была стандартизация что б весь зоопарк вызов привести в порядок для различных библиотек.
А тут обычно ж всё пишут в одном варианте - взяли кеил и пишут всё в нём или взяли иар и пишут всё в нём. А если уж идёт смешивание си и асм, то обычно асм подгоняют под компилятор.
Хорошо что в АРМ сразу всё обговорили и все работают в одном стандарте, но GCC поддерживает различные соглашения и ничто не мешает принудительно использовать другое соглашение. Правда не могу представить себе варианты когда это может потребоваться.
@@VadRov кстати, вот из Лабораторные ЦУиМП / Лабораторные / Keil1.pdf
Передача аргументов через регистры общего назначения микроконтроллера считается в настоящее время наиболее эффективной и быстрой. Аналогично возврату результата, аргументы обычно передаются в регистрах R0…R7 текущего банка. При этом однобайтовый результат передается в R7, двухбайтовый - в R6, R7 и т.д. Для передачи адресов (указатели) используются регистры R0, R1. При этом, в качестве локальных переменных процедур, используются свободные (и освобождающиеся) от аргументов регистры.
так что может кеил и прав что в си через регистры рулит... надо копать компилятор и его особенности короче :)
@@WWolf_13 , да, я вообще согласен.🙂 И стандарты - это хорошо, чтобы можно было в рамках одной архитектуры писать исходный код на любых языках, связывать эти независимые модули, и чтобы работало это все в связке и "без танцев с бубном". Вот поэтому и показал в данном видео, что ассемблер все-таки порой и может пригодиться для "сглаживания острых углов". Вот и весь посыл видео. А вообще я за С/С++, если что. 🙂
@@WWolf_13 , у меня сегодня ютуб тупить с комментариями.... в час по чайной ложке, подвисает, короче. Именно об этом я и говорю в видео (что через регистры - это по поводу ЦУиМП, доки по архитектуре были изучены для уверенного использования). Почитайте мой комментарий выше. Я его обновил (Upd.)
Так их этих высокоуровневых нублоидов! Не щадить и открывать глаза "умникам". Они видимо уже давно возомнили себя гуру, но позабыв выучить азы! )
Можно же просто обертку написать😊
Все можно, только осторожно. 🙂Когда надо передать параметры подпрограмме в определенных регистрах, в разрез Соглашения, то без ассемблера (ассемблерной вставки в код на С) возникают проблемы.
Однако регистры не сохранил в стек , перед вызовам, своей функции, пиши пропало!))
Подпрограмма "не портит" регистры. Я ее дизассемблировал.🙂
И опять... здесь работа в "один конец". Если передавать строку, то вызов должен быть в цикле через LCALL SEND_BYTE_SP0 Но это не так важно для примера перехода по заданному адресу с передачей аргумента. И сохранять регистры в стек для 8051 считается признаком "дурного тона", т.к. размер стека весьма ограничен. Он может быть только во встроенной памяти (регистр стека восьмиразрядный). Здесь четыре набора регистров для R0-R7. Стек рекомендуется использовать в основном для записи адресов возврата из подпрограмм и сохранения значений регистра аккумулятора. Параметры подпрограммам, если "не хватает регистров" даже не через стек передаются, а как переменные в памяти. Во как... Ограничения архитектуры 🙂
как тогда они freetoc организовали, интересно, там постоянно надо по флеш выходит забивать предедышие вычисления, прыгая с программы на програму. @@VadRov
можно через пайтон )
просто ты не ас )
+