第26部分- Linux x86 64位汇编 字符串操作反转实例
有了上面几个深入的语法知识后,我们继续来学习一个例子。
我们出两个版本的例子,实现字符串的反转效果。
AT&T汇编实现
将上诉的Intel汇编程序,我们改写成AT&T语法后汇编如下,这里将原来的mov rdi, $ + 15地方进行的修改,变成jmp命令了,jmp过去jmp回来,就不用涉及到栈中的函数地址了。
因为NASM 提供特殊的变量($ 和 $$ 变量)来操作位置计数器。在 GAS 中,无法操作位置计数器,必须使用标签计算下一个存储位置(数据、指令等等)。
.section .data
.equ SYS_WRITE,1
.equ STD_OUT,1
.equ SYS_EXIT,60
.equ EXIT_CODE,0
.equ NEW_LINE,0xa
INPUT: .ascii " Hello world!\n"
.section .bss;;//定义buffer缓存
.lcomm OUTPUT,12
.global _start
.section .text
_start:
movq $INPUT ,%rsi;//将字符串地址赋值为rsi
xor %rcx, %rcx;//清零
cld;//清空df标志位0
jmp calculateStrLength;//调用函数计算字符串长度
reversePre:
xor %rax, %rax;//清空rax
xor %rdi, %rdi;//清空rdi
jmp reverseStr;//跳转到reverseStr函数进行翻转
calculateStrLength:;//计算INPUT字符串的长度,并保存在rcx中。
cmpb $0 ,(%rsi);//与0对比,字符串最后一个’\0’字符串结束标志
je exitFromRoutine;//函数结束
lodsb;//从rds:rsi处加载一个字节到al,并增加rsi
pushq %rax;/将刚获取到的al进行压栈,不过这会破坏了原本返回地址的位置。返回使用通过push rdi来解决。
inc %rcx;//增加计数器
jmp calculateStrLength;//循环
exitFromRoutine:;//返回到_start主线。
pushq %rdi;//压栈return地址到栈中
jmp reversePre;
reverseStr:
cmpq $0,%rcx;//对比0,rcx已保存的是字符串的长度
je printResult;//相等则跳转到printResult
popq %rax;//从栈中弹出一个字符给rax
movq %rax,OUTPUT(%rdi);//写到[OUTPUT+rdi]对应的地址
dec %rcx;//写一个少一个
inc %rdi;//写一个地址加一个
jmp reverseStr;//返回循环
printResult:
mov %rdi, %rdx;//输出的长度赋值到rdx
mov $SYS_WRITE,%rax;//调用号
mov $STD_OUT, %rdi;//输出句柄
mov $OUTPUT, %rsi;//输出字符串地址
syscall;//调用sys_write输出OUTPUT保存的反向字符串。
jmp printNewLine;//调用函数输出换行符
printNewLine:
mov $SYS_WRITE, %rax;//调用号
mov $STD_OUT, %rdi;//输出句柄
mov $NEW_LINE, %rsi;//输出字符串地址
mov $1,%rdx;//输出的长度赋值到rdx
syscall
jmp exit
exit:
mov $SYS_EXIT ,%rax
mov $EXIT_CODE ,%rdi
syscall
编译:
as -g -o reverse_att.o reverse_att.s
ld -o reverse_att reverse_att.o
Intel汇编实现
section .data
SYS_WRITE equ 1
STD_OUT equ 1
SYS_EXIT equ 60
EXIT_CODE equ 0
NEW_LINE db 0xa
INPUT db "Hello world!"
section .bss;;//定义buffer缓存
OUTPUT resb 12
global _start
section .text
_start:
mov rsi, INPUT;//将字符串地址赋值为rsi
xor rcx, rcx;//清零
cld;//清空df标志位0
mov rdi, $ + 15;//预处理rdi寄存器,在calculateStrLength函数中会用到。$是表示当前指令的地址。$$是当前段的地址。通过$+15可以计算得到命令xor rax,rax这条指令的地址(可以通过objdump -D命令来数出来),将该地址赋值给rdi进行了保存。在calculateStrLength函数中可以直接获取rdi地址来返回到xor rax,rax命令。
call calculateStrLength;//调用函数计算字符串长度
xor rax, rax;//清空rax
xor rdi, rdi;//清空rdi
jmp reverseStr;//跳转到reverseStr函数进行翻转
calculateStrLength:;//计算INPUT字符串的长度,并保存在rcx中。
cmp byte [rsi], 0;//与0对比,字符串最后一个’\0’字符串结束标志
je exitFromRoutine;//函数结束
lodsb;//从rds:rsi处加载一个字节到al,并增加rsi
push rax;/将刚获取到的al进行压栈,不过这会破坏了原本返回地址的位置。返回使用通过push rdi来解决。
inc rcx;//增加计数器
jmp calculateStrLength;//循环
exitFromRoutine:;//返回到_start主线。
push rdi;//压栈return地址到栈中
ret
reverseStr:
cmp rcx, 0;//对比0,rcx已保存的是字符串的长度
je printResult;//相等则跳转到printResult
pop rax;//从栈中弹出一个字符给rax
mov [OUTPUT + rdi], rax;写到[OUTPUT+rdi]对应的地址
dec rcx;//写一个少一个
inc rdi;//写一个地址加一个
jmp reverseStr;//返回循环
printResult:
mov rdx, rdi;//输出的长度赋值到rdx
mov rax, SYS_WRITE;//调用号
mov rdi, STD_OUT;//输出句柄
mov rsi, OUTPUT;//输出字符串地址
syscall;//调用sys_write输出OUTPUT保存的反向字符串。
jmp printNewLine;//调用函数输出换行符
printNewLine:
mov rax, SYS_WRITE;//调用号
mov rdi, STD_OUT;//输出句柄
mov rsi, NEW_LINE;//输出字符串地址
mov rdx, 1;//输出的长度赋值到rdx
syscall
jmp exit
exit:
mov rax, SYS_EXIT
mov rdi, EXIT_CODE
syscall
编译和链接:
nasm -g -f elf64 -o reverse.o reverse.asm
ld -o reverse reverse.o
执行后输出如下:
# ./reverse
!dlrow olleH