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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

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

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

操作系統(tǒng)實驗報告4

實驗內(nèi)容

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

實驗環(huán)境

  • 架構(gòu):Intel x86_64 (虛擬機(jī))
  • 操作系統(tǒng):Ubuntu 20.04
  • 匯編器:gas (GNU Assembler) in AT&T mode
  • 編譯器:gcc

技術(shù)日志

Chapter 08

加法指令

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

add source, destination

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

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

  • 驗證實驗addtest1.s

在程序的源代碼的最后:

movl $1, %eax

這一行前,加上:

end:movl $1, %eax

便于進(jìn)行斷點調(diào)試。

執(zhí)行程序命令:

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

執(zhí)行截圖:

分析:和課本預(yù)期的輸出結(jié)果一致,對無符號數(shù)的加法執(zhí)行正確。

  • 驗證實驗addtest2.s

在程序的源代碼的最后:

movl $1, %eax

這一行前,加上:

end:movl $1, %eax

便于進(jìn)行斷點調(diào)試。

執(zhí)行程序命令:

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

執(zhí)行截圖:

分析:和課本預(yù)期的輸出結(jié)果一致,對帶符號整數(shù)的加法執(zhí)行也正確。

驗證實驗addtest3.s

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

改動寄存器的值,使加法不產(chǎn)生進(jìn)位,把原程序代碼中的:

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

改為:

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

執(zhí)行結(jié)果如下:

分析:程序?qū)Υ鎯υ贏L和BL寄存器中的2字節(jié)無符號整數(shù)值執(zhí)行簡單的加法。如果加法操作造成進(jìn)位,則把進(jìn)位標(biāo)志設(shè)置為1,并且JC指令將跳轉(zhuǎn)到標(biāo)簽over。程序的結(jié)果代碼要么是加法的結(jié)果,要么就是0值(如果結(jié)果超過255)。

第一個程序設(shè)置寄存器值使加法產(chǎn)生進(jìn)位,運(yùn)行程序,然后使用echo命令查看結(jié)果代碼,結(jié)果代碼為0,表示正確檢測到了進(jìn)位情況。

第二個程序改動寄存器的值,使加法不產(chǎn)生進(jìn)位,運(yùn)行程序之后,加法沒有產(chǎn)生進(jìn)位,沒有跳轉(zhuǎn),并且加法的結(jié)果被設(shè)置為結(jié)果代碼200。

  • 驗證實驗addtest4.s

執(zhí)行程序命令:

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

執(zhí)行截圖:

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

把原程序代碼中的:

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

改為:

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

執(zhí)行截圖:

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

ADC指令

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

ADC指令的格式如下:

adc source, destination

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

  • 驗證實驗adctest.s

在原程序代碼中的:

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

加上:

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

便于加斷點調(diào)試

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

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

減法指令

SUB指令的格式如下:

sub source, destination

其中從destination的值中減去source的值,結(jié)果存儲在destination操作數(shù)的位置。source可以是立即值或者8位、16位或者32位寄存器或內(nèi)存位置值,destination可以是8位、16位或者32位寄存器或內(nèi)存位置值。

  • 驗證實驗subtest1.s

在原程序代碼中的:

subl %eax, data
movl $1, %eax

加上:

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

便于加斷點調(diào)試

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

減法操作中的進(jìn)位和溢出

  • 驗證實驗subtest2.s

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

依靠溢出標(biāo)志來判斷到達(dá)了數(shù)據(jù)長度界限的情況

  • 驗證實驗subtest3.s

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

可以看到,溢出情況被檢測到,并且執(zhí)行JO指令和進(jìn)行跳轉(zhuǎn)。

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

movl $1259230143, %eax

改為:

movl $-1259230143, %eax

分析:程序減去負(fù)數(shù)生成一個絕對值更小的負(fù)數(shù),它在數(shù)據(jù)長度的界限之內(nèi),沒有設(shè)置溢出標(biāo)志。

SBB指令

可以使用進(jìn)位情況幫助執(zhí)行大的無符號整數(shù)值的減法操作,SBB指令在多字節(jié)減法操作中利用進(jìn)位和溢出標(biāo)志實現(xiàn)跨越數(shù)據(jù)邊界的借位特性。

SBB指令的格式如下:

sbb source, destination

其中進(jìn)位位被添加到source值,從destination的值中減去source的值,結(jié)果存儲在destination操作數(shù)的位置。source可以是8位、16位或者32位寄存器或內(nèi)存位置值,destination可以是8位、16位或者32位寄存器或內(nèi)存位置值,不能同時使用內(nèi)存位置作為源和目標(biāo)值。

  • 驗證實驗sbbtest.s

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

乘法指令

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

mul source

其中source可以是8位、16位或者32位寄存器或內(nèi)存值。

無符號整數(shù)乘法需求:

源操作數(shù)長度目標(biāo)操作數(shù)目標(biāo)位置
8位ALAX
16位AXDX:AX
32位EAXEDX:AX
  • 驗證實驗multest.s

在原程序代碼中的:

pushl %edx pushl %eax

加上:

aftermul:pushl %edxpushl %eax

便于加斷點調(diào)試

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

寄存器對組合EDX:EAX生產(chǎn)結(jié)果值,這個值被存儲在內(nèi)存位置result中,并且通過printf函數(shù)顯示出來,與課本上的預(yù)期的結(jié)果一致。

帶符號整數(shù)乘法

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

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

imul source

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

IMUL指令的第二種格式允許指定EAX寄存器之外的目標(biāo)操作數(shù):

imul source, destination

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

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

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

imul multiplier, source destination

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

  • 驗證實驗imultest.s

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

帶符號整數(shù)乘法檢查溢出

當(dāng)使用帶符號整數(shù)和IMUL指令時,總是要檢查結(jié)果中的溢出。一種方式是使用JO指令檢查溢出標(biāo)志。

  • 驗證實驗imultest2.s

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

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

movw $680, %ax

改為:

movw $60, %ax

執(zhí)行結(jié)果如下:

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

除法指令

DIV指令用于無符號整數(shù)的出發(fā)操作。DIV指令的格式如下:

div divisor

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

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

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

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

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

  • 驗證實驗divtest.s

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

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

divisor:.int 25

改為:

divisor:.int 0

執(zhí)行結(jié)果如下:

分析:發(fā)生除以0的情況時會產(chǎn)生錯誤,系統(tǒng)會產(chǎn)生中斷,需要進(jìn)行檢查。

移位乘法

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

sal destination sal %c1, destination sal shifter, destination

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

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

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

  • 驗證實驗saltest.s

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

把二進(jìn)制結(jié)果轉(zhuǎn)化為不打包BCD格式的指令(AAA為調(diào)整加法操作的結(jié)果)

  • 驗證實驗aaatest.s

在原程序代碼中的:

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

加上:

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

便于加斷點調(diào)試

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

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

最后,結(jié)果按照不打包BCD格式存放到內(nèi)存位置sum中。

調(diào)整SUB或者SBB指令

  • 驗證實驗dastest.s

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

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

執(zhí)行DAS指令之后,這個值改變?yōu)?x08 8

它表示結(jié)果的第一個十進(jìn)制位。

  • 驗證實驗cpuidtest.s

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

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

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

最后,使用TEST指令查看ID標(biāo)志位是否改變了。如果是,那么EAX中的值就不為零,然后使用JNZ指令進(jìn)行跳轉(zhuǎn),輸出適當(dāng)?shù)南ⅰ?/p>

Chapter 10

傳送字符串

創(chuàng)建MOVS指令是為了把字符串從一個內(nèi)存位置傳送到另一個內(nèi)存位置,MOVS指令有3種格式:

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

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

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

movl $output, %edi

這條指令把output標(biāo)簽的32位內(nèi)存位置傳送給EDI寄存器。

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

leal output, %edi

把output標(biāo)簽的32位內(nèi)存位置加載到EDI寄存器中。

  • 驗證實驗movstest1.s

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

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

  • 驗證實驗movstest2.s

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

在原程序代碼中的:

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

加上:

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

便于加斷點調(diào)試

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

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

  • 驗證實驗movstest3.s

把MOVSL指令放在循環(huán)中,通過把ECX寄存器設(shè)置為字符串的長度來進(jìn)行控制。

在原程序代碼中的:

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

加上:

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

便于加斷點調(diào)試

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

分析:ESI和EDI寄存器被設(shè)置為源和目標(biāo)內(nèi)存位置。ECX寄存器被設(shè)置為要傳送的字符串的長度。循環(huán)部分持續(xù)地執(zhí)行MOVSB指令。直到整個字符串傳送完畢。可以看到,查看內(nèi)存位置output的字符串值與課本上的預(yù)期輸出一樣。

REP前綴

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

  • 驗證實驗reptest1.s

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

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

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

  • 驗證實驗reptest2.s

也可以使用MOVSW和MOVSL指令在每次迭代中傳送1字節(jié)以上的數(shù)據(jù)。

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

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

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

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

  • 驗證實驗reptest3.s

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

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

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

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

"This is a test of the conversion program"

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

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

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

  • 驗證實驗reptest4.s

向后執(zhí)行和向前執(zhí)行REP指令都是可以的。可以把DF標(biāo)志設(shè)置為對字符串進(jìn)行向后處理,按照相反的方向在內(nèi)存位置之間傳送它。

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

STOS指令

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

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

STOS指令使用EDI寄存器作為隱含的目標(biāo)操作數(shù)。執(zhí)行STOS指令時,它按照使用的數(shù)據(jù)長
度遞增或者遞減EDI寄存器的值。

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

  • 驗證實驗stostest1.s

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

分析:程序吧ASCII空格字符加載到AL寄存器中,然后把它復(fù)制到buffer標(biāo)簽指向的內(nèi)存位置中256次。

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

構(gòu)建自己的字符串函數(shù)

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

  • 驗證實驗convert.s

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

分析:程序把ASCII字符串都轉(zhuǎn)換為大寫字母。

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

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

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

比較字符串

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

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

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

CMPS指令從源字符串中減去目標(biāo)字符串,并且適當(dāng)?shù)卦O(shè)置EFLAGS寄存器的進(jìn)位、符號、
溢出、零、奇偶校驗和輔助進(jìn)位標(biāo)志。CMPS指令執(zhí)行之后,可以根據(jù)字符串的值,使用一般的條件跳轉(zhuǎn)指令跳轉(zhuǎn)到分支。

  • 驗證實驗cmpstest1.s

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

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

  • 驗證實驗cmpstest2.s

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

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

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

字符串不等

  • 驗證實驗strcomp.s

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

結(jié)果代碼描述
255string1小于string2
0string1等于string2
1string1大于string2

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

掃描字符串

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

  • SCASB:比較內(nèi)存中的一個字節(jié)和AL寄存器的值
  • SCASW:比較內(nèi)存中的一個字和AX寄存器的值
  • SCASL:比較內(nèi)存中的一個雙字和EAX寄存器的值

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

  • 驗證實驗scastest1.s

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

搜索多個字符

  • 驗證實驗scastest2.s

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

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

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

計算字符串的長度

  • 驗證實驗strsize.s

程序的源代碼略。

執(zhí)行程序命令:

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

執(zhí)行結(jié)果如下:

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

遇到問題

1.當(dāng)運(yùn)行某些程序時,如果需要進(jìn)行調(diào)試,按照單步執(zhí)行一步一步按s真的很慢,而且也容易因為按鍵太多而出一些錯誤。比如按照課本進(jìn)行單步調(diào)試aaatest.s時,需要進(jìn)行三四十步單步調(diào)試,才能退出中間的循環(huán),在程序結(jié)束前查看結(jié)果的值,比較復(fù)雜:

# 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

解決方案:在程序適當(dāng)?shù)牡胤皆O(shè)置斷點,通過cont命令使程序在適當(dāng)?shù)牡胤酵O聛?#xff0c;而不需要一次一次地手動去按s進(jìn)行單步調(diào)試:

# 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處設(shè)置斷點,一個控制程序中間的循環(huán),使得循環(huán)還未結(jié)束時每次輸入cont都能在循環(huán)開始處停住,一個控制程序退出循環(huán)后,在結(jié)束程序前得到結(jié)果之后停住能夠查看結(jié)果。

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

總結(jié)

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

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。