Compilation x86中的基本块和控制流

Compilation x86中的基本块和控制流,compilation,x86,code-analysis,Compilation,X86,Code Analysis,红龙书包含第529页的算法9.1和将汇编代码划分为基本块的算法。该算法基本上是: 输入:汇编,作为说明列表。 输出:基本指令块的列表 第一阶段:标记领导者。(程序的)第一句话是引导语。分支目标的任何语句都是leader。任何紧跟在分支后面的语句都是前导 阶段2:组装模块。要生成基本块,请将引线和所有语句添加到块中,直到遇到另一个引线。这样做,直到没有语句留下 结束并返回块 随后,他们使用该区块结构进行控制流分析。开发的基本结构是控制流图;它是通过将基本块视为图形中的节点来实现的,如果Bi以跳到B

红龙书包含第529页的算法9.1和将汇编代码划分为基本块的算法。该算法基本上是:

输入:汇编,作为说明列表。
输出:基本指令块的列表

第一阶段:标记领导者。(程序的)第一句话是引导语。分支目标的任何语句都是leader。任何紧跟在分支后面的语句都是前导

阶段2:组装模块。要生成基本块,请将引线和所有语句添加到块中,直到遇到另一个引线。这样做,直到没有语句留下

结束并返回块

随后,他们使用该区块结构进行控制流分析。开发的基本结构是控制流图;它是通过将基本块视为图形中的节点来实现的,如果Bi以跳到Bj的方式结束,则创建从块Bi到Bj的有向边

Cooper、Harvey和Waterman(在从调度代码构建控制流图时)指出,如果存在到内存地址或寄存器中保存的位置的跳转,那么创建控制流图的这种算法是不够的


这带来了几个问题。程序集何时可以包含指向内存中某个位置的分支?什么是计划代码?从x86构建控制流图时,是否还有其他问题需要注意?直接从x86构建控制流图的最著名的算法/实现是什么?

在一些情况下,对寄存器的内容(或变量内容)执行分支

  • 用于交换机的跳转表,其中要跳转到的地址是使用某些地址/偏移量表和寄存器值计算的
  • 一种函数指针,如回调函数,其中要调用的函数作为参数提供给调用者,或存储在某个变量中
  • C++中的虚函数表,其中调用的函数在编译时不知道,并且在运行时可能会发生变化。 下面是函数指针调用的示例(64位Mac OS):

    C代码

    装配


    这与操作系统调度无关,它是一种提高性能的编译器技术,对CFG有影响。阅读-谢谢你的评论。我只是把问题重新写了一遍,让它更清楚。你的评论仍然有效。但我不明白:在重新排列指令之后,我仍然有x86代码。因此,CFG可能与原来的有所不同,但我不明白为什么在调度发生后构建CFG会使算法有所不同。感谢您提供了一个很好的示例。深入挖掘之后,发现直接从程序集生成CFG是相当棘手的,因为像您指出的那样存在间接跳转。似乎最好的方法就是进行某种模拟。这是静态反汇编程序的问题之一,这使得交互式反汇编程序,如IDA,功能强大。。。
    int fptr_call( int (*ptr)(int) ) {
        return (*ptr)( 3 ); 
    }
    
    _fptr_call:
    0000000100000e70        pushq   %rbp
    0000000100000e71        movq    %rsp, %rbp
    0000000100000e74        subq    $0x10, %rsp
    0000000100000e78        movl    $0x3, %eax
    0000000100000e7d        movq    %rdi, 0xfffffffffffffff8(%rbp)
    0000000100000e81        movl    %eax, %edi
    ; Call based on %rbp, copied from %rdi which is ptr
    0000000100000e83        callq   *0xfffffffffffffff8(%rbp) 
    0000000100000e86        addq    $0x10, %rsp
    0000000100000e8a        popq    %rbp
    0000000100000e8b        ret
    0000000100000e8c        nopl    (%rax)