资讯动态

OpenSSL 构建配置体系详解:Configurations 目录中的目标配置、build.info 与 unified 构建系统

发布时间:2026/9/10 11:20:11 来源:尧图企业网站定制
OpenSSL 构建配置体系详解Configurations 目录中的目标配置、build.info 与 unified 构建系统【免费下载链接】opensslGeneral purpose TLS and crypto library项目地址: https://gitcode.com/GitHub_Trending/ope/openssl在 OpenSSL 中Configurations/目录是整个构建系统的大脑它用 Perl 哈希表描述各目标平台的编译器、汇编器与链接规则用分散在源码树各处的build.info文件声明构建产物及其依赖再由Configure脚本统一消化生成 Makefile 等构建文件。本文基于 Configurations/README.md 的完整内容展开并结合 Configure 脚本与 Configurations/10-main.conf 等真实配置文件帮助读者掌握自定义平台目标、编写build.info以及理解 unified 构建方案的源码级实现原理。一、目录总览三类文件各司其职Configurations/README.md 开宗明义地说明该目录包含几组用途不同的文件*.conf—— 目标平台配置target platform configurations如 Configurations/10-main.conf、Configurations/00-base-templates.conf 以及各15-*.conf、50-*.conf平台专属文件*.tmpl—— 构建文件模板build file templates如 Configurations/unix-Makefile.tmpl、Configurations/windows-makefile.tmpl、Configurations/common0.tmpl 与 Configurations/descrip.mms.tmpl*.pm—— 主Configure脚本使用的辅助脚本/模块如 Configurations/platform.pm、Configurations/gentemplate.pm、Configurations/unix-checker.pm 和 Configurations/windows-checker.pm。从源码结构看Configure在启动时按文件名排序依次读取该目录下所有*.conf文件见 Configure 中glob(catfile(dirname($0), Configurations, *.conf))的加载逻辑这正是00-、10-、15-、50-这类数字前缀的意义基础模板先被加载主配置随后平台专属配置最后。此外还支持通过环境变量指定本地配置目录把用户自定义的*.conf叠加进来Configure。二、目标平台配置的哈希表结构2.1 配置目标 一张全局哈希表中的条目配置目标configuration target是关于不同平台及其能力的事实集合被组织成一张哈希表每个条目代表一个具体目标。文档特别强调配置目标名在所有配置文件之间必须唯一。Configure脚本会检查某个配置文件是否出现遮蔽shadow其他文件中同名目标的配置。这一检查在 Configure 的read_config子程序中真实存在——一旦发现重名条目脚本会打印出shadow pre-existing config targets with the same name并直接die终止配置。2.2 各配置键key的完整含义Configurations/README.md 给出了每个哈希表条目中有效键的权威定义这是自定义平台目标时必须查阅的参考逐项说明如下继承与模板inherit_from指定要继承其值的其他目标递归解析详见 2.3 节template设为 1 表示该条目并非真正的平台目标而是供其他目标继承的模板。系统身份sys_id对于难以自动识别的系统给出系统身份标识。在 Configurations/10-main.conf 中可以看到真实用例vos-gcc目标显式设置sys_id VOS。功能开关enable/disable启用/禁用特定配置功能必须是单词数组同一功能同时出现在两者中时disable优先。编译器与汇编器as汇编器命令Unix 上通常不用因为直接用 C 编译器处理asflags默认汇编器标志cppC 预处理器命令通常不给出因为构建文件默认值已足够cppflags默认预处理器标志definescppflags的替代形式以MACROvalue或仅MACRO的字符串数组给出宏定义includescppflags的替代形式以字符串数组给出包含目录ccC 编译器命令通常为cc、gcc或clang同时用于把目标文件和库链接成最终程序cxxC 编译器命令当程序至少含一个 C 源文件时用于链接cflags/cxxflags默认 C/C 编译标志cxxflags未设置时取cflags的值。链接器ld链接器命令通常不定义即使用编译器命令代替文档注明此项为将来使用而保留尚未实现lflags链接应用程序、共享库或 DSO 时的默认标志ex_libs链接共享库、DSO 或程序所需的额外库该值同时会被写入$(libdir)/pkgconfig/libcrypto.pc的Libs.private。共享库与模块shared_cppflags/shared_cflag/shared_ldflag编译和链接共享库时使用的额外标志shared_cflag通常类似-fPICmodule_cppflags/module_cflags/module_ldflags功能与对应的shared_属性相同但用于构建 DSO未设置时取对应shared_属性的值。归档工具ar库归档命令默认ar同样标注为未实现的预留项arflags归档命令使用的标志Unix 上包含命令字母默认r。从 Configurations/00-base-templates.conf 可见BASE_unix模板实际使用的是ARFLAGS qcBSD 风格 ar 的字母ranlib归档索引命令若系统存在则默认ranlib。BASE_unix中该值是一个代码块sub { which(...ranlib) ? ranlib : }即通过运行时探测决定取值——这是值可以是代码块特性的真实示例。文件扩展名与变体shared_extension共享库文件名扩展obj_extension/exe_extension目标文件/可执行文件扩展预留项Unix 上默认.o与空字符串shlib_variant插入在共享库基础名与扩展之间的变体标识。在 BSD、Linux、Solaris、MacOS/X 等 unixy 平台上它支持安装自定义 OpenSSL 库而不与系统已有的 OpenSSL 构建冲突。变体标识成为库 SONAME 和符号版本的一部分MacOS/X 不使用符号版本。文档给出的例子默认构建产生libssl.so - libssl.so.1.1软链值即 SONAME若目标定义设置shlib_variant -abc则产生libssl.so - libssl-abc.so.1.1符号版本变为OPENSSL_ABC_version而非默认的OPENSSL_version。插入符号版本的字符串由变体标识映射而来所有字母转大写所有非字母数字字符转_。线程与 DSO 方案thread_scheme平台使用的线程类型。当前已知取值(unknown)、pthreads、uithreads即 Solaris 线程、winthreads。除(unknown)外的实际值目前被忽略但可能在未来使用。文档脚注 [2] 说明OpenSSL 默认带线程能力构建除非用户指定no-threads当值为(unknown)时用户必须向Configure提供若干编译标志。Configurations/10-main.conf 中大量目标如gcc正设置了thread_scheme (unknown)dso_scheme要构建的动态共享对象类型主要影响模块modules也可用于其他目的。有效取值DLFCNdlopen() 等、DLFCN_NO_H使用 dlopen() 等但没有 fcntl.h 的系统、DLshl_load() 等、WIN32与VMS。汇编架构选择器asm_arch编译汇编源码使用的架构作为build.info文件中的选择器uplink_arch编译 uplink 源码使用的架构。之所以与asm_arch分开是因为 uplink 即使指定了no-asm也会被编译尽管它包含汇编源码。这一点在 Configurations/10-main.conf 的 Windows 配置中可印证注释明确写着 assembler is still used to compile uplink shimperlasm_scheme生成汇编实现文件所用的 perlasm 方法如nasm、masm、win32n等shared_target共享库构建方法用途有二——作为shared_info.pl见 Configurations/shared-info.pl中条目的索引以及链接脚本生成的选择器。为兼顾两个用途索引应以-shared结尾该后缀在用作链接脚本选择器时会被移除且后者仅在定义了shared_defflag时使用build_scheme生成 Makefile 所用的方案。最简单形式是方案名的字符串也可以取字符串列表形式列表第一个元素是方案名。当前唯一认可的方案是unified且该项必须是数组第一个元素为unified第二个元素是标识平台家族的词。Configurations/00-base-templates.conf 中三个基础模板分别设置build_scheme [ unified, unix ]、[ unified, windows ]、[ unified, VMS ]与文档描述完全一致。multilib 与 bn_opsmultilib在支持同一库多份实现典型为 32 位与 64 位变体的系统上用于把不同变体放入不同目录multibin与multilib配合把不同变体的二进制放入不同目录bn_ops构建选项历史上仅是大数选项故名 bn_ops。它是描述对该目标平台最优的算法实现参数的单词字符串如大数所用整数类型、某些密码算法的不同实现方式。文档明确建议要完全理解其含义最好去读受影响的源码。有效取值取值含义THIRTY_TWO_BIT大数字段limb为 32 位未指定任何选项时是默认值在任意受支持系统上可用除非汇编代码隐含了更宽的 limb 尺寸BN_LLONG大数字段为 32 位但计算内部使用 64 位unsigned long longSIXTY_FOUR_BIT_LONG大数字段为 64 位且sizeof(long)为 8SIXTY_FOUR_BIT大数字段为 64 位但运行环境是 ILP32RC4_CHARRC4 密钥调度由unsigned char构成注意不应再用于新配置目标RC4_INTRC4 密钥调度由unsigned int构成同样不应再用于新配置目标实际配置中gcc目标设置bn_ops BN_LLONGConfigurations/10-main.conf与bn_ops在 32 位通用平台上使用 64 位中间运算的历史惯例相符。2.3 继承机制inherit_from的完整规则文档脚注 [1] 对继承机制给出了权威定义inherit_from指示从哪些其他配置继承数据递归解析继承提供一组默认值可被继承方配置中同名的键值覆盖注意 1任何配置表都可以被用作模板注意 2纯模板具有template 1属性且不能被用作构建目标若inherit_from数组给出多个配置同名属性的值会以空格分隔拼接起来。这使得可以把多个针对不同配置方面的小模板组合成一个完整配置值还可以是sub { /* 你的代码 */ }形式的代码块该代码块以该键的所有继承值作为参数被调用。实际上字符串拼接正是用sub { join( ,_) }对继承值列表完成的。文档给出的经典示例laughter 条目继承 foo 与 bar 两个模板后的处理结果foo { template 1, haha ha ha, hoho ho, ignored This should not appear in the end result, }, bar { template 1, haha ah, hoho haho, hehe hehe }, laughter { inherit_from [ foo, bar ], hehe sub { join( ,(_,!!!)) }, ignored , }处理之后laughter 条目变为laughter { haha ha ha ah, hoho ho haho, hehe hehe !!!, ignored }这一机制在真实仓库中得到了充分运用Configurations/00-base-templates.conf 定义了DEFAULTS、BASE_common、BASE_unix、BASE_Windows、BASE_VMS等纯模板均带template 1Configurations/10-main.conf 中的具体目标如gcc { inherit_from [ BASE_unix ], ... }则通过继承获得完整能力。值得注意的是BASE_common的defines与includes就是代码块它们根据$disabled{zlib}、$withargs{zlib_include}等运行时状态动态计算宏定义与包含目录验证了值为代码块在实际配置中的用法。2.4 历史遗留的字符串格式已弃用历史上目标配置曾是冒号分隔的字符串。这种用法已被弃用其形态为target {cc}:{cflags}:{unistd}:{thread_cflag}:{sys_id}:{lflags}: {bn_ops}:{cpuid_obj}:{bn_obj}:{ec_obj}:{des_obj}:{aes_obj}: {bf_obj}:{md5_obj}:{sha1_obj}:{cast_obj}:{rc4_obj}: {rmd160_obj}:{rc5_obj}:{wp_obj}:{cmll_obj}:{modes_obj}: {padlock_obj}:{perlasm_scheme}:{dso_scheme}:{shared_target}: {shared_cflag}:{shared_ldflag}:{shared_extension}:{ranlib}: {arflags}:{multilib}了解这一格式有助于阅读旧版文档与第三方发行包的构建脚本但新代码一律应使用哈希表形式。2.5 三种链接对象与链接命令构成文档脚注 [3] 描述了 OpenSSL 从目标文件或静态库链接的三类对象共享库即 libcrypto 与 libssl共享对象有时称动态库即模块modules应用程序即apps/openssl与所有测试程序。链接的大致构成花括号内为上文配置项shared libraries: {ld} $(CFLAGS) {lflags} {shared_ldflag} -o libfoo.so \ foo/something.o foo/somethingelse.o {ex_libs} shared objects: {ld} $(CFLAGS) {lflags} {module_ldflags} -o libeng.so \ blah1.o blah2.o -lcrypto {ex_libs} applications: {ld} $(CFLAGS) {lflags} -o app \ app1.o utils.o -lssl -lcrypto {ex_libs}脚注 [4] 补充了一个易被忽略的细节上述属性存在lib_、dso_或bin_前缀的变体。这些变体在专门构建库、DSO 或程序模块时替换不带前缀的属性。例如 Configurations/00-base-templates.conf 中BASE_unix单独定义了bin_cflags追加-fPIE与bin_lflags追加-pie只影响程序bin而不影响库——正是该机制的实例。三、build.info 文件构建信息的最小声明语言3.1 基本约定散布在源码树各处的build.info文件包含构建和发行 OpenSSL 所需的最小信息使用一种简单却相当有力的语言来决定要构建什么、由哪些源码构成、以及文件间关系。核心约定对每个build.info文件源码文件的引用相对于该 build.info 所在目录若构建树与源码树分离构建文件的引用则相对于对应构建目录处理时每一行都经过 Perl 模块 Text::Template使用{-与-}作为定界符%config与%target两个哈希会被传给 Perl 代码片段连同$sourcedir和$builddir当前 build.info 文件的源码目录位置及对应构建目录均相对于构建树顶部Configure天生只知道顶层build.info文件其他含有 build.info 的目录必须显式声明才会被进一步查找语法是SUBDIRSsomething someelse。顶层 build.info 是这一机制的活教材开头即声明SUBDIRScrypto ssl apps util fuzz providers doc并配合 Text::Template 片段根据!$disabled{tests}、!$disabled{demos}条件追加SUBDIRStest与SUBDIRSdemos随后用LIBSlibcrypto libssl、INCLUDE[libcrypto]. include、DEPEND[libssl]libcrypto声明了两个核心库及其依赖关系——与文档描述的语法逐字对应。3.2 声明构建产物PROGRAMSfoo bar LIBSlibsomething MODULESlibeng SCRIPTSmyhackPROGRAMS、LIBS、MODULES 中提到的文件必须不带扩展名扩展名由构建文件模板根据平台自行确定。3.3 指定源码与依赖为每个构建产物声明其源码PROGRAMSfoo bar SOURCE[foo]foo.c common.c SOURCE[bar]bar.c extra.c common.c声明其他依赖DEPEND[foo]libsomething DEPEND[libbar]libsomethingelse文档特别澄清了一个微妙区别SOURCE给出的文件预期位于源码树中而DEPEND给出的文件预期位于构建树中虽然可以说libsomething也是源。显式依赖静态库的写法DEPEND[foo]libsomething.a DEPEND[libbar]libsomethingelse.a这应极少使用并需谨慎确保所用平台确实支持。文档举了 Windows 的例子原生 Windows 构建不支持同时构建静态库与 DLL因此在 Windows 上使用静态库只能在no-shared配置下进行。这一用法在 Configurations/README-design.md 的示例中也出现MODULES_NO_INSTossltest的模块显式DEPEND[ossltest]../libcrypto.a链接静态变体。3.4 仅共享库源码、包含目录与宏定义某些源码文件只想包含在库的共享形态中SHARED_SOURCE[libfoo]dllmain.c指定构建源码时使用的额外包含路径INCLUDE[foo]include指定应定义的 C 宏DEFINE[foo]FOO BAR1仓库顶层 build.info 中还展示了DEPEND的一个进阶用法DEPEND[]空索引表示依赖项应无条件地先于其他一切被构建例如对include/openssl/asn1.h等头文件依赖的声明。3.5 GENERATE从其他文件生成源码GENERATE[foo.s]asm/something.pl $(CFLAGS) GENERATE[bar.s]asm/bar.S每条 GENERATE 行的值是一行命令或其中的一部分。Configure对命令本身不设规则只要求第一项是生成器文件命令行如何被处理、输出如何捕获完全交由构建文件模板决定。限制每个 GENERATE 只能有一条命令。生成器文件本身也可能依赖其他文件例如依赖其他 Perl 模块的 Perl 脚本可用DEPEND表达DEPEND[asm/something.pl]../perlasm/Foo.pm若无法轻易指定确切文件、但仍需指定包含目录可用INCLUDEINCLUDE[asm/something.pl]../perlasm3.6 条件语句 IF / ELSIF / ELSE / ENDIF最后build.info支持简单的条件使用IF[1] something ELSIF[2] something other ELSE something else ENDIF方括号内的表达式被当作 Perl 字符串求值Perl 认为其为真则真否则为假所以上例中 something 会被采用因为 1 为真。结合 Text::Template可以基于传入变量构造条件例如IF[{- $disabled{shared} -}] LIBSlibcrypto SOURCE[libcrypto]... ELSE LIBSlibfoo SOURCE[libfoo]... ENDIF顶层 build.info 中IF[{- !$disabled{tests} -}] ... SUBDIRStest ... ENDIF正是该模式的真实应用。四、unified 构建系统从 build.info 到 Makefile4.1 模板文件的命名与优先级Build files 在不同系统上叫不同名字Unix 类系统上是MakefileVMS 的 MMS 上是descrip.mmsWindows 的nmake上是makefile等等。使用 unified 构建系统要求目标配置设置build_scheme、build_file与build_command三项。对build_file给出的任何名字unified 系统期望在Configurations/目录中找到一个以构建文件名字加.tmpl后缀命名的模板文件若可能产生歧义则使用build_scheme列表第二项与build_file名的组合。例如build_file为Makefile时模板可以是Configurations/Makefile.tmpl或Configurations/unix-Makefile.tmpl若两者同时存在Configurations/unix-Makefile.tmpl优先。当前仓库实际提供的是 Configurations/unix-Makefile.tmpl 与 Configurations/windows-makefile.tmpl命名恰好符合平台族前缀 构建文件名的消歧规则。构建文件模板用 Text::Template 处理以{-和-}为定界符其中的 Perl 代码片段生成配置相关内容且可以访问 configdata.pm 中的所有哈希变量。4.2 模板必须提供的规则生成函数构建文件模板期望在{- ... -}包裹的 Perl 代码片段中定义以下函数它们都应返回所生成行的字符串generatesrc—— 生成从输入生成源文件的构建文件行。调用形式为generatesrc(src PATH/TO/tobegenerated, generator [ generatingfile, ... ], generator_incs [ INCL/PATH, ... ], generator_deps [ dep1, ... ], incs [ INCL/PATH, ... ], deps [ dep1, ... ], intent one of libs, dso, bin );src是待生成文件名generator是生成命令或其一部分第一项预期是生成器文件generatesrc()负责分析该文件如何被应用、结果如何捕获generator_incs与generator_deps是生成器文件自身的包含目录与依赖文件incs与deps在生成最终产物时以$(CC)作为中间步骤使用的包含目录与依赖文件intent指示生成文件的用途。src2obj—— 生成从源文件及其关联数据构建目标文件的构建文件行src2obj(obj PATH/TO/objectfile, srcs [ PATH/TO/sourcefile, ... ], deps [ dep1, ... ], incs [ INCL/PATH, ... ], intent one of lib, dso, bin );obj是带.o扩展的意图目标文件src2obj()期望把它转换为适合平台的形式srcs是源文件列表第一项是与目标文件直接对应的源文件deps是显式依赖列表incs是头文件目录列表intent指示目标文件的用途。obj2lib—— 生成从目标文件构建静态库Unix 术语中即libfoo.a的行obj2lib(lib PATH/TO/libfile, objs [ PATH/TO/objectfile, ... ]);lib是不带扩展名的意图库文件名obj2lib期望自己补上扩展名objs是用于构建该库的目标文件列表。libobj2shlib—— 向后兼容函数用法与obj2shlib相同历史上当合适时期望从对应静态库构建共享库。注意从静态库构建共享库现已弃用因为二者不再共享目标文件尝试这样做会失败。obj2shlib—— 生成从对应目标文件构建可共享目标库即libfoo.so的行obj2shlib(shlib PATH/TO/shlibfile, lib PATH/TO/libfile, objs [ PATH/TO/objectfile, ... ], deps [ PATH/TO/otherlibfile, ... ]);lib是基础静态库文件名不带扩展名在需要辅助文件时很有用例如 Windows 上的导入库shlib是对应共享库名同样不带扩展名deps是该库需要链接的其他库列表也不带扩展名objs是构建该库的目标文件列表。obj2dso—— 生成从目标文件构建动态共享对象文件的行obj2dso(lib PATH/TO/libfile, objs [ PATH/TO/objectfile, ... ], deps [ PATH/TO/otherlibfile, ... ]);与obj2shlib几乎相同但意图是构建一个可以在运行时加载的可共享库即插件。obj2bin—— 生成从目标文件构建可执行文件的行obj2bin(bin PATH/TO/binfile, objs [ PATH/TO/objectfile, ... ], deps [ PATH/TO/libfile, ... ]);bin是不带扩展名的意图可执行文件名obj2bin期望自己补上objs是目标文件列表deps是程序需要链接的库文件列表同样不带扩展名。in2script—— 生成从输入构建脚本文件的行in2script(script PATH/TO/scriptfile, sources [ PATH/TO/infile, ... ]);script是意图脚本文件名sources是构建该脚本所用的源文件列表。在所有情况下文件路径相对于构建树顶部构建文件的动作以构建树顶部为当前工作目录运行。文档还特别叮嘱定义这些函数的代码片段必须以一段你认为合适的字符串结尾若无其他内容至少要以如下形式收尾防止残留值混入最终 Makefile; # Make sure no lingering values end up in the Makefile -}4.3 数据流build.info → %unified_info → 模板规则Configurations/README-design.md 进一步阐明了 unified 方案的数据链路unified 方案的全部数据来自源码树中各处可见的build.info文件Configure从中构建出一个名为%unified_info的哈希表数据库存储于构建树顶部的 configdata.pm构建树可能与源码树相同也可能不同随后由驱动模板 Configurations/common0.tmpl 遍历%unified_info的所有信息借助构建文件模板中定义的规则生成函数生成构建最终产物及中间文件的全部规则。%unified_info的索引包括depends来自 DEPEND、modules来自 MODULES、generate来自 GENERATE、includes来自 INCLUDE、install按programs/libraries/modules/scripts类型分类的安装清单、libraries、programs、scripts、sources与shared_sources。该文档还给出了一个完整的假想小型工程示例展示了顶层、apps/、crypto/、ssl/、engines/五个 build.info 如何被消化digest为统一的%unified_info表以及最终对libcrypto生成obj2shlib、obj2lib、src2obj、generatesrc系列调用的完整过程值得逐行对照阅读。此外该设计文档补充了 README 未展开的细节存在带_NO_INST后缀的变体声明如PROGRAM_NO_INST用于指定不应被安装的最终产物——README 主文档的MODULESlibeng示例对应的正是MODULES_NO_INST这类内部测试模块场景。五、Configure 辅助脚本检查器checker scriptsConfigure使用本目录下的辅助脚本。检查器脚本按平台家族组织用于检查配置与构建所用工具的完整性。所用的检查器脚本名为{build_platform}-{build_file}-checker.pm或{build_platform}-checker.pm其中{build_platform}是配置目标数据中build_scheme列表的第二个元素{build_file}是同一目标数据中的build_file。以 Unix 为例build_scheme [ unified, unix ]、build_file Makefile会定位到unix-Makefile-checker.pm找不到则回退到unix-checker.pm——仓库中实际存在的 Configurations/unix-checker.pm 与 Configurations/windows-checker.pm 即对应 unix/windows 两个平台家族的最终回退项。检查通过时脚本以非零表达式结束检查失败时脚本可以以零结束或以die结束。这一反直觉的约定成功为非零、失败为零直接源于 Perluse模块的返回值语义模块加载成功时use表达式为真非零。六、小结配置体系的分层与扩展路径综合 Configurations/README.md 及其配套设计文档OpenSSL 的配置体系可概括为四层模板层*.conf中的template 1条目如 Configurations/00-base-templates.conf 的BASE_unix/BASE_Windows/BASE_VMS沉淀平台家族的默认值含动态代码块目标层*.conf中的具体目标如 Configurations/10-main.conf 的gcc、solaris-common以及 Configurations/15-android.conf、Configurations/50-os390.conf 等平台专属文件通过inherit_from组合模板并覆盖差异项目标名全局唯一由 Configure 强制信息层各目录下的build.info由 build.info 顶层文件经SUBDIRS串联以最小声明语言描述产物、源码、依赖与生成规则支持 Text::Template 条件化渲染层*.tmpl模板如 Configurations/unix-Makefile.tmpl通过generatesrc/src2obj/obj2lib/obj2shlib/obj2dso/obj2bin/in2script七个规则生成函数把%unified_info数据库渲染成具体平台的构建文件并由 checker 脚本Configurations/unix-checker.pm 等在配置阶段校验工具链完整性。对需要在非常规平台上构建 OpenSSL 的开发者而言正确的扩展路径是在Configurations/下新增一个.conf文件定义自己的目标继承既有BASE_*模板、按 2.2 节键表填写差异项复用现有*.tmpl模板——这套机制正是 OpenSSL 能横跨 Linux、Windows、VMS、VOS、Android 等异构平台仍维持单一构建入口的根本原因。【免费下载链接】opensslGeneral purpose TLS and crypto library项目地址: https://gitcode.com/GitHub_Trending/ope/openssl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价