x86汇编基础及简单例子

#网络安全 共 1,802 字 约 6 分钟

参考链接: 《Linux C编程一站式学习》

plaintext
UTF-8|25 Lines|
 .section .data
data_items:
 .long 3,67,34,222,45,75,54,34,44,33,22,11,66,0
 .section .text
 .globl _start
_start:
 movl $0, %edi  	                          # move 0 into the index register
 movl data_items(,%edi,4), %eax    # load the first byte of data
 movl %eax, %ebx 	                          # since this is the first item, %eax is
			                                  # the biggest

start_loop: 		                          # start loop
 cmpl $0, %eax  	                         # check to see if we've hit the end
 je loop_exit
 incl %edi 		                        # load next value
 movl data_items(,%edi,4), %eax
 cmpl %ebx, %eax                  	# compare values ebx is the biigest num ,  jle start_loop # jump to loop beginning if the new one isn't bigger: jle means if eax is less than or equals ebx
 movl %eax, %ebx                    	# move the value as the largest
 jmp start_loop 	                       # jump to loop beginning

loop_exit:
                                                    # %ebx is the status code for the _exit system call
                                                    # and it already has the maximum number
 movl $1, %eax  	                   #1 is the _exit() syscall
 int $0x80

这是寒假表哥发到群里的一个asm文件,当时的要求有两个,一是得出输出结果,二是把这个文件想办法运行起来。当时由于胃痛提前睡觉,之后也把这个东西给忘了。现在补充学习一下。
首先显而易见这是个汇编语言的源程序文件。 但是语法和平常Windows下所见的汇编语言有所区别。因为 Windows使用intel语法,而UNIX平台的汇编器一直使用AT&T语法。
我们在linux环境下将其编译、链接并运行。

plaintext
UTF-8|4 Lines|
$ as test.s -o test.o   #编译
$ ld test.o -o test     #链接
$ ./test                #运行
$ echo $?               #打印结果

as是汇编语言的编译指令,生成test.o 即obj格式的目标文件
ld是链接程序,链接后生成可执行的exe
链接主要有两个作用,一是修改目标文件中的信息,对地址做重定位,二是把多个目标文件合并成一个可执行文件
运行结果是:

plaintext
UTF-8|1 Line|
222

显然是求这组数的最大值的一个逻辑功能。 接下来再仔细看内容。

plaintext
UTF-8|3 Lines|
 .section .data
data_items:
 .long 3,67,34,222,45,75,54,34,44,33,22,11,66,0

.data数据段
.long指示声明一组数,每个数占32位,相当于C语言中的数组。这个数组开头定义了一个符号data_items,汇编器会把数组的首地址作为data_items符号所代表的地址,data_items类似于C语言中的数组名。data_items这个标号没有用.globl声明,因为它只在这个汇编程序内部使用,链接器不需要用到这个名字。

plaintext
UTF-8|3 Lines|
.section .text
 .globl _start
_start:

.text开始代码段 这里注意下global和start的意义。
.globl指示告诉汇编器,_start这个符号要被链接器用到,所以要在目标文件的符号表中标记它是一个全局符号。
_start就像C程序的main函数一样特殊,是整个程序的入口,链接器在链接时会查找目标文件中的_start符号代表的地址,把它设置为整个程序的入口地址,所以每个汇编程序都要提供一个_start符号并且用.globl声明。

plaintext
UTF-8|3 Lines|
 movl $0, %edi  	                         
 movl data_items(,%edi,4), %eax    
 movl %eax, %ebx 	      

初始化edi,指向数组的第0个元素。
然后把数组的第0个元素传送到eax寄存器中。4表示数组的每个元素占4字节,那么数组中第edi个元素的地址应该是data_items + edi * 4,写在指令中就是data_items(,%edi,4) ebx的初始值也是数组的第0个元素。

plaintext
UTF-8|3 Lines|
 start_loop:
 cmpl $0, %eax
 je loop_exit

接下来进入循环,每次比较eax和0,eax表示当前的数,因为数组的最后一个数是0,所以比较到最后一个数的时候会跳出循环。

plaintext
UTF-8|2 Lines|
 incl %edi
 movl data_items(,%edi,4), %eax

edi的值加1,把数组的下一个数传到eax寄存器里

plaintext
UTF-8|2 Lines|
 cmpl %ebx, %eax
 jle start_loop

把当前数和截至目前的最大值ebx比较,如果最大值没有变,则跳到循环开头继续比较下一个数字。否则继续执行下一条指令。

plaintext
UTF-8|2 Lines|
 movl %eax, %ebx
 jmp start_loop

更新最大值,把eax的值传给ebx,然后再跳到开头比较下一个数。

plaintext
UTF-8|3 Lines|
loop_exit:
 movl $1, %eax  
 int $0x80

loop_exit后面的指令调_exit系统调用退出程序。
_exit 的系统调用号为1 ,而32位系统eax寄存器保存系统调用号。

在Linux 的汇编语言中(AT&T,x86汇编两种语法的一种),int 指令被称为==软中断指令== ,可以用此指令去故意产生一个异常 ,(异常与中断有点类似, 过程相当于CPU从用户模式切换到特权模式, 然后跳转到内核代码中执行异常处理的代码。)
这种异常称为系统调用(System Call)

Linux的各种系统调用都是由int $0x80指令引发的,内核需要通过eax判断用户要调哪个系统调用。

补充

plaintext
UTF-8|17 Lines|
.section .data

 .section .text
 .globl _start
_start:
 movl $1, %eax  # this is the linux kernel command
        # number (system call) for exiting
        # a program

 movl $4, %ebx  # this is the status number we will
        # return to the operating system.
        # Change this around and it will
        # return different things to
        # echo $?

 int $0x80  # this wakes up the kernel to run
        # the exit command
  • 把int $0x80指令去掉,汇编、链接也能通过,但是执行的时候出现段错误,为什么?
  • 程序从_start入口开始执行,如果去掉int $0x80,当执行完movl $4, %ebx,后就没有调用_exit系统调用结束程序,于是会接着从程序加载内存位置之后取下一条不属于本程序的指令,于是出现段错误
  • 一 般来说,==段错误就是指访问的内存超出了系统所给这个程序的内存空间==,通常这个值是由gdtr来保存的,他是一个48位的寄存器,其中的32位是保存由它指 向的gdt表,后13位保存相应于gdt的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别,指向的gdt是由以64位为一个单位的 表,在这张表中就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别还有内存粒度等等的信息。一旦一个程序发生了 越界访问,cpu就会产生相应的异常保护,于是segmentation fault就出现了. 在编程中以下几类做法容易导致段错误,基本是是错误地使用指针引起的
    1、 访问系统数据区,尤其是往 系统保护的内存地址写数据 最常见就是给一个指针以0地址
    2、 内存越界(数组越界,变量类型不一致)等
    3、 访问到不属于你的内存区域