Web server in Assembler
1 Цель
Для изучения ассемблера предлагается создать простой веб-сервер, который будет эмулировать базу данных key-value.
- Сервер должен поддерживать эндпоинты:
GET /{key}
- получить значение по ключуkey
.POST /{key}
- сохранить значение из тела запроса по ключуkey
.
- Сервер должен поддерживать параллельную обработку запросов.
- Сервер должен хранить все данные на диске.
- Весь код сервера должен быть на ассемблере.
Т.к. писать код на ассемблере сложно и недостаточно комфортно, то допустимы упрощения.
2 Примерная реализация
Ассемблерный код будет использовать системные вызовы socket
, bind
, listen
, accept
, fork
, read
, write
, open
, close
для взаимодействия с файловой системой и сетевой подсистемой.
Для реализации сервера, который обрабатывает сетевые запросы, мы используем последовательность системных вызовов:
socket
- создаёт сокет, который будет использоваться для связи по сетиbind
- связывает сокет с определённым IP-адресом и портом, на которых сервер будет принимать соединенияlisten
- переводит сокет в состояние ожидания входящих соединений, позволяя ОС управлять запросами
После настройки сокета, сервер переходит в главный цикл:
- Системный вызов
accept
блокирует выполнение программы, ожидая новое входящее соединение. - Когда клиент подключается,
accept
возвращает новый сокет, представляющий это соединение. - Вызывается
fork
для создания нового процесса для обработки входящего соединения. - Если
pid == 0
, то это дочерний процесс, который будет обрабатывать клиентский запрос. - Если
pid > 0
, то это родительский процесс, который будет ожидать новое входящее соединение.
Не забываем закрыть неиспользуемые сокеты послеfork
(для родительского процесса - сокет клиента, для дочернего - сокет сервера).
Обработка клиентского сокета включает:
- Чтение сырого http запроса из сокета.
- Для
GET
запроса:- Получение
key
из запроса. - Открытие файла с именем
key
. - Чтение значения
val
из этого файла. - Закрытие файла.
- Запись в сокет валидного http ответа со значением
val
. - Закрытие сокета.
- Получение
- Для
POST
запроса:- Получение
key
,val
из запроса. - Создание файла с именем
key
. - Запись значения
val
в этот файл. - Закрытие файла.
- Запись в сокет валидного http ответа.
- Закрытие сокета.
- Получение
Общая высокоуровневая схема на псевдоязыке:
void main() {
srv_socket = socket()
bind(srv_socket)
listen(srv_socket)
while true {
cli_socket = accept(srv_socket)
pid = fork()
if pid == 0 {
close(srv_socket)
process_request(cli_socket)
exit(0)
}
close(cli_socket)
}
}
void process_request(socket) {
req = read(socket)
method = parse_method(req)
key = parse_url(req)
if method == "GET" {
fd = open(key)
val = read(fd)
close(fd)
write(socket, http_status_ok)
write(socket, val)
close(socket)
}
if method == "POST" {
val = parse_body(req)
fd = create(key)
write(fd, val)
close(fd)
write(socket, http_status_ok)
close(socket)
}
}
3 Реализация
Рассмотрим основные моменты.
Системные вызовы socket
, bind
, listen
.
Нужно помнить, что номер порта должен быть указан с учетом сетевого порядка байт.
# int socket(int domain, int type, int protocol);
mov rdi, AF_INET # domain
mov rsi, SOCK_STREAM # type
mov rdx, IPPROTO_IP # protocol
mov rax, SYS_socket # socket
syscall
test rax, rax
js _exit_with_error
mov [rbp-24], rax # save srv_socket_fd
# init sockaddr_in
movw [rbp-16], AF_INET
movw ax, PORT
xchg al, ah
movw [rbp-16+2], ax
movd [rbp-16+4], INADDR_ANY
# int bind(int socket, const struct sockaddr *address, socklen_t address_len);
mov rdi, [rbp-24] # socket
lea rsi, [rbp-16] # address
mov rdx, 16 # address_len
mov rax, SYS_bind # socket
syscall
test rax, rax
js _exit_with_error
# int listen(int socket, int backlog);
mov rdi, [rbp-24] # socket
mov rsi, 0 # backlog
mov rax, SYS_listen # listen
syscall
test rax, rax
js _exit_with_error
Основной цикл ожидания входящих соединений.
После срабатывания accept
вызывается fork
. Закрывается соответствующий неиспользуемый сокет в разных процессах.
Родительский процесс переходи к вызову accept. Дочерний обрабатывает входящее соединение и завершает работу через вызов exit
.
.accept:
# int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
mov rdi, [rbp-24] # socket
mov rsi, 0 # address
mov rdx, 0 # adress_len
mov rax, SYS_accept # accept
syscall
test rax, rax
js _exit_with_error
mov [rbp-32], rax # save cli_socket_fd
# pid_t fork(void);
mov rax, SYS_fork # fork
syscall
test rax, rax
js _exit_with_error
mov r14, rax # pid
# close cli_socket_fd in parent
# close srv_socket_fd in child
jz .accept_set_srv_socket_fd
mov r15, [rbp-32]
jmp .accept_close_fd
.accept_set_srv_socket_fd:
mov r15, [rbp-24]
.accept_close_fd:
# int close(int fd);
mov rdi, r15 # fd
mov rax, SYS_close # close
syscall
# parent process wait new clients
test r14, r14
jg .accept
# child process precesses client
mov rdi, [rbp-32]
call process_request
# exit(EXIT_OK)
mov rdi, EXIT_OK
mov rax, SYS_exit
syscall
Функция чтения данных из запроса пользователя и определение метода (GET или POST) по первой букве.
Все данные хранятся на стеке. Используется преамбула и эпилог для манипулирования размером стека.
process_request:
# args:
# rdi = fd
# vars:
# [rbp-8] - 8b, fd
# [rbp-16] - 8b, buffer_length
# [rbp-1040] - 1024b, buffer
push rbp
mov rbp, rsp
sub rsp, 1040
mov [rbp-8], rdi # save client socket fd
# ssize_t read(int fd, void buf[.count], size_t count);
mov rdi, [rbp-8] # fd
lea rsi, [rbp-1040] # buf
mov rdx, 1024 # count
mov rax, SYS_read # read
syscall
cmpb [rbp-1040], 'P'
je _process_post
mov rdi, [rbp-8]
lea rsi, [rbp-1040]
call process_get
jmp _endif
_process_post:
mov rdi, [rbp-8]
lea rsi, [rbp-1040]
mov rdx, rax
call process_post
_endif:
mov rsp, rbp
pop rbp
ret
Обработка POST запроса.
Самое сложное - правильно извлечь из запроса ключ и значение. Все остальное делается просто
process_post:
# args:
# rdi = socket_fd
# rsi = &buffer
# rdx = buffer_length
# vars:
# [rbp-8] - 8b, socket_fd
# [rbp-16] - 8b, &req_buffer
# [rbp-24] - 8b, buffer_length
# [rbp-32] - 8b, file_fd
push rbp
mov rbp, rsp
sub rsp, 32
mov [rbp-8], rdi
mov [rbp-16], rsi
mov [rbp-24], rdx
# set \x00 after "xxx" in "POST /xxx HTTP/1.1\r\n....."
mov r8, rsi
add r8, 5
.process_post_path:
add r8, 1
cmpb [r8], ' '
jne .process_post_path
.process_post_path_end:
movb byte [r8-1], 0
# int open(const char *pathname, int flags, /* mode_t mode */);
lea rdi, [rsi]+6 # pathname
mov rsi, O_WRONLY # flags
xor rsi, O_CREAT # flags
mov rdx, 0777 # mode
mov rax, SYS_open # open
syscall
test rax, rax
js _exit_with_error
mov [rbp-32], rax # save fd
# parse data between '\n' and end of body
mov r8, [rbp-16]
add r8, [rbp-24]
.process_body:
sub r8, 1
cmpb [r8], '\n'
jne .process_body
.process_body_end:
add r8, 1 # data_offset
mov r9, [rbp-16]
add r9, [rbp-24]
sub r9, r8 # data_length
# ssize_t write(int fd, void buf[.count], size_t count);
mov rdi, [rbp-32] # fd
mov rsi, r8 # buf
mov rdx, r9 # count
mov rax, SYS_write # write
syscall
# int close(int fd);
mov rdi, [rbp-32] # fd
mov rax, SYS_close # close
syscall
test rax, rax
js _exit_with_error
# ssize_t write(int fd, const void buf[.count], size_t count);
mov rdi, [rbp-8] # fd
lea rsi, [response_header_ok] # buf
mov rdx, response_header_ok_length # count
mov rax, SYS_write # write
syscall
mov rsp, rbp
pop rbp
ret
Обработка GET запроса
process_get:
# args:
# rdi = socket_fd
# rsi = &buffer
# vars:
# [rbp-8] - 8b, socket_fd
# [rbp-16] - 8b, &req_buffer
# [rbp-24] - 8b, file_fd
# [rbp-1048] - 1024b, file_buffer
# [rbp-1056] - 8b, file_buffer_length
push rbp
mov rbp, rsp
sub rsp, 1056
mov [rbp-8], rdi
mov [rbp-16], rsi
# set \x00 after "xxx" in "GET /xxx HTTP/1.1\r\n....."
mov r8, rsi
add r8, 4
.process_get_path:
add r8, 1
cmpb [r8], ' '
jne .process_get_path
.process_get_path_end:
movb byte [r8-1], 0
# int open(const char *pathname, int flags, /* mode_t mode */);
lea rdi, [rsi]+5 # pathname
mov rsi, O_RDONLY # flags
mov rdx, 0 # mode
mov rax, SYS_open # open
syscall
test rax, rax
js _exit_with_error
mov [rbp-24], rax
# ssize_t read(int fd, void buf[.count], size_t count);
mov rdi, [rbp-24] # fd
lea rsi, [rbp-1048] # buf
mov rdx, 1024 # count
mov rax, SYS_read # read
syscall
mov [rbp-1056], rax
# int close(int fd);
mov rdi, [rbp-24] # fd
mov rax, SYS_close # close
syscall
# ssize_t write(int fd, const void buf[.count], size_t count);
mov rdi, [rbp-8] # fd
lea rsi, [response_header_ok] # buf
mov rdx, response_header_ok_length # count
mov rax, SYS_write # write
syscall
# ssize_t write(int fd, const void buf[.count], size_t count);
mov rdi, [rbp-8] # fd
lea rsi, [rbp-1048] # buf
mov rdx, [rbp-1056] # count
mov rax, SYS_write # write
syscall
mov rsp, rbp
pop rbp
ret
Полный исходный код сервера:
.intel_syntax noprefix
.globl _start
.equ SYS_read, 0
.equ SYS_write, 1
.equ SYS_open, 2
.equ SYS_close, 3
.equ SYS_socket, 41
.equ SYS_accept, 43
.equ SYS_bind, 49
.equ SYS_listen, 50
.equ SYS_fork, 57
.equ SYS_exit, 60
.equ EXIT_OK, 0
.equ EXIT_ERROR, 1
.equ AF_INET, 2
.equ SOCK_STREAM, 1
.equ IPPROTO_IP, 0
.equ INADDR_ANY, 0
.equ O_RDONLY, 0
.equ O_WRONLY, 1
.equ O_CREAT, 64
.equ PORT, 8080
.section .data
response_header_ok:
.ascii "HTTP/1.0 200 OK\r\n\r\n"
response_header_ok_length = . - response_header_ok
.section .text
_start:
# vars:
# [rbp-16] - 16b, sockaddr
# [rbp-24] - 8b, srv_socket_fd
# [rbp-32] - 8b, cli_socket_fd
push rbp
mov rbp, rsp
sub rsp, 32
# int socket(int domain, int type, int protocol);
mov rdi, AF_INET # domain
mov rsi, SOCK_STREAM # type
mov rdx, IPPROTO_IP # protocol
mov rax, SYS_socket # socket
syscall
test rax, rax
js _exit_with_error
mov [rbp-24], rax # save srv_socket_fd
# init sockaddr_in
movw [rbp-16], AF_INET
movw ax, PORT
xchg al, ah
movw [rbp-16+2], ax
movd [rbp-16+4], INADDR_ANY
# int bind(int socket, const struct sockaddr *address, socklen_t address_len);
mov rdi, [rbp-24] # socket
lea rsi, [rbp-16] # address
mov rdx, 16 # address_len
mov rax, SYS_bind # socket
syscall
test rax, rax
js _exit_with_error
# int listen(int socket, int backlog);
mov rdi, [rbp-24] # socket
mov rsi, 0 # backlog
mov rax, SYS_listen # listen
syscall
test rax, rax
js _exit_with_error
.accept:
# int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
mov rdi, [rbp-24] # socket
mov rsi, 0 # address
mov rdx, 0 # adress_len
mov rax, SYS_accept # accept
syscall
test rax, rax
js _exit_with_error
mov [rbp-32], rax # save cli_socket_fd
# pid_t fork(void);
mov rax, SYS_fork # fork
syscall
test rax, rax
js _exit_with_error
mov r14, rax # pid
# close cli_socket_fd in parent
# close srv_socket_fd in child
jz .accept_set_srv_socket_fd
mov r15, [rbp-32]
jmp .accept_close_fd
.accept_set_srv_socket_fd:
mov r15, [rbp-24]
.accept_close_fd:
# int close(int fd);
mov rdi, r15 # fd
mov rax, SYS_close # close
syscall
# parent process wait new clients
test r14, r14
jg .accept
# child process precesses client
mov rdi, [rbp-32]
call process_request
# exit(EXIT_OK)
mov rdi, EXIT_OK
mov rax, SYS_exit
syscall
mov rsp, rbp
pop rbp
ret
_exit_with_error:
# exit(EXIT_ERROR)
mov rdi, EXIT_ERROR
mov rax, SYS_exit
syscall
process_request:
# args:
# rdi = fd
# vars:
# [rbp-8] - 8b, fd
# [rbp-16] - 8b, buffer_length
# [rbp-1040] - 1024b, buffer
push rbp
mov rbp, rsp
sub rsp, 1040
mov [rbp-8], rdi # save client socket fd
# ssize_t read(int fd, void buf[.count], size_t count);
mov rdi, [rbp-8] # fd
lea rsi, [rbp-1040] # buf
mov rdx, 1024 # count
mov rax, SYS_read # read
syscall
cmpb [rbp-1040], 'P'
je _process_post
mov rdi, [rbp-8]
lea rsi, [rbp-1040]
call process_get
jmp _endif
_process_post:
mov rdi, [rbp-8]
lea rsi, [rbp-1040]
mov rdx, rax
call process_post
_endif:
mov rsp, rbp
pop rbp
ret
process_post:
# args:
# rdi = socket_fd
# rsi = &buffer
# rdx = buffer_length
# vars:
# [rbp-8] - 8b, socket_fd
# [rbp-16] - 8b, &req_buffer
# [rbp-24] - 8b, buffer_length
# [rbp-32] - 8b, file_fd
push rbp
mov rbp, rsp
sub rsp, 32
mov [rbp-8], rdi
mov [rbp-16], rsi
mov [rbp-24], rdx
# set \x00 after "xxx" in "POST /xxx HTTP/1.1\r\n....."
mov r8, rsi
add r8, 5
.process_post_path:
add r8, 1
cmpb [r8], ' '
jne .process_post_path
.process_post_path_end:
movb byte [r8-1], 0
# int open(const char *pathname, int flags, /* mode_t mode */);
lea rdi, [rsi]+6 # pathname
mov rsi, O_WRONLY # flags
xor rsi, O_CREAT # flags
mov rdx, 0777 # mode
mov rax, SYS_open # open
syscall
test rax, rax
js _exit_with_error
mov [rbp-32], rax # save fd
# parse data between '\n' and end of body
mov r8, [rbp-16]
add r8, [rbp-24]
.process_body:
sub r8, 1
cmpb [r8], '\n'
jne .process_body
.process_body_end:
add r8, 1 # data_offset
mov r9, [rbp-16]
add r9, [rbp-24]
sub r9, r8 # data_length
# ssize_t write(int fd, void buf[.count], size_t count);
mov rdi, [rbp-32] # fd
mov rsi, r8 # buf
mov rdx, r9 # count
mov rax, SYS_write # write
syscall
# int close(int fd);
mov rdi, [rbp-32] # fd
mov rax, SYS_close # close
syscall
test rax, rax
js _exit_with_error
# ssize_t write(int fd, const void buf[.count], size_t count);
mov rdi, [rbp-8] # fd
lea rsi, [response_header_ok] # buf
mov rdx, response_header_ok_length # count
mov rax, SYS_write # write
syscall
mov rsp, rbp
pop rbp
ret
process_get:
# args:
# rdi = socket_fd
# rsi = &buffer
# vars:
# [rbp-8] - 8b, socket_fd
# [rbp-16] - 8b, &req_buffer
# [rbp-24] - 8b, file_fd
# [rbp-1048] - 1024b, file_buffer
# [rbp-1056] - 8b, file_buffer_length
push rbp
mov rbp, rsp
sub rsp, 1056
mov [rbp-8], rdi
mov [rbp-16], rsi
# set \x00 after "xxx" in "GET /xxx HTTP/1.1\r\n....."
mov r8, rsi
add r8, 4
.process_get_path:
add r8, 1
cmpb [r8], ' '
jne .process_get_path
.process_get_path_end:
movb byte [r8-1], 0
# int open(const char *pathname, int flags, /* mode_t mode */);
lea rdi, [rsi]+5 # pathname
mov rsi, O_RDONLY # flags
mov rdx, 0 # mode
mov rax, SYS_open # open
syscall
test rax, rax
js _exit_with_error
mov [rbp-24], rax
# ssize_t read(int fd, void buf[.count], size_t count);
mov rdi, [rbp-24] # fd
lea rsi, [rbp-1048] # buf
mov rdx, 1024 # count
mov rax, SYS_read # read
syscall
mov [rbp-1056], rax
# int close(int fd);
mov rdi, [rbp-24] # fd
mov rax, SYS_close # close
syscall
# ssize_t write(int fd, const void buf[.count], size_t count);
mov rdi, [rbp-8] # fd
lea rsi, [response_header_ok] # buf
mov rdx, response_header_ok_length # count
mov rax, SYS_write # write
syscall
# ssize_t write(int fd, const void buf[.count], size_t count);
mov rdi, [rbp-8] # fd
lea rsi, [rbp-1048] # buf
mov rdx, [rbp-1056] # count
mov rax, SYS_write # write
syscall
mov rsp, rbp
pop rbp
ret
4 Тестирование
Компилируем исходный файл server.s
в исполняемый:
as -o server.o server.s
ld -o server server.o
Запускаем сервер (можно через strace
для отслеживания состояния и ошибок).
./server
strace -f -s 256 ./server
С помощью curl
выполняем тестовые запросы:
curl -i http://0.0.0.0:8080/key_abc --data "value_abc"
HTTP/1.0 200 OK
curl -i http://0.0.0.0:8080/12345
curl: (52) Empty reply from server
curl -i http://0.0.0.0:8080/12345 --data "abcdef"
HTTP/1.0 200 OK
curl -i http://0.0.0.0:8080/12345
HTTP/1.0 200 OK
abcdef
curl -i http://0.0.0.0:8080/12345 --data "new_value"
HTTP/1.0 200 OK
curl -i http://0.0.0.0:8080/12345
HTTP/1.0 200 OK
new_value
curl -i http://0.0.0.0:8080/key_abc
HTTP/1.0 200 OK
value_abc
Видно, что сервер корректно обрабатывает запросы получение значния по ключу и на создание/изменение записи по ключу.
Только, если запрошенный ключ не существет, то сервер закрывает соединение без корректного ответа - не реализовано.
В выводе strace такое же поведение как и задумывалось. Порядок системных вызовов и их аргументы валидны.
strace -f -s 256 ./server
execve("./server", ["./server"], 0x7fff3cce04e8 /* 16 vars */) = 0
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 0) = 0
accept(3, NULL, NULL) = 4
fork(strace: Process 2067 attached
) = 2067
[pid 2067] close(3 <unfinished ...>
[pid 2064] close(4 <unfinished ...>
[pid 2067] <... close resumed>) = 0
[pid 2064] <... close resumed>) = 0
[pid 2067] read(4, <unfinished ...>
[pid 2064] accept(3, NULL, NULL <unfinished ...>
[pid 2067] <... read resumed>"POST /key_abc HTTP/1.1\r\nHost: 0.0.0.0:8080\r\nUser-Agent: curl/8.7.1\r\nAccept: */*\r\nContent-Length: 9\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nvalue_abc", 1024) = 160
[pid 2067] open("key_abc", O_WRONLY|O_CREAT, 0777) = 3
[pid 2067] write(3, "value_abc", 9) = 9
[pid 2067] close(3) = 0
[pid 2067] write(4, "HTTP/1.0 200 OK\r\n\r\n", 19) = 19
[pid 2067] exit(0) = ?
[pid 2067] +++ exited with 0 +++
<... accept resumed>) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2067, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
accept(3, NULL, NULL) = 4
fork(strace: Process 2069 attached
) = 2069
[pid 2064] close(4 <unfinished ...>
[pid 2069] close(3 <unfinished ...>
[pid 2064] <... close resumed>) = 0
[pid 2069] <... close resumed>) = 0
[pid 2064] accept(3, NULL, NULL <unfinished ...>
[pid 2069] read(4, "GET /12345 HTTP/1.1\r\nHost: 0.0.0.0:8080\r\nUser-Agent: curl/8.7.1\r\nAccept: */*\r\n\r\n", 1024) = 80
[pid 2069] open("12345", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid 2069] exit(1) = ?
[pid 2069] +++ exited with 1 +++
<... accept resumed>) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2069, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
accept(3, NULL, NULL) = 4
fork(strace: Process 2071 attached
) = 2071
[pid 2071] close(3 <unfinished ...>
[pid 2064] close(4 <unfinished ...>
[pid 2071] <... close resumed>) = 0
[pid 2064] <... close resumed>) = 0
[pid 2064] accept(3, NULL, NULL <unfinished ...>
[pid 2071] read(4, "POST /12345 HTTP/1.1\r\nHost: 0.0.0.0:8080\r\nUser-Agent: curl/8.7.1\r\nAccept: */*\r\nContent-Length: 6\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nabcdef", 1024) = 155
[pid 2071] open("12345", O_WRONLY|O_CREAT, 0777) = 3
[pid 2071] write(3, "abcdef", 6) = 6
[pid 2071] close(3) = 0
[pid 2071] write(4, "HTTP/1.0 200 OK\r\n\r\n", 19) = 19
[pid 2071] exit(0) = ?
[pid 2071] +++ exited with 0 +++
<... accept resumed>) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2071, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
accept(3, NULL, NULL) = 4
fork(strace: Process 2073 attached
) = 2073
[pid 2064] close(4) = 0
[pid 2073] close(3 <unfinished ...>
[pid 2064] accept(3, NULL, NULL <unfinished ...>
[pid 2073] <... close resumed>) = 0
[pid 2073] read(4, "GET /12345 HTTP/1.1\r\nHost: 0.0.0.0:8080\r\nUser-Agent: curl/8.7.1\r\nAccept: */*\r\n\r\n", 1024) = 80
[pid 2073] open("12345", O_RDONLY) = 3
[pid 2073] read(3, "abcdef", 1024) = 6
[pid 2073] close(3) = 0
[pid 2073] write(4, "HTTP/1.0 200 OK\r\n\r\n", 19) = 19
[pid 2073] write(4, "abcdef", 6) = 6
[pid 2073] exit(0) = ?
[pid 2073] +++ exited with 0 +++
<... accept resumed>) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2073, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
accept(3, NULL, NULL) = 4
fork(strace: Process 2075 attached
) = 2075
[pid 2075] close(3 <unfinished ...>
[pid 2064] close(4 <unfinished ...>
[pid 2075] <... close resumed>) = 0
[pid 2064] <... close resumed>) = 0
[pid 2075] read(4, <unfinished ...>
[pid 2064] accept(3, NULL, NULL <unfinished ...>
[pid 2075] <... read resumed>"POST /12345 HTTP/1.1\r\nHost: 0.0.0.0:8080\r\nUser-Agent: curl/8.7.1\r\nAccept: */*\r\nContent-Length: 9\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nnew_value", 1024) = 158
[pid 2075] open("12345", O_WRONLY|O_CREAT, 0777) = 3
[pid 2075] write(3, "new_value", 9) = 9
[pid 2075] close(3) = 0
[pid 2075] write(4, "HTTP/1.0 200 OK\r\n\r\n", 19) = 19
[pid 2075] exit(0) = ?
[pid 2075] +++ exited with 0 +++
<... accept resumed>) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2075, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
accept(3, NULL, NULL) = 4
fork(strace: Process 2077 attached
) = 2077
[pid 2064] close(4 <unfinished ...>
[pid 2077] close(3 <unfinished ...>
[pid 2064] <... close resumed>) = 0
[pid 2077] <... close resumed>) = 0
[pid 2064] accept(3, NULL, NULL <unfinished ...>
[pid 2077] read(4, "GET /12345 HTTP/1.1\r\nHost: 0.0.0.0:8080\r\nUser-Agent: curl/8.7.1\r\nAccept: */*\r\n\r\n", 1024) = 80
[pid 2077] open("12345", O_RDONLY) = 3
[pid 2077] read(3, "new_value", 1024) = 9
[pid 2077] close(3) = 0
[pid 2077] write(4, "HTTP/1.0 200 OK\r\n\r\n", 19) = 19
[pid 2077] write(4, "new_value", 9) = 9
[pid 2077] exit(0) = ?
[pid 2077] +++ exited with 0 +++
<... accept resumed>) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2077, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
accept(3, NULL, NULL) = 4
fork(strace: Process 2079 attached
) = 2079
[pid 2064] close(4 <unfinished ...>
[pid 2079] close(3 <unfinished ...>
[pid 2064] <... close resumed>) = 0
[pid 2079] <... close resumed>) = 0
[pid 2064] accept(3, NULL, NULL <unfinished ...>
[pid 2079] read(4, "GET /key_abc HTTP/1.1\r\nHost: 0.0.0.0:8080\r\nUser-Agent: curl/8.7.1\r\nAccept: */*\r\n\r\n", 1024) = 82
[pid 2079] open("key_abc", O_RDONLY) = 3
[pid 2079] read(3, "value_abc", 1024) = 9
[pid 2079] close(3) = 0
[pid 2079] write(4, "HTTP/1.0 200 OK\r\n\r\n", 19) = 19
[pid 2079] write(4, "value_abc", 9) = 9
[pid 2079] exit(0) = ?
[pid 2079] +++ exited with 0 +++
<... accept resumed>) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2079, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
5 Полезные ссылки
- Linux system call information for various architechtures
- Code Browser for C, C++, Rust & Dart
- fork(2) — Linux manual page
- Building a Web Server - Computing 101 - pwn.college
6 Выводы
Как оказалось не так сложно написать на ассемблере не просто что-то рабочее, а работающий http сервер.
Размер исходного файла 300-400 строк. Размер исполняемого файла всего 8кб.
Конечно, не нужно так писать сервер.
Но так можно освоить ассемблер, и написать что-то компактное встаиваемое для других задач.