程序的转换与机器级表示 主要教学目标 了解高级语言与汇编语言、汇编语言与机器语言之间的关系

Slides:



Advertisements
Similar presentations
程序的转换与机器级表示 主要教学目标 了解高级语言与汇编语言、汇编语言与机器语言之间的关系
Advertisements

C语言程序设计 主讲教师 :张群燕 电话:
第4章 條件判斷與迴圈 Java 2 程式設計入門與應用.
第一章 C语言概述 计算机公共教学部.
编译原理上机实习
计算机硕士专业基础—C语言 赵海英
程序的转换与机器级表示 主要教学目标 了解高级语言与汇编语言、汇编语言与机器语言之间的关系
第三章 控制结构.
C语言程序设计 第八章 函数.
EBNF与操作语义 请用扩展的 BNF 描述 javascript语言里语句的结构;并用操作语义的方法描述对应的语义规则
C语言程序设计 第十二章 位运算.
C#程序设计基础 $5 流程控制.
Class 2 流程控制-選擇敘述與迴圈.
第3章 C 語言的基本知識.
第六章 运行时存储空间的组织和管理 术语 本章内容 讨论一个活动记录中的数据布局 程序执行过程中,所有活动记录的组织方式 过程的活动
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
2 C++ 的基本語法和使用環境 親自撰寫和執行程式是學好程式語言的不二法門。本章藉由兩個簡單的程式,介紹C++ 程式的基本結構和開發環境,讓初學者能逐漸建立使用C++ 的信心。
基本的”防”黑客技术 Basic” ” Hacker Technique
走进编程 程序的顺序结构(二).
辅导课程六.
Zhao4zhong1 (赵中) C语言指针与汇编语言地址.
Zhao4zhong1 (赵中) C语言指针与汇编语言地址.
第一单元 初识C程序与C程序开发平台搭建 ---观其大略
C语言 程序设计基础与试验 刘新国、2012年秋.
第3讲 C++程序控制结构 3.1 顺序结构 3.2 分支结构 3.3 循环结构 3.4 转向控制 3.5 综合案例分析.
第二章 Java语言基础.
本节内容 模拟线程切换 视频提供:昆山滴水信息技术有限公司 官网地址: 论坛地址: QQ交流 :
逆向工程-汇编语言
計數式重複敘述 for 迴圈 P
第七章 操作符重载 胡昊 南京大学计算机系软件所.
第1章 概述 本章要点: C语言程序结构和特点 C语言程序的基本符号与关键字 C语言程序的编辑及运行 学习方法建议:
第4章 PHP流程控制语句.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
1.3 C语言的语句和关键字 一、C语言的语句 与其它高级语言一样,C语言也是利用函数体中的可执行 语句,向计算机系统发出操作命令。按照语句功能或构成的不 同,可将C语言的语句分为五类。 goto, return.
C语言程序设计 主讲教师:陆幼利.
EBNF与操作语义 请用扩展的 BNF 描述 javascript语言里语句的结构;并用操作语义的方法描述对应的语义规则
第1讲 C语言基础 要求: (1) C程序的组成 (2) C语言的标识符是如何定义的。 (3) C语言有哪些基本数据类型?各种基本数
程式結構&語法.
第 二 章 数据类型、运算符与表达式.
C 语言程序设计 程序的循环结构 电大崇信县工作站 梁海亮.
第2章 算法与C语言程序 程序 (1)数据的描述:数据的类型和组织形式(数据结构) (2)操作的描述:操作步骤(算法) 沃思指出:
微机原理与接口技术 微机原理与接口技术 朱华贵 2015年11月13日.
指標
Chap 5 函数 5.1 计算圆柱体积 5.2 使用函数编写程序 5.3 变量与函数.
7.1 C程序的结构 7.2 作用域和作用域规则 7.3 存储属性和生存期 7.4 变量的初始化
第十四章 若干深入问题和C独有的特性 作业: 函数指针 函数作参数 函数副作用 运算 语句 位段 存储类别 编译预处理
C语言程序设计 李祥 QQ:
第2章 认识C语言 教学要点 2. 1 项目二C语言程序识读 2 .2 项目三班级成绩排名 2 .3 知识链接 返回.
<编程达人入门课程> 本节内容 为什么要使用变量? 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ:
第二章 Java语法基础.
第九节 赋值运算符和赋值表达式.
第二章 类型、对象、运算符和表达式.
累堆排序法 (Heap Sort).
College of Computer Science & Technology
多层循环 Private Sub Command1_Click() Dim i As Integer, j As Integer
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
#include <iostream.h>
第二章 Java基本语法 讲师:复凡.
第7章 模板 陈哲 副教授 南京航空航天大学 计算机科学与技术学院.
临界区问题的硬件指令解决方案 (Synchronization Hardware)
本节内容 C语言的汇编表示 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
第五章 逻辑运算和判断选取控制 §5.1 关系运算符和关系表达式
本节内容 通用寄存器 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++语言程序设计 C++语言程序设计 第一章 C++语言概述 第十一组 C++语言程序设计.
本节内容 动态链接库 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
大数据搜索挖掘实验室 第五章 子程序设计 张华平 副教授 博士 Website: 大数据搜索挖掘实验室
嵌入式Linux编程环境.
第三章 流程控制 程序的运行流程 选择结构语句 循环结构语句 主讲:李祥 时间:2015年10月.
第二章 Java基础语法 北京传智播客教育
函式庫補充資料 1.
Presentation transcript:

第三章 程序的转换与机器级表示 程序转换概述 IA-32 /x86-64指令系统 C语言程序的机器级表示 复杂数据类型的分配和访问 越界访问和缓冲区溢出、x86-64架构

程序的转换与机器级表示 主要教学目标 了解高级语言与汇编语言、汇编语言与机器语言之间的关系 掌握有关指令格式、操作数类型、寻址方式、操作类型等内容 了解高级语言源程序中的语句与机器级代码之间的对应关系 了解复杂数据类型(数组、结构等)的机器级实现 主要教学内容 介绍C语言程序与IA-32机器级指令之间的对应关系。 主要包括:程序转换概述、IA-32指令系统、C语言中控制语 句和过程调用等机器级实现、复杂数据类型(数组、结构等) 的机器级实现等。 本章所用的机器级表示主要以汇编语言形式表示为主。

程序的机器级表示 分以下五个部分介绍 第一讲:程序转换概述 机器指令和汇编指令 机器级程序员感觉到的属性和功能特性 高级语言程序转换为机器代码的过程 第二讲:IA-32 /x86-64指令系统 第三讲: C语言程序的机器级表示 过程调用的机器级表示 选择语句的机器级表示 循环结构的机器级表示 第四讲:复杂数据类型的分配和访问 数组的分配和访问 结构体数据的分配和访问 联合体数据的分配和访问 数据的对齐 第五讲:越界访问和缓冲区溢出 从高级语言程序出发,用其对应的机器级代码以及内存(栈)中信息的变化来说明底层实现 围绕C语言中的语句和复杂数据类型,解释其在底层机器级的实现方法

过程调用的机器级表示 以下过程(函数)调用对应的机器级代码是什么? 如何将t1(125)、t2(80)分别传递给add中的形式参数x、y add函数执行的结果如何返回给caller? int add ( int x, int y ) { return x+y; } int main ( ) { int t1 = 125; int t2 = 80; int sum = add (t1, t2); return sum; main: add: 存放参数 取出参数 调出add执行 执行 存返回结果 返回main add main

过程调用的机器级表示 过程调用的执行步骤(P为调用者,Q为被调用者) main: add: 何为现场? 存放参数 取出参数 存放参数 取出参数 调出add执行 执行 存返回结果 返回main 何为现场? 通用寄存器的内容! 为何要保存现场? 因为所有过程共享一套通用寄存器 想象:妈妈和你做菜时共用一套盘 子的情况。 过程调用的执行步骤(P为调用者,Q为被调用者) (1)P将入口参数(实参)放到Q能访问到的地方; (2)P保存返回地址,然后将控制转移到Q; (3)Q保存P的现场,并为自己的非静态局部变量分配空间; (4)执行Q的过程体(函数体); (5)Q恢复P的现场,释放局部变量空间; (6)Q取出返回地址,将控制转移到P。 P过程 CALL指令 准备阶段 Q过程 处理阶段 结束阶段 RET指令

过程调用的机器级表示 IA-32的寄存器使用约定 调用者保存寄存器:EAX、EDX、ECX 当过程P调用过程Q时,Q可以直接使用这三个寄存器,不用将它们的值保存到栈中。如果P在从Q返回后还要用这三个寄存器的话,P应在转到Q之前先保存,并在从Q返回后先恢复它们的值再使用。 被调用者保存寄存器:EBX、ESI、EDI Q必须先将它们的值保存到栈中再使用它们,并在返回P之前恢复它们的值。 EBP和ESP分别是帧指针寄存器和栈指针寄存器,分别用来指向当前栈帧的底部和顶部。 问题:为减少准备和结束阶段的开销,每个过程应先使用哪些寄存器? EAX、ECX、EDX!

过程调用的机器级表示 过程调用过程中栈和栈帧的变化 (Q为被调用过程) ① ② ⑤ ③ Q(参数1,…,参数n);

一个简单的过程调用例子 caller 帧底 -4 -8 -12 -16 ESP+4 -20 int add ( int x, int y ) { return x+y; } int caller ( ) { int t1 = 125; int t2 = 80; int sum = add (t1, t2); return sum; 一个简单的过程调用例子 caller 帧底 add caller -4 -8 -12 -16 -20 caller: pushl %ebp movl %esp, %ebp subl $24, %esp movl $125, -12(%ebp) movl $80, -8(%ebp) movl -8(%ebp), %eax movl %eax, 4(%esp) movl -12(%ebp), %eax movl %eax, (%esp) call add movl %eax, -4(%ebp) movl -4(%ebp), %eax leave ret ESP+4 准备阶段 分配局部变量 准备入口参数 返回参数总在EAX中 准备返回参数 add函数开始是什么? pushl %ebp movl %esp, %ebp 结束阶段 movl %ebp, %esp popl %ebp

可执行文件的存储器映像 内核区 栈区 共享库的代码 堆区 程序(段)头表描述如何映射 brk 从可执行文件装入 Kernel virtual memory Memory-mapped region for shared libraries Run-time heap (created by malloc) User stack (created at runtime) Unused Read/write segment (.data, .bss) Read-only segment (.init, .text, .rodata) 内核区 0xC00000000 ELF header Segment header table .text section .data section .bss section .symtab .debug .rodata section .line .init section .strtab 栈区 %esp (栈顶) 共享库的代码 brk 堆区 从可执行文件装入 0x08048000

过程调用参数传递举例 按地址传递参数 按值传递参数 执行结果?为什么? 程序二 程序一 #include <stdio.h> main ( ) { int a=15, b=22; printf (“a=%d\tb=%d\n”, a, b); swap (&a, &b); } swap (int *x, int *y ) int t=*x; *x=*y; *y=t; 程序二 #include <stdio.h> main ( ) { int a=15, b=22; printf (“a=%d\tb=%d\n”, a, b); swap (a, b); } swap (int x, int y ) int t=x; x=y; y=t; 按地址传递参数 按值传递参数 执行结果?为什么? 程序一的输出: a=15 b=22 a=22 b=15 程序二的输出: a=15 b=22

过程调用参数传递举例 局部变量a和b确实交换 22 15 EBP+12 EBP+8 返回地址 EBP EBP在main中的值 EBX在main中的值 R[ecx]←M[&a]=15 局部变量a和b确实交换 R[ebx]←M[&b]=22 M[&a] ← R[ebx] =22 M[&b] ← R[ecx] = 15

过程调用参数传递举例 EBP+12 15 EBP+8 22 返回地址 EBP EBP在main中的值 局部变量a和b没有交换,交换的仅是入口参数! R[edx]←15 R[eax]←22 M[R[ebp]+8] ← R[eax] =22 M[R[ebp]+12] ← R[edx] =15

入口参数的位置 每个过程开始两条指令总是 pushl %ebp movl %esp, %ebp 在IA-32中,若栈中存放的参数的类型是char、unsigned char或short、unsigned short,也都分配4个字节。 因而,在被调用函数的执行过程中,可以使用R[ebp]+8、R[ebp]+12、R[ebp]+16、…… 作为有效地址来访问函数的入口参数。 movl ……. 准备入口参数 call ……. EBP+16 入口参数3 EBP+12 入口参数2 入口参数1 EBP+8 返回地址 EBP EBP在main中的值

过程调用举例 &y: 300 1 void test ( int x, int *ptr ) 2 { P &a: 2 { 3 if ( x>0 && *ptr>0 ) 4 *ptr+=x; 5 } 6 7 void caller (int a, int y ) 8 { 9 int x = a>0 ? a : a+100; 10 test (x, &y); 11 } 调用caller的过程为P,P中给出形参a和y的 实参分别是100和200,画出相应栈帧中的状态,并回答下列问题。 (1)test的形参是按值传递还是按地址传递?test的形参ptr对应的实参是一个 什么类型的值? (2)test中被改变的*ptr的结果如何返回给它的调用过程caller? (3)caller中被改变的y的结果能否返回给过程P?为什么? test caller P caller 100 200 caller执行过程中,在进入test之前一刻栈中的状态如何? 则函数返回400 若return x+y; 进入test并生成其栈帧后,栈中状态如何? 前者按值、后者按地址。一定是一个地址 第10行执行后,P帧中200变成300,test退帧后,caller中通过y引用该值300 第11行执行后caller退帧并返回P,因P中无变量与之对应,故无法引用该值300

递归过程调用举例 int nn_sum ( int n) { nn_sum(n) int result; if (n<=0 ) P int nn_sum ( int n) { int result; if (n<=0 ) result=0; else result=n+nn_sum(n-1); return result; } P Sum(n) Sum(n-1) n R[ebx]←n R[eax]←0 if (n≤0)转L2 R[eax]←n-1 每次递归调用都会增加一个栈帧,所以空间开销很大。 R[eax] ← 0+1+2+…+(n-1)+n

过程调用的机器级表示 递归函数nn_sum的执行流程 过程功能由过程体实现,为支持过程调用,每个过程包含准备阶段和结束阶段。因而每增加一次过程调用,就要增加许多条包含在准备阶段和结束阶段的额外指令,它们对程序性能影响很大,应尽量避免不必要的过程调用,特别是递归调用。

过程调用举例 例:应始终返回d[0]中的3.14,但并非如此。Why? 不同系统上执行结果可能不同 double fun(int i) { volatile double d[1] = {3.14}; volatile long int a[2]; a[i] = 1073741824; /* Possibly out of bounds */ return d[0]; } fun(0)  3.14 fun(1)  3.14 fun(2)  3.1399998664856 fun(3)  2.00000061035156 fun(4)  3.14, 然后存储保护错 为何每次返回不一样?为什么会引起保护错?栈帧中的状态如何? 不同系统上执行结果可能不同

double fun(int i) { volatile double d[1] = {3.14}; volatile long int a[2]; a[i] = 1073741824; return d[0]; } EBP EBP的旧值 ESP a[i]=1073741824; 0x40000000 =230 =1073741824 return d[0];

选择结构的机器级表示 if ~ else语句的机器级表示 if (cond_expr) then_statement else else_statement

If-else语句举例 p1和p2对应实参的存储地址分别为R[ebp]+8、R[ebp]+12,EBP指向当前栈帧底部,结果存放在EAX。 int get_cont( int *p1, int *p2 ) { if ( p1 > p2 ) return *p2; else return *p1; } p1和p2对应实参的存储地址分别为R[ebp]+8、R[ebp]+12,EBP指向当前栈帧底部,结果存放在EAX。

switch-case语句举例 int sw_test(int a, int b, int c) R[eax]=a-10=i { int result; switch(a) { case 15: c=b&0x0f; case 10: result=c+50; break; case 12: case 17: result=b+50; case 14: result=b default: result=a; } return result; R[eax]=a-10=i if (a-10)>7 转 L5 转.L8+4*i 处的地址 跳转表在目标文件的只读节中,按4字节边界对齐。 10 11 12 13 14 15 16 17 a=

循环结构的机器级表示 红色处为条件转移指令! do~while循环的机器级表示 do loop_body_statement while (cond_expr); 红色处为条件转移指令! loop: loop_body_statement c=cond_expr; if (c) goto loop; for循环的机器级表示 for (begin_expr; cond_expr; update_expr) loop_body_statement while循环的机器级表示 begin_expr; c=cond_expr; if (!c) goto done; loop: loop_body_statement update_expr; if (c) goto loop; done: while (cond_expr) loop_body_statement c=cond_expr; if (!c) goto done; loop: loop_body_statement if (c) goto loop; done:

循环结构与递归的比较 递归函数nn_sum仅为说明原理,实际上可直接用公式,为说明循环的机器级表示,这里用循环实现。 movl 8(%ebp), %ecx movl $0, %eax movl $1, %edx cmpl %ecx, %edx jg .L2 .L1: addl %edx, %eax addl $1, %edx jle .L1 .L2 局部变量 i 和 result 被分别分配在EDX和EAX中。 通常复杂局部变量被分配在栈中,而这里都是简单变量 int nn_sum ( int n) { int i; int result=0; for (i=1; i <=n; i++) result+=i; return result; } SKIP 过程体中没用到被调用过程保存寄存器。因而,该过程栈帧中仅需保留EBP,即其栈帧仅占用4字节空间,而递归方式则占用了(16n+12)字节栈空间,多用了(16n+8)字节,每次递归调用都要执行16条指令,一共多了n次过程调用,因而,递归方式比循环方式至少多执行了16n条指令。由此可以看出,为了提高程序的性能,若能用非递归方式执行则最好用非递归方式。

递归过程调用举例 时间开销:每次递归执行16条指令,共16n条指令 空间开销:一次调用增加16B栈帧,共16n+12 P的栈帧至少占12B int nn_sum ( int n) { int result; if (n<=0 ) result=0; else result=n+nn_sum(n-1); return result; } P的栈帧至少占12B P Sum(n) Sum(n-1) n BACK 时间开销:每次递归执行16条指令,共16n条指令 空间开销:一次调用增加16B栈帧,共16n+12

逆向工程举例 ① 处为i=0,② 处为i≠32,③ 处为i++。 movl 8(%ebp), %ebx movl $0, %eax movl $0, %ecx .L12: leal (%eax,%eax), %edx movl %ebx, %eax andl $1, %eax orl %edx, %eax shrl %ebx addl $1, %ecx cmpl $32, %ecx jne .L12 int function_test( unsigned x) { int result=0; int i; for ( ① ; ② ; ③ ) { ④ ; } return result; ① 处为i=0,② 处为i≠32,③ 处为i++。 入口参数 x 在EBX中,返回参数 result 在EAX中。LEA实现“2*result”,即:将result左移一位;第6和第7条指令则实现“x&0x01”;第8条指令实现“result=(result<<1) | (x & 0x01)”,第9条指令实现“x>>=1”。综上所述,④ 处的C语言语句是“result=(result<<1) | (x & 0x01); x>>=1;”。