On the way to protected or long modes, real mode code needs to provide a lot of visual feedback. Video services INT 10H provide this functionality, but lack fluidity. This algorithm provides write character and attribute AH = 9 and write string AH = 13H functionality, reading NULL terminated strings.


;    BH = Video page
;    BL = Attribute 
;    CX = Repeat count or NULL to read string to EOS
;    DH = Row    (0 - 24)
;    DL = Column (0 - 79)
; DS:SI = Pointer to ASCIIZ string or character to be repeated CX times


;    CX = Number of characters displayed
; ES:SI = Points to the next position after null. Might be next string

To achieve a result like this:

enter image description here

The string would be defined as:

Title: db 'Proto_Sys 1.00.0', 0, 0xc4

and called by:

mov     si, Title
mov     dx, 0x11e       Line 2, Column 32 (values zero indexed)
mov     bx, 13          Light Magenta
xor     cx, cx
call    528H
mov     cl, 18          Repeat 18 times
mov     bl, 15          Bright White
add     dx, 255         Row 3, Column 30
call    528H

NOTE: As this is in a boot loader, there is a lot of code before this that initializes pertinent registers.

Begin with setting ES:DI to point to proper place in video memory B8000 - BFFFF:

528  31C0      xor  ax, ax
52A  88FC      mov  ah, bh
52C  80C4B8    add  ah, VIDEO_SEG
52F  06        push     es
530  8EC0      mov  es, ax
532  88D4      mov  ah, dl
534  C1E807    shr  ax, 7
537  57        push     di
538  89C7      mov  di, ax
53A  B0A0      mov  al, 0xa0
53C  52        push     dx
53D  C1EA08    shr  dx, 8
540  F7EA      imul     dx
542  5A        pop  dx
543  01C7      add  di, ax

Read the first character and then determine if this character is to be repeated CX times or the first of a NULL-terminated string:

547  AC        lodsb
548  09C9      or   cx, cx
54A  7407      jz   553H

Do repeat process and return to caller:

54C  51        push     cx
54D  F3AB      rep  stosw
54F  59        pop  cx

; see comments restored out of order
; -------------------------------
; 550  07        pop    es   [di] 5F
; 551  5F        pop    di   [es] 07
; -------------------------------

552  C3        ret

Keep reading until null and update CL as it returns the length of the string:

553  08C0      or     al, al
555  74F9      jz     550H
557  FEC1      inc    cl
559  AB        stosw
55A  AC        lodsb
55B  EBF6      jmp     553H
  • 2
    \$\begingroup\$ Is it my imagination, or are es/di popped out of order? \$\endgroup\$ – David Wohlferd Jun 28 '17 at 9:04
  • \$\begingroup\$ What's the minimum CPU you're targeting? \$\endgroup\$ – Adriano Repetti Jun 28 '17 at 10:54
  • \$\begingroup\$ @DavidWohlferd Good eye David and because those two registers are inconsequential at this point in development, the consequences weren't evident. This routine so far has been called 7 times and every second time, ES & DI were the proper values again. \$\endgroup\$ – Shift_Left Jun 28 '17 at 12:57
  • \$\begingroup\$ @AdrianoRepetti I still have a 386 with two 5 1/2" drives, but probably post Pentium and nothing newer than the Athlon II X4 640 system that the OS is being developed on. \$\endgroup\$ – Shift_Left Jun 28 '17 at 13:12
  • \$\begingroup\$ I don't have an environment to test this, but does setting the page to 1 work correctly? It doesn't seem like add ah, VIDEO_SEG is the right way to compute this offset. \$\endgroup\$ – David Wohlferd Jun 28 '17 at 22:01

Some corrections

  • The LEAVE comments mention ES:SI where you intended DS:SI
  • If the comment for mov dx, 0x11e talks about "Line 2", then it should also say "Column 31" (in stead of "32")

add     dx, 255         Row 3, Column 30

This is a very nice approach! Although for reviewing purposes I had to read it a few times over, it is definitely better here than the simpler mov dx, 0x021d would have been.


  • When registers need to be preserved, it will be better to group the pushes and pops together. You've put them amongst the rest of the instructions and maybe because of this introduced the error of inverse restoration. A trick I've been using for many years now is numbering my push and pop instructions (see below). This facilitates enormously discovering these errors.

    push    es            ;(1)
    push    di            ;(2)
  • The calculation of the ES segment register can be shorter and clearer.

    mov     ax, VIDEO_SEG ;0xB800
    add     ah, bh        ;BH=Display page
    mov     es, ax
  • The calculation for the video address in DI can be optimized a lot. Next code does it in 14 bytes, where your code used 18 bytes. Also it uses only half the instructions.

    movzx   ax, dh        ;DH=Row
    imul    ax, 80        ;80 characters per row
    add     al, dl        ;DL=Column
    adc     ah, 0         ;(*)
    imul    di, ax, 2     ;Double for character+attribute AND bring in DI

    (*) If you know that the counter in CX will always be reasonably small, then maybe write adc ah,ch here and shave off another byte.

  • In order to decide what the function should do you test the CX register using or cx, cx jz 553H. This can easily be shortened to jcxz 553H. A 2 byte gain.

  • Incrementing the resulting counter in CX will be shorter using inc cx because that's a 1-byte instruction.

  • The loop currently uses 2 jump instructions per iteration. This is wasteful in terms of speed and can easily be avoided as you'll see in below summary.

In summary

  push    es            ;(1)
  push    di            ;(2)
  mov     ax, VIDEO_SEG ;0xB800
  add     ah, bh        ;BH=Display page
  mov     es, ax
  movzx   ax, dh        ;DH=Row
  imul    ax, 80        ;80 characters per row
  add     al, dl        ;DL=Column
  adc     ah, 0         ;(*) Possibly use "adc ah, ch" with CH=0
  imul    di, ax, 2     ;Double for character+attribute AND bring in DI
  jcxz    Test          ;Till end of string marker
  push    cx            ;(3)
  rep     stosw
  pop     cx            ;(3)
  jmp     Exit
  inc     cx
  test    al, al
  jnz     String
  pop     di            ;(2)
  pop     es            ;(1)
  • 1
    \$\begingroup\$ I really like the idea of numbering stack usage as it's immediately identifiable. Won't work for me every time, as there are instances where I'll drop out of a routine and let that process cleanup the stack and other times I'll just let LEAVE clean everything up. \$\endgroup\$ – Shift_Left Jul 2 '17 at 20:17
  • 1
    \$\begingroup\$ Might have to spend a bit of time again, reviewing instruction set as I do miss monopolizing on instructions like jcxz. Your input is informative and I find myself thinking more than I'd like to admit, why did I miss that, as your points aren't foreign, just didn't stop to think about them. \$\endgroup\$ – Shift_Left Jul 2 '17 at 20:21
  • \$\begingroup\$ On the use of jcxz and friends. Don't worry, I spend years developing my own Assembler and Basic compilers and had to squeeze as much as possible in a 64KB segment. That's where you really learn to use these instructions. -;) \$\endgroup\$ – Sep Roland Jul 2 '17 at 20:46

See this post for details why this listings is at 628H rather than 528H, otherwise, I've adopted Sep Roland's [18% tighter] code unchanged except for instruction @ 63F. Got some interesting results without attribute being moved from BL.

628  06                push     es
629  57                push     di
62A  B800B8            mov      ax, 0xb800
62D  00FC              add      ah, bh
62F  8EC0              mov      es, ax
631  0FB6C6            movzx    ax, dh
634  6BC050            imul     ax, ax,0x50
637  00D0              add      al, dl
639  80D400            adc      ah, 0x0
63C  6BF802            imul     di, ax,0x2
63F  88DC              mov      ah, bl
641  AC                lodsb
642  E309              jcxz     64D
644  51                push     cx
645  F3AB              rep      stosw
647  59                pop      cx
648  EB07              jmp      651
64A  41                inc      cx
64B  AB                stosw
64C  AC                lodsb
64D  84C0              test     al, al
64F  75F9              jnz      64A
651  5F                pop      di
652  07                pop      es
653  C3                ret

The instruction @ 63C was of particular interest. In general though, the entire algorithm resonates efficiency. I'm not exactly sure, but I think this method speaks volumes as to branch predictability also.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.