QQ登录

只需一步,快速开始

登录 | 立即注册 | 找回密码

主题

帖子

荣誉

VIP至尊会员

Rank: 15Rank: 15Rank: 15

积分
248
查看: 382|回复: 10

[已解决]跳转指令与浮动装配(汇编相关)

[复制链接]
最佳答案
1 
累计签到:111 天
连续签到:1 天
比特阿尔法 发表于 2018-2-11 11:59:37 38210 | 显示全部楼层 |阅读模式

马上注册加入鱼C,享用更多服务吧^_^

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
本帖最后由 比特阿尔法 于 2018-2-12 09:56 编辑

        我在学王爽老师的第九章,在遇到 短转移  和  长转移  的时候很疑惑,
书上说:短转移  是通过位移,来修改IP  实现跳转的。
1.PNG



长转移   的机器指令 中就包含  标号的目的地址。
2.PNG


我的问题是:为什么长转移的目的地址在程序执行时,标号地址 永远是机器码的数值(也就是说不变的),
                    短转移 就一定要用 位移 来实现跳转,短转移的标号的地址是会变的吗?
最佳答案
2018-2-12 09:19:52
本帖最后由 兰陵月 于 2018-2-12 09:31 编辑


回复3#。。。。。。。。。。。。。。。。。
这涉及到的玩意就比较多了。以8086实模式为例(讲的肯定不全面,只是点了个知识点):
1、操作系统将程序重定位的时候,确定了程序加载到内存中的位置,这样每个段的段地址也就确定了。这其中的过程比较啰嗦,但不外乎就是先确定一个地址,然后从硬盘中把程序加载到内存指定位置。再确定每个段的段地址。
2、在执行1之间,还有一个编译过程,该过程是编译器多次扫描源程序之后,产生一个最后结果。你图中jmp fat ptr s在编译之后未运行之前有一个固定值,这个固定值在第1步的工作中又被替换成你在debug里看到的值。
保护模式下就更加复杂一点了,还涉及线性地址(虚拟地址)和硬件地址的转换,当然都是有规则的,都是可以反推的。

程序并不知道它会被加载到什么位置,所以在运行之前,远跳转后面跟的地址只是编译之后的地址,并不是运行时候的地址。运行的时候操作系统会自动更换地址。如果你自己写小型操作系统,那就必须自己编写加载规则,否则程序就不能正常运行。我们在DOS下面也好,在Windows下面也好,都是一个已经成型的操作系统,它都有一定的规则,编译器按照它们的规定和要求进行编译,这样DOS和Windows就能正确加载程序了。

建议学习完王爽老师的书后,再学习《x86汇编语言—从实模式到保护模式》,必定解答你心中的上述疑惑,只不过要学懂弄通的话,需要下点时间和功夫。
这本李老师的书是在裸机上运行的,也就是说纯粹基于硬件的,没有操作系统环境,所以学到后面的时候,是要从0000:7C00开始自己编写加载程序,自己编写用户程序,用自己的加载程序加载自己编写的用户程序。这样你问题中的各类跳转如何运作你一清二楚,当然,你能学到后面,肯定自己也都会了,都是很简单的事情。

突然感觉,推荐人学某本书,就像推荐人跳坑一样,计算机底层就是让欲罢不能的XX,知道了一点,就想一直搞下去,揭开它的面纱。
楼层
跳转到指定楼层
最佳答案
8 
累计签到:1180 天
连续签到:4 天
lies_for_L 发表于 2018-2-11 15:01:21 | 显示全部楼层

回帖奖励 +20

本帖最后由 lies_for_L 于 2018-2-11 15:02 编辑

1. 纯汇编写程序的情况下
一般来说无论长短跳转其后都是固定值
  1. DATA SEGMENT
  2. STR1 DB ‘ABCDEFG′NEQU-STR1 ;给N赋值,表示当前指令的地址,-STR1表示当前位置到STR1头部的距离,也就是STR1的长度。
  3. STR2 DB ‘BCDEFG′MEQU-STR2
  4. MESS DB ‘NO MATCH!′RIGHTDB‘MATCH!’
  5. DATA ENDS
  6. CODE SEGMENT
  7. ASSUME CS:CODE,DS:DATA,ES:DATA

  8. BEGIN:
  9. MOV AX,DATA
  10. MOV DS,AX
  11. MOV AX,DATA
  12. MOV ES,AX
  13. MOV AL,N
  14. CMP AL,M ;比较长度是否相等,相等ZF=0,否则ZF=1
  15. JNZ EXIT ; 111111111 JNZ:jump if not zero ,zero 指的是比较结果,而不是ZF标志位的值
  16. LEA SI,STR1
  17. LEA DI,STR2
  18. MOV CL,N
  19. MOV CH,0
  20. CLD
  21. REPE CMPSB ;挨个比较单个字符,如果有一个不等就跳EXIT
  22. JNZ EXIT
  23. LEA DX,RIGHT
  24. JMP OUT1 ; 2222222222

  25. EXIT:
  26. LEA DX,MESS
  27. OUT1:
  28. MOV AH,9
  29. INT 21H
  30. MOV AH,4CH
  31. INT 21H

  32. CODE ENDS
  33. END BEGIN
复制代码

当你手写完一个程序后编译时除1 2 处其它代码已经知道长度,然后填充两处跳转,所以都是固定值


2. 高级写法,及高级语言写程序
jmp后能跟动态值,短跳转我还没看到有动态的,还不确定,短跳转一般是判断的时候使用
比如用jmp来call动态链接库里函数时,因为动态链接库是在运行时载入,载入地址不确定,就需要动态地址
1.png
2.png
最佳答案
1 
累计签到:111 天
连续签到:1 天
比特阿尔法  楼主| 发表于 2018-2-11 19:51:07 | 显示全部楼层
本帖最后由 比特阿尔法 于 2018-2-11 19:52 编辑
lies_for_L 发表于 2018-2-11 15:01
1. 纯汇编写程序的情况下
一般来说无论长短跳转其后都是固定值


谢谢,明白了,我还有一个疑问:长转移带的是标号的地址,从机器码中就可以看出来,如:EA 0B 01 BD 0B,翻译成汇编就是  jmp  far ptr 标号。问题是程序在编译时  不知道自己运行时是否会被加载到   0BBD:010B  这里,为啥要带着这个地址呢?还是它本来就知道自己会被加载到这里运行?

                               
登录/注册后可看大图
最佳答案
41 
累计签到:493 天
连续签到:6 天
兰陵月 发表于 2018-2-12 09:19:52 | 显示全部楼层    本楼为最佳答案   

回帖奖励 +20

本帖最后由 兰陵月 于 2018-2-12 09:31 编辑


回复3#。。。。。。。。。。。。。。。。。
这涉及到的玩意就比较多了。以8086实模式为例(讲的肯定不全面,只是点了个知识点):
1、操作系统将程序重定位的时候,确定了程序加载到内存中的位置,这样每个段的段地址也就确定了。这其中的过程比较啰嗦,但不外乎就是先确定一个地址,然后从硬盘中把程序加载到内存指定位置。再确定每个段的段地址。
2、在执行1之间,还有一个编译过程,该过程是编译器多次扫描源程序之后,产生一个最后结果。你图中jmp fat ptr s在编译之后未运行之前有一个固定值,这个固定值在第1步的工作中又被替换成你在debug里看到的值。
保护模式下就更加复杂一点了,还涉及线性地址(虚拟地址)和硬件地址的转换,当然都是有规则的,都是可以反推的。

程序并不知道它会被加载到什么位置,所以在运行之前,远跳转后面跟的地址只是编译之后的地址,并不是运行时候的地址。运行的时候操作系统会自动更换地址。如果你自己写小型操作系统,那就必须自己编写加载规则,否则程序就不能正常运行。我们在DOS下面也好,在Windows下面也好,都是一个已经成型的操作系统,它都有一定的规则,编译器按照它们的规定和要求进行编译,这样DOS和Windows就能正确加载程序了。

建议学习完王爽老师的书后,再学习《x86汇编语言—从实模式到保护模式》,必定解答你心中的上述疑惑,只不过要学懂弄通的话,需要下点时间和功夫。
这本李老师的书是在裸机上运行的,也就是说纯粹基于硬件的,没有操作系统环境,所以学到后面的时候,是要从0000:7C00开始自己编写加载程序,自己编写用户程序,用自己的加载程序加载自己编写的用户程序。这样你问题中的各类跳转如何运作你一清二楚,当然,你能学到后面,肯定自己也都会了,都是很简单的事情。

突然感觉,推荐人学某本书,就像推荐人跳坑一样,计算机底层就是让欲罢不能的XX,知道了一点,就想一直搞下去,揭开它的面纱。
最佳答案
41 
累计签到:493 天
连续签到:6 天
兰陵月 发表于 2018-2-12 09:34:14 | 显示全部楼层
突然看到浮动装配4个字,哈哈哈哈哈哈

王爽老师果然不一般啊,重定位就重定位嘛,竟然换了浮动装配这么形象的词

最佳答案
1 
累计签到:111 天
连续签到:1 天
比特阿尔法  楼主| 发表于 2018-2-12 09:51:35 | 显示全部楼层
兰陵月 发表于 2018-2-12 09:19
回复3#。。。。。。。。。。。。。。。。。
这涉及到的玩意就比较多了。以8086实模式为例(讲的肯定不全 ...

谢谢月哥的耐心解释,小弟听懂了
最佳答案
1 
累计签到:111 天
连续签到:1 天
比特阿尔法  楼主| 发表于 2018-2-12 09:53:32 | 显示全部楼层
兰陵月 发表于 2018-2-12 09:34
突然看到浮动装配4个字,哈哈哈哈哈哈

王爽老师果然不一般啊,重定位就重定位嘛,竟然换了浮动装配这么 ...

为啥我看到一点感觉没有,差距好大
最佳答案
41 
累计签到:493 天
连续签到:6 天
兰陵月 发表于 2018-2-13 08:20:16 | 显示全部楼层
比特阿尔法 发表于 2018-2-12 09:53
为啥我看到一点感觉没有,差距好大

等你学习一下那种没有操作系统环境的汇编,体会自己去重定位的细节,你就会明白了。
最佳答案
1 
累计签到:111 天
连续签到:1 天
比特阿尔法  楼主| 发表于 2018-2-13 09:55:26 | 显示全部楼层
兰陵月 发表于 2018-2-13 08:20
等你学习一下那种没有操作系统环境的汇编,体会自己去重定位的细节,你就会明白了。

书我买了,等王爽的学完就看你推荐的那本
最佳答案
1 
累计签到:111 天
连续签到:1 天
比特阿尔法  楼主| 发表于 2018-4-23 18:50:58 | 显示全部楼层
虚拟内存到底是什么?为什么我们在C语言中看到的地址是假的?
在C语言中,指针变量的值就是一个内存地址,&运算符的作用也是取变量的内存地址,请看下面的代码:
#include <stdio.h>
#include <stdlib.h>
int a = 1, b = 255;
int main(){
    int *pa = &a;
    printf("pa = %#X, &b = %#X\n", pa, &b);
    system("pause");
    return 0;
}
在 C-Free 5.0 下运行,结果为:
pa = 0X402000, &b = 0X402004

代码中的 a、b 是全局变量,它们的内存地址在链接时就已经决定了,以后再也不能改变,该程序无论在何时运行,结果都是一样的。

那么问题来了,如果物理内存中的这两个地址被其他程序占用了怎么办,我们的程序岂不是无法运行了?

幸运的是,这些内存地址都是假的,不是真实的物理内存地址,而是虚拟地址。虚拟地址通过CPU的转换才能对应到物理地址,而且每次程序运行时,操作系统都会重新安排虚拟地址和物理地址的对应关系,哪一段物理内存空闲就使用哪一段。如下图所示:

虚拟地址
虚拟地址的整个想法是这样的:把程序给出的地址看做是一种虚拟地址(Virtual Address),然后通过某些映射的方法,将这个虚拟地址转换成实际的物理地址。这样,只要我们能够妥善地控制这个虚拟地址到物理地址的映射过程,就可以保证程序每次运行时都可以使用相同的地址。

例如,上面代码中变量 a 的地址是 0X402000,第一次运行时它对应的物理内存地址可能是 0X12ED90AA,第二次运行时可能又对应 0XED90,而我们的程序不需要关心这些,这些繁杂的内存管理工作交给操作系统处理即可。

让我们回到程序的运行本质上来。用户程序在运行时不希望介入到这些复杂的内存管理过程中,作为普通的程序,它需要的是一个简单的执行环境,有自己的内存,有自己的CPU,好像整个程序占有整个计算机而不用关心其他的程序。

除了在编程时可以使用固定的内存地址,给程序员带来方便外,使用虚拟地址还能够使不同程序的地址空间相互隔离,提高内存使用效率。
使不同程序的地址空间相互隔离
如果所有程序都直接使用物理内存,那么程序所使用的地址空间不是相互隔离的。恶意程序可以很容易改写其他程序的内存数据,以达到破坏的目的;有些非恶意、但是有 Bug 的程序也可能会不小心修改其他程序的数据,导致其他程序崩溃。

这对于需要安全稳定的计算机环境的用户来说是不能容忍的,用户希望他在使用计算机的时候,其中一个任务失败了,至少不会影响其他任务。

使用了虚拟地址后,程序A和程序B虽然都可以访问同一个地址,但它们对应的物理地址是不同的,无论如何操作,都不会修改对方的内存。
提高内存使用效率
使用虚拟地址后,操作系统会更多地介入到内存管理工作中,这使得控制内存权限成为可能。例如,我们希望保存数据的内存没有执行权限,保存代码的内存没有修改权限,操作系统占用的内存普通程序没有读取权限等。

另外,当物理内存不足时,操作系统能够更加灵活地控制换入换出的粒度,磁盘 I/O 是非常耗时的工作,这能够从很大程度上提高程序性能。

以上两点我们将在《内存分页机制》和《内存分页机制的实现》中进行详细讲解。
中间层思想
在计算机中,为了让操作更加直观、易于理解、增强用户体验,开发者经常会使用一件法宝——增加中间层,即使用一种间接的方式来屏蔽复杂的底层细节,只给用户提供简单的接口。虚拟地址是使用中间层的一个典型例子。

实际上,计算机的整个发展过程就是不断引入新的中间层:
计算机的早期,程序都是直接运行在硬件之上,自己负责硬件的管理工作;程序员也使用二进制进行编程,需要处理各种边界条件和安全问题。
后来人们不能忍受了,于是开发出了操作系统,让它来管理各种硬件,同时发明了汇编语言,减轻程序员的负担。
随着软件规模的不断增大,使用汇编语言编程开始变得捉襟见肘,不仅学习成本高,开发效率也很低,于是C语言诞生了。C语言编译器先将C代码翻译为汇编代码,再由汇编器将汇编代码翻译成机器指令。
随着计算机的发展,硬件越来越强大,软件越来越复杂,人们又不满足于使用C语言了,于是 C++、Java、C#、PHP 等现代化的编程语言诞生了。

转自----C语言中文网
最佳答案
1 
累计签到:111 天
连续签到:1 天
比特阿尔法  楼主| 发表于 2018-4-23 19:40:47 | 显示全部楼层
内存分页机制,完成虚拟地址的映射
关于虚拟地址和物理地址的映射有很多思路,我们可以假设以程序为单位,把一段与程序运行所需要的同等大小的虚拟空间映射到某段物理空间。

例如程序A需要 10MB 内存,虚拟地址的范围是从 0X00000000 到 0X00A00000,假设它被映射到一段同等大小的物理内存,地址范围从 0X00100000 到 0X00B00000,即虚拟空间中的每一个字节对应于物理空间中的每一个字节。

程序运行时,它们的对应关系如下图所示:


当程序A需要访问 0X00001000 时,系统会将这个虚拟地址转换成实际的物理地址 0X00101000,访问 0X002E0000 时,转换成 0X003E0000,以此类推。

这种以整个程序为单位的方法很好地解决了不同程序地址不隔离的问题,同时也能够在程序中使用固定的地址。
地址隔离
如上图所示,程序A和程序B分别被映射到了两块不同的物理内存,它们之间没有任何重叠,如果程序A访问的虚拟地址超出了 0X00A00000 这个范围,系统就会判断这是一个非法的访问,拒绝这个请求,并将这个错误报告给用户,通常的做法就是强制关闭程序。
程序可以使用固定的内存地址
虚拟内存无论被映射到物理内存的哪一个区域,对于程序员来说都是透明的,我们不需要关心物理地址的变化,只需要按照从地址 0X00000000 到 0X00A00000 来编写程序、放置变量即可,程序不再需要重定位。
内存使用效率问题
以程序为单位对虚拟内存进行映射时,如果物理内存不足,被换入换出到磁盘的是整个程序,这样势必会导致大量的磁盘读写操作,严重影响运行速度,所以这种方法还是显得粗糙,粒度比较大。
内存分页机制
我们知道,当一个程序运行时,在某个时间段内,它只是频繁地用到了一小部分数据,也就是说,程序的很多数据其实在一个时间段内都不会被用到。

以整个程序为单位进行映射,不仅会将暂时用不到的数据从磁盘中读取到内存,也会将过多的数据一次性写入磁盘,这会严重降低程序的运行效率。

现代计算机都使用分页(Paging)的方式对虚拟地址空间和物理地址空间进行分割和映射,以减小换入换出的粒度,提高程序运行效率。

分页(Paging)的思想是指把地址空间人为地分成大小相等(并且固定)的若干份,这样的一份称为一页,就像一本书由很多页面组成,每个页面的大小相等。如此,就能够以页为单位对内存进行换入换出:
当程序运行时,只需要将必要的数据从磁盘读取到内存,暂时用不到的数据先留在磁盘中,什么时候用到什么时候读取。
当物理内存不足时,只需要将原来程序的部分数据写入磁盘,腾出足够的空间即可,不用把整个程序都写入磁盘。
关于页的大小
页的大小是固定的,由硬件决定,或硬件支持多种大小的页,由操作系统选择决定页的大小。比如 Intel Pentium 系列处理器支持 4KB 或 4MB 的页大小,那么操作系统可以选择每页大小为 4KB,也可以选择每页大小为 4MB,但是在同一时刻只能选择一种大小,所以对整个系统来说,也就是固定大小的。

目前几乎所有PC上的操作系统都是用 4KB 大小的页。假设我们使用的PC机是32位的,那么虚拟地址空间总共有 4GB,按照 4KB 每页分的话,总共有 2^32 / 2^12 = 2^20 = 1M = 1048576 个页;物理内存也是同样的分法。
根据页进行映射
下面我们通过一个简单的例子来说明虚拟地址是如何根据页来映射到物理地址的,请先看下图:

图1:虚拟空间、物理空间和磁盘之间的页映射关系

程序1和程序2的虚拟空间都有8个页,为了方便说明问题,我们假设每页大小为 1KB,那么虚拟地址空间就是 8KB。假设计算机有13条地址线,即拥有 2^13 的物理寻址能力,那么理论上物理空间可以多达 8KB。但是出于种种原因,购买内存的资金不够,只买得起 6KB 的内存,所以物理空间真正有效的只是前 6KB。

当我们把程序的虚拟空间按页分隔后,把常用的数据和代码页加载到内存中,把不常用的暂时留在磁盘中,当需要用到的时候再从磁盘中读取。上图中,我们假设有两个程序 Program 1 和 Program 2,它们的部分虚拟页面被映射到物理页面,比如 Program 1 的 VP0、VP1 和 VP7 分别被映射到 PP0、PP2 和 PP3;而有部分却留在磁盘中,比如 VP2、VP3 分别位于磁盘的 DP0、DP1中;另外还有一些页面如 VP4、VP5、VP6 可能尚未被用到或者访问到,它们暂时处于未使用状态。
这里,我们把虚拟空间的页叫做虚拟页(VP,Virtual Page),把物理内存中的页叫做物理页(PP,Physical Page),把磁盘中的页叫做磁盘页(DP,Disk Page)。
图中的线表示映射关系,可以看到,Program 1 和 Program 2 中的有些虚拟页被映射到同一个物理页,这样可以实现内存共享。

Program 1 的 VP2、VP3 不在内存中,但是当进程需要用到这两个页的时候,硬件会捕获到这个消息,就是所谓的页错误(Page Fault),然后操作系统接管进程,负责将 VP2 和 PV3 从磁盘中读取出来并且装入内存,然后将内存中的这两个页与 VP2、VP3 之间建立映射关系。

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

小甲鱼强烈推荐 上一条 /1 下一条

    移动客户端下载(未启用)
    微信公众号

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备11014136号

Copyright 2018 鱼C论坛 版权所有 All Rights Reserved.

Powered by Discuz! X3.1 Copyright
© 2001-2018 Comsenz Inc.    All Rights Reserved.

小黑屋|手机版|Archiver|鱼C工作室 ( 粤公网安备 44051102000370号 | 粤ICP备11014136号

GMT+8, 2018-5-21 18:43

快速回复 返回顶部 返回列表