日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

操作系统实验报告3:Linux 下 x86 汇编语言2

發布時間:2024/6/3 linux 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 操作系统实验报告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, operand2

CMP指令把第二個操作數和第一個操作數進行比較。在幕后,它對兩個操作數執行減法操作(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, destination

MOVDQA和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的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。