gas学习
by
Jian Lee
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)original link:http://jianlee.ylinux.org/Computer/%E6%B1%89%E5%AD%97%E7%B3%BB%E7%BB%9F/gas%E5%AD%A6%E4%B9%A0.html
Linux系统调用
by
Jian Leewrite
eax 调用号4 ebx 将要写入的文件的文件描述符 ecx 需要写的字符串的内存起始地址 edx 需要写的字符串的长度 movl $4,%eax movl $1,%ebx movl $output,%ecx movl $14,%edx int $0x80这个例子是将内存地址为output长度为14字节的字符串写到标准输出 (linux下标准输出的文件描述符是1)。
exit
eax 1 ebx 退出状态数字 movl $1,%eax movl $0,%ebx int 0x80这个例子是返回退出状态号0。通常这代表程序正常结束。
MBR -- 主引导记录
by
Jian LeeMBR原理
结构
偏移值 内容 0000 MBR程序代码 01BE 分区表(4个分区,每个16字节) 01FE 结束标志(aa55)分区表结构
每个分区表都有16字节的大小。它的结构如下:
单位:字节 1 如果是引导分区,值为80H;如果不是,值为00H 2-4 该分区的起始扇区号 5 标志字节 05 扩展分区 82 Linux交换分区 83 ext3分区 0c fat32分区 6-8 该分区的终止扇区号 9-12 该分区已经使用的扇区数 13-16 该分区总共占用的扇区数Linux下备份修复mbr
备份
dd if=/dev/sda of=mbr.img bs=512 count=1修复
dd if=mbr.img of=/dev/sda bs=512 count=1修复分区表
dd if=mbr.img of=/dev/sda bs=512 skip=446 count=66