AArch64中va_list/va_start/va_arg/...的实现

2023-05-16

版权声明:本文为笔者本人「ashimida@」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lidan113lidan/article/details/123962416

更多内容可关注微信公众号   

一、背景[3]

1. 可变参函数

可变参函数指的是一个可以接受可变个参数的函数, 调用此函数时只有caller知道为此函数传入了多少个参数, 可变参函数callee只知道caller最少传入了多少个参数:

  • callee中可以确定的参数称为命名参数(Named arguments)
  • callee中不确定的参数称为匿名参数(Anonymous arguments)

  对于可变参函数(callee), 其需要:

  • 其函数栈中预留空间存储所有可能的匿名寄存器参数(否则由于不确定传入了多少个寄存器参数,不保存则AAPCS64标准中所有传参寄存器默认在callee中不能用)
  • 运行时通常需要通过已知的某个命名参数来确定caller到底传入了多少个参数(caller在调用可变参数函数时也需要显式或隐式的告知callee自己传入了多少个参数), 如:
/* 常见的处理可变参的宏定义如下,后续代码中直接使用内联函数
	#define va_start(v,l)	__builtin_va_start(v,l)
	#define va_end(v)		__builtin_va_end(v)
	#define va_arg(v,l)		__builtin_va_arg(v,l)
	#define va_copy(d,s)	__builtin_va_copy(d,s)
*/

//int printf (const char * format, ... );
#define weak __attribute__((weak))
weak int func1(int x, ...)
{
        int i = x;
    
        __builtin_va_list vl;
        __builtin_va_start (vl, x);
        while(i > 0)
        {
                printf("%d\n", va_arg(vl, int));					    // callee根据caller传入的参数个数逐个获取参数
                i --;
        }
        __builtin_va_end (vl);

        return 0;
}

int main(void)
{
   register void * sp asm("sp");
   printf("main1: sp:%p, fp:%p\n", sp, __builtin_frame_address(0));		//printf通过参数R0隐式确定参数个数
   
   func1(3, 1, 2, 3);													//一般情况下调用可变参数需通过命名参数显式告知参数个数
}  

## 输出
tangyuan@ubuntu:~/tests/gcc/aarch64_stack/caller_callee_parm_test$ ./main 
main1: sp:0x40007ff860, fp:0x40007ff860
1
2
3

2. 可变参函数的参数

可变参函数的参数分为四类, 即寄存器命名参数, 寄存器匿名参数, 栈命名参数, 栈匿名参数, 举例如下:

#define REP7(X) X,X,X,X,X,X,X
#define REP8(X) X,X,X,X,X,X,X,X
#define weak __attribute__((weak))
/*
   func1的:
   * 第1个参数(x0,必须指定)为命名寄存器参数
   * 第2-8个参数(若有)为匿名寄存器参数
   * 第9-n个参数(若有)为匿名栈参数
*/
weak int func1(int x0, ...) { ... }
/*
   func2的:
   * 前8个参数(x0-x7,必须指定)为命名寄存器参数
   * 第9个参数(x8,必须指定)为命名栈参数
   * 第10-n个参数(若有)为匿名栈参数
*/
weak int func2(int x0, int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8, ...) { ... }

可变参函数的参数传递同样满足AAPCS64:

* 对caller来说, 编译期间即可确定其向可变参callee传入了多少个参数,故其直接按照AAPCS64标准布局参数(寄存器参数直接赋值给硬件寄存器,栈参数保存到outgoing栈对应位置).

* 对callee来说, 编译期间只能确定有多少个命名参数:

  • 命名寄存器参数直接通过硬件寄存器获取
  • 命名栈参数通过incoming栈获取

* 对callee来说, 匿名参数的个数通常需直接或间接通过某个命名参数得知:

  • 匿名栈参数同样继续通过incoming区域获取
  • 匿名寄存器参数则是从匿名寄存器存储区域(callee-allocated save area for register varargs)获取

二、va_xxx系列函数

  在可变参函数中通常会使用va_xxx系列函数来获取匿名参数,va_xxx函数在gcc中会对应到其对应的一个builtin函数上:

//stdarg.h
#define va_start(v,l)	__builtin_va_start(v,l)
#define va_end(v)		__builtin_va_end(v)
#define va_arg(v,l)		__builtin_va_arg(v,l)
#define va_copy(d,s)	__builtin_va_copy(d,s)

  gcc中定义的此系列内联函数包括:

__builtin_va_list
__builtin_va_start
__builtin_va_end
__builtin_va_copy
__builtin_next_arg
__builtin_saveregs
__builtin_va_arg

  这里只介绍其中部分,其余类似的函数见源码.

三、__builtin_va_list

  __builtin_va_list是gcc内置的一个结构体类型, 在gcc内部定义如下:

/* 关于函数栈的构成可参考[3], AAPCS64中可变参函数的说明可参考[1] */
typedef struct {
       void *__stack;					/* __stack 记录下一个匿名栈参数的存储位置, 随着va_arg的调用可能变化 */
       void *__gr_top;					/* __gr_top 记录最后一个匿名通用寄存器参数的尾地址, 其不随va_arg调用变化 */
       void *__vr_top;					/* __vr_top 记录最后一个匿名浮点寄存器参数的尾地址, 其不随va_arg调用变化 */
       int   __gr_offs;				    /* __gr_offs 记录下一个匿名通用寄存器参数到__gr_top的偏移(负数),随着va_arg的调用可能变化 */
       int   __vr_offs;					/* __vr_offs 记录下一个匿名浮点寄存器参数到__vr_top的偏移(负数),随着va_arg的调用可能变化 */
} __builtin_va_list;

  各字段在函数栈中的位置如下:

/*
 *             high address
 *                 ---+--++------------------------------+
 *                    |  ||incoming stack arguments      |
 *                    |  ||------------------------------|
 *                    |  ||  ......                      |
 *                    |  ||------------------------------|
 *                    |  ||  param 9                     |
 *                    |  ||------------------------------|-- <-- vl->stack (指向incoming栈中第一个匿名栈参数的位置)
 *                    |  ||  param 8                     |
 *                    |  ||==============================+-+ <-- virtual_incoming_args_rtx/vl->grtop (指向最后一个匿名通用寄存器参数的尾地址)
 *                    |  ||callee-allocated save area    | |
 *                    |  ||for register varargs          | |
 *   stack for        |  ||------------------------------| |
 * variable arguments |  ||  param 7                     | | vl->groff		(记录下一个匿名通用寄存器参数到grtop的偏移)
 *                    |  ||------------------------------| |
 *                    |  ||  param 6                     | |
 *                    |  ||------------------------------+-+
 *                    |  ||  .......(aligned)            |
 *                    |  ||------------------------------+-+ <-- vl->vrtop	(指向最后一个匿名浮点寄存器参数的尾地址)
 *                    |  ||  VPRs(max is 8*16)           | |
 *                    |  ||                              | |
 *                    |  ||  ......                      | |
 *                    |  ||------------------------------| | vl->vroff		(记录下一个匿名浮点寄存器参数到vrtop的偏移)
 *                    |  ||  vparam1                     | |
 *                    |  ||------------------------------| |
 *                    |  ||  vparam0                     | |
 *                 ---+--+|==============================+-+
 *                       ||  .......                     |
 *                       ||                              |
 *                       ++------------------------------+-- <- stack_pointer_rtx
 *              low address
 */

  一个经过初始化后的__builtin_va_list结构体(vl)中:

  • 可以通过vl->__stack获取下一匿名栈参数, [vl->__stack, ......] 为匿名栈参数区
  • 可以通过vl->__gr_top + vl->__gr_offs 获取下一个匿名通用寄存器参数, [vl->__gr_top + vl->__gr_offs , vl->__gr_top]为匿名通用寄存器参数区
  • 可以通过vl->__vr_top + vl->__vr_offs 获取下一个匿名浮点寄存器参数, [vl->__vr_top + vl->__vr_offs , vl->__vr_top]为匿名浮点寄存器参数区

四、__builtin_va_start(vl, x)

  __builtin_va_start(vl, x)的作用是初始化vl结构体,在aarch64中这里的x没有实际的作用,但调用此函数时x必须是此函数的最后一个命名参数,否则会编译报错。__builtin_va_list vl; 就是一个简单的结构体局部变量定义,故在使用vl之前必须调用__builtin_va_start函数对其初始化, __builtin_va_start的作用为:

  • 设置vl->__stack 指向此函数第一个匿名栈参数可能出现的位置
  • 设置vl->__gr_top 指向最后一个匿名通用寄存器参数尾地址
  • 设置vl->__gr_offs 为vl->__gr_top 到 第一个匿名通用寄存器参数之间的偏移(负数)
  • 设置vl->__vr_top指向最后一个匿名浮点寄存器参数尾地址
  • 设置vl->__vr_offs 为vl->__vr_top 到 第一个匿名浮点寄存器参数之间的偏移(负数)

  需要注意的是, 编译器按照AAPCS64布局函数栈,一个函数的第一个匿名栈参数/通用/浮点寄存器参数的位置(基于sp的偏移)只与当前函数声明的(命名)参数列表有关故这些偏移在编译阶段即可确定, __builtin_va_start 基于当前sp + offset计算此次运行时这些参数的位置。

五、__builtin_va_arg(vl, type)

  __builtin_va_arg(vl, type) 的作用是返回当前尚未访问的一个类型为type的匿名参数的值(注意返回的是值而不是地址), 并调整vl中的__stack/__gr_offset/__vr_offset指向下一个匿名参数位置。__builtin_va_arg从哪个区域返回匿名参数是由当前vl中的信息和此次要获取的参数类型type共同决定的:

  • type为浮点类型时,__builtin_va_arg会先尝试在匿名浮点寄存器参数区域查找,若没有则在incoming栈中查找下一个参数
  • type为整型时, __builtin_va_arg会先尝试在匿名通用寄存器参数区查找,若没有则在incoming栈中查找下一个参数

  以通用寄存器为例, 判断匿名通用寄存器参数区是否还有参数的标准为:

  1. [vl->__gr_top + vl->__gr_offs , vl->__gr_top] 区域是否还能存下一个类型为type的参数
  2. 若能存下则按照type类型大小返回 vl->__gr_top + vl->__gr_offs 中存储的值作为下一个匿名参数 (此函数总是返回,无法get到地址)
  3. vl->__gr_offs += sizeof(type); offset指向下一个匿名通用寄存器参数(未考虑对齐)

  其伪代码[1]简化后如下:

type __builtin_va_arg (va_list ap, type)
{
      int nreg, offs;                                                                                                                                                                                     
      /* 若这是个浮点类型的参数(根据type判断) 则先尝试从VPRs 匿名浮点寄存器参数区域中获取参数 */
      if (type passed in simd/fp registers) 
      {
        offs = ap.__vr_offs;
        if (offs >= 0)         		   			/* 若匿名浮点寄存器参数区域已没有参数, 则直接去incoming栈区域获取下一个参数 */
          goto on_stack; 	
        
        nreg = (sizeof(type) + 15) / 16;		/* 否则看当前匿名浮点寄存器参数区域中是否能存下此类型的参数 */
        ap.__vr_offs = offs + (nreg * 16);  	/* 如果存不下则说明此参数只能是栈参数,跳转到on_stack从incoming栈获取 */ 	
        if (ap.__vr_offs > 0)		   	
          goto on_stack;
          
        return *(type *)(ap.__vr_top + offs);   /* 如果匿名浮点寄存器参数区域还有参数,则直接从此区域中获取并返回(返回"值"而不是地址) */	 
          
      } else {									/* 否则尝试从GPRs匿名通用寄存器参数区域获取参数 */
        offs = ap.__gr_offs;
        if (offs >= 0)          				/* 若匿名通用寄存器参数区域为空, 则直接去incoming栈区域获取参数 */
          goto on_stack;
          
        if (alignof(type) > 8) offs = (offs + 15) & -16; 
        nreg = (sizeof(type) + 7) / 8;           /* 计算此类型占用几个标准寄存器空间 */
        ap.__gr_offs = offs + (nreg * 8);        /* 根据类型确定匿名通用寄存器区域是否还能存下一个此类型的匿名参数 */
        if (ap.__gr_offs > 0)                    /* 如果不能(offs > 0),则去incoming栈区域获取下一个参数 */
          goto on_stack;
          
        return *(type *)(ap.__gr_top + offs);   /* 若参数在GPRs匿名通用寄存器区域,则直接返回参数值(而不是地址) */
      }
    
/* 若此匿名参数不在匿名寄存器区域则其只能保存在incoming栈中, 这里从incoming栈中获取此匿名参数 */
on_stack:
      intptr_t arg = ap.__stack;              /* 获取当前incoming栈中尚未被解析到的匿名栈参数地址 */
    
      if (alignof(type) > 8)			      /* 按需对齐 */
              arg = (arg + 15) & -16;
    
      ap.__stack = (void *)((arg + sizeof(type) + 7) & -8);		/* 修改ap.__stack指向下一个可能出现的匿名栈参数的位置 */
      return *(type *)arg;			      	  /* 返回此匿名栈参数的值 */
}

  举例如下:

#include <stdio.h>
#include <stdarg.h>

struct __va_list			/* 自定义的__va_list结构体 */
{
        void *__stack;
        void *__gr_top;
        void *__vr_top;
        int   __gr_offs;
        int   __vr_offs;
};

int func(int p0, int p1, int p2, ...)
{
        register unsigned long sp asm("sp");
        unsigned long  var0 = (unsigned long)&var0 - sp;		/* 第一个局部变量到callee入口sp的offset */
        unsigned long  var1 = (unsigned long)&var1 - sp;	    /* 第二个局部变量到callee入口sp的offset */
        __builtin_va_list vl;

        p0 = (unsigned long)&p0 - sp;							/* 第一个命名寄存器参数的存储位置到callee入口sp的offset
                                                                 * (解引用会导致callee在局部变量中为命名寄存器参数分配存储空间,
                                                                 *  这里顺便解释局部变量的内存分配)
                                                                 */
        p1 = (unsigned long)&p1 - sp;							/* 第二个命名寄存器参数的存储位置到callee入口sp的offset */
        printf("[+] sp:%p\n", sp);
        printf("[+] var0 in addr %p (offset %p)\n", &var0, var0);
        printf("[+] var1 in addr %p (offset %p)\n", &var1, var1);
        printf("[+] vl in addr %p, size:%d\n", &vl, sizeof(vl));	/* 局部变量vl的地址及大小 */

        printf("[+] p0 in addr %p (offset %p)\n", &p0, p0);
        printf("[+] p1 in addr %p (offset %p)\n", &p1, p1);

        var0 = 0;
        __builtin_va_start (vl, p2);	/* p2记录此函数匿名寄存器个数 */
        while(var0 < p2) {
                var0 ++;
                if( var0 < 6) {
                        /* 输出剩余匿名通用寄存器参数的存储首地址和offset */
                        var1 = ((struct __va_list *)&vl)->__gr_top + ((struct __va_list *)&vl)->__gr_offs;
                } else {
                        /* 输出下一个匿名栈参数的存储位置 */
                        var1 = ((struct __va_list *)&vl)->__stack;
                }
                printf("[+] p%d in addr %p (offset %p) with value %d\n", var0 + 2, var1, var1 - sp, va_arg(vl, int));
        }
}

int main(void)
{
        func(0, 0, 7, 1, 2, 3, 4, 5, 6, 7);
}

  输出结果:

##aarch64-linux-gnu-gcc main.c -O2 -o main
## 输出结果
tangyuan@ubuntu:~/tests/gcc/aarch64_stack/caller_callee_parm_test$ ./main
[+] sp:0x40007ffdf0
[+] var0 in addr 0x40007ffe38 (offset 0x48)
[+] var1 in addr 0x40007ffe30 (offset 0x40)
[+] vl in addr 0x40007ffe10, size:32
[+] p0 in addr 0x40007ffe0c (offset 0x1c)
[+] p1 in addr 0x40007ffe08 (offset 0x18)
[+] p3 in addr 0x40007ffec8 (offset 0xd8) with value 1
[+] p4 in addr 0x40007ffed0 (offset 0xe0) with value 2
[+] p5 in addr 0x40007ffed8 (offset 0xe8) with value 3
[+] p6 in addr 0x40007ffee0 (offset 0xf0) with value 4
[+] p7 in addr 0x40007ffee8 (offset 0xf8) with value 5
[+] p8 in addr 0x40007ffef0 (offset 0x100) with value 6
[+] p9 in addr 0x40007ffef8 (offset 0x108) with value 7

  其函数栈分布如图:

六、__builtin_va_end(vl)

  在man 手册中对于va_end的描述是:所有vl结构体在使用完毕后都要调用va_end, 以标记在此后的代码中vl结构体不再可用.

  未开启编译优化时,__builtin_va_end(vl)语句并没有什么实质性作用; 但当开启编译优化时若在 __builtin_va_end(vl)之后再使用 vl,则可能因优化导致不可预期的错误,如:

#include <stdio.h>
#include <alloca.h>
#include <stdarg.h>

#define weak __attribute__((weak))
struct __va_list
{
        void *__stack;     //下一个栈参数地址
        void *__gr_top;    //GPR参数保存的位置
        void *__vr_top;    //FP/SIMD参数保存的位置
        int   __gr_offs;   //从__gr_top到下一个GP参数的offset
        int   __vr_offs;   //从__vr_top到下一个FP/SIMD寄存器参数的位置
};

weak int func1(int p0, int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8, ...)
{
      __builtin_va_list vl; 
      __builtin_va_start (vl, p8);
      printf("%d\n", va_arg(vl, int));
        __builtin_va_end (vl);

      printf("%d\n", va_arg(vl, int));
      return 0;
}

int main(void)
{
        func1(3, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2); 
}


##aarch64-linux-gnu-gcc main.c -O2 -o main
##aarch64-linux-gnu-gcc main.c -O2 -o main1 -fno-tree-dse
tangyuan@ubuntu:~/tests/gcc/aarch64_stack/varargs_test$ ./main1 
1
2							## 正确
tangyuan@ubuntu:~/tests/gcc/aarch64_stack/varargs_test$ ./main
1
1							## 错误

七、源码分析(TLDR)

1. __builtin_va_list

  __builtin_va_list 是gcc内部定义的一个结构体类型, 在源码解析前 __builtin_va_list 类型声明结点已经定义好了,当语法分析解析到 符号 "__builtin_va_list"时会直接将其识别为一个结构体类型,其转换为c代码类似:

typedef struct {
       void *__stack;
       void *__gr_top;
       void *__vr_top;
       int   __gr_offs;
       int   __vr_offs;
} __builtin_va_list;

  此结构体在cc1初始化阶段定义:

//toplev::main => lang_dependent_init => lang_hooks.init => c_init_decl_processing => c_common_nodes_and_builtins
// => build_common_tree_nodes 
void build_common_tree_nodes (bool signed_char)
{
  ......
  tree t = targetm.build_builtin_va_list ();	/* aarch64中为 aarch64_build_builtin_va_list */
  va_list_type_node = t;
}

/* 定义结构体类型结点(RECORD_TYPE) __va_list */
static tree aarch64_build_builtin_va_list (void)
{
  tree va_list_name;
  tree f_stack, f_grtop, f_vrtop, f_groff, f_vroff;

  /* 创建名为 __va_list的结构体类型 */
  va_list_type = lang_hooks.types.make_type (RECORD_TYPE);
  va_list_name = build_decl (BUILTINS_LOCATION, TYPE_DECL, get_identifier ("__va_list"), va_list_type);
  DECL_ARTIFICIAL (va_list_name) = 1;			/* 标记这是编译器生成的一个类型声明结点 */
  TYPE_NAME (va_list_type) = va_list_name;		/* 设置类型结点对应的名字 */
  TYPE_STUB_DECL (va_list_type) = va_list_name;

  /* 定义此结构体中的每个字段, 结果类似c代码:
     struct
     {
        void *__stack;
        void *__gr_top;
        void *__vr_top;
        int   __gr_offs;
        int   __vr_offs;
     };
  */
  f_stack = build_decl (BUILTINS_LOCATION, FIELD_DECL, get_identifier ("__stack"), ptr_type_node);
  f_grtop = build_decl (BUILTINS_LOCATION, FIELD_DECL, get_identifier ("__gr_top"), ptr_type_node);
  f_vrtop = build_decl (BUILTINS_LOCATION, FIELD_DECL, get_identifier ("__vr_top"), ptr_type_node);
  f_groff = build_decl (BUILTINS_LOCATION, FIELD_DECL, get_identifier ("__gr_offs"), integer_type_node);
  f_vroff = build_decl (BUILTINS_LOCATION, FIELD_DECL, get_identifier ("__vr_offs"), integer_type_node);
  ......
  
  /* 设置成员所在的结构体 */
  DECL_FIELD_CONTEXT (f_stack) = va_list_type;
  DECL_FIELD_CONTEXT (f_grtop) = va_list_type;
  DECL_FIELD_CONTEXT (f_vrtop) = va_list_type;
  DECL_FIELD_CONTEXT (f_groff) = va_list_type;
  DECL_FIELD_CONTEXT (f_vroff) = va_list_type;

  /* 设置结构体成员顺序 */
  TYPE_FIELDS (va_list_type) = f_stack;
  DECL_CHAIN (f_stack) = f_grtop;
  DECL_CHAIN (f_grtop) = f_vrtop;
  DECL_CHAIN (f_vrtop) = f_groff;
  DECL_CHAIN (f_groff) = f_vroff;

  layout_type (va_list_type);			/* 计算此类型布局(各成员占空间大小) */
  return va_list_type;
}

/* 定义类型声明结点(TYPE_DECL) __builtin_va_list */
void c_common_nodes_and_builtins (void)
{
  lang_hooks.decls.pushdecl(build_decl (UNKNOWN_LOCATION,
		 TYPE_DECL, get_identifier ("__builtin_va_list"), va_list_type_node));
}

2. __builtin_va_xxx的定义

  __builtin_va_xxx系列函数包括:

__builtin_va_list
__builtin_va_start
__builtin_va_end
__builtin_va_copy
__builtin_next_arg
__builtin_saveregs

2.1 gcc中内联函数的定义

  gcc中所有的内联函数都记录在 "builtins.def"文件中,如:

./gcc/builtins.def
DEF_GCC_BUILTIN        (BUILT_IN_VA_COPY, "va_copy", BT_FN_VOID_VALIST_REF_VALIST_ARG, ATTR_NOTHROW_LEAF_LIST)
DEF_GCC_BUILTIN        (BUILT_IN_VA_END, "va_end", BT_FN_VOID_VALIST_REF, ATTR_NOTHROW_LEAF_LIST)
DEF_GCC_BUILTIN        (BUILT_IN_VA_START, "va_start", BT_FN_VOID_VALIST_REF_VAR, ATTR_NOTHROW_LEAF_LIST)
DEF_GCC_BUILTIN        (BUILT_IN_NEXT_ARG, "next_arg", BT_FN_PTR_VAR, ATTR_LEAF_LIST)
DEF_GCC_BUILTIN        (BUILT_IN_SAVEREGS, "saveregs", BT_FN_PTR_VAR, ATTR_NULL)

  在cc1初始化阶段会先为这些内联函数定义声明树节点(FUNCTION_DECL):

//toplev::main => lang_dependent_init => lang_hooks.init => c_init_decl_processing => c_common_nodes_and_builtins
// => c_define_builtins
static void
c_define_builtins (tree va_list_ref_type_node, tree va_list_arg_type_node)
{
    ......
    #define DEF_BUILTIN(ENUM, NAME, CLASS, TYPE, LIBTYPE, BOTH_P, FALLBACK_P, \
		    NONANSI_P, ATTRS, IMPLICIT, COND)			\
    if (NAME && COND)							\
    def_builtin_1 (ENUM, NAME, CLASS,                                   \
		   builtin_types[(int) TYPE],                           \
		   builtin_types[(int) LIBTYPE],                        \
		   BOTH_P, FALLBACK_P, NONANSI_P,                       \
		   built_in_attributes[(int) ATTRS], IMPLICIT);
    
    #include "builtins.def"				/* 对每个builtin函数都调用def_builtin_1 */
}

static void
def_builtin_1 (enum built_in_function fncode, const char *name, ......
{
    .......
    /* 为此builtin函数构建函数声明节点(FUNCTION_DECL) */
    decl = add_builtin_function (name, fntype, fncode, fnclass,
			       (fallback_p ? libname : NULL), fnattrs);
    .......
    /* 将函数声明结点添加到全局 visible_builtins 队列中 */
    add_builtin_function (libname, libtype, fncode, fnclass, NULL, fnattrs);
}

2.2 gcc解析源码中的内联函数

  内联函数声明结点定义后,当词法分析中发现一个内联函数名token时会将其自动转换为对应的内联函数声明树结点,此过程发生在:

toplev::main => do_compile => compile_file => c_common_parse_file => c_parser_translation_unit => c_parser_declaration_or_fndef

  细节参考[2], 这里不再做展开.

2.3 内联函数的展开

  在rtl_expand阶段, 源码中调用的所有内联函数都会被展开:

//pass_expand::execute => expand_gimple_basic_block => expand_call_stmt ... => expand_builtin
rtx expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode, int ignore)
{
    enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl);
    .......
    switch (fcode)
    {
     case BUILT_IN_VA_START:
        return expand_builtin_va_start (exp);
     case BUILT_IN_VA_END:
        return expand_builtin_va_end (exp);
     case BUILT_IN_VA_COPY:
        return expand_builtin_va_copy (exp);
     case BUILT_IN_NEXT_ARG:
        if (fold_builtin_next_arg (exp, false))
            return const0_rtx;
        return expand_builtin_next_arg ();
     case BUILT_IN_SAVEREGS:
          return expand_builtin_saveregs ();
     .......
     }
}

  需要注意的是,这里介绍的只是这些内联函数自身代码的展开,当调用某些函数时可能本身存在副总用(如BUILT_IN_VA_END会影响编译优化), 相关话题不在本文讨论范围内。

3. __builtin_va_start 的展开

  源码中调用的__builtin_va_start函数最终通过 expand_builtin_va_start 函数展开:

/*
   此函数负责展开对 __builtin_va_start 内联函数的调用,__builtin_va_start(vl, x)的作用是初始化vl结构体.
   此函数中会额外检查__builtin_va_start是否传入了>=2个参数, x是否为__builtin_va_start caller中最后一个定参.
   最终发射指令将vl结构体初始化,如下:
      vl->stack = virtual_incoming_args_rtx + cum->aapcs_stack_size * UNITS_PER_WORD;
      vl->grtop = virtual_incoming_args_rtx
      vl->vrtop = virtual_incoming_args_rtx - gr_save_area_size * UNITS_PER_WORD
      vl->groff = -gr_save_area_size
      vl->vroff = -vr_save_area_size
*/
static rtx expand_builtin_va_start (tree exp)
{
  rtx nextarg;
  tree valist;

  /* 这里检查了va_start是否传入了>=2个参数,同时也检查了第二个参数必须是此函数的最后一个定参 */
  ......
      
  /*
     返回一个rtx表达式,代表当前函数栈中可能出现的第一个匿名栈参数的位置, 即:
     virtual_incoming_args_rtx + crtl->args.arg_offset_rtx
     其中arg_offset_rtx是此函数中命名栈参数占用空间大小,在pass_expand::execute展开当前函数栈时确定的.
  */
  nextarg = expand_builtin_next_arg ();`
  
  /* 以 __builtin_va_start(vl,x)为例, 这里获取变量vl的左值结点 */
  valist = stabilize_va_list_loc (loc, CALL_EXPR_ARG (exp, 0), 1);

  /* 对于AArch64平台这里调用 aarch64_expand_builtin_va_start, 此函数中实际上没用上nextarg参数,
     此函数逻辑简单代码复杂,这里不做展开,此函数的作用是发射rtl指令初始化vl结构体, 伪代码如下:
      vl->stack = virtual_incoming_args_rtx + cum->aapcs_stack_size * UNITS_PER_WORD;
      vl->grtop = virtual_incoming_args_rtx
      vl->vrtop = virtual_incoming_args_rtx - gr_save_area_size * UNITS_PER_WORD
      vl->groff = -gr_save_area_size
      vl->vroff = -vr_save_area_size
  */
  if (targetm.expand_builtin_va_start)
    targetm.expand_builtin_va_start (valist, nextarg);
  ......

  return const0_rtx;
}

4. __builtin_va_end的展开

  __builtin_va_end(vl) 通过函数expand_builtin_va_end展开, 其作用是注销变量vl, 此后的代码中vl结构体不再可用。在expand_builtin_va_end函数中本身并没有做什么:

static rtx expand_builtin_va_end (tree exp)
{
  tree valist = CALL_EXPR_ARG (exp, 0);
  /* Evaluate for side effects, if needed.  I hate macros that don't do that.  */
  if (TREE_SIDE_EFFECTS (valist))
    expand_expr (valist, const0_rtx, VOIDmode, EXPAND_NORMAL);

  return const0_rtx;
}

  因此在未开启优化时源码中的 __builtin_va_end(vl); 语句并没有什么实质性的作用; 但当开启优化时可能导致不确定行为,大体原因可能是(这里笔者没有深入研究) __builtin_va_end(vl)会被认为是变量vl的def, 此后若再使用变量vl则会认为此时的vl与调用__builtin_va_end之前的vl无关:

//pass_dse::execute => dse_optimize_stmt => dse_classify_store => stmt_kills_ref_p
bool stmt_kills_ref_p (gimple *stmt, ao_ref *ref)
{
  ......
  if (is_gimple_call (stmt))
  {
    tree callee = gimple_call_fndecl (stmt);
    if (callee != NULL_TREE && gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
	switch (DECL_FUNCTION_CODE (callee))
	{
	  case BUILT_IN_VA_END:
	  {
	    tree ptr = gimple_call_arg (stmt, 0);
	    if (TREE_CODE (ptr) == ADDR_EXPR)
		{
		  tree base = ao_ref_base (ref);
		  if (TREE_OPERAND (ptr, 0) == base)
		    return true;
		}
	      break;
	  }
    }
  return false;
}

4. __builtin_va_copy

__builtin_va_copy(dest, src) 通过函数 expand_builtin_va_copy 展开,其作用是将src这个__va_list的各个当前值复制到 dest这个__va_list中, 细节见源码.

5 __builtin_next_arg

__builtin_next_arg 通过函数expand_builtin_next_arg 展开, 用来获取此函数第一个匿名栈参数的起始地址。

static rtx expand_builtin_next_arg (void)
{
  /* 返回 virtual_incoming_args_rtx + crtl->args.arg_offset_rtx, arg_offset_rtx 是当前函数中命名栈参数的个数. */
  return expand_binop (ptr_mode, add_optab,
		       crtl->args.internal_arg_pointer,
		       crtl->args.arg_offset_rtx,
		       NULL_RTX, 0, OPTAB_LIB_WIDEN);
}

6. __builtin_saveregs

  __builtin_saveregs通过expand_builtin_saveregs展开,aarch64不支持此函数(很少有平台支持此函数), 逻辑上此函数负责确保 varargs 都存到了栈上。

7. __builtin_va_arg

7.1 源码中的解析流程

  和其他__builtin_va_xxx函数不同, 在gcc中__builtin_va_arg实际上是一个关键字:

//./gcc/c-family/c-common.c
const struct c_common_resword c_common_reswords[] =
{  
    ......
    { "__builtin_va_arg", RID_VA_ARG,	0 },
    ......
}

1. 当源码中解析到 __builtin_va_arg 关键字时会先将其转换为一个VA_ARG_EXPR表达式:

//toplev::main => do_compile => compile_file => c_common_parse_file => c_parser_translation_unit => c_parser_declaration_or_fndef
// ... => c_parser_unary_expression => c_parser_postfix_expression
static struct c_expr
c_parser_postfix_expression (c_parser *parser)
{
    switch (c_parser_peek_token (parser)->type)
    {
     case RID_VA_ARG:
     {
        ......
	    e1 = c_parser_expr_no_commas (parser, NULL);	/* 解析如 __builtin_va_arg(vl, int) 中第一个参数vl */
	    t1 = c_parser_type_name (parser);				/* 解析第二个参数, 如类型 int */
         
        /* 构建一个 VA_ARG_EXPR 表达式, 此返回类型为 t1(如int), 参数为 e1(vl) */
		expr.value = c_build_va_arg (start_loc, e1.value, loc, groktypename (t1, &type_expr, NULL));
		......
	  }
     ......
    }
}

2. gimplify阶段会将 VA_ARG_EXPR 表达式转换为一个 函数编号为 IFN_VA_ARG 的gcall指令:

//toplev::main => do_compile => compile_file => symbol_table::finalize_compilation_unit => analyze_functions
// => cgraph_node::analyze => gimplify_function_tree => ... => gimplify_expr
enum gimplify_status
gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p,
	       bool (*gimple_test_f) (tr`ee), fallback_t fallback)
{
    switch (TREE_CODE (*expr_p))
	{
    case VA_ARG_EXPR:
	  ret = gimplify_va_arg_expr (expr_p, pre_p, post_p);
	  break;
    ......
    }
}

enum gimplify_status
gimplify_va_arg_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p ATTRIBUTE_UNUSED)
{ 
  ......
  tag = build_int_cst (build_pointer_type (type), 0);
  aptag = build_int_cst (TREE_TYPE (valist), 0);
    
  /* 构建一个CALL_EXPR, ifn为 IFN_VA_ARG, 其包含三个参数分别是 valist, tag, aptag,以 __builtin_va_arg(vl, type)为例, 其中:
     * valist是指向vl的一个指针树节点
     * tag是 type类型的一个常量结点
     * aptag是 __built_va_list类型的一个常量结点
  */
  *expr_p = build_call_expr_internal_loc (loc, IFN_VA_ARG, type, 3, valist, tag, aptag);
  .....
  return GS_OK;
}

3.  在pass_stdarg中通过平台相关函数单独处理 gcall(IFN_VA_ARG):

//toplev::main => do_compile => compile_file => symbol_table::finalize_compilation_unit => symbol_table::compile
// => expand_all_functions => cgraph_node::expand => execute_pass_list (cfun, g->get_passes ()->all_passes); => pass_stdarg
unsigned int pass_stdarg::execute (function *fun)
{
  expand_ifn_va_arg (fun);

  /* 对于可变参函数,若指定了 -fstdarg-opt优化, 则尝试优化其 va_list_gpr_size/va_list_fpr_size大小.
     对于没有调用过va_xxx的函数实际上不需要保存匿名寄存器参数.
     这里的结果只能是确定优化与不优化, 要么设置va_lsit_gpr_size=0要么保持不变.
  */
  if (flag_stdarg_opt && fun->stdarg != 0)
    optimize_va_list_gpr_fpr_size (fun);

  return 0;
}

//expand_ifn_va_arg => expand_ifn_va_arg_1
static void expand_ifn_va_arg_1 (function *fun)
{
  basic_block bb;
  gimple_stmt_iterator i;

  /* 遍历指令序列中所有 gcall(IFN_VA_ARG) */
  FOR_EACH_BB_FN (bb, fun)
    for (i = gsi_start_bb (bb); !gsi_end_p (i); gsi_next (&i))
    {
	  gimple *stmt = gsi_stmt (i);
    
	  if (!gimple_call_internal_p (stmt, IFN_VA_ARG))
	    continue;
        
      /* 在aarch64平台通过调用 aarch64_gimplify_va_arg_expr 展开对 __builtin_va_arg(vl, int)的调用 */
	  expr = targetm.gimplify_va_arg_expr (ap, type, &pre, &post);
      ......
    }
}

7.2 __builtin_va_arg的展开

  在aarch64中 __builtin_va_arg最终通过 aarch64_gimplify_va_arg_expr 函数展开, 此过程中构建了一系列表达式。源码全是表达式展开较复杂,还是结合AAPCS64[1]以一个简化的伪代码说明原理:

type va_arg (va_list ap, type)
{
      int nreg, offs;                                                                                                                                                                                     
      /* 若这是个浮点类型的参数(根据type判断) 则先尝试从VPRs 匿名浮点寄存器参数区域中获取参数, 
         在gcc源码中这里通过 aarch64_vfp_is_call_or_return_candidate 函数判断
       */
      if (type passed in simd/fp registers) 
      {
        offs = ap.__vr_offs;
        if (offs >= 0)         		   			/* 若匿名浮点寄存器参数区域已没有参数, 则直接去incoming栈区域获取下一个参数 */
          goto on_stack; 	
        
        nreg = (sizeof(type) + 15) / 16;		/* 否则看当前匿名浮点寄存器参数区域中是否能存下此类型的参数 */
        ap.__vr_offs = offs + (nreg * 16);  	/* 如果存不下则说明此参数只能是栈参数,跳转到on_stack从incoming栈获取 */ 	
        if (ap.__vr_offs > 0)		   	
          goto on_stack;
          
        return *(type *)(ap.__vr_top + offs);   /* 如果匿名浮点寄存器参数区域还有参数,则直接从此区域中获取并返回(返回"值"而不是地址) */	 
          
      } else {									/* 否则尝试从GPRs匿名通用寄存器参数区域获取参数 */
        offs = ap.__gr_offs;
        if (offs >= 0)          				/* 若匿名通用寄存器参数区域为空, 则直接去incoming栈区域获取参数 */
          goto on_stack;
          
        if (alignof(type) > 8) offs = (offs + 15) & -16; 
        nreg = (sizeof(type) + 7) / 8;           /* 计算此类型占用几个标准寄存器空间 */
        ap.__gr_offs = offs + (nreg * 8);        /* 根据类型确定匿名通用寄存器区域是否还能存下一个此类型的匿名参数 */
        if (ap.__gr_offs > 0)                    /* 如果不能(offs > 0),则去incoming栈区域获取下一个参数 */
          goto on_stack;
          
        return *(type *)(ap.__gr_top + offs);   /* 若参数在GPRs匿名通用寄存器区域,则直接返回参数值(而不是地址) */
      }
    
/* 若此匿名参数不在匿名寄存器区域则其只能保存在incoming栈中, 这里从incoming栈中获取此匿名参数 */
on_stack:
      intptr_t arg = ap.__stack;              /* 获取当前incoming栈中尚未被解析到的匿名栈参数地址 */
    
      if (alignof(type) > 8)			      /* 按需对齐 */
              arg = (arg + 15) & -16;
    
      ap.__stack = (void *)((arg + sizeof(type) + 7) & -8);		/* 修改ap.__stack指向下一个可能出现的匿名栈参数的位置 */
      return *(type *)arg;			      	  /* 返回此匿名栈参数的值 */
}

__builtin_va_arg(vl, type)获取下一个参数匿名的流程受到三个因素影响:

  1. 当前函数声明中记录的此函数命名参数个数及类型
  2. 此次调用要获取的参数的类型(type)
  3. vl结构体的内容(其中记录此前已经获取了多少个参数)

其中:

1. 函数声明:

  函数声明决定了此函数中已经有多少个命名参数,同时也影响了其不定参数的存储位置,如:

/*
   此函数中存在1个命名参数x0,按照AAPCS64标准x0是通用寄存器参数,因此此函数中:
   * 第1-7个匿名通用寄存器参数将保存在匿名通用寄存器参数区域,此区域的大小为 8 * 8 = 64 byte(aligned)
   * 第0-7个匿名浮点寄存器参数将保存在匿名浮点寄存器参数区域,此区域的大小为 8 * 16 = 128 byte;
   * 若还有其他匿名参数则一定为匿名栈参数,保存在incoming栈中,起始位置为 incoming栈首地址 + arg_offset_rtx(=0)
*/
int func1(int x0, ...) { ... }
/*
   此函数中存在9个命名参数x0-x8, 按照AAPCS64标准x0-x7是命名寄存器参数, x8为栈参数,此函数中:
   * 不存在匿名通用寄存器参数,对应区域大小为0(此时gr_top虽然指向incoming栈首地址,但 gr_offs为0).
   * 第0-7个匿名浮点寄存器参数同样保存在匿名浮点寄存器参数区域,大小为 8 * 16;
   * 若还存在其他匿名参数(不论什么类型),都一律为匿名栈参数,保存在incoming栈中. 
     此时由于incoming栈中已有一个命名栈参数x8, 故匿名栈参数起始位置(vl.__stack) 为incoming栈首地址 + arg_offset_rtx(=8)
*/
int func2(int x0, int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8, ...) { ... }

2. 参数类型(type)

  源码中使用va_arg(vl, type)时,编译器即可根据类型确定运行时应先从GPRs还是VPRs中动态获取参数. 对于如没有匿名通用寄存器参数的函数(如func2), 编译期间可直接优化为从incoming栈中获取匿名参数,如:

weak int func1(int p0, int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8, ...)
{
      __builtin_va_list vl; 
      __builtin_va_start (vl, p8);
      printf("%d\n", va_arg(vl, int));
      return 0;
}

## aarch64-linux-gnu-gcc main.c -O2 -S -o main.s
func1:
        stp     x29, x30, [sp, -48]!
        adrp    x0, .LC0
        add     x0, x0, :lo12:.LC0
        mov     x29, sp
        add     x2, sp, 56
        ldr     w1, [sp, 56]    				## 参数w1直接来自栈,因为此函数不会存在匿名通用寄存器参数
        str     x2, [sp, 16]
        add     x2, sp, 48
        stp     x2, x2, [sp, 24]
        stp     wzr, wzr, [sp, 40]
        bl      printf
        mov     w0, 0
        ldp     x29, x30, [sp], 48
        ret

3. vl结构体

  vl结构体中记录已经获取过了哪些匿名寄存器,在__builtin_va_start(vl, ...)初始化时,其记录的是第一个匿名通用寄存器/浮点寄存器/栈参数的地址, 每调用一次va_arg获取一个参数后,此结构体中对应字段会指向下一个通用寄存器/浮点寄存器/栈参数。

参考资料:

[1] 《Procedure Call Standard for the ARM 64-bit Architecturn》

[2] GCC源码分析(八) — 语法/语义分析之声明与函数定义的解析_ashimida@的博客-CSDN博客_gcc源码分析

[3] AArch64函数栈的分配,指令生成与GCC实现(上)_ashimida@的博客-CSDN博客

[4] AArch64函数栈的分配,指令生成与GCC实现(下)_ashimida@的博客-CSDN博客

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

AArch64中va_list/va_start/va_arg/...的实现 的相关文章

  • 向 list.extend() 传递不可迭代对象

    我正在创建一个公共方法来允许调用者将值写入设备 例如将其称为 write vals 由于这些值将实时输入 因此我希望通过允许用户输入列表或单个值来简化用户的生活 具体取决于他们需要写入的值的数量 例如 write to device 1 2
  • 如何访问对列表中对的每个元素?

    我有一个名为 对 的列表 pairs a 1 b 2 c 3 我可以通过以下方式访问元素 for x in pairs print x 其输出如下 a 1 b 2 c 3 但我想访问每对中的每个元素 就像在 c 中一样 如果我们使用pair
  • 如何在 Python 中从平面列表构建嵌套列表? [关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 我有一个简单的列表 例如 flat 1 1 1 1 1 1 1 2 2 2 1 2 2 3 我需要转换为嵌套列表 其中每个级别 破折号后跟数
  • 随机打乱列表[重复]

    这个问题在这里已经有答案了 可能的重复 在 C 中随机化 List https stackoverflow com questions 273313 randomize a listt in c sharp 随机播放 随机重新排列 List
  • 在 Django 中为多个查询编写视图的最佳方法?

    这是一个简单的问题 我已经组织了我的模型 以便提供给页面的大多数对象都属于一种类型 项目 该模型包含各种属性 可以帮助我以不同的方式提供服务 我有文章和视频 它们由模型上的 类型 字段确定 类型 文章 等 我有一个列表视图 它显示项目模型中
  • 计算 List 中相似的相邻项目数

    我试图在列表中找到相似的相邻项目并计算其数量 例如 List
  • for 循环中列表项未更改

    当以下代码没有达到我预期的效果时 我感到震惊 lines list this is line 1 n this is line 2 n this is line 3 n for line in lines list line line st
  • 如何从字典列表中查找键的值?

    如何从字典列表中获取给定键的值 mylist powerpoint color blue client name Sport Parents Regrouped sort order ascending chart layout 1 cha
  • 使用 Linq 返回具有最大计数的列表

    使用 C 和 Linq 如何返回具有最大大小 计数的 List 我假设您有一个名为的列表集合lists并且您想要返回此集合中元素最多的列表 如果是这样 请尝试以下操作 var listWithLargestCount lists Order
  • Python 有不可变列表吗?

    python 有不可变列表吗 假设我希望具有元素有序集合的功能 但又想保证它不会改变 如何实现呢 列表是有序的 但它们可以改变 是的 它被称为一个tuple 所以 而不是 1 2 这是一个list并且可以突变 1 2 is a tuple并
  • 我想将对象列表添加到 firestore 文档中,-flutter

    我想将对象列表添加到 firestore 文档 我定义了产品数据模型 我还有类别数据模型 我想将类别列表添加到 firestore 中的产品文档中 我将类别添加到临时列表 然后将值放入product categories 产品 类别 类别t
  • Python:两个列表之间的成对比较:列表 a >= 列表 b?

    如果我想检查列表中的所有元素 a 1 2 3 6 大于或等于另一个列表中对应的元素 b 0 2 3 5 如果 a i gt b i 对于所有i的 则返回 true 否则返回 false 这有逻辑功能吗 比如a gt b 谢谢 你可以这样做
  • Python 3 中的递归搜索 JSON/DICT

    我在 Python 3 中实现了一些 API 这些 API 允许我根据班级代码接收有关学校的信息 但我想知道如何通过类代码获取信息 例子 我输入代码GF528S我希望程序告诉我班级 3C INF 地址 Address 1 Milan 如果可
  • 如何在 Java 中获得列表的反向列表视图?

    我想在列表上有一个反向列表视图 与List sublist提供列表上的子列表视图 是否有一些函数可以提供此功能 我不想复制该列表 也不想修改该列表 在这种情况下 如果我能在列表上至少获得一个反向迭代器就足够了 另外 我知道如何自己实现这一点
  • C# 如何单击 IList 中的 IWebelement?

    所以我尝试单击 YouTube 上的按钮 但我无法通过 Xpath 找到该按钮 因为按钮太多 所以我尝试将它们保存在 IList 中 现在我想单击列表中的特定按钮 ChromeDriver chrome new ChromeDriver L
  • 使用 for 循环填充 python 字典列表

    我试图用 for 循环填充字典列表 但最终结果显示 for 循环填充的最后一个字典覆盖了所有先前字典的值 我尝试调整以下中提出的解决方案 如何使用循环填充 Python 字典 https stackoverflow com question
  • 按多个键分组并对字典列表的值进行汇总/平均值

    在Python中按多个键进行分组并对字典列表进行汇总 平均值的最Pythonic方法是什么 假设我有一个字典列表 如下所示 input dept 001 sku foo transId uniqueId1 qty 100 dept 001
  • Python 将列表追加到列表中

    我正在尝试编写一个通过矩阵的函数 当满足条件时 它会记住该位置 我从一个空列表开始 locations 当函数遍历行时 我使用以下方法附加坐标 locations append x locations append y 函数末尾的列表如下所
  • 使用 LINQ 洗牌

    我正在尝试编写一个简单的纸牌游戏 为了想出一个好的洗牌算法 我遇到了 Jeff Atwood 的post http www codinghorror com blog 2007 12 shuffling html关于恐怖编码 但是 当我在调
  • Collections.sort(list) 和 list.sort(Comparator) 之间的区别

    有什么理由让我应该选择Collections sort list 方法而不是简单地调用list sort 内部Collections sort只是调用sort的方法List无论如何 上课 令人惊讶的是几乎每个人都告诉我使用Collectio

随机推荐

  • Linux中与“内核模块”相关的数据结构

    摘要 本文详细解释了linux中与模块相关的内核数据结构 xff0c 便于大家在学习理解内核源码或驱动编程中理解相应代码和思想 三 内核模块相关的数据结构 目录 THIS MODULE宏module结构体module use 3 1 THI
  • Linux内核中与“文件系统”相关的数据结构

    文件系统相关的数据结构 4 1 file结构体 文件结构体代表一个打开的文件 xff0c 系统中的每个打开的文件在内核空间都有一个关联的struct file 它由内核在打开文件时创建 xff0c 并传递给在文件上进行操作的任何函数 在文件
  • 【可解释AI】图神经网络的可解释性方法及GNNexplainer代码示例

    图神经网络的可解释性方法及GNNexplainer代码示例 GNNExplainerIntroductionModelSingle instance explanations xff08 Explanation via Structural
  • 文本编辑器VI命令详解

    目录 一 xff1a 文本编辑器概述 1 文本编辑器含义 2 文本编辑器的作用 3 Linux中最常见的文本编辑器 二 vi编辑器的工作模式 1 vi编辑器的工作模式 2 各模式之间的切换 三 xff1a 命令模式概述 1 命令模式常用操作
  • Linux中与“内核安全”相关的数据结构

    五 内核安全相关数据结构 5 1 security operations结构体 这是一个钩子函数的指针数组 xff0c 其中每一个数组元素都是一个SELINUX安全钩子函数 xff0c 在2 6以上的内核中 xff0c 大部分涉及安全控制的
  • 洛谷 P3366 【模板】最小生成树

    题目描述 如题 xff0c 给出一个无向图 xff0c 求出最小生成树 xff0c 如果该图不连通 xff0c 则输出orz 输入输出格式 输入格式 xff1a 第一行包含两个整数N M xff0c 表示该图共有N个结点和M条无向边 xff
  • 关于网站最近出现504错误的总结,too open many files in system

    如果你有耐心看完这篇文章 xff0c 也许会给你带来真正的益处 网站出现504错误 xff0c 如果你用阿里云CDN的话还会报 504 Gateway Time out The gateway did not receive a timel
  • Manjaro21安装VNC,Win10远程连接manjaro桌面

    manjaro安装tigervnc xff0c win10使用VNC viewer TigerVNC 简体中文 ArchWiki archlinux org https wiki archlinux org title TigerVNC E
  • Proxmox虚拟环境搭建

    一 Proxmox VE简介 ProxmoxVE 是一个完整的 开源的企业虚拟化服务器管理平台 它在单个平台上紧密集成了 KVM 管理程序和 Linux 容器 LXC 软件定义的存储和网络功能 通过集成的基于 web 的用户界面 xff0c
  • HEX2DEC存储过程实现

    数据库当前有十进制转换为十六进制的函数hex 函数 xff0c 却没有十六进制转换为十进制的函数 xff0c 只能自己定义一个hex2dec xff0c 存储过程如下 xff1a span class token keyword drop
  • SQLite数据类型引起的问题——全数字字符串使用varchar出现错误

    问题 xff1a 项目中需要把某些数据保存到Android的数据库中 xff0c 因为保存的字符串全部为数字形式 xff0c SQLite把部分字符串自动转化为了科学技术法导致数据显示异常 xff0c 同时还把一些开头为0的字符串自动去掉了
  • IOS 自定义UIAlertController

    自定义UIAlertController xff1a 首先展示效果图 1 创建一个新的类来管理弹出的视图 继承于UIView 2 传建一个xib文件来自定义弹出视图 xff08 注意创建过后一定要将xib的class关联 xff09 3 在
  • python把txt文件里重复数据去重代码

    有时候会发现txt文件里有很多重复数据 xff0c 这里自写了一个去重的python程序 xff0c 供学习使用 xff01 def quchong print 39 39 50 print 39 导入txt文件中 39 num 61 0
  • ERROR 1064 (42000): You have an error in your SQL

    对于新手来说 xff0c MySQL数据库 xff0c 在命令行使用sql语句进行建库 xff0c 查库 xff0c 建表 xff0c 查表 时 xff0c MySQL 报错 xff1a ERROR 1064 42000 You have
  • 【图神经网络】GNNExplainer代码解读及其PyG实现

    GNNExplainer代码解读及其PyG实现 使用GNNExplainerGNNExplainer源码速读前向传播损失函数 基于GNNExplainer图分类解释的PyG代码示例参考资料 接上一篇博客图神经网络的可解释性方法及GNNexp
  • python之自动发送微信消息

    这篇文章主要是总结最近写自动发送微信消息的python代码时所接触的两个库 pyautogui和pyperclip的用法 在网上找了很多能实现发送微信消息的方法 xff0c 其中有使用itchat和wxpy库来实现的 xff0c 尝试过后发
  • CSU1646: HearthStone(DP)

    Description Henry十分钟爱炉石传说 Heart Stone 这款有趣的桌面卡牌游戏 我们简化的游戏规则如下 xff1a 游戏由两人对战 xff0c 出牌并尽量对对方造成最大的伤害 xff0c 一共进行 r轮 前10轮 xff
  • System.DllNotFoundException:“无法加载 DLL“XXX.dll”: 找不到指定的模块。 (异常来自 HRESULT:0x8007007E)。”

    System DllNotFoundException 无法加载 DLL XXX dll 找不到指定的模块 异常来自 HRESULT 0x8007007E 一般这种情况需要按是否安装完整了vc的运行时 xff0c 可以尝试安装 VC运行库合
  • Arch-004ArchLinux搜狗输入法安装

    搜狗输入法 1 sudo pacman Rsn fcitx im fcitx configtool 2 sudo pacman S fcitx lilydjwg git fcitx sogoupinyin 3 sudo pacman S f
  • AArch64中va_list/va_start/va_arg/...的实现

    版权声明 xff1a 本文为笔者本人 ashimida 64 的原创文章 xff0c 遵循CC 4 0 BY SA版权协议 xff0c 转载请附上原文出处链接及本声明 原文链接 xff1a https blog csdn net lidan