assembly x86 qemu:致命:尝试在 RAM 或 ROM 之外执行代码

2024-03-28

我正在开发一个非常基本的 shell,其中当前唯一的命令是“help”。如果您输入错误,系统会通知您该命令无法识别。在段和堆栈设置的某个地方,我有一个错误,导致 shell 在我输入任何内容后吐出一些废话,然后完全冻结。

我在终端中遇到错误

qemu: fatal: Trying to execute code outside RAM or ROM at 0xff0fe990
EAX=0000ffe0 EBX=0000ffff ECX=ff00e990 EDX=0000e000
ESI=000001a4 EDI=0000011e EBP=00000019 ESP=0000ffdc
EIP=ff00e990 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =b000 000b0000 0000ffff 00009300
CS =f000 000f0000 0000ffff 00009b00
SS =e000 000e0000 0000ffff 00009300
DS =e000 000e0000 0000ffff 00009300
FS =0000 00000000 0000ffff 00009300
GS =0000 00000000 0000ffff 00009300
LDT=0000 00000000 0000ffff 00008200
TR =0000 00000000 0000ffff 00008b00
GDT=     000f6688 00000037
IDT=     00000000 000003ff
CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000018 CCD=0000ffe0 CCO=SUBL    
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
compile-and-run.sh: line 18: 17015 Abort trap: 6           qemu-system-i386 -s -fda boot-disk.bin -boot a

我对组装非常陌生,所以我非常感谢有关如何理解我的错误的指南! 代码放在下面。我用这个脚本运行它,请执行同样的操作。

nasm bootloader.asm -o bootloader.bin
dd if=/dev/zero of=boot-disk.bin bs=512 count=2880
dd if=bootloader.bin of=boot-disk.bin conv=notrunc
nasm kernel.asm -o kernel.bin
dd if=kernel.bin of=boot-disk.bin conv=notrunc bs=512 seek=1
qemu-system-i386 -s -fda boot-disk.bin -boot a

引导程序.asm

BITS 16                ; NASM directive for declaring the bit-mode.

global start
start:
  mov ax, 0x07C0                ; 07C0 = 1984, the location where BIOS looks for the
                      ; bootloader on the floppy disk
  mov ds, ax            ; sets up data segment (ds)
  KERNEL_BLOCK_START equ 1    ; Starting disk block where kernel is written
  KERNEL_BLOCK_SIZE equ 1     ; Number of blocks containing kernel
  KERNEL_SEGMENT equ 1000h    ; Kernel will be loaded at segment 4096

  call load_kernel         ; begin OS

load_kernel:
  mov si, bootloader_status_message
  call bootloader_print_string

  ; begin reading kernel byte code from disk
  ; Uses interrupt 13h with AH = 2h
  ;     Options:   AL = sectors to read count
  ;                CH = Cylinder to read, 0 to 1023
  ;                CL = Sector within Cylinder to read, 1-63
  ;                DH = Head (0 in our case)
  ;                DL = Drive (also 0)
  ;                ES:BX = Buffer address pointer
  mov ah, 2
  mov al, KERNEL_BLOCK_SIZE
  push word KERNEL_SEGMENT
  pop es
  xor bx, bx      ; reset bx to 0
  mov cx, KERNEL_BLOCK_START + 1
  mov dx, 0
  int 13h        ; call interrupt. Writes error to Carry flag

  jnc jump_to_kernel    ; loading success, no error in carry flag

  mov si, bootloader_load_failed
  call bootloader_print_string
  jmp $          ; loop forever

jump_to_kernel :
  mov si, bootloader_load_success
  call bootloader_print_string
  jmp KERNEL_SEGMENT:0

bootloader_print_string:

  lodsb          ; Takes one byte from SI and puts it to AL (maintains pointer to next byte)

  or al, al      ; All strings end with zero to indicate that the string has finished
                 ; we use that to know when to stop printing characters from SI
                 ; takes logical OR of AL by itself. Result is store in Carry Flag

  jz .finish       ; checks if the carry flag is zero and if so jumps to finish subroutine

  ; continue printing below
  mov ah, 0x0E   ; BIOS directive for Teletype printing
  int 10h      ; BIOS interrupt for video services. (AH=Teletype & AL=character)

  jmp bootloader_print_string   ; recursive call until all characters are printed

  .finish:       ; finished printing all characters
    ret          ; return to where this routine was called from


bootloader_status_message db 'bootloader: loading kernel...', 0x0D, 0x0A, 0
bootloader_load_failed db 'bootloader fatal: loading kernel failed. Go home.', 0x0D, 0x0A, 0
bootloader_load_success db 'bootloader: reading kernel success, jumping now...', 0x0D, 0x0A, 0

  times 510 - ($ - $$) db 0    ; loop 510 times and pad with empty bytes
  dw 0xAA55                    ; last 2 bytes are 55h and 0AAh

内核.asm

  os_initialize_environment:
  STACK_SEGMENT equ 09000h        ; top of Conventional memory, 36864
  STACK_SIZE equ 0ffffh           ; stack length: 64K-1 bytes
  SCREEN_SEGMENT equ 0b800h       ; segment of memory where BIOS writes display data
  SCREEN_SIZE_COLUMNS equ 80      ; 80 width
  SCREEN_SIZE_ROWS equ 25         ; 25 height
  mov sp, STACK_SEGMENT
  mov ss, sp
  mov sp, STACK_SIZE
  push cs
  pop ds
  push word SCREEN_SEGMENT
  pop es

  mov al, 0xf
  mov si, os_kernel_read_signal
  call os_print_string

  call os_start                   ; begin OS

; ----------------------------------------------------------
; Start of main program
; Available routines: os_print_string, os_get_user_input, os_compare_string
; ----------------------------------------------------------
os_start:

  mov si, os_welcome_message    ; move welcome message to input
  call os_print_string

  mov si, os_alive_signal    ; move welcome message to input
  call os_print_string

  jmp shell_begin


; ---------------
; Shell + commands
; ---------------
shell_cursor db '> ', 0
shell_command_help db 'help', 0
shell_error_wrong_command db 'Wrong input. Type help for help.', 0x0D, 0x0A, 0


; ---------------
; OS strings
; ---------------
os_welcome_message db 'SsOS is a Simple Operating System. Keep expectations low. The pessimist is never disappointed.', 0x0D, 0x0A, 0
os_alive_signal db 'Command prompt ready', 0x0D, 0x0A, 0
os_kernel_read_signal db 'Kernel reached from bootloader', 0x0D, 0x0A, 0
os_action_help db 'available commands: help', 0x0D, 0x0A, 0
os_waiting_for_input db 'Please provide input below', 0x0D, 0x0A, 0


; ---------------
; Buffer
; ---------------
buffer times 128 db 0


; ----------------------------------------------------------
; Routine: Begins shell
; ----------------------------------------------------------
shell_begin:

  mov si, shell_cursor   ; print > cursor
  call os_print_string

  mov di, buffer         ; move buffer to destination output
  call os_get_user_input ; wait for user input

  mov si, buffer         ; copy user input to SI

  mov di, shell_command_help
  call os_compare_string ; checks if user typed help command
  jc .command_help


  ; command help (shell_command_help) selected
  .command_help:
    mov si, os_action_help
    call os_print_string
    jmp shell_begin      ; reset shell


  ; wrong user input (command not recognized)
  .wrong_input_error:
    mov si, shell_error_wrong_command
    call os_print_string
    jmp shell_begin


; ----------------------------------------------------------
; Routine: Print String in SI
; Input    1. SI: string to be printed must be copied to SI
; ----------------------------------------------------------
os_print_string:

  lodsb          ; Takes one byte from SI and puts it to AL (maintains pointer to next byte)

                 ; All strings end with zero to indicate that the string has finished
                 ; we use that to know when to stop printing characters from SI
  or al, al      ; takes logical OR of AL by itself. Result is store in Carry Flag

  jz .finish       ; checks if the carry flag is zero and if so jumps to finish subroutine

  ; continue printing below
  mov ah, 0x0E   ; BIOS directive for Teletype printing
  int 10h      ; BIOS interrupt for video services. (AH=Teletype & AL=character)

  jmp os_print_string   ; recursive call until all characters are printed

  .finish:       ; finished printing all characters
    ret          ; return to where this routine was called from


; ----------------------------------------------------------
; Routine: Get String from User
; Waits for a complete string of user input and puts it in buffer.
; Sensitive for backspace and Enter buttons
; Input    1. Buffer in DI
; Output   2. Input char in buffer
; ----------------------------------------------------------
os_get_user_input:
  xor cl, cl       ; CL will be our counter that keeps track of the number of characters the user has entered.
                   ; XORing cl by itself will set it to zero.

  mov si, os_waiting_for_input
  call os_print_string

  .get_char_and_add_to_buffer:

    mov ah, 0      ; We use bios interrupt 16h to capture user input.
                   ; AH=0 is an option for 16h that tells the interrupt to read the user input character
    int 16h      ; call interrupt. Stores read character in AL

    ; backspace button listener
    cmp al, 0x08   ; compares user input to the backspace button, stores result in Carry Flag
    je .backspace_pressed    ; if the results of the compare is 1, go to subroutine .backspace_pressed

    ; enter button listener
    cmp al, 0x0D   ; compares user input to enter button
    je .enter_pressed        ; go to appropriate subroutine for enter button

    ; input counter
    cmp cl, 0x80   ; Has the user entered 128 bytes yet? (buffer limit is 128)
    je .buffer_overflow

    ; User input is normal character

    ; print input
    mov ah, 0x0E   ; Teletype mode
    int 10h      ; Print interrupt

    stosb          ; puts character in buffer
    inc cl         ; increment counter
    jmp .get_char_and_add_to_buffer    ; recurse


  ; // Subroutines
  .backspace_pressed:
    cmp cl, 0      ; no point erasing anything if no input has been entered
    je .get_char_and_add_to_buffer   ; ignore backspace press

    ; Delete last input character from buffer
                 ; When you use stosb, movsb or similar functions, the system implicitly uses the SI and DI registers.
    dec di         ; Therefore we need to decrement di to get to the last input character and erase it.
    mov byte[di],0 ; Erases the byte at location [di]
    dec cl         ; decrement our counter

    ; Erase character from display
    mov ah, 0x0E   ; Teletype mode again
    mov al, 0x08   ; Backspace character
    int 10h

    mov al, ' '    ; Empty character to print
    int 10h

    mov al, 0x08
    int 10h

    jmp .get_char_and_add_to_buffer    ; go back to main routine


  ; enter button pressed. Jump to exit
  .enter_pressed:
    jmp .exit_routine


  ; buffer overflow (buffer is full). Don't accept any more chars and exit routine.
  .buffer_overflow:
    jmp .exit_routine


  .exit_routine:
    mov al, 0       ; end of user input signal
    stosb
    mov ah, 0x0E
    mov al, 0x0D    ; new line
    int 0x10
    mov al, 0x0A
    int 0x10

    ret             ; exit entire routine



; ----------------------------------------------------------
; Routine: Compare equality of two strings
; Waits for a complete string of user input and puts it in buffer.
; Sensitive for backspace and Enter buttons
; Input    1. String1 in SI    2. String2 in DI
; Output   1. result in carry flag
; ----------------------------------------------------------
os_compare_string:
  .compare_next_character:      ; a loop that goes character by character
    mov al, [si]      ; focus on next byte in si
    mov bl, [di]      ; focus on next byte in di
    cmp al, bl
    jne .conclude_not_equal       ; if not equal, conclude and exit

    ; we know: two bytes are equal

    cmp al, 0         ; did we just compare two zeros?
    je .conclude_equal         ; if yes, we've reached the end of the strings. They are equal.

    ; increment counters for next loop
    inc di
    inc si
    call .compare_next_character

  .conclude_equal:
    stc              ; sets the carry flag (meaning that they ARE equal)
    jmp .done


  .conclude_not_equal:
    clc              ; clears the carry flag (meaning that they ARE NOT equal)
    jmp .done

  .done:
    ret

@Jester 建议的修复

;bootloader sector problem
KERNEL_BLOCK_SIZE equ 2

1. obvious, just change call to jmp
2. mov es, buffer ;(ignore SCREEN_SEGMENT as its not even used)
3. change shell logic 
  shell_begin:
    mov di, buffer         ; move buffer to destination output
    call os_get_user_input ; wait for user input
    mov si, buffer         ; copy user input to SI
    mov di, shell_command_help
    call os_compare_string ; checks if user typed help command
    jc .command_help
    jnc .wrong_input_error

    .command_help:
      mov si, os_action_help
      call os_print_string
      jmp shell_begin      ; reset shell

    .wrong_input_error:
      mov si, shell_error_wrong_command
      call os_print_string
      jmp shell_begin

    jmp shell_begin

主要问题是你的 kernel.bin 已经增长到超过 1 个扇区,而你只加载 1 个扇区。

进一步的问题:

  1. In os_compare_string, the call .compare_next_character应该是一个jmp.
  2. In os_get_user_input, stosb使用es段,但您已将其设置为指向视频内存,因此它不会将输入的文本存储在缓冲区中
  3. In shell_begin当您将输入与帮助命令进行比较时,代码流将始终转到.command_help
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

assembly x86 qemu:致命:尝试在 RAM 或 ROM 之外执行代码 的相关文章

  • 调用可以是 cdecl 或 stdcall 的函数

    我需要编写调用外部函数的代码 该函数可以是 32 位 Windows 应用程序中的 stdcall 调用或 cdecl 我的代码 调用者 无法提前知道其中的哪一个 现在 如果我尝试从定义为 stdcall 的调用站点调用 cdecl 函数
  • 将 C 代码转换为 x86-64 汇编

    我正在尝试将 C 代码转换为 x86 64 我的目标是反转链表 传入的两个参数是 head ptr 和 offset to 以获取指针字段的地址 即指向列表中下一个节点的指针 据我了解 head ptr是通过rdi寄存器传入的 offset
  • Android NDK 代码中的 SIGILL

    我在市场上有一个 NDK 应用程序 并获得了有关以下内容的本机崩溃报告 SIGILL信号 我使用 Google Breakpad 生成本机崩溃报告 以下是详细信息 我的应用程序是为armeabi v7a with霓虹灯支持 它在 NVIDI
  • 一个地址有多少字节? [复制]

    这个问题在这里已经有答案了 在64位机器上 我们知道一个地址是8个字节 然而 我并不完全清楚一个地址中有多少字节的信息 虚拟内存中的每个字节都有一个地址吗 或者内存中的每 64 位都有一个地址 还是取决于架构 如果这取决于架构 那么我应该如
  • 如何与 QEMU 映像共享主机的本地主机?

    想知道这样的事情是否可能 我有一个服务器在监听localhost 1889我的本地 PC 和 QEMU 映像能够使用相同的端口和 IP 访问服务器 localhost 1889 确实正在寻找以下解决方案之一 用于启用此功能的 QEMU 标志
  • 学习 (N)ASM 的最佳资源是什么? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我想学习汇编已经有一段时间了 尽管我之前尝试过几次 但我还没有真正能够超越 Hello world 有
  • ICC 中的 -O3 会扰乱内在函数,使用 -O1 或 -O2 或相应的手动汇编即可

    这是后续这个问题 http stackoverflow com questions 49791664 o2 in icc messes up assembler fine with o1 in icc and all optimizatio
  • 难以理解汇编命令“加载有效地址”[重复]

    这个问题在这里已经有答案了 可能的重复 LEA 指令的目的是什么 https stackoverflow com questions 1658294 whats the purpose of the lea instruction LEA指
  • 是否有适用于双打 (__m128d) 的 Move (_mm_move_ss) 和 Set (_mm_set_ss) 内在函数?

    多年来 我有几次看到 in 中的内在函数float参数被转换为 m128使用以下代码 m128 b mm move ss m mm set ss a 例如 void MyFunction float y m128 a mm move ss
  • 如何在 MacOS 上使用 nasm 进行编译

    我正在尝试在汇编器上编译并链接我的第一个程序 我尝试编译以下代码 include stud io inc global main section text main xor eax eax again PRINT Hello PUTCHAR
  • x86 asm 图形设置的分辨率高于 640x480?

    我刚刚开始使用汇编语言 感觉像学习新东西 并且遇到了一些问题 到目前为止 我一直在浏览的所有教程都没有回答 或者太旧而无法知道 1 我尝试了一些搜索 也许我只是不知道正确的关键字 但我找不到用于更改屏幕分辨率等的图形模式的更新列表 我发现的
  • 上下文切换到安全模式(arm trustzone)的成本是多少

    我试图了解在arm中可信 安全 和非安全模式之间来回切换的成本 从非安全世界转移到安全世界时到底需要发生什么 我知道需要设置 ns 位 基于某些特殊指令 需要刷新和更新页表 刷新和更新处理器缓存 还有什么需要发生的吗 处理器缓存 它们是分段
  • 将十进制转换为十六进制

    首先 这是家庭作业 我正在尝试将 5 位数字读入寄存器 bx 假定该数字不大于 65535 16 位 以下是我尝试这样做的方法 但是 当我尝试打印该号码时 我仅打印输入的最后一位数字 这让我猜测 当我向 bx 添加另一个数字时 它会覆盖以前
  • movsbl指令的作用是什么? [复制]

    这个问题在这里已经有答案了 我在网上搜索过 但找不到明确的示例来理解该指令的作用 因此 如果有人可以举一个例子 这对我来说将会非常有帮助 用符号从字节扩展到长字移动 在Intel语法中 该指令的助记符是MOVSX 当变量类型为 C 时 C
  • gem5 se模式如何执行具有操作系统依赖的程序?

    Gem5 se 模式是非操作系统模式 但我能够在其上执行 row hammer 代码 其中包含具有操作系统依赖性的命令 但是如果在 se 模式下没有操作系统 那么它们如何在 se 模式下执行 大多数用户态允许的指令只做通常的事情 即稍微改变
  • 操作系统中的用户模式和内核模式有什么区别?

    用户模式和内核模式之间有什么区别 为什么以及如何激活它们 以及它们的用例是什么 内核模式 在内核模式下 执行代码具有完整且不受限制的 访问底层硬件 它 可以执行任何CPU指令并且 引用任意内存地址 核心 模式通常保留给 最低级别 最受信任的
  • 如何使 gcc 为 -fpatchable-function-entry 发出多字节 NOP?

    gcc确实有能力使用多字节用于对齐循环和函数的 NOP 然而当我尝试 fpatchable function entry option https gcc gnu org onlinedocs gcc Instrumentation Opt
  • x86:寄存器操作为内存内容和内存地址?

    寄存器 gt 内存地址 gt 内存内容 内存地址 gt 内存内容 上面的模型正确吗 而且 如果是的话 你能建议我是否认为正确吗 movl eax ebx gt 它将 eax 的内存地址移动到 ebx 这也会导致内容移动 movl eax e
  • 将代码保存在 L1 缓存中

    我一直在阅读维基百科关于 K 编程语言的文章 http en wikipedia org wiki K programming language Performance characteristics这就是我所看到的 解释器的小尺寸和语言的
  • 为什么这个函数在额外读取内存时运行速度如此之快?

    我目前正在尝试了解 x86 64 上某些循环的性能属性 特别是我的 Intel R Core TM i3 8145U CPU 2 10GHz 处理器 具体来说 在循环体内添加一条额外的指令来读取内存几乎可以使性能提高一倍 而细节并不是特别重

随机推荐