1) The BX register does not hold the character you want to print but holds the
memory address of where the character you want to print can be found (@HELLO_MSG).
Thus what you really want to say is not "mov al,bl" but "mov al,[bx]", which is to say
"Set AL to The byte stored at memory address BX" (mov AL,[BX])
2) The BX register is used by the BIOS for the colour component it teletypes with. You cant use that register as the index to the memory at HELLO_MSG. Try using the SI register instead - "mov al,[SI]".
3) With each loop you need the program to move to the next character in the string so that it both prints the next character and compared the next character to "0", so you need to increment your indexing register (SI as I suggest in point 2). So you need to put an "inc si" instruction somewhere after the setting of al and before the jne jump.
4) You are also misusing the stack. A pusha instruction will push all the registers onto the stack and they are put on the stack in a specific order. The best way to retrieve back from a pusha is a popa. If you pop bx, you will infact pop what was orignially on ax onto the stack and the stack pointer will be out of sync by about 10 bytes or so. I wouldnt even use the stack.
This is the way I would do what you are trying to do:
pusha ; save all the registers to the stack
mov si, HELLO_MSG ; set SI to to the address of the message
printstr:
mov ah, 0x0e ; BIOS teletype output
mov bh, 00h ; teletyping to the default page at B800h (IBM PC)
mov bl, 07h ; the CGA colour white
mov al,[si] ; put byte of memory address si into al
int 0x10 ; print it.
inc si
cmp al, 0 ; Compare if its a string termination character?
jne printstr ; if not go back to top
popa ; retrieve all the registers from the stack
ret ; return to the caller?
; Data
HELLO_MSG:
db 'Hello world!', 0
Note that I use pusha and popa just to demonstrate their use. Since I only ax,bx and si, it would be cheaper to do three pushes of these registers and three pops at the end if it was indeed even neccessary to save these registers in the context of the full program. I try to write so that registers can be immediately dumped unless saving their contents are an unavoidable neccessity.