Reputation: 1643
I am trying to implement a print function in the kernel module for my learning purposes. I am emulating it on QEMU.
#define va_alist __builtin_va_alist
#define va_dcl __builtin_va_list_t __builtin_va_list; ...
#define va_start(ap) __builtin_varargs_start(ap)
#define va_arg(ap, type) __builtin_va_arg((ap), type)
#define va_end(ap) __builtin_va_end(ap)
But I am getting the error that __builtin_va_alist is undeclared. Should I try to find the definition of __builtin_va_alist also and put it in my include file or am I not aware of something here? Also, If i change __builtin_va_alist to __builtin_va_list ( note: a is not there ), then I am getting an error called implicit declaration of __builtin_varargs_start
. Kindly help.
Thanks
Chidambaram
Upvotes: 1
Views: 687
Reputation: 129484
How varargs works on x86-64 is actually fairly complicated.
If we take this as an example:
#include <stdio.h>
int main()
{
double f=0.7;
printf("%d %f %p %d %f", 17, f, "hello", 42, 0.8);
return 0;
}
The code it generates is:
.file "printf.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC1:
.string "hello"
.LC3:
.string "%d %f %p %d %f"
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB11:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $42, %ecx
movl $.LC1, %edx
movsd .LC0(%rip), %xmm1
movl $17, %esi
movsd .LC2(%rip), %xmm0
movl $.LC3, %edi
movl $2, %eax
call printf
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE11:
.size main, .-main
.section .rodata.cst8,"aM",@progbits,8
.align 8
.LC0:
.long 2576980378
.long 1072273817
.align 8
.LC2:
.long 1717986918
.long 1072064102
.ident "GCC: (GNU) 4.6.3 20120306 (Red Hat 4.6.3-2)"
.section .note.GNU-stack,"",@progbits
As you can see, the floating point values are held in %xmm0
and %xmm1
, and the printf
function (like any other varargs function) is "told" how many arguments are passed in SSE registers by the value in %eax
(2 in this case). Regular arguments are passed in registers, so %edi
, %esi
, %edx
, %ecx
contain the format string, the first integer argument, the address of "hello"
and the second integer argument. This follows the standard argument ordering of x86_64.
The compiler normally generates code to then push all the argument registers on the stack, and "fish out" the registers in the va*
functions.
So if we take the above source code and replace the printf
with a myprintf
, which looks like this:
void myprintf(const char *fmt, ...)
{
va_list va;
int i;
va_start(va, fmt);
for(i = 0; i < 5; i++)
{
switch(i)
{
case 1:
case 4:
{
double d = va_arg(va, double);
printf("double %f:", d);
}
break;
default:
{
long l = va_arg(va, long);
printf("long %ld:", l);
}
}
}
printf("\n");
}
at the beginning of myprintf
it does:
...
movq %rsi, 40(%rsp)
movq %rdx, 48(%rsp)
movq %rcx, 56(%rsp)
movq %r8, 64(%rsp)
movq %r9, 72(%rsp)
je .L2
movaps %xmm0, 80(%rsp)
movaps %xmm1, 96(%rsp)
movaps %xmm2, 112(%rsp)
movaps %xmm3, 128(%rsp)
movaps %xmm4, 144(%rsp)
movaps %xmm5, 160(%rsp)
movaps %xmm6, 176(%rsp)
movaps %xmm7, 192(%rsp)
.L2:
...
The code to then fish things out of the stack is quite complicated. This is the floating point side:
.L4:
.cfi_restore_state
movl 12(%rsp), %edx
cmpl $176, %edx
jae .L5
movl %edx, %eax
addq 24(%rsp), %rax
addl $16, %edx
movl %edx, 12(%rsp)
.L6:
movsd (%rax), %xmm0
movl $.LC0, %edi
movl $1, %eax
call printf
jmp .L7
.p2align 4,,10
.p2align 3
.L8:
movq 16(%rsp), %rax
leaq 8(%rax), %rdx
movq %rdx, 16(%rsp)
jmp .L9
.p2align 4,,10
.p2align 3
.L5:
movq 16(%rsp), %rax
leaq 8(%rax), %rdx
movq %rdx, 16(%rsp)
jmp .L6
Now, I don't know what compiler flags you are using, because my compiler generates this code with gcc -O2 -nostdlib -fno-builtin -ffreestanding
without any problem.
Upvotes: 4