操作系统3---内嵌汇编 ( AT&T)

一,寄存器的使用发生了变化

引用寄存器要在寄存器号前加百分号%,如“movl %eax, %ebx”。
常用寄存器汇总:

8个32-bit寄存器 %eax,%ebx,%ecx,%edx,%edi,%esi,%ebp,%esp;

8个16-bit寄存器,它们事实上是上面8个32-bit寄存器的低16位:%ax,%bx,%cx,%dx,%di,%si,%bp,%sp;

8个8-bit寄存器:%ah,%al,%bh,%bl,%ch,%cl,%dh,%dl。它们事实上是寄存器%ax,%bx,%cx,%dx的高8位和低8位;

6个段寄存器:%cs(code),%ds(data),%ss(stack), %es,%fs,%gs;

3个控制寄存器:%cr0,%cr2,%cr3;

6个debug寄存器:%db0,%db1,%db2,%db3,%db6,%db7;

2个测试寄存器:%tr6,%tr7;

8个浮点寄存器栈:%st(0),%st(1),%st(2),%st(3),%st(4),%st(5),%st(6),%st(7)。

二,操作数使用顺序发生了变化

内嵌汇编的操作数 是左边一个而目的是右边一个

三,立即数从#变为$

movl $0x04, %ebx

将立即数0x04放入ebx中

四,符号常数发生变化

1,可以直接引用

value: .long 0x12a3f2de
movl value,%ebx

将立即数0x12a3f2de放入寄存器ebx中

2,引用符号地址在符号前加$,同立即数

movl $value, %ebx

将符号value的地址装入寄存器ebx

五,操作数的长度

操作数的长度用加在指令后的符号表示b(byte, 8-bit), w(word, 16-bits), l(long, 32-bits),如

movb %al, %bl
movw %ax, %bx
movl %eax, %ebx 

如果没有指定操作数长度的话,编译器将按照目标操作数的长度来设置。比如指令mov %ax, %bx
由于目标操作数bx的长度为word,那么编译器将把此指令等同于
movw %ax, %bx
同样道理,指令
mov $4, %ebx等同于指令movl $4, %ebxpush %al等同于pushb %al
对于没有指定操作数长度,但编译器又无法猜测的指令,编译器将会报错,比如指令push $4

六,调用和跳转

段内调用和跳转指令为"call",“ret"和"jmp”,
段间调用和跳转指令为"lcall",“lret"和"ljmp”。

段间调用和跳转指令的格式为lcall/ljmp $SECTION, $OFFSET
而段间返回指令则为lret $STACK-ADJUST0

七,内存引用

AT&T的内存寻址方式的形式为 offset(base, index, scale)
segment:[base+index*scale+offset]
原文中这样描述:

Memory references have the following syntax:segment:offset(base, index, scale).

segment:是x86架构的任意一个段地址寄存器,可选可缺省,如果指定了段寄存器必须和偏移量之间有一个冒号。如果segment缺省默认段寄存器ds就会被用于计算最终的地址。

offset:是指从段起始地址到目标地址之间的偏移量,同样可缺省

base和index:可以是任意的32位通用寄存器

scale:表示index寄存器内容需要乘的值,可以是1,2,4,8默认是1

例如movl base(%ebx, %esi, 4), %eax
表示 %eax = [ base + %ebx + %esi4 ]
将 base + %ebx + %esi
4 指向的内存位置的值赋值给eax寄存器。

例如 leal 32(, %edx, 8), %eax
表示%eax = 32 + ( %edx * 8 ) = 8 * (4 + %edx)
通常可以用lea指令表示一些乘法运算。

-4(%ebp):
base=%ebp,offset=-4,segment没有指定,由于base=%ebp,所以默认的segment=%ss,index,scale没有指定,则index为0。

foo(,%eax,4):
index=%eax,scale=4,offset=foo。其它域没有指定。这里默认的segment=%ds。

内联汇编

将一些C/C++语法无法表达的指令直接潜入C/C++代码中,另外也允许我们直接写 C/C++代码中使用汇编编写简洁高效的代码。

1.基本的内联汇编

格式:__asm__ __volatile__("Instruction List");
其中:

1 asm

是GCC关键字asm的宏定义:#define __asm__ asm
__asm__和asm可以用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以他开头的,必不可少。

2 Instruction List

Instruction List是汇编指令序列。
它可以是空的,比如:asm volatile(""); 或__asm__ ("");都是完全合法的内联汇编表达式,只不过这两条语句没有什么意义。

当在"Instruction List"中有多条指令的时候,你可以在一对引号中列出全部指令,也可以将一条或几条指令放在一对引号中,所有指令放在多对引号中。如果是前者,你可以将每一条指令放在一行,如果要将多条指令放在一行,则必须用分号(;)或换行符(/n,大多数情况下/n后还要跟一个/t,其中/n是为了换行,/t是为了空出一个tab宽度的空格)将它们分开。比如:
asm(“movl %eax, %ebx; sti popl %edi; subl %ecx, %ebx”);
asm(“movl %eax, %ebx; sti popl %edi; subl %ecx, %ebx”);
asm(“movl %eax, %ebx; sti/n/t popl %edi; subl %ecx, %ebx”);

任意两个指令间要么被分号(;)分开,要么被放在两行;
放在两行的方法既可以从通过/n的方法来实现,也可以真正的放在两行;
可以使用1对或多对引号,每1对引号里可以放任一多条指令,所有的指令都要被放到引号中。

asm(".align 2/n/t"
“movl %eax, %ebx/n/t”
“test %ebx, %ecx/n/t”
“jne error/n/t” “sti/n/t”
“error: popl %edi/n/t”
“subl %ecx, %ebx”);

3,volatile

#define __volatile__ volatile

volatile 或volatile是可选的,你可以用它也可以不用它。如果你用了它,则是向GCC声明“不要动我所写的Instruction List,我需要原封不动的保留每一条指令”,否则当你使用了优化选项(-O)进行编译时,GCC将会根据自己的判断决定是否将这个内联汇编表达式中的指令优化掉。

4,带有C/C++表达式的内联汇编

GCC允许你通过C/C++表达式指定内联汇编中"Instrcuction List"中指令的输入和输出,你甚至可以不关心到底使用哪个寄存器被使用,完全靠GCC来安排和指定。这一点可以让程序员避免去考虑有限的寄存器的使用,也可以提高目标代码的效率。

格式:__asm__ __volatile__("Instruction List" : Output : Input : Clobber/Modify);
asm volatile(“Instruction List” : Output : Input : Clobber/Modify);
这4个部分都不是必须的,任何一个部分都可以为空,其规则为:

如果Clobber/Modify为空,则其前面的冒号(:)必须省略。比如__asm__(“mov %%eax, %%ebx” : “=b”(foo) : “a”(inp) : )就是非法的写法;而__asm__(“mov %%eax, %%ebx” : “=b”(foo) : “a”(inp) )则是正确的。

如果Instruction List为空,则Input,Output,Clobber/Modify可以不为空,也可以为空。比如__asm__ ( " " : : : “memory” );和__asm__(" " : : );都是合法的写法。

如果Output,Input,Clobber/Modify都为空,Output,Input之前的冒号(:)既可以省略,也可以不省略。
如果都省略,则此汇编退化为一个基本内联汇编,否则,仍然是一个带有C/C++表达式的内联汇编,此时"Instruction List"中的寄存器写法要遵守相关规定,比如寄存器前必须使用两个百分号(%%),而不是像基本汇编格式一样在寄存器前只使用一个百分号(%)。
比如
asm( " mov %%eax, %%ebx" : : );
asm( " mov %%eax, %%ebx" : )
asm( " mov %eax, %ebx" )
都是正确的写法,

asm( " mov %eax, %ebx" : : );
asm( " mov %eax, %ebx" : )

asm( " mov %%eax, %%ebx" )
都是错误的写法。

如果Input,Clobber/Modify为空,但Output不为空,Input前的冒号(:)既可以省略,也可以不省略。
比如

 __asm__( " mov %%eax, %%ebx" : "=b"(foo) : )__asm__( " mov %%eax, %%ebx" : "=b"(foo) )

都是正确的。

如果后面的部分不为空,而前面的部分为空,则前面的冒号(:)都必须保留,否则无法说明不为空的部分究竟是第几部分。比如, Clobber/Modify,Output为空,而Input不为空,则Clobber/Modify前的冒号必须省略(前面的规则),而Output 前的冒号必须为保留。如果Clobber/Modify不为空,而Input和Output都为空,则Input和Output前的冒号都必须保留。
比如

 __asm__( " mov %%eax, %%ebx" : : "a"(foo) )
 
 __asm__( " mov %%eax, %%ebx" : : : "ebx" )

其中:
1. Output
示例:__asm__("movl %%cr0, %0": "=a" (cr0));

这个内联汇编语句的输出部分为"=r"(cr0),它是一个“操作表达式”,指定了一个输出操作。我们可以很清楚得看到这个输出操作由两部分组成:括号括住的部分(cr0)和引号引住的部分"=a"。这两部分都是每一个输出操作必不可少的。括号括住的部分是一个C/C++表达式,用来保存内联汇编的一个输出值,其操作就等于C/C++的相等赋值cr0 = output_value,因此,括号中的输出表达式只能是C/C++的左值表达式,也就是说它只能是一个可以合法的放在C/C++赋值操作中等号(=) 左边的表达式。那么右值output_value从何而来呢?

答案是引号中的内容,被称作“操作约束”(Operation Constraint),在这个例子中操作约束为"=a",它包含两个约束:等号(=)和字母a,其中等号(=)说明括号中左值表达式cr0是一个 Write-Only的,只能够被作为当前内联汇编的输入,而不能作为输入。而字母a是寄存器EAX / AX / AL的简写,说明cr0的值要从eax寄存器中获取,也就是说cr0 = eax,最终这一点被转化成汇编指令就是movl %eax, address_of_cr0。现在你应该清楚了吧,操作约束中会给出:到底从哪个寄存器传递值给cr0。

在Output域中可以有多个输出操作表达式,多个操作表达式中间必须用逗号(,)分开。例如:

__asm__( 
"movl %%eax, %0 /n/t" 
"pushl %%ebx /n/t" 
"popl %1 /n/t" 
"movl %1, %2" : "+a"(cr0), "=b"(cr1), "=c"(cr2));

2.Input
Input域的内容用来指定当前内联汇编语句的输入。
示例:__asm__("movl %0, %%db7" : : "a" (cpu->db7));

例中Input域的内容为一个表达式"a"[cpu->db7),被称作“输入表达式”,用来表示一个对当前内联汇编的输入。

像输出表达式一样,一个输入表达式也分为两部分:带括号的部分(cpu->db7)和带引号的部分"a"。这两部分对于一个内联汇编输入表达式来说也是必不可少的。

括号中的表达式cpu->db7是一个C/C++语言的表达式,它不必是一个左值表达式,也就是说它不仅可以是放在C/C++赋值操作左边的表达式,还可以是放在C/C++赋值操作右边的表达式。所以它可以是一个变量,一个数字,还可以是一个复杂的表达式(比如a+b/c*d)。比如上例可以改为:

__asm__("movl %0, %%db7" : : "a" (foo))__asm__("movl %0, %%db7" : : "a" (0x1000))
__asm__("movl %0, %%db7" : : "a" (va*vb/vc))

引号号中的部分是约束部分,和输出表达式约束不同的是,它不允许指定加号(+)约束和等号(=)约束,也就是说它只能是默认的Read-Only的。约束中必须指定一个寄存器约束,例中的字母a表示当前输入变量cpu->db7要通过寄存器eax输入到当前内联汇编中。

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值