Compare commits
12 Commits
1e09eb6b96
...
labs/04
| Author | SHA1 | Date | |
|---|---|---|---|
| 6794efeedd | |||
| 414f189d18 | |||
| 4b05989ba5 | |||
| e86f3701fe | |||
| a309019bdb | |||
| f2ca981037 | |||
| 0aed286bcb | |||
| 541524bd6f | |||
| df80a7190e | |||
| 4594912561 | |||
| 95df24eaa5 | |||
| 53a82c8ea0 |
67
01-asm-basics/time.asm
Normal file
67
01-asm-basics/time.asm
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
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
|
||||||
|
|
||||||
@ -2,87 +2,3 @@
|
|||||||
|
|
||||||
## Система команд процессора, ее связь с кодами команд
|
## Система команд процессора, ее связь с кодами команд
|
||||||
|
|
||||||
## Кодирование команд для x86-64 архитектуры
|
|
||||||
|
|
||||||
Преподаватель на ресурсном курсе оставил огромный талмуд Intel (далее именуемый "талмудик" и "талмуд") на тему того, как кодируются команды у их процессоров. И пусть даже наш дорогой препод на лекции дал пояснения по конверсии и прочему, он оставил без ответа вопросы следующего толка: когда какие байты задействованы, где посмотреть опкоды команд и прочие мелочи жизни. Я тот еще программист, поэтому на меня тут не надейтесь, но помогу чем смогу
|
|
||||||
|
|
||||||
Перво-наперво структура команды. Приведена она и у препода и в талмудике Intel, повторяться не хочу, но картинку оставлю
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
На этой же картинке видно, что может быть от разное количество байт на КОП (который я по привычке именую опкодом), на Displacement, на Immediate и прочем. Да и еще проскакивают надписи `(if required)` и `(optional)`. Вопрос назревает сам собой - а где смотреть-то. И ответ у меня к сожалению не утешительный - в том самом великом и ужасном талмудике (по крайней мере я не нашел другого способа понадежнее). Но тут есть одна так сказать проблемка... Таблички по командам от Intel выглядят мягко скажем как-то так...
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Хтонь лично на мой взгляд довольно неприятная, но на самом деле она не так страшна, как вы подумали... Она значительно хуже...
|
|
||||||
|
|
||||||
В общем я тут приведу свои пояснения ко всему, что указывают сами 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
|
|
||||||
|
|
||||||
Остальные инструкции здесь не привожу, поскольку они затрагивают работу с числами с плавающей точкой, векторами и прочими радостями, до которых надеюсь не дойдет
|
|
||||||
|
|
||||||
## Как собрать ~~своего покемона~~ свою команду из ассемблера
|
|
||||||
|
|
||||||
*оно же: "Да как этой б\*\*\*ской таблицей пользоаться вообще*
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Повторно привожу это изображение. Оно показывает общую структуру команды для процессора. Далее в инструкции я буду обращаться к разным участкам этой команды применяя соответствующие обозначения. Для примера команды можете смотреть на табличку для 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 разрядных системах
|
|
||||||
-->
|
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
#!/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")
|
|
||||||
|
|
||||||
13
03-asm-bios/Makefile
Normal file
13
03-asm-bios/Makefile
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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))
|
||||||
24
04-addr-methods/Makefile
Normal file
24
04-addr-methods/Makefile
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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))
|
||||||
@ -1,4 +1,4 @@
|
|||||||
# Лабораторная работа 4
|
# Лабораторная работа 4
|
||||||
|
|
||||||
## Способы адресации и сегментная организация памяти
|
На данный момент нормального README не будет, потому что у меня немного нет времени его офомить. Как только сдам лабу - напишу тут немного больше
|
||||||
|
|
||||||
|
|||||||
33
04-addr-methods/task1.asm
Normal file
33
04-addr-methods/task1.asm
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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
|
||||||
|
|
||||||
139
04-addr-methods/task2.asm
Normal file
139
04-addr-methods/task2.asm
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
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
|
||||||
|
|
||||||
96
04-addr-methods/task3.asm
Normal file
96
04-addr-methods/task3.asm
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
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
|
||||||
43
04-addr-methods/task3.c
Normal file
43
04-addr-methods/task3.c
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
13
04-addr-methods/timer.inc
Normal file
13
04-addr-methods/timer.inc
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 142 KiB |
Reference in New Issue
Block a user