MASM在保护模式下产生错误的调用目标。

问题描述 投票:1回答:1

我在保护模式下调用内存地址比当前函数低的函数时,遇到了异常。异常会根据代码配置的不同而不同,有时是一般保护故障,有时是无效操作码等。

下面是一个程序的源代码,在硬件上产生一般保护故障,在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生成了错误的调用目标,我正在执行垃圾。

我用debug加载程序测试了这个问题(只是为了检查操作码),debug加载调用指令到 06CA:05A9 和呼叫目标(push eax)至 06CA:057B. 调用指令的编码为 E8 CD FF 00 00 也就是 call loc_0000ffd2.

0x5a9加上0xffd2如果是16位的段,就会翻转成0x57b。或者,也许偏移量是有符号的,那是一个负数?我是不是用错了调用类型?

assembly x86 masm
1个回答
6
投票

问题是MASM 5.10链接器有缺陷,不能正确处理这种32位的重新定位。正如你所怀疑的那样,它将32位的相对位移视为一个16位的值,正如你所观察到的那样,它产生了错误的值(尤其是当调用负位移的代码时)。为了测试您的代码,我一直在使用MASM 5.10a,链接器是3.64版本。

你可以继续使用MASM.EXE 5.10a,但你需要更换你的链接器。与MASM 6.11配套的16位Microsoft Overlay Linker (LINK.EXE)可以正常工作。你将需要有一个扩展的内存管理器存在的LINK.EXE和或MASM.EXE正常工作。MASM 6.11是MASM产品的最后一个版本,可以从DOS运行。MASM 6.11的安装盘可以从以下网站下载。此处.


Borland的TASM和TLINK作为一种替代方案

如果您下载并安装 Borland's Turbo Assembler v2.0x (TASM) 你可以用TASM组装你的代码,然后用TLINK链接。如果你在TASM产生的对象文件上运行TLINK,它实际上会警告你这个问题! 这个错误看起来像这样。

模块中遇到32位记录,使用 "3 "选项。

如果您使用 /3 选项,它使32位处理成为可能,一个正确的可执行文件应该被生成。

如果要用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是一个兼容MASM的开源解决方案,基于Watcom的汇编器(WASM),并进行了更多的现代化更新。JWAsm也可以在其他平台上构建和运行,如Windows,Linux,MacOS等。JWasm能够像MASM一样将文件组装成DOS对象文件(OMF),但它也有一个集成的16位链接器,允许你直接构建一个DOS MZ可执行文件。你可以从以下网站下载一个预制的DOS版本的JWASM。此处.

JWasm和TASM一样对类型很挑剔,所以请看TASM部分关于 fword ptr

要组装和链接一个单一的源程序文件到一个DOS可执行文件,你可以简单地这样做。

jwasmr -mz filename.asm

这将产生一个名为 filename.exe


1
投票

由 Michael Petch 回答,这是我使用的老旧过时软件中的一个 bug。

我没有迁移到一个新的汇编器链接器,而是使用了一个宏,因为这个宏是由 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
© www.soinside.com 2019 - 2024. All rights reserved.