NASM: Hex to decimal, can't convert more than 0x99 - nasm

My program receives a string representing a hex value, say "324" and I want to convert it to an actual value in decimal (aka get the integer 804)
So far, I've read char by char, and for each one, I would convert it to value (move it to AL, and sub 48), then multiply by 16, and only at the end divide one time by 16.
The problem: If I input a hex string more than "99" in hex, my registers reset out to 0, but I need to be able to support much larger values.
My sketch:
initialize ecx register with 0 that will hold our overall decimal integer value
for each step:
1. mov al, byte [ebx] (ebx holds our string)
2. sub, al 48
3. add eax, ecx
4. multiply eax by 16, then move content of eax to ecx
Is there another way?

If I understand correctly you are trying to parse hex decimal string to value in register. It would be easier to see the problem if you posted actual code.
I would do something like this:
; accepts null terminated string with characters '0'-'9', 'A'-'F'
; return value in eax
xor eax, eax
.loop:
mov cl, byte [ebx]
test cl, cl
jz .end
cmp cl, 65
jl .numeric
sub cl, 7 ; 'A' code is 65, need to subtract 7 more so 'A' = 10
.numeric:
sub cl, 48
shl eax, 4
or al, cl
inc ebx
jmp .loop
.end:
ret

Related

converting string IP address to 32-bit integer (ASM)

I'm trying to figure out how to take a null-terminated normal IP address as a string and convert it to a 32-bit integer. In this example, my string is located at address ES:DI+Qapktdat. Qapktdat is a member of a struct defined as a string stored at ES:DI.
I have no problem reading from or writing to strings in the struct, but I have a problem with calculating the IP.
For example, If I use the string "192.168.7.2" then EDX should equal Hex value C0A80702 but I'm getting nothing close to it.
What could I be doing wrong?
ipconvmath:
mov BX,Qapktdat-1 ;BX=data pointer. Set it to beginning of data -1
mov CX,11h ;Look for null within the first 17 bytes of data
ipend:
inc BX
mov AL,[ES:DI+BX]
cmp AL,0
je ipok
loop ipend
mov DX,-1 ;cant find null char so exit
ret
ipok:
mov BP,10 ;BP = multiplier of multiplier
xor EDX,EDX ;EDX = eventual IP address
;We scan IP address starting at LAST digit
nextipseg:
mov CX,1 ;CX= immediate multiplier
xor DL,DL ;lower 8 bits of EDX = 0
nxb:
xor AX,AX
dec BX
mov AL,[ES:DI+BX]
cmp AL,'.'
je nodot
;Input isnt DOT so see if its number 0-9
cmp AL,30h ;#0
jb badipcnv
cmp AL,39h ;#9
ja badipcnv
sub AL,30h
mul CX ;multiply by 1, 10, or 100 depending on number position
add DL,AL ;add it to our numeric IP
mov AX,CX ;temporarily make AX=CX so multiply can work
mul BP ;multiply the immediate multiplier by 10
mov CX,AX
cmp BX,Qapktdat ;see if were at beginning of data
jne nxb
;we are so skip beginning
nodot:
rol EDX,8 ;shift existing contents over by 1 byte
cmp BX,Qapktdat ;see if were at beginning
jne nextipseg
;here we are at beginning so were done
;EDX = ip address converted
ret
;Reach here if a character is not 0-9 or a dot
badipcnv:
mov AX,1
ret

[MASM32, irvine32 library]Converting BYTE string separated by spaces to integer, am I doing it right?

Input is random number of integers(does not exceed 1 byte), separated with spaces like this : -3 4 5 7 8 -1
There is ReadInt function in Irvine32 library, as far as I know, one integer as its input function. But since there is no clear information about the number of inputs, I had to declare the space in BYTE string form like this :
keyListInString BYTE 31 DUP(0)
and get integer string like this :
mov edx,OFFSET keyListInString
mov ecx,SIZEOF keyListInString
call ReadString
and I tried to separate each integers by recognizing spaces,'-' symbol and manually inserting them to new space I have created.
keyListInInteger BYTE 31 DUP(0) ;new space for integers
And here is the code I used.
mov esi,OFFSET keyListInString
mov edi,OFFSET keyListInInteger
mov ecx,eax ;eax = length of string = number of cycles
L1:
mov al,[esi]
cmp al,45 ;if current component of keyListInString is '-'
je ifMinus
mov al,[esi]
cmp al,0 ;if this is null(space)
je ifNull
mov al,[esi] ;if its none of above(normal number)
sub al,48 ;sub 48 for ascii --> integer
mov [edi],al ;put integer to where edi points(keyListInInteger)
inc edi
jmp final
ifMinus:
inc esi ;points next index on keyListInString(number part)
mov al,[esi]
sub al,48
neg al ;have to reverse the sign, because it is originally a minus
mov [edi],al
inc edi ;manually increase edi
jmp final
ifNull:
inc esi ;it is space anyway, so point next.
jmp L1 ;and repeat from first.
final:
inc esi
loop L1
I tried to check if this is the right code in this way :
mov edx,OFFSET keyListInInteger
mov al,[edx] ;see [edx+2],[edx+4],...
movzx eax,al
call DumpRegs
and see al's value.
I think I somehow approached to the result I wanted, but not exactly.
I wanted to make each integers like elements in an array, but I have no idea how to do so.

How to find the hamming distance for strings that are not necessarily equal length?

I have an assignment asking me to find the hamming distance of two user-input strings that are not necessarily equal in length.
So, I made the following algorithm:
Read both strings
check the length of each string
compare the length of the strings
if(str1 is shorter)
set counter to be the length of str1
END IF
if(str1 is longer)
set counter to be the length of str2
END IF
if(str1 == str2)
set counter to be length of str1
END IF
loop through each digit of the strings
if(str1[digitNo] XOR str2[digitNo] == 1)
inc al
END IF
the final al value is the hamming distance of the strings, print it.
But I'm stuck at step 3 and I don't seem to get it working. any help?
I tried playing around with the registers to save the values in, but none of that worked, I still didn't get it working.
; THIS IS THE CODE I GOT
.model small
.data
str1 db 255
db ?
db 255 dup(?)
msg1 db 13,10,"Enter first string:$"
str2 db 255
db ?
db 255 dup(?)
msg2 db 13,10,"Enter second string:$"
one db "1"
count db ?
.code
.startup
mov ax,#data
mov ds,ax
; printing first message
mov ah, 9
mov dx, offset msg1
int 21h
; reading first string
mov ah, 10
mov dx, offset str1
int 21h
; printing second message
mov ah, 9
mov dx, offset msg2
int 21h
; reading second string
mov ah, 10
mov dx, offset str2
int 21h
; setting the values of the registers to zero
mov si, 0
mov di, 0
mov cx, 0
mov bx, 0
; checking the length of the first string
mov bl, str1+1
add bl, 30h
mov ah, 02h
mov dl, bl
int 21h
; checking the length of the second string
mov bl, str2+1
add bl, 30h
mov ah, 02h
mov dh, bl
int 21h
; comparing the length of the strings
cmp dl,dh
je equal
jg str1Greater
jl str1NotGreater
; if the strings are equal we jump here
equal:
mov cl, dl
call theLoop
; if the first string is greater than the second, we jump here and set counter of str1
str1Greater:
; if the second string is greater than the first, we jump here and set counter to length of str2
Str1NotGreater:
; this is the loop that finds and prints the hamming distance
;we find it by looping over the strings and taking the xor for each 2, then incrementing counter of ones for each xor == 1
theLoop:
end
So, in the code I provided, it's supposed to print the length of each string (it prints the lengths next to each other), but it seems to always keep printing the length of the first string, twice. The register used to store the length of the first string is dl, and the register used to store the length of the second is dh, if I change it back to dl, it would then print the correct length, but I want to compare the lengths, and I think it won't be possible to do so if I save it in dl both times.
but it seems to always keep printing the length of the first string, twice.
When outputting a character with the DOS function 02h you don't get to choose which register to use to supply the character! It's always DL.
Since after printing both lengths you still want to work with these lengths it will be better to not destroy them in the first place. Put the 1st length in BL and the second length in BH. For outputting you copy these in turn to DL where you do the conversion to a character. This of course can only work for strings of at most 9 characters.
; checking the length of the first string
mov BL, str1+1
mov dl, BL
add dl, 30h
mov ah, 02h
int 21h
; checking the length of the second string
mov BH, str2+1
mov dl, BH
add dl, 30h
mov ah, 02h
int 21h
; comparing the length of the strings
cmp BL, BH
ja str1LONGER
jb str1SHORTER
; if the strings are equal we ** FALL THROUGH ** here
equal:
mov cl, BL
mov ch, 0
call theLoop
!!!! You need some way out at this point. Don't fall through here !!!!
; if the first string is greater than the second, we set counter of str1
str1LONGER:
; if the second string is greater than the first, we set counter to length of str2
Str1SHORTER:
; this is the loop that finds and prints the hamming distance
;we find it by looping over the strings and taking the xor for each 2, then incrementing counter of ones for each xor == 1
theLoop:
Additional notes
Lengths are unsigned numbers. Use the unsigned conditions above and below.
Talking about longer and shorter makes more sense for strings.
Don't use 3 jumps if a mere fall through in the code can do the job.
Your code in theLoop will probably use CX as a counter. Don't forget to zero CH. Either using 2 instructions like I did above or else use movzx cx, BL if you're allowed to use instructions that surpass the original 8086.
Bonus
mov si, offset str1+2
mov di, offset str2+2
mov al, 0
MORE:
mov dl, [si]
cmp dl, [di]
je EQ
inc al
EQ:
inc si
inc di
loop MORE

NASM x64 read numbers to array and write out

I have got a big problem with reading 10 numbers from keyboard to array and then writing them out.
mov rcx, arr + qword [n]*8 - I don't know how to modify it properly, because actually it causes an error.
Additionally how should I set mov rdx, 1 when I want to read numbers like: 12 123 1234 not only digits?
I would be grateful for any kind of help.
global main
section .text
main:
mov rbp, rsp; for correct debugging
mov rdi, 0
_in:
mov rax, 3
mov rbx, 0
mov rcx, arr + rdi*8
mov rdx, 1
int 80h
mov rax, 3
mov rbx, 0
mov rcx, blank
mov rdx, 1
int 80h
inc qword [n]
cmp qword [n], 10
jz _next
jmp _in
inc rdi
_next:
mov qword [n], 0
mov rdi, 0
_out:
mov rax, 4
mov rbx, 1
mov rcx, arr + rdi*8
mov rdx, 1
int 80h
mov rax, 4
mov rbx, 1
mov rcx, nl
mov rdx, 1
int 80h
inc qword [n]
cmp qword [n], 10
jz _end
jmp _out
inc rdi
_end:
mov rax, 1
mov rbx, 0
ret
section .data
arr times 10 dq 0
blank db 0
n dq 0
nl db 10
About input and data structures.
Keep reading per char (rdx=1), in a loop. Input chars will be for example '1', '2', '3', '4', '5', 10, the character 10 is new line (maybe check also for other whitespace chars like 13, 9, 32 too, or even flip the test, any char out of '0'..'9' range is end of number).
While reading digits, decide if you want to store them as strings, or as numbers.
If strings, then write every new digit into memory at address arr+n*8+input_char_index, put probably zero value as terminator after the number (your current array can hold at most 7 character long strings + zero for each "n"), or store string length into separate array, or as first byte of element, and make first char go at +1 offset after the length byte, etc... (you can design your data structure as you wish). To display such string just load it's address lea rcx,[arr+n*8] and calculate it's length with strlen (it reads+counts char by char until 0 is found), or load the length if you have it stored somewhere, and sys_write it.
If you want to store numbers, set some spare register as zero ahead of input (for example rdi), then for every digit read do add rdi,rdi lea rdi,[rdi+rdi*4] => that's rdi *= 10, then convert the input character from ASCII digit to 64b 0-9 value, and add it to rdi ... loop until non-digit or newline is read (but 64b unsigned number will overflow for 19+ digits input). After end of input store the value into arr, now arr will contain numerical QWORD values.
To output them, you have to do the conversion in opposite direction, from numerical value into some memory buffer, producing digit by digit ASCII characters (have big enough buffer, again 20+ chars is safe for 64b value). After you have your number stored in memory as ASCII string + know it's length, you can SYS_WRITE it to stdout.
You may also consider to follow some more tutorials first and re-read some theory about common data structures/etc, memory, string encodings, registers, x86 addressing modes, .... before writing your own code (as it feels to me that you are guessing a bit too much, how things work).

How do I convert a string representing a signed hex int into its signed int Doubleword number in 80x86?

So I am taking an assembly language course and I am stuck on this problem from the book:
Using the windows 32 console (so I have an io.h to use), I am supposed to take a valid hex value inputted by the user and then display the actual hex value in the register EAX. So if the user entered "AB CD E2 18", then after the procedure EAX would hold the value: ABCDE218.
The parts that I am stuck on are the A-F values. If I use A for example, I can get the bits to read 00000010, but I don't know how to change that into its hex value A. Here is what I have so far:
.586
.MODEL FLAT
.CODE
hexToInt PROC
push ebp ; save base pointer
mov ebp, esp ; establish stack frame
sub esp, 4 ; local space for sign
push ebx ; Save registers
push edx
push esi
pushfd ; save flags
mov esi,[ebp+8] ; get parameter (source addr)
WhileBlankD:
cmp BYTE PTR [esi],' ' ; space?
jne EndWhileBlankD ; exit if not
inc esi ; increment character pointer
jmp WhileBlankD ; and try again
EndWhileBlankD:
mov eax,1 ; default sign multiplier
IfPlusD:cmp BYTE PTR [esi],'+' ; leading + ?
je SkipSignD ; if so, skip over
IfMinusD:
cmp BYTE PTR [esi],'-' ; leading - ?
jne EndIfSignD ; if not, save default +
mov eax,-1 ; -1 for minus sign
SkipSignD:
inc esi ; move past sign
EndIfSignD:
mov [ebp-4],eax ; save sign multiplier
mov eax,0 ; number being accumulated
WhileDigitD:
cmp BYTE PTR [esi],'0' ; compare next character to '0'
jb EndWhileDigitD ; not a digit if smaller than '0'
cmp BYTE PTR [esi],'9' ; compare to '9'
ja TestForHexD
mov bl,[esi] ; ASCII character to BL
and ebx,0000000Fh ; convert to single-digit integer
and eax, ebx
shl eax, 4
inc esi
jmp WhileDigitD
TestForHexD:
cmp BYTE PTR [esi], 'F'
ja EndWhileDigitD
mov bl, [esi]
sub bl, 31h
and ebx, 000000FFh
or al, bl
shl eax, 4
inc esi ; increment character pointer
jmp WhileDigitD ; go try next character
EndWhileDigitD:
; if value is < 80000000h, multiply by sign
cmp eax,80000000h ; 80000000h?
jnb endIfMaxD ; skip if not
imul DWORD PTR [ebp-4] ; make signed number
endIfMaxD:
popfd ; restore flags
pop esi ; restore registers
pop edx
pop ebx
mov esp, ebp ; delete local variable space
pop ebp
ret ; exit
hexToInt ENDP
END
The TestForHex label is where I am trying to convert the ASCII string to hex. I was looking around and read that I could accomplish my goal by shifting and masking, but I can't figure it out and I can't find any examples. At this point I am sure its something really small that I am just over looking, but I am stuck.
There are some bugs in your code.
First, in 0 ... 9 string to integer conversion code, you don't do ASCII to binary conversion as you should do, but instead you do and ebx,0Fh, which is incorrect. You need to subtract '0' (30h) from each ASCII character, like this:
mov bl,[esi]
sub bl,'0' ; sub bl,30h
Then, also in 0 ... 9 string to integer conversion code:
and eax, ebx
If the number consists of only 0...9 digits, and eax, ebx will produce always 0. It should be:
or al,bl
Then, you do shl eax,4, even if you don't know if there will be more digits. That means that the number will be 16 times bigger than it should.
Then, you give the example input with spaces, but your code does not handle spaces (20h) properly, it ends reading input for any value below '0' (30h), it seems to accept only leading spaces (skip this if you don't want to accept spaces in between).
So, the entire code block above should be:
WhileDigitD:
cmp byte ptr [esi], ' ' ; delete this if you don't want spaces in between.
je next_char ; delete this if you don't want spaces in between.
cmp BYTE PTR [esi],'0' ; compare next character to '0'
jb EndWhileDigitD ; not a digit if smaller than '0'
cmp BYTE PTR [esi],'9' ; compare to '9'
ja TestForHexD
mov bl,[esi] ; ASCII character to BL
sub bl,'0' ; sub bl,30h -> convert ASCII to binary.
shift_eax_by_4_and_add_bl:
shl eax,4 ; shift the current value 4 bits to left.
or al,bl ; add the value of the current digit.
next_char:
inc esi
jmp WhileDigitD
I also added labels next_char and shift_eax_by_4_and_add_bl. The reason for next_char should be evident, shift_eax_by_4_and_add_bl is to minimize duplicate code of 0...9 and A...F code blocks, see below.
You don't check that that the hexadecimal A...F digit is within range A ... F, only that it's below or equal to F. Otherwise it has same bug with shl eax,4. And as usually duplicate code should be avoided, I added shift_eax_by_4_and_add_bl label to minimize duplicate code.
So I think it should be:
Edit: corrected sub bl,31h -> sub bl,('A'-0Ah).
TestForHexD:
cmp BYTE PTR [esi], 'A'
jb EndWhileDigitD
cmp BYTE PTR [esi], 'F'
ja EndWhileDigitD
mov bl,[esi]
sub bl,('A'-0Ah) ; sub bl,55 -> convert ASCII to binary.
jmp shift_eax_by_4_and_add_bl
If you need to convert a character (for simplicity, say, in upper case) representing a hex digit into the value of that digit you need to do this:
IF char >= 'A'
value = char - 'A' + 10
ELSE
value = char - '0'
ENDIF
If you need to do the reverse, you do the reverse:
IF value >= 10
char = value - 10 + 'A'
ELSE
char = value + '0'
ENDIF
Here you exploit the fact that the ASCII characters 0 through 9 have consecutive ASCII codes and so do the ASCII characters A through F.

Resources