1

I decided to try using inline assembly in c for my basic operating system (using intel syntax because I am used to that with NASM). And I came across something I could not fix, I tried looking everywhere but nothing has worked. while trying the code shown after the error messages, GCC displays these messages:

main.c: Assembler messages:
main.c:8: Error: invalid use of register

The code being:

int main(){
    asm("mov ah, 0x00\n\t");
    asm("mov al, 0x13\n\t");
    asm("int 0x10\n\t");
    asm("mov bx, 0xA000\n\t");
    asm("mov es, bx\n\t");
    asm("mov di, 0\n\t");
    asm("mov byte [es:di], 0x0f\n\t"); // Problem line of error.
    while(1){
        asm("nop");
    }
    return 0;
}

Can someone help me?

Govind Parmar
  • 20,656
  • 7
  • 53
  • 85
  • 1
    Does GCC support NASM-style intel syntax though? Maybe try `mov byte ptr es:[di], 0x0f`. And I'm assuming you told GCC on the command line that you wanted to use intel syntax, since I don't see any `.intel_syntax` directive in your code. – Michael Feb 17 '21 at 17:39
  • 1
    Just FYI, this is not remotely close to a safe way to use GNU C inline asm, even apart from using NASM syntax. Never use Basic Asm (no constraints / clobbers) inside a non-`naked` function. You *must* always tell the compiler what registers you're modifying (and memory you're reading/writing, unless you use a `"memory"` clobber). Also, there's no need to destroy BX here; use AX or DI as a temporary to hold the desired ES value. – Peter Cordes Feb 18 '21 at 00:03

1 Answers1

4

Lots to mention about this. First, you are using Intel syntax, which is fine (at least with GCC, it's harder with clang). You can use -masm=intel when you compile it, or as a hack switch to .intel_syntax noprefix inside your asm template, then switch back to .att_syntax at the end of your asm string so the rest of the compiler's code still works. (How to set gcc to use intel syntax permanently?)

Next, you have lots of asm() blocks. It's probably better to have one, so you can specify inputs / outputs / clobbers / gotos for the entire block. That can be done pretty easily, since C concatenates adjacent strings.

Your code has NASM-style [es:di] in it. In GAS's dialect of Intel syntax, the segment name goes outside the brackets: es:[di]. I also note that [di] isn't going to be appropriate unless you are on a 16-bit architecture; the thing in square brackets must be the size of a pointer on the platform...

...However you are also using int 0x10, which is a BIOS interrupt, so I am guessing that 16-bits is actually what you intend here.

All that said, you do need to get in the habit of specifying clobbers. That is, specify which registers your code modifies, and whether it modifies the condition code register and memory. The compiler is making decisions about which variables to hold in registers, etc., and has no idea what your assembly code does. You have to tell it you are modifying, for instance, ax, so it knows and can save/restore it.

GCC doesn't really know about segmentation (it's a portable compiler that assumes a flat memory model, not specializing in x86-16), so it's not really safe to leave es with a different base than ds. e.g. GCC might choose to use rep stosd to initialize a struct or array. It's safe here because your code after the asm is minimal.

Here's my rewrite of your code.

int main()
{
    asm volatile(
        ".intel_syntax noprefix\n\t"
        "mov ah, 0x00\n\t"
        "mov al, 0x13\n\t"
        "int 0x10\n\t"
        "mov bx, 0xA000\n\t"
        "mov es, bx\n\t"
        "mov di, 0x00\n\t"
        "mov byte ptr es:[di], 0x0f\n\t"
        ".att_syntax"                      // undo .intel_syntax
        : /* output operands */
        : /* input operands */
        : "ax","bx","di","cc", "memory" /* clobbers */
    );
    while(1){
        // asm("nop");  // unneeded: empty infinite loops are already legal in C.
    }
    return 0;
}

If your code modifies or even reads memory that you also access via C pointers, you would also include "memory" in the clobbers line. See the documentation for inline assembly for gcc, and How can I indicate that the memory *pointed* to by an inline ASM argument may be used?

volatile is not strictly necessary here because with no output operands, the asm statement is implicitly volatile. But it's a good idea to include volatile to make it clear (to human readers) that there's a visible side effect (the BIOS call, and storing to video memory). So if you did include an output operand, it wouldn't get optimized away.

Addendum: In general, use -masm=intel to compile, instead of using .intel_syntax in the listing. That lets the compiler use the right syntax when substituting operands.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Stacy J
  • 158
  • 7
  • 1
    It's not a good idea to use `".intel_syntax noprefix\n\t"` inside an inline-asm template. (Use `gcc -masm=intel` so operands work properly, especially `"m"` will expand as `[reg]` instead of `(%reg)`.). But if you do, you need to end the block with `".att_syntax"`, otherwise you leave the assembler in Intel-syntax mode when it's trying to assemble the rest of the compiler-generated asm (which will still be AT&T syntax, unless you used `-masm=intel` in which case `.intel_syntax noprefix` was redundant, at least for GCC. IDK how to safely use Intel-syntax in clang inline asm) – Peter Cordes Feb 25 '21 at 02:06
  • An extremely good point that I failed to mention! Agreed. – Stacy J Feb 26 '21 at 02:47
  • It's `-masm=intel`, not `-mintel`. I fixed that in your answer and tweaked some other things (including adding links to existing Q&As that go into more detail about some things you mention.) – Peter Cordes Feb 26 '21 at 03:48