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

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'
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值