|
; Redis-like Key-Value Store in x86-64 Assembly for macOS |
|
; NASM syntax - Basic Redis server with SET, GET, DEL, PING commands |
|
|
|
extern _socket, _bind, _listen, _accept, _read, _write, _close, _exit, _printf, _htons |
|
extern _strcmp, _strlen, _strncmp, _sprintf |
|
|
|
section .data |
|
server_msg db "Redis-like server starting on port 6379...", 10 |
|
db "Connect with: redis-cli -p 6379", 10 |
|
db "Supported: PING, SET key value, GET key, DEL key, QUIT", 10, 0 |
|
|
|
; RESP responses |
|
resp_ok db "+OK", 13, 10 |
|
resp_ok_len equ $ - resp_ok |
|
|
|
resp_pong db "+PONG", 13, 10 |
|
resp_pong_len equ $ - resp_pong |
|
|
|
resp_nil db "$-1", 13, 10 |
|
resp_nil_len equ $ - resp_nil |
|
|
|
resp_err_unknown db "-ERR unknown command", 13, 10 |
|
resp_err_unknown_len equ $ - resp_err_unknown |
|
|
|
; Commands |
|
cmd_ping db "PING", 0 |
|
cmd_set db "SET", 0 |
|
cmd_get db "GET", 0 |
|
cmd_del db "DEL", 0 |
|
cmd_quit db "QUIT", 0 |
|
|
|
; Socket address |
|
sockaddr: |
|
sin_family dw 2 |
|
sin_port dw 0 |
|
sin_addr dd 0 |
|
sin_zero dq 0 |
|
|
|
sockaddr_len equ $ - sockaddr |
|
|
|
; Format strings (null-terminated!) |
|
fmt_bulk db "$%d", 13, 10, 0 |
|
fmt_int db ":%d", 13, 10, 0 |
|
crlf db 13, 10, 0 |
|
|
|
section .bss |
|
sock_fd resq 1 |
|
client_fd resq 1 |
|
buffer resb 8192 |
|
temp_key resb 256 |
|
temp_value resb 1024 |
|
response_buf resb 1024 |
|
saved_read_len resq 1 |
|
|
|
; Simple key-value storage (100 entries) |
|
kv_keys resb 10000 ; Key storage (100 keys * 100 bytes) |
|
kv_values resb 100000 ; Value storage (100 values * 1000 bytes) |
|
kv_count resq 1 ; Number of entries |
|
|
|
section .text |
|
global _main |
|
|
|
%define AF_INET 2 |
|
%define SOCK_STREAM 1 |
|
%define IPPROTO_TCP 6 |
|
%define PORT 6379 |
|
%define MAX_KEYS 100 |
|
|
|
_main: |
|
push rbp |
|
mov rbp, rsp |
|
|
|
; Initialize |
|
mov qword [rel kv_count], 0 |
|
|
|
; Print message |
|
lea rdi, [rel server_msg] |
|
call _printf |
|
|
|
; Setup port |
|
mov rdi, PORT |
|
call _htons |
|
mov [rel sockaddr + 2], ax |
|
|
|
; Create socket |
|
mov rdi, AF_INET |
|
mov rsi, SOCK_STREAM |
|
mov rdx, IPPROTO_TCP |
|
call _socket |
|
cmp rax, 0 |
|
jl error_exit |
|
mov [rel sock_fd], rax |
|
|
|
; Bind |
|
mov rdi, [rel sock_fd] |
|
lea rsi, [rel sockaddr] |
|
mov rdx, sockaddr_len |
|
call _bind |
|
cmp rax, 0 |
|
jl error_exit |
|
|
|
; Listen |
|
mov rdi, [rel sock_fd] |
|
mov rsi, 5 |
|
call _listen |
|
cmp rax, 0 |
|
jl error_exit |
|
|
|
server_loop: |
|
; Accept |
|
mov rdi, [rel sock_fd] |
|
mov rsi, 0 |
|
mov rdx, 0 |
|
call _accept |
|
cmp rax, 0 |
|
jl server_loop |
|
mov [rel client_fd], rax |
|
|
|
; Handle client |
|
call handle_client |
|
|
|
; Close |
|
mov rdi, [rel client_fd] |
|
call _close |
|
jmp server_loop |
|
|
|
handle_client: |
|
push rbp |
|
mov rbp, rsp |
|
|
|
.client_loop: |
|
; Read |
|
mov rdi, [rel client_fd] |
|
lea rsi, [rel buffer] |
|
mov rdx, 8192 |
|
call _read |
|
cmp rax, 0 |
|
jle .client_done |
|
|
|
; Save read length and null terminate |
|
mov [rel saved_read_len], rax |
|
lea rbx, [rel buffer] |
|
add rbx, rax |
|
mov byte [rbx], 0 |
|
|
|
; Parse and execute |
|
call parse_resp |
|
|
|
jmp .client_loop |
|
|
|
.client_done: |
|
pop rbp |
|
ret |
|
|
|
parse_resp: |
|
push rbp |
|
mov rbp, rsp |
|
|
|
; Check if RESP array (must start with '*') |
|
lea rbx, [rel buffer] |
|
cmp byte [rbx], '*' |
|
jne .parse_done |
|
|
|
; Search for command strings in buffer (case-insensitive) |
|
; Look for PING |
|
lea rdi, [rel buffer] |
|
mov rsi, [rel saved_read_len] |
|
lea rdx, [rel cmd_ping] |
|
call find_string_in_buffer |
|
cmp rax, 0 |
|
jne .found_ping |
|
|
|
; Look for SET |
|
lea rdi, [rel buffer] |
|
mov rsi, [rel saved_read_len] |
|
lea rdx, [rel cmd_set] |
|
call find_string_in_buffer |
|
cmp rax, 0 |
|
jne .found_set |
|
|
|
; Look for GET |
|
lea rdi, [rel buffer] |
|
mov rsi, [rel saved_read_len] |
|
lea rdx, [rel cmd_get] |
|
call find_string_in_buffer |
|
cmp rax, 0 |
|
jne .found_get |
|
|
|
; Look for DEL |
|
lea rdi, [rel buffer] |
|
mov rsi, [rel saved_read_len] |
|
lea rdx, [rel cmd_del] |
|
call find_string_in_buffer |
|
cmp rax, 0 |
|
jne .found_del |
|
|
|
; Look for QUIT |
|
lea rdi, [rel buffer] |
|
mov rsi, [rel saved_read_len] |
|
lea rdx, [rel cmd_quit] |
|
call find_string_in_buffer |
|
cmp rax, 0 |
|
jne .found_quit |
|
|
|
; Unknown command |
|
jmp .unknown_cmd |
|
|
|
.found_ping: |
|
jmp .do_ping |
|
.found_set: |
|
jmp .do_set |
|
.found_get: |
|
jmp .do_get |
|
.found_del: |
|
jmp .do_del |
|
.found_quit: |
|
jmp .do_quit |
|
|
|
.unknown_cmd: |
|
mov rdi, [rel client_fd] |
|
lea rsi, [rel resp_err_unknown] |
|
mov rdx, resp_err_unknown_len |
|
call _write |
|
jmp .parse_done |
|
|
|
.do_ping: |
|
mov rdi, [rel client_fd] |
|
lea rsi, [rel resp_pong] |
|
mov rdx, resp_pong_len |
|
call _write |
|
jmp .parse_done |
|
|
|
.do_set: |
|
; Extract key and value from RESP array |
|
call find_resp_command |
|
mov rbx, rax |
|
cmp rbx, 0 |
|
je .parse_done |
|
|
|
; Find end of command string |
|
mov rcx, rbx |
|
.find_set_cmd_end: |
|
cmp byte [rcx], 13 |
|
je .found_set_cmd_end |
|
cmp byte [rcx], 0 |
|
je .parse_done |
|
inc rcx |
|
jmp .find_set_cmd_end |
|
|
|
.found_set_cmd_end: |
|
add rcx, 2 ; Skip \r\n |
|
|
|
; Extract key (next bulk string) |
|
mov rdi, rcx |
|
call extract_resp_string_at |
|
cmp rax, 0 |
|
je .parse_done |
|
mov rsi, rax |
|
lea rdi, [rel temp_key] |
|
call copy_until_crlf |
|
|
|
; Extract value (next bulk string after key) |
|
mov rdi, rsi |
|
call find_string_end |
|
mov rdi, rax |
|
call extract_resp_string_at |
|
cmp rax, 0 |
|
je .parse_done |
|
mov rsi, rax |
|
lea rdi, [rel temp_value] |
|
call copy_until_crlf |
|
|
|
; Store key-value |
|
lea rdi, [rel temp_key] |
|
lea rsi, [rel temp_value] |
|
call kv_set |
|
|
|
mov rdi, [rel client_fd] |
|
lea rsi, [rel resp_ok] |
|
mov rdx, resp_ok_len |
|
call _write |
|
jmp .parse_done |
|
|
|
.do_get: |
|
call find_resp_command |
|
mov rbx, rax |
|
cmp rbx, 0 |
|
je .parse_done |
|
|
|
mov rcx, rbx |
|
.find_get_cmd_end: |
|
cmp byte [rcx], 13 |
|
je .found_get_cmd_end |
|
cmp byte [rcx], 0 |
|
je .parse_done |
|
inc rcx |
|
jmp .find_get_cmd_end |
|
|
|
.found_get_cmd_end: |
|
add rcx, 2 |
|
|
|
mov rdi, rcx |
|
call extract_resp_string_at |
|
cmp rax, 0 |
|
je .parse_done |
|
mov rsi, rax |
|
lea rdi, [rel temp_key] |
|
call copy_until_crlf |
|
|
|
lea rdi, [rel temp_key] |
|
call kv_get |
|
cmp rax, 0 |
|
je .get_not_found |
|
|
|
mov rdi, rax |
|
call send_bulk_string |
|
jmp .parse_done |
|
|
|
.get_not_found: |
|
mov rdi, [rel client_fd] |
|
lea rsi, [rel resp_nil] |
|
mov rdx, resp_nil_len |
|
call _write |
|
jmp .parse_done |
|
|
|
.do_del: |
|
call find_resp_command |
|
mov rbx, rax |
|
cmp rbx, 0 |
|
je .parse_done |
|
|
|
mov rcx, rbx |
|
.find_del_cmd_end: |
|
cmp byte [rcx], 13 |
|
je .found_del_cmd_end |
|
cmp byte [rcx], 0 |
|
je .parse_done |
|
inc rcx |
|
jmp .find_del_cmd_end |
|
|
|
.found_del_cmd_end: |
|
add rcx, 2 |
|
|
|
mov rdi, rcx |
|
call extract_resp_string_at |
|
cmp rax, 0 |
|
je .parse_done |
|
mov rsi, rax |
|
lea rdi, [rel temp_key] |
|
call copy_until_crlf |
|
|
|
lea rdi, [rel temp_key] |
|
call kv_del |
|
call send_integer |
|
jmp .parse_done |
|
|
|
.do_quit: |
|
mov rdi, [rel client_fd] |
|
lea rsi, [rel resp_ok] |
|
mov rdx, resp_ok_len |
|
call _write |
|
jmp .parse_done |
|
|
|
.parse_done: |
|
pop rbp |
|
ret |
|
|
|
; ============================================================ |
|
; Find string in buffer (case-insensitive) |
|
; rdi = buffer, rsi = buffer_len, rdx = search_string |
|
; Returns: pointer to match or 0 if not found |
|
; ============================================================ |
|
find_string_in_buffer: |
|
push rbp |
|
mov rbp, rsp |
|
push rbx |
|
push r12 |
|
push r13 |
|
push r14 |
|
push r15 |
|
|
|
mov r12, rdi ; buffer pointer (callee-saved) |
|
mov r13, rdx ; search string (callee-saved) |
|
mov r14, rsi ; buffer length (callee-saved) |
|
mov r15, 0 ; position (callee-saved) |
|
|
|
; Get search string length once |
|
mov rdi, r13 |
|
call _strlen |
|
mov rbx, rax ; search string length (callee-saved) |
|
|
|
.find_str_loop: |
|
cmp r15, r14 |
|
jge .find_str_not_found |
|
|
|
; Check if we have enough bytes left |
|
mov rax, r14 |
|
sub rax, r15 |
|
cmp rax, rbx |
|
jl .find_str_not_found |
|
|
|
; Compare strings (case-insensitive) |
|
lea rdi, [r12 + r15] |
|
mov rsi, r13 |
|
mov rdx, rbx |
|
call compare_strings_case_insensitive |
|
cmp rax, 0 |
|
je .find_str_found |
|
|
|
inc r15 |
|
jmp .find_str_loop |
|
|
|
.find_str_found: |
|
lea rax, [r12 + r15] |
|
jmp .find_str_done |
|
|
|
.find_str_not_found: |
|
mov rax, 0 |
|
|
|
.find_str_done: |
|
pop r15 |
|
pop r14 |
|
pop r13 |
|
pop r12 |
|
pop rbx |
|
pop rbp |
|
ret |
|
|
|
; ============================================================ |
|
; Compare strings case-insensitively |
|
; rdi = str1, rsi = str2, rdx = length |
|
; Returns: 0 if equal, 1 if not |
|
; ============================================================ |
|
compare_strings_case_insensitive: |
|
push rbp |
|
mov rbp, rsp |
|
|
|
mov r8, rdx ; length |
|
mov r9, 0 ; index |
|
|
|
.cmp_loop: |
|
cmp r9, r8 |
|
jge .cmp_equal |
|
|
|
movzx eax, byte [rdi + r9] |
|
movzx ecx, byte [rsi + r9] |
|
|
|
; Convert to uppercase |
|
cmp al, 'a' |
|
jl .al_ok |
|
cmp al, 'z' |
|
jg .al_ok |
|
sub al, 32 |
|
.al_ok: |
|
cmp cl, 'a' |
|
jl .cl_ok |
|
cmp cl, 'z' |
|
jg .cl_ok |
|
sub cl, 32 |
|
.cl_ok: |
|
|
|
cmp al, cl |
|
jne .cmp_not_equal |
|
|
|
inc r9 |
|
jmp .cmp_loop |
|
|
|
.cmp_equal: |
|
mov rax, 0 |
|
jmp .cmp_done |
|
|
|
.cmp_not_equal: |
|
mov rax, 1 |
|
|
|
.cmp_done: |
|
pop rbp |
|
ret |
|
|
|
; ============================================================ |
|
; Find command in RESP array |
|
; Format: *N\r\n$len\r\ncommand\r\n... |
|
; Returns: pointer to command string or 0 |
|
; ============================================================ |
|
find_resp_command: |
|
push rbp |
|
mov rbp, rsp |
|
lea rbx, [rel buffer] |
|
|
|
cmp byte [rbx], '*' |
|
jne .not_found_cmd |
|
inc rbx |
|
|
|
; Skip array count digits |
|
.skip_count: |
|
mov al, [rbx] |
|
cmp al, 13 |
|
je .check_crlf_count |
|
inc rbx |
|
jmp .skip_count |
|
|
|
.check_crlf_count: |
|
inc rbx ; skip \r |
|
inc rbx ; skip \n |
|
|
|
; Now at first '$' |
|
cmp byte [rbx], '$' |
|
jne .not_found_cmd |
|
inc rbx ; skip '$' |
|
|
|
; Skip length digits |
|
.skip_length: |
|
mov al, [rbx] |
|
cmp al, 13 |
|
je .check_crlf_len |
|
inc rbx |
|
jmp .skip_length |
|
|
|
.check_crlf_len: |
|
inc rbx ; skip \r |
|
inc rbx ; skip \n |
|
mov rax, rbx ; pointer to command string |
|
jmp .find_cmd_done |
|
|
|
.not_found_cmd: |
|
mov rax, 0 |
|
|
|
.find_cmd_done: |
|
pop rbp |
|
ret |
|
|
|
; ============================================================ |
|
; Extract RESP string at given position |
|
; rdi = position in buffer (should point to '$') |
|
; Returns: pointer to string data or 0 |
|
; ============================================================ |
|
extract_resp_string_at: |
|
push rbp |
|
mov rbp, rsp |
|
mov rbx, rdi |
|
|
|
cmp byte [rbx], '$' |
|
jne .not_found_str |
|
inc rbx ; skip '$' |
|
|
|
; Skip length digits |
|
.skip_len: |
|
mov al, [rbx] |
|
cmp al, 13 |
|
je .check_crlf_str |
|
inc rbx |
|
jmp .skip_len |
|
|
|
.check_crlf_str: |
|
inc rbx ; skip \r |
|
inc rbx ; skip \n |
|
mov rax, rbx ; pointer to string data |
|
jmp .extract_done |
|
|
|
.not_found_str: |
|
mov rax, 0 |
|
|
|
.extract_done: |
|
pop rbp |
|
ret |
|
|
|
; ============================================================ |
|
; Find end of string (skip past \r\n) |
|
; rdi = pointer to start of string |
|
; Returns: pointer after \r\n |
|
; ============================================================ |
|
find_string_end: |
|
push rbp |
|
mov rbp, rsp |
|
mov rbx, rdi |
|
|
|
.find_end_loop: |
|
mov al, [rbx] |
|
cmp al, 13 |
|
je .found_end |
|
cmp al, 0 |
|
je .found_end_null |
|
inc rbx |
|
jmp .find_end_loop |
|
|
|
.found_end: |
|
add rbx, 2 ; skip \r\n |
|
mov rax, rbx |
|
jmp .find_end_done |
|
|
|
.found_end_null: |
|
mov rax, rbx |
|
|
|
.find_end_done: |
|
pop rbp |
|
ret |
|
|
|
; ============================================================ |
|
; Copy string until \r\n or null |
|
; rdi = destination, rsi = source |
|
; ============================================================ |
|
copy_until_crlf: |
|
push rbp |
|
mov rbp, rsp |
|
mov rbx, rsi |
|
mov rcx, rdi |
|
|
|
.copy_loop: |
|
mov al, [rbx] |
|
cmp al, 13 |
|
je .copy_done |
|
cmp al, 0 |
|
je .copy_done |
|
mov [rcx], al |
|
inc rbx |
|
inc rcx |
|
jmp .copy_loop |
|
|
|
.copy_done: |
|
mov byte [rcx], 0 |
|
pop rbp |
|
ret |
|
|
|
; ============================================================ |
|
; Key-value store operations |
|
; ============================================================ |
|
|
|
kv_set: |
|
push rbp |
|
mov rbp, rsp |
|
push r12 |
|
push r13 |
|
|
|
mov r12, rdi ; key |
|
mov r13, rsi ; value |
|
|
|
; Check if key already exists |
|
mov rdi, r12 |
|
call kv_find |
|
cmp rax, -1 |
|
jne .kv_update |
|
|
|
; New key |
|
mov rbx, [rel kv_count] |
|
cmp rbx, MAX_KEYS |
|
jge .kv_set_done |
|
|
|
; Copy key to storage |
|
lea r8, [rel kv_keys] |
|
mov rax, rbx |
|
mov r9, 100 ; KEY_SIZE |
|
mul r9 |
|
add r8, rax |
|
mov rdi, r8 |
|
mov rsi, r12 |
|
call copy_string_simple |
|
|
|
; Copy value to storage |
|
lea r8, [rel kv_values] |
|
mov rax, rbx |
|
mov r9, 1000 ; VALUE_SIZE |
|
mul r9 |
|
add r8, rax |
|
mov rdi, r8 |
|
mov rsi, r13 |
|
call copy_string_simple |
|
|
|
; Increment count |
|
inc qword [rel kv_count] |
|
jmp .kv_set_done |
|
|
|
.kv_update: |
|
; Update existing value |
|
lea r8, [rel kv_values] |
|
mov r9, 1000 |
|
mul r9 |
|
add r8, rax |
|
mov rdi, r8 |
|
mov rsi, r13 |
|
call copy_string_simple |
|
|
|
.kv_set_done: |
|
pop r13 |
|
pop r12 |
|
pop rbp |
|
ret |
|
|
|
copy_string_simple: |
|
push rbp |
|
mov rbp, rsp |
|
mov rcx, rdi |
|
mov rdx, rsi |
|
|
|
.copy_simple_loop: |
|
mov al, [rdx] |
|
cmp al, 0 |
|
je .copy_simple_done |
|
mov [rcx], al |
|
inc rcx |
|
inc rdx |
|
jmp .copy_simple_loop |
|
|
|
.copy_simple_done: |
|
mov byte [rcx], 0 |
|
pop rbp |
|
ret |
|
|
|
kv_get: |
|
push rbp |
|
mov rbp, rsp |
|
|
|
call kv_find |
|
cmp rax, -1 |
|
je .kv_get_not_found |
|
|
|
; Return pointer to value |
|
lea r8, [rel kv_values] |
|
mov rbx, rax |
|
mov r9, 1000 |
|
mov rax, rbx |
|
mul r9 |
|
add r8, rax |
|
mov rax, r8 |
|
jmp .kv_get_done |
|
|
|
.kv_get_not_found: |
|
mov rax, 0 |
|
|
|
.kv_get_done: |
|
pop rbp |
|
ret |
|
|
|
kv_del: |
|
push rbp |
|
mov rbp, rsp |
|
push r12 |
|
|
|
call kv_find |
|
cmp rax, -1 |
|
je .kv_del_not_found |
|
|
|
; Delete by shifting entries down |
|
mov r12, rax ; index to delete |
|
mov rbx, [rel kv_count] |
|
dec rbx ; new count |
|
|
|
; Clear the key (set first byte to 0) |
|
lea r8, [rel kv_keys] |
|
mov rax, r12 |
|
mov r9, 100 |
|
mul r9 |
|
add r8, rax |
|
mov byte [r8], 0 |
|
|
|
; Clear the value |
|
lea r8, [rel kv_values] |
|
mov rax, r12 |
|
mov r9, 1000 |
|
mul r9 |
|
add r8, rax |
|
mov byte [r8], 0 |
|
|
|
mov rax, 1 ; 1 key deleted |
|
jmp .kv_del_done |
|
|
|
.kv_del_not_found: |
|
mov rax, 0 |
|
|
|
.kv_del_done: |
|
pop r12 |
|
pop rbp |
|
ret |
|
|
|
kv_find: |
|
push rbp |
|
mov rbp, rsp |
|
push r12 |
|
|
|
mov r12, rdi ; key to find |
|
mov rbx, [rel kv_count] |
|
mov rcx, 0 |
|
|
|
.find_loop: |
|
cmp rcx, rbx |
|
jge .find_not_found |
|
|
|
push rcx |
|
push rbx |
|
|
|
; Get key pointer |
|
lea r8, [rel kv_keys] |
|
mov rax, rcx |
|
mov r9, 100 |
|
mul r9 |
|
add r8, rax |
|
|
|
; Skip deleted entries (first byte = 0) |
|
cmp byte [r8], 0 |
|
je .find_skip |
|
|
|
; Compare |
|
mov rdi, r8 |
|
mov rsi, r12 |
|
call _strcmp |
|
|
|
pop rbx |
|
pop rcx |
|
|
|
cmp rax, 0 |
|
je .find_found |
|
|
|
inc rcx |
|
jmp .find_loop |
|
|
|
.find_skip: |
|
pop rbx |
|
pop rcx |
|
inc rcx |
|
jmp .find_loop |
|
|
|
.find_found: |
|
mov rax, rcx |
|
jmp .find_done |
|
|
|
.find_not_found: |
|
mov rax, -1 |
|
|
|
.find_done: |
|
pop r12 |
|
pop rbp |
|
ret |
|
|
|
; ============================================================ |
|
; Send bulk string: $len\r\nstring\r\n |
|
; rdi = pointer to null-terminated string |
|
; ============================================================ |
|
send_bulk_string: |
|
push rbp |
|
mov rbp, rsp |
|
push r12 |
|
|
|
mov r12, rdi ; string pointer |
|
|
|
; Get string length |
|
call _strlen |
|
mov rbx, rax |
|
|
|
; Format header: $len\r\n |
|
lea rdi, [rel response_buf] |
|
lea rsi, [rel fmt_bulk] |
|
mov rdx, rbx |
|
call _sprintf |
|
|
|
; Send header |
|
lea rdi, [rel response_buf] |
|
call _strlen |
|
mov rdx, rax |
|
mov rdi, [rel client_fd] |
|
lea rsi, [rel response_buf] |
|
call _write |
|
|
|
; Send string content |
|
mov rdi, r12 |
|
call _strlen |
|
mov rdx, rax |
|
mov rdi, [rel client_fd] |
|
mov rsi, r12 |
|
call _write |
|
|
|
; Send trailing \r\n |
|
mov rdi, [rel client_fd] |
|
lea rsi, [rel crlf] |
|
mov rdx, 2 |
|
call _write |
|
|
|
pop r12 |
|
pop rbp |
|
ret |
|
|
|
; ============================================================ |
|
; Send integer: :number\r\n |
|
; rax = integer value |
|
; ============================================================ |
|
send_integer: |
|
push rbp |
|
mov rbp, rsp |
|
|
|
; Format: :number\r\n |
|
lea rdi, [rel response_buf] |
|
lea rsi, [rel fmt_int] |
|
mov rdx, rax |
|
call _sprintf |
|
|
|
; Send |
|
lea rdi, [rel response_buf] |
|
call _strlen |
|
mov rdx, rax |
|
mov rdi, [rel client_fd] |
|
lea rsi, [rel response_buf] |
|
call _write |
|
|
|
pop rbp |
|
ret |
|
|
|
error_exit: |
|
mov rdi, 1 |
|
call _exit |