操作系统实验报告3:Linux 下 x86 汇编语言2
操作系統實驗報告3
實驗內容
- 驗證實驗 Blum’s Book: Sample programs in Chapter 06, 07 (Controlling Flow and Using Numbers)
實驗環境
- 架構:Intel x86_64 (虛擬機)
- 操作系統:Ubuntu 20.04
- 匯編器:gas (GNU Assembler) in AT&T mode
- 編譯器:gcc
技術日志
Chapter 06
跳轉指令
跳轉指令使用單一指令碼:
jmp location其中location是要跳轉到的內存地址
- 驗證實驗jumptest.s
1.構建一般可執行程序:
程序的源代碼略。
執行程序命令:
as --32 -o jumptest.o jumptest.s ld -m elf_i386 -o jumptest jumptest.o ./jumptest echo $?執行截圖:
分析:程序先把寄存器eax賦值為1,然后使用跳轉指令跳過把寄存器ebx賦值為10,跳轉到了把寄存器ebx賦值為20的語句,可以看到跳轉確實發生了。
2.使用objdump程序進行反匯編:
執行程序命令:
as --32 -gstabs -o jumptest.o jumptest.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o jumptest jumptest.o objdump -D jumptest執行截圖:
分析:程序開始時使用的第一個內存位置是0x8049001,overhere標簽指向的內存位置是0x8048083。
3.使用gdb運行程序:
執行程序命令:
as --32 -gstabs -o jumptest.o jumptest.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o jumptest jumptest.o gdb -q jumptest執行結果如下:
分析:
在程序的開始位置設置斷點,并運行程序,查看使用的第一個內存位置,顯示在寄存器eip中,這個值是0x8049001,它和objdump輸出中顯示的相同內存位置相對應,單步調試至執行了跳轉指令,再次顯示寄存器eip中的值,這個值是0x8048083,在objdump輸出中顯示,這是overhere標簽指向的位置,說明實現跳轉。
- 驗證實驗calltest.s
執行程序命令:
as --32 -o calltest.o calltest.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o calltest -lc calltest.o ./calltest執行結果如下:
This is section 1 This is section 2 This is section 3執行截圖:
分析:在程序的開始,使用prinif顯示第一個文本行,顯示程序處于什么位位置。下一步, 使用call指令把控制轉移到overhere標簽。在overhere標簽,寄存器esp的值被復制給指針ebp,以便在函數的結尾可以恢復它.再次使用prinf函數顯示第二行文本,然后恢復esp和ebp寄存器。程序的控制返回到緊跟在call指令后面的指令,并且再次使用printf函數顯示第三個文本行。
比較指令
CMP指令的格式如下:
cmp operand1, operand2CMP指令把第二個操作數和第一個操作數進行比較。在幕后,它對兩個操作數執行減法操作(operand2-operand1),比較指令不會修改這兩個操作數,但是如果發生減法操作,就設置EFLAGS寄存器.
- 驗證實驗cmptest.s
程序的源代碼略。
執行程序命令:
as --32 -o cmptest.o cmptest.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o cmptest cmptest.o ./cmptest echo $?執行結果如下:
分析:程序首先把15賦給寄存器eax,把10賦給寄存器ebx,再使用CMP指令比較這兩個寄存器,按照比較的結果,使用JGB指令進行分支操作,因為寄存器ebx的值小于寄存器eax的值,所以不執行條件分支,轉向下一條指令執行,將1存放到寄存器eax中,可以看到,寄存器ebx中的值確實仍是10,沒有進行分支操作。
使用奇偶校檢標志
奇偶校驗標志表明數學運算答案中應該為1的位的數目。可以使用它作為粗略的錯誤檢查系統.確保數學操作成功執行。
如果結果中被設況為1的位的數目是偶數,則設置奇偶校驗位(置1)。如果設置為1的位的數目是奇數,則不設置奇偶校驗位(置0)。
- 驗證實驗paritytest.s
程序的源代碼略。
執行程序命令:
as --32 -o paritytest.o paritytest.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o paritytest paritytest.o ./paritytest echo $?執行結果如下:
分析:減法的結果為1,以二進制表示是00000001。因為為1的位的數目是奇數,所以不設置奇偶校檢位,JP指令不會跳轉到分支,程序退出,并且以減法的結果1作為結果代碼。
為了測試相反的情況,把原程序中的:
subl $3, %ebx改為:
subl $1, %ebx分析:減法的結果是3,以二進制表示是00000011,因為為1的位的數目是偶數,所以設置奇偶校檢位,并且JP指令應該轉到overhere標簽的分支,設置結果代碼為100。
使用符號標志
符號標志使用在帶符號數中,用于表示寄存器中包含的值的符號改變。在帶符號數中,最后一位(最高位)用作符號位。它表明數字表示是負值(設置為1)還是正值(設置為0)。
- 驗證實驗signtest.s
程序的源代碼略。
執行程序命令:
as --32 -o signtest.o signtest.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o signtest -lc signtest.o ./signtest執行結果如下:
分析:signtest.s程序反向遍歷數據數組,使用寄存器edi作為變址,處理每個數組元素時遞減這個寄存器。使用JNS指令檢杳寄存器edi的值什么時候變成負值,如果不是負值,則返回到循環的開頭。
循環指令
循環指令基本格式:
loop address其中address是要跳轉到的程序代碼位置的標簽名稱。循環開始前,必須在寄存器ecx中設置執行迭代的次數。
- 驗證實驗loop.s
程序的源代碼略。
執行程序命令:
as --32 -o loop.o loop.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o loop -lc loop.o ./loop執行結果如下:
分析:循環指令執行100以及以內的正整數的相加指令,利用循環實現直到寄存器ecx的值為0,可以看到,結果為5050。
- 驗證實驗betterloop.s
把loop.s原程序代碼中的:
movl $100, %ecx改為:
movl $0, %ecx執行程序:
分析:將寄存器ecx設置為0時LOOP指令會將其遞減為-1,然后繼續執行下去,顯示錯誤的值。所以需要使用JCXZ指令執行條件分支避免出錯。
執行程序命令:
as --32 -o betterloop.o betterloop.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o betterloop -lc betterloop.o ./betterloop執行結果如下:
分析:結果輸出為0,確實正確的循環。
- 驗證實驗ifthen.c
程序的源代碼略。
執行程序命令:
gcc -m32 -S ifthen.c cat ifthen.s執行結果如下:
分析:實現if-then語句的匯編語言代碼邏輯
- 驗證實驗for.c
程序的源代碼略。
執行程序命令:
gcc -m32 -S for.c cat for.s執行結果如下:
分析:實現for語句的匯編語言代碼邏輯
Chapter 07
使用帶符號整數
- 驗證實驗inttest.s
程序的源代碼略。
執行程序命令:
as --32 -gstabs -o inttest.o inttest.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o inttest inttest.o gdb -q inttest執行結果如下:
分析:調試器假設寄存器ebx和ecx包含帶符號整數,并且使用我們期望的數據類型顯示答案。但是寄存器edx出現了問題。因為調試器試圖把整個寄存器edx作為帶符號整數數據值顯示,所以它假設整個寄存器edx包含一個雙字帶符號整數(32位)。因為寄存器edx只包含一個單字整數(16位),所以解釋出的值是錯誤的。寄存器中的數據仍然是正確的(0xFFB1),但是調試器認為的這個數字表示的內容是錯誤的.
MOVZE指令
MOVZX指令把長度小的無符號整數值(可以在寄存器中,也可以在內存中)傳送給長度大的無符號整數值(只能在寄存器中)。
MOVZX指令格式:
movzx source, destination其中source可以是8位或16位寄存器或者內存位置,destination可以是16位或者32位寄存器。
- 驗證實驗movzxtest.s
程序的源代碼略。
執行程序命令:
as --32 -gstabs -o movzxtest.o movzxtest.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o movzxtest movzxtest.o gdb -q movzxtest執行結果如下:
分析:movzxtest.s程序簡單地把一個大的值存放到寄存器ecx中,然后使用MOVZX指令把低8位 復制到寄存器ebx。因為存放在寄存器ecx中的值使用長度為字的無符號整數表示它(它大于255),所以CL中的值只表示完整值的一部分。
通過輸出寄存器ebx和ecx的十進制值,馬上就能發現無符號整數值沒有被正確地復制,原始值為279,但是新的值只是23。通過按照十六進制顯示值,可以發現為什么會這樣。十六進制格式的原始值為0x0117,它占用一個雙字。MOVZX指令只傳送了寄存器ecx的低位字節,而用0填充了寄存器ebx中剩余的字節,這樣就在寄存器ebx中生成了0x17這個值。
MOVSX指令
MOVSX指令允許擴展帶符號整數并且保留符號,它假設要傳送的字節是帶符號整數格式,并且試圖在傳送過程中保持帶符號整數的值不變。
- 驗證實驗movsxtest.s
程序的源代碼略。
執行程序命令:
as --32 -gstabs -o movsxtest.o movsxtest.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o movsxtest movsxtest.o gdb -q movsxtest執行結果如下:
分析:movsxtest.s程序在寄存器cx中(雙字長度)定義一個負值。然后試圖把這個值復制到寄存器ebx中,程序首先使用零填充寄存器ebx,然后使用MOV指令。下一步,使用MOVSX指令把寄存器cx的值傳送給寄存器eax。
單步運行程序,一直運行到MOVSX指令之后,可以使用調試器的info命令顯示寄存器值。寄存器ecx包含的值是0x0000FFB1,低16位包含的值是0xFFB1,它是帶符號整數格式的-79。當寄存器cx被傳送給寄存器ebx時,寄存器ebx包含的值是0x0000FFB1,它是帶符號整數格式 的65457,這是不對的。
使用MOVSX指令把寄存器cx傳送給寄存器eax之后,寄存器eax包含的值是0xFFFFFFB1,它是帶符號整數格式的-79,MOVSX指令正確地為這個值添加了高位部分的1。
- 驗證實驗movsxtest2.s
程序的源代碼略。
執行程序命令:
as --32 -gstabs -o movsxtest2.o movsxtest2.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o movsxtest2 movsxtest2.o gdb -q movsxtest2執行結果如下:
分析:movsxtest2.s和movsxtest.s完成相同的工作,但是使用的是帶符號整數正值。當寄存器cx被傳送給空的寄存器ebx時。值的格式是正確的(因為高位部分的零對正數是沒有問題的)。另外,MOVSX指令正確地使用零填充了寄存器eax,生成了正確的32位帶符號整數值。
在GNU匯編器中定義整數
.quad命令可以定義一個或者多個帶符號整數值
- 驗證實驗quadtest.s
程序的源代碼略。
執行程序命令:
as --32 -gstabs -o quadtest.o quadtest.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o quadtest quadtest.o gdb -q quadtest執行結果如下:
分析:程序簡單地在標簽data1的位置定義一個包含5個雙子帶符號整數的數組,在標簽data2的位置定義一個包含5個四字帶符號整數的數組,然后退出程序,為了查看執行情況,再次對程序進行匯編并且在調試器中運行它。
首先,顯示調試器認為的data1和data2數組的十進制值,data1數組的如期望,data2數組中的值不是程序中使用的值,這是因為調試器假設這些值是雙字的帶符號整數值。
然后查看內存中標簽data1位置的數組值是如何存儲的,可以看到,每個數組元素使用4個字節,并且按照小尾數格式存放。
接著查看存儲在標簽data2位置的數組值,可以看到,標簽data2位置的數據值是使用四字編碼的,所以每個值使用8個字節,匯編器把這些值放到了正確的位置,但是調試器不知道僅僅通過x/d命令如何顯示這些值,需要使用gd選項顯示這些值。
- 驗證實驗mmxtest.s
程序的源代碼略。
執行程序命令:
as --32 -gstabs -o mmxtest.o mmxtest.s ld -m elf_i386 -o mmxtest mmxtest.o gdb -q mmxtest執行結果如下:
分析:程序定義了兩個數據數組。第一個數組(value1)定義2個雙字帶符號整數,第二個數組(value2)定義8個字節帶符號整數值。使用MOVQ指令把這些值加載到前2個MMX寄存器中。
可以看到,單步運行到MOVQ指令之后,可以顯示MM0和MM1寄存器中的值,顯示寄存器時,調試器不知道寄存器中數據的格式是什么,所以它會顯示所有可能的情況,第一個pprint命令把MM0寄存器的內容顯示為雙字整數值。因為前面的例子使用雙字整數值,所以唯一有意義的顯示格式是int32,它顯示正確的信息。可以使用print/f命令使調試器只生成這一格式。
但是MM1寄存器包含字節整數值,所以不能按照十進制模式顯示它。可以使用print命令的x參數顯示寄存器中的原始字節,可以看到,各個字節被正確地存放到了MM1寄存器中。
傳送SSE整數
MOVDQA和MOVDQU指令的簡單格式:
movdqa source, destinationMOVDQA和MOVDQU指令用于把128位數據傳送到XMM寄存器中,或者在XMM寄存器之間傳送數據,對于對準16個字節邊界的數據,就使用A選項,否則,就使用U選項,其中source和destination可以是SSE128位寄存器或者128位的內存地址。
- 驗證實驗ssetest.s
程序的源代碼略。
執行程序命令:
as --32 -gstabs -o ssetest.o ssetest.s ld -m elf_i386 -o ssetest ssetest.o gdb -q ssetest執行結果如下:
分析:程序定義了兩個包含不同整數數據類型的數據數組。第一個數組(value1)定義4個雙字帶符號整數,第二個數組(value2)定義2個四字帶符號整數值。使用MOVDQU指令把這兩個數據數組傳送到SSE寄存器中。
可以看到,MOVDQU指令執行之后,XMM0和XMM1寄存器包含數據段中定義的數據值。XMM0寄存器包含4個雙字帶符號整數數據值,XMM1寄存器包含2個四字帶符號整數數據值。
傳送BCD值
IA-32指令集包含處理80位打包BCD值的指令。可以使用FBLD和FBSTP指令把80位打包 BCD值加載到FPU寄存器中以及從FPU寄存器獲取這些值。
使用FPU寄存器的方式和使用通用寄存器稍微有些區別。8個FPU寄存器的行為類似于內存中的堆棧區域。可以把值壓入和彈出FPU寄存器池。ST0引用位于堆棧頂部的寄存器。當值被壓
入FPU寄存器堆棧時.它被存放在ST0寄存器中,ST0中原來的值被加載到ST1中。
- 驗證實驗bcdtest.s
程序的源代碼略。
執行程序命令:
as --32 -gstabs -o bcdtest.o bcdtest.s ld -m elf_i386 -o bcdtest bcdtest.o gdb -q bcdtest執行結果如下:
分析:bcdtest.s程序在標簽data1定義的內存位置創建一個表示十進制值1234的簡單的BCD值(記住Intel使用小尾數表示法)。使用FBLD指令把這個值加載到FPU寄存器堆棧的頂部( ST0)。使用FIMUL指令把ST0寄存器和data2所在的內存位置中的整數值相乘。最后,使用FBSTP指令把堆棧中新的值傳送回data1所在的內存位置中。
首先,在執行程序前,使用x/10xb &data1查看data1所在的內存位置值。1234的BCD值被加載到了data1所在的內存位置。
然后,單步執行FBLD指令,并且使用info all命令查看ST0寄存器中的值,ST0寄存器的值應該顯示它加載了十進制值1234,但是這個寄存器的十六進制不是80位打包BCD格式,在FPU中,BCD值被轉換成了浮點表示方式。
接著單步執行下一條指令(FIMUL),并且再次查看寄存器,發現ST0寄存器中的值和2相乘了。
最后把ST0中的值存放回data1所在的內存位置中,使用x/10xb &data1顯示這個內存位置,可以看到,新的值被存放到了data1所在的內存位置,并且轉換回了BCD格式。
傳送浮點值
FLD指令用于把浮點值傳送入和傳送出FPU寄存器。FLD指令的格式是:
fld source其中source可以是32位、64位或者80位內存位置。
- 驗證實驗floattest.s
程序的源代碼略。
執行程序命令:
as --32 -gstabs -o floattest.o floattest.s ld -m elf_i386 -o floattest floattest.o gdb -q floattest執行結果如下:
分析:標簽value1指向存儲在4個字節內存中的單精度浮點值。標簽value2指向存儲在8個字節內存 中的雙精度浮點值。標簽data指向內存中的空緩沖區,它將被用于傳輸雙精度浮點值。
IA-32的FLD指令用于把存儲在內存中的單精度和雙精度浮點數加載到FPU寄存器堆棧中。為了區分這兩種數據長度,GNU匯編器使用FLDS指令加載單精度浮點數,而使用FLDL指令加載雙精度浮點數。
類似地,FST指令用于獲取FPU寄存器堆棧中頂部的值,并且把這個值存放到內存位置中。對于單精度數字,使用的指令是FSTS,雙精度數字使用的指令是FSTL。
首先使用x/f &value1和x/gf &value2查看十進制值。f選項顯示單精度數字,需要使用gf選項顯示雙精度值,顯示四字值,當調試器試圖計算要顯示的值時,已經存在舍入錯誤。
接著,使用x/4xb &value1和x/8xb &value2查看浮點值是如何存儲在內存位置的。
然后,單步運行第一條FLDS指令,使用print $st0查看ST0寄存器的值,可以看到,位于value1內存位置中的值12.340000152587890625被正確存放到了ST0寄存器中。
接下來,單步運行下一條指令,并且查看ST0寄存器中的值,這個值已經被替換為新加載的雙精度值2353.63099999999985812
為了查看對原來加載的值進行了什么處理,查看ST1寄存器,發現當加載新的值時,ST0中的值被下移到了ST1寄存器中。
再查看data標簽的值,單步執行FSTL指令,并且再次查看,發現FSTL指令把ST0寄存器中的值加載到了data標簽指向的內存位置。
- 驗證實驗fpuvals.s
程序的源代碼略。
執行程序命令:
as --32 -gstabs -o fpuvals.o fpuvals.s ld -m elf_i386 -o fpuvals fpuvals.o gdb -q fpuvals執行結果如下:
分析:程序簡單地把各個浮點常量壓入到FPU寄存器堆棧中。值的順序和它們被存放到堆棧中的順序是相反的。
- 驗證實驗ssefloat.s
程序的源代碼略。
執行程序命令:
as --32 -gstabs -o ssefloat.o ssefloat.s ld -m elf_i386 -o ssefloat ssefloat.o gdb -q ssefloat執行結果如下:
分析:程序創建兩個數據數組,每個數組由4個單精度浮點值組成。它們將成為被存儲到XMM寄存器中的打包數據值,還創建了一個數據緩沖區。它有足夠的空間保存4個單精度浮點值(即一個打包的值)。然后程序使用MOVUPS指令在XMM寄存器和內存之間傳送打包單精度浮點值。
可以看到,所以數據都被正確地加載到了XMM寄存器中。v4_float格式顯示使用的打包單精度浮點值。最后是把XMM寄存器的值復制到data位置。可以使用x/4f命令顯示結果。
按照十六進制復查答案,發現內存位置data和內存位置value1中的值是匹配的。
- 驗證實驗sse2float.s
程序的源代碼略。
執行程序命令:
as --32 -gstabs -o sse2float.o sse2float.s ld -m elf_i386 -o sse2float sse2float.o gdb -q sse2float執行結果如下:
分析:存儲在內存中的數值被改為雙精度浮點值。因為程序將傳輸打包值,所以創建了一個包含2個值的數組。
可以看到,對于v2_double數據類型,正確的值已經被傳送到了寄存器中。因為內存位置data包含2個雙精度浮點值,所以使用x/2gf命令顯示存儲在這個內存位置的2個值,發現確實正確的值也被復制到了這里。
轉換指令
- 驗證實驗convtest.s
程序的源代碼略。
執行程序命令:
as --32 -gstabs -o convtest.o convtest.s ld -m elf_i386 -o convtest convtest.o gdb -q convtest執行結果如下:
分析:convtest.s程序在內存位置value1定義一個打包單精度浮點值,在內存位置value2定義一個打包雙字整數值。第一對指令可以比較CVTPS2DQ和CVTTPS2DQ指令的結果。第一條指令執行一般的舍入,第二條指令通過向零方向舍入進行截斷。
按照v4_int32格式,值被正確地顯示出來,正如所見,一般轉換把浮點值124.79舍入為125。但是截斷轉換把它向零方向舍入,使之成為124。內存位置data的值被轉換為打包雙字整數后,可以使用x/4d命令顯示它。可以看到,顯示出了舍入后的整數值。
遇到問題
1.當運行signtest.s時,會發生報錯,顯示Error: can't open signtest.s for reading: No such file or directory
原因是課本提供的原來的代碼的有一行的的寄存器出錯:
add $8, $esp解決方案:這行代碼應該改為:
add $8, %esp執行截圖:
2.有時候按照課本的方式進行gdb調試,比如運行quadtest.s程序,使用x/20b &data1想以十六進制顯示data1數組里的數值時,最后顯示的是十進制的數值。
解決方法:把x/20b &data1改為x/20xb &data1,在代表顯示數值長度的n=20后面加上x,就可以以十六進制顯示數字,其它的程序顯示時也是一樣。
執行截圖:
3.當運行convtest.s時,會發生報錯,顯示Error: symbol `data' is already defined
原因是課本提供的原來的代碼的data命名重復了
data:.lcomm data, 16和
movdqu %xmm0, data都與開頭的.section .data重復
解決方案:代碼應該改為:
data1:.lcomm data, 16和
movdqu %xmm0, data1執行截圖:
4.當運行ifthen.c和for.c時,如果按照課本上給的指令編譯:
gcc -S ifthen.c cat ifthen.sgcc -S for.c cat for.s生成的.s文件是64位的,與課本上的代碼不符。
解決方案:
應該使用32位命令進行編譯:
gcc -m32 -S ifthen.c cat ifthen.sgcc -m32 -S for.c cat for.s這樣生成的.s文件就是32位的,與課本上的代碼相符。
總結
以上是生活随笔為你收集整理的操作系统实验报告3:Linux 下 x86 汇编语言2的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 操作系统实验报告2:Linux 下 x8
- 下一篇: 操作系统实验报告4:Linux 下 x8