This time we will be talking about FASMLIB's string library.
This string library is adopted (stolen
) 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
. 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.
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.
push [string_handle]
call str.del
jc error
So let's see some more action with this module:
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:
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:
;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
' '
;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:
;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:
;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