Entwickler-Ecke

Algorithmen, Optimierung und Assembler - [Assembler] Problem bei mehrfachem MOV


Silas - Mo 12.05.08 19:08
Titel: [Assembler] Problem bei mehrfachem MOV
Hallo!

Ich hab mal wieder ein (eigenartiges) Problem.
Mein Code funktionierte bis vor Kurzem einwandfrei, aber jetzt spielt er verrückt:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
procedure Bin2Hex(Source: Pointer; Length: Cardinal; Dest: PChar; Uppercase: Boolean); assembler;
// ...
procedure Bin2Hex(Source: Pointer; Length: Cardinal; Dest: PChar; Uppercase: Boolean);
asm
        push ebx
        mov bl, 10h       // bl = Divisor (16)
        mov esi, Source   // esi = Quelladresse
        mov ecx, Length   // ecx = Verbleibende bytes
        mov edi, Dest     // edi = Zieladresse       
        {push Source
        push Length
        push Dest
        pop edi
        pop ecx
        pop esi}

        mov al, UpperCase
        test al, al       // Großbuchstaben?
        jz @lc
        // ...
        pop ebx
end;

Der Code an sich dürfte klar sein; Source, Length und Dest werden in ihre Register geschrieben.
Da liegt jetzt das Problem: Sowohl ecx als auch edi erhalten den Wert von Length. Wenn ich das Ganze umdrehe, erhalten beide den Wert von Dest. :nut: (Ja, sie haben den Wert nachher wirklich, das liegt nicht am Debugger. Die Werte stehen zumindest in der AV :? )
Wenn ich statt diesem den auskommentierten Code verwende, funktioniert alles einwandfrei.

Was ist da los? :?!?: Hat das evtl. etwas mit der assembler-Direktive oder der Parameterübergabe zu tun (kein const / var)? Sollte man bei reinen Assemblerfunktionen eine andere Aufrufkonvention verwenden?


BenBE - Mo 12.05.08 19:45

Die Angabe Assembler wird von Delphi ignoriert, da sie nicht nötig ist.

Zu deinem Problem: Nur EAX, EDX und ECX sind frei verfügbar. Jegliche andren Register (EBX, EDI, ESI) müssen gesichert und wiederhergestellt werden.


Silas - Mo 12.05.08 20:27

user profile iconBenBE hat folgendes geschrieben:
Die Angabe Assembler wird von Delphi ignoriert, da sie nicht nötig ist.
Oh, hätte gedacht sie wäre nötig, wenn die Prozedur keinen begin-end-Block hat. Man lernt nie aus ;) .

user profile iconBenBE hat folgendes geschrieben:
Zu deinem Problem: Nur EAX, EDX und ECX sind frei verfügbar. Jegliche andren Register (EBX, EDI, ESI) müssen gesichert und wiederhergestellt werden.
Bei EBX wusste ich das, die Indexregister haben mir bis jetzt nie Probleme bereitet. Das Sichern ändert auch nichts am Problem (das ja (auch laut Debugger) bei der Zuweisung besteht). :nixweiss:


BenBE - Mo 12.05.08 20:44

ESI und EDI werden für OOP bei Delphi genutzt (glaub ESI war der Self-Pointer, EDI die Klassen-Referenz). Wenn Du die also in deiner Routine änderst, ...

Ferner solltest Du beachten, was Du in den ersten Zeilen geschrieben hast (Vergleich mal mit dem CPU-Fenster :P):


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
procedure Bin2Hex(Source: Pointer; Length: Cardinal; Dest: PChar; Uppercase: Boolean); assembler;
// ...
procedure Bin2Hex(Source: Pointer; Length: Cardinal; Dest: PChar; Uppercase: Boolean);
asm
        push ebx
        mov bl, 10h       // bl = Divisor (16)
        mov esi, EAX      // esi = Quelladresse
        mov ecx, EDX      // ecx = Verbleibende bytes
        mov edi, ECX      // edi = Zieladresse       
        {push Source
        push Length
        push Dest
        pop edi
        pop ecx
        pop esi}

        mov al, BYTE PTR [ESP+4]
        test al, al       // Großbuchstaben?
        jz @lc
        // ...
        pop ebx
end;


Merk Dir am Besten die Zuordnung der Register für die Register-Aufruf-Konvention und arbeite damit. Ist IMMER EAX, EDX, ECX und der Rest Stack, zugeordnet von Links nach Rechts.


Silas - Mo 12.05.08 21:11

user profile iconBenBE hat folgendes geschrieben:
ESI und EDI werden für OOP bei Delphi genutzt (glaub ESI war der Self-Pointer, EDI die Klassen-Referenz). Wenn Du die also in deiner Routine änderst, ...
Gut, meine Funktionen sind global, insofern ist es in dem Fall egal. Werds mir aber merken, danke für den Tip ;) .

user profile iconBenBE hat folgendes geschrieben:
Ferner solltest Du beachten, was Du in den ersten Zeilen geschrieben hast (Vergleich mal mit dem CPU-Fenster :P):
...
Merk Dir am Besten die Zuordnung der Register für die Register-Aufruf-Konvention und arbeite damit. Ist IMMER EAX, EDX, ECX und der Rest Stack, zugeordnet von Links nach Rechts.
:shock: Oha. Wusste nicht, dass Delphi ohne explizite register-Direktive die Register an Stelle des Stacks verwendet. Man gewöhnt sich so schnell an MASM... :D
Beim rumprobieren ist mir aufgefallen, dass sich das Problem mit stdcall lösen lässt, ich wusste nur nicht warum. Dann dreh ich die Zuweisungen mal ein wenig um :) .

Danke für die Hilfe!


BenBE - Mo 12.05.08 21:22

user profile iconSilas hat folgendes geschrieben:
user profile iconBenBE hat folgendes geschrieben:
ESI und EDI werden für OOP bei Delphi genutzt (glaub ESI war der Self-Pointer, EDI die Klassen-Referenz). Wenn Du die also in deiner Routine änderst, ...
Gut, meine Funktionen sind global, insofern ist es in dem Fall egal. Werds mir aber merken, danke für den Tip ;) .

user profile iconBenBE hat folgendes geschrieben:
Ferner solltest Du beachten, was Du in den ersten Zeilen geschrieben hast (Vergleich mal mit dem CPU-Fenster :P):
...
Merk Dir am Besten die Zuordnung der Register für die Register-Aufruf-Konvention und arbeite damit. Ist IMMER EAX, EDX, ECX und der Rest Stack, zugeordnet von Links nach Rechts.
:shock: Oha. Wusste nicht, dass Delphi ohne explizite register-Direktive die Register an Stelle des Stacks verwendet. Man gewöhnt sich so schnell an MASM... :D
Beim rumprobieren ist mir aufgefallen, dass sich das Problem mit stdcall lösen lässt, ich wusste nur nicht warum. Dann dreh ich die Zuweisungen mal ein wenig um :) .

Danke für die Hilfe!


Bei STDCall funkzt es, weil stdcall für alles den Stack (von Rechts nach Links) nutzt ...


Silas - Mo 12.05.08 21:38

user profile iconBenBE hat folgendes geschrieben:
Bei STDCall funkzt es, weil stdcall für alles den Stack (von Rechts nach Links) nutzt ...

Weiß ich, ich hatte mich nur darüber gewundert, weil ich davon ausgegangen bin, dass das Problem - nachdem es ja mit stdcall funzt - damit zusammenhängt, dass die Pascal-Konvention "falschherum" arbeitet. ;)