不区分用户空间和内核空间,很不安全
gcc -01 -o p p1.c
-01:表示使用第一级优化。通常提高优化级别会使最终程序运行得更快,但是编译时间可能会变长,用调试工具对代码进行调试会更困难。(实际中,第二级优化-02被认为是较好的选择)
gcc命令调用一系列程序将源代码转换成可执行代码:
- C预处理器 扩展源代码,插入#include命令指定的文件,扩展#define声明指定的宏。(.i)
- 编译器 产生两个源代码的汇编代码。(.s)
- 汇编器 将汇编代码转化成二进制目标代码(.o)
- 连接器 将两个目标代码与实现库函数的代码合并,并产生最终的可执行代码文件。
参考资料3:体系结构-指令集结构
计算机系统使用了多种不同形式的抽象,利用更简单的抽象模型来隐藏实现的细节。对于机器级编程来说,两种抽象尤为重要:
机器级程序的格式和行为,定义为 ISA :指令集体系结构。
ISA,定义了处理器状态,指令格式,以及每条指令对状态的影响。
处理器的硬件并发的执行许多指令,但是可以采取措施保证整体行为与ISA指定的顺序整形完全一致。
解决的问题:
指令的编码方式(即如何编码)
操作数和操作结构的存放位置
数据的类型和大小
支持哪些操作
下一条指令的地址
机器级程序使用的存储器地址是虚拟地址,提供的存储器模型看上去是一个非常大的字节数组。
一些通常对C语言程序员隐藏的机器代码在IA32中是可见的:
指示将要执行的下一条指令在存储器中的地址。
包含8个命名的位置,分别存储32位的数值,这些寄存器可以存储地址(对应C语言的指针)或整数数据,有的寄存器被用来记录某些重要的程序状态,其他的寄存器用来保存临时数据,例如过程的局部变量和函数的返回值。
保存着最近执行的算术或逻辑指令的状态信息,他们用来实现控制或数据流中的条件变化。
一组浮点寄存器存放浮点数据
一条机器指令只执行一个非常基本的操作
参考资料4:函数调用过程栈帧变化详解
获得汇编代码:
gcc -S xxx.c -o xxx.s
反汇编:
objdump -d xxx
汇编代码(函数前两条和后两条汇编代码,所有函数都有,建立函数调用栈帧):
前两条:
pushl %ebp 将寄存器%ebp的内容压入程序栈
movl %esp,%ebp 得到新栈低,将当前栈顶赋予栈低
后两条:
popl %ebp 过程调用结束,恢复旧栈低
ret 子程序的返回指令
注意:
二进制文件可以用od 命令查看,也可以用gdb的x命令查看。
有些输出内容过多,可以使用 more或less命令结合管道查看,也可以使用输出重定向来查看。
od code.o | more
od code.o > code.txt
字(word) 16位
双字(double words) 32位
四字(quad words) 64位
一个IA32的中央处理器单元包含一组8个存储32位数值的寄存器。所有八个寄存器都可以作为16位(字)或32位(双字)来访问。
存储器,根据计算出来的地址(有效地址)访问某个存储器位置。
有效地址的计算方式
Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s
参考资料5:汇编(五)——数据传送指令一
参考资料6:汇编(八)——数据传送类指令三
符号 意义
r8 任意一个8位通用寄存器AH/AL/BH/BL/CH/CL/DH/DL
r16 任意一个16通用寄存器AX/BX/CX/DX/SI/DI/BP/SP
reg 代表r8或r16
seg 段寄存器CS/DS/ES/SS
m8 一个8位存储器操作数单元(包括所有主存寻址方式)
m16 一个16位存储器操作数单元(包括所有主存寻址方式)
mem 代表m8或m16
i8 一个8位立即数
i16 一个16位立即数
imm 代表i8或i16
dest 目的操作数
src 源操作数
mov指令(指令的第一个是源操作数,第二个是目的操作数)
MOV reg/mem,imm 立即数送寄存器或是存储器
MOV reg/mem/seg,reg 寄存器送寄存器(包括段寄存器)或贮存
MOV reg/seg,mem 主存送寄存器(包括段寄存器)
MOV reg/mem,seg 段寄存器送主存或寄存器
IA32的限制:两个操作数都不能指向存储器。
不能从内存地址直接MOV到另一个内存地址,要用寄存器中转一下。
push与pop
栈向下增长,栈顶元素的地址是所有栈中元素地址中最低的。栈指针%esp保存栈顶元素的地址。
进栈指令PUSH:
PUSH reg/mem/seg SP←SP-2,SS←reg/mem/seg
- 进栈指令先使堆栈指令SP减2,然后把一个字操作数存入堆栈顶部。
- 堆栈操作的对象只能是字操作数。
- 进栈时,底字节存放于低地址,高字节存放在高地址,SP相应向低地址移动两个字节单元。
出栈指令POP:
POP reg/seg/mem reg/seg/mem←SS:[SP],SP←SP+2
- 出栈指令把栈顶的一个字传送至指定的目的操作数,然后堆栈指针SP加2。
- 目的操作数应为字操作数。
- 字从栈顶弹出时,低地址字节送低字节,高地址字节送高字节。
是movl指令的变形
一元操作
- INC 加1
- DEC 减1
- NEG 取负
- NOT 取补
二元操作
- ADD 加
- SUB 减
- IMUL 乘
- XOR 异或
- OR 或
- AND 与
注意操作的顺序:
第二个操作数 操作符 第一个操作数
先给出移位量,第二项给出要移位的数值。
- SAL 左移
- SHL 左移(等同于SAL)
- SAR 算术右移
- SHR 逻辑右移
目的操作数:一个寄存器或是一个存储器位置。
乘积截断
imull 双操作数
- 从两个32位操作数产生一个32位的乘积。
乘积不截断
mull 无符号数乘法
imull 有符号数乘法
- 要求一个参数必须在寄存器%eax中,另一个作为指令的源操作数给出。
- 乘积的高32位在%edx中,低32位在%eax中。
有符号除法
idivl 操作数
- 将DX:AX中的64位数作为被除数,操作数中为除数
- 结果:商在AX中,余数在DX中。
无符号除法
divl指令
- 通常会事先设定寄存器%edx为0.
常用条件码:
比较指令cmp和减法指令sub有何不同?
- sub d,s 是d-s,结果送回d中,即送回目的操作数中。
- cmp d,s 也是d-s,但结果不送回目的操作数中,是利用减法进行两个数值的比较。
间接跳转:跳转目标是从寄存器或存储器位置中读出的。
例:
jmp *%eax 用寄存器%eax中的值作为跳转目标。
jmp *(%eax) 以%eax中的值作为读地址,从存储器中读出跳转目标。
一些底层的机器指令有多个名字,条件跳转只能是直接跳转。
参考资料7:20135202闫佳歆——信息安全系统设计基础第四周学习总结
通用形式模板
if(test-expr)
then-statement
else
else-statement
(注:test-expr 整数表达式[假/真])
汇编实现形式
t = test-expr;
if (!t)
goto false;
then-statement
goto done;
false:
else-statement
done:
通用形式:
do
body-statement
while(test-expr);
翻译成如下条件和goto语句:
loop:
body-statement
t = test-expr;
if(t)
goto loop;
通用形式:
while (test-expr)
body-statement
转换成 do-while 形式:
if(!test-expr)
goto done;
do
body-statement
while(test-expr);
done:
翻译成 goto 形式:
t = test-expr;
if(!t)
goto done:
loop:
body-statement
t = test-expr;
if(t)
goto loop;
done:
通用形式
for(init-expr;test-expr;update-expr)
body-statement
同 while:
init-expr;
while(test-expr){
body-statement
update-expr;
}
对应 do-while 形式:
init-expr;
if(!test-expr)
goto done;
do{
body-statement
update-expr;
}while(test-expr);
done;
转换成 goto 代码:
init-expr
t = test-expr;
if(!t)
goto done:
loop:
body-statement
t = test-expr;
if(t)
goto loop;
done:
栈帧: 为单个过程分配的那部分栈
最顶端的栈帧以两个指针界定:
- 寄存器%ebp为帧指针
- 寄存器%esp为栈指针
栈向低地址方向增长,栈指针%esp指向栈顶元素:
- 栈指针值适当减小可以分配没有指定初始值的数据的空间
- 类似的,可以通过增加栈指针来释放空间
支持过程调用和返回的指令:
效果:将返回地址入栈,并跳转到被调用过程的起始处。
使用这个指令时,栈指针要指向前面call指令存储返回地址的位置。
等价于:
movl %ebp,%esp
popl %ebp
惯例:
- %eax,%edx,%ecx 划分为调用者保存寄存器
- %ebx,%esi,%edi 划分为被调用者保存寄存器
- %ebp,%esp 保持寄存器
- %eax 保存函数返回值
main函数保存%ebp,并设置新的帧指针。
pushl %ebp
movl %esp,%ebp
分配4字节的栈空间
subl $4,%esp
设置 arg1=8
movl $8,(%esp)
将(%esp)中的8给 %eax,即存入栈中
movl %eax,(%esp)
将 %eax 与立即数 3 相加
add $3,%eax
在gh结束前弹栈
popl %ebp
main继续 %eax 加1的操作
addl $1,%eax
leave为返回准备栈,相当于%ebp出栈,最后ret结束。
参考资料1:深入理解计算机系统(第二版)
参考资料2:教材导读与每周考试重点---不断更新
参考资料3:体系结构-指令集结构
参考资料4:函数调用过程栈帧变化详解
参考资料5:汇编(五)——数据传送指令一
参考资料6:汇编(八)——数据传送类指令三
参考资料7:20135202闫佳歆——信息安全系统设计基础第四周学习总结