; Эта директива делает функцию видимой. ; По умолчанию в ассемблере используется _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