From 237ffca09d2de3d87661fca77c154504139251e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8F=81=E7=8E=96?= Date: Thu, 29 May 2025 14:33:41 +0800 Subject: [PATCH] =?UTF-8?q?C++=E6=A0=87=E5=87=86=E5=BA=93=E6=96=87?= =?UTF-8?q?=E6=A1=A3AI=E5=BB=BA=E8=AE=AE=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 林叁玖 --- zh-cn/application-dev/napi/c-cpp-overview.md | 26 ++++---- zh-cn/application-dev/napi/fdsan.md | 65 ++++++++++---------- 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/zh-cn/application-dev/napi/c-cpp-overview.md b/zh-cn/application-dev/napi/c-cpp-overview.md index 520451bf777..47afd0ccd35 100644 --- a/zh-cn/application-dev/napi/c-cpp-overview.md +++ b/zh-cn/application-dev/napi/c-cpp-overview.md @@ -9,24 +9,24 @@ OpenHarmony NDK提供业界标准库[libc标准库](../reference/native-lib/musl - 系统库:使用libc++.so,随系统镜像发布。 - 应用Native库:使用libc++_shared.so,随应用发布。 -两个库使用的C++命名空间不同,libc++.so使用__h作为 C++ 符号的命名空间,而 libc++_shared.so使用__n1作为 C++ 符号的命名空间。 +两个库使用不同的C++命名空间,libc++.so使用__h,而libc++_shared.so使用__n1。 > **注意:** > -> 系统和应用使用的C++标准库不能进行混用,Native API接口当前只能是C接口,可以通过这个接口隔离两边的C++运行环境。因此在使用共享库HAR包构建应用时,如果HAR包含的libc++_shared.so不同于应用使用的libc++_shared.so版本,那么只有其中一个版本会安装到应用里,可能会导致不兼容问题,可以使用相同的SDK版本更新HAR包解决此问题。 +> 系统和应用使用的C++标准库不能混用,Native API接口当前只能是C接口,用于隔离两边的C++运行环境。使用共享库HAR包构建应用时,如果HAR包含的libc++_shared.so版本不同,可能导致不兼容问题。可以使用相同SDK版本更新HAR包解决此问题。 **已知C++兼容性问题:** -应用启动或者dlopen时hilog报错`symbol not found, s=__emutls_get_address`,原因是API9及之前版本SDK中的libc++_shared.so无此符号,而API11之后版本SDK的libc++_shared.so是有此符号的。解决此问题需要更新应用或者共享库HAR包的SDK版本。 +应用启动或者dlopen时hilog报错`symbol not found, s=__emutls_get_address`,原因是API9及之前版本SDK中的libc++_shared.so无此符号,而API11之后版本SDK的libc++_shared.so有此符号。解决此问题需要更新应用或共享库HAR包的SDK版本。 ## 2. musl libc动态链接器 ### 动态库加载命名空间隔离 -动态库加载命名空间(namespace,下面统称为ns)是动态链接器设计的一个概念(区别于C++语言中的命名空间),其设计的主要目的是为了在进程中做native库资源访问的管控,以达到安全隔离的目的。例如系统native库允许加载系统目录(/system/lib64;/vendor/lib64等)下的native库,但是普通应用native库仅允许加载普通应用native库和ndk库,而不允许直接加载系统native库。 +动态库加载命名空间(namespace,下面统称为ns)是动态链接器设计的一个概念(区别于C++语言中的命名空间),其主要目的是在进程中控制native库资源访问,实现安全隔离。例如,系统native库允许加载系统目录(/system/lib64;/vendor/lib64等)下的native库,但是普通应用native库仅允许加载普通应用native库和ndk库,而不允许直接加载系统native库。 -动态链接器无论是在加载编译依赖(DT_NEEDED)中指定的共享库,还是调用`dlopen`加载指定的共享库,都需要关联到具体的ns。 +动态链接器加载编译依赖(DT_NEEDED)指定的共享库或调用`dlopen`加载指定的共享库时,都需要关联到具体的ns。 -OpenHarmony中动态库加载namespace配置的情况 +OpenHarmony中发的动态库加载namespace配置的情况 - default ns:动态链接器启动时默认创建的ns,它可以搜索`/system/lib{abi};/vendor/lib{abi}`等系统目录路径下的so。 @@ -44,9 +44,9 @@ OpenHarmony中动态库加载namespace配置的情况 ### rpath机制 rpath(run-time path)是在运行时指定共享库搜索路径的机制。该机制允许在可执行文件或共享库中嵌入一个用于在运行时指定库的搜索路径的信息。 -由于命名空间隔离机制,应用仅允许加载对应安装目录拼接native库路径下(例如arm64平台上为`libs/arm64`)的应用native库,当应用程序涉及加载多个native库时,创建多个加载路径会导致无法加载新目录下的native库。这种情况可以通过rpath机制编译时指定搜索路径。 +由于命名空间隔离机制,应用仅允许加载对应安装目录下的native库(例如arm64平台上为`libs/arm64`)。当应用程序涉及加载多个native库时,创建多个加载路径会导致无法加载新目录下的native库。这种情况可以通过rpath机制编译时指定搜索路径。 -例如,应用安装目录`lib/arm64`下的`libhello.so`依赖新创建路径`lib/arm64/module`下的`libworld.so`,那么在应用的`CMakeList.txt`里设置上`rpath`编译选项后编译,使用`readelf`查看`libhello.so`的`rpath`配置如图所示,`$ORIGIN`为`libhello.so`所在路径,运行时即可正常加载module目录下的`libworld.so`。 +例如,应用安装目录`lib/arm64`下的`libhello.so`依赖`lib/arm64/module`下的`libworld.so`。在应用的`CMakeList.txt`中设置`rpath`编译选项后,使用`readelf`查看`libhello.so`的`rpath`配置如图所示,`$ORIGIN`为`libhello.so`所在路径,运行时即可正常加载module目录下的`libworld.so`。 ``` SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) SET(CMAKE_INSTALL_RPATH "\${ORIGIN}/module") @@ -54,18 +54,18 @@ SET(CMAKE_INSTALL_RPATH "\${ORIGIN}/module") ![zh-cn_image_musl_ld_rpath](figures/dl_rpath.png) ### 支持dlclose -支持使用dlclose真实卸载动态库的能力。 +支持使用`dlclose`卸载动态库。 ### 支持symbol-version机制 symbol-version是libc在**动态链接-符号重定位**阶段的符号检索机制,支持不同版本的符号重定位,也可以帮助解决重复符号的问题。可参考LD Version Scripts (GNU Gnulib)。 ### 网络接口select支持fd fortify检测 -宏定义FD_SET和FD_CLR增加了对fd有效值的检查。如果传入的fd不在区间`[0, 1024)`中,将触发abort crash。 +宏定义FD_SET和FD_CLR增加了对fd有效值的检查,如果传入的fd不在区间`[0, 1024)`中,将触发abort crash。 -宏定义FD_ISSET增加了对fd有效值的检查,如果传入的fd不在区间`[0, 1024)`中会返回false。 +宏定义FD_ISSET增加了对fd有效值的检查,如果传入的fd不在区间`[0, 1024)`中,返回false。 ### 全球化支持 -自API12起,newlocale及setlocale接口支持将locale设置C、C.UTF-8、en_US、en_US.UTF-8、zh_CN及zh_CN.UTF-8。新增在zh_CN及zh_CN.UTF-8的locale设置下对strtod_l、wcstod_l和localeconv的支持。注意strtod_l及wcstod_l不支持对十六进制及十六进制小数的转换。 +自API12起,newlocale及setlocale接口支持将locale设置C、C.UTF-8、en_US、en_US.UTF-8、zh_CN及zh_CN.UTF-8。新增在zh_CN及zh_CN.UTF-8的locale设置下支持strtod_l、wcstod_l和localeconv。注意strtod_l及wcstod_l不支持对十六进制及十六进制小数的转换。 ### fdsan功能 -[fdsan使用指导](./fdsan.md)可以帮助检测文件的重复关闭和关闭后使用问题。 \ No newline at end of file +开发者可以使用[fdsan使用指导](./fdsan.md)检测文件的重复关闭和关闭后使用问题。 \ No newline at end of file diff --git a/zh-cn/application-dev/napi/fdsan.md b/zh-cn/application-dev/napi/fdsan.md index bff89d4742c..f6bd7f21b64 100644 --- a/zh-cn/application-dev/napi/fdsan.md +++ b/zh-cn/application-dev/napi/fdsan.md @@ -2,19 +2,19 @@ ## 1. 功能介绍 -fdsan针对的操作对象是文件描述符,主要用于检测不同使用者对相同文件描述符的错误操作,包括多次关闭(double-close)和关闭后使用(use-after-close)。这些文件描述符可以是操作系统中的文件、目录、网络套接字和其他I/O设备等,在程序中,打开文件或套接字会生成一个文件描述符,如果此文件描述符在使用后出现反复关闭、或者关闭后使用等场景,就会造成内存泄露、文件句柄泄露等安全隐患问题。该类问题非常隐蔽,且难以排查,为了更好地检测此类问题,因此引入了此种针对文件描述符错误操作的检测工具fdsan。 +fdsan针对的操作对象是文件描述符,主要用于检测不同使用者对相同文件描述符的错误操作,包括多次关闭(double-close)和关闭后使用(use-after-close)。这些文件描述符可以是操作系统中的文件、目录、网络套接字和其他I/O设备等。在程序中,打开文件或套接字会生成一个文件描述符。如果此文件描述符在使用后出现反复关闭或关闭后使用等场景,会造成内存泄露以及文件句柄泄露等安全隐患问题。这类问题非常隐蔽,且难以排查,为了更好地检测此类问题,因此引入了此种针对文件描述符错误操作的检测工具fdsan。 ## 2. 实现原理 -设计思路:当打开已有文件或创建一个新文件的时候,在得到返回fd后,设置一个关联的tag,来标记fd的属主信息;关闭文件前,检测fd关联的tag,判断是否符合预期(属主信息一致),符合就继续走正常文件关闭流程;如果不符合就是检测到异常,根据设置,调用对应的异常处理。 +当打开已有文件或创建一个新文件的时候,设置一个关联的tag,来标记fd的属主信息。关闭文件前,检测fd关联的tag,判断是否符合预期(属主信息一致),符合就继续走正常文件关闭流程。如果不符合就是检测到异常,根据设置,调用对应的异常处理。 -tag由两部分组成,最高位的8-bit构成type,后面的56-bit构成value。 +tag由两部分组成。最高位的8-bit构成type,后面的56-bit构成value。 -type,标识fd通过何种封装形式进行管理,例如 `FDSAN_OWNER_TYPE_FILE`就表示fd通过普通文件进行管理,type类型在 `fdsan_owner_type`进行定义。 +type,标识fd通过何种封装形式进行管理。例如`FDSAN_OWNER_TYPE_FILE`就表示fd通过普通文件进行管理,type类型在`fdsan_owner_type`进行定义。 -value,则用于标识实际的owner tag。 +value,用于标识实际的owner tag。 - tag构成图示 + tag构成如下图所示: ![](./figures/tag.PNG) @@ -28,16 +28,16 @@ value,则用于标识实际的owner tag。 enum fdsan_error_level fdsan_set_error_level(enum fdsan_error_level new_level); ``` -**描述:** 可以通过`fdsan_set_error_level`设定error_level,error_level用于控制检测到异常后的处理行为。默认error_level为FDSAN_ERROR_LEVEL_WARN_ALWAYS。 +**描述:** 使用`fdsan_set_error_level`设置error_level,以控制检测到异常后的处理行为。默认error_level为FDSAN_ERROR_LEVEL_WARN_ALWAYS。 **参数:** fdsan_error_level | 名称 | 说明 | | -------------------------- | ------------------------------------------------------------ | | `FDSAN_ERROR_LEVEL_DISABLED` | disabled,此level代表什么都不处理。 | -| `FDSAN_ERROR_LEVEL_WARN_ONCE` | warn-once,第一次出现错误时在hilog中发出警告,然后将级别降低为disabled(FDSAN_ERROR_LEVEL_DISABLED)。 | -| `FDSAN_ERROR_LEVEL_WARN_ALWAYS` | warn-always,每次出现错误时都在hilog中发出警告。 | -| `FDSAN_ERROR_LEVEL_FATAL` | fatal,出现错误时调用abort异常退出。 | +| `FDSAN_ERROR_LEVEL_WARN_ONCE` | warn-once,第一次错误时在hilog中警告,然后禁用(FDSAN_ERROR_LEVEL_DISABLED)。 | +| `FDSAN_ERROR_LEVEL_WARN_ALWAYS` | warn-always,每次出现错误时都在hilog中警告。 | +| `FDSAN_ERROR_LEVEL_FATAL` | fatal,出现错误时调用abort退出。 | **返回值:** 返回旧的error_level。 @@ -47,7 +47,7 @@ enum fdsan_error_level fdsan_set_error_level(enum fdsan_error_level new_level); enum fdsan_error_level fdsan_get_error_level(); ``` -**描述:** 可以通过`fdsan_get_error_level`获取error level。 +**描述:** 通过`fdsan_get_error_level`获取error level。 **返回值:** 当前的error_level。 @@ -55,7 +55,7 @@ enum fdsan_error_level fdsan_get_error_level(); ``` uint64_t fdsan_create_owner_tag(enum fdsan_owner_type type, uint64_t tag); ``` -**描述:** 通过传入的type和tag字段,拼接成一个有效的文件描述符的关闭tag。 +**描述:** 通过传入的type和tag字段,拼接成有效的文件描述符的关闭tag。 **参数:** fdsan_owner_type @@ -63,10 +63,10 @@ uint64_t fdsan_create_owner_tag(enum fdsan_owner_type type, uint64_t tag); | -------------------------- | ------------------------------------------------------------ | | `FDSAN_OWNER_TYPE_GENERIC_00` | 默认未使用fd对应的type值。 | | `FDSAN_OWNER_TYPE_GENERIC_FF` | 默认非法fd对应的type值。 | -| `FDSAN_OWNER_TYPE_FILE` | 默认普通文件对应的type值,使用fopen或fdopen打开的文件具有该类型。 | -| `FDSAN_OWNER_TYPE_DIRECTORY` | 默认文件夹对应的type值,使用opendir或fdopendir打开的文件具有该类型。 | -| `FDSAN_OWNER_TYPE_UNIQUE_FD` | 默认unique_fd对应的type值,保留暂未使用。 | -| `FDSAN_OWNER_TYPE_ZIPARCHIVE` | 默认zip压缩文件对应的type值,保留暂未使用。 | +| `FDSAN_OWNER_TYPE_FILE` | 默认普通文件对应的type值,使用fopen或fdopen打开。 | +| `FDSAN_OWNER_TYPE_DIRECTORY` | 默认文件夹对应的type值,使用opendir或fdopendir打开。 | +| `FDSAN_OWNER_TYPE_UNIQUE_FD` | 默认unique_fd对应的type值,保留字段暂未使用。 | +| `FDSAN_OWNER_TYPE_ZIPARCHIVE` | 默认zip压缩文件对应的type值,保留字段暂未使用。 | **返回值:** 返回创建的tag,可以用于fdsan_exchange_owner_tag函数的输入。 @@ -77,15 +77,15 @@ void fdsan_exchange_owner_tag(int fd, uint64_t expected_tag, uint64_t new_tag); ``` **描述:** 修改文件描述符的关闭tag。 -通过fd所以找到对应的FdEntry,判断close_tag值与expected_tag是否一致,一致说明符合预期,可以用new_tag值重新设定对应的FdEntry。 +通过fd找到对应的FdEntry,判断close_tag值与expected_tag是否一致,如果一致,说明符合预期,可以用new_tag值重新设定对应的FdEntry。 -如果不符合,则说明检测到了异常,后续则进行对应的异常处理。 +如果不符合预期,说明检测到了异常,后续进行对应的异常处理。 **参数:** | 名称 | 类型 | 说明 | | -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -| `fd` | int | fd句柄,作为FdEntry的索引。 | +| `fd` | int | fd描述符,作为FdEntry的索引。 | | `expected_tag` | uint64_t | 期望的ownership tag值。 | | `new_tag` | uint64_t | 设置新的ownership tag值。 | @@ -98,13 +98,13 @@ int fdsan_close_with_tag(int fd, uint64_t tag); ``` **描述:** 根据tag描述符关闭文件描述符。 -通过fd找到匹配的FdEntry。如果close_tag与tag相同,则符合预期,可以继续执行文件描述符关闭流程,否则意味着检测到异常。 +通过fd找到匹配的FdEntry。如果close_tag与tag相同,说明符合预期,可以继续执行文件描述符关闭流程;否则,表示检测到异常。 **参数:** | 名称 | 类型 | 说明 | | -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -| `fd` | int | 待关闭的fd句柄。 | +| `fd` | int | 待关闭的fd描述符。 | | `tag` | uint64_t | 期望的ownership tag。 | **返回值:** 0或者-1,0表示close成功,-1表示close失败。 @@ -115,7 +115,7 @@ uint64_t fdsan_get_owner_tag(int fd); ``` **描述:** 根据文件描述符获取tag信息。 -通过fd找到匹配的FdEntry,并获取其对应的close_tag。 +通过fd找到匹配的FdEntry,并获取其close_tag。 **参数:** @@ -131,7 +131,7 @@ const char* fdsan_get_tag_type(uint64_t tag); ``` **描述:** 根据tag计算出对应的type类型。 -通过获取到的tag信息,通过计算获取对应tag中的type信息。 +通过获取到的tag信息,计算对应的type信息。 **参数:** @@ -159,7 +159,7 @@ uint64_t fdsan_get_tag_value(uint64_t tag); ## 4. 使用示例 -如何使用fdsan?这是一个简单的double-close问题: +如何使用fdsan?以下是一个简单的double-close问题示例: ``` void good_write() @@ -204,9 +204,9 @@ int main() ![](./figures/fdsan-error-2.png) -由于每次open返回的fd是顺序分配的,在进入主函数后第一个可用的fd是43,`bad_close`函数中第一次open返回的fd是43,在关闭之后,43就变成了可用的fd,在`good_write`函数中open返回了第一个可用的fd,即43,但是由于`bad_close`函数中存在double-close问题,因此错误的关闭了另一个线程中打开的文件,导致写入失败。 +由于每次open返回的fd是顺序分配的,进入主函数后第一个可用的fd是43,`bad_close`函数中第一次open返回的fd是43,关闭后,43就变成了可用的fd,在`good_write`函数中open返回的第一个可用的fd仍然是43。但由于`bad_close`函数中存在double-close问题,错误的关闭了另一个线程中打开的文件,导致写入失败。 -在fdsan引入之后,有两种方法可以检测这类问题:使用标准库接口或实现具有fdsan的函数接口。 +在引入fdsan之后,检测此类问题有两种方法:使用标准库接口或实现具有fdsan的函数接口。 ### 使用标准库接口 @@ -245,7 +245,7 @@ void good_write() 从这里的错误信息中可以看出FILE接口体的文件被其他人错误的关闭了,FILE接口体的地址可以协助进一步定位。 -此外,可以在代码中使用`fdsan_set_error_level`设置错误等级error_level,设置为Fatal之后如果fdsan检测到错误会提示日志信息同时crash生成堆栈信息用于定位。下面是error_level设置为Fatal之后生成的crash堆栈信息: +此外,可以在代码中使用`fdsan_set_error_level`设置错误等级error_leve。,设置为Fatal之后,如果fdsan检测到错误,会提示日志信息并生成堆栈信息用于定位。下面是error_level设置为Fatal之后生成的crash堆栈信息: ``` Reason:Signal:SIGABRT(SI_TKILL)@0x0000076e from:1902:20010043 @@ -264,7 +264,7 @@ Tid:15312, Name:e.myapplication #10 pc 000700b0 /system/lib/ld-musl-arm.so.1(3de40c79448a2bbced06997e583ef614) ``` -此时,从crash信息中可以看到是bad_close中存在问题,同时crash中也包含了所有打开的文件,协助进行定位,提升效率。 +此时,从crash信息中可以看到bad_close中存在问题。crash中也包含了所有打开的文件,协助进行定位,提升效率。 ``` OpenFiles: @@ -319,7 +319,7 @@ OpenFiles: ``` -### 实现具有fdsan的函数接口 +### 实现具有fdsan功能的函数接口 除了直接使用具有fdsan功能的标准库函数之外,还可以实现具有fdsan的函数接口。fdsan机制主要通过两个接口实现:`fdsan_exchange_owner_tag`和`fdsan_close_with_tag`,fdsan_exchange_owner_tag可以设置对应fd的tag,而fdsan_close_with_tag可以在关闭文件时检查对应的tag是否正确。 @@ -413,7 +413,7 @@ struct fdsan_fd { 这里的实现中使用`fdsan_exchange_owner_tag`在开始时将fd与结构体对象地址绑定,然后在关闭文件时使用`fdsan_close_with_tag`进行检测,预期tag是结构体对象地址。 -在实现了具有fdsan的函数接口之后,可以使用该接口包装fd: +实现具有fdsan的函数接口之后,可以使用该接口包装fd: ```cpp #define TEMP_FILE "/data/local/tmp/test.txt" @@ -437,7 +437,6 @@ void good_write() 此时运行该程序可以检测到另一个线程的double-close问题,详细信息可以参考日志。同样也可以设置error_level为fatal,这样可以使fdsan在检测到crash之后主动crash以获取更多信息。 ## 5. close函数信号安全性说明 -在POSIX标准中,`close`函数原本被定义为信号安全函数(async-signal-safe),这意味着它可以安全地在信号处理函数(signal handler)中调用。然而,在集成了fdsan(File Descriptor Sanitizer)机制的系统实现中,这一性质发生了变化。 - -由于fdsan的实现依赖mmap系统调用,`mmap`本身不是信号安全函数,因此这会导致close函数不再是信号安全的。因此在信号处理函数中请避免使用`close`,可以通过系统调用实现相同功能。 +在POSIX标准中,`close`函数原本被定义为信号安全函数(async-signal-safe),可以安全地在信号处理函数(signal handler)中调用。然而,在集成了fdsan(File Descriptor Sanitizer)机制的系统实现中,这一性质发生了变化。 +由于fdsan的实现依赖mmap系统调用,`mmap`本身不是信号安全函数,因此这会导致close函数不再是信号安全的。在信号处理函数中请避免使用`close`,可以通过系统调用实现相同功能。 \ No newline at end of file -- Gitee