一个令人蛋疼的链接错误

背景

我们APP的引擎包engine.so,包含了A、B、C三个工程,但每次都是源码形式编译,导致svn上存在多份相同代码拷贝。非常不科学。。。核心的B工程由我维护,整个SO编译工程由多个人维护。于是乎偶进行了一次升级:将B源代码从so工程中解耦:将B打成一个静态库,然后编译So的时候链接静态库。

开始行动

基本思路:将B的源码包到一个guide_b外壳工程中,ndk-build生成guide_b.so 的同时诱导生成libB.a静态库,然后这个libB.a可以发布。

android的编译目录实在蛋疼,eclipse下设置路径难用的很。还是习惯命令行下的ndk-build,但是ndk-build的前提是:当前路径下必须有一个jni文件夹,且它里面有一个Android.mk文件,以及srcxx子目录,里面放了源代码

为了遵循android ndk编译这个蛋疼的规定,而且又不破坏B工程的项目结构(旧的支持xcode和vs编译),在build目录下增加一个android子目录,创建Android.mk文件,然后通过python脚本将所需源码文件拷贝到android/jni下,所有这些操作通过一个run.bat批处理脚本串联,build完成以后删掉拷贝的源码和编译中间结果。

将python引入编译非常灵活。

踩雷了

拿到编译出的libB.a,放入engine.so编译工程中,修改mk文件,头部加入静态库预编译段,

include $(CLEAR_VARS)

LOCAL_MODULE := BModule

LOCAL_SRC_FILES := libB.a

include $(PREBUILT_STATIC_LIBRARY)

在so编译部分加载BModule模块:LOCAL_STATIC_LIBRARIES := BModule

编译so非常顺利。但是拿到APP工程中,傻眼了build以后的包只有地图文字,没有底图了。

泪奔了,libB.a和engine.so的编译过程都自我感觉非常之良好啊。。。尼玛。只能不断自我打击,暗示一定什么环节出问题了。。没有crash,文字标注还有,但是底图一直渲染不成功。。。

排雷的过程

1)将B的源码放入SO编译工程,最终so包没问题。只能怀疑自己的libB.a编译有问题或者链接有问题咯,于是进行第一个尝试:

不直接进行源码编译,而是通过ndk自带的arm-linux-androideabi-ar.exe工具,将源码编译时产生的一系列.o文件,手工编译成.a,然后链接这个.a,发现build的so包还是有问题。

source =>	*.o	=>	engine.so

*.o	=>	libB.a	=>	engine.so
	ar

上述两种路径:第一条表示源码编译,ok;第二条是源码编译的中间结果.o文件,手工通过ar打包成 libB.a,然后链接libB.a,就有问题。真是见鬼了。!。

2)躲不过了,只能source中增加log,第一次build成libB.a,然后第二次build成engine.so,最后拷贝到android工程中,build APK。

source =>	libB.a	=>	engine.so	=>	apk.

整个蛋疼的定位过程得益于windows的批处理脚本,可以实现半自动化。

不断重复这个过程,不断调整log精度。最终定位到底图瓦片绘制失败的问题:坐标转化函数GetGeoRect的结果错误,导致绘制时候取不到数据

定位问题的原因

尼玛,疑问重重:源码编译没问题,build成静态库,然后再链接就有问题。代码没有改动,为何单单这个核心函数出问题呢?

进一步验证:

GetGeoRect是一个类静态函数,写一个main.cpp,测试libB.a中的该函数是否正确,显式传入制定的参数,build成可执行程序,然后推到手机上执行,具体参见http://blog.csdn.net/ryfdizuo/article/details/28891649 结果函数执行结果正确。。说明libB.a内这些函数本身没有问题。问题出在so包的链接阶段

跟组内一经验丰富的哥们讨论,那天恰好周五,下班前还是没结果。。。晚上回去后回一直在回想编译的整个过程,想起他无心的一句话:“是不是可能有重复的定义啥的“。终于想到了一个问题,A工程里面B工程的两个头文件,当时为了解耦其他人将两个头文件重复拷贝了一份,(明显触犯了DRY原则)如下, yy.h中包含了静态函数的GetGeoRect定义,vv.h中包含了render_config_t结构体定义,而GetGeoRect中使用了render_config_t结构体。

我最近一次B模块升级,更新了vv.h中的render_config_t结构体,内部增加了一个256的char数组。。

附图新旧vv.h头文件中的render_config_t结构体:

旧的:新的:

第二天周六,按耐不住奔到公司,更新A模块中的vv.h头文件,build出的so包终于正确了。总算是找到问题所在了:

A和B工程中的vv.h和yy.h文件重复,B中vv.h文件最近被更新过。

1)当A、B工程均采用源码编译时,最终SO中的GetGeoRect函数内部使用了最新的render_config_t结构体布局(编译器可能根据文件的修改时间等等作为比对条件吧?),因此底图绘制正确。

2)但是当B工程build成静态库libB.a时,此时build成SO时,GetGeoRect函数定义采用了A工程中源码(编译器可能更加信赖源码吧),因此render_config_t也采用了旧的内存布局。因此当调用SO运行时,传入GetGeoRect函数的render_config_t的对象采用最新的内存布局,但是内部实际上是按旧的结构体解析和执行,当然结果就完全错了。

一句话总结问题

文件重复拷贝 =》 导致两个地方文件更新不同步 =》导致同一个结构体有两份定义,两种内存布局 =》 导致SO中的全局函数中,libB.a中没有被重复定义的函数采用了新的结构体布局,被重复的函数则采用了旧的内存布局 =》最终结果:传入GetGeoRect函数之前的结构体是新布局,函数内部按旧布局解析,所有参数错乱。

附录

静态库,只是.o文件的集合打包。

动态库文件:没有作用域区分,所有函数都以唯一的全局函数形式存在,C++的函数会被name-mangling处理,如果我们希望直接通过函数名获取so中的函数地址,则使用extern C包裹防止函数名被修改。通过objdump工具可以验证。

一个令人蛋疼的链接错误

时间: 2024-08-16 07:08:29

一个令人蛋疼的链接错误的相关文章

一个令人蛋疼的NDK链接错误

背景 我们APP的引擎包engine.so.包括了A.B.C三个project.但每次都是源代码形式编译,导致svn上存在多份同样代码拷贝. 很不科学. ..核心的Bproject由我维护.整个SO编译project由多个人维护. 于是乎偶进行了一次升级:将B源代码从soproject中解耦:将B打成一个静态库,然后编译So的时候链接静态库. 開始行动 基本思路:将B的源代码包到一个guide_b外壳project中.ndk-build生成guide_b.so 的同一时候诱导生成libB.a静态

一个令人蛋疼的 Microsoft.AspNet.FriendlyUrls

我一个项目都基本上做完了,结果部署到我服务器的时候结果一直报404 找不到 一看global.asax有个路由注册的代码 public static void RegisterRoutes(RouteCollection routes) { routes.EnableFriendlyUrls(); } 放在IIS中 始终是找不到类似伪静态的地址 如:localhost/default这种 找了两天 我还把多个IIS里面的模块都一一比对 还是不对,最后在老外的一篇文章中终于解决了这个问题 http

缺少libz.dylib库的时候引起的一个链接错误

导入ASI框架,遇到缺少libz.dylib库的时候引起的一个链接错误 Undefined symbols for architecture armv7s: "_inflate", referenced from: -[ASIDataDecompressor uncompressBytes:length:error:] in ASIDataDecompressor.o "_deflate", referenced from: -[ASIDataCompressor

模板函数(template function)出现编译链接错误(link error)之解析

总的结论:    将template function 或者 template class的完整定义直接放在.h文件中,然后加到要使用这些template function的.cpp文件中. 1. 现象描述 类似于参考文献[1],当我们以如下方式使用模板函数时,会出现模板函数声明.定义分离带来的链接错误: 1 // File "foo.h" 2 template<typename T> 3 extern void foo(); 1 // File "foo.cpp

Duplicate Symbol链接错误的原因总结和解决方法[转]

from:http://www.cocoachina.com/bbs/read.php?tid=177492 duplicate symbol是一种常见的链接错误,不像编译错误那样可以直接定位到问题的所在.但是经过一段时间的总结,发现这种错误总是有一些规律可以找的.例如,我们有如下的最简单的两个类代码: //  ClassA.h#import <Foundation/Foundation.h>@interface ClassA : NSObject@end //  ClassA.m#impor

关于我遇到的“LNK 2019无法解析的外部符号”的链接错误

昨天在调试程序的时候出现了"LNK 2019无法解析的外部符号"的问题(VS2008),依照网上说的方法都没有解决这个问题,最后在项目文件里发现有两个同名的可是不在同一个目录下的cpp文件,而产生LNK错误的cpp文件里有对这个重名文件的引用,结果导致了当中一个cpp文件产生的目标文件(obj)覆盖了真正须要的cpp产生的OBJ文件,导致链接的时候找不到指定的符号而出现了链接错误. 以下给个图说明一下我遇到的情况吧: 在项目中.同一时候包括了目录1和目录2中的全部cpp文件,而ref.

__system_property_get arm64 链接错误

undefined reference to `__system_property_get' 这一系列的函数在arm64的头文件有定义,但是在libc.so中却找不到实现. header: sys/system_properties.h __system_property_get __system_property_set __system_property_find __system_property_read __system_property_find_nth __system_prope

Treat wchar_t as built-in type不一致导致的链接错误

今天用VS2013新建了一个工程,生成时出现很多怪异的链接错误,比如: error LNK2019: unresolved external symbol "__declspec(dllimport) public: static class MTString __cdecl MTString::fromWCharArray(wchar_t const *,int)"([email protected]@@[email protected][email protected]) refe

VC++的链接错误LNK2005 已经在*.obj中定义

LNK2005错误--重复定义错误 形成的原因: 1. 重复定义全局变量.可能存在两种情况: A. 对于一些初学编程的程序员,有时候会以为需要使用全局变量的地方就可以使用定义申明一下.其实这是错误的,全局变量是针对整个工程的.正确的应该是在一个CPP文件中定义如下:int g_Test;那么在使用的CPP文件中就应该使用:extern int g_Test即可,如果还是使用int g_Test,那么就会产生LNK2005错误,一般错误错误信息类似:AAA.obj error LNK2005 in