汇编语言程序设计一书,在32位系统下应该不会有什么问题,然而在64位系统下,则会有些不一样的地方。有些程序范例还会汇编错误或者执行错误。
博主所用系统为CentOS v6.4 x64。本文主要解决32位的汇编程序如何在64位环境下汇编、连接,而不论述64位汇编语言如何设计。
1. 64位系统下编译32位的C程序
以程序test5.c为例,程序代码很简单,如下:
test5.c#include <stdio.h> int main() { char str[4]; str[0]=‘f‘; str[1]=‘g‘; str[2]=‘j‘; str[3] = 0; printf("CPU id is %s\n", str); exit(0); }
这个C源程序没有什么32位还是64位之说,用gcc编译后,在64位系统下就得到64位的elf文件,执行也不会有问题,如下图所示:
上图用file test5很清楚的看到是64-bit的文件。
那么怎么编译成32位的程序呢?用-m32参数。会发现有错误,错误如下:
对于警告和exit不兼容,可以包含头文件stdlib.h就可以解决。
对于gnu/stubs-32.h:没有哪个文件或目录,需要安装glibc-devel和glibc-devel.i686。对于CentOS,安装命令为:yum install glibc-devel和yum install glibc-devel.i686。
yum install glibc-devel,如下图:
yum install glibc-devel.i686,如下图:
之后使用-m32编译,就不会再发生问题了。如下图:
上图用file test5很清楚的看到是32-bit的文件。
总结以上主要有三点:
64位系统下编译32位程序使用-m32参数,即gcc –m32 –o output_file input_file.c
提示隐式声明与内建函数’exit’不兼容的警告,增加#include <stdlib.h>来解决
gnu/stubs-32.h:没有哪个文件或目录的错误,需要安装glibc-devel和glibc-devel.i686来解决
2. 64位系统下汇编32位的汇编程序
1). 64位汇编和32位汇编不同
汇编语言64位和32位是很不一样的,这里提供一份Intel官方的对64位的汇编简单介绍的pdf文档下载: Introduction_to_x64_Assembly。虽然该文档按照微软的MASM的格式来说明的,但是还是可以得到一些我们需要的信息,从该文档中可以知道64位的寄存器已经和32位的不一样了,比如64位寄存器是rax,rbx等,低32位的才是使用eax,ebx。
对于系统调用,64位系统和32位系统大大不一样了,比如sys_write的系统调用,32位系统和64位系统分别如下:
32位的sys_write(stdout, str, length)的汇编调用
32位系统的sys_write(stdout, str, length)的汇编调用# sys_write(stdout, str, length)的汇编调用 movl $4, %eax movl $stdout, %ebx movl $str, %ecx movl $length, %edx int $0x80
64位的sys_write(stdout, str, length)的汇编调用
64位系统的sys_write(stdout, str, length)的汇编调用# 64位的sys_write(stdout, str, length)的汇编调用 movq $1, %rax movq $stdout, %rdi movq $str, %rsi movq $length, %rdx syscall
虽然出于兼容性,32位系统的调用仍然可以在64位系统上运行,但两者已经大大不一样了。本文不是论述如何写64位汇编语言的,而是为了解决32位的汇编代码可以在64位环境下运行。
2). 64位系统下用as和ld汇编32位的汇编程序
那么怎样在64位系统中汇编32位的汇编程序呢?以以下例子cpuid2.s为例,代码为:
cpuid2.s# cpuid2.s file .section .data output: .asciz "CPUID is ‘%s‘\n" .section .bss .lcomm buffer, 12 .section .text .globl _start _start: nop movl $0, %eax cpuid movl $buffer, %edi movl %ebx, (%edi) movl %edx, 4(%edi) movl %ecx, 8(%edi) pushl $buffer pushl $output call printf addl $8, %esp pushl $0 call exit
这个代码的具体实现,后续的章节会介绍。目前只知道是用于输出CPU厂商ID字符串的就行了。
在64位系统下,如果按照书中所述的那样as和ld,会产生错误,如下图:
说是push操作无效。
如果不修改源代码为64位的汇编,要解决这个问题,就需要命令64位系统按照32位的去汇编,as参数是--32, ld参数是-m elf_i386。如下图:
file cpuid2可以看到确实是32位的文件,而且执行也没有问题。这里要特别对ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o cpuid2 -L/lib -lc cpuid2.o这行命令进行说明。
-m elf_i386:表示按照elf_i386的模块进行连接,即32位的。
-lc: 因为程序中调用了标准的C库函数printf和exit,因此需要连接C动态库libc.so,所以需要参数-lc来指定连接的库文件,一般而言libxxx.so采用-lxxx的参数。
-L/lib:博主的系统有多个libc.so,包括64位的,32位的,arm结构的,如下图所示,能搜出很多个libc.so。
而/lib/libc.so才是32位x86系统所需要的动态库,所以使用-L/lib来指定库文件的路径,那么-L/lib –lc就指定了连接的是/lib/libc.so。
-dynamic-linker /lib/ld-linux.so.2:用于运行时动态加载libc.so动态库的。否则执行生成的可执行文件时会出错。
3). 64位系统下用gcc汇编32位的汇编程序
还是以上面的cpuid2.s为例,使用gcc -m32的参数进行汇编成32位的系统文件,特别的,这里由于没有main函数,而是用_start做入口点,因此需要使用参数 –nostdlib,如下图:
对gcc -nostdlib -m32 -o cpuid2 cpuid2.s -L/lib -lc进行说明。
-m32:按照32位的编译。
-nostdlib:如果没有这个参数,gcc会连接gnu库的函数,该函数会以_start为进入点,执行一段程序后,跳到main执行,而这个汇编源程序中用_start做进入点,而且没有main,因此,没有这个参数的话,会提示没有main定义以及重复定义_start的错误。加了这个参数后,则不会去连接gnu函数。
综上所述,64位系统下汇编32位汇编程序的做法是:
as --32 –o output_file.o input_file.s
ld –m elf_i386 –dynamic-linker /lib/ld-linux.so.2 –o output_file –L/path –llibname input_file.s
或者
gcc –m32 –nostdlib –o output_file –L/path –llibname input_file.s