这篇文章的分析对象是赵炯的《Linux 内核完全注释》第 4.9 节的“一个简单的多任务内核实例”。
这里将对 boot.s 和 head.s 两个程序的关键语句进行汇编语法层面的分析。
语法格式的不同
汇编语言的语法主要分为两种:Intel 汇编语法、AT&T 汇编语法。
as86、MASM、TASM、NASM 等汇编器采用的是 Intel 汇编语法,而 GNU as (gas) 采用的是 AT&T 汇编语法。但即使采用同一汇编语法的汇编器,在具体的语法上也可能存在差别,需要特别注意。
关于两种语法的差异,可以参考文章《Linux 汇编语言开发指南》。
我们这里要分析的引导程序 boot.s 用的是 Intel 语法,内核程序 head.s 用的是 AT&T 语法。具体细节可以查阅《Linux 内核完全注释》的第 3 章。
boot.s
Intel 汇编语法中,注释用分号开头。
entry start
:指明程序的入口在 start 标签处
jmpi go, #BOOTSEG ; 井号是可选的立即数常量前缀符int 0x13
:int 指令用于产生软件中断,操作数即为中断号rep movw
:重复执行 movw 操作,操作次数由当前 ECX 寄存器的值确定,源操作数默认为 DS:SI,目的操作数为 ES:DIlidt idt_48
:这里的标签 idt_48 就相当与内存地址,即从该标签所在位置加载 6 个字节内容到 IDTR 中lmsw ax
:加载机器状态字,即将 AX 的内容加载到控制寄存器中.org 510
:从当前位置开始,填充 0 直到 510 字节处,指令语法为.org new_loc, fill
,其中 fill 操作数表示要填充的内容,可省略,即默认为 0
head.s
.text
:表明只读代码段的开始startup_32:
:程序入口标签movl $0x10, %eax
:$
是立即数的前缀,%
是寄存器的前缀,当操作数的大小可能产生歧义时,由指令的最后一个字母来确定操作数大小,movl 中的 l 表示 long,即操作数为 4 字节lss init_stack, %esp
:lss 是加载堆栈段寄存器(SS)的指令,init_stack 标签对应的内存地址加载到 ESP 寄存器中lea idt(,%ecx,8), %esi
:间接内存引用,完整指令格式为lea section:disp(base, index, scale), %esi
,其中 section 是所在段,disp 是偏移量,scale 是比例因子,等价于 Intel 语法的lea esi, section:[base + index*scale]
,即lea esi, [idt + ecx*8]
,表示把 idt 偏移 ecx*8 处的地址存入 esi 寄存器中。movl %eax, (%esi)
:括号表示寄存器间接寻址mov %edx, 4(%esi)
:等价于 Intel 格式的 [%esi + 4]pushfl
:将标志寄存器压栈popfl
:将标志寄存器弹栈iret
:中断返回指令shl $1, %ebx
:ebx 寄存器逻辑左移 1 位,shr 表示逻辑右移,sal 和 sar 对应算术左移和右移jb 1f
:跳转到标签 1 处。汇编中数字 0~9 为局部标签,不同地方可重复定义该标签。此处 f 表示 forward,即往前跳转到未执行的语句,对应还有 b,表示 backward,即往回跳转到执行过的语句。因此这里的 jb 1f 表示往前跳转到还未执行过的标签 1。lret
:远返回指令,从一个段返回到另一个段,Intel 语法为 ret farljmp $section, $offset
:远转移,从一个段跳转到另一个段,Intel 语法为jmp far ip, cs
,其中 jmp far 可简写为 jmpilcall $section, $offset
:远调用,调用其它段的程序,Intel 语法为call far ip, cs
或call far [cs:ip]
,后一种写法是提供一个内存地址[cs:ip]
,该指令会读取从该内存地址开始的 6 字节操作数,高两字节对应 cs,低四字节对应 ip。远转移指令也可以采用这种 6 字节操作数的写法。
标签/标号
由于之前发现还有很多人没有正确理解汇编中的标签,因此在这里稍做解释。
汇编器编译汇编程序时,会有一个计数器,初始为零,用于计算当前指令相对于程序起始位置的偏移量。
标签的值,就是当汇编器编译到标签位置时,计数器的值。也就是说,标签的值就是其所在位置与程序起始位置的地址偏移量。
以 boot.s 程序为例,当它被加载到 0x7c00 的位置时,go 标签对应的值为 0x05,此时处于实模式下,因此 jmp bootseg:go 等价于 jmp 0x07c0:0x05,即 jmp 0x7c05。
下面是 NASM 汇编器版本的 boot.s 程序,左边一列表示计数器的值,即程序内部的地址偏移,可对照该代码理解标签的含义。
bootseg equ 0x07c0
sysseg equ 0x1000
syslen equ 17
start:
00000000 jmp bootseg:go
go:
00000005 mov ax,cs
00000007 mov ds,ax
00000009 mov ss,ax
0000000B mov sp,0x0400
load_syetem:
0000000E mov dx,0x0000
00000011 mov cx,0x0002
00000014 mov ax,sysseg
00000017 mov es,ax
00000019 xor bx,bx
0000001B mov ax,0x200+syslen
0000001E int 0x13
00000020 jnc ok_load
die:
00000022 jmp die
ok_load:
00000024 cli
00000025 mov ax,sysseg
00000028 mov ds,ax
0000002A xor ax,ax
0000002C mov es,ax
0000002E mov cx,0x2000
00000031 sub si,si
00000033 sub di,di
00000035 rep movsb
00000037 mov ax,bootseg
0000003A mov ds,ax
0000003C lidt [idt_48]
00000041 lgdt [gdt_48]
00000046 mov ax,0x0001
00000049 lmsw ax
0000004C jmp 8:0
gdt:
00000051 dw 0,0,0,0
00000059 dw 0x07ff
0000005B dw 0x0000
0000005D dw 0x9a00
0000005F dw 0x00c0
00000061 dw 0x07ff
00000063 dw 0x0000
00000065 dw 0x9200
00000067 dw 0x00c0
idt_48:
00000069 dw 0,0,0
gdt_48:
0000006F dw 0x7ff
00000071 dw 0x7c00+gdt,0
00000075 times 510-($-$$) db 0
000001FE dw 0xaa55
作者:Wray Zheng
原文:http://www.codebelief.com/article/2017/12/operating-system-linux-0-00-assembly-syntax-explaination/