为什么这个程序集 HTTP 服务器不起作用?

Why doesn't this assembly HTTP server work?

我遇到了 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

我可以 assemble 和 link 代码没有任何错误。

但是当我运行它时,似乎什么也没有发生。

我需要做什么才能在浏览器中看到程序集 HTTP 服务器的输出?

虽然玛格丽特·布鲁姆 (Margaret Bloom) 注意到它有很多问题,但对我来说似乎几乎不起作用。 (它在 运行dom 端口上侦听,因为它进行了错误的 bind 系统调用。可能为 sa_family 传递了错误的数字)

在使用 nasm -felf32 / ld -melf_i386 构建/链接后,我 运行 在 strace 下查看它做了什么。

$ strace ./httpd
execve("./httpd", ["./httpd"], 0x7ffde685ac10 /* 54 vars */) = 0
[ Process PID=615796 runs in 32 bit mode. ]
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
bind(3, {sa_family=AF_UNIX, sun_path="\"`"}, 16) = -1 EAFNOSUPPORT (Address family not supported by protocol)
syscall_0xffffffffffffff66(0x4, 0xffd53c58, 0, 0x8049043, 0x3, 0) = -1 ENOSYS (Function not implemented)
syscall_0xffffffffffffff66(0x5, 0xffd53c4c, 0, 0x804904d, 0x3, 0) = -1 ENOSYS (Function not implemented)
syscall_0xffffffffffffff02(0x5, 0xffd53c4c, 0, 0xffffffda, 0x3, 0) = -1 ENOSYS (Function not implemented)
listen(3, 1)                            = 0
accept(3, NULL, NULL

mov al, callnum 保存字节的技巧假定 EAX 的高位字节仍然为 0。如果它们不是(来自 -errno return 的全一),下一个很少有系统调用带有无效的调用号。但最终它会 listen(3,1)accept,所以它 在某处监听。我用 ps 找到它的 PID,然后 使用 lsof 找出它正在监听的端口:

$ lsof -p 615796
COMMAND    PID  USER   FD   TYPE   DEVICE SIZE/OFF  NODE NAME
httpd   615796 peter  cwd    DIR     0,55      940     1 /tmp
httpd   615796 peter  rtd    DIR     0,27      158   256 /
httpd   615796 peter  txt    REG     0,55     5412 56241 /tmp/httpd
httpd   615796 peter    0u   CHR   136,20      0t0    23 /dev/pts/20
httpd   615796 peter    1u   CHR   136,20      0t0    23 /dev/pts/20
httpd   615796 peter    2u   CHR   136,20      0t0    23 /dev/pts/20
httpd   615796 peter    3u  IPv4 86480691      0t0   TCP *:36047 (LISTEN)

使用 nc (netcat) 连接到该端口使其转储其固定字符串有效负载并保持连接打开:

$ nc localhost 36047
HTTP/1.0 200 OK
Content-Type: text/html

<h1>PwN3d!</h1>
       CONTROL-C
$

将 Chromium 指向 http://localhost:36047/ 也加载了一个页面,但由于连接保持打开状态,它仍在旋转等待更多数据。

几个连接后的 strace 输出已经增长到

accept(3, NULL, NULL)                   = 4
fork()                                  = 615904
listen(3, 1)                            = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=615904, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
accept(3, NULL, NULL)                   = 5
fork()                                  = 615986
listen(3, 1)                            = 0
accept(3, NULL, NULL)                   = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=615986, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
accept(3, NULL, NULL

顺便说一句,另一个最小的 HTTPD,由于其构建脚本而构建成一个更小的二进制文件,https://github.com/Francesco149/nolibc-httpd. There's a C version (which calls handwritten asm wrappers for system calls, instead of just using inline asm). See 关于它。

正如我在那里提到的,通过切换到 clang -Oz 并使用内联 asm 进行系统调用,我将 C 版本从 1.2k 减少到 992 字节,因此整个事情可以是叶函数。 (不需要 save/restore 注册任何东西。)https://github.com/pcordes/nolibc-httpd/commit/ad3a80b89b98379304f1525339fa71700bf1a15d