Re: Question about yellow brick roads




Frank Kotler wrote:
Dragontamer wrote:
Markus Pitha wrote:
[snip]
It's not my target to see what's happened when I type in a number and see
the result on stdout. That's what computer newbies are probably interested
in but I want to understand what the little gremlins in the background are
doing while this happens.


I just fail to see what HLA does that prevents this.

True. (once you've got the file open :) So why don't the HLA fans
provide an example that *shows* how the little gremlins work, instead of
telling him "just call stdout.put"?

Well, I can do a "walk-through" of a "stdout.putu8" call to see what
Toto is barking at. But, remember: "Pay no attention to the man
behind the curtain."


stdout/putu8.hla
procedure putu8( b:byte );

This executes some CPU gremlins and calls stdout.putu32Size

stdout/putu32size.hla
procedure putu32Size( d:dword; width:int32; fill:char );

This executes some CPU gremlins and calls stdout.puti64Size

stdout/puti64size.hla
procedure puti64Size( q:qword; width:int32; fill:char );

This executes some CPU gremlins and calls fileio.puti64Size

fileio/fputi64size.hla
procedure fputi64Size( Handle:dword; q:qword; width:int32; fill:char );

This executes some more CPU gremlins and makes two calls --
conv.i64ToStr and fputs

conv/i64tostr.hla
procedure conv.i64ToStr( q:qword; width:int32; fill:char; buffer:string
);

This executes some CPU gremlins and calls conv.i64toBuf

conv/i64tobuf.hla
procedure conv.i64ToBuf( q:qword );

This executes some CPU gremlins and calls conv.u64toBuf

conv/u64tobuf.hla
procedure conv.u64ToBuf( q:qword );

This executes some CPU gremlins and sometimes calls the
internally-provided recursive routines recU64ToBuf and DivideBy10

Now, back to fputi64size.hla to the second call...

fileio/fputs.hla
procedure fputs( Handle:dword; s:string );

....where it finally makes a call to linux.write

linux/write.hla
procedure linux.write( fd:dword; var buf:var; count:linux.size_t );

Now, we finally find an "Int $80" instruction.

Phwew! <wipes forehead> Anyone else feeling exhausted yet? Sure
makes 'rocket science' or 'brain surgery' look like child's play! ;-)

If you don't have a 'good novel' to enjoy for the next few days, here
is the source code for the above:

unit StdOutput;



#include( "stdoutunit.hhf" );


/********************************************************/
/* */
/* putu64, */
/* putu32, */
/* putu16, */
/* putu8 */
/* */
/* "Wrapper" versions of the above routines that call */
/* the associate procedure with some default parameters */
/* (field width zero means print the number using the */
/* minimum field width, the fill character will be */
/* ignored). */
/* */
/********************************************************/


procedure putu8( b:byte ); @nodisplay; @noalignstack;
begin putu8;

push( eax );
movzx( b, eax );
push( eax );
pushd( 0 );
pushd( ' ' );
call( putu32Size );
pop( eax );

end putu8;



end StdOutput;

unit StdOutput;



#include( "stdoutunit.hhf" );


/***********************************************/
/* */
/* putu32size- */
/* */
/* Outputs a 32-bit unsigned integer to the */
/* standard output device. Lets the caller */
/* specify a minimum field width and a padding */
/* character (usually a space). */
/* */
/***********************************************/

procedure putu32Size( d:dword; width:int32; fill:char );
@nodisplay;
@noalignstack;
begin putu32Size;

pushd( 0 ); // Zero extend 32-bit value to 64-bits.
pushd( d );
push( width );
push( (type dword fill ));
call( puti64Size );

end putu32Size;


end StdOutput;

unit StdOutput;



#include( "stdoutunit.hhf" );


/*****************************************************/
/* */
/* puti64size- */
/* */
/* Outputs a 64-bit signed integer to the */
/* standard output device. Lets the caller */
/* specify a minimum field width and a padding */
/* character (usually a space). */
/* */
/* The width parameter specifies the minimum field */
/* width for the output. If the number requires */
/* more print positions than the specified value, */
/* puti64size will use however many are necessary. */
/* */
/* If the specified width is larger than the number */
/* of print positions, then puti64size will "pad" */
/* the output with the "fill" character (also passed */
/* as a parameter). If the width value is positive, */
/* then the numeric output will be right justified */
/* in the print field; if the width value is */
/* negative, then the number will be left justified */
/* in the print field. */
/* */
/*****************************************************/


procedure puti64Size( q:qword; width:int32; fill:char ); @nodisplay;
begin puti64Size;

ChkStdOut;
fileio.puti64Size( StdOutHandle, q, width, fill );

end puti64Size;




end StdOutput;

unit FileIOUnit;


#include( "fileiounit.hhf" )


procedure fputi64Size( Handle:dword; q:qword; width:int32; fill:char );

@nodisplay;
@noalignstack;

var
eaxSave: dword;

begin fputi64Size;

mov( eax, eaxSave ); // Save this in a known location!

/*
** Allocate storage for a string large enough
** to hold the output result.
**
** A 64-bit number requires about 20 digits, so
** allocate a minimum of 24 bytes for the string
** (we need to add one byte for the zero terminating
** byte and we need to have a multiple of four bytes).
** Also don't forget that we need to allocate an
** additional eight bytes for string data.
*/

mov( width, eax ); // Get size we need to allocate.
if( (type int32 eax) < 0 ) then // Must take absolute value because
// field widths can be negative!
neg( eax );

endif;

/*
** Just to be on the safe side, let's limit the field
** width to 1024 characters. If the user really needs
** more than this, they can print the extra characters
** themselves.
*/

if( eax > 1024 ) then

raise( ex.WidthTooBig );

endif;
add( 3, eax ); // Round size so it is an even multiple
and( $ffff_fffc, eax ); // of four bytes long.
if( eax < 32 ) then // Set size to a minimum of 32 bytes.

mov( 40, eax );

endif;
sub( eax, esp ); // Make room for the string.

/*
** HLA strings have a couple of values immediately before
** the string data. MaxStrLen is one of these values (which
** is currently in EAX). The following code allocates storage
** for these extra bytes and then stores EAX into the
** MaxStrLen field of this newly created record.
*/

sub( str.BytesBeforeStr+4, esp ); // Allocate extra bytes.

// Store the MaxStrLen field away.

mov( eax, [esp] );

/*
** Compute the base address of the string for later use.
** (The base address points at the character buffer.)
*/

lea( eax, [esp+8] );

pushd( (type dword q[4] ));
pushd( (type dword q ));
pushd( width );
pushd( (type dword fill ));
pushd( eax ); // Address of string to hold result.
call( conv.i64ToStr );

fputs( Handle, eax ); // Print the string.

mov( eaxSave, eax ); // Restore original eax value.

end fputi64Size;



end FileIOUnit;

unit ConvUnit;

#include( "conversions.hhf" );




/***********************************************************/
/* */
/* i64ToStr- */
/* */
/* This procedure converts a signed 64-bit (qword) */
/* value into a string of digits (decimal notation). */
/* */
/* "q" is the value to convert. */
/* */
/* "width" is the minimum number of character positions */
/* to use during the conversion. If the number requires */
/* more than this number of print positions, width's */
/* value is ignored, if the number requires fewer print */
/* positions, then the number is padded with the character */
/* specified by the "fill" parameter. If width is a */
/* positive value, then the number is right justified in */
/* the string. If width is a negative value, then the */
/* number is left justified in the string. */
/* */
/* "fill" contains the padding character to use if the */
/* number requires fewer print positions than specified */
/* by the "width" parameter. */
/* */
/* "buffer" is a pointer to the string that will hold */
/* the converted result. This buffer must be large */
/* enough to hold the converted data including any */
/* padding characters. */
/* */
/***********************************************************/


procedure conv.i64ToStr( q:qword; width:int32; fill:char; buffer:string
);
@nodisplay;
@noalignstack;
var
chars : byte[ 32 ]; // Holds converted result w/o padding.

begin i64ToStr;

pushad();
pushfd();
cld();

lea( edi, chars ); // Store converted digits here.
conv.i64ToBuf( q );

/*
** Compute the length of the string we've just generated.
*/

lea( edx, chars );
neg( edx );
add( edi, edx );


/*
** Determine if the result will fit into the destination buffer.
*/

mov( width, ecx );
if( (type int32 ecx) < 0 ) then // Negative implies left
justification.

neg( ecx );

endif;
mov( edx, eax );
if( edx < ecx ) then

mov( ecx, eax );

endif;

mov( buffer, edi ); // Get ptr to destination string.

/*
** If the destination string is too small, raise an exception.
*/

if( eax >= (type dword [edi+str.MaxStrLenOfs] )) then

raise( ex.StringOverflow );

endif;
mov( eax, [edi+str.lengthOfs] ); // Save new string length.

/*
** If the width value is positive, then the number must be
** right justified in the field width. Output any necessary
** leading padding characters here.
*/

if( width >= 0 ) then

if( edx < ecx ) then

mov( fill, al ); // Get the padding character.
push( ecx );
sub( edx, ecx ); // Computes # of padding chars to store.
rep.stosb();
pop( ecx );

endif;

endif;

/*
** Okay, now output the characters that make up the number.
*/


xchg( edx, ecx );
push( ecx );
lea( esi, chars ); // Pointer to our string.
rep.movsb(); // Copy the chars.
pop( ecx );

/*
** If the width value is negative, then the converted number
** must be left justified in the field width. Output any necessary
** trailing padding characters here.
*/

if( width < 0 ) then

xchg( ecx, edx ); // These were swapped, earlier.
if( edx < ecx ) then

mov( fill, al ); // Get the padding character.
push( ecx );
sub( edx, ecx ); // Computes # of padding chars to store.
rep.stosb();
pop( ecx );

endif;

endif;

/*
** Output the trailing zero terminating byte.
*/

mov( 0, al );
stosb();

popfd();
popad();

end i64ToStr;




end ConvUnit;

unit ConvUnit;

#include( "conversions.hhf" );

/***********************************************************/
/* */
/* i8ToBuf, u8ToBuf, */
/* i16ToBuf, u16ToBuf, */
/* i32ToBuf, u32ToBuf, */
/* i64ToBuf- */
/* */
/* These routines convert integers of their respective */
/* sizes to a sequence of characters. The 8, 16, and */
/* 32-bit integers are passed in al, ax, or eax */
/* (respectively). 64-bit values are passed on the stack. */
/* These routines store the resulting character sequence */
/* into the buffer pointed at by edi. These routines */
/* leave edi pointing at the first byte beyond the */
/* converted character sequence. */
/* */
/* These routines preserve eax. */
/* */
/***********************************************************/


procedure conv.i64ToBuf( q:qword ); @nodisplay; @noalignstack;
begin i64ToBuf;

push( eax );
push( edx );

mov( (type dword q), eax );
mov( (type dword q[4]), edx );
if( (type int32 edx) < 0 ) then

mov( '-', (type char [edi]));
inc( edi );
neg( edx );
neg( eax );
sbb( 0, edx );
mov( eax, (type dword q ));
mov( edx, (type dword q[4]));

endif;
pop( edx );
pop( eax );

conv.u64ToBuf( q );

end i64ToBuf;

end ConvUnit;

unit ConvUnit;

#include( "conversions.hhf" );

/***********************************************************/
/* */
/* i8ToBuf, u8ToBuf, */
/* i16ToBuf, u16ToBuf, */
/* i32ToBuf, u32ToBuf, */
/* i64ToBuf- */
/* */
/* These routines convert integers of their respective */
/* sizes to a sequence of characters. The 8, 16, and */
/* 32-bit integers are passed in al, ax, or eax */
/* (respectively). 64-bit values are passed on the stack. */
/* These routines store the resulting character sequence */
/* into the buffer pointed at by edi. These routines */
/* leave edi pointing at the first byte beyond the */
/* converted character sequence. */
/* */
/* These routines preserve eax. */
/* */
/***********************************************************/


procedure conv.u64ToBuf( q:qword ); @nodisplay; @noalignstack;

// DivideBy10-
//
// Divides edx:eax by 10.
// Returns quotient in edx:eax.
// Returns remainder in ecx.
// Returns 10 in ebx.

procedure DivideBy10; @nodisplay; @noframe;
begin DivideBy10;

push( eax );
mov( edx, eax );
xor( edx, edx );
mov( 10, ebx );
div( ebx, edx:eax );
mov( eax, ecx );
pop( eax );
div( ebx, edx:eax );
xchg( ecx, edx );
ret();

end DivideBy10;

procedure recU64ToBuf; @nodisplay; @noframe;
begin recU64ToBuf;

if
(#{
test( edx, edx );
jnz true;
test( eax, eax );
jz false;
}#) then

DivideBy10();
push( ecx );
recU64ToBuf();
pop( ecx );
or( '0', cl );
mov( cl, [edi] );
inc( edi );

endif;
ret();

end recU64ToBuf;

begin u64ToBuf;

push( eax );
push( ebx );
push( ecx );
push( edx );

mov( (type dword q), eax );
mov( (type dword q[4]), edx );
if
(#{
test( eax, eax );
jnz true;
test( edx, edx );
jz false;
}#) then

recU64ToBuf();

else

mov( '0', (type char [edi]));
inc( edi );

endif;
pop( edx );
pop( ecx );
pop( ebx );
pop( eax );

end u64ToBuf;


end ConvUnit;

unit FileIOUnit;


#include( "fileiounit.hhf" )

/*******************************************************/
/* */
/* fputs- */
/* */
/* This routine writes a string to the file */
/* specified by the file handle passed as a parameter. */
/* */
/*******************************************************/

#if( os.linux )

procedure fputs( Handle:dword; s:string ); @nodisplay;
var
bytesWritten:dword;
begin fputs;

push( eax );
push( esi );

/*
** Write the characters in the string to the specified file.
*/

mov( s, esi );
linux.write( Handle, [esi], (type linux.size_t (type str.strRec
[esi]).length) );
if( (type int32 eax) < 0 ) then // An error has occurred

if( eax = errno.enospc ) then

raise( ex.DiskFullError );

else

raise( ex.FileWriteError );

endif;

elseif( eax <> (type str.strRec [esi]).length ) then

raise( ex.FileWriteError );

endif;

pop( esi );
pop( eax );

end fputs;

#elseif( os.win32 )


procedure fputs( Handle:dword; s:string ); @nodisplay; @noalignstack;
var
bytesWritten:dword;
begin fputs;

pushad();
pushfd();
cld();

/*
** Write the characters in the string to the standard output device.
*/

mov( s, esi ); // Get the address of the string.
WriteFile
(
0,
bytesWritten,
(type str.strRec [esi]).length, // Push the length of the string.
(type byte [esi]), // Push the address of the string.
Handle
);
if( eax = 0 ) then

raise( ex.FileWriteError );

endif;
popfd();
popad();

end fputs;

#endif

end FileIOUnit;

unit LinuxUnit;
#include( "linux.hhf" )

// write - writes data via a file handle.

procedure linux.write( fd:dword; var buf:var; count:linux.size_t );
@nodisplay;
begin write;

linux.pushregs;
mov( linux.sys_write, eax );
mov( fd, ebx );
mov( buf, ecx );
mov( count, edx );
int( $80 );
linux.popregs;

end write;

end LinuxUnit;

Nathan.

.



Relevant Pages