程序的加载和执行(六)——《x86汇编语言:从实模式到保护模式》读书笔记26
程序的加載和執行(六)——《x86匯編語言:從實模式到保護模式》讀書筆記26
通過本文能學到什么?
- NASM的條件匯編
- 用NASM編譯的時候,通過命令行選項定義宏
- Makefile的條件語句
- 在make命令行中覆蓋Makefile中的變量值
- 第13章習題解答
- 復習如何構造棧段描述符
我們接著上篇博文說。
在我修改后的文件中,用到了條件匯編。
比如:
下文對此進行講解。
1.條件匯編
與C預處理器相似,NASM允許對一段源代碼只在某特定條件滿足時進行匯編。
注意,C語言的預處理指令是由#字符開頭的一些命令,NASM編譯器的預處理指令由%開頭。
1.1 只在某特定條件滿足時進行匯編
%if<condition>;if <condition>滿足時接下來的代碼被匯編。......%elif<condition2>; 當 if<condition>不滿足,而<condition2>滿足時,該段代碼被匯編。......%else;當<condition>跟<condition2>都不滿足時,該段代碼被匯編。......%endif%else跟%elif子句都是可選的,也可以使用多于一個的%elif子句。
1.2 測試單行宏是否存在
%ifdef DEBUG...%endif如果我們定義了宏DEBUG,那么省略號處的代碼就會被匯編,否則不會。
以定義宏DEBUG為例,可以用兩種方法。
1.3 在代碼中直接定義宏
%define DEBUG1.4 通過命令行選項
編譯文件的時候,用-d 宏名稱。
-d DEBUG例如:
nasm c13_core.asm -o c13_core.bin -d DEBUG注意:-d可以寫成-D,它和后面宏名之間的空格也可以不要。
例如:
2.關于Makefile
因為加入了條件匯編,所以Makefile和上篇博文中的不太一樣。
上篇博文地址:
程序的加載和執行(五)——《x86匯編語言:從實模式到保護模式》讀書筆記25
修改后的Makefile如下。
DEBUG = 0BIN = c13_mbr.bin c13_core.bin c13.bin empty A_DIR = /home/cjy/a.img C_DIR = /home/cjy/c.imgall:$(BIN).PHONY:all cleanc13_mbr.bin:c13_mbr.asmnasm $< -o $@dd if=$@ of=$(A_DIR)c13_core.bin:c13_core.asm ifeq ($(DEBUG),1)nasm $< -o $@ -D DEBUG elsenasm $< -o $@ endifdd if=$@ of=$(C_DIR) bs=512 seek=1 conv=notruncc13.bin:c13.asmnasm $< -o $@dd if=$@ of=$(C_DIR) bs=512 seek=50 conv=notruncempty:diskdata.txtdd if=$< of=$(C_DIR) bs=512 seek=100 conv=notrunctouch $@clean:$(RM) $(BIN)2.1 條件語句
首先,我們定義了一個變量(也被稱作宏)DEBUG,并給其賦值為0;
需要注意的是以下幾行:
這里用到了Makefile的條件語句。執行make時,會根據運行時的不同情況選擇不同的執行分支。
在這個例子中,當變量DEBUG的值為1時,就執行nasm $< -o $@ -D DEBUG ;
當變量DEBUG的值不為1時,就執行nasm $< -o $@ ;
當我們想編譯帶調試信息的源文件,修改Makefile中DEBUG的值為1就可以了。
但是,還有沒有更簡單的方法呢?
2.2 在命令行中定義變量
當命令行中的變量(宏)定義跟makefile中的定義有沖突時,以命令行中的定義為準。所以,我們可以在執行make的時候加上變量=新值,以覆蓋Makefile文件中的變量值。
對于本文的例子,除了修改Makefile中DEBUG的值為1這種方法外,還有一種方法是在命令行中重新給變量(宏)賦值。
make "DEBUG=1"注意:當沒有空格的時候,引號也可以省略。由于宏定義必須作為單個參數進行傳遞,所以要避免使用空格,所以更妥當的方法是使用引號。
3.第13章習題解答
在本章中,用戶程序只給出建議的棧大小,但并不提供棧空間。現在,修改內核程序和用戶程序,改由用戶程序自行提供棧空間。要求:棧段必須定義在用戶程序頭部之后。
在這里,我給出自己的答案,供學習者參考。
3.1 對于源文件c13.asm的修改
修改有兩處。第一處是:
由于棧空間由用戶程序提供,所以必須指明棧段的匯編地址和大小,這樣內核程序才能有足夠的信息為用戶創建棧段描述符。
第二處是:
因為題目已經要求棧段必須定義在用戶程序頭部之后,所以這里緊接著頭部定義棧空間。
小插曲
編譯這個文件,會報錯。
哦,這是為什么呢?我改成了
stack_len dd (stack_end-0)/(4*1024)依然報同樣的錯誤。
通過查資料,發現有的朋友是這樣回答的:
A label is a relocatable value——its value is modified by the linker/loader.The difference between two labels(in the same section) is a scalar value, and NASM will work with it.
好吧,既然同一個section的兩個標號的差值是標量,那我這樣寫好了:
stack_len dd (stack_end-stack_start)/(4*1024)其實stack_start這個標號之前是沒有的,是我為了做差值專門加上的。
你還別說,這樣寫真的管用,不報錯了。
3.2 對源文件c13_core.asm的修改
左邊的代碼(配書代碼),內核為用戶棧分配內存,然后計算棧的高端物理地址。
右邊的代碼(習題代碼),內核不需要分配內存,僅僅從頭部取出棧的起始地址,然后計算棧的高端物理地址。
這段代碼是我半年前寫的,可是現在讀起來也覺得陌生了。那就解釋一下最關鍵的3行,加深自己的記憶。
此時,DS指向了0-4GB的數據段,EDI中的內容是用戶程序的加載地址。
469:EDX中是用戶程序的加載地址;
470:從用戶頭部偏移0x08處取得section.stack.start,加上用戶程序的加載地址(EDX),就得到了用戶棧的起始地址。如圖所示。
471:棧段描述符中的基地址,應該是棧空間的高端物理地址,所以還要加上棧的大小(在EAX中)。
如果不明白為什么這樣構造棧段描述符,可以參考我的博文:
如何構造棧段描述符
3.3 在Bochs中驗證程序
我們的修改對嗎?“實踐出真知”。
3.3.1 驗證思路
因為棧空間是用戶程序提供的,內核只是根據頭部信息創建對應的棧段描述符。所以我們要驗證的就是棧段描述符和用戶定義的棧是否相符。在上面提到的博文中,我已經詳細推導了如何構造棧段描述符。
用戶程序的頭部結構如下圖所示:
本實驗的用戶程序的符號表有3個符號,所以頭部總長度為0x328;由于用戶程序的加載地址是0x100000,所以棧空間的起始地址為0x100328;又因為棧空間大小定義為0x1000(4KB),所以棧空間的高端物理地址為0x100328+0x1000=0x101328,
這應該就是棧段描述符的基地址。
有效段界限應為0xFFFFFFFF-棧的大小(以字節為單位),即0xFFFFFFFF-0x1000=0xFFFFEFFF,這應該是棧段描述符中的段界限。
3.3.2 驗證結果
在Bochs中運行程序,跑起來后,Ctrl+C暫停程序,輸入info gdt可以查看GTD的信息。我們看到在下圖中:
黃色劃線的描述符與我們的推理相符。所以,我們的修改是正確的。
【end】
總結
以上是生活随笔為你收集整理的程序的加载和执行(六)——《x86汇编语言:从实模式到保护模式》读书笔记26的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 策略产品经理那些事
- 下一篇: diff命令输出格式解读