Forneci 2 implementações diferentes de segmentos de comutação ao calcular o comprimento da string. Não entendo por que estamos comutando o segmento extra e o segmento de dados ou, mais precisamente, copiando o conteúdo do segmento de dados (registro DS) para o segmento extra (registro ES). Se eu remover as linhas push ds
e pop es
o resultado não mudar, mas quando eu substituir les di, [bp+4]
pela mov di, [bp+4]
mensagem é exibida na tela, mas com um monte de outros caracteres diferentes.
Por que isso está acontecendo? Sou novo em assembly.
org 0x0100
jmp start
message: db 'Hello world!', 0 ; First string to be concatenated
; Subroutine to calculate the length of a string
; Takes the segment and offset of a string as parameters
strlen:
push bp
mov bp, sp
push es
push cx
push di
les di, [bp+4] ; Point es:di to string
mov cx, 0xffff ; Load maximum number in cx
xor al, al ; Load a zero in al
repne scasb ; Find zero in the string
mov ax, 0xffff ; Load maximum number in ax
sub ax, cx ; Find change in cx
dec ax ; Exclude null from length
pop di
pop cx
pop es
pop bp
ret 4
; Subroutine to print a string
; Takes the x position, y position, attribute, and address of a null-terminated string as parameters
printstr:
push bp
mov bp, sp
pusha
push di
push ds ; Push segment of string
mov ax, [bp+4]
push ax ; Push offset of string
call strlen ; Calculate string length
cmp ax, 0 ; Is the string empty?
jz exit ; No printing if string is empty
mov cx, ax ; Save length in cx
mov ax, 0xb800 ; Video memory base address
mov es, ax ; Point es to video base
mov al, 80 ; Load al with columns per row
mul byte [bp+8] ; Multiply with y position
add ax, [bp+10] ; Add x position
shl ax, 1 ; Turn into byte offset
mov di, ax ; Point di to required location
mov si, [bp+4] ; Point si to string
mov ah, [bp+6] ; Load attribute in ah
cld ; Clear direction flag for auto-increment mode
nextchar:
lodsb ; Load next char in al
stosw ; Print char/attribute pair
loop nextchar ; Repeat for the whole string
exit:
pop ds
pop di
popa
pop bp
ret 8
start:
mov ax, 30
push ax ; Push x position
mov ax, 20
push ax ; Push y position
mov ax, 0x7 ; Blue on white attribute
push ax ; Push attribute
mov ax, message
push ax ; Push address of message
call printstr ; Call the printstr subroutine
mov ax, 0x4c00 ; Terminate program
int 0x21
Versão alternativa:
push bp
mov bp, sp
pusha
push ds
pop es ; load ds in es
mov di, [bp+4] ; point di to string
mov cx, 0xffff ; load maximum number in cx
xor al, al
repne scasb
mov ax, 0xffff
sub ax, cx
dec ax
jz done
mov cx, ax
...
A chave aqui é que os registradores de segmento no seu programa são todos iguais entre si desde o início. Isso porque você está criando um programa .COM (
ORG 0x100
faz isso). Toda essa mudança de DS ou ES não conta realmente, nem é necessária neste caso.A configuração do registrador de segmento ES é necessária para usar a
scasb
instrução que sempre depende de ES:DI.Agora, há uma incompatibilidade importante na maneira como você retorna da sub-rotina strlen (
ret 4
) e na maneira como você sai da sub-rotina printstr (exit:
pop ds
).ret 4
(o que descarta tanto o deslocamento quanto as partes do segmento do ponteiro distante), será necessário removê-lopop ds
.pop ds
, precisará escreverret 2
(isso descarta apenas a parte deslocada do ponteiro distante).Dica: printstr altera ES diferente de DS (definindo-o como 0xB800). Lá você normalmente preservaria ES na pilha.