程序的加载和执行(四)——《x86汇编语言:从实模式到保护模式》读书笔记24
程序的加载和执行(四)——《x86汇编语言:从实模式到保护模式》读书笔记24
通过本文能学到什么?
- 怎样跳转到用户程序
- 用户程序通过调用内核过程完成自己的功能
- 怎样从用户程序返回到内核
接着上篇博文说。
1.跳转到用户程序
截取内核程序的后半部分代码(文件名:c13_core.asm):
570 call load_relocate_program
571
572 mov ebx,do_status
573 call sys_routine_seg_sel:put_string
574
575 mov [esp_pointer],esp ;临时保存堆栈指针
576
577 mov ds,ax
578
579 jmp far [0x10] ;控制权交给用户程序(入口点)
580 ;堆栈可能切换
581
582 return_point: ;用户程序返回点
583 mov eax,core_data_seg_sel ;使ds指向核心数据段
584 mov ds,eax
585
586 mov eax,core_stack_seg_sel ;切换回内核自己的堆栈
587 mov ss,eax
588 mov esp,[esp_pointer]
589
590 mov ebx,message_6
591 call sys_routine_seg_sel:put_string
592
593 ;这里可以放置清除用户程序各种描述符的指令
594 ;也可以加载并启动其它程序
595
596 hlt
597
570:调用过程load_relocate_program
,关于此过程的详细讲解,可以参考我的博文:
程序的加载和执行(二)——《x86汇编语言:从实模式到保护模式》读书笔记22
程序的加载和执行(三)——《x86汇编语言:从实模式到保护模式》读书笔记23
在这个过程的末尾,有
517 mov ax,[es:0x04]
注意,这时候ES
是用户头部段的选择子,如下图所示,这句话就是取出头部偏移0x04处的头部段选择子,赋值给AX
;
其实这么做有点绕弯子,因为ES
自身的值就是头部段选择子,所以这句话可以修改为:
mov ax,es
也就是说,这个过程把用户程序头部段的选择子保存在AX
中,作为返回参数。
再复习一下这个过程的输入和返回参数。
387 load_relocate_program: ;加载并重定位用户程序
388 ;输入:ESI=起始逻辑扇区号
389 ;返回:AX=指向用户程序头部的选择子
572~573:调用过程put_string
,在屏幕上输出”Done.”,并且换行,表示加载和重定位工作已经完成。
369 do_status db 'Done.',0x0d,0x0a,0
575:通过把当前ESP
的值写入内核数据段来保存内核的栈指针。内核数据段第378行,保留了一个双字,专门用来保存内核栈指针。
378 esp_pointer dd 0 ;内核用来临时保存自己的栈指针
当内核把控制权交给用户程序后,用户程序应该切换到自己的栈。当从用户程序返回到内核的时候,内核需要从这个内存位置还原自己的栈指针。
577:将用户头部段的选择子传送到DS
,也就是说用户程序应该明白,从内核那里接过控制权的时候,DS
指向了用户程序的头部段。
579:如上图所示,偏移0x10处,绿色部分就是用户程序的入口。一个华丽的间接远转移,终于跳到了用户程序。
2.用户程序的执行
截取用户程序的部分代码(文件名:c13.asm)。
7 SECTION header vstart=0
8
9 program_length dd program_end ;程序总长度#0x00
10
11 head_len dd header_end ;程序头部的长度#0x04
12
13 stack_seg dd 0 ;用于接收堆栈段选择子#0x08
14 stack_len dd 1 ;程序建议的堆栈大小#0x0c
15 ;以4KB为单位
16
17 prgentry dd start ;程序入口#0x10
18 code_seg dd section.code.start ;代码段位置#0x14
19 code_len dd code_end ;代码段长度#0x18
20
21 data_seg dd section.data.start ;数据段位置#0x1c
22 data_len dd data_end ;数据段长度#0x20
40 ;===============================================================================
41 SECTION data vstart=0
42
43 buffer times 1024 db 0 ;缓冲区
44
45 message_1 db 0x0d,0x0a,0x0d,0x0a
46 db '**********User program is runing**********'
47 db 0x0d,0x0a,0
48 message_2 db ' Disk data:',0x0d,0x0a,0
49
50 data_end:
51
52 ;===============================================================================
53 [bits 32]
54 ;===============================================================================
55 SECTION code vstart=0
56 start:
57 mov eax,ds
58 mov fs,eax
59
60 mov eax,[stack_seg]
61 mov ss,eax
62 mov esp,0
63
64 mov eax,[data_seg]
65 mov ds,eax
66
67 mov ebx,message_1
68 call far [fs:PrintString]
69
70 mov eax,100 ;逻辑扇区号100
71 mov ebx,buffer ;缓冲区偏移地址
72 call far [fs:ReadDiskData] ;段间调用
73
74 mov ebx,message_2
75 call far [fs:PrintString]
76
77 mov ebx,buffer
78 call far [fs:PrintString] ;too.
79
80 jmp far [fs:TerminateProgram] ;将控制权返回到系统
81
82 code_end:
用户程序从第57行开始执行。注意,此时DS
指向用户程序的头部段。
57~58:把DS
赋值给FS
,令FS
指向头部段。因为后面要令DS
指向用户程序的数据段。
60~62:用户栈段的初始化,并且令ESP=0
;这样就完成了栈的切换。
64~65:令DS
指向用户数据段。
67~68:调用内核提供的例程put_string
;本质上是一个16位间接绝对远调用。
24;-------------------------------------------------------------------------------
25 ;符号地址检索表
26 salt_items dd (header_end-salt)/256 ;#0x24
27
28 salt: ;#0x28
29 PrintString db '@PrintString'
30 times 256-($-PrintString) db 0
31
32 TerminateProgram db '@TerminateProgram'
33 times 256-($-TerminateProgram) db 0
34
35 ReadDiskData db '@ReadDiskData'
36 times 256-($-ReadDiskData) db 0
当内核对用户程序的符号表完成重定位后,PrintString
处就拥有了内核例程put_string
的入口地址(低地址处是4字节的偏移地址,高地址处是2字节的段选择子);
80 jmp far [fs:TerminateProgram] ;将控制权返回到系统
执行这条指令的时候,处理器根据[fs:TerminateProgram]
进行内存寻址,得到偏移地址和段选择子,然后压栈CS,再压栈EIP,再然后把刚才取得的偏移地址和段选择子赋值给EIP和CS,于是程序的执行流就转移到内核代码段中的put_string
过程了。说得通俗点,就是用户程序调用了内核的代码,完成了自己的功能。这有点像Linux中的系统调用。
如果你对call far
指令不熟悉的话,可以参考我的博文:
call、ret、retf 指令详解
70~72:调用内核过程read_hard_disk_0
,从硬盘读取一个扇区。
read_hard_disk_0: ;从硬盘读取一个逻辑扇区;EAX=逻辑扇区号;DS:EBX=目标缓冲区地址;返回:EBX=EBX+512
用户程序传入的逻辑扇区号是100,当然这个值也可以是别的,这里仅仅是举个例子。目标缓冲区地址是数据段内buffer
标号处,这里定义了1024字节的0。为了用户程序可以顺利运行并且能看到效果,我们需要在100扇区写点东西。在配书代码中,提供了一个文本文件diskdata.txt,它的大小刚好是512字节。用UltraEdit软件打开后,如下图所示:
可以看到,这个文本刚好是512字节,最后一个字节是字符]
;
77~78:显示从刚才硬盘读出来的内容。
有一个细节需要说明,内核过程put_string
要求字符串必须以0终止,不然会无尽地显示下去。可是我们这个文件是以]
结尾的,这是否会影响显示呢?答案是不会。因为目标缓冲区buffer
标号处,定义了1024字节的0,当把目标文件读到这里后,前512字节被覆盖,字符]
后面有512个0,所以显示到]
为止。
到这个时候,用户程序的工作算是完成了,但是还差最后一步,把控制权交给系统。
3.返回到内核
80:一个潇洒的jmp far
,跳到内核过程return_point
,以把控制权返回给内核。注意,jmp
和call
的区别是:前者有去无回,后者有去有回。
我们再穿越到内核的代码:
582 return_point: ;用户程序返回点
583 mov eax,core_data_seg_sel ;使ds指向核心数据段
584 mov ds,eax
585
586 mov eax,core_stack_seg_sel ;切换回内核自己的堆栈
587 mov ss,eax
588 mov esp,[esp_pointer]
589
590 mov ebx,message_6
591 call sys_routine_seg_sel:put_string
592
593 ;这里可以放置清除用户程序各种描述符的指令
594 ;也可以加载并启动其它程序
595
596 hlt
583~584:DS
重新指向内核数据段;
586~588:切换到内核的栈,并恢复之前保存的ESP
的值。
对于一个内核来说,接下来应该回收前一个用户程序所占用的内存,并启动下一个用户程序。不过因为是初学,我们就不搞那么复杂了,于是596行,让处理器处于停机状态。从此世界安静了。
下一篇博文,我们会讲本章代码的编译、运行和调试。敬请期待……
程序的加载和执行(四)——《x86汇编语言:从实模式到保护模式》读书笔记24相关推荐
- 程序的加载和执行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25
程序的加载和执行(五)--<x86汇编语言:从实模式到保护模式>读书笔记25 前面几篇博文终于把代码分析完了.这篇就来说说代码的编译.运行和调试. 1.代码的编译及写入镜像文件 之前我们都 ...
- 程序的加载和执行(六)——《x86汇编语言:从实模式到保护模式》读书笔记26
程序的加载和执行(六)--<x86汇编语言:从实模式到保护模式>读书笔记26 通过本文能学到什么? NASM的条件汇编 用NASM编译的时候,通过命令行选项定义宏 Makefile的条件语 ...
- 程序的加载和执行(一)——《x86汇编语言:从实模式到保护模式》读书笔记21
程序的加载和执行(一) 本文及之后的几篇博文是原书第13章的学习笔记. 本章主要是学习一个例子,对应的代码分为3个文件: ;代码清单13-1;文件名:c13_mbr.asm;文件说明:硬盘主引导扇区代 ...
- 程序的加载和执行(三)——《x86汇编语言:从实模式到保护模式》读书笔记23
程序的加载和执行(三)--读书笔记23 接着上次的内容说. 关于过程load_relocate_program的讲解还没有完,还差创建栈段描述符和重定位符号表. 1.分配栈空间与创建栈段描述符 462 ...
- html 执行外部js的函数,javascript – Chrome扩展程序:加载并执行外部脚本
我无法在我的chrome扩展程序中加载和执行外部js-script.看起来和 this question一样,但我仍然无法弄清楚为什么它在我的情况下不起作用. 我的想法是,我希望在我的内容脚本中有一些 ...
- 小程序动画加载只执行一次的问题
问题 最近, 想做个小程序的圆盘抽奖出来, 想要实现的效果是点击一次就旋转一次. 不过每次只有第一次点击有效, 再次点击就没有任何动画效果. 代码如下 rotate: function() {// 创 ...
- 任务和特权级保护(一)——《x86汇编语言:从实模式到保护模式》读书笔记27
本文及后面的几篇文章是原书第14章的读书笔记. 1.LDT(局部描述符表) 在之前的学习中,不管是内核程序还是用户程序,我们都是把段描述符放在GDT中.但是,为了有效实施任务间的隔离,处理器建议每个任 ...
- X86汇编语言从实模式到保护模式13:保护模式程序的动态加载和执行
目录 1. 引入保护模式对程序加载与执行的影响 1.1 对应用程序的影响 1.2 对操作系统的影响 1.3 本章程序总体结构 2. MBR加载内核过程分析 2.1 内核头部段分析 2.1.1 内核总长 ...
- 第四回-MBR加载与执行实验
本回我们进行一次实验性的实操环节,机器上电运行相应BIOS代码,然后BIOS加载硬盘的MBR分区内容,并执行MBR代码.本次实验使用Bochs虚拟机模拟整个x86架构的真实机器.实验过程中我们需要为B ...
最新文章
- java中普通代码块,构造代码块,静态代码块的区别及代码示例
- 在线白板,基于socket.io的多人在线协作工具
- 网上有打印按键怎么设置下载_打印机共享怎么设置 如何设置打印机共享【详细攻略】...
- 如何判断线程运行结束
- python 修改字符串 循环_python – 模式匹配并用if else循环替换字符串
- [Java基础]System类的常用方法
- 石家庄学院c语言试题,谁会高级语言程序设计?要求用C语言,帮帮我把,愁死我啦...
- 王昶衡(帮别人名字作诗)
- label标签 for属性
- java sqlite 池_java – SQLite连接池
- C#:System.Data.SQLite数据库介绍
- vs 2015 密钥
- 禁忌搜索算法求解TSP问题python实现
- Python实现简易TCP服务器
- Excel VBA快速去除Excel中的所有公式
- 学好UI设计必备软件
- 定位“良心优品”,国民手机Z5能否让联想重回辉煌?
- SPCA5XX摄像头驱动源码分析
- office之PPT插入页码没有反应
- 利用adb设置安卓http代理