Thursday, May 17, 2012

Assembly, day 4 - memory modes

'Today' I wanted to learn about the different addressing modes available in x86 assembly, so I wrote a few functions which fill an array of bytes in various ways.

The addressing modes are: a constant (e.g, 5), a C variable (I suppose this is some inline assembly magic; e.g., x), the contents of a register (e.g., eax), the value in memory pointed to by a register (e.g., [eax]), that plus a fixed offset (e.g., [eax+5]), or that plus a variable offset ([eax+ecx]).

void fill(unsigned char* aDest, unsigned char aVal, unsigned int aLength)
{
  __asm {
    mov edi, aDest
    movzx ebx, aVal  // because I can!
    mov ecx, aLength
    dec ecx
start:
    mov BYTE PTR [edi+ecx], bl
    loop start
    mov BYTE PTR [edi], bl
  }
}

This function fills the aLength bytes starting at *aDest with aVal. We use loop to do this aLength times (which we setup by moving aLength into ecx). It turns out that loop is one-indexed, so we have to decrement at the start of the loop and do the zero iteration manually. Is there a better way to do this?

Here we use the edi register plus ecx offset to index into memory and set it with the value in bl. Note we have to use the BYTE PTR directive to move only a single byte. When we set up bl, we actually use movzx to fill the upper three bytes of ebx with zeroes, this was just for fun, we don't need to do this.

void fillWithCount(unsigned char* aDest, unsigned int aLength)
{
  __asm {
    mov ecx, aLength
    dec ecx
    mov edi, aDest
start:
    mov BYTE PTR [edi+ecx], cl
    loop start
    mov BYTE PTR [edi], cl
  }
}

Will with count puts the number n into the aDest+nth address. It is pretty similar to the above function.

void copy(unsigned char* aSrc, unsigned char* aDest, unsigned int aLength)
{
  __asm {
    mov eax, aDest
    mov esi, aSrc
    cmp eax, esi
    je finish
    mov ecx, aLength
start:
    mov dl, BYTE PTR [esi+ecx]
    mov BYTE PTR [eax+ecx], dl
    loop start
    mov dl, BYTE PTR [esi]
    mov BYTE PTR [eax], dl
finish:
  }
}

This one is a bit more fun, we copy aLength bytes from aSrc to aDest. We first check that aSrc and aDest are different. An interesting aside: instructions involving eax, such as 'cmp eax, esi' are often optimised on x86, that line ought to get compiled into a single byte opcode with a single argument.

Then we loop through the data as in the previous functions. We have to do two moves here because x86 does not support memory to memory moves, so we move a byte to dl and then from there to the destination. I should probably have used eax rather than edx as the temporary, rather than using it to hold the destination address.

No comments: