简体   繁体   English

MASM x86,ret 返回错误地址

[英]MASM x86 , ret returns to a wrong address

I have an assembly code which loads a COM file to the memory and runs it.我有一个汇编代码,它将 COM 文件加载到 memory 并运行它。 The COM file will be loaded to a separate data-segment, changes the DS and SS to that segment and calls it to run the COM file. COM 文件将加载到单独的数据段,将 DS 和 SS 更改为该段并调用它以运行 COM 文件。 My data and the Stack segments are:我的数据和堆栈段是:

STACKSEG SEGMENT  STACK 'stack'
    DW 512 DUP(?)
STACKSEG ENDS

DATASEG1 SEGMENT PARA  'data'
    
    stacksp dw 0
    stackbp dw 0
DATASEG1 ENDS

DATASEG2 SEGMENT PARA 'data'
WHERETOJ:
    ;stck db 32 dup (?)
    txt db 4096 dup (?)  , "$"
    
    ;string B
DATASEG2 ENDS

and my caller is:我的来电者是:

mov stacksp, sp
            mov stackbp, bp
            ASSUME DS: DATASEG2, SS:DATASEG2
            mov ax, DATASEG2
            mov DS, AX
            mov ES, AX
            MOV SS, AX
                   
            
            mov SP, 0FFFFh
               call far ptr WHERETOJ ; (Now registers are SP:FFFF, BP:0, SI:0C40, DI:0C16, DS:1820,ES:1820, SS:1820, CS:192D, IP:00BD)

            ASSUME DS: DATASEG1, SS:STACKSEG
            mov ax, STACKSEG
            MOV SS, AX

            MOV AX, DATASEG1
            MOV DS, AX
            MOV ES, AX

               
            mov bx, offset stackbp
            mov bp, [bx]
            mov bx, offset  stacksp
            mov sp, [bx]


            ret

The COM file just runs int 10h to print a character and should return to the caller: COM 文件只运行 int 10h 来打印一个字符,并且应该返回给调用者:

        mov ax, 0945h  ; (Now registers are changed to SP:FFFB, BP:0, SI:0C40, DI:0C16, DS:1820,ES:1820, SS:1820, CS:1820, IP:0000)
        mov bx, 0006
        mov cx, 40
        int 10h
        
        ret ;; ( registers are   SP:FFFB, BP:0, SI:0C40, DI:0C16, DS:1820,ES:1820, SS:1820, CS:1820, IP:000B)

;;ret is ran, the registers are: SP:FFFBD, BP:0, SI:0C40, DI:0C16, DS:1820,ES:1820, SS:1820, CS:1820, IP:00C2

The problem is that when COM file is ran, the ret does not return to the caller but to a wrong unknown IP.问题是当COM文件运行时, ret不会返回给调用者,而是返回到错误的未知 IP。

Your primary issue is that the RET you are performing is a NEAR return.您的主要问题是您正在执行的RET是 NEAR 回报。 A NEAR return in real mode will result in a 16-bit offset being popped off the stack and the IP (Instruction Pointer) being set to that value.实模式下的 NEAR 返回将导致 16 位偏移量从堆栈中弹出,并将IP (指令指针)设置为该值。 The segment will not change.段不会改变。

Your code:你的代码:

call far ptr WHERETOJ

Is a FAR CALL and pushed the 16-bit Code Segment ( CS ) followed by the 16-bit IP.是 FAR CALL并推送 16 位代码段 ( CS ),后跟 16 位 IP。 The NEAR RETURN only popped off the 16-bit IP and left the segment on the stack. NEAR RETURN 仅弹出 16 位 IP 并将段留在堆栈上。

At the point of the FAR CALL you said the registers had:在 FAR CALL 时,您说寄存器有:

SP:FFFF, SS:1820, CS:192D, IP:00BD SP:FFFF, SS:1820, CS:192D, IP:00BD

A CALL pushes the CS followed by IP of the instruction after the CALL . A CALL推动CS后跟IP之后的指令的CALL The FAR CALL is encoded as a 5 byte instruction so the address pushed on the stack is 192Dh:00C2h (00BDh+5=00C2h). FAR CALL 编码为 5 字节指令,因此压入堆栈的地址为 192Dh:00C2h (00BDh+5=00C2h)。 When you did the NEAR return it didn't change CS but it changed IP to 00C2h.当您执行 NEAR 返回时,它没有更改CS ,但将IP更改为 00C2h。 It only popped 2 bytes off the stack as well.它也只从堆栈中弹出 2 个字节。 This is why you saw this in the debugger when the RET instruction was executed:这就是为什么您在执行RET指令时在调试器中看到这一点的原因:

SP:FFFD, SS:1820, CS:1820, IP:00C2 SP:FFFD, SS:1820, CS:1820, IP:00C2

SP was incremented by 2 from 0FFFBh to 0FFFDh. SP从 0FFFBh 到 0FFFDh 递增 2。 CS remained the same and IP was set to 00C2h. CS保持不变, IP设置为 00C2h。 The CS:IP pair is incorrect so you ended up executing memory you didn't intend to. CS:IP 对不正确,因此您最终执行了您不打算执行的 memory。 If you replace RET with RETF (FAR RETURN) then your code would have worked as expected and the registers would have had these values when RET was executed:如果您将RET替换为RETF (FAR RETURN),那么您的代码将按预期工作,并且在执行RET时寄存器将具有以下值:

SP:FFFF, SS:1820, CS:192D, IP:00C2 SP:FFFF, SS:1820, CS:192D, IP:00C2


DOS COM programs DOS COM 程序

You use the term COM program, but your code suggests it is likely a binary with an ORG (origin point) of 0000h instead of a typical ORG of 0100h in DOS COM programs.您使用术语 COM 程序,但您的代码表明它可能是一个二进制文件,其 ORG(原点)为 0000h,而不是 DOS COM 程序中的典型 ORG 为 0100h。 To be compatible with DOS COM you have to load the code and data at an offset 256 bytes from the beginning of the Code Segment the program will be run from.为了与 DOS COM 兼容,您必须在程序将运行的代码段开头的 256 字节偏移处加载代码和数据。 In a DOS COM program, the first instruction executed is CS:0100h and not CS:0000h在 DOS COM 程序中,执行的第一条指令是 CS:0100h 而不是 CS:0000h

In a typical DOS COM program the DOS loader pushes 0000h on the top of the stack.在典型的 DOS COM 程序中,DOS 加载程序将 0000h 压入堆栈顶部。 If you do a NEAR RET that will start executing at CS:0000h.如果您执行 NEAR RET,它将在 CS:0000h 开始执行。 The first 256 bytes contain the DOS Program Segment Prefix (PSP) .前 256 个字节包含 DOS程序段前缀 (PSP) The first 2 bytes of the PSP (and thus CS:0000h) are an INT 20H instruction. PSP 的前 2 个字节(因此也是 CS:0000h)是INT 20H指令。

在此处输入图像描述

INT 20h will terminate a DOS COM program and return an ERRORLEVEL of 0 to the DOS command prompt that launched the program. INT 20h将终止 DOS COM 程序,并向启动该程序的 DOS 命令提示符返回 ERRORLEVEL 0。 INT 20h should not be used to exit DOS EXE programs, you use INT 21h/AH=4C instead.不应使用INT 20h退出 DOS EXE 程序,而应使用INT 21h/AH=4C

If your intention is to find a way to use a NEAR RETURN to exit your program like DOS does then you will have to provide a mechanism (code) inside the code segment to do that.如果您的意图是找到一种方法来使用 NEAR RETURN 像 DOS 那样退出您的程序,那么您必须在代码段内提供一种机制(代码)来做到这一点。 Since you don't have a DOS PSP (or have chosen not to use one) you will have to find a place to copy such code within the segment.由于您没有 DOS PSP(或选择不使用),您将不得不在该段中找到复制此类代码的位置。 The easiest mechanism is to create a code trampoline on the stack before you start executing the program.最简单的机制是在开始执行程序之前在堆栈上创建一个代码蹦床 The simplest way is to push a FAR CALL (or FAR JMP) onto the stack that takes you back to the instruction after the call far ptr WHERETOJ instruction.最简单的方法是将 FAR CALL(或 FAR JMP)压入堆栈,让您回到call far ptr WHERETOJ指令之后的指令。

A FAR CALL is encoded on the stack as: FAR CALL 在堆栈上编码为:

9A oooo ssss 9A 呜呜呜

Where 9A is the opcode for a FAR CALL, oooo is the offset to call, ssss is the segment to use.其中9A是 FAR CALL 的操作码, oooo是要调用的偏移量, ssss是要使用的段。 We want to keep the stack pointer ( SP ) on an even alignment 1 so we add a NOP instruction for a total of 6 bytes (6 is an even number).我们希望将堆栈指针 ( SP ) 保留在偶数 alignment 1上,因此我们添加了一条总共 6 个字节的NOP指令(6 是偶数)。 The stack would look like:堆栈看起来像:

90 9A oooo ssss 90 9A 呜呜呜呜

Once you have the NOP+FAR CALL built on the stack, you need to push the offset of that code so that a NEAR RET will end up calling it when executed.在堆栈上构建 NOP+FAR CALL 后,您需要推送该代码的偏移量,以便 NEAR RET 在执行时最终会调用它。

The path of execution in the COM program will be NEAR RET ( ret ) causing IP to change to the address on the stack where the NOP+FAR CALL is and execute that instruction to call back to the location of the FAR JMP used to start executing the COM program in the first place. COM 程序中的执行路径将是 NEAR RET ( ret ),导致IP更改为堆栈上 NOP+FAR CALL 所在的地址,并执行该指令以回调用于开始执行的 FAR JMP 的位置首先是 COM 程序。 You could have encoded a NOP+FAR JMP on the stack as well, but the NOP+FAR CALL has the advantage of pushing the value of CS on the stack which could be useful later on, especially if you load more than one COM program in memory.您也可以在堆栈上编码一个 NOP+FAR JMP,但是 NOP+FAR CALL 具有将CS的值推送到堆栈上的优点,这在以后可能很有用,特别是如果您在其中加载多个 COM 程序memory。

A sample program written to run on 8086 or later processors could look like this:为在 8086 或更高版本的处理器上运行而编写的示例程序可能如下所示:

.8086

STACKSEG SEGMENT  STACK 'stack'
    DW 512 DUP(?)
STACKSEG ENDS

DATASEG1 SEGMENT PARA  'data'
    finstr db 0dh, 0ah, 'Returned from COM program', 0dh, 0ah, '$'    
    stacksp dw 0
    stackbp dw 0
DATASEG1 ENDS

DATASEG2 SEGMENT PARA 'data'
WHERETOJ:
    mov ax, 0945h
    mov bx, 0057h
    mov cx, 40
    int 10h   
    ret
    org 65536            ; Expand the segment to 64KiB
DATASEG2 ENDS

CODESEG1 SEGMENT PARA 'code'
main:
    ASSUME DS: DATASEG1, SS:STACKSEG
    mov [stacksp], SP
    mov [stackbp], BP

    ASSUME DS: DATASEG2, SS:DATASEG2
    mov AX, DATASEG2
    mov DS, AX
    mov ES, AX           ; DS=ES=SS=DATASEG2

    ; CLI                ; If running on BUGGY 8088 you would need to have CLI/STI
    MOV SS, AX
    xor SP, SP           ; SP = 0. Grow down from top of 64KiB SS segment
    ; STI

    ; Build FAR CALL on the COM programs stack
    ; to return to this code when NEAR RET done
    push CS              ; Put CS on stack as part of FAR CALL
    mov AX, offset aftercom
                         ; Push the IP of the instruction after the FAR JMP below
    push AX
    mov AX, 09a90h       ; Put a NOP(90h) on the stack and 9AH (FAR CALL opcode)
    push AX              ; NOP used as padding to keep SP aligned on an even address

    mov AX, SP
    push AX              ; Push a copy of SP on the stack. SP is the address of the
                         ;     NOP
                         ;     CALL FAR PTR segment:offset instruction built on stack

    jmp far ptr WHERETOJ ; Start executing our program code
aftercom:
    add SP, 10           ; When we return the stack has 10 bytes on it (6 bytes
                         ;     for FAR CALL and the NOP + 4 bytes of the CALLers
                         ;     IP and CS). Clean them up

    ASSUME DS: DATASEG1, SS:STACKSEG
    MOV AX, DATASEG1
    MOV DS, AX
    MOV ES, AX

    mov AX, STACKSEG
    ; CLI                ; If running on BUGGY 8088 you would need to have CLI/STI
    MOV SS, AX           ; Restore SS:SP one after another since interrupts
                         ;     will be off until the instruction after changing SS
    mov SP, [stacksp]
    ; STI
    mov BP, [stackbp]
   
    mov AH, 09           ; Display a string saying we returned
    mov DX, offset finstr
    int 21h

    mov AX, 4c00h        ; Exit DOS EXE program with ERRORLEVEL 0
    int 21h
CODESEG1 ENDS

END main

A properly functioning version of the code would look similar to this when run:正常运行的代码版本在运行时看起来与此类似:

在此处输入图像描述


Footnotes脚注

  • 1 In real mode you should always align the stack pointer ( SP ) to an even offset for performance reasons. 1在实模式下,出于性能原因,您应该始终将堆栈指针 ( SP ) 对齐到一个偶数偏移量。 Rather than use 0FFFFh as a starting stack address use 0000h instead.不要使用 0FFFFh 作为起始堆栈地址,而是使用 0000h。 The first PUSH that gets done will wrap SP to 0FFFEh (0000h-0002h=0FFFEh) before writing the value on the stack.在将值写入堆栈之前,完成的第一个PUSH会将SP包装到 0FFFEh (0000h-0002h=0FFFEh)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM