昨天下午,格蠹的小伙伴Jacky在LINUX下构建NDB的新版本时,遇到一组链接错误:
/usr/local/bin/ld: ../bin/libndi.so: undefined reference to `NtpAgent::ReadTarget(ND_TARGET_SPACE, unsigned long, unsigned int, unsigned char, unsigned char*, unsigned int*)'
/usr/local/bin/ld: ../bin/libndi.so: undefined reference to `NtpAgent::Request(unsigned int, unsigned char*, unsigned int, unsigned char*, unsigned int, unsigned int*, unsigned int*)'
/usr/local/bin/ld: ../bin/libndi.so: undefined reference to `NtpAgent::WriteTarget(ND_TARGET_SPACE, unsigned long, unsigned int, unsigned char, unsigned char const*, unsigned int*)'根据gcc报告的错误信息,是libndi模块里使用了三个C++的方法:
NtpAgent::ReadTarget
NtpAgent::Request
NtpAgent::WriteTarget
但是没能找到实现。
值得说明的是,NDB的LINUX版本已经测试了一个多月,这并不是第一次构建,以前是成功的。另外,NtpAgent类有很多个方法,其它方法没有问题,唯独这三个方法有问题。
当时,我忙着准备晚上的直播,没顾得上细看,只是建议Jacky用readelf -s工具来排查。
Jacky想了很多种方法排查原因,比如核对这几个函数,在这几个函数的源代码里注入错误,确保它们是被编译过的,等等,但都没能找到根源,问题依旧。
今天上班后,我准备亲自看一下这个问题。我找到这三个函数的源代码,仔细看它们在头文件里的原型声明,以及在CPP里的实现。
virtual HRESULT Request(ULONG ulServiceID, PBYTE pbData2Server, ULONG ulSize2Server,
PBYTE pbData2Client, ULONG ulSize2Client, PULONG pulDataReturned = NULL,
PULONG pulQuickData = NULL);
virtual HRESULT ReadTarget(
ND_TARGET_SPACE Space,
/* [in] */ uint64_t Address,
/* [in] */ DWORD dwNbElemToRead,
/* [in] */ BYTE bAccessWidth,
/* [size_is][out] */ BYTE* pbReadBuffer,
/* [out] */ DWORD* pdwNbElementEffectRead);NDB的代码本来是在Windows上开发的,所以上面的函数原型明显带着浓浓的windows风格。最突出的是如下两个特征:
特征1:函数名和参数名都是大小写混合。
特征2:类型名使用完全的大写,比如BYTE,DWORD等。
近年来在Linux上做了很多开发工作之后,我已经喜欢上了Linux下流行的小写风格。因此,对于上面这样的代码,我已经有点不习惯了。
对于上面上面的两个特征,函数名和参数名大小写混合不算是大的问题,只是风格的问题。但是特征2则问题很大,常常是邪恶之源。
特征2是把基本的类型做重定义,一般被称为重定义基础类型。这种做法在今天看来是极其糟糕的做法。因为它常常导致歧义和故障,特别是在涉及到跨平台的时候。
因为此,我一看到这些大写的重定义类型,我立刻想到,可能是这些类型重定义出问题了。
顺着这个思路深挖,我继续思考是哪个类型出问题了。
我首先怀疑的是ULONG,因为在Windows和Linux两个平台上,long的定义是不同的,Windows上的long无论是32位还是64位,都是32位长(相当于int32_t),而Linux下,long在32位时是32位,在64位下是64位。

搜索代码,果然发现openocd的一个头文件里,把ULONG定义为了unsigned long,其实如果按照windows上的约定,应该是uint32_t。
为了明确到底是哪个类型引发的错误,我决定上工具观察,也就是执行readelf来观察编译器实际产生的符号。因为编译器会对C++的方法做mangling,看不清类型名,所以要增加-C选项来demangle,又因为C++的方法包含类名,比较长,后面的部分会被省略,所以要加上--wide选项。
readelf -s -C libntp.so --wide | grep NtpAgent | grep WriteTarget
有了这些选项后,readelf清楚地显示出了编译器产生的输出函数原型。
geduer@ulan:/gewu/NanoCode/nd3/untp$ readelf -s -C libntp.so --wide | grep NtpAgent | grep WriteTarget
490: 000000000003a630 284 FUNC GLOBAL DEFAULT 11 NtpAgent::WriteTarget(ND_TARGET_SPACE, unsigned long, unsigned int, wchar_t, wchar_t const*, unsigned int*)
3271: 000000000003a630 284 FUNC GLOBAL DEFAULT 11 NtpAgent::WriteTarget(ND_TARGET_SPACE, unsigned long, unsigned int, wchar_t, wchar_t const*, unsigned int*)果然与链接器所需要的原型不一样。
但出乎预料的是,差异没有出在long上,而是BYTE上,需要的是char,而导出的是wchat_t。
wchar_t, wchar_t const*
看到wchar_t,我非常惊讶,BYTE怎么会被定义为wchat_t呢?
原来前几天,另一个小伙伴在解决utf16和unicode的问题时,做了临时修改,没有及时恢复。

故事讲完了,归纳一下,对于C++的函数,考虑到函数重载,所以链接器链接时不仅要求类名和函数名严格匹配,还要求参数类型也要严格一致。而邪恶的类型重定义容易导致类型不一致,特别撰文,希望格友们在遇到类似问题时可以快速解决,避免耽误时间。
顺便预报一个活动,本周六下午,第二代挥码枪线上发布会,欢迎新老朋友们参加。

(写文章很辛苦,恳请各位读者点击“在看”,也欢迎转发)
*************************************************
正心诚意,格物致知,以人文情怀审视软件,以软件技术改变人生
扫描下方二维码或者在微信中搜索“盛格塾”小程序,可以阅读更多文章和有声读物

也欢迎关注格友公众号

文章讲述了Jacky在Linux环境下构建NDB遇到的链接错误,源于libndi模块中的NtpAgent类的三个方法定义与Windows风格冲突,特别是类型重定义(如BYTE被误定义为wchar_t)导致的问题。作者强调了类型一致性在C++函数链接中的重要性。

被折叠的 条评论
为什么被折叠?



