美文网首页
在汇编语言中使用C库函数

在汇编语言中使用C库函数

作者: wjundong | 来源:发表于2021-07-18 08:25 被阅读0次

    GNU 汇编: 第一个汇编程序 中, 利用Linux 系统调用将读到的 cpuid 显示到控制台上, 还有不使用系统调用的其他方法, 其中一种就是使用 C 库函数.

    实例

    demo.s

    .section .data
    
    output:
        .asciz "The processor Vender ID is '%s'\n"
    
    .section .bss
        .lcomm buffer, 12
    
    .section .text
    .globl _start
    
    _start:
        // 获取 CPU ID
        movl $0, %eax
        cpuid
        
        // 将 CPU ID 填充到 buffer
        movl $buffer, %edi
        movl %ebx,  (%edi)
        movl %edx, 4(%edi)
        movl %ecx, 8(%edi)
    
        // 调用C标准库 printf
        pushl $buffer
        pushl $output
        call printf
        addl $8, %esp
        push $0
    
        // 退出程序
        call exit
    
    

    注意这里使用的是 .asciz 命令而不是 .ascii, 这是因为 C 语言的字符串需要以空字符结尾来判断字符串的结束.

    命令 .comm.lcomm 用来声明通用内存区域, lcomm 表示该区域数据是静态的, 本地汇编代码之外的程序段无法访问该内存区域. 这里声明了 12 个字节的本地通用内存用于保存读取到的 cpuid.

    为了把参数传给 C 函数 printf, 必须把它们压入堆栈. 这是使用 pushl 指令完成的. 参数放入堆栈的顺序和 printf 函数获取它们的顺序是相反的, 所以 buffer 先被放入, 然后是输出字符串, 在这些操作完成之后, 使用 call 指令调用 printf 函数.

    指令 addl $8, %esp 将堆栈指针 sp 加 8, 其目的是为了清空调用 printf 函数时放入堆栈的参数, 使用相同的技术把返回值 0 压入堆栈供 C 函数 exit 使用.

    连接 C 库函数

    在 Linux 把 C 函数链接到汇编语言程序有两种办法:

    1. 静态链接
      静态链接把目标代码直接复制到应用程序的可执行文件中, 这样会使可执行文件巨大, 并且如果同时运行程序的多个实例, 因为每个实例都有自己相同函数的拷贝,这会造成内存浪费.
    2. 动态链接
      动态链接使用库的方式使程序员可以在应用程序中引用函数, 但是不会把代码拷贝到可执行文件中. 在程序运行时由操作系统调用动态链接库, 并且多个程序可以共享动态链接库.
    • 动态链接

      在 Linux 中, 标准的 C 动态库位于 libc.so.x 文件中, 其中 x 代表 库的版本. 并且库文件默认被放置于 /lib 目录下.

      我们可以使用 find 命令来查看 /lib 目录下的 C 库

      $ find /lib/ -name "libc.so*" 
      /lib/i386-linux-gnu/libc.so.6
      /lib/x86_64-linux-gnu/libc.so.6
      

      可以看到我的计算机中的 C 库版本是 6, 而且同时包含有 32 位和 64 位的版本.

    • 编译链接
      为了链接到 libc.so 文件, 需要使用 ld 的 -l 参数, 使用 -l 参数时, 不需要指定完整的库名称, 链接器会自动在默认库目录中查找:

      $ as demo.s -32
      $ ld ./a.out -o demo -m elf_i386 -lc
      $ ./demo
      ./demo: No such file or directory
      

      链接程序没有问题, 但是当我试图运行产生的可执行文件时, 出现了上述错误.

      这是由于 Linux 系统加载一个包含动态链接的应用程序时,OS 将控制权传递给动态加载器而不是应用程序的正常入口点。动态加载器搜索并加载未解析的库,然后将控制权传递给应用程序的起始点。在 Linux 中动态加载器是ld-linux.so.2.

    • 查看动态加载器
      使用 ldd 命令可以查看一个程序的所使用的动态加载器和所依赖的库:

      $ ldd ./demo
      linux-gate.so.1 (0xf7f5d000)
      libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d52000)
      /usr/lib/libc.so.1 => /lib/ld-linux.so.2 (0xf7f5f000)
      

      可以看到在默认情况下 ld 错误的把 /usr/lib/libc.so.1 作为动态加载器 ld-linux.so.2.

      为了找到正确的 ld-linux.so.2 加载器的位置, 再次使用 find 对关键字进行查找:

      $ find /lib/ -name "ld-linux*"
      /lib/i386-linux-gnu/ld-linux.so.2
      /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
      /lib/ld-linux.so.2
      

      可以看到本地目录中有 32 位和 64 位动态加载器

    • 在 ld 命令中指定动态加载器

      $ ld ./a.out -o demo -m elf_i386 -lc -dynamic-linker /lib/i386-linux-gnu/ld-linux.so.2
      $ ldd ./demo
      linux-gate.so.1 (0xf7fbd000)
      libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7db2000)
      /lib/i386-linux-gnu/ld-linux.so.2 => /lib/ld-linux.so.2 (0xf7fbf000)
      $ ./demo
      The processor Vender ID is 'GenuineIntel'
      

      这里将程序所依赖的动态加载器指定为 32 位动态加载器, 该加载器会在程序运行前查找并加载 32 位动态链接库目录下的动态链接库. 最后程序输出了正确的结果.

    使用 GCC 编译

    也可以使用 gcc 编译器进行汇编和链接, 实际上, 对于本例子来说, 这会容易许多, 因为 gcc 编译器会自动链接必须的 C 库, 会自动设置正确的动态加载器.

    • gcc 编译
      注意把 _start 标签改为 main, 因为 gcc 通过 main 标签来识别入口地址而不是 _start:
      $ gcc demo.s -m32
      $ ./demo
      The processor Vender ID is 'GenuineIntel'
      

    相关文章

      网友评论

          本文标题:在汇编语言中使用C库函数

          本文链接:https://www.haomeiwen.com/subject/ewkgpltx.html