Author Topic: I try to learn about esp and ebp  (Read 6072 times)

Offline grimoire

  • Jr. Member
  • *
  • Posts: 6
I try to learn about esp and ebp
« on: August 15, 2013, 12:45:36 AM »
hi

I try to learn how use ebp and esp, but I've a problem with ascii and decimal

This is my code using nasm, is a calculator and everything works fine doing this.

Code: (asm) [Select]
[Bits 32]

global _start

section .data

tit: db "Inicio de calculadora",10,10,0
in: db "Escribe el nombre de la funcion",10,0
buf1: times 24 db (0)

section .text

sumar:
push ebp
mov ebp, esp
sub esp, 8

mov eax, 4
mov ebx, 1
lea ecx, [ebp + 8]
mov edx, 4
int 0x80


mov eax, 3
mov ebx, 1
lea ecx, [ebp - 8]
mov edx, 5
int 0x80


mov eax, 4
mov ebx, 1
lea ecx, [ebp - 8]
mov edx, 5
int 0x80


mov esp, ebp
pop ebp

ret 8

_start:

mov eax, 4
mov ebx, 1
lea ecx, [tit]
mov edx, 23
int 0x80

mov eax, 4
mov ebx, 1
lea ecx, [in]
mov edx, 32
int 0x80

mov eax, 3
mov ebx, 1
lea ecx, [buf1]
mov edx, 8
int 0x80

cmp dword[buf1], "suma"
je suma
jne fin

suma:
push dword[buf1]
call sumar

fin:
mov eax, 1
int 0x80

But I tried to convert ascii to decimal with those lines.

Code: (asm) [Select]
xor eax, eax

strtodec:
mov al, byte[ebp - 8]
cmp al, 10
je con
sub al, 48
mov byte[buf2], al
inc byte[ebp - 8]
inc byte[buf2]
jmp strtodec


I tried to move 1 byte to [al], and check if is equal to 10 (NULL in ascii)
if is equal go to write, if not... sub 48 in [al] to convert to decimal

later move the decimal to [buf2] and incrementing after  [ebp] and [buf] to the next byte and repeat

when [al] is (null) go to write

but seems it doesn't work,  can somebody explain me how should I fix it?

Leave the code

Code: (asm) [Select]
[Bits 32]

global _start

section .data

tit: db "Inicio de calculadora",10,10,0
in: db "Escribe el nombre de la funcion",10,0
buf1: times 24 db (0)
buf2: times 20 db (0)

section .text

sumar:
push ebp
mov ebp, esp
sub esp, 8

mov eax, 4
mov ebx, 1
lea ecx, [ebp + 8]
mov edx, 4
int 0x80


mov eax, 3
mov ebx, 1
lea ecx, [ebp - 8]
mov edx, 5
int 0x80

xor eax, eax

strtodec:
mov al, byte[ebp - 8]
cmp al, 10
je con
sub al, 48
mov byte[buf2], al
inc byte[ebp - 8]
inc byte[buf2]
jmp strtodec

con:

mov eax, 4
mov ebx, 1
lea ecx, [buf2]
mov edx, 5
int 0x80


mov esp, ebp
pop ebp

ret 8

_start:

mov eax, 4
mov ebx, 1
lea ecx, [tit]
mov edx, 23
int 0x80

mov eax, 4
mov ebx, 1
lea ecx, [in]
mov edx, 32
int 0x80

mov eax, 3
mov ebx, 1
lea ecx, [buf1]
mov edx, 8
int 0x80

cmp dword[buf1], "suma"
je suma
jne fin

suma:
push dword[buf1]
call sumar

fin:
mov eax, 1
int 0x80

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: I try to learn about esp and ebp
« Reply #1 on: August 15, 2013, 08:04:31 AM »
Hi Grimoire,

You seem to be using esp and ebp correctly, so I don't know what I can tell you about that. If you have questions, ask again... but I don't think that's a problem.

Your routine to convert ascii to decimal starts out well, but then I get confused...
Code: [Select]
; ...
sumar:
push ebp ; save caller's ebp
mov ebp, esp ; create a "stack frame"
sub esp, 8 ; make a buffer on the stack

; this just repeats "suma"
; I don't know why, but it does no harm
mov eax, 4
mov ebx, 1
lea ecx, [ebp + 8]
mov edx, 4
int 0x80

; read some ascii into our buffer
mov eax, 3
mov ebx, 1 ; should be 0 - stdin?
lea ecx, [ebp - 8]
mov edx, 5
int 0x80

xor eax, eax

strtodec:
mov al, byte[ebp - 8] ; get a character
cmp al, 10 ; linefeed - not really "NULL"
je con
sub al, 48 ; convert a character to a number
; this is fine unless the pesky user types
; something other than '0' to '9'
; you might want to check that we have
; a "valid" character at this point, but okay...

mov byte[buf2], al ; store the number in another buffer
inc byte[ebp - 8]
inc byte[buf2]

Here's where I get confused. Suppose I typed "123". You get the character '1', convert it to the number 1, and store it in buf2. Then you increment the character '1' to '2', and increment the number 1 to 2. I think you want to move to the next character in the buffer. That would be at "[ebp - 7]", but I think you're better off to put the address of "[ebp - 8]" into a register, and increment that. Then, once we've got another valid character - but probably not before - we want to multiply the "result so far" by 10, and add in the next character (converted to a number, of course!). So the "result so far" is 12. Then when we get the '3', we convert it to 3, multiply the "result so far" by 10 again, and add in the 3. At this point we find the linefeed (also 10, by chance) so we're done and the result is 123.

The obvious way to "multiply by 10" is to use the "mul" instruction. This multiplies eax by the operand you provide - either a register or memory - and puts the result in edx:eax. No choice of the registers eax and edx!

The "imul" instruction is more complicated. With one operand, it is the same as "mul", only it handles signed numbers (could be negative). With three operands, it multiplies the second by the third, and puts the result in the first. This is a 32-bit x 32-bit = 32- bit operation, unlike "mul" and the one-operand "imul" which do 32 x 32 = 64. The two-operand form is just a special case of the three-operand form, in which the first and second operands are the same. Also, the third operand (or second) can be an immediate (signed byte), which we can't do with "mul".
Code: [Select]
imul ebx, 10
will work, and may be just what you want.

When it comes to "add in the next digit", there may be a complication. We can't:
Code: [Select]
add ebx, al
no matter how much we'd like to! We need to have the "next digit" in a 32-bit register, even though it would fit in a byte. It isn't a "problem", but we need to make sure that the upper bits are zero.

Besides "mul" and "imul" there's a "trick" using lea:
Code: [Select]
lea ebx, [ebx + ebx * 4] ; ebx = ebx * 5
lea ebx, [ebx * 2] ; multiplied by ten!
We can add in the "next digit" while we're at it:
Code: [Select]
lea ebx, [ebx + ebx * 4]
lea ebx, [eax + ebx * 2]
We can even do the "convert character to number" part:
Code: [Select]
lea ebx, [ebx * 5] ; we can write it this way, too
lea ebx, [eax + ebx * 2 - '0']
(I think writing it as '0' rather than 48 makes it clearer what the "purpose" of the number is - either will work). One disadvantage to this method is that there's no way to check for overflow using "lea". It's what I'm currently using, mostly because I think it's "cute". It was shown to me by Herbert Kleebauer, and was supposedly originally generated by a C compiler! Clever, those compilers (their authors, actually)!

So you can probably get that part working, one way or another. Then you go on to:
Code: [Select]
; ...
con:

; this is going to print the decimal, not ascii
; probably not useful!
mov eax, 4
mov ebx, 1
lea ecx, [buf2]
mov edx, 5
int 0x80


mov esp, ebp ; undo the "stack frame"
pop ebp ; resore caller's ebp

ret 8
The "ret 8" is not correct! That will "remove" two parameter's from the stack, but we only pushed one! You could do "ret 4" here - that would make it a "stdcall" calling convention. Windows APIs use this, and if you were using Windows you'd be wise to do it the same way. Linux more commonly uses the "cdecl" calling convention, in which the caller removes the parameters. You might want to stick with that (either will work) End your subroutine with just "ret", and where you call it:
Code: [Select]
push dword [buf1]
call sumar
add esp, 4 ; "remove" the parameter
;...
You could also "pop" a dummy register. Since you just sys_exit after this, it doesn't matter if you clobber the stack, but it isn't "right"!

I mentioned:
Code: [Select]
mov eax, 3 ; sys_write
mov ebx, 1 ; read from stdout?

I once caught Randy Hyde doing this, and pointed it out to him. He explained that with both Dos and Unix, you can use either stdin or stdout with a read or write, and it'll do the right thing. I don't know why this works, but it does. He agreed that the code would be clearer if he used stdin (0) to read from. You might want to do the same (couple places). Not a big deal!

When you've got your number(s) and done a calculation, you need to convert back to ascii before you can display the result. It won't work putting the number in ecx, nor putting the address of a buffer containing the number in ecx. You'll need to add that 48 (or '0') back in... for each digit. There should be a post nearby called something like "integer in, integer out" that has an example, if you need it. That post will also point out that you can "just call scanf" and "just call printf" to do this.

I could attempt a working version of your code, but I'm not sure why you're passing "suma" to your subroutine. You probably have doing something further in mind...

Best,
Frank


Offline grimoire

  • Jr. Member
  • *
  • Posts: 6
Re: I try to learn about esp and ebp
« Reply #2 on: August 16, 2013, 04:15:38 AM »
Ok, thank you Frank

I think I'm very accustomed still to programming in C, I will study more about convert bytes, is very similar to strcmp.

Meanwhile I used scanf and printf and it's working fine.

regards  ;D