Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Delphi Assembler Parameter auf Stack legen (https://www.delphipraxis.net/211945-assembler-parameter-auf-stack-legen.html)

mwilms 24. Nov 2022 19:18

Assembler Parameter auf Stack legen
 
Hallo,

weiß jemand, wie man in Assembler Parameter für eine aufzurufende Procedure auf den Stack legt?

Delphi-Quellcode:
procedure GetParm(Par1: Int64; Par2: Int64); stdcall;
asm
  nop
end;

procedure SendPar; stdcall;
asm
  mov rax,2
  push rax
  mov rax,1
  push rax
  call GetParm
end;
scheint nicht zu funktionieren. Par1 und Par2 weisen die Werte 1 bzw. 2 nicht auf.

Habt ihr eine Info-Quelle?

himitsu 24. Nov 2022 21:00

AW: Assembler Parameter auf Stack legen
 
Delphi-Quellcode:
procedure GetParm(Par1: Int64; Par2: Int64); stdcall;
begin
  //
end;

procedure SendPar; stdcall;
begin
  GetParm(1, 2);
end;
Und jetzt kannst du im Debugger abgucken, wie Delphi es macht. :zwinker:

Lyan 24. Nov 2022 21:40

AW: Assembler Parameter auf Stack legen
 
Du versucht hier 32-Bit ASM in x64 zu schreiben.

Du solltest dir erstmal die x64 calling convention anschauen, google. Ich erkläre dir mal eben die Basics:

Für nicht float Werte:
Arg1: Rcx, Arg2: Rdx, Arg3: R8, Arg4: R9 und alle weiteren Argumente werden auf dem Stack abgelegt (gleich mehr dazu).

Für float Werte:
Arg1: xmm0, Arg2: xmm1, Arg3: xmm2, Arg4: xmm3 und dann wieder Stack..

In x64 musst du Platz auf dem Stack machen bevor due eine Funktion callst, nennt sich Shadowspace oder scratchspace. Da die ersten vier Parameter via register passed werden, musst du 8*4 also 32 oder 0x20 bytes auf dem Stack reservieren, das heißt Argument 5 fängt dann effektiv bei Rsp-0x28 an. Außerdem musst du den Stackpointer RSP 16byte alignen, weil sonst sowas wie movabs rax, [rsp+x] crashen würde, da das "a" in movaps für aligned steht (gibt auch movups).

Für deinen Code würde das nun bedeuten:

Delphi-Quellcode:
procedure GetParm(Par1: Int64; Par2: Int64); stdcall;
asm
  nop
end;

procedure SendPar; stdcall;
asm
  mov rcx, 1
  mov rdx, 2
  call GetParm
end;
Da du hier keine "naked" funktion erstellst, wird der compiler automatisch rsp reservieren und shadowspace erstellen. Du könntest es auch manuell machen:
Delphi-Quellcode:
procedure GetParm(Par1: Int64; Par2: Int64); stdcall;
asm
  nop
end;

procedure SendPar; stdcall;
asm
  .NOFRAME
  push rbp // nicht volatiles register rbp auf stack speichern
  mov rbp, rsp
  sub rsp, 0x28 // shadowspace + 8 byte für alignment
  and rsp, not 8 // stack 16 byte alignen
  mov rcx, 1
  mov rdx, 2
  call GetParm
  mov rsp, rbp
  pop rbp
  ret
end;

grüße

himitsu 25. Nov 2022 00:24

AW: Assembler Parameter auf Stack legen
 
ach stimmt ja.

Delphi-Quellcode:
stdcall;
wird hier einfach ignoriert.
64 Bit kennt nur noch eine Calling-Convention, welche zufällig etwas so aussieht wie in Win32 die Pascal-Convention von Delphi, also der Aufrufer verwaltet den Parameter-Speicher, dann erst in die Register, Rest auf den Stack und mit StackFrame drumrum.

mwilms 25. Nov 2022 09:41

AW: Assembler Parameter auf Stack legen
 
Super Antworten! Vielen Dank!

Wo lernt man sowas? Gibt´s dazu Literatur, die man bestellen kann? Oder alles aus dem Internet?

VG Markus

Neutral General 25. Nov 2022 10:00

AW: Assembler Parameter auf Stack legen
 
Zitat:

Zitat von mwilms (Beitrag 1515352)
Super Antworten! Vielen Dank!

Wo lernt man sowas? Gibt´s dazu Literatur, die man bestellen kann? Oder alles aus dem Internet?

VG Markus

Google, bzw z.B. https://learn.microsoft.com/en-us/cp...?view=msvc-170

markus888 25. Nov 2022 10:53

AW: Assembler Parameter auf Stack legen
 
Hab gestern auch schon in die Doku von MS geschaut:

Bei den Parameter-Beispielen war ich dann aber doch überrascht.

Zitat:

func1(int a, int b, int c, int d, int e, int f);
// a in RCX, b in RDX, c in R8, d in R9, f then e pushed on stack
Zitat:

func2(float a, double b, float c, double d, float e, float f);
// a in XMM0, b in XMM1, c in XMM2, d in XMM3, f then e pushed on stack
Zitat:

func3(int a, double b, int c, float d, int e, float f);
// a in RCX, b in XMM1, c in R8, d in XMM3, f then e pushed on stack
Wie man bei Beispiel 3 sieht, wird RDX ausgelassen, weil der 2. Parameter double ist.

himitsu 25. Nov 2022 12:51

AW: Assembler Parameter auf Stack legen
 
Gut, so ist es wenigstens Eindeutig.
1. Paramater immer in RCX oder XMM0
2. Paramater immer in RDX oder XMM1
3. Paramater immer in R8 oder XMM2
4. Paramater immer in R9 oder XMM3
Rest auf dem Stack

Oder anders ausgedrückt in XMM3 pder R9 ist immer der 3. Parameter oder nichts.

---

Der Performance halber hätte ich möglichst viel auf den Stack gelegt,
aber größere Methoden kopieren das intern eh wieder auf den Stack, um das Register nutzen zu können.

Und eine Funktion mit 8 Parametrn (hälfte Floats), würde immer die Register RCX/RDX/R8/R9/XMM0/XMM1/XMM2/XMM3 belegen,
egal in welcher Reihenfolge die Parameter vorkämen.
(alles Weiter dann erst auf dem Stack, also ab dem 5. Float, bzw. dem 5. anderen Parameter erst das Jeweilige auf den Stack)

markus888 25. Nov 2022 19:46

AW: Assembler Parameter auf Stack legen
 
Zitat:

Zitat von Lyan (Beitrag 1515329)
In x64 musst du Platz auf dem Stack machen bevor due eine Funktion callst

Ich verstehe nicht, warum der Delphi Compiler das nicht macht.

Wenn ich eine Funktion/Prozedur aufrufe, dann reserviert immer die aufgerufene Funktion/Prozedur den Speicher.
Dem Aufrufer ist theoretisch ja auch nur der benötigte Stapelspeicher für die übergebenen Variablen bekannt.
Sprich der Vorgang wäre doppelt erforderlich, da der aufgerufene Code ja auch noch den Speicher für die internen Variablen reservieren müsste.

Wo siehst du den Vorteil, wenn der Aufrufer den Platz für die übergebenen Variablen reserviert?

Hier ein stupider Code:

Code:
function work_2(i,e:byte):Boolean;
var x:Integer;
begin
  x:=e+i;
  work_2:= x>0;
end;
function work_1(t, y :double;i,e,z,k:byte):boolean;stdcall;
var
  v:array[0..100] of Byte;
begin
 v[0]:= i;
 v[i]:= e;
 work_1:= (t<y) and work_2(i,e);
end;

begin
  var x:double;
  try
  work_1(3.5, 134.1, 20,30,10,40);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
Zitat:

Zitat von Stapelspeicher Reservierung bei Beginn von work_1
X64Fpu.dpr.25: begin
push rbp
sub rsp,$0000000000000090
mov rbp,rsp


Zitat:

Zitat von Funktion Rückgabe ins Register schreiben und Speicher freigeben bei end von work_1
X64Fpu.dpr.29: end;
movzx rax,byte ptr [rbp+$0000008f]
lea rsp,[rbp+$00000090]
pop rbp

Der Aufrufer reserviert nichts, das macht nur der Empfänger - warum hält sich Delphi hier nicht an die Vorgaben?
Weil was ist bei einer dll - da habe ich ja keinen Einfluss auf den Aufrufer - oder verhält sich der Compiler dann Normkonform?
Bei 32 Bit mit inline asm musste man sich damit ja nicht beschäftigen.
Da ich aber mit x64 noch keine asm Erfahrung habe, wäre es interessant hier eine universelle Lösung zu haben.

Edit: Hab jetzt etwas genauer hingesehen, und merke grade dass ich das Wesentliche mangels Erfordernis noch nicht ganz verstanden habe.
Der Stack-Base Pointer wird ja durch den call gesetzt. Muss mich da noch etwas vertiefen.

himitsu 26. Nov 2022 00:12

AW: Assembler Parameter auf Stack legen
 
Jupp, der Aufrufer kann sich garnicht um sowas kümmern ... woher soll er wissen was wie in der Funktion gemacht wird, und außerdem könnte es sich auch mal ändern.
Aber ja, rein von den Daten ist es schon möglich, dass der Aufrufer bereits Platz auf dem Stack vorbereitet, für alle Parameter in den Registern ... aber wenn, dann wäre es doch eigentlich schwachsinnig "eventuell" nutzlos sowas zu machen, falls es dann doch nicht benutzt wird. Da könnte man auch gleich ALLEs auf den Stack legen und sich das mit den Registern sparen.


Was aber geht, wie man es z.B. unter Win32 mit den verschiedenen CallingConventions kennt,

* wer den Speicher freigibt, also wer den Stack nach der Ausführung zurücksetzt
meißtens macht es die Funktion, welche normal weiß was sie für Parameter hat,
aber kennt sie es nicht, dann muß es der Aufrufer machen.
-> ein Beispiel sind VarArgs (beim cdecl), wo nur der Aufrufer zu 100% weiß, was er wirklich rein gab.

* oder eben wo und in welcher Reihenfolge die Parameter übergeben werden
-> Register oder ausschließlich auf dem Stack
-> von vorwärts oder rückwärts, bzw. von links nach rechts oder andersrum

So gesehn ist es praktisch, wenn es nur noch eine "offizielle" Variante gibt.
Ja, natürlich kann man dennoch sonstwelchen Mist bauen und es nach belieben machen, aber schon alleine für's Debuggen ist es günstig, wenn es möglichst wenig Vaiationen gibt, weil dann Compilerunabhängig alles gleich analysiert werden kann.


Alle Zeitangaben in WEZ +1. Es ist jetzt 02:04 Uhr.
Seite 1 von 2  1 2      

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