李忠《x86汇编语言:从实模式到保护模式》第2版检测点以及章节习题答案

源文件见github,欢迎在github通过 Issues 或 Pull requests 提出建议和修改意见。

第1章

检测点1.1

  • 13   15   78   255   128   56091

检测点1.2

  • 1000   1010   1100   1111   11001   100,0000   110,0100
    1111,1111,1111,1111   1,0000,0000,0000,0000,0000

检测点1.3

  • 8   10   11   12   13   14   15   16   31   1741   1022   4092   65535

检测点1.4

  • 8   A   C   F   19   40   64   FF   3E8   FFFF   10,0000

检测点1.5

  1. 将下列十六进制数转换成二进制数
    11   1010   1100   1111   10,0000   11,1111   10,1111,1110  
    1111,1111,1111,1111   1001,1111,1100,0000,0101,1101  
    111,1100,1100,1111,1111,1110,1111,1111
  2. 快速说出以下十进制数所对应的二进制数和十六进制数
    1/1   11/3   101/5   111/7   1001/9   1011/B   1101/D   1111/F
    0/0   10/2   100/4   110/6   1000/8   1010/A   1100/C   1110/E

检测点1.6

Windows11可以在开始菜单或搜索框中输入“计算器”,然后在左上角菜单中选择“程序员”

  1. FFCH:4092D/1111,1111,1100B
  2. FFCH乘以27C0H:27B,6100H/10,0111,1011,0110,0001,0000,0000B

第1章习题

  1. 5DCH15D=1111B
    12D=1100B10D=1010B8H=1000B
    11D=1011B14D=1110B16D=1 0000B
  2. 12H   1 0101B   1000 1111B   10 0000 0000B   1FFH

第2章

第2章习题

  • 0   FFFFF   2 20 2^{20} 220   1024   1

第3章

检测点3.1

  1. 一个字含有( 2 )字节和( 16 )比特,一个双字含有( 4 )字节、( 2 )个字和( 32 )比特。
  2. 二进制数1000 0000中,位( 7 )的那个比特是“1”,也就是第( 8 )位。它是最高位。
  3. 一个存储器的容量是16字节,地址范围为( 0 )~( 0FH )。用该存储器保存字数据时,可存放( 8 )个字,这些字的地址分别是( 0 2 4 6 8 0AH 0CH 0EH )。保存双字则为 0 4 8 0CH。

检测点3.2

  1. INTEL 8086 有AX、BX、CX、DX、SI、DI、BP、SP八款通用寄存器。长度为16比特,2字节。
  2. DX的内容是083CH。

检测点3.3

  1. ( 8 ) ( AX、BX、CX、DX、SI、DI、BP、SP ) ( AH、AL、BH、BL、CH、CL、DH、DL )
  2. ( A ) ( C ) ( DF )
  3. ABCDF

第3章习题

  1. 64个。1MB/16KB=64
  2. 25BC0H~35BBFH。25BC0H+FFFFH=35BBFH

第4章

检测点4.1

  1. ( B )( A )( C )

第4章习题

(1) 分别为0、35H、40H。
如图4-8,
源程序第1行第1个字符m的编码在第一行第一个,为6D,偏移量是0
源程序第2行第1个字符a的编码在第四行第六个,为61,偏移量是35H
源程序第3行第1个字符a的编码在第五行第一个,为61,偏移量是40H
(2) 73字节。
程序最后一个字符的偏移量是48H,0~48H共49H=73字节

第5章

检测点5.1

  1. ( 0 )( 0 )( 1 )( 0 )( 0 )( 1 )
  2. ABCD

检测点5.2

  1. 如果出现了“operation size not specified”的错误提示,可以试试在第3,4,5行的mov后面加上byte
    mov byte [0x00],'a'
    mov byte [0x02],'s'
    mov byte [0x04],'m'
    
  2. 屏幕上显示了asm3个字符

第6章

检测点6.1

  1. ( 0xb8000 )( 0xb800 )( 0xf9e )( 0x48 )( 0x27 )
    一行160字符,25行一共4000字节,4000=0xfa0,而0xfa0-2=0xf9e就是最后一个字符的偏移量,先写入字符的ASCII码,再写入字符的显示属性
    'H’的ASCII码为0x48,绿底白字为0010,0111B=27H
  2. ABCDIK
    A:al只有8比特,而0x55aa有16比特,数据宽度不同。如果保持这样写也能编译成功,但效果等同于mov al,0xaa
    B:不能直接把立即数传送给段寄存器,需要先传送给寄存器,寄存器再传送给段寄存器
    C:ds有16比特,而al只有8比特,数据宽度不同
    D:没有指定操作大小,0x55aa既可以按word操作,也可以按dword(双字)操作
    I:ax有16比特,而bl只有8比特,数据宽度不同
    K:处理器没有在两个内存单元之间直接进行传送的指令
    注:G会忽略word,效果等同于mov [0x0a],ax,但改成mov byte [0x0a],ax就会报错

检测点6.2

  • 0xf000处出现警告,如果不修改,将编译为00
    实际编译结果为:
    1 00000000 55000F                  data1 db 0x55,0xf000,0x0f
    1          ******************       warning: byte data exceeds bounds [-w+number-overflow]
    2 00000003 38002000AA55            data2 dw 0x38,0x20,0x55aa
    

检测点6.3

  1. 执行后AX中的内容是0x210E。
  2. 段内偏移地址为0x0030,物理内存地址为0x90230。
  3. BFGHIJ
    B:没有指定操作大小,既可以是byte也可以是word
    F:没有指定操作大小
    G:xor的目的操作数不能是通用寄存器或内存单元
    H:add的目的操作数只能是通用寄存器或内存单元
    I:div的操作数只能是通用寄存器或内存单元
    J:add无法将8位寄存器加给16位寄存器
  4. 如果执行div bh,那么AX=0x0001,BX=0x9000,DX=0x0001
    如果执行div bx,那么AX=0x0001,BX=0x9000,DX=0x7090

检测点6.4

  • ( E9 02 00 )
  • ( EA 05 00 00 20 )

检测点6.5

第6章习题

  1. 问题在于21015除以10后,商2101大于al的最大上限255即0xff。修正后的代码如下:
    mov ax,21015
    xor dx,dx
    mov bx,10
    div bx
    and cl,0xf0
    
  2. 将第37行的mov ax,number修改为mov ax,infi
    显示infi的偏移地址为299D0x12b
  3. 使用db 0xcd,0x88

第7章

检测点7.1

  • ( A )( B )( D )( C )( F )( E )( I )( H )( G )

检测点7.2

  • CE
    原因:在8086cpu上,只能使用BX、SI、DI、BP来提供偏移地址

检测点7.3

  • 0xf0=-16   0xff=-1   0x81=-127
  • 0xffff=-1   0x8a08=-30200

检测点7.4

  1. ( 1 )( 1 )( 0 )( 1 )( 0 )( 0 )( 0 )( 0 )( 0 )
    当ZF=1时,表明计算结果为零;
    而jz完全等价于je,它们对应同一个操作码,jnz和jne同理
    jz或je指令的意思是当ZF=1,即计算结果为0时转移;
    jnz或jne指令的意思是当ZF=0,即计算结果非0时转移;
  2. cmp ax,bx
    ja  lbb
    je  lbz
    jb  lbl
    

检测点7.5

第7章习题

  1. 正数7个,负数6个
    • 正数:
      • 0x05 0x30
      • 0x90 0xa0 0x1235 0x2f 0xc0
    • 负数:
      • 0xff 0x80 0xf0 0x97
      • 0xfff0 0xc5bc
  2. 见github源文件xt7-2.asm
  3. 65536次,即0x10000次
    cx=0,减去1次1后变成...1111 1111 1111,此时loop判断cx非0,跳转到delay,再减去1111,1111b次1后变成...1111 0000 0000,此时cx再一次等于0,loop跳出循环
    1111 1111b=65535,65535+1=65536

第8章

检测点8.1

  1. ax=0xfff0
    and [data],ax后data的内容为0xaa50
    or ax,[data]后ax不变
  2. AC
    B错误,是先访问栈段,读取栈顶数据,再将SP的内容减2。出栈是入栈的反向操作
  3. push ds
    push bx
    push ax
    mov bx,ss
    mov ds,bx   ;将ss赋值给ds
    mov bx,sp
    mov dx,[bx] ;此时ds:bx等同于ss:sp
    pop ax
    pop bx
    pop ds
    

第8章习题

  1. 修改后
    ;以下计算1到100的和 
    xor ax,ax
    mov cx,100
    @f:
    add ax,cx
    loop @f
    
  2. 见github源文件xt8-2.asm
    不足:1+2+…+1000=500500,除以10后小于65535,ax能够保存商,此时无需考虑除法溢出;
    但如果累加和的数值除以10后大于65535,ax不够保存商,此时会发生除法溢出;
    如果感兴趣可以参考王爽的《汇编语言》一书中实验10的2. 解决除法溢出的问题。(注意:MASM和NASM语法有些许差异)

第9章

检测点9.1

  1. 0x70
  2. 0x80
  3. 0

data1的vstart=0,lba相对于data1起始处的距离为0
注意mov ax,lbamov ax,[lba]的区别
如果是mov ax,lba,那么ax将等于ds:[0]的内容

  1. 2

data2的vstart=0,lbc相对于data2起始处的距离为2

  1. 0x80

data3没有说明vstart=0,lbd相对于整个程序开头的距离为0x80

检测点9.2

  1. call label_proc
  2. call bx
  3. call [bx]
  4. call 0xf000:0x0002
  5. call far [0x80]
  6. call far [bx+di+0x08]

检测点9.3

  1. ax=0x2a95
  2.  
    • a. jmp label_proc

    • b. jmp bx

    • c. jmp [es:bx]

      注意是附加段

    • d. jmp 0xf000:0x0002

    • e. jmp far [0x80]

    • f. jmp far [es:bx+di+0x08]

第9章习题

  1. 可以将第150到第154行代码改为
    mov ax,[es:code_2_segment]
    mov [gotocode_2+2],ax
    jmp far [gotocode_2]
    gotocode_2 dw begin,0
    
  2. 如果用户程序的实际长度小于等于512个字节,那么加载程序无法正常运行
    cx初始为0,执行一次loop @2后,cx变为0xffff,继续循环
    loop @2这条命令设置断点后,按c即可进入下一次循环,观察每次运行到loop @2时ds或ax的值
    在循环到ds=0xb800时,屏幕文字将开始依次消失(ASCII码全为0)
    在循环到ds=0xfffe时,下一次循环时ds将会变成0
    在循环到ds=0x07a0时,下一次循环时0x7c00的指令被替换,加载程序无法正常运行

第10章

检测点10.1

检测点10.2

  • 使用help xp查看xp命令的使用方法

  • 使用xp /512bx 0查看内存中0:0-0:200的数据

    512:512个,b:字节,x:16进制。512bx就是以16进制显示512个字节,0:从地址0x0开始

  • 使用xp /4bx 0x1c0查看0x70号中断处理过程的段地址和偏移地址

    0x70*4=0x1c0

第10章习题

  1. 将c10-1.asm中第157行到第159行的代码改为如下:
    mov al,11111110b     ;允许RTC中断,阻断从片其他中断
    out 0xa1,al          ;写到8259从片的IMR寄存器
    
    mov al,11111011b     ;只允许从片上的中断(从片通过bit 2与主片级联)
    out 0x21,al          ;写到8259主片的IMR寄存器
    
  2. 将c10-1.asm中第147行到第151行的代码改为如下:
    mov al,0x0a          ;RTC寄存器A
    or al,0x80           ;阻断NMI
    out 0x70,al
    in al,0x71           ;读取寄存器A
    and al,11110000b     ;清空第0位到第3位
    or al,00001110b      ;将分频电路的分节点设置为250ms
    ;or al,00001111b      ;如果要设置成500ms则替换为此指令
    out 0x71,al
    
    mov al,0x0b          ;RTC寄存器B
    or al,0x80           ;阻断NMI 
    out 0x70,al
    mov al,01000010b     ;设置寄存器B,允许周期性中断,BCD码,24小时制
    out 0x71,al
    

第12章

检测点12.1

参照P211图12-4

  1. 段基地址是0x00ffffff,段界限是0xfffff,G=0,D=1,L=0,AVL=0,P=1,DPL=00,S=1,TYPE=1010b=0xa
  2. 高32位:0x00C0 922F
    低32位:0xC0F0 01FF

    题目未明确指出特权级与数据段的TYPE(栈段也是特殊的数据段,P211),所以高32位的9,10,13,14位可以为0或1
    即高32位的第8到15位为1 _ _ 1 0 _ _ 0 b,其中下划线可以为0或1

第12章习题

该段一共8KB,段的线性地址范围为0~0x1fff

G=1表示段界限以4KB为单位,E=0表示向上扩展,段界限为1表示段从0到1单位
1个单位是4KB,0到1一共2个单位,所以段的大小为8KB
故段的线性地址范围为0~0x1fff


如果无法理解,可以想想段界限为0的时候,应当是指段有1个4KB,段界限为1的时候,就应当是指段有2个4KB,段界限为0xffff=65535的时候,是指段有0x10000=65536个4KB,即4GB。

第13章

检测点13.1

  1. jmp 0000000000010_0_00B:flush+0x7c00
    
    注意,基地址改成0后,段界限应改成0x7dff,即
    mov dword [bx+0x10],0x00007dff
    mov dword [bx+0x14],0x00409800
    

第13章习题

  1. mov bx,16的机器码是BB 10 00mov ebx,16的机器码是BB 10 00 00 00,在32位模式时,需要给BB 10 00加上反转操作数尺寸的前缀0x66
    66 BB 10 00             mov bx,16
    BB 10 00 00 00          mov ebx,16
    
  2. 机器指令序列会被识别成mov ebx,0xe3f70010

    在32位模式下,mov的操作尺寸是32位,所以BB后面的4个字节都是mov的操作数

第14章

检测点14.1

  1. 当粒度为字节时,实际使用的段界限是0xffffc
    当粒度为4KB时,实际使用的段界限是0xffffcfff

    参考P246

  2. 会产生异常

    0x0088=0000000010001_0_00B,索引号是10001B=0x11,0x11*8+7=0x8f大于0x87,在GDT边界之外

检测点14.2

该段的有效地址范围是0x006f,f000~0x006f,ffff

题目给出左侧的边界0x700000不对

当ESP的内容为0xffff,f002时,不能压入一个双字,因为0xffff,f002-4=0xffff,effe,而0xffff,effe小于实际使用的段界限0xffff,efff


个人感觉本节内容有些瑕疵

以检测点14.2为例,如果你令esp=1,然后压入一个双字,根据P253的规则,实际使用的段界限+1≤(ESP的内容-操作数的长度)≤0xffffffff,1-4=0xffff,fffd在此范围内,但是如果运行,会产生异常

对于TYPE字段的E,应当这样理解

E=0时,表示可访问的空间为 [base, base+limit],base 表示段基地址,limit 表示实际使用的段界限,中括号 [ ] 表示闭区间,即两个边界值都包括在内,base+limit 超出0x1,0000,0000的部分从0x0开始;
E=1时,表示 [base, base+limit] 这段空间不可访问,可访问的空间是该区间的补集,即除了这段空间,其他空间都可以访问。

以检测点14.2为例,[0x0070 0000, 0x1 006F EFFF]此段空间不可访问,0x1 0000 0000~0x1 006F EFFF这段部分溢出,从0x0开始,因此可访问的部分为[0x006F F000, 0x006F FFFF]

第14章习题

  1. 见github源文件xt14-1.asm

    个人觉得题目有点费解

  2. 描述符中的界限值应当是0xffffd

    0xffffd实际使用的段界限是0xffff dfff,设段基地址为0x10000,0xffff dfff+0x10000=0xdfff,有效地址范围则为0xe000~0xffff,共8KB

第15章

在内核(core)源代码中将第466行到第474行(建立程序堆栈段描述符)修改如下

;建立程序堆栈段描述符
mov ecx,4096       ;分配4KB栈段空间
call sys_routine_seg_sel:allocate_memory
mov eax,ecx                        ;堆栈段起始线性地址
mov ebx,4095                       ;段界限
mov ecx,0x00409200                 ;字节粒度的堆栈段描述符
call sys_routine_seg_sel:make_seg_descriptor
call sys_routine_seg_sel:set_up_gdt_descriptor
mov [edi+0x1c],cx

在用户程序(app)源代码中将堆栈段去除,程序头部的section.stack.startstack_end可改为0,并将第67行中的mov esp,stack_end修改为mov esp,4096

详情见github源文件xt15_core.asmxt15_app.asm

第16章

检测点16.1

  1. ( B ) ( A )
  2. CPL=RPL=DPLCPL≥DPL,RPL≥DPL
  3. 2和3

检测点16.2

  1. CPL≥目标代码段描述符的DPLCPL≤调用门描述符的DPL,RPL≤调用门描述符的DPL
  2. 调用门也可以安装在LDT中
    0x550x2fc021
    1≤CPL≤2,RPL≤2

检测点16.3

任务状态段TSS是在任务切换时,为了保护旧任务的状态,并在下次重新执行时恢复它们而设置的段,只会在任务切换时,才会读取新任务中的TSS的信息。

图16-26的情况并不涉及任务切换,所以即使所有的段寄存器和通用寄存器的值都是0,也不影响任务的执行。

任务状态段的说明详情见原书P284

第16章习题

  1. jmp far [fs:TerminateProgram]改为call far [fs:TerminateProgram]即可

    和c16_app.asm做法相同

  2. 见github源文件xt16-2_core.asmxt16-2_app.asm
    要点:

    • 由于其他例程的参数个数都为0,而只有读取硬盘扇区的例程参数个数为3,在安装调用门时不方便循环(代码清单16-1第829行开始),所以可以在SALT表中添加一个代表参数个数的位置,在循环时将参数个数传递给cx(代码清单16-1第836行)
      例如:
      在每个salt条目的结尾添加一字节的的位置来表示参数个数db 参数个数,循环时将参数传递给cx

      mov cx,[edi+262]              ;该条目的参数个数
      or cx,1_11_0_1100_000_00000B  ;特权级3的调用门
      
    • 内核的read_hard_disk_0是用寄存器传递参数。
      要么修改read_hard_disk_0例程,并将内核调用read_hard_disk_0的地方都改为用栈传递参数;
      要么再添加一个专门用栈传递参数的例程,比如可以对代码清单16-1第148行的read_hard_disk_with_gate进行修改,这样只需要在app中用栈传递参数

tips:在vscode中按Ctrl + Shift + P输入compare active file with可以将当前代码与其他代码比较

第17章

第17章习题

第18章

第18章习题

第19章

检测点19.1

  1. 有可能

    每个任务都可以拥有4GB的虚拟内存空间。任务A和任务B都有自己独立的4GB虚拟内存空间,线性地址可以相同。

  2. 有可能会访问到物理内存中的同一个位置。比如任务A访问0xa000,0000线性地址,任务B也访问0xa000,0000线性地址,这2个线性地址在A和B各自的页映射表中有可能对应了物理内存中的同一个位置。

    0x8000,0000~0xffff,ffff的虚拟内存空间是全局的,是所有任务共享的,对应着内核的文件。不过即使在所有任务共享的内核空间,同一个线性地址也有可能映射到不同的物理地址(不然为什么每个任务的虚拟内存空间都要一个全局部分呢,直接用同一个不就好了嘛)

检测点19.2

操作系统先分配一个空闲的页,作为程序的页目录;
然后根据线性地址的高10位0x030作为索引,在页目录中寻找对应的页目录项,然后寻找并分配了一个空闲的页0x0000,3000,作为0x030指向的页表的物理地址,将其高20位填写到0x030对应的页目录项;
之后根据线性地址的中间10位0x005作为索引,在页表中寻找到对应的页表项,然后寻找并分配了一个空闲的页0x0000,a000,作为该线性地址对应的物理页,将其高20位填写到0x005对应的页表项;
最后,从硬盘中将原本的数据写入到页内偏移0x032的物理地址中,即0x0000,a032物理地址中。

第19章习题

如果不映射到任务的4GB地址空间内,将无法正常通过执行该命令调用系统中显示字符串的例程。

第20章

检测点20.1

程序header段的vstart=0,而程序也是从线性地址0开始加载的,
标号start的汇编地址等于其相对于header段开头的偏移量加上0,而它的线性地址也等于偏移量加上0,
因此,它的汇编地址和线性地址相同。

检测点20.2

内核header段的vstart=0x80040000,而内核是从线性地址0x80040000开始映射的(低1MB都被映射到了线性地址高2GB的前1MB),
所以标号.return的汇编地址等于其相对于header段开头的偏移量加上0x80040000,而它的线性地址也等于偏移量加上0x80040000。
因此,它的汇编地址和线性地址相同。

第20章习题

  1. 本人尝试使用一个固定值0x7c00,也就是把c20_core.asm第494行改为mov dword [tss+4],0x7c00,并没有发现什么问题,如果你们发现了什么问题,欢迎提出 Issue 或者 Pull request
  2. 本人没有想到其他方法,如果你们想到了其他方法,欢迎提出 Issue 或者 Pull request
  • 12
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值