汇编语言----X86汇编指令

目录

1.汇编指令的构成

2.X86架构CPU中包含的寄存器

3.常见的x86汇编指令

(1)算数运算

(2)逻辑运算

(3)其他

4.AT&T格式

5.选择语句(分支结构)

6.循环语句

(1)条件转移指令实现循环

(2)loop指令实现循环

7.函数调用的机器级指令

如何访问栈帧中的数据

函数调用栈在内存的位置:

访问栈帧数据:push、pop指令:

函数调用时切换栈帧:

恢复esp与ebp的值:

执行ret:

栈帧内可能包含哪些内容

如何传递返回值


高级语言--->汇编语言---->机器语言

一条高级语言可能对应多条汇编语言,但是汇编语言指令与机器语言是一一对应的关系

机器语言与汇编语言合称为机器级代码

1.汇编指令的构成

汇编指令用来改变程序执行流以及处理数据,汇编指令的格式为

操作码+地址码

操作码:告诉机器如何处理

地址码:则告诉机器数据的位置(寄存器:在寄存器中给出"寄存器名",主存:在指令中给出"主存地址",指令:直接在指令中给出操作数,即立即寻址)

以mov指令为例:

 其中的内存地址[af996h](h表示16进制)前面的符号:dword ptr,byte ptr,是用来指明内存的读写长度

2.X86架构CPU中包含的寄存器

EAX,EBX,ECX,EDX:通用寄存器

mov eax ebx   #寄存器-->寄存器

mov eax dword ptr [af996h]   #主存-->寄存器

mov eax,5    #立即数-->寄存器

若想只使用低16bit

mov ax bx   #寄存器-->寄存器

mov ax dword ptr [af996h]   #主存-->寄存器

mov ax,5    #立即数-->寄存器

也可以使用高低8bit

mov ah bl   #寄存器-->寄存器

mov ah dword ptr [af996h]   #主存-->寄存器

mov ah,5    #立即数-->寄存器

ESI,EDI:变址寄存器

变址寄存器可用于线性表,字符串的处理

EBP,ESP:堆栈基指针

堆栈寄存器用于实现函数调用

注:只有通用寄存器能使用低16bit或8bit,变址寄存器以及堆栈寄存器只能固定使用32bit 

举例:

mov eax,dword ptr [ebx+8]   

#将ebx所指的地址偏移8个单位,从这个地址当中复制低32bit到eax寄存器中

mov eax,dword ptr [af996-12h]

#将af996-12所指的贮存地址的低32bit复制到eax寄存器中
 

3.常见的x86汇编指令

机器识别汇编语言的原理:

CU控制单元会发送控制信号,例如告诉ALU进行算数运算或逻辑运算,ALU就会将输入的数(d,s)进行相应的运算

destination:目的地(d 目的操作数):目的操作数不能为常量

source:来源地 (s 源操作数):可以为一个常量

注:两个操作数不能同时来自于主存,可以同时来自于寄存器

(1)算数运算

对于除法中的被除数edx:eax表示存放64位的被除数,高32位存放在edx,低32位存放在eax中

(2)逻辑运算

(3)其他

用于实现分支结构、循环结构的指令:cmp、test、jmp、jxxx

用于实现函数调用的指令: push、pop、call、ret

用于实现数据转移的指令: mov

4.AT&T格式

AT&T格式与intel格式的区别

AT&T格式是Unix,Linux常用的格式,intel格式是windows常用格式

对于[ebx+ecx*32+4]的使用情景:

5.选择语句(分支结构)

在X86寄存器中,程序计数器PC通常被称为IP,执行一条指令时,PC自动+1,指向下一条即将执行的指令 

无条件转移指令:

jmp <地址>    #pc无条件转移到<地址>

jmp 128         #<地址>可以用常数给出

jmp eax         #<地址>可以来自于寄存器

jmp [999]       #<地址>可以来自于主存

若想跳转到某一条指令,就需要知道某条指令的地址,这样是很难的,可以用"标号"瞄准位置

注:这里的NEXT,名字是可以自己取的

无条件转移指令无法实现条件转移,可以使用jxxx

对于比较a,b两个数,需要使用cmp a,b  

举个例子:

先else在if

先if后else

补充:cmp的底层原理

比较a,b的大小,本质是进行a-b减法运算

OF (Overflow Flag)溢出标志。溢出时为1,否则置0

SF(Sign Flag) 符号标志。结果为负时置1,否则置0

ZF(Zero Flag)零标志,运算结果为0时ZF位置1,否则置0

CF(Carry Flag)进位/借位标志,进位/借位时置1,否则置0

ALU每次运算的标志位都自动存入PSW程序状态字寄存器中(intel称为标志寄存器

如下是8086CPU中的16位bit的PSW标志寄存器:

根据标志位的结果,进行判断是否满足jxxx,例如:

jne:若ZF=0,那么满足jne,进行跳转,如果ZF\neq0,那么就不进行跳转

6.循环语句
(1)条件转移指令实现循环

循环语句由4个部分构成

1.循环前的初始化:

2.是否直接跳过循环:

3.循环主体

4.是否继续循环

(2)loop指令实现循环

这里的loop Looptop等价于

dec ecx

cmp ecx,0

jne Looptop 

所以使用loop指令实现的功能一定能用条件转移指令实现

补充:loopx指令--如loopnz,loopz

 

7.函数调用的机器级指令

函数调用指令:call<函数名>

函数返回指令:ret

例如:

对应的X86指令如下图所示:

call指令的作用:

①将IP寄存器(PC)的IP旧值压栈保存(保存在函数的栈帧顶部)

②设置IP新值,无条件转移到被调用函数的第一条指令

ret指令的作用:

从函数的栈帧顶部找到IP旧值,将其出栈并恢复IP寄存器

如何访问栈帧中的数据
函数调用栈在内存的位置:

在32位系统中,进程虚拟地址空间为4GB,栈低在上,栈顶在下

在常用寄存器中,我们可以看到两个关于栈的寄存器EBP,ESP

EBP与ESP代表的含义如下:EBP指向栈帧的底部,ESP指向栈帧的顶部

当add栈帧执行完后,会退到caller栈帧继续执行,esp与ebp指向的地址也会随之改变

访问栈帧数据:push、pop指令:

push、pop 指令实现入栈、出栈操作,x86 默认以4字节为单位。指令格式如下:

push x:先让esp减4,再将x压入

x可以为立即数,寄存器,主存地址

pop  x:栈顶元素出栈写入x,再让esp加4

x可以为寄存器,主存地址

push和pop只能对esp进行访问,但是不能访问栈中其他值,可以使用mov:

•可以用mov 指令,结合 esp、ebp 指针访问栈帧数据
•可以用减法/加法指令,即 sub/add 修改栈顶指针esp 的值

函数调用时切换栈帧:

call指令的作用:

①将IP寄存器(PC)的IP旧值压栈保存(保存在函数的栈帧顶部):效果相当于push ip

②设置IP新值,无条件转移到被调用函数的第一条指令:效果相当于jmp add(add表示标号,程序的执行流会转移到标号位置)

流程如下:

1.push ip,先减4,再将IP旧址压入

2.jmp add,转到add的第一条指令

3.push ebp,先减4,再将ebp指向的值放到栈顶

这里的作用是将ebp指向的地址保存下来,也就是上一层函数的栈帧基址,当执行完当前函数时,可以根据这一地址返回上一层函数

4.mov ebp,esp:让ebp寄存器指向esp所指的地址

让ebp指向当前函数的基地址,也就是设置当前函数的栈帧基址

灰色部分表示原来的ebp和esp,总之完成两件事情,记录上一层函数的基地址,以及将ebp指向当前函数的基地址:

push ebp

mov ebp,esp

这里也可以直接用enter这个指令代替,即上面两条指令等价于:enter

我们可以看到,当前函数的栈帧中的栈底总是存储了上一层函数的基地址,这样执行完当前函数后,就可以根据栈底的地址返回上一层函数

恢复esp与ebp的值:

在执行完当前函数指令后

1.mov esp,ebp   #让esp指向当前栈帧底部

2.pop ebp   #将esp所指元素出栈,写入寄存器ebp,也就是让ebp重新指回上一函数的栈帧底部,同时esp+4

mov esp,ebp

pop ebp

等价于

leave

执行ret:

恢复esp,ebp的地址后,esp指向了IP的旧值,那么继续执行ret

ret的作用:从函数的栈帧顶部找到IP旧值,将其出栈并恢复IP寄存器,也就是让程序的执行流回到call指令的下一条指令:

总结如下: 

栈帧内可能包含哪些内容

对于以下函数,栈帧存储的内容:

1.栈帧底部一定是上一层栈帧基地址(ebp旧值)

2.在调用下一层函数时,一定会使用到call指令,call指令会将IP寄存器的值压到栈顶保存,所以IP寄存器的值(返回地址)保存在栈帧顶部

3.将局部变量集中存储在栈帧底部区域,C语言中越靠前定义的局部变量越靠近栈顶

如何访问局部变量:

这里只需要将[ebp-4]=sum,[ebp-8]=temp2,[ebp-12]=temp3,[ebp-4]表示最后一个定义的局部变量

4.调用参数集中存储在栈帧顶部区域,参数列表中越靠前的参数越靠近栈顶

如何访问调用参数:

这里只需要将[ebp+8],得到第一个参数,[ebp+12],得到第二个参数

5.栈帧中可能出现空闲未使用的区域

在gcc 编译器中,将每个栈大小设置为 16B 的整数倍(当前函数的栈除外),因此栈帧内可能出现空闲未使用的区域。

例如add栈帧可以为4B,8B,但是只要这个函数需要调用下一层的函数就必须凑齐16B的整数倍

总结:

如何传递返回值

多个参数可以通过函数调用栈传递,但是函数的返回值只有一个,所以通常将返回值保存到eax寄存器中,所以当返回到上一层函数时,只需要到eax中取结果即可

总结:

补充:调用其他函数前,如果有必要,可将某些寄存器 (如: eax、edx、ecx)的值入栈保存,防止中间结果被破坏。


本篇总结:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/419149.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

huggingface学习 | 云服务器使用hf_hub_download下载huggingface上的模型文件

系列文章目录 huggingface学习 | 云服务器使用git-lfs下载huggingface上的模型文件 文章目录 系列文章目录一、hf_hub_download介绍二、找到需要下载的huggingface文件三、准备工作及下载过程四、全部代码 一、hf_hub_download介绍 hf_hub_download是huggingface官方支持&…

Linux中的共享内存

定义&#xff1a; 共享内存允许两个或者多个进程共享物理内存的同一块区域&#xff08;通常被称为段&#xff09;。由于一个共享内存段会称为一个进程用户空间的一部分&#xff0c;因此这种 IPC 机制无需内核介入。所有需要做的就是让一个进程将数 据复制进共享内存中&#xff…

力扣精选算法100题——串联所有单词的字串(滑动窗口专题)

本题链接——串联所有单词的字串 本题和找到字符串中所有字母异位词题目非常相似&#xff0c;思路都是一样。通过自己的大脑能发现其中的相似之处。 第一步&#xff1a;了解题意 就按实例来分析吧&#xff0c;这样更通俗易懂。 words["ab","cd","ef…

mysql从库重新搭建的流程

背景 生产环境上的主从集群&#xff0c;因为一些异常原因&#xff0c;导致主从同步失败。现记录下通过重做mysql从库的方式来解决&#xff0c;重做过程不影响主库。 步骤 1、在主库上的操作步骤 备份主库所有数据&#xff0c;并将dump.sql文件拷贝到从库/tmp目录 mysqldump …

Flutter 综述

Flutter 综述 1 介绍1.1 概述1.2 重要节点1.3 移动开发中三种跨平台框架技术对比1.4 flutter 技术栈1.5 IDE1.6 Dart 语言1.7 应用1.8 框架 2 Flutter的主要组成部分3 资料书籍 《Flutter实战第二版》Dart 语言官网Flutter中文开发者社区flutter 官网 4 搭建Flutter开发环境参考…

vue3-模版引用

模版引用 ref 属性 场景&#xff1a;需要直接访问底层 DOM 元素。 方法&#xff1a;使用特殊的 ref 属性。 <input ref"input">ref 属性 允许我们在一个特定的 DOM 元素或子组件实例被挂载后&#xff0c;获得对它的直接引用。 访问模板引用 小 Demo: 当 i…

Go 知识iota

Go 知识iota 1. 介绍2. 特性3. 原理4. 你真的理解了吗 1. 介绍 iota 是一个预定义的标识符&#xff0c;用于声明枚举常量。它在 const 声明中使用&#xff0c;表示连续的未类型化整数。其值从0开始&#xff0c;const声明块每增加一行&#xff0c;iota的值就会自增1&#xff0c…

优化您的服务请求,增强用户体验和服务交付

您的服务请求模板是否像一个复杂的迷宫&#xff0c;给您的团队带来延误和困惑&#xff1f;您的技术人员是否厌倦了为了解最终用户的需求而与他们来回奔波&#xff1f;强大且可定制的请求模板可能正是您所需要的&#xff01; 服务交付团队&#xff08;尤其是 IT&#xff09;的…

javascript入门分享(附:javascript基础入门视频教程)

javascript入门分享&#xff08;附&#xff1a;javascript基础入门视频教程&#xff09; 一、javascript入门了解 JavaScript&#xff08;简称“JS”&#xff09;是一种具有函数优先的轻量级&#xff0c;解释型或即时编译型的编程语言。 虽然它是作为开发Web页面的脚本语言而出…

Node.js Stream.pipeline() Method

Why Stream.pipeline 通过流我们可以将一大块数据拆分为一小部分一点一点的流动起来&#xff0c;而无需一次性全部读入&#xff0c;在 Linux 下我们可以通过 | 符号实现&#xff0c;类似的在 Nodejs 的 Stream 模块中同样也为我们提供了 pipe() 方法来实现。 未使用 Stream p…

基于WSL的Ubuntu命令行美化

大多数 Linux 发行版中的默认 Shell 是 Bash。Bash 缺乏代码高亮&#xff0c;不易阅读。本文旨在通过安装 Zsh、oh-my-zsh&#xff0c;并应用 Powerlevel10k 主题来解决这一问题。 环境&#xff1a;Windows10 Pro 21H2&#xff0c;OS build: 19044.1766&#xff1b;基于 WSL 的…

JVM之java内存区域[1](程序计数器、栈)

文章目录 版权声明零 运行时数据区一 程序计数器1.1 加载阶段1.2 执行阶段1.3 多线程情况 二 栈2.1 java虚拟机栈2.2 java虚拟机栈帧的组成2.2.1 局部变量表2.2.2 操作数栈2.2.3 帧数据 2.3 栈内存溢出2.4 设置帧大小2.5 本地方法栈 版权声明 本博客的内容基于我个人学习黑马程…