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

研究试验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 页 共 1 页
作者的图片

码龙黑曜

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


华中科技大学 本科在读


Wuhan, China