Tec*_*ech 1 assembly nasm osdev bootloader x86-16
我知道这个问题被问了很多,但我找到的每个答案都不适合我。我正在尝试加载stage 2位于图像文件第二个扇区的操作系统(0x200)
这是我尝试使用的代码:
bits 16 ; Starting at 16 bits
org 0x0 ; And starting at 0
jmp main ; Hop to main!
; TODO: copy comment from prev. loader
; args: SI
print:
lodsb ; Load the next/first character to AL
or al, al ; Is it 0?
jz donePrint ; Yes - Done.
mov ah, 0eh ; No - keep going.
int 10h ; Print character.
jmp print ; Repeat
donePrint:
ret ; Return
; todo: args
readSector:
mov ah, 02h
mov al, 1
mov dl, 0x80
mov ch, 0
mov dh, 0
mov cl, 2
mov bx, 0x500
int 13h
jnc good
jmp fail
main:
; First, setup some registers.
cli ; Clear interrupts
mov ax, 0x07C0 ; Point all registers to segment
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; Create the stack(0x0000-0xFFFF).
mov ax, 0x0000
mov ss, ax ; Point SS to 0x0000
mov sp, 0xFFFF ; Stack pointer at 0xFFFF
sti ; Restore interrupts
mov si, LOADING
call print
call readSector
fail:
mov si, FAILURE_MSG
call print
good:
mov si, LOADOK
call print
jmp 0x500
LOADING db 0x0D, 0x0A, "Booting loader...", 0x0D, 0x0A, 0x00
FAILURE_MSG db 0x0D, 0x0A, "ERROR: Press any key to reboot.", 0x0A, 0x00
LOADOK db 0x0D, 0x0A, "load ok", 0x0A, 0x00
TIMES 510 - ($-$$) DB 0
DW 0xAA55
Run Code Online (Sandbox Code Playgroud)
但它只是引导循环。我尝试了其他解决方案但无济于事。我究竟做错了什么?如果我需要更新问题,请告诉我。
谢谢你!
编辑#1:根据 Sep Roland 的回答,我更新了我的代码,但它仍然无法正常工作。如果有任何帮助,我将更新的代码放在这里。另外,如果需要,我可以发布我的第二阶段代码。它应该使用 0x500 作为 org. 新代码:
bits 16 ; Starting at 16 bits
org 0x0 ; And starting at 0
jmp main ; Hop to main!
; TODO: copy comment from prev. loader
; args: SI
print:
lodsb ; Load the next/first character to AL
or al, al ; Is it 0?
jz donePrint ; Yes - Done.
mov ah, 0eh ; No - keep going.
int 10h ; Print character.
jmp print ; Repeat
donePrint:
ret ; Return
; todo: args
readSector:
mov ah, 02h
mov al, 1
mov ch, 0
mov dh, 0
mov cl, 2
mov bx, 0x500
int 13h
jnc good
jmp fail
main:
; First, setup some registers.
cli ; Clear interrupts
mov ax, 0x07C0 ; Point all registers to segment
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; Create the stack(0x0000-0xFFFF).
mov ax, 0x0000
mov ss, ax ; Point SS to 0x0000
mov sp, 0xFFFE ; Stack pointer at 0xFFFE
sti ; Restore interrupts
mov si, LOADING
call print
call readSector
fail:
mov si, FAILURE_MSG
call print
end:
cli
hlt
jmp end
good:
mov si, LOADOK
call print
jmp 0x07C0:0x0500
LOADING db 0x0D, 0x0A, "Booting loader...", 0x0D, 0x0A, 0x00
FAILURE_MSG db 0x0D, 0x0A, "ERROR: Press any key to reboot.", 0x0A, 0x00
LOADOK db 0x0D, 0x0A, "load ok", 0x0A, 0x00
TIMES 510 - ($-$$) DB 0
DW 0xAA55
Run Code Online (Sandbox Code Playgroud)
编辑#2:发布第二阶段代码,包括gdt.inc因为提到的某人LGDT可能导致了问题:
主要代码(某些部分已被删除,但它们不是必需的,例如字符串)
bits 16 ; We start at 16 bits
org 0x500 ; We are loaded in at 0x500
jmp main ; Jump to main code.
; ----------------------------------------
; Includes
; ----------------------------------------
%include "include/stdio.inc"
%include "include/gdt.inc"
%include "include/a20.inc"
; ---------------------------------------
; Data and strings
; ---------------------------------------
stringhidden db "Not showing string.", 0x0D, 0x0A, 0x00
stringhidden db "Not showing string.", 0x0D, 0x0A, 0x00
; ---------------------------------------------------------------------
; main - 16-bit entry point
; Installing GDT, storing BIOS info, and enabling protected mode
; ---------------------------------------------------------------------
main:
; Our goal is jump to main32 to become 32-bit
; Setup segments and stack
cli ; Clear interrupts
xor ax, ax ; Null segments AX, DS, and ES
mov ds, ax
mov es, ax
mov ax, 0x9000 ; Stack begins at 0x9000-0xFFFF
mov ss, ax
mov sp, 0xFFFF ; Stack pointer is 0xFFFF
sti ; Enable interrupts
; Install the GDT
call installGDT ; Install the GDT
; Enable A20
call enableA20_KKbrd_Out ; Enable A20 through output port
; Print loading messages
mov si, msg1
call print16 ; Print the message
mov si, msg2 ; A message
call print16 ; Print the message
; Enter protected mode
cli ; Clear interrupts
mov eax, cr0 ; Set bit 0 in CR0--ENTER protected mode
or eax, 1
mov cr0, eax
jmp CODE_DESC:main32 ; Far jump to fix CS
; We can't re-enable interrupts because that would triple-fault. This will be fixed in main32.
bits 32 ; We are now 32 bit!
%include "include/stdio32.inc"
main32:
; Set registers up
mov ax, 0x10 ; Setup data segments to 0x10(data selector)
mov ds, ax
mov ss, ax
mov es, ax
mov esp, 90000h ; Stack begins from 90000h
call clear32 ; Clear screen
mov ebx, MSGHIDDEN ; Setup params for our message
call puts32 ; Call puts32 to print
cli ; Clear interrupts
hlt ; Halt the processor
Run Code Online (Sandbox Code Playgroud)
LGD 代码:
%ifndef __GDT_INC_67343546FDCC56AAB872_INCLUDED__
%define __GDT_INC_67343546FDCC56AAB872_INCLUDED__
bits 16 ; We are in 16-bit mode
; -----------------------------------------
; installGDT - install the GDT
; -----------------------------------------
installGDT:
cli ; Clear interrupts
pusha ; Save the registers
lgdt [toc] ; Load GDT into GDTR
sti ; Re-enable interrupts
popa ; Restore registers
ret ; Return!
; ----------------------------------------
; Global Descriptor Table data
; ----------------------------------------
gdt_data:
dd 0 ; Null descriptor
dd 0
; GDT code starts here
dw 0FFFFh ; Limit low
dw 0 ; Base low
db 0 ; Base middle
db 10011010b ; Access
db 11001111b ; Granularity
db 0 ; Base high
; GDT data starts here(mostly same as code, only difference is access)
dw 0FFFFh ; Limit low, again.
dw 0 ; Base low
db 0 ; Base middle
db 10010010b ; Access - different
db 11001111b ; Granularity
db 0
gdt_end:
toc:
dw gdt_end - gdt_data - 1
dd gdt_data ; Base of GDT
; Descriptor offsets names
%define NULL_DESC 0
%define CODE_DESC 0x8
%define DATA_DESC 0x10
; End of GDT code.
%endif ;__GDT_INC_67343546FDCC56AAB872_INCLUDED__
Run Code Online (Sandbox Code Playgroud)
编辑 #3:stdio 和 stdio32 可能存在问题,因此将它们放在这里
stdio.inc:
; ==============================================
; stdio.inc - IO routines
; Thanks to BrokenThorn Entertainment
; ==============================================
; First, show that we are defining stdio.inc
%ifndef __STDIO_INC_67343546FDCC56AAB872_INCLUDED__
%define __STDIO_INC_67343546FDCC56AAB872_INCLUDED__
; ------------------------------------------------
; Print16 - printing a null terminated string
; SI - 0 terminated string
; ------------------------------------------------
print16:
pusha ; Save registers for later
.loop1:
lodsb ; Load the next byte from the string into AL
or al, al ; Is AL 0?
jz print16done ; Yes - we are done.
mov ah, 0eh ; No - print next character
int 10h ; Call BIOS
jmp .loop1 ; Repeat!
print16done:
popa ; Restore registers
ret ; Return
%endif ;__STDIO_INC_67343546FDCC56AAB872_INCLUDED__
Run Code Online (Sandbox Code Playgroud)
stdio32.inc:
; ==================================================
; stdio32.inc - Handles 32-bit graphics
; ==================================================
%ifndef __GFX_INC_67343546FDCC56AAB872_INCLUDED__
%define __GFX_INC_67343546FDCC56AAB872_INCLUDED__
bits 32 ; 32-bits
%define VIDEO_MEMORY 0xB8000 ; Video memory address
%define COLS 80 ; Width of the screen
%define LINES 25 ; Height of the string
%define CHARACTER_ATTRIBURE 63 ; White text on Cyan background
_CurrentXPos db 0
_CurrentYPos db 0
; ---------------------------------------------------------
; char32 - Print a character to the screen(32-bit)
; BL - Character to print
; ---------------------------------------------------------
char32:
pusha ; Save registers
mov edi, VIDEO_MEMORY ; Get the pointer to the video memory
; Get current position
xor eax, eax ; Zero-out EAX
mov ecx, COLS*2 ; Mode 7 has 2 bytes per character - and so COLS*2 bytes per line.
mov al, byte [_CurrentYPos] ; Get Y position
mul ecx ; Multiply COLS * Y
push eax ; Save EAX--the multiplication
mov al, byte [_CurrentXPos] ; Multiply _CurrentXPos by 2 because 2 bytes per char(Mode 7)
mov cl, 2
mul cl
pop ecx ; Pop Y*COLS result
add eax, ecx
xor ecx, ecx
add edi, eax ; Add to base address
; Watch for a new line!
cmp bl, 0x0A ; 0x0A - newline character.
je .row ; Jump to .row if newline char
; Print the character
mov dl, bl ; Get character
mov dh, CHARACTER_ATTRIBURE ; Change DH to Character Attribute
mov word [edi], dx ; Write to video memory
; Update next pos
inc byte [_CurrentXPos] ; Go to next character
;cmp byte [_CurrentXPos], COLS ; EOL?
;je .row ; Yep - move to next row
jmp .done ; Nope - BAIL!
.row:
; Goto next row.
mov byte [_CurrentXPos], 0 ; Return to col 0
inc byte [_CurrentYPos] ; Go to next row.
.done:
; Return
popa
ret
; ---------------------------------------------------------
; puts32 - print a null terminated string
; EBX - String to print
; ---------------------------------------------------------
puts32:
; Store registers(EBX and EDI)
pusha ; Save registers
push ebx ; Copy string
pop edi
.loop:
mov bl, byte [edi] ; Get next character
cmp bl, 0 ; Check if it's null
je .done ; It is - done printing.
call char32 ; It isn't - print the character
inc edi ; Increment EDI for next character
jmp .loop ; Restart loop
.done:
; Update the hardware cursor
mov bh, byte [_CurrentXPos] ; BH and BL are the params for movecursor
mov bl, byte [_CurrentYPos]
call movecursor ; Update cursor position
popa ; Restore registers
ret ; Return!
bits 32
; ---------------------------------------------------------
; movecursor - Move the cursor to an X and Y position
; BH - X position
; BL - Y position
; ---------------------------------------------------------
movecursor:
pusha ; Save registers
; Get current position(BH and BL are relative to the current position on screen, not memory)
xor eax, eax ; Clear EAX
mov ecx, COLS ; Store COLS in ECX for multiplication
mov al, bh ; Get Y position
mul ecx ; Multiply Y by cols
add al, bl ; Add X
mov ebx, eax
; Set low byte index to VGA register
mov al, 0x0f
mov dx, 0x03D4
out dx, al
mov al, bl
mov dx, 0x03D5
out dx, al
; Do the same but for high byte
xor eax, eax
mov al, 0x0e
mov dx, 0x03D4
out dx, al
mov al, bl
mov dx, 0x03D5
out dx, al
popa ; Restore registers
ret ; Return
; ---------------------------------------------------------
; clear32 - clearing the screen
; ---------------------------------------------------------
clear32:
pusha ; Save registers
cld
mov edi, VIDEO_MEMORY ; Set EDI to video memory
mov cx, 2000
mov ah, CHARACTER_ATTRIBURE ; Clear screen with character attribute
mov al, ' ' ; Replace all chars with space
rep stosw
mov byte [_CurrentXPos], 0 ; Reset X and Y position
mov byte [_CurrentYPos], 0
popa ; Restore registers
ret
; ---------------------------------------------------------
; gotoxy - Set X and Y position
; AL - X position
; AH - Y position
; ---------------------------------------------------------
gotoxy:
pusha
mov [_CurrentXPos], al ; Set X and Y position
mov [_CurrentYPos], ah
popa
ret
%endif ;__STDIO_INC_67343546FDCC56AAB872_INCLUDED__
Run Code Online (Sandbox Code Playgroud)
EDIT0 评论第一阶段
EDIT1 评论第二阶段
EDIT2 评论包含的stdio32.inc
[编辑0]
您自己加载的扇区是在 0x7C0 的额外段中的偏移 0x500 处加载的。
该jmp 0x500指令跳转到代码段中的偏移量0x500。
无法保证 CS==0x7C0。使用远跳代替:
jmp 0x07C0:0x0500
Run Code Online (Sandbox Code Playgroud)
Run Code Online (Sandbox Code Playgroud)mov dl, 0x80
您确定这个驱动器号吗?当引导加载程序获得控制权时,最好使用 BIOS 在 DL 寄存器中提供的值。
Run Code Online (Sandbox Code Playgroud)mov sp, 0xFFFF
字对齐的堆栈指针会好得多!
使用mov sp, 0xFFFE或什至xor sp, sp(信任环绕)。
Run Code Online (Sandbox Code Playgroud)fail: mov si, FAILURE_MSG call print good: mov si, LOADOK call print jmp 0x500
如果加载扇区失败,您会跳转到failed,但在显示消息后,您会愉快地继续(失败)使用good 的代码。你需要停止:
fail:
mov si, FAILURE_MSG
call print
theEnd:
cli
hlt
jmp theEnd
good:
mov si, LOADOK
call print
jmp 0x07C0:0x0500
Run Code Online (Sandbox Code Playgroud)
[编辑1]
Run Code Online (Sandbox Code Playgroud)xor ax, ax ; Null segments AX, DS, and ES mov ds, ax mov es, ax
您已对第一阶段引导加载程序应用了更正。控制权已成功传递至 0x07C0:0x0500。由于第二阶段使用org 0x500段寄存器(至少 DS)保持在 0x07C0,这一点至关重要。但我看到的第一件事是你用 0 重新加载 DS 和 ES。这不会起作用,因为它会在汇编器生成的偏移量(根据 )org和实际的偏移量(相对 DS)之间产生不匹配。数据驻留。
这种不匹配的第一个表现是指令lgdt [toc]。
第二阶段的所有数据都驻留在内存中 0x8100 标记 (0x7C00 + 0x0500) 上方。另一方面,toc
标签将被汇编器转换为略高于 0x0500 的偏移地址。当 DS=0 时,该地址的内存地址远低于 0x8100 标记。根本没有有效的数据可以采取行动,因此崩溃(或类似)!
问题不仅仅在于指令lgdt [toc]。mov si, msg1和mov si, msg2将以同样的方式失败,并且mov ebx, MSGHIDDEN和根本dd gdt_data不是线性地址也会失败。dw gdt_end - gdt_data - 1不会受到损害,因为当两个量值都错误时(以相同的方式),差异不会改变。
您应该做的是将段寄存器保持在 0x07C0 与 一致org 0x0500,或者更好地将第一阶段引导加载程序中已有的段寄存器清零,并org 0x7C00为第一阶段使用 an ,org 0x8100为第二阶段使用 an 。这些设置会将所有内容保留在内存中的同一位置,第一阶段和第二阶段之间有 768 字节的间隙,这是我们每天都不会看到的。
然而,更优选的方法是将第一级引导加载程序中已有的段寄存器清零,并org 0x7C00为第一级使用 an,org 0x0600为第二级使用 an。这将第二阶段置于低内存 BIOS 变量之后。MS-DOS 是我们很好的例子。
Run Code Online (Sandbox Code Playgroud)installGDT: cli ; Clear interrupts pusha ; Save the registers lgdt [toc] ; Load GDT into GDTR sti ; Re-enable interrupts popa ; Restore registers ret ; Return!
在这段代码中保留通用寄存器是多余的。该lgdt指令不会改变其中任何一个。
Run Code Online (Sandbox Code Playgroud); Create the stack(0x0000-0xFFFF). mov ax, 0x0000 mov ss, ax ; Point SS to 0x0000 mov sp, 0xFFFE ; Stack pointer at 0xFFFE
Run Code Online (Sandbox Code Playgroud)mov ax, 0x9000 ; Stack begins at 0x9000-0xFFFF mov ss, ax mov sp, 0xFFFF ; Stack pointer is 0xFFFF
Run Code Online (Sandbox Code Playgroud)mov ax, 0x10 ; Setup data segments to 0x10(data selector) mov ds, ax mov ss, ax mov es, ax mov esp, 90000h ; Stack begins from 90000h
(1) SP 请勿使用奇数。
(2) 加载SS 和ESP 之间不要放置任何东西。
(3) 不要对堆栈的低端和高端都使用“begin”一词。
您将设置堆栈 3 次,每次都在不同的位置和大小!第一次从0x00000000运行到0x0000FFFD,第二次从0x00090000运行到0x0009FFFE,第三次从0x00000000运行到0x0008FFFF。
我建议设置堆栈,使实模式地址对应于保护模式地址。至少对于顶部 64KB 而言。
在第一阶段使用:
mov ax, 0x8000
mov ss, ax
xor sp, sp ; 0x00080000 - 0x0008FFFF (64KB)
Run Code Online (Sandbox Code Playgroud)
在第二阶段使用:
mov ax, DATA_DESC
mov ss, ax
mov esp, 0x00090000 ; 0x00000000 - 0x0008FFFF (576KB)
Run Code Online (Sandbox Code Playgroud)
bits 16
org 0x7C00
jmp main
; args: SI
print:
lodsb
or al, al
jz donePrint
mov bx, 0007h
mov ah, 0Eh
int 10h
jmp print
donePrint:
ret
main:
cli
xor ax, ax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ax, 0x8000 ; Stack between 0x8000:0x0000
mov ss, ax ; and 0x8000:0xFFFF (64KB)
xor sp, sp
sti
mov si, LOADING
call print
readSector:
mov dh, 0
mov cx, 0002h
mov bx, 0x0600 ; Sector buffer at 0x0000:0x0600
mov ax, 0201h
int 13h
jnc good
fail:
mov si, FAILURE_MSG
call print
end:
cli
hlt
jmp end
good:
mov si, LOADOK
call print
jmp 0x0000:0x0600 ; Start second stage
LOADING db 13, 10, "Booting loader...", 13, 10, 0
FAILURE_MSG db 13, 10, "ERROR: Press any key to reboot.", 10, 0
LOADOK db 13, 10, "load ok", 10, 0
TIMES 510 - ($-$$) DB 0
DW 0xAA55
Run Code Online (Sandbox Code Playgroud)
bits 16
org 0x0600
jmp main
; ---------------------------------------
; Includes
; ---------------------------------------
%include "include/stdio.inc"
%include "include/gdt.inc"
%include "include/a20.inc"
; ---------------------------------------
; Data and strings
; ---------------------------------------
stringhidden db "Not showing string.", 0x0D, 0x0A, 0x00
stringhidden db "Not showing string.", 0x0D, 0x0A, 0x00
; ---------------------------------------
; main - 16-bit entry point
; Installing GDT, storing BIOS info, and enabling protected mode
; ---------------------------------------
main:
call installGDT
call enableA20_KKbrd_Out
mov si, msg1
call print16
mov si, msg2
call print16
; Enter protected mode
cli
mov eax, cr0
or eax, 1
mov cr0, eax
jmp CODE_DESC:main32 ; Far jump to fix CS
; We can't re-enable interrupts because that would triple-fault. This will be fixed in main32.
bits 32 ; We are now 32 bit!
%include "include/stdio32.inc"
main32:
; Set registers up
mov ax, DATA_DESC
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax ; Stack between 0x00000000
mov esp, 0x00090000 ; and 0x0008FFFF (576KB)
call clear32 ; Clear screen
mov ebx, MSGHIDDEN
call puts32 ; Call puts32 to print
cli
hlt
...
Run Code Online (Sandbox Code Playgroud)
[编辑2]
@Sep Roland您的更改大部分有效,但不幸的是我的视频代码也可能有问题。系统似乎在一点点(24字节)后停止输入并放弃。我发布了视频代码,但如果这看起来要求太多,我可以到此为止。谢谢你!
我检查了您的stdio32.inc,发现其中有许多错误!
rep stosw将使用 ECX。ECX高位字中的垃圾会造成很大的危害。使用mov ecx, 2000。out两倍低字节,而不是低字节然后高字节。改进的stdio32.inc
; ==================================================
; stdio32.inc - Handles 32-bit graphics
; ==================================================
%ifndef __GFX_INC_67343546FDCC56AAB872_INCLUDED__
%define __GFX_INC_67343546FDCC56AAB872_INCLUDED__
bits 32
%define VIDEO_MEMORY 0xB8000 ; Video memory address
%define COLS 80 ; Width of the screen
%define LINES 25 ; Height of the screen
%define ATTRIB 0x3F ; WhiteOnCyan
_CurrentXPos db 0
_CurrentYPos db 0
; ---------------------------------------------------------
; char32 - Print a character to the screen(32-bit)
; BL - Character to print
; ---------------------------------------------------------
char32:
cmp bl, 10
je .row
push eax
push edi
movzx edi, byte [_CurrentYPos]
imul edi, COLS*2
movzx eax, byte [_CurrentXPos]
lea edi, [VIDEO_MEMORY + edi + eax * 2]
mov al, bl
mov ah, ATTRIB
mov [edi], ax
pop edi
pop eax
inc byte [_CurrentXPos]
cmp byte [_CurrentXPos], COLS ; EOL?
je .row
ret
.row:
mov byte [_CurrentXPos], 0
inc byte [_CurrentYPos]
ret
; ---------------------------------------------------------
; puts32 - print a null terminated string
; EBX - String to print
; ---------------------------------------------------------
puts32:
push ebx
push edi
mov edi, ebx
jmp .start
.loop:
call char32
inc edi
.start:
mov bl, [edi]
test bl, bl
jnz .loop
movzx ebx, word [_CurrentXPos] ; Load XPos and YPos together!
call movecursor ; Update hardware cursor
pop edi
pop ebx
ret
; ---------------------------------------------------------
; movecursor - Move the cursor to an X and Y position
; BL - X position
; BH - Y position
; BH and BL are relative to the current position on screen
; ---------------------------------------------------------
movecursor:
pushad
movzx eax, bh ; BH * COLS + BL
imul eax, COLS
movzx ebx, bl
add ebx, eax
; Set low byte index to VGA register
mov al, 0x0F
mov dx, 0x03D4
out dx, al
mov al, bl
inc dx
out dx, al
; Do the same but for high byte
mov al, 0x0E
dec dx
out dx, al
mov al, bh
inc dx
out dx, al
popad
ret
; ---------------------------------------------------------
; clear32 - clearing the screen
; ---------------------------------------------------------
clear32:
pushad
mov edi, VIDEO_MEMORY
mov ecx, 1000 ; 2000 words
mov eax, ((ATTRIB * 256 + 32) * 256 + ATTRIB) * 256 + 32
rep stosd
mov [_CurrentXPos], cx ; Reset XPos and YPos together!
popad
ret
; ---------------------------------------------------------
; gotoxy - Set X and Y position
; AL - X position
; AH - Y position
; ---------------------------------------------------------
gotoxy:
mov [_CurrentXPos], ax ; Set XPos and YPos together!
ret
%endif ;__STDIO_INC_67343546FDCC56AAB872_INCLUDED__
Run Code Online (Sandbox Code Playgroud)
在优化代码的同时,代码也变短了很多。
我现在的想法是,您的代码已超过 512 字节,并且第一阶段的单扇区加载并未将其全部带入内存。这当然可以解释您所遇到的一些部分字符串输出。
我不记得了(我的手册也没有提到),但是对于 NASM 来说与激活时pusha相同?
我是这样处理的:pushadbits 32
pushad使用 32 位代码编写。pushad并倾向于使用push单独的寄存器,因为这样更快。| 归档时间: |
|
| 查看次数: |
151 次 |
| 最近记录: |