MASM 在保护模式下生成错误的调用目标
MASM generating wrong call target in protected mode
我在保护模式下调用内存地址比当前函数低的函数时遇到异常。异常会因代码配置而异,有时是一般保护错误,有时是无效操作码等。
这是一个在硬件上产生一般保护错误和在 DOSBox 中产生双重错误的程序的源代码。相关代码在seg32
段。错误发生在func1
,尝试回调func2
single segment stack
assume cs:single,ds:single,ss:single
gdesc struc ;global descriptor structure definition
limit_lo dw 0ffffh ;low word of 20-bit limit (bits 15:0)
base_lo dw ? ;low word of base address (bits 15:0)
base_mid db ? ;middle byte of base address (bits 23:16)
priv db ? ;privilege and type bits
limit_hi db ? ;granularity, operand size, hi nybble of limit (bits 19:16)
base_hi db ? ;high byte of base address (bits 31:24)
gdesc ends
idesc struc ;interrupt descriptor structure definition
offset_lo dw ? ;low word of offset
selector dw ? ;selector in gdt
unused db 0 ;always zero
type_attr db ? ;type attribute bits
offset_hi dw ? ;high word of offset
idesc ends
;global descriptor table, phys addresses calculated by init code
nulld gdesc <0,0,0,0,0,0> ;null descriptor
pcode gdesc <,,,09eh,0cfh,> ;protected mode code descriptor
pdata gdesc <,,,092h,0cfh,> ;protected mode data descriptor
rcode gdesc <,,,09ah,08fh,> ;real mode code descriptor
rdata gdesc <,,,092h,08fh,> ;real mode data descriptor
vbuff gdesc <,0,0ah,092h,0cfh,> ;vga pixel buffer data descriptor
tbuff gdesc <,8000h,0bh,092h,0cfh,> ;text buffer data descriptor
gdt_limit dw offset gdt_limit-offset nulld-1 ;gdt_limit <- gdt size in bytes-1
gdt_addr dd offset nulld ;gdt_addr <- offset of gdt, physical address calculated at runtime
idt_div idesc <div_err-offset_0,8,0,0eeh,0> ;interrupt descriptor table, div error
idesc <dont_care-offset_0,8,0,0eeh,0> ;debugger call
idesc <nmi-offset_0,8,0,0eeh,0> ;nmi interrupt
idesc <dont_care-offset_0,8,0,0eeh,0> ;breakpoint
idesc <dont_care-offset_0,8,0,0eeh,0> ;into overflow
idesc <dont_care-offset_0,8,0,0eeh,0> ;bound overflow
idesc <invalid_op-offset_0,8,0,0eeh,0> ;invalid opcode
idesc <fpu_err-offset_0,8,0,0eeh,0> ;coprocessor unavailable
idesc <double_fault-offset_0,8,0,0eeh,0> ;double fault
idesc <fpu_err-offset_0,8,0,0eeh,0> ;coprocessor overrun
idesc <dont_care-offset_0,8,0,0eeh,0> ;invalid tss
idesc <not_present-offset_0,8,0,0eeh,0> ;segment not present
idesc <dont_care-offset_0,8,0,0eeh,0> ;stack exception
idesc <gp_fault-offset_0,8,0,0eeh,0> ;general protection fault
idesc <dont_care-offset_0,8,0,0eeh,0> ;reserved
idesc <fpu_err-offset_0,8,0,0eeh,0> ;coprocessor error
idesc 16 dup (<dont_care-offset_0,8,0,0eeh,0>) ;16 reserved
idt_pit idesc <pit_isr-offset_0,8,0,0eeh,0> ;timer isr
idt_kbd idesc <kbd_isr-offset_0,8,0,0eeh,0> ;keyboard isr
idt_limit dw offset idt_limit-offset idt_div-1 ;idt_limit <- idt size in bytes-1
idt_addr dd offset idt_div ;idt_addr <- offset of idt, complete physical address
;calculated at runtime
ridt_limit dw 3ffh ;real mode idt limit
ridt_addr dd 0 ;real mode idt address
m_pic_mask db ? ;original master pic mask
s_pic_mask db ? ;original slave pic mask
start:
mov ax, cs
mov ds, ax ;ds = cs, single segment
cli ;disable maskable interrupts
in al, 70h ;al <- cmos ram index register port
or al, 80h ;set bit 7 to disable nmi
out 70h, al ;non-maskable interrupts disabled
;check for 386+
;enable a20
;reinit PICs
mov al, 11h ;ICW1, IC4 bit set, cascade bit clr, edge trig, init bit set
out 20h, al ;send ICW1 to primary pic cmd register
jmp $+2
jmp $+2 ;delay needed on older systems
out 0a0h, al ;send ICW1 to slave pic cmd register
jmp $+2
jmp $+2
mov al, 20h ;ICW2 base address for primary pic = 20h
out 21h, al ;send ICW2 to primary pic data register
jmp $+2
jmp $+2
mov al, 28h ;ICW2 base address for slave pic = 28h
out 0a1h, al ;send ICW2 to slave pic data register
jmp $+2
jmp $+2
mov al, 4 ;ICW3, on primary pic, bits map to irq lines, use irq 2 for cascade
out 21h, al ;send ICW3 to primary pic data register
jmp $+2
jmp $+2
mov al, 2 ;ICW3, on slave pic, byte value = irq line, use irq 2 for cascade
out 0a1h, al ;send ICW3 to slave pic data register
jmp $+2
jmp $+2
mov al, 1 ;ICW4 set bit 1 to enable 80x86 mode
out 21h, al ;send ICW4 to primary pic data register
jmp $+2
jmp $+2
out 0a1h, al ;send ICW4 to slave pic data register
jmp $+2
jmp $+2
xor al, al ;clear the data registers
out 21h, al
jmp $+2
jmp $+2
out 0a1h, al
jmp $+2
jmp $+2
in al, 21h ;only need keyboard and timer irq enabled for now
mov m_pic_mask, al ;store original master pic mask register, restore before exit
or al, 0fch ;mask out all but irq 0 and 1
out 21h, al ;master pic mask updated
jmp $+2
jmp $+2
in al, 0a1h
mov s_pic_mask, al ;store original slave pic mask register, restore before exit
or al, 0ffh ;mask out every slave irq
out 0a1h, al
jmp $+2
jmp $+2
.386p ;calc and insert phys address into gdt entries
xor eax, eax ;clear high word of eax
mov ax, cs ;eax <- code segment address
shl eax, 4 ;multiply segment address by 16 to convert it to physical address
add gdt_addr, eax ;gdt_addr is defined with offset of gdt, gdt_addr + cs*16 = physical addres of gdt
add idt_addr, eax ;idt_addr is defined with offset of idt, idt_addr + cs*16 = physical addres of idt
lidt idt_limit ;load idtr
lgdt gdt_limit ;load gdtr
mov rcode.base_lo, ax
mov rdata.base_lo, ax ;store low word of cs phys address to real mode descriptors
shr eax, 16 ;shift eax to access high word
mov rcode.base_mid, al
mov rdata.base_mid, al ;store middle byte of cs phys address to real mode descriptors
mov rcode.base_hi, al
mov rdata.base_hi, al ;store high byte of cs phys address to real mode descriptors
xor eax, eax ;clear high word of eax
mov ax, seg seg32 ;eax <- seg32 segment address (fixed up by dos at runtime)
shl eax, 4 ;multiply segment address by 16 to convert it to physical address
mov pcode.base_lo, ax
mov pdata.base_lo, ax ;store low word of seg32 phys address to protected mode descriptors
shr eax, 16 ;shift eax to access high word
mov pcode.base_mid, al
mov pdata.base_mid, al ;store middle byte of seg32 phys address to protected mode descriptors
mov pcode.base_hi, al
mov pdata.base_hi, al ;store high byte of seg32 phys address to protected mode descriptors
mov eax, cr0 ;load control register 0
or al, 1 ;set pe bit
mov cr0, eax ;enable protected mode
;manually encoded jmp 8h:start32
db 66h ;specify 32-bit operand
db 0eah ;jmp opcode
dd offset start32 ;32 bit offset
dw 8 ;global descriptor selector (select protected mode code segment)
real_mode: ;transition back to real mode
.386p
mov eax, cr0 ;load control register into eax
and al, 0feh ;clear pe bit
mov cr0, eax ;real mode enabled
db 0eah ;jmp single:real_cs to load cs:ip
dw offset real_cs ;offset real_cs
dw seg single ;segment single (fixed up by dos at runtime)
real_cs: ;back in real mode
.8086
mov ax, cs
mov ds, ax ;ds = cs
mov ss, ax ;ss = cs
mov al, 11h ;ICW1, IC4 bit set, cascade bit clr, edge trig, init bit set
out 20h, al ;send ICW1 to primary pic cmd register
jmp $+2
jmp $+2 ;delay needed on older systems
out 0a0h, al ;send ICW1 to slave pic cmd register
jmp $+2
jmp $+2
mov al, 8 ;ICW2 base address for primary pic = 8
out 21h, al ;send ICW2 to primary pic data register
jmp $+2
jmp $+2
mov al, 70h ;ICW2 base address for slave pic = 70h
out 0a1h, al ;send ICW2 to slave pic data register
jmp $+2
jmp $+2
mov al, 4 ;ICW3, on primary pic, bits map to irq lines, use irq 2 for cascade
out 21h, al ;send ICW3 to primary pic data register
jmp $+2
jmp $+2
mov al, 2 ;ICW3, on slave pic, byte value = irq line, use irq 2 for cascade
out 0a1h, al ;send ICW3 to slave pic data register
jmp $+2
jmp $+2
mov al, 1 ;ICW4 set bit 1 to enable 80x86 mode
out 21h, al ;send ICW4 to primary pic data register
jmp $+2
jmp $+2
out 0a1h, al ;send ICW4 to slave pic data register
jmp $+2
jmp $+2
xor al, al ;clear the data registers
out 21h, al
jmp $+2
jmp $+2
out 0a1h, al
jmp $+2
jmp $+2
mov al, m_pic_mask ;al <- master pic mask
out 21h, al ;master pic mask restored
jmp $+2
jmp $+2
mov al, s_pic_mask ;al <- slave pic mask
out 0a1h, al ;slave pic mask restored
jmp $+2
jmp $+2
.386p
lidt ridt_limit ;setup idtr for real mode
.8086
mov ax, 40h
mov es, ax ;access kbd data area via segment 40h
mov word ptr es:[1ah], 1eh ;set the kbd buff head to start of buff
mov word ptr es:[1ch], 1eh ;kbd buff tail = head to clear kbd buffer
in al, 70h ;al <- cmos ram index register port
and al, 7fh ;clear bit 7 to enable nmi
out 70h, al ;nmi enabled
sti ;interrupts enabled
mov ax, 4c00h ;Terminate process function selected
int 21h ;return to dos
align 2 ;align stack for 16-bit accesses
s16 db 256 dup (0ffh) ;256 byte stack, need at least 256 bytes to change video
single ends ;modes (int 10h) with some vga bios
.386p
seg32 segment use32
assume cs:seg32,ds:seg32,ss:seg32
offset_0: ;used to generate 16-bit offsets in idt descriptor definitions
db "start" ;used to find start of segment in debug
div_err: ;division error isr
xor edi, edi
mov byte ptr es:[edi], '0'
hlt
iretd
dont_care: ;rare/obscure faults and exceptions
xor edi, edi
mov byte ptr es:[edi], '1'
hlt
iretd
nmi: ;non maskable interrupt isr
xor edi, edi
mov byte ptr es:[edi], '2'
hlt
iretd
invalid_op: ;invalid opcode isr
xor edi, edi
mov byte ptr es:[edi], '3'
hlt
iretd
double_fault: ;double fault isr
xor edi, edi
mov byte ptr es:[edi], '4'
hlt
iretd
fpu_err: ;fpu error isr
xor edi, edi
mov byte ptr es:[edi], '5'
hlt
iretd
not_present: ;descriptor not present isr
xor edi, edi
mov byte ptr es:[edi], '6'
hlt
iretd
gp_fault: ;general protection fault isr
xor edi, edi
mov byte ptr es:[edi], '7'
hlt
iretd
pit_isr: ;int 20h timer isr
push eax
mov al, 20h
out 20h, al
pop eax
iretd
kbd_isr: ;int 21h keyboard isr
push eax
in al, 60h
mov al, 20h
out 20h, al
pop eax
iretd
sp16 dw ? ;16-bit stack pointer
start32:
mov ax, 10h
mov ds, ax ;ds <- protected mode data descriptor (same physical address as code descriptor)
mov fs, ax
mov gs, ax ;setup extra segments
mov ss, ax ;setup stack segment
mov sp16, sp ;store old stack pointer, restore before returning to real mode
mov esp, offset s32_end ;setup 32-bit stack pointer
mov ax, 30h
mov es, ax ;es <- vga compatible text buffer
sti ;ready for interrupts, leave nmi disabled
call func1
exit_pm: ;return to real mode
cli ;interrupts disabled
mov sp, sp16 ;restore 16-bit stack pointer
mov ax, 20h
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax ;load real mode data descriptor selectors
db 0eah ;jmp 18h:ret_real to load real mode code descriptor
dd offset real_mode ;offset to 16-bit code in single segment
dw 18h ;real mode code selector
db "call_here" ;use this to find call target in debug
func2 proc
push eax
push ebx
push ecx
push edx
push esi
pop esi
pop edx
pop ecx
pop ebx
pop eax
ret
func2 endp
func1 proc
push eax
push ebx
push ecx
push edx
push esi
;do arbitrary work
mov eax, 934875h
xor eax, ebx
inc ecx
mul edx
add edx, 94357h
jmp target1
xor ecx, ecx
add edx, 987h
dec esi
target1:
call func2 ;IT NEVER MAKES IT TO FUNC2
jmp over_marker
db "calladdress" ;use this to find call instruction in debug
over_marker:
pop esi
pop edx
pop ecx
pop ebx
pop eax
ret
func1 endp
align 4 ;align stack for 32-bit accesses
s32 db 256 dup (0ffh) ;256 byte stack
s32_end: ;used to initialize esp
seg32 ends
end start
我认为问题在于 MASM 生成了错误的调用目标,而我正在执行垃圾。
我通过调试加载程序(只是为了检查操作码)对此进行了测试。调试将调用指令加载到 06CA:05A9
并将调用目标(push eax
)加载到 06CA:057B
.调用指令编码为 E8 CD FF 00 00
即 call loc_0000ffd2
.
0x5a9 加上 0xffd2 如果它是 16 位段,则将翻转为 0x57b。或者也许偏移量是有符号的并且是负数?我是否使用了错误的呼叫类型?
问题是 MASM 5.10 linker 有缺陷,不能正确处理这种 32 位重定位。正如您所怀疑的那样,它将 32 位相对位移视为 16 位值,正如您正确观察到的那样,它会产生错误的值(特别是在以负位移调用代码时)。为了测试您的代码,我一直在使用 MASM 5.10a,linker 是版本 3.64。
您可以继续使用 MASM.EXE 5.10a,但您需要更换 linker。 MASM 6.11 附带的 16 位 Microsoft Overlay Linker (LINK.EXE) 可以正常工作。您需要有一个扩展内存管理器才能使 LINK.EXE and/or MASM.EXE 正常运行。 MASM 6.11 是 MASM 产品的最后一个版本,可以从 DOS 运行。 MASM 6.11安装盘可以从here.
下载
Borland 的 TASM 和 TLINK 作为备选方案
如果您下载并安装 Borland's Turbo Assembler v2.0x,您可以 assemble 使用 TASM 和 link 使用 TLINK 的代码。如果您 运行 对 TASM 生成的目标文件进行 TLINK,它实际上会警告您这个问题!错误将类似于:
32-bit record encountered in module Use "/3" option
如果您使用 /3
选项,它会启用 32 位处理,并且应该生成适当的可执行文件。
为了 assemble 使用 TASM(它仍然可以使用 MASM)必须对这些行进行小的调整:
lidt idt_limit ;load idtr
lgdt gdt_limit ;load gdtr
...
lidt ridt_limit ;setup idtr for real mode
TASM 对类型很挑剔,必须写成:
lidt fword ptr idt_limit ;load idtr
lgdt fword ptr gdt_limit ;load gdtr
...
lidt fword ptr ridt_limit ;setup idtr for real mode
JWasm 作为替代品
JWasm 是基于 Watcom 的 assembler (WASM) 的 MASM 兼容开源解决方案,具有更多现代更新。 JWAsm 也可以在 Windows、Linux、MacOS 等其他平台上构建和 运行。JWasm 能够 assemble 文件到 DOS 目标文件(OMF),如 MASM,但它还有一个集成的 16 位 linker,允许您直接构建 DOS MZ 可执行文件。您可以从 here.
下载预构建的 DOS 版本的 JWASM
JWasm 和 TASM 一样对类型很挑剔,所以请参阅有关 fword ptr
的 TASM 部分
要assemble和link将单个源程序集文件转换为DOS可执行文件,您可以简单地执行以下操作:
jwasmr -mz filename.asm
这应该会生成一个名为 filename.exe
的文件
由 Michael Petch 回答,这是我使用的旧软件中的错误。
我没有迁移到新的 assembler/linker,而是决定使用宏,因为 jmp
指令仍然有效。
call32 macro target
push offset $+7
jmp target
endm
start32:
call func1
;...
func2 proc
ret
func2 endp
func1 proc
call32 func2
ret
func1 endp
我在保护模式下调用内存地址比当前函数低的函数时遇到异常。异常会因代码配置而异,有时是一般保护错误,有时是无效操作码等。
这是一个在硬件上产生一般保护错误和在 DOSBox 中产生双重错误的程序的源代码。相关代码在seg32
段。错误发生在func1
,尝试回调func2
single segment stack
assume cs:single,ds:single,ss:single
gdesc struc ;global descriptor structure definition
limit_lo dw 0ffffh ;low word of 20-bit limit (bits 15:0)
base_lo dw ? ;low word of base address (bits 15:0)
base_mid db ? ;middle byte of base address (bits 23:16)
priv db ? ;privilege and type bits
limit_hi db ? ;granularity, operand size, hi nybble of limit (bits 19:16)
base_hi db ? ;high byte of base address (bits 31:24)
gdesc ends
idesc struc ;interrupt descriptor structure definition
offset_lo dw ? ;low word of offset
selector dw ? ;selector in gdt
unused db 0 ;always zero
type_attr db ? ;type attribute bits
offset_hi dw ? ;high word of offset
idesc ends
;global descriptor table, phys addresses calculated by init code
nulld gdesc <0,0,0,0,0,0> ;null descriptor
pcode gdesc <,,,09eh,0cfh,> ;protected mode code descriptor
pdata gdesc <,,,092h,0cfh,> ;protected mode data descriptor
rcode gdesc <,,,09ah,08fh,> ;real mode code descriptor
rdata gdesc <,,,092h,08fh,> ;real mode data descriptor
vbuff gdesc <,0,0ah,092h,0cfh,> ;vga pixel buffer data descriptor
tbuff gdesc <,8000h,0bh,092h,0cfh,> ;text buffer data descriptor
gdt_limit dw offset gdt_limit-offset nulld-1 ;gdt_limit <- gdt size in bytes-1
gdt_addr dd offset nulld ;gdt_addr <- offset of gdt, physical address calculated at runtime
idt_div idesc <div_err-offset_0,8,0,0eeh,0> ;interrupt descriptor table, div error
idesc <dont_care-offset_0,8,0,0eeh,0> ;debugger call
idesc <nmi-offset_0,8,0,0eeh,0> ;nmi interrupt
idesc <dont_care-offset_0,8,0,0eeh,0> ;breakpoint
idesc <dont_care-offset_0,8,0,0eeh,0> ;into overflow
idesc <dont_care-offset_0,8,0,0eeh,0> ;bound overflow
idesc <invalid_op-offset_0,8,0,0eeh,0> ;invalid opcode
idesc <fpu_err-offset_0,8,0,0eeh,0> ;coprocessor unavailable
idesc <double_fault-offset_0,8,0,0eeh,0> ;double fault
idesc <fpu_err-offset_0,8,0,0eeh,0> ;coprocessor overrun
idesc <dont_care-offset_0,8,0,0eeh,0> ;invalid tss
idesc <not_present-offset_0,8,0,0eeh,0> ;segment not present
idesc <dont_care-offset_0,8,0,0eeh,0> ;stack exception
idesc <gp_fault-offset_0,8,0,0eeh,0> ;general protection fault
idesc <dont_care-offset_0,8,0,0eeh,0> ;reserved
idesc <fpu_err-offset_0,8,0,0eeh,0> ;coprocessor error
idesc 16 dup (<dont_care-offset_0,8,0,0eeh,0>) ;16 reserved
idt_pit idesc <pit_isr-offset_0,8,0,0eeh,0> ;timer isr
idt_kbd idesc <kbd_isr-offset_0,8,0,0eeh,0> ;keyboard isr
idt_limit dw offset idt_limit-offset idt_div-1 ;idt_limit <- idt size in bytes-1
idt_addr dd offset idt_div ;idt_addr <- offset of idt, complete physical address
;calculated at runtime
ridt_limit dw 3ffh ;real mode idt limit
ridt_addr dd 0 ;real mode idt address
m_pic_mask db ? ;original master pic mask
s_pic_mask db ? ;original slave pic mask
start:
mov ax, cs
mov ds, ax ;ds = cs, single segment
cli ;disable maskable interrupts
in al, 70h ;al <- cmos ram index register port
or al, 80h ;set bit 7 to disable nmi
out 70h, al ;non-maskable interrupts disabled
;check for 386+
;enable a20
;reinit PICs
mov al, 11h ;ICW1, IC4 bit set, cascade bit clr, edge trig, init bit set
out 20h, al ;send ICW1 to primary pic cmd register
jmp $+2
jmp $+2 ;delay needed on older systems
out 0a0h, al ;send ICW1 to slave pic cmd register
jmp $+2
jmp $+2
mov al, 20h ;ICW2 base address for primary pic = 20h
out 21h, al ;send ICW2 to primary pic data register
jmp $+2
jmp $+2
mov al, 28h ;ICW2 base address for slave pic = 28h
out 0a1h, al ;send ICW2 to slave pic data register
jmp $+2
jmp $+2
mov al, 4 ;ICW3, on primary pic, bits map to irq lines, use irq 2 for cascade
out 21h, al ;send ICW3 to primary pic data register
jmp $+2
jmp $+2
mov al, 2 ;ICW3, on slave pic, byte value = irq line, use irq 2 for cascade
out 0a1h, al ;send ICW3 to slave pic data register
jmp $+2
jmp $+2
mov al, 1 ;ICW4 set bit 1 to enable 80x86 mode
out 21h, al ;send ICW4 to primary pic data register
jmp $+2
jmp $+2
out 0a1h, al ;send ICW4 to slave pic data register
jmp $+2
jmp $+2
xor al, al ;clear the data registers
out 21h, al
jmp $+2
jmp $+2
out 0a1h, al
jmp $+2
jmp $+2
in al, 21h ;only need keyboard and timer irq enabled for now
mov m_pic_mask, al ;store original master pic mask register, restore before exit
or al, 0fch ;mask out all but irq 0 and 1
out 21h, al ;master pic mask updated
jmp $+2
jmp $+2
in al, 0a1h
mov s_pic_mask, al ;store original slave pic mask register, restore before exit
or al, 0ffh ;mask out every slave irq
out 0a1h, al
jmp $+2
jmp $+2
.386p ;calc and insert phys address into gdt entries
xor eax, eax ;clear high word of eax
mov ax, cs ;eax <- code segment address
shl eax, 4 ;multiply segment address by 16 to convert it to physical address
add gdt_addr, eax ;gdt_addr is defined with offset of gdt, gdt_addr + cs*16 = physical addres of gdt
add idt_addr, eax ;idt_addr is defined with offset of idt, idt_addr + cs*16 = physical addres of idt
lidt idt_limit ;load idtr
lgdt gdt_limit ;load gdtr
mov rcode.base_lo, ax
mov rdata.base_lo, ax ;store low word of cs phys address to real mode descriptors
shr eax, 16 ;shift eax to access high word
mov rcode.base_mid, al
mov rdata.base_mid, al ;store middle byte of cs phys address to real mode descriptors
mov rcode.base_hi, al
mov rdata.base_hi, al ;store high byte of cs phys address to real mode descriptors
xor eax, eax ;clear high word of eax
mov ax, seg seg32 ;eax <- seg32 segment address (fixed up by dos at runtime)
shl eax, 4 ;multiply segment address by 16 to convert it to physical address
mov pcode.base_lo, ax
mov pdata.base_lo, ax ;store low word of seg32 phys address to protected mode descriptors
shr eax, 16 ;shift eax to access high word
mov pcode.base_mid, al
mov pdata.base_mid, al ;store middle byte of seg32 phys address to protected mode descriptors
mov pcode.base_hi, al
mov pdata.base_hi, al ;store high byte of seg32 phys address to protected mode descriptors
mov eax, cr0 ;load control register 0
or al, 1 ;set pe bit
mov cr0, eax ;enable protected mode
;manually encoded jmp 8h:start32
db 66h ;specify 32-bit operand
db 0eah ;jmp opcode
dd offset start32 ;32 bit offset
dw 8 ;global descriptor selector (select protected mode code segment)
real_mode: ;transition back to real mode
.386p
mov eax, cr0 ;load control register into eax
and al, 0feh ;clear pe bit
mov cr0, eax ;real mode enabled
db 0eah ;jmp single:real_cs to load cs:ip
dw offset real_cs ;offset real_cs
dw seg single ;segment single (fixed up by dos at runtime)
real_cs: ;back in real mode
.8086
mov ax, cs
mov ds, ax ;ds = cs
mov ss, ax ;ss = cs
mov al, 11h ;ICW1, IC4 bit set, cascade bit clr, edge trig, init bit set
out 20h, al ;send ICW1 to primary pic cmd register
jmp $+2
jmp $+2 ;delay needed on older systems
out 0a0h, al ;send ICW1 to slave pic cmd register
jmp $+2
jmp $+2
mov al, 8 ;ICW2 base address for primary pic = 8
out 21h, al ;send ICW2 to primary pic data register
jmp $+2
jmp $+2
mov al, 70h ;ICW2 base address for slave pic = 70h
out 0a1h, al ;send ICW2 to slave pic data register
jmp $+2
jmp $+2
mov al, 4 ;ICW3, on primary pic, bits map to irq lines, use irq 2 for cascade
out 21h, al ;send ICW3 to primary pic data register
jmp $+2
jmp $+2
mov al, 2 ;ICW3, on slave pic, byte value = irq line, use irq 2 for cascade
out 0a1h, al ;send ICW3 to slave pic data register
jmp $+2
jmp $+2
mov al, 1 ;ICW4 set bit 1 to enable 80x86 mode
out 21h, al ;send ICW4 to primary pic data register
jmp $+2
jmp $+2
out 0a1h, al ;send ICW4 to slave pic data register
jmp $+2
jmp $+2
xor al, al ;clear the data registers
out 21h, al
jmp $+2
jmp $+2
out 0a1h, al
jmp $+2
jmp $+2
mov al, m_pic_mask ;al <- master pic mask
out 21h, al ;master pic mask restored
jmp $+2
jmp $+2
mov al, s_pic_mask ;al <- slave pic mask
out 0a1h, al ;slave pic mask restored
jmp $+2
jmp $+2
.386p
lidt ridt_limit ;setup idtr for real mode
.8086
mov ax, 40h
mov es, ax ;access kbd data area via segment 40h
mov word ptr es:[1ah], 1eh ;set the kbd buff head to start of buff
mov word ptr es:[1ch], 1eh ;kbd buff tail = head to clear kbd buffer
in al, 70h ;al <- cmos ram index register port
and al, 7fh ;clear bit 7 to enable nmi
out 70h, al ;nmi enabled
sti ;interrupts enabled
mov ax, 4c00h ;Terminate process function selected
int 21h ;return to dos
align 2 ;align stack for 16-bit accesses
s16 db 256 dup (0ffh) ;256 byte stack, need at least 256 bytes to change video
single ends ;modes (int 10h) with some vga bios
.386p
seg32 segment use32
assume cs:seg32,ds:seg32,ss:seg32
offset_0: ;used to generate 16-bit offsets in idt descriptor definitions
db "start" ;used to find start of segment in debug
div_err: ;division error isr
xor edi, edi
mov byte ptr es:[edi], '0'
hlt
iretd
dont_care: ;rare/obscure faults and exceptions
xor edi, edi
mov byte ptr es:[edi], '1'
hlt
iretd
nmi: ;non maskable interrupt isr
xor edi, edi
mov byte ptr es:[edi], '2'
hlt
iretd
invalid_op: ;invalid opcode isr
xor edi, edi
mov byte ptr es:[edi], '3'
hlt
iretd
double_fault: ;double fault isr
xor edi, edi
mov byte ptr es:[edi], '4'
hlt
iretd
fpu_err: ;fpu error isr
xor edi, edi
mov byte ptr es:[edi], '5'
hlt
iretd
not_present: ;descriptor not present isr
xor edi, edi
mov byte ptr es:[edi], '6'
hlt
iretd
gp_fault: ;general protection fault isr
xor edi, edi
mov byte ptr es:[edi], '7'
hlt
iretd
pit_isr: ;int 20h timer isr
push eax
mov al, 20h
out 20h, al
pop eax
iretd
kbd_isr: ;int 21h keyboard isr
push eax
in al, 60h
mov al, 20h
out 20h, al
pop eax
iretd
sp16 dw ? ;16-bit stack pointer
start32:
mov ax, 10h
mov ds, ax ;ds <- protected mode data descriptor (same physical address as code descriptor)
mov fs, ax
mov gs, ax ;setup extra segments
mov ss, ax ;setup stack segment
mov sp16, sp ;store old stack pointer, restore before returning to real mode
mov esp, offset s32_end ;setup 32-bit stack pointer
mov ax, 30h
mov es, ax ;es <- vga compatible text buffer
sti ;ready for interrupts, leave nmi disabled
call func1
exit_pm: ;return to real mode
cli ;interrupts disabled
mov sp, sp16 ;restore 16-bit stack pointer
mov ax, 20h
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax ;load real mode data descriptor selectors
db 0eah ;jmp 18h:ret_real to load real mode code descriptor
dd offset real_mode ;offset to 16-bit code in single segment
dw 18h ;real mode code selector
db "call_here" ;use this to find call target in debug
func2 proc
push eax
push ebx
push ecx
push edx
push esi
pop esi
pop edx
pop ecx
pop ebx
pop eax
ret
func2 endp
func1 proc
push eax
push ebx
push ecx
push edx
push esi
;do arbitrary work
mov eax, 934875h
xor eax, ebx
inc ecx
mul edx
add edx, 94357h
jmp target1
xor ecx, ecx
add edx, 987h
dec esi
target1:
call func2 ;IT NEVER MAKES IT TO FUNC2
jmp over_marker
db "calladdress" ;use this to find call instruction in debug
over_marker:
pop esi
pop edx
pop ecx
pop ebx
pop eax
ret
func1 endp
align 4 ;align stack for 32-bit accesses
s32 db 256 dup (0ffh) ;256 byte stack
s32_end: ;used to initialize esp
seg32 ends
end start
我认为问题在于 MASM 生成了错误的调用目标,而我正在执行垃圾。
我通过调试加载程序(只是为了检查操作码)对此进行了测试。调试将调用指令加载到 06CA:05A9
并将调用目标(push eax
)加载到 06CA:057B
.调用指令编码为 E8 CD FF 00 00
即 call loc_0000ffd2
.
0x5a9 加上 0xffd2 如果它是 16 位段,则将翻转为 0x57b。或者也许偏移量是有符号的并且是负数?我是否使用了错误的呼叫类型?
问题是 MASM 5.10 linker 有缺陷,不能正确处理这种 32 位重定位。正如您所怀疑的那样,它将 32 位相对位移视为 16 位值,正如您正确观察到的那样,它会产生错误的值(特别是在以负位移调用代码时)。为了测试您的代码,我一直在使用 MASM 5.10a,linker 是版本 3.64。
您可以继续使用 MASM.EXE 5.10a,但您需要更换 linker。 MASM 6.11 附带的 16 位 Microsoft Overlay Linker (LINK.EXE) 可以正常工作。您需要有一个扩展内存管理器才能使 LINK.EXE and/or MASM.EXE 正常运行。 MASM 6.11 是 MASM 产品的最后一个版本,可以从 DOS 运行。 MASM 6.11安装盘可以从here.
下载Borland 的 TASM 和 TLINK 作为备选方案
如果您下载并安装 Borland's Turbo Assembler v2.0x,您可以 assemble 使用 TASM 和 link 使用 TLINK 的代码。如果您 运行 对 TASM 生成的目标文件进行 TLINK,它实际上会警告您这个问题!错误将类似于:
32-bit record encountered in module Use "/3" option
如果您使用 /3
选项,它会启用 32 位处理,并且应该生成适当的可执行文件。
为了 assemble 使用 TASM(它仍然可以使用 MASM)必须对这些行进行小的调整:
lidt idt_limit ;load idtr
lgdt gdt_limit ;load gdtr
...
lidt ridt_limit ;setup idtr for real mode
TASM 对类型很挑剔,必须写成:
lidt fword ptr idt_limit ;load idtr
lgdt fword ptr gdt_limit ;load gdtr
...
lidt fword ptr ridt_limit ;setup idtr for real mode
JWasm 作为替代品
JWasm 是基于 Watcom 的 assembler (WASM) 的 MASM 兼容开源解决方案,具有更多现代更新。 JWAsm 也可以在 Windows、Linux、MacOS 等其他平台上构建和 运行。JWasm 能够 assemble 文件到 DOS 目标文件(OMF),如 MASM,但它还有一个集成的 16 位 linker,允许您直接构建 DOS MZ 可执行文件。您可以从 here.
下载预构建的 DOS 版本的 JWASMJWasm 和 TASM 一样对类型很挑剔,所以请参阅有关 fword ptr
要assemble和link将单个源程序集文件转换为DOS可执行文件,您可以简单地执行以下操作:
jwasmr -mz filename.asm
这应该会生成一个名为 filename.exe
由 Michael Petch 回答,这是我使用的旧软件中的错误。
我没有迁移到新的 assembler/linker,而是决定使用宏,因为 jmp
指令仍然有效。
call32 macro target
push offset $+7
jmp target
endm
start32:
call func1
;...
func2 proc
ret
func2 endp
func1 proc
call32 func2
ret
func1 endp