[关闭]
@perkyoung 2015-07-12T14:36:09.000000Z 字数 5118 阅读 3230

汇编语言-2

汇编语言


使用数字

整数

无符号整数

都是正数,所有的位都表示数字,例如0XFFFF,表示一个正数,但在gdb的时候会输出其补码,也就是负数。

有符号正数

最高为如果是1,表示负数,-16的补码表示方法:取16的二进制值,取反,加1

扩展整数

如果我们只用长度较小的无符号整数赋值给长度较大的。注意要将后者的高位清零。

  1. movl $0, %ebx
  2. movl %cl, %ebx

很麻烦,所以intel提供了一个指令,movzx %cl, %ebx后者的高位自动清零

如果扩展的是有符号整数,则不能将长度较大的寄存器的高位清零,因为最高为表示符号。intel提供了

  1. movw $-79, %cx
  2. movl $0, %ebx //下面两行显然是错了,因为%ebx显然失去了负数的属性
  3. movw %cx, %bx
  4. movsx %cx, %eax //这个是ok的,据算cx是整数,也会正确

如果想显示8字节整数x/5gd

二进制编码的是进制

浮点数

以后再看

基本数学功能

这个需要看,包括溢出,进位等标志的操作

高级数学功能

暂时略过

处理字符串

看完后面的函数之后在回来看,不着急

使用函数

定义函数

输入

定义函数处理

  1. .type func1, @function
  2. funcl:
  3. <instruction....>
  4. ret

定义输出

  1. .section .data
  2. precision:
  3. .byte 0x7f, 0x00
  4. .section .bss
  5. .lcomm radius, 4
  6. .lcomm result, 4
  7. .lcomm trach, 4
  8. .section .text
  9. .globl main
  10. main:
  11. nop
  12. finit
  13. fldcw precision
  14. movl $10, radius
  15. call area
  16. movl $2, radius
  17. call area
  18. movl $120, radius
  19. call area
  20. movl $1, %eax
  21. movl $0, %ebx
  22. int $0x80
  23. .type area, @function
  24. area:
  25. fldpi
  26. filds radius
  27. fmul %st(0), %st(0)
  28. fmulp %st(0), %st(1)
  29. fstps result
  30. ret

但是,寄存器,就那么几个,全局变量搞得太多头疼,函数调用却很多很多,显然不够用。如果真这么写的话,绝对每天晚上睡不着觉。

使用堆栈

经典程序堆栈
程序堆栈

命令行参数

虚拟内存地址空间,0X80480000~0XBFFFFFFF
虚拟内存空间
想当然的,程序一启动,esp就指向了0XBFFFFFFF,其实不是,在加载程序之前,linux就把一些内容存放到堆栈中,命令行参数就是如此

分析命令行堆栈

  1. #include <stdio.h>
  2. int main(int argc, char * argv[]) {
  3. int c = argc;
  4. char * p = argv[1];
  5. return 0;
  6. }

反汇编的结果是

  1. 08048354 <main>:
  2. 8048354: 55 push %ebp
  3. 8048355: 89 e5 mov %esp,%ebp
  4. 8048357: 83 ec 10 sub $0x10,%esp
  5. 804835a: 8b 45 08 mov 0x8(%ebp),%eax //0x4(%ebp)是返回地址,这个就是第一个参数
  6. 804835d: 89 45 fc mov %eax,-0x4(%ebp)//赋值给函数内的临时变量
  7. 8048360: 8b 45 0c mov 0xc(%ebp),%eax //第二个参数是包含多个指针的数组
  8. 8048363: 83 c0 04 add $0x4,%eax //指向了该数组的第2个(如果下标为0的是第一个的话),
  9. 8048366: 8b 00 mov (%eax),%eax //取出该内存地址的值,该值是指向一个字符串的指针
  10. 8048368: 89 45 f8 mov %eax,-0x8(%ebp) //赋值给第二个临时变量
  11. 804836b: b8 00 00 00 00 mov $0x0,%eax
  12. 8048370: c9 leave

使用linux系统调用

先跳过

高级汇编技术

基本的内联汇编代码

  1. #include <stdio.h>
  2. int main(){
  3. int i = 100;
  4. char * output = "the result is %d";
  5. asm("nop\n\t"
  6. "movl $0, %eax\n\t"
  7. "movl $2, %ebx\n\t"
  8. "int $0x80");
  9. }

编译出的汇编代码是

  1. main:
  2. pushl %ebp
  3. movl %esp, %ebp
  4. subl $16, %esp
  5. movl $100, -4(%ebp)
  6. movl $.LC0, -8(%ebp)
  7. #APP //从这里到后面的NO_APP
  8. # 6 "./test.c" 1
  9. nop
  10. movl $0, %eax
  11. movl $2, %ebx
  12. int $0x80
  13. # 0 "" 2
  14. #NO_APP
  15. pushl $100
  16. pushl $.LC0
  17. call printf
  18. addl $8, %esp
  19. leave

使用全局变量

  1. #include <stdio.h>
  2. int a = 10;
  3. int b = 20;
  4. int result;
  5. int main() {
  6. asm("pusha\n\t"
  7. "movl a, %eax\n\t"
  8. "movl b, %ebx\n\t"
  9. "imull %ebx, %eax\n\t"
  10. "movl %eax, result\n\t");
  11. printf("the answer is %d\n", result);
  12. return 0;
  13. }

对应点汇编代码是

  1. 27 #APP
  2. 28 # 7 "./globaltest.c" 1
  3. 29 pusha //内部代码使用了这些寄存器,最好压栈
  4. 30 movl a, %eax //如果objdump -D ./a.out ,a表示的是内存地址,而不是一个立即数
  5. 31 movl b, %ebx
  6. 32 imull %ebx, %eax
  7. 33 movl %eax, result
  8. 34
  9. 35 # 0 "" 2
  10. 36 #NO_APP

使用volatile修饰符,避免编译器优化

  1. asm volatile ("nop");
  2. __asm__ __volatile__("");

扩展asm

到目前,所有的输入值和输出值都必须使用都必须使用C的全局变量,下面要解决这个问题

格式

  1. asm ("assembly code" : output location : input operands : chagned registers)

使用寄存器

  1. int main() {
  2. int data1 = 10;
  3. int data2 = 20;
  4. int result;
  5. __asm__("imull %%edx, %%ecx\n\t" //寄存器要多一个%
  6. "movl %%ecx, %%eax\n\t"
  7. : "=a"(result) //=表示只能写入result
  8. : "d"(data1), "c"(data2));
  9. printf("the result is %d\n", result);
  10. return 0;
  11. }

不一定总要在内联汇编中指定输出值

  1. #include <stdio.h>
  2. int main() {
  3. char input[30] = "this is a test message.\n";
  4. char output[30];
  5. int length = 25;
  6. asm volatile ("cld\n\t"
  7. "rep movsb"
  8. :
  9. : "S"(input), "D"(output), "c"(length)); //output同时也是输出,movsb指令负责复制字符串,没有定义专门的输出值,所以,volatile很重要,编译器不会误以为它没必要,而删除它。
  10. printf("%s", output);
  11. return 0;
  12. }

使用占位符

前面的例子中,我们将变量赋值给寄存器,但是如果变量特别多的话,这样编程会很麻烦,所以利用占位符。使用占位符引用输入输出。占位符就是前面加%的数字,引用的是输入输出值在asm中出现的位置,从0开始

  1. int main() {
  2. int data1 = 10;
  3. int data2 = 20;
  4. int result;
  5. __asm__ ("imull %1, %2\n\t"
  6. "movl %2, %0"
  7. : "=r"(result)
  8. : "r"(data1), "r"(data2));
  9. printf("the result is %d\n", result);
  10. return 0;
  11. }
  12. 有兴趣可以看下对应的汇编代码
  13. #APP
  14. # 7 "./reg.c" 1
  15. imull %edx, %eax
  16. movl %eax, %eax //其实已经算好了,结果我们的asm代码又一次赋值
  17. # 0 "" 2
  18. #NO_APP

引用占位符

从上面的例子中可以看到,有时候使用相同的变量作为输入和输出值是很不错的。

  1. int main() {
  2. int data1 = 10;
  3. int data2 = 20;
  4. int result;
  5. __asm__ ("imull %1, %0\n\t"
  6. : "=r"(data2) //data2输出为%0寄存器
  7. : "r"(data1), "0"(data2)); //输入data2,指定到%0,
  8. printf("the result is %d\n", data2);
  9. return 0;
  10. }

替换的占位符

如果输入输出变量多,用0,1,2表示就会很混乱,可以用替换的占位符

  1. int main() {
  2. int data1 = 10;
  3. int data2 = 20;
  4. int result;
  5. __asm__ ("imull %[value1], %[value2]\n\t"
  6. : [value2]"=r"(data2)
  7. : [value1]"r"(data1), "0"(data2));
  8. printf("the result is %d\n", data2);
  9. return 0;
  10. }

改动的寄存器列表

还记得扩展asm格式吗?对,最后一个是改动的寄存器列表,下面谈谈

  1. int main(){
  2. int data1 = 10;
  3. int result = 20;
  4. asm ("addl %1, %0\n\t"
  5. : "=d"(result)
  6. : "c"(data1) ,"0"(result)
  7. : "edx"); //这里告诉编译器,edx改动了,其实这个明显是废话,因为赋值的时候就用到了edx作为输出的。所以这里是编译不过去的
  8. printf("result is %d\n", result);
  9. return 0;
  10. }

正确用法

  1. int main(){
  2. int data1 = 10;
  3. int result = 20;
  4. asm ("movl %1, %%eax\n\t"
  5. "addl %%eax, %0"
  6. : "=r"(result)
  7. : "r"(data1) ,"0"(result)
  8. : "eax"); //eax不是作为输入输出的,是在汇编代码中改动的,所以要写出来,一定要写出来。
  9. printf("result is %d\n", result);
  10. return 0;
  11. }

使用内存位置

不用寄存器,直接引用内存位置

  1. int main() {
  2. int dividend = 20;
  3. int divisor = 5;
  4. int result;
  5. asm("divb %2\n\t"
  6. "movl %%eax, %0"
  7. : "=m"(result)
  8. : "a"(dividend), "m"(divisor));
  9. printf("result is %d\n", result);
  10. return 0;
  11. }

使用内联汇编宏

C宏函数

  1. 格式:#define NAME(input value, output value) (function)
  2. #include <stdio.h>
  3. #define SUM(a,b,result) \
  4. ((result) = (a) + (b))
  5. int main() {
  6. int data1 = 5, data2 = 10;
  7. int result ;
  8. SUM(data1, data2, result);
  9. printf("%d\n", result);
  10. float fdata1 = 5.0, fdata2 = 10.0;
  11. float fresult;
  12. SUM(fdata1, fdata2,fresult);
  13. printf("%f\n", fresult);
  14. return 0;
  15. }

内联汇编宏函数

  1. #define GREATER(a, b, result)({ \
  2. asm("cmp %1, %2\n\t"\
  3. "jge 0f\n\t"\
  4. "movl %1, %0\n\t" \
  5. "jmp 1f\n\t"\
  6. "0:\n\t" \
  7. "movl %2, %0\n\t"\
  8. "1:"\
  9. :"=r"(result)\
  10. :"r"(a), "r"(b));})
  11. int main() {
  12. int data1 = 20, data2 = 10;
  13. int result;
  14. GREATER(data1, data2, result);
  15. printf("a = %d, b = %d result: %d\n", data1, data2, result);
  16. data1 = 30;
  17. GREATER(data1, data2, result);
  18. printf("a = %d, b = %d result: %d\n", data1, data2, result);
  19. return 0;
  20. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注