背景:
Xt160Api, 之前在windows下用.net調用,沒有任何問題。
但是移植到Linux去后,.net程序 調用 init(config_path) 總是報錯 /root/test 找不到 traderApi.ini (/root/test 是程序目錄) 然后退出程序
解決過程:
于是考慮是不是參數傳錯了,但是無論這個參數是什么, 報錯內容始終如此。
甚至某些情況下,比如加了幾句Console.WriteLIne(app_exe_folder) 還會出現段錯誤,這個問題至今也沒想明白。
尊崇愛因斯坦的指導意見: 不要做相同的事情,期望得到不同的結果
用Visual Studio遠程編譯 Linux C++ 程序 調用 init函數 。 發現 confg_path 可以被識別,如果config_path下沒有ini存在,他會報錯沒有在 config_path下找到 traderApi.ini 而不是 .net 那樣永遠顯示: /root/test 下找不到traderApi.ini
這個讓我極度困惑,我甚至嘗試用 unsafe模式,硬編碼字符串,傳入 init的參數, 但是輸出依舊。
我甚至認為這個api收到了 .net 的路徑影響,因為linux調用 .net 形如 dotnet ./MyApp.dll 這個api在查找ini時用的是 dotnet 的路徑,而顯示的時候用的是dll的路徑。(最后證明這個邏輯完全不對) 所以我在 dotnet 的文件夾下添加了 traderApi.ini , 依舊如故。
于是我又轉而認為2點,
- api 的指針有問題,可能我的EntryPoint寫錯了?
- 虛表有問題,什么地方寫錯了?但是 init 函數是第3個函數,前2個是 析構函數,setCallbak 函數, 之后就是 init 函數
非常疑惑,而且沮喪,覺得很無聊,甚至在想是不是.net core 3.1的bug? 要不要換.net8試試? 搞得我很煩。
但是還是掩蓋煩躁,開始用c++寫 so文件 TestSO.so
如下:
test.cpp:
#include "mylib.h"
#include "XtTraderApi.h"
#include<cstdio>extern "C"
{void MyInit(void* api, const char* path){printf("%s", "start start!!!");auto xt_api = (xti::XtTraderApi*)api;xt_api->init(path);printf("%s", path);printf("ok ok ok\r\n"); //auto xtApi = (xti::XtTraderApi*)api;//xtApi->init(path);}
}
mylib.h
#ifndef MYLIB_H
#define MYLIB_H#ifdef __cplusplus
extern "C"
{
#endif// 聲明 MyInit 函數void MyInit(void* api, const char* path);#ifdef __cplusplus
}
#endif#endif // MYLIB_H
遠程編譯后,把 so文件送入 /lib64 (映射后實際位置為/usr/lib64)
然后 ldconfig
最后用 ldconfig -p | grep libTest確認生效:
接下去先用c++ 程序調用TestSO.so, 傳入 api的地址和config_path,一切正常!
說明so文件運行正確。
接下去就用C#程序調用TestSO.so, 傳入 .net 中根據EntryPoint得到的 api地址,這次在so文件中調用 init函數居然一切正常!!
這樣我們幾乎就確定了那個我們認為最不可能有問題的 虛表 問題了!
但是怎么會?如果虛表有問題,說明函數地址是錯的,調用不應該是直接崩潰,或者輸出的是其他信息,怎么會返回 init函數的錯誤信息呢?
覺得非常不可思議,不可能啊。(但是,就是如此,原因很難想象)
如果虛表有問題,我還是只能從 C++ 是如何調用 api->init 的代碼,反匯編,看看 init 在虛表中的位置。
Visual Studio 支持 Linux 遠程反匯編 nice!
api->init 的匯編如下:
0x0000000000400fc1 e8 8a fe ff ff callq 0x400e50 <_ZN3xti11XtTraderApi17createXtTraderApiEPKc@plt>
0x0000000000400fc6 48 89 45 c0 mov %rax,-0x40(%rbp)
0x0000000000400fca 48 8b 45 c0 mov -0x40(%rbp),%rax
0x0000000000400fce 48 8b 00 mov (%rax),%rax
0x0000000000400fd1 48 83 c0 18 add $0x18,%rax
0x0000000000400fd1 48 83 c0 18 add $0x18,%rax
0x0000000000400fd5 48 8b 10 mov (%rax),%rdx
0x0000000000400fd8 48 8b 45 c0 mov -0x40(%rbp),%rax
0x0000000000400fdc be 80 7f 40 00 mov $0x407f80,%esi
0x0000000000400fe1 48 89 c7 mov %rax,%rdi
0x0000000000400fe4 ff d2 callq *%rdx
注意這兩句不知道為什么被Visual Studio 重復了2行,注意看他們地址是一樣的,說明是一條指令,而不是兩條指令。
而且這條語句極其重要,確定了init函數在虛表的位置。
0x0000000000400fd1 48 83 c0 18 add $0x18,%rax
0x0000000000400fd1 48 83 c0 18 add $0x18,%rax
神奇的是,deepseek直接把他們正確的識別為了1條語句。下面是他的解答:
This is a more complex snippet of x86-64 assembly code. Let’s break it down step by step:
- 0x0000000000400fc1 e8 8a fe ff ff callq 0x400e50 _ZN3xti11XtTraderApi17createXtTraderApiEPKc@plt
Address: 0x0000000000400fc1
Opcode: e8 8a fe ff ffInstruction: callq 0x400e50
Description: This calls the function
_ZN3xti11XtTraderApi17createXtTraderApiEPKc located at address 0x400e50. The function name is mangled and corresponds to a C++ method
xti::XtTraderApi::createXtTraderApi(char const*). The @plt indicates
that this is a dynamically linked function via the Procedure Linkage
Table (PLT).Purpose: This is likely creating an instance of the XtTraderApi class.
- 0x0000000000400fc6 48 89 45 c0 mov %rax,-0x40(%rbp)
Address: 0x0000000000400fc6
Opcode: 48 89 45 c0
Instruction: mov %rax, -0x40(%rbp)
Description: The return value of the createXtTraderApi function
(stored in rax) is moved into the memory location at -0x40 bytes
offset from the base pointer (rbp). This is likely storing the pointer
to the newly created XtTraderApi object in a local variable.
- 0x0000000000400fca 48 8b 45 c0 mov -0x40(%rbp),%rax
Address: 0x0000000000400fca
Opcode: 48 8b 45 c0
Instruction: mov -0x40(%rbp), %rax
Description: The pointer to the XtTraderApi object (stored at
-0x40(%rbp)) is loaded back into the rax register.
- 0x0000000000400fce 48 8b 00 mov (%rax),%rax
Address: 0x0000000000400fce
Opcode: 48 8b 00
Instruction: mov (%rax), %rax
Description: This dereferences the pointer in rax (which points to the
XtTraderApi object) and loads the value at that memory location into
rax. In C++ terms, this is likely accessing the vtable (virtual table)
pointer of the XtTraderApi object, which is typically the first member
of an object with virtual methods.
- 0x0000000000400fd1 48 83 c0 18 add $0x18,%rax
Address: 0x0000000000400fd1
Opcode: 48 83 c0 18
Instruction: add $0x18, %rax
Description: This adds 0x18 (24 in decimal) to the value in rax. Since
rax currently holds the vtable pointer, this is likely calculating the
address of a specific entry in the vtable. The offset 0x18 suggests
that this is the 4th entry in the vtable (assuming each entry is 8
bytes on a 64-bit system).
- 0x0000000000400fd5 48 8b 10 mov (%rax),%rdx
Address: 0x0000000000400fd5
Opcode: 48 8b 10
Instruction: mov (%rax), %rdx
Description: This dereferences the address in rax (which now points to
a specific vtable entry) and loads the value at that memory location
into rdx. This value is likely a function pointer corresponding to a
virtual method in the XtTraderApi class.
- 0x0000000000400fd8 48 8b 45 c0 mov -0x40(%rbp),%rax
Address: 0x0000000000400fd8
Opcode: 48 8b 45 c0
Instruction: mov -0x40(%rbp), %rax
Description: The pointer to the XtTraderApi object (stored at
-0x40(%rbp)) is loaded back into rax. This is likely preparing the this pointer for the virtual method call.
- 0x0000000000400fdc be 80 7f 40 00 mov $0x407f80,%esi
Address: 0x0000000000400fdc
Opcode: be 80 7f 40 00
Instruction: mov $0x407f80, %esi
Description: The immediate value 0x407f80 is moved into the esi
register. This is likely an argument being passed to the virtual
method.
- 0x0000000000400fe1 48 89 c7 mov %rax,%rdi
Address: 0x0000000000400fe1
Opcode: 48 89 c7
Instruction: mov %rax, %rdi
Description: The pointer to the XtTraderApi object (in rax) is moved
into rdi. In the System V AMD64 ABI, rdi is used for the first
argument to a function, which in this case is the this pointer for the
virtual method call.
- 0x0000000000400fe4 ff d2 callq *%rdx
Address: 0x0000000000400fe4
Opcode: ff d2
Instruction: callq *%rdx
Description: This calls the function whose address is stored in rdx.
Since rdx holds a function pointer from the vtable, this is invoking a
virtual method of the XtTraderApi object.
Summary:
A XtTraderApi object is created, and its pointer is stored on the stack.
The vtable pointer of the object is accessed.
A specific virtual method (at offset 0x18 in the vtable) is resolved.
The virtual method is called with the XtTraderApi object (this pointer) as the first argument and 0x407f80 as the second argument.
This is typical C++ behavior for calling a virtual method on an object. The vtable is used to dynamically resolve the correct method to call at runtime.
因此我們確定了 init 函數在虛表中的位置是 0x18 也就是24, 那么24/8 = 3 , 所以是 0,8,16,24 在虛表中是第4個!
但是,但是,我們的虛表顯示,ini是第3個函數,也就是位置 16 的那個函數!? windows下也用這個虛表,而且我們其他工程都是虛表(windows與linux)一致的啊!
這是怎么回事!!!??
下面展示部分頭文件,大家看看能明白怎么回事嗎:
class XT_API_EXPORT XtTraderApi{public:virtual ~XtTraderApi(){};/*** @brief 獲取XtTraderApi實例* @param [in] address XtApiService監聽端口*/static XtTraderApi* createXtTraderApi(const char* address);/*** @brief 設置數據回調對象* @param [in] pCallback XtTraderApiCallback類實例*/virtual void setCallback(XtTraderApiCallback* pCallback) = 0;/*** @brief 創建api實例,并進行初始化* @param [in] configFilePath 配置文件夾目錄,默認是"../config",運行目錄上一層的config下*/virtual bool init(const char* configFilePath = "../config") = 0;
你看, 析構函數,static函數, setCallback, init 但是 那個 static函數不可能寫在虛表里啊,windows下就是被撇除的,所以是 析構函數,setCallback, init 這個次序啊。
但是,Linux的反匯編已經說了, init 的位置是 第4個函數!!也就是 static函數 也被算在虛表里了。LINUX 下!
服了,之前其他工程,頭文件類聲明中的確沒有 static函數。。。。。。。
這個在 windows中和Linux中實現是不一樣的!!!
所以我們回過頭,再去看之前的錯誤,我們認為我們一直在調用init函數,其實調用的是 setCallback(*pCallback) 函數, 我們以為傳入的是字符串,但是api認為你傳入的是pCallback, 而且最 搞笑 的是, setCallback 函數會檢查 traderApi.ini 是否被加載過,沒有則顯示的錯誤和 init 顯示的錯誤一致!
這錯誤顯示太沒有邏輯了!直接誤導我了幾天!!!!!!
于是修改了虛表, all done!