如何在引导程序中添加数字并将其显示在控制台中? [英] How to add numbers and display them to the console in a bootloader?

查看:103
本文介绍了如何在引导程序中添加数字并将其显示在控制台中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在创建引导加载程序,该引导加载程序应在变量前加上512并打印结果,直到达到指定的数量为止.对我来说,它是4194304,但是问题是我真的不明白如何加上这些数字,因为最后我总是什么也没得到或损坏的字符串.那么我应该如何加上正确的数字呢?

I'm creating bootloader that should plus 512 to variable and print result until reaching the specified number. For me, it is 4194304, but the problem is that I really don't understand how to plus these numbers, because at the end I always get nothing or corrupted string. So how should I plus numbers correct?

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

推荐答案

TL; DR :看来您的主要问题是使用MOV指令将数字存储到内存不会转换值转换为字符串.您必须编写代码将整数转换为字符串.

TL;DR : It appears your main problem is that storing a number to memory using MOV instruction doesn't convert the value to a string. You must write the code to convert integers to strings.

您可以使用重复除法将寄存器(EAX)中的值转换为其他基数(十进制数字为10).通用算法是

You can use repeated division to convert a value in a register (EAX) to a different base (Base 10 for decimal digits). The general algorithm is

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

如果您的电话号码是1234:

If you have the number 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相反.您可以想出一种处理字符串反转的机制.一种快速而肮脏的方法是按相反的顺序将数字推入堆栈,然后可以按正确的顺序将每个数字从堆栈弹出.您可以将每个数字以相反的顺序存储在缓冲区中.

You'll observe that as we repeatedly divide by 10 we get the digits 4,3,2,1 which is reverse of what we want which is 1,2,3,4. You can come up with a mechanism to deal with reversing the string. One quick and dirty way is to push the digit on the stack in the reversed order and then you can pop each one back off the stack in the correct order. You could store each digit in a buffer in reverse order.

由于要显示32位无符号数字,因此需要在EAX中用val除.用EDX:EAX(其中EDX设置为0)中的值乘以10完成64位除法.x86指令

Since you are trying to display 32-bit unsigned numbers you will need to division with val in EAX. 64-bit division is done with value in EDX:EAX (where EDX is set to 0) by 10. The x86 instruction DIV computes quotient (returned in EAX) and remainder (returned in EDX).

我建议将常用代码移入函数中,以减少重复,简化开发并使代码更易于维护

I recommend moving commonly used code into functions to reduce repetition, simplify development, and make the code easier to maintain

创建一个函数uint32_to_str,该函数使用重复的除以10的值将ASCII数字在计算时存储在堆栈中.最后,将ASCII数字从堆栈中弹出,并存储到传递给该函数的缓冲区中.此功能与itoa函数相似,因为该数字始终写在缓冲区的开头.完成后,缓冲区以NUL(0)终止.函数原型可能如下:

Create a function uint32_to_str that uses repeated division by 10 storing the ASCII digits on the stack as they are computed. At the end the ASCII digits are popped of the stack and stored into a buffer passed to the function. This works similar to the itoa function in that the number is always written at the start of the buffer. When finished the buffer is NUL(0) terminated. The function prototype could look like:

; 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函数:

Your code also prints strings. create a print_str function with a prototype:

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

这些只是示例原型.您可以选择在选择的任何寄存器中传递值和地址.您还可以决定函数是否返回值以及哪些寄存器被破坏.在这段代码中,我保留了所有使用的寄存器.您可以选择保留部分或全部这些,这取决于您自己.

These are just example prototypes. You can choose to pass value and addresses in whichever registers you choose. You can also decide if your functions return a value and which registers are clobbered. In this code I preserve all the registers that are used. You can choose to preserve some or all of them, that is up to you.

然后您的引导程序可能看起来像:

Your bootloader could then look something like:

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指令:


Alternative Versions of the Functions

I would generally use code that jumps into the middle of the loop to allow the exit condition (character being zero) to be done at the end rather than the middle. This avoids having to do the unconditional JMP instruction at the end:

; 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 ,其中传递的缓冲区的地址与函数返回的地址相同.

The original uint32_to_str was designed to always return the string starting at the beginning of the buffer passed. This is similar behaviour to C's nonstandard function itoa where the address of the buffer passed is the same address returned by the function.

通过消除用于反转字符串的推动和弹出,可以大大简化代码.可以通过在输出缓冲区中将出现NUL终止符的位置开始写入ASCII数字来完成此操作. ASCII数字在计算时从字符串的结尾到开头插入到缓冲区中.从函数返回的地址可能在传递的缓冲区的中间.通过以下代码中的 DI 寄存器,将数字字符串的开头返回给调用方:

One can dramatically simplify the code by removing the pushes and pops used to reverse the string. This can be done by writing the ASCII digits starting at a position in the output buffer where the NUL terminator will appear. The ASCII digits are inserted into the buffer from the end of the string towards the beginning as they are computed. The address returned from the function may be in the middle of the buffer passed. The start of the string of digits is returned back to the caller via the DI register in this code:

; 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+处理器的系统上以实模式运行.您可以使用16位寄存器进行32位除法,但这更加复杂,超出了此答案的范围.
  • I'm unsure why you read the boot sector and extra sectors into memory at 0x0000:0x8000, but I have kept that code as is. That code works but I'm unsure why you are doing it.
  • Since you used the directive CPU 386 and were using 32-bit register EAX I created the code to use 32-bit registers when needed but used 16-bit registers otherwise. This cuts down on unnecessary instruction prefixes that bloat the code. This code will run in real-mode only on a system with a 386+ processor as a result. You can do 32-bit division using 16-bit registers but it is more complex and beyond the scope of this answer.

这篇关于如何在引导程序中添加数字并将其显示在控制台中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆