如何从已编译C++可执行文件中提取#define定义的预处理器版本值
问题1:从编译后的可执行文件提取#define定义的版本号是否可行?
完全可行。
你之前用strings没搜到版本号,核心原因是你用grep -i "version"过滤结果时,只匹配到了带version关键字的变量名versionText,而版本号本身是类似x.x.x的纯字符串,不带version关键字,直接被过滤掉了。#define定义的字符串常量只要在代码中被实际引用(你赋值给versionText就属于有效引用),编译后会默认存储在ELF可执行文件的.rodata只读数据段,只要你没有开启极端的字符串混淆编译参数、也没有用strip命令删除所有字符串段,就一定可以通过strings命令提取到。你可以用已知的历史版本号做测试,执行如下命令就能匹配到对应结果:
tar -xOf <归档路径> <可执行文件名> | strings | grep "x\.x\.x"
把x.x.x替换成你确认过的某个历史版本号即可验证。
问题2:反编译器能否适配该需求?
可以但完全没必要。
反编译工具需要额外安装、占用资源多,对于嵌入式Linux设备来说性价比极低。strings属于binutils工具集,几乎所有Linux发行版默认预装,完全能满足你的提取需求,不需要用更重的反编译器。
问题3:其他无需单独存储版本号文件的实现方案
方案1:给版本号加固定前缀(改造成本最低)
修改源码中的版本号宏定义,给版本字符串加一个独一无二的固定前缀,比如:
#define VERSION "MY_CUSTOM_APP_VERSION:1.2.3"
后续提取时直接过滤前缀即可,不需要提前知道具体版本号:
tar -xOf <归档路径> <可执行文件名> | strings | grep "MY_CUSTOM_APP_VERSION:" | awk -F ':' '{print $2}'
该方案仅修改宏定义格式,不影响原有业务逻辑,也不需要新增额外文件,完美符合向后兼容要求。如果要适配没有加前缀的老版本备份,你可以用正则匹配固定格式的版本号,比如三段式版本号可以用:
tar -xOf <归档路径> <可执行文件名> | strings | grep -E "^[0-9]+\.[0-9]+\.[0-9]+$"
方案2:写入ELF文件注释段(无业务侵入)
在源码中加入一行汇编指令,直接把版本号写入ELF文件的.comment注释段,不会对程序运行逻辑产生任何影响:
asm(".ident \"MY_APP_VERSION=" VERSION "\"");
后续可以用readelf命令直接读取:
tar -xOf <归档路径> <可执行文件名> | readelf -p .comment /dev/stdin | grep "MY_APP_VERSION"
方案3:Qt内置方案
你使用的是Qt 5.5.1,可以在程序入口调用QCoreApplication::setApplicationVersion(VERSION)设置全局应用版本,Qt会自动处理该字符串的存储,你依然可以用上述字符串提取的方式拿到版本号,同时还能在程序内部直接调用QCoreApplication::applicationVersion()获取版本值,复用性更高。
内容的提问来源于stack exchange,提问作者Xonu

