Распечатать возвращаемое значение из системного вызова в сборке

Цель состоит в том, чтобы напечатать время и дату, но пока я не могу даже напечатать метку времени (время эпохи).

Подробности:

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. Как получить возвращаемое значение в обоих решениях?
  2. Есть ли способ получить дату и время уже отформатированные?

Вам нужно преобразовать из двоичного в текстовый. Вы не делаете то, что printf("%ld") делаете, вы делаете write(&t).

Jester 17.12.2020 12:09

Я не могу закрыть это, потому что есть награда, но версия 1 в значительной степени дублирует этот вопрос, который имеет очень хороший ответ с кодом (вам просто нужно преобразовать синтаксис GAS AT&T в NASM), чтобы написать 64-битное значение в десятичном виде: stackoverflow.com/questions/45835456/…

Michael Petch 19.12.2020 16:48

@MichaelPetch: у меня есть NASM-версия практически того же ответа на Как мне напечатать целое число в программировании на уровне ассемблера без printf из библиотеки c?, а также x86-64 Linux. (Он использует 32-битный размер операнда, но его легче изменить, чем портировать AT&T на NASM).

Peter Cordes 19.12.2020 19:33

Основная трудность заключается в преобразовании целого числа в строку. Либо используйте существующую процедуру (например, printf), чтобы сделать эту часть за вас, либо реализуйте алгоритм преобразования вручную. Последнее уже хорошо описано в ответе, на который ссылается Майкл Петч, поэтому я не уверен, чего еще вы ожидаете, опубликовав награду.

fuz 19.12.2020 20:53
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
909
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Для этого написал ассемблерную программу из 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, чтобы проверить ее.

Мое решение очень наивно:

  • Сначала выясните общее количество дней, его можно найти, используя time/60/60/24 (и вы получите свой час/мин/сек на этом шаге).
  • Тогда определите год. Я сделал это, вычитая количество дней в году, год за годом. В качестве вспомогательной функции я разработал функцию для определения количества дней в любом году.
  • Найдите месяц года и день месяца. Это почти то же самое с шагом 2. В качестве вспомогательной функции я разработал функцию для определения количества дней в любом месяце любого года.

Редактировать:

Вставил всю программу в этот ответ, как просили несколько человек.

Для печати целой части я использовал реализацию от @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.

Peter Cordes 21.12.2020 04:00

Однако сторонние ссылки на код не одобряются Stack Overflow. Я бы предложил поставить хотя бы ключевые части этого вопроса прямо в вопрос. Вы можете сохранить ссылку, но ответ все равно должен работать / быть чем-то полезным, если ссылка исчезнет. Возможно, ваше текстовое описание алгоритма форматирования времени полезно без кода.

Peter Cordes 21.12.2020 04:04

@PeterCordes Спасибо за просмотр кода! Да, цикл while выглядит хорошо, ты гений. Я должен был сделать это LOL. О внешних ссылках: на самом деле я могу разместить здесь все, но не будет ли это делать этот ответ слишком длинным?

RabidBear 21.12.2020 04:33

Вы всегда хотите, чтобы циклы выполнялись{}во время ассемблера, когда это возможно; обычно легко для циклов, которые всегда могут выполнить хотя бы 1 итерацию. Это один из довольно редких случаев, когда условие наверху не только менее эффективно, но и фактически создает проблемы, которые нужно обойти. И спасибо, определение хороших способов написания кода приходит с опытом оптимизации (включая просмотр вывода компилятора, что иногда полезно), но я бы не отказался от того, чтобы меня называли гением :P

Peter Cordes 21.12.2020 04:47

re: длина ответов: вот почему я предложил вытащить некоторые ключевые части кода в качестве примеров, а вы сохраните текстовые пояснения к общей картине и ссылку. Но если бы вы поместили все в один гигантский блок кода, Stack Overflow предоставил бы блоку кода собственную полосу прокрутки. Однако это не очень хорошо для удобочитаемости, поэтому, по крайней мере, печатный целочисленный код в отдельном блоке, потому что он отвечает на отдельную часть вопроса. (Хотя эта часть является дубликатом существующих вопросов и ответов для печати целых чисел, поэтому вы можете просто связать другие ответы SO.)

Peter Cordes 21.12.2020 04:50

@PeterCordes А, да, ты прав. Я отредактировал ответ и добавил ваш ответ.

RabidBear 21.12.2020 05:19

@SepRoland Хорошо, я вставил код в этот ответ. Но я не знаю, почему вы и другие люди хотели, чтобы я это сделал. Есть ли за этим какая-то причина?

RabidBear 22.12.2020 04:00

Если Github выйдет из строя; выходит из бизнеса; или вы удаляете проект, тогда код в ответе все еще полезен.

Michael Petch 22.12.2020 04:02

@MichaelPetch А, понятно. Спасибо. Я никогда не думал о возможности того, что Github может выйти из бизнеса ... тогда в моих будущих ответах я не буду предоставлять внешние ссылки, когда это возможно.

RabidBear 22.12.2020 04:09

Другие вопросы по теме

Локальное реальное использование потоком базовых регистров сегмента
Как посмотреть, какая форма инструкции используется, по коду операции или дизассемблированию?
Как заставить CMake скомпилировать чистую статическую и разделяемую библиотеку сборки?
Исключения с плавающей запятой в LINUX — как преобразовать их во что-то полезное
Что означает `amd64` в файлах Python .whl?
Разница в распределении стека сборки x86-32 и x64 для буфера фиксированного размера с неоптимизированным C (GCC)
Как на самом деле работают popa и pusha?
SIMD-код для преобразования однобуквенного кода аминокислоты в целое число от 0 до 22
Это пропущенная оптимизация в GCC, загрузка 16-битного целочисленного значения из .rodata вместо немедленного сохранения?
Почему использование 8-байтового регистра для переноса 8-байтового типа, но не использование 4-байтового регистра для 4-байтового типа