Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Assembler equivalent of sprintf

889 views
Skip to first unread message

spam...@crayne.org

unread,
Feb 14, 2009, 6:39:44 PM2/14/09
to
I'm having some problems replicating sprintf in assembler.

When I try the below, I end up loading the buffer with: "\x03". I
want to load the buffer with just "3". This is what I have tried:

In the data section, I defined wBuffer as:

wBuffer: times 512 db 0


rBuffer contains the first 0x999 bytes of a PE file. I defined
rBuffer as:

rBuffer: times 0x1000 db 0 ; should be a page 4096 bytes


My attempt to simulate sprintf is below. Note: ebx contains the PE
offset, which in the file
I am opening occurs at 0x60.

xor eax, eax
mov word ax, [rBuffer+ebx+0x3C] ; move the # of sections from the PE
header to ax
mov dword edi, wBuffer ; move destination buffer to edi
stosw ; store what is in ax
in the buffer pointed at by edi

Now, wBuffer has: "\x03" followed by the remaining zeros. The hex and
decimal for 3 are the same,
so how do I tell stosw that I want what is in ax to be treated as a
decimal in the string? Likewise, how do I
do the reverse?

I want the equivalent of: sprintf(wBuffer, "%d", eax). I just want
to know how to do this without calling
a C function.

Thanks.


Frank Kotler

unread,
Feb 16, 2009, 11:06:18 AM2/16/09
to
bwa...@yahoo.com wrote:
> I'm having some problems replicating sprintf in assembler.

Hi Brian,

If you think "%d" is tough, wait until you get to "%f"! :)

> When I try the below, I end up loading the buffer with: "\x03".

You mean just the number (value) 3, right? Literally "\x03" would be 4
characters, and I don't see how you'd get it out of stosw...

> I
> want to load the buffer with just "3". This is what I have tried:
>
> In the data section, I defined wBuffer as:
>
> wBuffer: times 512 db 0
>
>
> rBuffer contains the first 0x999 bytes of a PE file. I defined
> rBuffer as:
>
> rBuffer: times 0x1000 db 0 ; should be a page 4096 bytes
>
>
> My attempt to simulate sprintf is below. Note: ebx contains the PE
> offset, which in the file
> I am opening occurs at 0x60.
>
> xor eax, eax
> mov word ax, [rBuffer+ebx+0x3C] ; move the # of sections from the PE
> header to ax
> mov dword edi, wBuffer ; move destination buffer to edi
> stosw ; store what is in ax
> in the buffer pointed at by edi
>
> Now, wBuffer has: "\x03" followed by the remaining zeros. The hex and
> decimal for 3 are the same,
> so how do I tell stosw that I want what is in ax to be treated as a
> decimal in the string? Likewise, how do I
> do the reverse?
>
> I want the equivalent of: sprintf(wBuffer, "%d", eax). I just want
> to know how to do this without calling
> a C function.

You've got the number (value) 3 in eax (it all fits in al, actually),
and you want the character '3' in wBuffer, right? For just a single
digit, just add '0' (the character '0', not the number 0 - 48 decimal or
30h). Probably "number of sections" would "always" fit in a single
digit, but it's apparently a 16-bit number, so it could take up to 5
characters to represent as decimal. Multiple digits are harder...

If we've got the number 123, we want characters '1', '2', '3'. Dividing
123 by 10, we get 12 with a remainder of 3, dividing 12 by 10, we get 1
with a remainder of 2. Dividing 1 by 10, we get 0 (which means we're
done) with a remainder of 1. The remainders are the digits we want -
still have to add '0' to each to make characters - but in the wrong
order. There are a number of ways to "reverse" 'em - I think the
"easiest to understand"(?) is to push 'em on the stack as we "get" 'em,
and pop 'em off in the right order to "print" (or just "store" in this
case). One last "gotcha"... the "div" instruction (in 32 bits) divides
edx:eax by something (edx * 4G + eax, that is - not to be confused with
segment:offset notation). If edx:eax/??? won't fit in 32 bits, it causes
a CPU exception... which may be reported as "divide by zero" or
"floating point exception"... highly confusing! We want edx to be zero
for the "div" - every time ("div" puts the remainder in edx, so it keeps
changing!)...

I usually call this "itoa", but it really should be "utoa", since it
assumes numbers are always positive (which "number of sections" should be).

; eax holds the number
; edi holds "where to put characters"
utoa:
mov ebx, 10 ; we'll divide by this
xor ecx, ecx ; use as digit-counter
pushloop:
xor edx, edx ; edx 0 - every time!
div ebx ; quotient in eax, remainder in edx
push edx ; store it for later
inc ecx ; count it
test eax, eax ; or cmp eax, 0
jnz pushloop
poploop:
pop eax ; get digit back
add al, '0' ; convert to character
stosb ; store it in destination
loop poploop ; cook until done
ret

That's untested, but maybe I've done it enough times to get it right. :)
You probably want to "improve" that to save the registers we're using. C
requires ebx, ebp, esi, edi to be preserved, but allows functions to
trash ecx and edx (return value in eax or edx:eax). We probably want to
save ecx and edx, since we're using them, but probably want to leave edi
pointing to the next spot in "destination" (or not, as you wish). This
doesn't return anything useful - might want to improve it to return
"number of characters stored" or something...

I should point out that "div" is a real slow instruction. It can be done
faster - even repeated subtraction is faster - multiplying by the
reciprocal of 10 ("Terje's method") is even faster... but a *lot* harder
to understand... Unless you've got a lot of numbers to convert, this
shouldn't be "too" slow...

To do the opposite ("atoi" or "atou"), fetch a character from the
string, verify that it's a valid decimal digit ("character representing
a decimal digit" I should say), convert the character to a number,
multiply the "result so far" by 10, and add the new digit. (until you
run out of string)

This expects the address of the string to be on the stack... You could
just pass it in edx... or some other register...

atoi:
mov edx, [esp + 4] ; pointer to string
xor eax, eax ; clear "result"
..top:
movzx ecx, byte [edx]
inc edx
cmp ecx, byte '0'
jb .done
cmp ecx, byte '9'
ja .done

; we have a valid character - multiply
; result-so-far by 10, subtract '0'
; from the character to convert it to
; a number, and add it to result.

lea eax, [eax + eax * 4]
lea eax, [eax * 2 + ecx - 48]

jmp short .top
..done
ret

(the "lea" method to multiply by 10, convert character to number and add
it in was stolen from the output of a C compiler - I ain't too proud to
learn from a C compiler - those guys are clever!) This just quits on
encountering an invalid character - including the terminating zero - and
doesn't guard against overflow. Could perhaps be improved in that
regard, but it's "what C does"...

That'll give you something to fool with anyway... holler if it isn't
what you've got in mind.

Best,
Frank

spam...@crayne.org

unread,
Feb 17, 2009, 11:31:28 AM2/17/09
to
On Feb 16, 9:06 am, Frank Kotler <spamt...@crayne.org> wrote:

>
> You've got the number (value) 3 in eax (it all fits in al, actually),
> and you want the character '3' in wBuffer, right? For just a single
> digit, just add '0' (the character '0', not the number 0 - 48 decimal or
> 30h). Probably "number of sections" would "always" fit in a single
> digit, but it's apparently a 16-bit number, so it could take up to 5
> characters to represent as decimal. Multiple digits are harder...

You're correct. My debugger was just representing the hex value
contained
in the string using C syntax.

>
> If we've got the number 123, we want characters '1', '2', '3'. Dividing
> 123 by 10, we get 12 with a remainder of 3, dividing 12 by 10, we get 1
> with a remainder of 2. Dividing 1 by 10, we get 0 (which means we're
> done) with a remainder of 1. The remainders are the digits we want -
> still have to add '0' to each to make characters - but in the wrong
> order. There are a number of ways to "reverse" 'em - I think the
> "easiest to understand"(?) is to push 'em on the stack as we "get" 'em,
> and pop 'em off in the right order to "print" (or just "store" in this
> case). One last "gotcha"... the "div" instruction (in 32 bits) divides
> edx:eax by something (edx * 4G + eax, that is - not to be confused with
> segment:offset notation). If edx:eax/??? won't fit in 32 bits, it causes
> a CPU exception... which may be reported as "divide by zero" or
> "floating point exception"... highly confusing! We want edx to be zero
> for the "div" - every time ("div" puts the remainder in edx, so it keeps
> changing!)...

Thanks. I had jumped ahead of myself, thinking that I did not have to
do the conversion, but after looking at the ASCII table again, I had
made a
mistake. I mis-read the string dump that I had from the PE header.
I thought that the string contained the number all ready converted,
while
the PE header represents the number in hex, so a conversion is
necessary.

So the steps are:

1) dump the PE header into a read buffer
2) extract the value that I want into eax (most are dwords)
-- should I be using lea instead of mov to index memory?
3) convert the value to it's character representation
4) store the converted value into my write buffer
5) in the case of my app, write the value to an edit box
6) repeat at the next offset

The app is just a simple PE editor, which is forcing me
to learn how use strings efficiently.

What is fascinating is that I struggled to find anything
about optimizing itoa. Is this an old art?

Thanks.

Tim Roberts

unread,
Feb 18, 2009, 1:42:41 AM2/18/09
to
"bwa...@yahoo.com" <spam...@crayne.org> wrote:
>
>What is fascinating is that I struggled to find anything
>about optimizing itoa. Is this an old art?

Well, yes. The problem of converting binary to ASCII was solved something
over 50 years ago. It is well understood. Further, it is extremely rare
that such a conversion is needed often enough to represent a performance
bottleneck.
--
Tim Roberts, ti...@probo.com
Providenza & Boekelheide, Inc.

Frank Kotler

unread,
Feb 18, 2009, 5:01:48 AM2/18/09
to
bwa...@yahoo.com wrote:

....


> So the steps are:
>
> 1) dump the PE header into a read buffer
> 2) extract the value that I want into eax (most are dwords)

Right. For a word value, you did:

xor eax, eax
mov ax, [...]

That's correct, but you could also use:

movzx eax, word [...]

and if there are any byte values to be read:

movzx eax, byte [...]

dwords can be done with just:

mov eax, [...]

(the register determines the size of the "mov", so you don't have to say
"dword")

> -- should I be using lea instead of mov to index memory?

You mean like "mov edi, wBuffer"? You could. "lea edi, [wBuffer]". It
seems like overkill to use lea here "tell me the address of the variable
whose address is buffer". Where you'd *need* lea is:

push ebp
mov ebp, esp
sub esp, 100h ; make room for a buffer on stack

%define buffer ebp - 100h

mov edi, buffer ; illegal syntax!!!
lea edi, [buffer] ; ok

There may be an advantage to lea, in that it executes in the Address
Generation Unit rather than the Integer Unit (???) and might be able to
"overlap" another instruction. I'm not very sure about that one...

Using:

lea eax, [eax + eax * 4]

lea eax, [eax * 2 + ecx - '0']

To do "multiply result-so-far by ten and add in the digit converted from
a character" is just a "trick"... (good one though, ain't it?)

> 3) convert the value to it's character representation
> 4) store the converted value into my write buffer
> 5) in the case of my app, write the value to an edit box
> 6) repeat at the next offset

Right. "Next" offset being +4 for a dword value, +2 for a word value and
+1 for a byte value (if any), of course.

> The app is just a simple PE editor, which is forcing me
> to learn how use strings efficiently.

Sounds like a good exercise... and potentially useful. You'll learn
about the PE header, too (which I obviously haven't...)

> What is fascinating is that I struggled to find anything
> about optimizing itoa. Is this an old art?

Yeah, I guess so. It's been a common newbie question since before I was
a newbie, "How do I print a number?". As Tim pointed out, it isn't often
"worth" optimizing. Randy Hyde started a thread on this newsgroup and
alt.lang.asm, titled "test this code, please" as I recall, comparing
different optimizations - and asking people to report results on
different processors. Turned out "multiply by reciprocal" was generally
fastest, but "repeated subtraction" was close, for certain numbers of
digits, on certain CPUs.

There's a simpler, slight, optimization. I showed you "keep dividing
until quotient is zero". We can save one "div" by comparing the quotient
to 10, and if less, use al for the digit instead of dl... I'm better at
optimizing for "small" than for "fast" - easier to keep score. :)

Best,
Frank

spam...@crayne.org

unread,
Feb 18, 2009, 2:43:57 PM2/18/09
to
On Feb 18, 3:01 am, Frank Kotler <spamt...@crayne.org> wrote:

>
> Right. For a word value, you did:
>
> xor eax, eax
> mov ax, [...]
>
> That's correct, but you could also use:
>
> movzx eax, word [...]
>
> and if there are any byte values to be read:
>
> movzx eax, byte [...]
>
> dwords can be done with just:
>
> mov eax, [...]
>
> (the register determines the size of the "mov", so you don't have to say
> "dword")

So for NASM syntax, I only need the size specification in the source?
For example:

mov ebx, dword [...]

or in your examples with movzx:

movzx ebx, word [...]

I would never need to write:

mov dword ebx, [...]

NASM determines the size based on the register in the destination?

> You mean like "mov edi, wBuffer"? You could. "lea edi, [wBuffer]". It
> seems like overkill to use lea here "tell me the address of the variable
> whose address is buffer". Where you'd *need* lea is:

I mean like:

mov edi, [wBuffer+ebx+offset]

where NASM assumes offset with [], right?

I'm confused a little by the use of [] between lea and mov in NASM.
The documentation leads me to believe that they are the same, which
makes
it confusing to determine when to lea over mov.

In MASM, these two would be equivalent, right?

mov eax, offset buffer
lea eax, buffer

whereas in NASM:

mov eax, [buffer]
lea eax, [buffer]

are the same?

>
> > 3) convert the value to it's character representation
> > 4) store the converted value into my write buffer
> > 5) in the case of my app, write the value to an edit box
> > 6) repeat at the next offset
>
> Right. "Next" offset being +4 for a dword value, +2 for a word value and
> +1 for a byte value (if any), of course.

In this case the next offset is the next offset relative to the
beginning
of the PE header. I'm basically just plucking values from the PE
header,
and I'm using the DOS header to determine where the PE header starts.

If I am using an array in NASM like:

offsets dd 0x60, 0x3C, 0x00

and I wanted to read the second offset value, I would just:

mov eax, [offsets+4]

right? So the size specification dd just tells me that each array
value is a dword apart, right? So if I had used db, then my offset
would be +1.

>
> Sounds like a good exercise... and potentially useful. You'll learn
> about the PE header, too (which I obviously haven't...)

A tool like this all ready exists in the form of LordPE. It's pretty
interesting to check out the tools used in reversing beyond just the
basic
disassembler.

> Yeah, I guess so. It's been a common newbie question since before I was
> a newbie, "How do I print a number?". As Tim pointed out, it isn't often
> "worth" optimizing. Randy Hyde started a thread on this newsgroup and
> alt.lang.asm, titled "test this code, please" as I recall, comparing
> different optimizations - and asking people to report results on
> different processors. Turned out "multiply by reciprocal" was generally
> fastest, but "repeated subtraction" was close, for certain numbers of
> digits, on certain CPUs.

In terms of my application, I just want to avoid using C function
calls
and write my own. This saves me from linking to one dll. I'd like to
learn how this stuff works. sprintf, for what I am doing, is really
overkill.

Thanks.

Frank Kotler

unread,
Feb 20, 2009, 12:22:56 AM2/20/09
to
bwa...@yahoo.com wrote:
> On Feb 18, 3:01 am, Frank Kotler <spamt...@crayne.org> wrote:
>
>> Right. For a word value, you did:
>>
>> xor eax, eax
>> mov ax, [...]
>>
>> That's correct, but you could also use:
>>
>> movzx eax, word [...]
>>
>> and if there are any byte values to be read:
>>
>> movzx eax, byte [...]
>>
>> dwords can be done with just:
>>
>> mov eax, [...]
>>
>> (the register determines the size of the "mov", so you don't have to say
>> "dword")
>
> So for NASM syntax, I only need the size specification in the source?
> For example:
>
> mov ebx, dword [...]

You don't *need* "dword" here - won't do any harm.

> or in your examples with movzx:
>
> movzx ebx, word [...]
>
> I would never need to write:
>
> mov dword ebx, [...]
>
> NASM determines the size based on the register in the destination?

Right. The reason you need to use the size with "movzx" is that "movzx"
actually represents two different instructions (true of many of the
mnemonics). If you used "movzx eax, dl" or "movzx eax, dx", Nasm would
figure that out from the register size.

Masm "remembers" that you said "buffer" was "db", say, and you could do,
for example, "inc buffer" (contents - "[]" optional)... but it's often
written as "inc byte ptr buffer" (for clarity, I guess). Nasm has
"amnesia" and needs to be told the size (but doesn't use the "ptr" part)
- "inc byte [buffer]" (contents).

>> You mean like "mov edi, wBuffer"? You could. "lea edi, [wBuffer]". It
>> seems like overkill to use lea here "tell me the address of the variable
>> whose address is buffer". Where you'd *need* lea is:
>
> I mean like:
>
> mov edi, [wBuffer+ebx+offset]
>
> where NASM assumes offset with [], right?

The "[]" tells Nasm that you want the contents of memory at that
address. You could say that Nasm assumes "offset" (the Masm keyword)
*without* the "[]". If you did "lea edi, [wBuffer+ ebx+offset]" (*not*
the Masm keyword!), you'd get the address. Same as:

mov edi, buffer ; "offset buffer" for Masm
add edi, ebx
add edi, offset

.... only in one instruction. If you then wanted the contents of that
address, "mov ax, [edi]" (or eax or al, depending on how much contents
you want).

> I'm confused a little by the use of [] between lea and mov in NASM.
> The documentation

The "instruction set reference" has been removed from the Nasm manual,
in recent versions (didn't want to maintain it, didn't want to add the
64-bit opcodes). You can find the old version here (among other places):

http://home.myfairpoint.net/fbkotler/nasmdocr.html

The description of "lea" says (in part):

-------------------
LEA, despite its syntax, does not access memory. It calculates the
effective address specified by its second operand as if it were going to
load or store data from it, but instead it stores the calculated address
into the register specified by its first operand. This can be used to
perform quite complex calculations (e.g. LEA EAX,[EBX+ECX*4+100]) in one
instruction.
--------------------
(there are other, potentially better, instruction set references - I'm
just used to this one...)

> leads me to believe that they are the same, which
> makes
> it confusing to determine when to lea over mov.

Essentially, the "complex calculation" part...

> In MASM, these two would be equivalent, right?
>
> mov eax, offset buffer
> lea eax, buffer

Right.

> whereas in NASM:
>
> mov eax, [buffer]
> lea eax, [buffer]
>
> are the same?

No. "mov eax, [buffer]" moves the contents of "buffer" into eax. In Nasm
syntax, just "mov eax, buffer" gets the address - like "mov eax offset
buffer" in Masm. Nasm doesn't use the "offset" keyword assumes "offset"
if it *isn't* in "[]". Masm doesn't use "[]" to indicate "contents"...
in this case. In other cases - "mov eax, [ebx]" - Masm *does* need the
"[]". This is one of the most confusing differences between Masm and
Nasm. In "Intel syntax", the "[]" doesn't mean "contents", but is
equivalent to "+". You could write "mov eax, 4[buffer]", and it would be
the same as "mov eax, [buffer + 4]". Nasm won't let you do the former -
all of the address has to be between the "[]"s. Masm uses Intel syntax,
Nasm uses "simplified Intel syntax", which is mostly the same, but not
in this case.

The distinction between an address, and the contents of that address, is
a *really* important one to understand, so it's an unfortunate place to
have this potential confusion creep in. Such is life.

>>> 3) convert the value to it's character representation
>>> 4) store the converted value into my write buffer
>>> 5) in the case of my app, write the value to an edit box
>>> 6) repeat at the next offset
>> Right. "Next" offset being +4 for a dword value, +2 for a word value and
>> +1 for a byte value (if any), of course.
>
> In this case the next offset is the next offset relative to the
> beginning
> of the PE header. I'm basically just plucking values from the PE
> header,
> and I'm using the DOS header to determine where the PE header starts.
>
> If I am using an array in NASM like:
>
> offsets dd 0x60, 0x3C, 0x00
>
> and I wanted to read the second offset value, I would just:
>
> mov eax, [offsets+4]
>
> right?

Right.

> So the size specification dd just tells me that each array
> value is a dword apart, right? So if I had used db, then my offset
> would be +1.

Right.

[PE header reader]


>> Sounds like a good exercise... and potentially useful. You'll learn
>> about the PE header, too (which I obviously haven't...)
>
> A tool like this all ready exists in the form of LordPE.

Yeah, but *you* didn't learn anything by writing it. :)

> It's pretty
> interesting to check out the tools used in reversing beyond just the
> basic
> disassembler.

Yes. A "basic disassembler" - like Ndisasm - just disassembles code...
well, it'll attempt to disassemble data, too - with totally meaningless
results. A lot of information is lost - it just isn't there in the code
stream. Much of that information can be recovered from the header, if
you know where to find it (including where the code starts). A more
sophisticated disassembler can do this. I'm not familiar with LordPE,
but Agner Fog's "objconv" gives a much more informative disassembly than
Ndisasm!

http://www.agner.org/optimize/#objconv

One "WARNING!!!" with it. The output filename ends in .asm, and will
overwrite your original source if you're not careful! I think it's a gem!

....


> In terms of my application, I just want to avoid using C function
> calls
> and write my own. This saves me from linking to one dll. I'd like to
> learn how this stuff works. sprintf, for what I am doing, is really
> overkill.

Agreed. For floating point numbers, it might be worth it...

In Linux - and I think this is true for Windows, too - sprintf (and all
the rest) is just sitting there in memory, waiting for someone to call
it. So no matter how "lean and mean" you make your routine, it's still
"duplicate code". But it's a lot more fun!

Best,
Frank

spam...@crayne.org

unread,
Feb 22, 2009, 7:17:46 PM2/22/09
to
On Feb 19, 10:22 pm, Frank Kotler <spamt...@crayne.org> wrote:

> The "[]" tells Nasm that you want the contents of memory at that
> address. You could say that Nasm assumes "offset" (the Masm keyword)
> *without* the "[]". If you did "lea edi, [wBuffer+ ebx+offset]" (*not*
> the Masm keyword!), you'd get the address. Same as:
>
> mov edi, buffer ; "offset buffer" for Masm
> add edi, ebx
> add edi, offset
>
> .... only in one instruction. If you then wanted the contents of that
> address, "mov ax, [edi]" (or eax or al, depending on how much contents
> you want).

Thanks. This example makes the use of LEA super clear. Due to the
limitations
of operators that mov can handle, I have to do memory calculations
step by step
with MOV, so LEA makes more sense.

I guess the only remaining part of LEA that is a little confusing is
if you use it after
a memory move. For example:

lea edi, [wBuffer+ebx+offset] ; address to edi
lea eax, [edi] ; dword content of edi, right?

so in this case, I'm better off just doing:

mov edi, [wBuffer+ebx+offset]

It's interesting how I can get to the same place, but the steps just
change. This
is one of the most fascinating aspects of assembly programming.

> No. "mov eax, [buffer]" moves the contents of "buffer" into eax. In Nasm
> syntax, just "mov eax, buffer" gets the address - like "mov eax offset
> buffer" in Masm. Nasm doesn't use the "offset" keyword assumes "offset"
> if it *isn't* in "[]". Masm doesn't use  "[]" to indicate "contents"...
> in this case. In other cases - "mov eax, [ebx]" - Masm *does* need the
> "[]". This is one of the most confusing differences between Masm and
> Nasm. In "Intel syntax", the "[]" doesn't mean "contents", but is
> equivalent to "+". You could write "mov eax, 4[buffer]", and it would be
> the same as "mov eax, [buffer + 4]". Nasm won't let you do the former -
> all of the address has to be between the "[]"s. Masm uses Intel syntax,
> Nasm uses "simplified Intel syntax", which is mostly the same, but not
> in this case.

This is the one piece of the NASM documentation that all ways has me
scratching my head after I read it. So in NASM, I can use [] for
memory
calculations and contents, and offset is assumed if I don't use [].

> The distinction between an address, and the contents of that address, is
> a *really* important one to understand, so it's an unfortunate place to
> have this potential confusion creep in. Such is life.

Very true. And I have used my debugger as too much of crutch. The
confusing
part for me is when the debugger is showing my code in MASM syntax,
but I'm
writing in NASM. For the most part, I am okay. But in certain cases,
I get
confused. This just forces me to learn them both.

However, the work done with golink that lets me avoid using lib files
makes
NASM a far better choice for me.

Frank Kotler

unread,
Feb 24, 2009, 1:35:44 AM2/24/09
to
bwa...@yahoo.com wrote:
> On Feb 19, 10:22 pm, Frank Kotler <spamt...@crayne.org> wrote:
>
>> The "[]" tells Nasm that you want the contents of memory at that
>> address. You could say that Nasm assumes "offset" (the Masm keyword)
>> *without* the "[]". If you did "lea edi, [wBuffer+ ebx+offset]" (*not*
>> the Masm keyword!), you'd get the address. Same as:
>>
>> mov edi, buffer ; "offset buffer" for Masm
>> add edi, ebx
>> add edi, offset
>>
>> .... only in one instruction. If you then wanted the contents of that
>> address, "mov ax, [edi]" (or eax or al, depending on how much contents
>> you want).
>
> Thanks. This example makes the use of LEA super clear. Due to the
> limitations
> of operators that mov can handle, I have to do memory calculations
> step by step
> with MOV, so LEA makes more sense.
>
> I guess the only remaining part of LEA that is a little confusing is
> if you use it after
> a memory move. For example:
>
> lea edi, [wBuffer+ebx+offset] ; address to edi
> lea eax, [edi] ; dword content of edi, right?

No, same as "mov eax, edi". That reminds me, though (not quite what you
did) of another way "lea" is sometimes used. If you disassemble C code -
or we can do it, too, you'll see lea with same source and destination
used as alignment padding. "lea esi, [esi]", "lea esi, [esi + byte 0]",
"lea esi, [esi + dword 0]" - all "multi-byte nops".

> so in this case, I'm better off just doing:
>
> mov edi, [wBuffer+ebx+offset]

Yeah, if you want "[contents]".

> It's interesting how I can get to the same place, but the steps just
> change. This
> is one of the most fascinating aspects of assembly programming.

Yes. Choosing the "best" of the ways - for the particular situation -
makes one program different from another... "better", we hope. True in
any language, of course, but in assembly, it's "right in front of your
face".

> This is the one piece of the NASM documentation that all ways has me
> scratching my head after I read it. So in NASM, I can use [] for
> memory
> calculations and contents, and offset is assumed if I don't use [].

Right.

>> The distinction between an address, and the contents of that address, is
>> a *really* important one to understand, so it's an unfortunate place to
>> have this potential confusion creep in. Such is life.
>
> Very true. And I have used my debugger as too much of crutch.

I don't think any way of understanding code better can be considered a
"crutch".

> The confusing
> part for me is when the debugger is showing my code in MASM syntax,
> but I'm
> writing in NASM. For the most part, I am okay. But in certain cases,
> I get
> confused. This just forces me to learn them both.

I think it's inevitable that we're going to have to learn - read-only,
at least - more than one assembler. Masm <-> Nasm is bad enough, but it
gets much worse.

> However, the work done with golink that lets me avoid using lib files
> makes
> NASM a far better choice for me.

Yeah, Nasm's the best. :) I assume that Golink would work with any
assembler which outputs a suitable linkable object format, though. It
*might* even work best of all with Goasm... :)

Best,
Frank

I have just gotten the sad news that Chuck Crayne - moderator of this
group, and important contributor to Nasm - has passed away. My
sympathies and best wishes to his family and friends. He will be missed.
Rest In Peace, Chuck.

0 new messages