參考《可信計算基礎》實驗指導書-20170326.PDF
目錄
1. 平台環境
1.1. 系統平台
1.2. TPM 仿真環境結構
2. cmake
2.1. cmake 簡介
2.2. 安裝 cmake
3. m4
3.1. m4 簡介
3.2. 安裝 m4
4. GNU MP Library
4.1. GMP 簡介
4.2. 安裝 GMP
5. TPM_Emulator
5.1. 編譯和安裝 TPM_Emulator
5.2. 初始化 TPM_Emulator
5.3. 啟動 TPM_Emulator
6. TSS 協議棧
6.1. 編譯和安裝 Trousers
6.2. 啟動 TCSD
7. tpm-tools
7.1. tpm-tools 簡介
7.2. 安裝 tpm-tools
8. 測試 TPM 環境是否搭建成功
9. 運行測試程序
9.1. 在 tpm-emulator-0.7.5/tddl/ 目錄下有測試程序
PS:解決后再更新???
9.2. 對TSS協議棧進行測試
9.2.1. 測試程序
9.2.2. 該程序實現的操作
9.2.3. 編譯和執行
PS:解決后再更新???
1. 平台環境
1.1. 系統平台
宿主機系統:Windows 10 Pro
虛擬機工具:VMware® Workstation 15 Pro
虛擬機系統:CentOS Linux release 7.6.1810 (Core) 64位
本文在 centos 虛擬機上搭建 TPM 仿真環境。
1.2. TPM 仿真環境結構
下面是TPM仿真環境軟件包依賴關系圖,搭建環境應該是由下至上按部就班的安裝。
2. cmake
2.1. cmake 簡介
CMake 是一個跨平台的自動化建構系統,它使用一個名為 CMakeLists.txt 的文件來描述構建過程,可以產生標准的構建文件,如 Unix 的 Makefile 或Windows Visual C++ 的 projects/workspaces 。文件 CMakeLists.txt 需要手工編寫,也可以通過編寫腳本進行半自動的生成。CMake 提供了比 autoconfig 更簡潔的語法。在 linux 平台下使用 CMake 生成 Makefile 並編譯的流程如下:
(1)編寫 CmakeLists.txt。
(2)執行命令“cmake PATH”或者“ccmake PATH”生成 Makefile ( PATH 是 CMakeLists.txt 所在的目錄 )。
(3)使用 make 命令進行編譯。
2.2. 安裝 cmake
tar zxvf cmake-3.22.3.tar.gz
解壓縮后 cd cmake-3.22.3
./bootstrap安裝
sudo make install安裝
完成后cmake --version
3. m4
3.1. m4 簡介
m4 是一種宏處理器,它掃描用戶輸入的文本並將其輸出,期間如果遇到宏就將其展開后輸出。宏有兩種,一種是內建的,另一種是用戶自定義的,它們能接受任意數量的參數。除了做展開宏的工作之外,m4 內建的宏能夠加載文件,執行 Shell 命令,做整數運算,操縱文本,形成遞歸等等。m4 可用作編譯器的前端,或者單純作為宏處理器來用。
3.2. 安裝 m4
sudo apt-get install m4
4. GNU MP Library
4.1. GMP 簡介
gmp是一個可以進行任意精度算術的自由庫,可以操作有符號整數、有理數和浮點數。除了運行gmp的機器中可用內存所暗示的精度外,沒有實際限制。gmp有一套豐富的功能,這些功能有一個常規的接口(本文不研究這個接口)。
gmp的主要目標應用是密碼學應用與研究、互聯網安全應用、代數系統、計算代數研究等。
gmp基於m4,所以在安裝gmp之前要先安裝m4。
4.2. 安裝 GMP
下載地址:http://gmplib.org
我下載的版本:gmp-6.2.1.tar.zst
執行下列命令進行編譯和安裝:
tar -I zstd -xvf gmp-6.2.1.tar.zst (因為zstd沒有安裝,所以sudo apt-get install zstd后再解壓縮)
解壓后cd gmp-6.2.1
cd gmp-6.2.1
./configure
make
make check
make install
5. TPM_Emulator
下載地址:https://github.com/PeterHuewe/tpm-emulator
我下載的版本:tpm-emulator-master.zip
解壓縮 unzip tpm-emulator-master.zip
該程序主要包含三個部分:
(1)tpmd:實現 TPM 仿真器的用戶空間應用程序,可以通過 Unix 域套接字(Unix)或命名管道(Windows)進行訪問。
(2)tpmd_dev:一個內核模塊,它提供設備 /dev/tpm 以實現向后兼容,並將收到的命令轉發給 tpmd(僅限Unix和Mac OS X)。
向后兼容(Backward Compatibility),又稱作向下兼容(Downward Compatibility)。在計算機中指在一個程序或者類庫更新到較新的版本后,用舊的版本程序創建的文檔或系統仍能被正常操作或使用,或在舊版本的類庫的基礎上開發的程序仍能正常編譯運行的情況。
(3)tddl:TPM 仿真器的 TSS 符合設備驅動程序庫。
5.1. 編譯和安裝 TPM_Emulator
TPM 仿真器包的編譯和安裝基於 CMake 構建環境(2.6或更高版本),並要求在您的系統上正確安裝 GNU MP 庫(版本4.0或更高版本)。因為前面已經安裝過了,所以這里直接執行下列命令進行編譯和安裝:
cd tpm-emulator-master
mkdir build
cd build
cmake ../
make
(make后出錯,原因沒有安裝GMP庫)sudo apt-get install libgmp-dev,再make即可
軟連接問題:
su root
cd /lib/modules/5.11.0-40-generic 后ll,看是否有軟連接存在 build-> /usr/lib/linux-headers-5.11.0-40-generic/是否存在,若為紅色說明不存在,則需要下載;若存在,需要重新鏈接
sudo apt-get install linux-headers-5.11.0-40-generic
sudo apt-get install linux-kernel-headers kernel-package
查看內核開發文件路徑下對應的文件名
ls /usr/src
cd /lib/modules/5.11.0-40-generic
鏈接文件
ln -s(或-sf表示強制鏈接) 、/usr/src/linux-headers-5.11.0-40-generic build
回到tpm-emulator-master文件夾 cd build重新make及make install
5.2. 初始化 TPM_Emulator
TPM的啟動模式(參見TPM規范第1部分)由啟動模式參數定義,可以設置為clear,save(默認)或deactivated,對應的中文意思分別是清除,保存和停用。 下面簡單介紹tpmd的用法以及參數的含義:
如果模擬器以“save”模式啟動並且無法加載先前存儲的TPM狀態,則它將進入“fail-stop”模式並且必須重新加載。 因此,第一次啟動TPM仿真器時,參數必須設置為“clear”。 想要恢復處於“fail-stop”模式的TPM仿真器,首先將其停用,然后以“clear”模式重新加載,我們執行下列命令進行初始化:
5.3. 啟動 TPM_Emulator
要想在 Unix 或 Mac OS X 上使用 TPM 仿真器,必須先啟動 TPM 仿真器的守護程序(tpmd)並加載 TPM 設備轉發模塊(tpmd_dev)。 我們執行下列命令進行啟動:
注釋:
(1)若在執行第一條命令時遇到:Module tpmd_dev not found,則執行命令:depmod -a
(2)modprobe命令:
modprobe命令用於智能地向內核中加載模塊或者從內核中移除模塊。
modprobe可載入指定的個別模塊,或是載入一組相依的模塊。modprobe會根據depmod所產生的相依關系,決定要載入哪些模塊。若在載入過程中發生錯誤,在modprobe會卸載整組的模塊。
(3)depmod命令:
depmod命令可產生模塊依賴的映射文件,在構建嵌入式系統時,需要由這個命令來生成相應的文件,由modprobe使用。
啟動時遇到的錯誤:
解決方法:
啟動tpm模擬器的時候出現了報錯:/tpm/tpm_startup.c:83: Error: restoring permanent data failed
解決方法就是在“啟動軟TPM”這個地方把
#tpmd -f -d 換成 #tpmd -f -d clear
#modprobe tpmd_dev //將該模塊加入內核
#tpmd -f -d
若出現tpmd.c:276:Error: bind(/var/run/tpm/tpmd_socket:0) failed: Address already in use,則:sudo rm/var/run/tpm/tpmd_socket:0
啟動成功界面:
啟動模擬器再同時啟動TSTD,這個時候就相當於模擬器啟動成功了,並可以查看這個模擬器的版本等等信息**
先打開2個窗口各運行tpmd和tcsd命令,Ctrl+alt+T打開新窗口
//setup tpm
#modprobe tpmd_dev //將該模塊加入內核
#tpmd -f -d
若出現TCSD ERROR: Failed bind: Address already in use,則:
l ps –e | grep tcsd
l kill -9 。。。
成功啟動后:
注:
#tpmd [-d] [-f][-h] [start mode]
其中[-d]: enable debug mode
[-f]:force the application to run in the forground 會顯示你發送給tpmd的命令
[-h]:print this help message 打印幫助消息
Start mode:’clear’清除之前的狀態, ’save’默認情況下打開之前的狀態, ‘deactivate’無效
然后TPMManger其實就是相當於提供了可視化的界面來查看TPM芯片的信息
鏈接:https://github.com/Rohde-Schwarz/TPMManager
一些長識:
” apt-get“是一條linux命令,適用於deb包管理式的操作系統,主要用於自動從互聯網的軟件倉庫中搜索、安裝、升級、卸載軟件或操作系統 ,可以為你獲取和安裝二進制軟件包,而節省編譯的時間
depmod可檢測模塊的相依性,供modprobe在安裝模塊時使用。
(原文鏈接:https://blog.csdn.net/qq_35297774/article/details/89188549)
再次啟動 tpmd 就沒有問題了。
查看TPM版本
tpm_version
若出現error while loading shared libraries: libtspi.so.1: cannot openshared object file: No such file or directory,則:
apt-get install libtspi-dev
tpm-tools-1.3.8/src/tpm_mgmt/目錄下查看以下三個信息:
#./tpm_version #查看版本號
#./tpm_getpubek #查看ek公鑰
#./tpm_takeownership #獲取owner
注:如果都成功表明TPM模擬環境已經完全構建成功了。
如果 tpm_version出現tpm_version: error while loading shared libraries: libtspi.so.1: cannot open shared object file: No such file or directory,則使用命令apt-get install libtspi-dev
若出現Tspi_Context_Connect failed: 0x00003011 - layer=tsp, code=0011 (17), Communication failure
,則開啟tpmd和tcsd即可。
原文鏈接:https://blog.csdn.net/lwyeluo/article/details/49963683
在tpm_emulator-0.7.4/tddl文件夾下有測試事例:
../tpm_emulator-0.7.4/tddl# gcc -o test_tddl test_tddl.c -ltddl// -ltddl 為鏈接tddl的動態鏈接庫
../tpm_emulator-0.7.4/tddl# LD_LIBRARY_PATH=/usr/local/lib ./test_tddl//運行結果
因為$PATH中沒有路徑/usr/local/lib,因此,需要加上LD_LIBRARY_PATH=/usr/local/lib,不然會報錯
6. TSS 協議棧
下載地址:https://sourceforge.net/projects/trousers/files/
我下載的版本:trousers-0.3.14.tar.gz
該程序是由 IBM 創建和發布的開源 TCG 軟件棧。
6.1. 編譯和安裝 Trousers
Tips:
如果直接解壓 trousers 的壓縮包,它的各種文件會分散在父目錄里面,所以要在解壓時為其指定一個解壓目錄。當然,如果不小心直接解壓了,也可以使用下列命令撤銷解壓操作:
我們先為 trousers 創建一個目錄,再將其進行解壓:
看一下 README 文件,並按要求安裝如下的依賴包:
pkgconfig安裝提示找不到包:
到pkg-config官網https://www.freedesktop.org/wiki/Software/pkg-config/,下載最新安裝包
目前,最新版是2017年3月20日發布的0.29.2,下載地址https://pkg-config.freedesktop.org/releases/pkg-config-0.29.2.tar.gz。
命令wget https://pkg-config.freedesktop.org/releases/pkg-config-0.29.2.tar.gz,下載完成后解壓
原文鏈接:https://blog.csdn.net/Charliewolf/article/details/101273248
tar -zxvf pkg-config-0.29.2.tar.gz
進入安裝包
cd pkg-config-0.29.2
依次執行以下四個命令
./configure --with-internal-glib
make
make check
sudo make install
查看版本
pkg-config --version
0.29.0
libtool安裝
wget http://ftpmirror.gnu.org/libtool/libtool-2.4.6.tar.gz
tar xvf libtool-2.4.6.tar.gz -C ~/
cd ~/libtool-2.4.6
配置
./configure --prefix=/home/jello/libtool
編譯
make -j4
安裝
make install
openssl-devel安裝下載地址 https://www.openssl.org/source/
可以直接安裝sudo apt-get install openssl
openssl version -a
#apt-get install build-essential#這將安裝gcc/g++/gdb/make等基本編程工具
#apt-get install gnome-core-devel#這將安裝 libgtk2.0-dev libglib2.0-dev等開發相關的庫文件
#apt-get install pkg-config #用於在編譯GTK程序時自動找出頭文件及庫文件位置
#apt-get install devhelp #這將安裝 devhelp GTK文檔查看程序
#apt-get install libglib2.0-doc libgtk2.0-doc #這將安裝 gtk/glib的API參考手冊及其它幫助文檔
#apt-get install glade libglade2-dev#這將安裝基於GTK的界面GTK是開發Gnome窗口的c/c++語言圖形庫
#apt-get install libgtk2.0* #gtk+2.0所需的所有文件統通下載安裝完畢
原文鏈接:https://blog.csdn.net/qq_41426887/article/details/89742881
出現錯誤:libgtk2.0-0-dbg : Depends: libgtk2.0-0 (= 2.24.23-0ubuntu1.4) but 2.24.32-4ubuntu4 is to be installed
采用降級的方式,如下命令
apt-get aptitude
sudo aptitude install libgtk2.0-dev
安裝完上述的依賴包后再執行下面的命令進行編譯和安裝:
Tips:
網上有說需要修改 Makefile.am 和 Makefile.in 文件,但那些都是 trousers-0.3.8 版本的教程,那個版本會遇到 Werror、ld 鏈接庫等報錯的問題。本文使用 trousers-0.3.14 版本,沒有修改 Makefile.* 文件,直接編譯安裝成功無報錯。
6.2. 啟動 TCSD
Tips:
在執行下列命令之前需要先啟動 TPM_Emulator,否則會提示找不到設備。
啟動時遇到的錯誤:
解決方法:
帶上 clear 參數重啟 tpm-emulator:
再次啟動 tcsd 就沒有問題了。
7. tpm-tools
7.1. tpm-tools 簡介
tpm-tools 是一組管理和利用可信計算組織的 TPM 硬件的工具。 TPM 硬件可以安全地創建,存儲和使用 RSA密鑰(不會暴露在內存中),使用加密哈希等來驗證平台的軟件狀態。該程序包含的工具允許平台管理員管理和診斷平台的 TPM 硬件。 此外,該程序還包含命令,以利用 openCryptoki 項目中實現的 TPM PKCS#11 接口中提供的某些功能。
7.2. 安裝 tpm-tools
8. 測試 TPM 環境是否搭建成功
(1)先啟動 tpmd
(2)再啟動 tcsd
(3)最后使用 tpm-tools 工具的命令加以驗證:
要運行 tpm-tools 工具的命令,必須先啟動 tpmd 和 tcsd,缺一不可。
安裝TPMManager
其實tpmmanager可裝可不裝,主要是它提供QT界面可以直觀查看和管理TPM而已,比較方便。tpmmanager-0.8安裝需要QT支持,需要安裝qt4-qmake。但是make過程會出現缺少uic-qt4 command not found,還需要安裝libqt4-dev。編譯安裝過程
也就是說,首先:
sudo apt-get install qt4-qmake
sudo apt-get install libqt4-dev
#cd tpmmanager-0.8 //這個版本是博客里作者的,要看自己下載的是什么版本
#qmake–v #查看qt版本,如果沒有,apt-get install qt4-qmake
#qmake
#make
(make的過程提示錯誤,src/TPMManager.cxx:412:47:error: can not call constructor ‘QUrl::Qurl’ directly[-fpermissive],如果出現了同樣的錯誤,打開這個文件,修改TPMManager.cxx 412行,將
QUrl myURL = QUrl::QUrl(url, QUrl::strictMode)修改為QUrl myURL =QUrl(url, QUrl::strictMode) 源碼就可以繼續編譯)
# install bin/tpmmanager /usr/local/bin
運行tpmmanager
#tpmmanager
提供幾個安裝的時候參考的網址:
1、http://www.uplook.cn/Linux/8/Linux_85695.html 超級詳細很有用
2、http://blog.chinaunix.net/uid-23757972-id-3074677.html 也很詳細,只是非ubuntu
3、 vTPM的安裝,這個很本文無關,只是在這里記錄一下
http://www.etcn.cn/Tech/Os/Linux/2012/1022/43906.html
4、http://blog.chinaunix.net/uid-23757972-id-3242694.html 這個和2是一個作者
5、http://blog.chinaunix.net/uid-20657527-id-1588199.html 這個解決了遇到的問題,就是tpm啟動的是要clear一下。tpmd –d –f clear
6、http://blog.csdn.net/cicisensy/article/details/5291569 這個也是安裝步驟,參考。不過就介紹了一點,只有TPM模擬器的安裝。
9. 運行測試程序
9.1. 在 tpm-emulator-master/tddl/ 目錄下有測試程序
gcc -o test_tddl test_tddl.c -ltddl //-ltddl為鏈接tddl的動態鏈接庫
./test_tddl運行后出現錯誤
通過cmake ../
cd tddl
make
sudo ./test_tddl解決
9.2. 對TSS協議棧進行測試
9.2.1. 測試程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tss/platform.h>
#include <tss/tss_error.h>
#include <tss/tss_defines.h>
#include <tss/tss_structs.h>
#include <tss/tss_typedef.h>
#include <tss/tss_error_basics.h>
#include <tss/tspi.h>
#include <trousers/tss.h>
#include <tss/tpm.h>
const char *get_error(TSS_RESULT res)
{
switch(ERROR_CODE(res))
{
case 0x0001L:
return "Authentication failed";
case TSS_SUCCESS:
return "success";
case TSS_E_INVALID_HANDLE:
return "hContext or phObject is an invalid handle";
case TSS_E_BAD_PARAMETER:
return "persistentstoragetype is not valid/One or more parameters is incorrect";
case TSS_E_INTERNAL_ERROR:
return "an error occurred internal to the TSS";
case TSS_E_PS_KEY_NOTFOUND:
return "NOT FOUND SRK";
case TSS_E_INVALID_ATTRIB_FLAG:
return "attribflag is incorrect";
case TSS_E_INVALID_ATTRIB_SUBFLAG:
return "subflag is incorrect";
case TSS_E_INVALID_ATTRIB_DATA:
return "ulAttrib is incorrect";
case TSS_E_KEY_ALREADY_REGISTERED:
return "UUID used";
case TSS_E_KEY_NOT_LOADED:
return "the addressed key is currently not loaded";
default:
return "unknown error";
}
}
int main(int argc,char *argv)
{
TSS_RESULT result;
TSS_HTPM hTPM;
TSS_HCONTEXT hContext;
TSS_HPOLICY hownerpolicy,hSRKPolicy;
TSS_HKEY hSRK;
TSS_HKEY hkey,hkey2;
TSS_UUID SRK_UUID=TSS_UUID_SRK;
TSS_FLAG initFlags; //密鑰標記
TSS_UUID bindkeyUUID=TSS_UUID_USK1; //用戶的綁定密鑰
printf("創建上下文對象......\n");
result=Tspi_Context_Create(&hContext);
if(result!=TSS_SUCCESS)
{
printf("Context_Create ERROR:%s(%04x)\n",get_error(result),result);
}
result=Tspi_Context_Connect(hContext,NULL);
if(result!=TSS_SUCCESS)
{
printf("Context_Connect ERROR:%s(%04x)\n",get_error(result),result);
}
printf("創建TPM對象......\n");
result=Tspi_Context_GetTpmObject(hContext,&hTPM);
if(result!=TSS_SUCCESS)
{
printf("Tspi_Context_GetTpmObject ERROR:%s(%04x)\n",get_error(result),result);
}
result=Tspi_GetPolicyObject(hTPM,TSS_POLICY_USAGE,&hownerpolicy);
if(result!=TSS_SUCCESS)
{
printf("Tspi_GetPolicyObject ERROR:%s(%04x)\n",get_error(result),result);
}
result=Tspi_Policy_SetSecret(hownerpolicy,TSS_SECRET_MODE_POPUP,0,NULL);
if(result!=TSS_SUCCESS)
{
printf("Tspi_Policy_SetSecret ERROR:%s(%04x)\n",get_error(result),result);
}
printf("載入SRK密鑰......\n");
result=Tspi_Context_LoadKeyByUUID(hContext,TSS_PS_TYPE_SYSTEM,SRK_UUID,&hSRK);
if(result!=TSS_SUCCESS)
{
printf("Tspi_Context_LoadKeyByUUID ERROR:%s(%04x)\n",get_error(result),result);
}
printf("獲取SRK的策略對象......");
result=Tspi_GetPolicyObject(hSRK,TSS_POLICY_USAGE,&hSRKPolicy);
if(result!=TSS_SUCCESS)
{
printf("Tspi_GetPolicyObject ERROR:%s(%04x)\n",get_error(result),result);
}
printf("設置SRK的策略授權......");
result=Tspi_Policy_SetSecret(hSRKPolicy,TSS_SECRET_MODE_POPUP,0,NULL);
if(result!=TSS_SUCCESS)
{
printf("Tspi_Policy_SetSecret ERROR:%s(%04x)\n",get_error(result),result);
}
printf("創建綁定密鑰......\n");
initFlags=TSS_KEY_TYPE_BIND|TSS_KEY_SIZE_512|TSS_KEY_NO_AUTHORIZATION; //設置密鑰標記
result=Tspi_Context_CreateObject(hContext,TSS_OBJECT_TYPE_RSAKEY,initFlags,&hkey); //創建綁定密鑰
if(result!=TSS_SUCCESS)
{
printf("Tspi_Context_CreateObject ERROR:%s(%04x)\n",get_error(result),result);
}
printf("在TPM產生密鑰前,設置填充類型......\n");
result=Tspi_SetAttribUint32(hkey,TSS_TSPATTRIB_KEY_INFO,TSS_TSPATTRIB_KEYINFO_ENCSCHEME,TSS_ES_RSAESPKCSV15);
if(result!=TSS_SUCCESS)
{
printf("Tspi_SetAttribUint32 ERROR:%s(%04x)\n",get_error(result),result);
}
printf("產生密鑰,該密鑰不合PCR綁定\n");
result=Tspi_Key_CreateKey(hkey,hSRK,0);
if(result!=TSS_SUCCESS)
{
printf("Tspi_Key_CreateKey ERROR:%s(%04x)\n",get_error(result),result);
}
printf("裝載綁定密鑰到UUID......\n");
result=Tspi_Context_RegisterKey(hContext,hkey,TSS_PS_TYPE_USER,bindkeyUUID,TSS_PS_TYPE_SYSTEM,SRK_UUID);
if(result!=TSS_SUCCESS)
{
if(ERROR_CODE(result)==TSS_E_KEY_ALREADY_REGISTERED)
{
printf("UUID已被使用,注銷此密鑰......\n");
result=Tspi_Context_UnregisterKey(hContext,TSS_PS_TYPE_USER,bindkeyUUID,&hkey);
if(result!=TSS_SUCCESS)
printf("UUID注銷失敗 Tspi_Context_UnregisterKey ERROR :%s(%04x)\n",get_error(result),result);
result=Tspi_Context_RegisterKey(hContext,hkey,TSS_PS_TYPE_USER,bindkeyUUID,TSS_PS_TYPE_SYSTEM,SRK_UUID);
if(result!=TSS_SUCCESS)
printf("Tspi_Context_RegisterKey ERROR:%s(%04x)\n",get_error(result),result);
}
}
printf("SUCCESS!\n");
Tspi_Context_Close(hContext);
return 0;
}
9.2.2. 該程序實現的操作
創建上下文對象…
創建TPM對象…
載入SRK密鑰…
獲取SRK的策略對象…
設置SRK的策略授權…
創建綁定密鑰…
裝載綁定密鑰到UUID…
9.2.3. 編譯和執行
Tips:
要先啟動 tpmd 和 tcsd 進程,再運行測試程序的可執行文件。
遇到如下的錯誤:
Tspi_Key_CreateKey ERROR:persistentstoragetype is not valid/One or more parameters is incorrect(0003)
PS:解決后再更新???
————————————————
版權聲明:本文為CSDN博主「l龍貓先生l」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/CSDN_FengXingwei/article/details/89342797