zoukankan      html  css  js  c++  java
  • Socket与系统调用深度分析(基于5.0.1/32,其实系统调用并不是int80,而是VDSO,另一种快速的系统调用方式

    Socket与系统调用深度分析

    em ,理论是INT80->entry_INT8_32,但实际调试最后才发现并不是的,所以我的过程中有很多的疑问,到最后我才发现。因此本博文实验过程可能有些奇怪,但如果你想知道5.0.1的系统调用方式到底是什么,请你继续观看

    系统linux-5.0.1 32位
    为加快大家查看源码的调用关系 提供 https://elixir.bootlin.com/linux/v5.0.1/source/net/ipv4/tcp_ipv4.c#L202
    以下调试都是基于下图的理解进行的,针对图中1,2两个点,博文主要解决四个问题

    1 int 0x80中断向量是如何与中断向量表绑定的?
    2 socketAPI是如何进入内核调用socket 接口的?
    3 socket接口是如何与传输层协议绑定的?
    4 sokcet接口是如何调用具体协议的接口的?
    

    Linux 引导过程综述

    BIOS->Bootloader->内核初始化:体系结构相关部分-><内核初始化:体系结构无关部分>
    

    内核初始化:体系结构相关部分

    1 内核映像结构
    2 初始化与保护模式 
    3 自解压内核 
    <4 startup_32(head_32.c)>
    

    startup_32(head_32.c)

    1 初始化参数(设置段的值,清楚BSS,初始化栈)
    2 开启分页机制
    3 初始化 Eflags
    4 检查处理器类型
    5 载入 GDT、IDT
    <6 i386_start_kernel>
    

    i386_start_kernel 执行与体系结构无关部分的内核初始化

    1 检查中断向量表(IDT)是否已经启动,em,IDT要被初始化第一次
    <2 调用start_kernel执行与体系结构无关部分的内核初始化>
    

    start_kernel

    使用"arch/alpha/kernel/entry.S"中的入口点设置系统自陷入口(trap_init())IDT初始化第二次
    

    以上过程只是简单分析从linux启动到终端向量表的初始化的过程,下面我们来看IDT第二次初始化的细节。

    中断向量int0x80是如何与中断处理例程绑定?

    中断向量表的初始化分有三次:
    (1)setup_once: early_idt_handler_array //./arch/x86/kernel/head_32.S line 377
    (2)对0~19号的一些和0x80号系统保留中断向量的初始化,在trap_init中完成
    (3)对其它中断向量的初始化,在init_IRQ中完成

    第二次初始化中断向量表,绑定0x80和SYS_INT80_32

    先给出答案:startup_32_smp-->i386_start_kernel -->start_kernel --> trap_init --> idt_setup_traps-->idt_setup_from_table
    
     gdb bt 调试结果如下:
     #0  <idt_setup_from_table> (t=0xc1d99b10 <def_idts+16>, size=<optimized out>, sys=true,
         idt=<optimized out>) at arch/x86/kernel/idt.c:225
     #1  0xc1d291d9 in <idt_setup_traps> () at arch/x86/kernel/idt.c:267
     #2  0xc1d29155 in <trap_init ()> at arch/x86/kernel/traps.c:934
     #3  0xc1d23a5b in <start_kernel> () at init/main.c:595
     #4  0xc1d2327c in <i386_start_kernel> () at arch/x86/kernel/head32.c:56
     #5  0xc10001ec in <startup_32_smp> () at arch/x86/kernel/head_32.S:363
     #6  0x00000000 in ?? ()
     
    

    下面我们来具体分析一下。

    idt_setup_traps

    void __init idt_setup_traps(void)
    {
        idt_setup_from_table(idt_table, def_idts, ARRAY_SIZE(def_idts), true);
    }
    

    先不管idt_setup_from_table的作用,先来看看结构体数组def_idts的最后一行SYSG(IA32_SYSCALL_VECTOR, entry_INT80_32),SYSG是一个宏,代表系统中断门,作用就是将中断向量IA32_SYSCALL_VECTOR和中断处理例程entry_INT80_32绑定,相信你现在已经明白idt_setup_from_table函数的作用了,就是在填一个table,包括中断向量号及其处理程序。

    #define IA32_SYSCALL_VECTOR		0x80 // ./arch/x86/include/asm/irq_vectors.h line 45
    

    def_idts

    static const __initconst struct idt_data def_idts[] = {
        ...
        SYSG(IA32_SYSCALL_VECTOR,   entry_INT80_32)  /*int 0x80*/
    };
    

    idt_setup_from_table

    idt_setup_from_table(gate_desc *idt, const struct idt_data *t, int size, bool sys)
    {
    	gate_desc desc;
    	for (; size > 0; t++, size--) {
    		idt_init_desc(&desc, t);//初始化描述符对应的门:门类型有任务门、中断门、陷阱门和调用门
    		write_idt_entry(idt, t->vector, &desc);//将对应的门和中断向量写入中断描述符表(IDT)
    		if (sys)
    			set_bit(t->vector, system_vectors);//系统设置了一个位图system_vectors,来表示每个中断向量表的使用情况,可以看到,这里是将size个向量表项对应的位图设置为1,表示已经被占用了。
    	}
    }
    //1 中断向量表的每个表项叫做一个门描述符(gate descriptor),“门”的含义是当中断发生时必须先通过这些门,然后才能进入相应的处理程序。
    

    关于门的描述参考:

    https://www.cnblogs.com/qintangtao/p/3325985.html

    https://blog.csdn.net/cwcmcw/article/details/21640363

    如果你想看write_idt_entry,如下,就是一个简单的内存拷贝。

    static inline void native_write_ldt_entry(struct desc_struct *ldt, int entry, const void *desc)
    {
    	memcpy(&ldt[entry], desc, 8);
    }
    

    到此我们已经理清楚了中断向量int80绑定了entry_INT80_32,下面我们看第二个问题。

    系统调用如何关联系统调用号,系统调用号如何绑定socket接口。

    直接查看:linux-5.0.1/arch/x86/entry/syscalls/syscall_32.tbl,以下列出socket相关的系统调用接口,以及对应的系统调用号和内核的socket接口。

    102 i386    socketcall      sys_socketcall          __ia32_compat_sys_socketcall
    
    359 i386    socket          sys_socket          __ia32_sys_socket
    360 i386    socketpair      sys_socketpair          __ia32_sys_socketpair
    361 i386    bind            sys_bind            __ia32_sys_bind
    362 i386    connect         sys_connect         __ia32_sys_connect
    363 i386    listen          sys_listen          __ia32_sys_listen
    364 i386    accept4         sys_accept4         __ia32_sys_accept4
    365 i386    getsockopt      sys_getsockopt          __ia32_compat_sys_getsockopt
    366 i386    setsockopt      sys_setsockopt          __ia32_compat_sys_setsockopt
    367 i386    getsockname     sys_getsockname         __ia32_sys_getsockname
    368 i386    getpeername     sys_getpeername         __ia32_sys_getpeername
    369 i386    sendto          sys_sendto          __ia32_sys_sendto
    370 i386    sendmsg         sys_sendmsg         __ia32_compat_sys_sendmsg
    371 i386    recvfrom        sys_recvfrom            __ia32_compat_sys_recvfrom
    372 i386    recvmsg         sys_recvmsg         __ia32_compat_sys_recvmsg
    
    

    两种调用方式,一种是以系统调用号102 socketcall 进入sys_socketcall,然后分支进入sys_socket,sys_bind 等,另一种是直接通过自身的系统调用号比如359(socket)找到sys_socket。

    我们可以通过调试来查看到底系统是使用哪一种?

    用户态调试

    先说明一下:由于我的内核无法完成升级,搞了两次暂时没有成功,所以以内核 4.0.5版本的linux系统在用户态调试下调试,如果以后升级成功了再来验证5.0.1.

    源代码

    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <netdb.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
    int main()
    {
        int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
        return 0;
    }
    

    静态编译

    gcc  -g -o static  test.c  -static  -m32
    gdb ./static
    
    

    //反汇编 main 和 socket
    disassemble main

    disassemble 0x806e790

    用户态系统调用过程:main->socked->mov $0x66 $eax->call *%gs:0x10
    库函数socket将eax寄存器设置为socketcall系统调用的调用号0x66,然后调用%gs:0x10所指向的函数。在gdb中,无法查看非DS段的数据内容,所以无法查看%gs:0x10所保存的实际数值,但它对应一个函数地址,而这个地址就是内核为我们映射的系统调用入口代码,这个函数地址里面应该包含了int 0x80。

    mov $0x66 $eax  这个语句相当重要,0x66恰好是十进制的102,保存到了eax寄存器里面了,对用socketcall的系统调用编号,然后陷入内核执行系统调用。
    

    由于知识水平有限,在用户态我也无法跟踪下去找到int 0x80指令的执行,当然我有想到反汇编static文件,然后查看out.txtx文件的结果,是否有int 0x80指令的调用

    objdump -d static > out.txt
    

    确实有,在如下几个文件里面都找到了int 0x80的踪影:

    __libc_setup_tls>:
     _exit
    _tunables_init
    _dl_sysinfo_int80
    _restore_rt
     _restore  
    __brk
    

    看以上几个函数名,我猜是_dl_sysinfo_int80 调用INT80指令进入内核态的,但我又没有证据,所以用户态下只能暂时告一段落。

    用户态看不到,我们去内核态下看吧,注意注意,我的内核态切回5.0.1了。

    内核态调试,基于linux-5.0.1

    1 在lab3 目录下启动menu终端:

    qemu -kernel ../../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img -append  nokaslr -s
    

    2 在另一个终端输入

    gdb
    file vmlinux
    break __sys_socket
    target remote:1234
    c
    

    3 在menu系统中输入replyhi,另一个终端自然进入了内核的断点。

    如图

    为什么我输入了两次replyhi呢?因为我第一次的断点打在sys_socket处,并没有停下,所以5.0.1系统中的socket系统调用并不是按照第二种方式,而是按照sys_socketcall分发的方式,这儿的细节我已经在上一篇博客最后写过了。
    堆栈情况如下:

    (gdb) bt
    #0  <__sys_socket >(family=2, type=1, protocol=0) at net/socket.c:1327
    #1  0xc1757b98 in __do_sys_socketcall (args=<optimized out>,
        call=<optimized out>) at net/socket.c:2555
    #2  <__se_sys_socketcall> (call=1, args=-1077721504) at net/socket.c:2527
    #3  0xc1002095 in <do_syscall_32_irqs_on> (regs=<optimized out>)
        at arch/x86/entry/common.c:334
    #4  <do_fast_syscall_32> (regs=0xc7191fb4) at arch/x86/entry/common.c:397
    #5  0xc199141b in <entry_SYSENTER_32> () at arch/x86/entry/entry_32.S:887
    #6  0x00000001 in ?? ()
    #7  0xbfc34660 in ?? ()
    

    现在我们来分析一下:enter_sysenter_32是如何通过系统调用号来找到__sys_socketcall的。你有发现点什么么?为什么进入内核的不是entry_INT80_32 ? int 0x80不是对应它吗?为什么是enter_SYSENTER_32,先不管我们就按照enter_SYSENTER_32分析下去。

    enter_SYSENTER_32中断处理例程到底发生了什么

    其中涉及:entry_SYSENTER_32 -> do_fast_syscall_32 -> do_syscall_32_irqs_on -> __do_sys_socketcall-> sys_socket,下面我们一个一个看

    entry_SYSENTER_32

    ENTRY(entry_SYSENTER_32) 截取重要部分
        movl    TSS_entry2task_stack(%esp), %esp //保存当进程内核栈
    .Lsysenter_past_esp:  //保存当前的一些重要寄存器到结构体 pt_regs中
        pushl   $__USER_DS      /* pt_regs->ss */
        pushl   %ebp            /* pt_regs->sp (stashed in bp) */
        pushfl              /* pt_regs->flags (except IF = 0) */
        orl $X86_EFLAGS_IF, (%esp)  /* Fix IF */
        pushl   $__USER_CS      /* pt_regs->cs */
        pushl   $0          /* pt_regs->ip = 0 (placeholder) */
        pushl   %eax     //压栈保存eax!!!还记得我们在用户态下保存的系统调用号吗?5.0.1下应该是102
        SAVE_ALL pt_regs_ax=$-ENOSYS    /* 保存其他寄存器保在 pt_regs 结构中 */
        movl    %esp, %eax
        call    do_fast_syscall_32
    

    在内核启动时,其中会有一个软中断的陷入门,当接收到一个系统调用的时候, 相应的文件就会被调用,然后通过 push 和 SAVE_ALL 将当前用户态的寄存器,保存在 pt_regs 结构中,而结构体pt_regs的内容将在do_syscall_32_irqs_on里面取值。

    调试验证,如下图,此时的eax正好是102

    do_fast_syscall_32
    
    
    __visible long do_fast_syscall_32(struct pt_regs *regs)
    {
        /*
         * Called using the internal vDSO SYSENTER/SYSCALL32 calling
         * convention.  Adjust regs so it looks like we entered using int80.
         */
    
        unsigned long landing_pad = (unsigned long)current->mm->context.vdso +
            vdso_image_32.sym_int80_landing_pad;
        regs->ip = landing_pad;
        enter_from_user_mode();//进入用户态
        local_irq_enable();//
        if (
    #ifdef CONFIG_X86_64
            __get_user(*(u32 *)&regs->bp,
                    (u32 __user __force *)(unsigned long)(u32)regs->sp)
    #else
            get_user(*(u32 *)&regs->bp,
                 (u32 __user __force *)(unsigned long)(u32)regs->sp)
    #endif
            ) {
    
            /* User code screwed up. */
            local_irq_disable();
            regs->ax = -EFAULT;
            prepare_exit_to_usermode(regs);//推出用户态
            return 0;
    
    

    Called using the internal vDSO SYSENTER/SYSCALL32 calling convention. Adjust regs so it looks like we entered using int80.

    这句话特别有意思,使其看起来像我们使用了INT80进入内核!!!!!!!!!事实证明它确实不是通过:entry_INT80_32->do_fast_INT80_32->do_syscall_32_irqs_on->__do_sys_socketcall-> sys_socket,有一种挂羊头卖狗肉的感觉,感觉被骗了

    实际上linux为了减少系统调用的开销,采取了一种比通过0x80->entry_INT80_32更快的方式,仿照entry_INT80_32,即通过vDSO SYSENTER/SYSCALL32,具体请参考: http://blog.chinaunix.net/uid-27717694-id-4233173.html ,但是二者确实很类似: 可以通过网站 https://elixir.bootlin.com/linux/v5.0.1/source/net/ipv4/tcp_ipv4.c#L202 搜索entry_INT80_32的实现,和entry_SYSENTER_32是差不多的。

    do_syscall_32_irqs_on

    static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs)
    {
        struct thread_info *ti = current_thread_info();
        unsigned int nr = (unsigned int)regs->orig_ax;  //取系统调用号102
        nr = array_index_nospec(nr, IA32_NR_syscalls);//检查nr是否越界
        regs->ax = ia32_sys_call_table[nr](regs);//取对调用号对应的函数地址和参数
        }
    

    regs的定义

    struct pt_regs {
    //重点是:orig_ax
    /*
     * C ABI says these regs are callee-preserved. They aren't saved on kernel entry
     * unless syscall needs a complete, fully filled "struct pt_regs".
     */
        unsigned long r15;
        unsigned long r14;
        unsigned long r13;
        unsigned long r12;
        unsigned long bp;
        unsigned long bx;
    /* These regs are callee-clobbered. Always saved on kernel entry. */
        unsigned long r11;
        unsigned long r10;
        unsigned long r9;
        unsigned long r8;
        unsigned long ax;
        unsigned long cx;
        unsigned long dx;
        unsigned long si;
        unsigned long di;
    /*
     * On syscall entry, this is syscall#. On CPU exception, this is error code.
     * On hw interrupt, it's IRQ number:
     */
        unsigned long orig_ax;
    /* Return frame for iretq */
        unsigned long ip;
        unsigned long cs;
        unsigned long flags;
        unsigned long sp;
        unsigned long ss;
    /* top of stack page */
    };
    
    

    调试验证,hh了,在编译内核的时候应该禁止编译优化的

    虽然我看不到,但是从我们之前的程序来看,regs->orig_ax就是102,从而调用了sys_socketcall.

    sys_socketcall

    switch (call) {
       case SYS_SOCKET:
           err = __sys_socket(a0, a1, a[2]);
           break;
       case SYS_BIND:
           err = __sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
           break;
       case SYS_CONNECT:
           err = __sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
           break;
       case SYS_LISTEN:
           err = __sys_listen(a0, a1);
           break;
       case SYS_ACCEPT:
           err = __sys_accept4(a0, (struct sockaddr __user *)a1,
                       (int __user *)a[2], 0);
           break;
       case SYS_GETSOCKNAME:
           err =
               __sys_getsockname(a0, (struct sockaddr __user *)a1,
                         (int __user *)a[2]);
           break;
       case SYS_GETPEERNAME:
    ...
    

    通过socket的总接口sys_socketcall进入socket

    __sys_socket

    int __sys_socket(int family, int type, int protocol)
    {
        int retval;
        struct socket *sock;
        int flags;
    
        flags = type & ~SOCK_TYPE_MASK;
        if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
            return -EINVAL;
        type &= SOCK_TYPE_MASK;
        if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
            flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
    
        retval = sock_create(family, type, protocol, &sock);
        if (retval < 0)
            return retval;
        return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
    }
    
    SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
    {
        return __sys_socket(family, type, protocol);
    }
    

    到此我们的前面两个问题已经解决了。

    socket接口是如何与传输层协议绑定的?

    由于socket接口对应传输层的协议,包括: TCP,UDP,PING RAW等,那么一个socket接口是如何与这些协议绑定的呢?即linux内核是如何初始化的传输层协议的。

    lab3目录下输入调式

    qemu -kernel ../../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img -append  nokaslr -s -S
    

    另一个终端

    gdb
    file vmlinux
    break inet_init
    target remote:1234
    c
    bt  //查看断点的堆栈情况
    
    
    #0  inet_init () at net/ipv4/af_inet.c:1900
    #1  0xc1000a2d in do_one_initcall (fn=0xc1d6cea6 <inet_init>)
        at init/main.c:887
    #2  0xc1d23da2 in do_initcall_level (level=<optimized out>) at init/main.c:955
    #3  do_initcalls () at init/main.c:963
    #4  do_basic_setup () at init/main.c:981
    #5  kernel_init_freeable () at init/main.c:1136
    #6  0xc198af98 in kernel_init (unused=<optimized out>) at init/main.c:1054
    #7  0xc1991386 in ret_from_fork () at arch/x86/entry/entry_32.S:722
    

    如上TCP/IP协议的初始化是从Linux内核初始化过程中加载TCP/IP协议栈的,从start_kernel、kernel_init、do_initcalls、inet_init,中间过程过

    inet_init

    static int __init inet_init(void)
    {
    	struct inet_protosw *q;
    	struct list_head *r;
    	int rc = -EINVAL;
    	sock_skb_cb_check_size(sizeof(struct inet_skb_parm));
    	rc = proto_register(&tcp_prot, 1);  //注册tcp
    	if (rc)
    		goto out;
    	rc = proto_register(&udp_prot, 1);//注册udp
    	if (rc)
    		goto out_unregister_tcp_proto;
    	rc = proto_register(&raw_prot, 1); //注册raw
    	if (rc)
    		goto out_unregister_udp_proto;
    	rc = proto_register(&ping_prot, 1);//注册ping
    	if (rc)
    		goto out_unregister_raw_proto;
    	/*
    	 *	Tell SOCKET that we are alive...
    	 */
    	(void)sock_register(&inet_family_ops);
    	}
    

    我们可能到注册不同的协议是通过结构体 struct inet_protosw来实现的,我们来看一下结构体的内容,大部分主要内容都是函数指针对应函数指针,即通过结构体包含协议类型协议的处理函数对应,将socket接口和不同的协议关联起来了,所以我们写应用程序socket时必须要指明协议族类型比如 tcp对应AF_INET。

    struct proto tcp_prot = {
    	.name			= "TCP",              //重要  类型
    	.owner			= THIS_MODULE,
    	.close			= tcp_close,          //函数close->tcp_close函数
    	.pre_connect		= tcp_v4_pre_connect,
    	.connect		= tcp_v4_connect,    //函数connect->tcp_v4_connect函数
    	.disconnect		= tcp_disconnect,
    	.accept			= inet_csk_accept,   //accept
    	.ioctl			= tcp_ioctl,
    	.init			= tcp_v4_init_sock,
    	.destroy		= tcp_v4_destroy_sock,
    	.shutdown		= tcp_shutdown,
    	.setsockopt		= tcp_setsockopt,
    	.getsockopt		= tcp_getsockopt,
    	.keepalive		= tcp_set_keepalive,
    	.recvmsg		= tcp_recvmsg,     //recvmsg
    	.sendmsg		= tcp_sendmsg,     //sendmsg
    	.sendpage		= tcp_sendpage,
    	.backlog_rcv		= tcp_v4_do_rcv,
    	.release_cb		= tcp_release_cb,
    	.hash			= inet_hash,
    	.unhash			= inet_unhash,
    	.get_port		= inet_csk_get_port,
    	.enter_memory_pressure	= tcp_enter_memory_pressure,
    	.leave_memory_pressure	= tcp_leave_memory_pressure,
    	.stream_memory_free	= tcp_stream_memory_free,
    	.sockets_allocated	= &tcp_sockets_allocated,
    	.orphan_count		= &tcp_orphan_count,
    	.memory_allocated	= &tcp_memory_allocated,
    	.memory_pressure	= &tcp_memory_pressure,
    	.sysctl_mem		= sysctl_tcp_mem,
    	.sysctl_wmem_offset	= offsetof(struct net, ipv4.sysctl_tcp_wmem),
    	.sysctl_rmem_offset	= offsetof(struct net, ipv4.sysctl_tcp_rmem),
    	.max_header		= MAX_TCP_HEADER,
    	.obj_size		= sizeof(struct tcp_sock),
    	.slab_flags		= SLAB_TYPESAFE_BY_RCU,
    	.twsk_prot		= &tcp_timewait_sock_ops,
    	.rsk_prot		= &tcp_request_sock_ops,
    	.h.hashinfo		= &tcp_hashinfo,
    	.no_autobind		= true,
    #ifdef CONFIG_COMPAT
    	.compat_setsockopt	= compat_tcp_setsockopt,
    	.compat_getsockopt	= compat_tcp_getsockopt,
    #endif
    	.diag_destroy		= tcp_abort,
    };
    

    好了,到此传输层协议的初始化过程就结束了。

    sokcet接口是如何调用具体协议的接口的?

    相信看了上面的传输层协议初始化,这儿你就已经清楚了,是通过应用层接口的参数指定了传输层的协议,再通过结构体的绑定,从而调用相应协议的函数接口。

    下面只是调试验证一下,由于我们写的协议是AF_INET,所以我的断点就直接打在tcp的tcp_v4_connect函数,看一看是否和我们想象的一样,接着前面我们分析到 __sys_connect,看看它是否是从 __sys_connect ->tcp_v4_connect。

    (gdb) bt
    #0  <tcp_v4_connect> (sk=0xc71b06a0, uaddr=0xc7895ec4, addr_len=16)
        at net/ipv4/tcp_ipv4.c:203
    #1  0xc18151a1 in __inet_stream_connect (sock=0xc77a04e0,
        uaddr=<optimized out>, addr_len=<optimized out>, flags=2, is_sendmsg=0)
        at net/ipv4/af_inet.c:655
    #2  0xc18152c6 in inet_stream_connect (sock=0xc77a04e0, uaddr=0xc7895ec4,
        addr_len=16, flags=2) at net/ipv4/af_inet.c:719
    #3  0xc1756f44 in <__sys_connect> (fd=<optimized out>,
        uservaddr=<optimized out>, addrlen=16) at net/socket.c:1663
    #4  0xc1757b78 in __do_sys_socketcall (args=<optimized out>,
        call=<optimized out>) at net/socket.c:2561
    #5  __se_sys_socketcall (call=3, args=-1076065920) at net/socket.c:2527
    #6  0xc1002095 in do_syscall_32_irqs_on (regs=<optimized out>)
        at arch/x86/entry/common.c:334
    #7  do_fast_syscall_32 (regs=0xc7895fb4) at arch/x86/entry/common.c:397
    #8  0xc199141b in entry_SYSENTER_32 () at arch/x86/entry/entry_32.S:887
    #9  0x00000003 in ?? ()
    #10 0x00000000 in ?? ()
    

    从堆栈情况来看,完全一致,关于tcp_v4_connect分析参考 https://blog.csdn.net/wangpengqi/article/details/9472699

    到此终于分析结束了,虽然中间分析有很多小波澜,尤其系统调用方式不是INT80,这个过程让我很矛盾,但总归结束了。

    致谢以下博客及文中提到的博客
    https://blog.csdn.net/sunnybeike/article/details/6958473
    https://blog.csdn.net/yin262/article/details/53928178
    https://blog.csdn.net/qyanqing/article/details/8039343
    https://cloud.tencent.com/developer/article/1492374

  • 相关阅读:
    racle SQL性能优化
    Oracle 删除重复数据只留一条
    oracle存储过程常用技巧
    详解:数据库名、实例名、ORACLE_SID、数据库域名、全局数据库名、服务名及手工脚本创建oracle数据库
    用友ERP-U8最新破解(再次更新版本,附安装过程中的解决办法)
    轻松三步教你配置Oracle—windows环境
    非常好的Oracle教程【转】
    Oracle新表使用序列(sequence)作为插入值,初始值不是第一个,oraclesequence
    大数据学习资源汇总
    Index
  • 原文地址:https://www.cnblogs.com/Alexkk/p/12049969.html
Copyright © 2011-2022 走看看