Author Topic: StrToInt Function  (Read 6102 times)

Offline RagingGrim

  • Jr. Member
  • *
  • Posts: 28
StrToInt Function
« on: January 07, 2015, 05:36:22 PM »
I'm back and on vacation! :D

I'm using my phone as a hotspot and we really don't have the best connectivity so I rushed my post in notepad - sorry if I messed up the tags or anything
I wrote a function to convert a string ( ACII characters ) to an integer ( unsigned integer... Although using neg for signed is easy enough :p )
At school we use strtoint ( delphi ) a lot and I thought it'd be fun to get a look at how it's done. Due to the lack of internet connection I kinda guessed my way through this
Any advise / ideas? ^^

Quick note though , this isn't for a project or homework assignment ( I'm afraid our education system is far below teaching actual programming ^^ ) - we had to create a geocash program for our project this year which made up 25 % of our final score XD All it had to do was read from a text file , some database manipulation and a few images which in delphi really isn't hard at all. I'm doing the assembly purely because I like it and I think the experience is good ^^


Code: [Select]
BITS 32
extern __cprintf
extern __getch
global main

section .data
intFormat db "%d",0
AStr db "12334",0

section .text
main:
mov ebx,AStr
call strToInt ;"1"=00110001
;1=1 -> 0000000001
push eax
push intFormat
call __cprintf
call __getch





;eax contains the strlength and will contain the resulting int
;ebx contains the string
strToInt:
call strlen
mov edi,10 ;Mul Value
mov edx,eax ;save strlength in edx
xor ecx,ecx ;counter

mov eax,[ebx+0] ;first char in ebx
and eax,0b00001111 ;Convert From ASCII to INT
;
strToInt_Loop:
inc ecx
CMP ecx,edx
JE strToInt_Done
mov esi,[ebx+ecx]
and esi,0b00001111
push edx ;save value to stack
mul edi
pop edx ;load value from stack
add eax,esi
JMP strToInt_Loop

strToInt_Done:
RET

strlen:
mov eax,0
strlen_loop:
inc eax
CMP byte [eax + ebx],0
JNE strlen_loop
RET

I was also wondering if my code is "spaghetti code" ; I mean do the jumps make sense? I tried to use descriptive labels ^^

Thanks in advance
« Last Edit: January 07, 2015, 05:38:55 PM by RagingGrim »

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: StrToInt Function
« Reply #1 on: January 17, 2015, 07:28:52 AM »
Hi RagingGrim,

Sorry for the delayed reply. I've been having a tough time getting "in the mood". Getting way behind in my personal correspondence, too (as you know). I'll try to get caught up...

Your meaningful labels are great! Your jumps are more or less as they need to be, but I wonder "how do we end?". After printing the result and waiting for a key, you appear to "fall through" into your StrToInt subroutine again. When this again gets to "ret" it's going to try to "ret" to your format string (which you probably should have removed from the stack?). Doesn't it crash? (you can tell I haven't tried this yet - still planning to...)

I'm also concerned that your strlen routine is going to fail if it's passed a zero-length string. You don't, so it isn't a problem, but I think that could be more robust. I don't actually use "strlen" when converting a string to an integer. I just scan down the string looking for a decimal digit, and stop on anythng that isn't - a terminating zero, a terminating linefeed (which Linux likes to give us), or any non-digit character.

Code: [Select]
...
    push buffer
    call atoi
    add esp, 4 ; "remove" parameter from stack
    mov [number], eax ; save the result
...

;--------------------
atoi:
    push ebx
   
    mov edx, [esp + 8]  ; pointer to string
    xor ebx, ebx ; assume not negative
   
    cmp byte [edx], '-'
    jnz notneg
    inc ebx ; indicate negative
    inc edx ; move past the '-'
notneg:

    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 - '0']

    jmp short .top
.done:
    test ebx, ebx
    jz notminus
    neg eax
notminus:
    pop ebx
    ret
;------------------------

This isn't so great, either. I think doing the arithmetic with the two "lea"s is cute, but "lea" doesn't touch flags so there's no way I can check for overflow. Doing it the way you do, you could check carry flag (for unsigned) or overflow flag (for signed) after  "mul" and "add". You don't, but you could. You don't check for a non-digit character... but you could.

If we were to detect an overflow or a non digit character (might want to skip leading spaces, and '-', maybe '+') what would we do? Can't return -1 - that could be a valid result. Set the carry flag maybe - and check it after each call? Doesn't play nicely with C (if we care). Best thing might be to pass, as a parameter, an address to put "status" or "error" - we could distinguish between overflow and "bad character" and instruct  the user accordingly. Easier to ignore it. I'm curious what Delphi does in these situations.

(on another topic - in case I don't get to it - "port" is a word - big-endian)

Best,
Frank


Offline RagingGrim

  • Jr. Member
  • *
  • Posts: 28
Re: StrToInt Function
« Reply #2 on: January 17, 2015, 11:48:14 AM »
I'm still working through your reply XD as for falling into the strtoint function again the file i was posting from is just a temporary text file i use for creating and saving functions so I apologise for not exiting properly ^^
I'll definitely look at the string functions again because I'm going to need them pretty soon :D

We have this contest coming up that's hosted by our regional municapility ( regeering -> Usually my afrikaans to english is pretty good but I'm not sure about that particular word ) and I thought since we're allowed to submit practically anything I'd do something in nasm. The olympiades we have are all more math based and we're only allowed to use java , delphi and python ( not that I have anything against the languages it's just when I started with C and was introduced to memory allocation and pointers it just blew my mind on so many levels ). I was thinking along the lines of a RAT (remote access tool) although in this scenario I have a server and a few clients connecting to it. The server is sends commands to the clients which in turn execute something and return a value to the server.

A few nice and fun functions to implement that I thought of while my teacher was discussing exam papers XD
1.) Tracking the mouse movement to determine whether a user is active on the station or not ( got the idea from malware )
2.) Disabling/Enabling the shutdown of certain workstations ( using the registry )
3.) Process monitor that kills any processes that are in a blacklist
4.) Disabling/Enabling the network ( Although this seems impractical because obviously i'd lose connection to the client :/ )
     Change that to network functions XD
5.) Some basic info about a client.
6.) A scripting engine for the client.

Then it hit me that if I made the server and had let's say ten clients connected to it I'd need 10 sockets for each client and well it'd slow down the program . I'm not THAT new to socket programming so I immediately thought of using threading to solve the problem. The idea was that I'd spawn one thread for every client connected. This is where things started to get interesting.

I started writing on the project yesterday and already managed to implement a server which handles a single connection and prints whatever is sent to it . Today I'm looking into the winapi's heaprealloc , heapalloc and heapfree functions aswell as createthread. I'm not familiar with any of the heap functions but I think I get them . As for createthread ... Obviously in delphi the arguments are exactly the same similar my only problem is that I can't figure out where my address is that I pass. Being new to assembly and all. I thought it might have something to do with the esi,ebp and esp register but I'm still not certain. I think I'll solve that in an hour or so though ^^

This is what I have thusfar!

Code: [Select]
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;THIS IS AN EXAMPLE TO HANDLE A SINGLE CLIENT ONLY
;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
BITS 32
extern _WSAStartup
extern _socket
extern _bind
extern _listen
extern _HeapReAlloc
extern _GetProcessHeap
extern _HeapAlloc
extern _HeapFree
extern _accept
extern _recv
extern _ExitProcess
extern __cprintf
extern _CreateThread
extern _bind
extern _ExitWindowsEx
extern _WSAGetLastError
extern __getch
extern _htons
global main
section .bss
WSADATA resb 400 ;Checked in C
sockaddr_inA resb 16 ;Checked in C
rcvBuffer resb 200
clientSockets resd 1
section .data
;ERRORS
errFormat db "ERRORCODE : %d",0ah,0
;ERRORS END
;DWORD
counterGeneral dd 0
counterSocket dd 0
ProcessHeap dd 0
socketServer dd 0
;DWORD END
;BYTES
strFormat db "%s",0
strConnected db "Client Connected!",0ah,0
strShutdown db "Shutdown",0
;BYTES END


section .text
main:
call _GetProcessHeap
mov dword [ProcessHeap],eax
;1.) Init WSA
push dword WSADATA
push 514 ;MakeWord(2,2)
call _WSAStartup
CMP eax,byte 0
JNE lblErrExit

;2.) Create A Socket
push 6 ; TCP
push 1 ; SOCK_STREAM
push 2 ; AF_INET
call _socket
cmp eax,-1 ;SOCK_ERROR
JE lblErrExit
mov dword [socketServer],eax

;3.) Bind The Socket
mov word [sockaddr_inA+0],2 ;AF_INET
push 968
call _htons
mov dword [sockaddr_inA+2],eax
mov dword [sockaddr_inA+6],0x0 ;ALL IP's

push 16
push sockaddr_inA
push dword [socketServer]
call _bind
CMP eax,0
JNE lblErrExit

;4.) Listen For A Connection

push 0
push dword [socketServer]
call _listen
CMP eax,0
JNE lblErrExit

;5.1) Spawn A Thread
;5.2.) Accept A Connection
push 0
push 0
push 0
push lblAccept
push 9
push 0
call _CreateThread


;5.3) Create a dynamic array for clientSockets
;LPVOID WINAPI HeapAlloc(  _In_  HANDLE hHeap,  _In_  DWORD dwFlags,  _In_  SIZE_T dwBytes
push 1
push 0
push dword [ProcessHeap]
call _HeapAlloc
CMP eax,0
JE lblErrExit

lblAccept:
push 0
push 0
push dword [socketServer]
call _accept
cmp eax,-1
JE lblErrExit
mov edx,dword [Counter]
add edx,4
mov dword [clientSockets+edx],eax ;Save Client Handle
push 0
push 0
push 0
push lblRecv
push 9
push 0
call _CreateThread
push strConnected
push strFormat
call __cprintf
add esp , 8
JMP lblAccept
;6.) Recv data
lblRecv:
push 0
push 255
push rcvBuffer
mov edx,dword [Counter]
push dword [socketClient+edx]
call _recv
cmp eax,0
JE lblErrExit
cmp eax,-1
JE lblErrExit
push rcvBuffer
push strFormat
call __cprintf
mov ebx, rcvBuffer
mov ecx, strShutdown
call strCmp
cmp eax,-1
JE lblShutdown
add esp,8
mov dword [rcvBuffer],""
JMP lblRecv

lblErrExit:
call _WSAGetLastError
push eax
push errFormat
call __cprintf
add esp , 8
call __getch
call _ExitProcess

strCmp: ;Compare ebx to ecx
;>>Setup for the loop
mov edx,-1;We Want To Start By Comparing AStr[0] to BStr[0]
mov eax,-1
strCmp_Loop:
inc edx ;Increment Index
mov ah,[ebx+edx]
CMP ah,[ecx+edx]
JNE strCmp_NEQU
CMP ah,0
JE strCmp_Done
JMP strCmp_Loop
strCmp_NEQU:
mov eax,edx
strCmp_Done:
RET

lblShutdown:
push 0
push 0x00000001 ;Shutdown
call _ExitWindowsEx


I'll take a short break and quickly have a look see at what delphi does with the strtoint function. Great idea on setting the carry flag :/ I felt a little lost when you said that bit about the negative 1. What do you mean it doesn't play well with C though? ^^

I also thought I'd post delphi's version of it here if you'd like to have a look ^^
Code: [Select]
function _ValLong(const s: string; var code: Integer): Longint;
{$IFDEF PUREPASCAL}
var
  I, Len, Digit: Integer;
  Negative, Hex: Boolean;
begin
  // U-OK
  I := 1;
  code := -1;
  Result := 0;
  Negative := False;
  Hex := False;
  Len := Length(s);
  while (I <= Len) and (s[I] = ' ') do
    Inc(I);
  if I > Len then
    Exit;
  case s[I] of
    '$',
    'x',
    'X':
      begin
        if I = Len then
        begin
          Code := I + 2; // Emulate Win32 _ValLong behaviour
          Exit;
        end;
        Hex := True;
        Inc(I);
      end;
    '0':
      begin
        Hex := (Len > I) and ((s[I+1] = 'X') or (s[I+1] = 'x'));
        if Hex then
          Inc(I, 2);
      end;
    '-':
      begin
        if I = Len then
        begin
          Code := I + 1; // Emulate Win32 _ValLong behaviour
          Exit;
        end;
        Negative := True;
        Inc(I);
      end;
    '+':
      begin
        if I = Len then
        begin
          Code := I + 1; // Emulate Win32 _ValLong behaviour
          Exit;
        end;
        Inc(I);
      end;
  end;
  if Hex then
    while I <= Len do
    begin
      // check for overflow
      if Result > (High(Result) shr 3) then
      begin
        code := I;
        Exit;
      end;
      case s[I] of
        '0'..'9': Result := Result * 16 + Ord(s[I]) - Ord('0');
        'a'..'f': Result := Result * 16 + Ord(s[I]) - Ord('a') + 10;
        'A'..'F': Result := Result * 16 + Ord(s[I]) - Ord('A') + 10;
      else
        code := I;
        Exit;
      end;
      Inc(I);
    end
  else
    while I <= Len do
    begin
      // check for overflow
      if Result > (High(Result) div 10) then
      begin
        code := I;
        Exit;
      end;
      Digit := Ord(s[I]) - Ord('0');
      if (Digit < 0) or (Digit > 9) then begin
         Code := I;
         Exit;
      end;
      Result := Result * 10 + Ord(s[I]) - Ord('0');
      Inc(I);
    end;
  if Negative then
    Result := -Result;
  code := 0;
end;
{$ELSE !PUREPASCAL}
asm
{       FUNCTION _ValLong( s: string; VAR code: Integer ) : Longint;        }
{     ->EAX     Pointer to string       }
{       EDX     Pointer to code result  }
{     <-EAX     Result                  }

        PUSH    EBX
        PUSH    ESI
        PUSH    EDI

        MOV     ESI,EAX
        PUSH    EAX             { save for the error case       }

        TEST    EAX,EAX
        JE      @@empty

        XOR     EAX,EAX
        XOR     EBX,EBX
        MOV     EDI,07FFFFFFFH / 10     { limit }

@@blankLoop:
        MOV     BX,[ESI]
        ADD     ESI, 2
        CMP     BX,' '
        JE      @@blankLoop

@@endBlanks:
        MOV     CH,0
        CMP     BX,'-'
        JE      @@minus
        CMP     BX,'+'
        JE      @@plus

@@checkDollar:
        CMP     BX,'$'
        JE      @@dollar

        CMP     BX, 'x'
        JE      @@dollar
        CMP     BX, 'X'
        JE      @@dollar
        CMP     BX, '0'
        JNE     @@firstDigit
        MOV     BX, [ESI]
        ADD     ESI, 2
        CMP     BX, 'x'
        JE      @@dollar
        CMP     BX, 'X'
        JE      @@dollar
        TEST    BX, BX
        JE      @@endDigits
        JMP     @@digLoop

@@firstDigit:
        TEST    BX,BX
        JE      @@error

@@digLoop:
        SUB     BX,'0'
        CMP     BX,9
        JA      @@error
        CMP     EAX,EDI         { value > limit ?       }
        JA      @@overFlow
        LEA     EAX,[EAX+EAX*4]
        ADD     EAX,EAX
        ADD     EAX,EBX         { fortunately, we can't have a carry    }
        MOV     BX,[ESI]
        ADD     ESI, 2
        TEST    BX,BX
        JNE     @@digLoop

@@endDigits:
        DEC     CH
        JE      @@negate
        TEST    EAX,EAX
        JGE     @@successExit
        JMP     @@overFlow

@@empty:
        ADD     ESI, 2
        JMP     @@error

@@negate:
        NEG     EAX
        JLE     @@successExit
        JS      @@successExit           { to handle 2**31 correctly, where the negate overflows }

@@error:
@@overFlow:
        POP     EBX
        SUB     ESI,EBX
        JMP     @@exit

@@minus:
        INC     CH
@@plus:
        MOV     BX,[ESI]
        ADD     ESI, 2
        JMP     @@checkDollar

@@dollar:
        MOV     EDI,0FFFFFFFH
        MOV     BX,[ESI]
        ADD     ESI, 2
        TEST    BX,BX
        JZ      @@empty

@@hDigLoop:
        CMP     BX,'a'
        JB      @@upper
        SUB     BX,'a' - 'A'
@@upper:
        SUB     BX,'0'
        CMP     BX,9
        JBE     @@digOk
        SUB     BX,'A' - '0'
        CMP     BX,5
        JA      @@error
        ADD     BX,10
@@digOk:
        CMP     EAX,EDI
        JA      @@overFlow
        SHL     EAX,4
        ADD     EAX,EBX
        MOV     BX,[ESI]
        ADD     ESI, 2
        TEST    BX,BX
        JNE     @@hDigLoop

        DEC     CH
        JNE     @@successExit
        NEG     EAX

@@successExit:
        POP     ECX                     { saved copy of string pointer  }
        XOR     ESI,ESI         { signal no error to caller     }

@@exit:
        SHR     ESI, 1
        MOV     [EDX],ESI
        POP     EDI
        POP     ESI
        POP     EBX
end;

The strtoint function calls Val which is short for Validate ^^
And please no don't apologise for late replies or anything like that, you have absolutely no obligation to help people out here ( unless it's like a moral thing being the developer - I wouldn't know because I've never done anything that huge :p - ).

I think it's really great that you do though not a lot of people would go to this amount of trouble ^^
Oh and please call me Connor :p
« Last Edit: January 17, 2015, 11:57:20 AM by RagingGrim »