Цель состоит в том, чтобы напечатать время и дату, но пока я не могу даже напечатать метку времени (время эпохи).
Подробности:
System: Linux
Assembler: NASM (Intel syntax)
Arch: x86_64
Версия 1:
time_t t = time(NULL);
printf("%ld\n");
время.насм:
global _start
section .text
_start:
mov rax, 201 ; sys_time
xor rdi, rdi
syscall
mov rsi, rax ; store return value in rsi (arg2) for sys_write
mov rax, 1 ; sys_write
mov rdi, 1
mov rdx, 64 ; How to get the proper size here?
syscall
jmp exit
exit:
mov rax, 60
xor rdi, rdi
syscall
Версия 2:
time_t t;
time(&t);
printf("%ld\n");
время.насм:
global _start
section .text
_start:
mov rax, 201 ; sys_time
mov rdi, time
syscall
mov rax, 1
mov rdi, 1
mov rsi, time
mov rdx, 64 ; How to get the proper size here?
syscall
jmp exit
exit:
mov rax, 60
xor rdi, rdi
syscall
section .data
time: dq 0x0
Компиляция и линковка:
nasm -g -f elf64 time.nasm -o time.o && ld time.o -o time && ./time
Вопрос:
Я не могу закрыть это, потому что есть награда, но версия 1 в значительной степени дублирует этот вопрос, который имеет очень хороший ответ с кодом (вам просто нужно преобразовать синтаксис GAS AT&T в NASM), чтобы написать 64-битное значение в десятичном виде: stackoverflow.com/questions/45835456/…
@MichaelPetch: у меня есть NASM-версия практически того же ответа на Как мне напечатать целое число в программировании на уровне ассемблера без printf из библиотеки c?, а также x86-64 Linux. (Он использует 32-битный размер операнда, но его легче изменить, чем портировать AT&T на NASM).
Основная трудность заключается в преобразовании целого числа в строку. Либо используйте существующую процедуру (например, printf
), чтобы сделать эту часть за вас, либо реализуйте алгоритм преобразования вручную. Последнее уже хорошо описано в ответе, на который ссылается Майкл Петч, поэтому я не уверен, чего еще вы ожидаете, опубликовав награду.
Для этого написал ассемблерную программу из 200+ строк. Сначала печатается отметка времени, а затем форматированные дата и время. Все его функции следуют соглашению о вызовах System-V x64.
global _start
section .rodata
strings: ; '\n', ' ', '/', ':'
db 0x1, 0xa, 0x1, 0x20
db 0x1, 0x2f, 0x1, 0x3a
weekdays: ; weekday strings
db 0x3, 0x54, 0x68, 0x75
db 0x3, 0x46, 0x72, 0x69
db 0x3, 0x53, 0x61, 0x74
db 0x3, 0x53, 0x75, 0x6e
db 0x3, 0x4d, 0x6f, 0x6e
db 0x3, 0x54, 0x75, 0x65
db 0x3, 0x57, 0x65, 0x64
months: ; length of months
db 0x1f, 0x1c, 0x1f, 0x1e
db 0x1f, 0x1e, 0x1f, 0x1f
db 0x1e, 0x1f, 0x1e, 0x1f
section .text
_start:
push rbx ; align stack
mov rax, 201 ; sys_time
xor rdi, rdi
syscall
mov rbx, rax
; you may uncomment the following line and put an arbitary timestamp to test it out
; mov rbx, 0
mov rdi, rbx
call print_num ; print unix timestamp
mov rdi, strings
call sys_print ; new line
mov rdi, rbx
call print_time ; print formatted date
pop rbx ; since we are exiting, we don't need this pop actually
mov rax, 60 ; sys_exit
xor rdi, rdi
syscall
leap_year: ; rsi + (year in rdi is leap)
mov rax, rdi
mov rcx, 4
xor rdx, rdx
div rcx
test rdx, rdx ; return 0 if year % 4
jnz func_leap_year_ret_0
mov rax, rdi
mov rcx, 100
xor rdx, rdx
div rcx
test rdx, rdx ; return 1 if year % 100
jnz func_leap_year_ret_1
mov rax, rdi
mov rcx, 400
xor rdx, rdx
div rcx
test rdx, rdx ; return 0 if year % 400
jnz func_leap_year_ret_0
func_leap_year_ret_1:
lea rax, [rsi + 1]
ret
func_leap_year_ret_0:
mov rax, rsi
ret
year_length: ; length of year in rdi
mov rsi, 365
jmp leap_year
month_length: ; length of month (year in rdi, month in rsi)
push r15
push r14
push r13
mov r14, rsi ; back up month in r14, will be used as index
cmp rsi, 1
setz r15b
movzx r13, r15b
xor rsi, rsi
call leap_year
and r13, rax
movzx rax, byte [r14 + months]
add rax, r13
pop r13
pop r14
pop r15
ret
print_time: ; print time_t in rdi
push r15
push r14
push r13
push r12
mov r14, 1970 ; 1970-01-01T00:00:00Z
xor r15, r15
mov rcx, 60
mov rax, rdi
xor rdx, rdx
div rcx
push rdx ; push #5
xor rdx, rdx
div rcx
push rdx ; push #6
mov rcx, 24
xor rdx, rdx
div rcx
push rdx ; push #7, the last one
mov r12, rax
mov r13, rax
func_print_time_loop_1_start:
mov rdi, r14
call year_length
cmp r13, rax
jb func_print_time_loop_2_start
sub r13, rax
inc r14
jmp func_print_time_loop_1_start
func_print_time_loop_2_start:
mov rdi, r14
mov rsi, r15
call month_length
cmp r13, rax
jb func_print_time_loop_end
sub r13, rax
inc r15
jmp func_print_time_loop_2_start
func_print_time_loop_end:
; print time
mov rdi, [rsp]
call print_num
mov rdi, strings + 6
call sys_print
mov rdi, [rsp + 8]
call print_num
mov rdi, strings + 6
call sys_print
mov rdi, [rsp + 16]
call print_num
; print " "
mov rdi, strings + 2
call sys_print
; print weekday
mov rax, r12
mov rcx, 7
xor rdx, rdx
div rcx
lea rdi, [rdx * 4 + weekdays]
call sys_print
; print " "
mov rdi, strings + 2
call sys_print
; print date
mov rdi, r15
inc rdi
call print_num
mov rdi, strings + 4
call sys_print
mov rdi, r13
inc rdi
call print_num
mov rdi, strings + 4
call sys_print
mov rdi, r14
call print_num
; print new line
mov rdi, strings
call sys_print
add rsp, 24
pop r12
pop r13
pop r14
pop r15
ret
print_num: ; print number in rdi
mov r8, rsp
sub rsp, 24 ; 21 bytes for local storage, with extra 3 bytes to keep stack aligned
xor r9, r9
mov rax, rdi
mov rcx, 10
func_print_num_loop_start:
dec r8
xor rdx, rdx
div rcx
add dl, 48
mov [r8], dl
inc r9b
test rax, rax
jnz func_print_num_loop_start
func_print_num_loop_end:
dec r8
mov [r8], r9b
mov rdi, r8
call sys_print
add rsp, 24 ; deallocate local storage, restore rsp
ret
sys_print: ; print a string pointed by rdi
movzx rdx, byte [rdi]
lea rsi, [rdi + 1]
mov rdi, 1 ; stdout
mov rax, 1 ; write
syscall
ret
print_num
функция печатает любое число в регистре rdi
. Если вы хотите узнать, как я печатаю число, вы можете посмотреть на эту функцию.
print_time
- это место, где вычисляются и распечатываются дата и время.
Вот вывод, а также вывод программы C, которая печатает отформатированную дату и время, используя asctime(gmtime(time_t t))
$ ./time && ./ct
1608515228
1:47:8 Mon 12/21/2020
Unix time: 1608515228
C library returns: Mon Dec 21 01:47:08 2020
(Последние две строки взяты из программы на C)
Вы также можете поместить любую временную метку в строку 34, чтобы проверить ее.
Мое решение очень наивно:
Редактировать:
Вставил всю программу в этот ответ, как просили несколько человек.
Для печати целой части я использовал реализацию от @PeterCordes здесь:
https://stackoverflow.com/a/46301894
Ваш print_num
имеет 0
как особый случай, но другой способ выполнения цикла может избежать этого: если вы используете структуру цикла do{digit = x % 10; x/=10; ... }while(x!=0);
, вы сохраняете один ноль, как и для ввода 1..9, потому что 0 % 10
равно 0. Мой ответ на Как мне напечатать целое число в программировании на уровне ассемблера без printf? делает это так. Кроме того, сохранение байта длины для перезагрузки оболочки системного вызова кажется слишком сложным. Мне нравится, что вы сохраняете в обратном порядке от конца буфера с коротким стеком; часто люди усложняют это с помощью циклов push/pop.
Однако сторонние ссылки на код не одобряются Stack Overflow. Я бы предложил поставить хотя бы ключевые части этого вопроса прямо в вопрос. Вы можете сохранить ссылку, но ответ все равно должен работать / быть чем-то полезным, если ссылка исчезнет. Возможно, ваше текстовое описание алгоритма форматирования времени полезно без кода.
@PeterCordes Спасибо за просмотр кода! Да, цикл while выглядит хорошо, ты гений. Я должен был сделать это LOL. О внешних ссылках: на самом деле я могу разместить здесь все, но не будет ли это делать этот ответ слишком длинным?
Вы всегда хотите, чтобы циклы выполнялись{}во время ассемблера, когда это возможно; обычно легко для циклов, которые всегда могут выполнить хотя бы 1 итерацию. Это один из довольно редких случаев, когда условие наверху не только менее эффективно, но и фактически создает проблемы, которые нужно обойти. И спасибо, определение хороших способов написания кода приходит с опытом оптимизации (включая просмотр вывода компилятора, что иногда полезно), но я бы не отказался от того, чтобы меня называли гением :P
re: длина ответов: вот почему я предложил вытащить некоторые ключевые части кода в качестве примеров, а вы сохраните текстовые пояснения к общей картине и ссылку. Но если бы вы поместили все в один гигантский блок кода, Stack Overflow предоставил бы блоку кода собственную полосу прокрутки. Однако это не очень хорошо для удобочитаемости, поэтому, по крайней мере, печатный целочисленный код в отдельном блоке, потому что он отвечает на отдельную часть вопроса. (Хотя эта часть является дубликатом существующих вопросов и ответов для печати целых чисел, поэтому вы можете просто связать другие ответы SO.)
@PeterCordes А, да, ты прав. Я отредактировал ответ и добавил ваш ответ.
@SepRoland Хорошо, я вставил код в этот ответ. Но я не знаю, почему вы и другие люди хотели, чтобы я это сделал. Есть ли за этим какая-то причина?
Если Github выйдет из строя; выходит из бизнеса; или вы удаляете проект, тогда код в ответе все еще полезен.
@MichaelPetch А, понятно. Спасибо. Я никогда не думал о возможности того, что Github может выйти из бизнеса ... тогда в моих будущих ответах я не буду предоставлять внешние ссылки, когда это возможно.
Вам нужно преобразовать из двоичного в текстовый. Вы не делаете то, что
printf("%ld")
делаете, вы делаетеwrite(&t)
.