linux 下va_start,va_end,va_arg,va_list这些宏到底是什么?

/* author:         hjjdebug
 * date:         2023年 07月 27日 星期四 10:50:21 CST
 * descriptor:  linux 下va_start,va_end,va_arg,va_list这些宏到底是什么?
 */
#include
#include
void test_va(int num,...)
{
    va_list args; // typedef __builtin_va_list va_list;
    va_start(args,num); //#define va_start(v,l) __builtin_va_start(v,l)
    for(int i=0; i < num; i++)
        printf("%d\n", va_arg(args, int)); //#define va_arg(v,l) __builtin_va_arg(v,l)
    va_end(args); // #define va_end(v) __builtin_va_end(v)
}

// 上面的注释是通过宏展开后获得的, 我用的是ubuntu20 环境
// 所以 va_start,va_arg,va_end,及 va_list 都是__builtin_ 内置变量
// 都依赖于编译器的实现, 成了黑箱操作了.
//
// 在简易内核linux0.11 上 , va_*操作并不是黑箱操作,而是显示定义的.如下:
// va_list args: va_list被定义成char *, 即args 为一个char *
// va_start(args,para)初始化 args 为第一个参数地址+1. 下一个参数地址
// va_arg(args,type), 循环调用该宏,可依次得到传来的参数
// va_end(args), 没有操作
//
// 对于linux gcc下的黑箱操作,我们还想进一步了解一下其实现,
// 方法有2
// 1. 用gdb 跟踪看其参数(本博客进行了这一步)
// 2. 反编译为汇编代码或用gdb 汇编跟踪

int main()
{
    test_va(3,1,2,3);
    return 0;
}

/*
args 被定义成包含一个元素的结构数组.
吐槽一下,这不就是一个结构吗, 非要搞成数组形式?! 服了,是避开俗套吗?
(gdb) ptype args
  type = struct typedef __va_list_tag __va_list_tag {
      unsigned int gp_offset;
      unsigned int fp_offset;
      void *overflow_arg_area;
      void *reg_save_area;
  } [1]
(gdb) ptype va_list
  type = struct typedef __va_list_tag __va_list_tag {
      unsigned int gp_offset;
      unsigned int fp_offset;
      void *overflow_arg_area;
      void *reg_save_area;
  } [1]

我们看到, args 就是va_list类型, va_list类型就是一个元素的结构数组.

未初始化的args 是这样的.
(gdb) display args
  1: args = {{
      gp_offset = 2496,
      fp_offset = 2496,
      overflow_arg_area = 0x9c0000009c0,
      reg_save_area = 0x9c0000009c0
    }}


初始化args指向第一个参数后面, 是这样的.
(gdb) next
  1: args = {{
      gp_offset = 8,
      fp_offset = 48,
      overflow_arg_area = 0x7fffffffdcc0,
      reg_save_area = 0x7fffffffdc00
    }}


我们看看num 的地址(第一个固定参数的地址)及其附近堆栈中的数据
(gdb) p &num
  $1 = (int *) 0x7fffffffdbcc
(gdb) x/32bx 0x7fffffffdbcc
  0x7fffffffdbcc:    0x03    0x00    0x00    0x00    0xc0    0x09    0x00    0x00
  0x7fffffffdbd4:    0xc0    0x09    0x00    0x00    0xc0    0x09    0x00    0x00
  0x7fffffffdbdc:    0xc0    0x09    0x00    0x00    0x08    0x00    0x00    0x00
  0x7fffffffdbe4:    0x30    0x00    0x00    0x00    0xc0    0xdc    0xff    0xff
意外的发现,num 是对的,但其附近并没有发现推入的变参参数, 那看一下args 所指的地址: reg_save_area
(gdb) x/32bx 0x7fffffffdc00
  0x7fffffffdc00:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
  0x7fffffffdc08:    0x01    0x00    0x00    0x00    0x00    0x00    0x00    0x00
  0x7fffffffdc10:    0x02    0x00    0x00    0x00    0x00    0x00    0x00    0x00
  0x7fffffffdc18:    0x03    0x00    0x00    0x00    0x00    0x00    0x00    0x00
 我们看到第一项没有使用, 后面依次存放了1,2,3 参数, 间隔是8bytes, 于时知道了堆栈是从右向左推入参数的,
 间隔是8btes
 同时推断出 reg_save_area+gp_offset = 0x7fffffffdc00+8 = 0x7fffffffdc08 第一个参数的地址
----------------------------------------------------------------------------------------------------
我们访问va_arg(args,int), 会使args 内的gp_offset值增加8,从而指向了下一个参数
(gdb) next
  8            printf("%d\n", va_arg(args, int)); //#define va_arg(v,l) __builtin_va_arg(v,l)
  1: args = {{
      gp_offset = 16,
      fp_offset = 48,
      overflow_arg_area = 0x7fffffffdcc0,
      reg_save_area = 0x7fffffffdc00
    }}

 推断出 reg_save_area+gp_offset = 0x7fffffffdc00+16 = 0x7fffffffdc10 第二个参数的地址

----------------------------------------------------------------------------------------------------
  8            printf("%d\n", va_arg(args, int)); //#define va_arg(v,l) __builtin_va_arg(v,l)
  1: args = {{
      gp_offset = 24,
      fp_offset = 48,
      overflow_arg_area = 0x7fffffffdcc0,
      reg_save_area = 0x7fffffffdc00
    }}

 推断出 reg_save_area+gp_offset = 0x7fffffffdc00+24 = 0x7fffffffdc18 第三个参数的地址
----------------------------------------------------------------------------------------------------
  8            printf("%d\n", va_arg(args, int)); //#define va_arg(v,l) __builtin_va_arg(v,l)
  我们只有3个参数,更大的地址就不用关心了, fp_offset 我估计是最大分配了这么多,而overflow_arg_area则是超过此区域就
  溢出了,不安全了的意思吧. gcc 代码没看,这里就望文生义,不影响理解就足够了.! 可见gcc具体实现在安全性上还考虑了不少.
  1: args = {{
      gp_offset = 32,
      fp_offset = 48,
      overflow_arg_area = 0x7fffffffdcc0,
      reg_save_area = 0x7fffffffdc00
    }}
----------------------------------------------------------------------------------------------------

*/

你可能感兴趣的:(C,编程,linux,va_start,va_end,va_arg,va_list)