[关闭]
@perkyoung 2015-07-12T14:36:22.000000Z 字数 5107 阅读 2370

汇编语言-1

汇编语言


汇编基础

寄存器

通用寄存器

  1. EAX,操作数和结果数据的累加器
  2. EBX,指向数据内存段中的数据的指针
  3. ECX,字符串和循环操作的计数器
  4. EDX,字的乘除运算,间接的输入输出
  5. EDI,存储器指针,字符串操作的目标数据的指针
  6. ESI,存储器指针,字符串操作的源数据的指针
  7. ESP,栈顶指针
  8. EBP,存取堆栈指针,存储器指针

段寄存器

  1. CS,代码段,结合EIP获取内存中的指令码,程序不能改变CS寄存器
  2. DS,数据段
  3. SS,堆栈段
  4. ESFSGS为附加段指针

指令指针寄存器

  1. EIP,程序不能直接修改指令指针本身。可以通过跳转等指令去修改

控制寄存器

  1. CR0,...CR4,不能直接取控制寄存器中的值,可以将控制寄存器中的值传给通用寄存器。如果想给控制寄存器赋值,可以先将值赋给通用寄存器,在赋值。

标志

状态标志

控制标志


相关工具

开发工具

汇编器
将汇编代码翻译成机器代码的程序,不同硬件平台的汇编器不同

连接器
如果有调用外部函数,则需要链接器找到该函数的位置,并且生成目标代码

调试器
gdb等工具

反汇编器

GNU汇编器

binutils工具

  1. addr2line 把地址转换成文件名和行号
  2. ar 创建、修改和展开文件存档
  3. as 把汇编语言代码汇编成目标代码
  4. c++filt 还原C++符号的过滤器
  5. gprof
  6. ld
  7. nm 列出目标文件的符号
  8. objcopy 复制和翻译目标文件
  9. objdump
  10. ranlib 生成存档文件内容的索引
  11. readelf 按照elf格式显示来自目标文件的索引
  12. size 列出目标文件或存档文件的段长度
  13. strings 显示目标文件中的可打印字符串
  14. strip 丢弃符号

汇编语言程序范例

程序示例

cpuinfo.s

  1. .section .data
  2. output:
  3. .ascii "the processor Vendor ID is 'xxxxxxxxxxxx'\n"
  4. .section .text
  5. .globl _start
  6. _start:
  7. movl $0, %eax
  8. cpuid //该程序通过寄存器传参
  9. movl $output, %edi
  10. movl %ebx, 28(%edi)
  11. movl %edx, 32(%edi)
  12. movl %ecx, 36(%edi)
  13. movl $4, %eax
  14. movl $1, %ebx
  15. movl $output, %ecx
  16. movl $42, %edx
  17. int $0x80
  18. movl $1, %eax
  19. movl $0, %ebx
  20. int $0x80

汇编,链接

  1. as -o cpuid.o cpuid.s -gstabs
  2. ld -o cpuid cpuid.o

调试

  1. break * _start+offset //停在某一个标签,但是貌似不支持+offset
  2. info registers 显示所有寄存器的值
  3. print 查看变量,或寄存器的值 print/d十进制, print/x十六进制,print/t二进制,print/d $eax
  4. x 查看特定内存位置的值,x/n[cdx]输出格式[bhw]每个字段的长度,x/

gdb

  1. next n
  2. setp n
  3. nisi单步执行机器码,可疑通过x/i $pc 查看机器码
  4. finish //运行完当前函数
  5. until //将循环运行完毕
  6. break 19 if $eax==11 //当eax为11,在19行下断点
  7. break 20
  8. condition 1 $eax==10 //编号为1的断点,只有在eax==10的情况下才有效
  9. condition 1 //取消编号为1的断点的条件
  10. ignore 1 2 //忽略编号为1的断点2次
  11. watch expression 该变量或表达式发生变化时停止
  12. rwatch expression 。。。。。被读时候停止
  13. awatch expression。。。。。被读或被写时候停止
  14. clear 清理在某行或者某函数上的停止点,操作对象是行号或这函数filename:function, filename:linenum
  15. delete 删除停止点,操作对象是断点编号
  16. disable 置为不可用,但是并未删除,操作对象是编号
  17. enable 置为可用,操作对象是编号
  18. 针对断点2运行命令
  19. conditions 2
  20. print $eax
  21. end

汇编使用C库函数

printf

  1. .section .data
  2. output:
  3. .asciz "The processor Vendor ID is '%s'\n"
  4. .section .bss
  5. .lcomm buffer, 12 //不需要定义缓冲区的值,所以bss段。类似C中全局为初始化的变量
  6. .section .text
  7. .globl _start
  8. _start:
  9. movl $0, %eax
  10. cpuid
  11. movl $buffer, %edi //标签前加$,表示地址。
  12. movl %ebx, (%edi)
  13. movl %edx, 4(%edi)
  14. movl %ecx, 8(%edi)
  15. pushl $buffer
  16. pushl $output
  17. call printf
  18. addl $8, %esp
  19. pushl $0
  20. call exit

执行以上程序需要gcc 4.5.1版本,高版本的4.8.1不支持变量到寄存器的传递,反过来亦是如此


传送数据

定义数据

数据类型

.octa十六字节整数,.quad 八字节整数,.short 十六位整数,.single单精度浮点数

数据段

  1. .section .data
  2. msg:
  3. .ascii "dfdsfas"
  4. factors:
  5. .double 34.3 , 343.3, 343.5
  6. height:
  7. .int 64
  8. length:
  9. .int 634

bss,无需声明类型,内存中会保留这块长度,原是内容不变。好处是数据不会被包含在可执行文件中,该内存区域被保留在运行时使用,但数据段会包含在可执行文件中,因为会用某一个值去初始化它。

  1. .section .bss
  2. .comm buffer, 10 全局
  3. .lcomm output, 20 局部

传送数据

寄存器之间传递

  1. movx source, destination
  2. movl %eax, %ebx
  3. movw %ax, %bx
  4. movb %al, %bl

变量与寄存器

  1. .section .data
  2. value:
  3. .int 100
  4. movl value, %eax //4.8.1不支持这种赋值,反过来亦是如此
  5. movl %eax, value

使用变址的内存位置

表达是格式:base_address (offset_address, index, size),
获取的地址是,base_address + offset_address + index * size, offset_address,index必须是寄存器

  1. .section .data
  2. output:
  3. .asciz "the value is %d\n"
  4. values:
  5. .int 9,8,7,6,5,4,3,2,1,0
  6. .globl main
  7. main:
  8. movl $0, $edi //刚开始,用到了 %ecx 寄存器,发生段错误,经调试,是因为,每次调用完printf,会有值覆盖%ecx寄存器。切记:%eax,%ebx,%ecx,%edx是函数返回值经常覆盖的寄存器
  9. loop:
  10. movl values(,$edi, 4), %eax //反过来赋值也可以
  11. pushl $edi
  12. pushl $output
  13. call printf
  14. addl $8, %esp //退栈顶
  15. inc %edi
  16. cmpl $10, %edi
  17. jne loop
  18. movl $1, %eax
  19. movl $0, %ebx
  20. int $0x80

使用寄存器间接寻址

  1. .section .data
  2. values:
  3. .int 2,4,5,6,7,865,3
  4. .section .text
  5. .globl main
  6. main:
  7. movl values, %eax //将第一个数字存入寄存器
  8. movl $values, %edi //将地址存入寄存器
  9. movl $100, 4(%edi) //加上括号后表示寄存器内的地址,这个表示后面的第四个字节,注意是第四个字节地址啊,也可以表示为,values(,4,1)
  10. movl $1, %edi
  11. movl values(,%edi,4), %ebx
  12. movl $1, %eax
  13. int $0x80

条件传送指令

  1. 找出数组中的最大值
  2. .section .data
  3. values:
  4. .int 4,6,8,45,2,44,678,11
  5. output:
  6. .asciz "the largest num is %d\n"
  7. .section .text
  8. .globl main
  9. main:
  10. movl values, %eax
  11. movl $1, %edi
  12. loop:
  13. movl values(,%edi,4), %ebx
  14. cmpl %eax, %ebx //第二个减去第一个,并会设置EFLAGS的值
  15. cmova %ebx, %eax //如果上面的cmpl结果为大于,则传输ebx到eax,注意,cmp和cmova后面寄存器的顺序正好相反
  16. inc %edi
  17. cmpl $7, %edi
  18. jne loop
  19. pushl %eax
  20. push $output
  21. call printf
  22. addl $8, $esp
  23. pushl $0
  24. call exit

数据交换指令

  1. XCHG 交换寄存器之间,或寄存器和内存之间的数据
  2. BSWAP 反转数据,不是32位反转,是4个字节进行反转,也就是大端小端之间的转换
  3. XADD 交换两个值(寄存器之间或寄存器与内存),并且将和加载到目的寄存器或内存位置

堆栈

堆栈示意图

堆栈示意图
栈的增长是由高地址向低地址增长,如图有新数据0X00000012入栈,小端法高位在高地址,低位在低地址,0X12在低地址

压入弹出所有寄存器

  1. PUSHA/POPA //压入弹出16位寄存器
  2. PUSHAD/POPAD //压入弹出32位寄存器
  3. PUSHF/POPF //压入弹出低16位eflags寄存器
  4. PUSHFD/POPFD //压入弹出全部32位寄存器

控制流程

无条件分支

跳转

jmp location //location表示要跳转到内存地址,被声明为标签
短跳转:128字节范围内
远跳转:分段模式下的跳转
近跳转:其他所有的跳转

  1. .section .text
  2. .globl main
  3. main:
  4. movl $1, %eax //$eip始终指向下一条指令的地址,也就是马上要执行的指令的地址
  5. jmp overhere //jmp指令,修改了eip,使他指向了overhere标签下的movl $20, %ebx,但是该指令还没有执行,jmp仅仅修改了修改了%eip,可以objdump -D 查看一下内存地址
  6. movl $0, %ebx
  7. int $0x80
  8. overhere:
  9. movl $20, %ebx
  10. int $0x80

调用

当执行call指令时,返回地址会添加到堆栈中

  1. .section .data
  2. output:
  3. .asciz "this is section %d\n"
  4. .section .text
  5. .globl main
  6. main:
  7. pushl $1
  8. pushl $output
  9. call printf
  10. addl $8, %esp //前面入栈两次,所以要恢复栈顶
  11. call overhere //在调用call之后,返回地址(pushl $3的内存地址)会压入栈中
  12. pushl $3
  13. pushl $output
  14. call printf
  15. add $8, %esp
  16. pushl $0
  17. call exit
  18. overhere:
  19. pushl %ebp //首先要保存%ebp,在这个例子中体现的不明显,如果是多层函数的调用,那上层函数的%ebp是非常有用的,但是这层函数又需要把当前的栈顶赋值给%ebp,所以要保存上层函数需要的%ebp,一会儿会恢复
  20. movl %esp, %ebp //为什么要将栈顶赋值给%ebp,因为在该函数内部有可能会有临时变量也会入栈,%esp会改变,所以首先保存esp的值,当函数内部再入栈临时数据的时候,esp会变化,变化之后,是偶那个该esp就可以了。
  21. pushl $2
  22. push $output
  23. call printf
  24. addl $8, %esp
  25. movl %ebp, %esp //恢复%esp
  26. popl %ebp //恢复%ebp
  27. ret //ret执行之后,当初调用call入栈的地址会出栈

中断

软件中断 ,如int $0x80
硬件中断

条件分支

不总是执行,取决于eflags的状态。下面的跳转常常把标签转化为偏移地址,所以条件跳转不支持分段模式下的远跳转,如果想跳转,则需判断,再进行无条件跳转
eflags

模仿高级条件分支

  1. movl $3, %ecx
  2. loop1:
  3. <instruction ....> //这里的函数容易有坑,比如printf函数,运行过程中会使用ecx。所以在使用call的时候要格外小心
  4. loop loop1 //执行这条指令之后,ecx自动减1

if,for

在反汇编代码中常常包括

  1. pushl %ebp
  2. pushl %esp, %ebp
  3. andl $-16, %esp //初步认为是内存对齐,esp指向的内存地址是16的倍数,-16二进制表示的后四位是0000
  4. subl $32, %esp //为该函数的临时变量开辟斩空间

优化分支

消除分支

  1. movl value, %ecx
  2. cmpl %ebx, %ecx
  3. cmova %ecx, %ebx //避免了分支

略去一大部分,关于优化的,回头再仔细研究

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注