背景:
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!