NASM Assembly mathematical logic - c

I have a program in assembly for the Linux terminal that's supposed to work through a series of mathematical manipulations, compare the final value to 20, and then using if logic, report <, > or = relationship. Code is:
segment .data
out_less db "Z is less than 20.", 10, 0
out_greater db "Z is greater than 20.", 10, 0
out_equal db "Z is equal to 20.", 10, 0
segment .bss
segment .text
global main
extern printf
main:
mov eax, 10
mov ebx, 12
mov ecx, eax
add ecx, ebx ;set c (ecx reserved)
mov eax, 3
mov ebx, ecx
sub ebx, eax ;set f (ebx reserved)
mov eax, 12
mul ecx
add ecx, 10 ;(a+b*c) (ecx reserved)
mov eax, 6
mul ebx
mov eax, 3
sub eax, ebx
mov ebx, eax ;(d-e*f) (ebx reserved) reassign to ebx to free eax
mov eax, ecx
div ebx
add ecx, 1 ;(a+b*c)/(d-e*f) + 1
cmp ecx, 20
jl less
jg greater
je equal
mov eax, 0
ret
less:
push out_less
call printf
jmp end
greater:
push out_greater
call printf
jmp end
equal:
push out_equal
call printf
jmp end
end:
mov eax, 0
ret
Commands for compiling in terminal using nasm and gcc:
nasm -f elf iftest.asm
gcc -o iftest iftest.o
./iftest
Equivalent C code would be:
main() {
int a, b, c, d, e, f, z;
a = 10;
b = 12;
c = a + b;
d = 3;
e = 6;
f = c - d;
z = ((a + b*c) / (d - e*f)) + 1;
if (z < 20) {
printf("Z (%d) is less than 20.\n", z);
}
else if (z > 20) {
printf("Z is greater than 20.\n");
}
else {
printf("Z is equal to 20.\n");
}
}
The current output is incorrect. The C code will report that z = -1, and therefore less than 20, but the assembly code outputs Z is greater than 20. I've tried printing out the final value, but I run into this issue where printing the value somehow changes it. I've checked and rechecked the math logic and I can't see why it shouldn't give the correct value, unless I'm using the math operators incorrectly. Any and all help is appreciated.

I think the problem is here:
div ebx
add ecx, 1 ;(a+b*c)/(d-e*f) + 1
The result of the div instruction is not in ecx.

Related

Assembly Power(A,b) function

I'm trying to make an assembly power(a, b) function, based on this c code:
int power(int x, int y)
{
int z;
z = 1;
while (y > 0) {
if ((y % 2) == 1) {
y = y - 1;
z = z * x;
} else {
y = y / 2;
x = x * x;
}
}
return z;
}
Though, for some reason, it only gets some outputs right, and I can't figure out where the problem is. Here is my assembly code:
;* int power(int x, int y); *
;*****************************************************************************
%define y [ebp+12]
%define x [ebp+8]
%define z [ebp-4]
power:
push ebp
mov ebp,esp
sub esp,16
mov byte[ebp-4], 1
jmp L3;
L1:
mov eax,[ebp+12]
and eax,0x1
test eax,eax
je L2;
sub byte[ebp+12],1
mov eax,[ebp-4]
imul eax, [ebp+8]
mov [ebp-4],eax
jmp L3;
L2:
mov eax,[ebp+12]
mov edx,eax
shr edx,31
add eax,edx
sar eax,1
mov [ebp+12],eax
mov eax,[ebp+8]
imul eax,[ebp+8]
mov [ebp+8],eax
L3:
cmp byte[ebp+12],0
jg L1;
mov eax,[ebp-4]
leave
ret
When I run it, this is my output:
power(2, 0) returned -1217218303 - incorrect, should return 1
power(2, 1) returned 2 - correct
power(2, 2) returned -575029244 - incorrect, should return 4
power(2, 3) returned 8 - correct
Could someone please help me figure out where I've gone wrong, or to correct my code, any help would be appreciated.
You're storing a single byte, then reading back that byte + 3 garbage bytes.
%define z [ebp-4] ; what's the point of this define if you write it out explicitly instead of mov eax, z?
; should just be a comment if you don't want use it
mov byte[ebp-4], 1 ; leaves [ebp-3..ebp-1] unmodified
...
mov eax, [ebp-4] ; loads 4 bytes, including whatever garbage was there
Using a debugger would have shown you that you were getting garbage in EAX at this point. You could fix it by using movzx eax, byte [ebp-4], or by storing 4B in the first place.
Or, better, don't use any extra stack memory at all, since you can use ECX without even saving/restoring it in the usual 32-bit calling conventions. Keep your data in registers, that's what they're for.
Your "solution" of using [ebp+16] is writing to stack space owned by the caller. You're just getting lucky that it happens to have the upper 3 bytes zeroed, I guess, and that clobbering it doesn't lead to a crash.
and eax,1
test eax,eax
is redundant: test eax, 1 is sufficient. (Or if you want to destroy eax, and eax, 1 sets flags based on the result).
There's a huge amount of other inefficiency in your code, too.

how do i simplify/condense the code (if possible)? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I'd like to present you my program in c and assembler code attached to his one. also, I've got some questions.
here is a piece of code in c
#include <stdio.h>
void podaj_znak(int tab[], int n);
int main()
{
int tab[7] = {4, 5, 6, 2, -80, 0, 56};
printf("Przed: ");
for (int i = 0; i < 7; i++)
printf("%d ", tab[i]);
printf("\n");
podaj_znak(tab, 7);
printf("Po: %d %d %d %d %d %d %d", tab[0], tab[1], tab[2], tab[3], tab[4], tab[5], tab[6]);
printf("\n");
return 0;
}
and asm right here
.686
.model flat
public _podaj_znak
.code
_podaj_znak PROC
push ebp
mov ebp, esp
mov edx, [ebp+8]
mov ecx, [ebp+12]
ptl:
mov eax, [edx]
cmp eax, 0
jl minus
ja plus
mov ebx, 0
mov [edx], ebx
jmp dalej
minus: mov ebx, -1
mov [edx], ebx
jmp dalej
plus: mov ebx, 1
mov [edx], ebx
jmp dalej
dalej: add edx, 4
sub ecx, 1
jnz ptl
pop ebp
ret
_podaj_znak ENDP
END
my question is, how can I simplify/condense the code?
edit: posting what the program does and what I like it to be like. it is just for me to train and to get used to assembler. the program is like you've got numbers from -inf to inf and it when the actual number is equal 0, it stays as it is, when it is something less than 0, it is replaced by -1, and when the number is more than 0, it is replaced by 1. the thing is, that I wanted to somehow optimize assembler code, but I don't know whether it is even possible to condense it.
Not really a good fit for this forum, but still:
For the C code, I'd create a PrintTab function that accepts tab and count and prints the table. Then invoke it both before and after the podaj_znak call.
For the asm code:
PLEASE add comments. I know this is probably just a class project, but still, get in the habit.
Why move [edx] to eax instead of just cmp [edx],0?
If perf matters, perhaps skip prolog/epilog and use a 'fastcall' calling convention.
Why repeat "mov [edx], ebx" for each case? Move it down to dalej.
As a 'trick' you might try checking for -1, but then handle the other 2 cases with setnz.
nasm syntax, may need subtle fixing for other asm, my solution:
; converts values in tab into [-1, 0, 1] as sgn()
; arguments: two on stack(int tab[], int n)
; modified registers: esi, edi, eax, ebx
; "no branch" version (except loop itself)
_podaj_znak:
mov esi,[esp+4] ; tab ptr
mov eax,[esp+8] ; count
xor ebx,ebx
lea edi,[esi+eax*4] ; tab.end() ptr
sgn_loop:
lodsd ; eax = [ds:esi], esi += 4
; change eax to [-1, 0, 1] by sgn(eax)
test eax,eax
setnz bl
sar eax,31
or eax,ebx
; overwrite original value with sgn() result
cmp esi,edi ; test if end of tab was reached
mov [esi-4],eax
jb sgn_loop
ret
And then for the curiosity googling Internet (just the loop part is different), 3 instructions version (my is 4):
...
; modifies also edx in this variant
sgn_loop:
lodsd ; eax = [ds:esi], esi += 4
; set edx to [-1, 0, 1] by sgn(eax)
cdq
cmp edx,eax
adc edx,ebx
; overwrite original value with sgn() result
cmp esi,edi
mov [esi-4],edx
jb sgn_loop
ret
Both variants are branch-less, so they should have superior performance to any branch variant (but I'm not going to profile it).
It is possible to optimize a little bit the assembly by calling only one time the mov [edx], ebx as follow:
ptl:
mov eax, [edx]
cmp eax, 0
jl minus
ja plus
mov ebx, 0 ; only set to 0
jmp dalej
minus: mov ebx, -1 ; only set to -1
jmp dalej
plus: mov ebx, 1 ; only set to 1
jmp dalej
dalej: mov [edx], ebx ; update the array[edx]
add edx, 4
sub ecx, 1
jnz ptl

Assembly segmentation fault ( array )

beginner in assembly here (IA-32 assembly). I keep getting a seg fault when compiling in gcc, I'm guessing its attempting to access forbidden memory, but I can't pin point it down. The task was to find the greatest common denominator of an array. I figured implementing this via an array with a side function gcd (returns the gcd of 2 numbers). Here is the code:
.intel_syntax noprefix
.text
.global GCD
GCD:
push edi
push esi
mov edi, [ebp + 12] # number of elements in the array
mov esi, [ebp + 8] #array address
mov eax, [esi] # place the first element of the array into eax
mov ecx, 1 # set the counter to 1
next_number: #accumulating the gcd of the entire array
cmp ecx, edi #if the counter reaches the max number of elements,
ja end #end the the loop
push [esi + 4*ecx] #calling the gcd function, pushing the numbers on the
push eax #stack, so gcd can pick them up
call gcd
add esp, 8
inc ecx #increment the counter
jmp next_number #next iteration
end:
pop esi
pop edi
leave
ret
And the gcd side function:
# The C algorithm for finding the gcd of 2 numbers implemented in IA-32
# while(y)
# {
# r = x % y;
# x = y;
# y = r;
# }
# return x;
.intel_syntax noprefix
.text
.global gcd
gcd:
enter 0,0
mov eax, [ebp + 8]
mov ecx, [ebp + 12]
loop:
cmp ecx, 0
je end
xor edx, edx
idiv ecx
mov eax, ecx
mov ecx, edx
jmp loop
end:
leave
ret

Calculating average of floats rounded to an integer NASM

Basically the program is suppose to input floating point numbers from the user, then get the sum of them, the average of them (from 0.0 to 100.0), anything above is not counted and anything below isn't counted as well. A negative number will not just not be counted but also exit the loop/program. The sum is outputted, number of valid numbers inputted is also outputted, and finally the average of the floating point numbers ROUNDED to nearest integer is outputted. I think I am close right now, my program thus far is below. Please, I could use some help finding the bug/(error in reasoning, if it exists) in my code below. Note I am using NASM.
Update: When it runs and input just the simple 1,2,3,4 it should output 3(rounded to nearest integer) is the average, but it outputs 100, which means it is running all the way through that loop or something similar is going wrong.
Update: I found the problem(s) there should have been a couple more comparisons and should have had more ffree st0 to free up st0, that kept creating a problem. Anyways got it working.
; Declare needed external functions
;
extern printf ; the C function, to be called
extern scanf ; the C function, to be called
SECTION .data ; Data section, initialized variables
three: dq 3.0
erroro: db "Did not work as intended...", 10, 0
zero: dq 0.0
half: dq 0.5
max_val: dq 100.0 ; max value is 100
min_val: dq 0.0 ; min value is 0
input: db "%lf", 0 ; input format
intro: db "Program title is Average and programmer is Joe", 10, 0 ; introduction format
iname: db "Enter your full name", 10, 0 ; get name instructions from user format
gname: db "%s %s", 0 ; get first and lats name
inst: db "Enter a number between 0.0 and 100.0, negative number to exit", 10, 0 ; instruction format
countout: db "The number of valid numbers entered: %d", 10, 0 ; counter output format
sumoutput: db "The sum is: %f", 10, 0 ; sum output format
avgoutput: db "The average is: %d", 10, 0 ; average output format
specialbye: db "You didn't enter any numbers, something wrong %s %s?", 10, 0 ; special goodbye format (if user did not input any valid numbers)
bye: db "Thanks for using this program, %s %s", 10, 0 ; goodbye output format
SECTION .bss ; BSS, uninitialized variables
x: resq 1 ; number entered by user, set to 0
sum: resq 1 ; sum variable is set to 0
avgc: resq 1 ; average used to compare (not what is outputted)
avg: resd 101 ; an array that is used for lookup value of average (it is how it outputs an integer)
count: resd 1 ; counter set to 0
avga: resq 201 ; an array of values from .5 to 100 (by increments of .5) used for comparing avg
fn: resw 10 ; first name set to 0, with a size of 10 words
ln: resw 10 ; last name set to 0, with a size of 10 words
SECTION .text ; Code section.
global main ; the standard gcc entry point
main: ; the program label for the entry point
push ebp ; set up stack frame
mov ebp,esp
mov eax, 0
mov ebx, 0
mov ecx, 101
.setavg: ;average array set up (used to output)
mov [avg+ebx], eax
push eax
mov eax, ebx
add eax, 4
mov ebx, eax
pop eax
add eax, 1
loop .setavg
finit
; get the first one taken care of...
fld qword [zero]
fstp qword [avga+0]
mov ebx, 0
mov ecx, 200
.setavga: ; average array set up to compare average values and then round to nearest integer (sort of..., actually it uses the average array to set which integer to "round to")
fld qword [avga+ebx]
fld qword [half]
fadd
mov eax, ebx
add eax, 8
mov ebx, eax
fstp qword [avga+ebx]
loop .setavga
jmp .start ; skip to .start label, used for testing purposes to reduce user input during testing
; output introduction
push dword intro
call printf
add esp, 4
; output asking for name
push dword iname
call printf
add esp, 4
; get first and last name
push dword ln
push dword fn
push dword gname
call scanf
add esp, 12
; start loop
.start:
; output number input instructions to user
push dword inst
call printf
add esp, 4
;get number
push dword x
push dword input
call scanf
add esp, 8
;compare x and minimum value
;mov eax, [x] ; put x in eax
;cmp eax, min_val ; compare x and minimum value
;jl .post_while ; if x is less than minimum value (which means it is a negative), jump to post while
; compare value to minimum value, if minimum is greater than user input, jump to post_while
fld qword [x]
fld qword [min_val]
fcomip st0, st1
ja .post_while
; free up st0
ffree st0
;compare x and max value
;mov eax, [x] ; put x in eax
;cmp eax, max_val ; compare x and max value
;jg .start ; if x is greater than max value, jump up to start (loop to start)
; compare against max value, if max is less than input jump back to .start label without counting it
fld qword [x]
fld qword [max_val]
fcomip st0, st1
jb .start
;free up st0
ffree st0
; else calculate sum
;mov eax, [sum]
;add eax, [x]
;mov [sum], eax
; calculate sum
fld qword [sum]
fld qword [x]
fadd
fstp qword [sum]
; update counter
mov eax, [count]
add eax, 1
mov [count], eax
jmp .start ; loop back to start
; after loop
.post_while:
; special check: if count = 0 that means no valid numbers have been received by user, then jump down to .special label
;mov eax, [count]
;mov ebx, 0
;cmp eax, ebx
;je .special
; calculate the average (sum/count)
;mov eax, [sum]
;mov ebx, [count]
;cdq
;div ebx
;mov [avg], eax
; calculate average
fld qword [sum]
fild dword [count]
fdiv
fstp qword [avgc]
; calculate rounding (closer to below or above) i.e. 1.3 is closer to 1 so avg is 1
;mov eax, edx
;mov ebx, 2
;mul ebx
;cmp eax, [count]
;jnge .dontadd ; if not greater i.e. 1.3, then jump down to .dontadd label
; else add one to the average
;mov eax, [avg]
;add eax, 1
;mov [avg], eax
; setup counter and index counters
mov ecx, 201
mov esi, 0
mov edi, 0
.roundloop: ; loop that rounds the average to nearest integer
; caluclate if going to increase edi (which is used for avg array *integer array*), if ecx is divisible by 2, then increase it.
mov eax, ecx
mov ebx, 2
cdq
div ebx
mov ebx, 0
cmp edx, ebx
jne .dontinc
inc edi
.dontinc:
; calculate if avga at index esi is above avgc (average calculate value), if so found where it is and end loop
fld qword [avgc]
fld qword [avga+esi]
;fld qword [three]
fcomip st0, st1
ja .endrloop
; increment esi by 8
mov eax,esi
mov ebx, 8
add eax, ebx
mov esi, eax
loop .roundloop
.endrloop:
mov ebx, edi ; save edi index of avg (integer array)
; means it is not closer to next integer
.dontadd:
push ebx
; output count
push dword [count]
push dword countout
call printf
add esp, 8
pop ebx
push ebx
; output sum
push dword [sum+4]
push dword [sum]
push dword sumoutput
call printf
add esp, 12
; output average
pop ebx
mov eax, ebx
mov ebx, 4
mul ebx
mov ebx, eax
push dword [avg+ebx]
push dword avgoutput
call printf
add esp, 8
jmp .regular ; output should be normal, since we got to here.
; special case where count == 0, meaning no valid numbers have been inputted
.special: ; not used in testing purposes at the moment
; output special goodbye message
push dword ln
push dword fn
push dword specialbye
call printf
add esp, 12
jmp .small ; now small jump to skip rest of output
.regular:
; output regular bye message
push dword ln
push dword fn
push dword bye
call printf
add esp, 12
; small jump used only in special case
.small:
mov esp, ebp ; takedown stack frame
pop ebp ; same as "leave" op
mov eax,0 ; normal, no error, return value
ret ; return
Glad you found a solution!
Could you tell us why you did the following the hard way?
.setavg: ;average array set up (used to output)
mov [avg+ebx], eax
push eax
mov eax, ebx
add eax, 4
mov ebx, eax
pop eax
add eax, 1
loop .setavg
Usual version
.setavg: ;average array set up (used to output)
mov [avg+ebx], eax
ADD EBX, 4
add eax, 1
loop .setavg

Assembly Addressing mode

This is the code:
section .data
v dw 4, 6, 8, 12
len equ 4
section .text
global main
main:
mov eax, 0 ;this is i
mov ebx, 0 ;this is j
cycle:
cmp eax, 2 ;i < len/2
jge exit
mov ebx, 0
jmp inner_cycle
continue:
inc eax
jmp cycle
inner_cycle:
cmp ebx, 2
jge continue
mov di, [v + eax * 2 * 2 + ebx * 2]
inc ebx
jmp inner_cycle
exit:
push dword 0
mov eax, 0
sub esp, 4
int 0x80
I'm using an array and scanning it as a matrix, this is the C translation of the above code
int m[4] = {1,2,3,4};
for(i = 0; i < 2; i++){
for(j = 0; j < 2; j++){
printf("%d\n", m[i*2 + j]);
}
}
When I try to compile the assembly code I get this error:
DoubleForMatrix.asm:20: error: beroset-p-592-invalid effective address
which refers to this line
mov di, [v + eax * 2 * 2 + ebx * 2]
can someone explain me what is wrong with this line? I think that it's because of the register dimensions, I tried with
mov edi, [v + eax * 2 * 2 + ebx * 2]
but I've got the same error.
This is assembly for Mac OS X, to make it work on another SO you have to change the exit syscall.
You can't use arbitrary expressions in assembler. Only a few addressingmodes are allowed.
basically the most complex form is register/imm+register*scale with scale 1,2,4,8
Of course constants (like 2*2) will probably be folded to 4, so that counts as a single scale with 4 (not as two multiplications)
Your example tries to do two multiplies at once.
Solution: insert an extra LEA instruction to calculate v+ebx*2 and use the result in the mov.
lea regx , [v+ebx*2]
mov edi, [eax*2*2+regx]
where regx is a free register.
The SIB (Scale Immediate Base) addressing mode takes only one Scale argument (1,2,4 or 8) to be applied to exactly one register.
The proposed solution is to premultiply eax by 4 (also has to modify the comparison). Then inc eax can be replaced with add eax,4 and the illegal instruction by mov di,[v+eax+ebx*2]
A higher level optimization would be just to for (i=0;i<4;i++) printf("%d\n",m[i]);

Resources