关于反编译那些事

前言

在从事软件开发这件事上,有很多东西是绕不开的,我们能做到的事情,就是记笔记,经常看,唯手熟尔。
本篇重点介绍:编译优化、gdb调试、反编译命令

编译优化

编译优化主要针对三个方面:
(1)编译时间;
(2)目标文件长度;
(3)执行效率。
编译优化的方法:
(1)精简操作指令;
(2)尽量满足cpu的流水操作;
(3)通过对程序行为地猜测,重新调整代码的执行顺序;
(4)充分使用寄存器;
(5)对简单的调用进行展开。
虽然GUN手册已经对提供的编译选项进行了描述,但从成千上万的编译选项中选择合适的编译参数,无异于大海捞针。所幸gcc已经提供了几种不同的优化级别。

优化级别

分别为O、O0、O1、O2、O3、Os

O0

不做任何优化,这是默认的编译选项。

-O和-O1:

对程序做部分编译优化,对于大函数,优化编译占用稍微多的时间和相当大的内存。使用本项优化,编译器会尝试减小生成代码的尺寸,以及缩短执行时间,但并不执行需要占用大量编译时间的优化。
打开的优化选项:
(1)-fdefer-pop
延迟栈的弹出时间。当完成一个函数调用,参数并不马上从栈中弹出,而是在多个函数被调用后,一次性弹出。l
(2) -fmerge-constants
尝试横跨编译单元合并同样的常量(string constants and floating point constants)
(3) -fthread-jumps
如果某个跳转分支的目的地存在另一个条件比较,而且该条件比较包含在前一个比较语句之内,那么执行本项优化.根据条件是true或者false,前面那条分支重定向到第二条分支的目的地或者紧跟在第二条分支后面.
(4)-floop-optimize
执行循环优化,将常量表达式从循环中移除,简化判断循环的条件,并且optionally do strength-reduction,或者将循环打开等。在大型复杂的循环中,这种优化比较显著。
(5) -fif-conversion
尝试将条件跳转转换为等价的无分支型式。优化实现方式包括条件移动,min,max,设置标志,以及abs指令,以及一些算术技巧等。
(6) -fif-conversion2
基本意义相同,没有找到更多的解释。
(7) -fdelayed-branch
这种技术试图根据指令周期时间重新安排指令。 它还试图把尽可能多的指令移动到条件分支前, 以便最充分的利用处理器的治理缓存。
(8) -fguess-branch-probability
当没有可用的profiling feedback或__builtin_expect时,编译器采用随机模式猜测分支被执行的可能性,并移动对应汇编代码的位置,这有可能导致不同的编译器会编译出迥然不同的目标代码。
(9) -fcprop-registers
因为在函数中把寄存器分配给变量, 所以编译器执行第二次检查以便减少调度依赖性(两个段要求使用相同的寄存器)并且删除不必要的寄存器复制操作。

O2

是比O1更高级的选项,进行更多的优化。Gcc将执行几乎所有的不包含时间和空间折中的优化。当设置O2选项时,编译器并不进行循环打开()loop unrolling以及函数内联。与O1比较而言,O2优化增加了编译时间的基础上,提高了生成代码的执行效率。O2打开所有的O1选项,并打开以下选项:
(1) -fforce-mem
在做算术操作前,强制将内存数据copy到寄存器中以后再执行。这会使所有的内存引用潜在的共同表达式,进而产出更高效的代码,当没有共同的子表达式时,指令合并将排出个别的寄存器载入。这种优化对于只涉及单一指令的变量, 这样也许不会有很大的优化效果. 但是对于再很多指令(必须数学操作)中都涉及到的变量来说, 这会时很显著的优化, 因为和访问内存中的值相比 ,处理器访问寄存器中的值要快的多。
(2) -foptimize-sibling-calls
优化相关的以及末尾递归的调用。通常, 递归的函数调用可以被展开为一系列一般的指令, 而不是使用分支。 这样处理器的指令缓存能够加载展开的指令并且处理他们, 和指令保持为需要分支操作的单独函数调用相比, 这样更快。
(3) -fstrength-reduce
这种优化技术对循环执行优化并且删除迭代变量。 迭代变量是捆绑到循环计数器的变量, 比如使用变量, 然后使用循环计数器变量执行数学操作的for-next循环。
(4) -fcse-follow-jumps
在公用子表达式消元时,当目标跳转不会被其他路径可达,则扫描整个的跳转表达式。例如,当公用子表达式消元时遇到if…else…语句时,当条件为false时,那么公用子表达式消元会跟随着跳转。
(5) -fcse-skip-blocks
与-fcse-follow-jumps类似,不同的是,根据特定条件,跟随着cse跳转的会是整个的blocksl -frerun-cse-after-loop:在循环优化完成后,重新进行公用子表达式消元操作。
(6) -frerun-loop-opt
两次运行循环优化
(7) -fgcse
执行全局公用子表达式消除pass。这个pass还执行全局常量和copy propagation。这些优化操作试图分析生成的汇编语言代码并且结合通用片段, 消除冗余的代码段。如果代码使用计算性的goto, gcc指令推荐使用-fno-gcse选项。
(8) -fgcse-lm
等等…
详细情况可以参考:编译优化

O3

比O2更进一步的进行优化。在包含了O2所有的优化的基础上,又打开了以下优化选项:
(1) -finline-functions
内联简单的函数到被调用函数中。由编译器启发式的决定哪些函数足够简单可以做这种内联优化。默认情况下,编译器限制内联的尺寸,3.4.6中限制为600(具体含义不详,指令条数或代码size?)可以通过-finline-limit=n改变这个长度。这种优化技术不为函数创建单独的汇编语言代码,而是把函数代码包含在调度程序的代码中。 对于多次被调用的函数来说, 为每次函数调用复制函数代码。 虽然这样对于减少代码长度不利, 但是通过最充分的利用指令缓存代码, 而不是在每次函数调用时进行分支操作, 可以提高性能。
(2) -fweb
构建用于保存变量的伪寄存器网络。 伪寄存器包含数据, 就像他们是寄存器一样, 但是可以使用各种其他优化技术进行优化, 比如cse和loop优化技术。这种优化会使得调试变得更加的不可能,因为变量不再存放于原本的寄存器中。
(3) -frename-registers
在寄存器分配后,通过使用registersleft over来避免预定代码中的虚假依赖。这会使调试变得非常困难,因为变量不再存放于原本的寄存器中了。
(4) -funswitch-loops
将无变化的条件分支移出循环,取而代之的将结果副本放入循环中。

Os

主要是对程序的尺寸进行优化。打开了大部分O2优化中不会增加程序大小的优化选项,并对程序代码的大小做更深层的优化。(通常我们不需要这种优化)Os会关闭如下选项:
-falign-functions
-falign-jumps
-falign-loops
-falign-labels
-freorder-blocks
-fprefetch-loop-arrays

优化代码有可能带来的问题

1.调试问题:正如上面所提到的,任何级别的优化都将带来代码结构的改变。例如:对分支的合并和消除,对公用子表达式的消除,对循环内load/store操作的替换和更改等,都将会使目标代码的执行顺序变得面目全非,导致调试信息严重不足。
2.内存操作顺序改变所带来的问题:在O2优化后,编译器会对影响内存操作的执行顺序。例如:-fschedule-insns允许数据处理时先完成其他的指令;-fforce-mem有可能导致内存与寄存器之间的数据产生类似脏数据的不一致等。对于某些依赖内存操作顺序而进行的逻辑,需要做严格的处理后才能进行优化。例如,采用volatile关键字限制变量的操作方式,或者利用barrier迫使cpu严格按照指令序执行的。
3.效率是否有较大的提高有待试验论证:并不是所有的优化都会对执行效率产生积极的作用,有的时候利用优化会起到适得其反的效果。这就需要在试验的基础上来不断调整优化选项,来谋求最佳优化效果。但通常这样做所投入的时间和最终产生的效果之间比较,往往得不偿失。

gdb调试

gdb 调试一般性指令在这就不一一的介绍。
我们这儿讲两种调试的方法。(多进程、线程,下面一篇紧接着介绍)

coredump调试

生成coredrump文件

1)使用ulimit -a查看core系统配置
ulimit -a
core file size为0,则无法生成coredump相关文件。
2)使用ulimit -c unlimited #生成文件大小不受限制(重启或者从新登陆会失效)。
再次使用ulimit -a查看
ulimit -a
永久开启core dump功能,须要修改配置文件/etc/security/limits.conf,写入:

  •           soft    core             unlimited
    
指定core dump文件的名称格式

core dump文件的参数说明:
%% 单个%字符
%p dump进程的进程ID
%u dump进程的用户ID
%g dump进程的组ID
%s 致使core dump的信号
%t core dump 的时间
%h 主机名
%e 程序文件名网络

指定core dump文件的生成路径

root权限执行:
echo /home/db/coredump/core.%e.%p >/proc/sys/kernel/core_pattern

测试生成core dump文件

gdb 可执行文件 core文件

反编译命令

调试core文件;
(1)使用bt打印堆栈。
(2)之后可以使用up、down命令切换栈帧,亦或者是由frame + 序号的形式切换栈帧。
(3)输入反汇编命令,查看代码的反汇编
disassemble $pc
(4)输入如下命令,查看rbp的值(寄存器):
info register rbp
(5)打印命令的格式如下:
x /nfu 地址
n:输出单元的个数
f:可以是下面的值:
x:16进制
t:二进制
o:八进制
d:10进制
u:单元长度,可以是下面的值:
b:代表字节
h:代表双字节
w:代表word,通常是4字节
g:代表八字节
(6)为了使产生的汇编语言能被给位看懂,下面给出了如下连接:
1)使用gdb反汇编的方法
2)逆向笔记(三)-汇编指令
3)C语言在ARM中函数调用时,栈是如何变化的?
4)深入理解函数调用过程以及函数栈的变化
5)ARM汇编基础
6)nasm代码测试

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值