2

In 16 bit x86 assembly you would write:

mov ax, @DATA
mov ds, ax

How is the extact code conversion for 32 bit ? I don't know the data segment in 32 bit.

mov eax, @DATA
mov ?  , eax

Thank you for your answers!

  • In protected mode, segment registers become selector registers to reflect the different semantic. The size, though, it's sill 16 bits. – Margaret Bloom Oct 04 '17 at 07:39
  • 3
    Note that since `mov ds, eax` is a valid encoding, the direct translation is the one you provided (with `ds` in place of `?`) granted that `@DATA` is a valid segment selector and you know what you are doing. Considering that `@DATA` is a TASM/MASM symbol for the value of the first segment of the `DGROUP` and that in protected mode an user mode program almost never sets the selectors, this may not be the case. I advise reading about protected mode, ELF/PE and the flat mode used by the OS. – Margaret Bloom Oct 04 '17 at 07:49
  • 3
    @MargaretBloom "16 bit x86 assembly" may be 80286 16-bit protected mode code, too. – Martin Rosenau Oct 04 '17 at 10:22

2 Answers2

5

The simple answer

The 32-bit code looks exactly like the 16-bit code:

mov ax, @DATA
mov ds, ax

... because the selector registers (cs, ds, es, ss, fs and gs) are still only 16 bits wide in 32-bit code. Therefore the "segment values" are also 16 bits wide and the lower 16 bits of the general purpose registers (e.g. ax) have not been renamed.

The more complex answer

Only few object file formats support selectors of segments in 32-bit code!

The line mov ax, @DATA will be rejected by the assembler because there is simply no possibility to represent this line in the object file!

Most 32-bit operating systems use a "flat" memory layout. This means that cs, ds, es and ss point to the physical address 0 and have a limit of 4GiB. In other words: The whole memory can be addressed directly without having the need to change the values of the selector registers.

For this reason most object file formats don't even support this feature.

There are few operating systems that really use the selector registers in 32-bit code. For such systems you'll have to use development software (assembler, compiler, linker ...) that uses an object file format supporting this!

If you have such software (and you use such an operating system) the code is identical to the 16-bit code (as shown above).

EDIT

After having read the comments I want to clarify the following sentence:

There are few operating systems that really use the selector registers in 32-bit code...

What is meant here is: Only few 32-bit operating systems use different values for the selector registers to access different sections (code, data, const, BSS...) in an executable file. (There are however other uses for different values in the selector registers.)

Most operating systems use the the same selector value for all segments/sections in the executable file and these operating systems already initialize the selector registers to the correct value. Therefore an instruction mov ax, @DATA does not really make sense. Unlike the instruction mov ax, ds (having the same effect under these conditions) an instruction mov ax, @DATA would also require a special feature (a special "relocation") in the object file format which will simply not be implemented because such an instruction does not make any sense.

However there are few 32-bit operating systems which do not use a "flat" memory layout but use different selector values for different data segments in a program. Such operating systems must use different object file formats, of course. For such operating systems an instruction mov ax, @DATA is definitely supported. However I doubt that there are assemblers that allow assigning selector values to 32-bit registers (mov eax, @DATA).

Martin Rosenau
  • 17,897
  • 3
  • 19
  • 38
  • I disagree with your second-to-last paragraph: Segment register are still extensively used as they are a very easy way to implement thread-local storage. There is still a `modify_ldt()` system call on Linux so you can set up your own segments for TLS, though currently, `set_thread_area()` is zsed generally. – fuz Oct 04 '17 at 12:48
  • @fuz This is right. In Windows the `fs` register also points to some special data. However on none of such OSs the use of a selector value as immediate argument (`mov ax, @DATA`) is supported. This is only supported on OSs which use different selector values for different sections (such as 16-bit Windows...). – Martin Rosenau Oct 04 '17 at 13:09
  • There *is* a value that will work as the segment selector under a modern OS. It's the same value that DS contains when your process starts, and it's the same constant value for *every* process on Linux at least. [In the kernel, there's are `__USER_DS` and `__USER32_DS` constants](http://elixir.free-electrons.com/linux/latest/source/arch/x86/include/asm/segment.h#L200). Barely-plausible use-case: you modify `DS` (or use as a tmp register, if it doesn't fault on arbitrary values?), and instead of saving/restoring the old value, you want to use a constant. – Peter Cordes Oct 08 '17 at 19:09
  • But if you want to hard-code a non-portable constant into your binary, you should just do that manually instead of asking the assembler to do it for you. It makes perfect sense that `@data` doesn't assemble outside of 16-bit mode. (I guess your claim is accurate that the assembler can't represent that line is close enough, since ELF64 or ELF32 output format doesn't imply that the code will run under a standard Linux kernel.) – Peter Cordes Oct 08 '17 at 19:10
  • @PeterCordes I edited my answer to make it more clear. – Martin Rosenau Oct 09 '17 at 06:20
  • Would upvote again for the 2nd part if I could :P. I'd be surprised if `mov eax, @DATA` was a problem, though. `@DATA` is an assemble-time constant, so in 32-bit mode you normally *want* to use `mov eax, imm32` rather than `mov ax, imm16` to avoid a false dependency and (on Intel before SnB) a length-changing prefix stall, even though it's one byte longer. In 16-bit mode, it's 3 bytes longer and the 32-bit form would LCP stall on CPUs where that's a problem, so you'd probably just go with `mov ax, imm16`. – Peter Cordes Oct 09 '17 at 06:56
2

(You'd normally only do this in kernel mode as part of saving/restoring user-space context. In 32-bit or 64-bit user-space under normal OSes with flat memory, use wrfsbase to modify the base address of fs for thread-local storage. Or equivalent for gs. These instructions require the FSGSBASE CPU feature.)


Intel recommends writing mov ds, eax to avoid wasting space on an operand-size prefix for 32-bit or 64-bit mode. However, NASM and YASM make this optimization for you. @Cody reports that MASM won't assemble mov fs, eax or mov eax, fs. The GNU assembler matches what NASM/YASM do.

                    ;  NASM/YASM machine code output
mov   fs, ax        ;    8e e0
mov   fs, eax       ;    8e e0
mov   fs, rax       ;    8e e0
mov   fs, r10d      ; 41 8e e2 (rex.w=0)

It matters when going the other direction:

    mov    ax, fs       ; 66 8c e0  only modifies AX, leaving upper bits
    mov   eax, fs       ;    8c e0  zeros whole rax (on P6 and later CPUs, undefined upper bytes on earlier)
    mov   rax, fs       ;    8c e0  zeros whole rax   (YASM: 48 8c e0)
    mov   r10d, fs      ; 41 8c e2  (rex.w=0)
    mov   r10, fs       ; 49 8c e2  (rex.w=1)

But note that a mov eax, fs / mov fs, eax round trip is still always safe, even on an old CPU where it could leave high garbage (see below), because the mov fs, eax ignores the high 2 bytes.

From Intel's vol. 2 manual (instruction-set reference), mov entry says this about reg,Sreg or Sreg,reg forms:

When operating in 32-bit mode and moving data between a segment register and a general-purpose register, the 32-bit IA-32 processors do not require the use of the 16-bit operand-size prefix (a byte with the value 66H) with this instruction, but most assemblers will insert it if the standard form of the instruction is used (for example, MOV DS, AX). [...]

When the processor executes the instruction with a 32-bit general-purpose register, it assumes that the 16 least-significant bits of the general-purpose register are the destination or source operand.

This is a confusing way to say that the segment reg value itself is read from or stored in the low 16.

It's confusing because it seem to imply (incorrectly) that the high 16b of a GP register is not part of the destination. Then they're still talking only about 32-bit (and 64-bit) destination registers, not GP destination registers in general.

If the register is a destination operand, the resulting value in the two high-order bytes of the register is implementation dependent. For the Pentium 4, Intel Xeon, and P6 family processors, the two high-order bytes are filled with zeros; for earlier 32-bit IA-32 processors, the two high order bytes are undefined.

So beware that on old CPUs (like P5 Pentium and earlier), mov eax, fs may not zero-extend, and may leave garbage instead. But mov fs, eax is always safe.


With a memory destination, it's always a 16-bit load or store, even if you use REX.W=1 (which NASM will encode with a qword size override, but YASM chokes on it.)

Intel's manual says there's a MOV r/m64,Sreg form, but that's bogus; it does not zero-extend to 64-bit with a memory destination.


This came up recently in a clang assembler bug about assembling movw %fs, (%rsi) with a 66 operand-size prefix (which turns out to be redundant). Some of the above is copy/pasted from what I wrote there.

I investigated by watching registers and memory with a debugger while single-stepping this NASM program:

global _start
_start:
    mov   rsi,   rsp
    mov   eax,   0xdeadbeef
    mov   [rsi], eax
    mov  dword [rsi+4], 0xbadf00d

     ;;; memory is set up                              
firstbreak:
    mov   [rsi], fs        ; 8c 26   16-bit store
    ;mov  dword [rsi], fs   ; not encodeable (even in 32-bit mode)
    mov  qword [rsi], fs   ; 48 8c 24 24   YASM chokes, NASM assembles REX.W 8c 26.  Still a 16-bit store!!!

    mov    ax, fs       ; 66 8c e0  only modifies AX, leaving upper bits
    mov   eax, fs       ;    8c e0  zeros whole rax
    mov   rax, fs       ;    8c e0  zeros whole rax   (YASM: 48 8c e0)
    mov   r10d, fs      ; 41 8c e2  (rex.w=0)
    mov   r10, fs       ; 49 8c e2  (rex.w=1)

    xor ebx,ebx
    mov eax,1
    int 0x80     ; sys_exit(0)   (32-bit ABI so you can more easily assemble this as 32-bit code)
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 1
    I'm pretty sure MASM won't assemble `mov fs, eax` or `mov eax, fs`. That's an "operand size conflict". I haven't tried on MASM64, but I imagine the same error would be generated. – Cody Gray - on strike Oct 04 '17 at 20:19
  • @CodyGray: Does it waste an operand-size prefix for `mov fs, ax`? And that means you can't get the `8c e0` encoding for `mov eax, fs`? – Peter Cordes Oct 04 '17 at 20:22
  • Yes, you get an operand size prefix. There may be some obscure option or mode that you can turn on that would affect this, but I don't know what it is. You'd probably need to do a `DB` and give the byte encoding directly. I confess I cannot ever think of a time where I have wanted to use 32-bit GP registers with segment registers. MASM probably isn't optimized for this case, either. All this logic probably hails from the 16-bit version. – Cody Gray - on strike Oct 04 '17 at 20:30
  • @CodyGray: Linux's system call entry/exit points has to check segment regs for some special cases. See https://github.com/torvalds/linux/blob/e7d0c41ecc2e372a81741a30894f556afec24315/arch/x86/entry/entry_64.S#L1014 for some kernel code that does `movl %ds, %ecx` and the same for the other segment regs to check that they match something in memory. Wasting bytes and false dependencies suck. – Peter Cordes Oct 04 '17 at 20:38
  • I happened to be looking at those files recently while writing [What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?](https://stackoverflow.com/questions/46087730/what-happens-if-you-use-the-32-bit-int-0x80-linux-abi-in-64-bit-code). – Peter Cordes Oct 04 '17 at 20:39