通过 USB 驱动器启动的自定义引导加载程序在某些计算机上会产生不正确的输出

发布于 2025-01-19 19:51:04 字数 2452 浏览 1 评论 0原文

我是组装的新手,但我正在尝试深入低水平计算的世界。我正在尝试学习如何编写将以引导加载程序代码运行的汇编代码;因此与Linux或Windows(例如Linux或Windows)无关。阅读此页面和其他一些x86指令集的列表,我提出了一些应该在屏幕上打印10 A的汇编代码,然后是1B。

      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

    mov cl, 10          ; Use this register as our loop counter
    mov ah, 0Eh         ; This register holds our BIOS instruction

.repeat:
    mov al, 41h         ; Put ASCII 'A' into this register
    int 10h             ; Execute our BIOS print instruction
    cmp cl, 0           ; Find out if we've reached the end of our loop
    dec cl              ; Decrement our loop counter
    jnz .repeat         ; Jump back to the beginning of our loop
    jmp .done           ; Finish the program when our loop is done

.done:
    mov al, 42h         ; Put ASCII 'B' into this register
    int 10h             ; Execute BIOS print instruction
    ret


times 510-($-$$) db 0   ; Pad remainder of boot sector with 0s
dw 0xAA55

因此,输出应该看起来像这样:

AAAAAAAAAAB

我使用在Windows 10 Ubuntu Bash程序上运行的NASM汇编程序组装了代码。生成.bin文件后,我使用十六进制编辑器将其打开。我使用相同的HEX编辑器将该.bin文件的内容复制到闪存驱动器的前512个字节中。将程序写入Flash Drive后,我将其断开并将其插入具有Intel Core i3-7100的计算机中。在启动时,我选择了USB Flash Drive作为引导设备,只是为了获得以下输出:

A

更改程序中的各种内容后,我终于感到沮丧,并在另一台计算机上尝试了程序。另一台计算机是带有i5-2520m的笔记本电脑。我遵循与以前提到的相同过程。果然,它给了我预期的输出:

AAAAAAAAAAB

我立即使用i3在原始计算机上尝试了它,但它仍然没有用。

所以我的问题是:为什么我的程序可以与一个X86处理器一起使用而不是另一个处理器?他们都支持X86指令集。什么给?


解决方案:
好的,我能够在一些帮助下追踪真正的解决方案。如果您在下面阅读Michael Petch的答案,您会找到一种解决我的问题的解决方案,以及BIOS寻找BPB的另一个问题。

这是我的代码的问题:我正在将程序写入闪存驱动器的第一个字节。这些字节被加载到内存中,但是一些BIOS中断为自己使用这些字节。因此,我的程序被BIOS覆盖。为了防止这种情况,您可以添加BPB说明,如下所示。如果您的BIOS以我的方式工作,它将仅在内存中覆盖BPB,而不是您的程序。另外,您可以将以下代码添加到程序的顶部:

jmp start
resb 0x50

start: 
;enter code here

此代码(由Ross Ridge提供)将您的程序将您的程序推向内存位置0x50(从0x7C00偏移),以防止执行过程中BIOS覆盖它。

还要记住,每当您致电任何子例程时,您使用的寄存器的值都可能被覆盖。确保您要么使用pushpop,要么在调用子例程之前将值保存到内存中。看看下面的马丁·罗塞瑙(Martin Rosenau)的答案,以了解有关此的更多信息。

感谢所有回答我的问题的人。现在,我对这种低级东西的工作原理有了更好的了解。

I am fairly new to assembly, but I'm trying to dive into the world of low level computing. I'm trying to learn how to write assembly code that would run as bootloader code; so independent of any other OS like Linux or Windows. After reading this page and a few other lists of x86 instruction sets, I came up with some assembly code that is supposed to print 10 A's on the screen and then 1 B.

      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

    mov cl, 10          ; Use this register as our loop counter
    mov ah, 0Eh         ; This register holds our BIOS instruction

.repeat:
    mov al, 41h         ; Put ASCII 'A' into this register
    int 10h             ; Execute our BIOS print instruction
    cmp cl, 0           ; Find out if we've reached the end of our loop
    dec cl              ; Decrement our loop counter
    jnz .repeat         ; Jump back to the beginning of our loop
    jmp .done           ; Finish the program when our loop is done

.done:
    mov al, 42h         ; Put ASCII 'B' into this register
    int 10h             ; Execute BIOS print instruction
    ret


times 510-($-$) db 0   ; Pad remainder of boot sector with 0s
dw 0xAA55

So the output should look like this:

AAAAAAAAAAB

I assembled the code using the nasm assembler running on the Windows 10 Ubuntu Bash program. After it produced the .bin file, I opened it using a hex editor. I used the same hex editor to copy the contents of that .bin file into the first 512 bytes of a flash drive. Once I had written my program to the flash drive, I disconnected it and plugged it into a computer with an Intel Core i3-7100. On bootup, I selected my USB flash drive as the boot device, only to get the following output:

A

After changing various things in the program, I finally got frustrated and tried the program on another computer. The other computer was a laptop with an i5-2520m. I followed the same process as I mentioned before. Sure enough, it gave me the expected output:

AAAAAAAAAAB

I immediately tried it on my original computer with the i3, but it still didn't work.

So my question is: Why does my program work with one x86 processor but not the other? They both support the x86 instruction set. What gives?


Solution:
Ok, I've been able to track down the real solution with some help. If you read Michael Petch's answer below, you'll find a solution that will fix my problem, and another problem of a BIOS looking for a BPB.

Here was the problem with my code: I was writing the program to the first bytes of my flash drive. Those bytes were loaded into memory, but some BIOS interrupts were using those bytes for itself. So my program was being overwritten by the BIOS. To prevent this, you can add a BPB description as shown below. If your BIOS works the same way mine does, it will simply overwrite the BPB in memory, but not your program. Alternatively, you can add the following code to the top of your program:

jmp start
resb 0x50

start: 
;enter code here

This code (courtesy of Ross Ridge) will push your program to memory location 0x50 (offset from 0x7c00) to prevent it from being overwritten by the BIOS during execution.

Also keep in mind that whenever you call any subroutine, the values of the registers you were using could be overwritten. Make sure you either use push, pop or save your values to memory before calling a subroutine. Look at Martin Rosenau's answer below to read more about that.

Thank you to all who replied to my question. I now have a better understanding of how this low-level stuff works.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

战皆罪 2025-01-26 19:51:04

这可能会成为该主题的规范答案。

真实硬件/USB/笔记本电脑问题

如果您尝试使用 USB 在真实硬件上启动,那么即使您在 BOCHSQEMU 中正常工作,您也可能会遇到另一个问题。如果您的 BIOS 设置为进行 USB FDD 模拟(而不是 USB HDD 或其他),您可能需要添加 BIOS 参数块 (BPB) 到引导加载程序的开头。您可以像这样创建一个假的:

org 0x7c00
bits 16

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

    ; 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   "

main:
    [insert your code here]

ORG 指令调整为您需要的内容,或者如果您只需要默认的 0x0000,则忽略它。

如果您要修改代码以具有 Unix/Linux file 命令上方的布局,则可能能够转储出它认为构成磁盘映像中的 VBR 的 BPB 数据。运行命令file disk.img,您可能会得到以下输出:

disk.img:DOS/MBR 引导扇区,代码偏移 0x3c+2,OEM-ID“mkfs.fat”,根条目 224,扇区 2880(卷 <=32 MB),扇区/FAT 9,扇区/磁道 18,序列号 0x2d7e5a1a,未标记,FAT(12 位)


如何修改此问题中的代码

在此 OP 的情况下 原始代码可以修改为如下所示:

bits 16

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

    ; 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   "

main:
    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

    mov cl, 10          ; Use this register as our loop counter
    mov ah, 0Eh         ; This register holds our BIOS instruction

.repeat:
    mov al, 41h         ; Put ASCII 'A' into this register
    int 10h             ; Execute our BIOS print instruction
    cmp cl, 0           ; Find out if we've reached the end of our loop
    dec cl              ; Decrement our loop counter
    jnz .repeat         ; Jump back to the beginning of our loop
    jmp .done           ; Finish the program when our loop is done

.done:
    mov al, 42h         ; Put ASCII 'B' into this register
    int 10h             ; Execute BIOS print instruction
    ret

times 510-($-$) db 0   ; Pad remainder of boot sector with 0s
dw 0xAA55

其他建议

正如已经指出的 - 您不能ret结束引导加载程序。您可以将其置于无限循环中,或者使用 cli 后跟 hlt 停止处理器。

如果您在堆栈上分配了大量数据或开始写入引导加载程序 512 字节之外的数据,您应该将自己的堆栈指针 (SS:SP) 设置为赢得的内存区域不要干扰你自己的代码。这个问题中的原始代码确实设置了一个堆栈指针。这是阅读本问答的其他人的一般观察。我在我的 Stackoverflow 答案中有更多信息,其中包含 常规引导加载程序提示


测试代码以查看 BIOS 是否覆盖 BPB

如果您想知道 BIOS 是否可能覆盖 BPB 中的数据并确定它写入的值,您可以使用此引导加载程序代码转储 BPB,因为引导加载程序在控制后会看到它被转移到它。正常情况下前3个字节应该是EB 3C 90,后面跟着一系列AA。任何不是 AA 的值都可能被 BIOS 覆盖。此代码位于 NASM 中,可以使用 nasm -f bin boot.asm -o boot.bin 组装到引导加载程序中,

; Simple bootloader that dumps the bytes in the BIOS Parameter
; Block BPB. First 3 bytes should be EB 3C 90. The rest should be 0xAA
; unless you have a BIOS that wrote drive geometry information
; into what it thinks is a BPB.

; Macro to print a character out with char in BX
%macro print_char 1
    mov al, %1
    call bios_print_char
%endmacro

org 0x7c00
bits 16

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

    ; Fake BPB filed with 0xAA
    TIMES 59 DB 0xAA

main:
    xor ax, ax
    mov ds, ax
    mov ss, ax              ; Set stack just below bootloader at 0x0000:0x7c00
    mov sp, boot
    cld                     ; Forward direction for string instructions

    mov si, sp              ; Print bytes from start of bootloader
    mov cx, main-boot       ; Number of bytes in BPB
    mov dx, 8               ; Initialize column counter to 8
                            ;     So first iteration prints address
.tblloop:
    cmp dx, 8               ; Every 8 hex value print CRLF/address/Colon/Space
    jne .procbyte
    print_char 0x0d         ; Print CRLF
    print_char 0x0a
    mov ax, si              ; Print current address
    call print_word_hex
    print_char ':'          ; Print ': '
    print_char ' '
    xor dx, dx              ; Reset column counter to 0
.procbyte:
    lodsb                   ; Get byte to print in AL
    call print_byte_hex     ; Print the byte (in BL) in HEX
    print_char ' '
    inc dx                  ; Increment the column count
    dec cx                  ; Decrement number of  bytes to process
    jnz .tblloop

    cli                     ; Halt processor indefinitely
.end:
    hlt
    jmp .end

; Print the character passed in AL
bios_print_char:
    push bx
    xor bx, bx              ; Attribute=0/Current Video Page=0
    mov ah, 0x0e
    int 0x10                ; Display character
    pop bx
    ret

; Print the 16-bit value in AX as HEX
print_word_hex:
    xchg al, ah             ; Print the high byte first
    call print_byte_hex
    xchg al, ah             ; Print the low byte second
    call print_byte_hex
    ret

; Print lower 8 bits of AL as HEX
print_byte_hex:
    push bx
    push cx
    push ax

    lea bx, [.table]        ; Get translation table address

    ; Translate each nibble to its ASCII equivalent
    mov ah, al              ; Make copy of byte to print
    and al, 0x0f            ;     Isolate lower nibble in AL
    mov cl, 4
    shr ah, cl              ; Isolate the upper nibble in AH
    xlat                    ; Translate lower nibble to ASCII
    xchg ah, al
    xlat                    ; Translate upper nibble to ASCII

    xor bx, bx              ; Attribute=0/Current Video Page=0
    mov ch, ah              ; Make copy of lower nibble
    mov ah, 0x0e
    int 0x10                ; Print the high nibble
    mov al, ch
    int 0x10                ; Print the low nibble

    pop ax
    pop cx
    pop bx
    ret
.table: db "0123456789ABCDEF", 0

; boot signature
TIMES 510-($-$) db 0
dw 0xAA55

对于任何未执行此操作的 BIOS,输出应如下所示在将控制权转移到引导加载程序代码之前更新 BPB:

<前><代码>7C00: EB 3C 90 AA AA AA AA AA
7C08:啊啊啊啊啊啊啊啊啊啊啊
7C10:啊啊啊啊啊啊啊啊啊啊啊
7C18:啊啊啊啊啊啊啊啊啊啊啊
7C20:啊啊啊啊啊啊啊啊啊啊啊
7C28:啊啊啊啊啊啊啊啊啊啊啊
7C30:啊啊啊啊啊啊啊啊啊啊啊
7C38:啊啊啊啊啊啊啊啊

This could probably be made into a canonical answer on this subject.

Real Hardware / USB / Laptop Issues

If you are attempting to use USB to boot on real hardware then you may encounter another issue even if you get it working in BOCHS and QEMU. If your BIOS is set to do USB FDD emulation (and not USB HDD or something else) you may need to add a BIOS Parameter Block(BPB) to the beginning of your bootloader. You can create a fake one like this:

org 0x7c00
bits 16

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

    ; 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   "

main:
    [insert your code here]

Adjust the ORG directive to what you need or omit it if you just need the default 0x0000.

If you were to modify your code to have the layout above the Unix/Linux file command may be able to dump out the BPB data that it thinks makes up your VBR in the disk image. Run the command file disk.img and you may get this output:

disk.img: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", root entries 224, sectors 2880 (volumes <=32 MB) , sectors/FAT 9, sectors/track 18, serial number 0x2d7e5a1a, unlabeled, FAT (12 bit)


How the Code in this Question Could be Modified

In the case of this OPs original code it could have been modified to look like this:

bits 16

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

    ; 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   "

main:
    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

    mov cl, 10          ; Use this register as our loop counter
    mov ah, 0Eh         ; This register holds our BIOS instruction

.repeat:
    mov al, 41h         ; Put ASCII 'A' into this register
    int 10h             ; Execute our BIOS print instruction
    cmp cl, 0           ; Find out if we've reached the end of our loop
    dec cl              ; Decrement our loop counter
    jnz .repeat         ; Jump back to the beginning of our loop
    jmp .done           ; Finish the program when our loop is done

.done:
    mov al, 42h         ; Put ASCII 'B' into this register
    int 10h             ; Execute BIOS print instruction
    ret

times 510-($-$) db 0   ; Pad remainder of boot sector with 0s
dw 0xAA55

Other Suggestions

As has been pointed out - you can't ret to end a bootloader. You can put it into an infinite loop or halt the processor with cli followed by hlt.

If you ever allocate a large amount of data on the stack or start writing to data outside the 512 bytes of your bootloader you should set your own stack pointer (SS:SP) to a region of memory that won't interfere with your own code. The original code in this question does setup a stack pointer. This is a general observation for anyone else reading this Q/A. I have more information on that in my Stackoverflow answer that contains General Bootloader Tips.


Test Code to See if Your BIOS is Overwriting the BPB

If you want to know if the BIOS might be overwriting data in the BPB and to determine what values it wrote you could use this bootloader code to dump the BPB as the bootloader sees it after control is transferred to it. Under normal circumstances the first 3 bytes should be EB 3C 90 followed by a series of AA. Any value that isn't AA was likely overwritten by the BIOS. This code is in NASM and can be assembled into a bootloader with nasm -f bin boot.asm -o boot.bin

; Simple bootloader that dumps the bytes in the BIOS Parameter
; Block BPB. First 3 bytes should be EB 3C 90. The rest should be 0xAA
; unless you have a BIOS that wrote drive geometry information
; into what it thinks is a BPB.

; Macro to print a character out with char in BX
%macro print_char 1
    mov al, %1
    call bios_print_char
%endmacro

org 0x7c00
bits 16

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

    ; Fake BPB filed with 0xAA
    TIMES 59 DB 0xAA

main:
    xor ax, ax
    mov ds, ax
    mov ss, ax              ; Set stack just below bootloader at 0x0000:0x7c00
    mov sp, boot
    cld                     ; Forward direction for string instructions

    mov si, sp              ; Print bytes from start of bootloader
    mov cx, main-boot       ; Number of bytes in BPB
    mov dx, 8               ; Initialize column counter to 8
                            ;     So first iteration prints address
.tblloop:
    cmp dx, 8               ; Every 8 hex value print CRLF/address/Colon/Space
    jne .procbyte
    print_char 0x0d         ; Print CRLF
    print_char 0x0a
    mov ax, si              ; Print current address
    call print_word_hex
    print_char ':'          ; Print ': '
    print_char ' '
    xor dx, dx              ; Reset column counter to 0
.procbyte:
    lodsb                   ; Get byte to print in AL
    call print_byte_hex     ; Print the byte (in BL) in HEX
    print_char ' '
    inc dx                  ; Increment the column count
    dec cx                  ; Decrement number of  bytes to process
    jnz .tblloop

    cli                     ; Halt processor indefinitely
.end:
    hlt
    jmp .end

; Print the character passed in AL
bios_print_char:
    push bx
    xor bx, bx              ; Attribute=0/Current Video Page=0
    mov ah, 0x0e
    int 0x10                ; Display character
    pop bx
    ret

; Print the 16-bit value in AX as HEX
print_word_hex:
    xchg al, ah             ; Print the high byte first
    call print_byte_hex
    xchg al, ah             ; Print the low byte second
    call print_byte_hex
    ret

; Print lower 8 bits of AL as HEX
print_byte_hex:
    push bx
    push cx
    push ax

    lea bx, [.table]        ; Get translation table address

    ; Translate each nibble to its ASCII equivalent
    mov ah, al              ; Make copy of byte to print
    and al, 0x0f            ;     Isolate lower nibble in AL
    mov cl, 4
    shr ah, cl              ; Isolate the upper nibble in AH
    xlat                    ; Translate lower nibble to ASCII
    xchg ah, al
    xlat                    ; Translate upper nibble to ASCII

    xor bx, bx              ; Attribute=0/Current Video Page=0
    mov ch, ah              ; Make copy of lower nibble
    mov ah, 0x0e
    int 0x10                ; Print the high nibble
    mov al, ch
    int 0x10                ; Print the low nibble

    pop ax
    pop cx
    pop bx
    ret
.table: db "0123456789ABCDEF", 0

; boot signature
TIMES 510-($-$) db 0
dw 0xAA55

Output should look like this for any BIOS that didn't update the BPB before transferring control to the bootloader code:

7C00: EB 3C 90 AA AA AA AA AA
7C08: AA AA AA AA AA AA AA AA
7C10: AA AA AA AA AA AA AA AA
7C18: AA AA AA AA AA AA AA AA
7C20: AA AA AA AA AA AA AA AA
7C28: AA AA AA AA AA AA AA AA
7C30: AA AA AA AA AA AA AA AA
7C38: AA AA AA AA AA AA
独自唱情﹋歌 2025-01-26 19:51:04

汇编代码仅适用于我的两个 x86 处理器之一

它不是处理器,而是 BIOS:

int 指令实际上是 call 的一种特殊变体代码>指令。该指令调用一些子例程(通常用汇编程序编写)。

(您甚至可以用您自己的子例程替换该子例程 - 例如,这实际上是由 MS-DOS 完成的。)

在两台计算机上,您有两个不同的 BIOS 版本(甚至供应商),这意味着由int 10h 指令是由不同的程序员编写的,因此其功能并不完全相同。

仅得到以下输出

我怀疑这里的问题是第一台计算机上的 int 10h 调用的子例程不保存寄存器值,而第二台计算机上的例程则保存寄存器值。

换句话说:

在第一台计算机上,由 int 10h 调用的例程可能如下所示:

...
mov cl, 5
mov ah, 6
...

...因此在 int 10h 调用 ah 之后> 寄存器不再包含值0Eh,甚至可能出现cl寄存器被修改的情况(此时将陷入无限循环)。

为了避免这个问题,您可以使用push保存cl寄存器(您必须保存整个cx寄存器)并在之后恢复它>int 指令。您还必须在每次调用 int 10h 子例程之前设置 ah 寄存器的值,因为您无法确定此后它没有被修改:

push cx
mov ah, 0Eh
int 10h
pop cx

mov sp, ... ...ret

请思考 Peter Cordes 的评论:

ret 指令如何工作以及它是如何相关的到 spss 寄存器?

这里的ret指令肯定不会做你期望的事情!

在软盘上,引导扇区通常包含以下代码:

mov ax, 0  ; (may be written as "xor ax, ax")
int 16h
int 19h

int 19h 完全按照 ret 指令执行您所期望的操作。

然而,BIOS 将再次启动计算机,这意味着它将从 USB 记忆棒加载代码并再次执行。

您将得到以下结果:

啊啊啊啊啊啊啊啊啊……

因此插入int 16h指令。当ax寄存器的值为0时,这将等待用户按下键盘上的按键,然后再调用int 16h子例程。

或者,您可以简单地添加一个无限循环:

.endlessLoop:
    jmp .endlessLoop

mov ss,...

当这两条指令之间发生中断时:

mov ss, ax
    ; <--- Here
mov sp, 4096

... spss 寄存器的组合并不代表值的“有效”表示。

如果您不幸运,中断会将数据写入您不需要的内存中的某个位置。它甚至可能会覆盖您的程序!

因此,您通常在修改 ss 寄存器时锁定中断:

cli          ; Forbid interrupts
mov ss, ax
mov sp, 4096
sti          ; Allow interrupts again

Assembly code only works on one of my two x86 processors

It is not the processors but the BIOSes:

The int instruction actually is a special variant of the call instruction. The instruction calls some sub-routine (typically written in assembler).

(You can even replace that sub-routine by your own one - which is actually done by MS-DOS, for example.)

On two computers you have two different BIOS versions (or even vendors) which means that the sub-routine called by the int 10h instruction has been written by different programmers and therefore does not exactly do the same.

only to get the following output

The problem I suspect here is that the sub-routine called by int 10h on the first computer does not save the register values while the routine on the second computer does.

In other words:

On the first computer the routine called by int 10h may look like this:

...
mov cl, 5
mov ah, 6
...

... so after the int 10h call the ah register does no longer contain the value 0Eh and it may even be the case that the cl register is modified (which will end in an endless loop then).

To avoid the problem you could save the cl register using push (you have to save the entire cx register) and restore it after the int instruction. You also have to set the value of the ah register before each call of the int 10h sub-routine because you cannot be sure that it has not modified since then:

push cx
mov ah, 0Eh
int 10h
pop cx

mov sp, ... ... ret

Please think about Peter Cordes' comment:

How does the ret instruction work and how is it related to the sp and ss registers?

The ret instruction here will definitely not do what you expect!

On floppy disks the boot sectors typically contain the following code instead:

mov ax, 0  ; (may be written as "xor ax, ax")
int 16h
int 19h

int 19h does exactly what you expect from the ret instruction.

However the BIOS will boot the computer again which means that it will load the code from your USB stick and execute it again.

You'll get the following result:

AAAAABAAAAABAAAAABAAAAAB...

Therefore the int 16h instruction is inserted. This will wait for the user to press a key on the keyboard when the ax register has the value 0 before calling the int 16h sub-routine.

Alternatively you can simply add an endless loop:

.endlessLoop:
    jmp .endlessLoop

mov ss, ...

When an interrupt occurs between these two instructions:

mov ss, ax
    ; <--- Here
mov sp, 4096

... the combination of the sp and ss registers does not represent a "valid" representation of values.

If you are unlucky the interrupt will write data somewhere to memory where you don't want it. It may even overwrite your program!

Therefore you typically lock interrupts when modifying the ss register:

cli          ; Forbid interrupts
mov ss, ax
mov sp, 4096
sti          ; Allow interrupts again
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文