Translate Delphi style ASM to English?
Asked Answered
M

2

6

A recent Delphi project i've inherited has a procedure in ASM. I'm a complete ASM newbie, so i dont understand it. I've read up on the various ASM instructions to try and decipher the procedures flow, but i still dont get it.

Could someone with ASM experience assist my understanding and translate the following procedure to English (then i can translate back to Delphi so the code is easier to read in the future!!!)

The declaration of Mem1 is an array [0..15] of Byte;. And Mem2 is a LongInt.

Here's the procedure:

 procedure TForm1.XorMem(var Mem1; const Mem2; Count : Cardinal); register;
 begin
 asm
   push esi
   push edi

   mov  esi, eax         //esi = Mem1
   mov  edi, edx         //edi = Mem2

   push ecx              //save byte count
   shr  ecx, 2           //convert to dwords
   jz   @Continue

   cld
 @Loop1:                 //xor dwords at a time
   mov  eax, [edi]
   xor  [esi], eax
   add  esi, 4
   add  edi, 4
   dec  ecx
   jnz  @Loop1

 @Continue:              //handle remaining bytes (3 or less)
   pop  ecx
   and  ecx, 3
   jz   @Done

 @Loop2:                 //xor remaining bytes
   mov  al, [edi]
   xor  [esi], al
   inc  esi
   inc  edi
   dec  ecx
   jnz  @Loop2

 @Done:
   pop  edi
   pop  esi
 end;

 end;

edit: Thanks to Roman R i've converted the ASM back to Delphi

procedure TForm1.XorMem2(var Mem1; const Mem2 :LongInt; Count : Cardinal);
var
  Key : array [0..3] of byte absolute Mem1;
  Num : array [0..3] of byte absolute Mem2;
  idx : byte;
begin
  for Idx := 0 to Count -1 do Key[idx] := Key[idx] Xor Num[idx];
end;
Moldavia answered 25/10, 2011 at 5:46 Comment(5)
Take care that the Mem2: Longint is not good in your code. Only if Count is <=4. And if you compile with range checking ON, this method will fail with Count>4.Voroshilovsk
The No. 1 aid to reading assembler (or any code, really) is to already know what it's supposed to do. Knowing what it's supposed to do gives you an idea of how you would implement it, so you can recognize various parts of the code and fit them into your own mental model of what the code's supposed to look like. In this case, the function signature pretty much tells you exactly what the code does, which makes translating out of assembler easy.Sherrylsherurd
@Rob I believe that this is what OP was asking. Could someone that knows ASM please tell me what this routine is supposed to do?Socratic
You don't have to know assembler for that, @Socratic Just look at the function signature. It takes two untyped memory buffers, one of which is modifiable, and a size. The function name tells us it applies the xor operation to them. Knowing that much, it's pretty easy to guess that Mem1 receives the result of xoring Mem2 with something. We can't yet be certain that the other operand is Mem1, but it's a reasonable guess, and that gives us something to watch for when reading the code.Sherrylsherurd
@Arnaud yep good point; i should probably take out the Count alltogether because its only ever the first 4 bytes which seem to affect the original arrayMoldavia
M
9

The function accepts two pointers (to arrays whatever) and their lengths in bytes. The function performs byte-to-byte XOR operation on first array bytes (Mem1) using second array bytes (Mem2). Pseudo-code:

for Index = 0 to Count - 1
  (Mem1 as Byte Array) [Index] = (Mem1 as Byte Array) [Index] Xor (Mem2 as Byte Array) [Index]
Mistrot answered 25/10, 2011 at 5:56 Comment(4)
Wow, thanks! This makes sense as it appears in your pseudo code; i dont imagine there would be much speed benefit using the original ASM version if this is all it looks like as a rough pascal versionMoldavia
Assembly version tends to process 4 bytes at once and then the remainder byte by byte. I am not sure if the performance gain is actually worth bothering. Note that with SIMD instructions you can do more bytes at once, which might be even faster.Mistrot
For values this small, there won't be much of a speed benefit. The function is trying to xor 4 bytes at a time, so it may very well be faster. But you'll have to call it many times, to notice.Melanson
@Roman Reading a DWORD at once makes sense if data is DWORD aligned. Otherwise, it can be effectively slower. The asm is strange: for instance, there is a cld opcode which looks like a relic of some previous version using lodsd / stosd. In short: the supplied asm is far from optimized. For small amount of data, a small Delphi loop will be fast enough, and much faster to convert to 64 bit (e.g.).Voroshilovsk
V
4

Here is a working and simple pure pascal version:

procedure TForm1.XorMem(var Mem1; const Mem2; Count : Cardinal);
var i: integer;
    M1: TByteArray absolute Mem1;
    M2: TByteArray absolute Mem2;
begin
  for i := 0 to Count-1 do
     M1[i] := M1[i] xor M2[i];
end;

Here is an optimized version using DWORD reading:

procedure TForm1.XorMem(var Mem1; const Mem2; Count : Cardinal);
var i, n: integer;
    M1: TByteArray absolute Mem1;
    M2: TByteArray absolute Mem2;
    I1: TIntegerArray absolute Mem1;
    I2: TIntegerArray absolute Mem2;
begin
  n := Count shr 2;
  for i := 0 to n-1 do
     I1[i] := I1[i] xor I2[i];
  n := n shl 2;
  for i := 0 to (Count and 3)-1 do
     M1[n+i] := M1[n+i] xor M2[n+i];
end;

IMHO the 2nd version is not mandatory. Reading a DWORD at once makes sense if data is DWORD aligned. Otherwise, it can be effectively slower. For small amount of data, a small Delphi loop will be fast enough, and clear to read. Latest CPUs (like Core i5 or i7) make wonders when using a small code loop (unrolling a loop is not necessary any more).

Voroshilovsk answered 25/10, 2011 at 12:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.