在Linux下用汇编写程序
内核还是离不开汇编的!
代码示例:
1 ; 编译链接方法
2 ; (ld 的‘-s’选项意为“strip all”)
3 ;
4 ; $ nasm -f elf hello.asm -o hello.o
5 ; $ ld -s hello.o -o hello
6 ; $ ./hello
7 ; Hello, world!
8 ; $
9
10 [section .data] ; 数据在此
11
12 strHello db "Hello, world!", 0Ah
13 STRLEN equ $ - strHello
14
15 [section .text] ; 代码在此
16
17 global _start ; 我们必须导出 _start 这个入口,以便让链接器识别
18
19 _start:
20 mov edx, STRLEN
21 mov ecx, strHello
22 mov ebx, 1
23 mov eax, 4 ; sys_write
24 int 0x80 ; 系统调用
25 mov ebx, 0
26 mov eax, 1 ; sys_exit
27 int 0x80 ; 系统调用
28
编译链接(每一步都用ls命令查看文件的增加情况)
-> ls
hello.asm
-> sudo nasm -f elf hello.asm -o hello.o
->ls
hello.asm hello.o
->ld -s hello.o -o hello
报错信息:
ld: i386 architecture of input file `hello.o' is incompatible with i386:x86-64 output
解决方法:
-> ld -m elf
ld: unrecognised emulation mode: elf
Supported emulations: elf_x86_64 elf32_x86_64 elf_i386 i386linux elf_l1om elf_k1om i386pep i386pe
根据输出选择对应的架构选项,将链接命令修改为:ld -m elf_i386 -s hello.o -o hello
-ls
hello hello.asm hello.o
生成了elf格式文件, 链接选项“-s” 意为strip,可以去掉符号表的内容!
执行 ` ./hello ` hello,world!
汇编和C互相调用
如下的小例子,源代码 foo.asm和bar.c
程序的入口_start在foo.asm中,一开始就是启动 程序将会调用bar.c 中的函数choose(),choose()将会比较传入的两个参数,根据比较结果的不同打印不同的字符串。打印字符串的工作是由foo.asm找那个的函数myprint()来完成!
这样能熟悉点,汇编代码和c语言互相调用!
代码如下:
foo.asm
1 ; 编译链接方法
2 ; (ld 的‘-s’选项意为“strip all”)
3 ;
4 ; $ nasm -f elf foo.asm -o foo.o
5 ; $ gcc -c bar.c -o bar.o
6 ; $ ld -s hello.o bar.o -o foobar
7 ; $ ./foobar
8 ; the 2nd one
9 ; $
10
11 extern choose ; int choose(int a, int b);
12
13 [section .data] ; 数据在此
14
15 num1st dd 3
16 num2nd dd 4
17
18 [section .text] ; 代码在此
19
20 global _start ; 我们必须导出 _start 这个入口,以便让链接器识别
21 global myprint ; 导出这个函数为了让 bar.c 使用
22
23 _start:
24 push dword [num2nd] ; `.
25 push dword [num1st] ; |
26 call choose ; | choose(num1st, num2nd);
27 add esp, 8 ; /
28
29 mov ebx, 0
30 mov eax, 1 ; sys_exit
31 int 0x80 ; 系统调用
32
33 ; void myprint(char* msg, int len)
34 myprint:
35 mov edx, [esp + 8] ; len
36 mov ecx, [esp + 4] ; msg
37 mov ebx, 1
38 mov eax, 4 ; sys_write
39 int 0x80 ; 系统调用
40 ret
41
42
- bar.c中要用函数myprint()。所以要用关键字global导出来!
- 用到本文件外定义的函数choose(),所以要用关键字extern声明!
- 不管myprint还是choose ,遵循的都是C调用约定(C Calling Convention),后面参数先入栈,并由调用者Caller 清理堆栈!
1 void myprint(char* msg, int len);
2
3 int choose(int a, int b)
4 {
5 if(a >= b){
6 myprint("the 1st one\n", 13);
7 }
8 else{
9 myprint("the 2nd one\n", 13);
10 }
11
12 return 0;
13 }
14
所以主要就是global 和 extern 关键字!
编译实践
编译链接指令
nasm -f elf foo.s -o foo.o
gcc -c bar.c -o bar.o
ld -s -o foobar bar.o foo.o
汇编语言用nasm编写并用nasm编译器编译,而C语言用的是gcc编译,这些都没有问题,但是在链接的时候出错了,提示如下:
ld: i386 architecture of input file 'foo.o' is incompatible with i386:x86-64 output
意思就是nasm 编译产生的是32位的目标代码,gcc 在64位平台上默认产生的是64位的目标代码,这两者在链接的时候出错,gcc在64位平台上默认以64位的方式链接。 这样在解决的时候就会有两种解决方案:
- 让gcc 产生32位的代码,并在链接的时候以32位的方式进行链接 在这种情况下只需要修改编译和链接指令即可,具体如下: 32位的编译链接指令
nasm -f elf foo.s -o foo.o
gcc -m32 -c bar.c -o bar.o
ld -m elf_i386 -s -o foobar foo.o bar.o
具体的-m32 和 -m elf_i386 请自行查阅gcc (man gcc)
如果你是高版本的gcc(可能是由于更新内核造成的),可能简单的使用-m32 的时候会提示以下错误(使用别人的历程,自己未曾遇到):
In file included from /usr/include/stdio.h:28:0, from test.c:1: /usr/include/features.h:323:26: fatal error: bits/predefs.h: No such file or directory compilation terminated. 这应该是缺少构建32 位可执行程序缺少的包,使用以下指令安装: sudo apt-get install libc6-dev-i386 此时应该就没有什么问题了。
使用的makefile如下!
$@ 代表目标 $^ 代表所有的依赖对象 $< 代表第一个依赖对象
#######################
# Makefile for foobar #
#######################
# Programs, flags, etc.
ASM = nasm
CC = gcc
LD = ld
ASMFLAGS = -f elf
CFLAGS = -c
LDFLAGS = -s
# This Program
BIN = foobar
OBJS = foo.o bar.o
# All Phony Targets
.PHONY : everything final image clean realclean disasm all buildimg
# Default starting position
everything : $(BIN)
all : realclean everything
final : all clean
clean :
rm -f $(OBJS)
realclean :
rm -f $(OBJS) $(BIN)
$(BIN) : $(OBJS)
$(LD) -m elf_i386 $(LDFLAGS) -o $(BIN) $(OBJS)
foo.o : foo.asm
$(ASM) $(ASMFLAGS) -o $@ $<
bar.o: bar.c
$(CC) -m32 -c $(CFLAGS) -o $@ $<