汇编基础
高级语言接口
大多数程序员不会用汇编语言写大的程序,因为这相当的花费时间。反之,高级语言会隐藏一些细节,开发效率更高。汇编语言广泛用于配置硬件驱动器,以优化程序速度和代码量
通用规范
-
调用规范(call convention)是指调用过程的底层细节。下面列出需要考虑的细节信息:
- 调用过程需要保存哪些寄存器
- 传递参数的方法:用寄存器,用堆栈,共享内存或其他方法
- 主调程序调用过程时参数传递的顺序
- 参数传值还是传引用
- 过程调用后,如何恢复堆栈指针
- 函数如何向主调程序返回结果
-
命名规范与外部标识符
当从其他语言程序中调用汇编过程时,外部标识符必须与命名规范兼容。外部标识符(external identifier)是放在模块目标文件中的名称,链接器使得这些名称能被其他程序模块使用。 -
段名称
汇编语言过程与高级语言程序连接时,段名称必须是兼容的。本章使用的简化段指令都与Microsoft C++编译器生成的段名称兼容 -
内存模式
主调程序与被调程过程使用的内存模式必须相同
Q:实地址模式下可以选择那些内存模式?
K:.model伪指令确定若干重要的程序特性:内存模式类型,过程命名模式,参数传递规则
A:有微模式,小模式,中模式,大模式,紧凑模式,巨模式,平坦模式,值得一提的是:平坦模式也是保护模式,代码与数据使用32位偏移量。所有的数据和代码(包括系统资源)都在一个32位段内。
Q:使用STDCALL语言说明符的汇编语言过程可以与C++程序链接
A:F,STDCALL用于Windows的系统函数调用,C语言说明符才是可以与C/C++链接的过程
K:需要注意,C说明符将从堆栈中移除参数的任务交给了主调方,C语言说明符在外部过程名的前面添加前导下滑线,如_AddTwo
内嵌汇编代码
Q:内嵌汇编代码与内嵌C++过程有什么不同之处?
A:内嵌汇编代码是指的直接插入高级语言程序的汇编源代码,而C++内嵌限定符则要求C++汇编器直接把函数体插入程序的编译代码,以便清除函数调用和返回时所耗费的时间。
Q:与使用外部汇编过程相比,内嵌汇编代码有什么优势?
A:简单,因为不需要考虑外部链接,命名以及参数传递协议
Q:请至少给出两种在内嵌汇编代码中添加注释的方法
A:建议不要使用汇编风格的注释,选择使用
/ xxxx /
/* xxxxx */
Q:内嵌语言是否可以引用__asm模块外的代码
A:可以
K:
编写内嵌汇编代码时允许:
- 使用x86指令集的大多数指令
- 使用寄存器名作为操作数
- 通过名字引用函数参数
- 引用asm块外定义的代码标号和变量。(这点很重要,因为局部变量必须在asm块的外面)
- 在语句中使用PTR,EVEN(使下一个变量或指令开始于偶数字节地址),ALIGN(双字对齐)
不允许: - 使用数据定义伪指令,如DB(BYTE)和DW(WORD)
- 使用汇编运算符(PTR除外)
- 使用STRUCT,RECORD,WIDTH,MASK
- 使用宏伪指令,以及宏运算符(<>,!,&,%)
虽然不能使用OFFSET运算符,但可以用lea指令获取变量的偏移值
lea esi, buffer
实例:
使用汇编加密
1 |
|
嵌入的汇编语言写在头文件里,加密方法就是让字符每一位与输入的数字异或
1 | void TranslateBuffer(char *buf, |
我们在命令行里运行
encode infile.txt encoded.txt
我们反汇编看到一些东西
1 | 000D7D80 push ebp |
编译器自动插入了一些语句用于设置EBP以及保存标志寄存器集合,集合内的寄存器不论是否被修改,总是会被保存
32位汇编程序与C/C++的链接
要想调用,需要在汇编源码中.MODEL伪指令中指定调用C的规范,而且要为每个调用外部的过程创建一个原型
1 | .586 |
在C程序中,声明外部汇编过程时要使用extern限定符,如果过程会被C++调用,则要添加“C”限定符,防止C++的名称修饰
1 | extern long IndexOf(long n, long array[], unsigned count); |
名称修饰(name decoration)就是一种标志C++编译技术,通过添加字符来修改函数名,添加的字符指明了每个函数参数的确切类型
详细的配置可以看我们的实验
调用C和C++的函数
函数原型
可以编写程序来调用C和C++函数,这样做的理由至少有:
- C和C++有丰富的输入输出库,因此有更大的灵活性,处理浮点数时,这相当有用
- 两种语言都有丰富的数学库
需要注意:调用标准C库或C++库,必须从C或C++的main()过程启动程序,以便运行库初始化代码
汇编语言调用的C++函数必须使用“C”和关键字extern,推荐不是修改每个函数的定义,而是把多个函数原型放在一个块中显得更容易
1 | extern "C"{ |
汇编语言模块
如果汇编语言模块要调用Irvine32链接库,就要使用.model flat, stdcall
,但这和c的调用规范不符(要用c规范),这时声明原型必须给PROTO伪指令加上C限定符
1 | INCLUDE Irvine32.inc |
当然如果不调用Irvine库,则全局声明为C规范即可.model flat, C
,PROTO就不用再添加C限定符
仍需要知道,Microsoft Visual C++函数怎样返回数值:
- bool和char的值用AL返回
- short,int值用AX返回
- int和long int值用EAX返回
- 指针用EAX返回
- float,double,long double的值分别以4,8,10字节值压入浮点堆栈
调用C库(Standard Library)函数
printf
C的原型
1 | int printf( |
汇编的等效原型
1 | printf PROTO C,format:PTR BYTE, args:VARARG |
可变长度参数类型为vararg
如何调用printf?
1 | ;显示双精度 |
需要注意的是:格式化字符串不是插入转义字符,如\n,必须插入ASCII字符(0dh,0ah)
写在后面
至此,基础基本完成,庆祝一下自己坚持看完了这本400+的书,习题会补上。