简体   繁体   English

为什么这个组件 HTTP 服务器不起作用?

[英]Why doesn't this assembly HTTP server work?

I came across arguably the smallest HTTP server in docker (written in assembly), and I would love to see it in action!我在 docker 中遇到了可以说是最小的 HTTP 服务器(用汇编语言编写),我很想看到它的实际应用!

I think they took the code from https://gist.github.com/DGivney/5917914 :我认为他们从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

I can assemble and link the code without any errors.我可以组装和链接代码而不会出现任何错误。

But when I run it, nothing seems to happen.但是当我运行它时,似乎什么也没有发生。

What do I need to do in order to see the output from the assembly HTTP server in my browser?我需要做什么才能在我的浏览器中从 HTTP 服务器中看到 output?

Seems to barely sort of work for me, although it is buggy as Margaret Bloom noticed.似乎对我来说几乎没有什么工作,尽管玛格丽特布鲁姆注意到它有问题。 (It listens on a random port since it makes a bad bind syscall. Presumably passing the wrong number for sa_family ) (它在随机端口上侦听,因为它进行了错误的bind系统调用。可能为sa_family传递了错误的数字)

After building / linking with nasm -felf32 / ld -melf_i386 , I ran it under strace to see what it did.使用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

The mov al, callnum trick to save bytes assumes the upper bytes of EAX are still 0. If they aren't (all-ones from a -errno return), the next few syscalls are with invalid call numbers. mov al, callnum保存字节的技巧假定 EAX 的高字节仍然为 0。如果它们不是(来自-errno返回的全一),那么接下来的几个系统调用将具有无效的调用号。 But eventually it does listen(3,1) and accept , so it is listening somewhere.但最终它确实listen(3,1)accept ,所以它某处听。 I found its PID with ps , then used lsof to find out what port it was listening on:我用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)

Connecting to that port with nc (netcat) gets it to dump its fixed string payload and keep the connection open:使用nc (netcat) 连接到该端口会使其转储其固定字符串有效负载并保持连接打开:

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

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

Pointing Chromium at http://localhost:36047/ also loaded a page, but since the connection stays open it's still spinning waiting for more data.将 Chromium 指向 http://localhost:36047/ 也加载了一个页面,但由于连接保持打开状态,它仍在旋转等待更多数据。

The strace output after a few connections has grown to几个连接后的 strace output 已经增长到

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

BTW, another minimal HTTPD, one that builds to an even smaller binary thanks to its build scripts, is https://github.com/Francesco149/nolibc-httpd .顺便说一句,另一个最小的 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).有一个 C 版本(它为系统调用调用手写的 asm 包装器,而不仅仅是使用内联 asm)。 See How does this C program without libc work?请参阅没有 libc 的 C 程序如何工作? about it.关于它。

As I mentioned there, I got the C version down from 1.2k to 992 bytes, by switching to clang -Oz and using inline asm for system calls, so the whole thing could be a leaf function.正如我在那里提到的,通过切换到clang -Oz并使用内联汇编进行系统调用,我将 C 版本从 1.2k 降低到 992 字节,所以整个事情可能是一片叶子 ZC1C42525268E68A785D14 (Not needing to save/restore registers around anything.) https://github.com/pcordes/nolibc-httpd/commit/ad3a80b89b98379304f1525339fa71700bf1a15d (不需要保存/恢复任何东西的寄存器。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM