Defining integers in GAS 在gas中定义整数:前一节汇编示例中的整数是以立即数的形式存在的,在汇编中还可以使用.int ,.short 和.long的伪操作符来定义不同尺寸的有符号整数或者整数数组,...

    本文由zengl.com站长对
    http://pan.baidu.com/share/link?shareid=3717576860&uk=940392313 汇编教程英文版相应章节进行翻译得来。
    另外再附加一个英特尔英文手册的共享链接地址:
    http://pan.baidu.com/share/link?shareid=2345340326&uk=940392313 (在某些例子中会用到)

    本篇翻译对应汇编教程英文原著的第203页到第213页,对应原著第7章 (注意这里的页数不是页脚的页数,而是pdf电子档顶部,分页输入框中的页数,也就是包含了目录,前言部分的总页数,pdf电子档总页数是577,当前已翻译完的页数为213 / 577)。

Defining integers in GAS 在gas中定义整数:

    前一节汇编示例中的整数是以立即数的形式存在的,在汇编中还可以使用.int ,.short 和.long的伪操作符来定义不同尺寸的有符号整数或者整数数组,下面的例子quadtest.s中就演示了这种用法:

# quadtest.s - An example of quad integers
.section .data
data1:
    .int 1, -1, 463345, -333252322, 0
data2:
    .quad 1, -1, 463345, -333252322, 0
.section .text
.globl _start
_start:
    nop
    movl $1, %eax
    movl $0, %ebx
    int $0x80

    上面的代码中在data1标签处使用.int定义了5个32位大小的有符号整数,另外在data2标签处使用.quad定义了5个64位大小的有符号整数,下面我们通过调试器来看下这两组整数在内存中的存储情况:

$ as -gstabs -o quadtest.o quadtest.s
$ ld -o quadtest quadtest.o
$ gdb -q quadtest

Reading symbols from /home/zengl/Downloads/asm_example/quadtest...done.
(gdb) break *_start
Breakpoint 1 at 0x8048074: file quadtest.s, line 10.
(gdb) r
Starting program: /home/zengl/Downloads/asm_example/quadtest

Breakpoint 1, _start () at quadtest.s:10
10        nop

(gdb) x/5d &data1
0x8049084 <data1>:	1	-1	463345	-333252322
0x8049094 <data1+16>:	0
(gdb) x/5d &data2
0x8049098 <data2>:	1	0	-1	-1
0x80490a8 <data2+16>:	463345

    上面在调试器中使用x/5d &data1命令可以看到显示的数据和原文件中.int定义的是一致的,但是x/5d &data2显示的数据就和原文件中.quad定义的不太一样,这是因为x/5d默认按照32位的大小来显示数据,data2中的数据都是64位大小的,所以每个数在x/5d中都被分为两部分,一个低32位部分,一个高32位部分,例如data2的第一个64位大小的整数1,它的低32位就是1,而高32位就是0,所以就有了上面的输出情况。

    下面我们再看下data1和data2定义的整数在内存中的字节存储情况:

(gdb) x/20bx &data1
0x8049084 <data1>:	0x01  0x00  0x00  0x00  0xff  0xff  0xff  0xff
0x804908c <data1+8>:	0xf1  0x11  0x07  0x00  0x1e  0xf9  0x22  0xec
0x8049094 <data1+16>:	0x00  0x00  0x00  0x00
(gdb) x/40bx &data2
0x8049098 <data2>:	0x01  0x00  0x00  0x00  0x00  0x00  0x00  0x00
0x80490a0 <data2+8>:	0xff  0xff  0xff  0xff  0xff  0xff  0xff  0xff
0x80490a8 <data2+16>:	0xf1  0x11  0x07  0x00  0x00  0x00  0x00  0x00
0x80490b0 <data2+24>:	0x1e  0xf9  0x22  0xec  0xff  0xff  0xff  0xff
0x80490b8 <data2+32>:	0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00

    data1中5个32位整数一共占20个字节,所以使用x/20bx命令来显示,20bx中b表示按字节大小来显示,x表示以十六进制来显示,可以看到data1处.int定义的5个整数,每个都占用了4个字节,例如整数1对应 0x01    0x00    0x00    0x00 ,-1对应 0xff    0xff    0xff    0xff

    data2中5个64位整数一共占40个字节,所以使用x/40bx来显示,从输出结果可以看到data2处.quad定义的5个整数,每个都占用了8个字节,例如整数1对应 0x01    0x00    0x00    0x00    0x00    0x00    0x00    0x00

    如果想在调试器中查看到完整的64位整数值,可以使用x命令中的gd选项:

(gdb) x/5gd &data2
0x8049098 <data2>:	1	-1
0x80490a8 <data2+16>:	463345	-333252322
0x80490b8 <data2+32>:	0
(gdb) help x

Examine memory: x/FMT ADDRESS.
ADDRESS is an expression for the memory address to examine.
FMT is a repeat count followed by a format letter and a size letter.
Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),
  t(binary), f(float), a(address), i(instruction), c(char) and s(string).
Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes).
The specified number of objects of the specified size are printed
according to the format.

    上面使用x/5gd命令将data2处的64位整数值给完整的显示出来,对于x命令的用法,可以使用help帮助命令,如上面的help x 就会输出x命令的详细使用方法和可用的各种选项。

SIMD Integers (SIMD整数):

    SIMD(Intel Single Instruction Multiple Data 单指令多数据模式技术) 引入了一种新的整数类型即packed integer(压缩整数)类型,不过译者觉得packed翻译为填充更合适,因为这种整数类型并不会执行什么压缩操作,只是多个相同尺寸的整数可以同时填充到一个大容量的寄存器中,处理器就可以对寄存器中的多个整数同时执行加减运算等,不过由于网上普遍将packed integer翻译为压缩整数,所以译者也使用这种叫法,下面我们就介绍下奔腾处理器中各种不同的压缩整数类型。

MMX integers (MMX整数):

    MMX(Multimedia Extension 多媒体扩展技术)是从奔腾MMX处理器和奔腾2处理器中引入的,该技术提供了三种新的整数类型:
  • 64-bit packed byte integers
    64位压缩字节整数
  • 64-bit packed word integers
    64位压缩字整数
  • 64-bit packed doubleword integers
    64位压缩双字整数
    上面每个数据类型都是在一个64位的MMX寄存器中包含了(或填充了)多个相同尺寸的整数,如下图所示:


图1

    可以看到64位的MMX寄存器中可以同时填充8个8位的字节整数,或者4个16位的字整数,或者2个32位的双字整数。

    小提示:由于MMX寄存器会被映射到FPU寄存器中,所以在使用任何MMX寄存器指令之前,要记得将FPU里的数据备份到内存中,这个在后面的浮点数介绍部分会提到。

    另外,MMX技术还提供了同时处理MMX寄存器里多个整数的指令,下面看下MMX赋值指令。

Moving MMX integers (MMX整数移动赋值指令):

    可以使用MOVQ指令将数据赋值到MMX寄存器中,下面是MOVQ指令的格式:

movq source, destination

    source源操作数和destination目标操作数可以是一个MMX寄存器,也可以是一个SSE寄存器,还可以是一个64位的内存位置,当然两个操作数不可以同时都是内存位置。

    下面的mmxtest.s例子演示了MOVQ指令的用法:

# mmxtest.s - An example of using the MMX data types
.section .data
values1:
    .int 1, -1
values2:
    .byte 0x10, 0x05, 0xff, 0x32, 0x47, 0xe4, 0x00, 0x01
.section .text
.globl _start
_start:
    nop
    movq values1, %mm0
    movq values2, %mm1
    movl $1, %eax
    movl $0, %ebx
    int $0x80

    对上面的代码进行汇编,调试:

$ as -gstabs -o mmxtest.o mmxtest.s
$ ld -o mmxtest mmxtest.o
$ gdb -q mmxtest

Reading symbols from /home/zengl/Downloads/asm_example/mmxtest/mmxtest...done.
(gdb) break *_start+1
Breakpoint 1 at 0x8048075: file mmxtest.s, line 11.
(gdb) r
Starting program: /home/zengl/Downloads/asm_example/mmxtest/mmxtest

Breakpoint 1, _start () at mmxtest.s:11
11        movq values1, %mm0

(gdb) s
12        movq values2, %mm1
(gdb) s
13        movl $1, %eax
(gdb) print $mm0
$1 = {uint64 = -4294967295, v2_int32 = {1, -1}, v4_int16 = {1, 0, -1, -1},
  v8_int8 = {1, 0, 0, 0, -1, -1, -1, -1}}

(gdb) print $mm1
$2 = {uint64 = 72308588487312656, v2_int32 = {855573776, 16835655},
  v4_int16 = {1296, 13055, -7097, 256}, v8_int8 = {16, 5, -1, 50, 71, -28, 0,1}}

(gdb) print/x $mm1
$3 = {uint64 = 0x100e44732ff0510, v2_int32 = {0x32ff0510, 0x100e447},
  v4_int16 = {0x510, 0x32ff, 0xe447, 0x100}, v8_int8 = {0x10, 0x5, 0xff, 0x32,
    0x47, 0xe4, 0x0, 0x1}}

(gdb) print $st0
$4 = -nan(0xffffffff00000001)
(gdb) print $st1
$5 = -nan(0x100e44732ff0510)

    由print $mm0命令输出的v2_int32 = {1, -1}可以看到,MMX第一个寄存器mm0中存放的{1 , -1}值就是代码中value1处定义的两个整数,说明movq values1, %mm0指令的作用就是将value1处的8个字节数据也就是2个32位整数给存储到mm0寄存器中,这样mm0中就存放了两个32位的双字整数。同理,由print/x $mm1命令输出的v8_int8 = {0x10, 0x5, 0xff, 0x32, 0x47, 0xe4, 0x0, 0x1}}可以看到,mm1寄存器中存放了value2标签处定义的8个字节数据。

    我们说过mmx寄存器会被映射到FPU浮点寄存器中,mm0对应st0浮点寄存器,mm1对应st1浮点寄存器,从print $st0的输出结果可以看到,st0里存放的值和mm0中存放的值是一样的,st1里的值也和mm1里的值一样,这就验证了这种MMX到FPU的映射关系。

    提示:在旧版本的调试器中,是没办法像上面那样直接显示出MMX寄存器里的值的,只有通过st0 ,st1之类的浮点寄存器来查看对应的MMX寄存器里的值。

SSE integers (SSE整数):

    SSE(Streaming SIMD Extensions SIMD流指令扩展技术)提供了8个128位的XMM寄存器(被命名为XMM0 , XMM1 .... XMM7) ,SSE2技术(由奔腾4处理器引入)提供了4个额外的有符号压缩整数类型:
  • 128-bit packed byte integers
    128位压缩字节整数
  • 128-bit packed word integers
    128位压缩字整数
  • 128-bit packed doubleword integers
    128位压缩双字整数
  • 128-bit packed quadword integers
    128位压缩四字整数
    这些整数类型都被填充到一个128位的XMM寄存器中,如下图所示:


图2

    上图显示了一个128位的XMM寄存器(也可以叫做SSE寄存器)里可以同时容纳16个8位的字节整数,或者8个16位的字整数,或者4个32位的双字整数,再或者2个64位的四字整数,同样的,SSE技术也提供了同时处理这些整数的指令,这些大容量的寄存器和对应的多数据操作指令让处理器可以用相同的时钟周期来处理更多的数据。

Moving SSE integers (SSE整数移动赋值指令):

   可以使用MOVDQA和MOVDQU指令来将128位的数据赋值到XMM寄存器中,或者在XMM寄存器间进行传值,这里的后缀A和U分别代表对齐和非对齐,当数据存放在16字节对齐的内存地址上时,就可以使用后缀A的指令,否则就要用后缀U的指令。

    MOVDQA和MOVDQU指令的通用格式如下:

movdqa source, destination

    source源操作数和destination目标操作数可以是128位的SSE寄存器,也可以是128位的内存位置,但两个操作数不可以同时为内存位置。MOVDQA操作对齐数据比MOVDQU操作非对齐数据要快,但是如果MOVDQA操作的数据是非对齐的话,就会产生硬件异常。

    下面的ssetest.s例子演示了这种指令的用法:

# ssetest.s - An example of using 128-bit SSE registers
.section .data
values1:
    .int 1, -1, 0, 135246
values2:
    .quad 1, -1
.section .text
.globl _start
_start:
    nop
    movdqu values1, %xmm0
    movdqu values2, %xmm1
    movl $1, %eax
    movl $0, %ebx
    int $0x80

    对代码进行汇编,调试:

$ as -gstabs -o ssetest.o ssetest.s
$ ld -o ssetest ssetest.o
$ gdb -q ssetest

Reading symbols from /home/zengl/Downloads/asm_example/ssetest/ssetest...done.
(gdb) break *_start+1
Breakpoint 1 at 0x8048075: file ssetest.s, line 11.

(gdb) r
Starting program: /home/zengl/Downloads/asm_example/ssetest/ssetest

Breakpoint 1, _start () at ssetest.s:11
11        movdqu values1, %xmm0

(gdb) s
12        movdqu values2, %xmm1
(gdb) s
13        movl $1, %eax
(gdb) print $xmm0

$1 = {v4_float = {1.40129846e-45, -nan(0x7fffff), 0, 1.89520012e-40},
  v2_double = {-nan(0xfffff00000001), 2.8699144274488922e-309},
  v16_int8 = {1, 0, 0, 0, -1, -1, -1, -1, 0, 0, 0, 0, 78, 16, 2, 0},
  v8_int16 = {1, 0, -1, -1, 0, 0, 4174, 2},
 
v4_int32 = {1, -1, 0, 135246},
  v2_int64 = {-4294967295, 580877146914816},
  uint128 = 0x0002104e00000000ffffffff00000001}


(gdb) print $xmm1

$2 = {v4_float = {1.40129846e-45, 0, -nan(0x7fffff), -nan(0x7fffff)},
  v2_double = {4.9406564584124654e-324, -nan(0xfffffffffffff)},
  v16_int8 = {1,0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1},
  v8_int16 = {1, 0, 0,0, -1, -1, -1, -1},
  v4_int32 = {1, 0, -1, -1},
 
v2_int64 = {1, -1},
  uint128 = 0xffffffffffffffff0000000000000001}


(gdb)

    在单步执行完两个movdqu指令后,从print $xmm0显示输出的结果v4_int32 = {1, -1, 0, 135246}可以看出,value1标签处定义的4个32位整数值被成功赋值到XMM0寄存器中,从print $xmm1输出结果v2_int64 = {1, -1}可以看出,value2标签处定义的2个64位整数也被成功的赋值到XMM1寄存器中。

    程序之所以使用movdqu指令是因为代码中并没有使用.align之类的对齐指令对数据进行对齐操作,所以value1和value2标签对应的内存地址不一定是16的整数倍(128位数据就是16个字节,所以MOVDQA操作的数据所在的内存地址必须是16的整数倍),如果不是16的整数倍,则数据就不是128位对齐的,使用MOVDQA就可能会产生硬件异常,比如我将上面的代码中movdqu改为movdqa,汇编调试就会产生如下结果:

11        movdqa values1, %xmm0
(gdb) s

Program received signal SIGSEGV, Segmentation fault.
_start () at ssetest.s:11
11        movdqa values1, %xmm0
(gdb) s

Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
(gdb)

    使用movdqa指令后,调试器抛出了Segmentation fault.段错误并终止了程序的执行。

    小提示:需要记住的是,SSE是从奔腾3处理器开始引入的技术,所以上面的ssetest.s例子只能运行在奔腾3及以后的处理器上,本章只介绍了传值指令,其他的MMX和SSE的指令集将在以后的篇章中专门进行介绍。

Binary Coded Decimal 二进码十进数:

    BCD码很早就存在于计算机系统中了,在时钟,定时器等很多数字显示相关的设备中都用到了BCD码,使用BCD码可以省去二进制和十进制之间的转换工作,下面就具体的介绍下BCD码。

What is BCD? 什么是BCD码:

    普通的BCD码中,每个组成部分都是一个8位的字节整数,它的范围不再是0到255,而是0到9的范围(字节中大于9的部分是无效的),以牺牲存储空间为代价,将要表示的十进制数的个,十,百,千等各位依次存放到从低到高的字节中,如下图所示:


图3

    上图中是按大字节序存放的数据,最左侧即最高字节Byte1里的值为2,那么对应的十进制数的百位就是2,Byte2为1,那么对应十进制数的十位就是1,最低字节Byte3为4,对应十进制数的个位就是4 。

    普通的BCD码比较浪费空间,可以使用Packed BCD(压缩的BCD码)来减少空间的浪费,压缩的BCD码使用每4位来对应一个十进制位,如下图所示:
 

图4

    上图也是大字节序,最高字节Byte1里的高4位的值1对应十进制数的千位1,低4位的值4对应十进制的百位4,以此类推。

    前面的例子中,BCD码都只能表示无符号整数值,下面就介绍如何通过IA-32平台的FPU浮点单元来表示有符号的BCD整数值。

FPU BCD values (FPU中使用BCD码):

   FPU寄存器可以用于BCD码的数学运算,FPU浮点单元包含8个80位的寄存器(ST0到ST7),这些寄存器也可以用于存放BCD码,当用于BCD码时,BCD的值存储在低9字节中,并且是采用压缩的BCD码格式,最高字节的最高位用于指示BCD码的正负号,1表示负的BCD码,0表示正的BCD码,如下图所示:
 

图5

    如果要将BCD码存储到FPU中,就需要先按照上图所示,在内存中低9字节存储BCD的B1到B18位,最高字节的最高位存储BCD的符号位,然后使用下面要提到的FPU指令将这10个字节加载到FPU寄存器中,在加载到FPU寄存器中后,系统会在内部自动将这些BCD码转为双精度扩展浮点格式(该格式将在以后的章节中进行介绍),然后FPU内部就都是以浮点格式进行运算,当程序员使用FPU指令将FPU里的值获取到内存中时,系统又会自动将FPU里的浮点格式转为上述10字节的压缩BCD码格式存储到目标内存中。

Moving BCD values (BCD码的传值指令):

    IA-32平台中可以使用FBLD指令来将10字节的压缩BCD码加载到FPU寄存器中,还可以使用FBSTP指令将FPU里的浮点数以压缩BCD码格式存储到内存中。

    FPU浮点寄存器和普通的通用寄存器的工作原理不同,8个FPU浮点寄存器的工作方式很像内存中的stack(栈),这8个寄存器构成了一个FPU寄存器栈,数据可以压入这个寄存器栈,也可以从寄存器栈中弹出数据,ST0就是指向栈顶的寄存器,当第一次将数据压入FPU寄存器栈时,该数据会存储在ST0中,当再次向FPU压入数据时,之前压入的数据会自动加载到ST1中,而最新压入的数据则存储在ST0中,这样ST0始终指向最新压入的数据,ST1指向之前压入的数据,ST2指向之前的之前压入的数据,以此类推,浮点寄存器的具体工作方式会在后面的"高级数学运算"章节中再进行详细的讲解。

    FBLD指令的格式如下:

fbld source

    source源操作数是一个指向包含10字节即80位压缩BCD码的内存位置。

    下面的bcdtest.s例子演示了使用FPU进行BCD码运算的方法:

# bcdtest.s - An example of using BCD integer values
.section .data
data1:
    .byte 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
data2:
    .int 2
.section .text
.globl _start
_start:
    nop
    fbld data1
    fimul data2
    fbstp data1
    movl $1, %eax
    movl $0, %ebx
    int $0x80

    进行汇编,调试:

$ as -gstabs -o bcdtest.o bcdtest.s
$ ld -o bcdtest bcdtest.o
$ gdb -q bcdtest

Reading symbols from /home/zengl/Downloads/asm_example/bcdtest/bcdtest...done.
(gdb) break *_start+1
Breakpoint 1 at 0x8048075: file bcdtest.s, line 11.
(gdb) r
Starting program: /home/zengl/Downloads/asm_example/bcdtest/bcdtest

Breakpoint 1, _start () at bcdtest.s:11
11        fbld data1

(gdb) x/10bx &data1
0x8049094 <data1>:	0x34  0x12  0x00  0x00  0x00  0x00  0x00  0x00
0x804909c <data1+8>:	0x00  0x00

    在gdb执行具体的代码之前,上面通过x/10bx &data1显示出data1中存放的确实是10字节的压缩BCD码,该BCD码对应的十进制值就是1234(每4个二进制位对应一个十进制位),接着我们单步执行fbld data1指令:

11        fbld data1
(gdb) s
12        fimul data2
(gdb) info all
...................  //省略N行输出
st0            1234    (raw 0x40099a40000000000000)

    通过s单步执行命令执行完fbld data1后,通过info all命令输出的寄存器信息中可以看到,st0浮点寄存器的值为十进制值1234,说明fbld指令确实将data1里的BCD码加载到浮点寄存器中了,同时从st0的(raw 0x40099a40000000000000)原始二进制显示中可以看到,浮点寄存器内部是以浮点格式来存放BCD码的。

    继续单步执行:

12        fimul data2
(gdb) s
13        fbstp data1
(gdb) info all
...................  //省略N行输出
st0            2468    (raw 0x400a9a40000000000000)

    通过s命令单步执行fimul指令,该指令会将st0里的值和data2里的值2进行乘法运算,info all命令显示st0变为2468也就是原来的1234的两倍。

    再继续单步执行:

13        fbstp data1
(gdb) s

14        movl $1, %eax
(gdb) x/10bx &data1
0x8049094 <data1>:	0x68  0x24  0x00  0x00  0x00  0x00  0x00  0x00
0x804909c <data1+8>:	0x00  0x00

    上面的输出和预期一样,fbstp data1指令将st0浮点寄存器里的计算结果2468又以10字节压缩BCD码形式存储回data1指向的内存中。

    提示:后面的"高级数学运算"章节还会对如何在数学运算中使用BCD码进行更详细的阐述。

    限于篇幅,本篇就到这里,下一篇介绍浮点数等内容,转载请注明来源:www.zengl.com

    OK,休息,休息一下 o(∩_∩)o~~
上下篇

下一篇: 汇编数据处理 (三) 浮点数

上一篇: 汇编数据处理 (一)

相关文章

调用汇编模块里的函数 (二)

IA-32平台(二)

高级数学运算 (四) 高级运算结束篇

基本数学运算 (三) 除法和移位指令

高级数学运算 (三) 高级浮点运算指令

Moving Data 汇编数据移动 (二)