The base address for the video memory in MS-DOS is 0xB8000. I am trying to write to this address using debug.exe, but I am getting an error:
1165:0103 mov [b8000],ax
^ Error
The base address for the video memory in MS-DOS is 0xB8000. I am trying to write to this address using debug.exe, but I am getting an error:
1165:0103 mov [b8000],ax
^ Error
You need to use a segment and offset, 0xB8000 can’t be represented directly in 16 bits:
mov ax, b800
mov ds, ax
; set AX appropriately here, or write an immediate value
mov [0000], ax
You need to go through another register to write to DS because mov ds, imm16 isn’t valid.
Note that by writing a word (AX), you’re writing a character and its attribute in one operation (in text mode).
Somewhat amusingly, mov ax, imm16 is encoded as 0xB8, so mov ax, b800 is 0xB800B8...
In monochrome video modes you’d have to use 0xB000 instead of 0xB800, and 0xA000 for some EGA/VGA graphics modes (see this table for details).
Another approach, as pointed out by tofro, is to use ES and the index registers:
mov ax, b800
mov es, ax
xor di, di
; set AX appropriately here
stosw
This has the same effect, using stosw to write the contents of AX to the memory pointed at by ES:DI (and incrementing DI in the process, so the next stosw writes to the next character on screen). This is how one would commonly go about writing to the screen, in combination with rep and movsb or movsw to copy content from DS:SI to ES:DI. For example, here’s how to clear the screen in 80×25 mode:
mov ax, b800
mov es, ax
xor di, di
mov cx, 07d0
mov ax, 2000
rep stosw
This writes 0x0020 (black-on-black space) to the screen 2000 times (80×25, 0x07D0).
AX anyway, you might as well use PUSH and POP to set DS directly—e.g., push B800h+pop ds.
– Cody Gray - on strike
Jun 07 '17 at 06:36
push imm16 requires a 286 and isn’t supported by debug, so you’d need to go through another register anyway (or manually write to the stack). I’m only preserving AX because I don’t know what comes before the OP’s mov here.
– Stephen Kitt
Jun 07 '17 at 06:39
push imm not existing prior to the 286. It's just a compulsion where I look at code and see ways to optimize it.
– Cody Gray - on strike
Jun 07 '17 at 06:42
mov reg, reg is much, much faster than push reg and pop reg (2 cycles for mov, at least 19 for push / pop on 8086), and it takes the same amount of code space.
– Stephen Kitt
Jun 07 '17 at 06:43
push reg. pop reg is even faster, at 12 cycles. That's not counting any of the cycle-eater penalties (as Abrash refers to them), but the standard published cycle counts don't generally do that. Of course mov reg, reg is going to be fast, but you have to move in an immediate here first. That is faster, taking 4 cycles, but it requires several more bytes. push and pop are 1 byte each, which is a big deal on the 8088. But I've forgotten 90% of what I ever knew about optimizing for pre-386 machines!
– Cody Gray - on strike
Jun 07 '17 at 06:48
push / pop (11 and 8 cycles respectively on 8086 according to the book I’m looking at, 15 and 12 on 8088 which matches your figures). You need the immediate move anyway at some point or other, regardless of how you get the value to the target register in the end. Thanks for the links!
– Stephen Kitt
Jun 07 '17 at 06:51
$-terminated string. Of course this had the pretty big downside in some situations of scrolling the whole screen after you wrote to the last character cell, making full-screen TUIs impossible to implement (though you could kinda-sorta do it by very deliberately not writing to the last character cell). That said, I think we can ignore the whole MS-DOS/PC-DOS part entirely and just focus on the question of how to do inter-segment writes in 8088/8086 assembly... – user Jun 09 '17 at 12:05