Posto vidim da nema rezultata nasih saveta ne preostaje nista drugo nego da
upotrebimo tesku artiljeriju, u ovom slucaju CPU prozor koji omogucava da
se vidi sta se tacno desava u memoriji i u registrima procesora. Napravio sam
primer koji se sastoji od jedne forme i dugmenceta na njoj, na koji kada se
klikne poziva se procedura YourTurn koja dalje poziva sledecu proceduru i tako
dalje. Cilj je da pokazem nacin na koji se prosledjuju argumenti do procedure
i kako mozemo proveriti ispravnost prenetih podataka. Postoje jos nekoliko metoda
kako se sve ovo moze uraditi, ali sam izabrao ovaj jer je malo zanimljiviji.
Potrebno znanje:
Stek, postavljanje prekida (breakpoint) i jos po nesto :)
U opcijama projekta na stranici compiler treba postaviti:
Optimization=iskljuceno
Stack frames=iskljuceno
Range checking=iskljuceno
Overflow checking=iskljuceno
Debug information=ukljuceno
i uraditi Build projekta.
Kod primera:
Code:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
procedure RootSearchAB(alpha, beta: integer; const depth: byte; var n,
x, y: byte);
procedure YourTurn;
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.RootSearchAB(alpha, beta: integer; const depth: byte; var n, x, y: byte);
begin
n:= depth;
end;
procedure TForm1.YourTurn;
Var
n, x, y, depth: byte;
begin
depth:= $63; // Decimalno 99
n:= $11; // Decimalno 17
x:= $22; // Decimalno 34
y:= $33; // Decimalno 51
RootSearchAB(-maxint-1, maxint, depth, n, x, y);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
YourTurn;
end;
end.
Postavljamo breakpoint na liniju:
depth:= $63;
i pokrenemo program. Kliknemo na button1 i program prekida izvrsavanje
i prikazuje se Delphi editor sa obelezenom linijom prekida.
Sada pritiskamo ctrl+alt+c i dobijamo CPU window kao na slici 1 koju
sam poslao. Od crvene tacke na dole mozemo videti kako promenjive
depth, n, x i y dobijaju vrednosti:
Code:
mov byte ptr [ebp-$08], $63
Ovo nije nista drugo do naredba da se vrednost $63 (decimalno 99) stavi na
lokaciju u memoriji na adresi [ebp-$08] (u pitanju je stek memorija u
kojoj se nalaze izmedju ostalog i lokalne promenjive. Ovo pravilo ne vazi
uvek ali da sada ne ulazim u detalje). Isto se desava i sa
ostalim promenjivama.
Slika1:
Od linije Unit1.pas.43 u CPU prozoru pocinju da se desavaju zanimljive
stvari. Krece pakovanje podataka za poziv procedure
RootSearchAB(). Prevodilac (compiler) pakuje argumente za
poziv procedure tako sto prva dva argumenta (
alpha i
beta)
stavlja u registre, dok ostale parametre pakuje na stek sa
leva na desno. Prvo pakuje na stek:
Code:
mov al,[ebp-$08] // Vrednost promenjive depth se kopira u
// registar procesora al (1 byte register)
push eax // Vrednost registra eax se smesta na stek
// al registar je deo eax registra
ovo je zbog toga sto je
depth prosledjen kao const i procedura
koju pozivamo zapravo dobija kopiju vrednosti
depth promenjive
i na taj nacin nece moci da izmeni originalnu
depth promenjivu
koja se nalazi na lokaciji [ebp-$08].
Treba sa F7 ici kroz naredbe dok se ne stigne do linije
Code:
push eax
(kao na slici 2) koja je pomenuta gore.
Slika2:
Ako pogledamo u gornjem desnom delu
CPU prozora vrednosti registra procesora u tom trenutku videcemo da je u
eax registru vrednost
$008C1E63. Posto je depth smesten u
al
registar treba gledati samo zadnje dve cifre a to iznosi $63 sto i jeste
vrednost promenjive
depth. Za sada je sve kako treba.
Ostale promenjive se prosledjuju kao
var sto znaci da treba
omoguciti proceduri koju pozivamo da promeni njihove vrednosti.
Kompajler ovo radi tako sto prosledjuje adrese promenjivih u
memoriji proceduri koju zovemo kako bi ona eventualno mogla da
trajno upise nove vrednosti:
Code:
lea eax,[ebp-$05] // Izracunaj efektivnu adresu promenjive n
// i izracunatu adresu stavi u eax registar
push eax // stavi vrednost eax registra na stek
i isto tako i za
x i
y promenjive.
Sada dolazimo i do
alpha i
beta argumenata. Kompajler ce
staviti ove vrednosti da se prenose do procedure
RootSearchAB() u
registrima
ecx i
edx kako i treba po konvenciji
register.
Sledeci interesantan korak je i pozivanje procedure RootSearchAB().
Potrebno je pritiskati F7 (korak po korak) dok ne dodjemo do tacke kao na
slici 3:
Slika3:
To je trenutak kada se u promenjivu
n stavlja vrednost promenjive
depth. Linija:
Code:
mov dl,[ebp+$14]
prebacuje vrednost promenjive
depth u registar
dl koji je deo
edx
registra. To je trenutak kada mozemo u gornjem desnom uglu CPU prozora pogledati
edx registar i videti da sadrzi vrednost $63 (zadnje dve cifre) sto znaci da
promenjiva ima ispravnu vrednost. I tako dalje.
Ovaj koliko toliko prost primer pokazuje kako funkcionisu pozivi procedura i kako
se prosledju parametri. Ako stavimo da procedura
RootSearchAB() koristi
stdcall prosledjivanje onda bi i
alpha, beta promenjive isle preko steka
a ne preko registara. Imali bi smo umesto:
Code:
mov ecx,$7fffffff
mov edx,$80000000
sledeci kod:
Code:
push $7fffffff
push $80000000
sto na oko i nije neka velika razlika dokle god se ne koriste dll-ovi. Sa njima ova
razlika postaje velika jer ako procedura u dll-u ocekuje sve argumente na steku a dobije
prva dva u registrima onda imamo veliki problem, koji se najcesce rezultuje korupcijom steka.
Sada bi PeraKojotSuperGenije trebao da primeni ovu tehniku na svom programu uz neke sitne izmene
(kao sto je n:=depth na pocetku procedure kako bi vec tu mogao lakse iz CPU prozora da proveri
vrednost koja je prosledjena).
Eto materijala za pocetak pa da vidimo, posalji slike CPU prozora u ove tru faze koje sam
pomenuo :) pa da analiziramo.
Izvinjavam se ako sam nesto izostavio, ali materija je obimna a ja sam probao da sve to sabijem
u par recenica, tako da je mnogo toga ostalo bez objasnjenja.
Ako ima pitanja u vezi sa ovim postom, slobodno...