Skip to content

Latest commit

 

History

History
162 lines (99 loc) · 7.68 KB

Dynamic-Linking.md

File metadata and controls

162 lines (99 loc) · 7.68 KB

Dynamic Linking On OSX(关于macOS的动态链接)

Static Libraries(静态库)

当你链接一个静态库,这个库的内容就会在编译的时候,全部拷贝到你的应用项目中,就好像是你自己写的代码一样.

Dynamic Libraries(动态库)

当你链接了一个动态库,其实并没有与这个库有紧密的关联,只是链接器简单的标记了一下你所用到的这个动态库里的函数(或方法).当你的应用程序加载运行的时候,动态链接器才会装载你链接的这个库.

Static linking (静态链接)

静态链接就是我们平时说的链接,它发生在编译动作之后.我们的源代码经过编译器的编译后生成机器码,经过链接后形成一个独立的二进制文件.

那么静态链接(static linking)对与动态链接(dynamic linking)有怎样的意义呢?

Mach-O 文件的基本结构

  • 头部(header structure)
  • 加载区(load command)
  • 段(segment)

准备工具:

  • clang 将源码文件生成汇编内容的文件 例如:

     clang -S abc.c -o abc.s   // 将abc.c 源码文件 转为 汇编文件 文件名为abs.s
    
  • xcrun

  • xctest

  • otool

otool是对目标文件或者库文件的特定部分进行展示

  • otool -l 相当于Linux系统中的ldd工具,经常用来反编译工程中的依赖库信息.(获取加载区的信息)
  • otool -h filename : 获取文件的头部结构(header structure)信息
  • otool -tV filename : 查看汇编码

编译与汇编

  • 关于 x86 CPU architecture cpu中有大量的寄存器(至少100个),幸运的是开发者(即便是使用汇编语言的开发者)几乎只需要使用20个左右就够了.

    同一个寄存器可以根据名字的不同来进行编址: 也就是说,对同一个寄存器使用不同的名字可以读取或写入寄存器不同的地址空间.例如对64位的通用寄存器来说:

    • rax: 代表寄存器的64位地址空间

    • eax: 代表寄存器的32位低地址空间

    • ax: 代表寄存器的16位低地址空间

    • ah: 代表寄存器次低8位的地址空间(也就是al上的8位地址空间)

    • al: 代表寄存器的最低的8位地址空间() 寄存器通过不同名称获取不同的操作空间是为了方便处理不同的数据类型和节省寄存器空间(毕竟cpu的存储空间十分宝贵).

    • rsp: 栈寄存器 (stack pointer)

    • rbp: 基址寄存器 (base bopinter)

通用寄存器

在x86 指令集中,通常一个cpu具有八个通用寄存器:EAX,EDX,ESI,EBP,EBX,EDI,ECX,ESP (E代表32位处理器,R代表64位处理)

  • EAX 寄存器:
    也被常常称为累加器.用于协助一些常见的运算操作和函数的返回值.在x86指令集中很多经过优化的指令都优先将数据写入或读出EAX寄存器,然后再对数据进行进一步的操作.大多数基本的运算操作(比如加法,减法和比较运算)都会借助EAX寄存器达到指令优化的效果.而一些特色的指令(比如乘法或除法)则必须在EAX寄存器中进行.
    函数调用的返回值也常常保存在EAX寄存器中.你可以基于存储在EAX中的值来判断一个函数调用是成功了或者失败了.除了布尔值以外,EAX中还可以保存一个确切的函数返回值.

  • EDX 寄存器:
    EDX是一个数据寄存器.可以认为它是EAX寄存器的一个延伸部分,用于协作一些更为复杂的运算指令.比如乘法和除法,EDX用于存储这些指令操作的额外数据结果.EDX也可以作为一般的数据存储,但更常见的方式是与EAX一起使用,协助执行更复杂的运算.

  • ECX 寄存器:
    ECX寄存器常常被称为计数器,用于支持循环操作.例如存储一个字符串或者进行计数就是典型的循环操作.同学们特别需要注意的是,ECX寄存器通常时反向计数的,而非正向计数.为了更具体的理解这一点,下面我们用一个代码片段来说明这个问题:

    // 循环10次,一次输出count的值
    count = 0;
    while (count < 10){
    		printf("current count %d",count);
    		count += 1;
    }
    

    如果这段代码的逻辑用汇编语言表示,那么在循环第一次执行的时候,ECX的值为10,当代码执行第二次循环时,ECX的值为9,如此往复,直到ECX的值为0时,循环终止. 汇编loop指令会先将ECX寄存器的值-1,然后再判断ECX寄存器的值是否位0,如果不为0,则执行循环体,每执行一次循环,ECX寄存器的值-1,直到为0时,退出循环

    请记住: 在汇编代码中计数都是按反向进行的

  • ESI寄存器和EDI寄存器: 在x86汇编中,涉及数据处理的循环操作都依赖于ESI和EDI这两个寄存器,以实现高效的数据操作.
    ESI寄存器通常被称为源变址寄存器,它用来存储着输入数据的位置信息.
    EDI寄存器通常被称为目的变址寄存器,它常常保存数据操作结果的位置信息.
    可以简单的理解为ESI用与"读数据",EDI用于"写数据".
    在数据操作过程中使用这两个寄存器可以极大的提高程序运行效率.

  • ESP寄存器和EBP寄存器:
    ESP寄存器和EBP寄存器常常被用于函数调用和有关的栈操作.当一个函数被调用时,调用参数以及函数的返回地址将先后被压入栈中.
    ESP寄存器始终指向函数栈的最顶端因此不难推断出在调用函数过程中的某一时刻,ESP指向了函数的返回地址.
    EBP用于指向函数栈的底端.
    在某些情况下编译器为了指令优化的目的可能会避免将EBP用作栈帧,这样EBP寄存器就可以像其他通用寄存器一样来使用.

  • EBX寄存器:
    EBX寄存器是唯一一个没有明确指定特殊用途的寄存器,它被用作额外的存储单元.

  • EIP寄存器:
    EIP寄存器始终指向当前正在执行的指令.(记住这一点很重要)当CPU执行

函数调用约定

函数调用约定是用来描述如何以正确的方式调用某一特定类型函数,它包括了函数参数在栈上的分配顺序以及哪些参数会被压入栈中,哪些参数通过寄存器传入,以及函数返回时函数栈的回收方式.

两种基本的函数调用约定:cdecl和stdcall

  • cdecl调用: 函数的参数列表以从右到左顺序依次入栈,并由函数调用者负责清除栈上的参数.在x86结构下,绝大多数的c编译器都采用此方式.

  • stdcall调用: stdcall被win32广为采用. 与cdecl唯一的区别在于回收函数栈的工作是有被调用者本身在函数返回前自行负责清除.

    cdel和stacall都选用EAX寄存器存放函数的返回值

栈是一种特殊的数据结构,是实现函数调用的基石.(8086 的cpu是没有存储栈底位置的寄存器)  

函数调用栈的平衡: 要保证函数调用前与调用后,栈顶是一致的
栈空间的增长方向是向着低地址的.

  • 外平栈: 由函数外部调用者保持栈的平衡(cdecl方式函数调用)
  • 内平栈: 由函数内部保持栈的平衡(stdcall方式函数调用)

AT&T 汇编与Intel汇编

  • AT&T:(代表系统Mac, Linux ,Unix)

    寄存器命名以%开头 : %eax
    常数以$开头: $0x10
    操作顺序: movl %eax %edx // 将 eax寄存器的值送到edx中

  • Intel:(代表系统DOS,Windwos)
    寄存器 : eax
    常数以: 0x10
    操作顺序: movl edx eax // 将 eax寄存器的值送到edx中

AT&T 与Intel 的寻址方式