OS开发过程涉及的一些基础知识

454 阅读6分钟
table th:first-of-type {width: 80px; } table th:nth-of-type(2) {width: 249px; } table th:nth-of-type(3) {width: 249px; }

开发过程涉及的一些基础知识

编译器和编译工具

  • gcc:GUN c语言编译器,支持C99标准并拥有独特的扩展
  • as:GAS汇编语言编译器,用于编译AT&T格式的汇编语言
  • ld:链接器,用于将编译文件链接成可执行文件
  • nasm:NASM汇编语言编译器,用于编译Intel格式的汇编语言
  • make:编译工具,根据编译脚本文件记录的内容编译程序

系统工具与命令

  • dd:复制制定大小的数据块,并在复制过程中转换数据格式
  • mount: 挂在命令,用于将U盘等存储设备挂在到指定路径上
  • umount:卸载命令,与mount命令功能相反
  • cp:复制命令
  • sync:数据同步命令,将已缓存的数据回写到存储设备上
  • rm:删除命令
  • objdump:反汇编命令,负责将可执行文件反编译成汇编语言
  • objcopy:文件提取命令,将源文件中的内容提取出来,再转存到目标文件中

Bochs相关的调试命令

指令说明举例
b address在某物理地址上设置断点b 0x7c00
c继续执行,知道遇到断点c
s单步执行s
info cpu查看寄存器信息info cpu
r查看寄存器信息r
sreg查看寄存器信息sreg
creg查看寄存器信息creg
xp /nuf addr查看内存地址内容xp /10bx 0x100000
x/nuf addr查看线性地址内容x /40wd 0x9000
u start end反汇编一段内存u 0x100000 ox100010
注: n代表显示单元个数;u代表显示单元大小[b: Byte, h:Word, w: DWord, g: QWord(4 字)];f代表显示格式(x: 16进制, d:10进制, t:2进制, c:字符)

AT&T汇编与Intel汇编格式对比表

项目Intel汇编格式AT&T汇编格式
书写格式关键字大写,如:MOV AX, 0x10关键字小写,如:mov %0x10, %ax
赋值方向从右向左,如:ADD 目的, 源从左向右
操作数前缀使用寄存器和立即数无需额外添加前缀,如:MOV CX,12使用寄存器必须在前面添加指令前缀%,使用立即数必须在前面添加前缀$,例如:mov $12, %cx
对于标识符变量,可以直接引用,无需添加前缀。例如:
values: .long 0x5a5a5a5a
movl values, %eax
此处的values是一个标识符变量,这条指令的意思是将values变量记录的0x5a5a5a5a装入寄存器eax中。
如果添加标识符前缀$,则说明正在引用该变量的地址。例如:
movl $values, %ebx
这条汇编指令的意思是将values变量的地址装入ebx寄存器
跳转和调用指令远跳转指令JMP的目标地址由段地址和段内偏移组成。远调用指令CALL的目标地址同样由段地址和段内偏移组成,远返回指令RET无操作数:
CALL FAR SECTION:OFFSET
JMP FAR SECTION:OFFSET
RET
对于远跳转指令和远调用指令必须使用前缀l加以修饰,与lcall指令相对应的是远返回指令lret。例如:
lcall $section:$offset
ljmp $section:$offset
lret
内存间接寻址格式Intel使用[ ]来表示间接寻址,完成格式为section:[base+index*scale+displacement]其中scale的默认值为1,可取值是1、2、4、8;section用于指定段寄存器,不同情况的默认段寄存器是不同的AT&T使用( )来表示间接寻址,格式为section:displacement(base, index, scale),这里的section, base, index, scale, displacement与Intel的使用规则相同
指令的后缀使用内存操作数时,应该对操作数的位宽加以限定,借助修饰符PTR可以限定操作数的位宽,例如:BYTE PTR代表一个字节、WORD PTR代表一个字、DWORD PTR代表一个双字等。例如:
MOV EAX, DWORD PTF [EBX]
AT&T语法中大部分指令在访问数据时都需要指明操作数的位宽,通常一个字节用b表示、一个字用w表示、一个双子用l表示、一个四字用q表示。例如:
movq %rax, %rbx
此外,跳转指令的地址标识符也可添加后缀以表示跳转方向,f表示向前跳转(forward),b表示向后跳转(back),如:
jmp lf
l:

NASM编译器

  1. 符号[]
  • 在NASM编译器中,如果直接引用变量名或者标识符,则被编译器认为正在引用该变量的地址
  • 如果希望访问变量内的数据,则必须使用符号[]
  1. 符号$
  • 符号$在NASM编译器中代表当前行被编译后的地址。
  1. 符号?
  • 代表一个Section(节)起始处被编译后的地址,也就是这个节的其实地址
  • $-?,表示本行程序距离Section起始处的偏移,如果只有一个节,它便表示本行程序距离程序起始处的距离

函数调用约定

  1. stdcall调用约定
  • 在调用函数时,参数将按照从右向左的顺序依次入栈。
  • 函数的栈平衡操作(参数出栈操作)是由被调用函数完成的。通过代码retn x可在函数返回时从栈上弹出x字节的数据。当CPU执行RET指令是,会将栈指针寄存器ESP向上移动x个字节,来模拟栈的弹出操作。
  • 函数编译过程中,编译器会在函数名前用下划线修饰,其后用符号@修饰,并加上入栈的字节数,因此int function(int first, int second)会被编译成_function@8
  1. cdecl调用约定
  • cdecl调用约定的参数压栈顺序与stdcall相同。
  • 函数的栈平衡操作使用调用函数完成的,这点与stdcall恰恰相反。stdcall调用约定使用代码retn x平衡栈,而cdecl调用约定则通常会借助代码leave、pop或向上移动栈指针等方法来平衡栈。
  • 每个函数调用者都含有平衡栈的代码,因此编译生成的可执行文件会比stdcall调用约定生成的文件大。
  • cdecl是GUN C编译器的默认调用约定。但GUN C在64位系统环境下,却使用寄存器作为函数参数的传递方式。函数调用者按照从左向右的顺序依次将前6个整形参数放在通用寄存器RDI、RSI、RDX、RCX、R8和R9中;同时,寄存器XMM0~XMM7用来保存浮点变量,而RAX寄存器则用来保存函数的返回值,函数调用者负责平衡栈。

参数传递方式

  1. 寄存器传递方式
  2. 内存传递方式

GUN C内嵌汇编语言

            `#define nop()  __asm__ __volatile__ ("nop    \n\t")`
  • __asm__关键字:用于声明这行代码是一个内嵌汇编表达式,它是关键字asm的宏定义(#define __asm__ asm)。
  • __volatile__关键字:告诉编译器此行代码不能被编译器优化,编译时代持代码原状。
  1. 内嵌汇编表达式

             `指令部分:输出部分:输入部分:损坏部分`
    
  • 指令部分是汇编代码本身,指令部分是内嵌汇编表达式的必填项,而其他部分可以省略。指令部分的编写规则要求是:当指令表达式中存在多条汇编代码时,可全部书写在一对双引号中;亦可将汇编代码放在多对双引号中。如果将所有指令编写在同一双引号中,那么相邻两条指令间必须使用分号(;)或换行符(\n)分隔。如果使用换行符,通常在其后还会紧跟一个制表符(\t)。当汇编代码引用寄存器时,必须在寄存器名前面再添加一个%符,以表示对寄存器的引用,例如代码"movl $0x10, %%eax"。
  • 输出部分
  1. 格式:(“输出操作约束符”(输出表达式······))
  2. 约束部分必须使用“=”或“+”进行修饰,“=”意味着输出表达式时一个纯粹的输出操作,“+”意味着输出表达式既用于输出操作,有用于输入操作。只能用在输出部分,不能用在输入部分,而且时可读写的。
  • 输入部分
    格式:(“输入操作约束符”(输入表达式······)),输入部分时只读的。
  • 损坏部分
  1. 操作约束和修饰符

每个输入/输出表达式都必须指定自身的操作约束。操作约束的类型可以细分为寄存器约束、内存约束和立即数约束。在输出表达式中,还有限定寄存器操作的修饰符。

  • 寄存器约束限定了表达式的载体是一个寄存器,可以明确指派,亦可模糊指派再由编译器自行分配。可用全名,也可以使用缩写名称。如下所示:
    __asm__ __volatile__ ("movl %0, %%cr0"::"eax"(cr0));
    __asm__ __volatile__ ("movl %0, %%cr0"::"a"(cr0));
缩写描述
r任何输入/输出型的寄存器
q从EAX / EBX / ECX / EDX中指派一个寄存器
g寄存器或者内存空间
m内存空间
a使用RAX / EAX / AX / AL寄存器
b使用RBX / EBX / BX / BL寄存器
c使用RCX / ECX / CX / CL寄存器
d使用RDX / EDX / DX / DL寄存器
D使用RDI / EDI / DI寄存器
S使用RSI / ESI / SI寄存器
f选用浮点寄存器
i一个整数类型的立即数
F一个浮点类型的立即数
  • 内存约束限定了表达式的载体是一个内存空间,使用约束名m表示。例如:
    __asm__ __volatile__ ("sgdt %0":"=m"(__gdt_addr)::);
    __asm__ __volatile__ ("lgdt %0"::"m"(__gdt_addr));
  • 立即数约束只能用于输入部分,它限定了表达式的载体是一个数值。例如:
   __asm__ __volatile__ ("movl %0, %%ebx"::"i"(50)); 

使用约束名i限定整数类型的立即数,F位浮点数。

  • 修饰符只可用在输出部分,除了等号=和加号+外,还有&符。