从 C 库中调用符号时忽略返回值是否安全



我一直在摆弄LLVM,并编写了一个简单的编译器。它使用 libc 作为其标准库。当然,我必须以某种方式在我的 IR 中声明函数。

我注意到以下内容似乎有效:

declare void @puts(i8*)

在 C 中,函数定义如下:

int puts(const char *s);

所以它真的应该是

declare i32 @puts(i8*)

这是一个非常简单的情况,但我相信在路上的某个地方我会犯错误来声明这些功能。例如,在我阅读手册页之前,我不知道puts返回了一个 int。

这些错误有多严重?它是弄乱堆栈还是LLVM以某种方式处理它?此类错误对安全有何影响?

注意:我无法在putsvoid声明中产生任何错误。

答案取决于 C 编译器的 ABI 使用的调用约定。在 x86 和 x86-64 上的大多数 C 编译器使用的约定中,返回值在寄存器中传递。错误地将 int -return 函数声明为 void将导致返回寄存器的值被忽略(如果您不使用它,无论如何都会被忽略)。这不会造成任何伤害,因为调用方无论如何都负责保存eax寄存器。

例如,以下代码:

void callee(int, int, int);
void caller(void)
{
  callee(1, 2, 3);
}

。如果您声明callee返回 int 而不是 void,将被编译成完全相同的程序集。

这适用于"小"返回类型,即由整数、双精度浮点数或 64 位整数(x86 在两个整数寄存器中返回)组成的返回类型。大型返回类型的处理方式不同 - 如果将 callee 声明更改为以下内容:

struct { char x[100]; } callee(int, int, int);

。调用代码将发生巨大变化,尽管传入的类型没有更改。返回结构现在将在调用方的堆栈上分配,其地址将作为隐藏的第一个参数传递给被调用方(这是在 x86 上,在 x86-64 上情况略有不同),预计会将返回值写入该区域。

换句话说,只要您了解调用约定,并且注意不要错误地声明按值返回大型类型的函数(AFAIK 在标准 C 和 POSIX 库中不存在),错误的声明就会起作用。

小的返回值通常放在返回值寄存器中,因此忽略这些值不会致命地崩溃。对于较大的值,某些 ABI 要求调用方分配堆栈空间并将其作为不可见的第一个参数传递给函数,在这种情况下,您的程序可能会很快崩溃,因为您不会分配或传递它。 如果您使用的是不存储前一帧指针的 abi,即它必须知道它自己的堆栈帧有多大,并且 abi 允许被调用方调整堆栈指针,这也是致命的。

基本上它可能会工作,直到它不工作。

理查德

到目前为止,答案很好,但我认为一个很大的含义是,如果您忽略 C 函数返回,作为其功能的一部分,分配内存或打开/创建文件等,然后返回某种指针。

当然,忽略这些将孤立仅在程序退出时释放的内存(如果它走得那么远),使文件保持打开等。

基本上,如果您调用的函数返回除寄存器值或堆栈实例值之外的任何内容,则影响可能很大。

最新更新