问题
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
回答1:
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.
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
If you have the number 1234:
- 1234/10=123 remainder 4 (digit)
- 123/10=12 remainder 3 (digit)
- 12/10=1 remainder 2 (digit)
- 1/10=0 remainder 1 (digit)
- Done
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.
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
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
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
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
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.
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
Footnotes
- 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.
来源:https://stackoverflow.com/questions/56480273/how-to-add-numbers-and-display-them-to-the-console-in-a-bootloader