此处汇编仅仅为了看懂 Linux 下编译, 连接, 载入过程及原理
Intel 汇编规则:
在汇编程序中, 立即数前面要加 $, 寄存器名前面要加 %, 以便跟符号名区分开.
mov 源 目的(字长用指令的后缀 l 表示 32 位)
- #PURPOSE: Simple program that exits and returns a
- # status code back to the Linux kernel
- #
- #INPUT: none
- #
- #OUTPUT: returns a status code. This can be viewed
- # by typing
- #
- # echo $?
- #
- # after running the program
- #
- #VARIABLES:
- # %eax holds the system call number
- # %ebx holds the return status
- #
- .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
这段汇编代码相当于在 C 程序的 main 函数中 return 4
. 开头的名称称为汇编指示 (Assembler Directive) 或伪操作(Pseudo-operation), 不会被翻译成机器指令, 而是给汇编器一些特殊指示.
.section 指示把代码划分成若干个段(Section), 程序被操作系统加载执行时, 每个段被加载到不同的地址, 操作系统对不同的页面设置不同的读, 写, 执行权限.
.data 段保存程序的数据, 是可读可写的, 相当于 C 程序的全局变量.
.text 段保存代码, 是只读和可执行的.
_start 是一个符号(Symbol), 符号在汇编程序中代表一个地址, 可以用在指令中, 汇编程序经过汇编器的处理之后, 所有的符号都被替换成它所代表的地址值..globl 指示告诉汇编器,_start 这个符号要被链接器用到, 所以要在目标文件的符号表中标记它是一个全局符号.
.globl 指示告诉汇编器,_start 这个符号要被链接器用到, 所以要在目标文件的符号表中标记它是一个全局符号.
- movl $1, %eax
- movl $4, %ebx
- int $0x80
int 指令称为软中断指令, 可以用这条指令故意产生一个异常, 异常的处理和中断类似, CPU 从用户模式切换到特权模式, 然后跳转到内核代码中执行异常处理程序.
int 指令中的立即数 0x80 是一个参数, 在异常处理程序中要根据这个参数决定如何处理, 在 Linux 内核中 int $0x80 这种异常称为系统调用 (System Call). 内核提供了很多系统服务供用户程序使用, 但这些系统服务不能像库函数(比如 printf) 那样调用, 因为在执行用户程序时 CPU 处于用户模式, 不能直接调用内核函数, 所以需要通过系统调用切换 CPU 模式, 经由异常处理程序进入内核, 用户程序只能通过寄存器传几个参数, 之后就要按内核设计好的代码路线走, 而不能由用户程序随心所欲, 想调哪个内核函数就调哪个内核函数, 这样可以保证系统服务被安全地调用. 在调用结束之后, CPU 再切换回用户模式, 继续执行 int $0x80 的下一条指令, 在用户程序看来就像函数调用和返回一样.
eax 和 ebx 的值是传递给系统调用的两个参数. eax 的值是系统调用号, Linux 的各种系统调用都是由 int $0x80 指令引发的, 内核需要通过 eax 判断用户要调哪个系统调用,_exit 的系统调用号是 1.ebx 的值是传给_exit 的参数, 表示退出状态. 大多数系统调用完成之后会返回用户空间继续执行后面的指令, 而_exit 系统调用比较特殊, 它会终止掉当前进程, 而不是返回用户空间继续执行.
寻址方式
访问内存时在指令中可以用多种方式表示内存地址, 比如可以用数组基地址, 元素长度和下标三个量来表示, 增加了寻址的灵活性. 本节介绍 x86 常用的几种寻址方式(Addressing Mode). 内存寻址在指令中可以表示成如下的通用格式:
ADDRESS_OR_OFFSET(%BASE_OR_OFFSET,%INDEX,MULTIPLIER)
它所表示的地址可以这样计算出来:
FINAL ADDRESS = ADDRESS_OR_OFFSET + BASE_OR_OFFSET + MULTIPLIER * INDEX
其中 ADDRESS_OR_OFFSET 和 MULTIPLIER 必须是常数, BASE_OR_OFFSET 和 INDEX 必须是寄存器. 在有些寻址方式中会省略这 4 项中的某些项, 相当于这些项是 0.
直接寻址(Direct Addressing Mode). 只使用 ADDRESS_OR_OFFSET 寻址, 例如 movl ADDRESS, %eax 把 ADDRESS 地址处的 32 位数传送到 eax 寄存器.
变址寻址(Indexed Addressing Mode) . 上一节的
movl data_items(,%edi,4), %eax
就属于这种寻址方式, 用于访问数组元素比较方便.
间接寻址(Indirect Addressing Mode). 只使用 BASE_OR_OFFSET 寻址, 例如 movl (%eax), %ebx, 把 eax 寄存器的值看作地址, 把内存中这个地址处的 32 位数传送到 ebx 寄存器. 注意和 movl %eax, %ebx 区分开.
基址寻址(Base Pointer Addressing Mode). 只使用 ADDRESS_OR_OFFSET 和 BASE_OR_OFFSET 寻址, 例如 movl 4(%eax), %ebx, 用于访问结构体成员比较方便, 例如一个结构体的基地址保存在 eax 寄存器中, 其中一个成员在结构体内的偏移量是 4 字节, 要把这个成员读上来就可以用这条指令.
立即数寻址(Immediate Mode). 就是指令中有一个操作数是立即数, 例如 movl $12, %eax 中的 $12, 这其实跟寻址没什么关系, 但也算作一种寻址方式.
寄存器寻址(Register Addressing Mode). 就是指令中有一个操作数是寄存器, 例如 movl $12, %eax 中的 %eax, 这跟内存寻址没什么关系, 但也算作一种寻址方式. 在汇编程序中寄存器用助记符来表示, 在机器指令中则要用几个 Bit 表示寄存器的编号, 这几个 Bit 也可以看作寄存器的地址, 但是和内存地址不在一个地址空间.
x86 的寄存器
x86 的通用寄存器有 eax,ebx,ecx,edx,edi,esi. 这些寄存器在大多数指令中是可以任意选用的, 比如 movl 指令可以把一个立即数传送到 eax 中, 也可传送到 ebx 中. 但也有一些指令规定只能用其中某个寄存器做某种用途, 例如除法指令 idivl 要求被除数在 eax 寄存器中, edx 寄存器必须是 0, 而除数可以在任意寄存器中, 计算结果的商数保存在 eax 寄存器中(覆盖原来的被除数), 余数保存在 edx 寄存器中. 也就是说, 通用寄存器对于某些特殊指令来说也不是通用的.
x86 的特殊寄存器有 ebp,esp,eip,eflags.eip 是程序计数器, eflags 保存着计算过程中产生的标志位, 包括进位标志, 溢出标志, 零标志和负数标志, 在 intel 的手册中这几个标志位分别称为 CF,OF,ZF,SF.ebp 和 esp 用于维护函数调用的栈帧
来源: http://www.bubuko.com/infodetail-2606120.html