Я предполагаю, что ваш DS не указывает на ваш сегмент данных.
Int21 Функция 0x09 берет строку из DS: DX.
Помните, что DX является только 16-битным регистром. Для доступа к данным за пределами 16-битного диапазона вы должны использовать регистры сегментов. Они называются DS и ES для данных, CS для кода и SS для стека (есть также FS и GS на i386).
Точный адрес, с которого вы загружаете, задается как 16 * сегмент_регистр + смещение_регистр. Int21 не может угадать, где находится ваш DS, поэтому вы должны загрузить его до вызова прерывания.
Полагаю, вы никогда не инициализировали свой регистр DS, поэтому он, скорее всего, указывает на код, а не на сегмент данных.
Попробуйте заменить свой
MOV DX, offset MSG
по:
LDS DX, MSG ; Check that, it's been ages since I've written 16 bit code.
К сожалению, прошли годы с тех пор, как я последний раз играл с 16-битным ассемблером, поэтому я не могу это проверить, но LDS должен сделать свое дело.
Вы также можете косвенно загрузить DS при запуске вашей программы примерно так:
MOV AX, SEG DATA ; check that - can be SEGMENT or so as well.
MOV DS, AX