毒害某些API函数(不是维护者)

Poisoning certain API functions (not being maintainer)

本文关键字:维护者 函数 API      更新时间:2023-10-16

与例如:如何在VC++中毒害标识符
这意味着什么"毒害函数";在C++中?

有没有办法";适当地";毒害一些声明(或实现)不在我控制之下的函数?

具体来说,我正在努力防止意外使用某些Windows API函数,这些函数可能会导致意外结果,例如CreateFileA(使用T宏会隐藏混合ANSI和Unicode的事实)或SetWindowLong(这会导致程序无提示地失败,不会出错,也不会知道64位系统出了什么问题)。

我对中毒的定义;适当地";尝试调用时,函数会以可读错误破坏构建(如果错误发生在源代码中的正确位置,则会获得加分)
最好是可移植的,并且至少它必须与GCC和clang一起使用。

到目前为止我已经尝试/查看了什么:

预处理器

最简单、显而易见的解决方案是利用预处理器:

#define CreateFileA __poison_ANSI_CreateFileA

这对一个特定的函数和其他几个函数都很有效,完全按照预期工作,有一个精确的错误提示问题并指向源中的正确位置:

error: no matching function for call to '__poison_ANSI_CreateFileA'
note: expanded from macro 'CreateFile'
note: expanded from ... (precise location in source)

对于不同的函数,每个函数都需要有一个唯一的名称,以避免冲突的定义错误,这很乏味,但很容易做到。到目前为止还不错,只是不适用于MinGW-w64标头本身使用可变宏篡改名称的函数,例如CreateWindowSetWindowLong。不管怎样,这些编译都很好,中毒与否。

杂注

clang碰巧也理解#pragma GCC poison(它也有自己的版本),看起来和听起来都应该按照我的意愿,以最地道的方式。唉,它不起作用。或者更确切地说,它工作得太好了
当中毒的名称出现在声明中时,被pragma指令中毒的函数已经触发错误,这意味着每次,在所有包含标头的构建中。这对编写程序没有多大帮助!

属性

这些具有巨大的缺点,即必须精确地复制类型定义;模糊函数调用";一方面是合法呼叫的错误。另一方面,它们不起作用。clang抱怨__attribute__((__error__)),但忽略gnu::__error__gnu::error以及gnu::deprecated。此外,标准[[deprecated]]非常好,但似乎什么都没做(编译得很好,甚至没有警告?!)。

我能做什么吗?

已经找到了一种方法,通过在命名空间中添加同一函数的另一个声明来强制执行模糊性。不使用任何杂注或属性,应可移植到任何C++11编译器:

#include <stdio.h>
#define POISONED_BY_NAME(name) 
namespace Poisoned 
{ 
using function_type = decltype(::name); 
extern function_type* name; 
} 
using namespace Poisoned;

POISONED_BY_NAME(puts)
int main()
{
puts("Hello"); 
}

Visual Studio消息:

error C2872: 'puts': ambiguous symbol
1>c:program fileswindows kits10include10.0.17763.0ucrtstdio.h(353): note: could be 'int puts(const char *)'
1>c:test.cpp(43): note: or       'Poisoned::function_type (__cdecl *__cdecl Poisoned::puts)'

gcc消息:

main.cpp: In function 'int main()':
main.cpp:16:5: error: reference to 'puts' is ambiguous
puts("Hello");
^~~~
main.cpp:12:22: note: candidates are: 'int (* Poisoned::puts)(const char*)'
POISONED_BY_NAME(puts)
^~~~
main.cpp:7:35: note: in definition of macro 'POISONED_BY_NAME'
extern function_type* name; 
^~~~
In file included from main.cpp:1:
/usr/include/stdio.h:695:12: note:                 'int puts(const char*)'
extern int puts (const char *__s);
^~~~

我会保留预处理器破解作为第一道防线,因为它似乎很好地捕捉了一些情况,为您提供了源代码中的确切位置。

我还将加入第二层防御,通过确保您提供一个对象文件,其中包含您想要中毒的函数的自己的变体,比如:

HWND WINAPI CreateWindowEx(DWORD dwExStyle, LPCTSTR lpClassName,
LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth,
int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance,
LPVOID lpParam)
{
std::cerr << "Attempting to use poisoned function CreateWindowExn";
exit(1);
}

如果你将其链接为一个对象文件(而不是库,因为库只会链接到未解析的引用),将有三种可能的情况(一旦它通过了标头中毒):

  • 您的对象文件和实际代码都将被链接,并且在链接时您将看到重复的定义
  • 只有您的对象文件是链接的(而不是真正的),所以,当您调用它时,您会收到错误消息并终止。这应该在测试时很早就发现了。不是按照您的要求在编译/链接期间,但希望在您发货之前
  • 只有你的对象文件被链接,而你不会调用它。在这种情况下,你浪费了一点代码空间,但你的可执行文件会运行良好

就这一点而言,一个poisoned.hpoisoned.c就可以了,你只需要确保所有你想要中毒的函数都添加到这两个函数中。

现在我还没有测试过这种方法是否适用于MinGW,但值得一试,因为在最低级别,它需要在某个时候调用realWindows函数。

我唯一能想到的是,它正在调用一些已经编译的MinGW对象,这些对象最终会调用Windows函数。如果发生了这种情况,那么链接器捕获也应该捕捉到这种情况。