【问题标题】:Linux x86_64 assembly socket programmingLinux x86_64 汇编套接字编程
【发布时间】:2015-01-09 14:32:35
【问题描述】:

大家好。

所以我正在学习汇编。
按照我学习任何新语言的通常学习步骤,我已经开始使用汇编建立网络。

遗憾的是,我在第 0 步几乎失败了,这将获得一个可以开始通信的套接字。

汇编代码应该大致等于下面的C代码:

#include <stdio.h>
#include <sys/socket.h>

int main(){
        int sock;
        sock = socket(AF_INET, SOCK_STREAM, 0);
}

(让我们忽略它现在没有关闭套接字的事实。)

这就是我到目前为止所做的:

  • 已检查the manual。这意味着我需要制作一个socketcall() 这一切都很好。问题开始于它需要一个int 来描述它应该进行什么样的套接字调用。电话 manpage 对此也没有多大帮助,因为它只描述了:

在某些架构上——例如,x86-64 和 ARM——没有 socketcall() 系统调用;取而代之的是 socket(2)、accept(2)、bind(2) 和 等等确实是作为单独的系统调用实现的。

  • 然而,在系统调用的原始列表中没有这样的调用——据我所知,socket()accept()bind()listen() 等是来自 libnet 的调用,而不是来自内核。这让我非常困惑,所以我决定编译上面的C 代码并使用strace 进行检查。这产生了以下结果:

    socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3

  • 虽然这并没有让我更接近了解socket() ,但它确实解释了它的论点。对于女巫,我似乎(再次)找不到合适的文档。我认为PF_INETSOCK_STREAMIPPROTO_IP 将在&lt;sys/socket.h&gt; 中定义,但我的grep-ing 似乎没有找到任何用处。所以我决定通过使用gdbdisass main 来查找值。这给出了以下输出:

    函数 main 的汇编代码转储: 0x00000000004004fd : 推送 rbp 0x00000000004004fe : mov rbp,rsp 0x0000000000400501 : sub rsp,0x10 0x0000000000400505 : 移动 edx,0x0 0x000000000040050a : 移动 esi,0x1 0x000000000040050f : mov edi,0x2 0x0000000000400514 : 调用 0x400400 0x0000000000400519 : mov DWORD PTR [rbp-0x4],eax 0x000000000040051c :离开
    0x000000000040051d : ret
    汇编程序转储结束。

  • 根据我的经验,这意味着 socket()EDX (PF_INET)、ESI (SOCK_STREAM) 和 EDI (IPPROTO_IP) 获取参数。这对于系统调用来说会很奇怪(因为 linux 系统调用的约定是使用EAX/RAX 作为调用号和其他寄存器以递增顺序的参数,例如RBXRCX,@ 987654352@ ...)。这是 CALL-ed 而不是 INT 0x80'd 的事实也意味着这实际上不是系统调用,而是从共享对象调用的东西。什么的。

  • 但话又说回来了。在寄存器中传递参数对于CALL-ed 来说是非常奇怪的。通常,据我所知,被调用事物的参数应该是PUSH-ed 到堆栈上,因为编译器不知道他们会尝试使用哪些寄存器。

  • 当使用ldd检查生成的二进制文件时,这种行为变得更加奇怪:

    linux-vdso.so.1 (0x00007fff4a7fc000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f56b0c61000) /lib64/ld-linux-x86-64.so.2 (0x00007f56b1037000)

  • 似乎没有链接网络库。

这就是我没有想法的地方。

所以我要求以下内容:

  • 描述x86-64 linux 内核的实际系统调用及其相关编号的文档。 (最好作为C的头文件。)
  • 定义 PF_INETSOCK_STREAMIPPROTO_IP 的头文件,因为我无法在自己的系统上找到它们,这让我很困扰。
  • 也许是x86-64 linux 上的汇编网络教程。 (对于x86-32,很容易找到材料,但由于某种原因,我对 64 位的东西一无所知。)

谢谢!

【问题讨论】:

  • 您没有在 grep 包含的头文件中找到定义,因为它又包含一个可移植性较差的头文件,该文件提供实际值(可能是几个这样的层)。大多数具有中到大量寄存器的处理器传统上是通过 ABI 进行编程,将前几个参数放在指定的寄存器中以提高效率,然后再求助于堆栈来处理其余部分。
  • '网络与装配' - 你真的选择它作为学习练习吗?有人用枪指着你吗?

标签: c linux sockets assembly x86-64


【解决方案1】:

这是针对 x86 系统的。如果您想用于 x86_64 系统,请将 x86 寄存器更改为 x86_64。例如将“eax”更改为“rax”或将“esp”更改为“rsp”。并更改 eax(rax) 中的系统调用值,请参阅 https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md

[bits 32]

global _start
section .data
   msg: db "Socket Failed To Create!",0xa,0
   len: equ $-msg

   msg1: db "Socket Created",0xa,0
   len1: equ $-msg1

   msg2: db "Recv Or Send Failed",0xa,0
   len2: equ $-msg2

   msg3: db "Shutdown Socket Failed",0xa,0
   len3: equ $-msg3

   DATASIZE:        equ 5

   SOCK_STREAM:     equ 1
   AF_INET:         equ 2
   AF_INET:         equ 2
   INADDR_ANY:      equ 0
   MSG_WAITALL:     equ 0x100
   MSG_DONTWAIT:    equ 0x40
   SHUT_RDWR:       equ 2

    SYS_SOCKET:     equ 1       ; sys_socket(2)
    SYS_BIND:       equ 2       ; sys_bind(2)
    SYS_CONNECT:    equ 3       ; sys_connect(2)
    SYS_LISTEN:     equ 4       ; sys_listen(2)
    SYS_ACCEPT:     equ 5       ; sys_accept(2)
    SYS_GETSOCKNAME:equ 6       ; sys_getsockname(2)
    SYS_GETPEERNAME:equ 7       ; sys_getpeername(2)
    SYS_SOCKETPAIR: equ 8       ; sys_socketpair(2)
    SYS_SEND:       equ 9       ; sys_send(2)
    SYS_RECV:       equ 10      ; sys_recv(2)
    SYS_SENDTO:     equ 11      ; sys_sendto(2)
    SYS_RECVFROM:   equ 12      ; sys_recvfrom(2)
    SYS_SHUTDOWN:   equ 13      ; sys_shutdown(2)
    SYS_SETSOCKOPT: equ 14      ; sys_setsockopt(2)
    SYS_GETSOCKOPT: equ 15      ; sys_getsockopt(2)
    SYS_SENDMSG:    equ 16      ; sys_sendmsg(2)
    SYS_RECVMSG:    equ 17      ; sys_recvmsg(2)
    SYS_ACCEPT4:    equ 18      ; sys_accept4(2)
    SYS_RECVMMSG:   equ 19      ; sys_recvmmsg(2)
    SYS_SENDMMSG:   equ 20      ; sys_sendmmsg(2)

struc sockaddr_in, -0x30
    .sin_family:    resb 2  ;2bytes
    .sin_port:      resb 2  ;2bytes
    .sin_addr:      resb 4  ;4bytes
    .sin_zero:      resb 8  ;8bytes
endstruc

struc socket, -0x40
    .socketfd       resb 4
    .connectionfd   resb 4
    .count          resb 4
    .data           resb DATASIZE
endstruc

section .text

_start:
    push ebp
    mov ebp, esp
    sub esp, 0x400 ;1024byte

    xor edx, edx    ;or use cdq
    ;
    ; int socket(int domain, int type, int protocol);
    ; domain: The domain argument specifies a communication domain
    ; 
    push edx                        ; Push protocol
    push dword SOCK_STREAM          ; Push type
    push dword AF_INET              ; Push domain
    mov ecx, esp                    ; ECX points to args
    mov ebx, SYS_SOCKET             ;
    mov eax, 0x66                   ; socketcall()
    int 0x80

    cmp eax, 0
    jl .socket_failed
    mov [ebp + socket.socketfd], eax

    ;
    ; fill struct sockaddr_in serv_addr;
    ;
    mov word [ebp + sockaddr_in.sin_family], AF_INET
    mov word [ebp + sockaddr_in.sin_port], 0x3905
    mov dword [ebp + sockaddr_in.sin_addr], INADDR_ANY

    push dword [ebp + sockaddr_in.sin_addr]
    push word  [ebp + sockaddr_in.sin_port]
    push word  [ebp + sockaddr_in.sin_family]

    mov ecx, esp

    ;
    ; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    ;
    push byte 0x10                  ; sizeof(struct sockaddr)
    push ecx                        ; pointer struct sockaddr
    push dword [ebp + socket.socketfd]
    mov ecx, esp                    ; ECX points to args
    mov ebx, SYS_BIND               ;
    mov eax, 0x66
    int 0x80

    cmp eax, 0
    jne .socket_failed


    ;
    ;   int listen(int sockfd, int backlog);
    ;
    push dword 0x10
    push dword [ebp + socket.socketfd]
    mov ecx, esp
    mov ebx, SYS_LISTEN
    mov eax, 0x66
    int 0x80
    cmp eax, 0
    jne .socket_failed



    ;
    ; int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    ;
    xor ebx, ebx
    push ebx
    push ebx
    push dword [ebp + socket.socketfd]
    mov ecx, esp
    mov ebx, SYS_ACCEPT
    mov eax, 0x66
    int 0x80
    cmp eax, -1
    je .socket_failed
    mov [ebp + socket.connectionfd], eax

    mov dword [ebp + socket.count], 0
.again:
    lea edi, [ebp + socket.data]
    mov ecx, DATASIZE
    mov eax, 0
    rep stosd

    lea eax, [ebp + socket.data]
    ;
    ; ssize_t recv(int sockfd, const void *buf, size_t len, int flags);
    ;
    push dword MSG_WAITALL
    push dword DATASIZE
    push eax
    push dword [ebp + socket.connectionfd]
    mov ecx, esp
    mov ebx, SYS_RECV
    mov eax, 0x66
    int 0x80
    cmp eax, 0
    jle .recv_or_send_failed

    mov edx, eax
    lea ecx, [ebp + socket.data]
    call printk

    inc dword [ebp + socket.count]
    cmp dword [ebp + socket.count], 5
    jle  .again
.break:
    ;
    ; int shutdown(int sockfd, int how);
    ;
    push dword SHUT_RDWR
    push dword [ebp + socket.socketfd]
    mov ecx, esp
    mov ebx, SYS_SHUTDOWN
    mov eax, 0x66
    int 0x80
    cmp eax, 0
    jne .shutdown_failed

    ;
    ; int close(int fd)
    ;
    mov ebx, [ebp + socket.connectionfd]
    mov eax, 0x06
    int 0x80
    cmp eax, 0
    jne .shutdown_failed

    jmp .success

.shutdown_failed:
    mov edx, len3
    mov ecx, msg3
    call printk
    jmp .end
.recv_or_send_failed:
    mov edx, len2
    mov ecx, msg2
    call printk
    jmp .end
.socket_failed:

    mov edx, len
    mov ecx, msg
    call printk
    jmp .end

.success:
    mov edx, len1
    mov ecx, msg1
    call printk
    jmp .end
.end:
   leave

   mov     ebx,0               ;first syscall argument: exit code
   mov     eax,1               ;system call number (sys_exit)
   int     0x80                ;call kernel

   ret

   
; EDX: message length
; ECX: pointer to message to write
printk:
    pusha
    mov     ebx,1               ;first argument: file handle (stdout)
    mov     eax,4               ;system call number (sys_write)
    int     0x80                ;call kernel
    popa
    ret

【讨论】:

  • 移植到 x86-64 不仅仅需要更改寄存器名称和电话号码。正如 Jester 的回答所说,syscall 64 位系统调用约定使用与int 0x80 32 位调用不同的 arg 寄存器。 (What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?)。此外,任何涉及指针或long 的结构布局可能会有所不同。
  • push 在 64 位模式下只能压入 2 或 8 个字节,而不是 4 个字节,因此您无法在 64 位版本中以完全相同的方式在堆栈上构建结构。 (不幸的是,NASM 允许push dword 毫无怨言,我想即使没有strict dword 也将其视为立即编码的建议,但该指令仍然是一个推送qword。)
【解决方案2】:

socket.asm

;插座

;编译:nasm -f elf socket.asm

;链接(64位系统需要elf_i386选项):ld -m elf_i386 socket.o -o socket

;运行:./socket

%include 'functions.asm'

部分 .text 全局_start

_开始:

xor     eax, eax            ; init eax 0
xor     ebx, ebx            ; init ebx 0
xor     edi, edi            ; init edi 0
xor     esi, esi            ; init esi 0

_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     ecx, esp            ; move address of arguments into ecx
mov     ebx, 1              ; invoke subroutine SOCKET (1)
mov     eax, 102            ; invoke SYS_SOCKETCALL (kernel opcode 102)
int     80h                 ; call the kernel

call    iprintLF            ; call our integer printing function (print the file descriptor in EAX or -1 on error)

_退出:

call    quit                ; call our quit function

more docs...

【讨论】:

    【解决方案3】:

    64 位调用约定确实使用寄存器在用户空间和系统调用中传递参数。如您所见,用户空间约定是rdirsirdxrcxr8r9。对于系统调用,使用r10 代替被系统调用指令破坏的rcx。有关详细信息,请参阅 wikipedia 或 ABI 文档。

    各种常量的定义隐藏在头文件中,但如果您安装了必要的开发包,则可以通过文件系统搜索轻松找到。您应该查看/usr/include/x86_64-linux-gnu/bits/socket.h/usr/include/linux/in.h

    至于系统调用列表,google一下很简单,比如this。当然,您也可以随时查看内核源代码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-01-20
      • 1970-01-01
      • 2013-12-19
      • 1970-01-01
      • 2012-11-20
      • 2018-11-13
      相关资源
      最近更新 更多