繁体   English   中英

如何用汇编语言修复“os.asm:113: error: TIMES value -138 is negative”

[英]How to fix "os.asm:113: error: TIMES value -138 is negative" in assembly language

我正在用汇编语言开发一个操作系统。 在某个时候我从 NASM 收到这个错误:

os.asm:113: 错误:TIMES 值 -138 为负数

我想把这个项目进行到底。 只有这样的错误让我感到绝望!

这是代码:

BITS 16

start:
    mov ax, 07C0h       ; Set up 4K stack space after this bootloader
    add ax, 288     ; (4096 + 512) / 16 bytes per paragraph
    mov ss, ax
    mov sp, 4096
    mov ax, 07C0h       ; Set data segment to where we're loaded
    mov ds, ax
    call cls
    MOV AH, 06h    ; Scroll up function
    XOR AL, AL     ; Clear entire screen
    XOR CX, CX     ; Upper left corner CH=row, CL=column
    MOV DX, 184FH  ; lower right corner DH=row, DL=column 
    MOV BH, 1Eh    ; YellowOnBlue
    INT 10H
    mov si, text_string ; Put string position into SI
    call print_string   ; Call our string-printing routine
push bx ;push registers
push cx
push dx
mov ah,0h
int 16h
       cmp al, '1'
       je reboot
       cmp al, '2'
       je shutdown
       cmp al, '3'
       je about
       cmp al, '4'
       je message
       cmp al, '5'
       je shutdown
       cmp al, '6'
       je credits

       jmp $            ; Jump here - infinite loop!


    text_string db '|Main Menu| |Smile OS V1.4|',13,10,'1) Reboot',13,10,'2) Shutdown',13,10,'3) About',13,10,'4) Message',13,10,'5) System Halt',13,10,'6) Credits',0
    about_string db '|About|',13,10,'Smile OS is a console based operating system in assembly language. 8 hours of intense work done by Alex~s Software. Many errors but solved and very successful.',13,10,'Press any key to go back!',0
    message_str db '|Message|',10,13,'Hello, World!',13,10,'Press any key to go back!',0
    cr_str db '|Credits|',13,10,'Copyright © 2018 Alex~s Software',13,10,'Main Programer: Alex',13,10,'Graphichs: What graphics?',13,10,'Idea:  nobody :)',0

reboot:
mov ax, 0
int 19h

shutdown:
mov ax, 0x1000
mov ax, ss
mov sp, 0xf000
mov ax, 0x5307
mov bx, 0x0001
mov cx, 0x0003
int 0x15

credits:
call cls
mov si, cr_str  ; Put string position into SI
call print_string   ; Call our string-printing routine
push bx ;push registers
push cx
push dx
mov ah,0h
int 16h
je start

message:
call cls
mov si, message_str ; Put string position into SI
call print_string   ; Call our string-printing routine
push bx ;push registers
push cx
push dx
mov ah,0h
int 16h
je start

cls:
  pusha
  mov ah, 0x00
  mov al, 0x03  ; text mode 80x25 16 colours
  int 0x10
  popa
  ret

about:
call cls
mov si, about_string    ; Put string position into SI
call print_string   ; Call our string-printing routine
push bx ;push registers
push cx
push dx
mov ah,0h
int 16h 
je start

print_string:           ; Routine: output string in SI to screen
    mov ah, 0Eh     ; int 10h 'print char' function

.repeat:
    lodsb           ; Get character from string
    cmp al, 0
    je .done        ; If char is zero, end of string
    int 10h         ; Otherwise, print it
    jmp .repeat

.done:
    ret     

times   512 - ($ - $$)    db  0
signature       dw      0xaa55

为什么时代价值是负数? 为什么其他人不会得到同样的错误? (或者那样)

我用这个:

NASM 版本 2.14

Oracle VM VirtualBox 版本 6.0.0_RC1

Windows 版本 0.5 的 rawwrite dd。

对于编译:

nasm os.asm -f bin -o os.bin  
dd if=/dev/zero of=os.img bs=1024 count=1440   
dd if=os.bin of=os.img

TL;DR :您的代码和数据太大,并且与文件最后 2 个字节中的启动签名冲突。 下面的代码是一个软盘引导加载程序,它读取第二阶段(您的内核)并将控制权转移给它。 提供的 BPB 用于 1.44MiB 软盘。 与引导加载程序不同,stage2 将加载到物理地址 0x07e00(在内存中的引导加载程序之后)。 这允许您的代码最大为 32.5KiB。 如果需要,您的第二阶段可以读取更多扇区。 此代码的设计目的是让其他人可以将其用作读取第二阶段并将控制权转移给它的模板。


这个问题实际上已经在您之前的Stackoverflow Question下得到了回答。 有一条关于填充使用times 512 - ($ - $$) db 0x00 to be 510 而不是 512 的警告。答案警告代码和数据过多(超过 512 字节),以及获得更好错误的方法/来自 NASM 关于大小的警告。 我的另一个答案中的注释将尺寸问题总结为:

如果文件 os.bin超过 512 字节,那么您将需要使用 BIOS 手动将更多磁盘扇区读入内存。 从软盘读取磁盘可以用INT 13h/AH=2h完成。

没有提供的是一种机制(示例),它使用 NASM 和 INT 13h/AH=2h 在物理地址 0x07E00 的引导加载程序之后立即将更多磁盘扇区(又名 stage2)读入内存。 代码被注释了,但它有效地做了:

  • 启动代码正确设置段寄存器并使用DL寄存器中 BIOS 传递的引导驱动器。 这在我的Stackoverflow General Bootloader Tips中进行了讨论
  • 堆栈位于引导加载程序下方的 0x0000:0x7c00。 将数据读入 0x7c00 到 0x7dff 以外的内存时,设置自己的堆栈很重要,因为您不知道 BIOS 在哪里设置默认堆栈 ( SS:SP )。
  • 将自身呈现为带有 BIOS 参数块的 1.44MB 软盘,使其与真实硬件上的 USB 软盘驱动器仿真引导兼容。
  • Stage2 使用从 0x07e00 开始的 INT 13h/AH=2h 一次读取一个扇区。 它支持错误重试。
  • Stage2 完成加载内核后,引导加载程序将控制权转移到 0x0000:0x7E00 ( stage2_start ) 处的 stage2 代码
  • Stage2 可以包含您希望运行的代码。 您将有 32.5KiB 的空间来测试您的代码,而不是单个引导扇区(512 字节)的限制。
  • Stage2 的磁盘扇区紧跟在磁盘映像中的引导扇区之后。
  • 您的 Stage2(内核)代码进入stage2.asm stage2.asm被组装成stage2.bin并且os.asm包含二进制文件stage2.bin以便可以确定 stage2 的大小以便引导加载程序将其加载到内存中。
  • stage2.asm必须使用ORG 0x7e00 ,因为上面的过程会将此代码加载到 0x7e00,因此必须将 ORG(原点)设置为匹配。
  • 这个bootloader会把寄存器DL中原来的boot drive number(BIOS传过来的)传给stage2运行的代码。
  • 文件stage2info.inc定义常量以确定 stage2 的原点是什么,以及在向 FAR JMP 转移控制时应该使用什么段和偏移量。 该文件的默认版本假定通过 0x0000:0x7e00 访问 stage2。 该文件的替代版本2可用于生成 0x07e0:0x0000。 后一个版本允许您的代码占用完整的 64kb 段。

代码:

英国石油公司

    jmp boot_start
    TIMES 3-($-$$) DB 0x90   ; Support 2 or 3 byte encoded JMPs before BPB.

bpb_disk_info:
    ; Dos 4.0 EBPB 1.44MB floppy
    OEMname:           db    "mkfs.fat"  ; mkfs.fat is what OEMname mkdosfs uses
    bytesPerSector:    dw    512
    sectPerCluster:    db    1
    reservedSectors:   dw    1
    numFAT:            db    2
    numRootDirEntries: dw    224
    numSectors:        dw    2880
    mediaType:         db    0xf0
    numFATsectors:     dw    9
    sectorsPerTrack:   dw    18
    numHeads:          dw    2
    numHiddenSectors:  dd    0
    numSectorsHuge:    dd    0
    driveNum:          db    0
    reserved:          db    0
    signature:         db    0x29
    volumeID:          dd    0x2d7e5a1a
    volumeLabel:       db    "NO NAME    "
    fileSysType:       db    "FAT12   "

stage2info.inc :

STAGE2_ABS_ADDR   equ 0x07e00    ; Physical address of stage2

; Segment and Offset to use to transfer (FAR JMP) control to Stage2
;     Segment:Offset = 0x0000:0x7e00
STAGE2_RUN_SEG   equ 0x0000
STAGE2_RUN_OFS   equ STAGE2_ABS_ADDR

操作系统.asm

%include "stage2info.inc"

STAGE2_LOAD_SEG  equ STAGE2_ABS_ADDR>>4
                                ; Segment to start reading Stage2 into
                                ;     right after bootloader

STAGE2_LBA_START equ 1          ; Logical Block Address(LBA) Stage2 starts on
                                ;     LBA 1 = sector after boot sector
STAGE2_LBA_END   equ STAGE2_LBA_START + NUM_STAGE2_SECTORS
                                ; Logical Block Address(LBA) Stage2 ends at
DISK_RETRIES     equ 3          ; Number of times to retry on disk error

bits 16
ORG 0x7c00

; Include a BPB (1.44MB floppy with FAT12) to be more comaptible with USB floppy media
%include "bpb.inc"

boot_start:
    xor ax, ax                  ; DS=SS=ES=0 for stage2 loading
    mov ds, ax
    mov ss, ax                  ; Stack at 0x0000:0x7c00
    mov sp, 0x7c00
    cld                         ; Set string instructions to use forward movement

    ; Read Stage2 1 sector at a time until stage2 is completely loaded
load_stage2:
    mov [bootDevice], dl        ; Save boot drive
    mov di, STAGE2_LOAD_SEG     ; DI = Current segment to read into
    mov si, STAGE2_LBA_START    ; SI = LBA that stage2 starts at
    jmp .chk_for_last_lba       ; Check to see if we are last sector in stage2

.read_sector_loop:
    mov bp, DISK_RETRIES        ; Set disk retry count

    call lba_to_chs             ; Convert current LBA to CHS
    mov es, di                  ; Set ES to current segment number to read into
    xor bx, bx                  ; Offset zero in segment

.retry:
    mov ax, 0x0201              ; Call function 0x02 of int 13h (read sectors)
                                ;     AL = 1 = Sectors to read
    int 0x13                    ; BIOS Disk interrupt call
    jc .disk_error              ; If CF set then disk error

.success:
    add di, 512>>4              ; Advance to next 512 byte segment (0x20*16=512)
    inc si                      ; Next LBA

.chk_for_last_lba:
    cmp si, STAGE2_LBA_END      ; Have we reached the last stage2 sector?
    jl .read_sector_loop        ;     If we haven't then read next sector

.stage2_loaded:
    mov ax, STAGE2_RUN_SEG      ; Set up the segments appropriate for Stage2 to run
    mov ds, ax
    mov es, ax

    ; FAR JMP to the Stage2 entry point at physical address 0x07e00
    jmp STAGE2_RUN_SEG:STAGE2_RUN_OFS

.disk_error:
    xor ah, ah                  ; Int13h/AH=0 is drive reset
    int 0x13
    dec bp                      ; Decrease retry count
    jge .retry                  ; If retry count not exceeded then try again

error_end:
    ; Unrecoverable error; print drive error; enter infinite loop
    mov si, diskErrorMsg        ; Display disk error message
    call print_string
    cli
.error_loop:
    hlt
    jmp .error_loop

; Function: print_string
;           Display a string to the console on display page 0
;
; Inputs:   SI = Offset of address to print
; Clobbers: AX, BX, SI

print_string:
    mov ah, 0x0e                ; BIOS tty Print
    xor bx, bx                  ; Set display page to 0 (BL)
    jmp .getch
.repeat:
    int 0x10                    ; print character
.getch:
    lodsb                       ; Get character from string
    test al,al                  ; Have we reached end of string?
    jnz .repeat                 ;     if not process next character
.end:
    ret

;    Function: lba_to_chs
; Description: Translate Logical block address to CHS (Cylinder, Head, Sector).
;              Works for all valid FAT12 compatible disk geometries.
;
;   Resources: http://www.ctyme.com/intr/rb-0607.htm
;              https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
;              https://stackoverflow.com/q/45434899/3857942
;              Sector    = (LBA mod SPT) + 1
;              Head      = (LBA / SPT) mod HEADS
;              Cylinder  = (LBA / SPT) / HEADS
;
;      Inputs: SI = LBA
;     Outputs: DL = Boot Drive Number
;              DH = Head
;              CH = Cylinder (lower 8 bits of 10-bit cylinder)
;              CL = Sector/Cylinder
;                   Upper 2 bits of 10-bit Cylinders in upper 2 bits of CL
;                   Sector in lower 6 bits of CL
;
;       Notes: Output registers match expectation of Int 13h/AH=2 inputs
;
lba_to_chs:
    push ax                     ; Preserve AX
    mov ax, si                  ; Copy LBA to AX
    xor dx, dx                  ; Upper 16-bit of 32-bit value set to 0 for DIV
    div word [sectorsPerTrack]  ; 32-bit by 16-bit DIV : LBA / SPT
    mov cl, dl                  ; CL = S = LBA mod SPT
    inc cl                      ; CL = S = (LBA mod SPT) + 1
    xor dx, dx                  ; Upper 16-bit of 32-bit value set to 0 for DIV
    div word [numHeads]         ; 32-bit by 16-bit DIV : (LBA / SPT) / HEADS
    mov dh, dl                  ; DH = H = (LBA / SPT) mod HEADS
    mov dl, [bootDevice]        ; boot device, not necessary to set but convenient
    mov ch, al                  ; CH = C(lower 8 bits) = (LBA / SPT) / HEADS
    shl ah, 6                   ; Store upper 2 bits of 10-bit Cylinder into
    or  cl, ah                  ;     upper 2 bits of Sector (CL)
    pop ax                      ; Restore scratch registers
    ret

; Uncomment these lines if not using a BPB (via bpb.inc)
; numHeads:        dw 2         ; 1.44MB Floppy has 2 heads & 18 sector per track
; sectorsPerTrack: dw 18

bootDevice:      db 0x00
diskErrorMsg:    db "Unrecoverable disk error!", 0

; Pad boot sector to 510 bytes and add 2 byte boot signature for 512 total bytes
TIMES 510-($-$$) db  0
dw 0xaa55

; Beginning of stage2. This is at 0x7E00 and will allow your stage2 to be 32.5KiB
; before running into problems. DL will be set to the drive number originally
; passed to us by the BIOS.

NUM_STAGE2_SECTORS equ (stage2_end-stage2_start+511) / 512
                                ; Number of 512 byte sectors stage2 uses.

stage2_start:
    ; Insert stage2 binary here. It is done this way since we
    ; can determine the size(and number of sectors) to load since
    ;     Size = stage2_end-stage2_start
    incbin "stage2.bin"

; End of stage2. Make sure this label is LAST in this file!
stage2_end:

您将所有要测试的代码放在文件stage2.asm中,该文件将包含在我的os.asm版本中。 删除了开头和结尾不必要部分的代码版本是:

stage2.asm

%include "stage2info.inc"
ORG STAGE2_RUN_OFS

BITS 16

start:
    ; Removed the segment and stack code
    call cls
    MOV AH, 06h    ; Scroll up function
    XOR AL, AL     ; Clear entire screen
    XOR CX, CX     ; Upper left corner CH=row, CL=column
    MOV DX, 184FH  ; lower right corner DH=row, DL=column
    MOV BH, 1Eh    ; YellowOnBlue
    INT 10H
    mov si, text_string ; Put string position into SI
    call print_string   ; Call our string-printing routine
push bx ;push registers
push cx
push dx
mov ah,0h
int 16h
       cmp al, '1'
       je reboot
       cmp al, '2'
       je shutdown
       cmp al, '3'
       je about
       cmp al, '4'
       je message
       cmp al, '5'
       je shutdown
       cmp al, '6'
       je credits

       jmp $            ; Jump here - infinite loop!


    text_string db '|Main Menu| |Smile OS V1.4|',13,10,'1) Reboot',13,10,'2) Shutdown',13,10,'3) About',13,10,'4) Message',13,10,'5) System Halt',13,10,'6) Credits',0
    about_string db '|About|',13,10,'Smile OS is a console based operating system in assembly language. 8 hours of intense work done by Alex~s Software. Many errors but solved and very successful.',13,10,'Press any key to go back!',0
    message_str db '|Message|',10,13,'Hello, World!',13,10,'Press any key to go back!',0
    cr_str db '|Credits|',13,10,'Copyright © 2018 Alex~s Software',13,10,'Main Programer: Alex',13,10,'Graphichs: What graphics?',13,10,'Idea:  nobody :)',0

reboot:
mov ax, 0
int 19h

shutdown:
mov ax, 0x1000
mov ax, ss
mov sp, 0xf000
mov ax, 0x5307
mov bx, 0x0001
mov cx, 0x0003
int 0x15

credits:
call cls
mov si, cr_str  ; Put string position into SI
call print_string   ; Call our string-printing routine
push bx ;push registers
push cx
push dx
mov ah,0h
int 16h
je start

message:
call cls
mov si, message_str ; Put string position into SI
call print_string   ; Call our string-printing routine
push bx ;push registers
push cx
push dx
mov ah,0h
int 16h
je start

cls:
  pusha
  mov ah, 0x00
  mov al, 0x03  ; text mode 80x25 16 colours
  int 0x10
  popa
  ret

about:
call cls
mov si, about_string    ; Put string position into SI
call print_string   ; Call our string-printing routine
push bx ;push registers
push cx
push dx
mov ah,0h
int 16h
je start

print_string:           ; Routine: output string in SI to screen
    mov ah, 0Eh     ; int 10h 'print char' function

.repeat:
    lodsb           ; Get character from string
    cmp al, 0
    je .done        ; If char is zero, end of string
    int 10h         ; Otherwise, print it
    jmp .repeat

.done:
    ret

然后使用这些命令1组装和构建磁盘映像:

# Build stage2 (kernel) FIRST as os.asm will include stage2.bin
nasm -f bin stage2.asm -o stage2.bin
# Build and combine stage1 (boot sector) and stage2 (kernel)
nasm -f bin os.asm -o os.bin

# Build 1.44MB disk image
dd if=/dev/zero of=disk.img bs=1024 count=1440
dd if=os.bin of=disk.img conv=notrunc

#开头的行只是注释,不是命令。


截图

主菜单显示为:

在此处输入图像描述

信用屏幕显示为:

在此处输入图像描述


笔记:

1您使用这些包含错误的命令:

nasm os.asm -f bin -o os.bin  
dd if=/dev/zero of=os.img bs=1024 count=1440   
dd if=os.bin of=os.img

最后一行应该是dd if=os.bin of=os.img conv=notrunc这样 1.44MB 的磁盘映像在写入os.bin文件时不会被截断。 如果您查看磁盘映像的大小,您可能会发现这不是预期的 1474560


2另一个stage2info.inc文件使用 0x07e0:0x0000 而不是 0x0000:0x7e00 将控制转移到 stage2:

STAGE2_ABS_ADDR   equ 0x07e00    ; Physical address of stage2

; Segment and Offset to use to transfer (FAR JMP) control to Stage2
;     Segment:Offset = 0x07e0:0x0000
STAGE2_RUN_SEG   equ STAGE2_ABS_ADDR>>4
STAGE2_RUN_OFS   equ 0x0000

由于您的行:

times 512 - ($ - $$) db 0

意味着用零填充剩余的 512 字节内存块,很可能你已经超过了它(大约 138 字节)。 您可能只需要缩短代码(或使其中一些字符串不那么冗长)以使其适合。

我的建议是从about_string开始,这似乎超出必要。 删除(相当自私的) " 8 hours of intense work done by Alex~s Software. Many errors but solved and very successful." 将是一个好的开始,因为它可以节省 93 个字节。 此外,以一些额外的代码字节为代价,您可以删除重复的"Press any key to go back!" (带有前导和尾随CR LF )。

这可以通过以下方式完成:

about_string db '|About|',13,10,'Smile OS is a console based operating system in assembly language.'
any_key      db 13,10,'Press any key to go back!',0
message_str db '|Message|',10,13,'Hello, World!',0

然后可以以完全相同的方式打印 about 字符串(因为about_string没有终止符0因此也将打印any_key )但是消息字符串将变为两步操作:

mov si, message_str     --> mov si, message_str
call print_string           call print_string
                            mov si, any_key
                            call print_string

这将再节省大约 20 个字节,从而为您节省 138 个字节中的大约 113 个字节。

除此之外,似乎还有一些小事情可以节省非常少量的空间,例如转换:

mov ah, 0x00
mov al, 0x03

进入:

mov ax, 0x0003

或将键输入重构为一个函数(这也将使您的堆栈保持平衡,这是您当前代码似乎没有做的事情,尽管我实际上不确定是否有必要-文档似乎表明ax是唯一受影响的寄存器,这意味着您可能会删除 pushes 和 pops):

get_kbd: push bx
         push cx
         push dx
         xor  ax,ax
         int  16h
         je   start
         pop  dx
         pop  cx
         pop  bx
         ret

当然,如果你做了所有这些但仍然不能低于阈值,则没有任何要求你将字符串放入引导代码区域。 您可以轻松地将它们存储在引导代码作为第一步加载的另一个区域。 这样,您就可以从引导代码区域中删除所有字符串,从而节省大约 460 多个字节(可能为加载字符串扇区的代码添加 20 个字节),因此远远低于阈值。

它是负数,因为510 - code_size是负数。 您的代码太大,无法作为 MBR 放在一个扇区中。

我注释掉了填充行,并组装了你的文件。 生成的二进制文件有 652 个字节长(包括填充后的 2 个字节)。 512 - 650 = -138

要么对您的程序进行编码,使其以更少的代码字节执行相同的操作( Tips for golfing in x86/x64 machine code ),要么将其分解为引导扇区,在使用 BIOS 调用引导后从磁盘加载其余代码.

对于所有这些长字符串,这里可能没有太多空间可以节省 140 个字节。 肯定有大量节省的空间,例如mov ax, 07C0h / add ax, 288mov ax, 07C0h + 288相比是愚蠢的,所以您可以轻松节省 3 个字节。

请参阅启用引导加载程序加载 USB 的第二个扇区如何加载内核或能够在自己的引导加载程序中使用更多空间?

Michael Petch 的一般引导加载程序开发技巧( 引导加载程序不会跳转到内核代码)如果您想弄乱旧版 BIOS 内容,应该会有所帮助。

您的另一个选择是编写 UEFI 引导加载程序而不是传统 BIOS,因此您的代码以 32 位或 64 位模式启动。 更重要的是,EFI“应用程序”可以是任何合理的大小,因此固件可以一次加载您的所有代码,而您不必编写代码来加载其自身的其余部分。


此外,您错误地使用了512 - size ,这不会在末尾为 MBR 签名 2 个字节留出空间。 使用510 - ($ - $$)

有关更多信息,请参见程序集为什么当我修改代码时结果变得无法启动

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM