(说明:1、采用nasm语法,';'冒号表示后面的为注释
2、程序是在实模式下运行的,所以所有寄存器都是16位的,地址也是16位表示的
3、程序的功能是调用一个函数将一个字符串打印出来,字符串长度参数保存在栈中
4、将每条语句、数据在内存中的地址以及有些寄存器中的值显示出来
5、可以使用nasm编译生成com文件,在DOS虚拟机中使用Turbo Debugger(tasm内嵌工具)调试)
org 0x100
jmp code ;cs:0100: jmp 0x0118 ;E91500
[section .data] ;cs:0103; ;00为何空1个字节,难道是为了对齐?
param1 dw 0x000D ;ds:0104: ;0D 00数据从低地址到高地址排列
param2 dd 0x00000001 ;ds:0106: ;01 00 00 00
strHello db "Hello, world!",0Ah ;ds:010A~011F: ;48 65 6C 6C ...0A
STRLEN equ $ - strHello
[section .code]
code:
mov ax, cs ;cs:0118; ;8CC8
mov ds, ax ;cs:011A; ;8ED8
mov es, ax ;cs:011C; ;8EC0
mov ss, ax ;cs:011E; ;8ED0
mov sp, 0x100 ;0~0x100可作为堆栈空间;cs:0120 ;BC0001
;mov ax, STRLEN
;mov word [param1], ax ;其实应该要这么写
push word [param1] ;cs:0123; push word ptr [0104] ;FF360401
push dword [param2] ;cs:0127; push dword ptr [0106] ;66FF360601
;0x0104,0x0106即为param2,param1的地址,这里知道push的是变量的值
;如果是push param1/param2的话则是param1/param2的地址,因为是在16位(dos)下,地址值占两个字节.
;此时堆栈0x0100前6个字节的值ds:00FA: 01 00 00 00 0D 00
;sp的值为00FA
call called ;cs:012C; call 0134 ;E80500
;要调试的话,需要在called中设断点,或者F7Trace,就相当于C语言中一样
;实际上这还是段内跳转,cs没有变,如何实现段间跳转呢?
jmp $ ;cs:012F; jmp 012F ;EBFE
[section .called] ;cs:0131; ;000000;为何留三个字节,难道又是为了对齐?
called:
push bp ;cs:0134; ;55
mov bp, sp ;防止修改堆栈把调用函数的数据覆盖;cs:0135 ;89E5
mov ax, strHello ;cs:0137; mov ax, 010A ;B80A01
;010A为strHello的地址
mov bp, ax ;cs:013A; ;89C5
mov dl, 0 ;cs:013C; ;B200
mov cx, [esp+8] ;cs:013E; ;678B442408
;涉及到取地址时,不能是两个字节的寄存器如sp,只能是esp!
;这时的堆栈如下所示
;high address(00FF) 00
; (00FE) 0D (0x000D表示param1的值,地址为0x0104)
; (00FD) 00
; (00FC) 00
; (00FB) 00
; (00FA) 01 (0x00000001表示param1的值,地址为0x0106)
; (00F9) 01
; (00F8) 2F (0x012F表示原来函数调用之前eip的地址)
; (00F7) 00
; low address(00F6) 00 (0x0000表示原来bp的值)
mov bx, 0x0c ;cs:0143; mov ax, 000C ;BB0C00
mov ax, 0x1301 ;cs:0146; ;B80113
int 0x10 ;系统调用 ;cs:0149; int 10 ;CD10
pop bp ;cs:014B; ;5D
ret 6 ;ret指令本身会将eip弹出,所以这里sp+6,返回之后使得
;sp重新执行0x0100