Inline Assembler in Delphi (III) Static Arrays

Inline Assembler in Delphi (III)
Static Arrays


By Ernesto De Spirito < edspirito@latiumsoftware.com>


Passing static arrays as parameters



Static arrays parameters are passed as pointers to the first element of the array, independently of whether the parameter is passed by value or by reference (either as "var" or as "const").

Given the following declarations...

  const
     ARRAY_MAX = 5;

  type
    TArrayOfInt = packed array [0.. ARRAY_MAX] of longint;

  var
    a, b: TArrayOfInt;

  procedure InitializeArray(var a: TArrayOfInt);
  var
    i: integer;
  begin
    for i := 0 to ARRAY_MAX do
      a[i] := i;
  end;

...the call to the procedure InitializeArray in assembler would be like this:

    // In Object Pascal:
    //   InitializeArray(a);
    // In Inline Assembler:
    asm
      mov eax, offset a        // EAX := @a;
      call InitializeArray     // InitializeArray;
    end;

OFFSET is an assembler unitary operator that returns the address of a symbol. OFFSET is not applicable to local symbols. You should use the LEA opcode (see below), which is more "universal".


Static arrays passed by value


If the array is passed by value, it is responsibility of the called function to preserve the array. When a function needs to change the values of one or more elements of an array passed by value, normally it creates a local copy and works on the copy. The compiler creates a copy for us in the "begin" of Pascal procedures and functions, but in full assembler procedures and functions we have to do it by ourselves. One way of doing it is like this:

  procedure OperateOnArrayPassedByValue(a: TArrayOfInt);
  var
    _a: TArrayOfInt;
  asm
    // Copy the elements of "a" (parameter) in "_a" (local copy)
    push esi                      // Saves ESI on the stack
    push edi                      // Saves EDI on the stack
    mov esi, eax                  // ESI := EAX; // @a
    lea edi, _a                   // EDI := @_a;
    mov eax, edi                  // EAX := EDI; // @_a
    mov ecx, type TArrayOfInt     // ECX := sizeof(TArrayOfInt);
    rep movsb                     // Move(ESI^, EDI^, ECX);
    pop edi                       // Restores EDI from the stack
    pop esi                       // Restores ESI from the stack

    // Here goes the rest of the function. We'll work on "_a" (the
    // local copy), whose first element is now pointed by EAX.
  end;

The new things here are the LEA and MOVSB opcodes, the REP prefix, and the TYPE operator, described below:


LEA  (Load Effective Address)


Moves to the first operand the address of the second. Here we compare LEA with MOV:

   Instruction           Translated as          Effect
  -------------------------------------------------------------------

   lea eax, localvar     lea eax, [ebp-$04]     EAX := @localvar;
                                                EAX := EBP - $04;

   mov eax, localvar     mov eax, [ebp-$04]     EAX := localvar;
                                                EAX := (EBP - $04)^;


MOVSB (MOVe String Byte)


Copies the byte pointed by ESI to the location pointed by EDI, and increments ESI and EDI so they point to the next byte. The work of MOVSB can be described as follows:

  ESI^ := EDI^;    // Assume ESI and EDI are of type PChar
  Inc(ESI);
  Inc(EDI);

Notes:

  1. MOVSW and MOVSD are the Word (16-bit) and DWord (32-bit) versionsrespectively (ESI and EDI are incremented by 2 and 4 respectively).
  2. The registers are decremented if the Direction Flag is set.



REP
---

The REP prefix is used in string operations to repeat the operation decrementing ECX until ECX is zero. The work of REP could be described as follows:

  // rep string_instruction

  @@rep:
    string_instruction
    loop @@rep

Notes:

  • REP is not a shorthand for a code like the above. It works a lot faster.
  • The value of ECX is not checked at the beginning of the loop (if ECX is zero, the instruction would be repeated 2^32 times, but will generate an AV long before that, as soon as ESI or EDI point to an invalid memory location).



TYPE


The TYPE operator is a unary operator evaluated at compile time, and it
returns the size in bytes of the operand, which must be a data type. For
example, TYPE WORD will return 2 and TYPE INTEGER will return 4.


Accessing the elements of an array



To access an element a[i] we need the values "@a[0]" and "i" in registers (like EDX and ECX, for example), and then we can use memory addressing as follows:

  lea edx, a                      // EDX := @a;
  mov ecx, i                      // ECX := i;
  mov ax, [edx+ecx*type integer]  // AX := EDX[ECX];  // a[i];
       // PWord(EDX + ECX * SizeOf(integer))^

In the example, we assumed that the elements have 2 bytes (we moved the value of a[i] to AX, a 16-bit register), that the array is not a packed one (each element actually occupies 4 bytes, the size of an integer, so this value was used to compute the position of the element), and that the array is zero-based. For example:

    var a: array [0..N] of word = (1, 2, 3, 6, ...);

    +------ EDX = @a
    |
    v
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+--
  | 1 | 0 |   |   | 2 | 0 |   |   | 3 | 0 |   |   | 6 | 0 |   |   |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+--
    a[0]             a[1]            a[2]            a[3]
    [edx]          [edx+04]        [edx+08]        [edx+12]

If the array is not zero-based, we have to adjust the value of the index to make it zero-based before addressing the element. Examples:

  // a[1..100]
  :
  mov ecx, i                      // ECX := i;
  dec ecx                         // Dec(ECX); // Adjust ECX
  :

  // a[-10..10]
  :
  mov ecx, i                      // ECX := i;
  add ecx, 10                     // Inc(ECX, 10); // Adjust ECX
  :

The procedure InitializeArray (introduced above) can be implemented in assembler like this:

  procedure InitializeArray(var a: TArrayOfInt);
  asm                                // EAX = PByte(@a[0]);
    xor ecx, ecx                     // ECX := 0;
  @@loop:
    mov [eax+ecx*type integer], ecx  // PInteger(EAX+ECX*4)^ := ECX;
                                     //  ...or  EAX[ECX] := ECX;
    inc ecx                          // ECX := ECX + 1;
    cmp ecx, ARRAY_MAX               // if ECX <= ARRAY_MAX then
    jle @@loop                       //   goto @@loop;
  end;

Or like this:

  procedure InitializeArray(var a: TArrayOfInt);
  asm                      // EAX = @a[0];
    xor ecx, ecx           // ECX := 0;
  @@loop:
    mov [eax], ecx         // EAX^ := ECX;
    inc ecx                // Inc(ECX);
    add eax, type integer  // Inc(EAX); // Point to the next element
    cmp ecx, ARRAY_MAX     // if ECX <= ARRAY_MAX then
    jle @@loop             //   goto @@loop;
  end;


Returning array values



Functions returning arrays receive an additional last parameter which is the pointer to the memory location where they should place their return value (memory is allocated and freed if necessary by the caller). For example, let's consider the following function:

  function ReverseArray(const a: TArrayOfInt): TArrayOfInt;
  var
    i: integer;
  begin
    for i := 0 to ARRAY_MAX do
      Result[i] := a[ ARRAY_MAX-i];
  end;

The function receives two parameters:

  1. EAX = the address of the first element of the array "a"
  2. EDX = the address of the first element of Result


The function can be rewritten in assembler as follows:

  function ReverseArray(const a: TArrayOfInt): TArrayOfInt;
  asm                                // EAX = @a[0]; EDX = @Result[0];
    push ebx                         // Save EBX
    mov ebx, eax                     // EBX := EAX;
    xor ecx, ecx                     // ECX := 0;
  @@loop:
    mov eax, ARRAY_MAX
    sub eax, ecx                     // EAX := ARRAY_MAX-ECX;
    mov eax, [ebx+eax*type integer]  // EAX := EBX[EAX];
    mov [edx+ecx*type integer], eax  // EDX[ECX] := EAX;
    inc ecx                          // ECX := ECX + 1;
    cmp ecx, ARRAY_MAX               // if ECX <= ARRAY_MAX then
    jle @@loop                       //   goto @@loop;
    pop ebx                          // Restore EBX
  end;

Well, this is it for now. In the next issue we'll see how to work with records.




Previous: Inline Assembler in Delphi (II) - ANSI strings
Next: Inline Assembler in Delphi (IV) - Records


转载于:https://www.cnblogs.com/abcliu110/archive/2010/11/02/1866809.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Here's an example of inline assembler code that calculates the length of a string: ```c #include <stdio.h> int main() { char str[] = "Hello, world!"; int len; asm volatile ( "movl $0, %0\n\t" // initialize counter to 0 "1:\n\t" "movb (%1), %%al\n\t" // load next character into al "cmpb $0, %%al\n\t" // compare with null terminator "je 2f\n\t" // if equal, jump to end "incl %0\n\t" // increment counter "incl %1\n\t" // increment pointer "jmp 1b\n\t" // jump to beginning of loop "2:\n\t" : "=r" (len) // output: len = counter : "r" (str) // input: str = pointer to string : "%eax" // clobbered register: eax ); printf("Length of string: %d\n", len); return 0; } ``` Explanation: - The `asm volatile` statement tells the compiler that this is inline assembler code that may modify registers and memory, and that it should not optimize or reorder instructions. - The `movl $0, %0` initializes the counter to 0 and assigns it to the output variable `len`. - The label `1:` marks the beginning of a loop that processes each character of the string. - The `movb (%1), %%al` instruction loads the next character into the `al` register. - The `cmpb $0, %%al` instruction compares the character with the null terminator. - The `je 2f` instruction jumps to the label `2:` if the character is a null terminator. - The `incl %0` instruction increments the counter. - The `incl %1` instruction increments the pointer to the next character. - The `jmp 1b` instruction jumps back to the beginning of the loop. - The label `2:` marks the end of the loop. - The output operand `=r` (len) tells the compiler that `len` is an output variable that will be stored in a register. - The input operand `"r" (str)` tells the compiler that `str` is an input variable that will be stored in a register. - The clobbered register `%eax` tells the compiler that the `eax` register may be modified by the inline assembler code.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值