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寄存器用于保存系统调用值。定义使用内核支持的系统调用中的哪个系统调用。
系统调用示例
-
.section .data
-
output:
-
.ascii "This is a test message.\n"
-
len:
-
.int 24
-
.section .text
-
.globl _start
-
_start:
-
movq $1, %rax
-
movq $1, %rdi
-
movq $output, %rsi
-
movq len, %rdx
-
syscall
-
-
movq $60, %rax
-
movq $0, %rdi
-
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.
示例
-
.section .data
-
result:
-
uptime:
-
.int 0,0
-
load1:
-
.int 0,0
-
load5:
-
.int 0,0
-
load15:
-
.int 0,0
-
totalram:
-
.int 0,0
-
freeram:
-
.int 0,0
-
sharedram:
-
.int 0,0
-
bufferram:
-
.int 0,0
-
totalswap:
-
.int 0,0
-
freeswap:
-
.int 0,0
-
procs:
-
.byte 0x00, 0x00
-
.section .text
-
.globl _start
-
_start:
-
nop
-
movq $result, %rdi
-
movq $99, %rax
-
syscall
-
-
movq $0, %rbx
-
movq $60, %rax
-
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 */
};
代码如下:
-
.section .data
-
timespec:;//定义了timespec结构·,5表示秒,0表示0纳秒。需要16个字节。
-
.int 5, 0,0,0
-
output:
-
.ascii "This is a test\n"
-
output_end:
-
.equ len, 15
-
temp:
-
.int 0
-
-
.section .bss;// 需要16个字节。
-
.lcomm rem,16
-
.section .text
-
.globl _start
-
_start:
-
nop
-
movq $10, %rcx
-
loop1:
-
movq %rcx, temp
-
movq $1, %rax
-
movq $1, %rdi
-
movq $output,%rsi
-
movq $len, %rdx
-
syscall
-
-
movq $35, %rax
-
movq $timespec, %rdi
-
movq $rem, %rsi
-
syscall
-
-
movq temp,%rcx
-
loop loop1
-
-
movq $60, %rax
-
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函数示例
-
.section .data
-
output:
-
.asciz "This is a test\n"
-
temp:
-
.int 0
-
.section .text
-
.globl _start
-
_start:
-
movq $10, %rcx
-
loop1:
-
movq %rcx, temp
-
movq $output,%rdi
-
call printf
-
movq $5, %rdi
-
call sleep
-
pop %rcx
-
movq temp,%rcx
-
loop loop1
-
movq $0,%rdi
-
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指令进行比较判断。
-
.section .data
-
gotmmx:
-
.asciz "Supports MMX"
-
gotsse:
-
.asciz "Supports SSE"
-
gotsse2:
-
.asciz "Supports SSE2"
-
gotsse3:
-
.asciz "Supports SSE3"
-
output:
-
.asciz "%s\n"
-
.section .bss
-
.lcomm ecxdata, 4
-
.lcomm edxdata, 4
-
.section .text
-
.globl _start
-
_start:
-
nop
-
movl $1, %eax;//CPUID的1号功能
-
cpuid
-
movl %ecx, ecxdata
-
movl %edx, edxdata
-
-
test $0x00800000, %edx
-
jz done
-
-
movq $output,%rdi
-
movq $gotmmx,%rsi
-
-
call printf
-
-
movl edxdata, %edx
-
test $0x02000000, %edx
-
jz done
-
movq $output,%rdi
-
movq $gotsse,%rsi
-
call printf
-
-
movl edxdata, %edx
-
test $0x04000000, %edx
-
jz done
-
movq $output,%rdi
-
movq $gotsse2,%rsi
-
call printf
-
-
movl ecxdata, %ecx
-
test $0x00000001, %ecx
-
jz done
-
movq $output,%rdi
-
movq $gotsse3,%rsi
-
-
call printf
-
-
done:
-
movq $60,%rax
-
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架构需要一下步骤
- 从整数值创建打包整数值
- 把打包整数值加载到MMX寄存器中
- 对打包整数值执行MMX数学操作。
- 从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中的。
-
- MMX乘法示例
乘法比较困难,因为乘法生产的结果可能会比输入操作数大得多。乘法允许使用两条指令完成乘法操作。
PMULL把每对打包字整数值相乘,结果的低16位存放到目标寄存器。
PMULH把每对打包字整数值相乘,结果的高16位存放到目标寄存器。
带符号的是PMULLW和PMULHW,无符号的是PMULLUW和PMULHUW。
乘法系列中还有一个附加指令是PMADDWD指令。
MMX逻辑和移位
MMX中可用的布尔逻辑指令如下图:
SOURCE可以是MMX寄存器或者64位的内存位置,目标必须是MMX寄存器。
MMX比较
两个值比较指令如下图:
比较结果存放到目标打包整数值中。
-
-
- 示例
-
比较的示例如下,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示例
-
.section .data
-
.align 16
-
value1:
-
.float 12.34, 2345., -93.2, 10.44
-
value2:
-
.float 39.234, 21.4, 100.94, 10.56
-
.section .bss
-
.lcomm result, 16
-
.section .text
-
.globl _start
-
_start:
-
nop
-
movaps value1, %xmm0
-
movaps value2, %xmm1
-
-
addps %xmm1, %xmm0
-
sqrtps %xmm0, %xmm0
-
maxps %xmm1, %xmm0
-
movaps %xmm0, result
-
-
movl $60, %eax
-
movl $0, %ebx
-
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操作数的伪指令,如下:
比较示例
-
.section .data
-
.align 16
-
value1:
-
.float 12.34, 2345., -93.2, 10.44
-
value2:
-
.float 12.34, 21.4, -93.2, 10.45
-
.section .bss
-
.lcomm result, 16
-
.section .text
-
.globl _start
-
_start:
-
nop
-
movaps value1, %xmm0
-
movaps value2, %xmm1
-
-
cmpeqps %xmm1, %xmm0
-
movaps %xmm0, result
-
-
movl $60, %eax
-
movl $0, %ebx
-
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加法指令:
示例
-
.section .data
-
.align 16
-
value1:
-
.double 10.42, -5.330
-
value2:
-
.double 4.25, 2.10
-
value3:
-
.int 10, 20, 30, 40
-
value4:
-
.int 5, 15, 25, 35
-
.section .bss
-
.lcomm result1, 16
-
.lcomm result2, 16
-
.section .text
-
.globl _start
-
_start:
-
nop
-
movapd value1, %xmm0
-
movapd value2, %xmm1
-
movdqa value3, %xmm2
-
movdqa value4, %xmm3
-
-
mulpd %xmm1, %xmm0
-
paddd %xmm3, %xmm2
-
-
movapd %xmm0, result1
-
movdqa %xmm2, result2
-
-
movl $1, %eax
-
movl $0, %ebx
-
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没有提供任何新的数据类型,仅仅添加了几条指令。