2-5、包含多个段的程序

语雀原文链接

文章目录

    • 1、概述
    • 2、代码段中使用数据
      • 示例1:不指定程序入口
      • 示例2:指定程序入口
      • 原理梳理
    • 3、在代码段中使用栈
      • 例子1
      • 例子2
    • 4、数据、代码、栈放入不同的段
      • 例子1:end start指定程序入口
        • 第一步:设置栈顶
        • 第二步:设置DS
        • 第三步:push入栈
        • 第四步:pop出站
        • 第五步:程序退出
        • 误解
      • 例子2:段占据空间是16的倍数
      • 例子3:db的使用

1、概述

  • 在操作系统的环境中,合法地通过操作系统取得的空间都是安全的,因为操作系统不会让一个程序所用的空间和其他程序以及系统自己的空间相冲突。在操作系统允许的情况下,程序可以取得任意容量的空间。
  • 程序取得所需空间的方法有两种,一是在加载程序的时候为程序分配,再就是程序在执行的过程中向系统申请。这里我们主要研究下第一种方式:加载程序的时候为程序分配空间。
  • 我们若要一个程序在被加载的时候取得所需的空间,则必须要在源程序中做出说明。我们通过在源程序中定义段来进行内存空间的获取。

2、代码段中使用数据

  • 目前有这样一个需求,计算8个数据之和,结果存储在AX寄存器中。可以先将这8个数放在一个连续的内存单元中。但是这里有个问题,这个内存单元具体在哪里?
  • 从规范的角度来讲,我们是不能自己随便决定哪段空间可以以使用的,应该让系统来为我们分配。我们可以在程序中,定义我们希望处理的数据,这些数据就会被编译、连接程序作为程序的一部分写到可执行文件中。当可执行文件中的的程序被加载入内存时,这些数据也同时被加载入内存中。与此同时,我们要处理的数据也就自然而然地获得了存储空间。

示例1:不指定程序入口

  • dw 的含义是定义字型数据,dw 即 define word,字型数据可以直接放入寄存器中去,因为数据寄存器的大小也是一个字的大小
  • db 的含义是定义字节型数据,db 即 define byte,字节型数据应该使用数据寄存器的高 8 位或是低 8 位进行存放。
assume cs:code
code segmentdw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987hmov bx,0mov ax,0mov cx,8s:add ax,cs:[bx]add bx,2loop smov ax,4c00hint 21hcode endsend
  • 编译链接debug调试如下

image.png

  • 程序是从CS:IP=076A:0000开始存放,但是-u命令是看不懂的程序,和我们的程序不太一样。仔细一看是我们076A:0000 000F保存的是我们的8个数据,每个占用2个字节,总共16个字节;从076A:0010开始才是我们的程序

image.png

  • 上述程序我们无法直接执行,只有将IP=0010h后才能指向程序的第一条指令,才能执行。

示例2:指定程序入口

  • end除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方。
  • 下述代码表示程序的入口在标号start处,也就是mov bx,0
assume cs:code
code segmentdw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987hstart:      mov bx,0mov ax,0mov cx,8s:      add ax,cs:[bx]add bx,2loop smov ax,4c00hint 21hcode endsend start
  • 编译链接debug运行后,发现CS:IP=076A:0010,也能正常执行了

image.png

原理梳理

  • 接下来我们在梳理下大致原理,在单任务系统中,可执行文件中的程序执行过程如下
    • 由其他的程序(Debug、command或其他程序)将可执行文件牛中的程序加载入内存;
    • 设置CS:IP指向程序的第一条要执行的指令(即程序的入口),从而使程序得以运行;
    • 程序运行结束后,返回到加载者。
  • 现在的问题是,根据什么设置CPU的CS:IP指向程序的第一条要执行的指令?这一点,是由可执行文件中的描述信息指明的。我们知道可执行文件由描述信息和程序组成,程序来自于源程序中的汇编指令和定义的数据:描述信息则主要是编译、连接程序对源程序中相关伪指令进行处理所得到的信息。用伪指令end描述了程序的结束和程序的入口。在编译、连接后,由"end start"指明的程序入口,被转化为一个入口地址,存储在可执行文件的描述信息中。
  • 标号不局限于start,也可以使用其他符号
assume cs:code
code segmentdw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987hstart123:      mov bx,0mov ax,0mov cx,8s:      add ax,cs:[bx]add bx,2loop smov ax,4c00hint 21hcode endsend start123
  • 因此,程序的框架大致如下

image.png

3、在代码段中使用栈

  • 将程序中定义的数据逆序存放,利用栈来实现
assume cs:code
code segmentdw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987hdw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0start:      mov ax,csmov ss,axmov sp,30hmov bx,0mov cx,8s:      push cs:[bx]add bx,2loop smov bx,0mov cx,8s0:     pop cs:[bx]add bx,2loop s0mov ax,4c00hint 21hcode endsend start
  • cs:0~cs:f 存储8个字,16个字节,保存数据
  • cs:10~cs:2f 存储16个字,32个字节,作为栈来用

image.png

  • 程序从CS:IP=076A:0030

image.png

  • 执行

image.png

  • 将数据push入栈(这里有个疑问:076A:0010 这行最后的数据为什么变了)

image.png

  • 出栈刚好实现逆序存放(这里有个疑问:076A:0020这行出栈后数据也发生了改变,076A:0010这行也发生了改变)

image.png
image.png

例子1

  • 下面的代码实现把内存0:0~0:F单元中的内容改写程序中的数据(也就是改写CS:0中的程序)
assume cs:codesgcodesg segmentdw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987hstart: 	mov ax,0mov ds,axmov bx,0mov cx,8s:	mov ax,[bx]mov cs:[bx],axadd bx,2loop smov ax,4c00hint 21hcodesg endsend start
  • 运行结果
    • DS=075A:0000 倒076A:0000 存储的事PSP区域
    • 076A:0 000F存储的是dw的8个字 16个字节长度
    • 076A:0010 开始存储程序 mov ax,0

image.png
image.png

例子2

  • 下面的代码实现把内存0:0~0:F单元中的内容改写程序中的数据(也就是改写CS:0中的程序),用栈来实现
assume cs:codesgcodesg segmentdw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987hdw 0,0,0,0,0,0,0,0,0,0start: 	mov ax,csmov ss,axmov sp,24hmov ax,0mov ds,axmov bx,0mov cx,8s:	push [bx]pop cs:[bx]add bx,2loop smov ax,4c00hint 21h
codesg endsend start
  • 初始状态

截屏2023-12-10 16.00.25.png
截屏2023-12-10 16.02.32.png

  • 运行结果

截屏2023-12-10 16.02.32.png

4、数据、代码、栈放入不同的段

  • 前面的例子中,我们将数据、栈和代码都放到一个段里面,编程的时候我们要注意何处是数据,何处是栈,何处是代码,这样有两个问题
    • 1 全部防在一个段中程序显得混乱
    • 2 如果数据、栈、代码需要的空间超过64KB,就不能防在一个段中

例子1:end start指定程序入口

  • 接下来我们用一个例子,将数据、栈、代码放在不同的段中。下例实现将8个字数据倒叙排列
    • ds:data:将段data的地址赋值给ds寄存器
    • ss:stack:将段stack的地址赋值给ss寄存器
    • cs:code:将段code的地址赋值给cs寄存器
assume cs:code,ds:data,ss:stackdata segmentdw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
data endsstack segmentdw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack endscode segmentstart: 	mov ax,stackmov ss,axmov sp,20hmov ax,datamov ds,axmov bx,0mov cx,8s:	push [bx]add bx,2loop smov bx,0mov cx,8s0:	pop [bx]add bx,2loop s0mov ax,4c00hint 21hcode endsend start
  • debug加载程序后,初始状态如下

image.png

  • 程序是从DS=075A开始,默认有256个字节的PSP区域,也就是从075A:0000开始的256字节是不可用的,程序和数据真真是从076A:0000开始
    • 076A:0000 ~ 076A:000F 存储的是要逆序的8个字(16个字节)
    • 076A:0010 ~ 076A:002F 存储的是定义的栈内存,占据16个字(32个字节)
    • 程序是从076A:0030开始的,也就是CS:IP中保存076D:0000

截屏2023-12-07 22.16.49.png

第一步:设置栈顶
  • 代码如下,其中的stack是段名,编译期会将他转换成段地址。也就是前面定义的16个字(32个字节)的栈内存,并且让栈顶指向
start: 	mov ax,stackmov ss,axmov sp,20h
  • 此时栈顶SS:SP=076B:0020,此时栈空,栈顶刚好在076D:0000
  • 运行结果如下

截屏2023-12-07 22.31.53.png

第二步:设置DS
  • data段之前定义了8个字,mov ax,data中data会被转换成段地址,就是这8个字保存的段地址,偏移地址就是0000~0007
      	mov ax,datamov ds,ax
  • debug运行,最终DS=076A

截屏2023-12-07 22.33.28.png

第三步:push入栈
      	mov bx,0mov cx,8s:	push [bx]add bx,2loop s
  • 实现了将076A:0000 ~ 076A:000F的8个字逆序存储倒076A:0020 ~ 076A:002F中

截屏2023-12-07 22.41.50.png

第四步:pop出站
      	mov bx,0mov cx,8s0:	pop [bx]add bx,2loop s0
  • 最终实现了076A:0000 ~ 076A:000F的8个字的逆序存储

截屏2023-12-07 22.45.47.png

第五步:程序退出

image.png

误解
  • 并不是程序中写assume cs:code,ds:data,ss:stack,程序就会自动将cs指向code,ds指向data,ss指向stack,这个完全靠程序中后续具体指令来决定的
  • 段的标号也可以随便定义
assume cs:b,ds:a,ss:ca segmentdw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
a endsc segmentdw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
c endsb segmentd: 	mov ax,stackmov ss,axmov sp,20hmov ax,datamov ds,axmov bx,0mov cx,8s: push [bx]add bx,2loop smov bx,0mov cx,8
s0:	pop [bx]add bx,2loop s0mov ax,4c00hint 21hb endsend d

例子2:段占据空间是16的倍数

  • 代码
assume cs:code,ds:data,ss:stackdata segmentdw 0123h,0456h
data endsstack segmentdw 1h,2h
stack endscode segmentstart: 	mov ax,stackmov ss,axmov sp,10hmov ax,datamov ds,axpush ds:[0]push ds:[2]pop ds:[2]pop ds:[0]mov ax,4c00hint 21h
code endsend start
  • 结论:数据段和栈段在程序加载后实际占据的空间以16个字节为单位,其余补零

image.png

例子3:db的使用

  • 将a段和b端中的数据依次相加,结果存储倒c中去
assume cs:codea segmentdb 1, 2, 3, 4, 5, 6, 7, 8
a endsb segmentdb 1, 2, 3, 4, 5, 6, 7, 8
b endsc segmentdb 0, 0, 0, 0, 0, 0, 0, 0
c endscode segmentstart:mov ax, amov ds, axmov ax, bmov es, axmov bx, 0mov cx, 8s:mov al, ds:[bx]add es:[bx], alinc bxloop smov ax, cmov ds, axmov bx, 0mov cx, 8s0:mov al, es:[bx]add ds:[bx], alinc bxloop s0mov ax,4c00hint 21hcode endsend start
  • 初始化状态
    • 076A:0 f 存储a段的数据
    • 076A:10 1F 存储b段数据
    • 076A:20 2F 存储c段数据

image.png

  • 运行结果

image.png

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

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

相关文章

Lambda表达式规则,用法

Lambda表达式是JDK8新增的一种语法格式 1.作用 简化匿名内部类的代码写法 Lambad用法前提:只能简化函数式接口(一般加有Funcationallnterface)(有且仅有一个抽象方法)的匿名内部类 匿名内部类:(本质是对…

Linux系统编程:进程间通信总结

管道 在Linux中,管道是一种进程间通信方式,它允许一个进程(写入端)将其输出直接连接到另一个进程(读取端)的输入。从本质上说,管道也是一种文件,但它又和一般的文件有所不同。 具体…

Dockerfile 指令的最佳实践

这些建议旨在帮助您创建一个高效且可维护的Dockerfile。 一、FROM 尽可能使用当前的官方镜像作为镜像的基础。Docker推荐Alpine镜像,因为它受到严格控制,体积小(目前不到6 MB),同时仍然是一个完整的Linux发行版。 FR…

win11 powershell conda 激活环境后不显示环境名称

win11 powershell conda 激活环境后不显示环境名称 问题现象解决方法 问题现象 安装 Anaconda 后在 powershell 中激活环境后,命令行前面不显示环境名称 解决方法 在 powershell 中执行 conda init 重新打开 poweshell 出现以下问题,请参考 win11 p…

05-详解调用服务时负载均衡的配置及其原理

负载均衡 负载均衡的原理(通用) LoadBalanced注解用来拦截它所标记的RestTemplate发起的http请求, 底层是利用了一个名为Ribbon的组件来实现负载均衡功能(Cloud高版本已经弃用) LoadBalancerInterceptor的intercept方法会对RestTemplate的请求进行拦截 public class LoadBal…

Java - CAS在Java中的应用、CAS的三大问题

什么是CAS CAS(Compare-and-Swap)是一种乐观锁的实现方式,全称为“比较并交换”,是一种无锁的原子操作。 在并发编程中,我们都知道i操作是非线程安全的,这是因为 i操作不是原子操作 i语句只需要执行一条指令…

111.am40刷机折腾记4-firefly镜像-dp正常显示

1. 平台: rk3399 am40 4g32g 2. 内核:firefly的内核(整体镜像) 版本: linux4.4.194 3. 交叉编译工具 :暂时不编译 4. 宿主机:ubuntu18.04 5. 需要的素材和资料:boot-am40-202…

python的Pandas库介绍

🎈 博主:一只程序猿子 🎈 博客主页:一只程序猿子 博客主页 🎈 个人介绍:爱好(bushi)编程! 🎈 创作不易:喜欢的话麻烦您点个👍和⭐! 🎈…

JavaScript中冷门但有用的String.raw

文章梗概 本文讲解的String.raw,作为JavaScript中的静态方法,用来获取模板字符串的原始字符串形式,需要注意的是与字符串模板搭配时候的事项。 介绍 String.raw() 静态方法是模板字符串的标签函数。它的作用类似于 Python 中的 r 前缀或 C#…

mmdetection里的测速脚本

由于大论文里需要对各个算法进行测速,因此抛开官方文档的使用说明,记录一下我是怎么使用mmdetection里的脚本进行测速的。 mmdetection版本:2.23.0 一、新版本benchmark.py(需要分布式) 打开tools/analysis_tools/b…

在Deepin中安装x11vnc工具并结合内网穿透软件实现远程访问桌面

文章目录 1. 安装x11vnc2. 本地远程连接测试3. Deepin安装Cpolar4. 配置公网远程地址5. 公网远程连接Deepin桌面6. 固定连接公网地址7. 固定公网地址连接测试 x11vnc是一种在Linux系统中实现远程桌面控制的工具,它的原理是通过X Window系统的协议来实现远程桌面的展…

Docker部署开源分布式任务调度平台DolphinScheduler并实现远程访问办公

文章目录 前言1. 安装部署DolphinScheduler1.1 启动服务 2. 登录DolphinScheduler界面3. 安装内网穿透工具4. 配置Dolphin Scheduler公网地址5. 固定DolphinScheduler公网地址 前言 本篇教程和大家分享一下DolphinScheduler的安装部署及如何实现公网远程访问,结合内…