汇编学习(7), Bit 操作
2022-12-09 本文已影响0人
android小奉先
本篇介绍
本篇介绍下汇编中的bit操作。
bit操作
首先写一个包含bit各种操作的函数:
; bits1.asm
extern printb
extern printf
section .data
msgn1 db "Number 1",10,0
msgn2 db "Number 2",10,0
msg1 db "XOR",10,0
msg2 db "OR",10,0
msg3 db "AND",10,0
msg4 db "NOT number 1",10,0
msg5 db "SHL 2 lower byte of number 1",10,0
msg6 db "SHR 2 lower byte of number 1",10,0
msg7 db "SAL 2 lower byte of number 1",10,0
msg8 db "SAR 2 lower byte of number 1",10,0
msg9 db "ROL 2 lower byte of number 1",10,0
msg10 db "ROL 2 lower byte of number 2",10,0
msg11 db "ROR 2 lower byte of number 1",10,0
msg12 db "ROR 2 lower byte of number 2",10,0
number1 dq -72
number2 dq 1064
section .bss
section .text
global main
main:
mov rbp, rsp; for correct debugging
push rbp
mov rbp,rsp
; print number1
mov rsi, msgn1
call printmsg
mov rdi, [number1]
call printb
; print number2
mov rsi, msgn2
call printmsg
mov rdi, [number2]
call printb
; print XOR (exclusive OR)------------------------
mov rsi, msg1
call printmsg
; xor and print
mov rax,[number1]
xor rax,[number2]
mov rdi, rax
call printb
; print OR ---------------------------------------
mov rsi, msg2
call printmsg
; or and print
mov rax,[number1]
or rax,[number2]
mov rdi, rax
call printb
; print AND ---------------------------------------
mov rsi, msg3
call printmsg
; and and print
mov rax,[number1]
and rax,[number2]
mov rdi, rax
call printb
; print NOT ---------------------------------------
mov rsi, msg4
call printmsg
; not and print
mov rax,[number1]
not rax
mov rdi, rax
call printb
; print SHL (shift left----------------------------
mov rsi, msg5
call printmsg
; shl and print
mov rax,[number1]
shl al,2
mov rdi, rax
call printb
; print SHR (shift right)--------------------------
mov rsi, msg6
call printmsg
;shr and print
mov rax,[number1]
shr al,2
mov rdi, rax
call printb
; print SAL (shift arithmetic left)----------------
mov rsi, msg7
call printmsg
; sal and print
mov rax,[number1]
sal al,2
mov rdi, rax
call printb
; print SAR (shift arithmetic right)----------------
mov rsi, msg8
call printmsg
; sar and print
mov rax,[number1]
sar al,2
mov rdi, rax
call printb
; print ROL (rotate left)---------------------------
mov rsi, msg9
call printmsg
; rol and print
mov rax,[number1]
rol al,2
mov rdi, rax
call printb
mov rsi, msg10
call printmsg
mov rax,[number2]
rol al,2
mov rdi, rax
call printb
; print ROR (rotate right)---------------------------
mov rsi, msg11
call printmsg
; ror and print
mov rax,[number1]
ror al,2
mov rdi, rax
call printb
mov rsi, msg12
call printmsg
mov rax,[number2]
ror al,2
mov rdi, rax
call printb
leave
ret
printmsg: ; print the heading for every bit operation
section .data
.fmtstr db "%s",0
section .text
push rbp
mov rbp, rsp
mov rdi,.fmtstr
mov rax,0
call printf
leave
ret
这儿再回顾下leave 和ret的区别:
leave 本质上就是epilogue 指令,恢复rsp指针,从栈上弹出rbp指针。
ret本质上也是弹栈,将栈上保存的返回地址弹出并赋值给rip指针,这样就可以接着执行了。
再写一个打印二进制的函数printb:
#include <stdio.h>
void printb(long long n){
long long s,c;
for (c = 63; c >= 0; c--)
{
s = n >> c;
// space after every 8th bit
if ((c+1) % 8 == 0) printf(" ");
if (s & 1)
printf("1");
else
printf("0");
}
printf("\n");
}
输出如下:
Number 1
11111111 11111111 11111111 11111111 11111111 11111111 11111111 10111000
Number 2
00000000 00000000 00000000 00000000 00000000 00000000 00000100 00101000
XOR
11111111 11111111 11111111 11111111 11111111 11111111 11111011 10010000
OR
11111111 11111111 11111111 11111111 11111111 11111111 11111111 10111000
AND
00000000 00000000 00000000 00000000 00000000 00000000 00000100 00101000
NOT number 1
00000000 00000000 00000000 00000000 00000000 00000000 00000000 01000111
SHL 2 lower byte of number 1
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11100000
SHR 2 lower byte of number 1
11111111 11111111 11111111 11111111 11111111 11111111 11111111 00101110
SAL 2 lower byte of number 1
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11100000
SAR 2 lower byte of number 1
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11101110
ROL 2 lower byte of number 1
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11100010
ROL 2 lower byte of number 2
00000000 00000000 00000000 00000000 00000000 00000000 00000100 10100000
ROR 2 lower byte of number 1
11111111 11111111 11111111 11111111 11111111 11111111 11111111 00101110
ROR 2 lower byte of number 2
00000000 00000000 00000000 00000000 00000000 00000000 00000100 00001010
基本看结果就可以和对应的操作对上。
这儿唯一需要注意的是shl,shr和sal,sar的区别,前者用0填补空出来的最高位,后者会用符号位填补空出来的最高位。
再看一个例子:
; bits2.asm
extern printf
section .data
msgn1 db "Number 1 is = %d",0
msgn2 db "Number 2 is = %d",0
msg1 db "SHL 2 = OK multiply by 4",0
msg2 db "SHR 2 = wrong divide by 4",0
msg3 db "SAL 2 = correctly multiply by 4",0
msg4 db "SAR 2 = correctly divide by 4",0
msg5 db "SHR 2 = OK divide by 4",0
number1 dq 8
number2 dq -8
result dq 0
section .bss
section .text
global main
main:
push rbp
mov rbp,rsp
;SHL
;positive number
mov rsi, msg1
call printmsg ;print heading
mov rsi, [number1]
call printnbr ;print number1
mov rax,[number1]
shl rax,2 ;multiply by 4 (logic)
mov rsi, rax
call printres
;negative number
mov rsi, msg1
call printmsg ;print heading
mov rsi, [number2]
call printnbr ;print number2
mov rax,[number2]
shl rax,2 ;multiply by 4 (logic)
mov rsi, rax
call printres
;SAL
;positive number
mov rsi, msg3
call printmsg ;print heading
mov rsi, [number1]
call printnbr ;print number1
mov rax,[number1]
sal rax,2 ;multiply by 4 (arithmetic)
mov rsi, rax
call printres
;negative number
mov rsi, msg3
call printmsg ;print heading
mov rsi, [number2]
call printnbr ;print number2
mov rax,[number2]
sal rax,2 ;multiply by 4 (arithmetic)
mov rsi, rax
call printres
;SHR
;positive number
mov rsi, msg5
call printmsg ;print heading
mov rsi, [number1]
call printnbr ;print number1
mov rax,[number1]
shr rax,2 ;divide by 4 (logic)
mov rsi, rax
call printres
;negative number
mov rsi, msg2
call printmsg ;print heading
mov rsi, [number2]
call printnbr ;print number2
mov rax,[number2]
shr rax,2 ;divide by 4 (logic)
mov [result], rax
mov rsi, rax
call printres
;SAR
;positive number
mov rsi, msg4
call printmsg ;print heading
mov rsi, [number1]
call printnbr ;print number1
mov rax,[number1]
sar rax,2 ;divide by 4 (arithmetic)
mov rsi, rax
call printres
;negative number
mov rsi, msg4
call printmsg ;print heading
mov rsi, [number2]
call printnbr ;print number2
mov rax,[number2]
sar rax,2 ;divide by 4 (arithmetic)
mov rsi, rax
call printres
leave
ret
printmsg:
section .data
.fmtstr db 10,"%s",10,0 ;format for a string
section .text
push rbp
mov rbp, rsp
mov rdi,.fmtstr
mov rax,0
call printf
leave
ret
printnbr:
section .data
.fmtstr db "The original number is %lld",10,0 ;format for an int
section .text
push rbp
mov rbp, rsp
mov rdi,.fmtstr
mov rax,0
call printf
leave
ret
printres:
section .data
.fmtstr db "The resulting number is %lld",10,0 ;format for an int
section .text
push rbp
mov rbp, rsp
mov rdi,.fmtstr
mov rax,0
call printf
leave
ret
输出如下:
SHL 2 = OK multiply by 4
The original number is 8
The resulting number is 32
SHL 2 = OK multiply by 4
The original number is -8
The resulting number is -32
SAL 2 = correctly multiply by 4
The original number is 8
The resulting number is 32
SAL 2 = correctly multiply by 4
The original number is -8
The resulting number is -32
SHR 2 = OK divide by 4
The original number is 8
The resulting number is 2
SHR 2 = wrong divide by 4
The original number is -8
The resulting number is 4611686018427387902
SAR 2 = correctly divide by 4
The original number is 8
The resulting number is 2
SAR 2 = correctly divide by 4
The original number is -8
The resulting number is -2
从结果可以看出,左移也就是放大,逻辑左移和算术左移结果是一样的,可是对于右移,也就是缩小,对于负数,逻辑右移就是明显错误的了,需要用算术右移。
因此涉及符号数字运算的时候,需要用算术移动,另外能用bit运算就用bit运算,有明显的性能优势。
最后再看一个bit操作的例子:
这儿还用到了printb,实现和前面一样,就不再重复。
; bits3.asm
extern printb
extern printf
section .data
msg1 db "No bits are set:",10,0
msg2 db 10,"Set bit #4, that is the 5th bit:",10,0
msg3 db 10,"Set bit #7, that is the 8th bit:",10,0
msg4 db 10,"Set bit #8, that is the 9th bit:",10,0
msg5 db 10,"Set bit #61, that is the 62nd bit:",10,0
msg6 db 10,"Clear bit #8, that is the 9th bit:",10,0
msg7 db 10,"Test bit #61, and display rdi",10,0
bitflags dq 0
section .bss
section .text
global main
main:
mov rbp, rsp; for correct debugging
push rbp
mov rbp,rsp
;print title
mov rdi, msg1
xor rax,rax
call printf
;print bitflags
mov rdi, [bitflags]
call printb
;set bit 4 (=5th bit)
;print title
mov rdi, msg2
xor rax,rax
call printf
bts qword [bitflags],4 ; set bit 4
;print bitflags
mov rdi, [bitflags]
call printb
;set bit 7 (=8th bit)
;print title
mov rdi, msg3
xor rax,rax
call printf
bts qword [bitflags],7 ; set bit 7
;print bitflags
mov rdi, [bitflags]
call printb
;set bit 8 (=9th bit)
;print title
mov rdi, msg4
xor rax,rax
call printf
bts qword [bitflags],8 ; set bit 8
;print bitflags
mov rdi, [bitflags]
call printb
;set bit 61 (=62nd bit)
;print title
mov rdi, msg5
xor rax,rax
call printf
bts qword [bitflags],61 ; set bit 61
;print bitflags
mov rdi, [bitflags]
call printb
;clear bit 8 (=9th bit)
;print title
mov rdi, msg6
xor rax,rax
call printf
btr qword [bitflags],8 ; bit reset 8
;print bitflags
mov rdi, [bitflags]
call printb
; test bit 61 (will set carry flag CF if 1)
;print title
mov rdi, msg7
xor rax,rax
call printf
xor rdi,rdi
mov rax,61 ; bit 61 to be tested
xor rdx,rdx ; make sure all bits are 0
bt [bitflags],rax ; bit test
setc dil ; set dil (=low rdi) to 1 if CF is set
call printb ; display rdi
leave
ret
结果如下:
No bits are set:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
Set bit #4, that is the 5th bit:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00010000
Set bit #7, that is the 8th bit:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 10010000
Set bit #8, that is the 9th bit:
00000000 00000000 00000000 00000000 00000000 00000000 00000001 10010000
Set bit #61, that is the 62nd bit:
00100000 00000000 00000000 00000000 00000000 00000000 00000001 10010000
Clear bit #8, that is the 9th bit:
00100000 00000000 00000000 00000000 00000000 00000000 00000000 10010000
Test bit #61, and display rdi
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
需要注意的如下:
- bit操作的索引也是从0开始
- bts 用来将某位设置为1
- btr用来将某位设置为0
- bt指令是测试某个bit位是否是1, bit位索引放到rax中,结果会放到relfag中的CF位
- setc是setCC中的一种,对于setc dil,如果reflags中CF是1,那么就会设置rdi的低八位为1。