我正在为我的玩具操作系统内核实现一个printk
功能,目标是x86平台。如果我这样打printk
:
uint64_t x = 0xdead;
uint64_t z = 0xbeef;
printk("%p %s\n", x & z, "yes");
即将64位整数(x & z
)传递给它,然后生成的汇编代码为:
c01000bd: c7 45 f0 ad de 00 00 movl $0xdead,-0x10(%ebp)
c01000c4: c7 45 f4 00 00 00 00 movl $0x0,-0xc(%ebp)
c01000cb: c7 45 e8 ef be 00 00 movl $0xbeef,-0x18(%ebp)
c01000d2: c7 45 ec 00 00 00 00 movl $0x0,-0x14(%ebp)
c01000d9: 8b 45 f0 mov -0x10(%ebp),%eax
c01000dc: 23 45 e8 and -0x18(%ebp),%eax
c01000df: 89 c3 mov %eax,%ebx
c01000e1: 8b 45 f4 mov -0xc(%ebp),%eax
c01000e4: 23 45 ec and -0x14(%ebp),%eax
c01000e7: 89 c6 mov %eax,%esi
c01000e9: 68 e4 17 10 c0 push $0xc01017e4
c01000ee: 56 push %esi
c01000ef: 53 push %ebx
c01000f0: 68 e8 17 10 c0 push $0xc01017e8
c01000f5: e8 27 0a 00 00 call c0100b21 <printk>
你可以看到这里gcc使用两个32位寄存器(%esi
和%ebx
)来存储64位值。结果是推送到堆栈的参数数量变为4而不是3.如果printk
内的代码无法确定参数的大小并正确使用它,则堆栈访问将被搞砸。 / p>
所以我的问题是,在实现可变参数函数时,当我使用va_arg
宏时,如何知道下一个参数的大小?或者具体来说,如何解决这个32位与64位printk
问题?
答案 0 :(得分:3)
C不提供确定参数大小或类型的一般方法。对于printf
类似函数,您必须依赖格式字符串。这就是为什么格式字符串与传递的参数相比错误的原因可能导致一些非常错误的代码;因为如果你的尺码错了,你可以阅读预期的论点。格式字符串还会告诉您参数的数量。
还要考虑的另一件事是可变参数函数的默认参数提升。您可以在this SO question。
中找到有关该信息的更多信息以下是格式字符串与通过参数传递的类型不匹配时返回的内容的示例。您需要在32位计算机上编译它或使用gcc -m32 file.c
:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
printf("%u: %s\n", (1ULL << 63), "Hello World");
return 0;
}
那么为什么这么糟糕,看起来没问题,我们有2个格式字符和2个参数对吗?好吧不是那么快,因为32位机器%u
是32位,所以是char指针。然而(1ULL <&lt;&lt; 63)是64位长。所以会发生什么是将96个字节压入堆栈(或者传递参数)。格式字符串虽然只使用前64个。也恰好是前32位将全为零,而第二个32位(用于字符指针的位)具有值(1&lt; 31)。由于printf
期望一个char指针,该值被解除引用,导致未定义的行为,特别是我的机器上的分段错误。
答案 1 :(得分:1)
您必须使用其中一个参数来确定大小(通常是第一个参数)。使用printf(),大小由格式字符串中的格式说明符确定。这没有什么真正神奇的。