Получение [закрытых] навыков программирования ассемблера

9
задан greatwolf 17 August 2013 в 20:14
поделиться

3 ответа

Хорошим местом для начала будет книга Джеффа Дантеманна, Assembly Language Step-by-Step. Книга посвящена программированию на x86 под Linux. Насколько я помню, в предыдущей версии книги рассматривалось программирование под Windows. Это книга для начинающих, так как она начинается с самого начала: биты, байты, двоичная арифметика и т.д. Вы можете пропустить эту часть, если хотите, но будет хорошей идеей хотя бы бегло ознакомиться с ней.

Я думаю, что лучший способ научиться кодировать ASM - это 1) изучить основы аппаратного обеспечения, а затем 2) изучить чужой код. Книга, которую я упомянул выше, стоящая. Вас также может заинтересовать The Art of Assembly Language Programming.

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

Тем не менее, я бы не стал отговаривать вас от стремления стать более эффективным с ASM. Более глубокое знакомство с работой процессора на этом уровне может только улучшить ваши навыки программирования на HLL.

6
ответ дан 4 December 2019 в 08:00
поделиться

Лучший способ - это скомпилировать материал C, посмотреть на выходные данные и найти документацию инструкций, которые вы не знаете. Опыт придет со временем...

0
ответ дан 4 December 2019 в 08:00
поделиться

Мой ответ в целом таков... напишите дизассемблер. Вы затронули ARM, возможно, вы знаете все инструкции ARM, возможно, нет, а как насчет thumb? ARM - хороший вариант для изучения этим методом, и популярный, и с фиксированной длиной инструкции, так что вы можете дизассемблировать линейно от начала до конца.

Я не имею в виду написать отполированный дизассемблер, достойный sourceforge, может быть, напишите 5 или 10 строк ассемблера за раз, максимум, может быть, одну и ту же инструкцию с разными регистрами, достаточно, чтобы разобрать двоичный файл с битовым деревом if-then-else или переключателями.

add r0,r0,#1
add r0,r1,#1
add r0,r2,#2

Ваша цель - изучить каждый бит в операционном коде, понять, почему вы можете иметь только 8-битные непосредственные значения, понять, почему некоторые процессоры позволяют вам прыгать только на 127 или 128 байт для локальной условной ветви. Для этого не обязательно писать дизассемблер, но для меня это работает, чтобы внедрить информацию в мой мозг.

Чтобы создать все возможные опкоды/инструкции для проверки дизассемблера, в итоге вы изучаете все синтаксические тонкости используемого вами ассемблера. Язык ассемблера в книге компаний-производителей микросхем не обязательно является точным синтаксисом, используемым каждым ассемблером для данного семейства процессоров. Инструкции mrc/mcr (ARM) являются хорошим примером этого. gas, в частности, известен тем, что он ужасно изменяет синтаксис, делая его более болезненным, чем синтаксис и инструменты компаний-производителей микросхем. Это зависит от того, что вы пытаетесь сделать, если вы просто хотите написать несколько строк или изменить что-то, вам не нужно знать все угловые случаи или особенности ассемблера, но если вы действительно хотите изучить набор инструкций, то я рекомендую такой подход.

Я также инженер по встроенному программному обеспечению, в основном использую C, но ежедневно дизассемблирую этот C (используя objdump, не мои инструменты), изучаю вывод, убеждаюсь, что этот код находится в этой области памяти, а этот код здесь, компоновщик. Но иногда мне приходится исследовать симуляцию процессора/чипа, и мне нужно проследить за обработкой инструкций и связанным с ними вводом/выводом, чтобы проследить за кодом через симуляцию. Или отлаживать плату с помощью логического анализатора на шине памяти или другой шине. Я изучил множество различных процессоров, 8, 16, 32, 64-битных (а также процессоры с длиной регистра, не входящие в этот список), cisc, risc, dsp и пару микродвигателей. Написал дизассемблер для каждого из них (ну, кроме pdp11 и x86, моих первых двух наборов инструкций). Изучение нового ISA занимает, возможно, полдня, как только вы увидите несколько из них. Нет, мне требуется день или два, чтобы перейти с языка, которым я пользовался ежедневно в течение нескольких дней/недель/месяцев, на тот, которым я не пользовался несколько месяцев/лет. Я не думаю на всех языках сразу.

Разборка инструкций переменной длины (большинство существующих процессоров), если делать это правильно, сама по себе является искусством и выходит далеко за рамки того, о чем я говорю, поэтому я рекомендую использовать только несколько инструкций за раз, не встраивая данные в эти инструкции. В идеале используйте этот метод, если у вас под рукой есть рабочий/хороший дизассемблер, чтобы вы могли сравнить свой результат с реальным, протестированным и отлаженным дизассемблером.

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

Любая среда обучения подходит вам лучше всего, будь то разборка, эмуляторы, одиночный шаг через симулятор ISA, основанный на gui, книги, веб-страницы. Изучение ассемблера для одного или многих процессоров определенно сделает ваше программирование на высоком уровне лучше. Даже если вы никогда не будете писать на ассемблере, а только изучать его. Напишите несколько кодов на Си, в которых используются массивы, указатели, структуры и без структур, циклы, разворачивающиеся циклы, скомпилируйте каждый из них с различными опциями компилятора, с включенным отладчиком и без него, без оптимизации, с максимальной/агрессивной оптимизацией. (компилируйте для разных процессоров и сравнивайте различия в потоке программы, количестве инструкций и т.д. llvm отлично подходит для этого).

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

Я рекомендую ознакомиться с совершенно разными семействами, в которых нет/меньше близкородственных связей, я упомянул ARM/thumb (и thumb2), которые определенно близкородственные, но популярные и будут платить по счетам, так что вы сможете изучать другие в свободное время. Вернитесь к 6802 или 68hc11, 8088 и/или z80. Старые pic pic12, или pic16 (pic32 - это просто mips). mips, power pc, avr. Я большой поклонник набора инструкций msp430, очень хорошего для изучения, с ощущением pdp11, дружественного компилятора, к сожалению, нацеленного на нишевой рынок. 8051 все еще не умер, удивительно. Более старые, большинство из них, имеют симуляторы набора инструкций в различных формах (например, в mame их много), так что вы можете взять эти симуляторы и распечатать память и регистры во время выполнения вашей программы, чтобы наблюдать, учиться и совершенствоваться. Затем сравните эти старые программы с более современными. Посмотрите, почему некоторые ISA при одинаковой тактовой частоте превосходят другие в разы. Некоторые имеют один аккумулятор, один регистр, может быть, два или четыре, и чтобы сделать что-нибудь полезное, вы должны постоянно загружать и сохранять, что занимает несколько инструкций для одной реальной операции. В то время как что-то более современное выполняет эту реальную операцию за одну-две-три инструкции/часа, просто имея больше регистров или общих регистров вместо регистров специального назначения.

Продвинутая тема - доступ к памяти. Thumb (не thumb2) не так эффективен, как ARM, есть заметные накладные расходы, на 5-10% больше инструкций требуется для той же задачи, так почему же thumb значительно быстрее на GameBoy Advance? Ответ: в основном 16-битные шины памяти с ненулевой памятью состояния ожидания. GBA не имеет кэша, но имеет префетч на интерфейсе rom, а синхронизация rom нелинейна, первое чтение - N часов, а чтение последовательных адресов - M часов (M меньше N) (что делает выполнение rom быстрее, чем ram). Незнание этого может сделать разницу между успехом и неудачей вашей встроенной программы для этой и других платформ. выходит далеко за рамки понимания языка ассемблера, но вы не сможете добраться туда, не умея читать и понимать вывод компилятора.

Еще одна продвинутая тема - кэши. Если у вас есть доступ к чему-то с кэшем и вы можете включать и выключать его (скажем, что-то из gamepark, gp32 или wiz, старый ipod, на котором вы можете делать homebrew) и т.п. В идеале, что-то, что вы можете контролировать кэш инструкций и данных отдельно. Вы получаете ощущение совершенно другого вида оптимизации, это больше не о наименьшем количестве инструкций с наименьшим количеством переходов/разветвлений и наименьшим количеством обращений к памяти. Теперь вам приходится иметь дело с длиной кэш-линии, с тем, где инструкции приземляются в пределах этой кэш-линии. Добавление одного, двух, трех, иногда большего количества nop в начале программы (нет, действительно, буквально добавление nop в start.S) может значительно улучшить или ухудшить производительность программы, созданной с помощью того же (более высокого уровня) исходного кода, компилятора и настроек оптимизации. Нужно изучить инструкции и понять аппаратное обеспечение, чтобы понять, почему.

Ваши вопросы конкретно:

-Опыт людей в кодировании на языках ассемблера (любой), который они приобрели за годы кодирования на языке ассемблера.

см. выше

-Руководства, которые нужно иметь в виду при изучении нового языка ассемблера

См. выше. Считайте, что процессоры больше похожи, чем отличаются, они загружают и хранят регистры, ветвятся безусловно и условно. Одни и те же несколько условных ветвлений хорошо известны и используются. Ищите сначала общие инструкции: немедленная загрузка, перемещение из одного регистра в другой, сложение на основе регистра, и, или, ксор. Не все процессоры имеют инструкцию деления, большинство нет, некоторые не имеют умножения, больше, чем вы думаете. И большинство из них вы не можете использовать универсально, если операнды и результат умножения находятся в регистре одного размера, то многие комбинации операндов будут переполнять результат.

- Конкретные советы и рекомендации по эффективному и правильному кодированию на языках ассемблера

Двигайтесь по середине дороги, не вдавайтесь в крутые трюки, характерные для данного ассемблера/компилятора, или в gee whiz особенности языка. Будьте проще, некоторые из моих программ на языке Си 20-летней давности компилируются и сегодня на многих компиляторах. Я часто встречаю в мире код нескольких лет или меньше, который не компилируется сегодня, его приходится постоянно поддерживать для выполнения той же функции с новыми компиляторами, просто из-за уловок компилятора или языка.

- Как эффективно преобразовать заданный код на C в оптимальный код на ассемблере

Начните с C или другого, компилируйте и дизассемблируйте, возможно, несколько уровней оптимизации, возможно, несколько разных компиляторов. Затем просто исправьте проблемы. Это увлекательная задача, но на самом деле вы попадаете в эту ловушку gee whiz. Часто экономия 1 или 2 или 7 инструкций из 5 или 10 или 20 не стоит того, чтобы таскать с собой ассемблер вместе с C и ставить вас в непортативную ситуацию, или в ситуацию, когда компилятор может догнать вас в следующей версии или двух, и даже превзойти ваши возможности, потому что он знает больше инструкций и как их использовать, чем вы.

Где я чаще всего использую ассемблер (кроме загрузки, естественно), так это для чтения и записи регистров или мест памяти. Каждый компилятор, который я использовал, в какой-то момент времени не смог получить нужную инструкцию, заменил 32-битный store на 8-битный, и тому подобное. Я фактически трачу инструкции и часы на реализацию процедур peek и poke в ассемблере, чтобы гарантировать, что компилятор не похоронит меня. Копии памяти и тому подобные вещи, как правило, действительно хороши (в библиотеках C), но это места, где вы можете воспользоваться преимуществами набора инструкций. Использование преимуществ специфических инструкций, которые не являются частью используемого языка, битовых тестов или битового набора (которые компилятор не распознает/оптимизирует). Замена байтов при наличии инструкции замены байта или полуслова. Определенные повороты, сдвиги или расширения знака.

Если вы можете найти его, то он есть в свободном доступе в черной книге Майкла Абраша "Дзен языка ассемблера". Измеряйте время выполнения и тестируйте, тестируйте, тестируйте. Неважно, насколько хорошим вы себя считаете, секундомер покажет настоящего победителя. Аппаратное обеспечение устранило половину его учения, но ход мысли и глубина изучения кода на таком уровне детализации (у меня есть оригинальная книга в печатном виде BTW), более поздние статьи в журналах были посвящены супермасштабируемым процессорам и простой перегруппировке некоторых инструкций, чтобы они могли быть распознаны и переданы отдельным блокам выполнения, заставляя те же инструкции выполняться во много раз быстрее, было интересно читать и понимать. И снова многое из этого было погребено под шумом конвейеров, большего количества блоков исполнения, параллельной обработки, более быстрых часов. На самом деле все это результат ужасных языков программирования, которые настолько неэффективны, что аппаратное обеспечение вынуждено это компенсировать. Но от этого нам становится еще веселее, когда мы можем выполнить одну и ту же операцию во многие тысячи и десятки тысяч раз быстрее, чем наши коллеги.

Очень легко прострелить себе ногу в этом занятии (улучшение вывода C с помощью ассемблера), будьте осторожны. Вы были предупреждены.

- Как четко понять данный ассемблерный код

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

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

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

Другой ответ, в зависимости от того, о чем вы на самом деле спрашивали, - это знать соглашение компилятора о вызове, хранятся ли операнды для функции в r0, r1, r2 ... и если да, то сколько их в регистрах, прежде чем они попадут в стек. Помещает ли компилятор все в стек? Флаги тоже хранятся в стеке? Где хранится адрес возврата? Все это МОЖЕТ различаться в разных компиляторах для одной и той же цели, как в x86 в старые времена (Zortech/Watcom против Microsoft/Borland), или для одного и того же процессора для одного и того же компилятора, как мы видим в наше время (ABI и EABI). В наше время вы можете обнаружить, что интерфейс разработан и определен кем-то (самой компанией-производителем микросхем?), и различные компиляторы будут соответствовать этому стандарту по различным причинам, переносимость, маркетинг, лень и т.д. Я считаю, что, изучив разборку и проехав по середине дороги, можно выяснить соглашения о вызове без необходимости идти и читать спецификацию.

Я рано выучил язык ассемблера и часто, к досаде моих коллег, склоняюсь к повторному изучению языка.использую общие переменные в своем Си, как если бы я писал на ассемблере. Поэтому отслеживание того, какие данные находятся в какой переменной в какой момент времени в программе, является для меня естественным. Ymmv. При анализе чужого вывода ассемблера или компилятора я взламываю этот вывод в текстовом редакторе, который я использую для его чтения. Вставляю визуальные пробелы, пустые строки между функциональными блоками, делаю комментарии после каждой инструкции о том, что теперь находится в регистре, r0 - номер индекса в таблице, r1 - смещение слова этого элемента в таблице, r0 - физический адрес этого элемента в таблице, r2 - сам элемент из таблицы и т.д.

удачи, развлекайтесь, извините за очень длинный ответ.

20
ответ дан 4 December 2019 в 08:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: