PutsF - альтернатива printf
Это небольшая библиотека на ассемблере (компилятор FASM), реализация printf из stdlib, для C
Установка
Эта программа есть в моем гитхаб репозитории
Код написан для 64 битной linux системы (ELF 64). Если у вас другая, вам придется редактировать код, указывать другой формат, если у вас и разрядность другая - то изменять имена регистров.
Также у вас должен быть установлен gnu-линковщик, fasm (flat assembly) и система сборки make.
Если у вас возникли сложности или вопросы по использованию Metalfish OS, создайте обсуждение в данном репозитории или напишите на электронную почту bro.alexeev@gmail.com.
# Клонирование репозитория
git clone https://github.com/OkulusDev/asm-putsf.git
cd asm-putsf
# компиляция и линковка
make build clean
# запуск
make run
typedef long long int int64_t;
extern void c_exit(int ret);
extern int64_t c_putsf(char *fmt, ...);
void _start(void) {
char *string = "PutsF";
int64_t decimal = 123;
char symbol = '!';
int64_t ret = c_putsf(
"{ %s, %d, %c }\n",
string, decimal, symbol
);
c_putsf("%d\n", ret); // print 3
c_exit(0);
}
Вот флаги для компилятора gcc: gcc -nostdlib -o <нелинкованные файлы) -o <main.c>
Компиляция в Makefile:
fasm src/putsf.asm bin/putsf.o
fasm src/c_putsf.asm bin/c_putsf.o
fasm src/c_exit.asm bin/c_exit.o
gcc -nostdlib -o bin/putsf.bin bin/putsf.o bin/c_putsf.o bin/c_exit.o bin/putsf_example.c
Код putsf.asm
; -----------------------------------------------------------------------------
; ASM PUTSF Source Code
; File: putsf.asm
; Title: Работа кода putsf
; Last Change Date: 2 November 2023, 14:10 (UTC)
; Author: Okulus Dev
; License: GNU GPL v3
; -----------------------------------------------------------------------------
; Description:
; putsf - это альтернатива printf. PutsF минималичстичный, ограниченн символами
; %c, %s, %d и %%.
; TODO: добавить реализации с %f (плавающие числа), %x (hex, 16-ые числа)
; -----------------------------------------------------------------------------
format ELF64 ; указываем 64 битный линуксовый формат
; 64-битный формат означает, что ко всем регистрам будем добавлять букву r
; (например rax вместо ax). Буква R означает, что регистр 64-битный.
; Регистры бывают разных типов: AH, AL, AX, EAX, RAX - это все один регистр.
; + RAX - 64 битный (8 байт)
; + EAX - 32 битный (4 байта)
; + AX - 16 битный (2 байта)
; + AH, AL - 8 байтные (1 байт)
; Регистр RAX это дополнение EAX, EAX это дополнение AX, AX это дополнение 2
; регистров AH и AL
include "puts_decimal.asm"
include "puts_string.asm"
include "puts_char.asm"
public putsf
section '.putsf' executable ; секция putsf
; Ввод:
; rax = format (rax = формат)
; stack = values (stack = значения)
; Вывод:
; rax = число, количество
putsf: ; метка putsf
; Логика довольно простая, т.к. основные алгоритмы выполнены
; Алгоритм:
; 1. Прочитать i символ в строке
; 2. Если символ равен %, тогда перейти на пункт 6
; 3. Если символ равен нулю, тогда завершить выполнение
; 4. Иначе, напечатать i символ
; 5. Инкрементировать значение i (i++)
; 6. Перейти на пункт 1
; 7. Инкрементировать значение i (i++)
; 8. Если i символ равен %, тогда напечатать его
; 9. Если i символ равен d, тогда взять из стека значение и напечатать число
; 10. Если i символ равен s, тогда взять из стека значение и напечатать строку
; 11. Если i символ равен c, тогда взять из стека значение и напечатать символ
; 12. Иначе, вернуть ошибку и перейти на пункт 3
; 13. Инкрементировать значение i
; 14. Перейти на пункт 1
push rbx
push rcx
; call/ret = 8byte
; rax+rbx+rcx = 24byte
mov rbx, 32
; count of format elements
xor rcx, rcx
.next_iter:
cmp [rax], byte 0
je .close
cmp [rax], byte '%'
je .special_char
jmp .default_char
.special_char:
inc rax
cmp [rax], byte 's'
je .print_string
cmp [rax], byte 'd'
je .print_decimal
cmp [rax], byte 'c'
je .print_char
cmp [rax], byte '%'
je .default_char
jmp .is_error
.print_string:
push rax
mov rax, [rsp+rbx]
call puts_string
pop rax
jmp .shift_stack
.print_decimal:
push rax
mov rax, [rsp+rbx]
call puts_decimal
pop rax
jmp .shift_stack
.print_char:
push rax
mov rax, [rsp+rbx]
call puts_char
pop rax
jmp .shift_stack
.default_char:
push rax
mov rax, [rax]
call puts_char
pop rax
jmp .next_step
.shift_stack:
inc rcx
add rbx, 8
.next_step:
inc rax
jmp .next_iter
.is_error:
mov rcx, -1
.close:
mov rax, rcx
pop rcx
pop rbx
ret
Код puts_char.asm
;; puts_char.asm
; Вывод символа
section '.puts_char' executable ; секция вывода символа
; Ввод:
; rax = char (регистр rax = символ)
puts_char:
push rax
push rdx
push rsi
push rdi
push rax
mov rsi, rsp
mov rdi, 1
mov rdx, 1
mov rax, 1
call do_syscall
pop rax
pop rdi
pop rsi
pop rdx
pop rax
ret
section '.do_syscall' executable ; секция сисвызова
do_syscall: ; метка сисвызова
push rcx
push r11
syscall
pop r11
pop rcx
ret
Код puts_string.asm
;; puts_string.asm
; Вывод строки
section '.puts_string' executable ; секция вывода строки
; Ввод:
; rax = string (регистр rax = строка для вывода)
puts_string: ; метка вывода строки
push rbx
xor rbx, rbx
.next_iter:
cmp [rax+rbx], byte 0
je .close
push rax
mov rax, [rax+rbx]
call puts_char
pop rax
inc rbx
jmp .next_iter
.close:
pop rbx
ret
Код puts_decimal.asm
;; pust_decimal.asm
; Вывод 64 битного числа
section '.puts_decimal' executable ; секция вывода числа
; Ввод:
; rax - число
puts_decimal: ; метка вывода числа
; Стоит учитывать, что данная метка работает только с 64-битными числами, и
; следовательно, и отрицательное число она будет трактовать если оно будет
; 64-битным. В противном случае, если число будет 32 битное, то оно со
; стороны 64-битного числа будет трактоваться как unsigned (число без знака)
; Алгоритм:
; 1. Если число меньше нуля, то напечать символ минус.
; 2. Разделить число на 10, взять частное и остаток от деления
; 3. Положить остаток в стек
; 4. Инкрементировать значение i (i++)
; 5. Если частное не равно 0, тогда перейти на 2 пункт
; 6. Если значение i равно 0, тогда закрыть выполнение
; 7. Выгрузить число из стека (остаток)
; 8. Привать к остатку символ '0' (число 48 по ASCII)
; 9. Напечатать получившийся символ (puts_char)
; 10. Декрементировать значение i (i--)
; 11. Перейти на пункт 6
push rax
push rbx
push rcx
push rdx
xor rcx, rcx
cmp rax, 0
jl .is_minus
jmp .next_iter
.is_minus:
neg rax
push rax
mov rax, '-'
call puts_char
pop rax
.next_iter:
mov rbx, 10
xor rdx, rdx
div rbx
push rdx
inc rcx
cmp rax, 0
je .puts_iter
jmp .next_iter
.puts_iter:
cmp rcx, 0
je .close
pop rax
add rax, '0'
call puts_char
dec rcx
jmp .puts_iter
.close:
pop rdx
pop rcx
pop rbx
pop rax
ret
Код c_exit.asm
; c_exit.asm
format ELF64
public c_exit
section '.c_exit' executable
c_exit:
mov rax, 60
syscall
Код c_putsf.asm
; -----------------------------------------------------------------------------
; ASM PUTSF Source Code
; File: cputsf.asm
; Title: Связка putsf с C
; Last Change Date: 2 November 2023, 14:10 (UTC)
; Author: Okulus Dev
; License: GNU GPL v3
; -----------------------------------------------------------------------------
; Description:
; putsf - это альтернатива printf. PutsF минималичстичный, ограниченн символами
; %c, %s, %d и %%.
; TODO: добавить реализации с %f (плавающие числа), %x (hex, 16-ые числа)
; -----------------------------------------------------------------------------
format ELF64
extrn putsf
public c_putsf
section '.c_putsf' executable
c_putsf:
pop r10
push r9
push r8
push rcx
push rdx
push rsi
mov rax, rdi
call putsf
pop rsi
pop rdx
pop rcx
pop r8
pop r9
push r10
ret
Код примера работы на C
typedef long long int int64_t;
extern void c_exit(int ret);
extern int64_t c_putsf(char *fmt, ...);
void _start(void) {
char *string = "PutsF";
int64_t decimal = 123;
char symbol = '!';
int64_t ret = c_putsf(
"{ %s, %d, %c }\n",
string, decimal, symbol
);
c_putsf("%d\n", ret); // 3
c_exit(0);
}
Makefile
# Директории
SRC_DIR=src
BIN_DIR=bin
# Компиляторы, линковщики
ASM=fasm
LD=ld
C=gcc
C_FLAGS=-nostdlib
# Исходный код, бинарники, вывод
CODE=putsf.asm
.phony: build clean run clean_all
build:
$(ASM) $(SRC_DIR)/putsf.asm $(BIN_DIR)/putsf.o
$(ASM) $(SRC_DIR)/c_putsf.asm $(BIN_DIR)/c_putsf.o
$(ASM) $(SRC_DIR)/c_exit.asm $(BIN_DIR)/c_exit.o
$(C) $(C_FLAGS) -o $(BIN_DIR)/putsf.bin $(BIN_DIR)/putsf.o $(BIN_DIR)/c_putsf.o $(BIN_DIR)/c_exit.o $(SRC_DIR)/putsf_example.c
run:
./$(BIN_DIR)/putsf.bin
clean:
rm $(BIN_DIR)/*.o
clean_all:
rm $(BIN_DIR)/*