14 Commits

Author SHA1 Message Date
1e09eb6b96 fix: подправил графу про rel 2024-09-19 13:53:42 +03:00
e14b35c2f3 fix: расширена информация по колонке instruction 2024-09-19 13:41:27 +03:00
db6a4b1339 feat: добавлен разбор колонки instructions 2024-09-19 13:31:52 +03:00
3ff51630f5 fix: добавил примечание к инструкции по кодированию 2024-09-18 23:41:20 +03:00
b97816df15 feat: приведена инструкция по кодированию
Это вряд ли исчерпывающее руководство как собирать инструкции, но оно подходит для сборки самых разных инструкций только чтением методички от intel
2024-09-18 23:20:56 +03:00
bfde784868 fix: исправлены неприятные опечатки 2024-09-18 22:27:26 +03:00
aa38e902ae feat: раскрыты обозначения в таблице от intel 2024-09-18 22:14:13 +03:00
5bd7ad95b3 fix: вернул как было 2024-09-18 21:35:54 +03:00
23bbc11bac fix: добавил забытые картинки в репозиторий 2024-09-18 21:35:07 +03:00
9b7dada982 fix: починил отобаржение картинок 2024-09-18 21:34:11 +03:00
c672fd4fcb fix: починил отобаржение картинок 2024-09-18 21:32:24 +03:00
d5a00202e7 feat(utils): расширен функционал hex2bin
Для удобства конструирования пайплайна появился флаг -s, при выставлении которого число читается со стандартного потока ввода, не в качества аргумента в консоли
2024-09-18 18:30:00 +03:00
2f1c3787dd sync(README)[02]: вкинуто немного информации по преффиксу REX и нытья по другим поводам 2024-09-17 18:29:06 +03:00
99b00ebcc0 feat(utils): добавлена простая утилита hex2bin
Утилита hex2bin принимает в качестве аргумента из командной строки число в 16-ричной системе счисления, а возвращает число в 2-ичной

это вспомогательная утилита для удобства сопоставления вывода radare2 и своим ответом
2024-09-17 16:10:47 +03:00
14 changed files with 103 additions and 429 deletions

View File

@ -1,67 +0,0 @@
global main
extern printf
%define CLOCK_REALTIME 0
; struct timespec { time_t tv_sec; long tv_nsec; }
struc timespec
.tv_sec: resq 1
.tv_nsec: resq 1
endstruc
section .note.GNU-stack ; чтобы не жаловался линкер
section .bss
start: ; uses timespec model
times 2 resq 1
finish:
times 2 resq 1
section .data
fstring db "Operations took %ul seconds and %ul milliseconds", 10, 0
flen equ $-fstring
section .text
main: ; лично в моей системе time_t представляет из себя long int
mov rax, 228 ; Системный вызов получения времени
mov rdi, CLOCK_REALTIME
mov rsi, start
syscall
; insert your code here
mov rcx, 20000
looper:
mov rax, start
loop looper
mov rax, 228
mov rdi, CLOCK_REALTIME
mov rsi, finish
syscall
; считаем время для секунда и миллисекунд
; секунды
mov rsi, [finish + timespec.tv_sec]
sub rsi, [start + timespec.tv_sec]
; миллисекунды
mov rdx, [finish + timespec.tv_nsec]
sub rdx, [start + timespec.tv_nsec]
mov rdi, fstring
mov rax, 0
sub rsp, 8
call printf
add rsp, 8
exit:
mov rax, 60
mov rdi, 0
syscall

View File

@ -2,3 +2,87 @@
## Система команд процессора, ее связь с кодами команд ## Система команд процессора, ее связь с кодами команд
## Кодирование команд для x86-64 архитектуры
Преподаватель на ресурсном курсе оставил огромный талмуд Intel (далее именуемый "талмудик" и "талмуд") на тему того, как кодируются команды у их процессоров. И пусть даже наш дорогой препод на лекции дал пояснения по конверсии и прочему, он оставил без ответа вопросы следующего толка: когда какие байты задействованы, где посмотреть опкоды команд и прочие мелочи жизни. Я тот еще программист, поэтому на меня тут не надейтесь, но помогу чем смогу
Перво-наперво структура команды. Приведена она и у препода и в талмудике Intel, повторяться не хочу, но картинку оставлю
![Структура команд](../assets/command_structure.png)
На этой же картинке видно, что может быть от разное количество байт на КОП (который я по привычке именую опкодом), на Displacement, на Immediate и прочем. Да и еще проскакивают надписи `(if required)` и `(optional)`. Вопрос назревает сам собой - а где смотреть-то. И ответ у меня к сожалению не утешительный - в том самом великом и ужасном талмудике (по крайней мере я не нашел другого способа понадежнее). Но тут есть одна так сказать проблемка... Таблички по командам от Intel выглядят мягко скажем как-то так...
![Пример таблицы опкода](../assets/opcode_table_example.png)
Хтонь лично на мой взгляд довольно неприятная, но на самом деле она не так страшна, как вы подумали... Она значительно хуже...
В общем я тут приведу свои пояснения ко всему, что указывают сами Intel, но не в сухую по руководству, а на основе собственного опыта ручного ассемблирования (который, к слову, не очень богат, потому что я еще не успел настолько сойти с ума, чтобы делать работу ассемблера за него)
### Колонка opcode
- `REX.W` - По идее этот префикс может означать много вещей, но на практике пока что я сталкивался с ним только в таком разрезе: если он есть в начале, значит в REX-байте нужно поставить единичку в 3 разряде (4-я цифра справа). Также это означает, что данный байт, вообще говоря, обязателен для функционирования этой команды
- `REX` - Такое встречается, на моей памяти, только рядом с восьмибитными инструкциями и всегда только для того, чтобы к ним тут же приложилось пояснение от intel, что какие-то там проблемы. В общем читайте все это в сносках, потому что сам по себе флаг означает простое наличие REX-байта перед опкодом по всей видимости
- `/digit` - можно порой встретить что-то типа `/0` или `/7`. Когда такое видите, это значит, что в ModR/M байте вместо поля reg нужно записать в двоичной системе то число, которое после слеша. То есть от `000` и до `111`. А все остальное адресуете как раньше
- `/r` - указывает на то, что в ModR/M байте все поля Mod, Reg и R/M используются в стандартном варианте
- `cb`, `cw`, `cd`, `cp`, `co`, `ct` - сам плохо понимаю, что это за покемоны такие. В 64-битных опкодах встречаются редко. Согласно мануалу показывают, сколько битов после опкода следует зарезервировать под смещение для сегмента кода (если вы откроете методичку Милицина, то это тот самый CS или Code Segment). Также согласно талмудику иногда оно может изменить значение сегментному регистру кода. Возможны варианты 1 байт, 2 байта, 4 байта, 6 байт, 8 байт, 10 байт соотвественно.
- `ib`, `iw`, `id`, `io` - Показывают, что после опкода, ModR/M байта (если есть) должен идти непосредственный операнд длиной 1, 2, 4, 8 байт соответственно. Встречается он в таблице обычно там же, где в колонке instruction в соответствующем месте производятся какие-то действия с непосредственными операндами. При чем надо понимать, что нельзя просто опустить байты, которые заполнены нулями, даже если очень хочется и мама разрешила. ставим столько, сколько требует спецификация
- `+rb`, `+rw`, `+rd`, `+ro` - встречается тогда, когда создатели процессора почему-то решили засунуть регистр прям в опкод операнда. Ну, не нам их за это судить. Фактически нам нужно просто глянуть в таблицу которая приведена самими Intel, чтобы определиться только с тем, какое число от 0 до 7 прибавлять. В целом это число является номером регистра, а идут они всегда в следующем порядке: rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi, а также дополнительные регистры r8-r15 работают в том же режиме, то есть начинают нумероваться с нуля. Единственное отличие - бит в REX байте нужно поставить. А вообще табличка должен сказать весьма любопытная, поэтому с ней придется ознакомиться самому. Находится она на 45 странице руководства.
- `+i` - используется в операциях с плавающей точкой. Такие операции любят использовать стек сопроцессора (потому что на самом деле вся арифметика с плавающей точкой аппаратно ускоряется и у нее тоже есть собственная память). Так вот, такой стек обозначается ST(i). Где ST(0) - вершина стека. Не берусь утверждать, но по всей видимости в стеке всего 8 ячеек, потому что по мануалу i может принимать значения от 0 до 7. Соотвественно наша задача просто прибавить это число к байту слева от плюса и на этом все. Больше ничего не требуется
### Колонка instruction
<!---Тут на самом деле говна еще очень много, но я постараюсь описать как можно проще-->
*Обозначений в этой колонке кратно больше, но я буду стараться приводить их кратко. поскольку тут есть регистры на любой вкус и цвет, я буду ставить символ # когда на месте # может быть число от 16 до 64*
- `rel8` - адресация относитльно rip насколько я понимаю. Воспринимается как знаковое число длиной 8 бит, которое прибавится к содержимому rip
- `rel#` - адресация внутри одного сегмента кода (когда в ассемблере начинается `section .text` - это оно). Встречается обычно когда надо обратиться к меткам при прыжках туда и сюда, а оттого встретить можно в `j*` и `call`. Разбиратьсят точнее у меня к сожалению нет возможности
- `r#` - бозначает регистр размером # битов (от 8-битных до 64-битных)
- `m#` - обозначает ячейку памяти размером # бит. периодически можено даже встретить # равное 128, что используется только в SSE и SSE2 инструкциях
- `r/m#` - показывает, что операнд или память (например `[r11 + rcx + 2]`), или регистр (например `rcx`). Если относиться как к памяти, то число `#` указывает не на то, как память адресуется, а сколько битов будет из памяти прочитано, А если как к регистру, то нужно выбирать регистр по размеру, иначе процессор будет жаловаться
- `imm#` - тут `#` может принимать помимо стандартных еще и значение 8. обозначает, что работа ведется с непосредственным операндом (как например в инструкции `mov rax, 12`)
- `moffs#` - сокращение от memory offset - весьма любопытно. Показывает, что программа не использует ModR/M байт. Встречается только в некоторых вариациях `mov`, а адрес задается в качестве смещения относительно базового сегмента (не уверен, что это означает, но думаю относительно сегмента, где сейчас находится rip). Сами интел например на `mov al, moffs8` пишет следующее описание *"Move byte at (seg:offset) to A"*. Также надо понимать, что # задает лишь сколько байтов будет прочитано, а offset задается вполне себе 4 байтами после опкода
- `Sreg` - указывает на использование сегментного регистра. Битовые присвоения для сегментных регистров таковы: e ES = 0, CS = 1, SS = 2, DS = 3, FS = 4, и GS = 5
Остальные инструкции здесь не привожу, поскольку они затрагивают работу с числами с плавающей точкой, векторами и прочими радостями, до которых надеюсь не дойдет
## Как собрать ~~своего покемона~~ свою команду из ассемблера
*оно же: "Да как этой б\*\*\*ской таблицей пользоаться вообще*
![Структура команд](../assets/command_structure.png)
Повторно привожу это изображение. Оно показывает общую структуру команды для процессора. Далее в инструкции я буду обращаться к разным участкам этой команды применяя соответствующие обозначения. Для примера команды можете смотреть на табличку для add в качестве примера того, что можно там увидеть
Самое важное чиселко, которое тут есть - 16-ричное породы "понятно-написанное". Оно - фундамент всего опкода, его мы и берем. А дальше алгоритм следующий:
1. Смотрим, колонку instruction. В ней ищем глазами базу и венец - понятно написанный опкод (это будет скорее всего от двух и до шести 16-ричных цифр). Дальше смотрим, надо ли к нему непосредственно что-то прибавать, и если надо - прибавляем. Ура - мы получили opcode.
2. Как только мы получили опкод, начинаем смотреть налево - если есть приписка REX.W, значит пишем REX байт. Пока что просто ставим заглушку: 0b01001000 или 0x48. Он нам потребуется если мы захотим адресоваться к регистрам с r8 по r15, а до тех пор он будет в заглушечном состоянии. На будущее также отмечу, что выглядит этот байт в общем случае примерно так - 0100 WRXB, где 0100 - обязательная часть, а все к остальным битам я буду адресоваться через точку
3. Далее присматриваемся, надо ли что-то прорезервировать (те самые `cb`) и если непосредственно после опкода ничего не требуется начинаем писать ModR/M байт
4. Написание ModR/M байта пожалуй самый запутанный процесс, но с ним нам должна помочь табличка от Intel. Находятся они в районе 32-34 страниц. Но расскажу так. Поля у ModR/M следующие - 2 битовый mod, потом 3 битный reg, потом 3 битный r/m.
1. mod - указывает на то, как будет адресоваться r/m (третье поле). r/m - сокращение от register/memory. То есть как можно из названия догадаться, только в этом поле процессор может адресовать память компьютера. Это же и есть фундаментальная причина, по которой нельзя заассемблировать команду вроде `mov [addr1], [addr2]`. Возможное содержимое этого поля такого: `00` - Будет адресоваться оперативная память, при чем использоваться будут только регистры (`mov rax, [rdi + rsi*4]`). `01` - будет адресоваться оперативная память и помимо регистра будет еще и смещение, но длиной не больше 1 байта (`add [rbp + rcx - 2], rax`). `10` - то же, что и `01`, но смещение уже занимает 4 байта. `11` - адресоваться будут 2 регистра (`xor rax, rbx`). Если мы поставили что угодно кроме `11` - это надо запомнить, потому что число которое мы записали нужно будет включить в двоичный вид команды использовав взятое нами число байт, а разместить их надо будет либо после ModR/M, либо после SIB байта, если последний будет.
2. reg - указывает регистр, если в колонке opcode не стоит что-то из разряда `/4`. Если стоит, то в reg записывается это число в двоичной форме. Все неиспользованные разряды заполняются нулями. Если длина регистра не вмещается, то самую старшую единицу можно поставить в REX.R
3. R/M - указывает регистр или участок в памяти, который будет адрессоваться. Можем писать сюда номер любого регистра (если не влезет, то расширять его при помощи X бита в REX байте). Однако особенностью тут обладает значение `100`, потому что оно показывает процессору, что нужен SIB байт. При любом другом значении адрес (если mod != 0b11) будет взят из регистра и если есть смещение, оно будет добавлено к значению этого адреса. Единственное, что если mod взят 0b00, то значение 101 тоже магическое - оно затребует 32-битное смещение и использует его в качестве адреса (это точно сработает в 32 битном режиме, но не уверен, что сработает в 64 битах)
5. Если вы взяли такую комбинацию, что вам понадобился SIB байт (а это mod != 11, r/m == 100), то разберемся со структурой байта. Весь этот байт завязан исключительно на адресацию в памяти и состоит из трех побей Scale (SS) в 2 бита, index в 3 бита, base и 3 бита. для разбора полей возьмем следующий пример `[rbx + rcx * 4 + 3]`
1. SS - это scale - это то, на что будет умножаться регистр индекса, при чем это двухбитовое число можно считать степенью двойки. То есть получается 2^(SS) - это коэфициент на который мы умножаем и можем получается умножить на 1, 2, 4 и 8.
2. index - это регистр, который будет умножаться на 2^(SS). Если вы хотите вписать регистры с r8-r15, то невлезающую единицу можно записать в REX.X. В целом же связка SS и index и обеспечивает наличие в адресе в нашем примере `rcx * 4`
3. base - указывает на регистр, значение которого в лоб прибавится к адресу, то есть в нашем примере он отвечает за `rbx`. Если базовый регистр не нужен, на его место ставится `101`
6. После SIB идет displacement байты (1 или 4 в зависимости от поля mod в ModR/M). Заполняем их согласно выделенному количеству
7. После dispacement идет immediate байты - они могут встретиться если в табличке в колонке opcode на этой строке есть что-то похожее на `ib` или другие, которые мы упоминали. заполняем сколько надо
NOTE: По какой-то причине это не указано в руководстве Intel, но по крайней мере если верить сайту, который я использовал для ассемблирования инструкций, то нужно обязательно учитыать префиксы к опкоду прежде чем начинать кодировать (опять же все по опыту):
1. `0x67` - ставится если команда **адресуется** при помощи 32-битных регистров
2. `0x66` - ставится, если программа иссользует хотя бы 1 16-битный регистр
Благо для 8-битных операций другие опкоды и хотя бы на них не надо префиксы запоминать)
<!--- Пока что я думаю эта инфа лишняя, может потом верну и раскомментирую
### Чутка про префикс REX
Судя по всему, префикс REX стал почти обязателен при переходе на 64 разряда. Что ж, это не удивительно, так как в 64-разрядных системах прибавилось регистров, а их номера нужно где-то и как-то хранить, поэтому это вот такой вот "костылик". На самом деле в талмудике преведены схемы всех подключений, которые я пока не привожу, потому что это не самое главное, но может потом добавлю
Вот что они пишут про префик REX во второй главе своего талмуда: "Префикс REX указывается не всегда в 64-разрядном режиме. Он необходим только тогда, когда инструкция адресуется к одному из рассширенных регистров или использует 64-разрядные операнды". То есть условно говоря если работам с 64 разрядами, то RAX нужен, а если нет - его может и не быть, если явно не сказано иное. Сами же REX - это 16 опкодов, которые берут пространство от 0x40 до 0x4F. В режиме режиме обратной совместимости и IA-32 отражают опкоды реальных инструкций, но нас естественно интересует режим 64 разрядов, а в нем они как отдельная инструкция не трактуются и идут только в связке. Также почему-то интел сокрушаются, что из-за этого однобайтовый опкод для инкреммента и декремента перестал существовать в 64 разрядных системах
-->

18
02-cpu-commnads/hex2bin.py Executable file
View File

@ -0,0 +1,18 @@
#!/usr/bin/python3
import argparse
def hex2bin(hexNum: str) -> None:
return bin(int(hexNum, 16))
parser = argparse.ArgumentParser(description="convert hex number to bin right in terminal")
parser.add_argument('-s', '--stdin', action='store_true', help="if passed takes input from terminal arguments")
parser.add_argument('number', nargs='?')
args = parser.parse_args()
if args.stdin:
print(hex2bin(input()))
elif args.number:
print(hex2bin(args.number))
else:
print("something went wrong")

View File

@ -1,13 +0,0 @@
ASM = nasm
ASM_FLAGS = -felf64 -g
LINK = ld
%: %.o
$(LINK) -o $@ $^
%.o: %.asm
$(ASM) $(ASM_FLAGS) $^ -o $@
clean:
rm -f *.o
rm -f $(subst .asm, $(empty), $(wildcard *.asm))

View File

@ -1,24 +0,0 @@
ASM = nasm
CXX = gcc
CXX_FLAGS = -Og -static
ASM_FLAGS = -felf64 -g
LINK = ld
task3: task3_c.o task3.o
$(CXX) -Og $^ -o $@ -g
task3_c.o: task3.c
$(CXX) -Og -c $^ -o $@ -g
task2: task2.o
$(CXX) $(CXX_FLAGS) $^ -o $@
%: %.o
$(LINK) -o $@ $^
%.o: %.asm
$(ASM) $(ASM_FLAGS) $^ -o $@
clean:
rm -f *.o
rm -f $(subst .asm, $(empty), $(wildcard *.asm))

View File

@ -1,4 +1,4 @@
# Лабораторная работа 4 # Лабораторная работа 4
На данный момент нормального README не будет, потому что у меня немного нет времени его офомить. Как только сдам лабу - напишу тут немного больше ## Способы адресации и сегментная организация памяти

View File

@ -1,33 +0,0 @@
global _start
section .data
%macro FILL_ASC 1
%assign NUM 0
%rep %1
db NUM
%assign NUM NUM + 1
%endrep
%endmacro
example: FILL_ASC 256
section .text
_start:
; В качестве базы возьму inc
; регистровая
inc ecx
mov rax, example
; косвенно-регистровая
inc byte [rax]
; "Индексно-базовая", хотя у меня почти все может быть базой
inc byte [rax + rbx]
; "Индексно-базовая" со смещением
inc byte [rax + rbx + 122]
; Ну в целом... все
mov rax, 60
mov rdi, 0
syscall

View File

@ -1,139 +0,0 @@
global main
extern printf
struc timespec ; структура, в которой линукс хранит время. Тут нужна для удобства в будущем
.tv_sec: resq 1
.tv_nsec: resq 1
endstruc
%include "timer.inc"
section .note.GNU-stack
section .data
example: times 128 db 127
section .bss
; uses timespec model
start: resq 2
finish: resq 2
deltatime: resq 2
section .text
%macro PUSH_M 1-*
%rep %0
push %1
%rotate 1
%endrep
%endmacro
%macro RPOP_M 1-*
%rotate -1
%rep %0
pop %1
%rotate -1
%endrep
%endmacro
%define CLOCK_REALTIME 0
%macro TIME_1_000_000 0-1+ ; принимает команду, которую будет пытаться обмерить по времени
PUSH_M rax, rdi, rsi, rcx
mov rax, 228 ; Время начала
mov rdi, CLOCK_REALTIME
mov rsi, start
syscall
RPOP_M rax, rdi, rsi, rcx
mov rcx, 10000000000; выполняем миллион раз
%%loop:
%1
loop %%loop
PUSH_M rax, rdi, rsi, rcx
mov rax, 228 ; Время конца
mov rdi, CLOCK_REALTIME
mov rsi, finish
syscall
RPOP_M rax, rdi, rsi, rcx
; считаем секунды
push rax ; можно было бы оптимизировать, но мне лень макросы переписывать
mov rax, [finish + timespec.tv_sec]
sub rax, [start + timespec.tv_sec]
mov [deltatime + timespec.tv_sec], rax
; считаем наносекунды
mov rax, [finish + timespec.tv_nsec]
sub rax, [start + timespec.tv_nsec]
jns %%save_result
dec qword [deltatime + timespec.tv_sec] ; занимаем миллиард наносекунд
add rax, 1000000000 ; прибавляем занятый разряд
%%save_result:
mov [deltatime + timespec.tv_nsec], rax
pop rax
%endmacro
%macro PRINT_DELTATIME 1
;sub rsp, 8
mov rdi, str_template
mov rsi, %1
mov rdx, [deltatime + timespec.tv_sec]
mov rcx, [deltatime + timespec.tv_nsec]
call printf
;add rsp, 8
%endmacro
main:
push rbp
mov rbp, rsp
sub rsp, 16
xor rax, rax ; поскольку приходим сюда из компилятора, лучше обнулить
TIME_1_000_000
PRINT_DELTATIME nop_command
TIME_1_000_000 inc rax
PRINT_DELTATIME reg_command
mov rax, example
TIME_1_000_000 inc byte [rax]
PRINT_DELTATIME rel_reg
mov rax, example
xor rbx, rbx
TIME_1_000_000 inc byte [rax + rbx]
PRINT_DELTATIME ind_base
mov rax, example
xor rbx, rbx
TIME_1_000_000 inc byte [rax + rbx + 122]
PRINT_DELTATIME ind_base_disp
; Под конец давайте посчитаем тактовую частоту на примере той же самой команды
rdtsc
mov [rbp - 4], edx
mov [rbp - 8], eax
mov rcx, 10000000000
mov rax, example
xor rbx, rbx
.loop:
inc byte [rax + rbx + 122]
loop .loop
rdtsc
sub eax, [rbp - 8]
sbb edx, [rbp - 4]
mov [rbp - 8], eax
mov [rbp - 4], edx
mov [rbp - 16], rsp
and rsp, -16
mov rdi, tick_count
mov rsi, [rbp - 8]
call printf
mov rsp, rbp
pop rbp
xor rax, rax ; сообщаем gcc, что все закончилось успешно
ret

View File

@ -1,96 +0,0 @@
global fill_arr1
global fill_arr2
section .note.GNU-stack
section .text
%macro PUSH_M 1-*
%rep %0
push %1
%rotate 1
%endrep
%endmacro
%macro RPOP_M 1-*
%rotate -1
%rep %0
pop %1
%rotate -1
%endrep
%endmacro
fill_arr1:
push rbp
mov rbp, rsp
PUSH_M rdi, rsi, rdx
; Вычисляем сколько числе в строке
mov rax, [rbp - 16]
xor rdx, rdx
div qword [rbp - 24]
push rax ; сохраняем в локальные переменные. rbp - 32
; Вычисляем сколько проходов цикла необходимо
mov rax, [rbp - 24]
xor rdx, rdx
mov rcx, 2
div rcx
push rax ; rbp-40
; Надеюсь rdi не успел поменяться
; заполняем память
push rbx
mov rbx, [rbp - 32]
mov rcx, [rbp - 40]
mov rax, 777 ; специально такое число, чтобы выделялось
.next_row:
push rcx
mov rcx, [rbp - 32]
rep stosd
lea rdi, [rdi + 4 * rbx] ; пропускаем строку
pop rcx
loop .next_row
pop rbx
add rsp, 16 ; чистим 2 доп переменные, образовавшиеся в процессе вычислений
RPOP_M rdi, rsi, rdx
pop rbp
ret
fill_arr2:
push rbp
mov rbp, rsp
PUSH_M rdi, rsi, rdx
; Вычисляем сколько числе в строке
mov rax, [rbp - 16]
xor rdx, rdx
div qword [rbp - 24]
push rax ; сохраняем в локальные переменные. rbp - 32
; Вычисляем сколько проходов цикла необходимо
mov rax, [rbp - 24]
xor rdx, rdx
mov rcx, 2
div rcx
push rax ; rbp-40
; Надеюсь rdi не успел поменяться
; заполняем память
push rbx
mov rbx, [rbp - 32]
mov rcx, [rbp - 40]
mov rax, 777 ; специально такое число, чтобы выделялось
.next_row:
push rcx
mov rcx, [rbp - 32]
.fill:
mov [rdi], rax
lea rdi, [rdi + 4]
loop .fill
lea rdi, [rdi + 4 * rbx] ; пропускаем строку
pop rcx
loop .next_row
pop rbx
add rsp, 16 ; чистим 2 доп переменные, образовавшиеся в процессе вычислений
RPOP_M rdi, rsi, rdx
pop rbp
ret

View File

@ -1,43 +0,0 @@
#include <stdio.h>
#include <time.h>
extern void fill_arr1(int* arr, size_t size, size_t row_count);
extern void fill_arr2(int* arr, size_t size, size_t row_count);
double measure_fill_time(void(*function)(int*, size_t, size_t), int* arr, size_t size, size_t row_count)
{
const size_t times = 10000000;
clock_t begin = clock();
for (size_t i = 0; i < times; i++)
{
function(arr, size, row_count);
}
clock_t end = clock();
return (double)(end - begin)/(CLOCKS_PER_SEC);
}
int main()
{
const int arr_size = 256;
int array1[arr_size];
printf("String methods took %fs to loop 10,000,000 times\n", measure_fill_time(fill_arr1, array1, arr_size, 16));
for (size_t i = 0; i < arr_size; i++)
{
printf("%d ", array1[i]);
}
printf("\b \n");
int array2[arr_size];
printf("Lea methods took %fs on to loop 10,000,000 times\n", measure_fill_time(fill_arr2, array2, arr_size, 16));
for (size_t i = 0; i < arr_size; i++)
{
printf("%d ", array2[i]);
}
printf("\b \n");
return 0;
}

View File

@ -1,13 +0,0 @@
section .data
str_template: db "Command %s took %lld seconds and %lld nanoseconds to execute 1 000 000 000 times", 10, 0
template_len equ $-str_template
nop_command: db '`empty loop`', 0
reg_command: db '`inc ebx`', 0
rel_reg: db '`inc byte [rax]`', 0
ind_base: db '`inc byte [rax + rbx]`', 0
ind_base_disp: db '`inc byte [rax + rbx + 122]`', 0
tick_count: db 'Last command also took %lli ticks to complete', 10, 0

BIN
assets/REX_scheme.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
assets/command_structure.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/opcode_table_example.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB