flat assembler
Message board for the users of flat assembler.

Index > Tutorials and Examples > Simple fasmg quesitons

Author
Thread Post new topic Reply to topic
florianmarkusse



Joined: 28 Feb 2024
Posts: 3
florianmarkusse 28 Feb 2024, 15:56
Hi there,

Have recently gained an interest in writing my own bootloader and thought it was a good idea to combine that with getting some exposure to fasm. So far it looks like a very powerful tool, it's really nice/

Now I was reading the manual https://flatassembler.net/docs.php?article=fasmg_manual#8 on macros and wanted to try some myself:

Code:
...
macro measured name,string
    local top
    name db string
    top: name.length = top - name
end macro

measured myVar 'hello'
...
    


This is taken from the fasmg manual. however, when assembling (INCLUDE=examples/x86/include/ ./fasmg.x64 boot.asm), I run into this error:
Code:
flat assembler  version g.kcgn
boot.asm [32]:
        measured myVar 'hello'
macro measured [2]:
        name db string
Processed: myVar 'hello' db 
Error: illegal instruction.
    

Can someone tell me what is wrong? It seems correct from looking at the manual.

Moreover, I was trying to change my print to TTY function into one that accepts any number of characters. This is how it is currently:
Code:
macro biosWrite char*
    push ax
    mov al, char
    call print
    pop ax
end macro

biosWrite 'H'
    


Now, in the fasm manual (https://flatassembler.net/docs.php?article=manual#1.2.5) it says to wrap the variadic argument in square brackets like so:

Code:
macro biosWrite [char]
    push ax
    mov al, char
    call print
    pop ax
end macro

biosWrite 'H'
    


This results in the following error:
Code:
flat assembler  version g.kcgn
boot.asm [14]:
        biosWrite 'H'
Processed: biosWrite 'H'
Error: illegal instruction.
    



Can anyone point me in the right direction of how to use these macros in the right way?

Thanks for your time :]
Post 28 Feb 2024, 15:56
View user's profile Send private message Reply with quote
macomics



Joined: 26 Jan 2021
Posts: 1044
Location: Russia
macomics 28 Feb 2024, 16:06
Code:
measured myVar, 'hello'    
You forgot the comma between the macro arguments

Code:
include '80386.inc'
use16

macro biosWrite char&
    push ax
    iterate <chr>, char
        mov al, chr
        call print
    end iterate
    pop ax
end macro

biosWrite 'H', 'e', 'l', 'l', 'o', 13, 10

print:
    retn    
To process the list of arguments, there is an iterate


Last edited by macomics on 28 Feb 2024, 16:36; edited 1 time in total
Post 28 Feb 2024, 16:06
View user's profile Send private message Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8359
Location: Kraków, Poland
Tomasz Grysztar 28 Feb 2024, 16:26
florianmarkusse wrote:
I run into this error:
Code:
flat assembler  version g.kcgn
boot.asm [32]:
        measured myVar 'hello'
macro measured [2]:
        name db string
Processed: myVar 'hello' db 
Error: illegal instruction.
    
You've given just a single argument to the macro (because of the aforementioned missing comma). If you look carefully at the error message, you should notice that the line "name db string" from the macro was processed into "myVar 'hello' db", because all the text went into "name" argument, and "string" argument is empty. Note that you could mark both the arguments as required by defining macro like this:
Code:
macro measured name*, string*    
and then the empty value would not be allowed and you would get an error on a macro call itself.

florianmarkusse wrote:
Now, in the fasm manual (https://flatassembler.net/docs.php?article=manual#1.2.5) it says to wrap the variadic argument in square brackets (...)
The macro systems of fasm 1 and fasmg (fasm 2) are quite a bit different, so it's better to not use documentation of fasm 1 when writing macros for fasmg. For the latter you need to use the "&" modifier to mark the variadic argument (and this new syntax has also been backported to fasm 1):
Code:
macro biosWrite char&    


And one more thing...
florianmarkusse wrote:
This results in the following error:
Code:
flat assembler  version g.kcgn
boot.asm [14]:
        biosWrite 'H'
Processed: biosWrite 'H'
Error: illegal instruction.
    
This is a second-order error, in that it was caused by another error, where the definition of "biosWrite" was not accepted. You should be getting the errors in the order in which they occurred in the source text, but because of fasm's multi-pass nature with extensive forward-referencing, the order of these errors may not reflect the cause-effect chain. I recommend to use the "-e" switch to allow fasmg to display more errors at once, then you should be able to find the original cause among them.
Post 28 Feb 2024, 16:26
View user's profile Send private message Visit poster's website Reply with quote
florianmarkusse



Joined: 28 Feb 2024
Posts: 3
florianmarkusse 28 Feb 2024, 18:14
ahhh damn, thanks so much for the quick responses!!

I see now what went wrong, fixed code:

Code:
macro biosWriteString string
    push ax
    push si
    cld
    mov si, string
    repeat string.length
        lodsb   ; mov al, [si] 
                ; inc si        is the same as this
        call print
    end repeat
    pop si
    pop ax
end macro


macro biosWrite char&*
    push ax
    iterate <chr>, char
        mov al, chr
        call print
    end iterate
    pop ax
end macro

macro measured name*,string*
    local top
    name db string
    top: name.length = top - name
end macro

measured myVar, 'hello hello'
    


This works like a charm, is this also how you would write these macros or is there some other construct that is better suited for this? I will be sure to only reference the fasmg manual then and use the -e switch, thanks!

I guess one more thing I can do is merge these macros somehow and have it decide at build time whether it is writing a string or a collection of chars, I guess I should use the match directive for that, right?
Post 28 Feb 2024, 18:14
View user's profile Send private message Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8359
Location: Kraków, Poland
Tomasz Grysztar 28 Feb 2024, 18:27
florianmarkusse wrote:
This works like a charm, is this also how you would write these macros or is there some other construct that is better suited for this?

I would definitely change this line:
Code:
    top: name.length = top - name    
It can be simplified with help of $ symbol (which always has the value of current address), and you no longer need the local label then:
Code:
    name.length = $ - name    
You could also overload the DB definition to add the length symbol automatically even with classic syntax:
Code:
struc db? values&
      . db values
      .length = $ - .
end struc

myVar db 'hello hello'

assert myVar.length = 11 ; check that it worked    

florianmarkusse wrote:
I guess one more thing I can do is merge these macros somehow and have it decide at build time whether it is writing a string or a collection of chars, I guess I should use the match directive for that, right?
Well, that depends on the syntax you would like to use with these macros. If you describe what the ideal syntax would look like, I should be able to show you how to arrange it using fasmg's macros.
Post 28 Feb 2024, 18:27
View user's profile Send private message Visit poster's website Reply with quote
florianmarkusse



Joined: 28 Feb 2024
Posts: 3
florianmarkusse 29 Feb 2024, 13:31
Awesome!

So this is what I settled on currently for creating a macro that can write any number of strings and chars, it's really neat that you can freely mix it!!

Code:
define ASCI_START 32  ; ' '
define ASCI_END   126 ; '~'
macro biosWrite charOrStrings*&
    iterate <charOrString>, charOrStrings
        if ASCI_START <= charOrString & charOrString <= ASCI_END
            biosWriteChars charOrString
        else
            biosWriteString charOrString
        end if
    end iterate
end macro

macro biosWriteString string
    push ax
    push si
    cld
    mov si, string
    repeat string.length
        lodsb   ; mov al, [si] 
                ; inc si        is the same as this
        call print
    end repeat
    pop si
    pop ax
end macro

macro biosWriteChars char&
    push ax
    iterate <chr>, char
        mov al, chr
        call print
    end iterate
    pop ax
end macro
    


My question to you would be twofold:
- How would you write something like this? Assuming you want a generic write to BIOS macro, or would u use a function?
- I check now that the value of the argument is within the (by me defined) printable ASCI chars. Is this method foolproof or can a string that starts within that range be classified as a char? What am I even passing to the macro when I use a string variable, I was thinking just the address of the struct but maybe that is incorrect?

Thanks again for your help already!
Post 29 Feb 2024, 13:31
View user's profile Send private message Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8359
Location: Kraków, Poland
Tomasz Grysztar 29 Feb 2024, 14:14
florianmarkusse wrote:
- How would you write something like this? Assuming you want a generic write to BIOS macro, or would u use a function?
I would probably use a function myself, especially considering the context of writing a bootloader where minimizing the code size is preferable.

florianmarkusse wrote:
- I check now that the value of the argument is within the (by me defined) printable ASCI chars. Is this method foolproof or can a string that starts within that range be classified as a char? What am I even passing to the macro when I use a string variable, I was thinking just the address of the struct but maybe that is incorrect?
Yes, you are passing the address of the data, because in fasm's language (and similarly to some other assemblers like NASM) label is just a constant with value being the address.

For example, the following three snippets all define the same "myVar" symbol:
Code:
org 100h
myVar:    
Code:
label myVar at 100h    
Code:
myVar := 100h    
It follows that the range check you used may not be reliable if the numeric value of the address lands in the same range as the ASCII code. Moreover, if you use a relocatable format, where addresses contain some variable terms (fasmg implements such addresses in form of linear polynomials), the comparison is going to fail with an error message, because then the address has an unknown value and fasmg cannot tell whether it falls in range or not.

You could make use of the fact that your strings have ".length" constant defined, and do something like:
Code:
macro biosWrite charOrStrings*&
    iterate charOrString, charOrStrings
        if charOrString eqtype ''
            biosWriteChars charOrString
        else if defined ?charOrString.length
            biosWriteString charOrString
        else
            biosWriteChars charOrString
        end if
    end iterate
end macro    
This should work for you, but is a bit fragile: the first IF is there to filter out arguments that are quoted characters, because the "if defined charOrString.length" would then be rejected as a malformed expression. Also the "?" is added in front of "charOrString" to ensure that it is treated as a symbol name even when it's a number.

Keep in mind that a classic macro uses a straightforward text substitution, so whatever wild text you give in an argument, it is going to be put in place of the name of the argument in macro text, possibly resulting in a malformed expressions or even unintended effects. More reliable approaches would be possible with help of MATCH or CALM TRANSFORM, etc. But since you're just starting out, I'm not going to throw too many things at you.
Post 29 Feb 2024, 14:14
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8359
Location: Kraków, Poland
Tomasz Grysztar 29 Feb 2024, 18:30
Well, just to give a little taste, here's a very simple example that uses MATCH to detect a special marker under a "name.string" symbol:
Code:
struc db? values&
      . db values
      .length = $ - .
      .string equ +
end struc

macro biosWrite charOrStrings*&
    iterate charOrString, charOrStrings
        match +, charOrString.string
            biosWriteString charOrString
        else
            biosWriteChars charOrString
        end match
    end iterate
end macro    
This works for all kinds of input, because MATCH accepts any text as its second argument, but if it recognizes a symbolic variable there (one made with DEFINE or EQU), it replaces it with the associated text before matching.
Post 29 Feb 2024, 18:30
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-2025, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.