Native相关知识点备忘

Native学习

最近想抽点时间学些一下native相关的知识,这篇文章只要记录一下学习的知识点,以免遗忘。包括但不限于:

C/C++语法
ELF文件格式
编译和链接过程
编译器和链接器内部特性

所以这篇文章应该会持续更新,主要是一些细碎的知识点,也会有一些原理讲解,但是不多。

1 获取栈帧地址和方法返回值

void * __builtin_return_address(unsigned int level)
void * __builtin_extract_return_addr (void *addr)
void * __builtin_frob_return_addr (void *addr)
void * __builtin_frame_address (unsigned int level)

参考
Getting the Return or Frame Address of a Function

2 dladdr()

获取一个地址所在的so信息以及地址所在的符号信息

参考
dladdr

3 ART会阻止在处理native信号期间通过JNI回调Java代码

ART prevents any Java calls from JNI during native signal handling.

4 ELF中存在.init段和.finit段

一般情况下,main方法是在.text段(代码段)中的,而.init是在程序初始化的时候执行的,所以比main方法先执行。而.finit段是在程序结束后执行写。
.init和.finit可以用来处理C++全局对象的初始化(new)和析构(delete)

5 编译和链接过程控制

编译和链接的各个阶段,很多都是可以控制的。比如写链接脚本,控制链接过程。编译阶段也可以指定多个参数,对编译过程进行自定义。

6 ELF可以分为链接视图和执行视图

链接视图以section为单位,对ELF进行区分。执行视图以segment为单位进行划分。属性(读,写,执行)相同的section,在加载的时候,为了节省页空间(每个section要以页为单位对齐进行映射),会合并成一个segment。

描述section的叫做section header, 段。 描述segment的,叫做program header,程序头。它描述ELF文件如何被系统加载和映射到进程虚拟地址空间。每个segment会被映射到一个单独的VMA。

readelf -S 查看所有的section
readelf -l 查看所有的segment (program header)

(readelf -l 会打印出所有的segment , 也会打印出section和segment的映射关系)

总结:

所有属性相同的section, 会被归类到一个segment,映射到同一个VMA

像段表(section header table)一样, program header也有自己的表,叫做 program header table。
可执行文件和共享库有program header table , 但是目标文件没有, 因为目标文件不需要被加载。

7 查看进程的虚拟地址空间的分布

通过以下命令可以查看进程的虚拟地址空间的分布:
cat /proc/[pid]/maps

8 为什么so文件也要参与静态链接

链接器首先确定当前目标文件中引用的符号是否在其他的目标文件中, 如果在的话,就会将两个目标文件合并,并进行地址的重定位,将符号引用替换为直接引用。

如果当前目标文件中引用的符号,并没有定义在其他目标文件红,连接器需要知道是不是一个需要动态链接的符号,这个信息需要去对应的so中去查询(以为so需要动态链接,所以so里面会保留所有的符号信息)。

如果在某个so中查询到这个符号,则说明对这个符号的引用,需要在动态链接的时候才能解析。所以就会把当前目标文件中的符号引用标识为动态链接符号。

9 so如何解决在不同进程中地址共享的问题

gcc编译so的时候,有两个参数。一个是-shared, 另一个是-fPIC 。

-shared输出的so使用装载时重定位。但是装载时重定位无法解决多个进程共享一个so的问题。因为转载时重定位需要在装载时修复so代码段中的地址,但是代码段是被多个进程共享的,多个进程的地址空间是不一样的。

-fPIC输出的so使用了地址无关代码(PIC, Position-independent Code)的技术。实现思路是,将指令中那些需要被修改的部分放到数据中,而数据可以在每个进程中拥有一个部分,剩余的指令可以在多个进程间共享。

10 PLT和_dl_runtime_resolve()

该函数是动态链接器的实现。负责将不同模块(so)之间的函数调用进行绑定。
绑定策略是延迟绑定,依赖PLT(Procedure Linkage Table)。
跳转的时候不直接通过GOT(Global Offset Table), 而是通过PLT进行跳转。
每个外部函数在PLT中都有一个表项。如bar@plt, 实现方式大概如下:

bar@plt:
jmp * (bar@GOT)
push n
push muduleID
jump _dl_runtime_resolve

ELF将GOT分成了两个表, 一个是.got , 另一个是.got.plt 。
.got用来保存全局变量引用的地址,.got.plt用来保存外部函数引用的地址。

.got.plt 的前三项比较特殊。

第一项为.dynamic段的地址,这个段保存本模块(so)的动态链接信息
第二项为本模块的ID
第三项为_dl_runtime_resolve函数的地址。

其实真是的PLT表项结构如下:

PLT0:
push * (GOT + 4)
jump * (GOT + 8)

......

bar@plt:
jmp *(bar@GOT)
push n
jump PLT0

push *(GOT + 4)表示将模块ID压栈, (GOT + 4)表示.got.plt表的第二项,也保存的正是当前模块ID, jump *(GOT + 8)表示跳转到_dl_runtime_resolve函数去执行,(GOT + 8)表示.got.plt的第三项,保存的是_dl_runtime_resolve的地址。

这两条指令是所有.got.plt表项共用的,ELF为了节省空间,将这两条指令放到了.got.plt的第一项中。

11 动态链接器

在linux中,动态连接器为ld.so, 实际上也是一个共享库。
如果可执行程序使用了动态链接,那么可执行程序加载到进程地址空间后,加载器不会直接跳转到可执行文件的入口地址,而是跳转到ld.so的入口地址,ld.so会先做一些初始化工作,然后会做一些动态链接的工作,然后再跳转到可执行的文件的入口地址。

12进程初始化时的堆栈信息

进程初始化的时候,需要在堆栈中保存一些参数,包括:

1 启动进程的命令行参数
2 进程的环境变量
3 传递给动态连接器ld的信息,如程序头(program header)的起始位置AT_PHDR,程序头中各个项的大小AT_PHENT,程序头中项的个数AT_PHNUM, 程序入口地址AT_ENTRY

13 共享库的构造和析构

在函数声明时加上__attribute__(constructor) 和 attribute(destructor)

14 atexit函数

atexit是一个特殊的函数,接受一个函数指针作为参数,并保证在程序正常退出(从main函数返回,或者调用exit函数),这个函数指针调用的函数会被调用。

void foo(void)
{
    printf("Bye!\n");
}

int main(void)
{
    atexit(&foo);
    printf("End of main\n");
}

atexit注册的函数的调用时机,是在main函数调用之后。

15 程序的入口点

程序的入口点并不是main, 一般来说都是运行库的一部分。

16 C语言printf的变长参数的实现

变长参数的实现得益于C语言默认的cdecl调用惯例的自右向左压栈传递方式。
并且cdecl是被调用方负责清除堆栈。
还依赖于va_list, va_start, va_arg, va_end等宏的实现

N 工具汇总

Lief

一个可以解析,修改和提取ELF,PE,MachO和Android文件的跨平台的工具。

Lief

xCrash

能为安卓 app 提供捕获 java 崩溃,native 崩溃和 ANR 的能力。不需要 root 权限或任何系统权限。

xCrash

xHook

一个针对 Android 平台 ELF (可执行文件和动态库) 的 PLT (Procedure Linkage Table) hook 库

xHook

readelf

解析ELF文件,展示ELF文件中的各种信息

readelf

objdump

解析目标文件,展示目标文件中的各种信息

objdump

ldd

查看可执行文件或者so依赖于哪些so

c++filt

用于反修饰c++的符号。C++编译的时候, 会对各种符号进行修饰。

如 _Z4funcf, _ZN1C4funcEi等。
对_ZN1C4funcEi执行c++filt , 结果如下:

 ~ c++filt _ZN1C4funcEi
C::func(int)

addr2line

将地址信息对应到文件行号

如果是android开发,可以使用 arm-linux-androideabi-addr2line

size

输出二进制文件各个段的大小总大小

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 React Native 中实现中心点缩放图片的步骤如下: 1. 创建一个 `Image` 组件,并设置它的 `style` 属性,包括宽高、缩放比例和位置等。 ```jsx import React, { useState } from 'react'; import { StyleSheet, View, Image, Dimensions } from 'react-native'; const { width, height } = Dimensions.get('window'); const imageWidth = 300; const imageHeight = 300; const CenterScaleImage = () => { const [scale, setScale] = useState(1); const handleDoubleTap = (event) => { const { locationX, locationY } = event.nativeEvent; const offsetX = (locationX - (imageWidth / 2)) * (1 - 2); const offsetY = (locationY - (imageHeight / 2)) * (1 - 2); setScale(2); }; return ( <View style={styles.container}> <Image source={require('./your-image.jpg')} style={{ width: imageWidth * scale, height: imageHeight * scale, transform: [ { translateX: -offsetX }, { translateY: -offsetY }, { scale: scale }, { translateX: offsetX }, { translateY: offsetY }, ], }} onDoubleTap={handleDoubleTap} /> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, }); export default CenterScaleImage; ``` 2. 监听 `Image` 组件的双击事件,计算缩放中心点的位置和缩放偏移量,并设置新的缩放比例。 上述代码中,我们通过 `useState` 钩子来保存当前的缩放比例,初始值为 1。在 `handleDoubleTap` 函数中,我们获取双击事件的坐标位置 `locationX` 和 `locationY`,然后根据上面提到的公式,计算出缩放中心点的位置和缩放偏移量,最后设置新的缩放比例为 2。 在 `Image` 组件的 `style` 属性中,我们使用 `transform` 属性来实现缩放和平移。首先,我们通过 `translateX` 和 `translateY` 属性来将图片的中心点移动到屏幕中心;然后,我们使用 `scale` 属性来设置缩放比例;最后,我们再次通过 `translateX` 和 `translateY` 属性来将图片移回到原来的位置。其中,`-offsetX` 和 `-offsetY` 表示缩放偏移量的相反数,用来将图片移动回原来的位置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值