开学三周,王爽的《汇编语言》(第三版)总算是基本上看完了,本文是总结的第三部分也是最后一部分(大概),主要是书后的综合研究

研究试验1 搭建一个精简的C语言开发环境

TC进行连接需要如下的文件:

  • C0S.OBJ
  • CS.LIB
  • EMU.LIB
  • GRAPHICS.LIB
  • MATHS.LIB

研究试验2 使用寄存器

  1. 编一个程序ur1.c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    main ()
    {
    _AX = 1;
    _BX = 1;
    _CX = 2;
    _AX = _BX + _CX;
    _AH = _BL + _CL;
    _AL = _BH + _CH;
    }
  2. 用Debug加载ur1.exe,用u命令查看ur1.exe编译后的机器码和汇编代码。
    思考:main函数的代码在什么段中?用Debug怎样找到ur1.exe中main函数的代码?
    回答:main函数的代码在CS段中,需要知道main函数的偏移地址才能找到main函数的代码。
  3. 用下面的方法打印出ur1.exe被加载运行时,main函数在代码段中的偏移地址。
    1
    2
    3
    4
    main ()
    {
    printf("%x\n", main);
    }
    思考:为什么这个程序能够打印出main函数在代码段中的偏移地址?
    回答:由学过的知识可知,C语言中用指针表示地址,这里的printf打印了main函数的入口地址,也即main函数在代码段中的偏移地址。
  4. 用Debug加载ur1.exe,根据上面打印出的main函数的偏移地址,用u命令察看main函数的汇编代码。仔细找到ur1.c中每条C语句对应的汇编代码。

    地址汇编语句C语句
    076A:01FAPUSH BP____
    076A:01FBMOV BP,SP____
    076A:01FDMOV AX,0001_AX = 1
    076A:0200MOV BX,0001_BX = 1
    076A:0203MOV CX,0002_CX = 2
    076A:0206MOV AX,BX____
    076A:0208ADD AX,CX_AX = _BX + _CX
    076A:020AMOV AH,BL____
    076A:020CADD AH,CL_AH = _BL + _CL
    076A:020EMOV AL,BH____
    076A:0210ADD AL,CH_AL = _BH + _CH
    076A:0212POP BP____
    076A:0213RET____

    注意:在这里,对于main函数汇编代码开始处的“push bp mov bp,sp”和结尾处的“pop bp”,这里只了解到:这是C编译器安排的为函数中可能使用到bp寄存器而设置的,就可以了。

  5. 通过main函数后面有ret指令,我们可以设想:C语言将函数实现为汇编语言中的子程序。研究下面程序的汇编代码,验证我们的设想。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void f(void);

    main()
    {
    _AX = 1; _BX = 1; _CX = 2;

    f();
    }

    void f(void)
    {
    _AX = _BX + _CX;
    }

    编译、连接后用Debug查看汇编代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    PUSH BP
    MOV BP,SP
    MOV AX,0001
    MOV BX,0001
    MOV CX,0002
    CALL 020B
    POP BP
    RET
    PUSH BP
    MOV BP,SP
    MOV AX,BX
    ADD AX,CX
    POP BP
    RET

    设想正确

研究试验3 使用内存空间

  1. 编一个程序um1.c:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    main()
    {
    *(char *)0x2000='a';
    *(int *)0x2000=0xf;
    *(char far *)0x20001000='a';

    _AX=0x2000;
    *(char *)_AX='b';

    _BX=0x1000;
    *(char *)(_BX+_BX)='a';
    *(char far *)(0x20001000+_BX)=*(char *)_AX;
    }

    把um1.c保存在C:\MINIC下,编译,连接生成um1.exe。然后用Debug加载um1.exe,对main函数的汇编代码进行分析,找到每条C语句对应的汇编代码;对main函数进行单步跟踪,察看相关内存单元的内容。

    地址汇编语句C语句相关内存单元
    076A:01FAPUSH BP________
    076A:01FBMOV BP,SP________
    076A:01FDMOV BYTE PTR [2000],61*(char *)0x2000=’a’07C4:2000 -> 61
    076A:0202MOV WORD PTR [2000],000F*(int *)0x2000=0xf07C4:2000-07C4:2001 -> 0F 00
    076A:0208MOV BX,2000________
    076A:020BMOV ES,BX________
    076A:020DMOV BX,1000________
    076A:0210ES:
    076A:0211MOV BYTE PTR [BX],61*(char far *)0x20001000=’a’2000:1000 -> 61
    076A:0214MOV AX,2000_AX=0x2000____
    076A:0217MOV BX,AX________
    076A:0219MOV BYTE PTR [BX],62*(char *)_AX=’b’07C4:2000 -> 62
    076A:021CMOV BX,1000_BX=0x1000____
    076A:021FADD BX,BX________
    076A:0221MOV BYTE PTR [BX],61*(char *)(_BX + _BX)=’a’07C4:2000 -> 61
    076A:0224MOV BX,AX________
    076A:0226MOV AL,[BX]________
    076A:0228XOR CX,CX________
    076A:022AADD BX,1000________
    076A:022EADC CX,2000________
    076A:0232MOV ES,CX________
    076A:0234ES:
    076A:0235MOV [BX],AL&#142(char far *)(0x20001000+BX)=*(char *)AX2000:3000 -> 61
    076A:0237POP BP________
    076A:0238RET________
  2. 编一个程序,用一条C语句实现在屏幕的中间显示一个绿色的字符”a”。

    1
    2
    3
    4
    main()
    {
    *(int far *)0xb80007d0=0x261
    }
  3. 分析下面程序中所有函数的汇编代码,思考相关的问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    int a1, a2, a3;
    void f(void);
    main()
    {
    int b1, b2, b3;
    a1 = 0xa1; a2 = 0xa2; a3 = 0xa3;
    b1 = 0xb1; b2 = 0xb2; b3 = 0xb3;
    }
    void f(void)
    {
    int c1, c2, c3;
    a1 = 0x0fa1; a2 = 0x0fa2; a3 = 0x0fa3;
    c1 = 0xc1; c2 = 0xc2; c3 = 0xc3;
    }

    问题:C语言将全局变量存放在哪里?将局部变量存放在哪里?每个函数开头的“push bp mov bp,sp”有何含义?
    程序中main函数的汇编代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    PUSH BP
    MOV BP, SP
    SUB SP, 6
    MOV WORD PTR [01A6], 00A1
    MOV WORD PTR [01A8], 00A2
    MOV WORD PTR [01AA], 00A3
    MOV WORD PTR [BP-6], 00B1
    MOV WORD PTR [BP-4], 00B2
    MOV WORD PTR [BP-2], 00B3
    MOV SP, BP
    POP BP
    RET

    程序中f函数的汇编代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    PUSH BP
    MOV BP, SP
    SUB SP, 6
    MOV WORD PTR [01A6], 0FA1
    MOV WORD PTR [01A8], 0FA2
    MOV WORD PTR [01AA], 0FA3
    MOV WORD PTR [BP-6], 00C1
    MOV WORD PTR [BP-4], 00C2
    MOV WORD PTR [BP-2], 00C3
    MOV SP, BP
    POP BP
    RET

    答案:
    C语言将全局变量存放在内存中,将局部变量存放在栈中,每个函数开头的push bp以及mov bp,sp是为了保护与还原现场。

  4. 分析下面程序的汇编代码,思考相关的问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    int f(void);

    int a,b,ab;

    main()
    {
    int c;
    c = f();
    }
    int f(void)
    {
    ab=a+b;

    return ab;
    }

    问题:C语言将函数的返回值存放在哪里?
    程序中main函数的汇编代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    PUSH BP
    MOV BP, SP
    SUB SP, 2
    CALL 020A
    MOV [BP-2], AX
    MOV SP, BP
    POP BP
    RET

    程序中f函数的汇编代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    PUSH BP
    MOV BP, SP
    MOV AX, [01A6]
    ADD AX, [01A8]
    MOV [01AA], AX
    MOV AX, [01AA]
    JMP 021C
    POP BP
    RET

    答案:
    在该情况下,C语言将返回值存放在通用寄存器AX中。

  5. 下面的程序向安全的内存空间写入从“a”到“h”8个字符,理解程序的含义,深入理解相关的知识。(注意:请自己学习、研究malloc函数的用法)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #define Buffer ((char *) * (int far *)0x02000000)

    main()
    {
    Buffer = (char *)malloc(20);

    Buffer[10] = 0;

    while(Buffer[10]!=8)
    {
    Buffer[Buffer[10]]='a'+Buffer[10];
    Buffer[10]++;
    }
    }

    理解:
    程序通过malloc分配大小为20字节的空间给并将其地址存入Buffer所指向的空间即0200:0000中。随后,通过一个while循环将这20个字节的空间的0-7个字节分别赋予对应的字符,并且用第10个字节计数。

研究试验4 不用main函数编程

  1. 把程序F.C保存在C:\MINIC下,对其进行编译,连接,思考相关的问题。
    问题:
    ①编译和连接哪个环节会出问题?
    ②显示出的错误信息是什么?
    ③这个错误信息可能和哪个文件有关?
    答案:
    ①连接环节出现了错误
    ②在C0S模块中未定义的标号_main
    ③和C0S.OBJ这个文件有关

  2. 用学习汇编语言时使用的LINK.EXE对TC.EXE生成的F.OBJ文件进行连接,生成F.EXE。用DEBUG加载F.EXE,察看整个程序的汇编代码。思考相关的问题。
    问题:
    ①F.EXE的程序代码总共有多少字节?
    ②F.EXE的程序能正确返回吗?
    ③F函数的偏移地址是多少?
    答案:
    ①F.EXE的程序代码总共有1DH个字节
    ②F.EXE的程序不能正确的返回
    ③F函数的偏移地址为0

  3. 写一个程序M.C。

    1
    2
    3
    4
    5
    main ()
    {
    *(char far *)(0xb8000000+160*10+80)='a';
    *(char far *)(0xb8000000+160*10+81)=2;
    }

    用TC.EXE对M.C进行编译,连接,生成M.EXE,用Debug察看M.EXE整个程序的汇编代码。思考相关的问题。
    问题:
    (1)M.EXE的程序代码总共有多少字节?
    (2)M.EXE的程序能正确返回吗?
    (3)M.EXE程序中的main函数和F.EXE中的f函数的汇编代码有何不同?
    答案:
    (1)M.EXE的程序代码总共有5F5H个字节
    (2)M.EXE的程序能正确返回
    (3)M.EXE程序中的main函数和F.EXE中的f函数的汇编代码没有不同

  4. 用Debug对m.exe进行跟踪:
    (1)找到对main函数进行调用的指令的地址
    (2)找到整个程序返回的指令
    注意:使用g命令和p命令。
    答案:
    (1)对main函数进行调用的指令的地址为076A:011A CALL 01FA
    (2)整个程序返回的指令为076A:0156 INT 21

  5. 思考如下几个问题:
    (1)对main函数调用的指令和程序返回的指令是哪里来的?
    (2)没有main函数时,出现的错误信息里有和“C0S”相关的信息;而前面在搭建开发环境时,没有C0S.OBJ文件TC.EXE就无法对程序进行连接。是不是TC.EXE把C0S.OBJ和用户程序的.OBJ一起进行连接生成.EXE文件?
    (3)对用户程序的main函数进行调用的指令和程序返回的指令是否就来自C0S.OBJ文件?
    (4)我们如何看到C0S.OBJ文件中的程序代码呢?
    (5)C0S.OBJ文件里有我们设想的代码吗?
    回答:
    (1)对main函数调用的指令和程序返回的指令是来自于其他的文件而非编译后的文件。
    (2)是的
    (3)是的
    (4)用LINK.EXE对其进行连接即可
    (5)有

  6. 用LINK.EXE对C:\MINIC目录下的C0S.OBJ进行连接,生成C0S.EXE。
    用Debug分别察看C0S.EXE和M.EXE的汇编代码。注意:从头开始察看,两个文件中的程序代码有和相同之处?
    两个程序的代码基本相同,且都是在011A调用了CALL指令,在0156调用了INT 21中断

  7. 用Debug找到M.EXE中调用main函数的CALL指令的偏移地址,从这个偏移地址开始向后察看10条指令;然后用Debug加载C0S.EXE,从相同的偏移地址开始向后察看10条指令,对两处的指令进行对比。
    M.EXE和C0S.EXE在偏移地址011A之后的10条指令除了跳转指令的跳转地址有所不同外几乎完全相同。

  8. 下面,我们用汇编语言编一个程序C0S.ASM,然后把它编译为C0S.OBJ,替代C:\MINIC目录下的C0S.OBJ。
    程序C0S.ASM:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    assume cs:code
    data segment
    db 128 dup (0)
    data ends

    code segment
    start: mov ax, data
    mov ds, ax
    mov ss, ax
    mov sp, 108

    call s

    mov ax, 4c00h
    int 21h

    s:

    code ends

    end start
  9. 在C:\MINIC目录下,用TC.EXE将F.C重新进行编译,连接,生成F.EXE。这次能通过连接吗?F.EXE可以正确运行吗?用Debug察看F.EXE的汇编代码。
    能通过连接,可以正确运行,汇编代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    MOV AX,076A
    MOV DS,AX
    MOV SS,AX
    MOV SP,0080
    CALL 0012
    MOV AX,4C00
    INT 21
    MOV BP,SP
    MOV BX,B800
    MOV ES,BX
    MOV BX,0690
    ES:
    MOV BYTE PTR [BX],61
    MOV BX,B800
    MOV ES,BX
    MOV BX,0691
    ES:
    MOV BYTE PTR [BX],02
    POP BP
    RET
    ...
  10. 在新的C0S.OBJ的基础上,写一个新的F.C,向安全的内存空间写入从“a”到“h”的8个字符,分析、理解F.C。
    程序F.C:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #define Buffer ((char *) * (int far *)0x02000000)

    f()
    {
    Buffer = 0;

    Buffer[10] = 0;

    while(Buffer[10]!=8)
    {
    Buffer[Buffer[10]]='a'+Buffer[10];
    Buffer[10]++;
    }
    }

    汇编代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    MOV AX,076A
    MOV DS,AX
    MOV SS,AX
    MOV SP,0080
    CALL 0012 ;调用f函数
    INT 21
    PUSH BP ;0012
    MOV BP,SP
    MOV BX,0200
    MOV ES,BX
    XOR BX,BX
    ES:
    MOV WORD PTR [BX],0000 ;设置Buffer=0,即将0200:0000处的内存单元改为0
    MOV BX,0200
    MOV ES,BX
    XOR BX,BX
    ES:
    MOV BX,[BX] ;将Buffer解引用,即将0200:0000处的内存字单元存入BX中
    MOV BYTE PTR [BX+0A],00 ;设置Buffer[10]=0 即将DS:000A处的内存单元改为0
    JMP 006D ;while 循环条件判断
    MOV BX,0200 ;0031
    MOV ES,BX
    XOR BX,BX
    ES:
    MOV BX,[BX] ;将Buffer解引用,即将0200:0000处的内存字单元存入BX中
    MOV AL,[BX+0A] ;AL等于Buffer[10],即将DS:000A内存字节单元的内容存入AL中
    ADD AL,61 ;为AL加上'a'
    MOV BX,0200
    MOV ES,BX
    XOR BX,BX
    ES:
    MOV BX,[BX] ;将Buffer解引用,即将0200:0000处的内存字单元存入BX中
    PUSH AX
    PUSH BX
    MOV BX,0200
    MOV ES,BX
    XOR BX,BX
    ES:
    MOV BX,[BX] ;将Buffer解引用,即将0200:0000处的内存字单元存入BX中
    MOV AL,[BX+0A] ;将Buffer[10]的值传入AL,即将DS:000A处的内存字节单元存入AL中
    CBW ;将AL扩展至16位
    POP BX ;取回BX
    ADD BX,AX ;BX现在值为Buffer[10]
    POP AX ;取回AX
    MOV [BX],AL ;将所要设置的数值'a'+Buffer[10]存入Buffer[Buffer[10]]中
    MOV BX,0200
    MOV ES,BX
    XOR BX,BX
    ES:
    MOV BX,[BX] ;将Buffer解引用,即将0200:0000处的内存字单元存入BX中
    INC BYTE PTR [BX+0A] ;将Buffer[10]自增
    MOV BX,0200 ;006D
    MOV ES,BX
    XOR BX,BX
    ES:
    MOV BX,[BX]
    CMP BYTE PTR [BX+0A],08 ;判断Buffer[10]是否等于8
    JNZ 0031 ;不等于则继续
    POP BP ;等于则返回
    RET

研究试验5 函数如何接受不定数量的参数

用C:\MINIC下的TC.EXE完成下面的试验。

  1. 写一个程序A.C:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void showchar(char a, int b);

    main()
    {
    showchar('a', 2);
    }

    void showchar(char a, int b)
    {
    *(char far *)(0xb8000000+160*10+80)=a;

    *(char far *)(0xb8000000+160*10+81)=b;
    }

    用TC.EXE对A.C进行编译,连接,生成A.EXE。用Debug加载A.EXE,对函数的汇编代码进行分析。解答这两个问题:main函数是如何给showchar传递参数的?showchar是如何接受参数的?
    main函数的汇编代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    PUSH BP
    MOV BP,SP
    MOV AX,0002
    PUSH AX
    MOV AL,61
    PUSH AX
    CALL 020B ;调用showchar函数
    POP CX
    POP CX
    POP BP
    RET

    showchar函数的汇编代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    PUSH BP
    MOV BP,SP
    MOV AL,[BP+4]
    MOV BX,B800
    MOV ES,BX
    MOV BX,0690
    ES:
    MOV [BX],AL ;f函数的第1条语句
    MOV AL,[BP+6]
    MOV BX,B800
    MOV ES,BX
    MOV BX,0691
    ES:
    MOV [BX],AL ;f函数的第2条语句
    POP BP
    RET

    答案:
    main函数通过将对应的参数压栈来给showchar传递参数,并且在函数返回后通过POP操作将参数退栈。
    showchar通过SS:BP以及寻址在栈中取得对应的参数。

  2. 写一个程序B.C:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    void showchar(int,int,...);

    main()
    {
    showchar(8,2,'a','b','c','d','e','f','g','f');
    }

    void showchar(int n,int color,...)
    {
    int a;

    for(a=0;a!=n;a++)
    {
    *(char far *)(0xb8000000+160*10+80+a+a)=*(int *)(_BP+8+a+a);//加8是因~为call的时候将CS、IP压栈

    *(char far *)(0xb8000000+160*10+81+a+a)=color;
    }
    }

    分析程序B.C,深入理解相关的知识。
    思考:showchar函数是如何知道要显示多少个字符的?printf函数是如何知道有多少个参数的?
    待显示字符的个数为showchar函数的第一个参数,showchar函数以此得知要显示的字符的个数。
    通过对printf的一个参数所指向的字符串的分析,printf函数以此得知参数的个数。

  3. 实现一个简单的printf函数,只需支持“%c %d”即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    void myprintf(char *, ...);

    main()
    {
    myprintf("%d %c abc %%\n", -12345, 48);
    }

    void myprintf(char * str, ...)
    {
    int count = 0;
    int position = 0;
    while (*str != 0)
    {
    if (*str == 37)
    {
    str++;
    if (*str == 68 || *str == 100)
    {
    int index;
    char flag = 0;
    int length = 0;
    int num = *(int *)(_BP + 6 + count);
    if (num < 0) {
    flag = 1;
    num *= -1;
    }
    while (num != 0)
    {
    num = num / 10;
    length++;
    }
    num = *(int *)(_BP + 6 + count);
    if (flag == 1) num *= -1;
    position += length + flag - 1;
    for (index = 0 ; index < length; ++index)
    {
    *(char far *)(0xb8000000 + 1600 + position * 2) = num % 10 + 48;
    num = num / 10;
    position--;
    }
    if (flag == 1)
    {
    *(char far *)(0xb8000000 + 1600 + position * 2) = '-';
    position--;
    }
    position += length + flag + 1;
    count += 2;
    str++;
    }
    else if (*str == 67 || *str == 99)
    {
    *(char far *)(0xb8000000 + 1600 + position * 2) = *(char *)(_BP + 6 + count);
    count++;
    position++;
    str++;
    }
    else
    {
    *(char far *)(0xb8000000 + 1600 + position * 2) = '%';
    position++;
    }
    }
    else
    {
    *(char far *)(0xb8000000 + 1600 + position * 2) = *str;
    position++;
    str++;
    }
    }
    }

评论和共享

开学三周,王爽的《汇编语言》(第三版)总算是基本上看完了,本文是总结的第二部分,包括了书本上的实验

实验1 查看CPU和内存,用机器指令和汇编指令编程

  1. 使用Debug,将下面的程序段写入内存,逐条执行,观察每条指令执行后,CPU中相关寄存器的变化。

    机器码汇编指令执行后相关寄存器变化
    b8 20 4emov ax, 4E20HAX=4E20H
    05 16 14add ax, 1416HAX=6236H
    bb 00 20mov bx, 2000HBX=2000H
    01 d8add ax, bxAX=8236H
    89 c3mov bx, axBX=8236H
    01 d8add ax, bxAX=046CH
    b8 1a 00mov ax, 001AHAX=001AH
    bb 26 00mov bx, 0026HBX=0026H
    00 d8add al, blAX=0040H
    00 dcadd ah, blAX=2640H
    00 c7add bh, alBX=4026H
    b4 00mov ah, 0AX=0040H
    00 d8add al, blAX=0066H
    04 9cadd al, 9CHAX=0002H

    提示,可用E命令和A命令以两种方式将指令写入内存。注意用T命令执行时,CS:IP的指向。

  2. 将下面3条指令写入从2000:0开始的内存单元中,利用这3条指令计算2的8次方。

    1
    2
    3
    mov ax, 1
    add ax, ax
    jmp 2000:0003

    每次执行 add ax, ax 相当于将ax乘2,重复执行该条指令8次即可。

  3. 查看内存中的内容。
    PC机主板上的ROM写有一个生产日期,在内存FFF00H~FFFFFH的某几个单元中,请找到这个生产日期并试图改变它。
    提示,如果读者对实验的结果感到疑惑,请仔细阅读第1章中的1.15节。

通过Debug中的D命令,可以观察到生产日期以MM/DD/YY的格式存储在内存的ffff:0005-ffff:000c共计8个字节处。
该生产日期不可被修改,因为其只读。

  1. 向内存从B8100H开始的单元中填写数据,如:
    1
    -e B810:0000 01 01 02 02 03 03 04 04
    请读者先填写不同的数据,观察产生的现象;再改变填写的地址,观察产生的现象。
    提示,如果读者对实验的结果感到疑惑,请仔细阅读第1章中的1.15节。

通过向内存中的显存地址空间写入数据,控制在屏幕上的不同位置显示不同颜色的字符。

实验2 用机器指令和汇编指令编程

  1. 使用Debug,将下面的程序段写入内存,逐条执行,根据指令执行后的实验运行情况填空。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    mov ax, ffff
    mov ds, ax

    mov ax, 2200
    mov ss, ax

    mov sp, 1000

    mov ax,[0] ;ax = _____
    add ax,[2] ;ax = _____
    mov bx,[4] ;bx = _____
    add bx,[6] ;bx = _____

    push ax ;sp = _____ 修改的内存单元的地址是_____内容为_____
    push bx ;sp = _____ 修改的内存单元的地址是_____内容为_____
    pop ax ;sp = _____ ax = _____
    pop bx ;sp = _____ bx = _____

    push [4] ;sp = _____ 修改的内存单元的地址是_____内容为_____
    push [6] ;sp = _____ 修改的内存单元的地址是_____内容为_____

    结果(不唯一):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    D4EA
    25EE
    3002
    5F37
    00FE 220FE 25EE
    00FC 220FC 5F37
    00FE 5F37
    0100 25EE
    00FE 220FE 3002
    00FC 220FC 2F35
  2. 仔细观察图3.19中的实验过程,然后分析:为什么2000:0~2000:f中的内容会发生改变?
    在使用T命令进行单步追踪的时候,产生了中断,为了保护现场,CPU将PSW、CS和IP依此入栈,导致了内存相关位置内容的改变。

实验3 编程、编译、连接、跟踪

  1. 将下面的程序保存为t1.asm文件,将其生成可执行文件t1.exe。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    assume cs:codesg

    codesg segment
    mov ax, 2000H
    mov ss, ax
    mov sp, 0
    add sp, 10
    pop ax
    pop bx
    push ax
    push bx
    pop ax
    pop bx

    mov ax, 4c00h
    int 21h
    codesg ends

    end

    结果:

    1
    2
    3
    edit t1.asm
    masm.exe t1.asm;
    link.exe t1.obj;
  2. 用Debug跟踪t1.exe的执行过程,写出每一步执行后,相关寄存器中的内容和栈顶的内容。

    汇编指令相关寄存器的内容栈顶的内容
    mov ax, 2000HAX = 2000H00B8H
    mov ss, axSS = 2000H_____
    mov sp, 0SP = 00000H
    add sp, 10SP = 100000H
    pop axAX = 0 SP = 120000H
    pop bxBX = 0 SP = 140000H
    push axSP = 120000H
    push bxSP = 100000H
    pop axAX = 0 SP = 120000H
    pop bxBX = 0 SP = 140000H
    mov ax, 4c00hAX = 4C00H0000H
    int 21h__________

    结果不唯一

  3. PSP的头两个字节是CD 20,用Debug加载t1.exe,查看PSP的内容。

    1
    -d 2119:0

    2119为CS-0010H

实验4 [bx]和loop的使用

  1. 编程,向内存0:200~0:23F依此传送数据0~63(3FH)。

  2. 编程,向内存0:200~0:23F依此传送数据0~63(3FH),程序中只能使用9条指令,9条指令中包括“mov ax, 4c00h”和“int 21h”。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    assume cs:code
    code segment
    mov ax, 0020h
    mov ds, ax
    mov bx, 0
    mov cx, 64

    s: mov [bx], bl
    inc bl
    loop s

    mov ax, 4c00h
    int 21h
    code ends
    end
  3. 下面的程序的功能是将“mov ax, 4c00h”之前的指令复制到内存的0:200处,补全程序,上机调试,跟踪运行结果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    assume cs:code
    code segment
    mov ax, _____
    mov ds, ax
    mov ax, 0020h
    mov es, ax
    mov bx, 0
    mov cx, _____
    s: mov al, [bx]
    mov es:[bx], al
    inc bx
    loop s
    mov ax, 4c00h
    int 21h
    code ends
    end

    结果:

    1
    2
    code
    18H

    提示:
    复制的是什么?从哪里到哪里?
    复制的是代码段中mov ax, 4c00h之前的代码,以数据的形式,从内存中代码段的位置复制到内存中0:200处开始的一段连续的空间。
    复制的是什么?有多少个字节?你如何直到要复制的字节的数量?
    可以用offset计算得出,也可以在Debug中用T命令观察得出。

实验5 编写、调试具有多个段的程序

  1. 将下面的程序编译连接,用Debug加载、跟踪,然后回答问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    assume cs:code, ds:data, ss:stack

    data segment
    dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
    data ends

    stack segment
    dw 0, 0, 0, 0, 0, 0, 0, 0
    stack ends

    code segment
    start: mov ax, stack
    mov ss, ax
    mov sp, 16

    mov ax, data
    mov ds, ax

    push ds:[0]
    push ds:[2]
    pop ds:[2]
    pop ds:[0]

    mov ax, 4c00h
    int 21h
    code ends

    end start

    ①CPU执行程序,程序返回前,data段中的数据为多少?
    ②CPU执行程序,程序返回前,CS = _____、SS = _____、DS = _____。
    ③设程序加载后,code段的段地址为X,则data段的段地址为_____,stack段的段地址为_____。
    答案:
    ①data段中的数据不变。
    ②212B、212A、2129(答案不唯一)
    ③X-2、X-1

  2. 将下面的程序编译连接,用Debug加载、跟踪,然后回答问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    assume cs:code, ds:data, ss:stack

    data segment
    dw 0123h, 0456h
    data ends

    stack segment
    dw 0, 0
    stack ends

    code segment
    start: mov ax, stack
    mov ss, ax
    mov sp, 16

    mov ax, data
    mov ds, ax

    push ds:[0]
    push ds:[2]
    pop ds:[2]
    pop ds:[0]

    mov ax, 4c00h
    int 21h
    code ends

    end start

    (1)CPU执行程序,程序返回前,data段中的数据为多少?
    (2)CPU执行程序,程序返回前,CS = _____、SS = _____、DS = _____。
    (3)设程序加载后,code段的段地址为X,则data段的段地址为_____,stack段的段地址为_____。
    (4)对于如下定义的段:

    1
    2
    3
    name segment
    ...
    segment ends

    如果段中的数据占N个字节,则程序加载后,该段实际占有的空间为_____。
    答案:
    (1)data段中的数据不变。
    (2)212B、212A、2129(答案不唯一)
    (3)X-2、X-1
    (4)((N-1)/16 + 1)*16 其中除法为整除

  3. 将下面的程序编译连接,用Debug加载、跟踪,然后回答问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    assume cs:code, ds:data, ss:stack

    code segment
    start: mov ax, stack
    mov ss, ax
    mov sp, 16

    mov ax, data
    mov ds, ax

    push ds:[0]
    push ds:[2]
    pop ds:[2]
    pop ds:[0]

    mov ax, 4c00h
    int 21h
    code ends

    data segment
    dw 0123h, 0456h
    data ends

    stack segment
    dw 0, 0
    stack ends

    end start

    (1)CPU执行程序,程序返回前,data段中的数据为多少?
    (2)CPU执行程序,程序返回前,CS = _____、SS = _____、DS = _____。
    (3)设程序加载后,code段的段地址为X,则data段的段地址为_____,stack段的段地址为_____。
    答案:
    (1)data段中的数据不变。
    (2)2129、212D、212C(答案不唯一)
    (3)X+3、X+4

  4. 如果将1. 2. 3.题中的最后一条伪指令“end start”改为“end”(也就是说,不指明程序的入口),则哪个程序仍然可以正确执行?请说明原因。
    答案:
    只有程序3可以正确运行,在不指明程序入口的情况下,程序默认按照顺序从头开始执行,而3个程序中只有程序3的code段位于最开始的部分,所以只有程序3可以正确运行。

  5. 程序如下,编写code段中的代码,将a段和b段中的数据依此相加,将结果存到c段中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    assume cs:code
    a segment
    db 1, 2, 3, 4, 5, 6, 7, 8
    a ends

    b segment
    db 1, 2, 3, 4, 5, 6, 7, 8
    b ends

    c segment
    db 0, 0, 0, 0, 0, 0, 0, 0
    c ends

    code segment
    start:
    ?
    code ends
    end start

    答案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    start:  mov ax, a
    mov ds, ax
    mov ax, b
    mov es, ax
    mov ax, c
    mov ss, ax
    mov bx, 0
    mov cx, 8
    s: mov al, [bx]
    add al, es:[bx]
    mov ss:[bx], al
    inc bx
    loop s
    mov ax, 4c00h
    int 21h
  6. 程序如下,编写code段中的代码,用push指令将a段中的前8个字型数据,逆序存储到b段中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    assume cs:code

    a segment
    dw 1, 2, 3, 4, 5, 6, 7, 8, 9, 0ah, 0bh, 0ch, 0dh, 0eh, 0fh, 0ffh
    a ends

    b segment
    dw 0, 0, 0, 0, 0, 0, 0, 0
    b ends

    code segment
    start:
    ?
    code ends

    end start

    答案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    start:  mov ax, b
    mov ss, ax
    mov sp, 16
    mov ax, a
    mov ds, ax
    mov bx, 0
    mov cx, 8
    s: push [bx]
    add bx, 2
    loop s
    mov ax, 4c00h
    int 21h

实验6 实践课程中的程序

  1. 将课程中所有讲解过的程序上机调试,用Debug跟踪其执行过程,并在过程中进一步理解所讲内容。

  2. 编程,完成问题7.9中的程序。
    程序如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    assume cs:codesg, ss:stacksg, ds:datasg
    stacksg segment
    dw 0, 0, 0, 0, 0, 0, 0, 0
    stacksg ends

    datasg segment
    db '1. display '
    db '2. brows '
    db '3. replace '
    db '4. modify '
    datasg ends

    codesg segment
    start: mov ax, datasg
    mov ds, ax
    mov ax, stacksg
    mov ss, ax
    mov sp, 16
    mov bx, 0
    mov cx, 4
    s: push cx
    mov cx, 4
    mov si, 0
    s0: mov al, [bx + si + 3]
    and al, 11011111b
    mov [bx + si + 3], al
    inc si
    loop s0
    add bx, 16
    pop cx
    loop s
    mov ax, 4c00h
    int 21h
    codesg ends
    end start

实验7 寻址方式在结构化数据访问中的应用

编程,将data段中的数据按如下格式写入到table段中,并计算21年中的人均收入(取整),结果也按照下面的格式保存在table段中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
assume cs:codesg, ss:stack

stack segment
dw 8 dup (0)
stack ends

data segment
db '1975', '1976', '1977', '1978', '1979', '1980', '1981', '1982', '1983'
db '1984', '1985', '1986', '1987', '1988', '1989', '1990', '1991', '1992'
db '1993', '1994', '1995'

dd 16, 22, 382, 1356, 2390, 8000, 16000, 24486, 50065, 97479, 140417, 197514
dd 345980, 590827, 803530, 1183000, 1843000, 2759000, 3753000, 4649000, 5937000

dw 3, 7, 9, 13, 28, 38, 130, 220, 476, 778, 1001, 1442, 2258, 2793, 4037, 5635, 8226
dw 11542, 14430, 15257, 17800
data ends

table segment
db 21 dup ('year summ ne ?? ')
table ends

codesg segment
start: mov ax, stack
mov ss, ax
mov sp, 16
mov ax, table
mov ds, ax
mov ax, data
mov es, ax
mov bx, 0
mov si, 0
mov cx, 21
year: push cx
mov cx, 4
mov di, 0
char: mov al, es:[si]
mov [bx+di], al
inc si
inc di
loop char
add bx, 16
pop cx
loop year
mov cx, 21
mov bx, 0
income: mov ax, es:[si]
mov [5+bx], ax
add si, 2
mov ax, es:[si]
mov [7+bx], ax
add si, 2
add bx, 16
loop income
mov cx, 21
mov bx, 0
staff: mov ax, es:[si]
mov [10+bx], ax
add si, 2
add bx, 16
loop staff
mov cx, 21
mov bx, 0
average:mov ax, [bx+5]
mov dx, [bx+7]
div word ptr [bx+10]
mov [13+bx], ax
add bx, 16
loop average
mov ax, 4c00h
int 21h
codesg ends
end start

实验8 分析一个奇怪的程序

分析下面的程序,在运行前思考,这个程序可以正确返回吗?
运行后再思考:为什么是这种结果?
通过这个程序加深对相关内容的理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
assume cs:codesg
codesg segment
mov ax, 4c00h
int 21h
start: mov ax, 0
s: nop
nop
mov di, offset s
mov si, offset s2
mov ax, cs:[si]
mov cs:[di], ax
s0: jmp short s
s1: mov ax, 0
int 21h
mov ax, 0
s2: jmp short s1
nop
codesg ends
end start

分析:
这个程序可以正确返回,程序的入口为mov ax, 0,注意到指令jmp short s1占2字节,于是指令mov di, offset s将s的偏移地址传送到寄存器DI,mov si, offset s2将s2的偏移地址传送到SI,然后再通过通用寄存器ax做中转将s2处的指令复制到s处,最后再跳转至s处执行复制过来的指令。
注意jmp short s1是相对跳转,其直接修改IP寄存器,从s2到s1共有8个字节的偏移,实际上 jmp short s1等价于(ip)=(ip)-8,通过Debug可知第一个nop指令的偏移地址为8,所以再执行了复制过的指令后,IP将指向0,程序按照顺序执行mov ax, 4c00h和int 21h,正确返回。

实验9 根据材料编程

编程:在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串’welcome to masm!’。
编程所需的只是通过阅读、分析下面的材料获得。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
assume ds:data, cs:code

data segment
db 'welcome to masm!'
data ends

code segment
start: mov ax, data
mov ds, ax
mov ax, 0B800H
mov es, ax

mov bx, 1664
mov si, 0
mov cx, 16
char1: mov al, [si]
mov ah, 10000010B
mov es:[bx], ax
add bx, 2
inc si
loop char1

mov bx, 1824
mov si, 0
mov cx, 16
char2: mov al, [si]
mov ah, 10100100B
mov es:[bx], ax
add bx, 2
inc si
loop char2

mov bx, 1984
mov si, 0
mov cx, 16
char3: mov al, [si]
mov ah, 11110001B
mov es:[bx], ax
add bx, 2
inc si
loop char3

mov ax, 4C00H
int 21H
code ends
end start

实验10 编写子程序

  1. 显示字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    assume cs:code, ss:stack

    stack segment
    dw 16 dup (0)
    stack ends

    data segment
    db 'Welcome to masm!', 0
    data ends

    code segment
    start: mov dh, 8
    mov dl, 3
    mov cl, 2
    mov ax, data
    mov ds, ax
    mov ax, stack
    mov ss, ax
    mov sp, 32
    mov si, 0
    call show_str

    mov ax, 4c00h
    int 21h

    show_str:
    push cx
    push bx
    push ax
    push si
    push di
    push es
    ;using cx, bx, ax, si, di, es
    mov ax, 0b800h
    mov es, ax
    mov bx, 0
    mov di, 0
    mov al, 160
    mul dh
    add bx, ax
    mov al, 2
    mul dl
    add bx, ax ;bx stores address of start character
    mov al, cl ;al stores the color of character
    char: mov ch, 0
    mov cl, ds:[si]
    jcxz zero
    mov ch, al
    mov es:[bx+di], cx
    add di, 2
    inc si
    jmp char
    zero: pop es
    pop di
    pop si
    pop ax
    pop bx
    pop cx
    ret

    code ends
    end start
  2. 解决除法溢出的问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    assume cs:code, ss:stack

    stack segment
    dw 16 dup (0)
    stack ends

    code segment
    start: mov ax, stack
    mov ss, ax
    mov sp, 32
    mov ax, 4240h
    mov dx, 000fh
    mov cx, 0ah
    call divdw

    mov ax, 4c00h
    int 21h

    divdw: push bx

    mov bx, ax ; bx stores L
    mov ax, dx ; ax stores H
    mov dx, 0
    div cx ; after div, ax holds int(H/N), dx holds rem(H/N)
    push ax ; push int(H/N) temporarily
    mov ax, bx ; ax stores L
    div cx
    mov cx, dx
    pop dx

    pop bx
    ret

    code ends
    end start
  3. 数值显示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    assume cs:code, ss:stack

    stack segment
    dw 16 dup (0)
    stack ends

    data segment
    db 10 dup (0)
    data ends

    code segment
    start: mov ax, data
    mov ds, ax
    mov ax, stack
    mov ss, ax
    mov sp, 32
    mov ax, 12666
    mov si, 0
    call dtoc

    mov dh, 8
    mov dl, 3
    mov cl, 2
    call show_str

    mov ax, 4c00h
    int 21h

    dtoc: push ax
    push si
    push di
    push dx
    push bx
    push cx
    mov di, 0
    mov dx, 0
    mov bx, 10

    devide: mov cx, ax
    jcxz stop
    div bx
    inc di
    push dx
    mov dx, 0
    jmp devide
    stop: mov cx, di
    string: pop bx
    add bx, 30h
    mov [si], bl
    inc si
    loop string

    pop cx
    pop bx
    pop dx
    pop di
    pop si
    pop ax
    ret

    show_str:
    push cx
    push bx
    push ax
    push si
    push di
    push es
    ;using cx, bx, ax, si, di, es
    mov ax, 0b800h
    mov es, ax
    mov bx, 0
    mov di, 0
    mov al, 160
    mul dh
    add bx, ax
    mov al, 2
    mul dl
    add bx, ax ;bx stores address of start character
    mov al, cl ;al stores the color of character
    char: mov ch, 0
    mov cl, ds:[si]
    jcxz zero
    mov ch, al
    mov es:[bx+di], cx
    add di, 2
    inc si
    jmp char
    zero: pop es
    pop di
    pop si
    pop ax
    pop bx
    pop cx
    ret

    code ends
    end start

实验11 编写子程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
assume cs:code

stack segment
dw 8 dup (0)
stack ends

data segment
db "Beginner's All-purpose Symbolic Instruction Code.", 0
data ends

code segment
begin: mov ax, stack
mov ss, ax
mov sp, 16
mov ax, data
mov ds, ax
mov si, 0
call letterc

mov ax, 4c00h
int 21h

letterc:
push cx
push si
pushf

mov ch, 0
start: mov cl, ds:[si]
jcxz zero
cmp cl, 97
jb next
cmp cl, 122
ja next
sub cl, 20h
mov ds:[si], cl
next: inc si
jmp start

zero: popf
pop si
pop cx
ret

code ends
end begin

实验12 编写0号中断的处理程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
assume cs:code

code segment
start:
mov ax, cs
mov ds, ax
mov si, offset do0

mov ax, 0
mov es, ax
mov di, 200h

mov cx, offset do0end - offset do0

cld

rep movsb

mov ax, 0
mov es, ax
mov word ptr es:[0], 200h
mov word ptr es:[2], 0

mov ax, 4c00h
int 21h

do0:
jmp short do0start
db "overflow!"
do0start:
mov ax, cs
mov ds, ax
mov si, 202h
mov ax, 0b800h
mov es, ax
mov di, 12*160+36*2
mov cx, 9
s:
mov al, [si]
mov es:[di], al
inc si
add di, 2
loop s

mov ax, 4c00h
int 21h
do0end:
nop

code ends
end start

实验13 编写、应用中断例程

  1. 编写并安装int 7ch中断例程,功能为显示一个用0结束的字符串,中断例程安装在0:200处。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    assume cs:code

    code segment
    start: mov ax, cs
    mov ds, ax
    mov si, offset print
    mov ax, 0
    mov es, ax
    mov di, 200h
    mov cx, offset printend - offset print
    cld
    rep movsb

    mov ax, 0
    mov es, ax
    mov word ptr es:[7ch * 4], 200h
    mov word ptr es:[7ch * 4 + 2], 0
    mov ax, 4c00h
    int 21h

    print:
    push es
    push ax
    push cx
    push dx
    push si
    push di

    mov ax, 0b800h
    mov es, ax
    mov al, 160
    mov ah, 0
    mul dh
    mov di, ax
    add dl, dl
    mov dh, 0
    add di, dx

    mov al, cl
    printstart:
    mov ch, 0
    mov cl, [si]
    jcxz zero
    mov ch, al
    mov es:[di], cx
    add di, 2
    inc si
    jmp printstart

    zero:
    pop di
    pop si
    pop dx
    pop cx
    pop ax
    pop es
    iret
    printend:
    nop

    code ends

    end start
  2. 编写并安装int 7ch中断例程,功能为完成loop指令功能。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    assume cs:code

    code segment
    start: mov ax, cs
    mov ds, ax
    mov si, offset lp
    mov ax, 0
    mov es, ax
    mov di, 200h
    mov cx, offset lpend - offset lp
    cld
    rep movsb

    mov ax, 0
    mov es, ax
    mov word ptr es:[7ch * 4], 200h
    mov word ptr es:[7ch * 4 + 2], 0
    mov ax, 4c00h
    int 21h

    lp: push bp
    mov bp, sp
    dec cx
    jcxz lpret
    add [bp + 2], bx
    lpret: pop bp
    iret
    lpend: nop

    code ends

    end start
  3. 下面的程序,分别在屏幕的第2、4、6、8行显示4句英文诗,补全程序。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    assume cs:code
    code segment
    s1: db 'Good,better,best,', '$'
    s2: db 'Never let it rest,', '$'
    s3: db 'Till good is better,', '$'
    s4: db 'And better,best.', '$'
    s: dw offset s1, offset s2, offset s3, offset s4
    row: db 2, 4, 6, 8

    start: mov ax, cs
    mov ds, ax
    mov bx, offset s
    mov si, offset row
    mov cx, 4
    ok: mov bh, 0
    mov dh, _____
    mov dl, 0
    mov ah, 2
    int 10h

    mov dx, _____
    mov ah, 9
    int 21h
    _________
    _________
    loop ok
    mov ax, 4c00h
    int 21h
    code ends
    end start

    答案:

    1
    2
    3
    4
    [si]
    [bx]
    add bx, 2
    inc si

实验14 访问CMOS RAM

编程,以”年/月/日 时:分:秒”的格式,显示当前的日期,时间。
注意:CMOS RAM中存储着系统的配置信息,出了保存时间信息的单元外,不要向其他的单元中写入内容,否则将引起一些系统错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
assume cs:code, ds:data

data segment
s db 9, 8, 7, 4, 2, 0
data ends
code segment
start: mov ax, 0b800h
mov es, ax
mov di, 160 * 12
mov ax, data
mov ds, ax
mov si, 0
mov cx, 6

print: mov al, s[si]
out 70h, al
in al, 71h
call number
cmp si, 2
jb slash
je space
cmp si, 5
jb colon
next: inc si
loop print

mov ax, 4c00h
int 21h

;al->number, es:di->begin
number: push cx
mov ah, al
mov cl, 4
shr ah, cl
and al, 00001111b
add ah, 30h
add al, 30h
mov byte ptr es:[di], ah
mov byte ptr es:[di + 2], al
add di, 4
pop cx
ret

slash: mov byte ptr es:[di], '\'
add di, 2
jmp next

colon: mov byte ptr es:[di], ':'
add di, 2
jmp next

space: mov byte ptr es:[di], ' '
add di, 2
jmp next

code ends

end start

实验15 安装新的int 9中断例程

安装一个新的int 9中断例程,功能:在DOS下,按下’A’键后,除非不再松开,如果松开,就显示满屏幕的’A’;其他键照常处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
assume cs:code

stack segment
db 128 dup (0)
stack ends

code segment
start: mov ax, stack
mov ss, ax
mov sp, 128

push cs
pop ds

mov ax, 0
mov es, ax

mov si, offset int9
mov di, 204h
mov cx, offset int9end - offset int9
cld
rep movsb

push es: [9 * 4]
pop es: [200h]
push es: [9 * 4 + 2]
pop es: [202h]

cli
mov word ptr es: [9 * 4], 204h
mov word ptr es: [9 * 4 + 2], 0
sti

mov ax, 4c00h
int 21h

int9: push ax
push bx
push cx
push es

in al, 60h

pushf
call word ptr cs:[200h]

cmp al, 9eh
jne int9ret

mov ax, 0b800h
mov es, ax
mov bx, 0
mov cx, 2000
s: mov byte ptr es:[bx], 'A'
add bx, 2
loop s

int9ret:pop es
pop cx
pop bx
pop ax
iret

int9end:nop

code ends
end start

实验16 编写包含多个功能子程序的中断例程

安装一个新的int 7ch中断例程,为显示输出提供如下功能子程序

  1. 清屏
  2. 设置前景色
  3. 设置背景色
  4. 向上滚动一行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
assume cs:code

code segment
start: mov ax, cs
mov ds, ax
mov si, offset screen
mov ax, 0
mov es, ax
mov di, 200h
mov cx, offset screenend - offset screen
cld
rep movsb

mov ax, 0
mov es, ax
mov word ptr es:[7ch * 4], 200h
mov word ptr es:[7ch * 4 + 2], 0
mov ax, 4c00h
int 21h

screen: jmp short set
;考虑到安装中断例程后偏移地址发生了变化,需要重新计算相关的偏移地址
table dw offset sub1 - offset screen + 200h, offset sub2 - offset screen + 200h, offset sub3 - offset screen + 200h, offset sub4 - offset screen + 200h

set: push bx

cmp ah, 3
ja sret
mov bl, ah
mov bh, 0
add bx, bx

;同上
call word ptr cs:(table - screen + 200h)[bx]

sret: pop bx
iret

sub1: push bx
push cx
push es
mov bx, 0b800h
mov es, bx
mov bx, 0
mov cx, 2000
sub1s: mov byte ptr es:[bx], ' '
add bx, 2
loop sub1s
pop es
pop cx
pop bx
ret

sub2: push bx
push cx
push es

mov bx, 0b800h
mov es, bx
mov bx, 1
mov cx, 2000
sub2s: and byte ptr es:[bx], 11111000B
or es:[bx], al
add bx, 2
loop sub2s

pop es
pop cx
pop bx
ret

sub3: push bx
push cx
push es
mov cl, 4
shl al, cl
mov bx, 0b800h
mov es, bx
mov bx, 1
mov cx, 2000
sub3s: and byte ptr es:[bx], 10001111B
or es:[bx], al
add bx, 2
loop sub3s
pop es
pop cx
pop bx
ret

sub4: push cx
push si
push di
push es
push ds

mov si, 0b800h
mov es, si
mov ds, si
mov si, 160
mov di, 0
cld
mov cx, 24
sub4s: push cx
mov cx, 160
rep movsb
pop cx
loop sub4s

mov cx, 80
mov si, 0
sub4s1: mov byte ptr [160*24+si], ' '
add si, 2
loop sub4s1

pop ds
pop es
pop di
pop si
pop cx
ret

screenend:
nop

code ends
end start

参考王爽《汇编语言》实验16:包含多个功能子程序的中断例程 解答
可以用伪指令org x简化该程序
org x表明接下来的指令从偏移地址x开始
修改后的相关指令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
org 200h
screen: jmp short set

table dw sub1, sub2, sub3, sub4

set: push bx

cmp ah, 3
ja sret
mov bl, ah
mov bh, 0
add bx, bx

call word ptr table[bx]

实验17 编写包含多个功能子程序的中断例程

安装一个新的int 7ch中断例程,实现通过逻辑扇区号对软盘进行读写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
assume cs:code

code segment
start: mov ax, cs
mov ds, ax
mov si, offset floppyio
mov ax, 0
mov es, ax
mov di, 200h
mov cx, offset floppyioend - offset floppyio
cld
rep movsb

mov ax, 0
mov es, ax
mov word ptr es:[7ch * 4], 200h
mov word ptr es:[7ch * 4 + 2], 0
mov ax, 4c00h
int 21h

floppyio:
push ax
push cx
push dx

add ah, 2
mov al, 1
push ax ;计算相应的ah和al并压栈
mov ax, dx
mov dx, 0
mov cx, 1440
div cx ;计算逻辑扇区号/1440
push ax ;将商即面号压栈
mov ax, dx
mov dl, 18
div dl ;计算逻辑扇区号/1440的余数/18
inc ah
mov ch, al
mov cl, ah ;设置相应的ch和cl
pop ax ;将相应的面号出栈
mov dh, al
mov dl, 0 ;设置相应的dh和dl
pop ax ;将相应的ah和al出栈
int 13h ;调用13h例程进行实际的读写

pop dx
pop cx
pop ax
iret
floppyioend:
nop

code ends
end start

评论和共享

开学三周,王爽的《汇编语言》(第三版)总算是基本上看完了,本文是总结的第一部分,包括了书上所有的检测点答案

第一章 基础知识

检测点 1.1

  1. 1个CPU的寻址能力为8KB,那么它的地址总线的宽度为____。
    13
    解析:CPU在内存中寻址的最小单位是Byte(字节),8KB = 2^13B,因此地址总线的宽度为13.
  2. 1KB的存储器有____个存储单元。存储单元的编号从____到____。
    1024 0 1023
  3. 1KB的存储器可以存储____个bit,____个Byte。
    2^13 2^10
  4. 1GB、1MB、1KB分别是____________Byte
    2^30 2^20 2^10
  5. 8080、8088、80286、80386的地址总线宽度分别为16根、20根、24根、32根,则他们的寻址能力分别为____(KB)、____(MB)、____(MB)、____(GB)。
    64 1 16 4
  6. 8080、8088、8086、80286、80386的数据总线宽度分别为8根、8根、16根、16根、32根。则它们一次可以传送的数据为:____(B)、____(B)、____(B)、____(B)、____(B)。
    1 1 2 2 4
  7. 从内存中读取1024字节的数据,8086至少要读取____次,80386至少要读取____次。
    512 256
  8. 在存储器中,数据和程序以____形式存放。
    二进制

第二章 寄存器

检测点 2.1

  1. 写出每条汇编指令执行后相关寄存器中的值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    mov ax, 62627		AX =
    mov ah, 31H AX =
    mov al, 23H AX =
    add ax, ax AX =
    mov bx, 826CH BX =
    mov cx, ax CX =
    mov ax, bx AX =
    add ax, bx AX =
    mov al, bh AX =
    mov ah, bl AX =
    add ah, ah AX =
    add al, 6 AX =
    add al, al AX =
    mov ax, cx AX =
    F4A3H 31A3H 3123H 6246H 826CH 6246H 826CH 04D8H 0482H 6C82H D882H D888H D810H 6246H
  2. 只能使用目前学过的汇编指令,最多使用4条指令,编程计算2的4次方。
    1
    2
    3
    4
    mov ax, 2
    add ax, ax
    add ax, ax
    add ax, ax

检测点 2.2

  1. 给定段地址为0001H,仅通过变化偏移地址寻址,CPU的寻址范围为_____到_____。
    00010H到1000FH
  2. 有一数据存放在内存20000H的单元中,现给定段地址为SA,若想用偏移地址寻到此单元。则SA应满足的条件是:最小为_____,最大为_____。提示,反过来思考一下,当段地址给定为多少,CPU无论怎么变化偏移地址都无法寻到20000H单元?
    1001F 2000H
    20000H - 0FFFFH = 10001H
    20000H - 00000H = 20000H

检测点 2.3

下面的3条指令执行后,CPU几次修改IP?都是在什么时候?最后IP中的值是多少?

1
2
3
mov ax, bx
sub ax, ax
jmp ax

修改了四次:

  1. 第1条指令执行后,IP指向第2条指令
  2. 第2条指令执行后,IP指向第3条指令
  3. 第3条指令执行后,IP指向第4条指令
  4. JMP指令执行后,IP重新指向第1条指令

第三章 寄存器(内存访问)

检测点 3.1

  1. 在Debug中,用“d 0:0 1f”查看内存,结果如下。

    1
    2
    0000:0000 70 80 F0 30 EF 60 30 E2-00 80 80 12 66 20 22 60
    0000:0010 62 26 E6 D6 CC 2E 3C 3B-AB BA 00 00 26 06 66 88

    下面的程序执行前,AX=0,BX=0,写出每条汇编指令执行完后相关寄存器中的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    mov ax, 1
    mov ds, ax
    mov ax, [0000] AX =
    mov bx, [0001] BX =
    mov ax, bx AX =
    mov ax, [0000] AX =
    mov bx, [0002] BX =
    add ax, bx AX =
    add ax, [0004] AX =
    mov ax, 0 AX =
    mov al, [0002] AX =
    mov bx, 0 BX =
    mov bl, [000C] BX =
    add al, bl AX =

    提示,注意ds的设置。

    2662H E626H E626H 2662H D6E6H FD48H 2C14H 0 00E6H 0 0026H 000CH

  2. 内存中的情况如图3.6所示。
    各寄存器的初始值:CS=2000H,IP=0,DS=1000H,AX=0,BX=0;
    (1)写出CPU执行的指令序列(用汇编指令写出)
    (2)写出CPU执行每条指令后,CS、IP和相关寄存器中的数值。
    (3)再次体会:数据和程序有区别吗?如何确定内存中的信息哪些是数据,哪些是程序?
指令 CS:IP DS AX BX
mov ax, 6622H 2000:3 1000H 6622H 0
jmp 0ff0:0100 2000:8->0ff0:0100 1000H 6622H 0
mov ax, 2000H 0ff0:0103 1000H 2000H 0
mov ds, ax 0ff0:0105 2000H 2000H 0
mov ax, [0008] 0ff0:0108 2000H C389H 0
mov ax, [0002] 0ff0:010B 2000H EA66H 0

程序和数据没有区别,本质上都是二进制01码,关键在于CPU如何解读。

检测点 3.2

  1. 补全下面的程序,使其可以将10000H~1000FH中的8个字,逆序复制到20000H~2000FH中。逆序复制的含义如图3.17所示(图中内存里的数据均为假设)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    mov ax, 1000H
    mov ds, ax
    __________
    __________
    __________
    push [0]
    push [2]
    push [4]
    push [6]
    push [8]
    push [A]
    push [C]
    push [E]

    答案:

    1
    2
    3
    mov ax, 2000H
    mov ss, ax
    mov sp, 0010H
  2. 补全下面的程序,使其可以将10000H~1000FH中的8个字,逆序复制到20000H~2000FH中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    mov ax, 2000H
    mov ds, ax
    __________
    __________
    __________
    pop [E]
    pop [C]
    pop [A]
    pop [8]
    pop [6]
    pop [4]
    pop [2]
    pop [0]

    答案:

    1
    2
    3
    mov ax, 1000H
    mov ss, ax
    mov sp, 0

第六章 包含多个段的程序

检测点 6.1

  1. 下面的程序实现依此用内存0:0-0:15单元中的内容改写程序中的数据,完成程序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    assume cs:codesg

    codesg segment
    dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h

    start: mov ax, 0
    mov ds, ax
    mov bx, 0

    mov cx, 8
    s: mov ax, [bx]
    _________
    add bx, 2
    loop s

    mov ax, 4c00h
    int 21h

    codesg ends

    end start

    答案:

    1
    mov cs:[bx], ax
  2. 下面的程序实现依此用内存0:0-0:15单元中的内容改写程序中的数据,数据的传送用栈来进行。栈空间设置在程序内。完成程序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    assume cs:codesg
    dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h

    dw 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

    start: mov ax, ____
    mov ss, ax
    mov sp, ____

    mov ax, 0
    mov ds, ax
    mov bx, 0
    mov cx, 8
    s: push [bx]
    ____________
    add bx, 2
    loop s

    mov ax, 4c00h
    int 21h

    codesg ends

    end start

    答案:

    1
    2
    3
    cs
    24h
    pop cs:[bx]

第九章 转移指令的原理

检测点 9.1

  1. 程序如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    assume cs:code

    data segment
    ___________
    data ends

    code segment
    start: mov ax, data
    mov ds, ax
    mov bx, 0
    jmp word ptr [bx+1]

    code ends
    end start

    若要使程序中的jmp指令执行后,CS:IP指向程序的第一条指令,在data段中应该定义哪些数据?
    答案:

    1
    dw 0, 0
  2. 程序如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    assume cs:code

    data segment
    dd 12345678H
    data ends

    code segment

    start: mov ax, data
    mov ds, ax
    mov bx, 0
    mov [bx], ____
    mov [bx + 2], ____
    jmp dword ptr ds:[0]

    code ends

    end start

    补全程序,使jmp指令执行后,CS:IP指向程序的第一条指令。
    答案:

    1
    2
    bx
    cs

    注意:bx指向低位,bx+2指向高位,低位为IP,而高位为CS。

  3. 用Debug查看内存,结果如下:

    1
    2000:1000 BE 00 06 00 00 00 ......

    则此时,CPU执行指令:

    1
    2
    3
    mov ax, 2000H
    mov es, ax
    jmp dword ptr es:[1000H]

    后,(CS)=?,(IP)=?
    答案:
    (CS)=0006,(IP)=00BE

检测点 9.2

补全编程,利用jcxz指令,实现在内存2000H段中查找第一个值为0的字节,找到后,将它的偏移地址存储在dx中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assume cs:code
code segment
start: mov ax, 2000H
mov ds, ax
mov bx, 0
s: __________
__________
__________
__________
jmp short s
ok: mov dx, bx
mov ax, 4c00h
int 21h
code ends
end start

答案:
1
2
3
4
mov cl, [bx]
mov ch, 0 ;注意这一步的必要性
jcxz ok
inc bx

检测点 9.3

补全编程,利用loop指令,实现在内存的2000H段中查找第一个值为0的字节,找到后,将它的偏移地址存储在dx中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assume cs:code
code segment
start: mov ax, 2000H
mov ds, ax
mov bx, 0
s: mov cl, [bx]
mov ch, 0
_________
inc bx
loop s
ok: dec bx
mov dx, bx
mov ax, 4c00h
int 21h
code ends
end start

答案:
1
inc cx ;注意loop的工作原理

第十章 CALL和RET指令

检测点 10.1

补全程序,实现从内存1000:0000处开始执行指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
assume cs:code

stack segment
db 16 dup (0)
stack ends

code segment
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax, ____
push ax
mov ax, ____
push ax
retf
code ends

end start

答案:
1
2
1000h
0

检测点 10.2

下面的程序执行后,ax中的数值为多少?

内存地址 机器码 汇编指令
1000:0 b8 00 00 mov ax,0
1000:3 e8 01 00 call s
1000:6 40 inc ax
1000:7 58 s: pop ax

ax中的数值为6,注意执行完call s后,IP先变为6,然后将IP的值压栈,最后跳转至s。

检测点 10.3

下面的程序执行后,ax中的数值为多少?

内存地址 机器码 汇编指令
1000:0 b8 00 00 mov ax,0
1000:3 9a 09 00 00 10 call far ptr s
1000:8 40 inc ax
1000:9 58 s: pop ax
add ax,ax
pop bx
add ax,bx

ax中的数值为1010H,注意执行完call far ptr s后,IP先变为8,然后将CS、IP的值分别为1000和8依此压栈,最后再跳转至s继续执行。

检测点 10.4

下面的程序执行后,ax中的数值为多少?

内存地址 机器码 汇编指令
1000:0 b8 06 00 mov ax,6
1000:3 ff d0 call ax
1000:5 40 inc ax
1000:6 58 mov bp,sp
add ax,[bp]

ax中的数值为0BH,分析方法类似检测点10.2

检测点 10.5

  1. 下面的程序执行后,ax中的数值为多少?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    assume cs:code
    stack segment
    dw 8 dup (0)
    stack ends
    code segment
    start: mov ax,stack
    mov ss,ax
    mov sp,16
    mov ds,ax
    mov ax,0
    call word ptr ds:[0eh]
    inc ax
    inc ax
    inc ax
    mov ax,4c00h
    int 21h
    code ends
    end start

    ax中的数值为3,注意ds与ss中存放的段地址相同,在执行了call word ptr ds:[0EH]之后,程序会先将下一条指令inc ax的偏移量压栈,然后跳转到栈顶所指向的指令的位置,即跳转至第一条inc ax的位置,故最后ax的值为3。
    注意:在使用Debug单步跟踪的时候,由于t命令所导致的中断,而影响了栈中的值。

  2. 下面的程序执行后,ax和bx中的数值为多少?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    assume cs:codesg
    data segment
    dw 8 dup (0)
    data ends
    code segment
    start: mov ax,data
    mov ss,ax
    mov sp,16
    mov word ptr ss:[0],offset s
    mov ss:[2],cs
    call dword ptr ss:[0]
    nop
    s: mov ax,offset s
    sub ax,ss:[0ch]
    mov bx,cs
    sub bx,ss:[0eh]
    mov ax,4c00h
    int 21h
    code ends
    end start

    ax中的数值为1,bx中的数值为0,注意到程序的一开始将a的偏移量和cs放入ss:[0]和ss:[2]中,然后调用call指令,将CS和IP(nop指令的偏移量)依此压栈后跳转到s处继续执行,ax最终为s的偏移量减去nop指令所在位置的偏移量,为1,bx最终为cs的段地址相减,为0。

第十一章 标志寄存器

检测点 11.1

写出下面每条指令执行后,ZF、PF、SF等标志位的值

指令 ZF PF SF
sub al, al 1 1 0
mov al, 1 1 1 0
push ax 1 1 0
pop bx 1 1 0
add al, bl 0 0 0
add bl, 10 0 1 0
mul al 0 1 0

检测点 11.2

写出下面每条指令执行后,ZF、PF、SF、CF、OF等标志位的值

指令 CF OF SF ZF PF
sub al, al 0 0 0 1 1
mov al, 10H 0 0 0 1 1
add al, 90H 0 0 1 0 1
mov al, 80H 0 0 1 0 1
add al, 80H 1 1 0 1 1
mov al, 0FCH 1 1 0 1 1
add al, 05H 1 0 0 0 0
mov al, 7DH 1 0 0 0 0
add al, 0BH 0 1 1 0 1

检测点 11.3

  1. 补全下面的程序,统计F000:0处32个字节中,大小在[32,128]的数据的个数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    	mov ax, 0f000h
    mov ds, ax

    mov bx, 0
    mov dx, 0
    mov cx, 32
    s: mov al, [bx]
    cmp al, 32
    __________
    cmp al, 128
    __________
    inc dx
    s0: inc bx
    loop s

    答案:

    1
    2
    jb s0
    ja s0
  2. 补全下面的程序,统计F000:0处32个字节中,大小在(32,128)的数据的个数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    	mov ax, 0f000h
    mov ds, ax

    mov bx, 0
    mov dx, 0
    mov cx, 32
    s: mov al, [bx]
    cmp al, 32
    __________
    cmp al, 128
    __________
    inc dx
    s0: inc bx
    loop s

    答案:

    1
    2
    jna s0
    jnb s0

检测点 11.4

下面的程序执行后:(ax)=?

1
2
3
4
5
6
7
8
9
mov ax, 0
push ax
popf ;将PSW置0(本章所学习的标志位都为0)
mov ax, 0fff0h
add ax, 0010h ;修改相关标志位
pushf
pop ax ;(将PSW保存至ax)
and al, 11000101B
and ah, 00001000B ;只考虑CF,PF,ZF,SF,OF五个标志位

答案:
(ax)=45H

第十二章 内中断

检测点 12.1

  1. 用Debug查看内存,情况如下:
    0000:0000 68 10 A7 00 8B 01 70 00-16 00 9D 03 8B 01 70 00
    则3号中断源对应的中断处理程序的入口地址为:____
    答案:
    0070:018B
    注意高地址存放段地址,低地址存放偏移地址

  2. 存储N号中断源对应的中断处理程序入口的偏移地址的内存单元的地址为:
    存储N号中断源对应的中断处理程序入口的段地址的内存单元的地址为:

    答案:4N,4N+2

第十三章 int指令

检测点 13.1

  1. 在上面的内容中,我们用7ch中断例程实现loop的功能,则上面的7ch中断例程所能进行的最大转移位移为多少?
    答案:65535

  2. 用7ch中断例程完成jmp near ptr s指令的功能,用bx向中断例程传送转移位移。
    答案:

    1
    2
    3
    4
    5
    jnp:	push bp
    mov bp, sp
    add [bp+2], bx
    pop bp
    iret

检测点 13.2

判断下面说法的正误:

  1. 我们可以编程改变FFFF:0处的指令,使得CPU不去执行BIOS中的硬件系统检测和初始化程序。
    错误:FFFF:0处的内容无法修改
  2. int 19h中断例程,可以由DOS提供。
    错误:此时DOS还未被引导

第十四章 端口

检测点 14.1

  1. 编程,读取CMOS RAM的2号单元的内容。
    1
    2
    3
    mov al, 2
    out 70h, 2
    in al, 71h
  2. 编程,向CMOS RAM的2号单元写入0。
    1
    2
    3
    4
    mov al, 2
    out 70h, 2
    mov al, 0
    out 71h, al

检测点 14.2

编程,用加法和位移指令计算(ax)=(ax)*10。
提示,(ax)*10=(ax)*2+(ax)*8。

1
2
3
4
5
mov bx, ax
shl ax, 1
mov cl, 3
shl bx, cx
add ax, bx

第十五章 外中断

检测点 15.1

  1. 仔细分析一下上面的int 9中断例程,看看是否可以精简一下?
    其实在我们的int 9中断例程中,模拟int指令调用原int 9中断例程的程序段是可以精简的,因为在进入中断例程后,IF和TF都已经置0,没有必要再进行设置了。对于程序段:

    1
    2
    3
    4
    5
    6
    7
    pushf
    pushf
    pop ax
    and ah, 11111100B
    push ax
    popf
    call dword ptr ds:[0]

    可以精简为:

    1
    2
    pushf
    call dword ptr ds:[0]

    两条指令。

  2. 仔细分析上面程序中的主程序,看看有什么潜在的问题?
    在主程序中,如果在执行设置int 9中断例程的段地址和偏移地址的指令之间发生了键盘中断,则CPU将转去一个错误的地址执行,将发生错误。
    找出这样的程序段,改写它们,排除潜在的问题。
    提示,注意sti和cli指令的用法。
    答案:

    1
    2
    3
    4
    cli
    mov word ptr es:[9*4],offset int9
    mov word ptr es:[9*4+2],cs
    sti

    以及:

    1
    2
    3
    4
    5
    6
    cli
    push ds:[0]
    pop es:[9*4]
    push ds:[2]
    pop es:[9*4+2]
    sti

第十六章 直接定址表

检测点 16.1

下面的程序将code段中a处的8个数据累加,结果存储到b处的双字中,补全程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
assume cs:code
code segment
a dw 1, 2, 3, 4, 5, 6, 7, 8
b dd 0
start: mov si, 0
mov cx, 8
s: mov ax, ____
add ____, ax
adc ____, 0
add si, ____
loop s

mov ax, 4c00h
int 21h

code ends
end start

答案:
1
2
3
4
a[si]
word ptr b[0]
word ptr b[2]
2

注意word ptr的使用

检测点 16.2

下面的程序将data段中a处的8个数据累加,结果存储到b处的字中,补全程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
assume cs:code, es:data

data segment
a db 1, 2, 3, 4, 5, 6, 7, 8
b dw 0
data ends

code segment
start: __________
__________
mov si, 0
mov cx, 8
s: mov al, a[si]
mov ah, 0
add b, ax
inc si
loop s

mov ax, 4c00h
int 21h
code ends
end start

答案:
1
2
mov ax, data
mov es, ax

第十七章 使用BIOS进行键盘输入和磁盘读写

检测点 17.1

“在int 16h中断例程中,一定有设置IF=1的指令”,这种说法对吗?

正确,int 16h中断例程在键盘缓冲区中没有数据时,会等待直到键盘缓冲区中有数据为止,因此,int 16h中需要处理int 9h中断,所以一定有设置IF=1的指令。

评论和共享

  • 第 1 页 共 1 页
作者的图片

码龙黑曜

iOS开发者/计算机科学/兽人控


华中科技大学 本科在读


Wuhan, China