Ассемблер: x86/x86_64/Arm32/Arm64

a56815313723445bfe241197d0664b6e.png

Я достаточно давно решил ознакомится с ассемблером архитектуры Arm. По большей части для ознакомления и понимания внутренних процессов архитектуры. Чем больше я изучал литературу и различные источники, тем больше я понимал, что нужна практика. Я решил вспомнить ассемблер для x86-х машин и заодно потренироваться на архитектуре Arm.

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

Памятка. Для теста кода я использую Linux. Точнее, если у вас Windows то вам надо будет использовать WSL или виртуальную машину. А если у вас Mac, то есть вероятность, что вы сможете сделать всё точно так же как и на Linux. Не сможете? Не переживайте, просто создайте виртуальную машину Linux.

Сначала надо было определиться с выбором ассемблера. Выбор из ассемблеров достаточно большой: Masm, Nasm, Yasm, Fasm, Gas и другие. Мне же требовался ассемблер, который будет на архитектуре x86 создавать код под любую платформу. И я остановился на ассемблере Gas, по той причине, что это был единственный ассемблер удовлетворяющий моим условиям. Но в настоящий момент, как видно из обновлений по форуму, Fasm так же стал поддерживать архитектуру Arm64 (до этого были ошибки с компиляцией под данную архитектуру). Но всё же я буду использовать Gas, ведь у меня уже давно подготовлено к разработке на данном ассемблере.

Но вы можете попробовать и Fasm.)))

Это видео с общей информацией, кода в нём ни какого нет. Видео сделано давно, помидорами просьба не кидаться.

https://youtu.be/Wk4o7OjXmEw — ссылка на всякий случай, что-то не хочет без ВПН ни чего грузить…

Ни каких эмуляторов использовать не будем, не считая эмуляторов, которые будут исполнять наш код под нашей архитектурой. Это будут эмуляторы от Qemu: qemu-arm и qemu-aarch64. Данные эмуляторы смогут выполнять код только в командной строке, поэтому не надейтесь что вы сможете исполнить код для какого-либо графического окна. Для тестов кода это вполне подойдёт. Кстати, данные файлы могут находиться в Android-SDK (или NDK?), но я могу ошибаться.

Здесь я выкладываю весь код, и два эмулятора, о которых писал выше. Не забываем, эти эмуляторы для Linux, если вы используете Windows или Mac, то вам надо будет либо найти их под данные системы, либо собрать самим… Ни то ни другое не утешительно…

не забудьте, если качаете архив.

Пароль 456123. Пришлось поставить потому что внутри лежат исполняемые файлы для Linux.

Важно! В примерах идут вызовы для ОС Linux! Для того чтоб код идущий в примерах работал с Windows или Mac вам придётся найти информацию самим и переделать эти примеры.
А так же вы можете поделиться этой информацией с другими.))) Если найдёте её.

Для того чтоб переходить к изучению кода, что вы скачаете по ссылке выше, вам надо знать ассемблер для всех четырёх перечисленных архитектур: x86/x86_64/Arm32/Arm64. Я, как обычно, выкладываю информацию не совсем для новичков.

Установите binutils для нужных архитектур, в данном случае это: i686, x86_64, arm-eabihf (arm-eabi) и aarch64.

Почитайте литературу и источники, если это необходимо.

Различная информация по ассемблерам.
Ассемблер aarch64.
Ассемблер Arm.
Linux x86_64.
Полезно будет почитать книгу »Рудольф Марек. Ассемблер на примерах.» раздел »Программирование в Linux».

Думаю на просторах интернета не мало информации, потому поищите. Не забывайте основную документацию, которые предоставляют производители процессоров и создатели архитекрур.

В данном видео пробегаюсь по настройке необходимых инструментов и пробегаюсь по коду Hello World для разных архитектур.

https://youtu.be/c9iC8GW-7mQ — ссылку дублирую.

Это достаточно важная информация с системными вызовами Linux под разные архитектуры!

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

Makefile для helloGas.

CC64 = as
LC64 = gcc
CC32 = /usr/i686-linux-gnu/bin/as
LC32 = /usr/i686-linux-gnu/bin/ld
CCA64 = /usr/aarch64-linux-gnu/bin/as
LCA64 = /usr/aarch64-linux-gnu/bin/ld
CCA32 = /usr/arm-linux-gnueabihf/bin/as
LCA32 = /usr/arm-linux-gnueabihf/bin/ld
RA64 = ../qemu/qemu-aarch64
RA32 = ../qemu/qemu-arm
default: bX64 rX64
help:

bX64: HelloX64.asm
	$(CC64) -g HelloX64.asm -o HelloX64.o
	$(LC64) -s HelloX64.o -o HelloX64 -no-pie
rX64: HelloX64
	clear
	./HelloX64
bX32: HelloX32.asm
	$(CC32) HelloX32.asm -o HelloX32.o
	$(LC32) HelloX32.o -o HelloX32
rX32: HelloX32
	clear
	./HelloX32
bA64: HelloA64.asm
	$(CCA64) -g HelloA64.asm -o HelloA64.o
	$(LCA64) HelloA64.o -o HelloA64
rA64: HelloA64
	clear
	$(RA64) -L /usr/aarch64-linux-gnu/ -g 1234 ./HelloA64
bA32: HelloA32.asm
	$(CCA32) -g HelloA32.asm -o HelloA32.o
	$(LCA32) HelloA32.o -o HelloA32
rA32: HelloA32
	clear
	$(RA32) ./HelloA32
rgA32: HelloA32
	clear
	$(RA32) -L /usr/arm-linux-gnueabihf/ -g 1234 HelloA32

Данный Makefile настраивался для архитектуры x86_64. Если вы ведёте разработку на других архитектурах, возможно нужны правки. А так же некоторые параметры в файле просто для тестирования были сделаны и их можно убрать или добавить какие-то свои параметры.

Давайте обратим внимание на сборку проекта. Это: bX64, bX32, bA32 и bA64; где b — build, X — x86, A — ARM. Так же и с запуском: rX64, rX32, rA32 и rA64; где r — это run. Всё остальное надеюсь понятно.

В остальном многие (многие — это не все!) лучше меня разбираются, а если вы не разбираетесь в этом, то придётся всё подучить. На данный момент я тут не помощник, уж извините. Но забегая вперёд, можете что-то посмотреть тут, это хоть и относится больше к отладке, но важная информация там так же есть. Так же смотрите документацию по Qemu, в частности загляните сюда. Вероятно это вам поможет разобраться во всём (честно говоря, я уже что-то подзабыл, наверняка надо будет вспомнить и мне).

Да, загляните ещё в эту тему, именно там я начал выкладывать всю информацию и что-то мог упустить здесь.

Давайте быстро взглянем код для Hello World:

x86

// cpu x86_64

.global _start

.section .data
msg:	.ascii	"Hello World!\n"
len = . - msg		# вычисляется длина строки

.section .text
_start:
	mov $4, %eax	# 4 - write
	mov $1, %ebx	# 1 - stdout
	lea msg, %ecx	# адрес сообщения
	mov $len, %edx	# длина сообщения - обязателен префикс '$' перед len
	int $0x80
	
	mov $1, %eax	# 1 - exit
	xor %ebx, %ebx	# 0 - Return
	int $0x80

x86_64

// cpu x86_64

.global _main

.section .data
msg:	.ascii	"Hello World!\n"
len = . - msg		# вычисляется длина строки

.section .text
_main:
	mov $len, %rdx	# длина сообщения - обязателен префикс '$' перед len
	mov $msg, %rsi	# адрес сообщения
	mov $1, %rdi	# 1 - stdout
	mov $1, %rax	# 1 - write
	syscall

	xor %rdi, %rdi
	mov $60, %rax
	syscall

Arm32

// cpu ARM32

.global _start

.section .data
msg:	.ascii	"Hello World!\n"
len = . - msg

.section .text
_start:
	mov r0, #1		// 1 - stdout
	ldr r1, =msg	// загружаем адрес строки
	mov r2, #len	// длина строки - интересная ситуация... требует префикс '#'
	mov r7, #4		// write
	swi 0

	mov r0, #0		// код возврата
	mov r7, #1		// exit
	swi 0

Arm64

// cpu ARM64

.global _start

.section .data
msg:	.ascii	"Hello World!\n"
len = . - msg

.section .text
_start:
	mov x0, #1		// 1 - stdout
	ldr x1, =msg	// адрес
	mov x2, len 	// длина строки
	mov x8, #64		// системный вызов write
	svc 0			// программное прерывание, сгенерированное пользователем.
					// (вызов супервизора...)
	
	mov x0, #0		// код возврата
	mov x8, #93		// указание на закрытие программы Exit - Terminate
	svc 0

Просматривая код, можно увидеть, что сильно он не отличается для разных архитектур. Данный код отличается системными вызовами и тем, как ассемблер работает с комментариями. Если для архитектур Arm комментарии начинаются с »//», то для архитектур x86 они начинаются с »#»… С чем это связано непонятно… Ассемблер вроде один Gas. Но вот такие вещи запоминать приходится.

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

Так же надо не забывать про то, что регистры для архитектур Arm разные. Если для 32-х битных это регистры r0-r15, то для 64-х битных это регистры x0-x31 (w0-w31 их нижняя часть, что так же расходится с первоначальными регистрами).

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

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

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

Процедуры read/write. (https://youtu.be/r7A0RfLG0l4 — дублирую).

отладка. Выше скидывал ссылку и скину ещё ниже, где рассмотрим отладку немного другую. Но возможно кому-то полезно будет. (https://youtu.be/eUiFfLebXM8 — дублирую).

Давайте немного заденем отладку.

Есть графический отладчик EDB. Это достаточно простой отладчик и разобраться с ним проблем практически не возникает. Но! Этот отладчик работает с архитектурами x86, x86_64 и Arm32Arm64 он не поддерживает… Так же, для использования данного отладчика вам надо иметь реальную машину на данной архитектуре или как минимум эмулятор с работающей графической оболочкой…

Да, я создавал эмуляторы Arm. Но графическая оболочка на Arm32 не запустилась, на Arm64 работает.

И, из-за всего вышеперечисленного, я искал способ, который будет работать везде. И я нашёл его. Всё было сделано очень давно и достаточно хорошо расписано. Потому я остановился на отладчике GDB. Ознакомьтесь обязательно если вы так же решили использовать GDB для отладки.

Ещё одно видео где делал функции StrToInt/StrToUInt. А так же, на 25-й минуте разбираю отладку используя GDB.

Думаю на этом стоит заканчивать статью. Информации всегда много и… всегда мало. Мало, по той причине, что люди, которые знают эту информацию, думают что о ней знают и другие. А может не хотят просто делиться или считают что в этом нет необходимости. Но необходимость есть. Я потратил очень немало времени на сбор капли в море. И сколько ещё капель не собрал?

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

Надеюсь информация была полезной для вас! Всего хорошего!

© Habrahabr.ru