嵌入汇编与CPUID指令

来源:互联网 发布:法国电影预先知剧情 编辑:程序博客网 时间:2024/06/05 16:49

一.嵌入汇编基础

嵌入式汇编语言存在怎样分配和使用寄存器,以及把C代码中的变量应该存放在哪个寄存器中。

嵌入式汇编的一般形式:

__asm__ __volatile__ ("<asm routine>" : output : input : modify);

__asm__表示汇编代码的开始,__volatile__(这是可选项)含义是避免“asm”指令被删除、移动或组合;

"<asm routine>"为汇编指令部分,"movl %%cr0,%0\n\t"。数字前加前缀“%“,如%1,%2等表示使用寄存器的样板操作数(在GCC内联汇编语句的指令部中,加上前缀'%'的数字 (如%0,%1) 表示的就是需要使用寄存器的"样板"操作数.指令部中使用了几个样板操作数,就表明有几个变量需要与寄存器相结合 )。可以使用的操作数总数取决于具体CPU中通用寄存器的数量,如Intel可以有8个。由于样板操作数的前缀使用了”%“,因此,在用到具体的寄存器时就在前面加两个“%”,如%%cr0。

输出部分

:"=r" (__dummy)

“=r”表示相应的目标操作数(指令部分的%0)可以使用任何一个通用寄存器,并且变量__dummy 存放在这个寄存器中,但如果是:

:“=m”(__dummy)

“=m”就表示相应的目标操作数是存放在内存单元__dummy中。

主要的约束字母及其含义

字母

含义

m, v,o

表示内存单元

R

表示任何通用寄存器

Q

表示寄存器eax, ebx, ecx,edx之一

I, h

表示直接操作数

E, F

表示浮点数

G

表示“任意”

a, b.c d

表示要求使用寄存器eax/ax/al, ebx/bx/bl, ecx/cx/cl或edx/dx/dl

S, D

表示要求使用寄存器esi或edi

I

表示常数(0~31)

输入部分(Input):输入部分与输出部分相似,但没有“=”。如果输入部分一个操作数所要求使用的寄存器,与前面输出部分某个约束所要求的是同一个寄存器,那就把对应操作数的编号(如“1”,“2”等)放在约束条件中.

修改部分(modify):这部分常常以“memory”为约束条件,以表示操作完成后内存中的内容已有改变,如果原来某个寄存器的内容来自内存,那么现在内存中这个单元的内容已经改变。

注意,指令部分为必选项,而输入部分、输出部分及修改部分为可选项,当输入部分存在,而输出部分不存在时,分号“:“要保留,当“memory”存在时,三个分号都要保留。

二.输入,输出和约束

1. Output用来指定当前内联汇编语句的输出

__asm__("movl %%cr0, %0": "=a" (cr0));

这个内联汇编语句的输出部分为"=a"(cr0),指定了一个输出操作。括号括住的部分用来保存内联汇编的一个输出值,其操作就等于cr0 = output_value,因此,括号中的输出表达式只能是C/C++的左值表达式。

引号中的内容,被称作“操作约束”,在这个例子中操作约束为"=a",它包含两个约束:等号(=)和字母a,其中等号(=)说明括号中左值表达式cr0是一个Write-Only的,只能够被作为当前内联汇编的输入,而不能作为输入。而字母a是寄存器EAX / AX / AL的简写,说明cr0的值要从eax寄存器中获取,也就是说cr0 = eax。

等号(=)约束说明当前的表达式是一个Write-Only的,但另外还有一个符号——加号(+)用来说明当前表达式是一个Read-Write的,如果一个操作约束中没有给出这两个符号中的任何一个,则说明当前表达式是Read-Only的。因为对于输出操作来说,肯定是必须是可写的,而等号(=)和加号(+)都表示可写,只不过加号(+)同时也表示是可读的。所以对于一个输出操作来说,其操作约束只需要有等号(=)或加号(+)中的任意一个就可以了。

无论是等号(=)约束还是加号(+)约束所约束的操作表达式都只能放在Output域中,而不能被用在Input域中。
从编译的结果可以看出,当使用加号(+)约束的时候,cr0不仅作为输出,还作为输入,所使用寄存器都是寄存器约束(字母a,表示使用eax寄存器)指定的。

在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域的内容用来指定当前内联汇编语句的输入

__asm__("movl %0, %%db7" : : "a" (cpu->db7));

例中Input域的内容为一个表达式"a"[cpu->db7),被称作“输入表达式”。

cpu->db7不必是一个左值表达式,它不仅可以放在赋值操作左边,还可以放在赋值操作右边。所以它可以是一个变量,一个数字,还可以是一个复杂的表达式(比如a+b/c*d)。

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

3. Operation Constraint(操作约束)

每一个Input和Output表达式都必须指定自己的操作约束Operation Constraint,我们这里来讨论在80386平台上所可能使用的操作约束。

1、寄存器约束

当你当前的输入或输入需要借助一个寄存器时,你需要为其指定一个寄存器约束。你可以直接指定一个寄存器的名字,比如:

__asm__ __volatile__("movl %0, %%cr0"::"eax" (cr0));

也可以指定一个缩写,比如:

__asm__ __volatile__("movl %0, %%cr0"::"a" (cr0));

如果你指定一个缩写,比如字母a,则GCC将会根据当前操作表达式中C/C++表达式的宽度决定使用%eax,还是%ax或%al。比如:

unsigned short __shrt;

__asm__ ("mov %0,%%bx" : : "a"(__shrt));

由于变量__shrt是16-bit short类型,则编译出来的汇编代码中,则会让此变量使用%ex寄存器。编译结果为:

movw -2(%ebp), %ax # %ax = __shrt
#APP
movl %ax, %bx
#NO_APP

无论是Input,还是Output操作表达式约束,都可以使用寄存器约束。

下表中列出了常用的寄存器约束的缩写。

约束Input/Output意义r表示使用一个通用寄存器,由GCC在%eax/%ax/%al, %ebx/%bx/%bl, %ecx/%cx/%cl, %edx/%dx/%dl中选取一个GCC认为合适的。q表示使用一个通用寄存器,和r的意义相同。a表示使用%eax / %ax / %al。b表示使用%ebx / %bx / %bl。c表示使用%ecx / %cx / %cl。d表示使用%edx / %dx / %dl。D表示使用%edi / %di。S表示使用%esi / %si。f表示使用浮点寄存器。t表示使用第一个浮点寄存器。u表示使用第二个浮点寄存器

2、内存约束

如果一个Input/Output操作表达式的C/C++表达式表现为一个内存地址,可以使用内存约束。比如:

__asm__ ("lidt %0" : "=m"(__idt_addr)); 或 __asm__ ("lidt %0" : :"m"(__idt_addr));

我们看一下它们分别被放在一个C源文件中,然后被GCC编译后的结果:

使用内存方式进行输入输出时,由于不借助寄存器,所以GCC不会按照你的声明对其作任何的输入输出处理。GCC只会直接拿来用,究竟对这个C/C++表达式而言是输入还是输出,完全依赖与你写在"Instruction List"中的指令对其操作的指令。

约束Input/Output意义m表示使用系统所支持的任何一种内存方式,不需要借助寄存器

3、立即数约束

如果一个Input/Output操作表达式的C/C++表达式是一个数字常数,不想借助于任何寄存器,则可以使用立即数约束。

由于立即数在C/C++中只能作为右值,所以对于使用立即数约束的表达式而言,只能放在Input域。

比如:__asm__ __volatile__("movl %0, %%eax" : : "i" (100) );

约束Input/Output意义i表示输入表达式是一个立即数(整数),不需要借助任何寄存器表示输入表达式是一个立即数(浮点数),不需要借助任何寄存器

4、通用约束

约束Input/Output意义g表示可以使用通用寄存器,内存,立即数等任何一种处理方式。0,1,2,3,4,5,6,7,8,9表示和第n个操作表达式使用相同的寄存器/内存。


三.CPUID指令

1. 检测处理器是否支持 cpuid 指令

在 eflags.ID 标志位是 Processor Feature Identification 位,通过修改这个标志位的值,以此来检测是否支持 cpuid 指令。若能成功地修改 eflags.ID 标志位,说明 cpu 是支持 cpuid 指令的。

CPUID这条指令,除了用于识别CPU(CPU的型号、家族、类型等),还可以读出CPU支持的功能(比如是否支持MMX,是否支持4MB的页等等)。CPUID指令有两组功能,一组返回的是基本信息,另一组返回的是扩展信息。

2、CPUID指令的执行方法

把功能代码放在EAX寄存器中,执行CPUID指令即可。例如:

mov eax, 1
cpuid
当执行返回基本信息的CPUID指令时,EAX中功能代码的bit 31为0,当执行返回扩展信息的CPUID指令时,EAX中的功能代码的bit 31为1。那么不管是那组功能,如何知道EAX中的功能代码最大可以是多少呢?根据Intel的说明,可以用如下方法:

mov eax, 0
cpuid

执行完CPUID指令后,EAX中返回的值就是返回基本信息时,功能代码的最大值,在执行CPUID指令要求返回基本信息时,EAX中的值必须小于或等于该值。

mov eax, 80000000h
cpuid

执行完CPUID指令后,EAX中返回的值就是返回扩展信息时,功能代码的最大值,在执行CPUID指令要求返回扩展信息时,EAX中的值必须小于或等于该值。

3、返回基本信息的功能全貌

在实际介绍每一个功能之前,我们先通过一张图了解一下返回基本信息的功能全貌。

4、EAX=0:获取CPU的Vendor ID

mov eax, 0
cpuid

执行CPUID指令后,AX中返回的内容前面已经说过了,返回的Vendor ID固定为12个ASCII字符依次存放在EBX、EDX、ECX中,对于Intel的CPU,返回的字符串永远是:GenuineIntel。

尽管本文是介绍Intel的CPUID指令。

5、EAX=1:处理器签名(Processor Signiture)和功能(Feature)位

mov eax, 1
cpuid
执行完成后,处理器签名放在EAX中,功能位及其它内容分别放在EBX、ECX和EDX中。

处理器签名(Processor Signiture):返回在EAX中,定义如下:

图中的灰色区域表示没有定义。通过处理器签名,可以确定CPU的具体型号,以下是部分Intel CPU的处理器签名数据(资料来自Intel):

当处理器签名一样时的处理

有时候,从处理器签名上仍然不能识别CPU,要区别他们只能通过检查他们的高速缓存(Cache)的大小。

有些情况下,从处理器签名上不能区分CPU,也可以使用Brand ID(在EBX的bit7:0返回)来区分CPU,比如Pentium III, Model 8、Pentium III Xeon, Model 8和Celeron®, Model 8三种处理器的处理器签名也是一样的,但它们的Brand ID是不同的。

6、EAX=2:高速缓存描述符(Cache Descriptor)

mov eax, 2
cpuid

执行完CPUID指令后,高速缓存描述符和TLB(Translation Lookable Buffer)特性将在EAX、EBX、ECX和EDX中返回,每个寄存器中的4个字节分别表示4个描述符,描述符中不同的值表示不同的含义(后面有定义),其中EAX中的最低8位(AL)的值表示要得到完整的。

高速缓存的信息,需要执行EAX=2的CPUID指令的次数,同时,寄存器的最高位(bit 31)为0,表示该寄存器中的描述符是有效的。

8、EAX=80000001h:最大扩展功能号

mov eax, 80000001h
cpuid

该功能除返回CPU支持的最大扩展功能号外,并没有其它作用,EBX、ECX、EDX都不返回有意义的信息。

9、EAX=80000002h:返回CPU支持的扩展功能

mov eax, 80000002h
cpuid
0 LAHF LAHF / SAHF
31:01 Reserved

10、EAX=80000002h、80000003h、80000004h:返回处理器名称/商标字符串

mov eax, 80000002h
cpuid
......
mov eax, 80000003h
cpuid
......
mov eax, 80000004h
cpuid

每次调用CPUID分别在EAX、EBX、ECX、EDX中返回16个ASCII字符,处理器名称/商标字串最多48个字符,前导字符为空格,结束字符为NULL,在寄存器中的排列顺序为little-endian(即低字符在前)

12、EAX=80000006h:扩展L2高速缓存功能

mov eax, 80000006h
cpuid

执行完CPUID指令后,相应信息在ECX中返回。

13、EAX=80000007h:电源管理

mov eax, 80000007h
cpuid

执行CPUID指令后,是否支持电源管理功能在EDX的bit8中返回,其余位无意义。

14、EAX=80000008h:虚拟地址和物理地址大小

mov eax, 80000008h
cpuid

执行CPUID指令后,物理地址的大小在EAX的bit[7:0]返回,虚拟地址的大小在EAX的bit[15:8]返回,返回的内容为虚拟(物理)地址的位数。