1

出于学习目的,我正在尝试在内核模块中实现打印功能。我在 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. 请帮忙。

谢谢

奇丹巴拉姆

4

1 回答 1

4

可变参数在 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

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

编译器通常会生成代码,然后将所有参数寄存器压入堆栈,并“捞出”va*函数中的寄存器。

因此,如果我们将上面的源代码替换为printfa 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毫无问题地生成此代码。

于 2013-09-07T00:23:52.040 回答