asn1c v0.9.29 源码编译、依赖避坑与 DER 编解码验证 简介asn1c工具 v0.9.29 面向需要在 C 语言环境中处理 ASN.1 编码的通信协议、嵌入式及网络开发人员核心作用是把 ASN.1 定义文件编译为可直接调用的 C 头文件与源文件并生成构造、析构、编码、解码接口减少手写序列化代码的工作量。压缩包共 17257 个文件约 189.42MB包含大量 c、h 源码与头文件、asn1 定义及测试样例另有 bin、o、makefile、构建脚本和日志等编译验证产物便于查看编译流程、运行测试和排查问题。已有 1221 人学习下载。资料覆盖 BER、DER、PER 等编码规则相关示例并提供较完整的测试用例、模块定义与工程构建文件读者可据此理解 ASN.1 到 C 的映射方式研究选择性编译、约束与扩展等用法也可将其作为协议栈开发、设备管理协议数据编解码的参考工具包。1. asn1c v0.9.29 的版本坑为什么 ./configure make 会在 bison 那一步停下第一次在一台干净的构建机上装 asn1c v0.9.29我照习惯敲./configure make结果停在 parser 那一步满屏 bison 的 deprecated 警告最后变成 error。asn1c 不大解开源码包也就几百 KB可它是一条完整链路flex/bison 处理 ASN.1 语法、autotools 管理构建、生成的 C 代码再依赖一套 skeleton 运行时。哪一环版本对不上报错往往不是出在 asn1c 自己身上而是三天后生成代码链接时缺符号排查起来像查一个没开日志的服务。这套环境适合手里有 3GPP 规范里那种.asn1文件、要在 C 工程里做编解码的人也适合只想把协议描述转成好调试的 C 结构体的人。装不好不是不能用是出问题之后你根本不知道该从哪一层重来。2. 装 asn1c 之前的依赖清单flex、bison 与 autotools 的配对asn1c 的依赖数量不多但每一个都卡在关键路径上。我一般先把构建工具链一次性装齐再回头确认版本避免 configure 过了一半才发现某个工具是系统自带的老版本。这一章把依赖分成三类编译器与 make、语法分析器 flex/bison、以及只在从源码树构建时才用得到的 autotools。2.1 编译器与构建工具链先确认能出活C 编译器这一块要求不高0.9.29 的代码是 C99 风格gcc 4.4 往上都能编。真正要留意的是 make 的并行行为和头文件搜索路径后面编译生成代码时会反复用到-I。# apt 系环境一次装齐构建工具链 sudo apt-get update sudo apt-get install -y \ build-essential \ autoconf automake libtool \ flex bison \ pkg-config # dnf 系环境等价的一条 # sudo dnf groupinstall -y Development Tools # sudo dnf install -y autoconf automake libtool flex bison pkg-configbuild-essential这一类元包里包含 gcc、g、make 和 libc 头文件是编译 C 项目的最小集合。autoconf/automake/libtool只在你要从 git 源码树构建时才是必需的release 包里已经带了生成好的configure脚本装了也不亏。pkg-config属于顺手装上后面如果想让生成代码链接到别的库它能省掉手写一堆-I和-L的麻烦。装完先做一轮存在性检查把版本记下来出问题时好对照gcc --version | head -n 1 make --version | head -n 1 flex --version bison --version | head -n 1 autoconf --version | head -n 1 automake --version | head -n 1 libtool --version | head -n 1这几条输出能拼出一张环境快照。我习惯把它贴到项目的构建说明里下次换机器时直接比对比事后猜“当时是什么版本”靠谱得多。2.2 flex 与 bison真正会卡住的两个asn1c 内部用 flex 做词法、bison 做语法把 ASN.1 规格解析成内部语法树。configure 阶段对这两个工具只做存在性探测不严格卡版本所以版本不匹配的后果会延迟到make阶段才暴露典型表现是编译parser.c或lexer.c时一堆警告升级成错误。经验上flex 2.5.35 以上都没问题2.6.x 是现在发行版里的主流版本直接可用。bison 是变量最多的地方0.9.29 的语法文件是按 bison 2.x 的写法组织的遇到 bison 3.x 会刷出deprecated directive之类的警告多数情况下还能编过但在某些 3.7 以后的版本上会因为 yacc 兼容性收紧直接报语法错误。# 看 flex 和 bison 实际被解析到哪个二进制 command -v flex bison readlink -f $(command -v flex) readlink -f $(command -v bison) # 如果系统里同时存在多个版本确认默认命中哪个 ls -1 /usr/bin/bison* 2/dev/nullcommand -v看的是 PATH 里第一个命中的可执行文件readlink -f把它展开成真实路径。有些环境里/usr/bin/bison是个指向 alternatives 系统的软链这两条命令能看出它最终指向哪个版本。如果发现默认命中的是 3.x 而 make 阶段报语法错误常见做法是装一个 2.7 的并行版本在构建 asn1c 的那次 shell 里临时把 PATH 指过去而不是去动系统默认版本。提示不要为了装 asn1c 去卸系统自带的 bison系统里可能还有别的构建依赖它。用 PATH 或--with-bison之类的前缀参数做局部覆盖影响面最小。另外有一类失败很隐蔽flex 和 bison 都装了版本也对但 m4 缺失或者 liby 没装全--version能打出来真到生成 parser 时才报错。遇到这种先把 m4 补上再重试。2.3 从 release 包还是源码树开始autogen.sh 的时机这一步经常被跳过但它决定你后面要不要跟 autotools 的版本问题较劲。asn1c 的发布包asn1c-0.9.29.tar.gz里已经包含configure、Makefile.in这些生成物解包后直接 configure 即可。如果你是从版本库导出的源码树里面只有configure.ac和Makefile.am必须先跑autogen.sh生成 configure而这一步对 autoconf、automake、libtool 的版本比较敏感三者版本差太多会报possibly undefined macro之类的错误。判断方法很简单解包后看根目录有没有configure文件tar -xzf asn1c-0.9.29.tar.gz cd asn1c-0.9.29 ls -l configure configure.ac autogen.sh 2/dev/null如果configure存在跳过 autogen直接进下一章。如果只有configure.ac和autogen.sh就先跑一遍# 仅在源码树缺少 configure 时执行 sh ./autogen.shautogen.sh内部会依次调用 aclocal、autoheader、autoconf、automake任何一步版本不满足都会中断。这类失败信息通常指向具体宏名按提示把对应的 autotools 组件升级即可不用整机升级。我一般优先选 release 包能少踩一层构建系统的坑——这不是玄学是 autotools 的版本兼容矩阵确实比 C 代码本身更难伺候。3. 源码编译 asn1c v0.9.29configure 参数与 /opt 隔离安装依赖齐了之后安装本身其实很快真正需要想清楚的是装到哪里。asn1c 是个纯命令行工具加上一套 skeleton 头文件装到系统默认的/usr/local会和以后可能装的其它版本打架所以我习惯按版本号放进/opt再做一个不带版本号的软链指过去。3.1 解包与目录布局为什么把它放 /opt/opt/asn1c-0.9.29这种布局的好处是可回滚。你哪天要试 0.9.28 或者更新的版本装到隔壁目录改一下软链就切过去了旧版本原样留着。系统目录里只留一个软链PATH 里也只加一条不会出现“到底在用哪个 asn1c”的问题。# 约定的目录结构 # /opt/asn1c-0.9.29/ 实际安装前缀 # /opt/asn1c - asn1c-0.9.29 稳定软链 sudo mkdir -p /opt tar -xzf asn1c-0.9.29.tar.gz -C /tmp cd /tmp/asn1c-0.9.29解包到/tmp而不是直接在/opt里编译是为了把构建产物和安装产物分开。编译目录里会生成大量.o和中间文件装在/opt里会显得很脏后面想比对文件也费劲。3.2 configure 参数逐项过--prefix、CFLAGS 与静默规则configure 这一步我一般只动三个地方安装前缀、编译选项、以及把静默规则关掉。关掉静默规则是为了在出错时看到完整的编译命令行排错时能直接复制出来重跑。./configure \ --prefix/opt/asn1c-0.9.29 \ --disable-silent-rules \ CFLAGS-O2 -g -fno-omit-frame-pointer--prefix决定安装后的布局可执行文件进binskeleton 头文件和源码进share/asn1c后面生成代码时要用的就是share/asn1c这个路径。--disable-silent-rules让 make 输出完整命令行默认的CC xxx.c简写看不出实际用了哪些宏和包含路径。CFLAGS里加-g是为了万一崩了能拿到有意义的栈-fno-omit-frame-pointer是给后续可能的性能分析留后路代价很小。configure 的输出值得扫一眼重点看它探测到的编译器、flex、bison 路径是不是你预期的那个。如果 PATH 里有多个版本这里显示的就是最终生效的版本比事后猜要省事。# 把 configure 的关键探测结果留档 ./configure --prefix/opt/asn1c-0.9.29 --disable-silent-rules \ CFLAGS-O2 -g 21 | tee /tmp/asn1c-configure.log grep -Ei flex|bison|compiler|checking for gcc /tmp/asn1c-configure.logtee把完整输出存一份grep只挑关键字看。这份日志在 configure 失败时特别有用因为报错往往只有最后几行前面的探测过程才是定位原因的地方。3.3 make 并行度与 make install 之后的 PATH编译这一步有个小陷阱asn1c 的语法分析器是单个比较大的 C 文件make -j开太高容易把内存吃满在小的构建机上表现为编译器被 OOM 杀掉报错信息还很不直观。我一般压到 4 路。make -j4 sudo make install # 做稳定软链以后切版本只改这一条 sudo ln -sfn /opt/asn1c-0.9.29 /opt/asn1c # PATH 只加不带版本号的那条 echo export PATH/opt/asn1c/bin:$PATH ~/.bashrc source ~/.bashrcln -sfn里-n的作用是当目标已经是软链时把它当文件处理否则会在软链目录里再建一层。PATH 加/opt/asn1c/bin而不是带版本号的路径是为了切版本时不用改环境变量。安装完成后验证一下工具本身能不能起来asn1c -v正常情况下会打印版本号。如果提示命令找不到先确认command -v asn1c的输出是不是/opt/asn1c/bin/asn1c再确认这个软链指向的目录里确实有可执行文件。到这一步工具就装好了但离“能用”还差一步skeleton 路径和生成代码的编译规则。4. 用 asn1c 生成第一套 C 编解码器命令行参数与 skeleton 路径asn1c 装好之后它的角色是把.asn1规格翻译成 C 结构体和编解码函数。这一步的参数选择会直接决定生成代码能不能编译、类型名会不会撞车、以及后面调用编码函数时有没有可用的描述符。4.1 一份最小 .asn1 规格与生成命令先造一个够小、能手工验证的规格一个 SEQUENCE两个成员一个定长整数、一个定长字节串。用这个规格跑通整条链路比直接丢一份几千行的 3GPP 规范进去更容易定位问题。mkdir -p ~/work/asn1-demo cd ~/work/asn1-demo cat demo.asn1 EOF DemoModule DEFINITIONS :: BEGIN Msg :: SEQUENCE { id INTEGER (0..65535), payload OCTET STRING (SIZE(1..64)) } END EOF asn1c -S /opt/asn1c/share/asn1c \ -fcompound-names \ -fincludes-quoted \ -gen-PER \ -pduall \ -D . \ demo.asn1-S指向安装目录下的 skeleton 路径生成的代码会按这个路径去引用运行时头文件省得后面手工补-I。-fcompound-names让复合类型用带父类型前缀的名字避免多个模块里出现同名匿名类型时符号冲突这个参数基本属于必加。-fincludes-quoted让生成的头文件用双引号包含同级头文件而不是尖括号配合本地编译更顺。-gen-PER额外生成 PER 编解码器如果协议只走 DER 可以不加。-pduall生成 PDU 描述符集合-D .指定输出目录为当前目录。跑完之后看输出目录应该有一组.c/.h文件加上一个Makefile.am.sample。如果 asn1c 只打印了几行信息、目录里什么都没多大概率是-D指向了一个不存在的路径或者规格文件本身解析失败但错误信息被刷过去了。4.2 生成目录里的东西skeleton 与 Makefile.am.sample生成的.c文件依赖 asn1c 自带的 skeleton 运行时这些文件在/opt/asn1c/share/asn1c下面是一组asn_application.h、der_encoder.c之类的通用代码。编译生成代码时这个目录必须进头文件搜索路径。Makefile.am.sample是 automake 用的模板不是能直接 make 的 Makefile。想走 autotools 就把它改成Makefile.am自己补configure.ac和Makefile.am的骨架只是想把生成代码编出来做验证直接用 gcc 更快# 直接编译生成的所有 .c先只出目标文件看有没有头文件问题 gcc -I. -I/opt/asn1c/share/asn1c -c *.c # 打成静态库后面主程序链接时用 ar rcs libdemo.a *.o-I.让编译器找到当前目录下的生成头文件-I/opt/asn1c/share/asn1c让它找到 skeleton 头文件。ar rcs把目标文件归档成静态库rcs分别表示替换已有成员、创建库、写索引。这一步如果报asn_application.h: No such file or directory说明 skeleton 路径不对回头确认/opt/asn1c/share/asn1c真实存在。4.3 参数取舍-pduall 与 -gen-PER 什么时候该加-pduall会生成一组 PDU 描述符方便你按名字在运行时查找类型。如果只是自己写死调用其实不加也行但加上之后调试时可以用统一入口打印结构代价是生成文件多几个。-gen-PER只在协议走 PER 编码时才需要DER/BER 场景加了只是多编译一些代码不影响正确性但会拖慢构建。如果规格里类型名重复严重还可以考虑-fno-constraints之类的开关来简化生成的约束检查但这是以牺牲部分运行时校验为代价的除非遇到性能瓶颈我一般不关。参数选择上没有绝对正确的组合原则是先让生成代码编译通过再按实际编码需求逐步加减每加一个参数就重新编译一遍别一次改五项然后对着报错猜是哪一项引起的。5. 排查与避坑从 configure 失败到生成代码链接不上asn1c 这套东西的报错有个共同特点错误信息出现在下游根因在上游。下面五条是我在不同机器上反复遇到的按“现象 → 原因 → 解决”记下来遇到同类问题时可以按顺序对照。5.1 configure 报找不到 flex 或 bison现象./configure最后输出error: cannot find flex或cannot find bison但flex --version明明能打出来。原因通常是 configure 用的 PATH 和你当前 shell 的 PATH 不一致比如在 sudo 下跑 configure或者 env 里重置过 PATH。解决是先确认command -v flex的路径再用显式 PATH 跑一次 configurePATH/usr/local/bin:/usr/bin:/bin ./configure --prefix/opt/asn1c-0.9.29如果这样能过说明原来的 PATH 里有个同名但不可执行的占位文件把它清理掉或者调整顺序即可。5.2 make 阶段 bison 报语法错误现象编译到语法分析器那一步输出大量deprecated directive然后出现syntax errormake 中断。原因是 bison 3.x 对老式 yacc 语法收紧而 0.9.29 的语法文件是按 2.x 写的。解决是装一个 bison 2.7 的并行版本只在这次构建的 shell 里把它放到 PATH 前面# 假设 2.7 版本装在 /opt/bison-2.7 export PATH/opt/bison-2.7/bin:$PATH make -j4不要动系统默认的 bison其它项目的构建可能依赖新版本。5.3 生成代码编译时报找不到 asn_application.h现象在生成目录里执行gcc -c *.c报fatal error: asn_application.h: No such file or directory。原因是编译命令没有包含 skeleton 目录或者 asn1c 安装时 skeleton 被装到了非预期路径。解决是先确认路径存在再补-Ils /opt/asn1c/share/asn1c/asn_application.h gcc -I. -I/opt/asn1c/share/asn1c -c *.c如果文件确实不在那里用find /opt/asn1c -name asn_application.h找一下实际位置多半是 configure 时 prefix 写错了。5.4 链接时报 undefined reference to asn_DEF_xxx现象主程序链接生成代码时报某个asn_DEF_Msg未定义。原因是只编译了主程序用到的那个.c而asn_DEF_Msg定义在另一个生成文件里或者静态库没链接进来。解决是把生成目录下所有.c都编进去或者确认libdemo.a在链接命令里出现且顺序在目标文件之后gcc -I. -I/opt/asn1c/share/asn1c -o demo_dump main.c libdemo.a链接顺序在 gcc 里是有讲究的库要放在使用它的目标文件后面否则符号解析不到。5.5 编码函数返回 -1buffer 明明够大现象der_encode_to_buffer返回负数但缓冲区尺寸看起来远大于预期。原因通常是结构体里有成员没初始化或者约束不满足比如给INTEGER (0..65535)赋了一个越界值编码器在检查约束时直接失败。解决是先确认每个字段都被正确赋值、指针成员都指向有效内存再检查约束范围/* 初始化后再赋值避免结构体里有未初始化字段 */ Msg_t msg; memset(msg, 0, sizeof(msg)); msg.id 42; /* 必须落在 0..65535 */如果约束确实无法满足可以考虑用-fno-constraints重新生成但优先修数据别急着关校验。这类问题在没开日志的情况下看起来像玄学实际上编码器返回 -1 之前已经把原因记在内部错误栈里了配合调试器看一眼就能定位。6. 装完怎么验证跑通一次 DER 编解码闭环验证 asn1c 装没装好最直接的办法不是看版本号而是拿第 4 章那份规格走一遍“编码 → 解码 → 比对字段”的闭环。写一个二十来行的主程序把生成的结构体填上值编码到内存缓冲区再解回来打印字段。#include stdio.h #include string.h #include Msg.h #include asn_application.h int main(void) { Msg_t msg; memset(msg, 0, sizeof(msg)); msg.id 42; /* OCTET_STRING 会自己托管一份内存传栈上字符串即可 */ OCTET_STRING_fromBuf(msg.payload, hello-asn1c, 11); unsigned char buf[256]; ssize_t encoded der_encode_to_buffer(asn_DEF_Msg, msg, buf, sizeof(buf)); if (encoded 0) { fprintf(stderr, encode failed\n); return 1; } printf(encoded %zd bytes\n, encoded); Msg_t *decoded NULL; asn_dec_rval_t rv der_decode(0, asn_DEF_Msg, (void **)decoded, buf, encoded); if (rv.code ! RC_OK) { fprintf(stderr, decode failed at byte %zu\n, rv.consumed); return 1; } printf(decoded id%ld payload%.*s\n, (long)decoded-id, (int)decoded-payload.size, decoded-payload.buf); ASN_STRUCT_FREE(asn_DEF_Msg, decoded); ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_Msg, msg); return 0; }der_encode_to_buffer返回编码字节数负数表示失败der_decode返回的asn_dec_rval_t里code为RC_OK才算成功consumed告诉你解到第几个字节。两个释放函数别省ASN_STRUCT_FREE释放解码器分配的结构体ASN_STRUCT_FREE_CONTENTS_ONLY只释放栈上结构体内部的动态内存混用会出内存问题。编译时把主程序和生成代码一起编进去gcc -I. -I/opt/asn1c/share/asn1c -o demo_dump main.c libdemo.a ./demo_dump预期输出类似encoded 15 bytes decoded id42 payloadhello-asn1c只要这两行能对上说明 asn1c 本体、skeleton 运行时、生成代码、编译链接四个环节全部通了。如果编码通过但解码失败重点看约束和字段类型如果编译链接失败回到第 5 章按报错对号入座。从那以后我每次在新机器上装完 asn1c都强制走一遍这个闭环不管多急都不跳过——版本号能骗人跑通的字节流不会。希望帮到你。本文还有配套的精品资源点击获取