flat assembler
Message board for the users of flat assembler.

Index > Macroinstructions > Changing numerical constants: An unexpected order effect

Author
Thread Post new topic Reply to topic
Frank



Joined: 17 Jun 2003
Posts: 100
Frank 17 Jul 2005, 00:59
The code below seems to demonstrate an undesired interaction in FASM's treatment of two (unrelated) numerical constants. It features a system of macros to declare procedures. I have simplified the macros as much as possible, so that it is easier to see the unexpected behavior.

Here is a short explanation of what the macros aim to achieve. The PROC macro initializes a variable called .dStack to zero. The ENDP macro tests if .dStack is still (or again) zero, and displays an error message if not. In between, two sets of macros change .dStack:

A) One set (PUSH, STDCALL) tracks changes to .dStack caused by calling procedures;
B) The other set (VAR, SUB, ADD) takes care of local stack-variables.

Four procedures demonstrate when the macro-sets do versus do not work. StartProc1 and StartProc2 compile, showing that each of the two macro-sets does work when used in isolation. StartProc3 compiles, but StartProc4 does not, showing that the macro sets cannot reliably be used in combination.

Note that StartProc3 and StartProc4 differ only in an apparently minor detail, namely in the order of using the macro subsets (StartProc3: B-B-B-A-A, StartProc4: B-B-A-A-B). This leads me to the assumption that there may be some kind of nonlinearity in the compilation process, and not a problem with the macros per se. This behavior may have been discussed before, or may have a trivial explanation -- in any case, I'd appreciate pointers.

Regards

Frank


Code:
format PE GUI 4.0

; --- Macro-set PROC / ENDP ---

macro proc procedurename, [param]
{
  common
    procedurename:
    if used procedurename
        local count
        count = 0
        if ~(param eq)
            forward count = count + 1
            common
        end if
        .paramcount = count
        .stackdatasize = 0
        .dStack = 0
}

macro endp
{
        if .dStack <> 0
            display 'Stack not balanced'
            halt
        end if
    end if
}

; --- Macro-set A) ---

macro push argument
{
    push argument
    .dStack = .dStack + 4
}

macro stdcall procedurename, [argument]
{
  reverse
    if ~(argument eq)
        push argument
    end if
  common
    call procedurename
    .dStack = .dStack - (procedurename#.paramcount*4)
}

; --- Macro-set B) ---

macro var param
{
    .stackdatasize = 4
}

macro sub target, argument
{
    sub target, argument
    if (target eq esp)
        .dStack = .dStack + argument
    end if
}

macro add target, argument
{
    add target, argument
    if (target eq esp)
        .dStack = .dStack - argument
    end if
}


; --- The procedures ---

proc StartProc1
    stdcall  TestProc, 1,2,3
    ret
endp

proc StartProc2
var xyz
    sub esp, .stackdatasize
    add esp, .stackdatasize
    ret
endp

proc StartProc3
var xyz
    sub esp, .stackdatasize
    add esp, .stackdatasize
    stdcall  TestProc, 1,2,3
    ret
endp

proc StartProc4
var xyz
    sub esp, .stackdatasize
    stdcall  TestProc, 1,2,3
    add esp, .stackdatasize
    ret
endp

proc TestProc, arg1,arg2,arg3
    ret 3*4
endp

; --- Uncomment the following lines, but only one at a time ---

;entry StartProc1
;entry StartProc2
;entry StartProc3
;entry StartProc4
    
Post 17 Jul 2005, 00:59
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20309
Location: In your JS exploiting you and your system
revolution 17 Jul 2005, 09:46
I don't have a solution for you but I did notice that if you put the following code at the end then the program willl compile.
Code:
call TestProc    

An interesting problem.
Post 17 Jul 2005, 09:46
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8351
Location: Kraków, Poland
Tomasz Grysztar 17 Jul 2005, 11:15
The problem comes from the fact that you use this code:
Code:
.dStack = .dStack - (procedurename#.paramcount*4)    

with procedure TestProc before it gets actually defined, so during the first intermediate pass the value of .paramcount is not yet known (so is assigned to be zero). Nevertheless you then rely on value calculated this way to check the stack balance. If you remove the "halt" instruction, you will see that the message about unbalanced stack doesn't get displayed finally when you allow fasm to completely resolve the code. The illegal instruction however stops the assembly during the first pass, preventing your code from being resolved.

The solution might be to replace "halt" with some instruction that cause "resolvable" error, in which case fasm doesn't report an error unless it still remains after resolving is complete - like jump out of range, or "rb" with invalid value:
Code:
rb -1    


Or maybe I should implement "halt" internally, as decard suggested, to cause an "user error" in resolvable way.
Post 17 Jul 2005, 11:15
View user's profile Send private message Visit poster's website Reply with quote
Frank



Joined: 17 Jun 2003
Posts: 100
Frank 17 Jul 2005, 15:32
Tomasz Grysztar wrote:
The problem comes from the fact that you use this code:
Code:
.dStack = .dStack - (procedurename#.paramcount*4)    


Tomasz, your explanation does not cover the "full story". The same line of code is also used by two procedures that compile nicely (StartProc1, StartProc3). Therefore the line alone cannot fully explain why FASM takes a different compilation path in StartProc4.

A full explanation will need to account for the unexpected order effect: the same macros with the same parameters compile nicely when called in the sequence SUB-ADD-STDCALL (as in StartProc3), but lead to a completely different compilation path when called in the order SUB-STDCALL-ADD (as in StartProc4). Note that my code example has been carefully constructed such that sequence should not matter at all.

Well, anyway. Your suggestion of replacing "halt" by "rb -1" makes the code compile. It is a really elegant workaround, thank you.

Regards,

Frank
Post 17 Jul 2005, 15:32
View user's profile Send private message Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8351
Location: Kraków, Poland
Tomasz Grysztar 17 Jul 2005, 15:59
Well, I might have not been clear enough: when the expression has an value that is not yet known, it has a value of zero assigned temporarily (as this usually makes better optimization, but that's another story). So this line:
Code:
.dStack = .dStack - (procedurename#.paramcount*4)    

assigns 0 to .dStack contant in the first pass, when the value of TestProc.paramcount is not yet known. You can easily see how setting .dStack to zero in this place leads to your problem. It goes like:
Code:
.dStack = .dStack + .stackdatasize
.dStack = .dStack - (procedurename#.paramcount*4) ; unknown value, sets to 0
.dStack = .dStack - .stackdatasize ; sets to 0-.stackdatasize    

and so you get non-zero .dStack value in this case.
Post 17 Jul 2005, 15:59
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20309
Location: In your JS exploiting you and your system
revolution 18 Jul 2005, 01:11
How about this:
Code:
halt fix db -1    

Then the "halt" looks good in the code and the "db -1" gives the desired effect.
Post 18 Jul 2005, 01:11
View user's profile Send private message Visit poster's website Reply with quote
Frank



Joined: 17 Jun 2003
Posts: 100
Frank 18 Jul 2005, 04:12
Excellent -- I see it now Very Happy
Post 18 Jul 2005, 04:12
View user's profile Send private message Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8351
Location: Kraków, Poland
Tomasz Grysztar 18 Jul 2005, 08:52
revolution: yes, and even the EQU will be enough, since we need this replacement at the assembly stage, not earlier. Also I would recommend the "err" name instead of "halt", as this one doesn't halt compilation and causes error only when the resolving doesn't fix it.
Post 18 Jul 2005, 08:52
View user's profile Send private message Visit poster's website 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.