Как добавить цифры и вывести их на консоль в загрузчике? - PullRequest
1 голос
/ 06 июня 2019

Я создаю загрузчик, который должен плюс 512 к переменной и печатать результат до достижения указанного числа. Для меня это 4194304, но проблема в том, что я действительно не понимаю, как сложить эти числа, потому что в конце я всегда получаю ничего или искаженную строку. Так, как я должен исправить числа плюс?

cpu 386
bits 16
org 0h

start:
    cld
    xor ax,ax
    mov ss,ax
    mov sp,7c00h           ; setup stack

    mov ax,8000h
    mov es,ax              ; initialize es w/ 8000h
    mov ds,ax              ; initialize ds w/ 8000h

;===============================================================================================================

load_prog:
    mov ax,0206h           ;function/# of sec to read
    mov cx,0001h           ;0-5 sec # (counts from one), 6-7 hi cyl bits

    mov dh,00h             ;dh=head dl=drive (bit 7=hdd)
    mov bx,0h              ;data buffer, points to es:0
    int 13h
    cmp ah,0
    jne load_prog          ;this is allowable because it is relative

;============================================================================================================    

next:
    mov eax, [NUMBERS]
    add eax, 512           ;I think this have to plus numbers, so result have to be 512 = 0 + 512
    mov [NUMBERS], eax     ;And this i think have to store result to NUMBERS


print_1:
    mov si, msg0
    push ax
    cld
printchar_1:
    mov al,[si]
    cmp al,0
    jz print_2
    mov ah,0x0e
    int 0x10
    inc si
    jmp printchar_1


print_2:
    mov si, [NUMBERS]
    push ax
    cld
printchar_2:
    mov al,[si]
    cmp al,0
    jz print_3
    mov ah,0x0e
    int 0x10
    inc si
    jmp printchar_2


print_3:
    mov si, msg1
    push ax
    cld
printchar_3:
    mov al,[si]
    cmp al,0
    jz next
    mov ah,0x0e
    int 0x10
    inc si
    jmp printchar_3


done:
    hlt
    jmp done

;=====================================================================================================================    

MBR_Signature:
    msg0 db 'Counted numbers ',0
    msg1 db ' of 4194304',13,10,0
    NUMBERS dd 0
    times 510-($-$$) db 0
    db 55h,0aah
    times 4096-($-$$) db 0

1 Ответ

1 голос
/ 06 июня 2019

TL; DR : Ваша основная проблема заключается в том, что при сохранении числа в памяти с помощью инструкции MOV значение не преобразуется в строку. Вы должны написать код для преобразования целых чисел в строки.


Вы можете использовать повторное деление для преобразования значения в регистре (EAX) в другую базу (База 10 для десятичных цифр). Общий алгоритм

val = number to convert
repeat 
  digit = val MOD 10     ; digit = remainder of val/10
  val   = val DIV 10     ; val = quotient of val/10 
  digit = digit + '0'    ; Convert digit to character value by adding '0'
  Store digit
until val == 0

Print digits in reverse order

Если у вас есть номер 1234:

  • 1234/10 = 123, остаток 4 (цифра)
  • 123/10 = 12 остаток 3 (цифра)
  • 12/10 = 1 остаток 2 (цифра)
  • 1/10 = 0 остаток 1 (цифра)
  • Готово

Вы заметите, что, когда мы многократно делим на 10, мы получаем цифры 4,3,2,1, которые противоположны тому, что мы хотим, это 1,2,3,4. Вы можете придумать механизм для обращения строки. Один быстрый и грязный способ состоит в том, чтобы поместить цифру в стек в обратном порядке, а затем вы можете вытолкнуть каждый из стека в правильном порядке.

Поскольку вы пытаетесь отобразить 32-разрядные числа без знака, вам нужно разделить на val в EAX. 64-разрядное деление выполняется со значением в EDX: EAX (где EDX установлен в 0) на 10. Инструкция x86 DIV вычисляет частное (возвращается в EAX) и остаток (возвращается в EDX).

Я рекомендую переместить часто используемый код в функции, чтобы уменьшить количество повторений, упростить разработку и упростить поддержку кода

Создайте функцию uint32_to_str, которая использует повторное деление на 10, сохраняя цифры ASCII в стеке по мере их вычисления. В конце ASCII-цифры извлекаются из стека и сохраняются в буфере, передаваемом функции. Это работает аналогично функции itoa в том, что число всегда записывается в начале буфера. По окончании буфер NUL (0) завершается. Прототип функции может выглядеть так:

; uint32_to_str
;
; Parameters:
;     EAX   = 32-bit unsigned value to print
;     ES:DI = buffer to store NUL terminated ASCII string
;
; Returns:
;     None
;
; Clobbered:
;     None

Ваш код также печатает строки. создать функцию print_str с прототипом:

; print_str
;
; Parameters:
;     DS:SI = NUL terminated ASCII string to print
;
; Returns:
;     None
;
; Clobbered:
;     None

Это только примерные прототипы. Вы можете передать значение и адреса в любой регистр, который вы выберете. Вы также можете решить, возвращают ли ваши функции значение и какие регистры засорены. В этом коде я сохраняю все используемые регистры. Вы можете сохранить некоторые или все из них, на ваше усмотрение.

Ваш загрузчик может выглядеть примерно так:

cpu 386
bits 16
org 0h

start:
    cld
    xor ax,ax
    mov ss,ax
    mov sp,7c00h               ; setup stack

    mov ax,8000h
    mov es,ax                  ; initialize es w/ 8000h
    mov ds,ax                  ; initialize ds w/ 8000h

;=================================================================================

load_prog:
    mov ax,0206h               ; function/# of sec to read
    mov cx,0001h               ; 0-5 sec # (counts from one), 6-7 hi cyl bits

    mov dh,00h                 ; dh=head dl=drive (bit 7=hdd)
    mov bx,0h                  ; data buffer, points to es:0
    int 13h
    cmp ah,0
    jne load_prog              ; this is allowable because it is relative

;=================================================================================

    mov eax, [NUMBERS]
next:
    add eax, 512               ; Advance value by 512

    mov si, msg0
    call print_str

    mov di, strbuf             ; ES:DI points to string buffer to store to
    call uint32_to_str         ; Convert 32-bit unsigned value in EAX to ASCII string

    mov si, di                 ; DS:SI points to string buffer to print
    call print_str

    mov si, msg1
    call print_str

    cmp eax, 1024*4096         ; End loop at 4194304 (1024*4096)
    jl next                    ; Continue until we reach limit

    mov [NUMBERS], eax         ; Store final value in NUMBERS

done:
    hlt
    jmp done


; print_str
;
; Parameters:
;     DS:SI = NUL terminated ASCII string to print
;
; Returns:
;     None
;
; Clobbered:
;     None

print_str:
    push ax
    push di

    mov ah,0x0e
.getchar:
    lodsb                      ; Same as mov al,[si] and inc si
    test al, al                ; Same as cmp al,0
    jz .end
    int 0x10
    jmp .getchar
.end:

    pop di
    pop ax
    ret

; uint32_to_str
;
; Parameters:
;     EAX   = 32-bit unsigned value to print
;     ES:DI = buffer to store NUL terminated ASCII string
;
; Returns:
;     None
;
; Clobbered:
;     None

uint32_to_str:
    push edx
    push eax
    push ecx
    push bx
    push di

    xor bx, bx                 ; Digit count
    mov ecx, 10                ; Divisor

.digloop:
    xor edx, edx               ; Division will use 64-bit dividend in EDX:EAX
    div ecx                    ; Divide EDX:EAX by 10
                               ;     EAX=Quotient
                               ;     EDX=Remainder(the current digit)
    add dl, '0'                ; Convert digit to ASCII
    push dx                    ; Push on stack so digits can be popped off in
                               ;     reverse order when finished

    inc bx                     ; Digit count += 1
    test eax, eax
    jnz .digloop               ; If dividend is zero then we are finished
                               ;     converting the number

    ; Get digits from stack in reverse order we pushed them
.popdigloop:
    pop ax
    stosb                      ; Same as mov [ES:DI], al and inc di
    dec bx
    jne .popdigloop            ; Loop until all digits have been popped

    mov al, 0
    stosb                      ; NUL terminate string
                               ; Same as mov [ES:DI], al and inc di

    pop di
    pop bx
    pop ecx
    pop eax
    pop edx
    ret
    ;================================================================================

    NUMBERS dd 0
    msg0    db 'Counted numbers ',0
    msg1    db ' of 4194304',13,10,0

    ; String buffer to hold ASCII string of 32-bit unsigned number
    strbuf times 11 db 0

    times 510-($-$$) db 0
MBR_Signature:
    db 55h,0aah
    times 4096-($-$$) db 0

Альтернативные версии функций

Я бы обычно использовал код, который переходит в середину цикла, чтобы позволить условие выхода (символ равен нулю) выполняться в конце, а не в середине. Это позволяет избежать необходимости выполнять безусловную инструкцию JMP в конце:

; print_str
;
; Parameters:
;     DS:SI = NUL terminated ASCII string to print
;
; Returns:
;     None
;
; Clobbered:
;     None

print_str:
    push ax
    push di

    mov ah,0x0e
    jmp .getchar               ; Start by getting next character
.printchar:
    int 0x10
.getchar:
    lodsb                      ; Same as mov al,[si] and inc si
    test al, al                ; Is it NUL terminator?
    jnz .printchar             ; If not print character and repeat

    pop di
    pop ax
    ret

Оригинальный uint32_to_str был разработан, чтобы всегда возвращать строку, начинающуюся с начала пройденного буфера. Это поведение аналогично нестандартной функции C itoa, где адрес переданного буфера совпадает с адресом, возвращаемым функцией.

Можно значительно упростить код, удалив нажатия и всплывающие окна, используемые для переворачивания строки. Это можно сделать, записав цифры ASCII, начиная с позиции в буфере вывода, где появится терминатор NUL. Цифры ASCII вставляются в буфер от конца строки к началу по мере их вычисления. Адрес, возвращаемый функцией, может быть в середине переданного буфера. Начало строки цифр возвращается вызывающей стороне через регистр DI в этом коде:

; uint32_to_str
;
; Parameters:
;     EAX   = 32-bit unsigned value to print.
;     ES:DI = buffer to store NUL terminated ASCII string.
;             buffer must be at a minimum 11 bytes in length to
;             hold the largest unsigned decimal number that
;             can be represented in 32-bits including a 
;             NUL terminator.
; Returns:
;     ES:DI   Points to beginning of buffer where the string starts.
;             This may not be the same address that was passed as a
;             parameter in DI initially. DI may point to a position in
;             in the middle of the buffer.
;
; Clobbered:
;     None

uint32_to_str:
    MAX_OUT_DIGITS equ 10      ; Largest unsigned int represented in 32-bits is 10 bytes

    push edx
    push eax
    push ecx

    mov ecx, 10                ; Divisor
    add di, MAX_OUT_DIGITS     ; Start at a point in the buffer we
                               ;     can move backwards from that can handle
                               ;     a 10 digit number and NUL terminator
    mov byte [es:di], 0        ; NUL terminate string

.digloop:
    xor edx, edx               ; Division will use 64-bit dividend in EDX:EAX
    div ecx                    ; Divide EDX:EAX by 10
                               ;     EAX=Quotient
                               ;     EDX=Remainder(the current digit)
    add dl, '0'                ; Convert digit to ASCII
    dec di                     ; Move to previous position in buffer
    mov [es:di], dl            ; Store the digit in the buffer

    test eax, eax
    jnz .digloop               ; If dividend is zero then we are finished
                               ;     converting the number

    pop ecx
    pop eax
    pop edx
    ret

Сноска

  • Я не уверен, почему вы считываете загрузочный сектор и дополнительные сектора в память по адресу 0x0000: 0x8000, но я сохранил этот код как есть. Этот код работает, но я не уверен, почему вы это делаете.
  • Так как вы использовали директиву CPU 386 и использовали 32-битный регистр EAX Я создал код для использования 32-битных регистров, когда это необходимо, но использовал 16-битные регистры в противном случае.Это сокращает ненужные префиксы команд, которые раздувают код.Этот код будет работать в реальном режиме только в системе с процессором 386+.Вы можете выполнить 32-битное деление, используя 16-битные регистры, но это более сложно и выходит за рамки этого ответа.
...