Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Delphi Inline Assembler Zugriff auf Privates Objekt (https://www.delphipraxis.net/11937-delphi-inline-assembler-zugriff-auf-privates-objekt.html)

OLLI_T 17. Nov 2003 12:28


Delphi Inline Assembler Zugriff auf Privates Objekt
 
Hallo Gemeinde!

Folgende Methode bereitet mir leichte Bauchschmerzen:

Delphi-Quellcode:
Function TDIBSection24.GetMaskValue(X,Y:DWord):Byte;
asm // eax = Self; edx = X; ecx = Y
    push ebx
    cmp  [eax].FMask, 0
    je   @NoMaskValue        // Keine Maske definiert
    // X-Wert anpassen und überprüfen
    mov  ebx, [eax].FMaskRect.Left
    sub  edx, ebx            // X-Offset der Maske berücksichtigen
    js   @NoMaskValue        // X < 0     -> ausserhalb der Maske
    mov  ebx, [eax].FMask    // Zeiger auf FMask Object
    cmp  edx, [ebx+$08]      // ebx + $08 = FMask.FWidth
    jge  @NoMaskValue        // X >= Width -> ausserhalb der Maske
    add  edx, [ebx+$04]      // Zeiger auf Byte X, 0
    // Y-Wert überprüfen und anpassen
    mov  edi, [eax].FMaskRect.Top
    sub  ecx, edi            // Y-Offset der Maske berücksichtigen
    js   @NoMaskValue        // Y < 0 -> ausserhalb der Maske
    cmp  ecx, [ebx+$0C]      // X >= FMask.FHeight -> ausserhalb der Maske
    jge  @NoMaskValue
    jecxz @FirstRow
    imul ecx, [ebx+$08]      // Y-Offset nur bei Y > 0
    add  edx, ecx
  @FirstRow:
    mov  eax, [edx]
    and  eax, $000000FF
    jmp  @EndFunc
  @NoMaskValue:
    xor  eax, eax           // Result = 0
  @EndFunc:
    pop  ebx
End;
Ich bekomme keinen direkten Zugriff auf die privaten Variablen des Objektes FMask über den Punktoperator. Die Anweisung [eax].FMask.FWidth wird vom Compiler nicht korrekt aufgelöst. So bleibt mir nur der Umweg über mov ebx, [eax].FMask den Zeiger auf das Objekt zu ziehen und den Offset zu FWidth direkt über [ebx+$04] zu setzen. Hab auch schon diverse Casting Varianten ausprobiert; leider ohne Erfolg. Vielleicht gibt es doch eine Lösung, denn so ist der GAU vorprogrammiert, wenn ich die privaten Variablen der Klasse TDIBMask ändere.

Vielen Dank für´s mitdenken.

Gruss

OLLI

choose 17. Nov 2003 12:49

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Hallo OLLI,

probier diesen Cast:
Delphi-Quellcode:
type
  TMyClass = class
    FAttribute : Integer;
  end;

  TAnotherClass = class
  private
    FAggregate : TMyClass;
  end;

//...

  asm
    mov eax, TAnotherClass([ebx]).FAggregate.FAttribute
  end;

OLLI_T 17. Nov 2003 13:16

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Hallo Choose!

So wie ich Deinen Code interpretiere gehört Deine asm-Funktion zur Klasse TAnotherClass. Dann ist der Cast aber doch unnötig. [ebx].FAggregate liefert den Zeiger auf das Objekt FAggregate. Genauso hab ich es doch probiert und bekomme vom Compiler einen völlig falschen Offset berechnet. Oder hab ich was falsch verstanden?

Gruss

OLLI

OLLI_T 17. Nov 2003 13:18

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Nachtrag!

Mir erschien diese Variante noch plausibel:

mov eax, TMyClass([ebx].FAggregate).FAttribute

Dem Compiler leider nicht :?

choose 17. Nov 2003 14:43

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Hallo OLLI
Zitat:

Zitat von OLLI_T
So wie ich Deinen Code interpretiere gehört Deine asm-Funktion zur Klasse TAnotherClass.

Nein, in den Klassen sind keine Methoden deklariert sondern dienen lediglich der Veranschaulichung des Problems mit den Attribute. Ich habe den Code zusammenhangslos verwendet.
Leider habe ich meinen Betrag etwas zu flüchtig abgegeben, er wird zwar kompiliert, allerdings zu Code, wie man ihn warhscheinlich nicht erwartet.

Wenn ich Dich richtig Verstanden habe, möchtest Du etwas in der Form
Delphi-Quellcode:
 
asm
  //Result:= MyObject.FMyStyle.FMyFont.FMyColor
  mov eax, TMyClass([ebx]).FMyStyle.FMyFont.FMyColor
end
haben, oder?

Dieser Source lässt sich leider nicht innerhalb eines Statements derefferenzieren und würde auch vom Delphi-Compiler in mehrere Statements übersetzt werden.
Sollte es sich bei einem Attribute allerdings um einen Record handeln, lässt sich der Zugriff über ein Displacement erreichen und Delphi übersetzt es korrekt:
Delphi-Quellcode:
  TMyClass = class
    FAttribute : TPoint;
  end;
var
  myObject: TMyClass;
begin
  myObject:= TMyClass.Create;
  myObject.FAttribute.x:= 42;
  myObject.FAttribute.y:= 23;

  asm
    mov ebx, myObject
    mov ecx, TMyClass([ebx]).FAttribute.y
  end;
Ich habe den Fall mit Deinem D5-Enterprise als auch mit D7-Architect getestet. Leider gibt der Compiler im Fall eines aggregierten Objekts weder eine Warnung noch einen Fehler aus.

mr2 17. Nov 2003 14:59

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Hallo,

ich hab' zwar nur wenig Plan von Assembler, aber ich frage mich ernsthaft:

Warum?

Private Felder sind nun mal privat und wenn ihr über Trick 17 versucht direkt darauf zuzugreifen, macht ihr damit das ganze Konzept von OOP & Kapselung zu nichte :wall:

Ihr solltet lieber darüber nachdenken ob das wirklich nötig ist und ob es nicht einen einfacherern Weg gibt.

OLLI_T 17. Nov 2003 15:00

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Hallo Choose!

Erst mal vielen Dank für Deine ausführliche Nachricht! 8)

Das mit den Record-Typen ist klar. Hab ich ja auch in meinem Code verwendet (TRect).

Ich erwarte auch gar nicht, dass der Compiler das Statement innerhalb eines Befehls dereferenzieren kann. Aber ich möchte wenn nur irgend möglich Befehle wie: [ebx+$04] vermeiden. Da sind Fehler im warsten Sinne des Wortes vorprogrammiert. Es muss doch einen Weg geben, dem Compiler begreiflich zu machen, dass im Register ebx nicht der Zeiger auf das Objekt Self sondern eben ein anderes Objekt steht. :gruebel:

OLLI

OLLI_T 17. Nov 2003 15:12

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Hallo mr2!

Hier geht es nicht um Trick 17 sondern um Perfomance. OOP find ich geil sonst würde ich nicht für meine NONVCL Anwendungen konsequent (fast) alles in Klassen kapseln.

Die beschriebene Methode ist privat deklariert und kann von aussen nicht aufgerufen werden.

Beide Klassen sind in derselben Unit von mir deklariert.

Und ob ich nun über property Width:DWord read FWidth ... oder direkt auf FWidth zugreife ist doch wohl Jacke wie Hose oder?

Gruss

OLLI

choose 17. Nov 2003 15:24

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Zitat:

Zitat von mr2
Warum?
Private Felder sind nun mal privat und wenn ihr über Trick 17 versucht direkt darauf zuzugreifen, macht ihr damit das ganze Konzept von OOP & Kapselung zu nichte :wall:

Hallo mr2,
wenn ich OLLI richtig verstehe, ist das nicht das eigentliche Problem: Private Felder sind außerhalb des Scopes auch in ASM nicht sichtbar. Vielmehr scheint er einen performaten Zugriff aus einer Hilfsklasse heraus erreich zu wollen (dies hätte man in C++ mit Friends, in Java mit protected und Packages und in Delphi eben über das selbe PAS-File erreicht). Darüber hinaus sind solche "17er Tricks", die Du zu meinen scheinst, auch mit purem ObjectPascal hinzubekommen...

[Edit]Inzwischen (ich hab mal wieder zulange getippt) hat OLLI das ja bestätigt ;)[/EDIT]

Zitat:

Zitat von OLLI_T
Es muss doch einen Weg geben, dem Compiler begreiflich zu machen, dass im Register ebx nicht der Zeiger auf das Objekt Self sondern eben ein anderes Objekt steht.

Hallo OLLI,

das sollte so Funktionieren (nur mit D7 getestet):
Delphi-Quellcode:
type
  TMyClass = class
  private
    FAttribute : Integer;
  public
    constructor Create(AValue: Integer);
  end;

constructor TMyClass.Create(AValue: Integer);
begin
  inherited Create;
  FAttribute:= AValue;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  myObject: TMyClass;
  myInt: Integer;
begin
  myObject:= TMyClass.Create(42);
  try
    asm
      mov ebx, myObject
      mov eax, TMyClass([ebx]).FAttribute
    end
  finally
    myObject.Free;
  end;
end;
in eax landet, wir erwartet, 0x2a.

Vielleicht kannst Du einmal eine Zuweisung in der Delphi Language schreiben, die Du gerne 1:1 in ASM hättest?

OLLI_T 17. Nov 2003 16:44

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Hallo Choose!

Ich brauch nix mehr in Delphi zu schreiben, weil es mit dem einfachen Cast prima funktioniert! :-D

Schon peinlich, dass ich darauf nicht selber gekommen bin bei meinen wilden Cast Konstrukten.

Ich danke Dir jedenfalls für Deine konstruktive Hilfe. :thuimb:

Zur Info hier noch kurz ein Codeausschnitt:

Delphi-Quellcode:
    mov  ebx, [eax].FMask       // hier übertrage ich den Zeiger auf das Objekt FMask der Klasse TDIBMask ins Register ebx
    cmp  edx, TDIBMask([ebx]).FWidth  // Nun ist der Zugriff auf die Variablen möglich!

  cmp  edx, TDIBMask([eax].FMask).FWidth   // Sowas wird durchaus compiliert aber falsch ausgewertet!!!!
Viele Grüsse

OLLI

negaH 18. Nov 2003 08:03

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Korrekt, da FMask ein Zeiger ist der im "Record" eines Objectes steht, musst du selber dereferenzieren. Ein Machinenbefehl zum direkten derefernezieren mehrerer Referenzen existiert dafür nicht. Es liegt also nicht an Delphi oder Assembler, sondern einfach an den Möglichkeiten der CPU.

Desweiteren nutze nicht deine obige Art des "Typcast" in ASM. Besser ist:
Delphi-Quellcode:
asm
   MOV EBX,[EAX].TMyClass.FMask
   CMP EAX,[EBX].TMaskClass.FRect.Left
end;
Ab D7 ist deine obige Syntax ungültig.

Gruß Hagen

choose 18. Nov 2003 08:15

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Zitat:

Zitat von negaH
Desweiteren nutze nicht deine obige Art des "Typcast" in ASM. Besser ist:
Delphi-Quellcode:
asm
   MOV EBX,[EAX].TMyClass.FMask
   CMP EAX,[EBX].TMaskClass.FRect.Left
end;
Ab D7 ist deine obige Syntax ungültig.

Hallo Hagen,

beide Varianten werden sowohl in D5 (Anforderung) als auch in D7 (Deine Aussage) zu idetischem Code übersetzt.
Obwohl ich den Cast in der Form AType(AVar) aus Konsistentzgründen mit der übrigen Delphi Language als angenehmer finde und die Variante AVar.AType zu Doppeldeutigkeiten führen kann, sofern man sich nicht an Nameskonventionen hält, wird die von Dir dargestellte Variante in ASM-Abschnitten von Borland verwendet (zB Unit System).

Ich denke, dass man aus diesem Grund tatsächlich die zweite Notation verwenden sollte.

negaH 18. Nov 2003 08:28

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Nein, ab D7 funktionieren nur die zweite Notation als Typcast, bzw genauer gesagt als korrekter Qualifier eines Recordelementes. Denn die erste Notation ist ein Pascaltypischer Typcast, die zweite Notation aber ein in Asm reguklärer Strukturqualifier.

Delphi-Quellcode:
type
  TMyObject = class
    FProc: procedure(const Data; DataSize: Integer) of object;
  end;

procedure TMyObject.Calc(const Data; DataSize: Integer); assembler;
asm
    JMP  DWord Ptr [EAX].FProc.TMethod.Code // in D7 

    JMP  TMethod([EAX].FProc).Code          // bis D7 möglich
end;
Beides wären regulär gültige Syntax, aber nur der erste Fall wird ab D7 durch den inline Asm akzeptiert. Betrachtet man meine obige Aussage was der Unterschied beider Syntax sind (Typcast <> Qualifier) dann ist dies auch logisch. Auch ich habe dies erneut lernen müssen :) und meine nun das es korrekt war in D7 diese Syntax zu straffen.

Gruß Hagen

choose 18. Nov 2003 08:50

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Interessant, Hagen.

Delphi-Quellcode:
JMP  TMethod([EAX].FProc).Code
funktioniert unter D7 tatsächlich nicht,
Delphi-Quellcode:
JMP  TMethod([EAX]).Code
hingegen schon :gruebel:

Ich konnte leider nichts zu "struktur qualifier", "struktur qualifizierer", "structure qualifier", etc. in unserem Kontext finden. Könntest Du auf eine Quelle verweisen oder das Prinzip, dass Du entdeckt hast, näher darstellen?

Vielen Dank!

negaH 18. Nov 2003 09:49

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
"Ich" habe da nichts entdeckt.

Es ist üblich in Assembler einen qualifizierten Bezeichner zu benutzen. Statt also einen Zeiger zu typcasten = umzubiegen und dem Compiler weiszumachen das der untypisierte Zeiger vom korrekten Typ ist, wird bei einem qualifizierten Bezeichner durch die Qualifizierung an sich Typsicherheit erzeugt.

Statt also
Code:

  TTier(Auto).Laufe

wird

 Auto.TTier.Laufe
wobei aber der Unterschied in Assembler besteht das EAX als Register das Auto enthält aber von sich aus ohne Typ ist. D.h. Auto im obigen Beispiel wäre in PASCAL ein Typ mit Typisierung TAuto und in Assembler ein stinknormaler Zeiger. Deshalb ist ein qualifizierter Bezeichner in Asembler auch ein impliziter "Typcast". Da aber in Assembler noch nie Typsicherheit bestanden hat, und man sich diese aber wünschte, hat man angefangen in die asm-Syntax "Record Typen" mit obiger Syntax ein wenig besser Typsicher zu machen.
Der Unterschied besteht also in der Denkweise. Ein Typcast soll einen bestehenden Typ so erscheinen lassen als wäre es ein anderer Typ. Damit hebelt ein Typcast die Sicherheiten der Typüberprüfung des Compilers aus. Ein qualifizierter Bezeichner soll dagegen aus einer Typlosigkeit eine Typsicherheit erschaffen. Auf die Frage in diesem Thread projeziert heisst das das ein PASCAL like Typcast nichts in Assembler zu suchen hat, und das in Assembler die qualifizierten Bezeichner benutzt werden sollten.

Das eine zerstört Typsicherheit und das andere erschafft sie.

Gruß Hagen

OLLI_T 18. Nov 2003 09:53

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Hallo Hagen!

Vielen Dank für Deine Ausführungen. Es ist immer wieder ein Genuss, Deine Beträge zu studieren.

Du hast folgendes Beispiel angeführt, wobei ich ehrlich gesagt noch nicht ganz verstehe, was der Code bezwecken soll.
Delphi-Quellcode:
type
  TMyObject = class
    FProc: procedure(const Data; DataSize: Integer) of object;
  end;

procedure TMyObject.Calc(const Data; DataSize: Integer); assembler;
asm
    JMP  DWord Ptr [EAX].FProc.TMethod.Code // in D7 

    JMP  TMethod([EAX].FProc).Code          // bis D7 möglich
end;
Wieso verwendest Du den Operanten JMP und nicht CALL? Es geht doch darum, eine variable Methode zur Berechnung zu implementieren; ein für meine Begriffe sehr elegantes Programmierkonstrukt. Aber wie es geht nach dem Sprung zur Adresse Code und der Abarbeitung der Fkt weiter? Oder wird hier einfach der Stackframe der Methode Calc genutzt? Bitte um Aufklärung!

Viele Grüsse

OLLI

choose 18. Nov 2003 10:12

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Danke für Deine Ausführung Hagen, klar und schlüssig.

@OLLI: Da FProc identisch zur Methode Calc deklariert ist, ist in der dortigen Implementierung alles notwendige zum "Aufräumen" des Stacks und zum "Rückspringen" vorhanden (sofern FProc<>nil). Wolltest Du einen Call ausführen und wäre die Deklaration etwas komplexer, so dass nicht ausschließlich Register für die Überhabe verwendet werden oder wäre stattdessen ein stdcall oä verwendet worden, hättest Du diese Werte zunächst kopieren und erneut auf den Stapel legen müssen. In Hagens Implementierung hingegen, wird alles unangetastet gelassen und der Rücksprüng, der in FProc ausgeführt werden wird, nutzt die Adresse, die vom Klienten eigentlich für Calc gedacht war (ebenso verhält es sich mit den Parametern).

Schön an der Lösung ist, dass man über den impliziten Paramter EAX und das Displacement (Offset des Attributs im Objekt) die Adresse der tatsächlich auszuführenen Methode "gratis mitbekommt".

OLLI_T 18. Nov 2003 11:00

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
HY Choose!

Viel gelernt von Euch beiden! :-D

Also hab ich richtig vermutet. Dann ist auch sicher folgende Sicherheitsabfrage und Default-Berechnung möglich!? Wenn ihr schon mal da seid, quetsch ich euch auch aus.

Delphi-Quellcode:
    CMP  [EAX].FProc, 0
    JNE  DWord Ptr [EAX].FProc.TMethod.Code // in D7
    XOR  EAX, EAX
 @Loop:
    ADD EAX, [EDX]
    INC EDX
    DEC ECX
    JNZ @Loop
OLLI

choose 18. Nov 2003 11:18

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Hallo OLLI,

ich gehe davon aus, dass Deine Implementierung nur Beispielhaft ist (es handelt sich um eine Prozedur, das Ändern von EAX bleibt daher ohne Effekt).

Eine Default-Implementierung ist mit Sicherheit eine gute Sache, ich persönlich würde sie aber wiederum als Delphi-Methode implementieren und diese stattdessen absolut anspringen. Sofern Du nicht das letzte an Performance herausholen möchtest oder die Implementierung trivial ist (zB Result:= 0) würde ich immer zugunsten der Wartbarkeit und Übersicht entscheiden (zB mit Struktur-Qualifizierern ;)).

Zwar macht Delphi keine Annahmen über irgendwelche Flags, allerdings möchte ich zu bedenken geben, dass der Vergleich
Code:
CMP  [EAX].FProc, 0
eben das Flagregister verändert. Solltest Du hinter FProc eine ASM-Implementierung legen, die auch aus anderen Situationen verwendet wird und dort weitere Informationen aus den Flags verwendet, solltest Du das noch berücksichten (weit hergeholt, ich weiß).

negaH 18. Nov 2003 14:33

Re: Delphi Inline Assembler Zugriff auf Privates Objekt
 
Zitat:

Wieso verwendest Du den Operanten JMP und nicht CALL? Es geht doch darum, eine variable Methode zur Berechnung zu implementieren; ein für meine Begriffe sehr elegantes Programmierkonstrukt. Aber wie es geht nach dem Sprung zur Adresse Code und der Abarbeitung der Fkt weiter? Oder wird hier einfach der Stackframe der Methode Calc genutzt? Bitte um Aufklärung!
Naja es stammt aus meinem Delphi Encryption Compendium. Ich hatte zwei Methoden der Blowfish Verschlüsselung programmiert. Eine für CPU <= i386 und eine für CPU's ab i486. Bei der Erstellung eines Blowfish Cipher Objectes wurde nun in FProc die Methode hinterlegt die zur CPU passte.

D.h. in EAX = Self, EDX = Data, ECX = Datasize, somit besteht der "Stackframe" in .Calc() nur aus der Rücksprungadresse. Ein CALL nach FProc würde also zusätzlich ein PUSH + CALL + RET mehr kosten als ein JMP. Die Methode in FProc kehrt also durch dem JMP sofort zum Aufrufer von .Calc() zurück.

Man hätte also CALL nehmen können, aber warum ein PUSH + RET verschenken wenn man ein JMP nehmen kann ohne großen Aufwand im Quelltext zu machen.
Es ging mir also nicht unbedingt darum den letzten Taktzyklus aus der CPU rauszuquetschen sondern nur darum das der Tipaufwand im Quelltext jeweils gleich ist, aber 2 Taktzyklen und ein paar Branches/Misses eleminiert werden.

Wenn durch den JMP noch zusätzliche Parameteränderungen nötig gewesen wären dann hätte ich den CALL bevorzugt.

Nungut, es sollte auch nur ein Beweis sein das ab D7 der inline Assembler strenger ist als in der Hilfe beschrieben wird.

Gruß Hagen


Alle Zeitangaben in WEZ +1. Es ist jetzt 13:26 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz