Re: Question about jumps



Markus Pitha wrote:
Thanks for the tipps. As I read in AoA that shifting a byte to the left would multiply the
operand by two, I was in mood to try it out. But unfortunately it didn't
work. I have no clue why, but my following code read a number from stdin,
but doesn't display any output. What's wrong here?

In short, stdin/stdout deals with letters, not numbers.

segment .code

".code" isn't one of the section names Nasm "knows", so you're getting default "properties". This is essentially an unaligned ".rodata" ("const") section. Okay for your purposes, but kind of a misleading name. Did you do this because Herbert suggested that "readonly" data could go in a code section? True enough, but he meant to put it in ".text", I think - rather than make another section for it...

See "elf extensions to the SECTION directive" for more info on what Nasm does with this crap...

http://home.comcast.net/~fbkotler/nasmdoc6.html#section-6.5.1

msg1 db "Enter a number: ", 0

The zero just gives you an extra space...

len1 equ $-msg1

segment .bss

nmbr resb 8

segment .text
global _start

_start:
mov eax, 4 ;write
mov ebx, 1
mov ecx, msg1
mov edx, len1
int 0x80

mov eax, 3 ;read
mov ebx, 0
mov ecx, nmbr
mov edx, 8
int 0x80

Okay, now you've got whatever the user typed in your buffer...

mov eax, nmbr

This moves the address/offset of your buffer into eax.

shl eax, 1 ; nmbr * 2

Double the address...

mov ecx, eax ; mov it to ecx

.... and use if for the buffer address for the next "write". This isn't going to do what you want!

mov eax, 4
mov ebx, 1
mov edx, 8
int 0x80

mov eax, 1 ; sys_exit
mov ebx, 0
int 0x80

You know C, right? So you know what "atoi" and "itoa" do? That'ss what we need to do here. Nathan just posted a "write_hex" that would take care of the latter part... although we probably want it in decimal. We can represent a number as decimal, hex, binary, octal... same number. The same applies to input. We probably want decimal, but if we *say* "decimal", the pesky user will enter "1.23", sure as hell! Better just tell 'em "a number". :)

So... we've got some text - hopefully all ascii characters representing decimal digits - in our "nmbr" buffer. We need to convert it to a number we can do arithmetic on. Here's one way...

global _start

section .data
number_string db '123', 10

section .text
_start:
nop

push number_string
call atoi
add esp, byte 4

mov ebx, eax
; if we return our number as an exit code
; we can see it with "echo $?" - up to
; a byte...
mov eax, 1
int 80h


atoi:
mov edx, [esp + 4] ; pointer to string
xor eax, eax ; clear "result"
..top:
movzx ecx, byte [edx]
inc edx
cmp ecx, byte '0'
jb .done
cmp ecx, byte '9'
ja .done

; we have a valid character - multiply
; result-so-far by 10, subtract '0'
; from the character to convert it to
; a number, and add it to result.

lea eax, [eax + eax * 4]
lea eax, [eax * 2 + ecx - 48]

jmp short .top
..done
ret

As you can see, that's sort of a "C-like" implementation - based on some compiler output that Herbert showed me, actually. It expects a pointer to string on the stack (caller cleans up stack), it quits on any invalid digit, and in case of overflow (which won't happen if you limit input to 8 characters), returns the modulo 4G result - arguably "correct", but probably not what the user expects. It feels free to trash ecx and edx. We can do something different, if we want - raise a fuss if there's an invalid digit, or overflow... preserve registers... whatever.

Now we've got a number (returned in eax) that we can multiply by two by shifting left! Obviously, if we double "5", we can't print "10" as a single character - we're going to have to print "1" and then "0". So we need to reverse the process. A more flexible routine might just put the ascii text representing the number into a buffer - then we could format it and print it as we like. What I use is just a "print the number in eax to stdout... now!" routine...

;---------------------------------
showeaxd:
push eax
push ebx
push ecx
push edx
push esi

; make room on stack for our buffer
sub esp, 10h
; get its address in ecx - this is the "end"
; of the buffer - we're going to work "back to
; front", since we get the digits in "wrong"
; order from the "div"
lea ecx, [esp + 12]
; divide by 10
mov ebx, 10
; we're going to use esi to count the
; length of the string. we'd like to use
; edx, since that's where "write" expects
; the length, but edx is an implicit
; operand to "div".
xor esi, esi
; zero-terminate the string. this isn't
; necessary here! Leftover from previous code,
; probably. We *could*, however, put a 10 there
; (and bump the count) if we wanted a newline
; after the number... Maybe that's why I left
; it in. Bloat!
mov byte [ecx], 0
..top:
; next byte towards the "front" of the buffer
dec ecx
; "div" divides edx:eax by whatever - not just eax.
; if the result won't fit in eax - which it won't
; if edx >= ebx (in this case), it causes an
; exception, crashing your program right there.
; this bites newbies all the time!
xor edx, edx
; "div" puts the quotient in eax, and the
; remainder in edx. It's the remainder
; we're interested in.
div ebx
; convert from number to ascii character
add dl, '0'
; store it (back to front) in our buffer
mov [ecx], dl
; bump the count
inc esi
; if the quotient was zero, we're all done
or eax, eax
; else, do more
jnz .top

; now we can print it

mov edx, esi ; length
mov ebx, 1 ; stdout
mov eax, 4 ; __NR_write
int 80h ; do it

; "free" our buffer
add esp, 10h

pop esi
pop edx
pop ecx
pop ebx
pop eax

ret
;---------------------------------

Here's my hex version...

;------------------------------
showeaxh:
push eax
push ebx
push ecx
push edx

sub esp, 10h

mov ecx, esp
xor edx, edx
mov ebx, eax
..top:
rol ebx, 4
mov al, bl
and al, 0Fh
cmp al, 0Ah
sbb al, 69h
das
mov [ecx + edx], al
inc edx
cmp edx, 8
jnz .top
mov ebx, 1
mov eax, 4
int 80h

add esp, 10h

pop edx
pop ecx
pop ebx
pop eax
ret
;------------------------------

The funny trickery:
cmp al, 0Ah
sbb al, 69h
das
is a short, but slow, way to convert the number 0 to 0Fh in al to '0' to '9' and 'A' to 'F'. Same as:

cmp al, 10
jb skip
add al, 7 ; or 39(?) for lowercase
skip:
add al, '0'

I should probably be using that... I just think the short sbb/das trick is "cute"...

There are a number of ways to do these conversions - HLA's stdlib has a bunch, including convert to roman numerals, and to "English" ("one hundred twenty three"). If you want to play with it "by hand" to see how the magician does the trick, the above should give you something to start with...

Best,
Frank
.



Relevant Pages