一、汇编基础

1、指令码与数据处理

当计算机处理应用程序运行指令码时,数据指针指示处理器如何在内存的数据区域寻找要处理的数据,这块区域也称为堆栈,指令码放在另外的指令区,此外,还有指令指针机制,当处理器完成一个指令码的处理后,指令指针指向下一条指令码。

IA-32指令码(INTEL、AMD公司的CPU使用)由一堆二进制码构成,其格式为:

指令前缀、操作码、可选修饰符、可选数据元素

指令前缀可包含1到4个修改操作码行为的1字节前缀,分为:

锁定前缀和重复前缀

段覆盖前缀和分支提示前缀

操作数长度覆盖前缀

地址长度覆盖前缀

操作码定义了处理器执行的功能

修饰符定义执行功能时涉及的寄存器和内存位置。

数据元素是完成功能需要使用的数据,这些数据可以是直接的数据值,也可以是数据在内存中的地址。

2、汇编语言

以LINUX/UNIX环境下的汇编语言AT&T汇编(WINDOWS下有一种常用的汇编格式Intel汇编)进行讲解,汇编语言允许程序员方便地创建指令码程序,但不是用那些二进制编码的格式,还是使用助记符,助记符使用不同的词表示不同的指令码,有了助记符,程序员可以用英语来书写在目标机器上执行的指令码,不用记忆那些无趣的二进制编码。

 通常, FreeBSD 的内核使用 C 语言的调用规范。 此外, 虽然我们使用 int  $0x80来访问内核, 但是我们常常通过调用一个函数来执行  int  $0x80, 而不是直接访问。这个规范是非常方便的, 比 Microsoft� 的 MS-DOS上使用的规范更加优越。 为什么呢? 因为 UNIX� 的规范允许任何语言所写的程序访0问内核。这意味着在freebsd下访问内核需要先将参数压入栈中,然后再执行 int  $0x80调用内核中断,执行内核函数。

下面这段代码是经典的helloworld汇编代码:

dp@dp:~ % vim helloworld.s

#hello.s

.data # 数据段声明

    msg : .string "Hello, world!\n" # 要输出的字符串len = . - msg                   # 字串长度

.text # 代码段声明

.global _start # 指定入口函数

_start: # 在屏幕上显示一个字符串

    pushl $len  # 参数三:字符串长度pushl $msg  # 参数二:要显示的字符串pushl $1    # 参数一:文件描述符(stdout) movl $4, %eax    # 系统调用号(sys_write) pushl %eaxint  $0x80       # 调用内核功能# 退出程序movl $0,%ebx     # 参数一:退出代码movl $1,%eax     # 系统调用号(sys_exit) int  $0x80       # 调用内核功能

在LINUX/UNIX(以freebsd为例)下,可以使用gs和ld软件汇编和链接。

dp@dp:~ % as -o helloworld.o helloworld.s

dp@dp:~ % ld -o helloworld helloworld.o

dp@dp:~ % ./helloworld

Hello, world!

dp@dp:~ %

也可以直接使用GCC命令编译,但用gcc编译时将入口函数名由_start改为main。

dp@dp:~ % vim helloworld.s

#hello.s

.data # 数据段声明

    msg : .string "Hello, world!\n" # 要输出的字符串len = . - msg                   # 字串长度

.text # 代码段声明

.global main # 指定入口函数

main: # 在屏幕上显示一个字符串

    pushl $len  # 参数三:字符串长度pushl $msg  # 参数二:要显示的字符串pushl $1    # 参数一:文件描述符(stdout) movl $4, %eax    # 系统调用号(sys_write) pushl %eaxint  $0x80       # 调用内核功能# 退出程序movl $0,%ebx     # 参数一:退出代码movl $1,%eax     # 系统调用号(sys_exit) int  $0x80       # 调用内核功能

~

汇编后运行

dp@dp:~ % gcc -o helloworld helloworld.s

dp@dp:~ % ./helloworld

Hello, world!

dp@dp:~ %

对于ubuntu等Linux 是一个类 UNIX 操作系统。 但是, 它的内核在传递参数的时候, 使用和 MS-DOS 相同系统调用规范。 比如在 UNIX 的规范中, 代表内核函数的数字存放在 EAX 中。 但是在 Linux 中, 参数不进行压栈而是存放在 EBX, ECX, EDX, ESI, EDI, EBP。因此在ubuntu下这段代码需要这样编写(设使用GCC编译)

#hello.s

.data # 数据段声明

    msg : .string "Hello, world!\n" # 要输出的字符串len = . - msg                   # 字串长度

ext # 代码段声明

global main # 指定入口函数

main: # 在屏幕上显示一个字符串

    movl $len, %edx  # 参数三:字符串长度movl $msg, %ecx  # 参数二:要显示的字符串movl $1, %ebx    # 参数一:文件描述符(stdout) movl $4, %eax    # 系统调用号(sys_write) int  $0x80       # 调用内核功能# 退出程序movl $0,%ebx     # 参数一:退出代码movl $1,%eax     # 系统调用号(sys_exit) int  $0x80       # 调用内核功能

2、C语言编译

   C语言属于高级语言,对汇编语言程序员来说也许是一种解脱,完成同样的任务,程序编码量减少很多,这只是把很多编码生成转移除到了编译器上来而已,让机器承担这部分编码生成工作。

以freebsd系统、intel 的Intel® Pentium®CPU为例,编写以下hello,world代码

dp@dp:~ % cat hello.c

include <stdio.h>

int main(){

printf(“hello,world\n”);

return 0;

}
dp@dp:~ %

编译后,运行

dp@dp:~ % gcc -o hello hello.c

dp@dp:~ % ./hello

hello,world

使用GCC的 -S选项生成C语言对应的汇编代码

dp@dp:~ % gcc -S hello.c

下面是刚才生成的汇编语言代码

dp@dp:~ % cat hello.s

.file “hello.c”

.section .rodata

.LC0:

.string “hello,world”

.text

.p2align 4,15

.globl main

.type main, @function

main:

leal 4(%esp), %ecx

andl $-16, %esp

pushl -4(%ecx)

pushl %ebp

movl %esp, %ebp

pushl %ecx

subl $4, %esp

movl $.LC0, (%esp)

call puts

movl $0, %eax

addl $4, %esp

popl %ecx

popl %ebp

leal -4(%ecx), %esp

ret

.size main, .-main

.ident “GCC: (GNU) 4.2.1 20070831 patched [FreeBSD]”

.section .note.GNU-stack,"",@progbits
dp@dp:~ %

分析通过GCC编译C语言程序生成的汇编代码,能清楚得了解C语句运行机制、内存分配机制等隐藏在C语言代码下的内部工作原理。下面将对helloworld程序生成的汇编进行分析。

(1)寄存器基础知识

寄存器是中央处理器内的组成部份。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序计数器。在中央处理器的算术及逻辑部件中,包含的寄存器有累加器。

虽然计算机都拥有内存,但由于CPU的运行速度一般比主内存的读取速度快,访问内存所需要的时间为数个时钟周期,要访问内存的话,就必须等待数个CPU周期从而造成浪费,因此内存并不是数据存取最快的装置,后来在现代计算机上使用的AMD或Intel微处理器在芯片内部集成了大小不等的数据高速缓存和指令高速缓存,统称为cache(高速缓存),cache让数据访问的速度适应CPU的处理速度,其原理是内存中程序执行与数据访问的局域性行为,即一定程序执行时间和空间内,被访问的代码集中于一部分,但是这些仍不是访问数据最快的途径。

寄存器是存储器层次结构中的最顶端,也是系统操作数据的最快速途径,但它数量少能存储的空间有限,它直接安放在中央处理器内,是有限存贮容量的高速存贮部件,可用来暂存指令、数据和地址。

IA-32处理器有8个通用寄存器,分别为:
EAX 一般用作累加器
EBX 一般用作基址寄存器(Base)
ECX 一般用来计数(Count)
EDX 一般用来存放数据(Data)
EBP 一般用作堆栈指针(Stack Pointer)
EBP 一般用作基址指针(Base Pointer)
ESI 一般用作源变址(Source Index)
EDI 一般用作目标变址(Destinatin Index)

IA-32处理器有6个常用的段寄存器,分别为 :
CS 代码段寄存器
DS 数据段寄存器
SS 堆栈段寄存器
ES、FS及GS 附加数据段寄存器

它还有标志寄存器EFLAGS,用来存放有关处理器的控制标志,此外还有控制寄存器.还拥有调试寄存器和测试寄存器以及系统地址寄存器。

这些寄存器,使用的最多的是通用寄存器,在AT&T汇编中,使用%寄存器名的方式表示通用寄存器,比如:

%ebx表示ebx寄存器

%ecx表示ecx寄存器

(2)C变量内存分配

在C语言中,变量在内存中拥有自己的位置,这个位置就是变量的地址,可用指针来保存这个地址。而汇编语言中变量包括标记、数据类型、默认值三个部分,标记指示了变量的内存位置,存储的数据类型决定了变量在内存占有多少字节的空间,默认值决定了变量的初始值。观察上面C语言版的helloworld生成的汇编代码中的一段(如下所示),输出的helloworld字符串被放置在由“.LC0”标记的内存中,类型为string型,默认值为"hello,world"。

.LC0:

.string “hello,world”

“.LC0”标记的内存位于应用程序的静态分配区域,这个区域在程序运行后即被分配,即“hello,world”作为一个C语言字符串常量被安排在静态分配区域。

还有一个非常重要内存分配区域就是堆栈,,堆栈是特殊的内存区域,用于程序中函数传递参数、数据的临时存取,通常是应用程序内存范围的结尾位置的内存区域,为了方便堆栈数据的存取,有一个堆栈指针(栈顶指针)指向堆栈中的下个内存位置,这意味着如何仅依靠栈顶指针不采用任意偏移地址机制(偏移地址可以以基地址为中心进行调整,比如说访问某个变量,该变量的基地址为0x400,偏移地址为0x16,则该变量的最终地址为0x416),则只能按照先进后出的顺序来访问堆栈内部存储的变量。

在汇编语言中将数据放入堆栈中,使用pushl助记符,而将数据从堆栈中弹出,使用popl助记符,每次对堆栈数据的放入与弹出都会导致栈顶指针的变化,因为栈顶指针永远指向堆栈中下一个可用的地址。下面这段汇编完成了将10压入堆栈,然后将10弹出到ebx寄存器中的过程。

pushl $10

popl %ebx

(2)C程序执行

C语言的源代码被翻译成若干行汇编代码,由几个简单的指令组成的汇编代码生成二进制文件,执行这个二进制文件,完成了helloworld的执行。

汇编语言中用的较多助记符是movl、addl、subl

movl完成数据的复制,而addl完成数据的加法,subl完成数据的减法。

这3个助记符的语法格式是:

助记符 源数据 目标数据

比如,对于这段静态分配变量的汇编代码:

myvalue:

.long 190

mess:

.ascii “hello”

通过addl与movl可以完成将myvalue指示的long类型变量190加100,然后减20的功能。

movl myvalue,%ebx

addl $100,%ebx

subl $20,%ebx

movl %ebx,myvalue

汇编语言的代码放在了.text段中,分析上面的helloworld的反汇编代码中一段:

.text

.p2align 4,15

.globl main

.type main, @function

globl 命令指定了main函数为入口函数(程序启动时执行的函数),然后接着在后面定义了main函数的组成:

main:leal 4(%esp), %ecxandl $-16, %esppushl -4(%ecx)pushl %ebpmovl %esp, %ebppushl %ecxsubl $4, %espmovl $.LC0, (%esp)call putsmovl $0, %eaxaddl $4, %esppopl %ecxpopl %ebpleal -4(%ecx), %espret.size main, .-main.ident "GCC: (GNU) 4.2.1 20070831 patched [FreeBSD]".section .note.GNU-stack,"",@progbits观察这些汇编代码,里面充斥着pushl、popl、movl、subl与addl等助记符,C程序最终就是通过复制、入栈、出栈、加法、减法等简单操作来完成执行的。注意观察这些代码中的如下几行:leal 4(%esp), %ecxandl $-16, %esppushl -4(%ecx)pushl %ebpmovl %esp, %ebppushl %ecxsubl $4, %espmovl $.LC0, (%esp)call puts

C语句的print(“helloworld”)输出字符串就是通过上述几行实现的,除开最后一行call puts(call指令完成调用C语言的puts函数输出字符串的功能,puts函数向终端输出一个字符串,其唯一的参数是char *str,str表示需要输出的字符串)外,其它行做的所有工作就是将调用puts函数的唯一参数(指向字符串”helloworld”地址的标示“.LC0”)的放入堆栈中,以供puts函数调用,倒数第二行将.LC0标记的地址复制到当前堆栈的栈顶,前面几行分配堆栈,调整栈顶指针,将需要保存的寄存器入栈(因为调用puts函数会破坏现有寄存器的值,称之为保存现场),当puts函数完成后,会将入栈的寄存器值弹回各自的寄存器中(称之为恢复现场)。

C指针原理(43)-helloworld的C程序汇编剖析相关推荐

  1. C指针原理(23)-win32汇编及.NET调试

    2018-12-28 20:36:07 在WINDOWS系统能用到汇编的机会不多,基本都可以用C或C++代劳,更何况现在MICROSOFT的Visual Studio 系列工具非常强大,WINDOWS ...

  2. C指针原理(42)-内存管理与控制

    C语言的stdlib库提供了内存分配与管理的函数: 1.通过调用calloc.malloc和realloc所分配的空间,如果连续调用它们,不能保证空间是顺利或连续的.当分配成功后,函数将返回一个指针, ...

  3. iOS arc weak指针原理

    iOS arc weak指针原理 ARC 都帮我们做了什么? weak是什么? weak是怎么实现的? 1. weak原理简介 2. weak简单测试 3. weak原理分析 3.1 weak指针帮我 ...

  4. C指针原理(20)-C指针基础

    2018-12-28 12:59:57 结构与malloc 结构是C语言中重要的一环,malloc是一个重要的函数,它完成了动态内存分配,用malloc分配的内存块要通过free释放.通过结构可以将不 ...

  5. C指针原理(9)-C内嵌汇编

    我们使用m标记可以直接在内存中对数进行操作,前面的例子对变量进行操作时都需要将变量值存储在要修改的寄存器中,然后将它写回内存位置中. #include <stdio.h> int main ...

  6. MapReduce的工作原理,详细解释WordCount程序

    本篇文章主要说两部分:简单介绍MapReduce的工作原理:详细解释WordCount程序. MapReduce的工作原理 在<Hadoop in action>一书中,对MapReduc ...

  7. Objective-C中,ARC下的 strong和weak指针原理解释

    Objective-C中,ARC下的 strong和weak指针原理解释 提示:本文中所说的"实例变量"即是"成员变量","局部变量"即是& ...

  8. 操作系统之内存管理:1、内存管理基础知识(指令工作原理、地址转化、程序运行过程)

    1.内存管理基础知识(指令工作原理.地址转化.程序运行过程) 思维导图 什么是内存? 指令的工作原理 装入模块的三种实现 绝对装入 可重定位装入 动态重定位 程序的运行过程 链接的三种方式 思维导图 ...

  9. 微型计算机循环结构程序设计,微机原理实验之_分支程序、循环程序设计

    <微机原理实验之_分支程序.循环程序设计>由会员分享,可在线阅读,更多相关<微机原理实验之_分支程序.循环程序设计(6页珍藏版)>请在人人文库网上搜索. 1.实验三分支程序.循 ...

最新文章

  1. 电脑开不了不用U盘怎么装系统?
  2. 阐述计算机历程以及未来发展方向,计算机程序的发展史
  3. 2019已过半,薪资相匹配除了实力,其实最重要的是……
  4. 别吵吵,分布式锁也是锁
  5. beanfactorypostprocessor_Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)
  6. oracle函数 case,oracle的case函数和case控制结构 (摘)
  7. Luogu1443 马的遍历【STL通俗BFS】
  8. verilator编译 更新文件的规则
  9. 【恋上数据结构】串匹配算法(蛮力匹配、KMP【重点】、Boyer-Moore、Karp-Rabin、Sunday)
  10. jq判断是否为整数_jquery怎么判断是否是数字?
  11. modbus发送接收_自己编写MODBUS协议代码所踩过的坑
  12. 转分享[Mac] QQ音乐Mac特别版 可以下载无损
  13. win10绿联usb转串口_win10 usb转串口驱动-win10 usb转串口sb转驱动下载 PL2303 最新版 - 河东下载站...
  14. chrome不跟随系统分辨率
  15. 红帽RHCE考试要注意的几点
  16. property属性的使用
  17. python3.7批量爬取百度图片/搜狗图片
  18. Android11增加虚拟键盘开关
  19. pythonset是什么意思_Python 中 set 是什么?为何要是用它?
  20. Ubuntu 22.04 将python3.10设置为3.9部分软件不能启动

热门文章

  1. phpcms v9的url优化
  2. Sublime3 快捷键
  3. lua--面向对象使用middleclass
  4. 牛客OI周赛10-提高组:B-Taeyeon的困惑(值域线段树)
  5. P1351 联合权值
  6. 端口报错listen eaddrinuse:::xxx
  7. 使用Disruptor实现生产者和消费者模型
  8. Linux CentOS服务启动
  9. EOS page问题
  10. jquery实现点击改变背景色,点击其他恢复原来背景色,被点击的改变背景色