News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Newbie problem, HLA: returning from Procedure crashes

Started by nbryant, June 12, 2005, 07:23:58 AM

Previous topic - Next topic

nbryant

My first attempt at code in HLA, and I've stymied myself, already =]

The following snippet (which is currently the entire code) should just take an error code (ie, 5) and print the length of the error message. Yes, I know that's largely useless, but I was originally outputting the error message itself; and as soon as I hit a longish message (about 32 characters) it crashes (generally with an access fault, and it often looks like it is trying to read the memory at [eax] although that could be a coincidence, so I cut it down to the minimum code to try and troubleshoot it. A couple of wierd things (at least, from my position):

a) it doesn't error on the api call (although it only errors if the api call is used) - it appears to error as it returns from the procedure.
b) if I take the procedure, and put it inline, (so there is no procedure to call) I still get the error, but at around 100 characters or so. I'm not at the moment aware at which point it crashes, in that case (since there is no return.)
c) it doesn't *always* crash. Sometimes it just hangs. But it's consistent. If you enter 5 or 6, it works. If you enter 7 (44 chars long) it crashes. If you enter 8 (58 chars long) it hangs. It seems very consistent for other values, based on message length. It is also consistent across different machines.

I'm sure I've overlooked something simple and newbyish, but for the life of me, I can't seem to find it, and I'm stuck for further troubleshooting. If anybody could provide me a clue, a pointer, or even just constructive criticism, it would be appreciated.

On both machines I'm using, I downloaded HLA and Radasm just this week, so it's all current, AFAIK. I'm sure the error has to do with the returned length of the buffer, but I'm reserving more space than it needs, and 32 shouldn't be a big enough number to overflow or anything . . .

program FormatError;

#include( "conv.hhf" );
#include( "strings.hhf" );
#include( "memory.hhf" );
#include( "w.hhf" );
#include( "stdout.hhf" );
#include( "stdin.hhf" );

Procedure FormatSysError( ErrNum: dword ); @returns( "eax" );
var
fmFlags: dword;
fmSource: string;
fmMessageId: dword;
fmLanguageId: dword;
fmBuffer: string;
fmSize: uns32;
fmArguments: string;

begin FormatSysError;
stdout.puti32( ErrNum ); // print the error number
mov( ErrNum, fmMessageId ); // and stick it in a variable
mov( w.FORMAT_MESSAGE_FROM_SYSTEM, fmFlags );
mov( 0, fmLanguageId ); // language is automatic
mov( w.MAX_PATH, fmSize ); // MAX_PATH = 260

mov( stralloc( w.MAX_PATH ), fmSource ); // 260 character string buffers
mov( stralloc( w.MAX_PATH ), fmBuffer );
mov( stralloc( w.MAX_PATH ), fmArguments );

w.FormatMessage(fmFlags,fmSource,fmMessageId,fmLanguageId,fmBuffer,fmSize,fmArguments);
stdout.put( " size: " ); // eax returns the size of the string in fmBuffer
stdout.puti32( eax );
stdout.put( nl );

// str.free( fmArguments );
// str.free( fmBuffer );
// str.free( fmSource );
end FormatSysError;


begin FormatError;
forever
stdout.put( "Enter an error number: " );
stdin.geti32();
FormatSysError( eax );
stdout.put( " returned." );
endfor;
end FormatError;


Another question, btw; If I use the three str.free commands (that are commented out) it crashes with them. I can uncomment *one* of them (and it might be only one specific one), but shouldn't I be freeing all of the strings?

aTdHvAaNnKcSe

~Neil

AeroASM

1. You only need to have fmBuffer. Get rid of fmSource and fmArguments, and pass 0 instead.

2. Why do you have variables for all of the arguments passed to FormatMessage? Why not place some of the constants directly in the call? For example:


mov(stralloc(w.MAX_PATH),fmBuffer)

w.FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,0,ErrNum,0,fmBuffer,MAX_PATH,0


3. Why do you use MAX_PATH?

4. Why does eax not get changed by stdout.put?

nbryant

First of all, Thank you, AeroASM, for taking the time to help me out. It is appreciated.

Quote from: AeroASM on June 12, 2005, 07:48:41 AM
1. You only need to have fmBuffer. Get rid of fmSource and fmArguments, and pass 0 instead.

2. Why do you have variables for all of the arguments passed to FormatMessage? Why not place some of the constants directly in the call? For example:


mov(stralloc(w.MAX_PATH),fmBuffer)

w.FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,0,ErrNum,0,fmBuffer,MAX_PATH,0


Yes. I actually started there, and put everything in variables as a way of troubleshooting; I wanted to make sure I wasn't doing anything lame with value-versus-reference or anything like that.

Case and point; fmSize is actually an in/out value. Passing it MAX_PATH as a constant could (as in, I'm not sure) lead to unpredictable results.

To follow up with the postscript to my question, though; if I am allocating three variables, shouldn't I be freeing all of them, also? If I attempt to free fmArguments, it works as normal, but if I try to free either of the other two, it crashes at that point. (but only in the presence of the api call. If I comment that out, I can free all three.)

Quote from: AeroASM on June 12, 2005, 07:48:41 AM
3. Why do you use MAX_PATH?

MAX_PATH is sort of a personal convention; the actual value doesn't matter - but it's large enough for this, I am familiar with it, and it existed in the HLA libraries. If it's all working, I would actually do a check on the return of fmSize to see if I need to allocate a larger buffer.

Quote from: AeroASM on June 12, 2005, 07:48:41 AM
4. Why does eax not get changed by stdout.put?

Should it? It hasn't appeared to, at any point (although that could, of course, be coincidence). If so, it would lead to another error, but not this one (I don't think), as I'm not actually doing anything with EAX. And, if I remove the @returns( "eax" ), nothing changes.

BTW, I did try pushing and popping ebx, ecx, edx, esi, edi directly around the api call . . . it seemed to have no effect.

I know I'm doing something dumb; not defining something properly, etc, but I'm at a loss how to figure out what.

Again, thank you kindly for your assistance. ~Neil

AeroASM

To be honest, I don't know HLA at all, so I cannot figure out what is really going on. Am I right in think that HLA compiles your code into a MASM compatible source? If so, then please could you post it.

On a side note, I seriously suggest you give up HLA and learn MASM instead. HLA is no more like assembler than C. However, if you are required to learn HLA for some course, then learn MASM as well.

Sevag.K

Quote from: nbryant on June 12, 2005, 07:23:58 AM
My first attempt at code in HLA, and I've stymied myself, already =]


Hi.

The problem is that you are sending HLA strings to be manipulated by a Windows API.  This won't work because HLA strings are different from the standard C-string (zero=terminated only).

What you need to do is use a charactger buffer instead of HLA strings (which can only be manipulated by HLA standard library functions).

Here is a working version:


program FormatError;

#include( "conv.hhf" );
#include( "strings.hhf" );
#include( "memory.hhf" );
#include( "w.hhf" );
#include( "stdout.hhf" );
#include( "stdin.hhf" );

Procedure FormatSysError( ErrNum: dword ); @returns( "eax" );
var
buffer :char[260];
endvar;

begin FormatSysError;


w.FormatMessage(w.FORMAT_MESSAGE_FROM_SYSTEM, NULL, ErrNum, NULL, buffer, 259, NULL);
lea (ecx, buffer);


dec (ecx);
find_length:
inc (ecx);
mov ([ecx], al);
cmp (al, 0);
jne find_length;
lea (eax, buffer);
sub (eax, ecx);

// ecx contains string length:
stdout.write ([eax], ecx);
stdout.newln();
stdout.put ("Length : ", (type uns32 ecx),nl);

end FormatSysError;


begin FormatError;
forever
stdout.put( "Enter an error number: " );
stdin.geti32();
FormatSysError( eax );
stdout.put(nl, " returned.",nl );
endfor;
end FormatError;


- str.free crashes it because the strings no longer contain valid HLA strings after Windows mangles it

You might want to post this kind of stuff in the HLA forum next time :)

nbryant

Quote from: AeroASM on June 12, 2005, 07:23:20 PM
To be honest, I don't know HLA at all, so I cannot figure out what is really going on. Am I right in think that HLA compiles your code into a MASM compatible source? If so, then please could you post it.

Sure. Apologies for the length; I don't yet know a lot about what to cut out. At the same time, if I should be asking this elsewhere, I apologize; I chose Newbies over the HLA forum, because I figured it was a newbie error.

On second thought, it's long enough that I'm attaching it . . .

Quote from: AeroASM on June 12, 2005, 07:23:20 PM
On a side note, I seriously suggest you give up HLA and learn MASM instead. HLA is no more like assembler than C. However, if you are required to learn HLA for some course, then learn MASM as well.

HLA looks like a good way for me to leverage what I already know, and pick up MASM on the fly (I intend to learn both). Unfortunately, I'm not in school, and anything I use at work I have to be able to hit the ground running. Since I'm learning it on my own initiative, I have minimal time to give it. HLA is a way for me to level the learning curve. Without that, I'd probably have to stick with VB, or move to Delphi, etc.

[attachment deleted by admin]

AeroASM

I think Sevag.K has got it spot on; I thought it might be something HLA-specific that I didn't know.

BTW you are not supposed to pass a string to Arguments of FormatMessage, you are supposed to pass a DWORD array.

Also all of the main forums are assumed to be for MASM.

nbryant

Quote from: Sevag.K on June 12, 2005, 07:56:45 PM
The problem is that you are sending HLA strings to be manipulated by a Windows API.  This won't work because HLA strings are different from the standard C-string (zero=terminated only).

Yoinks. That's where I got messed up . . . I was understadning it that if you mucked about with them as zero-term strings, it just messed up the length parameter:
Quote from: HLADocumentation
The pointer points at the first byte of the character string. Successive memory locations contain successive characters in the string up to the zero terminating byte. This format is compatible with zero-terminated strings like those that C/C++ uses.

Quote from: Sevag.K on June 12, 2005, 07:56:45 PM
What you need to do is use a charactger buffer instead of HLA strings (which can only be manipulated by HLA standard library functions).

Here is a working version:

Thank you! you guys rock! (Now off to study it.)

Quote from: Sevag.K on June 12, 2005, 07:56:45 PM
- str.free crashes it because the strings no longer contain valid HLA strings after Windows mangles it

This, then, I will need to dig into further. One of the examples (which I can't locate, now) seemed to allocate a string, use it as a zero-term, then fix the length . . . or something like that. I probably just mixed it up in my mind. But, if the string points to the beginning of a character array, and the string length is known, shouldn't that safely deallocate, anyway? Or am I (obviously) missing something?

Quote from: Sevag.K on June 12, 2005, 07:56:45 PM
You might want to post this kind of stuff in the HLA forum next time :)

I shall do so, and thanks, again =]

Sevag.K

An HLA string pointer begins at the start of the character array and zero terminates, so it's safe to pass it on any routine that reads C-strings, but if that routine changes any characters around, you'll have to manually get the string length and store it at an offset -4 from the string pointer.

Here is some pseudo code for doing it:



// get the string pointer into a register
mov (myStrPtr, ecx);

// find the length
dec (ecx);
find_length:
inc (ecx);
mov ([ecx], al);
cmp (al, 0);
jne find_length;

mov (myStrPtr, eax);
sub (eax, ecx);

// now we have length in ecx and string pointer in eax so...

mov (ecx, (type str.strRec[ecx]).length );



This should fix problems.

There are other algorithms for finding string length.  Play around and see what you get.


nbryant

OK, thats what I understood to be the case . . .

So, here's where I break down . . . if there isn't a simple answer, I will work it out over time:
If an HLA string is a pointer to the first character in a zero-terminated character array (with string-4 and string-8 being the length parameters) and I write 'stuff' with a terminating zero into that string, without changing the lengths...

Why would that crash? Wouldn't HLA still see the string as MAX_PATH long, and clean it up? Does the inclusion of the zero do something to mess it up? I mean, if I could expect it to work, had I fixed the string length, but after I made the api call, I never actually did anything else with the string . . .

As I said, I will work this out over time, so if there isn't a simple answer, don't worry about it =]

Thanks, again, for all the assistance.

~neil

Sevag.K

Quote from: nbryant on June 12, 2005, 10:21:29 PM
OK, thats what I understood to be the case . . .

So, here's where I break down . . . if there isn't a simple answer, I will work it out over time:
If an HLA string is a pointer to the first character in a zero-terminated character array (with string-4 and string-8 being the length parameters) and I write 'stuff' with a terminating zero into that string, without changing the lengths...

Why would that crash? Wouldn't HLA still see the string as MAX_PATH long, and clean it up? Does the inclusion of the zero do something to mess it up? I mean, if I could expect it to work, had I fixed the string length, but after I made the api call, I never actually did anything else with the string . . .

As I said, I will work this out over time, so if there isn't a simple answer, don't worry about it =]

Thanks, again, for all the assistance.

~neil

The string data contains the maximum lenght and current length.  If you make changes that don't alter the current length, whatever that may be, then there won't be any problems.

When you define a string as MAX_PATH, then the string is created with storage for 260 characters + NULL, but the current length of a newly created string is 0.


nbryant

I'm just going to leave it as well-explained, but I'm sort of dense <g> I understand what you're saying, and I'm sure when I know more the remaining details will be clear.

Thanks, again, for your assistance.

BTW, for the sake of passing it on to anybody who may read this in the future, this is how I boiled down the code (this is an entire, stand-alone app, but without any real error-handling (ie, errors in the 40s just say there was an error)).

FORMAT_MESSAGE_ALLOCATE_BUFFER tells the api to allocate the buffer, and accepts the buffer length (formerly fmSize) as a minimum, which we set to 0. Since the function returns either 0 or the new buffer-length, we don't have to scan for it.


program FormatError;

#include( "w.hhf" );
#include( "stdout.hhf" );
#include( "stdin.hhf" );

Procedure FormatSysError( ErrNum: dword );
var
fmBuffer : DWORD;
endvar;

begin FormatSysError;
w.FormatMessage(w.FORMAT_MESSAGE_FROM_SYSTEM | w.FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, ErrNum, NULL, fmBuffer, 0, NULL);
if ( eax = 0 ) then
stdout.put( "FormatError: had an error.", nl, nl );
else
mov( fmBuffer, ecx );
stdout.write ( [ecx], eax );
stdout.newln();

w.LocalFree( ecx );
if ( eax <> 0 ) then
stdout.put( "error freeing buffer.", nl );
endif;

endif;
end FormatSysError;


begin FormatError;
forever
stdout.put( "Enter an error number: " );
stdin.geti32();
FormatSysError( eax );
endfor;
end FormatError;


Hope this helps somebody else.
~Neil