Kod generowany kiedy mogę skompilować ten, pod ustawień debugowania, jest tak:
begin
005A9414 55 push ebp
005A9415 8BEC mov ebp,esp
005A9417 83C4E4 add esp,-$1c
005A941A 33C9 xor ecx,ecx
005A941C 894DEC mov [ebp-$14],ecx
005A941F 894DE8 mov [ebp-$18],ecx
005A9422 894DE4 mov [ebp-$1c],ecx
005A9425 8955F0 mov [ebp-$10],edx
005A9428 8945F4 mov [ebp-$0c],eax
005A942B 33C0 xor eax,eax
005A942D 55 push ebp
005A942E 6890945A00 push $005a9490
005A9433 64FF30 push dword ptr fs:[eax]
005A9436 648920 mov fs:[eax],esp
mov X, eax
005A9439 8945FC mov [ebp-$04],eax
mov Y, edx
005A943C 8955F8 mov [ebp-$08],edx
Kiedy rozpoczyna się wykonywanie kodu, eax
jest rzeczywiście wskaźnik siebie. Ale kompilator wybrał zapisanie go na ebp-$0c
, a następnie zerowanie eax
. To naprawdę zależy od kompilatora.
Kod pod ustawieniami wydania jest dość podobny. Kompilator nadal wybiera zeroise eax
. Oczywiście nie można polegać na tym kompilatorze.
begin
005A82A4 55 push ebp
005A82A5 8BEC mov ebp,esp
005A82A7 33C9 xor ecx,ecx
005A82A9 51 push ecx
005A82AA 51 push ecx
005A82AB 51 push ecx
005A82AC 51 push ecx
005A82AD 51 push ecx
005A82AE 33C0 xor eax,eax
005A82B0 55 push ebp
005A82B1 6813835A00 push $005a8313
005A82B6 64FF30 push dword ptr fs:[eax]
005A82B9 648920 mov fs:[eax],esp
mov X, eax
005A82BC 8945FC mov [ebp-$04],eax
mov Y, edx
005A82BF 8955F8 mov [ebp-$08],edx
Należy pamiętać, że przekazywanie parametrów określa stan rejestrów i stosu, gdy funkcja rozpoczyna wykonywanie. Co dzieje się dalej, jak funkcja dekoduje parametry, sprowadza się do kompilatora. Nie ma obowiązku pozostawiać nietkniętych rejestrów i stosów, które były używane do przekazywania parametrów.
Jeśli wstrzykniesz moduł ASM w środek funkcji, nie możesz oczekiwać, że rejestry ulotne, takie jak eax
, będą miały określone wartości. Będą trzymać wszystko, co ostatnio kompilator włożył w nie.
Jeśli chcesz sprawdzić rejestry na samym początku wykonywania funkcji, należy użyć czystego funkcji asm mieć pewność, aby uniknąć konieczności kompilator modyfikacji rejestrów, które zostały użyte do parametru Podania:
var
X, Y: Pointer;
asm
mov X, eax
mov Y, edx
// .... do something with X and Y
end;
kompilator dokona jego wyboru bardzo dużo zależy od kodu w pozostałej części funkcji. Dla twojego kodu, złożoność zestawienia ciągu znaków do przekazania do ShowMessage
powoduje dość dużą preambułę. Rozważmy ten kod zamiast:
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
i: Integer;
function Sum(j: Integer): Integer;
end;
....
procedure TForm1.FormCreate(Sender: TObject);
begin
i := 624;
Caption := IntToStr(Sum(42));
end;
function TForm1.Sum(j: Integer): Integer;
var
X: Pointer;
begin
asm
mov X, eax
end;
Result := TForm1(X).i + j;
end;
W tym przypadku kod jest dość proste do kompilator opuścić eax
sam. Zoptymalizowany kod release build dla Sum
jest:
begin
005A8298 55 push ebp
005A8299 8BEC mov ebp,esp
005A829B 51 push ecx
mov X, eax
005A829C 8945FC mov [ebp-$04],eax
Result := TForm4(X).i + j;
005A829F 8B45FC mov eax,[ebp-$04]
005A82A2 8B80A0030000 mov eax,[eax+$000003a0]
005A82A8 03C2 add eax,edx
end;
005A82AA 59 pop ecx
005A82AB 5D pop ebp
005A82AC C3 ret
A po uruchomieniu kodu, napis postaci zostaje zmieniona na wartość oczekiwaną.
Szczerze mówiąc, montaż inline, umieszczony jako blok asm wewnątrz funkcji Pascal, nie jest zbyt użyteczny. Rzecz w pisaniu asemblacji polega na tym, że musisz w pełni zrozumieć stan rejestrów i stosu. dobrze zdefiniowane na początku i na końcu funkcji zdefiniowanej przez ABI.
Ale w środku funkcji, stan ten zależy całkowicie od decyzji podejmowanych przez kompilator. Wstrzykiwanie bloków asm tam wymaga poznania decyzji podjętych przez kompilator. Oznacza to również, że kompilator nie może zrozumieć podjętych decyzji. Zwykle jest to niepraktyczne. Rzeczywiście dla kompilatora x64 Embarcadero zbanował takie wbudowane bloki asm. Osobiście nigdy nie użyłem wbudowanego bloku asm w moim kodzie. Jeśli kiedykolwiek piszę ASm, zawsze piszę czyste funkcje asm.
Dziękuję za wiedzą komentarzach! – SOUser