简体   繁体   English

C ++中的函数指针和未知数量的参数

[英]Function pointers and unknown number of arguments in C++

I came across the following weird chunk of code.Imagine you have the following typedef: 我遇到了以下奇怪的代码块。想象你有以下typedef:

typedef int (*MyFunctionPointer)(int param_1, int param_2);

And then , in a function , we are trying to run a function from a DLL in the following way: 然后,在函数中,我们尝试以下列方式从DLL运行函数:

LPCWSTR DllFileName;    //Path to the dll stored here
LPCSTR _FunctionName;   // (mangled) name of the function I want to test

MyFunctionPointer functionPointer;

HINSTANCE hInstLibrary = LoadLibrary( DllFileName );
FARPROC functionAddress = GetProcAddress( hInstLibrary, _FunctionName );

functionPointer = (MyFunctionPointer) functionAddress;

//The values are arbitrary
int a = 5;
int b = 10;
int result = 0;

result = functionPointer( a, b );  //Possible error?

The problem is, that there isn't any way of knowing if the functon whose address we got with LoadLibrary takes two integer arguments.The dll name is provided by the user at runtime, then the names of the exported functions are listed and the user selects the one to test ( again, at runtime :S:S ). 问题是,没有任何方法可以知道我们使用LoadLibrary获取的地址的功能是否有两个整数参数.dll名称由用户在运行时提供,然后列出导出函数的名称和用户选择要测试的那个(再次,在运行时:S:S)。 So, by doing the function call in the last line, aren't we opening the door to possible stack corruption? 那么,通过在最后一行执行函数调用,我们不是打开可能的堆栈损坏的大门吗? I know that this compiles, but what sort of run-time error is going to occur in the case that we are passing wrong arguments to the function we are pointing to? 我知道这会编译,但是在我们将错误的参数传递给我们指向的函数的情况下会发生什么样的运行时错误?

I'm afraid there is no way to know - the programmer is required to know the prototype beforehand when getting the function pointer and using it. 我担心没有办法知道 - 程序员在获取函数指针并使用它时需要事先知道原型。

If you don't know the prototype beforehand then I guess you need to implement some sort of protocol with the DLL where you can enumerate any function names and their parameters by calling known functions in the DLL. 如果你事先不知道原型那么我猜你需要用DLL实现某种协议,你可以通过调用DLL中的已知函数来枚举任何函数名称及其参数。 Of course, the DLL needs to be written to comply with this protocol. 当然,需要编写DLL以符合此协议。

There are three errors I can think of if the expected and used number or type of parameters and calling convention differ: 如果预期和使用的参数数量或类型以及调用约定不同,我可以想到三个错误:

  • if the calling convention is different, wrong parameter values will be read 如果调用约定不同,则将读取错误的参数值
  • if the function actually expects more parameters than given, random values will be used as parameters (I'll let you imagine the consequences if pointers are involved) 如果函数实际上需要的参数多于给定的参数,则随机值将用作参数(我会让你想象如果涉及指针的后果)
  • in any case, the return address will be complete garbage, so random code with random data will be run as soon as the function returns. 在任何情况下,返回地址都是完全垃圾,因此一旦函数返回,就会运行随机数据和随机数据。

In two words: Undefined behavior 用两个词来说: Undefined behavior

If it's a __stdcall function and they've left the name mangling intact (both big ifs, but certainly possible nonetheless) the name will have @nn at the end, where nn is a number. 如果它是一个__stdcall函数, 并且它们保留了完整的名称(两个都是大的ifs,但当然可能仍然可能),名称最后会有@nn ,其中nn是一个数字。 That number is the number of bytes the function expects as arguments, and will clear off the stack before it returns. 该数字是函数期望作为参数的字节数,并将在返回之前清除堆栈。

So, if it's a major concern, you can look at the raw name of the function and check that the amount of data you're putting onto the stack matches the amount of data it's going to clear off the stack. 因此,如果这是一个主要问题,您可以查看函数的原始名称,并检查您放入堆栈的数据量是否与要清除堆栈的数据量相匹配。

Note that this is still only a protection against Murphy, not Machiavelli. 请注意,这仍然只是对Murphy的保护,而不是对Machiavelli的保护。 When you're creating a DLL, you can use an export file to change the names of functions. 在创建DLL时,可以使用导出文件来更改函数的名称。 This is frequently used to strip off the name mangling -- but I'm pretty sure it would also let you rename a function from xxx@12 to xxx@16 (or whatever) to mislead the reader about the parameters it expects. 这经常被用来剥离名称错误 - 但我很确定它也可以让你将一个函数从xxx @ 12重命名为xxx @ 16(或其他),以误导读者关于它所期望的参数。

Edit: (primarily in reply to msalters's comment): it's true that you can't apply __stdcall to something like a member function, but you can certainly use it on things like global functions, whether they're written in C or C++. 编辑:(主要是回复msalters的评论):你确实不能将__stdcall应用于类似成员函数的东西,但你肯定可以在全局函数中使用它,无论它们是用C还是C ++编写的。

For things like member functions, the exported name of the function will be mangled. 对于像成员函数这样的东西,函数的导出名称将被破坏。 In that case, you can use UndecorateSymbolName to get its full signature. 在这种情况下,您可以使用UndecorateSymbolName来获取其完整签名。 Using that is somewhat nontrivial, but not outrageously complex either. 使用它有点不重要,但也不是非常复杂。

I do not think so, it is a good question, the only provision is that you MUST know what the parameters are for the function pointer to work, if you don't and blindly stuff the parameters and call it, it will crash or jump off into the woods never to be seen again... It is up to the programmer to convey the message on what the function expects and the type of parameters, luckily you could disassemble it and find out from looking at the stack pointer and expected address by way of the 'stack pointer' (sp) to find out the type of parameters. 我不这么认为,这是一个很好的问题,唯一的规定就是你必须知道函数指针的工作参数是什么,如果不这样做,盲目地填充参数并调用它,它会崩溃或跳转永远不会再被人看到...这需要程序员传达关于函数期望和参数类型的信息,幸运的是你可以反汇编它并从查看堆栈指针和预期地址中找出通过'堆栈指针'(sp)来找出参数的类型。

Using PE Explorer for instance, you can find out what functions are used and examine the disassembly dump... 例如,使用PE Explorer,您可以找出使用的函数并检查反汇编转储...

Hope this helps, Best regards, Tom. 希望这会有所帮助,最好的问候,汤姆。

It will either crash in the DLL code (since it got passed corrupt data), or: I think Visual C++ adds code in debug builds to detect this type of problem. 它会在DLL代码中崩溃(因为它传递了损坏的数据),或者:我认为Visual C ++在调试版本中添加了代码以检测此类问题。 It will say something like: "The value of ESP was not saved across a function call", and will point to code near the call. 它会说:“ESP的值不是通过函数调用保存的”,而是指向调用附近的代码。 It helps but isn't totally robust - I don't think it'll stop you passing in the wrong but same-sized argument (eg. int instead of a char* parameter on x86). 它有帮助,但并不完全健壮 - 我不认为它会阻止你传入错误但相同大小的参数(例如.int而不是x86上的char *参数)。 As other answers say, you just have to know, really. 正如其他答案所说,你必须知道,真的。

There is no general answer. 没有一般的答案。 The Standard mandates that certain exceptions be thrown in certain circumstances, but aside from that describes how a conforming program will be executed, and sometimes says that certain violations must result in a diagnostic. 标准要求在某些情况下抛出某些异常,但除此之外描述了如何执行符合性的程序,有时还说某些违规必须导致诊断。 (There may be something more specific here or there, but I certainly don't remember one.) (这里或那里可能有更具体的东西,但我当然不记得了。)

What the code is doing there isn't according to the Standard, and since there is a cast the compiler is entitled to go ahead and do whatever stupid thing the programmer wants without complaint. 代码在那里做的不是根据标准,并且由于有一个演员,编译器有权继续做任何程序员想要的愚蠢的事情而不抱怨。 This would therefore be an implementation issue. 因此,这将是一个实施问题。

You could check your implementation documentation, but it's probably not there either. 您可以查看您的实施文档,但它可能也不存在。 You could experiment, or study how function calls are done on your implementation. 您可以尝试或研究如何在您的实现上完成函数调用。

Unfortunately, the answer is very likely to be that it'll screw something up without being immediately obvious. 不幸的是,答案很可能就是它会把事情搞砸而不会立即显而易见。

Generally if you are calling LoadLibrary and GetProcByAddrees you have documentation that tells you the prototype. 通常,如果您正在调用LoadLibrary和GetProcByAddrees,那么您可以获得告诉您原型的文档。 Even more commonly like with all of the windows.dll you are provided a header file. 更常见的是,与所有windows.dll一样,您将获得一个头文件。 While this will cause an error if wrong its usually very easy to observe and not the kind of error that will sneak into production. 虽然这会导致错误,如果错误,通常很容易观察,而不是那种潜入生产的错误。

Most C/C++ compilers have the caller set up the stack before the call, and readjust the stack pointer afterwards. 大多数C / C ++编译器让调用者在调用之前设置堆栈,然后重新调整堆栈指针。 If the called function does not use pointer or reference arguments, there will be no memory corruption, although the results will be worthless. 如果被调用的函数不使用指针或引用参数,则不存在内存损坏,尽管结果将毫无价值。 And as rerun says, pointer/reference mistakes almost always show up with a modicum of testing. 正如重新运行所说,指针/参考错误几乎总是会出现一些测试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM