AT&T 汇编-9: 位操作

位操作是针对整数的二进制位进行的。

左移

用于左移的指令有两种:sal(向左算术移位)和shl(向左逻辑移位)。它们执行的操作相同,对于有符号和无符号整数的结果也一样。指令有 3 种不同的格式(以shl为例):

shl   destination
shl   %cl,  destination
shl   shift,   destination

destination 可以是 8 位,16 位或 32 位的值,根据不同的长度在 sal/shl 后附加不同的字母区分。第一种格式把 destination 的各个二进制位依次向左移动 1 位;第二种格式把 destination 的各个二进制位依次向左移动寄存器 cl 中指定的位数;第三种和第二种意义一样,只不过移动的位数由 shift 指定。

左移后,数值右边造成的空位用 0 填充,左边超出数据长度的位(最高有效位)会被暂时存放到进位标志(CF)中。

# shl.s

.section .rodata
msg:
   .asciz   "The value is: %d.\n"
cf:
   .asciz   "CF is set to 1.\n"

.section .data
value:
   .byte 234 # 11101010

.section .text
.globl main
main:
   shlb  value    # 11010100 <-> 212, CF = 1
   jnc   cfz1

   pushl $cf
   call  printf
   addl  $4,   %esp

cfz1:
   movl  $0,      %eax
   movb  value,   %al
   pushl %eax
   pushl $msg
   call  printf
   addl  $8,      %esp

   shlb  $3,   value   # 10100000 <-> 160, CF = 0
   jnc   cfz2

   pushl $cf
   call  printf
   addl  $4,   %esp

cfz2:
   movl  $0,      %eax
   movb  value,   %al
   pushl %eax
   pushl $msg
   call  printf
   addl  $8,      %esp

   pushl $0
   call  exit

第 16 行将 value 左移 1 位,最低位用 0 填充,最高位被暂存到 CF 中;第 31 行接着将 value 左移 3 位。程序打印了相关信息。

左移相当于原来的数值乘以 2,溢出部分被丢弃。

右移

对应于左移操作,右移操作也有两种指令:sar(向右算术移位)和shr(向右逻辑移位)。和左移一样有三种格式(以sar为例):

sar   destination
sar   %cl,  destination
sar   shift,   destination

destination 可以是 8 位,16 位或 32 位的值,根据不同的长度在sar/shr 后附加不同的字母区分。第一种格式把 destination 的各个二进制位依次向右移动 1 位;第二种格式把 destination 的各个二进制位依次向右移动寄存器 cl 中指定的位数;第三种和第二种意义一样,只不过移动的位数由 shift 指定。

右移后,数值右边边超出数据长度的位(最低有效位)会被暂时存放到进位标志(CF)中,但左边的空位不能简单的用 0 来填充。因为在计算机中数据使用补码表示,对于无符号整数空位可以用 0 来填充,而对于有符号整数来说则需要用 1 来填充(相当于范围提升时的符号扩展)。shr 指令使用 0 来填充空位,所以只能针对无符号进行移位操作,而 sar 指令会根据整数的符号位(最高有效位)判断应该用 0 还是 1 来填充空位。

# sar.s

.section .rodata
msg:
   .asciz   "The value is: %d.\n"
cf:
   .asciz   "CF is set to 1.\n"

.section .data
value:
   .byte 167 # 10100111, MSB(most significant bit) = 1

.section .text
.globl main
main:
   sarb  value    # 11010011 <-> 211, CF = 1
   jnc   cfz1

   pushl $cf
   call  printf
   addl  $4,   %esp

cfz1:
   movl  $0,      %eax
   movb  value,   %al
   pushl %eax
   pushl $msg
   call  printf
   addl  $8,      %esp

   sarb  $3,   value   # 11111010 <-> 250, CF = 0
   jnc   cfz2

   pushl $cf
   call  printf
   addl  $4,   %esp

cfz2:
   movl  $0,      %eax
   movb  value,   %al
   pushl %eax
   pushl $msg
   call  printf
   addl  $8,      %esp

   pushl $0
   call  exit

第 11 行声明了一个整数 167,因为二进制的最高位为 1,所以右移时使用 1 来填充。

右移相当于原数值除以 2,最低位(即余数)被丢弃。

循环移位

循环移位指令和普通移位指令的区别是,移位溢出的值不是被丢弃,而是被放回值的另一端。例如字节循环左移,最高位(第 7 位)溢出的值会被放到二进制位的最低位(第0位)。

循环移位指令有两种:一种是移位时不修改 CF 位的指令 rol(循环左移)和 ror(循环右移);另一种是移位时修改 CF 位的指令 rcl(循环左移)和 rcr(循环右移)。对于修改 CF 位的循环移动,移位时溢出的位被放到由移位造成的空位中的同时也会被暂存到 CF 中。

和普通移位操作一样,循环移位操作也支持 3 种格式(以 rol 为例):

rol   destination
rol   %cl,  destination
rol   shift,   destination

每种格式的含义和普通移位操作相似。

# rol.s

.section .rodata
msg:
   .asciz   "The value is: %d.\n"
cf:
   .asciz   "CF is set to 1.\n"

.section .data
value:
   .byte 167 # 10100111

.section .text
.globl main
main:
   rolb  value    # 01001111 <-> 79, CF = 1
   jnc   cfz1

   pushl $cf
   call  printf
   addl  $4,   %esp

cfz1:
   movl  $0,      %eax
   movb  value,   %al
   pushl %eax
   pushl $msg
   call  printf
   addl  $8,      %esp

   rolb  value   # 10011110 <-> 158, CF = 0
   jnc   cfz2

   pushl $cf
   call  printf
   addl  $4,   %esp

cfz2:
   movl  $0,      %eax
   movb  value,   %al
   pushl %eax
   pushl $msg
   call  printf
   addl  $8,      %esp

   pushl $0
   call  exit

检查和修改位

检查指定的某个二进制位使用 test 指令:

test  mask, value

test 指令对 mask 和 value 执行按位与操作(如果两个操作位都为 1 的话结果为 1,否则结果为 0),根据结果设置相应的 EFLAGS 标志位,并且不会修改 value 的值。

如果要修改某个二进制位使用按位与的 and 指令和按位或的 or 指令:

and   mask, value
or    mask, value

and 指令执行按位与操作,如果两个操作位都为 1 的话结果为 1,否则结果为 0;or 指令执行按位或操作,如果两个操作位均为 0 的话结果为 0,否则结果为 1。操作结果保存到 value 中。

# and-or.s

.section .rodata
msg1:
   .asciz   "bit %d is 1.n"
msg2:
   .asciz   "old value is: %d.\n"
msg3:
   .asciz   "new value is: %d.\n"

.section .data
value:
   .int 167  # 10100111

.section .text
.globl main
main:
   pushl value
   pushl $msg2
   call  printf
   addl  $8,   %esp  # old value is 167

   movl  $8,   %eax
   or    %eax, value # 10100111 | 00001000 = 10101111

   pushl value
   pushl $msg3
   call  printf
   addl  $8,   %esp  # new value is 175

   test  %eax, value # 00001000 & 10101111
   jz    z2

   pushl $3  # bit pos
   pushl $msg1
   call  printf
   addl  $8,   %esp

z2:
   pushl value
   pushl $msg2
   call  printf
   addl  $8,   %esp  # old value is 175

   movl  $254, %eax
   and   %eax, value # 11111110 & 10101111 -> 10101110

   pushl value
   pushl $msg3
   call  printf
   addl  $8,   %esp  # new value is 174

   pushl $0
   call  exit

可以修改位的还有 xor 指令:

xor   mask, value

xor 指令执行按位异或操作,即如果两个操作位相同则结果为 0,否则结果为 1,最后结果保存到 value 中。

# test-xor.s

.section .rodata
msg1:
   .asciz   "bit %d is 1.\n"
msg2:
   .asciz   "old value is: %d.\n"
msg3:
   .asciz   "new value is: %d.\n"

.section .data
value:
   .int 167  # 10100111

.section .text
.globl main
main:
   movl  $4,   %eax
   test  %eax, value # 00000100 & 10100111
   jz    z1

   pushl $2  # bit pos
   pushl $msg1
   call  printf
   addl  $8,   %esp  # bit 2 is 1.

z1:
   pushl value
   pushl $msg2
   call  printf
   addl  $8,   %esp  # old value

   movl  $8,   %eax
   xor   %eax, value # 10100111 -> 10101111

   pushl value
   pushl $msg3
   call  printf
   addl  $8,   %esp  # new value

   test  %eax, value # 00001000 & 10101111
   jz    z2

   pushl $3  # bit pos
   pushl $msg1
   call  printf
   addl  $8,   %esp  # bit 3 is 1

z2:
   pushl value
   pushl $msg2
   call  printf
   addl  $8,   %esp  # old value

   movl  $8,   %eax
   xor   %eax, value # 10101111 -> 10100111

   pushl value
   pushl $msg3
   call  printf
   addl  $8,   %esp  # new value

   pushl $0
   call  exit
发表于 2011年6月16日
本文目前尚无任何评论.

发表评论

XHTML: 您可以使用这些标签: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>