hello world машинный код
Изучаем C++. Часть 1. Что такое программа и языки программирования
Разбираемся, как устроен язык программирования C++, что такое программы и как научиться их писать.
Чтобы научиться программировать с нуля, нужно только уметь пользоваться компьютером, устанавливать программы, создавать папки и файлы. Это первая статья из серии «Глубокое погружение в C++», с помощью которой вы сделаете первые шаги в профессии разработчика серверных приложений и игр.
Не пугайтесь большого объёма информации: чем больше вы будете знать, тем лучше сможете программировать. Это особенно важно, если вы выбрали C++, потому что на этом языке невозможно без хорошей теоретической базы написать высокопроизводительный код.
Внимание! В следующих разделах информация сильно упрощена.
Пишет о программировании, в свободное время создает игры. Мечтает открыть свою студию и выпускать ламповые RPG.
Что такое язык программирования
Компьютер — это бесполезный кусок железа, который ни с чем не может справиться самостоятельно. И чтобы получить какой-то результат, нужно написать для него программу — подробный набор инструкций.
Компьютер понимает только машинный код — специальный язык, который очень сложно изучить. Вот как выглядит вывод на экран надписи «Hello, World!» на машинном коде (в шестнадцатеричном представлении):
Чтобы упростить разработку, были созданы языки программирования — с их помощью пишут инструкции для компьютера, которые понятны и человеку. Одним из первых был язык ассемблера. Вот как на нём выглядит вывод надписи «Hello, World!»:
Всё равно достаточно сложно, да? В результате человечество создало более понятные языки программирования, на которых гораздо быстрее писать программы. А чтобы они потом запускались, код переводится в машинный или какой-нибудь промежуточный язык — этот процесс называется компиляцией.
Если язык похож на машинный код, то его называют низкоуровневым. Если же он больше понятен человеку, то — высокоуровневым. C++ сочетает в себе свойства и того, и другого.
Как работают программы
Любая программа работает с данными: она их получает, обрабатывает, а потом возвращает результат обработки. Данные могут быть переданы пользователем, считаны из файла или записаны в самом коде.
Например, браузер получает текст из адресной строки, который ввёл пользователь, отправляет адрес на сервер, а потом выводит ответ в виде страницы.
Также программа может состоять из других подпрограмм — наборов инструкций. Обработка запроса пользователя, отправка данных на сервер, получение ответа, вывод страницы — всё это подпрограммы.
Вот пример логики такой подпрограммы:
Можно вернуть данные пользователю, записать их в файл или передать другой подпрограмме. Вот ещё один пример использования программы:
Эта команда запускает программу в ОС Linux, которая конвертирует изображение img.jpg в другой формат, а потом сохраняет в файл img.png.
Сейчас слово «подпрограмма» встречается редко — вместо него используют слова «функция», «процедура» или «метод».
Пишем первую программу на C++
По традиции программистов первая программа, написанная на изучаемом языке, должна выводить на экран надпись «Hello, World!».
В C++ такой код будет выглядеть так:
Попробуем его запустить, а потом разберём, как он работает.
Запускаем код в интернете
Самый простой вариант — зайти на сайт repl.it. На главной странице нажмите Start Coding, выберите C++ и нажмите Create Repl.
У вас откроется следующая страница:
Слева — менеджер файлов, в центре — текстовый редактор, где уже введён нужный нам код, а справа — консоль, в которой выводится результат. Чтобы запустить программу, нажмите Run.
Запускаем код на компьютере
Если же вы хотите компилировать код на своём компьютере, то установите компилятор. Я пользуюсь G++, его и вам рекомендую.
После установки компилятора создайте отдельную папку для кода, а в ней — файл hello.cpp (это расширение для файлов с командами на C++). Желательно, чтобы путь к папке состоял из латинских символов и был написан без пробелов.
Откройте hello.cpp с помощью любого блокнота или редактора кода (например, Sublime text или VS Code) и вставьте в него код, указанный выше. Теперь откройте терминал с помощью инструкции ниже.
Windows | Linux |
---|---|
1. Нажмите Win + R, введите cmd и нажмите Enter. | 1. Нажмите Ctrl + Alt + t. Если не помогло, то подходящий способ можно найти в этой статье. |
2. Введите команду cd [путь]. Например, cd projects\cpp1, если ваша папка находится по адресу c:\projects\cpp1. | 2. Введите команду cd [путь]. Например: cd /projects/cpp1 |
3. Если папка находится не на системном диске, то команду нужно изменить: cd /D [диск:][путь]. Например: cd /D d:\projects\cpp1 |
Затем введите следующую команду:
Эта команда скажет компьютеру, что нужно скомпилировать код из файла hello.cpp и сохранить результат в файл hello (или hello.exe в Windows).
После этого запустите скомпилированную программу, и вы увидите, что всё работает:
Теперь, когда мы смогли запустить программу, разберёмся в ней.
Из чего состоит программа на C++
Начнём с самой первой строчки:
С помощью языка программирования можно:
Но чтобы получить больше возможностей, программе нужно как-то взаимодействовать с операционной системой. Писать код, который будет отправлять команды ОС, достаточно сложно, но нам и не придётся — всё уже написано другими разработчиками и помещено в специальную библиотеку.
Одна из таких библиотек, iostream, позволяет запрашивать пользовательский ввод или выводить что-то в консоли.
Есть и другие библиотеки, которые помогают работать с графикой, отправлять запросы через интернет, воспроизводить звук и так далее. В будущем вы научитесь самостоятельно писать свои собственные библиотеки.
Hello World из байт-кода для JVM
Скомпилируем простенькую программу выводящую «Hello World» и пройдемся по его структуре
Не думаю, что статья будет достаточно информативной для тех, кто поверхностно не знает как выглядит байт-код и как с ним работает JVM (например, хотя бы простейшие инструкции (знание об их существовании)).
На самом деле, это не так сложно. Достаточно использовать инструмент javap из JDK и рассмотреть дизассемблированный код.
А мы приступим к разбору самой структуры байт-кода для JVM
Очень полезной книгой для этого стала официальная спецификация JVM — The Java Virtual Machine Specification на сайте oracle
Для начала создадим простенькую программу:
Скомпилируем её командой javac Main.java и собственно сделаем дизассемблинг
Это просто представление байт-кода, которое человеку видеть легче, чем оригинальный байт-код, но сам он выглядит иначе:
С этим кодом мы и будем работать.
Но для начала нам нужно его отформатировать, чтобы не путаться что где находится, а байт-код, на самом деле, имеет вполне жесткую структуру:
Её вы можете найти в спецификации JVM Chapter 4.1 The ClassFile Structure
Тут все просто — слева указана размерность в байтах, а справа описание.
Разбирать байт-код мы будем в hexadecimal, где каждая цифра занимает 4 бита, а следовательно, на два байта — 4 цифры и на четыре байта — 8 цифр.
magic
minor_version, major_version
Это версии вашего class файла. Если мы назовем major_version M и minor_version m, то получаем версию нашего class файла как M.m
Сейчас я сразу буду приводить примеры из нашей программы «Hello World», чтобы посмотреть как они используются:
Его же мы можем видеть в дизассемблированном коде, но уже в десятичной системе счисления:
constant_pool_count
Здесь указывается количество переменных в пуле констант. При этом, если вы решили писать код на чистом байт-коде, то вам обязательно нужно следить за его значением, так как если вы укажете не то значение, то вся программа полетит к чертям (проверено!).
Также следует не забывать, что вы должны писать туда количество_переменных_в_пуле + 1
constant_pool[]
Каждый тип переменной в пуле констант имеет свою структуру:
Таблица с тэгами можно найти в спецификации Table 4.3 Constant pool tags
Собственно, вот табличка:
Constant Type | Value |
---|---|
CONSTANT_Class | 7 |
CONSTANT_Fieldref | 9 |
CONSTANT_Methodref | 10 |
CONSTANT_InterfaceMethodref | 11 |
CONSTANT_String | 8 |
CONSTANT_Integer | 3 |
CONSTANT_Float | 4 |
CONSTANT_Long | 5 |
CONSTANT_Double | 6 |
CONSTANT_NameAndType | 12 |
CONSTANT_Utf8 | 1 |
CONSTANT_MethodHandle | 15 |
CONSTANT_MethodType | 16 |
CONSTANT_InvokeDynamic | 18 |
Как ранее уже говорилось, каждый тип константы имеет свою структуру.
Вот, например, структура CONSTANT_Class :
Структура поля и метода:
Тут важно заметить, что разные структуры, могут иметь разную длину.
Рассмотрим часть нашего кода:
Итак, смотрим на структуру константы и узнаем, что первый байт отведен под тип константы. Здесь мы видим 0a (10) — а, следовательно, это CONSTANT_Methodref
Смотрим его структуру:
После одного байта для тэга, нам нужно еще 4 байта для class_index и name_and_type_index
Отлично, мы нашли одну из значений пула констант. Идем дальше. Смотрим, 09 — значит тип CONSTANT_Fieldref
Вам может показаться, что большинство типов имеет одинаковую форму, но это не так.
Например, структура следующего типа выглядит так, CONSTANT_String :
Все эти структуры можно посмотреть в Chapter 4.4 The Constant Pool
Теперь разберем, что значат типы внутри самого info
Это же мы можем видеть в дизассемблированном коде:
Также можно выделить представление чисел и строк.
Про представление чисел можно прочитать начиная с главы 4.4.4, а мы пока разберем лишь строки, так как числа пока не входят в программу Hello World
Собственно, вот так представляется строка:
Например, наш Hello World:
Разбирая весь пул констант байт-кода, получим:
Также, мы можем сравнить его с дизассемблированным кодом:
Тем самым проверив, что все совпадает, ведь по сути javap просто обрабатывает этот байт-код и показывает нам его в форматированном виде.
Пул констант нужен для инструкций. Например:
Подробнее обо всех типах в пуле констант можно узнать в Chapter 4.4 The Constant Pool
Идем дальше по структуре ClassFile
access_flags
Это битовая маска для свойств модификаторов
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC | 0x0001 | Declared public ; may be accessed from outside its package. |
ACC_FINAL | 0x0010 | Declared final ; no subclasses allowed. |
ACC_SUPER | 0x0020 | Treat superclass methods specially when invoked by the invokespecial instruction. |
ACC_INTERFACE | 0x0200 | Is an interface, not a class. |
ACC_ABSTRACT | 0x0400 | Declared abstract ; must not be instantiated. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code. |
ACC_ANNOTATION | 0x2000 | Declared as an annotation type. |
ACC_ENUM | 0x4000 | Declared as an enum type. |
this_class
Должна содержать адрес на this класса. В нашем случае, она находится по адресу 5:
Следует заметить, что структуру этой переменной должна соответствовать CONSTANT_Class_info
super_class
То есть в этих ячейках указан name_index из структуры:
interfaces_count, fields_count
methods_count
Количество методов. Хоть и в коде мы видим один метод в классе, но, на самом деле, их два. Кроме main метода еще есть конструктор по умолчанию. Поэтому их количество равно двум, в нашем случае.
methods[]
Каждый элемент должен соответствовать структуре method_info описанной в Chapter 4.6 Methods
В нашем байт-коде (отформатированном, с комментариями) выглядит это так:
Разберем по-подробнее структуру методов:
access_flags
К слову, ACC_VARARGS здесь необязательный, в том плане, что, если бы мы
использовали String[] args вместо String… args, то этого флага бы не было
name_index
descriptor_index
Грубо говоря, это адрес указывающий на дескриптор метода. Этот дескриптор содержит тип возвращаемого значения и тип его сигнатуры.
Также, в JVM используются интерпретируемые сокращения:
BaseType Character | Type | Interpretation |
---|---|---|
B | byte | signed byte |
C | char | Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16 |
D | double | double-precision floating-point value |
F | float | single-precision floating-point value |
I | int | integer |
J | long | long integer |
L ClassName ; | reference | an instance of class ClassName |
S | short | signed short |
Z | boolean | true or false |
[ | reference | one array dimension |
В общем случае это выглядит так:
Например, следующий метод:
Можно представить в виде
Далее, идут атрибуты, которые также имеют свою структуру.
Но сначала, как и всегда, идет его количество attributes_count
Затем сами атрибуты со структурой описанной в Chapter 4.7 Attributes
attribute_name_index
attribute_length
Содержит длину атрибута, не включая attribute_name_index и attribute_length
info
max_stack
Мне кажется, что имя этого атрибута может ввести в заблуждение из-за приставки max. На самом деле, это минимальный размер стека нужный для выполнения операции. Ну, это имя приобретает логику, если сказать, максимальный размер стека, который будет достигнут во время выполнения операции.
Упрощенно говоря, JVM выделит место для стека операндов. Там можно указывать значение, которое больше, чем нужно, но определение в этом атрибуте значения меньше, чем нужно приведет к ошибке.
max_locals
Максимальный размер локальных переменных
Ознакомится с локальными переменными можно либо в Mastering Java Bytecode at the Core of the JVM или в том же JVM Internals
code_length
Размер кода, который будет исполнятся внутри метода
code[]
Каждый код указывает на какую-то инструкцию. Таблицу соотношения optcode и команды с мнемоникой можно найти в википедии — Java bytecode instruction listings или в самой спецификации в конце книги
Для примера, возьмем наш конструктор:
Здесь мы можем найти наш код:
Ищем в таблице команды и сопоставляем:
Также описания этих команд можно найти здесь: Chapter 4.10.1.9. Type Checking Instructions
exception_table_length
Задает число элементов в таблице exception_table. У нас пока нет перехватов исключений поэтому разбирать его не будем. Но дополнительно можно почитать Chapter 4.7.3 The Code Attribute
exception_table[]
Имеет вот такую структуру:
attributes_count
Количество атрибутов в Code
attributes[]
Атрибуты, часто используются анализаторами или отладчиками.
Средства для работы с байт-кодом
Это немного не та тема, которая относится к данной статье, но все же косвенно связанная с ней.
Средств для работы с байт-кодом, на самом деле, достаточно много. Здесь я бы хотел рассмотреть Byte Code Engineering Library (BCEL) от Apache Commons.
Для начала, с помощью него мы может получить некоторые атрибуты байт-кода:
Помимо этого мы можем сгенерировать, изменить или дизассемблировать (например, в Jasmin) байт-код.
Парочку примеров можно найти в моем репозитории или в официальных примерах
Также, я уделил внимание и Jasmin. На самом деле, я не знаю, чем оно может быть полезно, но я её использовал при изучении механизма работы JVM с байт-кодом.
С помощью неё можно писать на упрощенном ассемблерном коде:
Вот мы и разобрали простую программку Hello World
Листинг байт-кода с комментариями можно найти на моем гисте: gist.github
Если есть ошибки прошу писать в комментариях или в сообщениях.
Hello world машинный код
Многие любители не испытывают серьезных трудностей в овладении БЕЙСИКом. Для этого достаточно немного практики. Но рано или поздно они приходят к барьеру «машинного кода». Как это ни печально, но некоторые так перед ним и останавливаются. Это ни в коей мере не связано с отсутствием желания или способностей, просто многие не знают, с чего начать. Если в БЕЙСИКе можно начинать с чего угодно (при ошибке компьютер сам Вас поправит), то здесь Вы оказываетесь с процессором один на один, и такой метод проб и ошибок не срабатывает.
Итак, давайте напишем первую программу в машинном коде. Прежде всего, выделим для нее область памяти. Если Вы читали нашу книгу «Большие возможности Вашего «ZX-Spectrum`а», то знаете, что для БЕЙСИКа в оперативной памяти компьютера отведена область памяти, начинающаяся с адреса, на который указывает системная переменная PROG и заканчивается адресом, на который указывает системная переменная RAMTOP. Предположим, что Вы хотите записать программу в машинных кодах, начиная с адреса 30000. Дайте команду CLEAR 29999. Эта команда установит RAMTOP в 29999 и Ваша программа будет защищена от возможной порчи из БЕЙСИКа. Даже если Вы дадите команду NEW, области памяти, находящиеся выше RAMTOP, не будут поражены.
Теперь дайте две прямые команды одну за другой:
Если все, что Вы здесь прочитали, Вам понятно, то Вы уже поняли, как составляются программы в машинных кодах. Можно, конечно, возразить, что пользы от такой программы не очень много, но сейчас не в этом суть. Важно, чтобы Вы поняли, что некая последовательность чисел может быть последовательностью команд для процессора Z-80.
Теперь давайте вернемся к нашей первой программе и попробуем ее несколько развить, чтобы она все же что-то делала. Процессор Z-80 имеет несколько регистров, у которых есть имена – «А», «В», «С» и т.д. Каждый из них может содержать одно какое-либо целое число от 0 до 255 (т.е. один байт).
Существуют десятки команд процессора, которые позволяют копировать содержимое регистров из одного в другой, а также выполнять связь с внешним миром, в т.ч. и с оперативной памятью.
Итак, мы уже готовы к тому, чтобы написать программу, которая будет перебрасывать какое-либо число из одного регистра процессора в другой.
Java байткод «Hello world»
На хабре уже есть статья про java байткод. Я решил ее немного дополнить и в меру сил развить тему. Мне кажется довольно логичным разобрать простейшее приложение на Java. А что может быть проще «Hello world»?
Для своего эксперимента я создал директорию src, куда в папку hello положил файл App.java:
Скопилируем файл командой:
На выходе в папке classes у меня появился файл App.class Для начала сравним размеры java и class файлов.
App.java 139B
App.class 418B
Это было неожиданно. Мне почему-то казалось, что скомпилированный файл должен быть меньше. Попытаюсь открыть class файл:
Довольно непривычный вид для Java кода. Попробуем с помощью описание формата class файлов понять, что здесь закодировано.
Это 4 байта для magic, который определяет формат файла.
minor version — Минорная версия как следует из названия
major version — 2 байта под мажорную версию.
Сочетание minor и major version говорит о том, что я компилировал этот код с помощью J2SE 8.
Эти два байта представляют constant_pool_count и отвечают за размер constant_pool. В моем случае count равен 29, а размер пула, соответственно 28. Дальше идут элементы вида:
cp_info <
u1 tag; // 1 байт на тег
u1 info[]; // массив с описанием
>
Рассмотрим элементы в constant_pool.
Этот тег соответствует CONSTANT_Methodref, а значит дальше должно быть описание:
CONSTANT_Methodref_info <
u1 tag;
u2 class_index;
u2 name_and_type_index;
>
соответственно:
class_index, указывает на 6 элемент в constant_pool
name_and_type_index, указывает на 15 элемент в constant_pool
Пока не понятно, на какой метод указывает эта ссылка и мы идем дальше:
Это CONSTANT_Fieldref, а значит дальше ищем:
CONSTANT_Fieldref_info <
u1 tag;
u2 class_index;
u2 name_and_type_index;
>
И тут все очень похоже на предыдущий элемент, хотя не понятно что это за поле, в своем классе я вроде ничего такого не объявлял.
class_index в 16 элементе
name_and_type_index в 17 элементе
tag для CONSTANT_String
получаем, что самое интересное лежит в 18 элементе:
Tag соответствующий ссылке на метод:
класс которого описан в 19 элементе
a название и тип в 20 элементе:
5-ый элемент:
Tag для CONSTANT_Class
название, которого в 21 элементе
6-ой элемент:
Cнова CONSTANT_Class
c названием в 22 элементе
Как мы помним 1-ый элемент constant_pool относится к этому классу.
7-ой элемент:
tag, CONSTANT_Utf8, первая строчка
Она должна соответствовать:
CONSTANT_Utf8_info <
u1 tag;
u2 length;
u1 bytes[length];
>
Тогда длина нашей строчки 6 байт:
Это особое название, так помечаются конструкторы.
строчка длины 3 — «()V»:
Это описание нашего конструктора без параметров, который был упомянут в седьмом элементе.
9-ый элемент:
CONSTANT_Utf8
10-ый элемент:
Строка LineNumberTable
15-ый элемент
Tag, соответствует CONSTANT_NameAndType
а значит нам понадобится
CONSTANT_NameAndType_info <
u1 tag;
u2 name_index;
u2 descriptor_index;
>
и тогда:
ссылка на 7 элемент
ccылка на 8 элемент
Учитывая что первый элемент ссылался на это, мы можем заключить что первым был объявлен конструктор класса без параметров. Название класса, мы должны найти в 22 элементе.
16-ый элемент:
Tag, для CONSTANT_Class
c названием в 23 элементе
17-ый элемент:
Tag, CONSTANT_NameAndType, со ссылкой на 24 и 25 элемент constant_pool
18-ый элемент:
Ура «Hello world!»
19-ый элемент:
Tag, для CONSTANT_class c названием в 25-ом элементе
20-ый элемент:
Tag CONSTANT_NameAndType cо ссылкой на 27 и 28 элемент
25-ый элемент:
«Ljava/io/PrintStream;»
26-ой элемент:
«java/io/PrintStream»
methods_count у нас 2 метода в классе, конструктор по умолчанию и метод main:
Method 1 — Constructor
Один из самых интересных аттрибутов с кодом нашего метода code[code_length], разбор инструкций отдельная большая тема:
Аттрибут закончился и продолжается описание метода
Attribute 1 код метода main
Описание методов закончено и идет описание атрибутов класса
Теперь когда мы закончили с по-байтовым разбором class файла, становится понятно как работает:
Он автоматически выводит тоже самое, что я выписал руками:
А вот здесь можно посмотреть пример разбора class файла: