607 lines
14 KiB
NASM
607 lines
14 KiB
NASM
%include "src/constants.asm"
|
|
|
|
extern strlen
|
|
extern strcat
|
|
extern itoa
|
|
extern utoa
|
|
|
|
section .rodata
|
|
mNL db NL
|
|
__fmt_BuffLen equ 128
|
|
section .bss
|
|
__fmt_Buff resb __fmt_BuffLen
|
|
__fmt_Args resq 5 ;5=>rsi,rdx,rcx,r8,r9
|
|
readsCBuff resb 1
|
|
section .text
|
|
global __INTERNAL_fmt
|
|
|
|
global print
|
|
global puts
|
|
global printf
|
|
global eprintf
|
|
global reads
|
|
|
|
;----- print(*str[]) -----;
|
|
; Prints given string to the console to stdout
|
|
; Return value: Amount of printed characters
|
|
; Used registers:
|
|
; rax* syscall >> (ret) amount of printed characters
|
|
; rdi* (arg) Pointer to str[] >> syscall arg (fd)
|
|
; rsi* syscall arg (pointer to str[])
|
|
; rdx* syscall arg (length of str[])
|
|
print:
|
|
sub rsp, SIZE_QWORD
|
|
mov rsi, rdi
|
|
call strlen
|
|
mov rdx, rax
|
|
mov eax, NR_write
|
|
mov edi, FD_stdout
|
|
syscall
|
|
add rsp, SIZE_QWORD
|
|
ret
|
|
|
|
;----- puts(*str[]) -----;
|
|
; Prints given string to the console to stdout and prints a new line
|
|
; Return value: Amount of printed characters
|
|
; Used registers:
|
|
; rax* syscall >> (ret) amount of printed characters
|
|
; rdi* (arg) Pointer to str[] >> syscall arg (fd)
|
|
; rsi* syscall arg (pointer to str[])
|
|
; rdx* syscall arg (length of str[]+1)
|
|
puts:
|
|
sub rsp, SIZE_QWORD
|
|
call print
|
|
mov esi, mNL
|
|
mov r10, rax
|
|
mov eax, NR_write
|
|
mov edi, FD_stdout
|
|
mov edx, 1
|
|
syscall
|
|
mov rax, r10
|
|
inc rax
|
|
add rsp, SIZE_QWORD
|
|
ret
|
|
|
|
;----- printf(*format[], ...) -----;
|
|
; Formats and prints given string to console to stdout
|
|
; Return value: Amount of printed characters
|
|
; Supported specifiers:
|
|
; %% Literal percentage sign
|
|
; %c Single character
|
|
; %d Signed integer, printed as decimal
|
|
; %i Alias for %d
|
|
; %u Unsigned integer, printed as decimal
|
|
; %x Unsigned integer, printed as hexadecimal number (lowercase)
|
|
; %X Unsigned integer, printed as hexadecimal number (uppercase)
|
|
; %b Unsigned integer, printed as binary number
|
|
; %o Unsigned integer, printed as octal number
|
|
; %p Print pointer, but truly alias for %#x -- this printf/udec processes all arguments as qwords so both %x and %p (and other specifiers) will always print full true value.
|
|
; That is also why %l/%ll is not supported - it's not needed.
|
|
; %s String
|
|
; %#{x} Use prefix for printed number (non-decimal). Supported specifiers: x, X, b, o. Works in combination with width specifier (see below)
|
|
; %[#]n* Pad left, n chars (maximum 64). Supported specifiers: d, i, u, x, X, b, o
|
|
; *[#]0n* Pad left with zeroes, n chars (maximum 64). Supported specifiers: d, i, u, x, X, b, o
|
|
; <!> Unsupported specifiers are printed as-is
|
|
; <!> For all specifiers (except %%) an argument is expected. Mismatch between arguments given and specifiers provided will lead to issues
|
|
;
|
|
; Supported special characters:
|
|
; \n New line
|
|
; \t Tab
|
|
; Used registers:
|
|
; rax* (ret) amount of printed characters
|
|
; rbx Length of array being written to (if rax != FD)
|
|
; rdi* (arg) pointer to format[] to format and print >> pointer to buffer
|
|
; rsi* (optional arg) >> Used for inserting strings to buffer
|
|
; rdx* (optional arg) >> misc
|
|
; rcx* (optional arg) >> Stores arg for %x/%X
|
|
; r8* (optional arg) >> Used for moving characters
|
|
; r9* (optional arg) >> Keeps track of where to jump after flushing buffer
|
|
; r10* Keeps track of current index of __fmt_Buff
|
|
; r11* Keeps track of total characters (return value)
|
|
; r12 Padding length
|
|
; r13 Bitmask, 1 = insert specifier yes/no, 2 = use zeroes for padding yes/no
|
|
; r14 Keeps track of amount of processed format specifiers
|
|
; r15 Stores fd to write output to (passed to __INTERNAL_fmt via RAX)
|
|
printf:
|
|
mov rax, FD_stdout
|
|
jmp __INTERNAL_fmt
|
|
|
|
;----- eprintf(*format[], ...) -----;
|
|
; Same as printf, except prints to stderr
|
|
eprintf:
|
|
mov rax, FD_stderr
|
|
jmp __INTERNAL_fmt
|
|
|
|
;----- __INTERNAL_fmt(*format[], ...) -----;
|
|
; See printf description above, +
|
|
; 1) Return value can be amount of printed characters or -errno (eg if bad FD was passed, -EBADF is returned)
|
|
; 2) FD has to be passed via RAX (this is clearly an INTERNAL function so passed arguments are slightly different, that is, RAX required for FD)
|
|
; 3) When writing a function(/wrapper) that calls this function, make sure that:
|
|
; - That function has function prologue (ie push rbp + mov rbp, rsp)
|
|
; - __INTERNAL_fmt is called via CALL and not a simple JMP like in printf
|
|
; - Arguments from function/wrapper are all shifted
|
|
; This is because in such I/O functions, first argument should be file descriptor, and following SYS V ABI, that first argument is passed to RDI
|
|
; However since __INTERNAL_fmt expects FD via RAX, all arguments have to be shifted. Due to this, argument in R9 becomes a stack argument
|
|
; Because of that, __INTERNAL_fmt has to load from stack with a different offset; +24 bytes extra (extra +8 for function call, extra +8 for function prologue and extra +8 to account for possible R9 arg becoming a stack arg)
|
|
;
|
|
; To make it even more complicated, instead of simply writing to a buffer and simply printing to given FD, it is now also possible to write to memory (array) directly (used for format)
|
|
; To achieve that, pass ~RAX (thus ~pointer_to_array) and push length of array to the stack (from within function wrapper). See format()
|
|
__INTERNAL_fmt:
|
|
%macro load_arg 1
|
|
cmp r14, 4
|
|
ja %%fromStack
|
|
mov %1, [__fmt_Args + SIZE_QWORD * r14]
|
|
jmp %%continue
|
|
%%fromStack:
|
|
cmp r15, FD_stderr
|
|
jle %%stackNoShift
|
|
test rbx, rbx
|
|
jnz %%stackShift_format
|
|
mov %1, [rbp + (RBP_OFFSET_CALLER*2 + SIZE_QWORD) + ((r14-5) * SIZE_QWORD)] ;Offset when called from wrapper like fwrite() (shifted args, output is to FD)
|
|
jmp %%continue
|
|
%%stackShift_format:
|
|
mov %1, [rbp + (RBP_OFFSET_CALLER*2 + SIZE_QWORD*4) + ((r14-5) * SIZE_QWORD)] ;Offset when called from wrapper like format() (shifted args, output is memory; extra push (+stack offset) for length of array)
|
|
;Not entirely sure why I need to do SIZE_QWORD*4 though, should be *3 (because of one push and one stack offset: 16 bytes)
|
|
;At %%fromStack I was also not sure why (according to comment at description: '+8 to account for possible R9 arg becoming a stack arg'); this does not apply
|
|
;It 1*8 is needed it checks out: 1 + 2 + that extra 1 = 4. Still not sure where the extra 8 is coming from.
|
|
jmp %%continue
|
|
%%stackNoShift:
|
|
mov %1, [rbp + RBP_OFFSET_CALLER + ((r14-5) * SIZE_QWORD)] ;Offset when called from wrapper like printf (no shifted args)
|
|
%%continue:
|
|
%endmacro
|
|
|
|
%macro push_regs 0
|
|
sub rsp, SIZE_QWORD
|
|
push rax
|
|
push rdi
|
|
push r9
|
|
push r10
|
|
push r11
|
|
%endmacro
|
|
|
|
%macro pop_regs 0
|
|
pop r11
|
|
pop r10
|
|
pop r9
|
|
pop rdi
|
|
pop rax
|
|
add rsp, SIZE_QWORD
|
|
%endmacro
|
|
|
|
%macro process_arg 3 ;process_arg [base] [upper/lowercase (r8)] [itoa/utoa]
|
|
load_arg rsi
|
|
push_regs
|
|
mov rdi, rsi
|
|
test r13, 1
|
|
jz %%baseNormal
|
|
mov rsi, ~%1
|
|
jmp %%args
|
|
%%baseNormal:
|
|
mov rsi, %1
|
|
%%args:
|
|
mov rdx, r12
|
|
test r13, 2
|
|
jz %%padSpaces
|
|
mov rcx, 1
|
|
jmp %%movr8 ;nice label name isn't it?
|
|
%%padSpaces:
|
|
xor rcx, rcx
|
|
%%movr8:
|
|
mov r8, %2
|
|
call %3
|
|
mov rsi, rax
|
|
pop_regs
|
|
jmp .insertString
|
|
%endmacro
|
|
|
|
%macro check_formatted_len 2 ;check_format_len [return_label] [offset]
|
|
test rbx, rbx
|
|
jz %%checkBufferLen
|
|
cmp r11, rbx
|
|
jae .wrapup
|
|
jmp .flushReturn_%1
|
|
%%checkBufferLen:
|
|
mov r9b, %1
|
|
cmp r10, __fmt_BuffLen-%2
|
|
je .flushBuffer
|
|
.flushReturn_%1:
|
|
%endmacro
|
|
|
|
%macro write_format_data 3 ;write_format_data [size] [offset] [what]
|
|
test rbx, rbx
|
|
jz %%writeToBuffer
|
|
mov %1 [r15 + %2], %3
|
|
jmp %%cnt
|
|
%%writeToBuffer:
|
|
mov %1 [__fmt_Buff + %2], %3
|
|
%%cnt:
|
|
%endmacro
|
|
|
|
; entry:
|
|
test rax, rax
|
|
js .start_fmt ;jump if signed; FD => Mem
|
|
cmp rax, 0
|
|
jg .start_fmt
|
|
mov rax, -EBADF
|
|
ret
|
|
.start_fmt:
|
|
push rbp
|
|
mov rbp, rsp
|
|
sub rsp, SIZE_QWORD
|
|
push rbx
|
|
push r12
|
|
push r13
|
|
push r14
|
|
push r15
|
|
mov r15, rax
|
|
xor rbx, rbx
|
|
test r15, r15
|
|
jns .checkEmptyStr
|
|
not r15
|
|
mov rbx, [rbp + RBP_OFFSET_CALLER]
|
|
|
|
.checkEmptyStr:
|
|
cmp byte [rdi], EOS
|
|
je .emptyStr
|
|
|
|
; Store arguments to memory - easier to load data + more available registers (= less stack usage)
|
|
mov [rel __fmt_Args + SIZE_QWORD * 0], rsi
|
|
mov [rel __fmt_Args + SIZE_QWORD * 1], rdx
|
|
mov [rel __fmt_Args + SIZE_QWORD * 2], rcx
|
|
mov [rel __fmt_Args + SIZE_QWORD * 3], r8
|
|
mov [rel __fmt_Args + SIZE_QWORD * 4], r9
|
|
|
|
xor rdx, rdx
|
|
xor r10, r10
|
|
xor r11, r11
|
|
xor r14, r14
|
|
|
|
.process:
|
|
cmp byte [rdi], EOS
|
|
je .wrapup
|
|
check_formatted_len 0,1
|
|
cmp byte [rdi], '\'
|
|
je .asciiReplacement
|
|
cmp byte [rdi], '%'
|
|
je .argReplacement
|
|
mov r8b, [rdi]
|
|
write_format_data byte,r10,r8b
|
|
inc r10
|
|
inc r11
|
|
inc rdi
|
|
jmp .process
|
|
|
|
;-- Replace special characters --;
|
|
.asciiReplacement:
|
|
cmp byte [rdi + 1], EOS
|
|
je .wrapup
|
|
mov r8, NL
|
|
cmp byte [rdi + 1], 'n'
|
|
cmove r9, r8
|
|
je .replaceAscii
|
|
mov r8, TAB
|
|
cmp byte [rdi + 1], 't'
|
|
cmove r9, r8
|
|
je .replaceAscii
|
|
jmp .invalidReplacement
|
|
.replaceAscii:
|
|
check_formatted_len 1, 1
|
|
write_format_data byte,r10,r8b
|
|
add rdi, 2
|
|
inc r10
|
|
inc r11
|
|
jmp .process
|
|
|
|
;-- Replace specifiers --;
|
|
.argReplacement:
|
|
cmp byte [rdi + 1], EOS
|
|
je .wrapup
|
|
cmp byte [rdi + 1], '%'
|
|
je .rep_pct
|
|
xor r12, r12
|
|
; Include specifier (r13 bit0=1) or not (bit0=0)
|
|
xor r13, r13
|
|
cmp byte [rdi + 1], '#'
|
|
jne .argGetPadZeroes
|
|
inc rdi
|
|
bts r13, 0
|
|
; Padding: zeroes (r13i bit1=1) or spaces (bit1=0)
|
|
.argGetPadZeroes:
|
|
cmp byte [rdi + 1], '0'
|
|
jb .checkReplArg
|
|
ja .padSpaces
|
|
bts r13, 1
|
|
.padSpaces:
|
|
cmp byte [rdi + 1], '9'
|
|
jg .checkReplArg
|
|
xor rcx, rcx
|
|
inc rdi
|
|
.findPaddingNumLen:
|
|
cmp byte [rdi], EOS
|
|
je .wrapup
|
|
cmp byte [rdi], '0'
|
|
jb .getPadding
|
|
cmp byte [rdi], '9'
|
|
ja .getPadding
|
|
inc rcx
|
|
inc rdi
|
|
jmp .findPaddingNumLen
|
|
.getPadding:
|
|
dec rdi
|
|
push rdi
|
|
push rax
|
|
xor rax, rax
|
|
mov r9, 1
|
|
.getPaddingLoop:
|
|
xor rax, rax
|
|
mov al, byte [rdi]
|
|
sub al, '0'
|
|
imul r9
|
|
add r12, rax
|
|
dec rdi
|
|
mov rax, r9
|
|
mov r9, 10
|
|
imul r9
|
|
mov r9, rax
|
|
loop .getPaddingLoop
|
|
pop rax
|
|
pop rdi
|
|
test r12, r12
|
|
jnz .checkReplArg
|
|
dec rdi
|
|
.checkReplArg:
|
|
cmp byte [rdi + 1], 'c'
|
|
je .rep_c
|
|
cmp byte [rdi + 1], 'i'
|
|
je .rep_d
|
|
cmp byte [rdi + 1], 'd'
|
|
je .rep_d
|
|
cmp byte [rdi + 1], 'u'
|
|
je .rep_d
|
|
cmp byte [rdi + 1], 'x'
|
|
je .rep_x
|
|
cmp byte [rdi + 1], 'X'
|
|
je .rep_x
|
|
cmp byte [rdi + 1], 'b'
|
|
je .rep_b
|
|
cmp byte [rdi + 1], 'o'
|
|
je .rep_o
|
|
cmp byte [rdi + 1], 'p'
|
|
je .rep_p
|
|
cmp byte [rdi + 1], 's'
|
|
je .rep_s
|
|
|
|
;--- Invalid ---;
|
|
.invalidReplacement:
|
|
test r12, r12
|
|
jz .irNoPadding
|
|
dec rdi
|
|
.irNoPadding:
|
|
cmp byte [rdi + 1], '\'
|
|
je .invalidReplacement_specialChar; '%\n' would become "'%','\','n'" instead of "'%',EOS" when inserting full invalid specifier.
|
|
mov r9w, word [rdi]
|
|
write_format_data word,r10,r9w
|
|
add rdi, 2
|
|
add r10, 2
|
|
add r11, 2
|
|
inc r14
|
|
jmp .process
|
|
.invalidReplacement_specialChar:
|
|
mov r9b, byte [rdi]
|
|
write_format_data byte,r10,r9b
|
|
inc rdi
|
|
inc r10
|
|
inc r11
|
|
inc r14
|
|
jmp .process
|
|
|
|
;--- '%%' ---;
|
|
.rep_pct:
|
|
mov sil, '%'
|
|
dec r14
|
|
jmp .insertChar
|
|
|
|
;--- '%c' ---;
|
|
.rep_c:
|
|
load_arg rsi
|
|
jmp .insertChar
|
|
|
|
;--- '%i' / '%d' / '%u' ---;
|
|
.rep_d:
|
|
cmp byte [rdi + 1], 'u'
|
|
je .unsigned
|
|
process_arg 10, 0, itoa
|
|
.unsigned:
|
|
process_arg 10, 0, utoa
|
|
|
|
;--- '%x' / '%X' ---;
|
|
.rep_x:
|
|
cmp byte [rdi + 1], 'x'
|
|
je .lower
|
|
process_arg 16, 1, utoa
|
|
.lower:
|
|
process_arg 16, 0, utoa
|
|
|
|
;--- '%b' ---;
|
|
.rep_b:
|
|
process_arg 2, 0, utoa
|
|
|
|
;--- '%o' ---;
|
|
.rep_o:
|
|
process_arg 8, 0, utoa
|
|
|
|
;--- '%p' ---;
|
|
.rep_p:
|
|
bts r13, 0 ;always force prefix, no matter if %p or %#p was used. Do not override padding though
|
|
process_arg 16, 0, utoa
|
|
|
|
;--- '%s' ---;
|
|
.rep_s:
|
|
load_arg rsi
|
|
|
|
;--- Insert string to buffer ---;
|
|
.insertString:
|
|
test rsi, rsi
|
|
jnz .doInsertString
|
|
check_formatted_len 2, 7
|
|
write_format_data byte,r10,'('
|
|
write_format_data qword,r10+1,'null'
|
|
write_format_data byte,r10+5,')'
|
|
add r10, 6
|
|
add r11, 6
|
|
jmp .endInsertString
|
|
|
|
.doInsertString:
|
|
cmp byte [rsi], EOS
|
|
je .endInsertString
|
|
check_formatted_len 3, 1
|
|
mov r8b, byte [rsi]
|
|
write_format_data byte,r10,r8b
|
|
inc rsi
|
|
inc r10
|
|
inc r11
|
|
jmp .insertString
|
|
.endInsertString:
|
|
inc r14
|
|
add rdi, 2
|
|
xor r12, r12
|
|
jmp .process
|
|
|
|
;--- Insert char to buffer ---;
|
|
.insertChar:
|
|
write_format_data byte,r10,sil
|
|
add rdi, 2
|
|
add r10, 1
|
|
add r11, 1
|
|
xor r12, r12
|
|
inc r14
|
|
jmp .process
|
|
|
|
.flushBuffer:
|
|
test rbx, rbx
|
|
jz .flush_print
|
|
sub rsp, SIZE_QWORD
|
|
push rdi
|
|
push rsi
|
|
push rdx
|
|
lea rdi, [r15]
|
|
lea rsi, [rel __fmt_Buff]
|
|
mov rdx, rbx
|
|
call strcat
|
|
pop rdx
|
|
pop rsi
|
|
pop rdi
|
|
add rsp, SIZE_QWORD
|
|
jmp .flush_ret
|
|
.flush_print:
|
|
push rdi
|
|
push rsi
|
|
push rdx
|
|
push r11
|
|
mov rax, NR_write
|
|
mov rdi, r15
|
|
lea rsi, [rel __fmt_Buff]
|
|
mov rdx, r10
|
|
syscall
|
|
pop r11
|
|
pop rdx
|
|
pop rsi
|
|
pop rdi
|
|
test rax, rax
|
|
js .quit
|
|
xor r10, r10
|
|
.flush_ret:
|
|
cmp r9b, 0
|
|
je .flushReturn_0
|
|
cmp r9b, 1
|
|
je .flushReturn_1
|
|
cmp r9b, 2
|
|
je .flushReturn_2
|
|
cmp r9b, 3
|
|
je .flushReturn_3
|
|
|
|
.wrapup:
|
|
test rbx, rbx
|
|
jz .wrapup_print
|
|
mov byte [r15+r10+1], EOS
|
|
lea rdi, [r15]
|
|
lea rsi, [rel __fmt_Buff]
|
|
mov rdx, rbx
|
|
sub rsp, SIZE_QWORD
|
|
push r11
|
|
call strcat
|
|
pop rax
|
|
add rsp, SIZE_QWORD
|
|
jmp .quit
|
|
.wrapup_print:
|
|
mov rax, NR_write
|
|
mov rdi, r15
|
|
lea rsi, [rel __fmt_Buff]
|
|
mov rdx, r10
|
|
mov r10, r11
|
|
syscall
|
|
test rax, rax
|
|
js .quit
|
|
mov rax, r10
|
|
jmp .quit
|
|
|
|
.emptyStr:
|
|
xor rax, rax
|
|
|
|
.quit:
|
|
pop r15
|
|
pop r14
|
|
pop r13
|
|
pop r12
|
|
pop rbx
|
|
add rsp, SIZE_QWORD
|
|
leave
|
|
ret
|
|
|
|
;----- reads(*inputBuffer[], len) -----;
|
|
; Reads from console (stdin), stores input to inputBuffer[]
|
|
; Return value: Amount of read characters
|
|
; Used registers:
|
|
; rax* (ret)
|
|
; rdi* (arg) Pointer to inputBuffer[]
|
|
; rsi* (arg) Size of inputBuffer[] (or less to read less characters)
|
|
; rdx* arg for syscall
|
|
; r12 Stores inputBuffer[]
|
|
; r13 Stores len
|
|
; r14 Counts amount of read characters
|
|
reads:
|
|
push r12
|
|
push r13
|
|
push r14
|
|
|
|
lea r12, [rdi]
|
|
mov r13, rsi
|
|
dec r13 ;account for EOS
|
|
xor r14, r14
|
|
|
|
.readLoop:
|
|
mov rax, NR_read
|
|
mov rdi, FD_stdin
|
|
lea rsi, [rel readsCBuff]
|
|
mov rdx, 1
|
|
syscall
|
|
|
|
mov al, [readsCBuff]
|
|
cmp al, NL
|
|
je .finish
|
|
cmp r14, r13
|
|
jae .readLoop ;Keep reading from stdin until \n is found as to flush stdin (too bad sys_lseek is not allowed on stdin, returns ESPIPE;illegal seek)
|
|
mov byte [r12], al
|
|
inc r12
|
|
inc r14
|
|
jmp .readLoop
|
|
|
|
.finish:
|
|
mov byte [r12], EOS
|
|
mov rax, r14
|
|
|
|
pop r14
|
|
pop r13
|
|
pop r12
|
|
ret
|