c - va_alist(在 64 位机器上使用变量列表)

标签 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 的错误。请帮忙。

谢谢

奇丹巴拉姆

最佳答案

可变参数在 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 中的值(在本例中为 2)在 SSE 寄存器中传递了多少参数。常规参数在寄存器中传递,因此 %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:
    ... 

然后从堆栈中取出东西的代码非常复杂。这是 float :

.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 生成此代码没有任何问题。

关于c - va_alist(在 64 位机器上使用变量列表),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18667830/

相关文章:

linux - 两个 open/proc 条目的处理方式不同

c - gcc - 2 个版本,对内联函数的不同处理

c - 如何在库加载时将参数传递给构造函数?

c++ - 逗号运算符有什么作用?

c++ - CUDA PTX,驱动程序 api - 执行后如何从内核获取全局变量

c - 外部声明的 Genie 语法

php - ubuntu php 5.5.38 升级到 5.5.9

c - 使用 sscanf() 函数解析归档文件

linux - qnx 获取旧资源管理器的 resmgr_context_t

ios - 为什么 GCC -Wselector 和 -Wundeclared-selector 对已声明的选择器发出警告?