Bunty
Bunty

Reputation: 3

Multiplication of corresponding values in an array

I want to write an x86 program that multiplies corresponding elements of 2 arrays (array1[0]*array2[0] and so on till 5 elements) and stores the results in a third array. I don't even know where to start. Any help is greatly appreciated.

Upvotes: 0

Views: 1725

Answers (1)

0x777C
0x777C

Reputation: 1047

First thing you'll want to get is an assembler, I'm personally a big fan of NASM in my opinion it has a very clean and concise syntax, it's also what I started on so that's what I'll use for this answer. Other than NASM you have:

  • GAS

    This is the GNU assembler, unlike NASM there are versions for many architectures so the directives and way of working will be about the same other than the instructions if you switch architectures. GAS does however have the unfortunate downside of being somewhat unfriendly for people who want to use the Intel syntax.

  • FASM
    This is the Flat Assembler, it is an assembler written in Assembly. Like NASM it's unfriendly to people who want to use AT&T syntax. It has a few rough edges but some people seem to prefer it for DOS applications (especially because there's a DOS port of it) and bare metal work.

Now you might be reading 'AT&T syntax' and 'Intel syntax' and wondering what's meant by that. These are dialects of x86 assembly, they both assemble to the same machine code but reflect slightly different ways of thinking about each instruction. AT&T syntax tends to be more verbose whereas Intel syntax tends to be more minimal, however certain parts of AT&T syntax have nicer operand orderings tahn Intel syntax, a good demonstration of the difference is the mov instruction:

AT&T syntax:

movl (0x10), %eax

This means get the long value (1 dword, aka 4 bytes) and put it in the register eax. Take note of the fact that:

  • The mov is suffixed with the operand length.
  • The memory address is surrounded in parenthesis (you can think of them like a pointer dereference in C)
  • The register is prefixed with %
  • The instruction moves the left operand into the right operand

Intel Syntax:

mov eax, [0x10]

Take note of the fact that:

  • We do not need to suffix the instruction with the operand size, the assembler infers it, there are situations where it can't, in which case we specify the size next to the address.
  • The register is not prefixed
  • Square brackets are used to address memory
  • The second operand is moved into the first operand

I will be using Intel syntax for this answer.
Once you've installed NASM on your machine you'll want a simple build script (when you start writing bigger programs use a Makefile or some other proper build system, but for now this will do):

nasm -f elf arrays.asm
ld -o arrays arrays.o -melf_i386
rm arrays.o
echo
echo " Done building, the file 'arrays' is your executable"

Remember to chmod +x the script or you won't be able to execute it. Now for the code along with some comments explaining what everything means:

global _start ; The linker will be looking for this entrypoint, so we need to make it public

section .data ; We're going on to describe our data here
    array_length equ 5 ; This is effectively a macro and isn't actually being stored in memory
    array1 dd 1,4,1,5,9 ; dd means declare dwords
    array2 dd 2,6,5,3,5

    sys_exit equ 1

section .bss ; Data that isn't initialised with any particular value
    array3 resd 5 ; Leave us 5 dword sized spaces

section .text
_start:
    xor  ecx,ecx     ; index = 0 to start
    ; In a Linux static executable, registers are initialized to 0 so you could leave this out if you're never going to link this as a dynamic executable.

    _multiply_loop:
        mov eax, [array1+ecx*4] ; move the value at the given memory address into eax
        ; We calculate the address we need by first taking ecx (which tells us which
        ; item we want) multiplying it by 4 (i.e: 4 bytes/1 dword) and then adding it
        ; to our array's start address to determine the address of the given item
        imul eax, dword [array2+ecx*4] ; This performs a 32-bit integer multiply
        mov dword [array3+ecx*4], eax ; Move our result to array3

        inc ecx ; Increment ecx
        ; While ecx is a general purpose register the convention is to use it for
        ; counting hence the 'c'
        cmp ecx, array_length ; Compare the value in ecx with our array_length
        jb _multiply_loop ; Restart the loop unless we've exceeded the array length

    ; If the loop has concluded the instruction pointer will continue
_exit:
    mov eax, sys_exit ; The system call we want
    ; ebx is already equal to 0, ebx contains the exit status
    mov ebp, esp ; Prepare the stack before jumping into the system
    sysenter ; Call the Linux kernel and tell it that our program has concluded

If you wanted the full 64-bit result of the 32-bit multiply, use one-operand mul. But normally you only want a result that's the same width as the inputs, in which case imul is most efficient and easiest to use. See links in the x86 tag wiki for docs and tutorials.

You'll notice that this program has no output. I'm not going to cover writing the algorithm to print numbers because we'd be here all day, that's an exercise for the reader (or see this Q&A)

However in the meantime we can run our program in gdbtui and inspect the data, use your build script to build then open your program with the command gdbtui arrays. You'll want to enter these commands:

layout asm
break _exit
run
print (int[5])array3

And GDB will display the results.

Upvotes: 5

Related Questions