flat assembler
Message board for the users of flat assembler.

Index > Main > FASMLIB tutorial part 3 - String Library

Author
Thread Post new topic Reply to topic
vid
Verbosity in development


Joined: 05 Sep 2003
Posts: 7105
Location: Slovakia
vid 09 Aug 2006, 08:18
This time we will be talking about FASMLIB's string library.

This string library is adopted (stolen Wink ) from Fresh project, for which it was written mainly by JohnFound and later improved by Decard and roticv.

So to the string library... first i will explain WHY is string library needed. I bet that if you ever needed to work with strings in ASM, you found out yourself Wink . Even C's handling of strings is not the best choice for application nowadays. Because these strings are "static", eg. they reside on given place in memory, their maximal length is limited. But you often need a string, for which you can't tell what it's maximal length should be. And whenever you pass string to another routine, you also must pass it's buffer's length, so routine won't overflow. And this is another problem - static strings are often unchecked for overflow, and placed on stack near exploitable things.

All these problems can be solved by using dynamic strings. That means, that strings will reside in allocated block of memory. These blocks can be "reallocated" any time, eg. size of these blocks can be changed. This way, size of string is not limited.

Such string is created using procedure str.new from str module. It creates empty dynamic string, and returns handle to it.

Code:
call str.new
jc error
mov [string_handle], eax    

Now what is string handle.

Simply, string handle is a number, which identifies dynamic string inside str module. This number is always lesser than 10000h, unlike data pointers which are always higher. This way it is easily possible to differentiate between dynamic and static strings. This allows routines of str module to accept both dynamic and static strings.

Note that static strings are accepted only as source, you cannot modify them with str module.

Each created string handle should be deleted. We use procedure str.del for this. It takes string handle to delete as argument.
Code:
push [string_handle]
call str.del
jc error    

So let's see some more action with this module:

Code:
call str.new
jc error
mov ebx, eax

;now string, whose handle is in EBX, is empty

push _str1
push ebx
call str.cat ;concatenate strings, eg. append _str1 to end of EBX string
jc error

;now string whose handle is in EBX holds: "Part 1",13,10,0

push _str2
push 3
push ebx
call str.ins
jc error

;now string whose handle is in EBX holds: "Par***t 1",13,10,0

;and don't forget to delete the string and free memory
push ebx
call str.del
jc error

...

_str1 db "Part 1",13,10,0
_str2 db "***",0    

In this example we used str.cat and str.ins.

str.cat concatenates strings, eg. appends second string to end of first string. We use it to move static string "Part 1",13,10 to string handle. Then we use str.ins to insert *** into our string at position 3, eg. behind 3rd character.

Note that when we wanted to initialize string with constant value, we did it this way:
Code:
call str.new
jc error
mov ebx, eax

push _string_const
push ebx
call str.cat
jc error    


There is also more efficient way, using str.dup. str.dup takes one string as argument, and returns handle to it's duplicate. Argument can be constant string, so this way we can create and initialize dynamic string from constant string:
Code:
;same effect as above example
push _string_const
call str.dup
jc error
mov ebx, eax    

str module offers plenty of basic routines to manipalute with strings, you can find their description in doc/modules/str.txt. But sometimes you need to do something with strings, that str module can't do. In that case, you need pointer to string buffer, not handle. You can obtain pointer to string from handle using str.ptr. It takes string handle as argument, and returns pointer to string buffer. You can also pass pointer to string constant to str.ptr and it will then return the same pointer you passed. So you can use str.ptr to get pointer to string from either handle or string constant pointer.

Best explained on example: Let's say you want to replace every ':' character in string with space ' '
Code:
        ;EBX holds string handle

        ;ESI = pointer to EBX string
        push ebx
        call str.ptr
        jc error
        mov esi, eax

        ;replace ':' characters
.scan_char:
        lodsb
        cmp al, 0       ;0 character is end of string
        je .end_of_string
        cmp al, ':'
        jne .scan_char
        mov byte [esi-1], ' ' ;replace char
        jmp .scan_char
.end_of_string:    

But problem comes out when you want to change length of string. If you want to make string longer, you need to enlarge buffer, and if you are shortening the string, you should also shorten buffer to preserve memory. So you should use str.setlen to set length of string before every such operation. Don't care if the size really needs to be changed, it is solved (a little) in str.setlen. Just calculate the resulting string length before every operation, and call this.

Here is example, that will append 13,10 characters (end of line) to the end of string:
Code:
        ;EBX holds string handle
        
        ;ECX = length of string
        push ebx
        call str.len ;str.len returns lenght of string
        jc error
        mov ecx, eax
        
        ;set new length of string to ECX+2
        mov eax, ecx
        add eax, 2
        push eax ;new length of string
        push ebx ;string handle to resize
        call str.setlen
        jc error

        ;ESI = pointer to EBX string buffer
        push ebx
        call str.ptr
        jc error
        mov esi, eax

        ;append 13,10 to end of EBX string
        mov word [esi+ecx], 0A0Dh    

But there is one more issue with modyfing string yourself - after you get the pointer with str.ptr, you shouldn't use any function from str module that alter string length, because string buffer can be moved to another place in memory, and so the pointer will become invalid. For example, if previous code will be written in this way, it will be a possible bug:

Code:
        ;EBX holds string handle
        
        ;ECX = length of string
        push ebx
        call str.len ;str.len returns lenght of string
        jc error
        mov ecx, eax
        
        ;ESI = pointer to EBX string buffer
        push ebx
        call str.ptr
        jc error
        mov esi, eax

        ;set new length of string to ECX+2
        mov eax, ecx
        add eax, 2
        push eax ;new length of string
        push ebx ;string handle to resize
        call str.setlen
        jc error

        ;BUG!!! pointer in ESI may have becomed invalid, if string was moved

        ;append 13,10 to end of EBX string
        mov word [esi+ecx], 0A0Dh    


In conclussion, i suggest not to modify string buffer yourself, if you can use procedures from str module, you will prevent some bugs. Hopefully, more functions will be added to str module soon.

I also suggest you to browse documentation of str module, so you know what it can do and what it can't.

And don't forget that you must initialize str module before you use it with str.init, and uninitialize it with str.uninit after you're done with it


Last edited by vid on 10 Sep 2006, 20:41; edited 2 times in total
Post 09 Aug 2006, 08:18
View user's profile Send private message Visit poster's website AIM Address MSN Messenger ICQ Number Reply with quote
vid
Verbosity in development


Joined: 05 Sep 2003
Posts: 7105
Location: Slovakia
vid 10 Sep 2006, 20:14
Here is one more thing you should be interested in. You may remember how we used to output strings in tutorial part 2 - we had to pass pointer to buffer, and number of bytes. This isn't very handy way for regular use. Good-old null-terminated strings are easier. With knowledge we now have, we can write procedure that does this for us:
Code:
;===============================================================================================
; stream.write
; desc: writes string to stream
; args: stream - handle of stream to write to
;       string - string handle or pointer
; ret: CF set on error
;===============================================================================================
proc stream.write, stream, string
        push    ebx ecx
        
        ; EBX = handle or pointer to string
        mov     ebx, [string]
        
        ; ECX = length of EBX string
        push    ebx
        call    str.len
        jc      .r
        mov     ecx, ebx

        ; EAX = actual pointer to string (if it is string handle)
        push    ebx
        call    str.ptr
        jc      .r
        
        ; write string to stream
        push    ecx             ; length of buffer
        push    eax             ; pointer to buffer
        push    [stream]        ; stream handle
        call    stream.writebuf
        jc      .r      

        ;everything went OK
        clc

.r:     pop     ecx ebx
        ret
endp    


in fact, FASMLIB already has such procedure, with same name and arguments Wink
Post 10 Sep 2006, 20:14
View user's profile Send private message Visit poster's website AIM Address MSN Messenger ICQ Number Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2024, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.