【发布时间】:2021-07-30 09:25:57
【问题描述】:
我遇到了arguably the smallest HTTP server in docker(用汇编语言编写),我很想看到它的实际应用!
我认为他们从https://gist.github.com/DGivney/5917914 获取了代码:
section .text
global _start
_start:
xor eax, eax ; init eax 0
xor ebx, ebx ; init ebx 0
xor esi, esi ; init esi 0
jmp _socket ; jmp to _socket
_socket_call:
mov al, 0x66 ; invoke SYS_SOCKET (kernel opcode 102)
inc byte bl ; increment bl (1=socket, 2=bind, 3=listen, 4=accept)
mov ecx, esp ; move address arguments struct into ecx
int 0x80 ; call SYS_SOCKET
jmp esi ; esi is loaded with a return address each call to _socket_call
_socket:
push byte 6 ; push 6 onto the stack (IPPROTO_TCP)
push byte 1 ; push 1 onto the stack (SOCK_STREAM)
push byte 2 ; push 2 onto the stack (PF_INET)
mov esi, _bind ; move address of _bind into ESI
jmp _socket_call ; jmp to _socket_call
_bind:
mov edi, eax ; move return value of SYS_SOCKET into edi (file descriptor for new socket, or -1 on error)
xor edx, edx ; init edx 0
push dword edx ; end struct on stack (arguments get pushed in reverse order)
push word 0x6022 ; move 24610 dec onto stack
push word bx ; move 1 dec onto stack AF_FILE
mov ecx, esp ; move address of stack pointer into ecx
push byte 0x10 ; move 16 dec onto stack
push ecx ; push the address of arguments onto stack
push edi ; push the file descriptor onto stack
mov esi, _listen ; move address of _listen onto stack
jmp _socket_call ; jmp to _socket_call
_listen:
inc bl ; bl = 3
push byte 0x01 ; move 1 onto stack (max queue length argument)
push edi ; push the file descriptor onto stack
mov esi, _accept ; move address of _accept onto stack
jmp _socket_call ; jmp to socket call
_accept:
push edx ; push 0 dec onto stack (address length argument)
push edx ; push 0 dec onto stack (address argument)
push edi ; push the file descriptor onto stack
mov esi, _fork ; move address of _fork onto stack
jmp _socket_call ; jmp to _socket_call
_fork:
mov esi, eax ; move return value of SYS_SOCKET into esi (file descriptor for accepted socket, or -1 on error)
mov al, 0x02 ; invoke SYS_FORK (kernel opcode 2)
int 0x80 ; call SYS_FORK
test eax, eax ; if return value of SYS_FORK in eax is zero we are in the child process
jz _write ; jmp in child process to _write
xor eax, eax ; init eax 0
xor ebx, ebx ; init ebx 0
mov bl, 0x02 ; move 2 dec in ebx lower bits
jmp _listen ; jmp in parent process to _listen
_write:
mov ebx, esi ; move file descriptor into ebx (accepted socket id)
push edx ; push 0 dec onto stack then push a bunch of ascii (http headers & reponse body)
push dword 0x0a0d3e31 ; [\n][\r]>1
push dword 0x682f3c21 ; h/<!
push dword 0x6f6c6c65 ; ello
push dword 0x683e3148 ; H<1h
push dword 0x3c0a0d0a ; >[\n][\r][\n]
push dword 0x0d6c6d74 ; [\r]lmt
push dword 0x682f7478 ; h/tx
push dword 0x6574203a ; et :
push dword 0x65707954 ; epyT
push dword 0x2d746e65 ; -tne
push dword 0x746e6f43 ; tnoC
push dword 0x0a4b4f20 ; \nKO
push dword 0x30303220 ; 002
push dword 0x302e312f ; 0.1/
push dword 0x50545448 ; PTTH
mov al, 0x04 ; invoke SYS_WRITE (kernel opcode 4)
mov ecx, esp ; move address of stack arguments into ecx
mov dl, 64 ; move 64 dec into edx lower bits (length in bytes to write)
int 0x80 ; call SYS_WRITE
_close:
mov al, 6 ; invoke SYS_CLOSE (kernel opcode 6)
mov ebx, esi ; move esi into ebx (accepted socket file descriptor)
int 0x80 ; call SYS_CLOSE
mov al, 6 ; invoke SYS_CLOSE (kernel opcode 6)
mov ebx, edi ; move edi into ebx (new socket file descriptor)
int 0x80 ; call SYS_CLOSE
_exit:
mov eax, 0x01 ; invoke SYS_EXIT (kernel opcode 1)
xor ebx, ebx ; 0 errors
int 0x80 ; call SYS_EXIT
我可以毫无错误地组装和链接代码。
但是当我运行它时,似乎什么也没发生。
我需要做什么才能在我的浏览器中查看程序集 HTTP 服务器的输出?
【问题讨论】:
-
在这里构建(最小)Dockerfile 是否比
docker import更好?容器真的启动了吗?服务器在监听哪个地址? -
要创建一个最小的dockerfile,我需要从汇编代码构建一个二进制文件,所以这似乎更容易?容器确实启动了。我不知道服务器正在监听什么地址或端口...
-
我不明白你为什么需要 docker。首先,通过用
push word 2替换push word bx来更正那个shi**y 代码,然后组装它(nasm -felf32 httpd.asm -o httpd.o)并链接它(ld -melf_i386 httpd.o -o http)。然后运行它./httpd。它将在端口8800上响应(您可以从绑定调用中轻松看到,并记住端口以大端顺序排列)。服务器会响应但很糟糕。 Chromium 将继续加载页面。 “功能”也绝对不是这个服务器。为什么要使用mov esi, ret_addr /jmp而不是call? -
@MargaretBloom:整个 ELF 图像的大小。他们使用
build.sh和gcc -Os和strip之类的东西,最终的二进制文件没有.data部分/段,所以IDK 需要多少空间。但是,是的,它可能会通过_write中的推送指令数量来收回成本,每条指令都比它们的直接有效负载多花费 1 个字节。mov esi, label/jmp _socket_call的数量相当愚蠢;与call相比,可能不会为自己买单。并且_socket_call可以在_start之前使用以避免跳过它。 -
@MargaretBloom:哦,这是一个不同的迷你 httpd。与github.com/Francesco149/nolibc-httpd 不同的来源 - 一个用于 x86-64,该 repo 中的纯 asm 来源基本上是手动调整的反汇编输出。并使用 muppetlabs.com/~breadbox/software/tiny/teensy.html 的 ELF64 版本 - 让
nasm -fbin发出 ELF 程序头,而不是依赖于ld。这一个旨在正常组装+链接到32位静态可执行文件中。所以别管之前的 SO Q&A,那是一个不同的玩具 httpd。
标签: linux assembly x86 system-calls