x86/Debian Linux/gcc。

 

1 目前学习汇编的步骤

  • 怎么将汇编代码添加到汇编程序框架中。
  • 理解汇编语句的含义。
  • 汇编源代码是怎么转换为可执行代码的。

 

2 AT&T汇编程序结构

在汇编中,一般具有如下的结构:

  • 数据段(.data)
  • 附加段(.bss)
  • 代码段(.text)

 

在汇编程序中,数据和附加段可选,但在汇编程序中经常包含有这两个段。代码段必含,是声明指令代码的地方。

 

(1)定义段

每个段用”.section”或“.标识符”标识。如”.section.data””.data”就形象的表示一个数据段。其实对于对于系统来说,它只是一个段,以“.”标识。

 

(2) 汇编程序入口

当汇编代码被编译成为可执行程序的时候,连接器必须要知道程序入口点在哪里。在GNU汇编器中,_start被定义为汇编程序的入口标识符,_start标识符暗示程序从这里开始运行。

 

为了让这个入口标识符能被其它文件的汇编代码认识,提供 “.globl”指令实现,即用.globl  _start语句声明程序入口标识符。

 

所以,一个AT&T汇编程序的结构大概如此:

.section  .data

         < initialized data here>

 

.section  .bss

         < uninitialized data here>

 

.section .text

.globl _start

_start:

         <instruction code goes here>

在代码段中,汇编代码依靠系统调用来完成一些操作,包括在程序退出之前的一些操作及程序退出操作

 

3一个简单的汇编程序

(1) 数据段

.data

msg:   .ascii  "hello, world\n"

len= .-msg
  •  形象的标名数据段。
  • .asciiAT&T汇编中的数据类型,类似的数据类型还有“.short”、“.int”、“.long”、“.byte”、“.float”、“.double”、“.comm”。第二条语句叫表示msg作为后续字符串的起始地址。
  • len求得字符串的长度。在汇编程序中“.”可以取出当前地址计数器的值,该值是一个常量。

(2) 代码段

代码段主要是用系统调用来完成程序功能。包含“程序入口”、“功能代码”和“程序退出代码”。

 

[1]程序入口

.text

.global  _start

_start:

按照GNU AT&T汇编的格式,_start为汇编程序的入口,且由.global保证其它文件内的汇编代码可识别_start入口。

 

[2]功能代码

_start:

    movl $4, %eax       #Linux system wirte call

    movl $1, %ebx       #STDOUT

    movl $msg, %ecx  #Give string start address to %ecx

    movl $len, %edx  #Give string lenght to %edx

    int  $0x80                    #Software interrupt

 int指令中的立即数0x80是一个参数,在异常处理程序中要根据这个参数决定如何处理,在Linux内核中int $0x80这种异常称为系统调用(System Call)。内核提供了很多系统服务供用户程序使用,但这些系统服务不能像库函数(比如printf)那样调用,因为在执行用户程序时CPU处于用户模式,不能直接调用内核函数,所以需要通过系统调用切换CPU模式,经由异常处理程序进入内核,用户程序只能通过寄存器传几个参数,之后就要按内核设计好的代码路线走,而不能由用户程序随心所欲,想调哪个内核函数就调哪个内核函数,这样可以保证系统服务被安全地调用。在调用结束之后,CPU再切换回用户模式,继续执行int $0x80的下一条指令,在用户程序看来就像函数调用和返回一样。[Linux C 编程一站式学习]

 

int $0x80引发的系统调用中,在eax寄存器内的值表示系统调用号,内核需要通过eax判断用户要调哪个系统调用。4代表linux write system call1代表linux exits ystem  call。ebx的值是传给_exit的参数,表示退出状态。大多数系统调用完成之后会返回用户空间继续执行后面的指令,而_exit系统调用比较特殊,它会终止掉当前进程,而不是返回用户空间继续执行。And so on

 

linux 写系统调用(eax值为4)下,其它的寄存器内所存值也会代表一些含义:

  • ebx包含要写向的文件描述符(STANDOUT1STDIN0STDERR2)。
  • ecx包含子符串的起始地址。
  • edx包含ecx字符串的长度。

 

故而,在为写操作准备好所有的条件之后。int $0x80经执行引起的软件中断后,照eax内存的值就访问linux内核的一些函数,向控制台(ebx:1)输出字符串(ecx:msg)。

 

[3]程序退出

movl $1, %eax   #Linux systemexit call

movl $0, %ebx   #Acceptprogram exit value

int $0x80       #software interrupt

Linux汇编程序的退出也是依靠系统调用来完成。此时寄存器eax中的值应该为1ebx的值为程序退出后返回给shell的值。这几句汇编程序完成_exit()调用。

 

(1)、(2)这段汇编代码相当于以下这段C代码:

#include <unistd.h>

char msg[14]  = "hello, world\n";
#define len  14

int  main(void)
{
        write(1, msg, len);
        _exit(0);
}

 

4 编译执行AT&T linux汇编

加入以上各部分组成的代码文件名为,learn.s,则编译执行过程为:

as     -o   learn.o     learn.s      [生成learn.o文件]

ld      -o   learn         learn.o   [生成learn可执行文件]

./learn                                     [ 得程序执行结果]

hello, world

 

5 总结

关于涉及到的AT&T汇编的语法

  • 立即数前面需要加$。
  • 直接引用符号常数,在符号常数前加$表示引用符号常数的地址。
  • movl  %eax, %ebx,源操作数为eax内的值,目的操作数为ebx的值。源、目的操作数符合从左到右的操作顺序。
  • 寄存器前面要加%。
  • Linux 下用vim编辑器编辑AT&T代码时#表示注释符号。

 

初学汇编,在了解汇编程序框架后,如果不知代码段应该写什么内容,不明白要用哪一个寄存器来操作时。应该查查各寄存器在系统调用软件中断发生时(int $0x80)要发生的操作所对应的寄存器的值。这样,起码就能编写“hello world!”的入门代码了。

 

当然,要了解整个汇编代码的机制还得多编写,达到只需查询就可完成功能的目标。目前根据这个程序得到的经验是:汇编代码可以依靠系统调用软件中断来调用内核的函数,系统调用的类型由eax寄存器你的值。我们需要提前配置好eax及相关寄存器的值,从而实现咱的目标功能一般是eax寄存器里面的值决定软件中断后linux系统调用的类型(eax:4linux write system call,eax:1 linux exit system call),然后其它的寄存器配合这种统调用[好像总结得有点浅显]


我们用C标准I/O库函数最终也是通过系统调用把I/O操作从用户空间传给内核,然后让内核去做I/O操作。

 

ASNote Over。

Logo

更多推荐