va_alist(在64位机器中使用变量列表)

时间:2013-09-06 23:01:18

标签: c linux gcc kernel

我正在尝试在内核模块中实现一个打印功能,以用于学习目的。我在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)

但是我收到__builtin_va_alist未声明的错误。我是否应该尝试找到__builtin_va_alist的定义并将其放入我的包含文件中,或者我不知道这里有什么内容?另外,如果我将__builtin_va_alist更改为__builtin_va_list(注意:a不存在),那么我收到一个名为implicit declaration of __builtin_varargs_start的错误。请帮助。

由于

奇丹巴拉姆

1 个答案:

答案 0 :(得分:3)

varargs如何在x86-64上运行实际上相当复杂。

如果我们以此为例:

#include <stdio.h>

int main()
{
    double f=0.7;
    printf("%d %f %p %d %f", 17, f, "hello", 42, 0.8);

    return 0;
}

它生成的代码是:

    .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

如您所见,浮点值保存在%xmm0%xmm1中,printf函数(与任何其他varargs函数一样)被“告知”有多少参数是通过%eax中的值传递SSE寄存器(在本例中为2)。常规参数在寄存器中传递,因此%edi%esi%edx%ecx包含格式字符串,第一个整数参数,"hello"的地址和第二个整数参数。这遵循x86_64的标准参数排序。

编译器通常会生成代码,然后将所有参数寄存器推送到堆栈中,并“剔除”va*函数中的寄存器。

因此,如果我们采用上述源代码并将printf替换为myprintf,如下所示:

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");
}

myprintf的开头,它确实:

    ... 
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:
    ... 

然后从堆栈中捕获东西的代码非常复杂。这是浮点方面:

.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

现在,我不知道您正在使用哪些编译器标志,因为我的编译器使用gcc -O2 -nostdlib -fno-builtin -ffreestanding生成此代码没有任何问题。