1. 入门
利用 cpuid 取得 intel 集容cpu的厂商信息。
.section .data
output1:
.ascii "当前CPU厂商ID是:"
output2:
.ascii "xxxxxxxxxxxx\n"
.section .text
.global _start
_start:
movl $0,%eax
cpuid
movl $output2,%edi
movl %ebx,(%edi)
movl %edx,4(%edi)
movl %ecx,8(%edi)
movl $4,%eax
movl $1,%ebx
movl $output1,%ecx
movl $(output2-output1+14),%edx
int $0x80
movl $1,%eax
movl $0,%ebx
int $0x80
编译和链接上面的源代码并运行:
as -o cpuid.o cpuid.s # 汇编源代码为目标文件 ld -o cpuid cpuid.o # 链接目标文件为linux可执行的文件 ./cpuid # 执行
1.1 基本gas程序模板
gas程序分为几个部分,通常有data段和text段。data段存放数据,其 内容会被放在最终的可执行程序中。text段是汇编指令。通常还有 bss段,这里相当于缓存,临时创建。
.section .data
...
.section .text
...
1.2 gas汇编程序伪指令
一般汇编程序本身会提供一些方便的"指令",但是这些"指令"不是 cpu指令,它们只是方便汇编程序开发者,解释权归汇编程序本身。
gas中的“伪指令”都是以 "." 开头,如上面示例中的一些伪指令:
.section 定义一个段 .data 同.section一起定义数据段 .ascii 定义一个字符串 .text 同.section一起定义程序段 .global 定义全局标签
1.3 gas的标签
在gas中的标签等价于一个内存地址,相当于C语言中的指针。不过C语 言的指针还有数据类型属性,这里的数据类型需要自己指定了。
gas中的标签是以冒号结尾的字符串。如示例中的:
output1: output2:
既然gas中的标签是内存地址,这是一个数值。那么,我们可以在gas 程序中对标签进行算术运算,且对于“立即数”能出现的地方,标签也 都可以出现:
movl $(output2-output1+14),%edx
1.4 gas的开始标签
如果用 ld 手动链接程序,则需要在汇编程序中设置一个为 _start: 的开始标签,相当于C程序中的main函数。如果用gcc编译汇编程序, 那么要将 "_start:" 换成 "main:"
如果没有指定 "_start" 标签,可以在运新 ld 命令的时候指定开始标签:
ld -e
1.5 gas中的系统调用
Linux下的汇编程序可以使用系统调用,这样可以避免很多复杂的工作。 在gas里使用系统调用就是在相关寄存器存入系统调用号、调用函数的 参数,再执行 "int $0x80" 命令就可以了。
1.6 基本 AT&T 汇编风格
- 立即数 表示立即数要用"$"符号
- 寄存器 表示寄存器要用"%"符号
- 内存寻址 内存地址要放在寄存器里,才能被寻址,这是intel的cpu指令集规定的。
movl %ebx,(%edi) # 将ebx里面的内容放到edi指向的内存地址单元处。 movl %edx,4(%edi) # 将edx里面的内容放到比edi指向的内存地址高4个值的内存单元处。
2. 使用标准C库函数
使用标准C库函数的 cpuid2.s 程序:
.section .data
output:
.asciz "当前CPU厂商信息是:%s \n"
.section .bss
.lcomm buffer,12
.section .text
.global _start
_start:
movl $0,%eax
cpuid
movl $buffer,%edi
movl %ebx,(%edi)
movl %edx,4(%edi)
movl %ecx,8(%edi)
pushl $buffer
pushl $output
call printf
addl $8,%esp
pushl $0
call exit
汇编、链接并运行:
as -o cpuid2.o cpuid2.s ld -dynamic-linker /lib/ld-linux.so.2 -o cpuid2 -lc cpuid2.o
参数说明:
-lc 链接C库/lib/libc.so;如果是-lx,默认链接/lib/libx.so。 -dynamic-linker /lib/ld-linux.so.2 使用/lib/ld-linux.so.2加载共享库。
3. 定义数据元素
3.1 定义数据元素的命令
.ascii 文本字符串 .asciz 带零结束符的文本字符串 .byte 字节值 .double 双精度值 .float 单精度值 .int 32位整数 .long 同.int .octa 16字节长度整数 .quad 8字节长整数 .short 16位整数 .single 同.float
有几种数据段:
.section .data 定义通常的数据段,数据被包含在最终程序中 .section .rodata 同.data,但是这里定义的数据的值不可修改 .section .bss 相当于缓冲
数据定义可以一个标签一个命令一个数据的定义:
output:
.asciz "这是一个.asciz定义的字符串"
value:
.int 100
也可以,一个标签一个命令定义一堆数据:
values:
.int 15,20,25,30,35,40,45,50,55,60
无论是哪种形式定义的,数据在内存中都是一个挨着一个的存放的。 这样我们可以用索引来引用它们。
3.2 赋值命令
gas中也可以用一个符号代表一个值,但是符号只不可修改:
.equ factor,3 .equ LINUX_SYS_CALL,0x80
3.3 bss段
这个段无须声明特定的类型,只要声明大小:
.comm 声明未初始化数据的通用内存区域 .lcomm 声明未初始化数据的"本地"通用内存区域
例如,声明一个1000字节的缓冲区,通过buffer引用这个区域的基址:
.section .bss .lcomm buffer,1000
3.4 .fill 命令
这个命令让汇编器自动创建一段内存区域,并用0填充:
.section .data
buffer:
.fill 1000
4. MOV命令
gas中把mov命令加了不同后缀,每个后缀表示操作不同的数据大小:
movl 32位 movw 16 movb 8
下面一个例子把一个值移到ecx寄存器,然后调用linux系统的exit正 常退出。用gdb可以看见寄存器的变化。
# 程序 movetest1.s
.section .data
value:
.int 1
.section .text
.global _start
_start:
nop
movl value,%ecx
movl $1,%eax
movl $0,%ebx
int $0x80
用-gtabs参数汇编程序并链接:
as -gtabs -o movetest1.o movetest1.s ld -o movetest1 movetest1.o
使用gdb调试程序:
root@jianlee:~/lab/asm# gdb -q movetest1 (gdb) break *_start+1 Breakpoint 1 at 0x8048075: file movetest1.s, line 8. (gdb) run Starting program: /root/lab/asm/movetest1 Breakpoint 1, _start () at movetest1.s:8 8 movl value,%ecx Current language: auto; currently asm (gdb) print/x %ecx A syntax error in expression, near `%ecx'. (gdb) print/x $ecx $1 = 0x0 (gdb) next _start () at movetest1.s:9 9 movl $1,%eax (gdb) print/x $ecx $2 = 0x1 (gdb) s _start () at movetest1.s:10 10 movl $0,%ebx (gdb) cont Continuing. Program exited normally. (gdb)
把寄存器的值传到内存里:
# movetest2.s 把寄存器的值传到内存中
.section .data
value:
.int 1
.section .text
.global _start
_start:
nop
movl $100,%eax
movl %eax,value
movl $1,%eax
movl $0,%ebx
int $0x80
调试程序,查看程序执行过程:
root@jianlee:~/lab/asm# as -gtabs -o movetest2.o movetest2.s movetest2.s: Assembler messages: movetest2.s:0: Warning: end of file not at end of a line; newline inserted root@jianlee:~/lab/asm# ld -o movetest2 movetest2.o root@jianlee:~/lab/asm# gdb -q movetest2 (gdb) break *_start+1 Breakpoint 1 at 0x8048075: file movetest2.s, line 9. (gdb) run Starting program: /root/lab/asm/movetest2 Breakpoint 1, _start () at movetest2.s:9 9 movl $100,%eax Current language: auto; currently asm (gdb) print/x $eax $1 = 0x0 (gdb) x/d &value 0x804908c <value>: 1 (gdb) s _start () at movetest2.s:10 10 movl %eax,value (gdb) print/x $eax $2 = 0x64 (gdb) x/d &value 0x804908c <value>: 1 (gdb) s _start () at movetest2.s:12 12 movl $1,%eax (gdb) x/d &value 0x804908c <value>: 100 (gdb) cont Continuing. Program exited normally. (gdb)
