程序的加载和执行(四)——《x86汇编语言:从实模式到保护模式》读书笔记24
程序的加載和執(zhí)行(四)——《x86匯編語言:從實(shí)模式到保護(hù)模式》讀書筆記24
通過本文能學(xué)到什么?
- 怎樣跳轉(zhuǎn)到用戶程序
- 用戶程序通過調(diào)用內(nèi)核過程完成自己的功能
- 怎樣從用戶程序返回到內(nèi)核
接著上篇博文說。
1.跳轉(zhuǎn)到用戶程序
截取內(nèi)核程序的后半部分代碼(文件名: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 ;臨時(shí)保存堆棧指針 576 577 mov ds,ax 578 579 jmp far [0x10] ;控制權(quán)交給用戶程序(入口點(diǎn)) 580 ;堆棧可能切換 581 582 return_point: ;用戶程序返回點(diǎn) 583 mov eax,core_data_seg_sel ;使ds指向核心數(shù)據(jù)段 584 mov ds,eax 585 586 mov eax,core_stack_seg_sel ;切換回內(nèi)核自己的堆棧 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 ;也可以加載并啟動(dòng)其它程序 595 596 hlt 597570:調(diào)用過程load_relocate_program,關(guān)于此過程的詳細(xì)講解,可以參考我的博文:
程序的加載和執(zhí)行(二)——《x86匯編語言:從實(shí)模式到保護(hù)模式》讀書筆記22
程序的加載和執(zhí)行(三)——《x86匯編語言:從實(shí)模式到保護(hù)模式》讀書筆記23
在這個(gè)過程的末尾,有
注意,這時(shí)候ES是用戶頭部段的選擇子,如下圖所示,這句話就是取出頭部偏移0x04處的頭部段選擇子,賦值給AX;
其實(shí)這么做有點(diǎn)繞彎子,因?yàn)镋S自身的值就是頭部段選擇子,所以這句話可以修改為:
也就是說,這個(gè)過程把用戶程序頭部段的選擇子保存在AX中,作為返回參數(shù)。
再?gòu)?fù)習(xí)一下這個(gè)過程的輸入和返回參數(shù)。
572~573:調(diào)用過程put_string,在屏幕上輸出”Done.”,并且換行,表示加載和重定位工作已經(jīng)完成。
369 do_status db 'Done.',0x0d,0x0a,0575:通過把當(dāng)前ESP的值寫入內(nèi)核數(shù)據(jù)段來保存內(nèi)核的棧指針。內(nèi)核數(shù)據(jù)段第378行,保留了一個(gè)雙字,專門用來保存內(nèi)核棧指針。
378 esp_pointer dd 0 ;內(nèi)核用來臨時(shí)保存自己的棧指針當(dāng)內(nèi)核把控制權(quán)交給用戶程序后,用戶程序應(yīng)該切換到自己的棧。當(dāng)從用戶程序返回到內(nèi)核的時(shí)候,內(nèi)核需要從這個(gè)內(nèi)存位置還原自己的棧指針。
577:將用戶頭部段的選擇子傳送到DS,也就是說用戶程序應(yīng)該明白,從內(nèi)核那里接過控制權(quán)的時(shí)候,DS指向了用戶程序的頭部段。
579:如上圖所示,偏移0x10處,綠色部分就是用戶程序的入口。一個(gè)華麗的間接遠(yuǎn)轉(zhuǎn)移,終于跳到了用戶程序。
2.用戶程序的執(zhí)行
截取用戶程序的部分代碼(文件名:c13.asm)。
7 SECTION header vstart=0 8 9 program_length dd program_end ;程序總長(zhǎng)度#0x00 10 11 head_len dd header_end ;程序頭部的長(zhǎng)度#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 ;代碼段長(zhǎng)度#0x18 20 21 data_seg dd section.data.start ;數(shù)據(jù)段位置#0x1c 22 data_len dd data_end ;數(shù)據(jù)段長(zhǎng)度#0x20 40 ;=============================================================================== 41 SECTION data vstart=0 42 43 buffer times 1024 db 0 ;緩沖區(qū) 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 ;邏輯扇區(qū)號(hào)100 71 mov ebx,buffer ;緩沖區(qū)偏移地址 72 call far [fs:ReadDiskData] ;段間調(diào)用 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] ;將控制權(quán)返回到系統(tǒng) 81 82 code_end:用戶程序從第57行開始執(zhí)行。注意,此時(shí)DS指向用戶程序的頭部段。
57~58:把DS賦值給FS,令FS指向頭部段。因?yàn)楹竺嬉頓S指向用戶程序的數(shù)據(jù)段。
60~62:用戶棧段的初始化,并且令ESP=0;這樣就完成了棧的切換。
64~65:令DS指向用戶數(shù)據(jù)段。
67~68:調(diào)用內(nèi)核提供的例程put_string;本質(zhì)上是一個(gè)16位間接絕對(duì)遠(yuǎn)調(diào)用。
當(dāng)內(nèi)核對(duì)用戶程序的符號(hào)表完成重定位后,PrintString處就擁有了內(nèi)核例程put_string的入口地址(低地址處是4字節(jié)的偏移地址,高地址處是2字節(jié)的段選擇子);
80 jmp far [fs:TerminateProgram] ;將控制權(quán)返回到系統(tǒng)執(zhí)行這條指令的時(shí)候,處理器根據(jù)[fs:TerminateProgram]進(jìn)行內(nèi)存尋址,得到偏移地址和段選擇子,然后壓棧CS,再壓棧EIP,再然后把剛才取得的偏移地址和段選擇子賦值給EIP和CS,于是程序的執(zhí)行流就轉(zhuǎn)移到內(nèi)核代碼段中的put_string過程了。說得通俗點(diǎn),就是用戶程序調(diào)用了內(nèi)核的代碼,完成了自己的功能。這有點(diǎn)像Linux中的系統(tǒng)調(diào)用。
如果你對(duì)call far指令不熟悉的話,可以參考我的博文:
call、ret、retf 指令詳解
70~72:調(diào)用內(nèi)核過程read_hard_disk_0,從硬盤讀取一個(gè)扇區(qū)。
read_hard_disk_0: ;從硬盤讀取一個(gè)邏輯扇區(qū);EAX=邏輯扇區(qū)號(hào);DS:EBX=目標(biāo)緩沖區(qū)地址;返回:EBX=EBX+512用戶程序傳入的邏輯扇區(qū)號(hào)是100,當(dāng)然這個(gè)值也可以是別的,這里僅僅是舉個(gè)例子。目標(biāo)緩沖區(qū)地址是數(shù)據(jù)段內(nèi)buffer標(biāo)號(hào)處,這里定義了1024字節(jié)的0。為了用戶程序可以順利運(yùn)行并且能看到效果,我們需要在100扇區(qū)寫點(diǎn)東西。在配書代碼中,提供了一個(gè)文本文件diskdata.txt,它的大小剛好是512字節(jié)。用UltraEdit軟件打開后,如下圖所示:
可以看到,這個(gè)文本剛好是512字節(jié),最后一個(gè)字節(jié)是字符];
77~78:顯示從剛才硬盤讀出來的內(nèi)容。
有一個(gè)細(xì)節(jié)需要說明,內(nèi)核過程put_string要求字符串必須以0終止,不然會(huì)無盡地顯示下去。可是我們這個(gè)文件是以]結(jié)尾的,這是否會(huì)影響顯示呢?答案是不會(huì)。因?yàn)槟繕?biāo)緩沖區(qū)buffer標(biāo)號(hào)處,定義了1024字節(jié)的0,當(dāng)把目標(biāo)文件讀到這里后,前512字節(jié)被覆蓋,字符]后面有512個(gè)0,所以顯示到]為止。
到這個(gè)時(shí)候,用戶程序的工作算是完成了,但是還差最后一步,把控制權(quán)交給系統(tǒng)。
3.返回到內(nèi)核
80:一個(gè)瀟灑的jmp far,跳到內(nèi)核過程return_point,以把控制權(quán)返回給內(nèi)核。注意,jmp和call的區(qū)別是:前者有去無回,后者有去有回。
我們?cè)俅┰降絻?nèi)核的代碼:
583~584:DS重新指向內(nèi)核數(shù)據(jù)段;
586~588:切換到內(nèi)核的棧,并恢復(fù)之前保存的ESP的值。
對(duì)于一個(gè)內(nèi)核來說,接下來應(yīng)該回收前一個(gè)用戶程序所占用的內(nèi)存,并啟動(dòng)下一個(gè)用戶程序。不過因?yàn)槭浅鯇W(xué),我們就不搞那么復(fù)雜了,于是596行,讓處理器處于停機(jī)狀態(tài)。從此世界安靜了。
下一篇博文,我們會(huì)講本章代碼的編譯、運(yùn)行和調(diào)試。敬請(qǐng)期待……
總結(jié)
以上是生活随笔為你收集整理的程序的加载和执行(四)——《x86汇编语言:从实模式到保护模式》读书笔记24的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 爬虫项目之豆瓣电影排行榜前10页
- 下一篇: ubuntu终端显示乱码的解决