news 2026/10/27 3:39:42

asn1c v0.9.29 源码编译、依赖避坑与 DER 编解码验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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-config

build-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/null

command -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.sh

autogen.sh内部会依次调用 aclocal、autoheader、autoconf、automake,任何一步版本不满足都会中断。这类失败信息通常指向具体宏名,按提示把对应的 autotools 组件升级即可,不用整机升级。我一般优先选 release 包,能少踩一层构建系统的坑——这不是玄学,是 autotools 的版本兼容矩阵确实比 C 代码本身更难伺候。

3. 源码编译 asn1c v0.9.29:configure 参数与 /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决定安装后的布局:可执行文件进bin,skeleton 头文件和源码进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" 2>&1 | tee /tmp/asn1c-configure.log grep -Ei 'flex|bison|compiler|checking for gcc' /tmp/asn1c-configure.log

tee把完整输出存一份,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 ~/.bashrc

ln -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 \ -pdu=all \ -D . \ demo.asn1

-S指向安装目录下的 skeleton 路径,生成的代码会按这个路径去引用运行时头文件,省得后面手工补-I。-fcompound-names让复合类型用带父类型前缀的名字,避免多个模块里出现同名匿名类型时符号冲突,这个参数基本属于必加。-fincludes-quoted让生成的头文件用双引号包含同级头文件,而不是尖括号,配合本地编译更顺。-gen-PER额外生成 PER 编解码器,如果协议只走 DER 可以不加。-pdu=all生成 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 参数取舍:-pdu=all 与 -gen-PER 什么时候该加

-pdu=all会生成一组 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 跑一次 configure:

PATH="/usr/local/bin:/usr/bin:/bin" ./configure --prefix=/opt/asn1c-0.9.29

如果这样能过,说明原来的 PATH 里有个同名但不可执行的占位文件,把它清理掉或者调整顺序即可。

5.2 make 阶段 bison 报语法错误

现象:编译到语法分析器那一步,输出大量deprecated directive然后出现syntax error,make 中断。原因是 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 被装到了非预期路径。解决是先确认路径存在,再补-I:

ls /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 编码函数返回 -1,buffer 明明够大

现象: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 id=42 payload=hello-asn1c

只要这两行能对上,说明 asn1c 本体、skeleton 运行时、生成代码、编译链接四个环节全部通了。如果编码通过但解码失败,重点看约束和字段类型;如果编译链接失败,回到第 5 章按报错对号入座。

从那以后我每次在新机器上装完 asn1c,都强制走一遍这个闭环,不管多急都不跳过——版本号能骗人,跑通的字节流不会。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/27 3:39:39

SSD固件工程师必掌握的Linux命令调试体系

1. 为什么SSD固件工程师必须亲手敲Linux命令&#xff0c;而不是只靠GUI或封装脚本“SSD固件开发”这六个字听起来很硬核&#xff0c;但很多人第一反应是&#xff1a;这不就是写C代码、跑仿真、烧片、测时序吗&#xff1f;跟Linux命令有什么关系&#xff1f;我见过太多刚入行的同…

作者头像 李华
网站建设 2026/10/27 3:39:35

虚拟化环境下服务器集体故障排查与恢复实录

1. 虚拟化环境下的服务器集体故障实录那天凌晨3点17分&#xff0c;监控系统的告警短信把我从睡梦中惊醒。9台关键业务服务器同时离线&#xff0c;控制面板上一片飘红。作为运维负责人&#xff0c;我瞬间清醒——这不是普通的硬件故障&#xff0c;因为我们使用的正是当下流行的P…

作者头像 李华
网站建设 2026/10/27 3:39:13

VMware Tools 12 全解析:安装配置、参数调优与避坑巡检

简介&#xff1a;VMware Tools 12 是面向 VMware 虚拟化平台用户与运维人员的增强组件&#xff0c;主要用于改善虚拟机与宿主机之间的驱动兼容、图形显示、文件互传和时间同步等问题&#xff0c;适合桌面虚拟化、测试环境及企业级 vSphere 场景。压缩包共 1568 个文件&#xff…

作者头像 李华
网站建设 2026/10/27 3:37:40

ButterKnife替代方案:ViewBinding与DataBinding技术解析

1. ButterKnife的历史地位与当前困境ButterKnife作为Android开发史上最具影响力的视图绑定库之一&#xff0c;自2013年由Jake Wharton发布以来&#xff0c;凭借其简洁的注解语法和高效的编译时处理机制&#xff0c;彻底改变了Android开发者处理视图绑定的方式。在Android Studi…

作者头像 李华
网站建设 2026/10/27 3:37:23

oh-my-codex:增强AI编程CLI的工程化实践

1. 项目概述&#xff1a;oh-my-codex 的定位与价值在开发者工具链中&#xff0c;命令行界面&#xff08;CLI&#xff09;始终扮演着关键角色。oh-my-codex 作为 OpenAI Codex CLI 的工程化增强方案&#xff0c;其核心价值在于将实验性 AI 编程能力转化为可集成到日常开发流程的…

作者头像 李华
网站建设 2026/10/27 3:37:23

Android Studio开发本地备忘录应用实战指南

1. 项目概述&#xff1a;基于Android Studio的本地备忘录开发这个纯安卓项目采用Android Studio作为开发环境&#xff0c;实现了一个功能完整的本地记事本应用。核心功能包括备忘录的增删改查&#xff0c;所有数据都存储在设备本地的SQLite数据库中。不同于需要网络连接的云笔记…

作者头像 李华