您的位置:首页 > 其它

程序的加载和执行(四)——《x86汇编语言:从实模式到保护模式》读书笔记24

2016-03-27 11:05 239 查看

程序的加载和执行(四)——《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行,让处理器处于停机状态。从此世界安静了。

下一篇博文,我们会讲本章代码的编译、运行和调试。敬请期待……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: