Files
solutions/01-asm-basics/time.asm
Miheev Egor e219503f22 docs: документирование ассемблерной части
- Добавил комментарии в time.asm

- Добавил секцию про ассемблер в README
2024-09-14 16:12:58 +03:00

98 lines
5.7 KiB
NASM
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

; Эта директива делает функцию видимой.
; По умолчанию в ассемблере используется _start,
; но поскольку для вывода на экран я пользуюсь
; С'шной функцией prinf, для корректного подключения библиотек на этапе линковки
global main
; Объявляю, что буду ссылаться на метку printf, которой нет внутри кода программы
; extern вообще обзначает, что метка объявлена где-то еще
extern printf
; тут объявлен макрос CLOCK_REALTIME, который на этапе ассемблирования заменится на число 0
; Использован он тут, так как является clock_id, о котором будет сказано позже. И я не уверен
; что на всех системах это число будет одинаково. Свое я посмотрел в файлах компилятора.
%define CLOCK_REALTIME 0
; так в ассемблере задаются структуры. Существуют они лишь на уровне препроцессора
; да и применение их весьма специфично. Но подробнее лучше погуглите
; struct timespec { time_t tv_sec; long tv_nsec; } - это шаблон из C
struc timespec
.tv_sec: resq 1
.tv_nsec: resq 1
endstruc
section .note.GNU-stack ; чтобы не жаловался линкер
; Секция с данными, ее особенность в том, что нужно указать лишь сколько нужно зарезервировать
section .bss
; вообще можно было бы использовать istruc и создать эти 2 структуры в .data, но я решил,
; что не хочу тратить время на инициализацию того, что и так будет перезаписано
; обе эти инструкции просто нужны чтобы застолбить по 16 памяти на каждый замер времени
; потому что time_t и long имеют размер 8 байт, а поля 2
start:
resq 2
finish:
resq 2
; Секция с данными, которые заранее заполняются чем-то
section .data
fstring db "Operations took %ul seconds and %ul nanoseconds", 10, 0 ; строки стиля C должны оканчиваться нулем
flen equ $-fstring ; длина строки. $ - это текущий адрес. Подробнее не буду рассказывать - мне лень
section .text
main: ; лично в моей системе time_t представляет из себя long int
mov rax, 228 ; Системный вызов получения времени
mov rdi, CLOCK_REALTIME
mov rsi, start
syscall
; здесь место для кода под замер времени
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]
; вызываем функцию printf. Согласно соглашению о вызовах fastcall
; при вызове функций для передачи аргументов используются регистры по порядку следования аргументов
; rdi, rsi, rdx, rcx, r8, r9, а остальные пушатся в ассемблер.
; Свои заморочки там с числами с плавающей точкой, но об этом не сейчас
mov rdi, fstring
mov rax, 0
; Вот тут все во имя выравнивания стека. Об этом я сейчас рассказывать не буду, только если попросят в readme чиркану
sub rsp, 8
; собственно вызов функции. На самом деле это обычный jmp, который предварительно пушит в стек адрес возврата.
; в будущем будьте аккуратнее с этими приколами, потому что при встрече ключевого слова ret ассемблер всегда.
; подчеркиваю ВСЕГДА прочитает 8 байт со стека и передаст туда управление. И как бы что там будет - одному богу ведомо
; Так что в ваших же интересах следить за тем, чтобы в стеке лежали правильные байты
call printf
; поскольку выравнивание больше не нужно, возвращаем стек в исходное состояние
add rsp, 8
exit:
; Тут происходит системный вызов выхода из приложения. Если его не увидит
; linux, то он решит, что программа завершилась аварийно
mov rax, 60
mov rdi, 0 ; код ошибки. если вернется 0 - считается, что ошибок не произошло
syscall