前言
这里描述的是把C语言里的算法移植到汇编程序中,迁移工作主要在x86_64的平台上,测试系统是Ubuntu 20.04.3 LTS,gcc版本9.3.0。
一.解读C语言算法
这里以冒泡算法做例子。
#include <stdio.h>
#include <stdlib.h>
void swap(int *a, int *b);
void bubble_sort(int arr[], int size);
int main()
{
int num[16] = {5, 1, 3, 6, 2, 8, 9, 11, 13, 15, 14, 26, 45, 32, 12, 7};
int len =16;
printf("Before sorted:");
for (int i=0; i < len; i++)
{
printf("%4d", num[i]);
}
printf("\n");
bubble_sort(num, len);
printf("After sorted:");
for (int i=0; i < len; i++)
{
printf("%4d", num[i]);
}
printf("\n");
return 0;
}
void bubble_sort(int arr[], int size)
{
int i, j, flag;
for(i = 0; i < size - 1; i++)
{
flag = 0;
for(j = 0; j < size - 1; j++)
{
if(arr[j] > arr[j + 1])
{
swap(&arr[j], &arr[j + 1]);
flag = 1;
}
}
if(flag == 0)
{
break;
}
}
}
void swap(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
这个是在网上随便一搜就能找到的例程,等会要把这个例程中的算法迁移到汇编程序中。首先对这个例程进行简单分析:
- 算法核心在bubble_sort子函数中,子函数只调用了一个swap函数。
- 这个冒泡算法是改良后的冒泡算法,其中有一个flag做标志,flag等于0标志排序完成并跳出循环。
- 主函数中用到了数组历遍算法,并使用两次,把排序前后的数组分两次打印到屏幕上。
简单分析C语言例程后要想想在汇编里面如何实现:
1. 不改变C语言例程中的层次,把排序算法编写在子程序中。
2. 在bubble_sort的汇编子程序中也采用双层循环,由于汇编程序中的循环都是采用ecx寄存器做计数,因而双重循环中ecx寄存器要入栈保护。
3. 不用C语言例程中的swap算法,汇编语言里有xchg指令提供原子级的操作。
4. 主函数里面的数组历遍并打印的代码,在汇编里要提取出来做为独立的子程序,因为c语言里的几行代码在汇编中就是一长串。
5. 在汇编冒泡子程序中用eax作为数据交换寄存器,用ebx做待排序数组的基址寄存器,用edi做数组的变址寄存器,相当于数组下标,ecx做循环计数寄存器,edx做为c语言例程的flag标志。
可以编写算法描述文件了,写好算法描述文件后再转为汇编代码。
二、编写算法描述文件
1.先搭个框架
;-------------------------------------------------------------
; non-Basic Description for Assembly
;This is an algorithm description attemp to describ a bubble
;sort algorithm.
;-------------------------------------------------------------
statement
end statement
declare extern function printf
declare extern function exit
begin
pass address of banner_before to function
call function printf
call array_printer
call bubble_sort
pass address of banner_after to function
call function printf
call array_printer
pass parameter for function
call function exit
end
2.编写statement
statement内部的就相当于汇编程序里的数据段,要依照C语言例程中用到的字符和数值来写。
c语言例程中用到
1. num数组, int型数据。
2. 存放数组元素个数的变量len,int型的数据。
3. 字符串"Before sorted:"和"After sorted:"
4. 指示printf函数打印格式的字符"%4d"
5. 回车换行字符"\n"
6. 再定义两个常数DONE和NOT_DONE分别为0和0xffffffff
statement
num int array
define len (length of array)/(sizeof int)
banner_before char string "Before sorted: "
banner_after char string "After sort: "
format char string "%4d"
key_ret char string "\n"
define const DONE 0
define const NOT_DONE 0xffffffff
end statement
3.编写汇编子程序的算法描述
除了两个调用的c函数外,要完成另两个子程序的编写,array_printer和bubble_sort。
先编写array_printer:
要调用c函数printf需要三个寄存器:
- rdi寄存器存放的是字符串和打印格式字符的首地址
- rsi寄存器要存放待打印出来的数值,如果没有要打印处理的数值,rsi寄存器可以不传入数据。
- 要把rax清零,才能开始调用printf。
- 调用结果会影响ecx和edx寄存器中的数值。
把这部分的c语言代码复制出来,然后照着上面的结构编写算法描述:
for (int i=0; i < len; i++)
{
printf("%4d", num[i]);
}
printf("\n");
这是一个单循环结构,里面调用c函数printf,还是先搭个框架:
subproc
push rax, rcx, rdi, rsi
let ecx = len
loop _num_array
push rcx, rdi
pass array parameter to function
call printf
pop rdi, rcx
let edi = edi + 1
jump to loop _num_array until ecx decrease to zero
pass key_ret parameter to function
call printf
pop rsi, rdi, rcx, rax
return
endproc
调用printf前还是小心翼翼地把rcx放入栈中,调用完成后再出栈。现在可以完成这部分的算法描述了。
subproc array_printer
push rax, rcx, rdi, rsi
let ecx = len
let edi = 0
loop _num_array:
push rcx, rdi
let esi = num[edi*4]
let rdi load address of format
let rax = 0
call function printf
pop rdi, rcx
let edi = edi + 1
jump to loop _num_array until ecx decrease to zero
;end loop _num_array
let rdi load address of ket_ret
let rax =0
call function printf
pop rsi, rdi, rcx, rax
return
endproc
再编写bubble_sort的算法描述
先把这段c语言的代码贴上来,照上面的层次转写。
void bubble_sort(int arr[], int size)
{
int i, j, flag;
for(i = 0; i < size - 1; i++)
{
flag = 0;
for(j = 0; j < size - 1; j++)
{
if(arr[j] > arr[j + 1])
{
swap(&arr[j], &arr[j + 1]);
flag = 1;
}
}
if(flag == 0)
{
break;
}
}
}
这部分是冒泡算法的核心,双重循环结构,ecx寄存器由len-1开始递减到零才完成一次循环。swap放在内循环里,进入内循环前flag置零,只要交换数据就把flag置1,我把flag置成NOT_DONE,相应的进入循环前flag置成DONE,这样程序好读一些。按之前所设想的那样,eax唱主角,ebx当助手,ecx做循环计数,edx当旗手,edi做数组下标。还是先搭个框架吧。
subproc bubble_sort
push registers
let ecx = len -1
loop outer:
push rcx
let edx = DONE
;instructments
let ecx = len -1
let edi = 0
loop inner:
;instructments
if no exchange then jump to label skip
endif
exchange two elements
let edx = NOT_DONE
label skip:
let edi = edi +1
jump to loop inner until ecx decrease to zero
;end loop inner
pop rcx
jump to loop outer until ecx decrease to zero
;end loop outer
label endsub:
pop registers
return
endproc
现在要填写进去的就剩条件判断和数组元数交换,完整的bubble_sort子程序算法描述如下:
subproc bubble_sort
push rax, rbx, rcx, rdx, rdi
xor rdx, rdx
let ecx = len - 1
loop outer:
let edx = DONE
push rcx
let ecx = len - 1
let edi = 0
loop inner:
let eax = num[edi*4]
if (eax <= num[edi*4+4]) then jump to label skip
endif
exchange eax, num[edi*4+4]
let num[edi*4] = eax
let edx = NOT_DONE
label skip:
let edi = edi + 1
jump to loop inner until ecx decrease to zero
;end loop inner
pop rcx
if (edx != DONE) then jump to label endsub
endif
jump to loop outer until ecx decrease to zero
;end loop outer
label endsub:
pop rdi, rdx, rcx, rbx, rax
return
endproc
4.完成整个算法描述
回头把主程序中的描述写完整,再把statement和两个子程序的算法描述整合一起就完成了冒泡算法的算法描述了,我不再啰嗦了,把整个算法描述写在下面。
;-------------------------------------------------------------
; non-Basic Description for Assembly
;This is an algorithm description attemp to describ a bubble
;sort algorithm.
;-------------------------------------------------------------
statement
num int array
define len (length of array)/(sizeof int)
banner_before char string "Before sorted: "
banner_after char string "After sort: "
format char string "%4d"
key_ret char string "\n"
define const DONE 0
define const NOT_DONE 0xffffffff
end statement
declare extern function printf
declare extern function exit
begin
;pass parameter for function
let rdi load address of banner_before
let rax = 0
call function printf
call array_printer
call bubble_sort
;pass parameter for function
let rdi load address of banner_after
let rax = 0
call function printf
call array_printer
;pass parameter for function
let rdi = 0
call function exit
end
subproc array_printer
push rax, rcx, rdi, rsi
let ecx = len
let edi = 0
loop _num_array:
push rcx, rdi
let esi = num[edi*4]
let rdi load address of format
let rax = 0
call function printf
pop rdi, rcx
let edi = edi + 1
jump to loop _num_array until ecx decrease to zero
;end loop _num_array
let rdi load address of ket_ret
let rax =0
call function printf
pop rsi, rdi, rcx, rax
return
endproc
subproc bubble_sort
push rax, rbx, rcx, rdx, rdi
xor rdx, rdx
let ecx = len - 1
loop outer:
let edx = DONE
push rcx
let ecx = len - 1
let edi = 0
loop inner:
let eax = num[edi*4]
if (eax <= num[edi*4+4]) then jump to label skip
endif
exchange eax, num[edi*4+4]
let num[edi*4] = eax
let edx = NOT_DONE
label skip:
let edi = edi + 1
jump to loop inner until ecx decrease to zero
;end loop inner
pop rcx
if (edx != DONE) then jump to label endsub
endif
jump to loop outer until ecx decrease to zero
;end loop outer
label endsub:
pop rdi, rdx, rcx, rbx, rax
return
endproc
三、依照算法描述编写汇编代码
我把完成的汇编程序贴出来,我是按照AT&T语法风格编写的,为了使用gcc编译,把汇编入口由_start改成main。编译的时候gcc要带上-no-pie这个选项。
/*----------------------------------------------------------------
This is an example which according to bubble sort
algorithm attemp to sort numbers in an array.
-----------------------------------------------------------------*/
.section .data
num:
.int 5, 1, 3, 6, 2, 8, 9, 11, 13, 15, 14, 26, 45, 32, 12, 7
len = (. - num)/4
banner_before:
.asciz "Before sorted: "
banner_after:
.asciz "After sorted: "
format:
.asciz "%4d"
key_ret:
.asciz "\n"
.equ DONE, 0
.equ NOT_DONE, 0xffffffff
//.data ends
.section .text
.global main
.extern printf
.extern exit
main:
mov $banner_before, %rdi
xor %rax, %rax
call printf
call array_printer
call bubble_sort
mov $banner_after, %rdi
xor %rax, %rax
call printf
call array_printer
xor %rdi, %rdi
call exit
//end main
array_printer:
push %rax
push %rcx
push %rdi
push %rsi
mov $len, %ecx
xor %edi, %edi
_num_array:
push %rcx
push %rdi
mov num(,%edi,4), %esi
mov $format, %rdi
xor %rax, %rax
call printf
pop %rdi
pop %rcx
inc %edi
loop _num_array
//end loop
mov $key_ret, %rdi
xor %rax, %rax
call printf
pop %rsi
pop %rdi
pop %rcx
pop %rax
ret
//endproc
bubble_sort:
push %rax
push %rbx
push %rcx
push %rdx
push %rdi
xor %rdx, %rdx
mov $len-1, %ecx
mov $num, %ebx
outer:
mov $DONE, %edx
push %rcx
mov $len-1, %ecx
xor %edi, %edi
inner:
mov (%ebx,%edi,4), %eax
cmp 4(%ebx,%edi,4), %eax
jna skip
xchg 4(%ebx,%edi,4), %eax
mov %eax, (%ebx,%edi,4)
mov $NOT_DONE, %edx
skip:
inc %edi
loop inner
//end loop inner
pop %rcx
cmp $DONE, %edx
je endsub
loop outer
//end loop outer
endsub:
pop %rdi
pop %rdx
pop %rcx
pop %rbx
pop %rax
ret
//endproc
//.text ends
//end