汇编代码入门 AT&T指令格式

计算机系统结构

cpu内部:               
1. PC Program Counter
   指令指针寄存器
   指向下一条指令的地址
   EIP(X86-32)或者
   RIP(X86-64)
2. 寄存器与寄存器堆
  Registers
   在处理器CPU内部以名字来访问的快速存储单元
3. 条件状态码
   Condition Codes
    用于存储最近执行指令的结果状态信息
    用于条件指令的判断执行
内存单元Memory:
  以字节编码的连续存储空间
    存储程序代码、数据、运行栈stack 以及操作系统数据

汇编语言数据格式

c 语言          数据类型   汇编代码后缀  大小(字节为单位)
char              字节         b  byte         1
short       字           w  word         2
int        双字         l               4
long int      双字         l               4
long long int   无           无              4
char*        双字     l               4
float       单精度        s              4
double       双精度        l              8
long double    扩展精度      t             10/12

第一条汇编指令实例

c 语言代码:
 int t = x+y // 两个整数(32位)
汇编代码:
 addl 8(%ebp) %eax//l表示双字 8是位偏移量
  操作数:
  x: 寄存器 Register  eax
  y: 内存    Memory   M[ebp+8]   ebp是函数栈基址寄存器
  t: 寄存器  Register eax
  结果t保存在寄存器eax中
类似于表达:
 x += y
或者:
  int eax;
  int* ebp;
  eax += ebp[2];//这里按字节

数据传送指令

movel 源地 目的地
将一个双字从源地移动到目的地
允许的操作数类型有:
   立即数Imm:常整数
      如: $0x400, $-533
      可以用1,24个字节来表示
   寄存器 Reg:
      8个通用寄存器之一
      %eax
      %ebx
      %ecx
      %edx
      %esi
      %edi
      %esp  栈顶
      %ebp  栈底
   存储器Mem:四个连续的字节
                     汇编       类似C语言
立即数--->寄存器  movl $0x41, %eax     temp = 0x41;
立即数--->内存     movl $-43, (%eax)    *p   = -43;
寄存器--->寄存器   movl %eax, %edx      temp2 = temp;
寄存器--->内存     movl %eax, (%edx)    *p    = temp;
内存 --->寄存器   movl (%eax), %edx    temp  = *p;
不允许内存到内存  

简单得寻址模式

1. 间接寻址 (R)     Mem[Reg[R]]
寄存器R 指定得内存地址
movl (%ecx), %eax
2. 基址+偏移量 寻址 D(R)  Mem[Reg[R] + D]
寄存器R 指定内存的起始地址
常数D 给出偏移地址
movl 8(%ebp), %ecx

寻址模式使用示例

交换两个数
C语言
void swap(int* xp, int* yp){
int t0 = *xp;
int t1 = *yp;
*xp = t1;
*yp = t0;
}

汇编语言分析
寄存器 变量:
%ecx   yp
%edx   xp
%eax   t1
%ebx   t0
对应汇编:
movl 12(%ebp),%ecx  # ecx = yp  是地址  放到ecx寄存器中
movl 8(%ebp), %edx  # edx = xp 是地址  放到edx寄存器中
movl (%ecx), %eax   # eax = *yp t1 值   取寄存器中地址指向的内存地址中的内容放入 寄存器eax
movl (%edx), %ebx   # ebx = *xp t0 值  取寄存器中地址指向的内存地址中的内容放入 寄存器ebx
movl %eax, (%edx)   # *xp = eax          交换内容放入原来内存指向得地址中
movl %ebx, (%ecx)   # *yp = ebx

ebp 是函数栈 基地址
ebp+8 的位置 存储 指针xp 指向内存的一个地址
ebp+12 的位置 存储 指针yp 指向内存的一个地址

变址寻址

常见形式:
   D(Rb,Ri,S) Mem[Reg[Rb] + S*Reg[Ri] + D]
 D:  常量(地址偏移量)
 Rb: 基址寄存器:8个通用寄存器之一
 Ri: 索引寄存器: %esp不作为索引寄存器
         一般%ebp也不做这个用途
 S: 比例因子, 1,2,4,8
 其他变形:
   D(Rb,Ri) Mem[Reg[Rb] + Reg[Ri] + D]
   (Rb,Ri) Mem[ Reg[Rb] + Reg[Ri] ]
   (Rb,Ri) Mem[Reg[Rb] + S*Reg[Ri]]

地址计算指令 leal    ,  lea +l

leal src, dest
  src 是地址计算表达式子  D(Rb,Ri,S) ---> Reg[Rb] + S*Reg[Ri] + D

  计算出来得地址赋给 dest
使用实例:
  地址计算,无需访问内存 auto *p = &x[i];
  进行x+k*y这一类型得整数计算,k = 1,2,4,8

整数计算指令:

addl src,dest   #  dest = dest + src #加法
subl src,dest   #  dest = dest - src #减法
imull src,dest  #  dest = dest * src #乘法
sall src,dest   #  dest = dest << src #左移位 等价于shll
sarl src,dest   #  dest = dest >> src #算术右移位 补 被移动数的最高位 
shrl src,dest   #  dest = dest >> src #逻辑右移位 左边单纯补 0
xorl src,dest   #  dest = dest ^ src  #按位异或
andl src,dest   #  dest = dest & src  #按位与
orl  src,dest   #  dest = dest | src  #按位或

incl dest  #  dest = dest + 1  #++ 自增1
decl dest  #  dest = dest - 1  #-- 自减1
negl dest  #  dest = - dest # 取非
notl dest  #  dest = ~ dest #  取反

将leal指令用于计算

c语言:
   int temp(int x, int y){
      int t1 = x+y;
      int t2 = z+t1;
      int t3 = x+4;
      int t4 = y*48;
      int t5 = t3+t4;
      int ret = t2*t5
      return ret;
   }
汇编代码:
   movl 8(%ebp), %eax   # eax = x
   movl 12(%ebp), %edx  # edx = y
   leal (%eax, %edx), %ecx # t1  = ecx = x+y 
   addl 16(%ebp), %ecx     # t2 = ecx = z+t1
   leal (%edx,%edx,2), %edx # edx = 3*y
   sall $4, %edx            # t4 = edx = 2^4 * 3 * y =16*3*y = 48*y
   leal 4(%eax,%edx), %eax  # t5 = eax =4+x+t4
   imul %ecx, %eax          # ret = eax = t2*t5

逻辑运算示例

C语言:
   int logical_(int x, int y){
      int t1 = x^y;
      int t2 = t1>>17;
      int mask = (1<<13) - 7;
      int ret = t2 & mask;
      return ret;
   }
汇编语言:
movl 8(%ebp), %eax  # eax = x
xorl 12(%ebp), %eax # t1 = eax = x^y  
sarl $17, %eax      # t2 = eax = t1 >> 12
andl $8185, %eax    # ret = eax = t2 & 8185  2^13-7 = 8185 这里编译器会算出来

x86-32 与 x86-64主要类型数据宽度的区别

             x86-32  x86-64
  long int     4       8
  char*        4       8   内存地址编号长度

使用 条件码(标志寄存器) 来进行控制 

  addl src,dest    t =a+b
  CF Carry Flag 进位标志  可用于无符号整数运算的溢出 
  SF Sign Flag  符号位标志 结果<0 的话,被设置为1
  ZF Zero Flag 0结果标志  结果为0 的话,被设置为1
  OF Overflow Flag 溢出标志 if a>0 & b>0 & t<0 被设置为1

比较指令 compare 被比较数 比较数 compare b,a a-b(但是不会改变a的值)

  compare b,a  a-b
  if a == b , ZF =1
  if a < b  , SF=1
  if (a>0 && b<0 && (a-b)<0)||(a<0 && b>0 && (a-b)>0) OF=1

测试指令 test 做与操作

  testl s2,s1
  testq s2,s1
  计算 s1 & s2并设置相应的条件码,不改变目的操作数
  testl b,a   计算a&b
  if a&b ==0,  ZF =1
  if a&b < 0,  SF =1
  CF=0,OF=0

setX指令 获取条件码状态

  sete  相等 结果为0 ZF =1时
  setne 不相等        ZF =0

  sets  结果<0      SF =1
  setns 结果>=0       SF=0
  有符号数:
  setg  大于      
  setge 大于等于
  setl  小于
  setle 小于等于
  无符号数:
  setb  小于 below
  seta  大于  ablove

读取条件码实例

c语言:
   int gt(int x, int y){
   return x>y;
   }
汇编语言:
movl 12(%ebp), %eax    # eax = y
cmpl %eax, 8(%ebp)     # compare x:y  x-y
setg %al               # al寄存器是eax的低八位得名字 al = x>y
movzbl #al, %eax       # 8位扩展到16位 

跳转指令 Jx

  jmp   无条件跳转
  je    相等
  jne   不相等
  js    结果<0
  jns   结果>=0

  有符号 
  jg      大于
  jge   大于等于
  jl   小于
  jle   小与等与

  无符号 
  ja   大于
  jb   小于

跳转指令 Jx 实例

c语言:
 int abs_(int x, int y){
       int rest;
       if(x>y){
        rest = x - y;
       }
       else{
        rest = y-x;
       }
    return rest;
  }
汇编语言:
   movl 8(%ebp), %edx  #  edx = x
   movl 12(%ebp), %eax #  eax = y
   cmpl %eax, %edx     # x - y
   jle  .L7            # x <=y 跳转到.L7
   subl %eax, %edx     # x>y  ,  计算rest = x-y
   movl %edx, %eax     # 结果放在 寄存器eax指向的地址
   .L8:
     leave
     ret
   .L7:
      subl %edx, %eax  # x <=y 跳转到.L7 计算 y-x 
      jmp L8

循环的汇编语言表示

for循环:
   for(Init初始条件; 判断测试条件Test; 更新量Update)
       循环体Body;

转换到 While-do结构:
   Init初始条件;
   while(判断测试条件Test){
      循环体Body;
      更新量Update;
   }

再转换成go-to结构:
   Init初始条件;
   goto middle;  # 无条件跳转 jmp
loop:
   循环体Body;
   更新量Update;
middle:
   if(判断测试条件Test)
      goto loop;

switch case的汇编语言格式

c语言版本:

long switch_eg(long x, long y, long z){
   long w = 1;
   switch(x){
   case 1:
      w = y*z;
      break;
   case 2:
      w = y/z;
      break;
   case 3:
      w +=z;
      break;
   case 5:   
   case 6:
      w -= z;
      break; 
   defult:    // 这里 case 0 和 case 4 和其他情况
      w = 2;
     }
   return w;
}

汇编语言版本:

pushl %ebp      # 保存 寄存器老的 ebp的值
movl %esp, %ebp # 函数堆栈 栈顶指针 %esp 存放在%ebp
pushl %ebx      # 保存 寄存器ebx的值

movl 8(%ebp), %ebx  # 函数堆栈距离栈顶 偏移8位处存放x的值
movl 12(%ebp), %eax # y
movl 16(%ebp), %ecx # z
cmpl $6, %ebx       # x - 6 
jbe .L11            # X <= 6 才进入正常的 case

.L8:                # default 部分
   movl $2, %eax    # w = 2
   jmp .L12         # break

.L11:
   jmp *.L7(,%ebx,4) %访问.L7段表 对应的 case block
  
   .section     .rodata
.L7:
   .long .L8   # case 0 情况 进入 default
   .long .L3   # case 1 情况 
   .long .L4   # case 2 情况 
   .long .L9   # case 3 情况 
   .long .L8   # case 4 情况 进入 default  
   .long .L6   # case 5 情况 case 6
   .long .L6   # case 6 情况 进入 default
   
.L12:         # break
   popl %ebx  # 后进先出
   popl %ebp  # 先进后出
   ret        # 函数段返回

.L3:                # case 1
   imull %ecx, %eax # w = w*z
   jmp .L12         # break
   
.L4:                # case 2
   movl %eax, %edx  
   sarl $31, %edx
   idivl %ecx      # w =y/z
   addl %ecx, %eax # case 3 w += z  
   jmp .L12        # break
   
.L9:               # case 3
   movl %1, %eax   # w = 1
   addl %ecx, %eax # w +=z
   jmp .L12        # break

.L6:               # case 6  case 5
   movl $1, %eax   # w = 1
   subl %ecx, %eax # w -= Z
   jmp .L12        # break

x86-32的程序栈

  栈---水桶结构的一块内存区域---->只有一个出口(栈底%ebp (桶口)高地址(计算机地址逆向生长))
  先进后出后进先出 FILO

  寄存器 %esp 存储栈顶地址(下部)
  寄存器 %ebp 存储栈底地址(上部)

  注意 %esp  %ebp  始终指向当前正在运行的活动的函数过程栈

存储的内容

  局部变量
  返回地址
  临时空间

栈帧的分配与释放

  1. 进入过程 先分配栈帧空间
    set-up code
  2. 过程返回时 释放栈帧空间
    finish code

pushl 压栈 把大象装进水桶的操作

  水面栈顶%esp会上什(步进单位为4个内存地址块)
pushl src  # 从src 取得操作数
      # %esp = %esp-4 栈顶上升 水面上什

popl 出栈操作 把大象从水桶中取出来

  水面栈顶%esp会下降
popl Dest # 读取栈顶数据(%esp) 放入 目标位置Dest
          # %esp = %esp+4 栈顶下降 水面下降

过程调用 call label 过程返回指令 ret

过程调用指令:
call label #将返回地址(call指令的下一条指令地址(%eip)压入栈),跳转至label继续执行
过程返回指令
ret  #跳转至栈顶的返回地址

函数调用时 寄存器的使用惯例

  8个寄存器:
     两个特殊寄存器:
        调用者和被调用者可能都需要保存
        始终指向当前活动的堆栈上两端
        %ebp 栈底指针
        %esp 栈顶指针
     三个由调用者保存:
        %eax  常用于保存过程的返回值的
        %ecx
        %edx
     三个由被调用者保存:
        %ebx   
        %esi
        %edi

递归调用实例

  c语言版本:

  int rfact(int x){
     int rval;
     // 递归出口
     if(x<=1) return 1;
     // 递归调用
     rval = rfact(x-1);
     return rval * x;
  }

  汇编语言版本:
调用者栈情况          前 %ebp       <-  %ebp 栈底
                     前 %ebx
                     变量x 父过程调用的参数
                     返回地址 adr   <- %esp 栈顶
#  进入过程 先分配栈帧空间
被调用者(函数rfact)   老的 %ebp      第一步需要保存%ebp    pushl %ebp 指向 前%ebp   <- %esp 栈顶 栈顶自动调整
                                    第二步 movl %esp, %ebp                        <- %ebp 栈第指针也指向了栈顶 (栈基址)
                     老的 %ebx      第三步 pushl %ebx                             <- %esp 栈顶 栈顶自动调整 
                     (用来存储函数内的变量值,之前的值需要先保存)
                     
主体代码:
movl 8(%ebp), %ebx   # ebx = x
cmpl $1, %ebx        # compare x : 1  , 计算 x - 1
jle .L78             # x <= 1  直接跳转到递归出口 .L78
leal -1(%ebx), %eax  # eax = x-1  被调用者这里需要用到 %eax 作为过程的返回值的
pushl %eax           # push x-1  入栈保存 这里用作为调用者需要保存 %eax
call rfact           # rval = rfact(x-1), 函数返回值保存在 %eax
imull %ebx, %eax     # rval * x
jmp .L79             # 跳转到 done
.L 78:
   movl $1, %eax     # return eax = 1
   
 # 返回代码  过程返回时 释放栈帧空间
.L79:
   movl -4(%ebp), %ebx # 复原老的 %ebx 的值
   movl %ebp, %esp     # 栈顶指针 %esp  也指向%ebp指向的位置
   popl %ebp           # 恢复老的 %ebp 
   ret

x86-64的通用寄存器

  x86-32 有 8个32位的寄存器
        8个寄存器:
           两个特殊寄存器:
              调用者和被调用者可能都需要保存
              始终指向当前活动的堆栈上两端
              %ebp 栈底指针
              %esp 栈顶指针
           三个由调用者保存:
              %eax  常用于保存过程的返回值的  累加器 计算操作数和存放结果数据
              %ecx  计数寄存器
              %edx  数据寄存器
           三个由被调用者保存:
              %ebx  基础寄存器  指向DS数据段的数据指针
              %esi  源索引
              %edi  目的索引

  x86-64 有 16个64位的寄存器:
           与 x86-32兼容的8个64位的寄存器
                 %rax  低16位也叫 %eax     eax 低16位称为ax,ax的高8位称为ah,低8位称为al
                 %rbx            %ebx     ebx 低16位称为bx,bx的高8位称为bh,低8位称为bl
                 %rcx            %ecx     ecx 低16位称为cx,cx的高8位称为ch,低8位称为cl
                 %rdx            %edx     edx 低16位称为dx,dx的高8位称为dh,低8位称为dl
                 %rsi            %esi     esi 低16位称为si
                 %rdi            %edi     edi 低16位称为di
                 %rsp            %esp     esp 低16位称为sp
                 %rbp            %ebp     ebp 低16位称为bp

           新增加的8个64位的寄存器:
                 %r8   低16位也叫 %r8d
                 %r9             %r9d
                 %r10            %r10d
                 %r11            %r11d
                 %r12            %r12d
                 %r13            %r13d
                 %r14            %r14d
                 %r15            %r15d

x86-32 6个16位的段寄存器,定义内存中的段

        CS   代码段   存储指令和执行的地方
        DS, ES, FS, GS  数据段
        SS  堆栈段  当前程序存储堆栈的地方

数组的表示 访问 操作

访问:
c语言:
typedef int array_i_5[5];// 定义一个长度为5的整数数组类型 array_i_5
array_i_5 cmu = {1, 2, 5, 3, 4};// 定义一个数组cmu  函数五个整形变量
cum[index];//可以获取相应索引位置处的 元素

汇编语言:
# %edx = cmu   数组首地址
# %eax = index 索引
movl (%edx, %eax, 4), %eax # cmu[index]  4为一个整形类型所占据的直接数量
数组循环遍历修改
c语言:
void temp(array_i_5 z){
int i;
for (i=0; i<5; i++)
   z[i]++;// 数组各元素自增1
}
汇编语言:
movl $0, %eax  # 循环变量 %eax = i
.L4:
   addl $1, (%edx, %eax, 4) # z[i]++
   addl $1, %eax            # i++
   cmpl $5, %eax            # compare i:5
   jne .L4                  # i < 5 循环

多维数组的表示

int arr_i_i[row_index][col_index];
一个int类型占据4个字节地址
则 arr_i_i[y][x] 的实际地址为 arr_i_i + y*row_index*col_index + 4*x
例如一个 arrii[4][5]的二维数组,每一行占有 4*5=20个字节
那么 arrii[i][j] 的地址为  arrii + i*20 + 4*j
                          arrii + 4*(i+4*i) + 4*j 
相关汇编代码:
先计算列地址
leal 0(,%ecx,4), %edx        #  4*j
leal (%eax, %eax, 4), %eax   # 5 *i
movl arrii(%edx, %eax, 4), %eax # arrii + 4*j + 4*(i+4*i) 

hello world程序

      //helloworld.c
      #include <stdlib.h>
      #include <stdio.h>
      int main(){
         printf("Hello World\n");
         exit(0);
         return 0;//这里就不会编译了
      }
      // gcc 编译
      gcc -S -O2 helloworld.c
         -S   生成汇编代码(与系统类型相同)  helloworld.s
         -m32 64位系统生成32位汇编
         -On  -O2 2级编译优化
     // asm      
     movl $LC0 (%esp)  #设置过程调用参数 字符串首地址 放在栈顶对应的地址
     call _puts
     movl $0, (%esp)   #设置过程调用参数
     call _exit

标记符号

.text  #代码段
.p2align 4,,15 #对齐方式 按16 = 2^4字节对齐,填充,, 0, 最大填充15个字节
.section .rdata "dr"  #只读数据段
LC0:# 字符串的起始地址
      .ascii "Hello World\12\0"

linux 汇编命令

编译:

  //gcc -S -O2 helloworld.c
  as -o my-object-file.o helloworld.s
  # -gstabs 产生带调试信息的object文件
  # 64位环境下添加命令行 --32 生成 32位目标文件
  链接成执行程序
  ld -o my-exe-file my-object-file.o
  # 64位环境下添加命令行 -m elf_i386 生成 32位目标文件
helloworld.s
.data     #数据段
msg:      # 字符 首地址
   .ascii "Hello World\n"
   len = .-msg #  字符长度 . 表示当前地址
.text     # 代码段
.globl _start  # 汇编程序入口地址,如同C语言的main函数
_start:
   movl $len, %edx # 字符串长度 字节数量
   movl $msg, %ecx # 字符串存储位置的起始地址
   movl $1, %ebx   # 系统输出(write 系统调用1)
   movl $4, %eax   #
   int $0x80       # 中断 来指向系统调用
                   # eax存放的是系统调用的功能号
                   # ebx 为参数 为1时 是 write 显示器输出
                   # edx 字符串长度 字节数量
                   # ecx 字符串存储位置的起始地址
   
   movl $0, %ebx  # 系统调用(程序退出 0
   movl $4, eax
   int $0x80      # 中断 来指向系统调用

猜你喜欢

转载自blog.csdn.net/xiaoxiaowenqiang/article/details/80530438