X86处理器汇编技术系列5

第35部分-Linux x86 64位汇编 系统调用

如果对Linux内核有一定的了解,那么理解起来会非常简单。系统调用是用户态程序陷入到内核态的API接口。

         每个系统调用都有一个名称和调用号,调用号一旦确认就基本不会改变。现在Linux系统调用有三百多个。

     64位系统中可以在文件中找到:

arch/x86/entry/syscalls/syscall_64.tbl

文件定义的是

在Linux环境中可以通过man命令来获取。例如

man 2 exit

其中2表示man页的第二部分。

系统调用格式

我们已经知道EAX寄存器用于保存系统调用值。定义使用内核支持的系统调用中的哪个系统调用。

系统调用示例

 
  1. .section .data
  2. output:
  3. .ascii "This is a test message.\n"
  4. len:
  5. .int 24
  6. .section .text
  7. .globl _start
  8. _start:
  9. movq $1, %rax
  10. movq $1, %rdi
  11. movq $output, %rsi
  12. movq len, %rdx
  13. syscall
  14.  
  15. movq $60, %rax
  16. movq $0, %rdi
  17. syscall
 

as -g -o syscalltest.o syscalltest.s

ld -o syscalltest syscalltest.o

 

int 80h 和syscall

SYSCALL指令是INT 0X80的64位版本,syscall是在x86-64上进入内核模式的默认方式。

但是仍然可以在64位代码中使用INT 0X80,但是强烈不建议使用了。

int 0x80是调用系统调用的传统方法,应避免使用。

而且syscall具有比int 0x80更少的开销。

int 0x80是选择eax作为系统调用编号,ebx,ecx,edx,esi,edi和ebp传递参数。

Syscall是使用rdi,rsi,rdx,rcx,r8,r9传递传输。

第36部分-Linux x86 64位汇编 sysinfo系统调用

Sysinfo可以返回关于系统配置的资源信息。

通过man 查看如下:

NAME

       sysinfo - return system information

 

SYNOPSIS

       #include <sys/sysinfo.h>

       int sysinfo(struct sysinfo *info);

结构体如下:

struct sysinfo {

         long uptime;             /* Seconds since boot */

         unsigned long loads[3];/* 1, 5, and 15 minute load averages*/

         unsigned long totalram;  /* Total usable main memory size */

         unsigned long freeram;   /* Available memory size */

         unsigned long sharedram; /* Amount of shared memory */

         unsigned long bufferram; /* Memory used by buffers */

         unsigned long totalswap; /* Total swap space size */

         unsigned long freeswap;  /* Swap space still available */

         unsigned short procs;    /* Number of current processes */

         char _f[22];             /* Pads structure to 64 bytes */

};

通过查看文件arch/x86/entry/syscalls/syscall_64.tbl

知道系统调用号是99.

示例

 
  1. .section .data
  2. result:
  3. uptime:
  4. .int 0,0
  5. load1:
  6. .int 0,0
  7. load5:
  8. .int 0,0
  9. load15:
  10. .int 0,0
  11. totalram:
  12. .int 0,0
  13. freeram:
  14. .int 0,0
  15. sharedram:
  16. .int 0,0
  17. bufferram:
  18. .int 0,0
  19. totalswap:
  20. .int 0,0
  21. freeswap:
  22. .int 0,0
  23. procs:
  24. .byte 0x00, 0x00
  25. .section .text
  26. .globl _start
  27. _start:
  28. nop
  29. movq $result, %rdi
  30. movq $99, %rax
  31. syscall
  32.  
  33. movq $0, %rbx
  34. movq $60, %rax
  35. syscall
 

as -g -o sysinfo.o sysinfo.s

ld -o sysinfo sysinfo.o

在执行过程中进行查看如下:

(gdb) x /d &load5

0x6000e1:   23584

(gdb) x /d &load15

0x6000e9:   10656

(gdb) x /d &totalram

0x6000f1:   -467349504

(gdb) x /ud &totalram

0x6000f1:   -467349504

(gdb) x /u &totalram

0x6000f1:   3827617792

(gdb) x /u &procs

0x600121:   1565

(gdb) x /u &totalswap

0x600111:   999288832

(gdb) x /u &freeswap

0x600119:   999288832

(gdb)

第37部分-Linux x86 64位汇编 系统调用跟踪

我们可以使用strace进行系统调用跟踪。

本章我们通过汇编来实现一个后台持续的进程,然后strace通过-p参数来跟踪该进程的系统调用。

示例

调用nanosleep系统调用来进行等待,系统调用号位35.

NAME

       nanosleep - high-resolution sleep

 

SYNOPSIS

       #include <time.h>

 

       int nanosleep(const struct timespec *req, struct timespec *rem);

其中结构体timespec是:

struct timespec {

               time_t tv_sec;        /* seconds */

               long   tv_nsec;       /* nanoseconds */

};

代码如下:

 
  1. .section .data
  2. timespec:;//定义了timespec结构·,5表示秒,0表示0纳秒。需要16个字节。
  3. .int 5, 0,0,0
  4. output:
  5. .ascii "This is a test\n"
  6. output_end:
  7. .equ len, 15
  8. temp:
  9. .int 0
  10.  
  11. .section .bss;// 需要16个字节。
  12. .lcomm rem,16
  13. .section .text
  14. .globl _start
  15. _start:
  16. nop
  17. movq $10, %rcx
  18. loop1:
  19. movq %rcx, temp
  20. movq $1, %rax
  21. movq $1, %rdi
  22. movq $output,%rsi
  23. movq $len, %rdx
  24. syscall
  25.  
  26. movq $35, %rax
  27. movq $timespec, %rdi
  28. movq $rem, %rsi
  29. syscall
  30.  
  31. movq temp,%rcx
  32. loop loop1
  33.  
  34. movq $60, %rax
  35. syscall
 

as -g -o nanotest.o nanotest.s

ld -o nanotest nanotest.o

然后通过strace进行进程的系统调用。

# strace -p 87720

strace: Process 87720 attached

restart_syscall(<... resuming interrupted nanosleep ...>) = 0

write(1, "This is a test\n", 15)        = 15

nanosleep({tv_sec=5, tv_nsec=0},

0x600130) = 0

write(1, "This is a test\n", 15)        = 15

nanosleep({tv_sec=5, tv_nsec=0}, 0x600130) = 0

write(1, "This is a test\n", 15)        = 15

nanosleep({tv_sec=5, tv_nsec=0}, 0x600130) = 0

write(1, "This is a test\n", 15)        = 15

nanosleep({tv_sec=5, tv_nsec=0}, 0x600130) = 0

write(1, "This is a test\n", 15)        = 15

nanosleep({tv_sec=5, tv_nsec=0}, 0x600130) = 0

exit(6291720)                           = ?

+++ exited with 8 +++

第38部分-Linux x86 64位汇编 系统调用和C库

C库提供了丰富的函数,可以在汇编语言中利用他们。

C库函数包含在libc库中,必须连接到汇编语言程序中。C库包含在man页的第3部分。系统调用包含在man页的第2部分。

跟踪C函数示例

 
  1. .section .data
  2. output:
  3. .asciz "This is a test\n"
  4. temp:
  5. .int 0
  6. .section .text
  7. .globl _start
  8. _start:
  9. movq $10, %rcx
  10. loop1:
  11. movq %rcx, temp
  12. movq $output,%rdi
  13. call printf
  14. movq $5, %rdi
  15. call sleep
  16. pop %rcx
  17. movq temp,%rcx
  18. loop loop1
  19. movq $0,%rdi
  20. call exit
 

as -g -o cfunctest.o cfunctest.s

ld -o cfunctest cfunctest.o -lc -I /lib64/ld-linux-x86-64.so.2

运行cfunctest后使用strace进行跟踪

strace: Process 88728 attached

restart_syscall(<... resuming interrupted nanosleep ...>) = 0

write(1, "This is a test\n", 15)        = 15

nanosleep({tv_sec=5, tv_nsec=0}, 0x7ffe2039be70) = 0

write(1, "This is a test\n", 15)        = 15

nanosleep({tv_sec=5, tv_nsec=0}, 0x7ffe2039be78) = 0

write(1, "This is a test\n", 15)        = 15

nanosleep({tv_sec=5, tv_nsec=0}, 0x7ffe2039be80) = 0

write(1, "This is a test\n", 15)        = 15

nanosleep({tv_sec=5, tv_nsec=0}, 0x7ffe2039be88) = 0

write(1, "This is a test\n", 15)        = 15

nanosleep({tv_sec=5, tv_nsec=0}, 0x7ffe2039be90) = 0

write(1, "This is a test\n", 15)        = 15

nanosleep({tv_sec=5, tv_nsec=0}, 0x7ffe2039be98) = 0

write(1, "This is a test\n", 15)        = 15

nanosleep({tv_sec=5, tv_nsec=0}, 0x7ffe2039bea0) = 0

write(1, "This is a test\n", 15)        = 15

nanosleep({tv_sec=5, tv_nsec=0},

我们发现C库和系统调用基本一致。

另外,我们在执行的时候,使用strace -c ./cfunctest

# strace -c ./cfunctest

This is a test

This is a test

This is a test

This is a test

This is a test

This is a test

This is a test

This is a test

This is a test

This is a test

% time     seconds  usecs/call     calls    errors syscall

------ ----------- ----------- --------- --------- ----------------

  0.00    0.000000           0         1           read

  0.00    0.000000           0        10           write

  0.00    0.000000           0         2           close

  0.00    0.000000           0         3           fstat

  0.00    0.000000           0         5           mmap

  0.00    0.000000           0         4           mprotect

  0.00    0.000000           0         1           munmap

  0.00    0.000000           0         3           brk

  0.00    0.000000           0         3         3 access

  0.00    0.000000           0        10           nanosleep

  0.00    0.000000           0         1           execve

  0.00    0.000000           0         1           arch_prctl

  0.00    0.000000           0         2           openat

------ ----------- ----------- --------- --------- ----------------

100.00    0.000000                    46         3 total

 

# strace -c ./nanotest

This is a test

This is a test

This is a test

This is a test

This is a test

This is a test

This is a test

This is a test

This is a test

This is a test

% time     seconds  usecs/call     calls    errors syscall

------ ----------- ----------- --------- --------- ----------------

  0.00    0.000000           0        10           write

  0.00    0.000000           0        10           nanosleep

  0.00    0.000000           0         1           execve

------ ----------- ----------- --------- --------- ----------------

100.00    0.000000                    21           total

使用C库使用了46次系统调用,而nanotest只使用了21次系统调用。

C库和系统调用小结

从而我们可以得出结论,系统调用的好处是:

  • 代码长度短,不需要把外部库连接到程序中
  • 速度快,不需要外部库连接到程序中。
  • 连接后可执行文件独立于外部代码,只依赖内核系统调用。

使用C库的好处是:

  • 包含函数多,而汇编去实现比较费力。
  • C库在操作系统之间可移植。
  • C库函数在程序间利用共享,减少内存需求。

第39部分-Linux x86 64位汇编 跳转/调用/中断/循环

跳转

JMP指令把指令指针的值改变为JMP指令中指定的内存位置。

单一汇编跳转指令被汇编为跳转操作码的3种不同类型之一。

短跳转

近跳转

远跳转

由当前指令的内存位置和目的点的内存位置之间的距离决定的。

依据跳过的字节数目决定使用哪种跳转类型。

当偏移量小于128字节时候,用短跳转。

在分段内存模式下,当跳转到另一个段中的指令时使用元跳转。

近跳转用于所有其他跳转。

使用汇编语言助记符指令时,不需要担心跳转的长度,单一跳转指令用于跳转到程序代码中的任务位置。

 

条件跳转指令不支持分段内存模式下的远跳转。

调用

调用是具有在需要的时候返回这个位置的能力。

调用指令有两个部分,第一个部分是实际的call指令,需要单一操作数——跳转到的位置地址。

         紧跟call指针后面的是RET。

         调用CALL指令后,会把EIP寄存器放到堆栈中,然后修改EIP寄存器指向被调用的函数地址。函数调用完成后,从堆栈获得过去的EIP寄存器值,返回控制权。

中断

中断是处理器中断当前指令码路径并且切换到不同路径的方式。

中断有软件中断/硬件中断。

硬件中断是硬件层发生的事件。

软件中断是操作系统提供的。在Linux中,0x80中断用于提供低级内核函数。

 

循环

使用Loop命令。

 

未来防止loop限制,不要将ecs置0后开始循环。

Loop操作是先将ecs减1.

第40部分-Linux x86 64位汇编 Intel向量指令历史

SIMD主要是四种技术:

多媒体扩展(MMX, Multimedia Extension)

流化SIMD扩展(SSE,Streaming SIMD Extension)

流化SIMD扩展第二实现(SSE2, Streaming SIMD Extension Second Implementation)

流化SIMD扩展第三年实现(SSE3,Streaming SIMD Extension Third Implement)

SIMD的主要好处是使用单一指令执行并行数据操作的能力。

MMX和SSE架构可以保存打包数据的附加寄存器。

 

发展历史如下:

 

 

MMX

MMX提供3种整数值类型:

  • 64位打包字节整数(8个单字节整数值)/64位打包整数(4个字整数值)/64位打包双字整数(2个双字整数值)

MMX整数数据类型需要使用FPU寄存器来保存执行数学操作。MMX寄存器被命名为MM0到MM7。

MMX寄存器直接映射到FPU寄存器。但是不能当做堆栈使用。直接映射到FPU的R0到R7寄存器。

所以,FPU寄存器既用于保存MMX数据,也保存FPU数据,两者容易混乱。

MMX模式下使用这些寄存器处理MMX数据,在FPU模式下使用这些寄存器处理一般的FPU扩展双精度浮点数据。

     在MMX模式下使用FPU寄存器时,FPU的标记寄存器会被破坏。最后的办法是将FPU寄存器的指令和MMX寄存器的指令分开。

 

SSE

SSE主要是对浮点数据执行SIMD操作。新的数据类型:128位打包的单精度浮点数据类型。

SSE2

SSE2扩展了SSE核心架构。

128位打包的双精度浮点值(2个双精度值)

128位打包字节整数值(16个单字节整数值)

128位打包字整数值(8个字整数值)

128位打包双字整数值(4个双字整数值)

128位打包四字整数值(2个四字整数值)

SSE3没有添加新的数据类型,增加了数据高级处理指令。

 

向量指令支持检测

通过调用cpuid值,将eax寄存器赋值为1.

可以获取处理器签名信息。

使用test指令进行比较判断。

 
  1. .section .data
  2. gotmmx:
  3. .asciz "Supports MMX"
  4. gotsse:
  5. .asciz "Supports SSE"
  6. gotsse2:
  7. .asciz "Supports SSE2"
  8. gotsse3:
  9. .asciz "Supports SSE3"
  10. output:
  11. .asciz "%s\n"
  12. .section .bss
  13. .lcomm ecxdata, 4
  14. .lcomm edxdata, 4
  15. .section .text
  16. .globl _start
  17. _start:
  18. nop
  19. movl $1, %eax;//CPUID的1号功能
  20. cpuid
  21. movl %ecx, ecxdata
  22. movl %edx, edxdata
  23.  
  24. test $0x00800000, %edx
  25. jz done
  26.  
  27. movq $output,%rdi
  28. movq $gotmmx,%rsi
  29.  
  30. call printf
  31.  
  32. movl edxdata, %edx
  33. test $0x02000000, %edx
  34. jz done
  35. movq $output,%rdi
  36. movq $gotsse,%rsi
  37. call printf
  38.  
  39. movl edxdata, %edx
  40. test $0x04000000, %edx
  41. jz done
  42. movq $output,%rdi
  43. movq $gotsse2,%rsi
  44. call printf
  45.  
  46. movl ecxdata, %ecx
  47. test $0x00000001, %ecx
  48. jz done
  49. movq $output,%rdi
  50. movq $gotsse3,%rsi
  51.  
  52. call printf
  53.  
  54. done:
  55. movq $60,%rax
  56. call exit
 

as -g -o features.o features.s

ld -o features features.o -lc -I /lib64/ld-linux-x86-64.so.2

第41部分-Linux x86 64位汇编MMX使用

使用MMX架构需要一下步骤

  1. 从整数值创建打包整数值
  2. 把打包整数值加载到MMX寄存器中
  3. 对打包整数值执行MMX数学操作。
  4. 从MMX寄存器获得结果放到内存位置中。

加载和获得打包的整数值,使用movq指令把整数值传送进/出MMX寄存器。

对比打包整数值操作,同时计算多个结果值,单一一组标志不能表示操作的结果。可以从执行数学操作的3种溢出方法中选择: 环绕/带符号饱和/无符号饱和。

带符号和无符号饱和运算方法把溢出情况的结果设置为预先设置的值。

正溢出就设置为最大值,负溢出就设置为最小值,数学角度没有意义。主要是应用于执行图像计算显示图片,正溢出就是最大值为白色,负溢出就是最小值为黑色。

操作的指令有如下:

MMX加法示例

两个long类型整数值存储到单一内存位置,创建打包双字整数值。移动到MMX寄存器,然后通过PADD指令相加,存放到MM0中,最后复制到result内存位置。

.section .data
value1:
   .int 10, 20
value2:
   .int 30, 40
.section .bss
   .lcomm result, 8
.section .text
.globl _start
_start:
   nop
   movq value1, %mm0
   movq value2, %mm1
   paddd %mm1, %mm0
   movq %mm0, result
 
   movl $60, %eax
   movl $0, %ebx
   syscall

编译:

as -g -o mmxadd.o mmxadd.s

ld -o mmxadd mmxadd.o

使用gdb调试,在退出前断点进行查看结果。

(gdb) x /2d &value1

0x6000d8:   10  20

(gdb) x /2d &value2

0x6000e0:   30  40

(gdb) x /2x &result

0x6000e8 <result>: 0x00000028  0x0000003c

gdb>info all

st0            <invalid float value>   (raw 0xffff0000003c00000028)

可以在st0寄存器中看到两个结果。MM0就是存在st0中的。

 

    1. MMX乘法示例

乘法比较困难,因为乘法生产的结果可能会比输入操作数大得多。乘法允许使用两条指令完成乘法操作。

PMULL把每对打包字整数值相乘,结果的低16位存放到目标寄存器。

PMULH把每对打包字整数值相乘,结果的高16位存放到目标寄存器。

带符号的是PMULLW和PMULHW,无符号的是PMULLUW和PMULHUW。

     乘法系列中还有一个附加指令是PMADDWD指令。

MMX逻辑和移位

MMX中可用的布尔逻辑指令如下图:

SOURCE可以是MMX寄存器或者64位的内存位置,目标必须是MMX寄存器。

 

MMX比较

两个值比较指令如下图:

比较结果存放到目标打包整数值中。

      1. 示例

比较的示例如下,value1和value2被设置为保存4个short类型的整数值。加载到MMX寄存器中,然后使用PCMPEQW指令比较打包整数值的4个字值。结果存放到MM0寄存器,然后传送到result中。

.section .data
value1:
   .short 10, 20, -30, 40
value2:
   .short 10, 40, -30, 45
.section .bss
   .lcomm result, 8
.section .text
.globl _start
_start:
   nop
   movq value1, %mm0
   movq value2, %mm1
   pcmpeqw %mm1, %mm0
   movq %mm0, result
 
   movl $60, %eax
   movl $0, %ebx
   syscall

as -g -o mmxcomp.o mmxcomp.s

ld -g -o mmxcomp mmxcomp.o

使用gdb进行调试,开始时如下;

(gdb) x /x &value1

0x6000d8:   0x0014000a

(gdb) x /x &value2

0x6000e0:   0x0028000a

(gdb) x /x &result

0x6000e8 <result>: 0x00000000

执行移动到MM0/MM1后,ST0/ST1的寄存器如下:

st0 <invalid float value>  (raw 0xffff0028ffe20014000a)

st1 <invalid float value>  (raw 0xffff002dffe20028000a)

执行pcmpeqw后st0寄存器如下:

st0 <invalid float value>  (raw 0xffff0000ffff0000ffff)

(gdb) x /8x & result

0x6000e8 <result>: 0x0000ffff  0x0000ffff

我们发现结果如下:

相等的打包整数值,结果相等等于FFFF,不相等的等于0000。

第42部分-Linux x86 64位汇编SSE指令

SSE架构提供对打包单精度浮点值的SIMD支持。数据传送到XMM寄存器中。

SSE指令有两个版本,后缀PS和后缀SS。PS是对打包单精度浮点值执行类型的运算操作,每个值都参与。SS结尾的,只对打包值中的低位双字执行。

传送数据

传送单精度浮点值很大程度上依赖于值是否在内存中对准了。

MOVAPS指令要求数据在内存中对准16字节边界。如果不对准会出现分段错误。

gas汇编器使用.align命令来把数据对准特定的内存边界。

处理数据

运算指令如下;

这些指令都是用两个操作数,源操作数可以是128位内存或者XMM寄存器,目标操作数必须是XMM寄存器。

sse示例

 
  1. .section .data
  2. .align 16
  3. value1:
  4. .float 12.34, 2345., -93.2, 10.44
  5. value2:
  6. .float 39.234, 21.4, 100.94, 10.56
  7. .section .bss
  8. .lcomm result, 16
  9. .section .text
  10. .globl _start
  11. _start:
  12. nop
  13. movaps value1, %xmm0
  14. movaps value2, %xmm1
  15.  
  16. addps %xmm1, %xmm0
  17. sqrtps %xmm0, %xmm0
  18. maxps %xmm1, %xmm0
  19. movaps %xmm0, result
  20.  
  21. movl $60, %eax
  22. movl $0, %ebx
  23. syscall
 

把单精度浮点值加载到XMM寄存器中。并执行基本运算操作,XMM0结果被传送回内存中使用标签result标记的位置中。

as -g -o ssemath.o ssemath.s

ld -o ssemath ssemath.o

使用gdb进行过程中的查看:

(gdb) print $xmm0

$3 = {v4_float = {12.3400002, 2345, -93.1999969, 10.4399996}, v2_double = {5.6101725574714474e+24, 754974.88032836909}, v16_int8 = {-92, 112, 69, 65, 0, -112, 18, 69, 102, 102, -70,

    -62, 61, 10, 39, 65}, v8_int16 = {28836, 16709, -28672, 17682, 26214, -15686, 2621, 16679}, v4_int32 = {1095069860, 1158844416, -1027971482, 1093077565}, v2_int64 = {

    4977198868967288996, 4694732396933310054}, uint128 = 86602527020781775578804592404012036260}

(gdb) print $xmm1

$4 = {v4_float = {39.2340012, 21.3999996, 100.940002, 10.5600004}, v2_double = {228170145.05651563, 817889.63044647221}, v16_int8 = {-98, -17, 28, 66, 51, 51, -85, 65, 72, -31, -55,

    66, -61, -11, 40, 65}, v8_int16 = {-4194, 16924, 13107, 16811, -7864, 17097, -2621, 16680}, v4_int32 = {1109192606, 1101738803, 1120526664, 1093203395}, v2_int64 = {

    4731932128728379294, 4695272830521696584}, uint128 = 86612496260875578387995842694967259038}

相加addps后:

(gdb) s

18     sqrtps %xmm0, %xmm0

(gdb) print $xmm0

$5 = {v4_float = {51.5740013, 2366.3999, 7.74000549, 21}, v2_double = {6.014405302368266e+24, 201326624.48375034}, v16_int8 = {-57, 75, 78, 66, 102, -26, 19, 69, 32, -82, -9, 64, 0,

    0, -88, 65}, v8_int16 = {19399, 16974, -6554, 17683, -20960, 16631, 0, 16808}, v4_int32 = {1112427463, 1158932070, 1089973792, 1101529088}, v2_int64 = {4977575340048010183,

    4731031409642679840}, uint128 = 87272125618359850373392885123090631623}

sqrtps后,求平方:

(gdb) s

19     maxps %xmm1, %xmm0

(gdb) print $xmm0

$6 = {v4_float = {7.18150425, 48.6456566, 2.78208661, 4.5825758}, v2_double = {159623578059.61627, 1193.1154792614809}, v16_int8 = {-30, -50, -27, 64, 39, -107, 66, 66, -75, 13, 50,

    64, 118, -92, -110, 64}, v8_int16 = {-12574, 16613, -27353, 16962, 3509, 16434, -23434, 16530}, v4_int32 = {1088802530, 1111659815, 1077022133, 1083352182}, v2_int64 = {

    4774542550791212770, 4652962192817262005}, uint128 = 85832002755546427910696780764134362850}

maxps后,计算两个打包值中最大值:

(gdb) s

20     movaps %xmm0, result

(gdb) print $xmm0

$7 = {v4_float = {39.2340012, 48.6456566, 100.940002, 10.5600004}, v2_double = {159623578681.87201, 817889.63044647221}, v16_int8 = {-98, -17, 28, 66, 39, -107, 66, 66, 72, -31,

    -55, 66, -61, -11, 40, 65}, v8_int16 = {-4194, 16924, -27353, 16962, -7864, 17097, -2621, 16680}, v4_int32 = {1109192606, 1111659815, 1120526664, 1093203395}, v2_int64 = {

    4774542550811602846, 4695272830521696584}, uint128 = 86612496260875578388038453117050482590}

movaps后,result得到结果:

(gdb) x /4f &result

0x600100 <result>: 39.2340012  48.6456566  100.940002  10.5600004

 

比较指令

SSE的比较指令同MMX比较指令类似,单独比较128位打包单精度浮点值的每个元素。

其中CMPSS有3个操作数

CMPPS imp, source,destination

imp可以有如下:

结果是位掩码,存放在寄存器XMM0中。

Gas汇编器提供了替代imp操作数的伪指令,如下:

比较示例

 
  1. .section .data
  2. .align 16
  3. value1:
  4. .float 12.34, 2345., -93.2, 10.44
  5. value2:
  6. .float 12.34, 21.4, -93.2, 10.45
  7. .section .bss
  8. .lcomm result, 16
  9. .section .text
  10. .globl _start
  11. _start:
  12. nop
  13. movaps value1, %xmm0
  14. movaps value2, %xmm1
  15.  
  16. cmpeqps %xmm1, %xmm0
  17. movaps %xmm0, result
  18.  
  19. movl $60, %eax
  20. movl $0, %ebx
  21. syscall
 

as -g -o ssecomp.o ssecomp.s

ld -o ssecomp ssecomp.o

比较前:

(gdb) print $xmm0

$1 = {v4_float = {12.3400002, 2345, -93.1999969, 10.4399996}, v2_double = {5.6101725574714474e+24, 754974.88032836909}, v16_int8 = {-92, 112, 69, 65, 0, -112, 18, 69, 102, 102, -70,

    -62, 61, 10, 39, 65}, v8_int16 = {28836, 16709, -28672, 17682, 26214, -15686, 2621, 16679}, v4_int32 = {1095069860, 1158844416, -1027971482, 1093077565}, v2_int64 = {

    4977198868967288996, 4694732396933310054}, uint128 = 86602527020781775578804592404012036260}

(gdb) print $xmm1

$2 = {v4_float = {12.3400002, 21.3999996, -93.1999969, 10.4499998}, v2_double = {228170144.635625, 760217.88032836909}, v16_int8 = {-92, 112, 69, 65, 51, 51, -85, 65, 102, 102, -70,

    -62, 51, 51, 39, 65}, v8_int16 = {28836, 16709, 13107, 16811, 26214, -15686, 13107, 16679}, v4_int32 = {1095069860, 1101738803, -1027971482, 1093088051}, v2_int64 = {

    4731932128714256548, 4694777433960375910}, uint128 = 86603357807293900154403331565622227108}

比较后:

(gdb) print $xmm0

$3 = {v4_float = {-nan(0x7fffff), 0, -nan(0x7fffff), 0}, v2_double = {2.1219957904712067e-314, 2.1219957904712067e-314}, v16_int8 = {-1, -1, -1, -1, 0, 0, 0, 0, -1, -1, -1, -1, 0,

    0, 0, 0}, v8_int16 = {-1, -1, 0, 0, -1, -1, 0, 0}, v4_int32 = {-1, 0, -1, 0}, v2_int64 = {4294967295, 4294967295}, uint128 = 79228162495817593524129366015}

 (gdb) x /4x &result

0x600100 <result>: 0xffffffff  0x00000000  0xffffffff  0x00000000

表示第一个和第三个整数相等。

sse整数指令

sse提供处理64位打包整数值的一些扩展特性。扩展了MMX提供的功能。如下:

第43部分-Linux x86 64位汇编SSE2指令

SS2架构扩展了SSE指令。

SSE2使用128位XMM寄存器保存2个双精度,4个双字整数值或者2个四字整数值。

传送数据

提供了5条指令

处理数据

SSE2加法指令:

示例

 
  1. .section .data
  2. .align 16
  3. value1:
  4. .double 10.42, -5.330
  5. value2:
  6. .double 4.25, 2.10
  7. value3:
  8. .int 10, 20, 30, 40
  9. value4:
  10. .int 5, 15, 25, 35
  11. .section .bss
  12. .lcomm result1, 16
  13. .lcomm result2, 16
  14. .section .text
  15. .globl _start
  16. _start:
  17. nop
  18. movapd value1, %xmm0
  19. movapd value2, %xmm1
  20. movdqa value3, %xmm2
  21. movdqa value4, %xmm3
  22.  
  23. mulpd %xmm1, %xmm0
  24. paddd %xmm3, %xmm2
  25.  
  26. movapd %xmm0, result1
  27. movdqa %xmm2, result2
  28.  
  29. movl $1, %eax
  30. movl $0, %ebx
  31. int $0x80
 

as -g -o sse2math.o sse2math.s

ld -o sse2math sse2math.o

MOVAPD和MOVDQA指令加载XMM寄存器必须对准16字节边界。

(gdb) print $xmm0

$2 = {v4_float = {0.0587499999, 2.57562494, -7.46297859e-36, -2.33312488}, v2_double = {10.42, -5.3300000000000001}, v16_int8 = {-41, -93, 112, 61, 10, -41, 36, 64, 82, -72, 30,

    -123, -21, 81, 21, -64}, v8_int16 = {-23593, 15728, -10486, 16420, -18350, -31458, 20971, -16363}, v4_int32 = {1030792151, 1076156170, -2061584302, -1072344597}, v2_int64 = {

    4622055556569408471, -4605684971923916718}, uint128 = 255322474959727810128669020838031303639}

(gdb) print $xmm1

$3 = {v4_float = {0, 2.265625, -107374184, 2.01249981}, v2_double = {4.25, 2.1000000000000001}, v16_int8 = {0, 0, 0, 0, 0, 0, 17, 64, -51, -52, -52, -52, -52, -52, 0, 64},

  v8_int16 = {0, 0, 0, 16401, -13107, -13108, -13108, 16384}, v4_int32 = {0, 1074855936, -858993459, 1073794252}, v2_int64 = {4616471093031469056, 4611911198408756429},

  uint128 = 85074745567721443736252296162778808320}

(gdb) print $xmm2

$4 = {v4_float = {1.40129846e-44, 2.80259693e-44, 4.20389539e-44, 5.60519386e-44}, v2_double = {4.2439915824246103e-313, 8.4879831653432862e-313}, v16_int8 = {10, 0, 0, 0, 20, 0, 0,

    0, 30, 0, 0, 0, 40, 0, 0, 0}, v8_int16 = {10, 0, 20, 0, 30, 0, 40, 0}, v4_int32 = {10, 20, 30, 40}, v2_int64 = {85899345930, 171798691870},

  uint128 = 3169126501123975826038943907850}

(gdb) print $xmm3

$5 = {v4_float = {7.00649232e-45, 2.1019477e-44, 3.50324616e-44, 4.90454463e-44}, v2_double = {3.1829936866949413e-313, 7.4269852696136172e-313}, v16_int8 = {5, 0, 0, 0, 15, 0, 0,

    0, 25, 0, 0, 0, 35, 0, 0, 0}, v8_int16 = {5, 0, 15, 0, 25, 0, 35, 0}, v4_int32 = {5, 15, 25, 35}, v2_int64 = {64424509445, 150323855385},

  uint128 = 2772985688460420417681201561605}

调用mulpd后,2个双精度浮点值的乘法操作,xmm0寄存器如下:

(gdb) print $xmm0

$6 = {v4_float = {-2.30215358e+20, 3.09597635, -6.61886922e+22, -2.59978104}, v2_double = {44.284999999999997, -11.193000000000001}, v16_int8 = {20, -82, 71, -31, 122, 36, 70, 64,

    -118, 65, 96, -27, -48, 98, 38, -64}, v8_int16 = {-20972, -7865, 9338, 16454, 16778, -6816, 25296, -16346}, v4_int32 = {-515396076, 1078338682, -446676598, -1071226160},

  v2_int64 = {4631429376981315092, -4600881319971372662}, uint128 = 255411086697915565509973403936689204756}

执行paddd后,四个双字整数加法

(gdb) print $xmm2

$7 = {v4_float = {2.1019477e-44, 4.90454463e-44, 7.70714155e-44, 1.05097385e-43}, v2_double = {7.4269852691195516e-313, 1.5914968434956903e-312}, v16_int8 = {15, 0, 0, 0, 35, 0, 0,

    0, 55, 0, 0, 0, 75, 0, 0, 0}, v8_int16 = {15, 0, 35, 0, 55, 0, 75, 0}, v4_int32 = {15, 35, 55, 75}, v2_int64 = {150323855375, 322122547255},

  uint128 = 5942112189584396243720145469455}

最后结果:

(gdb) x /4wd &result2

0x600150 <result2>:    15   35   55   75

(gdb) x /2gf &result1

0x600140 <result1>:    44.284999999999997 -11.193000000000001

 

SSE3指令

SSE3相比SSE2没有提供任何新的数据类型,仅仅添加了几条指令。

 

posted @ 2023-04-08 09:55  CharyGao  阅读(305)  评论(0编辑  收藏  举报