OS lab1

Outline:

  • 汇编问答
  • 调用约束
  • 汇编避坑

NASM tutorial

NASM教程

汇编问答

用于OS第一次作业

  1. 请简述 80x86 系列的发展历史

    • 1978年6月,intel推出第一款16位微处理器8086,采用20位地址线
    • 1982年发布80286,主频提高至12MHz
    • 1985年发布80386,处理器变为32位,地址线扩展至32位
    • 1989年发布80486,1993年发布80586并命名为奔腾
  2. 说明小端和大端的区别,并说明 80x86 系列采用了哪种方式?

    • 小端存储:数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中
    • 大端存储反之
    • 80x86采用小端存储
  3. 8086 有哪五类寄存器,请分别举例说明其作用?

    • 通用寄存器:
      • 数据寄存器: AX,BX,CX,DX
        • AX (Accumulator):累加寄存器,也称之为累加器;
        • BX (Base):基地址寄存器;
        • CX (Count):计数器寄存器;
        • DX (Data):数据寄存器;
      • 指针寄存器:
        • SP (Stack Pointer):堆栈指针寄存器;
        • BP (Base Pointer):基指针寄存器;
      • 变址寄存器:
        • SI (Source Index):源变址寄存器;
        • DI (Destination Index):目的变址寄存器;
    • 控制寄存器:
      • IP (Instruction Pointer):指令指针寄存器;
      • FLAG:标志寄存器;
    • 段寄存器:
      • CS (Code Segment):代码段寄存器;
      • DS (Data Segment):数据段寄存器;
      • SS (Stack Segment):堆栈段寄存器;
      • ES (Extra Segment):附加段寄存器;
  4. 什么是寻址?立即寻址和直接寻址的区别是什么?

    • 找到操作数的地址(从而能够取出操作数)
    • 区别:
      • 立即寻址
        • MOV AX 1234H
        • 直接给出了操作数,事实上没有“寻址”
      • 直接寻址
        • MOV AX [1234H]
        • 直接给出了地址1234H,用[]符号取数
  5. 请举例说明寄存器间接寻址、寄存器相对寻址、基址加变址寻址、相对基址加变址寻址四种方式的 区别

    • 寄存器间接寻址

      MOV AX [BX] 操作数有效地址在寄存器之中(SI、DI、BX、BP)

    • 寄存器相对寻址 • MOV AX [SI+3]

    • 基址加变址 •MOV AX [BX+DI] • 把一个基址寄存器(BX、BP)的内容,加上变址寄存器(SI、DI)的内容。

    • 相对基址加变址 •MOV AX [3+BX+DI]

  6. 请分别简述 MOV 指令和 LEA 指令的用法和作用?

    • LEA:load effective address, 将一个内存地址直接赋给目的操作数
      • lea eax,[ebx+8]就是将ebx+8这个值直接赋给eax,而不是把ebx+8处的内存地址里的数据赋给eax。
    • MOV: 与LEA相反
  7. 请说出主程序与子程序之间至少三种参数传递方式

    • 利用寄存器传递参数,就是把参数放在约定的寄存器中

    • 利用约定的存储单元传递参数

    • 利用堆栈传递参数

  8. 如何处理输入和输出,代码中哪里体现出来?

    • 宏,系统调用
  9. 有哪些段寄存器

    • 见上文
  10. 通过什么寄存器保存前一次的运算结果,在代码中哪里体现出来。

    • 注释里写了
  11. 解释 boot.asm文件中, org 0700h的作用

    • 告诉汇编器,当前这段代码会放在07c00h处。所以,如果之后遇到需要绝对寻址的指令,那么绝对地址就是07c00h加上相对地址。
    • 在第一行加上org 07c00h只是让编译器从相对地址07c00h处开始编译第一条指令,相对地址被编译加载后就正好和绝对地址吻合
  12. boot.bin 应该放在软盘的哪一个扇区?为什么?

    • first
    • 开机,从ROM运行BIOS程序,BIOS程序检查软盘0面0磁道1扇区,如果扇区以0xaa55结束,则认定为引导扇区,将其512字节的数据加载到内存的07c00处,然后设置PC,跳到内存07c00处开始执行代码。
  13. loader 的作用有哪些?

    • 为了突破512字节的限制,我们引入另外一个重要的文件loader.asm,引导扇区只负责把loader加载入内存并把控制权交给他,这样将会灵活得多。
    • 最终,由loader将内核kernel加载入内存,才开始了真正操作系统内核的运行。
      • 跳入保护模式
        • 最开始的x86处理器16位,寄存器用ax, bx等表示,称为实模式。后来扩 充成32位,eax,ebx等,为了向前兼容,提出了保护模式
        • 必须从实模式跳转到保护模式,才能访问1M以上的内存。
      • 启动内存分⻚
      • 从kernel.bin中读取内核,并放入内存,然后跳转到内核所在的 开始地址,运行内核
        • 跟boot类似,使用汇编直接在软盘下搜索kernel.bin
        • 但是,不能把整个kernel.bin放在内存,而是要以ELF文件的格式读取并 提取代码。
  14. 解释 NASM 语言中[ ]的作用

    • 解地址
  15. 解释语句 times 510-(\(-\)$) db 0 ,为什么是 510? $ 和 $$ 分别表示什么?

    • 填充剩下的空间,使得生成的二进制代码恰好为512字节( dw是两个字节 )
    • $表示当前行的偏移地址, $$表示当前段的起始偏移地址,
  16. 解释配置文件bochsrc 文件中如下参数的含义

    1
    2
    3
    4
    megs:32
    display_library: sdl
    floppya: 1_44=a.img, status=inserted
    boot: floppy
    • megs:虚拟机内存大小 (MB)
    • display_library:bochs使用的GUI库,在Ubuntu下面是sdl
    • floppya:虚拟机外设,软盘为a.img文件
    • boot:虚拟机启动方式,从软盘启动

调用约束

64 位 Linux 写一个集成了 C 语言库的程序时,你必须遵循以下的调用约束条件:

  • 传递参数时,按照从左到右的顺序,将尽可能多的参数依次保存在寄存器中。存放位置的寄存器顺序是确定的:
    • 对于整数和指针,rdirsirdxrcxr8r9
    • 对于浮点数(float 和 double 类型),xmm0xmm1xmm2xmm3xmm4xmm5xmm6xmm7
  • 剩下的参数将按照从右到左的顺序压入栈中,并在调用之后 由调用函数推出栈
  • 等所有的参数传入后,会生成调用指令。所以当被调用函数得到控制权后,返回地址会被保存在 [rsp] 中,第一个局部变量会被保存在 [rsp+8] 中,以此类推。
  • 栈指针 rsp 在调用前必须进行 16 字节对齐处理 。当然,调用的过程中只会把一个 8 bytes 的返回地址推入栈中,所以当函数得到控制权时,rsp 并没有对齐。你需要向栈中压入数据或者从 rsp 减去 8 来使之对齐。
  • 调用函数需要保存如下的寄存器:rbprbxr12r13r14r15。其他的寄存器可以自由使用。
  • 被调用函数也需要保存 XMCSR 的控制位和 x87 指令集的控制字,但是 x87 指令在 64 位系统上十分少见所以你不必担心这点。
  • 整数返回值存放在 rax 或者 rdx:rax 中,浮点数返回值存放在 xmm0 或者 xmm1:xmm0 中。

Tips

  • 栈是由高地址向低地址增长的.

  • 传递参数时,按照从左到右的顺序,将尽可能多的参数依次保存在寄存器中。存放位置的寄存器顺序是确定的:

    • 对于整数和指针,rdirsirdxrcxr8r9
    • 对于浮点数(float 和 double 类型),xmm0xmm1xmm2xmm3xmm4xmm5xmm6xmm7
  • 剩下的参数将按照从右到左的顺序压入栈中,并在调用之后 由调用函数推出栈

  • 等所有的参数传入后,会生成调用指令。所以当被调用函数得到控制权后,返回地址会被保存在 [rsp]第一个局部变量会被保存在 [rsp-8] 中,以此类推

    • 局部变量的入栈顺序:

      • 在没有溢出保护机制下的编译时,我们可以发现,所有的局部变量入栈的顺序(准确来说是系统为局部变量申请内存中栈空间的顺序)是正向的,即哪个变量先申明哪个变量就先得到空间, 也就是说,编译器给变量空间的申请是直接按照变量申请顺序执行的。

      • 在有溢出保护机制下的编译时,情况有了顺序上的变化,对于每一种类型的变量来说,栈空间申请的顺序都与源代码中相反,即哪个变量在源代码中先出现则后申请空间;而对不同的变量来说,申请的顺序也不同,有例子可以看出,int型总是在char的buf型之后申请,不管源代码中的顺序如何(这应该来源于编译器在进行溢出保护时设下的规定)。

汇编避坑

  • 慎用宏定义,它内部无法跳转和定义标签,因此无法封装复杂逻辑 ; 同时,使用时要记住宏定义内部对寄存器的更改
  • 可以push rax, 并使用al, 事实上你无法push al,PUSH 指令只对至少16位的寄存器使用