Linux2.6.32驱动笔记(3)分析应用程序read访问驱动过程

摘要: 利用objdump反汇编编译好的应用程序,分析了应用程序程序中调用read时时候,如何一层层的找到驱动中我们自己实现的对应的read方法。


一、反汇编编译好的应用程序

之前有一个读的应用程序read-mem.c,具体内容如下:

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

 

int main()

{

    intfd = 0;

    intdst = 0;

   

    /*打开设备文件*/

    fd= open("/dev/memdev0",O_RDWR);

   

    /*写入数据*/

    read(fd,&dst, sizeof(int));

   

    printf("dstis %d\n",dst);

   

    /*关闭设备*/

    close(fd);

   

    return0; 

 

}

然后我们将其编译,在编译时候加上-g选项以及静态链接库。

#arm-linux-gcc –static –gread-mem.c –o read-mem

之后再对其进行反汇编,将结果输出到叫dump文件里看具体做了哪些事:

#arm-linux-objdump –D –S read-mem>dump


二、阅读dump文件

因为整个反汇编文件还是挺多的,我们只需要找到main函数里面的read就可以了,在第119行:

/*写入数据*/

    read(fd, &dst, sizeof(int));

    8258:  e24b300c  sub r3,fp, #12   ; 0xc

    825c:  e51b0008  ldr r0,[fp, #-8]

    8260:  e1a01003  mov r1,r3

    8264:  e3a02004  mov r2,#4 ; 0x4

    8268:  eb0028e4  bl  12600<__libc_read>

    这里汇编一行一行的去理解比较费时费劲,自己尝试了下,首先就要了解fp寄存器已经程序的堆栈问题,这里前面几行代码就是把传进来的三个参数做一个保存,最后一句是终点,跳到__libc_read里面去了,我们切过去看下,__libc_read,在第10888行,真是长啊,还在文件的上半部分。。。看来静态链接确实会大大的增加文件的大小。

    由于__lib_read也比较长,前面部分主要是对当前状态的一个“保护”,r1和r7寄存器的一个堆栈操作,防止调用svc之后,回不来了,但是主要关注第10904开始这两行:

  1263c: e3a07003   mov r7,#3 ; 0x3

  12640:  ef000000   svc 0x00000000

    第一行代码,自己在之前系统调用那个帖子里已经分析过了,其实是把系统调用号3传给了arm的r7寄存器,然后调用svc这个指令,svc是一个系统调用指令,执行之后我们的pc指针就会从用户态,切到内核态来执行,到内核态的这个入口是固定的。到了内核态之后,内核就回去取r7当中的这个系统调用号。这里开始就是上一次我们分析的系统调用了,具体步骤再说一遍。


三、内核如何获取系统调号找到对应的系统调用

主要是以下两个文件实现:

1.entry-common.S

    当用户空间进行系统调用,产生“swi”中断,这时候entry-common.S当中,在第187行:

    ENTRY(vector_swi)

    sub sp, sp, #S_FRAME_SIZE

    stmia  sp, {r0 - r12}           @Calling r0 - r12

    ARM(   add r8, sp, #S_PC     )

    ARM(   stmdb  r8, {sp, lr}^     )   @ Calling sp, lr

    THUMB( mov r8, sp        )

    THUMB( store_user_sp_lrr8, r10, S_SP  )   @ calling sp, lr

    mrs r8, spsr          @called from non-FIQ mode, so ok.

    str lr, [sp, #S_PC]          @Save calling PC

    str r8, [sp, #S_PSR]     @ Save CPSR

    str r0, [sp, #S_OLD_R0]      @ Save OLD_R0

    zero_fp   

    会对swi进行响应,因为响应之后,我们要切换到内核去执行系统调用,所以这里先对当前用户态执行的一些寄存器和状态进行堆栈操作,保存r0到r12的值,保存PC,CPSR,和OLD_R0。然后下面从第226行到第249行,会根据不同的情况进行具体的操作,我们这里是从r7当中获取系统调用号:

    /*

     * Pure EABI user space always put syscall number into scno(r7).

     */

  A710(    ldr ip, [lr, #-4]        @get SWI instruction    )

  A710(    and ip, ip, #0x0f000000      @ check for SWI      )

  A710(    teq ip, #0x0f000000                    )

  A710(    bne .Larm710bug                     )

 

#elifdefined(CONFIG_ARM_THUMB)

 

    /* Legacy ABI only, possibly thumb mode. */

    tst r8,#PSR_T_BIT           @ this is SPSR fromsave_user_regs

    addne  scno, r7, #__NR_SYSCALL_BASE    @ put OS number in

    ldreq  scno,[lr, #-4]

 

#else

 

    /* Legacy ABI only. */

    ldr scno,[lr, #-4]          @ get SWI instruction

  A710(    and ip, scno, #0x0f000000       @ check for SWI      )

  A710(    teq ip, #0x0f000000                    )

  A710(    bne .Larm710bug                     )

 

#endif

 

    然后在259行,载入了我们的系统调用表:

    adr tbl, sys_call_table      @ load syscall table pointer

    这里sys_call_table在第470行:

        .type  sys_oabi_call_table, #object

    ENTRY(sys_oabi_call_table)

    #include "calls.S"

    这时候指向了我们的calls.S文件。


   2.calls.S

   是由各个系统调用函数函数指针组成的表格。

    /*0 */       CALL(sys_restart_syscall)

       CALL(sys_exit)

       CALL(sys_fork_wrapper)

       CALL(sys_read)

       CALL(sys_write)

    /* 5*/       CALL(sys_open)

       CALL(sys_close)

    .........

    /*355 */  CALL(sys_signalfd4)

       CALL(sys_eventfd2)

       CALL(sys_epoll_create1)

       CALL(sys_dup3)

       CALL(sys_pipe2)

    /* 360*/  CALL(sys_inotify_init1)

       CALL(sys_preadv)

       CALL(sys_pwritev)

       CALL(sys_rt_tgsigqueueinfo)

       CALL(sys_perf_event_open)

    这里面都是系统调用函数,我们有了调用号作为偏移地址,有了表格的基地址,那么就可以找到对应的系统调用。


    3.unist.h

    这里面也包含了各个系统调用的系统调用号,和calls.S是一致的。

    #define__NR_restart_syscall       (__NR_SYSCALL_BASE+  0)

    #define__NR_exit        (__NR_SYSCALL_BASE+  1)

    #define__NR_fork        (__NR_SYSCALL_BASE+  2)

    #define__NR_read        (__NR_SYSCALL_BASE+  3)

    .. . . . .

    #define__NR_preadv         (__NR_SYSCALL_BASE+361)

    #define__NR_pwritev        (__NR_SYSCALL_BASE+362)

    #define__NR_rt_tgsigqueueinfo     (__NR_SYSCALL_BASE+363)

    #define__NR_perf_event_open       (__NR_SYSCALL_BASE+364)

   其实说白了这个方法是通过用户空间传进来的read对应的系统调用号,内核根据这个号进行查表,找到sys_read。


四、sys_read

    这个函数的原型在/fs/read_write.c里面,在第372行:

SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t,count)

{

    structfile *file;

    ssize_tret = -EBADF;

    intfput_needed;

 

    file= fget_light(fd, &fput_needed);

    if(file) {

       loff_tpos = file_pos_read(file);

       ret = vfs_read(file, buf, count, &pos);

       file_pos_write(file,pos);

       fput_light(file,fput_needed);

    }

 

    returnret;

}

    这里名称变掉了,但是我们理解还是sys_read其实在老版本的linux内核里面,确实还是sys_read,但是由于在09年,随着大批量的64位处理器的出现,很多用户在调用的时候,无法填充64位的系统调用,就会被黑客利用,导致系统奔溃和权限升级,所以linux大牛们相处了一套通用的方法,开发出了这一套宏来避免这个bug,这个宏会对参数和系统调用名进行展开解析,从而变成我们需要的sys_read,具体如何做到的,网上有帖子,可以去参看。这里着重关心红色字体ret = vfs_read(file, buf, count, &pos);也就是又调用了一个vfs_read,切过去看。

   

五、vfs_read

    还是在/fs/read-write.c里面,第277行:

    ssize_t vfs_read(struct file *file, char __user *buf, size_tcount, loff_t *pos)

{

    ssize_tret;

 

    if(!(file->f_mode & FMODE_READ))

       return-EBADF;

    if(!file->f_op || (!file->f_op->read &&!file->f_op->aio_read))

       return-EINVAL;

    if(unlikely(!access_ok(VERIFY_WRITE, buf, count)))

       return-EFAULT;

 

    ret= rw_verify_area(READ, file, pos, count);

    if(ret >= 0) {

       count= ret;

       if (file->f_op->read)

           ret= file->f_op->read(file, buf, count, pos);

       else

           ret= do_sync_read(file, buf, count, pos);

       if(ret > 0) {

           fsnotify_access(file->f_path.dentry);

           add_rchar(current,ret);

       }

       inc_syscr(current);

    }

 

    return ret;

}

    首先看函数参数,和我们的read是一致的,然后下面一行红字,判断file结构体里面对应的file_operations里面我们驱动当中的read是否存在,存在的的话就

ret = file->f_op->read(file, buf, count, pos);

    调用我们驱动里面实现的read方法,然后一层层的返回过来,给ret,整个这个函数的返回值就用这个ret作为返回值。


六、总结

    应用程序read->调用__libc_read->把调用号给r7,产生swi中断,执行svc指令切换到内核->内核读走调用号,与系统调用号表格查找对比找出是哪个系统调用(entry-common.S和calls.S)->调用SYSCALL_DEFINE3及sys_read->调用vfs_read->vfs_read调用与file绑定的file_operations里面的具体的我们实现的驱动的read->返回值给用户空间,结束!

    这篇帖子就总结到这里,如有不正确的地方,还请指出,大家共同进步!

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值