x86 Assembly Program: Procedures, FPU Stack and Array Processing
//მოდელისთვის განკუთვნილი სეგმენტი
//უბრალოდ საჭიროა ყველგან
.model flat, stdcall
//სტეკისთვის განკუთვნილი სეგმენტი
//უბრალოდ საჭიროა ყველგან
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD
//მონაცემებისთვის განკუთვნილი სეგმენტი
.data
//პირველი პროცედურისთვის საჭირო მონაცემები
var_x QWORD 1.2 //შევარჩიეთ პირობითად, შესაძლოა მოცემული იყოს (შესაძლოა კლავიატურიდან შეიტანეთ მოთხოვნაც ყოფილიყო, თუმცა საკონტროლოზე ნაკლებად სავარაუდოა), ან არ იყოს და ნებისმიერი რიცხვი შეგიძლიათ დაწეროთ
var_comp QWORD 4.5 //ვიყენებთ შედარებისთვის
var_el1 QWORD 7 //ვიყენებთ 7-ზე გამრავლებისთვის
var_el2 QWORD 2.7 //ვიყენებთ 2.7-ის დასამატებლად
var_el3 QWORD 5.2 //ვიყენებთ 5-ის დასამატებლად
proc_1_res QWORD ? //საჭიროა პირველი პროცედურის შედეგის შენახვა მთავარი პროცედურისთვის, პირობის მიხედვით
//მეორე პროცედურისთვის საჭირო მონაცემები
arr_source DWORD 1,2,3,4,5,6,7,8,9,10 //მასივის ელემენტების მნიშვნელობები, შევარჩიეთ პირობითად ჩვენი სურვილით
arr_desti DWORD 10 DUP(?) //საჭიროა გადაკოპირებული მასივისთვის
var_tmp DWORD ? //ცვლადი დროებითი მონაცემის შენახვისთვის, დაგვჭირდება მეორე და მესამე პროცედურაში
//მესამე პროცედურისთვის საჭირო მონაცემები
proc_3_res QWORD ? //საჭიროა მესამე პროცედურის შედეგის შენახვა მთავარი პროცედურისთვის, პირობის მიხედვით
//მთავარი პროცედურისთვის საჭირო მონაცემები
RESULT QWORD ? //საჭიროა მთავარი პროცედურის შედეგის შესანახად
//კოდისთვის განკუთვნილი სეგმენტი
.code
//მთავარი პროცედურა
main PROC
CALL proc_1 //პირველი პროცედურის გამოძახება
CALL proc_2 //მეორე პროცედურის გამოძახება
CALL proc_3 //მესამე პროცედურის გამოძახება
FLD proc_1_res //პირველი პროცედურის შედეგის დამახსოვრება სტეკში ST(0)-ში
FMUL proc_3_res //სტეკის ნულოვანი ST(0)-ის გადამრავლება მესამე პროცედურის შედეგზე, შედეგი მახსოვრდება ST(0)-ში
FSTP RESULT //ST(0)-ის დამახსოვრება RESULT ცვლადში
invoke ExitProcess, 0 //ე.წ. return 0
main ENDP //მთავარი პროცედურის სკრიპტის დასასრული
//პირველი პროცედურა
proc_1 PROC
FLD var_el1 // ST(0)-ზე var_el1 ცვლადის მნიშვნელობის ჩაწერა
FLD var_x // ST(0)-ზე var_x ცვლადის მნიშვნელობის ჩაწერა, var_el1 ცვლადის მნიშვნელობა გადაინაცვლებს ST(1)-ზე => ST(0)=var_x=1.2 & ST(1)=var_el1=7
FCOM var_comp // var_comp-ის შედარება ST(0)-თან (4.5-ის შედარება 1.2-თან)
JA N1 // თუ var_comp>ST(0)-ზე (4.5>1.2-ზე შემთხვევა), გადახტება N1:-ზე
JMP N2 // თუ var_comp<ST(0)-ზე (4.5<1.2-ზე შემთხვევა), გადახტება N2:-ზე
N1:
FMUL ST(0), ST(1) // ST(0)-ის გამრავლება ST(1)-ზე (ST(0)=var_x=1.2 & ST(1)=var_el1=7), შედეგი ჩაიწერება ST(0)-ში (1.2*7=8.4) => (ST(0)=var_x=8.4 & ST(1)=var_el1=7)
FCHS ST(0) // ST(0)-სთვის ნიშნის შეცვლა (-ST(0)=-8.4) და შედეგის ST(0)-ში ჩაწერა => (ST(0)=-8.4 & ST(1)=var_el1=7)
FADD var_el2 // ST(0)-ზე var_el2 ცვლადის მნიშვნელობის დამატება (-8.4+2.7=-5.7) და შედეგის ST(0)-ში ჩაწერა => (ST(0)=-5.7 & ST(1)=var_el1=7)
FSQRT // ST(0)-დან ფესვის ამოღება და შედეგის ST(0)-ში ჩაწერა
JMP N3 //N3:-ზე გადახტომა, რათა არ შესრულდეს N2: შემთხვევა
N2:
FLD ST(0) // ST(0)-ის მნიშვნელობის ჩაწერა ST(0)-ში, ანუ ST(0)-ის მნიშვნელობას იმახსოვრებს, წერს ისევ ST(0)-ში, ძველი ST(0)-ის მნიშვნელობა გადაინაცვლებს ST(1)-ში, ST(1)-> ST(2) => (ST(0)=1.2 & ST(1)=var_x=1.2 & ST(2)=var_el1=7)
FMUL ST(1), ST(0) //ST(1)-ის გამრავლება ST(0)-ზე (ST(0)=1.2 & ST(1)=var_x=1.2), შედეგი ჩაიწერება ST(1)-ში (1.2*1.2=1.44) => (ST(0)=1.2 & ST(1)=1.44 & ST(2)=var_el1=7)... შედეგს ვიმახსოვრებთ ST(1)-ში რადგან ST(0)-შემდგომ გვჭირდება 5.2-ის დასამატებლად
FADD var_el3 // ST(0)-ზე var_el3 ცვლადის მნიშვნელობის დამატება (1.2+5.2=6.4) და შედეგის ჩაწერა ST(0)-ში => (ST(0)=6.4 & ST(1)=1.44 & ST(2)=var_el1=7)
FDIVP // ST(0)-ის გაყოფა ST(1)-ზე (6.4/1.44=4.4) და შედეგის ჩაწერა ST(0)-ში => (ST(0)=4.4 & ST(1)=var_el1=7)
N3:
FSTP proc_1_res //ST(0)-ის მნიშვნელობის შენახვა proc_1_res ცვლადში
RET // return ამ პროცედურის წამომწყებ პროცედურაში, ანუ ჩვენს შემთხვევაში მთავარ პროცედურაში
proc_1 ENDP //პირველი პროცედურის დასასრული
//მეორე პროცედურა
proc_2 PROC
MOV ECX, LENGTHOF arr_source // შევინახოთ ECX-ში წყაროს მასივის ზომა
MOV ESI, OFFSET arr_source // შევინახოთ ESI-ში წყაროს მასივის მისამართი (ამ შემთხვევაში ის შეიცავს მასივის პირველი ელემენტის მისამართს)
MOV EDI, OFFSET arr_desti // შევინახოთ EDI-ში დანიშნულების მასივის მისამართი (ასევე შეიცავს პირველი ელემენტის მისამართს)
MOV EAX, 1 // რადგანაც დაგვჭირდება გამრავლების ოპერაციის შესრულება, ამიტომ შევინახოთ 1
MOV EDX, 2 // დაგვეხმარება 2-ის ხარისხების დათვლაში
//ციკლი მთლიანი მასივის გადატანისთვის
Loop1:
PUSH EDI // ვინახავთ დანიშნულების მასივის (EDI) პირველი ელემენტის მისამართს სტეკში, რადგანაც მოგვიწევს ამ მისამართის შეცვლა. შემდგომში შევძლებთ სტეკიდან ამ მისამართის აღდგენა
MOV EDI, OFFSET var_tmp // რადგანაც EDI შენახულია სტეკში, ამიტომ შეგვიძლია მისი შეცვლა. შევინახოთ EDI-ში დროებითი ცვლადის მისამართი.
MOVSD // ამ ბრძანების შემდეგ var_tmp-ში გადაკოპირდება წყაროს მასივის ელემენტი (ციკლის პირველ დატრიალებაზე გვექნება 1, რადგან პირველი ელემენტი = 1), მოხდება ESI და EDI მისამართების გაზრდა 4-ით (რადგან მონაცემთა ტიპია DWORD). ანუ ESI უკვე უთითებს წყაროს მასივის არა პირველ, არამედ მეორე ელემენტის მისამართზე.
MUL EDX // EAX-ში გვაქვს 1, ეს მნიშვნელობა გამრავლდება 2-ზე, ანუ მივიღებთ 2^1.
ADD var_tmp, EAX // შევკრიბოთ EAX და var_tmp, ანუ პირველი ელემენტი გაიზრდება 2^1-ით. ასევე, თუ დავუკვირდებით, ყოველ იტერაციაზე EAX-ის მნიშვნელობა მატულობს, უფრო ზუსტად კი იგი მრავლდება 2-ზე.
POP EDI // ამის შემდეგ ხდება EDI-ს აღდგენა, ანუ EDI-ში ისევ წერია დანიშნულების მასივის პირველი ელემენტის მისამართი.
PUSH ESI // ახლა კი შევინახოთ წყაროს მისამართი (უკვე მეორე ელემენტის მისამართი)
MOV ESI, OFFSET var_tmp // ჩავწეროთ მასში (ESI-ში) ჩვენ მიერ შეცვლილი დროებითი ცვლადის მისამართი.
MOVSD // ამ ბრძანების შესრულების შემდეგ კი, დანიშნულების მასივის პირველი ელემენტი იქნება var_tmp. არ უნდა დაგვავიწყდეს, რომ EDI გაიზრდება და მასში ჩაიწერება დანიშნულების მასივის მეორე ელემენტის მისამართი.
POP ESI // აღვადგინოთ ESI, ანუ ESI-ში ისევ ეწერება წყაროს მეორე ელემენტის მისამართი.
MOV EDX, 2 // EDX-ში დავაბრუნოთ მნიშვნელობა ორი, ციკლის შემდგომი იტერაციისთვის
// ანუ ამ ციკლის ერთხელ დატრიალების შემდეგ, დანიშნულების მასივის პირველი ელემენტი იქნება 3, რადგან 2^1 + 1 = 3. ციკლის ყოველ დატრიალებაზე, EAX მნიშვნელობა მრავლდება 2-ზე.
LOOP Loop1 // ვუბრუნდებით Loop1 სანიშნეს
//ამ ციკლიდან გამოსვლის შემდეგ დანიშნულების მასივს ექნება სახე: 3, 6, 11, 20, 37, 70, 135, 264, 521, 1034
6 = 2^2 + 2; 11 = 2^3 + 3; ... და ასე შემდეგ
RET // return ამ პროცედურის წამომწყებ პროცედურაში, ანუ ჩვენს შემთხვევაში მთავარ პროცედურაში
proc_2 ENDP // მეორე პროცედურის დასასრული
//მესამე პროცედურა
proc_3 PROC
MOV ECX, LENGTHOF arr_desti // შევინახოთ ECX-ში დანიშნულების მასივის ზომა
MOV ESI, OFFSET arr_desti // შევინახოთ ESI-ში დანიშნულების მასივის მისამართი (ამ შემთხვევაში ის შეიცავს მასივის პირველი ელემენტის მისამართს)
MOV EBX, 0 // EBX რეგისტრი გამოვიყენოთ 5-ის ჯერადი ელემენტების რაოდენობის დასათვლელად, მისი ინიციალიზირება მოვახდინოთ 0-ით
MOV var_tmp, 5 // დროებით ცვლადში ჩავწეროთ მნიშვნელობა 5
//ციკლი 5-ის ჯერადი ელემენტების განსათავსებლად სტეკში
Loop2:
MOV EAX, [ESI] // EAX რეგისტრში გადავწეროთ დანიშნულების მასივის მიმდინარე ელემენტი ([ ] არის განმისამართება)
DIV var_tmp // EAX რეგისტრში ჩაწერილი მიმდინარე ელემენტის გაყოფა 5-ზე, თუ ის 5-ზე უნაშთოდ იყოფა (ნაშთში გვაძლევს 0-ს) EDX-ის მნიშვნელობა იქნება 0
CMP EDX, 0 // EDX-ის შედარება 0-თან
JE N4 // შემთხვევა, როცა EDX = 0, გადახტება N4:-ზე
JMP N5 // შემთხვევა, როცა EDX != 0, გადახტება N5:-ზე
N4:
PUSH EAX // შესაბამის ელემენტს ვანთავსებთ სტეკში
ADD EBX, 1 // ელემენტების რაოდენობას EBX რეგისტრში ვზრდით 1-ით
N5:
ADD ESI, TYPE DWORD // ამის შემდეგ გადავდივარ N5 სანიშნეზე და იზრდება ESI რეგისტრის მნიშვნელობა ისე, რომ მიუთითოს მასივის შემდეგი ელემენტის მისამართზე
LOOP Loop2 // ვუბრუნდებით Loop2 სანიშნეს
MOV ECX, EBX // ECX რეგისტრში შევინახოთ 5-ის ჯერადი ელემენტების რაოდენობა
MOV EAX, 0 // ჯამის შესანახად მოვახდინოთ EAX რეგისტრის ინიციალიზირება ნულოვანი მნიშვნელობით
//ციკლი სტეკის ელემენტების ჯამის დასათვლელად
Loop3:
POP EBX // სტეკიდან 5-ის ჯერადი ელემენტების გადმოტანას ვახდენთ EBX რეგისტრში
ADD EAX, EBX // ელემენტს ვუმატებთ EAX რეგისტრის მნიშვნელობას
LOOP Loop3 // ვუბრუნდებით Loop3 სანიშნეს
MOV proc_3_res, EAX // მიღებულ შედეგს (EAX) ვინახავთ proc_3_res ცვლადში
RET // return ამ პროცედურის წამომწყებ პროცედურაში, ანუ ჩვენს შემთხვევაში მთავარ პროცედურაში
proc_3 ENDP // მესამე პროცედურის დასასრული
END main // მთავარი პროცედურის დასასრული