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

歡迎訪問 生活随笔!

生活随笔

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

linux

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

發布時間:2024/6/3 linux 58 豆豆
生活随笔 收集整理的這篇文章主要介紹了 操作系统实验报告4:Linux 下 x86 汇编语言3 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

操作系統實驗報告4

實驗內容

  • 驗證實驗 Blum’s Book: Sample programs in Chapter 08, 10 (Basic Math Functions and Using Strings)

實驗環境

  • 架構:Intel x86_64 (虛擬機)
  • 操作系統:Ubuntu 20.04
  • 匯編器:gas (GNU Assembler) in AT&T mode
  • 編譯器:gcc

技術日志

Chapter 08

加法指令

ADD指令用于把兩個整數相加,指令格式如下:

add source, destination

其中source可以是立即值、內存位置或者寄存器。destination參數可以是寄存器或者內存位置中存儲的值(但是不能同時使用內存位置作為源和目標)。加法的結果存放在目標位置。

ADD指令可以將8位、16位或者32位值相加。和其他GNU匯編器指令一樣,必須通過在ADD助記符的結尾添加b(用于字節)、w(用于字)或者l(用于雙字)來指定操作數的長度。

  • 驗證實驗addtest1.s

在程序的源代碼的最后:

movl $1, %eax

這一行前,加上:

end:movl $1, %eax

便于進行斷點調試。

執行程序命令:

as --32 -gstabs -o addtest1.o addtest1.s ld -m elf_i386 -o addtest1 addtest1.o gdb -q addtest1

執行截圖:

分析:和課本預期的輸出結果一致,對無符號數的加法執行正確。

  • 驗證實驗addtest2.s

在程序的源代碼的最后:

movl $1, %eax

這一行前,加上:

end:movl $1, %eax

便于進行斷點調試。

執行程序命令:

as --32 -gstabs -o addtest2.o addtest2.s ld -m elf_i386 -o addtest2 addtest2.o gdb -q addtest2

執行截圖:

分析:和課本預期的輸出結果一致,對帶符號整數的加法執行也正確。

驗證實驗addtest3.s

執行程序命令:

as --32 -gstabs -o addtest3.o addtest3.s ld -m elf_i386 -o addtest3 addtest3.o ./addtest3 echo $?

執行結果如下:

改動寄存器的值,使加法不產生進位,把原程序代碼中的:

movb $190, %bl movb $100, %al

改為:

movb $190, %bl movb $10, %al

執行結果如下:

分析:程序對存儲在AL和BL寄存器中的2字節無符號整數值執行簡單的加法。如果加法操作造成進位,則把進位標志設置為1,并且JC指令將跳轉到標簽over。程序的結果代碼要么是加法的結果,要么就是0值(如果結果超過255)。

第一個程序設置寄存器值使加法產生進位,運行程序,然后使用echo命令查看結果代碼,結果代碼為0,表示正確檢測到了進位情況。

第二個程序改動寄存器的值,使加法不產生進位,運行程序之后,加法沒有產生進位,沒有跳轉,并且加法的結果被設置為結果代碼200。

  • 驗證實驗addtest4.s

執行程序命令:

as --32 -o addtest4.o addtest4.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o addtest4 -lc addtest4.o ./addtest4

執行截圖:

分析:程序試圖把兩個大的負數相加,這造成了溢出情況。JO指令用于檢查溢出并且把控制傳遞到標簽over。運行程序,輸出0,這表明檢測到了溢出情況。

把原程序代碼中的:

movl $-1590876934, %ebx movl $-1259230143, %eax

改為:

movl $-190876934, %ebx movl $-159230143, %eax

執行截圖:

分析:修改MOVL指令,使兩個值相加不產生溢出情況,就會看到加法的結果。

ADC指令

使用ADC指令處理非常大的、不能存放到雙字數據長度中的帶符號或者無符號整數的相加。

ADC指令的格式如下:

adc source, destination

其中source可以是立即值或者8位、16位或者32位寄存器或內存位置值,destination可以是8位、16位或者32位寄存器或內存位置值。

  • 驗證實驗adctest.s

在原程序代碼中的:

addl %ebx, %edx adcl %eax, %ecx pushl %ecx pushl %edx

加上:

allmov:addl %ebx, %edxadcl %eax, %ecx alladd:pushl %ecxpushl %edx

便于加斷點調試

執行程序命令:

as --32 -gstabs -o adctest.o adctest.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o adctest -lc adctest.o gdb -q adctestas --32 -gstabs -o adctest.o adctest.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o adctest -lc adctest.o ./adctest

執行結果如下:

分析:程序把兩個64位值相加,一個值保持在EAX:EBX寄存器組合中,另一個值保持在ECX:EDX寄存器組合中。

可以看到,執行加法操作之后,64位整數的十六進制值被加載到了寄存器中,ECX:EDX寄存器對包含結果數據,使用printf函數也顯示出了十進制形式的結果。

減法指令

SUB指令的格式如下:

sub source, destination

其中從destination的值中減去source的值,結果存儲在destination操作數的位置。source可以是立即值或者8位、16位或者32位寄存器或內存位置值,destination可以是8位、16位或者32位寄存器或內存位置值。

  • 驗證實驗subtest1.s

在原程序代碼中的:

subl %eax, data
movl $1, %eax

加上:

end:subl %eax, datamovl $1, %eax

便于加斷點調試

執行程序命令:

as --32 -gstabs -o subtest1.o subtest1.s ld -m elf_i386 -o subtest1 subtest1.o gdb -q subtest1

執行結果如下:

分析:內存位置data1的值(40)減去EAX寄存器中的值(-30),得到正確的結果70。

減法操作中的進位和溢出

  • 驗證實驗subtest2.s

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o subtest2.o subtest2.s ld -m elf_i386 -o subtest2 subtest2.o ./subtest2 echo $?

執行結果如下:

分析:對于無符號整數,從2中減去5,可以看到,當結果小于0時,進位標志被設置為1,發生跳轉,程序的結果代碼為0,檢查EBX寄存器中的值,發現為-3,盡管它被“認為是”無符號整數,但是由程序負責確定值是否超出了無符號(或者帶符號)值的范圍,只能使用進位標志確定無符號整數的減法產生負數結果的情況,如果執行帶符號整數的減法,進位標志是沒有用處的,因為結果長常常可能是負值,所以要依靠溢出標志來判斷到達了數據長度界限的情況。

依靠溢出標志來判斷到達了數據長度界限的情況

  • 驗證實驗subtest3.s

程序的源代碼略。

執行程序命令:

as --32 -o subtest3.o subtest3.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o subtest3 -lc subtest3.o ./subtest3

執行結果如下:

分析:程序演示從存儲在EBX寄存器中的負值中減去存儲在EAX寄存器中的正值,生產一個超過32位EBX寄存器范圍的值。JO指令用于檢測溢出標志,并且把程序轉到over,把輸出設置位0。

可以看到,溢出情況被檢測到,并且執行JO指令和進行跳轉。

為了測試程序在相反的情況是否正常工作,可以把EAX寄存器的值改為負值,把原程序中的:

movl $1259230143, %eax

改為:

movl $-1259230143, %eax

分析:程序減去負數生成一個絕對值更小的負數,它在數據長度的界限之內,沒有設置溢出標志。

SBB指令

可以使用進位情況幫助執行大的無符號整數值的減法操作,SBB指令在多字節減法操作中利用進位和溢出標志實現跨越數據邊界的借位特性。

SBB指令的格式如下:

sbb source, destination

其中進位位被添加到source值,從destination的值中減去source的值,結果存儲在destination操作數的位置。source可以是8位、16位或者32位寄存器或內存位置值,destination可以是8位、16位或者32位寄存器或內存位置值,不能同時使用內存位置作為源和目標值。

  • 驗證實驗sbbtest.s

執行程序命令:

as --32 -o sbbtest.o sbbtest.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o sbbtest -lc sbbtest.o ./sbbtest

執行結果如下:

分析:結果與課本上預期的結果一樣,得到了正確的減法值。

乘法指令

MUL指令用于兩個無符號整數相乘,MUL指令的格式如下:

mul source

其中source可以是8位、16位或者32位寄存器或內存值。

無符號整數乘法需求:

源操作數長度目標操作數目標位置
8位ALAX
16位AXDX:AX
32位EAXEDX:AX
  • 驗證實驗multest.s

在原程序代碼中的:

pushl %edx pushl %eax

加上:

aftermul:pushl %edxpushl %eax

便于加斷點調試

執行程序命令:

as --32 -gstabs -o multest.o multest.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o multest -lc multest.o gdb -q multest ./multest

執行結果如下:

分析:程序演示兩個32位無符號整數的乘法操作,并且從EDX:EAX寄存器獲得結果。

寄存器對組合EDX:EAX生產結果值,這個值被存儲在內存位置result中,并且通過printf函數顯示出來,與課本上的預期的結果一致。

帶符號整數乘法

MUL指令只能用于無符號整數,而IMUL指令可以用于帶符號和無符號整數、但是必須小心結果不使用目標的最高有效位,對于較大的值,IMUL指令只對帶符號整數是合法的。為了應付比較復雜的情況,IMUL指令而3種不同的指令格式:

IMUL指令的第一種格式使用一個操作數,其行為和MUL指令完全一樣:

imul source

source操作數可以是8位、16位或者32位寄存器或內存中的值,它與位于AL、AX或者EAX寄存器(取決于源操作數的長度)中的隱含操作數相乘。然后,結果被存放到AX寄存器、DX:AX寄存器對或者EDX:EAX寄存器對中。

IMUL指令的第二種格式允許指定EAX寄存器之外的目標操作數:

imul source, destination

其中source可以是16位或者32位寄存器或內存中的值,destination必須是16位或者32位通用寄存器。這種格式允許指定把乘法操作的結果存放到哪個位置(而不是強制使用AX和DX寄存器)。

這種格式的缺陷在于乘法操作的結果被限制為單一目標寄存器的長度(非64位結果)。使用這種格式時必須非常小心,不要溢出目標寄存器。

IMUL指令的第三種格式允許指定3個操作數:

imul multiplier, source destination

其中multiplier是一個立即值,source是16位或者32位寄存器或內存中的值,destination必須是通用寄存器。這種格式允許執行一個值(source)和一個帶符號整數(multiplier)的快速乘法操作,把結果存儲到通用寄存器(destination)中.

  • 驗證實驗imultest.s

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o imultest.o imultest.s ld -m elf_i386 -o imultest imultest.o gdb -q imultest

執行結果如下:

分析:EAX寄存器包含EDX寄存器的值(400)和立即值2相乘得到的結果。ECX寄存器包含EBX寄存器的值(10)和最初加載到ECX寄存器中的值(-35)相乘的結果。注意,結果作為帶符號整數值被存放到ECX寄存器中。

帶符號整數乘法檢查溢出

當使用帶符號整數和IMUL指令時,總是要檢查結果中的溢出。一種方式是使用JO指令檢查溢出標志。

  • 驗證實驗imultest2.s

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o imultest2.o imultest2.s ld -m elf_i386 -o imultest2 imultest2.o gdb -q imultest2

執行結果如下:

分析:程序杷兩個值傳送到16位寄存器中(AX和CX),然后使用16位IMUL指令將它們相乘。設置結果會導致16位寄存器溢出,并且JO指令跳轉到標簽over,這里退出程序,帶有結果代碼1。

修改加載到寄存器中的立即數值,使結果小于65535,把原程序中的:

movw $680, %ax

改為:

movw $60, %ax

執行結果如下:

分析:如果修改加載到寄存器中的立即數值,使結果小于65535, IMUL指令就不會把溢出標志設置為1,不會執行JO指令,程序退出,帶有結果代碼0。

除法指令

DIV指令用于無符號整數的出發操作。DIV指令的格式如下:

div divisor

其中divisor(除數)是隱含的被除數要除以的值,它可以是8位、16位或者32位寄存器或內存中的值。在執行DIV指令之前,被除數必須已經存儲到了AX寄存器(對于16位值)、DX:AX寄存器對(對于32位值)或者EDX:EAX寄存器對(對于64位值)。

允許的除數的最大值取決于被除數的長度。對于16位被除數,除數只能是8位;對于32位被除數,除數只能是16位;對于64位被除數,除數只能是32位。

除法操作的結果是兩個單獨的數字:商和余數。這兩個值都存儲在被除數值使用的相同寄存器中。下表列出了其設置的情況。

被除數被除數長度商余數
AX16位ALAH
DX:AX32位AXDX
EDX:EAX64位EAXEDX

這就是說,當除法操作完成時,會丟失被除數,所以要確保這不是這個值的唯一拷貝(除非在除法操作之后就不需要被除數的值了)。還要記住,結果會改變DX或者EDX寄存器的值,所以也要小心其中存儲的內容。

  • 驗證實驗divtest.s

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o divtest.o divtest.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o divtest -lc divtest.o ./divtest

執行結果如下:

分析:程序把一個64位四字按數加栽到EDX:EAX寄存器對中,然后使用一個存儲在內存中的32位雙字整數除這個值。32位的商值存儲在一個內存位置中,32位的余數值存儲在另一個內存位置中。

修改除數的值為0,檢測除以0的情況,把原程序中的:

divisor:.int 25

改為:

divisor:.int 0

執行結果如下:

分析:發生除以0的情況時會產生錯誤,系統會產生中斷,需要進行檢查。

移位乘法

為了使整數乘以2的乘方,必須把值向左移位。可以使用兩個指令使整數值向左移位,SAL(向左算術移位)和SHL(向左邏輯移位)。這兩個指令執行相同的操作,并且是可以互換的。它們有3種不同格式:

sal destination sal %c1, destination sal shifter, destination

第一種格式把destination的值向左移1位,這等同于使值乘以2。

第二種格式把destination的值向左移動CL寄存器中指定的位數。

最后一個版本把destination的值向左移動shifter值指定的位數。在所有的格式中,目標操作數可以是8位、16位或者32位寄存器或內存中的值。

  • 驗證實驗saltest.s

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o saltest.o saltest.s ld -m elf_i386 -o saltest saltest.o gdb -q saltest

執行結果如下:

分析:一開始,十進制值10被加載到EBX寄存器中。第一條SAL指令把它移動1位(使之乘以2,結果為20)。第二條SAL指令把它移動2位(使之乘以4,結果為80),第三條SAL指令把它再移動2位(使之乘以4,結果為320)。value1位置中的值(25)被移動1位(使之為50),然后再移動2位(使之為200)。

把二進制結果轉化為不打包BCD格式的指令(AAA為調整加法操作的結果)

  • 驗證實驗aaatest.s

在原程序代碼中的:

movl $1, %eax movl $0, %ebx

加上:

end:movl $1, %eaxmovl $0, %ebx

便于加斷點調試

執行程序命令:

as --32 -gstabs -o aaatest.o aaatest.s ld -m elf_i386 -o aaatest aaatest.o gdb -q aaatest

執行結果如下:

分析:第三次執行ADC指令后,AL寄存器中包含的值為10,顯示9和1的二進制加法結果為10。

執行AAA指令后,AX寄存器的值為0x100 256,它顯示AH寄存器中的不打包值為1,AL寄存器中的值為0。1被帶人到下一位的值的加法操作。

最后,結果按照不打包BCD格式存放到內存位置sum中。

調整SUB或者SBB指令

  • 驗證實驗dastest.s

執行程序命令:

as --32 -gstabs -o dastest.o dastest.s ld -m elf_i386 -o dastest dastest.o gdb -q dastest

執行結果如下:

分析:程序把第一個打包BCD值加載到AL寄存器中(每次一個十進制位)。然后使用SBB指令從它減去第二個打包BCD值。這樣,前一次減法操作留下的任何進位位都會被考慮在內。然后使用DAS指令把結果轉換為將存儲在內存位置result中的打包BCD格式。ECX寄存器用于控制必須經過的循環次數(每個打包BCD字節一次)。轉換之后,如果留有剩下的進位位,就把它存放在結果值中。

第一個減法操作之后,EAX寄存器的值為0x0e 14

執行DAS指令之后,這個值改變為0x08 8

它表示結果的第一個十進制位。

  • 驗證實驗cpuidtest.s

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o cpuidtest.o cpuidtest.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o cpuidtest -lc cpuidtest.o ./cpuidtest

執行結果如下:

分析:程序首先使用PUSHFL指令把EFLAGS寄存器的值保存到堆棧頂部。然后,使用POPL指令把EFLAGS值讀取到EAX寄存器中。

下一個步驟演示如何使用XOR指令設置寄存器的一位。使用MOVL指令把EFLAGS值的拷貝
保存到EDX寄存器中,然后使用XOR指令設置ID位(仍然在EAX寄存器中)為值1。XOR指令使用一個設置了ID位的立即值。EAX寄存器經過異或操作之后,就確保ID位被設置為1了。下一個步驟把新的EAX寄存器值壓入到堆棧中,然后使用POPFL指令把它存儲在EFLAGS寄存器中。

現在必須確定是否成功地設置了ID標志。再一次使用PUSHFL指令把EFLAGS寄存器壓入堆棧,然后使用POPL指令把它彈出到EAX寄存器中。這個值和原始的EFLAGS值(先前存儲在EDX寄存器中)進行XOR操作,查看值改變成了什么。

最后,使用TEST指令查看ID標志位是否改變了。如果是,那么EAX中的值就不為零,然后使用JNZ指令進行跳轉,輸出適當的消息。

Chapter 10

傳送字符串

創建MOVS指令是為了把字符串從一個內存位置傳送到另一個內存位置,MOVS指令有3種格式:

  • MOVSB:傳送單一字節
  • MOVSW:傳送一個字(2字節)
  • MOVSL:傳送一個雙字(4字節)

MOVS指令使用隱含的源和目標操作數。隱含的源操作數是ESI寄存器。它指向源字字符串的內存位置。隱含的目標操作數是EDI寄存器。它指向字符串要被復制到的目標內存位置。記住操作數順序的好方法是ESI中的S代表源(source),而EDI中的D代表目標(destination)。

使用GNU匯編器時,有兩種方式加載ESI和EDI值。第一種方式是使用間接尋址。通過在內存位置標簽前面添加$,內存位置的地址被加載到了ESI或者EDI寄存器中:

movl $output, %edi

這條指令把output標簽的32位內存位置傳送給EDI寄存器。

指定內存位置的另一種方式是LEA指令。LEA指令加載一個對象的有效地址。因為Linux使用32位值引用內存位置,所以對象的內存地址必須存儲在32位的目標值中,源操作數必須指向一個內存位置,比如.data段中使用的標簽。指令

leal output, %edi

把output標簽的32位內存位置加載到EDI寄存器中。

  • 驗證實驗movstest1.s

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o movstest1.o movstest1.s ld -m elf_i386 -o movstest1 movstest1.o gdb -q movstest1

執行結果如下:

分析:程序把內存位置value1的位置加載到ESI寄存器中。把output內存位置的位置加載到EDI寄存器中。當執行MOVSB指令時,它把1字節的數據從value1位置傳送到output位置。因為在.bss段中聲明output變量,所以存放在這里的任何字符串數據的結尾會被自動地加上空字符。

可以看到,MOVSB指令把“T”從value1位置傳送到output位置。但是,無須改變ESI和EDI寄存器,當運行MOVSW指令時.它沒有傳送"Th"(字符串的前2個字節),而是把"hi"從value1位置傳送到了output位置。然后MOVSL指令繼續添加下4個字節的值。

  • 驗證實驗movstest2.s

使用STD指令時,ESI和EDI寄存器在每條MOVS指令執行之后遞減,所以它們應該指向字
符串的末尾,而不是開頭。

在原程序代碼中的:

movl $1, %eax movl $0, %ebx

加上:

end:movl $1, %eaxmovl $0, %ebx

便于加斷點調試

執行程序命令:

as --32 -gstabs -o movstest2.o movstest2.s ld -m elf_i386 -o movstest2 movstest2.o gdb -q movstest2

執行結果如下:

分析:這一次,value1內存位置的地址位置被存放到EAX寄存器中,測試字符串的長度(減去1是因為字符串從地址0開始)與它相加。這個值被存放到ESI寄存器中。這使ESI寄存器指向測試字符串的末尾。對EDI進行相同的操作,使它指向內存位置output的末尾,STD指令用于設置DF標志,使ESI和EDI寄存器在每條MOVS指令執行之后遞減。

3條MOVS指令在兩個字符串位置之間傳送1、2和4個字節的數據,output位置的字符串從字符串的末尾開始填充,但是3條MOVS指令執行之后,只有4個內存位置被填充了。在向前移動的movstest1.s程序中,使用相同的3條指令卻填充了7個內存位置。這是因為盡管ESI和EDI寄存器向后計數。MOVSW和MOVSL指令還是按照向前的順序獲得內存位置。當MOVSB指令完成時,它使ESI和EDI寄存器遞減1,但是M0VSW指令獲得兩個內存位置。同樣,當M0VSW指令完成時,它使ESI和EDI寄存器遞減2,但是MOVSL指令獲得4個內存位置。

  • 驗證實驗movstest3.s

把MOVSL指令放在循環中,通過把ECX寄存器設置為字符串的長度來進行控制。

在原程序代碼中的:

movl $1, %eax movl $0, %ebx

加上:

end:movl $1, %eaxmovl $0, %ebx

便于加斷點調試

執行程序命令:

as --32 -gstabs -o movstest3.o movstest3.s ld -m elf_i386 -o movstest3 movstest3.o gdb -q movstest3

執行結果如下:

分析:ESI和EDI寄存器被設置為源和目標內存位置。ECX寄存器被設置為要傳送的字符串的長度。循環部分持續地執行MOVSB指令。直到整個字符串傳送完畢。可以看到,查看內存位置output的字符串值與課本上的預期輸出一樣。

REP前綴

REP指令用于按照特定次數重復執行字符串指令,由ECX寄存器中的值進行控制。這和使用循環類似,但是不需要額外的LOOP指令。REP指令重復地執行緊跟在它后面的字符串指令,直到ECX寄存器中的值為零。

  • 驗證實驗reptest1.s

MOVSB指令可以和REP指令一起使用,每次1字節地把字符串傳送到另一個位置。

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o reptest1.o reptest1.s ld -m elf_i386 -o reptest1 reptest1.o gdb -q reptest1

執行結果如下:

分析:要傳送的字符串長度被加載到ECX寄存器中,然后使用REP指令執行MOVSB指令23次(字符串的長度),每次傳送1字節的數據。在調試器中單步執行程序時,REP指令仍然只被算作一個指令步驟,而不是23個。

雖然單步執行指令時,REP指令只占用一個步驟,但是在這個步驟之后,源字符申的所有23個字節都被傳送到了目標字符串位置。

  • 驗證實驗reptest2.s

也可以使用MOVSW和MOVSL指令在每次迭代中傳送1字節以上的數據。

如果使用MOVSW和MOVSL指令,ECX寄存器就應該包含遍歷字符串所需的迭代次數。例如,如果要傳送8字節的字符串,如果使用MOVSB指令的話,就需要把ECX設置為8,使用MOVSW指令就設置為4,使用MOVSL指令就設置為2。

使用MOVSW和MOVSL指令遍歷字符串時,小心不要超出字符串的邊界。

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o reptest2.o reptest2.s ld -m elf_i386 -o reptest2 reptest2.o gdb -q reptest2

執行結果如下:

分析:程序通過6次循環,傳送每個5字節的數據塊。但是源字符串的整個數據長度并不正好是4的倍數。最后一次執行MOVSL指令時,它不僅獲得value1字符串的末尾,而且會錯誤地獲得定義的下一個字符串一字節的數據。

可以看到,output字符串的輸出包含value2字符串的第一個字符,它被添加到了value1字符串中,是錯誤的結果。

  • 驗證實驗reptest3.s

當知道字符串的長度時,就容易執行整數除法以便確定字符串中包含多少個雙字。然后余數可以使用MOVSB指令進行傳送(迭代次數應該小于3次)。

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o reptest3.o reptest3.s ld -m elf_i386 -o reptest3 reptest3.o gdb -q reptest3

執行結果如下:

分析:程序把源和目標內存位置加載到ESI和EDI寄存器中,但是然后把字符串長度值加載到AX寄存器中。為了使字符串長度被4整除,使用SHR指令把長度值向右移動2位(這和被4整除相同),再把商值加載到ECX寄存器中。然后使REP MOVSL指令組合執行這個值指定的次數。完成之后,確定余數值。

如果除數是2的乘方,可以通過從除數中減去1,然后把它和被除數進行AND操作快速地獲得余數。然后把這個值加載到ECX寄存器中,執行REP MOVSB指令組合來傳送剩余的字符。

可以看到,首先,在執行REP MOVSL指令組合之后停止程序,顯示內存位置buffer的內容:

"This is a test of the conversion program"

注意,前40個字符從源字符串傳送到了目標字符串。下面,執行REP MOVSB指令組合,并且再次查看內存位置buffer的內容:

"This is a test of the conversion program!\n"

字符串中的最后兩個字符被成功地傳送了。

  • 驗證實驗reptest4.s

向后執行和向前執行REP指令都是可以的。可以把DF標志設置為對字符串進行向后處理,按照相反的方向在內存位置之間傳送它。

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o reptest4.o reptest4.s ld -m elf_i386 -o reptest4 reptest4.o gdb -q reptest4

執行結果如下:

分析:程序把源和目標字符串的末尾加載到ESI和EDI寄存器中,然后使用STD指令設置DF標志。這使目標字符串按照相反的順序被存儲。

STOS指令

使用LODS指令把字符串值存放到EAX寄存器之后,可以使用STOS指令把它存放到另一個
內存位置中。和LODS指令類似,根據要傳送的數據的數量,STOS指令有3種格式:

  • STOSB: 存儲AL寄存器中一個字節的數據
  • STOSW: 存儲AX寄存器中一個字(2字節)的數據
  • STOSL: 存儲EAX寄存器中一個雙字(4個字節)的數據

STOS指令使用EDI寄存器作為隱含的目標操作數。執行STOS指令時,它按照使用的數據長
度遞增或者遞減EDI寄存器的值。

STOS指令可以和REP指令一起使用,多次把一個字符串值復制到大型字符串值中。

  • 驗證實驗stostest1.s

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o stostest1.o stostest1.s ld -m elf_i386 -o stostest1 stostest1.o gdb -q stostest1

執行結果如下:

分析:程序吧ASCII空格字符加載到AL寄存器中,然后把它復制到buffer標簽指向的內存位置中256次。

可以看到,通過LODSB指令把空格字符加載到了AL寄存器中。在STOSB指令執行之前,內存位置buffer包含0。STOSB指令執行之后,內存位置buffer包含的都是空格。

構建自己的字符串函數

STOS和LODS 指令可以用于各種字符串操作,通過使ESI和EDI寄存器指向相同的字符串,可以對字符串執行簡單的操作。可以使用LODS指令遍歷字符串,一次把一個字符加載到AL寄存器中,對這個字符執行某些操作,然后使用STOS指令把新的字符加載回字符串中。

  • 驗證實驗convert.s

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o convert.o convert.s ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o convert -lc convert.o ./convert

執行結果如下:

分析:程序把ASCII字符串都轉換為大寫字母。

程序把內存位置string1加載到ESI和EDI寄存器中,把字符串長度加載到ECX寄存器中。然后程序使用LOOP指令對字符串中的每個字符執行字符檢查。程序進行字符檢查的方法是,把每個字符加載到AL寄存器中,并且判斷它是否小于字母a的ASCII值(0x61),或者大于字母z的ASCII值(0x7a)。如果字符在這個范圍之內,那么它必然是小寫字母,必須通過減去0x20把它轉換為大寫字母。

不管是否對字符進行了轉換,都必須把它存放回字符串,以便保持ESI和EDI寄存器的同步。對每個字符都運行STOSB指令,然后代碼向后循環到下一個字符,直到完成字符串中所有字符的處理。

可以看到,確實所有的字母都被轉換成了大寫字母。

比較字符串

CMPS指令系列用于比較字符串值。和其他字符串指令一樣,CMPS指令有3種格式:

  • CMPSB:比較字節值
  • CMPSW:比較字(2字節)值
  • CMPSL:比較雙字(4字節)值

和其他字符串指令一樣,隱含的源和目標操作數的位置同樣存儲在ESI和EDI寄存器中。每次執行CMPS指令時,根據DF標志的設置,ESI和EDI寄存器按照被比較的數據的長度遞增或者遞減。

CMPS指令從源字符串中減去目標字符串,并且適當地設置EFLAGS寄存器的進位、符號、
溢出、零、奇偶校驗和輔助進位標志。CMPS指令執行之后,可以根據字符串的值,使用一般的條件跳轉指令跳轉到分支。

  • 驗證實驗cmpstest1.s

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o cmpstest1.o cmpstest1.s ld -m elf_i386 -o cmpstest1 cmpstest1.o ./cmpstest1 echo $?

執行結果如下:

分析:程序比較兩個字符串值,并且根據比較的結果設置程序的返回代碼。程序首先把
exit系統調用值加載到EAX寄存器中。把要測試的兩個字符串的位置加載到ESI和EDI寄存器中之后,程序使用CMPSL指令比較字符串的前4個字節。如果字符串相等,就使用JE指令跳轉到標簽equal,這里把程序結果代碼設置為0并且退出。如果字符串不相等,則不會跳轉到分支,程序順序執行,設置結果代碼為1并且退出。

可以看到,結果代碼為0,表示字符串互相匹配。

  • 驗證實驗cmpstest2.s

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o cmpstest2.o cmpstest2.s ld -m elf_i386 -o cmpstest2 cmpstest2.o ./cmpstest2 echo $?

執行結果如下:

分析:程序把源和目標字符串的位置加載到ESI和EDI寄存器中,把字符串長度加載到
ECX寄存器中。REPE CMPSB指令逐字節地重復字符串的比較,直到ECX寄存器為零,或者零標志被設置,這表明不匹配。

REPE指令執行之后,像以往那樣使JE指令檢查EFLAGS寄存器以便確定字符串是否相等。
如果REPE指令退出,則零標志將被設置,JE指令不跳轉到分支,表示字符串不相同。ESI和EDI寄存器將包含字符串中不匹配字符的內存位置,并且ECX寄存器將包含不匹配字符在字符串中的位置(從字符串的末尾向回計數)。

可以看出,字符串比較是區分大小寫的。兩個字符串之間只有一個字符的大小寫有區
別,這會被比較程序檢測到。CMP指令從源字符串的十六進制值中減去目標字符串的值,得到結果11。

字符串不等

  • 驗證實驗strcomp.s

程序定義兩個字符串string1和string2,還有它們的長度(length1和length2)。程序生成的結果代碼反映兩個字符串的比較情況:

結果代碼描述
255string1小于string2
0string1等于string2
1string1大于string2

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o strcomp.o strcomp.s ld -m elf_i386 -o strcomp strcomp.o ./strcomp echo $?

執行結果如下:

分析:從結果255可以看出,第一個字符串"test"小于第二個字符串"test1"。

掃描字符串

SCAS指令系列用于掃描字符串搜索一個或者多個字符。和其他字符串指令一樣,SCAS指
令有3個版本:

  • SCASB:比較內存中的一個字節和AL寄存器的值
  • SCASW:比較內存中的一個字和AX寄存器的值
  • SCASL:比較內存中的一個雙字和EAX寄存器的值

SCAS指令使用EDI寄存器作為隱含的目標操作數。EDI寄存器必須包含要掃描的字符串的
內存地址。和其他字符串指令一樣,當執行SCAS指令時,EDI寄存器的值按照搜索字符的數據長度遞增或者遞減(這取決于DF標志的值)。

  • 驗證實驗scastest1.s

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o scastest1.o scastest1.s ld -m elf_i386 -o scastest1 scastest1.o ./scastest1 echo $?

執行結果如下:

分析:可以看到,在字符串的第16個位置找到了"-"字符。

搜索多個字符

  • 驗證實驗scastest2.s

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o scastest2.o scastest2.s ld -m elf_i386 -o scastest2 scastest2.o ./scastest2 echo $?

執行結果如下:

分析:程序試圖在字符串中查找字符序列“test"。它把整個搜索字符串加載到EAX寄存
器中,然后使用SCASL指令一次檢查字符串的4個字節。注意ECX寄存器沒有被設置為字符串的長度,而是被設置為REPNE指令遍歷整個字符串所需的迭代次數。因為每次迭代檢查4個字節,所以ECX寄存器的值是整個字符串長度44的四分之一。

可以看到,結果代碼為0,說明SCASL指令在字符串中沒有找到字符序列"test"。顯然,出現了某些錯誤。

這是因為REPNE指令的第一次選代比較4個字節的"This"和EAX中的字符序列。因為它們不匹配,所以ECX寄存器遞增4,然后檢查下面的4個字節"is"。被測試的每一組4個字節都不和搜索字符序列相匹配,盡管這個序列確實在這個字符串中。

計算字符串的長度

  • 驗證實驗strsize.s

程序的源代碼略。

執行程序命令:

as --32 -gstabs -o strsize.o strsize.s ld -m elf_i386 -o strsize strsize.o ./strsize echo $?

執行結果如下:

分析:程序結果說明字符串的長度為35。

遇到問題

1.當運行某些程序時,如果需要進行調試,按照單步執行一步一步按s真的很慢,而且也容易因為按鍵太多而出一些錯誤。比如按照課本進行單步調試aaatest.s時,需要進行三四十步單步調試,才能退出中間的循環,在程序結束前查看結果的值,比較復雜:

# aaatest.s - An example of using the AAA instruction .section .data value1:.byte 0x05, 0x02, 0x01, 0x08, 0x02 value2:.byte 0x03, 0x03, 0x09, 0x02, 0x05 .section .bss.lcomm sum, 6 .section .text .globl _start _start:nopxor %edi, %edimovl $5, %ecxclc loop1:movb value1(, %edi, 1), %aladcb value2(, %edi, 1), %alaaamovb %al, sum(, %edi, 1)inc %ediloop loop1adcb $0, sum(, %edi, 4)movl $1, %eaxmovl $0, %ebxint $0x80

解決方案:在程序適當的地方設置斷點,通過cont命令使程序在適當的地方停下來,而不需要一次一次地手動去按s進行單步調試:

# aaatest.s - An example of using the AAA instruction .section .data value1:.byte 0x05, 0x02, 0x01, 0x08, 0x02 value2:.byte 0x03, 0x03, 0x09, 0x02, 0x05 .section .bss.lcomm sum, 6 .section .text .globl _start _start:nopxor %edi, %edimovl $5, %ecxclc loop1:movb value1(, %edi, 1), %aladcb value2(, %edi, 1), %alaaamovb %al, sum(, %edi, 1)inc %ediloop loop1adcb $0, sum(, %edi, 4) end:movl $1, %eaxmovl $0, %ebxint $0x80

在loop1和end處設置斷點,一個控制程序中間的循環,使得循環還未結束時每次輸入cont都能在循環開始處停住,一個控制程序退出循環后,在結束程序前得到結果之后停住能夠查看結果。

可以看到,按鍵次數大大減少,無需按幾十次單步調試的命令才能完成對程序的調試和查看

總結

以上是生活随笔為你收集整理的操作系统实验报告4:Linux 下 x86 汇编语言3的全部內容,希望文章能夠幫你解決所遇到的問題。

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