PDA

View Full Version : Some Serious IA-32 Help needed...Assembly



bullet2urbrain
03-16-2008, 02:34 PM
Hey everyone, coming down to the wire, and I'm having some trouble coding this in Intel Assembly.

The project.

100 16 bit signed numbers, stored in a memory location have to sort them into 2 seperate arrays, by positive and negative numbers, and also output how many zeros are contained within the original array.

our original array is X and stored @ [ebp + 8]

so we have to compare to 0, compare to a positive number, compare to a negative number,

so to compare to 0 we could do

CMP [ESI],0 ; assuming that our element to compare is in [ESI]


however i dont know how to compare to a positive or a negative number...

i realize we will have to use Jump instructions and conditional branching. but the CMP instruction is whats throwing me.

anyone able to chime in some advice?????????

Marvin_The_Martian
03-16-2008, 03:14 PM
Maybe...

CMP Variable, 0

JE Zero <- if equal goto label Zero
JNA BelowZero <- if not above zero goto label BelowZero
JNB Positive <- if not below zero goto label Positive

??

Not sure, been ages since I used wdasm for some tinkering ;)

bullet2urbrain
03-16-2008, 03:20 PM
Maybe...

CMP Variable, 0

JE Zero <- if equal goto label Zero
JNA BelowZero <- if not above zero goto label BelowZero
JNB Positive <- if not below zero goto label Positive

??

Not sure, been ages since I used wdasm for some tinkering ;)


I believe you are correct, i went back to the intel manuals, and started looking... apparently i was mistaken in how CMP works.

JE Zero ; jumps to Zero "subroutine"
JG Positive ; Jumps to greater than zero "label"
JL Negative ; Jumps to Negative label.


EDIT: oh yeah , Im using NASMW for assembling this one... my brain is still working on PIC assembly and not intel, plus im a terrible programmer, im definitely EE not CpE as they designate here at my school.

PS: My professor is from the Netherlands, He graduated from Delft (sp?)

Marvin_The_Martian
03-16-2008, 03:30 PM
I'm not allowed to tell you what I used to do with wdasm or softice for that matter. I don't have much assembly programming skills, just dissasembly and breakpoints :)

I never seen JL and JG though, only JNA and JNB. Like I said been out off the scene for a long time.

I'm starting my bachelor of information & communication technology this year, I'm sure we will get some assembly. That is, if they allow me in based on the addmitance test.

emboss
03-18-2008, 08:58 PM
First, you need to check what CMP does, and how it sets the flags. CMP is effectively a SUB, but without actually storing the result (ie: it only affects the flags). Second, remember that the for the purposes of addition and subtraction (with a fixed bit size), there is no difference between an signed and unsigned number. To a 32-bit CPU, -10 is the same as 4294967286.

So, a "cmp eax, -10" is the same thing as "sub eax, 4294967286" but without changing the value of eax.

The final thing to look at is how the important flags are set:
{} CF (carry flag) = set if there is a carry (addition) or borrow (subtraction) out of the MSB.
{} ZF (zero flag) = set if the result is zero.
{} SF (sign flag) = set to the most significant bit of the result.
{} OF (overflow flag) = a bit more complex. This one is the carry into the MSB XORed with the carry out of the MSB (ie: the CF). This one takes a bit of thought to see why it works :)
There's a couple others but they're rarely used.

So, "cmp eax,0" will clear the carry flag and the overflow flag, set the sign flag to be the MSB of eax, and set the ZF if eax is zero. So, you can first branch off using "jz" for the zero case, and then "js" for the negative case, and fall through to the positive case.

In this case, the "jl/jnge" instruction (see volume 1, appendix B) will act the same as the "js" as the overflow flag will be zero. And depending on the way you think, it could make the code clearer.

bullet2urbrain
03-19-2008, 09:44 AM
Assuming you put [ebp+8] (that is the address of the x-array) in esi, [ebp+12] in edi and [ebp+16] in edx.

Anyway, start simple. For example, in part 1, you can start by copying the contents over from x to p and n for example:

mov ecx, 100
NXT: mov ax, [esi]
mov [edi], ax
mov [edx], ax
add esi, 2
add edi, 2
add edx, 2
dec ecx
jnz NXT

This is just to get a good and warm fuzzy for the loops. If you want to check for zero and increment ax, you can easily insert that in the above code:

mov ax, 0
mov ecx, 100
NXT: cmp [esi], 0
jnz SKIPO
inc ax
SKIPO: add esi, 2
dec ecx
jnz NXT




This is the "help email" i got from my professor.. in addition to


CMP [ESI],0
JE INCREASE_ZEROES ; Jump if Equal, is (ESI) == 0
JG POSITIVE_NUM ; Jump to POSITIVE_NUM if (ESI)> 0



But, I get what he is trying to do with the CMP [ESI],0 similar to what you mentioned Emboss... however I do not understand the part from way above about moving the contents of AX to both locations contained in EDI and EDX.. I see he moves the first element of the array to ESI, but then i get completely lost??? :confused:

Am I missing some of his logic somewhere???

Thanks for all the help guys.

emboss
03-20-2008, 07:07 AM
OK, first let me make sure I understand what's going on :)

By the looks of things, you're writing a function that takes 3 pointers: the address of the source buffer (containing 100 elements), the address of the buffer where the positive elements go, and the address of the buffer where the negative elements go. These are passed on the stack, and the C prototype would look something like "void SplitArray(short *Src, short *DstPositive, short *DstNegative);".

The usual function prologue:

push ebp
mov ebp,esp
Results in a stack that looks like:

[esp+00] old ebp
[esp+04] (return address)
[esp+08] Src
[esp+12] DstPositive
[esp+16] DstNegative
The three parameters are then loaded into esi, edi, and edx respectively.

The example code he then gave basically just copies the contents of Src into both DstPositive and DstNegative in an element by element fashon: it loads the value from Src ("mov ax, [esi]"), stores it into DstPositive ("mov [edi], ax"), stores it into DstNegative ("mov [edx], ax"), increments all the pointers to point to the next element of their respective buffers, (the three adds), and then loops around (dec/jnz).

This is just to show how looping works (dec sets the ZF if the result is zero, so it loops around 100 times).

The next step is to actually do what the assignment is :) Instead of blindly storing the value to both the arrays, you need to look at the value you load out of Src (ie: the value in ax) and decide what you should do about it. This is where the cmp/jz/jl bit comes in. If it's zero, don't store it anywhere and just increment a counter. If it's negative, only write into the negative array (and only increment the negative array pointer). Likewise if it's positive. Obviously, you always have to increment the pointer for the Src array.

I don't know if you've covered the various addressing modes yet, but there's a fun trick you can pull in these situations to eliminate an instruction. You've got your source pointer in esi, and you need to loop over 100 elements. What you can do is combine the loop counter and the array index.

First, add 200 (100 elements, each 2 bytes) to the source pointer, and store negative 200 in the loop counter (I'll keep it in ecx). Now, if you add the source pointer to the loop counter, you get back to the start of the array. Adding 2 to the loop counter will then advance you to the next element, while also "decrementing" the counter. When the counter hits zero, you're done.

In code, this looks like:

sub esi, 200
mov ecx, -200
NextElement:
mov ax, [esi+ecx]
; Do whatever processing needs to be done
add ecx, 2
jnz NextElement

Ahh, the joys of x86!

bullet2urbrain
03-20-2008, 07:10 AM
mov esi,[ebp+8] ; move x array to ESI
mov ebx,[ebp+12] ; move location of p array to ebx
mov edx,[ebp+16] ; move location of n array to edx
mov ecx,99 ; init a counter to 100
mov eax,0 ; init to 0

elem: CMP WORD[esi],0 ; compare to zero to establish logic
JE zernum ; jump to label zernum if 0
JG posnum ; jump to posnum if greater than 0
JL negnum ; jump to negnum if less than 0


zernum: inc eax ; increment eax for each 0 we get
dec ecx ; dec the counter
add esi,2 ; move through the x array
jmp CNT ; return to CNT

posnum: mov edi,[esi] ; move element to temp register
mov [ebx],edi ; move element to location of ebx
dec ecx ; decrement the counter
add esi,2 ; move through array
add ebx,2 ; move to next element of
jmp CNT ; return back to CNT

negnum: mov edi,[esi] ; move element to temp reg
mov [edx],edi ; move element to location of
dec ecx ; reduce counter
add esi,2 ; move through x array
add edx,2 ; move through n array
jmp CNT ; return to compare next


CNT: CMP ecx,0
JE FIN
jmp elem


FIN:
; Restore the base pointer
mov esp,ebp
pop ebp
ret



Emboss,

Here is how i managed to do this and get it working... the only problem is the 2nd to last element always appears in both Positive and Negative arrays.

Thanks for all the help.. Now i have one last project using MMX and SSE instructions.

emboss
03-20-2008, 04:49 PM
You're very close! The main thing you've done wrong is you're transferring 32 bits at a time: the edi register is 32 bits, whereas you should only be moving 16 bits at a time. Once you fix this, the remaining trivial change should become apparaent :)

Apart from that, it looks fine. As far as general coding style, I'd shuffle things around a little bit. Since you have "dec ecx/add esi,2" in each block, I'd move that to the common code at the end (CNT). Also, if you do the dec after the add, you can simply follow the dec with a "jnz elem" to loop around.

In a similar style, you can move the "mov di,[esi]" up into the common code in the loop (ie: the first instruction after "elem:"). Then instead of doing "cmp WORD[esi],0" you do "cmp di, 0". For the sake of learning, consider why "or di,di" would also work (this is often preferred as it generates smaller code).

Also, you can eliminate one of the jcc's (the "generic" name for a conditional jump in x86) after the cmp. If it's not positive (jg) or negative (jl), then it must be zero. Instead of doing the "jz", just move the element-is-zero code after the two jcc's.

Overall, then, your code would be in the form of

; Prologue stuff goes here
elem:
mov di, [esi]
cmp di, 0
jg posnum
jl negnum

; Your zero case code goes here
jmp CNT

posnum:
; Your positive case code goes here
jmp CNT

negnum:
; Your negative case code goes here
; No "jmp CNT" needed

CNT:
add esi,2
dec ecx
jnz elem

; Epilogue code goes here

Finally, a very subjective style thing: (e)di isn't usually used for "temporary" storage. For silly architectual reasons, edi is usually used for a destination pointer, and esi is usually used for a source pointer. If both of these are in use, then edx and ebx in that order are usually used. For styleistically "perfect" code, I'd change the positive and negative buffer pointers to be stored in edi and edx respectively, and use bx to hold the current element. But this is being very pedantic :)

Anyhow, only do these changes if you feel like it (except the 16 bit thing). As far correctness and assignment marks go, they're probably irrelevant. Apart from that, have fun with SSE! It's IMO the best part of assembly programming, and one of the few areas where a skilled assembly coder can write code that is several times faster than a compiler.

bullet2urbrain
03-20-2008, 05:00 PM
You're very close! The main thing you've done wrong is you're transferring 32 bits at a time: the edi register is 32 bits, whereas you should only be moving 16 bits at a time. Once you fix this, the remaining trivial change should become apparaent :)

Apart from that, it looks fine. As far as general coding style, I'd shuffle things around a little bit. Since you have "dec ecx/add esi,2" in each block, I'd move that to the common code at the end (CNT). Also, if you do the dec after the add, you can simply follow the dec with a "jnz elem" to loop around.

In a similar style, you can move the "mov di,[esi]" up into the common code in the loop (ie: the first instruction after "elem:"). Then instead of doing "cmp WORD[esi],0" you do "cmp di, 0". For the sake of learning, consider why "or di,di" would also work (this is often preferred as it generates smaller code).

Also, you can eliminate one of the jcc's (the "generic" name for a conditional jump in x86) after the cmp. If it's not positive (jg) or negative (jl), then it must be zero. Instead of doing the "jz", just move the element-is-zero code after the two jcc's.

Overall, then, your code would be in the form of

; Prologue stuff goes here
elem:
mov di, [esi]
cmp di, 0
jg posnum
jl negnum

; Your zero case code goes here
jmp CNT

posnum:
; Your positive case code goes here
jmp CNT

negnum:
; Your negative case code goes here
; No "jmp CNT" needed

CNT:
add esi,2
dec ecx
jnz elem

; Epilogue code goes here

Finally, a very subjective style thing: (e)di isn't usually used for "temporary" storage. For silly architectual reasons, edi is usually used for a destination pointer, and esi is usually used for a source pointer. If both of these are in use, then edx and ebx in that order are usually used. For styleistically "perfect" code, I'd change the positive and negative buffer pointers to be stored in edi and edx respectively, and use bx to hold the current element. But this is being very pedantic :)

Anyhow, only do these changes if you feel like it (except the 16 bit thing). As far correctness and assignment marks go, they're probably irrelevant. Apart from that, have fun with SSE! It's IMO the best part of assembly programming, and one of the few areas where a skilled assembly coder can write code that is several times faster than a compiler.

Yeah, i realize that EDI and ESI are generally reserved for indexing and pointing to locations as opposed to temp storage, although i felt it was easier to use them as avoiding using other registers...


The MMX SSE project is royally going to be a pain, I'm a terrible coder as evidenced by my above "Hack Job" but we will see what i come up with.


Thanks for the tips, im going to try and fix the smallish errors and reduce the amount of code... Thanks again.