Search code examples
assemblyx86linkergnugnu-assembler

What is the purpose of GNU assembler directive .code16?


I do not understand the practical usage of .code16 or other .code* directives. What I understood from the answers at this question on StackOverflow,

when someone defines .code16 in their assembly code and does the following :

$ gcc -c -m32 -o main.o main.s

It ignores the .code16 and the output assembly would be meant to run on 32-bit platform. If someone does not specify -m flag, it seems to take the one configured for gcc as default depending on the host. Hence, to conclude, .code* directive is always ignored and superseded by -m flag.

Can someone please correct me if I am wrong in my understanding, and what is the situation when I would use .code16 because I can always define that using -m16 and .code* is anyway going to be ignored depending on the target mode.

Are .code16 (or others) only meant to throw errors when the data couldn't fit in 16-bit registers, else otherwise, they would remain dormant?


Solution

  • The only reason you normally ever have for using .code16, .code32, or .code64 is in a kernel or bootloader when you want to have machine code for different modes in the same file. e.g. a bootloader that starts in real mode (.code16) could enable protected mode and far jump (ljmp) to a 32-bit code segment. You'd want to use .code32 before that block of code.

    If that's not what you're doing, don't use them.

    Using them in other cases just lets you shoot yourself in the foot and put 16-bit machine code into a 32-bit or 64-bit ELF executable so you get runtime failure instead of catching the mistake at build time. (e.g. because push %eax isn't valid in 64-bit mode). Don't put .code32 at the top of your 32-bit program; use a comment that says to assemble with gcc -m32.

    These directives tell the assembler what mode the CPU will be in when it decodes these instructions. So it knows what the default operand-size and address size will be, and whether or not a prefix is needed for an instruction that uses a 32-bit or 16-bit register.

    So for example mov %eax, (%ecx) assembles to 89 01 in 32-bit mode.

    But after .code16, it assembles to 67 66 89 01.

    If you then disassemble that as 32-bit machine code, it's 67 66 89 01 mov %ax, (%bx,%di) (because ModRM is different for memory operands in 16 vs. 32 and 64-bit mode).


    You wouldn't normally use .code16 manually. You can use gcc -m16 foo.c to get GCC to insert .code16gcc at the top of the file, so you can run it in 16-bit mode even though it will still use 32-bit operand-size and address-size (requiring a 386-compatible CPU).


    If you wanted to include 32 or 16-bit machine code as data in a normal 64-bit program, e.g. so your program could write it to a file or modify a running process with it, you could also use .code32 or .code16.