8BTCCI: 13790.68 -0.22% 8BTCVI: 6779.46 -2.24% 24H成交额: ¥3339.18亿 -7.05% 总市值: ¥19011.60亿 -0.51%
VC编译调试比特币源码

VC编译调试比特币源码

007龙少 发布在 链头条 技术指南 98128

目录

前言

第一节 依赖库

  1. 库OpenSSL
  2. 库Boost
  3. 库Berkeley DB
  4. 库QT
  5. QT的VC插件
  6. 库Protobuf
第二节 创建VC工程

第三节 添加代码文件

第四节 设置工程属性

第五节 修改代码

  1. 工程Bitcoin-cli、Bitcoind
  2. 工程bitcoin-qt、bitcoin-qt-test
  3. 工程test_bitcoin
第六节 调试建议


前言

作为不擅长Linux开发的VC程序员,面对开源的比特币源码,只好想办法用VC编译调试比特币源码了,除非你能拿出很多时间精力学习Linux开发(目前Linux开发很吃香,借此转平台似乎也是好事)。

通过最近一阵的努力,各个程序编译成功,可运行,但同步区块有个问题,但其他功能可以正常使用,因个人精力有限,遗留问题后续再处理。如果有已经可以用VC调试BTC源码的朋友知道问题所在,还请指明。

同步区块半小时后不再同步了,重启程序重建数据库索引后又可以继续同步,耗时太长,后续的发送、接收比特币也就不能调试了。

编译、调试、修改比特币源码,是熟悉比特币源码的好机会,如果能发现BUG,提交到github,也是对比特币的贡献。

工程已经做好,但太大了,依赖太多,有些细节还需要整理,共享尚需时日,本着“授人以鱼不如授人以渔”的目标,先出文档,再共享工程。如果你跟我一样,也是不擅长Linux开发的VC程序员,可以参考这篇文档,用VC编译调试比特币源码。

如果对这个话题感兴趣,可直接看你感兴趣的部分。我会说明这些操作是哪个工程需要做的,不在意的可以跳过。比如,你只关心bitcoind程序,则不需要看bitcoin-qt以及测试程序相关的。

做这件事让我N次头大,感谢申屠的鼓励与支持,还有很多QQ、微博网友、laanwj大神。

先说明我的电脑环境:Windows 8.1企业版64位、VC 2008 SP、VC 2010 SP1。

我是在2014年5月8日18点在MAC上用git获取比特币源码。如果你用最新版的BTC源码,可能修改会有不同。

我都嫌自己啰嗦了,但又怕说不清楚!

第一节 依赖库

下载、编译比特币源码的依赖库。

比特币源码的依赖库包含OpenSSL、Berkeley DB、Boost、miniupnpc、QT等第三方依赖库。

特别说明:

  1. 库miniupnpc需要在工程属性中添加USE_UPNP宏定义时才会启用,我没有使用miniupnpc库,后续将会加上。
  2. 库LevelDB中涉及到snappy库,同样需要定义SNAPPY宏才启用,我没有启用snappy,后续将会加上。
1、库OpenSSL

库OpenSSL的编译依赖Perl,先下载安装Perl,下载地址: http://www.activestate.com/activeperl/downloads,可下载x86、x64版本的。

下载库OpenSSL,下载地址:http://www.openssl.org/source/,下载openssl-1.0.1c.tar.gz文件,下载后解压。

打开VC2010的命令行窗口,编译方式参照openssl-1.0.1c目录下的INSTALL.W32文件中的说明,输入以下命令进行编译:

perl Configure VC-WIN32 no-asm --prefix=c:/openssl
ms\do_ms
nmake -f ms\ntdll.mak
nmake -f ms\ntdll.mak test
nmake -f ms\ntdll.mak install
编译完成后,在各个工程中添加OpenSSL的目录,头文件路径是:c:\openssl\include,lib路径是:c:\openssl\lib,lib库名称是:libeay32.lib、ssleay32.lib。

2、库Boost

下载地址:http://sourceforge.net/projects/boost/files/boost/1.50.0/,下载文件boost_1_50_0.zip,解压。

在VC2010的命令行窗口中编译boost_1_50_0,输入以下命令:

bootstrap
.\b2
编译完成后,在各个工程中添加Boost的目录,头文件路径是:\boost_1_50_0,lib路径是:\boost_1_50_0\stage\lib。

3、库Berkeley DB

下载地址:http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index-082944.html,下载文件db-4.8.30.NC.zip,解压。

用VC2008打开\db-4.8.30.NC\buildwindows目录下的Berkeley DB.sln,从VC2005工程转换为VC2008工程,然后生成解决方案,

编译完成后,在各个工程中添加Berkeley DB的相关目录,头文件路径是:\db-4.8.30.NC\build_windows,lib路径是:\db-4.8.30.NC\build_windows\Win32\Debug,lib库是libdb48d.lib,DLL库是libdb48d.dll,bitcoin的程序运行时需要libdb48d.dll,复制到bitcoin的程序相同目录中。

注:VC2010调用VC2008编译的库,我不确定是否没问题。

4、库QT

这是bitcoin-qt、bitcoin-qt-test需要的,不在意的可以跳过。

下载地址:http://download.qt-project.org/archive/qt/5.2/5.2.0/。

下载文件:qt-windows-opensource-5.2.0-msvc2010_opengl-x86-offline.exe。

下载完成后安装即可。

QT工程的程序执行需要QT的DLL库,路径: \Qt\Qt5.2.0\5.2.0\msvc2010\bin,复制需要的DLL库到可执行程序的目录中。

5、QT的VC插件

这是bitcoin-qt、bitcoin-qt-test需要的,不在意的可以跳过。

下载地址:http://download.qt-project.org/official_releases/vsaddin/qt-vs-addin-1.2.3-opensource.exe。

下载后安装,在VC2010中菜单多了”Qt5“,在”Qt Options“中添加QT版本、路径。例如:

adfa

 

6、库Protobuf

这也是bitcoin-qt、bitcoin-qt-test需要的,不在意的可以跳过。

下载地址:http://code.google.com/p/protobuf/,下载文件:protobuf-2.5.0.tar.bz2,用VC2010打开,从VC2005工程转换为VC2010工程,编译2次,第一次编译后有些工程没成功,再次编译即可(不是rebuild)。

Protobuf库是用来把paymentrequest.proto文件生成对应的.h、.cc文件的。

编译完成后,在QT相关的bitcoin-qt、bitcoin-qt-test工程中添加Protobuf的相关目录,头文件路径是:\protobuf-2.5.0\src,lib路径是:\protobuf-2.5.0\vsprojects\Debug,lib库是libprotobuf.lib、libprotoc.lib。

第二节 创建VC工程

分析比特币的源码,总结成3个程序、2个测试程序,3个程序对应着BTC钱包中的3个程序。分别是:
  1. bitcoin-cli
  2. bitcoind
  3. bitcoin-qt,QT钱包。
  4. bitcoin-qt-test,QT钱包测试程序。
  5. test_bitcoin,bitcoin core单元测试程序。
在VC中的工程结构如图:

23

 

为保持BTC的文件目录结构,Bitcoin.sln、bitcoin-cli.vcxproj、bitcoind.vcxproj在src目录下,bitcoin-qt.vcxproj位于src\qt目录中,bitcoin-qt-test.vcxproj位于src\qt\test目录中,test_bitcoin.vcxproj位于src\test目录中。

说明:

  1. bitcoin-cli、bitcoind、test_bitcoin是console类型。
  2. bitcoin-qt是QT的Application类型,依赖QT的QtCore、QtGui、QtNetwork、QtWidgets。
  3. bitcoin-qt-test是QT console application类型,依赖QT的QtCore、QtGui、QtNetwork、QtWidgets、QtTest。
 

第三节 添加代码文件

现在开始给各个工程添加相关的代码文件。

因为src目录下的文件在所有工程中都用到,所以先在bitcoin-cli工程新建文件夹(在资源管理器中新建筛选器),添加各个工程公用的文件夹(Common),再复制到其他工程中。目录结构如下:

333

 

注意几点:

  1. 头文件、源文件只包含src目录下的代码文件,不包含子目录下的代码文件,且不包含bitcoind.cpp、bitcoin-cli.cpp以及资源文件。
  2. Common中包含src\json目录下的文件。
  3. Common中包含src\leveldb下的目录、文件,不包含文件名为xxx_test格式的文件,不包含\leveldb\port\portposix.cc文件。
在bitcoin-cli工程中添加bitcoin-cli.cpp以及资源文件bitcoin-cli-res.rc。工程结构如下:

31

在bitcoind工程中添加bitcoind.cpp以及资源文件bitcoind-res.rc。工程结构如下:

322

在test_bitcoin工程中添加src\test目录下的代码文件,data中包含了src\test\data目录下的文件。工程结构如下:

33333

在bitcoin-qt中添加src\qt中的目录、文件。不包含test目录下的文件。工程结构如下:

4

forms下包含src\qt\forms下的文件,locale下包含src\qt\locale下的文件,res中包含src\qt\res下的目录、文件,Resource Files中包含bitcoin.qrc。

proto下的.h、.cc文件是用工具Protobuf生成的,后面介绍。

在bitcoin-qt-test工程中添加src\qt\test目录下的文件。添加bitcoin-qt的代码文件。工程结构如下:

5

第四节 设置工程属性

设置工程属性,除了设置前面介绍的依赖库的相关路径、lib库外,还有些特殊设置,这里特别介绍。

1、bitcoin-cli、bitcoind、test_bitcoin工程的字符集从UNICODE改为多字符集。

2、所有工程设置leveldb相关的头文件路径,包含3个,分别是:

  • \leveldb
  • \leveldb\include
  • \leveldb\helpers\memenv
3、工程添加预处理器定义:

预处理定义分所有工程都需要定义、单个工程需要定义的,在下面会指明。

先说所有工程都需要知名的预处理器定义:

  • _WIN32_WINDOWS:代码中需要定义_WIN32_WINNT或者_WIN32_WINDOWS,我定义了_WIN32_WINDOWS。
  • HAVE_WORKING_BOOST_SLEEP:在src目录下的util.h文件的MilliSleep函数中根据宏定义调用不同的BOOST的函数,我不知道该调用BOOST的哪个函数,就随意选择了HAVE_WORKING_BOOST_SLEEP,这个定义决定了调用boost::this_thread::sleep函数。
  • LEVELDB_PLATFORM_WINDOWS:leveldb编译支持windows平台。
  • OS_WIN:leveldb需要,包含windows相关的头文件。
  • ENABLE_WALLET:表示是否启用钱包,因为代码不完善,禁用钱包不要取消ENABLE_WALLET的定义,添加参数”-disablewallet”来完成。如果取消ENABLE_WALLET的定义,编译失败。
  • _CRT_SECURE_NO_WARNINGS:大家都懂了,去掉警告而已,可以不加的。
  • Bitcoin-qt、bitcoin-qt-test工程多加定义:WIN32_LEAN_AND_MEAN,不加时编译报很多winsock的错误。。
4、所有工程添加链接lib库:Shlwapi.lib。

第五节 修改代码

编译、运行过程中发生错误,逐条修改,数量很多,但改动很小。

1、工程Bitcoin-cli、Bitcoind

(1)文件main.cpp编译release版时,报错"Bitcoin cannot be compiled without assertions."。把这句代码注释掉,增加#include <assert.h>,把assert宏定义为空语句。

(2)在文件net.h中添加#define __func__ __FUNCTION__,__func__是GCC的,__FUNCTION__是VC的。

(3)文件addrman.h中的类CAddrMan中的IMPLEMENT_SERIALIZE因为括号不对,编译报错,不用IMPLEMENT_SERIALIZE宏,把宏IMPLEMENT_SERIALIZE定义的3个函数函数GetSerializeSize、Serialize、Unserialize中的‘{statements} ’替换为IMPLEMENT_SERIALIZE中的参数。

因为变量nVersion在函数参数中有定义,且局部变量中也有定义,重复定义,注释掉局部变量中的nVersion。

(4)把文件core.h中类CTxOutCompressor的IMPLEMENT_SERIALIZE宏展开,用实际代码替换,不用IMPLEMENT_SERIALIZE宏,用宏的参数替换宏定义中的{statements}字段。

(5)把文件serialize.h中ReadCompactSize函数中的0x100000000LLu改为(unsigned __int64)0x100000000。VC不支持LLu类型的数据。

(6)文件serialize.h中,类CDataStream的构造函数中: CDataStream(const std::vector& vchIn, int nTypeIn, int nVersionIn) : vch((char*)&vchIn.begin()[0], (char*)&vchIn.end()[0]),运行时报错,注释掉”[0]“。VC不允许数组大小为0。

(7)文件script.h中的CScriptCompressor中的Serialize函数、Unserialize函数中的数组越界了,且需要处理大小为0的数组,共4处。

Serialize函数中:

if(compr.size() > 0)
    s << CFlatData(&compr[0], &compr[compr.size()-1]);

if(script.size() > 0) s << CFlatData(&script[0], &script[script.size()-1]);

Unserialize函数:
if(vch.size() > 0)
    s >> REF(CFlatData(&vch[0], &vch[vch.size()-1]));

if(script.size() > 0) s >> REF(CFlatData(&script[0], &script[script.size()-1]));

这个问题我在github上提过,但laanwj有他的看法,有兴趣的可以看看。 地址:https://github.com/bitcoin/bitcoin/pull/4239。

(8)文件script.h中类CScript中:

#ifndef _MSC_VER
CScript(const unsigned char* pbegin, const unsigned char* pend) : std::vector<unsigned char>(pbegin, pend) { }
#endif
注释掉_MSC_VER的宏定义。

因为文件\qt\walletmodel.cpp中的WalletModel::prepareTransaction函数中的代码:

const unsigned char* scriptStr = (const unsigned char*)out.script().data();
CScript scriptPubKey(scriptStr, scriptStr+out.script().size());
编译错误,scriptStr的类型转换失败。

(9)在文件key.cpp中,添加函数:

int CompareBigEndianEx(const unsigned char *c1, size_t c1len) {
    while (c1len > 0) {
        if (*c1)
           return 1;
        c1++;
        c1len--;
    }
    return 0;
}
注释掉const unsigned char vchZero[0] = {};

函数CKey::Check、CKey::CheckSignatureElement中的CompareBigEndian(vch, len, vchZero, 0)改为CompareBigEndianEx(vch, 32)。

GCC允许数组大小为0,但VC不允许。

(10)文件addrman.cpp中的CAddrMan::Select_函数中的代码:

double nCorTried = sqrt(nTried) * (100.0 - nUnkBias);
double nCorNew = sqrt(nNew) * nUnkBias;
改为:
double nCorTried = sqrt((double)nTried) * (100.0 - nUnkBias);
double nCorNew = sqrt((double)nNew) * nUnkBias;
在sqrt函数的参数加入(double)强制类型转换,VC支持3个sqrt函数,参数类型不同,这里增加强制类型转换。

(11)文件init.cpp中的AppInit2函数中的Setvbuf的最后一个参数0改为INT_MAX。


setvbuf(stdout, NULL, _IOLBF, INT_MAX /*0*/);
VC不允许最后一个参数为0。

(12)把文件db.h中类CDB的部分代码注释掉,分别是:

  1. Read函数中的free(datValue.get_data());
  2. ReadAtCursor函数中的free(datKey.getdata());free(datValue.getdata());
Free地址时报错,我跟踪过,在数据库的pdb->get函数中确实调用了malloc函数申请内存,按理说free此地址没问题,但报错,这个问题暂时解决不了,后续解决。

(13)文件db.cpp中,类CDBEnv的CloseDb函数,delete pdb;时报错,解决不了,暂时注释掉,可能会产生数据库操作错误。

同步区块半小时后不在同步,这个问题可以跟与数据库的这2个修改相关,尚未确定。

(14)BTC源码中有2个bloom文件,分别是bloom.cpp、\leveldb\util\bloom.cc,编译时生成的bloom.obj冲突,把其中1个文件改名即可,把bloom.cc改名为bloomdb.cc。

(15)BTC源码中有2个hash文件,分别是hash.cpp、\leveldb\util\hash.cc,编译时生成的hash.obj冲突,把其中1个文件改名即可,把hash.cc改名为hashdb.cc。

(16)把文件\leveldb\util\env_win.cc中的CreateDirInner函数中的GetFileAttributes改为GetFileAttributesA,CreateDirectory改为CreateDirectoryA,强制使用ASCI函数。

(17)注释掉\leveldb\db\dbbench.cc、\leveldb\db\leveldbmain.cc中的main函数,冲突了。

(18)在文件netbase.cpp、\leveldb\db\dbiter.cc中添加语句:”typedef signed int ssizet;”,VC中没有定义ssize_t数据类型。

(19)注释掉文件\leveldb\db\c.cc中的#include <unistd.h>,这是linux的头文件。

(20)函数min()、max()在windef.h、stl中有不同的定义,需要把std::numericlimits::max()改为(std::numericlimits::max)(),否则编译时max()参数报错。

需要改的地方包括:

  1. 文件serialize.h中的GetSizeOfCompactSize函数
  2. script.h文件中的类CScriptNum中的减号重载、加等于、减等于、getint()函数,WriteCompactSize函数
  3. 文件core.h中的类CTxIn的3个构造函数,、IsFinal()函数
  4. \Qt\Qt5.2.0\5.2.0\msvc2010\include\QtCore\qdatetime.h中的类QDate中的nullJd函数
文件wallet.h中类CWallet的LoadMinVersion函数中的参数std::max加上小括号。

文件net.h中的类CNode的AskFor函数中的std::max加上小括号。

2、工程bitcoin-qt、bitcoin-qt-test

(1)在CMD中,运行\protobuf-2.5.0\vsprojects\Release目录下的protoc.exe,生成paymentrequest.proto文件对应的头文件、源文件,命令格式为:

protoc --proto_path=e:/bitcoin/qt --cpp_out=e:/bitcoin/qt e:/bitcoin/qt/paymentrequest.proto
把生成的.h、.cc文件添加到bitcoin-qt、bitcoin-qt-test工程中。

(2)对\qt\locale下的所有文件执行lrelease操作,在资源管理器中,在locale下的文件上点击右键,执行lrelease操作,生成文字的多国语言版本。

(3)在文件\qt\winshutdownmonitor.h中语句“#include <windef.h> // for HWND”前面添加#include <Windows.h>。因为编译时报错:1>c:\program files (x86)\microsoft sdks\windows\v7.0a\include\winnt.h(6361): error C2146: syntax error : missing ';' before identifier 'ContextRecord',多个错误。

(4)修改bitcoin-qt、bitcoin-qt-test工程中的某些cpp文件的编译方式,把Custom Build Tool改为C/C++ compiler。需要修改的文件为:rpcconsole.cpp、intro.cpp、overviewpage.cpp、bitcoin.cpp。

(5)注释掉文件\qt\test\test_main.cpp的第一行代码:#include "bitcoin-config.h"。我已经提交github修改此BUG,已通过,5月22日之后的代码没有这个问题,github上的修改BUG地址:https://github.com/bitcoin/bitcoin/pull/4212。有兴趣可以查看。

3、工程test_bitcoin

  1. 移除test_bitcoin工程中COMMON的源文件目录下的init.cpp,因为变量pwalletMain等重定义了,冲突。
  2. 在文件\test\utiltests.cpp中添加代码:typedef signed int ssizet;
  3. 在MAC系统下编译bitcoin,把\src\test\data目录下的所有h文件复制到相同目录中。(因为分析makefile,把json文件转换成h文件的方法是通过创建h文件,输出字符串,字符转换等完成的,需要新写程序来做,所以暂时使用MAC编译后的h文件。)
在src目录中的Makefile.include文件完成此操作,转换代码如下:
%.json.h: %.json
    @$(MKDIR_P) $(@D)
    @echo "namespace json_tests{" > $@
    @echo "static unsigned const char $(*F)[] = {" >> $@
    @$(HEXDUMP) -v -e '8/1 "0x%02x, "' -e '"\n"' $< | $(SED) -e 's/0x  ,//g' >> $@
    @echo "};};" >> $@
    @echo "Generated $@"

%.raw.h: %.raw @$(MKDIR_P) $(@D) @echo "namespace alert_tests{" > $@ @echo "static unsigned const char $(*F)[] = {" >> $@ @$(HEXDUMP) -v -e '8/1 "0x%02x, "' -e '"\n"' $< | $(SED) -e 's/0x ,//g' >> $@ @echo "};};" >> $@ @echo "Generated $@"

 

第六节 调试建议

各个程序编译通过,但尚未经过大量测试,只可用于研究技术,不可作为钱包以及比特币的发送、接收,我用bitcoin-qt调试比较多。

运行界面如下:

6

 

建议:

  1. 从bitcoin官网下载安装包,安装标准版。
  2. 把注册表路径HKEY_CURRENT_USER\Software\Bitcoin\Bitcoin-Qt下的strDataDir的值改为别的路径,这样调试的bitcoin-qt采用新设置的路径。
  3. 把注册表路径HKEY_CURRENT_USER\Software\Bitcoin\Bitcoin-Qt下的language改为en,bitcoin-qt的界面显示为英文,可以根据英文字符串定位代码。默认显示中文,中文是根据英文字符串翻译后的,用中文字符串不太容易定位代码。
--------------------

作者: 龙少

新浪微博昵称: 007龙少

新浪微博地址: http://weibo.com/u/1064323301

BTC捐助地址: 1CeeGr858xjLJQB3a9uLawHAdZ2qWjzTGT

PDF下载版:http://pan.baidu.com/s/1hqefNd6

评论(4)
登录 账号发表你的看法,还没有账号?立即免费 注册
  • 巴比特资讯 2014-06-23
    c3162e0881ba482e2bf3ed80a19a30aa20007c9d2f18077b0ff0c6b4689b54fa
  • chehw 2014-06-06
    没用VC调试过,学习过程中。发现个小问题: 【第五节 修改代码】中的Serialize函数中不应修改,CFlatData的构造函数是CFlatData (void * pbeginIn, void * pendIn),取的是首地址和尾地址,源代码中的&amp;script[script.size()]是正确的,相当于是pendln = &amp;script[0]+script.size()。 如果更为&amp;script[script.size()-1],读写时会遗漏一个script。
    • 007龙少: 2014-06-06
      &script[script.size()]在VC中调试时会报数组越界。