12

I created simple code to load second sector from hard drive, and then write to whole screen, with spaces with red background. The problem is that always instead of spaces I got @ signs. This is the code:

org 0x7C00
bits 16

xor ax,ax
mov ds,ax
mov es,ax

mov bx,0x8000
cli
mov ss,bx
mov sp,ax
sti

cld
clc

xor ah,ah
int 0x13
mov bx,0x07E0
mov es,bx
xor bx,bx
mov ah,0x2 ;function
mov al,0x5 ;sectors to read
mov ch,0x0 ;track
mov cl,0x2 ;sector
mov dh,0x0 ;head
int 0x13
;jc error
;mov ah, [0x7E00]
;cmp ah,0x0
;je error
jmp error
cli
hlt
jmp 0x07E0:0x0000

error:
    xor bx,bx
    mov ax,0xb800
    mov es,ax
    mov al,0x40 ;colour
    mov ah,' ' ;character
    .red:
        cmp bx,0x0FA0
        je .end
        mov WORD [es:bx], ax
        inc bx
        jmp .red
    .end:
        cli
        hlt

times 0x1FE - ($ - $$) db 0x0
db 0x55
db 0xAA

according to this code screen should be filled with spaces but it is not.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
vakus
  • 732
  • 1
  • 10
  • 24
  • `mov ss, src` implicitly disables interrupts until after the *next* instruction, so you don't need to `cli/sti` when setting `ss`, as long as you set `sp` with the very next insn. This also prevents your code from enabling interrupts unconditionally (e.g. if they were already disabled before the `cli`). Useful if writing a function you could call in a context with interrupts either enabled or disabled. – Peter Cordes Nov 13 '15 at 06:40
  • 1
    That is partially true @PeterCordes except for the fact that there are buggy 8086 processors (that are probably rarely encountered now unless you can find a batch from the 80s) where setting `ss` did not turn of interrupts until the end of the next instruction. This is why on bootloaders that are trying to work initially in all environments that you may see cli/sti specifically around the SS:SP change. This bug hit me in the 80s so I knew it intimately. Foot note: On the 8086/8088 (unlike the 80386) any `mov` to a segment register was suppose to turn off interrupts, not just _SS_ – Michael Petch Nov 13 '15 at 06:49
  • @MichaelPetch: huh, that sounds exciting to debug. I *hope* all 8086 cores in micro-controllers and whatnot don't have that bug. It is exactly the kind of extra special-case logic that you'd love to leave out if you're trying to make a small core, though. – Peter Cordes Nov 13 '15 at 06:52
  • 1
    @PeterCordes Chuckle, it seems thanks to Google - PC Magazine from 1987 discusses this [bug](https://books.google.ca/books?id=1L7PVOhfUIoC&pg=PA492&lpg=PA492&dq=a+serious+bug+that+should+be+attended+to&source=bl&ots=zjzOSdSv-i&sig=brN68x9WSP3g1lnO_8m57AS4sOY&hl=en&sa=X&ved=0CB0Q6AEwAGoVChMIwpmt8-eMyQIVgaWICh3mWgK-#v=onepage&q=a%20serious%20bug%20that%20should%20be%20attended%20to&f=false) . Sorry, it was 8088 chips (not the 8086). My mind can't remember every detail from 30 years ago ;). I think it's unlikely(rare) that buggy chips from a batch made in the 80s are in use, but I guess possible – Michael Petch Nov 13 '15 at 06:52
  • Minimal example from protected mode: https://github.com/cirosantilli/x86-bare-metal-examples/blob/5c672f73884a487414b3e21bd9e579c67cd77621/protected_mode.S And always search osdev.org first: http://wiki.osdev.org/Printing_To_Screen – Ciro Santilli OurBigBook.com Nov 13 '15 at 09:31
  • 1
    @CiroSantilli六四事件法轮功包卓轩 That printing to screen link isn't going to help. It is _C_ code that abstracts away the issue the user is having here. He has a minimal example that works with some minor bugs. Your link doesn't seem to address that low level issue, and doesn't even mention endianness. This is a very obvious bug that can be rectified without a bunch of links that won't help solve this fellows issue. – Michael Petch Nov 13 '15 at 10:14
  • @MichaelPetch Why is this code not working is a close reason. The osdev link mentions the point you say about using two bytes per char. A minimal working example can be used to find the error by diffing. – Ciro Santilli OurBigBook.com Nov 13 '15 at 10:16
  • @MichaelPetch if we debug every non-minimal problem, we can keep at it all day. I'd rather give the tools for OPs to do it themselves :-) – Ciro Santilli OurBigBook.com Nov 13 '15 at 10:18
  • The OP knows it is two characters, just look at the code. What isn't mentioned is that at the assembly level if you move a WORD to memory that endianness has to be accounted for. This person knew there was a character and attribute but didn't realize they would be reversed in memory with the instruction used. – Michael Petch Nov 13 '15 at 10:18
  • Personally I think you can't be bothered to actually help with some questions because you don't actually have the ability to spot the errors. So the best you can do is promote your github account. – Michael Petch Nov 13 '15 at 10:25
  • @MichaelPetch If you find another website with minimal working examples of how to do things, I will link to it instead of my GitHub. I really honestly only do it because I think once you have a minimal example, it is easy to get things working. It is really not about promoting that repo. I could make an anonymous clone of that, and link to it instead if you think more appropriate. – Ciro Santilli OurBigBook.com Nov 13 '15 at 10:36
  • @MichaelPetch about spotting errors, you are of course better than I am :-) But if I compared the OPs code to mine, I would get it working in 15 minutes. It's just about doing it faster ;-) – Ciro Santilli OurBigBook.com Nov 13 '15 at 10:37
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/95030/discussion-between-ciro-santilli----and-michael-petch). – Ciro Santilli OurBigBook.com Nov 13 '15 at 10:54

1 Answers1

13

When writing to video memory (starting @ 0xb8000) there are 2 bytes for every cell on the screen. The character to display is in the first byte, and the attribute in the second. To print out a red (color code 0x40) space (0x20) character to the first cell on the screen the bytes need to be placed in memory like this:

0xb800:0x0000 :  0x20         ; ASCII char for 0x20 is ' '
0xb800:0x0001 :  0x40         ; Red background, black foreground

In your code it seems you were trying to do this with code like:

mov al,0x40 ;colour
mov ah,' ' ;character
.red:
    cmp bx,0x0FA0
    je .end
    mov WORD [es:bx], ax
    inc bx
    jmp .red

Unfortunately because x86 architecture is little-endian, the values that get placed into memory have the least significant byte first and most significant byte last (when dealing with a 16-bit WORD). You have AX containing 0x2040 and moved the entire WORD with mov WORD [es:bx], ax to video memory. For example it would have written these bytes to the first cell:

0xb800:0x0000 :  0x40         ; ASCII char for 0x40 is `@'
0xb800:0x0001 :  0x20         ; Green background, black foreground

I believe this is a green @ but because of the second bug I will mention it may have appeared red. To fix this you need to reverse the position of the character and attribute in the AX register (swap the values in AH and AL). The code would look like this:

mov ah,0x40 ;colour is now in AH, not AL 
mov al,' '  ;character is now in AL, not AH
.red:
    cmp bx,0x0FA0
    je .end
    mov WORD [es:bx], ax
    inc bx
    jmp .red

The second bug is related to traversing the video area. Because each cell takes 2 bytes you need to increment the BX counter by 2 on each iteration. Your code does:

mov WORD [es:bx], ax
inc bx                 ; Only increments 1 byte where it should be 2 
jmp .red

Modify the code to add 2 to BX:

mov WORD [es:bx], ax
add bx,2               ; Increment 2 since each cell is char/attribute pair 
jmp .red

You could have simplified the code by using the STOSW instruction that takes the value in AX and copies it to ES:[DI]. You can prefix this instruction with REP which will repeat it CX times (it will update DI accordingly during each iteration). The code could have looked like this:

error:
    mov ax,0xb800 
    mov es,ax     ;Set video segment to 0xb800
    mov ax,0x4020 ;colour + space character(0x20)
    mov cx,2000   ;Number of cells to update 80*25=2000
    xor di,di     ;Video offset starts at 0 (upper left of screen)
    rep stosw     ;Store AX to CX # of words starting at ES:[DI]

Your code already clears the direction flag with CLD at the beginning of your code, so REP will increase DI during each iteration. Had the direction flag been set with STD, DI would have been decremented.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • @vacus: `rep stosw` would be the perfect choice for filling a chunk of memory with the same 2-byte value. – Peter Cordes Nov 13 '15 at 06:26
  • 1
    @PeterCordes Agreed, most code I see I will not optimize and will reuse what the people post. This definitely wasn't efficient code The main thing answered was the `why` the code failed the way it did. The code can definitely be reworked to take advantage of the string manipulation instructions of the x86. I'll give credit to the OP. One of the few times where the initialization code to set up the segment registers and the stack was actually correct! – Michael Petch Nov 13 '15 at 06:35
  • 1
    I usually do point out how code could be optimized. If you're not trying to tune the hell out of your code, just use a compiler and don't write asm directly in the first place. Even if you just want to understand compiler output, it helps to understand stuff like putting the conditional branch at the end of the loop. I've often had people thank me in comments for helping them "get it", so I think it's useful. – Peter Cordes Nov 13 '15 at 06:36
  • 1
    The thing about a bootloader is that generally it is the one place where you basically write it in assembly these days (unless you try to use OpenWatcom C). usually the bootloader (which this clearly is) has to fit within 512 bytes. If the OP was pressed for space then spending the time optimizing it for space (not speed) would be beneficial. Most people who write bootloaders bootstrap to code written in a higher level language once they get into protected mode. The bootloader is also one of the places where optimizing for space is far more valuable than optimizing for speed. – Michael Petch Nov 13 '15 at 06:39
  • Yeah, I agree it does make more sense to write bootloaders in asm. `rep stosw` is a good space optimization, too. Anyway, this looks like the OP was just trying to learn some asm / low-level machine stuff, and chose bare-metal as the target instead of 16bit DOS (yuck), or 64bit Linux/Windows functions. Clearly an asm novice, given the loop structure (conditional at the top, `jmp` at the bottom, even though having the loop run at least once would be a safe assumption). Anyway, nice catch figuring out the byte-order problem. – Peter Cordes Nov 13 '15 at 06:49