ubuntu16.04 GNU汇编学习笔记-环境搭建和简单示例
一)序言
作为有着十几年开发经验的老程序员,自己一直对汇编比较“懵懂”,曾经购买过MASM这本书来学习,但是一直未能坚持和深入下去,等几年不看就完全忘记了。最近到了新的公司,对汇编,c编译,链接,运行内存等等都有了更高,更深的要求,自己也意识到这点,希望可以花一段时间来学习,掌握,精通汇编语言。最好的状态是看到c代码可以自动转换为汇编代码。
我一直觉得一个新手刚刚进入一个新的领域,不要一开始就看那些深奥的框架知识,众多的语法说明,而是先写一些小程序,编译运行,看看效果,自己给自己设定一些小的功能实现,等这些完成后,在继续看框架和语法说明效果可能会更好一些。
二)开发环境搭建,验证
因为工作中使用的是GNU GCC开发,所以这里也专注于此方向,操作系统是Ubuntu16.04 64位系统。
目前我们一般开发还都是32位,而ubuntu16.04则是64位系统,这里需要我们按照一些系统组件开支持开发,百度的结果,这里不单独做转发说明。注意下面三个必须同时安装,只安装第三个会有问题。
ubuntu16.04(64位)兼容32位程序 sudo apt install libc6:i386 sudo apt install libstdc++6:i386 或者直接安装gcc-multilib解决问题(推荐使用此方法) sudo apt install gcc-multilib
汇编程序代码hello.s 代码来自https://blog.csdn.net/shallnet/article/details/45544271,非常感谢作者。
#hello.s sample program to print hello world information .section .data #数据段声明 msg: .ascii "hello world -00- !\n" #要输出的字符串 len=.-msg #字符串长度 .section .text #代码段声明 # .global main # main: .global _start #指定入口函数 _start: #函数在屏幕上输出hello world! movl $len, %edx #第三个参数: 字符串长度 movl $msg, %ecx #第二个参数: hello world!字符串 movl $1, %ebx #第一个参数: 输出文件描述符 movl $4, %eax #系统调用号sys_write int $0x80 #调用内核功能 #下面为退出程序代码 movl $0, %ebx #第一个参数: 退出返回码 movl $1, %eax #系统调用sys_exit int $0x80 #调用内核功能
编译脚本:as_ld_run.sh
#!/bin/sh rm hello.o hello as -o hello.o hello.s ld -o hello hello.o ./hello
运行结果:
hello world -00- !
知识点:0)AS汇编的入口函数;1)汇编代码的最简单框架;2)汇编中调用linux系统调用的实现方法。
三)汇编里定义字符串,c语言里面显示此字符串功能实现
汇编代码addfunc.s
# addfunc.s .code32 .section .data #数据段声明 msg: .ascii "hello world 111111111111122222222233333333!" #要输出的字符串 len=.-msg #字符串长度 .section .text #代码段声明 .global _start #指定入口函数 .type addfunc, @function .globl addfunc addfunc: pushl %ebp # 因为该函数不影响ebx,edi、esi寄存器,所以开头和结尾没有包含它们。 movl %esp, %ebp pushl $msg pushl 8(%ebp) call strcpy addl $16, %esp movl $0, %eax popl %ecx #恢复ecx movl %ebp, %esp popl %ebp ret
c代码
#include <string.h> extern int addfunc(char*); int main(int argc, const char *argv[]) { int ret = 2; char *p = NULL; char str[128] = ""; p = str; ret = addfunc(p); printf("The return value is %d str=%s strlen(p)=%d.\n", ret, p,strlen(p)); return 0; }
Makefile
# Makefile for linux as CFLAGS= -Wall -g -m32 ASFLAGS= -gstabs --32 SRC_BIN=target_bin SRC_C=$(wildcard *.c) SRC_S=$(wildcard *.s) SRC_OBJ=$(SRC_C:.c=.o) SRC_OBJ+=$(SRC_S:.s=.o) all: $(SRC_BIN) $(SRC_BIN): $(SRC_OBJ) $(CC) -m32 -o $@ $(SRC_OBJ) clean: $(RM) $(SRC_OBJ) $(SRC_BIN) *~ .PHONY: all clean
运行命令和结果:
gaozh@T0:~/works/Application/c_assemblyfunc_str$ make clean&&make && ./target_bin rm -f main.o addfunc.o target_bin *~ cc -Wall -g -m32 -c -o main.o main.c as -gstabs --32 -o addfunc.o addfunc.s cc -m32 -o target_bin main.o addfunc.o The return value is 0 str=hello world 111111111111122222222233333333! strlen(p)=43.
知识点:1)64位系统编译32位汇编程序和32位C程序;2)汇编中自定字符串(符号常数)定义,使用;3)汇编中函数实现模板,进口和出口;4)汇编中strcpy API的调用,(未彻底搞清楚,寄存器与此api的关系)。
四)在第三步的基础上,增加汇编函数返回值表示字符串长度的功能实现
汇编代码:
# addfunc.s .code32 .section .data #数据段声明 msg: .ascii "hello world <111111111111122222222233333333!>" #要输出的字符串 len=.-msg #字符串长度 .section .text #代码段声明 .global _start #指定入口函数 .type addfunc, @function .globl addfunc addfunc: pushl %ebp # 因为该函数不影响ebx,edi、esi寄存器,所以开头和结尾没有包含它们。 movl %esp, %ebp movl 8(%ebp),%eax #取得C函数传过来的参数 pushl %ecx #保护ecx,用作临时变量 movl (%eax),%ecx #取得指针所指的内容 pushl $msg pushl 8(%ebp) call strcpy addl $16, %esp movl $0, %eax popl %ecx #恢复ecx:w movl $len, %eax movl %ebp, %esp popl %ebp ret
运行结果:
gaozh@T0:~/works/Application/c_assemblyfunc_str$ make clean&&make && ./target_bin rm -f main.o addfunc.o target_bin *~ cc -Wall -g -m32 -c -o main.o main.c as -gstabs --32 -o addfunc.o addfunc.s cc -m32 -o target_bin main.o addfunc.o The return value is 0 str=hello world 111111111111122222222233333333! strlen(p)=43.
总结:1)汇编函数中对唯一参数和函数返回值如何进行处理;
五)总结
1)两个例子都可以在预定环境下运行;
2)通过两个例子对汇编函数,函数参数,参数返回值有了一定的了解;
3)汇编中调用strcpy的实现,我是通过c代码实现,然后编译成.s文件做参考获得的代码,这将是后面很多功能实现的一个好的思路。