1

I have this assembly code (on Linux):

.globl _start
_start:
  cli                         

  xorw    %ax,%ax             # Set %ax to zero
  movw    %ax,%ds             
  movw    %ax,%es             
  movw    %ax,%ss             

I first add .code16 at the top to generate a 16-bit code and then replace that with .code32 to generate a 32-bit code. I compile these with these two commands:

gcc -m32 -nostdinc -c file.s
ld -m elf_i386 -o file.exe file.o

And, then I examine with

objdump -d file.exe

For the first case (.code16) I get this output:

08048054 <_start>:
 8048054:   fa                      cli    
 8048055:   31 c0                   xor    %eax,%eax
 8048057:   8e d8                   mov    %eax,%ds
 8048059:   8e c0                   mov    %eax,%es
 804805b:   8e d0                   mov    %eax,%ss

For the second case (.code32) I get this output:

08048054 <_start>:
 8048054:   fa                      cli    
 8048055:   66 31 c0                xor    %ax,%ax
 8048058:   8e d8                   mov    %eax,%ds
 804805a:   8e c0                   mov    %eax,%es
 804805c:   8e d0                   mov    %eax,%ss

I understand the 66 operand prefix part. What confuses me is the assembly mnemonics printed. Shouldn't xor %eax, %eax be printed for the .code32 case as well? Or, should it be printing xor %ax, %ax for .code16 case? Can somebody please clarify?

Peter Cordes
  • 245,674
  • 35
  • 423
  • 606
Kamalakshi
  • 5,176
  • 3
  • 14
  • 20
  • It looks like 16 bit ( a word ), seems to be translated into double world xoring, meaning, this includes above 16 bits, just to ensure, they are zero. Compiled for 32 bit, this works, as You would expect, because You are really zeroing out ax ( al, and ah, which both a togehter assemble a word ). And higher bits are NOT touched, as Your instruction says. So, 32bit code is completely right, as I think, and 16 bit seem to take care, that , though compiled for 16 bits, no garbage will be present in the higher 16 bits, because it is assembled with -m32. – icbytes Sep 04 '15 at 11:15
  • 1
    The -m32 option wins. It just disassembles the machine code for a 32-bit processor. Not that many practical cases where that 16-bit machine code is actually usable. – Hans Passant Sep 04 '15 at 11:41
  • 1
    If you want to objdump to disassemble the code using the 16-bit interpretation of the opcodes then use `objdump -d -M i8086 file.exe`. – Ross Ridge Sep 04 '15 at 16:59
  • @HansPassant : I believe `-m32` would have no role here in case its a hand-written assembly. Please correct me if I am wrong. – Insane Coder Feb 02 '20 at 12:26
  • `-m32` only holds good for assembly generated by gcc. – Insane Coder Feb 02 '20 at 12:44

1 Answers1

4

.code 16 tells the assembler to assume the code will be run in 16-bit mode, e.g. to use the 66 operand-size prefix for 32-bit operand-size instead of the default 16. However, you assemble and link it into an elf32 binary, which means the file metadata still indicates 32-bit code. (There's no such thing as an x86-16 Linux ELF file).

Objdump disassembles according to the file metadata, thus as 32-bit code, unless you override with -m i8086. The sizes you're getting match the binary for 32-bit disassembly.

You'll probably actually see breakage if you assemble an instruction that has a different length in 16bit mode, like

add  $129,  %ax  # 129 doesn't fit in an imm8

If assembled as a 16bit instruction, it will have no prefix, and an imm16 source operand. Decoded as a 32bit instruction, it will have an imm32 source operand, which takes more total bytes following the opcode. An operand-size prefix would change the length of the rest of the instruction (not including prefixes), for either mode. BTW, (pre-)decoding slows down on Intel CPUs for this special case where a prefix is length-changing for the rest of the instruction. (https://agner.org/optimize/)

Anyway, disassembling that instruction with the wrong code size will lead to the disassembler getting out of sync with instruction boundaries, so it will definitively test what mode it's being interpreted in.

If you're making normal user-space code (not a kernel that switches modes, or needs to be 16-bit), .code32 and .code64 are useless. They just let you put the machine code into the wrong kind of ELF file. (Assembling 32-bit binaries on a 64-bit system (GNU toolchain))


BTW, moving to %ss implicitly prevents interrupts until after the next instruction. (Which should set the stack pointer). You can avoid cli/sti that way.

Peter Cordes
  • 245,674
  • 35
  • 423
  • 606
  • Most of this is right. But NASM does not assume. .code 16 spawns an executalbe section whose interpretation has to be treated as 16 bit instructions. – icbytes Sep 04 '15 at 12:10
  • @icbytes: I meant it assumes you use your output the way you said you would, by running code assembled with `.code 16` in 16bit mode. Also, this is GNU assembler, not NASM :P. – Peter Cordes Sep 04 '15 at 12:28
  • I gather than `.code16` and `.code32` will cause the assembler to generate code of the appropriate execution mode, but it is up to the developer to ensure the CPU is in the proper mode before jumping there, right? – sherrellbc Nov 10 '17 at 12:22
  • @sherrellbc: yes, exactly. See also https://stackoverflow.com/questions/36861903/assembling-32-bit-binaries-on-a-64-bit-system-gnu-toolchain/36901649#36901649 for `.code32` vs. using `gcc -m32` when you want to build 32-bit code. – Peter Cordes Nov 10 '17 at 16:45
  • 1
    "There's no such thing as an x86-16 Linux ELF file" I believe TK Chia's ia16 gcc toolchain does use an extension of the ELF format for its 8086-compatible 16-bit output. – ecm Mar 17 '21 at 19:21