我一直在摆弄LLVM并写了一个简单的编译器。它使用libc作为其标准库。当然,我必须以某种方式在我的IR中声明函数。
我注意到以下内容似乎有效:
declare void @puts(i8*)
在C中,函数的定义如下:
int puts(const char *s);
所以它应该是
declare i32 @puts(i8*)
这是一个非常简单的案例,但我确信在路上的某个地方,我会犯错,宣布这些功能。例如,在我阅读联机帮助页之前,我不知道puts
返回了一个int。
这些错误有多严重?它是否混乱堆栈或LLVM以某种方式处理它?这些错误的安全隐患是什么?
注意:我无法使用void
puts
声明生成任何错误。
答案 0 :(得分:4)
答案取决于C编译器的ABI使用的调用约定。在大多数C编译器在x86和x86-64上使用的约定中,返回值在寄存器中传递。将int
- 返回函数错误地声明为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上略有不同),预计会写入返回值那个地区。
换句话说,只要您理解调用约定,并且注意不要错误地声明按值返回大型类型的函数(标准C和POSIX库中不存在AFAIK),则错误宣言将有效。
答案 1 :(得分:3)
小返回值通常放在返回值寄存器中,因此忽略那些不会发生致命崩溃的事件。对于较大的值,一些ABI要求调用者分配堆栈空间并将其作为不可见的第一个参数传递给函数,在这种情况下,您的程序可能会快速崩溃,因为您不会分配或传递它。如果您正在使用不存储前一帧指针的abi,请执行此操作。它必须知道它自己的堆栈帧有多大,而abi允许被调用者调整堆栈指针,这也是致命的。
基本上它可能会起作用,直到它没有。
答案 2 :(得分:0)
理查德
到目前为止答案是好的,但我会考虑一个重要的含义是,如果你忽略C函数返回,作为其功能的一部分,分配内存或打开/创建文件等等,然后返回某种指针。
当然,忽略这些将会释放只在程序退出时才会释放的内存(如果它使它那么远),保持文件打开等等。
基本上,如果您调用的函数返回任何 BUT 寄存器值或堆栈实例值,则含义可能很大。