首页 技术 正文
技术 2022年11月16日
0 收藏 858 点赞 2,625 浏览 2340 个字

1. Linux中可变列表实现的源码分析

查看Linux0.11的内核源代码,对va_list, va_start, va_arg 的实现如下:

    1. va_list的实现没有差别,char
      typedef char
      va_list;
    2. va_start的实现

      #define va_start(AP, LASTARG)                                           \
      (__builtin_saveregs (), \
      AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))

      注:__builtin_saveregs()是gcc内置的函数,用来保存堆栈,其实好像也没什么必要这么做
      然后将AP指向可变参数前一个参数的的结尾。这里的LASTARG就是前面的param1

    3. va_arg的实现

      #define va_arg(AP, TYPE)                                                \
      (AP += __va_rounded_size (TYPE), \
      *((TYPE *) (AP - __va_rounded_size (TYPE))))

      注:这里的逻辑有点奇怪,先将指针移动到该参数的结尾,再返回指针减去该参数长度的位置内容。
      其实这是对C语言的 , 操作符的利用,用来实现类型转换。,后面的值才是返回值,所以移动指针的操作必须在前。一个很好的小技巧

    4. 辅助函数

      #define __va_rounded_size(TYPE)  \
      (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))

      注:前面的va_start和va_arg 都没有使用 sizeof,而是用的 va_rounded_size,这个是什么意思呢?其实和结构的成员对齐相似,每个参数最少占用4个字节。
      例如一个char型的参数,也要占用4个字节,4个连续的char型参数,就要占用16个字节。
      所以这里使用用 [x+ (n-1)] / n * n 的对齐公式来实现最少对齐。
      如果要取一个char型参数,要用va_arg(args, int), 而不能用va_arg(args, char),否则会产生编译警告,运行异常。

2. ARM上可变参数列表实现分析——二进制

对于pc上的可变参数列表,比较容易理解:参数全部存储在栈上。所以:va_list p定义一个指针,va_start(p, arg_a)获取参数列表地址,该地址就是va_start第二个参数对应数据之后的地址,,在栈上表现为:arg_a+sizeof(arg_a). 此后根据参数类型,使用va_arg依次从指定的参数列表地址取数据。

但时对于arm上,一个会使用寄存器传递参数的平台,又回怎样处理的呢?

通过写一个简单的示例程序:

 int test(int a, ...){
va_list p;
va_start(p, a);
printf("%d", va_arg(p, int));
va_end(p);
}

实际编译后如下:

.text:00000BE0 ; test(int, ...)
.text:00000BE0 EXPORT _Z4testiz
.text:00000BE0 _Z4testiz ; CODE XREF: main+8p
.text:00000BE0
.text:00000BE0 var_1C = -0x1C
.text:00000BE0 varg_r0 = -0x10
.text:00000BE0 varg_r1 = -0xC
.text:00000BE0 varg_r2 = -
.text:00000BE0 varg_r3 = -
.text:00000BE0
.text:00000BE0 PUSH {R0-R3}
.text:00000BE2 PUSH
{R0-R2,LR}
.text:00000BE4 LDR R0, =(unk_2168 - 0xBEE)
.text:00000BE6 ADD R3, SP, #0x20+varg_r2
.text:00000BE8 LDR R1, [SP,#0x20+varg_r1]
.text:00000BEA ADD R0, PC ; format
.text:00000BEC STR R3, [SP,#0x20+var_1C]
.text:00000BEE BLX printf
.text:00000BF2 ADD SP, SP, #0xC
.text:00000BF4 POP {R3}
.text:00000BF6 ADD SP, SP, #0x10
.text:00000BF8 BX R3
.text:00000BF8 ; End of function test(int,...)

可以看到粗体部分,进行了2次push。而有所了解得程序员,我们知道一般只会push一次,即后面的那一个——用来保存寄存器状态和返回地址。

那么第一次是干什么呢?显然这和可变参数有关!

arm上使用寄存器传递参数时一般只是用r0~r3,因此,系统直接将其压入栈中!然后和一般函数一样的方式去保护可能会被覆盖的寄存器的状态。

将r0~r3压入栈中之后,如果还有更多的参数,则之前必须(应当)已经压入栈中,那么此时的状态就和pc上的类似了。如下所示

(高地址)

arg_5

arg_4

arg_3<—r3

arg_2<—r2

arg_1<—r1

arg_0<—r0

<需要保护的寄存器值>

(低地址)


3. 可变参数列表类型的转换

在Android上, va_list可变参数列表并没有直接定义,是gcc的一个built in类型.但是实际分析中发现,其实va_list就是一个char*,因此我们只需要将其想办法进行类型转化即可.

但是实际过程中,无法通过强制类型转化完成.因此,使用内联汇编完成类型转换.如下

    va_list args;
char* argBuf; __asm__(
"mov %[argBuf], %[args]"
: [argBuf] "=r" (argBuf)
: [args] "r" (args)
:
);
相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,031
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,520
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,368
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,148
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,781
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,860