How to switch between multiple data segments in 8086 assembly?
Asked Answered
W

2

1

I'm programming using MASM 6.11 on an old Windows 95 laptop, and I'm having a problem switching between data segments.

For the sake of organization I wanted to use a different data segment to hold all of the variables that are only used by my macros. That different data segment is also placed inside the macro file.

I thought I could specify a different data segment by just MOVing the new segment into DS, but that doesn't seem to be working. I get the following error repeated many times upon assembling.

error A20068: Cannot address with segment register

Here's an example .ASM, and .MAC file showing my program's basic layout.

;********************
; STACK SEGMENT 
;********************

TheStack STACK SEGMENT

 DB 64 DUP ('THESTACK') ;512 bytes for the stack

TheStack ENDS

;********************
; END STACK SEGMENT
;********************

;********************
; DATA SEGMENT 
;********************

Data SEGMENT

var db ?

Data ENDS

;********************
; END DATA SEGMENT 
;********************


;********************
; CODE SEGMENT 
;********************

Code SEGMENT

assume CS:Code,DS:Data

MAIN PROC

    ;set Data to be the Data Segment
    mov ax, Data
    mov ds, ax

    MAC3   

    ;Return to DOS
    mov ah,4ch ;setup the terminate process DOS service
    mov al,0 ;ERRORLEVEL takes 0
    int 21h ;return to DOS

MAIN ENDP

Code ENDS

;********************
; END CODE SEGMENT 
;********************

END Start 

And the .MAC file:

MAC1 MACRO

     mov MacVar1,bx

     ENDM


MAC2 MACRO

     mov MacVar2,cx

     ENDM



MAC3 MACRO

     mov ax, MacData
     mov ds, ax

     MAC1
     MAC2

     mov ax, Data
     mov ds, ax

     ENDM



;********************
; DATA SEGMENT 
;********************

MacData SEGMENT

macVar1 dw ?
macVar2 dw ?

MacData ENDS

;********************
; END DATA SEGMENT 
;********************
Woodenware answered 28/8, 2016 at 2:25 Comment(0)
Z
3

For the sake of organization I wanted to use a different data segment to hold all of the variables that are only used by my macros.

That sounds like a good way to make your macros less useful and harder to use. As well as destroying performance. What if you want to use a macro with a memory operand?

If you don't actually care about 8086, and just want 16-bit code that can run on a 386 or later, then you could still use this weird idea but with FS or GS segment overrides on instructions inside your macro, instead of changing DS.

Or even use ES for 8086 compatibility, but remember it's used by string instructions.


error A20068: Cannot address with segment register

You didn't say which instructions are generating those.

; this is valid:
mov  ax, imm16
mov  ds, ax

If that's not what your syntax means to MASM, then that's your problem. If mov ax, MacData looks like a load to MASM, that's not good, but IDK why you get an error. Maybe it's trying to implicitly generate segment overrides based on where it thinks different variables are supposed to be?

See the tag wiki for links to assembler manuals and lots of other stuff.


Segmented 16-bit code is not a good way to learn asm in general, IMO. Especially not learning the DOS system call API at the same time, since that's obsolete knowledge that you won't use while looking at the asm of real programs (while debugging or performance tuning). See this answer for more about how I think 16-bit code is a bad way to learn.

Ziguard answered 28/8, 2016 at 2:29 Comment(8)
Oh, I didn't know that. I'm just tying to organize my cluttered source code. I guess i'll just break the one data segment into sections using comment lines and boxes. Also i'm still very new to assembly, and programming in general. I write the most efficient code that I can, but performance isn't very important to me.Woodenware
@Proughgrammor: if you're new to asm in general, use the "tiny" code model where everything is in the same segment. Then you don't have to learn segmentation until after you know what you're doing with the registers and memory. (Or ideally, you can move on to 32-bit or 64-bit code without ever bothering with that mostly-obsolete knowledge until you pick it up naturally from reading StackOverflow questions from people with 8086 DOS programming homework questions) See my update to my answer.Ziguard
And yes, just group things together with blank lines and comments, not segments that actually makes things harder for you! Tip #2: when you get it to compile and have to start debugging, use a debugger to show you the values in registers as you single-step. Writing asm without a debugger is like trying to build a robot while blindfolded.Ziguard
I actually want to buy a vintage computer with an 8086/8088 CPU and write programs for it. That's why I'm choosing to learn from an old assembly programming book from the early 90's, and assembling using an ancient assembler. I would never use assembly to program a modern machine, although it might be useful to do so at times. Anyway, Thanks for all of the info, and i'll mark your answer as the solution.Woodenware
@Proughgrammor: Ok. If you want to use more than 64k of RAM, then you'll eventually want to learn about segments. But for now, definitely stick with a "tiny" code model (like a DOS .com) where everything is in the same segment. Segmentation itself isn't very complicated, but dealing with "far pointers" (segment:offset) is extra complexity while you're still learning.Ziguard
What makes it so complex? The segment is the current 64k Memory block, and the offset is the distance you are into that block. What I worry about is writing very slow code that "just works".Woodenware
BTW, I don't typically write in assembly, but reading assembly is very useful to understand why one C function runs faster than another. You can see what the compiler did, and understand it. I guess restricting yourself to old books could be fun, but after learning 32/64-bit asm and then learning about segmentation, and how DOS and BIOS calls work, I could write 16-bit code if I wanted to. I'm sure there are code-size saving tricks I don't know about (8086 cares way more about code-size than modern CPUs), but I have posted some code-golf answers in x86 and x86-64 machine code :)Ziguard
@Pro segmentation isn't a complex concept, but it's a complexity multiplier on top of any code you write. e.g. if you reserve 16 bytes on the stack, you can't just pass that address to another function as a pointer argument. Well you can, but you have to pass SS as well, as a far-pointer. And of course you can't modify CS directly, only with a far-jmp. And modifying SS requires disabling interrupts until you also modify SP to point to a valid location in the new segment (or else an IRQ could push IP:FLAGS at a bogus SS:SP). In fact, mov to SS disables intrs until after the next insn.Ziguard
K
4

As Peter Cordes said in his answer what you're doing a terrible idea. If you're going to the effort of programming in assembly language then you should be intelligently laying out your segments in order to minimize the amount of segment switching you need to do. If you ever do get a 8088-based PC then this becomes even more important because of how slow these CPUs were. Your Windows 95 laptop is at least 10 times faster than a 4.77 Mhz 8086 PC and could be 100 times faster.

In any case, your problem is that when you use a label in a memory operand the assembler needs to know what segment register to use for the instruction. You've told it, through the ASSUME directive, that CS points to the Code segment and DS points to the Data segment. However you haven't said what segment register points to the MacData segment, so the assembler doesn't know what segment register to use for the macVar1 and macVar2 operands.

The simple fix would be to change the ASSUME'd DS value when you change DS:

MAC3 MACRO
     mov ax, MacData
     mov ds, ax

     ASSUME DS:MacData

     MAC1
     MAC2

     mov ax, Data
     mov ds, ax

     ASSUME DS:Data

     ENDM

You could also change your memory operands to explicitly tell the assembler which segment register to use. This would let you make your macros more flexible so they don't have assume what DS value the rest of the code is using:

MAC1 MACRO
     mov ds:MacVar1,bx
     ENDM

MAC2 MACRO
     mov ds:MacVar2,cx
     ENDM

MAC3 MACRO
     push ds
     mov ax, MacData
     mov ds, ax

     MAC1
     MAC2

     pop ds
     ENDM
Keratin answered 28/8, 2016 at 4:13 Comment(2)
Oh, woops. I forgot about ASSUME. Anyway, I decided it would be easier, and apparently faster, to stick with just the one data segment, but now it's organized into separate sections with separators.Woodenware
Thanks for posting a real answer to go with my "don't do that but here's a terrible workaround" answer. :) I realized that a good answer would say something about the assembler syntax for telling it about segments and symbols, but I didn't want to look up the details and choose to remain blissfully ignorant.Ziguard
Z
3

For the sake of organization I wanted to use a different data segment to hold all of the variables that are only used by my macros.

That sounds like a good way to make your macros less useful and harder to use. As well as destroying performance. What if you want to use a macro with a memory operand?

If you don't actually care about 8086, and just want 16-bit code that can run on a 386 or later, then you could still use this weird idea but with FS or GS segment overrides on instructions inside your macro, instead of changing DS.

Or even use ES for 8086 compatibility, but remember it's used by string instructions.


error A20068: Cannot address with segment register

You didn't say which instructions are generating those.

; this is valid:
mov  ax, imm16
mov  ds, ax

If that's not what your syntax means to MASM, then that's your problem. If mov ax, MacData looks like a load to MASM, that's not good, but IDK why you get an error. Maybe it's trying to implicitly generate segment overrides based on where it thinks different variables are supposed to be?

See the tag wiki for links to assembler manuals and lots of other stuff.


Segmented 16-bit code is not a good way to learn asm in general, IMO. Especially not learning the DOS system call API at the same time, since that's obsolete knowledge that you won't use while looking at the asm of real programs (while debugging or performance tuning). See this answer for more about how I think 16-bit code is a bad way to learn.

Ziguard answered 28/8, 2016 at 2:29 Comment(8)
Oh, I didn't know that. I'm just tying to organize my cluttered source code. I guess i'll just break the one data segment into sections using comment lines and boxes. Also i'm still very new to assembly, and programming in general. I write the most efficient code that I can, but performance isn't very important to me.Woodenware
@Proughgrammor: if you're new to asm in general, use the "tiny" code model where everything is in the same segment. Then you don't have to learn segmentation until after you know what you're doing with the registers and memory. (Or ideally, you can move on to 32-bit or 64-bit code without ever bothering with that mostly-obsolete knowledge until you pick it up naturally from reading StackOverflow questions from people with 8086 DOS programming homework questions) See my update to my answer.Ziguard
And yes, just group things together with blank lines and comments, not segments that actually makes things harder for you! Tip #2: when you get it to compile and have to start debugging, use a debugger to show you the values in registers as you single-step. Writing asm without a debugger is like trying to build a robot while blindfolded.Ziguard
I actually want to buy a vintage computer with an 8086/8088 CPU and write programs for it. That's why I'm choosing to learn from an old assembly programming book from the early 90's, and assembling using an ancient assembler. I would never use assembly to program a modern machine, although it might be useful to do so at times. Anyway, Thanks for all of the info, and i'll mark your answer as the solution.Woodenware
@Proughgrammor: Ok. If you want to use more than 64k of RAM, then you'll eventually want to learn about segments. But for now, definitely stick with a "tiny" code model (like a DOS .com) where everything is in the same segment. Segmentation itself isn't very complicated, but dealing with "far pointers" (segment:offset) is extra complexity while you're still learning.Ziguard
What makes it so complex? The segment is the current 64k Memory block, and the offset is the distance you are into that block. What I worry about is writing very slow code that "just works".Woodenware
BTW, I don't typically write in assembly, but reading assembly is very useful to understand why one C function runs faster than another. You can see what the compiler did, and understand it. I guess restricting yourself to old books could be fun, but after learning 32/64-bit asm and then learning about segmentation, and how DOS and BIOS calls work, I could write 16-bit code if I wanted to. I'm sure there are code-size saving tricks I don't know about (8086 cares way more about code-size than modern CPUs), but I have posted some code-golf answers in x86 and x86-64 machine code :)Ziguard
@Pro segmentation isn't a complex concept, but it's a complexity multiplier on top of any code you write. e.g. if you reserve 16 bytes on the stack, you can't just pass that address to another function as a pointer argument. Well you can, but you have to pass SS as well, as a far-pointer. And of course you can't modify CS directly, only with a far-jmp. And modifying SS requires disabling interrupts until you also modify SP to point to a valid location in the new segment (or else an IRQ could push IP:FLAGS at a bogus SS:SP). In fact, mov to SS disables intrs until after the next insn.Ziguard

© 2022 - 2024 — McMap. All rights reserved.