汇编

汇编

存储程序计算机
图灵机
冯诺依曼机
运算器、控制器、存储器、输入和输出设备

CPU由运算器(算术逻辑单元ALU),控制器和寄存器组成
寄存器程序计数器PC,在IA32(x86-32)中是EIP,指示要执行的下一条指令在存储器中的地址

x86-32汇编

  • 通用寄存器
    4个数据寄存器EAX,EBX,ECX,EDX
    2个指针寄存器ESP,EBP
    2个变址和指针寄存器ESI,EDI
  • 控制寄存器
    1个指令指针寄存器EIP
    1个标志寄存器EFlags
  • 段寄存器
    6个段寄存器CS,ES,SS,DS,FS,GS

32位数据寄存器更具通用性,可以暂存数据,如ALU的运算结果,还可以作为指针寄存器
代码段寄存器CS,指令存储在代码段中,使用CS:EIP指明指令地址
堆栈段寄存器SS,每个进程都有内核态堆栈和用户态堆栈

寻址方式和汇编指令
汇编指令包括操作码和操作数

操作码,即汇编指令
movb, movw, movl, movq

操作数

  • 立即数
    即常数,如$8
  • 寄存器数
    %eax
  • 存储器引用

寻址方式

  • 寄存器寻址register
    操作的是寄存器,不与内存交互,如movl %eax, %edx
  • 立即寻址immediate
    $符号后跟一个数值,如movl $0x123, %edx
  • 直接寻址direct
    直接用数值表示内存地址,如movl 0x123, %edx
  • 间接寻址indirect
    寄存器加个小括号,表示寄存器中存储的是内存地址,如move (%ebx), %edx
  • 变址寻址displaced
    在原地址上加一个立即数,如movl 4(%ebx), %edx

AT&T格式汇编,也是Linux内核使用的汇编格式
一般全是大写字母的为Intel汇编,全是小写字母的是AT&T汇编
pushl/popl
call/ret

pushl %eax
将EAX寄存器值压栈,实际上做了两个动作
subl $4, %esp
movl %eax, (%esp)
堆栈是向低地址增长的,所以用subl指令

popl %eax
从堆栈栈顶取一个存储单元放入EAX寄存器,实际上做了两个动作
movl (%esp), %eax
addl $4, %esp

call 0x12345
实际上做了两个动作,将EIP压栈,并将地址0x12345传入EIP寄存器,由硬件一次性完成

ret
将堆栈栈顶的一个存储单元放入EIP寄存器,该动作由硬件一次性完成

存储程序
函数调用堆栈
中断

ESP堆栈寄存器
EBP基址寄存器
x86体系结构,堆栈空间是从高地址向低地址增长的,即向上增长
堆栈操作push/pop

在函数传递返回值时,通过EAX寄存器来保存返回值,若有多个返回值,EAX返回内存地址
堆栈提供局部变量的空间,编译器会预留足够的栈空间以保存局部变量,但是早期编译器并没有这么做,而是要求局部变量声明在函数体前面
通过堆栈传递参数的方法是从右向左以此压栈,如形参的压栈顺序

C语言内嵌汇编

__asm__ __volatile__(
    汇编语句模板:
    输出部分:
    输入部分:
    破坏描述部分:
);

示例代码

#include<stdio.h>
int main(){
    /* val1 + val2 = val3 */
    unsigned int val1= 1;
    unsigned int val2= 2;
    unsigned int val3= 0;
    printf("val1:%d, val2:%d, val3:%d\n", val1, val2, val3);
    asm volatile(
        "movl $0, %%eax\n\t" // clear %eax
        "addl %1, %%eax\n\t" // %eax+= val1
        "addl %2, %%eax\n\t" // %eax+= val2
        "movl %%eax, %0\n\t" // val2= %eax
        : "=m" (val3)        // =m mean only write output memory variable
        : "c" (val1), "d" (val2) // input c or d mean %ecx/%edx
    );
    printf("val1:%d + val2:%d = val3:%d\n", val1, val2, val3);

    return 0;
}

__asm__是gcc关键字asm的宏定义,是内嵌汇编关键字
__volatile__是gcc关键字volatile的宏定义,告诉编译器不要优化代码

内嵌汇编的寄存器前有两个%,一个%加一个数字表示第二部分输出,第三部分输入或第四部分破坏描述
%1指的是val1,用c修饰,即存入ECX寄存器
%2指的是val2,用d修饰,即存入EDX寄存器
%0指的是val3,用=m修饰,即写入内存变量

内嵌汇编语法

内嵌汇编常用的修饰限定符

通用寄存器

限定符 说明
a EAX寄存器
b EBX寄存器
c ECX寄存器
d EDX寄存器
s ESI寄存器
D EDI寄存器
q 将输入变量放入eax,ebx,ecx,edx中的一个
r 将输入变量放入eax,ebx,ecx,esi,edi中的一个
A 将eax和edx合成为一个64位寄存器

内存

限定符 说明
m 内存变量
o 操作数为内存变量,寻址方式是偏移量寻址
v 操作数为内存变量,寻址方式不是偏移量寻址
. 操作数为内存变量,寻址方式是自动增量
p 操作数是一个合法的内存地址

操作数类型
| = | 操作数在指令中是只写的 |
| + | 操作数在指令中是读写的 |

寄存器或内存

限定符 说明
g 将输入变量放入eax,ebx,ecx,edx中的一个或作为内存变量
x 操作数可以是任何类型

立即数

限定符 说明
i 0~31之间的立即数,用于32位移位指令
J 0~63之间的立即数,用于64位移位指令
N 0~255之间的立即数,用于out指令
l 立即数
n 立即数,有些系统不支持除字以外的立即数,这些系统应该使用n

浮点数

限定符 说明
f 浮点数
t 第一个浮点寄存器
u 第二个浮点寄存器
G 标准的80387浮点常数
% 该操作数可以和下一个操作数交换位置

示例

int main(){
    int input, output, temp;
    intput= 1;
    __asm__ __volatile__(
        "movl $0, %%eax;\n\t"
        "movl %%eax, %1;\n\t"
        "movl %2, %%eax;\n\t"
        "movl %%eax, %0;\n\t"
        : "=m" (output), "=m" (temp)
        : "r" (input)
        : "eax"
    );
    printf("%d, %d\n", temp, output);

    return 0;
}

破坏描述部分"eax",表示EAX寄存器会被改动

posted @ 2024-12-05 13:10  sgqmax  阅读(40)  评论(0)    收藏  举报