flat assembler
Message board for the users of flat assembler.

Index > Windows > strcat(GetLastError)

Author
Thread Post new topic Reply to topic
jumpex



Joined: 29 Jan 2006
Posts: 38
jumpex 20 Jul 2010, 17:20
This is my implementation of strcat as a Flat Assembler macro.

Code:
macro astrocat dst,src
{
 mov edi,dst

 xor al,al

 xor ecx,ecx
 dec ecx
 cld

 repne scasb
 dec edi

 mov esi,src

 cpy:
  lodsb

  push eax
   ;p
  pop eax

  stosb
  test al,al
 jnz cpy
}    


First question - how do I convert it to a procedure?

This is my whole program so far:
Code:
format PE GUI 4.0
include 'win32ax.inc'

entry start

;=================================
;section '.macro' readable

include 'astrocat.asm'

macro p {invoke MessageBoxA,0,psed,psed,0}
macro e
{
 invoke GetLastError
 mov [errn],eax

 lea eax,[errm]
 invoke FormatMessageA,FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_FROM_SYSTEM,0,[errn],0,eax,0,0

 astrocat [errm],slen

 invoke MessageBoxA,0,[errm],errt,0
}
;=================================


;=================================
section '.data' readable writeable

;RND
slen dd "232",0

;DEBUG
errt db "ERROR",0
errn dd 0
errm dd 0
psed db "PAUSED",0
;=======================================


;=======================================
section '.text' code readable executable
start:

invoke GetModuleHandleA,0
;invoke GetModuleHandleA,'f' ;'f' is an invalid module
e

invoke ExitProcess,0
;=======================================

;==============================================
section '.idata' import data readable writeable

 library kernel32,'KERNEL32.DLL',\
         user32,'USER32.DLL'

 import kernel32,\
        ExitProcess,'ExitProcess',\
        FormatMessageA,'FormatMessageA',\
        GetLastError,'GetLastError',\
        GetModuleHandleA,'GetModuleHandleA'

 import user32,\
        MessageBoxA,'MessageBoxA'
;==============================================
    


My strcat function works great with these two (for example):

Code:
aaaa dd "AA",0
bbbb dd "BB",0
    


It also works without problems, when GetModuleHandleA fails, but if GetLastError returns 0, it CRASHES, saying it tried to access some memory location that can't be null (IDAPro).

Hense - it the whole code works with invoke GetModuleHandleA,'f', which generates an error code not equal to zero, but not with invoke GetModuleHandleA,0, which doesn't.

I'm extremely puzzled.
Does anybody see anything wrong (it can be anything, not even directly related to the question at hand)?
Post 20 Jul 2010, 17:20
View user's profile Send private message Reply with quote
bitshifter



Joined: 04 Dec 2007
Posts: 796
Location: Massachusetts, USA
bitshifter 20 Jul 2010, 18:23
Code:
aaaa dd "AA",0
    

should be BYTE or TCHAR and not dword
Code:
aaaa db "AA",0    

It takes a dword to point to it though...

As for GetLastError, you should use SetLastError(0)
to clear the last code before using it...

And strcat, well win32 api has its lstrcat procedure.
I prefer to use it when already implemented by win lib.
It works for ASCII and unicode so its more robust.

But i can see the need to roll your own
whether it for practice or just for fun or special purpose.

Edit:
Here is one that Loco wrote which is small and has no length restrictions...
http://board.flatassembler.net/topic.php?t=7063
Post 20 Jul 2010, 18:23
View user's profile Send private message Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
LocoDelAssembly 20 Jul 2010, 18:44
bitshifter wrote:

As for GetLastError, you should use SetLastError(0)
to clear the last code before using it...
You mean at application start, right?

jumpex, since you're using win32ax.inc then you're already provided with the proc macro so, you can do this:
Code:
proc astrocat, src, dst
 mov edi,[dst]

 xor al,al

 or ecx, -1 ; This also takes 3 bytes.
 cld ; In general this is not required, by convention, anything that used std should use cld before leaving so the DF is always set to forward (all Windows API adhere to this convention, for instance).

 repne scasb
 dec edi

 mov esi, [src]

 cpy:
  lodsb
  stosb
  test al,al
 jnz cpy

ret ; REQUIRED AND IT MUST BE WRITTEN ALL LOWERCASE, endp doesn't add it automatically
endp    
You can see more info in http://flatassembler.net/docs.php?article=win32

To invoke the procedure you can either push the values and call or use "stdcall astrocat, source, destination" that does the same automatically.
Post 20 Jul 2010, 18:44
View user's profile Send private message Reply with quote
bitshifter



Joined: 04 Dec 2007
Posts: 796
Location: Massachusetts, USA
bitshifter 20 Jul 2010, 19:01
LocoDelAssembly wrote:

You mean at application start, right?


MSDN says for each time you expect a meaningful result...
Code:
invoke SetLastError,0
call   DoSomething
invoke GetLastError
...
invoke SetLastError,0
call   DoSomethingElse
invoke GetLastError
    

_________________
Coding a 3D game engine with fasm is like trying to eat an elephant,
you just have to keep focused and take it one 'byte' at a time.
Post 20 Jul 2010, 19:01
View user's profile Send private message Reply with quote
jumpex



Joined: 29 Jan 2006
Posts: 38
jumpex 20 Jul 2010, 19:04
Thanks for pointing these out.
I've added SetLastError.

Also tried with lstrcat, but I don't know how to pass [errm] to it.
My strcat gets it like this - [errm]. But I'm not sure that's even right.

As for the DD or DB - does it matter that much?

Another question - how do I convert this mystical errm char* pointer to some variable I can use with my strcat, the strcat you gave me a link to or lstrcat?

Again - WHY does my program work with any other error, but not with "no error"? (it concatenates right)
The pieces of code aren't even linked.

And lastly, do you see the "p" macro in my source?
If I put that in my strcat macro like this:

Code:
macro astrocat dst,src
{
 mov edi,dst

 xor al,al

 xor ecx,ecx
 dec ecx
 cld

 repne scasb
 dec edi

 mov esi,src

 cpy:
  lodsb

  push eax
   p
  pop eax

  stosb
  test al,al
 jnz cpy
}
    


... it works. Pauses every time, but doesn't hang with ANY values.
What's this black magic?
Post 20 Jul 2010, 19:04
View user's profile Send private message Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
LocoDelAssembly 20 Jul 2010, 20:02
Well, after doing some testings seems that the problem is that you can't write the memory allocated by FormatMessageA, so one solution would be to allocate memory yourself or even reserve it in the data area like this:
Code:
macro e
{
 invoke GetLastError
 mov [errn],eax

 invoke FormatMessageA,FORMAT_MESSAGE_FROM_SYSTEM,0,[errn],0,errm,64*1024 - 1,0

 astrocat errm,slen

 invoke MessageBoxA,0,errm,errt,0
}    
And in the data section do this change:
Code:
errm rb 128*1024; errm dd 0    
(The size is not really needed to be that large)

I hope I understand correctly that your problem was that the program was either not showing the message or even crashing (the latter was the problem I had here but it was working when running from OllyDbg).
Post 20 Jul 2010, 20:02
View user's profile Send private message Reply with quote
jumpex



Joined: 29 Jan 2006
Posts: 38
jumpex 20 Jul 2010, 21:03
I am very thankful for your time, LocoDelAssembly.
But sadly, you have misunderstood my problem.

Problem
{
The thing is, it works with any error number that's not a ZERO(0).
"It works" -> concatenated and outputed with MessageBox without problems or crashes.

But with a GetLastError return (stored in errn) equal TO 0, it crashes.
}

In these conditions (NULL in errn), it doesn't crash only if I add the magical "p" macro. I don't understand what the MessageBox call in the p macro changes. It doesn't even crash after debugging, if I choose to skip the exception (null memory reference).
Post 20 Jul 2010, 21:03
View user's profile Send private message Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
LocoDelAssembly 20 Jul 2010, 21:54
It works here with errn==0 (but with the changes I provided).

Code:
 lea ebx,[errm]
 invoke FormatMessageA,FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_FROM_SYSTEM,0,0,0,ebx,40000,0
 mov ebx, [ebx]
 mov dword [ebx+44], '234' ; There is a '\0' char implicitly at the end
 invoke MessageBoxA,0,ebx,errt,0    
That fragment works on my computer (it displays "La operaciĆ³n se ha completado correctamente.234"), but if I change 44 to 45 the message is displayed but after accepting the program crashes. With 46 the program crashes when calling the MessageBox (OllyDbg shows it is allocating from heap).

It seems that we are having heap corruption here, but I don't understand why it happens, the documentation says that the parameter specifies the MINIMUM number of TCHARs (when FORMAT_MESSAGE_ALLOCATE_BUFFER is used, maximum size otherwise), but it seems that it ends up allocating exactly the amount needed for that message... The fact you see your problem solved when using that MessageBox inside astrocat may be just consequence of the unpredictible behaviour when corrupting the heap (apparently favorable in your case, but maybe if you exercise the heap a bit more you'll see new problems)

The stack when 46 is used:
Code:
Call stack of main thread
Address    Stack      Procedure / arguments                 Called from                   Frame
0007FF14   7E3A890D   ? ntdll.RtlAllocateHeap               USER32.7E3A8907               0007FF10
0007FF18   00090000     hHeap = 00090000
0007FF1C   00000008     Flags = HEAP_ZERO_MEMORY
0007FF20   00000064     HeapSize = 64 (100.)
0007FF28   7E3A8927   ? USER32.7E3A88F7                     USER32.7E3A8922               0007FF24
0007FF40   7E3E6433   ? USER32.MBToWCSEx                    USER32.7E3E642E               0007FF3C
0007FF74   7E3D0877   USER32.MessageBoxTimeoutA             USER32.7E3D0872               0007FF70
0007FF94   7E3D082F   USER32.MessageBoxExA                  USER32.7E3D082A               0007FF90
0007FF98   00000000     hOwner = NULL
0007FF9C   00093850     Text = "La operaci",F3,"n se ha co
0007FFA0   00401004     Title = "ERROR"
0007FFA4   00000000     Style = MB_OK|MB_APPLMODAL
0007FFA8   00000000     LanguageID = 0 (LANG_NEUTRAL)
0007FFB0   00402050   USER32.MessageBoxA                    test.0040204A                 0007FFAC
0007FFB4   00000000     hOwner = NULL
0007FFB8   00093850     Text = "La operaci",F3,"n se ha co
0007FFBC   00401004     Title = "ERROR"
0007FFC0   00000000     Style = MB_OK|MB_APPLMODAL    


I'll also quote the documentation for reference:
Quote:
nSize [in]

If the FORMAT_MESSAGE_ALLOCATE_BUFFER flag is not set, this parameter specifies the size of the output buffer, in TCHARs. If FORMAT_MESSAGE_ALLOCATE_BUFFER is set, this parameter specifies the minimum number of TCHARs to allocate for an output buffer.

The output buffer cannot be larger than 64K bytes.
But it seems it doesn't have the implicit meaning that you can use the extra bytes...
Post 20 Jul 2010, 21:54
View user's profile Send private message Reply with quote
jumpex



Joined: 29 Jan 2006
Posts: 38
jumpex 20 Jul 2010, 22:13
Your changes scare me, I admit.

But that's not te only reason I don't do it your way - I just see my way working with every other combination. But why does your way work? What's rb (EDIT: reserve data, I see... still don't undertand why it works my way with the other numbers)?

And heap corruption was exactly what I wanted to see - that's what's happening. Because the strcat call and FormatMessage are linked only by [errm]. But I can't see why this happens and HOW a MessageBox can fix it.

That's why I was looking for manual ways of "fixing" the heap (push, pop, xor...).

But another thing - you say that it's crashing because it's overflowing [errm]. I agree with that. But why does it work with, for example, 2 as error number? Or 1, or anything else, except 0? How does the zero for error number affect things so, that the stack gets corrupted?

This works, NO problems:

Code:
lea eax,[errm]
 invoke FormatMessageA,FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_FROM_SYSTEM,0,2,0,eax,0,0

astrocat [errm],slen

invoke MessageBoxA,0,[errm],errt,0  
    


Shows "The system cannot find the file specified. 1440" (with a new line before "1440" - is that strange?). But it works.

I'm really glad that someone's getting near the stage I am at understanding the problem - I honestly didn't expect that.

EDIT(maybe 5th): I think my way, with [errm] as dd worked by magic with other values, because it was wrong. rb was something I had thought about, but never knew existed. Thanks for pointing that out. I think that's the right way to allocate a buffer and have a pointer to it.
Post 20 Jul 2010, 22:13
View user's profile Send private message Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
LocoDelAssembly 20 Jul 2010, 23:07
Quote:

Shows "The system cannot find the file specified. 1440" (with a new line before "1440" - is that strange?). But it works.
You're more lucky than I here, if I set slen to <"1440",0>, it crashes without showing nothing, but with <"144",0> it works but crashes after pressing accept. All my messages here seems to end with a new line.

If you want to investigate this further, you should take a look at what happens when you do a LocalAlloc, FreeAlloc and LocalAlloc again (always same size) to see if the second LocalAlloc gets the same memory address. Maybe the reason that it is healed with MessageBox is that further memory allocations from the heap are performed far away from the corrupted region and that is the reason it is working (notice that "p" is first executed BEFORE the first, and more importantly, the second write which is the first one in overflow the buffer).

I've also checked some minutes ago how FormatMessageA works when you specify your custom size, it first calls an undocumented function CreateVirtualBuffer requesting your curstom size plus one, then it call something that gets the unicode string of your error code, then it is converted to ansi and LocalAlloc is used to get an smaller buffer, and finally ntdll.memmove is used to copy from the virtual buffer to the memory allocated by LocalAlloc (and before returning FreeVirtualBuffer is used). I think that now we can be certain that you get a buffer which better approximates the needed size.

PS: BTW, heap allocations has some granularity, it is not possible to allocate exactly 1 byte for instance AFAIK, this also must be helping your program to survive to some messages whose (strlen(message) + 1) mod granularity >= (strlen(slen) + 1).
Post 20 Jul 2010, 23:07
View user's profile Send private message Reply with quote
jumpex



Joined: 29 Jan 2006
Posts: 38
jumpex 20 Jul 2010, 23:30
Man, I was writing my post and it contaiend your "P.S" in other words.
Exactly - granularity. (I remember really understanding that from "Write Great Code" - not to advertise it or anything, it's nice though)

But your research is way more in depth. Thanks for that.
I'll use rb and your method (which I, after giving it considerable thought, declared the right one).

Also, I did astrocat (name intended) as a procedure, along with another function that I ruled out of my original source for simplicity - itoa (I named it aitos). So here they are, if anyone needs them (along with my errors and crude documentation style):

Code:
proc pastrocat,dst,src
 mov edi,[dst]

 xor al,al

 xor ecx,ecx
 dec ecx
 cld

 repne scasb
 dec edi

 mov esi,[src]

 @@:
  lodsb
  stosb
  test al,al
 jnz @B

ret
endp

;====================================================
;DOCUMENTATION=======================================
;====================================================
;ARG-------------------------------------------------
;----------------------------------------------------
;dst - destination string to contain both (in,out)
;src - source to be added to destination (in)
;----------------------------------------------------
;mov edi,[dst] - destination address is in edi
;
;xor al,al - clear al
;xor ecx,ecx - clear ecx
;dec ecx - ecx = -1
;cld - clear direction flag
;
;repne scasb - find first ascii(0) in string
;dec edi - repne scasb skips null so we get back
;
;mov esi,[src] - source address is in esi
;
;cpy(@@): - copy src to end of dst
;lodsb - load byte from source (esi) to al
;stosb - store byte from al to destination (edi)
;test al,al - AND to test if zero
;jnz cpy(@B) - continue until al is zero (end of esi)
;====================================================

;----------------------------------------------------
;TODO------------------------------------------------
;----------------------------------------------------
;boundry check
;length of each and both together
;----------------------------------------------------    


Code:
proc paitos num,str
 mov edi,[str]
 mov esi,[str]
 xor ebx,ebx
 mov eax,[num]

 @@:
  inc ebx

  xor edx,edx
  mov ecx,10
  div ecx

  push eax

  add dl,'0'
  mov al,dl
  stosb

  pop eax
  test eax,eax
 jnz @B

 xor eax,eax
 stosb
 inc ebx
 sub edi,2

 mov ecx,ebx
 shr ecx,1
 cld

 @@:
  mov al,[esi]
  xchg al,[edi]
  mov [esi],al
  inc esi
  dec edi
 loop @B
ret
endp

;====================================================
;DOCUMENTATION=======================================
;====================================================
;ARG-------------------------------------------------
;----------------------------------------------------
;num - number to be converted (in)
;str - string version of num (out)
;\ebx - holds [str] length (x)
;----------------------------------------------------
;push num - store [num] as it will change
;mov edi,[str] - destination string address is in edi
;mov esi,[str] - in esi too for later reversing
;xor ebx,ebx - ebx will hold [str] length
;mov eax,[num] - register ops faster, keep [num] val
;
;spt(@@): - split number digit by digit /10
;inc ebx - increase [str] length
;
;xor edx,edx - edx is 0 (number to divide is edx:eax)
;mov ecx,10 - ecx is 10 (divisor)
;div ecx - divide eax/ecx, eax holds [num]
;
;push eax - it's next devident if not zero
;
;add dl,'0' - remainder in edx to char num
;mov al,dl - move remainder to al
;stosb - store al (char [num] of remainder) in edi
;
;pop eax - restore eax to check if zero
;test eax,eax - compare eax to 0
;jnz spt(@B) - if eax is not zero, continue
;
;xor eax,eax - zero eax
;stosb - store terminating zero at end of string
;inc ebx - increase string length
;sub edi,2 - scasb skips one, skip '\0' and goto last
;
;mov ecx,ebx - ecx is ebx (length of string)
;shr ecx,1 - divide by 2 (swap chars to half of str)
;cld - clear direction flag
;
;rvs(@@): - reverse edi as it's stored backwards
;mov al,[esi] - byte in esi (1-first in str) to al
;xchg al,[edi] - swap al and edi
;mov [esi],al  - al to byte in esi
;inc esi - increase pointer <
;dec edi - decrease pointer >
;loop rvs(@B) - until ecx is 0
;====================================================

;----------------------------------------------------
;TODO------------------------------------------------
;----------------------------------------------------
;valid input check
;----------------------------------------------------    
Post 20 Jul 2010, 23:30
View user's profile Send private message Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
LocoDelAssembly 21 Jul 2010, 00:01
Something I forgot to tell earlier, if you plan to go the rb way, then make sure that after using rb nothing other than more rb, db ?, and all other things that declares uninitialized space comes after in the container section, not taking care of this will result in a bigger executable.For instance:
Code:
section '.data' readable writable
buff rb 4096
message db "Hello World", 0    
Will assure an executable at least 4 KB bigger than:
Code:
section '.data' readable writable
message db "Hello World", 0
buff rb 4096    
Post 21 Jul 2010, 00:01
View user's profile Send private message Reply with quote
jumpex



Joined: 29 Jan 2006
Posts: 38
jumpex 21 Jul 2010, 00:08
Tested - true.
Extremely valuable.
You've been very helpful, thank you.
Post 21 Jul 2010, 00:08
View user's profile Send private message 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.