Погружение в ассемблер. Делаем первые шаги в освоении асма

Ты решил освоить ассемблер, но не знаешь, с чего начать и какие инструменты для этого нужны? Сейчас расскажу и покажу — на примере программы «Hello, world!». А попутно объясню, что процессор твоего компьютера делает после того, как ты запускаешь программу.

От редакции

В 2017 году мы опубликовали первую статью из планировавшегося цикла про ассемблер x86. Материал имел огромный успех, однако, к нашему стыду, так и остался единственным. Прошло два с половиной года, и теперь за дело берется новый автор. В честь этого мы делаем прошлую статью бесплатной, а Антона Карева попросили пропустить введение и без оглядки нырять в практику.

Читай далее:

Готовимся к работе

Я буду исходить из того, что ты уже знаком с программированием — знаешь какой-нибудь из языков высокого уровня (С, PHP, Java, JavaScript и тому подобные), тебе доводилось в них работать с шестнадцатеричными числами, плюс ты умеешь пользоваться командной строкой под Windows, Linux или macOS.

Если наборы инструкций у процессоров разные, то на каком учить ассемблер лучше всего?

Знаешь, что такое 8088? Это дедушка всех компьютерных процессоров! Причем живой дедушка. Я бы даже сказал — бессмертный и бессменный. Если с твоего процессора, будь то Ryzen, Core i9 или еще какой-то, отколупать все примочки, налепленные туда под влиянием технологического прогресса, то останется старый добрый 8088.

SGX-анклавы, MMX, 512-битные SIMD-регистры и другие новшества приходят и уходят. Но дедушка 8088 остается неизменным. Подружись сначала с ним. После этого ты легко разберешься с любой примочкой своего процессора.

Больше того, когда ты начинаешь с начала — то есть сперва выучиваешь классический набор инструкций 8088 и только потом постепенно знакомишься с современными фичами, — ты в какой-то миг начинаешь видеть нестандартные способы применения этих самых фич. Смотри, например, что я сделал с SGX-анклавами и SIMD-регистрами.

Что и как процессор делает после того, как ты запускаешь программу

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

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

90
B0 77
B8 AA 77
C7 06 66 55 AA 77

Вернее, даже так:

90 B0 77 B8 AA 77 C7 06 66 55 AA 77

Хотя погоди! Только машина может понять такое. Поэтому много лет назад программисты придумали более гуманный способ общения с компьютером: создали ассемблер.

Благодаря ассемблеру ты теперь вместо того, чтобы танцевать с бубном вокруг шестнадцатеричных чисел, можешь те же самые инструкции писать в мнемонике:

nop
mov al, 0x77
mov ax, 0x77AA
mov word [0x5566], 0x77AA

Согласись, такое читать куда легче. Хотя, с другой стороны, если ты видишь ассемблерный код впервые, такая мнемоника для тебя, скорее всего, тоже непонятна. Но мы сейчас это исправим.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


Антон Карев: Окончил физтех Алтайского госуниверситета. 20 лет занимаюсь низкоуровневым программированием на Ассемблере и Си. С уклоном в Secure Coding. Провожу в скайпе индивидуальные уроки по программированию на Ассемблере, Си, Питоне. Хочешь пообщаться? Пиши на email: anton.barnaul.1984@mail.ru

Комментарии (19)

  • До этого восхищался статьей с флопи берд и это является классной мотивацией читать такие статья для начинающих

    • Получилось реализовать самостоятельно? Заработало?

      • Сейчас занимаюсь проектами по проще. Написал вам на почту

  • Коллеги, приветствую! Редакция попросила меня пропустить философское введение, и начать сразу с практики, что я и сделал. Но если вам интересно моё мнение по вопросам вроде тех, которые перечисляю ниже, можем пообщаться лично – пишите на email (указан в подписи к статье); или прямо здесь в комментариях спрашивайте.
    - Ради чего ассемблер изучать стоит, а ради чего нет?
    - Программы написанные на ассемблере работают в 10 раз быстрее: миф или реальность?
    - Есть ли какие-то программы, которые нельзя написать на ассемблере?
    - Какие преимущества ассемблер даёт тебе как программисту?
    - Стоит ли начинать изучать программирование с ассемблера?
    - Насколько легче учить Си или другой язык высокого уровня, когда ты уже знаешь ассемблер?
    - Насколько выгодно в плане денег, когда ты умеешь программировать на ассемблере?

  • Спасибо за статью!

    Есть пара вопросов:

    1. А зачем нам отступать 0x100 байт в сегменте кода? Что это за бюрократия?
    1.1 Что будет, если зарезервировать 0x101 или 0x99 байт?

    2. В статье гвоорится, что название метки может начинаться "либо со знака собаки". Значит ли это, что мы можем назвать метку как @@@ и прыгать на неё так jmp @@@ ?
    Или всё-таки имелись ввиду те метки на которые можно прыгать, т.е. с двух знаков собаки, а те метки к которым можно обращаться, а-ля string, могут начинаться с букв или _ ?

    3. А можно обращаться к меткам с собаки так же как мы обратились к метке string?
    mov bx, string
    mov bx, @@foo

    4. Что это за зверь такой - прерывание BIOS? :)
    4.1 Используют ли это прерывание и по сей день?
    4.2 Судя по всему мы черех регистр AH передаем номер функции 0x0E. Где посмотреть список функций?
    4.2.1 Список функции зависит от версии BIOS?

    5. Что такое нулевая страница и страница вообще?
    5.1 Если задать там FF, то что будет страницей FF?

    6 Так как мы делаем инкремент, выходит мы действуем в рамках соглашения между функциями вызываемыми через прерывание 10h и нами? Наверное, должна быть документация на эти функции (см. 4.2). Т.е. любой байт из AL - трактуется функцией которую мы вызвали через прерывание как ASCII символ?

    7. "Обрати внимание, мы не можем использовать AX для хранения адреса, потому что нет таких инструкций, которые бы считывали память, используя AX в качестве регистра-источника."
    Т.е. мы не можем сделать mov ax, string ; вместо mov bx, string ; т.к. mov bl, [ax] не сработает?

    Не очень понял о чём речь, можно пример?

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

      1. Первые 0x100 байт программы – это так называемый PSP (префикс программного сегмента). Вникать на первом уроке по Асму в то, что это такое и зачем оно нужно – не стоит. Пока лучше просто принять на веру, что ТАК НАДО. 0x101 байт отступить можно, 0x99 – нельзя. Но вы наверно хотели сказать 0xFF? Ведь 0x100-0x1 = 0xFF (а не 0x99). В принципе, если повезёт, программа не обвалится, даже если только на 0x10 отступить. Но если идти наперекор стандартом, поздно или рано это всё равно боком вылезет. Что-нибудь, да сломается.

      2. А вы попробуйте задать такое имя, и скомпилировать программу. Сразу на собственном опыте увидите, можно или нет.

      3. Не очень понял вопрос. В метках собака – это точно такой же символ, как и другие. Сколько собак в названии метки стоит – столько и надо писать в джампе.

      На остальные вопросы чуть позже отвечу вам на почту.

    • Всё таки ответы на остальные вопросы здесь тоже продублирую (наверняка кому-то интересны тоже будут):
      4. «Прерывания BIOS» - это наборы системных функций, которыми можно пользоваться, даже когда на компьютере ещё никакие программы не установлены. Эти функции записаны в специальной микросхеме, которая припаяна на материнской плате ПК. Это микросхема так и называется: BIOS (базовая система ввода вывода).
      4.1. Прерывания или системные вызовы будут с нами всегда. Сейчас на смену BIOS постепенно приходит UEFI, но сам принцип остаётся тот же: на компьютере должны быть какие-то базовые функции, от которых низкоуровневый программист (который пишет программу для компьютера, на котором ещё ничего не установлено) может оттолкнуться. Даже если когда-то не только BIOS выйдет из моды, но и UEFI тоже, и придут новые технологии с новыми названиями (и прерываний вроде как больше не будет) – будет что-то другое, что называется по-другому, но делает то же самое.
      4.2. Да, вы правы. Список прерывания и их функций можно посмотреть, например вот здесь: http://www.codenet.ru/progr/dos/
      4.2.1. Список функций у прерываний (по крайней мере у прерывания 0x10) – одинаков, у всех версий BIOS.
      5. Вывод на экран происходит следующим образом. Сначала то что надо вывести помещается в специальную область оперативной памяти компьютера, а потом оттуда уже проецируется на видео-карту. Таких «специальных областей» может быть несколько. Например, у графического адаптера EGA – есть две таких страницы. Зачем придуманы страницы? Чтобы можно было на неактивной странице что-то нарисовать, и потом быстро всё разом вывести – чтобы без мельканий всё отображалось.
      5.1. Что будет, если мы, зная, что у нас только две доступных страницы, попробуем обратиться к странице номер 0xFF? Введите её номер, перекомпилируйте программу и посмотрите.
      6. Да, у каждой функции есть свои входы и выходы. Она берёт входные данные с определённых регистров, и возвращает результат тоже в определённые регистры. Какие именно, указано в документации. В том числе по той ссылке, которую я вам дал в п. 4.2.
      7. Да, вы правильно поняли. mov bl, [ax] не сработает.

      • Спасибо!
        Хотел бы ещё уточнить.

        Вы пишите: "Прерывания или системные вызовы будут с нами всегда".

        В статье https://xakep.ru/2018/06/18/lets-write-a-kernel/#toc01. пишут:
        «Большинство регистров процессора x86 имеют определенные значения после загрузки. Регистр — указатель на инструкцию (EIP) содержит адрес инструкции, которая будет исполнена процессором. Его захардкоженное значение — это 0xFFFFFFF0»

        Вопрос:
        0xFFFFFFF0 — это адрес оперативной памяти? Если да, то мы можем туда что-то как-то поместить и обойти BIOS? Но тогда, кто и как эту память готовит, она же энергозависимая? Выходит, всё-таки, перед тем как в регистрах появляются какие-то значения, кто-то как-то заполняет память?

        • - 0xFFFFFFF0 – да это адрес RAM (энергозависимой оперативной памяти). Перед тем как x86-процессор джампнется по нему – туда сначала записывается джамп на код BIOS'а. Вернее не на сам код BIOS’а (который в ROM хранится), а на его копию в RAM. Каким образом код BIOS и джамп по адресу 0xFFFFFFF0 оказываются в RAM? Посредством механизма, который называется shadowing. Кто этот механизм реализует? Одна из тех микросхем, которые прикручены к материнской плате.
          - Обойти BIOS? А зачем? Вы боитесь, что прерывания вносят накладные расходы в работу процессора? Так они активируются только когда ты в программе к ним обращаешься, а до этого никак не выдают своего присутствия – ни одного лишнего такта процессора не откусывают.
          - Ну или допустим, вам таки каким-то чудом удалось обойти BIOS. А дальше-то что? Кто будет его функции выполнять? Вам свой тогда придётся реализовать. И после долгих лет работы над этими функциями, вы придёте к тому, что тот же самый BIOS и разработаете. Правда, скорее всего он будет работать не так оптимально, как тот, который вы пытались обойти.
          - Но если вам сама идея низкоуровневого обхода нравится, – из любви к искусству, а не ради практической необходимости, – то можно попытаться и что-то более низкоуровневое пообходить, чем BIOS. Ведь между моментом нажатия кнопки включения ПК и моментом переноса BIOS в RAM – внутри процессора много интересных шагов происходит, в которые при желании можно вмешаться. В качестве затравки см. вот эту статью: https://habr.com/ru/post/427757/

          • Спасибо большое за развёрнутые овтеты!

            По ссылке почитал про микрокоды, интересно.
            Ещё интересно про перекрываемые инструкции там же. Но это, наверное, другая история : )

  • Ради чего ассемблер изучать стоит, а ради чего нет?
    Программы написанные на ассемблере работают в 10 раз быстрее: миф или реальность?
    Какие преимущества ассемблер даёт тебе как программисту?
    Есть ли смысл ассемблера в 21 веке с высокоуровневыми языками программирования в арсенале, если можно написать на нем, а потом просто промежуточные версии компиляции вытащить как ассемблированный код и разбирать его, если потребуется? (использую Swift и lldb + Hopper Disassembler для понимания работы чужих программ)

    • Если на все вопросы сразу отвечать - целая статья получится. Вы 1 пожалуйста задайте, который важный самый.

  • Насколько сейчас вообще востребован асмщик средней паршивости (тот же юниор) с миддлсишкой за плечами?
    Любой разговор о языках программирования рано или поздно приходит к вопросу востребованности...

    • - Если ты только ассемблер знаешь, то вряд ли куда-нибудь приткнёшься. Потому что даже микроконтроллеры сегодня программируют на Си.

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

      - То же самое с языками более высокого уровня, чем Си: Java, C#, Python и т.п. Я даже слышал истории о работодателях, которые для написания кода на Java и Python нанимают Си-программистов, которые не работали с этими языками; отдают предпочтение им, а не тем, кто на Питоне и Java специализируется. Это связано с тем, что Си-программисты пишут более эффективный код.

  • Долго же вы, господа-редакция, раскачивались на продолжение, у меня даже годовая подписка успела закончиться.
    Автору спасибо, надеюсь, дочитаю-дожду обещанный, некогда, цикл статей.

  • Супер! Давно тянет к asm, лет 10 всё порываюсь, но руки дошли только после этой статьи. Автору большое спасибо! Не останавливайтесь!

  • Ребят вы бы там хоть согласовали в редакции что к чему, а то самая первая статья из цикла, которая в 2017 году вышла говорит:
    "Однако сейчас такой выбор платформы[имея ввиду 8086 в 16-битном режиме работы] для изучения совершенно неприемлем. MS-DOS как среда выполнения программ безнадежно устарела уже к середине девяностых годов, а с переходом к 32-битным процессорам, начиная с процессора 80386, сама система команд стала намного более логичной. Так что бессмысленно тратить время на изучение и объяснение странностей архитектуры реального режима, которые заведомо никогда уже не появятся ни на одном процессоре."

    А в статье, которая должна стать продолжением цикла говорят, что нужно "подружиться с 8080 дедушкой и компилировать программы под DOS"

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

  • Пользуюсь ubuntu, nasm можно скачать через apt, как мне скомпилировать эту программу и запустить ее с терминала, буду очень признателен

Похожие материалы