• 从linux0.11中起动部分代码看汇编调用c语言函数


    上一篇分析了c语言的函数调用栈情况,知道了c语言的函数调用机制后,我们来看一下,linux0.11中起动部分的代码是如何从汇编跳入c语言函数的。在LINUX 0.11中的head.s文件中会看到如下一段代码(linux0.11的启动分析部分会在另一部分中再分析,由于此文仅涉及c与汇编代码的问题,)。

    after_page_tables:
        pushl $0        # These are the parameters to main :-)
        pushl $0
        pushl $0
        pushl $L6        # return address for main, if it decides to.
        pushl $main
        jmp setup_paging

    这段代码执行后程序的执行流程会跳转到main函数中运行(init/main.c),通过上一篇文件的c语言的汇编代码我们可以知道,main.c函数经过汇编后会生成main符号(汇编语言),而且是全局可见的。所以这里的pushl $main就是将main符号(函数)的地址压入栈中,然后在跳到setup_paging去执行,进行内存的全局页目录的处理,为进入保护模式做最后的准备,代码如下。

    setup_paging:
        movl $1024*5,%ecx        /* 5 pages - pg_dir+4 page tables */
        xorl %eax,%eax
        xorl %edi,%edi            /* pg_dir is at 0x000 */
        cld;rep;stosl
        movl $pg0+7,pg_dir        /* set present bit/user r/w */
        movl $pg1+7,pg_dir+4        /*  --------- " " --------- */
        movl $pg2+7,pg_dir+8        /*  --------- " " --------- */
        movl $pg3+7,pg_dir+12        /*  --------- " " --------- */
        movl $pg3+4092,%edi
        movl $0xfff007,%eax        /*  16Mb - 4096 + 7 (r/w user,p) */
        std
    1:    stosl            /* fill pages backwards - more efficient :-) */
        subl $0x1000,%eax
        jge 1b
        xorl %eax,%eax        /* pg_dir is at 0x0000 */
        movl %eax,%cr3        /* cr3 - page directory start */
        movl %cr0,%eax
        orl $0x80000000,%eax
        movl %eax,%cr0        /* set paging (PG) bit */
        ret            /* this also flushes prefetch-queue */

    这段代码中的ret语句会将上一段代码压入栈内的main符号(函数)地址弹出然后跳到main函数中执行。而main是c语言函数,在执行后会进行一系列的栈帧处理,如果执行完毕返回时会跳到L6符号地址去执行,但实际上main函数是不会返回的,如果真的返回则说明内核出错了,只能停机。

    现在我们来分析一下这几行的入栈操作

    1 pushl $0

    2 pushl $0

    3 pushl $0

    4 pushl $L6

    5 pushl $main

    根据《注释》一书的说法,行1-3是为main函数准备的参数,但实际上main函数并不需要参数,虽然这里的main函数对于汇编代码来说,仅仅是一个c语言的函数而已,早已不是c程序中的入口函数的概念了,而只是名称上叫main罢了。所以main在执行时并不需要参数,按照上一篇我们分析的结果看,这里不用pushl $0这三次入栈操作也应该是完全可以的。 但结果是不是这样呢?让我们先来做个实验。

    实验1、在linux下进行汇编调用c函数

    我们用汇编和c语言混合来写一个最简单的hello world程序。在汇编中直接调用c文件中的main函数。起初我们也模仿linux 0.11中那样加入三个入栈动作,看看结果。然后再把这三个入栈动作去掉(因为在c文件中main函数没有参数)。再次运行看看结果会怎么样。如果去掉这三行入栈操作也没有问题的话,我们基本可以断定linux 0.11中那三行入栈操作也是可以省略的。

    hello.s文件:

    #hello.s
    #关于汇编中直接使用系统调用
    #在汇编中使用系统调用,均通过0x80调用来实现。在具体编程时要将调用号放在eax中,
    #其他的参数按定义的逆序入栈即可。这里用到两个调用号,一个是4号调用,即:sys_write,这个调用有三个参数:fd,msg,len;
    #另一个调用是1号调用,好:sys_exit,这个调用只有一个参数,就是返回值。
    .section .data
        msg: .string "In hello.s!
    "
        len=.-msg
    .text
    .global _start,myprint
    _start:
    #第一次显示In hello.s!
        movl $4,%eax
        movl $1,%ebx
        movl $msg,%ecx
        movl $len,%edx
        int $0x80
        
    #模仿linux 0.11中的入栈动作    
        pushl $0
        pushl $0
        pushl $0
        
    #调用c函数,显示hello, in hello.c!
        call main
        
    #调用汇编函数,第二次显示In hello.s!
        call echohello
        movl $1,%eax
        movl $0,%ebx
        int $0x80
    #
    echohello:
        movl $4,%eax
        movl $1,%ebx
        movl $msg,%ecx
        movl $len,%edx
        int  $0x80
        ret
        
    #c函数中显示字串使用的函数,其在c中的原型如下:
    #myprint(char * msg,int len)
    #由于使用ld直接连接,并没有连接标准库,所以c函数中不能使用printf等库函数。
    myprint:
        movl 4(%esp),%ecx        #这里esp指向谁?想想上篇文章就明白了。
        movl 8(%esp),%edx        #参数的入栈是在c函数中完成的
        movl $1,%ebx
        movl $4,%eax
        int  $0x80
        ret


    hello.c文件:

    //hello.c
    //这个程序段不用任何解释
    #include <stdio.h>
    void myprint(char * msg,int len);
    int main()
    {
        myprint("Hello, In hello.c!
    ",20);
        return 0;
    }

    汇编及链接运行:

    as -o hello.o hello.s ;如果在64位系统下,要加上 --32选项来编译

    gcc -c -o hello_c.o hello.c ;如果在64位系统上,要加上-m32选项

    ld -o hello hello.o hello_c.o ;如果在64位系统上,要加上 -m elf_i386选项

    然后运行 ./hello

    如果没有输入等错误,就可以得到预期的输出。共输出三次hello,二次是汇编代码输出的,一次是c代码输出的。

    然后,我们去掉那三句入栈语句再次进行编译链接以及运行。会如何呢?

    ...

    毫无悬念,程序依旧会正确执行,也没有任何warning。得到同上一次一样的结果。

    实验2、linux 0.11内核部分修改验证

    既然上一个实验中可以去掉那三种入栈动作的语句,那我们回到linux 0.11中,将head.s文件中那三句pushl $0全部注释掉(汇编语言的注释是用#),然后重新编译linux0.11内核,再次运行。

    可以看到,内核没有提示任何错误,进行正常的操作也没有任何问题。所以这里为何要有这三行入栈操作其实很存疑的。

    ps:我们可以得到如下结论。

    1、内核代码也不是完全不可动的。至少看起来是这样。linux 0.11中代码之所以那样写,想来也许是当时编译器等原因造成的。但现在的环境下是可以去掉的。

    2、我们复习了汇编和c语言互相调用的方法。

  • 相关阅读:
    OSError: [Errno 13] Permission denied: '/Library/Python/2.7/site-packages/django'
    mac 安装pip
    同学公司倒闭了
    web开发中的字体选择(同事分享)
    svg 学习笔记
    用highchaarts做股票分时图
    highcharts,highStock 中文图表配置
    为什么使用 npm Scripts 构建项目
    JS 浮点型计算的精度问题 推荐的js 库 推荐的类库 Numeral.js 和 accounting.js
    HTML代码转换为JavaScript字符串
  • 原文地址:https://www.cnblogs.com/mqmelon/p/4773486.html
Copyright © 2020-2023  润新知