一、汇编基础
1、指令码与数据处理
当计算机处理应用程序运行指令码时,数据指针指示处理器如何在内存的数据区域寻找要处理的数据,这块区域也称为堆栈,指令码放在另外的指令区,此外,还有指令指针机制,当处理器完成一个指令码的处理后,指令指针指向下一条指令码。
IA-32指令码(INTEL、AMD公司的CPU使用)由一堆二进制码构成,其格式为:
指令前缀、操作码、可选修饰符、可选数据元素
指令前缀可包含1到4个修改操作码行为的1字节前缀,分为:
锁定前缀和重复前缀
段覆盖前缀和分支提示前缀
操作数长度覆盖前缀
地址长度覆盖前缀
操作码定义了处理器执行的功能
修饰符定义执行功能时涉及的寄存器和内存位置。
数据元素是完成功能需要使用的数据,这些数据可以是直接的数据值,也可以是数据在内存中的地址。
2、汇编语言
以LINUX/UNIX环境下的汇编语言AT&T汇编(WINDOWS下有一种常用的汇编格式Intel汇编)进行讲解,汇编语言允许程序员方便地创建指令码程序,但不是用那些二进制编码的格式,还是使用助记符,助记符使用不同的词表示不同的指令码,有了助记符,程序员可以用英语来书写在目标机器上执行的指令码,不用记忆那些无趣的二进制编码。
通常, FreeBSD 的内核使用 C 语言的调用规范。 此外, 虽然我们使用 int $0x80来访问内核, 但是我们常常通过调用一个函数来执行 int $0x80, 而不是直接访问。这个规范是非常方便的, 比 Microsoft� 的 MS-DOS上使用的规范更加优越。 为什么呢? 因为 UNIX� 的规范允许任何语言所写的程序访0问内核。这意味着在freebsd下访问内核需要先将参数压入栈中,然后再执行 int $0x80调用内核中断,执行内核函数。
下面这段代码是经典的helloworld汇编代码:
dp@dp:~ % vim helloworld.s
#hello.s
.data # 数据段声明
msg : .string "Hello, world!\n" # 要输出的字符串
len = . - msg # 字串长度
.text # 代码段声明
.global _start # 指定入口函数
_start: # 在屏幕上显示一个字符串
pushl $len # 参数三:字符串长度
pushl $msg # 参数二:要显示的字符串
pushl $1 # 参数一:文件描述符(stdout)
movl $4, %eax # 系统调用号(sys_write)
pushl %eax
int $0x80 # 调用内核功能
# 退出程序
movl $0,%ebx # 参数一:退出代码
movl $1,%eax # 系统调用号(sys_exit)
int $0x80 # 调用内核功能
在LINUX/UNIX(以freebsd为例)下,可以使用gs和ld软件汇编和链接。
dp@dp:~ % as -o helloworld.o helloworld.s
dp@dp:~ % ld -o helloworld helloworld.o
dp@dp:~ % ./helloworld
Hello, world!
dp@dp:~ %
也可以直接使用GCC命令编译,但用gcc编译时将入口函数名由_start改为main。
dp@dp:~ % vim helloworld.s
#hello.s
.data # 数据段声明
msg : .string "Hello, world!\n" # 要输出的字符串
len = . - msg # 字串长度
.text # 代码段声明
.global main # 指定入口函数
main: # 在屏幕上显示一个字符串
pushl $len # 参数三:字符串长度
pushl $msg # 参数二:要显示的字符串
pushl $1 # 参数一:文件描述符(stdout)
movl $4, %eax # 系统调用号(sys_write)
pushl %eax
int $0x80 # 调用内核功能
# 退出程序
movl $0,%ebx # 参数一:退出代码
movl $1,%eax # 系统调用号(sys_exit)
int $0x80 # 调用内核功能
~
汇编后运行
dp@dp:~ % gcc -o helloworld helloworld.s
dp@dp:~ % ./helloworld
Hello, world!
dp@dp:~ %
对于ubuntu等Linux 是一个类 UNIX 操作系统。 但是, 它的内核在传递参数的时候, 使用和 MS-DOS 相同系统调用规范。 比如在 UNIX 的规范中, 代表内核函数的数字存放在 EAX 中。 但是在 Linux 中, 参数不进行压栈而是存放在 EBX, ECX, EDX, ESI, EDI, EBP。因此在ubuntu下这段代码需要这样编写(设使用GCC编译)
#hello.s
.data # 数据段声明
msg : .string "Hello, world!\\n" # 要输出的字符串
len = . - msg # 字串长度
ext # 代码段声明
global main # 指定入口函数
main: # 在屏幕上显示一个字符串
movl $len, %edx # 参数三:字符串长度
movl $msg, %ecx # 参数二:要显示的字符串
movl $1, %ebx # 参数一:文件描述符(stdout)
movl $4, %eax # 系统调用号(sys_write)
int $0x80 # 调用内核功能
# 退出程序
movl $0,%ebx # 参数一:退出代码
movl $1,%eax # 系统调用号(sys_exit)
int $0x80 # 调用内核功能
2、C语言编译
C语言属于高级语言,对汇编语言程序员来说也许是一种解脱,完成同样的任务,程序编码量减少很多,这只是把很多编码生成转移除到了编译器上来而已,让机器承担这部分编码生成工作。
以freebsd系统、intel 的Intel(R) Pentium(R)CPU为例,编写以下hello,world代码
dp@dp:~ % cat hello.c
#include <stdio.h>
int main(){
printf("hello,world\n");
return 0;
}
dp@dp:~ %
编译后,运行
dp@dp:~ % gcc -o hello hello.c
dp@dp:~ % ./hello
hello,world
使用GCC的 -S选项生成C语言对应的汇编代码
dp@dp:~ % gcc -S hello.c
下面是刚才生成的汇编语言代码
dp@dp:~ % cat hello.s
.file "hello.c"
.section .rodata
.LC0:
.string "hello,world"
.text
.p2align 4,,15
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $4, %esp
movl $.LC0, (%esp)
call puts
movl $0, %eax
addl $4, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.ident "GCC: (GNU) 4.2.1 20070831 patched [FreeBSD]"
.section .note.GNU-stack,"",@progbits
dp@dp:~ %