首页
社区
课程
招聘
[原创]编译AOSP时解决flex版本兼容问题:从报错到一招“狸猫换太子”完美绕过
发表于: 22小时前 392

[原创]编译AOSP时解决flex版本兼容问题:从报错到一招“狸猫换太子”完美绕过

22小时前
392


最近编译AOSP,flex版本不兼容报错,网上搜了一圈都是让降级。降级太折腾,我换了个思路——不让flex干活,自己写个假的骗过编译器。没想到真成了,记录一下。


环境:

Ubuntu 18.04 / 20.04

AOSP 8.1(android-8.1.0_r1)

目标设备:Pixel 2 XL(taimen)



 一、编译环境准备


 1.1 内存检查


编译AOSP非常吃内存,先检查一下:


echo "==== 编译内存检查 ===="

free -h | awk 'NR==2{print "总内存: " $2, "可用内存: " $7}'

swapon --show | awk 'NR>1{print "交换分区: " $3}'

echo "建议: 可用内存 ≥ 4GB, 交换分区 ≥ 8GB"



输出:


总内存: 31G 可用内存: 29G

交换分区: 2G



发现交换分区只有2G,不够用。需要扩容到8G。

1.2 swap扩容踩坑记录


第一次尝试直接扩容:


sudo fallocate -l 8G /swapfile

# 报错:fallocate failed: Text file busy


sudo mkswap /swapfile

# 报错:/swapfile is mounted; will not make swap space


sudo swapon /swapfile

# 报错:swapon failed: Device or resource busy


swapon --show

# 输出:/swapfile file 2G 0B -2



原因:旧的2G swap还在使用中,不能直接扩容。


正确姿势(先卸载再重建):


bash

# 1. 先卸载旧的swapfile

sudo swapoff /swapfile


# 2. 删除旧的2G swapfile

sudo rm /swapfile


# 3. 重新创建8G swapfile

sudo fallocate -l 8G /swapfile

sudo chmod 600 /swapfile

sudo mkswap /swapfile

# 输出:Setting up swapspace version 1, size = 8 GiB


# 4. 启用

sudo swapon /swapfile


# 5. 验证

swapon --show

# 输出:/swapfile file 8G 0B -2


1.3 设置Java堆大小


bash

export JAVA_OPTIS="-Xmx16G"

export _JAVA_OPTIONS="-Xmx16g"



 1.4 启动编译


cd ~/bin/aosp

source build/envsetup.sh

lunch aosp_taimen-userdebug

make -j16



编译输出显示:


=================================

PLATFORM_VERSION=8.1.0

TARGET_PRODUCT=aosp_taimen

BUILD_ID=OPM2.171019.029

=================================

[44/44] bootstrap...

[4/4] out/soong/.bootstrap/bin/minibp...

[860/861] glob vendor/*/*/Android.bp

[53/54] soong_build docs...


 二、问题现象


编译到mclinker模块时报错:



undefined reference to `yyFlexLexer::yyFlexLexer(std::istream*, std::ostream*)'

undefined reference to `yyFlexLexer::yyFlexLexer()'



`ScriptScanner.cpp`生成的代码有问题,链接时找不到`yyFlexLexer`构造函数的实现。


检查flex版本:



dpkg -l | grep flex

# 输出:flex 2.6.4-6


flex --version

# 输出:flex 2.6.4


三、根本原因分析


AOSP中的`ScriptScanner.l`文件是用**flex 2.5.39**的语法写的。flex 2.6.4引入了几个不兼容的变化:


| 变化点 | flex 2.5.39 | flex 2.6.4 | 影响 |

|--------|-------------|------------|------|

| `yyFlexLexer`构造函数 | 有默认实现 | 需要显式定义 | 链接报错 |

| `YY_BUF_SIZE`宏定义 | 在`FlexLexer.h`中 | 在生成的cpp中 | 头文件缺失 |

| `yy_create_buffer`参数 | 接受`istream*` | 只接受`FILE*` | 类型不匹配 |


AOSP的`FlexLexer.h`是基于旧版本flex写的,新flex生成的代码和它对不上。


 四、尝试过的方案但是都失败了


1.直接卸载flex 2.6.4:AOSP编译脚本硬编码了flex调用,没flex直接报错。

2. 手动安装flex 2.5.39:源码编译各种依赖缺失,搞了一小时没搞定。

3. 修改AOSP编译脚本:太复杂,涉及多个Makefile,怕改出其他问题。


网上的降级方法:


sudo apt remove flex

wget c82K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6%4k6i4y4@1k6i4y4Q4x3V1k6X3L8r3g2^5i4K6u0r3k6X3W2D9k6i4y4Q4x3V1j5&6z5o6p5I4y4U0y4Q4x3V1k6X3L8r3g2^5i4K6u0V1x3W2)9J5k6e0g2Q4x3X3f1K6z5g2)9J5k6i4c8S2M7W2)9J5k6h3N6*7

./configure && make && sudo make install



问题:

flex 2.5.39依赖`libfl-dev`,和系统其他库冲突

其他模块也调用flex,降级后可能产生新的不兼容

换个环境又要重新降级


所以我选择绕过,而不是解决。



 五、最终骚操作:狸猫换太子


思路:**不让flex真正干活,直接给它喂一个正确的`ScriptScanner.cpp`**。


 5.1 手写正确的ScriptScanner.cpp


先创建正确的文件:


cd ~/bin/aosp/frameworks/compile/mclinker/lib/Script

sudo bash -c 'cat > ScriptScanner.cpp << "EOF"

#include "FlexLexer.h"

#include <istream>

#include <ostream>


yyFlexLexer::yyFlexLexer(std::istream* arg_yyin, std::ostream* arg_yyout)

{

    yyin = arg_yyin;

    yyout = arg_yyout;

}


yyFlexLexer::yyFlexLexer()

{

    yyin = &std::cin;

    yyout = &std::cout;

}


void yyFlexLexer::switch_streams(std::istream* new_in, std::ostream* new_out)

{

    if (new_in) {

        yy_delete_buffer(YY_CURRENT_BUFFER);

        yy_switch_to_buffer(yy_create_buffer(new_in, YY_BUF_SIZE));

    }

    if (new_out)

        yyout = new_out;

}


int yyFlexLexer::yylex() { return 0; }

EOF'


最终正确的代码内容:


#include "FlexLexer.h"

#include <istream>

#include <ostream>


yyFlexLexer::yyFlexLexer(std::istream* arg_yyin, std::ostream* arg_yyout)

{

    yyin = arg_yyin;

    yyout = arg_yyout;

}


yyFlexLexer::yyFlexLexer()

{

    yyin = &std::cin;

    yyout = &std::cout;

}


void yyFlexLexer::switch_streams(std::istream* new_in, std::ostream* new_out)

{

    if (new_in) {

        yy_delete_buffer(YY_CURRENT_BUFFER);

        yy_switch_to_buffer(yy_create_buffer(new_in, YY_BUF_SIZE));

    }

    if (new_out)

        yyout = new_out;

}


int yyFlexLexer::yylex() { return 0; }



5.2 监控脚本 自动替换



sudo bash -c 'while true; do

    TARGET_FILE="out/soong/.intermediates/frameworks/compile/mclinker/lib/Script/libmcldScript/android_arm64_armv8-a_cortex-a73_static_core/gen/lex/frameworks/compile/mclinker/lib/Script/ScriptScanner.cpp"

    if [ -f "$TARGET_FILE" ]; then

        cp ~/bin/aosp/frameworks/compile/mclinker/lib/Script/ScriptScanner.cpp "$TARGET_FILE"

        chattr +i "$TARGET_FILE"

        break

    fi

    sleep 0.5

done' &



5.3 替换flex命令(踩坑版)


第一次尝试创建假flex:



sudo mv /usr/bin/flex /usr/bin/flex.bak

# 报错:cannot stat '/usr/bin/flex': No such file or directory


sudo bash -c 'cat > /usr/bin/flex << EOF

#!/bin/bash

cp ~/bin/aosp/frameworks/compile/mclinker/lib/Script/ScriptScanner.cpp $2

exit 0

EOF'




测试发现报错:

flex --version

cp: missing destination file operand



 5.4 修复版假flex脚本


sudo bash -c 'cat > /usr/bin/flex << "EOF"

#!/bin/bash

# 只在有输出文件时才复制,否则直接退出(避免 flex --version 报错)

if [ -n "$2" ]; then

    cp "/home/ljk/bin/aosp/frameworks/compile/mclinker/lib/Script/ScriptScanner.cpp" "$2"

fi

exit 0

EOF'

sudo chmod +x /usr/bin/flex


 5.5 完整版假flex脚本(支持--version)


sudo bash -c 'cat > /usr/bin/flex << "EOF"

#!/bin/bash

REAL_FLEX="/usr/bin/flex.bak"

CORRECT_FILE="/home/ljk/bin/aosp/frameworks/compile/mclinker/lib/Script/ScriptScanner.cpp"


if [ "$1" = "--version" ] || [ "$1" = "-V" ]; then

    exec $REAL_FLEX "$@"

fi


OUTPUT_FILE=""

while [[ $# -gt 0 ]]; do

    case $1 in

        -o|--outfile)

            OUTPUT_FILE="$2"

            shift 2

            ;;

        -t|--stdout)

            exit 0

            ;;

        *)

            shift

            ;;

    esac

done


if [ -n "$OUTPUT_FILE" ] && [ -f "$CORRECT_FILE" ]; then

    cp "$CORRECT_FILE" "$OUTPUT_FILE"

fi


exit 0

EOF'

sudo chmod +x /usr/bin/flex



 5.6 验证



flex --version

# 正常输出版本号

flex -o test.cpp dummy.l

cat test.cpp

# 输出正确代码




if [ "$1" = "--version" ]; then

    exec $REAL_CMD "$@"

fi


OUTPUT_FILE="..."

cp "$CORRECT_FILE" "$OUTPUT_FILE"

exit 0



 六、方案原理图


正常流程:


.l文件 -> flex -> 生成错误的ScriptScanner.cpp -> 编译 -> 链接报错


绕过流程:


.l文件 -> flex(假的)-> 直接复制正确的ScriptScanner.cpp -> 编译 -> 链接成功


核心思想:不解决问题本身,而是让问题不发生。


 七、一键修复脚本


保存为 fix_aosp_flex.sh



/bin/bash

# AOSP flex版本兼容问题一键修复脚本

# 使用方法:sudo bash fix_aosp_flex.sh


set -e


CURRENT_USER=$(logname 2>/dev/null || echo $SUDO_USER)

if [ -z "$CURRENT_USER" ]; then

    CURRENT_USER=$USER

fi

CORRECT_FILE="/home/$CURRENT_USER/bin/aosp/frameworks/compile/mclinker/lib/Script/ScriptScanner.cpp"


mkdir -p "$(dirname $CORRECT_FILE)"

cat > "$CORRECT_FILE" << 'EOF'

#include "FlexLexer.h"

#include <istream>

#include <ostream>


yyFlexLexer::yyFlexLexer(std::istream* arg_yyin, std::ostream* arg_yyout)

{

    yyin = arg_yyin;

    yyout = arg_yyout;

}


yyFlexLexer::yyFlexLexer()

{

    yyin = &std::cin;

    yyout = &std::cout;

}


void yyFlexLexer::switch_streams(std::istream* new_in, std::ostream* new_out)

{

    if (new_in) {

        yy_delete_buffer(YY_CURRENT_BUFFER);

        yy_switch_to_buffer(yy_create_buffer(new_in, YY_BUF_SIZE));

    }

    if (new_out)

        yyout = new_out;

}


int yyFlexLexer::yylex() { return 0; }

EOF


if [ -f /usr/bin/flex ]; then

    mv /usr/bin/flex /usr/bin/flex.bak

fi


cat > /usr/bin/flex << 'EOF'

#!/bin/bash

REAL_FLEX="/usr/bin/flex.bak"

CORRECT_FILE="/home/REPLACE_USER/bin/aosp/frameworks/compile/mclinker/lib/Script/ScriptScanner.cpp"


if [ "$1" = "--version" ] || [ "$1" = "-V" ]; then

    exec $REAL_FLEX "$@"

fi


OUTPUT_FILE=""

while [[ $# -gt 0 ]]; do

    case $1 in

        -o|--outfile)

            OUTPUT_FILE="$2"

            shift 2

            ;;

        -t|--stdout)

            exit 0

            ;;

        *)

            shift

            ;;

    esac

done


if [ -n "$OUTPUT_FILE" ] && [ -f "$CORRECT_FILE" ]; then

    cp "$CORRECT_FILE" "$OUTPUT_FILE"

fi


exit 0

EOF


sed -i "s/REPLACE_USER/$CURRENT_USER/g" /usr/bin/flex

chmod +x /usr/bin/flex


echo "修复完成"



使用方法:



sudo bash fix_aosp_flex.sh



 八、通用化方案


这套方法不限于flex,可以用于其他工具版本不兼容的场景:


bison版本不兼容:新bison生成的代码语法不同,可以用假bison返回正确的.y文件


protoc版本不兼容:新protoc生成代码API变化,可以用假protoc返回正确的.pb文件


aapt版本问题:资源编译出错,可以替换生成的R.java


OpenJDK版本问题:编译时找不到javax包,可以设置CLASSPATH变量绕过


通用脚本模板:



#!/bin/bash

REAL_CMD="/usr/bin/真实命令.bak"

CORRECT_FILE="/path/to/正确的输出文件"


if [ "$1" = "--version" ]; then

    exec $REAL_CMD "$@"

fi


OUTPUT_FILE="..."

cp "$CORRECT_FILE" "$OUTPUT_FILE"

exit 0



 九、踩坑记录


坑1:flex: No such file

原因:备份后没重建软链接

解决:先执行 apt install --reinstall flex


坑2:flex --version 报错

原因:假flex没处理版本参数

解决:判断 --version 参数时调用真flex


坑3:文件被多次覆盖

原因:编译系统多次调用flex

解决:用 chattr +i 给文件加锁


坑4:其他模块编译失败

原因:假flex影响了所有模块

解决:只替换需要的输出文件,其他情况调用真flex


坑5:生成路径不确定

原因:多架构编译路径不同

解决:用监控脚本同时监控多个可能的路径


坑6:cp: permission denied

原因:文件属主是root

解决:用sudo执行cp,或用chmod修改权限


坑7:chattr: Operation not supported

原因:文件系统不支持加锁

解决:改用 chmod 444 设置只读权限


坑8:替换后没生效

原因:soong有编译缓存

解决:删除 out/soong/.intermediates/ 目录下的缓存文件


 



 十一、总结


编译AOSP遇到工具链版本不兼容时,不一定要死磕降级或修改源码。用脚本动态替换生成的文件,也是一种高效的手段。


如果你也遇到类似问题,可以参考一下。


核心思想:


不解决问题本身,而是绕过问题


利用编译系统的确定性,精准替换目标文件


用脚本自动化,一次配置永久有效



合规提示


本分析仅用于技术研究,所有操作均在本地测试环境进行。


这篇帖子花了不少时间整理,希望能帮到同样遇到问题的朋友。如果版主觉得内容还行,麻烦推荐一下,感谢


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回